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
@@ -0,0 +1,198 @@
1
+ /**
2
+ * SetupTab/SettingsPanel.tsx — comprehensive adjustable-settings panel.
3
+ *
4
+ * Renders every MEGACOMPACT_* setting from /api/rag-settings, grouped into
5
+ * collapsible categories. Each setting renders by type (boolean → Switch,
6
+ * number/string → styled input) and writes back via POST /api/rag-settings on
7
+ * change. Switches POST immediately; text/number inputs POST after a 500ms
8
+ * debounce. Requires-LLM settings show an amber badge when no LLM embedder is
9
+ * active. A restart-required toast appears after a successful write.
10
+ *
11
+ * PREVENT-PI-004: all data comes from localhost dashboard API endpoints.
12
+ */
13
+ import type React from "react";
14
+ import { useState, useEffect, useCallback, useRef } from "react";
15
+ import type { SettingsResponse, SettingState } from "@contracts";
16
+ import { fetchSettings, postSetting } from "../../api/client";
17
+ import { Switch } from "../../components/ui/switch";
18
+ import SettingsSection from "./SettingsSection";
19
+
20
+ const INPUT_CLASS =
21
+ "rounded-md border border-border bg-bg-elevated/50 px-2 py-1 text-sm outline-none transition-colors focus:border-primary disabled:cursor-not-allowed disabled:opacity-50";
22
+
23
+ function LlmBadge(): React.ReactElement {
24
+ return (
25
+ <span
26
+ className="ml-2 inline-flex items-center rounded-md border border-warning/40 bg-warning/15 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-warning"
27
+ title="Requires an LLM embedder (Ollama/HTTP) to be effective"
28
+ >
29
+ LLM
30
+ </span>
31
+ );
32
+ }
33
+
34
+ interface RowProps {
35
+ readonly setting: SettingState;
36
+ readonly id: string;
37
+ readonly llmActive: boolean;
38
+ readonly saving: boolean;
39
+ readonly onUpdate: (key: string, raw: string) => Promise<string | null>;
40
+ }
41
+
42
+ function SettingRow({
43
+ setting,
44
+ id,
45
+ llmActive,
46
+ saving,
47
+ onUpdate,
48
+ }: RowProps): React.ReactElement {
49
+ const [error, setError] = useState<string | null>(null);
50
+ const locked = setting.requiresLlm && !llmActive;
51
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
52
+
53
+ useEffect(() => {
54
+ return () => {
55
+ if (timerRef.current) clearTimeout(timerRef.current);
56
+ };
57
+ }, []);
58
+
59
+ const submit = useCallback(
60
+ (raw: string) => {
61
+ onUpdate(setting.key, raw)
62
+ .then((msg) => setError(msg))
63
+ .catch((e: unknown) =>
64
+ setError(e instanceof Error ? e.message : String(e)),
65
+ );
66
+ },
67
+ [onUpdate, setting.key],
68
+ );
69
+
70
+ const scheduleSave = useCallback(
71
+ (raw: string) => {
72
+ if (timerRef.current) clearTimeout(timerRef.current);
73
+ timerRef.current = setTimeout(() => submit(raw), 500);
74
+ },
75
+ [submit],
76
+ );
77
+
78
+ const inputDisabled = saving || locked;
79
+
80
+ return (
81
+ <div className="py-2.5">
82
+ <div className="flex items-start justify-between gap-3">
83
+ <div>
84
+ <div className="flex items-center text-sm font-semibold text-foreground">
85
+ <label htmlFor={`${id}-input`} className="cursor-pointer">
86
+ {setting.label}
87
+ </label>
88
+ {setting.requiresLlm && !llmActive && <LlmBadge />}
89
+ </div>
90
+ <div className="mt-0.5 text-xs text-muted-foreground">
91
+ {setting.description}
92
+ </div>
93
+ </div>
94
+ <div className="flex shrink-0 items-center gap-2">
95
+ {setting.type === "boolean" ? (
96
+ <Switch
97
+ id={`${id}-input`}
98
+ checked={Boolean(setting.value)}
99
+ disabled={inputDisabled || locked}
100
+ onCheckedChange={(checked) => submit(String(checked))}
101
+ aria-label={setting.label}
102
+ />
103
+ ) : (
104
+ <>
105
+ <input
106
+ id={`${id}-input`}
107
+ type={setting.type === "number" ? "number" : "text"}
108
+ defaultValue={String(setting.value ?? "")}
109
+ min={setting.min}
110
+ max={setting.max}
111
+ disabled={inputDisabled}
112
+ onChange={(e) => scheduleSave(e.target.value)}
113
+ className={`${INPUT_CLASS} ${setting.type === "number" ? "w-24" : "w-56"}`}
114
+ />
115
+ {setting.unit && (
116
+ <span className="text-xs text-muted-foreground">{setting.unit}</span>
117
+ )}
118
+ </>
119
+ )}
120
+ </div>
121
+ </div>
122
+ {error && <p className="mt-1 text-xs text-danger">Error: {error}</p>}
123
+ </div>
124
+ );
125
+ }
126
+
127
+ export default function SettingsPanel(): React.ReactElement {
128
+ const [settings, setSettings] = useState<SettingsResponse | null>(null);
129
+ const [error, setError] = useState<string | null>(null);
130
+ const [saving, setSaving] = useState(false);
131
+ const [toast, setToast] = useState<string | null>(null);
132
+
133
+ const load = useCallback(() => {
134
+ fetchSettings()
135
+ .then(setSettings)
136
+ .catch((e: unknown) =>
137
+ setError(e instanceof Error ? e.message : String(e)),
138
+ );
139
+ }, []);
140
+
141
+ useEffect(() => {
142
+ load();
143
+ }, [load]);
144
+
145
+ const handleUpdate = useCallback(
146
+ async (key: string, raw: string): Promise<string | null> => {
147
+ setSaving(true);
148
+ try {
149
+ const res = await postSetting({ key, value: raw });
150
+ setToast(
151
+ res.restartRequired
152
+ ? "Saved — restart pi to apply changes."
153
+ : "Saved.",
154
+ );
155
+ // Refresh to pick up the server's normalized value (and to keep the
156
+ // controlled Switch in sync with the persisted state).
157
+ load();
158
+ return null;
159
+ } catch (e) {
160
+ return e instanceof Error ? e.message : String(e);
161
+ } finally {
162
+ setSaving(false);
163
+ }
164
+ },
165
+ [load],
166
+ );
167
+
168
+ return (
169
+ <div className="flex flex-col gap-3">
170
+ {error && <p className="text-sm text-danger">Error: {error}</p>}
171
+ {!settings && !error && (
172
+ <p className="text-sm text-muted-foreground">Loading settings...</p>
173
+ )}
174
+ {settings?.categories.map((cat) => (
175
+ <SettingsSection key={cat.name} title={cat.name}>
176
+ {cat.settings.map((setting) => (
177
+ <SettingRow
178
+ key={setting.key}
179
+ setting={setting}
180
+ id={`${cat.name.replace(/[^a-z0-9]+/gi, "-")}-${setting.key}`}
181
+ llmActive={settings.llmActive}
182
+ saving={saving}
183
+ onUpdate={handleUpdate}
184
+ />
185
+ ))}
186
+ </SettingsSection>
187
+ ))}
188
+ {toast && (
189
+ <div
190
+ className="mt-1 rounded-md border border-warning/40 bg-warning/10 p-2 text-xs text-warning"
191
+ role="status"
192
+ >
193
+ {toast}
194
+ </div>
195
+ )}
196
+ </div>
197
+ );
198
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * SetupTab/SettingsSection.tsx — collapsible settings category section.
3
+ *
4
+ * Each category from /api/rag-settings renders as a <section> with a toggle
5
+ * button (aria-expanded + aria-controls) that shows/hides its setting rows.
6
+ * Matches the Sidebar collapsible-accessibility pattern.
7
+ */
8
+ import type React from "react";
9
+ import { useState } from "react";
10
+ import { ChevronDown } from "lucide-react";
11
+ import { cn } from "../../utils/cn";
12
+
13
+ interface SettingsSectionProps {
14
+ readonly title: string;
15
+ readonly children: React.ReactNode;
16
+ }
17
+
18
+ export default function SettingsSection({
19
+ title,
20
+ children,
21
+ }: SettingsSectionProps): React.ReactElement {
22
+ const [open, setOpen] = useState<boolean>(true);
23
+ const panelId = `settings-${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
24
+
25
+ return (
26
+ <section className="overflow-hidden rounded-xl border border-border bg-bg-elevated/40">
27
+ <button
28
+ type="button"
29
+ onClick={() => setOpen((o) => !o)}
30
+ className="flex w-full items-center justify-between rounded-t-xl bg-bg-elevated/60 px-4 py-3 text-left text-sm font-semibold text-foreground transition-colors hover:bg-bg-elevated"
31
+ aria-expanded={open}
32
+ aria-controls={panelId}
33
+ >
34
+ <span>{title}</span>
35
+ <ChevronDown
36
+ className={cn(
37
+ "h-4 w-4 text-muted-foreground transition-transform",
38
+ open && "rotate-180",
39
+ )}
40
+ aria-hidden="true"
41
+ />
42
+ </button>
43
+ {open && (
44
+ <div id={panelId} className="divide-y divide-border px-4 py-1">
45
+ {children}
46
+ </div>
47
+ )}
48
+ </section>
49
+ );
50
+ }
@@ -13,73 +13,35 @@ import GameTab from "./GameTab";
13
13
  import AchievementsTab from "./AchievementsTab";
14
14
  import ConfigTab from "./ConfigTab";
15
15
  import EmbedderSetup from "./SetupTab/EmbedderSetup";
16
-
17
- const containerStyle: React.CSSProperties = {
18
- padding: "1rem",
19
- maxWidth: "720px",
20
- margin: "0 auto",
21
- fontFamily: "system-ui, sans-serif",
22
- color: "#e0e0e0",
23
- };
16
+ import { Toggle } from "../components/ui/toggle";
24
17
 
25
18
  type SetupSubTab = "embedder" | "config" | "game" | "achievements";
26
19
 
27
- const subTabNavStyle: React.CSSProperties = {
28
- display: "flex",
29
- gap: "0.5rem",
30
- marginBottom: "1rem",
31
- borderBottom: "1px solid #2a2a4e",
32
- paddingBottom: "0.5rem",
33
- };
34
-
35
- function subTabBtnStyle(active: boolean): React.CSSProperties {
36
- return {
37
- background: active ? "#3a5a9f" : "transparent",
38
- color: active ? "#fff" : "#a0a0c0",
39
- border: "1px solid",
40
- borderColor: active ? "#3a5a9f" : "#2a2a4e",
41
- borderRadius: "6px",
42
- padding: "0.4rem 0.8rem",
43
- cursor: "pointer",
44
- fontSize: "0.9rem",
45
- fontWeight: active ? 600 : 400,
46
- };
47
- }
20
+ const SUB_TABS: ReadonlyArray<{ id: SetupSubTab; label: string }> = [
21
+ { id: "embedder", label: "Embedder" },
22
+ { id: "config", label: "Config" },
23
+ { id: "game", label: "Game Mode" },
24
+ { id: "achievements", label: "Achievements" },
25
+ ];
48
26
 
49
27
  export default function SetupTab(): React.ReactElement {
50
28
  const [subTab, setSubTab] = useState<SetupSubTab>("embedder");
51
29
 
52
30
  return (
53
- <div style={containerStyle}>
54
- <nav style={subTabNavStyle} aria-label="Setup sections">
55
- <button
56
- type="button"
57
- style={subTabBtnStyle(subTab === "embedder")}
58
- onClick={() => setSubTab("embedder")}
59
- >
60
- Embedder
61
- </button>
62
- <button
63
- type="button"
64
- style={subTabBtnStyle(subTab === "config")}
65
- onClick={() => setSubTab("config")}
66
- >
67
- Config
68
- </button>
69
- <button
70
- type="button"
71
- style={subTabBtnStyle(subTab === "game")}
72
- onClick={() => setSubTab("game")}
73
- >
74
- Game Mode
75
- </button>
76
- <button
77
- type="button"
78
- style={subTabBtnStyle(subTab === "achievements")}
79
- onClick={() => setSubTab("achievements")}
80
- >
81
- Achievements
82
- </button>
31
+ <div className="mx-auto flex max-w-3xl flex-col gap-4 p-4">
32
+ <nav
33
+ className="flex gap-2 border-b border-border pb-2"
34
+ aria-label="Setup sections"
35
+ >
36
+ {SUB_TABS.map((t) => (
37
+ <Toggle
38
+ key={t.id}
39
+ pressed={subTab === t.id}
40
+ onClick={() => setSubTab(t.id)}
41
+ >
42
+ {t.label}
43
+ </Toggle>
44
+ ))}
83
45
  </nav>
84
46
 
85
47
  {subTab === "embedder" && <EmbedderSetup />}
@@ -12,6 +12,8 @@ import { useCallback, useMemo, useState } from "react";
12
12
  import { useApi } from "../hooks/useApi";
13
13
  import { fetchTopics, fetchTopicMemories } from "../api/client";
14
14
  import type { TopicsResponse, TopicMemoriesResponse } from "@contracts";
15
+ import { Badge } from "../components/ui/badge";
16
+ import { Card } from "../components/ui/card";
15
17
 
16
18
  function fmtTs(ms: number | null): string {
17
19
  if (!ms) return "—";
@@ -48,33 +50,45 @@ export default function TopicsTab(): React.ReactElement {
48
50
 
49
51
  if (error && !data) {
50
52
  return (
51
- <div className="tab-stub">Error loading topics: {error.message}</div>
53
+ <div className="rounded-lg border border-border bg-bg-card p-4 text-sm text-foreground">
54
+ Error loading topics: {error.message}
55
+ </div>
52
56
  );
53
57
  }
54
58
  if (loading && !data) {
55
- return <div className="tab-stub">Loading topics…</div>;
59
+ return (
60
+ <div className="rounded-lg border border-border bg-bg-card p-4 text-sm text-muted-foreground">
61
+ Loading topics…
62
+ </div>
63
+ );
56
64
  }
57
65
  if (!data) {
58
- return <div className="tab-stub">No topic data available.</div>;
66
+ return (
67
+ <div className="rounded-lg border border-border bg-bg-card p-4 text-sm text-muted-foreground">
68
+ No topic data available.
69
+ </div>
70
+ );
59
71
  }
60
72
 
61
73
  if (data.totalTopics === 0) {
62
74
  return (
63
- <div className="tab-stub">
64
- <h3>Wiki</h3>
65
- <p>
75
+ <Card className="p-4">
76
+ <h3 className="font-heading text-lg font-semibold">Wiki</h3>
77
+ <p className="mt-2 text-sm">
66
78
  No topics yet. Topics are auto-generated after every 3rd compaction
67
79
  from real memory embeddings (k-means + TF-IDF).
68
80
  </p>
69
- <p>Check back after a few more compaction cycles.</p>
70
- </div>
81
+ <p className="mt-2 text-sm text-muted-foreground">
82
+ Check back after a few more compaction cycles.
83
+ </p>
84
+ </Card>
71
85
  );
72
86
  }
73
87
 
74
88
  return (
75
- <div className="tab-content topics-tab">
76
- <h3>Wiki</h3>
77
- <p className="subtitle">
89
+ <div className="flex flex-col gap-4">
90
+ <h3 className="font-heading text-lg font-semibold">Wiki</h3>
91
+ <p className="text-sm text-muted-foreground">
78
92
  {data.totalTopics} topic{data.totalTopics !== 1 ? "s" : ""} ·{" "}
79
93
  {data.totalAssigned} assigned memori
80
94
  {data.totalAssigned !== 1 ? "es" : "y"}
@@ -85,87 +99,105 @@ export default function TopicsTab(): React.ReactElement {
85
99
 
86
100
  <input
87
101
  type="search"
88
- className="topics-search"
102
+ className="mb-1 w-full max-w-sm rounded-md border border-border bg-bg-elevated/50 px-3 py-2 text-sm outline-none transition-colors focus:border-primary"
89
103
  placeholder="Filter topics by label or term…"
90
104
  value={query}
91
105
  onChange={(e) => setQuery(e.target.value)}
92
106
  />
93
107
 
94
108
  {drillTopicId && (
95
- <section className="topic-drilldown">
96
- <h4>
109
+ <Card>
110
+ <h4 className="flex items-center gap-2 font-heading text-base font-semibold">
97
111
  <button
98
112
  type="button"
99
- className="link-btn"
113
+ className="text-sm text-muted-foreground transition-colors hover:text-foreground"
100
114
  onClick={() => setDrillTopicId(null)}
101
115
  >
102
116
  ← back
103
- </button>{" "}
117
+ </button>
104
118
  Topic <code>{drillTopicId}</code>
105
119
  {drill.data && <> · {drill.data.label}</>}
106
120
  {drill.data && <> · {drill.data.assignments.length} memories</>}
107
121
  </h4>
108
122
  {drill.error ? (
109
- <div className="tab-stub">
123
+ <div className="mt-2 text-sm text-foreground">
110
124
  Error: {(drill.error as Error).message}
111
125
  </div>
112
126
  ) : drill.loading && !drill.data ? (
113
- <div className="tab-stub">Loading memories…</div>
127
+ <div className="mt-2 text-sm text-muted-foreground">
128
+ Loading memories…
129
+ </div>
114
130
  ) : drill.data ? (
115
- <table className="data-table">
131
+ <table className="mt-2 w-full border-collapse text-sm">
116
132
  <thead>
117
133
  <tr>
118
- <th>Memory id</th>
119
- <th>Confidence</th>
120
- <th>Assigned</th>
134
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">
135
+ Memory id
136
+ </th>
137
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">
138
+ Confidence
139
+ </th>
140
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">
141
+ Assigned
142
+ </th>
121
143
  </tr>
122
144
  </thead>
123
145
  <tbody>
124
146
  {drill.data.assignments.map((a) => (
125
- <tr key={a.memoryId}>
126
- <td>
147
+ <tr key={a.memoryId} className="border-b border-border/50">
148
+ <td className="px-3 py-2">
127
149
  <code>{a.memoryId}</code>
128
150
  </td>
129
- <td>
151
+ <td className="px-3 py-2">
130
152
  {a.confidence != null ? a.confidence.toFixed(2) : "—"}
131
153
  </td>
132
- <td>{fmtTs(a.assignedAt)}</td>
154
+ <td className="px-3 py-2">{fmtTs(a.assignedAt)}</td>
133
155
  </tr>
134
156
  ))}
135
157
  </tbody>
136
158
  </table>
137
159
  ) : null}
138
- </section>
160
+ </Card>
139
161
  )}
140
162
 
141
163
  {!drillTopicId && (
142
- <table className="data-table">
164
+ <table className="w-full border-collapse text-sm">
143
165
  <thead>
144
166
  <tr>
145
- <th>Label</th>
146
- <th>Memories</th>
147
- <th>Top Terms</th>
148
- <th></th>
167
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">
168
+ Label
169
+ </th>
170
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">
171
+ Memories
172
+ </th>
173
+ <th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">
174
+ Top Terms
175
+ </th>
176
+ <th className="border-b border-border px-3 py-2 text-right font-medium text-muted-foreground"></th>
149
177
  </tr>
150
178
  </thead>
151
179
  <tbody>
152
180
  {filtered.map((t) => (
153
181
  <tr
154
182
  key={t.id}
155
- className="topic-row"
183
+ className="cursor-pointer border-b border-border/50 transition-colors hover:bg-bg-elevated/40"
156
184
  onClick={() => setDrillTopicId(t.id)}
157
- style={{ cursor: "pointer" }}
158
185
  >
159
- <td className="topic-label">{t.label}</td>
160
- <td className="topic-count">{t.memoryCount}</td>
161
- <td className="topic-terms">
186
+ <td className="px-3 py-2 font-semibold">{t.label}</td>
187
+ <td className="px-3 py-2">
188
+ <Badge variant="accent">{t.memoryCount}</Badge>
189
+ </td>
190
+ <td className="px-3 py-2 text-muted-foreground">
162
191
  {t.termScore
163
192
  .slice(0, 8)
164
193
  .map((s) => s.term)
165
194
  .join(", ")}
166
195
  </td>
167
- <td>
168
- <button type="button" className="mini-btn">
196
+ <td className="px-3 py-2 text-right">
197
+ <button
198
+ type="button"
199
+ className="rounded-md border border-border px-2 py-1 text-xs text-muted-foreground transition-colors hover:bg-bg-elevated/40 hover:text-foreground"
200
+ >
169
201
  drill
170
202
  </button>
171
203
  </td>
@@ -173,7 +205,10 @@ export default function TopicsTab(): React.ReactElement {
173
205
  ))}
174
206
  {filtered.length === 0 && (
175
207
  <tr>
176
- <td colSpan={4} className="muted">
208
+ <td
209
+ colSpan={4}
210
+ className="px-3 py-2 text-sm text-muted-foreground"
211
+ >
177
212
  No topics match “{query}”.
178
213
  </td>
179
214
  </tr>