573 lines
18 KiB
TypeScript
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
|