@lemoncat7/dsh-partner 0.1.0-alpha.22 → 1.0.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/lib/client.js CHANGED
@@ -1483,6 +1483,12 @@ var client_default = `.dsh-partner-sidebar,
1483
1483
  .dsh-partner-automation-grid { display: grid; grid-template-columns: minmax(480px, .95fr) minmax(430px, 1.05fr); align-items: stretch; gap: 12px; margin-top: 17px; }
1484
1484
  .dsh-partner-automation-grid .dsh-partner-automation { align-content: start; margin-top: 0; }
1485
1485
  .dsh-partner-automation-grid .dsh-partner-automation:last-child .dsh-partner-automation-actions { margin-top: auto; }
1486
+ .dsh-partner-review-policy { display: grid; grid-template-columns: auto minmax(0, 1fr) 90px auto; align-items: center; gap: 10px; padding-top: 12px; border-top: 1px solid var(--partner-border); }
1487
+ .dsh-partner-review-policy > span { display: grid; gap: 2px; }
1488
+ .dsh-partner-review-policy > span strong { font-size: var(--partner-font-body); }
1489
+ .dsh-partner-review-policy > span small { color: var(--dsw-alias-label-tertiary); font-size: var(--partner-font-caption); line-height: 1.4; }
1490
+ .dsh-partner-review-policy select { width: 100%; height: 32px; padding: 0 26px 0 9px; border: 1px solid var(--partner-border); border-radius: 8px; background-color: var(--partner-control); font-size: var(--partner-font-caption); }
1491
+ .dsh-partner-review-policy > button:last-child { height: 32px; padding: 0 10px; border: 1px solid var(--partner-border); border-radius: 8px; color: var(--dsw-alias-label-secondary); background: var(--partner-control); font-size: var(--partner-font-caption); cursor: pointer; }
1486
1492
  .dsh-partner-automation > header { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
1487
1493
  .dsh-partner-automation > header > span { display: grid; gap: 4px; }
1488
1494
  .dsh-partner-automation > header strong { font-size: var(--partner-font-body); }
@@ -1563,6 +1569,29 @@ var client_default = `.dsh-partner-sidebar,
1563
1569
  .dsh-partner-diary-detail ul { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; }
1564
1570
  .dsh-partner-diary-detail li { position: relative; padding-left: 12px; color: var(--dsw-alias-label-secondary); font-size: var(--partner-font-caption); line-height: 1.55; }
1565
1571
  .dsh-partner-diary-detail li::before { position: absolute; top: .62em; left: 1px; width: 3px; height: 3px; border-radius: 50%; background: var(--partner-accent); content: ''; }
