init: 昔涟项目骨架
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { useEffect, useCallback } from 'react';
|
||||
import { useWebSocket } from '@/hooks/useWebSocket';
|
||||
import { useChatStore } from '@/store/chatStore';
|
||||
import { MessageList } from './MessageList';
|
||||
import { ChatInput } from './ChatInput';
|
||||
import { CyreneAvatar } from '@/components/persona/CyreneAvatar';
|
||||
import { MoodIndicator } from '@/components/persona/MoodIndicator';
|
||||
|
||||
export function ChatContainer({ sessionId }: { sessionId: string }) {
|
||||
const { messages, isTyping, addMessage, setTyping } = useChatStore();
|
||||
const { connect, sendMessage, onMessage } = useWebSocket(sessionId);
|
||||
|
||||
useEffect(() => {
|
||||
// 连接WebSocket (使用JWT token)
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) connect(token);
|
||||
|
||||
// 监听回复
|
||||
onMessage('onResponse', (msg) => {
|
||||
addMessage({
|
||||
id: msg.message_id,
|
||||
role: 'assistant',
|
||||
content: msg.text || '',
|
||||
audioUrl: msg.full_audio_url,
|
||||
segments: msg.segments?.map(s => ({
|
||||
index: s.index,
|
||||
text: s.text,
|
||||
audioUrl: s.audio_url,
|
||||
})),
|
||||
timestamp: msg.timestamp,
|
||||
isStreaming: false,
|
||||
});
|
||||
setTyping(false);
|
||||
});
|
||||
|
||||
onMessage('onError', (msg) => {
|
||||
addMessage({
|
||||
id: msg.message_id,
|
||||
role: 'assistant',
|
||||
content: '啊……不好意思,人家刚才走神了。能再说一遍吗?♪',
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
setTyping(false);
|
||||
});
|
||||
}, [sessionId]);
|
||||
|
||||
const handleSend = useCallback((content: string, mode: string) => {
|
||||
// 添加用户消息
|
||||
addMessage({
|
||||
id: `user-${Date.now()}`,
|
||||
role: 'user',
|
||||
content,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
setTyping(true);
|
||||
sendMessage(content, mode);
|
||||
}, [sendMessage, addMessage, setTyping]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen bg-[#FFFAF5] dark:bg-[#1a1a2e]">
|
||||
{/* 顶部栏 */}
|
||||
<header className="flex items-center justify-between px-6 py-3 border-b border-pink-100 dark:border-pink-900">
|
||||
<div className="flex items-center gap-3">
|
||||
<CyreneAvatar size="sm" />
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold text-pink-600">昔涟</h1>
|
||||
<MoodIndicator />
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-sm text-gray-400">🌸 永远在你身边</span>
|
||||
</header>
|
||||
|
||||
{/* 消息列表 */}
|
||||
<MessageList messages={messages} isTyping={isTyping} />
|
||||
|
||||
{/* 输入区域 */}
|
||||
<ChatInput onSend={handleSend} disabled={isTyping} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { CyreneAvatar } from '@/components/persona/CyreneAvatar';
|
||||
|
||||
interface MessageBubbleProps {
|
||||
role: 'user' | 'assistant';
|
||||
content: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export function MessageBubble({ role, content, timestamp }: MessageBubbleProps) {
|
||||
if (role === 'user') {
|
||||
return (
|
||||
<div className="flex justify-end px-4 py-2">
|
||||
<div className="max-w-[70%] bg-pink-400 text-white rounded-2xl rounded-br-md px-4 py-2 shadow-sm">
|
||||
<p className="text-sm leading-relaxed">{content}</p>
|
||||
<span className="text-xs text-pink-100 mt-1 block">
|
||||
{new Date(timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex px-4 py-2 gap-3">
|
||||
<CyreneAvatar size="sm" className="flex-shrink-0 mt-1" />
|
||||
<div className="max-w-[70%]">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-2xl rounded-bl-md px-4 py-2 shadow-sm border border-pink-100 dark:border-pink-900">
|
||||
<p className="text-sm leading-relaxed text-gray-700 dark:text-gray-200">
|
||||
{content}
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs text-gray-400 mt-1 block ml-1">
|
||||
{new Date(timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user