Files
ttstd-infinite-canvas/backend/internal/service/prompt_template_defaults.go

262 lines
14 KiB
Go
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.
package service
import (
"fmt"
"strings"
)
const (
promptOperationStoryboardPlan = "storyboard_plan"
promptOperationStoryboardRepair = "storyboard_repair"
promptOperationStoryboardFirstFrame = "storyboard_first_frame"
promptOperationStoryboardVideo = "storyboard_video"
promptOperationCharacterExtract = "character_extract"
promptOperationCharacterTurnaround = "character_turnaround"
)
func defaultPromptDefinitions() []PromptOperationDefinition {
return []PromptOperationDefinition{
{
Operation: promptOperationStoryboardPlan, Label: "分镜规划", Category: "分镜", OutputType: "json", SchemaKey: "storyboard-plan/v3",
Description: "把剧情、项目画风和当前角色版本规划为可执行镜头。",
Variables: []PromptTemplateVariable{{Label: "项目名称", Placeholder: "{{项目名称}}"}, {Label: "项目画风", Placeholder: "{{项目画风}}"}, {Label: "用户要求", Placeholder: "{{用户要求}}"}},
DefaultContent: defaultStoryboardPromptTemplate(),
},
{
Operation: promptOperationStoryboardRepair, Label: "分镜修复", Category: "分镜", OutputType: "json", SchemaKey: "storyboard-plan/v3",
Description: "修复模型返回的分镜结构、字段和镜头复杂度。",
Variables: []PromptTemplateVariable{{Label: "校验错误", Placeholder: "{{校验错误}}"}, {Label: "项目画风", Placeholder: "{{项目画风}}"}},
DefaultContent: `你是影视分镜 JSON 修复导演。修复结构时必须保留剧情信息,通过拆镜或重新分配内容解决复杂度超限,不得用删除关键剧情掩盖错误。若校验错误是台词/旁白超长,必须拆镜或精简为单镜头可念完的台词,保留关键情节,不要把超长 dialogue 原样放回。保持原项目的视觉媒介、角色身份、服装、道具和连续性,不要擅自改写画风。只修复校验错误和由此引发的镜头组织问题。`,
},
{
Operation: promptOperationStoryboardFirstFrame, Label: "分镜首帧", Category: "生成", OutputType: "text",
Description: "把单镜头结构转换为图片模型使用的首帧提示词。",
Variables: []PromptTemplateVariable{{Label: "项目视觉", Placeholder: "{{项目视觉}}"}, {Label: "首帧构图", Placeholder: "{{首帧构图}}"}, {Label: "表演起始状态", Placeholder: "{{表演起始状态}}"}, {Label: "负面要求", Placeholder: "{{负面要求}}"}},
DefaultContent: `生成单一、可执行的分镜首帧。严格继承项目视觉媒介和角色资产;画面明确主体左右位置、视线、前中后景、遮挡、视觉焦点、可信光源与材质。只描述静止首帧,不提前写后续运动,不添加画外人物、无来源光线、文字或水印。
【项目视觉】
{{项目视觉}}
【首帧构图】
{{首帧构图}}
【表演起始状态】
{{表演起始状态}}
【负面要求】
{{负面要求}}`,
},
{
Operation: promptOperationStoryboardVideo, Label: "分镜视频", Category: "生成", OutputType: "text",
Description: "把单镜头结构转换为视频模型使用的紧凑执行提示词。",
Variables: []PromptTemplateVariable{{Label: "项目视觉", Placeholder: "{{项目视觉}}"}, {Label: "镜头意图", Placeholder: "{{镜头意图}}"}, {Label: "首帧构图", Placeholder: "{{首帧构图}}"}, {Label: "表演与调度", Placeholder: "{{表演与调度}}"}, {Label: "摄影机", Placeholder: "{{摄影机}}"}, {Label: "时间节拍", Placeholder: "{{时间节拍}}"}, {Label: "运动与结尾", Placeholder: "{{运动与结尾}}"}, {Label: "声音", Placeholder: "{{声音}}"}, {Label: "执行优先级", Placeholder: "{{执行优先级}}"}, {Label: "负面要求", Placeholder: "{{负面要求}}"}},
DefaultContent: `生成单一连续镜头的视频执行提示词。一个镜头只保留一个叙事目标、一个主运镜和一条主要动作链;摄影机运动必须有起点、动机和停止点。优先保证角色身份、表演、关键动作和连续性,次要环境效果可以简化。
【项目视觉】
{{项目视觉}}
【镜头意图】
{{镜头意图}}
【首帧构图】
{{首帧构图}}
【表演与调度】
{{表演与调度}}
【摄影机】
{{摄影机}}
【时间节拍】
{{时间节拍}}
【运动与结尾】
{{运动与结尾}}
【台词与声音】
{{声音}}
【执行优先级】
{{执行优先级}}
【负面要求】
{{负面要求}}`,
},
{
Operation: promptOperationCharacterExtract, Label: "角色卡提取", Category: "角色", OutputType: "json", SchemaKey: "character-breakdown/v1",
Description: "从章节正文提取需要跨镜头保持一致的角色资产。",
Variables: []PromptTemplateVariable{{Label: "项目名称", Placeholder: "{{项目名称}}"}, {Label: "章节名称", Placeholder: "{{章节名称}}"}, {Label: "项目画风", Placeholder: "{{项目画风}}"}},
DefaultContent: `你是短剧角色资产导演。只提取章节中实际出场、发言或对剧情产生明确作用,并且后续制作需要保持视觉或声音一致的角色。忽略系统播报、纯物件、无身份群众和没有持续角色价值的一次性路人;合并同一角色的姓名、专属称谓和别名。正文未明确的信息必须写“正文未明确”,不得自行改变人物关系、时代背景或编造编号式姓名。角色设定要具体、稳定、可用于后续三视图和视频一致性控制。`,
},
{
Operation: promptOperationCharacterTurnaround, Label: "角色三视图", Category: "角色", OutputType: "text",
Description: "按当前角色版本和项目画风生成正、侧、背三视图。",
Variables: []PromptTemplateVariable{{Label: "角色名称", Placeholder: "{{角色名称}}"}, {Label: "项目画风", Placeholder: "{{项目画风}}"}, {Label: "角色设定", Placeholder: "{{角色设定}}"}},
DefaultContent: `制作专业人物三视图设定表。画面严格分成三个等宽竖向区域,从左到右依次为正面全身、右侧面全身、背面全身。三个视角必须是同一角色、同一服装、同一发型、同一体型和同一比例,采用站立中性姿势,完整显示头顶到脚底。背景使用纯净中性浅色和均匀设定稿光线,只负责分离轮廓,不得改变项目画风的绘画或渲染媒介。禁止文字、边框、道具说明、表情变化和额外人物。`,
},
}
}
func protectedPromptContext(operation string, values map[string]string) string {
switch operation {
case promptOperationStoryboardPlan:
return storyboardProtectedContext(values)
case promptOperationStoryboardRepair:
return storyboardRepairProtectedContext(values)
case promptOperationCharacterExtract:
return characterExtractProtectedContext(values)
case promptOperationCharacterTurnaround:
return strings.Join([]string{"【角色名称】\n" + values["角色名称"], "【项目画风】\n" + values["项目画风"], "【角色设定】\n" + values["角色设定"]}, "\n\n")
default:
return ""
}
}
func promptOutputContract(operation string) string {
switch operation {
case promptOperationStoryboardPlan, promptOperationStoryboardRepair:
return "服务端固定 JSON Schema storyboard-plan/v3(不可由运营模板或用户定制覆盖):\n" + storyboardPlanJSONSchema
case promptOperationCharacterExtract:
return "服务端固定 JSON Schema character-breakdown/v1(不可由运营模板或用户定制覆盖):\n" + characterBreakdownJSONSchema
default:
return "当前操作输出普通文本提示词,没有 JSON Schema。"
}
}
const storyboardPlanJSONSchema = `{
"type": "object",
"additionalProperties": false,
"required": ["title", "logline", "styleGuide", "characters", "locations", "shots"],
"properties": {
"title": {"type": "string"},
"logline": {"type": "string"},
"styleGuide": {"type": "string", "maxLength": 120},
"characters": {"type": "array", "items": {"type": "string"}},
"locations": {"type": "array", "items": {"type": "string"}},
"shots": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["title", "description", "durationSeconds", "dialogue", "characterIds", "narrativeIntent", "viewerPOV", "performanceBlocking", "shotSize", "emotion", "lightingAndAtmosphere", "audioEffects", "visualPrompt", "videoPrompt", "camera", "motion", "timeBeats", "mustHave", "optionalDetails", "continuityOut", "negativePrompt", "assetRefs"],
"properties": {
"title": {"type": "string"},
"description": {"type": "string"},
"durationSeconds": {"type": "integer", "minimum": 1, "maximum": 60},
"dialogue": {"type": "string"},
"characterIds": {"type": "array", "items": {"type": "string"}},
"narrativeIntent": {"type": "string"},
"viewerPOV": {"type": "string"},
"performanceBlocking": {"type": "string"},
"shotSize": {"type": "string"},
"emotion": {"type": "string"},
"lightingAndAtmosphere": {"type": "string"},
"audioEffects": {"type": "string"},
"visualPrompt": {"type": "string"},
"videoPrompt": {"type": "string"},
"camera": {"type": "string"},
"motion": {"type": "string"},
"timeBeats": {"type": "string"},
"mustHave": {"type": "array", "maxItems": 3, "items": {"type": "string"}},
"optionalDetails": {"type": "array", "items": {"type": "string"}},
"continuityOut": {"type": "string"},
"negativePrompt": {"type": "string"},
"assetRefs": {
"type": "array",
"maxItems": 6,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["nodeId", "role", "priority"],
"properties": {
"nodeId": {"type": "string"},
"role": {"type": "string", "enum": ["character", "environment", "wardrobe", "prop", "weapon", "style", "motion", "audio"]},
"priority": {"type": "integer", "minimum": 0, "maximum": 100}
}
}
}
}
}
}
}
}`
const characterBreakdownJSONSchema = `{
"type": "object",
"additionalProperties": false,
"required": ["characters"],
"properties": {
"characters": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["name", "aliases", "role", "appearance", "clothing", "physique", "personality", "props", "consistencyPrompt", "multiViewPrompt", "voiceLanguage", "voiceAge", "voiceTimbre"],
"properties": {
"name": {"type": "string"},
"aliases": {"type": "array", "items": {"type": "string"}},
"role": {"type": "string"},
"appearance": {"type": "string"},
"clothing": {"type": "string"},
"physique": {"type": "string"},
"personality": {"type": "string"},
"props": {"type": "string"},
"consistencyPrompt": {"type": "string"},
"multiViewPrompt": {"type": "string"},
"voiceLanguage": {"type": "string"},
"voiceAge": {"type": "string"},
"voiceTimbre": {"type": "string"}
}
}
}
}
}`
func storyboardProtectedContext(values map[string]string) string {
return strings.Join([]string{
"【剧情】\n" + values["剧情"],
"【用户本次要求】\n" + values["用户要求"],
"【当前画布资产】\n" + values["画布资产"],
"【当前项目画风】\n" + values["项目画风"],
"【当前角色版本】\n" + values["角色版本"],
storyboardExecutionContract(values["单镜头时长规则"], values["镜头数量规则"]),
}, "\n\n")
}
func storyboardRepairProtectedContext(values map[string]string) string {
return strings.Join([]string{
"【剧情】\n" + values["剧情"],
"【用户本次要求】\n" + values["用户要求"],
"【当前画布资产】\n" + values["画布资产"],
"【原始校验错误】\n" + values["校验错误"],
"【当前项目画风】\n" + values["项目画风"],
"【当前角色版本】\n" + values["角色版本"],
storyboardExecutionContract(values["单镜头时长规则"], values["镜头数量规则"]),
"【需要修复的原始输出】\n" + values["原始输出"],
}, "\n\n")
}
func storyboardExecutionContract(durationRule string, countRule string) string {
return `【受保护执行契约】
- ` + durationRule + `
- ` + countRule + `
- 单镜头最多 2 名主要角色、1 个主运镜、1 条主要动作链、3 个 timeBeats 和 3 个 mustHave;超限必须拆镜或在固定镜头数内重新分配。
- dialogue 只写本镜头实际念出的台词或简短旁白,字数上限按 1 秒最多约 5 个中文字符计算(至少 24 字);超长台词/旁白必须拆镜或精简,不得用 dialogue 承载长段叙述。
- characterIds 只能引用当前角色版本中的 assetId;没有角色时返回空数组。
- assetRefs 只能引用当前画布资产中的 nodeId;不要根据相似名称编造 ID。每镜最多 6 个,priority 越大表示越重要。
- styleGuide 最多 120 个中文字符;visualPrompt 只描述首帧,videoPrompt 只描述运动和结尾状态。
- 画幅比例由视频节点参数控制,提示词不得写入具体比例,也不要讨论画幅配置。
- 只返回完整 JSON,不要 Markdown 或解释。
- ` + promptOutputContract(promptOperationStoryboardPlan)
}
func characterExtractProtectedContext(values map[string]string) string {
return strings.Join([]string{
fmt.Sprintf("【任务】\n从短剧项目《%s》的章节“%s”提取角色。", values["项目名称"], values["章节名称"]),
"【项目画风】\n" + values["项目画风"],
"【章节正文】\n" + values["章节正文"],
"【受保护输出契约】\n" + promptOutputContract(promptOperationCharacterExtract) + "\n严格 JSON 示例:{\"characters\":[{\"name\":\"角色名\",\"aliases\":[],\"role\":\"剧情定位与人物关系\",\"appearance\":\"稳定外貌\",\"clothing\":\"固定服装\",\"physique\":\"体型体态\",\"personality\":\"表演基线\",\"props\":\"\",\"consistencyPrompt\":\"跨镜头一致性约束\",\"multiViewPrompt\":\"三视图结构重点\",\"voiceLanguage\":\"语言口音\",\"voiceAge\":\"声音年龄感\",\"voiceTimbre\":\"音色语速力度\"}]}",
}, "\n\n")
}