@morlay/ui-conversation-manager 0.0.2-alpha.4 → 0.0.2-alpha.6
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 +13 -9
- package/dist/client.cjs +72 -43
- package/dist/client.d.cts +331 -170
- package/dist/client.d.mts +331 -170
- package/package.json +13 -13
- package/src/client/ConversationManagerIcon.tsx +2 -2
- package/src/client/ConversationManagerPage.tsx +70 -56
- package/src/client/controller.ts +51 -0
package/README.md
CHANGED
|
@@ -5,7 +5,9 @@ ui-plugin-manager 同一种注册方式)。页面分两层 Tabs(官方 `Pill
|
|
|
5
5
|
会话视图给出全量会话的搜索、分页与归档 / 取消归档 / 导出 / 删除(确认弹窗),并带导入为新会话与
|
|
6
6
|
孤儿数据 GC;统计视图给出全部对话的 token 用量。
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
会话列表取 `POST /api/session.rows` 的完整语料(含归档),按最近活动在前;搜索(标题或工作区名)、
|
|
9
|
+
子代理开关与翻页都是**后端**请求(前端只渲染当前页,输入停 250ms 再发);**已归档**的行带标记,
|
|
10
|
+
也只有这些行的「删除」可用
|
|
9
11
|
(未归档行提供「归档」,两者互斥)。子代理派生会话(`origin: 'subagent'`)默认不列(它们既不可删
|
|
10
12
|
也多数无意义,实测在真实库里占七成),勾选「显示子代理会话」即真全量。
|
|
11
13
|
|
|
@@ -21,11 +23,14 @@ ui-plugin-manager 同一种注册方式)。页面分两层 Tabs(官方 `Pill
|
|
|
21
23
|
## 用法
|
|
22
24
|
|
|
23
25
|
页面是 profile 的一行(由 `@morlay/better-session` 的 patch 插入),装好即在侧栏出现「对话管理」。
|
|
24
|
-
它不新增 host
|
|
26
|
+
它不新增 host 面,只读既有服务与路由。**会话行走我们自己的列表路由**(`POST /api/session.rows`,
|
|
27
|
+
`@morlay/session-rdb`):完整语料(含归档)+ 标题 + 最后活动时间随行给出——官方 `session/list` 按部署策略
|
|
28
|
+
**默认排除已归档**(给上游 UI 用),归档集的管理动作要完整集合,两条路不混(决策见
|
|
29
|
+
[session-rdb 的 ADR](../session-rdb/.agents/adrs/20260922-会话列表两条路.md)):
|
|
25
30
|
|
|
26
31
|
| 动作 | 接缝 |
|
|
27
32
|
| ------------ | ----------------------------------------------------------------------------------------------------------- |
|
|
28
|
-
| 列表与标题 |
|
|
33
|
+
| 列表与标题 | `POST /api/session.rows`(`@morlay/session-rdb`:完整语料 + 标题 + 最后活动时间 + 工作区归属;搜索 / 子代理过滤 / 分页都在后端) |
|
|
29
34
|
| 归档 | `ctx.uiWorkspace.archiveSession`(上游 ui-workspace,未归档行提供) |
|
|
30
35
|
| 取消归档 | `ctx.uiWorkspace.unarchiveSession`(上游 ui-workspace,已归档行提供) |
|
|
31
36
|
| 导出 | `POST /api/session.export`(`@morlay/session-rdb`,直接下载 zip) |
|
|
@@ -34,7 +39,7 @@ ui-plugin-manager 同一种注册方式)。页面分两层 Tabs(官方 `Pill
|
|
|
34
39
|
| 清理孤儿数据 | `POST /api/session.gc`(停 agent → 回收孤儿 subagent 会话 → 回收孤儿事件行 → VACUUM;执行期间阻塞界面) |
|
|
35
40
|
| 用量统计 | `POST /api/session.usage`(`@morlay/session-rdb` 读专用用量表聚合;进入统计视图时拉一次,维度切换本地折叠) |
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
归档 / 取消归档 / 删除 / 导入 / GC 成功后重拉会话行;host 拒绝(未归档 / 正在使用 / 不存在)
|
|
38
43
|
时按错误码给出可读文案。列表每页 20 条,搜索框复用官方 `Input`(连同官方图标与焦点样式)。
|
|
39
44
|
|
|
40
45
|
GC 是唯一会**停止所有运行中 agent** 的动作:确认后进入不可关闭的等待弹窗,避免用户在 VACUUM 期间
|
|
@@ -61,15 +66,14 @@ GC 是唯一会**停止所有运行中 agent** 的动作:确认后进入不可
|
|
|
61
66
|
| `data-tab` | 一层 tab 按钮 | 该按钮切到的视图 |
|
|
62
67
|
| `data-action` | 按钮 | `import` / `gc` / `archive` / `unarchive` / `export` / `remove` |
|
|
63
68
|
| `data-filter` | 过滤器 | `search` / `subagents` |
|
|
64
|
-
| `data-
|
|
69
|
+
| `data-scroll` | 滚动容器 | `page`(会话列表与统计各一处) |
|
|
65
70
|
| `data-session-id` / `data-archived` / `data-subagent` | 会话行 | 该行的会话 id 与状态(`true` / `false`) |
|
|
66
|
-
| `data-session-title` / `data-session-meta` | 行内字段 | 标题、`工作区 · 时间` |
|
|
67
71
|
| `data-pagination` / `data-page-current` / `data-page-total` | 分页条 | 当前页与总页数 |
|
|
68
72
|
| `data-notice` / `data-failure` / `data-status` | 顶部提示与空态 | 结果、错误、`empty` / `empty-search` |
|
|
69
|
-
| `data-usage-view` / `data-usage-tab`
|
|
73
|
+
| `data-usage-view` / `data-usage-tab` / `data-usage-range` | 统计视图与二级 tab | 当前维度(`overview` / `models` / `sessions`)与时间范围 |
|
|
74
|
+
| `data-range` | 时间范围选项 | 该选项对应的 range 值 |
|
|
70
75
|
| `data-usage-cell` / `data-usage-value` | 总览格子 | 维度键与原始数值(未格式化) |
|
|
71
|
-
| `data-usage-
|
|
72
|
-
| `data-usage-label` / `data-usage-total` / `data-usage-meta` | 统计行内字段 | 名称、合计、明细 |
|
|
76
|
+
| `data-usage-key` | 统计划表行 | 维度键(日期 / `provider / model` / 会话 id / `subagent`) |
|
|
73
77
|
| `data-usage-status` | 统计状态行 | `loading` / `error` / `empty` |
|
|
74
78
|
|
|
75
79
|
## 已知限制
|
package/dist/client.cjs
CHANGED
|
@@ -15,6 +15,11 @@ window.__ModuleLoader__.load({
|
|
|
15
15
|
const SESSION_EXPORT_PATH = "/api/session.export";
|
|
16
16
|
const SESSION_GC_PATH = "/api/session.gc";
|
|
17
17
|
const SESSION_USAGE_PATH = "/api/session.usage";
|
|
18
|
+
/**
|
|
19
|
+
* 管理面的会话行(**完整语料,含归档**):我们自己的路由,与官方 `session/list` 分开——那条按部署策略
|
|
20
|
+
* 默认排除归档(给上游 UI 用),归档集的管理动作需要完整集合。
|
|
21
|
+
*/
|
|
22
|
+
const SESSION_ROWS_PATH = "/api/session.rows";
|
|
18
23
|
/** 带 host 错误码的请求失败:页面据此选本地化文案。 */
|
|
19
24
|
var ConversationManagerRequestError = class extends Error {
|
|
20
25
|
code;
|
|
@@ -58,6 +63,7 @@ window.__ModuleLoader__.load({
|
|
|
58
63
|
constructor(ports) {
|
|
59
64
|
this.ports = ports;
|
|
60
65
|
this.face = {
|
|
66
|
+
listRows: (query) => this.loadRows(query),
|
|
61
67
|
archive: (sessionId) => this.ports.archiveSession(sessionId),
|
|
62
68
|
unarchive: (sessionId) => this.ports.unarchiveSession(sessionId),
|
|
63
69
|
remove: (sessionId) => this.remove(sessionId),
|
|
@@ -67,6 +73,17 @@ window.__ModuleLoader__.load({
|
|
|
67
73
|
loadUsage: (range) => this.loadUsage(range)
|
|
68
74
|
};
|
|
69
75
|
}
|
|
76
|
+
async loadRows(query = {}) {
|
|
77
|
+
const value = await postJson(SESSION_ROWS_PATH, query);
|
|
78
|
+
const items = value["items"];
|
|
79
|
+
if (!Array.isArray(items) || typeof value["total"] !== "number") throw new ConversationManagerRequestError("会话列表响应不可用", void 0);
|
|
80
|
+
return {
|
|
81
|
+
items,
|
|
82
|
+
total: value["total"],
|
|
83
|
+
page: typeof value["page"] === "number" ? value["page"] : query.page ?? 1,
|
|
84
|
+
pageSize: typeof value["pageSize"] === "number" ? value["pageSize"] : query.pageSize ?? 20
|
|
85
|
+
};
|
|
86
|
+
}
|
|
70
87
|
async remove(sessionId) {
|
|
71
88
|
await postJson(SESSION_DELETE_PATH, { sessionId });
|
|
72
89
|
await this.ports.refresh();
|
|
@@ -410,10 +427,6 @@ window.__ModuleLoader__.load({
|
|
|
410
427
|
const { unit, n } = (0, _deepseek_ai_dsh_client_ui_primitives.relativeTime)(updatedAt, now);
|
|
411
428
|
return unit === "now" ? t("time.now") : t(`time.${unit}`, { n });
|
|
412
429
|
}
|
|
413
|
-
/** 标题或工作区名命中归一化后的查询。 */
|
|
414
|
-
function matches(row, normalizedQuery) {
|
|
415
|
-
return normalizedQuery.length === 0 || row.title.toLowerCase().includes(normalizedQuery) || row.workspace.toLowerCase().includes(normalizedQuery);
|
|
416
|
-
}
|
|
417
430
|
/** host 错误码 → 可读文案;没有码时保留原文。 */
|
|
418
431
|
function failureText(error, t) {
|
|
419
432
|
const code = error instanceof ConversationManagerRequestError ? error.code : void 0;
|
|
@@ -422,10 +435,11 @@ window.__ModuleLoader__.load({
|
|
|
422
435
|
if (code === "SESSION_NOT_FOUND") return t("failure.missing");
|
|
423
436
|
return t("failure.other", { reason: error instanceof Error ? error.message : String(error) });
|
|
424
437
|
}
|
|
425
|
-
function ConversationManagerPage({ t,
|
|
426
|
-
const
|
|
427
|
-
const
|
|
438
|
+
function ConversationManagerPage({ t, listRows, archive, unarchive, remove, exportZip, importZip, collectGarbage, loadUsage }) {
|
|
439
|
+
const [rowsPage, setRowsPage] = (0, react.useState)(null);
|
|
440
|
+
const [rowsFailure, setRowsFailure] = (0, react.useState)(null);
|
|
428
441
|
const [query, setQuery] = (0, react.useState)("");
|
|
442
|
+
const [settledQuery, setSettledQuery] = (0, react.useState)("");
|
|
429
443
|
const [page, setPage] = (0, react.useState)(1);
|
|
430
444
|
const [showSubagents, setShowSubagents] = (0, react.useState)(false);
|
|
431
445
|
const [view, setView] = (0, react.useState)("sessions");
|
|
@@ -440,34 +454,53 @@ window.__ModuleLoader__.load({
|
|
|
440
454
|
const [notice, setNotice] = (0, react.useState)(null);
|
|
441
455
|
const [failure, setFailure] = (0, react.useState)(null);
|
|
442
456
|
const fileRef = (0, react.useRef)(null);
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
archived: archivedIds.has(id),
|
|
456
|
-
subagent: summary.origin === "subagent",
|
|
457
|
-
updatedAt: summary.updatedAt
|
|
458
|
-
}];
|
|
459
|
-
}).sort((left, right) => right.updatedAt - left.updatedAt);
|
|
457
|
+
const reloadRows = (0, react.useCallback)(async () => {
|
|
458
|
+
try {
|
|
459
|
+
setRowsPage(await listRows({
|
|
460
|
+
...settledQuery === "" ? {} : { query: settledQuery },
|
|
461
|
+
...showSubagents ? { includeSubagents: true } : {},
|
|
462
|
+
page,
|
|
463
|
+
pageSize: PAGE_SIZE
|
|
464
|
+
}));
|
|
465
|
+
setRowsFailure(null);
|
|
466
|
+
} catch (error) {
|
|
467
|
+
setRowsFailure(failureText(error, t));
|
|
468
|
+
}
|
|
460
469
|
}, [
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
470
|
+
listRows,
|
|
471
|
+
t,
|
|
472
|
+
settledQuery,
|
|
473
|
+
showSubagents,
|
|
474
|
+
page
|
|
465
475
|
]);
|
|
476
|
+
(0, react.useEffect)(() => {
|
|
477
|
+
reloadRows();
|
|
478
|
+
}, [reloadRows]);
|
|
479
|
+
(0, react.useEffect)(() => {
|
|
480
|
+
if (query === settledQuery) return;
|
|
481
|
+
const timer = window.setTimeout(() => {
|
|
482
|
+
setSettledQuery(query);
|
|
483
|
+
setPage(1);
|
|
484
|
+
}, 250);
|
|
485
|
+
return () => {
|
|
486
|
+
window.clearTimeout(timer);
|
|
487
|
+
};
|
|
488
|
+
}, [query, settledQuery]);
|
|
489
|
+
const ungrouped = t("ungrouped");
|
|
490
|
+
const rows = (0, react.useMemo)(() => (rowsPage?.items ?? []).map((record) => ({
|
|
491
|
+
id: record.sessionId,
|
|
492
|
+
title: record.title ?? record.sessionId,
|
|
493
|
+
workspace: record.workspace ?? ungrouped,
|
|
494
|
+
archived: record.archived,
|
|
495
|
+
subagent: record.origin === "subagent",
|
|
496
|
+
updatedAt: record.updatedAt
|
|
497
|
+
})), [rowsPage, ungrouped]);
|
|
466
498
|
const run = (action, settle) => {
|
|
467
499
|
setFailure(null);
|
|
468
500
|
setNotice(null);
|
|
469
501
|
action.then(() => {
|
|
470
502
|
settle?.();
|
|
503
|
+
reloadRows();
|
|
471
504
|
}, (error) => {
|
|
472
505
|
settle?.();
|
|
473
506
|
setFailure(failureText(error, t));
|
|
@@ -483,6 +516,7 @@ window.__ModuleLoader__.load({
|
|
|
483
516
|
sessions: result.orphanSessions,
|
|
484
517
|
events: result.orphanEvents
|
|
485
518
|
}));
|
|
519
|
+
reloadRows();
|
|
486
520
|
}, (error) => {
|
|
487
521
|
setGcPhase("idle");
|
|
488
522
|
setFailure(failureText(error, t));
|
|
@@ -504,16 +538,16 @@ window.__ModuleLoader__.load({
|
|
|
504
538
|
setView("usage");
|
|
505
539
|
if (usage === null && !usageLoading) requestUsage(usageRange);
|
|
506
540
|
};
|
|
507
|
-
if (
|
|
541
|
+
if (rowsPage === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
508
542
|
..._morlay_dsh_client_ui_primitives_client.styling.props(styles.status),
|
|
509
|
-
children: t("loading")
|
|
543
|
+
children: rowsFailure ?? t("loading")
|
|
510
544
|
});
|
|
511
545
|
const now = Date.now();
|
|
512
|
-
const
|
|
513
|
-
const
|
|
514
|
-
const pageCount = Math.max(1, Math.ceil(
|
|
546
|
+
const visible = rows;
|
|
547
|
+
const total = rowsPage.total;
|
|
548
|
+
const pageCount = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
|
515
549
|
const currentPage = Math.min(page, pageCount);
|
|
516
|
-
const
|
|
550
|
+
const matched = rows;
|
|
517
551
|
const confirmed = confirming;
|
|
518
552
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
519
553
|
..._morlay_dsh_client_ui_primitives_client.styling.props(styles.page),
|
|
@@ -618,7 +652,7 @@ window.__ModuleLoader__.load({
|
|
|
618
652
|
className: _morlay_dsh_client_ui_primitives_client.styling.className(styles.search),
|
|
619
653
|
"data-filter": "search",
|
|
620
654
|
type: "search",
|
|
621
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
655
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutlineRegular, {}),
|
|
622
656
|
value: query,
|
|
623
657
|
placeholder: t("search"),
|
|
624
658
|
"aria-label": t("search"),
|
|
@@ -652,15 +686,10 @@ window.__ModuleLoader__.load({
|
|
|
652
686
|
role: "alert",
|
|
653
687
|
children: failure
|
|
654
688
|
}),
|
|
655
|
-
|
|
689
|
+
total === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
656
690
|
..._morlay_dsh_client_ui_primitives_client.styling.props(styles.status),
|
|
657
691
|
"data-status": "empty",
|
|
658
|
-
children: t("empty")
|
|
659
|
-
}) : null,
|
|
660
|
-
listed.length > 0 && matched.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
661
|
-
..._morlay_dsh_client_ui_primitives_client.styling.props(styles.status),
|
|
662
|
-
"data-status": "empty-search",
|
|
663
|
-
children: t("emptySearch")
|
|
692
|
+
children: t(settledQuery === "" ? "empty" : "emptySearch")
|
|
664
693
|
}) : null,
|
|
665
694
|
visible.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
666
695
|
..._morlay_dsh_client_ui_primitives_client.styling.props(styles.list),
|
|
@@ -1103,7 +1132,7 @@ window.__ModuleLoader__.load({
|
|
|
1103
1132
|
//#endregion
|
|
1104
1133
|
//#region src/client/ConversationManagerIcon.tsx
|
|
1105
1134
|
function ConversationManagerIcon({ size }) {
|
|
1106
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
1135
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutlineRegular, { size });
|
|
1107
1136
|
}
|
|
1108
1137
|
//#endregion
|
|
1109
1138
|
//#region src/client/locales.ts
|