From b68687c61e43729e236783d8da37a82b13ffc302 Mon Sep 17 00:00:00 2001
From: hongjli <3117313295@qq.com>
Date: 星期一, 28 四月 2025 11:22:26 +0800
Subject: [PATCH] 渲染echarts图优化
---
src/app/chat/page.tsx | 628 +++++++++++++++++++++++++++++++++++---------------------
1 files changed, 387 insertions(+), 241 deletions(-)
diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx
index 5530946..7c8d06c 100644
--- a/src/app/chat/page.tsx
+++ b/src/app/chat/page.tsx
@@ -43,232 +43,274 @@
// 娣诲姞鐢ㄤ簬瑙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 [isModalOpen, setIsModalOpen] = useState(false);
const chartContainerRef = useRef<HTMLDivElement>(null);
- const codeRef = useRef<string>(code);
- const renderAttemptedRef = useRef(false);
+ const modalChartRef = useRef<HTMLDivElement>(null);
+ const hasRenderedRef = useRef<boolean>(false);
+ const chartInstanceRef = useRef<any>(null);
- // 鏇存柊浠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 initializeChart = async () => {
try {
- // 鍔ㄦ�佸鍏charts
const echarts = await import('echarts');
- if (!chartContainerRef.current) {
- console.warn('鍥捐〃瀹瑰櫒宸蹭笉瀛樺湪');
- return;
- }
-
// 鍒濆鍖栧浘琛�
- chartInstance = echarts.init(chartContainerRef.current);
+ const chartInstance = echarts.init(chartContainerRef.current);
+ chartInstanceRef.current = chartInstance;
- // 浣跨敤Function鏋勯�犲櫒锛屼絾娣诲姞棰濆鐨勪繚鎶�
+ // 灏濊瘯瑙f瀽鍜岃缃浘琛ㄩ�夐」
try {
- const getFinalOption = new Function(`
+ const safeCode = code.replace(/window\.option/g, 'option');
+ const safeFunc = new Function(`
"use strict";
let option;
- let _uniqueOptionVar;
try {
${safeCode}
- return option || _uniqueOptionVar;
+ return option;
} catch (e) {
- console.error("ECharts鍐呴儴鎵ц閿欒:", e);
+ console.error("鍥捐〃浠g爜鎵ц閿欒:", e);
return null;
}
`);
- const chartOption = getFinalOption();
+ const chartOption = safeFunc();
if (chartOption) {
chartInstance.setOption(chartOption);
setError(null);
} else {
- console.warn('鏈幏鍙栧埌鏈夋晥鐨勫浘琛ㄩ厤缃紝浣跨敤澶囩敤閰嶇疆');
- useFallbackOption(chartInstance);
+ throw new Error("鏃犳硶鑾峰彇鍥捐〃閰嶇疆");
}
} catch (e) {
- console.error('鎵ц鍥捐〃浠g爜閿欒:', e);
- setError(e instanceof Error ? e.message : '鍥捐〃浠g爜鎵ц閿欒');
- useFallbackOption(chartInstance);
- }
-
- // 娣诲姞鍝嶅簲寮忚皟鏁�
- const handleResize = () => {
- chartInstance && chartInstance.resize();
- };
-
- window.addEventListener('resize', handleResize);
-
- // 纭繚缁勪欢鍗歌浇鏃舵竻鐞嗚祫婧�
- return () => {
- window.removeEventListener('resize', handleResize);
- if (chartInstance) {
- try {
- chartInstance.dispose();
- } catch (e) {
- console.warn('鍥捐〃瀹炰緥閿�姣佸け璐�', e);
- }
- }
- };
- } catch (e) {
- console.error('鍥捐〃鍒濆鍖栧け璐�:', e);
- setError('鍔犺浇鍥捐〃搴撳け璐�');
- return () => {};
- }
- };
-
- // 澶囩敤閰嶇疆鍑芥暟
- 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[]
- }];
+ console.error('鍥捐〃浠g爜鎵ц閿欒:', e);
+ setError('鍥捐〃閰嶇疆閿欒');
- 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%'
- }
+ // 璁剧疆涓�涓粯璁ゅ浘琛ㄤ互鏄剧ず閿欒
+ chartInstance.setOption({
+ title: { text: '鍥捐〃閰嶇疆閿欒' },
+ xAxis: { type: 'category', data: ['閿欒'] },
+ yAxis: { type: 'value' },
+ series: [{ data: [0], type: 'bar' }]
});
}
- // 璁剧疆澶囩敤閰嶇疆
- instance.setOption(fallbackOption);
+ // 娣诲姞绐楀彛澶у皬鍙樺寲鐩戝惉鍣�
+ const handleResize = () => chartInstance.resize();
+ window.addEventListener('resize', handleResize);
+
+ // 娓呯悊鍑芥暟
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ chartInstance.dispose();
+ };
} catch (e) {
- console.error('搴旂敤澶囩敤閰嶇疆澶辫触:', e);
+ console.error('ECharts鍔犺浇澶辫触:', e);
+ setError('鍥捐〃搴撳姞杞藉け璐�');
+ } finally {
+ setIsLoading(false);
}
};
- // 鍒濆鍖栧浘琛ㄥ苟鑾峰彇娓呯悊鍑芥暟
- 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]);
+ initializeChart();
+ }, 500); // 澧炲姞寤惰繜锛岀‘淇滵OM瀹屽叏娓叉煋
+
+ return () => clearTimeout(timer);
+ }, [code]);
+
+ // 褰撳叏灞忕姸鎬佸彉鍖栨椂閲嶆柊璋冩暣鍥捐〃澶у皬
+ useEffect(() => {
+ if (chartInstanceRef.current) {
+ setTimeout(() => {
+ chartInstanceRef.current.resize();
+ }, 300); // 缁橠OM涓�浜涙椂闂存潵鏇存柊
+ }
+ }, [isModalOpen]);
+
+ // 澶勭悊鍏ㄥ睆鍒囨崲
+ const toggleModal = useCallback(() => {
+ setIsModalOpen(prev => !prev);
+ }, []);
- // 娓叉煋鍥捐〃瀹瑰櫒
+ // 澶勭悊妯℃�佺獥鍙g殑鍥捐〃
+ useEffect(() => {
+ if (!isModalOpen || !modalChartRef.current) return;
+
+ const initModalChart = async () => {
+ try {
+ const echarts = await import('echarts');
+
+ // 鍒濆鍖栨ā鎬佺獥鍙d腑鐨勫浘琛�
+ const modalChartInstance = echarts.init(modalChartRef.current);
+
+ // 濡傛灉涓诲浘琛ㄥ凡缁忓垵濮嬪寲锛屽鐢ㄥ叾閰嶇疆
+ if (chartInstanceRef.current) {
+ const option = chartInstanceRef.current.getOption();
+ modalChartInstance.setOption(option);
+ } else {
+ // 濡傛灉涓诲浘琛ㄦ病鏈夊垵濮嬪寲锛屽皾璇曚粠浠g爜鍒濆鍖�
+ try {
+ const safeCode = code.replace(/window\.option/g, 'option');
+ const safeFunc = new Function(`
+ "use strict";
+ let option;
+ try {
+ ${safeCode}
+ return option;
+ } catch (e) {
+ console.error("妯℃�佸浘琛ㄤ唬鐮佹墽琛岄敊璇�:", e);
+ return null;
+ }
+ `);
+
+ const chartOption = safeFunc();
+
+ if (chartOption) {
+ modalChartInstance.setOption(chartOption);
+ } else {
+ throw new Error("鏃犳硶鑾峰彇妯℃�佸浘琛ㄩ厤缃�");
+ }
+ } catch (e) {
+ console.error('妯℃�佸浘琛ㄤ唬鐮佹墽琛岄敊璇�:', e);
+
+ // 璁剧疆涓�涓粯璁ゅ浘琛�
+ modalChartInstance.setOption({
+ title: { text: '鍥捐〃閰嶇疆閿欒' },
+ xAxis: { type: 'category', data: ['閿欒'] },
+ yAxis: { type: 'value' },
+ series: [{ data: [0], type: 'bar' }]
+ });
+ }
+ }
+
+ // 娣诲姞绐楀彛澶у皬鍙樺寲鐩戝惉鍣�
+ const handleResize = () => modalChartInstance.resize();
+ window.addEventListener('resize', handleResize);
+
+ // 杩斿洖娓呯悊鍑芥暟
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ modalChartInstance.dispose();
+ };
+ } catch (e) {
+ console.error('妯℃�佸浘琛ㄥ垵濮嬪寲澶辫触:', e);
+ }
+ };
+
+ // 寤惰繜涓�鐐规墽琛岋紝纭繚DOM宸叉洿鏂板畬鎴�
+ const timer = setTimeout(initModalChart, 100);
+ return () => clearTimeout(timer);
+ }, [isModalOpen, code]);
+
+ // 绠�鍖栫殑娓叉煋閫昏緫锛岀‘淇濆鍣ㄦ湁鏄庣‘鐨勫昂瀵�
return (
<div className="relative my-4">
+ {isLoading && (
+ <div className="absolute inset-0 flex items-center justify-center bg-white/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>
+ )}
+
{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>
)}
- <div
- ref={chartContainerRef}
- className="w-full bg-white border border-gray-200 rounded-lg overflow-hidden"
- style={{ height: '400px' }}
+
+ {/* 鍥捐〃瀹瑰櫒 */}
+ <div
+ ref={chartContainerRef}
+ className="w-full bg-white border border-gray-200 rounded-lg overflow-hidden chart-container"
+ style={{
+ height: '300px',
+ minHeight: '300px',
+ visibility: 'visible',
+ position: 'relative'
+ }}
+ data-echarts-container="true"
/>
+
+ {/* 鎵撳紑寮圭獥鎸夐挳 */}
+ <button
+ onClick={() => setIsModalOpen(true)}
+ className="absolute top-2 right-2 bg-white/90 hover:bg-white p-2 rounded-full shadow-md z-20 transition-all duration-300 hover:scale-110 cursor-pointer"
+ title="鍦ㄥ脊绐椾腑鏌ョ湅"
+ >
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 3h6v6m0-6L14 9M9 21H3v-6m0 6l7-7" />
+ </svg>
+ </button>
+
+ {/* 寮瑰嚭寮忓璇濇 */}
+ {isModalOpen && (
+ <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/50 p-4">
+ <div className="bg-white rounded-lg w-[90vw] max-w-6xl h-[80vh] relative flex flex-col">
+ {/* 瀵硅瘽妗嗗ご閮� */}
+ <div className="flex justify-between items-center p-4 border-b">
+ <h3 className="text-xl font-semibold text-gray-800">鏁版嵁鍙鍖栧浘琛�</h3>
+ <button
+ onClick={() => setIsModalOpen(false)}
+ className="p-1 rounded-full hover:bg-gray-100 cursor-pointer"
+ aria-label="鍏抽棴"
+ >
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
+ </svg>
+ </button>
+ </div>
+
+ {/* 瀵硅瘽妗嗕富浣� - 鍥捐〃 */}
+ <div className="flex-1 overflow-hidden p-4">
+ <div
+ ref={modalChartRef}
+ className="w-full h-full"
+ data-echarts-modal
+ ></div>
+ </div>
+ </div>
+ </div>
+ )}
</div>
);
}
@@ -322,6 +364,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 cursor-pointer"
+ >
+ <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 +472,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 +581,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 +595,7 @@
// 鍒涘缓鏂版秷鎭�
const userMessage: Message = {
role: 'user',
- content: message.trim(),
+ content: inputMessage.trim(),
timestamp: Date.now(),
id: 'user-' + Date.now().toString()
};
@@ -466,6 +622,9 @@
setCurrentMessageId(assistantMessage.id);
setMessage('');
+
+ // 娓呴櫎杈撳叆鍐呭
+ (window as any).messageToSend = '';
let controller: AbortController | null = new AbortController();
@@ -768,16 +927,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 +946,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 +1150,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瀽娑堟伅鍐呭锛屾彁鍙栨�濊�冮儴鍒�
@@ -1021,7 +1192,9 @@
<div className="p-3">
<div className="text-gray-800 leading-relaxed">
{mainContent ? (
- <MessageCompletionContext.Provider value={isMessageComplete && msg.id === currentMessageId}>
+ <MessageCompletionContext.Provider
+ value={msg.id !== currentMessageId || isMessageComplete}
+ >
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw, rehypeSanitize]}
@@ -1054,7 +1227,9 @@
</div>
{/* 鍔犺浇鎸囩ず鍣� */}
- {msg.role === 'assistant' && !isMessageComplete && (
+ {msg.role === 'assistant' &&
+ !isMessageComplete &&
+ msg.id === currentMessageId && ( // 鍙湪褰撳墠澶勭悊鐨勬秷鎭樉绀哄姞杞芥寚绀哄櫒
<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>
@@ -1089,41 +1264,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