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: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
|
element: LazyLoad(lazy(() => import('@/pages/ai-content-generator')))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/home',
|
||||||
element: LazyLoad(lazy(() => import('@/pages/home')))
|
element: LazyLoad(lazy(() => import('@/pages/home')))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user