feat(webui, llm, i18n): 添加可自定义的解说文案字数配置

在WebUI脚本设置页面新增字数输入控件,支持100-5000区间的目标字数设置
更新LLM提示词模板,使用用户指定字数作为目标(±10%浮动)替代原固定范围
补充中英文国际化翻译文案
升级项目版本至0.8.7
更新相关服务函数与单元测试适配新参数
This commit is contained in:
linyq-laien 2026-07-18 04:26:44 +08:00
parent 7639c9d289
commit 022b8bbea3
10 changed files with 66 additions and 6 deletions

View File

@ -409,6 +409,7 @@ class SubtitleAnalyzer:
temperature: float = 0.7,
narration_language: str = "简体中文(中国)",
drama_genre: str = "逆袭/复仇",
narration_word_count: int = 500,
) -> Dict[str, Any]:
"""生成供用户审核修改的解说正文。"""
try:
@ -420,6 +421,7 @@ class SubtitleAnalyzer:
"plot_analysis": plot_analysis,
"subtitle_content": subtitle_content,
"narration_language": narration_language,
"narration_word_count": int(narration_word_count),
},
)
return self._generate_plain_text(prompt, system_prompt, temperature)
@ -964,6 +966,7 @@ def generate_narration_copy(
narration_language: str = "简体中文(中国)",
drama_genre: str = "逆袭/复仇",
prompt_category: str = "short_drama_narration",
narration_word_count: int = 500,
) -> Dict[str, Any]:
"""生成可供用户审核修改的解说正文。"""
analyzer = SubtitleAnalyzer(
@ -982,6 +985,7 @@ def generate_narration_copy(
temperature=temperature,
narration_language=narration_language,
drama_genre=drama_genre,
narration_word_count=narration_word_count,
)

View File

@ -292,6 +292,7 @@ class SubtitleAnalyzerAdapter:
temperature: float = 0.7,
narration_language: str = "简体中文(中国)",
drama_genre: str = "逆袭/复仇",
narration_word_count: int = 500,
) -> Dict[str, Any]:
"""Generate editable narration copy before timeline matching."""
try:
@ -303,6 +304,7 @@ class SubtitleAnalyzerAdapter:
"plot_analysis": plot_analysis,
"subtitle_content": subtitle_content,
"narration_language": narration_language,
"narration_word_count": int(narration_word_count),
},
)
narration_copy = self._generate_plain_text(prompt, system_prompt, temperature)

View File

@ -24,11 +24,14 @@ class SubtitleAnalyzerAdapterPipelineTests(unittest.TestCase):
temperature=0.7,
narration_language="简体中文(中国)",
drama_genre="家庭伦理",
narration_word_count=800,
)
self.assertEqual("success", result["status"])
self.assertIn("反击", result["narration_copy"])
self.assertIn("家庭伦理", call.call_args.kwargs["prompt"])
self.assertIn("800", call.call_args.kwargs["prompt"])
self.assertNotIn("300-650", call.call_args.kwargs["prompt"])
self.assertNotIn("response_format", call.call_args.kwargs)
def test_generate_narration_copy_can_use_film_tv_prompt_category(self):
@ -49,11 +52,14 @@ class SubtitleAnalyzerAdapterPipelineTests(unittest.TestCase):
temperature=0.7,
narration_language="简体中文(中国)",
drama_genre="悬疑/犯罪",
narration_word_count=1200,
)
self.assertEqual("success", result["status"])
self.assertIn("影视解说正文创作任务", call.call_args.kwargs["prompt"])
self.assertIn("用户选择的影视类型", call.call_args.kwargs["prompt"])
self.assertIn("1200", call.call_args.kwargs["prompt"])
self.assertNotIn("350-750", call.call_args.kwargs["prompt"])
self.assertNotIn("短剧解说正文创作任务", call.call_args.kwargs["prompt"])
def test_film_tv_script_prompts_exclude_intro_outro_and_ads(self):

View File

