feat: Added personality weight slider (0.0–1.0) to ChatWidget header an…

- "frontend/src/api/chat.ts"
- "frontend/src/components/ChatWidget.tsx"
- "frontend/src/components/ChatWidget.module.css"

GSD-Task: S02/T02
This commit is contained in:
jlightner 2026-04-04 09:30:56 +00:00
parent d1efdbb3fa
commit 3bb0266a19
6 changed files with 187 additions and 2 deletions

View file

@ -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.01.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

View file

@ -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
}

View file

@ -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.01.0) to ChatWidget header and wired it through streamChat() to the chat API
> Added personality weight slider (0.01.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.01.0) to ChatWidget header and wired it through streamChat() to the chat API
**Added personality weight slider (0.01.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.

View file

@ -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,
})

View file

@ -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;

View file

@ -121,6 +121,7 @@ export default function ChatWidget({ creatorName, techniques }: ChatWidgetProps)
const [input, setInput] = useState("");
const [streaming, setStreaming] = useState(false);
const [conversationId, setConversationId] = useState<string | undefined>(undefined);
const [personalityWeight, setPersonalityWeight] = useState(0);
const abortRef = useRef<AbortController | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(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)
</button>
</div>
{/* Personality slider */}
<div className={styles.sliderRow}>
<span className={styles.sliderLabel}>Encyclopedic</span>
<input
type="range"
className={styles.slider}
min={0}
max={1}
step={0.1}
value={personalityWeight}
onChange={(e) => setPersonalityWeight(parseFloat(e.target.value))}
aria-label="Personality weight"
/>
<span className={styles.sliderLabel}>Creator Voice</span>
</div>
{/* Messages */}
<div className={styles.messages}>
{showSuggestions && (