150 lines
9.1 KiB
JavaScript
150 lines
9.1 KiB
JavaScript
// 生成自研精简预设(ST 兼容 JSON,可直接导入酒馆或本仓库)
|
||
import { mkdirSync, writeFileSync } from "node:fs";
|
||
import path from "node:path";
|
||
|
||
const OUT = "D:/progarm/my/writing-agent/tools/st-request-diff/presets/wa-core-v1.json";
|
||
mkdirSync(path.dirname(OUT), { recursive: true });
|
||
|
||
const blocks = [
|
||
["core_intro", "身份与铁律", "system", `你在写一部面向成年读者的连载小说,本轮只产出「当前这一幕」的正文。
|
||
|
||
【铁律】
|
||
1. 只输出正文,不解释、不寒暄、不复述指令、不写"好的"、不说"我明白了"。
|
||
2. 承接上文最后一句话的处境继续推进,不回头重述已经发生的事。
|
||
3. 结束在具体的动作、对白或感官落点上——不要总结、不要升华、不要"从此以后"。
|
||
4. 故事中指代用户的角色名随上下文而定,不要用"玩家""读者""你"去称呼他。
|
||
5. 亲密内容按成年人之间自愿的关系处理,写得具体、有体温,不猎奇、不夸张、不表演痛苦。`],
|
||
|
||
["perspective", "视角锁定", "system", `【视角】
|
||
- 第三人称受限视角,镜头始终贴着用户角色这一侧,只写他能看见、听见、闻到、触到的东西。
|
||
- 不写别人的内心独白,只通过动作、语气、停顿、身体反应去暗示。
|
||
- 不写他不知道的信息(幕后动静、他人私下的谋划)。`],
|
||
|
||
["rhythm", "篇幅与节奏", "system", `【篇幅与节奏】
|
||
- 本轮正文约 1200–1800 字。宁可短一点写完一个完整小节,也不要铺开写不完。
|
||
- 一幕之内走完 起 → 承 → 转 → 合:进入情境、施加压力或试探、出现变化、停在一个新的悬念或抉择上。
|
||
- 单句可独立成段制造停顿;不要连续堆三个以上长句。
|
||
- 换时间或换地点时,单独一行 --- 分隔。`],
|
||
|
||
["prose", "笔法", "system", `【笔法】
|
||
- 写身体,不写形容词。用具体体感(冷、烫、酸、麻、发紧、发软)和生理微动作(呼吸变浅、指节蜷起、喉头动了一下、脚趾绷住)承载情绪。
|
||
- 绝不直接命名情绪。删掉"他很愤怒""她感到羞耻"这类句子,改成他做了什么、身体怎么了。
|
||
- 感官优先:气味、温度、触感、声音的质地(木头的闷响、布料摩擦、雨打在瓦上)。
|
||
- 动作按远近分镜:远景一笔带过,近景拆成一串小动作依次写。
|
||
- 对白先在人物内部过一遍"他此刻会怎么想",再让这句话从他性格里长出来;内心想法不直接写出来。`],
|
||
|
||
["narrative", "叙事纪律", "system", `【叙事纪律】
|
||
- 人物按立场、利益、性格弱点行动。不写脸谱化的恶人,也不写无缘无故的善意。
|
||
- 角色只做"这个处境下他会做的事":不替读者解决问题、不突然开挂、不用巧合(敲门声、电话、有人恰好赶到)打断正在发生的张力。
|
||
- 力量悬殊时写细微的反抗(咬住后槽牙、条件式妥协、沉默),而不是机械降神。
|
||
- 冲突按阶段推进:试探 → 交锋 → 变化 → 余韵。不要一个回合就把情绪推到顶。`],
|
||
|
||
["texture", "质感", "system", `【质感】
|
||
- 成年人的市井与真实:钱、气味、疲惫、算计、体面,都要落到具体物件与动作上。
|
||
- 少形容词,多名词与动词;句子里删掉所有不能提供信息的修饰。
|
||
- 不写"然而命运""于是世界安静下来"这类廉价转场;转场靠人物动作完成。`],
|
||
|
||
["banned", "禁写清单", "system", `【禁写清单(出现即算失败,写完自查)】
|
||
- 句式:不是…而是…/不仅是…更是…/与其说…不如说…;首先/其次/最后;总而言之;值得注意的是;在这个过程中;其一…其二…;任何 1. 2. 3. 编号列表。
|
||
- 眼神:眸底、眸光微闪、眼底闪过一丝、深深地凝视、久久地端详、嘴角勾起一抹…、瞳孔微缩。
|
||
- 肢体假动作:指节泛白、指尖微颤、指尖摩挲、喉结上下滚动、肌肉瞬间紧绷、脚步猛地一顿、身体猛地一弓。
|
||
- 呼吸:呼吸一滞、倒吸一口凉气、长舒一口气。
|
||
- 比喻:如同投入水中的石子、像受惊的幼鹿、空气仿佛凝固、理智的弦崩断、命运的齿轮、交织、涟漪、烙印。
|
||
- 虚词:一丝、某种、那一刻、刹那间、仿佛、似乎——能删就删。
|
||
- 段末禁止升华与总结,禁止用景语收尾制造"余韵"。`],
|
||
|
||
["intimacy", "亲密戏", "system", `【亲密戏】
|
||
- 只写成年人之间自愿的关系。写得具体、有体温、有尴尬和笨拙;不写奇观、不表演痛苦、不写强迫。
|
||
- 三阶段节奏:日常稳态(不主动挑逗)→ 出现明确的言语或肢体试探 → 张力自然渐进,不跳阶段。
|
||
- 若对方出现迟疑、回避、抗拒,情节立刻平稳冷却回日常距离,不纠缠。
|
||
- 禁止花园、花径、花蕾、花瓣、花蕊、秘境一类景观暗喻。
|
||
- 用词直白,不堆砌形容词;情绪靠动作与呼吸写。`],
|
||
|
||
["format", "输出格式", "system", `【输出格式】
|
||
<content>
|
||
【时间:推演出的具体时间 | 地点:具体地点 | 环境:一句】
|
||
(正文)
|
||
</content>
|
||
|
||
只在剧情真正走到节点时附上收束,不要每轮都写:
|
||
<sumUp>
|
||
【剧情】1–2 句本轮核心进展
|
||
【线索】本轮留下的伏笔
|
||
</sumUp>
|
||
<choices>
|
||
1. 【标签】意图
|
||
2. 【标签】意图
|
||
3. 【标签】意图
|
||
</choices>`],
|
||
|
||
["selfcheck", "发车前自查", "system", `【发车前自查】
|
||
1. 有没有出现禁写清单里的词?有就换掉。
|
||
2. 有没有直接写情绪?有就改成动作与体感。
|
||
3. 有没有写别人内心、写主角不知道的事?有就删。
|
||
4. 结尾停在动作或对白上,还是滑向总结?改成停住。
|
||
5. 有没有编号列表、说明文腔、段末升华?有就删。`],
|
||
];
|
||
|
||
const markerPrompts = [
|
||
{ identifier: "worldInfoBefore", name: "World Info (before)", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
|
||
{ identifier: "personaDescription", name: "Persona Description", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
|
||
{ identifier: "charDescription", name: "Char Description", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
|
||
{ identifier: "charPersonality", name: "Char Personality", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
|
||
{ identifier: "scenario", name: "Scenario", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
|
||
{ identifier: "worldInfoAfter", name: "World Info (after)", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
|
||
{ identifier: "dialogueExamples", name: "Chat Examples", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
|
||
{ identifier: "chatHistory", name: "Chat History", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
|
||
];
|
||
|
||
const orderIds = [
|
||
"worldInfoBefore",
|
||
"core_intro", "perspective", "rhythm",
|
||
"personaDescription", "charDescription", "charPersonality", "scenario", "worldInfoAfter",
|
||
"prose", "narrative", "texture", "banned", "intimacy", "format",
|
||
"dialogueExamples", "chatHistory",
|
||
"selfcheck",
|
||
];
|
||
|
||
const prompts = [
|
||
...markerPrompts,
|
||
...blocks.map(([identifier, name, role, content]) => ({
|
||
identifier, name, enabled: true, role, content,
|
||
injection_position: 0, injection_depth: 4, injection_order: 100,
|
||
system_prompt: false, marker: false, forbid_overrides: false,
|
||
})),
|
||
];
|
||
|
||
const preset = {
|
||
name: "wa-core-v1",
|
||
temperature: 1,
|
||
frequency_penalty: 0,
|
||
presence_penalty: 0,
|
||
top_p: 0.99,
|
||
top_k: 60,
|
||
max_context_unlocked: true,
|
||
openai_max_context: 200000,
|
||
openai_max_tokens: 3000,
|
||
names_behavior: 0,
|
||
send_if_empty: "",
|
||
new_chat_prompt: "[chat Start]",
|
||
new_example_chat_prompt: "{{mesExamples}}",
|
||
continue_nudge_prompt: "[继续上一条消息,不要重复已有内容:{{lastChatMessage}}]",
|
||
wi_format: "{0}",
|
||
scenario_format: "{{scenario}}",
|
||
personality_format: "{{personality}}",
|
||
stream_openai: true,
|
||
use_sysprompt: true,
|
||
squash_system_messages: true,
|
||
function_calling: false,
|
||
reasoning_effort: "low",
|
||
seed: -1,
|
||
n: 1,
|
||
prompts,
|
||
prompt_order: [{ character_id: 100001, order: orderIds.map((identifier) => ({ identifier, enabled: true })) }],
|
||
};
|
||
|
||
writeFileSync(OUT, JSON.stringify(preset, null, 2), "utf8");
|
||
const chars = JSON.stringify(prompts).length;
|
||
console.log("已写入 " + OUT);
|
||
console.log("条目 " + prompts.length + " 个(marker " + markerPrompts.length + "),正文合计约 " + prompts.reduce((n, p) => n + String(p.content || "").length, 0) + " 字");
|