dsh-claude-workbench 0.1.2 → 0.2.2
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 +4 -2
- package/README.md +6 -2
- package/lib/client.js +298 -8
- package/package.json +4 -2
package/README.en.md
CHANGED
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/dsh-claude-workbench) [](LICENSE)
|
|
4
4
|
|
|
5
|
-
A Claude-flavoured workbench enhancement for the **DeepSeek Harness (DSH)** Web GUI — one plugin,
|
|
5
|
+
A Claude-flavoured workbench enhancement for the **DeepSeek Harness (DSH)** Web GUI — one plugin, six features:
|
|
6
6
|
|
|
7
7
|
1. **Claude skin** — warm cream canvas, warm stone surfaces and clay accents; serif **Crimson Pro** prose / **DM Sans** UI (fonts inlined, works offline), complete light/dark/system support.
|
|
8
8
|
2. **Split view** — the “◫ Split view” pill at the bottom-right splits the window into 2–3 full DSH panes, each with its own session view (the same idea as Claude’s Split view).
|
|
9
9
|
3. **Side chat** — select text in a conversation → right-click → **“在旁聊中提问”**; a docked right-hand panel boots a fork of the current session with the selection quoted in the composer. Closing archives the fork.
|
|
10
10
|
4. **Quick appearance switch** — a theme button at the top-right of the session header toggles follow-system / light / dark (same as Settings → General → Appearance, persisted).
|
|
11
|
-
5. **Completion chime** — a short, crisp chime when a turn finishes (like OpenCode’s completion sound); toggle it in the appearance menu and the choice is remembered.
|
|
11
|
+
5. **Completion chime** — a short, crisp chime when a turn finishes (like OpenCode’s completion sound); toggle it in the appearance menu, adjust its **volume** with the slider (0–250%, default 150%, previews while dragging), and the choice is remembered.
|
|
12
|
+
6. **Inline next-step chips** — the assistant ends a message with a `suggestions` fenced block (a JSON array); the plugin renders it as clickable chips under the message and clicking one sends its prompt directly — the Claude-style next-step buttons. Blocking choices still use the built-in `ask_user_question` form card.
|
|
12
13
|
|
|
13
14
|

|
|
14
15
|

|
|
15
16
|

|
|
16
17
|

|
|
18
|
+

