feat(語域): 自稱/句尾/口癖/不說做成第三種 voice kind,四格輪替注入
語氣層原本只有減法(SPEECH_BLACKLIST 刪 AI 腔)與原句樣本。刪完剩下的是「乾淨的 中文」,不是「這個人的中文」——把名字遮掉還認得出是誰,靠的是自稱、句尾、口癖。 做成 voice kind(voice/idiolect.md)而不是 IDENTITY 的新區塊,是為了讓 persona-story 與 persona-anime 天生寫同一格:同一個目錄、同一支 voice add、 同一條 Gitea Wiki 同步路徑(voice/ 是前綴比對,新檔自動涵蓋)。 TODO 要的「兩邊用同一個欄位」因此不必再另外對齊。 四格寫死在 IDIOLECT_FACETS。自由欄位會長出「風格」「語氣」這種什麼都能塞的格子, 注入端就不知道該怎麼講給人格聽。「不說」單獨一格而不是塞進禁忌:禁忌是話題 (不能提的事),不說是用詞(這個人不會用的字),混在一起會讓人格以為某個話題不能碰。 一輪注入 3 格、四格輪替,起點由第幾輪(felt.jsonl 筆數)算出來。不用隨機數—— 那是本專案明文禁止的充人味手段,而且同一輪重跑要得到同樣的結果。 一次塞四格會變成模仿腔:他開始逐條照著演,每句都要有口癖。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
-8
@@ -1233,12 +1233,16 @@ commands.voice = ({ flags, positional }) => {
|
||||
const kind = str(flags.kind) || "sample";
|
||||
if (!(kind in pl.VOICE_KINDS)) {
|
||||
die(`--kind 只能是 ${Object.keys(pl.VOICE_KINDS).join("/")}` +
|
||||
"(sample=他自己講過的原句,reaction=事件對上他做了什麼)。");
|
||||
"(sample=他自己講過的原句,reaction=事件對上他做了什麼,idiolect=語域的四格)。");
|
||||
}
|
||||
const render = (entry) => (kind === "sample"
|
||||
? `「${entry.text}」${[entry.to ? `對 ${entry.to}` : "", entry.scene].filter(Boolean).join("/")
|
||||
? `(${[entry.to ? `對 ${entry.to}` : "", entry.scene].filter(Boolean).join("/")})` : ""}`
|
||||
: `${entry.event} → ${entry.action || "(沒記)"}${entry.emotion ? `(${entry.emotion})` : ""}`);
|
||||
const render = (entry) => {
|
||||
if (kind === "idiolect") return `${entry.facet}→${entry.value}`;
|
||||
if (kind === "reaction") {
|
||||
return `${entry.event} → ${entry.action || "(沒記)"}${entry.emotion ? `(${entry.emotion})` : ""}`;
|
||||
}
|
||||
const at = [entry.to ? `對 ${entry.to}` : "", entry.scene].filter(Boolean).join("/");
|
||||
return `「${entry.text}」${at ? `(${at})` : ""}`;
|
||||
};
|
||||
if (action === "show" || action === "list") {
|
||||
requireMember(slug, session, Boolean(flags["as-guest"]));
|
||||
const voice = pl.loadVoice(slug);
|
||||
@@ -1254,7 +1258,7 @@ commands.voice = ({ flags, positional }) => {
|
||||
]);
|
||||
return;
|
||||
}
|
||||
const rows = kind === "sample" ? voice.samples : voice.reactions;
|
||||
const rows = kind === "sample" ? voice.samples : kind === "reaction" ? voice.reactions : voice.idiolect;
|
||||
const limit = num(flags.limit, 20);
|
||||
const shown = Number.isFinite(limit) && limit > 0 ? rows.slice(-limit) : rows;
|
||||
emit({ persona: slug, kind, total: rows.length, entries: shown }, flags.json, [
|
||||
@@ -1268,11 +1272,16 @@ commands.voice = ({ flags, positional }) => {
|
||||
if (action === "add") {
|
||||
const res = kind === "sample"
|
||||
? pl.addVoiceSample(slug, { text: str(flags.text), to: str(flags.to), scene: str(flags.scene) })
|
||||
: pl.addVoiceReaction(slug, { event: str(flags.event), action: str(flags.action), emotion: str(flags.emotion) });
|
||||
: kind === "reaction"
|
||||
? pl.addVoiceReaction(slug, { event: str(flags.event), action: str(flags.action), emotion: str(flags.emotion) })
|
||||
: pl.addVoiceIdiolect(slug, { facet: str(flags.facet), value: str(flags.value) });
|
||||
if (!res) {
|
||||
die(kind === "sample"
|
||||
? "需要 `--text`(他自己講過的原句,照抄不要改寫)。"
|
||||
: "需要 `--event`(發生了什麼事);`--action` 寫他做了什麼,不要寫他感覺到什麼。");
|
||||
: kind === "reaction"
|
||||
? "需要 `--event`(發生了什麼事);`--action` 寫他做了什麼,不要寫他感覺到什麼。"
|
||||
: `需要 \`--facet\`(只能是 ${pl.IDIOLECT_FACETS.join("/")})與 \`--value\`。` +
|
||||
"「不說」寫的是**用詞**(這個人不會用的字),不是話題禁忌。");
|
||||
}
|
||||
if (!res.added) {
|
||||
ok(`已經有一模一樣的一條了,沒有重複寫入:${res.line}`);
|
||||
|
||||
Reference in New Issue
Block a user