pi-mega-compact 0.13.4 → 0.13.6

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.
Files changed (217) hide show
  1. package/README.md +11 -8
  2. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +50 -0
  3. package/dist/extensions/dashboard-server/api-contracts/rag-metrics.js +1 -0
  4. package/dist/extensions/dashboard-server/api-contracts/rag-settings.js +3 -3
  5. package/dist/extensions/dashboard-server/api-contracts/wiki.js +1 -0
  6. package/dist/extensions/dashboard-server/routes-rag-metrics.js +60 -0
  7. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +167 -0
  8. package/dist/extensions/dashboard-server/routes-rag-settings.js +136 -77
  9. package/dist/extensions/dashboard-server/routes-wiki-helpers.js +97 -0
  10. package/dist/extensions/dashboard-server/routes-wiki.js +330 -0
  11. package/dist/extensions/dashboard-server/routes.js +2 -0
  12. package/dist/extensions/dashboard-server/server.js +17 -3
  13. package/dist/extensions/mega-events/context-handler/afterCompact.js +35 -2
  14. package/dist/extensions/mega-pipeline/recall.js +31 -0
  15. package/dist/extensions/mega-turn-store.js +2 -1
  16. package/dist/src/config/turns.js +10 -0
  17. package/dist/src/config.js +2 -0
  18. package/dist/src/recall/async.js +10 -1
  19. package/dist/src/recall/format.js +10 -1
  20. package/dist/src/recall/hydeTelemetry.js +22 -0
  21. package/dist/src/recall/sync.js +28 -2
  22. package/dist/src/store/turns/hydeStore.js +32 -0
  23. package/dist/src/store/turns/index.js +1 -0
  24. package/dist/src/store/turns/schema.js +102 -1
  25. package/dist/src/store/turns/sqlite-store/rows.js +23 -0
  26. package/dist/src/store/turns/sqlite-store/write.js +7 -3
  27. package/dist/src/topics/durability.js +187 -0
  28. package/dist/src/topics/index.js +2 -1
  29. package/dist/src/topics/store.js +28 -9
  30. package/dist/src/wiki/curation-helpers.js +71 -0
  31. package/dist/src/wiki/curation.js +147 -0
  32. package/dist/src/wiki/incremental.js +126 -0
  33. package/dist/src/wiki/index.js +5 -0
  34. package/extensions/dashboard-client/dist/assets/AreaChart-KAmp_6bL.js +70 -0
  35. package/extensions/dashboard-client/dist/assets/{SessionsTab-C30AwyoF.js.map → AreaChart-KAmp_6bL.js.map} +1 -1
  36. package/extensions/dashboard-client/dist/assets/CacheTab-Bk-gm-R0.js +2 -0
  37. package/extensions/dashboard-client/dist/assets/CacheTab-Bk-gm-R0.js.map +1 -0
  38. package/extensions/dashboard-client/dist/assets/EventsTab-Cyylo0of.js +2 -0
  39. package/extensions/dashboard-client/dist/assets/EventsTab-Cyylo0of.js.map +1 -0
  40. package/extensions/dashboard-client/dist/assets/HealthTab-D-IzmV29.js +2 -0
  41. package/extensions/dashboard-client/dist/assets/HealthTab-D-IzmV29.js.map +1 -0
  42. package/extensions/dashboard-client/dist/assets/MaintenanceTab-CrlkCFVj.js +2 -0
  43. package/extensions/dashboard-client/dist/assets/MaintenanceTab-CrlkCFVj.js.map +1 -0
  44. package/extensions/dashboard-client/dist/assets/MemoryMapTab-DHUdZ7RR.js +2 -0
  45. package/extensions/dashboard-client/dist/assets/MemoryMapTab-DHUdZ7RR.js.map +1 -0
  46. package/extensions/dashboard-client/dist/assets/MetricsTab-B7I4HWVg.js +2 -0
  47. package/extensions/dashboard-client/dist/assets/MetricsTab-B7I4HWVg.js.map +1 -0
  48. package/extensions/dashboard-client/dist/assets/OverviewTab-K6j4ZI7u.js +2 -0
  49. package/extensions/dashboard-client/dist/assets/OverviewTab-K6j4ZI7u.js.map +1 -0
  50. package/extensions/dashboard-client/dist/assets/ReposTab-BSRWhn56.js +2 -0
  51. package/extensions/dashboard-client/dist/assets/ReposTab-BSRWhn56.js.map +1 -0
  52. package/extensions/dashboard-client/dist/assets/SessionsTab-BXdizHN1.js +2 -0
  53. package/extensions/dashboard-client/dist/assets/SessionsTab-BXdizHN1.js.map +1 -0
  54. package/extensions/dashboard-client/dist/assets/SetupTab-CAgyfqY3.js +2 -0
  55. package/extensions/dashboard-client/dist/assets/SetupTab-CAgyfqY3.js.map +1 -0
  56. package/extensions/dashboard-client/dist/assets/TimeSavedCard-DwgCzPD9.js +2 -0
  57. package/extensions/dashboard-client/dist/assets/TimeSavedCard-DwgCzPD9.js.map +1 -0
  58. package/extensions/dashboard-client/dist/assets/TurnsTab-CVKcStBp.js +5 -0
  59. package/extensions/dashboard-client/dist/assets/TurnsTab-CVKcStBp.js.map +1 -0
  60. package/extensions/dashboard-client/dist/assets/WikiTab-BNohxTbs.js +2 -0
  61. package/extensions/dashboard-client/dist/assets/WikiTab-BNohxTbs.js.map +1 -0
  62. package/extensions/dashboard-client/dist/assets/button-B4A-_dCA.js +2 -0
  63. package/extensions/dashboard-client/dist/assets/button-B4A-_dCA.js.map +1 -0
  64. package/extensions/dashboard-client/dist/assets/card-Cjn7Ifoe.js +2 -0
  65. package/extensions/dashboard-client/dist/assets/card-Cjn7Ifoe.js.map +1 -0
  66. package/extensions/dashboard-client/dist/assets/index-CQjCLvv7.js +137 -0
  67. package/extensions/dashboard-client/dist/assets/index-CQjCLvv7.js.map +1 -0
  68. package/extensions/dashboard-client/dist/assets/index-CUA6EDRF.css +1 -0
  69. package/extensions/dashboard-client/dist/assets/switch-CcdXX0Zm.js +2 -0
  70. package/extensions/dashboard-client/dist/assets/switch-CcdXX0Zm.js.map +1 -0
  71. package/extensions/dashboard-client/dist/assets/toggle-KdBnFLu3.js +2 -0
  72. package/extensions/dashboard-client/dist/assets/toggle-KdBnFLu3.js.map +1 -0
  73. package/extensions/dashboard-client/dist/assets/{useSSE-p1pOI_-P.js → useSSE-B6t44vzc.js} +2 -2
  74. package/extensions/dashboard-client/dist/assets/{useSSE-p1pOI_-P.js.map → useSSE-B6t44vzc.js.map} +1 -1
  75. package/extensions/dashboard-client/dist/index.html +2 -2
  76. package/extensions/dashboard-client/package-lock.json +1995 -191
  77. package/extensions/dashboard-client/package.json +12 -1
  78. package/extensions/dashboard-client/postcss.config.js +6 -0
  79. package/extensions/dashboard-client/src/App.tsx +127 -88
  80. package/extensions/dashboard-client/src/api/client.ts +109 -10
  81. package/extensions/dashboard-client/src/components/ActiveReposTable.tsx +15 -7
  82. package/extensions/dashboard-client/src/components/ActiveSessionsTable.tsx +67 -55
  83. package/extensions/dashboard-client/src/components/CacheHitRateTrendCard.tsx +17 -13
  84. package/extensions/dashboard-client/src/components/CacheHitsCard.tsx +35 -30
  85. package/extensions/dashboard-client/src/components/CacheStatusPerModel.tsx +33 -28
  86. package/extensions/dashboard-client/src/components/EventCategoryFilter.tsx +5 -6
  87. package/extensions/dashboard-client/src/components/EventStream.tsx +17 -13
  88. package/extensions/dashboard-client/src/components/HydeDetailPanel.tsx +173 -0
  89. package/extensions/dashboard-client/src/components/LegendCard.tsx +16 -9
  90. package/extensions/dashboard-client/src/components/PerfCards.tsx +28 -20
  91. package/extensions/dashboard-client/src/components/PerfChart.tsx +9 -6
  92. package/extensions/dashboard-client/src/components/ProviderCacheCard.tsx +15 -7
  93. package/extensions/dashboard-client/src/components/RagDashboard.tsx +253 -0
  94. package/extensions/dashboard-client/src/components/RepoAllSessionsCard.tsx +15 -10
  95. package/extensions/dashboard-client/src/components/RepoContextStack.tsx +24 -16
  96. package/extensions/dashboard-client/src/components/RepoDetailModal.tsx +52 -49
  97. package/extensions/dashboard-client/src/components/RepoTable.tsx +26 -17
  98. package/extensions/dashboard-client/src/components/SessionContextGauges.tsx +11 -14
  99. package/extensions/dashboard-client/src/components/SessionInfo.tsx +20 -15
  100. package/extensions/dashboard-client/src/components/SessionsMemoryChart.tsx +8 -5
  101. package/extensions/dashboard-client/src/components/SummaryTiles.tsx +26 -17
  102. package/extensions/dashboard-client/src/components/TabBar.tsx +1 -3
  103. package/extensions/dashboard-client/src/components/layout/AppShell.tsx +42 -0
  104. package/extensions/dashboard-client/src/components/layout/BottomBar.tsx +88 -0
  105. package/extensions/dashboard-client/src/components/layout/Sidebar.tsx +83 -0
  106. package/extensions/dashboard-client/src/components/ui/badge.tsx +30 -0
  107. package/extensions/dashboard-client/src/components/ui/button.tsx +44 -0
  108. package/extensions/dashboard-client/src/components/ui/card.tsx +32 -0
  109. package/extensions/dashboard-client/src/components/ui/select.tsx +64 -0
  110. package/extensions/dashboard-client/src/components/ui/switch.tsx +26 -0
  111. package/extensions/dashboard-client/src/components/ui/tabs.tsx +36 -0
  112. package/extensions/dashboard-client/src/components/ui/toggle.tsx +28 -0
  113. package/extensions/dashboard-client/src/components/ui/tooltip.tsx +27 -0
  114. package/extensions/dashboard-client/src/config.ts +10 -0
  115. package/extensions/dashboard-client/src/main.tsx +2 -2
  116. package/extensions/dashboard-client/src/memory-map-layout.ts +1 -2
  117. package/extensions/dashboard-client/src/styles/index.css +79 -0
  118. package/extensions/dashboard-client/src/tabs/CacheTab.tsx +33 -26
  119. package/extensions/dashboard-client/src/tabs/EventsTab.tsx +22 -7
  120. package/extensions/dashboard-client/src/tabs/HealthTab.tsx +77 -46
  121. package/extensions/dashboard-client/src/tabs/MaintenanceTab/ActionsCard.tsx +60 -50
  122. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DbStatsCard.tsx +61 -35
  123. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DebugBundleCard.tsx +34 -23
  124. package/extensions/dashboard-client/src/tabs/MaintenanceTab/HealthMitigationCard.tsx +62 -43
  125. package/extensions/dashboard-client/src/tabs/MaintenanceTab/SchemaHealthCard.tsx +53 -32
  126. package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +2 -2
  127. package/extensions/dashboard-client/src/tabs/MemoryMapTab/MemoryMapView.tsx +29 -46
  128. package/extensions/dashboard-client/src/tabs/MemoryMapTab/RaptorTreeView.tsx +10 -34
  129. package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +14 -34
  130. package/extensions/dashboard-client/src/tabs/MetricsTab.tsx +5 -2
  131. package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +11 -5
  132. package/extensions/dashboard-client/src/tabs/ReposTab.tsx +5 -5
  133. package/extensions/dashboard-client/src/tabs/SessionsTab.tsx +47 -33
  134. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +6 -2
  135. package/extensions/dashboard-client/src/tabs/SetupTab/RagHealthCard.tsx +78 -0
  136. package/extensions/dashboard-client/src/tabs/SetupTab/SettingsPanel.tsx +198 -0
  137. package/extensions/dashboard-client/src/tabs/SetupTab/SettingsSection.tsx +50 -0
  138. package/extensions/dashboard-client/src/tabs/SetupTab.tsx +21 -59
  139. package/extensions/dashboard-client/src/tabs/TopicsTab.tsx +75 -40
  140. package/extensions/dashboard-client/src/tabs/TurnsTab.tsx +150 -112
  141. package/extensions/dashboard-client/src/tabs/WikiTab/TopicEvolutionGraph.tsx +274 -0
  142. package/extensions/dashboard-client/src/tabs/WikiTab/TopicEvolutionView.tsx +71 -0
  143. package/extensions/dashboard-client/src/tabs/WikiTab/TopicTimeline.tsx +103 -0
  144. package/extensions/dashboard-client/src/tabs/WikiTab/WikiPage.tsx +213 -0
  145. package/extensions/dashboard-client/src/tabs/WikiTab/WikiPageControls.tsx +188 -0
  146. package/extensions/dashboard-client/src/tabs/WikiTab.tsx +222 -0
  147. package/extensions/dashboard-client/src/tabs/registry.ts +57 -0
  148. package/extensions/dashboard-client/src/utils/cn.ts +7 -0
  149. package/extensions/dashboard-client/tailwind.config.js +46 -0
  150. package/extensions/dashboard-server/api-contracts/core.ts +59 -0
  151. package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +79 -0
  152. package/extensions/dashboard-server/api-contracts/index.ts +48 -2
  153. package/extensions/dashboard-server/api-contracts/rag-metrics.ts +22 -0
  154. package/extensions/dashboard-server/api-contracts/rag-settings.ts +30 -20
  155. package/extensions/dashboard-server/api-contracts/wiki.ts +138 -0
  156. package/extensions/dashboard-server/routes-rag-metrics.ts +82 -0
  157. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +314 -0
  158. package/extensions/dashboard-server/routes-rag-settings.ts +147 -97
  159. package/extensions/dashboard-server/routes-wiki-helpers.ts +126 -0
  160. package/extensions/dashboard-server/routes-wiki.ts +376 -0
  161. package/extensions/dashboard-server/routes.ts +2 -0
  162. package/extensions/dashboard-server/server.ts +18 -2
  163. package/extensions/mega-events/context-handler/afterCompact.ts +44 -1
  164. package/extensions/mega-pipeline/recall.ts +31 -0
  165. package/extensions/mega-turn-store.ts +13 -0
  166. package/package.json +1 -1
  167. package/src/config/turns.ts +17 -0
  168. package/src/config.ts +3 -0
  169. package/src/recall/async.ts +10 -1
  170. package/src/recall/format.ts +15 -2
  171. package/src/recall/hydeTelemetry.ts +44 -0
  172. package/src/recall/sync.ts +34 -2
  173. package/src/recall/types.ts +36 -0
  174. package/src/store/turns/hydeStore.ts +82 -0
  175. package/src/store/turns/index.ts +4 -0
  176. package/src/store/turns/schema.ts +126 -1
  177. package/src/store/turns/sqlite-store/rows.ts +25 -0
  178. package/src/store/turns/sqlite-store/write.ts +20 -2
  179. package/src/store/turns/types.ts +33 -0
  180. package/src/topics/durability.ts +231 -0
  181. package/src/topics/index.ts +7 -1
  182. package/src/topics/store.ts +35 -9
  183. package/src/wiki/curation-helpers.ts +140 -0
  184. package/src/wiki/curation.ts +217 -0
  185. package/src/wiki/incremental.ts +164 -0
  186. package/src/wiki/index.ts +13 -0
  187. package/extensions/dashboard-client/dist/assets/CacheTab-Cqnc7lMg.js +0 -2
  188. package/extensions/dashboard-client/dist/assets/CacheTab-Cqnc7lMg.js.map +0 -1
  189. package/extensions/dashboard-client/dist/assets/EventsTab-tYk3VFAp.js +0 -2
  190. package/extensions/dashboard-client/dist/assets/EventsTab-tYk3VFAp.js.map +0 -1
  191. package/extensions/dashboard-client/dist/assets/HealthTab-D2_kjgxw.js +0 -2
  192. package/extensions/dashboard-client/dist/assets/HealthTab-D2_kjgxw.js.map +0 -1
  193. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Bc-uPYrz.js +0 -2
  194. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Bc-uPYrz.js.map +0 -1
  195. package/extensions/dashboard-client/dist/assets/MemoryMapTab-D1V9g_NC.js +0 -2
  196. package/extensions/dashboard-client/dist/assets/MemoryMapTab-D1V9g_NC.js.map +0 -1
  197. package/extensions/dashboard-client/dist/assets/MetricsTab-DT_XfL96.js +0 -2
  198. package/extensions/dashboard-client/dist/assets/MetricsTab-DT_XfL96.js.map +0 -1
  199. package/extensions/dashboard-client/dist/assets/OverviewTab-BY6KZCme.js +0 -2
  200. package/extensions/dashboard-client/dist/assets/OverviewTab-BY6KZCme.js.map +0 -1
  201. package/extensions/dashboard-client/dist/assets/ReposTab-CeS-tpDM.js +0 -2
  202. package/extensions/dashboard-client/dist/assets/ReposTab-CeS-tpDM.js.map +0 -1
  203. package/extensions/dashboard-client/dist/assets/SessionsTab-C30AwyoF.js +0 -70
  204. package/extensions/dashboard-client/dist/assets/SetupTab-BJUHOVK2.js +0 -2
  205. package/extensions/dashboard-client/dist/assets/SetupTab-BJUHOVK2.js.map +0 -1
  206. package/extensions/dashboard-client/dist/assets/TimeSavedCard-p55K1tFP.js +0 -2
  207. package/extensions/dashboard-client/dist/assets/TimeSavedCard-p55K1tFP.js.map +0 -1
  208. package/extensions/dashboard-client/dist/assets/TopicsTab-D8FqyAbK.js +0 -2
  209. package/extensions/dashboard-client/dist/assets/TopicsTab-D8FqyAbK.js.map +0 -1
  210. package/extensions/dashboard-client/dist/assets/TurnsTab-ju-nXDys.js +0 -2
  211. package/extensions/dashboard-client/dist/assets/TurnsTab-ju-nXDys.js.map +0 -1
  212. package/extensions/dashboard-client/dist/assets/index-BEj1Zahy.js +0 -42
  213. package/extensions/dashboard-client/dist/assets/index-BEj1Zahy.js.map +0 -1
  214. package/extensions/dashboard-client/dist/assets/index-CUNn22iV.css +0 -1
  215. package/extensions/dashboard-client/src/styles/metrics-extra.css +0 -69
  216. package/extensions/dashboard-client/src/styles/turns.css +0 -142
  217. package/extensions/dashboard-client/src/tabs/SetupTab/RagSettingsCard.tsx +0 -160
