From 2142f781197ef4251f9e9aab7bbf79483616f31b Mon Sep 17 00:00:00 2001 From: GinHa <15201596918@163.com> Date: Wed, 17 Jun 2026 17:55:02 +0800 Subject: [PATCH] dev app commit --- video-gen-app/src/api/index.ts | 5 + .../components/VideoPromptSchemaEditor.tsx | 274 ++++++++ .../src/components/VideoTrimPicker.tsx | 624 ++++++++++++++++++ video-gen-app/src/pages/InitialInfo.tsx | 202 ++---- video-gen-app/src/pages/RemoveInfo.tsx | 335 ++++++---- 5 files changed, 1150 insertions(+), 290 deletions(-) create mode 100644 video-gen-app/src/components/VideoPromptSchemaEditor.tsx create mode 100644 video-gen-app/src/components/VideoTrimPicker.tsx diff --git a/video-gen-app/src/api/index.ts b/video-gen-app/src/api/index.ts index d789cb8d..994005b9 100644 --- a/video-gen-app/src/api/index.ts +++ b/video-gen-app/src/api/index.ts @@ -393,6 +393,11 @@ export async function getfour(projectId: string, stepId: string ,params: any): P return api.post(`/hot-opening-replications/tasks/${projectId}/steps/${stepId}/generate-video`, params); } +// 修改第四步视频 AI 提词 JSON schema +export async function updateHotOpeningVideoPromptSchema(projectId: string, stepId: string, params: { prompt_schema: Record }): Promise { + return api.put(`/hot-opening-replications/tasks/${projectId}/steps/${stepId}/video-prompt-schema`, params); +} + // 镜头复刻 export async function createShotReplication(params: any): Promise { return api.post('/shot-replications/task-sets', params); diff --git a/video-gen-app/src/components/VideoPromptSchemaEditor.tsx b/video-gen-app/src/components/VideoPromptSchemaEditor.tsx new file mode 100644 index 00000000..eb865c92 --- /dev/null +++ b/video-gen-app/src/components/VideoPromptSchemaEditor.tsx @@ -0,0 +1,274 @@ +import React from 'react'; +import { Button, Input, Space, Tag, Typography } from 'antd'; + +const { Text } = Typography; +const { TextArea } = Input; + +type JsonValue = any; + +type VideoPromptSchemaEditorProps = { + value: Record; + onChange: (nextValue: Record) => void; +}; + +const LOCKED_TOP_LEVEL_KEYS = new Set([ + 'schema_version', + 'schema_usage', + '动态时间规划', + '输出规格限制', + '合规控制', + '质量控制', +]); + +const LOCKED_FRAME_KEYS = new Set([ + '视频时长', + '视频比例', + '清晰度', + '帧率', + '推荐分辨率', +]); + +const EDITABLE_FRAME_KEYS = new Set([ + '主体描述', + '主体数量', + '主体位置', + '主体占比', + '场景描述', + '构图方式', + '画面风格', + '光影色彩', +]); + +const EDITABLE_FINAL_PROMPT_KEYS = new Set([ + '主提示词', + '动作提示词', + '镜头提示词', + '字幕提示词', + '音频提示词', + '风格提示词', + '负面提示词', +]); + +function clonePlain(value: T): T { + return value === undefined ? value : JSON.parse(JSON.stringify(value)); +} + +function pathIncludes(path: Array, key: string): boolean { + return path.some((item) => String(item) === key); +} + +function getPathValue(root: JsonValue, path: Array): JsonValue { + let current = root; + for (const key of path) { + if (current === undefined || current === null) return undefined; + current = current[key as keyof typeof current]; + } + return current; +} + +function setPathValue(root: JsonValue, path: Array, value: JsonValue): JsonValue { + const next = clonePlain(root); + let current = next; + for (let index = 0; index < path.length - 1; index += 1) { + current = current[path[index] as keyof typeof current]; + } + current[path[path.length - 1] as keyof typeof current] = value; + return next; +} + +function removeArrayItem(root: JsonValue, path: Array, index: number): JsonValue { + const arrayValue = getPathValue(root, path); + if (!Array.isArray(arrayValue)) return root; + return setPathValue(root, path, arrayValue.filter((_, itemIndex) => itemIndex !== index)); +} + +function addArrayItem(root: JsonValue, path: Array, sampleValue: JsonValue): JsonValue { + const arrayValue = getPathValue(root, path); + if (!Array.isArray(arrayValue)) return root; + const nextItem = typeof sampleValue === 'object' && sampleValue !== null ? clonePlain(sampleValue) : ''; + return setPathValue(root, path, [...arrayValue, nextItem]); +} + +function stringifyReadonly(value: JsonValue): string { + if (value === null || value === undefined) return ''; + if (typeof value === 'object') return JSON.stringify(value, null, 2); + return String(value); +} + +function shouldUseTextArea(value: JsonValue): boolean { + const text = stringifyReadonly(value); + return text.length > 40 || text.includes('\n') || text.includes(',') || text.includes('。'); +} + +function isLockedPath(path: Array): boolean { + const rootKey = String(path[0] ?? ''); + const currentKey = String(path[path.length - 1] ?? ''); + + if (LOCKED_TOP_LEVEL_KEYS.has(rootKey)) return true; + if (rootKey === '画面属性') { + if (LOCKED_FRAME_KEYS.has(currentKey)) return true; + if (!EDITABLE_FRAME_KEYS.has(currentKey)) return false; + } + if (rootKey === '最终提示词' && path.length === 2) { + return !EDITABLE_FINAL_PROMPT_KEYS.has(currentKey); + } + if ((rootKey === '动作流程' || rootKey === '镜头流程') && currentKey === '时间段') { + return true; + } + + return false; +} + +function canAddOrRemoveArray(path: Array): boolean { + const rootKey = String(path[0] ?? ''); + if (LOCKED_TOP_LEVEL_KEYS.has(rootKey)) return false; + if (rootKey === '动作流程' || rootKey === '镜头流程') return false; + return true; +} + +function fieldTitle(key: string | number): string { + return typeof key === 'number' ? `第 ${key + 1} 项` : key; +} + +function ReadonlyBlock({ value }: { value: JsonValue }) { + const text = stringifyReadonly(value); + return shouldUseTextArea(value) ? ( +