@morlay/ui-conversation-manager 0.1.5 → 0.1.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.
@@ -0,0 +1,1211 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { Button, IconCloseOutlineRegular, IconSearchOutlineRegular, Input, Modal, Row, Spinner, Stack, Tag, Text, relativeTime } from "@morlay/dsh-client-ui-primitives";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ import { IconArchiveOutlineRegular } from "@deepseek-ai/dsh-client-ui-primitives";
5
+ //#region src/client/controller.ts
6
+ const SESSION_DELETE_PATH = "/api/morlay/v1/session/delete";
7
+ const SESSION_IMPORT_PATH = "/api/morlay/v1/session/import";
8
+ const SESSION_EXPORT_PATH = "/api/morlay/v1/session/export";
9
+ const SESSION_GC_PATH = "/api/morlay/v1/session/gc";
10
+ const SESSION_USAGE_PATH = "/api/morlay/v1/session/usage";
11
+ const SESSION_ROWS_PATH = "/api/morlay/v1/session/rows";
12
+ var ConversationManagerRequestError = class extends Error {
13
+ code;
14
+ constructor(message, code) {
15
+ super(message);
16
+ this.code = code;
17
+ this.name = "ConversationManagerRequestError";
18
+ }
19
+ };
20
+ async function postJson(path, body) {
21
+ const response = await fetch(path, {
22
+ method: "POST",
23
+ headers: {
24
+ accept: "application/json",
25
+ "content-type": "application/json"
26
+ },
27
+ body: JSON.stringify(body)
28
+ });
29
+ const value = await response.json().catch(() => ({}));
30
+ if (!response.ok) throw new ConversationManagerRequestError(typeof value.error === "string" ? value.error : `请求失败:HTTP ${response.status}`, typeof value.code === "string" ? value.code : void 0);
31
+ return value;
32
+ }
33
+ async function zipBase64(file) {
34
+ const dataUrl = await new Promise((resolve, reject) => {
35
+ const reader = new FileReader();
36
+ reader.onload = () => {
37
+ resolve(typeof reader.result === "string" ? reader.result : "");
38
+ };
39
+ reader.onerror = () => {
40
+ reject(reader.error ?? /* @__PURE__ */ new Error("failed to read the selected file"));
41
+ };
42
+ reader.readAsDataURL(file);
43
+ });
44
+ const comma = dataUrl.indexOf(",");
45
+ return comma < 0 ? dataUrl : dataUrl.slice(comma + 1);
46
+ }
47
+ var ConversationManagerController = class {
48
+ ports;
49
+ face;
50
+ constructor(ports) {
51
+ this.ports = ports;
52
+ this.face = {
53
+ listRows: (query) => this.loadRows(query),
54
+ archive: (sessionId) => this.ports.archiveSession(sessionId),
55
+ unarchive: (sessionId) => this.ports.unarchiveSession(sessionId),
56
+ remove: (sessionId) => this.remove(sessionId),
57
+ exportZip: (sessionId) => this.exportZip(sessionId),
58
+ importZip: (file) => this.importZip(file),
59
+ collectGarbage: () => this.collectGarbage(),
60
+ loadUsage: (range) => this.loadUsage(range)
61
+ };
62
+ }
63
+ async loadRows(query = {}) {
64
+ const value = await postJson(SESSION_ROWS_PATH, query);
65
+ const items = value["items"];
66
+ if (!Array.isArray(items) || typeof value["total"] !== "number") throw new ConversationManagerRequestError("会话列表响应不可用", void 0);
67
+ return {
68
+ items,
69
+ total: value["total"],
70
+ page: typeof value["page"] === "number" ? value["page"] : query.page ?? 1,
71
+ pageSize: typeof value["pageSize"] === "number" ? value["pageSize"] : query.pageSize ?? 20
72
+ };
73
+ }
74
+ async remove(sessionId) {
75
+ await postJson(SESSION_DELETE_PATH, { sessionId });
76
+ await this.ports.refresh();
77
+ }
78
+ async exportZip(sessionId) {
79
+ const response = await fetch(SESSION_EXPORT_PATH, {
80
+ method: "POST",
81
+ headers: {
82
+ accept: "application/zip",
83
+ "content-type": "application/json"
84
+ },
85
+ body: JSON.stringify({ sessionId })
86
+ });
87
+ if (!response.ok) {
88
+ const value = await response.json().catch(() => ({}));
89
+ throw new ConversationManagerRequestError(typeof value.error === "string" ? value.error : `请求失败:HTTP ${response.status}`, typeof value.code === "string" ? value.code : void 0);
90
+ }
91
+ downloadBlob(await response.blob(), filenameOf(response.headers.get("content-disposition"), String(sessionId)));
92
+ }
93
+ async importZip(file) {
94
+ const zip = await zipBase64(file);
95
+ const value = await postJson(SESSION_IMPORT_PATH, { zip });
96
+ await this.ports.refresh();
97
+ return value["sessionId"];
98
+ }
99
+ async collectGarbage() {
100
+ const value = await postJson(SESSION_GC_PATH, {});
101
+ await this.ports.refresh();
102
+ return {
103
+ orphanSessions: typeof value["orphanSessions"] === "number" ? value["orphanSessions"] : 0,
104
+ orphanEvents: typeof value["orphanEvents"] === "number" ? value["orphanEvents"] : 0,
105
+ stoppedAgents: typeof value["stoppedAgents"] === "number" ? value["stoppedAgents"] : 0
106
+ };
107
+ }
108
+ async loadUsage(range) {
109
+ const report = await postJson(SESSION_USAGE_PATH, { range });
110
+ if (report.totals === void 0 || !Array.isArray(report.buckets) || !Array.isArray(report.sessions)) throw new ConversationManagerRequestError("用量统计响应不可用", void 0);
111
+ return report;
112
+ }
113
+ };
114
+ function filenameOf(disposition, sessionId) {
115
+ return (disposition === null ? null : /filename="([^"]+)"/u.exec(disposition))?.[1] ?? `${sessionId}.zip`;
116
+ }
117
+ function downloadBlob(blob, filename) {
118
+ const url = URL.createObjectURL(blob);
119
+ const anchor = document.createElement("a");
120
+ anchor.href = url;
121
+ anchor.download = filename;
122
+ anchor.rel = "noopener";
123
+ document.body.append(anchor);
124
+ anchor.click();
125
+ anchor.remove();
126
+ window.setTimeout(() => {
127
+ URL.revokeObjectURL(url);
128
+ }, 0);
129
+ }
130
+ //#endregion
131
+ //#region \0dsh-css-modules:/Users/morlay/src/github.com/morlay/dsh-plugin/packages/session/ui-conversation-manager/src/client/ConversationManagerPage.module.css.mjs
132
+ const css = ".AmU5Dq_page{width:100%;min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);font-size:14px;line-height:1.6;display:flex;overflow:hidden}.AmU5Dq_listPane{flex-direction:column;flex:1;min-width:0;min-height:0;display:flex}.AmU5Dq_pageScroll{scrollbar-gutter:stable;flex:1;min-height:0;overflow:auto}.AmU5Dq_pageContent{max-width:960px;margin:0 auto;padding:0 clamp(24px,4vw,48px) 48px}.AmU5Dq_pageHeading{justify-content:space-between;align-items:center;gap:16px;margin-bottom:24px;padding-top:28px;display:flex}[data-platform=darwin] .AmU5Dq_pageHeading{padding-top:calc(28px + var(--dsh-frame-top-clearance,0px))}.AmU5Dq_pageHeading h1{flex:1;min-width:0;margin:0;font-size:20px;font-weight:500;line-height:28px}.AmU5Dq_headingActions{flex:none;align-items:center;gap:8px;display:flex}.AmU5Dq_filters{flex-wrap:wrap;align-items:center;gap:8px 20px;margin-bottom:14px;display:flex}.AmU5Dq_filterTabs{flex-wrap:wrap;align-items:center;gap:8px 12px;display:flex}.AmU5Dq_filterTab{height:28px;color:var(--dsw-alias-label-tertiary);font:inherit;white-space:nowrap;cursor:pointer;background:0 0;border:0;border-radius:14px;flex:none;align-items:center;padding:0 10px;font-size:14px;line-height:22px;display:inline-flex}.AmU5Dq_filterTab:hover{background:var(--dsw-alias-interactive-bg-hover)}.AmU5Dq_filterTabActive{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.AmU5Dq_searchField{border:.5px solid var(--dsw-alias-border-l3);height:36px;color:var(--dsw-alias-label-tertiary);border-radius:12px;align-items:center;margin:0 0 16px;padding:0 10px;transition:border-color .15s;display:flex}.AmU5Dq_searchField:hover{border-color:var(--dsw-alias-border-l2)}.AmU5Dq_searchField:focus-within{border-color:var(--dsw-alias-state-business-primary)}.AmU5Dq_searchField>:first-child{background:0 0;border:0;border-radius:0;flex:1;min-width:0;height:100%;padding:0}.AmU5Dq_searchField>:first-child:focus-within{border-color:#0000}.AmU5Dq_searchField>:first-child svg{width:14px;height:14px}.AmU5Dq_searchField input::placeholder{color:var(--dsw-alias-label-caption)}.AmU5Dq_searchField input:focus-visible{outline:none}.AmU5Dq_searchField input::-webkit-search-cancel-button{display:none}.AmU5Dq_searchClear{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);flex:none;margin-right:-6px;padding:0}.AmU5Dq_searchClear svg{width:14px;height:14px}.AmU5Dq_list{flex-direction:column;gap:4px;min-width:0;display:flex}.AmU5Dq_status{text-align:center;flex:1;place-items:center;min-width:0;min-height:0;padding:24px;display:grid}.AmU5Dq_statusBody{flex-direction:column;align-items:center;gap:12px;display:flex}.AmU5Dq_statusText{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}.AmU5Dq_statusFailure{color:var(--dsw-alias-state-error-primary)}.AmU5Dq_listStatus{text-align:center;place-items:center;padding:32px 0;display:grid}";
133
+ const tagId = "@morlay/ui-conversation-manager/ConversationManagerPage.module.css";
134
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"@morlay/ui-conversation-manager/ConversationManagerPage.module.css\"]") === null) {
135
+ const tag = document.createElement("style");
136
+ tag.dataset.plugin = "@morlay/ui-conversation-manager";
137
+ tag.dataset.pluginCss = tagId;
138
+ tag.textContent = css;
139
+ document.head.appendChild(tag);
140
+ }
141
+ var ConversationManagerPage_module_css_default = {
142
+ "searchField": "AmU5Dq_searchField",
143
+ "statusFailure": "AmU5Dq_statusFailure",
144
+ "filterTabActive": "AmU5Dq_filterTabActive",
145
+ "page": "AmU5Dq_page",
146
+ "pageScroll": "AmU5Dq_pageScroll",
147
+ "filterTabs": "AmU5Dq_filterTabs",
148
+ "filterTab": "AmU5Dq_filterTab",
149
+ "status": "AmU5Dq_status",
150
+ "statusText": "AmU5Dq_statusText",
151
+ "statusBody": "AmU5Dq_statusBody",
152
+ "listStatus": "AmU5Dq_listStatus",
153
+ "pageContent": "AmU5Dq_pageContent",
154
+ "listPane": "AmU5Dq_listPane",
155
+ "filters": "AmU5Dq_filters",
156
+ "headingActions": "AmU5Dq_headingActions",
157
+ "searchClear": "AmU5Dq_searchClear",
158
+ "list": "AmU5Dq_list",
159
+ "pageHeading": "AmU5Dq_pageHeading"
160
+ };
161
+ //#endregion
162
+ //#region src/client/format.ts
163
+ function trim(value) {
164
+ return value >= 100 ? String(Math.round(value)) : value.toFixed(1).replace(/\.0$/u, "");
165
+ }
166
+ function formatTokens(value) {
167
+ if (!Number.isFinite(value) || value <= 0) return "0";
168
+ if (value < 1e3) return String(Math.round(value));
169
+ if (value < 1e6) return `${trim(value / 1e3)}K`;
170
+ if (value < 1e9) return `${trim(value / 1e6)}M`;
171
+ return `${trim(value / 1e9)}B`;
172
+ }
173
+ function formatCount(value) {
174
+ if (!Number.isFinite(value) || value <= 0) return "0";
175
+ return Math.round(value).toLocaleString("en-US");
176
+ }
177
+ function formatPercent(value) {
178
+ if (!Number.isFinite(value) || value <= 0) return "0%";
179
+ return `${value >= 100 ? Math.round(value) : Math.round(value * 10) / 10}%`;
180
+ }
181
+ //#endregion
182
+ //#region src/client/ConversationManagerPage.tsx
183
+ const PAGE_SIZE = 20;
184
+ const USAGE_SESSION_ROWS = 20;
185
+ function timeLabel(updatedAt, now, t) {
186
+ const { unit, n } = relativeTime(updatedAt, now);
187
+ return unit === "now" ? t("time.now") : t(`time.${unit}`, { n });
188
+ }
189
+ function failureText(error, t) {
190
+ const code = error instanceof ConversationManagerRequestError ? error.code : void 0;
191
+ if (code === "SESSION_NOT_ARCHIVED") return t("failure.notArchived");
192
+ if (code === "SESSION_LIVE") return t("failure.live");
193
+ if (code === "SESSION_NOT_FOUND") return t("failure.missing");
194
+ return t("failure.other", { reason: error instanceof Error ? error.message : String(error) });
195
+ }
196
+ function FilterChip({ active, label, tab, onSelect }) {
197
+ return /* @__PURE__ */ jsx("button", {
198
+ type: "button",
199
+ className: active ? `${ConversationManagerPage_module_css_default.filterTab} ${ConversationManagerPage_module_css_default.filterTabActive}` : ConversationManagerPage_module_css_default.filterTab,
200
+ "aria-pressed": active,
201
+ ...tab === void 0 ? {} : { "data-tab": tab },
202
+ onClick: onSelect,
203
+ children: label
204
+ });
205
+ }
206
+ function PageStatus({ t, status, text, spinner = false, failure = false }) {
207
+ return /* @__PURE__ */ jsx("section", {
208
+ className: ConversationManagerPage_module_css_default.page,
209
+ "aria-label": t("title"),
210
+ children: /* @__PURE__ */ jsx("div", {
211
+ className: ConversationManagerPage_module_css_default.listPane,
212
+ children: /* @__PURE__ */ jsx("div", {
213
+ className: ConversationManagerPage_module_css_default.status,
214
+ "data-status": status,
215
+ children: /* @__PURE__ */ jsxs("div", {
216
+ className: ConversationManagerPage_module_css_default.statusBody,
217
+ children: [spinner ? /* @__PURE__ */ jsx(Spinner, { label: t("loading") }) : null, /* @__PURE__ */ jsx("p", {
218
+ className: failure ? `${ConversationManagerPage_module_css_default.statusText} ${ConversationManagerPage_module_css_default.statusFailure}` : ConversationManagerPage_module_css_default.statusText,
219
+ ...failure ? { role: "alert" } : {},
220
+ children: text
221
+ })]
222
+ })
223
+ })
224
+ })
225
+ });
226
+ }
227
+ function ConversationManagerPage({ t, listRows, archive, unarchive, remove, exportZip, importZip, collectGarbage, loadUsage }) {
228
+ const [rowsPage, setRowsPage] = useState(null);
229
+ const [rowsFailure, setRowsFailure] = useState(null);
230
+ const [query, setQuery] = useState("");
231
+ const [settledQuery, setSettledQuery] = useState("");
232
+ const [page, setPage] = useState(1);
233
+ const [showSubagents, setShowSubagents] = useState(false);
234
+ const [view, setView] = useState("sessions");
235
+ const [usageTab, setUsageTab] = useState("overview");
236
+ const [usageRange, setUsageRange] = useState("day");
237
+ const [usageOnlySubagents, setUsageOnlySubagents] = useState(false);
238
+ const [usage, setUsage] = useState(null);
239
+ const [usageLoading, setUsageLoading] = useState(false);
240
+ const [usageError, setUsageError] = useState(null);
241
+ const [confirming, setConfirming] = useState(null);
242
+ const [gcPhase, setGcPhase] = useState("idle");
243
+ const [importing, setImporting] = useState(false);
244
+ const [notice, setNotice] = useState(null);
245
+ const [failure, setFailure] = useState(null);
246
+ const fileRef = useRef(null);
247
+ const reloadRows = useCallback(async () => {
248
+ try {
249
+ setRowsPage(await listRows({
250
+ ...settledQuery === "" ? {} : { query: settledQuery },
251
+ ...showSubagents ? { includeSubagents: true } : {},
252
+ page,
253
+ pageSize: PAGE_SIZE
254
+ }));
255
+ setRowsFailure(null);
256
+ } catch (error) {
257
+ setRowsFailure(failureText(error, t));
258
+ }
259
+ }, [
260
+ listRows,
261
+ t,
262
+ settledQuery,
263
+ showSubagents,
264
+ page
265
+ ]);
266
+ useEffect(() => {
267
+ reloadRows();
268
+ }, [reloadRows]);
269
+ useEffect(() => {
270
+ if (query === settledQuery) return;
271
+ const timer = window.setTimeout(() => {
272
+ setSettledQuery(query);
273
+ setPage(1);
274
+ }, 250);
275
+ return () => {
276
+ window.clearTimeout(timer);
277
+ };
278
+ }, [query, settledQuery]);
279
+ const ungrouped = t("ungrouped");
280
+ const rows = useMemo(() => (rowsPage?.items ?? []).map((record) => ({
281
+ id: record.sessionId,
282
+ title: record.title ?? record.sessionId,
283
+ workspace: record.workspace ?? ungrouped,
284
+ archived: record.archived,
285
+ subagent: record.origin === "subagent",
286
+ updatedAt: record.updatedAt
287
+ })), [rowsPage, ungrouped]);
288
+ const run = (action, settle) => {
289
+ setFailure(null);
290
+ setNotice(null);
291
+ action.then(() => {
292
+ settle?.();
293
+ reloadRows();
294
+ }, (error) => {
295
+ settle?.();
296
+ setFailure(failureText(error, t));
297
+ });
298
+ };
299
+ const startGc = () => {
300
+ setGcPhase("running");
301
+ setFailure(null);
302
+ setNotice(null);
303
+ collectGarbage().then((result) => {
304
+ setGcPhase("idle");
305
+ setNotice(t("gc.done", {
306
+ sessions: result.orphanSessions,
307
+ events: result.orphanEvents
308
+ }));
309
+ reloadRows();
310
+ }, (error) => {
311
+ setGcPhase("idle");
312
+ setFailure(failureText(error, t));
313
+ });
314
+ };
315
+ const requestUsage = (range) => {
316
+ setUsageRange(range);
317
+ setUsageLoading(true);
318
+ setUsageError(null);
319
+ loadUsage(range).then((report) => {
320
+ setUsage(report);
321
+ }, (error) => {
322
+ setUsageError(failureText(error, t));
323
+ }).finally(() => {
324
+ setUsageLoading(false);
325
+ });
326
+ };
327
+ const openUsage = () => {
328
+ setView("usage");
329
+ if (usage === null && !usageLoading) requestUsage(usageRange);
330
+ };
331
+ if (rowsPage === null) return rowsFailure === null ? /* @__PURE__ */ jsx(PageStatus, {
332
+ t,
333
+ status: "loading",
334
+ text: t("loading"),
335
+ spinner: true
336
+ }) : /* @__PURE__ */ jsx(PageStatus, {
337
+ t,
338
+ status: "failure",
339
+ text: rowsFailure,
340
+ failure: true
341
+ });
342
+ const now = Date.now();
343
+ const visible = rows;
344
+ const total = rowsPage.total;
345
+ const pageCount = Math.max(1, Math.ceil(total / PAGE_SIZE));
346
+ const currentPage = Math.min(page, pageCount);
347
+ const matched = rows;
348
+ const confirmed = confirming;
349
+ if (total === 0 && settledQuery === "") return /* @__PURE__ */ jsx(PageStatus, {
350
+ t,
351
+ status: "empty",
352
+ text: t("empty")
353
+ });
354
+ return /* @__PURE__ */ jsxs("section", {
355
+ className: ConversationManagerPage_module_css_default.page,
356
+ "data-view": view,
357
+ "aria-label": t("title"),
358
+ children: [
359
+ /* @__PURE__ */ jsx("div", {
360
+ className: ConversationManagerPage_module_css_default.listPane,
361
+ children: /* @__PURE__ */ jsx("div", {
362
+ className: ConversationManagerPage_module_css_default.pageScroll,
363
+ "data-scroll": "page",
364
+ children: /* @__PURE__ */ jsxs("div", {
365
+ className: ConversationManagerPage_module_css_default.pageContent,
366
+ children: [
367
+ /* @__PURE__ */ jsxs("div", {
368
+ className: ConversationManagerPage_module_css_default.pageHeading,
369
+ children: [/* @__PURE__ */ jsx("h1", { children: t("title") }), /* @__PURE__ */ jsxs("div", {
370
+ className: ConversationManagerPage_module_css_default.headingActions,
371
+ children: [/* @__PURE__ */ jsx(Button, {
372
+ variant: "outline",
373
+ size: "sm",
374
+ "data-action": "import",
375
+ disabled: importing,
376
+ "aria-busy": importing,
377
+ "aria-label": importing ? t("importing") : t("import"),
378
+ onClick: () => {
379
+ fileRef.current?.click();
380
+ },
381
+ children: importing ? t("importing") : t("import")
382
+ }), /* @__PURE__ */ jsx(Button, {
383
+ variant: "outline",
384
+ size: "sm",
385
+ "data-action": "gc",
386
+ disabled: gcPhase !== "idle",
387
+ "aria-label": t("gc.button"),
388
+ onClick: () => {
389
+ setFailure(null);
390
+ setNotice(null);
391
+ setGcPhase("confirm");
392
+ },
393
+ children: t("gc.button")
394
+ })]
395
+ })]
396
+ }),
397
+ /* @__PURE__ */ jsx("input", {
398
+ ref: fileRef,
399
+ type: "file",
400
+ accept: ".zip,application/zip",
401
+ hidden: true,
402
+ onChange: (event) => {
403
+ const file = event.currentTarget.files?.[0];
404
+ event.currentTarget.value = "";
405
+ if (file === void 0) return;
406
+ setImporting(true);
407
+ setFailure(null);
408
+ setNotice(null);
409
+ importZip(file).then(() => {
410
+ setNotice(t("imported"));
411
+ }, (error) => {
412
+ setFailure(failureText(error, t));
413
+ }).finally(() => {
414
+ setImporting(false);
415
+ });
416
+ }
417
+ }),
418
+ /* @__PURE__ */ jsx("div", {
419
+ className: ConversationManagerPage_module_css_default.filters,
420
+ children: /* @__PURE__ */ jsxs("div", {
421
+ className: ConversationManagerPage_module_css_default.filterTabs,
422
+ role: "group",
423
+ "aria-label": t("filter.view"),
424
+ "data-filter": "view",
425
+ children: [/* @__PURE__ */ jsx(FilterChip, {
426
+ active: view === "sessions",
427
+ label: t("view.sessions"),
428
+ tab: "sessions",
429
+ onSelect: () => {
430
+ setView("sessions");
431
+ }
432
+ }), /* @__PURE__ */ jsx(FilterChip, {
433
+ active: view === "usage",
434
+ label: t("view.usage"),
435
+ tab: "usage",
436
+ onSelect: openUsage
437
+ })]
438
+ })
439
+ }),
440
+ view === "usage" ? null : /* @__PURE__ */ jsx("div", {
441
+ className: ConversationManagerPage_module_css_default.filters,
442
+ children: /* @__PURE__ */ jsx("div", {
443
+ className: ConversationManagerPage_module_css_default.filterTabs,
444
+ role: "group",
445
+ "aria-label": t("filter.subagents"),
446
+ "data-filter": "subagents",
447
+ children: /* @__PURE__ */ jsx(FilterChip, {
448
+ active: showSubagents,
449
+ label: t("showSubagents"),
450
+ onSelect: () => {
451
+ setShowSubagents(!showSubagents);
452
+ setPage(1);
453
+ }
454
+ })
455
+ })
456
+ }),
457
+ view === "usage" ? null : /* @__PURE__ */ jsxs("div", {
458
+ className: ConversationManagerPage_module_css_default.searchField,
459
+ children: [/* @__PURE__ */ jsx(Input, {
460
+ "data-filter": "search",
461
+ type: "search",
462
+ icon: /* @__PURE__ */ jsx(IconSearchOutlineRegular, {}),
463
+ value: query,
464
+ placeholder: t("search"),
465
+ "aria-label": t("search"),
466
+ onChange: (event) => {
467
+ setQuery(event.currentTarget.value);
468
+ setPage(1);
469
+ }
470
+ }), query === "" ? null : /* @__PURE__ */ jsx(Button, {
471
+ variant: "ghost",
472
+ size: "sm",
473
+ className: ConversationManagerPage_module_css_default.searchClear,
474
+ "aria-label": t("clearSearch"),
475
+ onClick: () => {
476
+ setQuery("");
477
+ setPage(1);
478
+ },
479
+ children: /* @__PURE__ */ jsx(IconCloseOutlineRegular, {})
480
+ })]
481
+ }),
482
+ view === "usage" ? /* @__PURE__ */ jsx(UsageView, {
483
+ report: usage,
484
+ loading: usageLoading,
485
+ error: usageError,
486
+ tab: usageTab,
487
+ onTab: setUsageTab,
488
+ range: usageRange,
489
+ onRange: requestUsage,
490
+ onlySubagents: usageOnlySubagents,
491
+ onOnlySubagents: setUsageOnlySubagents,
492
+ t
493
+ }) : /* @__PURE__ */ jsxs("div", {
494
+ className: ConversationManagerPage_module_css_default.list,
495
+ children: [
496
+ notice === null ? null : /* @__PURE__ */ jsx(Text, {
497
+ "data-notice": "result",
498
+ as: "p",
499
+ size: "md",
500
+ tone: "tertiary",
501
+ children: notice
502
+ }),
503
+ failure === null ? null : /* @__PURE__ */ jsx(Text, {
504
+ "data-failure": "result",
505
+ role: "alert",
506
+ as: "p",
507
+ size: "md",
508
+ tone: "danger",
509
+ children: failure
510
+ }),
511
+ total === 0 ? /* @__PURE__ */ jsx("div", {
512
+ className: ConversationManagerPage_module_css_default.listStatus,
513
+ "data-status": "empty-search",
514
+ children: /* @__PURE__ */ jsx("p", {
515
+ className: ConversationManagerPage_module_css_default.statusText,
516
+ children: t("emptySearch")
517
+ })
518
+ }) : null,
519
+ visible.length > 0 ? /* @__PURE__ */ jsx(Stack, {
520
+ as: "ul",
521
+ gap: 4,
522
+ plain: true,
523
+ children: visible.map((row) => /* @__PURE__ */ jsxs(Row, {
524
+ as: "li",
525
+ gap: 16,
526
+ justify: "between",
527
+ boxed: true,
528
+ pad: "row",
529
+ fixed: true,
530
+ "data-session-id": String(row.id),
531
+ "data-archived": row.archived ? "true" : "false",
532
+ "data-subagent": row.subagent ? "true" : "false",
533
+ children: [/* @__PURE__ */ jsxs(Stack, {
534
+ gap: 2,
535
+ grow: true,
536
+ children: [/* @__PURE__ */ jsxs(Row, {
537
+ gap: 8,
538
+ grow: true,
539
+ children: [
540
+ /* @__PURE__ */ jsx(Text, {
541
+ size: "md",
542
+ truncate: true,
543
+ children: row.title
544
+ }),
545
+ row.archived ? /* @__PURE__ */ jsx(Tag, {
546
+ tone: "neutral",
547
+ children: t("archived")
548
+ }) : null,
549
+ row.subagent ? /* @__PURE__ */ jsx(Tag, {
550
+ tone: "quiet",
551
+ children: t("subagent")
552
+ }) : null
553
+ ]
554
+ }), /* @__PURE__ */ jsx(Text, {
555
+ size: "sm",
556
+ tone: "tertiary",
557
+ children: [row.workspace, timeLabel(row.updatedAt, now, t)].join(" · ")
558
+ })]
559
+ }), /* @__PURE__ */ jsxs(Row, {
560
+ gap: 8,
561
+ fixed: true,
562
+ children: [
563
+ row.archived ? /* @__PURE__ */ jsx(Button, {
564
+ variant: "outline",
565
+ size: "sm",
566
+ "data-action": "unarchive",
567
+ "aria-label": t("unarchiveNamed", { title: row.title }),
568
+ onClick: () => {
569
+ run(unarchive(row.id));
570
+ },
571
+ children: t("unarchive")
572
+ }) : /* @__PURE__ */ jsx(Button, {
573
+ variant: "outline",
574
+ size: "sm",
575
+ "data-action": "archive",
576
+ "aria-label": t("archiveNamed", { title: row.title }),
577
+ onClick: () => {
578
+ run(archive(row.id));
579
+ },
580
+ children: t("archive")
581
+ }),
582
+ /* @__PURE__ */ jsx(Button, {
583
+ variant: "outline",
584
+ size: "sm",
585
+ "data-action": "export",
586
+ "aria-label": t("exportNamed", { title: row.title }),
587
+ onClick: () => {
588
+ run(exportZip(row.id));
589
+ },
590
+ children: t("export")
591
+ }),
592
+ /* @__PURE__ */ jsx(Button, {
593
+ variant: "outline",
594
+ size: "sm",
595
+ disabled: !row.archived,
596
+ "data-action": "remove",
597
+ "aria-label": t("removeNamed", { title: row.title }),
598
+ onClick: () => {
599
+ setFailure(null);
600
+ setNotice(null);
601
+ setConfirming(row);
602
+ },
603
+ children: t("remove")
604
+ })
605
+ ]
606
+ })]
607
+ }, row.id))
608
+ }) : null,
609
+ matched.length > 0 ? /* @__PURE__ */ jsxs(Row, {
610
+ "data-pagination": "",
611
+ "data-page-current": currentPage,
612
+ "data-page-total": pageCount,
613
+ gap: 8,
614
+ justify: "end",
615
+ fixed: true,
616
+ children: [
617
+ /* @__PURE__ */ jsx(Button, {
618
+ variant: "ghost",
619
+ size: "sm",
620
+ disabled: currentPage <= 1,
621
+ onClick: () => {
622
+ setPage(currentPage - 1);
623
+ },
624
+ children: t("page.previous")
625
+ }),
626
+ /* @__PURE__ */ jsx(Text, {
627
+ size: "sm",
628
+ tone: "tertiary",
629
+ tabular: true,
630
+ children: t("page.label", {
631
+ page: currentPage,
632
+ total: pageCount
633
+ })
634
+ }),
635
+ /* @__PURE__ */ jsx(Button, {
636
+ variant: "ghost",
637
+ size: "sm",
638
+ disabled: currentPage >= pageCount,
639
+ onClick: () => {
640
+ setPage(currentPage + 1);
641
+ },
642
+ children: t("page.next")
643
+ })
644
+ ]
645
+ }) : null
646
+ ]
647
+ })
648
+ ]
649
+ })
650
+ })
651
+ }),
652
+ /* @__PURE__ */ jsx(Modal, {
653
+ open: confirmed !== null,
654
+ onClose: () => {
655
+ setConfirming(null);
656
+ },
657
+ title: t("confirmTitle"),
658
+ closeLabel: t("close"),
659
+ description: t("confirmDescription"),
660
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
661
+ variant: "ghost",
662
+ onClick: () => {
663
+ setConfirming(null);
664
+ },
665
+ children: t("confirmCancel")
666
+ }), /* @__PURE__ */ jsx(Button, {
667
+ variant: "primary",
668
+ onClick: () => {
669
+ if (confirmed === null) return;
670
+ run(remove(confirmed.id), () => {
671
+ setConfirming(null);
672
+ });
673
+ },
674
+ children: t("confirmAccept")
675
+ })] })
676
+ }),
677
+ /* @__PURE__ */ jsx(Modal, {
678
+ open: gcPhase === "confirm",
679
+ onClose: () => {
680
+ setGcPhase("idle");
681
+ },
682
+ title: t("gc.title"),
683
+ closeLabel: t("close"),
684
+ description: t("gc.description"),
685
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
686
+ variant: "ghost",
687
+ onClick: () => {
688
+ setGcPhase("idle");
689
+ },
690
+ children: t("gc.cancel")
691
+ }), /* @__PURE__ */ jsx(Button, {
692
+ variant: "primary",
693
+ onClick: startGc,
694
+ children: t("gc.confirm")
695
+ })] })
696
+ }),
697
+ /* @__PURE__ */ jsx(Modal, {
698
+ open: gcPhase === "running",
699
+ onClose: () => {},
700
+ headless: true,
701
+ title: t("gc.title"),
702
+ children: /* @__PURE__ */ jsxs(Stack, {
703
+ align: "center",
704
+ gap: 14,
705
+ pad: "blocking",
706
+ children: [/* @__PURE__ */ jsx(Spinner, { label: t("gc.running") }), /* @__PURE__ */ jsx(Text, {
707
+ as: "p",
708
+ size: "md",
709
+ children: t("gc.running")
710
+ })]
711
+ })
712
+ })
713
+ ]
714
+ });
715
+ }
716
+ const USAGE_RANGES = [
717
+ "day",
718
+ "week",
719
+ "7d",
720
+ "30d",
721
+ "90d",
722
+ "all"
723
+ ];
724
+ function rangeLabel(range, t) {
725
+ switch (range) {
726
+ case "all": return t("usage.range.all");
727
+ case "day": return t("usage.range.day");
728
+ case "week": return t("usage.range.week");
729
+ case "7d": return t("usage.range.days", { n: 7 });
730
+ case "30d": return t("usage.range.days", { n: 30 });
731
+ case "90d": return t("usage.range.days", { n: 90 });
732
+ }
733
+ }
734
+ function cacheHitPercent(totals) {
735
+ const total = totals.inputTokens + totals.cacheReadTokens;
736
+ if (total <= 0) return 0;
737
+ return totals.cacheReadTokens / total * 100;
738
+ }
739
+ function usageMetrics(totals, t, options = {}) {
740
+ return [
741
+ {
742
+ key: "input",
743
+ label: t("usage.inputWithCache"),
744
+ value: totals.inputTokens + totals.cacheReadTokens,
745
+ kind: "tokens"
746
+ },
747
+ {
748
+ key: "cacheInput",
749
+ label: t("usage.cacheInput"),
750
+ value: totals.cacheReadTokens,
751
+ kind: "tokens"
752
+ },
753
+ {
754
+ key: "cacheRate",
755
+ label: t("usage.cacheRate"),
756
+ value: cacheHitPercent(totals),
757
+ kind: "percent"
758
+ },
759
+ {
760
+ key: "output",
761
+ label: t("usage.output"),
762
+ value: totals.outputTokens,
763
+ kind: "tokens"
764
+ },
765
+ {
766
+ key: "reasoning",
767
+ label: t("usage.reasoning"),
768
+ value: totals.reasoningTokens,
769
+ kind: "tokens"
770
+ },
771
+ ...options.withActivity === false ? [] : [
772
+ {
773
+ key: "turns",
774
+ label: t("usage.turns"),
775
+ value: totals.turns,
776
+ kind: "count"
777
+ },
778
+ {
779
+ key: "steps",
780
+ label: t("usage.steps"),
781
+ value: totals.steps,
782
+ kind: "count"
783
+ },
784
+ {
785
+ key: "userInputs",
786
+ label: t("usage.userInputs"),
787
+ value: totals.userInputs,
788
+ kind: "count"
789
+ },
790
+ {
791
+ key: "toolCalls",
792
+ label: t("usage.toolCalls"),
793
+ value: totals.toolCalls,
794
+ kind: "count"
795
+ }
796
+ ]
797
+ ];
798
+ }
799
+ function sortWeight(totals) {
800
+ return totals.inputTokens + totals.cacheReadTokens + totals.outputTokens;
801
+ }
802
+ function emptyTotals() {
803
+ return {
804
+ turns: 0,
805
+ steps: 0,
806
+ userInputs: 0,
807
+ toolCalls: 0,
808
+ inputTokens: 0,
809
+ outputTokens: 0,
810
+ cacheReadTokens: 0,
811
+ reasoningTokens: 0,
812
+ totalTokens: 0
813
+ };
814
+ }
815
+ function foldBuckets(buckets, keyOf) {
816
+ const folded = /* @__PURE__ */ new Map();
817
+ for (const bucket of buckets) {
818
+ const key = keyOf(bucket);
819
+ const row = folded.get(key) ?? {
820
+ key,
821
+ label: key,
822
+ totals: emptyTotals()
823
+ };
824
+ row.totals.inputTokens += bucket.inputTokens;
825
+ row.totals.outputTokens += bucket.outputTokens;
826
+ row.totals.cacheReadTokens += bucket.cacheReadTokens;
827
+ row.totals.reasoningTokens += bucket.reasoningTokens;
828
+ row.totals.totalTokens += bucket.totalTokens;
829
+ folded.set(key, row);
830
+ }
831
+ return [...folded.values()].sort((left, right) => sortWeight(right.totals) - sortWeight(left.totals));
832
+ }
833
+ function UsageRow({ rowKey, label, totals, t, subagent, withActivity = true }) {
834
+ return /* @__PURE__ */ jsxs(Stack, {
835
+ as: "li",
836
+ gap: 6,
837
+ boxed: true,
838
+ pad: "row",
839
+ fixed: true,
840
+ "data-usage-key": rowKey,
841
+ ...subagent === void 0 ? {} : { "data-subagent": subagent ? "true" : "false" },
842
+ children: [/* @__PURE__ */ jsxs(Row, {
843
+ gap: 8,
844
+ grow: true,
845
+ children: [/* @__PURE__ */ jsx(Text, {
846
+ size: "sm",
847
+ tone: "tertiary",
848
+ truncate: true,
849
+ children: label
850
+ }), subagent === true ? /* @__PURE__ */ jsx(Tag, {
851
+ tone: "quiet",
852
+ children: t("subagent")
853
+ }) : null]
854
+ }), /* @__PURE__ */ jsx(Row, {
855
+ gap: "wide",
856
+ wrap: true,
857
+ children: usageMetrics(totals, t, { withActivity }).map((metric) => /* @__PURE__ */ jsxs(Stack, {
858
+ "data-usage-cell": metric.key,
859
+ "data-usage-value": metric.value,
860
+ gap: 2,
861
+ children: [/* @__PURE__ */ jsx(Text, {
862
+ size: "sm",
863
+ tone: "tertiary",
864
+ children: metric.label
865
+ }), /* @__PURE__ */ jsx(Text, {
866
+ size: "lg",
867
+ weight: "medium",
868
+ tabular: true,
869
+ children: metric.kind === "percent" ? formatPercent(metric.value) : metric.kind === "count" ? formatCount(metric.value) : formatTokens(metric.value)
870
+ })]
871
+ }, metric.key))
872
+ })]
873
+ });
874
+ }
875
+ function UsageList({ rows, t, withActivity = true }) {
876
+ if (rows.length === 0) return /* @__PURE__ */ jsx(Text, {
877
+ "data-usage-status": "empty",
878
+ as: "p",
879
+ size: "md",
880
+ tone: "tertiary",
881
+ children: t("usage.empty")
882
+ });
883
+ return /* @__PURE__ */ jsx(Stack, {
884
+ as: "ul",
885
+ gap: 4,
886
+ plain: true,
887
+ children: rows.map((row) => /* @__PURE__ */ jsx(UsageRow, {
888
+ rowKey: row.key,
889
+ label: row.label,
890
+ totals: row.totals,
891
+ t,
892
+ ...row.subagent === void 0 ? {} : { subagent: row.subagent },
893
+ withActivity
894
+ }, row.key))
895
+ });
896
+ }
897
+ function UsageOverview({ report, t }) {
898
+ return /* @__PURE__ */ jsxs(Stack, {
899
+ as: "ul",
900
+ gap: 4,
901
+ plain: true,
902
+ children: [/* @__PURE__ */ jsx(UsageRow, {
903
+ rowKey: "all",
904
+ label: t("usage.all"),
905
+ totals: report.totals,
906
+ t
907
+ }), /* @__PURE__ */ jsx(UsageRow, {
908
+ rowKey: "subagent",
909
+ label: t("usage.subagentOnly"),
910
+ totals: report.subagent,
911
+ t
912
+ })]
913
+ });
914
+ }
915
+ function UsageView({ report, loading, error, tab, onTab, range, onRange, onlySubagents, onOnlySubagents, t }) {
916
+ const items = [
917
+ "overview",
918
+ "models",
919
+ "sessions"
920
+ ];
921
+ const labels = {
922
+ overview: t("usage.overview"),
923
+ models: t("usage.models"),
924
+ sessions: t("usage.sessions")
925
+ };
926
+ const rows = report === null || tab === "overview" ? [] : tab === "models" ? foldBuckets(report.buckets, (bucket) => `${bucket.provider ?? t("usage.unknownModel")} / ${bucket.model ?? t("usage.unknownModel")}`) : report.sessions.filter((row) => !onlySubagents || row.subagent).map((row) => ({
927
+ key: row.sessionId,
928
+ label: row.title ?? row.sessionId,
929
+ totals: row,
930
+ subagent: row.subagent
931
+ })).sort((left, right) => sortWeight(right.totals) - sortWeight(left.totals)).slice(0, USAGE_SESSION_ROWS);
932
+ return /* @__PURE__ */ jsxs(Stack, {
933
+ "data-usage-view": tab,
934
+ "data-usage-range": range,
935
+ gap: 12,
936
+ fixed: true,
937
+ children: [
938
+ /* @__PURE__ */ jsx("div", {
939
+ className: ConversationManagerPage_module_css_default.filterTabs,
940
+ role: "group",
941
+ "aria-label": t("usage.range"),
942
+ "data-filter": "usage-range",
943
+ children: USAGE_RANGES.map((option) => /* @__PURE__ */ jsx(FilterChip, {
944
+ active: option === range,
945
+ label: rangeLabel(option, t),
946
+ tab: option,
947
+ onSelect: () => {
948
+ onRange(option);
949
+ }
950
+ }, option))
951
+ }),
952
+ /* @__PURE__ */ jsx("div", {
953
+ className: ConversationManagerPage_module_css_default.filterTabs,
954
+ role: "group",
955
+ "aria-label": t("usage.dimension"),
956
+ "data-filter": "usage-tabs",
957
+ children: items.map((item) => /* @__PURE__ */ jsx(FilterChip, {
958
+ active: item === tab,
959
+ label: labels[item],
960
+ tab: item,
961
+ onSelect: () => {
962
+ onTab(item);
963
+ }
964
+ }, item))
965
+ }),
966
+ tab === "sessions" ? /* @__PURE__ */ jsx("div", {
967
+ className: ConversationManagerPage_module_css_default.filterTabs,
968
+ role: "group",
969
+ "aria-label": t("filter.subagents"),
970
+ "data-filter": "usage-subagents",
971
+ children: /* @__PURE__ */ jsx(FilterChip, {
972
+ active: onlySubagents,
973
+ label: t("usage.onlySubagents"),
974
+ onSelect: () => {
975
+ onOnlySubagents(!onlySubagents);
976
+ }
977
+ })
978
+ }) : null,
979
+ loading ? /* @__PURE__ */ jsx(Text, {
980
+ "data-usage-status": "loading",
981
+ as: "p",
982
+ size: "md",
983
+ tone: "tertiary",
984
+ children: t("usage.loading")
985
+ }) : null,
986
+ error === null ? null : /* @__PURE__ */ jsx(Text, {
987
+ "data-usage-status": "error",
988
+ role: "alert",
989
+ as: "p",
990
+ size: "md",
991
+ tone: "danger",
992
+ children: error
993
+ }),
994
+ report === null ? null : tab === "overview" ? /* @__PURE__ */ jsx(UsageOverview, {
995
+ report,
996
+ t
997
+ }) : /* @__PURE__ */ jsx(UsageList, {
998
+ rows,
999
+ t,
1000
+ withActivity: tab === "sessions"
1001
+ })
1002
+ ]
1003
+ });
1004
+ }
1005
+ //#endregion
1006
+ //#region src/client/ConversationManagerIcon.tsx
1007
+ function ConversationManagerIcon({ size }) {
1008
+ return /* @__PURE__ */ jsx(IconArchiveOutlineRegular, { size });
1009
+ }
1010
+ //#endregion
1011
+ //#region src/client/locales.ts
1012
+ const zh = {
1013
+ panel: "对话管理",
1014
+ title: "对话管理",
1015
+ search: "搜索会话",
1016
+ loading: "正在读取会话…",
1017
+ empty: "暂无会话。",
1018
+ emptySearch: "没有匹配的会话。",
1019
+ archived: "已归档",
1020
+ subagent: "子代理",
1021
+ showSubagents: "显示子代理会话",
1022
+ clearSearch: "清空搜索",
1023
+ "filter.view": "视图",
1024
+ "filter.subagents": "会话筛选",
1025
+ "view.sessions": "会话",
1026
+ "view.usage": "统计",
1027
+ "usage.dimension": "用量维度",
1028
+ "usage.overview": "总览",
1029
+ "usage.all": "全部会话",
1030
+ "usage.range": "时间范围",
1031
+ "usage.range.all": "全部",
1032
+ "usage.range.day": "本日",
1033
+ "usage.range.week": "本周",
1034
+ "usage.range.days": "近 {n} 天",
1035
+ "usage.models": "按模型",
1036
+ "usage.sessions": "按会话",
1037
+ "usage.loading": "正在统计…",
1038
+ "usage.empty": "暂无用量数据。",
1039
+ "usage.input": "输入",
1040
+ "usage.inputWithCache": "输入(含缓存)",
1041
+ "usage.output": "输出",
1042
+ "usage.cacheInput": "缓存输入",
1043
+ "usage.cacheRate": "缓存命中率",
1044
+ "usage.reasoning": "推理",
1045
+ "usage.total": "合计",
1046
+ "usage.turns": "轮次",
1047
+ "usage.steps": "步骤",
1048
+ "usage.userInputs": "用户输入",
1049
+ "usage.toolCalls": "工具调用",
1050
+ "usage.subagentOnly": "其中子代理",
1051
+ "usage.onlySubagents": "只看子代理会话",
1052
+ "usage.unknownModel": "未知模型",
1053
+ archive: "归档",
1054
+ archiveNamed: "归档 {title}",
1055
+ unarchive: "取消归档",
1056
+ unarchiveNamed: "取消归档 {title}",
1057
+ remove: "删除",
1058
+ removeNamed: "删除 {title}",
1059
+ export: "导出",
1060
+ exportNamed: "导出 {title}",
1061
+ ungrouped: "未分组",
1062
+ "page.previous": "上一页",
1063
+ "page.next": "下一页",
1064
+ "page.label": "第 {page} / {total} 页",
1065
+ "gc.button": "清理孤儿数据",
1066
+ "gc.title": "清理孤儿数据",
1067
+ "gc.description": "会先停止所有运行中的 Agent,期间界面不可操作;随后回收孤儿 subagent 会话与孤儿数据,并执行 VACUUM。",
1068
+ "gc.confirm": "开始清理",
1069
+ "gc.cancel": "取消",
1070
+ "gc.running": "正在清理,请稍候…",
1071
+ "gc.done": "已回收 {sessions} 条孤儿会话、{events} 行孤儿数据。",
1072
+ import: "导入对话",
1073
+ importing: "导入中…",
1074
+ imported: "已导入为新会话。",
1075
+ confirmTitle: "删除会话",
1076
+ confirmDescription: "删除后无法恢复:该会话的内容会从存储中移除。",
1077
+ confirmAccept: "删除",
1078
+ confirmCancel: "取消",
1079
+ close: "关闭",
1080
+ "failure.notArchived": "只有已归档的会话可以删除。",
1081
+ "failure.live": "会话正在使用中,无法删除。",
1082
+ "failure.missing": "会话不存在或已被删除。",
1083
+ "failure.other": "操作失败:{reason}",
1084
+ "time.now": "刚刚",
1085
+ "time.minutes": "{n}分钟",
1086
+ "time.hours": "{n}小时",
1087
+ "time.days": "{n}天",
1088
+ "time.months": "{n}个月",
1089
+ "time.years": "{n}年"
1090
+ };
1091
+ const en = {
1092
+ panel: "Conversations",
1093
+ title: "Conversations",
1094
+ search: "Search sessions",
1095
+ loading: "Reading sessions…",
1096
+ empty: "No sessions.",
1097
+ emptySearch: "No matching sessions.",
1098
+ archived: "Archived",
1099
+ subagent: "Subagent",
1100
+ showSubagents: "Show subagent sessions",
1101
+ clearSearch: "Clear search",
1102
+ "filter.view": "View",
1103
+ "filter.subagents": "Session filter",
1104
+ "view.sessions": "Sessions",
1105
+ "view.usage": "Usage",
1106
+ "usage.dimension": "Usage dimension",
1107
+ "usage.overview": "Overview",
1108
+ "usage.all": "All sessions",
1109
+ "usage.range": "Time range",
1110
+ "usage.range.all": "All",
1111
+ "usage.range.day": "Today",
1112
+ "usage.range.week": "This week",
1113
+ "usage.range.days": "Last {n}d",
1114
+ "usage.models": "By model",
1115
+ "usage.sessions": "By session",
1116
+ "usage.loading": "Reading usage…",
1117
+ "usage.empty": "No usage recorded.",
1118
+ "usage.input": "Input",
1119
+ "usage.inputWithCache": "Input (incl. cache)",
1120
+ "usage.output": "Output",
1121
+ "usage.cacheInput": "Cache input",
1122
+ "usage.cacheRate": "Cache hit rate",
1123
+ "usage.reasoning": "Reasoning",
1124
+ "usage.total": "Total",
1125
+ "usage.turns": "Turns",
1126
+ "usage.steps": "Steps",
1127
+ "usage.userInputs": "User inputs",
1128
+ "usage.toolCalls": "Tool calls",
1129
+ "usage.subagentOnly": "Subagent share",
1130
+ "usage.onlySubagents": "Subagent sessions only",
1131
+ "usage.unknownModel": "Unknown model",
1132
+ archive: "Archive",
1133
+ archiveNamed: "Archive {title}",
1134
+ unarchive: "Unarchive",
1135
+ unarchiveNamed: "Unarchive {title}",
1136
+ remove: "Delete",
1137
+ removeNamed: "Delete {title}",
1138
+ export: "Export",
1139
+ exportNamed: "Export {title}",
1140
+ ungrouped: "Ungrouped",
1141
+ "page.previous": "Previous",
1142
+ "page.next": "Next",
1143
+ "page.label": "Page {page} / {total}",
1144
+ "gc.button": "Clean orphan data",
1145
+ "gc.title": "Clean orphan data",
1146
+ "gc.description": "Stops every running Agent first — the UI is blocked meanwhile — then reclaims orphan subagent sessions and orphan rows, and runs VACUUM.",
1147
+ "gc.confirm": "Start",
1148
+ "gc.cancel": "Cancel",
1149
+ "gc.running": "Cleaning, please wait…",
1150
+ "gc.done": "Reclaimed {sessions} orphan sessions and {events} orphan rows.",
1151
+ import: "Import conversation",
1152
+ importing: "Importing…",
1153
+ imported: "Imported as a new session.",
1154
+ confirmTitle: "Delete session",
1155
+ confirmDescription: "This cannot be undone: the session's content is removed from storage.",
1156
+ confirmAccept: "Delete",
1157
+ confirmCancel: "Cancel",
1158
+ close: "Close",
1159
+ "failure.notArchived": "Only archived sessions can be deleted.",
1160
+ "failure.live": "The session is in use and cannot be deleted.",
1161
+ "failure.missing": "The session does not exist or was already deleted.",
1162
+ "failure.other": "Action failed: {reason}",
1163
+ "time.now": "now",
1164
+ "time.minutes": "{n}min",
1165
+ "time.hours": "{n}h",
1166
+ "time.days": "{n}d",
1167
+ "time.months": "{n}mo",
1168
+ "time.years": "{n}y"
1169
+ };
1170
+ //#endregion
1171
+ //#region src/client/index.ts
1172
+ const NS = "conversationManager";
1173
+ const PANEL_ID = "conversations";
1174
+ const PANEL_ORDER = 1;
1175
+ const inject = [
1176
+ "slots",
1177
+ "locale",
1178
+ "uiWorkspace",
1179
+ "sessions"
1180
+ ];
1181
+ function apply(ctx) {
1182
+ ctx.effect(() => ctx.locale.register(NS, {
1183
+ zh,
1184
+ en
1185
+ }), "ui-conversation-manager: dictionaries");
1186
+ const t = ctx.locale.bind(NS);
1187
+ const sessions = ctx.get("sessions");
1188
+ const refreshList = sessions.refresh;
1189
+ const controller = new ConversationManagerController({
1190
+ archiveSession: (sessionId) => ctx.uiWorkspace.archiveSession(sessionId),
1191
+ unarchiveSession: (sessionId) => ctx.uiWorkspace.unarchiveSession(sessionId),
1192
+ refresh: async () => {
1193
+ await refreshList?.call(sessions);
1194
+ }
1195
+ });
1196
+ ctx.slots.inject("main", () => ctx.slots.register({
1197
+ name: "main",
1198
+ key: PANEL_ID,
1199
+ locale: NS,
1200
+ inject: () => controller.face
1201
+ }, ConversationManagerPage));
1202
+ ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
1203
+ name: "sidebar.panellist",
1204
+ id: PANEL_ID,
1205
+ order: 1,
1206
+ label: () => t("panel"),
1207
+ locale: NS
1208
+ }, ConversationManagerIcon));
1209
+ }
1210
+ //#endregion
1211
+ export { ConversationManagerController, ConversationManagerRequestError, NS, PANEL_ID, PANEL_ORDER, apply, inject };