dsh-split-view 0.2.0 → 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 +66 -0
- package/README.md +30 -22
- package/lib/client.js +38 -71
- package/package.json +1 -1
package/README.en.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# dsh-split-view
|
|
2
|
+
|
|
3
|
+
[中文](README.md) | English
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/dsh-split-view)
|
|
6
|
+
|
|
7
|
+
One plugin that turns the DeepSeek Harness main window into split panes, so you can watch several sessions at once.
|
|
8
|
+
|
|
9
|
+
If you have ever run several tasks at the same time, you know the pain: switching between tabs over and over.
|
|
10
|
+
|
|
11
|
+
dsh-split-view borrows the idea of terminal splitting. The DSH main window becomes a draggable split container, and each pane runs a full DSH client of its own.
|
|
12
|
+
|
|
13
|
+
## What it looks like
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
Above is daily use: split right, split down, drag the divider, double-click the title bar to maximize, close.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
Horizontal and vertical splits nest freely. Every pane has a title bar: the live session title on the left, and five buttons on the right — split right, split down, maximize/restore, reload, close. Click the title to rename the session, double-click to maximize, and copy "session id + session title" with one click.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
dsh plugin --profile web add dsh-split-view
|
|
33
|
+
dsh web # restart to activate
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Install from source for local development: `dsh plugin --profile web add -w /path/to/dsh-split-view`.
|
|
37
|
+
|
|
38
|
+
Uninstall with `dsh plugin --profile web remove dsh-split-view`, restart, and the window is back to normal. If the plugin itself breaks, boot stops with an error instead of a silent blank page, so uninstalling is also the emergency recovery path.
|
|
39
|
+
|
|
40
|
+
## Default shortcuts (customizable in Settings)
|
|
41
|
+
|
|
42
|
+
| Key (⌘ on macOS, Ctrl on Windows/Linux) | Action |
|
|
43
|
+
|---|---|
|
|
44
|
+
| ⇧⌘D | Split right |
|
|
45
|
+
| ⇧⌘V | Split down |
|
|
46
|
+
| ⇧⌘X | Close focused pane |
|
|
47
|
+
| ⇧⌘Enter | Maximize / restore focused pane |
|
|
48
|
+
|
|
49
|
+
## Settings
|
|
50
|
+
|
|
51
|
+
Open Settings from any pane; the navigation on the left has a "Split View" entry.
|
|
52
|
+
|
|
53
|
+
Four groups:
|
|
54
|
+
|
|
55
|
+
- **Shortcuts**: click the combo of an action to start recording, press the new combo to rebind, Esc to cancel. What you bind is up to you: combos taken by the browser or the OS, or combos that collide with another action, still bind — you get a warning, not a block. The only hard rule is that a combo must include ⌘/Ctrl.
|
|
56
|
+
- **Focus highlight color**: the outline and title-bar tint of the focused pane. Preset palette, a custom color picker, plus "Follow theme" — follows the brand color token of the current skin, so it changes when you switch skins.
|
|
57
|
+
- **Title bar**: one switch hides all pane title bars. Note that the title bar carries the copy-session-info, split, maximize, reload and close buttons, and double-click-to-maximize and click-title-to-rename also live there — with it hidden, those actions are keyboard-only. Clicking a pane to focus it is unaffected.
|
|
58
|
+
- **Restore defaults**: resets everything above; asks for confirmation first.
|
|
59
|
+
|
|
60
|
+
## For other plugins
|
|
61
|
+
|
|
62
|
+
The whole split-view capability is exposed as a Cordis service: pane operations, pane×session operations (load a given session, send a message, rename, switch model, cancel a turn), plus queries and subscriptions. See [api.md](./api.md) for the service API aimed at integrators.
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT © dsh-split-view contributors
|
package/README.md
CHANGED
|
@@ -1,61 +1,69 @@
|
|
|
1
|
-
# dsh-split-view
|
|
1
|
+
# dsh-split-view(分屏插件)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
中文 | [English](README.en.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/dsh-split-view)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
一个插件把 DeepSeek Harness 主窗口变成多个分屏,同时查看多个会话的状态。
|
|
8
|
+
|
|
9
|
+
有过【同时跑多个任务】经历的同学应该能感受这种痛苦:多个标签切来切去,很心累。
|
|
10
|
+
|
|
11
|
+
dsh-split-view 分屏插件借鉴了终端分屏的思想。将 DSH 主窗口变成一个可拖拽的分屏容器,每个分屏里跑一个完整的 DSH 客户端。
|
|
8
12
|
|
|
9
13
|
## 长什么样
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
上面是日常操作:向右分屏、向下分屏、拖动分割条调整大小、双击标题栏放大、关闭。
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+

