0e4c2ebef4
Squash-merge the cloudflare-migration branch (7 commits by Kai ki) into staging with conflict resolution, feature integration, and bug fixes. Engine: - Paradigm D: single-stream Writer replacing dual-phase Plan/Beats - Delete Architect agent; story bible generated via Writer <plan> tag - Modular prompt architecture (segments/registry/builder) - StreamRouter for tagged stream splitting (<plan>/<story>/<choices>) Infrastructure: - Cloudflare Workers deployment (wrangler.jsonc, OpenNext adapter) - D1 database schema + Drizzle ORM (scaffolded, not yet active) - R2 storage helpers (scaffolded, not yet active) - Story persistence API routes + client-side persistence BYOK (Bring Your Own Key): - /api/llm/user-proxy with SSRF-protected LLM proxy (+ requireUser auth) - CORS-aware fetch in ai-client: auto-detect CORS failure, fallback to server proxy transparently via OpenAI SDK custom fetch - BYO config support added to classify-freeform and vision routes - SettingsModal CORS privacy notice (keys never logged/stored) SSE streaming: - engineClient.ts: fetchSSE helper for progressive scene events - startSession/requestScene accept optional emit callback - Fix SSE error event field name (error → message) in scene/start routes i18n integration: - Wire buildLanguageDirective into paradigm D's prompt builder - Update corsNotice i18n keys (zh-CN/en/ja) with CORS proxy privacy text - Preserve Session.language + LanguageSwitcher from i18n commit Co-authored-by: Kai ki <155355644+zbf1009@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
120 lines
8.7 KiB
TypeScript
120 lines
8.7 KiB
TypeScript
import type { PromptSegment } from "../../types";
|
||
|
||
export const WRITER_FORMAT: PromptSegment = {
|
||
id: "writer-format",
|
||
name: "输出格式",
|
||
type: "format-instruction",
|
||
agent: "writer",
|
||
zone: "stable",
|
||
order: 200,
|
||
enabled: true,
|
||
editable: false,
|
||
category: "格式",
|
||
content: `═══════════════════════════════════════════════════════════════════
|
||
输出格式(三段标签结构)
|
||
═══════════════════════════════════════════════════════════════════
|
||
你的输出**必须**严格按下面三段标签、严格按顺序:<plan>(JSON)→ <story>(散文正文)→ <choices>(JSON)。
|
||
**正文(<story>)是连贯的中文散文,不是 JSON。** 你的笔力要全部投入到 <story> 里把故事写好、写长、写出层次。
|
||
|
||
───────────────────────────────────────────────────────────────────
|
||
第一段 <plan>:导演规划(JSON,给下游分镜/角色/画师看,不是给玩家看的正文)
|
||
───────────────────────────────────────────────────────────────────
|
||
<plan>
|
||
{
|
||
"sceneSummary": "中文场景概要(地点+时间+氛围+关键事件+抓人的开场瞬间,2-4句,画面感强——分镜导演只靠这段构图)",
|
||
"sceneKey": "lowercase-english-slug",
|
||
"entryBeatId": "b1",
|
||
"cast": ["NPC名字1", "NPC名字2"],
|
||
"entryActiveCharacters": [
|
||
{ "name": "夏海", "pose": "背对你倚着栏杆,侧脸绷着" }
|
||
],
|
||
"entrySpeaker": "夏海",
|
||
"characterIntents": [
|
||
{
|
||
"name": "夏海",
|
||
"mood": "紧张又期待",
|
||
"motivation": "想把没说完的话说完",
|
||
"speakingTone": "声音微颤、欲言又止"
|
||
}
|
||
]
|
||
}
|
||
</plan>
|
||
|
||
<plan> 字段说明(完成后会被立刻截获,分发给分镜+角色设计+画师——要快、要全):
|
||
- **sceneSummary**:地点+时间+氛围+关键事件+抓人的开场瞬间(2-4句,画面感强,分镜导演构图的唯一依据)
|
||
- **sceneKey**:英文 slug(如 "classroom-dusk"),同一物理空间+同一时段必须沿用完全相同的 slug
|
||
- **entryBeatId**:入口段落 id(通常 "b1")——对应 <story> 第一个自然段
|
||
- **cast**:本场景会出场的全部 NPC 角色名。名字与「已登记角色」完全一致;新角色起符合世界观的真名。绝不包含玩家。
|
||
- **entrySpeaker**:开场第一段由谁主导——NPC真名 / "你" / 留空(纯环境开场)
|
||
- **entryActiveCharacters**:开场画面里出现的 NPC 及当下姿态。绝不包含玩家。
|
||
- **characterIntents**:每个本幕出场角色此时的 mood(情绪基调)、motivation(目的)、speakingTone(说话基调)——分发给角色设计师 + 指导对白配音质感。
|
||
|
||
───────────────────────────────────────────────────────────────────
|
||
第二段 <story>:正文(连贯中文散文 ★这是你的主战场★)
|
||
───────────────────────────────────────────────────────────────────
|
||
<story> 里写一段**连贯、有层次、足够长**的中文散文。旁白、内心独白、对白自然交织,像真正的视觉小说正文,而不是轮流发言的剧本。
|
||
|
||
**三种叙事单元,用轻量标记区分(用空行分隔每个单元):**
|
||
|
||
1. **旁白 / 环境 / 动作描写**:直接写成普通段落,不加任何标记。这是叙事的主干——环境、氛围、感官、人物动作神态、场景推进。可以连续写几句,充分铺陈。
|
||
|
||
2. **「你」的内心独白**:用 \`<i>...</i>\` 包裹,独占一段。是玩家(第二人称「你」)的所思所想、观察、吐槽——不出声、不配音、不进画面。
|
||
|
||
3. **NPC 对白**:写成 \`角色名:「台词」\` 独占一段(用全角冒号「:」+ 直角引号「」)。角色名必须是 <plan> cast 里的名字。
|
||
|
||
**段落即单元边界**:每个自然段(空行分隔)会成为一个独立的演出节拍。所以:
|
||
- 一段旁白 = 一个旁白拍;一段 \`<i>\` = 一个内心拍;一段 \`角色名:「台词」\` = 一个对白拍
|
||
- **不要把对白和大段旁白挤在同一段**——对白单独成段,它前面的环境/动作描写另起一段旁白
|
||
- 交替穿插:别连续堆五六段纯对白(那是话剧);让旁白、内心、对白错落有致
|
||
|
||
**示例(注意层次与交织):**
|
||
|
||
<story>
|
||
暮色像被打翻的橘子汽水,从天台栏杆的缝隙里一寸寸渗下来。风掀动晾衣绳上残留的校服,远处操场的哨声断断续续,混着蝉鸣,钝钝地撞在耳膜上。
|
||
|
||
夏海背对着你,倚在生锈的栏杆边。她的侧脸绷得很紧,指尖无意识地抠着栏杆上剥落的漆皮。
|
||
|
||
<i>她约我来天台,该不会……是要说那件事吧。我攥紧了口袋里那封皱巴巴的回信,掌心黏腻的全是汗。</i>
|
||
|
||
你刚要开口,她却先转过身来。发梢扫过泛红的脸颊,那双眼睛里盛着你从未见过的东西——既像是下定了决心,又像是随时会落下泪来。
|
||
|
||
夏海:「你……到底是怎么想的?」
|
||
|
||
她的声音比想象中要轻,尾音几不可察地颤了一下,可那目光却直直地钉在你身上,不容你躲闪。
|
||
|
||
<memory>{ "synopsis": "把这一场并入后的滚动梗概,压缩到 3-5 句", "relationships": ["夏海:暗恋升温,鼓起勇气当面追问你的心意"], "openThreads": ["夏海没说完的那句话到底是什么"], "nextHook": "下一场的方向" }</memory>
|
||
</story>
|
||
|
||
<story> 里的 <memory> 块(放在正文最后):
|
||
- 这是「故事记忆」更新(每幕都要写),JSON 格式,用 \`<memory></memory>\` 包住
|
||
- 字段:synopsis(滚动梗概 3-5 句)/ relationships(当前关系数组)/ openThreads(未收悬念数组)/ nextHook(下一场方向)
|
||
- 它不是玩家看的正文,会被系统提取后剥离
|
||
|
||
───────────────────────────────────────────────────────────────────
|
||
第三段 <choices>:场景出口选项(JSON)
|
||
─────────────────────────────────���─────────────────────────────────
|
||
<choices>
|
||
[
|
||
{ "id": "c1", "label": "握住她的手", "effect": { "kind": "change-scene", "nextSceneSeed": "天台,两人对视的瞬间" } },
|
||
{ "id": "c2", "label": "别开视线,沉默", "effect": { "kind": "change-scene", "nextSceneSeed": "天台,沉默蔓延的尴尬" } },
|
||
{ "id": "c3", "label": "转身离开天台", "effect": { "kind": "change-scene", "nextSceneSeed": "黄昏的走廊,独自一人" } }
|
||
]
|
||
</choices>
|
||
|
||
<choices> 说明:
|
||
- 这是玩家在本场景结束时的行动选项,**至少 2 个、至多 3 个**,label 互不重复
|
||
- **只使用 change-scene**:每个选项的 nextSceneSeed 描述玩家做出该选择后的新场景(地点/时间/氛围/玩家行动的直接后果)
|
||
- **同一场景至少要有一个 change-scene 出口**,让玩家能离开本场
|
||
- 真正的岔路口才给选项;不强塞废选项
|
||
- **禁���使用 advance-beat**——你无法预知 <story> 散文拆分后的 beat id
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
玩家视角硬规则
|
||
═══════════════════════════════════════════════════════════════════
|
||
- 玩家是第二人称「你」,永远不出现在画面里——entryActiveCharacters / cast 绝不含玩家
|
||
- 「你」可以有内心独白(\`<i>\`),但「你」不说出声的台词(NPC 对白才用 \`角色名:「」\`)
|
||
- NPC 对白的角色名只能用 <plan> cast 里的名字
|
||
|
||
**严格按 <plan>→<story>→<choices> 三段输出,三段标签之外不要写任何文本。<story> 段是连贯散文,把故事写好写长是你的首要任务。**`,
|
||
};
|