Skip to content

feat(frontend): add voice assistant flows - #132

Merged
gac0812 merged 3 commits into
1024XEngineer:MVPfrom
gac0812:codex/frontend-voice-assistant
Aug 1, 2026
Merged

feat(frontend): add voice assistant flows#132
gac0812 merged 3 commits into
1024XEngineer:MVPfrom
gac0812:codex/frontend-voice-assistant

Conversation

@gac0812

@gac0812 gac0812 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add the assistant dock and full in-app chat sheet
  • implement press-and-hold voice capture, release-to-send, and slide-up cancellation feedback
  • stream recorded audio through the WebSocket voice protocol
  • render parsed schedule drafts and support confirm, edit, and cancel actions

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

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread frontend/src/dev/fakes/FakeWsServer.ts Outdated

const parseResult: VoiceParseResultMessage = {
type: 'voice.parse.result',
request_id: message.request_id,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@gac0812
gac0812 merged commit 964c7d7 into 1024XEngineer:MVP Aug 1, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant