@copilotz/admin 0.9.39 → 0.9.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -32,8 +32,11 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ADMIN_GROUP_LABELS: () => ADMIN_GROUP_LABELS,
34
34
  ADMIN_GROUP_ORDER: () => ADMIN_GROUP_ORDER,
35
+ BRAIN_RELATION_GROUP_DEFINITIONS: () => BRAIN_RELATION_GROUP_DEFINITIONS,
36
+ BRAIN_VIEW_LABELS: () => BRAIN_VIEW_LABELS,
35
37
  CopilotzAdmin: () => CopilotzAdmin,
36
38
  EMPTY_USAGE_TOTALS: () => EMPTY_USAGE_TOTALS,
39
+ ENTITY_FOCUS_RELATION_TYPES: () => ENTITY_FOCUS_RELATION_TYPES,
37
40
  EmptyState: () => EmptyState,
38
41
  FilterBar: () => FilterBar,
39
42
  InspectorPanel: () => InspectorPanel,
@@ -62,11 +65,16 @@ __export(index_exports, {
62
65
  formatNumber: () => formatNumber,
63
66
  formatPercent: () => formatPercent,
64
67
  formatUsageBucket: () => formatUsageBucket,
68
+ getBrainViewBaseFilters: () => getBrainViewBaseFilters,
69
+ getKnowledgeRelationGroups: () => getKnowledgeRelationGroups,
65
70
  getUsageDimensionLabel: () => getUsageDimensionLabel,
66
71
  getUsageGroupLabel: () => getUsageGroupLabel,
67
72
  getUsageMetricLabel: () => getUsageMetricLabel,
68
73
  getUsageRange: () => getUsageRange,
69
74
  getUsageTotalValue: () => getUsageTotalValue,
75
+ getWorkRelationGroups: () => getWorkRelationGroups,
76
+ groupBrainRelationsByKind: () => groupBrainRelationsByKind,
77
+ isEntityBrainView: () => isEntityBrainView,
70
78
  mergeAdminConfig: () => mergeAdminConfig,
71
79
  overviewModule: () => overviewModule,
72
80
  participantsModule: () => participantsModule,
@@ -313,6 +321,14 @@ function createAdminClient(options = {}) {
313
321
  kind: filters.kind === "all" ? void 0 : filters.kind,
314
322
  status: filters.status === "all" ? void 0 : filters.status,
315
323
  search: filters.search,
324
+ searchMode: filters.searchMode,
325
+ focusNodeId: filters.focusNodeId,
326
+ includeRelated: filters.includeRelated ? "true" : void 0,
327
+ includeSimilar: filters.includeSimilar ? "true" : void 0,
328
+ similarLimit: filters.similarLimit ? String(filters.similarLimit) : void 0,
329
+ minSimilarity: typeof filters.minSimilarity === "number" ? String(filters.minSimilarity) : void 0,
330
+ relationDepth: filters.relationDepth ? String(filters.relationDepth) : void 0,
331
+ relationTypes: filters.relationTypes?.join(","),
316
332
  limit: String(filters.limit ?? 160),
317
333
  offset: filters.offset ? String(filters.offset) : void 0
318
334
  }),
@@ -2021,11 +2037,157 @@ function AgentDetailPage({ context }) {
2021
2037
  // src/modules/brain/index.tsx
2022
2038
  var import_react3 = __toESM(require("react"), 1);
2023
2039
  var import_lucide_react7 = require("lucide-react");
2040
+
2041
+ // src/modules/brain/view-model.ts
2042
+ var ENTITY_FOCUS_RELATION_TYPES = [
2043
+ "mentions",
2044
+ "related_to",
2045
+ "supports",
2046
+ "depends_on",
2047
+ "contradicts",
2048
+ "supersedes"
2049
+ ];
2050
+ var BRAIN_VIEW_LABELS = {
2051
+ entities: "Entities",
2052
+ knowledge: "Knowledge",
2053
+ work: "Work",
2054
+ map: "Map",
2055
+ all: "All nodes"
2056
+ };
2057
+ var BRAIN_RELATION_GROUP_DEFINITIONS = [
2058
+ {
2059
+ id: "decisions",
2060
+ label: "Decisions",
2061
+ description: "Durable choices connected to this entity.",
2062
+ kinds: ["decision"]
2063
+ },
2064
+ {
2065
+ id: "facts",
2066
+ label: "Facts",
2067
+ description: "Known statements and observations.",
2068
+ kinds: ["fact"]
2069
+ },
2070
+ {
2071
+ id: "tasks",
2072
+ label: "Tasks",
2073
+ description: "Actions, next steps, and execution work.",
2074
+ kinds: ["task", "next_action"]
2075
+ },
2076
+ {
2077
+ id: "preferences",
2078
+ label: "Preferences",
2079
+ description: "Remembered user, org, or agent preferences.",
2080
+ kinds: ["preference"]
2081
+ },
2082
+ {
2083
+ id: "constraints",
2084
+ label: "Constraints",
2085
+ description: "Limits, rules, and requirements.",
2086
+ kinds: ["constraint"]
2087
+ },
2088
+ {
2089
+ id: "risks",
2090
+ label: "Risks",
2091
+ description: "Known risks or possible failures.",
2092
+ kinds: ["risk"]
2093
+ },
2094
+ {
2095
+ id: "openQuestions",
2096
+ label: "Open questions",
2097
+ description: "Questions that still need resolution.",
2098
+ kinds: ["open_question"]
2099
+ },
2100
+ {
2101
+ id: "currentState",
2102
+ label: "Current state",
2103
+ description: "Shorter-term context, challenges, and active state.",
2104
+ kinds: ["current_state", "challenge"]
2105
+ },
2106
+ {
2107
+ id: "entities",
2108
+ label: "Other entities",
2109
+ description: "Neighboring people, systems, projects, or concepts.",
2110
+ kinds: ["entity"]
2111
+ },
2112
+ {
2113
+ id: "other",
2114
+ label: "Other",
2115
+ description: "Related nodes that do not fit a standard Brain kind yet.",
2116
+ kinds: []
2117
+ }
2118
+ ];
2119
+ var GROUP_BY_KIND = new Map(
2120
+ BRAIN_RELATION_GROUP_DEFINITIONS.flatMap(
2121
+ (group) => group.kinds.map((kind) => [kind, group.id])
2122
+ )
2123
+ );
2124
+ function getBrainViewBaseFilters(view) {
2125
+ if (view === "entities") {
2126
+ return { kind: "entity", layer: "knowledge" };
2127
+ }
2128
+ if (view === "knowledge") {
2129
+ return { kind: "all", layer: "knowledge" };
2130
+ }
2131
+ if (view === "work") {
2132
+ return { kind: "all", layer: "working" };
2133
+ }
2134
+ return { kind: "all", layer: "all" };
2135
+ }
2136
+ function isEntityBrainView(view) {
2137
+ return view === "entities";
2138
+ }
2139
+ function groupBrainRelationsByKind(related) {
2140
+ const groups = BRAIN_RELATION_GROUP_DEFINITIONS.map((definition) => ({
2141
+ id: definition.id,
2142
+ label: definition.label,
2143
+ description: definition.description,
2144
+ items: []
2145
+ }));
2146
+ const byId = new Map(groups.map((group) => [group.id, group]));
2147
+ for (const item of related) {
2148
+ const groupId = GROUP_BY_KIND.get(item.node.kind) ?? "other";
2149
+ byId.get(groupId)?.items.push(item);
2150
+ }
2151
+ return groups.filter((group) => group.items.length > 0);
2152
+ }
2153
+ function getKnowledgeRelationGroups(related) {
2154
+ const knowledgeGroupIds = [
2155
+ "decisions",
2156
+ "facts",
2157
+ "preferences",
2158
+ "constraints"
2159
+ ];
2160
+ return groupBrainRelationsByKind(related).filter(
2161
+ (group) => knowledgeGroupIds.includes(group.id)
2162
+ );
2163
+ }
2164
+ function getWorkRelationGroups(related) {
2165
+ const workGroupIds = [
2166
+ "tasks",
2167
+ "risks",
2168
+ "openQuestions",
2169
+ "currentState"
2170
+ ];
2171
+ return groupBrainRelationsByKind(related).filter(
2172
+ (group) => workGroupIds.includes(group.id)
2173
+ );
2174
+ }
2175
+
2176
+ // src/modules/brain/index.tsx
2024
2177
  var import_jsx_runtime21 = require("react/jsx-runtime");
2025
2178
  var BRAIN_LAYERS = ["all", "knowledge", "working"];
2179
+ var BRAIN_SEARCH_MODES = ["hybrid", "semantic", "keyword"];
2026
2180
  var BRAIN_STATUSES = ["active", "all", "superseded", "archived"];
2181
+ var BRAIN_VIEWS = [
2182
+ "entities",
2183
+ "knowledge",
2184
+ "work",
2185
+ "map",
2186
+ "all"
2187
+ ];
2027
2188
  var BRAIN_KINDS = [
2028
2189
  "all",
2190
+ "entity",
2029
2191
  "decision",
2030
2192
  "fact",
2031
2193
  "preference",
@@ -2059,7 +2221,11 @@ function brainModule() {
2059
2221
  };
2060
2222
  }
2061
2223
  function BrainPage({ context }) {
2224
+ const [view, setView] = import_react3.default.useState("entities");
2062
2225
  const [search, setSearch] = import_react3.default.useState("");
2226
+ const [searchMode, setSearchMode] = import_react3.default.useState(
2227
+ "hybrid"
2228
+ );
2063
2229
  const [layer, setLayer] = import_react3.default.useState("all");
2064
2230
  const [status, setStatus] = import_react3.default.useState(
2065
2231
  "active"
@@ -2072,25 +2238,44 @@ function BrainPage({ context }) {
2072
2238
  const [selectedNode, setSelectedNode] = import_react3.default.useState(
2073
2239
  null
2074
2240
  );
2241
+ const [focusNodeId, setFocusNodeId] = import_react3.default.useState(null);
2075
2242
  const [loading, setLoading] = import_react3.default.useState(false);
2076
2243
  const [error, setError] = import_react3.default.useState(null);
2077
- const loadBrain = import_react3.default.useCallback(async () => {
2244
+ const loadBrain = import_react3.default.useCallback(async (focusOverride) => {
2078
2245
  setLoading(true);
2079
2246
  setError(null);
2247
+ const effectiveFocusNodeId = focusOverride === void 0 ? focusNodeId : focusOverride;
2248
+ const viewFilters = getBrainViewBaseFilters(view);
2249
+ const effectiveLayer = view === "map" || view === "all" ? layer : viewFilters.layer;
2250
+ const effectiveKind = view === "entities" ? "entity" : kind;
2080
2251
  try {
2081
2252
  const next = await context.client.getBrain({
2082
2253
  namespace: context.scope.namespace || void 0,
2083
2254
  search: search.trim() || void 0,
2084
- layer,
2255
+ searchMode,
2256
+ layer: effectiveLayer,
2085
2257
  status,
2086
- kind,
2258
+ kind: effectiveKind,
2087
2259
  agentId: agentId.trim() || void 0,
2260
+ focusNodeId: effectiveFocusNodeId || void 0,
2261
+ includeRelated: Boolean(effectiveFocusNodeId),
2262
+ includeSimilar: Boolean(effectiveFocusNodeId),
2263
+ similarLimit: 24,
2264
+ minSimilarity: 0.2,
2265
+ relationTypes: effectiveFocusNodeId ? [...ENTITY_FOCUS_RELATION_TYPES] : void 0,
2088
2266
  limit: 180
2089
2267
  });
2090
2268
  setResponse(next);
2091
- setSelectedNode(
2092
- (current) => current && next.nodes.some((node) => node.id === current.id) ? current : next.nodes[0] ?? null
2093
- );
2269
+ setSelectedNode((current) => {
2270
+ const currentInResults = current ? next.nodes.find((node) => node.id === current.id) ?? current : null;
2271
+ if (effectiveFocusNodeId) {
2272
+ return next.nodes.find((node) => node.id === effectiveFocusNodeId) ?? currentInResults;
2273
+ }
2274
+ if (view === "entities") {
2275
+ return current && next.nodes.some((node) => node.id === current.id) ? currentInResults : null;
2276
+ }
2277
+ return current && next.nodes.some((node) => node.id === current.id) ? currentInResults : next.nodes[0] ?? null;
2278
+ });
2094
2279
  } catch (cause) {
2095
2280
  setError(cause instanceof Error ? cause.message : "Failed to load brain");
2096
2281
  setResponse(null);
@@ -2102,17 +2287,38 @@ function BrainPage({ context }) {
2102
2287
  agentId,
2103
2288
  context.client,
2104
2289
  context.scope.namespace,
2290
+ focusNodeId,
2105
2291
  kind,
2106
2292
  layer,
2107
2293
  search,
2108
- status
2294
+ searchMode,
2295
+ status,
2296
+ view
2109
2297
  ]);
2110
2298
  import_react3.default.useEffect(() => {
2111
2299
  void loadBrain();
2112
- }, [context.refreshKey, context.scope.namespace, loadBrain]);
2300
+ }, [context.refreshKey, context.scope.namespace, focusNodeId, loadBrain]);
2113
2301
  const data = response ?? emptyBrainResponse();
2302
+ const matches = data.matches ?? {};
2303
+ const related = data.related ?? [];
2304
+ const similar = data.similar ?? [];
2305
+ const entityCount = data.stats.byKind.entity ?? data.nodes.filter((node) => node.kind === "entity").length;
2114
2306
  const knowledgeCount = data.stats.byLayer.knowledge ?? 0;
2115
2307
  const workingCount = data.stats.byLayer.working ?? 0;
2308
+ const selectNode = import_react3.default.useCallback((node) => {
2309
+ setSelectedNode(node);
2310
+ setFocusNodeId(node.id);
2311
+ }, []);
2312
+ const selectView = import_react3.default.useCallback((nextView) => {
2313
+ setView(nextView);
2314
+ setFocusNodeId(null);
2315
+ setSelectedNode(null);
2316
+ }, []);
2317
+ const runSearch = import_react3.default.useCallback(() => {
2318
+ setFocusNodeId(null);
2319
+ setSelectedNode(null);
2320
+ void loadBrain(null);
2321
+ }, [loadBrain]);
2116
2322
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "space-y-4", children: [
2117
2323
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2118
2324
  PageHeader,
@@ -2146,16 +2352,16 @@ function BrainPage({ context }) {
2146
2352
  {
2147
2353
  items: [
2148
2354
  {
2149
- label: "Brain nodes",
2150
- value: data.stats.total,
2151
- detail: `${data.pageInfo.returned} shown`,
2152
- icon: import_lucide_react7.Brain
2355
+ label: "Entities",
2356
+ value: entityCount,
2357
+ detail: view === "entities" ? `${data.pageInfo.returned} shown` : "Stable anchors",
2358
+ icon: import_lucide_react7.UsersRound
2153
2359
  },
2154
2360
  {
2155
2361
  label: "Knowledge",
2156
2362
  value: knowledgeCount,
2157
2363
  detail: "Durable nodes",
2158
- icon: import_lucide_react7.CircleDot
2364
+ icon: import_lucide_react7.BookOpen
2159
2365
  },
2160
2366
  {
2161
2367
  label: "Working",
@@ -2164,14 +2370,27 @@ function BrainPage({ context }) {
2164
2370
  icon: import_lucide_react7.Sparkles
2165
2371
  },
2166
2372
  {
2167
- label: "Clusters",
2168
- value: data.clusters.length,
2169
- detail: "Layer and kind",
2170
- icon: import_lucide_react7.Network
2373
+ label: selectedNode ? "Focus links" : "Brain nodes",
2374
+ value: selectedNode ? related.length + similar.length : data.stats.total,
2375
+ detail: selectedNode ? "Related and similar" : `${data.pageInfo.returned} shown`,
2376
+ icon: selectedNode ? import_lucide_react7.GitBranch : import_lucide_react7.Brain
2171
2377
  }
2172
2378
  ]
2173
2379
  }
2174
2380
  ),
2381
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "inline-flex max-w-full overflow-hidden rounded-md border bg-background", children: BRAIN_VIEWS.map((item) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2382
+ "button",
2383
+ {
2384
+ className: cn(
2385
+ "min-w-0 px-3 py-2 text-xs transition-colors sm:px-4",
2386
+ view === item ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"
2387
+ ),
2388
+ onClick: () => selectView(item),
2389
+ type: "button",
2390
+ children: BRAIN_VIEW_LABELS[item]
2391
+ },
2392
+ item
2393
+ )) }),
2175
2394
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2176
2395
  FilterBar,
2177
2396
  {
@@ -2179,7 +2398,7 @@ function BrainPage({ context }) {
2179
2398
  Button,
2180
2399
  {
2181
2400
  disabled: loading,
2182
- onClick: () => void loadBrain(),
2401
+ onClick: runSearch,
2183
2402
  size: "sm",
2184
2403
  type: "button",
2185
2404
  children: [
@@ -2189,20 +2408,40 @@ function BrainPage({ context }) {
2189
2408
  }
2190
2409
  ),
2191
2410
  onSearchChange: setSearch,
2192
- searchPlaceholder: "Search brain",
2411
+ searchPlaceholder: view === "entities" ? "Search entities" : "Search brain",
2193
2412
  searchValue: search,
2194
2413
  children: [
2195
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2414
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "inline-flex h-8 overflow-hidden rounded-md border bg-background", children: BRAIN_SEARCH_MODES.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2415
+ "button",
2416
+ {
2417
+ className: cn(
2418
+ "px-3 text-xs transition-colors",
2419
+ searchMode === mode ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"
2420
+ ),
2421
+ onClick: () => setSearchMode(mode),
2422
+ type: "button",
2423
+ children: formatLabel(mode)
2424
+ },
2425
+ mode
2426
+ )) }),
2427
+ view === "map" || view === "all" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2196
2428
  Select,
2197
2429
  {
2198
2430
  value: layer,
2199
2431
  onValueChange: (value) => setLayer(value),
2200
2432
  children: [
2201
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SelectTrigger, { className: "h-8 w-[140px] text-xs", "aria-label": "Layer", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SelectValue, {}) }),
2433
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2434
+ SelectTrigger,
2435
+ {
2436
+ className: "h-8 w-[140px] text-xs",
2437
+ "aria-label": "Layer",
2438
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SelectValue, {})
2439
+ }
2440
+ ),
2202
2441
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SelectContent, { children: BRAIN_LAYERS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SelectItem, { value: option, children: option === "all" ? "All layers" : formatLabel(option) }, option)) })
2203
2442
  ]
2204
2443
  }
2205
- ),
2444
+ ) : null,
2206
2445
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2207
2446
  Select,
2208
2447
  {
@@ -2214,24 +2453,31 @@ function BrainPage({ context }) {
2214
2453
  ]
2215
2454
  }
2216
2455
  ),
2217
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2456
+ view !== "entities" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2218
2457
  Select,
2219
2458
  {
2220
2459
  value: kind,
2221
2460
  onValueChange: (value) => setKind(value),
2222
2461
  children: [
2223
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SelectTrigger, { className: "h-8 w-[170px] text-xs", "aria-label": "Kind", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SelectValue, {}) }),
2462
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2463
+ SelectTrigger,
2464
+ {
2465
+ className: "h-8 w-[170px] text-xs",
2466
+ "aria-label": "Kind",
2467
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SelectValue, {})
2468
+ }
2469
+ ),
2224
2470
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SelectContent, { children: BRAIN_KINDS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SelectItem, { value: option, children: option === "all" ? "All kinds" : formatLabel(option) }, option)) })
2225
2471
  ]
2226
2472
  }
2227
- ),
2473
+ ) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { className: "h-8 px-3", variant: "secondary", children: "Entity index" }),
2228
2474
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2229
2475
  Input,
2230
2476
  {
2231
2477
  className: "h-8 w-[190px]",
2232
2478
  onChange: (event) => setAgentId(event.target.value),
2233
2479
  onKeyDown: (event) => {
2234
- if (event.key === "Enter") void loadBrain();
2480
+ if (event.key === "Enter") runSearch();
2235
2481
  },
2236
2482
  placeholder: "Agent ID",
2237
2483
  value: agentId
@@ -2240,10 +2486,23 @@ function BrainPage({ context }) {
2240
2486
  ]
2241
2487
  }
