dsh-code-ui 1.1.3 → 1.1.5

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 CHANGED
@@ -16,7 +16,7 @@
16
16
  - 🌐 翻译(创建翻译引用,交给 AI 翻译)
17
17
  - 🔗 引用给 AI
18
18
  - **引用 chip**:引用后输入框上方显示 `文件名(起行-止行)` + ✕,删除引用
19
- - **内嵌 AI 输入框**:直接输入、`Ctrl+Enter` 发送,发送时自动附带已引用的完整代码
19
+ - **内嵌 AI 输入框**:直接输入、`Ctrl+Enter` 发送;发送时不粘贴引用正文,只附一行引用指针,内容由 AI 调用 `get_referenced_snippets` 工具读取
20
20
  - **主题跟随**:使用 DSH 的 `--dsw-alias-*` 语义变量,浅色/深色自动切换
21
21
  - **窗口化/全屏**:可拖拽、可缩放、可全屏/还原
22
22
  - **以此文件夹为根**:文件浏览器里可选任意目录作为工作区根
@@ -52,7 +52,7 @@ npm install dsh-code-ui
52
52
  1. 点顶部 `Code UI` 进入编辑器
53
53
  2. 在文件树/浏览器里打开文件
54
54
  3. 选中代码右键 → 引用 / 翻译 / 备注 / 复制
55
- 4. 底部输入框直接发消息(`Ctrl+Enter` 发送),已引用的代码会自动附带
55
+ 4. 底部输入框直接发消息(`Ctrl+Enter` 发送);引用只以标签显示,正文由 AI 工具读取
56
56
  5. 也可对 AI 说「读取我引用的代码」,AI 会调用 `get_referenced_snippets` 工具
57
57
 
58
58
  ## 目录结构
@@ -89,6 +89,21 @@ npm publish
89
89
 
90
90
  发布前确认 `name` 未被占用,必要时改为 scope 如 `@yourname/dsh-code-ui`。
91
91
 
92
+ ## 更新日志
93
+
94
+ > 约定:每次版本升级在本节顶部追加一行(时间 | 版本 | 修复/新增内容)。
95
+
96
+ | 时间 | 版本 | 修复 / 新增 |
97
+ | --- | --- | --- |
98
+ | 2026-08-31 | v1.1.5 | 新增:无会话时输入框给出明确提示(常驻警告行 + 置灰按钮点击弹原因 + Ctrl+Enter 兜底提示) |
99
+ | 2026-08-28 | v1.1.4 | 新增:卡片内编辑器启用/禁用开关,切换即时挂载/卸载(无需重启/刷新),状态持久化 |
100
+ | 2026-08-28 | v1.1.3 | 改进:引用/翻译统一标签制--翻译引用加 🌐 标签,引用不再把代码正文粘贴进消息,AI 经工具读取 |
101
+ | 2026-08-28 | v1.1.2 | 改进:插件配置卡片样式与宿主卡片(PluginCard/fields)逐条统一 |
102
+ | 2026-08-28 | v1.1.1 | 修复:插件配置卡片不显示--补 host 端 settings 命名空间注册 + client inject 边 |
103
+ | 2026-08-27 | v1.1.0 | 新增:检查更新 / 一键更新 / 本地包安装(pnpm 流式进度、失败诊断、防降级) |
104
+ | 2026-08-27 | v1.0.1 | 改进:标准化 npm 插件包结构(dsh.bundle 声明、可 `dsh plugin add` 安装) |
105
+ | 2026-08-26 | v1.0.0 | 初版:Cursor 风格工作区(文件树、多标签、语法高亮、引用/备注/翻译、内嵌 AI 输入框) |
106
+
92
107
  ## License
93
108
 
94
109
  MIT
