Update from Vibe Studio
This commit is contained in:
64
src/api/ai-content-generator.ts
Normal file
64
src/api/ai-content-generator.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
export interface ContentGenerationRequest {
|
||||
inputs: {
|
||||
user_input: string
|
||||
files?: any[]
|
||||
}
|
||||
query: string
|
||||
response_mode: string
|
||||
preview_url?: string
|
||||
transfer_method?: string
|
||||
user?: string
|
||||
}
|
||||
|
||||
export interface DifyResponse {
|
||||
message: {
|
||||
content: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成AI内容
|
||||
* @param userInput 用户输入的文本
|
||||
* @param files 可选的文件数组
|
||||
* @returns Promise<DifyResponse>
|
||||
*/
|
||||
export function generateContent(userInput: string, files?: any[]) {
|
||||
const requestBody: ContentGenerationRequest = {
|
||||
inputs: {
|
||||
user_input: userInput,
|
||||
files: files || []
|
||||
},
|
||||
query: '1',
|
||||
response_mode: 'streaming',
|
||||
preview_url: files && files.length > 0 ? '1' : undefined,
|
||||
transfer_method: files && files.length > 0 ? 'local_file' : undefined,
|
||||
user: files && files.length > 0 ? 'admin' : undefined
|
||||
}
|
||||
|
||||
return fetch('https://copilot.sino-bridge.com/v1/chat-messages', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer app-YG1qx3qGCPsgqMIyBr8tCmJ7'
|
||||
},
|
||||
body: JSON.stringify(requestBody)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件到Dify平台
|
||||
* @param file 文件对象
|
||||
* @returns Promise<any>
|
||||
*/
|
||||
export function uploadFile(file: File) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
return fetch('https://copilot.sino-bridge.com/v1/files/upload', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer app-YG1qx3qGCPsgqMIyBr8tCmJ7'
|
||||
},
|
||||
body: formData
|
||||
})
|
||||
}
|
||||
295
src/pages/ai-content-generator/index.tsx
Normal file
295
src/pages/ai-content-generator/index.tsx
Normal file
@@ -0,0 +1,295 @@
|
||||
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
|
||||
@@ -30,6 +30,10 @@ const router: RouteObject[] = [
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
element: LazyLoad(lazy(() => import('@/pages/ai-content-generator')))
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
element: LazyLoad(lazy(() => import('@/pages/home')))
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user