Files
Jeffery (jiantw83)andClaude Sonnet 5.5 697ed6a0b9 feat: 亞絲娜與直葉的角色包 (#6, #7)
build_pack.py 產生對白索引(放在使用者資料目錄);render_skill.py 把人格卡渲染成
角色 skill 並檢查精選對白逐字來自索引;插件改成執行期從資料目錄讀索引。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 20:52:40 +00:00

79 lines
3.0 KiB
Python
Raw Permalink 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.
#!/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()