creative_studio/frontend/src/pages/ReviewConfig.tsx

573 lines
18 KiB
TypeScript

/**
* Review Configuration Page
* Configure dimensions, rules, and presets for content review
*/
import React, { useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import {
Card,
Form,
Switch,
Slider,
Button,
Space,
Select,
Input,
Modal,
Tag,
Divider,
Row,
Col,
Alert,
List,
message,
Tabs,
Tooltip,
Typography
} from 'antd'
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
SaveOutlined,
PlayCircleOutlined,
CopyOutlined,
InfoCircleOutlined,
ArrowLeftOutlined
} from '@ant-design/icons'
import { useReviewStore } from '@/stores/reviewStore'
import { ReviewPreset, SeverityLevel, ReviewRule } from '@/services/reviewService'
const { TextArea } = Input
const { TabPane } = Tabs
const { Title } = Typography
const ReviewConfig: React.FC = () => {
const navigate = useNavigate()
const { id: projectId } = useParams<{ id: string }>()
const {
configuration,
ruleTemplates,
loading,
error,
fetchConfiguration,
updateConfiguration,
applyPreset,
fetchRuleTemplates,
createRule,
updateRule,
deleteRule,
testRule,
testResult
} = useReviewStore()
const [form] = Form.useForm()
const [ruleModalVisible, setRuleModalVisible] = useState(false)
const [testModalVisible, setTestModalVisible] = useState(false)
const [editingRule, setEditingRule] = useState<ReviewRule | null>(null)
const [testContent, setTestContent] = useState('')
const [selectedRuleForTest, setSelectedRuleForTest] = useState<ReviewRule | null>(null)
const [previewImpact, setPreviewImpact] = useState<any>(null)
useEffect(() => {
if (projectId) {
fetchConfiguration(projectId)
fetchRuleTemplates()
}
}, [projectId])
useEffect(() => {
if (configuration) {
form.setFieldsValue(configuration)
}
}, [configuration])
const dimensions = [
{ key: 'character_consistency', name: '角色一致性', description: '角色性格、行为、对话风格的一致性' },
{ key: 'plot_coherence', name: '剧情连贯性', description: '剧情逻辑、时间线、因果关系的一致性' },
{ key: 'dialogue_quality', name: '对话质量', description: '对话的自然度、角色声音的区分度' },
{ key: 'pacing', name: '节奏控制', description: '故事节奏、张力、高潮设置' },
{ key: 'emotional_depth', name: '情感深度', description: '情感表达的真实性、感染力' },
{ key: 'thematic_strength', name: '主题强度', description: '主题表达的一致性、深度' }
]
const severityColors: Record<SeverityLevel, string> = {
low: 'green',
medium: 'orange',
high: 'red'
}
const presetLabels: Record<ReviewPreset, string> = {
draft: '草稿模式',
standard: '标准模式',
strict: '严格模式',
custom: '自定义'
}
const handleApplyPreset = async (preset: ReviewPreset) => {
if (!projectId) return
try {
await applyPreset(projectId, preset)
message.success(`已应用${presetLabels[preset]}`)
} catch (error) {
message.error('应用预设失败')
}
}
const handleSaveConfiguration = async (values: any) => {
if (!projectId) return
try {
await updateConfiguration(projectId, values)
message.success('配置保存成功')
} catch (error) {
message.error('保存配置失败')
}
}
const handleCreateRule = async (values: any) => {
if (!projectId) return
try {
await createRule(projectId, values)
message.success('规则创建成功')
setRuleModalVisible(false)
setEditingRule(null)
form.resetFields(['ruleName', 'ruleDescription', 'triggerCondition', 'severity', 'category'])
} catch (error) {
message.error('创建规则失败')
}
}
const handleUpdateRule = async (values: any) => {
if (!projectId || !editingRule) return
try {
await updateRule(projectId, editingRule.id, values)
message.success('规则更新成功')
setRuleModalVisible(false)
setEditingRule(null)
form.resetFields(['ruleName', 'ruleDescription', 'triggerCondition', 'severity', 'category'])
} catch (error) {
message.error('更新规则失败')
}
}
const handleDeleteRule = async (ruleId: string) => {
if (!projectId) return
Modal.confirm({
title: '确认删除',
content: '确定要删除这条规则吗?',
onOk: async () => {
try {
await deleteRule(projectId, ruleId)
message.success('规则删除成功')
} catch (error) {
message.error('删除规则失败')
}
}
})
}
const handleEditRule = (rule: ReviewRule) => {
setEditingRule(rule)
form.setFieldsValue({
ruleName: rule.name,
ruleDescription: rule.description,
triggerCondition: rule.triggerCondition,
severity: rule.severity,
category: rule.category
})
setRuleModalVisible(true)
}
const handleTestRule = async () => {
if (!projectId || !selectedRuleForTest) return
try {
await testRule(projectId, selectedRuleForTest, testContent)
message.success('测试完成')
} catch (error) {
message.error('测试规则失败')
}
}
const handleUseTemplate = (template: ReviewRule) => {
form.setFieldsValue({
ruleName: template.name,
ruleDescription: template.description,
triggerCondition: template.triggerCondition,
severity: template.severity,
category: template.category
})
setRuleModalVisible(true)
}
const renderDimensionSliders = () => (
<Card title="维度配置">
<Form form={form} layout="vertical" onFinish={handleSaveConfiguration}>
<Form.Item label="预设模式">
<Space>
<Button onClick={() => handleApplyPreset('draft')}>稿</Button>
<Button onClick={() => handleApplyPreset('standard')}></Button>
<Button onClick={() => handleApplyPreset('strict')}></Button>
{configuration?.preset === 'custom' && (
<Tag color="blue"></Tag>
)}
</Space>
</Form.Item>
<Divider />
{dimensions.map(dim => (
<Card key={dim.key} size="small" style={{ marginBottom: 16 }}>
<Row gutter={16}>
<Col span={8}>
<Space direction="vertical">
<div>
<strong>{dim.name}</strong>
<Tooltip title={dim.description}>
<InfoCircleOutlined style={{ marginLeft: 8, color: '#888' }} />
</Tooltip>
</div>
<Form.Item
name={['dimensions', dim.key, 'enabled']}
valuePropName="checked"
initialValue={true}
>
<Switch checkedChildren="启用" unCheckedChildren="禁用" />
</Form.Item>
</Space>
</Col>
<Col span={8}>
<Form.Item
label="严格度"
name={['dimensions', dim.key, 'strictness']}
initialValue={50}
>
<Slider min={0} max={100} marks={{ 0: '宽松', 50: '适中', 100: '严格' }} />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
label="权重"
name={['dimensions', dim.key, 'weight']}
initialValue={0.5}
>
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
</Form.Item>
</Col>
</Row>
</Card>
))}
<Form.Item>
<Button type="primary" htmlType="submit" icon={<SaveOutlined />}>
</Button>
</Form.Item>
</Form>
</Card>
)
const renderRuleBuilder = () => (
<Card
title="自定义规则"
extra={
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => {
setEditingRule(null)
form.resetFields(['ruleName', 'ruleDescription', 'triggerCondition', 'severity', 'category'])
setRuleModalVisible(true)
}}
>
</Button>
}
>
{configuration?.customRules && configuration.customRules.length > 0 ? (
<List
dataSource={configuration.customRules}
renderItem={(rule) => (
<List.Item
actions={[
<Button
key="test"
size="small"
icon={<PlayCircleOutlined />}
onClick={() => {
setSelectedRuleForTest(rule)
setTestContent('')
setTestModalVisible(true)
}}
>
</Button>,
<Button
key="edit"
size="small"
icon={<EditOutlined />}
onClick={() => handleEditRule(rule)}
>
</Button>,
<Button
key="delete"
size="small"
danger
icon={<DeleteOutlined />}
onClick={() => handleDeleteRule(rule.id)}
>
</Button>
]}
>
<List.Item.Meta
title={
<Space>
{rule.name}
<Tag color={severityColors[rule.severity]}>{rule.severity}</Tag>
<Tag>{rule.category}</Tag>
{!rule.isActive && <Tag color="default"></Tag>}
</Space>
}
description={rule.description}
/>
</List.Item>
)}
/>
) : (
<Alert message="暂无自定义规则" type="info" />
)}
<Divider orientation="left"></Divider>
<List
dataSource={ruleTemplates}
renderItem={(template) => (
<List.Item
actions={[
<Button
size="small"
icon={<CopyOutlined />}
onClick={() => handleUseTemplate(template)}
>
使
</Button>
]}
>
<List.Item.Meta
title={template.name}
description={template.description}
/>
</List.Item>
)}
/>
</Card>
)
const renderPreview = () => (
<Card title="配置预览">
<Alert
message="配置影响预览"
description="当前配置对评分的影响"
type="info"
showIcon
style={{ marginBottom: 16 }}
/>
{configuration && (
<div>
<p><strong>:</strong> {presetLabels[configuration.preset]}</p>
<p><strong>:</strong></p>
<ul>
{dimensions.map(dim => (
configuration.dimensions[dim.key as keyof typeof configuration.dimensions]?.enabled && (
<li key={dim.key}>
{dim.name} - : {configuration.dimensions[dim.key as keyof typeof configuration.dimensions]?.strictness}%
, : {configuration.dimensions[dim.key as keyof typeof configuration.dimensions]?.weight}
</li>
)
))}
</ul>
<p><strong>:</strong> {configuration.customRules.length}</p>
</div>
)}
</Card>
)
return (
<div style={{ padding: 24 }}>
<div style={{ marginBottom: 24, display: 'flex', alignItems: 'center', justifyContent: 'space-between', background: '#fff', padding: '16px', borderRadius: '8px', boxShadow: '0 1px 2px rgba(0,0,0,0.03)' }}>
<Space size="middle">
<Button
icon={<ArrowLeftOutlined />}
onClick={() => navigate(`/projects/${projectId}`)}
size="large"
>
</Button>
<div>
<Title level={4} style={{ margin: 0 }}></Title>
<Text type="secondary" style={{ fontSize: '12px' }}></Text>
</div>
</Space>
</div>
<Card>
<Tabs defaultActiveKey="dimensions">
<TabPane tab="维度配置" key="dimensions">
{renderDimensionSliders()}
</TabPane>
<TabPane tab="自定义规则" key="rules">
{renderRuleBuilder()}
</TabPane>
<TabPane tab="预览" key="preview">
{renderPreview()}
</TabPane>
</Tabs>
</Card>
{/* Rule Modal */}
<Modal
title={editingRule ? '编辑规则' : '添加规则'}
open={ruleModalVisible}
onCancel={() => {
setRuleModalVisible(false)
setEditingRule(null)
form.resetFields(['ruleName', 'ruleDescription', 'triggerCondition', 'severity', 'category'])
}}
footer={null}
width={600}
>
<Form
form={form}
layout="vertical"
onFinish={editingRule ? handleUpdateRule : handleCreateRule}
>
<Form.Item
name="ruleName"
label="规则名称"
rules={[{ required: true, message: '请输入规则名称' }]}
>
<Input placeholder="规则名称" />
</Form.Item>
<Form.Item
name="ruleDescription"
label="规则描述"
rules={[{ required: true, message: '请输入规则描述' }]}
>
<TextArea rows={3} placeholder="描述这条规则的用途..." />
</Form.Item>
<Form.Item
name="category"
label="分类"
rules={[{ required: true, message: '请选择分类' }]}
>
<Select placeholder="选择规则分类">
<Select.Option value="character"></Select.Option>
<Select.Option value="plot"></Select.Option>
<Select.Option value="dialogue"></Select.Option>
<Select.Option value="pacing"></Select.Option>
<Select.Option value="emotion"></Select.Option>
<Select.Option value="theme"></Select.Option>
<Select.Option value="other"></Select.Option>
</Select>
</Form.Item>
<Form.Item
name="triggerCondition"
label="触发条件"
rules={[{ required: true, message: '请输入触发条件' }]}
extra="使用自然语言描述触发条件"
>
<TextArea
rows={4}
placeholder="例如: 当角色行为与其性格设定不符时触发..."
/>
</Form.Item>
<Form.Item
name="severity"
label="严重程度"
rules={[{ required: true }]}
>
<Select>
<Select.Option value="low"></Select.Option>
<Select.Option value="medium"></Select.Option>
<Select.Option value="high"></Select.Option>
</Select>
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit" loading={loading}>
{editingRule ? '更新' : '创建'}
</Button>
<Button onClick={() => setRuleModalVisible(false)}></Button>
</Space>
</Form.Item>
</Form>
</Modal>
{/* Test Rule Modal */}
<Modal
title="测试规则"
open={testModalVisible}
onCancel={() => {
setTestModalVisible(false)
setSelectedRuleForTest(null)
setTestContent('')
setTestResult(null)
}}
footer={[
<Button key="close" onClick={() => setTestModalVisible(false)}>
</Button>,
<Button key="test" type="primary" onClick={handleTestRule} loading={loading}>
</Button>
]}
width={700}
>
{selectedRuleForTest && (
<div>
<Alert
message={`测试规则: ${selectedRuleForTest.name}`}
description={selectedRuleForTest.description}
type="info"
style={{ marginBottom: 16 }}
/>
<Form.Item label="输入测试内容">
<TextArea
rows={8}
value={testContent}
onChange={(e) => setTestContent(e.target.value)}
placeholder="输入要测试的内容片段..."
/>
</Form.Item>
{testResult && (
<Alert
message={testResult.triggered ? '规则触发' : '规则未触发'}
description={
<div>
<p>{testResult.explanation}</p>
{testResult.triggered && testResult.issues.length > 0 && (
<div>
<strong>:</strong>
<ul>
{testResult.issues.map((issue: any, idx: number) => (
<li key={idx}>{issue.description}</li>
))}
</ul>
</div>
)}
</div>
}
type={testResult.triggered ? 'warning' : 'success'}
showIcon
/>
)}
</div>
)}
</Modal>
</div>
)
}
export default ReviewConfig