nx-kv 0.6.0 → 0.7.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/CHANGELOG.md +16 -0
- package/README.md +3 -2
- package/assets/kv-sessiontask/SKILL.md +7 -1
- package/assets/kv-sessiontask/references/sessiontask-workflow.md +18 -2
- package/package.json +1 -1
- package/src/modules/sessiontask/index.js +46 -0
- package/src/modules/sessiontask/service.js +83 -1
- package/src/modules/sessiontask/view.jsx +133 -20
- package/src/web/public/assets/{CliHints-uNZoZkhO.js → CliHints-BcGuVv3q.js} +1 -1
- package/src/web/public/assets/{index-DDDim1NE.js → index-BC4hXOgL.js} +2 -2
- package/src/web/public/assets/{view-DqK-OdJn.js → view-Bq7gBnSO.js} +1 -1
- package/src/web/public/assets/view-DpLqgnYO.js +1 -0
- package/src/web/public/assets/{view-D7T4AoiM.js → view-Xx2TV723.js} +1 -1
- package/src/web/public/assets/{view-CVVca46Z.js → view-hrrRARfz.js} +1 -1
- package/src/web/public/index.html +1 -1
- package/src/web/public/assets/view-DpGJgXe_.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录对外可见的变更。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。
|
|
4
4
|
|
|
5
|
+
## [0.7.0] - 2026-10-09
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **支持删除整个主题与批量删除**:
|
|
10
|
+
- 「实时任务」页主题卡片右下角新增「删除主题」,一次删除该主题及其全部任务;
|
|
11
|
+
- 卡片左上角可勾选(总览标题处支持「全选」),顶部「批量删除 (N)」一键删除多个主题。
|
|
12
|
+
- 新增 CLI/HTTP:
|
|
13
|
+
- `nx-kv sessiontask topic remove --cwd "<目录>"`(或 `--key`)/ `DELETE /api/sessiontasks/topic`;
|
|
14
|
+
- `nx-kv sessiontask topic remove-many --keys "<k,k>"`(也可 `--cwds` 或 `--all`)
|
|
15
|
+
/ `POST /api/sessiontasks/topics/remove`。
|
|
16
|
+
- 安全保护:等待中的主题默认不删(避免阻塞的 agent 干等到超时),界面二次确认、
|
|
17
|
+
命令加 `--force` 才连等待中一起删;批量返回 `{count,removed,skipped}`,skipped
|
|
18
|
+
列出被跳过的「等待中 / 不存在」主题。
|
|
19
|
+
- 新增删除/批量删除单测与冒烟断言。
|
|
20
|
+
|
|
5
21
|
## [0.6.0] - 2026-10-08
|
|
6
22
|
|
|
7
23
|
### Added
|
package/README.md
CHANGED
|
@@ -81,8 +81,9 @@ nx-kv sessiontask wait --summary "<当前现状总结>" --json
|
|
|
81
81
|
新等待点。弹窗/面板顶部直接显示现状,用户不读对话也能快速了解、点选等待点
|
|
82
82
|
**分别回填(分散答复)**,也可提前排队;等待点非空时 wait 立即取走、不开窗。
|
|
83
83
|
纯本机协调、存本地文件,**不需登录后端**。其余命令:
|
|
84
|
-
`sessiontask list / add / get / update / remove
|
|
85
|
-
|
|
84
|
+
`sessiontask list / add / get / update / remove`,以及主题级
|
|
85
|
+
`sessiontask topic remove`(删整个主题)与 `sessiontask topic remove-many`
|
|
86
|
+
(批量删除,`--keys/--cwds/--all`;等待中需 `--force`),面板上对应「实时任务」页。
|
|
86
87
|
|
|
87
88
|
配套 skill:
|
|
88
89
|
|
|
@@ -77,9 +77,15 @@ nx-kv sessiontask list # 等待点总览
|
|
|
77
77
|
nx-kv sessiontask add "<回填内容>" --summary "<现状>" # 针对现状排队/投递
|
|
78
78
|
nx-kv sessiontask get --id <n> --summary "<现状>" # 查看单条
|
|
79
79
|
nx-kv sessiontask update --id <n> --text "<新内容>" --summary "<现状>"
|
|
80
|
-
nx-kv sessiontask remove --id <n> --summary "<现状>" #
|
|
80
|
+
nx-kv sessiontask remove --id <n> --summary "<现状>" # 删除单条任务
|
|
81
|
+
nx-kv sessiontask topic remove --cwd "<工作目录>" # 删除整个主题(等待中需 --force)
|
|
82
|
+
nx-kv sessiontask topic remove-many --keys "<key,key>" # 批量删除主题(或 --cwds "<dir,dir>",或 --all)
|
|
81
83
|
```
|
|
82
84
|
|
|
85
|
+
> 删除主题:面板「主题总览」每张卡有「删除主题」,顶部勾选后可「批量删除」。
|
|
86
|
+
> 等待中的主题默认不删(避免让阻塞的 agent 干等),确认要删会二次提示并以
|
|
87
|
+
> `--force` 强删、agent 随后超时收尾。
|
|
88
|
+
|
|
83
89
|
常驻面板(`nx-kv serve`)的「实时任务」页就是等待点总览,可直接点选回填;
|
|
84
90
|
wait 同样能取走(跨进程由轮询兜底)。
|
|
85
91
|
|
|
@@ -103,7 +103,23 @@ wait 必带一句当前现状总结,它被归一化(折叠首尾/连续空
|
|
|
103
103
|
各现状下,而不是挤在一个队列。
|
|
104
104
|
- agent 用对应现状调 wait 时只取该等待点的任务,互不串。
|
|
105
105
|
|
|
106
|
-
##
|
|
106
|
+
## 七、删除主题与批量删除
|
|
107
|
+
|
|
108
|
+
主题(工作目录)可整体删除,不必逐条删任务:
|
|
109
|
+
|
|
110
|
+
- **单个删除**:
|
|
111
|
+
- 面板:主题卡片右下角「删除主题」;
|
|
112
|
+
- CLI:`nx-kv sessiontask topic remove --cwd "<工作目录>"`(或 `--key <key>`)。
|
|
113
|
+
- **批量删除**:
|
|
114
|
+
- 面板:勾选卡片左上角(或总览标题处「全选」)→「批量删除 (N)」;
|
|
115
|
+
- CLI:`nx-kv sessiontask topic remove-many --keys "<key,key>"`,也可
|
|
116
|
+
`--cwds "<dir,dir>"`,或 `--all` 清空。
|
|
117
|
+
- **安全保护**:等待中的主题默认不删(删了阻塞的 agent 只能等到超时);界面会
|
|
118
|
+
二次确认,命令需加 `--force` 才连等待中一起删,agent 随后超时收尾。
|
|
119
|
+
- **返回**:单个 `{status:"removed",topic}`;批量 `{count,removed,skipped}`,
|
|
120
|
+
skipped 列出被跳过的「等待中 / 不存在」主题。
|
|
121
|
+
|
|
122
|
+
## 八、常见错误与避坑
|
|
107
123
|
|
|
108
124
|
| 错误做法 | 后果 | 正确做法 |
|
|
109
125
|
| --- | --- | --- |
|
|
@@ -117,7 +133,7 @@ wait 必带一句当前现状总结,它被归一化(折叠首尾/连续空
|
|
|
117
133
|
| 同时开多个 wait(同一等待点) | 只有一个收到回填,另一个等到超时 | 同一等待点同一时刻只调一个 wait |
|
|
118
134
|
| 没用 --json | 输出人读文本,宿主难以可靠解析 | agent 调用一律加 `--json` |
|
|
119
135
|
|
|
120
|
-
##
|
|
136
|
+
## 九、与常驻面板配合 / 排障
|
|
121
137
|
|
|
122
138
|
- 已 `nx-kv serve` 打开面板时,直接在「等待点总览」点选现状回填;正在阻塞的
|
|
123
139
|
wait 通过轮询(约每 0.5 秒)取走,不依赖弹窗本身。
|
package/package.json
CHANGED
|
@@ -60,6 +60,21 @@ function renderWait(r) {
|
|
|
60
60
|
);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// 批量删除的人读输出:删除清单 + 跳过清单(等待中/不存在)
|
|
64
|
+
function renderRemoveMany(d) {
|
|
65
|
+
const lines = [d.all ? `清空:删除 ${d.count} 个主题` : `已删除 ${d.count} 个主题`];
|
|
66
|
+
for (const r of d.removed) lines.push(` - ${r.name}${r.cwd ? ` ${r.cwd}` : ''}`);
|
|
67
|
+
if (d.skipped.length) {
|
|
68
|
+
lines.push(`跳过 ${d.skipped.length} 个:`);
|
|
69
|
+
for (const s of d.skipped) {
|
|
70
|
+
const why = s.reason === 'waiting' ? '等待中' : '不存在';
|
|
71
|
+
lines.push(` - [${why}] ${s.name || s.key}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!d.removed.length && !d.skipped.length) lines.push('(没有匹配的主题)');
|
|
75
|
+
return lines.join('\n');
|
|
76
|
+
}
|
|
77
|
+
|
|
63
78
|
export default {
|
|
64
79
|
id: 'sessiontask',
|
|
65
80
|
title: '实时任务(= Web「实时任务」页)',
|
|
@@ -121,6 +136,37 @@ export default {
|
|
|
121
136
|
render: (d) => `已删除主题「${d.name}」#${d.removed.id}: ${d.removed.text}`,
|
|
122
137
|
},
|
|
123
138
|
|
|
139
|
+
// ─── 主题删除(单个 / 批量)──────────────────────────────
|
|
140
|
+
{
|
|
141
|
+
id: 'sessiontask.topic.remove',
|
|
142
|
+
cli: ['sessiontask', 'topic', 'remove'],
|
|
143
|
+
http: ['DELETE', '/api/sessiontasks/topic'],
|
|
144
|
+
summary: '删除整个主题(含全部任务;按 --cwd 或 --key,等待中需 --force)',
|
|
145
|
+
flags: {
|
|
146
|
+
cwd: { type: 'string', hint: '工作目录(与 --key 二选一)' },
|
|
147
|
+
key: { type: 'string', hint: '主题 key(与 --cwd 二选一)' },
|
|
148
|
+
force: { type: 'boolean', hint: '连等待中的主题也删(agent 将超时收尾)' },
|
|
149
|
+
},
|
|
150
|
+
run: (ctx) => service.removeTopic({ cwd: ctx.cwd, key: ctx.key, force: ctx.force }),
|
|
151
|
+
render: (d) =>
|
|
152
|
+
`已删除主题「${d.name}」(待领取/共 ${d.topic.pending}/${d.topic.total} 条任务一并删除)`,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'sessiontask.topic.removeMany',
|
|
156
|
+
cli: ['sessiontask', 'topic', 'remove-many'],
|
|
157
|
+
http: ['POST', '/api/sessiontasks/topics/remove'],
|
|
158
|
+
summary: '批量删除主题(--keys/--cwds 逗号分隔,或 --all;等待中默认跳过,--force 连等待中删)',
|
|
159
|
+
flags: {
|
|
160
|
+
keys: { type: 'array', hint: '主题 key,逗号分隔' },
|
|
161
|
+
cwds: { type: 'array', hint: '工作目录,逗号分隔' },
|
|
162
|
+
all: { type: 'boolean', hint: '清空全部主题(等待中仍跳过,除非 --force)' },
|
|
163
|
+
force: { type: 'boolean', hint: '连等待中的主题一起删' },
|
|
164
|
+
},
|
|
165
|
+
run: (ctx) =>
|
|
166
|
+
service.removeTopics({ keys: ctx.keys, cwds: ctx.cwds, all: ctx.all, force: ctx.force }),
|
|
167
|
+
render: renderRemoveMany,
|
|
168
|
+
},
|
|
169
|
+
|
|
124
170
|
{
|
|
125
171
|
id: 'sessiontask.config',
|
|
126
172
|
cli: ['sessiontask', 'config'],
|
|
@@ -274,6 +274,18 @@ async function setWaiting(key, cwd, summary, waiting, deadline) {
|
|
|
274
274
|
await saveStore(store);
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
// wait 结束复位:**仅当主题仍存在**时清等待态,绝不重建——主题可能已被 force 删除,
|
|
278
|
+
// 重建会让「删除主题」失效(曾在超时清理路径上把删掉的主题又建回来)。
|
|
279
|
+
async function clearWaiting(key) {
|
|
280
|
+
const store = await loadStore();
|
|
281
|
+
const t = store.topics[key];
|
|
282
|
+
if (!t) return;
|
|
283
|
+
t.waiting = false;
|
|
284
|
+
t.waitingUntil = '';
|
|
285
|
+
t.updatedAt = stampNow();
|
|
286
|
+
await saveStore(store);
|
|
287
|
+
}
|
|
288
|
+
|
|
277
289
|
// ─── CRUD ───────────────────────────────────────────────────────────
|
|
278
290
|
|
|
279
291
|
function settingsView(s) {
|
|
@@ -430,6 +442,76 @@ export async function removeTask({ cwd: rawCwd, id } = {}) {
|
|
|
430
442
|
return { status: 'ok', key, cwd, name: t.name, removed: normalizeTask(removed) };
|
|
431
443
|
}
|
|
432
444
|
|
|
445
|
+
// ─── 主题删除(单个 / 批量) ───────────────────────────────────────
|
|
446
|
+
|
|
447
|
+
// 定位主题 key:显式 --key 优先,否则按工作目录(目录名)推导
|
|
448
|
+
function resolveTopicKey({ key: rawKey, cwd: rawCwd }) {
|
|
449
|
+
if (rawKey && String(rawKey).trim()) return String(rawKey).trim();
|
|
450
|
+
return normalizeCwd(rawCwd).key;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// 删除整个主题(连同其全部任务)。默认拒绝删除「等待中」主题——那会让正阻塞的
|
|
454
|
+
// agent 只能等到超时;force=true 才强删(agent 随后超时收尾)。
|
|
455
|
+
export async function removeTopic({ cwd: rawCwd, key: rawKey, force = false } = {}) {
|
|
456
|
+
if (!rawKey && !rawCwd) {
|
|
457
|
+
throw badInput('需要 --cwd "<工作目录>" 或 --key "<主题key>"');
|
|
458
|
+
}
|
|
459
|
+
const key = resolveTopicKey({ key: rawKey, cwd: rawCwd });
|
|
460
|
+
const store = await loadStore();
|
|
461
|
+
const t = store.topics[key];
|
|
462
|
+
if (!t) {
|
|
463
|
+
const { notFound } = await import('../../core/errors.js');
|
|
464
|
+
throw notFound(`主题不存在(key ${key})`);
|
|
465
|
+
}
|
|
466
|
+
if (t.waiting && !force) {
|
|
467
|
+
throw badInput('主题正在等待中,不能删除(确认要删请加 --force,agent 将超时收尾)');
|
|
468
|
+
}
|
|
469
|
+
const view = topicView(t);
|
|
470
|
+
delete store.topics[key];
|
|
471
|
+
await saveStore(store);
|
|
472
|
+
return { status: 'removed', key, name: t.name, cwd: t.cwd, topic: view };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// 批量删除主题:keys / cwds(数组,可并用),或 all=true 清空全部。
|
|
476
|
+
// 默认把「等待中」「不存在」计入 skipped 而不删;force=true 连等待中一起删。
|
|
477
|
+
export async function removeTopics({ keys, cwds, all = false, force = false } = {}) {
|
|
478
|
+
const hasList = (a) => Array.isArray(a) && a.some((x) => String(x).trim());
|
|
479
|
+
if (!all && !hasList(keys) && !hasList(cwds)) {
|
|
480
|
+
throw badInput('需要 --keys "<key,key>" 或 --cwds "<目录,目录>",或 --all');
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const store = await loadStore();
|
|
484
|
+
let targets;
|
|
485
|
+
if (all) {
|
|
486
|
+
targets = Object.keys(store.topics);
|
|
487
|
+
} else {
|
|
488
|
+
const list = [];
|
|
489
|
+
if (Array.isArray(keys)) list.push(...keys.map((x) => String(x).trim()).filter(Boolean));
|
|
490
|
+
if (Array.isArray(cwds)) {
|
|
491
|
+
for (const c of cwds) if (String(c).trim()) list.push(normalizeCwd(c).key);
|
|
492
|
+
}
|
|
493
|
+
targets = [...new Set(list)];
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const removed = [];
|
|
497
|
+
const skipped = [];
|
|
498
|
+
for (const key of targets) {
|
|
499
|
+
const t = store.topics[key];
|
|
500
|
+
if (!t) {
|
|
501
|
+
skipped.push({ key, reason: 'not_found' });
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (t.waiting && !force) {
|
|
505
|
+
skipped.push({ key, name: t.name, cwd: t.cwd, reason: 'waiting' });
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
removed.push({ key, name: t.name, cwd: t.cwd });
|
|
509
|
+
delete store.topics[key];
|
|
510
|
+
}
|
|
511
|
+
if (removed.length) await saveStore(store);
|
|
512
|
+
return { status: 'ok', all: !!all, count: removed.length, removed, skipped };
|
|
513
|
+
}
|
|
514
|
+
|
|
433
515
|
// 调整**待领取**任务的优先级:把 #id 移动到 #beforeId 之前或 #afterId 之后。
|
|
434
516
|
// 领取顺序 = tasks 数组里 pending 子序列的先后(consumeFirstPending 取第一条 pending),
|
|
435
517
|
// 故只在 pending 之间换位;已领取(历史)任务固定、不可移动。
|
|
@@ -562,7 +644,7 @@ export async function waitForTask({
|
|
|
562
644
|
clearInterval(poll);
|
|
563
645
|
clearTimeout(timer);
|
|
564
646
|
waiters.delete(key);
|
|
565
|
-
await
|
|
647
|
+
await clearWaiting(key);
|
|
566
648
|
await stopServer(server);
|
|
567
649
|
}
|
|
568
650
|
return result;
|
|
@@ -41,6 +41,10 @@ export default function SessionTaskView() {
|
|
|
41
41
|
const [over, setOver] = useState(null); // { id, pos:'top'|'bottom' }
|
|
42
42
|
const [reordering, setReordering] = useState(false);
|
|
43
43
|
|
|
44
|
+
// 主题勾选(批量删除)
|
|
45
|
+
const [selected, setSelected] = useState(() => new Set());
|
|
46
|
+
const [deleting, setDeleting] = useState(false);
|
|
47
|
+
|
|
44
48
|
const load = useCallback(async () => {
|
|
45
49
|
try {
|
|
46
50
|
setOverview(await api('/api/sessiontasks'));
|
|
@@ -106,8 +110,27 @@ export default function SessionTaskView() {
|
|
|
106
110
|
setActiveKey(w.key);
|
|
107
111
|
}, [topics, activeKey]);
|
|
108
112
|
|
|
113
|
+
// 主题被删除后,从勾选集合里剔除失效 key
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
setSelected((s) => {
|
|
116
|
+
const valid = new Set([...s].filter((k) => topics.some((t) => t.key === k)));
|
|
117
|
+
return valid.size === s.size ? s : valid;
|
|
118
|
+
});
|
|
119
|
+
}, [topics]);
|
|
120
|
+
|
|
109
121
|
const active = topics.find((t) => t.key === activeKey) || null;
|
|
110
122
|
|
|
123
|
+
const toggleSelect = (key) =>
|
|
124
|
+
setSelected((s) => {
|
|
125
|
+
const n = new Set(s);
|
|
126
|
+
if (n.has(key)) n.delete(key);
|
|
127
|
+
else n.add(key);
|
|
128
|
+
return n;
|
|
129
|
+
});
|
|
130
|
+
const allSelected = topics.length > 0 && selected.size === topics.length;
|
|
131
|
+
const toggleAll = () =>
|
|
132
|
+
setSelected(allSelected ? new Set() : new Set(topics.map((t) => t.key)));
|
|
133
|
+
|
|
111
134
|
const submit = () =>
|
|
112
135
|
guard(async () => {
|
|
113
136
|
const body = text.trim();
|
|
@@ -143,6 +166,56 @@ export default function SessionTaskView() {
|
|
|
143
166
|
await load();
|
|
144
167
|
});
|
|
145
168
|
|
|
169
|
+
// 删除整个主题(卡片上的「删除主题」);等待中需二次确认并 force
|
|
170
|
+
const deleteTopic = (t) =>
|
|
171
|
+
guard(async () => {
|
|
172
|
+
const msg = t.waiting
|
|
173
|
+
? `主题「${t.name}」正在等待中,删除后其 agent 将超时收尾,确认删除?`
|
|
174
|
+
: `删除主题「${t.name}」及其全部 ${t.total} 条任务?`;
|
|
175
|
+
if (!window.confirm(msg)) return;
|
|
176
|
+
setDeleting(true);
|
|
177
|
+
try {
|
|
178
|
+
await api('/api/sessiontasks/topic', {
|
|
179
|
+
method: 'DELETE',
|
|
180
|
+
body: t.waiting ? { key: t.key, force: true } : { key: t.key },
|
|
181
|
+
});
|
|
182
|
+
setSelected((s) => {
|
|
183
|
+
const n = new Set(s);
|
|
184
|
+
n.delete(t.key);
|
|
185
|
+
return n;
|
|
186
|
+
});
|
|
187
|
+
toast('已删除主题');
|
|
188
|
+
await load();
|
|
189
|
+
} finally {
|
|
190
|
+
setDeleting(false);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// 批量删除勾选主题;含等待中时 force 并提示
|
|
195
|
+
const batchDelete = () =>
|
|
196
|
+
guard(async () => {
|
|
197
|
+
if (!selected.size) return;
|
|
198
|
+
const sel = topics.filter((t) => selected.has(t.key));
|
|
199
|
+
const waitingCount = sel.filter((t) => t.waiting).length;
|
|
200
|
+
let msg = `确认删除选中的 ${sel.length} 个主题?`;
|
|
201
|
+
if (waitingCount) msg += `(其中 ${waitingCount} 个等待中,删除后其 agent 将超时收尾)`;
|
|
202
|
+
if (!window.confirm(msg)) return;
|
|
203
|
+
setDeleting(true);
|
|
204
|
+
try {
|
|
205
|
+
const r = await api('/api/sessiontasks/topics/remove', {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
body: { keys: [...selected], force: waitingCount > 0 },
|
|
208
|
+
});
|
|
209
|
+
toast(
|
|
210
|
+
`已删除 ${r.count} 个主题` + (r.skipped.length ? `,跳过 ${r.skipped.length} 个` : '')
|
|
211
|
+
);
|
|
212
|
+
setSelected(new Set());
|
|
213
|
+
await load();
|
|
214
|
+
} finally {
|
|
215
|
+
setDeleting(false);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
146
219
|
const startEdit = (t) => {
|
|
147
220
|
setEditingId(t.id);
|
|
148
221
|
setEditText(t.text);
|
|
@@ -256,17 +329,34 @@ export default function SessionTaskView() {
|
|
|
256
329
|
</div>
|
|
257
330
|
|
|
258
331
|
<div className="colhead">
|
|
332
|
+
<input
|
|
333
|
+
type="checkbox"
|
|
334
|
+
checked={allSelected}
|
|
335
|
+
onChange={toggleAll}
|
|
336
|
+
disabled={!topics.length}
|
|
337
|
+
title="全选 / 取消全选"
|
|
338
|
+
style={{ margin: 0 }}
|
|
339
|
+
/>
|
|
259
340
|
<h3>主题总览(按工作目录)</h3>
|
|
260
341
|
<span className="muted">{overview ? `${topics.length} 个 · 等待中 ${overview.waiting}` : ''}</span>
|
|
261
|
-
<
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
342
|
+
<span style={{ marginLeft: 'auto', display: 'flex', gap: 8 }}>
|
|
343
|
+
<button
|
|
344
|
+
className="btn small ghost"
|
|
345
|
+
onClick={batchDelete}
|
|
346
|
+
disabled={!selected.size || deleting}
|
|
347
|
+
title="删除勾选的主题(含其全部任务)"
|
|
348
|
+
>
|
|
349
|
+
{deleting ? '删除中…' : `批量删除${selected.size ? ` (${selected.size})` : ''}`}
|
|
350
|
+
</button>
|
|
351
|
+
<button
|
|
352
|
+
className="btn small ghost"
|
|
353
|
+
onClick={prune}
|
|
354
|
+
disabled={pruning}
|
|
355
|
+
title="清理旧模型无目录主题与空主题(等待中一律保留)"
|
|
356
|
+
>
|
|
357
|
+
{pruning ? '清理中…' : '清理旧数据'}
|
|
358
|
+
</button>
|
|
359
|
+
</span>
|
|
270
360
|
</div>
|
|
271
361
|
|
|
272
362
|
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', marginBottom: 14 }}>
|
|
@@ -274,7 +364,7 @@ export default function SessionTaskView() {
|
|
|
274
364
|
topics.map((t) => {
|
|
275
365
|
const isActive = t.key === activeKey;
|
|
276
366
|
return (
|
|
277
|
-
<
|
|
367
|
+
<div
|
|
278
368
|
key={t.key}
|
|
279
369
|
onClick={() => setActiveKey(t.key)}
|
|
280
370
|
className="card"
|
|
@@ -283,21 +373,31 @@ export default function SessionTaskView() {
|
|
|
283
373
|
width: 270,
|
|
284
374
|
padding: '10px 12px',
|
|
285
375
|
cursor: 'pointer',
|
|
286
|
-
font: 'inherit',
|
|
287
|
-
color: 'inherit',
|
|
288
376
|
border: isActive ? '1px solid var(--ink,#14161a)' : '1px solid transparent',
|
|
289
377
|
boxShadow: isActive
|
|
290
378
|
? '0 0 0 2px rgba(20,22,26,.12)'
|
|
291
379
|
: '0 1px 2px rgba(20,22,26,.06), 0 6px 16px rgba(20,22,26,.05)',
|
|
292
380
|
}}
|
|
293
381
|
>
|
|
294
|
-
<div style={{
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
382
|
+
<div style={{ display: 'flex', gap: 8, alignItems: 'flex-start' }}>
|
|
383
|
+
<input
|
|
384
|
+
type="checkbox"
|
|
385
|
+
checked={selected.has(t.key)}
|
|
386
|
+
onChange={() => toggleSelect(t.key)}
|
|
387
|
+
onClick={(e) => e.stopPropagation()}
|
|
388
|
+
title="选择以批量删除"
|
|
389
|
+
style={{ margin: '3px 0 0' }}
|
|
390
|
+
/>
|
|
391
|
+
<div style={{ flex: 1, minWidth: 0 }}>
|
|
392
|
+
<div style={{ fontWeight: 700, marginBottom: 2 }}>{t.name || '(未命名)'}</div>
|
|
393
|
+
<div
|
|
394
|
+
className="mono muted"
|
|
395
|
+
title={t.cwd}
|
|
396
|
+
style={{ fontSize: 11, wordBreak: 'break-all', marginBottom: 6, minHeight: 15 }}
|
|
397
|
+
>
|
|
398
|
+
{t.cwd || '(旧数据,无目录)'}
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
301
401
|
</div>
|
|
302
402
|
{t.summary && (
|
|
303
403
|
<div className="muted" style={{ fontSize: 11, whiteSpace: 'pre-wrap', marginBottom: 6 }}>
|
|
@@ -314,7 +414,20 @@ export default function SessionTaskView() {
|
|
|
314
414
|
待领取 {t.pending}/{t.total}
|
|
315
415
|
</span>
|
|
316
416
|
</div>
|
|
317
|
-
|
|
417
|
+
<div
|
|
418
|
+
style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 8 }}
|
|
419
|
+
onClick={(e) => e.stopPropagation()}
|
|
420
|
+
>
|
|
421
|
+
<button
|
|
422
|
+
className="btn small ghost"
|
|
423
|
+
onClick={() => deleteTopic(t)}
|
|
424
|
+
disabled={deleting}
|
|
425
|
+
title="删除整个主题(含全部任务)"
|
|
426
|
+
>
|
|
427
|
+
删除主题
|
|
428
|
+
</button>
|
|
429
|
+
</div>
|
|
430
|
+
</div>
|
|
318
431
|
);
|
|
319
432
|
})
|
|
320
433
|
) : (
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{o as e,s as t,t as n}from"./index-
|
|
1
|
+
import{o as e,s as t,t as n}from"./index-BC4hXOgL.js";var r=t();function i({module:t}){let{boot:i}=e(),a=(i?.commands||[]).filter(e=>e.module===t);return a.length?(0,r.jsxs)(`div`,{className:`cli-hint`,children:[(0,r.jsx)(`span`,{className:`cli-hint-label`,children:`CLI 等价(同构命令,加 --json 得机器可读输出):`}),a.map(e=>(0,r.jsx)(n,{className:`cli-cmd`,text:e.command,title:`点击复制:${e.usage}`,children:e.command},e.id))]}):null}export{i as t};
|