2242
2488
  ),
2489
+ data.semantic?.requested && data.semantic.error ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: [
2490
+ "Semantic search unavailable: ",
2491
+ data.semantic.error
2492
+ ] }) : null,
2243
2493
  error ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EmptyState, { title: "Unable to load brain", description: error }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2244
2494
  InspectorPanel,
2245
2495
  {
2246
- side: selectedNode ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(BrainNodeInspector, { node: selectedNode }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2496
+ side: selectedNode ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2497
+ BrainNodeInspector,
2498
+ {
2499
+ match: matches[selectedNode.id],
2500
+ node: selectedNode,
2501
+ onSelectNode: selectNode,
2502
+ related,
2503
+ similar
2504
+ }
2505
+ ) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2247
2506
  EmptyState,
2248
2507
  {
2249
2508
  icon: import_lucide_react7.Brain,
@@ -2251,70 +2510,299 @@ function BrainPage({ context }) {
2251
2510
  description: "Select a node from the map or table."
2252
2511
  }
2253
2512
  ),
2254
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "space-y-4", children: [
2513
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "space-y-4", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2514
+ BrainPrimaryContent,
2515
+ {
2516
+ data,
2517
+ loading,
2518
+ matches,
2519
+ onSelectNode: selectNode,
2520
+ related,
2521
+ selectedNode,
2522
+ similar,
2523
+ view
2524
+ }
2525
+ ) })
2526
+ }
2527
+ )
2528
+ ] });
2529
+ }
2530
+ function BrainPrimaryContent({
2531
+ data,
2532
+ loading,
2533
+ matches,
2534
+ onSelectNode,
2535
+ related,
2536
+ selectedNode,
2537
+ similar,
2538
+ view
2539
+ }) {
2540
+ if (view === "entities") {
2541
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "space-y-4", children: [
2542
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2543
+ EntityEgoGraph,
2544
+ {
2545
+ entity: selectedNode?.kind === "entity" ? selectedNode : null,
2546
+ loading,
2547
+ onSelectNode,
2548
+ related,
2549
+ similar
2550
+ }
2551
+ ),
2552
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2553
+ BrainNodeTable,
2554
+ {
2555
+ emptyDescription: "Entity nodes will appear here once the Brain has named people, tenants, projects, products, tools, policies, or concepts.",
2556
+ emptyTitle: loading ? "Loading entities" : "No entities yet",
2557
+ loading,
2558
+ matches,
2559
+ nodes: data.nodes,
2560
+ onSelectNode,
2561
+ variant: "entities"
2562
+ }
2563
+ )
2564
+ ] });
2565
+ }
2566
+ if (view === "map") {
2567
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "space-y-4", children: [
2568
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2569
+ BrainMap,
2570
+ {
2571
+ clusters: data.clusters,
2572
+ edges: data.edges,
2573
+ loading,
2574
+ nodes: data.nodes,
2575
+ onSelectNode,
2576
+ similar,
2577
+ selectedNodeId: selectedNode?.id ?? null
2578
+ }
2579
+ ),
2580
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2581
+ BrainNodeTable,
2582
+ {
2583
+ emptyDescription: "Knowledge and working-state nodes will appear here.",
2584
+ emptyTitle: loading ? "Loading brain" : "No brain nodes",
2585
+ loading,
2586
+ matches,
2587
+ nodes: data.nodes,
2588
+ onSelectNode
2589
+ }
2590
+ )
2591
+ ] });
2592
+ }
2593
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2594
+ BrainNodeTable,
2595
+ {
2596
+ emptyDescription: view === "work" ? "Working-state nodes will appear here when the Brain captures current challenges, tasks, risks, and open questions." : view === "knowledge" ? "Durable facts, decisions, preferences, constraints, and entities will appear here." : "Knowledge and working-state nodes will appear here.",
2597
+ emptyTitle: loading ? `Loading ${BRAIN_VIEW_LABELS[view].toLowerCase()}` : `No ${BRAIN_VIEW_LABELS[view].toLowerCase()}`,
2598
+ loading,
2599
+ matches,
2600
+ nodes: data.nodes,
2601
+ onSelectNode
2602
+ }
2603
+ );
2604
+ }
2605
+ function BrainNodeTable({
2606
+ emptyDescription,
2607
+ emptyTitle,
2608
+ loading,
2609
+ matches,
2610
+ nodes,
2611
+ onSelectNode,
2612
+ variant = "default"
2613
+ }) {
2614
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2615
+ ResourceTable,
2616
+ {
2617
+ rows: nodes,
2618
+ getRowKey: (row) => row.id,
2619
+ onRowClick: onSelectNode,
2620
+ empty: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2621
+ EmptyState,
2622
+ {
2623
+ icon: variant === "entities" ? import_lucide_react7.UsersRound : import_lucide_react7.Brain,
2624
+ title: emptyTitle,
2625
+ description: loading ? "Fetching Brain nodes for the active namespace." : emptyDescription
2626
+ }
2627
+ ),
2628
+ columns: [
2629
+ {
2630
+ id: "node",
2631
+ header: variant === "entities" ? "Entity" : "Node",
2632
+ className: "max-w-[420px]",
2633
+ render: (row) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "min-w-0", children: [
2634
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "truncate font-medium", children: row.name }),
2635
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "truncate text-xs text-muted-foreground", children: row.content || "-" }),
2636
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(MatchBadges, { match: matches[row.id] })
2637
+ ] })
2638
+ },
2639
+ ...variant === "entities" ? [{
2640
+ id: "updated",
2641
+ header: "Updated",
2642
+ className: "whitespace-nowrap text-xs",
2643
+ render: (row) => formatDateTime(row.updatedAt)
2644
+ }] : [{
2645
+ id: "layer",
2646
+ header: "Layer",
2647
+ render: (row) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LayerBadge, { layer: row.layer })
2648
+ }, {
2649
+ id: "kind",
2650
+ header: "Kind",
2651
+ render: (row) => formatLabel(row.kind)
2652
+ }],
2653
+ {
2654
+ id: "status",
2655
+ header: "Status",
2656
+ render: (row) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StatusBadge, { status: row.status })
2657
+ },
2658
+ {
2659
+ id: "agent",
2660
+ header: "Agent",
2661
+ className: "max-w-[160px] truncate font-mono text-xs",
2662
+ render: (row) => row.agentId ?? "-"
2663
+ }
2664
+ ]
2665
+ }
2666
+ );
2667
+ }
2668
+ function EntityEgoGraph({
2669
+ entity,
2670
+ loading,
2671
+ onSelectNode,
2672
+ related,
2673
+ similar
2674
+ }) {
2675
+ if (!entity) {
2676
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex min-h-[300px] items-center justify-center rounded-lg border bg-background", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2677
+ EmptyState,
2678
+ {
2679
+ icon: import_lucide_react7.UsersRound,
2680
+ title: loading ? "Loading entity workspace" : "Select an entity",
2681
+ description: loading ? "Fetching entity nodes for the active namespace." : "Choose an entity to see its explicit relations and semantic neighbors."
2682
+ }
2683
+ ) });
2684
+ }
2685
+ const relatedItems = related.slice(0, 18);
2686
+ const similarItems = similar.slice(0, 10);
2687
+ const orbitCount = relatedItems.length + similarItems.length;
2688
+ const center = { x: 500, y: 260 };
2689
+ const radius = orbitCount > 12 ? 190 : 165;
2690
+ const orbitNodes = [
2691
+ ...relatedItems.map((item, index) => ({
2692
+ id: item.edge.id,
2693
+ index,
2694
+ node: item.node,
2695
+ tone: "relation",
2696
+ label: formatLabel(item.edge.type)
2697
+ })),
2698
+ ...similarItems.map((item, index) => ({
2699
+ id: `similar-${item.node.id}`,
2700
+ index: index + relatedItems.length,
2701
+ node: item.node,
2702
+ tone: "similar",
2703
+ label: formatSimilarityScore(item.similarity)
2704
+ }))
2705
+ ].map((item) => {
2706
+ const angle = orbitCount > 0 ? Math.PI * 2 * item.index / orbitCount - Math.PI / 2 : 0;
2707
+ return {
2708
+ ...item,
2709
+ x: center.x + Math.cos(angle) * radius,
2710
+ y: center.y + Math.sin(angle) * radius
2711
+ };
2712
+ });
2713
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "overflow-hidden rounded-lg border bg-background", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "grid gap-0 lg:grid-cols-[minmax(0,1.5fr)_320px]", children: [
2714
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2715
+ "svg",
2716
+ {
2717
+ className: "block h-[380px] w-full bg-muted/20",
2718
+ role: "img",
2719
+ viewBox: "0 0 1000 520",
2720
+ children: [
2721
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("rect", { fill: "transparent", height: "520", width: "1000" }),
2722
+ orbitNodes.map((item) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2723
+ "line",
2724
+ {
2725
+ stroke: item.tone === "similar" ? "#0ea5e9" : "#64748b",
2726
+ strokeDasharray: item.tone === "similar" ? "6 7" : void 0,
2727
+ strokeOpacity: item.tone === "similar" ? "0.48" : "0.38",
2728
+ strokeWidth: item.tone === "similar" ? "1.8" : "2.2",
2729
+ x1: center.x,
2730
+ x2: item.x,
2731
+ y1: center.y,
2732
+ y2: item.y
2733
+ },
2734
+ `line-${item.id}`
2735
+ )),
2255
2736
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2256
- BrainMap,
2737
+ "circle",
2257
2738
  {
2258
- clusters: data.clusters,
2259
- edges: data.edges,
2260
- loading,
2261
- nodes: data.nodes,
2262
- onSelectNode: setSelectedNode,
2263
- selectedNodeId: selectedNode?.id ?? null
2739
+ cx: center.x,
2740
+ cy: center.y,
2741
+ fill: nodeColor(entity),
2742
+ r: "22",
2743
+ stroke: "#0f172a",
2744
+ strokeWidth: "3",
2745
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("title", { children: entity.name })
2264
2746
  }
2265
2747
  ),
2266
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2267
- ResourceTable,
2748
+ orbitNodes.map((item) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2749
+ "g",
2268
2750
  {
2269
- rows: data.nodes,
2270
- getRowKey: (row) => row.id,
2271
- onRowClick: setSelectedNode,
2272
- empty: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2273
- EmptyState,
2274
- {
2275
- icon: import_lucide_react7.Brain,
2276
- title: loading ? "Loading brain" : "No brain nodes",
2277
- description: loading ? "Fetching brain nodes for the active namespace." : "Knowledge and working-state nodes will appear here."
2278
- }
2279
- ),
2280
- columns: [
2281
- {
2282
- id: "node",
2283
- header: "Node",
2284
- className: "max-w-[360px]",
2285
- render: (row) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "min-w-0", children: [
2286
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "truncate font-medium", children: row.name }),
2287
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "truncate text-xs text-muted-foreground", children: row.content || "-" })
2288
- ] })
2289
- },
2290
- {
2291
- id: "layer",
2292
- header: "Layer",
2293
- render: (row) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LayerBadge, { layer: row.layer })
2294
- },
2295
- {
2296
- id: "kind",
2297
- header: "Kind",
2298
- render: (row) => formatLabel(row.kind)
2299
- },
2300
- {
2301
- id: "status",
2302
- header: "Status",
2303
- render: (row) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StatusBadge, { status: row.status })
2304
- },
2751
+ className: "cursor-pointer",
2752
+ onClick: () => onSelectNode(item.node),
2753
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2754
+ "circle",
2305
2755
  {
2306
- id: "agent",
2307
- header: "Agent",
2308
- className: "max-w-[160px] truncate font-mono text-xs",
2309
- render: (row) => row.agentId ?? "-"
2756
+ cx: item.x,
2757
+ cy: item.y,
2758
+ fill: nodeColor(item.node),
2759
+ r: item.tone === "similar" ? 9 : 11,
2760
+ stroke: item.tone === "similar" ? "#0ea5e9" : "#ffffff",
2761
+ strokeWidth: "2",
2762
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("title", { children: `${item.node.name} \xB7 ${formatLabel(item.node.kind)} \xB7 ${item.label}` })
2310
2763
  }
