From 7c2752d0743e42e46951b2593281027f74297930 Mon Sep 17 00:00:00 2001 From: Shu Guang <61069967+shuguangnet@users.noreply.github.com> Date: Sat, 19 Apr 2025 01:46:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=88=86=E6=9E=90?= =?UTF-8?q?=E4=B8=AD=E5=BF=83=E5=9B=BE=E8=A1=A8=E7=94=9F=E6=88=90&markdown?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Forecast/index.less | 7 + src/pages/Forecast/index.tsx | 484 ++++++++++++++++++++-------------- 2 files changed, 287 insertions(+), 204 deletions(-) diff --git a/src/pages/Forecast/index.less b/src/pages/Forecast/index.less index 090d541..d50a4db 100644 --- a/src/pages/Forecast/index.less +++ b/src/pages/Forecast/index.less @@ -85,6 +85,12 @@ border-radius: 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + :global { + .ant-list-item-meta { + width: 40vw !important; + } + } + .avatar { background: #1890ff; } @@ -92,6 +98,7 @@ .chartContainer { margin-top: 16px; padding: 16px; + width: 40vw; background: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04); diff --git a/src/pages/Forecast/index.tsx b/src/pages/Forecast/index.tsx index a39f6d1..1a5d0ac 100644 --- a/src/pages/Forecast/index.tsx +++ b/src/pages/Forecast/index.tsx @@ -29,6 +29,7 @@ import type { UploadFile } from 'antd/es/upload/interface'; import ReactECharts from 'echarts-for-react'; import styles from './index.less'; import * as XLSX from 'xlsx'; +import { marked } from 'marked'; const { Option } = Select; const { Dragger } = Upload; @@ -107,79 +108,7 @@ const AnalysisCenter: React.FC = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; - const handleFileAnalysis = async (file: File) => { - try { - const reader = new FileReader(); - - reader.onload = async (e) => { - const data = e.target?.result; - let textContent = ''; - - if (file.name.toLowerCase().endsWith('.csv')) { - textContent = data as string; - } else { - const workbook = XLSX.read(data, { type: 'array' }); - const firstSheet = workbook.Sheets[workbook.SheetNames[0]]; - const jsonData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 }); - textContent = jsonData.map(row => row.join('\t')).join('\n'); - } - - const response = await fetch('https://aizex.top/v1/chat/completions', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer sk-Bp4AtAw19a6lENrPUQeqfiS9KP46Z5A43j4QkNeX4NRnGKMU' - }, - body: JSON.stringify({ - model: 'gpt-4o', - messages: [ - { - role: 'user', - content: [ - { - type: 'text', - text: `请对以下数据进行${analysisOptions.find(opt => opt.value === analysisType)?.label},并给出专业的分析见解:\n${textContent}` - } - ] - } - ], - max_tokens: 2000 - }) - }); - - const result = await response.json(); - - const userMessage: Message = { - type: 'user', - content: `已上传文件:${file.name}`, - timestamp: Date.now(), - }; - - const assistantMessage: Message = { - type: 'assistant', - content: result.choices[0].message.content, - timestamp: Date.now(), - charts: generateMockChart(analysisType), - }; - - setMessages(prev => [...prev, userMessage, assistantMessage]); - setLoading(false); - scrollToBottom(); - }; - - if (file.name.toLowerCase().endsWith('.csv')) { - reader.readAsText(file); - } else { - reader.readAsArrayBuffer(file); - } - } catch (error) { - console.error('文件处理失败:', error); - message.error('文件处理失败'); - setLoading(false); - } -}; - - const handleSend = async () => { + const handleSend = async () => { if (!inputValue.trim()) return; const userMessage: Message = { @@ -193,148 +122,295 @@ const AnalysisCenter: React.FC = () => { setLoading(true); try { - // 这里应该调用后端API进行分析 - // const response = await analyzeData({ type: analysisType, message: inputValue }); + const response = await fetch('https://aizex.top/v1/chat/completions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer sk-Bp4AtAw19a6lENrPUQeqfiS9KP46Z5A43j4QkNeX4NRnGKMU' + }, + body: JSON.stringify({ + model: 'gpt-4o', + messages: [ + { + role: 'system', + content: '你是一个数据分析专家,请根据用户输入进行分析并生成分析报告和 ECharts 图表配置。图表配置需要包含在 ```json 代码块中。' + }, + { + role: 'user', + content: [ + { + type: 'text', + text: `请对以下内容进行${analysisOptions.find(opt => opt.value === analysisType)?.label},并给出专业的分析见解。 + 分析报告之后,请生成一个用于可视化的 ECharts 配置对象(使用 \`\`\`json 包裹),配置中需要包含: + 1. 标题、图例、提示框等基本配置 + 2. 合适的图表类型(折线图、柱状图、饼图等) + 3. 坐标轴配置(如果适用) + 4. 数据系列配置 + 5. 主题色彩配置 + + 分析内容:${inputValue}` + } + ] + } + ], + max_tokens: 2000 + }) + }); + + const result = await response.json(); + let chartOption; - // 模拟API响应 - setTimeout(() => { - const assistantMessage: Message = { - type: 'assistant', - content: generateMockResponse(analysisType), - timestamp: Date.now(), - charts: generateMockChart(analysisType), - }; - setMessages(prev => [...prev, assistantMessage]); - setLoading(false); - scrollToBottom(); - }, 1500); + try { + const matches = result.choices[0].message.content.match(/```json\n([\s\S]*?)\n```/); + if (matches && matches[1]) { + chartOption = JSON.parse(matches[1]); + } + } catch (error) { + console.error('解析图表配置失败:', error); + chartOption = generateMockChart(analysisType); + } + + const assistantMessage: Message = { + type: 'assistant', + content: result.choices[0].message.content.replace(/```json\n[\s\S]*?\n```/g, '').trim(), + timestamp: Date.now(), + charts: chartOption, + }; + + setMessages(prev => [...prev, assistantMessage]); + setLoading(false); + scrollToBottom(); } catch (error) { message.error('分析请求失败'); setLoading(false); } + } + + + const handleFileAnalysis = async (file: File) => { + try { + const reader = new FileReader(); + + reader.onload = async (e) => { + const data = e.target?.result; + let textContent = ''; + + if (file.name.toLowerCase().endsWith('.csv')) { + textContent = data as string; + } else { + const workbook = XLSX.read(data, { type: 'array' }); + const firstSheet = workbook.Sheets[workbook.SheetNames[0]]; + const jsonData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 }); + textContent = jsonData.map(row => row.join('\t')).join('\n'); + } + + const response = await fetch('https://aizex.top/v1/chat/completions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer sk-Bp4AtAw19a6lENrPUQeqfiS9KP46Z5A43j4QkNeX4NRnGKMU' + }, + body: JSON.stringify({ + model: 'gpt-4o', + messages: [ + { + role: 'system', + content: '你是一个数据分析专家,请根据用户输入的数据进行分析并生成分析报告和 ECharts 图表配置。图表配置需要包含在 ```json 代码块中。' + }, + { + role: 'user', + content: [ + { + type: 'text', + text: `请对以下数据进行${analysisOptions.find(opt => opt.value === analysisType)?.label},并给出专业的分析见解。 + 分析报告之后,请生成一个用于可视化的 ECharts 配置对象(使用 \`\`\`json 包裹),配置中需要包含: + 1. 标题、图例、提示框等基本配置 + 2. 合适的图表类型(折线图、柱状图、饼图等) + 3. 坐标轴配置(如果适用) + 4. 数据系列配置 + 5. 主题色彩配置 + + 数据内容:\n${textContent}` + } + ] + } + ], + max_tokens: 2000 + }) + }); + + const result = await response.json(); + let chartOption; + + try { + const matches = result.choices[0].message.content.match(/```json\n([\s\S]*?)\n```/); + if (matches && matches[1]) { + chartOption = JSON.parse(matches[1]); + } + } catch (error) { + console.error('解析图表配置失败:', error); + message.error('图表生成失败,使用默认图表'); + chartOption = generateMockChart(analysisType); + } + + const userMessage: Message = { + type: 'user', + content: `已上传文件:${file.name}`, + timestamp: Date.now(), + }; + + const assistantMessage: Message = { + type: 'assistant', + content: marked(result.choices[0].message.content + .replace(/```json\n[\s\S]*?\n```/g, '') // 先移除 JSON 代码块 + .trim() + ), + timestamp: Date.now(), + charts: chartOption, + }; + + setMessages(prev => [...prev, userMessage, assistantMessage]); + setLoading(false); + scrollToBottom(); + }; + + if (file.name.toLowerCase().endsWith('.csv')) { + reader.readAsText(file); + } else { + reader.readAsArrayBuffer(file); + } + } catch (error) { + console.error('文件处理失败:', error); + message.error('文件处理失败'); + setLoading(false); + } }; - const generateMockResponse = (type: string) => { - const responses: { [key: string]: string } = { - predictive: '根据历史数据分析,预计未来三个月的销售增长率将达到15%,主要增长点来自新市场的开拓。', - descriptive: '数据集中包含1000条记录,平均值为45.6,标准差为12.3,分布呈现正态分布特征。', - anomaly: '检测到3个异常值点,主要出现在数据的边缘区域,建议进行进一步核实。', - quality: '数据完整性为98.5%,存在少量缺失值,建议对缺失数据进行适当的填充处理。', + const generateMockResponse = (type: string) => { + const responses: { [key: string]: string } = { + predictive: '根据历史数据分析,预计未来三个月的销售增长率将达到15%,主要增长点来自新市场的开拓。', + descriptive: '数据集中包含1000条记录,平均值为45.6,标准差为12.3,分布呈现正态分布特征。', + anomaly: '检测到3个异常值点,主要出现在数据的边缘区域,建议进行进一步核实。', + quality: '数据完整性为98.5%,存在少量缺失值,建议对缺失数据进行适当的填充处理。', + }; + return responses[type] || '分析完成'; }; - return responses[type] || '分析完成'; + + return ( + + + +
+ {analysisOptions.map(option => ( + + setAnalysisType(option.value)} + > + {option.label} + + + ))} +
+ + + setFileList(fileList)} + beforeUpload={(file) => { + const isExcelOrCsv = /\.(xlsx|xls|csv)$/.test(file.name.toLowerCase()); + if (!isExcelOrCsv) { + message.error('只支持 Excel 或 CSV 文件!'); + return false; + } + setFileList([file]); + setLoading(true); + handleFileAnalysis(file); + return false; + }} + > +

+ +

+

点击或拖拽文件上传

+

支持 Excel (.xlsx, .xls) 或 CSV 文件格式

+
+
+ +
+ ( + + + : } + className={styles.avatar} + style={{ padding: '8px' }} + /> + } + title={item.type === 'user' ? '你' : 'AI 助手'} + description={ +
+ } + /> + {item.charts && ( +
+ +
+ )} + + + )} + /> +
+
+ +
+