refactor: 统一 system/_meta.json 管理分类;修复前端 TypeScript 报错
This commit is contained in:
@@ -14,6 +14,7 @@ Prompt 简单加载器
|
||||
└── ...
|
||||
"""
|
||||
|
||||
import json
|
||||
import json
|
||||
import random
|
||||
from pathlib import Path
|
||||
@@ -60,12 +61,25 @@ def render_template(template: str, **kwargs) -> str:
|
||||
# ====================== 新分类体系:动态扫描 ======================
|
||||
|
||||
SYSTEM_PROMPTS_DIR = _PROMPTS_DIR / "system"
|
||||
_SYSTEM_META_PATH = SYSTEM_PROMPTS_DIR / "_meta.json"
|
||||
|
||||
|
||||
def _load_system_meta() -> dict:
|
||||
"""读取 system/_meta.json"""
|
||||
if _SYSTEM_META_PATH.exists():
|
||||
try:
|
||||
return json.loads(_SYSTEM_META_PATH.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, OSError):
|
||||
pass
|
||||
return {}
|
||||
|
||||
|
||||
def list_categories() -> list[dict]:
|
||||
"""
|
||||
扫描 system/ 目录,返回所有分类结构
|
||||
|
||||
名称从 system/_meta.json 读取,count 动态扫描目录统计。
|
||||
|
||||
Returns:
|
||||
[
|
||||
{
|
||||
@@ -79,7 +93,10 @@ def list_categories() -> list[dict]:
|
||||
...
|
||||
]
|
||||
"""
|
||||
meta = _load_system_meta()
|
||||
meta_categories = {c["code"]: c for c in meta.get("categories", [])}
|
||||
categories = []
|
||||
|
||||
if not SYSTEM_PROMPTS_DIR.exists():
|
||||
return categories
|
||||
|
||||
@@ -87,34 +104,34 @@ def list_categories() -> list[dict]:
|
||||
if not cat_dir.is_dir():
|
||||
continue
|
||||
|
||||
# 读取大类元数据
|
||||
cat_meta = _load_meta(cat_dir)
|
||||
cat_name = cat_meta.get("name", cat_dir.name)
|
||||
cat_code = cat_dir.name
|
||||
cat_meta = meta_categories.get(cat_code, {})
|
||||
meta_subs = {s["code"]: s for s in cat_meta.get("subcategories", [])}
|
||||
cat_name = cat_meta.get("name", cat_code)
|
||||
|
||||
subcategories = []
|
||||
for sub_dir in sorted(cat_dir.iterdir()):
|
||||
if not sub_dir.is_dir():
|
||||
continue
|
||||
|
||||
# 读取小类元数据
|
||||
sub_meta = _load_meta(sub_dir)
|
||||
sub_name = sub_meta.get("name", sub_dir.name)
|
||||
sub_code = sub_dir.name
|
||||
sub_name = meta_subs.get(sub_code, {}).get("name", sub_code)
|
||||
|
||||
# 统计提示词文件数量(排除 _meta.json)
|
||||
prompt_files = [
|
||||
f for f in sub_dir.iterdir()
|
||||
if f.is_file() and f.name != "_meta.json"
|
||||
if f.is_file() and f.suffix == ".txt"
|
||||
]
|
||||
|
||||
subcategories.append({
|
||||
"code": sub_dir.name,
|
||||
"code": sub_code,
|
||||
"name": sub_name,
|
||||
"count": len(prompt_files),
|
||||
})
|
||||
|
||||
if subcategories:
|
||||
categories.append({
|
||||
"code": cat_dir.name,
|
||||
"code": cat_code,
|
||||
"name": cat_name,
|
||||
"subcategories": subcategories,
|
||||
})
|
||||
@@ -122,17 +139,6 @@ def list_categories() -> list[dict]:
|
||||
return categories
|
||||
|
||||
|
||||
def _load_meta(directory: Path) -> dict:
|
||||
"""读取目录下的 _meta.json"""
|
||||
meta_path = directory / "_meta.json"
|
||||
if meta_path.exists():
|
||||
try:
|
||||
return json.loads(meta_path.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, OSError):
|
||||
pass
|
||||
return {}
|
||||
|
||||
|
||||
def load_system_prompt(category: str, subcategory: str) -> str:
|
||||
"""
|
||||
根据大类+小类随机加载一个 System Prompt
|
||||
@@ -148,7 +154,7 @@ def load_system_prompt(category: str, subcategory: str) -> str:
|
||||
if not sub_dir.exists():
|
||||
return ""
|
||||
|
||||
# 收集所有提示词文件(排除 _meta.json)
|
||||
# 收集所有提示词文件
|
||||
prompt_files = [
|
||||
f for f in sub_dir.iterdir()
|
||||
if f.is_file() and f.suffix == ".txt"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"categories": [
|
||||
{
|
||||
"code": "bk",
|
||||
"name": "装修避坑",
|
||||
"subcategories": [
|
||||
{ "code": "ht", "name": "装修合同避坑" },
|
||||
{ "code": "lc", "name": "装修全流程避坑" },
|
||||
{ "code": "qw", "name": "全屋定制避坑" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{"name": "装修合同避坑"}
|
||||
@@ -1 +0,0 @@
|
||||
{"name": "装修全流程避坑"}
|
||||
@@ -1 +0,0 @@
|
||||
{"name": "全屋定制避坑"}
|
||||
@@ -20,7 +20,6 @@ export default function ScriptCreation() {
|
||||
const segments = useProjectStore(state => state.segments);
|
||||
const setSegments = useProjectStore(state => state.setSegments);
|
||||
const updateSegment = useProjectStore(state => state.updateSegment);
|
||||
const topic = useProjectStore(state => state.topic) || '';
|
||||
const setTopic = useProjectStore(state => state.setTopic);
|
||||
const scriptDuration = useProjectStore(state => state.scriptDuration) || 45;
|
||||
const setScriptDuration = useProjectStore(state => state.setScriptDuration);
|
||||
|
||||
@@ -218,7 +218,7 @@ export default function VoiceDubbing() {
|
||||
<span className="voice-row-desc-inline">{v.description}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button className="preview-icon" onClick={e => handlePlayPause(v.voiceId, v.previewUrl, e)}>
|
||||
<button className="preview-icon" onClick={e => handlePlayPause(v.voiceId, v.previewUrl ?? null, e)}>
|
||||
{playingVoiceId === v.voiceId ? '⏸' : '▶'}
|
||||
</button>
|
||||
</div>
|
||||
@@ -243,7 +243,7 @@ export default function VoiceDubbing() {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button className="preview-icon" onClick={e => handlePlayPause(m.voiceId, m.trialUrl, e)}>
|
||||
<button className="preview-icon" onClick={e => handlePlayPause(m.voiceId, m.trialUrl ?? null, e)}>
|
||||
{playingVoiceId === m.voiceId ? '⏸' : '▶'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -88,6 +88,9 @@ interface SaveState {
|
||||
selectedHumanId?: string;
|
||||
selectedElementId?: number;
|
||||
selectedVoiceId?: string;
|
||||
dubbingAudioUrl?: string;
|
||||
dubbingAudioPath?: string;
|
||||
dubbingVoiceId?: string;
|
||||
scriptDuration?: number;
|
||||
scriptType?: string;
|
||||
currentStep?: number;
|
||||
|
||||
Reference in New Issue
Block a user