@morlay/ui-conversation-manager 0.1.3 → 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/dist/client.d.cts CHANGED
@@ -38,6 +38,7 @@ declare const zh: {
38
38
  "usage.userInputs": string;
39
39
  "usage.toolCalls": string;
40
40
  "usage.subagentOnly": string;
41
+ "usage.onlySubagents": string;
41
42
  "usage.unknownModel": string;
42
43
  archive: string;
43
44
  archiveNamed: string;
package/dist/client.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import "react";
2
1
  import { Context, Service } from "@deepseek-ai/cordis";
2
+ import "react";
3
3
  import { MainPanelId } from "@deepseek-ai/dsh-client-ui-layout/client";
4
4
  import { InjectFace, PropsLocale, PropsRuntime } from "@deepseek-ai/dsh-client-ui-slots";
5
5
  //#region src/client/locales.d.ts
@@ -38,6 +38,7 @@ declare const zh: {
38
38
  "usage.userInputs": string;
39
39
  "usage.toolCalls": string;
40
40
  "usage.subagentOnly": string;
41
+ "usage.onlySubagents": string;
41
42
  "usage.unknownModel": string;
42
43
  archive: string;
43
44
  archiveNamed: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morlay/ui-conversation-manager",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "「对话管理」全局面板页(浏览器半):会话与统计两层 Tabs——会话视图给出全量会话(含已归档)的搜索、分页、归档 / 取消归档 / 导出 / 删除、导入为新会话与孤儿数据 GC,统计视图给出全部对话的 token 用量。",
5
5
  "keywords": [
6
6
  "archived-sessions",
@@ -40,17 +40,17 @@
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@deepseek-ai/cordis": "^4.0.4",
43
- "@deepseek-ai/dsh-api-session-controller": "^0.2.0-rc.1",
44
- "@deepseek-ai/dsh-client-locale": "^0.2.0-rc.1",
45
- "@deepseek-ai/dsh-client-ui-layout": "^0.2.0-rc.1",
46
- "@deepseek-ai/dsh-client-ui-primitives": "^0.2.0-rc.1",
47
- "@deepseek-ai/dsh-client-ui-renderer": "^0.2.0-rc.1",
48
- "@deepseek-ai/dsh-client-ui-session": "^0.2.0-rc.1",
49
- "@deepseek-ai/dsh-client-ui-sidebar": "^0.2.0-rc.1",
50
- "@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.1",
51
- "@deepseek-ai/dsh-client-ui-workspace": "^0.2.0-rc.1",
52
- "@deepseek-ai/dsh-session": "^0.2.0-rc.1",
53
- "@morlay/dsh-client-ui-primitives": "0.0.6"
43
+ "@deepseek-ai/dsh-api-session-controller": "^0.2.0-rc.2",
44
+ "@deepseek-ai/dsh-client-locale": "^0.2.0-rc.2",
45
+ "@deepseek-ai/dsh-client-ui-layout": "^0.2.0-rc.2",
46
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.2.0-rc.2",
47
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.2.0-rc.2",
48
+ "@deepseek-ai/dsh-client-ui-session": "^0.2.0-rc.2",
49
+ "@deepseek-ai/dsh-client-ui-sidebar": "^0.2.0-rc.2",
50
+ "@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.2",
51
+ "@deepseek-ai/dsh-client-ui-workspace": "^0.2.0-rc.2",
52
+ "@deepseek-ai/dsh-session": "^0.2.0-rc.2",
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 "@deepseek-ai/dsh-client-ui-primitives";
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 {...styling.props(styles.status)}>{rowsFailure ?? t("loading")}</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
- <div {...styling.props(styles.page)} data-view={view}>
225
- <div {...styling.props(styles.header)}>
226
- <h1 {...styling.props(styles.title)}>{t("title")}</h1>
227
- <div {...styling.props(styles.tabs)} role="tablist">
228
- <button
229
- type="button"
230
- role="tab"
231
- data-tab="sessions"
232
- aria-selected={view === "sessions"}
233
- className={styling.className(styles.tab, view === "sessions" && styles.tabActive)}
234
- onClick={() => {
235
- setView("sessions");
236
- }}
237
- >
238
- {t("view.sessions")}
239
- </button>
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
- </div>
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
- </div>
298
+ </Row>
307
299
  {view === "usage" ? (
308
- <div {...styling.props(styles.scroll)} data-scroll="page">
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
- </div>
313
+ </Stack>
320
314
  ) : (
321
315
  <>
322
- <div {...styling.props(styles.filters)}>
323
- <Input
324
- className={styling.className(styles.search)}
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
- </div>
347
- <div {...styling.props(styles.scroll)} data-scroll="page">
340
+ </Row>
341
+ <Stack data-scroll="page" gap={12} scroll>
348
342
  {notice === null ? null : (
349
- <p {...styling.props(styles.status)} data-notice="result">
343
+ <Text data-notice="result" as="p" size="md" tone="tertiary">
350
344
  {notice}
351
- </p>
345
+ </Text>
352
346
  )}
353
347
  {failure === null ? null : (
354
- <p {...styling.props(styles.failure)} data-failure="result" role="alert">
348
+ <Text data-failure="result" role="alert" as="p" size="md" tone="danger">
355
349
  {failure}
356
- </p>
350
+ </Text>
357
351
  )}
358
352
  {total === 0 ? (
359
- <p {...styling.props(styles.status)} data-status="empty">
353
+ <Text data-status="empty" as="p" size="md" tone="tertiary">
360
354
  {t(settledQuery === "" ? "empty" : "emptySearch")}
361
- </p>
355
+ </Text>
362
356
  ) : null}
363
357
  {visible.length > 0 ? (
364
- <ul {...styling.props(styles.list)}>
358
+ <Stack as="ul" gap={4} plain>
365
359
  {visible.map((row) => (
366
- <li
360
+ <Row
361
+ as="li"
367
362
  key={row.id}
368
- {...styling.props(styles.row)}
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
- <span {...styling.props(styles.identity)}>
374
- <span {...styling.props(styles.titleLine)}>
375
- <span {...styling.props(styles.rowTitle)}>{row.title}</span>
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
- </span>
379
- <span {...styling.props(styles.meta)}>
377
+ </Row>
378
+ <Text size="sm" tone="tertiary">
380
379
  {[row.workspace, timeLabel(row.updatedAt, now, t)].join(" · ")}
381
- </span>
382
- </span>
383
- <span {...styling.props(styles.actions)}>
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
- </span>
435
- </li>
433
+ </Row>
434
+ </Row>
436
435
  ))}
437
- </ul>
436
+ </Stack>
438
437
  ) : null}
439
438
  {matched.length > 0 ? (
440
- <div
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
- <span {...styling.props(styles.paginationLabel)}>
453
+ <Text size="sm" tone="tertiary" tabular>
457
454
  {t("page.label", { page: currentPage, total: pageCount })}
458
- </span>
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
- </div>
466
+ </Row>
470
467
  ) : null}
471
- </div>
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
- <div {...styling.props(styles.blocking)}>
533
- <span {...styling.props(styles.spinner)} aria-hidden="true" />
534
- <p {...styling.props(styles.blockingText)}>{t("gc.running")}</p>
535
- </div>
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
- </div>
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
- <li {...styling.props(styles.usageRow)} data-usage-key={rowKey}>
703
- <span {...styling.props(styles.usageRowLabel)}>{label}</span>
704
- <span {...styling.props(styles.usageMetrics)}>
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
- <span
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
- <span {...styling.props(styles.usageMetricLabel)}>{metric.label}</span>
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
- </span>
720
- </span>
729
+ </Text>
730
+ </Stack>
721
731
  ))}
722
- </span>
723
- </li>
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
- <p {...styling.props(styles.status)} data-usage-status="empty">
748
+ <Text data-usage-status="empty" as="p" size="md" tone="tertiary">
739
749
  {t("usage.empty")}
740
- </p>
750
+ </Text>
741
751
  );
742
752
  }
