dsh-as-aistudio 0.2.8 → 0.2.11
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/package.json +4 -4
- package/src/client.js +124 -22
- package/src/components.js +1 -1
- package/src/studio.js +1 -1
- package/src/vendor/dsh-delete-turn.js +96 -6
- package/src/vendor/dsh-edit-turn.js +15 -11
- package/src/vendor/dsh-rerun-turn.js +15 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-as-aistudio",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Google-AI-Studio-style turn controls for DeepSeek Harness, in one install: edit a prompt and re-run it, re-run a reply in place, delete a turn from the model context, and render sent prompts as Markdown. Self-contained: the plugin list shows this one row; the four components are dependencies mounted by it. Each still installs alone and works alone.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"verify:manifest": "node tools/verify-manifest.mjs"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"dsh-delete-turn": "^0.1.
|
|
56
|
-
"dsh-edit-turn": "^0.2.
|
|
55
|
+
"dsh-delete-turn": "^0.1.9",
|
|
56
|
+
"dsh-edit-turn": "^0.2.20",
|
|
57
57
|
"dsh-markdown-bubble": "^0.1.4",
|
|
58
|
-
"dsh-rerun-turn": "^0.1.
|
|
58
|
+
"dsh-rerun-turn": "^0.1.26"
|
|
59
59
|
},
|
|
60
60
|
"dsh": {
|
|
61
61
|
"bundle": {
|
package/src/client.js
CHANGED
|
@@ -40,7 +40,7 @@ window.__ModuleLoader__.load({
|
|
|
40
40
|
const RERUN_PREFIX = '/dsh-rerun-turn'
|
|
41
41
|
|
|
42
42
|
/** Keep in sync with package.json and lib/index.js. */
|
|
43
|
-
const PLUGIN_VERSION = '0.2.
|
|
43
|
+
const PLUGIN_VERSION = '0.2.20'
|
|
44
44
|
|
|
45
45
|
// --- copy -----------------------------------------------------------------
|
|
46
46
|
|
|
@@ -83,7 +83,6 @@ window.__ModuleLoader__.load({
|
|
|
83
83
|
'error.prompt': '已回退,但改后的内容没能写进上下文。请重试。',
|
|
84
84
|
'error.reply': '已回退,但替换没有落地。请重试。',
|
|
85
85
|
'error.empty': '改写后的内容不能为空。',
|
|
86
|
-
'error.unchanged': '内容没有变化,未做任何修改。',
|
|
87
86
|
'error.rerun-nothing': '已保存,但这一轮没有可重跑的作答。',
|
|
88
87
|
'error.rerun-unavailable': '已保存,但重跑没有开始,请稍后重试。',
|
|
89
88
|
'error.not-rerunnable': '这一轮没有可重跑的作答。',
|
|
@@ -131,7 +130,6 @@ window.__ModuleLoader__.load({
|
|
|
131
130
|
'error.prompt': 'Rolled back, but the re-run did not start. Send the revised text manually.',
|
|
132
131
|
'error.reply': 'Rolled back, but the replacement did not land. Try again.',
|
|
133
132
|
'error.empty': 'The revised message cannot be empty.',
|
|
134
|
-
'error.unchanged': 'Nothing changed, so no edit was made.',
|
|
135
133
|
'error.rerun-nothing': 'Saved, but this turn has no reply that can be re-run.',
|
|
136
134
|
'error.rerun-unavailable': 'Saved, but the re-run did not start; try again.',
|
|
137
135
|
'error.not-rerunnable': 'This turn has no reply that can be re-run.',
|
|
@@ -746,10 +744,18 @@ window.__ModuleLoader__.load({
|
|
|
746
744
|
failure: null,
|
|
747
745
|
})
|
|
748
746
|
// `unchanged` means the host wrote NOTHING: the draft is (verbatim)
|
|
749
|
-
// the text the message already carries.
|
|
750
|
-
//
|
|
751
|
-
//
|
|
752
|
-
//
|
|
747
|
+
// the text the message already carries. The flag is read here for one
|
|
748
|
+
// reason, and it is not the user's: a no-op is not a failure. The
|
|
749
|
+
// `applied === false` wording below would claim the rollback landed
|
|
750
|
+
// and the replacement did not, which is a lie about an edit that was
|
|
751
|
+
// never attempted.
|
|
752
|
+
//
|
|
753
|
+
// Nothing is announced for it, deliberately (0.2.20 removed the
|
|
754
|
+
// notice this used to draw): the edit was simply not an edit, so the
|
|
755
|
+
// editor closes like any save and the screen is exactly as it was -
|
|
756
|
+
// no row was shadowed, so none is hidden, and the message keeps the
|
|
757
|
+
// pencil it had. (The banner also landed on top of the composer's
|
|
758
|
+
// placeholder text, which is not a place to report anything from.)
|
|
753
759
|
//
|
|
754
760
|
// The re-run intent survives it. "Re-run" saves first only because the
|
|
755
761
|
// sibling regenerates the turn from the prompt the surface shows; a
|
|
@@ -757,9 +763,7 @@ window.__ModuleLoader__.load({
|
|
|
757
763
|
// wording moved, so the chain below is NOT gated on a write having
|
|
758
764
|
// happened. (The decision itself is the host's - the text is parsed
|
|
759
765
|
// there, once - and this half only reads the flag it sends back.)
|
|
760
|
-
if (data.unchanged
|
|
761
|
-
if (!rerunIntent) this.notify('unchanged')
|
|
762
|
-
} else {
|
|
766
|
+
if (data.unchanged !== true) {
|
|
763
767
|
// The rollback has landed, so the editor is gone with its row: a
|
|
764
768
|
// failed second half has to be reported somewhere that survives that.
|
|
765
769
|
if (data.kind === 'reply' && data.applied === false) this.notify('reply')
|
|
@@ -2643,7 +2647,7 @@ window.__ModuleLoader__.load({
|
|
|
2643
2647
|
const ROUTE_PREFIX = '/dsh-rerun-turn'
|
|
2644
2648
|
|
|
2645
2649
|
/** Keep in sync with package.json and lib/index.js. */
|
|
2646
|
-
const PLUGIN_VERSION = '0.1.
|
|
2650
|
+
const PLUGIN_VERSION = '0.1.27'
|
|
2647
2651
|
|
|
2648
2652
|
// --- copy -----------------------------------------------------------------
|
|
2649
2653
|
|
|
@@ -3175,11 +3179,19 @@ window.__ModuleLoader__.load({
|
|
|
3175
3179
|
resolved.set(key, decision)
|
|
3176
3180
|
return decision
|
|
3177
3181
|
}
|
|
3178
|
-
// The turn a row belongs to
|
|
3179
|
-
// (`turn
|
|
3180
|
-
|
|
3182
|
+
// The turn a row belongs to. The platform publishes it on the flow item
|
|
3183
|
+
// itself (`data-chat-turn`, the same anchor the prompt-row buttons read),
|
|
3184
|
+
// and a key sometimes spells it out as well
|
|
3185
|
+
// (`turn-process17`, `turn-tail17`, `assistant-step17:1`). Reading the
|
|
3186
|
+
// key alone is what left a bookkeeping turn's empty "completed" strip on
|
|
3187
|
+
// screen once DSH started keying those rows by node kind
|
|
3188
|
+
// (`turn-tail`, `["turn-tail","response"]`) with the turn only in the
|
|
3189
|
+
// data attribute.
|
|
3190
|
+
const keyTurnOf = (key, row) => {
|
|
3181
3191
|
const match = /(?:turn-process|turn-tail)(\d+)|assistant-step(\d+):/.exec(key)
|
|
3182
|
-
|
|
3192
|
+
if (match !== null) return Number(match[1] ?? match[2])
|
|
3193
|
+
const fromRow = row && row.dataset ? Number(row.dataset.chatTurn) : Number.NaN
|
|
3194
|
+
return Number.isInteger(fromRow) ? fromRow : null
|
|
3183
3195
|
}
|
|
3184
3196
|
const nodeOf = (key) => {
|
|
3185
3197
|
const direct = snapshot.nodes.get(key)
|
|
@@ -3209,7 +3221,7 @@ window.__ModuleLoader__.load({
|
|
|
3209
3221
|
key,
|
|
3210
3222
|
kind: node ? node.kind : null,
|
|
3211
3223
|
seqs: node ? seqsFor(node) : [],
|
|
3212
|
-
keyTurn: keyTurnOf(key),
|
|
3224
|
+
keyTurn: keyTurnOf(key, row),
|
|
3213
3225
|
inTarget: false,
|
|
3214
3226
|
inTail: false,
|
|
3215
3227
|
fresh: false,
|
|
@@ -3649,12 +3661,15 @@ window.__ModuleLoader__.load({
|
|
|
3649
3661
|
|
|
3650
3662
|
const zh = {
|
|
3651
3663
|
'action.tooltip.message': '删除这条消息',
|
|
3664
|
+
'action.tooltip.splice': '删除这一轮并接上后面',
|
|
3652
3665
|
'action.tooltip.step': '删除这一步',
|
|
3653
3666
|
'action.tooltip.reply': '删除这条回复',
|
|
3654
3667
|
'dialog.title.message': '删除这条消息?',
|
|
3668
|
+
'dialog.title.splice': '删除这一轮并接上后面?',
|
|
3655
3669
|
'dialog.title.step': '删除这一步?',
|
|
3656
3670
|
'dialog.title.reply': '删除这条回复?',
|
|
3657
3671
|
'dialog.desc.message': '这条消息将从模型上下文中移除,并从当前转录中隐藏。原始会话日志保持不变。',
|
|
3672
|
+
'dialog.desc.splice': '这一轮(含你的提问)会从模型上下文中移除;它之后的每一轮随后以新的连续轮号原样重放回来,所以对话视图里这一轮消失、后面的内容直接接上(轮号本身仍在日志里单调递增)。',
|
|
3658
3673
|
'dialog.desc.step': '这一步的回复与它请求的工具结果将一并从模型上下文中移除,同一回合的其它步骤保留。',
|
|
3659
3674
|
'dialog.desc.reply': '这条回复连同它的思考、工具调用与注入上下文将从模型上下文中移除,你的提问会保留。',
|
|
3660
3675
|
'dialog.note': '删除只影响模型后续看到的内容,不会改写历史日志。',
|
|
@@ -3677,12 +3692,15 @@ window.__ModuleLoader__.load({
|
|
|
3677
3692
|
|
|
3678
3693
|
const en = {
|
|
3679
3694
|
'action.tooltip.message': 'Delete this message',
|
|
3695
|
+
'action.tooltip.splice': 'Delete this turn and close the gap',
|
|
3680
3696
|
'action.tooltip.step': 'Delete this step',
|
|
3681
3697
|
'action.tooltip.reply': 'Delete this reply',
|
|
3682
3698
|
'dialog.title.message': 'Delete this message?',
|
|
3699
|
+
'dialog.title.splice': 'Delete this turn and close the gap?',
|
|
3683
3700
|
'dialog.title.step': 'Delete this step?',
|
|
3684
3701
|
'dialog.title.reply': 'Delete this reply?',
|
|
3685
3702
|
'dialog.desc.message': 'This message leaves the model context and is hidden from the current transcript. The original session log stays untouched.',
|
|
3703
|
+
'dialog.desc.splice': 'This turn (your prompt included) leaves the model context, and every later turn is replayed back unchanged under fresh consecutive turn numbers, so the transcript closes the gap where this turn stood (the log keeps its monotonic numbering).',
|
|
3686
3704
|
'dialog.desc.step': 'This step and the tool results it requested leave the model context together; other steps in the same turn stay.',
|
|
3687
3705
|
'dialog.desc.reply': 'This reply leaves the model context together with its reasoning, tool calls and injected context; your prompt stays.',
|
|
3688
3706
|
'dialog.note': 'Deletion only changes what the model sees next; the append-only log is never rewritten.',
|
|
@@ -3720,6 +3738,16 @@ window.__ModuleLoader__.load({
|
|
|
3720
3738
|
'[data-variant="think"]:hover .dshdt-think-action,.dshdt-think-action:focus-within{opacity:1}',
|
|
3721
3739
|
'.dshdt-collapsing{overflow:hidden;transition:height .2s ease,opacity .14s ease,margin .2s ease,padding .2s ease}',
|
|
3722
3740
|
'[data-dshdt-hidden="1"]{display:none!important}',
|
|
3741
|
+
// The second row action: delete the whole turn and splice the later turns
|
|
3742
|
+
// back. It is its own class so the trash action's selector, its counts and
|
|
3743
|
+
// the interop contract stay exactly as they were; the row attribute turns
|
|
3744
|
+
// it on only where the host can plan the operation.
|
|
3745
|
+
'.dshdt-splice{width:28px;height:28px;padding:6px;display:none;align-items:center;justify-content:center;border:none;border-radius:28px;background:transparent;color:var(--dsw-alias-label-tertiary,#8a8f98);cursor:pointer;transition:background-color .12s,color .12s}',
|
|
3746
|
+
'[data-dshdt-splice="1"] .dshdt-splice{display:inline-flex}',
|
|
3747
|
+
'.dshdt-splice:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-button-primary-fill,#4d6bfe)}',
|
|
3748
|
+
'.dshdt-splice:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill,#4d6bfe);outline-offset:2px}',
|
|
3749
|
+
'.dshdt-splice:disabled{cursor:default;opacity:.4}',
|
|
3750
|
+
'.dshdt-splice svg{width:15px;height:15px}',
|
|
3723
3751
|
'[data-dshdt-no-target="1"] .dshdt-action{display:none!important}',
|
|
3724
3752
|
'.dshdt-dialog-text{margin:0;color:var(--dsw-alias-label-primary,inherit);font-size:14px;line-height:22px}',
|
|
3725
3753
|
'.dshdt-dialog-note{margin:10px 0 0;color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:13px;line-height:20px}',
|
|
@@ -3755,6 +3783,23 @@ window.__ModuleLoader__.load({
|
|
|
3755
3783
|
).join('') +
|
|
3756
3784
|
'</svg>'
|
|
3757
3785
|
|
|
3786
|
+
// Collapse glyph: a centre rail with one arrow above and one below, both
|
|
3787
|
+
// pointing at it - "the neighbours move up into this one's place".
|
|
3788
|
+
const SPLICE_ICON_PATHS = [
|
|
3789
|
+
'M3.2 8h9.6',
|
|
3790
|
+
'M8 2.4v4',
|
|
3791
|
+
'M6.3 4.7L8 6.4l1.7-1.7',
|
|
3792
|
+
'M8 13.6v-4',
|
|
3793
|
+
'M6.3 11.3L8 9.6l1.7 1.7',
|
|
3794
|
+
]
|
|
3795
|
+
const SPLICE_ICON_MARKUP =
|
|
3796
|
+
'<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">' +
|
|
3797
|
+
SPLICE_ICON_PATHS.map(
|
|
3798
|
+
(d) =>
|
|
3799
|
+
'<path d="' + d + '" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
3800
|
+
).join('') +
|
|
3801
|
+
'</svg>'
|
|
3802
|
+
|
|
3758
3803
|
function TrashIcon() {
|
|
3759
3804
|
return jsx('svg', {
|
|
3760
3805
|
width: 16,
|
|
@@ -3781,6 +3826,9 @@ window.__ModuleLoader__.load({
|
|
|
3781
3826
|
// replacement seq -> the seq it was recorded as shadowing. See hiddenViaOf.
|
|
3782
3827
|
hiddenVia: new Map(),
|
|
3783
3828
|
surface: new Set(),
|
|
3829
|
+
// The seqs the host would accept a turn-delete for (advertised, not
|
|
3830
|
+
// guessed): the entry appears only on those rows.
|
|
3831
|
+
spliceSeqs: new Set(),
|
|
3784
3832
|
replyTurns: new Set(),
|
|
3785
3833
|
edits: new Map(),
|
|
3786
3834
|
markerTurns: new Set(),
|
|
@@ -3847,6 +3895,8 @@ window.__ModuleLoader__.load({
|
|
|
3847
3895
|
const hiddenVia = hiddenViaOf(data.hidden)
|
|
3848
3896
|
const surface = new Set()
|
|
3849
3897
|
for (const seq of Array.isArray(data.surface) ? data.surface : []) surface.add(seq)
|
|
3898
|
+
const spliceSeqs = new Set()
|
|
3899
|
+
for (const seq of Array.isArray(data.spliceSeqs) ? data.spliceSeqs : []) spliceSeqs.add(seq)
|
|
3850
3900
|
const replyTurns = new Set()
|
|
3851
3901
|
for (const turn of Array.isArray(data.replyTurns) ? data.replyTurns : []) replyTurns.add(turn)
|
|
3852
3902
|
const edits = new Map()
|
|
@@ -3858,7 +3908,7 @@ window.__ModuleLoader__.load({
|
|
|
3858
3908
|
if (typeof turn === 'number') markerTurns.add(turn)
|
|
3859
3909
|
}
|
|
3860
3910
|
const surfaceThrough = typeof data.lastSeq === 'number' ? data.lastSeq : -1
|
|
3861
|
-
this.publish({ hidden, hiddenVia, surface, replyTurns, edits, markerTurns, surfaceReady: true, surfaceThrough, loaded: true, loadError: false })
|
|
3911
|
+
this.publish({ hidden, hiddenVia, surface, spliceSeqs, replyTurns, edits, markerTurns, surfaceReady: true, surfaceThrough, loaded: true, loadError: false })
|
|
3862
3912
|
})
|
|
3863
3913
|
.catch(() => {
|
|
3864
3914
|
this.publish({ loadError: true })
|
|
@@ -3984,6 +4034,17 @@ window.__ModuleLoader__.load({
|
|
|
3984
4034
|
}
|
|
3985
4035
|
}
|
|
3986
4036
|
|
|
4037
|
+
// The second entry: delete the WHOLE turn the row belongs to - the prompt
|
|
4038
|
+
// included - and splice every later turn back under fresh turn numbers. Only
|
|
4039
|
+
// a human prompt anchors it; an injected context row or a reply row is not
|
|
4040
|
+
// the turn's own question.
|
|
4041
|
+
function spliceTargetFor(node) {
|
|
4042
|
+
if (node.kind !== 'user') return null
|
|
4043
|
+
const data = node.data || {}
|
|
4044
|
+
const seq = typeof data.seq === 'number' ? data.seq : node.anchorSeq
|
|
4045
|
+
return typeof seq === 'number' ? { mode: 'splice', seq, label: 'splice' } : null
|
|
4046
|
+
}
|
|
4047
|
+
|
|
3987
4048
|
function seqsFor(node) {
|
|
3988
4049
|
const data = node.data || {}
|
|
3989
4050
|
const out = []
|
|
@@ -4106,6 +4167,7 @@ window.__ModuleLoader__.load({
|
|
|
4106
4167
|
// does not, and a re-apply (HMR, plugin toggle, bundle-group reload) starts
|
|
4107
4168
|
// with fresh module state over the very same page.
|
|
4108
4169
|
const HOST_MARK = 'data-dshdt-action-host'
|
|
4170
|
+
const SPLICE_CLASS = 'dshdt-splice'
|
|
4109
4171
|
const THINK_MARK = 'data-dshdt-think-action'
|
|
4110
4172
|
const HOST_CLASS = 'dshdt-action-host'
|
|
4111
4173
|
const THINK_CLASS = 'dshdt-think-action'
|
|
@@ -4148,7 +4210,23 @@ window.__ModuleLoader__.load({
|
|
|
4148
4210
|
button.innerHTML = ICON_MARKUP
|
|
4149
4211
|
host.appendChild(button)
|
|
4150
4212
|
}
|
|
4151
|
-
|
|
4213
|
+
// The row host carries this plugin's second action as well: delete the
|
|
4214
|
+
// whole turn and splice the later turns back. It rides in the SAME host -
|
|
4215
|
+
// the trash action's namespace, its selector and its one-host-per-row
|
|
4216
|
+
// contract are untouched - under its own class, and the reasoning host
|
|
4217
|
+
// never gets one (a reasoning card owns no turn).
|
|
4218
|
+
let splice = null
|
|
4219
|
+
if (kind !== 'think') {
|
|
4220
|
+
splice = host.querySelector('.' + SPLICE_CLASS)
|
|
4221
|
+
if (splice === null) {
|
|
4222
|
+
splice = document.createElement('button')
|
|
4223
|
+
splice.type = 'button'
|
|
4224
|
+
splice.className = SPLICE_CLASS + ' dshdt-row-action'
|
|
4225
|
+
splice.innerHTML = SPLICE_ICON_MARKUP
|
|
4226
|
+
host.appendChild(splice)
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
return { host, button, splice }
|
|
4152
4230
|
}
|
|
4153
4231
|
|
|
4154
4232
|
// Drop every node this plugin injected. Matched through this plugin's own
|
|
@@ -4237,7 +4315,7 @@ window.__ModuleLoader__.load({
|
|
|
4237
4315
|
rowActions.delete(row)
|
|
4238
4316
|
}
|
|
4239
4317
|
|
|
4240
|
-
function injectRowAction(row, node, target, controller, t) {
|
|
4318
|
+
function injectRowAction(row, node, target, spliceTarget, controller, t) {
|
|
4241
4319
|
const label = t(`action.tooltip.${target.label}`)
|
|
4242
4320
|
let entry = rowActions.get(row)
|
|
4243
4321
|
if (!entry) {
|
|
@@ -4246,7 +4324,7 @@ window.__ModuleLoader__.load({
|
|
|
4246
4324
|
entry = adoptHost(findOwnedHost(row, false), 'row')
|
|
4247
4325
|
rowActions.set(row, entry)
|
|
4248
4326
|
}
|
|
4249
|
-
const { host, button } = entry
|
|
4327
|
+
const { host, button, splice } = entry
|
|
4250
4328
|
if (button.getAttribute('aria-label') !== label) {
|
|
4251
4329
|
button.setAttribute('aria-label', label)
|
|
4252
4330
|
button.setAttribute('title', label)
|
|
@@ -4256,6 +4334,24 @@ window.__ModuleLoader__.load({
|
|
|
4256
4334
|
event.stopPropagation()
|
|
4257
4335
|
controller.open(target)
|
|
4258
4336
|
}
|
|
4337
|
+
// The second action is wired only where a splice target exists (and stays
|
|
4338
|
+
// hidden by the row attribute otherwise), so a row can never offer an
|
|
4339
|
+
// operation the host would refuse for the wrong reason.
|
|
4340
|
+
if (splice !== null && splice !== undefined) {
|
|
4341
|
+
const spliceLabel = t('action.tooltip.splice')
|
|
4342
|
+
if (splice.getAttribute('aria-label') !== spliceLabel) {
|
|
4343
|
+
splice.setAttribute('aria-label', spliceLabel)
|
|
4344
|
+
splice.setAttribute('title', spliceLabel)
|
|
4345
|
+
}
|
|
4346
|
+
splice.onclick =
|
|
4347
|
+
spliceTarget === null || spliceTarget === undefined
|
|
4348
|
+
? null
|
|
4349
|
+
: (event) => {
|
|
4350
|
+
event.preventDefault()
|
|
4351
|
+
event.stopPropagation()
|
|
4352
|
+
controller.open(spliceTarget)
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4259
4355
|
const anchor = node.kind === 'user' || node.kind === 'steering' ? row.querySelector('[class*="_actions"]') : null
|
|
4260
4356
|
if (anchor) {
|
|
4261
4357
|
host.classList.remove('dshdt-floating')
|
|
@@ -4365,8 +4461,14 @@ window.__ModuleLoader__.load({
|
|
|
4365
4461
|
const target = hidden ? null : liveTarget(targetFor(node), view.edits)
|
|
4366
4462
|
const covered = target !== null || slotCoversRow(node)
|
|
4367
4463
|
const deletable = !hidden && covered && rowDeletable(node, seqs, view)
|
|
4368
|
-
|
|
4464
|
+
// The turn-delete entry rides on the same gates as the delete entry: a
|
|
4465
|
+
// row whose content already left the context offers neither.
|
|
4466
|
+
const spliceTarget =
|
|
4467
|
+
deletable && spliceTargetFor(node) !== null && view.spliceSeqs.has(target.seq) ? liveTarget(spliceTargetFor(node), view.edits) : null
|
|
4468
|
+
if (deletable && target !== null) injectRowAction(row, node, target, spliceTarget, controller, t)
|
|
4369
4469
|
else removeRowAction(row)
|
|
4470
|
+
if (deletable && target !== null && spliceTarget !== null) row.dataset.dshdtSplice = '1'
|
|
4471
|
+
else delete row.dataset.dshdtSplice
|
|
4370
4472
|
if (!hidden && covered && !rowDeletable(node, seqs, view)) row.dataset.dshdtNoTarget = '1'
|
|
4371
4473
|
else delete row.dataset.dshdtNoTarget
|
|
4372
4474
|
}
|
|
@@ -5465,7 +5567,7 @@ window.__ModuleLoader__.load({
|
|
|
5465
5567
|
const NS = 'dsh-as-aistudio'
|
|
5466
5568
|
|
|
5467
5569
|
/** Keep in sync with package.json and src/components.js. */
|
|
5468
|
-
const PLUGIN_VERSION = '0.2.
|
|
5570
|
+
const PLUGIN_VERSION = '0.2.11'
|
|
5469
5571
|
|
|
5470
5572
|
/** Presence marker the live verifier reads. */
|
|
5471
5573
|
const DEBUG_KEY = '__DSH_AS_AISTUDIO__'
|
package/src/components.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
export const STUDIO_ID = 'dsh-as-aistudio'
|
|
27
27
|
|
|
28
28
|
/** Keep in sync with package.json and src/client.js. */
|
|
29
|
-
export const STUDIO_VERSION = '0.2.
|
|
29
|
+
export const STUDIO_VERSION = '0.2.11'
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* The AI Studio action strip this studio composes, in the order the user meets
|
package/src/studio.js
CHANGED
|
@@ -48,7 +48,7 @@ window.__ModuleLoader__.load({
|
|
|
48
48
|
const NS = 'dsh-as-aistudio'
|
|
49
49
|
|
|
50
50
|
/** Keep in sync with package.json and src/components.js. */
|
|
51
|
-
const PLUGIN_VERSION = '0.2.
|
|
51
|
+
const PLUGIN_VERSION = '0.2.11'
|
|
52
52
|
|
|
53
53
|
/** Presence marker the live verifier reads. */
|
|
54
54
|
const DEBUG_KEY = '__DSH_AS_AISTUDIO__'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-delete-turn browser half, VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-delete-turn/src/client.js (v0.1.
|
|
3
|
+
// Source: ../dsh-delete-turn/src/client.js (v0.1.9, sha256 ea27911ab31c1647)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/vendor-sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// The component registers its factory with the host module loader; inside this
|
|
@@ -25,12 +25,15 @@ export default (require) => {
|
|
|
25
25
|
|
|
26
26
|
const zh = {
|
|
27
27
|
'action.tooltip.message': '删除这条消息',
|
|
28
|
+
'action.tooltip.splice': '删除这一轮并接上后面',
|
|
28
29
|
'action.tooltip.step': '删除这一步',
|
|
29
30
|
'action.tooltip.reply': '删除这条回复',
|
|
30
31
|
'dialog.title.message': '删除这条消息?',
|
|
32
|
+
'dialog.title.splice': '删除这一轮并接上后面?',
|
|
31
33
|
'dialog.title.step': '删除这一步?',
|
|
32
34
|
'dialog.title.reply': '删除这条回复?',
|
|
33
35
|
'dialog.desc.message': '这条消息将从模型上下文中移除,并从当前转录中隐藏。原始会话日志保持不变。',
|
|
36
|
+
'dialog.desc.splice': '这一轮(含你的提问)会从模型上下文中移除;它之后的每一轮随后以新的连续轮号原样重放回来,所以对话视图里这一轮消失、后面的内容直接接上(轮号本身仍在日志里单调递增)。',
|
|
34
37
|
'dialog.desc.step': '这一步的回复与它请求的工具结果将一并从模型上下文中移除,同一回合的其它步骤保留。',
|
|
35
38
|
'dialog.desc.reply': '这条回复连同它的思考、工具调用与注入上下文将从模型上下文中移除,你的提问会保留。',
|
|
36
39
|
'dialog.note': '删除只影响模型后续看到的内容,不会改写历史日志。',
|
|
@@ -53,12 +56,15 @@ export default (require) => {
|
|
|
53
56
|
|
|
54
57
|
const en = {
|
|
55
58
|
'action.tooltip.message': 'Delete this message',
|
|
59
|
+
'action.tooltip.splice': 'Delete this turn and close the gap',
|
|
56
60
|
'action.tooltip.step': 'Delete this step',
|
|
57
61
|
'action.tooltip.reply': 'Delete this reply',
|
|
58
62
|
'dialog.title.message': 'Delete this message?',
|
|
63
|
+
'dialog.title.splice': 'Delete this turn and close the gap?',
|
|
59
64
|
'dialog.title.step': 'Delete this step?',
|
|
60
65
|
'dialog.title.reply': 'Delete this reply?',
|
|
61
66
|
'dialog.desc.message': 'This message leaves the model context and is hidden from the current transcript. The original session log stays untouched.',
|
|
67
|
+
'dialog.desc.splice': 'This turn (your prompt included) leaves the model context, and every later turn is replayed back unchanged under fresh consecutive turn numbers, so the transcript closes the gap where this turn stood (the log keeps its monotonic numbering).',
|
|
62
68
|
'dialog.desc.step': 'This step and the tool results it requested leave the model context together; other steps in the same turn stay.',
|
|
63
69
|
'dialog.desc.reply': 'This reply leaves the model context together with its reasoning, tool calls and injected context; your prompt stays.',
|
|
64
70
|
'dialog.note': 'Deletion only changes what the model sees next; the append-only log is never rewritten.',
|
|
@@ -96,6 +102,16 @@ export default (require) => {
|
|
|
96
102
|
'[data-variant="think"]:hover .dshdt-think-action,.dshdt-think-action:focus-within{opacity:1}',
|
|
97
103
|
'.dshdt-collapsing{overflow:hidden;transition:height .2s ease,opacity .14s ease,margin .2s ease,padding .2s ease}',
|
|
98
104
|
'[data-dshdt-hidden="1"]{display:none!important}',
|
|
105
|
+
// The second row action: delete the whole turn and splice the later turns
|
|
106
|
+
// back. It is its own class so the trash action's selector, its counts and
|
|
107
|
+
// the interop contract stay exactly as they were; the row attribute turns
|
|
108
|
+
// it on only where the host can plan the operation.
|
|
109
|
+
'.dshdt-splice{width:28px;height:28px;padding:6px;display:none;align-items:center;justify-content:center;border:none;border-radius:28px;background:transparent;color:var(--dsw-alias-label-tertiary,#8a8f98);cursor:pointer;transition:background-color .12s,color .12s}',
|
|
110
|
+
'[data-dshdt-splice="1"] .dshdt-splice{display:inline-flex}',
|
|
111
|
+
'.dshdt-splice:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-button-primary-fill,#4d6bfe)}',
|
|
112
|
+
'.dshdt-splice:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill,#4d6bfe);outline-offset:2px}',
|
|
113
|
+
'.dshdt-splice:disabled{cursor:default;opacity:.4}',
|
|
114
|
+
'.dshdt-splice svg{width:15px;height:15px}',
|
|
99
115
|
'[data-dshdt-no-target="1"] .dshdt-action{display:none!important}',
|
|
100
116
|
'.dshdt-dialog-text{margin:0;color:var(--dsw-alias-label-primary,inherit);font-size:14px;line-height:22px}',
|
|
101
117
|
'.dshdt-dialog-note{margin:10px 0 0;color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:13px;line-height:20px}',
|
|
@@ -131,6 +147,23 @@ export default (require) => {
|
|
|
131
147
|
).join('') +
|
|
132
148
|
'</svg>'
|
|
133
149
|
|
|
150
|
+
// Collapse glyph: a centre rail with one arrow above and one below, both
|
|
151
|
+
// pointing at it - "the neighbours move up into this one's place".
|
|
152
|
+
const SPLICE_ICON_PATHS = [
|
|
153
|
+
'M3.2 8h9.6',
|
|
154
|
+
'M8 2.4v4',
|
|
155
|
+
'M6.3 4.7L8 6.4l1.7-1.7',
|
|
156
|
+
'M8 13.6v-4',
|
|
157
|
+
'M6.3 11.3L8 9.6l1.7 1.7',
|
|
158
|
+
]
|
|
159
|
+
const SPLICE_ICON_MARKUP =
|
|
160
|
+
'<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">' +
|
|
161
|
+
SPLICE_ICON_PATHS.map(
|
|
162
|
+
(d) =>
|
|
163
|
+
'<path d="' + d + '" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
164
|
+
).join('') +
|
|
165
|
+
'</svg>'
|
|
166
|
+
|
|
134
167
|
function TrashIcon() {
|
|
135
168
|
return jsx('svg', {
|
|
136
169
|
width: 16,
|
|
@@ -157,6 +190,9 @@ export default (require) => {
|
|
|
157
190
|
// replacement seq -> the seq it was recorded as shadowing. See hiddenViaOf.
|
|
158
191
|
hiddenVia: new Map(),
|
|
159
192
|
surface: new Set(),
|
|
193
|
+
// The seqs the host would accept a turn-delete for (advertised, not
|
|
194
|
+
// guessed): the entry appears only on those rows.
|
|
195
|
+
spliceSeqs: new Set(),
|
|
160
196
|
replyTurns: new Set(),
|
|
161
197
|
edits: new Map(),
|
|
162
198
|
markerTurns: new Set(),
|
|
@@ -223,6 +259,8 @@ export default (require) => {
|
|
|
223
259
|
const hiddenVia = hiddenViaOf(data.hidden)
|
|
224
260
|
const surface = new Set()
|
|
225
261
|
for (const seq of Array.isArray(data.surface) ? data.surface : []) surface.add(seq)
|
|
262
|
+
const spliceSeqs = new Set()
|
|
263
|
+
for (const seq of Array.isArray(data.spliceSeqs) ? data.spliceSeqs : []) spliceSeqs.add(seq)
|
|
226
264
|
const replyTurns = new Set()
|
|
227
265
|
for (const turn of Array.isArray(data.replyTurns) ? data.replyTurns : []) replyTurns.add(turn)
|
|
228
266
|
const edits = new Map()
|
|
@@ -234,7 +272,7 @@ export default (require) => {
|
|
|
234
272
|
if (typeof turn === 'number') markerTurns.add(turn)
|
|
235
273
|
}
|
|
236
274
|
const surfaceThrough = typeof data.lastSeq === 'number' ? data.lastSeq : -1
|
|
237
|
-
this.publish({ hidden, hiddenVia, surface, replyTurns, edits, markerTurns, surfaceReady: true, surfaceThrough, loaded: true, loadError: false })
|
|
275
|
+
this.publish({ hidden, hiddenVia, surface, spliceSeqs, replyTurns, edits, markerTurns, surfaceReady: true, surfaceThrough, loaded: true, loadError: false })
|
|
238
276
|
})
|
|
239
277
|
.catch(() => {
|
|
240
278
|
this.publish({ loadError: true })
|
|
@@ -360,6 +398,17 @@ export default (require) => {
|
|
|
360
398
|
}
|
|
361
399
|
}
|
|
362
400
|
|
|
401
|
+
// The second entry: delete the WHOLE turn the row belongs to - the prompt
|
|
402
|
+
// included - and splice every later turn back under fresh turn numbers. Only
|
|
403
|
+
// a human prompt anchors it; an injected context row or a reply row is not
|
|
404
|
+
// the turn's own question.
|
|
405
|
+
function spliceTargetFor(node) {
|
|
406
|
+
if (node.kind !== 'user') return null
|
|
407
|
+
const data = node.data || {}
|
|
408
|
+
const seq = typeof data.seq === 'number' ? data.seq : node.anchorSeq
|
|
409
|
+
return typeof seq === 'number' ? { mode: 'splice', seq, label: 'splice' } : null
|
|
410
|
+
}
|
|
411
|
+
|
|
363
412
|
function seqsFor(node) {
|
|
364
413
|
const data = node.data || {}
|
|
365
414
|
const out = []
|
|
@@ -482,6 +531,7 @@ export default (require) => {
|
|
|
482
531
|
// does not, and a re-apply (HMR, plugin toggle, bundle-group reload) starts
|
|
483
532
|
// with fresh module state over the very same page.
|
|
484
533
|
const HOST_MARK = 'data-dshdt-action-host'
|
|
534
|
+
const SPLICE_CLASS = 'dshdt-splice'
|
|
485
535
|
const THINK_MARK = 'data-dshdt-think-action'
|
|
486
536
|
const HOST_CLASS = 'dshdt-action-host'
|
|
487
537
|
const THINK_CLASS = 'dshdt-think-action'
|
|
@@ -524,7 +574,23 @@ export default (require) => {
|
|
|
524
574
|
button.innerHTML = ICON_MARKUP
|
|
525
575
|
host.appendChild(button)
|
|
526
576
|
}
|
|
527
|
-
|
|
577
|
+
// The row host carries this plugin's second action as well: delete the
|
|
578
|
+
// whole turn and splice the later turns back. It rides in the SAME host -
|
|
579
|
+
// the trash action's namespace, its selector and its one-host-per-row
|
|
580
|
+
// contract are untouched - under its own class, and the reasoning host
|
|
581
|
+
// never gets one (a reasoning card owns no turn).
|
|
582
|
+
let splice = null
|
|
583
|
+
if (kind !== 'think') {
|
|
584
|
+
splice = host.querySelector('.' + SPLICE_CLASS)
|
|
585
|
+
if (splice === null) {
|
|
586
|
+
splice = document.createElement('button')
|
|
587
|
+
splice.type = 'button'
|
|
588
|
+
splice.className = SPLICE_CLASS + ' dshdt-row-action'
|
|
589
|
+
splice.innerHTML = SPLICE_ICON_MARKUP
|
|
590
|
+
host.appendChild(splice)
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return { host, button, splice }
|
|
528
594
|
}
|
|
529
595
|
|
|
530
596
|
// Drop every node this plugin injected. Matched through this plugin's own
|
|
@@ -613,7 +679,7 @@ export default (require) => {
|
|
|
613
679
|
rowActions.delete(row)
|
|
614
680
|
}
|
|
615
681
|
|
|
616
|
-
function injectRowAction(row, node, target, controller, t) {
|
|
682
|
+
function injectRowAction(row, node, target, spliceTarget, controller, t) {
|
|
617
683
|
const label = t(`action.tooltip.${target.label}`)
|
|
618
684
|
let entry = rowActions.get(row)
|
|
619
685
|
if (!entry) {
|
|
@@ -622,7 +688,7 @@ export default (require) => {
|
|
|
622
688
|
entry = adoptHost(findOwnedHost(row, false), 'row')
|
|
623
689
|
rowActions.set(row, entry)
|
|
624
690
|
}
|
|
625
|
-
const { host, button } = entry
|
|
691
|
+
const { host, button, splice } = entry
|
|
626
692
|
if (button.getAttribute('aria-label') !== label) {
|
|
627
693
|
button.setAttribute('aria-label', label)
|
|
628
694
|
button.setAttribute('title', label)
|
|
@@ -632,6 +698,24 @@ export default (require) => {
|
|
|
632
698
|
event.stopPropagation()
|
|
633
699
|
controller.open(target)
|
|
634
700
|
}
|
|
701
|
+
// The second action is wired only where a splice target exists (and stays
|
|
702
|
+
// hidden by the row attribute otherwise), so a row can never offer an
|
|
703
|
+
// operation the host would refuse for the wrong reason.
|
|
704
|
+
if (splice !== null && splice !== undefined) {
|
|
705
|
+
const spliceLabel = t('action.tooltip.splice')
|
|
706
|
+
if (splice.getAttribute('aria-label') !== spliceLabel) {
|
|
707
|
+
splice.setAttribute('aria-label', spliceLabel)
|
|
708
|
+
splice.setAttribute('title', spliceLabel)
|
|
709
|
+
}
|
|
710
|
+
splice.onclick =
|
|
711
|
+
spliceTarget === null || spliceTarget === undefined
|
|
712
|
+
? null
|
|
713
|
+
: (event) => {
|
|
714
|
+
event.preventDefault()
|
|
715
|
+
event.stopPropagation()
|
|
716
|
+
controller.open(spliceTarget)
|
|
717
|
+
}
|
|
718
|
+
}
|
|
635
719
|
const anchor = node.kind === 'user' || node.kind === 'steering' ? row.querySelector('[class*="_actions"]') : null
|
|
636
720
|
if (anchor) {
|
|
637
721
|
host.classList.remove('dshdt-floating')
|
|
@@ -741,8 +825,14 @@ export default (require) => {
|
|
|
741
825
|
const target = hidden ? null : liveTarget(targetFor(node), view.edits)
|
|
742
826
|
const covered = target !== null || slotCoversRow(node)
|
|
743
827
|
const deletable = !hidden && covered && rowDeletable(node, seqs, view)
|
|
744
|
-
|
|
828
|
+
// The turn-delete entry rides on the same gates as the delete entry: a
|
|
829
|
+
// row whose content already left the context offers neither.
|
|
830
|
+
const spliceTarget =
|
|
831
|
+
deletable && spliceTargetFor(node) !== null && view.spliceSeqs.has(target.seq) ? liveTarget(spliceTargetFor(node), view.edits) : null
|
|
832
|
+
if (deletable && target !== null) injectRowAction(row, node, target, spliceTarget, controller, t)
|
|
745
833
|
else removeRowAction(row)
|
|
834
|
+
if (deletable && target !== null && spliceTarget !== null) row.dataset.dshdtSplice = '1'
|
|
835
|
+
else delete row.dataset.dshdtSplice
|
|
746
836
|
if (!hidden && covered && !rowDeletable(node, seqs, view)) row.dataset.dshdtNoTarget = '1'
|
|
747
837
|
else delete row.dataset.dshdtNoTarget
|
|
748
838
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-edit-turn browser half, VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-edit-turn/lib/client.js (v0.2.
|
|
3
|
+
// Source: ../dsh-edit-turn/lib/client.js (v0.2.20, sha256 dd3f857d93cbdb0c)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/vendor-sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// The component registers its factory with the host module loader; inside this
|
|
@@ -32,7 +32,7 @@ export default (require) => {
|
|
|
32
32
|
const RERUN_PREFIX = '/dsh-rerun-turn'
|
|
33
33
|
|
|
34
34
|
/** Keep in sync with package.json and lib/index.js. */
|
|
35
|
-
const PLUGIN_VERSION = '0.2.
|
|
35
|
+
const PLUGIN_VERSION = '0.2.20'
|
|
36
36
|
|
|
37
37
|
// --- copy -----------------------------------------------------------------
|
|
38
38
|
|
|
@@ -75,7 +75,6 @@ export default (require) => {
|
|
|
75
75
|
'error.prompt': '已回退,但改后的内容没能写进上下文。请重试。',
|
|
76
76
|
'error.reply': '已回退,但替换没有落地。请重试。',
|
|
77
77
|
'error.empty': '改写后的内容不能为空。',
|
|
78
|
-
'error.unchanged': '内容没有变化,未做任何修改。',
|
|
79
78
|
'error.rerun-nothing': '已保存,但这一轮没有可重跑的作答。',
|
|
80
79
|
'error.rerun-unavailable': '已保存,但重跑没有开始,请稍后重试。',
|
|
81
80
|
'error.not-rerunnable': '这一轮没有可重跑的作答。',
|
|
@@ -123,7 +122,6 @@ export default (require) => {
|
|
|
123
122
|
'error.prompt': 'Rolled back, but the re-run did not start. Send the revised text manually.',
|
|
124
123
|
'error.reply': 'Rolled back, but the replacement did not land. Try again.',
|
|
125
124
|
'error.empty': 'The revised message cannot be empty.',
|
|
126
|
-
'error.unchanged': 'Nothing changed, so no edit was made.',
|
|
127
125
|
'error.rerun-nothing': 'Saved, but this turn has no reply that can be re-run.',
|
|
128
126
|
'error.rerun-unavailable': 'Saved, but the re-run did not start; try again.',
|
|
129
127
|
'error.not-rerunnable': 'This turn has no reply that can be re-run.',
|
|
@@ -738,10 +736,18 @@ export default (require) => {
|
|
|
738
736
|
failure: null,
|
|
739
737
|
})
|
|
740
738
|
// `unchanged` means the host wrote NOTHING: the draft is (verbatim)
|
|
741
|
-
// the text the message already carries.
|
|
742
|
-
//
|
|
743
|
-
//
|
|
744
|
-
//
|
|
739
|
+
// the text the message already carries. The flag is read here for one
|
|
740
|
+
// reason, and it is not the user's: a no-op is not a failure. The
|
|
741
|
+
// `applied === false` wording below would claim the rollback landed
|
|
742
|
+
// and the replacement did not, which is a lie about an edit that was
|
|
743
|
+
// never attempted.
|
|
744
|
+
//
|
|
745
|
+
// Nothing is announced for it, deliberately (0.2.20 removed the
|
|
746
|
+
// notice this used to draw): the edit was simply not an edit, so the
|
|
747
|
+
// editor closes like any save and the screen is exactly as it was -
|
|
748
|
+
// no row was shadowed, so none is hidden, and the message keeps the
|
|
749
|
+
// pencil it had. (The banner also landed on top of the composer's
|
|
750
|
+
// placeholder text, which is not a place to report anything from.)
|
|
745
751
|
//
|
|
746
752
|
// The re-run intent survives it. "Re-run" saves first only because the
|
|
747
753
|
// sibling regenerates the turn from the prompt the surface shows; a
|
|
@@ -749,9 +755,7 @@ export default (require) => {
|
|
|
749
755
|
// wording moved, so the chain below is NOT gated on a write having
|
|
750
756
|
// happened. (The decision itself is the host's - the text is parsed
|
|
751
757
|
// there, once - and this half only reads the flag it sends back.)
|
|
752
|
-
if (data.unchanged
|
|
753
|
-
if (!rerunIntent) this.notify('unchanged')
|
|
754
|
-
} else {
|
|
758
|
+
if (data.unchanged !== true) {
|
|
755
759
|
// The rollback has landed, so the editor is gone with its row: a
|
|
756
760
|
// failed second half has to be reported somewhere that survives that.
|
|
757
761
|
if (data.kind === 'reply' && data.applied === false) this.notify('reply')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-rerun-turn browser half, VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-rerun-turn/lib/client.js (v0.1.
|
|
3
|
+
// Source: ../dsh-rerun-turn/lib/client.js (v0.1.27, sha256 1db55a5a2a12ac66)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/vendor-sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// The component registers its factory with the host module loader; inside this
|
|
@@ -19,7 +19,7 @@ export default (require) => {
|
|
|
19
19
|
const ROUTE_PREFIX = '/dsh-rerun-turn'
|
|
20
20
|
|
|
21
21
|
/** Keep in sync with package.json and lib/index.js. */
|
|
22
|
-
const PLUGIN_VERSION = '0.1.
|
|
22
|
+
const PLUGIN_VERSION = '0.1.27'
|
|
23
23
|
|
|
24
24
|
// --- copy -----------------------------------------------------------------
|
|
25
25
|
|
|
@@ -551,11 +551,19 @@ export default (require) => {
|
|
|
551
551
|
resolved.set(key, decision)
|
|
552
552
|
return decision
|
|
553
553
|
}
|
|
554
|
-
// The turn a row belongs to
|
|
555
|
-
// (`turn
|
|
556
|
-
|
|
554
|
+
// The turn a row belongs to. The platform publishes it on the flow item
|
|
555
|
+
// itself (`data-chat-turn`, the same anchor the prompt-row buttons read),
|
|
556
|
+
// and a key sometimes spells it out as well
|
|
557
|
+
// (`turn-process17`, `turn-tail17`, `assistant-step17:1`). Reading the
|
|
558
|
+
// key alone is what left a bookkeeping turn's empty "completed" strip on
|
|
559
|
+
// screen once DSH started keying those rows by node kind
|
|
560
|
+
// (`turn-tail`, `["turn-tail","response"]`) with the turn only in the
|
|
561
|
+
// data attribute.
|
|
562
|
+
const keyTurnOf = (key, row) => {
|
|
557
563
|
const match = /(?:turn-process|turn-tail)(\d+)|assistant-step(\d+):/.exec(key)
|
|
558
|
-
|
|
564
|
+
if (match !== null) return Number(match[1] ?? match[2])
|
|
565
|
+
const fromRow = row && row.dataset ? Number(row.dataset.chatTurn) : Number.NaN
|
|
566
|
+
return Number.isInteger(fromRow) ? fromRow : null
|
|
559
567
|
}
|
|
560
568
|
const nodeOf = (key) => {
|
|
561
569
|
const direct = snapshot.nodes.get(key)
|
|
@@ -585,7 +593,7 @@ export default (require) => {
|
|
|
585
593
|
key,
|
|
586
594
|
kind: node ? node.kind : null,
|
|
587
595
|
seqs: node ? seqsFor(node) : [],
|
|
588
|
-
keyTurn: keyTurnOf(key),
|
|
596
|
+
keyTurn: keyTurnOf(key, row),
|
|
589
597
|
inTarget: false,
|
|
590
598
|
inTail: false,
|
|
591
599
|
fresh: false,
|