dsh-novel-writer 2.5.1 → 2.6.0

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.en.md CHANGED
@@ -8,7 +8,7 @@ English | [**中文**](./README.md)
8
8
  [![GitHub release](https://img.shields.io/github/v/release/siweina/dsh-novel-writer.svg?style=flat-square)](https://github.com/siweina/dsh-novel-writer/releases)
9
9
  [![DSH plugin](https://img.shields.io/badge/DSH-plugin-4b8bbe.svg?style=flat-square)](https://github.com/deepseek-ai/deepseek-harness)
10
10
 
11
- A novel-writing assistant plugin for **DeepSeek Harness (DSH)** (v2.5.0): chapter library management, sentence-pattern analysis, emotion purification & quantification, **12-axis vibe spectrum**, **style portrait report**, plot & settings management, local semantic search (0 token), webnovel signal detection, batch import, and AI-assisted continuation writing. Zero third-party dependencies on host.
11
+ A novel-writing assistant plugin for **DeepSeek Harness (DSH)** (v2.6.0): chapter library management, sentence-pattern analysis, emotion purification & quantification, **12-axis vibe spectrum**, **style portrait report**, plot & settings management, local semantic search (0 token), webnovel signal detection, batch import, and AI-assisted continuation writing. Zero third-party dependencies on host. **Requires Node ≥ 22.3.**
12
12
 
13
13
  ---
14
14
 
@@ -34,14 +34,14 @@ After installing, **restart the web app** to activate (host registers 15 tools +
34
34
 
35
35
  ## Features
36
36
 
37
- 1. **Style portrait report** (`novel_style_report`, v2.5.0): 6-dimension measurement — style fingerprint / high-frequency lexicon / genre-theme / emotion quantification / 12-axis vibe spectrum / semantic style distance. **Measurement-judgment separation**: the plugin only reports numbers, never labels; AI judgment can be saved back to `.novel-writer/style-reports/` for consistent continuation writing.
38
- 2. **12-axis vibe spectrum** (v2.5.0): nightmare / angst / heartwarming / fluff / tearjerker / dark / mystery / blaze / absurd / lonesome / aesthetic / sensual — with traceable evidence, 0 token.
39
- 3. **Local semantic engine** (v2.0.0): bge-small-zh Chinese model (23MB, shipped with the plugin) local CPU inference — `novel_semantic_search` finds semantically related passages with natural language, semantic style comparison, semantic implicit emotion; lazy loading + graceful fallback.
37
+ 1. **Style portrait report** (`novel_style_report`): 6-dimension measurement — style fingerprint / high-frequency lexicon / genre-theme / emotion quantification / 12-axis vibe spectrum / semantic style distance. **Measurement-judgment separation**: the plugin only reports numbers, never labels; AI judgment can be saved back to `.novel-writer/style-reports/` for consistent continuation writing.
38
+ 2. **12-axis vibe spectrum**: nightmare / angst / heartwarming / fluff / tearjerker / dark / mystery / blaze / absurd / lonesome / aesthetic / sensual — with traceable evidence, 0 token.
39
+ 3. **Local semantic engine**: bge-small-zh Chinese model (23MB, shipped with the plugin) local CPU inference — `novel_semantic_search` finds semantically related passages with natural language (with chapter location), semantic style comparison, semantic implicit emotion; lazy loading + graceful fallback.
40
40
  4. **Sentence-pattern analysis**: 9 categories, arrangement patterns, rhythm, emotion curve, style fingerprint + guidance, with cache & report export.
41
41
  5. **Emotion purification & quantification**: strong/weak emotion-word grading, pollution detection, caveat warning + AI re-verification; Valence sliding window → variance V / delta Δ / conflict index C + implicit imagery carriers.
42
- 6. **Worldview & pragmatics detection**: auto cultural-baseline detection with confidence; speechStyle title/honorifics/rituals/tone norms; genre & theme + webnovel signals (v2.2).
42
+ 6. **Worldview & pragmatics detection**: auto cultural-baseline detection with confidence; speechStyle title/honorifics/rituals/tone norms; genre & theme + webnovel signals.
43
43
  7. **Writing toolkit**: plot tracking / five settings tables (characters·locations·items·timeline·worldview) / chapter summaries / continuity audit / batch import / style check / continuation writing.
44
- 8. **Per-tool UI toggles**: "Writing Assistant" sidebar panel (master + grouped tool toggles + feature toggles), plain-language labels.
44
+ 8. **Per-tool UI toggles**: "Writing Assistant" sidebar panel (master + grouped tool toggles + feature toggles), plain-language labels, data-dir usage & semantic-engine status display.
45
45
 
46
46
  ---
47
47
 
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  [![GitHub release](https://img.shields.io/github/v/release/siweina/dsh-novel-writer.svg?style=flat-square)](https://github.com/siweina/dsh-novel-writer/releases)
9
9
  [![DSH plugin](https://img.shields.io/badge/DSH-plugin-4b8bbe.svg?style=flat-square)](https://github.com/deepseek-ai/deepseek-harness)
10
10
 
11
- 为 **DeepSeek Harness (DSH)** 打造的小说写作助手插件(v2.5.0):章节库管理、句式分析、情感净化与量化、氛围光谱、**风格画像报告**、伏笔设定管理、本地语义检索(0 token)、网文信号识别、批量导入与 AI 续写辅助。宿主端零第三方依赖,浏览器端仅依赖 Web GUI 自带的 react
11
+ 为 **DeepSeek Harness (DSH)** 打造的小说写作助手插件(v2.6.0):章节库管理、句式分析、情感净化与量化、氛围光谱、**风格画像报告**、伏笔设定管理、本地语义检索(0 token)、网文信号识别、批量导入与 AI 续写辅助。宿主端零第三方依赖,浏览器端仅依赖 Web GUI 自带的 react。**要求 Node ≥ 22.3。**
12
12
 
13
13
  ---
14
14
 
@@ -34,14 +34,14 @@ dsh plugin --profile web add github:siweina/dsh-novel-writer#main
34
34
 
35
35
  ## 功能
36
36
 
37
- 1. **风格画像报告**(novel_style_report,v2.5.0):6 维测量报告——文风指纹 / 高频词汇 / 题材流派 / 情感量化 / 氛围光谱 12 轴 / 语义风格距离。**测量与判断分离**:插件只报数不贴标签,AI 判断可回传存盘(`.novel-writer/style-reports/`),续写保持风格一致。
38
- 2. **氛围光谱 12 轴**(v2.5.0):噩梦感 / 焦虑压抑 / 温馨治愈 / 甜宠日常 / 催泪虐心 / 黑暗残酷 / 悬疑神秘 / 热血激昂 / 荒诞无厘头 / 孤独疏离 / 文艺唯美 / 情欲暧昧——证据链可追溯,0 token。
39
- 3. **本地语义引擎**(v2.0.0):bge-small-zh 中文模型(23MB 随插件分发)本地 CPU 推理——`novel_semantic_search` 自然语言搜全书语义相关段落,语义级风格对比、语义隐性情感,懒加载 + 自动回退。
37
+ 1. **风格画像报告**(novel_style_report):6 维测量报告——文风指纹 / 高频词汇 / 题材流派 / 情感量化 / 氛围光谱 12 轴 / 语义风格距离。**测量与判断分离**:插件只报数不贴标签,AI 判断可回传存盘(`.novel-writer/style-reports/`),续写保持风格一致。
38
+ 2. **氛围光谱 12 轴**:噩梦感 / 焦虑压抑 / 温馨治愈 / 甜宠日常 / 催泪虐心 / 黑暗残酷 / 悬疑神秘 / 热血激昂 / 荒诞无厘头 / 孤独疏离 / 文艺唯美 / 情欲暧昧——证据链可追溯,0 token。
39
+ 3. **本地语义引擎**:bge-small-zh 中文模型(23MB 随插件分发)本地 CPU 推理——`novel_semantic_search` 自然语言搜全书语义相关段落(带章节定位),语义级风格对比、语义隐性情感,懒加载 + 自动回退。
40
40
  4. **句式模式分析**:九类句式分布、排列规律、句长节奏、情感曲线、风格指纹与节奏建议,带缓存与报告导出。
41
41
  5. **情感净化 + 量化**:强/弱情绪词分级、污染源检测、caveat 预警 + AI 复核;Valence 滑动窗口 → 方差 V / 斜率 Δ / 矛盾指数 C + 隐性意象载体。
42
- 6. **世界观与语用检测**:文化基准自动判断(西/东/混合)+ 置信度;speechStyle 称谓/客套/仪式/语气规范;题材流派 + 网文信号(v2.2)。
42
+ 6. **世界观与语用检测**:文化基准自动判断(西/东/混合)+ 置信度;speechStyle 称谓/客套/仪式/语气规范;题材流派 + 网文信号。
43
43
  7. **写作辅助全家桶**:伏笔登记表 / 设定五张表(人物·地点·道具·时间线·世界观)/ 章节摘要 / 连贯性审计 / 批量导入 / 风格自检 / 续写辅助。
44
- 8. **全工具 UI 开关**:侧边栏「写作助手功能」面板(总开关 + 工具开关分组 + 功能开关),大白话文案。
44
+ 8. **全工具 UI 开关**:侧边栏「写作助手功能」面板(总开关 + 工具开关分组 + 功能开关),大白话文案,显示数据目录占用与语义引擎状态。
45
45
 
46
46
  ---
47
47
 
package/lib/client.js CHANGED
@@ -20,116 +20,137 @@ window.__ModuleLoader__.load({
20
20
  let react_dom_client = require("react-dom/client");
21
21
 
22
22
  // ---- 样式 ----
23
- var css = "[data-pane=conversation],[class*=centerCol]{position:relative}" +
24
- "[data-dsh-novel-writer-view]{position:absolute;inset:0;z-index:60;background:var(--dsw-alias-bg-base,#fff);display:none;overflow:auto}" +
25
- "html[data-dsh-novel-writer-active]:not([data-dsh-ssh-active]):not([data-dsh-taskboard-active]) [data-dsh-novel-writer-view]{display:block}" +
26
- "html[data-dsh-novel-writer-active]:not([data-dsh-ssh-active]):not([data-dsh-taskboard-active]) [data-pane=conversation]>:not([data-dsh-novel-writer-view])," +
27
- "html[data-dsh-novel-writer-active]:not([data-dsh-ssh-active]):not([data-dsh-taskboard-active]) [class*=centerCol]>:not([data-dsh-novel-writer-view]){display:none!important}" +
28
- ".nwEntry{width:100%;height:32px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:8px;padding:0 12px;font-size:13px;display:flex}" +
29
- ".nwEntry:hover{background:var(--dsw-specific-sidebar-nav-item-hover);color:var(--dsw-alias-label-primary)}" +
30
- ".nwEntry[data-active]{background:var(--dsw-specific-sidebar-nav-item-active);color:var(--dsw-alias-label-primary);font-weight:600}" +
31
- ".nwEntryIcon{flex:none;justify-content:center;align-items:center;display:inline-flex;width:16px;font-size:13px}" +
32
- ".nwEntryLabel{text-overflow:ellipsis;overflow:hidden}" +
33
- "[data-dsh-frame][data-sidebar-collapsed] .nwEntry{justify-content:center;width:100%;padding:0}" +
34
- "[data-dsh-frame][data-sidebar-collapsed] .nwEntryLabel{display:none}" +
35
- ".nwPanel{max-width:560px;margin:0 auto;padding:28px 24px;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);display:flex;flex-direction:column;gap:10px}" +
36
- ".nwPanelHeader{display:flex;align-items:center;gap:10px}" +
37
- ".nwPanelTitle{font-size:16px;font-weight:700;flex:1;min-width:0}" +
38
- ".nwClose{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);width:30px;height:30px;line-height:1}" +
39
- ".nwRefresh{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1;padding:5px 9px}" +
40
- ".nwRefresh:hover{background:var(--dsw-alias-bg-layer-2)}" +
41
- ".nwDesc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.6;margin:0}" +
42
- ".nwBanner{display:flex;align-items:center;gap:10px;border-radius:12px;padding:14px 16px;font-size:15px;font-weight:700;border:1px solid}" +
43
- ".nwBannerOn{background:rgba(34,197,94,.12);border-color:#22c55e;color:#15803d}" +
44
- ".nwBannerOff{background:rgba(239,68,68,.10);border-color:#ef4444;color:#b91c1c}" +
45
- ".nwBannerIcon{flex:none;font-size:18px;line-height:1}" +
46
- ".nwBannerSub{font-size:12px;font-weight:500;opacity:.9;margin-left:6px}" +
47
- ".nwRow{display:flex;align-items:center;gap:12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:12px;padding:12px 14px;transition:border-color .18s,background .18s}" +
48
- ".nwRowOn{border-color:#22c55e;background:rgba(34,197,94,.07)}" +
49
- ".nwRowOff{border-color:var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2)}" +
50
- ".nwRowText{flex:1;min-width:0}" +
51
- ".nwRowLabel{font-size:13px;font-weight:600;line-height:1.5}" +
52
- ".nwRowHint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;margin-top:2px}" +
53
- ".nwSwitchWrap{flex:none;display:flex;align-items:center;gap:8px}" +
54
- ".nwSwitch{appearance:none;flex:none;width:56px;height:30px;border-radius:999px;border:2px solid #9ca3af;background:#9ca3af;cursor:pointer;position:relative;padding:0;transition:background .18s,border-color .18s,box-shadow .18s}" +
55
- ".nwSwitch:hover{box-shadow:0 0 0 4px rgba(156,163,175,.20)}" +
56
- ".nwSwitch:focus-visible{outline:2px solid #22c55e;outline-offset:2px}" +
57
- ".nwSwitchOn{background:#22c55e;border-color:#22c55e;box-shadow:0 0 0 4px rgba(34,197,94,.18)}" +
58
- ".nwSwitchOn:hover{box-shadow:0 0 0 4px rgba(34,197,94,.28)}" +
59
- ".nwSwitchKnob{position:absolute;top:2px;left:2px;width:22px;height:22px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.3);transition:left .18s}" +
60
- ".nwSwitchOn .nwSwitchKnob{left:28px}" +
61
- ".nwSwitch:disabled{opacity:.5;cursor:default;box-shadow:none}" +
62
- ".nwBadge{flex:none;border-radius:999px;padding:3px 10px;font-size:12px;font-weight:700;white-space:nowrap;letter-spacing:.5px}" +
63
- ".nwBadgeOn{background:#22c55e;color:#fff}" +
64
- ".nwBadgeOff{background:#e5e7eb;color:#6b7280}" +
65
- ".nwSectionTitle{font-size:13px;font-weight:700;margin-top:6px}" +
66
- ".nwNavEntry{display:flex;align-items:center;justify-content:space-between;width:100%;box-sizing:border-box;padding:8px 10px;margin:4px 0;border:1px solid var(--dsw-alias-border);border-radius:8px;background:var(--dsw-alias-surface-1);cursor:pointer;text-align:left;color:inherit;font:inherit}" +
67
- ".nwNavEntry:hover{border-color:#22c55e}" +
68
- ".nwNavEntryText{flex:1;min-width:0;padding-right:8px}" +
69
- ".nwNavEntryTitle{font-size:13px;font-weight:600}" +
70
- ".nwNavEntryHint{font-size:11px;color:var(--dsw-alias-label-tertiary);margin-top:2px}" +
71
- ".nwNavEntryRight{display:flex;align-items:center;gap:6px;flex:none}" +
72
- ".nwNavEntryRight .nwBadge{white-space:nowrap;flex:none}" +
73
- ".nwNavEntryArrow{font-size:18px;color:var(--dsw-alias-label-tertiary);flex:none}" +
74
- ".nwBackBtn{font-size:12px;padding:3px 8px;margin:2px 0 6px;border:1px solid var(--dsw-alias-border);border-radius:6px;background:var(--dsw-alias-surface-1);cursor:pointer}" +
75
- ".nwModelBtn{font-size:11px;padding:2px 6px;margin-top:4px;border:1px solid #7c3aed;color:#7c3aed;border-radius:6px;background:transparent;cursor:pointer}" +
76
- ".nwRefreshSpin{animation:nwspin 1s linear infinite}" +
77
- "@keyframes nwspin{from{transform:rotate(0)}to{transform:rotate(360deg)}}" +
78
- ".nwPlotOk{color:#22c55e}" +
79
- ".nwFlash{animation:nwflash 1.2s ease}" +
80
- "@keyframes nwflash{0%{background:rgba(34,197,94,.25)}100%{background:transparent}}" +
81
- ".nwToolRight{display:flex;flex-direction:column;align-items:flex-end;gap:4px}" +
82
- ".nwToolRowRaw{border-color:#ef4444}" +
83
- ".nwToolLabelRaw{color:#ef4444;font-weight:700}" +
84
- ".nwRawDanger{color:#ef4444!important;font-weight:600}" +
85
- ".nwRawOn{font-size:11px;color:#ef4444;margin-top:2px;font-weight:600}" +
86
- ".nwModal{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:9999}" +
87
- ".nwModalBox{background:var(--dsw-alias-surface-1,#fff);border-radius:12px;padding:18px 20px;max-width:420px;width:90%;box-shadow:0 8px 30px rgba(0,0,0,.3)}" +
88
- ".nwModalTitle{font-size:15px;font-weight:700;margin-bottom:10px}" +
89
- ".nwModalText{font-size:13px;line-height:1.7;color:var(--dsw-alias-label-secondary);white-space:pre-line;margin-bottom:12px}" +
90
- ".nwModalInput{width:100%;box-sizing:border-box;padding:8px 10px;font-size:13px;border:1px solid var(--dsw-alias-border);border-radius:8px;background:var(--dsw-alias-surface-2,#fafafa);color:inherit;margin-bottom:12px}" +
91
- ".nwModalBtns{display:flex;gap:10px;justify-content:flex-end}" +
92
- ".nwBtnDanger{background:#ef4444;color:#fff;border-color:#ef4444}" +
93
- ".nwToolItem{width:100%;margin-bottom:2px}" +
94
- ".nwToolItem .nwPlotBox{width:100%;box-sizing:border-box}" +
95
- ".nwToolsHint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;margin:2px 0 4px}" +
96
- ".nwToolRow{display:flex;align-items:center;gap:10px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;padding:8px 12px}" +
97
- ".nwToolRowOn{border-color:rgba(34,197,94,.55);background:rgba(34,197,94,.05)}" +
98
- ".nwToolLabel{flex:1;min-width:0;font-size:12px;line-height:1.4;color:var(--dsw-alias-label-primary)}" +
99
- ".nwSwitchSmall{appearance:none;flex:none;width:40px;height:22px;border-radius:999px;border:2px solid #9ca3af;background:#9ca3af;cursor:pointer;position:relative;padding:0;transition:background .16s,border-color .16s}" +
100
- ".nwSwitchSmallOn{background:#22c55e;border-color:#22c55e}" +
101
- ".nwSwitchSmallKnob{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:#fff;transition:left .16s}" +
102
- ".nwSwitchSmallOn .nwSwitchSmallKnob{left:20px}" +
103
- ".nwToolDesc{font-size:11px;line-height:1.45;color:var(--dsw-alias-label-tertiary);margin-top:2px}" +
104
- ".nwPlotBox{margin-top:6px;padding:6px 8px;background:rgba(127,127,127,.08);border-radius:8px}" +
105
- ".nwPlotPath{font-size:11px;line-height:1.5;color:var(--dsw-alias-label-secondary);word-break:break-all}" +
106
- ".nwBtnGroup{display:flex;gap:6px;margin-top:6px}" +
107
- ".nwBtn{font-size:11px;line-height:1;padding:5px 10px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer}" +
108
- ".nwBtn:hover{background:var(--dsw-alias-bg-layer-2)}" +
109
- ".nwBtn:disabled{opacity:.45;cursor:not-allowed}" +
110
- ".nwPlotMsg{font-size:11px;line-height:1.5;color:var(--dsw-alias-label-secondary);margin-top:4px;word-break:break-all}" +
111
- ".nwPlotErr{color:#ef4444}" +
112
- ".nwStatus{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;word-break:break-all}" +
113
- ".nwFoot{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.6;border-top:1px solid var(--dsw-alias-border-l2);padding-top:10px;margin-top:4px}" +
114
- ".nwToolGroup{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-bottom:8px;overflow:hidden}" +
115
- ".nwToolGroupHead{display:flex;align-items:center;gap:8px;width:100%;padding:9px 12px;background:rgba(127,127,127,.08);border:none;cursor:pointer;font-size:13px;text-align:left;color:inherit}" +
116
- ".nwToolGroupHead:hover{background:rgba(127,127,127,.14)}" +
117
- ".nwToolGroupIcon{font-size:15px}" +
118
- ".nwToolGroupName{font-weight:600}" +
119
- ".nwToolGroupBadge{margin-left:auto;font-size:11px;color:#c62828;background:rgba(198,40,40,.1);border-radius:10px;padding:1px 7px}" +
120
- ".nwToolGroupArrow{font-size:12px;opacity:.6}" +
121
- ".nwToolGroupBody{padding:4px 8px 8px}" +
122
- ".nwToolGroupToggle{flex:none;width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:0}" +
123
- ".nwGroupState{width:16px;height:16px;border-radius:4px;border:2px solid #9ca3af;display:inline-flex;align-items:center;justify-content:center;position:relative;overflow:hidden;background:#fff}" +
124
- ".nwGroupStateOn{border-color:#22c55e;background:#22c55e}" +
125
- ".nwGroupStatePartial{border-color:#22c55e;background:#fff}" +
126
- ".nwGroupStateOff{border-color:#9ca3af;background:#fff}" +
127
- ".nwGroupStateCheck{color:#fff;font-size:11px;font-weight:700;line-height:1}" +
128
- ".nwGroupStateCheckPartial{width:10px;height:10px;border-radius:2px;background:#22c55e;color:transparent}" +
129
- ".nwToolGroupHead{display:flex;align-items:center;gap:6px;width:100%;padding:9px 12px;background:rgba(127,127,127,.08);border:none;cursor:default;font-size:13px;text-align:left;color:inherit}" +
130
- ".nwToolGroupHeadBtn{flex:1;display:flex;align-items:center;gap:8px;background:none;border:none;cursor:pointer;padding:0;text-align:left;color:inherit;font-size:13px}" +
131
- ".nwToolGroupHeadText{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}" +
132
- ".nwToolGroupDescInline{font-size:11px;line-height:1.4;color:var(--dsw-alias-label-tertiary);font-weight:400;white-space:normal}";;
23
+ var css = `
24
+
25
+ /* ===== 杂项 ===== */
26
+ [data-pane=conversation],[class*=centerCol]{position:relative}
27
+ [data-dsh-novel-writer-view]{position:absolute;inset:0;z-index:60;background:var(--dsw-alias-bg-base,#fff);display:none;overflow:auto}
28
+ html[data-dsh-novel-writer-active]:not([data-dsh-ssh-active]):not([data-dsh-taskboard-active]) [data-dsh-novel-writer-view]{display:block}
29
+ html[data-dsh-novel-writer-active]:not([data-dsh-ssh-active]):not([data-dsh-taskboard-active]) [data-pane=conversation]>:not([data-dsh-novel-writer-view]),
30
+ html[data-dsh-novel-writer-active]:not([data-dsh-ssh-active]):not([data-dsh-taskboard-active]) [class*=centerCol]>:not([data-dsh-novel-writer-view]){display:none!important}
31
+ [data-dsh-frame][data-sidebar-collapsed] .nwEntry{justify-content:center;width:100%;padding:0}
32
+ [data-dsh-frame][data-sidebar-collapsed] .nwEntryLabel{display:none}
33
+ .nwSectionTitle{font-size:13px;font-weight:700;margin-top:6px}
34
+ .nwNavEntry{display:flex;align-items:center;justify-content:space-between;width:100%;box-sizing:border-box;padding:8px 10px;margin:4px 0;border:1px solid var(--dsw-alias-border);border-radius:8px;background:var(--dsw-alias-surface-1);cursor:pointer;text-align:left;color:inherit;font:inherit}
35
+ .nwNavEntry:hover{border-color:#22c55e}
36
+ .nwNavEntryText{flex:1;min-width:0;padding-right:8px}
37
+ .nwNavEntryTitle{font-size:13px;font-weight:600}
38
+ .nwNavEntryHint{font-size:11px;color:var(--dsw-alias-label-tertiary);margin-top:2px}
39
+ .nwNavEntryRight{display:flex;align-items:center;gap:6px;flex:none}
40
+ .nwNavEntryRight .nwBadge{white-space:nowrap;flex:none}
41
+ .nwNavEntryArrow{font-size:18px;color:var(--dsw-alias-label-tertiary);flex:none}
42
+ .nwBackBtn{font-size:12px;padding:3px 8px;margin:2px 0 6px;border:1px solid var(--dsw-alias-border);border-radius:6px;background:var(--dsw-alias-surface-1);cursor:pointer}
43
+ .nwModelBtn{font-size:11px;padding:2px 6px;margin-top:4px;border:1px solid #7c3aed;color:#7c3aed;border-radius:6px;background:transparent;cursor:pointer}
44
+ .nwToolRight{display:flex;flex-direction:column;align-items:flex-end;gap:4px}
45
+
46
+ /* ===== 导航/入口 ===== */
47
+ .nwEntry{width:100%;height:32px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:8px;padding:0 12px;font-size:13px;display:flex}
48
+ .nwEntry:hover{background:var(--dsw-specific-sidebar-nav-item-hover);color:var(--dsw-alias-label-primary)}
49
+ .nwEntry[data-active]{background:var(--dsw-specific-sidebar-nav-item-active);color:var(--dsw-alias-label-primary);font-weight:600}
50
+ .nwEntryIcon{flex:none;justify-content:center;align-items:center;display:inline-flex;width:16px;font-size:13px}
51
+ .nwEntryLabel{text-overflow:ellipsis;overflow:hidden}
52
+ .nwPanel{max-width:560px;margin:0 auto;padding:28px 24px;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);display:flex;flex-direction:column;gap:10px}
53
+ .nwPanelHeader{display:flex;align-items:center;gap:10px}
54
+ .nwPanelTitle{font-size:16px;font-weight:700;flex:1;min-width:0}
55
+ .nwClose{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);width:30px;height:30px;line-height:1}
56
+ .nwRefresh{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1;padding:5px 9px}
57
+ .nwRefresh:hover{background:var(--dsw-alias-bg-layer-2)}
58
+ .nwDesc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.6;margin:0}
59
+ .nwRefreshSpin{animation:nwspin 1s linear infinite}
60
+
61
+ /* ===== 横幅与开关 ===== */
62
+ .nwBanner{display:flex;align-items:center;gap:10px;border-radius:12px;padding:14px 16px;font-size:15px;font-weight:700;border:1px solid}
63
+ .nwBannerOn{background:rgba(34,197,94,.12);border-color:#22c55e;color:#15803d}
64
+ .nwBannerOff{background:rgba(239,68,68,.10);border-color:#ef4444;color:#b91c1c}
65
+ .nwBannerIcon{flex:none;font-size:18px;line-height:1}
66
+ .nwBannerSub{font-size:12px;font-weight:500;opacity:.9;margin-left:6px}
67
+ .nwRow{display:flex;align-items:center;gap:12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:12px;padding:12px 14px;transition:border-color .18s,background .18s}
68
+ .nwRowOn{border-color:#22c55e;background:rgba(34,197,94,.07)}
69
+ .nwRowOff{border-color:var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2)}
70
+ .nwRowText{flex:1;min-width:0}
71
+ .nwRowLabel{font-size:13px;font-weight:600;line-height:1.5}
72
+ .nwRowHint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;margin-top:2px}
73
+ .nwSwitchWrap{flex:none;display:flex;align-items:center;gap:8px}
74
+ .nwSwitch{appearance:none;flex:none;width:56px;height:30px;border-radius:999px;border:2px solid #9ca3af;background:#9ca3af;cursor:pointer;position:relative;padding:0;transition:background .18s,border-color .18s,box-shadow .18s}
75
+ .nwSwitch:hover{box-shadow:0 0 0 4px rgba(156,163,175,.20)}
76
+ .nwSwitch:focus-visible{outline:2px solid #22c55e;outline-offset:2px}
77
+ .nwSwitchOn{background:#22c55e;border-color:#22c55e;box-shadow:0 0 0 4px rgba(34,197,94,.18)}
78
+ .nwSwitchOn:hover{box-shadow:0 0 0 4px rgba(34,197,94,.28)}
79
+ .nwSwitchKnob{position:absolute;top:2px;left:2px;width:22px;height:22px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.3);transition:left .18s}
80
+ .nwSwitchOn .nwSwitchKnob{left:28px}
81
+ .nwSwitch:disabled{opacity:.5;cursor:default;box-shadow:none}
82
+ .nwSwitchSmall{appearance:none;flex:none;width:40px;height:22px;border-radius:999px;border:2px solid #9ca3af;background:#9ca3af;cursor:pointer;position:relative;padding:0;transition:background .16s,border-color .16s}
83
+ .nwSwitchSmallOn{background:#22c55e;border-color:#22c55e}
84
+ .nwSwitchSmallKnob{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:#fff;transition:left .16s}
85
+ .nwSwitchSmallOn .nwSwitchSmallKnob{left:20px}
86
+
87
+ /* ===== 徽章 ===== */
88
+ .nwBadge{flex:none;border-radius:999px;padding:3px 10px;font-size:12px;font-weight:700;white-space:nowrap;letter-spacing:.5px}
89
+ .nwBadgeOn{background:#22c55e;color:#fff}
90
+ .nwBadgeOff{background:#e5e7eb;color:#6b7280}
91
+
92
+ /* ===== 动画与杂项 ===== */
93
+ @keyframes nwspin{from{transform:rotate(0)}to{transform:rotate(360deg)}}
94
+ @keyframes nwflash{0%{background:rgba(34,197,94,.25)}100%{background:transparent}}
95
+
96
+ /* ===== 路径/按钮/状态 ===== */
97
+ .nwPlotOk{color:#22c55e}
98
+ .nwFlash{animation:nwflash 1.2s ease}
99
+ .nwBtnDanger{background:#ef4444;color:#fff;border-color:#ef4444}
100
+ .nwPlotBox{margin-top:6px;padding:6px 8px;background:rgba(127,127,127,.08);border-radius:8px}
101
+ .nwPlotPath{font-size:11px;line-height:1.5;color:var(--dsw-alias-label-secondary);word-break:break-all}
102
+ .nwBtnGroup{display:flex;gap:6px;margin-top:6px}
103
+ .nwBtn{font-size:11px;line-height:1;padding:5px 10px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer}
104
+ .nwBtn:hover{background:var(--dsw-alias-bg-layer-2)}
105
+ .nwBtn:disabled{opacity:.45;cursor:not-allowed}
106
+ .nwPlotMsg{font-size:11px;line-height:1.5;color:var(--dsw-alias-label-secondary);margin-top:4px;word-break:break-all}
107
+ .nwPlotErr{color:#ef4444}
108
+ .nwStatus{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;word-break:break-all}
109
+ .nwFoot{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.6;border-top:1px solid var(--dsw-alias-border-l2);padding-top:10px;margin-top:4px}
110
+ .nwGroupState{width:16px;height:16px;border-radius:4px;border:2px solid #9ca3af;display:inline-flex;align-items:center;justify-content:center;position:relative;overflow:hidden;background:#fff}
111
+ .nwGroupStateOn{border-color:#22c55e;background:#22c55e}
112
+ .nwGroupStatePartial{border-color:#22c55e;background:#fff}
113
+ .nwGroupStateOff{border-color:#9ca3af;background:#fff}
114
+ .nwGroupStateCheck{color:#fff;font-size:11px;font-weight:700;line-height:1}
115
+ .nwGroupStateCheckPartial{width:10px;height:10px;border-radius:2px;background:#22c55e;color:transparent}
116
+
117
+ /* ===== 非净化模式 ===== */
118
+ .nwToolRowRaw{border-color:#ef4444}
119
+ .nwToolLabelRaw{color:#ef4444;font-weight:700}
120
+ .nwRawDanger{color:#ef4444!important;font-weight:600}
121
+ .nwRawOn{font-size:11px;color:#ef4444;margin-top:2px;font-weight:600}
122
+
123
+ /* ===== 弹窗 ===== */
124
+ .nwModal{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:9999}
125
+ .nwModalBox{background:var(--dsw-alias-surface-1,#fff);border-radius:12px;padding:18px 20px;max-width:420px;width:90%;box-shadow:0 8px 30px rgba(0,0,0,.3)}
126
+ .nwModalTitle{font-size:15px;font-weight:700;margin-bottom:10px}
127
+ .nwModalText{font-size:13px;line-height:1.7;color:var(--dsw-alias-label-secondary);white-space:pre-line;margin-bottom:12px}
128
+ .nwModalInput{width:100%;box-sizing:border-box;padding:8px 10px;font-size:13px;border:1px solid var(--dsw-alias-border);border-radius:8px;background:var(--dsw-alias-surface-2,#fafafa);color:inherit;margin-bottom:12px}
129
+ .nwModalBtns{display:flex;gap:10px;justify-content:flex-end}
130
+
131
+ /* ===== 工具行/开关/说明 ===== */
132
+ .nwToolItem{width:100%;margin-bottom:2px}
133
+ .nwToolItem .nwPlotBox{width:100%;box-sizing:border-box}
134
+ .nwToolsHint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;margin:2px 0 4px}
135
+ .nwToolRow{display:flex;align-items:center;gap:10px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;padding:8px 12px}
136
+ .nwToolRowOn{border-color:rgba(34,197,94,.55);background:rgba(34,197,94,.05)}
137
+ .nwToolLabel{flex:1;min-width:0;font-size:12px;line-height:1.4;color:var(--dsw-alias-label-primary)}
138
+ .nwToolDesc{font-size:11px;line-height:1.45;color:var(--dsw-alias-label-tertiary);margin-top:2px}
139
+
140
+ /* ===== 工具组 ===== */
141
+ .nwToolGroup{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-bottom:8px;overflow:hidden}
142
+ .nwToolGroupHead{display:flex;align-items:center;gap:6px;width:100%;padding:9px 12px;background:rgba(127,127,127,.08);border:none;cursor:default;font-size:13px;text-align:left;color:inherit}
143
+ .nwToolGroupHead:hover{background:rgba(127,127,127,.14)}
144
+ .nwToolGroupIcon{font-size:15px}
145
+ .nwToolGroupName{font-weight:600}
146
+ .nwToolGroupBadge{margin-left:auto;font-size:11px;color:#c62828;background:rgba(198,40,40,.1);border-radius:10px;padding:1px 7px}
147
+ .nwToolGroupArrow{font-size:12px;opacity:.6}
148
+ .nwToolGroupBody{padding:4px 8px 8px}
149
+ .nwToolGroupToggle{flex:none;width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:0}
150
+ .nwToolGroupHeadBtn{flex:1;display:flex;align-items:center;gap:8px;background:none;border:none;cursor:pointer;padding:0;text-align:left;color:inherit;font-size:13px}
151
+ .nwToolGroupHeadText{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
152
+ .nwToolGroupDescInline{font-size:11px;line-height:1.4;color:var(--dsw-alias-label-tertiary);font-weight:400;white-space:normal}
153
+ `;
133
154
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"dsh-novel-writer\"]") === null) {
134
155
  var tag = document.createElement("style");
135
156
  tag.dataset.plugin = "dsh-novel-writer";
@@ -263,7 +284,14 @@ window.__ModuleLoader__.load({
263
284
  "card.statusOff": "写作助手功能:已关闭",
264
285
  "card.statusAutoOff": "写作助手功能:已启用 · 自动分析:关",
265
286
  "card.hint": "全部开关统一在侧边栏「写作助手功能」面板(也可让 AI 用 novel_sentence_config 调整),本卡片仅显示状态。",
266
- "card.open": "打开开关面板"
287
+ "card.open": "打开开关面板",
288
+ "dir.size": "数据目录占用",
289
+ "model.engine": "语义引擎(本地模型)",
290
+ "model.engineReady": "可用 · 模型已加载",
291
+ "model.engineIdle": "可用 · 未加载(首次使用时自动加载)",
292
+ "model.engineMissing": "模型文件缺失,语义功能不可用",
293
+ "model.engineError": "加载失败:{err}",
294
+ "model.engineUnknown": "状态未知"
267
295
  };
268
296
  var en = {
269
297
  "entry.label": "Writing Assistant",
@@ -380,7 +408,14 @@ window.__ModuleLoader__.load({
380
408
  "card.statusOff": "Writing assistant: OFF",
381
409
  "card.statusAutoOff": "Writing assistant: ON · auto-analyze: OFF",
382
410
  "card.hint": "All switches live in the sidebar 'Writing Assistant' panel (or ask the AI to run novel_sentence_config); this card only shows status.",
383
- "card.open": "Open the switch panel"
411
+ "card.open": "Open the switch panel",
412
+ "dir.size": "Data dir usage",
413
+ "model.engine": "Semantic engine (local model)",
414
+ "model.engineReady": "Ready · model loaded",
415
+ "model.engineIdle": "Ready · lazy-loaded on first use",
416
+ "model.engineMissing": "Model files missing, semantic features unavailable",
417
+ "model.engineError": "Load failed: {err}",
418
+ "model.engineUnknown": "Unknown"
384
419
  };
385
420
  function dictionary() {
386
421
  return (typeof document !== "undefined" && (document.documentElement.lang || "zh").toLowerCase().startsWith("en")) ? en : zh;
@@ -427,6 +462,8 @@ window.__ModuleLoader__.load({
427
462
  panelOpen: false,
428
463
  plotsDir: "",
429
464
  dataDir: "",
465
+ dataDirSize: 0,
466
+ embeddingStatus: null,
430
467
  features: {},
431
468
  dirs: null,
432
469
  view: "main",
@@ -462,6 +499,8 @@ window.__ModuleLoader__.load({
462
499
  features: remote.features || {},
463
500
  plotsDir: remote.plotsDir || "",
464
501
  dataDir: remote.dataDir || "",
502
+ dataDirSize: remote.dataDirSize || 0,
503
+ embeddingStatus: remote.embeddingStatus || null,
465
504
  dirs: remote.dirs || null,
466
505
  file: remote.file || "",
467
506
  hostOk: true,
@@ -684,6 +723,20 @@ window.__ModuleLoader__.load({
684
723
  )
685
724
  );
686
725
  };
726
+ function formatSize(bytes) {
727
+ if (!bytes || bytes <= 0) return "0 B";
728
+ var units = ["B", "KB", "MB", "GB"];
729
+ var i = 0, v = bytes;
730
+ while (v >= 1024 && i < units.length - 1) { v /= 1024; i++; }
731
+ return v.toFixed(v >= 10 || i === 0 ? 0 : 1) + " " + units[i];
732
+ }
733
+ function engineStatusText(st, t) {
734
+ if (!st) return t("model.engineUnknown");
735
+ if (!st.modelPresent) return t("model.engineMissing");
736
+ if (st.loaded) return t("model.engineReady");
737
+ if (st.error) return String(t("model.engineError")).replace("{err}", String(st.error).slice(0, 80));
738
+ return t("model.engineIdle");
739
+ }
687
740
  var pathBox = function (label, dir, target) {
688
741
  if (!dir) {
689
742
  return el("div", { className: "nwPlotBox nwPlotBoxEmpty" },
@@ -824,6 +877,13 @@ var TOOL_GROUPS = [
824
877
  backBtn(),
825
878
  el("div", { className: "nwSectionTitle" }, "🖥 " + t("model.title")),
826
879
  el("div", { className: "nwToolsHint" }, t("model.hint")),
880
+ // v2.6.0:语义引擎状态(模型就绪/未加载/缺失/失败)
881
+ el("div", { className: "nwRow nwRowOff" },
882
+ el("div", { className: "nwRowText" },
883
+ el("div", { className: "nwRowLabel" }, t("model.engine")),
884
+ el("div", { className: "nwRowHint" }, engineStatusText(state.embeddingStatus, t))
885
+ )
886
+ ),
827
887
  switchRow("semanticSearch", f.semanticSearch !== false, function (n, v) { props.toggle({ features: (function (patch) { patch[n] = v; return patch; })({}) }); }),
828
888
  switchRow("semanticStyle", f.semanticStyle !== false, function (n, v) { props.toggle({ features: (function (patch) { patch[n] = v; return patch; })({}) }); }),
829
889
  switchRow("semanticImplicit", f.semanticImplicit !== false, function (n, v) { props.toggle({ features: (function (patch) { patch[n] = v; return patch; })({}) }); }),
@@ -870,6 +930,13 @@ var TOOL_GROUPS = [
870
930
  })(),
871
931
  entry(t("panel.featuresTitle"), t("panel.featuresHint"), onFeatures, "features"),
872
932
  entry(t("panel.toolsTitle"), t("panel.toolsHint"), onTools, "tools"),
933
+ // v2.6.0:数据目录占用 + 语义引擎状态(一目了然)
934
+ el("div", { className: "nwRow nwRowOff" },
935
+ el("div", { className: "nwRowText" },
936
+ el("div", { className: "nwRowLabel" }, t("dir.size")),
937
+ el("div", { className: "nwRowHint" }, formatSize(state.dataDirSize) + " · " + engineStatusText(state.embeddingStatus, t))
938
+ )
939
+ ),
873
940
  pathBox(t("dir.data"), dirs.dataDir || state.dataDir || "", "data-dir")
874
941
  );
875
942
  }
@@ -1052,6 +1119,9 @@ var TOOL_GROUPS = [
1052
1119
  features: remote.features || {},
1053
1120
  plotsDir: remote.plotsDir || "",
1054
1121
  dataDir: remote.dataDir || "",
1122
+ // v2.6.0:初始加载同样接入数据目录占用 + 语义引擎状态(与 refresh 白名单一致)
1123
+ dataDirSize: remote.dataDirSize || 0,
1124
+ embeddingStatus: remote.embeddingStatus || null,
1055
1125
  dirs: remote.dirs || null,
1056
1126
  file: remote.file || "",
1057
1127
  source: "host",