pi-mega-compact 0.20.58 → 0.20.59
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/config/vector-cortex-dash-0b.js +21 -0
- package/dist/config/vector-cortex.js +2 -0
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
- package/dist/src/config/vector-cortex-dash-0b.js +21 -0
- package/dist/src/config/vector-cortex.js +2 -0
- package/dist/src/config.js +1 -1
- package/extensions/dashboard-client/dist/assets/{AreaChart-CtJ6dSMT.js → AreaChart-GTwQxOAm.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-CtJ6dSMT.js.map → AreaChart-GTwQxOAm.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-DJ3j3xDz.js → BarChart-BFqUD5L4.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-DJ3j3xDz.js.map → BarChart-BFqUD5L4.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-CXU1_9lR.js → CacheTab-BLM6_PQQ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-CXU1_9lR.js.map → CacheTab-BLM6_PQQ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-DF-Q3o7x.js → EventsTab-BYLh6cud.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-DF-Q3o7x.js.map → EventsTab-BYLh6cud.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-Dr1HGOkl.js → HealthTab-CkJXEbjn.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-Dr1HGOkl.js.map → HealthTab-CkJXEbjn.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/HydeDetailPanel-Dt9T7kc5.js +5 -0
- package/extensions/dashboard-client/dist/assets/HydeDetailPanel-Dt9T7kc5.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-zKhR18_s.js → MaintenanceTab-Lm3v0nJO.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-zKhR18_s.js.map → MaintenanceTab-Lm3v0nJO.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DekIzfn2.js → MemoryMapTab-C8AeBIQi.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DekIzfn2.js.map → MemoryMapTab-C8AeBIQi.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CEKajY38.js → MetricsTab-m410T4vk.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CEKajY38.js.map → MetricsTab-m410T4vk.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-24sG8xiE.js → OverviewTab-BfDfMFXx.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-24sG8xiE.js.map → OverviewTab-BfDfMFXx.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-70gkV8l9.js → ReposTab-CsFxxqJ6.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-70gkV8l9.js.map → ReposTab-CsFxxqJ6.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/SessionsTab-DstDOzwu.js +2 -0
- package/extensions/dashboard-client/dist/assets/SessionsTab-DstDOzwu.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{SetupTab-tbPrrfYx.js → SetupTab-DVT4PrH0.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-tbPrrfYx.js.map → SetupTab-DVT4PrH0.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B6mdtCWE.js → TimeSavedCard-BkRzjyRm.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B6mdtCWE.js.map → TimeSavedCard-BkRzjyRm.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/TurnsTab-D5QTnv52.js +2 -0
- package/extensions/dashboard-client/dist/assets/TurnsTab-D5QTnv52.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{VcStatusBadge-SDThp0R0.js → VcStatusBadge-B8B8RStV.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{VcStatusBadge-SDThp0R0.js.map → VcStatusBadge-B8B8RStV.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-CrGANNd_.js +2 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-CrGANNd_.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-Cki48_8N.js → WikiTab-DyD0yGzI.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-Cki48_8N.js.map → WikiTab-DyD0yGzI.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-CojFYd7q.js → button-Bynl3ONj.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-CojFYd7q.js.map → button-Bynl3ONj.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-Cxeeje9o.js → card-Daf3pQIu.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-Cxeeje9o.js.map → card-Daf3pQIu.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{client-extra-DLxyGxW8.js → client-extra-DqAYZBW1.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{client-extra-DLxyGxW8.js.map → client-extra-DqAYZBW1.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DRlqBks2.js → generateCategoricalChart-B-fe5jjr.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DRlqBks2.js.map → generateCategoricalChart-B-fe5jjr.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-CkbdlPBm.js → index-Bjvv23bp.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-CkbdlPBm.js.map → index-Bjvv23bp.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{switch-Cx_nl-Ba.js → switch-CivyERPS.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-Cx_nl-Ba.js.map → switch-CivyERPS.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-CfIFzp6P.js → toggle-qmdkrAgF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-CfIFzp6P.js.map → toggle-qmdkrAgF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-B9_N7ccG.js → useSSE-uNuduyGe.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-B9_N7ccG.js.map → useSSE-uNuduyGe.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/tabs/SessionsTab/TurnMemoryView.tsx +383 -0
- package/extensions/dashboard-client/src/tabs/SessionsTab.tsx +77 -6
- package/extensions/dashboard-client/src/tabs/VectorCortexTab/sections.tsx +201 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +168 -122
- package/extensions/dashboard-client/src/tabs/index.ts +30 -0
- package/extensions/dashboard-client/src/tabs/registry.ts +17 -0
- package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
- package/package.json +1 -1
- package/src/config/vector-cortex-dash-0b.ts +23 -0
- package/src/config/vector-cortex.ts +2 -0
- package/src/config.ts +1 -0
- package/extensions/dashboard-client/dist/assets/SessionsTab-B3quvYPZ.js +0 -2
- package/extensions/dashboard-client/dist/assets/SessionsTab-B3quvYPZ.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/TurnsTab-CmhRJJqr.js +0 -5
- package/extensions/dashboard-client/dist/assets/TurnsTab-CmhRJJqr.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-DDjCkTjk.js +0 -2
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-DDjCkTjk.js.map +0 -1
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/tabs/SessionsTab/TurnMemoryView.tsx — Turn-by-turn memory
|
|
3
|
+
* tracking + recall (S52), moved VERBATIM from the top-level TurnsTab.tsx.
|
|
4
|
+
*
|
|
5
|
+
* DASH-0b: the Sessions surface absorbs the existent TurnsTab as a drill-down.
|
|
6
|
+
* This file holds the byte-preserved turns body (the `TurnMemoryView`
|
|
7
|
+
* component). The standalone `tabs/TurnsTab.tsx` is LEFT UNTOUCHED this sprint —
|
|
8
|
+
* its copy stays live for flag-off / pre-DASH parity. A DASH-0d cleanup deletes
|
|
9
|
+
* the standalone copy after a deep-link audit proves no live consumer points
|
|
10
|
+
* at it. Only the export name changed (TurnsTab → TurnMemoryView); the render
|
|
11
|
+
* body is otherwise identical.
|
|
12
|
+
*
|
|
13
|
+
* Fetches /api/turns (conversation list), expands the active conversation into
|
|
14
|
+
* per-turn detail (/api/turns/conversation/:id), and renders:
|
|
15
|
+
* - Conversation summary list (turn count, recall total, epochs, last turn).
|
|
16
|
+
* - Per-turn rows: turn index, role, ctx tokens/percent + pressure band,
|
|
17
|
+
* epoch id, and the injected-checkpoint recall set (the memory recalled
|
|
18
|
+
* at each turn, with score + source).
|
|
19
|
+
* - Pending rewind intents (S52A) + Fork action (S50C primitive).
|
|
20
|
+
*
|
|
21
|
+
* Read-mostly via /api/turns; fork/intent/prune via POST. No absolute URLs
|
|
22
|
+
* (PREVENT-PI-004: loopback-only, same-origin static bundle).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type React from "react";
|
|
26
|
+
import { useCallback, useState } from "react";
|
|
27
|
+
import { useApi } from "../../hooks/useApi";
|
|
28
|
+
import {
|
|
29
|
+
fetchTurns,
|
|
30
|
+
fetchConversationTurns,
|
|
31
|
+
fetchTurnIntents,
|
|
32
|
+
postFork,
|
|
33
|
+
postTurnIntent,
|
|
34
|
+
} from "../../api/client";
|
|
35
|
+
import type {
|
|
36
|
+
TurnsResponse,
|
|
37
|
+
ConversationTurnsResponse,
|
|
38
|
+
RewindIntentsResponse,
|
|
39
|
+
TurnRow,
|
|
40
|
+
ConversationSummary,
|
|
41
|
+
} from "@contracts";
|
|
42
|
+
import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card";
|
|
43
|
+
import { Badge } from "../../components/ui/badge";
|
|
44
|
+
import { Button } from "../../components/ui/button";
|
|
45
|
+
import { HydeDetailPanel } from "../../components/HydeDetailPanel";
|
|
46
|
+
|
|
47
|
+
function fmtTs(ms: number): string {
|
|
48
|
+
if (!ms) return "—";
|
|
49
|
+
return new Date(ms).toLocaleString();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function bandVariant(band: TurnRow["pressureBand"]): "success" | "warning" | "danger" | "default" {
|
|
53
|
+
if (band === "green") return "success";
|
|
54
|
+
if (band === "yellow") return "warning";
|
|
55
|
+
if (band === "red") return "danger";
|
|
56
|
+
return "default";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function sourceLabel(s: TurnRow["recall"][number]["source"]): string {
|
|
60
|
+
if (s === "checkpoint") return "ckpt";
|
|
61
|
+
if (s === "cluster_summary") return "raptor";
|
|
62
|
+
return "mem";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function TurnMemoryView(): React.ReactElement {
|
|
66
|
+
const {
|
|
67
|
+
data: turns,
|
|
68
|
+
loading,
|
|
69
|
+
error,
|
|
70
|
+
} = useApi<TurnsResponse>(
|
|
71
|
+
useCallback(() => fetchTurns(), []),
|
|
72
|
+
{
|
|
73
|
+
pollInterval: 10_000,
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
const { data: intents } = useApi<RewindIntentsResponse>(
|
|
77
|
+
useCallback(() => fetchTurnIntents(), []),
|
|
78
|
+
{ pollInterval: 5_000 },
|
|
79
|
+
);
|
|
80
|
+
const [expanded, setExpanded] = useState<string | null>(null);
|
|
81
|
+
const [detail, setDetail] = useState<ConversationTurnsResponse | null>(null);
|
|
82
|
+
const [detailLoading, setDetailLoading] = useState(false);
|
|
83
|
+
const [busy, setBusy] = useState<string | null>(null);
|
|
84
|
+
const [notice, setNotice] = useState<string | null>(null);
|
|
85
|
+
|
|
86
|
+
const expand = useCallback(
|
|
87
|
+
async (conversationId: string) => {
|
|
88
|
+
if (expanded === conversationId) {
|
|
89
|
+
setExpanded(null);
|
|
90
|
+
setDetail(null);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
setExpanded(conversationId);
|
|
94
|
+
setDetailLoading(true);
|
|
95
|
+
setDetail(null);
|
|
96
|
+
try {
|
|
97
|
+
const d = await fetchConversationTurns(conversationId);
|
|
98
|
+
setDetail(d);
|
|
99
|
+
} finally {
|
|
100
|
+
setDetailLoading(false);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
[expanded],
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const onFork = useCallback(
|
|
107
|
+
async (conversationId: string, turnIndex: number) => {
|
|
108
|
+
setBusy(`fork:${conversationId}:${turnIndex}`);
|
|
109
|
+
setNotice(null);
|
|
110
|
+
try {
|
|
111
|
+
const out = await postFork(conversationId, turnIndex);
|
|
112
|
+
setNotice(
|
|
113
|
+
`Forked → ${out.childConversationId} (${out.recalledCount} recalled)`,
|
|
114
|
+
);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
setNotice(`Fork failed: ${(e as Error).message}`);
|
|
117
|
+
} finally {
|
|
118
|
+
setBusy(null);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
[],
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const onRewind = useCallback(
|
|
125
|
+
async (conversationId: string, turnIndex: number) => {
|
|
126
|
+
setBusy(`rewind:${conversationId}:${turnIndex}`);
|
|
127
|
+
setNotice(null);
|
|
128
|
+
try {
|
|
129
|
+
await postTurnIntent(conversationId, turnIndex);
|
|
130
|
+
setNotice(`Rewind intent queued for turn ${turnIndex}`);
|
|
131
|
+
} catch (e) {
|
|
132
|
+
setNotice(`Rewind failed: ${(e as Error).message}`);
|
|
133
|
+
} finally {
|
|
134
|
+
setBusy(null);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
[],
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (error && !turns) {
|
|
141
|
+
return <div className="tab-stub">Error loading turns: {error.message}</div>;
|
|
142
|
+
}
|
|
143
|
+
if (loading && !turns) {
|
|
144
|
+
return <div className="tab-stub">Loading turns…</div>;
|
|
145
|
+
}
|
|
146
|
+
if (!turns || turns.conversations.length === 0) {
|
|
147
|
+
return (
|
|
148
|
+
<div className="tab-stub">
|
|
149
|
+
<h3>Turn-by-turn memory</h3>
|
|
150
|
+
<p>
|
|
151
|
+
No turns recorded yet. Per-turn tracking starts when{" "}
|
|
152
|
+
<code>MEGACOMPACT_TURNS_DB=1</code> and the session runs through a
|
|
153
|
+
compaction. Each turn records its context metrics + the checkpoints
|
|
154
|
+
recalled into it.
|
|
155
|
+
</p>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<div className="flex flex-col gap-4">
|
|
162
|
+
<h3 className="font-heading text-lg font-semibold">
|
|
163
|
+
Turn-by-turn memory tracking + recall
|
|
164
|
+
</h3>
|
|
165
|
+
{notice && <Badge variant="warning">{notice}</Badge>}
|
|
166
|
+
|
|
167
|
+
{intents && intents.intents.length > 0 && (
|
|
168
|
+
<Card>
|
|
169
|
+
<CardHeader>
|
|
170
|
+
<CardTitle>
|
|
171
|
+
Pending rewind intents ({intents.intents.length})
|
|
172
|
+
</CardTitle>
|
|
173
|
+
</CardHeader>
|
|
174
|
+
<CardContent>
|
|
175
|
+
<ul className="list-disc space-y-1 pl-5">
|
|
176
|
+
{intents.intents.map((i) => (
|
|
177
|
+
<li key={i.id}>
|
|
178
|
+
{fmtTs(i.createdAt)} — rewind{" "}
|
|
179
|
+
<code>{i.conversationId}</code> to turn {i.targetTurnIndex}
|
|
180
|
+
</li>
|
|
181
|
+
))}
|
|
182
|
+
</ul>
|
|
183
|
+
</CardContent>
|
|
184
|
+
</Card>
|
|
185
|
+
)}
|
|
186
|
+
|
|
187
|
+
<Card>
|
|
188
|
+
<CardHeader>
|
|
189
|
+
<CardTitle>
|
|
190
|
+
Conversations ({turns.conversations.length})
|
|
191
|
+
</CardTitle>
|
|
192
|
+
</CardHeader>
|
|
193
|
+
<CardContent>
|
|
194
|
+
<table className="w-full border-collapse text-sm">
|
|
195
|
+
<thead>
|
|
196
|
+
<tr>
|
|
197
|
+
<th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Conversation</th>
|
|
198
|
+
<th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Turns</th>
|
|
199
|
+
<th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Recall</th>
|
|
200
|
+
<th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Epochs</th>
|
|
201
|
+
<th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Avg ctx%</th>
|
|
202
|
+
<th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Last turn</th>
|
|
203
|
+
<th className="border-b border-border px-3 py-2"></th>
|
|
204
|
+
</tr>
|
|
205
|
+
</thead>
|
|
206
|
+
<tbody>
|
|
207
|
+
{turns.conversations.map((c: ConversationSummary) => (
|
|
208
|
+
<ConversationRow
|
|
209
|
+
key={c.conversationId}
|
|
210
|
+
c={c}
|
|
211
|
+
active={turns.activeConversationId === c.conversationId}
|
|
212
|
+
expanded={expanded === c.conversationId}
|
|
213
|
+
onExpand={() => expand(c.conversationId)}
|
|
214
|
+
/>
|
|
215
|
+
))}
|
|
216
|
+
</tbody>
|
|
217
|
+
</table>
|
|
218
|
+
</CardContent>
|
|
219
|
+
</Card>
|
|
220
|
+
|
|
221
|
+
{expanded && (
|
|
222
|
+
<Card>
|
|
223
|
+
<CardHeader>
|
|
224
|
+
<CardTitle>
|
|
225
|
+
Turns in <code>{expanded}</code>
|
|
226
|
+
</CardTitle>
|
|
227
|
+
</CardHeader>
|
|
228
|
+
<CardContent>
|
|
229
|
+
{detailLoading ? (
|
|
230
|
+
<div className="text-sm text-muted-foreground">Loading turns…</div>
|
|
231
|
+
) : detail ? (
|
|
232
|
+
<TurnDetail
|
|
233
|
+
detail={detail}
|
|
234
|
+
busy={busy}
|
|
235
|
+
onFork={onFork}
|
|
236
|
+
onRewind={onRewind}
|
|
237
|
+
/>
|
|
238
|
+
) : (
|
|
239
|
+
<div className="text-sm text-muted-foreground">No detail.</div>
|
|
240
|
+
)}
|
|
241
|
+
</CardContent>
|
|
242
|
+
</Card>
|
|
243
|
+
)}
|
|
244
|
+
</div>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function ConversationRow({
|
|
249
|
+
c,
|
|
250
|
+
active,
|
|
251
|
+
expanded,
|
|
252
|
+
onExpand,
|
|
253
|
+
}: {
|
|
254
|
+
c: ConversationSummary;
|
|
255
|
+
active: boolean;
|
|
256
|
+
expanded: boolean;
|
|
257
|
+
onExpand: () => void;
|
|
258
|
+
}): React.ReactElement {
|
|
259
|
+
return (
|
|
260
|
+
<>
|
|
261
|
+
<tr
|
|
262
|
+
className={`border-b border-border/50 hover:bg-bg-elevated/40 ${
|
|
263
|
+
active ? "bg-primary/10" : ""
|
|
264
|
+
}`}
|
|
265
|
+
onClick={onExpand}
|
|
266
|
+
style={{ cursor: "pointer" }}
|
|
267
|
+
>
|
|
268
|
+
<td className="px-3 py-2">
|
|
269
|
+
{expanded ? "▾" : "▸"} <code>{c.conversationId}</code>
|
|
270
|
+
{active && <Badge variant="accent" className="ml-2">active</Badge>}
|
|
271
|
+
</td>
|
|
272
|
+
<td className="px-3 py-2 text-right">{c.turnCount}</td>
|
|
273
|
+
<td className="px-3 py-2 text-right">{c.totalRecall}</td>
|
|
274
|
+
<td className="px-3 py-2 text-right">{c.epochCount}</td>
|
|
275
|
+
<td className="px-3 py-2 text-right">{c.avgCtxPercent.toFixed(0)}%</td>
|
|
276
|
+
<td className="px-3 py-2">{fmtTs(c.lastTurnAt)}</td>
|
|
277
|
+
<td className="px-3 py-2" />
|
|
278
|
+
</tr>
|
|
279
|
+
</>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function TurnDetail({
|
|
284
|
+
detail,
|
|
285
|
+
busy,
|
|
286
|
+
onFork,
|
|
287
|
+
onRewind,
|
|
288
|
+
}: {
|
|
289
|
+
detail: ConversationTurnsResponse;
|
|
290
|
+
busy: string | null;
|
|
291
|
+
onFork: (conversationId: string, turnIndex: number) => void;
|
|
292
|
+
onRewind: (conversationId: string, turnIndex: number) => void;
|
|
293
|
+
}): React.ReactElement {
|
|
294
|
+
return (
|
|
295
|
+
<table className="w-full border-collapse text-sm">
|
|
296
|
+
<thead>
|
|
297
|
+
<tr>
|
|
298
|
+
<th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">#</th>
|
|
299
|
+
<th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Role</th>
|
|
300
|
+
<th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Ctx</th>
|
|
301
|
+
<th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Band</th>
|
|
302
|
+
<th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Epoch</th>
|
|
303
|
+
<th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Recalled checkpoints (memory recall)</th>
|
|
304
|
+
<th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">HyDE</th>
|
|
305
|
+
<th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Ended</th>
|
|
306
|
+
<th className="border-b border-border px-3 py-2 text-left font-medium text-muted-foreground">Actions</th>
|
|
307
|
+
</tr>
|
|
308
|
+
</thead>
|
|
309
|
+
<tbody>
|
|
310
|
+
{detail.turns.map((t) => (
|
|
311
|
+
<tr
|
|
312
|
+
key={`${t.conversationId}:${t.turnIndex}`}
|
|
313
|
+
className="border-b border-border/50 hover:bg-bg-elevated/40"
|
|
314
|
+
>
|
|
315
|
+
<td className="px-3 py-2 text-right">{t.turnIndex}</td>
|
|
316
|
+
<td className="px-3 py-2">{t.role}</td>
|
|
317
|
+
<td className="px-3 py-2">
|
|
318
|
+
{t.ctxTokens ?? "—"}
|
|
319
|
+
{t.ctxPercent != null && (
|
|
320
|
+
<span className="text-muted-foreground">
|
|
321
|
+
{" "}
|
|
322
|
+
· {t.ctxPercent.toFixed(0)}%
|
|
323
|
+
</span>
|
|
324
|
+
)}
|
|
325
|
+
</td>
|
|
326
|
+
<td className="px-3 py-2">
|
|
327
|
+
<Badge variant={bandVariant(t.pressureBand)}>
|
|
328
|
+
{t.pressureBand ?? "—"}
|
|
329
|
+
</Badge>
|
|
330
|
+
</td>
|
|
331
|
+
<td className="px-3 py-2">{t.epochId ? <code>{t.epochId.slice(0, 8)}</code> : "—"}</td>
|
|
332
|
+
<td className="px-3 py-2">
|
|
333
|
+
{t.recall.length === 0 ? (
|
|
334
|
+
<span className="text-muted-foreground">—</span>
|
|
335
|
+
) : (
|
|
336
|
+
<ul className="space-y-1">
|
|
337
|
+
{t.recall.map((r) => (
|
|
338
|
+
<li key={r.checkpointId}>
|
|
339
|
+
<code>{r.checkpointId.slice(0, 12)}</code>{" "}
|
|
340
|
+
<span className="text-muted-foreground">
|
|
341
|
+
{sourceLabel(r.source)} · {r.score.toFixed(2)}
|
|
342
|
+
{r.raptorLevel != null ? ` · L${r.raptorLevel}` : ""}
|
|
343
|
+
</span>
|
|
344
|
+
</li>
|
|
345
|
+
))}
|
|
346
|
+
</ul>
|
|
347
|
+
)}
|
|
348
|
+
</td>
|
|
349
|
+
<td className="px-3 py-2 align-top">
|
|
350
|
+
<HydeDetailPanel
|
|
351
|
+
conversationId={t.conversationId}
|
|
352
|
+
turnIndex={t.turnIndex}
|
|
353
|
+
/>
|
|
354
|
+
</td>
|
|
355
|
+
<td className="px-3 py-2">{fmtTs(t.endedAt)}</td>
|
|
356
|
+
<td className="px-3 py-2">
|
|
357
|
+
<div className="flex gap-2">
|
|
358
|
+
<Button
|
|
359
|
+
type="button"
|
|
360
|
+
variant="outline"
|
|
361
|
+
size="sm"
|
|
362
|
+
disabled={busy !== null}
|
|
363
|
+
onClick={() => onFork(t.conversationId, t.turnIndex)}
|
|
364
|
+
>
|
|
365
|
+
fork
|
|
366
|
+
</Button>
|
|
367
|
+
<Button
|
|
368
|
+
type="button"
|
|
369
|
+
variant="outline"
|
|
370
|
+
size="sm"
|
|
371
|
+
disabled={busy !== null}
|
|
372
|
+
onClick={() => onRewind(t.conversationId, t.turnIndex)}
|
|
373
|
+
>
|
|
374
|
+
rewind
|
|
375
|
+
</Button>
|
|
376
|
+
</div>
|
|
377
|
+
</td>
|
|
378
|
+
</tr>
|
|
379
|
+
))}
|
|
380
|
+
</tbody>
|
|
381
|
+
</table>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dashboard-client/src/tabs/SessionsTab.tsx — Sessions tab (S39).
|
|
2
|
+
* dashboard-client/src/tabs/SessionsTab.tsx — Sessions tab (S39, DASH-0b).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* table (
|
|
4
|
+
* DASH-0b: the Sessions surface absorbs the existent TurnsTab as a drill-down.
|
|
5
|
+
* A `SessionsViews` toggle ("Active Sessions" / "Turn Memory") switches between
|
|
6
|
+
* the existent sessions summary/chart/table body and the turns view (rendered by
|
|
7
|
+
* `SessionsTab/TurnMemoryView.tsx` — the turns body moved VERBATIM from the
|
|
8
|
+
* standalone TurnsTab.tsx, which is left untouched this sprint for flag-off
|
|
9
|
+
* parity).
|
|
10
|
+
*
|
|
11
|
+
* Flag: `MEGACOMPACT_DASH_0B` default ON. The dashboard client is a browser
|
|
12
|
+
* bundle with NO `process` global, so the positive sprint flag cannot be read
|
|
13
|
+
* server-side-style (`process.env`) inside the client. Instead the flag is read
|
|
14
|
+
* from the server-authoritative `/api/rag-settings` state: the server resolves
|
|
15
|
+
* `process.env["MEGACOMPACT_DASH_0B"] ?? default` into the `SettingState.value`
|
|
16
|
+
* boolean. When the flag is OFF (settings confirmed value=false), this surface
|
|
17
|
+
* renders ONLY the existent sessions body — no toggle, no TurnMemoryView branch
|
|
18
|
+
* (byte-identical predecessor). The standalone TurnsTab.tsx top-level surface is
|
|
19
|
+
* untouched and remains reachable for flag-off parity.
|
|
7
20
|
*
|
|
8
21
|
* Data sources: fetchSessions() + fetchSessionTimeseries(minutes). Both poll
|
|
9
22
|
* every 2s for near-real-time updates. useSSE() listens for session_sample
|
|
@@ -18,12 +31,19 @@ import type React from "react";
|
|
|
18
31
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
19
32
|
import { useApi } from "../hooks/useApi";
|
|
20
33
|
import { useSSE } from "../hooks/useSSE";
|
|
21
|
-
import { fetchSessions, fetchSessionTimeseries } from "../api/client";
|
|
22
|
-
import type {
|
|
34
|
+
import { fetchSessions, fetchSessionTimeseries, fetchSettings } from "../api/client";
|
|
35
|
+
import type {
|
|
36
|
+
SseEvent,
|
|
37
|
+
SseSessionSample,
|
|
38
|
+
SessionsResponse,
|
|
39
|
+
SessionTimeseriesResponse,
|
|
40
|
+
SettingsResponse,
|
|
41
|
+
} from "@contracts";
|
|
23
42
|
import { SessionsMemoryChart } from "../components/SessionsMemoryChart";
|
|
24
43
|
import { ActiveSessionsTable } from "../components/ActiveSessionsTable";
|
|
25
44
|
import { Toggle } from "../components/ui/toggle";
|
|
26
45
|
import { Card, CardContent } from "../components/ui/card";
|
|
46
|
+
import { TurnMemoryView } from "./SessionsTab/TurnMemoryView";
|
|
27
47
|
|
|
28
48
|
/**
|
|
29
49
|
* Shape of the {@link SseSessionSample} SSE event appended to events.log by
|
|
@@ -40,6 +60,21 @@ function isSessionSample(
|
|
|
40
60
|
return e.type === "session_sample";
|
|
41
61
|
}
|
|
42
62
|
|
|
63
|
+
const DASH_0B_KEY = "MEGACOMPACT_DASH_0B";
|
|
64
|
+
|
|
65
|
+
/** Resolve the DASH-0b consolidation flag from the server settings state.
|
|
66
|
+
* Absent/not-yet-loaded => false (flag-off posture), so flag-off users never
|
|
67
|
+
* see a toggle flash; the toggle appears only once settings confirm it is ON. */
|
|
68
|
+
function dash0bEnabled(settings: SettingsResponse | null): boolean {
|
|
69
|
+
if (!settings) return false;
|
|
70
|
+
for (const cat of settings.categories) {
|
|
71
|
+
for (const s of cat.settings) {
|
|
72
|
+
if (s.key === DASH_0B_KEY && s.type === "boolean") return s.value === true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
43
78
|
const WINDOWS: ReadonlyArray<{ value: number; label: string }> = [
|
|
44
79
|
{ value: 5, label: "5m" },
|
|
45
80
|
{ value: 15, label: "15m" },
|
|
@@ -98,8 +133,19 @@ function SummaryTiles({ sessions }: SummaryTilesProps): React.ReactElement {
|
|
|
98
133
|
);
|
|
99
134
|
}
|
|
100
135
|
|
|
136
|
+
type SessionsView = "active" | "turns";
|
|
137
|
+
|
|
101
138
|
export default function SessionsTab(): React.ReactElement {
|
|
102
139
|
const [minutes, setMinutes] = useState(30);
|
|
140
|
+
const [view, setView] = useState<SessionsView>("active");
|
|
141
|
+
|
|
142
|
+
const {
|
|
143
|
+
data: settingsData,
|
|
144
|
+
} = useApi<SettingsResponse>(
|
|
145
|
+
useCallback(() => fetchSettings(), []),
|
|
146
|
+
{ pollInterval: 0, maxRetries: 0 },
|
|
147
|
+
);
|
|
148
|
+
const dash0bOn = dash0bEnabled(settingsData);
|
|
103
149
|
|
|
104
150
|
const {
|
|
105
151
|
data: sessionsData,
|
|
@@ -142,6 +188,10 @@ export default function SessionsTab(): React.ReactElement {
|
|
|
142
188
|
refetchTimeseries();
|
|
143
189
|
}, [lastSampleTs, refetchSessions, refetchTimeseries]);
|
|
144
190
|
|
|
191
|
+
if (dash0bOn && view === "turns") {
|
|
192
|
+
return <TurnMemoryView />;
|
|
193
|
+
}
|
|
194
|
+
|
|
145
195
|
if (sessionsErr && !sessionsData) {
|
|
146
196
|
return (
|
|
147
197
|
<div className="tab-stub">
|
|
@@ -185,6 +235,27 @@ export default function SessionsTab(): React.ReactElement {
|
|
|
185
235
|
</div>
|
|
186
236
|
</div>
|
|
187
237
|
|
|
238
|
+
{dash0bOn && (
|
|
239
|
+
<div
|
|
240
|
+
className="flex items-center gap-2"
|
|
241
|
+
role="tablist"
|
|
242
|
+
aria-label="Sessions views"
|
|
243
|
+
>
|
|
244
|
+
<Toggle
|
|
245
|
+
pressed={view === "active"}
|
|
246
|
+
onClick={() => setView("active")}
|
|
247
|
+
>
|
|
248
|
+
Active Sessions
|
|
249
|
+
</Toggle>
|
|
250
|
+
<Toggle
|
|
251
|
+
pressed={view === "turns"}
|
|
252
|
+
onClick={() => setView("turns")}
|
|
253
|
+
>
|
|
254
|
+
Turn Memory
|
|
255
|
+
</Toggle>
|
|
256
|
+
</div>
|
|
257
|
+
)}
|
|
258
|
+
|
|
188
259
|
{sessions.length === 0 ? (
|
|
189
260
|
<div className="text-sm text-muted-foreground">No active sessions.</div>
|
|
190
261
|
) : (
|