6.8 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 42-wallpapers-social-format-conversion-voice | 04 | execute | 2 |
|
|
true |
|
|
Purpose: Fulfills VOICE-01..03 by ensuring the existing voice pipeline components are properly wired and the offline capability is surfaced in the UI. Output: New useSystemProviders hook, updated ChatInput with offline badge.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/42-wallpapers-social-format-conversion-voice/42-RESEARCH.md @.planning/phases/42-wallpapers-social-format-conversion-voice/42-UI-SPEC.md@ui/src/components/ChatInput.tsx @ui/src/components/VoiceMicButton.tsx @ui/src/components/VoiceModeToggle.tsx @server/src/routes/hardware.ts
From server/src/routes/hardware.ts: ```typescript router.get("/system/providers", async (_req, res) => { // Returns { whisperAvailable: boolean, piperAvailable: boolean, ... } }); ```From ui/src/components/ChatInput.tsx:
// Props include enableVoiceInput?: boolean
// VoiceMicButton already renders when enableVoiceInput=true
// VoiceModeToggle already renders when enableVoiceInput=true
Task 1: Create useSystemProviders hook
ui/src/hooks/useSystemProviders.ts
ui/src/api/hardware.ts, ui/src/hooks/useContentJob.ts, server/src/routes/hardware.ts
Create ui/src/hooks/useSystemProviders.ts:
-
Export interface SystemProviders { whisperAvailable: boolean; piperAvailable: boolean } (match server response shape)
-
Export function useSystemProviders():
- Use useState for providers (SystemProviders | null, initially null)
- Use useEffect to fetch GET /api/system/providers on mount (one-time fetch)
- Use the same fetch/API client pattern used in existing hooks (check useContentJob or api/hardware.ts for the project's fetch pattern — likely uses the api client from ui/src/api/client.ts)
- Return { providers, loading: providers === null }
- On error: return null providers, do not throw (graceful degradation — badge simply won't show)
This is a simple data-fetching hook. Do NOT over-engineer with caching or SWR. cd /opt/nexus/ui && npx tsc --noEmit 2>&1 | head -20 <acceptance_criteria> - grep "useSystemProviders" ui/src/hooks/useSystemProviders.ts - grep "whisperAvailable" ui/src/hooks/useSystemProviders.ts - grep "system/providers" ui/src/hooks/useSystemProviders.ts </acceptance_criteria> useSystemProviders hook fetches /api/system/providers and exposes whisperAvailable boolean.
Task 2: Add offline badge next to VoiceMicButton in ChatInput ui/src/components/ChatInput.tsx ui/src/components/ChatInput.tsx, ui/src/components/VoiceMicButton.tsx Update ChatInput.tsx to show an "Offline" badge when Whisper is locally available:-
Import useSystemProviders from "../hooks/useSystemProviders"
-
Import WifiOff from lucide-react (icon library per UI spec)
-
Inside the ChatInput component, call useSystemProviders() to get { providers }
-
Next to the VoiceMicButton (in the same flex container where it renders when enableVoiceInput=true), add the offline badge:
{enableVoiceInput && providers?.whisperAvailable && ( <span className="text-xs text-muted-foreground flex items-center gap-1" aria-label="Voice input is offline (local model)" > <WifiOff className="h-3 w-3" /> Offline </span> )} -
The badge renders ONLY when:
- enableVoiceInput is true (voice mode is active)
- providers?.whisperAvailable is true (local Whisper binary detected)
-
Verify that VoiceMicButton and VoiceModeToggle are already rendering correctly:
- VoiceMicButton shows when enableVoiceInput=true
- VoiceModeToggle shows when enableVoiceInput=true
- If either is NOT rendering, wire them following the existing pattern
Do NOT change VoiceMicButton or VoiceModeToggle components — they are already correct from Phase 37.
Per Pitfall 7 from research: badge shows when whisperAvailable===true (binary detected), NOT based on an env var. cd /opt/nexus/ui && npx tsc --noEmit 2>&1 | head -20 <acceptance_criteria> - grep "useSystemProviders" ui/src/components/ChatInput.tsx - grep "WifiOff" ui/src/components/ChatInput.tsx - grep "Offline" ui/src/components/ChatInput.tsx - grep "whisperAvailable" ui/src/components/ChatInput.tsx - grep "aria-label" ui/src/components/ChatInput.tsx </acceptance_criteria> Offline badge shows next to VoiceMicButton when local Whisper is detected. Existing voice components verified working.
- `cd /opt/nexus/ui && npx tsc --noEmit` passes - Offline badge only renders when whisperAvailable is true - Voice mic button and mode toggle render when enableVoiceInput=true<success_criteria>
- useSystemProviders hook fetches whisperAvailable from /api/system/providers
- Offline badge renders with WifiOff icon and correct aria-label
- Existing VoiceMicButton/VoiceModeToggle integration unchanged and working
- tsc compiles cleanly </success_criteria>