feat: 圖示改為人物形象圖(有臉),並讓 Wiki 保存 SVG + PNG

1. 圖示三種樣式,優先看得到臉
   photo    有參考圖且工具齊全 → 從官方視覺自動偵測並**裁出臉**,圓角+瞳色外框
   portrait 有參考圖但缺工具   → **有五官的向量人物**(髮型/瞳色/服裝色都取自那張圖)
   badge    完全沒有參考圖     → 舊的雙色漸層 + 編號字母

   portrait 與 badge 共用同一份「圖形清單」,SVG 與自寫柵格器從同一份資料畫,
   兩邊不可能長得不一樣;photo 的 SVG 以 base64 內嵌裁好的 PNG,一樣不外連。

2. 缺工具會「提示安裝」,不靜默降級
   新增 scripts/portrait.py(選用):Pillow 解碼裁切、OpenCV + lbpcascade_animeface
   偵測動漫臉。toolReport() 會列出缺什麼、為什麼要、怎麼裝(venv 免 sudo),
   CLI 在退回向量形象時把這些印出來。
   注意:**OpenCV 5 拿掉了 CascadeClassifier,必須裝 4.x**。
   plugin 本體仍然零依賴——沒有這些工具照樣產得出有臉的形象圖。

   新增 `icon faces`:列出參考圖裡偵測到的所有臉。多角色的主視覺一定要先看再挑
   (--pick <索引>|largest|leftmost|rightmost 或 --face x,y,w,h),挑錯就是別人的臉。

3. Wiki 保存形象圖
   icon.svg 與 icon.png 都在 Wiki 區,另外自動產生一頁 Icon:同時展示兩種格式,
   並列出樣式、調色盤、來源網址、造型說明與裁切框。PNG 同時設為存取庫頭像。

已套用到兩個真實人格(皆為真實裁臉):
  ASUNA-01  《Unanswered//butterfly》(2026) 官方主視覺,臉 #1(anime-cascade)
  YUI-01    AniList 官方角色圖(frontal-cascade)

