Update code
This commit is contained in:
175
test2/client/src/App.jsx
Normal file
175
test2/client/src/App.jsx
Normal file
@@ -0,0 +1,175 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { Settings2, Zap, Mic, MessageSquare } from 'lucide-react';
|
||||
import VoicePanel from './components/VoicePanel';
|
||||
import ChatPanel from './components/ChatPanel';
|
||||
import SettingsPanel from './components/SettingsPanel';
|
||||
import { getVoiceConfig } from './services/voiceApi';
|
||||
|
||||
export default function App() {
|
||||
const [showSettings, setShowSettings] = 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({
|
||||
botName: '小智',
|
||||
systemRole: '你是一个友善的智能助手,名叫小智。你擅长帮用户解答各类问题。',
|
||||
speakingStyle: '请使用温和、清晰的口吻。',
|
||||
modelVersion: '1.2.1.0',
|
||||
speaker: 'zh_female_vv_jupiter_bigtts',
|
||||
enableWebSearch: false,
|
||||
});
|
||||
|
||||
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]);
|
||||
|
||||
// 直接进入文字模式(新会话)
|
||||
const handleStartChat = useCallback(() => {
|
||||
const newSid = `chat_${Date.now().toString(36)}`;
|
||||
setCurrentSessionId(newSid);
|
||||
setHandoff({
|
||||
sessionId: newSid,
|
||||
subtitles: [],
|
||||
});
|
||||
setMode('chat');
|
||||
console.log(`[App] New chat session: ${newSid}`);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
|
||||
{/* Header */}
|
||||
<header className="border-b border-slate-700/50 bg-slate-900/80 backdrop-blur-sm sticky top-0 z-30">
|
||||
<div className="max-w-5xl mx-auto px-3 sm:px-4 h-14 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-violet-500 to-indigo-600 flex items-center justify-center">
|
||||
<Zap className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-sm font-semibold text-white leading-tight">
|
||||
{mode === 'voice' ? '语音通话' : '文字对话'}
|
||||
</h1>
|
||||
<p className="text-[11px] text-slate-400 leading-tight">
|
||||
{mode === 'voice'
|
||||
? '混合编排模式 · OutputMode=1'
|
||||
: handoff?.subtitles?.length > 0
|
||||
? '语音转接 · 上下文已延续'
|
||||
: '方舟 LLM · Function Calling'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{/* Mode toggle buttons */}
|
||||
<div className="flex items-center bg-slate-800/60 rounded-lg border border-slate-700/40 p-0.5 mr-2">
|
||||
<button
|
||||
onClick={() => { if (mode !== 'voice') handleBackToVoice(); }}
|
||||
className={`flex items-center gap-1 px-2.5 py-1 rounded-md text-xs transition-all ${
|
||||
mode === 'voice'
|
||||
? 'bg-violet-500/20 text-violet-300 font-medium'
|
||||
: 'text-slate-500 hover:text-slate-300'
|
||||
}`}
|
||||
>
|
||||
<Mic className="w-3 h-3" /> 语音
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { if (mode !== 'chat') handleStartChat(); }}
|
||||
className={`flex items-center gap-1 px-2.5 py-1 rounded-md text-xs transition-all ${
|
||||
mode === 'chat'
|
||||
? 'bg-violet-500/20 text-violet-300 font-medium'
|
||||
: 'text-slate-500 hover:text-slate-300'
|
||||
}`}
|
||||
>
|
||||
<MessageSquare className="w-3 h-3" /> 文字
|
||||
</button>
|
||||
</div>
|
||||
{mode === 'voice' && (
|
||||
<button
|
||||
onClick={() => setShowSettings(!showSettings)}
|
||||
className="p-2 rounded-lg hover:bg-slate-700/50 text-slate-400 hover:text-white transition-colors"
|
||||
title="语音设置"
|
||||
>
|
||||
<Settings2 className="w-5 h-5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="max-w-5xl mx-auto px-4 py-6">
|
||||
{/* Settings Panel */}
|
||||
{showSettings && mode === 'voice' && (
|
||||
<SettingsPanel
|
||||
settings={settings}
|
||||
onChange={setSettings}
|
||||
voiceConfig={voiceConfig}
|
||||
onClose={() => setShowSettings(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{mode === 'voice' ? (
|
||||
<>
|
||||
{/* Voice Panel */}
|
||||
<VoicePanel settings={settings} onVoiceEnd={handleVoiceEnd} chatHistory={chatMessages} sessionId={currentSessionId} />
|
||||
|
||||
{/* Architecture Info */}
|
||||
<div className="mt-6 p-4 rounded-xl bg-slate-800/40 border border-slate-700/40">
|
||||
<h3 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">方案B 混合编排架构</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 text-xs">
|
||||
<div className="p-3 rounded-lg bg-slate-700/30 border border-slate-600/30">
|
||||
<div className="text-emerald-400 font-medium mb-1">闲聊场景</div>
|
||||
<div className="text-slate-400">端到端模型直接回复 · ~300-800ms</div>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg bg-slate-700/30 border border-slate-600/30">
|
||||
<div className="text-amber-400 font-medium mb-1">工具调用场景</div>
|
||||
<div className="text-slate-400">LLM 决策 + Function Calling · ~1-2s</div>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg bg-slate-700/30 border border-slate-600/30">
|
||||
<div className="text-violet-400 font-medium mb-1">自动切换</div>
|
||||
<div className="text-slate-400">系统自动判断走 S2S 或 LLM 分支</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
/* Chat Panel */
|
||||
handoff && (
|
||||
<ChatPanel
|
||||
sessionId={handoff.sessionId}
|
||||
voiceSubtitles={handoff.subtitles}
|
||||
settings={settings}
|
||||
onBack={handleBackToVoice}
|
||||
onMessagesChange={setChatMessages}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user