import { useState, useEffect, useCallback, useRef } from 'react'; import { Settings2, Zap, Mic, MessageSquare, History, Plus, Film } from 'lucide-react'; import VoicePanel from './components/VoicePanel'; import ChatPanel from './components/ChatPanel'; import SettingsPanel from './components/SettingsPanel'; import { getVoiceConfig } from './services/voiceApi'; import SessionHistoryPanel from './components/SessionHistoryPanel'; import VideoPanel from './components/VideoPanel'; export default function App() { const [showSettings, setShowSettings] = useState(false); const [showHistory, setShowHistory] = useState(false); const [voiceConfig, setVoiceConfig] = useState(null); // 'voice' | 'chat' const [mode, setMode] = useState('voice'); // 统一会话 ID(贯穿语音和文字模式,数据库用此 ID 关联所有消息) const [currentSessionId, setCurrentSessionId] = useState(null); // 语音转文字的交接数据(保留兼容) const [handoff, setHandoff] = useState(null); // 文字聊天消息(用于切回语音时注入上下文) const [chatMessages, setChatMessages] = useState([]); const [settings, setSettings] = useState({ modelVersion: 'O', speaker: 'zh_female_vv_jupiter_bigtts', enableWebSearch: false, }); // 文字对话引擎: 'coze' (原方舟 HTTP/SSE) | 's2s' (S2S WebSocket, event 501) const [textEngine, setTextEngine] = useState(() => { try { return localStorage.getItem('bigwo_text_engine') || 'coze'; } catch (_) { return 'coze'; } }); const toggleTextEngine = useCallback(() => { setTextEngine((prev) => { const next = prev === 'coze' ? 's2s' : 'coze'; try { localStorage.setItem('bigwo_text_engine', next); } catch (_) {} return next; }); }, []); useEffect(() => { getVoiceConfig() .then(setVoiceConfig) .catch((err) => console.warn('Failed to load config:', err)); }, []); // 语音通话结束后,无缝切换到文字对话(携带同一个 sessionId) const handleVoiceEnd = useCallback((data) => { if (data?.sessionId) { const sid = data.sessionId; setCurrentSessionId(sid); setHandoff({ sessionId: sid, subtitles: data.subtitles || [], }); setMode('chat'); console.log(`[App] Voice→Chat, sessionId=${sid}`); } }, []); // 从文字模式返回语音模式(使用同一个 sessionId,数据库已有完整历史) const handleBackToVoice = useCallback(() => { console.log(`[App] Chat→Voice, sessionId=${currentSessionId}`); setMode('voice'); }, [currentSessionId]); // 切换到文字模式(复用已有 sessionId,没有时新建) const handleStartChat = useCallback(() => { const sid = currentSessionId || `chat_${Date.now().toString(36)}`; setCurrentSessionId(sid); setHandoff({ sessionId: sid, subtitles: [], }); setMode('chat'); console.log(`[App] Switch to chat, sessionId=${sid}`); }, [currentSessionId]); // 语音会话创建时同步 sessionId 到 App 状态 const handleSessionCreated = useCallback((sessionId) => { if (sessionId && sessionId !== currentSessionId) { setCurrentSessionId(sessionId); console.log(`[App] Voice session synced: ${sessionId}`); } }, [currentSessionId]); // 新建会话:重置所有状态 const handleNewSession = useCallback(() => { setCurrentSessionId(null); setHandoff(null); setChatMessages([]); setMode('voice'); console.log('[App] New session created'); }, []); // 从历史记录中选择会话 const handleSelectSession = useCallback((session) => { const sid = session.id; setCurrentSessionId(sid); setChatMessages([]); // 根据会话最后的模式决定打开方式,默认用文字模式查看历史 setHandoff({ sessionId: sid, subtitles: [], }); setMode('chat'); console.log(`[App] Selected session: ${sid}, mode: ${session.mode}`); }, []); return (
{/* Header */}

{mode === 'voice' ? '语音通话' : mode === 'chat' ? '文字对话' : 'AI 视频'}

{mode === 'voice' ? '直连 S2S 语音 · ChatTTSText' : mode === 'chat' ? (textEngine === 's2s' ? 'S2S 文字 · event 501 ChatTextQuery' : (handoff?.subtitles?.length > 0 ? '语音转接 · 上下文已延续' : '方舟 LLM · Function Calling')) : 'Seedance · AI 视频生成'}

{/* History button */} {/* New session button */} {/* Mode toggle buttons */}
{mode === 'chat' && ( )} {mode === 'voice' && ( )}
{/* Settings Panel */} {showSettings && mode === 'voice' && ( setShowSettings(false)} /> )} {mode === 'voice' ? ( <> {/* Voice Panel */} {/* Architecture Info */}

RTC 直路由语音架构

上行链路
浏览器 RTC 麦克风 → 房间字幕/消息 → 后端前置路由
应答链路
知识库/工具结果 → ExternalTextToSpeech → 语音播报
当前目标
彻底绕开原生链纯 S2S 抢答,保证知识库结果能播报
) : mode === 'chat' ? ( /* Chat Panel */ handoff && ( ) ) : ( /* Video Panel */ )}
{/* Session History Sidebar */} {showHistory && ( setShowHistory(false)} /> )}
); }