stratagate-dsh 0.2.21 → 0.2.26
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/CHANGELOG.md +30 -0
- package/README.md +14 -11
- package/dist/client.js +315 -53
- package/dist/index.js +954 -142
- package/dist/index.js.map +1 -1
- package/docs/README.zh-CN.md +14 -11
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -35,7 +35,7 @@ window.__ModuleLoader__.load({
|
|
|
35
35
|
--sg-warn-soft:var(--dsw-alias-state-warn-tertiary,#fef5e7);
|
|
36
36
|
--sg-danger:var(--dsw-alias-state-error-primary,#ec1313);
|
|
37
37
|
--sg-danger-soft:var(--dsw-alias-interactive-bg-hover-danger,rgba(236,19,19,.05));
|
|
38
|
-
box-sizing:border-box;width:100%;max-width:
|
|
38
|
+
box-sizing:border-box;width:100%;max-width:1200px;min-width:0;margin:0 auto;padding:16px 18px 32px;
|
|
39
39
|
background:var(--sg-page);color:var(--sg-text);font:14px/1.55 ui-sans-serif,system-ui,-apple-system,"Segoe UI","Microsoft YaHei",sans-serif;
|
|
40
40
|
letter-spacing:0;overflow-wrap:anywhere;
|
|
41
41
|
}
|
|
@@ -57,6 +57,15 @@ window.__ModuleLoader__.load({
|
|
|
57
57
|
.sg-block-list{position:relative;border:1px solid var(--sg-border);border-radius:9px;background:color-mix(in srgb,var(--sg-surface) 52%,transparent)}.sg-block-header,.sg-block-toggle{display:grid;grid-template-columns:88px 98px minmax(128px,1fr) 116px 24px;align-items:center;gap:8px;width:100%;min-width:0}.sg-block-header{padding:8px 12px;border-bottom:1px solid var(--sg-border);color:var(--sg-muted);font-size:11px}.sg-block-unit+.sg-block-unit{border-top:1px solid var(--sg-border)}.sg-block-toggle{padding:9px 12px;border:0;background:transparent;text-align:left;cursor:pointer}.sg-block-toggle:hover{background:color-mix(in srgb,var(--sg-accent) 5%,transparent)}.sg-level-cell{display:flex;align-items:center;gap:6px;min-width:0}.sg-level-badge{display:inline-grid;place-items:center;min-width:38px;height:25px;padding:0 8px;border:1px solid color-mix(in srgb,var(--sg-accent) 42%,var(--sg-border));border-radius:5px;font-weight:720}.sg-lifted{padding:1px 5px;border:1px solid var(--sg-border);border-radius:999px;color:var(--sg-muted);font-size:10px;white-space:nowrap}.sg-block-name{font-weight:670}.sg-block-distance,.sg-block-turn{color:var(--sg-muted);font-size:12px}.sg-row-chevron{color:var(--sg-muted);font-size:17px;text-align:right}.sg-open-row{cursor:default}.sg-open-row:hover{background:transparent}.sg-open-badge{display:inline-flex;align-items:center;height:25px;padding:0 8px;border:1px dashed var(--sg-muted);border-radius:5px;white-space:nowrap}.sg-block-expanded{padding:0 10px 11px}.sg-layer-panel{padding:10px;border:1px solid var(--sg-border);border-radius:8px;background:color-mix(in srgb,var(--sg-page) 82%,var(--sg-surface))}.sg-layer-heading{display:flex;justify-content:space-between;gap:10px;margin-bottom:8px}.sg-layer-heading strong{font-size:13px}.sg-layer-heading span{color:var(--sg-muted);font-size:11px}.sg-layer-stack{display:flex;flex-direction:column;gap:5px}.sg-layer-hover{position:relative}.sg-layer-hover:after{content:"";position:absolute;left:100%;top:0;width:12px;height:100%}.sg-layer-item{position:relative;display:grid;grid-template-columns:39px minmax(0,1fr) 30px;align-items:stretch;min-height:39px;border:1px solid var(--sg-border);border-radius:6px;background:color-mix(in srgb,var(--sg-soft) 55%,transparent);color:var(--sg-muted)}.sg-layer-item.current{border-color:color-mix(in srgb,var(--sg-accent) 72%,var(--sg-border));color:var(--sg-text);box-shadow:inset 2px 0 0 var(--sg-accent)}.sg-layer-label{display:grid;place-items:center;border-right:1px solid var(--sg-border);font-weight:720;color:inherit}.sg-layer-preview{padding:8px 10px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:12px}.sg-layer-more{width:30px;border:0;border-left:1px solid transparent;border-radius:0 6px 6px 0;background:transparent;color:inherit;cursor:pointer;font-weight:800;letter-spacing:1px}.sg-layer-more:hover,.sg-layer-more[aria-expanded="true"]{background:color-mix(in srgb,var(--sg-accent) 14%,transparent)}.sg-layer-menu{position:absolute;right:3px;top:34px;z-index:14;min-width:132px;padding:4px;border:1px solid var(--sg-border);border-radius:7px;background:var(--sg-surface);box-shadow:0 8px 24px rgba(0,0,0,.24)}.sg-layer-menu button{width:100%;padding:7px 9px;border:0;border-radius:5px;background:transparent;text-align:left;cursor:pointer;white-space:nowrap}.sg-layer-menu button:hover:not(:disabled){background:var(--sg-soft);color:var(--sg-accent)}.sg-layer-menu button:disabled{cursor:not-allowed;opacity:.45}.sg-layer-popover{position:absolute;left:calc(100% + 11px);top:-7px;z-index:12;width:min(360px,46vw);max-height:340px;padding:12px;overflow:auto;border:1px solid color-mix(in srgb,var(--sg-accent) 32%,var(--sg-border));border-radius:9px;background:var(--sg-surface);box-shadow:0 14px 36px rgba(0,0,0,.28);visibility:hidden;opacity:0;transform:translateX(-4px);transition:opacity .12s ease,transform .12s ease,visibility .12s}.sg-layer-hover:hover .sg-layer-popover,.sg-layer-item:focus-visible + .sg-layer-popover{visibility:visible;opacity:1;transform:translateX(0)}.sg-layer-popover strong{display:block;margin-bottom:7px}.sg-layer-full{margin:0;white-space:pre-wrap;font:12px/1.55 ui-sans-serif,system-ui,-apple-system,"Segoe UI","Microsoft YaHei",sans-serif}.sg-inline-error{padding:7px 9px;margin-bottom:7px;border-radius:6px;background:var(--sg-danger-soft);color:var(--sg-danger);font-size:12px}
|
|
58
58
|
.sg-loading{padding:32px 0}.sg-skeleton{height:12px;margin:10px 0;border-radius:4px;background:var(--sg-soft)}.sg-skeleton:nth-child(2){width:72%}.sg-empty{padding:34px 14px;text-align:center;color:var(--sg-muted)}.sg-empty strong{display:block;margin-bottom:5px;color:var(--sg-text)}.sg-error{padding:13px;margin-bottom:16px;border:1px solid color-mix(in srgb,var(--sg-danger) 28%,var(--sg-border));border-radius:8px;background:var(--sg-danger-soft)}.sg-error-title{font-weight:700;color:var(--sg-danger)}.sg-error details{margin-top:7px;font-size:12px}
|
|
59
59
|
.sg-muted{color:var(--sg-muted);font-size:12px}
|
|
60
|
+
.sg-long-tabs{display:flex;gap:4px;width:max-content;margin:0 0 14px;padding:4px;border:1px solid var(--sg-border);border-radius:999px;background:color-mix(in srgb,var(--sg-surface) 62%,transparent)}.sg-long-tabs button{padding:7px 19px;border:0;border-radius:999px;background:transparent;color:var(--sg-muted);cursor:pointer}.sg-long-tabs button.active{background:var(--sg-soft);color:var(--sg-accent)}
|
|
61
|
+
.sg-migration{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:9px;margin:0 0 10px;padding:9px 12px;border:1px solid color-mix(in srgb,var(--sg-accent) 28%,var(--sg-border));border-radius:8px;background:color-mix(in srgb,var(--sg-accent) 7%,transparent);font-size:12px}.sg-migration small{color:var(--sg-muted)}
|
|
62
|
+
.sg-long-explorer{position:relative}.sg-long-explorer.fullscreen{position:fixed;inset:12px;z-index:2147482000;display:flex;flex-direction:column;max-width:none;padding:18px;border:1px solid var(--sg-border);border-radius:12px;background:var(--sg-page);box-shadow:0 24px 80px rgba(0,0,0,.48)}.sg-long-explorer.fullscreen .sg-long-layout{flex:1;height:auto;min-height:0}.sg-long-explorer.fullscreen .sg-long-tabs{flex:0 0 auto}.sg-long-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:8px}.sg-long-toolbar .sg-search{flex:1;margin:0}.sg-toolbar-button{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:39px;padding:0 13px;border:1px solid var(--sg-border);border-radius:7px;background:var(--sg-surface);color:var(--sg-text);cursor:pointer;white-space:nowrap}.sg-toolbar-button:hover,.sg-toolbar-button[aria-expanded="true"]{border-color:var(--sg-accent);color:var(--sg-accent)}.sg-toolbar-count{color:var(--sg-muted);font-size:11px;white-space:nowrap}.sg-long-layout{display:grid;grid-template-columns:minmax(0,1.72fr) minmax(280px,.9fr);height:min(680px,calc(100vh - 320px));min-height:480px;border:1px solid var(--sg-border);border-radius:9px;overflow:hidden;background:color-mix(in srgb,var(--sg-page) 74%,transparent)}.sg-summary-layout{grid-template-columns:minmax(0,1fr)}
|
|
63
|
+
.sg-filterbar{display:flex;gap:7px;margin:0 0 8px;padding:8px;border:1px solid var(--sg-border);border-radius:7px;background:color-mix(in srgb,var(--sg-surface) 62%,transparent);flex-wrap:wrap}.sg-filterbar select{min-width:112px;padding:6px 25px 6px 8px;border:1px solid var(--sg-border);border-radius:6px;background:var(--sg-surface);color:var(--sg-text);font-size:11px}.sg-active-filter-count{display:inline-grid;place-items:center;min-width:18px;height:18px;padding:0 5px;border-radius:999px;background:var(--sg-accent);color:white;font-size:10px}
|
|
64
|
+
.sg-graph-canvas{position:relative;min-width:0;overflow:hidden;background-image:radial-gradient(circle at center,color-mix(in srgb,var(--sg-accent) 6%,transparent),transparent 62%)}.sg-graph-canvas svg{display:block;width:100%;height:calc(100% - 38px)}.sg-graph-edge{stroke:color-mix(in srgb,var(--sg-muted) 58%,transparent);stroke-width:1.25}.sg-edge-label{fill:var(--sg-text);paint-order:stroke;stroke:var(--sg-page);stroke-width:4px;font-size:11px}.sg-graph-node{cursor:pointer;outline:none}.sg-graph-node:hover circle,.sg-graph-node.selected circle{filter:drop-shadow(0 0 9px rgba(86,145,255,.35))}.sg-node-name{fill:var(--sg-text);font-size:13px;font-weight:720;pointer-events:none}.sg-node-type{fill:color-mix(in srgb,var(--sg-text) 70%,transparent);font-size:10px;pointer-events:none}.sg-graph-legend{position:absolute;left:14px;bottom:10px;display:flex;gap:14px;flex-wrap:wrap;color:var(--sg-muted);font-size:11px}.sg-graph-legend span{display:flex;align-items:center;gap:5px}.sg-graph-legend i{width:9px;height:9px;border-radius:50%}.sg-node-bubble{position:absolute;z-index:8;width:min(310px,42%);padding:12px;border:1px solid color-mix(in srgb,var(--sg-accent) 38%,var(--sg-border));border-radius:9px;background:color-mix(in srgb,var(--sg-surface) 94%,transparent);box-shadow:0 14px 34px rgba(0,0,0,.3);transform:translate(35px,-50%)}.sg-node-bubble.flip{transform:translate(calc(-100% - 35px),-50%)}.sg-node-bubble h3{margin:0;font-size:14px}.sg-node-bubble-head,.sg-node-bubble-foot{display:flex;align-items:center;justify-content:space-between;gap:8px}.sg-node-bubble-state{margin:7px 0;color:var(--sg-muted);font-size:11px}.sg-node-bubble ul{margin:7px 0;padding-left:17px;font-size:11px;line-height:1.55}.sg-node-bubble-foot{padding-top:8px;border-top:1px solid var(--sg-border);color:var(--sg-muted);font-size:11px}.sg-node-bubble button{border:0;background:transparent;color:var(--sg-accent);cursor:pointer;font-weight:680}
|
|
65
|
+
.sg-long-detail{min-width:0;padding:16px;overflow:auto;border-left:1px solid var(--sg-border);background:color-mix(in srgb,var(--sg-surface) 70%,transparent)}.sg-placeholder-detail{display:grid;place-items:center;color:var(--sg-muted);text-align:center}.sg-long-detail h2{margin:0;font-size:17px}.sg-long-detail section{padding:13px 0;border-top:1px solid var(--sg-border)}.sg-long-detail section:first-of-type{margin-top:13px}.sg-long-detail h3{margin:0 0 8px;font-size:13px}.sg-long-detail ul{margin:0;padding-left:18px;font-size:12px;line-height:1.65}.sg-detail-copy{margin:0;color:var(--sg-muted);font-size:12px;line-height:1.65;white-space:pre-wrap}.sg-node-head{display:flex;align-items:center;gap:10px}.sg-node-avatar{display:grid;place-items:center;width:42px;height:42px;border-radius:50%;color:white;font-size:20px}.sg-status-dot{color:var(--sg-good);font-size:12px}.sg-relation-row{display:grid;grid-template-columns:68px 1fr auto;gap:7px;padding:7px 0;border-bottom:1px solid color-mix(in srgb,var(--sg-border) 65%,transparent);font-size:12px}.sg-relation-row span,.sg-relation-row small{color:var(--sg-muted)}.sg-support-event{display:flex;align-items:center;justify-content:space-between;width:100%;margin:5px 0;padding:9px;border:1px solid var(--sg-border);border-radius:7px;background:transparent;color:var(--sg-text);text-align:left;cursor:pointer}.sg-support-event:hover{border-color:var(--sg-accent)}
|
|
66
|
+
.sg-timeline-layout{grid-template-columns:minmax(420px,1.35fr) minmax(320px,.9fr)}.sg-timeline{position:relative;padding:10px 20px 18px;overflow:auto}.sg-time-group{position:relative}.sg-time-group>h3{margin:7px 0 8px;padding-left:17px;font-size:13px}.sg-time-group:before{content:"";position:absolute;left:5px;top:30px;bottom:-12px;width:1px;background:var(--sg-border)}.sg-timeline-card{position:relative;display:grid;grid-template-columns:112px minmax(0,1fr) auto;gap:12px;width:100%;margin:0 0 8px;padding:12px 14px;border:1px solid var(--sg-border);border-radius:8px;background:color-mix(in srgb,var(--sg-surface) 62%,transparent);color:var(--sg-text);text-align:left;cursor:pointer}.sg-timeline-card:hover,.sg-timeline-card.selected{border-color:var(--sg-accent);background:color-mix(in srgb,var(--sg-accent) 7%,var(--sg-surface))}.sg-timeline-dot{position:absolute;left:-19px;top:17px;width:9px;height:9px;border:2px solid var(--sg-accent);border-radius:50%;background:var(--sg-page)}.sg-event-clock{font-size:11px;white-space:normal}.sg-event-clock small{display:block;margin-top:3px;color:var(--sg-muted);line-height:1.35}.sg-event-title-row{display:flex;align-items:center;gap:7px;min-width:0}.sg-event-title-row strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sg-event-main p{margin:5px 0 7px;color:var(--sg-muted);font-size:12px;line-height:1.4;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.sg-type-badge{display:inline-flex;align-items:center;width:max-content;padding:2px 7px;border:1px solid color-mix(in srgb,var(--sg-accent) 30%,var(--sg-border));border-radius:5px;background:color-mix(in srgb,var(--sg-accent) 10%,transparent);color:var(--sg-accent);font-size:10px}.sg-event-status{display:inline-flex;width:max-content;height:max-content;padding:3px 8px;border:1px solid var(--sg-border);border-radius:999px;color:var(--sg-muted);font-size:10px;white-space:nowrap}.sg-event-status.occurred{border-color:color-mix(in srgb,var(--sg-good) 40%,var(--sg-border));background:var(--sg-good-soft);color:var(--sg-good)}.sg-event-status.planned,.sg-event-status.ongoing{border-color:color-mix(in srgb,var(--sg-accent) 35%,var(--sg-border));color:var(--sg-accent)}.sg-event-status.cancelled{color:var(--sg-danger)}.sg-event-detail-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:7px}.sg-event-popover{position:fixed;z-index:2147483000;width:min(380px,calc(100vw - 24px));max-height:min(72vh,620px);overflow:auto;border:1px solid color-mix(in srgb,var(--sg-accent) 38%,var(--sg-border));border-radius:10px;background:var(--sg-page);box-shadow:0 18px 50px rgba(0,0,0,.38)}.sg-event-popover .sg-long-detail{border:0;background:transparent}.sg-popover-close{position:absolute;right:8px;top:8px;z-index:2;width:27px;height:27px;border:0;border-radius:6px;background:var(--sg-soft);cursor:pointer}.sg-time-grid{display:grid;grid-template-columns:92px 1fr;margin:0;border:1px solid var(--sg-border);border-radius:7px;overflow:hidden;font-size:11px}.sg-time-grid dt,.sg-time-grid dd{margin:0;padding:7px 8px;border-bottom:1px solid var(--sg-border)}.sg-time-grid dt{color:var(--sg-muted);background:var(--sg-soft)}.sg-time-grid dt:nth-last-of-type(1),.sg-time-grid dd:nth-last-of-type(1){border-bottom:0}.sg-entity-pill{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid var(--sg-border);border-radius:999px;background:transparent;color:var(--sg-text);font-size:11px;cursor:pointer}.sg-evidence-row{display:flex;justify-content:space-between;gap:8px;padding:6px 0;color:var(--sg-muted);font-size:11px}.sg-evidence-row code{max-width:180px;overflow:hidden;text-overflow:ellipsis}.sg-source-button{width:100%;padding:9px;border:1px solid color-mix(in srgb,var(--sg-accent) 42%,var(--sg-border));border-radius:7px;background:transparent;color:var(--sg-accent);cursor:pointer}
|
|
67
|
+
@media (max-width:900px){.sg-long-layout{grid-template-columns:1fr;height:auto;max-height:none}.sg-long-explorer.fullscreen{inset:4px;padding:10px}.sg-long-explorer.fullscreen .sg-long-layout{overflow:auto}.sg-graph-canvas{height:520px}.sg-long-detail{max-height:520px;border-top:1px solid var(--sg-border);border-left:0}.sg-timeline{max-height:520px}.sg-toolbar-count{display:none}.sg-node-bubble{width:min(300px,72%)}}
|
|
68
|
+
@media (max-width:620px){.sg-long-toolbar{flex-wrap:wrap}.sg-long-toolbar .sg-search{flex-basis:100%}.sg-toolbar-button{flex:1}.sg-timeline-card{grid-template-columns:78px minmax(0,1fr)}.sg-timeline-card>.sg-event-status{grid-column:2}.sg-node-bubble{left:50%!important;top:auto!important;bottom:45px;width:calc(100% - 24px);transform:translateX(-50%)!important}}
|
|
60
69
|
@media (max-width:860px){.sg-layer-hover:after{display:none}.sg-layer-popover{position:relative;left:auto;top:auto;width:auto;max-height:280px;margin:5px 0 1px;display:none;transform:none}.sg-layer-hover:hover .sg-layer-popover,.sg-layer-item:focus-visible + .sg-layer-popover{display:block;transform:none}}
|
|
61
70
|
@media (max-width:560px){.sg-memory{padding:12px 12px 26px}.sg-brand-name{font-size:14px}.sg-tabs{margin-left:-2px;margin-right:-2px}.sg-tab{padding-left:0;padding-right:0}.sg-alert{grid-template-columns:auto minmax(0,1fr)}.sg-alert>.sg-chevron{display:none}.sg-tech-row{grid-template-columns:1fr;gap:1px}.sg-counts{gap:16px}.sg-entry-title{font-size:14px}.sg-block-header{display:none}.sg-block-toggle{grid-template-columns:76px minmax(76px,1fr) 24px;gap:6px}.sg-block-turn{grid-column:1/3}.sg-block-distance{display:none}.sg-layer-heading{display:block}.sg-layer-heading span{display:block;margin-top:2px}}
|
|
62
71
|
.sg-decay-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.sg-conversation{display:flex;align-items:center;justify-content:flex-end;gap:5px;min-width:0;color:var(--sg-muted);font-size:11px}.sg-conversation select{min-width:0;max-width:230px;padding:3px 21px 3px 6px;border:1px solid var(--sg-border);border-radius:5px;background:var(--sg-surface);font-size:11px;text-overflow:ellipsis}.sg-distribution{scrollbar-width:none}.sg-distribution::-webkit-scrollbar{display:none}.sg-distribution-rail{display:block;width:100%;height:14px;margin:4px 0 0;accent-color:var(--sg-accent);cursor:pointer}.sg-distribution-rail:disabled{cursor:default;opacity:.38}.sg-layer-hover:after{display:none}.sg-layer-more-placeholder{width:30px}.sg-layer-popover{position:fixed!important;left:0;top:0;z-index:2147483000;width:min(390px,calc(100vw - 24px));max-height:min(70vh,520px);display:block!important;margin:0;overflow:auto;visibility:visible!important;opacity:1!important;transform:none!important;transition:opacity .1s ease;border:1px solid color-mix(in srgb,var(--sg-accent) 38%,var(--sg-border));background:var(--sg-surface);color:var(--sg-text);box-shadow:0 18px 50px rgba(0,0,0,.38)}
|
|
@@ -156,36 +165,291 @@ window.__ModuleLoader__.load({
|
|
|
156
165
|
h('span', { className: 'sg-processing-icon', 'aria-hidden': 'true' }, '↻'),
|
|
157
166
|
h('span', null,
|
|
158
167
|
h('span', { className: 'sg-processing-title' }, '正在触发记忆整理'),
|
|
159
|
-
h('span', { className: 'sg-processing-copy' }, 'Block、Event
|
|
168
|
+
h('span', { className: 'sg-processing-copy' }, 'Block、Event 和知识图谱正在生成,请稍候。')))
|
|
160
169
|
}
|
|
161
170
|
|
|
162
171
|
function SearchBox({ value, onChange }) {
|
|
163
172
|
return h('div', { className: 'sg-search' }, h('span', { className: 'sg-search-mark', 'aria-hidden': 'true' }, '⌕'), h('input', { value, onChange: (event) => onChange(event.target.value), placeholder: '搜索记忆、人物、项目、概念…', 'aria-label': '搜索长期记忆' }))
|
|
164
173
|
}
|
|
165
174
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
175
|
+
const NODE_META = {
|
|
176
|
+
person: ['人物', '#8b6ccf', '♙'], project: ['项目', '#6f55ae', '▣'], organization: ['组织', '#3978cf', '⌘'],
|
|
177
|
+
tool: ['工具', '#3b854d', '▤'], place: ['地点', '#a9632b', '⌂'],
|
|
169
178
|
}
|
|
179
|
+
const EVENT_TYPE_TEXT = { decision: '决策', release: '发布', task_completed: '任务完成', plan: '计划', change: '变更', cancellation: '取消', incident: '事件', meeting: '会议', collaboration: '协作', migration: '迁移', other: '其他' }
|
|
180
|
+
const EVENT_STATUS_TEXT = { occurred: '已发生', planned: '计划中', cancelled: '已取消', ongoing: '进行中', unknown: '未知' }
|
|
181
|
+
const EVENT_RELATIONS = [['supersedesEventIds', '取代'], ['conflictsWithEventIds', '冲突'], ['sameEventId', '同一事件'], ['beforeEventIds', '之前'], ['afterEventIds', '之后'], ['relatedEventIds', '相关']]
|
|
170
182
|
|
|
171
|
-
function
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
function exactTime(value) {
|
|
184
|
+
if (!value) return '未知'
|
|
185
|
+
const date = new Date(value)
|
|
186
|
+
return Number.isNaN(date.getTime()) ? String(value) : new Intl.DateTimeFormat('zh-CN', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false }).format(date)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function eventOccurrence(event) {
|
|
190
|
+
const happened = event.temporal?.happenedStart || event.temporal?.happenedEnd
|
|
191
|
+
return { value: happened || event.temporal?.mentionedAt || event.createdAt, known: Boolean(happened) }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function NodePill({ node, onClick }) {
|
|
195
|
+
const meta = NODE_META[node.type] || ['实体', '#64748b', '•']
|
|
196
|
+
return h('button', { className: 'sg-entity-pill', onClick }, h('span', { style: { color: meta[1] } }, meta[2]), node.name)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function NodeSummaryBubble({ node, x, y, width, onViewDetails }) {
|
|
200
|
+
if (!node) return null
|
|
201
|
+
const facts = (node.facts || []).filter((fact) => fact.status === 'active').slice(0, 5)
|
|
202
|
+
const eventCount = new Set(node.sourceEventIds || []).size
|
|
203
|
+
return h('aside', { className: 'sg-node-bubble ' + (x > width * .62 ? 'flip' : ''), style: { left: (x / width * 100) + '%', top: (y / 560 * 100) + '%' } },
|
|
204
|
+
h('div', { className: 'sg-node-bubble-head' }, h('h3', null, node.name), h('span', { className: 'sg-status-dot' }, '● ' + (node.status === 'active' ? '活跃' : node.status || '未知'))),
|
|
205
|
+
h('p', { className: 'sg-node-bubble-state' }, node.currentState || '暂无当前状态'),
|
|
206
|
+
facts.length ? h('ul', null, facts.map((fact) => h('li', { key: fact.id }, h('strong', null, fact.key + ':'), Array.isArray(fact.value) ? fact.value.join('、') : fact.value))) : h('p', { className: 'sg-node-bubble-state' }, '暂无关键事实'),
|
|
207
|
+
h('div', { className: 'sg-node-bubble-foot' }, h('span', null, eventCount + ' 条相关事件'), h('button', { type: 'button', onClick: onViewDetails }, '查看详情 →')))
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const GRAPH_NODE_RADIUS = { peripheral: 30, normal: 38, important: 46, core: 54 }
|
|
211
|
+
const GRAPH_IMPORTANCE_TEXT = { peripheral: '边缘节点', normal: '普通节点', important: '重要节点', core: '核心节点' }
|
|
212
|
+
|
|
213
|
+
function graphNameKey(value) {
|
|
214
|
+
return String(value || '').toLocaleLowerCase().replace(/[^\p{L}\p{N}]+/gu, '')
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function graphTimestamp(value) {
|
|
218
|
+
const timestamp = Date.parse(String(value || ''))
|
|
219
|
+
return Number.isFinite(timestamp) ? timestamp : 0
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Importance is derived from the complete persisted graph snapshot. The caller
|
|
223
|
+
// deliberately computes this before search/type filters are applied, so a view
|
|
224
|
+
// change never changes the meaning of a node's size.
|
|
225
|
+
function graphNodeImportance(nodes, edges, project) {
|
|
226
|
+
const activeEdges = edges.filter((edge) => edge.status === 'active')
|
|
227
|
+
const activeSupportingEvents = (node) => (node.supportingEvents || []).filter((event) => event.status !== 'forgotten' && event.status !== 'archived')
|
|
228
|
+
const relationCounts = new Map(nodes.map((node) => [node.id, 0]))
|
|
229
|
+
const neighbors = new Map(nodes.map((node) => [node.id, new Set()]))
|
|
230
|
+
for (const edge of activeEdges) {
|
|
231
|
+
relationCounts.set(edge.fromNodeId, (relationCounts.get(edge.fromNodeId) || 0) + 1)
|
|
232
|
+
relationCounts.set(edge.toNodeId, (relationCounts.get(edge.toNodeId) || 0) + 1)
|
|
233
|
+
neighbors.get(edge.fromNodeId)?.add(edge.toNodeId)
|
|
234
|
+
neighbors.get(edge.toNodeId)?.add(edge.fromNodeId)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const projectKey = graphNameKey(project)
|
|
238
|
+
const directWorkspaceNodes = new Set(nodes.filter((node) => projectKey && [node.name, ...(node.aliases || [])].some((name) => {
|
|
239
|
+
const key = graphNameKey(name)
|
|
240
|
+
return key === projectKey || (key.length >= 5 && projectKey.length >= 5 && (key.includes(projectKey) || projectKey.includes(key)))
|
|
241
|
+
})).map((node) => node.id))
|
|
242
|
+
const workspaceAffinity = new Map(nodes.map((node) => [node.id, directWorkspaceNodes.has(node.id) ? 1 : 0]))
|
|
243
|
+
for (const nodeId of directWorkspaceNodes) {
|
|
244
|
+
for (const neighborId of neighbors.get(nodeId) || []) {
|
|
245
|
+
workspaceAffinity.set(neighborId, Math.max(workspaceAffinity.get(neighborId) || 0, .55))
|
|
246
|
+
for (const secondHopId of neighbors.get(neighborId) || []) {
|
|
247
|
+
workspaceAffinity.set(secondHopId, Math.max(workspaceAffinity.get(secondHopId) || 0, .25))
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const supportCounts = new Map(nodes.map((node) => {
|
|
253
|
+
const supportingEvents = activeSupportingEvents(node)
|
|
254
|
+
return [node.id, supportingEvents.length ? new Set(supportingEvents.map((event) => event.id)).size : new Set(node.sourceEventIds || []).size]
|
|
255
|
+
}))
|
|
256
|
+
const maxSupport = Math.max(1, ...supportCounts.values())
|
|
257
|
+
const maxRelations = Math.max(1, ...relationCounts.values())
|
|
258
|
+
let latestActivity = 0
|
|
259
|
+
for (const node of nodes) {
|
|
260
|
+
latestActivity = Math.max(latestActivity, graphTimestamp(node.updatedAt))
|
|
261
|
+
for (const event of activeSupportingEvents(node)) latestActivity = Math.max(latestActivity, graphTimestamp(event.updatedAt || event.createdAt))
|
|
262
|
+
}
|
|
263
|
+
for (const edge of activeEdges) latestActivity = Math.max(latestActivity, graphTimestamp(edge.updatedAt))
|
|
264
|
+
const halfLife = 90 * 24 * 60 * 60 * 1000
|
|
265
|
+
const recency = (timestamp) => timestamp && latestActivity ? Math.exp(-Math.max(0, latestActivity - timestamp) / halfLife) : 0
|
|
266
|
+
|
|
267
|
+
const scored = nodes.map((node) => {
|
|
268
|
+
const supportingEvents = activeSupportingEvents(node)
|
|
269
|
+
const sustainedMentions = supportingEvents.map((event) => recency(graphTimestamp(event.updatedAt || event.createdAt))).sort((left, right) => right - left).slice(0, 3)
|
|
270
|
+
const recentScore = .25 * recency(graphTimestamp(node.updatedAt)) + .75 * sustainedMentions.reduce((sum, value) => sum + value, 0) / 3
|
|
271
|
+
const supportScore = Math.log1p(supportCounts.get(node.id) || 0) / Math.log1p(maxSupport)
|
|
272
|
+
const relationScore = Math.log1p(relationCounts.get(node.id) || 0) / Math.log1p(maxRelations)
|
|
273
|
+
const score = .42 * supportScore + .28 * relationScore + .15 * recentScore + .15 * (workspaceAffinity.get(node.id) || 0)
|
|
274
|
+
return { id: node.id, score }
|
|
275
|
+
})
|
|
276
|
+
const ordered = scored.map(({ score }) => score).sort((left, right) => left - right)
|
|
277
|
+
const quantile = (ratio) => ordered[Math.ceil(Math.max(0, ordered.length - 1) * ratio)] || 0
|
|
278
|
+
const spread = (ordered[ordered.length - 1] || 0) - (ordered[0] || 0)
|
|
279
|
+
const thresholds = { normal: quantile(.25), important: quantile(.6), core: quantile(.85) }
|
|
280
|
+
return new Map(scored.map(({ id, score }) => {
|
|
281
|
+
let tier
|
|
282
|
+
if (spread < .02) tier = score >= .65 ? 'core' : score >= .38 ? 'important' : 'normal'
|
|
283
|
+
else if (score >= thresholds.core) tier = 'core'
|
|
284
|
+
else if (score >= thresholds.important) tier = 'important'
|
|
285
|
+
else if (score >= thresholds.normal) tier = 'normal'
|
|
286
|
+
else tier = 'peripheral'
|
|
287
|
+
return [id, { score, tier, radius: GRAPH_NODE_RADIUS[tier] }]
|
|
288
|
+
}))
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function GraphCanvas({ nodes, edges, importance, selectedId, onSelect, showSummary = false, onViewDetails }) {
|
|
292
|
+
if (!nodes.length) return h(Empty, { title: '知识图谱正在形成', copy: 'Event 会在后台分批投影为节点与关系。' })
|
|
293
|
+
const width = 760; const height = 560; const cx = width / 2; const cy = height / 2
|
|
294
|
+
const placed = nodes.slice(0, 40).map((node, index) => {
|
|
295
|
+
if (index === 0) return { node, x: cx, y: cy }
|
|
296
|
+
const ring = index <= 10 ? 1 : 2; const slot = ring === 1 ? index - 1 : index - 11
|
|
297
|
+
const count = ring === 1 ? Math.min(10, Math.max(1, nodes.length - 1)) : Math.max(1, Math.min(29, nodes.length - 11))
|
|
298
|
+
const angle = -Math.PI / 2 + slot * Math.PI * 2 / count
|
|
299
|
+
const radiusX = ring === 1 ? 205 : 320; const radiusY = ring === 1 ? 170 : 245
|
|
300
|
+
return { node, x: cx + Math.cos(angle) * radiusX, y: cy + Math.sin(angle) * radiusY }
|
|
301
|
+
})
|
|
302
|
+
const positions = new Map(placed.map((item) => [item.node.id, item]))
|
|
303
|
+
return h('div', { className: 'sg-graph-canvas' },
|
|
304
|
+
h('svg', { viewBox: `0 0 ${width} ${height}`, role: 'img', 'aria-label': '知识图谱' },
|
|
305
|
+
h('defs', null, h('marker', { id: 'sg-arrow', markerWidth: '8', markerHeight: '8', refX: '7', refY: '4', orient: 'auto' }, h('path', { d: 'M0,0 L8,4 L0,8 Z', fill: 'var(--sg-muted)' }))),
|
|
306
|
+
edges.filter((edge) => edge.status === 'active').map((edge) => {
|
|
307
|
+
const from = positions.get(edge.fromNodeId); const to = positions.get(edge.toNodeId)
|
|
308
|
+
if (!from || !to) return null
|
|
309
|
+
const mx = (from.x + to.x) / 2; const my = (from.y + to.y) / 2
|
|
310
|
+
return h('g', { key: edge.id }, h('line', { x1: from.x, y1: from.y, x2: to.x, y2: to.y, className: 'sg-graph-edge', markerEnd: 'url(#sg-arrow)' }), h('text', { x: mx, y: my - 5, className: 'sg-edge-label' }, edge.relation))
|
|
311
|
+
}),
|
|
312
|
+
placed.map(({ node, x, y }) => {
|
|
313
|
+
const meta = NODE_META[node.type] || ['实体', '#64748b', '•']; const selected = node.id === selectedId
|
|
314
|
+
const visualImportance = importance.get(node.id) || { tier: 'normal', radius: GRAPH_NODE_RADIUS.normal }
|
|
315
|
+
return h('g', { key: node.id, className: 'sg-graph-node ' + (selected ? 'selected' : ''), 'data-importance': visualImportance.tier, onClick: () => onSelect(node.id), onKeyDown: (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); onSelect(node.id) } }, role: 'button', tabIndex: 0, 'aria-label': node.name + ',' + meta[0] + ',' + GRAPH_IMPORTANCE_TEXT[visualImportance.tier] },
|
|
316
|
+
h('circle', { cx: x, cy: y, r: visualImportance.radius, fill: meta[1], fillOpacity: '.28', stroke: meta[1], strokeWidth: selected ? '3' : '1.5' }),
|
|
317
|
+
h('text', { x, y: y - 7, textAnchor: 'middle', className: 'sg-node-name' }, node.name.slice(0, 14)),
|
|
318
|
+
h('text', { x, y: y + 14, textAnchor: 'middle', className: 'sg-node-type' }, meta[0]))
|
|
319
|
+
})),
|
|
320
|
+
showSummary && selectedId ? (() => { const item = positions.get(selectedId); return item ? h(NodeSummaryBubble, { node: item.node, x: item.x, y: item.y, width, onViewDetails }) : null })() : null,
|
|
321
|
+
h('div', { className: 'sg-graph-legend' }, Object.entries(NODE_META).map(([type, meta]) => h('span', { key: type }, h('i', { style: { background: meta[1] } }), meta[0]))))
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function NodeDetailPanel({ node, nodes, edges, events, onEvent }) {
|
|
325
|
+
if (!node) return h('aside', { className: 'sg-long-detail sg-placeholder-detail' }, '选择节点查看当前状态与证据')
|
|
326
|
+
const meta = NODE_META[node.type] || ['实体', '#64748b', '•']
|
|
327
|
+
const relations = edges.filter((edge) => edge.status === 'active' && (edge.fromNodeId === node.id || edge.toNodeId === node.id))
|
|
328
|
+
const eventMap = new Map(events.map((event) => [event.id, event]))
|
|
329
|
+
return h('aside', { className: 'sg-long-detail' },
|
|
330
|
+
h('div', { className: 'sg-node-head' }, h('span', { className: 'sg-node-avatar', style: { background: meta[1] } }, meta[2]), h('div', null, h('h2', null, node.name), h('span', { className: 'sg-muted' }, meta[0]))),
|
|
331
|
+
h('section', null, h('h3', null, '当前状态'), h('span', { className: 'sg-status-dot' }, '● ' + (node.status === 'active' ? '活跃' : node.status)), node.currentState ? h('p', { className: 'sg-detail-copy' }, node.currentState) : null),
|
|
332
|
+
h('section', null, h('h3', null, '关键事实'), node.facts?.filter((fact) => fact.status === 'active').length ? h('ul', null, node.facts.filter((fact) => fact.status === 'active').map((fact) => h('li', { key: fact.id }, h('strong', null, fact.key + ':'), Array.isArray(fact.value) ? fact.value.join('、') : fact.value))) : h('p', { className: 'sg-muted' }, '暂无关键事实')),
|
|
333
|
+
h('section', null, h('h3', null, '相关关系'), relations.length ? relations.map((edge) => { const outgoing = edge.fromNodeId === node.id; const other = nodes.find((item) => item.id === (outgoing ? edge.toNodeId : edge.fromNodeId)); return h('div', { key: edge.id, className: 'sg-relation-row' }, h('span', null, outgoing ? edge.relation : '被' + edge.relation), h('strong', null, other?.name || '未知节点'), h('small', null, Math.round(edge.confidence * 100) + '%')) }) : h('p', { className: 'sg-muted' }, '暂无关系')),
|
|
334
|
+
h('section', null, h('h3', null, '支撑事件'), (node.sourceEventIds || []).flatMap((id) => eventMap.get(id) || []).slice(0, 8).map((event) => h('button', { key: event.id, className: 'sg-support-event', onClick: () => onEvent(event.id) }, h('span', null, exactTime(eventOccurrence(event).value) + ' · ' + event.title), h('b', null, '›')))))
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function EventDetailPanel({ event, nodes, events, onNode, openSource, className = '', onMouseEnter, onMouseLeave }) {
|
|
338
|
+
if (!event) return h('aside', { className: 'sg-long-detail sg-placeholder-detail' }, '选择事件查看完整时间、关系与证据')
|
|
339
|
+
const temporal = event.temporal || {}; const eventMap = new Map(events.map((item) => [item.id, item])); const nodeMap = new Map(nodes.map((node) => [node.id, node]))
|
|
340
|
+
const relations = EVENT_RELATIONS.flatMap(([key, label]) => { const value = temporal[key]; const ids = Array.isArray(value) ? value : value ? [value] : []; return ids.map((id) => ({ id, label })) })
|
|
341
|
+
return h('aside', { className: 'sg-long-detail ' + className, onMouseEnter, onMouseLeave },
|
|
342
|
+
h('div', { className: 'sg-event-detail-head' }, h('h2', null, event.title), h('span', { className: 'sg-event-status ' + (temporal.status || 'unknown') }, EVENT_STATUS_TEXT[temporal.status] || '未知')),
|
|
343
|
+
h('span', { className: 'sg-type-badge' }, EVENT_TYPE_TEXT[temporal.eventType] || temporal.eventType || '其他'),
|
|
344
|
+
h('section', null, h('h3', null, '摘要'), h('p', { className: 'sg-detail-copy' }, event.summary || '暂无摘要')),
|
|
345
|
+
event.narrative && event.narrative !== event.summary ? h('section', null, h('h3', null, '事件叙述'), h('p', { className: 'sg-detail-copy' }, event.narrative)) : null,
|
|
346
|
+
h('section', null, h('h3', null, '时间信息'), h('dl', { className: 'sg-time-grid' },
|
|
347
|
+
h('dt', null, '实际发生时间'), h('dd', null, temporal.happenedStart ? exactTime(temporal.happenedStart) + (temporal.happenedEnd && temporal.happenedEnd !== temporal.happenedStart ? ' — ' + exactTime(temporal.happenedEnd) : '') : '未知'),
|
|
348
|
+
h('dt', null, '提及时间'), h('dd', null, exactTime(temporal.mentionedAt || event.createdAt)),
|
|
349
|
+
h('dt', null, '时间精度'), h('dd', null, temporal.precision || 'unknown'),
|
|
350
|
+
h('dt', null, '原始表达'), h('dd', null, temporal.originalText || '未记录'))),
|
|
351
|
+
h('section', null, h('h3', null, '参与实体'), h('div', { className: 'sg-tags' }, (temporal.participantNodeIds || []).flatMap((id) => nodeMap.get(id) || []).map((node) => h(NodePill, { key: node.id, node, onClick: () => onNode(node.id) })), !(temporal.participantNodeIds || []).length ? (temporal.participants || []).map((name) => h('span', { key: name, className: 'sg-tag' }, name)) : null)),
|
|
352
|
+
relations.length ? h('section', null, h('h3', null, '事件关系'), relations.map((relation) => h('div', { key: relation.label + relation.id, className: 'sg-relation-row' }, h('span', null, relation.label), h('strong', null, eventMap.get(relation.id)?.title || relation.id)))) : null,
|
|
353
|
+
h('section', null, h('h3', null, '证据来源'), h('div', { className: 'sg-evidence-row' }, h('span', null, '来源 Block'), h('code', null, event.sourceBlockId)), h('div', { className: 'sg-evidence-row' }, h('span', null, '置信度'), h('strong', null, Math.round((event.confidence || 0) * 100) + '%'))),
|
|
354
|
+
h('button', { className: 'sg-source-button', onClick: () => openSource(event) }, '查看来源原始消息 →'))
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function TimelineList({ events, nodes, query, filters, onSelect, selectedId, floating = false, onNode, openSource }) {
|
|
358
|
+
const [preview, setPreview] = React.useState(null)
|
|
359
|
+
const pinnedId = React.useRef('')
|
|
360
|
+
const hideTimer = React.useRef(null)
|
|
361
|
+
const cancelHide = () => { if (hideTimer.current) window.clearTimeout(hideTimer.current); hideTimer.current = null }
|
|
362
|
+
const hidePreview = () => { cancelHide(); setPreview(null); pinnedId.current = '' }
|
|
363
|
+
const showPreview = (event, target, pin = false) => {
|
|
364
|
+
if (!floating || (!pin && pinnedId.current)) return
|
|
365
|
+
cancelHide()
|
|
366
|
+
const rect = target.getBoundingClientRect(); const width = Math.min(380, window.innerWidth - 24); const maxHeight = Math.min(620, window.innerHeight * .72)
|
|
367
|
+
const rightSide = rect.right + 12 + width <= window.innerWidth - 12
|
|
368
|
+
const left = rightSide ? rect.right + 12 : Math.max(12, rect.left - width - 12)
|
|
369
|
+
const top = Math.min(Math.max(12, rect.top - 8), Math.max(12, window.innerHeight - maxHeight - 12))
|
|
370
|
+
setPreview({ event, left, top })
|
|
371
|
+
if (pin) pinnedId.current = event.id
|
|
372
|
+
}
|
|
373
|
+
const scheduleHide = () => {
|
|
374
|
+
if (pinnedId.current) return
|
|
375
|
+
cancelHide()
|
|
376
|
+
hideTimer.current = window.setTimeout(() => setPreview(null), 140)
|
|
377
|
+
}
|
|
378
|
+
React.useEffect(() => () => cancelHide(), [])
|
|
379
|
+
const normalized = query.trim().toLocaleLowerCase(); const now = new Date(); const weekAgo = now.getTime() - 7 * 86400000
|
|
380
|
+
const groups = { '今天': [], '本周': [], '更早': [] }
|
|
381
|
+
events.filter((event) => event.status !== 'forgotten' && event.status !== 'archived')
|
|
382
|
+
.filter((event) => !normalized || JSON.stringify(event).toLocaleLowerCase().includes(normalized))
|
|
383
|
+
.filter((event) => !filters.type || event.temporal?.eventType === filters.type)
|
|
384
|
+
.filter((event) => !filters.status || event.temporal?.status === filters.status)
|
|
385
|
+
.filter((event) => !filters.node || (event.temporal?.participantNodeIds || []).includes(filters.node))
|
|
386
|
+
.filter((event) => { const info = eventOccurrence(event); const time = Date.parse(info.value); return !filters.time || (filters.time === 'unknown' ? !info.known : filters.time === 'today' ? Number.isFinite(time) && new Date(time).toDateString() === now.toDateString() : filters.time === 'week' ? time >= weekAgo : true) })
|
|
387
|
+
.sort((a, b) => String(eventOccurrence(b).value).localeCompare(String(eventOccurrence(a).value)))
|
|
388
|
+
.forEach((event) => { const info = eventOccurrence(event); const time = Date.parse(info.value); const sameDay = Number.isFinite(time) && new Date(time).toDateString() === now.toDateString(); groups[sameDay ? '今天' : time >= weekAgo ? '本周' : '更早'].push(event) })
|
|
389
|
+
const nodeMap = new Map(nodes.map((node) => [node.id, node]))
|
|
390
|
+
return h('div', { className: 'sg-timeline' }, Object.entries(groups).map(([label, items]) => items.length ? h('section', { key: label, className: 'sg-time-group' }, h('h3', null, label), items.map((event) => {
|
|
391
|
+
const info = eventOccurrence(event); const temporal = event.temporal || {}
|
|
392
|
+
return h('button', { key: event.id, className: 'sg-timeline-card ' + (event.id === selectedId ? 'selected' : ''), onMouseEnter: (hover) => showPreview(event, hover.currentTarget), onMouseLeave: scheduleHide, onFocus: (focus) => showPreview(event, focus.currentTarget), onBlur: scheduleHide, onClick: (click) => { onSelect(event.id); showPreview(event, click.currentTarget, true) } },
|
|
393
|
+
h('span', { className: 'sg-timeline-dot' }), h('div', { className: 'sg-event-clock' }, info.known ? exactTime(info.value).replace(/^\d{4}\//, '') : '发生时间未知', !info.known ? h('small', null, '提及于 ' + exactTime(info.value)) : null),
|
|
394
|
+
h('div', { className: 'sg-event-main' }, h('div', { className: 'sg-event-title-row' }, h('strong', null, event.title), h('span', { className: 'sg-type-badge' }, EVENT_TYPE_TEXT[temporal.eventType] || temporal.eventType || '其他')), h('p', null, event.summary || '暂无摘要'), h('div', { className: 'sg-tags' }, (temporal.participantNodeIds || []).flatMap((id) => nodeMap.get(id) || []).map((node) => h('span', { key: node.id, className: 'sg-tag' }, node.name)), !(temporal.participantNodeIds || []).length ? (temporal.participants || []).map((name) => h('span', { key: name, className: 'sg-tag' }, name)) : null)),
|
|
395
|
+
h('span', { className: 'sg-event-status ' + (temporal.status || 'unknown') }, EVENT_STATUS_TEXT[temporal.status] || '未知'))
|
|
396
|
+
})) : null), floating && preview ? h('div', { className: 'sg-event-popover', style: { left: preview.left + 'px', top: preview.top + 'px' }, onMouseEnter: cancelHide, onMouseLeave: scheduleHide },
|
|
397
|
+
h('button', { type: 'button', className: 'sg-popover-close', 'aria-label': '关闭事件详情', onClick: hidePreview }, '×'),
|
|
398
|
+
h(EventDetailPanel, { event: preview.event, nodes, events, onNode, openSource })) : null)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function LongTermPage({ events, graph, project, query, setQuery, openEvent }) {
|
|
402
|
+
const [mode, setMode] = React.useState('graph')
|
|
403
|
+
const [selectedNodeId, setSelectedNodeId] = React.useState('')
|
|
404
|
+
const [selectedEventId, setSelectedEventId] = React.useState('')
|
|
405
|
+
const [eventFilters, setEventFilters] = React.useState({ time: '', node: '', type: '', status: '' })
|
|
406
|
+
const [nodeType, setNodeType] = React.useState('')
|
|
407
|
+
const [filtersOpen, setFiltersOpen] = React.useState(false)
|
|
408
|
+
const [fullScreen, setFullScreen] = React.useState(false)
|
|
409
|
+
const nodes = graph.nodes || []; const edges = graph.edges || []; const normalized = query.trim().toLocaleLowerCase()
|
|
410
|
+
const nodeImportance = React.useMemo(() => graphNodeImportance(nodes, edges, project), [nodes, edges, project])
|
|
411
|
+
React.useEffect(() => {
|
|
412
|
+
if (!fullScreen) return undefined
|
|
413
|
+
const previous = document.body.style.overflow
|
|
414
|
+
document.body.style.overflow = 'hidden'
|
|
415
|
+
const onKeyDown = (event) => { if (event.key === 'Escape') { setFullScreen(false); setFiltersOpen(false) } }
|
|
416
|
+
window.addEventListener('keydown', onKeyDown)
|
|
417
|
+
return () => { document.body.style.overflow = previous; window.removeEventListener('keydown', onKeyDown) }
|
|
418
|
+
}, [fullScreen])
|
|
419
|
+
const visibleNodes = nodes.filter((node) => node.status !== 'archived').filter((node) => !nodeType || node.type === nodeType).filter((node) => !normalized || JSON.stringify(node).toLocaleLowerCase().includes(normalized))
|
|
420
|
+
const selectEvent = (id) => { setSelectedEventId(id); setMode('timeline') }
|
|
421
|
+
const selectNode = (id) => { setSelectedNodeId(id); setMode('graph') }
|
|
422
|
+
const openExplorer = () => {
|
|
423
|
+
if (mode === 'graph' && !selectedNodeId && visibleNodes[0]) setSelectedNodeId(visibleNodes[0].id)
|
|
424
|
+
if (mode === 'timeline' && !selectedEventId && events[0]) setSelectedEventId(events[0].id)
|
|
425
|
+
setFiltersOpen(true)
|
|
426
|
+
setFullScreen(true)
|
|
427
|
+
}
|
|
428
|
+
const migration = graph.migration || { projected: events.length, total: events.length, complete: true }
|
|
429
|
+
const activeFilterCount = mode === 'graph' ? Number(Boolean(nodeType)) : Object.values(eventFilters).filter(Boolean).length
|
|
430
|
+
const filterControls = mode === 'graph'
|
|
431
|
+
? h('div', { className: 'sg-filterbar' }, h('select', { value: nodeType, onChange: (event) => setNodeType(event.target.value), 'aria-label': '节点类型' }, h('option', { value: '' }, '全部节点类型'), Object.entries(NODE_META).map(([value, meta]) => h('option', { key: value, value }, meta[0]))))
|
|
432
|
+
: h('div', { className: 'sg-filterbar' },
|
|
433
|
+
h('select', { value: eventFilters.time, onChange: (event) => setEventFilters({ ...eventFilters, time: event.target.value }), 'aria-label': '时间范围' }, h('option', { value: '' }, '全部时间'), h('option', { value: 'today' }, '今天'), h('option', { value: 'week' }, '本周'), h('option', { value: 'unknown' }, '发生时间未知')),
|
|
434
|
+
h('select', { value: eventFilters.node, onChange: (event) => setEventFilters({ ...eventFilters, node: event.target.value }), 'aria-label': '参与实体' }, h('option', { value: '' }, '全部实体'), nodes.map((node) => h('option', { key: node.id, value: node.id }, node.name))),
|
|
435
|
+
h('select', { value: eventFilters.type, onChange: (event) => setEventFilters({ ...eventFilters, type: event.target.value }), 'aria-label': '事件类型' }, h('option', { value: '' }, '全部类型'), Object.entries(EVENT_TYPE_TEXT).map(([value, label]) => h('option', { key: value, value }, label))),
|
|
436
|
+
h('select', { value: eventFilters.status, onChange: (event) => setEventFilters({ ...eventFilters, status: event.target.value }), 'aria-label': '事件状态' }, h('option', { value: '' }, '全部状态'), Object.entries(EVENT_STATUS_TEXT).map(([value, label]) => h('option', { key: value, value }, label))))
|
|
437
|
+
return h('section', { className: 'sg-long-explorer ' + (fullScreen ? 'fullscreen' : ''), 'aria-label': fullScreen ? '长期记忆全屏探索' : '长期记忆浏览' },
|
|
438
|
+
h('nav', { className: 'sg-long-tabs' }, h('button', { className: mode === 'graph' ? 'active' : '', onClick: () => setMode('graph') }, '知识图谱'), h('button', { className: mode === 'timeline' ? 'active' : '', onClick: () => setMode('timeline') }, '事件时间线')),
|
|
439
|
+
!migration.complete ? h('div', { className: 'sg-migration', role: 'status' }, h('span', { className: 'sg-processing-icon' }, '↻'), h('span', null, '正在升级长期记忆 · ' + migration.projected + ' / ' + migration.total + ' Events'), h('small', null, migration.failed ? migration.failed + ' 批待重试' : '后台增量处理,不影响其他功能')) : null,
|
|
440
|
+
h('div', { className: 'sg-long-toolbar' },
|
|
441
|
+
h(SearchBox, { value: query, onChange: setQuery }),
|
|
442
|
+
h('button', { type: 'button', className: 'sg-toolbar-button', 'aria-expanded': filtersOpen ? 'true' : 'false', onClick: () => setFiltersOpen(!filtersOpen) }, '筛选 ▾', activeFilterCount ? h('span', { className: 'sg-active-filter-count' }, activeFilterCount) : null),
|
|
443
|
+
h('span', { className: 'sg-toolbar-count' }, mode === 'graph' ? visibleNodes.length + ' 个节点 · ' + edges.filter((edge) => edge.status === 'active').length + ' 条关系' : events.length + ' 条事件 · ' + project),
|
|
444
|
+
h('button', { type: 'button', className: 'sg-toolbar-button', onClick: fullScreen ? () => { setFullScreen(false); setFiltersOpen(false) } : openExplorer }, fullScreen ? '退出全屏' : '⛶ 全屏查看' + (mode === 'graph' ? '图谱' : '时间线'))),
|
|
445
|
+
filtersOpen ? filterControls : null,
|
|
446
|
+
mode === 'graph'
|
|
447
|
+
? h('div', { className: 'sg-long-layout ' + (fullScreen ? '' : 'sg-summary-layout') },
|
|
448
|
+
h(GraphCanvas, { nodes: visibleNodes, edges, importance: nodeImportance, selectedId: selectedNodeId, onSelect: setSelectedNodeId, showSummary: !fullScreen, onViewDetails: openExplorer }),
|
|
449
|
+
fullScreen ? h(NodeDetailPanel, { node: nodes.find((node) => node.id === selectedNodeId), nodes, edges, events, onEvent: selectEvent }) : null)
|
|
450
|
+
: h('div', { className: 'sg-long-layout ' + (fullScreen ? 'sg-timeline-layout' : 'sg-summary-layout') },
|
|
451
|
+
h(TimelineList, { events, nodes, query, filters: eventFilters, onSelect: setSelectedEventId, selectedId: selectedEventId, floating: !fullScreen, onNode: selectNode, openSource: openEvent }),
|
|
452
|
+
fullScreen ? h(EventDetailPanel, { event: events.find((event) => event.id === selectedEventId), nodes, events, onNode: selectNode, openSource: openEvent }) : null))
|
|
189
453
|
}
|
|
190
454
|
|
|
191
455
|
function previewText(value) {
|
|
@@ -372,17 +636,17 @@ window.__ModuleLoader__.load({
|
|
|
372
636
|
h('h3', { className: 'sg-section-title' }, '来源'),
|
|
373
637
|
h('div', { className: 'sg-source-label' }, h('span', { className: 'sg-source-icon', 'aria-hidden': 'true' }, '▣'), h('span', null, '当前 DeepSeek 对话')),
|
|
374
638
|
h('details', { className: 'sg-tech' }, h('summary', null, '技术详情'), h('div', { className: 'sg-tech-body' },
|
|
375
|
-
h('div', { className: 'sg-tech-row' }, h('span', { className: 'sg-muted' }, kind === 'block' ? 'Block ID' :
|
|
639
|
+
h('div', { className: 'sg-tech-row' }, h('span', { className: 'sg-muted' }, kind === 'block' ? 'Block ID' : 'Event ID'), h('span', { className: 'sg-code' }, item.id)),
|
|
376
640
|
kind === 'block' && item.turnRange ? h('div', { className: 'sg-tech-row' }, h('span', { className: 'sg-muted' }, 'Turn'), h('span', null, item.turnRange.join(' - '))) : null,
|
|
377
641
|
messages.length ? messages.map((message) => h('div', { key: message.id, className: 'sg-raw-message' }, h('div', { className: 'sg-muted' }, String(message.role || '') + ' · ' + formatTime(message.createdAt)), h('div', { className: 'sg-code' }, String(message.content || '')))) : h('div', { className: 'sg-muted' }, '当前数据中没有可显示的来源消息。'))))
|
|
378
642
|
}
|
|
379
643
|
|
|
380
|
-
function EventDetail({ event, project, source,
|
|
644
|
+
function EventDetail({ event, project, source, onBack, backLabel }) {
|
|
381
645
|
return h(React.Fragment, null,
|
|
382
646
|
h(BackBar, { label: backLabel, onBack }),
|
|
383
647
|
h('header', { className: 'sg-detail-header' }, h('h2', { className: 'sg-detail-title' }, event.title || '记忆详情'), event.summary ? h('p', { className: 'sg-detail-subtitle' }, event.summary) : null, h('div', { className: 'sg-meta' }, h('span', null, formatTime(event.updatedAt || event.createdAt)), h('span', { className: 'sg-meta-sep' }, project))),
|
|
384
648
|
event.narrative ? h('div', { className: 'sg-detail-section' }, h('h3', { className: 'sg-section-title' }, 'AI 对这段经历的理解'), h('p', { className: 'sg-prose' }, event.narrative)) : null,
|
|
385
|
-
h('div', { className: 'sg-detail-section' }, h('h3', { className: 'sg-section-title' }, '
|
|
649
|
+
h('div', { className: 'sg-detail-section' }, h('h3', { className: 'sg-section-title' }, '参与实体'), (event.temporal?.participants || []).length ? h('div', { className: 'sg-tags' }, event.temporal.participants.map((name) => h('span', { key: name, className: 'sg-tag' }, name))) : h('span', { className: 'sg-muted' }, '未记录')),
|
|
386
650
|
h(SourceDetails, { item: event, source, kind: 'event' }))
|
|
387
651
|
}
|
|
388
652
|
|
|
@@ -413,7 +677,7 @@ window.__ModuleLoader__.load({
|
|
|
413
677
|
h('div', { className: 'sg-detail-section' }, h('div', { className: 'sg-pipeline' },
|
|
414
678
|
h('div', { className: 'sg-stage' }, h('span', null, '记忆保存'), h('span', { className: 'sg-stage-value done' }, '✓ 已完成')),
|
|
415
679
|
h('div', { className: 'sg-stage' }, h('span', null, '事件提取'), h('span', { className: 'sg-stage-value ' + (first?.kind === 'event-extraction' ? 'failed' : 'done') }, first?.kind === 'event-extraction' ? '× 失败' : '✓ 已完成')),
|
|
416
|
-
h('div', { className: 'sg-stage' }, h('span', null, '
|
|
680
|
+
h('div', { className: 'sg-stage' }, h('span', null, '图谱投影'), h('span', { className: 'sg-stage-value ' + (first?.kind === 'graph-projection' ? 'failed' : first?.kind === 'event-extraction' ? 'waiting' : 'done') }, first?.kind === 'graph-projection' ? '× 失败' : first?.kind === 'event-extraction' ? '— 等待' : '✓ 已完成')))),
|
|
417
681
|
failedBlocks.length ? h('div', { className: 'sg-detail-section' }, h('h3', { className: 'sg-section-title' }, '受影响的短期记忆'), failedBlocks.map((block) => h('div', { key: block.id, className: 'sg-result-event' }, block.title || block.summary || '短期记忆'))) : null,
|
|
418
682
|
h('button', { className: 'sg-alert', onClick: refresh, style: { marginTop: '16px' } }, h('span', { className: 'sg-alert-mark' }, '↻'), h('span', null, h('span', { className: 'sg-alert-title' }, '重新检查状态'), h('br'), h('span', { className: 'sg-alert-copy' }, '任务会沿用现有重试机制继续处理。')), h('span', { className: 'sg-chevron' }, '›')),
|
|
419
683
|
h('details', { className: 'sg-tech' }, h('summary', null, '技术错误详情'), h('pre', { className: 'sg-tech-body sg-code' }, first?.lastErrorFull || first?.lastError || '没有记录技术错误。')))
|
|
@@ -421,10 +685,10 @@ window.__ModuleLoader__.load({
|
|
|
421
685
|
|
|
422
686
|
function MoreHome({ selected, setView }) {
|
|
423
687
|
const rows = [
|
|
424
|
-
['structure', '◇', '记忆结构', '
|
|
688
|
+
['structure', '◇', '记忆结构', '浏览 Event 与知识图谱'],
|
|
425
689
|
['system', '✓', '系统状态', '处理任务和最近整理时间'],
|
|
426
690
|
['audit', '↗', '使用记录', '长期记忆何时被使用'],
|
|
427
|
-
['raw', '{}', '原始数据', 'Block、Event、
|
|
691
|
+
['raw', '{}', '原始数据', 'Block、Event、Graph 与模型响应'],
|
|
428
692
|
['settings', '⚙', '高级设置', 'Schema、提取间隔与项目空间'],
|
|
429
693
|
['support', '?', '反馈与支持', '报告问题、提出建议或咨询使用方法'],
|
|
430
694
|
]
|
|
@@ -456,19 +720,19 @@ window.__ModuleLoader__.load({
|
|
|
456
720
|
'- 当前工作区:' + project,
|
|
457
721
|
'- blockTurnSize:' + (selected?.blockTurnSize ?? 'unknown'),
|
|
458
722
|
'- 已封存块:' + (selected?.blocks ?? 0),
|
|
459
|
-
'- Event /
|
|
723
|
+
'- Event / Graph Node 数量:' + (selected?.events ?? 0) + ' / ' + (selected?.graphNodes ?? 0),
|
|
460
724
|
'- 最近错误:' + (safeErrorSummary(recentError || latestJobError) || '无'),
|
|
461
725
|
'',
|
|
462
|
-
'> 默认诊断不包含原始聊天、L5、Event 或
|
|
726
|
+
'> 默认诊断不包含原始聊天、L5、Event 或 Graph 内容。',
|
|
463
727
|
]
|
|
464
728
|
if (includeLogs) lines.push('', '<details><summary>诊断日志</summary>', '', '```json', limitedJson({ frontendError: recentError || null, failedJobs: selected?.failedJobDetails || [] }), '```', '</details>')
|
|
465
|
-
if (includeMemory) lines.push('', '<details><summary>用户主动附加的记忆数据(可能包含对话内容)</summary>', '', '```json', limitedJson({ blocks: data.blocks, events: data.events,
|
|
729
|
+
if (includeMemory) lines.push('', '<details><summary>用户主动附加的记忆数据(可能包含对话内容)</summary>', '', '```json', limitedJson({ blocks: data.blocks, events: data.events, graph: data.graph }), '```', '</details>')
|
|
466
730
|
const issueParams = new URLSearchParams({ title: '[Bug] StrataGate:', body: lines.join('\n') })
|
|
467
731
|
const featureParams = new URLSearchParams({ title: '[Feature] StrataGate:', body: '请描述希望增加的能力、使用场景和预期行为。' })
|
|
468
732
|
return h(React.Fragment, null,
|
|
469
733
|
h(BackBar, { label: '更多', onBack }),
|
|
470
734
|
h('div', { className: 'sg-intro' }, h('h2', null, '反馈与支持'), h('p', null, '选择最合适的入口,我们会带上必要且安全的上下文。')),
|
|
471
|
-
h('div', { className: 'sg-privacy-note' }, '基础诊断不会附带原始聊天、L5、Event 或
|
|
735
|
+
h('div', { className: 'sg-privacy-note' }, '基础诊断不会附带原始聊天、L5、Event 或 Graph 内容。'),
|
|
472
736
|
h('section', { className: 'sg-support-card' }, h('h3', null, '遇到问题'), h('p', null, '插件会整理版本、配置、数量和最近错误,帮助快速定位。'),
|
|
473
737
|
h('label', { className: 'sg-check' }, h('input', { type: 'checkbox', checked: includeLogs, onChange: (event) => setIncludeLogs(event.target.checked) }), h('span', null, '附加诊断日志')),
|
|
474
738
|
h('label', { className: 'sg-check' }, h('input', { type: 'checkbox', checked: includeMemory, onChange: (event) => setIncludeMemory(event.target.checked) }), h('span', null, '附加记忆数据(可能包含对话内容)')),
|
|
@@ -478,25 +742,25 @@ window.__ModuleLoader__.load({
|
|
|
478
742
|
h('section', { className: 'sg-support-card' }, h('h3', null, '使用疑问'), h('p', null, '在 GitHub Discussion 的 Q&A 区交流使用方法。'), h('a', { className: 'sg-link', href: DISCUSSION_URL, target: '_blank', rel: 'noopener noreferrer' }, '前往 Discussion / Q&A →')))
|
|
479
743
|
}
|
|
480
744
|
|
|
481
|
-
function StructurePage({ events,
|
|
745
|
+
function StructurePage({ events, graph, openEvent, onBack }) {
|
|
482
746
|
return h(React.Fragment, null,
|
|
483
747
|
h(BackBar, { label: '更多', onBack }),
|
|
484
748
|
h('div', { className: 'sg-intro' }, h('h2', null, '记忆结构'), h('p', null, '完整浏览现有结构化记忆')),
|
|
485
749
|
h('div', { className: 'sg-counts' },
|
|
486
750
|
h('div', null, h('div', { className: 'sg-count-value' }, events.length), h('div', { className: 'sg-count-label' }, '经历 / Event')),
|
|
487
|
-
h('div', null, h('div', { className: 'sg-count-value' },
|
|
751
|
+
h('div', null, h('div', { className: 'sg-count-value' }, graph.nodes.length), h('div', { className: 'sg-count-label' }, '知识图谱节点'))),
|
|
488
752
|
h('div', { className: 'sg-structured-group' },
|
|
489
753
|
h('h3', { className: 'sg-section-title' }, '经历'),
|
|
490
754
|
events.map((event) => h('button', { key: event.id, className: 'sg-related', onClick: () => openEvent(event) }, h('span', null, event.title), h('span', { className: 'sg-related-time' }, formatTime(event.updatedAt))))),
|
|
491
755
|
h('div', { className: 'sg-structured-group' },
|
|
492
|
-
h('h3', { className: 'sg-section-title' }, '
|
|
493
|
-
|
|
756
|
+
h('h3', { className: 'sg-section-title' }, '图谱节点'),
|
|
757
|
+
graph.nodes.map((node) => h('div', { key: node.id, className: 'sg-related' }, h('span', null, node.name), h('span', { className: 'sg-related-time' }, (node.sourceEventIds || []).length + ' 条支撑事件')))))
|
|
494
758
|
}
|
|
495
759
|
|
|
496
760
|
function SystemPage({ selected, blocks, onBack, refresh }) {
|
|
497
761
|
const processing = blocks.filter((block) => block.status === 'processing').length
|
|
498
762
|
return h(React.Fragment, null, h(BackBar, { label: '更多', onBack }), h('div', { className: 'sg-intro' }, h('h2', null, '系统状态'), h('p', null, '仅在这里展示记忆处理的工程状态')), h('div', { className: 'sg-pipeline' },
|
|
499
|
-
[['Block 数量', selected.blocks], ['待处理任务', processing], ['失败任务', selected.failedJobs], ['最近整理时间', formatTime(selected.lastActivityAt)], ['Event 提取', selected.failedJobDetails?.some((item) => item.kind === 'event-extraction') ? '有失败' : '正常'], ['
|
|
763
|
+
[['Block 数量', selected.blocks], ['待处理任务', processing], ['失败任务', selected.failedJobs], ['最近整理时间', formatTime(selected.lastActivityAt)], ['Event 提取', selected.failedJobDetails?.some((item) => item.kind === 'event-extraction') ? '有失败' : '正常'], ['图谱投影', selected.failedJobDetails?.some((item) => item.kind === 'graph-projection') ? '有失败' : '正常']].map(([label, value]) => h('div', { key: label, className: 'sg-stage' }, h('span', null, label), h('span', { className: 'sg-stage-value ' + (label === '失败任务' && value ? 'failed' : 'waiting') }, String(value))))), h('button', { className: 'sg-quiet-button', onClick: refresh, style: { marginTop: '14px' } }, '↻ 重新检查'))
|
|
500
764
|
}
|
|
501
765
|
|
|
502
766
|
function AuditPage({ audit, onBack }) {
|
|
@@ -504,8 +768,8 @@ window.__ModuleLoader__.load({
|
|
|
504
768
|
}
|
|
505
769
|
|
|
506
770
|
function RawPage({ data, selected, onBack }) {
|
|
507
|
-
const groups = [['Block raw', data.blocks], ['Event raw', data.events], ['
|
|
508
|
-
return h(React.Fragment, null, h(BackBar, { label: '更多', onBack }), h('div', { className: 'sg-intro' }, h('h2', null, '原始数据'), h('p', null, '供排查问题使用的内部字段与 JSON')), groups.map(([label, value]) => h('details', { key: label, className: 'sg-raw-group' }, h('summary', null, label + ' (' + value.length + ')'), h('pre', { className: 'sg-raw-json sg-code' }, JSON.stringify(value, null, 2)))))
|
|
771
|
+
const groups = [['Block raw', data.blocks], ['Event raw', data.events], ['Graph raw', data.graph], ['Usage raw', data.audit], ['模型响应', selected.successfulModelResponses || []]]
|
|
772
|
+
return h(React.Fragment, null, h(BackBar, { label: '更多', onBack }), h('div', { className: 'sg-intro' }, h('h2', null, '原始数据'), h('p', null, '供排查问题使用的内部字段与 JSON')), groups.map(([label, value]) => h('details', { key: label, className: 'sg-raw-group' }, h('summary', null, label + ' (' + (Array.isArray(value) ? value.length : ((value.nodes?.length || 0) + (value.edges?.length || 0))) + ')'), h('pre', { className: 'sg-raw-json sg-code' }, JSON.stringify(value, null, 2)))))
|
|
509
773
|
}
|
|
510
774
|
|
|
511
775
|
function SettingsPage({ selected, namespace, onBack, updateLambda, savingLambda }) {
|
|
@@ -538,7 +802,7 @@ window.__ModuleLoader__.load({
|
|
|
538
802
|
const [conversationId, setConversationId] = React.useState('')
|
|
539
803
|
const [section, setSection] = React.useState('short')
|
|
540
804
|
const [view, setView] = React.useState({ name: 'root' })
|
|
541
|
-
const [data, setData] = React.useState({ events: [],
|
|
805
|
+
const [data, setData] = React.useState({ events: [], graph: { nodes: [], edges: [], migration: null }, blocks: [], openBlock: null, conversations: [], activeThreadId: null, audit: [] })
|
|
542
806
|
const [query, setQuery] = React.useState('')
|
|
543
807
|
const [source, setSource] = React.useState(null)
|
|
544
808
|
const [loading, setLoading] = React.useState(true)
|
|
@@ -572,18 +836,18 @@ window.__ModuleLoader__.load({
|
|
|
572
836
|
|
|
573
837
|
const loadMemoryData = React.useCallback((activeNamespace, options = {}) => {
|
|
574
838
|
const background = options.background === true
|
|
575
|
-
if (!activeNamespace) { setData({ events: [],
|
|
839
|
+
if (!activeNamespace) { setData({ events: [], graph: { nodes: [], edges: [], migration: null }, blocks: [], openBlock: null, conversations: [], activeThreadId: null, audit: [] }); if (!background) setLoading(false); return Promise.resolve() }
|
|
576
840
|
if (!background) setLoading(true)
|
|
577
841
|
return Promise.allSettled([
|
|
578
842
|
api('memories', { namespace: activeNamespace, kind: 'events', limit: '200' }),
|
|
579
|
-
api('memories', { namespace: activeNamespace, kind: '
|
|
843
|
+
api('memories', { namespace: activeNamespace, kind: 'graph' }),
|
|
580
844
|
api('memories', { namespace: activeNamespace, kind: 'blocks', limit: '200', ...(options.threadId ? { threadId: options.threadId } : {}) }),
|
|
581
845
|
api('audit', { namespace: activeNamespace, limit: '100' }),
|
|
582
846
|
]).then((results) => {
|
|
583
|
-
const [events,
|
|
847
|
+
const [events, graph, blocks, audit] = results
|
|
584
848
|
setData((previous) => ({
|
|
585
849
|
events: events.status === 'fulfilled' ? events.value.items || [] : previous.events,
|
|
586
|
-
|
|
850
|
+
graph: graph.status === 'fulfilled' ? graph.value : previous.graph,
|
|
587
851
|
blocks: blocks.status === 'fulfilled' ? blocks.value.items || [] : previous.blocks,
|
|
588
852
|
openBlock: blocks.status === 'fulfilled' ? blocks.value.openBlock || null : previous.openBlock,
|
|
589
853
|
conversations: blocks.status === 'fulfilled' ? blocks.value.conversations || [] : previous.conversations,
|
|
@@ -599,7 +863,7 @@ window.__ModuleLoader__.load({
|
|
|
599
863
|
|
|
600
864
|
React.useEffect(() => { setLoading(true); void loadOverview().finally(() => setLoading(false)) }, [])
|
|
601
865
|
React.useEffect(() => {
|
|
602
|
-
setConversationId(''); setView({ name: 'root' }); setSource(null); setData({ events: [],
|
|
866
|
+
setConversationId(''); setView({ name: 'root' }); setSource(null); setData({ events: [], graph: { nodes: [], edges: [], migration: null }, blocks: [], openBlock: null, conversations: [], activeThreadId: null, audit: [] }); void loadMemoryData(namespace)
|
|
603
867
|
}, [namespace])
|
|
604
868
|
React.useEffect(() => {
|
|
605
869
|
if (!namespace) return undefined
|
|
@@ -642,14 +906,13 @@ window.__ModuleLoader__.load({
|
|
|
642
906
|
setView((current) => ({ name: kind, item, back: current })); loadSource(kind, item)
|
|
643
907
|
}
|
|
644
908
|
const openEvent = (event) => openWithSource('event', data.events.find((item) => item.id === event.id) || event)
|
|
645
|
-
const openElement = (id) => { const element = data.elements.find((item) => item.id === id); if (element) openWithSource('element', element) }
|
|
646
909
|
const goBack = () => {
|
|
647
910
|
const previous = view.back || { name: 'root' }
|
|
648
911
|
setView(previous)
|
|
649
|
-
if (previous.item && ['event', '
|
|
912
|
+
if (previous.item && ['event', 'block'].includes(previous.name)) loadSource(previous.name, previous.item)
|
|
650
913
|
else setSource(null)
|
|
651
914
|
}
|
|
652
|
-
const backLabel = view.back?.name === 'block' ? '短期记忆' : view.back?.name === '
|
|
915
|
+
const backLabel = view.back?.name === 'block' ? '短期记忆' : view.back?.name === 'event' ? '长期记忆' : view.back?.name === 'structure' ? '记忆结构' : section === 'short' ? '短期记忆' : '长期记忆'
|
|
653
916
|
const refresh = () => Promise.all([loadOverview(), loadMemoryData(namespace, { threadId: conversationId })])
|
|
654
917
|
const selectConversation = (nextConversationId) => {
|
|
655
918
|
setConversationId(nextConversationId)
|
|
@@ -673,10 +936,9 @@ window.__ModuleLoader__.load({
|
|
|
673
936
|
let content = null
|
|
674
937
|
if (loading && !selected) content = h(Loading)
|
|
675
938
|
else if (!selected) content = h(Empty, { title: '还没有记忆', copy: '完成一些 DSH 对话后,短期记忆和长期记忆会出现在这里。' })
|
|
676
|
-
else if (view.name === 'event') content = h(EventDetail, { event: view.item, project, source,
|
|
677
|
-
else if (view.name === 'element') content = h(ElementDetail, { element: view.item, events: data.events, source, openEvent, onBack: goBack, backLabel })
|
|
939
|
+
else if (view.name === 'event') content = h(EventDetail, { event: view.item, project, source, onBack: goBack, backLabel })
|
|
678
940
|
else if (view.name === 'status') content = h(ProcessingStatus, { overview: selected, blocks: data.blocks, onBack: () => setView({ name: 'root' }), refresh })
|
|
679
|
-
else if (view.name === 'structure') content = h(StructurePage, { events: data.events,
|
|
941
|
+
else if (view.name === 'structure') content = h(StructurePage, { events: data.events, graph: data.graph, openEvent, onBack: moreBack })
|
|
680
942
|
else if (view.name === 'system') content = h(SystemPage, { selected, blocks: data.blocks, onBack: moreBack, refresh })
|
|
681
943
|
else if (view.name === 'audit') content = h(AuditPage, { audit: data.audit, onBack: moreBack })
|
|
682
944
|
else if (view.name === 'raw') content = h(RawPage, { data, selected, onBack: moreBack })
|
|
@@ -684,7 +946,7 @@ window.__ModuleLoader__.load({
|
|
|
684
946
|
else if (view.name === 'support') content = h(SupportPage, { overview, selected, project, data, recentError, onBack: moreBack })
|
|
685
947
|
else content = h(React.Fragment, null,
|
|
686
948
|
h(FailureAlert, { count: failedCount, onOpen: () => setView({ name: 'status' }) }),
|
|
687
|
-
loading ? h(Loading) : section === 'short' ? h(ShortTermPage, { key: namespace + ':' + conversationId, blocks: data.blocks, openBlock: data.openBlock, conversations, activeThreadId: conversationId || data.activeThreadId || '', namespace, onConversationChange: selectConversation, refresh }) : section === 'long' ? h(LongTermPage, { events: data.events,
|
|
949
|
+
loading ? h(Loading) : section === 'short' ? h(ShortTermPage, { key: namespace + ':' + conversationId, blocks: data.blocks, openBlock: data.openBlock, conversations, activeThreadId: conversationId || data.activeThreadId || '', namespace, onConversationChange: selectConversation, refresh }) : section === 'long' ? h(LongTermPage, { events: data.events, graph: data.graph, project, query, setQuery, openEvent }) : h(MoreHome, { setView }))
|
|
688
950
|
|
|
689
951
|
return h('main', { className: 'sg-memory', 'data-testid': 'stratagate-memory-ui' },
|
|
690
952
|
h('style', null, css),
|