2311
- ]
2312
- }
2313
- )
2314
- ] })
2764
+ )
2765
+ },
2766
+ item.id
2767
+ ))
2768
+ ]
2315
2769
  }
2316
- )
2317
- ] });
2770
+ ),
2771
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "border-t p-4 lg:border-l lg:border-t-0", children: [
2772
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
2773
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LayerBadge, { layer: entity.layer }),
2774
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(StatusBadge, { status: entity.status }),
2775
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: "outline", children: "Entity" })
2776
+ ] }),
2777
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h3", { className: "mt-3 truncate text-base font-semibold", children: entity.name }),
2778
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "mt-2 line-clamp-4 text-sm leading-6 text-muted-foreground", children: entity.content || "-" }),
2779
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "mt-4 grid grid-cols-2 gap-2 text-xs", children: [
2780
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "rounded-md border bg-muted/20 p-3", children: [
2781
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "text-muted-foreground", children: "Explicit" }),
2782
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "mt-1 text-lg font-semibold", children: related.length })
2783
+ ] }),
2784
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "rounded-md border bg-muted/20 p-3", children: [
2785
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "text-muted-foreground", children: "Semantic" }),
2786
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "mt-1 text-lg font-semibold", children: similar.length })
2787
+ ] })
2788
+ ] }),
2789
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "mt-4 space-y-2", children: [
2790
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "text-xs font-medium text-muted-foreground", children: "Knowledge orbit" }),
2791
+ groupBrainRelationsByKind(related).slice(0, 5).map((group) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2792
+ "div",
2793
+ {
2794
+ className: "flex items-center justify-between gap-3 text-xs",
2795
+ children: [
2796
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "truncate", children: group.label }),
2797
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: "secondary", children: group.items.length })
2798
+ ]
2799
+ },
2800
+ group.id
2801
+ )),
2802
+ related.length === 0 && similar.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-xs leading-5 text-muted-foreground", children: "Select an entity with relations or embeddings to see its neighborhood." }) : null
2803
+ ] })
2804
+ ] })
2805
+ ] }) });
2318
2806
  }
