feat: 开发平台&代码分析&报告中心接入newapi
This commit is contained in:
parent
326689155f
commit
e851d2ca02
@ -119,6 +119,12 @@ export default defineConfig({
|
|||||||
async: true,
|
async: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
externals: {
|
||||||
|
'react-markdown': 'ReactMarkdown',
|
||||||
|
},
|
||||||
|
scripts: [
|
||||||
|
'https://unpkg.com/react-markdown@8.0.7/react-markdown.min.js',
|
||||||
|
],
|
||||||
//================ pro 插件配置 =================
|
//================ pro 插件配置 =================
|
||||||
presets: ['umi-presets-pro'],
|
presets: ['umi-presets-pro'],
|
||||||
/**
|
/**
|
||||||
|
@ -53,9 +53,12 @@
|
|||||||
"antd": "5.24.5",
|
"antd": "5.24.5",
|
||||||
"antd-style": "^3.6.1",
|
"antd-style": "^3.6.1",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
|
"docx": "^9.4.1",
|
||||||
"echarts": "^5.6.0",
|
"echarts": "^5.6.0",
|
||||||
"echarts-for-react": "^3.0.2",
|
"echarts-for-react": "^3.0.2",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"marked": "^15.0.8",
|
||||||
"mermaid": "^11.6.0",
|
"mermaid": "^11.6.0",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"omit.js": "^2.0.2",
|
"omit.js": "^2.0.2",
|
||||||
@ -66,7 +69,8 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-echarts": "^0.1.1",
|
"react-echarts": "^0.1.1",
|
||||||
"react-helmet-async": "^1.3.0",
|
"react-helmet-async": "^1.3.0",
|
||||||
"react-intl": "^7.1.6"
|
"react-intl": "^7.1.6",
|
||||||
|
"react-markdown": "^10.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/pro-cli": "^3.3.0",
|
"@ant-design/pro-cli": "^3.3.0",
|
||||||
|
@ -11,6 +11,15 @@ const { Dragger } = Upload;
|
|||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
|
|
||||||
|
interface AnalysisResponse {
|
||||||
|
id: string;
|
||||||
|
choices: {
|
||||||
|
message: {
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
const CodeAnalysisPage: React.FC = () => {
|
const CodeAnalysisPage: React.FC = () => {
|
||||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@ -33,48 +42,54 @@ const CodeAnalysisPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
// 这里应该调用后端API进行分析
|
const response = await fetch('https://aizex.top/v1/chat/completions', {
|
||||||
// const response = await analyzeCode({ content: codeContent, type: analysisType });
|
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响应
|
// 解析返回的内容,提取 mermaid 图表代码和分析建议
|
||||||
setTimeout(() => {
|
const [diagramPart, analysisPart] = content.split('分析建议:').map(part => part.trim());
|
||||||
if (analysisType === 'er') {
|
|
||||||
setDiagramCode(`
|
// 提取 mermaid 代码块
|
||||||
erDiagram
|
const mermaidCode = diagramPart.match(/```mermaid\n([\s\S]*?)\n```/)?.[1] || diagramPart;
|
||||||
USER ||--o{ POST : creates
|
|
||||||
POST ||--|{ COMMENT : has
|
setDiagramCode(mermaidCode);
|
||||||
USER {
|
setAnalysisResult(analysisPart || '暂无具体分析建议');
|
||||||
string username
|
setActiveTab('result');
|
||||||
string email
|
message.success('分析成功');
|
||||||
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);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('分析失败');
|
console.error('分析失败:', error);
|
||||||
|
message.error('分析失败,请重试');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理文件上传
|
// 处理文件上传
|
||||||
|
@ -1,39 +1,44 @@
|
|||||||
import { PageContainer, ProCard } from '@ant-design/pro-components';
|
import { PageContainer, ProCard } from '@ant-design/pro-components';
|
||||||
import { Button, Card, Col, Descriptions, Input, message, Modal, Row, Space, Table, Tag } from 'antd';
|
import { Button, Card, Col, Descriptions, Input, message, Modal, Row, Space, Table, Tag } from 'antd';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { CopyOutlined, KeyOutlined, ReloadOutlined } from '@ant-design/icons';
|
import { CopyOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
const OpenPlatform: React.FC = () => {
|
const OpenPlatform: React.FC = () => {
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [keyList] = useState([
|
||||||
const [visible, setVisible] = useState<boolean>(false);
|
|
||||||
const [apiKey, setApiKey] = useState<string>('');
|
|
||||||
|
|
||||||
// TODO:API密钥数据
|
|
||||||
const [keyList, setKeyList] = useState([
|
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
accessKey: 'ak_xxxxxxxxxxxx',
|
accessKey: 'sk-1PBIyxIdJ42yyC11XRNqbEXYDt2eZRNVNbd8XxmKjnPXGh5S',
|
||||||
secretKey: 'sk_xxxxxxxxxxxx',
|
description: 'GPT-3.5-Turbo API,支持中英文对话,适合日常对话场景',
|
||||||
status: 'active',
|
status: 'active',
|
||||||
createTime: '2024-01-01 12:00:00',
|
qps: '3次/秒',
|
||||||
expiryTime: '2025-01-01 12:00:00',
|
dailyLimit: '1000次/天',
|
||||||
calls: 1234,
|
},
|
||||||
|
{
|
||||||
|
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 = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: 'Access Key',
|
title: 'API Key',
|
||||||
dataIndex: 'accessKey',
|
dataIndex: 'accessKey',
|
||||||
key: 'accessKey',
|
key: 'accessKey',
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Secret Key',
|
|
||||||
dataIndex: 'secretKey',
|
|
||||||
key: 'secretKey',
|
|
||||||
render: (text: string) => (
|
render: (text: string) => (
|
||||||
<Space>
|
<Space>
|
||||||
<span>{'*'.repeat(20)}</span>
|
<span>{text}</span>
|
||||||
<Button
|
<Button
|
||||||
icon={<CopyOutlined />}
|
icon={<CopyOutlined />}
|
||||||
type="link"
|
type="link"
|
||||||
@ -45,112 +50,65 @@ const OpenPlatform: React.FC = () => {
|
|||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '描述',
|
||||||
|
dataIndex: 'description',
|
||||||
|
key: 'description',
|
||||||
|
width: '40%',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
key: 'status',
|
key: 'status',
|
||||||
render: (status: string) => (
|
render: (status: string) => (
|
||||||
<Tag color={status === 'active' ? 'green' : 'red'}>
|
<Tag color={status === 'active' ? 'green' : 'red'}>
|
||||||
{status === 'active' ? '启用' : '禁用'}
|
{status === 'active' ? '正常' : '维护中'}
|
||||||
</Tag>
|
</Tag>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '创建时间',
|
title: 'QPS限制',
|
||||||
dataIndex: 'createTime',
|
dataIndex: 'qps',
|
||||||
key: 'createTime',
|
key: 'qps',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '过期时间',
|
title: '每日限额',
|
||||||
dataIndex: 'expiryTime',
|
dataIndex: 'dailyLimit',
|
||||||
key: 'expiryTime',
|
key: 'dailyLimit',
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '调用次数',
|
|
||||||
dataIndex: 'calls',
|
|
||||||
key: 'calls',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '操作',
|
|
||||||
key: 'action',
|
|
||||||
render: (_: any, record: any) => (
|
|
||||||
<Space>
|
|
||||||
<Button type="link" danger onClick={() => handleDisableKey(record.id)}>
|
|
||||||
{record.status === 'active' ? '禁用' : '启用'}
|
|
||||||
</Button>
|
|
||||||
<Button type="link" danger onClick={() => handleDeleteKey(record.id)}>
|
|
||||||
删除
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
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 (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<ProCard>
|
<ProCard>
|
||||||
<Descriptions title="接口信息" column={2}>
|
<Descriptions title="公益API说明" column={1}>
|
||||||
<Descriptions.Item label="接口状态">
|
<Descriptions.Item label="项目介绍">
|
||||||
<Tag color="green">正常</Tag>
|
本项目提供免费的AI API服务,支持GPT、Claude、Stable Diffusion等多个模型,仅用于学习研究使用。
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="使用须知">
|
||||||
|
1. 请勿用于商业用途
|
||||||
|
2. 请勿滥用API资源
|
||||||
|
3. 遵守相关法律法规
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="联系方式">
|
||||||
|
遇到问题请通过以下方式联系:
|
||||||
|
Email: support@example.com
|
||||||
|
GitHub: https://github.com/your-repo
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="计费方式">按调用次数</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="单价">0.01元/次</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="总调用次数">1234次</Descriptions.Item>
|
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</ProCard>
|
</ProCard>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Card
|
<Card style={{ marginTop: 16 }} title="可用API列表">
|
||||||
style={{ marginTop: 16 }}
|
<Table
|
||||||
title="API密钥管理"
|
columns={columns}
|
||||||
extra={
|
dataSource={keyList}
|
||||||
<Button type="primary" icon={<KeyOutlined />} onClick={handleCreateKey} loading={loading}>
|
rowKey="id"
|
||||||
创建密钥
|
pagination={false}
|
||||||
</Button>
|
/>
|
||||||
}
|
|
||||||
>
|
|
||||||
<Table columns={columns} dataSource={keyList} rowKey="id" />
|
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card style={{ marginTop: 16 }} title="调用示例">
|
<Card style={{ marginTop: 16 }} title="调用示例">
|
||||||
@ -159,14 +117,17 @@ const OpenPlatform: React.FC = () => {
|
|||||||
<pre>
|
<pre>
|
||||||
{`import requests
|
{`import requests
|
||||||
|
|
||||||
url = "https://api.yourdomain.com/v1/generate"
|
# GPT-3.5对话示例
|
||||||
|
url = "http://api.example.com/v1/chat/completions"
|
||||||
headers = {
|
headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"X-API-KEY": "your_api_key"
|
"Authorization": "Bearer ${keyList[0].accessKey}"
|
||||||
}
|
}
|
||||||
data = {
|
data = {
|
||||||
"prompt": "你的提示词",
|
"model": "gpt-3.5-turbo",
|
||||||
"model": "gpt-3.5-turbo"
|
"messages": [
|
||||||
|
{"role": "user", "content": "你好,请介绍一下你自己"}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(url, json=data, headers=headers)
|
response = requests.post(url, json=data, headers=headers)
|
||||||
@ -174,6 +135,29 @@ print(response.json())`}
|
|||||||
</pre>
|
</pre>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
|
|
||||||
|
<Descriptions title="Node.js示例" column={1}>
|
||||||
|
<Descriptions.Item>
|
||||||
|
<pre>
|
||||||
|
{`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);
|
||||||
|
}`}
|
||||||
|
</pre>
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
</Card>
|
</Card>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
@ -90,4 +90,153 @@
|
|||||||
color: #1890ff;
|
color: #1890ff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -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 { Upload, Card, Button, Steps, message, Input, Spin, Result, Space, Progress, Alert, Typography } from 'antd';
|
||||||
import type { UploadFile } from 'antd/es/upload/interface';
|
import type { UploadFile } from 'antd/es/upload/interface';
|
||||||
import ReactECharts from 'echarts-for-react';
|
import ReactECharts from 'echarts-for-react';
|
||||||
|
import { marked } from 'marked';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
import { Document, Packer, Paragraph as DocxParagraph, TextRun } from 'docx';
|
||||||
|
import { saveAs } from 'file-saver';
|
||||||
|
|
||||||
const { Dragger } = Upload;
|
const { Dragger } = Upload;
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
const { Title, Paragraph } = Typography;
|
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 ReportPage: React.FC = () => {
|
||||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||||
const [currentStep, setCurrentStep] = useState(0);
|
const [currentStep, setCurrentStep] = useState(0);
|
||||||
@ -23,94 +49,158 @@ const ReportPage: React.FC = () => {
|
|||||||
const handleUpload = async (file: File) => {
|
const handleUpload = async (file: File) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
// 读取文件为 base64
|
||||||
formData.append('file', file);
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
// 模拟上传进度
|
reader.onload = async () => {
|
||||||
const timer = setInterval(() => {
|
const base64Image = reader.result as string;
|
||||||
setProgress(prev => {
|
|
||||||
if (prev >= 99) {
|
// 调用 NewAPI 接口
|
||||||
clearInterval(timer);
|
try {
|
||||||
return 99;
|
const response = await fetch('https://aizex.top/v1/chat/completions', {
|
||||||
}
|
method: 'POST',
|
||||||
return prev + 10;
|
headers: {
|
||||||
});
|
'Content-Type': 'application/json',
|
||||||
}, 200);
|
'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
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
// 模拟预览数据
|
const data: AnalysisResponse = await response.json();
|
||||||
setTimeout(() => {
|
|
||||||
setPreviewData({
|
setPreviewData({
|
||||||
columns: ['日期', '销售额', '利润'],
|
columns: ['分析结果'],
|
||||||
data: [
|
data: [[data.choices[0].message.content]]
|
||||||
['2023-01', 1000, 200],
|
});
|
||||||
['2023-02', 1500, 300],
|
|
||||||
['2023-03', 1200, 250],
|
message.success('图片分析成功');
|
||||||
]
|
setCurrentStep(1);
|
||||||
});
|
} catch (error) {
|
||||||
setProgress(100);
|
message.error('图片分析失败');
|
||||||
message.success('文件上传成功');
|
console.error('API调用失败:', error);
|
||||||
setCurrentStep(1);
|
}
|
||||||
setLoading(false);
|
};
|
||||||
clearInterval(timer);
|
|
||||||
}, 2000);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('文件上传失败');
|
message.error('文件处理失败');
|
||||||
|
} finally {
|
||||||
setLoading(false);
|
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 () => {
|
const generateReport = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
let progress = 0;
|
const response = await fetch('https://aizex.top/v1/chat/completions', {
|
||||||
const timer = setInterval(() => {
|
method: 'POST',
|
||||||
progress += 20;
|
headers: {
|
||||||
if (progress <= 100) {
|
'Content-Type': 'application/json',
|
||||||
setProgress(progress);
|
'Authorization': 'Bearer sk-Bp4AtAw19a6lENrPUQeqfiS9KP46Z5A43j4QkNeX4NRnGKMU'
|
||||||
}
|
},
|
||||||
}, 1000);
|
body: JSON.stringify({
|
||||||
|
model: 'gpt-4o',
|
||||||
setTimeout(() => {
|
messages: [
|
||||||
clearInterval(timer);
|
|
||||||
setProgress(100);
|
|
||||||
setWordUrl('https://example.com/report.docx');
|
|
||||||
setReportData({
|
|
||||||
title: '数据分析报告',
|
|
||||||
summary: '根据您提供的数据,我们生成了详细的分析报告...',
|
|
||||||
charts: [
|
|
||||||
{
|
{
|
||||||
title: '销售趋势',
|
role: 'user',
|
||||||
type: 'line',
|
content: [
|
||||||
data: {
|
{
|
||||||
title: {
|
type: 'text',
|
||||||
text: '销售趋势分析'
|
text: `请基于以下分析目标和数据,生成一份详细的markdown格式分析报告,包含标题、概述、详细分析等章节:${goal}\n${previewData?.data[0][0]}`
|
||||||
},
|
}
|
||||||
tooltip: {
|
]
|
||||||
trigger: 'axis'
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: ['1月', '2月', '3月']
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value'
|
|
||||||
},
|
|
||||||
series: [{
|
|
||||||
name: '销售额',
|
|
||||||
type: 'line',
|
|
||||||
data: [1000, 1500, 1200],
|
|
||||||
smooth: true
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
});
|
max_tokens: 2000
|
||||||
message.success('报告生成成功');
|
})
|
||||||
setCurrentStep(2);
|
});
|
||||||
setLoading(false);
|
|
||||||
}, 5000);
|
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) {
|
} catch (error) {
|
||||||
message.error('报告生成失败');
|
message.error('报告生成失败');
|
||||||
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -141,6 +231,17 @@ const ReportPage: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderReport = () => {
|
||||||
|
if (!reportData?.markdown) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.reportContent}
|
||||||
|
dangerouslySetInnerHTML={{ __html: marked(reportData.markdown) }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{
|
{
|
||||||
title: '上传文件',
|
title: '上传文件',
|
||||||
@ -157,14 +258,14 @@ const ReportPage: React.FC = () => {
|
|||||||
<Dragger
|
<Dragger
|
||||||
fileList={fileList}
|
fileList={fileList}
|
||||||
beforeUpload={(file) => {
|
beforeUpload={(file) => {
|
||||||
const isExcelOrCsv =
|
// const isExcelOrCsv =
|
||||||
file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
|
// file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
|
||||||
file.type === 'application/vnd.ms-excel' ||
|
// file.type === 'application/vnd.ms-excel' ||
|
||||||
file.type === 'text/csv';
|
// file.type === 'text/csv';
|
||||||
if (!isExcelOrCsv) {
|
// if (!isExcelOrCsv) {
|
||||||
message.error('只支持上传 Excel 或 CSV 文件!');
|
// message.error('只支持上传 Excel 或 CSV 文件!');
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
setFileList([file]);
|
setFileList([file]);
|
||||||
handleUpload(file);
|
handleUpload(file);
|
||||||
return false;
|
return false;
|
||||||
@ -242,7 +343,7 @@ const ReportPage: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<FileWordOutlined />}
|
icon={<FileWordOutlined />}
|
||||||
onClick={() => window.open(wordUrl)}
|
onClick={handleDownload}
|
||||||
key="download"
|
key="download"
|
||||||
size="large"
|
size="large"
|
||||||
>
|
>
|
||||||
@ -264,17 +365,7 @@ const ReportPage: React.FC = () => {
|
|||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{reportData && (
|
{renderReport()}
|
||||||
<div className={styles.reportPreview}>
|
|
||||||
<Title level={4}>{reportData.title}</Title>
|
|
||||||
<Paragraph>{reportData.summary}</Paragraph>
|
|
||||||
{reportData.charts.map((chart: any, index: number) => (
|
|
||||||
<Card key={index} title={chart.title} className={styles.chartCard}>
|
|
||||||
<ReactECharts option={chart.data} style={{ height: 300 }} />
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@ -283,8 +374,8 @@ const ReportPage: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
title="智能报告生成"
|
title="智能图表分析"
|
||||||
subTitle="上传数据文件,快速生成专业分析报告"
|
subTitle="上传数据图表,快速获取专业分析见解"
|
||||||
>
|
>
|
||||||
<Card className={styles.container}>
|
<Card className={styles.container}>
|
||||||
<Steps
|
<Steps
|
||||||
@ -300,4 +391,6 @@ const ReportPage: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ReportPage;
|
|
||||||
|
|
||||||
|
export default ReportPage;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user