dsh-input-traffic 0.1.0 → 0.2.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 +41 -5
- package/README.md +40 -4
- package/lib/client.js +144 -49
- package/lib/client.js.map +1 -1
- package/lib/types/client/locales.d.ts +3 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +6 -0
- package/lib/types/client/locales.js.map +1 -1
- package/lib/types/client/steer-queue-dock.d.ts.map +1 -1
- package/lib/types/client/steer-queue-dock.js +123 -19
- package/lib/types/client/steer-queue-dock.js.map +1 -1
- package/package.json +104 -96
package/README.en.md
CHANGED
|
@@ -71,10 +71,15 @@ Each waiting-area message (while not frozen) offers:
|
|
|
71
71
|
| Action | Description |
|
|
72
72
|
|---|---|
|
|
73
73
|
| Move up / down | Reorder the FIFO queue (the whole queue is rebuilt in the new order; disabled while any image message is queued) |
|
|
74
|
+
| **Drag to reorder** | Drag a row onto its target position (native HTML5 DnD, no extra dependency); same server-side rebuild as the arrow buttons |
|
|
74
75
|
| Edit in composer | Back-fill the message into the composer draft and remove it from the queue for editing |
|
|
75
76
|
| Edit / remove | Edit the queued content in a multi-line editor / cancel the message |
|
|
76
77
|
| Red / yellow / green planning | See "The three tiers" |
|
|
77
|
-
| Cancel and clear |
|
|
78
|
+
| Cancel and clear | Two-step confirm, then stop the current run and remove every queued message (first click shows "Confirm clear?") |
|
|
79
|
+
|
|
80
|
+
**Concurrency protection** for reordering: if a message was already claimed by the agent during the rebuild (`queue-item-not-found`), the reorder stops immediately and nothing is re-sent — the changed queue is never scrambled.
|
|
81
|
+
|
|
82
|
+
The waiting area's **collapse state is remembered** across sessions.
|
|
78
83
|
|
|
79
84
|
When editing a queued message (inline):
|
|
80
85
|
|
|
@@ -85,8 +90,12 @@ When editing a queued message (inline):
|
|
|
85
90
|
## Installation
|
|
86
91
|
|
|
87
92
|
```sh
|
|
88
|
-
#
|
|
89
|
-
|
|
93
|
+
# Option 1: install from npm (recommended)
|
|
94
|
+
# (the profile is a pnpm workspace root, so -w is required)
|
|
95
|
+
dsh plugin --profile web add dsh-input-traffic -w
|
|
96
|
+
|
|
97
|
+
# Option 2: assemble from git or a local path
|
|
98
|
+
# dsh plugin --profile web add /absolute/path/to/dsh-input-traffic -w
|
|
90
99
|
# (after git install, build in the profile's node_modules: npm install --legacy-peer-deps && npm run build)
|
|
91
100
|
|
|
92
101
|
# Confirm the composed tree contains the new row
|
|
@@ -102,9 +111,36 @@ Local build and tests:
|
|
|
102
111
|
npm install --legacy-peer-deps # the @deepseek-ai client package chain is incomplete on npm; toolchain only
|
|
103
112
|
npm run build # tsc (lib/types) + tsdown (lib/index.js + lib/client.js)
|
|
104
113
|
node examples/verify-assembly.mjs # 12 assembly assertions
|
|
105
|
-
npm test #
|
|
114
|
+
npm test # 36 vitest component tests
|
|
115
|
+
npm run lint # ESLint (src + tests, flat config)
|
|
116
|
+
npm run verify # one-shot gate: lint + test + build + verify-assembly
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Development (TDD + Lint)
|
|
120
|
+
|
|
121
|
+
This project is maintained with **TDD** (test-driven development): write the failing test first, then implement to green.
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
npm run tdd # vitest watch: re-runs on change, red-to-green loop
|
|
106
125
|
```
|
|
107
126
|
|
|
127
|
+
Workflow:
|
|
128
|
+
|
|
129
|
+
1. Add/update a case in `tests/` (red: confirm the new behavior is not implemented yet);
|
|
130
|
+
2. `npm run tdd` and watch it fail;
|
|
131
|
+
3. Implement the minimal change in `src/` (green);
|
|
132
|
+
4. `npm run verify` all green before committing (lint + 36 tests + build + 12 assembly assertions).
|
|
133
|
+
|
|
134
|
+
Lint:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
npm run lint # ESLint flat config (eslint.config.mjs)
|
|
138
|
+
npm run lint:fix # auto-fix what can be fixed
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- Scope: `src/` and `tests/` (TypeScript + React); build output `lib/` is ignored;
|
|
142
|
+
- Rules: `@typescript-eslint/recommended` + `react-hooks` best practices; unused variables are errors (underscore prefix `_` exempts).
|
|
143
|
+
|
|
108
144
|
## Usage
|
|
109
145
|
|
|
110
146
|
1. While the agent is busy, type and send — the message enters the **waiting area** (green queue by default);
|
|
@@ -189,7 +225,7 @@ End-to-end browser verification on a live `dsh web`, zero application console er
|
|
|
189
225
|
| Red now | Interrupted message is processed immediately: the agent replies to it explicitly and continues; no stranded intermediate state |
|
|
190
226
|
| Yellow next + green revoke | After interjecting, green pulls the message back to the queue |
|
|
191
227
|
| Freeze / resume | Current turn finishes naturally without interruption, queue frozen with banner; resume drains everything in FIFO order |
|
|
192
|
-
| Queue editing (multi-line / fallback) | Covered by component tests (
|
|
228
|
+
| Queue editing (multi-line / fallback / drag-reorder / confirm) | Covered by component tests (36/36 green); real-environment re-check pending |
|
|
193
229
|
|
|
194
230
|
## References
|
|
195
231
|
|
package/README.md
CHANGED
|
@@ -71,10 +71,15 @@
|
|
|
71
71
|
| 操作 | 说明 |
|
|
72
72
|
|---|---|
|
|
73
73
|
| 上移 / 下移 | 调整 FIFO 顺序(整个队列按新顺序重建;含图片消息时禁用) |
|
|
74
|
+
| **拖拽排序** | 按住行直接拖动到目标位置(原生 HTML5 DnD,无额外依赖);与箭头按钮同样走服务端重建 |
|
|
74
75
|
| 打回输入框编辑 | 消息内容回填 composer 输入框并从队列移除,编辑后重新发送 |
|
|
75
76
|
| 编辑 / 删除 | 多行文本区直接修改排队内容 / 取消该消息 |
|
|
76
77
|
| 红 / 黄 / 绿规划 | 见「三档语义」 |
|
|
77
|
-
| 取消并清空 |
|
|
78
|
+
| 取消并清空 | 两步确认后停止当前执行并清空全部排队消息(首次点击弹出「确认清空?」) |
|
|
79
|
+
|
|
80
|
+
排序的**并发保护**:重建期间若某条消息已被 agent 认领(`queue-item-not-found`),本次排序立即中止且不重发,提示「队列已变化,本次排序已取消」——绝不会把变化中的队列排乱。
|
|
81
|
+
|
|
82
|
+
等待区的**展开状态会记忆**:手动收起/展开后,下次打开插件保持同样状态。
|
|
78
83
|
|
|
79
84
|
编辑排队消息(行内编辑)时:
|
|
80
85
|
|
|
@@ -85,8 +90,12 @@
|
|
|
85
90
|
## 安装
|
|
86
91
|
|
|
87
92
|
```sh
|
|
88
|
-
#
|
|
89
|
-
|
|
93
|
+
# 方式一:从 npm 安装(推荐)
|
|
94
|
+
# (profile 是 pnpm workspace root,add 需带 -w 参数)
|
|
95
|
+
dsh plugin --profile web add dsh-input-traffic -w
|
|
96
|
+
|
|
97
|
+
# 方式二:git 或本地路径组装
|
|
98
|
+
# dsh plugin --profile web add /absolute/path/to/dsh-input-traffic -w
|
|
90
99
|
# (git 安装后需在 profile 的 node_modules 内现场构建:npm install --legacy-peer-deps && npm run build)
|
|
91
100
|
|
|
92
101
|
# 确认组合树包含新行
|
|
@@ -102,9 +111,36 @@ dsh web
|
|
|
102
111
|
npm install --legacy-peer-deps # @deepseek-ai client 包链在 npm 上不完整,仅装工具链
|
|
103
112
|
npm run build # tsc(lib/types)+ tsdown(lib/index.js + lib/client.js)
|
|
104
113
|
node examples/verify-assembly.mjs # 12 项装配断言
|
|
105
|
-
npm test #
|
|
114
|
+
npm test # 36 项 vitest 组件测试
|
|
115
|
+
npm run lint # ESLint(src + tests,flat config)
|
|
116
|
+
npm run verify # 一体化门禁:lint + test + build + verify-assembly
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## 开发(TDD + Lint)
|
|
120
|
+
|
|
121
|
+
本项目按 **TDD**(测试驱动开发)维护:先写失败用例,再实现到全绿。
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
npm run tdd # vitest watch:改动即重跑,红→绿闭环
|
|
106
125
|
```
|
|
107
126
|
|
|
127
|
+
流程:
|
|
128
|
+
|
|
129
|
+
1. 在 `tests/` 新增/修改用例(红:确认新行为尚未实现);
|
|
130
|
+
2. `npm run tdd` 观察失败;
|
|
131
|
+
3. 在 `src/` 最小实现(绿);
|
|
132
|
+
4. `npm run verify` 全绿后提交(lint + 36 测试 + 构建 + 12 项装配断言)。
|
|
133
|
+
|
|
134
|
+
Lint 说明:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
npm run lint # ESLint flat config(eslint.config.mjs)
|
|
138
|
+
npm run lint:fix # 自动修复可修复项
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- 范围:`src/` 与 `tests/`(TypeScript + React);构建产物 `lib/` 忽略;
|
|
142
|
+
- 规则:`@typescript-eslint/recommended` + `react-hooks` 最佳实践;未使用变量报错(下划线前缀 `_` 可豁免)。
|
|
143
|
+
|
|
108
144
|
## 使用方式
|
|
109
145
|
|
|
110
146
|
1. 智能体忙碌时直接输入并发送,消息**统一进入等待区**(默认绿色排队);
|
package/lib/client.js
CHANGED
|
@@ -40,6 +40,8 @@ window.__ModuleLoader__.load({
|
|
|
40
40
|
"steer.moveDown": "下移",
|
|
41
41
|
"steer.reorder.unsupported": "包含非文本内容,暂不支持排序",
|
|
42
42
|
"steer.reorderFailed": "调整顺序失败,请重试。",
|
|
43
|
+
"steer.reorderStale": "队列已变化,本次排序已取消,请重试。",
|
|
44
|
+
"steer.dragReorder": "拖动调整顺序",
|
|
43
45
|
"steer.pullBack": "打回输入框编辑",
|
|
44
46
|
"steer.pullBack.unsupported": "包含非文本内容,暂不支持打回编辑",
|
|
45
47
|
"steer.pullBack.composerBusy": "主输入框已有内容,请先发送或清空后再打回",
|
|
@@ -49,6 +51,7 @@ window.__ModuleLoader__.load({
|
|
|
49
51
|
"steer.badge.later": "排队",
|
|
50
52
|
"steer.unavailable.running": "仅运行中可规划",
|
|
51
53
|
"steer.clear": "取消并清空",
|
|
54
|
+
"steer.clear.confirm": "确认清空?",
|
|
52
55
|
"steer.clear.aria": "取消当前执行并清空全部排队消息",
|
|
53
56
|
"steer.clearFailed": "取消并清空失败,请重试。",
|
|
54
57
|
"steer.freeze": "冻结会话",
|
|
@@ -86,6 +89,8 @@ window.__ModuleLoader__.load({
|
|
|
86
89
|
"steer.moveDown": "Move down",
|
|
87
90
|
"steer.reorder.unsupported": "Contains non-text content; reordering is unsupported",
|
|
88
91
|
"steer.reorderFailed": "Reorder failed, please retry.",
|
|
92
|
+
"steer.reorderStale": "The queue changed; this reorder was cancelled, please retry.",
|
|
93
|
+
"steer.dragReorder": "Drag to reorder",
|
|
89
94
|
"steer.pullBack": "Edit in composer",
|
|
90
95
|
"steer.pullBack.unsupported": "Contains non-text content; pulling back is unsupported",
|
|
91
96
|
"steer.pullBack.composerBusy": "The composer already has content; send or clear it first",
|
|
@@ -95,6 +100,7 @@ window.__ModuleLoader__.load({
|
|
|
95
100
|
"steer.badge.later": "queued",
|
|
96
101
|
"steer.unavailable.running": "Planning requires a running session",
|
|
97
102
|
"steer.clear": "Cancel and clear",
|
|
103
|
+
"steer.clear.confirm": "Confirm clear?",
|
|
98
104
|
"steer.clear.aria": "Cancel the current run and clear all queued messages",
|
|
99
105
|
"steer.clearFailed": "Cancel and clear failed, please retry.",
|
|
100
106
|
"steer.freeze": "Freeze session",
|
|
@@ -128,7 +134,7 @@ window.__ModuleLoader__.load({
|
|
|
128
134
|
};
|
|
129
135
|
//#endregion
|
|
130
136
|
//#region \0dsh-css:E:\test\rewrite-agently\dsh-input-traffic\src\client\steer-queue-dock.module.css.mjs
|
|
131
|
-
const css$1 = ".cev0eq_dock{width:100%}.cev0eq_panel{background:var(--ds-color-surface,#fffc);border:1px solid var(--ds-color-border,#00000014);border-radius:10px;overflow:hidden}.cev0eq_toolbar{border-bottom:1px solid var(--ds-color-border,#0000000f);align-items:center;gap:8px;padding:4px 8px;display:flex}.cev0eq_header{color:var(--ds-color-text-secondary,#6b7280);cursor:pointer;background:0 0;border:0;align-items:center;gap:6px;padding:2px 6px;font-size:12px;display:inline-flex}.cev0eq_header:disabled{cursor:default}.cev0eq_lead{color:var(--ds-color-text-tertiary,#9ca3af);display:inline-flex}.cev0eq_count{font-weight:600}.cev0eq_chevron{color:var(--ds-color-text-tertiary,#9ca3af);display:inline-flex}.cev0eq_toolbarActions{margin-left:auto}.cev0eq_clear{color:var(--ds-color-text-secondary,#6b7280);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;align-items:center;gap:4px;padding:3px 8px;font-size:12px;display:inline-flex}.cev0eq_freeze{border:1px solid var(--ds-color-border,#0000001f);color:var(--ds-color-text-secondary,#6b7280);cursor:pointer;background:0 0;border-radius:6px;align-items:center;gap:4px;padding:3px 8px;font-size:12px;display:inline-flex}.cev0eq_freeze[aria-pressed=true]{border-color:var(--ds-steer-green,#30a46c);color:var(--ds-steer-green,#30a46c)}.cev0eq_freeze:hover:not(:disabled){background:var(--ds-color-surface-hover,#0000000a)}.cev0eq_freeze:disabled{opacity:.5;cursor:default}.cev0eq_frozenBanner{border-bottom:1px solid var(--ds-color-border,#0000000f);background:color-mix(in srgb, var(--ds-steer-green,#30a46c) 8%, transparent);color:var(--ds-steer-green,#30a46c);padding:4px 10px;font-size:12px}.cev0eq_clear:hover:not(:disabled){border-color:var(--ds-color-border,#0000001f);background:var(--ds-color-surface-hover,#0000000a)}.cev0eq_clear:disabled{opacity:.5;cursor:default}.cev0eq_clearLabel{font-size:12px}.cev0eq_list{margin:0;padding:4px;list-style:none}.cev0eq_row{border-radius:6px;align-items:center;gap:8px;padding:4px 6px;display:flex}.cev0eq_row:hover{background:var(--ds-color-surface-hover,#00000008)}.cev0eq_row[data-editing]{align-items:flex-start}.cev0eq_preview{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--ds-color-text,#1f2937);flex:1;font-size:13px;overflow:hidden}.cev0eq_badge{white-space:nowrap;border-radius:999px;flex:none;align-items:center;gap:4px;padding:1px 6px;font-size:11px;line-height:16px;display:inline-flex}.cev0eq_badgeLabel{color:inherit}.cev0eq_badgeNow{color:var(--ds-steer-red,#e5484d);background:color-mix(in srgb, var(--ds-steer-red,#e5484d) 10%, transparent)}.cev0eq_badgeNext{color:var(--ds-steer-yellow,#e8a33d);background:color-mix(in srgb, var(--ds-steer-yellow,#e8a33d) 12%, transparent)}.cev0eq_badgeLater{color:var(--ds-steer-green,#30a46c);background:color-mix(in srgb, var(--ds-steer-green,#30a46c) 12%, transparent)}.cev0eq_badgeNow .cev0eq_dot{background:var(--ds-steer-red,#e5484d)}.cev0eq_badgeNext .cev0eq_dot{background:var(--ds-steer-yellow,#e8a33d)}.cev0eq_badgeLater .cev0eq_dot{background:var(--ds-steer-green,#30a46c)}.cev0eq_steeringList{margin:0;padding:0 4px 4px;list-style:none}.cev0eq_steeringList .cev0eq_row{opacity:.85}.cev0eq_editor{border:1px solid var(--ds-color-border,#00000026);background:var(--ds-color-surface,#fff);resize:none;border-radius:6px;flex:1;min-width:0;max-height:160px;padding:4px 6px;font-size:13px;line-height:20px;overflow-y:auto}.cev0eq_actions{align-items:center;gap:2px;display:inline-flex}.cev0eq_action{width:24px;height:24px;color:var(--ds-color-text-tertiary,#9ca3af);cursor:pointer;background:0 0;border:0;border-radius:6px;justify-content:center;align-items:center;display:inline-flex}.cev0eq_action:hover:not(:disabled){background:var(--ds-color-surface-hover,#0000000d);color:var(--ds-color-text,#1f2937)}.cev0eq_action:disabled{opacity:.4;cursor:default}.cev0eq_plan{border-left:1px solid var(--ds-color-border,#00000014);align-items:center;gap:2px;margin-left:4px;padding-left:6px;display:inline-flex}.cev0eq_tier{cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;justify-content:center;align-items:center;width:20px;height:20px;padding:0;display:inline-flex}.cev0eq_tier:hover:not(:disabled){border-color:var(--ds-color-border,#0003)}.cev0eq_tier:disabled{cursor:default}.cev0eq_tierLater{border-color:var(--ds-steer-green,#30a46c);background:color-mix(in srgb, var(--ds-steer-green,#30a46c) 12%, transparent)}.cev0eq_dot{border-radius:50%;width:10px;height:10px}.cev0eq_tierNow .cev0eq_dot{background:var(--ds-steer-red,#e5484d)}.cev0eq_tierNext .cev0eq_dot{background:var(--ds-steer-yellow,#e8a33d)}.cev0eq_tierLater .cev0eq_dot{background:var(--ds-steer-green,#30a46c)}";
|
|
137
|
+
const css$1 = ".cev0eq_dock{width:100%}.cev0eq_panel{background:var(--ds-color-surface,#fffc);border:1px solid var(--ds-color-border,#00000014);border-radius:10px;overflow:hidden}.cev0eq_toolbar{border-bottom:1px solid var(--ds-color-border,#0000000f);align-items:center;gap:8px;padding:4px 8px;display:flex}.cev0eq_header{color:var(--ds-color-text-secondary,#6b7280);cursor:pointer;background:0 0;border:0;align-items:center;gap:6px;padding:2px 6px;font-size:12px;display:inline-flex}.cev0eq_header:disabled{cursor:default}.cev0eq_lead{color:var(--ds-color-text-tertiary,#9ca3af);display:inline-flex}.cev0eq_count{font-weight:600}.cev0eq_chevron{color:var(--ds-color-text-tertiary,#9ca3af);display:inline-flex}.cev0eq_toolbarActions{margin-left:auto}.cev0eq_clear{color:var(--ds-color-text-secondary,#6b7280);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;align-items:center;gap:4px;padding:3px 8px;font-size:12px;display:inline-flex}.cev0eq_freeze{border:1px solid var(--ds-color-border,#0000001f);color:var(--ds-color-text-secondary,#6b7280);cursor:pointer;background:0 0;border-radius:6px;align-items:center;gap:4px;padding:3px 8px;font-size:12px;display:inline-flex}.cev0eq_freeze[aria-pressed=true]{border-color:var(--ds-steer-green,#30a46c);color:var(--ds-steer-green,#30a46c)}.cev0eq_freeze:hover:not(:disabled){background:var(--ds-color-surface-hover,#0000000a)}.cev0eq_freeze:disabled{opacity:.5;cursor:default}.cev0eq_frozenBanner{border-bottom:1px solid var(--ds-color-border,#0000000f);background:color-mix(in srgb, var(--ds-steer-green,#30a46c) 8%, transparent);color:var(--ds-steer-green,#30a46c);padding:4px 10px;font-size:12px}.cev0eq_clear:hover:not(:disabled){border-color:var(--ds-color-border,#0000001f);background:var(--ds-color-surface-hover,#0000000a)}.cev0eq_clear:disabled{opacity:.5;cursor:default}.cev0eq_clearConfirm{border-color:var(--ds-steer-red,#e5484d);color:var(--ds-steer-red,#e5484d)}.cev0eq_clearConfirm:hover:not(:disabled){border-color:var(--ds-steer-red,#e5484d);background:color-mix(in srgb, var(--ds-steer-red,#e5484d) 8%, transparent)}.cev0eq_clearLabel{font-size:12px}.cev0eq_list{margin:0;padding:4px;list-style:none}.cev0eq_row{border-radius:6px;align-items:center;gap:8px;padding:4px 6px;display:flex}.cev0eq_row[draggable=true]{cursor:grab}.cev0eq_row[draggable=true]:active{cursor:grabbing}.cev0eq_rowDragOver{outline:2px dashed var(--ds-color-border-strong,#00000040);outline-offset:-2px;background:var(--ds-color-surface-hover,#0000000d)}.cev0eq_row:hover{background:var(--ds-color-surface-hover,#00000008)}.cev0eq_row[data-editing]{align-items:flex-start}.cev0eq_preview{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--ds-color-text,#1f2937);flex:1;font-size:13px;overflow:hidden}.cev0eq_badge{white-space:nowrap;border-radius:999px;flex:none;align-items:center;gap:4px;padding:1px 6px;font-size:11px;line-height:16px;display:inline-flex}.cev0eq_badgeLabel{color:inherit}.cev0eq_badgeNow{color:var(--ds-steer-red,#e5484d);background:color-mix(in srgb, var(--ds-steer-red,#e5484d) 10%, transparent)}.cev0eq_badgeNext{color:var(--ds-steer-yellow,#e8a33d);background:color-mix(in srgb, var(--ds-steer-yellow,#e8a33d) 12%, transparent)}.cev0eq_badgeLater{color:var(--ds-steer-green,#30a46c);background:color-mix(in srgb, var(--ds-steer-green,#30a46c) 12%, transparent)}.cev0eq_badgeNow .cev0eq_dot{background:var(--ds-steer-red,#e5484d)}.cev0eq_badgeNext .cev0eq_dot{background:var(--ds-steer-yellow,#e8a33d)}.cev0eq_badgeLater .cev0eq_dot{background:var(--ds-steer-green,#30a46c)}.cev0eq_steeringList{margin:0;padding:0 4px 4px;list-style:none}.cev0eq_steeringList .cev0eq_row{opacity:.85}.cev0eq_editor{border:1px solid var(--ds-color-border,#00000026);background:var(--ds-color-surface,#fff);resize:none;border-radius:6px;flex:1;min-width:0;max-height:160px;padding:4px 6px;font-size:13px;line-height:20px;overflow-y:auto}.cev0eq_actions{align-items:center;gap:2px;display:inline-flex}.cev0eq_action{width:24px;height:24px;color:var(--ds-color-text-tertiary,#9ca3af);cursor:pointer;background:0 0;border:0;border-radius:6px;justify-content:center;align-items:center;display:inline-flex}.cev0eq_action:hover:not(:disabled){background:var(--ds-color-surface-hover,#0000000d);color:var(--ds-color-text,#1f2937)}.cev0eq_action:disabled{opacity:.4;cursor:default}.cev0eq_plan{border-left:1px solid var(--ds-color-border,#00000014);align-items:center;gap:2px;margin-left:4px;padding-left:6px;display:inline-flex}.cev0eq_tier{cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;justify-content:center;align-items:center;width:20px;height:20px;padding:0;display:inline-flex}.cev0eq_tier:hover:not(:disabled){border-color:var(--ds-color-border,#0003)}.cev0eq_tier:disabled{cursor:default}.cev0eq_tierLater{border-color:var(--ds-steer-green,#30a46c);background:color-mix(in srgb, var(--ds-steer-green,#30a46c) 12%, transparent)}.cev0eq_dot{border-radius:50%;width:10px;height:10px}.cev0eq_tierNow .cev0eq_dot{background:var(--ds-steer-red,#e5484d)}.cev0eq_tierNext .cev0eq_dot{background:var(--ds-steer-yellow,#e8a33d)}.cev0eq_tierLater .cev0eq_dot{background:var(--ds-steer-green,#30a46c)}";
|
|
132
138
|
const tagId$1 = "dsh-input-traffic/steer-queue-dock.module.css";
|
|
133
139
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
134
140
|
const tag = document.createElement("style");
|
|
@@ -138,36 +144,38 @@ window.__ModuleLoader__.load({
|
|
|
138
144
|
document.head.appendChild(tag);
|
|
139
145
|
}
|
|
140
146
|
var steer_queue_dock_module_css_default = {
|
|
141
|
-
"
|
|
142
|
-
"
|
|
143
|
-
"list": "cev0eq_list",
|
|
144
|
-
"tierLater": "cev0eq_tierLater",
|
|
145
|
-
"toolbar": "cev0eq_toolbar",
|
|
146
|
-
"count": "cev0eq_count",
|
|
147
|
-
"action": "cev0eq_action",
|
|
148
|
-
"tierNext": "cev0eq_tierNext",
|
|
147
|
+
"badgeLabel": "cev0eq_badgeLabel",
|
|
148
|
+
"dot": "cev0eq_dot",
|
|
149
149
|
"tierNow": "cev0eq_tierNow",
|
|
150
|
-
"
|
|
150
|
+
"actions": "cev0eq_actions",
|
|
151
|
+
"editor": "cev0eq_editor",
|
|
152
|
+
"badge": "cev0eq_badge",
|
|
153
|
+
"toolbarActions": "cev0eq_toolbarActions",
|
|
151
154
|
"clearLabel": "cev0eq_clearLabel",
|
|
152
|
-
"badgeLabel": "cev0eq_badgeLabel",
|
|
153
|
-
"badgeNow": "cev0eq_badgeNow",
|
|
154
155
|
"dock": "cev0eq_dock",
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
"
|
|
156
|
+
"panel": "cev0eq_panel",
|
|
157
|
+
"row": "cev0eq_row",
|
|
158
|
+
"rowDragOver": "cev0eq_rowDragOver",
|
|
158
159
|
"plan": "cev0eq_plan",
|
|
159
|
-
"
|
|
160
|
-
"
|
|
160
|
+
"tierLater": "cev0eq_tierLater",
|
|
161
|
+
"freeze": "cev0eq_freeze",
|
|
161
162
|
"badgeNext": "cev0eq_badgeNext",
|
|
162
|
-
"steeringList": "cev0eq_steeringList",
|
|
163
|
-
"lead": "cev0eq_lead",
|
|
164
|
-
"header": "cev0eq_header",
|
|
165
163
|
"tier": "cev0eq_tier",
|
|
166
|
-
"
|
|
167
|
-
"
|
|
168
|
-
"
|
|
169
|
-
"
|
|
170
|
-
"
|
|
164
|
+
"count": "cev0eq_count",
|
|
165
|
+
"preview": "cev0eq_preview",
|
|
166
|
+
"badgeNow": "cev0eq_badgeNow",
|
|
167
|
+
"chevron": "cev0eq_chevron",
|
|
168
|
+
"toolbar": "cev0eq_toolbar",
|
|
169
|
+
"list": "cev0eq_list",
|
|
170
|
+
"clear": "cev0eq_clear",
|
|
171
|
+
"header": "cev0eq_header",
|
|
172
|
+
"tierNext": "cev0eq_tierNext",
|
|
173
|
+
"lead": "cev0eq_lead",
|
|
174
|
+
"clearConfirm": "cev0eq_clearConfirm",
|
|
175
|
+
"frozenBanner": "cev0eq_frozenBanner",
|
|
176
|
+
"action": "cev0eq_action",
|
|
177
|
+
"steeringList": "cev0eq_steeringList",
|
|
178
|
+
"badgeLater": "cev0eq_badgeLater"
|
|
171
179
|
};
|
|
172
180
|
//#endregion
|
|
173
181
|
//#region src/client/steer-queue-dock.tsx
|
|
@@ -198,6 +206,8 @@ window.__ModuleLoader__.load({
|
|
|
198
206
|
*/
|
|
199
207
|
/** Busy marker for a whole-queue rebuild (reorder); locks every row action. */
|
|
200
208
|
const REORDER_MARK = "__reorder__";
|
|
209
|
+
/** localStorage key for the manual collapse state (dsh-queue-plus parity). */
|
|
210
|
+
const COLLAPSE_KEY = "dsh-input-traffic:collapsed";
|
|
201
211
|
/**
|
|
202
212
|
* Project the tier badge of one inbox row from its placement. Pure so the
|
|
203
213
|
* row rendering and its tests share one truth.
|
|
@@ -232,7 +242,18 @@ window.__ModuleLoader__.load({
|
|
|
232
242
|
const [editing, setEditing] = (0, react.useState)(null);
|
|
233
243
|
const [busy, setBusy] = (0, react.useState)(null);
|
|
234
244
|
const [clearing, setClearing] = (0, react.useState)(false);
|
|
235
|
-
const [
|
|
245
|
+
const [confirmClear, setConfirmClear] = (0, react.useState)(false);
|
|
246
|
+
const confirmTimer = (0, react.useRef)(null);
|
|
247
|
+
const dragIndex = (0, react.useRef)(null);
|
|
248
|
+
const [dragOver, setDragOver] = (0, react.useState)(null);
|
|
249
|
+
const [collapsed, setCollapsed] = (0, react.useState)(() => {
|
|
250
|
+
try {
|
|
251
|
+
const v = localStorage.getItem(COLLAPSE_KEY);
|
|
252
|
+
return v === null ? true : v === "1";
|
|
253
|
+
} catch {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
236
257
|
const { frozen } = (0, react.useSyncExternalStore)(freezeStore.subscribe, freezeStore.getSnapshot);
|
|
237
258
|
const listId = (0, react.useId)();
|
|
238
259
|
const editorRef = (0, react.useRef)(null);
|
|
@@ -246,10 +267,19 @@ window.__ModuleLoader__.load({
|
|
|
246
267
|
queueMutable
|
|
247
268
|
]);
|
|
248
269
|
(0, react.useEffect)(() => {
|
|
249
|
-
|
|
270
|
+
try {
|
|
271
|
+
localStorage.setItem(COLLAPSE_KEY, collapsed ? "1" : "0");
|
|
272
|
+
} catch {}
|
|
273
|
+
}, [collapsed]);
|
|
274
|
+
(0, react.useEffect)(() => () => {
|
|
275
|
+
if (confirmTimer.current !== null) clearTimeout(confirmTimer.current);
|
|
276
|
+
}, []);
|
|
277
|
+
const editingId = editing?.id;
|
|
278
|
+
(0, react.useEffect)(() => {
|
|
279
|
+
if (editingId === void 0) return;
|
|
250
280
|
const el = editorRef.current;
|
|
251
281
|
if (el !== null) resizeEditor(el);
|
|
252
|
-
}, [
|
|
282
|
+
}, [editingId]);
|
|
253
283
|
if (queue.length === 0 && steering.length === 0 && !running) return null;
|
|
254
284
|
const interactionActive = queueMutable && (editing !== null || busy !== null || clearing);
|
|
255
285
|
const expanded = !collapsed || interactionActive;
|
|
@@ -326,10 +356,34 @@ window.__ModuleLoader__.load({
|
|
|
326
356
|
}
|
|
327
357
|
};
|
|
328
358
|
/**
|
|
329
|
-
*
|
|
330
|
-
* the
|
|
331
|
-
* (
|
|
332
|
-
*
|
|
359
|
+
* Rebuild the queue in a new order: remove every queued row, then re-send
|
|
360
|
+
* the texts sequentially so the agent's next-turn list matches the new
|
|
361
|
+
* order. Concurrency protection (dsh-queue-plus parity): if a remove fails
|
|
362
|
+
* with queue-item-not-found, the agent already claimed that row — the
|
|
363
|
+
* rebuild stops immediately and nothing is re-sent, so the changed queue
|
|
364
|
+
* is never scrambled.
|
|
365
|
+
*/
|
|
366
|
+
const rebuildQueue = async (rows, next) => {
|
|
367
|
+
setBusy(REORDER_MARK);
|
|
368
|
+
try {
|
|
369
|
+
for (const row of rows) try {
|
|
370
|
+
await updateQueue(row.id, { kind: "remove" });
|
|
371
|
+
} catch (error) {
|
|
372
|
+
if (error instanceof Error && error.message.includes("queue-item-not-found")) {
|
|
373
|
+
notify("error", t("steer.reorderStale"));
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
378
|
+
for (const row of next) if (row.text !== null) await send(row.text);
|
|
379
|
+
} catch {
|
|
380
|
+
notify("error", t("steer.reorderFailed"));
|
|
381
|
+
} finally {
|
|
382
|
+
setBusy(null);
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
/**
|
|
386
|
+
* Move one queued row up/down in the FIFO order (arrow buttons).
|
|
333
387
|
*/
|
|
334
388
|
const reorder = async (rowId, delta) => {
|
|
335
389
|
const rows = queue;
|
|
@@ -344,15 +398,36 @@ window.__ModuleLoader__.load({
|
|
|
344
398
|
notify("error", t("steer.reorder.unsupported"));
|
|
345
399
|
return;
|
|
346
400
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
401
|
+
await rebuildQueue(rows, next);
|
|
402
|
+
};
|
|
403
|
+
/**
|
|
404
|
+
* Move one row to an absolute position (drag-and-drop drop handler).
|
|
405
|
+
*/
|
|
406
|
+
const reorderTo = async (fromIndex, toIndex) => {
|
|
407
|
+
if (fromIndex === toIndex) return;
|
|
408
|
+
const rows = queue;
|
|
409
|
+
if (rows[fromIndex] === void 0 || rows[toIndex] === void 0) return;
|
|
410
|
+
const next = [...rows];
|
|
411
|
+
const [moved] = next.splice(fromIndex, 1);
|
|
412
|
+
if (moved === void 0) return;
|
|
413
|
+
next.splice(toIndex, 0, moved);
|
|
414
|
+
if (next.some((row) => row.text === null)) {
|
|
415
|
+
notify("error", t("steer.reorder.unsupported"));
|
|
416
|
+
return;
|
|
355
417
|
}
|
|
418
|
+
await rebuildQueue(rows, next);
|
|
419
|
+
};
|
|
420
|
+
/** Arm or execute the two-step clear confirmation. */
|
|
421
|
+
const armClear = () => {
|
|
422
|
+
if (confirmClear) {
|
|
423
|
+
if (confirmTimer.current !== null) clearTimeout(confirmTimer.current);
|
|
424
|
+
setConfirmClear(false);
|
|
425
|
+
clearAll();
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
setConfirmClear(true);
|
|
429
|
+
if (confirmTimer.current !== null) clearTimeout(confirmTimer.current);
|
|
430
|
+
confirmTimer.current = setTimeout(() => setConfirmClear(false), 3e3);
|
|
356
431
|
};
|
|
357
432
|
/**
|
|
358
433
|
* Pull one queued row back into the composer draft for editing: the text
|
|
@@ -419,20 +494,18 @@ window.__ModuleLoader__.load({
|
|
|
419
494
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
420
495
|
className: steer_queue_dock_module_css_default.toolbarActions,
|
|
421
496
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
422
|
-
label: t("steer.clear"),
|
|
497
|
+
label: confirmClear ? t("steer.clear.confirm") : t("steer.clear"),
|
|
423
498
|
side: "top",
|
|
424
499
|
delayMs: 500,
|
|
425
500
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
426
501
|
type: "button",
|
|
427
|
-
className: steer_queue_dock_module_css_default.clear
|
|
428
|
-
"aria-label": t("steer.clear"),
|
|
502
|
+
className: `${steer_queue_dock_module_css_default.clear} ${confirmClear ? steer_queue_dock_module_css_default.clearConfirm : ""}`,
|
|
503
|
+
"aria-label": confirmClear ? t("steer.clear.confirm") : t("steer.clear"),
|
|
429
504
|
disabled: clearing || busy !== null || nothingPending,
|
|
430
|
-
onClick:
|
|
431
|
-
clearAll();
|
|
432
|
-
},
|
|
505
|
+
onClick: armClear,
|
|
433
506
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
434
507
|
className: steer_queue_dock_module_css_default.clearLabel,
|
|
435
|
-
children: t("steer.clear")
|
|
508
|
+
children: confirmClear ? t("steer.clear.confirm") : t("steer.clear")
|
|
436
509
|
})]
|
|
437
510
|
})
|
|
438
511
|
})
|
|
@@ -447,10 +520,32 @@ window.__ModuleLoader__.load({
|
|
|
447
520
|
id: listId,
|
|
448
521
|
className: steer_queue_dock_module_css_default.list,
|
|
449
522
|
hidden: !listVisible,
|
|
450
|
-
children: listVisible && queue.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
451
|
-
className: steer_queue_dock_module_css_default.row
|
|
523
|
+
children: listVisible && queue.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
524
|
+
className: `${steer_queue_dock_module_css_default.row} ${dragOver === index ? steer_queue_dock_module_css_default.rowDragOver : ""}`,
|
|
452
525
|
"data-tier": badgeFor("queued") ?? void 0,
|
|
453
526
|
"data-editing": editing?.id === row.id ? "" : void 0,
|
|
527
|
+
draggable: queueMutable && !reorderUnsupported && busy === null && !frozen,
|
|
528
|
+
title: queueMutable && !reorderUnsupported && busy === null && !frozen ? t("steer.dragReorder") : void 0,
|
|
529
|
+
onDragStart: (event) => {
|
|
530
|
+
dragIndex.current = index;
|
|
531
|
+
event.dataTransfer.effectAllowed = "move";
|
|
532
|
+
},
|
|
533
|
+
onDragOver: (event) => {
|
|
534
|
+
if (dragIndex.current === null) return;
|
|
535
|
+
event.preventDefault();
|
|
536
|
+
setDragOver(index);
|
|
537
|
+
},
|
|
538
|
+
onDrop: (event) => {
|
|
539
|
+
event.preventDefault();
|
|
540
|
+
const from = dragIndex.current;
|
|
541
|
+
dragIndex.current = null;
|
|
542
|
+
setDragOver(null);
|
|
543
|
+
if (from !== null) reorderTo(from, index);
|
|
544
|
+
},
|
|
545
|
+
onDragEnd: () => {
|
|
546
|
+
dragIndex.current = null;
|
|
547
|
+
setDragOver(null);
|
|
548
|
+
},
|
|
454
549
|
children: [
|
|
455
550
|
queue.length === 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
456
551
|
className: steer_queue_dock_module_css_default.lead,
|