Files
infiplot-web/lib/engine/prompts/segments/writer/format.ts
T
Zonghao Yuan 0e4c2ebef4 feat(engine): merge cloudflare-migration — paradigm D engine, BYOK proxy, story persistence (#95)
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>
2026-06-18 18:05:38 +08:00

120 lines
8.7 KiB
TypeScript
Raw 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.
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> 段是连贯散文,把故事写好写长是你的首要任务。**`,
};