From e851d2ca0240038259cf135cc5b03c1564f94573 Mon Sep 17 00:00:00 2001 From: Shu Guang <61069967+shuguangnet@users.noreply.github.com> Date: Sat, 19 Apr 2025 01:11:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BC=80=E5=8F=91=E5=B9=B3=E5=8F=B0&?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=88=86=E6=9E=90&=E6=8A=A5=E5=91=8A?= =?UTF-8?q?=E4=B8=AD=E5=BF=83=E6=8E=A5=E5=85=A5newapi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.ts | 6 + package.json | 6 +- src/pages/Code/index.tsx | 93 +++++----- src/pages/OpenPlatform/index.tsx | 192 ++++++++++---------- src/pages/Report/index.less | 151 +++++++++++++++- src/pages/Report/index.tsx | 289 ++++++++++++++++++++----------- 6 files changed, 494 insertions(+), 243 deletions(-) diff --git a/config/config.ts b/config/config.ts index b39d1dc..8bcf004 100644 --- a/config/config.ts +++ b/config/config.ts @@ -119,6 +119,12 @@ export default defineConfig({ async: true, }, ], + externals: { + 'react-markdown': 'ReactMarkdown', + }, + scripts: [ + 'https://unpkg.com/react-markdown@8.0.7/react-markdown.min.js', + ], //================ pro 插件配置 ================= presets: ['umi-presets-pro'], /** diff --git a/package.json b/package.json index 509d801..7b9b9c3 100644 --- a/package.json +++ b/package.json @@ -53,9 +53,12 @@ "antd": "5.24.5", "antd-style": "^3.6.1", "classnames": "^2.5.1", + "docx": "^9.4.1", "echarts": "^5.6.0", "echarts-for-react": "^3.0.2", + "file-saver": "^2.0.5", "lodash": "^4.17.21", + "marked": "^15.0.8", "mermaid": "^11.6.0", "moment": "^2.30.1", "omit.js": "^2.0.2", @@ -66,7 +69,8 @@ "react-dom": "^18.2.0", "react-echarts": "^0.1.1", "react-helmet-async": "^1.3.0", - "react-intl": "^7.1.6" + "react-intl": "^7.1.6", + "react-markdown": "^10.1.0" }, "devDependencies": { "@ant-design/pro-cli": "^3.3.0", diff --git a/src/pages/Code/index.tsx b/src/pages/Code/index.tsx index 79a9aaa..984234c 100644 --- a/src/pages/Code/index.tsx +++ b/src/pages/Code/index.tsx @@ -11,6 +11,15 @@ const { Dragger } = Upload; const { TabPane } = Tabs; const { TextArea } = Input; +interface AnalysisResponse { + id: string; + choices: { + message: { + content: string; + }; + }[]; +} + const CodeAnalysisPage: React.FC = () => { const [fileList, setFileList] = useState([]); const [loading, setLoading] = useState(false); @@ -33,48 +42,54 @@ const CodeAnalysisPage: React.FC = () => { } setLoading(true); try { - // 这里应该调用后端API进行分析 - // const response = await analyzeCode({ content: codeContent, type: analysisType }); + 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: analysisType === 'er' + ? '请分析这段代码并生成对应的ER图,使用mermaid语法。分析要点:1. 实体关系 2. 属性定义 3. 关系类型' + : '请分析这段代码并生成功能模块图,使用mermaid语法。分析要点:1. 模块划分 2. 依赖关系 3. 调用流程' + }, + { + type: 'text', + text: codeContent + } + ] + } + ], + max_tokens: 2000 + }) + }); + + const data: AnalysisResponse = await response.json(); + const content = data.choices[0].message.content; - // 模拟API响应 - setTimeout(() => { - if (analysisType === 'er') { - setDiagramCode(` -erDiagram - USER ||--o{ POST : creates - POST ||--|{ COMMENT : has - USER { - string username - string email - timestamp created_at - } - POST { - string title - text content - timestamp published_at - } - COMMENT { - text content - timestamp created_at - } - `); - } else { - setDiagramCode(` -graph TD - A[用户模块] --> B[认证服务] - A --> C[个人中心] - B --> D[权限管理] - C --> E[设置] - `); - } - setAnalysisResult('分析完成,建议优化数据库索引结构,添加适当的外键约束。'); - setActiveTab('result'); - message.success('分析成功'); - }, 1500); + // 解析返回的内容,提取 mermaid 图表代码和分析建议 + const [diagramPart, analysisPart] = content.split('分析建议:').map(part => part.trim()); + + // 提取 mermaid 代码块 + const mermaidCode = diagramPart.match(/```mermaid\n([\s\S]*?)\n```/)?.[1] || diagramPart; + + setDiagramCode(mermaidCode); + setAnalysisResult(analysisPart || '暂无具体分析建议'); + setActiveTab('result'); + message.success('分析成功'); } catch (error) { - message.error('分析失败'); + console.error('分析失败:', error); + message.error('分析失败,请重试'); + } finally { + setLoading(false); } - setLoading(false); }; // 处理文件上传 diff --git a/src/pages/OpenPlatform/index.tsx b/src/pages/OpenPlatform/index.tsx index f8015f6..3da8ee1 100644 --- a/src/pages/OpenPlatform/index.tsx +++ b/src/pages/OpenPlatform/index.tsx @@ -1,39 +1,44 @@ import { PageContainer, ProCard } from '@ant-design/pro-components'; import { Button, Card, Col, Descriptions, Input, message, Modal, Row, Space, Table, Tag } from 'antd'; -import React, { useEffect, useState } from 'react'; -import { CopyOutlined, KeyOutlined, ReloadOutlined } from '@ant-design/icons'; +import React, { useState } from 'react'; +import { CopyOutlined } from '@ant-design/icons'; const OpenPlatform: React.FC = () => { - const [loading, setLoading] = useState(false); - const [visible, setVisible] = useState(false); - const [apiKey, setApiKey] = useState(''); - - // TODO:API密钥数据 - const [keyList, setKeyList] = useState([ + const [keyList] = useState([ { id: 1, - accessKey: 'ak_xxxxxxxxxxxx', - secretKey: 'sk_xxxxxxxxxxxx', + accessKey: 'sk-1PBIyxIdJ42yyC11XRNqbEXYDt2eZRNVNbd8XxmKjnPXGh5S', + description: 'GPT-3.5-Turbo API,支持中英文对话,适合日常对话场景', status: 'active', - createTime: '2024-01-01 12:00:00', - expiryTime: '2025-01-01 12:00:00', - calls: 1234, + qps: '3次/秒', + dailyLimit: '1000次/天', + }, + { + id: 2, + accessKey: 'sk-2ABCyxIdJ42yyC11XRNqbEXYDt2eZRNVNbd8XxmKjnPXABCD', + description: 'Claude-2 API,支持多语言对话,适合学术研究场景', + status: 'active', + qps: '2次/秒', + dailyLimit: '500次/天', + }, + { + id: 3, + accessKey: 'sk-3DEFyxIdJ42yyC11XRNqbEXYDt2eZRNVNbd8XxmKjnPXDEF', + description: 'Stable Diffusion API,支持文生图、图生图等功能', + status: 'active', + qps: '1次/秒', + dailyLimit: '100次/天', }, ]); const columns = [ { - title: 'Access Key', + title: 'API Key', dataIndex: 'accessKey', key: 'accessKey', - }, - { - title: 'Secret Key', - dataIndex: 'secretKey', - key: 'secretKey', render: (text: string) => ( - {'*'.repeat(20)} + {text} - - - ), + title: '每日限额', + dataIndex: 'dailyLimit', + key: 'dailyLimit', }, ]; - const handleCreateKey = () => { - setLoading(true); - // 这里应该调用后端API创建密钥 - setTimeout(() => { - const newKey = { - id: keyList.length + 1, - accessKey: `ak_${Math.random().toString(36).substring(2)}`, - secretKey: `sk_${Math.random().toString(36).substring(2)}`, - status: 'active', - createTime: new Date().toLocaleString(), - expiryTime: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toLocaleString(), - calls: 0, - }; - setKeyList([...keyList, newKey]); - setLoading(false); - message.success('创建成功'); - }, 1000); - }; - - const handleDisableKey = (id: number) => { - setKeyList( - keyList.map((key) => - key.id === id ? { ...key, status: key.status === 'active' ? 'inactive' : 'active' } : key, - ), - ); - }; - - const handleDeleteKey = (id: number) => { - Modal.confirm({ - title: '确认删除', - content: '删除后无法恢复,是否继续?', - onOk: () => { - setKeyList(keyList.filter((key) => key.id !== id)); - message.success('删除成功'); - }, - }); - }; - return ( - - - 正常 + + + 本项目提供免费的AI API服务,支持GPT、Claude、Stable Diffusion等多个模型,仅用于学习研究使用。 + + + 1. 请勿用于商业用途 + 2. 请勿滥用API资源 + 3. 遵守相关法律法规 + + + 遇到问题请通过以下方式联系: + Email: support@example.com + GitHub: https://github.com/your-repo - 按调用次数 - 0.01元/次 - 1234次 - } onClick={handleCreateKey} loading={loading}> - 创建密钥 - - } - > - + +
@@ -159,14 +117,17 @@ const OpenPlatform: React.FC = () => {
               {`import requests
 
-url = "https://api.yourdomain.com/v1/generate"
+# GPT-3.5对话示例
+url = "http://api.example.com/v1/chat/completions"
 headers = {
     "Content-Type": "application/json",
-    "X-API-KEY": "your_api_key"
+    "Authorization": "Bearer ${keyList[0].accessKey}"
 }
 data = {
-    "prompt": "你的提示词",
-    "model": "gpt-3.5-turbo"
+    "model": "gpt-3.5-turbo",
+    "messages": [
+        {"role": "user", "content": "你好,请介绍一下你自己"}
+    ]
 }
 
 response = requests.post(url, json=data, headers=headers)
@@ -174,6 +135,29 @@ print(response.json())`}
             
+ + + +
+              {`const axios = require('axios');
+
+async function generateImage() {
+  const response = await axios.post('http://api.example.com/v1/images/generations', {
+    prompt: '一只可爱的猫咪',
+    n: 1,
+    size: '512x512'
+  }, {
+    headers: {
+      'Content-Type': 'application/json',
+      'Authorization': 'Bearer ${keyList[2].accessKey}'
+    }
+  });
+  
+  console.log(response.data);
+}`}
+            
+
+
); diff --git a/src/pages/Report/index.less b/src/pages/Report/index.less index 958b662..3c8c4e2 100644 --- a/src/pages/Report/index.less +++ b/src/pages/Report/index.less @@ -90,4 +90,153 @@ color: #1890ff; } } -} \ No newline at end of file +} + +.reportContent { + padding: 24px; + + :global { + h1 { + font-size: 28px; + margin-bottom: 24px; + } + + h2 { + font-size: 24px; + margin: 20px 0 16px; + } + + h3 { + font-size: 20px; + margin: 16px 0 12px; + } + + h4 { + font-size: 16px; + margin: 12px 0 8px; + } + + p { + margin-bottom: 16px; + line-height: 1.8; + } + + ul, ol { + margin-bottom: 16px; + padding-left: 24px; + + li { + margin-bottom: 8px; + } + } + + table { + width: 100%; + border-collapse: collapse; + margin-bottom: 16px; + + th, td { + border: 1px solid #e8e8e8; + padding: 12px; + text-align: left; + } + + th { + background: #fafafa; + font-weight: 500; + } + + tr:hover { + background: #fafafa; + } + } + } +} + +.container { + margin: 24px; +} + +.steps { + margin-bottom: 24px; +} + +.content { + min-height: 400px; + padding: 24px; +} + +.uploadStep { + max-width: 800px; + margin: 0 auto; +} + +.previewCard { + margin-top: 24px; +} + +.previewTable { + width: 100%; + border-collapse: collapse; + + th, td { + border: 1px solid #e8e8e8; + padding: 12px; + text-align: left; + } + + th { + background: #fafafa; + font-weight: 500; + } +} + +.goalStep { + max-width: 800px; + margin: 0 auto; + + .goalInput { + margin: 24px 0; + } + + .generateButton { + margin-bottom: 24px; + } +} + +.reportStep { + .chartCard { + margin-top: 24px; + } +} + +.markdownList { + margin-left: 24px; + margin-bottom: 16px; + + li { + margin-bottom: 8px; + } + } + + .markdownTable { + width: 100%; + border-collapse: collapse; + margin-bottom: 16px; + + th, td { + border: 1px solid #e8e8e8; + padding: 8px 16px; + text-align: left; + } + + th { + background: #fafafa; + font-weight: 500; + } + + tr:hover { + background: #fafafa; + } + } + diff --git a/src/pages/Report/index.tsx b/src/pages/Report/index.tsx index 1292019..572ef2b 100644 --- a/src/pages/Report/index.tsx +++ b/src/pages/Report/index.tsx @@ -4,12 +4,38 @@ import { InboxOutlined, FileExcelOutlined, BarChartOutlined, FileWordOutlined } import { Upload, Card, Button, Steps, message, Input, Spin, Result, Space, Progress, Alert, Typography } from 'antd'; import type { UploadFile } from 'antd/es/upload/interface'; import ReactECharts from 'echarts-for-react'; +import { marked } from 'marked'; import styles from './index.less'; +import { Document, Packer, Paragraph as DocxParagraph, TextRun } from 'docx'; +import { saveAs } from 'file-saver'; const { Dragger } = Upload; const { TextArea } = Input; const { Title, Paragraph } = Typography; +interface AnalysisResponse { + id: string; + choices: { + message: { + content: string; + }; + }[]; +} + + +interface ReportSection { + title: string; + content: string; +} + +interface ReportData { + title: string; + summary: string; + sections: ReportSection[]; + charts: any[]; + markdown: string; +} + const ReportPage: React.FC = () => { const [fileList, setFileList] = useState([]); const [currentStep, setCurrentStep] = useState(0); @@ -23,94 +49,158 @@ const ReportPage: React.FC = () => { const handleUpload = async (file: File) => { setLoading(true); try { - const formData = new FormData(); - formData.append('file', file); - - // 模拟上传进度 - const timer = setInterval(() => { - setProgress(prev => { - if (prev >= 99) { - clearInterval(timer); - return 99; - } - return prev + 10; - }); - }, 200); + // 读取文件为 base64 + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = async () => { + const base64Image = reader.result as string; + + // 调用 NewAPI 接口 + try { + 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: '请分析这张图表中的数据趋势和关键信息,并给出专业的分析见解。' + }, + { + type: 'image_url', + image_url: { + url: base64Image + } + } + ] + } + ], + max_tokens: 1000 + }) + }); - // 模拟预览数据 - setTimeout(() => { - setPreviewData({ - columns: ['日期', '销售额', '利润'], - data: [ - ['2023-01', 1000, 200], - ['2023-02', 1500, 300], - ['2023-03', 1200, 250], - ] - }); - setProgress(100); - message.success('文件上传成功'); - setCurrentStep(1); - setLoading(false); - clearInterval(timer); - }, 2000); + const data: AnalysisResponse = await response.json(); + + setPreviewData({ + columns: ['分析结果'], + data: [[data.choices[0].message.content]] + }); + + message.success('图片分析成功'); + setCurrentStep(1); + } catch (error) { + message.error('图片分析失败'); + console.error('API调用失败:', error); + } + }; } catch (error) { - message.error('文件上传失败'); + message.error('文件处理失败'); + } finally { setLoading(false); } }; + const generateWordDocument = async (markdown: string) => { + const doc = new Document({ + sections: [ + { + properties: {}, + children: markdown.split('\n').map(line => { + if (line.startsWith('# ')) { + return new DocxParagraph({ + text: line.replace('# ', ''), + heading: 'Heading1' + }); + } + if (line.startsWith('## ')) { + return new DocxParagraph({ + text: line.replace('## ', ''), + heading: 'Heading2' + }); + } + return new DocxParagraph({ + children: [ + new TextRun({ + text: line, + size: 24 + }) + ] + }); + }) + } + ] + }); + + const blob = await Packer.toBlob(doc); + saveAs(blob, '数据分析报告.docx'); +}; + +const handleDownload = async () => { + if (reportData?.markdown) { + try { + await generateWordDocument(reportData.markdown); + message.success('报告下载成功'); + } catch (error) { + message.error('报告下载失败'); + console.error('下载失败:', error); + } + } +}; + const generateReport = async () => { setLoading(true); try { - let progress = 0; - const timer = setInterval(() => { - progress += 20; - if (progress <= 100) { - setProgress(progress); - } - }, 1000); - - setTimeout(() => { - clearInterval(timer); - setProgress(100); - setWordUrl('https://example.com/report.docx'); - setReportData({ - title: '数据分析报告', - summary: '根据您提供的数据,我们生成了详细的分析报告...', - charts: [ + 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: [ { - title: '销售趋势', - type: 'line', - data: { - title: { - text: '销售趋势分析' - }, - tooltip: { - trigger: 'axis' - }, - xAxis: { - type: 'category', - data: ['1月', '2月', '3月'] - }, - yAxis: { - type: 'value' - }, - series: [{ - name: '销售额', - type: 'line', - data: [1000, 1500, 1200], - smooth: true - }] - } + role: 'user', + content: [ + { + type: 'text', + text: `请基于以下分析目标和数据,生成一份详细的markdown格式分析报告,包含标题、概述、详细分析等章节:${goal}\n${previewData?.data[0][0]}` + } + ] } - ] - }); - message.success('报告生成成功'); - setCurrentStep(2); - setLoading(false); - }, 5000); + ], + max_tokens: 2000 + }) + }); + + const data: AnalysisResponse = await response.json(); + const markdownContent = data.choices[0].message.content; + + // 解析 markdown 内容 + const titleMatch = markdownContent.match(/^#\s+(.+)$/m); + const title = titleMatch ? titleMatch[1] : '数据分析报告'; + + setReportData({ + title, + summary: '', + sections: [], + charts: [], + markdown: markdownContent + }); + + setWordUrl('https://example.com/report.docx'); + message.success('报告生成成功'); + setCurrentStep(2); } catch (error) { message.error('报告生成失败'); + } finally { setLoading(false); } }; @@ -141,6 +231,17 @@ const ReportPage: React.FC = () => { ); }; + const renderReport = () => { + if (!reportData?.markdown) return null; + + return ( +
+ ); + }; + const steps = [ { title: '上传文件', @@ -157,14 +258,14 @@ const ReportPage: React.FC = () => { { - const isExcelOrCsv = - file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || - file.type === 'application/vnd.ms-excel' || - file.type === 'text/csv'; - if (!isExcelOrCsv) { - message.error('只支持上传 Excel 或 CSV 文件!'); - return false; - } + // const isExcelOrCsv = + // file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || + // file.type === 'application/vnd.ms-excel' || + // file.type === 'text/csv'; + // if (!isExcelOrCsv) { + // message.error('只支持上传 Excel 或 CSV 文件!'); + // return false; + // } setFileList([file]); handleUpload(file); return false; @@ -242,7 +343,7 @@ const ReportPage: React.FC = () => { , ]} /> - {reportData && ( -
- {reportData.title} - {reportData.summary} - {reportData.charts.map((chart: any, index: number) => ( - - - - ))} -
- )} + {renderReport()}
), @@ -283,8 +374,8 @@ const ReportPage: React.FC = () => { return ( { ); }; -export default ReportPage; \ No newline at end of file + + +export default ReportPage;