From 3bc274ad0cd5021ba17b3e585762c846b87ee199 Mon Sep 17 00:00:00 2001
From: hongjli <3117313295@qq.com>
Date: 星期一, 28 四月 2025 10:43:22 +0800
Subject: [PATCH] 渲染echarts图优化

---
 src/app/chat/page.tsx |  491 +++++++++++++++++++++++++++++-------------------------
 1 files changed, 262 insertions(+), 229 deletions(-)

diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx
index 525b052..d7c0918 100644
--- a/src/app/chat/page.tsx
+++ b/src/app/chat/page.tsx
@@ -43,59 +43,76 @@
 // 娣诲姞鐢ㄤ簬瑙f瀽鍜屾覆鏌� ECharts 鐨勭粍浠�
 function EchartsRenderer({ code }: { code: string }) {
   const [error, setError] = useState<string | null>(null);
-  const [isLoaded, setIsLoaded] = useState(false);
+  const [isLoading, setIsLoading] = useState(true);
   const chartContainerRef = useRef<HTMLDivElement>(null);
-  const codeRef = useRef<string>(code);
-  const renderAttemptedRef = useRef(false);
+  const hasRenderedRef = useRef<boolean>(false);
   
-  // 鏇存柊浠g爜寮曠敤
+  // 鍒濆鍖朌OM瀹瑰櫒
   useEffect(() => {
-    codeRef.current = code;
-  }, [code]);
-
-  // 寤惰繜鍒濆鍖栫洿鍒版秷鎭ǔ瀹�
-  useEffect(() => {
-    // 涓虹‘淇濇祦寮忓唴瀹圭ǔ瀹氾紝璁剧疆寤惰繜
-    const timer = setTimeout(() => {
-      setIsLoaded(true);
-    }, 500);
-    
-    return () => clearTimeout(timer);
-  }, []);
-
-  // 涓昏娓叉煋閫昏緫 - 浠呭湪缁勪欢鏍囪涓哄凡鍔犺浇鍚庢墽琛�
-  useEffect(() => {
-    // 濡傛灉缁勪欢鏈爣璁颁负宸插姞杞芥垨宸茬粡灏濊瘯杩囨覆鏌擄紝鍒欓��鍑�
-    if (!isLoaded || renderAttemptedRef.current) return;
-    
-    // 纭繚鍙湪瀹㈡埛绔墽琛�
-    if (typeof window === 'undefined' || !chartContainerRef.current) return;
-    
-    // 鏍囪涓哄凡灏濊瘯娓叉煋锛岄槻姝㈤噸澶嶆覆鏌�
-    renderAttemptedRef.current = true;
-    
-    // 闃叉浠g爜鎵ц涓殑鍙橀噺鍐茬獊
-    const safeCode = codeRef.current.replace(/window\.option/g, '_uniqueOptionVar');
-    
-    // 绛夊緟DOM鏇存柊瀹屾垚
-    setTimeout(() => {
-      let chartInstance: any = null;
+    // 纭繚瀹瑰櫒鍑嗗濂戒簡
+    if (chartContainerRef.current) {
+      // 璁剧疆涓�涓垵濮嬮珮搴﹀拰瀹藉害锛岄伩鍏�"invalid dom"閿欒
+      const container = chartContainerRef.current;
+      container.style.width = '100%';
+      container.style.height = '400px';
       
-      const initChart = async () => {
+      // 娣诲姞涓存椂鍐呭锛岀‘淇滵OM娓叉煋瀹屾垚
+      container.innerHTML = '<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;"><span>姝e湪鍑嗗鍥捐〃...</span></div>';
+    }
+  }, []);
+  
+  // 涓�娆℃�у姞杞藉浘琛紝涓嶈繘琛屽姩鎬佹洿鏂�
+  useEffect(() => {
+    // 濡傛灉宸茬粡娓叉煋杩囷紝璺宠繃
+    if (hasRenderedRef.current) {
+      return;
+    }
+    
+    setIsLoading(true);
+    
+    // 纭繚DOM鍏冪礌宸茬粡瀹屽叏鎸傝浇鍜屾覆鏌�
+    const timer = setTimeout(() => {
+      // 纭繚DOM鍏冪礌浠嶇劧瀛樺湪
+      if (!chartContainerRef.current) {
+        console.error('鍥捐〃瀹瑰櫒涓嶅瓨鍦紝璺宠繃鍒濆鍖�');
+        setIsLoading(false);
+        return;
+      }
+      
+      // 娓呴櫎涓存椂鍐呭
+      chartContainerRef.current.innerHTML = '';
+      
+      // 鏍囪涓哄凡娓叉煋
+      hasRenderedRef.current = true;
+      
+      // 绠�鍖栫殑鍥捐〃鍒濆鍖栭�昏緫
+      const loadChart = async () => {
         try {
           // 鍔ㄦ�佸鍏charts
           const echarts = await import('echarts');
           
-          if (!chartContainerRef.current) {
-            console.warn('鍥捐〃瀹瑰櫒宸蹭笉瀛樺湪');
+          // 鍐嶆纭DOM鍏冪礌瀛樺湪骞跺叿鏈夋湁鏁堝昂瀵�
+          if (!chartContainerRef.current || 
+              !chartContainerRef.current.offsetWidth || 
+              !chartContainerRef.current.offsetHeight) {
+            console.error('鍥捐〃瀹瑰櫒灏哄鏃犳晥锛岃烦杩囧垵濮嬪寲');
+            setError('鍥捐〃瀹瑰櫒灏哄鏃犳晥');
+            setIsLoading(false);
             return;
           }
           
-          // 鍒濆鍖栧浘琛�
-          chartInstance = echarts.init(chartContainerRef.current);
+          // 鍒濆鍖栧浘琛� - 娣诲姞娓叉煋鍣ㄧ被鍨嬪弬鏁�
+          const chartInstance = echarts.init(
+            chartContainerRef.current, 
+            undefined, 
+            { renderer: 'canvas', devicePixelRatio: window.devicePixelRatio || 1 }
+          );
           
-          // 浣跨敤Function鏋勯�犲櫒锛屼絾娣诲姞棰濆鐨勪繚鎶�
           try {
+            // 瀹夊叏澶勭悊浠g爜
+            const safeCode = code.replace(/window\.option/g, '_uniqueOptionVar');
+            
+            // 鎵ц浠g爜鑾峰彇閰嶇疆
             const getFinalOption = new Function(`
               "use strict";
               let option;
@@ -111,163 +128,79 @@
             
             const chartOption = getFinalOption();
             
+            // 搴旂敤閰嶇疆
             if (chartOption) {
               chartInstance.setOption(chartOption);
-              setError(null);
             } else {
-              console.warn('鏈幏鍙栧埌鏈夋晥鐨勫浘琛ㄩ厤缃紝浣跨敤澶囩敤閰嶇疆');
-              useFallbackOption(chartInstance);
+              // 搴旂敤榛樿閰嶇疆
+              chartInstance.setOption({
+                title: { text: '鍥捐〃鏁版嵁瑙f瀽澶辫触' },
+                xAxis: { type: 'category', data: ['鏃犳暟鎹�'] },
+                yAxis: { type: 'value' },
+                series: [{ data: [0], type: 'bar' }]
+              });
             }
+            
+            setIsLoading(false);
           } catch (e) {
-            console.error('鎵ц鍥捐〃浠g爜閿欒:', e);
-            setError(e instanceof Error ? e.message : '鍥捐〃浠g爜鎵ц閿欒');
-            useFallbackOption(chartInstance);
+            console.error('鍥捐〃浠g爜鎵ц閿欒:', e);
+            setError('鍥捐〃瑙f瀽澶辫触');
+            setIsLoading(false);
           }
           
-          // 娣诲姞鍝嶅簲寮忚皟鏁�
-          const handleResize = () => {
-            chartInstance && chartInstance.resize();
-          };
-          
+          // 绐楀彛澶у皬鏀瑰彉鏃讹紝閲嶈鍥捐〃澶у皬
+          const handleResize = () => chartInstance.resize();
           window.addEventListener('resize', handleResize);
           
-          // 纭繚缁勪欢鍗歌浇鏃舵竻鐞嗚祫婧�
+          // 缁勪欢鍗歌浇鏃舵竻鐞�
           return () => {
             window.removeEventListener('resize', handleResize);
-            if (chartInstance) {
-              try {
-                chartInstance.dispose();
-              } catch (e) {
-                console.warn('鍥捐〃瀹炰緥閿�姣佸け璐�', e);
-              }
-            }
+            try {
+              chartInstance.dispose();
+            } catch (e) {}
           };
         } catch (e) {
-          console.error('鍥捐〃鍒濆鍖栧け璐�:', e);
-          setError('鍔犺浇鍥捐〃搴撳け璐�');
-          return () => {};
+          console.error('鍥捐〃鍔犺浇澶辫触:', e);
+          setError('鍥捐〃搴撳姞杞藉け璐�');
+          setIsLoading(false);
         }
       };
       
-      // 澶囩敤閰嶇疆鍑芥暟
-      const useFallbackOption = (instance: any) => {
-        try {
-          // 绠�鍗曠殑澶囩敤閰嶇疆
-          const fallbackOption = {
-            title: {
-              text: '鏁版嵁鍙鍖栧浘琛�',
-              subtext: '(鍘熷浠g爜瑙f瀽澶辫触锛屾樉绀哄鐢ㄥ浘琛�)'
-            },
-            tooltip: {
-              trigger: 'axis'
-            },
-            legend: {
-              data: ['鏁版嵁']
-            },
-            xAxis: {
-              type: 'category',
-              data: ['椤圭洰1', '椤圭洰2', '椤圭洰3', '椤圭洰4', '椤圭洰5']
-            },
-            yAxis: {
-              type: 'value'
-            },
-            series: [{
-              name: '鏁版嵁',
-              type: 'bar',
-              data: [5, 20, 36, 10, 10]
-            }]
-          };
-          
-          // 妫�娴嬫槸鍚︽槸鐑姏鍥句唬鐮�
-          if (safeCode.includes('heatmap') || 
-              safeCode.includes('visualMap') || 
-              safeCode.includes('椋庨櫓')) {
-            (fallbackOption.series as any) = [{
-              type: 'heatmap',
-              name: '椋庨櫓鍊�',  // 娣诲姞缂哄け鐨刵ame灞炴��
-              data: [
-                [0, 0, 5], [0, 1, 7], [0, 2, 3],
-                [1, 0, 7], [1, 1, 8], [1, 2, 6],
-                [2, 0, 9], [2, 1, 10], [2, 2, 8]
-              ] as any[]
-            }];
-            
-            fallbackOption.title.text = 'VIP瀹㈡埛璁㈠崟浜や粯椋庨櫓鐑姏鍥�';
-            fallbackOption.title.subtext = '鏁版嵁瑙f瀽澶辫触锛屾樉绀虹ず渚嬬儹鍔涘浘';
-            
-            // 娣诲姞鐑姏鍥炬墍闇�鐨勫叾浠栭厤缃�
-            Object.assign(fallbackOption, {
-              tooltip: {
-                position: 'top'
-              },
-              xAxis: {
-                type: 'category',
-                data: ['浣�', '涓�', '楂�'],
-                name: '寤惰繜椋庨櫓'
-              },
-              yAxis: {
-                type: 'category',
-                data: ['浣�', '涓�', '楂�'],
-                name: '杩濈害鎴愭湰'
-              },
-              visualMap: {
-                min: 1,
-                max: 10,
-                calculable: true,
-                orient: 'horizontal',
-                left: 'center',
-                bottom: '15%'
-              }
-            });
-          }
-          
-          // 璁剧疆澶囩敤閰嶇疆
-          instance.setOption(fallbackOption);
-        } catch (e) {
-          console.error('搴旂敤澶囩敤閰嶇疆澶辫触:', e);
-        }
-      };
-      
-      // 鍒濆鍖栧浘琛ㄥ苟鑾峰彇娓呯悊鍑芥暟
-      const cleanupPromise = initChart();
-      
-      // 瀹屽叏閲嶅啓娓呯悊鍑芥暟閫昏緫锛岄伩鍏嶄娇鐢≒romise鐨勪笉纭畾杩斿洖绫诲瀷
-      // 鍒濆鍖栧浘琛�
-      initChart().then(cleanupFn => {
-        // 瀛樺偍娓呯悊鍑芥暟渚涗互鍚庝娇鐢�
-        if (typeof cleanupFn === 'function') {
-          // 浣跨敤ref瀛樺偍娓呯悊鍑芥暟
-          const currentCleanup = cleanupFn;
-          // 缁勪欢鍗歌浇鏃舵墽琛�
-          return () => currentCleanup();
-        }
-      }).catch(e => {
-        console.error('鍒濆鍖栧浘琛ㄥけ璐�:', e);
-      });
-      
-      // 杩斿洖涓�涓┖鐨勬竻鐞嗗嚱鏁帮紝閬垮厤绫诲瀷閿欒
-      return () => {};
-    }, 100);
-  }, [isLoaded]);
-
-  // 娓叉煋鍥捐〃瀹瑰櫒
+      // 鎵ц鍔犺浇
+      loadChart();
+    }, 500); // 澧炲姞寤惰繜锛岀‘淇滵OM瀹屽叏娓叉煋
+    
+    return () => clearTimeout(timer);
+  }, [code]);
+  
+  // 绠�鍖栫殑娓叉煋閫昏緫锛岀‘淇濆鍣ㄦ湁鏄庣‘鐨勫昂瀵�
   return (
-    <div className="relative my-4">
+    <div className="relative my-4 echart-container">
       {error && (
         <div className="absolute top-0 left-0 right-0 bg-red-50 border border-red-200 text-red-600 px-3 py-2 rounded-md text-xs z-10">
           {error}
-          <button 
-            className="ml-2 text-red-700 hover:text-red-900" 
-            onClick={() => setError(null)}
-          >
-            脳
-          </button>
         </div>
       )}
+      
+      {isLoading && (
+        <div className="absolute inset-0 flex items-center justify-center bg-white bg-opacity-70 z-10">
+          <div className="flex flex-col items-center">
+            <div className="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-blue-500 mb-3"></div>
+            <p className="text-gray-500">鍥捐〃鍔犺浇涓�...</p>
+          </div>
+        </div>
+      )}
+      
       <div 
         ref={chartContainerRef} 
-        className="w-full bg-white border border-gray-200 rounded-lg overflow-hidden" 
-        style={{ height: '400px' }}
+        className="w-full bg-white border border-gray-200 rounded-lg overflow-hidden chart-container"
+        style={{ 
+          height: '400px',
+          minHeight: '300px', // 纭繚鏈夋渶灏忛珮搴�
+          visibility: 'visible', // 纭繚瀹瑰櫒鍙
+          position: 'relative' // 鍒涘缓BFC
+        }}
+        data-echarts-container="true" // 娣诲姞鏍囪瘑灞炴��
       />
     </div>
   );
@@ -322,6 +255,84 @@
   );
 }
 
+// 鍒涘缓鐙珛鐨勮緭鍏ョ粍浠讹紝閬垮厤鐘舵�佸叡浜鑷寸殑閲嶆覆鏌�
+interface ChatInputProps {
+  onSendMessage: () => void;
+  isStreaming: boolean;
+  isMessageComplete: boolean;
+}
+
+function ChatInput({ onSendMessage, isStreaming, isMessageComplete }: ChatInputProps) {
+  // 鍐呴儴鐘舵�侊紝涓庡閮ㄥ畬鍏ㄩ殧绂�
+  const [inputText, setInputText] = useState('');
+  const inputRef = useRef<HTMLTextAreaElement>(null);
+  
+  // 澶勭悊杈撳叆鍙樺寲
+  const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
+    setInputText(e.target.value);
+  };
+  
+  // 澶勭悊鎸夐敭浜嬩欢
+  const handleKeyDown = (e: React.KeyboardEvent) => {
+    if (e.key === 'Enter' && !e.shiftKey) {
+      e.preventDefault();
+      handleSend();
+    }
+  };
+  
+  // 澶勭悊鍙戦��
+  const handleSend = () => {
+    if (isStreaming || !inputText.trim() || !isMessageComplete) return;
+    
+    // 閫氱煡鐖剁粍浠跺彂閫佹秷鎭墠鏇存柊娑堟伅鍐呭
+    (window as any).messageToSend = inputText.trim();
+    
+    // 娓呯┖杈撳叆
+    setInputText('');
+    
+    // 璋冪敤鐖剁粍浠剁殑鍙戦�佹柟娉�
+    onSendMessage();
+  };
+  
+  return (
+    <div className="fixed bottom-0 left-0 right-0 bg-gradient-to-t from-white via-white to-white/95 pt-4 pb-6" style={{ zIndex: 50 }}>
+      <div className="max-w-4xl mx-auto px-6">
+        <div className="relative">
+          <div className="absolute -top-6 left-1/2 -translate-x-1/2 w-48 h-[1px] bg-gradient-to-r from-transparent via-gray-200 to-transparent"></div>
+          
+          <div className="flex gap-4 items-start">
+            <div className="flex-1 relative">
+              <textarea
+                ref={inputRef}
+                value={inputText}
+                onChange={handleInputChange}
+                onKeyDown={handleKeyDown}
+                placeholder="杈撳叆娑堟伅..."
+                disabled={isStreaming}
+                className="w-full resize-none rounded-xl border-0 bg-gray-50 px-4 py-3 text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-100/50 focus:bg-white min-h-[48px] max-h-32 shadow-inner transition-all duration-300 ease-in-out hover:bg-gray-100/70 disabled:opacity-50 disabled:cursor-not-allowed"
+                style={{ height: '48px' }}
+              />
+              <div className="absolute right-4 bottom-2 text-xs text-gray-400 bg-gray-50 px-2">
+                鎸塃nter鍙戦�侊紝Shift+Enter鎹㈣
+              </div>
+            </div>
+            <button
+              onClick={handleSend}
+              disabled={isStreaming || !inputText.trim() || !isMessageComplete}
+              className="h-12 px-5 bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white font-medium rounded-xl transition-all duration-300 flex items-center gap-2 shadow-lg shadow-blue-500/20 hover:shadow-blue-500/30 hover:scale-[1.02] active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none disabled:shadow-none"
+            >
+              <span>{isStreaming ? '鍥炲涓�...' : (isMessageComplete ? '鍙戦��' : '澶勭悊涓�...')}</span>
+              <svg xmlns="http://www.w3.org/2000/svg" className={`h-4 w-4 transform rotate-45 ${isStreaming ? 'animate-pulse' : ''}`} viewBox="0 0 20 20" fill="currentColor">
+                <path d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z" />
+              </svg>
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}
+
 export default function Page() {
   const router = useRouter();
   const [apiKey, setApiKey] = useState<string>('');
@@ -352,6 +363,40 @@
 
   // 鍦ㄧ粍浠堕《閮ㄦ坊鍔犱竴涓紩鐢紝鐢ㄤ簬璺熻釜缁勪欢鏄惁宸插嵏杞�
   const isMountedRef = useRef(true);
+
+  // 鍒嗙娑堟伅杈撳叆鐘舵�侊紝閬垮厤瑙﹀彂涓嶅繀瑕佺殑閲嶆覆鏌�
+  const messageInputRef = useRef<HTMLTextAreaElement>(null);
+  const [localMessage, setLocalMessage] = useState('');
+  
+  const handleMessageChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
+    // 鍙洿鏂版湰鍦扮姸鎬侊紝涓嶈Е鍙戝叏灞�閲嶆覆鏌�
+    setLocalMessage(e.target.value);
+  };
+
+  // 浠呭湪鍙戦�佹椂鏇存柊鍏ㄥ眬娑堟伅鐘舵��
+  const handleKeyPress = (e: React.KeyboardEvent) => {
+    if (e.key === 'Enter' && !e.shiftKey) {
+      e.preventDefault();
+      // 鏇存柊鍏ㄥ眬鐘舵�佸苟鍙戦��
+      setMessage(localMessage);
+      setTimeout(() => handleSendMessage(), 10);
+    }
+  };
+
+  // 鐐瑰嚮鍙戦�佹寜閽椂
+  const handleSendButtonClick = () => {
+    // 妫�鏌ユ秷鎭槸鍚︿负绌�
+    if (!localMessage.trim() || !isMessageComplete) return;
+    
+    // 鏇存柊鍏ㄥ眬鐘舵�佸苟鍙戦��
+    setMessage(localMessage);
+    setTimeout(() => handleSendMessage(), 10);
+  };
+
+  // 鍚屾娑堟伅鐘舵�佸埌鏈湴鐘舵��
+  useEffect(() => {
+    setLocalMessage(message);
+  }, [message]);
 
   useEffect(() => {
     // 鑾峰彇瀛樺偍鐨凙PI Key
@@ -427,7 +472,9 @@
   };
 
   const handleSendMessage = async () => {
-    if (!message.trim() || !isMessageComplete) return;
+    // 浠巜indow瀵硅薄鑾峰彇娑堟伅鍐呭
+    const inputMessage = (window as any).messageToSend || '';
+    if (!inputMessage.trim() || !isMessageComplete) return;
     if (!apiKey) {
       showErrorMessage('璇峰厛璁剧疆API Key');
       return;
@@ -439,7 +486,7 @@
     // 鍒涘缓鏂版秷鎭�
     const userMessage: Message = {
       role: 'user',
-      content: message.trim(),
+      content: inputMessage.trim(),
       timestamp: Date.now(),
       id: 'user-' + Date.now().toString()
     };
@@ -466,6 +513,9 @@
 
     setCurrentMessageId(assistantMessage.id);
     setMessage('');
+
+    // 娓呴櫎杈撳叆鍐呭
+    (window as any).messageToSend = '';
 
     let controller: AbortController | null = new AbortController();
     
@@ -768,16 +818,6 @@
     });
   }, []);
 
-  // 鏇存柊鍙戦�佹寜閽殑绂佺敤鐘舵��
-  const isSendDisabled = isStreaming || !message.trim() || !isMessageComplete;
-
-  const handleKeyPress = (e: React.KeyboardEvent) => {
-    if (e.key === 'Enter' && !e.shiftKey) {
-      e.preventDefault();
-      handleSendMessage();
-    }
-  };
-
   useEffect(() => {
     // 椤甸潰鍔犺浇鍚庡欢杩熸樉绀烘秷鎭垪琛紝閬垮厤闂儊
     const timer = setTimeout(() => {
@@ -797,6 +837,37 @@
       .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; }
+      
+      /* 浼樺寲鍥捐〃瀹瑰櫒鏍峰紡锛屼笉鍐嶄娇鐢╥solation鍜寃ill-change */
+      .echart-wrapper {
+        position: relative;
+        z-index: auto;
+      }
+      
+      /* 浣跨敤鏇存俯鍜岀殑鎬ц兘浼樺寲灞炴�э紝閬垮厤灞傜骇閿欒 */
+      canvas {
+        transition: none !important;  
+      }
+      
+      /* 淇杈撳叆妗嗛伄鎸¢棶棰� */
+      .fixed.bottom-0 {
+        z-index: 10;
+      }
+      
+      /* 闃叉鍥捐〃闂儊浣嗕笉褰卞搷鍏朵粬鍏冪礌 */
+      .echart-wrapper > div {
+        transform: translateZ(0);
+        will-change: transform;
+        transition: none !important;
+      }
+      
+      /* 淇echarts娓叉煋闂 */
+      [data-echarts-container] {
+        visibility: visible !important;
+        opacity: 1 !important;
+        width: 100% !important;
+        contain: none !important;
+      }
     `;
     document.head.appendChild(style);
 
@@ -970,15 +1041,6 @@
                           : '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瀽娑堟伅鍐呭锛屾彁鍙栨�濊�冮儴鍒�
@@ -1089,41 +1151,12 @@
           </div>
         </div>
 
-        {/* 杈撳叆鍖哄煙 */}
-        <div className="fixed bottom-0 left-0 right-0 bg-gradient-to-t from-white via-white to-white/95 pt-4 pb-6">
-          <div className="max-w-4xl mx-auto px-6">
-            <div className="relative">
-              <div className="absolute -top-6 left-1/2 -translate-x-1/2 w-48 h-[1px] bg-gradient-to-r from-transparent via-gray-200 to-transparent"></div>
-              
-              <div className="flex gap-4 items-start">
-                <div className="flex-1 relative">
-                  <textarea
-                    value={message}
-                    onChange={(e) => setMessage(e.target.value)}
-                    onKeyDown={handleKeyPress}
-                    placeholder="杈撳叆娑堟伅..."
-                    disabled={isStreaming}
-                    className="w-full resize-none rounded-xl border-0 bg-gray-50 px-4 py-3 text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-100/50 focus:bg-white min-h-[48px] max-h-32 shadow-inner transition-all duration-300 ease-in-out hover:bg-gray-100/70 disabled:opacity-50 disabled:cursor-not-allowed"
-                    style={{ height: '48px' }}
-                  />
-                  <div className="absolute right-4 bottom-2 text-xs text-gray-400 bg-gray-50 px-2">
-                    鎸塃nter鍙戦�侊紝Shift+Enter鎹㈣
-                  </div>
-                </div>
-                <button
-                  onClick={handleSendMessage}
-                  disabled={isSendDisabled}
-                  className="h-12 px-5 bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white font-medium rounded-xl transition-all duration-300 flex items-center gap-2 shadow-lg shadow-blue-500/20 hover:shadow-blue-500/30 hover:scale-[1.02] active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none disabled:shadow-none"
-                >
-                  <span>{isStreaming ? '鍥炲涓�...' : (isMessageComplete ? '鍙戦��' : '澶勭悊涓�...')}</span>
-                  <svg xmlns="http://www.w3.org/2000/svg" className={`h-4 w-4 transform rotate-45 ${isStreaming ? 'animate-pulse' : ''}`} viewBox="0 0 20 20" fill="currentColor">
-                    <path d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z" />
-                  </svg>
-                </button>
-              </div>
-            </div>
-          </div>
-        </div>
+        {/* 杈撳叆鍖哄煙 - 鎶藉彇涓虹嫭绔嬬粍浠� */}
+        <ChatInput 
+          onSendMessage={handleSendMessage} 
+          isStreaming={isStreaming} 
+          isMessageComplete={isMessageComplete} 
+        />
       </div>
     </div>
   );

--
Gitblit v1.9.3