From 229af563f799c9e9daad503d5353b6d29e7f3b34 Mon Sep 17 00:00:00 2001
From: hongjli <3117313295@qq.com>
Date: 星期五, 25 四月 2025 14:35:11 +0800
Subject: [PATCH] 聊天页面优化
---
src/app/chat/page.tsx | 612 ++++++++++++++++++++++++++++++++++++++++++-------------
1 files changed, 469 insertions(+), 143 deletions(-)
diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx
index 21ff564..1301f70 100644
--- a/src/app/chat/page.tsx
+++ b/src/app/chat/page.tsx
@@ -11,7 +11,7 @@
interface Message {
role: 'user' | 'assistant';
- content: string;
+ content: string | null;
timestamp: number;
id: string;
conversation_id?: string;
@@ -48,8 +48,20 @@
// 娣诲姞涓�涓柊鐨勭姸鎬佹潵鎺у埗娑堟伅鐨勬樉绀�
const [showMessages, setShowMessages] = useState(false);
+ // 鍦ㄧ粍浠堕《閮ㄥ鍔犱竴涓己鍒舵洿鏂拌鏁板櫒
+ const [forceUpdateCounter, setForceUpdateCounter] = useState(0);
+
+ // 娣诲姞鐘舵�佹潵鎺у埗鎬濊�冨唴瀹圭殑鏄剧ず/闅愯棌
+ const [expandedThinkMessages, setExpandedThinkMessages] = useState<Record<string, boolean>>({});
+
+ // 鍦ㄧ粍浠堕《閮ㄦ坊鍔犳樉绀�/闅愯棌瀵嗙爜鐨勭姸鎬�
+ const [showApiKey, setShowApiKey] = useState(false);
+
const messagesEndRef = useRef<HTMLDivElement>(null);
const errorTimeoutRef = useRef<any>(null);
+
+ // 鍦ㄧ粍浠堕《閮ㄦ坊鍔犱竴涓紩鐢紝鐢ㄤ簬璺熻釜缁勪欢鏄惁宸插嵏杞�
+ const isMountedRef = useRef(true);
useEffect(() => {
// 鑾峰彇瀛樺偍鐨凙PI Key
@@ -98,6 +110,22 @@
}
};
return cleanup;
+ }, []);
+
+ // 娣诲姞缁勪欢鍗歌浇鏃剁殑娓呯悊宸ヤ綔
+ useEffect(() => {
+ // 缁勪欢鎸傝浇鏃讹紝璁剧疆涓簍rue
+ isMountedRef.current = true;
+
+ // 缁勪欢鍗歌浇鏃讹紝璁剧疆涓篺alse
+ return () => {
+ isMountedRef.current = false;
+
+ // 娓呴櫎鎵�鏈夊畾鏃跺櫒
+ if (errorTimeoutRef.current) {
+ clearTimeout(errorTimeoutRef.current);
+ }
+ };
}, []);
const handleApiKeySubmit = () => {
@@ -149,6 +177,8 @@
setCurrentMessageId(assistantMessage.id);
setMessage('');
+ let controller: AbortController | null = new AbortController();
+
try {
const response = await fetch(`${BASE_URL}/v1/chat-messages`, {
method: 'POST',
@@ -164,112 +194,289 @@
conversation_id: conversationId || '',
user: 'abc-123',
files: []
- })
+ }),
+ signal: controller.signal // 娣诲姞涓淇″彿
});
+ console.log("API鍝嶅簲鐘舵��:", response.status, response.statusText);
+
if (!response.ok) {
const errorText = await response.text();
+ console.error("API閿欒鍝嶅簲:", errorText);
let errorMessage;
try {
const errorJson = JSON.parse(errorText);
- errorMessage = errorJson.error || `Error: ${response.status}`;
+ errorMessage = errorJson.error || `閿欒: ${response.status}`;
} catch {
- errorMessage = `Error: ${response.status}`;
+ errorMessage = `閿欒: ${response.status}`;
}
throw new Error(errorMessage);
}
- const reader = response.body?.getReader();
- if (!reader) throw new Error('No reader available');
+ // 纭繚response.body瀛樺湪
+ if (!response.body) {
+ throw new Error('鍝嶅簲娌℃湁鎻愪緵鏁版嵁娴�');
+ }
+ const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
- const { done, value } = await reader.read();
- if (done) {
- setIsMessageComplete(true);
- break;
- }
+ try {
+ const { done, value } = await reader.read();
+ if (done) {
+ console.log("娴佸紡鍝嶅簲鎺ユ敹瀹屾瘯");
+ setIsMessageComplete(true);
+ break;
+ }
- buffer += decoder.decode(value, { stream: true });
- const lines = buffer.split('\n');
- buffer = lines.pop() || '';
+ const chunk = decoder.decode(value, { stream: true });
+ console.log("鎺ユ敹鍒版暟鎹潡:", chunk);
+ buffer += chunk;
+ const lines = buffer.split('\n');
+ buffer = lines.pop() || '';
- for (const line of lines) {
- if (line.trim() === '') continue;
- if (line.startsWith('data: ')) {
- try {
- const data = JSON.parse(line.slice(6));
-
- // 蹇界暐ping浜嬩欢
- if (data.event === 'ping') continue;
+ for (const line of lines) {
+ if (line.trim() === '') continue;
+
+ console.log("澶勭悊琛屾暟鎹�:", line);
+
+ if (line.startsWith('data: ')) {
+ try {
+ const jsonStr = line.slice(6);
+ console.log("瀹屾暣鍘熷鏁版嵁:", line);
+ console.log("瑙f瀽JSON瀛楃涓�:", jsonStr);
+ const data = JSON.parse(jsonStr);
+ console.log("瑙f瀽鍚庣殑鏁版嵁瀵硅薄:", data);
+
+ // 蹇界暐ping浜嬩欢
+ if (data.event === 'ping') {
+ console.log("蹇界暐ping浜嬩欢");
+ continue;
+ }
- switch (data.event) {
- case 'message':
- setMessages(prev => {
- const newMessages = [...prev];
- const lastMessage = newMessages[newMessages.length - 1];
- if (lastMessage?.role === 'assistant' && lastMessage.id === currentMessageId) {
- lastMessage.content = data.answer || lastMessage.content;
- if (data.message_id) {
- lastMessage.id = data.message_id;
- setCurrentMessageId(data.message_id);
+ switch (data.event) {
+ case 'message':
+ console.log(`鏀跺埌message浜嬩欢:`, data);
+
+ // 鎻愬彇message鏁版嵁
+ const messageId = data.message_id;
+ const answerChunk = data.answer || '';
+ const convId = data.conversation_id;
+
+ // 濡傛灉涓嶅湪寰幆涓紝鐩存帴璋冪敤鏇存柊鍑芥暟
+ handleMessageChunk(messageId, answerChunk, convId);
+ break;
+
+ case 'message_end':
+ console.log('鏀跺埌message_end浜嬩欢:', data);
+
+ // 妫�鏌ユ槸鍚︽湁鏈�缁堢瓟妗�
+ if (data.metadata && data.id) {
+ setIsMessageComplete(true);
+ setConversationId(data.conversation_id || null);
+ }
+ break;
+
+ case 'workflow_finished':
+ console.log('宸ヤ綔娴佸畬鎴�:', data);
+
+ // 妫�鏌ュ伐浣滄祦杈撳嚭鏄惁鏈夌瓟妗�
+ if (data.data && data.data.outputs && data.data.outputs.answer) {
+ // 濡傛灉宸ヤ綔娴佹湁鏈�缁堢瓟妗堬紝鏇存柊娑堟伅鍐呭
+ const finalAnswer = data.data.outputs.answer;
+ const messageId = data.message_id;
+
+ if (finalAnswer) {
+ console.log('浠庡伐浣滄祦鑾峰彇鏈�缁堢瓟妗�:', finalAnswer);
+ handleFinalAnswer(messageId, finalAnswer, data.conversation_id);
}
}
- return newMessages;
- });
- break;
-
- case 'message_end':
- if (data.conversation_id) {
- setConversationId(data.conversation_id);
- setMessages(prev => {
- const newMessages = [...prev];
- const lastMessage = newMessages[newMessages.length - 1];
- if (lastMessage?.role === 'assistant' && lastMessage.id === currentMessageId) {
- lastMessage.conversation_id = data.conversation_id;
- if (data.metadata) {
- lastMessage.metadata = data.metadata;
- }
+ break;
+
+ case 'node_finished':
+ // 妫�鏌ヨ妭鐐圭被鍨嬫槸鍚︿负answer鑺傜偣
+ if (data.data && data.data.node_type === 'answer' && data.data.outputs && data.data.outputs.answer) {
+ console.log('浠巃nswer鑺傜偣鑾峰彇绛旀:', data.data.outputs.answer);
+ const answer = data.data.outputs.answer;
+ if (answer) {
+ handleFinalAnswer(data.message_id, answer, data.conversation_id);
}
- return newMessages;
- });
- }
- setIsMessageComplete(true);
- break;
-
- case 'error':
- console.error('Error event received:', data);
- setIsMessageComplete(true);
- throw new Error(data.message || '鍙戦�佹秷鎭椂鍑洪敊');
- }
- } catch (e) {
- console.error('Error parsing SSE data:', e);
- if (!messages[messages.length - 1]?.content) {
+ }
+ break;
+
+ // 澶勭悊鍏朵粬宸ヤ綔娴佷簨浠�
+ case 'workflow_started':
+ case 'node_started':
+ // 杩欎簺浜嬩欢鍙渶璁板綍锛屼笉闇�瑕佹洿鏂癠I
+ console.log(`宸ヤ綔娴佷簨浠� ${data.event}:`, data);
+ break;
+
+ case 'error':
+ console.error('鏈嶅姟鍣ㄨ繑鍥為敊璇簨浠�:', data);
+ setIsMessageComplete(true);
+ throw new Error(data.message || '鍙戦�佹秷鎭椂鍑洪敊');
+ }
+ } catch (e) {
+ console.error('瑙f瀽SSE鏁版嵁鍑洪敊:', e, '鍘熷琛�:', line);
setIsMessageComplete(true);
throw e;
}
}
}
+ } catch (err) {
+ console.error('澶勭悊娴佸紡鍝嶅簲鏃跺嚭閿�:', err);
+ throw err;
}
}
} catch (err) {
- console.error('Chat error:', err);
- showErrorMessage(err instanceof Error ? err.message : '鍙戦�佹秷鎭椂鍑洪敊');
+ // 妫�鏌ユ槸鍚︽槸涓閿欒
+ if (err instanceof Error && err.name === 'AbortError') {
+ console.log('璇锋眰琚腑姝紝鍙兘鏄粍浠跺嵏杞藉鑷寸殑');
+ return; // 涓閿欒涓嶉渶瑕佹樉绀虹粰鐢ㄦ埛
+ }
- setMessages(prev => {
- const newMessages = [...prev];
- const lastMessage = newMessages[newMessages.length - 1];
- if (lastMessage?.role === 'assistant') {
- lastMessage.content = '鎶辨瓑锛屾秷鎭彂閫佸け璐ワ紝璇风◢鍚庨噸璇曘��';
- }
- return newMessages;
- });
+ console.error('鑱婂ぉ璇锋眰閿欒:', err);
+ let errorMsg = err instanceof Error ? err.message : '鍙戦�佹秷鎭椂鍑洪敊';
+
+ // 鍙湁鍦ㄧ粍浠朵粛鐒舵寕杞芥椂鎵嶆洿鏂癠I
+ if (isMountedRef.current) {
+ showErrorMessage(errorMsg);
+
+ setMessages(prev => {
+ const newMessages = [...prev];
+ const lastMessage = newMessages[newMessages.length - 1];
+ if (lastMessage?.role === 'assistant') {
+ lastMessage.content = `鎶辨瓑锛屾棤娉曡幏鍙栧洖澶�: ${errorMsg}`;
+ }
+ return newMessages;
+ });
+ }
} finally {
- setIsStreaming(false);
+ // 娓呴櫎鎺у埗鍣�
+ controller = null;
+
+ // 鍙湁鍦ㄧ粍浠朵粛鐒舵寕杞芥椂鎵嶆洿鏂癠I
+ if (isMountedRef.current) {
+ setIsStreaming(false);
+ setIsMessageComplete(true);
+ }
}
};
+
+ // 淇敼handleMessageChunk鍑芥暟锛屾坊鍔犵粍浠舵寕杞芥鏌�
+ const handleMessageChunk = useCallback((messageId: string, answerChunk: string, convId?: string) => {
+ // 濡傛灉缁勪欢宸插嵏杞斤紝鍒欎笉鎵ц鏇存柊
+ if (!isMountedRef.current) return;
+
+ console.log(`澶勭悊娑堟伅鐗囨: ID=${messageId}, 鐗囨="${answerChunk}"`);
+
+ // 浣跨敤鍑芥暟寮忔洿鏂扮‘淇濊幏鍙栨渶鏂扮姸鎬�
+ setMessages(prevMessages => {
+ // 濡傛灉缁勪欢宸插嵏杞斤紝鍒欎笉鎵ц鏇存柊
+ if (!isMountedRef.current) return prevMessages;
+
+ // 鍒涘缓娑堟伅鏁扮粍鐨勬嫹璐�
+ const newMessages = [...prevMessages];
+
+ // 鏌ユ壘鏈�鍚庝竴鏉″姪鎵嬫秷鎭�
+ const lastMessage = newMessages[newMessages.length - 1];
+ if (lastMessage?.role !== 'assistant') {
+ console.warn('鎵句笉鍒板姪鎵嬫秷鎭潵鏇存柊');
+ return prevMessages; // 涓嶉渶瑕佹洿鏂�
+ }
+
+ // 鏇存柊娑堟伅鍐呭鍜孖D
+ const updatedContent = (lastMessage.content || '') + answerChunk;
+
+ // 鍒涘缓娑堟伅鐨勬柊鍓湰浠ョ‘淇漅eact妫�娴嬪埌鍙樺寲
+ const updatedMessage = {
+ ...lastMessage,
+ id: messageId,
+ content: updatedContent,
+ timestamp: Date.now()
+ };
+
+ if (convId) {
+ updatedMessage.conversation_id = convId;
+ }
+
+ // 鏇存柊褰撳墠姝e湪澶勭悊鐨勬秷鎭疘D
+ if (isMountedRef.current) {
+ setCurrentMessageId(messageId);
+ }
+
+ // 鏇挎崲鏈�鍚庝竴鏉℃秷鎭�
+ newMessages[newMessages.length - 1] = updatedMessage;
+
+ console.log(`鏇存柊鍚庣殑鍐呭闀垮害: ${updatedContent.length}`);
+
+ // 瑙﹀彂寮哄埗鏇存柊
+ if (isMountedRef.current) {
+ setTimeout(() => {
+ if (isMountedRef.current) {
+ setForceUpdateCounter(count => count + 1);
+ }
+ }, 0);
+ }
+
+ return newMessages;
+ });
+ }, []);
+
+ // 淇敼handleFinalAnswer鍑芥暟锛屾坊鍔犵粍浠舵寕杞芥鏌�
+ const handleFinalAnswer = useCallback((messageId: string, answer: string, convId?: string) => {
+ // 濡傛灉缁勪欢宸插嵏杞斤紝鍒欎笉鎵ц鏇存柊
+ if (!isMountedRef.current) return;
+
+ console.log(`璁剧疆鏈�缁堢瓟妗�: ID=${messageId}, 鍐呭="${answer}"`);
+
+ setMessages(prevMessages => {
+ // 濡傛灉缁勪欢宸插嵏杞斤紝鍒欎笉鎵ц鏇存柊
+ if (!isMountedRef.current) return prevMessages;
+
+ const newMessages = [...prevMessages];
+ const lastMessage = newMessages[newMessages.length - 1];
+
+ if (lastMessage?.role !== 'assistant') {
+ console.warn('鎵句笉鍒板姪鎵嬫秷鎭潵鏇存柊鏈�缁堢瓟妗�');
+ return prevMessages;
+ }
+
+ // 鍒涘缓娑堟伅鐨勬柊鍓湰锛岃缃渶缁堢瓟妗�
+ const updatedMessage = {
+ ...lastMessage,
+ id: messageId,
+ content: answer,
+ timestamp: Date.now()
+ };
+
+ if (convId) {
+ updatedMessage.conversation_id = convId;
+ }
+
+ // 鏇存柊鐘舵��
+ if (isMountedRef.current) {
+ setIsMessageComplete(true);
+ setCurrentMessageId(messageId);
+ }
+
+ // 鏇挎崲鏈�鍚庝竴鏉℃秷鎭�
+ newMessages[newMessages.length - 1] = updatedMessage;
+
+ // 瑙﹀彂寮哄埗鏇存柊
+ if (isMountedRef.current) {
+ setTimeout(() => {
+ if (isMountedRef.current) {
+ setForceUpdateCounter(count => count + 1);
+ }
+ }, 0);
+ }
+
+ return newMessages;
+ });
+ }, []);
// 鏇存柊鍙戦�佹寜閽殑绂佺敤鐘舵��
const isSendDisabled = isStreaming || !message.trim() || !isMessageComplete;
@@ -287,6 +494,59 @@
setShowMessages(true);
}, 100);
return () => clearTimeout(timer);
+ }, []);
+
+ // 娣诲姞鑷畾涔塁SS鍔ㄧ敾鏍峰紡
+ useEffect(() => {
+ const style = document.createElement('style');
+ style.innerHTML = `
+ @keyframes dotBounce {
+ 0%, 80%, 100% { transform: translateY(0); }
+ 40% { transform: translateY(-6px); }
+ }
+ .dot-animation span:nth-child(1) { animation: dotBounce 1.4s -0.32s infinite ease-in-out; }
+ .dot-animation span:nth-child(2) { animation: dotBounce 1.4s -0.16s infinite ease-in-out; }
+ .dot-animation span:nth-child(3) { animation: dotBounce 1.4s 0s infinite ease-in-out; }
+ `;
+ document.head.appendChild(style);
+
+ return () => {
+ document.head.removeChild(style);
+ };
+ }, []);
+
+ // 娣诲姞澶勭悊鎬濊�冨唴瀹圭殑杈呭姪鍑芥暟
+ const parseMessageContent = (content: string | null) => {
+ if (!content) return { mainContent: '', thinkContent: null };
+
+ // 鍖归厤<think>鏍囩鍐呭 (涓嶅尯鍒嗗ぇ灏忓啓)
+ const thinkMatch = content.match(/<think>([\s\S]*?)<\/think>/i);
+
+ if (thinkMatch) {
+ // 鎻愬彇鎬濊�冨唴瀹�
+ const thinkContent = thinkMatch[1].trim();
+ // 绉婚櫎<think>鏍囩,淇濈暀涓昏鍐呭
+ const mainContent = content.replace(/<think>[\s\S]*?<\/think>/i, '').trim();
+ return { mainContent, thinkContent };
+ }
+
+ return { mainContent: content, thinkContent: null };
+ };
+
+ // 鍒囨崲鎬濊�冨唴瀹圭殑鏄剧ず/闅愯棌
+ const toggleThinkContent = (messageId: string) => {
+ setExpandedThinkMessages(prev => ({
+ ...prev,
+ [messageId]: !prev[messageId]
+ }));
+ };
+
+ // 娣诲姞缁勪欢鍗歌浇鏃剁殑娓呯悊鍑芥暟
+ useEffect(() => {
+ return () => {
+ // 缁勪欢鍗歌浇鏃剁殑娓呯悊宸ヤ綔
+ console.log('鑱婂ぉ缁勪欢鍗歌浇锛屾竻鐞嗚祫婧�');
+ };
}, []);
return (
@@ -307,13 +567,34 @@
</button>
</div>
<div className="space-y-2">
- <input
- type="password"
- value={apiKey}
- onChange={(e) => setApiKey(e.target.value)}
- placeholder="璇疯緭鍏ユ偍鐨� API Key"
- className="w-full px-4 py-2 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-100/50"
- />
+ <div className="relative">
+ <input
+ type={showApiKey ? "text" : "password"}
+ value={apiKey}
+ onChange={(e) => setApiKey(e.target.value)}
+ placeholder="璇疯緭鍏ユ偍鐨� API Key"
+ className="w-full px-4 py-2 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-100/50"
+ />
+ <button
+ type="button"
+ onClick={() => setShowApiKey(!showApiKey)}
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
+ >
+ {showApiKey ? (
+ // 鐪肩潧鍏抽棴鍥炬爣
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
+ <path fillRule="evenodd" d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z" clipRule="evenodd" />
+ <path d="M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z" />
+ </svg>
+ ) : (
+ // 鐪肩潧鍥炬爣
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
+ <path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
+ <path fillRule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clipRule="evenodd" />
+ </svg>
+ )}
+ </button>
+ </div>
</div>
<div className="flex justify-end gap-2">
<button
@@ -371,74 +652,119 @@
<div className="flex-1 overflow-y-auto pt-20 pb-32">
<div className="max-w-4xl mx-auto px-6">
<div className={`space-y-6 ${showMessages ? 'opacity-100' : 'opacity-0'} transition-opacity duration-200`}>
- {messages.map((msg, index) => (
- <div
- key={msg.id}
- className={`flex items-start gap-4 ${msg.role === 'user' ? 'flex-row-reverse' : ''}`}
- >
- <div className="relative flex-shrink-0">
- <div className="w-8 h-8 rounded-lg overflow-hidden shadow-inner bg-gray-50">
- <Image
- src={msg.role === 'assistant' ? "/images/logo.jpg" : DEFAULT_USER_AVATAR}
- alt={msg.role === 'assistant' ? "AI鍔╂墜" : "鐢ㄦ埛"}
- width={32}
- height={32}
- className="w-full h-full object-cover"
- />
- </div>
- {msg.role === 'assistant' && (
- <div className="absolute -bottom-1 -right-1 w-3 h-3 bg-green-400 rounded-full border-2 border-white"></div>
- )}
- </div>
- <div className={`flex-1 min-w-0 ${msg.role === 'user' ? 'text-right' : ''}`}>
- <div className={`${
- msg.role === 'assistant'
- ? 'bg-white rounded-xl shadow-sm border border-gray-100'
- : 'bg-[#E8F4FF] rounded-xl'
- } inline-block max-w-[85%] relative min-h-[42px]`}>
- {msg.role === 'assistant' && !isMessageComplete && currentMessageId === msg.id && (
- <div className="absolute left-0 top-0 w-full h-full flex items-center justify-center">
- <div className="w-5 h-5 border-2 border-blue-500/30 border-t-blue-500/80 rounded-full animate-spin"></div>
- </div>
- )}
- <div className={`flex items-center px-4 min-h-[42px] ${
- msg.role === 'assistant' && !isMessageComplete && currentMessageId === msg.id ? 'invisible' : ''
- }`}>
- <div className="prose prose-sm max-w-none text-[14px] leading-[1.3]
- prose-p:my-0 prose-p:leading-[1.3]
- prose-headings:font-medium prose-headings:text-gray-800
- prose-h1:text-lg prose-h1:my-2
- prose-h2:text-base prose-h2:my-2
- prose-h3:text-base prose-h3:my-1.5
- prose-ul:my-1.5 prose-ul:pl-4 prose-li:my-0.5
- prose-ol:my-1.5 prose-ol:pl-4
- prose-code:px-1 prose-code:py-0.5 prose-code:bg-gray-100 prose-code:rounded prose-code:text-gray-800 prose-code:before:content-[''] prose-code:after:content-['']
- prose-pre:my-2 prose-pre:p-2.5 prose-pre:bg-gray-800 prose-pre:rounded-lg
- prose-a:text-blue-500 prose-a:no-underline hover:prose-a:underline
- prose-blockquote:my-2 prose-blockquote:pl-3 prose-blockquote:border-l-4 prose-blockquote:border-gray-200
- prose-strong:font-medium prose-strong:text-gray-800
- prose-table:my-2 prose-tr:border-gray-200 prose-td:py-1 prose-td:px-2">
- {msg.content.split('\n').map((line, i) => (
- <div key={i} className="my-0">
- {line.trim() && (
- <ReactMarkdown
- remarkPlugins={[remarkGfm]}
- rehypePlugins={[rehypeRaw, rehypeSanitize]}
- >
- {line.trim()}
- </ReactMarkdown>
- )}
- </div>
- ))}
- </div>
+ {messages.length === 0 ? (
+ <div className="flex items-center justify-center h-[200px] text-gray-400">
+ <p>鍙戦�佹秷鎭紑濮嬪璇�</p>
+ </div>
+ ) : (
+ messages.map((msg, index) => (
+ <div
+ key={msg.id}
+ className={`flex items-start gap-4 ${msg.role === 'user' ? 'flex-row-reverse' : ''}`}
+ >
+ <div className="relative flex-shrink-0">
+ <div className="w-8 h-8 rounded-lg overflow-hidden shadow-inner bg-gray-50">
+ <Image
+ src={msg.role === 'assistant' ? "/images/logo.jpg" : DEFAULT_USER_AVATAR}
+ alt={msg.role === 'assistant' ? "AI鍔╂墜" : "鐢ㄦ埛"}
+ width={32}
+ height={32}
+ className="w-full h-full object-cover"
+ />
</div>
</div>
- <div className="mt-0.5 text-xs text-gray-400">
- {msg.role === 'user' && new Date(msg.timestamp).toLocaleTimeString()}
+ <div className={`flex-1 min-w-0 ${msg.role === 'user' ? 'text-right' : ''}`}>
+ <div className={`${
+ msg.role === 'assistant'
+ ? 'bg-white rounded-xl shadow-sm border border-gray-100'
+ : 'bg-[#E8F4FF] rounded-xl'
+ } inline-block max-w-[85%] relative overflow-hidden`}>
+
+ {/* 璋冭瘯淇℃伅 - 绉婚櫎杩欓儴鍒� */}
+ {/*
+ {process.env.NODE_ENV !== 'production' && (
+ <div className="bg-gray-100 px-2 py-1 text-xs text-gray-500">
+ 闀垮害: {(msg.content || '').length} | 璁℃暟: {forceUpdateCounter}
+ </div>
+ )}
+ */}
+
+ {msg.role === 'assistant' && (
+ <>{(() => {
+ // 瑙f瀽娑堟伅鍐呭锛屾彁鍙栨�濊�冮儴鍒�
+ const { mainContent, thinkContent } = parseMessageContent(msg.content);
+ const isExpanded = expandedThinkMessages[msg.id] || false;
+ const thinkingDuration = 8; // 鎬濊�冩椂闂�(绉�)
+
+ return (
+ <>
+ {/* 鎬濊�冨唴瀹瑰尯鍩� (濡傛灉瀛樺湪) */}
+ {thinkContent && (
+ <div className="border-b border-gray-200">
+ <button
+ onClick={() => toggleThinkContent(msg.id)}
+ className="w-full flex items-center justify-between px-3 py-2 bg-gray-50 hover:bg-gray-100 transition-colors text-sm text-gray-700"
+ >
+ <div className="flex items-center space-x-2">
+ <span>宸叉繁搴︽�濊�� (鐢ㄦ椂 {thinkingDuration} 绉�)</span>
+ </div>
+ <svg
+ className={`w-4 h-4 transform transition-transform ${isExpanded ? 'rotate-180' : ''}`}
+ fill="none"
+ stroke="currentColor"
+ viewBox="0 0 24 24"
+ >
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
+ </svg>
+ </button>
+
+ {/* 鍙姌鍙犵殑鎬濊�冨唴瀹� */}
+ {isExpanded && (
+ <div className="bg-gray-50 px-4 py-3 text-sm text-gray-700 border-t border-gray-200 whitespace-pre-wrap">
+ {thinkContent}
+ </div>
+ )}
+ </div>
+ )}
+
+ {/* 涓昏鍐呭 */}
+ <div className="p-3">
+ <div className="text-gray-800 leading-relaxed whitespace-pre-wrap">
+ {mainContent || (msg.role === 'assistant' && !isMessageComplete ? '姝e湪鎬濊��...' : '')}
+ </div>
+ </div>
+
+ {/* 鍔犺浇鎸囩ず鍣� */}
+ {msg.role === 'assistant' && !isMessageComplete && (
+ <div className="absolute bottom-1 right-2">
+ <div className="flex space-x-1">
+ <div className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse delay-0"></div>
+ <div className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse delay-150"></div>
+ <div className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse delay-300"></div>
+ </div>
+ </div>
+ )}
+ </>
+ );
+ })()}</>
+ )}
+
+ {/* 鐢ㄦ埛娑堟伅绠�鍗曟樉绀� */}
+ {msg.role === 'user' && (
+ <div className="p-3">
+ <div className="text-gray-800 leading-relaxed whitespace-pre-wrap">
+ {msg.content}
+ </div>
+ </div>
+ )}
+ </div>
+ <div className="mt-0.5 text-xs text-gray-400">
+ {msg.role === 'user' && new Date(msg.timestamp).toLocaleTimeString()}
+ </div>
</div>
</div>
- </div>
- ))}
+ ))
+ )}
<div ref={messagesEndRef} />
</div>
</div>
--
Gitblit v1.9.3