@xia-sc/dsh-cc-studio 0.3.7 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -11
- package/lib/client.js +276 -144
- package/package.json +17 -5
package/README.md
CHANGED
|
@@ -15,7 +15,8 @@ DSH(DeepSeek Harness)插件:输入框上方的胶囊 → 全屏融合工
|
|
|
15
15
|
| 项目 | 值 |
|
|
16
16
|
| --- | --- |
|
|
17
17
|
| 插件包名 | `@xia-sc/dsh-cc-studio` |
|
|
18
|
-
| 当前版本 | `0.
|
|
18
|
+
| 当前版本 | `0.4.1` |
|
|
19
|
+
| 兼容与权限 | DSH `>=0.1.5-rc.1`、Node `>=22`、**零运行依赖**;权限边界见 [兼容与权限](#兼容与权限) |
|
|
19
20
|
| 宿主 RPC | `/dsh-cc-studio-rpc`(自带路由,适配 dsh ≥ `0.1.5-rc.1`;已在 `0.1.6-alpha.2` / `0.1.7-alpha.1` / `0.1.7-rc.1` 实测) |
|
|
20
21
|
| CC 预设 | `presets/cc.patch.yml`(dsh ≥ `0.1.7-alpha.1`,组合声明行)/`<DSH_HOME>/.agent-presets/cc`(≤ `0.1.6-alpha.2`,目录形态) |
|
|
21
22
|
| 客户端挂载点 | `conversation.input.dock`(胶囊)+ `shell.overlay`(工坊)+ `settings.section` |
|
|
@@ -23,13 +24,15 @@ DSH(DeepSeek Harness)插件:输入框上方的胶囊 → 全屏融合工
|
|
|
23
24
|
|
|
24
25
|
## 安装
|
|
25
26
|
|
|
27
|
+
本插件同时支持 **`dsh web`(CLI 起的 Web GUI)** 与 **DSH 桌面版(DeepSeek Harness Desktop)**:两者是同一个 dsh 宿主,插件都装在 profile 的 `dsh.profile.bundles` 里,加载方式与全部功能完全一致。下面以 `web` profile 为例;桌面版的差异只有 profile 名与「重启应用」这一步,见下文 **[7. 桌面版](#7-桌面版deepseek-harness-desktop)**。
|
|
28
|
+
|
|
26
29
|
### 1. 从 npm 安装(推荐)
|
|
27
30
|
|
|
28
31
|
```bash
|
|
29
32
|
dsh plugin --profile web add @xia-sc/dsh-cc-studio
|
|
30
33
|
|
|
31
34
|
# 锁定版本
|
|
32
|
-
dsh plugin --profile web add @xia-sc/dsh-cc-studio@0.
|
|
35
|
+
dsh plugin --profile web add @xia-sc/dsh-cc-studio@0.4.0
|
|
33
36
|
```
|
|
34
37
|
|
|
35
38
|
**或从 GitHub 源安装**:
|
|
@@ -218,6 +221,22 @@ dsh plugin --profile web add @xia-sc/dsh-cc-studio
|
|
|
218
221
|
| 校验报错 | 必填 `spec: chara_card_v3` / `spec_version: 3.0` / `group_only_greetings`;主图标需唯一;正则需合法 |
|
|
219
222
|
| 重启后浏览器报 `Failed to load plugins` / `web boot: 1 entry did not activate` / `import failed` | 包名迁移后**没有按新名重装**:profile 里的安装身份(`dsh.profile.bundles` 那行与 `dependencies` 里的 `link:`)还是旧名 `@dsh-plugins/dsh-cc-studio`,而包内 `package.json` 与 `lib/client.js` 已改叫 `@xia-sc/dsh-cc-studio`,客户端资源就解析不上(宿主照样起,只有浏览器那一半挂)。看一眼 `~/.dsh/profiles/web/node_modules/` 下的目录名是否等于 `package.json` 的 `name` 即可确认;修法是按新名重装(见「5. 更新与卸载」的迁移命令)→ 重启 `dsh web` → **硬刷新**(`Ctrl+Shift+R`;旧页面的引导图是缓存,普通刷新会复现同一句错) |
|
|
220
223
|
|
|
224
|
+
### 7. 桌面版(DeepSeek Harness Desktop)
|
|
225
|
+
|
|
226
|
+
**桌面版就是同一个 dsh 宿主**(Electron 内嵌),插件照样装在 profile 的 `dsh.profile.bundles` 里,所以本插件**不需要任何额外适配**:三个客户端挂载点(胶囊 / 工坊 / 设置页)、`CC 模式` 预设、RPC 通道、草稿与角色卡落盘,都与 `dsh web` 一模一样(0.4.0 在桌面版 profile 上实测跑通全流程:胶囊 → 工坊 → 导入导出 → 草稿落盘)。
|
|
227
|
+
|
|
228
|
+
差异只有「怎么装」和「怎么生效」两步:
|
|
229
|
+
|
|
230
|
+
| | `dsh web`(CLI) | 桌面版 |
|
|
231
|
+
| --- | --- | --- |
|
|
232
|
+
| 安装 | `dsh plugin --profile web add @xia-sc/dsh-cc-studio` | 应用内左侧「插件」面板安装 `@xia-sc/dsh-cc-studio`;装了 CLI 的话也可以用 `dsh plugin --profile desktop add @xia-sc/dsh-cc-studio` |
|
|
233
|
+
| profile | 一般是 `web` | 一般是 `desktop`(`~/.dsh/profiles/desktop`),以应用内显示为准 |
|
|
234
|
+
| 宿主 / 插件改动 | 重启 `dsh web` | 退出并重新打开桌面应用 |
|
|
235
|
+
| 只改客户端(`lib/client.js`) | 刷新页面 | 在应用窗口里刷新(找不到刷新入口就重启应用) |
|
|
236
|
+
| 数据位置 | `<DSH_HOME>/cc-drafts/`、`<DSH_HOME>/cc-library/`(默认 `~/.dsh`) | 同上,桌面版共用同一套 `DSH_HOME` 与落盘目录 |
|
|
237
|
+
|
|
238
|
+
> 桌面版的应用窗口本身就是它的 Web GUI(端口由应用分配),所以「重启应用」≈ 重启宿主、「刷新窗口」≈ 刷新页面,排查问题时两者可以混用。
|
|
239
|
+
|
|
221
240
|
## 快速上手
|
|
222
241
|
|
|
223
242
|
1. 新建会话,会话模式选 **CC 模式**(胶囊随即出现在输入框上方)。
|
|
@@ -230,12 +249,13 @@ dsh plugin --profile web add @xia-sc/dsh-cc-studio
|
|
|
230
249
|
|
|
231
250
|
### 融合工坊(客户端)
|
|
232
251
|
|
|
233
|
-
- **布局**:输入框上方胶囊(CC 模式自动出现)→ `shell.overlay` 全屏工坊,左侧导航
|
|
252
|
+
- **布局**:输入框上方胶囊(CC 模式自动出现)→ `shell.overlay` 全屏工坊,左侧导航 **3 页**(**5维世界观 / 角色细化 / 世界书**)+ 左下角「**校验导出**」入口 / 自适应主区 / 268px 可折叠已存角色侧栏 / 主区底部两块默认收起的阅读辅助(实时 `card.json` 预览、CBS 速查)。深浅色自适应(DSW Token + 品牌紫 `#7c5cff` 固定)。
|
|
253
|
+
- **导出入口只有两处**(0.4.1 归一):左下角「校验导出」与已存角色卡片上的「导出」,两者打开的是同一个**导出页**(`JSON / PNG / 写入PNG / CHARX / 校验`)——导出页导出的始终是当前工坊里的角色卡(标题后面就写着卡名,没名字时显示「未命名」),要导某张已存卡请先「载入」;顶部工具条不再有「导出 JSON」。卡片上的「导出」点下去会立刻变成「已切到 ✓」,若那张卡还没载入,导出页顶部会提示「要导它请先点它那行的『载入』」。
|
|
234
254
|
- **风格标签**:在「角色细化」中以逗号分隔编辑 `tags`,实时写回 `data.tags`。
|
|
235
|
-
- **5 维世界观**:年表 / 势力 / 地理 / 力量体系 / 日常 → `cc_patch_world(autoLorebook=true)` 自动生成带 `@@position / @@depth / @@activate` 的 Lorebook 条目(至少 1 条 `constant`
|
|
236
|
-
- **全量长文本大框编辑**:`description / personality / scenario / system_prompt / post_history_instructions / first_mes / alternate_greetings / group_only_greetings / mes_example / creator_notes`
|
|
237
|
-
- **已存角色侧栏(ID 化 CRUD)**:
|
|
238
|
-
-
|
|
255
|
+
- **5 维世界观**:年表 / 势力 / 地理 / 力量体系 / 日常 → `cc_patch_world(autoLorebook=true)` 自动生成带 `@@position / @@depth / @@activate` 的 Lorebook 条目(至少 1 条 `constant` 常驻;再次调用自动覆盖旧自动条目、保留手动条目)。五维默认是**文档式阅读排版**(无边框 textarea 按内容自动撑高,点进去就能改),写长文可点右上「⛶」开 720px 大框(带打开时快照,「取消」真回滚)。
|
|
256
|
+
- **全量长文本大框编辑**:`description / personality / scenario / system_prompt / post_history_instructions / first_mes / alternate_greetings / group_only_greetings / mes_example / creator_notes` 都可点「⛶」开 720px 大框,实时同步,解决多行长文难预览/编辑的问题。
|
|
257
|
+
- **已存角色侧栏(ID 化 CRUD)**:268px 可折叠(收成 40px 竖条),高亮当前载入卡(紫框 + 顶部 `已载入 ID:xxxx`,显示 ID 前 8 位),`★ 保存当前` 在已载入 ID 时原地覆盖、`+ 另存为新` 强制新建、`✎ 重命名` / `+ 新建`;搜索 / 载入 / 导出 / 删除落盘 `~/.dsh/cc-library/<id>.json`。
|
|
258
|
+
- **导入导出**:侧栏 `⬆ 导入 JSON/PNG/CHARX` 自动识别容器;导出页 `⬇ JSON` / `⬇ PNG`(1×1 占位图)/ `⬆ 写入 PNG`(写入你上传的任意 PNG,自动剥离旧 `ccv3/chara` 块并 `CRC32` 重算)/ `⬇ CHARX`(打包 `card.json`)。
|
|
239
259
|
- **中英双语**:完整 `zh / en` 词表(`locale: dshCcStudio`),跟随全局 `设置 → 通用 → 语言` 自动切换(胶囊/工坊/设置即时刷新,插件内无手动开关)。
|
|
240
260
|
- **深浅色自适应**:全量使用 `var(--dsw-alias-bg-* / border-l1/l2 / label-primary/secondary)`,主按钮/选中态固定品牌紫,刷新即生效。
|
|
241
261
|
|
|
@@ -269,12 +289,16 @@ dsh-cc-studio/
|
|
|
269
289
|
│ └── cc/ # CC 预设目录模板(dsh ≤ 0.1.6-alpha.2,插件挂载时安装到用户根)
|
|
270
290
|
│ ├── preset.yml
|
|
271
291
|
│ └── agent.cordis.yml
|
|
272
|
-
├── tests/ #
|
|
292
|
+
├── tests/ # 10 个测试文件、380 项断言(不随包发布)
|
|
273
293
|
│ ├── rpc-channel.test.mjs # host 半 RPC 通道回归(假 ctx + 真实 http,45 项断言)
|
|
274
|
-
│ ├── preset-install.test.mjs # 预设安装决策 + native 让位 + 两份清单防漂移 + 行契约,
|
|
275
|
-
│ ├── cc-detection.test.mjs # CC 模式探测的源码级守卫,
|
|
294
|
+
│ ├── preset-install.test.mjs # 预设安装决策 + native 让位 + 两份清单防漂移 + 行契约,79 项断言
|
|
295
|
+
│ ├── cc-detection.test.mjs # CC 模式探测的源码级守卫,51 项断言
|
|
276
296
|
│ ├── client-greetings.test.mjs # 客户端问候语与 i18n 守卫,62 项断言
|
|
277
297
|
│ ├── draft-slot-sync.test.mjs # 草稿槽行为级回归(假 React/slot/RPC harness),18 项断言
|
|
298
|
+
│ ├── nav-highlight.test.mjs # 工坊步骤导航三态回归(选中/键盘焦点/悬停互斥),23 项断言
|
|
299
|
+
│ ├── clear-card.test.mjs # 「清空当前角色卡」二次确认回归,20 项断言
|
|
300
|
+
│ ├── export-entry.test.mjs # 导出入口归一回归(三处导出收成两处),31 项断言
|
|
301
|
+
│ ├── close-button.test.mjs # 工坊关闭按钮 ✕(悬停红底)回归,14 项断言
|
|
278
302
|
│ └── data-root.test.mjs # 落盘根:DSH_HOME 优先 + 读时回退 ~/.dsh,37 项断言
|
|
279
303
|
├── CHANGELOG.md # 完整版本历史
|
|
280
304
|
├── README.md # 中文(默认)
|
|
@@ -286,7 +310,7 @@ dsh-cc-studio/
|
|
|
286
310
|
## 开发与测试
|
|
287
311
|
|
|
288
312
|
```bash
|
|
289
|
-
npm test #
|
|
313
|
+
npm test # 10 个文件、380 项断言(十个 node 脚本串联)
|
|
290
314
|
node tests/rpc-channel.test.mjs # 只跑 host 半 RPC 通道回归(45 项断言)
|
|
291
315
|
```
|
|
292
316
|
|
|
@@ -297,6 +321,28 @@ node tests/rpc-channel.test.mjs # 只跑 host 半 RPC 通道回归(
|
|
|
297
321
|
|
|
298
322
|
深浅色通过 `var(--dsw-alias-*)` 自动适配(`body[data-ds-dark-theme]`),主操作固定 `#7c5cff` 保证对比度。切换路径:设置 → 外观 → 浅色/深色/跟随系统,刷新后工坊立即生效。
|
|
299
323
|
|
|
324
|
+
## 兼容与权限
|
|
325
|
+
|
|
326
|
+
上架元数据(`dsh.compatibility` / `engines` / 运行依赖)与权限边界都集中在这里;DSH STORE 的固定源审查读的也是这几处。
|
|
327
|
+
|
|
328
|
+
| 项 | 值 |
|
|
329
|
+
| --- | --- |
|
|
330
|
+
| DSH 兼容范围 | `>=0.1.5-rc.1`(`package.json` 的 `dsh.compatibility.dsh`) |
|
|
331
|
+
| 逐版本证据 | `dsh.compatibility.dshReleases` 里 `0.1.5-rc.1` / `0.1.5-rc.2` / `0.1.6-alpha.1` / `0.1.6-alpha.2` / `0.1.7-alpha.1` / `0.1.7-rc.1` / **`0.2.0-rc.2`** 为 `compatible`。**没实测过的版本不写**(`0.1.7-alpha.2`、`0.1.7-rc.2`、`0.2.0-rc.1`、`0.2.1-alpha.1` 保持未声明,商城显示为未知),而不是先写 `compatible` 再补测 |
|
|
332
|
+
| Node.js | `engines.node: ">=22"`(实测运行时 Node v26.9.0) |
|
|
333
|
+
| 宿主形态 | `dsh web`(CLI)与 **DSH 桌面版(DeepSeek Harness Desktop)** 都是同一套宿主 + profile bundles 机制,**均支持**(差异与安装方式见 [7. 桌面版](#7-桌面版deepseek-harness-desktop)) |
|
|
334
|
+
| 客户端平台 | `dsh.client.platform: "web"` —— 这是**浏览器半**的平台标识,与 dsh 的 profile 名(`web` / `desktop` …)不是一回事;桌面版与 CLI 加载的是同一份客户端 bundle |
|
|
335
|
+
| 运行依赖 | **无**。`lib/*.js` 只 `import` `node:` 内建与相对路径;随包的补丁 / 预设是静态 YAML,没有安装期脚本 |
|
|
336
|
+
| 生命周期脚本 | **无** `preinstall` / `install` / `postinstall` / `prepare`(只有 `test` 与 `prepublishOnly`,只在本仓库跑,不发给安装端) |
|
|
337
|
+
| 权限 · 文件 | 读 `<DSH_HOME>`(含旧根 `~/.dsh`)下自己的三个目录:`cc-drafts/`、`cc-library/`、`.agent-presets/cc/`(后者只在 dsh ≤ `0.1.6-alpha.2` 上写);另读自身 `package.json`。不写宿主配置,不碰别的插件的数据 |
|
|
338
|
+
| 权限 · 网络 | 无。宿主半只在宿主 `webServer` 上注册 `POST /dsh-cc-studio-rpc/<endpoint>`;浏览器半只打同源 RPC |
|
|
339
|
+
| 权限 · 命令 | 无。不起子进程、不执行 shell |
|
|
340
|
+
| 权限 · 凭据 | 不读也不存任何 token / 密钥 / cookie。只读两个环境变量:`DSH_HOME`、`DSH_CC_STUDIO_PRESET_INSTALL`。宿主 HTTP 路由复用宿主自己的 cookie 围栏(`connection.requestRejection`),插件不解析凭据 |
|
|
341
|
+
|
|
342
|
+
> **为什么商城不会给它「自动通过」**:DSH STORE 的自动低风险通道(`source-verified`)要求运行时代码**完全不出现**文件、网络、命令、凭据四类信号;而本插件的核心功能就是角色卡与草稿落盘(文件信号),并且要读 `DSH_HOME`(扫描器把任何 `process.env` 读取都算凭据信号)。这两条不会为了迎合扫描器而消除 —— 对这类合法插件,商城的正确档位是 `user-reviewed`(展示变更、由用户逐次确认安装)。
|
|
343
|
+
|
|
344
|
+
一次性 Profile 的安装 / 启动 / 卸载证据见 [CHANGELOG.md](./CHANGELOG.md) 的 `0.3.7` 小节:隔离 profile + 隔离 `DSH_HOME` / `HOME` / `USERPROFILE`,三个前端挂载点全部 `active`、预设 roster 含 `cc`、RPC 往返一致、PNG / CHARX 导出→导入往返一致,真实 `~/.dsh` 全程零变化。
|
|
345
|
+
|
|
300
346
|
## 规范依据
|
|
301
347
|
|
|
302
348
|
- [CCv3 SPEC_V3.md](https://github.com/kwaroran/character-card-spec-v3/blob/main/SPEC_V3.md)(权威)
|
|
@@ -306,6 +352,9 @@ node tests/rpc-channel.test.mjs # 只跑 host 半 RPC 通道回归(
|
|
|
306
352
|
|
|
307
353
|
完整历史见 [CHANGELOG.md](./CHANGELOG.md)。最近几版:
|
|
308
354
|
|
|
355
|
+
- `0.4.1` **导出入口归一**:同一个「导出」原本在工坊里出现三处(顶部「⬇ 导出 JSON」/ 左侧导航第 4 步「校验导出」/ 已存角色卡片里的「导出」),彼此重复又不一致。现在顶部那枚删掉;左侧导航与顶部胶囊都只留写作三步,「校验导出」与左下角那枚「校验」按钮合并成一枚入口,点开即是导出页;卡片上的「导出」保留原位置、但改成切到同一页 —— 导出动作只剩导出页那一套实现(卡片「导出」不再直接下载 JSON,`store.exportLibraryEntry` 旁路已删),导出页新增一行说明「导出的是当前工坊里的卡,要导某张已存角色请先载入」。**另一半是文档**:README / README_EN 写明**桌面版(DeepSeek Harness Desktop)支持** —— 新增「7. 桌面版」一节(安装入口、profile 名、重启应用 vs 刷新窗口、数据位置),「兼容与权限」表补「宿主形态」行,并把容易误读的 `Profile | web` 改名为「客户端平台」。另外按实机反馈补了四处:导出页标题带上当前卡名(没名字时显示「未命名」)、卡片「导出」点下去立刻变成「已切到 ✓」并在 6 秒后复原、导出页在「你点的那张卡还没载入」时给出醒目提示、左下角那枚入口按钮**不做选中态**(它一旦跟着亮,就和卡片那枚反馈凑成「两处看起来像选中」)。工作坊头部的「收起」也换成了「✕」图标按钮(悬停红底白字,点击关闭)。测试 335 → 380 项(新增 `tests/export-entry.test.mjs` 31 项、`tests/close-button.test.mjs` 14 项)。
|
|
356
|
+
- `0.4.0` 修工坊左侧步骤列表的高亮:鼠标点过某一步后,那一项周围会多出一圈「莫名其妙的描边」,再用顶部胶囊切步时描边还赖在旧项上,看起来像同时选中了两项。根因是 `navItem` 只画 `s.cur` 的选中态、从没管过浏览器焦点(`<button>` 被点击后一直持有焦点,宿主/UA 的焦点描边就留在按钮外沿)。现在:导航项 `onMouseDown` 阻止默认(鼠标点击不抢焦点、键盘 Tab 照旧)、`outline` 由插件自己接管(只在 `:focus-visible` 时给 2px 品牌色环)、选中态换成品牌紫淡底 + 左竖条 + `aria-current`,并补上 hover 反馈与「鼠标一动就收起焦点环」。新增**「清空当前角色卡」按钮**(左侧底部「校验」下面,`window.confirm` 二次确认后换回空卡,并解除与已存角色的关联 —— 否则接着点「保存当前」会把库里那张覆盖成空卡)。**并重构了工坊 UI(本版主线)**:砍掉右侧占地 360px 那一栏,实时 CCv3 预览与 CBS 速查收到主区底部、**默认全部折叠**(预览那行仍显示 spec 与通过/失败徽章;原「融合说明」那块纯介绍文案已删除);字段改成「文档式」阅读排版(无边框 textarea + `field-sizing: content` 自动撑高),手动编辑退成次要入口(长文走 ⛶ 大框),阅读区从 ~700px 拓到 ~876px。功能一个没少。`tests/nav-highlight.test.mjs`(23 项)与 `tests/clear-card.test.mjs`(20 项)钉住新行为。测试 292 → 335 项。
|
|
357
|
+
- `0.3.8` 补 DSH STORE 上架契约([Store #1173](https://github.com/AI-Scarlett/DSH-Store/issues/1173)):`engines.node: ">=22"`、`dsh.compatibility`(兼容范围 + **只写实测过版本**的精确矩阵)、删掉那个从 0.3.0 起就没被任何代码 import 过的 `@deepseek-ai/cordis` 运行依赖,README / README_EN 新增「兼容与权限」披露节,`tests/preset-install.test.mjs` 加 11 项契约守卫(281 → 292 项断言)。**但这个 issue 光改仓库修不完**:商城条目的包名还停在 0.3.0 的旧名 `@dsh-plugins/dsh-cc-studio`,STORE 复检第一步就因「包名与条目身份不符」判 `update-blocked`,得先由 STORE 侧迁移条目身份(重新提交也绕不过,`entryIds` 会撞旧条目);而 files / credentials 两个权限信号是本插件的核心功能(角色卡草稿落盘 + 读 `DSH_HOME`),自动低风险通道**不可能**通过,商城只应按 `user-reviewed`(用户逐次确认)收录。另已做完 **dsh `0.2.0-rc.2` 兼容性审计**(隔离宿主实测宿主行/预设行组合、RPC 线协议与围栏、会话槽 key、预设 roster 含 `cc` 且 `broken` 空、三个客户端挂载点 `active`、PNG/CHARX 往返逐字一致、真实 `~/.dsh` 零变化),矩阵补上 `0.2.0-rc.2: compatible`;`0.2.0-rc.1` / `0.2.1-alpha.1` 未测,仍不声明。
|
|
309
358
|
- `0.3.7` dsh `0.1.7-rc.1` 兼容性审计:**接口面无一处需要适配**(隔离宿主实测了 RPC 路由/线协议、预设 roster、三个前端挂载点、草稿槽 key、落盘根、PNG/CHARX 往返)。真正的修复是预设里**一行自始漏抄**:`command-goal` 在内置 `standard` 里从 `0.1.6-alpha.2` 起就有,本预设初次拷贝时就漏了它 —— 不报错、不判 broken,只是 CC 模式会话少了 `/goal` 斜杠命令(与 0.3.4 补回的 `present` 同类),现已两份载体同步补齐并加了**行集快照**守卫。另更正 3 处会误导下次升级的文档口径(「异步迭代 `IncomingMessage` 必抛」实测不成立且官方 `/api` 桥自己就在用;`/api/agentPresets/list` 其实带可选 `name`/`description`/`broken`;channel/endpoint 命名正则在 `dsh-client-connection` 而非 `webServer.register`),并给「客户端 `preset.ccLabel` 必须逐字等于宿主预设显示名」这条隐性耦合补了跨文件守卫。顺带精简了 roster 下拉里「CC 模式」的介绍文案:原来把模型侧的 6 个工具名(`cc_get_card` / `cc_patch_character` / …)全列在用户界面上,既撑满下拉项又对用户没有信息量(工具名只对读 persona 的模型有意义),现在只讲「模型先问再填 / 胶囊实时同步 / 工坊全屏编辑 / 导出 JSON、PNG、CHARX」。测试 278 → 281 项。
|
|
310
359
|
- `0.3.6` 修 dsh `0.1.7-alpha.1` 上「`CC 模式` 预设消失」+ 落盘根统一。① 预设:那一版起 dsh 的预设只来自组合里的**声明行**,`<DSH_HOME>/.agent-presets/` 已无人读取(目录还在、只是没人看),插件改为随包带一层补丁 `presets/cc.patch.yml`(与内置 `standard` 同构的 `@deepseek-ai/dsh-agent-preset` 声明),新版上不再写任何文件,目录安装只为 ≤ `0.1.6-alpha.2` 保留并在新版自动让位。② 草稿与角色库:以前写死 `~/.dsh`、不认 `DSH_HOME`(自定义 `DSH_HOME` 的用户两套根并存,隔离实测还会写进真实 `~/.dsh`),现在改为 **`DSH_HOME` 优先 + 读时回退 `~/.dsh`**,未设置时路径与以前完全一致。测试 216 → 278 项。
|
|
311
360
|
- `0.3.5` 修 #5「前端与 Tools 读到不同草稿槽」:`useCcPreset` 读的 `useSessions().current` 字段在 dsh `0.1.6-alpha.2` 的 `SessionListState` 里并不存在(恒为 `null`),加上启动时那次无 key 的拉取占掉了全局节流,前端一直用 `default` 槽 —— 而模型经 Tools 写的是 `session-<会话id>` 槽,于是「模型说写好了 / 工坊说没数据」。现在会话 id 只从 slot props 取并发布到 store、拉草稿没有会话 id 就一次都不发、节流按 key、主机回传 key 不一致时不渲染而是告警;另修胶囊闪退(根域实例每秒钟把会话域实例判定的 CC 状态清掉)并新增 `cc_migrateDraft` 与「迁入本会话槽」一键救回。测试 158 → 216 项(新增行为级 `tests/draft-slot-sync.test.mjs`)。
|
package/lib/client.js
CHANGED
|
@@ -30,10 +30,8 @@ window.__ModuleLoader__.load({
|
|
|
30
30
|
"step.world.desc":"年表/势力/地理/力量/日常",
|
|
31
31
|
"step.char.desc":"六件套 + 开场",
|
|
32
32
|
"step.lore.desc":"Lorebook + Decorator",
|
|
33
|
-
"step.export.desc":"校验导出",
|
|
34
33
|
"action.expand":"✦ AI 扩写",
|
|
35
34
|
"action.validate":"校验",
|
|
36
|
-
"action.export":"⬇ 导出 JSON",
|
|
37
35
|
"action.exportJson":"⬇ JSON",
|
|
38
36
|
"action.exportPng":"⬇ PNG",
|
|
39
37
|
"action.embedPng":"⬆ 写入 PNG",
|
|
@@ -85,6 +83,7 @@ window.__ModuleLoader__.load({
|
|
|
85
83
|
"workshop.world.title":"世界观 5维(Q2-B 深度问卷)",
|
|
86
84
|
"workshop.world.hint":"每维填 1-2 句即可,CC 模式下 LLM 会通过工具引导你补全,并自动生成带 @@decorator 的 Lorebook。点击卡片预览可展开大框,适合长文编辑。",
|
|
87
85
|
"workshop.world.empty":"(空)点击右上“展开编辑”填写,支持多行长文",
|
|
86
|
+
"workshop.field.empty":"未填写",
|
|
88
87
|
"workshop.world.previewHint":"预览(点击卡片或按钮展开大框编辑)",
|
|
89
88
|
"workshop.world.edit":"⛶ 编辑",
|
|
90
89
|
"workshop.world.fill":"+ 填写",
|
|
@@ -108,6 +107,8 @@ window.__ModuleLoader__.load({
|
|
|
108
107
|
"workshop.export.failed":"✗ 校验未通过",
|
|
109
108
|
"workshop.export.hint":"PNG=tEXt ccv3 · CHARX=ZIP card.json",
|
|
110
109
|
"workshop.export.hint2":"⬇ PNG=1x1底图 · ⬆ 写入=选本地PNG写入ccv3 · CHARX=ZIP",
|
|
110
|
+
"workshop.export.currentHint":"导出的是当前工坊里的角色卡;要导出某张已存角色,请先在右侧载入它",
|
|
111
|
+
"workshop.export.jumpedOther":"已切到导出页 · 导出的是当前工坊里的「{name}」;「{from}」还没载入,要导它请先点它那行的「载入」",
|
|
111
112
|
"workshop.export.creatorNotes":"creator_notes / 多语言",
|
|
112
113
|
"workshop.export.creationDate":"creation_date",
|
|
113
114
|
"workshop.export.modificationDate":"modification_date(导出自动刷新)",
|
|
@@ -121,8 +122,6 @@ window.__ModuleLoader__.load({
|
|
|
121
122
|
"workshop.preview.title":"实时 CCv3 预览",
|
|
122
123
|
"workshop.preview.cbsTitle":"CBS 速查",
|
|
123
124
|
"workshop.preview.cbsHint":"大小写不敏感,逗号用 \\, 转义。",
|
|
124
|
-
"workshop.preview.fusionTitle":"融合说明",
|
|
125
|
-
"workshop.preview.fusionDesc":"默认只占一行胶囊;点开为全屏工坊,左侧导航可收起为图标,右侧实时预览常驻。后续可加“侧边抽屉”半屏态。",
|
|
126
125
|
"workshop.library.title":"已存角色",
|
|
127
126
|
"workshop.library.collapsed":"已存 {n}",
|
|
128
127
|
"workshop.library.loaded":"已载入 ID:{id} · 再次保存将更新此卡",
|
|
@@ -139,6 +138,8 @@ window.__ModuleLoader__.load({
|
|
|
139
138
|
"workshop.library.noMatch":"无匹配",
|
|
140
139
|
"workshop.library.load":"载入",
|
|
141
140
|
"workshop.library.export":"导出",
|
|
141
|
+
"workshop.library.exportTip":"切到导出页(导出的仍是当前工坊里的卡)",
|
|
142
|
+
"workshop.library.exportJumped":"已切到 ✓",
|
|
142
143
|
"workshop.library.delete":"删除",
|
|
143
144
|
"workshop.library.unnamed":"未命名",
|
|
144
145
|
"workshop.edit.large":"大框编辑",
|
|
@@ -192,6 +193,8 @@ window.__ModuleLoader__.load({
|
|
|
192
193
|
"store.savedAsNew":"已另存为新卡:{name}",
|
|
193
194
|
"store.errSaveAsFailed":"另存失败",
|
|
194
195
|
"store.switchedNew":"已切换为新卡(保存将新建)",
|
|
196
|
+
"store.confirmClear":"确定清空当前角色卡的全部内容?\n\n已存角色里的存档不受影响,此操作不可撤销。",
|
|
197
|
+
"store.cleared":"已清空当前角色卡(已存角色未动)",
|
|
195
198
|
"store.loadedCard":"已载入:{name} · {n} lore(ID:{id},再次保存将更新此卡)",
|
|
196
199
|
"store.errLoadFailed":"载入失败",
|
|
197
200
|
"store.confirmDelete":"确定删除该角色?此操作不可撤销。",
|
|
@@ -201,8 +204,6 @@ window.__ModuleLoader__.load({
|
|
|
201
204
|
"store.promptRename":"输入新名称",
|
|
202
205
|
"store.renamed":"已重命名为:{name}",
|
|
203
206
|
"store.errRenameFailed":"重命名失败",
|
|
204
|
-
"store.exported":"已导出:{name}",
|
|
205
|
-
"store.errExportFailed":"导出失败",
|
|
206
207
|
"store.saving":"更新中…",
|
|
207
208
|
"store.savingNew":"另存中…",
|
|
208
209
|
"workshop.greeting.entryLabel":"{title} · 第 {n} 条",
|
|
@@ -241,10 +242,8 @@ window.__ModuleLoader__.load({
|
|
|
241
242
|
"step.world.desc":"Timeline/Factions/Geo/Power/Daily",
|
|
242
243
|
"step.char.desc":"6-piece + Openings",
|
|
243
244
|
"step.lore.desc":"Lorebook + Decorator",
|
|
244
|
-
"step.export.desc":"Validate & Export",
|
|
245
245
|
"action.expand":"AI Expand",
|
|
246
246
|
"action.validate":"Validate",
|
|
247
|
-
"action.export":"Export JSON",
|
|
248
247
|
"action.exportJson":"⬇ JSON",
|
|
249
248
|
"action.exportPng":"⬇ PNG",
|
|
250
249
|
"action.embedPng":"⬆ Embed into PNG",
|
|
@@ -296,6 +295,7 @@ window.__ModuleLoader__.load({
|
|
|
296
295
|
"workshop.world.title":"Worldbuilding 5D (Q2-B Deep Dive)",
|
|
297
296
|
"workshop.world.hint":"1–2 sentences per dimension is enough; LLM guides completion and auto-generates Lorebook with @@decorator. Click preview to edit in large modal.",
|
|
298
297
|
"workshop.world.empty":"(empty) Click Edit to fill, multi-line supported",
|
|
298
|
+
"workshop.field.empty":"Empty",
|
|
299
299
|
"workshop.world.previewHint":"Preview (click card or button for large editor)",
|
|
300
300
|
"workshop.world.edit":"⛶ Edit",
|
|
301
301
|
"workshop.world.fill":"+ Fill",
|
|
@@ -319,6 +319,8 @@ window.__ModuleLoader__.load({
|
|
|
319
319
|
"workshop.export.failed":"✗ Failed",
|
|
320
320
|
"workshop.export.hint":"PNG=tEXt ccv3 · CHARX=ZIP card.json",
|
|
321
321
|
"workshop.export.hint2":"⬇ PNG=1×1 placeholder · ⬆ Embed=write into your PNG · CHARX=ZIP",
|
|
322
|
+
"workshop.export.currentHint":"Exports the card currently in the workshop; to export a saved one, load it from the right first",
|
|
323
|
+
"workshop.export.jumpedOther":"Jumped to the export page — it exports “{name}” from the workshop; “{from}” is not loaded yet, hit Load on its row first",
|
|
322
324
|
"workshop.export.creatorNotes":"creator_notes / i18n",
|
|
323
325
|
"workshop.export.creationDate":"creation_date",
|
|
324
326
|
"workshop.export.modificationDate":"modification_date (auto on export)",
|
|
@@ -332,8 +334,6 @@ window.__ModuleLoader__.load({
|
|
|
332
334
|
"workshop.preview.title":"Live CCv3 Preview",
|
|
333
335
|
"workshop.preview.cbsTitle":"CBS Cheatsheet",
|
|
334
336
|
"workshop.preview.cbsHint":"Case-insensitive, escape comma with \\,.",
|
|
335
|
-
"workshop.preview.fusionTitle":"Fusion",
|
|
336
|
-
"workshop.preview.fusionDesc":"One-line capsule by default; click for fullscreen workshop, left nav collapsible, right preview sticky. Drawer half-screen later.",
|
|
337
337
|
"workshop.library.title":"Saved Characters",
|
|
338
338
|
"workshop.library.collapsed":"Saved {n}",
|
|
339
339
|
"workshop.library.loaded":"Loaded ID:{id} · save will update",
|
|
@@ -350,6 +350,8 @@ window.__ModuleLoader__.load({
|
|
|
350
350
|
"workshop.library.noMatch":"No matches",
|
|
351
351
|
"workshop.library.load":"Load",
|
|
352
352
|
"workshop.library.export":"Export",
|
|
353
|
+
"workshop.library.exportTip":"Jump to the export page (it exports the card currently in the workshop)",
|
|
354
|
+
"workshop.library.exportJumped":"Opened ✓",
|
|
353
355
|
"workshop.library.delete":"Delete",
|
|
354
356
|
"workshop.library.unnamed":"Unnamed",
|
|
355
357
|
"workshop.edit.large":"Large Editor",
|
|
@@ -403,6 +405,8 @@ window.__ModuleLoader__.load({
|
|
|
403
405
|
"store.savedAsNew":"Saved as a new card: {name}",
|
|
404
406
|
"store.errSaveAsFailed":"Save-as failed",
|
|
405
407
|
"store.switchedNew":"Switched to a new card (saving will create one)",
|
|
408
|
+
"store.confirmClear":"Clear everything in the current card?\n\nSaved characters in the library are untouched. This cannot be undone.",
|
|
409
|
+
"store.cleared":"Current card cleared (saved characters untouched)",
|
|
406
410
|
"store.loadedCard":"Loaded: {name} · {n} lore (ID:{id}, saving again will update this card)",
|
|
407
411
|
"store.errLoadFailed":"Load failed",
|
|
408
412
|
"store.confirmDelete":"Delete this character? This cannot be undone.",
|
|
@@ -412,8 +416,6 @@ window.__ModuleLoader__.load({
|
|
|
412
416
|
"store.promptRename":"Enter a new name",
|
|
413
417
|
"store.renamed":"Renamed to: {name}",
|
|
414
418
|
"store.errRenameFailed":"Rename failed",
|
|
415
|
-
"store.exported":"Exported: {name}",
|
|
416
|
-
"store.errExportFailed":"Export failed",
|
|
417
419
|
"store.saving":"Updating…",
|
|
418
420
|
"store.savingNew":"Saving as new…",
|
|
419
421
|
"workshop.greeting.entryLabel":"{title} · #{n}",
|
|
@@ -837,6 +839,21 @@ window.__ModuleLoader__.load({
|
|
|
837
839
|
set({libraryBusy:null});
|
|
838
840
|
}
|
|
839
841
|
function clearCurrentLibraryId(){ set({currentLibraryId:null, lastMsg:t("store.switchedNew")}); }
|
|
842
|
+
// 清空当前角色卡:整份草稿换回空卡(makeDraft),并解除「跟已存角色的关联」——
|
|
843
|
+
// 否则清空后点「保存当前」会把库里那张卡覆盖成空卡(要删卡请用右侧列表的删除)。
|
|
844
|
+
// 落盘与其它写入走同一条路(pushDraftToHost):刷新页面、模型侧读到的都是空卡。
|
|
845
|
+
// 二次确认由调用方负责(工坊底部那枚按钮),这里只管状态本身。
|
|
846
|
+
function clearDraft(){
|
|
847
|
+
set({
|
|
848
|
+
draft: makeDraft(),
|
|
849
|
+
world: { timeline:"", factions:"", geo:"", power:"", daily:"" },
|
|
850
|
+
valid: {valid:true, errors:[], warnings:[]},
|
|
851
|
+
editingWorld:null, editingWorldRevert:null, editingText:null,
|
|
852
|
+
currentLibraryId:null,
|
|
853
|
+
lastMsg: t("store.cleared")
|
|
854
|
+
});
|
|
855
|
+
setTimeout(pushDraftToHost, 80);
|
|
856
|
+
}
|
|
840
857
|
async function loadFromLibrary(id){
|
|
841
858
|
if(!id) return;
|
|
842
859
|
try{
|
|
@@ -891,47 +908,52 @@ window.__ModuleLoader__.load({
|
|
|
891
908
|
}catch(e){ set({lastMsg: String(e && e.message || e)}); }
|
|
892
909
|
set({libraryBusy:null});
|
|
893
910
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
var res=await rpc("cc_getLibraryEntry", {id:id});
|
|
899
|
-
if(res && res.ok && res.value && res.value.draft){
|
|
900
|
-
var draft=res.value.draft;
|
|
901
|
-
var blob=new Blob([JSON.stringify(draft,null,2)],{type:"application/json"});
|
|
902
|
-
var a=document.createElement("a"); a.href=URL.createObjectURL(blob); a.download=(res.value.meta.name||draft.data.name||"card")+".json"; a.click(); setTimeout(function(){ URL.revokeObjectURL(a.href); },800);
|
|
903
|
-
set({lastMsg:t("store.exported",{name:(res.value.meta.name||id)})});
|
|
904
|
-
} else set({lastMsg: res && res.error? String(res.error.message):t("store.errExportFailed")});
|
|
905
|
-
}catch(e){ set({lastMsg: String(e && e.message || e)}); }
|
|
906
|
-
set({libraryBusy:null});
|
|
907
|
-
}
|
|
908
|
-
return { getSnapshot:getSnapshot, subscribe:subscribe, setCur:setCur, setPanel:setPanel, setSideExpanded:setSideExpanded, setIsCcMode:setIsCcMode, setDraftFromHost:setDraftFromHost, setSessionId:setSessionId, setSlotWarn:setSlotWarn, setAlternateSlots:setAlternateSlots, migrateSlot:migrateSlot, checkIsCcMode:checkIsCcMode, setPollEnabled:setPollEnabled, setPollInterval:setPollInterval, setAutoValidate:setAutoValidate, setAutoSaveOnImport:setAutoSaveOnImport, updateDraft:updateDraft, validate:validate, exportJSON:exportJSON, exportPNG:exportPNG, embedIntoPng:embedIntoPng, exportCharx:exportCharx, importJSON:importJSON, importAny:importAny, setWorldPatch:setWorldPatch, setEditingWorld:setEditingWorld, cancelEditingWorld:cancelEditingWorld, setEditingText:setEditingText, clearEditingText:clearEditingText, cancelEditingText:cancelEditingText, updateEditingText:updateEditingText, addEntry:addEntry, setLibraryQuery:setLibraryQuery, toggleLibraryCollapsed:toggleLibraryCollapsed, refreshLibrary:refreshLibrary, saveCurrentToLibrary:saveCurrentToLibrary, saveAsNewToLibrary:saveAsNewToLibrary, clearCurrentLibraryId:clearCurrentLibraryId, loadFromLibrary:loadFromLibrary, deleteFromLibrary:deleteFromLibrary, exportLibraryEntry:exportLibraryEntry, renameCurrentInLibrary:renameCurrentInLibrary };
|
|
911
|
+
// 0.4.1:这里原本还有一个 exportLibraryEntry —— 直接下载某张已存角色的 JSON。
|
|
912
|
+
// 三个导出入口归一之后,卡片上的「导出」改成切到导出页,导出动作只剩导出页那一套实现,
|
|
913
|
+
// 这条旁路就没有调用者了(留着它等于「同一次导出有两套行为」),故删除。
|
|
914
|
+
return { getSnapshot:getSnapshot, subscribe:subscribe, setCur:setCur, setPanel:setPanel, setSideExpanded:setSideExpanded, setIsCcMode:setIsCcMode, setDraftFromHost:setDraftFromHost, setSessionId:setSessionId, setSlotWarn:setSlotWarn, setAlternateSlots:setAlternateSlots, migrateSlot:migrateSlot, checkIsCcMode:checkIsCcMode, setPollEnabled:setPollEnabled, setPollInterval:setPollInterval, setAutoValidate:setAutoValidate, setAutoSaveOnImport:setAutoSaveOnImport, updateDraft:updateDraft, validate:validate, exportJSON:exportJSON, exportPNG:exportPNG, embedIntoPng:embedIntoPng, exportCharx:exportCharx, importJSON:importJSON, importAny:importAny, setWorldPatch:setWorldPatch, setEditingWorld:setEditingWorld, cancelEditingWorld:cancelEditingWorld, setEditingText:setEditingText, clearEditingText:clearEditingText, cancelEditingText:cancelEditingText, updateEditingText:updateEditingText, addEntry:addEntry, setLibraryQuery:setLibraryQuery, toggleLibraryCollapsed:toggleLibraryCollapsed, refreshLibrary:refreshLibrary, saveCurrentToLibrary:saveCurrentToLibrary, saveAsNewToLibrary:saveAsNewToLibrary, clearCurrentLibraryId:clearCurrentLibraryId, clearDraft:clearDraft, loadFromLibrary:loadFromLibrary, deleteFromLibrary:deleteFromLibrary, renameCurrentInLibrary:renameCurrentInLibrary };
|
|
909
915
|
}
|
|
910
916
|
|
|
911
917
|
var S={
|
|
912
918
|
overlay:{ position:"fixed", inset:0, background:"rgba(0,0,0,.45)", backdropFilter:"blur(8px)", display:"flex", alignItems:"center", justifyContent:"center", padding:"18px", zIndex:30 },
|
|
913
919
|
workshop:{ width:"min(1360px, 96vw)", height:"min(860px, 92vh)", background:"var(--dsw-alias-bg-layer-1, #fff)", border:"1px solid var(--dsw-alias-border-l1, rgba(0,0,0,.1))", borderRadius:18, overflow:"hidden", display:"flex", flexDirection:"column", boxShadow:"var(--dsw-shadow-lv3, 0 20px 80px rgba(0,0,0,.2))" },
|
|
914
920
|
head:{ height:56, display:"flex", alignItems:"center", justifyContent:"space-between", padding:"0 16px", borderBottom:"1px solid var(--dsw-alias-border-l1)", background:"var(--dsw-alias-bg-layer-2, #f9fafb)" },
|
|
915
|
-
step:{ padding:"6px 10px", borderRadius:999, fontSize:12, border:"1px solid var(--dsw-alias-border-l1)", color:"var(--dsw-alias-label-secondary)", background:"var(--dsw-alias-bg-layer-2)", cursor:"pointer" },
|
|
921
|
+
step:{ padding:"6px 10px", borderRadius:999, fontSize:12, border:"1px solid var(--dsw-alias-border-l1)", color:"var(--dsw-alias-label-secondary)", background:"var(--dsw-alias-bg-layer-2)", cursor:"pointer", outline:"none", transition:"background .12s ease, border-color .12s ease, color .12s ease" },
|
|
916
922
|
stepActive:{ background:"#7c5cff", borderColor:"#7c5cff", color:"#fff" },
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
+
stepHover:{ background:"var(--dsw-alias-bg-layer-1)", borderColor:"rgba(124,92,255,.55)", color:"var(--dsw-alias-label-primary)" },
|
|
924
|
+
body:{ flex:1, display:"grid", gridTemplateColumns:"200px 1fr 268px", minHeight:0, background:"var(--dsw-alias-bg-base)" },
|
|
925
|
+
nav:{ borderRight:"1px solid var(--dsw-alias-border-l1)", background:"var(--dsw-alias-bg-layer-1)", padding:"12px", display:"flex", flexDirection:"column", gap:8 },
|
|
926
|
+
navBtn:{ width:"100%", boxSizing:"border-box", maxWidth:"100%", textAlign:"left", padding:"10px 12px", borderRadius:10, border:"1px solid transparent", background:"transparent", color:"var(--dsw-alias-label-secondary)", cursor:"pointer", outline:"none", transition:"background .12s ease, border-color .12s ease, color .12s ease" },
|
|
927
|
+
navBtnHover:{ background:"var(--dsw-alias-bg-layer-2)", color:"var(--dsw-alias-label-primary)" },
|
|
928
|
+
navBtnFocus:{ outline:"2px solid #7c5cff", outlineOffset:2 },
|
|
929
|
+
navBtnActive:{ background:"rgba(124,92,255,.16)", borderColor:"rgba(124,92,255,.5)", color:"var(--dsw-alias-label-primary)", boxShadow:"inset 3px 0 0 0 #7c5cff" },
|
|
930
|
+
main:{ padding:"18px 20px 28px", overflow:"auto", minWidth:0, background:"var(--dsw-alias-bg-base)" },
|
|
931
|
+
// —— 阅读态排版(0.4.0 重构)——
|
|
932
|
+
// 字段默认以「文档」形态呈现:无边框 textarea + field-sizing:content(Chrome 123+ 按内容自动撑高),
|
|
933
|
+
// 点进去就能改;长文仍可走右上角 ⛶ 大框(带快照回滚)。手动编辑从「满屏输入框」退成次要入口。
|
|
934
|
+
readCard:{ background:"var(--dsw-alias-bg-layer-1)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:14, padding:"16px 18px", marginBottom:12 },
|
|
935
|
+
readTitle:{ margin:"0 0 4px", fontSize:14, fontWeight:600, color:"var(--dsw-alias-label-primary)" },
|
|
936
|
+
readHint:{ fontSize:12, color:"var(--dsw-alias-label-secondary)", marginBottom:10, lineHeight:1.7 },
|
|
937
|
+
fieldRow:{ marginBottom:12 },
|
|
938
|
+
fieldLabel:{ display:"flex", alignItems:"center", justifyContent:"space-between", gap:8, marginBottom:2 },
|
|
939
|
+
fieldName:{ fontSize:12, fontWeight:600, color:"var(--dsw-alias-label-secondary)" },
|
|
940
|
+
fieldTools:{ display:"flex", alignItems:"center", gap:6 },
|
|
941
|
+
iconBtn:{ appearance:"none", border:"1px solid transparent", background:"transparent", color:"var(--dsw-alias-label-secondary)", borderRadius:6, padding:"0 6px", fontSize:12, lineHeight:"20px", cursor:"pointer", outline:"none" },
|
|
942
|
+
readText:{ display:"block", width:"100%", boxSizing:"border-box", maxWidth:"100%", background:"transparent", border:"1px solid transparent", borderRadius:8, color:"var(--dsw-alias-label-primary)", padding:"6px 8px", margin:"0 -8px", font:"inherit", fontSize:13, lineHeight:1.8, resize:"none", outline:"none", fieldSizing:"content", minHeight:30, overflow:"hidden" },
|
|
943
|
+
readInput:{ display:"block", width:"100%", boxSizing:"border-box", maxWidth:"100%", background:"transparent", border:"1px solid transparent", borderRadius:8, color:"var(--dsw-alias-label-primary)", padding:"6px 8px", margin:"0 -8px", font:"inherit", fontSize:13, lineHeight:1.8, outline:"none" },
|
|
944
|
+
// 主区底部的折叠区(实时预览 / CBS 速查 / 融合说明):默认只占一行标题,点开才铺开
|
|
945
|
+
fold:{ border:"1px solid var(--dsw-alias-border-l1)", borderRadius:12, background:"var(--dsw-alias-bg-layer-1)", marginBottom:8, overflow:"hidden" },
|
|
946
|
+
foldHead:{ display:"flex", alignItems:"center", justifyContent:"space-between", gap:8, width:"100%", boxSizing:"border-box", padding:"10px 14px", background:"transparent", border:"none", color:"var(--dsw-alias-label-primary)", fontSize:12, fontWeight:600, cursor:"pointer", textAlign:"left", outline:"none", fontFamily:"inherit" },
|
|
947
|
+
foldBody:{ padding:"12px 14px 14px", borderTop:"1px solid var(--dsw-alias-border-l1)" },
|
|
923
948
|
library:{ borderLeft:"1px solid var(--dsw-alias-border-l1)", background:"var(--dsw-alias-bg-layer-1)", display:"flex", flexDirection:"column", minWidth:0, overflow:"hidden" },
|
|
924
949
|
libraryHead:{ height:46, display:"flex", alignItems:"center", justifyContent:"space-between", padding:"8px 10px", borderBottom:"1px solid var(--dsw-alias-border-l1)", background:"var(--dsw-alias-bg-layer-2)", flexShrink:0 },
|
|
925
950
|
libraryList:{ flex:1, overflow:"auto", padding:"8px", display:"flex", flexDirection:"column", gap:8 },
|
|
926
951
|
libraryItem:{ background:"var(--dsw-alias-bg-layer-2)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:10, padding:10 },
|
|
927
952
|
libraryCollapsedBar:{ width:40, borderLeft:"1px solid var(--dsw-alias-border-l1)", background:"var(--dsw-alias-bg-layer-1)", display:"flex", flexDirection:"column", alignItems:"center", padding:"10px 6px", gap:10, boxShadow:"inset 1px 0 0 var(--dsw-alias-border-l1)" },
|
|
928
|
-
card:{ background:"var(--dsw-alias-bg-layer-1)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:12, padding:12, marginBottom:12 },
|
|
929
|
-
field:{ display:"flex", flexDirection:"column", gap:6, marginBottom:10 },
|
|
930
953
|
badge:{ fontSize:11, padding:"2px 6px", borderRadius:999, border:"1px solid var(--dsw-alias-border-l1)", background:"var(--dsw-alias-bg-layer-2)", color:"var(--dsw-alias-label-secondary)" },
|
|
931
954
|
chip:{ fontSize:11, padding:"4px 8px", borderRadius:999, background:"var(--dsw-alias-bg-layer-2)", border:"1px solid var(--dsw-alias-border-l1)", color:"var(--dsw-alias-label-secondary)", cursor:"pointer" },
|
|
932
955
|
btn:{ appearance:"none", border:"1px solid transparent", background:"#7c5cff", color:"#fff", borderRadius:10, padding:"8px 14px", fontWeight:600, cursor:"pointer" },
|
|
933
956
|
btnGhost:{ background:"transparent", borderColor:"var(--dsw-alias-border-l1)", color:"var(--dsw-alias-label-primary)" },
|
|
934
|
-
mono:{ fontFamily:"ui-monospace,Menlo,Consolas,monospace", fontSize:12, color:"var(--dsw-alias-label-secondary)" },
|
|
935
957
|
dockWrap:{ boxSizing:"border-box", display:"flex", justifyContent:"flex-start", width:"100%", boxSizing:"border-box", maxWidth:"100%", paddingLeft:"calc((100% - var(--dsh-composer-card-max-width, 778px)) / 2)", margin:"2px 0" },
|
|
936
958
|
capsule:{ display:"inline-flex", alignItems:"center", gap:8, maxWidth:360, padding:"1px 10px", borderRadius:999, border:"1px solid rgba(127,127,127,0.25)", background:"rgba(127,127,127,0.12)", color:"var(--dsw-alias-label-secondary, #c8ccd2)", fontSize:12, lineHeight:"20px", cursor:"pointer", whiteSpace:"nowrap", overflow:"hidden", textOverflow:"ellipsis" },
|
|
937
959
|
input:{ width:"100%", boxSizing:"border-box", maxWidth:"100%", background:"var(--dsw-alias-bg-base, #fff)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:8, color:"var(--dsw-alias-label-primary)", padding:"6px 10px", fontSize:12 },
|
|
@@ -1097,142 +1119,211 @@ window.__ModuleLoader__.load({
|
|
|
1097
1119
|
);
|
|
1098
1120
|
}
|
|
1099
1121
|
|
|
1122
|
+
// 破坏性操作的二次确认:拿不到确认框(或它抛错)一律当「没确认」——
|
|
1123
|
+
// 宁可什么都不做,也不静默清掉用户的数据。
|
|
1124
|
+
function confirmClear(t){
|
|
1125
|
+
try{
|
|
1126
|
+
if(typeof window==="undefined" || typeof window.confirm!=="function") return false;
|
|
1127
|
+
return !!window.confirm(t("store.confirmClear"));
|
|
1128
|
+
}catch(e){ return false; }
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// 键盘焦点判定:只有 :focus-visible(键盘 Tab 进来)才画焦点环;鼠标点出来的焦点不画。
|
|
1132
|
+
// 老浏览器没有 :focus-visible 时按「有焦点」处理 —— 宁可多画一环,不可让键盘用户失去位置感。
|
|
1133
|
+
function isFocusVisible(e){
|
|
1134
|
+
try{
|
|
1135
|
+
var el=e && e.currentTarget;
|
|
1136
|
+
if(!el || typeof el.matches!=="function") return true;
|
|
1137
|
+
return !!el.matches(":focus-visible");
|
|
1138
|
+
}catch(err){ return true; }
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1100
1141
|
function Workshop(props){
|
|
1101
1142
|
var store=props.store; var t=props.t;
|
|
1102
1143
|
var currentId = useCcPreset(store, props, t);
|
|
1103
1144
|
var sRef=React.useState(function(){ return store.getSnapshot(); }); var s=sRef[0], setS=sRef[1];
|
|
1145
|
+
// 悬停 / 键盘焦点是纯局部视觉状态,不进 store:否则鼠标每移动一次都要重渲染整棵工坊树(右侧预览是整份 JSON)。
|
|
1146
|
+
var hoverRef=React.useState(null); var hoverKey=hoverRef[0], setHoverKey=hoverRef[1];
|
|
1147
|
+
var focusRef=React.useState(null); var focusKey=focusRef[0], setFocusKey=focusRef[1];
|
|
1148
|
+
// 主区底部三块阅读辅助(实时预览 / CBS 速查 / 融合说明)默认全收起:占地方、但不常看
|
|
1149
|
+
var foldRef=React.useState({}); var openFold=foldRef[0], setOpenFold=foldRef[1];
|
|
1150
|
+
// 从「已存角色」卡片跳进导出页时的来源卡(几秒后自动清掉):用来给那枚按钮一个即时反馈,
|
|
1151
|
+
// 并在导出页把「导出的仍是工坊里这张、你点的那张还没载入」说清楚。清空后回到常态提示。
|
|
1152
|
+
var jumpRef=React.useState(null); var jumpFrom=jumpRef[0], setJumpFrom=jumpRef[1];
|
|
1104
1153
|
React.useEffect(function(){ return store.subscribe(function(){ setS(store.getSnapshot()); }); }, [store]);
|
|
1105
1154
|
React.useEffect(function(){
|
|
1106
1155
|
if(s.panelOpen) store.refreshLibrary();
|
|
1107
1156
|
}, [s.panelOpen]);
|
|
1108
1157
|
if(!s.panelOpen) return null;
|
|
1109
|
-
|
|
1158
|
+
// 顶部胶囊与左侧导航都只有写作三步:导出不再是「第 4 步」(0.4.1 归一)——
|
|
1159
|
+
// 「校验导出」并进左下角那枚按钮,已存角色卡片上的「导出」也指向同一页,三处入口收成两处。
|
|
1160
|
+
var steps=[t("step.world"), t("step.char"), t("step.lore")];
|
|
1161
|
+
// 步骤项的三个视觉状态严格互斥:选中(active) / 键盘焦点(focus-visible) / 悬停(hover)。
|
|
1162
|
+
// 鼠标点击走 onMouseDown preventDefault,不把焦点抢到按钮上 —— 否则旧项的焦点环会留在原地,
|
|
1163
|
+
// 与新的选中项同时发亮,看起来像「同时选中了两个」(这是本次修的问题)。
|
|
1110
1164
|
function navItem(i, title, desc){
|
|
1111
1165
|
var active=s.cur===i;
|
|
1112
|
-
|
|
1166
|
+
var hovered=hoverKey==="nav:"+i;
|
|
1167
|
+
var focused=focusKey==="nav:"+i;
|
|
1168
|
+
return h("button",{key:i, type:"button", "aria-current": active? "step": undefined,
|
|
1169
|
+
style: Object.assign({}, S.navBtn, hovered&&!active? S.navBtnHover:{}, focused? S.navBtnFocus:{}, active? S.navBtnActive:{}),
|
|
1170
|
+
onMouseDown:function(e){ if(e && e.preventDefault) e.preventDefault(); },
|
|
1171
|
+
onMouseEnter:function(){ setHoverKey("nav:"+i); },
|
|
1172
|
+
onMouseLeave:function(){ setHoverKey(null); },
|
|
1173
|
+
onFocus:function(e){ setFocusKey(isFocusVisible(e)? "nav:"+i : null); },
|
|
1174
|
+
onBlur:function(){ setFocusKey(null); },
|
|
1175
|
+
onClick:function(){ store.setCur(i); }
|
|
1176
|
+
}, title, h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginTop:2}}, desc));
|
|
1177
|
+
}
|
|
1178
|
+
// 工具按钮(⛶ / 删除…)鼠标按下就拦掉默认行为:别把焦点抢走,否则又留下一圈焦点描边
|
|
1179
|
+
function stopFocusSteal(e){ if(e && e.preventDefault) e.preventDefault(); }
|
|
1180
|
+
// 卡片上的「导出」= 切到导出页 + 立刻反馈(按钮文案短暂变成「已切到 ✓」)。
|
|
1181
|
+
// 只切页本身太安静了:用户的视线在右侧卡片上,中栏换了内容他未必看得见。
|
|
1182
|
+
function jumpToExport(entry){
|
|
1183
|
+
store.setCur(3);
|
|
1184
|
+
setJumpFrom({ id:String((entry&&entry.id)||""), name:String((entry&&entry.name)||"") });
|
|
1185
|
+
try{ setTimeout(function(){ setJumpFrom(null); }, 6000); }catch(e){}
|
|
1186
|
+
}
|
|
1187
|
+
// 折叠区:主区底部那几块「阅读辅助」(实时 CCv3 预览 / CBS 速查 / 融合说明)。
|
|
1188
|
+
// 默认全部收起 —— 它们不常看,收起来只占一行标题,阅读区因此能拿到整幅宽度。
|
|
1189
|
+
function foldPanel(id, title, badge, body){
|
|
1190
|
+
var open = !!openFold[id];
|
|
1191
|
+
return h("section",{style:S.fold},
|
|
1192
|
+
h("button",{type:"button", style:S.foldHead, "aria-expanded": open? "true": "false", onMouseDown:stopFocusSteal,
|
|
1193
|
+
onClick:function(){ setOpenFold(function(prev){ var nx=Object.assign({}, prev); nx[id]=!nx[id]; return nx; }); }},
|
|
1194
|
+
h("span",null, (open? "▾ " : "▸ ")+title),
|
|
1195
|
+
badge||null),
|
|
1196
|
+
open? h("div",{style:S.foldBody}, body): null);
|
|
1113
1197
|
}
|
|
1114
|
-
//
|
|
1115
|
-
//
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1198
|
+
// —— 阅读态字段(0.4.0 重构的核心构件)——
|
|
1199
|
+
// 标签(+ 可选工具)+ 无边框输入控件;textarea 用 field-sizing:content 按内容自动撑高,
|
|
1200
|
+
// 所以默认视图看起来就是排好版的正文,点进去直接改。长文仍可走 ⛶ 大框(带打开时快照,「取消」真回滚)。
|
|
1201
|
+
function field(title, key, opts){
|
|
1202
|
+
opts=opts||{};
|
|
1203
|
+
var raw = opts.value!==undefined ? opts.value : s.draft.data[key];
|
|
1204
|
+
var val = raw==null? "" : String(raw);
|
|
1205
|
+
var write = opts.onChange || function(v){ store.updateDraft(function(d){ d[key]=v; }); };
|
|
1206
|
+
var tools=[];
|
|
1207
|
+
if(opts.tools) tools=tools.concat(opts.tools);
|
|
1208
|
+
if(opts.bigBox!==false) tools.push(h("button",{key:"bigbox", type:"button", style:S.iconBtn, title:t("action.expand"), onMouseDown:stopFocusSteal,
|
|
1209
|
+
onClick:function(){
|
|
1210
|
+
var snapshot=s.draft.data[key]; // 「取消」回滚用
|
|
1211
|
+
store.setEditingText(title, val,
|
|
1212
|
+
function(v){ store.updateDraft(function(d){ d[key]=v; }); },
|
|
1213
|
+
function(){ store.updateDraft(function(d){ d[key]=snapshot; }); });
|
|
1214
|
+
}}, "⛶"));
|
|
1215
|
+
return h("div",{key:opts.rowKey, style:Object.assign({}, S.fieldRow, opts.style)},
|
|
1216
|
+
h("div",{style:S.fieldLabel},
|
|
1217
|
+
h("label",{style:S.fieldName}, title),
|
|
1218
|
+
tools.length? h("div",{style:S.fieldTools}, tools): null),
|
|
1219
|
+
opts.input
|
|
1220
|
+
? h("input",{style:Object.assign({}, S.readInput, opts.readOnly? {color:"var(--dsw-alias-label-secondary)"}:{}), value:val, readOnly:!!opts.readOnly, placeholder:opts.placeholder||t("workshop.field.empty"), onChange:function(e){ write(e.target.value); }})
|
|
1221
|
+
: h("textarea",{style:S.readText, value:val, readOnly:!!opts.readOnly, placeholder:opts.placeholder||t("workshop.field.empty"), onChange:function(e){ write(e.target.value); }})
|
|
1222
|
+
);
|
|
1126
1223
|
}
|
|
1127
|
-
//
|
|
1224
|
+
// 每条问候语一段正文(元素内部换行原样保留,不做「每行一条」的序列化);阅读态 + 逐条 ⛶ / 删除
|
|
1128
1225
|
function greetingList(key, title, hint){
|
|
1129
1226
|
var arr = Array.isArray(s.draft.data[key]) ? s.draft.data[key] : [];
|
|
1130
1227
|
var atMax = arr.length >= GREETING_MAX;
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
h("
|
|
1135
|
-
h("div",{style:{display:"flex", gap:6, alignItems:"center"}},
|
|
1136
|
-
h("button",{style:Object.assign({}, S.btnGhost, {padding:"2px 8px", fontSize:11, border:"1px solid var(--dsw-alias-border-l1)", background:"transparent", color:"var(--dsw-alias-label-primary)"}),
|
|
1137
|
-
onClick:function(){
|
|
1138
|
-
store.setEditingText(t("workshop.greeting.entryLabel",{title:title, n:(i+1)}), String(raw==null? "": raw),
|
|
1139
|
-
function(v){ store.updateDraft(function(d){ d[key]=setGreetingAt(d[key], i, v); }); },
|
|
1140
|
-
function(){ store.updateDraft(function(d){ d[key]=setGreetingAt(d[key], i, raw); }); });
|
|
1141
|
-
}}, "⛶"),
|
|
1142
|
-
h("button",{style:Object.assign({}, S.btnGhost, {padding:"2px 8px", fontSize:11, border:"1px solid #3a2a2a", background:"transparent", color:"#e8a6a6"}),
|
|
1143
|
-
onClick:function(){ store.updateDraft(function(d){ d[key]=removeGreetingAt(d[key], i); }); }}, t("common.delete"))
|
|
1144
|
-
));
|
|
1145
|
-
}
|
|
1146
|
-
return h("div",{style:S.field},
|
|
1147
|
-
h("div",{style:{display:"flex", justifyContent:"space-between", alignItems:"center", marginBottom:4}},
|
|
1148
|
-
h("label",{style:{fontSize:12,color:"var(--dsw-alias-label-secondary)"}}, title),
|
|
1149
|
-
h("div",{style:{display:"flex", gap:6, alignItems:"center"}},
|
|
1228
|
+
return h("div",{style:S.fieldRow},
|
|
1229
|
+
h("div",{style:S.fieldLabel},
|
|
1230
|
+
h("label",{style:S.fieldName}, title),
|
|
1231
|
+
h("div",{style:S.fieldTools},
|
|
1150
1232
|
h("span",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)"}}, hint+" · "+arr.length+"/"+GREETING_MAX),
|
|
1151
|
-
h("button",{style:Object.assign({}, S.
|
|
1152
|
-
onClick:function(){ store.updateDraft(function(d){ d[key]=appendGreeting(d[key]); }); }}, atMax? t("workshop.greeting.atMax",{max:GREETING_MAX}):t("workshop.greeting.add"))
|
|
1153
|
-
|
|
1233
|
+
h("button",{type:"button", style:Object.assign({}, S.iconBtn, {borderColor:"var(--dsw-alias-border-l1)"}), disabled:atMax, onMouseDown:stopFocusSteal,
|
|
1234
|
+
onClick:function(){ store.updateDraft(function(d){ d[key]=appendGreeting(d[key]); }); }}, atMax? t("workshop.greeting.atMax",{max:GREETING_MAX}):t("workshop.greeting.add")))
|
|
1235
|
+
),
|
|
1154
1236
|
arr.length===0
|
|
1155
|
-
? h("div",{style:{fontSize:12, color:"var(--dsw-alias-label-secondary)", border:"1px dashed var(--dsw-alias-border-l1)", borderRadius:
|
|
1237
|
+
? h("div",{style:{fontSize:12, color:"var(--dsw-alias-label-secondary)", border:"1px dashed var(--dsw-alias-border-l1)", borderRadius:10, padding:"12px", textAlign:"center"}}, t("workshop.greeting.empty"))
|
|
1156
1238
|
: arr.map(function(text, i){
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
h("
|
|
1160
|
-
|
|
1239
|
+
var label=t("workshop.greeting.entryLabel",{title:title, n:(i+1)});
|
|
1240
|
+
return h("div",{key:i, style:{border:"1px solid var(--dsw-alias-border-l1)", borderRadius:10, padding:"8px 10px", marginBottom:8, background:"var(--dsw-alias-bg-base)"}},
|
|
1241
|
+
h("div",{style:S.fieldLabel},
|
|
1242
|
+
h("span",{style:{fontSize:11, fontWeight:600, color:"var(--dsw-alias-label-secondary)"}}, label),
|
|
1243
|
+
h("div",{style:S.fieldTools},
|
|
1244
|
+
h("button",{type:"button", style:S.iconBtn, title:t("action.expand"), onMouseDown:stopFocusSteal,
|
|
1245
|
+
onClick:function(){
|
|
1246
|
+
store.setEditingText(label, String(text==null? "": text),
|
|
1247
|
+
function(v){ store.updateDraft(function(d){ d[key]=setGreetingAt(d[key], i, v); }); },
|
|
1248
|
+
function(){ store.updateDraft(function(d){ d[key]=setGreetingAt(d[key], i, text); }); });
|
|
1249
|
+
}}, "⛶"),
|
|
1250
|
+
h("button",{type:"button", style:Object.assign({}, S.iconBtn, {color:"#e8a6a6"}), onMouseDown:stopFocusSteal,
|
|
1251
|
+
onClick:function(){ store.updateDraft(function(d){ d[key]=removeGreetingAt(d[key], i); }); }}, t("common.delete")))
|
|
1252
|
+
),
|
|
1253
|
+
h("textarea",{style:S.readText, value:String(text==null? "": text), placeholder:t("workshop.field.empty"),
|
|
1161
1254
|
onChange:function(e){ var v=e.target.value; store.updateDraft(function(d){ d[key]=setGreetingAt(d[key], i, v); }); }}));
|
|
1162
1255
|
})
|
|
1163
1256
|
);
|
|
1164
1257
|
}
|
|
1165
1258
|
var main=null;
|
|
1166
1259
|
if(s.cur===0){
|
|
1260
|
+
// 五维世界观:默认直接读全文(无边框 textarea 自动撑高),⛶ 仍可放大框写长文
|
|
1167
1261
|
function worldField(key, title){
|
|
1168
1262
|
var val = s.world[key]||"";
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
h("div",{style:{fontSize:12, fontWeight:600}}, title),
|
|
1174
|
-
h("div",{style:{display:"flex", gap:6, alignItems:"center"}},
|
|
1263
|
+
return h("div",{key:key, style:S.fieldRow},
|
|
1264
|
+
h("div",{style:S.fieldLabel},
|
|
1265
|
+
h("label",{style:S.fieldName}, title),
|
|
1266
|
+
h("div",{style:S.fieldTools},
|
|
1175
1267
|
h("span",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)"}}, t("workshop.world.chars",{n:String(val.length)})),
|
|
1176
|
-
h("button",{
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
h("div",{style:{flex:1, maxHeight:84, overflow:"auto", background:"var(--dsw-alias-bg-base)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:8, padding:"8px", fontSize:12, lineHeight:1.5, whiteSpace:"pre-wrap", color: has? "var(--dsw-alias-label-primary)" : "var(--dsw-alias-label-secondary)", cursor:"pointer"}, onClick:function(){ store.setEditingWorld(key); }}, has ? preview : preview),
|
|
1180
|
-
h("div",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)", marginTop:6, textAlign:"right"}}, has ? t("workshop.world.previewHint") : " ")
|
|
1181
|
-
);
|
|
1268
|
+
h("button",{type:"button", style:S.iconBtn, title:t("action.expand"), onMouseDown:stopFocusSteal, onClick:function(){ store.setEditingWorld(key); }}, "⛶"))),
|
|
1269
|
+
h("textarea",{style:S.readText, value:String(val), placeholder:t("workshop.world.empty"),
|
|
1270
|
+
onChange:function(e){ store.setWorldPatch(key, e.target.value); }}));
|
|
1182
1271
|
}
|
|
1183
1272
|
main=h("div",null,
|
|
1184
|
-
h("div",{style:S.
|
|
1185
|
-
h("h3",{style:
|
|
1186
|
-
h("div",{style:
|
|
1187
|
-
h("div",{style:{display:"grid", gridTemplateColumns:"1fr 1fr", gap:
|
|
1273
|
+
h("div",{style:S.readCard},
|
|
1274
|
+
h("h3",{style:S.readTitle},t("workshop.world.title")),
|
|
1275
|
+
h("div",{style:S.readHint},t("workshop.world.hint")),
|
|
1276
|
+
h("div",{style:{display:"grid", gridTemplateColumns:"1fr 1fr", gap:"0 18px"}},
|
|
1188
1277
|
worldField("timeline", t("workshop.world.timeline")),
|
|
1189
1278
|
worldField("factions", t("workshop.world.factions")),
|
|
1190
1279
|
worldField("geo", t("workshop.world.geo")),
|
|
1191
1280
|
worldField("power", t("workshop.world.power")),
|
|
1192
|
-
h("div",{style:{gridColumn:"span 2"}}, worldField("daily", t("workshop.world.daily")))
|
|
1281
|
+
h("div",{style:{gridColumn:"span 2", display:"grid"}}, worldField("daily", t("workshop.world.daily")))
|
|
1193
1282
|
),
|
|
1194
|
-
h("div",{style:{display:"flex", gap:8, marginTop:
|
|
1195
|
-
h("button",{style: Object.assign({}, S.btn, S.btnGhost), onClick:function(){ store.setCur(1); }}, t("action.nextChar"))
|
|
1283
|
+
h("div",{style:{display:"flex", gap:8, marginTop:6}},
|
|
1284
|
+
h("button",{type:"button", style: Object.assign({}, S.btn, S.btnGhost), onMouseDown:stopFocusSteal, onClick:function(){ store.setCur(1); }}, t("action.nextChar"))
|
|
1196
1285
|
),
|
|
1197
1286
|
s.lastMsg? h("div",{style:{marginTop:10, fontSize:12, color:"var(--dsw-alias-label-secondary)"}}, s.lastMsg):null
|
|
1198
1287
|
)
|
|
1199
1288
|
);
|
|
1200
1289
|
} else if(s.cur===1){
|
|
1201
1290
|
main=h("div",null,
|
|
1202
|
-
h("div",{style:S.
|
|
1203
|
-
h("
|
|
1204
|
-
|
|
1205
|
-
|
|
1291
|
+
h("div",{style:S.readCard},
|
|
1292
|
+
h("h3",{style:S.readTitle},t("workshop.char.sixTitle")),
|
|
1293
|
+
h("div",{style:{display:"grid", gridTemplateColumns:"1fr 1fr", gap:"0 18px"}},
|
|
1294
|
+
field("name","name",{rowKey:"name", input:true}),
|
|
1295
|
+
field(t("workshop.field.nickname"),"nickname",{rowKey:"nickname", input:true})
|
|
1206
1296
|
),
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1297
|
+
field(t("workshop.field.tags"),"tags",{rowKey:"tags", input:true, bigBox:false, value:s.draft.data.tags.join(", "), onChange:function(v){ store.updateDraft(function(d){ d.tags=v.split(",").map(function(x){return x.trim();}).filter(Boolean); }); }}),
|
|
1298
|
+
field("description","description",{rowKey:"desc"}),
|
|
1299
|
+
field("personality","personality",{rowKey:"pers"}),
|
|
1300
|
+
field("scenario","scenario",{rowKey:"scen"}),
|
|
1211
1301
|
h("div",{style:{display:"grid", gridTemplateColumns:"1fr 1fr", gap:10}},
|
|
1212
|
-
|
|
1213
|
-
|
|
1302
|
+
field("system_prompt","system_prompt",{rowKey:"sys"}),
|
|
1303
|
+
field("post_history_instructions","post_history_instructions",{rowKey:"phi"})
|
|
1214
1304
|
)
|
|
1215
1305
|
),
|
|
1216
|
-
h("div",{style:S.
|
|
1217
|
-
h("
|
|
1306
|
+
h("div",{style:S.readCard},
|
|
1307
|
+
h("h3",{style:S.readTitle},t("workshop.char.openingTitle")),
|
|
1308
|
+
field("first_mes","first_mes",{rowKey:"first"}),
|
|
1218
1309
|
greetingList("alternate_greetings", t("workshop.greeting.altTitle"), t("workshop.greeting.altHint")),
|
|
1219
1310
|
greetingList("group_only_greetings", t("workshop.greeting.groupTitle"), t("workshop.greeting.groupHint")),
|
|
1220
|
-
|
|
1311
|
+
field(t("workshop.field.mesExample"),"mes_example",{rowKey:"mes"})
|
|
1221
1312
|
)
|
|
1222
1313
|
);
|
|
1223
1314
|
} else if(s.cur===2){
|
|
1224
1315
|
var entries=s.draft.data.character_book.entries;
|
|
1225
1316
|
main=h("div",null,
|
|
1226
|
-
h("div",{style:S.
|
|
1317
|
+
h("div",{style:S.readCard},
|
|
1227
1318
|
h("div",{style:{display:"flex", alignItems:"center", justifyContent:"space-between", marginBottom:8}},
|
|
1228
|
-
h("h3",{style:
|
|
1319
|
+
h("h3",{style:S.readTitle},t("workshop.lore.title")),
|
|
1229
1320
|
h("span",{style:S.badge}, "scan_depth "+s.draft.data.character_book.scan_depth+" · token_budget "+s.draft.data.character_book.token_budget+" · "+t("common.entries",{n:entries.length}))
|
|
1230
1321
|
),
|
|
1231
1322
|
h("div",null, entries.map(function(e){
|
|
1232
1323
|
return h("div",{key:e.id, style:{display:"grid", gridTemplateColumns:"1fr auto", gap:8, background:"var(--dsw-alias-bg-base)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:10, padding:10, marginBottom:8}},
|
|
1233
1324
|
h("div",null,
|
|
1234
1325
|
h("div",{style:{fontWeight:600, fontSize:12}}, e.name, h("span",{style:Object.assign({}, S.badge, {marginLeft:6})}, e.constant? "constant": (e.keys.join(", ")||"—")), h("span",{style:Object.assign({}, S.badge, {marginLeft:6})}, "order "+e.insertion_order)),
|
|
1235
|
-
h("div",{style:
|
|
1326
|
+
h("div",{style:{fontSize:13, lineHeight:1.8, color:"var(--dsw-alias-label-primary)", marginTop:4, whiteSpace:"pre-wrap", wordBreak:"break-word"}}, e.content),
|
|
1236
1327
|
h("div",{style:{display:"flex", flexWrap:"wrap", gap:6, marginTop:6}}, h("span",{style:S.badge},"@@depth/@@position"), h("span",{style:S.badge},"CBS: {{char}}"))
|
|
1237
1328
|
),
|
|
1238
1329
|
h("div",{style:{display:"flex", flexDirection:"column", gap:6}}, h("label",{style:{fontSize:12}}, h("input",{type:"checkbox", checked:e.enabled, onChange:function(ev){ store.updateDraft(function(d){ var f=d.character_book.entries.find(function(x){return x.id===e.id;}); if(f) f.enabled=ev.target.checked; }); }}), " enabled"), h("button",{style: Object.assign({}, S.btn, S.btnGhost, {padding:"4px 8px", fontSize:12}), onClick:function(){ store.updateDraft(function(d){ d.character_book.entries=d.character_book.entries.filter(function(x){return x.id!==e.id;}); }); }}, t("common.delete")))
|
|
@@ -1248,8 +1339,11 @@ window.__ModuleLoader__.load({
|
|
|
1248
1339
|
} else {
|
|
1249
1340
|
var v=s.valid;
|
|
1250
1341
|
main=h("div",null,
|
|
1251
|
-
h("div",{style:S.
|
|
1252
|
-
|
|
1342
|
+
h("div",{style:S.readCard},
|
|
1343
|
+
// 标题后面挂上「现在导的是哪张卡」:从已存角色卡片跳进来时,用户最需要确认的就是这个
|
|
1344
|
+
h("h3",{style:S.readTitle}, t("workshop.export.title"),
|
|
1345
|
+
h("span",{style:{marginLeft:8, fontSize:12, fontWeight:400, color:"var(--dsw-alias-label-secondary)"}},
|
|
1346
|
+
"· "+(String(s.draft.data.name||"").trim() || t("workshop.library.unnamed")))),
|
|
1253
1347
|
h("div",{style:{display:"flex", flexWrap:"wrap", gap:8, marginBottom:8}},
|
|
1254
1348
|
h("span",{style: Object.assign({}, S.badge, v.valid? {borderColor:"var(--dsw-alias-state-success-primary)", color:"#a6e8c8", background:"color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, var(--dsw-alias-bg-base))"}:{borderColor:"var(--dsw-alias-state-error-primary)", color:"#e8a6a6", background:"color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, var(--dsw-alias-bg-base))"})}, v.valid? t("workshop.export.passed"):t("workshop.export.failed")),
|
|
1255
1349
|
h("span",{style:S.badge},t("common.groupGreetings",{n:s.draft.data.group_only_greetings.length})),
|
|
@@ -1258,17 +1352,24 @@ window.__ModuleLoader__.load({
|
|
|
1258
1352
|
v.errors.length? h("div",{style:{background:"color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, var(--dsw-alias-bg-base))", border:"1px solid var(--dsw-alias-state-error-primary)", borderRadius:8, padding:8, marginBottom:8, color:"#e8a6a6", fontSize:12}}, v.errors.map(function(e){ return h("div",{key:e}, "• "+e); })):null,
|
|
1259
1353
|
v.warnings.length? h("div",{style:{background:"color-mix(in srgb, var(--dsw-alias-border-l1) 40%, var(--dsw-alias-bg-base))", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:8, padding:8, marginBottom:8, color:"var(--dsw-alias-label-secondary)", fontSize:12}}, v.warnings.map(function(e){ return h("div",{key:e}, "• "+e); })):null,
|
|
1260
1354
|
h("div",{style:{display:"flex", flexWrap:"wrap", gap:8, margin:"12px 0"}},
|
|
1261
|
-
h("button",{style:S.btn, onClick:function(){ store.exportJSON(); }}, "
|
|
1355
|
+
h("button",{style:S.btn, onClick:function(){ store.exportJSON(); }}, t("action.exportJson")),
|
|
1262
1356
|
h("button",{style: Object.assign({}, S.btn, {background:"#e05555"}), onClick:function(){ store.exportPNG(); }}, s.busy==="exportPng"? t("action.generating"):t("action.exportPng")),
|
|
1263
1357
|
h("button",{style: Object.assign({}, S.btn, {background:"#e05555", opacity:0.92}), onClick:function(){ store.embedIntoPng(); }}, s.busy==="exportPng"? t("action.writing"):t("action.embedPng")),
|
|
1264
1358
|
h("button",{style: Object.assign({}, S.btn, {background:"#2e86de"}), onClick:function(){ store.exportCharx(); }}, s.busy==="exportCharx"? t("action.generating"):t("action.exportCharx")),
|
|
1265
1359
|
h("button",{style: Object.assign({}, S.btn, S.btnGhost), onClick:function(){ store.validate(); }}, s.busy==="validate"? t("action.validating"): t("action.validate")),
|
|
1266
1360
|
h("span",{style:{color:"var(--dsw-alias-label-secondary)", fontSize:11, alignSelf:"center"}}, t("workshop.export.hint2"))
|
|
1267
1361
|
),
|
|
1268
|
-
|
|
1362
|
+
// 导出页只认「当前工坊里的卡」:右侧卡片上的「导出」也是切到这一页,所以这里把口径写清楚
|
|
1363
|
+
// 从卡片跳进来、且那张卡不是工坊里这张时,把口径说透(导出的是谁、要导它该怎么做);
|
|
1364
|
+
// 其余情况只留常态说明。
|
|
1365
|
+
(jumpFrom && jumpFrom.id && jumpFrom.id!==s.currentLibraryId)
|
|
1366
|
+
? h("div",{style:{fontSize:11, margin:"-4px 0 10px", lineHeight:1.7, color:"var(--dsw-alias-label-primary)", background:"rgba(255,170,60,.10)", border:"1px solid rgba(255,170,60,.45)", borderRadius:8, padding:"6px 10px"}},
|
|
1367
|
+
t("workshop.export.jumpedOther",{name:(String(s.draft.data.name||"").trim()||t("workshop.library.unnamed")), from:(jumpFrom.name||t("workshop.library.unnamed"))}))
|
|
1368
|
+
: h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", margin:"-4px 0 10px", lineHeight:1.7}}, t("workshop.export.currentHint")),
|
|
1369
|
+
field(t("workshop.export.creatorNotes"),"creator_notes",{rowKey:"notes"}),
|
|
1269
1370
|
h("div",{style:{display:"grid", gridTemplateColumns:"1fr 1fr", gap:10}},
|
|
1270
|
-
|
|
1271
|
-
|
|
1371
|
+
field("creation_date","creation_date",{rowKey:"cdate", input:true, readOnly:true, bigBox:false, value:String(s.draft.data.creation_date)}),
|
|
1372
|
+
field(t("workshop.export.modificationDate"),"modification_date",{rowKey:"mdate", input:true, readOnly:true, bigBox:false, value:String(s.draft.data.modification_date)})
|
|
1272
1373
|
),
|
|
1273
1374
|
s.lastMsg? h("div",{style:{marginTop:8, fontSize:12, color:"var(--dsw-alias-label-secondary)"}}, s.lastMsg):null
|
|
1274
1375
|
)
|
|
@@ -1276,7 +1377,11 @@ window.__ModuleLoader__.load({
|
|
|
1276
1377
|
}
|
|
1277
1378
|
var stepsEls=steps.map(function(label, i){
|
|
1278
1379
|
var active=s.cur===i;
|
|
1279
|
-
|
|
1380
|
+
var hovered=hoverKey==="step:"+i;
|
|
1381
|
+
return h("span",{key:i, style: Object.assign({}, S.step, hovered&&!active? S.stepHover:{}, active? S.stepActive:{}),
|
|
1382
|
+
onMouseEnter:function(){ setHoverKey("step:"+i); },
|
|
1383
|
+
onMouseLeave:function(){ setHoverKey(null); },
|
|
1384
|
+
onClick:function(){ store.setCur(i); }}, (i+1)+". "+label);
|
|
1280
1385
|
});
|
|
1281
1386
|
var filteredLibrary = (function(){
|
|
1282
1387
|
var q = String(s.libraryQuery||"").trim().toLowerCase();
|
|
@@ -1288,7 +1393,7 @@ window.__ModuleLoader__.load({
|
|
|
1288
1393
|
return nameMatch || tagMatch || loreMatch;
|
|
1289
1394
|
});
|
|
1290
1395
|
})();
|
|
1291
|
-
var bodyStyle = Object.assign({}, S.body, {gridTemplateColumns: s.libraryCollapsed ? "
|
|
1396
|
+
var bodyStyle = Object.assign({}, S.body, {gridTemplateColumns: s.libraryCollapsed ? "200px 1fr 40px" : "200px 1fr 268px"});
|
|
1292
1397
|
function renderLibrary(){
|
|
1293
1398
|
if(s.libraryCollapsed){
|
|
1294
1399
|
return h("div",{style:S.libraryCollapsedBar},
|
|
@@ -1349,7 +1454,10 @@ window.__ModuleLoader__.load({
|
|
|
1349
1454
|
h("div",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)", marginTop:4}}, new Date(entry.updatedAt*1000).toLocaleString()),
|
|
1350
1455
|
h("div",{style:{display:"flex", gap:6, marginTop:8}},
|
|
1351
1456
|
h("button",{style:Object.assign({}, S.btn, {padding:"4px 8px", fontSize:11, flex:1}), onClick:function(){ store.loadFromLibrary(entry.id); }, disabled: !!isBusy}, isBusy?"…":t("workshop.library.load")),
|
|
1352
|
-
|
|
1457
|
+
// 「导出」保留在原位,但改成切到导出页(0.4.1 归一):导出页导出的始终是当前工坊里的卡,
|
|
1458
|
+
// 想导这张已存角色先点「载入」—— 免得这里悄悄换掉用户正在编辑的草稿。
|
|
1459
|
+
// 点下去按钮自己先变(「已切到 ✓」),不然切页发生在中栏、用户视线却在右侧,会以为没反应。
|
|
1460
|
+
h("button",{style:Object.assign({}, S.btnGhost, {border:"1px solid var(--dsw-alias-border-l1)", background:"transparent", color:(jumpFrom && jumpFrom.id===entry.id)? "#a6e8c8" : "var(--dsw-alias-label-secondary)", borderRadius:8, padding:"4px 8px", fontSize:11}), title:t("workshop.library.exportTip"), onClick:function(){ jumpToExport(entry); }}, (jumpFrom && jumpFrom.id===entry.id)? t("workshop.library.exportJumped"): t("workshop.library.export")),
|
|
1353
1461
|
h("button",{style:Object.assign({}, S.btnGhost, {border:"1px solid #3a2a2a", background:"transparent", color:"#e8a6a6", borderRadius:8, padding:"4px 8px", fontSize:11}), onClick:function(){ store.deleteFromLibrary(entry.id); }, disabled: !!isBusy}, t("common.delete"))
|
|
1354
1462
|
)
|
|
1355
1463
|
);
|
|
@@ -1357,7 +1465,12 @@ window.__ModuleLoader__.load({
|
|
|
1357
1465
|
)
|
|
1358
1466
|
);
|
|
1359
1467
|
}
|
|
1360
|
-
return h("div",{style:S.overlay,
|
|
1468
|
+
return h("div",{style:S.overlay,
|
|
1469
|
+
onClick:function(e){ if(e.target===e.currentTarget) store.setPanel(false); },
|
|
1470
|
+
// 鼠标一操作就收起键盘焦点环:否则「键盘 Tab 聚焦 A → 鼠标点顶部胶囊切步」时,
|
|
1471
|
+
// A 的光环会一直挂在那儿,看起来又是一个「选中」。这里只清我们自己的环,不抢焦点
|
|
1472
|
+
// (输入框/文本域照常能点进去)。
|
|
1473
|
+
onMouseDown:function(){ if(focusKey) setFocusKey(null); }},
|
|
1361
1474
|
h("div",{style:S.workshop, role:"dialog", "aria-modal":"true"},
|
|
1362
1475
|
h("div",{style:S.head},
|
|
1363
1476
|
h("div",{style:{display:"flex", gap:12, alignItems:"center", flexWrap:"wrap"}},
|
|
@@ -1365,10 +1478,20 @@ window.__ModuleLoader__.load({
|
|
|
1365
1478
|
h("span",{style:S.badge}, t("panel.badge")),
|
|
1366
1479
|
h("div",{style:{display:"flex", gap:6, alignItems:"center"}}, stepsEls)
|
|
1367
1480
|
),
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
)
|
|
1481
|
+
// 工具条右侧只留关闭:这里原本还有一枚「⬇ 导出 JSON」(与导出页里的「⬇ JSON」重复,0.4.1 归一)。
|
|
1482
|
+
// 关闭做成 ✕ 图标按钮:不再占「收起」两个字的宽度,悬停给红底 —— 红色是「离开 / 收起」的通用暗示,
|
|
1483
|
+
// 平时保持低调的描边样式。hover 用局部 state(内联样式没有伪类),鼠标按下仍不抢焦点(同步骤项)。
|
|
1484
|
+
h("button",{type:"button", title:t("action.close"), "aria-label":t("action.close"),
|
|
1485
|
+
style:{ appearance:"none", boxSizing:"border-box", width:30, height:30, display:"flex", alignItems:"center", justifyContent:"center",
|
|
1486
|
+
padding:0, font:"inherit", fontSize:15, lineHeight:1, cursor:"pointer", borderRadius:8,
|
|
1487
|
+
background: hoverKey==="close"? "#e05555" : "transparent",
|
|
1488
|
+
border:"1px solid "+(hoverKey==="close"? "#e05555" : "var(--dsw-alias-border-l1)"),
|
|
1489
|
+
color: hoverKey==="close"? "#fff" : "var(--dsw-alias-label-primary)",
|
|
1490
|
+
transition:"background .12s ease, border-color .12s ease, color .12s ease" },
|
|
1491
|
+
onMouseDown:stopFocusSteal,
|
|
1492
|
+
onMouseEnter:function(){ setHoverKey("close"); },
|
|
1493
|
+
onMouseLeave:function(){ setHoverKey(null); },
|
|
1494
|
+
onClick:function(){ store.setPanel(false); }}, "✕")
|
|
1372
1495
|
),
|
|
1373
1496
|
(function(){
|
|
1374
1497
|
var st = s.draftStatus;
|
|
@@ -1404,27 +1527,36 @@ window.__ModuleLoader__.load({
|
|
|
1404
1527
|
navItem(0, t("step.world"), t("step.world.desc")),
|
|
1405
1528
|
navItem(1, t("step.char"), t("step.char.desc")),
|
|
1406
1529
|
navItem(2, t("step.lore"), t("step.lore.desc")),
|
|
1407
|
-
navItem(3, t("step.export"), t("step.export.desc")),
|
|
1408
1530
|
h("div",{style:{marginTop:"auto", paddingTop:12, borderTop:"1px solid var(--dsw-alias-border-l1)", display:"flex", flexDirection:"column", gap:8}},
|
|
1409
|
-
|
|
1531
|
+
// 「校验导出」入口:这一步原本是导航里的第 4 项,与这枚按钮重复,0.4.1 合并成一枚。
|
|
1532
|
+
// 点它切到导出页(校验按钮在那一页里)。
|
|
1533
|
+
// ⚠️ 它**不做选中态**:曾经给过淡紫底 + aria-current,结果从右侧卡片点「导出」时,
|
|
1534
|
+
// 这枚按钮和卡片那枚「已切到 ✓」会同时发亮 —— 又是「两处看起来像选中」(0.4.0 修过的同一个坑)。
|
|
1535
|
+
// 「现在在导出页」靠中栏标题表达(标题里写着卡名),不靠按钮。
|
|
1536
|
+
h("button",{type:"button", style: Object.assign({}, S.btn, {padding:"6px 10px", fontSize:12}),
|
|
1537
|
+
onMouseDown:function(e){ if(e && e.preventDefault) e.preventDefault(); }, // 鼠标点击不抢焦点,免得又留下一圈焦点描边(同左侧步骤项)
|
|
1538
|
+
onClick:function(){ store.setCur(3); }}, t("step.export")),
|
|
1539
|
+
// 清空是破坏性操作:先过二次确认才动手;工坊里没有撤销,所以按钮用危险色并与「校验」分开放
|
|
1540
|
+
h("button",{type:"button", style: Object.assign({}, S.btn, S.btnGhost, {padding:"6px 10px", fontSize:12, borderColor:"rgba(224,85,85,.55)", color:"#e05555"}),
|
|
1541
|
+
onMouseDown:function(e){ if(e && e.preventDefault) e.preventDefault(); },
|
|
1542
|
+
onClick:function(){ if(confirmClear(t)) store.clearDraft(); }}, t("action.clear")),
|
|
1410
1543
|
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", textAlign:"center"}}, s.busy? s.busy: (s.lastMsg||t("workshop.library.noCcHint")))
|
|
1411
1544
|
)
|
|
1412
1545
|
),
|
|
1413
|
-
h("div",{style:S.main},
|
|
1546
|
+
h("div",{style:S.main},
|
|
1547
|
+
main,
|
|
1548
|
+
// 三块阅读辅助收到主区底部、默认收起:它们原来占着右侧 360px 一整栏,把阅读区压得很窄
|
|
1549
|
+
foldPanel("preview", t("workshop.preview.title"),
|
|
1550
|
+
h("span",{style:{display:"flex", gap:6, alignItems:"center", flexShrink:0}},
|
|
1551
|
+
h("span",{style:S.badge}, t("common.spec")),
|
|
1552
|
+
h("span",{style:Object.assign({}, S.badge, s.valid.valid? {color:"#a6e8c8", borderColor:"var(--dsw-alias-state-success-primary)"}:{color:"#e8a6a6", borderColor:"var(--dsw-alias-state-error-primary)"})}, s.valid.valid? t("common.passed"):t("common.failed"))),
|
|
1553
|
+
h("pre",{style:{whiteSpace:"pre-wrap", wordBreak:"break-all", background:"var(--dsw-alias-bg-base)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:10, padding:12, maxHeight:420, overflow:"auto", margin:0, fontSize:11, lineHeight:1.6}}, JSON.stringify(s.draft, null, 2))),
|
|
1554
|
+
foldPanel("cbs", t("workshop.preview.cbsTitle"), null,
|
|
1555
|
+
h("div",null,
|
|
1556
|
+
h("div",{style:{display:"flex", flexWrap:"wrap", gap:6}}, ["{{char}}","{{user}}","{{random:A,B}}","{{roll:d6}}",t("workshop.preview.cbsComment")].map(function(c){ return h("span",{key:c, style:S.chip}, c); })),
|
|
1557
|
+
h("div",{style:{color:"var(--dsw-alias-label-secondary)", fontSize:12, marginTop:8}}, t("workshop.preview.cbsHint")))),
|
|
1558
|
+
),
|
|
1414
1559
|
renderLibrary(),
|
|
1415
|
-
h("div",{style:S.side},
|
|
1416
|
-
h("div",{style:S.card}, h("h3",{style:{margin:"0 0 8px", fontSize:13}},t("workshop.preview.title")),
|
|
1417
|
-
h("div",{style:{display:"flex", flexWrap:"wrap", gap:6, marginBottom:8}}, h("span",{style:S.badge},t("common.spec")), h("span",{style: Object.assign({}, S.badge, s.valid.valid? {color:"#a6e8c8", borderColor:"var(--dsw-alias-state-success-primary)"}:{color:"#e8a6a6", borderColor:"var(--dsw-alias-state-error-primary)"})}, s.valid.valid? t("common.passed"):t("common.failed"))),
|
|
1418
|
-
h("pre",{style:{whiteSpace:"pre-wrap", wordBreak:"break-all", background:"var(--dsw-alias-bg-base)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:10, padding:10, maxHeight:420, overflow:"auto", margin:0, fontSize:11}}, JSON.stringify(s.draft, null, 2))
|
|
1419
|
-
),
|
|
1420
|
-
h("div",{style:S.card}, h("h3",{style:{margin:"0 0 8px", fontSize:13}},t("workshop.preview.cbsTitle")),
|
|
1421
|
-
h("div",{style:{display:"flex", flexWrap:"wrap", gap:6}}, ["{{char}}","{{user}}","{{random:A,B}}","{{roll:d6}}",t("workshop.preview.cbsComment")].map(function(c){ return h("span",{key:c, style:S.chip}, c); })),
|
|
1422
|
-
h("div",{style:{color:"var(--dsw-alias-label-secondary)", fontSize:12, marginTop:8}}, t("workshop.preview.cbsHint"))
|
|
1423
|
-
),
|
|
1424
|
-
h("div",{style:S.card}, h("h3",{style:{margin:"0 0 8px", fontSize:13}},t("workshop.preview.fusionTitle")),
|
|
1425
|
-
h("div",{style:{color:"var(--dsw-alias-label-secondary)", fontSize:12, lineHeight:1.5}}, t("workshop.preview.fusionDesc"))
|
|
1426
|
-
)
|
|
1427
|
-
)
|
|
1428
1560
|
)
|
|
1429
1561
|
),
|
|
1430
1562
|
s.editingWorld ? h("div",{style:Object.assign({}, S.overlay, {background:"rgba(0,0,0,.55)", zIndex:40}), onClick:function(e){ if(e.target===e.currentTarget) store.setEditingWorld(null); }},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xia-sc/dsh-cc-studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "CCv3 角色卡工坊:融合工坊 + CC 模式 + JSON/PNG/CHARX + 中英双语(跟随全局)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
},
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"type": "module",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=22"
|
|
27
|
+
},
|
|
25
28
|
"main": "lib/index.js",
|
|
26
29
|
"exports": {
|
|
27
30
|
".": "./lib/index.js",
|
|
@@ -35,7 +38,7 @@
|
|
|
35
38
|
"presets"
|
|
36
39
|
],
|
|
37
40
|
"scripts": {
|
|
38
|
-
"test": "node tests/rpc-channel.test.mjs && node tests/preset-install.test.mjs && node tests/cc-detection.test.mjs && node tests/client-greetings.test.mjs && node tests/draft-slot-sync.test.mjs && node tests/data-root.test.mjs",
|
|
41
|
+
"test": "node tests/rpc-channel.test.mjs && node tests/preset-install.test.mjs && node tests/cc-detection.test.mjs && node tests/client-greetings.test.mjs && node tests/draft-slot-sync.test.mjs && node tests/nav-highlight.test.mjs && node tests/clear-card.test.mjs && node tests/export-entry.test.mjs && node tests/close-button.test.mjs && node tests/data-root.test.mjs",
|
|
39
42
|
"prepublishOnly": "npm test"
|
|
40
43
|
},
|
|
41
44
|
"dsh": {
|
|
@@ -52,11 +55,20 @@
|
|
|
52
55
|
"@deepseek-ai/dsh-client-ui-conversation",
|
|
53
56
|
"@deepseek-ai/dsh-client-ui-layout"
|
|
54
57
|
]
|
|
58
|
+
},
|
|
59
|
+
"compatibility": {
|
|
60
|
+
"dsh": ">=0.1.5-rc.1",
|
|
61
|
+
"dshReleases": {
|
|
62
|
+
"0.1.5-rc.1": "compatible",
|
|
63
|
+
"0.1.5-rc.2": "compatible",
|
|
64
|
+
"0.1.6-alpha.1": "compatible",
|
|
65
|
+
"0.1.6-alpha.2": "compatible",
|
|
66
|
+
"0.1.7-alpha.1": "compatible",
|
|
67
|
+
"0.1.7-rc.1": "compatible",
|
|
68
|
+
"0.2.0-rc.2": "compatible"
|
|
69
|
+
}
|
|
55
70
|
}
|
|
56
71
|
},
|
|
57
|
-
"dependencies": {
|
|
58
|
-
"@deepseek-ai/cordis": "^4.0.1"
|
|
59
|
-
},
|
|
60
72
|
"publishConfig": {
|
|
61
73
|
"access": "public"
|
|
62
74
|
}
|