2319
2807
  function BrainMap({
2320
2808
  clusters,
@@ -2322,6 +2810,7 @@ function BrainMap({
2322
2810
  loading,
2323
2811
  nodes,
2324
2812
  onSelectNode,
2813
+ similar,
2325
2814
  selectedNodeId
2326
2815
  }) {
2327
2816
  const nodeById = import_react3.default.useMemo(
@@ -2374,12 +2863,15 @@ function BrainMap({
2374
2863
  const source = nodeById.get(edge.sourceNodeId);
2375
2864
  const target = nodeById.get(edge.targetNodeId);
2376
2865
  if (!source || !target) return null;
2866
+ const isSelectedEdge = Boolean(
2867
+ selectedNodeId && (edge.sourceNodeId === selectedNodeId || edge.targetNodeId === selectedNodeId)
2868
+ );
2377
2869
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2378
2870
  "line",
2379
2871
  {
2380
2872
  stroke: edge.type === "contradicts" ? "#dc2626" : "#64748b",
2381
- strokeOpacity: "0.32",
2382
- strokeWidth: edge.type === "supports" ? 2 : 1.3,
2873
+ strokeOpacity: isSelectedEdge ? "0.75" : "0.32",
2874
+ strokeWidth: isSelectedEdge ? 2.6 : edge.type === "supports" ? 2 : 1.3,
2383
2875
  x1: source.x * 1e3,
2384
2876
  x2: target.x * 1e3,
2385
2877
  y1: source.y * 600,
@@ -2388,6 +2880,25 @@ function BrainMap({
2388
2880
  edge.id
2389
2881
  );
2390
2882
  }),
2883
+ selectedNodeId ? similar.map((item) => {
2884
+ const source = nodeById.get(selectedNodeId);
2885
+ const target = nodeById.get(item.node.id);
2886
+ if (!source || !target) return null;
2887
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2888
+ "line",
2889
+ {
2890
+ stroke: "#0ea5e9",
2891
+ strokeDasharray: "5 6",
2892
+ strokeOpacity: "0.42",
2893
+ strokeWidth: "1.8",
2894
+ x1: source.x * 1e3,
2895
+ x2: target.x * 1e3,
2896
+ y1: source.y * 600,
2897
+ y2: target.y * 600
2898
+ },
2899
+ `similar-${item.node.id}`
2900
+ );
2901
+ }) : null,
2391
2902
  nodes.map((node) => {
2392
2903
  const isSelected = node.id === selectedNodeId;
2393
2904
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
@@ -2415,7 +2926,19 @@ function BrainMap({
2415
2926
  }
2416
2927
  ) });
2417
2928
  }
2418
- function BrainNodeInspector({ node }) {
2929
+ function BrainNodeInspector({
2930
+ match,
2931
+ node,
2932
+ onSelectNode,
2933
+ related,
2934
+ similar
2935
+ }) {
2936
+ const [tab, setTab] = import_react3.default.useState("overview");
2937
+ const isEntity = node.kind === "entity";
2938
+ const tabs = isEntity ? ["overview", "knowledge", "work", "relations", "similar", "evidence", "json"] : ["overview", "relations", "similar", "evidence", "json"];
2939
+ import_react3.default.useEffect(() => {
2940
+ setTab("overview");
2941
+ }, [node.id]);
2419
2942
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "space-y-3", children: [
2420
2943
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "rounded-lg border bg-background p-4", children: [
2421
2944
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
@@ -2425,24 +2948,161 @@ function BrainNodeInspector({ node }) {
2425
2948
  ] }),
2426
2949
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h3", { className: "mt-3 text-base font-semibold", children: node.name }),
2427
2950
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "mt-2 text-sm leading-6 text-muted-foreground", children: node.content || "-" }),
2428
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("dl", { className: "mt-4 grid gap-2 text-xs", children: [
2429
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Agent", value: node.agentId }),
2430
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Thread", value: node.threadId }),
2431
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Memory space", value: node.memorySpaceId }),
2432
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Checkpoint", value: node.checkpointId }),
2433
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Source field", value: node.sourceField }),
2434
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2435
- InspectorRow,
2436
- {
2437
- label: "Updated",
2438
- value: formatDateTime(node.updatedAt)
2439
- }
2440
- )
2441
- ] })
2951
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(MatchBadges, { match })
2442
2952
  ] }),
