@morlay/ui-conversation-manager 0.1.4 → 0.1.5
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 +4 -0
- package/dist/client.cjs +1307 -1881
- package/dist/client.d.cts +1 -0
- package/dist/client.d.mts +1 -0
- package/package.json +2 -2
- package/src/client/ConversationManagerPage.tsx +146 -136
- package/src/client/locales.ts +2 -0
- package/src/client/ConversationManagerPage.styles.ts +0 -256
package/dist/client.d.cts
CHANGED
package/dist/client.d.mts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morlay/ui-conversation-manager",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "「对话管理」全局面板页(浏览器半):会话与统计两层 Tabs——会话视图给出全量会话(含已归档)的搜索、分页、归档 / 取消归档 / 导出 / 删除、导入为新会话与孤儿数据 GC,统计视图给出全部对话的 token 用量。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"archived-sessions",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.2",
|
|
51
51
|
"@deepseek-ai/dsh-client-ui-workspace": "^0.2.0-rc.2",
|
|
52
52
|
"@deepseek-ai/dsh-session": "^0.2.0-rc.2",
|
|
53
|
-
"@morlay/dsh-client-ui-primitives": "0.0.
|
|
53
|
+
"@morlay/dsh-client-ui-primitives": "0.0.8"
|
|
54
54
|
},
|
|
55
55
|
"dsh": {
|
|
56
56
|
"client": {
|
|
@@ -6,12 +6,16 @@ import {
|
|
|
6
6
|
Button,
|
|
7
7
|
Checkbox,
|
|
8
8
|
IconSearchOutlineRegular,
|
|
9
|
-
Input,
|
|
10
9
|
Modal,
|
|
10
|
+
Row,
|
|
11
|
+
SearchInput,
|
|
12
|
+
Spinner,
|
|
13
|
+
Stack,
|
|
14
|
+
Tabs,
|
|
11
15
|
Tag,
|
|
16
|
+
Text,
|
|
12
17
|
relativeTime,
|
|
13
|
-
} from "@
|
|
14
|
-
import { styling } from "@morlay/dsh-client-ui-primitives/client";
|
|
18
|
+
} from "@morlay/dsh-client-ui-primitives/client";
|
|
15
19
|
import type { InjectFace, PropsLocale, PropsRuntime } from "@deepseek-ai/dsh-client-ui-slots";
|
|
16
20
|
import type { SessionId } from "@deepseek-ai/dsh-session";
|
|
17
21
|
import {
|
|
@@ -24,7 +28,6 @@ import {
|
|
|
24
28
|
type UsageTotals,
|
|
25
29
|
} from "./controller.ts";
|
|
26
30
|
import { formatCount, formatPercent, formatTokens } from "./format.ts";
|
|
27
|
-
import { styles } from "./ConversationManagerPage.styles.ts";
|
|
28
31
|
|
|
29
32
|
// 一页的行数(会话列表)。
|
|
30
33
|
const PAGE_SIZE = 20;
|
|
@@ -91,6 +94,7 @@ export function ConversationManagerPage({
|
|
|
91
94
|
const [view, setView] = useState<PageView>("sessions");
|
|
92
95
|
const [usageTab, setUsageTab] = useState<UsageTab>("overview");
|
|
93
96
|
const [usageRange, setUsageRange] = useState<UsageRange>("day");
|
|
97
|
+
const [usageOnlySubagents, setUsageOnlySubagents] = useState(false);
|
|
94
98
|
const [usage, setUsage] = useState<SessionUsageReport | null>(null);
|
|
95
99
|
const [usageLoading, setUsageLoading] = useState(false);
|
|
96
100
|
const [usageError, setUsageError] = useState<string | null>(null);
|
|
@@ -208,7 +212,7 @@ export function ConversationManagerPage({
|
|
|
208
212
|
};
|
|
209
213
|
|
|
210
214
|
if (rowsPage === null) {
|
|
211
|
-
return <p
|
|
215
|
+
return <Text as="p" size="md" tone="tertiary">{rowsFailure ?? t("loading")}</Text>;
|
|
212
216
|
}
|
|
213
217
|
|
|
214
218
|
const now = Date.now();
|
|
@@ -221,34 +225,22 @@ export function ConversationManagerPage({
|
|
|
221
225
|
const confirmed = confirming;
|
|
222
226
|
|
|
223
227
|
return (
|
|
224
|
-
<
|
|
225
|
-
<
|
|
226
|
-
<h1
|
|
227
|
-
<
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
<button
|
|
241
|
-
type="button"
|
|
242
|
-
role="tab"
|
|
243
|
-
data-tab="usage"
|
|
244
|
-
aria-selected={view === "usage"}
|
|
245
|
-
className={styling.className(styles.tab, view === "usage" && styles.tabActive)}
|
|
246
|
-
onClick={openUsage}
|
|
247
|
-
>
|
|
248
|
-
{t("view.usage")}
|
|
249
|
-
</button>
|
|
250
|
-
</div>
|
|
251
|
-
<div {...styling.props(styles.headerActions)}>
|
|
228
|
+
<Stack data-view={view} fill pad="page" gap={12}>
|
|
229
|
+
<Row gap={12} justify="between" fixed>
|
|
230
|
+
<Text as="h1" size="lg" weight="medium">{t("title")}</Text>
|
|
231
|
+
<Tabs
|
|
232
|
+
label={t("title")}
|
|
233
|
+
items={[
|
|
234
|
+
{ value: "sessions", label: t("view.sessions") },
|
|
235
|
+
{ value: "usage", label: t("view.usage") },
|
|
236
|
+
]}
|
|
237
|
+
value={view}
|
|
238
|
+
onChange={(next) => {
|
|
239
|
+
if (next === "usage") openUsage();
|
|
240
|
+
else setView("sessions");
|
|
241
|
+
}}
|
|
242
|
+
/>
|
|
243
|
+
<Row gap={8} fixed>
|
|
252
244
|
<Button
|
|
253
245
|
variant="outline"
|
|
254
246
|
size="sm"
|
|
@@ -276,7 +268,7 @@ export function ConversationManagerPage({
|
|
|
276
268
|
>
|
|
277
269
|
{t("gc.button")}
|
|
278
270
|
</Button>
|
|
279
|
-
</
|
|
271
|
+
</Row>
|
|
280
272
|
<input
|
|
281
273
|
ref={fileRef}
|
|
282
274
|
type="file"
|
|
@@ -303,9 +295,9 @@ export function ConversationManagerPage({
|
|
|
303
295
|
});
|
|
304
296
|
}}
|
|
305
297
|
/>
|
|
306
|
-
</
|
|
298
|
+
</Row>
|
|
307
299
|
{view === "usage" ? (
|
|
308
|
-
<
|
|
300
|
+
<Stack data-scroll="page" gap={12} scroll>
|
|
309
301
|
<UsageView
|
|
310
302
|
report={usage}
|
|
311
303
|
loading={usageLoading}
|
|
@@ -314,14 +306,16 @@ export function ConversationManagerPage({
|
|
|
314
306
|
onTab={setUsageTab}
|
|
315
307
|
range={usageRange}
|
|
316
308
|
onRange={requestUsage}
|
|
309
|
+
onlySubagents={usageOnlySubagents}
|
|
310
|
+
onOnlySubagents={setUsageOnlySubagents}
|
|
317
311
|
t={t}
|
|
318
312
|
/>
|
|
319
|
-
</
|
|
313
|
+
</Stack>
|
|
320
314
|
) : (
|
|
321
315
|
<>
|
|
322
|
-
<
|
|
323
|
-
<
|
|
324
|
-
|
|
316
|
+
<Row gap={16} fixed>
|
|
317
|
+
<SearchInput
|
|
318
|
+
width="search"
|
|
325
319
|
data-filter="search"
|
|
326
320
|
type="search"
|
|
327
321
|
icon={<IconSearchOutlineRegular />}
|
|
@@ -343,44 +337,49 @@ export function ConversationManagerPage({
|
|
|
343
337
|
}}
|
|
344
338
|
/>
|
|
345
339
|
</span>
|
|
346
|
-
</
|
|
347
|
-
<
|
|
340
|
+
</Row>
|
|
341
|
+
<Stack data-scroll="page" gap={12} scroll>
|
|
348
342
|
{notice === null ? null : (
|
|
349
|
-
<
|
|
343
|
+
<Text data-notice="result" as="p" size="md" tone="tertiary">
|
|
350
344
|
{notice}
|
|
351
|
-
</
|
|
345
|
+
</Text>
|
|
352
346
|
)}
|
|
353
347
|
{failure === null ? null : (
|
|
354
|
-
<
|
|
348
|
+
<Text data-failure="result" role="alert" as="p" size="md" tone="danger">
|
|
355
349
|
{failure}
|
|
356
|
-
</
|
|
350
|
+
</Text>
|
|
357
351
|
)}
|
|
358
352
|
{total === 0 ? (
|
|
359
|
-
<
|
|
353
|
+
<Text data-status="empty" as="p" size="md" tone="tertiary">
|
|
360
354
|
{t(settledQuery === "" ? "empty" : "emptySearch")}
|
|
361
|
-
</
|
|
355
|
+
</Text>
|
|
362
356
|
) : null}
|
|
363
357
|
{visible.length > 0 ? (
|
|
364
|
-
<ul {
|
|
358
|
+
<Stack as="ul" gap={4} plain>
|
|
365
359
|
{visible.map((row) => (
|
|
366
|
-
<
|
|
360
|
+
<Row
|
|
361
|
+
as="li"
|
|
367
362
|
key={row.id}
|
|
368
|
-
{
|
|
363
|
+
gap={16}
|
|
364
|
+
justify="between"
|
|
365
|
+
boxed
|
|
366
|
+
pad="row"
|
|
367
|
+
fixed
|
|
369
368
|
data-session-id={String(row.id)}
|
|
370
369
|
data-archived={row.archived ? "true" : "false"}
|
|
371
370
|
data-subagent={row.subagent ? "true" : "false"}
|
|
372
371
|
>
|
|
373
|
-
<
|
|
374
|
-
<
|
|
375
|
-
<
|
|
372
|
+
<Stack gap={2} grow>
|
|
373
|
+
<Row gap={8} grow>
|
|
374
|
+
<Text size="md" truncate>{row.title}</Text>
|
|
376
375
|
{row.archived ? <Tag tone="neutral">{t("archived")}</Tag> : null}
|
|
377
376
|
{row.subagent ? <Tag tone="quiet">{t("subagent")}</Tag> : null}
|
|
378
|
-
</
|
|
379
|
-
<
|
|
377
|
+
</Row>
|
|
378
|
+
<Text size="sm" tone="tertiary">
|
|
380
379
|
{[row.workspace, timeLabel(row.updatedAt, now, t)].join(" · ")}
|
|
381
|
-
</
|
|
382
|
-
</
|
|
383
|
-
<
|
|
380
|
+
</Text>
|
|
381
|
+
</Stack>
|
|
382
|
+
<Row gap={8} fixed>
|
|
384
383
|
{row.archived ? (
|
|
385
384
|
<Button
|
|
386
385
|
variant="outline"
|
|
@@ -431,18 +430,16 @@ export function ConversationManagerPage({
|
|
|
431
430
|
>
|
|
432
431
|
{t("remove")}
|
|
433
432
|
</Button>
|
|
434
|
-
</
|
|
435
|
-
</
|
|
433
|
+
</Row>
|
|
434
|
+
</Row>
|
|
436
435
|
))}
|
|
437
|
-
</
|
|
436
|
+
</Stack>
|
|
438
437
|
) : null}
|
|
439
438
|
{matched.length > 0 ? (
|
|
440
|
-
<
|
|
441
|
-
{...styling.props(styles.pagination)}
|
|
439
|
+
<Row
|
|
442
440
|
data-pagination=""
|
|
443
441
|
data-page-current={currentPage}
|
|
444
|
-
data-page-total={pageCount}
|
|
445
|
-
>
|
|
442
|
+
data-page-total={pageCount} gap={8} justify="end" fixed>
|
|
446
443
|
<Button
|
|
447
444
|
variant="ghost"
|
|
448
445
|
size="sm"
|
|
@@ -453,9 +450,9 @@ export function ConversationManagerPage({
|
|
|
453
450
|
>
|
|
454
451
|
{t("page.previous")}
|
|
455
452
|
</Button>
|
|
456
|
-
<
|
|
453
|
+
<Text size="sm" tone="tertiary" tabular>
|
|
457
454
|
{t("page.label", { page: currentPage, total: pageCount })}
|
|
458
|
-
</
|
|
455
|
+
</Text>
|
|
459
456
|
<Button
|
|
460
457
|
variant="ghost"
|
|
461
458
|
size="sm"
|
|
@@ -466,9 +463,9 @@ export function ConversationManagerPage({
|
|
|
466
463
|
>
|
|
467
464
|
{t("page.next")}
|
|
468
465
|
</Button>
|
|
469
|
-
</
|
|
466
|
+
</Row>
|
|
470
467
|
) : null}
|
|
471
|
-
</
|
|
468
|
+
</Stack>
|
|
472
469
|
</>
|
|
473
470
|
)}
|
|
474
471
|
<Modal
|
|
@@ -529,12 +526,12 @@ export function ConversationManagerPage({
|
|
|
529
526
|
/>
|
|
530
527
|
{/* 运行期不给关闭手段:没有关闭按钮,mask 与 Escape 都只走空 onClose。 */}
|
|
531
528
|
<Modal open={gcPhase === "running"} onClose={() => {}} headless title={t("gc.title")}>
|
|
532
|
-
<
|
|
533
|
-
<
|
|
534
|
-
<p
|
|
535
|
-
</
|
|
529
|
+
<Stack align="center" gap={14} pad="blocking">
|
|
530
|
+
<Spinner label={t("gc.running")} />
|
|
531
|
+
<Text as="p" size="md">{t("gc.running")}</Text>
|
|
532
|
+
</Stack>
|
|
536
533
|
</Modal>
|
|
537
|
-
</
|
|
534
|
+
</Stack>
|
|
538
535
|
);
|
|
539
536
|
}
|
|
540
537
|
|
|
@@ -647,6 +644,8 @@ interface UsageListRow {
|
|
|
647
644
|
key: string;
|
|
648
645
|
label: string;
|
|
649
646
|
totals: UsageTotals;
|
|
647
|
+
// 子代理派生会话的行(按会话维度才有此维度;其余维度不带标记)。
|
|
648
|
+
subagent?: boolean;
|
|
650
649
|
}
|
|
651
650
|
|
|
652
651
|
function emptyTotals(): UsageTotals {
|
|
@@ -663,7 +662,7 @@ function emptyTotals(): UsageTotals {
|
|
|
663
662
|
};
|
|
664
663
|
}
|
|
665
664
|
|
|
666
|
-
//
|
|
665
|
+
// 把桶按一个键折叠成行并按用量降序(按模型维度用它:人类的桶与子代理的桶折进同一行)。
|
|
667
666
|
function foldBuckets(
|
|
668
667
|
buckets: readonly UsageBucket[],
|
|
669
668
|
keyOf: (bucket: UsageBucket) => string,
|
|
@@ -684,43 +683,54 @@ function foldBuckets(
|
|
|
684
683
|
);
|
|
685
684
|
}
|
|
686
685
|
|
|
687
|
-
// 一行用量:label
|
|
686
|
+
// 一行用量:label 在上(子代理行另带标记),下面是横向排布的单项。
|
|
688
687
|
function UsageRow({
|
|
689
688
|
rowKey,
|
|
690
689
|
label,
|
|
691
690
|
totals,
|
|
692
691
|
t,
|
|
692
|
+
subagent,
|
|
693
693
|
withActivity = true,
|
|
694
694
|
}: {
|
|
695
695
|
rowKey: string;
|
|
696
696
|
label: string;
|
|
697
697
|
totals: UsageTotals;
|
|
698
698
|
t: Translate;
|
|
699
|
+
subagent?: boolean;
|
|
699
700
|
withActivity?: boolean;
|
|
700
701
|
}): ReactNode {
|
|
701
702
|
return (
|
|
702
|
-
<
|
|
703
|
-
|
|
704
|
-
|
|
703
|
+
<Stack
|
|
704
|
+
as="li"
|
|
705
|
+
gap={6}
|
|
706
|
+
boxed
|
|
707
|
+
pad="row"
|
|
708
|
+
fixed
|
|
709
|
+
data-usage-key={rowKey}
|
|
710
|
+
{...(subagent === undefined ? {} : { "data-subagent": subagent ? "true" : "false" })}
|
|
711
|
+
>
|
|
712
|
+
<Row gap={8} grow>
|
|
713
|
+
<Text size="sm" tone="tertiary" truncate>{label}</Text>
|
|
714
|
+
{subagent === true ? <Tag tone="quiet">{t("subagent")}</Tag> : null}
|
|
715
|
+
</Row>
|
|
716
|
+
<Row gap="wide" wrap>
|
|
705
717
|
{usageMetrics(totals, t, { withActivity }).map((metric) => (
|
|
706
|
-
<
|
|
718
|
+
<Stack
|
|
707
719
|
key={metric.key}
|
|
708
|
-
{...styling.props(styles.usageMetric)}
|
|
709
720
|
data-usage-cell={metric.key}
|
|
710
|
-
data-usage-value={metric.value}
|
|
711
|
-
|
|
712
|
-
<
|
|
713
|
-
<span {...styling.props(styles.usageMetricValue)}>
|
|
721
|
+
data-usage-value={metric.value} gap={2}>
|
|
722
|
+
<Text size="sm" tone="tertiary">{metric.label}</Text>
|
|
723
|
+
<Text size="lg" weight="medium" tabular>
|
|
714
724
|
{metric.kind === "percent"
|
|
715
725
|
? formatPercent(metric.value)
|
|
716
726
|
: metric.kind === "count"
|
|
717
727
|
? formatCount(metric.value)
|
|
718
728
|
: formatTokens(metric.value)}
|
|
719
|
-
</
|
|
720
|
-
</
|
|
729
|
+
</Text>
|
|
730
|
+
</Stack>
|
|
721
731
|
))}
|
|
722
|
-
</
|
|
723
|
-
</
|
|
732
|
+
</Row>
|
|
733
|
+
</Stack>
|
|
724
734
|
);
|
|
725
735
|
}
|
|
726
736
|
|
|
@@ -735,13 +745,13 @@ function UsageList({
|
|
|
735
745
|
}): ReactNode {
|
|
736
746
|
if (rows.length === 0) {
|
|
737
747
|
return (
|
|
738
|
-
<
|
|
748
|
+
<Text data-usage-status="empty" as="p" size="md" tone="tertiary">
|
|
739
749
|
{t("usage.empty")}
|
|
740
|
-
</
|
|
750
|
+
</Text>
|
|
741
751
|
);
|
|
742
752
|
}
|
|
743
753
|
return (
|
|
744
|
-
<ul {
|
|
754
|
+
<Stack as="ul" gap={4} plain>
|
|
745
755
|
{rows.map((row) => (
|
|
746
756
|
<UsageRow
|
|
747
757
|
key={row.key}
|
|
@@ -749,20 +759,21 @@ function UsageList({
|
|
|
749
759
|
label={row.label}
|
|
750
760
|
totals={row.totals}
|
|
751
761
|
t={t}
|
|
762
|
+
{...(row.subagent === undefined ? {} : { subagent: row.subagent })}
|
|
752
763
|
withActivity={withActivity}
|
|
753
764
|
/>
|
|
754
765
|
))}
|
|
755
|
-
</
|
|
766
|
+
</Stack>
|
|
756
767
|
);
|
|
757
768
|
}
|
|
758
769
|
|
|
759
770
|
// 总览:全部与「其中子代理」两行,与列表行同形。
|
|
760
771
|
function UsageOverview({ report, t }: { report: SessionUsageReport; t: Translate }): ReactNode {
|
|
761
772
|
return (
|
|
762
|
-
<ul {
|
|
773
|
+
<Stack as="ul" gap={4} plain>
|
|
763
774
|
<UsageRow rowKey="all" label={t("usage.all")} totals={report.totals} t={t} />
|
|
764
775
|
<UsageRow rowKey="subagent" label={t("usage.subagentOnly")} totals={report.subagent} t={t} />
|
|
765
|
-
</
|
|
776
|
+
</Stack>
|
|
766
777
|
);
|
|
767
778
|
}
|
|
768
779
|
|
|
@@ -775,6 +786,8 @@ function UsageView({
|
|
|
775
786
|
onTab,
|
|
776
787
|
range,
|
|
777
788
|
onRange,
|
|
789
|
+
onlySubagents,
|
|
790
|
+
onOnlySubagents,
|
|
778
791
|
t,
|
|
779
792
|
}: {
|
|
780
793
|
report: SessionUsageReport | null;
|
|
@@ -784,6 +797,8 @@ function UsageView({
|
|
|
784
797
|
onTab: (tab: UsageTab) => void;
|
|
785
798
|
range: UsageRange;
|
|
786
799
|
onRange: (range: UsageRange) => void;
|
|
800
|
+
onlySubagents: boolean;
|
|
801
|
+
onOnlySubagents: (next: boolean) => void;
|
|
787
802
|
t: Translate;
|
|
788
803
|
}): ReactNode {
|
|
789
804
|
const items: UsageTab[] = ["overview", "models", "sessions"];
|
|
@@ -802,64 +817,59 @@ function UsageView({
|
|
|
802
817
|
`${bucket.provider ?? t("usage.unknownModel")} / ${bucket.model ?? t("usage.unknownModel")}`,
|
|
803
818
|
)
|
|
804
819
|
: report.sessions
|
|
820
|
+
// 子代理会话单个用量小,混排时会被行数上限挤掉:给一个只看它们的开关(过滤在本页,数据已全量在手)。
|
|
821
|
+
.filter((row) => !onlySubagents || row.subagent)
|
|
805
822
|
.map((row) => ({
|
|
806
823
|
key: row.sessionId,
|
|
807
824
|
label: row.title ?? row.sessionId,
|
|
808
825
|
totals: row,
|
|
826
|
+
subagent: row.subagent,
|
|
809
827
|
}))
|
|
810
828
|
.sort((left, right) => sortWeight(right.totals) - sortWeight(left.totals))
|
|
811
829
|
.slice(0, USAGE_SESSION_ROWS);
|
|
812
830
|
return (
|
|
813
|
-
<
|
|
814
|
-
<
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
{
|
|
833
|
-
<
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
onClick={() => {
|
|
841
|
-
onTab(item);
|
|
842
|
-
}}
|
|
843
|
-
>
|
|
844
|
-
{labels[item]}
|
|
845
|
-
</button>
|
|
846
|
-
))}
|
|
847
|
-
</div>
|
|
831
|
+
<Stack data-usage-view={tab} data-usage-range={range} gap={12} fixed>
|
|
832
|
+
<Tabs
|
|
833
|
+
role="radiogroup"
|
|
834
|
+
label={t("usage.range")}
|
|
835
|
+
items={USAGE_RANGES.map((option) => ({ value: option, label: rangeLabel(option, t) }))}
|
|
836
|
+
value={range}
|
|
837
|
+
onChange={(next) => {
|
|
838
|
+
onRange(next as UsageRange);
|
|
839
|
+
}}
|
|
840
|
+
/>
|
|
841
|
+
<Tabs
|
|
842
|
+
label={t("usage.range")}
|
|
843
|
+
items={items.map((item) => ({ value: item, label: labels[item] }))}
|
|
844
|
+
value={tab}
|
|
845
|
+
onChange={(next) => {
|
|
846
|
+
onTab(next as UsageTab);
|
|
847
|
+
}}
|
|
848
|
+
/>
|
|
849
|
+
{tab === "sessions" ? (
|
|
850
|
+
<Row data-filter="usage-subagents" gap={16} fixed>
|
|
851
|
+
<Checkbox
|
|
852
|
+
checked={onlySubagents}
|
|
853
|
+
label={t("usage.onlySubagents")}
|
|
854
|
+
onChange={onOnlySubagents}
|
|
855
|
+
/>
|
|
856
|
+
</Row>
|
|
857
|
+
) : null}
|
|
848
858
|
{loading ? (
|
|
849
|
-
<
|
|
859
|
+
<Text data-usage-status="loading" as="p" size="md" tone="tertiary">
|
|
850
860
|
{t("usage.loading")}
|
|
851
|
-
</
|
|
861
|
+
</Text>
|
|
852
862
|
) : null}
|
|
853
863
|
{error === null ? null : (
|
|
854
|
-
<
|
|
864
|
+
<Text data-usage-status="error" role="alert" as="p" size="md" tone="danger">
|
|
855
865
|
{error}
|
|
856
|
-
</
|
|
866
|
+
</Text>
|
|
857
867
|
)}
|
|
858
868
|
{report === null ? null : tab === "overview" ? (
|
|
859
869
|
<UsageOverview report={report} t={t} />
|
|
860
870
|
) : (
|
|
861
871
|
<UsageList rows={rows} t={t} withActivity={tab === "sessions"} />
|
|
862
872
|
)}
|
|
863
|
-
</
|
|
873
|
+
</Stack>
|
|
864
874
|
);
|
|
865
875
|
}
|
package/src/client/locales.ts
CHANGED
|
@@ -36,6 +36,7 @@ export const zh = {
|
|
|
36
36
|
"usage.userInputs": "用户输入",
|
|
37
37
|
"usage.toolCalls": "工具调用",
|
|
38
38
|
"usage.subagentOnly": "其中子代理",
|
|
39
|
+
"usage.onlySubagents": "只看子代理会话",
|
|
39
40
|
"usage.unknownModel": "未知模型",
|
|
40
41
|
archive: "归档",
|
|
41
42
|
archiveNamed: "归档 {title}",
|
|
@@ -116,6 +117,7 @@ export const en = {
|
|
|
116
117
|
"usage.userInputs": "User inputs",
|
|
117
118
|
"usage.toolCalls": "Tool calls",
|
|
118
119
|
"usage.subagentOnly": "Subagent share",
|
|
120
|
+
"usage.onlySubagents": "Subagent sessions only",
|
|
119
121
|
"usage.unknownModel": "Unknown model",
|
|
120
122
|
archive: "Archive",
|
|
121
123
|
archiveNamed: "Archive {title}",
|