Update code
This commit is contained in:
170
test2/client/src/components/VoicePanel.jsx
Normal file
170
test2/client/src/components/VoicePanel.jsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import { Mic, MicOff, Phone, PhoneOff, Loader2, MessageSquare } from 'lucide-react';
|
||||
import { useVoiceChat } from '../hooks/useVoiceChat';
|
||||
import SubtitleDisplay from './SubtitleDisplay';
|
||||
|
||||
export default function VoicePanel({ settings, onVoiceEnd, chatHistory = [], sessionId: parentSessionId }) {
|
||||
const {
|
||||
isActive,
|
||||
isMuted,
|
||||
isConnecting,
|
||||
subtitles,
|
||||
connectionState,
|
||||
error,
|
||||
duration,
|
||||
start,
|
||||
stop,
|
||||
toggleMute,
|
||||
clearError,
|
||||
} = useVoiceChat();
|
||||
|
||||
const formatTime = (s) => {
|
||||
const m = Math.floor(s / 60);
|
||||
const sec = s % 60;
|
||||
return `${m.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const handleStart = () => {
|
||||
start({
|
||||
botName: settings.botName,
|
||||
systemRole: settings.systemRole,
|
||||
speakingStyle: settings.speakingStyle,
|
||||
modelVersion: settings.modelVersion,
|
||||
speaker: settings.speaker,
|
||||
enableWebSearch: settings.enableWebSearch,
|
||||
chatHistory: chatHistory.length > 0 ? chatHistory.slice(-10) : undefined,
|
||||
parentSessionId,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl bg-slate-800/60 border border-slate-700/50 overflow-hidden">
|
||||
{/* Status Bar */}
|
||||
<div className="px-4 py-2.5 border-b border-slate-700/40 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
isActive ? 'bg-emerald-400 animate-pulse' : isConnecting ? 'bg-amber-400 animate-pulse' : 'bg-slate-500'
|
||||
}`}
|
||||
/>
|
||||
<span className="text-xs text-slate-400">
|
||||
{isActive
|
||||
? `通话中 · ${formatTime(duration)}`
|
||||
: isConnecting
|
||||
? '正在连接...'
|
||||
: '未连接'}
|
||||
</span>
|
||||
</div>
|
||||
{isActive && (
|
||||
<div className="flex items-center gap-2 text-xs text-slate-500">
|
||||
<span>模型: {settings.modelVersion}</span>
|
||||
<span>·</span>
|
||||
<span>{settings.botName}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Visualization Area */}
|
||||
<div className="relative flex flex-col items-center justify-center py-12 px-4">
|
||||
{/* Voice Wave Animation */}
|
||||
<div className="relative w-32 h-32 mb-6">
|
||||
{/* Outer rings */}
|
||||
{isActive && !isMuted && (
|
||||
<>
|
||||
<div className="absolute inset-0 rounded-full bg-violet-500/10 animate-ping" style={{ animationDuration: '2s' }} />
|
||||
<div className="absolute inset-3 rounded-full bg-violet-500/10 animate-ping" style={{ animationDuration: '2.5s' }} />
|
||||
</>
|
||||
)}
|
||||
{/* Center circle */}
|
||||
<div
|
||||
className={`absolute inset-4 rounded-full flex items-center justify-center transition-all duration-300 ${
|
||||
isActive
|
||||
? isMuted
|
||||
? 'bg-slate-600 shadow-lg shadow-slate-600/20'
|
||||
: 'bg-gradient-to-br from-violet-500 to-indigo-600 shadow-lg shadow-violet-500/30'
|
||||
: 'bg-slate-700'
|
||||
}`}
|
||||
>
|
||||
{isConnecting ? (
|
||||
<Loader2 className="w-10 h-10 text-white animate-spin" />
|
||||
) : isActive ? (
|
||||
isMuted ? (
|
||||
<MicOff className="w-10 h-10 text-slate-300" />
|
||||
) : (
|
||||
<Mic className="w-10 h-10 text-white" />
|
||||
)
|
||||
) : (
|
||||
<Phone className="w-10 h-10 text-slate-400" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* AI Info */}
|
||||
<div className="text-center mb-6">
|
||||
<h2 className="text-lg font-semibold text-white">{settings.botName}</h2>
|
||||
<p className="text-xs text-slate-400 mt-1 max-w-sm">
|
||||
{isActive
|
||||
? isMuted
|
||||
? '麦克风已静音'
|
||||
: '正在聆听...'
|
||||
: '点击下方按钮开始语音通话'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Controls */}
|
||||
<div className="flex items-center gap-4">
|
||||
{isActive ? (
|
||||
<>
|
||||
<button
|
||||
onClick={toggleMute}
|
||||
className={`w-12 h-12 rounded-full flex items-center justify-center transition-all ${
|
||||
isMuted
|
||||
? 'bg-amber-500/20 text-amber-400 hover:bg-amber-500/30'
|
||||
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
|
||||
}`}
|
||||
title={isMuted ? '取消静音' : '静音'}
|
||||
>
|
||||
{isMuted ? <MicOff className="w-5 h-5" /> : <Mic className="w-5 h-5" />}
|
||||
</button>
|
||||
<button
|
||||
onClick={async () => {
|
||||
const result = await stop();
|
||||
if (onVoiceEnd) onVoiceEnd(result);
|
||||
}}
|
||||
className="w-14 h-14 rounded-full bg-red-500 hover:bg-red-600 text-white flex items-center justify-center transition-all shadow-lg shadow-red-500/25"
|
||||
title="结束通话并转接文字对话"
|
||||
>
|
||||
<PhoneOff className="w-6 h-6" />
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<button
|
||||
onClick={handleStart}
|
||||
disabled={isConnecting}
|
||||
className="w-14 h-14 rounded-full bg-gradient-to-br from-violet-500 to-indigo-600 hover:from-violet-600 hover:to-indigo-700 text-white flex items-center justify-center transition-all shadow-lg shadow-violet-500/25 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="开始通话"
|
||||
>
|
||||
{isConnecting ? (
|
||||
<Loader2 className="w-6 h-6 animate-spin" />
|
||||
) : (
|
||||
<Phone className="w-6 h-6" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Error */}
|
||||
{error && (
|
||||
<div className="mt-4 px-4 py-2 rounded-lg bg-red-500/10 border border-red-500/20 text-red-400 text-xs max-w-md text-center">
|
||||
{error}
|
||||
<button onClick={clearError} className="ml-2 underline hover:text-red-300">
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Subtitles */}
|
||||
<SubtitleDisplay subtitles={subtitles} isActive={isActive} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user