opencode-subagent-magazine 1.1.2 → 1.2.0
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 +12 -4
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/index.js +180 -21
- package/dist/tui.js +2038 -0
- package/package.json +6 -3
- package/src/_version.ts +1 -1
- package/src/index.tsx +198 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-subagent-magazine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "OpenCode TUI plugin monitoring sub-agent invocation status in real time",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts"
|
|
12
12
|
},
|
|
13
13
|
"./tui": {
|
|
14
|
-
"import": "./
|
|
14
|
+
"import": "./dist/tui.js",
|
|
15
15
|
"config": {
|
|
16
16
|
"enabled": true
|
|
17
17
|
}
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"opencode-subagent-magazine": "install.mjs"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
|
-
"build": "tsc",
|
|
30
|
+
"build": "tsc && node build.tui.mjs",
|
|
31
|
+
"build:tui": "node build.tui.mjs",
|
|
31
32
|
"typecheck": "tsc --noEmit",
|
|
32
33
|
"version": "node -e \"require('fs').writeFileSync('src/_version.ts','// auto-generated\\nexport const PLUGIN_VERSION='+JSON.stringify(require('./package.json').version)+';\\n')\"",
|
|
33
34
|
"prepublishOnly": "tsc"
|
|
@@ -54,6 +55,8 @@
|
|
|
54
55
|
"devDependencies": {
|
|
55
56
|
"@opencode-ai/plugin": "^1.14.50",
|
|
56
57
|
"@opencode-ai/sdk": "^1.14.50",
|
|
58
|
+
"esbuild": "^0.25.0",
|
|
59
|
+
"esbuild-plugin-solid": "^0.5.0",
|
|
57
60
|
"tsx": "^4.22.3",
|
|
58
61
|
"typescript": "^5.8.0"
|
|
59
62
|
}
|
package/src/_version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// auto-generated
|
|
2
|
-
export const PLUGIN_VERSION="1.
|
|
2
|
+
export const PLUGIN_VERSION="1.2.0";
|
package/src/index.tsx
CHANGED
|
@@ -45,6 +45,8 @@ interface SubEntry {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
type Lang = "zh" | "en"
|
|
48
|
+
type SortOrder = "desc" | "asc"
|
|
49
|
+
type ScrollMode = "wheel" | "click"
|
|
48
50
|
|
|
49
51
|
/** OpenCode built-in tool names that spawn sub-agents or delegate tasks. */
|
|
50
52
|
const SUBAGENT_TOOLS = new Set(["task", "delegate", "call_omo_agent"])
|
|
@@ -68,10 +70,15 @@ const I18N: Record<Lang, Record<string, string>> = {
|
|
|
68
70
|
"cost.label": "费用",
|
|
69
71
|
"scroll.more": "更多",
|
|
70
72
|
"scroll.top": "回顶",
|
|
73
|
+
"scroll.bottom": "回底",
|
|
71
74
|
"dismiss.label": "标记完成",
|
|
72
75
|
"status.running": "运行中",
|
|
73
76
|
"status.done": "已完成",
|
|
74
77
|
"status.error": "错误",
|
|
78
|
+
"order.desc": "降序(最新在前)",
|
|
79
|
+
"order.asc": "升序(最早在前)",
|
|
80
|
+
"scroll.wheel": "滚轮翻页",
|
|
81
|
+
"scroll.click": "点击翻页",
|
|
75
82
|
},
|
|
76
83
|
en: {
|
|
77
84
|
"panel.title": "SubAgent",
|
|
@@ -87,10 +94,15 @@ const I18N: Record<Lang, Record<string, string>> = {
|
|
|
87
94
|
"cost.label": "cost",
|
|
88
95
|
"scroll.more": "more",
|
|
89
96
|
"scroll.top": "Top",
|
|
97
|
+
"scroll.bottom": "Bottom",
|
|
90
98
|
"dismiss.label": "dismiss",
|
|
91
99
|
"status.running": "running",
|
|
92
100
|
"status.done": "done",
|
|
93
101
|
"status.error": "error",
|
|
102
|
+
"order.desc": "Desc (newest first)",
|
|
103
|
+
"order.asc": "Asc (oldest first)",
|
|
104
|
+
"scroll.wheel": "Wheel Scroll",
|
|
105
|
+
"scroll.click": "Click Scroll",
|
|
94
106
|
},
|
|
95
107
|
}
|
|
96
108
|
|
|
@@ -247,8 +259,7 @@ function safeErrorMsg(err: unknown): string {
|
|
|
247
259
|
// Sidebar component
|
|
248
260
|
// ===================================================================
|
|
249
261
|
|
|
250
|
-
//
|
|
251
|
-
// 当用户在子 session 内部时,handleSessionEnd 可通过此缓存找到父 session 的 entries。
|
|
262
|
+
// 模块级缓存:各 session 的 entry 状态独立存储,不随当前视图切换而清除。
|
|
252
263
|
const globalEntryCache = new Map<string, Map<string, SubEntry>>()
|
|
253
264
|
|
|
254
265
|
function SubAgentPanel(props: {
|
|
@@ -256,6 +267,8 @@ function SubAgentPanel(props: {
|
|
|
256
267
|
api: TuiPluginApi
|
|
257
268
|
lang: () => Lang
|
|
258
269
|
maxEntries: () => number
|
|
270
|
+
sortOrder: () => SortOrder
|
|
271
|
+
scrollMode: () => ScrollMode
|
|
259
272
|
sessionId: string
|
|
260
273
|
}): JSX.Element {
|
|
261
274
|
const t = (key: string) => I18N[props.lang()][key] ?? key
|
|
@@ -347,8 +360,8 @@ function SubAgentPanel(props: {
|
|
|
347
360
|
setEntryMapRaw((prev) => {
|
|
348
361
|
const next = typeof arg === "function" ? (arg as Function)(prev) : arg
|
|
349
362
|
|
|
350
|
-
//
|
|
351
|
-
//
|
|
363
|
+
// entry 状态落定(done/error)时立即持久化到 KV,跳过常规 debounce,
|
|
364
|
+
// 确保跨视图的状态一致性。
|
|
352
365
|
let needsImmediateFlush = false
|
|
353
366
|
for (const [id, entry] of next) {
|
|
354
367
|
const prevEntry = prev.get(id)
|
|
@@ -369,7 +382,7 @@ function SubAgentPanel(props: {
|
|
|
369
382
|
persistEntries(props.sessionId, next)
|
|
370
383
|
}
|
|
371
384
|
|
|
372
|
-
//
|
|
385
|
+
// 同步到模块级缓存,供其他视图读取当前 session 的最新状态
|
|
373
386
|
globalEntryCache.set(props.sessionId, new Map(next))
|
|
374
387
|
|
|
375
388
|
return next
|
|
@@ -386,6 +399,8 @@ function SubAgentPanel(props: {
|
|
|
386
399
|
const [hoveredOpen, setHoveredOpen] = createSignal<string | undefined>(undefined)
|
|
387
400
|
const [hoveredDismiss, setHoveredDismiss] = createSignal<string | undefined>(undefined)
|
|
388
401
|
const [hoveredTop, setHoveredTop] = createSignal(false)
|
|
402
|
+
const [hoveredMoreAbove, setHoveredMoreAbove] = createSignal(false)
|
|
403
|
+
const [hoveredMoreBelow, setHoveredMoreBelow] = createSignal(false)
|
|
389
404
|
const [scrollOffset, setScrollOffset] = createSignal(
|
|
390
405
|
(() => { try { return loadSessionData()[props.sessionId]?.scroll ?? 0 } catch { return 0 } })()
|
|
391
406
|
)
|
|
@@ -723,14 +738,13 @@ function SubAgentPanel(props: {
|
|
|
723
738
|
return changed ? next : prev
|
|
724
739
|
})
|
|
725
740
|
|
|
726
|
-
//
|
|
727
|
-
//
|
|
728
|
-
// 通过全局缓存(切换前保留的父 session entries)查找并更新,再同步回 KV。
|
|
741
|
+
// 当子代理所属的父 session 与当前视图不同时,通过模块级缓存定位
|
|
742
|
+
// 并更新父 session 的 entry 状态,随后写回 KV。
|
|
729
743
|
try {
|
|
730
744
|
const sessionObj = props.api.state.session.get(sid)
|
|
731
745
|
const parentSid = sessionObj?.parentID
|
|
732
746
|
if (parentSid && parentSid !== props.sessionId) {
|
|
733
|
-
//
|
|
747
|
+
// 优先从模块级缓存获取父 session 的 entries,不受当前视图切换影响
|
|
734
748
|
const parentCache = globalEntryCache.get(parentSid)
|
|
735
749
|
const nowTs = Date.now()
|
|
736
750
|
let found = false
|
|
@@ -739,7 +753,7 @@ function SubAgentPanel(props: {
|
|
|
739
753
|
found = tryMatchAndUpdate(parentCache, sid, status, nowTs)
|
|
740
754
|
}
|
|
741
755
|
|
|
742
|
-
// 缓存未命中时回退到 KV
|
|
756
|
+
// 缓存未命中时回退到 KV 读取
|
|
743
757
|
if (!found) {
|
|
744
758
|
const data = loadSessionData()
|
|
745
759
|
const rec = data[parentSid]
|
|
@@ -747,7 +761,7 @@ function SubAgentPanel(props: {
|
|
|
747
761
|
const fallbackMap = new Map(rec.entries.map((e: SubEntry) => [e.id, e]))
|
|
748
762
|
found = tryMatchAndUpdate(fallbackMap, sid, status, nowTs)
|
|
749
763
|
if (found) {
|
|
750
|
-
//
|
|
764
|
+
// 回退命中后写入 KV 并回填缓存
|
|
751
765
|
data[parentSid] = { ...rec, ts: nowTs, entries: [...fallbackMap.values()] }
|
|
752
766
|
saveSessionData(data)
|
|
753
767
|
globalEntryCache.set(parentSid, fallbackMap)
|
|
@@ -755,7 +769,7 @@ function SubAgentPanel(props: {
|
|
|
755
769
|
}
|
|
756
770
|
}
|
|
757
771
|
|
|
758
|
-
//
|
|
772
|
+
// 将模块级缓存中的最新状态同步到 KV
|
|
759
773
|
if (found && parentCache) {
|
|
760
774
|
const data = loadSessionData()
|
|
761
775
|
data[parentSid] = { ...data[parentSid], ts: nowTs, entries: [...parentCache.values()] }
|
|
@@ -878,7 +892,7 @@ function SubAgentPanel(props: {
|
|
|
878
892
|
// scan uses setEntryMapRaw — ephemeral data, not persisted to kv.
|
|
879
893
|
// Only event-driven changes (handlePartUpdated, handleSessionEnd) persist.
|
|
880
894
|
setEntryMapRaw((prev) => {
|
|
881
|
-
//
|
|
895
|
+
// 优先从模块级缓存加载,KV 仅作缓存未命中时的回退
|
|
882
896
|
const next = switched
|
|
883
897
|
? new Map(globalEntryCache.get(sid) ?? loadEntries(sid))
|
|
884
898
|
: new Map(prev)
|
|
@@ -1022,7 +1036,11 @@ function SubAgentPanel(props: {
|
|
|
1022
1036
|
// ── derived signals ──
|
|
1023
1037
|
// Stable list — only changes when entryMap changes
|
|
1024
1038
|
const entryList = createMemo(() => {
|
|
1025
|
-
|
|
1039
|
+
const entries = [...entryMap().values()]
|
|
1040
|
+
if (props.sortOrder() === "desc") {
|
|
1041
|
+
return entries.sort((a, b) => b.startedAt - a.startedAt)
|
|
1042
|
+
}
|
|
1043
|
+
return entries.sort((a, b) => a.startedAt - b.startedAt)
|
|
1026
1044
|
})
|
|
1027
1045
|
|
|
1028
1046
|
const max = props.maxEntries
|
|
@@ -1036,6 +1054,48 @@ function SubAgentPanel(props: {
|
|
|
1036
1054
|
const hiddenAbove = createMemo(() => clampedOffset())
|
|
1037
1055
|
const hiddenBelow = createMemo(() => Math.max(0, entryList().length - clampedOffset() - max()))
|
|
1038
1056
|
|
|
1057
|
+
// Drop hover state when ↑ more disappears (hiddenAbove hits zero)
|
|
1058
|
+
createEffect(() => {
|
|
1059
|
+
if (hiddenAbove() === 0) setHoveredMoreAbove(false)
|
|
1060
|
+
})
|
|
1061
|
+
|
|
1062
|
+
// Reset scroll on sort order change: jump to newest in view
|
|
1063
|
+
let sortInitialized = false
|
|
1064
|
+
createEffect(() => {
|
|
1065
|
+
props.sortOrder()
|
|
1066
|
+
if (!sortInitialized) { sortInitialized = true; return }
|
|
1067
|
+
const total = untrack(() => entryList().length)
|
|
1068
|
+
const m = untrack(() => max())
|
|
1069
|
+
const target = props.sortOrder() === "desc" ? 0 : Math.max(0, total - m)
|
|
1070
|
+
setScrollOffset(target)
|
|
1071
|
+
setTimeout(() => {
|
|
1072
|
+
try { persistScroll(props.sessionId, target) } catch {}
|
|
1073
|
+
}, 0)
|
|
1074
|
+
})
|
|
1075
|
+
|
|
1076
|
+
// When new entries arrive while viewing the newest end, keep the view at newest
|
|
1077
|
+
let prevEntryCount = 0
|
|
1078
|
+
createEffect(() => {
|
|
1079
|
+
const total = entryList().length
|
|
1080
|
+
if (prevEntryCount === 0) { prevEntryCount = total; return }
|
|
1081
|
+
if (total === prevEntryCount) return
|
|
1082
|
+
|
|
1083
|
+
const m = max()
|
|
1084
|
+
const wasAtNewest = props.sortOrder() === "desc"
|
|
1085
|
+
? untrack(() => scrollOffset() === 0)
|
|
1086
|
+
: untrack(() => scrollOffset() >= prevEntryCount - m)
|
|
1087
|
+
|
|
1088
|
+
prevEntryCount = total
|
|
1089
|
+
|
|
1090
|
+
if (wasAtNewest) {
|
|
1091
|
+
const target = props.sortOrder() === "desc" ? 0 : Math.max(0, total - m)
|
|
1092
|
+
setScrollOffset(target)
|
|
1093
|
+
setTimeout(() => {
|
|
1094
|
+
try { persistScroll(props.sessionId, target) } catch {}
|
|
1095
|
+
}, 0)
|
|
1096
|
+
}
|
|
1097
|
+
})
|
|
1098
|
+
|
|
1039
1099
|
const entries = createMemo(() => {
|
|
1040
1100
|
const nowVal = now()
|
|
1041
1101
|
return entryList().map((e) => ({
|
|
@@ -1195,6 +1255,7 @@ function SubAgentPanel(props: {
|
|
|
1195
1255
|
>
|
|
1196
1256
|
<box
|
|
1197
1257
|
onMouseScroll={(e) => {
|
|
1258
|
+
if (props.scrollMode() === "click") return
|
|
1198
1259
|
const total = entryList().length
|
|
1199
1260
|
const m = max()
|
|
1200
1261
|
if (total <= m) return
|
|
@@ -1207,8 +1268,28 @@ function SubAgentPanel(props: {
|
|
|
1207
1268
|
}}
|
|
1208
1269
|
>
|
|
1209
1270
|
<Show when={hiddenAbove() > 0}>
|
|
1210
|
-
<text
|
|
1211
|
-
{
|
|
1271
|
+
<text
|
|
1272
|
+
onMouseOver={() => setHoveredMoreAbove(true)}
|
|
1273
|
+
onMouseOut={() => setHoveredMoreAbove(false)}
|
|
1274
|
+
onMouseUp={() => {
|
|
1275
|
+
const total = entryList().length
|
|
1276
|
+
const m = max()
|
|
1277
|
+
if (total <= m) return
|
|
1278
|
+
const next = Math.max(0, scrollOffset() - m)
|
|
1279
|
+
if (next === 0) {
|
|
1280
|
+
setTimeout(() => {
|
|
1281
|
+
setScrollOffset(next)
|
|
1282
|
+
try { persistScroll(props.sessionId, next) } catch {}
|
|
1283
|
+
}, 0)
|
|
1284
|
+
} else {
|
|
1285
|
+
setScrollOffset(next)
|
|
1286
|
+
try { persistScroll(props.sessionId, next) } catch {}
|
|
1287
|
+
}
|
|
1288
|
+
}}
|
|
1289
|
+
>
|
|
1290
|
+
<span style={{ fg: hoveredMoreAbove() ? pal().warning : pal().muted }}>
|
|
1291
|
+
{" "}↑ {hiddenAbove()} {t("scroll.more")}
|
|
1292
|
+
</span>
|
|
1212
1293
|
</text>
|
|
1213
1294
|
</Show>
|
|
1214
1295
|
<For each={visibleList()}>
|
|
@@ -1396,23 +1477,52 @@ function SubAgentPanel(props: {
|
|
|
1396
1477
|
)
|
|
1397
1478
|
}}
|
|
1398
1479
|
</For>
|
|
1399
|
-
<Show when={hiddenBelow() > 0 || scrollOffset() > 0}>
|
|
1480
|
+
<Show when={hiddenBelow() > 0 || (props.sortOrder() === "desc" ? scrollOffset() > 0 : entryList().length > max() && clampedOffset() < entryList().length - max())}>
|
|
1400
1481
|
{(() => {
|
|
1401
1482
|
const showMore = hiddenBelow() > 0
|
|
1402
|
-
const showTop =
|
|
1483
|
+
const showTop = props.sortOrder() === "desc"
|
|
1484
|
+
? scrollOffset() > 0
|
|
1485
|
+
: entryList().length > max() && clampedOffset() < entryList().length - max()
|
|
1403
1486
|
const left = showMore ? ` \u2193 ${hiddenBelow()} ${t("scroll.more")}` : " "
|
|
1404
|
-
const right =
|
|
1487
|
+
const right = props.sortOrder() === "desc"
|
|
1488
|
+
? `\u2191 ${t("scroll.top")}`
|
|
1489
|
+
: `\u2193 ${t("scroll.bottom")}`
|
|
1405
1490
|
const pad = showTop ? Math.max(1, panelWidth() - visualWidth(left) - visualWidth(right)) : 0
|
|
1406
1491
|
return (
|
|
1407
1492
|
<box flexDirection="row">
|
|
1408
|
-
<text
|
|
1493
|
+
<text
|
|
1494
|
+
onMouseOver={() => showMore && setHoveredMoreBelow(true)}
|
|
1495
|
+
onMouseOut={() => setHoveredMoreBelow(false)}
|
|
1496
|
+
onMouseUp={() => {
|
|
1497
|
+
if (!showMore) return
|
|
1498
|
+
const total = entryList().length
|
|
1499
|
+
const m = max()
|
|
1500
|
+
if (total <= m) return
|
|
1501
|
+
setScrollOffset((prev) => Math.min(total - m, prev + m))
|
|
1502
|
+
try { persistScroll(props.sessionId, scrollOffset()) } catch {}
|
|
1503
|
+
setHoveredMoreBelow(false)
|
|
1504
|
+
}}
|
|
1505
|
+
>
|
|
1506
|
+
<span style={{ fg: showMore && hoveredMoreBelow() ? pal().warning : pal().muted }}>
|
|
1507
|
+
{left}
|
|
1508
|
+
</span>
|
|
1509
|
+
</text>
|
|
1409
1510
|
{showTop ? (
|
|
1410
1511
|
<>
|
|
1411
1512
|
<text style={{ fg: pal().muted }}>{" ".repeat(pad)}</text>
|
|
1412
1513
|
<text
|
|
1413
1514
|
onMouseOver={() => setHoveredTop(true)}
|
|
1414
1515
|
onMouseOut={() => setHoveredTop(false)}
|
|
1415
|
-
onMouseUp={() => {
|
|
1516
|
+
onMouseUp={() => {
|
|
1517
|
+
const total = entryList().length
|
|
1518
|
+
const m = max()
|
|
1519
|
+
if (props.sortOrder() === "desc") {
|
|
1520
|
+
setScrollOffset(0)
|
|
1521
|
+
} else {
|
|
1522
|
+
setScrollOffset(Math.max(0, total - m))
|
|
1523
|
+
}
|
|
1524
|
+
setHoveredTop(false)
|
|
1525
|
+
}}
|
|
1416
1526
|
>
|
|
1417
1527
|
<span style={{ fg: hoveredTop() ? pal().warning : pal().muted }}>{right}</span>
|
|
1418
1528
|
</text>
|
|
@@ -1438,6 +1548,10 @@ interface SharedSignals {
|
|
|
1438
1548
|
setLang: (l: Lang) => void
|
|
1439
1549
|
maxEntries: () => number
|
|
1440
1550
|
setMaxEntries: (n: number) => void
|
|
1551
|
+
sortOrder: () => SortOrder
|
|
1552
|
+
setSortOrder: (o: SortOrder) => void
|
|
1553
|
+
scrollMode: () => ScrollMode
|
|
1554
|
+
setScrollMode: (m: ScrollMode) => void
|
|
1441
1555
|
sessionId: string
|
|
1442
1556
|
}
|
|
1443
1557
|
|
|
@@ -1453,6 +1567,8 @@ function createSidebarSlot(api: TuiPluginApi, sig: SharedSignals): TuiSlotPlugin
|
|
|
1453
1567
|
api={api}
|
|
1454
1568
|
lang={sig.lang}
|
|
1455
1569
|
maxEntries={sig.maxEntries}
|
|
1570
|
+
sortOrder={sig.sortOrder}
|
|
1571
|
+
scrollMode={sig.scrollMode}
|
|
1456
1572
|
sessionId={input.session_id}
|
|
1457
1573
|
/>
|
|
1458
1574
|
)
|
|
@@ -1472,8 +1588,14 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1472
1588
|
const [maxEntries, setMaxEntries] = createSignal(
|
|
1473
1589
|
parseInt(String(api.kv.get(`${KV_PREFIX}.max_entries`, "10")), 10) || 10
|
|
1474
1590
|
)
|
|
1591
|
+
const [sortOrder, setSortOrder] = createSignal<SortOrder>(
|
|
1592
|
+
String(api.kv.get(`${KV_PREFIX}.order`, "desc")) === "asc" ? "asc" : "desc"
|
|
1593
|
+
)
|
|
1594
|
+
const [scrollMode, setScrollMode] = createSignal<ScrollMode>(
|
|
1595
|
+
String(api.kv.get(`${KV_PREFIX}.scroll_mode`, "wheel")) === "click" ? "click" : "wheel"
|
|
1596
|
+
)
|
|
1475
1597
|
|
|
1476
|
-
const signals: SharedSignals = { lang, setLang, maxEntries, setMaxEntries, sessionId: "" }
|
|
1598
|
+
const signals: SharedSignals = { lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, sessionId: "" }
|
|
1477
1599
|
|
|
1478
1600
|
api.slots.register(createSidebarSlot(api, signals))
|
|
1479
1601
|
|
|
@@ -1505,6 +1627,60 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1505
1627
|
))
|
|
1506
1628
|
},
|
|
1507
1629
|
},
|
|
1630
|
+
{
|
|
1631
|
+
title: "SubAgent Magazine: Sort Order",
|
|
1632
|
+
value: "subagent-order",
|
|
1633
|
+
description: "Set sub-agent entry sort order (desc / asc)",
|
|
1634
|
+
slash: { name: "subagent-order" },
|
|
1635
|
+
onSelect: (dialog) => {
|
|
1636
|
+
const t = (k: string) => I18N[lang()][k] ?? k
|
|
1637
|
+
dialog?.replace(() => (
|
|
1638
|
+
<api.ui.DialogSelect
|
|
1639
|
+
title="Sort Order / 排序方式"
|
|
1640
|
+
options={[
|
|
1641
|
+
{ title: t("order.desc"), value: "desc" },
|
|
1642
|
+
{ title: t("order.asc"), value: "asc" },
|
|
1643
|
+
]}
|
|
1644
|
+
onSelect={(opt) => {
|
|
1645
|
+
const o = opt.value as SortOrder
|
|
1646
|
+
setSortOrder(o)
|
|
1647
|
+
api.kv.set(`${KV_PREFIX}.order`, o)
|
|
1648
|
+
api.ui.toast({
|
|
1649
|
+
message: o === "desc" ? t("order.desc") : t("order.asc"),
|
|
1650
|
+
})
|
|
1651
|
+
dialog?.clear()
|
|
1652
|
+
}}
|
|
1653
|
+
/>
|
|
1654
|
+
))
|
|
1655
|
+
},
|
|
1656
|
+
},
|
|
1657
|
+
{
|
|
1658
|
+
title: "SubAgent Magazine: Scroll Mode",
|
|
1659
|
+
value: "subagent-scroll",
|
|
1660
|
+
description: "Set scroll mode (wheel / click)",
|
|
1661
|
+
slash: { name: "subagent-scroll" },
|
|
1662
|
+
onSelect: (dialog) => {
|
|
1663
|
+
const t = (k: string) => I18N[lang()][k] ?? k
|
|
1664
|
+
dialog?.replace(() => (
|
|
1665
|
+
<api.ui.DialogSelect
|
|
1666
|
+
title="Scroll Mode / 滚动模式"
|
|
1667
|
+
options={[
|
|
1668
|
+
{ title: t("scroll.wheel"), value: "wheel" },
|
|
1669
|
+
{ title: t("scroll.click"), value: "click" },
|
|
1670
|
+
]}
|
|
1671
|
+
onSelect={(opt) => {
|
|
1672
|
+
const m = opt.value as ScrollMode
|
|
1673
|
+
setScrollMode(m)
|
|
1674
|
+
api.kv.set(`${KV_PREFIX}.scroll_mode`, m)
|
|
1675
|
+
api.ui.toast({
|
|
1676
|
+
message: m === "wheel" ? t("scroll.wheel") : t("scroll.click"),
|
|
1677
|
+
})
|
|
1678
|
+
dialog?.clear()
|
|
1679
|
+
}}
|
|
1680
|
+
/>
|
|
1681
|
+
))
|
|
1682
|
+
},
|
|
1683
|
+
},
|
|
1508
1684
|
{
|
|
1509
1685
|
title: "SubAgent Magazine: Max Entries",
|
|
1510
1686
|
value: "subagent-max",
|