|
import React, { useState, useRef } from 'react';
// 定义消息结构
interface Message {
id: string;
role: 'user' | 'assistant';
content: string;
status?: 'complete' | 'stopped'; // 标记消息状态
}
const ChatComponent: React.FC = () => {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
// 用于中断请求的控制器
const abortControllerRef = useRef<AbortController | null>(null);
const [isGenerating, setIsGenerating] = useState(false);
// 发送消息的核心逻辑
const handleSend = async () => {
if (!input.trim() || isGenerating) return;
const userMessage: Message = { id: Date.now().toString(), role: 'user', content: input };
// 初始化AI回复占位符
const aiMessage: Message = { id: (Date.now() + 1).toString(), role: 'assistant', content: '', status: 'complete' };
// 更新本地状态
setMessages(prev => [...prev, userMessage, aiMessage]);
setInput('');
setIsGenerating(true);
// 创建新的 AbortController
abortControllerRef.current = new AbortController();
try {
// 模拟流式请求 (实际项目中替换为 fetch SSE 接口)
const response = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ prompt: input }),
signal: abortControllerRef.current.signal, // 关键:绑定中断信号
headers: { 'Content-Type': 'application/json' }
});
// 模拟流式读取
const reader = response.body?.getReader();
// ... 此处省略具体的流式解码逻辑,重点在于数据追加 ...
} catch (error: any) {
if (error.name === 'AbortError') {
console.log('用户主动停止了生成');
// 更新最后一条消息的状态为 stopped
setMessages(prev => {
const newMsgs = [...prev];
const lastMsg = newMsgs[newMsgs.length - 1];
if (lastMsg.role === 'assistant') lastMsg.status = 'stopped';
return newMsgs;
});
}
} finally {
setIsGenerating(false);
abortControllerRef.current = null;
}
};
// 【核心功能1】停止生成
const handleStop = () => {
if (abortControllerRef.current) {
abortControllerRef.current.abort(); // 触发中断
}
};
// 【核心功能2】重新回答
const handleRetry = async (retryIndex: number) => {
// 1. 找到对应的用户提问 (当前索引-1)
const userPrompt = messages[retryIndex - 1].content;
// 2. 状态回滚:移除当前AI回答及之后的对话 (防止上下文污染)
// 注意:这里保留用户提问,只移除AI回答,也可以根据业务需求移除两者
setMessages(prev => prev.slice(0, retryIndex));
// 3. 重新触发发送逻辑 (这里简化处理,实际应复用 handleSend 逻辑)
// 实际开发中建议封装一个 receiveStream(prompt) 方法供调用
console.log(`Retrying with prompt: ${userPrompt}`);
// await handleSendInternal(userPrompt);
};
return (
<div className="chat-container">
{/* 渲染消息列表 */}
{messages.map((msg, index) => (
<div key={msg.id} className={`message ${msg.role}`}>
{msg.content}
{msg.status === 'stopped' && <span className="tag"> (已停止)</span>}
{msg.role === 'assistant' && (
<button onClick={() => handleRetry(index)}>重新生成</button>
)}
</div>
))}
{/* 底部操作栏 */}
<div className="input-area">
{isGenerating ? (
<button onClick={handleStop}>停止生成</button>
) : (
<button onClick={handleSend}>发送</button>
)}
</div>
</div>
);
};
|