feat: 抽离AI请求为hooks
This commit is contained in:
parent
c3950fd4c7
commit
c8f251a4a2
51
src/hooks/useAIRequest.ts
Normal file
51
src/hooks/useAIRequest.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import useAddress from './useAddress';
|
||||||
|
|
||||||
|
interface AIRequestOptions {
|
||||||
|
model?: string;
|
||||||
|
maxTokens?: number;
|
||||||
|
systemPrompt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useAIRequest = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { NewAPiAddress } = useAddress();
|
||||||
|
|
||||||
|
const sendRequest = async (content: any, options: AIRequestOptions = {}) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${NewAPiAddress}/v1/chat/completions`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer sk-mw9ekhJlSj3GeGiw0hLRSHlwdkDFst8q6oBfQrW0L15QilbY'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: options.model || 'gpt-4o-mini',
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'system',
|
||||||
|
content: options.systemPrompt || '你是一个智能助手,请根据用户输入进行分析并给出专业的见解。'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content
|
||||||
|
}
|
||||||
|
],
|
||||||
|
max_tokens: options.maxTokens || 2000
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.choices[0].message.content;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { loading, sendRequest };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useAIRequest;
|
5
src/hooks/useAddress.tsx
Normal file
5
src/hooks/useAddress.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const useAddress = () => {
|
||||||
|
const NewAPiAddress = "https://openai.933999.xyz"
|
||||||
|
return {NewAPiAddress};
|
||||||
|
}
|
||||||
|
export default useAddress;
|
@ -6,6 +6,7 @@ import type { UploadFile } from 'antd/es/upload/interface';
|
|||||||
import Mermaid from '@/components/Mermaid';
|
import Mermaid from '@/components/Mermaid';
|
||||||
import MonacoEditor from '@/components/MonacoEditor';
|
import MonacoEditor from '@/components/MonacoEditor';
|
||||||
import html2canvas from 'html2canvas';
|
import html2canvas from 'html2canvas';
|
||||||
|
import useAIRequest from '@/hooks/useAIRequest';
|
||||||
|
|
||||||
const { Dragger } = Upload;
|
const { Dragger } = Upload;
|
||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
@ -28,7 +29,6 @@ const CodeAnalysisPage: React.FC = () => {
|
|||||||
const [codeContent, setCodeContent] = useState<string>('');
|
const [codeContent, setCodeContent] = useState<string>('');
|
||||||
const [activeTab, setActiveTab] = useState<string>('editor');
|
const [activeTab, setActiveTab] = useState<string>('editor');
|
||||||
|
|
||||||
// 处理代码编辑
|
|
||||||
const handleCodeChange = (value: string) => {
|
const handleCodeChange = (value: string) => {
|
||||||
setCodeContent(value);
|
setCodeContent(value);
|
||||||
};
|
};
|
||||||
@ -43,7 +43,7 @@ const CodeAnalysisPage: React.FC = () => {
|
|||||||
|
|
||||||
const canvas = await html2canvas(chartElement, {
|
const canvas = await html2canvas(chartElement, {
|
||||||
useCORS: true,
|
useCORS: true,
|
||||||
scale: 2, // 提高导出图片质量
|
scale: 2,
|
||||||
backgroundColor: '#ffffff'
|
backgroundColor: '#ffffff'
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -62,26 +62,16 @@ const CodeAnalysisPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理代码分析
|
const { loading: aiLoading, sendRequest } = useAIRequest();
|
||||||
|
|
||||||
const handleAnalyze = async () => {
|
const handleAnalyze = async () => {
|
||||||
if (!codeContent.trim()) {
|
if (!codeContent.trim()) {
|
||||||
message.warning('请输入或上传代码');
|
message.warning('请输入或上传代码');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('https://openai.933999.xyz/v1/chat/completions', {
|
const content = await sendRequest([
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer sk-mw9ekhJlSj3GeGiw0hLRSHlwdkDFst8q6oBfQrW0L15QilbY'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
model: 'gpt-4o-mini',
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: 'user',
|
|
||||||
content: [
|
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text: analysisType === 'er'
|
text: analysisType === 'er'
|
||||||
@ -92,20 +82,9 @@ const CodeAnalysisPage: React.FC = () => {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
text: codeContent
|
text: codeContent
|
||||||
}
|
}
|
||||||
]
|
]);
|
||||||
}
|
|
||||||
],
|
|
||||||
max_tokens: 2000
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const data: AnalysisResponse = await response.json();
|
|
||||||
const content = data.choices[0].message.content;
|
|
||||||
|
|
||||||
// 解析返回的内容,提取 mermaid 图表代码和分析建议
|
|
||||||
const [diagramPart, analysisPart] = content.split('分析建议:').map(part => part.trim());
|
const [diagramPart, analysisPart] = content.split('分析建议:').map(part => part.trim());
|
||||||
|
|
||||||
// 提取 mermaid 代码块
|
|
||||||
const mermaidCode = diagramPart.match(/```mermaid\n([\s\S]*?)\n```/)?.[1] || diagramPart;
|
const mermaidCode = diagramPart.match(/```mermaid\n([\s\S]*?)\n```/)?.[1] || diagramPart;
|
||||||
|
|
||||||
setDiagramCode(mermaidCode);
|
setDiagramCode(mermaidCode);
|
||||||
@ -115,27 +94,24 @@ const CodeAnalysisPage: React.FC = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('分析失败:', error);
|
console.error('分析失败:', error);
|
||||||
message.error('分析失败,请重试');
|
message.error('分析失败,请重试');
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理文件上传
|
|
||||||
const handleUpload = async (file: File) => {
|
const handleUpload = async (file: File) => {
|
||||||
setLoading(true);
|
|
||||||
try {
|
try {
|
||||||
// 读取文件内容
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
const content = e.target?.result as string;
|
const content = e.target?.result as string;
|
||||||
setCodeContent(content);
|
setCodeContent(content);
|
||||||
setActiveTab('editor');
|
setActiveTab('editor');
|
||||||
|
message.success('文件上传成功');
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
|
return false; // 阻止默认上传行为
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('文件读取失败');
|
message.error('文件读取失败');
|
||||||
|
console.error('文件读取失败:', error);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -176,7 +152,7 @@ const CodeAnalysisPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<Button>上传文件</Button>
|
<Button>上传文件</Button>
|
||||||
</Dragger>
|
</Dragger>
|
||||||
<Button type="primary" onClick={handleAnalyze} loading={loading}>
|
<Button type="primary" onClick={handleAnalyze} loading={aiLoading}>
|
||||||
开始分析
|
开始分析
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
|
@ -28,6 +28,7 @@ import ReactECharts from 'echarts-for-react';
|
|||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
|
import useAIRequest from '@/hooks/useAIRequest';
|
||||||
|
|
||||||
const { Dragger } = Upload;
|
const { Dragger } = Upload;
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
@ -105,6 +106,8 @@ const AnalysisCenter: React.FC = () => {
|
|||||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { loading: aiLoading, sendRequest } = useAIRequest();
|
||||||
|
|
||||||
const handleSend = async () => {
|
const handleSend = async () => {
|
||||||
if (!inputValue.trim()) return;
|
if (!inputValue.trim()) return;
|
||||||
|
|
||||||
@ -119,22 +122,7 @@ const AnalysisCenter: React.FC = () => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('https://openai.933999.xyz/v1/chat/completions', {
|
const content = await sendRequest([
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer sk-mw9ekhJlSj3GeGiw0hLRSHlwdkDFst8q6oBfQrW0L15QilbY'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
model: 'gpt-4o-mini',
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: 'system',
|
|
||||||
content: '你是一个数据分析专家,请根据用户输入进行分析并生成分析报告和 ECharts 图表配置。图表配置需要包含在 ```json 代码块中。'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'user',
|
|
||||||
content: [
|
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text: `请对以下内容进行${analysisOptions.find(opt => opt.value === analysisType)?.label},并给出专业的分析见解。
|
text: `请对以下内容进行${analysisOptions.find(opt => opt.value === analysisType)?.label},并给出专业的分析见解。
|
||||||
@ -147,18 +135,14 @@ const AnalysisCenter: React.FC = () => {
|
|||||||
|
|
||||||
分析内容:${inputValue}`
|
分析内容:${inputValue}`
|
||||||
}
|
}
|
||||||
]
|
], {
|
||||||
}
|
systemPrompt: '你是一个数据分析专家,请根据用户输入进行分析并生成分析报告和 ECharts 图表配置。图表配置需要包含在 ```json 代码块中。'
|
||||||
],
|
|
||||||
max_tokens: 2000
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
let chartOption;
|
let chartOption;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const matches = result.choices[0].message.content.match(/```json\n([\s\S]*?)\n```/);
|
const matches = content.match(/```json\n([\s\S]*?)\n```/);
|
||||||
if (matches && matches[1]) {
|
if (matches && matches[1]) {
|
||||||
chartOption = JSON.parse(matches[1]);
|
chartOption = JSON.parse(matches[1]);
|
||||||
}
|
}
|
||||||
@ -169,7 +153,7 @@ const AnalysisCenter: React.FC = () => {
|
|||||||
|
|
||||||
const assistantMessage: Message = {
|
const assistantMessage: Message = {
|
||||||
type: 'assistant',
|
type: 'assistant',
|
||||||
content: result.choices[0].message.content.replace(/```json\n[\s\S]*?\n```/g, '').trim(),
|
content: content.replace(/```json\n[\s\S]*?\n```/g, '').trim(),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
charts: chartOption,
|
charts: chartOption,
|
||||||
};
|
};
|
||||||
@ -181,7 +165,7 @@ const AnalysisCenter: React.FC = () => {
|
|||||||
message.error('分析请求失败');
|
message.error('分析请求失败');
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleFileAnalysis = async (file: File) => {
|
const handleFileAnalysis = async (file: File) => {
|
||||||
|
@ -8,6 +8,7 @@ import styles from './index.less';
|
|||||||
import { Document, Packer, Paragraph as DocxParagraph, TextRun } from 'docx';
|
import { Document, Packer, Paragraph as DocxParagraph, TextRun } from 'docx';
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
|
import useAIRequest from '@/hooks/useAIRequest';
|
||||||
|
|
||||||
const { Dragger } = Upload;
|
const { Dragger } = Upload;
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
@ -96,37 +97,22 @@ const ReportPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { loading: aiLoading, sendRequest } = useAIRequest();
|
||||||
|
|
||||||
const analyzeData = async (content: any) => {
|
const analyzeData = async (content: any) => {
|
||||||
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch('https://openai.933999.xyz/v1/chat/completions', {
|
const result = await sendRequest([
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer sk-mw9ekhJlSj3GeGiw0hLRSHlwdkDFst8q6oBfQrW0L15QilbY'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
model: 'gpt-4o-mini',
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: 'user',
|
|
||||||
content: [
|
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text: '请分析这些数据的趋势和关键信息,并给出专业的分析见解。'
|
text: '请分析这些数据的趋势和关键信息,并给出专业的分析见解。'
|
||||||
},
|
},
|
||||||
content
|
content
|
||||||
]
|
]);
|
||||||
}
|
|
||||||
],
|
|
||||||
max_tokens: 1000
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const data: AnalysisResponse = await response.json();
|
|
||||||
|
|
||||||
setPreviewData({
|
setPreviewData({
|
||||||
columns: ['分析结果'],
|
columns: ['分析结果'],
|
||||||
data: [[data.choices[0].message.content]]
|
data: [[result]]
|
||||||
});
|
});
|
||||||
|
|
||||||
message.success('数据分析成功');
|
message.success('数据分析成功');
|
||||||
@ -139,6 +125,37 @@ const ReportPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const generateReport = async () => {
|
||||||
|
try {
|
||||||
|
const result = await sendRequest([
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: `请基于以下分析目标和数据,生成一份详细的markdown格式分析报告,包含标题、概述、详细分析等章节:${goal}\n${previewData?.data[0][0]}`
|
||||||
|
}
|
||||||
|
], {
|
||||||
|
maxTokens: 2000
|
||||||
|
});
|
||||||
|
|
||||||
|
// 解析 markdown 内容
|
||||||
|
const titleMatch = result.match(/^#\s+(.+)$/m);
|
||||||
|
const title = titleMatch ? titleMatch[1] : '数据分析报告';
|
||||||
|
|
||||||
|
setReportData({
|
||||||
|
title,
|
||||||
|
summary: '',
|
||||||
|
sections: [],
|
||||||
|
charts: [],
|
||||||
|
markdown: result
|
||||||
|
});
|
||||||
|
|
||||||
|
setWordUrl('https://example.com/report.docx');
|
||||||
|
message.success('报告生成成功');
|
||||||
|
setCurrentStep(2);
|
||||||
|
} catch (error) {
|
||||||
|
message.error('报告生成失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const generateWordDocument = async (markdown: string) => {
|
const generateWordDocument = async (markdown: string) => {
|
||||||
const doc = new Document({
|
const doc = new Document({
|
||||||
sections: [
|
sections: [
|
||||||
@ -186,57 +203,6 @@ const handleDownload = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateReport = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const response = await fetch('https://openai.933999.xyz/v1/chat/completions', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer sk-mw9ekhJlSj3GeGiw0hLRSHlwdkDFst8q6oBfQrW0L15QilbY'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
model: 'gpt-4o-mini',
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: 'user',
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
text: `请基于以下分析目标和数据,生成一份详细的markdown格式分析报告,包含标题、概述、详细分析等章节:${goal}\n${previewData?.data[0][0]}`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderPreview = () => {
|
const renderPreview = () => {
|
||||||
if (!previewData) return null;
|
if (!previewData) return null;
|
||||||
return (
|
return (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user