diff --git a/docs/briefs/artifact-card-coverage.md b/docs/briefs/artifact-card-coverage.md new file mode 100644 index 0000000..b302768 --- /dev/null +++ b/docs/briefs/artifact-card-coverage.md @@ -0,0 +1,24 @@ +# 创作产物 · 前端美化卡覆盖(快查) + +> 随 `web/agent-ui.js` 的 `renderSpecialtyBodyHtml` / `renderKnownArtifactHtml` 维护。 + +| 产物/技能 | 美化程度 | 渲染入口 | +|-----------|----------|----------| +| 美学纲领与交互范式 | **专用 mosaic**(设定逻辑 / 交互范式 / 美学纲领) | `renderSpecialtyBodyHtml` → aesthetics | +| 实现机制 | **专用卡**(支撑点) | `renderMechanismBodyHtml` | +| 舞台骨架 | **专用卡** | `renderWorldBlueprintBodyHtml` | +| 生成规则 | **专用卡** | `renderGenerationRulesBodyHtml` | +| 具体实例 | 通用 fragment / 结构化 | context-fragment 外壳 | +| 叙事指南与故事推进 | **专用 mosaic**(风格与写法 \| 推进与决策) | `renderNarrativeBodyHtml` | +| 变量设计 | **结构化卡**(真值 / side_effects) | `renderVariableDesignHtml` | +| 变量控制上下文 | 通用 fragment | context-fragment | +| 正文组成 | 通用 fragment + 游玩期 **present 壳** | fragment;游玩 `present-shells.js` | +| 设计监控栏 | 通用 fragment | context-fragment | +| 游玩拓扑 | **槽位芯片卡** | `renderPlaySlotsHtml` | +| 上下文投影排序 | **排序卡** | `renderContextOrderHtml` | +| 裁决包 settlement.v1 | **分节卡** | `renderSettlementSectionsHtml` | +| 开场白与开场变量 | 通用 fragment | context-fragment | +| 拓扑图谱 | (骨架未写) | — | + +**说明:** 「通用 fragment」= 公共头(brief/mount/自评)+ 正文结构化树,已比生 JSON 友好;「专用」= 按技能排版的 mosaic/卡片。 +游玩美化面板另见 `docs/play-presentation-shells.md`(与创作验收卡不同层)。 diff --git a/docs/briefs/prompt-revision-inventory.md b/docs/briefs/prompt-revision-inventory.md index d1382a4..a59acb7 100644 --- a/docs/briefs/prompt-revision-inventory.md +++ b/docs/briefs/prompt-revision-inventory.md @@ -27,13 +27,13 @@ | `aesthetics-interaction` | `feeds: design_only`(再压缩进常驻);呈现要点可摘要给 narrator | | `mechanism` | **已改** output→`context-fragment.v1`;`feeds: gm` | | `world-blueprint` | **已改**→「舞台骨架」+ `context-fragment.v1`;`feeds: gm` | -| `generation-rules` / `concrete-instances` | **已改**→`context-fragment.v1`;`feeds: gm`;宽原则+严外壳 | -| `narrative` | **已改**→「叙事指南与故事推进」;遣词/笔墨/禁忌+推进;`feeds: narrator` | -| `variable-design` | **已写**;核对 output 与 `tables.side_effects`、勿暗示「变量管理 worker」 | -| `variable-context` | **已写**;核对挂载对象用「主世界层/叙事转述」而非乱拆 | -| `reply-format` / `status-bar` | **已改**→正文组成 + 设计监控栏;`context-fragment.v1`;非程序报文 | -| `topology` | 骨架;若固定槽后可能降级为可选或删 | -| `worker-templates/*.yaml` | **程序已改** gm/narrator;`role-decide` 可再收紧「只出建议」 | +| `generation-rules` / `concrete-instances` | **已改** fragment;生成规则 **mount/feeds 含 auditor(合同级)** | +| `narrative` | **已改**:一步两块(风格与写法|推进与决策)+ 双挂;专用 mosaic | +| `variable-design` | **已写** | +| `variable-context` | **已改**:mount 含 auditor + 旁观汇总 | +| `reply-format` / `status-bar` | 已有正文;见 artifact-card-coverage | +| `topology` | 骨架;等你定「小循环」选项后再写 | +| `worker-templates/*.yaml` | gm/narrator/auditor 已齐 | ## 优先级 P2(文档口径) diff --git a/docs/context-fragment-design.md b/docs/context-fragment-design.md index 1b95e47..11ed848 100644 --- a/docs/context-fragment-design.md +++ b/docs/context-fragment-design.md @@ -46,11 +46,12 @@ 舞台骨架正文:尺度 / 基底与变造 / **社会结构** / **世界状况** / 关键舞台区 / 未展开范围(见 `world-blueprint`)。 生成规则正文:必要性判断 + `rules[]`(生成与描写 + **单层**产物格式 + 可选多池绑顶层字段);自评=必要性/属性妥当/格式准确。 美学纲领自评仍为:交互范式 / 美学纲领 / 整体协调(勿与生成规则三维混用)。 -叙事指南与故事推进正文:纲领 / 风格与遣词(+示范) / 笔墨焦点 / 禁忌与不偏好 / 情境备用 / 推进与决策 / 内容与表达;自评=写法可执行/推进可执行/与美学对齐;artifact=`设计.叙事指南与故事推进`。 +叙事指南与故事推进正文:一份全文(纲领/遣词/笔墨/禁忌/推进等);**整份**双挂转述与主世界层,不为省 token 拆两套投影。artifact=`设计.叙事指南与故事推进`。 正文组成:**基于固定呈现壳适配微调**(选 `shell_id` + 微调轴;块只挂壳已有区域);artifact=`设计.正文组成`。见 `docs/play-presentation-shells.md`。 设计监控栏:只监控会变字段(可含攻略目标/场景交互物);artifact=`设计.监控栏`(旧称状态栏)。 变量设计:真值 + **维护语句** + **Data映射索引**(指向具体实例)+ side_effects。 -变量控制上下文:各槽视野 + **旁观汇总**(当前档摘要)。 +变量控制上下文:各槽视野 + **旁观汇总**;mount 含主世界层与旁观维护。 +生成规则:合同挂主世界层 + 旁观(旁观读 rule_id/格式/必要性,描写长文可 summary)。 开场白与开场变量:开场正文(守正文组成)+ 开场初值;可经 opening-generator 落库。 diff --git a/docs/play-dm-auditor.md b/docs/play-dm-auditor.md new file mode 100644 index 0000000..aacd4f3 --- /dev/null +++ b/docs/play-dm-auditor.md @@ -0,0 +1,68 @@ +# 主世界层 与 旁观维护:怎么配合 + +> 用白话说明「DM」和「副 LLM(旁观)」谁先谁后、谁干什么。 +> 实现上当前管线是:`旁观 →(可选角色视角)→ 主世界层 → 转述`。 + +## 1. 先说清楚两个角色 + +| 称呼 | 槽 | 干什么 | 不干什么 | +|------|-----|--------|----------| +| **主世界层(DM)** | gm | 读设定与当前状态,对本轮玩家行动做**裁决**;可提议改变量 | 不负责长文文风;不靠「再想一遍」修表 | +| **旁观维护(副 LLM)** | auditor | **查漏补缺**:表缺字段、该不该按生成规则补一条实例;多数轮**空操作** | 不当作者写正文;不替代裁决 | + +转述只吃裁决包写用户可见文字——与「俩人怎么配合」无关。 + +## 2. 你提到的两种做法 + +**做法甲:DM 先提要求 → 副 LLM 交作业 → DM 再过一遍** + +```text +DM:「按怪物规则补 1 个」→ 旁观生成 → DM 再裁决一次 +``` + +- 优点:DM 始终握笔。 +- 缺点:同一回合容易 **3 次模型调用**,贵、慢;旁观一不小心变成「小作者」。 + +**做法乙:DM 先交齐裁决 → 把结果和要求交给副 LLM → 副 LLM 用工具改表** + +```text +DM 出裁决包(含变量变更)→ 旁观只动表/触发生成 → 程序合并,不再开一轮 DM +``` + +- 优点:叙事权威仍在 DM;旁观像**库管/校对**,适合工具改表。 +- 缺点:若裁决里已经写错事实,旁观「修表」救不了叙事,只能修状态。 + +## 3. 本仓库采用的(推荐) + +**既不是甲也不是乙的「整段返工」,而是:旁观轻扫 → DM 拍板 →(程序合并旁观的改表)→ 转述。** + +```text +每轮: + 1. 旁观:看规则合同 + 当前表 + 玩家输入 + → 多数轮:什么都不改(空 maintain) + → 少数轮:写 table_ops / need_generate(补字段或点名 rule_id) + 2. 主世界层:出 settlement 裁决包(可含变量变更) + 3. 程序:合并旁观改表 + DM 变量变更(边沿副作用照常) + 4. 转述:只根据裁决包写正文 +``` + +要点: + +1. **不要**默认「DM 写完再让旁观润色全文」(贵,且旁观会抢笔)。 +2. **不要**默认「旁观先写一大段设定再交给 DM」(旁观变作者)。 +3. **生成规则给旁观**:只要**合同**(有哪些 rule、格式、何时该生成),不要整本描写范文。 +4. 真要「按规则生成实例」:旁观点 `need_generate.rule_id`,由**程序/既定生成通路**落表,而不是旁观写小说。 + +若以后要改成「乙」:把旁观挪到 **DM 之后**、只保留工具改表、禁止旁观输出正文——可以再开一刀改调度序;当前以已落地的「旁观在前、轻维护」为准。 + +## 4. 和「生成规则要不要给副 LLM」的关系 + +问题本质不是「塞不塞那一份文档」,而是: + +- 旁观要不要能说「该按某条规则补东西」?→ **要**,就得看见规则目录/格式。 +- 旁观要不要会写角色描写?→ **不要**,所以描写长文不必进旁观上下文。 + +## 5. 一句话 + +**旁观看门与补表;DM 断事;转述说话。** +交互上优先「轻维护 + 一次裁决」,不优先「来回多轮 LLM 传话」。 diff --git a/docs/play-presentation-shells.md b/docs/play-presentation-shells.md index e9942e4..4d34b74 100644 --- a/docs/play-presentation-shells.md +++ b/docs/play-presentation-shells.md @@ -1,143 +1,122 @@ -# 游玩呈现壳(P1)· 可微调前端模板 +# 游玩呈现壳 · 可微调前端模板 > **阶段**:P1 = 定类与契约;**P2** = 程序/样式适配与灌数。 -> 上游:游玩管线 `auditor → gm → narrator`(已落地)。 +> 上游:游玩管线 `auditor → gm → narrator`。 > 创作挂钩:`设计.正文组成` / `设计.监控栏`;终稿 tag 通常 `输出.用户展示`。 -> **创作定位:基于固定壳做适配微调,不是从零设计前端。** +> **创作定位:基于固定壳做适配微调,不是从零设计前端。** +> 预览:`/shells.html` ## 1. 创作怎么做(权威口径) ```text -固定壳(本仓库预置,少数几种) +固定壳(本仓库预置,七种) → 创作期:选一个 shell_id → 只改允许的微调轴(字段、显示名、开关、tone_chrome…) - → 禁止改分区骨架、禁止发明第五种布局 + → 禁止改分区骨架、禁止发明第八种布局 游玩期:往壳的区域里灌数(present.v1 / 投影 / settlement) ``` 「正文组成」= **壳适配单**,不是 UI 创意稿。 用户看见的差异应来自**同壳不同绑定**,而不是每局一套新布局。 -## 2. 问题(若不用壳) +## 2. 信息层级与长文容量 -创作期若只写散文式「想要好看界面」,模型难稳定产出可跑界面,也会和程序渲染脱节。 -壳把布局钉死,创作只填内容契约。 +所有壳共用同一优先级——**改壳只是改谁占主面积**: -```text -创作:选壳 + 微调绑定(字段/文案/开关) -游玩:narrator / 程序灌入对应区域 -伪 0 层:壳自带的 HUD 区,不是新 LLM -``` +| 优先级 | 区域 | 视觉待遇 | +|--------|------|----------| +| **P0** | `body` | 阅读主轴;**随字数撑高**(单轮按 600~3000 字设计),由页面/消息区滚动 | +| **P1** | 建议行动 | 要决策时醒目;`choice_dock` 坞 **sticky 贴底** | +| **P2** | `monitor` | 芯片密排,高度固定矮 | +| **P3** | `header` / `footer` / `aside` | 辅助;双栏 **aside sticky**,长文仍可对照 | -## 3. 四大常用壳(微调模板,非成品皮肤) +**不要**把正文塞进固定高度小框再内部硬滚(除非外层消息流另有约束)。 +选壳时问:用户 80% 时间是在「读长场面」还是「做选择」还是「对线索」? -| id | 中文 | 主场景 | 默认分区 | 默认可调 | -|----|------|--------|----------|----------| -| `prose` | 纯散文流 | 只要沉浸正文、不要 HUD | 单流 `body` | 文末可选折叠备注 | -| `chat_monitor` | 对话 + 顶栏监控 | 网恋 / 日常扮演 / 轻 AIRP | `monitor` 顶栏 + `body` 主聊 | 监控字段;是否显示建议行动 | -| `turn_panel` | 回合面板 | 强机制、资源/检定、场景交互 | `monitor` + `body` + `footer`(建议行动/检定结果) | 栏位;footer 开闭;机遇区 | -| `chapter_reader` | 章节阅读 | 长文 / 爽文 / 助手分段 | `header`(章题/卷)+ `body` + 可选 `aside`(进度) | 章标题样式;是否显示大纲进度 | +## 3. 七种常用壳 -旧规格未写 `shell_id` → 运行时视为 `prose`(与「可不排正文组成」一致)。 +| id | 中文 | 主面积给谁 | 主场景 | +|----|------|------------|--------| +| `prose` | 纯散文 | 正文 ≈ 全屏 | 只要沉浸、不要 HUD | +| `chat_monitor` | 对话 + 监控 | 正文(聊天气泡区) | 网恋 / 日常 / 轻 AIRP | +| `spotlight` | 场面主视 | 正文极大(追光) | 电影感一幕、少机制 | +| `turn_panel` | 回合面板 | 中栏场面 + 侧栏交互 | 资源/检定/跑团 | +| `split_board` | 双栏看板 | 左正文 · 右线索 | 调查、推理 | +| `choice_dock` | 选择坞 | 正文 + **底部大行动坞** | 选项驱动 / AVG 关口 | +| `chapter_reader` | 章节阅读 | 阅读柱 + 窄进度侧栏 | 长文 / 爽文 | -### 3.1 各壳固定槽位(程序认这些区域 id;创作不得增删分区) +旧规格未写 `shell_id` → 运行时视为 `prose`。 -| 区域 id | prose | chat_monitor | turn_panel | chapter_reader | -|---------|-------|--------------|------------|----------------| -| `monitor` | — | 顶栏 | 顶/侧栏 | — 或弱化进 header | -| `header` | — | 可选会话抬头 | 可选回合号 | **章题/卷信息** | -| `body` | **主文** | **主聊/场面** | **场面叙述** | **章正文** | -| `footer` | 可选 | 可选短吐槽 | **建议行动 / 机遇摘要** | 可选作者注 | -| `aside` | — | — | 可选场景交互物 | **进度/卷纲摘要** | -| `hidden` | 变量段 | 变量段 | 变量段 | 变量段 | +### 3.1 各壳固定槽位 -隐藏段从不进用户主视线(默认折叠/调试可见)。 +| 区域 id | prose | chat_monitor | spotlight | turn_panel | split_board | choice_dock | chapter_reader | +|---------|-------|--------------|-----------|------------|-------------|-------------|----------------| +| `monitor` | — | 顶栏芯片 | 顶栏芯片 | 顶栏 | — | 可选顶栏 | — | +| `header` | — | — | — | 回合号 | 场景抬头 | — | **章题** | +| `body` | **主** | **主聊** | **主舞台** | **场面** | **主读** | **局面** | **章正文** | +| `footer` | 可选弱 | 可选 | 可选 | 检定摘要 | 可选 | 可选 | 作者注 | +| `aside` | — | — | — | 交互物 | **线索栏** | — | **进度** | +| `hidden` | 变量段 | 变量段 | 变量段 | 变量段 | 变量段 | 变量段 | 变量段 | -### 3.2 微调轴(创作只许改这些;布局骨架只读) +### 3.2 微调轴 -每壳允许覆盖、禁止改布局骨架: +| 轴 | 说明 | +|----|------| +| `monitor_fields` | 监控栏字段子集 | +| `block_labels` | 可见块显示名(如 body→「短信」) | +| `show_suggested_actions` | 是否渲染建议行动;`turn_panel` / `choice_dock` 默认开 | +| `tone_chrome` | `default` / `messenger` / `book` / `terminal` | +| `empty_states` | 空态文案 | -| 轴 | 说明 | 例 | -|----|------|-----| -| `monitor_fields` | 监控栏字段子集(来自设计.监控栏) | 好感、体力、今日话题 | -| `block_labels` | 可见块显示名 | 「场面」→「短信记录」 | -| `show_suggested_actions` | 是否渲染 settlement.suggested_actions | turn_panel 默认开 | -| `tone_chrome` | 极轻装饰关键词(非完整 CSS) | `messenger` / `terminal` / `book` | -| `empty_states` | 监控/aside 空态文案 | 「尚无攻略目标」 | - -**禁止**创作期输出完整 CSS/工程;`tone_chrome` 只是壳内预设皮肤键。 -**禁止**增删壳的分区、禁止自造第五种 `shell_id`(要新壳走仓库改模板,不走单局创作)。 +**禁止**创作期输出完整 CSS;**禁止**自造 `shell_id`(要新壳改仓库模板)。 ## 4. 与创作产物的映射 ```text 设计.正文组成 - 正文.呈现壳.shell_id → 必须是四大之一(选壳) - 正文.呈现壳.微调 → 只填允许轴(适配) - 正文.可见块[] → 挂到壳已有区域,不新开区域 - 正文.监控栏锚点 → chat_monitor / turn_panel 的字段意图 - 正文.隐藏段 → 区域 hidden + 正文.呈现壳.shell_id → 七选一 + 正文.呈现壳.微调 → 只填允许轴 + 正文.可见块[] → 挂到壳已有区域 -设计.监控栏 - → 灌入该壳 monitor 区的字段表(仍是微调,不是新布局) - -运行.本轮.裁决 (settlement.v1) - → body 事实来源;suggested_actions → footer(若壳开启) - -输出.用户展示 - → 优先 present.v1 灌进壳区域;纯 Markdown → body +设计.监控栏 → monitor 字段表 +运行.本轮.裁决 → body 事实;suggested_actions → 行动区 +输出.用户展示 → present.v1 或纯 Markdown(→ body) ``` -## 5. 游玩灌数契约(P2 实现目标) - -推荐 `输出.用户展示` 在分块壳下为: +## 5. 游玩灌数契约 ```json { "schema": "present.v1", - "shell": "chat_monitor", + "shell": "spotlight", "blocks": { - "monitor": { "好感": "暖络", "今日": "下雨" }, - "body": "……用户可读正文……", - "footer": "" + "monitor": { "地点": "天桥下", "张力": "紧" }, + "body": "……用户可读正文……" }, - "meta": { - "suggested_actions": ["回一句关心", "约周末"], - "hide": ["<<>>…<<>>"] - } + "meta": { "suggested_actions": ["先开口", "递伞"] } } ``` -- `shell` 可省略 → 用规格里正文组成的 `shell_id`。 -- 若整段非 JSON:壳把全文写入 `body`,其它区用变量投影/监控栏程序填(P2)。 +- `monitor` 宜为 **短键值对象**(程序渲染成芯片),不要长文。 - `prose`:只需 `body` 字符串或 Markdown。 -## 6. 创作期只做适配(正文组成) +## 6. 创作期只做适配 -1. **选壳**(四选一):无 HUD → `prose`;对话+状态 → `chat_monitor`;机制重 → `turn_panel`;写章 → `chapter_reader`。 -2. **微调**:监控字段、块显示名、建议行动开关、`tone_chrome`、空态文案。 -3. **不要**:重画分区、发明新区域 id、写 CSS、谈「另一种全新界面」。 -4. `版式隐喻` 仅供人读;**程序只认 `shell_id` + 微调轴**。 +1. **选壳**:按「主面积给谁」(§2)七选一。 +2. **微调**:字段、显示名、行动开关、tone、空态。 +3. **不要**:重画分区、发明区域 id、写 CSS。 ## 7. P2 适配清单 | 序 | 项 | 状态 | |----|----|------| -| 1 | `web/` 四壳最小渲染器 | **已接** `web/present-shells.js` + CSS | -| 2 | 读 `设计.正文组成` 微调进 SessionView | **已接** `presentationTweaks` | -| 3 | 解析 `present.v1` / 纯文本回退 | **已接** `src/skills/present-packet.ts` | -| 4 | narrator 模板按壳输出 | **已接** `worker-templates/narrator.yaml` | -| 5 | 正文组成只产出壳适配单 | **已接**(P1 prompt) | - -仍可加深:监控栏字段程序灌 monitor、tone 皮肤精修、章阅读 aside 自动进度。 - -## 8. 与路线图 - -| 本文 | `px-roadmap` | -|------|----------------| -| P1 定壳 | 创作 = 选壳适配 | -| P2 灌数渲染 | 挂 **PX2 阅读面** | +| 1 | `web/` 壳渲染器 + 层级样式 | **已接** `present-shells.js` + `styles.css` | +| 1b | 壳预览页 | **已接** `/shells.html` | +| 2 | 正文组成微调进 SessionView | **已接** | +| 3 | present.v1 解析 | **已接** `present-packet.ts` | +| 4 | narrator 按壳输出 | **已接** | +| 5 | 正文组成产出壳适配单 | **已接** | --- -修订记录:2026-08-11 · P1;口径明确为创作基于壳适配微调。 +修订:2026-08-13 · 七壳;明确信息层级与主面积。 diff --git a/docs/world-simulator-modules.md b/docs/world-simulator-modules.md index 2ecef20..8c2a68b 100644 --- a/docs/world-simulator-modules.md +++ b/docs/world-simulator-modules.md @@ -5,7 +5,8 @@ > **标准范例**:`modules/aesthetics-interaction/prompt.md`。 > **真值 / Data / Progressive(变量与门控)**:**`docs/progressive-data-design.md`**。 > **上下文片段 / 槽位 / 投影排序**:**`docs/context-fragment-design.md`**。 -> **游玩呈现壳(P1)**:**`docs/play-presentation-shells.md`**。 +> **游玩呈现壳**:**`docs/play-presentation-shells.md`**(预览页 `/shells.html`)。 +> **主世界层↔旁观怎么配合**:**`docs/play-dm-auditor.md`**。 > **提示词待改清单**:**`docs/briefs/prompt-revision-inventory.md`**。 > **给外部 AI 的完整泛用规范**:**`docs/briefs/capability-authoring-brief.md`**(含 `context-fragment.v1`)。 @@ -128,14 +129,14 @@ recipes/world-simulator|expand-assistant/recipe.yaml | 美学纲领与交互范式 | `aesthetics-interaction` | **范例已写**;前端 mosaic 视图 | | 实现机制 | `mechanism` | **已写**:`context-fragment.v1`(支撑点正文 + 自评 + 追问);`feeds: gm`;**专用卡已接** | | 舞台骨架 | `world-blueprint` | **已写**:`context-fragment.v1`;社会结构 + 世界状况;**专用卡已接** | -| 生成规则 | `generation-rules` | **已写**:`context-fragment.v1`;宽原则 + 严合同键;可反复;`params.target`;**专用卡已接** | +| 生成规则 | `generation-rules` | **已写**:`context-fragment.v1`;宽原则 + 严合同键;可反复;`params.target`;挂 gm+auditor;**专用卡已接** | | 具体实例 | `concrete-instances` | **已写**:`context-fragment.v1`;只按规则执行;可反复;`params.rule_id` | -| 叙事指南与故事推进 | `narrative` | **已写**:遣词/笔墨焦点/禁忌与不偏好 + 推进;挂转述 | -| 拓扑图谱 | `topology` | 骨架,待细写 | -| 设计监控栏 | `status-bar` | **已写**:只盯会变信息;旧称状态栏 | -| 正文组成 | `reply-format` | **已写**:壳适配微调;P2 present 渲染已接;见 `docs/play-presentation-shells.md` | -| 变量设计与更新规则 | `variable-design` | **已写**:真值+维护语句+Data映射索引+side_effects | -| 变量控制上下文 | `variable-context` | **已写**:挂载+旁观汇总 | +| 叙事指南与故事推进 | `narrative` | **已写**:一份全文双挂;**不**为省 token 拆投影;结构化卡 | +| 拓扑图谱 | `topology` | 骨架,待细写(拟改为选项小循环或降级) | +| 设计监控栏 | `status-bar` | **已写**:只盯会变信息;通用 fragment 卡 | +| 正文组成 | `reply-format` | **已写**:壳适配;P2 present 渲染已接 | +| 变量设计与更新规则 | `variable-design` | **已写**;专用/结构化卡 | +| 变量控制上下文 | `variable-context` | **已写**:旁观汇总;mount 含 auditor;通用 fragment 卡 | | 开场白与开场变量 | `opening-setup` | **已写**:开场守版式+同真相初值 | 共用收成(池内保留,按需): diff --git a/skills/dialogue/world-simulator/modules/catalog.yaml b/skills/dialogue/world-simulator/modules/catalog.yaml index cada232..c8453d6 100644 --- a/skills/dialogue/world-simulator/modules/catalog.yaml +++ b/skills/dialogue/world-simulator/modules/catalog.yaml @@ -79,8 +79,8 @@ modules: - id: narrative name: 叙事指南与故事推进 declaration: >- - 钉转述写法与故事推进:纲领、遣词造句、笔墨焦点、禁忌/不偏好、示范与备用、 - 推进决策(可含变量钩子);美学写感觉,本步写怎么写、往哪推 + 一次钉清写法与推进(遣词/笔墨/禁忌/示范 + 故事如何往前); + 整份双挂转述与主世界层,不为省 token 拆投影 artifact: 设计.叙事指南与故事推进 # —— 机制与数据 —— diff --git a/skills/dialogue/world-simulator/modules/generation-rules/prompt.md b/skills/dialogue/world-simulator/modules/generation-rules/prompt.md index 778dd47..5099ad2 100644 --- a/skills/dialogue/world-simulator/modules/generation-rules/prompt.md +++ b/skills/dialogue/world-simulator/modules/generation-rules/prompt.md @@ -29,7 +29,8 @@ boundary: | 具体实例:按规则填最终 records;本步不写最终实例(池条目≠实例)。 舞台骨架 / 实现机制:可引用,不重写百科、不重做移除检验。 变量*:游玩期状态更新——本步只约束生成时的类型与合法初值范围。 -feeds: gm + 旁观维护:可读「合同」——rule_id、产物格式、何时可 need_generate;不依赖整份描写长文。 +feeds: gm,auditor ``` ## opening @@ -105,7 +106,7 @@ summary:建立 → `生成规则 · {对象} · {仅动态|仅预生成|预生 "schema": "context-fragment.v1", "技能": "生成规则", "brief": "一句话:对象族 + 建立规则|无需专门规则 + 生命周期(若建立)+ 有池|无池", - "mount": ["world-simulator"], + "mount": ["world-simulator", "auditor"], "稳变": "stable", "正文": { "本步参数": { @@ -235,7 +236,7 @@ summary:建立 → `生成规则 · {对象} · {仅动态|仅预生成|预生 5. **产物格式强制单层**:`schema` 的 `type` 不得为 `object`;`array` 的 `items` 不得为 object。禁止 `外貌.发色` 这类嵌套路径。 6. 结论为「无需专门规则」时:`rules` 必须 `[]`。上下文策略布尔须与生命周期一致。 7. 池条目≠最终实例;禁止输出具体实例 `records`、变量更新规则、执行单元列表。 -8. `mount` 默认主世界层;**禁止**写 `order`。summary 见 task。 +8. `mount` 默认主世界层 + 旁观维护(旁观只需合同键:rule_id/格式/必要性;描写长文可在投影时 summary);**禁止**写 `order`。summary 见 task。 ## checklist diff --git a/skills/dialogue/world-simulator/modules/narrative/prompt.md b/skills/dialogue/world-simulator/modules/narrative/prompt.md index ef70f98..cea98fa 100644 --- a/skills/dialogue/world-simulator/modules/narrative/prompt.md +++ b/skills/dialogue/world-simulator/modules/narrative/prompt.md @@ -1,9 +1,9 @@ # 叙事指南与故事推进 > 技能文档。程序只切割下方 **fence 块**;`##` 标题仅供人读。 -> 产物外壳:`docs/context-fragment-design.md`;对照:`aesthetics-interaction`(感觉)↔ 本技(怎么写 / 往哪推)。 -> 默认服务**叙事转述**;「推进与决策」同时给主世界层用。 -> 旧称:叙事指南(流程 name / artifact 兼容见 creation-flow)。 +> 产物外壳:`docs/context-fragment-design.md`;对照美学:感觉 → 本步「怎么写、故事怎么往前」。 +> **一份产物、一次询问**;转述与主世界层都挂同一份(不必为省 token 拆成两套投影)。 +> 旧称:叙事指南。 ## meta @@ -12,41 +12,27 @@ name: 叙事指南与故事推进 id: narrative artifact: 设计.叙事指南与故事推进 declaration: > - 钉转述「怎么写」与主世界「怎么推」:叙事纲领、遣词造句与笔墨焦点、绝对禁忌/不偏好、 - 示范与情境备用,以及推进/决策(可含变量门控);不重做美学感觉问卷 + 一次钉清写法与推进:遣词、笔墨、禁忌、示范,以及故事如何往前(可含变量钩子); + 不重做美学感觉问卷;整份双挂转述与主世界层 when: | - 已有(或等价)美学纲领,需要把「要的感觉」落成可执行的写法与推进时; - 或转述缺遣词/示范/笔墨重心(如男频写女、女频写男、末日绝望 vs 日常小确幸); - 或缺少情境切换语料、或主世界层缺推进框架(可含好感/日期/冷却钩子)时。 + 已有美学纲领,需要把感觉落成可执行的写法与推进时。 when_not: | - 美学未钉、还在谈「要什么感觉」→ 先「美学纲领与交互范式」。 - 契约已细到可直接转述、用户明确不要本步 → 可不排。 - 用户可见块序/监控栏版式 → 「正文组成」/「设计监控栏」。 - 事件池 schema / 预生成 → 「生成规则」/「具体实例」。 - 真值与 side_effects → 「变量设计与更新规则」。 + 美学未钉 → 先「美学纲领与交互范式」。 + 版式/监控栏 → 「正文组成」/「设计监控栏」。 + 事件池 schema / side_effects → 「生成规则」/「变量设计」。 boundary: | - 本技能:叙事宪法 + 遣词造句/笔墨焦点 + 禁忌与不偏好 + 示范/备用 + 推进与决策 + 内容表达; - 产出 context-fragment.v1。美学写「感觉」,本步写「花笔墨在哪、怎么遣词、故事怎么推」。 - 美学纲领与交互范式:体验与轮转——已写清的感觉勿重问。 - 生成规则 / 变量*:合同与副作用规格——本步只给推进钩子与写法,不建表。 - 设计回复格式:块结构——本步不定。 -feeds: narrator + 本技能:一份 context-fragment,写清怎么写 + 怎么推;不必拆技能、也不必为省 token 做精细双投影。 + 美学:感觉与轮转。生成规则/变量*:合同与表。 +feeds: narrator,gm ``` ## opening ```opening -美学纲领里的「感觉」已有方向时,请谈叙事「怎么写、往哪推」(想到什么写什么): +美学里的感觉已有方向时,请谈「正文怎么写、故事怎么往前」(想到什么写什么): -1. 正文应该像什么?遣词大概什么味道? - 例:第三人称网文、偏爽文、简明带张力;句短、少文艺腔…… - -2. 笔墨该花在谁/什么上?有什么绝对不能写、或不想多写? - 例:男频爱情 → 多写女角色美丽与反应;女频相反; - 末日 → 生存绝望,禁止轻松解决一切;日常 → 小确幸,禁止突然飞来横祸…… - -3. 故事怎么往前推?要不要跟变量挂钩?有没有情境要换一套语料? - 例:好感够了才插入倒追;NSFW 改官能笔法;丛林三两句带过 vs 荒野逐步细写…… +1. 像什么文风?遣词什么味道?笔墨花在谁身上?有什么绝对不能写? +2. 故事怎么推进?要不要跟好感/进度挂钩?有没有情境要换一套写法? ``` ## task @@ -54,60 +40,32 @@ feeds: narrator ```task 你正在执行「叙事指南与故事推进」。产物必须是 **context-fragment.v1** JSON,写入「设计.叙事指南与故事推进」。 -美学钉感觉;本步钉:**怎么遣词造句、笔墨花在哪里、绝对禁忌与不偏好、示范长什么样、故事如何推进**。 +一次对话钉清写法与推进(可交叉追问),输出**一份**正文即可。 +mount:`narrator` 与 `world-simulator` 都挂同一份全文——不要为省一点点 token 拆两套裁剪方案。 执行顺序: -1. 读美学纲领(及可选机制/舞台):继承体验内核与禁忌;改写成创作导向句,不重复问卷。 -2. 写「叙事纲领」:核心定位 + 叙事使命 + 叙事追求(浓缩)。 -3. 写「风格与遣词」:叙述者人格、整体基调、**遣词造句**(用什么词/句式/节奏、避免什么)、修辞;附至少 1 段示范。 -4. 写「笔墨焦点」:观众/代入取向下应详写谁、略写谁(例:男用户向爱情线详女角色;女用户向详男角色);与体验类型对齐的场景重心。 -5. 写「禁忌与不偏好」:绝对禁忌(硬禁)与不偏好(能避则避、出现须极克制)——含题材气质禁令(末日禁轻松通关、日常禁无端灾难等)。 -6. 需要时写「情境备用」多套语料;写「推进与决策」(驱动力、速度/详略、成败、困境、节奏、可选变量钩子)。 -7. 写「内容与表达」:视角执行、场景详略、信息组织。 -8. 自评 + 追问;输出 JSON。 +1. 继承美学体验与禁忌,改写成做法向句子。 +2. 写纲领、遣词(含示范)、笔墨焦点、禁忌/不偏好;需要时写情境备用。 +3. 写推进与决策(可写「无专门推进」);变量只写钩子。 +4. 自评 + 追问。 -mount:默认挂叙事转述 + 主世界层。禁止写最终 `order`。 -summary:`叙事指南与故事推进 · …`(点题体裁/笔墨重心 + 推进有无)。 +summary:`叙事指南与故事推进 · …` ``` ## principles ```principles -1. 感觉 vs 做法 - 美学:装逼的爽感。本步:第三人称爽文、遣词短促有力、笔墨落在哪、示范段落、推进是否心想事成…… - 禁止复读体验口号。 - -2. 遣词造句必须可执行 - 不只写「要有张力」:写句长、用词场、可否网感/文言、感官词偏好、情绪直给 vs show。 - 每条倾向尽量带「做法 → 目的」。 - -3. 笔墨焦点跟体验与观众站位走 - 爱情线:男用户向通常详写女角色外貌/气质/反应;女用户向详写男角色——写清本局取向,勿默认「男女均衡」。 - 末日天灾:详写匮乏、威胁、代价与绝望感;禁止轻松解决一切、轻松段子冲淡生存压。 - 日常小确幸:详写细微暖意与节奏;禁止无铺垫的突发致命危险或崩坏(除非用户明确要)。 - 其它类型同理:问「读者眼神会停在哪」。 - -4. 绝对禁忌 ≠ 不偏好 - 绝对禁忌:出现即破坏体验,硬禁。 - 不偏好:能避则避;偶尔出现须克制、不抢主戏。 - 两者都要写,并说明与美学的关系。 - -5. 示范与情境备用 - 至少一段默认示范;NSFW/战斗/日常等用「情境备用」换遣词与禁忌增量。 - -6. 推进与写法同框,推进可轻可无 - 变量门控只写钩子;表规格交给变量/生成规则。 - -7. 继承优先;与美学硬禁忌冲突时以体验禁忌为准并点明。 +1. 感觉 vs 做法:美学写要什么爽;本步写怎么遣词、笔墨落哪、故事怎么推。 +2. 遣词与禁忌要可执行;绝对禁忌 ≠ 不偏好。 +3. 推进可轻可无;表规格交给变量/生成规则。 +4. 整份双挂即可;禁止拆成两个技能。 ``` ## probe ```probe -每轮 1~2 点。已在美学写清的感觉禁止重问。 - -优先:体裁与遣词味道;笔墨焦点(详谁/略谁);绝对禁忌 vs 不偏好;末日/日常等气质禁令;推进门控与详略成败;是否要情境备用。 -不追问:回复块排版、事件池 schema、side_effects 细节、舞台百科。 +每轮 1~2 点。已在美学写清的感觉勿重问。 +优先:体裁遣词、笔墨详谁、禁忌、推进门控。 ``` ## output @@ -116,164 +74,70 @@ summary:`叙事指南与故事推进 · …`(点题体裁/笔墨重心 + 推 { "schema": "context-fragment.v1", "技能": "叙事指南与故事推进", - "brief": "一句话:体裁遣词 + 笔墨重心 + 推进有无", + "brief": "一句话:体裁 + 笔墨重心 + 推进有无", "mount": ["narrator", "world-simulator"], "稳变": "stable", "正文": { - "依据的体验": [ - "从美学纲领忠实转化;可点名禁忌继承" - ], + "依据的体验": ["…"], "叙事纲领": { - "核心定位": "一句话:叙事本质/灵魂", - "叙事使命": "1~2 句:要让用户体验到什么", - "叙事追求": [ - "关键词 - 简短解释" - ] + "核心定位": "…", + "叙事使命": "…", + "叙事追求": ["…"] }, "风格与遣词": { - "叙述者人格": "形象化一句话:这个声音是谁", - "整体基调": "温度/节奏/质感/距离的整体描述", - "遣词造句": [ - "原则 - 具体做法(用词/句式/段落节奏) → 目的" - ], - "核心修辞": [ - "修辞名 - 示例 → 作用" - ], - "示范": [ - { - "情境": "常态/开场/高潮…", - "段落": "一段可朗读的示范(体现遣词与张力)" - } - ] + "叙述者人格": "…", + "整体基调": "…", + "遣词造句": ["原则 - 做法 → 目的"], + "示范": [{ "情境": "常态", "段落": "…" }] }, "笔墨焦点": { - "观众与代入取向": "男用户向|女用户向|中性/双关|未定;可附一句站位", - "详写优先": [ - "应花笔墨的对象或维度(例:女角色外貌与反应;生存压迫与资源匮乏;微表情与小确幸)" - ], - "可略写": [ - "可三两句带过或背景化的部分" - ], - "说明": "与体验类型如何对齐(男频/女频/末日/日常…)" + "观众与代入取向": "…", + "详写优先": ["…"], + "可略写": ["…"] }, "禁忌与不偏好": { - "绝对禁忌": [ - "禁止… → 原因(硬禁;破坏体验)" - ], - "不偏好": [ - "尽量避免… → 若出现则如何克制" - ] + "绝对禁忌": ["…"], + "不偏好": ["…"] }, - "情境备用": [ - { - "触发": "例:NSFW / 战斗 / 日常过场", - "改用风格": "例:官能小说笔法", - "遣词与禁忌增量": ["相对默认改什么"], - "笔墨增量": "该情境笔墨是否偏移;无则空字符串", - "示范": "可选示范段" - } - ], + "情境备用": [], "推进与决策": { - "有无专门推进": "有|无(沿用常识与体验即可)", - "驱动力来源": "故事主要由什么推动", - "叙事速度与详略": "何种过程可带过,何种必须细写", - "成败与难度": "心想事成 / 允许受挫 / 其它;与体验关系", - "变量门控钩子": [ - { - "变量或信号": "好感|日期|冷却|进度|seed|其它", - "条件意图": "例:好感≥某档;冷却归零;纯随机", - "插入或推进什么": "例:倒追线;诡异遭遇", - "实现指向": "投影/动态表|生成规则事件池|主世界层临场判断", - "说明": "可较长;本步不写 side_effects" - } - ], - "困境应对": [ - "困境简述 → 应对方向(关键词)" - ], - "节奏意识": [ - "情境 → 应对" - ] + "有无专门推进": "有|无", + "驱动力来源": "…", + "叙事速度与详略": "…", + "成败与难度": "…", + "变量门控钩子": [] }, "内容与表达": { - "视角执行": { - "主次原则": "主信息源占比/辅助源条件", - "切换时机": ["情境 → 应对"], - "切换方式": "如何平滑切换" - }, - "场景详略映射": [ - "场景类型: 聚焦… | 详写条件 → 原因(与笔墨焦点对齐)" - ], - "信息组织模式": [ - "模式名: 结构 → 适用情境" - ] + "视角执行": "…", + "场景详略映射": ["…"] } }, "自评": { "维度": [ - { - "名": "写法可执行", - "分数": 8, - "说明": "遣词+笔墨焦点+禁忌/不偏好+示范是否够转述落地" - }, - { - "名": "推进可执行", - "分数": 8, - "说明": "主世界层是否知道如何推进;变量钩子清楚或明确无需" - }, - { - "名": "与美学对齐", - "分数": 8, - "说明": "是否把感觉落成做法;笔墨与禁忌服务体验而非跑偏" - } + { "名": "写法可执行", "分数": 8, "说明": "…" }, + { "名": "推进可执行", "分数": 8, "说明": "…" }, + { "名": "与美学对齐", "分数": 8, "说明": "…" } ], - "薄弱点": "一句话" - }, - "追问": { - "导语": "为钉准写法与推进,还需确认:", - "题目": [ - { - "问": "…", - "建议选项": ["选项A", "选项B", "其它(请写明)"], - "示例": "可选短示范句" - } - ] + "薄弱点": "" }, + "追问": { "导语": "", "题目": [] }, "开放问题": [] } ``` -硬规则: -1. 合法 JSON;含 `schema` / `技能` / `brief` / `正文` / `自评` / `追问`。 -2. 正文键固定:依据的体验、叙事纲领、**风格与遣词**、**笔墨焦点**、**禁忌与不偏好**、情境备用、推进与决策、内容与表达。 -3. `风格与遣词.示范` 至少 1 条;`遣词造句` 不得空数组(建立本步时)。`情境备用` 可 `[]`。 -4. `绝对禁忌` 与 `不偏好` 均须出现(可短,但不可整段省略);须能对上体验类型(如末日/日常/频向)。 -5. 自评:写法可执行 / 推进可执行 / 与美学对齐。禁止 `order`。 -6. 不要输出事件池 schema、side_effects、回复块结构、执行单元列表。 -7. summary:`叙事指南与故事推进 · {brief 缩略}`。 -8. 兼容:若下游仍读旧 tag「设计.叙事指南」,细化终稿可将本产物同步或改挂新 tag(本步只写新 artifact)。 +硬规则:合法 JSON;示范至少 1 段;禁忌与不偏好都要有;双挂;禁止 order;summary 见 task。 ## checklist ```checklist -- [ ] 含 schema / brief / 正文 / 自评 / 追问? -- [ ] 自评为写法可执行 / 推进可执行 / 与美学对齐? -- [ ] 遣词造句具体?有示范?笔墨焦点写清详谁略谁? -- [ ] 绝对禁忌与不偏好都有,且贴合体裁(男频/女频/末日/日常等)? -- [ ] 推进钩子清楚或明确无需?未越权写生成规则/变量规格? +- [ ] 一份产物同时服务转述与主世界层? +- [ ] 遣词+示范+禁忌够用?推进清楚或明确无? +- [ ] 未为省 token 拆两套投影? ``` ## examples ```examples -好: -- 男频爱情爽文:遣词网文短句;笔墨详女角色美丽与心动反应;略写男主外貌流水账;禁忌:禁止把女主写成无魅力背景板。 -- 女频:笔墨详男角色气质/行动力;不偏好过度描写女主外貌抢戏。 -- 末日:详写寒冷/饥饿/信任崩裂;绝对禁忌「轻松解决危机」;推进允许失败与代价。 -- 日常小确幸:详写琐碎暖意;绝对禁忌无铺垫的突发灾难;推进忌强行高潮。 -- NSFW 备用:触发后改官能遣词;笔墨可临时偏移身体感官。 - -坏: -- 只写「要有沉浸感」无遣词、无笔墨焦点。 -- 默认「男女都要均衡描写」却无视本局男/女用户向。 -- 末日指南里鼓励轻松打闹通关。 +好:一次问清爽文遣词 + 好感门控倒追,整份挂两边。 +坏:拆成「文风技能」和「推进技能」两步;或为省 token 给两边裁成两套残缺稿。 ``` diff --git a/skills/dialogue/world-simulator/modules/reply-format/prompt.md b/skills/dialogue/world-simulator/modules/reply-format/prompt.md index 6de4ca8..2fff466 100644 --- a/skills/dialogue/world-simulator/modules/reply-format/prompt.md +++ b/skills/dialogue/world-simulator/modules/reply-format/prompt.md @@ -12,7 +12,7 @@ name: 正文组成 id: reply-format artifact: 设计.正文组成 declaration: > - 基于四大固定呈现壳做适配微调(选 shell_id + 填字段/显示名/开关等); + 基于七种固定呈现壳做适配微调(选 shell_id + 填字段/显示名/开关等); 产出壳适配单;禁止从零发明布局或完整 UI when: | 需要为本局选定呈现壳,或微调监控字段/块显示名/建议行动开关时; @@ -34,10 +34,13 @@ feeds: narrator ```opening 本局在固定呈现壳上做适配(不是设计新界面)。 -0. 四选一壳: - · prose — 纯散文 - · chat_monitor — 对话 + 顶栏监控 - · turn_panel — 回合面板 +0. 七选一壳(主面积给谁): + · prose — 纯散文(正文≈全屏) + · chat_monitor — 对话 + 顶栏芯片 + · spotlight — 场面主视(正文最大) + · turn_panel — 回合面板(场面+侧栏交互) + · split_board — 双栏(左故事·右线索) + · choice_dock — 选择坞(底部大行动区) · chapter_reader — 章节阅读 1. 要微调什么?(监控字段、块称呼、要不要建议行动、tone_chrome…) @@ -50,13 +53,13 @@ feeds: narrator ```task 你正在执行「正文组成」。产物必须是 **context-fragment.v1** JSON,写入「设计.正文组成」。 -本步 = **壳适配单**:四大壳里选一个,只改允许的微调轴。 +本步 = **壳适配单**:七种壳里选一个,只改允许的微调轴。 权威:`docs/play-presentation-shells.md`。 禁止从零设计分区、禁止自造 shell_id、禁止完整 CSS。 执行顺序: 1. 读美学/叙事:继承人称与终稿归属;不重开文风问卷。 -2. **选 shell_id**;说明为何是这个壳而不是另三个。 +2. **选 shell_id**;说明为何是这个壳而不是其它壳(主面积给谁)。 3. 可见块:只挂到该壳已有区域;写显示名与职责(适配文案,不是新分区)。 4. 监控栏:字段意图(细则可交「设计监控栏」);无 monitor 的壳写「不启用」。 5. 隐藏段(若有):定界与用途。 @@ -107,7 +110,7 @@ summary:`正文组成 · 适配 {shell_id} · …`。 "从美学/叙事继承的呈现要点" ], "呈现壳": { - "shell_id": "prose|chat_monitor|turn_panel|chapter_reader", + "shell_id": "prose|chat_monitor|spotlight|turn_panel|split_board|choice_dock|chapter_reader", "为何选它": "一句话", "微调": { "tone_chrome": "messenger|terminal|book|default", @@ -187,7 +190,7 @@ summary:`正文组成 · 适配 {shell_id} · …`。 { "名": "可实现", "分数": 8, - "说明": "shell_id 是否在四大壳内;分区与隐藏段是否可交给转述/程序" + "说明": "shell_id 是否在七种壳内;分区与隐藏段是否可交给转述/程序" } ], "薄弱点": "一句话" @@ -208,7 +211,7 @@ summary:`正文组成 · 适配 {shell_id} · …`。 硬规则: 1. 合法 JSON;含公共三段外壳。 -2. 正文须含 `呈现壳.shell_id`(四大之一)与 `微调`;可见块只挂壳已有区域。 +2. 正文须含 `呈现壳.shell_id`(七种之一)与 `微调`;可见块只挂壳已有区域。 3. `可见块` 至少 1 条(通常含 body);禁止自造区域 id / 第五种壳。 4. 禁止程序 API schema、context `order`、完整 CSS。 5. 自评:可见完备 / 监控克制 / 可实现(=壳适配是否可执行)。 @@ -219,7 +222,7 @@ summary:`正文组成 · 适配 {shell_id} · …`。 ```checklist - [ ] 是「选壳+微调」,不是从零设计布局? -- [ ] shell_id 在四大壳内?可见块未超出该壳分区? +- [ ] shell_id 在七种壳内?可见块未超出该壳分区? - [ ] 微调轴已填或明确默认?监控栏未塞死名片字段? - [ ] 隐藏段(若有)定界清楚?未写 CSS 工程? ``` diff --git a/skills/dialogue/world-simulator/modules/variable-context/prompt.md b/skills/dialogue/world-simulator/modules/variable-context/prompt.md index cfd442b..ed8c5f5 100644 --- a/skills/dialogue/world-simulator/modules/variable-context/prompt.md +++ b/skills/dialogue/world-simulator/modules/variable-context/prompt.md @@ -23,7 +23,7 @@ boundary: | 变量设计:字段、映射索引、维护语句形状、side_effects。 具体实例:表行正文——本步不重生成,只声明「旁观读当前档/哪几个摘要键」。 设计监控栏 / 正文组成:用户可见层;本步管提示词视野。 -feeds: gm +feeds: gm,auditor ``` ## opening @@ -72,13 +72,13 @@ summary:`变量控制上下文 · …`。 "schema": "context-fragment.v1", "技能": "变量控制上下文", "brief": "一句话:各槽看见什么状态", - "mount": ["world-simulator"], + "mount": ["world-simulator", "auditor"], "稳变": "stable", "正文": { "挂载": [ { "对象": "主世界层|叙事转述|旁观|常驻", - "ref或mount": "world-simulator|narrator|…", + "ref或mount": "world-simulator|narrator|auditor|…", "真值键": ["好感"], "投影tag": ["上下文.角色态度"], "映射摘要": ["从映射id取当前档的哪些键;无则 []"], diff --git a/skills/dialogue/world-simulator/orchestrator.md b/skills/dialogue/world-simulator/orchestrator.md index ef10637..0a58b93 100644 --- a/skills/dialogue/world-simulator/orchestrator.md +++ b/skills/dialogue/world-simulator/orchestrator.md @@ -120,7 +120,8 @@ play 4. `waiting_user(review_artifact)` → 引导验收 5. reject → 收修订 → 重跑同一 worker(含修订流程 = 再调味) 6. 终稿(含 `设计.worker集`)已 accept 且需开局 → `opening-generator` -7. **play**:按 `play_slots` 序 `auditor → perspective? → gm → narrator`;`pendingSideEffectWorkers` 优先 +7. **play**:按 `play_slots` 序 `auditor → perspective? → gm → narrator`;`pendingSideEffectWorkers` 优先 + (旁观与主世界层怎么配合:见 `docs/play-dm-auditor.md`——旁观看门补表,DM 断事,勿默认多轮传话) ## 禁用行为 diff --git a/skills/dialogue/world-simulator/worker-templates/narrator.yaml b/skills/dialogue/world-simulator/worker-templates/narrator.yaml index a865d5b..90f5b7f 100644 --- a/skills/dialogue/world-simulator/worker-templates/narrator.yaml +++ b/skills/dialogue/world-simulator/worker-templates/narrator.yaml @@ -23,7 +23,7 @@ presentation_hints: tone: 依 Worker 集 presentation / 叙事指南 / 正文组成壳 prompt_excerpt: | 你是面向用户的叙事转述。输入是结构化裁决包(settlement.v1),不是自由发挥的世界真相。 - 先读「设计.正文组成」的呈现壳 shell_id(prose|chat_monitor|turn_panel|chapter_reader): + 先读「设计.正文组成」的呈现壳 shell_id(prose|chat_monitor|spotlight|turn_panel|split_board|choice_dock|chapter_reader): - prose:可直接输出 Markdown 正文(程序会落入 body)。 - 其它壳:优先输出 JSON,schema 为 present.v1: { @@ -34,10 +34,10 @@ prompt_excerpt: | "header": "可选", "body": "用户可读正文", "footer": "可选", - "aside": "可选" + "aside": "可选短文或键值" }, "meta": { "suggested_actions": ["…"] } } - 只填该壳已有区域;监控字段来自设计.监控栏 / 投影,禁止编造真值数字若 do_not_say 禁止。 + monitor 用短键值(芯片),勿写长文。只填该壳已有区域。 禁止写出 do_not_say;禁止编造包中没有的事实。 若裁决含 tone_hint / npc_moves / suggested_actions,须体现在 body 或 meta。 diff --git a/src/skills/present-packet.ts b/src/skills/present-packet.ts index 850be4d..46acc79 100644 --- a/src/skills/present-packet.ts +++ b/src/skills/present-packet.ts @@ -10,7 +10,10 @@ export const PRESENT_TAG = "输出.用户展示"; export const PRESENT_SHELL_IDS = [ "prose", "chat_monitor", + "spotlight", "turn_panel", + "split_board", + "choice_dock", "chapter_reader", ] as const; @@ -70,7 +73,7 @@ export function defaultShellTweaks( ): PresentShellTweaks { return { tone_chrome: "default", - show_suggested_actions: shell === "turn_panel", + show_suggested_actions: shell === "turn_panel" || shell === "choice_dock", block_labels: {}, empty_states: {}, }; @@ -317,13 +320,19 @@ export function shellDefaultRegions( switch (shell) { case "chat_monitor": return ["monitor", "body", "footer"]; + case "spotlight": + return ["monitor", "body", "footer"]; case "turn_panel": - return ["monitor", "header", "body", "footer", "aside"]; + return ["monitor", "header", "body", "aside", "footer"]; + case "split_board": + return ["header", "body", "aside", "footer"]; + case "choice_dock": + return ["monitor", "body", "footer"]; case "chapter_reader": return ["header", "body", "aside", "footer"]; case "prose": default: - return ["body"]; + return ["body", "footer"]; } } diff --git a/web/agent-ui.js b/web/agent-ui.js index 2bb08c4..95b3cd3 100644 --- a/web/agent-ui.js +++ b/web/agent-ui.js @@ -370,9 +370,15 @@ function formatPlayPresentHtml(body, view) { tweaks?.shell_id && PRESENT_SHELL_IDS?.includes?.(tweaks.shell_id) ? tweaks.shell_id : tweaks?.shell_id && - ["prose", "chat_monitor", "turn_panel", "chapter_reader"].includes( - tweaks.shell_id, - ) + [ + "prose", + "chat_monitor", + "spotlight", + "turn_panel", + "split_board", + "choice_dock", + "chapter_reader", + ].includes(tweaks.shell_id) ? tweaks.shell_id : "prose"; const parsed = tryParseJsonDoc(trimmed); @@ -1404,6 +1410,14 @@ function renderSpecialtyBodyHtml(body, skill) { const html = renderMechanismBodyHtml(body); if (html) return html; } + if ( + body.风格与写法 != null || + body.推进与决策 != null || + skillName.includes("叙事指南") + ) { + const html = renderNarrativeBodyHtml(body); + if (html) return html; + } const looksAesthetics = body.设定逻辑 != null || @@ -1432,6 +1446,60 @@ function renderSpecialtyBodyHtml(body, skill) { return parts.length ? `
${parts.join("")}
` : ""; } +/** 叙事指南 · 一份全文结构化卡(不强调双投影裁剪) */ +function renderNarrativeBodyHtml(body) { + const order = [ + "依据的体验", + "叙事纲领", + "风格与遣词", + "笔墨焦点", + "禁忌与不偏好", + "情境备用", + "推进与决策", + "内容与表达", + "风格与写法", + ]; + const used = new Set(); + const parts = []; + // 若仍是旧的「风格与写法 / 推进与决策」两大包,整块展示 + if (body.风格与写法 != null || body.推进与决策 != null) { + if (Array.isArray(body.依据的体验) && body.依据的体验.length) { + parts.push( + `

