295 lines
8.2 KiB
TypeScript
295 lines
8.2 KiB
TypeScript
import React, { useState } from 'react'
|
||
import {
|
||
Card,
|
||
Form,
|
||
Input,
|
||
Button,
|
||
Typography,
|
||
Space,
|
||
Upload,
|
||
message,
|
||
Spin
|
||
} from 'antd'
|
||
import {
|
||
RocketOutlined,
|
||
ReloadOutlined,
|
||
UploadOutlined
|
||
} from '@ant-design/icons'
|
||
import type { UploadFile, UploadProps } from 'antd'
|
||
|
||
const { Title, Text } = Typography
|
||
const { TextArea } = Input
|
||
|
||
const AIContentGenerator: React.FC = () => {
|
||
const [form] = Form.useForm()
|
||
const [generatedResult, setGeneratedResult] = useState<string>('')
|
||
const [isGenerating, setIsGenerating] = useState<boolean>(false)
|
||
const [fileList, setFileList] = useState<UploadFile[]>([])
|
||
|
||
// 处理文件上传
|
||
const handleUpload: UploadProps['customRequest'] = async ({ file, onSuccess, onError }) => {
|
||
try {
|
||
const fileObj = file as File
|
||
const response = await fetch('https://copilot.sino-bridge.com/v1/files/upload', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': 'Bearer app-YG1qx3qGCPsgqMIyBr8tCmJ7'
|
||
},
|
||
body: (() => {
|
||
const formData = new FormData()
|
||
formData.append('file', fileObj)
|
||
return formData
|
||
})()
|
||
})
|
||
|
||
if (response.ok) {
|
||
const data = await response.json()
|
||
message.success('文件上传成功')
|
||
onSuccess && onSuccess(data, {} as any)
|
||
} else {
|
||
throw new Error('文件上传失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('文件上传错误:', error)
|
||
message.error('文件上传失败')
|
||
onError && onError(error as any)
|
||
}
|
||
}
|
||
|
||
// 处理文件变化
|
||
const handleFileChange: UploadProps['onChange'] = (info) => {
|
||
let newFileList = [...info.fileList]
|
||
|
||
// 限制上传文件数量为 3 个
|
||
newFileList = newFileList.slice(-3)
|
||
|
||
setFileList(newFileList)
|
||
}
|
||
|
||
// 生成内容
|
||
const handleGenerate = async () => {
|
||
try {
|
||
const values = await form.validateFields()
|
||
const userInput = values.user_input?.trim()
|
||
|
||
if (!userInput) {
|
||
message.warning('请输入需要生成内容的描述')
|
||
return
|
||
}
|
||
|
||
setIsGenerating(true)
|
||
setGeneratedResult('')
|
||
|
||
// 准备文件数据
|
||
const files = fileList.map(file => {
|
||
if (file.response) {
|
||
return file.response
|
||
}
|
||
return null
|
||
}).filter(Boolean)
|
||
|
||
const requestBody = {
|
||
inputs: {
|
||
user_input: userInput,
|
||
files: files
|
||
},
|
||
query: '1',
|
||
response_mode: 'streaming',
|
||
...(files.length > 0 && {
|
||
preview_url: '1',
|
||
transfer_method: 'local_file',
|
||
user: 'admin'
|
||
})
|
||
}
|
||
|
||
const response = await fetch('https://copilot.sino-bridge.com/v1/chat-messages', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer app-YG1qx3qGCPsgqMIyBr8tCmJ7'
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
})
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`请求失败: ${response.status}`)
|
||
}
|
||
|
||
const reader = response.body?.getReader()
|
||
if (!reader) {
|
||
throw new Error('无法读取响应流')
|
||
}
|
||
|
||
const decoder = new TextDecoder()
|
||
let content = ''
|
||
|
||
while (true) {
|
||
const { done, value } = await reader.read()
|
||
if (done) break
|
||
|
||
const chunk = decoder.decode(value, { stream: true })
|
||
const lines = chunk.split('\n')
|
||
|
||
for (const line of lines) {
|
||
if (line.startsWith('data: ')) {
|
||
const dataStr = line.slice(6)
|
||
if (dataStr === '[DONE]') {
|
||
continue
|
||
}
|
||
try {
|
||
const data = JSON.parse(dataStr)
|
||
if (data.event === 'message' && data.answer) {
|
||
content += data.answer
|
||
setGeneratedResult(content)
|
||
}
|
||
} catch (e) {
|
||
// 忽略解析错误的行
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
message.success('内容生成完成')
|
||
} catch (error) {
|
||
console.error('生成内容错误:', error)
|
||
message.error('生成内容失败,请稍后重试')
|
||
} finally {
|
||
setIsGenerating(false)
|
||
}
|
||
}
|
||
|
||
// 清空重置
|
||
const handleReset = () => {
|
||
form.resetFields()
|
||
setGeneratedResult('')
|
||
setFileList([])
|
||
}
|
||
|
||
return (
|
||
<div style={{
|
||
minHeight: '100vh',
|
||
backgroundColor: '#ffffff',
|
||
padding: '24px',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
alignItems: 'center'
|
||
}}>
|
||
{/* 页面标题区 */}
|
||
<div style={{ marginBottom: 32, textAlign: 'center' }}>
|
||
<Title level={2} style={{ color: '#1890ff', marginBottom: 8 }}>
|
||
AI内容生成工具
|
||
</Title>
|
||
<Text type='secondary'>
|
||
输入您的需求,AI将为您生成高质量内容
|
||
</Text>
|
||
</div>
|
||
|
||
<div style={{
|
||
width: '100%',
|
||
maxWidth: '800px',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
gap: 24
|
||
}}>
|
||
{/* 参数输入区 */}
|
||
<Card title='内容描述' style={{ width: '100%' }}>
|
||
<Form form={form} layout='vertical'>
|
||
<Form.Item
|
||
name='user_input'
|
||
rules={[{ required: true, message: '请输入您需要生成内容的描述' }]}
|
||
>
|
||
<TextArea
|
||
placeholder='请输入您需要生成内容的描述...'
|
||
rows={4}
|
||
style={{ fontSize: 14 }}
|
||
/>
|
||
</Form.Item>
|
||
|
||
{/* 文件上传区 */}
|
||
<Form.Item label='附件(可选)'>
|
||
<Upload
|
||
multiple
|
||
fileList={fileList}
|
||
onChange={handleFileChange}
|
||
customRequest={handleUpload}
|
||
beforeUpload={() => false} // 阻止自动上传
|
||
onRemove={() => {
|
||
setFileList([])
|
||
}}
|
||
>
|
||
<Button icon={<UploadOutlined />}>选择文件</Button>
|
||
</Upload>
|
||
<Text type='secondary' style={{ fontSize: 12, display: 'block', marginTop: 4 }}>
|
||
支持上传相关文件,AI将结合文件内容生成更精准的内容
|
||
</Text>
|
||
</Form.Item>
|
||
|
||
{/* 操作按钮区 */}
|
||
<Form.Item>
|
||
<Space size='middle'>
|
||
<Button
|
||
type='primary'
|
||
icon={<RocketOutlined />}
|
||
onClick={handleGenerate}
|
||
loading={isGenerating}
|
||
size='large'
|
||
>
|
||
{isGenerating ? '生成中...' : '生成内容'}
|
||
</Button>
|
||
<Button
|
||
icon={<ReloadOutlined />}
|
||
onClick={handleReset}
|
||
disabled={isGenerating}
|
||
size='large'
|
||
>
|
||
清空重置
|
||
</Button>
|
||
</Space>
|
||
</Form.Item>
|
||
</Form>
|
||
</Card>
|
||
|
||
{/* 内容展示区 */}
|
||
<Card title='生成结果' style={{ width: '100%', minHeight: '200px' }}>
|
||
{isGenerating ? (
|
||
<div style={{
|
||
display: 'flex',
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
minHeight: '150px',
|
||
flexDirection: 'column',
|
||
gap: 16
|
||
}}>
|
||
<Spin size='large' />
|
||
<Text type='secondary'>AI正在生成内容,请稍候...</Text>
|
||
</div>
|
||
) : generatedResult ? (
|
||
<div style={{
|
||
padding: '16px',
|
||
backgroundColor: '#fafafa',
|
||
borderRadius: '6px',
|
||
whiteSpace: 'pre-wrap',
|
||
wordBreak: 'break-word',
|
||
minHeight: '100px'
|
||
}}>
|
||
{generatedResult}
|
||
</div>
|
||
) : (
|
||
<div style={{
|
||
display: 'flex',
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
minHeight: '100px',
|
||
color: '#999',
|
||
fontStyle: 'italic'
|
||
}}>
|
||
{generatedResult === '' ? '生成的内容将在这里显示...' : ''}
|
||
</div>
|
||
)}
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default AIContentGenerator |