feat:20260126修改

This commit is contained in:
hjjjj 2026-01-26 18:26:20 +08:00
parent 482fd2d292
commit d72991aa95
8 changed files with 402 additions and 203 deletions

View File

@ -8,7 +8,12 @@
"Bash(npx tsc:*)",
"Bash(findstr:*)",
"Bash(tree:*)",
"Bash(npx eslint:*)"
"Bash(npx eslint:*)",
"Bash(curl:*)",
"Bash(node:*)",
"Bash(netstat:*)",
"Bash(tail:*)",
"Bash(tasklist:*)"
]
}
}

View File

@ -103,7 +103,7 @@ async def execute_generate_characters(
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
],
temperature=0.7
temperature=0.9
)
task_manager.update_task_progress(
@ -188,7 +188,7 @@ async def execute_generate_outline(
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
],
temperature=0.7
temperature=0.9
)
task_manager.update_task_progress(
@ -268,7 +268,7 @@ async def execute_generate_world(
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
],
temperature=0.7
temperature=0.9
)
task_manager.update_task_progress(

View File

@ -56,6 +56,8 @@ class GlobalContext(BaseModel):
sceneSettings: Dict[str, SceneSetting] = Field(default_factory=dict)
overallOutline: str = ""
styleGuide: str = ""
uploadedScript: str = "" # 上传的剧本内容
inspiration: str = "" # 创意灵感内容
class Memory(BaseModel):

View File

@ -71,7 +71,7 @@ class TaskCreateRequest(BaseModel):
class TaskResponse(BaseModel):
"""任务响应"""
id: str
id: str = Field(serialization_alias="taskId")
type: TaskType
status: TaskStatus
progress: TaskProgress

View File

@ -106,7 +106,7 @@ export const TaskProgressTracker = ({
clearTimeout(pollTimer)
}
}
}, [taskId, pollInterval, onComplete, onError])
}, [taskId, pollInterval])
// 获取状态图标
const getStatusIcon = (status: string) => {
@ -284,7 +284,7 @@ export const StageProgressTracker = ({
clearTimeout(pollTimer)
}
}
}, [taskId, pollInterval, onComplete, onError])
}, [taskId, pollInterval])
// 获取当前阶段索引
const getCurrentStageIndex = () => {

View File

@ -7,19 +7,18 @@ import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import {
Form, Input, InputNumber, Button, Card, Steps, message,
Upload, Select, Space, Divider, Tag, Alert, Popover, Progress, Modal, Radio
Upload, Select, Space, Divider, Tag, Alert, Popover, Progress, Radio
} from 'antd'
import {
UploadOutlined, FileTextOutlined, RobotOutlined,
CheckCircleOutlined, ArrowRightOutlined, SettingOutlined,
ThunderboltOutlined, CloseOutlined
ThunderboltOutlined, EditOutlined
} from '@ant-design/icons'
import { useProjectStore } from '@/stores/projectStore'
import { useSkillStore } from '@/stores/skillStore'
import { api } from '@/services'
import { ProjectCreateRequest } from '@/services/projectService'
import { taskService } from '@/services/taskService'
import { TaskProgressTracker, TaskResultDisplay } from '@/components/TaskProgressTracker'
const { TextArea } = Input
const { Step } = Steps
@ -253,8 +252,6 @@ export const ProjectCreateEnhanced = () => {
// 异步任务状态
const [currentTaskId, setCurrentTaskId] = useState<string | null>(null)
const [showTaskProgress, setShowTaskProgress] = useState(false)
const [taskResult, setTaskResult] = useState<any>(null)
const [taskType, setTaskType] = useState<string>('')
useEffect(() => {
@ -378,9 +375,13 @@ export const ProjectCreateEnhanced = () => {
}
// AI 生成人物设定
// 启动异步AI生成人物任务
const handleAIGenerateCharacters = async () => {
// 防止连续触发
if (aiGeneratingCharacters) return
setAiGeneratingCharacters(true)
message.loading('AI 正在生成人物设定...', 0)
try {
const projectName = form.getFieldValue('name') || '未命名项目'
const totalEpisodes = form.getFieldValue('totalEpisodes') || 30
@ -401,67 +402,92 @@ export const ProjectCreateEnhanced = () => {
customPrompt: characterPrompt || undefined
})
// 显示任务进度
// 设置任务ID和类型用于后续查询结果
setCurrentTaskId(response.taskId)
setTaskType('generate_characters')
setShowTaskProgress(true)
message.info('AI 生成任务已启动,正在后台执行...')
// 轮询任务结果
let attempts = 0
const maxAttempts = 60 // 最多等待60秒
const pollResult = async (): Promise<any> => {
attempts++
try {
const result = await api.get(`/tasks/${response.taskId}`)
if (result.status === 'completed') {
return result.result
} else if (result.status === 'failed') {
throw new Error(result.error || '任务执行失败')
} else if (attempts >= maxAttempts) {
throw new Error('任务执行超时')
} else {
// 继续轮询
await new Promise(resolve => setTimeout(resolve, 1000))
return pollResult()
}
} catch (error: any) {
console.error('创建任务失败:', error)
message.error(`创建任务失败: ${error.message || '未知错误'}`)
throw error
}
}
const result = await pollResult()
// 处理结果
let characterText = ''
if (Array.isArray(result?.characters)) {
characterText = result.characters.map((c: any) => {
if (typeof c === 'string') return c
if (c.name) {
return c.lines ? `${c.name}】出场 ${c.lines}` : `${c.name}`
}
return JSON.stringify(c)
}).join('\n')
} else if (typeof result?.characters === 'string') {
characterText = result.characters
} else if (typeof result === 'string') {
characterText = result
} else if (Array.isArray(result)) {
characterText = result.map((c: any) => {
if (typeof c === 'string') return c
if (c.name) {
return c.lines ? `${c.name}】出场 ${c.lines}` : `${c.name}`
}
return JSON.stringify(c)
}).join('\n')
}
if (characterText) {
setAiGeneratedContent((prev: any) => ({ ...prev, characters: characterText }))
form.setFieldsValue({ characters: characterText })
// 关闭loading并显示成功提示
message.destroy()
message.success('人物设定生成完成!', 1.5)
} else {
message.destroy()
message.warning('生成完成,但未返回有效内容')
}
} catch (error: any) {
console.error('AI 生成人物设定失败:', error)
message.destroy()
message.error(`生成失败: ${error.message || '未知错误'}`)
} finally {
setAiGeneratingCharacters(false)
}
}
// 任务完成回调
const handleTaskComplete = (result: any) => {
setShowTaskProgress(false)
if (taskType === 'generate_characters') {
const characters = result?.characters || result
if (characters) {
setAiGeneratedContent((prev: any) => ({ ...prev, characters }))
form.setFieldsValue({ characters })
message.success('人物设定生成完成!')
}
} else if (taskType === 'generate_outline') {
const outline = result?.outline || result
if (outline) {
setAiGeneratedContent((prev: any) => ({ ...prev, outline }))
form.setFieldsValue({ overallOutline: outline })
message.success('剧情大纲生成完成!')
}
} else if (taskType === 'generate_world') {
const worldSetting = result?.worldSetting || result
if (worldSetting) {
setAiGeneratedContent((prev: any) => ({ ...prev, worldSetting }))
form.setFieldsValue({ worldSetting })
message.success('世界观设定生成完成!')
}
}
setTaskResult(result)
}
// 任务失败回调
const handleTaskError = (error: string) => {
setShowTaskProgress(false)
message.error(`任务执行失败: ${error}`)
}
// 关闭任务进度对话框
const handleCloseTaskProgress = () => {
setShowTaskProgress(false)
}
// AI 分析上传的剧本
const handleAIAnalyzeScript = async () => {
setAiAnalyzingScript(true)
try {
console.log('开始调用 AI 分析剧本...')
// 防止连续触发
if (aiAnalyzingScript) return
setAiAnalyzingScript(true)
message.loading('AI 正在分析剧本...', 0)
try {
// 获取剧本分析配置的技能信息
const selectedSkillsInfo = skills.filter(s => analyzeSkills.includes(s.id))
@ -477,8 +503,6 @@ export const ProjectCreateEnhanced = () => {
customPrompt: analyzePrompt || undefined
}) as any
console.log('AI 剧本分析响应:', JSON.stringify(data, null, 2))
// 提取分析结果
const characters = data?.characters || []
const outline = data?.outline || ''
@ -492,21 +516,23 @@ export const ProjectCreateEnhanced = () => {
).join('\n')
}
console.log('分析结果 - 人物:', characterText, '概要:', summary)
// 更新表单字段
if (characterText) {
form.setFieldsValue({ characters: characterText })
setAiGeneratedContent((prev: any) => ({ ...prev, characters: characterText }))
}
if (outline) {
form.setFieldsValue({ overallOutline: outline })
setAiGeneratedContent((prev: any) => ({ ...prev, outline }))
const outlineText = typeof outline === 'string' ? outline : JSON.stringify(outline, null, 2)
form.setFieldsValue({ overallOutline: outlineText })
setAiGeneratedContent((prev: any) => ({ ...prev, outline: outlineText }))
}
message.success(`AI 分析完成!${summary}`)
// 关闭loading并显示成功提示
message.destroy()
message.success(`AI 分析完成!${summary}`, 1.5)
} catch (error: any) {
console.error('AI 分析失败,完整错误:', error)
message.destroy()
message.error(`AI 分析失败: ${error.message || '未知错误'}`)
} finally {
setAiAnalyzingScript(false)
@ -515,10 +541,13 @@ export const ProjectCreateEnhanced = () => {
// AI 从剧本分析世界观
const handleAIAnalyzeWorldFromScript = async () => {
setAiAnalyzingWorldFromScript(true)
try {
console.log('开始调用 AI 分析世界观...')
// 防止连续触发
if (aiAnalyzingWorldFromScript) return
setAiAnalyzingWorldFromScript(true)
message.loading('AI 正在分析世界观...', 0)
try {
// 获取世界观生成配置的技能信息
const selectedSkillsInfo = skills.filter(s => worldSkills.includes(s.id))
@ -535,13 +564,10 @@ export const ProjectCreateEnhanced = () => {
customPrompt: worldPrompt || undefined
}) as any
console.log('AI 世界观分析响应:', JSON.stringify(data, null, 2))
const worldSetting = data?.worldSetting || data?.result || data?.content || ''
console.log('提取的世界观:', worldSetting?.substring(0, 100) + '...')
if (!worldSetting) {
console.error('未能从响应中提取世界观,响应数据:', data)
message.destroy()
message.error('AI 分析失败:未返回有效内容')
return
}
@ -550,9 +576,12 @@ export const ProjectCreateEnhanced = () => {
form.setFieldsValue({ worldSetting })
setAiGeneratedContent((prev: any) => ({ ...prev, worldSetting }))
message.success('AI 世界观分析完成!')
// 关闭loading并显示成功提示
message.destroy()
message.success('AI 世界观分析完成!', 1.5)
} catch (error: any) {
console.error('AI 世界观分析失败,完整错误:', error)
message.destroy()
message.error(`AI 分析失败: ${error.message || '未知错误'}`)
} finally {
setAiAnalyzingWorldFromScript(false)
@ -561,7 +590,12 @@ export const ProjectCreateEnhanced = () => {
// AI 生成世界观
const handleAIGenerateWorldSetting = async () => {
// 防止连续触发
if (aiGeneratingWorldSetting) return
setAiGeneratingWorldSetting(true)
message.loading('AI 正在生成世界观设定...', 0)
try {
const projectName = form.getFieldValue('name') || '未命名项目'
const selectedSkillsInfo = skills.filter(s => worldSkills.includes(s.id))
@ -581,15 +615,57 @@ export const ProjectCreateEnhanced = () => {
customPrompt: worldPrompt || undefined
})
// 显示任务进度
// 设置任务ID和类型用于后续查询结果
setCurrentTaskId(response.taskId)
setTaskType('generate_world')
setShowTaskProgress(true)
message.info('AI 生成任务已启动,正在后台执行...')
// 轮询任务结果
let attempts = 0
const maxAttempts = 60 // 最多等待60秒
const pollResult = async (): Promise<any> => {
attempts++
try {
const result = await api.get(`/tasks/${response.taskId}`)
if (result.status === 'completed') {
return result.result
} else if (result.status === 'failed') {
throw new Error(result.error || '任务执行失败')
} else if (attempts >= maxAttempts) {
throw new Error('任务执行超时')
} else {
// 继续轮询
await new Promise(resolve => setTimeout(resolve, 1000))
return pollResult()
}
} catch (error: any) {
console.error('创建任务失败:', error)
message.error(`创建任务失败: ${error.message || '未知错误'}`)
throw error
}
}
const result = await pollResult()
// 处理结果
const worldSetting = result?.worldSetting || result
const worldText = typeof worldSetting === 'string' ? worldSetting : JSON.stringify(worldSetting, null, 2)
if (worldText) {
setAiGeneratedContent((prev: any) => ({ ...prev, worldSetting: worldText }))
form.setFieldsValue({ worldSetting: worldText })
// 关闭loading并显示成功提示
message.destroy()
message.success('世界观设定生成完成!', 1.5)
} else {
message.destroy()
message.warning('生成完成,但未返回有效内容')
}
} catch (error: any) {
console.error('AI 生成世界观设定失败:', error)
message.destroy()
message.error(`生成失败: ${error.message || '未知错误'}`)
} finally {
setAiGeneratingWorldSetting(false)
}
@ -597,7 +673,12 @@ export const ProjectCreateEnhanced = () => {
// AI 生成大纲
const handleAIGenerateOutline = async () => {
// 防止连续触发
if (aiGeneratingOutline) return
setAiGeneratingOutline(true)
message.loading('AI 正在生成剧情大纲...', 0)
try {
const projectName = form.getFieldValue('name') || '未命名项目'
const totalEpisodes = form.getFieldValue('totalEpisodes') || 30
@ -619,15 +700,57 @@ export const ProjectCreateEnhanced = () => {
customPrompt: outlinePrompt || undefined
})
// 显示任务进度
// 设置任务ID和类型用于后续查询结果
setCurrentTaskId(response.taskId)
setTaskType('generate_outline')
setShowTaskProgress(true)
message.info('AI 生成任务已启动,正在后台执行...')
// 轮询任务结果
let attempts = 0
const maxAttempts = 60 // 最多等待60秒
const pollResult = async (): Promise<any> => {
attempts++
try {
const result = await api.get(`/tasks/${response.taskId}`)
if (result.status === 'completed') {
return result.result
} else if (result.status === 'failed') {
throw new Error(result.error || '任务执行失败')
} else if (attempts >= maxAttempts) {
throw new Error('任务执行超时')
} else {
// 继续轮询
await new Promise(resolve => setTimeout(resolve, 1000))
return pollResult()
}
} catch (error: any) {
console.error('创建任务失败:', error)
message.error(`创建任务失败: ${error.message || '未知错误'}`)
throw error
}
}
const result = await pollResult()
// 处理结果
const outline = result?.outline || result
const outlineText = typeof outline === 'string' ? outline : JSON.stringify(outline, null, 2)
if (outlineText) {
setAiGeneratedContent((prev: any) => ({ ...prev, outline: outlineText }))
form.setFieldsValue({ overallOutline: outlineText })
// 关闭loading并显示成功提示
message.destroy()
message.success('剧情大纲生成完成!', 1.5)
} else {
message.destroy()
message.warning('生成完成,但未返回有效内容')
}
} catch (error: any) {
console.error('AI 生成剧情大纲失败:', error)
message.destroy()
message.error(`生成失败: ${error.message || '未知错误'}`)
} finally {
setAiGeneratingOutline(false)
}
@ -665,8 +788,9 @@ export const ProjectCreateEnhanced = () => {
const worldSetting = data?.worldSetting || data?.result || data?.content || ''
if (worldSetting) {
form.setFieldsValue({ worldSetting })
setAiGeneratedContent((prev: any) => ({ ...prev, worldSetting }))
const worldText = typeof worldSetting === 'string' ? worldSetting : JSON.stringify(worldSetting, null, 2)
form.setFieldsValue({ worldSetting: worldText })
setAiGeneratedContent((prev: any) => ({ ...prev, worldSetting: worldText }))
}
} catch (error) {
console.error('生成世界观失败:', error)
@ -718,8 +842,22 @@ export const ProjectCreateEnhanced = () => {
const characters = data?.characters || data?.result || data?.content || ''
if (characters) {
form.setFieldsValue({ characters })
setAiGeneratedContent((prev: any) => ({ ...prev, characters }))
let characterText = ''
if (typeof characters === 'string') {
characterText = characters
} else if (Array.isArray(characters)) {
characterText = characters.map((c: any) => {
if (typeof c === 'string') return c
if (c.name) {
return c.lines ? `${c.name}】出场 ${c.lines}` : `${c.name}`
}
return JSON.stringify(c)
}).join('\n')
} else {
characterText = JSON.stringify(characters, null, 2)
}
form.setFieldsValue({ characters: characterText })
setAiGeneratedContent((prev: any) => ({ ...prev, characters: characterText }))
}
}
} catch (error) {
@ -749,8 +887,9 @@ export const ProjectCreateEnhanced = () => {
const outline = data?.outline || data?.analysis || ''
if (outline) {
form.setFieldsValue({ overallOutline: outline })
setAiGeneratedContent((prev: any) => ({ ...prev, outline }))
const outlineText = typeof outline === 'string' ? outline : JSON.stringify(outline, null, 2)
form.setFieldsValue({ overallOutline: outlineText })
setAiGeneratedContent((prev: any) => ({ ...prev, outline: outlineText }))
}
} else {
// 从头创作模式:生成大纲
@ -769,8 +908,9 @@ export const ProjectCreateEnhanced = () => {
const outline = data?.outline || data?.result || data?.content || ''
if (outline) {
form.setFieldsValue({ overallOutline: outline })
setAiGeneratedContent((prev: any) => ({ ...prev, outline }))
const outlineText = typeof outline === 'string' ? outline : JSON.stringify(outline, null, 2)
form.setFieldsValue({ overallOutline: outlineText })
setAiGeneratedContent((prev: any) => ({ ...prev, outline: outlineText }))
}
}
} catch (error) {
@ -849,12 +989,29 @@ export const ProjectCreateEnhanced = () => {
}
}
// 处理灵感内容(如果是文件上传,需要读取内容)
let inspirationContent = directTextInput || ''
if (createSubMode === 'file' && inspirationFile) {
// 灵感文件内容已经在 handleInspirationUpload 中读取
// 这里使用 originFileObj 来读取
const reader = new FileReader()
const fileContent = await new Promise<string>((resolve) => {
reader.onload = (e) => resolve(e.target?.result as string || '')
reader.readAsText(inspirationFile.originFileObj || inspirationFile)
})
inspirationContent = fileContent
}
const projectData: ProjectCreateRequest = {
name: values.name,
totalEpisodes: values.totalEpisodes || 30,
globalContext: {
worldSetting: values.worldSetting || '',
overallOutline: values.overallOutline || '',
// 保存创作内容
uploadedScript: uploadedScript || '', // 上传的剧本
inspiration: inspirationContent, // 灵感内容(直接输入或文件)
styleGuide: values.characters || '', // 人物设定
characterProfiles: {},
sceneSettings: {}
},
@ -1589,7 +1746,11 @@ export const ProjectCreateEnhanced = () => {
maxHeight: '200px',
overflowY: 'auto'
}}>
{aiGeneratedContent.characters || '未设置'}
{typeof aiGeneratedContent.characters === 'string'
? aiGeneratedContent.characters
: aiGeneratedContent.characters
? JSON.stringify(aiGeneratedContent.characters, null, 2)
: '未设置'}
</div>
</Form.Item>
@ -1602,7 +1763,11 @@ export const ProjectCreateEnhanced = () => {
maxHeight: '200px',
overflowY: 'auto'
}}>
{aiGeneratedContent.outline || '未设置'}
{typeof aiGeneratedContent.outline === 'string'
? aiGeneratedContent.outline
: aiGeneratedContent.outline
? JSON.stringify(aiGeneratedContent.outline, null, 2)
: '未设置'}
</div>
</Form.Item>
@ -1639,25 +1804,6 @@ export const ProjectCreateEnhanced = () => {
)}
</Form>
</Card>
{/* 异步任务进度对话框 */}
<Modal
title="AI 生成进度"
open={showTaskProgress}
onCancel={handleCloseTaskProgress}
footer={null}
width={600}
closeIcon={<CloseOutlined />}
>
{currentTaskId && (
<TaskProgressTracker
taskId={currentTaskId}
onComplete={handleTaskComplete}
onError={handleTaskError}
pollInterval={1000}
/>
)}
</Modal>
</div>
)
}

View File

@ -16,9 +16,8 @@ import { useProjectStore } from '@/stores/projectStore'
import { useSkillStore } from '@/stores/skillStore'
import { Episode } from '@/services/projectService'
import { taskService } from '@/services/taskService'
import { TaskProgressTracker } from '@/components/TaskProgressTracker'
const { TextParagraph } = Typography
const { Paragraph } = Typography
const { TabPane } = Tabs
const { TextArea } = Input
@ -129,10 +128,9 @@ export const ProjectDetail = () => {
// 全局设定生成相关状态
const [globalForm] = Form.useForm()
const [generating, setGenerating] = useState(false)
const [currentTaskId, setCurrentTaskId] = useState<string | null>(null)
const [showTaskProgress, setShowTaskProgress] = useState(false)
const [taskType, setTaskType] = useState<string | null>(null)
const [generatingWorld, setGeneratingWorld] = useState(false)
const [generatingCharacters, setGeneratingCharacters] = useState(false)
const [generatingOutline, setGeneratingOutline] = useState(false)
// Skills 配置
const [worldSkills, setWorldSkills] = useState<string[]>([])
@ -180,7 +178,7 @@ export const ProjectDetail = () => {
// 初始化全局设定表单
globalForm.setFieldsValue({
worldSetting: currentProject.globalContext?.worldSetting || '',
characters: currentProject.globalContext?.characterProfiles || '',
characters: currentProject.globalContext?.styleGuide || '',
overallOutline: currentProject.globalContext?.overallOutline || ''
})
}
@ -273,19 +271,29 @@ export const ProjectDetail = () => {
// AI生成/分析世界观
const handleGenerateWorld = async () => {
if (!id) return
if (generatingWorld) return // 防止重复点击
const projectName = currentProject?.name || '未命名项目'
const selectedSkillsInfo = skills.filter(s => worldSkills.includes(s.id))
// 根据创作方式决定是分析还是生成
// 根据创作方式决定是分析还是生成(从项目数据中实时获取最新内容)
const projectScript = currentProject?.globalContext?.uploadedScript || ''
const projectInspiration = currentProject?.globalContext?.inspiration || ''
// 如果有编辑中的内容,优先使用;否则使用项目中保存的内容
const finalScript = scriptContent || projectScript
const finalInspiration = inspirationContent || projectInspiration
const isAnalysis = creationMode === 'script'
const baseContent = isAnalysis ? scriptContent : inspirationContent
const baseContent = isAnalysis ? finalScript : finalInspiration
const idea = isAnalysis
? `分析以下剧本,提取世界观设定:\n${baseContent?.substring(0, 2000)}`
: `项目名称:${projectName}\n创意灵感\n${baseContent}`
const hideMessage = message.loading(isAnalysis ? '正在分析剧本...' : '正在生成世界观设定...', 0)
try {
setGenerating(true)
setGeneratingWorld(true)
const response = await taskService.generateWorld({
idea,
projectName,
@ -299,31 +307,58 @@ export const ProjectDetail = () => {
projectId: id
})
setCurrentTaskId(response.taskId)
setTaskType('generate_world')
setShowTaskProgress(true)
// 轮询任务直到完成
const result = await taskService.pollTask(response.taskId)
const worldSetting = result?.worldSetting || result
const worldText = typeof worldSetting === 'string' ? worldSetting : JSON.stringify(worldSetting, null, 2)
globalForm.setFieldsValue({ worldSetting: worldText })
hideMessage()
message.success(isAnalysis ? '世界观分析完成!' : '世界观设定生成完成!')
} catch (error: any) {
message.error(`创建任务失败: ${error.message || '未知错误'}`)
setGenerating(false)
hideMessage()
message.error(`生成失败: ${error.message || '未知错误'}`)
} finally {
setGeneratingWorld(false)
}
}
// AI生成/分析人物设定
const handleGenerateCharacters = async () => {
if (!id) return
if (generatingCharacters) return // 防止重复点击
const projectName = currentProject?.name || '未命名项目'
const totalEpisodes = currentProject?.totalEpisodes || 30
const selectedSkillsInfo = skills.filter(s => characterSkills.includes(s.id))
// 获取已生成的世界观设定,保证一致性
const worldSetting = globalForm.getFieldValue('worldSetting') || ''
// 从项目数据中实时获取最新内容
const projectScript = currentProject?.globalContext?.uploadedScript || ''
const projectInspiration = currentProject?.globalContext?.inspiration || ''
// 如果有编辑中的内容,优先使用;否则使用项目中保存的内容
const finalScript = scriptContent || projectScript
const finalInspiration = inspirationContent || projectInspiration
const isAnalysis = creationMode === 'script'
const baseContent = isAnalysis ? scriptContent : inspirationContent
const idea = isAnalysis
const baseContent = isAnalysis ? finalScript : finalInspiration
let idea = isAnalysis
? `分析以下剧本,提取人物设定:\n${baseContent?.substring(0, 2000)}`
: `项目名称:${projectName},总集数:${totalEpisodes}\n创意灵感\n${baseContent}`
// 如果已有世界观设定,将其作为上下文
if (worldSetting && !isAnalysis) {
idea += `\n\n【世界观设定】\n${worldSetting}`
}
const hideMessage = message.loading(isAnalysis ? '正在分析人物设定...' : '正在生成人物设定...', 0)
try {
setGenerating(true)
setGeneratingCharacters(true)
const response = await taskService.generateCharacters({
idea,
projectName,
@ -337,31 +372,77 @@ export const ProjectDetail = () => {
projectId: id
})
setCurrentTaskId(response.taskId)
setTaskType('generate_characters')
setShowTaskProgress(true)
// 轮询任务直到完成
const result = await taskService.pollTask(response.taskId)
const characters = result?.characters || result
let characterText = ''
if (typeof characters === 'string') {
characterText = characters
} else if (Array.isArray(characters)) {
characterText = characters.map((c: any) => {
if (typeof c === 'string') return c
if (c.name) {
return c.lines ? `${c.name}】出场 ${c.lines}` : `${c.name}`
}
return JSON.stringify(c)
}).join('\n')
} else {
characterText = JSON.stringify(characters, null, 2)
}
globalForm.setFieldsValue({ characters: characterText })
hideMessage()
message.success(isAnalysis ? '人物设定分析完成!' : '人物设定生成完成!')
} catch (error: any) {
message.error(`创建任务失败: ${error.message || '未知错误'}`)
setGenerating(false)
hideMessage()
message.error(`生成失败: ${error.message || '未知错误'}`)
} finally {
setGeneratingCharacters(false)
}
}
// AI生成/分析大纲
const handleGenerateOutline = async () => {
if (!id) return
if (generatingOutline) return // 防止重复点击
const projectName = currentProject?.name || '未命名项目'
const totalEpisodes = currentProject?.totalEpisodes || 30
const selectedSkillsInfo = skills.filter(s => outlineSkills.includes(s.id))
// 获取已生成的世界观设定和人物设定,保证一致性
const worldSetting = globalForm.getFieldValue('worldSetting') || ''
const characters = globalForm.getFieldValue('characters') || ''
// 从项目数据中实时获取最新内容
const projectScript = currentProject?.globalContext?.uploadedScript || ''
const projectInspiration = currentProject?.globalContext?.inspiration || ''
// 如果有编辑中的内容,优先使用;否则使用项目中保存的内容
const finalScript = scriptContent || projectScript
const finalInspiration = inspirationContent || projectInspiration
const isAnalysis = creationMode === 'script'
const baseContent = isAnalysis ? scriptContent : inspirationContent
const idea = isAnalysis
const baseContent = isAnalysis ? finalScript : finalInspiration
let idea = isAnalysis
? `分析以下剧本,提取整体大纲:\n${baseContent?.substring(0, 2000)}`
: `项目名称:${projectName},总集数:${totalEpisodes}\n创意灵感\n${baseContent}`
// 如果已有世界观设定和人物设定,将其作为上下文
if (!isAnalysis) {
if (worldSetting) {
idea += `\n\n【世界观设定】\n${worldSetting}`
}
if (characters) {
idea += `\n\n【人物设定】\n${characters}`
}
}
const hideMessage = message.loading(isAnalysis ? '正在分析整体大纲...' : '正在生成整体大纲...', 0)
try {
setGenerating(true)
setGeneratingOutline(true)
const response = await taskService.generateOutline({
idea,
totalEpisodes,
@ -376,40 +457,21 @@ export const ProjectDetail = () => {
projectId: id
})
setCurrentTaskId(response.taskId)
setTaskType('generate_outline')
setShowTaskProgress(true)
} catch (error: any) {
message.error(`创建任务失败: ${error.message || '未知错误'}`)
setGenerating(false)
}
}
// 轮询任务直到完成
const result = await taskService.pollTask(response.taskId)
// 任务完成回调
const handleTaskComplete = (result: any) => {
setShowTaskProgress(false)
setGenerating(false)
if (taskType === 'generate_world') {
const worldSetting = result?.worldSetting || result
globalForm.setFieldsValue({ worldSetting })
message.success('世界观设定生成完成!')
} else if (taskType === 'generate_characters') {
const characters = result?.characters || result
globalForm.setFieldsValue({ characters })
message.success('人物设定生成完成!')
} else if (taskType === 'generate_outline') {
const outline = result?.outline || result
globalForm.setFieldsValue({ overallOutline: outline })
message.success('大纲生成完成!')
}
}
const outlineText = typeof outline === 'string' ? outline : JSON.stringify(outline, null, 2)
globalForm.setFieldsValue({ overallOutline: outlineText })
// 任务失败回调
const handleTaskError = (error: string) => {
setShowTaskProgress(false)
setGenerating(false)
message.error(`任务执行失败: ${error}`)
hideMessage()
message.success(isAnalysis ? '整体大纲分析完成!' : '整体大纲生成完成!')
} catch (error: any) {
hideMessage()
message.error(`生成失败: ${error.message || '未知错误'}`)
} finally {
setGeneratingOutline(false)
}
}
// 保存全局设定
@ -424,8 +486,9 @@ export const ProjectDetail = () => {
...currentProject?.globalContext,
worldSetting: worldSetting || '',
overallOutline: overallOutline || '',
characterProfiles: characters ? {} : {},
sceneSettings: {}
styleGuide: characters || '',
characterProfiles: currentProject?.globalContext?.characterProfiles || {},
sceneSettings: currentProject?.globalContext?.sceneSettings || {}
}
})
@ -812,9 +875,9 @@ export const ProjectDetail = () => {
<Button
type="primary"
size="small"
icon={generating ? <LoadingOutlined /> : <RobotOutlined />}
icon={generatingWorld ? <LoadingOutlined /> : <RobotOutlined />}
onClick={handleGenerateWorld}
disabled={generating}
disabled={generatingWorld}
>
{isAnalysisMode ? 'AI 分析' : 'AI 生成'}
</Button>
@ -852,9 +915,9 @@ export const ProjectDetail = () => {
<Button
type="primary"
size="small"
icon={generating ? <LoadingOutlined /> : <RobotOutlined />}
icon={generatingCharacters ? <LoadingOutlined /> : <RobotOutlined />}
onClick={handleGenerateCharacters}
disabled={generating}
disabled={generatingCharacters}
>
{isAnalysisMode ? 'AI 分析' : 'AI 生成'}
</Button>
@ -892,9 +955,9 @@ export const ProjectDetail = () => {
<Button
type="primary"
size="small"
icon={generating ? <LoadingOutlined /> : <RobotOutlined />}
icon={generatingOutline ? <LoadingOutlined /> : <RobotOutlined />}
onClick={handleGenerateOutline}
disabled={generating}
disabled={generatingOutline}
>
{isAnalysisMode ? 'AI 分析' : 'AI 生成'}
</Button>
@ -1059,25 +1122,6 @@ export const ProjectDetail = () => {
</Space>
)}
</Modal>
{/* AI生成任务进度弹窗 */}
<Modal
title="AI 生成进度"
open={showTaskProgress}
onCancel={() => setShowTaskProgress(false)}
footer={null}
width={600}
closable={!generating}
>
{currentTaskId && (
<TaskProgressTracker
taskId={currentTaskId}
onComplete={handleTaskComplete}
onError={handleTaskError}
pollInterval={1000}
/>
)}
</Modal>
</div>
)
}

View File

@ -9,6 +9,7 @@ interface ProjectStore {
currentProject: SeriesProject | null
episodes: Episode[]
loading: boolean
loadingEpisodes: boolean
error: string | null
// Actions
@ -27,6 +28,7 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
currentProject: null,
episodes: [],
loading: false,
loadingEpisodes: false,
error: null,
fetchProjects: async () => {
@ -93,12 +95,12 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
},
fetchEpisodes: async (projectId: string) => {
set({ loading: true, error: null })
set({ loadingEpisodes: true, error: null })
try {
const episodes = await projectService.listEpisodes(projectId)
set({ episodes, loading: false })
set({ episodes, loadingEpisodes: false })
} catch (error) {
set({ error: (error as Error).message, loading: false })
set({ error: (error as Error).message, loadingEpisodes: false })
}
},