dsh-as-aistudio 0.2.8 → 0.2.12
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 +255 -28
- package/src/components.js +1 -1
- package/src/studio.js +1 -1
- package/src/vendor/dsh-delete-turn.js +227 -12
- 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.12",
|
|
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,
|
|
@@ -3645,16 +3657,78 @@ window.__ModuleLoader__.load({
|
|
|
3645
3657
|
// Minimum gap between automatic snapshot refreshes while a transcript grows.
|
|
3646
3658
|
const REFRESH_INTERVAL_MS = 3000
|
|
3647
3659
|
|
|
3660
|
+
// --- preferences ----------------------------------------------------------
|
|
3661
|
+
//
|
|
3662
|
+
// One client-side preference: whether a deletion opens the confirmation
|
|
3663
|
+
// dialog first. It lives in localStorage - the same store the official
|
|
3664
|
+
// client plugins keep their view options in - so it survives reloads with no
|
|
3665
|
+
// host round-trip, and a subscription keeps the settings page and the delete
|
|
3666
|
+
// paths on one value.
|
|
3667
|
+
|
|
3668
|
+
const PREFS_KEY = 'dsh-delete-turn:prefs'
|
|
3669
|
+
const DEFAULT_ASK = true
|
|
3670
|
+
|
|
3671
|
+
function readStoredAsk() {
|
|
3672
|
+
try {
|
|
3673
|
+
if (typeof window === 'undefined' || !window.localStorage) return DEFAULT_ASK
|
|
3674
|
+
const raw = window.localStorage.getItem(PREFS_KEY)
|
|
3675
|
+
if (raw === null) return DEFAULT_ASK
|
|
3676
|
+
const parsed = JSON.parse(raw)
|
|
3677
|
+
return parsed && typeof parsed.ask === 'boolean' ? parsed.ask : DEFAULT_ASK
|
|
3678
|
+
} catch {
|
|
3679
|
+
// A missing or malformed entry falls back to the shipped default rather
|
|
3680
|
+
// than breaking the host: the dialog path is the safe behaviour.
|
|
3681
|
+
return DEFAULT_ASK
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3685
|
+
let askBeforeDelete = readStoredAsk()
|
|
3686
|
+
const prefListeners = new Set()
|
|
3687
|
+
|
|
3688
|
+
function getPrefs() {
|
|
3689
|
+
return { ask: askBeforeDelete }
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3692
|
+
function setAskBeforeDelete(value) {
|
|
3693
|
+
const ask = value === true
|
|
3694
|
+
if (askBeforeDelete === ask) return
|
|
3695
|
+
askBeforeDelete = ask
|
|
3696
|
+
try {
|
|
3697
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
3698
|
+
window.localStorage.setItem(PREFS_KEY, JSON.stringify({ ask }))
|
|
3699
|
+
}
|
|
3700
|
+
} catch {
|
|
3701
|
+
// Storage that refuses writes (private mode) must not break the toggle.
|
|
3702
|
+
}
|
|
3703
|
+
for (const listener of prefListeners) {
|
|
3704
|
+
try {
|
|
3705
|
+
listener()
|
|
3706
|
+
} catch (error) {
|
|
3707
|
+
console.error('[dsh-delete-turn] preference subscriber threw:', error)
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3712
|
+
function subscribePrefs(listener) {
|
|
3713
|
+
prefListeners.add(listener)
|
|
3714
|
+
return () => {
|
|
3715
|
+
prefListeners.delete(listener)
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
|
|
3648
3719
|
// --- copy -----------------------------------------------------------------
|
|
3649
3720
|
|
|
3650
3721
|
const zh = {
|
|
3651
3722
|
'action.tooltip.message': '删除这条消息',
|
|
3723
|
+
'action.tooltip.turn': '删除这一轮',
|
|
3652
3724
|
'action.tooltip.step': '删除这一步',
|
|
3653
3725
|
'action.tooltip.reply': '删除这条回复',
|
|
3654
3726
|
'dialog.title.message': '删除这条消息?',
|
|
3727
|
+
'dialog.title.turn': '删除这一轮?',
|
|
3655
3728
|
'dialog.title.step': '删除这一步?',
|
|
3656
3729
|
'dialog.title.reply': '删除这条回复?',
|
|
3657
3730
|
'dialog.desc.message': '这条消息将从模型上下文中移除,并从当前转录中隐藏。原始会话日志保持不变。',
|
|
3731
|
+
'dialog.desc.turn': '这一轮(含你的提问、注入上下文、全部回复与工具结果)将从模型上下文中移除;它之后的轮次原样保留,在对话视图里直接接上来。',
|
|
3658
3732
|
'dialog.desc.step': '这一步的回复与它请求的工具结果将一并从模型上下文中移除,同一回合的其它步骤保留。',
|
|
3659
3733
|
'dialog.desc.reply': '这条回复连同它的思考、工具调用与注入上下文将从模型上下文中移除,你的提问会保留。',
|
|
3660
3734
|
'dialog.note': '删除只影响模型后续看到的内容,不会改写历史日志。',
|
|
@@ -3662,6 +3736,11 @@ window.__ModuleLoader__.load({
|
|
|
3662
3736
|
'dialog.confirm': '删除',
|
|
3663
3737
|
'dialog.pending': '删除中…',
|
|
3664
3738
|
'dialog.retry': '重试',
|
|
3739
|
+
'config.ask': '删除时询问',
|
|
3740
|
+
'config.hint.ask': '每次删除前都会弹出确认框。',
|
|
3741
|
+
'config.hint.auto': '删除会直接执行,不再询问。',
|
|
3742
|
+
'config.summary.ask': '删除前询问',
|
|
3743
|
+
'config.summary.auto': '直接删除',
|
|
3665
3744
|
'error.invalid': '请求无效,请刷新后重试。',
|
|
3666
3745
|
'error.session-not-active': '这个会话当前未激活,请先打开该会话再删除。',
|
|
3667
3746
|
'error.session-not-found': '找不到该会话的日志。',
|
|
@@ -3677,12 +3756,15 @@ window.__ModuleLoader__.load({
|
|
|
3677
3756
|
|
|
3678
3757
|
const en = {
|
|
3679
3758
|
'action.tooltip.message': 'Delete this message',
|
|
3759
|
+
'action.tooltip.turn': 'Delete this turn',
|
|
3680
3760
|
'action.tooltip.step': 'Delete this step',
|
|
3681
3761
|
'action.tooltip.reply': 'Delete this reply',
|
|
3682
3762
|
'dialog.title.message': 'Delete this message?',
|
|
3763
|
+
'dialog.title.turn': 'Delete this turn?',
|
|
3683
3764
|
'dialog.title.step': 'Delete this step?',
|
|
3684
3765
|
'dialog.title.reply': 'Delete this reply?',
|
|
3685
3766
|
'dialog.desc.message': 'This message leaves the model context and is hidden from the current transcript. The original session log stays untouched.',
|
|
3767
|
+
'dialog.desc.turn': 'This turn (your prompt, injected context, every reply step and tool result) leaves the model context. Later turns stay exactly where they are and simply move up in the transcript.',
|
|
3686
3768
|
'dialog.desc.step': 'This step and the tool results it requested leave the model context together; other steps in the same turn stay.',
|
|
3687
3769
|
'dialog.desc.reply': 'This reply leaves the model context together with its reasoning, tool calls and injected context; your prompt stays.',
|
|
3688
3770
|
'dialog.note': 'Deletion only changes what the model sees next; the append-only log is never rewritten.',
|
|
@@ -3690,6 +3772,11 @@ window.__ModuleLoader__.load({
|
|
|
3690
3772
|
'dialog.confirm': 'Delete',
|
|
3691
3773
|
'dialog.pending': 'Deleting...',
|
|
3692
3774
|
'dialog.retry': 'Retry',
|
|
3775
|
+
'config.ask': 'Ask before deleting',
|
|
3776
|
+
'config.hint.ask': 'Show a confirmation dialog before every deletion.',
|
|
3777
|
+
'config.hint.auto': 'Delete right away without asking.',
|
|
3778
|
+
'config.summary.ask': 'Ask before deleting',
|
|
3779
|
+
'config.summary.auto': 'Delete without asking',
|
|
3693
3780
|
'error.invalid': 'Invalid request; refresh and try again.',
|
|
3694
3781
|
'error.session-not-active': 'This session is not open in DSH; open it first.',
|
|
3695
3782
|
'error.session-not-found': 'No session log was found for this id.',
|
|
@@ -3720,10 +3807,22 @@ window.__ModuleLoader__.load({
|
|
|
3720
3807
|
'[data-variant="think"]:hover .dshdt-think-action,.dshdt-think-action:focus-within{opacity:1}',
|
|
3721
3808
|
'.dshdt-collapsing{overflow:hidden;transition:height .2s ease,opacity .14s ease,margin .2s ease,padding .2s ease}',
|
|
3722
3809
|
'[data-dshdt-hidden="1"]{display:none!important}',
|
|
3810
|
+
// The second row action: delete the whole turn (prompt included). It is
|
|
3811
|
+
// its own class so the trash action's selector, its counts and the
|
|
3812
|
+
// interop contract stay exactly as they were; the row attribute turns it
|
|
3813
|
+
// on only where the host can plan the operation.
|
|
3814
|
+
'.dshdt-turn{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}',
|
|
3815
|
+
'[data-dshdt-turn="1"] .dshdt-turn{display:inline-flex}',
|
|
3816
|
+
'.dshdt-turn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-button-primary-fill,#4d6bfe)}',
|
|
3817
|
+
'.dshdt-turn:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill,#4d6bfe);outline-offset:2px}',
|
|
3818
|
+
'.dshdt-turn:disabled{cursor:default;opacity:.4}',
|
|
3819
|
+
'.dshdt-turn svg{width:15px;height:15px}',
|
|
3723
3820
|
'[data-dshdt-no-target="1"] .dshdt-action{display:none!important}',
|
|
3724
3821
|
'.dshdt-dialog-text{margin:0;color:var(--dsw-alias-label-primary,inherit);font-size:14px;line-height:22px}',
|
|
3725
3822
|
'.dshdt-dialog-note{margin:10px 0 0;color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:13px;line-height:20px}',
|
|
3726
3823
|
'.dshdt-dialog-error{margin:10px 0 0;color:var(--dsw-alias-state-error-primary,#d54941);font-size:13px;line-height:20px}',
|
|
3824
|
+
'.dshdt-config{display:flex;flex-direction:column;gap:8px}',
|
|
3825
|
+
'.dshdt-config-hint{margin:0;color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:13px;line-height:20px}',
|
|
3727
3826
|
'.dshdt-danger{background:var(--dsw-alias-state-error-primary,#d54941)!important;color:var(--dsw-alias-label-primary-foreground,#fff)!important}',
|
|
3728
3827
|
'@media (prefers-reduced-motion:reduce){.dshdt-collapsing{transition:none}.dshdt-floating{transition:none}}',
|
|
3729
3828
|
].join('')
|
|
@@ -3755,6 +3854,23 @@ window.__ModuleLoader__.load({
|
|
|
3755
3854
|
).join('') +
|
|
3756
3855
|
'</svg>'
|
|
3757
3856
|
|
|
3857
|
+
// Collapse glyph: a centre rail with one arrow above and one below, both
|
|
3858
|
+
// pointing at it - "the neighbours move up into this one's place".
|
|
3859
|
+
const TURN_ICON_PATHS = [
|
|
3860
|
+
'M3.2 8h9.6',
|
|
3861
|
+
'M8 2.4v4',
|
|
3862
|
+
'M6.3 4.7L8 6.4l1.7-1.7',
|
|
3863
|
+
'M8 13.6v-4',
|
|
3864
|
+
'M6.3 11.3L8 9.6l1.7 1.7',
|
|
3865
|
+
]
|
|
3866
|
+
const TURN_ICON_MARKUP =
|
|
3867
|
+
'<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">' +
|
|
3868
|
+
TURN_ICON_PATHS.map(
|
|
3869
|
+
(d) =>
|
|
3870
|
+
'<path d="' + d + '" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
3871
|
+
).join('') +
|
|
3872
|
+
'</svg>'
|
|
3873
|
+
|
|
3758
3874
|
function TrashIcon() {
|
|
3759
3875
|
return jsx('svg', {
|
|
3760
3876
|
width: 16,
|
|
@@ -3781,6 +3897,9 @@ window.__ModuleLoader__.load({
|
|
|
3781
3897
|
// replacement seq -> the seq it was recorded as shadowing. See hiddenViaOf.
|
|
3782
3898
|
hiddenVia: new Map(),
|
|
3783
3899
|
surface: new Set(),
|
|
3900
|
+
// The seqs the host would accept a turn-delete for (advertised, not
|
|
3901
|
+
// guessed): the entry appears only on those rows.
|
|
3902
|
+
turnSeqs: new Set(),
|
|
3784
3903
|
replyTurns: new Set(),
|
|
3785
3904
|
edits: new Map(),
|
|
3786
3905
|
markerTurns: new Set(),
|
|
@@ -3847,6 +3966,8 @@ window.__ModuleLoader__.load({
|
|
|
3847
3966
|
const hiddenVia = hiddenViaOf(data.hidden)
|
|
3848
3967
|
const surface = new Set()
|
|
3849
3968
|
for (const seq of Array.isArray(data.surface) ? data.surface : []) surface.add(seq)
|
|
3969
|
+
const turnSeqs = new Set()
|
|
3970
|
+
for (const seq of Array.isArray(data.turnSeqs) ? data.turnSeqs : []) turnSeqs.add(seq)
|
|
3850
3971
|
const replyTurns = new Set()
|
|
3851
3972
|
for (const turn of Array.isArray(data.replyTurns) ? data.replyTurns : []) replyTurns.add(turn)
|
|
3852
3973
|
const edits = new Map()
|
|
@@ -3858,7 +3979,7 @@ window.__ModuleLoader__.load({
|
|
|
3858
3979
|
if (typeof turn === 'number') markerTurns.add(turn)
|
|
3859
3980
|
}
|
|
3860
3981
|
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 })
|
|
3982
|
+
this.publish({ hidden, hiddenVia, surface, turnSeqs, replyTurns, edits, markerTurns, surfaceReady: true, surfaceThrough, loaded: true, loadError: false })
|
|
3862
3983
|
})
|
|
3863
3984
|
.catch(() => {
|
|
3864
3985
|
this.publish({ loadError: true })
|
|
@@ -3881,8 +4002,25 @@ window.__ModuleLoader__.load({
|
|
|
3881
4002
|
}
|
|
3882
4003
|
|
|
3883
4004
|
async confirm() {
|
|
3884
|
-
|
|
3885
|
-
|
|
4005
|
+
await this.perform(this.view.dialog)
|
|
4006
|
+
}
|
|
4007
|
+
|
|
4008
|
+
// The entry every action uses. With "ask" on, it opens the confirmation
|
|
4009
|
+
// dialog as before. With it off, it deletes straight away - and since a
|
|
4010
|
+
// silent failure would have nowhere to surface, a failed attempt opens the
|
|
4011
|
+
// dialog carrying its error so the user can retry or cancel.
|
|
4012
|
+
async trigger(target) {
|
|
4013
|
+
if (target === null || target === undefined || this.view.pending) return
|
|
4014
|
+
if (getPrefs().ask) {
|
|
4015
|
+
this.open(target)
|
|
4016
|
+
return
|
|
4017
|
+
}
|
|
4018
|
+
const result = await this.perform(target)
|
|
4019
|
+
if (result.failure !== null) this.publish({ dialog: target, pending: false, failure: result.failure })
|
|
4020
|
+
}
|
|
4021
|
+
|
|
4022
|
+
async perform(requested) {
|
|
4023
|
+
if (requested === null || this.view.pending) return { failure: null }
|
|
3886
4024
|
this.publish({ pending: true, failure: null })
|
|
3887
4025
|
try {
|
|
3888
4026
|
// The live node can move while the dialog sits open: another producer
|
|
@@ -3903,7 +4041,7 @@ window.__ModuleLoader__.load({
|
|
|
3903
4041
|
}
|
|
3904
4042
|
if (!result.ok) {
|
|
3905
4043
|
this.publish({ pending: false, failure: result.code })
|
|
3906
|
-
return
|
|
4044
|
+
return { failure: result.code }
|
|
3907
4045
|
}
|
|
3908
4046
|
const data = result.data
|
|
3909
4047
|
const hidden = new Map(this.view.hidden)
|
|
@@ -3914,8 +4052,10 @@ window.__ModuleLoader__.load({
|
|
|
3914
4052
|
for (const [via, seq] of hiddenViaOf(data.hidden)) hiddenVia.set(via, seq)
|
|
3915
4053
|
this.animateOnce = true
|
|
3916
4054
|
this.publish({ pending: false, dialog: null, hidden, hiddenVia, loaded: true, loadError: false })
|
|
4055
|
+
return { failure: null }
|
|
3917
4056
|
} catch {
|
|
3918
4057
|
this.publish({ pending: false, failure: 'generic' })
|
|
4058
|
+
return { failure: 'generic' }
|
|
3919
4059
|
}
|
|
3920
4060
|
}
|
|
3921
4061
|
|
|
@@ -3984,6 +4124,16 @@ window.__ModuleLoader__.load({
|
|
|
3984
4124
|
}
|
|
3985
4125
|
}
|
|
3986
4126
|
|
|
4127
|
+
// The second entry: delete the WHOLE turn the row belongs to - the prompt
|
|
4128
|
+
// included - as one range replace. Only a human prompt anchors it; an
|
|
4129
|
+
// injected context row or a reply row is not the turn's own question.
|
|
4130
|
+
function turnTargetFor(node) {
|
|
4131
|
+
if (node.kind !== 'user') return null
|
|
4132
|
+
const data = node.data || {}
|
|
4133
|
+
const seq = typeof data.seq === 'number' ? data.seq : node.anchorSeq
|
|
4134
|
+
return typeof seq === 'number' ? { mode: 'turn', seq, label: 'turn' } : null
|
|
4135
|
+
}
|
|
4136
|
+
|
|
3987
4137
|
function seqsFor(node) {
|
|
3988
4138
|
const data = node.data || {}
|
|
3989
4139
|
const out = []
|
|
@@ -4106,6 +4256,7 @@ window.__ModuleLoader__.load({
|
|
|
4106
4256
|
// does not, and a re-apply (HMR, plugin toggle, bundle-group reload) starts
|
|
4107
4257
|
// with fresh module state over the very same page.
|
|
4108
4258
|
const HOST_MARK = 'data-dshdt-action-host'
|
|
4259
|
+
const TURN_CLASS = 'dshdt-turn'
|
|
4109
4260
|
const THINK_MARK = 'data-dshdt-think-action'
|
|
4110
4261
|
const HOST_CLASS = 'dshdt-action-host'
|
|
4111
4262
|
const THINK_CLASS = 'dshdt-think-action'
|
|
@@ -4148,7 +4299,23 @@ window.__ModuleLoader__.load({
|
|
|
4148
4299
|
button.innerHTML = ICON_MARKUP
|
|
4149
4300
|
host.appendChild(button)
|
|
4150
4301
|
}
|
|
4151
|
-
|
|
4302
|
+
// The row host carries this plugin's second action as well: delete the
|
|
4303
|
+
// whole turn. It rides in the SAME host -
|
|
4304
|
+
// the trash action's namespace, its selector and its one-host-per-row
|
|
4305
|
+
// contract are untouched - under its own class, and the reasoning host
|
|
4306
|
+
// never gets one (a reasoning card owns no turn).
|
|
4307
|
+
let turn = null
|
|
4308
|
+
if (kind !== 'think') {
|
|
4309
|
+
turn = host.querySelector('.' + TURN_CLASS)
|
|
4310
|
+
if (turn === null) {
|
|
4311
|
+
turn = document.createElement('button')
|
|
4312
|
+
turn.type = 'button'
|
|
4313
|
+
turn.className = TURN_CLASS + ' dshdt-row-action'
|
|
4314
|
+
turn.innerHTML = TURN_ICON_MARKUP
|
|
4315
|
+
host.appendChild(turn)
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
4318
|
+
return { host, button, turn }
|
|
4152
4319
|
}
|
|
4153
4320
|
|
|
4154
4321
|
// Drop every node this plugin injected. Matched through this plugin's own
|
|
@@ -4237,7 +4404,7 @@ window.__ModuleLoader__.load({
|
|
|
4237
4404
|
rowActions.delete(row)
|
|
4238
4405
|
}
|
|
4239
4406
|
|
|
4240
|
-
function injectRowAction(row, node, target, controller, t) {
|
|
4407
|
+
function injectRowAction(row, node, target, turnTarget, controller, t) {
|
|
4241
4408
|
const label = t(`action.tooltip.${target.label}`)
|
|
4242
4409
|
let entry = rowActions.get(row)
|
|
4243
4410
|
if (!entry) {
|
|
@@ -4246,7 +4413,7 @@ window.__ModuleLoader__.load({
|
|
|
4246
4413
|
entry = adoptHost(findOwnedHost(row, false), 'row')
|
|
4247
4414
|
rowActions.set(row, entry)
|
|
4248
4415
|
}
|
|
4249
|
-
const { host, button } = entry
|
|
4416
|
+
const { host, button, turn } = entry
|
|
4250
4417
|
if (button.getAttribute('aria-label') !== label) {
|
|
4251
4418
|
button.setAttribute('aria-label', label)
|
|
4252
4419
|
button.setAttribute('title', label)
|
|
@@ -4254,7 +4421,25 @@ window.__ModuleLoader__.load({
|
|
|
4254
4421
|
button.onclick = (event) => {
|
|
4255
4422
|
event.preventDefault()
|
|
4256
4423
|
event.stopPropagation()
|
|
4257
|
-
controller.
|
|
4424
|
+
controller.trigger(target)
|
|
4425
|
+
}
|
|
4426
|
+
// The second action is wired only where a turn target exists (and stays
|
|
4427
|
+
// hidden by the row attribute otherwise), so a row can never offer an
|
|
4428
|
+
// operation the host would refuse for the wrong reason.
|
|
4429
|
+
if (turn !== null && turn !== undefined) {
|
|
4430
|
+
const turnLabel = t('action.tooltip.turn')
|
|
4431
|
+
if (turn.getAttribute('aria-label') !== turnLabel) {
|
|
4432
|
+
turn.setAttribute('aria-label', turnLabel)
|
|
4433
|
+
turn.setAttribute('title', turnLabel)
|
|
4434
|
+
}
|
|
4435
|
+
turn.onclick =
|
|
4436
|
+
turnTarget === null || turnTarget === undefined
|
|
4437
|
+
? null
|
|
4438
|
+
: (event) => {
|
|
4439
|
+
event.preventDefault()
|
|
4440
|
+
event.stopPropagation()
|
|
4441
|
+
controller.trigger(turnTarget)
|
|
4442
|
+
}
|
|
4258
4443
|
}
|
|
4259
4444
|
const anchor = node.kind === 'user' || node.kind === 'steering' ? row.querySelector('[class*="_actions"]') : null
|
|
4260
4445
|
if (anchor) {
|
|
@@ -4295,7 +4480,7 @@ window.__ModuleLoader__.load({
|
|
|
4295
4480
|
button.onclick = (event) => {
|
|
4296
4481
|
event.preventDefault()
|
|
4297
4482
|
event.stopPropagation()
|
|
4298
|
-
controller.
|
|
4483
|
+
controller.trigger(target)
|
|
4299
4484
|
}
|
|
4300
4485
|
if (host.parentElement !== think) think.appendChild(host)
|
|
4301
4486
|
}
|
|
@@ -4365,8 +4550,14 @@ window.__ModuleLoader__.load({
|
|
|
4365
4550
|
const target = hidden ? null : liveTarget(targetFor(node), view.edits)
|
|
4366
4551
|
const covered = target !== null || slotCoversRow(node)
|
|
4367
4552
|
const deletable = !hidden && covered && rowDeletable(node, seqs, view)
|
|
4368
|
-
|
|
4553
|
+
// The turn-delete entry rides on the same gates as the delete entry: a
|
|
4554
|
+
// row whose content already left the context offers neither.
|
|
4555
|
+
const turnTarget =
|
|
4556
|
+
deletable && turnTargetFor(node) !== null && view.turnSeqs.has(target.seq) ? liveTarget(turnTargetFor(node), view.edits) : null
|
|
4557
|
+
if (deletable && target !== null) injectRowAction(row, node, target, turnTarget, controller, t)
|
|
4369
4558
|
else removeRowAction(row)
|
|
4559
|
+
if (deletable && target !== null && turnTarget !== null) row.dataset.dshdtTurn = '1'
|
|
4560
|
+
else delete row.dataset.dshdtTurn
|
|
4370
4561
|
if (!hidden && covered && !rowDeletable(node, seqs, view)) row.dataset.dshdtNoTarget = '1'
|
|
4371
4562
|
else delete row.dataset.dshdtNoTarget
|
|
4372
4563
|
}
|
|
@@ -4436,7 +4627,7 @@ window.__ModuleLoader__.load({
|
|
|
4436
4627
|
'aria-label': label,
|
|
4437
4628
|
title: label,
|
|
4438
4629
|
disabled: view.pending,
|
|
4439
|
-
onClick: () => controller.
|
|
4630
|
+
onClick: () => controller.trigger({ mode: 'reply', messageId }),
|
|
4440
4631
|
children: jsx(TrashIcon, {}),
|
|
4441
4632
|
})
|
|
4442
4633
|
}
|
|
@@ -4481,6 +4672,27 @@ window.__ModuleLoader__.load({
|
|
|
4481
4672
|
})
|
|
4482
4673
|
}
|
|
4483
4674
|
|
|
4675
|
+
// The plugin page entry (the component row's "configure" arrow, slot
|
|
4676
|
+
// `plugins.row.config`, key `<bundle>#<row id>`). It renders the row's
|
|
4677
|
+
// one-liner for the manager's summary slot and the toggle card for the page.
|
|
4678
|
+
function AskBeforeDeleteOption({ view, t }) {
|
|
4679
|
+
const tr = typeof t === 'function' ? t : (key) => key
|
|
4680
|
+
const [ask, setAsk] = react.useState(getPrefs().ask)
|
|
4681
|
+
react.useEffect(() => subscribePrefs(() => setAsk(getPrefs().ask)), [])
|
|
4682
|
+
if (view === 'summary') return tr(ask ? 'config.summary.ask' : 'config.summary.auto')
|
|
4683
|
+
return jsxs('div', {
|
|
4684
|
+
className: 'dshdt-config',
|
|
4685
|
+
children: [
|
|
4686
|
+
jsx(primitives.Checkbox, {
|
|
4687
|
+
checked: ask,
|
|
4688
|
+
label: tr('config.ask'),
|
|
4689
|
+
onChange: (checked) => setAskBeforeDelete(checked),
|
|
4690
|
+
}),
|
|
4691
|
+
jsx('p', { className: 'dshdt-config-hint', children: tr(ask ? 'config.hint.ask' : 'config.hint.auto') }),
|
|
4692
|
+
],
|
|
4693
|
+
})
|
|
4694
|
+
}
|
|
4695
|
+
|
|
4484
4696
|
function OverlayEntry({ useChat, useDeletion, controller, t }) {
|
|
4485
4697
|
const snapshot = typeof useChat === 'function' ? useChat((state) => state) : undefined
|
|
4486
4698
|
const view = useDeletion((state) => state)
|
|
@@ -4575,6 +4787,21 @@ window.__ModuleLoader__.load({
|
|
|
4575
4787
|
OverlayEntry,
|
|
4576
4788
|
),
|
|
4577
4789
|
)
|
|
4790
|
+
|
|
4791
|
+
// The plugin's own page in the manager: the row this bundle inserts
|
|
4792
|
+
// (`cordis.patch.yml`, id `dsh-delete-turn`) gains a configure arrow that
|
|
4793
|
+
// opens this card. The key is `<bundle package>#<row id>`, the contract of
|
|
4794
|
+
// ui-plugin-manager's rowConfigKey.
|
|
4795
|
+
ctx.slots.inject('plugins.row.config', () =>
|
|
4796
|
+
ctx.slots.register(
|
|
4797
|
+
{
|
|
4798
|
+
name: 'plugins.row.config',
|
|
4799
|
+
key: 'dsh-delete-turn#dsh-delete-turn',
|
|
4800
|
+
locale: NS,
|
|
4801
|
+
},
|
|
4802
|
+
AskBeforeDeleteOption,
|
|
4803
|
+
),
|
|
4804
|
+
)
|
|
4578
4805
|
}
|
|
4579
4806
|
|
|
4580
4807
|
exports.apply = apply
|
|
@@ -5465,7 +5692,7 @@ window.__ModuleLoader__.load({
|
|
|
5465
5692
|
const NS = 'dsh-as-aistudio'
|
|
5466
5693
|
|
|
5467
5694
|
/** Keep in sync with package.json and src/components.js. */
|
|
5468
|
-
const PLUGIN_VERSION = '0.2.
|
|
5695
|
+
const PLUGIN_VERSION = '0.2.12'
|
|
5469
5696
|
|
|
5470
5697
|
/** Presence marker the live verifier reads. */
|
|
5471
5698
|
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.12'
|
|
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.12'
|
|
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.11, sha256 7d14658143d66eb2)
|
|
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
|
|
@@ -21,16 +21,78 @@ export default (require) => {
|
|
|
21
21
|
// Minimum gap between automatic snapshot refreshes while a transcript grows.
|
|
22
22
|
const REFRESH_INTERVAL_MS = 3000
|
|
23
23
|
|
|
24
|
+
// --- preferences ----------------------------------------------------------
|
|
25
|
+
//
|
|
26
|
+
// One client-side preference: whether a deletion opens the confirmation
|
|
27
|
+
// dialog first. It lives in localStorage - the same store the official
|
|
28
|
+
// client plugins keep their view options in - so it survives reloads with no
|
|
29
|
+
// host round-trip, and a subscription keeps the settings page and the delete
|
|
30
|
+
// paths on one value.
|
|
31
|
+
|
|
32
|
+
const PREFS_KEY = 'dsh-delete-turn:prefs'
|
|
33
|
+
const DEFAULT_ASK = true
|
|
34
|
+
|
|
35
|
+
function readStoredAsk() {
|
|
36
|
+
try {
|
|
37
|
+
if (typeof window === 'undefined' || !window.localStorage) return DEFAULT_ASK
|
|
38
|
+
const raw = window.localStorage.getItem(PREFS_KEY)
|
|
39
|
+
if (raw === null) return DEFAULT_ASK
|
|
40
|
+
const parsed = JSON.parse(raw)
|
|
41
|
+
return parsed && typeof parsed.ask === 'boolean' ? parsed.ask : DEFAULT_ASK
|
|
42
|
+
} catch {
|
|
43
|
+
// A missing or malformed entry falls back to the shipped default rather
|
|
44
|
+
// than breaking the host: the dialog path is the safe behaviour.
|
|
45
|
+
return DEFAULT_ASK
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let askBeforeDelete = readStoredAsk()
|
|
50
|
+
const prefListeners = new Set()
|
|
51
|
+
|
|
52
|
+
function getPrefs() {
|
|
53
|
+
return { ask: askBeforeDelete }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function setAskBeforeDelete(value) {
|
|
57
|
+
const ask = value === true
|
|
58
|
+
if (askBeforeDelete === ask) return
|
|
59
|
+
askBeforeDelete = ask
|
|
60
|
+
try {
|
|
61
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
62
|
+
window.localStorage.setItem(PREFS_KEY, JSON.stringify({ ask }))
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// Storage that refuses writes (private mode) must not break the toggle.
|
|
66
|
+
}
|
|
67
|
+
for (const listener of prefListeners) {
|
|
68
|
+
try {
|
|
69
|
+
listener()
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('[dsh-delete-turn] preference subscriber threw:', error)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function subscribePrefs(listener) {
|
|
77
|
+
prefListeners.add(listener)
|
|
78
|
+
return () => {
|
|
79
|
+
prefListeners.delete(listener)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
24
83
|
// --- copy -----------------------------------------------------------------
|
|
25
84
|
|
|
26
85
|
const zh = {
|
|
27
86
|
'action.tooltip.message': '删除这条消息',
|
|
87
|
+
'action.tooltip.turn': '删除这一轮',
|
|
28
88
|
'action.tooltip.step': '删除这一步',
|
|
29
89
|
'action.tooltip.reply': '删除这条回复',
|
|
30
90
|
'dialog.title.message': '删除这条消息?',
|
|
91
|
+
'dialog.title.turn': '删除这一轮?',
|
|
31
92
|
'dialog.title.step': '删除这一步?',
|
|
32
93
|
'dialog.title.reply': '删除这条回复?',
|
|
33
94
|
'dialog.desc.message': '这条消息将从模型上下文中移除,并从当前转录中隐藏。原始会话日志保持不变。',
|
|
95
|
+
'dialog.desc.turn': '这一轮(含你的提问、注入上下文、全部回复与工具结果)将从模型上下文中移除;它之后的轮次原样保留,在对话视图里直接接上来。',
|
|
34
96
|
'dialog.desc.step': '这一步的回复与它请求的工具结果将一并从模型上下文中移除,同一回合的其它步骤保留。',
|
|
35
97
|
'dialog.desc.reply': '这条回复连同它的思考、工具调用与注入上下文将从模型上下文中移除,你的提问会保留。',
|
|
36
98
|
'dialog.note': '删除只影响模型后续看到的内容,不会改写历史日志。',
|
|
@@ -38,6 +100,11 @@ export default (require) => {
|
|
|
38
100
|
'dialog.confirm': '删除',
|
|
39
101
|
'dialog.pending': '删除中…',
|
|
40
102
|
'dialog.retry': '重试',
|
|
103
|
+
'config.ask': '删除时询问',
|
|
104
|
+
'config.hint.ask': '每次删除前都会弹出确认框。',
|
|
105
|
+
'config.hint.auto': '删除会直接执行,不再询问。',
|
|
106
|
+
'config.summary.ask': '删除前询问',
|
|
107
|
+
'config.summary.auto': '直接删除',
|
|
41
108
|
'error.invalid': '请求无效,请刷新后重试。',
|
|
42
109
|
'error.session-not-active': '这个会话当前未激活,请先打开该会话再删除。',
|
|
43
110
|
'error.session-not-found': '找不到该会话的日志。',
|
|
@@ -53,12 +120,15 @@ export default (require) => {
|
|
|
53
120
|
|
|
54
121
|
const en = {
|
|
55
122
|
'action.tooltip.message': 'Delete this message',
|
|
123
|
+
'action.tooltip.turn': 'Delete this turn',
|
|
56
124
|
'action.tooltip.step': 'Delete this step',
|
|
57
125
|
'action.tooltip.reply': 'Delete this reply',
|
|
58
126
|
'dialog.title.message': 'Delete this message?',
|
|
127
|
+
'dialog.title.turn': 'Delete this turn?',
|
|
59
128
|
'dialog.title.step': 'Delete this step?',
|
|
60
129
|
'dialog.title.reply': 'Delete this reply?',
|
|
61
130
|
'dialog.desc.message': 'This message leaves the model context and is hidden from the current transcript. The original session log stays untouched.',
|
|
131
|
+
'dialog.desc.turn': 'This turn (your prompt, injected context, every reply step and tool result) leaves the model context. Later turns stay exactly where they are and simply move up in the transcript.',
|
|
62
132
|
'dialog.desc.step': 'This step and the tool results it requested leave the model context together; other steps in the same turn stay.',
|
|
63
133
|
'dialog.desc.reply': 'This reply leaves the model context together with its reasoning, tool calls and injected context; your prompt stays.',
|
|
64
134
|
'dialog.note': 'Deletion only changes what the model sees next; the append-only log is never rewritten.',
|
|
@@ -66,6 +136,11 @@ export default (require) => {
|
|
|
66
136
|
'dialog.confirm': 'Delete',
|
|
67
137
|
'dialog.pending': 'Deleting...',
|
|
68
138
|
'dialog.retry': 'Retry',
|
|
139
|
+
'config.ask': 'Ask before deleting',
|
|
140
|
+
'config.hint.ask': 'Show a confirmation dialog before every deletion.',
|
|
141
|
+
'config.hint.auto': 'Delete right away without asking.',
|
|
142
|
+
'config.summary.ask': 'Ask before deleting',
|
|
143
|
+
'config.summary.auto': 'Delete without asking',
|
|
69
144
|
'error.invalid': 'Invalid request; refresh and try again.',
|
|
70
145
|
'error.session-not-active': 'This session is not open in DSH; open it first.',
|
|
71
146
|
'error.session-not-found': 'No session log was found for this id.',
|
|
@@ -96,10 +171,22 @@ export default (require) => {
|
|
|
96
171
|
'[data-variant="think"]:hover .dshdt-think-action,.dshdt-think-action:focus-within{opacity:1}',
|
|
97
172
|
'.dshdt-collapsing{overflow:hidden;transition:height .2s ease,opacity .14s ease,margin .2s ease,padding .2s ease}',
|
|
98
173
|
'[data-dshdt-hidden="1"]{display:none!important}',
|
|
174
|
+
// The second row action: delete the whole turn (prompt included). It is
|
|
175
|
+
// its own class so the trash action's selector, its counts and the
|
|
176
|
+
// interop contract stay exactly as they were; the row attribute turns it
|
|
177
|
+
// on only where the host can plan the operation.
|
|
178
|
+
'.dshdt-turn{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}',
|
|
179
|
+
'[data-dshdt-turn="1"] .dshdt-turn{display:inline-flex}',
|
|
180
|
+
'.dshdt-turn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-button-primary-fill,#4d6bfe)}',
|
|
181
|
+
'.dshdt-turn:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill,#4d6bfe);outline-offset:2px}',
|
|
182
|
+
'.dshdt-turn:disabled{cursor:default;opacity:.4}',
|
|
183
|
+
'.dshdt-turn svg{width:15px;height:15px}',
|
|
99
184
|
'[data-dshdt-no-target="1"] .dshdt-action{display:none!important}',
|
|
100
185
|
'.dshdt-dialog-text{margin:0;color:var(--dsw-alias-label-primary,inherit);font-size:14px;line-height:22px}',
|
|
101
186
|
'.dshdt-dialog-note{margin:10px 0 0;color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:13px;line-height:20px}',
|
|
102
187
|
'.dshdt-dialog-error{margin:10px 0 0;color:var(--dsw-alias-state-error-primary,#d54941);font-size:13px;line-height:20px}',
|
|
188
|
+
'.dshdt-config{display:flex;flex-direction:column;gap:8px}',
|
|
189
|
+
'.dshdt-config-hint{margin:0;color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:13px;line-height:20px}',
|
|
103
190
|
'.dshdt-danger{background:var(--dsw-alias-state-error-primary,#d54941)!important;color:var(--dsw-alias-label-primary-foreground,#fff)!important}',
|
|
104
191
|
'@media (prefers-reduced-motion:reduce){.dshdt-collapsing{transition:none}.dshdt-floating{transition:none}}',
|
|
105
192
|
].join('')
|
|
@@ -131,6 +218,23 @@ export default (require) => {
|
|
|
131
218
|
).join('') +
|
|
132
219
|
'</svg>'
|
|
133
220
|
|
|
221
|
+
// Collapse glyph: a centre rail with one arrow above and one below, both
|
|
222
|
+
// pointing at it - "the neighbours move up into this one's place".
|
|
223
|
+
const TURN_ICON_PATHS = [
|
|
224
|
+
'M3.2 8h9.6',
|
|
225
|
+
'M8 2.4v4',
|
|
226
|
+
'M6.3 4.7L8 6.4l1.7-1.7',
|
|
227
|
+
'M8 13.6v-4',
|
|
228
|
+
'M6.3 11.3L8 9.6l1.7 1.7',
|
|
229
|
+
]
|
|
230
|
+
const TURN_ICON_MARKUP =
|
|
231
|
+
'<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">' +
|
|
232
|
+
TURN_ICON_PATHS.map(
|
|
233
|
+
(d) =>
|
|
234
|
+
'<path d="' + d + '" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
235
|
+
).join('') +
|
|
236
|
+
'</svg>'
|
|
237
|
+
|
|
134
238
|
function TrashIcon() {
|
|
135
239
|
return jsx('svg', {
|
|
136
240
|
width: 16,
|
|
@@ -157,6 +261,9 @@ export default (require) => {
|
|
|
157
261
|
// replacement seq -> the seq it was recorded as shadowing. See hiddenViaOf.
|
|
158
262
|
hiddenVia: new Map(),
|
|
159
263
|
surface: new Set(),
|
|
264
|
+
// The seqs the host would accept a turn-delete for (advertised, not
|
|
265
|
+
// guessed): the entry appears only on those rows.
|
|
266
|
+
turnSeqs: new Set(),
|
|
160
267
|
replyTurns: new Set(),
|
|
161
268
|
edits: new Map(),
|
|
162
269
|
markerTurns: new Set(),
|
|
@@ -223,6 +330,8 @@ export default (require) => {
|
|
|
223
330
|
const hiddenVia = hiddenViaOf(data.hidden)
|
|
224
331
|
const surface = new Set()
|
|
225
332
|
for (const seq of Array.isArray(data.surface) ? data.surface : []) surface.add(seq)
|
|
333
|
+
const turnSeqs = new Set()
|
|
334
|
+
for (const seq of Array.isArray(data.turnSeqs) ? data.turnSeqs : []) turnSeqs.add(seq)
|
|
226
335
|
const replyTurns = new Set()
|
|
227
336
|
for (const turn of Array.isArray(data.replyTurns) ? data.replyTurns : []) replyTurns.add(turn)
|
|
228
337
|
const edits = new Map()
|
|
@@ -234,7 +343,7 @@ export default (require) => {
|
|
|
234
343
|
if (typeof turn === 'number') markerTurns.add(turn)
|
|
235
344
|
}
|
|
236
345
|
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 })
|
|
346
|
+
this.publish({ hidden, hiddenVia, surface, turnSeqs, replyTurns, edits, markerTurns, surfaceReady: true, surfaceThrough, loaded: true, loadError: false })
|
|
238
347
|
})
|
|
239
348
|
.catch(() => {
|
|
240
349
|
this.publish({ loadError: true })
|
|
@@ -257,8 +366,25 @@ export default (require) => {
|
|
|
257
366
|
}
|
|
258
367
|
|
|
259
368
|
async confirm() {
|
|
260
|
-
|
|
261
|
-
|
|
369
|
+
await this.perform(this.view.dialog)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// The entry every action uses. With "ask" on, it opens the confirmation
|
|
373
|
+
// dialog as before. With it off, it deletes straight away - and since a
|
|
374
|
+
// silent failure would have nowhere to surface, a failed attempt opens the
|
|
375
|
+
// dialog carrying its error so the user can retry or cancel.
|
|
376
|
+
async trigger(target) {
|
|
377
|
+
if (target === null || target === undefined || this.view.pending) return
|
|
378
|
+
if (getPrefs().ask) {
|
|
379
|
+
this.open(target)
|
|
380
|
+
return
|
|
381
|
+
}
|
|
382
|
+
const result = await this.perform(target)
|
|
383
|
+
if (result.failure !== null) this.publish({ dialog: target, pending: false, failure: result.failure })
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async perform(requested) {
|
|
387
|
+
if (requested === null || this.view.pending) return { failure: null }
|
|
262
388
|
this.publish({ pending: true, failure: null })
|
|
263
389
|
try {
|
|
264
390
|
// The live node can move while the dialog sits open: another producer
|
|
@@ -279,7 +405,7 @@ export default (require) => {
|
|
|
279
405
|
}
|
|
280
406
|
if (!result.ok) {
|
|
281
407
|
this.publish({ pending: false, failure: result.code })
|
|
282
|
-
return
|
|
408
|
+
return { failure: result.code }
|
|
283
409
|
}
|
|
284
410
|
const data = result.data
|
|
285
411
|
const hidden = new Map(this.view.hidden)
|
|
@@ -290,8 +416,10 @@ export default (require) => {
|
|
|
290
416
|
for (const [via, seq] of hiddenViaOf(data.hidden)) hiddenVia.set(via, seq)
|
|
291
417
|
this.animateOnce = true
|
|
292
418
|
this.publish({ pending: false, dialog: null, hidden, hiddenVia, loaded: true, loadError: false })
|
|
419
|
+
return { failure: null }
|
|
293
420
|
} catch {
|
|
294
421
|
this.publish({ pending: false, failure: 'generic' })
|
|
422
|
+
return { failure: 'generic' }
|
|
295
423
|
}
|
|
296
424
|
}
|
|
297
425
|
|
|
@@ -360,6 +488,16 @@ export default (require) => {
|
|
|
360
488
|
}
|
|
361
489
|
}
|
|
362
490
|
|
|
491
|
+
// The second entry: delete the WHOLE turn the row belongs to - the prompt
|
|
492
|
+
// included - as one range replace. Only a human prompt anchors it; an
|
|
493
|
+
// injected context row or a reply row is not the turn's own question.
|
|
494
|
+
function turnTargetFor(node) {
|
|
495
|
+
if (node.kind !== 'user') return null
|
|
496
|
+
const data = node.data || {}
|
|
497
|
+
const seq = typeof data.seq === 'number' ? data.seq : node.anchorSeq
|
|
498
|
+
return typeof seq === 'number' ? { mode: 'turn', seq, label: 'turn' } : null
|
|
499
|
+
}
|
|
500
|
+
|
|
363
501
|
function seqsFor(node) {
|
|
364
502
|
const data = node.data || {}
|
|
365
503
|
const out = []
|
|
@@ -482,6 +620,7 @@ export default (require) => {
|
|
|
482
620
|
// does not, and a re-apply (HMR, plugin toggle, bundle-group reload) starts
|
|
483
621
|
// with fresh module state over the very same page.
|
|
484
622
|
const HOST_MARK = 'data-dshdt-action-host'
|
|
623
|
+
const TURN_CLASS = 'dshdt-turn'
|
|
485
624
|
const THINK_MARK = 'data-dshdt-think-action'
|
|
486
625
|
const HOST_CLASS = 'dshdt-action-host'
|
|
487
626
|
const THINK_CLASS = 'dshdt-think-action'
|
|
@@ -524,7 +663,23 @@ export default (require) => {
|
|
|
524
663
|
button.innerHTML = ICON_MARKUP
|
|
525
664
|
host.appendChild(button)
|
|
526
665
|
}
|
|
527
|
-
|
|
666
|
+
// The row host carries this plugin's second action as well: delete the
|
|
667
|
+
// whole turn. It rides in the SAME host -
|
|
668
|
+
// the trash action's namespace, its selector and its one-host-per-row
|
|
669
|
+
// contract are untouched - under its own class, and the reasoning host
|
|
670
|
+
// never gets one (a reasoning card owns no turn).
|
|
671
|
+
let turn = null
|
|
672
|
+
if (kind !== 'think') {
|
|
673
|
+
turn = host.querySelector('.' + TURN_CLASS)
|
|
674
|
+
if (turn === null) {
|
|
675
|
+
turn = document.createElement('button')
|
|
676
|
+
turn.type = 'button'
|
|
677
|
+
turn.className = TURN_CLASS + ' dshdt-row-action'
|
|
678
|
+
turn.innerHTML = TURN_ICON_MARKUP
|
|
679
|
+
host.appendChild(turn)
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return { host, button, turn }
|
|
528
683
|
}
|
|
529
684
|
|
|
530
685
|
// Drop every node this plugin injected. Matched through this plugin's own
|
|
@@ -613,7 +768,7 @@ export default (require) => {
|
|
|
613
768
|
rowActions.delete(row)
|
|
614
769
|
}
|
|
615
770
|
|
|
616
|
-
function injectRowAction(row, node, target, controller, t) {
|
|
771
|
+
function injectRowAction(row, node, target, turnTarget, controller, t) {
|
|
617
772
|
const label = t(`action.tooltip.${target.label}`)
|
|
618
773
|
let entry = rowActions.get(row)
|
|
619
774
|
if (!entry) {
|
|
@@ -622,7 +777,7 @@ export default (require) => {
|
|
|
622
777
|
entry = adoptHost(findOwnedHost(row, false), 'row')
|
|
623
778
|
rowActions.set(row, entry)
|
|
624
779
|
}
|
|
625
|
-
const { host, button } = entry
|
|
780
|
+
const { host, button, turn } = entry
|
|
626
781
|
if (button.getAttribute('aria-label') !== label) {
|
|
627
782
|
button.setAttribute('aria-label', label)
|
|
628
783
|
button.setAttribute('title', label)
|
|
@@ -630,7 +785,25 @@ export default (require) => {
|
|
|
630
785
|
button.onclick = (event) => {
|
|
631
786
|
event.preventDefault()
|
|
632
787
|
event.stopPropagation()
|
|
633
|
-
controller.
|
|
788
|
+
controller.trigger(target)
|
|
789
|
+
}
|
|
790
|
+
// The second action is wired only where a turn target exists (and stays
|
|
791
|
+
// hidden by the row attribute otherwise), so a row can never offer an
|
|
792
|
+
// operation the host would refuse for the wrong reason.
|
|
793
|
+
if (turn !== null && turn !== undefined) {
|
|
794
|
+
const turnLabel = t('action.tooltip.turn')
|
|
795
|
+
if (turn.getAttribute('aria-label') !== turnLabel) {
|
|
796
|
+
turn.setAttribute('aria-label', turnLabel)
|
|
797
|
+
turn.setAttribute('title', turnLabel)
|
|
798
|
+
}
|
|
799
|
+
turn.onclick =
|
|
800
|
+
turnTarget === null || turnTarget === undefined
|
|
801
|
+
? null
|
|
802
|
+
: (event) => {
|
|
803
|
+
event.preventDefault()
|
|
804
|
+
event.stopPropagation()
|
|
805
|
+
controller.trigger(turnTarget)
|
|
806
|
+
}
|
|
634
807
|
}
|
|
635
808
|
const anchor = node.kind === 'user' || node.kind === 'steering' ? row.querySelector('[class*="_actions"]') : null
|
|
636
809
|
if (anchor) {
|
|
@@ -671,7 +844,7 @@ export default (require) => {
|
|
|
671
844
|
button.onclick = (event) => {
|
|
672
845
|
event.preventDefault()
|
|
673
846
|
event.stopPropagation()
|
|
674
|
-
controller.
|
|
847
|
+
controller.trigger(target)
|
|
675
848
|
}
|
|
676
849
|
if (host.parentElement !== think) think.appendChild(host)
|
|
677
850
|
}
|
|
@@ -741,8 +914,14 @@ export default (require) => {
|
|
|
741
914
|
const target = hidden ? null : liveTarget(targetFor(node), view.edits)
|
|
742
915
|
const covered = target !== null || slotCoversRow(node)
|
|
743
916
|
const deletable = !hidden && covered && rowDeletable(node, seqs, view)
|
|
744
|
-
|
|
917
|
+
// The turn-delete entry rides on the same gates as the delete entry: a
|
|
918
|
+
// row whose content already left the context offers neither.
|
|
919
|
+
const turnTarget =
|
|
920
|
+
deletable && turnTargetFor(node) !== null && view.turnSeqs.has(target.seq) ? liveTarget(turnTargetFor(node), view.edits) : null
|
|
921
|
+
if (deletable && target !== null) injectRowAction(row, node, target, turnTarget, controller, t)
|
|
745
922
|
else removeRowAction(row)
|
|
923
|
+
if (deletable && target !== null && turnTarget !== null) row.dataset.dshdtTurn = '1'
|
|
924
|
+
else delete row.dataset.dshdtTurn
|
|
746
925
|
if (!hidden && covered && !rowDeletable(node, seqs, view)) row.dataset.dshdtNoTarget = '1'
|
|
747
926
|
else delete row.dataset.dshdtNoTarget
|
|
748
927
|
}
|
|
@@ -812,7 +991,7 @@ export default (require) => {
|
|
|
812
991
|
'aria-label': label,
|
|
813
992
|
title: label,
|
|
814
993
|
disabled: view.pending,
|
|
815
|
-
onClick: () => controller.
|
|
994
|
+
onClick: () => controller.trigger({ mode: 'reply', messageId }),
|
|
816
995
|
children: jsx(TrashIcon, {}),
|
|
817
996
|
})
|
|
818
997
|
}
|
|
@@ -857,6 +1036,27 @@ export default (require) => {
|
|
|
857
1036
|
})
|
|
858
1037
|
}
|
|
859
1038
|
|
|
1039
|
+
// The plugin page entry (the component row's "configure" arrow, slot
|
|
1040
|
+
// `plugins.row.config`, key `<bundle>#<row id>`). It renders the row's
|
|
1041
|
+
// one-liner for the manager's summary slot and the toggle card for the page.
|
|
1042
|
+
function AskBeforeDeleteOption({ view, t }) {
|
|
1043
|
+
const tr = typeof t === 'function' ? t : (key) => key
|
|
1044
|
+
const [ask, setAsk] = react.useState(getPrefs().ask)
|
|
1045
|
+
react.useEffect(() => subscribePrefs(() => setAsk(getPrefs().ask)), [])
|
|
1046
|
+
if (view === 'summary') return tr(ask ? 'config.summary.ask' : 'config.summary.auto')
|
|
1047
|
+
return jsxs('div', {
|
|
1048
|
+
className: 'dshdt-config',
|
|
1049
|
+
children: [
|
|
1050
|
+
jsx(primitives.Checkbox, {
|
|
1051
|
+
checked: ask,
|
|
1052
|
+
label: tr('config.ask'),
|
|
1053
|
+
onChange: (checked) => setAskBeforeDelete(checked),
|
|
1054
|
+
}),
|
|
1055
|
+
jsx('p', { className: 'dshdt-config-hint', children: tr(ask ? 'config.hint.ask' : 'config.hint.auto') }),
|
|
1056
|
+
],
|
|
1057
|
+
})
|
|
1058
|
+
}
|
|
1059
|
+
|
|
860
1060
|
function OverlayEntry({ useChat, useDeletion, controller, t }) {
|
|
861
1061
|
const snapshot = typeof useChat === 'function' ? useChat((state) => state) : undefined
|
|
862
1062
|
const view = useDeletion((state) => state)
|
|
@@ -951,6 +1151,21 @@ export default (require) => {
|
|
|
951
1151
|
OverlayEntry,
|
|
952
1152
|
),
|
|
953
1153
|
)
|
|
1154
|
+
|
|
1155
|
+
// The plugin's own page in the manager: the row this bundle inserts
|
|
1156
|
+
// (`cordis.patch.yml`, id `dsh-delete-turn`) gains a configure arrow that
|
|
1157
|
+
// opens this card. The key is `<bundle package>#<row id>`, the contract of
|
|
1158
|
+
// ui-plugin-manager's rowConfigKey.
|
|
1159
|
+
ctx.slots.inject('plugins.row.config', () =>
|
|
1160
|
+
ctx.slots.register(
|
|
1161
|
+
{
|
|
1162
|
+
name: 'plugins.row.config',
|
|
1163
|
+
key: 'dsh-delete-turn#dsh-delete-turn',
|
|
1164
|
+
locale: NS,
|
|
1165
|
+
},
|
|
1166
|
+
AskBeforeDeleteOption,
|
|
1167
|
+
),
|
|
1168
|
+
)
|
|
954
1169
|
}
|
|
955
1170
|
|
|
956
1171
|
exports.apply = apply
|
|
@@ -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,
|