import React, { useState, useEffect, useRef } from "react"; import { motion, AnimatePresence } from "motion/react"; import { Send, Trash2, Flame, Sparkles, MessageSquare, Mic, Square, Play, Volume2, Loader2 } from "lucide-react"; import { chatWithSpaceModel, generateSpeech, ChatMode, Message } from "../services/hfSpaceService"; export default function Chat() { const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); const [mode, setMode] = useState(ChatMode.VENTING); const [isLoading, setIsLoading] = useState(false); const [showSwitchPrompt, setShowSwitchPrompt] = useState(false); const [isRecording, setIsRecording] = useState(false); const [recordedAudio, setRecordedAudio] = useState(null); const [mediaRecorder, setMediaRecorder] = useState(null); const [generatingSpeech, setGeneratingSpeech] = useState(null); const scrollRef = useRef(null); // Auto-scroll to bottom useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }, [messages, isLoading]); const startRecording = async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const recorder = new MediaRecorder(stream); const chunks: Blob[] = []; recorder.ondataavailable = (e) => chunks.push(e.data); recorder.onstop = async () => { const blob = new Blob(chunks, { type: "audio/webm" }); const reader = new FileReader(); reader.readAsDataURL(blob); reader.onloadend = () => { const base64 = (reader.result as string).split(",")[1]; setRecordedAudio(base64); }; stream.getTracks().forEach(track => track.stop()); }; recorder.start(); setMediaRecorder(recorder); setIsRecording(true); } catch (err) { console.error("Error accessing microphone:", err); alert("无法访问麦克风,请检查权限。"); } }; const stopRecording = () => { if (mediaRecorder) { mediaRecorder.stop(); setIsRecording(false); } }; const playAudio = (base64Wav: string) => { const audio = new Audio(`data:audio/wav;base64,${base64Wav}`); return audio.play(); }; const handleGenerateSpeech = async (message: Message) => { if (message.aiAudio) { playAudio(message.aiAudio).catch(console.error); return; } if (generatingSpeech !== null) return; setGeneratingSpeech(message.timestamp); const aiAudio = await generateSpeech(message.text); setGeneratingSpeech(null); if (!aiAudio) return; setMessages(prev => prev.map(item => ( item.timestamp === message.timestamp ? { ...item, aiAudio } : item ))); playAudio(aiAudio).catch(console.error); }; const handleSend = async (audioPayload?: string) => { const finalInput = input.trim(); const finalAudio = audioPayload || recordedAudio; if (!finalInput && !finalAudio) return; if (isLoading) return; const userMessage: Message = { role: "user", text: finalInput || "🎤 语音消息", timestamp: Date.now(), audio: finalAudio || undefined }; const newMessages = [...messages, userMessage]; setMessages(newMessages); setInput(""); setRecordedAudio(null); setIsLoading(true); const response = await chatWithSpaceModel(newMessages, mode, finalAudio || undefined); const aiMessage: Message = { role: "model", text: response, timestamp: Date.now(), }; setMessages([...newMessages, aiMessage]); setIsLoading(false); // Suggest switching to Guiding mode after 4 user messages in Venting mode if (mode === ChatMode.VENTING && newMessages.filter(m => m.role === "user").length >= 4) { setShowSwitchPrompt(true); } }; const toggleMode = (newMode: ChatMode) => { setMode(newMode); setShowSwitchPrompt(false); // Add a transition message from AI when mode changes const transitionMsg: Message = { role: "model", text: newMode === ChatMode.GUIDING ? "既然你愿意听听我的看法,那我们就坐下来,慢慢把这件事捋顺。❤️" : "好嘞!咱们继续,这事儿换谁谁不气啊?咱接着骂!🔥", timestamp: Date.now(), }; setMessages(prev => [...prev, transitionMsg]); }; const clearChat = () => { setMessages([]); setMode(ChatMode.VENTING); setShowSwitchPrompt(false); }; return (
{/* Header */}
{mode === ChatMode.VENTING ? ( ) : ( )}

{mode === ChatMode.VENTING ? "情绪宣泄室" : "静心引导室"}

{/* Chat Area */}
{messages.length === 0 && (

受委屈了?

在这里,你可以毫无顾忌地发泄。我永远站在你这一边。

)} {messages.map((msg, i) => (
{msg.audio && (
User Audio
)}

{msg.text}

{msg.role === "model" && ( )}

{new Date(msg.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}

))} {isLoading && (
)} {showSwitchPrompt && (

心跳平复一点了吗?要不要尝试一点静心开导?

)}
{/* Input Area */}
{recordedAudio && (

语音已录制

点击发送键一起发送文字

)}