视频提词优化管理后台配置BUG修复

This commit is contained in:
2026-06-22 15:56:09 +08:00
parent ada0243919
commit 03e5b049b4
5 changed files with 597 additions and 467 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -28,7 +28,7 @@
}
})();
</script>
<script type="module" crossorigin src="/assets/index-CYRc4RUo.js"></script>
<script type="module" crossorigin src="/assets/index-Te6nFn2L.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D7ShJUt4.css">
</head>
<body>
@@ -58,6 +58,17 @@ const NODE_TYPES: { label: string; value: VideoPromptSchemaNodeType }[] = [
{ label: '流程数组', value: 'flow' },
];
const VIDEO_SPEC_SECTION_KEY = '画面属性';
const VIDEO_SPEC_LOCKED_FIELDS = new Set(['视频时长', '视频比例', '清晰度', '帧率', '推荐分辨率']);
function isLockedVideoSpecField(sectionKey: string | undefined, fieldKey: string | undefined): boolean {
return sectionKey === VIDEO_SPEC_SECTION_KEY && !!fieldKey && VIDEO_SPEC_LOCKED_FIELDS.has(fieldKey);
}
function isLockedVideoSpecSection(sectionKey: string | undefined): boolean {
return sectionKey === VIDEO_SPEC_SECTION_KEY;
}
function clone<T>(value: T): T {
return JSON.parse(JSON.stringify(value ?? null));
}
@@ -137,17 +148,19 @@ function normalizeConfig(data: any): VideoPromptSchemaConfigData {
};
}
function toApiNode(node: VideoPromptSchemaNode): any {
function toApiNode(node: VideoPromptSchemaNode, parentKey?: string): any {
const lockedSection = isLockedVideoSpecSection(node.key);
const lockedField = isLockedVideoSpecField(parentKey, node.key);
const base: any = {
key: node.key,
label: node.label,
type: node.type,
enabled: !!node.enabled,
editable: !!node.editable,
type: lockedField ? 'string' : node.type,
enabled: lockedSection || lockedField ? true : !!node.enabled,
editable: lockedField ? false : !!node.editable,
max_length: node.maxLength,
};
if (node.type === 'object') {
base.children = (node.children || []).map(toApiNode);
base.children = (node.children || []).map(child => toApiNode(child, node.key));
} else if (node.type === 'flow') {
base.content_key = node.contentKey;
base.content_aliases = node.contentAliases || [];
@@ -169,7 +182,7 @@ function toApiConfig(config: VideoPromptSchemaConfigData, enabled: boolean): any
return {
version: config.version,
enabled,
sections: (config.sections || []).map(toApiNode),
sections: (config.sections || []).map(section => toApiNode(section)),
time_plan_rules: (config.timePlanRules || []).map(rule => ({
min_duration: rule.minDuration,
max_duration: rule.maxDuration,
@@ -364,6 +377,12 @@ const AdminVideoPromptSchemaConfig: React.FC = () => {
const updateSection = (index: number, patch: Partial<VideoPromptSchemaNode>) => {
updateConfig(draft => {
const current = draft.sections[index];
if (isLockedVideoSpecSection(current?.key)) {
delete patch.key;
delete patch.type;
delete patch.enabled;
}
draft.sections[index] = { ...draft.sections[index], ...patch };
if (patch.type === 'object') {
draft.sections[index].children = draft.sections[index].children || [];
@@ -392,7 +411,15 @@ const AdminVideoPromptSchemaConfig: React.FC = () => {
const updateChild = (sectionIndex: number, childIndex: number, patch: Partial<VideoPromptSchemaNode>) => {
updateConfig(draft => {
const children = draft.sections[sectionIndex].children || [];
const section = draft.sections[sectionIndex];
const children = section.children || [];
const child = children[childIndex];
if (isLockedVideoSpecField(section?.key, child?.key)) {
delete patch.key;
delete patch.type;
delete patch.enabled;
patch.editable = false;
}
children[childIndex] = { ...children[childIndex], ...patch };
draft.sections[sectionIndex].children = children;
});
@@ -470,29 +497,30 @@ const AdminVideoPromptSchemaConfig: React.FC = () => {
};
const sectionItems = useMemo(() => sections.map((section, sectionIndex) => ({
key: `${section.key}-${sectionIndex}`,
key: `section-${sectionIndex}`,
label: (
<Space>
<Text strong>{section.label || section.key || '未命名分组'}</Text>
{section.key && section.key !== section.label && <Tag color="blue">key: {section.key}</Tag>}
<Tag>{section.type}</Tag>
{!section.enabled && <Tag color="default"></Tag>}
{isLockedVideoSpecSection(section.key) && <Tag color="red"></Tag>}
{!section.enabled && !isLockedVideoSpecSection(section.key) && <Tag color="default"></Tag>}
</Space>
),
children: (
<Space direction="vertical" style={{ width: '100%' }} size="middle">
<Row gutter={12} align="middle">
<Col span={4}><Input addonBefore="key" value={section.key} maxLength={64} placeholder="传给 AI 的字段名" onChange={e => updateSection(sectionIndex, { key: e.target.value })} /></Col>
<Col span={4}><Input addonBefore="key" value={section.key} maxLength={64} placeholder="传给 AI 的字段名" disabled={isLockedVideoSpecSection(section.key)} onChange={e => updateSection(sectionIndex, { key: e.target.value })} /></Col>
<Col span={4}><Input addonBefore="label" value={section.label} maxLength={64} placeholder="后台显示名称" onChange={e => updateSection(sectionIndex, { label: e.target.value })} /></Col>
<Col span={4}>
<Select value={section.type as any} options={NODE_TYPES} style={{ width: '100%' }} onChange={value => updateSection(sectionIndex, { type: value })} />
<Select value={section.type as any} options={NODE_TYPES} style={{ width: '100%' }} disabled={isLockedVideoSpecSection(section.key)} onChange={value => updateSection(sectionIndex, { type: value })} />
</Col>
<Col span={3}><Space><Switch checked={section.enabled} onChange={checked => updateSection(sectionIndex, { enabled: checked })} /></Space></Col>
<Col span={3}><Space><Switch checked={isLockedVideoSpecSection(section.key) ? true : section.enabled} disabled={isLockedVideoSpecSection(section.key)} onChange={checked => updateSection(sectionIndex, { enabled: checked })} /></Space></Col>
<Col span={3}><Space><Switch checked={section.editable} onChange={checked => updateSection(sectionIndex, { editable: checked })} /></Space></Col>
<Col span={4}><InputNumber min={1} max={20000} value={section.maxLength} addonBefore="长度" style={{ width: '100%' }} onChange={value => updateSection(sectionIndex, { maxLength: Number(value || 1000) })} /></Col>
<Col span={2} style={{ textAlign: 'right' }}>
<Popconfirm title="确认删除该分组?" onConfirm={() => deleteSection(sectionIndex)}>
<Button danger icon={<DeleteOutlined />} />
<Popconfirm title="确认删除该分组?" disabled={isLockedVideoSpecSection(section.key)} onConfirm={() => deleteSection(sectionIndex)}>
<Button danger icon={<DeleteOutlined />} disabled={isLockedVideoSpecSection(section.key)} />
</Popconfirm>
</Col>
</Row>
@@ -501,18 +529,18 @@ const AdminVideoPromptSchemaConfig: React.FC = () => {
<Card size="small" title="对象字段" extra={<Button size="small" icon={<PlusOutlined />} onClick={() => addChild(sectionIndex)}></Button>}>
<Space direction="vertical" style={{ width: '100%' }}>
{(section.children || []).map((child, childIndex) => (
<Row gutter={8} align="middle" key={`${child.key}-${childIndex}`}>
<Col span={4}><Input addonBefore="key" value={child.key} maxLength={64} placeholder="传给 AI 的字段名" onChange={e => updateChild(sectionIndex, childIndex, { key: e.target.value })} /></Col>
<Row gutter={8} align="middle" key={`child-${sectionIndex}-${childIndex}`}>
<Col span={4}><Input addonBefore="key" value={child.key} maxLength={64} placeholder="传给 AI 的字段名" disabled={isLockedVideoSpecField(section.key, child.key)} onChange={e => updateChild(sectionIndex, childIndex, { key: e.target.value })} /></Col>
<Col span={4}><Input addonBefore="label" value={child.label} maxLength={64} placeholder="后台显示名称" onChange={e => updateChild(sectionIndex, childIndex, { label: e.target.value })} /></Col>
<Col span={3}><Select value={child.type as any} options={NODE_TYPES.filter(item => item.value !== 'object' && item.value !== 'flow')} style={{ width: '100%' }} onChange={value => updateChild(sectionIndex, childIndex, { type: value })} /></Col>
<Col span={2}><Space><Switch checked={child.enabled} onChange={checked => updateChild(sectionIndex, childIndex, { enabled: checked })} /></Space></Col>
<Col span={2}><Space><Switch checked={child.editable} onChange={checked => updateChild(sectionIndex, childIndex, { editable: checked })} /></Space></Col>
<Col span={3}><Select value={child.type as any} options={NODE_TYPES.filter(item => item.value !== 'object' && item.value !== 'flow')} style={{ width: '100%' }} disabled={isLockedVideoSpecField(section.key, child.key)} onChange={value => updateChild(sectionIndex, childIndex, { type: value })} /></Col>
<Col span={2}><Space><Switch checked={isLockedVideoSpecField(section.key, child.key) ? true : child.enabled} disabled={isLockedVideoSpecField(section.key, child.key)} onChange={checked => updateChild(sectionIndex, childIndex, { enabled: checked })} /></Space></Col>
<Col span={2}><Space><Switch checked={isLockedVideoSpecField(section.key, child.key) ? false : child.editable} disabled={isLockedVideoSpecField(section.key, child.key)} onChange={checked => updateChild(sectionIndex, childIndex, { editable: checked })} /></Space></Col>
<Col span={3}><InputNumber min={1} max={20000} value={child.maxLength} addonBefore="长度" style={{ width: '100%' }} onChange={value => updateChild(sectionIndex, childIndex, { maxLength: Number(value || 1000) })} /></Col>
<Col span={5}>
<Input value={Array.isArray(child.value) ? child.value.join('/') : String(child.value ?? '')} maxLength={child.maxLength || 1000} placeholder="默认值" onChange={e => updateChild(sectionIndex, childIndex, { value: child.type === 'array' ? e.target.value.split('/').filter(Boolean) : e.target.value })} />
</Col>
<Col span={1} style={{ textAlign: 'right' }}>
<Button danger size="small" icon={<DeleteOutlined />} onClick={() => deleteChild(sectionIndex, childIndex)} />
<Button danger size="small" icon={<DeleteOutlined />} disabled={isLockedVideoSpecField(section.key, child.key)} onClick={() => deleteChild(sectionIndex, childIndex)} />
</Col>
</Row>
))}
@@ -528,7 +556,7 @@ const AdminVideoPromptSchemaConfig: React.FC = () => {
</Row>
<Space direction="vertical" style={{ width: '100%' }}>
{(section.itemFields || []).map((field, fieldIndex) => (
<Row gutter={8} align="middle" key={`${field.key}-${fieldIndex}`}>
<Row gutter={8} align="middle" key={`flow-${sectionIndex}-${fieldIndex}`}>
<Col span={5}><Input addonBefore="key" value={field.key} maxLength={64} placeholder="流程对象字段名" onChange={e => updateFlowField(sectionIndex, fieldIndex, { key: e.target.value })} /></Col>
<Col span={5}><Input addonBefore="label" value={field.label} maxLength={64} placeholder="后台显示名称" onChange={e => updateFlowField(sectionIndex, fieldIndex, { label: e.target.value })} /></Col>
<Col span={3}><Space><Switch checked={field.enabled} onChange={checked => updateFlowField(sectionIndex, fieldIndex, { enabled: checked })} /></Space></Col>
@@ -612,7 +640,7 @@ const AdminVideoPromptSchemaConfig: React.FC = () => {
type="warning"
showIcon
message="字段说明"
description="key 是最终传给 AI 和保存到 prompt_schema 的真实字段名,例如「素材理解」「动作流程」;label 只用于管理后台显示,帮助运营/管理员理解这个字段,不会传给 AI;启用关闭后该字段不会进入运行时 schema;可编辑关闭后用户在第 4 步修改时不能改这个字段。"
description="key 是最终传给 AI 和保存到 prompt_schema 的真实字段名,例如「素材理解」「动作流程」;label 只用于管理后台显示,帮助运营/管理员理解这个字段,不会传给 AI;启用关闭后该字段不会进入运行时 schema;可编辑关闭后用户在第 4 步修改时不能改这个字段。「画面属性」里的视频时长、视频比例、清晰度、帧率、推荐分辨率属于系统锁定参数,不能改 key、不能禁用、不能开放用户编辑。"
/>
<Button icon={<PlusOutlined />} onClick={addSection}></Button>
<Collapse items={sectionItems} />