依据的体验

${skillProseList(body.依据的体验)}
`, + ); + used.add("依据的体验"); + } + if (body.风格与写法 != null) { + parts.push( + `

风格与写法

${renderStructuredValueHtml(body.风格与写法, 0)}
`, + ); + used.add("风格与写法"); + } + if (body.推进与决策 != null) { + parts.push( + `

推进与决策

${renderStructuredValueHtml(body.推进与决策, 0)}
`, + ); + used.add("推进与决策"); + } + } else { + for (const key of order) { + if (body[key] == null) continue; + used.add(key); + const wide = key === "风格与遣词" || key === "推进与决策"; + parts.push( + `

${esc(key)}

${renderStructuredValueHtml(body[key], 0)}
`, + ); + } + } + for (const [k, v] of Object.entries(body)) { + if (used.has(k) || v == null || v === "") continue; + parts.push( + `

${esc(k)}

${renderStructuredValueHtml(v, 0)}
`, + ); + } + return parts.length ? `
${parts.join("")}
` : ""; +} + function skillSection(title, inner, cls = "") { if (!inner) return ""; return `

${esc(title)}

${inner}
`; diff --git a/web/index.html b/web/index.html index 72a8d38..91e57d7 100644 --- a/web/index.html +++ b/web/index.html @@ -61,6 +61,7 @@
技能 + 呈现壳 Token 设置 diff --git a/web/modules.html b/web/modules.html index 08bc503..f711d2b 100644 --- a/web/modules.html +++ b/web/modules.html @@ -13,6 +13,7 @@ diff --git a/web/present-shells.js b/web/present-shells.js index cb86425..ba7dcc7 100644 --- a/web/present-shells.js +++ b/web/present-shells.js @@ -1,19 +1,31 @@ /** - * 游玩呈现壳渲染(P2 最小适配) + * 游玩呈现壳渲染(P2) * 契约:docs/play-presentation-shells.md · present.v1 + * + * 信息层级约定(所有壳共用): + * P0 body — 最大字号与面积,阅读主轴 + * P1 actions — 需要决策时醒目,但不压过正文 + * P2 monitor — 芯片密排,一眼扫完 + * P3 header / footer / aside — 辅助,紧凑 */ export const PRESENT_SHELL_IDS = [ "prose", "chat_monitor", + "spotlight", "turn_panel", + "split_board", + "choice_dock", "chapter_reader", ]; const SHELL_LABELS = { prose: "纯散文", chat_monitor: "对话+监控", + spotlight: "场面主视", turn_panel: "回合面板", + split_board: "双栏看板", + choice_dock: "选择坞", chapter_reader: "章节阅读", }; @@ -26,20 +38,30 @@ const REGION_LABELS = { hidden: "隐藏", }; +/** 默认开建议行动的壳 */ +const ACTIONS_DEFAULT_ON = new Set(["turn_panel", "choice_dock"]); + function isPresentShellId(v) { return typeof v === "string" && PRESENT_SHELL_IDS.includes(v); } -function shellDefaultRegions(shell) { +export function shellDefaultRegions(shell) { switch (shell) { case "chat_monitor": return ["monitor", "body", "footer"]; + case "spotlight": + return ["monitor", "body", "footer"]; case "turn_panel": - return ["monitor", "header", "body", "footer", "aside"]; + return ["monitor", "header", "body", "aside", "footer"]; + case "split_board": + return ["header", "body", "aside", "footer"]; + case "choice_dock": + return ["monitor", "body", "footer"]; case "chapter_reader": return ["header", "body", "aside", "footer"]; + case "prose": default: - return ["body"]; + return ["body", "footer"]; } } @@ -71,9 +93,35 @@ function normalizeBlocks(raw) { return out; } +function renderChipsHtml(value, esc) { + if (!value || typeof value !== "object" || Array.isArray(value)) return null; + const entries = Object.entries(value).filter(([k]) => k != null && String(k).trim()); + if (!entries.length) return null; + return `
${entries + .map( + ([k, v]) => + `${esc(k)}${esc( + typeof v === "string" ? v : JSON.stringify(v), + )}`, + ) + .join("")}
`; +} + +function regionInnerHtml(region, rawValue, esc) { + if (region === "monitor") { + const chips = renderChipsHtml(rawValue, esc); + if (chips) return chips; + } + if (region === "aside" && rawValue && typeof rawValue === "object" && !Array.isArray(rawValue)) { + const chips = renderChipsHtml(rawValue, esc); + if (chips) return chips; + } + const text = formatBlockContent(rawValue).trim(); + return `
${esc(text).replace(/\n/g, "
")}
`; +} + /** * @returns {{ ok: boolean, packet: object, fallbackPlain: boolean } | null} - * null = 不是 present 包(交给其它渲染器) */ export function parsePresentDoc(doc, fallbackShell = "prose") { if (!doc || typeof doc !== "object" || Array.isArray(doc)) return null; @@ -110,7 +158,6 @@ export function parsePresentDoc(doc, fallbackShell = "prose") { }; } -/** 纯文本 → prose 包(供调用方在非 JSON 时使用) */ export function presentFromPlain(text, shell = "prose") { return { schema: "present.v1", @@ -120,6 +167,65 @@ export function presentFromPlain(text, shell = "prose") { }; } +function regionTitle(region, labels, _shell) { + // 正文默认不挂标题(面积留给文字);创作填了 block_labels.body 才显示 + if (region === "body") { + return labels.body || labels["正文"] || null; + } + return ( + labels[region] || + labels[REGION_LABELS[region]] || + REGION_LABELS[region] || + region + ); +} + +function renderRegion(region, rawValue, esc, { labels, emptyStates, shell }) { + let hasContent = + rawValue !== undefined && + rawValue !== null && + (typeof rawValue === "string" + ? rawValue.trim() + : Array.isArray(rawValue) + ? rawValue.length + : typeof rawValue === "object" + ? Object.keys(rawValue).length + : true); + + let displayRaw = rawValue; + if (!hasContent && emptyStates[region]) { + displayRaw = String(emptyStates[region]); + hasContent = true; + } + if (!hasContent && region !== "body") return ""; + if (!hasContent && region === "body") displayRaw = "(暂无正文)"; + + const title = regionTitle(region, labels, shell); + const titleHtml = title + ? `

${esc(title)}

` + : ""; + + return ` +
+ ${titleHtml} + ${regionInnerHtml(region, displayRaw, esc)} +
`; +} + +function renderActionsHtml(actions, esc) { + if (!actions.length) return ""; + return ` +
+

建议行动

+
${actions + .map( + (a) => + `${esc(a)}`, + ) + .join("")}
+
`; +} + /** * @param {object} packet present.v1 * @param {(s: string) => string} esc @@ -141,7 +247,7 @@ export function renderPresentShellHtml(packet, esc, opts = {}) { const showActions = typeof tweaks.show_suggested_actions === "boolean" ? tweaks.show_suggested_actions - : shell === "turn_panel"; + : ACTIONS_DEFAULT_ON.has(shell); const tone = typeof tweaks.tone_chrome === "string" && tweaks.tone_chrome.trim() ? tweaks.tone_chrome.trim() @@ -149,41 +255,96 @@ export function renderPresentShellHtml(packet, esc, opts = {}) { const blocks = packet.blocks ?? {}; const regions = shellDefaultRegions(shell); - const parts = []; + const ctx = { labels, emptyStates, shell }; - for (const region of regions) { - if (region === "hidden") continue; - let text = formatBlockContent(blocks[region]).trim(); - if (!text && emptyStates[region]) text = String(emptyStates[region]); - if (!text && region !== "body") continue; - if (!text && region === "body") text = "(暂无正文)"; - - const title = - labels[region] || - labels[REGION_LABELS[region]] || - REGION_LABELS[region] || - region; - const isBody = region === "body"; - parts.push(` -
- ${isBody && shell === "prose" ? "" : `

${esc(title)}

`} -
${esc(text).replace(/\n/g, "
")}
-
`); + const by = {}; + for (const r of regions) { + by[r] = renderRegion(r, blocks[r], esc, ctx); } const actions = showActions ? packet.meta?.suggested_actions ?? [] : []; - if (actions.length) { - parts.push(` -
-

建议行动

-
    ${actions.map((a) => `
  • ${esc(a)}
  • `).join("")}
-
`); + const actionsHtml = renderActionsHtml(actions, esc); + + let layoutHtml = ""; + switch (shell) { + case "chat_monitor": + layoutHtml = ` +
+ ${by.monitor || ""} +
${by.body || ""}
+ ${by.footer || ""} + ${actionsHtml} +
`; + break; + case "spotlight": + layoutHtml = ` +
+ ${by.monitor || ""} +
${by.body || ""}
+ ${by.footer || ""} + ${actionsHtml} +
`; + break; + case "turn_panel": + layoutHtml = ` +
+
${by.monitor || ""}${by.header || ""}
+
+
${by.body || ""}
+
${by.aside || ""}${by.footer || ""}
+
+ ${actionsHtml} +
`; + break; + case "split_board": + layoutHtml = ` +
+
+ ${by.header || ""} +
${by.body || ""}
+ ${by.footer || ""} +
+
${by.aside || ""}
+ ${actionsHtml} +
`; + break; + case "choice_dock": + layoutHtml = ` +
+ ${by.monitor || ""} +
${by.body || ""}
+ ${by.footer || ""} +
${actionsHtml}
+
`; + break; + case "chapter_reader": + layoutHtml = ` +
+
+ ${by.header || ""} +
${by.body || ""}
+ ${by.footer || ""} +
+
${by.aside || ""}
+
`; + break; + case "prose": + default: + layoutHtml = ` +
+
${by.body || ""}
+ ${by.footer || ""} + ${actionsHtml} +
`; + break; } const shellLabel = SHELL_LABELS[shell] || shell; return `
${esc(shellLabel)}
- ${parts.join("")} + ${layoutHtml}
`; } + +export { SHELL_LABELS, REGION_LABELS, isPresentShellId }; diff --git a/web/settings.html b/web/settings.html index 1e72693..e67d846 100644 --- a/web/settings.html +++ b/web/settings.html @@ -12,6 +12,7 @@ diff --git a/web/shells.css b/web/shells.css new file mode 100644 index 0000000..368ad20 --- /dev/null +++ b/web/shells.css @@ -0,0 +1,173 @@ +/* 呈现壳预览页 + * 注意:本页也引入 styles.css(present 样式),会给 body 设 overflow:hidden; + * 必须在此覆盖,否则侧栏后几项点不到。 + */ +html, +body.shells-page { + height: auto; + min-height: 100%; + overflow: auto; +} + +.shells-page .st-shell.shells-shell { + max-width: 1180px; + align-items: start; +} + +.shells-page .st-rail { + --st-rail-w: 220px; + width: var(--st-rail-w); + position: sticky; + top: var(--st-top-h, 48px); + max-height: calc(100dvh - var(--st-top-h, 48px)); + overflow-y: auto; + min-height: 0; + align-self: start; +} + +.shells-tone-label { + margin-top: 1rem !important; +} + +.shells-density { + display: flex; + flex-wrap: wrap; + gap: 6px 10px; + align-items: baseline; + margin: 0.5rem 0 0.35rem; + padding: 8px 10px; + border-radius: 8px; + background: color-mix(in srgb, var(--accent-soft, #e8f0fe) 70%, transparent); + font-size: 13px; + line-height: 1.45; +} + +.shells-density span { + font-size: 11px; + font-weight: 700; + letter-spacing: 0.04em; + color: var(--muted, #666); + text-transform: uppercase; +} + +.shells-list { + display: flex; + flex-direction: column; + gap: 4px; + flex: 0 0 auto; +} + +.shells-tones { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.shells-tone-chip { + font: inherit; + font-size: 12px; + padding: 4px 10px; + border-radius: 999px; + border: 1px solid var(--border, #ddd); + background: var(--surface, #fff); + cursor: pointer; + color: inherit; +} + +.shells-tone-chip.active { + border-color: transparent; + background: var(--accent-soft, #e8f0fe); + font-weight: 600; +} + +.shells-body { + display: flex; + flex-direction: column; + gap: 1.25rem; +} + +.shells-meta p { + margin: 0 0 0.35rem; + line-height: 1.5; +} + +.shells-preview-label, +.shells-tweaks h2, +.shells-help h2 { + margin: 0 0 0.5rem; + font-size: 13px; + font-weight: 700; + color: var(--muted, #666); +} + +.shells-preview { + min-height: 280px; + padding: 16px; + border-radius: 12px; + border: 1px dashed var(--border, #ccc); + background: + radial-gradient(ellipse at 20% 0%, rgba(15, 118, 110, 0.06), transparent 50%), + var(--bg, #f6f6f4); +} + +.shells-preview .present-shell { + max-width: 640px; + margin: 0 auto; + box-shadow: 0 10px 28px rgba(28, 25, 23, 0.07); +} + +.shells-preview[data-shell="turn_panel"] .present-shell, +.shells-preview[data-shell="split_board"] .present-shell, +.shells-preview[data-shell="chapter_reader"] .present-shell { + max-width: 780px; +} + +/* 预览区不裁切长正文 */ +.shells-preview { + overflow: visible; +} + +.shells-tweak-grid { + display: flex; + flex-direction: column; + gap: 10px; +} + +.shells-tweak-grid .field { + display: flex; + flex-direction: column; + gap: 4px; + font-size: 13px; +} + +.shells-tweak-grid input[type="text"] { + font: inherit; + padding: 6px 8px; + border-radius: 6px; + border: 1px solid var(--border, #ddd); +} + +.shells-help ul { + margin: 0; + padding-left: 1.2rem; + line-height: 1.55; + font-size: 13px; +} + +.shells-help .muted { + font-size: 12px; + color: var(--muted, #888); +} + +@media (max-width: 800px) { + .shells-page .st-rail { + position: static; + max-height: none; + width: auto; + overflow: visible; + } + + .shells-preview .present-shell { + max-width: 100%; + } +} \ No newline at end of file diff --git a/web/shells.html b/web/shells.html new file mode 100644 index 0000000..e043f81 --- /dev/null +++ b/web/shells.html @@ -0,0 +1,74 @@ + + + + + + 呈现壳预览 · Writing Agent + + + + + + + + +
+ Writing Agent + +
+ +
+ + +
+
+
+

呈现壳预览

+

用示例数据看布局与可调项

+
+
+
+
+
+

预览

+
+
+
+

本页可调(对应创作「微调轴」)

+
+
+
+

设计意图

+
    +
  • 正文默认装得下单轮 600~3000 字:区域随内容长高,由页面/消息区滚动,不塞进固定小框。
  • +
  • 双栏壳:侧栏 sticky;选择坞:选项区 sticky 贴底——长文读完控件还在。
  • +
  • 壳差在「谁占主柱」;可改的是字段/显示名/行动开关/tone,不是重画布局。
  • +
+

说明文档:docs/play-presentation-shells.md

+
+
+
+
+ + + + diff --git a/web/shells.js b/web/shells.js new file mode 100644 index 0000000..c44620c --- /dev/null +++ b/web/shells.js @@ -0,0 +1,358 @@ +/** + * 呈现壳预览页:切换壳 / 装饰语气 / 正文长度,用示例数据渲染 present-shells。 + * 正文设计目标:单轮常见 600~3000 字 —— 随字数撑高,外层滚动;侧栏/选项坞钉住。 + */ +import { + PRESENT_SHELL_IDS, + renderPresentShellHtml, +} from "./present-shells.js"; + +const SHELL_INFO = { + prose: { + name: "纯散文", + blurb: "整块都是正文,无 HUD。长文直接往下滚。", + density: "正文随字数伸展;无侧栏争面积", + scenes: "轻对话、纯叙事、不要状态栏", + }, + chat_monitor: { + name: "对话 + 监控", + blurb: "顶栏芯片固定矮;主聊/场面再长也只往下长。", + density: "监控一行;正文撑高滚动", + scenes: "网恋 / 日常扮演 / 轻 AIRP", + }, + spotlight: { + name: "场面主视", + blurb: "芯片一行 + 大正文柱。适合一幕写满。", + density: "正文主导并伸展;监控不占高", + scenes: "电影感 RP、一幕一景、少机制", + }, + turn_panel: { + name: "回合面板", + blurb: "场面叙述再长,右侧交互物钉在视口内对照。", + density: "正文撑高;侧栏 sticky;行动在下", + scenes: "资源、检定、场景交互、跑团感", + }, + split_board: { + name: "双栏看板", + blurb: "左栏长文滚动,右栏线索钉住——对照读。", + density: "左正文伸展;右侧栏 sticky", + scenes: "调查、推理、多线索并行", + }, + choice_dock: { + name: "选择坞", + blurb: "先读完长局面;选项坞 sticky 贴视口底,不用翻回顶。", + density: "正文撑高;底部坞钉住", + scenes: "选项驱动、AVG、分支关口", + }, + chapter_reader: { + name: "章节阅读", + blurb: "章题 + 长阅读柱;窄进度侧栏 sticky。", + density: "正文伸展为主;侧栏窄且钉住", + scenes: "长文 / 爽文 / 先纲后章", + }, +}; + +const TONES = [ + { id: "default", label: "默认" }, + { id: "messenger", label: "讯息感" }, + { id: "book", label: "书页感" }, + { id: "terminal", label: "终端感" }, +]; + +const LENGTHS = [ + { id: "short", label: "短(~120)" }, + { id: "mid", label: "中(~800)" }, + { id: "long", label: "长(~2500)" }, +]; + +/** 中等长度 ≈800 字 */ +const BODY_MID = `雨还在下。你把伞往她那边偏了偏,她没说话,只是把购物袋往怀里收紧了一点。水从伞骨边缘连成线,砸在柏油路上,溅起细碎的白点。 + +路口的灯跳成绿色。你们一起迈步——这一次,谁也没有先松开。对面便利店的玻璃上映着两个人并肩的轮廓,忽明忽暗,像谁随手画的草稿。 + +「伞……」她终于开口,声音被雨声压得很低,「明天不用还。」 + +你点头,又觉得这回答太轻,补了一句:「那我请你喝热的?」她侧过脸,雨丝粘在睫毛上,笑意只闪了一下,像怕被看穿。 + +电梯门开的时候,楼道里干燥得过分。她把袋子换到另一只手,钥匙在指间转了一圈,忽然停住:「其实我今天本来想绕路的。」 + +「为什么?」 + +「因为预报说会停。结果没有。」她把钥匙推进锁孔,「所以——只好借你的伞了。」 + +门开了一条缝。暖光漏出来,照在你们脚边的水渍上。她没有立刻进去,只是抬眼看你,像在等一句还没说出口的话。你把伞收拢,水仍顺着伞尖滴到地毯边缘。谁也没有提「进来坐坐」,但空气里已经有了那句话的形状。`; + +/** 长文 ≈2500 字(重复段落拼成,预览用) */ +const BODY_LONG = `${BODY_MID} + +天桥下的风从另一头灌过来,把塑料袋吹得啪啪响。你想起上一次走过这里,还是夏天,蝉声吵得人没法好好说话;如今只剩雨,以及偶尔驶过的车灯,把积水切成一条条亮痕。 + +便利店里的收音机断断续续播着交通路况。店员打着哈欠换班,玻璃门开合时带进一阵湿冷。你站在门口抖伞,余光看见货架尽头有人停了很久——拿起一瓶水,又放下,再拿起,像在跟自己商量一件比购物更大的事。 + +她的短信迟了三分钟才到:「到家了吗?」你盯着屏幕上的光标,打了又删:刚进小区 / 伞还在我这 / 明天……最后只留下一句:「刚到。伞明天给你。」她回了个省略号,又补:「不用那么快。」 + +你把手机塞回口袋,忽然觉得「不用那么快」四个字比天气预报更准。有些东西一旦说破就会散,只好让它停在半湿半干之间。 + +楼道灯感应得慢,你摸黑上了两级台阶才亮。隔壁门缝里漏出电视剧的笑声,被雨声削成模糊的边。你靠着墙站了一会儿,听自己的呼吸从急变匀,才继续往上走。 + +到家后你把伞撑在阳台,水沿着伞骨汇成细流,滴进盆里,一声一声,像在数今晚还剩多少没说清的话。窗玻璃上凝着雾,你用指尖随便划了一道,很快又糊住——外面的城市灯火依旧,只是被雨帘隔成另一层世界。 + +你泡了杯热水,坐到桌前,本想写点什么,笔尖却停在空白处。纸页吸走一点水汽,皱起细纹。你写下一句又涂掉:关于伞,关于绿灯,关于她说「绕路」时那种几乎听不见的停顿。最后只留下日期,和一行没写完的「明天」。 + +夜里雨势小了些。你关灯前又看了一眼阳台——伞还在,水还在滴,只是间隔更长了,像什么人放慢了脚步,却没有真的离开。 + +(——以下为加长段,模拟单轮 2000+ 字场面——) + +次日清晨,天边只剩薄云。你带着伞出门,金属骨架在布套里轻轻碰撞。电梯里遇见邻居遛狗,它的爪子在地垫上留下两串湿印,很快被后来的人踩散。你想:痕迹就是这样,被人看见时已不是原样。 + +到了约定的路口,她比你早到半分钟,手里多了一杯热饮,杯套上印着歪歪扭扭的笑脸。「还给你。」你把伞递过去。她却摇头:「今天晴。」伞停在两人之间,像一句还没决定归属的句子。 + +你们沿着河堤走。水面上漂着昨夜的落叶,偶有涟漪被风抹平。她讲起最近在忙的展览,讲到一半又停住,说其实不太想聊工作。于是话题滑向更碎的东西:哪家面馆换了老板、哪条巷子的灯坏了三天、哪一次下雨你们刚好都没带伞。 + +「所以才需要借。」她说。 + +「借完还要还。」你说。 + +「有的不用还。」她看着河对岸的塔吊,「有的还了,反而像没发生过。」 + +塔吊缓缓旋转,吊臂在灰蓝天空里划出钝角。你忽然明白,呈现壳要解决的不是「字大一点」,而是这一长段话落在界面里时,读者还愿不愿意往下看——状态栏不能抢戏,选项不能在中途消失,侧栏得钉住好让人对照线索。 + +你们在桥上站了一会儿。风把伞套吹得鼓起,又瘪下去。她把热饮塞进你手里:「暖的。你昨晚肯定没睡好。」你没有否认。杯壁的温度从掌心爬上手腕,像把雨夜重新烘了一遍,只留下能说出口的那一层。 + +分开前,她抬手点了点伞:「这个,先放你那儿。等下一场雨。」 + +你点头。伞重新沉进包里,重量不大,却让背包的一侧微微倾斜——刚好提醒你:故事还会继续,而界面必须装得下继续。`; + +const BODY_SHORT = { + prose: + "雨还在下。你把伞往她那边偏了偏,她没说话,只是把购物袋往怀里收紧了一点。\n\n路口的灯跳成绿色。你们一起迈步——这一次,谁也没有先松开。", + chat_monitor: + "【短信】\n她:到家了吗?\n你:刚进小区。伞借你的,明天还给你?\n她:……不用那么快。", + spotlight: + "水从铁栏杆上砸下来,把对面那人的轮廓冲得忽明忽暗。\n\n他抬起头——认出是你的瞬间,肩膀松了一下,又立刻绷紧。", + turn_panel: + "货架后传来塑料袋摩擦声。你屏住呼吸,左手按住刀柄——也许只是风。\n\n收银台抽屉半开,里面有未拆封的电池。", + split_board: + "抽屉夹层里有一张折痕发白的收据。日期是失踪前夜,商户栏写着「北港五金」。\n\n窗台积灰被抹过一道——有人近来开过窗。", + choice_dock: + "她把钥匙放在桌上,没有推过来,也没有收回。\n\n「你要是现在走,我就当今晚什么都没发生。」", + chapter_reader: + "  天亮得比往日晚。林晚站在窗边,把画册合上,指尖在封面上停了一息。\n\n  「今天,」她说,「我们换条路回家。」", +}; + +const SAMPLE_META = { + prose: { + blocks: { footer: "本段为示例备注(可关)" }, + meta: { suggested_actions: [] }, + }, + chat_monitor: { + blocks: { + monitor: { 好感: "暖络", 今日话题: "下雨天", 体力: "还好" }, + footer: "", + }, + meta: { suggested_actions: ["回一句关心", "约周末还伞"] }, + }, + spotlight: { + blocks: { + monitor: { 地点: "天桥下", 天气: "暴雨", 张力: "紧" }, + footer: "", + }, + meta: { suggested_actions: ["先开口", "递伞过去"] }, + }, + turn_panel: { + blocks: { + monitor: { 体力: "6/10", 物资: "压缩饼干×2", 威胁: "低" }, + header: "第 12 回合 · 废弃便利店", + footer: "检定:潜行(通过)", + aside: { 可交互: "电池 / 后门 / 死角", 噪音: "低" }, + }, + meta: { suggested_actions: ["摸电池", "从后门离开", "大声喝问"] }, + }, + split_board: { + blocks: { + header: "案发现场 · 二楼书房", + aside: { + 线索: "北港收据", + 证物: "窗台指痕", + 待查: "五金店监控", + }, + footer: "", + }, + meta: { suggested_actions: ["去北港", "采指纹", "先问管家"] }, + }, + choice_dock: { + blocks: { + monitor: { 信任: "中", 时间: "子夜前" }, + footer: "", + }, + meta: { + suggested_actions: ["拿走钥匙留下", "把钥匙推回去", "什么也不说,坐着"], + }, + }, + chapter_reader: { + blocks: { + header: "卷三 · 第 7 章 雨停之前", + aside: "进度:卷三 7/18\n下一钩:母亲来电", + footer: "", + }, + meta: { suggested_actions: [] }, + }, +}; + +function pickBody(shell, length) { + if (length === "short") return BODY_SHORT[shell] || BODY_SHORT.prose; + if (length === "mid") return BODY_MID; + return BODY_LONG; +} + +function countChars(s) { + return Array.from(String(s ?? "").replace(/\s/g, "")).length; +} + +function esc(s) { + return String(s ?? "") + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); +} + +const state = { + shell: "spotlight", + tone: "default", + length: "mid", + showActions: true, + blockLabels: {}, +}; + +function buildTweaks() { + return { + tone_chrome: state.tone, + show_suggested_actions: state.showActions, + block_labels: { ...state.blockLabels }, + empty_states: { + aside: "(侧栏空:可在创作里改空态文案)", + footer: "(文末空)", + }, + }; +} + +function buildPacket() { + const base = SAMPLE_META[state.shell] || SAMPLE_META.prose; + const body = pickBody(state.shell, state.length); + return { + schema: "present.v1", + shell: state.shell, + blocks: { ...base.blocks, body }, + meta: base.meta, + _chars: countChars(body), + }; +} + +function render() { + const info = SHELL_INFO[state.shell] || { + name: state.shell, + blurb: "", + scenes: "", + density: "", + }; + const packet = buildPacket(); + document.getElementById("panel-title").textContent = info.name; + document.getElementById("panel-subtitle").textContent = `${info.scenes} · 正文约 ${packet._chars} 字(不计空白)`; + + document.getElementById("shell-meta").innerHTML = ` +

${esc(info.name)}${esc(state.shell)}

+

${esc(info.blurb)}

+

长文策略${esc(info.density)} · 当前试读 ${packet._chars}

+

适用:${esc(info.scenes)}。请用上方「正文长度」切到长文,确认侧栏/选项是否仍可用。

+ `; + + const preview = document.getElementById("shell-preview"); + preview.dataset.shell = state.shell; + preview.innerHTML = renderPresentShellHtml(packet, esc, { tweaks: buildTweaks() }); + + document.querySelectorAll("[data-shell]").forEach((btn) => { + btn.classList.toggle("active", btn.getAttribute("data-shell") === state.shell); + }); + document.querySelectorAll("[data-tone]").forEach((btn) => { + btn.classList.toggle("active", btn.getAttribute("data-tone") === state.tone); + }); + document.querySelectorAll("[data-length]").forEach((btn) => { + btn.classList.toggle("active", btn.getAttribute("data-length") === state.length); + }); + + const showActionsEl = document.getElementById("tweak-show-actions"); + if (showActionsEl) showActionsEl.checked = state.showActions; +} + +function mount() { + const list = document.getElementById("shell-list"); + list.innerHTML = PRESENT_SHELL_IDS.map((id) => { + const info = SHELL_INFO[id]; + return ``; + }).join(""); + + document.getElementById("tone-list").innerHTML = TONES.map( + (t) => + ``, + ).join(""); + + document.getElementById("length-list").innerHTML = LENGTHS.map( + (t) => + ``, + ).join(""); + + document.getElementById("tweak-controls").innerHTML = ` + + + + `; + + list.addEventListener("click", (e) => { + const btn = e.target.closest("[data-shell]"); + if (!btn) return; + state.shell = btn.getAttribute("data-shell"); + render(); + }); + + document.getElementById("tone-list").addEventListener("click", (e) => { + const btn = e.target.closest("[data-tone]"); + if (!btn) return; + state.tone = btn.getAttribute("data-tone"); + render(); + }); + + document.getElementById("length-list").addEventListener("click", (e) => { + const btn = e.target.closest("[data-length]"); + if (!btn) return; + state.length = btn.getAttribute("data-length"); + render(); + }); + + const syncLabels = () => { + state.showActions = document.getElementById("tweak-show-actions").checked; + const body = document.getElementById("tweak-label-body").value.trim(); + const monitor = document.getElementById("tweak-label-monitor").value.trim(); + state.blockLabels = {}; + if (body) state.blockLabels.body = body; + if (monitor) state.blockLabels.monitor = monitor; + render(); + }; + document.getElementById("tweak-controls").addEventListener("change", syncLabels); + document.getElementById("tweak-controls").addEventListener("input", syncLabels); + + render(); +} + +mount(); diff --git a/web/stats.html b/web/stats.html index d20a41b..0b55ace 100644 --- a/web/stats.html +++ b/web/stats.html @@ -67,7 +67,7 @@
-

← 对话 · 技能 · 设置

+

← 对话 · 技能 · 呈现壳 · 设置

Token 统计

按时间戳筛选调用记录(存于本地 stats/*.jsonl)

diff --git a/web/styles.css b/web/styles.css index 3c4f4e7..bc4fbe4 100644 --- a/web/styles.css +++ b/web/styles.css @@ -1,4 +1,4 @@ -/* Functional layout — agent-first, no production-line chrome */ +/* Functional layout — agent-first, no production-line chrome */ :root { --bg: #f4f4f5; @@ -22,7 +22,7 @@ color: var(--text); } -/* 开创作后缩进左侧栏 */ +/* 开创作后缩进左侧栏 */ body.rail-collapsed { --books-w: var(--books-w-collapsed); } @@ -31,7 +31,7 @@ body.rail-collapsed { html, body { height: 100%; margin: 0; - overflow: hidden; /* 浏览器层永不出现页面滚动条 */ + overflow: hidden; /* 浏览器层永不出现页面滚动条 */ } button, input, textarea { font: inherit; } a.link { color: var(--accent); text-decoration: none; } @@ -138,7 +138,7 @@ body.rail-collapsed .rail-tab { } body.rail-collapsed .rail-panels { display: none; } body.rail-collapsed .panel-rail { align-items: stretch; } -/* composer 按内容自适应;中间区吃掉剩余高度,整页锁在视口内 */ +/* composer 按内容自适应;中间区吃掉剩余高度,整页锁在视口内 */ .panel-main { display: grid; grid-template-rows: var(--head-h) minmax(0, 1fr) auto; @@ -195,7 +195,7 @@ body.rail-collapsed .panel-rail { align-items: stretch; } .panel-head-actions { display: flex; gap: 4px; align-items: center; } .btn-sm.active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); } -/* Books — explorer breadcrumb + list */ +/* Books — explorer breadcrumb + list */ .path-bar { display: flex; align-items: center; flex-wrap: nowrap; gap: 2px; margin: 6px 8px 4px; padding: 6px 10px; @@ -267,7 +267,7 @@ body.rail-collapsed .panel-rail { align-items: stretch; } body[data-lifecycle="play"] { --accent: #16a34a; --accent-soft: #dcfce7; --bg: #f0fdf4; } body[data-lifecycle="play"] .panel-rail { background: #f7fef9; } -/* Skill picker — primary action when starting */ +/* Skill picker — primary action when starting */ .skill-picker { padding: 16px; border-bottom: 2px solid var(--accent); @@ -285,7 +285,7 @@ body[data-lifecycle="play"] .panel-rail { background: #f7fef9; } .skill-card-desc { font-size: 12px; color: var(--muted); line-height: 1.4; } .skill-card-tag { font-size: 11px; color: var(--accent); margin-top: 6px; } -/* Feed — SillyTavern 式对话气泡 */ +/* Feed — SillyTavern 式对话气泡 */ .message-feed { flex: 1; overflow-y: auto; @@ -557,7 +557,7 @@ body.rail-collapsed .message-feed { margin: 0 0 8px; } -/* 主对话验收卡:吃满中间栏 */ +/* 主对话验收卡:吃满中间栏 */ .msg-review { align-self: stretch; width: 100%; @@ -594,7 +594,7 @@ body.rail-collapsed .message-feed { .msg-review .ws-focus-hint { margin-bottom: 0; font-size: 11px; } .msg-review .review-worker-set { margin: 0 0 6px; } -/* 产物紧凑卡:横向主轴,预览一行 */ +/* 产物紧凑卡:横向主轴,预览一行 */ .artifact-compact-list { display: flex; flex-direction: column; @@ -650,8 +650,8 @@ body.rail-collapsed .message-feed { border-radius: 999px; background: var(--accent-soft); } -.artifact-compact-toggle::before { content: "展开"; } -.artifact-compact[open] .artifact-compact-toggle::before { content: "收起"; } +.artifact-compact-toggle::before { content: "展开"; } +.artifact-compact[open] .artifact-compact-toggle::before { content: "收起"; } .artifact-compact-preview { margin: 0; font-family: var(--font-read); @@ -707,7 +707,7 @@ body.rail-collapsed .message-feed { } .ws-focus-body .artifact-compact-list { margin: 0; } -/* 展开区:mosaic / 瓷砖网格,吃满横向宽度 */ +/* 展开区:mosaic / 瓷砖网格,吃满横向宽度 */ .af-mosaic { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -818,7 +818,7 @@ body.rail-collapsed .af-mosaic { gap: 5px; } -/* 技能专用正文卡 */ +/* 技能专用正文卡 */ .skill-view { display: flex; flex-direction: column; @@ -1244,7 +1244,7 @@ body.rail-collapsed .af-mosaic { .msg-q-list li { margin: 0.35rem 0; } .msg-q-lead { margin: 0; line-height: 1.55; font-size: 14px; } -/* Cursor 式询问卡:贴在消息流下方,占满协调列宽度,不遮盖历史 */ +/* Cursor 式询问卡:贴在消息流下方,占满协调列宽度,不遮盖历史 */ .questions-card-host { flex: 0 1 auto; width: 100%; @@ -1255,7 +1255,7 @@ body.rail-collapsed .af-mosaic { min-height: 0; } .questions-card-host[hidden] { display: none !important; } -/* 开启时占 feed 剩余高度的上限,不把整页撑出视口;勿与 [hidden] 并存抢 display */ +/* 开启时占 feed 剩余高度的上限,不把整页撑出视口;勿与 [hidden] 并存抢 display */ .questions-card-host.is-open:not([hidden]) { display: flex; flex-direction: column; @@ -1276,7 +1276,7 @@ body.rail-collapsed .af-mosaic { background: var(--surface, #fff); overflow: hidden; } -/* 悬浮控件层:不占文档流高度 */ +/* 悬浮控件层:不占文档流高度 */ .qcard-float { position: absolute; inset: 0; @@ -1349,7 +1349,7 @@ body.rail-collapsed .af-mosaic { flex: 1 1 auto; min-height: 0; padding: 10px 12px; - overflow: auto; /* 仅卡内滚动;页面本身不滚 */ + overflow: auto; /* 仅卡内滚动;页面本身不滚 */ } .qcard.has-pager .qcard-body { padding-top: 36px; } .qcard.has-skip .qcard-body { padding-bottom: 40px; } @@ -1461,7 +1461,7 @@ body.rail-collapsed .af-mosaic { } .ws-slot-chip.ws-slot-on { background: var(--accent-soft); border-color: transparent; } .ws-slot-chip.ws-slot-off { opacity: 0.55; text-decoration: line-through; } -/* 产物结构化展示 — 对齐 qcard / flow-step / ws-badge */ +/* 产物结构化展示 — 对齐 qcard / flow-step / ws-badge */ .artifact-friendly { display: flex; flex-direction: column; gap: 10px; } .artifact-list { margin: 0; padding-left: 18px; line-height: 1.5; } .artifact-hero { @@ -1561,7 +1561,7 @@ body.rail-collapsed .af-mosaic { .artifact-kv-row.complex .artifact-kv-key { margin-bottom: 2px; } .artifact-kv-val { min-width: 0; font-size: 13px; line-height: 1.45; } -/* 百分比进度条 */ +/* 百分比进度条 */ .pct-meter { display: flex; flex-direction: column; @@ -1913,7 +1913,7 @@ body.rail-collapsed .af-mosaic { .timeline-item { padding: 6px 0; border-bottom: 1px solid var(--border); } .timeline-empty { color: var(--muted); } -/* Composer — 高度随内容,但有上限,避免挤爆视口 */ +/* Composer — 高度随内容,但有上限,避免挤爆视口 */ .composer { border-top: 1px solid var(--border); padding: 10px 12px; @@ -2001,81 +2001,362 @@ body.rail-collapsed .af-mosaic { body:not(.rail-collapsed) .rail-panels { display: none; } } -/* ֿ present.v1P2 С */ +/* —— 游玩呈现壳 present.v1 —— */ .present-shell { + --present-ink: #1c1917; + --present-muted: #78716c; + --present-line: #e7e5e4; + --present-soft: #f5f5f4; + --present-accent: #0f766e; + --present-body: 17.5px; + --present-body-lh: 1.78; + --present-body-hero: 19px; + --present-body-read: 18.5px; display: flex; flex-direction: column; - gap: 10px; + gap: 0; position: relative; - padding: 10px 12px 12px; - border: 1px solid var(--border); - border-radius: 10px; - background: var(--surface); + padding: 14px 16px 16px; + border: 1px solid var(--present-line); + border-radius: 12px; + background: var(--surface, #fff); + color: var(--present-ink); } .present-shell-badge { align-self: flex-start; - font-size: 11px; + margin-bottom: 10px; + font-size: 10px; font-weight: 600; - letter-spacing: 0.04em; - color: var(--muted); - padding: 1px 7px; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--present-muted); + padding: 2px 8px; border-radius: 4px; - border: 1px solid var(--border); - background: var(--surface-2, rgba(0, 0, 0, 0.03)); + border: 1px solid var(--present-line); + background: var(--present-soft); +} +.present-stack, +.present-board, +.present-split, +.present-reader { + display: flex; + flex-direction: column; + gap: 12px; + min-width: 0; } .present-region { display: flex; flex-direction: column; - gap: 4px; + gap: 6px; + min-width: 0; } .present-region-title { margin: 0; font-size: 11px; font-weight: 700; - color: var(--muted); - letter-spacing: 0.04em; + color: var(--present-muted); + letter-spacing: 0.05em; } .present-region-body { margin: 0; - line-height: 1.55; + line-height: 1.65; white-space: pre-wrap; font-size: 14px; } -.present-region--monitor .present-region-body, -.present-region--aside .present-region-body { - font-size: 13px; - color: var(--text, inherit); - padding: 6px 8px; +/* P0:正文舞台 —— 随字数伸展(600~3000 靠外层滚动,不塞进小框) */ +.present-stage { + flex: 1 1 auto; + min-height: 0; + min-width: 0; +} +.present-region--body { + min-width: 0; +} +.present-region--body .present-region-body, +.present-stage .present-region--body .present-region-body, +.present-stage.present-region--body .present-region-body { + font-size: var(--present-body); + line-height: var(--present-body-lh); + letter-spacing: 0.01em; + max-height: none; + overflow: visible; +} +.present-stage--hero .present-region-body { + font-size: var(--present-body-hero); + line-height: 1.82; + padding: 4px 2px 8px; +} +.present-stage--read .present-region-body { + font-family: var(--font-read); + font-size: var(--present-body-read); + line-height: 1.9; + max-width: 42rem; +} +/* 双栏:侧栏钉住,正文再长也能对照 */ +.present-board-side, +.present-split-side, +.present-reader-aside { + position: sticky; + top: 0.75rem; + align-self: start; + max-height: calc(100dvh - 3rem); + overflow-y: auto; +} +/* P2:监控芯片 */ +.present-chips { + display: flex; + flex-wrap: wrap; + gap: 6px 8px; +} +.present-chip { + display: inline-flex; + align-items: baseline; + gap: 6px; + padding: 4px 10px; border-radius: 6px; - border: 1px solid var(--border); - background: var(--surface-2, rgba(0, 0, 0, 0.03)); + border: 1px solid var(--present-line); + background: var(--present-soft); + font-size: 12px; + line-height: 1.35; } -.present-shell--chat_monitor .present-region--monitor { - order: -1; -} -.present-shell--turn_panel { - display: grid; - grid-template-columns: minmax(0, 1fr); - gap: 10px; -} -.present-shell--chapter_reader .present-region--header .present-region-body { - font-size: 15px; +.present-chip-k { + color: var(--present-muted); font-weight: 600; } +.present-chip-v { + color: var(--present-ink); + font-weight: 500; +} +.present-region--monitor .present-region-body, +.present-region--aside .present-region-body, +.present-region--footer .present-region-body { + font-size: 12.5px; + line-height: 1.45; + color: var(--present-ink); + padding: 8px 10px; + border-radius: 8px; + border: 1px solid var(--present-line); + background: var(--present-soft); +} +.present-region--header .present-region-body { + font-size: 14px; + font-weight: 650; + letter-spacing: 0.01em; + line-height: 1.4; +} +/* P1:行动 */ +.present-action-row { + display: flex; + flex-wrap: wrap; + gap: 8px; +} +.present-action { + display: inline-flex; + align-items: center; + padding: 7px 12px; + border-radius: 8px; + border: 1px solid color-mix(in srgb, var(--present-accent) 35%, var(--present-line)); + background: color-mix(in srgb, var(--present-accent) 8%, #fff); + color: var(--present-ink); + font-size: 13px; + font-weight: 550; + line-height: 1.3; +} .present-actions { margin: 0; padding-left: 18px; line-height: 1.45; font-size: 13px; } -.present-tone--messenger .present-region--body { - border-left: 3px solid var(--border); - padding-left: 10px; +/* —— 各壳布局 —— */ +.present-shell--chat_monitor { + --present-accent: #0369a1; + background: + linear-gradient(180deg, #f0f9ff 0%, #fff 48px); } -.present-tone--book .present-region--body { - max-width: 42rem; +.present-shell--chat_monitor .present-stage { + padding: 14px 16px; + border-radius: 10px; + border: 1px solid var(--present-line); + background: #fff; } -.present-tone--terminal .present-region-body { +.present-shell--spotlight { + --present-accent: #b45309; + padding-top: 14px; +} +.present-shell--spotlight .present-stack--spotlight { + gap: 14px; +} +.present-shell--spotlight .present-stage--hero { + padding: 6px 4px 4px; +} +.present-shell--spotlight .present-region--monitor .present-region-title { + display: none; +} +.present-shell--turn_panel { + --present-accent: #047857; + background: linear-gradient(165deg, #f0fdf4 0%, #fff 28%); +} +.present-board-top { + display: flex; + flex-direction: column; + gap: 8px; +} +.present-board-mid { + display: grid; + grid-template-columns: minmax(0, 1.7fr) minmax(140px, 0.85fr); + gap: 12px; + align-items: start; +} +.present-board-side { + display: flex; + flex-direction: column; + gap: 8px; +} +.present-shell--turn_panel .present-stage { + padding: 16px; + border-radius: 10px; + border: 1px solid var(--present-line); + background: #fff; +} +.present-shell--split_board { + --present-accent: #4338ca; +} +.present-split { + display: grid; + grid-template-columns: minmax(0, 1.55fr) minmax(150px, 0.9fr); + gap: 14px 16px; + align-items: start; +} +.present-split-main { + display: flex; + flex-direction: column; + gap: 10px; + min-width: 0; +} +.present-split-side { + min-width: 0; + padding: 10px; + border-radius: 10px; + border: 1px solid var(--present-line); + background: var(--present-soft); +} +.present-split .present-region--actions { + grid-column: 1 / -1; +} +.present-shell--choice_dock { + --present-accent: #be123c; +} +.present-stack--dock { + gap: 10px; +} +.present-dock { + margin-top: 4px; + padding: 12px; + border-radius: 12px; + border: 1px solid color-mix(in srgb, var(--present-accent) 28%, var(--present-line)); + background: color-mix(in srgb, var(--present-accent) 6%, #fff); + /* 长正文时选项坞钉在视口底,读完仍看得到 */ + position: sticky; + bottom: 0; + z-index: 2; + box-shadow: 0 -8px 20px color-mix(in srgb, #fff 70%, transparent); +} +.present-dock .present-region-title { + margin-bottom: 2px; +} +.present-dock .present-action { + padding: 9px 14px; + font-size: 13.5px; + border-width: 1.5px; +} +.present-shell--chapter_reader { + --present-accent: #44403c; + background: + linear-gradient(90deg, transparent 0, transparent), + linear-gradient(180deg, #fafaf9 0%, #fff 40%); +} +.present-reader { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(120px, 0.42fr); + gap: 18px; + align-items: start; +} +.present-reader-main { + display: flex; + flex-direction: column; + gap: 12px; + min-width: 0; +} +.present-reader-aside { + font-size: 12px; + padding-top: 4px; + border-left: 1px solid var(--present-line); + padding-left: 14px; +} +.present-shell--chapter_reader .present-region--header .present-region-body { + font-family: var(--font-read); + font-size: 20px; + font-weight: 600; +} +.present-shell--prose .present-stage--read { + padding: 6px 2px; +} +.present-shell--prose .present-region--footer .present-region-body { + opacity: 0.85; +} +@media (max-width: 640px) { + .present-board-mid, + .present-split, + .present-reader { + grid-template-columns: 1fr; + } + .present-board-side, + .present-split-side, + .present-reader-aside { + position: static; + max-height: none; + } + .present-reader-aside { + border-left: none; + border-top: 1px solid var(--present-line); + padding-left: 0; + padding-top: 12px; + } +} +.present-tone--messenger .present-stage { + border-left: 3px solid #7dd3fc; + padding-left: 12px; +} +.present-tone--book .present-stage--read .present-region-body, +.present-tone--book .present-stage .present-region-body { + font-family: var(--font-read); + max-width: 38rem; +} +.present-tone--terminal { + --present-ink: #d6d3d1; + --present-muted: #a8a29e; + --present-line: #3f3f46; + --present-soft: #27272a; + background: #18181b; + color: #e7e5e4; +} +.present-tone--terminal .present-chip, +.present-tone--terminal .present-stage, +.present-tone--terminal .present-dock, +.present-tone--terminal .present-split-side { + background: #27272a; + border-color: #3f3f46; +} +.present-tone--terminal .present-region-body, +.present-tone--terminal .present-action { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; - font-size: 13px; +} +.present-tone--terminal .present-region--body .present-region-body { + font-size: calc(var(--present-body) - 0.5px); +} +.present-tone--terminal .present-action { + font-size: 13px; + color: #e7e5e4; + background: #3f3f46; }