1572
+ .dsh-partner-graph-browser { display: grid; grid-template-columns: minmax(240px, 28%) minmax(0, 1fr); height: clamp(430px, 58vh, 640px); min-height: 0; overflow: hidden; }
1573
+ .dsh-partner-graph-index { min-height: 0; overflow-y: auto; border-right: 1px solid var(--partner-border); }
1574
+ .dsh-partner-graph-index > button { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 5px 9px; width: 100%; padding: 12px 14px; border: 0; border-bottom: 1px solid color-mix(in srgb, var(--partner-border) 70%, transparent); color: inherit; background: transparent; text-align: left; cursor: pointer; }
1575
+ .dsh-partner-graph-index > button:hover { background: color-mix(in srgb, var(--partner-accent) 4%, transparent); }
1576
+ .dsh-partner-graph-index > button.is-active { background: color-mix(in srgb, var(--partner-accent) 8%, transparent); box-shadow: inset 2px 0 var(--partner-accent); }
1577
+ .dsh-partner-graph-index span { grid-row: span 2; padding: 4px 6px; border-radius: 6px; color: var(--partner-accent-strong); background: color-mix(in srgb, var(--partner-accent) 8%, transparent); font-size: var(--partner-font-caption); }
1578
+ .dsh-partner-graph-index strong { overflow: hidden; font-size: var(--partner-font-body); text-overflow: ellipsis; white-space: nowrap; }
1579
+ .dsh-partner-graph-index small { color: var(--dsw-alias-label-tertiary); font-size: var(--partner-font-caption); }
1580
+ .dsh-partner-graph-stage { min-width: 0; min-height: 0; overflow-y: auto; padding: 26px clamp(22px, 4vw, 42px) 40px; background: radial-gradient(circle at 78% 14%, color-mix(in srgb, var(--partner-accent) 7%, transparent), transparent 38%); }
1581
+ .dsh-partner-graph-stage > header { display: grid; gap: 7px; max-width: 66ch; padding-bottom: 20px; border-bottom: 1px solid var(--partner-border); }
1582
+ .dsh-partner-graph-stage > header span { width: max-content; color: var(--partner-accent); font-size: var(--partner-font-caption); font-weight: 650; }
1583
+ .dsh-partner-graph-stage > header strong { font-size: var(--partner-font-title); letter-spacing: -.025em; }
1584
+ .dsh-partner-graph-stage > header p { margin: 0; color: var(--dsw-alias-label-secondary); font-size: var(--partner-font-body); line-height: 1.7; }
1585
+ .dsh-partner-graph-links { position: relative; display: grid; gap: 8px; margin: 21px 0 0 16px; padding-left: 24px; }
1586
+ .dsh-partner-graph-links::before { position: absolute; top: 0; bottom: 0; left: 3px; width: 1px; background: linear-gradient(var(--partner-accent), color-mix(in srgb, var(--partner-accent) 8%, transparent)); content: ''; }
1587
+ .dsh-partner-graph-links > button { position: relative; display: grid; grid-template-columns: 8px minmax(0, 1fr) auto; align-items: center; gap: 11px; padding: 12px 13px; border: 1px solid var(--partner-border); border-radius: 11px; color: inherit; background: color-mix(in srgb, var(--partner-control) 82%, transparent); text-align: left; cursor: pointer; }
1588
+ .dsh-partner-graph-links > button::before { position: absolute; top: 50%; right: 100%; width: 25px; height: 1px; background: color-mix(in srgb, var(--partner-accent) 42%, transparent); content: ''; }
1589
+ .dsh-partner-graph-links > button > i { width: 7px; height: 7px; border-radius: 50%; background: var(--partner-accent); }
1590
+ .dsh-partner-graph-links > button > span { display: grid; gap: 3px; }
1591
+ .dsh-partner-graph-links small { color: var(--partner-accent); font-size: var(--partner-font-caption); }
1592
+ .dsh-partner-graph-links strong { font-size: var(--partner-font-body); }
1593
+ .dsh-partner-graph-links p { margin: 0; color: var(--dsw-alias-label-tertiary); font-size: var(--partner-font-caption); }
1594
+ .dsh-partner-graph-links b { color: var(--dsw-alias-label-tertiary); font-size: var(--partner-font-caption); font-weight: 560; font-variant-numeric: tabular-nums; }
1566
1595
  .dsh-partner-state { display: grid; place-items: center; align-content: center; min-height: 280px; padding: 30px; color: var(--dsw-alias-label-tertiary); text-align: center; }
1567
1596
  .dsh-partner-state > svg { margin-bottom: 10px; color: var(--partner-accent); }
1568
1597
  .dsh-partner-state strong { color: var(--dsw-alias-label-secondary); font-size: var(--partner-font-label); }
