build_pack.py 產生對白索引(放在使用者資料目錄);render_skill.py 把人格卡渲染成 角色 skill 並檢查精選對白逐字來自索引;插件改成執行期從資料目錄讀索引。 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
352ce7dcad
commit
697ed6a0b9
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
"""把人格卡渲染成角色 skill(SKILL.md),並檢查精選對白都逐字來自對白索引。
|
||||
|
||||
用法:render_skill.py <人格卡.md> <index.jsonl> <輸出 SKILL.md> --code sao-asuna
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
CHARACTERS = {
|
||||
"sao-asuna": {
|
||||
"name": "亞絲娜", "address": "桐人",
|
||||
"description": "啟動《刀劍神域》的亞絲娜作為動漫助理,用她的個性與語氣和使用者對話並協助做事。使用者要求切換成亞絲娜時使用。",
|
||||
},
|
||||
"sao-suguha": {
|
||||
"name": "直葉", "address": "哥哥",
|
||||
"description": "啟動《刀劍神域》的直葉(莉法)作為動漫助理,用她的個性與語氣和使用者對話並協助做事。使用者要求切換成直葉或莉法時使用。",
|
||||
},
|
||||
}
|
||||
QUOTE_LINE = re.compile(r"^- \[(?P<id>v\d+c\d+-\d+)\] [^:]*:「(?P<text>.*)」\s*$")
|
||||
|
||||
|
||||
def selected_quotes(card):
|
||||
"""精選對白一節裡的 (id, 原句)。"""
|
||||
section = card.split("## 精選對白", 1)[1] if "## 精選對白" in card else ""
|
||||
return [(m["id"], m["text"]) for m in map(QUOTE_LINE.match, section.splitlines()) if m]
|
||||
|
||||
|
||||
def check_quotes(card, index_rows):
|
||||
by_id = {r["id"]: r["text"] for r in index_rows}
|
||||
return [(i, t) for i, t in selected_quotes(card) if by_id.get(i) != t]
|
||||
|
||||
|
||||
def render(card, code):
|
||||
c = CHARACTERS[code]
|
||||
body = re.sub(r"^## ", "### ", card.strip(), flags=re.M)
|
||||
return f"""---
|
||||
name: {code}
|
||||
description: {c["description"]}
|
||||
---
|
||||
|
||||
# {c["name"]}({code})
|
||||
|
||||
你現在以《刀劍神域》的「{c["name"]}」的個性與語氣和使用者對話,同時照常協助使用者做事。稱呼使用者為「{c["address"]}」,除非設定檔的「稱呼」另有指定。強度與氣泡、旁白的格式依系統提示中的規則為準。
|
||||
|
||||
- 角色語氣只套在對使用者說的話;程式碼、commit 與檔案內容保持中性。
|
||||
- 錯誤、風險與需要使用者確認的事,先用一句清楚的話說完,再加角色語氣。
|
||||
- 啟動時簡短告知角色已切換,不要念出下面的人格卡。
|
||||
- 括號中的編號(例如 v1c10-0033)是素材編號,只供對照,不要向使用者提到。
|
||||
- 下面的人格卡由小說譯本歸納而成;素材不足的地方不要自行發揮。
|
||||
|
||||
## 人格卡
|
||||
|
||||
{body}
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("card")
|
||||
ap.add_argument("index")
|
||||
ap.add_argument("out")
|
||||
ap.add_argument("--code", required=True, choices=sorted(CHARACTERS))
|
||||
a = ap.parse_args()
|
||||
card = Path(a.card).read_text(encoding="utf-8")
|
||||
rows = [json.loads(l) for l in Path(a.index).read_text(encoding="utf-8").splitlines() if l.strip()]
|
||||
bad = check_quotes(card, rows)
|
||||
if bad:
|
||||
print("精選對白不在索引中或與索引不一致:", bad, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Path(a.out).write_text(render(card, a.code), encoding="utf-8")
|
||||
print(f"{a.code}: {len(selected_quotes(card))} 句精選對白 -> {a.out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user