From 05d231695c248634477f2d161cb7c59f06e3ca2d Mon Sep 17 00:00:00 2001 From: mac Date: Fri, 31 Jul 2026 18:59:00 +0800 Subject: [PATCH 1/2] feat(frontend): add voice assistant flows --- .../components/AssistantChatSheet.test.tsx | 77 ++++++ .../components/AssistantDock.test.tsx | 31 +++ .../components/AssistantDraftCard.test.tsx | 49 ++++ .../components/TempoAssistantIcon.test.tsx | 13 + .../components/VoiceHoldButton.test.tsx | 71 +++++ .../hooks/useAssistantSession.test.tsx | 250 +++++++++++++++++ .../components/AssistantChatSheet.styles.ts | 95 +++++++ .../components/AssistantChatSheet.tsx | 133 +++++++++ .../components/AssistantDock.styles.ts | 20 ++ .../assistant/components/AssistantDock.tsx | 32 +++ .../components/AssistantDraftCard.styles.ts | 134 +++++++++ .../components/AssistantDraftCard.tsx | 93 +++++++ .../components/TempoAssistantIcon.tsx | 24 ++ .../components/VoiceHoldButton.styles.ts | 54 ++++ .../assistant/components/VoiceHoldButton.tsx | 177 ++++++++++++ .../assistant/data/VoiceStreamPort.ts | 154 +++++++++++ .../assistant/hooks/useAssistantSession.ts | 256 ++++++++++++++++++ frontend/src/features/assistant/index.ts | 5 + frontend/src/features/assistant/types.ts | 31 +++ 19 files changed, 1699 insertions(+) create mode 100644 frontend/__tests__/features/assistant/components/AssistantChatSheet.test.tsx create mode 100644 frontend/__tests__/features/assistant/components/AssistantDock.test.tsx create mode 100644 frontend/__tests__/features/assistant/components/AssistantDraftCard.test.tsx create mode 100644 frontend/__tests__/features/assistant/components/TempoAssistantIcon.test.tsx create mode 100644 frontend/__tests__/features/assistant/components/VoiceHoldButton.test.tsx create mode 100644 frontend/__tests__/features/assistant/hooks/useAssistantSession.test.tsx create mode 100644 frontend/src/features/assistant/components/AssistantChatSheet.styles.ts create mode 100644 frontend/src/features/assistant/components/AssistantChatSheet.tsx create mode 100644 frontend/src/features/assistant/components/AssistantDock.styles.ts create mode 100644 frontend/src/features/assistant/components/AssistantDock.tsx create mode 100644 frontend/src/features/assistant/components/AssistantDraftCard.styles.ts create mode 100644 frontend/src/features/assistant/components/AssistantDraftCard.tsx create mode 100644 frontend/src/features/assistant/components/TempoAssistantIcon.tsx create mode 100644 frontend/src/features/assistant/components/VoiceHoldButton.styles.ts create mode 100644 frontend/src/features/assistant/components/VoiceHoldButton.tsx create mode 100644 frontend/src/features/assistant/data/VoiceStreamPort.ts create mode 100644 frontend/src/features/assistant/hooks/useAssistantSession.ts create mode 100644 frontend/src/features/assistant/index.ts create mode 100644 frontend/src/features/assistant/types.ts diff --git a/frontend/__tests__/features/assistant/components/AssistantChatSheet.test.tsx b/frontend/__tests__/features/assistant/components/AssistantChatSheet.test.tsx new file mode 100644 index 0000000..50ae6d7 --- /dev/null +++ b/frontend/__tests__/features/assistant/components/AssistantChatSheet.test.tsx @@ -0,0 +1,77 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react-native'; + +import { AssistantChatSheet } from '@/features/assistant/components/AssistantChatSheet'; + +describe('AssistantChatSheet', () => { + it('shows empty state and closes', () => { + const onClose = jest.fn(); + render( + , + ); + expect(screen.getByText('等你说第一句话')).toBeTruthy(); + fireEvent.press(screen.getAllByLabelText('关闭语音助手')[0]!); + expect(onClose).toHaveBeenCalled(); + }); + + it('renders user, draft and assistant messages', () => { + const onAction = jest.fn(); + render( + , + ); + expect(screen.getByText('明天下午开会')).toBeTruthy(); + expect(screen.getByText('开会')).toBeTruthy(); + expect(screen.getByText('已记下')).toBeTruthy(); + fireEvent.press(screen.getByLabelText('加入')); + expect(onAction).toHaveBeenCalledWith('d1', { id: 'ok', kind: 'confirm', label: '加入' }); + }); + + it('hides when not visible', () => { + render( + , + ); + expect(screen.queryByText('语音助手')).toBeNull(); + }); + + it('shows a processing state after recording is released', () => { + render( + , + ); + expect(screen.getByText('正在整理录音…')).toBeTruthy(); + }); +}); diff --git a/frontend/__tests__/features/assistant/components/AssistantDock.test.tsx b/frontend/__tests__/features/assistant/components/AssistantDock.test.tsx new file mode 100644 index 0000000..553fe6c --- /dev/null +++ b/frontend/__tests__/features/assistant/components/AssistantDock.test.tsx @@ -0,0 +1,31 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react-native'; + +import { AssistantDock } from '@/features/assistant/components/AssistantDock'; + +jest.mock('@/features/assistant/components/VoiceHoldButton', () => ({ + VoiceHoldButton: ({ onPress }: { onPress?: () => void }) => { + const { Pressable, Text } = require('react-native'); + return ( + + voice-hold + + ); + }, +})); + +describe('AssistantDock', () => { + it('hides when requested', () => { + const { queryByText } = render( +