@ -22,7 +22,14 @@ class NarrationCopyPrompt(ParameterizedPrompt):
model_type=ModelType.TEXT,
output_format=OutputFormat.TEXT,
tags=["影视", "解说文案", "电影解说", "剧情承接", "用户审核"],
parameters=["drama_name", "drama_genre", "plot_analysis", "subtitle_content", "narration_language"],
parameters=[
"drama_name",
"drama_genre",
"plot_analysis",
"subtitle_content",
"narration_language",
"narration_word_count",
],
)
super().__init__(metadata, required_parameters=["drama_name", "plot_analysis", "subtitle_content"])
@ -57,6 +64,11 @@ ${narration_language}
${drama_genre}
</drama_genre>
## 用户要求的文案字数
<narration_word_count>
${narration_word_count}
</narration_word_count>
## 类型写作规则
必须按用户选择的影视类型调整表达重点不要自行改判类型
- 剧情/情感突出人物选择关系裂痕命运压力和情绪余波
@ -81,7 +93,7 @@ ${drama_genre}
4. 每句话只表达一个信息点适合后续按句匹配画面
5. 句子尽量短单句优先 15-35 信息复杂时拆成多句
6. 2-3 句要有明确承接让观众知道为什么从上一幕来到下一幕
7. 总长度控制在 350-750 短素材取下限长素材取上限
7. 总长度${narration_word_count} 字为目标允许上下浮动 10%中日韩语言按非空白字符计数其他语言按单词计数不得再套用固定长度区间
8. 不要使用编号项目符号章节标题或括号说明
## 输出要求

View File

@ -22,7 +22,14 @@ class NarrationCopyPrompt(ParameterizedPrompt):
model_type=ModelType.TEXT,
output_format=OutputFormat.TEXT,
tags=["短剧", "解说文案", "爆款开头", "叙事连续性", "用户审核"],
parameters=["drama_name", "drama_genre", "plot_analysis", "subtitle_content", "narration_language"],
parameters=[
"drama_name",
"drama_genre",
"plot_analysis",
"subtitle_content",
"narration_language",
"narration_word_count",
],
)
super().__init__(metadata, required_parameters=["drama_name", "plot_analysis", "subtitle_content"])
@ -57,6 +64,11 @@ ${narration_language}
${drama_genre}
</drama_genre>
## 用户要求的文案字数
<narration_word_count>
${narration_word_count}
</narration_word_count>
## 类型写作规则
必须按用户选择的短剧类型调整表达重点不要自行改判类型
- 霸总/甜宠突出误会身份差暧昧拉扯守护感和情绪反差
@ -81,7 +93,7 @@ ${drama_genre}
4. 每句话只表达一个信息点适合后续按句匹配画面
5. 句子尽量短单句优先 15-35 信息复杂时拆成多句
6. 2-3 句要有明确因果承接让观众知道为什么从上一幕来到下一幕
7. 总长度控制在 300-650 短素材取下限长素材取上限
7. 总长度${narration_word_count} 字为目标允许上下浮动 10%中日韩语言按非空白字符计数其他语言按单词计数不得再套用固定长度区间
8. 不要使用编号项目符号章节标题或括号说明
## 输出要求

View File

@ -1 +1 @@
0.8.6
0.8.7

View File