|
|
12
22
|
|
|
13
|
-
- 横竖任意嵌套分屏,可拖动调整大小
|
|
14
|
-
- 每个面板一条标题栏,左边实时显示当前会话标题,右边五个按钮,向右分屏、向下分屏、放大还原、刷新、关闭
|
|
15
|
-
- 一键复制「会话 id + 会话标题」
|
|
16
23
|
|
|
17
|
-
## 安装
|
|
18
24
|
|
|
19
|
-
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
横竖任意嵌套。每个面板一条标题栏,左边实时显示当前会话标题,右边是向右分屏、向下分屏、放大还原、刷新、关闭五个按钮。点标题直接改名,双击放大,还能一键复制「会话 id + 会话标题」。
|
|
28
|
+
|
|
29
|
+
## 安装
|
|
20
30
|
|
|
21
31
|
```sh
|
|
22
|
-
dsh plugin --profile web add
|
|
32
|
+
dsh plugin --profile web add dsh-split-view
|
|
23
33
|
dsh web # 重启生效
|
|
24
34
|
```
|
|
25
35
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
发布之后一条命令就行,`dsh plugin --profile web add dsh-split-view`(CLI 安装要重启 dsh web 生效)。也可以在插件市场(dshmarket)里搜索 dsh-split-view 一键安装:市场会把插件热挂载进运行中的实例,不用重启,页面刷新一次即可。
|
|
36
|
+
本地开发从源码装:`dsh plugin --profile web add -w /path/to/dsh-split-view`。
|
|
29
37
|
|
|
30
38
|
卸载用 `dsh plugin --profile web remove dsh-split-view`,重启恢复单窗口。插件本身如果出问题,boot 会停下来报错,不会静默白屏,所以卸载也就是紧急恢复的办法。
|
|
31
39
|
|
|
32
|
-
##
|
|
40
|
+
## 默认快捷键(支持在设置中自定义)
|
|
33
41
|
|
|
34
42
|
| 键(macOS ⌘,Windows/Linux Ctrl) | 动作 |
|
|
35
43
|
|---|---|
|
|
36
|
-
| ⇧⌘
|
|
44
|
+
| ⇧⌘D | 向右分屏 |
|
|
37
45
|
| ⇧⌘V | 向下分屏 |
|
|
38
46
|
| ⇧⌘X | 关闭聚焦面板 |
|
|
39
47
|
| ⇧⌘Enter | 最大化 / 还原聚焦面板 |
|
|
40
48
|
|
|
41
|
-
这几个组合是挑出来的,⌘W 关页、⌘D 书签、⌘V 粘贴这些浏览器保留键页面拦不住。关闭键原本用过 ⌘⇧Q,会把浏览器锁死;换 ⌘⇧W,又被飞书的全局热键占了;最后落在 X,就当它是 ✕。都是默认值,可以在设置里改(见下节)。
|
|
42
49
|
|
|
43
|
-
焦点在 iframe 里时快捷键照样有效,面板内的 agent 捕获后转发给顶层。标题栏按钮做的是同一批动作,tooltip 里的写法按平台来,macOS 显示 ⌘⇧,其余显示 Ctrl+Shift+。点标题栏任意位置就是聚焦那个面板。面板超过 4 秒没就绪会挂一个「⚠ 未就绪 · 重载」角标,点一下单独重载它。
|
|
44
50
|
|
|
45
51
|
## 设置
|
|
46
52
|
|
|
47
|
-
|
|
53
|
+
在任意面板里打开设置,左侧导航有一项「分屏」。
|
|
54
|
+
|
|
55
|
+
四组设置:
|
|
48
56
|
|
|
49
|
-
- **快捷键**:点某个动作的组合键进入录制,按新组合完成改绑,Esc
|
|
57
|
+
- **快捷键**:点某个动作的组合键进入录制,按新组合完成改绑,Esc 取消。绑什么由你决定:被浏览器或系统占用的组合、和别的动作撞车的组合照样能绑,只会给出提示;唯一的硬性要求是得带 ⌘/Ctrl。
|
|
50
58
|
- **选中高亮色**:聚焦面板的描边和标题栏染色。预设色板、自定义取色器,外加「跟随主题」——跟随当前皮肤的品牌色 token,换皮肤自动跟着变。
|
|
51
59
|
- **标题栏**:一个开关,隐藏所有面板的标题栏。注意标题栏上有复制会话信息、分屏、最大化、刷新、关闭按钮,双击放大、点标题重命名也走标题栏,隐藏后这些动作只能靠快捷键;点面板聚焦不受影响。
|
|
52
60
|
- **恢复默认**:一键把上面全部还原,点之前会弹确认。
|
|
53
61
|
|
|
54
|
-
|
|
62
|
+
|
|
55
63
|
|
|
56
64
|
## 供其它插件调用
|
|
57
65
|
|
|
58
|
-
分屏能力整体暴露成了一个 Cordis
|
|
66
|
+
分屏能力整体暴露成了一个 Cordis 服务:面板操作、面板×会话操作(加载指定会话、发消息、改名、换模型、取消回合)、查询与订阅。面向集成方的服务 API 见 [api.md](./api.md)。
|
|
59
67
|
|
|
60
68
|
## 协议
|
|
61
69
|
|
package/lib/client.js
CHANGED
|
@@ -427,13 +427,16 @@ function collectSyncedStorage() {
|
|
|
427
427
|
// tab, ⌘D bookmark, ⌘V paste…); ⌘⇧Q locked the browser and ⌘⇧W is
|
|
428
428
|
// taken by Feishu's global hotkey — X reads as ✕. Mod+Shift+letter is
|
|
429
429
|
// the space a page can actually capture. ⌘⇧Enter keeps maximize/restore.
|
|
430
|
+
// split-right sits on D so the whole chord is a one-left-hand press;
|
|
431
|
+
// note ⌘⇧D is reserved by some browsers ("bookmark all tabs") but is
|
|
432
|
+
// capturable in chrome-less shells such as the native WKWebView app.
|
|
430
433
|
/** localStorage key holding user preferences (persisted by the top window). */
|
|
431
434
|
const PREFS_KEY = "dsh-split:prefs";
|
|
432
435
|
/** Locale namespace of the settings section and its rows. */
|
|
433
436
|
const SETTINGS_NS = "settings.splitView";
|
|
434
437
|
const ACTION_IDS = ["split-right", "split-down", "close", "maximize"];
|
|
435
438
|
const DEFAULT_SHORTCUTS = Object.freeze({
|
|
436
|
-
"split-right": "Mod+Shift+
|
|
439
|
+
"split-right": "Mod+Shift+D",
|
|
437
440
|
"split-down": "Mod+Shift+V",
|
|
438
441
|
"close": "Mod+Shift+X",
|
|
439
442
|
"maximize": "Mod+Shift+Enter"
|
|
@@ -456,7 +459,7 @@ function collectSyncedStorage() {
|
|
|
456
459
|
return event.key.toLowerCase();
|
|
457
460
|
}
|
|
458
461
|
|
|
459
|
-
/** Parse a serialized combo ("Mod+Shift+
|
|
462
|
+
/** Parse a serialized combo ("Mod+Shift+D"). Mod (matched as ⌘ OR Ctrl on
|
|
460
463
|
* every platform — the same unification the original fixed map used) is
|
|
461
464
|
* mandatory; returns {shift, alt, key} or null. */
|
|
462
465
|
function parseCombo(text) {
|
|
@@ -502,7 +505,7 @@ function collectSyncedStorage() {
|
|
|
502
505
|
return null;
|
|
503
506
|
}
|
|
504
507
|
|
|
505
|
-
/** Platform-aware label for tooltips and the settings row (⌘⇧
|
|
508
|
+
/** Platform-aware label for tooltips and the settings row (⌘⇧D / Ctrl+Shift+D). */
|
|
506
509
|
function comboLabel(text) {
|
|
507
510
|
const parsed = parseCombo(text);
|
|
508
511
|
if (!parsed) return "—";
|
|
@@ -511,19 +514,22 @@ function collectSyncedStorage() {
|
|
|
511
514
|
return "Ctrl+" + (parsed.shift ? "Shift+" : "") + (parsed.alt ? "Alt+" : "") + key;
|
|
512
515
|
}
|
|
513
516
|
|
|
514
|
-
/** Recording-time
|
|
515
|
-
* "shortcuts.*" by the caller) or null
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
517
|
+
/** Recording-time advisory. Returns a locale warning key (resolved under
|
|
518
|
+
* "shortcuts.*" by the caller) or null. Every combo is bindable — the
|
|
519
|
+
* caller always binds — the warning only tells the user the binding may
|
|
520
|
+
* be shadowed by the browser, the OS, or another action; the choice is
|
|
521
|
+
* theirs. */
|
|
522
|
+
function recordedWarning(parsed, map, actionId) {
|
|
523
|
+
// Bare ⌘/Ctrl+letter is usually browser-reserved (⌘W closes the
|
|
524
|
+
// tab, ⌘D bookmarks…) — the page may never see those events.
|
|
525
|
+
if (parsed.key.length === 1 && !parsed.shift && !parsed.alt) return "warn.letter";
|
|
520
526
|
// Combinations with a painful history or a browser/system reservation
|
|
521
527
|
// (⌘⇧Q locked the browser, ⌘⇧W closes all windows / is Feishu's
|
|
522
528
|
// global hotkey, ⌘⇧T reopens tabs, ⌘⇧N opens private windows).
|
|
523
|
-
if (parsed.shift && !parsed.alt && (parsed.key === "w" || parsed.key === "q" || parsed.key === "t" || parsed.key === "n")) return "
|
|
529
|
+
if (parsed.shift && !parsed.alt && (parsed.key === "w" || parsed.key === "q" || parsed.key === "t" || parsed.key === "n")) return "warn.danger";
|
|
524
530
|
for (const other of ACTION_IDS) {
|
|
525
531
|
if (other === actionId) continue;
|
|
526
|
-
if (combosEqual(parseCombo(map[other]), parsed)) return "
|
|
532
|
+
if (combosEqual(parseCombo(map[other]), parsed)) return "warn.dup";
|
|
527
533
|
}
|
|
528
534
|
return null;
|
|
529
535
|
}
|
|
@@ -534,12 +540,10 @@ function collectSyncedStorage() {
|
|
|
534
540
|
const source = raw && typeof raw === "object" ? raw : {};
|
|
535
541
|
const rawShortcuts = source.shortcuts && typeof source.shortcuts === "object" ? source.shortcuts : {};
|
|
536
542
|
const shortcuts = {};
|
|
537
|
-
const claimed = [];
|
|
538
543
|
for (const cmd of ACTION_IDS) {
|
|
539
544
|
let parsed = parseCombo(rawShortcuts[cmd]);
|
|
540
|
-
if (!parsed
|
|
545
|
+
if (!parsed) parsed = parseCombo(DEFAULT_SHORTCUTS[cmd]);
|
|
541
546
|
shortcuts[cmd] = comboToText(parsed);
|
|
542
|
-
claimed.push(parsed);
|
|
543
547
|
}
|
|
544
548
|
let focusColor = DEFAULT_PREFS.focusColor;
|
|
545
549
|
if (source.focusColor === "theme") focusColor = "theme";
|
|
@@ -568,8 +572,6 @@ function collectSyncedStorage() {
|
|
|
568
572
|
if (text === undefined) continue;
|
|
569
573
|
const parsed = parseCombo(text);
|
|
570
574
|
if (!parsed) continue;
|
|
571
|
-
const collides = ACTION_IDS.some((other) => other !== cmd && combosEqual(parseCombo(next.shortcuts[other]), parsed));
|
|
572
|
-
if (collides) continue;
|
|
573
575
|
const nextText = comboToText(parsed);
|
|
574
576
|
if (next.shortcuts[cmd] !== nextText) {
|
|
575
577
|
next.shortcuts[cmd] = nextText;
|
|
@@ -610,11 +612,10 @@ function collectSyncedStorage() {
|
|
|
610
612
|
"shortcuts.record": "按新的组合键…",
|
|
611
613
|
"shortcuts.recordHint": "按 Esc 取消",
|
|
612
614
|
"shortcuts.reset": "恢复默认",
|
|
613
|
-
"shortcuts.hint": "组合键需包含 ⌘/Ctrl;单个字母或数字需搭配 Shift 或 Alt,否则会被浏览器保留。个别组合可能仍被浏览器或系统全局热键占用,遇到无效换一个即可。",
|
|
614
615
|
"shortcuts.err.noMod": "组合键需包含 ⌘ 或 Ctrl",
|
|
615
|
-
"shortcuts.
|
|
616
|
-
"shortcuts.
|
|
617
|
-
"shortcuts.
|
|
616
|
+
"shortcuts.warn.letter": "已绑定。单个字母/数字不带 Shift 或 Alt 的组合(如 ⌘D)通常被浏览器保留,页面可能捕捉不到",
|
|
617
|
+
"shortcuts.warn.danger": "已绑定。该组合可能被浏览器或系统全局热键占用(如 ⌘⇧W 会关掉所有窗口),没反应就换一个",
|
|
618
|
+
"shortcuts.warn.dup": "已绑定。该组合与另一个动作的快捷键相同,按下时列表里靠前的动作响应",
|
|
618
619
|
"color.title": "选中高亮色",
|
|
619
620
|
"color.theme": "跟随主题",
|
|
620
621
|
"color.pick": "自定义…",
|
|
@@ -639,11 +640,10 @@ function collectSyncedStorage() {
|
|
|
639
640
|
"shortcuts.record": "Press a new combo…",
|
|
640
641
|
"shortcuts.recordHint": "Esc to cancel",
|
|
641
642
|
"shortcuts.reset": "Reset to defaults",
|
|
642
|
-
"shortcuts.hint": "Combos need ⌘/Ctrl; a bare letter or digit also needs Shift or Alt, otherwise the browser keeps it. A few combos may still be taken by the browser or global hotkeys — pick another if one does nothing.",
|
|
643
643
|
"shortcuts.err.noMod": "The combo needs ⌘ or Ctrl",
|
|
644
|
-
"shortcuts.
|
|
645
|
-
"shortcuts.
|
|
646
|
-
"shortcuts.
|
|
644
|
+
"shortcuts.warn.letter": "Bound. A bare letter/digit without Shift or Alt (e.g. ⌘D) is usually kept by the browser, so the page may never see it",
|
|
645
|
+
"shortcuts.warn.danger": "Bound. This combo may be taken by the browser or an OS global hotkey (e.g. ⌘⇧W closes all windows) — switch if it does nothing",
|
|
646
|
+
"shortcuts.warn.dup": "Bound. This combo is shared with another action; the action listed earlier responds on keypress",
|
|
647
647
|
"color.title": "Focus Highlight Color",
|
|
648
648
|
"color.theme": "Follow theme",
|
|
649
649
|
"color.pick": "Custom…",
|
|
@@ -995,6 +995,7 @@ function collectSyncedStorage() {
|
|
|
995
995
|
title: { color: "var(--dsw-alias-label-primary)", fontSize: "14px", fontWeight: 400, lineHeight: "22px" },
|
|
996
996
|
hint: { color: "var(--dsw-alias-label-tertiary)", fontSize: "12px", lineHeight: "18px" },
|
|
997
997
|
error: { color: "var(--dsw-alias-state-error-primary,#e5484d)", fontSize: "12px", lineHeight: "18px" },
|
|
998
|
+
warning: { color: "var(--dsw-alias-state-warning-primary,#b8860b)", fontSize: "12px", lineHeight: "18px" },
|
|
998
999
|
actionRow: { display: "flex", alignItems: "center", gap: "10px", flexWrap: "wrap" },
|
|
999
1000
|
kvRow: { display: "flex", alignItems: "center", gap: "10px" },
|
|
1000
1001
|
kvLabel: { color: "var(--dsw-alias-label-secondary)", fontSize: "13px", lineHeight: "20px" },
|
|
@@ -1109,12 +1110,16 @@ function collectSyncedStorage() {
|
|
|
1109
1110
|
}
|
|
1110
1111
|
|
|
1111
1112
|
/** Shortcut row: one recording chip per action. Click a chip, press the
|
|
1112
|
-
* new combination (Esc cancels);
|
|
1113
|
-
*
|
|
1113
|
+
* new combination (Esc cancels). Every combo is bindable; combos that
|
|
1114
|
+
* the browser/OS is likely to keep, or that collide with another
|
|
1115
|
+
* action, are accepted with an inline warning instead of rejected.
|
|
1116
|
+
* Only a missing ⌘/Ctrl stays a hard error (the runtime never fires
|
|
1117
|
+
* without it). */
|
|
1114
1118
|
function ShortcutsRow({ t, useStore, setShortcut }) {
|
|
1115
1119
|
const prefs = useStore((s) => s.prefs);
|
|
1116
1120
|
const [recording, setRecording] = React.useState(null);
|
|
1117
1121
|
const [error, setError] = React.useState(null);
|
|
1122
|
+
const [warning, setWarning] = React.useState(null);
|
|
1118
1123
|
const prefsRef = React.useRef(prefs);
|
|
1119
1124
|
prefsRef.current = prefs;
|
|
1120
1125
|
React.useEffect(() => {
|
|
@@ -1134,14 +1139,11 @@ function collectSyncedStorage() {
|
|
|
1134
1139
|
return;
|
|
1135
1140
|
}
|
|
1136
1141
|
const parsed = { shift: event.shiftKey, alt: event.altKey, key };
|
|
1137
|
-
const errKey = validateRecorded(parsed, prefsRef.current.shortcuts, recording);
|
|
1138
|
-
if (errKey !== null) {
|
|
1139
|
-
setError(t("shortcuts." + errKey));
|
|
1140
|
-
return;
|
|
1141
|
-
}
|
|
1142
1142
|
setShortcut(recording, comboToText(parsed));
|
|
1143
1143
|
setRecording(null);
|
|
1144
1144
|
setError(null);
|
|
1145
|
+
const warnKey = recordedWarning(parsed, prefsRef.current.shortcuts, recording);
|
|
1146
|
+
setWarning(warnKey === null ? null : t("shortcuts." + warnKey));
|
|
1145
1147
|
};
|
|
1146
1148
|
window.addEventListener("keydown", onKey, true);
|
|
1147
1149
|
return () => window.removeEventListener("keydown", onKey, true);
|
|
@@ -1159,15 +1161,17 @@ function collectSyncedStorage() {
|
|
|
1159
1161
|
"aria-pressed": recording === cmd ? "true" : undefined,
|
|
1160
1162
|
onClick: () => {
|
|
1161
1163
|
setError(null);
|
|
1164
|
+
setWarning(null);
|
|
1162
1165
|
setRecording(recording === cmd ? null : cmd);
|
|
1163
1166
|
}
|
|
1164
1167
|
}, recording === cmd ? t("shortcuts.record") : comboLabel(prefs.shortcuts[cmd]))
|
|
1165
1168
|
)),
|
|
1166
1169
|
React.createElement("div", { style: settingsStyles.actionRow },
|
|
1167
|
-
error !== null
|
|
1170
|
+
error !== null
|
|
1171
|
+
? React.createElement("span", { style: settingsStyles.error }, error)
|
|
1172
|
+
: warning !== null && recording === null ? React.createElement("span", { style: settingsStyles.warning }, warning) : null,
|
|
1168
1173
|
recording !== null ? React.createElement("span", { style: settingsStyles.hint }, t("shortcuts.recordHint")) : null
|
|
1169
|
-
)
|
|
1170
|
-
React.createElement("div", { style: settingsStyles.hint }, t("shortcuts.hint"))
|
|
1174
|
+
)
|
|
1171
1175
|
);
|
|
1172
1176
|
}
|
|
1173
1177
|
|
|
@@ -2644,43 +2648,6 @@ function collectSyncedStorage() {
|
|
|
2644
2648
|
} catch {
|
|
2645
2649
|
// storage unavailable — the hook stays off
|
|
2646
2650
|
}
|
|
2647
|
-
// TEMP(agent-team-demo): auto-pin agent-teams member sessions into panes
|
|
2648
|
-
// on top-level boot. Remove once the dedicated dsh-agent-team plugin lands.
|
|
2649
|
-
(() => {
|
|
2650
|
-
if (window.__dshMemberDemoScheduled) return;
|
|
2651
|
-
window.__dshMemberDemoScheduled = true;
|
|
2652
|
-
const KEY = "dsh-split:memberPanes";
|
|
2653
|
-
const TRACE = "dsh-split:demoTrace";
|
|
2654
|
-
const trace = (m) => { try { localStorage.setItem(TRACE, (localStorage.getItem(TRACE) || "").slice(-400) + "\n" + new Date().toISOString().slice(11,19) + " " + m); } catch (e) {} };
|
|
2655
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2656
|
-
const run = async () => {
|
|
2657
|
-
try {
|
|
2658
|
-
trace("start iframes=" + document.querySelectorAll("iframe").length);
|
|
2659
|
-
const res = await fetch("/plugins/dsh-agent-teams/state", { cache: "no-store" });
|
|
2660
|
-
if (!res.ok) { trace("state route not ok: " + res.status); return; }
|
|
2661
|
-
const data = await res.json();
|
|
2662
|
-
const teams = (data && data.teams) || [];
|
|
2663
|
-
trace("fetched teams=" + teams.length);
|
|
2664
|
-
const pinned = new Set(JSON.parse(localStorage.getItem(KEY) || "[]"));
|
|
2665
|
-
let changed = false;
|
|
2666
|
-
for (const team of teams) {
|
|
2667
|
-
for (const m of team.members || []) {
|
|
2668
|
-
if (!m || !m.id || pinned.has(m.id)) continue;
|
|
2669
|
-
trace("split before " + m.id.slice(0, 8));
|
|
2670
|
-
try {
|
|
2671
|
-
const out = await splitImpl({ session: m.id, direction: "right" });
|
|
2672
|
-
trace("split after leaf=" + (out && out.leafId));
|
|
2673
|
-
if (out && out.leafId) { pinned.add(m.id); changed = true; await sleep(1200); }
|
|
2674
|
-
} catch (e) { trace("split err: " + (e && e.message)); }
|
|
2675
|
-
}
|
|
2676
|
-
}
|
|
2677
|
-
if (changed) localStorage.setItem(KEY, JSON.stringify([...pinned]));
|
|
2678
|
-
trace("done iframes=" + document.querySelectorAll("iframe").length);
|
|
2679
|
-
} catch (e) { trace("outer err: " + (e && e.message)); }
|
|
2680
|
-
};
|
|
2681
|
-
// TEMP(agent-team-demo): 6s delay lets boot/layout restore settle first.
|
|
2682
|
-
setTimeout(run, 6000);
|
|
2683
|
-
})();
|
|
2684
2651
|
const handleCommand = (cmd, paneId) => {
|
|
2685
2652
|
const snap = layout.getSnapshot();
|
|
2686
2653
|
const target = paneId && findNode(snap.tree, paneId) ? paneId : snap.focused;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-split-view",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Multi-session split view for the DeepSeek Harness web GUI: the top window shadows the root slot with a resizable split container; each pane is an iframe running a full DSH client pinned to its own session, with per-pane title bars, LRU hibernation and a dual-loopback origin pool.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|