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 +17 -2
- package/lib/client.js +88 -19
- package/lib/index.js +26 -3
- package/package.json +1 -1
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)
|
|
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 ? '
|
|
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
|
-
|
|
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
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
)
|
|
1095
|
-
|
|
1096
|
-
const
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
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(默认官方源,镜像有同步滞后);主源不可达时用另一个兜底做「检查」
|