@ -97,6 +97,9 @@ FILM_TV_TYPE_VALUES = {
"horror_thriller": "恐怖/惊悚",
}
SHORT_DRAMA_ORIGINAL_SOUND_RATIO_OPTIONS = list(range(0, 100, 10))
DEFAULT_NARRATION_WORD_COUNT = 500
MIN_NARRATION_WORD_COUNT = 100
MAX_NARRATION_WORD_COUNT = 5000
SUMMARY_MODE_CONFIGS = {
MODE_FILM_SUMMARY: {
"mode_label_key": "Film TV Narration",
@ -1574,6 +1577,7 @@ def render_script_buttons(tr, params):
type_option_key = _summary_state_key(summary_config, "type_option")
custom_type_key = _summary_state_key(summary_config, "custom_type")
original_sound_ratio_key = _summary_state_key(summary_config, "original_sound_ratio")
narration_word_count_key = _summary_state_key(summary_config, "narration_word_count")
language_option_key = _summary_state_key(summary_config, "narration_language_option")
custom_language_key = _summary_state_key(summary_config, "custom_narration_language")
narration_copy_key = _summary_state_key(summary_config, "narration_copy")
@ -1592,7 +1596,7 @@ def render_script_buttons(tr, params):
config_col_widths = [1.15]
if show_custom_type:
config_col_widths.append(1.15)
config_col_widths.extend([0.9, 1.15])
config_col_widths.extend([0.9, 0.9, 1.15])
if show_custom_language:
config_col_widths.append(1.15)
@ -1623,6 +1627,16 @@ def render_script_buttons(tr, params):
key=original_sound_ratio_key,
)
config_col_index += 1
with config_cols[config_col_index]:
st.number_input(
tr("文案字数"),
min_value=MIN_NARRATION_WORD_COUNT,
max_value=MAX_NARRATION_WORD_COUNT,
value=DEFAULT_NARRATION_WORD_COUNT,
step=50,
key=narration_word_count_key,
)
config_col_index += 1
with config_cols[config_col_index]:
st.selectbox(
tr("解说语言"),
@ -1663,6 +1677,7 @@ def render_script_buttons(tr, params):
type_option_key = _summary_state_key(summary_config, "type_option")
custom_type_key = _summary_state_key(summary_config, "custom_type")
original_sound_ratio_key = _summary_state_key(summary_config, "original_sound_ratio")
narration_word_count_key = _summary_state_key(summary_config, "narration_word_count")
language_option_key = _summary_state_key(summary_config, "narration_language_option")
custom_language_key = _summary_state_key(summary_config, "custom_narration_language")
narration_copy_key = _summary_state_key(summary_config, "narration_copy")
@ -1674,6 +1689,9 @@ def render_script_buttons(tr, params):
narration_language = _resolve_summary_narration_language(summary_config)
drama_genre = _resolve_summary_type(summary_config)
original_sound_ratio = int(st.session_state.get(original_sound_ratio_key, 30))
narration_word_count = int(
st.session_state.get(narration_word_count_key, DEFAULT_NARRATION_WORD_COUNT)
)
if (
st.session_state.get(type_option_key) == "custom"
and not str(st.session_state.get(custom_type_key, '') or '').strip()
@ -1720,6 +1738,7 @@ def render_script_buttons(tr, params):
video_paths=_selected_video_paths(),
narration_language=narration_language,
drama_genre=drama_genre,
narration_word_count=narration_word_count,
prompt_category=summary_config["prompt_category"],
search_keywords=summary_config["search_keywords"],
empty_title_message_key=summary_config["empty_title_message_key"],

View File

@ -341,6 +341,7 @@
"影视类型": "Film/TV Type",
"自定义影视类型": "Custom Film/TV Type",
"原片占比": "Original Footage Ratio",
"文案字数": "Copy Length",
"例如:豪门虐恋": "For example: billionaire angst romance",
"例如:悬疑犯罪": "For example: suspense crime",
"请输入自定义短剧类型": "Please enter a custom short drama type",

View File

@ -703,6 +703,7 @@
"影视类型": "影视类型",
"自定义影视类型": "自定义影视类型",
"原片占比": "原片占比",
"文案字数": "文案字数",
"例如:豪门虐恋": "例如:豪门虐恋",
"例如:悬疑犯罪": "例如:悬疑犯罪",
"请输入自定义短剧类型": "请输入自定义短剧类型",

View File

@ -363,6 +363,7 @@ def generate_short_drama_narration_copy(
search_keywords: str = SHORT_DRAMA_SEARCH_KEYWORDS,
empty_title_message_key: str = "Please enter short drama name before web search",
web_search_context_description: str = "短剧名称、人物关系、剧情背景和公开剧情梗概",
narration_word_count: int = 500,
):
"""生成可由用户审核修改的短剧解说正文,不绑定时间戳。"""
subtitle_paths = _normalize_paths(subtitle_path)
@ -422,6 +423,7 @@ def generate_short_drama_narration_copy(
temperature=temperature,
narration_language=narration_language,
drama_genre=drama_genre,
narration_word_count=narration_word_count,
)
except Exception as e:
logger.warning(f"使用新LLM服务生成文案失败回退到旧实现: {str(e)}")
@ -436,6 +438,7 @@ def generate_short_drama_narration_copy(
provider=text_provider,
narration_language=narration_language,
drama_genre=drama_genre,
narration_word_count=narration_word_count,
prompt_category=prompt_category,
)