dsh-newbe-response-window 0.2.0 → 0.3.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.md +17 -3
- package/cordis.patch.yml +6 -0
- package/lib/client.js +183 -44
- package/lib/index.js +7 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,6 +16,10 @@ DeepSeek Harness (DSH) Web 插件:把一轮里的 think(推理)和工具
|
|
|
16
16
|
- 主体:`max-height: N 行`(默认 10)的**内部滚动区**
|
|
17
17
|
- **think 与工具调用严格按真实输出顺序交错排列**(不做「think 全在上 / bash 全在下」的强行分区)
|
|
18
18
|
- 每段 think 一行(Think + 单行摘要 + 展开箭头),默认折叠,点击展开完整推理(内部限高滚动);展开文字与工具输出同字号(同一 mono 字体)
|
|
19
|
+
- **think 流式显示三种模式**(`liveStreamThink`,默认 `'out'`):
|
|
20
|
+
- `'out'`(默认):正在流式的 think 在 **slide 外**(原生位置)实时显示,流式结束后**收进 slide 折叠**成单行摘要
|
|
21
|
+
- `'in'`:流式 think 在 **slide 内**自动展开、正文实时滚动,结束后自动收起为单行摘要
|
|
22
|
+
- `'off'`:think 全程折叠为单行摘要,不做流式展开
|
|
19
23
|
- 每条工具调用一行(状态点 + 工具名 + 单行摘要),点击展开参数/输出(输出再限高一档,内部滚动)
|
|
20
24
|
- 执行中自动跟随底部
|
|
21
25
|
- **原生 Think 行从消息流中隐藏**(该段已并入 slide,避免重复显示),与 slide 内展示共用同一份内容
|
|
@@ -26,6 +30,14 @@ DeepSeek Harness (DSH) Web 插件:把一轮里的 think(推理)和工具
|
|
|
26
30
|
|
|
27
31
|
## 安装
|
|
28
32
|
|
|
33
|
+
从 npm 安装:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
dsh plugin --profile web add dsh-newbe-response-window
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
或从合集仓库 GitHub 子目录安装:
|
|
40
|
+
|
|
29
41
|
```bash
|
|
30
42
|
dsh plugin --profile web add github:qiqiangvae/dsh-newbe-plugins#path:packages/dsh-newbe-response-window
|
|
31
43
|
```
|
|
@@ -54,13 +66,15 @@ dsh plugin --profile web add "link:$(pwd)/packages/dsh-newbe-response-window"
|
|
|
54
66
|
| `collapsed` | `false` | 每轮 slide 是否默认收起成一行 bar。默认 `false`:始终展开、内容可见 |
|
|
55
67
|
| `showReadOnly` | `true` | 是否在 slide 里列出 read/grep/web_search 等只读调用(默认全列出,不藏) |
|
|
56
68
|
| `minCollapseRows` | `3` | 仅 `collapsed: true` 时生效:少于该数量的轮次不收起 |
|
|
69
|
+
| `liveStreamThink` | `'out'` | 流式 think 的显示模式:`'out'` 盒外流式结束后进盒折叠 / `'in'` 盒内自动展开 / `'off'` 全程折叠 |
|
|
57
70
|
|
|
58
71
|
### 设置页(Settings → General)
|
|
59
72
|
|
|
60
|
-
插件在 Web UI 的 **Settings → General**
|
|
73
|
+
插件在 Web UI 的 **Settings → General** 里注册了两项:
|
|
61
74
|
|
|
62
|
-
-
|
|
63
|
-
-
|
|
75
|
+
- **「响应窗口大小(行数)」**:`−` / 数值输入 / `+` 调整 `lines`(0–200,`0` = 不限高,默认 10)
|
|
76
|
+
- **「流式思考显示」**:三选一(关闭 / 盒内 / 出盒),对应 `liveStreamThink`(默认出盒)
|
|
77
|
+
- 均**即时生效**:改动后已渲染的 slide 立刻变化(经宿主 settings namespace `dsh-newbe-response-window` 持久化)
|
|
64
78
|
|
|
65
79
|
## 实现说明(为什么安全)
|
|
66
80
|
|
package/cordis.patch.yml
CHANGED
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
# minCollapseRows only auto-collapse a finished slide when it has at least
|
|
20
20
|
# this many tool calls (default 3; only used with
|
|
21
21
|
# collapsed=true).
|
|
22
|
+
# liveStreamThink how a streaming think block is shown (default 'out'):
|
|
23
|
+
# 'off' — always folded into a one-line summary
|
|
24
|
+
# 'in' — auto-expand inside the slide, live scroll
|
|
25
|
+
# 'out' — stream outside the slide (native position),
|
|
26
|
+
# then fold into the slide once settled
|
|
22
27
|
- insert:
|
|
23
28
|
- id: dsh-newbe-response-window
|
|
24
29
|
name: 'dsh-newbe-response-window'
|
|
@@ -27,3 +32,4 @@
|
|
|
27
32
|
collapsed: false
|
|
28
33
|
showReadOnly: true
|
|
29
34
|
minCollapseRows: 3
|
|
35
|
+
liveStreamThink: 'out'
|
package/lib/client.js
CHANGED
|
@@ -61,17 +61,28 @@ window.__ModuleLoader__.load({
|
|
|
61
61
|
var createSnapshotStore = storeLib.createSnapshotStore
|
|
62
62
|
|
|
63
63
|
// ---- config -----------------------------------------------------------
|
|
64
|
+
// liveStreamThink mode: 'off' (always folded) | 'in' (auto-expand the row
|
|
65
|
+
// inside the slide while streaming) | 'out' (stream outside the slide in
|
|
66
|
+
// the native position, fold into the slide when settled).
|
|
64
67
|
var DEFAULTS = {
|
|
65
68
|
lines: 10,
|
|
66
69
|
collapsed: false,
|
|
67
70
|
showReadOnly: true,
|
|
68
71
|
minCollapseRows: 3,
|
|
72
|
+
liveStreamThink: 'out',
|
|
69
73
|
}
|
|
70
74
|
function clampInt(value, min, max, fallback) {
|
|
71
75
|
var n = Number(value)
|
|
72
76
|
if (!Number.isFinite(n)) return fallback
|
|
73
77
|
return Math.min(max, Math.max(min, Math.round(n)))
|
|
74
78
|
}
|
|
79
|
+
function normalizeLiveStreamThink(value, fallback) {
|
|
80
|
+
if (value === 'off' || value === 'in' || value === 'out') return value
|
|
81
|
+
// Legacy boolean row config: true meant in-box live streaming.
|
|
82
|
+
if (value === true) return 'in'
|
|
83
|
+
if (value === false) return 'off'
|
|
84
|
+
return fallback
|
|
85
|
+
}
|
|
75
86
|
function readConfig(cfg) {
|
|
76
87
|
cfg = cfg || {}
|
|
77
88
|
return {
|
|
@@ -79,19 +90,22 @@ window.__ModuleLoader__.load({
|
|
|
79
90
|
collapsed: cfg.collapsed === true,
|
|
80
91
|
showReadOnly: cfg.showReadOnly !== false,
|
|
81
92
|
minCollapseRows: clampInt(cfg.minCollapseRows, 1, 50, DEFAULTS.minCollapseRows),
|
|
93
|
+
liveStreamThink: normalizeLiveStreamThink(cfg.liveStreamThink, DEFAULTS.liveStreamThink),
|
|
82
94
|
}
|
|
83
95
|
}
|
|
84
96
|
|
|
85
97
|
// ---- live settings state ----------------------------------------------
|
|
86
|
-
// `lines`
|
|
98
|
+
// `lines` and `liveStreamThink` are user-adjustable from Settings; the
|
|
99
|
+
// rest come from row config.
|
|
87
100
|
var linesStore = null
|
|
101
|
+
var liveStreamThinkStore = null
|
|
88
102
|
var settingsScope = null
|
|
89
103
|
var NS = 'dsh-newbe-response-window'
|
|
90
104
|
function clampLines(value) { return clampInt(value, 0, 200, DEFAULTS.lines) }
|
|
91
105
|
|
|
92
106
|
/** Minimal observable-value hook over a SnapshotStore (plain React). */
|
|
93
|
-
function useStoreValue(store) {
|
|
94
|
-
var pair = React.useState(function () { return store === null ?
|
|
107
|
+
function useStoreValue(store, fallback) {
|
|
108
|
+
var pair = React.useState(function () { return store === null ? fallback : store.getSnapshot() })
|
|
95
109
|
var value = pair[0]
|
|
96
110
|
var setValue = pair[1]
|
|
97
111
|
React.useEffect(function () {
|
|
@@ -105,7 +119,7 @@ window.__ModuleLoader__.load({
|
|
|
105
119
|
|
|
106
120
|
/** Live window size in lines (follows the Settings store). */
|
|
107
121
|
function useLiveLines() {
|
|
108
|
-
return useStoreValue(linesStore)
|
|
122
|
+
return useStoreValue(linesStore, DEFAULTS.lines)
|
|
109
123
|
}
|
|
110
124
|
|
|
111
125
|
// ---- shared style injection ------------------------------------------
|
|
@@ -195,7 +209,7 @@ window.__ModuleLoader__.load({
|
|
|
195
209
|
'.drw-think-name { color: var(--dsw-alias-label-secondary, #bbb); font-size: 11px; font-weight: 600; letter-spacing: 0.04em; flex: none; }',
|
|
196
210
|
'.drw-think-summary { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #999); font-size: 12.5px; flex: 1 1 auto; }',
|
|
197
211
|
'.drw-think-toggle { color: var(--dsw-alias-label-tertiary, #888); font-size: 10px; flex: none; }',
|
|
198
|
-
'.drw-think-body { margin: 0; padding: 6px 8px; color: var(--dsw-alias-label-primary, #eee); font: var(--dsw-font-markdown-code-block-small, 12px/1.5 ui-monospace, monospace); white-space: pre-wrap; word-break: break-word; max-height:
|
|
212
|
+
'.drw-think-body { margin: 0; padding: 6px 8px; color: var(--dsw-alias-label-primary, #eee); font: var(--dsw-font-markdown-code-block-small, 12px/1.5 ui-monospace, monospace); white-space: pre-wrap; word-break: break-word; max-height: 96px; overflow-y: auto; }',
|
|
199
213
|
/* Hide the native Think row once it's inside a slide (DOM + CSS only) */
|
|
200
214
|
'[data-variant="think"][data-drw-hidethink="1"] { display: none !important; }',
|
|
201
215
|
/* Settings row: response window size */
|
|
@@ -213,6 +227,10 @@ window.__ModuleLoader__.load({
|
|
|
213
227
|
'}',
|
|
214
228
|
'.drw-set-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,0.16)); }',
|
|
215
229
|
'.drw-set-btn:disabled { opacity: 0.4; cursor: default; }',
|
|
230
|
+
'.drw-set-btn.drw-set-on {',
|
|
231
|
+
' border-color: var(--dsw-alias-state-business-primary, var(--dsw-alias-brand, #3dbbf5));',
|
|
232
|
+
' color: var(--dsw-alias-state-business-primary, var(--dsw-alias-brand, #3dbbf5));',
|
|
233
|
+
'}',
|
|
216
234
|
'.drw-set-input {',
|
|
217
235
|
' width: 56px; height: 28px; text-align: center;',
|
|
218
236
|
' border: 1px solid var(--dsw-alias-border-l2, var(--dsw-alias-border-strong, rgba(128,128,128,0.35)));',
|
|
@@ -343,16 +361,36 @@ window.__ModuleLoader__.load({
|
|
|
343
361
|
// ---- one think (reasoning) row inside the slide -------------------------
|
|
344
362
|
// A compact single-line row, collapsible to the full reasoning text — the
|
|
345
363
|
// same look and feel as a tool-call row (think and tool calls are both
|
|
346
|
-
// "implementation" and should read the same inside the window).
|
|
364
|
+
// "implementation" and should read the same inside the window). While the
|
|
365
|
+
// block is still streaming the row auto-expands and the body pins to the
|
|
366
|
+
// latest line; when it settles the row folds back to its one-line summary.
|
|
367
|
+
// The head stays a static first-line summary the whole time, so the only
|
|
368
|
+
// moving part during a stream is the expanding body — one visual focus.
|
|
347
369
|
function ThinkRow(props) {
|
|
348
370
|
var text = props.text
|
|
349
371
|
var index = props.index
|
|
372
|
+
var streaming = props.streaming === true
|
|
350
373
|
var openState = React.useState(false)
|
|
351
374
|
var open = openState[0]
|
|
352
375
|
var setOpen = openState[1]
|
|
376
|
+
var bodyRef = React.useRef(null)
|
|
353
377
|
var firstLine = text.split('\n')[0].trim() || text.trim()
|
|
354
378
|
var summary = firstLine.length > 120 ? firstLine.slice(0, 117) + '…' : firstLine
|
|
355
|
-
|
|
379
|
+
// Live streaming: auto-expand while streaming, fold back once settled.
|
|
380
|
+
React.useEffect(function () {
|
|
381
|
+
if (streaming) setOpen(true)
|
|
382
|
+
else setOpen(false)
|
|
383
|
+
}, [streaming])
|
|
384
|
+
// While streaming, keep the body pinned to the latest line.
|
|
385
|
+
React.useEffect(function () {
|
|
386
|
+
if (!open || !bodyRef.current) return
|
|
387
|
+
if (streaming) bodyRef.current.scrollTop = bodyRef.current.scrollHeight
|
|
388
|
+
})
|
|
389
|
+
return React.createElement('div', {
|
|
390
|
+
className: 'drw-think',
|
|
391
|
+
'data-open': open ? '1' : undefined,
|
|
392
|
+
'data-streaming': streaming ? '1' : undefined,
|
|
393
|
+
},
|
|
356
394
|
React.createElement('div', {
|
|
357
395
|
className: 'drw-think-head', role: 'button', tabIndex: 0,
|
|
358
396
|
'aria-expanded': open || undefined,
|
|
@@ -364,7 +402,7 @@ window.__ModuleLoader__.load({
|
|
|
364
402
|
React.createElement('span', { className: 'drw-think-toggle', 'aria-hidden': true }, open ? '\u25BE' : '\u25B8'),
|
|
365
403
|
),
|
|
366
404
|
open
|
|
367
|
-
? React.createElement('pre', { className: 'drw-think-body' }, text)
|
|
405
|
+
? React.createElement('pre', { ref: bodyRef, className: 'drw-think-body' }, text)
|
|
368
406
|
: null,
|
|
369
407
|
)
|
|
370
408
|
}
|
|
@@ -382,6 +420,7 @@ window.__ModuleLoader__.load({
|
|
|
382
420
|
var openFile = props.openFile
|
|
383
421
|
var inspectCall = props.inspectCall
|
|
384
422
|
var cfg = props.config
|
|
423
|
+
var streamingThink = props.streamingThink || null
|
|
385
424
|
var lines = useLiveLines() // live window size from Settings
|
|
386
425
|
var toolItems = items.filter(function (it) { return it.kind === 'tool' })
|
|
387
426
|
var reasoningCount = items.reduce(function (acc, it) { return it.kind === 'think' ? acc + 1 : acc }, 0)
|
|
@@ -393,10 +432,27 @@ window.__ModuleLoader__.load({
|
|
|
393
432
|
var setOpen = openState[1]
|
|
394
433
|
var bodyRef = React.useRef(null)
|
|
395
434
|
var running = stats.running > 0
|
|
396
|
-
//
|
|
435
|
+
// Streaming detection depends on the mode:
|
|
436
|
+
// 'in' — a think block inside the box is still streaming (the row
|
|
437
|
+
// auto-expands and the slide is forced open so the live
|
|
438
|
+
// reasoning stays readable);
|
|
439
|
+
// 'out' — the live tail is streaming OUTSIDE the slide (native row)
|
|
440
|
+
// and folds in when settled; the slide is forced open so the
|
|
441
|
+
// fold-in is visible.
|
|
442
|
+
// In both cases streaming overrides `collapsed`, then the user's
|
|
443
|
+
// collapsed setting is restored (Q7).
|
|
444
|
+
var mode = cfg.liveStreamThink
|
|
445
|
+
var streaming = mode === 'in'
|
|
446
|
+
? items.some(function (it) { return it.kind === 'think' && it.streaming === true })
|
|
447
|
+
: mode === 'out'
|
|
448
|
+
? streamingThink !== null
|
|
449
|
+
: false
|
|
450
|
+
var effectiveOpen = open || streaming
|
|
451
|
+
// Auto-follow to the bottom while calls are still running or a think is
|
|
452
|
+
// still streaming.
|
|
397
453
|
React.useEffect(function () {
|
|
398
|
-
if (!
|
|
399
|
-
if (running) bodyRef.current.scrollTop = bodyRef.current.scrollHeight
|
|
454
|
+
if (!effectiveOpen || !bodyRef.current) return
|
|
455
|
+
if (running || streaming) bodyRef.current.scrollTop = bodyRef.current.scrollHeight
|
|
400
456
|
})
|
|
401
457
|
var list = items
|
|
402
458
|
if (!cfg.showReadOnly) {
|
|
@@ -405,10 +461,10 @@ window.__ModuleLoader__.load({
|
|
|
405
461
|
})
|
|
406
462
|
}
|
|
407
463
|
var thinkSeen = 0
|
|
408
|
-
return React.createElement('div', { className: 'drw-slide', 'data-running': running ? '1' : undefined },
|
|
464
|
+
return React.createElement('div', { className: 'drw-slide', 'data-running': running || streaming ? '1' : undefined },
|
|
409
465
|
React.createElement('div', {
|
|
410
466
|
className: 'drw-head', role: 'button', tabIndex: 0,
|
|
411
|
-
'aria-expanded':
|
|
467
|
+
'aria-expanded': effectiveOpen || undefined,
|
|
412
468
|
onClick: function () { setOpen(!open) },
|
|
413
469
|
onKeyDown: function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setOpen(!open) } },
|
|
414
470
|
},
|
|
@@ -416,6 +472,9 @@ window.__ModuleLoader__.load({
|
|
|
416
472
|
stats.total + ' 个工具调用'
|
|
417
473
|
+ (reasoningCount > 0 ? ' · ' + reasoningCount + ' Think' : '')
|
|
418
474
|
),
|
|
475
|
+
streaming
|
|
476
|
+
? React.createElement('span', { className: 'drw-head-badge', 'data-state': 'running' }, '思考中')
|
|
477
|
+
: null,
|
|
419
478
|
running
|
|
420
479
|
? React.createElement('span', { className: 'drw-head-badge', 'data-state': 'running' }, '进行中')
|
|
421
480
|
: null,
|
|
@@ -425,18 +484,23 @@ window.__ModuleLoader__.load({
|
|
|
425
484
|
!cfg.showReadOnly && stats.readOnly > 0
|
|
426
485
|
? React.createElement('span', { className: 'drw-head-badge' }, '隐藏只读 ' + stats.readOnly)
|
|
427
486
|
: null,
|
|
428
|
-
React.createElement('span', { className: 'drw-head-toggle' },
|
|
487
|
+
React.createElement('span', { className: 'drw-head-toggle' }, effectiveOpen ? '收起 ▴' : '展开 ▾'),
|
|
429
488
|
),
|
|
430
489
|
React.createElement('div', {
|
|
431
490
|
ref: bodyRef,
|
|
432
|
-
className: 'drw-body' + (
|
|
433
|
-
style:
|
|
491
|
+
className: 'drw-body' + (effectiveOpen ? '' : ' drw-collapsed'),
|
|
492
|
+
style: effectiveOpen && lines > 0 ? { maxHeight: (lines * 1.55) + 'em' } : undefined,
|
|
434
493
|
},
|
|
435
494
|
list.map(function (it) {
|
|
436
495
|
if (it.kind === 'think') {
|
|
437
496
|
var tid = thinkSeen
|
|
438
497
|
thinkSeen += 1
|
|
439
|
-
return React.createElement(ThinkRow, {
|
|
498
|
+
return React.createElement(ThinkRow, {
|
|
499
|
+
key: 'think-' + tid,
|
|
500
|
+
text: it.text,
|
|
501
|
+
index: tid,
|
|
502
|
+
streaming: it.streaming === true,
|
|
503
|
+
})
|
|
440
504
|
}
|
|
441
505
|
return React.createElement(SimpleToolRow, {
|
|
442
506
|
key: it.node.key,
|
|
@@ -469,12 +533,13 @@ window.__ModuleLoader__.load({
|
|
|
469
533
|
var current = null
|
|
470
534
|
function ensure() {
|
|
471
535
|
if (current === null) {
|
|
472
|
-
current = { items: [], tools: [], startKey: null }
|
|
536
|
+
current = { items: [], tools: [], startKey: null, streamingThink: null }
|
|
473
537
|
segments.push(current)
|
|
474
538
|
}
|
|
475
539
|
return current
|
|
476
540
|
}
|
|
477
541
|
function closeSegment() { current = null }
|
|
542
|
+
var mode = currentConfig.liveStreamThink
|
|
478
543
|
for (var i = 0; i < order.length; i++) {
|
|
479
544
|
var n = chat.nodes.get(order[i])
|
|
480
545
|
if (n === undefined) continue
|
|
@@ -482,13 +547,29 @@ window.__ModuleLoader__.load({
|
|
|
482
547
|
if (n.kind === 'assistant-step') {
|
|
483
548
|
var blocks = n.data && Array.isArray(n.data.blocks) ? n.data.blocks : []
|
|
484
549
|
var staged = hasVisibleTextBlock(blocks)
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
//
|
|
550
|
+
// While a step is running, its LAST reasoning block is the live
|
|
551
|
+
// streaming tail — the exact rule the native ReasoningRow uses
|
|
552
|
+
// (`running: streaming && i === last`).
|
|
553
|
+
// mode 'out': the tail streams OUTSIDE the slide (native row) and
|
|
554
|
+
// folds in once settled — recorded as streamingThink.
|
|
555
|
+
// mode 'in' : the tail stays INSIDE the box, tagged `streaming` so
|
|
556
|
+
// the row auto-expands and shows live progress.
|
|
557
|
+
// mode 'off': no streaming behavior, always folded.
|
|
558
|
+
var stepRunning = !!n.data && n.data.status === 'running'
|
|
559
|
+
var lastIdx = blocks.length - 1
|
|
488
560
|
for (var b = 0; b < blocks.length; b++) {
|
|
489
561
|
var block = blocks[b]
|
|
490
562
|
if (block && block.kind === 'reasoning' && typeof block.text === 'string' && block.text.trim() !== '') {
|
|
491
|
-
|
|
563
|
+
if (mode === 'out' && stepRunning && b === lastIdx) {
|
|
564
|
+
ensure().streamingThink = { text: block.text, nodeKey: n.key }
|
|
565
|
+
} else {
|
|
566
|
+
ensure().items.push({
|
|
567
|
+
kind: 'think',
|
|
568
|
+
text: block.text,
|
|
569
|
+
nodeKey: n.key,
|
|
570
|
+
streaming: mode === 'in' && stepRunning && b === lastIdx,
|
|
571
|
+
})
|
|
572
|
+
}
|
|
492
573
|
}
|
|
493
574
|
}
|
|
494
575
|
if (staged) closeSegment()
|
|
@@ -554,6 +635,7 @@ window.__ModuleLoader__.load({
|
|
|
554
635
|
cwd: cwd,
|
|
555
636
|
openFile: openFile,
|
|
556
637
|
inspectCall: inspectCall,
|
|
638
|
+
streamingThink: seg.streamingThink || null,
|
|
557
639
|
config: currentConfig,
|
|
558
640
|
})
|
|
559
641
|
} catch (e) {
|
|
@@ -658,7 +740,15 @@ window.__ModuleLoader__.load({
|
|
|
658
740
|
if (isSegmentBoundary(next)) break
|
|
659
741
|
if (next.querySelector && next.querySelector('.drw-slide')) { hasSlide = true; break }
|
|
660
742
|
}
|
|
661
|
-
if (hasSlide)
|
|
743
|
+
if (hasSlide) {
|
|
744
|
+
// In 'out' mode the streaming think tail lives OUTSIDE the box
|
|
745
|
+
// (native row) until it settles: keep it visible while
|
|
746
|
+
// `data-streaming` is present, then fold it in (hide) the moment the
|
|
747
|
+
// stream is gone. In 'in'/'off' modes the native row is always
|
|
748
|
+
// hidden — the box renders the same content.
|
|
749
|
+
var streamingOut = currentConfig.liveStreamThink === 'out' && !!think.closest('[data-streaming]')
|
|
750
|
+
if (!streamingOut) think.dataset.drwHidethink = '1'
|
|
751
|
+
}
|
|
662
752
|
else if (think.dataset.drwHidethink) delete think.dataset.drwHidethink
|
|
663
753
|
}
|
|
664
754
|
}
|
|
@@ -711,34 +801,64 @@ window.__ModuleLoader__.load({
|
|
|
711
801
|
}
|
|
712
802
|
|
|
713
803
|
// ---- Settings row: response window size ---------------------------------
|
|
804
|
+
var STREAM_MODES = [
|
|
805
|
+
{ value: 'off', label: '关闭' },
|
|
806
|
+
{ value: 'in', label: '盒内' },
|
|
807
|
+
{ value: 'out', label: '出盒' },
|
|
808
|
+
]
|
|
714
809
|
function ResponseWindowSettingsRow(props) {
|
|
715
810
|
var useLines = props.useLines
|
|
716
811
|
var setLines = props.setLines
|
|
812
|
+
var useLiveStreamThink = props.useLiveStreamThink
|
|
813
|
+
var setLiveStreamThink = props.setLiveStreamThink
|
|
717
814
|
var lines = (typeof useLines === 'function' ? useLines(function (s) { return s }) : null) ?? currentConfig.lines
|
|
718
815
|
var value = clampLines(lines)
|
|
719
816
|
var step = function (delta) {
|
|
720
817
|
setLines(value + delta)
|
|
721
818
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
819
|
+
var liveStreamThink = (typeof useLiveStreamThink === 'function' ? useLiveStreamThink(function (s) { return s }) : null) ?? currentConfig.liveStreamThink
|
|
820
|
+
var pickMode = function (mode) {
|
|
821
|
+
if (typeof setLiveStreamThink === 'function') setLiveStreamThink(mode)
|
|
822
|
+
}
|
|
823
|
+
return React.createElement(React.Fragment, null,
|
|
824
|
+
React.createElement('div', { className: 'drw-set-row' },
|
|
825
|
+
React.createElement('div', { className: 'drw-set-text' },
|
|
826
|
+
React.createElement('div', { className: 'drw-set-title' }, '响应窗口大小(行数)'),
|
|
827
|
+
React.createElement('div', { className: 'drw-set-desc' }, '每个响应 slide 的限高滚动窗口。默认 10 行,0 = 不限高,改动即时生效。'),
|
|
828
|
+
),
|
|
829
|
+
React.createElement('div', { className: 'drw-set-control' },
|
|
830
|
+
React.createElement('button', {
|
|
831
|
+
type: 'button', className: 'drw-set-btn', 'aria-label': '减小窗口',
|
|
832
|
+
disabled: value <= 0,
|
|
833
|
+
onClick: function () { step(-1) },
|
|
834
|
+
}, '−'),
|
|
835
|
+
React.createElement('input', {
|
|
836
|
+
type: 'number', className: 'drw-set-input', min: 0, max: 200, step: 1, value: String(value),
|
|
837
|
+
onChange: function (e) { setLines(e.target.value) },
|
|
838
|
+
}),
|
|
839
|
+
React.createElement('button', {
|
|
840
|
+
type: 'button', className: 'drw-set-btn', 'aria-label': '增大窗口',
|
|
841
|
+
disabled: value >= 200,
|
|
842
|
+
onClick: function () { step(1) },
|
|
843
|
+
}, '+'),
|
|
844
|
+
),
|
|
726
845
|
),
|
|
727
|
-
React.createElement('div', { className: 'drw-set-
|
|
728
|
-
React.createElement('
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
846
|
+
React.createElement('div', { className: 'drw-set-row' },
|
|
847
|
+
React.createElement('div', { className: 'drw-set-text' },
|
|
848
|
+
React.createElement('div', { className: 'drw-set-title' }, '流式思考显示'),
|
|
849
|
+
React.createElement('div', { className: 'drw-set-desc' }, '出盒:思考在 slide 外实时显示,结束后收进 slide 折叠;盒内:在 slide 内自动展开实时滚动;关闭:think 全程保持折叠。'),
|
|
850
|
+
),
|
|
851
|
+
React.createElement('div', { className: 'drw-set-control' },
|
|
852
|
+
STREAM_MODES.map(function (m) {
|
|
853
|
+
return React.createElement('button', {
|
|
854
|
+
key: m.value,
|
|
855
|
+
type: 'button',
|
|
856
|
+
className: 'drw-set-btn' + (liveStreamThink === m.value ? ' drw-set-on' : ''),
|
|
857
|
+
'aria-pressed': liveStreamThink === m.value,
|
|
858
|
+
onClick: function () { pickMode(m.value) },
|
|
859
|
+
}, m.label)
|
|
860
|
+
}),
|
|
861
|
+
),
|
|
742
862
|
),
|
|
743
863
|
)
|
|
744
864
|
}
|
|
@@ -754,6 +874,7 @@ window.__ModuleLoader__.load({
|
|
|
754
874
|
// Durable settings binding: the host half registered the `dsh-newbe-response-window`
|
|
755
875
|
// namespace; the browser scope mirrors it and persists user overrides.
|
|
756
876
|
linesStore = createSnapshotStore(currentConfig.lines)
|
|
877
|
+
liveStreamThinkStore = createSnapshotStore(currentConfig.liveStreamThink)
|
|
757
878
|
var scopeService = null
|
|
758
879
|
try { scopeService = ctx.get('settingsScope') } catch (e) { scopeService = null }
|
|
759
880
|
if (scopeService && typeof scopeService.bind === 'function') {
|
|
@@ -772,6 +893,15 @@ window.__ModuleLoader__.load({
|
|
|
772
893
|
var n = clampLines(value.lines)
|
|
773
894
|
if (linesStore !== null && linesStore.getSnapshot() !== n) linesStore.set(n)
|
|
774
895
|
}
|
|
896
|
+
// liveStreamThink is stored as 'off' | 'in' | 'out'; tolerate
|
|
897
|
+
// legacy boolean rows (true → 'in', false → 'off').
|
|
898
|
+
if (value && value.liveStreamThink !== undefined) {
|
|
899
|
+
var v = normalizeLiveStreamThink(value.liveStreamThink, currentConfig.liveStreamThink)
|
|
900
|
+
if (liveStreamThinkStore !== null && liveStreamThinkStore.getSnapshot() !== v) {
|
|
901
|
+
liveStreamThinkStore.set(v)
|
|
902
|
+
currentConfig.liveStreamThink = v
|
|
903
|
+
}
|
|
904
|
+
}
|
|
775
905
|
} catch (e) {}
|
|
776
906
|
}
|
|
777
907
|
settingsScope.subscribe(adopt)
|
|
@@ -796,7 +926,8 @@ window.__ModuleLoader__.load({
|
|
|
796
926
|
}, TextWindowDock)
|
|
797
927
|
})
|
|
798
928
|
|
|
799
|
-
// Settings → General
|
|
929
|
+
// Settings → General rows (window size + live-stream think, both live
|
|
930
|
+
// and persisted).
|
|
800
931
|
ctx.slots.inject('settings.general.item', function () {
|
|
801
932
|
return ctx.slots.register({
|
|
802
933
|
name: 'settings.general.item',
|
|
@@ -804,7 +935,7 @@ window.__ModuleLoader__.load({
|
|
|
804
935
|
order: 60,
|
|
805
936
|
inject: function () {
|
|
806
937
|
return {
|
|
807
|
-
hooks: { lines: linesStore },
|
|
938
|
+
hooks: { lines: linesStore, liveStreamThink: liveStreamThinkStore },
|
|
808
939
|
setLines: function (value) {
|
|
809
940
|
var n = clampLines(value)
|
|
810
941
|
if (linesStore !== null) linesStore.set(n)
|
|
@@ -812,6 +943,14 @@ window.__ModuleLoader__.load({
|
|
|
812
943
|
settingsScope.set('lines', n).catch(function () {})
|
|
813
944
|
}
|
|
814
945
|
},
|
|
946
|
+
setLiveStreamThink: function (value) {
|
|
947
|
+
var v = normalizeLiveStreamThink(value, currentConfig.liveStreamThink)
|
|
948
|
+
if (liveStreamThinkStore !== null) liveStreamThinkStore.set(v)
|
|
949
|
+
currentConfig.liveStreamThink = v
|
|
950
|
+
if (settingsScope !== null) {
|
|
951
|
+
settingsScope.set('liveStreamThink', v).catch(function () {})
|
|
952
|
+
}
|
|
953
|
+
},
|
|
815
954
|
}
|
|
816
955
|
},
|
|
817
956
|
}, ResponseWindowSettingsRow)
|
package/lib/index.js
CHANGED
|
@@ -10,7 +10,12 @@
|
|
|
10
10
|
// collapsed start slides collapsed instead of always-expanded
|
|
11
11
|
// showReadOnly include read-only tools in the slide list
|
|
12
12
|
// minCollapseRows min tool-call count for auto-collapse (collapsed mode)
|
|
13
|
-
//
|
|
13
|
+
// liveStreamThink how a streaming think block is shown:
|
|
14
|
+
// 'off' — always folded into a one-line summary
|
|
15
|
+
// 'in' — auto-expand inside the slide, live scroll
|
|
16
|
+
// 'out' — stream outside the slide (native row), then
|
|
17
|
+
// fold into the slide once settled (default 'out')
|
|
18
|
+
|
|
14
19
|
// `settingsNamespace()` is just a validated string (kebab-case pattern); the
|
|
15
20
|
// namespace below already matches, so we pass the raw string and avoid an extra
|
|
16
21
|
// host dependency.
|
|
@@ -29,6 +34,7 @@ export const Config = z.object({
|
|
|
29
34
|
collapsed: z.boolean().default(false),
|
|
30
35
|
showReadOnly: z.boolean().default(true),
|
|
31
36
|
minCollapseRows: z.natural().min(1).max(50).default(3),
|
|
37
|
+
liveStreamThink: z.string().default('out'),
|
|
32
38
|
})
|
|
33
39
|
|
|
34
40
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-newbe-response-window",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "DeepSeek Harness (DSH) web plugin: per-turn response window. Each turn's tool calls collapse into ONE always-expanded bounded slide (configurable N-line window, inner scroll, nothing hidden), and long assistant markdown gets a bounded scroll window too — Grok-build style, keeps every intermediate response visible inside a finite window.",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "DeepSeek Harness (DSH) web plugin: per-turn response window. Each turn's tool calls collapse into ONE always-expanded bounded slide (configurable N-line window, inner scroll, nothing hidden), and long assistant markdown gets a bounded scroll window too — Grok-build style, keeps every intermediate response visible inside a finite window. Streaming think blocks can live-stream outside the slide and fold in when settled (or auto-expand inside), switchable via Settings.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|