dsh-edit-turn 0.5.24 → 0.6.1
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/lib/client.js +74 -16
- package/lib/index.js +15 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -457,7 +457,7 @@ window.__ModuleLoader__.load({
|
|
|
457
457
|
// read as a different, "active" icon next to the neutral grey ones. The
|
|
458
458
|
// variable resolves on the action bar (and everywhere above it); the
|
|
459
459
|
// fallback is the value it resolves to in the default theme.
|
|
460
|
-
'.dshet-action{width:28px;height:28px;padding:6px;display:inline-flex;align-items:center;justify-content:center;border:none;border-radius:28px;background:transparent;color:var(--dsw-alias-label-tertiary,#81858c);cursor:pointer;transition:background-color .12s,color .12s}',
|
|
460
|
+
'.dshet-action{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));padding:6px;display:inline-flex;align-items:center;justify-content:center;border:none;border-radius:28px;background:transparent;color:var(--dsw-alias-label-tertiary,#81858c);cursor:pointer;transition:background-color .12s,color .12s}',
|
|
461
461
|
// The fallback strip for a turn with no closing body.
|
|
462
462
|
//
|
|
463
463
|
// The gate is the host's OWN marker: TurnTailNodeView renders
|
|
@@ -473,7 +473,7 @@ window.__ModuleLoader__.load({
|
|
|
473
473
|
'.dshet-action:hover:not(:disabled){background:var(--dshet-chip);box-shadow:inset 0 0 0 1px var(--dshet-line);color:var(--dsw-alias-label-primary,var(--dshet-ink))}',
|
|
474
474
|
'.dshet-action:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill,var(--dshet-accent));outline-offset:2px}',
|
|
475
475
|
'.dshet-action:disabled{cursor:default;opacity:.4}',
|
|
476
|
-
'.dshet-action svg{width:
|
|
476
|
+
'.dshet-action svg{width:calc(16px + var(--dsh-content-font-delta,0px));height:calc(16px + var(--dsh-content-font-delta,0px))}',
|
|
477
477
|
// The reply pencil leads its strip. The host draws the copy button ahead of
|
|
478
478
|
// the slot this entry is rendered into, and the strip lays its children out
|
|
479
479
|
// in DOM order, so in DOM position the pencil can only ever come second -
|
|
@@ -547,7 +547,7 @@ window.__ModuleLoader__.load({
|
|
|
547
547
|
'.dshet-editor:focus-within{border-color:var(--dsw-alias-button-primary-fill,var(--dshet-accent))}',
|
|
548
548
|
'.dshet-editor[data-dshet-inplace="1"]{overflow:hidden;border:1.5px solid var(--dsw-alias-button-primary-fill,#4a7dff);border-radius:var(--dshet-radius-md);background:var(--dshet-panel);backdrop-filter:none;box-shadow:none}',
|
|
549
549
|
'.dshet-editor[data-dshet-inplace="1"] textarea{position:absolute;min-height:0;max-height:none;padding:6px 10px;border:0;border-radius:8px;background:transparent;overflow-y:auto}',
|
|
550
|
-
'.dshet-editor[data-dshet-inplace="1"] .dshet-footer{position:absolute;top:auto;left:0;right:0;bottom:0;height:46px;align-items:center;padding:6px 10px;box-sizing:border-box;background:
|
|
550
|
+
'.dshet-editor[data-dshet-inplace="1"] .dshet-footer{position:absolute;top:auto;left:0;right:0;bottom:0;height:calc(46px + var(--dsh-content-font-delta,0px));align-items:center;padding:6px 10px;box-sizing:border-box;background:transparent;border-radius:0 0 var(--dshet-radius-md) var(--dshet-radius-md);gap:6px}',
|
|
551
551
|
'.dshet-editor[data-dshet-inplace="1"] .dshet-btn{padding:2px 8px;font-size:12px;line-height:18px;border-radius:8px}',
|
|
552
552
|
'.dshet-editor[data-dshet-inplace="1"] .dshet-pick{position:static;top:auto;left:auto;padding:0;background:transparent;border-radius:0}',
|
|
553
553
|
'.dshet-editor[data-dshet-inplace="1"] .dshet-note,.dshet-editor[data-dshet-inplace="1"] .dshet-warn{position:absolute;left:8px;bottom:6px;max-width:60%;margin:0;background:var(--dshet-panel);border-radius:8px;padding:2px 6px}',
|
|
@@ -598,19 +598,19 @@ window.__ModuleLoader__.load({
|
|
|
598
598
|
'.dshet-btn:hover:not(:disabled){background:var(--dshet-hover)}',
|
|
599
599
|
'.dshet-btn:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill,var(--dshet-accent));outline-offset:2px}',
|
|
600
600
|
'.dshet-btn:disabled{cursor:default;opacity:.5}',
|
|
601
|
-
'.dshet-btn-save{width:28px;height:28px;padding:0;border:0;border-radius:50%;background:transparent;color:#81858c}',
|
|
601
|
+
'.dshet-btn-save{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));padding:0;border:0;border-radius:50%;background:transparent;color:#81858c}',
|
|
602
602
|
'.dshet-btn-save:hover:not(:disabled){background:var(--dshet-hover)}',
|
|
603
|
-
'.dshet-btn-save svg{width:
|
|
603
|
+
'.dshet-btn-save svg{width:calc(16px + var(--dsh-content-font-delta,0px));height:calc(16px + var(--dsh-content-font-delta,0px));display:block;stroke:currentColor;fill:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round}',
|
|
604
604
|
'.dshet-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}',
|
|
605
605
|
'.dshet-btn-primary{border-color:var(--dsw-alias-button-primary-fill,var(--dshet-accent));background:var(--dsw-alias-button-primary-fill,var(--dshet-accent));color:var(--dsw-alias-label-primary-foreground,var(--dshet-on-accent))}',
|
|
606
606
|
'.dshet-btn-primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-fill,var(--dshet-accent));filter:brightness(1.08)}',
|
|
607
|
-
'.dshet-btn.dshet-btn-save{width:28px;height:28px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--dshet-ink-soft);filter:none}',
|
|
607
|
+
'.dshet-btn.dshet-btn-save{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));padding:0;border:0;border-radius:50%;background:transparent;color:var(--dshet-ink-soft);filter:none}',
|
|
608
608
|
'.dshet-btn.dshet-btn-save:hover:not(:disabled){background:var(--dshet-hover);filter:none}',
|
|
609
609
|
'.dshet-btn.dshet-btn-save.dshet-btn-confirm{background:var(--dshet-hover);box-shadow:inset 0 0 0 1.5px var(--dshet-accent)}',
|
|
610
|
-
'.dshet-editor .dshet-footer .dshet-btn.dshet-btn-icon{width:28px;height:28px;padding:0;border:0;border-radius:50%;background:transparent;color:#81858c;filter:none}',
|
|
610
|
+
'.dshet-editor .dshet-footer .dshet-btn.dshet-btn-icon{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));padding:0;border:0;border-radius:50%;background:transparent;color:#81858c;filter:none}',
|
|
611
611
|
'.dshet-editor .dshet-footer .dshet-btn.dshet-btn-icon:hover:not(:disabled){background:var(--dshet-hover);color:var(--dshet-ink-soft);filter:none}',
|
|
612
|
-
'.dshet-btn-icon svg{width:
|
|
613
|
-
'.dshet-editor[data-dshet-inplace="1"] .dshet-btn.dshet-btn-save{width:28px;height:28px;padding:0;border:0;border-radius:50%;background:transparent;color:#81858c;filter:none}',
|
|
612
|
+
'.dshet-btn-icon svg{width:calc(16px + var(--dsh-content-font-delta,0px));height:calc(16px + var(--dsh-content-font-delta,0px));display:block;fill:none;stroke:currentColor;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round}',
|
|
613
|
+
'.dshet-editor[data-dshet-inplace="1"] .dshet-btn.dshet-btn-save{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));padding:0;border:0;border-radius:50%;background:transparent;color:#81858c;filter:none}',
|
|
614
614
|
'.dshet-editor[data-dshet-inplace="1"] .dshet-btn.dshet-btn-save:hover:not(:disabled){background:var(--dshet-hover);filter:none}',
|
|
615
615
|
'.dshet-pick{display:flex;align-items:center;gap:4px;padding:0;font-size:12px;color:var(--dshet-ink-dim)}',
|
|
616
616
|
'.dshet-config{display:flex;flex-direction:column;gap:10px}',
|
|
@@ -3845,6 +3845,8 @@ window.__ModuleLoader__.load({
|
|
|
3845
3845
|
})
|
|
3846
3846
|
box.addEventListener('dragover', (event) => {
|
|
3847
3847
|
event.preventDefault()
|
|
3848
|
+
// 不让事件传到输入框:否则宿主也会把这次拖拽当成它的附件。
|
|
3849
|
+
event.stopPropagation()
|
|
3848
3850
|
box.dataset.dshetDrop = '1'
|
|
3849
3851
|
})
|
|
3850
3852
|
box.addEventListener('dragleave', () => {
|
|
@@ -3852,9 +3854,17 @@ window.__ModuleLoader__.load({
|
|
|
3852
3854
|
})
|
|
3853
3855
|
box.addEventListener('drop', (event) => {
|
|
3854
3856
|
event.preventDefault()
|
|
3857
|
+
event.stopPropagation()
|
|
3855
3858
|
delete box.dataset.dshetDrop
|
|
3856
3859
|
const files = filesFromData(event.dataTransfer)
|
|
3857
3860
|
if (files.length > 0) controller.attachFiles(files)
|
|
3861
|
+
// 宿主的「文件或图片拖动到此处即可添加」遮罩是靠它自己的 document drop
|
|
3862
|
+
// 关闭的;我们把事件拦下来了,就得替它收尾,否则遮罩会一直挂着。
|
|
3863
|
+
try {
|
|
3864
|
+
window.dispatchEvent(new DragEvent('dragend', { bubbles: true }))
|
|
3865
|
+
} catch {
|
|
3866
|
+
/* 老环境没有 DragEvent 就算了 */
|
|
3867
|
+
}
|
|
3858
3868
|
})
|
|
3859
3869
|
}
|
|
3860
3870
|
|
|
@@ -4376,7 +4386,16 @@ window.__ModuleLoader__.load({
|
|
|
4376
4386
|
}
|
|
4377
4387
|
}
|
|
4378
4388
|
}
|
|
4379
|
-
if (block === undefined)
|
|
4389
|
+
if (block === undefined) {
|
|
4390
|
+
// `/state` 的回复只有身份,没有 blocks。补丁自带 original 时按文本匹配;
|
|
4391
|
+
// 老宿主没带 original 时也不必放弃——按「同一行 + 块类型」在 DOM 里定位。
|
|
4392
|
+
const key = `${patch.seq}:${patch.blockIndex}`
|
|
4393
|
+
const original = typeof patch.original === 'string' ? patch.original : ''
|
|
4394
|
+
const wanted = { key, seq: patch.seq, original, text: String(patch.text ?? '') }
|
|
4395
|
+
if (patch.kind === 'text') wantedText.set(key, wanted)
|
|
4396
|
+
else if (patch.kind === 'reasoning') wantedReasoning.set(key, { ...wanted, reply: null, block: null })
|
|
4397
|
+
continue
|
|
4398
|
+
}
|
|
4380
4399
|
if (patch.kind === 'text') {
|
|
4381
4400
|
wantedText.set(`${patch.seq}:${patch.blockIndex}`, {
|
|
4382
4401
|
key: `${patch.seq}:${patch.blockIndex}`,
|
|
@@ -4456,7 +4475,31 @@ window.__ModuleLoader__.load({
|
|
|
4456
4475
|
}
|
|
4457
4476
|
const original = matchText(wanted.original)
|
|
4458
4477
|
const previous = matchText(patchedTexts.get(key) ?? '')
|
|
4459
|
-
if (original === '' && previous === '')
|
|
4478
|
+
if (original === '' && previous === '') {
|
|
4479
|
+
// 没有旧文本可比:按「该补丁所在行 + 正文容器」直接换掉。
|
|
4480
|
+
const scope = owner !== undefined ? owner : root
|
|
4481
|
+
let target = null
|
|
4482
|
+
for (const el of containers) {
|
|
4483
|
+
if (el.getAttribute(PATCH_ATTR) !== null) continue
|
|
4484
|
+
try {
|
|
4485
|
+
if (el.closest('[data-chat-flow-key]') !== scope) continue
|
|
4486
|
+
if (el.closest('[data-variant="think"]') !== null) continue
|
|
4487
|
+
} catch {
|
|
4488
|
+
continue
|
|
4489
|
+
}
|
|
4490
|
+
if ((el.textContent || '').trim() === '') continue
|
|
4491
|
+
target = el
|
|
4492
|
+
break
|
|
4493
|
+
}
|
|
4494
|
+
if (target === null) continue
|
|
4495
|
+
patchOriginals.set(target, { html: target.innerHTML, text: target.textContent })
|
|
4496
|
+
target.textContent = displayText(wanted.text)
|
|
4497
|
+
target.setAttribute(PATCH_ATTR, key)
|
|
4498
|
+
target.style.whiteSpace = 'pre-wrap'
|
|
4499
|
+
patchedElements.set(key, target)
|
|
4500
|
+
patchedTexts.set(key, wanted.text)
|
|
4501
|
+
continue
|
|
4502
|
+
}
|
|
4460
4503
|
let best = null
|
|
4461
4504
|
let bestScore = Infinity
|
|
4462
4505
|
for (const el of containers) {
|
|
@@ -4709,25 +4752,39 @@ window.__ModuleLoader__.load({
|
|
|
4709
4752
|
if (typeof unsubscribe === 'function') unsubscribe()
|
|
4710
4753
|
}
|
|
4711
4754
|
}, [controller])
|
|
4712
|
-
|
|
4713
|
-
|
|
4755
|
+
// 先渲染、后确认:/state 还没回来时也把铅笔画出来(置灰),
|
|
4756
|
+
// 否则宿主和别的插件都在了,只有我们要等一次请求才出现。
|
|
4757
|
+
// 没有 messageId 就无从谈起:这种行不进入口。
|
|
4758
|
+
if (messageId === undefined || messageId === null || messageId === '') return null
|
|
4759
|
+
const loaded = view !== null
|
|
4760
|
+
const entry = loaded ? view.repliesByMessage.get(messageId) : undefined
|
|
4761
|
+
if (loaded && (entry === undefined || entry === null || view.hidden.has(entry.seq))) return null
|
|
4714
4762
|
const label = t === null ? '' : t('action.edit-reply')
|
|
4715
|
-
const open = (event) => {
|
|
4763
|
+
const open = async (event) => {
|
|
4716
4764
|
event.preventDefault()
|
|
4717
4765
|
event.stopPropagation()
|
|
4766
|
+
if (entry === undefined) return
|
|
4767
|
+
// `/state` 只发回复的身份(textLength / blockCount),正文和多块内容要按需
|
|
4768
|
+
// 取一次;不取就打开成空框 + 占位符,也没有正文/思维链页。
|
|
4769
|
+
const body = typeof controller.replyBody === 'function' ? await controller.replyBody(entry.messageId) : null
|
|
4770
|
+
const blocks = body !== null && Array.isArray(body.blocks) && body.blocks.length > 0
|
|
4771
|
+
? body.blocks
|
|
4772
|
+
: Array.isArray(entry.blocks) ? entry.blocks : []
|
|
4718
4773
|
controller.open({
|
|
4719
4774
|
anchor: event.currentTarget,
|
|
4720
4775
|
seq: entry.seq,
|
|
4721
4776
|
mode: 'reply',
|
|
4722
4777
|
turn: entry.turn,
|
|
4723
4778
|
messageId: entry.messageId,
|
|
4724
|
-
text:
|
|
4779
|
+
text: body !== null && typeof body.text === 'string'
|
|
4780
|
+
? body.text
|
|
4781
|
+
: typeof entry.text === 'string' ? entry.text : '',
|
|
4725
4782
|
attachments: typeof entry.attachments === 'number' ? entry.attachments : 0,
|
|
4726
4783
|
// On the in-place path the reply keeps its tool calls and other
|
|
4727
4784
|
// blocks, so the editor can offer each editable block (output text
|
|
4728
4785
|
// and reasoning). On the replay path there is no block picker: the
|
|
4729
4786
|
// whole reply is replaced.
|
|
4730
|
-
...(
|
|
4787
|
+
...(blocks.length > 0 ? { blocks, patch: true } : { blocks: [] }),
|
|
4731
4788
|
})
|
|
4732
4789
|
}
|
|
4733
4790
|
// The config object is always an object: this runtime's `jsx` reads
|
|
@@ -4738,6 +4795,7 @@ window.__ModuleLoader__.load({
|
|
|
4738
4795
|
className: 'dshet-action dshet-row-action dshet-reply-action',
|
|
4739
4796
|
'aria-label': label,
|
|
4740
4797
|
title: label,
|
|
4798
|
+
disabled: entry === undefined,
|
|
4741
4799
|
onClick: open,
|
|
4742
4800
|
children: jsx(PencilIcon, {}),
|
|
4743
4801
|
})
|
package/lib/index.js
CHANGED
|
@@ -78,7 +78,7 @@ export const PLUGIN_ID = 'dsh-edit-turn'
|
|
|
78
78
|
export const name = PLUGIN_ID
|
|
79
79
|
|
|
80
80
|
/** Keep in sync with package.json and lib/client.js. */
|
|
81
|
-
export const PLUGIN_VERSION = '0.
|
|
81
|
+
export const PLUGIN_VERSION = '0.6.1'
|
|
82
82
|
|
|
83
83
|
// A ring of the last few requests, answering "did the browser half even ask, and
|
|
84
84
|
// what did it get?" without needing its console. Read-only diagnostics: the
|
|
@@ -1821,7 +1821,20 @@ async function stateOf(ctx, sessionId, config) {
|
|
|
1821
1821
|
// The landed in-place patches, latest per block. The browser half uses them
|
|
1822
1822
|
// to draw the projected text in place of the recorded one; the model already
|
|
1823
1823
|
// sees them through the host projection.
|
|
1824
|
-
|
|
1824
|
+
// 每个补丁带上原文:`/state` 的回复只发身份(没有 blocks),浏览器端
|
|
1825
|
+
// 投影时需要一个用于匹配 DOM 的旧文本,否则保存成功却不会重绘。
|
|
1826
|
+
patches: (() => {
|
|
1827
|
+
const seqs = new Set(activePatches.keys())
|
|
1828
|
+
const messageBySeq = new Map()
|
|
1829
|
+
for (const event of events) {
|
|
1830
|
+
if (seqs.has(event.seq)) messageBySeq.set(event.seq, event.data && event.data.message !== undefined ? event.data.message : event.data)
|
|
1831
|
+
}
|
|
1832
|
+
return patchList(activePatches).map((patch) => {
|
|
1833
|
+
const message = messageBySeq.get(patch.seq)
|
|
1834
|
+
const block = message === undefined ? undefined : editableBlocks(message).find((entry) => entry.index === patch.blockIndex)
|
|
1835
|
+
return { ...patch, original: block === undefined ? '' : block.text }
|
|
1836
|
+
})
|
|
1837
|
+
})(),
|
|
1825
1838
|
mode: config.patch === true ? 'patch' : 'replay',
|
|
1826
1839
|
live: Boolean(live),
|
|
1827
1840
|
busy: isBusy(events),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-edit-turn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Edit any message in DeepSeek Harness in place: a prompt is replaced where it stands; a model reply block (answer text or reasoning) is patched through a plugin record and a message projection, so the turn stays where it is, later turns are untouched and the log grows by one small record. Falls back to the replay splice on hosts without the projection API. The append-only session log is never rewritten.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|