feat(frontend): add voice assistant flows - #132
Conversation
There was a problem hiding this comment.
Review summary
The new frontend stack is not mounted by the application entry point: frontend/App.tsx still renders only HomeScreen, and the production source has no caller for SessionProvider, ScheduleScreen, AssistantDock, AssistantChatSheet, or useAssistantSession. As submitted, none of the schedule or voice-assistant flows are reachable. Please wire the providers and feature screens into the app composition root.
Validation: npm run check passes (lint, formatting, typecheck, and 173 tests).
| () => (options.client ? new WsVoiceStreamPort(options.client) : null), | ||
| [options.client], | ||
| ); | ||
| const recorder = useMemo(() => options.recorder ?? unavailableRecorder, [options.recorder]); |
There was a problem hiding this comment.
[P1] Provide a production recorder before exposing this flow. unavailableRecorder is currently the only non-test VoiceRecorder implementation in the repository; there is no microphone adapter, audio dependency, or recording permission setup. Consequently, every device long-press will open a voice stream and then reject with 录音适配器未注入 instead of capturing audio.
There was a problem hiding this comment.
Fixed in 90fa3fe. Removed the unavailable default recorder and made a production PCM VoiceRecorder a required host injection, so this branch cannot expose the recording flow without a concrete platform adapter. The native adapter remains the responsibility of the dependent native-integration PR #136. All 178 tests pass.
|
|
||
| const parseResult: VoiceParseResultMessage = { | ||
| type: 'voice.parse.result', | ||
| request_id: message.request_id, |
There was a problem hiding this comment.
[P1] Correlate this result to the start request, not the end request. WsVoiceStreamPort.end() filters voice.parse.result using the resultRequestId returned by start(), which matches the backend's stored stream request id. Fake mode sends the end command's id here, so the client ignores the result and waits until the 20-second parse timeout. Store the start request id with each fake stream/job and reuse it here.
| const scheduleId = response.payload.schedule_id; | ||
|
|
||
| const offsetMinutes = draft.time_remind_offset_minutes ?? 0; | ||
| const syncedSystemScheduleRefId = await this.alarm.syncForSchedule({ |
There was a problem hiding this comment.
[P2] Avoid reporting a failed save after the server has already committed it. The upsert succeeds before this local alarm call; if the adapter rejects, the method exits without updating the cache, so the UI says the save failed while the backend contains the new/edited schedule. The same ordering affects toggle and delete. Preserve or resync the confirmed server state and report alarm-sync failure separately, or add explicit compensation.
There was a problem hiding this comment.
Fixed in 90fa3fe. Save, toggle, and delete now update the cache immediately after server confirmation, preserve the prior alarm reference until local sync succeeds, and raise a distinct partial-success error when only alarm sync fails. Added regression tests for all three mutations. All 178 tests pass.
| latitude: 31.236305, | ||
| longitude: 121.480237, | ||
| }; | ||
| const initialJson = JSON.stringify(initialLocation); |
There was a problem hiding this comment.
[P2] Escape values for an inline-script context. JSON.stringify leaves </script> intact, and initialLocation.address can originate from Baidu search/reverse-geocode data before this JSON is interpolated into the document's <script> block. A crafted address can terminate the script and execute arbitrary JavaScript in the WebView. Encode < (for example as \u003c) or transfer the initial value outside the inline script.
There was a problem hiding this comment.
Fixed in 90fa3fe. Initial location and center data now use HTML-safe inline-script serialization for <, >, &, U+2028, and U+2029, with a closing-script regression test. All 178 tests pass.
Summary
Why
The assistant flow needs a feature-owned state machine rather than screen-level callbacks. This keeps recording, upload, parse progress, message state, and confirmation behavior together while exposing a small integration API to the app shell.
Dependency