package/lib/client.js CHANGED
@@ -146,6 +146,8 @@ window.__ModuleLoader__.load({
146
146
  .cdex-aiinput-ta::placeholder{color:var(--dsw-alias-label-secondary,#8a8a8a);}
147
147
  .cdex-aiinput-ta:disabled{opacity:.55;}
148
148
  .cdex-aiinput-send{flex:0 0 auto;height:36px;min-width:76px;border:none;background:var(--dsw-alias-brand-primary,#0e639c);color:#ffffff;border-radius:999px;padding:0 18px;cursor:pointer;font-size:13px;font-weight:600;transition:filter .15s ease, opacity .15s ease;}
149
+ .cdex-aiinput-send.ghosted{opacity:.4;cursor:not-allowed;}
150
+ .cdex-aiinput-warn{margin:10px 12px 0;padding:6px 10px;border-radius:8px;background:rgba(230,162,60,.12);border:1px solid rgba(230,162,60,.4);color:#e6a23c;font-size:12px;line-height:1.5;}
149
151
  .cdex-aiinput-send:hover:not(:disabled){filter:brightness(1.12);}
150
152
  .cdex-aiinput-send:disabled{opacity:.4;cursor:not-allowed;}
151
153
  .cdex-aiinput-foot{display:flex;align-items:center;gap:12px;padding:8px 14px 12px;color:var(--dsw-alias-label-secondary,#8a8a8a);font-size:12px;border-top:1px solid transparent;}
@@ -746,7 +748,11 @@ window.__ModuleLoader__.load({
746
748
  const citeCount = refs.filter((r) => r.kind === 'cite').length
747
749
  const trCount = refs.length - citeCount
748
750
  const submit = () => {
749
- if (!st.inputActions || !taEl) return
751
+ if (!st.inputActions || !taEl) {
752
+ // 无会话时明确告知原因,而不是无声无息
753
+ if (!st.inputActions) showToast('请先打开一个会话再发送:在主界面新建或选择会话后,这里即可发送')
754
+ return
755
+ }
750
756
  const text = taEl.value
751
757
  if (!text.trim() && refs.length === 0) return
752
758
  const payload = buildSendPayload(text)
@@ -756,6 +762,7 @@ window.__ModuleLoader__.load({
756
762
  }
757
763
  return h('div', { className: 'cdex-aiinput-wrap' },
758
764
  h('div', { className: 'cdex-aiinput-card' },
765
+ disabled ? h('div', { className: 'cdex-aiinput-warn' }, '⚠ 未打开会话:请先在主界面新建或选择一个会话,之后即可在此发送(引用标签会保留)') : null,
759
766
  refs.length > 0 ? h('div', { className: 'cdex-aiinput-tags' },
760
767
  refs.map((r) => h('span', { className: 'cdex-aiinput-tag' + (r.kind === 'translate' ? ' cdex-aiinput-tag-tr' : ''), key: r.id },
761
768
  h('span', { className: 'cdex-aiinput-tag-label' }, (r.kind === 'translate' ? '🌐 翻译 ' : '🔗 ') + tagLabel(r)),
@@ -766,7 +773,7 @@ window.__ModuleLoader__.load({
766
773
  h('textarea', {
767
774
  className: 'cdex-aiinput-ta',
768
775
  ref: (el) => { taEl = el },
769
- placeholder: disabled ? '请先打开一个会话' : '给 AI 发消息…(Ctrl+Enter 发送)',
776
+ placeholder: disabled ? '请先打开一个会话(在主界面新建或选择会话后即可发送)' : '给 AI 发消息…(Ctrl+Enter 发送)',
770
777
  disabled: disabled,
771
778
  onKeyDown: (e) => {
772
779
  if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
@@ -775,7 +782,16 @@ window.__ModuleLoader__.load({
775
782
  }
776
783
  },
777
784
  }),
778
- h('button', { className: 'cdex-aiinput-send', disabled: disabled, onClick: submit }, '发送'),
785
+ // 不用 HTML disabled(收不到点击):置灰样式 + 点击弹原因,点了就有反馈
786
+ h('button', {
787
+ className: 'cdex-aiinput-send' + (disabled ? ' ghosted' : ''),
788
+ 'aria-disabled': disabled,
789
+ title: disabled ? '请先打开一个会话' : '发送(Ctrl+Enter)',
790
+ onClick: () => {
791
+ if (disabled) { showToast('请先打开一个会话再发送:在主界面新建或选择会话后,这里即可发送'); return }
792
+ submit()
793
+ },
794
+ }, '发送'),
779
795
  ),
780
796
  h('div', { className: 'cdex-aiinput-foot' },
781
797
  h('span', null, 'Ctrl+Enter 发送'),
@@ -1087,22 +1103,44 @@ window.__ModuleLoader__.load({
1087
1103
  )
1088
1104
  }
1089
1105
 
1090
- /* ============ §8 装配与 slot 注册 ============ */
1091
- slots.inject('conversation.session.header.actions', () => slots.register(
1092
- { name: 'conversation.session.header.actions', id: 'cdex-input-bridge', order: 9999, label: '' },
1093
- (props) => h(InputBridge, props),
1094
- ))
1095
- slots.inject('shell.overlay', () => {
1096
- const d1 = slots.register(
1097
- { name: 'shell.overlay', id: 'cdex-mode-tabs', order: 5, label: '界面模式' },
1098
- () => h(ModeTabs, null),
1099
- )
1100
- const d2 = slots.register(
1101
- { name: 'shell.overlay', id: 'cdex-workbench', order: 10, label: '代码编辑器' },
1102
- () => h(Workbench, null),
1103
- )
1104
- return () => { if (d1) d1(); if (d2) d2() }
1105
- })
1106
+ /* ============ §8 装配与 slot 注册(受「启用开关」控制,可动态挂/卸载) ============ */
1107
+ // 开关存 host 端 settings(enabled 字段,持久化)。默认启用:
1108
+ // get-self 失败 / 字段缺失(旧 host)时绝不隐藏编辑器,只有明确 enabled===false 才不挂载。
1109
+ let unmountEditorFns = null
1110
+ function mountEditor() {
1111
+ if (unmountEditorFns) return
1112
+ const disposers = []
1113
+ disposers.push(slots.inject('conversation.session.header.actions', () => slots.register(
1114
+ { name: 'conversation.session.header.actions', id: 'cdex-input-bridge', order: 9999, label: '' },
1115
+ (props) => h(InputBridge, props),
1116
+ )))
1117
+ disposers.push(slots.inject('shell.overlay', () => {
1118
+ const d1 = slots.register(
1119
+ { name: 'shell.overlay', id: 'cdex-mode-tabs', order: 5, label: '界面模式' },
1120
+ () => h(ModeTabs, null),
1121
+ )
1122
+ const d2 = slots.register(
1123
+ { name: 'shell.overlay', id: 'cdex-workbench', order: 10, label: '代码编辑器' },
1124
+ () => h(Workbench, null),
1125
+ )
1126
+ return () => { if (d1) d1(); if (d2) d2() }
1127
+ }))
1128
+ unmountEditorFns = () => { disposers.forEach((d) => { try { d() } catch (e) {} }) }
1129
+ }
1130
+ function unmountEditor() {
1131
+ if (!unmountEditorFns) return
1132
+ try { unmountEditorFns() } catch (e) {}
1133
+ unmountEditorFns = null
1134
+ }
1135
+ // 卡片里切换开关 -> set-enabled 成功后调用:立即挂/卸载编辑器(无需刷新页面、无需重启 DSH)
1136
+ function applyEnabled(v) {
1137
+ if (v) mountEditor()
1138
+ else unmountEditor()
1139
+ }
1140
+ // 启动时先查开关再挂载(RPC 失败 = 默认启用)
1141
+ callHost('get-self').then((r) => {
1142
+ applyEnabled(!(r && !r.error && r.enabled === false))
1143
+ }).catch(() => applyEnabled(true))
1106
1144
 
1107
1145
  /* ---- 插件配置卡片(settings.plugin.item):信息 + 检查更新 / 一键更新 / 本地包更新 ---- */
1108
1146
  function PluginConfigCard() {
@@ -1111,9 +1149,27 @@ window.__ModuleLoader__.load({
1111
1149
  const [chk, setChk] = React.useState(null)
1112
1150
  const [run, setRun] = React.useState(null)
1113
1151
  const [localPath, setLocalPath] = React.useState('')
1152
+ const [enabling, setEnabling] = React.useState(false)
1114
1153
  const lastPayloadRef = React.useRef(null)
1115
1154
  const pollDisposeRef = React.useRef(null)
1116
1155
 
1156
+ // 编辑器开关:写 host settings 持久化;成功后立即挂/卸载编辑器 UI
1157
+ function toggleEnabled() {
1158
+ if (!self || enabling) return
1159
+ const next = self.enabled === false
1160
+ setEnabling(true)
1161
+ callHost('set-enabled', { enabled: next }).then((r) => {
1162
+ setEnabling(false)
1163
+ if (r && !r.error) {
1164
+ setSelf(Object.assign({}, self, { enabled: next }))
1165
+ applyEnabled(next)
1166
+ showToast(next ? '已启用编辑器(立即生效)' : '已禁用编辑器(立即生效)')
1167
+ } else {
1168
+ showToast('切换失败:' + ((r && r.error) || '未知错误'))
1169
+ }
1170
+ })
1171
+ }
1172
+
1117
1173
  function stopPoll() {
1118
1174
  if (pollDisposeRef.current) { try { pollDisposeRef.current() } catch (e) {} pollDisposeRef.current = null }
1119
1175
  }
@@ -1186,6 +1242,19 @@ window.__ModuleLoader__.load({
1186
1242
  : h('span', { className: 'cdx-pc-chevron' + (cardOpen ? ' cdx-pc-chevron-open' : '') }, '▾'),
1187
1243
  ),
1188
1244
  cardOpen ? h('div', { className: 'cdx-pc-body' },
1245
+ // 字段零:编辑器启用开关
1246
+ h('div', { className: 'cdx-pc-field' },
1247
+ h('div', { className: 'cdx-pc-head' },
1248
+ h('span', { className: 'cdx-pc-label' }, '编辑器开关(页面入口 + 编辑器整体)'),
1249
+ h('button', {
1250
+ type: 'button',
1251
+ className: 'cdx-pc-btn ' + (self && self.enabled !== false ? 'cdx-pc-btn-outline' : 'cdx-pc-btn-primary'),
1252
+ disabled: !self || enabling,
1253
+ onClick: toggleEnabled,
1254
+ }, enabling ? '切换中…' : (self && self.enabled !== false ? '已启用 · 点击禁用' : '已禁用 · 点击启用')),
1255
+ ),
1256
+ h('p', { className: 'cdx-pc-note' }, '切换立即生效(实时挂载/卸载编辑器),无需重启 DSH,也无需刷新页面;若个别界面未跟着变化,普通刷新(F5)即可。开关状态持久化保存。'),
1257
+ ),
1189
1258
  // 字段一:在线检查更新
1190
1259
  h('div', { className: 'cdx-pc-field' },
1191
1260
  h('div', { className: 'cdx-pc-head' },
package/lib/index.js CHANGED
@@ -94,16 +94,20 @@ const CODE_UI_SETTINGS_NS = settingsNamespace("dsh-code-ui");
94
94
  const CodeUiSettingsSchema = z.object({
95
95
  // 检查更新用的 registry(只影响在线「检查」;安装仍走 profile 自己的 .npmrc)
96
96
  updateRegistry: z.string().default(REGISTRY_DEFAULT),
97
+ // 编辑器总开关:false 时 client 端不挂载编辑器 UI(配置卡片始终保留,可随时再开)
98
+ enabled: z.boolean().default(true),
97
99
  });
98
100
  // 解析后的当前值:settings 服务未挂载/未写入时保持默认
99
- const selfSettings = { updateRegistry: REGISTRY_DEFAULT };
101
+ const selfSettings = { updateRegistry: REGISTRY_DEFAULT, enabled: true };
102
+ // settings 服务引用(「启用/禁用」开关写入用;由 installSelfSettings 捕获)
103
+ let settingsServiceRef = null;
100
104
  function installSelfSettings(ctx) {
101
- let source = () => ({ updateRegistry: selfSettings.updateRegistry });
105
+ let source = () => ({ updateRegistry: selfSettings.updateRegistry, enabled: selfSettings.enabled });
102
106
  installSettingsSection(
103
107
  ctx,
104
108
  CODE_UI_SETTINGS_NS,
105
109
  CodeUiSettingsSchema,
106
- { updateRegistry: selfSettings.updateRegistry },
110
+ { updateRegistry: selfSettings.updateRegistry, enabled: selfSettings.enabled },
107
111
  {
108
112
  setSource: (current) => { source = current; },
109
113
  onChange: () => {
@@ -112,10 +116,13 @@ function installSelfSettings(ctx) {
112
116
  if (typeof v.updateRegistry === "string" && /^https?:\/\//.test(v.updateRegistry)) {
113
117
  selfSettings.updateRegistry = v.updateRegistry.replace(/\/+$/, "");
114
118
  }
119
+ if (typeof v.enabled === "boolean") selfSettings.enabled = v.enabled;
115
120
  } catch (_) {}
116
121
  },
117
122
  },
118
123
  );
124
+ // 捕获 settings 服务实例:卡片的「启用/禁用」开关经 set-enabled RPC 写入本命名空间
125
+ ctx.inject(["settings"], (sctx) => { settingsServiceRef = sctx.settings; });
119
126
  }
120
127
 
121
128
  async function fetchLatestVersion(name, registry) {
@@ -463,9 +470,25 @@ function rpcHandler(ctx) {
463
470
  profileRoot: profileRoot,
464
471
  workspace: profileRoot ? existsSync(join(profileRoot, "pnpm-workspace.yaml")) : false,
465
472
  updateRegistry: selfSettings.updateRegistry,
473
+ enabled: selfSettings.enabled !== false,
466
474
  });
467
475
  }
468
476
 
477
+ if (endpoint === "set-enabled") {
478
+ if (typeof args.enabled !== "boolean") return ok({ error: "enabled 参数需为布尔值" });
479
+ if (!settingsServiceRef || typeof settingsServiceRef.update !== "function") {
480
+ return ok({ error: "settings 服务不可用,重启 DSH 后再试" });
481
+ }
482
+ try {
483
+ // 写入命名空间(schema 校验 + 持久化;watch 会经 onChange 再同步一次 selfSettings)
484
+ await settingsServiceRef.update(CODE_UI_SETTINGS_NS, { enabled: args.enabled });
485
+ selfSettings.enabled = args.enabled;
486
+ return ok({ ok: true, enabled: args.enabled });
487
+ } catch (err) {
488
+ return ok({ error: "写入设置失败:" + ((err && err.message) || String(err)) });
489
+ }
490
+ }
491
+
469
492
  if (endpoint === "check-update") {
470
493
  const out = { current: SELF_VERSION, latest: null, updateAvailable: false, notPublished: false, registryUsed: null, error: null };
471
494
  // 主源 = 设置里的 updateRegistry(默认官方源,镜像有同步滞后);主源不可达时用另一个兜底做「检查」
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-code-ui",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Cursor 风格的 AI 代码编辑器工作台插件(DSH):文件树、多标签页编辑、引用/备注/翻译、内嵌 AI 输入框、语法高亮",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",