From 3bb0266a1917feacfca537f5e3140f51e1d0c58b Mon Sep 17 00:00:00 2001 From: jlightner Date: Sat, 4 Apr 2026 09:30:56 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Added=20personality=20weight=20slider?= =?UTF-8?q?=20(0.0=E2=80=931.0)=20to=20ChatWidget=20header=20an=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - "frontend/src/api/chat.ts" - "frontend/src/components/ChatWidget.tsx" - "frontend/src/components/ChatWidget.module.css" GSD-Task: S02/T02 --- .gsd/milestones/M023/slices/S02/S02-PLAN.md | 2 +- .../M023/slices/S02/tasks/T01-VERIFY.json | 24 ++++++ .../M023/slices/S02/tasks/T02-SUMMARY.md | 79 +++++++++++++++++++ frontend/src/api/chat.ts | 2 + frontend/src/components/ChatWidget.module.css | 62 +++++++++++++++ frontend/src/components/ChatWidget.tsx | 20 ++++- 6 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 .gsd/milestones/M023/slices/S02/tasks/T01-VERIFY.json create mode 100644 .gsd/milestones/M023/slices/S02/tasks/T02-SUMMARY.md diff --git a/.gsd/milestones/M023/slices/S02/S02-PLAN.md b/.gsd/milestones/M023/slices/S02/S02-PLAN.md index 9a714ff..bbaaed6 100644 --- a/.gsd/milestones/M023/slices/S02/S02-PLAN.md +++ b/.gsd/milestones/M023/slices/S02/S02-PLAN.md @@ -47,7 +47,7 @@ - Estimate: 1h - Files: backend/routers/chat.py, backend/chat_service.py, backend/tests/test_chat.py - Verify: cd backend && python -m pytest tests/test_chat.py -v -- [ ] **T02: Frontend — personality slider UI + API wiring in ChatWidget** — Add a personality weight slider to ChatWidget and thread it through the streamChat() API call. +- [x] **T02: Added personality weight slider (0.0–1.0) to ChatWidget header and wired it through streamChat() to the chat API** — Add a personality weight slider to ChatWidget and thread it through the streamChat() API call. ## Steps diff --git a/.gsd/milestones/M023/slices/S02/tasks/T01-VERIFY.json b/.gsd/milestones/M023/slices/S02/tasks/T01-VERIFY.json new file mode 100644 index 0000000..d536c32 --- /dev/null +++ b/.gsd/milestones/M023/slices/S02/tasks/T01-VERIFY.json @@ -0,0 +1,24 @@ +{ + "schemaVersion": 1, + "taskId": "T01", + "unitId": "M023/S02/T01", + "timestamp": 1775294915224, + "passed": false, + "discoverySource": "task-plan", + "checks": [ + { + "command": "cd backend", + "exitCode": 0, + "durationMs": 9, + "verdict": "pass" + }, + { + "command": "python -m pytest tests/test_chat.py -v", + "exitCode": 4, + "durationMs": 231, + "verdict": "fail" + } + ], + "retryAttempt": 1, + "maxRetries": 2 +} diff --git a/.gsd/milestones/M023/slices/S02/tasks/T02-SUMMARY.md b/.gsd/milestones/M023/slices/S02/tasks/T02-SUMMARY.md new file mode 100644 index 0000000..aad987b --- /dev/null +++ b/.gsd/milestones/M023/slices/S02/tasks/T02-SUMMARY.md @@ -0,0 +1,79 @@ +--- +id: T02 +parent: S02 +milestone: M023 +provides: [] +requires: [] +affects: [] +key_files: ["frontend/src/api/chat.ts", "frontend/src/components/ChatWidget.tsx", "frontend/src/components/ChatWidget.module.css"] +key_decisions: ["Slider placed below header as its own row with border-bottom separator for clean layout at 400px panel width"] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "Frontend build passes with zero errors (184 modules, 2.88s). Backend tests all pass (22/22) including T01's personality weight tests." +completed_at: 2026-04-04T09:30:50.961Z +blocker_discovered: false +--- + +# T02: Added personality weight slider (0.0–1.0) to ChatWidget header and wired it through streamChat() to the chat API + +> Added personality weight slider (0.0–1.0) to ChatWidget header and wired it through streamChat() to the chat API + +## What Happened +--- +id: T02 +parent: S02 +milestone: M023 +key_files: + - frontend/src/api/chat.ts + - frontend/src/components/ChatWidget.tsx + - frontend/src/components/ChatWidget.module.css +key_decisions: + - Slider placed below header as its own row with border-bottom separator for clean layout at 400px panel width +duration: "" +verification_result: passed +completed_at: 2026-04-04T09:30:50.962Z +blocker_discovered: false +--- + +# T02: Added personality weight slider (0.0–1.0) to ChatWidget header and wired it through streamChat() to the chat API + +**Added personality weight slider (0.0–1.0) to ChatWidget header and wired it through streamChat() to the chat API** + +## What Happened + +Added personalityWeight optional parameter to streamChat() in chat.ts, sent as personality_weight in the POST body. In ChatWidget.tsx, added slider state and a styled range input between the header and messages area, bounded by 'Encyclopedic' and 'Creator Voice' labels. The value flows through sendMessage to streamChat as the 5th argument. Added CSS for slider row with custom thumb/track styling matching the dark theme. + +## Verification + +Frontend build passes with zero errors (184 modules, 2.88s). Backend tests all pass (22/22) including T01's personality weight tests. + +## Verification Evidence + +| # | Command | Exit Code | Verdict | Duration | +|---|---------|-----------|---------|----------| +| 1 | `cd frontend && npm run build` | 0 | ✅ pass | 6500ms | +| 2 | `cd backend && python -m pytest tests/test_chat.py -v` | 0 | ✅ pass | 3800ms | + + +## Deviations + +Slice-level verification command must run from backend/ directory, not project root — the original failure was a path issue. + +## Known Issues + +None. + +## Files Created/Modified + +- `frontend/src/api/chat.ts` +- `frontend/src/components/ChatWidget.tsx` +- `frontend/src/components/ChatWidget.module.css` + + +## Deviations +Slice-level verification command must run from backend/ directory, not project root — the original failure was a path issue. + +## Known Issues +None. diff --git a/frontend/src/api/chat.ts b/frontend/src/api/chat.ts index cc75c36..fb0e319 100644 --- a/frontend/src/api/chat.ts +++ b/frontend/src/api/chat.ts @@ -42,6 +42,7 @@ export function streamChat( callbacks: ChatCallbacks, creator?: string, conversationId?: string, + personalityWeight?: number, ): AbortController { const controller = new AbortController(); @@ -67,6 +68,7 @@ export function streamChat( query, creator: creator ?? null, conversation_id: conversationId ?? null, + personality_weight: personalityWeight ?? 0, }), signal: controller.signal, }) diff --git a/frontend/src/components/ChatWidget.module.css b/frontend/src/components/ChatWidget.module.css index 3a36018..788906f 100644 --- a/frontend/src/components/ChatWidget.module.css +++ b/frontend/src/components/ChatWidget.module.css @@ -67,6 +67,68 @@ flex-shrink: 0; } +/* ── Personality slider ───────────────────────────────────── */ + +.sliderRow { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + border-bottom: 1px solid var(--color-border); + flex-shrink: 0; +} + +.sliderLabel { + font-size: 0.6875rem; + color: var(--color-text-secondary); + white-space: nowrap; + user-select: none; +} + +.slider { + flex: 1; + -webkit-appearance: none; + appearance: none; + height: 4px; + border-radius: 2px; + background: var(--color-border); + outline: none; + cursor: pointer; +} + +.slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--color-accent); + border: 2px solid var(--color-bg-surface); + box-shadow: 0 1px 4px var(--color-shadow-heavy); + cursor: pointer; + transition: transform 0.1s; +} + +.slider::-webkit-slider-thumb:hover { + transform: scale(1.15); +} + +.slider::-moz-range-thumb { + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--color-accent); + border: 2px solid var(--color-bg-surface); + box-shadow: 0 1px 4px var(--color-shadow-heavy); + cursor: pointer; +} + +.slider::-moz-range-track { + height: 4px; + border-radius: 2px; + background: var(--color-border); +} + .headerTitle { font-size: 0.875rem; font-weight: 600; diff --git a/frontend/src/components/ChatWidget.tsx b/frontend/src/components/ChatWidget.tsx index 1f45a1e..65c2fc6 100644 --- a/frontend/src/components/ChatWidget.tsx +++ b/frontend/src/components/ChatWidget.tsx @@ -121,6 +121,7 @@ export default function ChatWidget({ creatorName, techniques }: ChatWidgetProps) const [input, setInput] = useState(""); const [streaming, setStreaming] = useState(false); const [conversationId, setConversationId] = useState(undefined); + const [personalityWeight, setPersonalityWeight] = useState(0); const abortRef = useRef(null); const messagesEndRef = useRef(null); @@ -224,9 +225,10 @@ export default function ChatWidget({ creatorName, techniques }: ChatWidgetProps) }, creatorName, cid, + personalityWeight, ); }, - [messages, streaming, creatorName, conversationId], + [messages, streaming, creatorName, conversationId, personalityWeight], ); const handleSubmit = (e: React.FormEvent) => { @@ -272,6 +274,22 @@ export default function ChatWidget({ creatorName, techniques }: ChatWidgetProps) + {/* Personality slider */} +
+ Encyclopedic + setPersonalityWeight(parseFloat(e.target.value))} + aria-label="Personality weight" + /> + Creator Voice +
+ {/* Messages */}
{showSuggestions && (