743
753
  return (
744
- <ul {...styling.props(styles.usageList)}>
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
- </ul>
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 {...styling.props(styles.usageList)}>
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
- </ul>
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
- <div {...styling.props(styles.usage)} data-usage-view={tab} data-usage-range={range}>
814
- <div {...styling.props(styles.tabs)} role="radiogroup" aria-label={t("usage.range")}>
815
- {USAGE_RANGES.map((option) => (
816
- <button
817
- key={option}
818
- type="button"
819
- role="radio"
820
- aria-checked={range === option}
821
- data-range={option}
822
- className={styling.className(styles.tab, range === option && styles.tabActive)}
823
- onClick={() => {
824
- onRange(option);
825
- }}
826
- >
827
- {rangeLabel(option, t)}
828
- </button>
829
- ))}
830
- </div>
831
- <div {...styling.props(styles.tabs)} role="tablist">
832
- {items.map((item) => (
833
- <button
834
- key={item}
835
- type="button"
836
- role="tab"
837
- data-usage-tab={item}
838
- aria-selected={tab === item}
839
- className={styling.className(styles.tab, tab === item && styles.tabActive)}
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
- <p {...styling.props(styles.status)} data-usage-status="loading">
859
+ <Text data-usage-status="loading" as="p" size="md" tone="tertiary">
850
860
  {t("usage.loading")}
851
- </p>
861
+ </Text>
852
862
  ) : null}
853
863
  {error === null ? null : (
854
- <p {...styling.props(styles.failure)} data-usage-status="error" role="alert">
864
+ <Text data-usage-status="error" role="alert" as="p" size="md" tone="danger">
855
865
  {error}
856
- </p>
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
- </div>
873
+ </Stack>
864
874
  );
865
875
  }
@@ -9,6 +9,7 @@ import type {} from "@deepseek-ai/dsh-client-ui-sidebar/client";
9
9
  import type {} from "@deepseek-ai/dsh-client-ui-session/client";
10
10
  import type {} from "@deepseek-ai/dsh-client-ui-workspace/client";
11
11
  import type {} from "@deepseek-ai/dsh-api-session-controller/client";
12
+ import { apply as installUiPrimitives } from "@morlay/dsh-client-ui-primitives/client";
12
13
  import { ConversationManagerController } from "./controller.ts";
13
14
  import { ConversationManagerPage } from "./ConversationManagerPage.tsx";
14
15
  import { ConversationManagerIcon } from "./ConversationManagerIcon.tsx";
@@ -39,6 +40,8 @@ export const PANEL_ORDER = 1;
39
40
  export const inject = ["slots", "locale", "uiWorkspace", "sessions"];
40
41
 
41
42
  export function apply(ctx: ClientContext): void {
43
+ // 基础面随本包 inline(不再是装配行):装上它提供的字典、字段槽与按行配置页;多份副本只装一次。
44
+ installUiPrimitives(ctx);
42
45
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "ui-conversation-manager: dictionaries");
43
46
  const t = ctx.locale.bind(NS);
44
47
  // ctx.sessions 的类型被别的 client 半的声明占住(SessionStore),按既有做法从服务面取。
@@ -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}",