@240xu/dsh-message-ops 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/package.json +1 -1
- package/src/client.js +59 -19
package/README.md
CHANGED
|
@@ -172,3 +172,12 @@ MIT
|
|
|
172
172
|
确认弹窗内联语义说明(评审缺口销账)。
|
|
173
173
|
- 官方已有但易混淆的:0.2.0 原生分支按钮(仅限已完成轮次最后一条消息)与
|
|
174
174
|
本插件的「任意位置分支/回滚」互补不冲突。
|
|
175
|
+
|
|
176
|
+
### 0.3.1 · 槽按钮重构(opencode 式一键 + 去重)
|
|
177
|
+
|
|
178
|
+
- **一键立即执行**:回滚/删除点击即生效(无对话框),devkit toast 反馈;宿主
|
|
179
|
+
0.2.0 chat store 原生响应 surface replace——遮蔽即时呈现,无需刷新页面。
|
|
180
|
+
- **与官方去重**:槽内移除分支按钮——官方 0.2.0 已在消息操作行自带分支
|
|
181
|
+
(限已完成轮次最后一条);任意位置分支保留在对话框(官方没有的能力)。
|
|
182
|
+
- **原生观感**:按钮改用官方 primitives 图标(IconClockOutline/IconTrashOutline,
|
|
183
|
+
require 失败回退内联 SVG);S3 命中区 ≥44px;S11 双提交防护(busy 互斥)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@240xu/dsh-message-ops",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Roll back, branch, and export any DSH conversation — from the UI or straight from the agent via a message_ops tool. Non-destructive: logs stay append-only and recoverable. 消息回滚/删除/分支/导出四合一,内置 message_ops agent 工具;非破坏语义,日志只追加可恢复。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/client.js
CHANGED
|
@@ -523,10 +523,10 @@ window.__ModuleLoader__.load({
|
|
|
523
523
|
}
|
|
524
524
|
|
|
525
525
|
// 成功反馈统一出口:devkit 标准 toast 优先,无 devkit 时对话框内文案兜底。
|
|
526
|
-
function notifyDone(msg) {
|
|
526
|
+
function notifyDone(msg, kind) {
|
|
527
527
|
try {
|
|
528
528
|
const dk = window.__dshDevkit
|
|
529
|
-
if (dk && typeof dk.toast === 'function') dk.toast(msg, { kind: 'ok' })
|
|
529
|
+
if (dk && typeof dk.toast === 'function') dk.toast(msg, { kind: kind || 'ok' })
|
|
530
530
|
} catch { /* toast 缺席不阻断成功反馈 */ }
|
|
531
531
|
}
|
|
532
532
|
|
|
@@ -550,6 +550,25 @@ window.__ModuleLoader__.load({
|
|
|
550
550
|
}, React.createElement(BranchIcon))
|
|
551
551
|
}
|
|
552
552
|
|
|
553
|
+
// 官方图标适配:0.2.0 primitives 的 IconClock/IconTrash 与原生操作行同款;
|
|
554
|
+
// require 失败(0.1.x 或裁剪环境)回退内联 SVG,槽在 0.1.x 本就不存在,仅防御。
|
|
555
|
+
var IconClock = null
|
|
556
|
+
var IconTrash = null
|
|
557
|
+
try {
|
|
558
|
+
const P = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
559
|
+
IconClock = P && P.IconClockOutlineRegular
|
|
560
|
+
IconTrash = P && P.IconTrashOutlineRegular
|
|
561
|
+
} catch { /* fallback below */ }
|
|
562
|
+
if (!IconClock) IconClock = function () {
|
|
563
|
+
return React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 16 16', fill: 'none' },
|
|
564
|
+
React.createElement('circle', { cx: 8, cy: 8, r: 6.2, stroke: 'currentColor', strokeWidth: 1.3 }),
|
|
565
|
+
React.createElement('path', { d: 'M8 4.8V8l2.2 1.6', stroke: 'currentColor', strokeWidth: 1.3, strokeLinecap: 'round' }))
|
|
566
|
+
}
|
|
567
|
+
if (!IconTrash) IconTrash = function () {
|
|
568
|
+
return React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 16 16', fill: 'none' },
|
|
569
|
+
React.createElement('path', { d: 'M3 5h10M6.5 5V3.5h3V5M4.5 5l.6 7.5h5.8L11.5 5', stroke: 'currentColor', strokeWidth: 1.3, strokeLinecap: 'round', strokeLinejoin: 'round' }))
|
|
570
|
+
}
|
|
571
|
+
|
|
553
572
|
// --- 0.3.0 assistant-actions 官方槽:每条 AI 消息旁的原生回撤按钮 ------------
|
|
554
573
|
// 契约(dsh-cordis-client-runner):scope session,条目组件收 { messageId } +
|
|
555
574
|
// 标准props(useSessions/sessionId/useSession/useChat…)。0.1.x 无此 key,
|
|
@@ -558,22 +577,21 @@ window.__ModuleLoader__.load({
|
|
|
558
577
|
const CHAT_ACTIONS_ID = 'message-ops-row'
|
|
559
578
|
|
|
560
579
|
function MsgSlotActions(props) {
|
|
561
|
-
const { messageId, sessionId, useSessions
|
|
580
|
+
const { messageId, sessionId, useSessions } = props
|
|
562
581
|
const t = (props && props.t) || __t
|
|
563
582
|
useLocaleRevision()
|
|
564
583
|
const sessions = useSessions ? useSessions((s) => s) : undefined
|
|
565
584
|
const summary = sessions && sessions.byId ? sessions.byId[sessionId] : undefined
|
|
566
585
|
const running = summary ? summary.running === true : false
|
|
567
|
-
// messageId → seq 反查:官方槽只给 messageId;seq 由槽按钮需要。
|
|
568
|
-
// 路径:messages 端点一次性拉取建索引(每会话缓存),失败则按钮隐藏。
|
|
569
586
|
const [seq, setSeq] = React.useState(null)
|
|
587
|
+
const [busy, setBusy] = React.useState(null) // 'revert' | 'delete' | null(S11 双提交防护)
|
|
570
588
|
React.useEffect(() => {
|
|
571
589
|
if (!messageId || !sessionId) return
|
|
572
590
|
let alive = true
|
|
573
591
|
const cached = __seqIndexCache.get(sessionId)
|
|
574
592
|
const resolve = (index) => {
|
|
575
593
|
if (!alive) return
|
|
576
|
-
const hit = index.get(String(messageId))
|
|
594
|
+
const hit = index && index.get(String(messageId))
|
|
577
595
|
if (typeof hit === 'number') setSeq(hit)
|
|
578
596
|
}
|
|
579
597
|
if (cached && typeof cached.then === 'function') cached.then(resolve).catch(() => {})
|
|
@@ -594,20 +612,42 @@ window.__ModuleLoader__.load({
|
|
|
594
612
|
}
|
|
595
613
|
return () => { alive = false }
|
|
596
614
|
}, [messageId, sessionId])
|
|
615
|
+
|
|
616
|
+
// opencode 式一键回撤:点击立即执行(无对话框),成功走 devkit toast。
|
|
617
|
+
// 宿主 0.2.0 chat store 原生响应 surface replace 事件——遮蔽无需刷新页面。
|
|
618
|
+
const run = (mode) => {
|
|
619
|
+
if (busy || seq == null || running) return
|
|
620
|
+
setBusy(mode)
|
|
621
|
+
fetch('/api/message-ops/' + mode, {
|
|
622
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
623
|
+
body: JSON.stringify({ sessionId, seq }),
|
|
624
|
+
})
|
|
625
|
+
.then(async (res) => {
|
|
626
|
+
let data = {}
|
|
627
|
+
try { data = await res.json() } catch { /* keep {} */ }
|
|
628
|
+
if (!res.ok || !data.ok) throw new Error(data.error || ('HTTP ' + res.status))
|
|
629
|
+
notifyDone(t(mode === 'revert' ? 'done.revert' : 'done.delete'))
|
|
630
|
+
})
|
|
631
|
+
.catch((reason) => {
|
|
632
|
+
notifyDone(t('errorPrefix') + (reason && reason.message ? reason.message : String(reason)), 'error')
|
|
633
|
+
})
|
|
634
|
+
.finally(() => setBusy(null))
|
|
635
|
+
}
|
|
636
|
+
|
|
597
637
|
if (seq == null) return null
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
638
|
+
// 官方图标(与原生操作行同款);S3:视觉 16px,命中区 ≥44px。
|
|
639
|
+
const act = (icon, label, mode) => React.createElement('button', {
|
|
640
|
+
type: 'button', title: running ? t('dialog.runningWarn') : t('slot.' + mode),
|
|
641
|
+
'aria-label': t('slot.' + mode), disabled: !!busy || running,
|
|
642
|
+
style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
|
643
|
+
minWidth: 44, minHeight: 44, padding: 0, border: 'none', background: 'transparent',
|
|
644
|
+
color: 'inherit', opacity: busy && busy !== mode ? 0.4 : running ? 0.35 : 0.72,
|
|
645
|
+
cursor: busy || running ? 'default' : 'pointer', borderRadius: 6 },
|
|
646
|
+
onClick: () => run(mode),
|
|
647
|
+
}, icon)
|
|
648
|
+
return React.createElement('span', { style: { display: 'inline-flex', gap: 0 } },
|
|
649
|
+
act(React.createElement(IconClock, { size: 16 }), t('slot.revert'), 'revert'),
|
|
650
|
+
act(React.createElement(IconTrash, { size: 16 }), t('slot.delete'), 'delete'),
|
|
611
651
|
)
|
|
612
652
|
}
|
|
613
653
|
|