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