dsh-remember 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,7 +29,7 @@ The memory entry at the bottom of the sidebar opens the status panel.
29
29
  <img src="docs/shots/01-sidebar-panel.png" width="420" alt="Sidebar entry and status panel">
30
30
  </p>
31
31
 
32
- The memory viewer: overview, summary, index, and records.
32
+ The memory viewer: overview (stats, most-cited, records), summary, and registry.
33
33
 
34
34
  <p align="center">
35
35
  <img src="docs/shots/02-viewer-overview.png" width="330" alt="Overview">
@@ -38,7 +38,6 @@ The memory viewer: overview, summary, index, and records.
38
38
 
39
39
  <p align="center">
40
40
  <img src="docs/shots/04-viewer-index.png" width="330" alt="Index">
41
- <img src="docs/shots/05-viewer-records.png" width="330" alt="Records">
42
41
  </p>
43
42
 
44
43
  ## Install
package/README.zh-CN.md CHANGED
@@ -29,7 +29,7 @@
29
29
  <img src="docs/shots/01-sidebar-panel.png" width="420" alt="侧栏入口与状态面板">
30
30
  </p>
31
31
 
32
- 记忆浏览页:概览、摘要、注册表、记录。
32
+ 记忆浏览页:概览(统计、最常引用、记录)、摘要、注册表。
33
33
 
34
34
  <p align="center">
35
35
  <img src="docs/shots/02-viewer-overview.png" width="330" alt="概览">
@@ -38,7 +38,6 @@
38
38
 
39
39
  <p align="center">
40
40
  <img src="docs/shots/04-viewer-index.png" width="330" alt="注册表">
41
- <img src="docs/shots/05-viewer-records.png" width="330" alt="记录">
42
41
  </p>
43
42
 
44
43
  ## 安装
package/lib/client.js CHANGED
@@ -583,62 +583,101 @@ function ModelSelect(props) {
583
583
  style: inputStyle(false)
584
584
  }, options);
585
585
  }
586
+ /** Upper bound for an import traveling base64 through the settings mirror. */
587
+ const IMPORT_LIMIT_BYTES = 32 * 1024 * 1024;
588
+ /** Save bundle bytes: native save dialog where available, download fallback. */
589
+ async function saveBundleFile(name$1, base64) {
590
+ const binary = atob(base64);
591
+ const bytes = new Uint8Array(binary.length);
592
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
593
+ const picker = window.showSaveFilePicker;
594
+ if (picker != null) try {
595
+ const writable = await (await picker.call(window, { suggestedName: name$1 })).createWritable();
596
+ await writable.write(bytes);
597
+ await writable.close();
598
+ return;
599
+ } catch (error) {
600
+ if (error?.name === "AbortError") return;
601
+ }
602
+ const url = URL.createObjectURL(new Blob([bytes]));
603
+ const anchor = document.createElement("a");
604
+ anchor.href = url;
605
+ anchor.download = name$1;
606
+ anchor.click();
607
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
608
+ }
586
609
  /**
587
- * Import/export entry: the host-side transfer runs through the memory-commands
588
- * channel (path field + action), and the outcome comes back as a `transfer:`-
589
- * prefixed status detail line.
610
+ * Import/export entry: export asks the host for the bundle and saves it via
611
+ * the browser's file dialog; import reads a picked file and ships it base64
612
+ * through the memory-commands channel. Outcomes come back as `transfer:`-
613
+ * prefixed status detail lines.
590
614
  */
