= 1 ? '#52c41a' : '#d9d9d9',
+ transition: 'all 0.3s'
+ }} />
+
= 2 ? '#52c41a' : '#d9d9d9',
+ transition: 'all 0.3s'
+ }} />
+
= 3 ? '#52c41a' : '#d9d9d9',
+ transition: 'all 0.3s'
+ }} />
+
+
+ 世界观
+ 人物
+ 大纲
+
+
+
+
+ {/* 生成进度提示 */}
+ {(generatingAll || generatingWorld || generatingCharacters || generatingOutline) && (
+
+
+
+
+ {generatingAll ? '正在依次生成全部内容...' :
+ generatingWorld ? '正在生成世界观设定...' :
+ generatingCharacters ? '正在生成人物设定...' :
+ generatingOutline ? '正在生成整体大纲...' : ''}
+
+
+
+ )}
+
@@ -217,19 +219,35 @@ const ProjectCard = ({ project, onEdit, onDelete, onView }: {
export const ProjectList = () => {
const navigate = useNavigate()
const { projects, loading, fetchProjects, deleteProject } = useProjectStore()
- const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
useEffect(() => {
fetchProjects()
}, [])
- const handleDelete = async (id: string) => {
- try {
- await deleteProject(id)
- message.success('项目已删除')
- } catch (error) {
- message.error(`删除失败: ${(error as Error).message}`)
- }
+ const handleDelete = async (project: SeriesProject) => {
+ // 使用 Modal.confirm 添加确认对话框
+ Modal.confirm({
+ title: '确认删除项目',
+ content: (
+
+
您确定要删除项目 {project.name} 吗?
+
+ 此操作不可撤销,项目的所有数据(包括世界观设定、人物设定、已生成的剧集等)将被永久删除。
+
+
+ ),
+ okText: '确认删除',
+ okType: 'danger',
+ cancelText: '取消',
+ onOk: async () => {
+ try {
+ await deleteProject(project.id)
+ message.success('项目已删除')
+ } catch (error) {
+ message.error(`删除失败: ${(error as Error).message}`)
+ }
+ }
+ })
}
const handleContinueEdit = (id: string) => {
@@ -259,12 +277,6 @@ export const ProjectList = () => {
- )
- }
- }
- ]
-
- // 统计数据
- const getStatistics = () => {
- const total = contents.length
- const approved = contents.filter(c => c.status === 'approved').length
- const pending = contents.filter(c => c.status === 'pending_review').length
- const avgScore = contents.length > 0
- ? contents.reduce((sum, c) => sum + (c.quality_score || 0), 0) / contents.length
- : 0
-
- return { total, approved, pending, avgScore }
+ // 画布内容变更处理
+ const handleContentChange = (content: string) => {
+ setCanvasContent(content);
}
- const stats = getStatistics()
+ // 画布内容保存处理
+ const handleContentSave = async (content: string) => {
+ if (!currentEpisode || !currentEpisode.id) {
+ message.warning('请先选择要保存的剧集');
+ return;
+ }
+
+ try {
+ // 调用后端 API 保存剧集内容
+ await projectService.updateEpisode(currentEpisode.id, {
+ content: content,
+ status: 'draft'
+ });
+ message.success('内容已保存');
+ } catch (error) {
+ message.error(`保存失败: ${(error as Error).message}`);
+ }
+ }
if (loading) {
return (
-
-
+
+
+
+ )
+ }
+
+ if (!project) {
+ return (
+
+ 项目加载失败或不存在
+
+
)
}
return (
-
- {/* 头部 */}
-
- }
- onClick={() => navigate('/projects')}
- >
- 返回
-
- {project?.name || '项目工作台'}
- ID: {projectId}
-
- }
- extra={
-
- }>设置
-
- }>
- 导出内容
-
-
-
- }
- />
-
- {/* 统计概览 */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- = 80 ? '#3f8600' : '#cf1322' }}
- />
-
-
-
-
- {/* 主内容区域 */}
-
- {/* 左侧:剧集列表 */}
-
-
-
-
-
- }
- type="primary"
- onClick={() => navigate(`/projects/${projectId}/execute`)}
- >
- 继续生成
-
-
- }
+
+
+
+ }
+ onClick={() => navigate('/projects')}
+ />
+ {project?.name}
+ ID: {projectId}
+
+ {wsConnected ? '已连接' : '未连接'}
+
+ }
+ onClick={() => setShowEpisodeSidebar(!showEpisodeSidebar)}
+ style={{ color: showEpisodeSidebar ? '#1677ff' : '#666' }}
>
-
+
+
+
+
+
+
+
+
+
+ {/* 剧集管理侧边栏 */}
+ {showEpisodeSidebar && (
+
+ {
+ setCurrentEpisode(episode)
+ // 可以在这里更新画布内容显示剧集内容
+ if (episode.content) {
+ setCanvasContent(episode.content)
+ }
}}
- pagination={{
- pageSize: 10,
- showSizeChanger: true,
- showTotal: (total) => `共 ${total} 集`
- }}
- scroll={{ x: 1000 }}
+ currentEpisodeId={currentEpisode?.id}
/>
-
-
+
+ )}
- {/* 右侧:项目信息 */}
-
-
- {/* 项目信息 */}
-
-
- 总集数:
- {project?.totalEpisodes || '-'} 集
-
- 进度:
-
-
-
-
- 创建时间:
- {project?.createdAt ? dayjs(project.createdAt).format('YYYY-MM-DD') : '-'}
-
- Agent:
- {project?.agentId || '-'}
-
-
-
- {/* 使用的 Skills */}
-
- {project?.skillSettings && Object.keys(project.skillSettings).length > 0 ? (
-
- {Object.entries(project.skillSettings).map(([skillId, config]: [string, any]) => (
-
- {skillId}
-
- ))}
-
- ) : (
-
- )}
-
-
- {/* 最近活动 */}
-
- ({
- color: c.status === 'approved' ? 'green' : 'blue',
- children: (
-
- 第 {c.episode_number} 集 {c.status === 'approved' ? '完成' : '更新'}
-
- {dayjs(c.updated_at).format('MM-DD HH:mm')}
-
-
- )
- }))}
- />
- {contents.length === 0 && (
-
- )}
-
-
-
-
-
- {/* 内容编辑器 */}
- {
- setEditorVisible(false)
- setSelectedEpisode(null)
- setSelectedContent(null)
- }}
- onSave={loadContents}
- />
-
- {/* Skills 配置 */}
- {projectId && selectedConfigEpisode && (
- {
- setSkillConfigVisible(false)
- setSelectedConfigEpisode(null)
- setCurrentEpisodeConfig(null)
- }}
+ {/* 左侧:Context Panel */}
+
- )}
-
+
+ {/* 中间:Smart Canvas */}
+
+ {
+ handleDirectorMessage('开始生成大纲');
+ }}
+ onContentChange={handleContentChange}
+ onContentSave={handleContentSave}
+ />
+
+
+ {/* 右侧:Director Inbox */}
+
+
+
)
}
diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts
index 0bbea63..960640a 100644
--- a/frontend/src/services/api.ts
+++ b/frontend/src/services/api.ts
@@ -4,7 +4,7 @@
import axios from 'axios'
const api = axios.create({
- baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/api/v1',
+ baseURL: import.meta.env.VITE_API_BASE_URL || '/api/v1',
timeout: 120000, // 2分钟超时(LLM 调用可能需要较长时间)
headers: {
'Content-Type': 'application/json',
@@ -13,17 +13,11 @@ const api = axios.create({
maxRedirects: 5,
})
-// 请求拦截器 - 自动添加末尾斜杠
+// 请求拦截器 - 仅对列表类请求添加末尾斜杠
api.interceptors.request.use(
(config) => {
- // 确保 URL 路径有末尾斜杠(如果路径不以 / 结尾)
- if (config.url && !config.url.includes('?') && !config.url.endsWith('/')) {
- // 只为没有参数的路径添加末尾斜杠
- const urlParts = config.url.split('/')
- if (urlParts.length > 0 && !urlParts[urlParts.length - 1].includes('.')) {
- config.url = config.url + '/'
- }
- }
+ // 不自动添加末尾斜杠,FastAPI 后端不需要
+ // 只在某些特定端点需要时手动添加
return config
},
(error) => {
diff --git a/frontend/src/services/projectService.ts b/frontend/src/services/projectService.ts
index a68b187..9fe06b0 100644
--- a/frontend/src/services/projectService.ts
+++ b/frontend/src/services/projectService.ts
@@ -132,6 +132,11 @@ export const projectService = {
return await api.get(`/projects/${projectId}/episodes/${episodeNumber}`)
},
+ // 更新剧集内容
+ updateEpisode: async (episodeId: string, data: Partial) => {
+ return await api.put(`/episodes/${episodeId}`, data)
+ },
+
// 执行单集创作
executeEpisode: async (
projectId: string,
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 46ea221..0d8c338 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -17,6 +17,11 @@ export default defineConfig({
target: 'http://localhost:8000',
changeOrigin: true,
},
+ '/ws': {
+ target: 'http://localhost:8000',
+ ws: true,
+ changeOrigin: true,
+ },
},
},
})
diff --git a/test.md b/test.md
new file mode 100644
index 0000000..71eb55d
--- /dev/null
+++ b/test.md
@@ -0,0 +1,8 @@
+# 我的python 环境是"C:\ProgramData\Anaconda3\envs\creative_studio\python.exe"
+## 1
+
+## 2
+ 页面上的人物、初始状态这些为什么是无内容,没有从项目设置和全局设定中同步过来,同时这个世界观和人物在剧集创作界面都不能进行修改了,而是只能从前一页内容中进行同步过来。另外,这些设定都有没有正确注入项目agent的前置信息里面?
+## 3
+ 关于这个创建项目,删除按钮有没有确认的流程?如果没有需要添加上,确认后才会删除项目;关于项目完成度应该按照剧集制作完成度来计算显示。
+
\ No newline at end of file