2443
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(JsonPanel, { title: "Node JSON", value: node, minHeight: 300 })
2953
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "inline-flex w-full overflow-hidden rounded-md border bg-background", children: tabs.map((item) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2954
+ "button",
2955
+ {
2956
+ className: cn(
2957
+ "min-w-0 flex-1 px-2 py-2 text-xs transition-colors",
2958
+ tab === item ? "bg-muted font-medium text-foreground" : "text-muted-foreground hover:bg-muted/60"
2959
+ ),
2960
+ onClick: () => setTab(item),
2961
+ type: "button",
2962
+ children: formatLabel(item)
2963
+ },
2964
+ item
2965
+ )) }),
2966
+ tab === "overview" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(BrainOverview, { node }) : null,
2967
+ tab === "knowledge" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2968
+ RelationGroupsPanel,
2969
+ {
2970
+ emptyDescription: "Durable decisions, facts, preferences, and constraints related to this entity will appear here.",
2971
+ emptyTitle: "No durable knowledge",
2972
+ groups: getKnowledgeRelationGroups(related),
2973
+ onSelectNode
2974
+ }
2975
+ ) : null,
2976
+ tab === "work" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2977
+ RelationGroupsPanel,
2978
+ {
2979
+ emptyDescription: "Tasks, risks, open questions, and current-state nodes related to this entity will appear here.",
2980
+ emptyTitle: "No current work",
2981
+ groups: getWorkRelationGroups(related),
2982
+ onSelectNode
2983
+ }
2984
+ ) : null,
2985
+ tab === "relations" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2986
+ RelationGroupsPanel,
2987
+ {
2988
+ emptyDescription: "Explicit relation edges will appear here when this node is connected to other concepts.",
2989
+ emptyTitle: "No explicit relations",
2990
+ groups: groupBrainRelationsByKind(related),
2991
+ onSelectNode
2992
+ }
2993
+ ) : null,
2994
+ tab === "similar" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SimilarPanel, { onSelectNode, similar }) : null,
2995
+ tab === "evidence" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EvidencePanel, { node }) : null,
2996
+ tab === "json" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(JsonPanel, { title: "Node JSON", value: node, minHeight: 300 }) : null
2444
2997
  ] });