591
615
  function TransferSection(props) {
592
616
  const { t, commands } = props;
593
- const [exportPath, setExportPath] = (0, react.useState)("");
594
- const [importPath, setImportPath] = (0, react.useState)("");
595
617
  const [requested, setRequested] = (0, react.useState)(null);
596
- const send = (action, file) => {
597
- if (commands == null || file.trim() === "") return;
598
- setRequested(action);
618
+ const [localError, setLocalError] = (0, react.useState)("");
619
+ const fileInput = (0, react.useRef)(null);
620
+ const lastExportAt = (0, react.useRef)(0);
621
+ const send = (action, path = "", payload = "") => {
622
+ if (commands == null) return;
599
623
  (async () => {
600
- await commands.set("path", file.trim());
624
+ await commands.set("path", path);
625
+ await commands.set("payload", payload);
601
626
  await commands.set("action", action);
602
627
  await commands.set("requestedAt", Date.now());
603
628
  })();
604
629
  };
605
- const feedback = props.statusDetail?.startsWith("transfer:") === true ? props.statusDetail.slice(9).trim() : null;
606
- const feedbackIsError = feedback != null && /fail|need|must|unsupported|error/iu.test(feedback);
607
- const transferRow = (value, setValue, labelKey, action) => {
608
- const empty = value.trim() === "";
609
- return (0, react.createElement)("div", {
610
- className: "dshm-row",
611
- style: rowStyle()
612
- }, (0, react.createElement)("div", { style: labelStackStyle() }, (0, react.createElement)("span", { style: { fontSize: 13 } }, t(labelKey))), (0, react.createElement)("div", { style: {
613
- display: "flex",
614
- alignItems: "center",
615
- gap: 8,
616
- flexShrink: 0
617
- } }, (0, react.createElement)("input", {
618
- type: "text",
619
- value,
620
- spellCheck: false,
621
- className: "dshm-input",
622
- placeholder: "C:\\…\\memory.dshmem.json",
623
- onChange: (event) => {
624
- setValue(event.target.value);
625
- setRequested(null);
626
- },
627
- style: inputStyle(false)
628
- }), (0, react.createElement)("button", {
629
- type: "button",
630
- disabled: commands == null || empty,
631
- className: "dshm-btn",
632
- onClick: () => send(action, value),
633
- style: primaryButtonStyle(commands == null || empty)
634
- }, t(`transfer_${action}`))));
630
+ (0, react.useEffect)(() => {
631
+ const bundle = props.exportBundle;
632
+ if (bundle == null || bundle.data === "" || bundle.at <= lastExportAt.current) return void 0;
633
+ lastExportAt.current = bundle.at;
634
+ saveBundleFile(bundle.name, bundle.data).finally(() => send("export-done"));
635
+ }, [props.exportBundle?.at ?? 0]);
636
+ const pickImport = (event) => {
637
+ const file = event.target.files?.[0];
638
+ event.target.value = "";
639
+ if (file == null) return;
640
+ if (file.size > IMPORT_LIMIT_BYTES) {
641
+ setLocalError(t("transfer_tooBig"));
642
+ return;
643
+ }
644
+ setLocalError("");
645
+ setRequested("import");
646
+ const reader = new FileReader();
647
+ reader.onload = () => {
648
+ const result = String(reader.result ?? "");
649
+ send("import", "", result.slice(result.indexOf(",") + 1));
650
+ };
651
+ reader.onerror = () => setLocalError(t("transfer_readFailed"));
652
+ reader.readAsDataURL(file);
635
653
  };
636
- return (0, react.createElement)("section", { style: sectionStyle() }, (0, react.createElement)("h3", { style: sectionHeaderStyle() }, t("group_transfer")), (0, react.createElement)("div", { style: { borderTop: `1px solid ${BORDER$1}` } }, transferRow(exportPath, setExportPath, "transfer_exportPath", "export"), transferRow(importPath, setImportPath, "transfer_importPath", "import")), (0, react.createElement)("p", { style: {
654
+ const feedback = localError !== "" ? localError : props.statusDetail?.startsWith("transfer:") === true ? props.statusDetail.slice(9).trim() : null;
655
+ const feedbackIsError = feedback != null && (localError !== "" || /fail|need|must|unsupported|error/iu.test(feedback));
656
+ const transferRow = (labelKey, descKey, action) => (0, react.createElement)("div", {
657
+ className: "dshm-row",
658
+ style: rowStyle()
659
+ }, (0, react.createElement)("div", { style: labelStackStyle() }, (0, react.createElement)("span", { style: { fontSize: 13 } }, t(labelKey)), (0, react.createElement)("span", { style: {
637
660
  fontSize: 11.5,
638
- opacity: .5,
639
- margin: "8px 2px 0",
640
- lineHeight: 1.5
641
- } }, t("transfer_hint")), (0, react.createElement)("div", { style: {
661
+ opacity: .5
662
+ } }, t(descKey))), (0, react.createElement)("button", {
663
+ type: "button",
664
+ disabled: commands == null,
665
+ className: "dshm-btn",
666
+ onClick: () => {
667
+ setLocalError("");
668
+ setRequested(action);
669
+ if (action === "export") send("export");
670
+ else fileInput.current?.click();
671
+ },
672
+ style: primaryButtonStyle(commands == null)
673
+ }, t(`transfer_${action}`)));
674
+ return (0, react.createElement)("section", { style: sectionStyle() }, (0, react.createElement)("h3", { style: sectionHeaderStyle() }, t("group_transfer")), (0, react.createElement)("div", { style: { borderTop: `1px solid ${BORDER$1}` } }, transferRow("transfer_exportLabel", "transfer_exportDesc", "export"), transferRow("transfer_importLabel", "transfer_importDesc", "import")), (0, react.createElement)("input", {
675
+ type: "file",
676
+ accept: ".json,.gz,.dshmem",
677
+ ref: fileInput,
678
+ style: { display: "none" },
679
+ onChange: pickImport
680
+ }), (0, react.createElement)("div", { style: {
642
681
  minHeight: 18,
643
682
  margin: "4px 2px 0"
644
683
  } }, feedback != null ? (0, react.createElement)("span", { style: {
@@ -731,6 +770,13 @@ function renderMemoryPage(props) {
731
770
  } }, t("unavailable"));
732
771
  const disabled = !state.writable;
733
772
  const status = statusSnapshot?.status === "ready" ? statusSnapshot.value : void 0;
773
+ const viewSnapshot = props.useMemoryView != null ? props.useMemoryView((value) => value) : void 0;
774
+ const viewValue = viewSnapshot?.status === "ready" ? viewSnapshot.value : void 0;
775
+ const exportBundle = viewValue != null && (viewValue.exportData ?? "") !== "" ? {
776
+ name: viewValue.exportName ?? "memory.dshmem",
777
+ data: viewValue.exportData,
778
+ at: viewValue.exportAt ?? 0
779
+ } : void 0;
734
780
  const active = status?.phase === "phase1" || status?.phase === "phase2";
735
781
  const statusLabel = active ? status?.phase === "phase1" ? t("phase1") : t("phase2") : t("statusIdle");
736
782
  const groups = /* @__PURE__ */ new Map();
@@ -824,7 +870,8 @@ function renderMemoryPage(props) {
824
870
  } }, status?.updatedAt ? new Date(status.updatedAt).toLocaleTimeString() : "")), fieldSection("general"), fieldSection("models"), fieldSection("schedule"), fieldSection("recall"), (0, react.createElement)(TransferSection, {
825
871
  t,
826
872
  commands: props.commands,
827
- statusDetail: status?.detail
873
+ statusDetail: status?.detail,
874
+ exportBundle
828
875
  }), (0, react.createElement)(AdvancedSection, {
829
876
  t,
830
877
  fields: groups.get("advanced") ?? [],
@@ -1322,7 +1369,11 @@ function ensureViewerStyles() {
1322
1369
  ".dshmv-usage { display: flex; align-items: center; gap: 6px; }",
1323
1370
  ".dshmv-usage-bar { height: 5px; border-radius: 3px; background: var(--dsw-alias-brand-primary, #4d6bfe); min-width: 2px; transition: width 200ms ease; }",
1324
1371
  ".dshmv-empty { color: var(--dsw-alias-label-tertiary, rgba(0,0,0,0.45)); padding: 24px 0; text-align: center; font-size: 12px; }",
1325
- ".dshmv-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; color: var(--dsw-alias-label-tertiary, rgba(0,0,0,0.45)); }"
1372
+ ".dshmv-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; color: var(--dsw-alias-label-tertiary, rgba(0,0,0,0.45)); }",
1373
+ ".dshmv-section { margin: 2px 0 8px; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-secondary, rgba(0,0,0,0.65)); }",
1374
+ ".dshmv-toprow { display: flex; align-items: center; gap: 10px; padding: 7px 10px; border-radius: 8px; border: 0.5px solid var(--dsw-alias-border-l2, rgba(0,0,0,0.1)); background: var(--dsw-alias-fill-tertiary, rgba(0,0,0,0.04)); margin-bottom: 6px; }",
1375
+ ".dshmv-toprow-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; font-size: 12px; }",
1376
+ ".dshmv-toprow-meta { flex: 0 0 auto; font-size: 11px; color: var(--dsw-alias-label-tertiary, rgba(0,0,0,0.45)); font-variant-numeric: tabular-nums; }"
1326
1377
  ].join("\n");
1327
1378
  document.head.appendChild(tag);
1328
1379
  styleInjected = true;
@@ -1406,12 +1457,10 @@ function renderViewer(props) {
1406
1457
  await commands.set("requestedAt", Date.now());
1407
1458
  })();
1408
1459
  };
1409
- const effectiveText = showTranslated && hasTranslation ? texts.translatedSummary : texts.summary;
1410
1460
  return (0, react.createElement)("div", { className: "dshmv-root" }, (0, react.createElement)("div", { className: "dshmv-tabs" }, ...[
1411
1461
  "overview",
1412
1462
  "summary",
1413
- "index",
1414
- "records"
1463
+ "index"
1415
1464
  ].map((id) => (0, react.createElement)("button", {
1416
1465
  key: id,
1417
1466
  type: "button",
@@ -1457,30 +1506,54 @@ function renderViewer(props) {
1457
1506
  }, t("viewer_rebuild")), showTranslated && hasTranslation && view.translatedStale === true ? (0, react.createElement)("span", { className: "dshmv-badge dshmv-badge-stale" }, t("viewer_stale")) : null, showTranslated && hasTranslation && view.translatedStale !== true ? (0, react.createElement)("span", { className: "dshmv-badge dshmv-badge-ok" }, t("viewer_fresh")) : null, (0, react.createElement)("span", { className: "dshmv-status" }, status.detail ?? "")), (0, react.createElement)("div", {
1458
1507
  className: "dshmv-body",
1459
1508
  "data-fill": "true"
1460
- }, tab === "overview" ? renderOverview(t, texts, effectiveText, showTranslated, status, records) : tab === "summary" ? (0, react.createElement)(SummaryView, {
1509
+ }, tab === "overview" ? (0, react.createElement)(OverviewView, {
1461
1510
  t,
1462
1511
  texts,
1463
- showTranslated
1464
- }) : tab === "index" ? renderIndex(t, texts, showTranslated) : (0, react.createElement)(RecordsView, {
1465
- t,
1512
+ showTranslated,
1513
+ status,
1466
1514
  records
1467
- })));
1515
+ }) : tab === "summary" ? (0, react.createElement)(SummaryView, {
1516
+ t,
1517
+ texts,
1518
+ showTranslated
1519
+ }) : renderIndex(t, texts, showTranslated)));
1468
1520
  }
1469
- function renderOverview(t, texts, effectiveText, showTranslated, status, records) {
1521
+ /** Overview dashboard: stat cards, status, most-cited, and the records table. */
1522
+ function OverviewView(props) {
1523
+ const { t, texts, showTranslated, status, records } = props;
1524
+ const [query, setQuery] = (0, react.useState)("");
1470
1525
  const totalUsage = records.reduce((sum, record) => sum + record.usageCount, 0);
1471
1526
  const consolidated = records.filter((record) => record.selectedForPhase2).length;
1527
+ const topCited = records.filter((record) => record.usageCount > 0).sort((a, b) => b.usageCount - a.usageCount).slice(0, 5);
1472
1528
  const phase = status.phase ?? "idle";
1473
1529
  const active = phase === "phase1" || phase === "phase2";
1530
+ const needle = query.trim().toLowerCase();
1531
+ const filtered = needle === "" ? records : records.filter((record) => record.sessionId.toLowerCase().includes(needle) || record.rolloutSlug.toLowerCase().includes(needle) || record.workspacePath.toLowerCase().includes(needle));
1532
+ const maxUsage = Math.max(1, ...records.map((record) => record.usageCount));
1474
1533
  const card = (num, label) => (0, react.createElement)("div", {
1475
1534
  className: "dshmv-card",
1476
1535
  key: label
1477
1536
  }, (0, react.createElement)("div", { className: "dshmv-card-num" }, num), (0, react.createElement)("div", { className: "dshmv-card-label" }, label));
1478
- return (0, react.createElement)("div", { className: "dshmv-fill" }, (0, react.createElement)("div", { className: "dshmv-cards" }, card(records.length, t("viewer_stat_records")), card(consolidated, t("viewer_stat_consolidated")), card(totalUsage, t("viewer_stat_citations")), card(formatBytes(texts.summary.length), t("viewer_stat_summarySize"))), (0, react.createElement)("div", { style: {
1537
+ return (0, react.createElement)("div", { className: "dshmv-scroll-fill" }, (0, react.createElement)("div", { className: "dshmv-cards" }, card(records.length, t("viewer_stat_records")), card(consolidated, t("viewer_stat_consolidated")), card(totalUsage, t("viewer_stat_citations")), card(formatBytes(texts.summary.length), t("viewer_stat_summarySize")), card(formatBytes(texts.index.length), t("viewer_stat_indexSize"))), (0, react.createElement)("div", { style: {
1479
1538
  display: "flex",
1480
1539
  alignItems: "center",
1481
1540
  gap: 8,
1482
1541
  marginBottom: 10
1483
- } }, (0, react.createElement)("span", { className: `dshmv-badge ${active ? "dshmv-badge-stale" : "dshmv-badge-ok"}` }, active ? phase === "phase1" ? t("phase1") : t("phase2") : t("statusIdle")), status.updatedAt != null && status.updatedAt > 0 ? (0, react.createElement)("span", { className: "dshmv-mono" }, new Date(status.updatedAt).toLocaleString()) : null, showTranslated ? (0, react.createElement)("span", { className: "dshmv-badge dshmv-badge-ok" }, t("viewer_badgeTranslated")) : null), effectiveText !== "" || texts.summary !== "" ? (0, react.createElement)("div", { className: "dshmv-md dshmv-md-fill" }, effectiveText !== "" ? effectiveText : texts.summary) : (0, react.createElement)("div", { className: "dshmv-empty dshmv-md-fill" }, texts.failed ? t("viewer_decodeFailed") : t("viewer_empty")));
1542
+ } }, (0, react.createElement)("span", { className: `dshmv-badge ${active ? "dshmv-badge-stale" : "dshmv-badge-ok"}` }, active ? phase === "phase1" ? t("phase1") : t("phase2") : t("statusIdle")), status.updatedAt != null && status.updatedAt > 0 ? (0, react.createElement)("span", { className: "dshmv-mono" }, new Date(status.updatedAt).toLocaleString()) : null, showTranslated ? (0, react.createElement)("span", { className: "dshmv-badge dshmv-badge-ok" }, t("viewer_badgeTranslated")) : null), records.length === 0 ? null : (0, react.createElement)("div", { className: "dshmv-section" }, t("viewer_topCited")), ...topCited.map((record) => (0, react.createElement)("div", {
1543
+ className: "dshmv-toprow",
1544
+ key: record.sessionId
1545
+ }, (0, react.createElement)("span", { className: "dshmv-toprow-name" }, record.rolloutSlug || record.sessionId.slice(0, 8)), (0, react.createElement)("span", { className: "dshmv-toprow-meta" }, `${record.usageCount} · ${formatTime(record.lastUsage ?? record.sourceUpdatedAt)}`))), records.length > 0 && topCited.length === 0 ? (0, react.createElement)("div", { className: "dshmv-empty" }, t("viewer_noCitations")) : null, records.length === 0 ? null : (0, react.createElement)("div", {
1546
+ className: "dshmv-section",
1547
+ style: { marginTop: 14 }
1548
+ }, t("viewer_tab_records")), records.length === 0 ? null : (0, react.createElement)("input", {
1549
+ className: "dshmv-search",
1550
+ placeholder: t("viewer_search"),
1551
+ value: query,
1552
+ onChange: (event) => setQuery(event.target.value)
1553
+ }), records.length === 0 ? (0, react.createElement)("div", { className: "dshmv-empty" }, t("viewer_empty")) : (0, react.createElement)("table", { className: "dshmv-table" }, (0, react.createElement)("thead", null, (0, react.createElement)("tr", null, (0, react.createElement)("th", null, t("viewer_col_session")), (0, react.createElement)("th", null, t("viewer_col_usage")), (0, react.createElement)("th", null, t("viewer_col_updated")))), (0, react.createElement)("tbody", null, ...filtered.map((record) => (0, react.createElement)("tr", { key: record.sessionId }, (0, react.createElement)("td", null, (0, react.createElement)("div", { style: { fontWeight: 600 } }, record.rolloutSlug || record.sessionId.slice(0, 8)), (0, react.createElement)("div", { className: "dshmv-mono" }, record.workspacePath)), (0, react.createElement)("td", null, (0, react.createElement)("div", { className: "dshmv-usage" }, (0, react.createElement)("span", {
1554
+ className: "dshmv-usage-bar",
1555
+ style: { width: `${Math.round(record.usageCount / maxUsage * 48)}px` }
1556
+ }), (0, react.createElement)("span", null, String(record.usageCount))), record.lastUsage != null ? (0, react.createElement)("div", { className: "dshmv-mono" }, `${t("viewer_col_lastUsed")}: ${formatTime(record.lastUsage)}`) : null), (0, react.createElement)("td", { className: "dshmv-mono" }, formatTime(record.sourceUpdatedAt)))))));
1484
1557
  }
1485
1558
  function SummaryView(props) {
1486
1559
  const { t, texts, showTranslated } = props;
@@ -1491,22 +1564,6 @@ function renderIndex(t, texts, showTranslated) {
1491
1564
  const text = showTranslated && texts.translatedIndex !== "" ? texts.translatedIndex : texts.index;
1492
1565
  return text !== "" ? (0, react.createElement)("div", { className: "dshmv-md dshmv-md-fill" }, text) : (0, react.createElement)("div", { className: "dshmv-empty dshmv-fill" }, texts.failed ? t("viewer_decodeFailed") : t("viewer_empty"));
1493
1566
  }
1494
- function RecordsView(props) {
1495
- const { t, records } = props;
1496
- const [query, setQuery] = (0, react.useState)("");
1497
- const needle = query.trim().toLowerCase();
1498
- const filtered = needle === "" ? records : records.filter((record) => record.sessionId.toLowerCase().includes(needle) || record.rolloutSlug.toLowerCase().includes(needle) || record.workspacePath.toLowerCase().includes(needle));
1499
- const maxUsage = Math.max(1, ...records.map((record) => record.usageCount));
1500
- return (0, react.createElement)("div", { className: "dshmv-fill" }, (0, react.createElement)("input", {
1501
- className: "dshmv-search",
1502
- placeholder: t("viewer_search"),
1503
- value: query,
1504
- onChange: (event) => setQuery(event.target.value)
1505
- }), (0, react.createElement)("div", { className: "dshmv-scroll-fill" }, filtered.length === 0 ? (0, react.createElement)("div", { className: "dshmv-empty" }, t("viewer_empty")) : (0, react.createElement)("table", { className: "dshmv-table" }, (0, react.createElement)("thead", null, (0, react.createElement)("tr", null, (0, react.createElement)("th", null, t("viewer_col_session")), (0, react.createElement)("th", null, t("viewer_col_usage")), (0, react.createElement)("th", null, t("viewer_col_updated")))), (0, react.createElement)("tbody", null, ...filtered.map((record) => (0, react.createElement)("tr", { key: record.sessionId }, (0, react.createElement)("td", null, (0, react.createElement)("div", { style: { fontWeight: 600 } }, record.rolloutSlug || record.sessionId.slice(0, 8)), (0, react.createElement)("div", { className: "dshmv-mono" }, record.workspacePath)), (0, react.createElement)("td", null, (0, react.createElement)("div", { className: "dshmv-usage" }, (0, react.createElement)("span", {
1506
- className: "dshmv-usage-bar",
1507
- style: { width: `${Math.round(record.usageCount / maxUsage * 48)}px` }
1508
- }), (0, react.createElement)("span", null, String(record.usageCount))), record.lastUsage != null ? (0, react.createElement)("div", { className: "dshmv-mono" }, `${t("viewer_col_lastUsed")}: ${formatTime(record.lastUsage)}`) : null), (0, react.createElement)("td", { className: "dshmv-mono" }, formatTime(record.sourceUpdatedAt))))))));
1509
- }
1510
1567
  /** Title chip for the viewer tab. */
1511
1568
  function MemoryViewerTitle(props) {
1512
1569
  return (0, react.createElement)("span", null, props.t("viewer_title"));
@@ -1617,12 +1674,15 @@ const ZH = {
1617
1674
  phase1: "记忆提取中",
1618
1675
  phase2: "记忆整理中",
1619
1676
  processing: "记忆处理中",
1620
- transfer_hint: "导出生成单个 gzip 压缩的 bundle 文件;导入按内容哈希去重合并,冲突以本地为准,未压缩的 JSON 也能读。",
1621
- transfer_exportPath: "导出到文件",
1622
- transfer_importPath: "从文件导入",
1677
+ transfer_exportLabel: "导出记忆",
1678
+ transfer_exportDesc: "保存为单个 bundle 文件,位置在浏览器里选",
1679
+ transfer_importLabel: "导入记忆",
1680
+ transfer_importDesc: "选择一个 bundle 文件合并进当前记忆库",
1623
1681
  transfer_export: "导出",
1624
1682
  transfer_import: "导入",
1625
1683
  transfer_requested: "已发送,结果见上方状态栏",
1684
+ transfer_tooBig: "文件超过 32 MB,请改用 memory_export 工具直接导出到磁盘",
1685
+ transfer_readFailed: "文件读取失败",
1626
1686
  viewer_title: "记忆浏览",
1627
1687
  viewer_refresh: "刷新",
1628
1688
  viewer_translate: "翻译记忆",
@@ -1635,6 +1695,9 @@ const ZH = {
1635
1695
  viewer_stat_consolidated: "已整理",
1636
1696
  viewer_stat_citations: "被引用次数",
1637
1697
  viewer_stat_summarySize: "摘要大小",
1698
+ viewer_stat_indexSize: "注册表大小",
1699
+ viewer_topCited: "最常引用",
1700
+ viewer_noCitations: "还没有引用记录",
1638
1701
  viewer_original: "显示原文",
1639
1702
  viewer_translated: "显示译文",
1640
1703
  viewer_showTranslated: "显示译文",
@@ -1718,12 +1781,15 @@ const EN = {
1718
1781
  phase1: "Extracting memories",
1719
1782
  phase2: "Consolidating memories",
1720
1783
  processing: "Memory processing",
1721
- transfer_hint: "Export writes one gzip-compressed bundle file; import merges by content hash (conflicts keep the local copy) and also accepts uncompressed JSON.",
1722
- transfer_exportPath: "Export to file",
1723
- transfer_importPath: "Import from file",
1784
+ transfer_exportLabel: "Export memory",
1785
+ transfer_exportDesc: "Save as a single bundle file — pick the location in the browser",
1786
+ transfer_importLabel: "Import memory",
1787
+ transfer_importDesc: "Pick a bundle file to merge into the current memory store",
1724
1788
  transfer_export: "Export",
1725
1789
  transfer_import: "Import",
1726
1790
  transfer_requested: "Sent — result appears in the status bar above",
1791
+ transfer_tooBig: "File exceeds 32 MB — use the memory_export tool to write it to disk instead",
1792
+ transfer_readFailed: "Failed to read the file",
1727
1793
  viewer_title: "Memory",
1728
1794
  viewer_refresh: "Refresh",
1729
1795
  viewer_translate: "Translate memory",
@@ -1736,6 +1802,9 @@ const EN = {
1736
1802
  viewer_stat_consolidated: "Consolidated",
1737
1803
  viewer_stat_citations: "Citations",
1738
1804
  viewer_stat_summarySize: "Summary size",
1805
+ viewer_stat_indexSize: "Registry size",
1806
+ viewer_topCited: "Most cited",
1807
+ viewer_noCitations: "No citations yet",
1739
1808
  viewer_original: "Show original",
1740
1809
  viewer_translated: "Show translation",
1741
1810
  viewer_showTranslated: "Translation",
@@ -1828,7 +1897,8 @@ function apply(ctx) {
1828
1897
  commands: commandsView,
1829
1898
  hooks: {
1830
1899
  memoryPage: card.pageStore(),
1831
- memoryStatus: statusSource
1900
+ memoryStatus: statusSource,
1901
+ memoryView: viewSource
1832
1902
  }
1833
1903
  })
1834
1904
  }, MemorySettingsPage));