selftest 148 項全綠(新增第 ⑯ 節:形象圖真的畫了五官、SVG/PNG 出自同一份清單、
工具偵測與安裝提示、Wiki 形象圖專頁與來源)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-30 02:39:58 +00:00
co-authored by Claude Opus 5
parent 1884a9c3d7
commit 15e4eea8ac
11 changed files with 769 additions and 91 deletions
+33 -19
View File
@@ -10,7 +10,7 @@ skills 是共通標準,**鎖與隔離的強制執行需要 hook,目前只有
---
## 十條硬規則
## 十條硬規則
| 規則 | 怎麼做到 |
| --- | --- |
@@ -26,7 +26,8 @@ skills 是共通標準,**鎖與隔離的強制執行需要 hook,目前只有
| **9. 人格可以匯出匯入** | `export` 把身分/情緒/記憶/心智圖/關係圖打包成單一 JSON bundle(可 `--gzip`、附 sha256),`import` 還原或換名複製;**不帶**載入鎖與 guest 租約,`journal/` 要明確 `--with-journal` 才帶走 |
| **10. 人格有編號** | 編號 = **英文名全大寫 + 兩位索引**(同名才遞增):`ASUNA-01``YUI-01``ASUNA-02`。編號同時是新人格的本機目錄名與 Gitea 存取庫名稱;中文名先轉羅馬拼音並跟使用者確認拼法 |
| **11. 人格存在 Gitea,依更新頻率分區** | 每個人格一個**私有存取庫**(庫名=編號)。**檔案區**放每輪都在變的活狀態(情緒/短期記憶/心裡話/說過的話/逐字),**Wiki 區**放低頻的身分與長期結構(IDENTITY/SOUL/長期記憶/心智圖/關係圖)當設定百科。本機仍是工作副本,同步失敗不阻斷對話 |
| **12. 建立並補齊資料後產生圖示,配色取自最新造型** | `/jsc-persona:persona-icon` 上網找出該人格**最新一次登場**的官方視覺、**親眼看過**那張圖,取出髮色/瞳色/服裝色,再繪製 `icon.svg` + `icon.png`512×512),來源網址一併存進 `config.json` 備查;沒有參考圖才退回用編號雜湊配色。PNG 是**自寫柵格器 + zlib 手工編碼**,不依賴任何影像函式庫 |
| **12. 圖示是人物形象圖,取自最新登場的官方視覺** | `/jsc-persona:persona-icon` 上網找出該人格**最新一次登場**的官方視覺、**親眼看過**,有 PillowOpenCV 就直接**裁出臉**當形象圖;沒工具則畫**有五官的向量人物**(配色同樣取自那張圖),並印出安裝指令。來源網址與裁切框存進 `config.json` 備查 |
| **13. Wiki 保存形象圖** | `icon.svg``icon.png` 一起同步到 Wiki 區,另外自動產生一頁 **Icon** 同時展示兩種格式並列出來源、樣式與裁切框;PNG 同時設為存取庫頭像 |
---
@@ -146,11 +147,27 @@ AI 最容易露餡的三件事:把推理過程講出來、一次講一大段
## 人格圖示(SVG + PNG,零外部依賴)
建立人格**並補齊 IDENTITYSOUL 之後**產生,一張 512×512 的幾何徽章。配色有兩種來源:
建立人格**並補齊 IDENTITYSOUL 之後**產生,512×512。**優先是人物形象圖(看得到臉)**,
依環境有無工具分三種樣式:
**① 取自最新造型(`/jsc-persona:persona-icon`,動漫角色建議用這個)**
| 樣式 | 條件 | 長什麼樣 |
| --- | --- | --- |
| **`photo`** | 有參考圖 **且** 有 PillowOpenCV | 從官方視覺**裁出的臉**(自動臉部偵測),圓角+瞳色外框 |
| **`portrait`** | 有參考圖但缺工具 | **有五官的向量人物**:髮型、瞳色、服裝色都取自那張圖 |
| **`badge`** | 完全沒有參考圖 | 雙色漸層 + 編號前兩個字母 |
上網查出角色**最新一次登場**的官方視覺 → 下載 → **親眼看過** → 取五個色 → 繪製
缺工具時 CLI **會印出安裝指令**再退回 `portrait`,不會靜默降級
```bash
python3 -m venv ~/.cache/jsc-persona/venv
~/.cache/jsc-persona/venv/bin/pip install pillow "opencv-python-headless<5" # OpenCV 5 拿掉了 CascadeClassifier
curl -sL -o ~/.cache/jsc-persona/lbpcascade_animeface.xml \
https://raw.githubusercontent.com/nagadomi/lbpcascade_animeface/master/lbpcascade_animeface.xml
```
(venv 放這個路徑會被自動偵測;也可用 `PERSONA_PYTHON` 指定。)
**配色一律取自實際看過的參考圖**
| 元素 | 取自照片的哪裡 |
| --- | --- |
@@ -163,13 +180,7 @@ AI 最容易露餡的三件事:把推理過程講出來、一次講一大段
兩個顏色一深一淺時(藍黑髮 + 淡粉洋裝),程式會把較亮的一端往較暗的壓到對比 ≥ 3.2,
確保字讀得到——顏色仍然是照片來的,只是收斂色階。
**② 沒有參考圖時:編號雜湊**
| 元素 | 怎麼決定 |
| --- | --- |
| 雙色對角漸層 | `sha256(編號|NameEmoji)` → 色相/飽和度/明度 |
| 5×5 左右對稱點陣紋 | 同一組雜湊的位元 |
| 中央兩個字母 | 編號的前兩個字,自動選黑或白確保對比 |
沒有參考圖時(原創人格)才退回雜湊配色:`sha256(編號|NameEmoji)` 決定漸層與點陣紋。
- **同一個人格永遠得到同一張圖**(純函數,沒有隨機);`ASUNA-01``ASUNA-02` 明顯不同。
- SVG 與 PNG **是同一張圖**:兩者共用同一組單位座標與同一份點陣字資料。
@@ -180,17 +191,20 @@ AI 最容易露餡的三件事:把推理過程講出來、一次講一大段
rsvgimagemagickPillowffmpeg 都沒有),JPEG/WebP 讀不進來,無法把原圖轉成 PNG;
而且把他人的美術作品原樣放進存取庫是散布、不是引用。**取配色**是有依據又不搬運原圖的做法。
emoji 同理無法柵格化,但仍參與雜湊配色。
- 圖示屬於低頻資料 → 同步到 **Wiki 區**,並自動設成 Gitea **存取庫頭像**
- 圖示屬於低頻資料 → `icon.svg``icon.png`同步到 **Wiki 區**,並自動產生一頁 **Icon**
展示兩種格式與來源;PNG 同時設成 Gitea **存取庫頭像**
```bash
# 取自最新造型(必須同時給來源網址
# 先看參考圖裡有幾張臉(多角色的主視覺務必先看
node scripts/persona.mjs icon faces --session <id> --photo "<圖片網址>"
# 裁臉 + 取色(--palette 必須配 --source-url
node scripts/persona.mjs icon generate --session <id> --force \
--photo "<圖片網址>" --pick 1 \
--palette "hair=#d9a45b,eye=#9e5b3e,accent=#c0392b,secondary=#e77a8e,light=#f2ebe3" \
--source-url "<官方視覺的網址>" --source-note "<作品(年份)+造型描述>"
# 沒有參考圖:編號雜湊配色
node scripts/persona.mjs icon generate --session <id> [--size 512] [--force]
node scripts/persona.mjs icon show --session <id> # 看配色與來源
node scripts/persona.mjs icon show --session <id> # 看樣式、配色、來源與裁切框
```
## 人格編號與 Gitea 儲存
@@ -355,11 +369,11 @@ node scripts/persona.mjs said check --session <id> --text "<話>" # 這句是
node scripts/persona.mjs room script --session <id> --room <room> # 乾淨對話稿(劇場模式用)
node scripts/persona.mjs export --session <id> --out lumi.json # 離線搬家(單檔)
node scripts/persona.mjs sync status --session <id> # Gitea 同步狀態
node scripts/selftest.mjs # 136 項驗證:鎖、隔離、情緒、固化、說話節制、劇場模式、匯出匯入、編號與 Gitea 分區、圖示與照片配色、hooks
node scripts/selftest.mjs # 148 項驗證:鎖、隔離、情緒、固化、說話節制、劇場模式、匯出匯入、編號與 Gitea 分區、形象圖與裁臉、hooks
```
檔案結構:`scripts/persona-lib.mjs`(核心:鎖/隔離/情緒/記憶)、`scripts/persona.mjs`CLI)、
`scripts/persona-gitea.mjs`(編號與 Gitea 同步)、`scripts/persona-icon.mjs`(圖SVG + 自寫 PNG 編碼)、`hooks/*.mjs`(六個 hook)、`scripts/selftest.mjs`(自我測試)。
`scripts/persona-gitea.mjs`(編號與 Gitea 同步)、`scripts/persona-icon.mjs`形象圖:SVG + 自寫 PNG 編碼)、`scripts/portrait.py`(選用:照片裁臉)、`hooks/*.mjs`(六個 hook)、`scripts/selftest.mjs`(自我測試)。
---