@@ -1607,6 +1636,11 @@ var client_default = `.dsh-partner-sidebar,
1607
1636
  .dsh-partner-library-browser { grid-template-columns: 1fr; height: auto; overflow: visible; }
1608
1637
  .dsh-partner-library-list { max-height: 300px; border-right: 0; border-bottom: 1px solid var(--partner-border); }
1609
1638
  .dsh-partner-library-detail { min-height: 280px; }
1639
+ .dsh-partner-review-policy { grid-template-columns: auto minmax(0, 1fr); }
1640
+ .dsh-partner-review-policy > label, .dsh-partner-review-policy > button:last-child { grid-column: span 1; }
1641
+ .dsh-partner-graph-browser { grid-template-columns: 1fr; height: auto; }
1642
+ .dsh-partner-graph-index { max-height: 280px; border-right: 0; border-bottom: 1px solid var(--partner-border); }
1643
+ .dsh-partner-graph-stage { min-height: 320px; }
1610
1644
  }
1611
1645
  @media (prefers-reduced-motion: reduce) {
1612
1646
  .dsh-partner-workspace *,
@@ -1724,6 +1758,12 @@ function createController(ctx) {
1724
1758
  controller.close();
1725
1759
  clientSessions(ctx).open(sessionId);
1726
1760
  },
1761
+ async renewSession(routeId) {
1762
+ const renewed = await api(`/sessions/${encodeURIComponent(routeId)}/renew`, { method: "POST" });
1763
+ await waitForClientSession(ctx, renewed.sessionId);
1764
+ controller.close();
1765
+ clientSessions(ctx).open(renewed.sessionId);
1766
+ },
1727
1767
  subscribe(listener) {
1728
1768
  listeners.add(listener);
1729
1769
  return () => listeners.delete(listener);
@@ -1815,6 +1855,14 @@ function PartnerWorkspace({ controller }) {
1815
1855
  setError(message(reason));
1816
1856
  }
1817
1857
  };
1858
+ const renewSession = async (routeId) => {
1859
+ try {
1860
+ setError(void 0);
1861
+ await controller.renewSession(routeId);
1862
+ } catch (reason) {
1863
+ setError(message(reason));
1864
+ }
1865
+ };
1818
1866
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("main", { className: "dsh-partner-workspace", "data-xiaohei-surface": "plugin-workspace", children: [
1819
1867
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("header", { className: "dsh-partner-topbar", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
1820
1868
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", "data-xiaohei-workspace-close": true, onClick: controller.close, "aria-label": "\u8FD4\u56DE\u4F1A\u8BDD", title: "\u8FD4\u56DE\u4F1A\u8BDD", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives.IconChevronLeftOutline14, { size: 15 }) }),
@@ -1878,11 +1926,11 @@ function PartnerWorkspace({ controller }) {
1878
1926
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TabButton, { active: tab === "memory", onClick: () => setTab("memory"), icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives.IconDataOutline16, { size: 16 }), children: "\u8BB0\u5FC6" })
1879
1927
  ] }),
1880
1928
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-partner-stage-scroll", children: [
1881
- tab === "home" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(HomePanel, { companion: selected, snapshot, navigate: setTab, openSession }),
1929
+ tab === "home" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(HomePanel, { companion: selected, snapshot, navigate: setTab, openSession, renewSession }),
1882
1930
  tab === "identity" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IdentityEditor, { companion: selected, count: snapshot?.companions.length ?? 1, onChanged: refresh }),
1883
1931
  tab === "capabilities" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CapabilityEditor, { companion: selected, presets: snapshot?.presets ?? [], onChanged: refresh }),
1884
1932
  tab === "weixin" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(WeixinPanel, { companion: selected, snapshot, onChanged: refresh }),
1885
- tab === "memory" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MemoryPanel, { companion: selected, snapshot, openSession, onChanged: refresh })
1933
+ tab === "memory" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MemoryPanel, { companion: selected, snapshot, openSession, renewSession, onChanged: refresh })
1886
1934
  ] })
1887
1935
  ] }),
1888
1936
  error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-partner-error", role: "alert", children: error })
@@ -1890,7 +1938,7 @@ function PartnerWorkspace({ controller }) {
1890
1938
  ] })
1891
1939
  ] });
1892
1940
  }
1893
- function HomePanel({ companion, snapshot, navigate, openSession }) {
1941
+ function HomePanel({ companion, snapshot, navigate, openSession, renewSession }) {
1894
1942
  const channel = snapshot.channels.find((item) => item.companionId === companion.id);
1895
1943
  const sessions = snapshot.sessions.filter((item) => item.companionId === companion.id);
1896
1944
  const pending = channel ? snapshot.pairings.filter((item) => item.channelId === channel.id && item.status === "pending").length : 0;
@@ -1955,7 +2003,7 @@ function HomePanel({ companion, snapshot, navigate, openSession }) {
1955
2003
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { children: "\u4F1A\u8BDD\u8FDE\u7EED\u6027" }),
1956
2004
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: sessions.length ? `${sessions.length} \u4E2A\u5171\u4EAB\u4F1A\u8BDD` : "\u7B49\u5F85\u7B2C\u4E00\u6761\u6D88\u606F" })
1957
2005
  ] }),
1958
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", onClick: () => latestSession === void 0 ? navigate("memory") : void openSession(latestSession.id, latestSession.sessionId), children: latestSession === void 0 ? "\u67E5\u770B" : "\u6253\u5F00\u4F1A\u8BDD" })
2006
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", onClick: () => latestSession === void 0 ? navigate("memory") : latestSession.archived ? void renewSession(latestSession.id) : void openSession(latestSession.id, latestSession.sessionId), children: latestSession === void 0 ? "\u67E5\u770B" : latestSession.archived ? "\u5F00\u59CB\u65B0\u4F1A\u8BDD" : "\u6253\u5F00\u4F1A\u8BDD" })
1959
2007
  ] }),
1960
2008
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: sessions.length ? `\u6700\u8FD1\u6D3B\u52A8\u4E8E ${relativeTime(Math.max(...sessions.map((item) => item.lastMessageAt)))}` : "\u5FAE\u4FE1\u8054\u7CFB\u4EBA\u901A\u8FC7\u914D\u5BF9\u540E\uFF0C\u5C06\u5728\u8FD9\u91CC\u5EFA\u7ACB\u72EC\u7ACB\u4E0A\u4E0B\u6587\u3002" })
1961
2009
  ] })
@@ -2028,9 +2076,13 @@ function IdentityEditor({ companion, count, onChanged }) {
2028
2076
  }
2029
2077
  function CapabilityEditor({ companion, presets, onChanged }) {
2030
2078
  const [form, setForm] = (0, import_react4.useState)(() => companionDraft(companion));
2079
+ const [modelCatalog, setModelCatalog] = (0, import_react4.useState)();
2031
2080
  const [saving, setSaving] = (0, import_react4.useState)(false);
2032
2081
  const [error, setError] = (0, import_react4.useState)();
2033
2082
  (0, import_react4.useEffect)(() => setForm(companionDraft(companion)), [companion.id, companion.updatedAt]);
2083
+ (0, import_react4.useEffect)(() => {
2084
+ void api("/models").then(setModelCatalog).catch((reason) => setError(message(reason)));
2085
+ }, []);
2034
2086
  const toggle = (capability) => setForm((current) => ({ ...current, capabilities: current.capabilities.includes(capability) ? current.capabilities.filter((item) => item !== capability) : [...current.capabilities, capability] }));
2035
2087
  const save = async () => {
2036
2088
  setSaving(true);
@@ -2050,6 +2102,9 @@ function CapabilityEditor({ companion, presets, onChanged }) {
2050
2102
  { id: "ssh", title: "SSH", detail: "\u901A\u8FC7 SSH \u63D2\u4EF6\u6388\u6743\u7684\u4E3B\u673A\u4E0E\u547D\u4EE4\u8FB9\u754C\u5DE5\u4F5C\u3002" },
2051
2103
  { id: "git", title: "Git", detail: "\u9884\u7559 Git \u5DE5\u5177\u80FD\u529B\uFF0C\u4ECD\u9700\u5BF9\u5E94\u63D2\u4EF6\u5B9E\u9645\u5B89\u88C5\u3002" }
2052
2104
  ];
2105
+ const selectedProvider = form.provider || modelCatalog?.defaultSelection.provider || "";
2106
+ const modelOptions = modelCatalog?.providers.find((item) => item.id === selectedProvider)?.models ?? [];
2107
+ const currentModelMissing = Boolean(form.model) && !modelOptions.some((item) => item.id === form.model);
2053
2108
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-partner-form is-capabilities", children: [
2054
2109
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Section, { eyebrow: "COMPOSITION", title: "\u80FD\u529B\u7EC4\u5408", detail: "\u4F19\u4F34\u58F0\u660E\u610F\u56FE\u8303\u56F4\uFF1B\u771F\u6B63\u53EF\u8C03\u7528\u7684\u5DE5\u5177\u4ECD\u6765\u81EA\u6240\u9009 Agent Preset\uFF0C\u5E76\u7EE7\u7EED\u6267\u884C\u5404\u63D2\u4EF6\u6743\u9650\u3002" }),
2055
2110
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Field, { label: "Agent Preset", hint: "\u51B3\u5B9A\u8FD9\u4E2A\u4F19\u4F34\u5B9E\u9645\u52A0\u8F7D\u54EA\u4E9B\u5DE5\u5177\u3001\u6280\u80FD\u548C\u7CFB\u7EDF\u63D0\u793A\u3002", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { value: form.presetId, onChange: (event) => setForm({ ...form, presetId: event.target.value }), children: [
@@ -2062,8 +2117,24 @@ function CapabilityEditor({ companion, presets, onChanged }) {
2062
2117
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { children: choice.detail })
2063
2118
  ] }, choice.id)) }),
2064
2119
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-partner-fields two", children: [
2065
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Field, { label: "\u6A21\u578B\u63D0\u4F9B\u65B9", hint: "\u7559\u7A7A\u4F7F\u7528 DSH \u9ED8\u8BA4\u8DEF\u7531", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { value: form.provider, onChange: (event) => setForm({ ...form, provider: event.target.value }), placeholder: "\u4F8B\u5982 openai" }) }),
2066
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Field, { label: "\u6A21\u578B", hint: "\u7559\u7A7A\u4F7F\u7528\u5F53\u524D\u9ED8\u8BA4\u6A21\u578B", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { value: form.model, onChange: (event) => setForm({ ...form, model: event.target.value }), placeholder: "\u4F8B\u5982 gpt-5" }) })
2120
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Field, { label: "\u6A21\u578B\u63D0\u4F9B\u65B9", hint: "\u6A21\u578B\u76EE\u5F55\u6765\u81EA\u5F53\u524D\u5BA2\u6237\u7AEF", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { value: form.provider, onChange: (event) => setForm({ ...form, provider: event.target.value, model: "" }), children: [
2121
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("option", { value: "", children: [
2122
+ "\u8DDF\u968F DSH \u9ED8\u8BA4 \xB7 ",
2123
+ modelCatalog?.defaultSelection.provider || "\u6B63\u5728\u8BFB\u53D6"
2124
+ ] }),
2125
+ modelCatalog?.providers.map((provider) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: provider.id, children: provider.name || provider.id }, provider.id))
2126
+ ] }) }),
2127
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Field, { label: "\u6A21\u578B", hint: "\u968F\u63D0\u4F9B\u65B9\u8054\u52A8", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { value: form.model, onChange: (event) => setForm({ ...form, model: event.target.value }), children: [
2128
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("option", { value: "", children: [
2129
+ "\u8DDF\u968F DSH \u9ED8\u8BA4 \xB7 ",
2130
+ modelCatalog?.defaultSelection.model || "\u6B63\u5728\u8BFB\u53D6"
2131
+ ] }),
2132
+ currentModelMissing && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("option", { value: form.model, children: [
2133
+ "\u5F53\u524D\u914D\u7F6E \xB7 ",
2134
+ form.model
2135
+ ] }),
2136
+ modelOptions.map((model) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: model.id, children: model.name || model.id }, model.id))
2137
+ ] }) })
2067
2138
  ] }),
2068
2139
  error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-partner-inline-error", children: error }),
2069
2140
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-partner-form-actions", children: [
@@ -2214,21 +2285,23 @@ function WeixinPanel({ companion, snapshot, onChanged }) {
2214
2285
  error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-partner-inline-error", children: error })
2215
2286
  ] });
2216
2287
  }
2217
- function MemoryPanel({ companion, snapshot, openSession, onChanged }) {
2288
+ function MemoryPanel({ companion, snapshot, openSession, renewSession, onChanged }) {
2218
2289
  const sessions = snapshot.sessions.filter((item) => item.companionId === companion.id);
2219
2290
  const heartbeat = snapshot.heartbeatStates.find((item) => item.companionId === companion.id);
2220
2291
  const [automation, setAutomation] = (0, import_react4.useState)(() => structuredClone(companion.automation));
2221
2292
  const [memories, setMemories] = (0, import_react4.useState)([]);
2222
2293
  const [reflections, setReflections] = (0, import_react4.useState)([]);
2294
+ const [graph, setGraph] = (0, import_react4.useState)({ memories: [], relations: [] });
2223
2295
  const [editing, setEditing] = (0, import_react4.useState)();
2224
2296
  const [busy, setBusy] = (0, import_react4.useState)(false);
2225
2297
  const [notice, setNotice] = (0, import_react4.useState)();
2226
2298
  const [error, setError] = (0, import_react4.useState)();
2227
2299
  const [modelCatalog, setModelCatalog] = (0, import_react4.useState)();
2228
2300
  const loadMemory = (0, import_react4.useCallback)(async () => {
2229
- const result = await api(`/companions/${companion.id}/memory`);
2301
+ const [result, relations] = await Promise.all([api(`/companions/${companion.id}/memory`), api(`/companions/${companion.id}/memory/graph`)]);
2230
2302
  setMemories(result.memories);
2231
2303
  setReflections(result.reflections);
2304
+ setGraph(relations);
2232
2305
  }, [companion.id]);
2233
2306
  (0, import_react4.useEffect)(() => {
2234
2307
  setAutomation(structuredClone(companion.automation));
@@ -2268,6 +2341,20 @@ function MemoryPanel({ companion, snapshot, openSession, onChanged }) {
2268
2341
  setBusy(false);
2269
2342
  }
2270
2343
  };
2344
+ const review = async () => {
2345
+ setBusy(true);
2346
+ setError(void 0);
2347
+ setNotice(void 0);
2348
+ try {
2349
+ const result = await api(`/companions/${companion.id}/memory/review`, { method: "POST" });
2350
+ setNotice(result.reason ?? `\u7EC8\u5BA1\u5B8C\u6210 ${result.reviewed} \u7BC7${result.failed ? `\uFF0C\u5931\u8D25 ${result.failed} \u7BC7` : ""}`);
2351
+ await loadMemory();
2352
+ } catch (reason) {
2353
+ setError(message(reason));
2354
+ } finally {
2355
+ setBusy(false);
2356
+ }
2357
+ };
2271
2358
  const saveMemory = async () => {
2272
2359
  if (!editing) return;
2273
2360
  setBusy(true);
@@ -2351,6 +2438,20 @@ function MemoryPanel({ companion, snapshot, openSession, onChanged }) {
2351
2438
  ] }),
2352
2439
  modelOptions.map((model) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: model.id, children: model.name || model.id }, model.id))
2353
2440
  ] }) })
2441
+ ] }),
2442
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-partner-review-policy", children: [
2443
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "dsh-partner-switch", "data-on": automation.memory.dailyReviewEnabled, "aria-label": "\u542F\u7528\u6BCF\u65E5\u7EC8\u5BA1", onClick: () => setAutomation((current) => ({ ...current, memory: { ...current.memory, dailyReviewEnabled: !current.memory.dailyReviewEnabled } })), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("i", {}) }),
2444
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
2445
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: "\u6BCF\u65E5\u7EC8\u5BA1" }),
2446
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { children: "\u6B21\u65E5\u81EA\u52A8\u5408\u5E76\u91CD\u590D\u3001\u7EA0\u6B63\u504F\u5DEE\u5E76\u5EFA\u7ACB\u8BB0\u5FC6\u5173\u7CFB" })
2447
+ ] }),
2448
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("select", { "aria-label": "\u6BCF\u65E5\u7EC8\u5BA1\u65F6\u95F4", value: automation.memory.dailyReviewHour, onChange: (event) => setAutomation((current) => ({ ...current, memory: { ...current.memory, dailyReviewHour: Number(event.target.value) } })), children: Array.from({ length: 24 }, (_, hour) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("option", { value: hour, children: [
2449
+ String(hour).padStart(2, "0"),
2450
+ ":00"
2451
+ ] }, hour)) }) }),
2452
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", disabled: busy || reflections.length === 0, onClick: () => {
2453
+ void review();
2454
+ }, children: "\u7ACB\u5373\u7EC8\u5BA1" })
2354
2455
  ] })
2355
2456
  ] }),
2356
2457
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { className: "dsh-partner-automation", children: [
@@ -2397,7 +2498,7 @@ function MemoryPanel({ companion, snapshot, openSession, onChanged }) {
2397
2498
  notice
2398
2499
  ] }),
2399
2500
  error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-partner-inline-error", children: error }),
2400
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MemoryLibrary, { memories, reflections, editing, busy, setEditing, save: () => {
2501
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MemoryLibrary, { memories, reflections, graph, editing, busy, setEditing, save: () => {
2401
2502
  void saveMemory();
2402
2503
  }, remove: (item) => {
2403
2504
  void deleteMemory(item);
@@ -2407,29 +2508,26 @@ function MemoryPanel({ companion, snapshot, openSession, onChanged }) {
2407
2508
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: "\u5171\u4EAB\u4F1A\u8BDD" })
2408
2509
  ] }) }),
2409
2510
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-partner-session-list", children: [
2410
- sessions.map((item) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("article", { children: [
2511
+ sessions.map((item) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("article", { "data-archived": item.archived, children: [
2411
2512
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives.IconDataOutline16, { size: 16 }) }),
2412
2513
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
2413
2514
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("strong", { children: [
2414
2515
  "\u5FAE\u4FE1\u8054\u7CFB\u4EBA \xB7 ",
2415
2516
  item.userId.slice(-6)
2416
2517
  ] }),
2417
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("small", { children: [
2418
- item.sessionId,
2419
- " \xB7 ",
2420
- new Date(item.lastMessageAt).toLocaleString()
2421
- ] })
2518
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { children: item.archived ? "\u5DF2\u5F52\u6863 \xB7 \u957F\u671F\u8BB0\u5FC6\u4FDD\u7559" : `${item.sessionId} \xB7 ${new Date(item.lastMessageAt).toLocaleString()}` })
2422
2519
  ] }),
2423
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", onClick: () => {
2424
- void openSession(item.id, item.sessionId);
2425
- }, children: "\u6253\u5F00\u4F1A\u8BDD" })
2520
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: item.archived ? "is-primary" : void 0, onClick: () => {
2521
+ if (item.archived) void renewSession(item.id);
2522
+ else void openSession(item.id, item.sessionId);
2523
+ }, children: item.archived ? "\u5F00\u59CB\u65B0\u4F1A\u8BDD" : "\u6253\u5F00\u4F1A\u8BDD" })
2426
2524
  ] }, item.id)),
2427
2525
  sessions.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(State, { title: "\u8FD8\u6CA1\u6709\u5171\u4EAB\u4F1A\u8BDD", detail: "\u8054\u7CFB\u4EBA\u5B8C\u6210\u914D\u5BF9\u5E76\u53D1\u6765\u7B2C\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u4F19\u4F34\u4F1A\u521B\u5EFA\u7F51\u9875\u4E0E\u5FAE\u4FE1\u5171\u7528\u7684\u4F1A\u8BDD\u3002", compact: true })
2428
2526
  ] })
2429
2527
  ] });
2430
2528
  }
2431
2529
  var MEMORY_KINDS = ["all", "profile", "preference", "task", "event", "relationship", "emotion"];
2432
- function MemoryLibrary({ memories, reflections, editing, busy, setEditing, save, remove }) {
2530
+ function MemoryLibrary({ memories, reflections, graph, editing, busy, setEditing, save, remove }) {
2433
2531
  const activeMemories = (0, import_react4.useMemo)(() => memories.filter((item) => item.status === "active"), [memories]);
2434
2532
  const [mode, setMode] = (0, import_react4.useState)("memory");
2435
2533
  const [kind, setKind] = (0, import_react4.useState)("all");
@@ -2460,6 +2558,10 @@ function MemoryLibrary({ memories, reflections, editing, busy, setEditing, save,
2460
2558
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { type: "button", className: mode === "reflection" ? "is-active" : "", onClick: () => setMode("reflection"), children: [
2461
2559
  "\u6BCF\u65E5\u56DE\u987E ",
2462
2560
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("b", { children: reflections.length })
2561
+ ] }),
2562
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { type: "button", className: mode === "graph" ? "is-active" : "", onClick: () => setMode("graph"), children: [
2563
+ "\u5173\u7CFB\u56FE\u8C31 ",
2564
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("b", { children: graph.relations.length })
2463
2565
  ] })
2464
2566
  ] })
2465
2567
  ] }),
@@ -2481,7 +2583,7 @@ function MemoryLibrary({ memories, reflections, editing, busy, setEditing, save,
2481
2583
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { children: relativeTime(item.updatedAt) })
2482
2584
  ] }, item.id)) }),
2483
2585
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-partner-library-detail", children: selectedMemory ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MemoryCard, { item: selectedMemory, editing: editing?.id === selectedMemory.id ? editing : void 0, busy, setEditing, save, remove: () => remove(selectedMemory) }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(State, { title: "\u9009\u62E9\u4E00\u6761\u8BB0\u5FC6", detail: "\u8BB0\u5FC6\u8BE6\u60C5\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\u3002", compact: true }) })
2484
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-partner-library-browser", children: [
2586
+ ] }) : mode === "reflection" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-partner-library-browser", children: [
2485
2587
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-partner-library-list is-diary", role: "list", children: filteredReflections.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(State, { title: reflections.length === 0 ? "\u8FD8\u6CA1\u6709\u6BCF\u65E5\u56DE\u987E" : "\u6CA1\u6709\u5339\u914D\u7684\u56DE\u987E", detail: reflections.length === 0 ? "\u6BCF\u65E5\u56DE\u987E\u4F1A\u5728\u6BCF\u8F6E\u5B8C\u6574\u5BF9\u8BDD\u540E\u6301\u7EED\u66F4\u65B0\u3002" : "\u6362\u4E2A\u5173\u952E\u8BCD\u6216\u65E5\u671F\u8BD5\u8BD5\u3002", compact: true }) : filteredReflections.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { type: "button", role: "listitem", className: selectedReflection?.date === entry.date ? "is-active" : "", onClick: () => setSelectedDate(entry.date), children: [
2486
2588
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("time", { children: entry.date }),
2487
2589
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("strong", { children: [
@@ -2497,9 +2599,52 @@ function MemoryLibrary({ memories, reflections, editing, busy, setEditing, save,
2497
2599
  ] })
2498
2600
  ] }, entry.date)) }),
2499
2601
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-partner-library-detail", children: selectedReflection ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DailyReflectionDetail, { entry: selectedReflection }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(State, { title: "\u9009\u62E9\u4E00\u5929", detail: "\u5F53\u5929\u7684\u603B\u7ED3\u4E0E\u5F85\u529E\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\u3002", compact: true }) })
2500
- ] })
2602
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MemoryGraph, { graph, query: normalized })
2501
2603
  ] });
2502
2604
  }
2605
+ function MemoryGraph({ graph, query }) {
2606
+ const visible = graph.memories.filter((item) => !query || `${item.subject} ${item.content}`.toLocaleLowerCase().includes(query));
2607
+ const [selectedId, setSelectedId] = (0, import_react4.useState)();
2608
+ const selected = visible.find((item) => item.id === selectedId) ?? visible[0];
2609
+ const related = selected ? graph.relations.filter((item) => item.sourceMemoryId === selected.id || item.targetMemoryId === selected.id) : [];
2610
+ const byId = new Map(graph.memories.map((item) => [item.id, item]));
2611
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-partner-graph-browser", children: [
2612
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-partner-graph-index", children: visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(State, { title: "\u8FD8\u6CA1\u6709\u5173\u7CFB\u56FE\u8C31", detail: "\u5B8C\u6210\u6BCF\u65E5\u7EC8\u5BA1\u540E\uFF0C\u4F1A\u628A\u6709\u53EF\u9760\u8BC1\u636E\u7684\u8BB0\u5FC6\u5173\u7CFB\u6574\u7406\u5230\u8FD9\u91CC\u3002", compact: true }) : visible.map((item) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { type: "button", className: selected?.id === item.id ? "is-active" : "", onClick: () => setSelectedId(item.id), children: [
2613
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "data-kind": item.kind, children: memoryKind(item.kind) }),
2614
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: item.subject }),
2615
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("small", { children: [
2616
+ graph.relations.filter((edge) => edge.sourceMemoryId === item.id || edge.targetMemoryId === item.id).length,
2617
+ " \u6761\u5173\u7CFB"
2618
+ ] })
2619
+ ] }, item.id)) }),
2620
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-partner-graph-stage", children: selected ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2621
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("header", { children: [
2622
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "data-kind": selected.kind, children: memoryKind(selected.kind) }),
2623
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: selected.subject }),
2624
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: selected.content })
2625
+ ] }),
2626
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-partner-graph-links", children: related.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(State, { title: "\u6682\u65E0\u53EF\u9760\u5173\u7CFB", detail: "\u7EC8\u5BA1\u4E0D\u4F1A\u4E3A\u4E86\u586B\u6EE1\u56FE\u8C31\u800C\u731C\u6D4B\u5173\u7CFB\u3002", compact: true }) : related.map((edge) => {
2627
+ const outgoing = edge.sourceMemoryId === selected.id;
2628
+ const target = byId.get(outgoing ? edge.targetMemoryId : edge.sourceMemoryId);
2629
+ return target ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { type: "button", onClick: () => setSelectedId(target.id), children: [
2630
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("i", {}),
2631
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
2632
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { children: outgoing ? relationLabel(edge.kind) : `\u88AB${relationLabel(edge.kind)}` }),
2633
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: target.subject }),
2634
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: edge.label })
2635
+ ] }),
2636
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("b", { children: [
2637
+ Math.round(edge.confidence * 100),
2638
+ "%"
2639
+ ] })
2640
+ ] }, edge.id) : null;
2641
+ }) })
2642
+ ] }) : null })
2643
+ ] });
2644
+ }
2645
+ function relationLabel(kind) {
2646
+ return { supports: "\u652F\u6301", depends_on: "\u4F9D\u8D56", about: "\u5173\u4E8E", conflicts_with: "\u51B2\u7A81", follows: "\u540E\u7EED" }[kind];
2647
+ }
2503
2648
  function DailyReflectionDetail({ entry }) {
2504
2649
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("article", { className: "dsh-partner-diary-detail", children: [
2505
2650
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("header", { children: [