dsh-edit-turn 0.6.0 → 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 +46 -5
- package/lib/index.js +15 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -4386,7 +4386,16 @@ window.__ModuleLoader__.load({
|
|
|
4386
4386
|
}
|
|
4387
4387
|
}
|
|
4388
4388
|
}
|
|
4389
|
-
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
|
+
}
|
|
4390
4399
|
if (patch.kind === 'text') {
|
|
4391
4400
|
wantedText.set(`${patch.seq}:${patch.blockIndex}`, {
|
|
4392
4401
|
key: `${patch.seq}:${patch.blockIndex}`,
|
|
@@ -4466,7 +4475,31 @@ window.__ModuleLoader__.load({
|
|
|
4466
4475
|
}
|
|
4467
4476
|
const original = matchText(wanted.original)
|
|
4468
4477
|
const previous = matchText(patchedTexts.get(key) ?? '')
|
|
4469
|
-
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
|
+
}
|
|
4470
4503
|
let best = null
|
|
4471
4504
|
let bestScore = Infinity
|
|
4472
4505
|
for (const el of containers) {
|
|
@@ -4727,23 +4760,31 @@ window.__ModuleLoader__.load({
|
|
|
4727
4760
|
const entry = loaded ? view.repliesByMessage.get(messageId) : undefined
|
|
4728
4761
|
if (loaded && (entry === undefined || entry === null || view.hidden.has(entry.seq))) return null
|
|
4729
4762
|
const label = t === null ? '' : t('action.edit-reply')
|
|
4730
|
-
const open = (event) => {
|
|
4763
|
+
const open = async (event) => {
|
|
4731
4764
|
event.preventDefault()
|
|
4732
4765
|
event.stopPropagation()
|
|
4733
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 : []
|
|
4734
4773
|
controller.open({
|
|
4735
4774
|
anchor: event.currentTarget,
|
|
4736
4775
|
seq: entry.seq,
|
|
4737
4776
|
mode: 'reply',
|
|
4738
4777
|
turn: entry.turn,
|
|
4739
4778
|
messageId: entry.messageId,
|
|
4740
|
-
text:
|
|
4779
|
+
text: body !== null && typeof body.text === 'string'
|
|
4780
|
+
? body.text
|
|
4781
|
+
: typeof entry.text === 'string' ? entry.text : '',
|
|
4741
4782
|
attachments: typeof entry.attachments === 'number' ? entry.attachments : 0,
|
|
4742
4783
|
// On the in-place path the reply keeps its tool calls and other
|
|
4743
4784
|
// blocks, so the editor can offer each editable block (output text
|
|
4744
4785
|
// and reasoning). On the replay path there is no block picker: the
|
|
4745
4786
|
// whole reply is replaced.
|
|
4746
|
-
...(
|
|
4787
|
+
...(blocks.length > 0 ? { blocks, patch: true } : { blocks: [] }),
|
|
4747
4788
|
})
|
|
4748
4789
|
}
|
|
4749
4790
|
// The config object is always an object: this runtime's `jsx` reads
|
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.6.
|
|
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.6.
|
|
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",
|