hjjjj 5487450f34 feat: 实现审核系统核心功能与UI优化
- 新增审核卡片和确认卡片模型,支持Agent推送审核任务和用户确认
- 实现审核卡片API服务,支持创建、更新、批准、驳回等操作
- 扩展审核维度配置,新增角色一致性、剧情连贯性等维度
- 优化前端审核配置页面,修复API路径错误和状态枚举问题
- 改进剧集创作平台布局,新增左侧边栏用于剧集管理和上下文查看
- 增强Skill管理,支持从审核系统跳转创建/编辑Skill
- 修复episodes.json数据问题,清理聊天历史记录
- 更新Agent提示词,明确Skill引用加载流程
- 统一前端主题配置,优化整体UI体验
2026-01-30 18:32:48 +08:00

978 lines
35 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
import { Layout, Typography, Spin, Button, Card, Tooltip, message, Modal, Select, Input, Space } from 'antd';
import { LoadingOutlined, WarningOutlined, SaveOutlined, EditOutlined, CheckOutlined, RobotOutlined, PlayCircleOutlined, FileTextOutlined } from '@ant-design/icons';
const { Content } = Layout;
const { Title, Text } = Typography;
const { TextArea } = Input;
interface SmartCanvasProps {
content: string;
streaming: boolean;
annotations?: any[];
onStartWriting?: () => void;
onContentChange?: (content: string) => void;
onContentSave?: (content: string) => void;
onAIAssist?: (content: string, options?: AIAssistOptions) => void;
episodeTitle?: string;
episodeNumber?: number | null;
episodeStatus?: 'pending' | 'draft' | 'writing' | 'completed';
availableSkills?: any[];
projectId?: string;
onTitleChange?: (title: string) => void;
onConfirmComplete?: () => void;
// 新增大纲相关props
outlineContent?: string;
onOutlineChange?: (outline: string) => void;
onOutlineGenerate?: () => void;
onOutlineSave?: (outline: string) => void;
onOutlineAIAssist?: (outline: string, options?: AIAssistOptions) => void;
outlineStreaming?: boolean;
}
interface AIAssistOptions {
skills?: any[];
customPrompt?: string;
injectAgent?: boolean;
injectOriginalContent?: boolean;
}
export const SmartCanvas: React.FC<SmartCanvasProps> = ({
content,
streaming,
annotations = [],
onStartWriting,
onContentChange,
onContentSave,
onAIAssist,
episodeTitle = '未命名草稿',
episodeNumber = null,
episodeStatus = 'pending',
availableSkills = [],
projectId,
onTitleChange,
onConfirmComplete,
// 新增大纲相关props
outlineContent = '',
onOutlineChange,
onOutlineGenerate,
onOutlineSave,
onOutlineAIAssist,
outlineStreaming = false,
}) => {
// 标题编辑状态
const [isEditingTitle, setIsEditingTitle] = useState(false);
const [editTitle, setEditTitle] = useState(episodeTitle);
// 内容编辑状态
const [editContent, setEditContent] = useState(content);
// 大纲编辑状态
const [editOutline, setEditOutline] = useState(outlineContent);
// 分割线相关状态(大纲和内容的分割)
const [outlineHeightPercent, setOutlineHeightPercent] = useState(40); // 大纲占40%
const [isResizingOutline, setIsResizingOutline] = useState(false);
// AI辅助相关状态
const [showOutlineAIAssistModal, setShowOutlineAIAssistModal] = useState(false);
const [showContentAIAssistModal, setShowContentAIAssistModal] = useState(false);
const [showCreateConfigModal, setShowCreateConfigModal] = useState(false);
const [outlineSelectedSkills, setOutlineSelectedSkills] = useState<any[]>([]);
const [contentSelectedSkills, setContentSelectedSkills] = useState<any[]>([]);
const [createSelectedSkills, setCreateSelectedSkills] = useState<any[]>([]);
const [outlineCustomPrompt, setOutlineCustomPrompt] = useState('');
const [contentCustomPrompt, setContentCustomPrompt] = useState('');
const [createCustomPrompt, setCreateCustomPrompt] = useState('');
const [outlineInjectAgent, setOutlineInjectAgent] = useState(true);
const [outlineInjectOriginal, setOutlineInjectOriginal] = useState(true);
const [contentInjectAgent, setContentInjectAgent] = useState(true);
const [contentInjectOriginal, setContentInjectOriginal] = useState(true);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const outlineTextareaRef = useRef<HTMLTextAreaElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
// 显示标题处理
const displayTitle = useMemo(() => {
if (episodeTitle && episodeTitle !== '未命名草稿') {
if (episodeNumber !== null && !episodeTitle.includes(`${episodeNumber}`)) {
return `${episodeNumber}集:${episodeTitle}`;
}
return episodeTitle;
}
if (episodeNumber !== null && episodeNumber !== undefined) {
return `${episodeNumber}`;
}
return '请选择剧集';
}, [episodeNumber, episodeTitle]);
// 当props变化时更新本地状态仅在剧集切换时执行避免输入时被覆盖
const prevEpisodeRef = useRef<{ number: number | null; title: string }>({ number: null, title: '' });
const prevContentRef = useRef<string>('');
useEffect(() => {
const currentEpisodeKey = `${episodeNumber}_${episodeTitle}`;
const prevEpisodeKey = `${prevEpisodeRef.current.number}_${prevEpisodeRef.current.title}`;
// 只在剧集切换或内容从外部更新时才同步
const isEpisodeChanged = currentEpisodeKey !== prevEpisodeKey;
const isContentFromExternal = content !== prevContentRef.current && content !== editContent;
if (isEpisodeChanged || isContentFromExternal) {
let cleaned = content?.trim() || '';
if (episodeNumber !== null) {
const titlePrefixPattern = new RegExp(`^第\\s*${episodeNumber}\\s*集[:\\s]*.*?(?:\\n|$)`, 'i');
cleaned = cleaned.replace(titlePrefixPattern, '').trim();
}
if (episodeTitle && episodeTitle !== '未命名草稿') {
const escapedTitle = episodeTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const titlePattern = new RegExp(`^${escapedTitle}[:\\s]*(?:\\n|$)`, 'i');
cleaned = cleaned.replace(titlePattern, '').trim();
}
setEditContent(cleaned);
}
// 更新 ref
prevEpisodeRef.current = { number: episodeNumber, title: episodeTitle };
prevContentRef.current = content;
}, [content, episodeNumber, episodeTitle, editContent]);
useEffect(() => {
setEditOutline(outlineContent);
}, [outlineContent]);
// 只在 displayTitle 实际变化时更新 editTitle避免不必要的更新
useEffect(() => {
if (editTitle !== displayTitle) {
setEditTitle(displayTitle);
}
}, [displayTitle]);
// 使用 useCallback 优化 onChange 处理,避免频繁重新渲染
const handleOutlineChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value;
setEditOutline(value);
if (onOutlineChange) {
onOutlineChange(value);
}
}, [onOutlineChange]);
const handleContentChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value;
setEditContent(value);
if (onContentChange) {
onContentChange(value);
}
}, [onContentChange]);
// 处理内容保存
const handleSave = () => {
if (onContentSave) {
onContentSave(editContent);
message.success('内容已保存');
}
};
// 处理大纲保存
const handleOutlineSave = () => {
if (onOutlineSave) {
onOutlineSave(editOutline);
message.success('大纲已保存');
}
};
// 处理大纲AI辅助
const handleOutlineAIAssistConfirm = () => {
if (onOutlineAIAssist) {
onOutlineAIAssist(editOutline, {
skills: outlineSelectedSkills,
customPrompt: outlineCustomPrompt || undefined,
injectAgent: outlineInjectAgent,
injectOriginalContent: outlineInjectOriginal
});
setShowOutlineAIAssistModal(false);
}
};
// 处理内容AI辅助
const handleContentAIAssistConfirm = () => {
if (onAIAssist) {
onAIAssist(editContent, {
skills: contentSelectedSkills,
customPrompt: contentCustomPrompt || undefined,
injectAgent: contentInjectAgent,
injectOriginalContent: contentInjectOriginal
});
setShowContentAIAssistModal(false);
}
};
// 分割线拖动处理
useEffect(() => {
if (!isResizingOutline) return;
const handleMouseMove = (e: MouseEvent) => {
if (!containerRef.current) return;
const containerRect = containerRef.current.getBoundingClientRect();
const newHeight = ((e.clientY - containerRect.top) / containerRect.height) * 100;
setOutlineHeightPercent(Math.max(20, Math.min(60, newHeight)));
};
const handleMouseUp = () => {
setIsResizingOutline(false);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [isResizingOutline]);
const startOutlineResize = (e: React.MouseEvent) => {
e.preventDefault();
setIsResizingOutline(true);
};
return (
<Content style={{
padding: '24px 32px',
background: '#ffffff',
height: '100%',
position: 'relative',
display: 'flex',
flexDirection: 'column',
borderLeft: '1px solid #e8eaed',
borderRight: '1px solid #e8eaed',
boxShadow: 'inset 0 0 20px rgba(0,0,0,0.02)'
}}>
<div ref={containerRef} style={{
flex: 1,
maxWidth: '900px',
margin: '0 auto',
width: '100%',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden'
}}>
{/* 标题区域 */}
<div style={{ textAlign: 'center', marginBottom: '16px', position: 'relative', flexShrink: 0 }}>
{isEditingTitle ? (
<Space direction="vertical" align="center">
<Space>
<Input
value={editTitle}
onChange={(e) => setEditTitle(e.target.value)}
onPressEnter={() => {
setIsEditingTitle(false);
const titleOnly = editTitle.replace(/^第\s*\d+\s*集/, '');
onTitleChange?.(titleOnly || editTitle);
}}
style={{
width: '320px',
fontSize: '18px',
fontWeight: 'bold',
borderRadius: '8px',
border: '2px solid #667eea'
}}
placeholder="输入剧集名称"
/>
<Button
type="primary"
size="small"
icon={<CheckOutlined />}
onClick={() => {
setIsEditingTitle(false);
const titleOnly = editTitle.replace(/^第\s*\d+\s*集/, '');
onTitleChange?.(titleOnly || editTitle);
}}
style={{ borderRadius: '6px' }}
>
</Button>
<Button
size="small"
onClick={() => {
setIsEditingTitle(false);
setEditTitle(displayTitle);
}}
style={{ borderRadius: '6px' }}
>
</Button>
</Space>
<Text type="secondary" style={{ fontSize: '12px' }}>
"第X集"
</Text>
</Space>
) : (
<div style={{ display: 'inline-block', position: 'relative' }}>
<Title level={3} style={{
display: 'inline-block',
margin: 0,
color: '#1a1a1a',
paddingRight: '40px',
fontWeight: 600
}}>
{displayTitle}
</Title>
<Button
type="text"
size="small"
icon={<EditOutlined />}
onClick={() => {
const titleOnly = displayTitle.replace(/^第\s*\d+\s*集/, '');
setEditTitle(titleOnly || displayTitle);
setIsEditingTitle(true);
}}
style={{
position: 'absolute',
right: '0',
top: '50%',
transform: 'translateY(-50%)',
color: '#999'
}}
/>
</div>
)}
</div>
{/* 大纲编辑区域 */}
<div style={{
height: `${outlineHeightPercent}%`,
display: 'flex',
flexDirection: 'column',
minHeight: '150px',
position: 'relative'
}}>
<div style={{
marginBottom: '8px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
flexShrink: 0
}}>
<Space>
<FileTextOutlined style={{ color: '#667eea' }} />
<Text strong style={{ fontSize: '14px' }}></Text>
</Space>
<Space size="small">
<Tooltip title="AI 辅助修改大纲">
<Button
type="text"
size="small"
icon={<RobotOutlined />}
onClick={() => setShowOutlineAIAssistModal(true)}
style={{ color: '#667eea' }}
>
AI
</Button>
</Tooltip>
</Space>
</div>
<div style={{ position: 'relative', flex: 1, minHeight: 0 }}>
<textarea
ref={outlineTextareaRef}
value={editOutline}
onChange={handleOutlineChange}
placeholder="在此编辑剧集大纲..."
style={{
width: '100%',
height: '100%',
padding: '16px',
fontSize: '14px',
lineHeight: '1.6',
color: '#1a1a1a',
fontFamily: "'Merriweather', 'Georgia', serif",
border: '2px solid #e5e7eb',
borderRadius: '12px',
resize: 'none',
outline: 'none',
whiteSpace: 'pre-wrap',
backgroundColor: '#fafbfc',
transition: 'all 0.2s',
boxSizing: 'border-box',
boxShadow: '0 1px 3px rgba(0,0,0,0.05)'
}}
onFocus={(e) => {
e.currentTarget.style.borderColor = '#667eea';
e.currentTarget.style.backgroundColor = '#fff';
e.currentTarget.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.15), 0 2px 8px rgba(102, 126, 234, 0.2)';
}}
onBlur={(e) => {
e.currentTarget.style.borderColor = '#e5e7eb';
e.currentTarget.style.backgroundColor = '#fafbfc';
e.currentTarget.style.boxShadow = '0 1px 3px rgba(0,0,0,0.05)';
}}
/>
{outlineStreaming && (
<div style={{
position: 'absolute',
top: '12px',
right: '12px',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: '#fff',
padding: '4px 12px',
borderRadius: '12px',
fontSize: '12px',
fontWeight: 500,
display: 'flex',
alignItems: 'center',
gap: '6px',
boxShadow: '0 2px 8px rgba(102, 126, 234, 0.3)'
}}>
<Spin indicator={<LoadingOutlined style={{ fontSize: 10 }} spin />} />
...
</div>
)}
</div>
{/* 大纲操作按钮 */}
{!outlineStreaming && (
<div style={{
marginTop: '8px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
gap: '12px',
flexShrink: 0
}}>
<Button
size="middle"
icon={<RobotOutlined />}
onClick={() => {
// 打开AI辅助模态框带有生成大纲的预设提示
setOutlineCustomPrompt('请生成当前剧集的大纲');
setShowOutlineAIAssistModal(true);
}}
style={{
borderRadius: '6px',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
border: 'none',
color: '#fff'
}}
>
</Button>
<Button
type="primary"
size="middle"
icon={<SaveOutlined />}
onClick={handleOutlineSave}
style={{
borderRadius: '6px'
}}
>
</Button>
</div>
)}
</div>
{/* 水平分割线 */}
<div
style={{
height: '6px',
cursor: 'row-resize',
backgroundColor: '#e8e8e8',
margin: '8px 0',
borderRadius: '3px',
transition: 'all 0.2s',
flexShrink: 0,
position: 'relative',
zIndex: 100
}}
onMouseDown={startOutlineResize}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = '#667eea';
e.currentTarget.style.height = '8px';
e.currentTarget.style.boxShadow = '0 0 8px rgba(102, 126, 234, 0.4)';
}}
onMouseLeave={(e) => {
if (!isResizingOutline) {
e.currentTarget.style.backgroundColor = '#e8e8e8';
e.currentTarget.style.height = '6px';
e.currentTarget.style.boxShadow = 'none';
}
}}
>
<div style={{
position: 'absolute',
left: '50%',
top: '50%',
transform: 'translate(-50%, -50%)',
color: '#999',
fontSize: '10px',
pointerEvents: 'none'
}}>
</div>
</div>
{/* 剧集内容编辑区域 */}
<div style={{
height: `${100 - outlineHeightPercent - 3}%`,
display: 'flex',
flexDirection: 'column',
minHeight: '200px',
position: 'relative'
}}>
<div style={{
marginBottom: '8px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
flexShrink: 0
}}>
<Space>
<EditOutlined style={{ color: '#52c41a' }} />
<Text strong style={{ fontSize: '14px' }}></Text>
</Space>
<Space size="small">
<Tooltip title="AI 辅助修改内容">
<Button
type="text"
size="small"
icon={<RobotOutlined />}
onClick={() => setShowContentAIAssistModal(true)}
style={{ color: '#52c41a' }}
>
AI
</Button>
</Tooltip>
</Space>
</div>
<div style={{ position: 'relative', flex: 1, minHeight: 0 }}>
<textarea
ref={textareaRef}
value={editContent}
onChange={handleContentChange}
placeholder={episodeStatus === 'pending' ? '等待开始创作...' : '在此编辑剧集内容...'}
style={{
width: '100%',
height: '100%',
padding: '16px',
fontSize: '15px',
lineHeight: '1.8',
color: '#1a1a1a',
fontFamily: "'Merriweather', 'Georgia', serif",
border: '2px solid #e5e7eb',
borderRadius: '12px',
resize: 'none',
outline: 'none',
whiteSpace: 'pre-wrap',
backgroundColor: '#fafbfc',
transition: 'all 0.2s',
boxSizing: 'border-box',
boxShadow: '0 1px 3px rgba(0,0,0,0.05)'
}}
onFocus={(e) => {
e.currentTarget.style.borderColor = '#667eea';
e.currentTarget.style.backgroundColor = '#fff';
e.currentTarget.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.15), 0 2px 8px rgba(102, 126, 234, 0.2)';
}}
onBlur={(e) => {
e.currentTarget.style.borderColor = '#e5e7eb';
e.currentTarget.style.backgroundColor = '#fafbfc';
e.currentTarget.style.boxShadow = '0 1px 3px rgba(0,0,0,0.05)';
}}
/>
{streaming && (
<div style={{
position: 'absolute',
top: '12px',
right: '12px',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: '#fff',
padding: '4px 12px',
borderRadius: '12px',
fontSize: '12px',
fontWeight: 500,
display: 'flex',
alignItems: 'center',
gap: '6px',
boxShadow: '0 2px 8px rgba(102, 126, 234, 0.3)'
}}>
<Spin indicator={<LoadingOutlined style={{ fontSize: 10 }} spin />} />
...
</div>
)}
</div>
{/* 内容操作按钮 */}
{!streaming && (
<div style={{
marginTop: '8px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
gap: '12px',
flexShrink: 0
}}>
{/* 左侧:创作按钮 */}
<div style={{ display: 'flex', justifyContent: 'center', gap: '12px' }}>
<Button
type="primary"
size="middle"
icon={<PlayCircleOutlined />}
onClick={() => setShowCreateConfigModal(true)}
style={{
borderRadius: '6px',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
border: 'none'
}}
>
</Button>
{/* 当有内容且状态为草稿或创作中时显示确认按钮 */}
{content && (episodeStatus === 'draft' || episodeStatus === 'writing') && onConfirmComplete && (
<Button
type="primary"
size="middle"
icon={<CheckOutlined />}
onClick={onConfirmComplete}
style={{
backgroundColor: '#52c41a',
borderColor: '#52c41a',
borderRadius: '6px'
}}
>
</Button>
)}
</div>
{/* 右侧:保存按钮 */}
<Button
type="primary"
size="middle"
icon={<SaveOutlined />}
onClick={handleSave}
style={{
borderRadius: '6px'
}}
>
</Button>
</div>
)}
</div>
</div>
{/* Annotations Sidebar */}
{annotations.length > 0 && (
<div style={{ width: '250px', borderLeft: '1px solid #f0f0f0', paddingLeft: '16px' }}>
<Title level={5} style={{ fontSize: '14px', marginBottom: '16px' }}> (Annotations)</Title>
{annotations.map((note, idx) => (
<Card
key={idx}
size="small"
style={{ marginBottom: '8px', borderColor: '#ffccc7', background: '#fff1f0' }}
title={<span style={{ color: '#cf1322', fontSize: '12px' }}><WarningOutlined /> {note.type || 'Review Issue'}</span>}
>
<Text style={{ fontSize: '12px' }}>{note.content || note.description}</Text>
{note.suggestion && (
<div style={{ marginTop: '8px', fontSize: '12px', color: '#666' }}>
: {note.suggestion}
</div>
)}
</Card>
))}
</div>
)}
{/* 大纲AI辅助配置模态框 */}
<Modal
title="AI 辅助修改大纲"
open={showOutlineAIAssistModal}
onOk={handleOutlineAIAssistConfirm}
onCancel={() => setShowOutlineAIAssistModal(false)}
okText="开始优化"
cancelText="取消"
width={600}
>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>
Skills
</label>
<Select
mode="multiple"
placeholder="选择要应用的 Skills"
style={{ width: '100%' }}
value={outlineSelectedSkills}
onChange={setOutlineSelectedSkills}
options={availableSkills.map((skill: any) => ({
label: skill.name,
value: skill.id,
description: skill.description
}))}
optionRender={(option) => (
<div>
<div>{option.data.label}</div>
<div style={{ fontSize: '12px', color: '#999' }}>
{option.data.description}
</div>
</div>
)}
/>
<p style={{ marginTop: '4px', fontSize: '12px', color: '#999' }}>
Skills AI
</p>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>
</label>
<TextArea
placeholder="输入自定义的修改要求或指导..."
value={outlineCustomPrompt}
onChange={(e) => setOutlineCustomPrompt(e.target.value)}
rows={3}
maxLength={500}
showCount
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}>
<input
type="checkbox"
checked={outlineInjectAgent}
onChange={(e) => setOutlineInjectAgent(e.target.checked)}
style={{ marginRight: '8px' }}
/>
<span>
Agent
</span>
</label>
<p style={{ marginTop: '4px', marginLeft: '24px', fontSize: '12px', color: '#999' }}>
AI 使 Agent
</p>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}>
<input
type="checkbox"
checked={outlineInjectOriginal}
onChange={(e) => setOutlineInjectOriginal(e.target.checked)}
style={{ marginRight: '8px' }}
/>
<span>
</span>
</label>
<p style={{ marginTop: '4px', marginLeft: '24px', fontSize: '12px', color: '#999' }}>
AI
</p>
</div>
<div style={{ padding: '12px', background: '#f0f5ff', borderRadius: '4px', border: '1px solid #adc6ff' }}>
<div style={{ fontSize: '13px', color: '#333' }}>
<strong></strong>
<ul style={{ margin: '8px 0 0 0', paddingLeft: '20px' }}>
<li>AI </li>
<li> Skills </li>
<li>Agent </li>
<li></li>
</ul>
</div>
</div>
</Modal>
{/* 内容AI辅助配置模态框 */}
<Modal
title="AI 辅助修改内容"
open={showContentAIAssistModal}
onOk={handleContentAIAssistConfirm}
onCancel={() => setShowContentAIAssistModal(false)}
okText="开始优化"
cancelText="取消"
width={600}
>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>
Skills
</label>
<Select
mode="multiple"
placeholder="选择要应用的 Skills"
style={{ width: '100%' }}
value={contentSelectedSkills}
onChange={setContentSelectedSkills}
options={availableSkills.map((skill: any) => ({
label: skill.name,
value: skill.id,
description: skill.description
}))}
optionRender={(option) => (
<div>
<div>{option.data.label}</div>
<div style={{ fontSize: '12px', color: '#999' }}>
{option.data.description}
</div>
</div>
)}
/>
<p style={{ marginTop: '4px', fontSize: '12px', color: '#999' }}>
Skills AI
</p>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>
</label>
<TextArea
placeholder="输入自定义的修改要求或指导..."
value={contentCustomPrompt}
onChange={(e) => setContentCustomPrompt(e.target.value)}
rows={3}
maxLength={500}
showCount
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}>
<input
type="checkbox"
checked={contentInjectAgent}
onChange={(e) => setContentInjectAgent(e.target.checked)}
style={{ marginRight: '8px' }}
/>
<span>
Agent
</span>
</label>
<p style={{ marginTop: '4px', marginLeft: '24px', fontSize: '12px', color: '#999' }}>
AI 使 Agent
</p>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}>
<input
type="checkbox"
checked={contentInjectOriginal}
onChange={(e) => setContentInjectOriginal(e.target.checked)}
style={{ marginRight: '8px' }}
/>
<span>
</span>
</label>
<p style={{ marginTop: '4px', marginLeft: '24px', fontSize: '12px', color: '#999' }}>
AI
</p>
</div>
<div style={{ padding: '12px', background: '#f0f5ff', borderRadius: '4px', border: '1px solid #adc6ff' }}>
<div style={{ fontSize: '13px', color: '#333' }}>
<strong></strong>
<ul style={{ margin: '8px 0 0 0', paddingLeft: '20px' }}>
<li>AI </li>
<li> Skills </li>
<li>Agent </li>
<li></li>
</ul>
</div>
</div>
</Modal>
{/* 创作配置模态框 */}
<Modal
title="配置创作选项"
open={showCreateConfigModal}
onOk={() => {
setShowCreateConfigModal(false);
if (onStartWriting) {
// 构建包含skills信息的创作消息
let message = `开始创作第${episodeNumber}集完整内容`;
if (createSelectedSkills.length > 0) {
const skillNames = createSelectedSkills.map(s => s.name).join('、');
message += `\n使用Skills${skillNames}`;
}
if (createCustomPrompt) {
message += `\n要求${createCustomPrompt}`;
}
// 通过onAIAssist发送这会使用WebSocket和agent
if (onAIAssist) {
onAIAssist('', {
skills: createSelectedSkills,
customPrompt: message
});
} else if (onStartWriting) {
onStartWriting();
}
}
}}
onCancel={() => setShowCreateConfigModal(false)}
okText="开始创作"
cancelText="取消"
width={600}
>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>
Skills
</label>
<Select
mode="multiple"
placeholder="选择要使用的Skills不选择则AI自动判断"
style={{ width: '100%' }}
value={createSelectedSkills}
onChange={setCreateSelectedSkills}
options={availableSkills.map((skill: any) => ({
label: skill.name,
value: skill.id,
description: skill.description
}))}
optionRender={(option) => (
<div>
<div>{option.data.label}</div>
<div style={{ fontSize: '12px', color: '#999' }}>
{option.data.description}
</div>
</div>
)}
/>
<p style={{ marginTop: '4px', fontSize: '12px', color: '#999' }}>
AI将根据项目配置自动选择合适的Skills
</p>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>
</label>
<TextArea
placeholder="输入特殊的创作要求或指导..."
value={createCustomPrompt}
onChange={(e) => setCreateCustomPrompt(e.target.value)}
rows={3}
maxLength={500}
showCount
/>
</div>
<div style={{ padding: '12px', background: '#e6f7ff', borderRadius: '4px', border: '1px solid #91d5ff' }}>
<div style={{ fontSize: '13px', color: '#333' }}>
<strong></strong>
<ul style={{ margin: '8px 0 0 0', paddingLeft: '20px' }}>
<li>AI将按照保存的大纲进行创作</li>
<li>Skills将融入创作过程</li>
<li></li>
<li></li>
</ul>
</div>
</div>
</Modal>
</Content>
);
};