|
|
17
19
|
|
|
18
20
|
> Preview images are **synthetic renders** (plugin styles + mock data), not real conversations.
|
|
19
21
|
|
package/README.md
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/dsh-claude-workbench) [](LICENSE)
|
|
4
4
|
|
|
5
|
-
给 **DeepSeek Harness(DSH)Web GUI** 的 Claude 风格工作台增强包 ——
|
|
5
|
+
给 **DeepSeek Harness(DSH)Web GUI** 的 Claude 风格工作台增强包 —— 一个插件,六件事:
|
|
6
6
|
|
|
7
7
|
1. **Claude 皮肤**:暖米白画布 + 暖石灰 + 陶土色,正文/标题用衬线 **Crimson Pro**、界面用 **DM Sans**(字体已内联,离线可用),浅色/深色/跟随系统都完整适配;
|
|
8
8
|
2. **Split view(并排会话)**:右下角「◫ Split view」→ 把窗口切成 **2 栏或 3 栏**,每栏是一个完整 DSH 界面,可各自打开/切换不同会话(对应 Claude 里的 Split view);
|
|
9
9
|
3. **旁聊(side chat)**:在对话里**选中文字 → 右键 → 「在旁聊中提问」**,右侧 dock 出当前会话的 fork(继承上下文),选中的内容以引用形式预填进输入框;关闭时自动归档该 fork;
|
|
10
10
|
4. **外观快切**:右上角主题按钮,一键在 **跟随系统 / 浅色 / 深色** 之间切换(与「设置 → 通用 → 外观」等价、持久化);
|
|
11
|
-
5. **完成提示音**:一轮对话结束时播放一声清脆提示音(类似 OpenCode
|
|
11
|
+
5. **完成提示音**:一轮对话结束时播放一声清脆提示音(类似 OpenCode 的完成提醒),可在右上角外观菜单里开关、并可拖动**音量滑块**(0–250%,默认 150%,调节时会试听),设置会记住。
|
|
12
|
+
6. **下一步建议(内联 chips)**:助手在消息末尾输出一个 `suggestions` 代码块(JSON 数组),插件把它渲染成**消息下方的可点按钮**,点一下直接把对应 prompt 发出去 —— 和 Claude 里的下一步建议同款。
|
|
13
|
+
|
|
14
|
+
> 两种"下一步"并存:**内联 chips** 用于非阻塞的 2–3 个建议(点了直接发);**表单**(内置 `ask_user_question` 卡片)用于需要阻塞确认的决策。
|
|
12
15
|
|
|
13
16
|

|
|
14
17
|

|
|
@@ -46,6 +49,7 @@ dsh plugin --profile web add github:<owner>/dsh-claude-workbench
|
|
|
46
49
|
- **旁聊**:选中对话内容 → 右键 →「◧ 在旁聊中提问」;面板左缘可拖动调宽;「在主对话打开」把 fork 提为主对话;关闭自动归档 fork。
|
|
47
50
|
- **外观**:会话 header 右上角按钮(右侧栏展开按钮左边),点开选 跟随系统/浅色/深色;菜单里还有「完成提示音」开关。
|
|
48
51
|
- **完成提示音**:默认开启;对话完成(运行中 → 空闲)时响一声;子代理会话不响。
|
|
52
|
+
- **下一步建议**:只要让我"给下一步建议",我就会在消息末尾输出 `suggestions` 块,插件渲染成按钮;点按钮直接发送。
|
|
49
53
|
|
|
50
54
|
## 兼容性
|
|
51
55
|
|
package/lib/client.js
CHANGED
|
@@ -3,7 +3,9 @@ window.__ModuleLoader__.load({
|
|
|
3
3
|
factory: (require) => {
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
|
-
const inject = [];
|
|
6
|
+
const inject = ["slots", "sessions"];
|
|
7
|
+
const react = require("react");
|
|
8
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
7
9
|
function panesUi(boot, ctx) {
|
|
8
10
|
try {
|
|
9
11
|
if (window.self !== window.top) return;
|
|
@@ -104,10 +106,21 @@ window.__ModuleLoader__.load({
|
|
|
104
106
|
'#dsh-theme-menu .dsh-theme-check{width:14px;height:14px;flex:none;margin-left:auto;color:var(--dsw-alias-state-business-primary,#c6613f);visibility:hidden}',
|
|
105
107
|
'#dsh-theme-menu button[data-active="1"] .dsh-theme-check{visibility:visible}',
|
|
106
108
|
'#dsh-theme-menu .dsh-theme-sep{height:1px;margin:4px 6px;background:var(--dsw-alias-border-l2,#0001)}',
|
|
109
|
+
'#dsh-theme-menu .dsh-theme-volume{display:flex;align-items:center;gap:8px;padding:2px 9px 7px;color:var(--dsw-alias-label-secondary,#555);font:500 12px/1 var(--dsw-font-family,system-ui)}',
|
|
110
|
+
'#dsh-theme-menu .dsh-theme-volume-label{flex:none}',
|
|
111
|
+
'#dsh-theme-menu .dsh-theme-volume input{flex:1;min-width:84px;height:14px;margin:0;accent-color:var(--dsw-alias-state-business-primary,#c6613f);cursor:pointer}',
|
|
112
|
+
'#dsh-theme-menu .dsh-theme-volume-value{flex:none;width:38px;text-align:right;color:var(--dsw-alias-label-caption,#999);font-variant-numeric:tabular-nums}',
|
|
107
113
|
'#dsh-sidechat-menu{position:fixed;z-index:2147483002;display:none;min-width:196px;padding:4px;border-radius:10px;background:var(--dsw-menu-surface-fill,#fff);border:.5px solid var(--dsw-alias-border-l3,#0002);box-shadow:0 8px 24px #00000022,0 2px 6px #00000012;backdrop-filter:blur(20px);font-family:var(--dsw-font-family,system-ui)}',
|
|
108
114
|
'#dsh-sidechat-menu button{display:flex;align-items:center;gap:8px;width:100%;height:30px;padding:0 9px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-primary,#111);font:500 12px/1 var(--dsw-font-family,system-ui);cursor:pointer;text-align:left}',
|
|
109
115
|
'#dsh-sidechat-menu button:hover{background:var(--dsw-alias-interactive-bg-hover,#00000012)}',
|
|
110
|
-
'#dsh-sidechat-menu .dsh-ctx-icon{width:16px;text-align:center;color:var(--dsw-alias-label-tertiary,#888)}'
|
|
116
|
+
'#dsh-sidechat-menu .dsh-ctx-icon{width:16px;text-align:center;color:var(--dsw-alias-label-tertiary,#888)}',
|
|
117
|
+
'#dsh-suggestions,.dsh-suggestions{display:flex;flex-wrap:wrap;align-items:center;gap:.5em;margin:1em 0 .35em}',
|
|
118
|
+
'.dsh-suggestions button{display:inline-flex;align-items:center;gap:.5em;min-height:2.4em;padding:.45em 1em;border-radius:.5em;border:1px solid var(--dsw-alias-border-l3,#0002);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#111);font-family:var(--dsw-font-family,system-ui);font-weight:500;font-size:.95em;line-height:1.3;cursor:pointer;transition:background .15s ease,border-color .15s ease,opacity .15s ease}',
|
|
119
|
+
'.dsh-suggestions button:hover{background:var(--dsw-alias-interactive-bg-hover,#00000010);border-color:var(--dsw-alias-border-l2,#0003)}',
|
|
120
|
+
'.dsh-suggestions button.dsh-suggestion-primary{background:var(--dsw-alias-label-primary,#141413);color:var(--dsw-alias-bg-base,#fbfaf6);border-color:transparent}',
|
|
121
|
+
'.dsh-suggestions button.dsh-suggestion-primary:hover{background:var(--dsw-alias-label-primary,#141413);opacity:.92}',
|
|
122
|
+
'.dsh-suggestions[data-sent="1"] button{opacity:.55;pointer-events:none}',
|
|
123
|
+
'.dsh-suggestions .dsh-suggestion-hint{font-family:var(--dsw-font-family-code,ui-monospace,SFMono-Regular,monospace);font-weight:500;font-size:.72em;line-height:1;opacity:.6}'
|
|
111
124
|
].join("\n");
|
|
112
125
|
document.head.appendChild(style);
|
|
113
126
|
document.body.setAttribute("data-dsh-sidechat", "0");
|
|
@@ -572,7 +585,8 @@ window.__ModuleLoader__.load({
|
|
|
572
585
|
'<button type="button" data-pref="light"><span class="dsh-theme-icon"></span><span>浅色</span><span class="dsh-theme-check">✓</span></button>' +
|
|
573
586
|
'<button type="button" data-pref="dark"><span class="dsh-theme-icon"></span><span>深色</span><span class="dsh-theme-check">✓</span></button>' +
|
|
574
587
|
'<div class="dsh-theme-sep"></div>' +
|
|
575
|
-
'<button type="button" data-sound="1"><span class="dsh-theme-icon">' + SOUND_ICON + '</span><span>完成提示音</span><span class="dsh-theme-check">✓</span></button>'
|
|
588
|
+
'<button type="button" data-sound="1"><span class="dsh-theme-icon">' + SOUND_ICON + '</span><span>完成提示音</span><span class="dsh-theme-check">✓</span></button>' +
|
|
589
|
+
'<div class="dsh-theme-volume"><span class="dsh-theme-volume-label">音量</span><input id="dsh-theme-volume" type="range" min="0" max="250" step="10" aria-label="完成提示音音量"><span class="dsh-theme-volume-value"></span></div>';
|
|
576
590
|
document.body.appendChild(themeMenu);
|
|
577
591
|
var themeMenuItems = Array.prototype.slice.call(themeMenu.querySelectorAll("button[data-pref]"));
|
|
578
592
|
themeMenuItems.forEach(function (button) {
|
|
@@ -582,8 +596,19 @@ window.__ModuleLoader__.load({
|
|
|
582
596
|
|
|
583
597
|
// ------------------------------------------ completion sound (turn finished)
|
|
584
598
|
var CHIME_KEY = "dsh-workbench.completion-sound";
|
|
599
|
+
var CHIME_VOLUME_KEY = "dsh-workbench.chime-volume";
|
|
585
600
|
var chimeEnabled = true;
|
|
586
601
|
try { if (localStorage.getItem(CHIME_KEY) === "0") chimeEnabled = false; } catch (error) { /* ignore */ }
|
|
602
|
+
var chimeVolume = 1.5;
|
|
603
|
+
try {
|
|
604
|
+
var storedChimeVolumeRaw = localStorage.getItem(CHIME_VOLUME_KEY);
|
|
605
|
+
if (storedChimeVolumeRaw !== null && storedChimeVolumeRaw !== "") {
|
|
606
|
+
var storedChimeVolume = Number(storedChimeVolumeRaw);
|
|
607
|
+
if (Number.isFinite(storedChimeVolume) && storedChimeVolume >= 0 && storedChimeVolume <= 2.5) chimeVolume = storedChimeVolume;
|
|
608
|
+
}
|
|
609
|
+
} catch (error) {
|
|
610
|
+
/* ignore */
|
|
611
|
+
}
|
|
587
612
|
var chimeLog = [];
|
|
588
613
|
var chimeAttachAttempts = 0;
|
|
589
614
|
var chimeUnsubscribe = null;
|
|
@@ -591,6 +616,30 @@ window.__ModuleLoader__.load({
|
|
|
591
616
|
var chimeLastPlayedAt = 0;
|
|
592
617
|
var chimePreviousRunning = new Map();
|
|
593
618
|
var soundButton = themeMenu.querySelector("button[data-sound]");
|
|
619
|
+
var volumeInput = themeMenu.querySelector("#dsh-theme-volume");
|
|
620
|
+
var volumeValueLabel = themeMenu.querySelector(".dsh-theme-volume-value");
|
|
621
|
+
|
|
622
|
+
function refreshVolumeControl() {
|
|
623
|
+
var percent = Math.round(chimeVolume * 100);
|
|
624
|
+
if (volumeInput !== null) volumeInput.value = String(percent);
|
|
625
|
+
if (volumeValueLabel !== null) volumeValueLabel.textContent = percent + "%";
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
refreshVolumeControl();
|
|
629
|
+
if (volumeInput !== null) {
|
|
630
|
+
var volumePreviewTimer = null;
|
|
631
|
+
volumeInput.addEventListener("input", function () {
|
|
632
|
+
var nextVolume = Number(volumeInput.value) / 100;
|
|
633
|
+
if (!Number.isFinite(nextVolume)) return;
|
|
634
|
+
chimeVolume = Math.max(0, Math.min(2.5, nextVolume));
|
|
635
|
+
try { localStorage.setItem(CHIME_VOLUME_KEY, String(chimeVolume)); } catch (error) { /* ignore */ }
|
|
636
|
+
refreshVolumeControl();
|
|
637
|
+
if (chimeEnabled) {
|
|
638
|
+
clearTimeout(volumePreviewTimer);
|
|
639
|
+
volumePreviewTimer = setTimeout(function () { playCompletionChime(); }, 350);
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
}
|
|
594
643
|
|
|
595
644
|
function ensureChimeAudio() {
|
|
596
645
|
try {
|
|
@@ -614,6 +663,9 @@ window.__ModuleLoader__.load({
|
|
|
614
663
|
if (audio === null) return;
|
|
615
664
|
try {
|
|
616
665
|
var start = audio.currentTime + 0.01;
|
|
666
|
+
var master = audio.createGain();
|
|
667
|
+
master.gain.value = chimeVolume;
|
|
668
|
+
master.connect(audio.destination);
|
|
617
669
|
function tone(frequency, offset, duration, peak, type) {
|
|
618
670
|
var oscillator = audio.createOscillator();
|
|
619
671
|
var gain = audio.createGain();
|
|
@@ -623,13 +675,13 @@ window.__ModuleLoader__.load({
|
|
|
623
675
|
gain.gain.exponentialRampToValueAtTime(peak, start + offset + 0.006);
|
|
624
676
|
gain.gain.exponentialRampToValueAtTime(0.0001, start + offset + duration);
|
|
625
677
|
oscillator.connect(gain);
|
|
626
|
-
gain.connect(
|
|
678
|
+
gain.connect(master);
|
|
627
679
|
oscillator.start(start + offset);
|
|
628
680
|
oscillator.stop(start + offset + duration + 0.03);
|
|
629
681
|
}
|
|
630
|
-
tone(1046.5, 0, 0.34, 0.
|
|
631
|
-
tone(1567.98, 0.07, 0.3, 0.
|
|
632
|
-
tone(3135.96, 0.07, 0.14, 0.
|
|
682
|
+
tone(1046.5, 0, 0.34, 0.16, "sine");
|
|
683
|
+
tone(1567.98, 0.07, 0.3, 0.11, "sine");
|
|
684
|
+
tone(3135.96, 0.07, 0.14, 0.03, "sine");
|
|
633
685
|
} catch (error) {
|
|
634
686
|
/* audio is best effort */
|
|
635
687
|
}
|
|
@@ -674,8 +726,10 @@ window.__ModuleLoader__.load({
|
|
|
674
726
|
initial.forEach(function (value, id) {
|
|
675
727
|
chimePreviousRunning.set(id, value !== undefined && value !== null && value.running === true);
|
|
676
728
|
});
|
|
677
|
-
chimeUnsubscribe = status.subscribe(function (
|
|
729
|
+
chimeUnsubscribe = status.subscribe(function () {
|
|
678
730
|
try {
|
|
731
|
+
// Client stores notify subscribers without passing the snapshot; read it.
|
|
732
|
+
var snapshot = status.getSnapshot();
|
|
679
733
|
snapshot.forEach(function (value, id) {
|
|
680
734
|
onSessionRunningChanged(id, value !== undefined && value !== null && value.running === true);
|
|
681
735
|
});
|
|
@@ -873,6 +927,7 @@ window.__ModuleLoader__.load({
|
|
|
873
927
|
return {
|
|
874
928
|
enabled: chimeEnabled,
|
|
875
929
|
attached: typeof chimeUnsubscribe === "function",
|
|
930
|
+
volume: Math.round(chimeVolume * 100),
|
|
876
931
|
log: chimeLog.slice()
|
|
877
932
|
};
|
|
878
933
|
},
|
|
@@ -885,6 +940,240 @@ window.__ModuleLoader__.load({
|
|
|
885
940
|
console.error("[dsh-panes] initialization failed", error);
|
|
886
941
|
}
|
|
887
942
|
}
|
|
943
|
+
function suggestionChips(ctx, runtime) {
|
|
944
|
+
try {
|
|
945
|
+
var react = runtime.react;
|
|
946
|
+
var jsx = runtime.jsxRuntime.jsx;
|
|
947
|
+
var jsxs = runtime.jsxRuntime.jsxs;
|
|
948
|
+
var CURRENT_KEY = "dsh.sessions.current";
|
|
949
|
+
var MAX_ITEMS = 6;
|
|
950
|
+
var attachAttempts = 0;
|
|
951
|
+
var slotState = { found: false, registered: false, error: null, attempts: 0 };
|
|
952
|
+
|
|
953
|
+
function getService(name) {
|
|
954
|
+
try {
|
|
955
|
+
return ctx === undefined || ctx === null || typeof ctx.get !== "function" ? undefined : ctx.get(name);
|
|
956
|
+
} catch (error) {
|
|
957
|
+
return undefined;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function currentSessionId() {
|
|
962
|
+
var sessions = getService("sessions");
|
|
963
|
+
try {
|
|
964
|
+
if (sessions !== undefined && sessions.list !== undefined && sessions.list.getSnapshot !== undefined) {
|
|
965
|
+
var list = sessions.list.getSnapshot();
|
|
966
|
+
var byId = (list && list.byId) || {};
|
|
967
|
+
var ids = Object.keys(byId);
|
|
968
|
+
for (var index = 0; index < ids.length; index += 1) {
|
|
969
|
+
var summary = byId[ids[index]];
|
|
970
|
+
var retained = summary && summary.retainedBy;
|
|
971
|
+
if (retained !== undefined && retained !== null && (retained.mainView || 0) > 0) {
|
|
972
|
+
return summary.id !== undefined ? summary.id : summary.sessionId;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
} catch (error) {
|
|
977
|
+
/* fall through to persisted selection */
|
|
978
|
+
}
|
|
979
|
+
try {
|
|
980
|
+
var raw = localStorage.getItem(CURRENT_KEY);
|
|
981
|
+
if (raw !== null) {
|
|
982
|
+
var parsed = JSON.parse(raw);
|
|
983
|
+
if (parsed !== null && typeof parsed === "object" && typeof parsed.sessionId === "string") return parsed.sessionId;
|
|
984
|
+
}
|
|
985
|
+
} catch (error) {
|
|
986
|
+
/* ignore malformed persisted selection */
|
|
987
|
+
}
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* Parse the fence body into suggestion items.
|
|
993
|
+
* @param {string} code - raw fence text.
|
|
994
|
+
* @returns {Array<{label: string, prompt: string}>|null} parsed items, or null when invalid.
|
|
995
|
+
*/
|
|
996
|
+
function parseSuggestions(code) {
|
|
997
|
+
try {
|
|
998
|
+
var data = JSON.parse(String(code === undefined || code === null ? "" : code));
|
|
999
|
+
if (!Array.isArray(data)) return null;
|
|
1000
|
+
var items = [];
|
|
1001
|
+
for (var index = 0; index < data.length && items.length < MAX_ITEMS; index += 1) {
|
|
1002
|
+
var item = data[index];
|
|
1003
|
+
if (item === null || typeof item !== "object") continue;
|
|
1004
|
+
var label = typeof item.label === "string" ? item.label.trim() : "";
|
|
1005
|
+
var prompt = typeof item.prompt === "string" ? item.prompt.trim() : "";
|
|
1006
|
+
if (label.length === 0 || prompt.length === 0) continue;
|
|
1007
|
+
items.push({ label: label, prompt: prompt });
|
|
1008
|
+
}
|
|
1009
|
+
return items.length === 0 ? null : items;
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
return null;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* Send one suggestion as the next user message.
|
|
1017
|
+
* @param {string} prompt - text submitted on click.
|
|
1018
|
+
* @returns {boolean} whether a submission path was found.
|
|
1019
|
+
*/
|
|
1020
|
+
function sendPrompt(prompt) {
|
|
1021
|
+
var sessions = getService("sessions");
|
|
1022
|
+
var sessionId = currentSessionId();
|
|
1023
|
+
try {
|
|
1024
|
+
if (sessionId !== null && sessions !== undefined && typeof sessions.binding === "function") {
|
|
1025
|
+
var binding = sessions.binding(sessionId);
|
|
1026
|
+
var live = binding === undefined || binding === null ? undefined : binding.session;
|
|
1027
|
+
if (live !== undefined && live !== null && typeof live.prompt === "function") {
|
|
1028
|
+
var signal = typeof AbortController === "function" ? new AbortController().signal : undefined;
|
|
1029
|
+
var result = live.prompt([{ type: "text", text: prompt }], "queue", signal);
|
|
1030
|
+
if (result !== undefined && result !== null && typeof result.catch === "function") result.catch(function () {});
|
|
1031
|
+
return true;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
console.error("[claude-workbench] suggestion prompt failed", error);
|
|
1036
|
+
}
|
|
1037
|
+
// Fallback: type into the composer and press Enter.
|
|
1038
|
+
try {
|
|
1039
|
+
var editor = document.querySelector('[contenteditable="true"]');
|
|
1040
|
+
if (editor === null) return false;
|
|
1041
|
+
editor.focus();
|
|
1042
|
+
var selection = window.getSelection();
|
|
1043
|
+
if (selection !== null) {
|
|
1044
|
+
var range = document.createRange();
|
|
1045
|
+
range.selectNodeContents(editor);
|
|
1046
|
+
selection.removeAllRanges();
|
|
1047
|
+
selection.addRange(range);
|
|
1048
|
+
}
|
|
1049
|
+
document.execCommand("insertText", false, prompt);
|
|
1050
|
+
editor.dispatchEvent(
|
|
1051
|
+
new KeyboardEvent("keydown", {
|
|
1052
|
+
key: "Enter",
|
|
1053
|
+
code: "Enter",
|
|
1054
|
+
keyCode: 13,
|
|
1055
|
+
which: 13,
|
|
1056
|
+
bubbles: true,
|
|
1057
|
+
cancelable: true
|
|
1058
|
+
})
|
|
1059
|
+
);
|
|
1060
|
+
return true;
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
console.error("[claude-workbench] composer fallback failed", error);
|
|
1063
|
+
return false;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
var SUGGESTION_LANGS = { suggestions: true, suggestion: true, chips: true, next: true, "next-steps": true };
|
|
1068
|
+
|
|
1069
|
+
function fenceTakesOver(fence, items) {
|
|
1070
|
+
if (items === null) return false;
|
|
1071
|
+
var lang = typeof fence.lang === "string" ? fence.lang.trim().toLowerCase() : "";
|
|
1072
|
+
if (lang !== "") return SUGGESTION_LANGS[lang] === true;
|
|
1073
|
+
// The renderer can drop an unknown info string; fall back to content
|
|
1074
|
+
// detection only for fences that clearly contain our JSON pair shape.
|
|
1075
|
+
return true;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function SuggestionChips(props) {
|
|
1079
|
+
var fence = props === undefined || props === null ? {} : props;
|
|
1080
|
+
var ownerContent = fence.children;
|
|
1081
|
+
var items = parseSuggestions(fence.code);
|
|
1082
|
+
if (!fenceTakesOver(fence, items)) return ownerContent === undefined ? null : ownerContent;
|
|
1083
|
+
var state = react.useState(-1);
|
|
1084
|
+
var sentIndex = state[0];
|
|
1085
|
+
var setSentIndex = state[1];
|
|
1086
|
+
var lang = typeof fence.lang === "string" ? fence.lang.trim().toLowerCase() : "";
|
|
1087
|
+
if (lang !== "" && SUGGESTION_LANGS[lang] !== true) return ownerContent === undefined ? null : ownerContent;
|
|
1088
|
+
var children = [];
|
|
1089
|
+
for (var index = 0; index < items.length; index += 1) {
|
|
1090
|
+
children.push(renderChip(items[index], index, sentIndex, setSentIndex));
|
|
1091
|
+
}
|
|
1092
|
+
return jsx("div", {
|
|
1093
|
+
className: "dsh-suggestions",
|
|
1094
|
+
"data-sent": sentIndex >= 0 ? "1" : "0",
|
|
1095
|
+
children: children
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
function renderChip(item, index, sentIndex, setSentIndex) {
|
|
1100
|
+
var primary = index === 0;
|
|
1101
|
+
return jsx(
|
|
1102
|
+
"button",
|
|
1103
|
+
{
|
|
1104
|
+
type: "button",
|
|
1105
|
+
className: primary ? "dsh-suggestion dsh-suggestion-primary" : "dsh-suggestion",
|
|
1106
|
+
title: item.prompt,
|
|
1107
|
+
onClick: function (event) {
|
|
1108
|
+
if (event !== undefined) {
|
|
1109
|
+
event.preventDefault();
|
|
1110
|
+
event.stopPropagation();
|
|
1111
|
+
}
|
|
1112
|
+
if (sentIndex >= 0) return;
|
|
1113
|
+
setSentIndex(sendPrompt(item.prompt) ? index : -2);
|
|
1114
|
+
},
|
|
1115
|
+
children: primary
|
|
1116
|
+
? jsxs("span", {
|
|
1117
|
+
className: "dsh-suggestion-content",
|
|
1118
|
+
children: [
|
|
1119
|
+
jsx("span", { className: "dsh-suggestion-label", children: item.label }),
|
|
1120
|
+
jsx("span", { className: "dsh-suggestion-hint", children: "⌘↵" })
|
|
1121
|
+
]
|
|
1122
|
+
})
|
|
1123
|
+
: jsx("span", { className: "dsh-suggestion-label", children: item.label })
|
|
1124
|
+
},
|
|
1125
|
+
item.label + "#" + index
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
function attachSlot() {
|
|
1130
|
+
var slots;
|
|
1131
|
+
try {
|
|
1132
|
+
slots = ctx !== undefined && ctx !== null && ctx.slots !== undefined ? ctx.slots : getService("slots");
|
|
1133
|
+
} catch (error) {
|
|
1134
|
+
slots = undefined;
|
|
1135
|
+
}
|
|
1136
|
+
slotState.found = slots !== undefined && slots !== null;
|
|
1137
|
+
if (slots === undefined || slots === null || typeof slots.inject !== "function") {
|
|
1138
|
+
attachAttempts += 1;
|
|
1139
|
+
slotState.attempts = attachAttempts;
|
|
1140
|
+
if (attachAttempts < 150) setTimeout(attachSlot, 200);
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
try {
|
|
1144
|
+
slots.inject("conversation.chat.markdown.fence", function () {
|
|
1145
|
+
return slots.register(
|
|
1146
|
+
{
|
|
1147
|
+
name: "conversation.chat.markdown.fence",
|
|
1148
|
+
id: "dsh-claude-workbench/fence/suggestions"
|
|
1149
|
+
},
|
|
1150
|
+
SuggestionChips
|
|
1151
|
+
);
|
|
1152
|
+
});
|
|
1153
|
+
slotState.registered = true;
|
|
1154
|
+
} catch (error) {
|
|
1155
|
+
slotState.error = String(error && error.message ? error.message : error);
|
|
1156
|
+
console.error("[claude-workbench] suggestion slot registration failed", error);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
try {
|
|
1161
|
+
globalThis.__dshPanesSuggestions = {
|
|
1162
|
+
parse: parseSuggestions,
|
|
1163
|
+
send: sendPrompt,
|
|
1164
|
+
state: function () {
|
|
1165
|
+
return { found: slotState.found, registered: slotState.registered, error: slotState.error, attempts: slotState.attempts };
|
|
1166
|
+
}
|
|
1167
|
+
};
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
/* debug hook is optional */
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
attachSlot();
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
console.error("[claude-workbench] suggestion chips initialization failed", error);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
888
1177
|
|
|
889
1178
|
let started = false;
|
|
890
1179
|
async function start(ctx) {
|
|
@@ -902,6 +1191,7 @@ window.__ModuleLoader__.load({
|
|
|
902
1191
|
|
|
903
1192
|
function apply(ctx) {
|
|
904
1193
|
start(ctx);
|
|
1194
|
+
try { suggestionChips(ctx, { react: react, jsxRuntime: jsxRuntime }); } catch (error) { console.error("[claude-workbench] suggestion chips failed", error); }
|
|
905
1195
|
if (typeof ctx.effect === "function") {
|
|
906
1196
|
ctx.effect(() => () => {
|
|
907
1197
|
try { globalThis.__dshPanesTeardown?.(); } catch (error) { /* ignore */ }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-claude-workbench",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Claude-inspired workbench for the DeepSeek Harness Web GUI: warm editorial skin (Crimson Pro/DM Sans), split view with 2-3 side-by-side conversations, selection-based side chat, a top-right light/dark switch, and a completion chime when a turn finishes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -45,7 +45,9 @@
|
|
|
45
45
|
"@deepseek-ai/dsh-api-session-controller",
|
|
46
46
|
"@deepseek-ai/dsh-client-ui-workspace",
|
|
47
47
|
"@deepseek-ai/dsh-client-ui-theme",
|
|
48
|
-
"@deepseek-ai/dsh-client-ui-conversation"
|
|
48
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
49
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-chat"
|
|
49
51
|
],
|
|
50
52
|
"platform": "web",
|
|
51
53
|
"immediately": true
|