@@ -30,17 +30,21 @@ import type {
30
30
  TurnRow,
31
31
  ConversationSummary,
32
32
  } from "@contracts";
33
+ import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card";
34
+ import { Badge } from "../components/ui/badge";
35
+ import { Button } from "../components/ui/button";
36
+ import { HydeDetailPanel } from "../components/HydeDetailPanel";
33
37
 
34
38
  function fmtTs(ms: number): string {
35
39
  if (!ms) return "—";
36
40
  return new Date(ms).toLocaleString();
37
41
  }
38
42
 
39
- function bandClass(band: TurnRow["pressureBand"]): string {
40
- if (band === "green") return "band band-green";
41
- if (band === "yellow") return "band band-yellow";
42
- if (band === "red") return "band band-red";
43
- return "band";
43
+ function bandVariant(band: TurnRow["pressureBand"]): "success" | "warning" | "danger" | "default" {
44
+ if (band === "green") return "success";
45
+ if (band === "yellow") return "warning";
46
+ if (band === "red") return "danger";
47
+ return "default";
44
48
  }
45
49
 
46
50
  function sourceLabel(s: TurnRow["recall"][number]["source"]): string {
@@ -145,70 +149,88 @@ export default function TurnsTab(): React.ReactElement {
145
149
  }
146
150
 
147
151
  return (
148
- <div className="turns-tab">
149
- <h3>Turn-by-turn memory tracking + recall</h3>
150
- {notice && <div className="turns-notice">{notice}</div>}
152
+ <div className="flex flex-col gap-4">
153
+ <h3 className="font-heading text-lg font-semibold">
154
+ Turn-by-turn memory tracking + recall
155
+ </h3>
156
+ {notice && <Badge variant="warning">{notice}</Badge>}
151
157
 
152
158
  {intents && intents.intents.length > 0 && (
153
- <section className="turns-intents">
154
- <h4>Pending rewind intents ({intents.intents.length})</h4>
155
- <ul>
156
- {intents.intents.map((i) => (
157
- <li key={i.id}>
158
- {fmtTs(i.createdAt)} — rewind <code>{i.conversationId}</code> to
159
- turn {i.targetTurnIndex}
160
- </li>
161
- ))}
162
- </ul>
163
- </section>
159
+ <Card>
160
+ <CardHeader>
161
+ <CardTitle>
162
+ Pending rewind intents ({intents.intents.length})
163
+ </CardTitle>
164
+ </CardHeader>
165
+ <CardContent>
166
+ <ul className="list-disc space-y-1 pl-5">
167
+ {intents.intents.map((i) => (
168
+ <li key={i.id}>
169
+ {fmtTs(i.createdAt)} — rewind{" "}
170
+ <code>{i.conversationId}</code> to turn {i.targetTurnIndex}
171
+ </li>
172
+ ))}
173
+ </ul>
174
+ </CardContent>
175
+ </Card>
164
176
  )}
165
177
 
166
- <section className="turns-conv-list">
167
- <h4>Conversations ({turns.conversations.length})</h4>
168
- <table className="turns-table">
169
- <thead>
170
- <tr>
171
- <th>Conversation</th>
172
- <th>Turns</th>
173
- <th>Recall</th>
174
- <th>Epochs</th>
175
- <th>Avg ctx%</th>
176
- <th>Last turn</th>
177
- <th></th>
178
- </tr>
179
- </thead>
180
- <tbody>
181
- {turns.conversations.map((c: ConversationSummary) => (
182
- <ConversationRow
183
- key={c.conversationId}
184
- c={c}
185
- active={turns.activeConversationId === c.conversationId}
186
- expanded={expanded === c.conversationId}
187
- onExpand={() => expand(c.conversationId)}
188
- />
189
- ))}
190
- </tbody>
191
- </table>
192
- </section>
178
+ <Card>
179
+ <CardHeader>
180
+ <CardTitle>
181
+ Conversations ({turns.conversations.length})
182
+ </CardTitle>
183
+ </CardHeader>
184
+ <CardContent>
185
+ <table className="w-full border-collapse text-sm">
186
+ <thead>
187
+ <tr>
188
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Conversation</th>
189
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Turns</th>
190
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Recall</th>
191
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Epochs</th>
192
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Avg ctx%</th>
193
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Last turn</th>
194
+ <th className="border-b border-border px-3 py-2"></th>
195
+ </tr>
196
+ </thead>
197
+ <tbody>
198
+ {turns.conversations.map((c: ConversationSummary) => (
199
+ <ConversationRow
200
+ key={c.conversationId}
201
+ c={c}
202
+ active={turns.activeConversationId === c.conversationId}
203
+ expanded={expanded === c.conversationId}
204
+ onExpand={() => expand(c.conversationId)}
205
+ />
206
+ ))}
207
+ </tbody>
208
+ </table>
209
+ </CardContent>
210
+ </Card>
193
211
 
194
212
  {expanded && (
195
- <section className="turns-detail">
196
- <h4>
197
- Turns in <code>{expanded}</code>
198
- </h4>
199
- {detailLoading ? (
200
- <div className="tab-stub">Loading turns…</div>
201
- ) : detail ? (
202
- <TurnDetail
203
- detail={detail}
204
- busy={busy}
205
- onFork={onFork}
206
- onRewind={onRewind}
207
- />
208
- ) : (
209
- <div className="tab-stub">No detail.</div>
210
- )}
211
- </section>
213
+ <Card>
214
+ <CardHeader>
215
+ <CardTitle>
216
+ Turns in <code>{expanded}</code>
217
+ </CardTitle>
218
+ </CardHeader>
219
+ <CardContent>
220
+ {detailLoading ? (
221
+ <div className="text-sm text-muted-foreground">Loading turns…</div>
222
+ ) : detail ? (
223
+ <TurnDetail
224
+ detail={detail}
225
+ busy={busy}
226
+ onFork={onFork}
227
+ onRewind={onRewind}
228
+ />
229
+ ) : (
230
+ <div className="text-sm text-muted-foreground">No detail.</div>
231
+ )}
232
+ </CardContent>
233
+ </Card>
212
234
  )}
213
235
  </div>
214
236
  );
@@ -228,20 +250,22 @@ function ConversationRow({
228
250
  return (
229
251
  <>
230
252
  <tr
231
- className={active ? "turns-row active" : "turns-row"}
253
+ className={`border-b border-border/50 hover:bg-bg-elevated/40 ${
254
+ active ? "bg-primary/10" : ""
255
+ }`}
232
256
  onClick={onExpand}
233
257
  style={{ cursor: "pointer" }}
234
258
  >
235
- <td>
259
+ <td className="px-3 py-2">
236
260
  {expanded ? "▾" : "▸"} <code>{c.conversationId}</code>
237
- {active && <span className="pill">active</span>}
261
+ {active && <Badge variant="accent" className="ml-2">active</Badge>}
238
262
  </td>
239
- <td>{c.turnCount}</td>
240
- <td>{c.totalRecall}</td>
241
- <td>{c.epochCount}</td>
242
- <td>{c.avgCtxPercent.toFixed(0)}%</td>
243
- <td>{fmtTs(c.lastTurnAt)}</td>
244
- <td />
263
+ <td className="px-3 py-2 text-right">{c.turnCount}</td>
264
+ <td className="px-3 py-2 text-right">{c.totalRecall}</td>
265
+ <td className="px-3 py-2 text-right">{c.epochCount}</td>
266
+ <td className="px-3 py-2 text-right">{c.avgCtxPercent.toFixed(0)}%</td>
267
+ <td className="px-3 py-2">{fmtTs(c.lastTurnAt)}</td>
268
+ <td className="px-3 py-2" />
245
269
  </tr>
246
270
  </>
247
271
  );
@@ -259,48 +283,52 @@ function TurnDetail({
259
283
  onRewind: (conversationId: string, turnIndex: number) => void;
260
284
  }): React.ReactElement {
261
285
  return (
262
- <table className="turns-detail-table">
286
+ <table className="w-full border-collapse text-sm">
263
287
  <thead>
264
288
  <tr>
265
- <th>#</th>
266
- <th>Role</th>
267
- <th>Ctx</th>
268
- <th>Band</th>
269
- <th>Epoch</th>
270
- <th>Recalled checkpoints (memory recall)</th>
271
- <th>Ended</th>
272
- <th>Actions</th>
289
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">#</th>
290
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Role</th>
291
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Ctx</th>
292
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Band</th>
293
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Epoch</th>
294
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Recalled checkpoints (memory recall)</th>
295
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">HyDE</th>
296
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Ended</th>
297
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Actions</th>
273
298
  </tr>
274
299
  </thead>
275
300
  <tbody>
276
301
  {detail.turns.map((t) => (
277
- <tr key={`${t.conversationId}:${t.turnIndex}`}>
278
- <td>{t.turnIndex}</td>
279
- <td>{t.role}</td>
280
- <td>
302
+ <tr
303
+ key={`${t.conversationId}:${t.turnIndex}`}
304
+ className="border-b border-border/50 hover:bg-bg-elevated/40"
305
+ >
306
+ <td className="px-3 py-2 text-right">{t.turnIndex}</td>
307
+ <td className="px-3 py-2">{t.role}</td>
308
+ <td className="px-3 py-2">
281
309
  {t.ctxTokens ?? "—"}
282
310
  {t.ctxPercent != null && (
283
- <span className="muted">
311
+ <span className="text-muted-foreground">
284
312
  {" "}
285
313
  · {t.ctxPercent.toFixed(0)}%
286
314
  </span>
287
315
  )}
288
316
  </td>
289
- <td>
290
- <span className={bandClass(t.pressureBand)}>
317
+ <td className="px-3 py-2">
318
+ <Badge variant={bandVariant(t.pressureBand)}>
291
319
  {t.pressureBand ?? "—"}
292
- </span>
320
+ </Badge>
293
321
  </td>
294
- <td>{t.epochId ? <code>{t.epochId.slice(0, 8)}</code> : "—"}</td>
295
- <td className="recall-cell">
322
+ <td className="px-3 py-2">{t.epochId ? <code>{t.epochId.slice(0, 8)}</code> : "—"}</td>
323
+ <td className="px-3 py-2">
296
324
  {t.recall.length === 0 ? (
297
- <span className="muted">—</span>
325
+ <span className="text-muted-foreground">—</span>
298
326
  ) : (
299
- <ul className="recall-list">
327
+ <ul className="space-y-1">
300
328
  {t.recall.map((r) => (
301
329
  <li key={r.checkpointId}>
302
330
  <code>{r.checkpointId.slice(0, 12)}</code>{" "}
303
- <span className="muted">
331
+ <span className="text-muted-foreground">
304
332
  {sourceLabel(r.source)} · {r.score.toFixed(2)}
305
333
  {r.raptorLevel != null ? ` · L${r.raptorLevel}` : ""}
306
334
  </span>
@@ -309,24 +337,34 @@ function TurnDetail({
309
337
  </ul>
310
338
  )}
311
339
  </td>
312
- <td>{fmtTs(t.endedAt)}</td>
313
- <td className="actions-cell">
314
- <button
315
- type="button"
316
- className="mini-btn"
317
- disabled={busy !== null}
318
- onClick={() => onFork(t.conversationId, t.turnIndex)}
319
- >
320
- fork
321
- </button>
322
- <button
323
- type="button"
324
- className="mini-btn"
325
- disabled={busy !== null}
326
- onClick={() => onRewind(t.conversationId, t.turnIndex)}
327
- >
328
- rewind
329
- </button>
340
+ <td className="px-3 py-2 align-top">
341
+ <HydeDetailPanel
342
+ conversationId={t.conversationId}
343
+ turnIndex={t.turnIndex}
344
+ />
345
+ </td>
346
+ <td className="px-3 py-2">{fmtTs(t.endedAt)}</td>
347
+ <td className="px-3 py-2">
348
+ <div className="flex gap-2">
349
+ <Button
350
+ type="button"
351
+ variant="outline"
352
+ size="sm"
353
+ disabled={busy !== null}
354
+ onClick={() => onFork(t.conversationId, t.turnIndex)}
355
+ >
356
+ fork
357
+ </Button>
358
+ <Button
359
+ type="button"
360
+ variant="outline"
361
+ size="sm"
362
+ disabled={busy !== null}
363
+ onClick={() => onRewind(t.conversationId, t.turnIndex)}
364
+ >
365
+ rewind
366
+ </Button>
367
+ </div>
330
368
  </td>
331
369
  </tr>
332
370
  ))}
@@ -0,0 +1,274 @@
1
+ /**
2
+ * dashboard-client/src/tabs/WikiTab/TopicEvolutionGraph.tsx — D3 evolution.
3
+ *
4
+ * Force-directed graph of wiki topics over time. Nodes are topics (radius scales
5
+ * with memoryCount); edges are merge/split curation events. A bucket scrubber
6
+ * (from the response buckets) dims nodes that had no activity up to the selected
7
+ * time window — a lightweight "growth over time" view.
8
+ *
9
+ * Self-contained force simulation (Coulomb repulsion + Hooke attraction +
10
+ * center gravity) — the codebase deliberately avoids external graph libraries
11
+ * (see memory-map-layout.ts). No data is fabricated: only real node/edge/bucket
12
+ * fields from the contract are used. Styling is Tailwind.
13
+ */
14
+
15
+ import type React from "react";
16
+ import { useEffect, useMemo, useRef, useState } from "react";
17
+ import type { TopicEvolutionResponse } from "@contracts";
18
+
19
+ // ── Simulation constants ────────────────────────────────────────────────
20
+ const W = 900;
21
+ const H = 560;
22
+ const REPULSION = 60_000;
23
+ const ATTRACTION = 0.012;
24
+ const CENTER_GRAVITY = 0.012;
25
+ const DAMPING = 0.8;
26
+ const SPEED_LIMIT = 2;
27
+ const MIN_VELOCITY = 0.02;
28
+ const MAX_ITERATIONS = 250;
29
+
30
+ interface SimNode {
31
+ id: string;
32
+ label: string;
33
+ memoryCount: number;
34
+ pos: { x: number; y: number };
35
+ vx: number;
36
+ vy: number;
37
+ }
38
+
39
+ interface SimEdge {
40
+ source: number;
41
+ target: number;
42
+ kind: "merge" | "split";
43
+ }
44
+
45
+ function edgeColor(kind: "merge" | "split"): string {
46
+ return kind === "merge" ? "#f59e0b" : "#22c55e"; // amber / green
47
+ }
48
+
49
+ function initialLayout(nodes: TopicEvolutionResponse["nodes"]): SimNode[] {
50
+ return nodes.map((n, i) => {
51
+ const angle = (2 * Math.PI * i) / Math.max(nodes.length, 1);
52
+ return {
53
+ id: n.id,
54
+ label: n.label,
55
+ memoryCount: n.memoryCount,
56
+ pos: { x: Math.cos(angle) * 220 + W / 2, y: Math.sin(angle) * 220 + H / 2 },
57
+ vx: 0,
58
+ vy: 0,
59
+ };
60
+ });
61
+ }
62
+
63
+ function applyForces(sim: { nodes: SimNode[]; edges: SimEdge[] }): void {
64
+ const { nodes, edges } = sim;
65
+ for (let i = 0; i < nodes.length; i++) {
66
+ const n = nodes[i];
67
+ let fx = 0;
68
+ let fy = 0;
69
+ for (let j = 0; j < nodes.length; j++) {
70
+ if (i === j) continue;
71
+ const dx = n.pos.x - nodes[j].pos.x;
72
+ const dy = n.pos.y - nodes[j].pos.y;
73
+ const distSq = dx * dx + dy * dy || 1;
74
+ const dist = Math.sqrt(distSq);
75
+ fx += (dx / dist) * (REPULSION / distSq);
76
+ fy += (dy / dist) * (REPULSION / distSq);
77
+ }
78
+ for (const e of edges) {
79
+ let other: number;
80
+ if (e.source === i) other = e.target;
81
+ else if (e.target === i) other = e.source;
82
+ else continue;
83
+ const dx = nodes[other].pos.x - n.pos.x;
84
+ const dy = nodes[other].pos.y - n.pos.y;
85
+ fx += dx * ATTRACTION;
86
+ fy += dy * ATTRACTION;
87
+ }
88
+ fx -= n.pos.x * CENTER_GRAVITY;
89
+ fy -= n.pos.y * CENTER_GRAVITY;
90
+ n.vx = (n.vx + fx) * DAMPING;
91
+ n.vy = (n.vy + fy) * DAMPING;
92
+ const speed = Math.sqrt(n.vx * n.vx + n.vy * n.vy);
93
+ if (speed > SPEED_LIMIT) {
94
+ n.vx = (n.vx / speed) * SPEED_LIMIT;
95
+ n.vy = (n.vy / speed) * SPEED_LIMIT;
96
+ }
97
+ if (speed > MIN_VELOCITY) {
98
+ n.pos.x += n.vx;
99
+ n.pos.y += n.vy;
100
+ }
101
+ }
102
+ }
103
+
104
+ interface Props {
105
+ data: TopicEvolutionResponse;
106
+ }
107
+
108
+ export default function TopicEvolutionGraph({
109
+ data,
110
+ }: Props): React.ReactElement {
111
+ const simRef = useRef<{
112
+ nodes: SimNode[];
113
+ edges: SimEdge[];
114
+ timeByActivity: Map<string, number> | null;
115
+ } | null>(null);
116
+ const animRef = useRef(0);
117
+ const [, setFrame] = useState(0);
118
+ const [scrub, setScrub] = useState<number>(0);
119
+
120
+ // Initialize simulation from data (re-run when data changes).
121
+ useEffect(() => {
122
+ const nodes = initialLayout(data.nodes);
123
+ const indexById = new Map(nodes.map((n, i) => [n.id, i]));
124
+ const edges: SimEdge[] = data.edges
125
+ .map((e) => {
126
+ const si = indexById.get(e.source);
127
+ const ti = indexById.get(e.target);
128
+ if (si === undefined || ti === undefined || si === ti) return null;
129
+ return { source: si, target: ti, kind: e.kind };
130
+ })
131
+ .filter((e): e is SimEdge => e !== null);
132
+ // Latest activity per node (from merge/split edge timestamps).
133
+ const timeByActivity = new Map<string, number>();
134
+ for (const e of data.edges) {
135
+ timeByActivity.set(e.source, Math.max(timeByActivity.get(e.source) ?? 0, e.at));
136
+ timeByActivity.set(e.target, Math.max(timeByActivity.get(e.target) ?? 0, e.at));
137
+ }
138
+ simRef.current = { nodes, edges, timeByActivity };
139
+ setFrame(1);
140
+ return () => cancelAnimationFrame(animRef.current);
141
+ }, [data]);
142
+
143
+ // Run the force simulation to stable layout.
144
+ useEffect(() => {
145
+ if (!simRef.current) return;
146
+ let iter = 0;
147
+ const tick = () => {
148
+ if (!simRef.current || iter >= MAX_ITERATIONS) return;
149
+ applyForces(simRef.current);
150
+ iter++;
151
+ setFrame(iter);
152
+ animRef.current = requestAnimationFrame(tick);
153
+ };
154
+ animRef.current = requestAnimationFrame(tick);
155
+ return () => cancelAnimationFrame(animRef.current);
156
+ }, [data]);
157
+
158
+ const sim = simRef.current;
159
+
160
+ const scrubMax = Math.max(data.buckets.length - 1, 0);
161
+ const scrubBucket = useMemo(() => {
162
+ if (data.buckets.length === 0) return null;
163
+ const idx = Math.min(scrub, data.buckets.length - 1);
164
+ return data.buckets[idx].bucket;
165
+ }, [scrub, data.buckets]);
166
+
167
+ if (!sim || data.nodes.length === 0) {
168
+ return (
169
+ <div className="rounded-lg border border-border bg-bg-card p-6 text-sm text-muted-foreground">
170
+ No evolution data yet. Merge or split topics to see connections, then
171
+ check back after a rebuild.
172
+ </div>
173
+ );
174
+ }
175
+
176
+ const activeSet =
177
+ scrubBucket == null || !sim.timeByActivity
178
+ ? null
179
+ : new Set(
180
+ [...sim.timeByActivity.entries()]
181
+ .filter(([, t]) => t <= scrubBucket)
182
+ .map(([id]) => id),
183
+ );
184
+
185
+ return (
186
+ <div className="flex flex-col gap-3">
187
+ {/* Scrubber */}
188
+ {data.buckets.length > 1 && (
189
+ <div className="flex items-center gap-3">
190
+ <input
191
+ type="range"
192
+ min={0}
193
+ max={scrubMax}
194
+ value={scrub}
195
+ onChange={(e) => setScrub(Number(e.target.value))}
196
+ className="w-full accent-primary"
197
+ aria-label="Scrub through topic activity time"
198
+ />
199
+ <span className="whitespace-nowrap text-xs text-muted-foreground">
200
+ {scrubBucket ? new Date(scrubBucket).toLocaleDateString() : "all"}
201
+ </span>
202
+ </div>
203
+ )}
204
+
205
+ {/* SVG graph */}
206
+ <svg
207
+ width="100%"
208
+ height={H}
209
+ viewBox={`0 0 ${W} ${H}`}
210
+ role="img"
211
+ aria-label="Topic evolution graph"
212
+ className="rounded-lg border border-border/60 bg-bg-card"
213
+ >
214
+ {sim.edges.map((e, i) => {
215
+ const a = sim.nodes[e.source];
216
+ const b = sim.nodes[e.target];
217
+ return (
218
+ <line
219
+ key={`edge-${i}`}
220
+ x1={a.pos.x}
221
+ y1={a.pos.y}
222
+ x2={b.pos.x}
223
+ y2={b.pos.y}
224
+ stroke={edgeColor(e.kind)}
225
+ strokeWidth={1.5}
226
+ strokeDasharray={e.kind === "split" ? "4 3" : undefined}
227
+ opacity={0.5}
228
+ />
229
+ );
230
+ })}
231
+ {sim.nodes.map((n, i) => {
232
+ const active =
233
+ activeSet == null || activeSet.size === 0 || activeSet.has(n.id);
234
+ const r = Math.min(32, 8 + n.memoryCount * 3);
235
+ return (
236
+ <g key={`node-${i}`} opacity={active ? 1 : 0.15}>
237
+ <circle
238
+ cx={n.pos.x}
239
+ cy={n.pos.y}
240
+ r={r}
241
+ fill="hsl(var(--primary) / 0.18)"
242
+ stroke="hsl(var(--primary))"
243
+ strokeWidth={1.5}
244
+ />
245
+ <text
246
+ x={n.pos.x}
247
+ y={n.pos.y + 4}
248
+ textAnchor="middle"
249
+ fontSize="10px"
250
+ fill="hsl(var(--foreground))"
251
+ className="pointer-events-none"
252
+ >
253
+ {n.label.length > 16 ? n.label.slice(0, 15) + "…" : n.label}
254
+ </text>
255
+ </g>
256
+ );
257
+ })}
258
+ </svg>
259
+
260
+ <div className="flex items-center gap-4 text-xs text-muted-foreground">
261
+ <span className="flex items-center gap-1.5">
262
+ <span className="inline-block h-0.5 w-6 bg-amber-500" /> merge
263
+ </span>
264
+ <span className="flex items-center gap-1.5">
265
+ <span className="inline-block h-0.5 w-6 border-t border-dashed border-green-500" />
266
+ split
267
+ </span>
268
+ <span>
269
+ {data.nodes.length} nodes · {data.edges.length} curation events
270
+ </span>
271
+ </div>
272
+ </div>
273
+ );
274
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * dashboard-client/src/tabs/WikiTab/TopicEvolutionView.tsx — planet evolution.
3
+ *
4
+ * Thin shell over TopicEvolutionGraph: fetches GET /api/wiki/evolution via
5
+ * useApi and refetches when a `wiki_rebuilt` SSE event lands (emitted by the
6
+ * extension after a topic-model rebuild), so the graph + scrubber stay live
7
+ * between polls. No data is fabricated — only the contract fields are used.
8
+ *
9
+ * Styling: Tailwind + shadcn (Card). No legacy CSS classes.
10
+ */
11
+
12
+ import type React from "react";
13
+ import { useCallback, useEffect, useMemo } from "react";
14
+ import { useApi } from "../../hooks/useApi";
15
+ import { useSSE } from "../../hooks/useSSE";
16
+ import { fetchTopicEvolution } from "../../api/client";
17
+ import type { TopicEvolutionResponse, SseEvent } from "@contracts";
18
+ import { Card, CardContent } from "../../components/ui/card";
19
+ import TopicEvolutionGraph from "./TopicEvolutionGraph";
20
+
21
+ function isWikiRebuilt(e: SseEvent): e is Extract<SseEvent, { type: "wiki_rebuilt" }> {
22
+ return e.type === "wiki_rebuilt";
23
+ }
24
+
25
+ export default function TopicEvolutionView(): React.ReactElement {
26
+ const { data, error, loading, refetch } = useApi<TopicEvolutionResponse>(
27
+ useCallback(() => fetchTopicEvolution(), []),
28
+ { pollInterval: 60_000 },
29
+ );
30
+
31
+ const { events } = useSSE();
32
+ const lastRebuiltTs = useMemo(() => {
33
+ const rebuilt = events.filter(isWikiRebuilt);
34
+ if (rebuilt.length === 0) return null;
35
+ return rebuilt[rebuilt.length - 1].ts;
36
+ }, [events]);
37
+
38
+ useEffect(() => {
39
+ if (lastRebuiltTs != null) refetch();
40
+ }, [lastRebuiltTs, refetch]);
41
+
42
+ if (error && !data) {
43
+ return (
44
+ <div className="rounded-lg border border-border bg-bg-card p-6 text-sm text-danger">
45
+ Error loading evolution graph: {error.message}
46
+ </div>
47
+ );
48
+ }
49
+ if (loading && !data) {
50
+ return (
51
+ <div className="rounded-lg border border-border bg-bg-card p-6 text-sm text-muted-foreground">
52
+ Loading evolution graph…
53
+ </div>
54
+ );
55
+ }
56
+
57
+ return (
58
+ <Card>
59
+ <CardContent>
60
+ <h3 className="mb-2 font-heading text-sm font-semibold text-muted-foreground">
61
+ Topic evolution
62
+ </h3>
63
+ {data ? (
64
+ <TopicEvolutionGraph data={data} />
65
+ ) : (
66
+ <p className="text-sm text-muted-foreground">No data yet.</p>
67
+ )}
68
+ </CardContent>
69
+ </Card>
70
+ );
71
+ }