2445
2998
  }
2999
+ function BrainOverview({ node }) {
3000
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "rounded-lg border bg-background p-4", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("dl", { className: "grid gap-2 text-xs", children: [
3001
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Agent", value: node.agentId }),
3002
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Thread", value: node.threadId }),
3003
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Memory space", value: node.memorySpaceId }),
3004
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Checkpoint", value: node.checkpointId }),
3005
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Source field", value: node.sourceField }),
3006
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Confidence", value: formatNullableNumber(node.confidence) }),
3007
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Updated", value: formatDateTime(node.updatedAt) })
3008
+ ] }) });
3009
+ }
3010
+ function RelationGroupsPanel({
3011
+ emptyDescription,
3012
+ emptyTitle,
3013
+ groups,
3014
+ onSelectNode
3015
+ }) {
3016
+ if (groups.length === 0) {
3017
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3018
+ EmptyState,
3019
+ {
3020
+ icon: import_lucide_react7.GitBranch,
3021
+ title: emptyTitle,
3022
+ description: emptyDescription
3023
+ }
3024
+ );
3025
+ }
3026
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "space-y-3", children: groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("section", { className: "rounded-lg border bg-background", children: [
3027
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "border-b px-3 py-2", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [
3028
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "min-w-0", children: [
3029
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h4", { className: "truncate text-sm font-medium", children: group.label }),
3030
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "mt-0.5 line-clamp-2 text-xs text-muted-foreground", children: group.description })
3031
+ ] }),
3032
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: "secondary", children: group.items.length })
3033
+ ] }) }),
3034
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "divide-y", children: group.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
3035
+ "button",
3036
+ {
3037
+ className: "w-full p-3 text-left transition-colors hover:bg-muted/40",
3038
+ onClick: () => onSelectNode(item.node),
3039
+ type: "button",
3040
+ children: [
3041
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
3042
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: "outline", children: formatLabel(item.edge.type) }),
3043
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: "secondary", children: item.direction === "out" ? "Outgoing" : "Incoming" }),
3044
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LayerBadge, { layer: item.node.layer })
3045
+ ] }),
3046
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "mt-2 text-sm font-medium", children: item.node.name }),
3047
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "mt-1 line-clamp-3 text-xs leading-5 text-muted-foreground", children: item.node.content || "-" })
3048
+ ]
3049
+ },
3050
+ item.edge.id
3051
+ )) })
3052
+ ] }, group.id)) });
3053
+ }
3054
+ function SimilarPanel({
3055
+ onSelectNode,
3056
+ similar
3057
+ }) {
3058
+ if (similar.length === 0) {
3059
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3060
+ EmptyState,
3061
+ {
3062
+ icon: import_lucide_react7.Sparkles,
3063
+ title: "No semantic neighbors",
3064
+ description: "Similar nodes appear here when the selected node has an embedding."
3065
+ }
3066
+ );
3067
+ }
3068
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "space-y-2", children: similar.map((item) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
3069
+ "button",
3070
+ {
3071
+ className: "w-full rounded-lg border bg-background p-3 text-left transition-colors hover:bg-muted/40",
3072
+ onClick: () => onSelectNode(item.node),
3073
+ type: "button",
3074
+ children: [
3075
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
3076
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: "secondary", children: formatSimilarityScore(item.similarity) }),
3077
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LayerBadge, { layer: item.node.layer }),
3078
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: "outline", children: formatLabel(item.node.kind) })
3079
+ ] }),
3080
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "mt-2 text-sm font-medium", children: item.node.name }),
3081
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "mt-1 line-clamp-3 text-xs leading-5 text-muted-foreground", children: item.node.content || "-" })
3082
+ ]
3083
+ },
3084
+ item.node.id
3085
+ )) });
3086
+ }
3087
+ function EvidencePanel({ node }) {
3088
+ const sourceMessageIds = Array.isArray(node.sourceMessageIds) ? node.sourceMessageIds : [];
3089
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "rounded-lg border bg-background p-4", children: [
3090
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("dl", { className: "grid gap-2 text-xs", children: [
3091
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Source", value: node.sourceType }),
3092
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Source ID", value: node.sourceId }),
3093
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Thread", value: node.threadId }),
3094
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(InspectorRow, { label: "Checkpoint", value: node.checkpointId })
3095
+ ] }),
3096
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "mt-4", children: [
3097
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "text-xs font-medium text-muted-foreground", children: "Source messages" }),
3098
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "mt-2 flex flex-wrap gap-1", children: sourceMessageIds.length ? sourceMessageIds.map((id) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { className: "max-w-full truncate", variant: "outline", children: id }, id)) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xs text-muted-foreground", children: "-" }) })
3099
+ ] })
3100
+ ] });
3101
+ }
3102
+ function MatchBadges({ match }) {
3103
+ if (!match?.reasons.length) return null;
3104
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "mt-2 flex flex-wrap gap-1", children: match.reasons.slice(0, 4).map((reason) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { className: "text-[10px]", variant: "outline", children: reason }, reason)) });
3105
+ }
2446
3106
  function InspectorRow({
2447
3107
  label,
2448
3108
  value
@@ -2471,6 +3131,12 @@ function formatDateTime(value) {
2471
3131
  if (Number.isNaN(date.getTime())) return value;
2472
3132
  return date.toLocaleString();
2473
3133
  }
3134
+ function formatNullableNumber(value) {
3135
+ return typeof value === "number" && Number.isFinite(value) ? value.toFixed(2) : "-";
3136
+ }
3137
+ function formatSimilarityScore(value) {
3138
+ return Number.isFinite(value) ? `Similarity ${value.toFixed(2)}` : "-";
3139
+ }
2474
3140
  function emptyBrainResponse() {
2475
3141
  return {
2476
3142
  nodes: [],
@@ -2482,6 +3148,14 @@ function emptyBrainResponse() {
2482
3148
  byKind: {},
2483
3149
  byStatus: {}
2484
3150
  },
3151
+ matches: {},
3152
+ related: [],
3153
+ similar: [],
3154
+ semantic: {
3155
+ requested: false,
3156
+ available: false,
3157
+ error: null
3158
+ },
2485
3159
  pageInfo: {
2486
3160
  limit: 0,
2487
3161
  offset: 0,
@@ -4841,8 +5515,11 @@ function useCopilotzAdmin(options = {}) {
4841
5515
  0 && (module.exports = {
4842
5516
  ADMIN_GROUP_LABELS,
4843
5517
  ADMIN_GROUP_ORDER,
5518
+ BRAIN_RELATION_GROUP_DEFINITIONS,
5519
+ BRAIN_VIEW_LABELS,
4844
5520
  CopilotzAdmin,
4845
5521
  EMPTY_USAGE_TOTALS,
5522
+ ENTITY_FOCUS_RELATION_TYPES,
4846
5523
  EmptyState,
4847
5524
  FilterBar,
4848
5525
  InspectorPanel,
@@ -4871,11 +5548,16 @@ function useCopilotzAdmin(options = {}) {
4871
5548
  formatNumber,
4872
5549
  formatPercent,
4873
5550
  formatUsageBucket,
5551
+ getBrainViewBaseFilters,
5552
+ getKnowledgeRelationGroups,
4874
5553
  getUsageDimensionLabel,
4875
5554
  getUsageGroupLabel,
4876
5555
  getUsageMetricLabel,
4877
5556
  getUsageRange,
4878
5557
  getUsageTotalValue,
5558
+ getWorkRelationGroups,
5559
+ groupBrainRelationsByKind,
5560
+ isEntityBrainView,
4879
5561
  mergeAdminConfig,
4880
5562
  overviewModule,
4881
5563
  participantsModule,