壳完善
This commit is contained in:
24
docs/briefs/artifact-card-coverage.md
Normal file
24
docs/briefs/artifact-card-coverage.md
Normal file
@@ -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`(与创作验收卡不同层)。
|
||||
@@ -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(文档口径)
|
||||
|
||||
|
||||
@@ -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 落库。
|
||||
|
||||
|
||||
|
||||
68
docs/play-dm-auditor.md
Normal file
68
docs/play-dm-auditor.md
Normal file
@@ -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 传话」。
|
||||
@@ -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": ["<<<VARS>>>…<<<END>>>"]
|
||||
}
|
||||
"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 · 七壳;明确信息层级与主面积。
|
||||
|
||||
@@ -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` | **已写**:开场守版式+同真相初值 |
|
||||
|
||||
共用收成(池内保留,按需):
|
||||
|
||||
@@ -79,8 +79,8 @@ modules:
|
||||
- id: narrative
|
||||
name: 叙事指南与故事推进
|
||||
declaration: >-
|
||||
钉转述写法与故事推进:纲领、遣词造句、笔墨焦点、禁忌/不偏好、示范与备用、
|
||||
推进决策(可含变量钩子);美学写感觉,本步写怎么写、往哪推
|
||||
一次钉清写法与推进(遣词/笔墨/禁忌/示范 + 故事如何往前);
|
||||
整份双挂转述与主世界层,不为省 token 拆投影
|
||||
artifact: 设计.叙事指南与故事推进
|
||||
|
||||
# —— 机制与数据 ——
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 给两边裁成两套残缺稿。
|
||||
```
|
||||
|
||||
@@ -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 工程?
|
||||
```
|
||||
|
||||
@@ -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取当前档的哪些键;无则 []"],
|
||||
|
||||
@@ -121,6 +121,7 @@ play
|
||||
5. reject → 收修订 → 重跑同一 worker(含修订流程 = 再调味)
|
||||
6. 终稿(含 `设计.worker集`)已 accept 且需开局 → `opening-generator`
|
||||
7. **play**:按 `play_slots` 序 `auditor → perspective? → gm → narrator`;`pendingSideEffectWorkers` 优先
|
||||
(旁观与主世界层怎么配合:见 `docs/play-dm-auditor.md`——旁观看门补表,DM 断事,勿默认多轮传话)
|
||||
|
||||
## 禁用行为
|
||||
|
||||
|
||||
@@ -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。
|
||||
|
||||
@@ -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"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ? `<div class="af-mosaic">${parts.join("")}</div>` : "";
|
||||
}
|
||||
|
||||
/** 叙事指南 · 一份全文结构化卡(不强调双投影裁剪) */
|
||||
function renderNarrativeBodyHtml(body) {
|
||||
const order = [
|
||||
"依据的体验",
|
||||
"叙事纲领",
|
||||
"风格与遣词",
|
||||
"笔墨焦点",
|
||||
"禁忌与不偏好",
|
||||
"情境备用",
|
||||
"推进与决策",
|
||||
"内容与表达",
|
||||
"风格与写法",
|
||||
];
|
||||
const used = new Set();
|
||||
const parts = [];
|
||||
// 若仍是旧的「风格与写法 / 推进与决策」两大包,整块展示
|
||||
if (body.风格与写法 != null || body.推进与决策 != null) {
|
||||
if (Array.isArray(body.依据的体验) && body.依据的体验.length) {
|
||||
parts.push(
|
||||
`<section class="af-panel"><h4>依据的体验</h4>${skillProseList(body.依据的体验)}</section>`,
|
||||
);
|
||||
used.add("依据的体验");
|
||||
}
|
||||
if (body.风格与写法 != null) {
|
||||
parts.push(
|
||||
`<section class="af-panel af-panel-wide"><h4>风格与写法</h4>${renderStructuredValueHtml(body.风格与写法, 0)}</section>`,
|
||||
);
|
||||
used.add("风格与写法");
|
||||
}
|
||||
if (body.推进与决策 != null) {
|
||||
parts.push(
|
||||
`<section class="af-panel af-panel-wide"><h4>推进与决策</h4>${renderStructuredValueHtml(body.推进与决策, 0)}</section>`,
|
||||
);
|
||||
used.add("推进与决策");
|
||||
}
|
||||
} else {
|
||||
for (const key of order) {
|
||||
if (body[key] == null) continue;
|
||||
used.add(key);
|
||||
const wide = key === "风格与遣词" || key === "推进与决策";
|
||||
parts.push(
|
||||
`<section class="af-panel${wide ? " af-panel-wide" : ""}"><h4>${esc(key)}</h4>${renderStructuredValueHtml(body[key], 0)}</section>`,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const [k, v] of Object.entries(body)) {
|
||||
if (used.has(k) || v == null || v === "") continue;
|
||||
parts.push(
|
||||
`<section class="af-panel"><h4>${esc(k)}</h4>${renderStructuredValueHtml(v, 0)}</section>`,
|
||||
);
|
||||
}
|
||||
return parts.length ? `<div class="af-mosaic af-narrative">${parts.join("")}</div>` : "";
|
||||
}
|
||||
|
||||
function skillSection(title, inner, cls = "") {
|
||||
if (!inner) return "";
|
||||
return `<section class="skill-card-section${cls ? ` ${cls}` : ""}"><h4>${esc(title)}</h4>${inner}</section>`;
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
</div>
|
||||
<div class="main-head-right">
|
||||
<a class="link" href="/modules.html">技能</a>
|
||||
<a class="link" href="/shells.html">呈现壳</a>
|
||||
<a class="link" href="/stats.html">Token</a>
|
||||
<a class="link" href="/settings.html">设置</a>
|
||||
<button type="button" class="btn-sm" id="btn-save-instance" hidden>保存定稿</button>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<nav class="st-nav" aria-label="主导航">
|
||||
<a href="/">对话</a>
|
||||
<a href="/modules.html" aria-current="page">技能</a>
|
||||
<a href="/shells.html">呈现壳</a>
|
||||
<a href="/stats.html">Token</a>
|
||||
<a href="/settings.html">设置</a>
|
||||
</nav>
|
||||
|
||||
@@ -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 `<div class="present-chips" role="list">${entries
|
||||
.map(
|
||||
([k, v]) =>
|
||||
`<span class="present-chip" role="listitem"><span class="present-chip-k">${esc(k)}</span><span class="present-chip-v">${esc(
|
||||
typeof v === "string" ? v : JSON.stringify(v),
|
||||
)}</span></span>`,
|
||||
)
|
||||
.join("")}</div>`;
|
||||
}
|
||||
|
||||
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 `<div class="present-region-body">${esc(text).replace(/\n/g, "<br>")}</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
? `<h4 class="present-region-title">${esc(title)}</h4>`
|
||||
: "";
|
||||
|
||||
return `
|
||||
<section class="present-region present-region--${esc(region)}" data-region="${esc(region)}">
|
||||
${titleHtml}
|
||||
${regionInnerHtml(region, displayRaw, esc)}
|
||||
</section>`;
|
||||
}
|
||||
|
||||
function renderActionsHtml(actions, esc) {
|
||||
if (!actions.length) return "";
|
||||
return `
|
||||
<section class="present-region present-region--actions" data-region="actions">
|
||||
<h4 class="present-region-title">建议行动</h4>
|
||||
<div class="present-action-row" role="list">${actions
|
||||
.map(
|
||||
(a) =>
|
||||
`<span class="present-action" role="listitem">${esc(a)}</span>`,
|
||||
)
|
||||
.join("")}</div>
|
||||
</section>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(`
|
||||
<section class="present-region present-region--${esc(region)}" data-region="${esc(region)}">
|
||||
${isBody && shell === "prose" ? "" : `<h4 class="present-region-title">${esc(title)}</h4>`}
|
||||
<div class="present-region-body">${esc(text).replace(/\n/g, "<br>")}</div>
|
||||
</section>`);
|
||||
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(`
|
||||
<section class="present-region present-region--actions" data-region="actions">
|
||||
<h4 class="present-region-title">建议行动</h4>
|
||||
<ul class="present-actions">${actions.map((a) => `<li>${esc(a)}</li>`).join("")}</ul>
|
||||
</section>`);
|
||||
const actionsHtml = renderActionsHtml(actions, esc);
|
||||
|
||||
let layoutHtml = "";
|
||||
switch (shell) {
|
||||
case "chat_monitor":
|
||||
layoutHtml = `
|
||||
<div class="present-stack">
|
||||
${by.monitor || ""}
|
||||
<div class="present-stage">${by.body || ""}</div>
|
||||
${by.footer || ""}
|
||||
${actionsHtml}
|
||||
</div>`;
|
||||
break;
|
||||
case "spotlight":
|
||||
layoutHtml = `
|
||||
<div class="present-stack present-stack--spotlight">
|
||||
${by.monitor || ""}
|
||||
<div class="present-stage present-stage--hero">${by.body || ""}</div>
|
||||
${by.footer || ""}
|
||||
${actionsHtml}
|
||||
</div>`;
|
||||
break;
|
||||
case "turn_panel":
|
||||
layoutHtml = `
|
||||
<div class="present-board">
|
||||
<div class="present-board-top">${by.monitor || ""}${by.header || ""}</div>
|
||||
<div class="present-board-mid">
|
||||
<div class="present-stage">${by.body || ""}</div>
|
||||
<div class="present-board-side">${by.aside || ""}${by.footer || ""}</div>
|
||||
</div>
|
||||
${actionsHtml}
|
||||
</div>`;
|
||||
break;
|
||||
case "split_board":
|
||||
layoutHtml = `
|
||||
<div class="present-split">
|
||||
<div class="present-split-main">
|
||||
${by.header || ""}
|
||||
<div class="present-stage">${by.body || ""}</div>
|
||||
${by.footer || ""}
|
||||
</div>
|
||||
<div class="present-split-side">${by.aside || ""}</div>
|
||||
${actionsHtml}
|
||||
</div>`;
|
||||
break;
|
||||
case "choice_dock":
|
||||
layoutHtml = `
|
||||
<div class="present-stack present-stack--dock">
|
||||
${by.monitor || ""}
|
||||
<div class="present-stage">${by.body || ""}</div>
|
||||
${by.footer || ""}
|
||||
<div class="present-dock">${actionsHtml}</div>
|
||||
</div>`;
|
||||
break;
|
||||
case "chapter_reader":
|
||||
layoutHtml = `
|
||||
<div class="present-reader">
|
||||
<div class="present-reader-main">
|
||||
${by.header || ""}
|
||||
<div class="present-stage present-stage--read">${by.body || ""}</div>
|
||||
${by.footer || ""}
|
||||
</div>
|
||||
<div class="present-reader-aside">${by.aside || ""}</div>
|
||||
</div>`;
|
||||
break;
|
||||
case "prose":
|
||||
default:
|
||||
layoutHtml = `
|
||||
<div class="present-stack present-stack--prose">
|
||||
<div class="present-stage present-stage--read">${by.body || ""}</div>
|
||||
${by.footer || ""}
|
||||
${actionsHtml}
|
||||
</div>`;
|
||||
break;
|
||||
}
|
||||
|
||||
const shellLabel = SHELL_LABELS[shell] || shell;
|
||||
return `
|
||||
<div class="present-shell present-shell--${esc(shell)} present-tone--${esc(tone)}" data-shell="${esc(shell)}">
|
||||
<div class="present-shell-badge" title="呈现壳">${esc(shellLabel)}</div>
|
||||
${parts.join("")}
|
||||
${layoutHtml}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
export { SHELL_LABELS, REGION_LABELS, isPresentShellId };
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<nav class="st-nav" aria-label="主导航">
|
||||
<a href="/">对话</a>
|
||||
<a href="/modules.html">技能</a>
|
||||
<a href="/shells.html">呈现壳</a>
|
||||
<a href="/stats.html">Token</a>
|
||||
<a href="/settings.html" aria-current="page">设置</a>
|
||||
</nav>
|
||||
|
||||
173
web/shells.css
Normal file
173
web/shells.css
Normal file
@@ -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%;
|
||||
}
|
||||
}
|
||||
74
web/shells.html
Normal file
74
web/shells.html
Normal file
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>呈现壳预览 · Writing Agent</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&family=Noto+Serif+SC:wght@500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="stylesheet" href="/settings.css" />
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
<link rel="stylesheet" href="/shells.css" />
|
||||
</head>
|
||||
<body class="settings-page shells-page">
|
||||
<header class="st-top">
|
||||
<a class="st-brand" href="/">Writing Agent</a>
|
||||
<nav class="st-nav" aria-label="主导航">
|
||||
<a href="/">对话</a>
|
||||
<a href="/modules.html">技能</a>
|
||||
<a href="/shells.html" aria-current="page">呈现壳</a>
|
||||
<a href="/stats.html">Token</a>
|
||||
<a href="/settings.html">设置</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="st-shell shells-shell">
|
||||
<aside class="st-rail" aria-label="壳列表">
|
||||
<p class="st-rail-label">呈现壳</p>
|
||||
<nav class="shells-list" id="shell-list" role="tablist"></nav>
|
||||
<p class="st-rail-label shells-tone-label">装饰语气</p>
|
||||
<div class="shells-tones" id="tone-list" role="group" aria-label="tone_chrome"></div>
|
||||
<p class="st-rail-label shells-tone-label">正文长度</p>
|
||||
<div class="shells-tones" id="length-list" role="group" aria-label="body length"></div>
|
||||
<p class="st-rail-note">
|
||||
正文按 600~3000 字设计:随字数撑高、页面滚动;侧栏/选择坞钉住。创作只改内容绑定。
|
||||
</p>
|
||||
</aside>
|
||||
|
||||
<main class="st-main">
|
||||
<header class="st-main-head">
|
||||
<div>
|
||||
<h1 id="panel-title">呈现壳预览</h1>
|
||||
<p class="st-sub" id="panel-subtitle">用示例数据看布局与可调项</p>
|
||||
</div>
|
||||
</header>
|
||||
<div class="st-body shells-body">
|
||||
<section class="shells-meta" id="shell-meta"></section>
|
||||
<section class="shells-preview-wrap">
|
||||
<h2 class="shells-preview-label">预览</h2>
|
||||
<div id="shell-preview" class="shells-preview"></div>
|
||||
</section>
|
||||
<section class="shells-tweaks">
|
||||
<h2>本页可调(对应创作「微调轴」)</h2>
|
||||
<div class="shells-tweak-grid" id="tweak-controls"></div>
|
||||
</section>
|
||||
<section class="shells-help">
|
||||
<h2>设计意图</h2>
|
||||
<ul>
|
||||
<li>正文默认装得下单轮 <strong>600~3000 字</strong>:区域随内容长高,由页面/消息区滚动,不塞进固定小框。</li>
|
||||
<li>双栏壳:侧栏 sticky;选择坞:选项区 sticky 贴底——长文读完控件还在。</li>
|
||||
<li>壳差在「谁占主柱」;可改的是字段/显示名/行动开关/tone,不是重画布局。</li>
|
||||
</ul>
|
||||
<p class="muted">说明文档:<code>docs/play-presentation-shells.md</code></p>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/shells.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
358
web/shells.js
Normal file
358
web/shells.js
Normal file
@@ -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, ">")
|
||||
.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 = `
|
||||
<p><strong>${esc(info.name)}</strong>(<code>${esc(state.shell)}</code>)</p>
|
||||
<p>${esc(info.blurb)}</p>
|
||||
<p class="shells-density"><span>长文策略</span>${esc(info.density)} · 当前试读 <strong>${packet._chars}</strong> 字</p>
|
||||
<p class="muted">适用:${esc(info.scenes)}。请用上方「正文长度」切到长文,确认侧栏/选项是否仍可用。</p>
|
||||
`;
|
||||
|
||||
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 `<button type="button" class="st-rail-item" data-shell="${esc(id)}" role="tab">
|
||||
${esc(info?.name || id)}
|
||||
</button>`;
|
||||
}).join("");
|
||||
|
||||
document.getElementById("tone-list").innerHTML = TONES.map(
|
||||
(t) =>
|
||||
`<button type="button" class="shells-tone-chip" data-tone="${esc(t.id)}">${esc(t.label)}</button>`,
|
||||
).join("");
|
||||
|
||||
document.getElementById("length-list").innerHTML = LENGTHS.map(
|
||||
(t) =>
|
||||
`<button type="button" class="shells-tone-chip" data-length="${esc(t.id)}">${esc(t.label)}</button>`,
|
||||
).join("");
|
||||
|
||||
document.getElementById("tweak-controls").innerHTML = `
|
||||
<label class="field">
|
||||
<input type="checkbox" id="tweak-show-actions" />
|
||||
显示「建议行动」(对应微调轴 show_suggested_actions)
|
||||
</label>
|
||||
<label class="field">
|
||||
正文区显示名(block_labels.body)
|
||||
<input type="text" id="tweak-label-body" placeholder="例如:短信记录 / 场面 / 本章" />
|
||||
</label>
|
||||
<label class="field">
|
||||
监控区显示名(block_labels.monitor)
|
||||
<input type="text" id="tweak-label-monitor" placeholder="例如:状态 / 今日" />
|
||||
</label>
|
||||
`;
|
||||
|
||||
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();
|
||||
@@ -67,7 +67,7 @@
|
||||
<body>
|
||||
<div class="stats-page">
|
||||
<header>
|
||||
<p><a href="/">← 对话</a> · <a href="/modules.html">技能</a> · <a href="/settings.html">设置</a></p>
|
||||
<p><a href="/">← 对话</a> · <a href="/modules.html">技能</a> · <a href="/shells.html">呈现壳</a> · <a href="/settings.html">设置</a></p>
|
||||
<h1>Token 统计</h1>
|
||||
<p class="stats-muted">按时间戳筛选调用记录(存于本地 stats/*.jsonl)</p>
|
||||
</header>
|
||||
|
||||
401
web/styles.css
401
web/styles.css
@@ -1,4 +1,4 @@
|
||||
/* Functional layout 鈥<EFBFBD> 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; /* 娴忚<EFBFBD>鍣ㄥ眰姘镐笉鍑虹幇椤甸潰婊氬姩鏉<EFBFBD> */
|
||||
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 鎸夊唴瀹硅嚜閫傚簲锛涗腑闂村尯鍚冩帀鍓╀綑楂樺害锛屾暣椤甸攣鍦ㄨ<EFBFBD>鍙e唴 */
|
||||
/* 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 鈥<EFBFBD> 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 鈥<EFBFBD> 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 鈥<EFBFBD> SillyTavern 寮忓<EFBFBD>璇濇皵娉<EFBFBD> */
|
||||
/* Feed â SillyTavern å¼å¯¹è¯æ°æ³¡ */
|
||||
.message-feed {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
@@ -557,7 +557,7 @@ body.rail-collapsed .message-feed {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
/* 涓诲<EFBFBD>璇濋獙鏀跺崱锛氬悆婊′腑闂存爮 */
|
||||
/* 主对è¯éªæ¶å¡ï¼å满ä¸é´æ */
|
||||
.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; }
|
||||
|
||||
/* 浜х墿绱у噾鍗★細妯<EFBFBD>悜涓昏酱锛岄<EFBFBD>瑙堜竴琛<EFBFBD> */
|
||||
/* 产ç©ç´§åå¡ï¼æ¨ªå主轴ï¼é¢è§ä¸è¡ */
|
||||
.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 / 鐡风爾缃戞牸锛屽悆婊℃í鍚戝<E98D9A>搴<EFBFBD> */
|
||||
/* å±å¼åºï¼mosaic / ç·ç ç½æ ¼ï¼å满横å宽度 */
|
||||
.af-mosaic {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@@ -818,7 +818,7 @@ body.rail-collapsed .af-mosaic {
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
/* 鎶€鑳戒笓鐢ㄦ<EFBFBD>鏂囧崱 */
|
||||
/* æè½ä¸ç¨æ£æå¡ */
|
||||
.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 寮忚<EFBFBD>闂<EFBFBD>崱锛氳创鍦ㄦ秷鎭<EFBFBD>祦涓嬫柟锛屽崰婊″崗璋冨垪瀹藉害锛屼笉閬<EFBFBD>洊鍘嗗彶 */
|
||||
/* 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; }
|
||||
/* 寮€鍚<EFBFBD>椂鍗<EFBFBD> feed 鍓╀綑楂樺害鐨勪笂闄愶紝涓嶆妸鏁撮〉鎾戝嚭瑙嗗彛锛涘嬁涓<E5AC81> [hidden] 骞跺瓨鎶<EFBFBD> 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;
|
||||
}
|
||||
/* 鎮<EFBFBD>诞鎺т欢灞傦細涓嶅崰鏂囨。娴侀珮搴<EFBFBD> */
|
||||
/* æ¬æµ®æ§ä»¶å±ï¼ä¸å ææ¡£æµé«åº¦ */
|
||||
.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; /* 浠呭崱鍐呮粴鍔<EFBFBD>紱椤甸潰鏈<EFBFBD>韩涓嶆粴 */
|
||||
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; }
|
||||
/* 浜х墿缁撴瀯鍖栧睍绀<EFBFBD> 鈥<> 瀵归綈 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 鈥<EFBFBD> 楂樺害闅忓唴瀹癸紝浣嗘湁涓婇檺锛岄伩鍏嶆尋鐖嗚<E99096>鍙<EFBFBD> */
|
||||
/* 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.v1(P2 最小) —— */
|
||||
/* —— 游玩呈现壳 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user