@omg-dev/admin 0.4.27 → 0.4.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +2 -2
- package/dist/{react-COCR3YSL.mjs → react-BNHn5Pe0.mjs} +998 -14
- package/dist/server.mjs +2 -1
- package/dist/standalone.mjs +1 -1
- package/package.json +1 -1
- package/src/client.ts +77 -0
- package/src/index.ts +4 -0
- package/src/react.tsx +658 -18
- package/src/safe-url.ts +11 -0
- package/src/server.ts +1 -1
- package/src/styles.ts +252 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
//#region src/styles.ts
|
|
4
4
|
const STYLE_ID = "vibes-admin-styles";
|
|
@@ -20,6 +20,7 @@ const CSS = `
|
|
|
20
20
|
--vadm-accent-fg: var(--primary-foreground, #ffffff);
|
|
21
21
|
--vadm-card: var(--card, #fafafa);
|
|
22
22
|
--vadm-danger: #dc2626;
|
|
23
|
+
--vadm-warn: color-mix(in srgb, #f59e0b 72%, var(--vadm-fg));
|
|
23
24
|
color: var(--vadm-fg);
|
|
24
25
|
font-family: inherit;
|
|
25
26
|
font-size: 14px;
|
|
@@ -160,6 +161,240 @@ button.vadm-user-row { cursor: pointer; }
|
|
|
160
161
|
animation: vadmPulse 1.4s ease-in-out infinite;
|
|
161
162
|
}
|
|
162
163
|
.vadm-skel-avatar { width: 38px; height: 38px; border-radius: 50%; flex: none; }
|
|
164
|
+
/* ── routing flow graph ───────────────────────────────────────────────────
|
|
165
|
+
Port of the docs site's <Flow> (apps/docs/app/components/flow.tsx) onto
|
|
166
|
+
--vadm-* tokens: hairline connector, 12px SVG chevron, pill edge label,
|
|
167
|
+
rounded card node with muted sub + accent ring. Horizontal by default so a
|
|
168
|
+
100-trace log stays scannable; the 760px breakpoint below collapses it to
|
|
169
|
+
the docs' vertical trunk. No per-trace canvas — the row border is the
|
|
170
|
+
canvas — and rows are left-aligned so decision nodes column-align. */
|
|
171
|
+
.vadm-brain-trace { position: relative; background: var(--vadm-bg); padding: 13px 16px 12px; }
|
|
172
|
+
.vadm-brain-trace[data-failed="true"] { border-color: color-mix(in srgb, var(--vadm-danger) 55%, var(--vadm-border)); }
|
|
173
|
+
.vadm-flow-time {
|
|
174
|
+
position: absolute; top: 12px; right: 14px;
|
|
175
|
+
color: var(--vadm-muted); font-size: 11px; white-space: nowrap;
|
|
176
|
+
}
|
|
177
|
+
.vadm-flow { display: flex; align-items: center; justify-content: flex-start; min-width: 0; padding-right: 76px; }
|
|
178
|
+
|
|
179
|
+
.vadm-flow-node {
|
|
180
|
+
flex: 0 1 auto; min-width: 118px; max-width: 260px; min-height: 0;
|
|
181
|
+
display: flex; flex-direction: column; align-items: center; text-align: center;
|
|
182
|
+
border: 1px solid var(--vadm-border); border-radius: 12px;
|
|
183
|
+
background: var(--vadm-card); padding: 7px 12px;
|
|
184
|
+
text-decoration: none; color: inherit;
|
|
185
|
+
}
|
|
186
|
+
.vadm-flow-node--bare {
|
|
187
|
+
flex: 0 1 260px; min-width: 0; align-items: flex-start; text-align: left;
|
|
188
|
+
border-color: transparent; background: none; padding: 7px 10px 7px 0;
|
|
189
|
+
}
|
|
190
|
+
.vadm-flow-node--accent {
|
|
191
|
+
border-color: var(--vadm-accent);
|
|
192
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--vadm-accent) 25%, transparent);
|
|
193
|
+
}
|
|
194
|
+
.vadm-flow-node--accent .vadm-flow-label { color: var(--vadm-accent); }
|
|
195
|
+
/* Amber is mixed toward the host's foreground so it stays legible whether the
|
|
196
|
+
host theme is light or dark (the panel can be dark while the OS is light). */
|
|
197
|
+
.vadm-flow-node--changed {
|
|
198
|
+
border-color: color-mix(in srgb, var(--vadm-warn) 60%, var(--vadm-border));
|
|
199
|
+
background: color-mix(in srgb, #f59e0b 9%, var(--vadm-card));
|
|
200
|
+
}
|
|
201
|
+
.vadm-flow-node--changed .vadm-flow-sub { color: var(--vadm-warn); }
|
|
202
|
+
.vadm-flow-node--danger {
|
|
203
|
+
border-color: var(--vadm-danger);
|
|
204
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--vadm-danger) 22%, transparent);
|
|
205
|
+
}
|
|
206
|
+
.vadm-flow-node--danger .vadm-flow-label { color: var(--vadm-danger); }
|
|
207
|
+
a.vadm-flow-node:hover { border-color: var(--vadm-accent); }
|
|
208
|
+
|
|
209
|
+
.vadm-flow-label {
|
|
210
|
+
display: block; max-width: 100%; font-size: 13px; font-weight: 500; line-height: 1.35;
|
|
211
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
212
|
+
}
|
|
213
|
+
.vadm-flow-label[data-mono="true"] { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
|
|
214
|
+
.vadm-flow-pre { color: var(--vadm-muted); font-weight: 400; }
|
|
215
|
+
.vadm-flow-out { color: var(--vadm-muted); font-size: 10px; margin-left: 4px; }
|
|
216
|
+
.vadm-flow-quote::before { content: "\\201C"; }
|
|
217
|
+
.vadm-flow-quote::after { content: "\\201D"; }
|
|
218
|
+
.vadm-flow-sub {
|
|
219
|
+
display: block; max-width: 100%; margin-top: 2px;
|
|
220
|
+
font-size: 11px; line-height: 1.35; color: var(--vadm-muted);
|
|
221
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.vadm-flow-edge {
|
|
225
|
+
position: relative; flex: none; align-self: center;
|
|
226
|
+
display: flex; align-items: center; justify-content: flex-end;
|
|
227
|
+
width: 68px; height: 12px; color: var(--vadm-muted);
|
|
228
|
+
}
|
|
229
|
+
.vadm-flow-edge::before {
|
|
230
|
+
content: ""; position: absolute; left: 0; right: 0; top: 50%;
|
|
231
|
+
border-top: 1px solid var(--vadm-border);
|
|
232
|
+
}
|
|
233
|
+
.vadm-flow-edge[data-danger="true"] { color: var(--vadm-danger); }
|
|
234
|
+
.vadm-flow-edge[data-danger="true"]::before { border-top-style: dashed; border-top-color: var(--vadm-danger); }
|
|
235
|
+
.vadm-flow-edge[data-running="true"]::before { animation: vadmPulse 1.4s ease-in-out infinite; }
|
|
236
|
+
.vadm-flow-arrow { position: relative; flex: none; margin-right: -2px; transform: rotate(-90deg); }
|
|
237
|
+
.vadm-flow-pill {
|
|
238
|
+
position: absolute; left: 50%; bottom: calc(50% + 5px); transform: translateX(-50%);
|
|
239
|
+
white-space: nowrap; font-size: 10px; font-weight: 500; line-height: 16px;
|
|
240
|
+
padding: 0 6px; border-radius: 999px;
|
|
241
|
+
border: 1px solid var(--vadm-border); background: var(--vadm-card); color: var(--vadm-muted);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* ── message-list-first view ───────────────────────────────────────────────
|
|
245
|
+
The Chat Brain panel leads with a scannable list of inbound iMessages;
|
|
246
|
+
tapping a row unveils the routing flow graph (TraceRouting) inline. */
|
|
247
|
+
.vadm-filter-pill {
|
|
248
|
+
display: flex; align-items: center; gap: 8px; margin-bottom: 12px;
|
|
249
|
+
padding: 6px 6px 6px 12px; border: 1px solid var(--vadm-border); border-radius: 999px;
|
|
250
|
+
background: color-mix(in srgb, var(--vadm-accent) 7%, var(--vadm-bg)); font-size: 13px;
|
|
251
|
+
width: fit-content; max-width: 100%;
|
|
252
|
+
}
|
|
253
|
+
.vadm-filter-pill strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
254
|
+
.vadm-userchips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 12px; }
|
|
255
|
+
.vadm-userchip {
|
|
256
|
+
appearance: none; cursor: pointer; font: inherit; font-size: 12px;
|
|
257
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
258
|
+
border: 1px solid var(--vadm-border); border-radius: 999px;
|
|
259
|
+
background: var(--vadm-bg); color: var(--vadm-fg); padding: 3px 6px 3px 10px;
|
|
260
|
+
}
|
|
261
|
+
.vadm-userchip:hover { border-color: var(--vadm-accent); }
|
|
262
|
+
.vadm-userchip-count {
|
|
263
|
+
min-width: 18px; padding: 0 5px; border-radius: 999px; text-align: center;
|
|
264
|
+
font-size: 11px; font-weight: 600; color: var(--vadm-muted);
|
|
265
|
+
background: color-mix(in srgb, var(--vadm-muted) 15%, transparent);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* Conversation (user view): a self-contained, scrollable chat pane. Oldest at
|
|
269
|
+
the top, newest at the bottom; "Load older" prepends above. iMessage-style
|
|
270
|
+
bubbles — their inbound messages (left) + our replies (right). A bubble with
|
|
271
|
+
a routing trace is tappable and reveals TraceRouting full-width below it.
|
|
272
|
+
Every color reads from the shared --vadm-* design tokens so the pane stays
|
|
273
|
+
consistent with the rest of the admin console in both light and dark. */
|
|
274
|
+
.vadm-conv {
|
|
275
|
+
display: flex; flex-direction: column; gap: 4px; min-width: 0;
|
|
276
|
+
max-height: min(64vh, 640px); overflow-y: auto; overscroll-behavior: contain;
|
|
277
|
+
padding: 12px; border: 1px solid var(--vadm-border); border-radius: 12px;
|
|
278
|
+
background: color-mix(in srgb, var(--vadm-muted) 5%, var(--vadm-bg));
|
|
279
|
+
}
|
|
280
|
+
.vadm-conv-more {
|
|
281
|
+
position: sticky; top: -12px; z-index: 1;
|
|
282
|
+
display: flex; justify-content: center; padding: 4px 0 8px;
|
|
283
|
+
margin: -12px -12px 4px; /* bleed to pane edges so the blur covers full width */
|
|
284
|
+
background: linear-gradient(
|
|
285
|
+
to bottom,
|
|
286
|
+
color-mix(in srgb, var(--vadm-muted) 5%, var(--vadm-bg)) 60%,
|
|
287
|
+
transparent
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
.vadm-bubble-row { display: flex; flex-direction: column; min-width: 0; }
|
|
291
|
+
.vadm-bubble-row--in { align-items: flex-start; }
|
|
292
|
+
.vadm-bubble-row--out { align-items: flex-end; }
|
|
293
|
+
.vadm-bubble {
|
|
294
|
+
appearance: none; font: inherit; text-align: left; cursor: default;
|
|
295
|
+
max-width: 82%; min-width: 0; border-radius: 16px; padding: 8px 12px;
|
|
296
|
+
border: 1px solid transparent; display: flex; flex-direction: column; gap: 3px;
|
|
297
|
+
}
|
|
298
|
+
.vadm-bubble[data-dir="in"] {
|
|
299
|
+
background: var(--vadm-card); border-color: var(--vadm-border);
|
|
300
|
+
color: var(--vadm-fg); border-bottom-left-radius: 5px;
|
|
301
|
+
}
|
|
302
|
+
.vadm-bubble[data-dir="out"] {
|
|
303
|
+
background: var(--vadm-accent); color: var(--vadm-accent-fg);
|
|
304
|
+
border-bottom-right-radius: 5px;
|
|
305
|
+
}
|
|
306
|
+
.vadm-bubble[data-has-trace] { cursor: pointer; }
|
|
307
|
+
.vadm-bubble[data-has-trace]:hover { filter: brightness(0.97); }
|
|
308
|
+
.vadm-bubble[data-open] {
|
|
309
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--vadm-accent) 60%, transparent);
|
|
310
|
+
}
|
|
311
|
+
.vadm-bubble-text { font-size: 14px; line-height: 1.35; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
312
|
+
.vadm-bubble-media { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
313
|
+
.vadm-bubble-att {
|
|
314
|
+
display: block; max-width: 220px; max-height: 260px; width: auto; height: auto;
|
|
315
|
+
border-radius: 10px; object-fit: cover; background: rgba(0,0,0,0.2);
|
|
316
|
+
}
|
|
317
|
+
.vadm-bubble-meta {
|
|
318
|
+
display: flex; align-items: center; gap: 5px; font-size: 11px;
|
|
319
|
+
color: var(--vadm-muted);
|
|
320
|
+
}
|
|
321
|
+
.vadm-bubble[data-dir="out"] .vadm-bubble-meta {
|
|
322
|
+
color: color-mix(in srgb, var(--vadm-accent-fg) 80%, var(--vadm-accent));
|
|
323
|
+
}
|
|
324
|
+
.vadm-bubble-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--vadm-muted); }
|
|
325
|
+
.vadm-bubble-dot[data-outcome="completed"] { background: color-mix(in srgb, #16a34a 78%, var(--vadm-fg)); }
|
|
326
|
+
.vadm-bubble-dot[data-outcome="failed"] { background: var(--vadm-danger); }
|
|
327
|
+
.vadm-bubble-dot[data-outcome="running"] {
|
|
328
|
+
background: var(--vadm-accent-fg); animation: vadmPulse 1.4s ease-in-out infinite;
|
|
329
|
+
}
|
|
330
|
+
.vadm-bubble-detail {
|
|
331
|
+
align-self: stretch; margin: 4px 0 8px;
|
|
332
|
+
border: 1px solid color-mix(in srgb, var(--vadm-accent) 40%, var(--vadm-border));
|
|
333
|
+
border-radius: 10px; background: var(--vadm-bg); padding: 12px 14px;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.vadm-msg-list { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
337
|
+
.vadm-msg {
|
|
338
|
+
border: 1px solid var(--vadm-border); border-radius: 10px;
|
|
339
|
+
background: var(--vadm-bg); overflow: hidden;
|
|
340
|
+
}
|
|
341
|
+
.vadm-msg[data-open="true"] {
|
|
342
|
+
border-color: color-mix(in srgb, var(--vadm-accent) 45%, var(--vadm-border));
|
|
343
|
+
background: var(--vadm-card);
|
|
344
|
+
}
|
|
345
|
+
.vadm-msg[data-failed="true"] { border-color: color-mix(in srgb, var(--vadm-danger) 45%, var(--vadm-border)); }
|
|
346
|
+
.vadm-msg-row {
|
|
347
|
+
appearance: none; width: 100%; border: none; background: none; cursor: pointer;
|
|
348
|
+
color: inherit; font: inherit; text-align: left;
|
|
349
|
+
display: flex; align-items: center; gap: 11px; padding: 11px 13px; min-width: 0;
|
|
350
|
+
}
|
|
351
|
+
.vadm-msg-row:hover { background: color-mix(in srgb, var(--vadm-muted) 6%, transparent); }
|
|
352
|
+
.vadm-msg-dot {
|
|
353
|
+
flex: none; width: 9px; height: 9px; border-radius: 50%;
|
|
354
|
+
background: var(--vadm-muted);
|
|
355
|
+
}
|
|
356
|
+
.vadm-msg-dot[data-outcome="completed"] { background: color-mix(in srgb, #16a34a 78%, var(--vadm-fg)); }
|
|
357
|
+
.vadm-msg-dot[data-outcome="failed"] { background: var(--vadm-danger); }
|
|
358
|
+
.vadm-msg-dot[data-outcome="running"] {
|
|
359
|
+
background: var(--vadm-accent); animation: vadmPulse 1.4s ease-in-out infinite;
|
|
360
|
+
}
|
|
361
|
+
.vadm-msg-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
|
|
362
|
+
.vadm-msg-text {
|
|
363
|
+
font-weight: 550; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
364
|
+
}
|
|
365
|
+
.vadm-msg-meta {
|
|
366
|
+
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
|
|
367
|
+
color: var(--vadm-muted); font-size: 12px; min-width: 0;
|
|
368
|
+
}
|
|
369
|
+
.vadm-msg-decision {
|
|
370
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px;
|
|
371
|
+
color: var(--vadm-fg);
|
|
372
|
+
}
|
|
373
|
+
.vadm-msg-decision[data-variant="accent"] { color: var(--vadm-accent); }
|
|
374
|
+
.vadm-msg-decision[data-variant="danger"] { color: var(--vadm-danger); }
|
|
375
|
+
.vadm-msg-sender { cursor: pointer; text-decoration: underline dotted; text-underline-offset: 2px; }
|
|
376
|
+
.vadm-msg-sender:hover { color: var(--vadm-fg); }
|
|
377
|
+
.vadm-msg-chevron {
|
|
378
|
+
flex: none; width: 8px; height: 8px; margin-right: 3px;
|
|
379
|
+
border-right: 1.5px solid var(--vadm-muted); border-bottom: 1.5px solid var(--vadm-muted);
|
|
380
|
+
transform: rotate(-45deg); transition: transform 0.15s;
|
|
381
|
+
}
|
|
382
|
+
.vadm-msg[data-open="true"] .vadm-msg-chevron { transform: rotate(45deg); }
|
|
383
|
+
.vadm-msg-detail { padding: 4px 14px 14px; border-top: 1px dashed var(--vadm-border); }
|
|
384
|
+
|
|
385
|
+
.vadm-pager { display: flex; align-items: center; justify-content: center; gap: 14px; margin-top: 14px; }
|
|
386
|
+
|
|
387
|
+
.vadm-flow-error { margin: 10px 0 0; font-size: 12px; }
|
|
388
|
+
.vadm-flow-more { margin-top: 2px; }
|
|
389
|
+
.vadm-flow-more > summary {
|
|
390
|
+
list-style: none; cursor: pointer; text-align: right;
|
|
391
|
+
color: var(--vadm-muted); font-size: 12px; line-height: 1.2;
|
|
392
|
+
}
|
|
393
|
+
.vadm-flow-more > summary::-webkit-details-marker { display: none; }
|
|
394
|
+
.vadm-flow-more > summary::after { content: "\\2026"; letter-spacing: 1px; }
|
|
395
|
+
.vadm-flow-more[open] > summary::after { content: "\\2212"; }
|
|
396
|
+
.vadm-flow-more > summary:hover { color: var(--vadm-fg); }
|
|
397
|
+
.vadm-flow-more .vadm-kv { margin-top: 8px; padding-top: 10px; border-top: 1px dashed var(--vadm-border); }
|
|
163
398
|
@keyframes vadmPulse {
|
|
164
399
|
0%, 100% { opacity: 0.55; }
|
|
165
400
|
50% { opacity: 1; }
|
|
@@ -178,6 +413,23 @@ button.vadm-user-row { cursor: pointer; }
|
|
|
178
413
|
.vadm-user-stats { flex-direction: row; align-items: center; gap: 8px; width: 100%; padding-left: 48px; margin-top: -4px; }
|
|
179
414
|
.vadm-table { display: block; overflow-x: auto; white-space: nowrap; -webkit-overflow-scrolling: touch; }
|
|
180
415
|
.vadm-detail.vadm-card { width: 100% !important; max-width: 100%; }
|
|
416
|
+
/* Below the fold the graph becomes the docs' vertical trunk verbatim:
|
|
417
|
+
full-width nodes stacked, hairline + chevron between, pill beside it. */
|
|
418
|
+
.vadm-flow { flex-direction: column; align-items: stretch; padding-right: 0; }
|
|
419
|
+
.vadm-flow-node { flex: none; width: 100%; max-width: none; align-items: center; text-align: center; }
|
|
420
|
+
.vadm-flow-node--bare { align-items: center; text-align: center; padding: 4px 0 6px; }
|
|
421
|
+
.vadm-flow-edge {
|
|
422
|
+
flex-direction: column; justify-content: flex-end; align-self: stretch;
|
|
423
|
+
width: 100%; height: 26px;
|
|
424
|
+
}
|
|
425
|
+
.vadm-flow-edge::before {
|
|
426
|
+
left: 50%; right: auto; top: 0; bottom: 0;
|
|
427
|
+
border-top: 0; border-left: 1px solid var(--vadm-border);
|
|
428
|
+
}
|
|
429
|
+
.vadm-flow-edge[data-danger="true"]::before { border-left-style: dashed; border-left-color: var(--vadm-danger); }
|
|
430
|
+
.vadm-flow-arrow { transform: none; margin: 0 0 -2px; }
|
|
431
|
+
.vadm-flow-pill { left: calc(50% + 8px); bottom: auto; top: 50%; transform: translateY(-50%); }
|
|
432
|
+
.vadm-flow-time { position: static; display: block; text-align: right; margin-bottom: 4px; }
|
|
181
433
|
}
|
|
182
434
|
|
|
183
435
|
@media (max-width: 480px) {
|
|
@@ -190,6 +442,19 @@ button.vadm-user-row { cursor: pointer; }
|
|
|
190
442
|
}
|
|
191
443
|
`;
|
|
192
444
|
//#endregion
|
|
445
|
+
//#region src/safe-url.ts
|
|
446
|
+
/** URLs in the admin console can originate from reports or user-controlled
|
|
447
|
+
* Computers. Only ordinary web links may become clickable operator actions. */
|
|
448
|
+
function safeHref(value) {
|
|
449
|
+
if (typeof value !== "string") return void 0;
|
|
450
|
+
try {
|
|
451
|
+
const url = new URL(value);
|
|
452
|
+
return url.protocol === "http:" || url.protocol === "https:" ? value : void 0;
|
|
453
|
+
} catch {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
//#endregion
|
|
193
458
|
//#region src/client.ts
|
|
194
459
|
var AdminError = class extends Error {
|
|
195
460
|
status;
|
|
@@ -303,6 +568,12 @@ var AdminClient = class {
|
|
|
303
568
|
removeReport(id) {
|
|
304
569
|
return this.rpc("bugReports", "removeReport", { id });
|
|
305
570
|
}
|
|
571
|
+
listImessageBrainRouting(args) {
|
|
572
|
+
return this.rpc("imessageDebug", "listBrainRouting", args ?? {});
|
|
573
|
+
}
|
|
574
|
+
listImessageBrainTranscript(args) {
|
|
575
|
+
return this.rpc("imessageDebug", "listBrainTranscript", args);
|
|
576
|
+
}
|
|
306
577
|
};
|
|
307
578
|
//#endregion
|
|
308
579
|
//#region src/react.tsx
|
|
@@ -1168,15 +1439,6 @@ function fmtTime(ms) {
|
|
|
1168
1439
|
return String(ms);
|
|
1169
1440
|
}
|
|
1170
1441
|
}
|
|
1171
|
-
function safeHref(v) {
|
|
1172
|
-
if (typeof v !== "string") return void 0;
|
|
1173
|
-
try {
|
|
1174
|
-
const u = new URL(v);
|
|
1175
|
-
return u.protocol === "http:" || u.protocol === "https:" ? v : void 0;
|
|
1176
|
-
} catch {
|
|
1177
|
-
return;
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
1442
|
const STORAGE_HOSTS = ["t3.storage.dev"];
|
|
1181
1443
|
function safeImageSrc(v) {
|
|
1182
1444
|
if (typeof v !== "string") return void 0;
|
|
@@ -1409,7 +1671,7 @@ function ReportsPanel({ client }) {
|
|
|
1409
1671
|
"app:",
|
|
1410
1672
|
" ",
|
|
1411
1673
|
/* @__PURE__ */ jsx("a", {
|
|
1412
|
-
href: `https://omg.dev/${detail.slug}`,
|
|
1674
|
+
href: `https://app.omg.dev/${detail.slug}`,
|
|
1413
1675
|
target: "_blank",
|
|
1414
1676
|
rel: "noreferrer",
|
|
1415
1677
|
children: detail.slug
|
|
@@ -1449,13 +1711,13 @@ function ReportsPanel({ client }) {
|
|
|
1449
1711
|
},
|
|
1450
1712
|
children: [/* @__PURE__ */ jsx("a", {
|
|
1451
1713
|
className: "vadm-btn vadm-btn-sm",
|
|
1452
|
-
href: `https://omg.dev/${detail.slug}`,
|
|
1714
|
+
href: `https://app.omg.dev/${detail.slug}`,
|
|
1453
1715
|
target: "_blank",
|
|
1454
1716
|
rel: "noreferrer",
|
|
1455
1717
|
children: "Open app"
|
|
1456
1718
|
}), /* @__PURE__ */ jsx("a", {
|
|
1457
1719
|
className: "vadm-btn vadm-btn-sm",
|
|
1458
|
-
href: `https://omg.dev/${detail.slug}/inspect/data`,
|
|
1720
|
+
href: `https://app.omg.dev/${detail.slug}/inspect/data`,
|
|
1459
1721
|
target: "_blank",
|
|
1460
1722
|
rel: "noreferrer",
|
|
1461
1723
|
children: "Inspect"
|
|
@@ -1550,7 +1812,729 @@ function ReportsPanel({ client }) {
|
|
|
1550
1812
|
})
|
|
1551
1813
|
] });
|
|
1552
1814
|
}
|
|
1815
|
+
function shortId(value) {
|
|
1816
|
+
if (!value) return "none";
|
|
1817
|
+
return value.length > 16 ? `${value.slice(0, 8)}…${value.slice(-5)}` : value;
|
|
1818
|
+
}
|
|
1819
|
+
function maskedPhone(value) {
|
|
1820
|
+
const tail = value.replace(/\D/g, "").slice(-4);
|
|
1821
|
+
return tail ? `••• ${tail}` : "unknown";
|
|
1822
|
+
}
|
|
1823
|
+
function formatMs(ms) {
|
|
1824
|
+
return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
|
|
1825
|
+
}
|
|
1826
|
+
function shortTime(at) {
|
|
1827
|
+
const d = new Date(at);
|
|
1828
|
+
const time = d.toLocaleTimeString([], {
|
|
1829
|
+
hour: "2-digit",
|
|
1830
|
+
minute: "2-digit"
|
|
1831
|
+
});
|
|
1832
|
+
return (/* @__PURE__ */ new Date()).toDateString() === d.toDateString() ? time : `${d.toLocaleDateString([], {
|
|
1833
|
+
month: "short",
|
|
1834
|
+
day: "numeric"
|
|
1835
|
+
})} ${time}`;
|
|
1836
|
+
}
|
|
1837
|
+
/** `computer.steer` → ["computer.", "steer"] so the namespace can be demoted. */
|
|
1838
|
+
function splitDecision(decision) {
|
|
1839
|
+
const dot = decision.indexOf(".");
|
|
1840
|
+
return dot === -1 ? ["", decision] : [decision.slice(0, dot + 1), decision.slice(dot + 1)];
|
|
1841
|
+
}
|
|
1842
|
+
/** routeVia is only ever "reply-reference" | "fallback"; never render null. */
|
|
1843
|
+
function routeViaLabel(via) {
|
|
1844
|
+
if (!via) return null;
|
|
1845
|
+
return via === "reply-reference" ? "reply-ref" : via;
|
|
1846
|
+
}
|
|
1847
|
+
function timingLine(fleetMs, llmMs, total) {
|
|
1848
|
+
const parts = [
|
|
1849
|
+
total != null ? `${total}ms total` : null,
|
|
1850
|
+
llmMs != null ? `brain ${llmMs}ms` : null,
|
|
1851
|
+
fleetMs != null ? `fleet ${fleetMs}ms` : null
|
|
1852
|
+
].filter(Boolean);
|
|
1853
|
+
return parts.length ? parts.join(" · ") : "—";
|
|
1854
|
+
}
|
|
1855
|
+
function FlowArrow() {
|
|
1856
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
1857
|
+
className: "vadm-flow-arrow",
|
|
1858
|
+
width: "12",
|
|
1859
|
+
height: "12",
|
|
1860
|
+
viewBox: "0 0 24 24",
|
|
1861
|
+
fill: "none",
|
|
1862
|
+
"aria-hidden": "true",
|
|
1863
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
1864
|
+
d: "M6 13l6 6 6-6",
|
|
1865
|
+
stroke: "currentColor",
|
|
1866
|
+
strokeWidth: "2.2",
|
|
1867
|
+
strokeLinecap: "round",
|
|
1868
|
+
strokeLinejoin: "round"
|
|
1869
|
+
})
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Connector between two nodes — hairline + chevron + optional pill edge label
|
|
1874
|
+
* (the docs Trunk). `danger` dashes it red, `running` pulses it.
|
|
1875
|
+
*/
|
|
1876
|
+
function FlowEdge({ label, danger, running }) {
|
|
1877
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1878
|
+
className: "vadm-flow-edge",
|
|
1879
|
+
"data-danger": danger ? "true" : void 0,
|
|
1880
|
+
"data-running": running ? "true" : void 0,
|
|
1881
|
+
children: [label ? /* @__PURE__ */ jsx("span", {
|
|
1882
|
+
className: "vadm-flow-pill",
|
|
1883
|
+
children: label
|
|
1884
|
+
}) : null, /* @__PURE__ */ jsx(FlowArrow, {})]
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
function FlowNode({ variant, mono, href, children, sub }) {
|
|
1888
|
+
const className = `vadm-flow-node${variant ? ` vadm-flow-node--${variant}` : ""}`;
|
|
1889
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
|
|
1890
|
+
className: "vadm-flow-label",
|
|
1891
|
+
"data-mono": mono ? "true" : void 0,
|
|
1892
|
+
children: [children, href ? /* @__PURE__ */ jsx("span", {
|
|
1893
|
+
className: "vadm-flow-out",
|
|
1894
|
+
"aria-hidden": "true",
|
|
1895
|
+
children: "↗"
|
|
1896
|
+
}) : null]
|
|
1897
|
+
}), sub ? /* @__PURE__ */ jsx("span", {
|
|
1898
|
+
className: "vadm-flow-sub",
|
|
1899
|
+
children: sub
|
|
1900
|
+
}) : null] });
|
|
1901
|
+
if (href) return /* @__PURE__ */ jsx("a", {
|
|
1902
|
+
className,
|
|
1903
|
+
href,
|
|
1904
|
+
target: "_blank",
|
|
1905
|
+
rel: "noreferrer",
|
|
1906
|
+
children: body
|
|
1907
|
+
});
|
|
1908
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1909
|
+
className,
|
|
1910
|
+
children: body
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
function BrainTraceSkeleton() {
|
|
1914
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1915
|
+
className: "vadm-list",
|
|
1916
|
+
"aria-label": "Loading Brain routing traces",
|
|
1917
|
+
children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsx("div", {
|
|
1918
|
+
className: "vadm-card vadm-brain-trace",
|
|
1919
|
+
"aria-hidden": "true",
|
|
1920
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1921
|
+
className: "vadm-flow",
|
|
1922
|
+
children: [
|
|
1923
|
+
/* @__PURE__ */ jsx("div", {
|
|
1924
|
+
className: "vadm-flow-node vadm-flow-node--bare",
|
|
1925
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
1926
|
+
className: "vadm-skel",
|
|
1927
|
+
style: { width: `${86 - i * 8}%` }
|
|
1928
|
+
})
|
|
1929
|
+
}),
|
|
1930
|
+
/* @__PURE__ */ jsx(FlowEdge, {}),
|
|
1931
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1932
|
+
className: "vadm-flow-node",
|
|
1933
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1934
|
+
className: "vadm-skel",
|
|
1935
|
+
style: { width: "70%" }
|
|
1936
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1937
|
+
className: "vadm-skel",
|
|
1938
|
+
style: {
|
|
1939
|
+
width: "88%",
|
|
1940
|
+
marginTop: 7
|
|
1941
|
+
}
|
|
1942
|
+
})]
|
|
1943
|
+
}),
|
|
1944
|
+
i % 2 === 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FlowEdge, {}), /* @__PURE__ */ jsx("div", {
|
|
1945
|
+
className: "vadm-flow-node",
|
|
1946
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
1947
|
+
className: "vadm-skel",
|
|
1948
|
+
style: { width: "64%" }
|
|
1949
|
+
})
|
|
1950
|
+
})] })
|
|
1951
|
+
]
|
|
1952
|
+
})
|
|
1953
|
+
}, i))
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
const BRAIN_FETCH_LIMIT = 200;
|
|
1957
|
+
const BRAIN_TRANSCRIPT_PAGE = 30;
|
|
1958
|
+
function BrainRoutingPanel({ client }) {
|
|
1959
|
+
const [traces, setTraces] = useState(null);
|
|
1960
|
+
const [steps, setSteps] = useState(/* @__PURE__ */ new Map());
|
|
1961
|
+
const [capture, setCapture] = useState(null);
|
|
1962
|
+
const [transcript, setTranscript] = useState(null);
|
|
1963
|
+
const [hasMore, setHasMore] = useState(false);
|
|
1964
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
1965
|
+
const [error, setError] = useState(null);
|
|
1966
|
+
const [query, setQuery] = useState("");
|
|
1967
|
+
const [hits, setHits] = useState([]);
|
|
1968
|
+
const [filter, setFilter] = useState(null);
|
|
1969
|
+
const [openMsgId, setOpenMsgId] = useState(null);
|
|
1970
|
+
const scrollRef = useRef(null);
|
|
1971
|
+
const restoreRef = useRef(null);
|
|
1972
|
+
const bottomRef = useRef(false);
|
|
1973
|
+
useLayoutEffect(() => {
|
|
1974
|
+
const el = scrollRef.current;
|
|
1975
|
+
if (!el) return;
|
|
1976
|
+
if (restoreRef.current) {
|
|
1977
|
+
el.scrollTop = restoreRef.current.top + (el.scrollHeight - restoreRef.current.height);
|
|
1978
|
+
restoreRef.current = null;
|
|
1979
|
+
} else if (bottomRef.current) {
|
|
1980
|
+
el.scrollTop = el.scrollHeight;
|
|
1981
|
+
bottomRef.current = false;
|
|
1982
|
+
}
|
|
1983
|
+
}, [transcript]);
|
|
1984
|
+
const load = useCallback(async (userId) => {
|
|
1985
|
+
setTraces(null);
|
|
1986
|
+
try {
|
|
1987
|
+
const result = await client.listImessageBrainRouting({
|
|
1988
|
+
userId,
|
|
1989
|
+
limit: BRAIN_FETCH_LIMIT,
|
|
1990
|
+
steps: true
|
|
1991
|
+
});
|
|
1992
|
+
setTraces(result.traces);
|
|
1993
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1994
|
+
for (const row of result.steps ?? []) {
|
|
1995
|
+
const list = grouped.get(row.traceId);
|
|
1996
|
+
if (list) list.push(row);
|
|
1997
|
+
else grouped.set(row.traceId, [row]);
|
|
1998
|
+
}
|
|
1999
|
+
setSteps(grouped);
|
|
2000
|
+
setCapture(result.capture);
|
|
2001
|
+
setError(null);
|
|
2002
|
+
} catch (e) {
|
|
2003
|
+
setError(e);
|
|
2004
|
+
setTraces([]);
|
|
2005
|
+
}
|
|
2006
|
+
}, [client]);
|
|
2007
|
+
const loadTranscript = useCallback(async (userId) => {
|
|
2008
|
+
setTranscript(null);
|
|
2009
|
+
setHasMore(false);
|
|
2010
|
+
setOpenMsgId(null);
|
|
2011
|
+
try {
|
|
2012
|
+
const result = await client.listImessageBrainTranscript({
|
|
2013
|
+
userId,
|
|
2014
|
+
limit: BRAIN_TRANSCRIPT_PAGE
|
|
2015
|
+
});
|
|
2016
|
+
bottomRef.current = true;
|
|
2017
|
+
setTranscript(result.messages);
|
|
2018
|
+
setHasMore(result.hasMore);
|
|
2019
|
+
} catch (e) {
|
|
2020
|
+
setError(e);
|
|
2021
|
+
setTranscript([]);
|
|
2022
|
+
}
|
|
2023
|
+
}, [client]);
|
|
2024
|
+
const loadOlder = useCallback(async () => {
|
|
2025
|
+
if (!filter || loadingMore || !hasMore) return;
|
|
2026
|
+
const el = scrollRef.current;
|
|
2027
|
+
const oldest = transcript?.[0];
|
|
2028
|
+
if (!oldest) return;
|
|
2029
|
+
setLoadingMore(true);
|
|
2030
|
+
try {
|
|
2031
|
+
const result = await client.listImessageBrainTranscript({
|
|
2032
|
+
userId: filter.id,
|
|
2033
|
+
limit: BRAIN_TRANSCRIPT_PAGE,
|
|
2034
|
+
before: oldest.id
|
|
2035
|
+
});
|
|
2036
|
+
if (el) restoreRef.current = {
|
|
2037
|
+
top: el.scrollTop,
|
|
2038
|
+
height: el.scrollHeight
|
|
2039
|
+
};
|
|
2040
|
+
setTranscript((cur) => cur ? [...result.messages, ...cur] : result.messages);
|
|
2041
|
+
setHasMore(result.hasMore);
|
|
2042
|
+
} catch (e) {
|
|
2043
|
+
setError(e);
|
|
2044
|
+
} finally {
|
|
2045
|
+
setLoadingMore(false);
|
|
2046
|
+
}
|
|
2047
|
+
}, [
|
|
2048
|
+
client,
|
|
2049
|
+
filter,
|
|
2050
|
+
hasMore,
|
|
2051
|
+
loadingMore,
|
|
2052
|
+
transcript
|
|
2053
|
+
]);
|
|
2054
|
+
useEffect(() => {
|
|
2055
|
+
ensureStyles();
|
|
2056
|
+
load();
|
|
2057
|
+
}, [load]);
|
|
2058
|
+
useEffect(() => {
|
|
2059
|
+
if (!query.trim() || filter) {
|
|
2060
|
+
setHits([]);
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
let live = true;
|
|
2064
|
+
const timer = setTimeout(async () => {
|
|
2065
|
+
try {
|
|
2066
|
+
const result = await client.findUsers(query.trim());
|
|
2067
|
+
if (live) setHits(result);
|
|
2068
|
+
} catch {
|
|
2069
|
+
if (live) setHits([]);
|
|
2070
|
+
}
|
|
2071
|
+
}, 200);
|
|
2072
|
+
return () => {
|
|
2073
|
+
live = false;
|
|
2074
|
+
clearTimeout(timer);
|
|
2075
|
+
};
|
|
2076
|
+
}, [
|
|
2077
|
+
client,
|
|
2078
|
+
query,
|
|
2079
|
+
filter
|
|
2080
|
+
]);
|
|
2081
|
+
function openConversation(next) {
|
|
2082
|
+
setFilter(next);
|
|
2083
|
+
setQuery("");
|
|
2084
|
+
setHits([]);
|
|
2085
|
+
load(next.id);
|
|
2086
|
+
loadTranscript(next.id);
|
|
2087
|
+
}
|
|
2088
|
+
function pickUser(hit) {
|
|
2089
|
+
openConversation({
|
|
2090
|
+
id: hit.id,
|
|
2091
|
+
label: hit.name || hit.email
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
function clearFilter() {
|
|
2095
|
+
setFilter(null);
|
|
2096
|
+
setQuery("");
|
|
2097
|
+
setTranscript(null);
|
|
2098
|
+
setHasMore(false);
|
|
2099
|
+
load();
|
|
2100
|
+
}
|
|
2101
|
+
function refresh() {
|
|
2102
|
+
if (filter) {
|
|
2103
|
+
load(filter.id);
|
|
2104
|
+
loadTranscript(filter.id);
|
|
2105
|
+
} else load();
|
|
2106
|
+
}
|
|
2107
|
+
const traceByMsgId = useMemo(() => {
|
|
2108
|
+
const m = /* @__PURE__ */ new Map();
|
|
2109
|
+
for (const t of traces ?? []) if (t.inboundMessageId) m.set(t.inboundMessageId, t);
|
|
2110
|
+
return m;
|
|
2111
|
+
}, [traces]);
|
|
2112
|
+
const traceByText = useMemo(() => {
|
|
2113
|
+
const m = /* @__PURE__ */ new Map();
|
|
2114
|
+
for (const t of traces ?? []) m.set(t.text, t);
|
|
2115
|
+
return m;
|
|
2116
|
+
}, [traces]);
|
|
2117
|
+
function traceForMessage(msg) {
|
|
2118
|
+
if (msg.direction !== "in") return null;
|
|
2119
|
+
return (msg.providerMessageId ? traceByMsgId.get(msg.providerMessageId) : void 0) ?? traceByText.get(msg.text) ?? null;
|
|
2120
|
+
}
|
|
2121
|
+
const senders = useMemo(() => {
|
|
2122
|
+
if (!traces) return [];
|
|
2123
|
+
const m = /* @__PURE__ */ new Map();
|
|
2124
|
+
for (const t of traces) {
|
|
2125
|
+
const e = m.get(t.userId);
|
|
2126
|
+
if (e) e.count++;
|
|
2127
|
+
else m.set(t.userId, {
|
|
2128
|
+
userId: t.userId,
|
|
2129
|
+
phone: t.phone,
|
|
2130
|
+
count: 1
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
return [...m.values()].sort((a, b) => b.count - a.count);
|
|
2134
|
+
}, [traces]);
|
|
2135
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
2136
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2137
|
+
className: "vadm-spread vadm-panel-head",
|
|
2138
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
|
|
2139
|
+
className: "vadm-h",
|
|
2140
|
+
children: "Chat Brain conversations"
|
|
2141
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
2142
|
+
className: "vadm-sub",
|
|
2143
|
+
style: { margin: 0 },
|
|
2144
|
+
children: filter ? "Their messages and our replies — tap any message to reveal the backend routing behind it." : "Pick a conversation to see the full thread and the routing behind each message."
|
|
2145
|
+
})] }), /* @__PURE__ */ jsx("button", {
|
|
2146
|
+
className: "vadm-btn vadm-btn-sm",
|
|
2147
|
+
onClick: refresh,
|
|
2148
|
+
children: "Refresh"
|
|
2149
|
+
})]
|
|
2150
|
+
}),
|
|
2151
|
+
filter ? /* @__PURE__ */ jsxs("div", {
|
|
2152
|
+
className: "vadm-filter-pill",
|
|
2153
|
+
children: [
|
|
2154
|
+
/* @__PURE__ */ jsx("span", {
|
|
2155
|
+
className: "vadm-muted",
|
|
2156
|
+
children: "Conversation with"
|
|
2157
|
+
}),
|
|
2158
|
+
/* @__PURE__ */ jsx("strong", { children: filter.label }),
|
|
2159
|
+
/* @__PURE__ */ jsx("button", {
|
|
2160
|
+
className: "vadm-btn vadm-btn-sm",
|
|
2161
|
+
onClick: clearFilter,
|
|
2162
|
+
children: "← All"
|
|
2163
|
+
})
|
|
2164
|
+
]
|
|
2165
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
2166
|
+
className: "vadm-search",
|
|
2167
|
+
style: {
|
|
2168
|
+
position: "relative",
|
|
2169
|
+
marginBottom: 12
|
|
2170
|
+
},
|
|
2171
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
2172
|
+
className: "vadm-input",
|
|
2173
|
+
placeholder: "Find by account email…",
|
|
2174
|
+
value: query,
|
|
2175
|
+
onChange: (e) => setQuery(e.target.value)
|
|
2176
|
+
}), hits.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
2177
|
+
className: "vadm-card",
|
|
2178
|
+
style: {
|
|
2179
|
+
position: "absolute",
|
|
2180
|
+
zIndex: 5,
|
|
2181
|
+
top: 38,
|
|
2182
|
+
left: 0,
|
|
2183
|
+
right: 0,
|
|
2184
|
+
padding: 4
|
|
2185
|
+
},
|
|
2186
|
+
children: hits.map((hit) => /* @__PURE__ */ jsxs("button", {
|
|
2187
|
+
className: "vadm-user-row",
|
|
2188
|
+
onClick: () => pickUser(hit),
|
|
2189
|
+
children: [/* @__PURE__ */ jsx(UserAvatar, { user: hit }), /* @__PURE__ */ jsxs("span", {
|
|
2190
|
+
className: "vadm-grow",
|
|
2191
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2192
|
+
style: {
|
|
2193
|
+
display: "block",
|
|
2194
|
+
fontWeight: 600
|
|
2195
|
+
},
|
|
2196
|
+
children: hit.name || hit.email
|
|
2197
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2198
|
+
className: "vadm-muted",
|
|
2199
|
+
style: { fontSize: 12 },
|
|
2200
|
+
children: hit.email
|
|
2201
|
+
})]
|
|
2202
|
+
})]
|
|
2203
|
+
}, hit.id))
|
|
2204
|
+
})]
|
|
2205
|
+
}), senders.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
2206
|
+
className: "vadm-userchips",
|
|
2207
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2208
|
+
className: "vadm-muted",
|
|
2209
|
+
style: { fontSize: 12 },
|
|
2210
|
+
children: "Recent:"
|
|
2211
|
+
}), senders.slice(0, 8).map((s) => /* @__PURE__ */ jsxs("button", {
|
|
2212
|
+
className: "vadm-userchip",
|
|
2213
|
+
onClick: () => openConversation({
|
|
2214
|
+
id: s.userId,
|
|
2215
|
+
label: maskedPhone(s.phone)
|
|
2216
|
+
}),
|
|
2217
|
+
children: [maskedPhone(s.phone), /* @__PURE__ */ jsx("span", {
|
|
2218
|
+
className: "vadm-userchip-count",
|
|
2219
|
+
children: s.count
|
|
2220
|
+
})]
|
|
2221
|
+
}, s.userId))]
|
|
2222
|
+
})] }),
|
|
2223
|
+
/* @__PURE__ */ jsx(ErrorBanner, { error }),
|
|
2224
|
+
filter ? transcript === null ? /* @__PURE__ */ jsx(BrainTraceSkeleton, {}) : transcript.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
2225
|
+
className: "vadm-empty",
|
|
2226
|
+
children: [/* @__PURE__ */ jsx("strong", {
|
|
2227
|
+
style: {
|
|
2228
|
+
display: "block",
|
|
2229
|
+
color: "var(--foreground)",
|
|
2230
|
+
marginBottom: 5
|
|
2231
|
+
},
|
|
2232
|
+
children: "No messages yet"
|
|
2233
|
+
}), "This account has no iMessage history with the line."]
|
|
2234
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
2235
|
+
className: "vadm-conv",
|
|
2236
|
+
ref: scrollRef,
|
|
2237
|
+
children: [hasMore && /* @__PURE__ */ jsx("div", {
|
|
2238
|
+
className: "vadm-conv-more",
|
|
2239
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
2240
|
+
className: "vadm-btn vadm-btn-sm",
|
|
2241
|
+
onClick: loadOlder,
|
|
2242
|
+
disabled: loadingMore,
|
|
2243
|
+
children: loadingMore ? "Loading…" : "Load older messages"
|
|
2244
|
+
})
|
|
2245
|
+
}), transcript.map((msg) => {
|
|
2246
|
+
const inbound = msg.direction === "in";
|
|
2247
|
+
const trace = traceForMessage(msg);
|
|
2248
|
+
const open = openMsgId === msg.id;
|
|
2249
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2250
|
+
className: `vadm-bubble-row vadm-bubble-row--${inbound ? "in" : "out"}`,
|
|
2251
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
2252
|
+
type: "button",
|
|
2253
|
+
className: "vadm-bubble",
|
|
2254
|
+
"data-dir": inbound ? "in" : "out",
|
|
2255
|
+
"data-open": open ? "true" : void 0,
|
|
2256
|
+
"data-has-trace": trace ? "true" : void 0,
|
|
2257
|
+
"aria-expanded": trace ? open : void 0,
|
|
2258
|
+
onClick: trace ? () => setOpenMsgId(open ? null : msg.id) : void 0,
|
|
2259
|
+
children: [
|
|
2260
|
+
msg.media && msg.media.length > 0 && /* @__PURE__ */ jsx("span", {
|
|
2261
|
+
className: "vadm-bubble-media",
|
|
2262
|
+
children: msg.media.map((url) => /\.(mp4|mov|webm)(?:$|\?)/i.test(url) ? /* @__PURE__ */ jsx("video", {
|
|
2263
|
+
className: "vadm-bubble-att",
|
|
2264
|
+
src: url,
|
|
2265
|
+
controls: true,
|
|
2266
|
+
preload: "metadata"
|
|
2267
|
+
}, url) : /* @__PURE__ */ jsx("img", {
|
|
2268
|
+
className: "vadm-bubble-att",
|
|
2269
|
+
src: url,
|
|
2270
|
+
alt: "attachment",
|
|
2271
|
+
loading: "lazy"
|
|
2272
|
+
}, url))
|
|
2273
|
+
}),
|
|
2274
|
+
msg.text && /* @__PURE__ */ jsx("span", {
|
|
2275
|
+
className: "vadm-bubble-text",
|
|
2276
|
+
children: msg.text
|
|
2277
|
+
}),
|
|
2278
|
+
/* @__PURE__ */ jsxs("span", {
|
|
2279
|
+
className: "vadm-bubble-meta",
|
|
2280
|
+
children: [
|
|
2281
|
+
/* @__PURE__ */ jsx("span", { children: inbound ? "them" : "us" }),
|
|
2282
|
+
/* @__PURE__ */ jsx("span", {
|
|
2283
|
+
"aria-hidden": "true",
|
|
2284
|
+
children: "·"
|
|
2285
|
+
}),
|
|
2286
|
+
/* @__PURE__ */ jsx("time", {
|
|
2287
|
+
dateTime: new Date(msg.at).toISOString(),
|
|
2288
|
+
children: shortTime(msg.at)
|
|
2289
|
+
}),
|
|
2290
|
+
trace && /* @__PURE__ */ jsx("span", {
|
|
2291
|
+
className: "vadm-bubble-dot",
|
|
2292
|
+
"data-outcome": trace.outcome,
|
|
2293
|
+
"aria-hidden": "true"
|
|
2294
|
+
})
|
|
2295
|
+
]
|
|
2296
|
+
})
|
|
2297
|
+
]
|
|
2298
|
+
}), open && trace && /* @__PURE__ */ jsx("div", {
|
|
2299
|
+
className: "vadm-bubble-detail",
|
|
2300
|
+
children: /* @__PURE__ */ jsx(TraceRouting, {
|
|
2301
|
+
trace,
|
|
2302
|
+
steps: steps.get(trace.id) ?? []
|
|
2303
|
+
})
|
|
2304
|
+
})]
|
|
2305
|
+
}, msg.id);
|
|
2306
|
+
})]
|
|
2307
|
+
}) : traces === null ? /* @__PURE__ */ jsx(BrainTraceSkeleton, {}) : senders.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
2308
|
+
className: "vadm-empty",
|
|
2309
|
+
children: [
|
|
2310
|
+
/* @__PURE__ */ jsx("strong", {
|
|
2311
|
+
style: {
|
|
2312
|
+
display: "block",
|
|
2313
|
+
color: "var(--foreground)",
|
|
2314
|
+
marginBottom: 5
|
|
2315
|
+
},
|
|
2316
|
+
children: "No conversations yet"
|
|
2317
|
+
}),
|
|
2318
|
+
"No inbound iMessage has arrived since capture started. Send the line a message, then refresh.",
|
|
2319
|
+
capture?.gatewayStartedAt && /* @__PURE__ */ jsxs("span", {
|
|
2320
|
+
style: {
|
|
2321
|
+
display: "block",
|
|
2322
|
+
marginTop: 7,
|
|
2323
|
+
fontSize: 12
|
|
2324
|
+
},
|
|
2325
|
+
children: ["Gateway online since ", new Date(capture.gatewayStartedAt).toLocaleString()]
|
|
2326
|
+
})
|
|
2327
|
+
]
|
|
2328
|
+
}) : null
|
|
2329
|
+
] });
|
|
2330
|
+
}
|
|
2331
|
+
/** The routing story for one message: the flow graph + error + detail grid.
|
|
2332
|
+
* Rendered inline when a message row is expanded. */
|
|
2333
|
+
/** Kind → label + accent. Rules read as ✓/· in their own name (set by the
|
|
2334
|
+
* gateway), so the badge only needs to name the category. */
|
|
2335
|
+
const STEP_KIND_LABEL = {
|
|
2336
|
+
rule: "rule",
|
|
2337
|
+
context: "context",
|
|
2338
|
+
prompt: "prompt",
|
|
2339
|
+
"llm-text": "model",
|
|
2340
|
+
"tool-call": "tool →",
|
|
2341
|
+
"tool-result": "← result",
|
|
2342
|
+
effect: "effect"
|
|
2343
|
+
};
|
|
2344
|
+
/**
|
|
2345
|
+
* The turn, step by step: which deterministic rules were evaluated (and which
|
|
2346
|
+
* declined), the prompt blocks the model was given, every tool call with its
|
|
2347
|
+
* arguments, every result, and the effects committed.
|
|
2348
|
+
*
|
|
2349
|
+
* This is the panel's answer to the operator complaint that motivated it —
|
|
2350
|
+
* "we don't actually see the actual routing logic … what tool calling is
|
|
2351
|
+
* happening behind the scene" (2026-07-25). Before this, the only thing
|
|
2352
|
+
* recorded about a model turn was its duration.
|
|
2353
|
+
*
|
|
2354
|
+
* Long payloads (a prompt snapshot is thousands of chars) collapse to a
|
|
2355
|
+
* summary line and expand on click, so the common case stays scannable.
|
|
2356
|
+
*/
|
|
2357
|
+
function TraceSteps({ steps }) {
|
|
2358
|
+
const [openSeq, setOpenSeq] = useState(null);
|
|
2359
|
+
if (!steps.length) return null;
|
|
2360
|
+
const t0 = steps[0].at;
|
|
2361
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2362
|
+
style: {
|
|
2363
|
+
marginTop: 12,
|
|
2364
|
+
paddingTop: 10,
|
|
2365
|
+
borderTop: "1px dashed var(--vadm-border)"
|
|
2366
|
+
},
|
|
2367
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2368
|
+
style: {
|
|
2369
|
+
fontSize: 11,
|
|
2370
|
+
opacity: .6,
|
|
2371
|
+
marginBottom: 6
|
|
2372
|
+
},
|
|
2373
|
+
children: [
|
|
2374
|
+
steps.length,
|
|
2375
|
+
" step",
|
|
2376
|
+
steps.length === 1 ? "" : "s",
|
|
2377
|
+
" — tap one to see its payload"
|
|
2378
|
+
]
|
|
2379
|
+
}), steps.map((step) => {
|
|
2380
|
+
const open = openSeq === step.seq;
|
|
2381
|
+
const detail = step.detail ?? "";
|
|
2382
|
+
const inline = step.kind === "rule" || detail.length <= 120;
|
|
2383
|
+
const firstLine = detail.split("\n")[0] ?? "";
|
|
2384
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2385
|
+
style: {
|
|
2386
|
+
borderTop: "1px solid var(--vadm-border)",
|
|
2387
|
+
padding: "5px 0"
|
|
2388
|
+
},
|
|
2389
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
2390
|
+
type: "button",
|
|
2391
|
+
onClick: () => setOpenSeq(open ? null : step.seq),
|
|
2392
|
+
style: {
|
|
2393
|
+
display: "flex",
|
|
2394
|
+
gap: 8,
|
|
2395
|
+
alignItems: "baseline",
|
|
2396
|
+
width: "100%",
|
|
2397
|
+
background: "none",
|
|
2398
|
+
border: "none",
|
|
2399
|
+
padding: 0,
|
|
2400
|
+
textAlign: "left",
|
|
2401
|
+
cursor: detail ? "pointer" : "default",
|
|
2402
|
+
color: "inherit",
|
|
2403
|
+
font: "inherit"
|
|
2404
|
+
},
|
|
2405
|
+
children: [
|
|
2406
|
+
/* @__PURE__ */ jsxs("code", {
|
|
2407
|
+
style: {
|
|
2408
|
+
fontSize: 10,
|
|
2409
|
+
opacity: .5,
|
|
2410
|
+
minWidth: 44
|
|
2411
|
+
},
|
|
2412
|
+
children: ["+", formatMs(step.at - t0)]
|
|
2413
|
+
}),
|
|
2414
|
+
/* @__PURE__ */ jsx("span", {
|
|
2415
|
+
style: {
|
|
2416
|
+
fontSize: 10,
|
|
2417
|
+
opacity: .6,
|
|
2418
|
+
minWidth: 58
|
|
2419
|
+
},
|
|
2420
|
+
children: STEP_KIND_LABEL[step.kind]
|
|
2421
|
+
}),
|
|
2422
|
+
/* @__PURE__ */ jsx("strong", {
|
|
2423
|
+
style: { fontSize: 12 },
|
|
2424
|
+
children: step.name ?? ""
|
|
2425
|
+
}),
|
|
2426
|
+
step.durationMs != null && /* @__PURE__ */ jsx("span", {
|
|
2427
|
+
style: {
|
|
2428
|
+
fontSize: 10,
|
|
2429
|
+
opacity: .5
|
|
2430
|
+
},
|
|
2431
|
+
children: formatMs(step.durationMs)
|
|
2432
|
+
}),
|
|
2433
|
+
!open && !inline && firstLine && /* @__PURE__ */ jsx("span", {
|
|
2434
|
+
style: {
|
|
2435
|
+
fontSize: 11,
|
|
2436
|
+
opacity: .55,
|
|
2437
|
+
overflow: "hidden",
|
|
2438
|
+
whiteSpace: "nowrap",
|
|
2439
|
+
textOverflow: "ellipsis"
|
|
2440
|
+
},
|
|
2441
|
+
children: firstLine
|
|
2442
|
+
})
|
|
2443
|
+
]
|
|
2444
|
+
}), (open || inline) && detail && /* @__PURE__ */ jsx("pre", {
|
|
2445
|
+
style: {
|
|
2446
|
+
margin: "4px 0 0 52px",
|
|
2447
|
+
fontSize: 11,
|
|
2448
|
+
whiteSpace: "pre-wrap",
|
|
2449
|
+
wordBreak: "break-word",
|
|
2450
|
+
opacity: .8,
|
|
2451
|
+
maxHeight: open ? 420 : void 0,
|
|
2452
|
+
overflow: open ? "auto" : void 0
|
|
2453
|
+
},
|
|
2454
|
+
children: detail
|
|
2455
|
+
})]
|
|
2456
|
+
}, step.seq);
|
|
2457
|
+
})]
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
function TraceRouting({ trace, steps }) {
|
|
2461
|
+
const failed = trace.outcome === "failed";
|
|
2462
|
+
const running = trace.outcome === "running";
|
|
2463
|
+
const sessionChanged = trace.activeSessionBefore !== trace.activeSessionAfter;
|
|
2464
|
+
const duration = trace.completedAt ? trace.completedAt - trace.createdAt : null;
|
|
2465
|
+
const targetHref = safeHref(trace.targetSessionUrl);
|
|
2466
|
+
const routed = trace.targetSessionId != null;
|
|
2467
|
+
const [decisionPrefix, decisionVerb] = splitDecision(trace.decision);
|
|
2468
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2469
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2470
|
+
className: "vadm-flow",
|
|
2471
|
+
children: [
|
|
2472
|
+
/* @__PURE__ */ jsx(FlowNode, {
|
|
2473
|
+
variant: "bare",
|
|
2474
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
2475
|
+
className: "vadm-flow-quote",
|
|
2476
|
+
children: trace.text
|
|
2477
|
+
})
|
|
2478
|
+
}),
|
|
2479
|
+
/* @__PURE__ */ jsx(FlowEdge, { label: routeViaLabel(trace.routeVia) }),
|
|
2480
|
+
/* @__PURE__ */ jsxs(FlowNode, {
|
|
2481
|
+
variant: failed ? "danger" : trace.decision.startsWith("computer.") ? "accent" : void 0,
|
|
2482
|
+
sub: trace.reason ? /* @__PURE__ */ jsx("span", {
|
|
2483
|
+
title: trace.reason,
|
|
2484
|
+
children: trace.reason
|
|
2485
|
+
}) : null,
|
|
2486
|
+
children: [decisionPrefix && /* @__PURE__ */ jsx("span", {
|
|
2487
|
+
className: "vadm-flow-pre",
|
|
2488
|
+
children: decisionPrefix
|
|
2489
|
+
}), decisionVerb]
|
|
2490
|
+
}),
|
|
2491
|
+
routed && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FlowEdge, {
|
|
2492
|
+
label: running ? null : duration != null ? formatMs(duration) : null,
|
|
2493
|
+
danger: failed,
|
|
2494
|
+
running
|
|
2495
|
+
}), /* @__PURE__ */ jsx(FlowNode, {
|
|
2496
|
+
variant: sessionChanged ? "changed" : void 0,
|
|
2497
|
+
mono: true,
|
|
2498
|
+
href: targetHref ?? void 0,
|
|
2499
|
+
sub: sessionChanged ? `was ${shortId(trace.activeSessionBefore)}` : null,
|
|
2500
|
+
children: shortId(trace.targetSessionId)
|
|
2501
|
+
})] })
|
|
2502
|
+
]
|
|
2503
|
+
}),
|
|
2504
|
+
trace.error && /* @__PURE__ */ jsx("div", {
|
|
2505
|
+
className: "vadm-err vadm-flow-error",
|
|
2506
|
+
children: trace.error
|
|
2507
|
+
}),
|
|
2508
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2509
|
+
className: "vadm-kv",
|
|
2510
|
+
style: {
|
|
2511
|
+
marginTop: 12,
|
|
2512
|
+
paddingTop: 10,
|
|
2513
|
+
borderTop: "1px dashed var(--vadm-border)"
|
|
2514
|
+
},
|
|
2515
|
+
children: [
|
|
2516
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", { children: "Generation" }), /* @__PURE__ */ jsx("strong", { children: trace.generation })] }),
|
|
2517
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", { children: "From" }), /* @__PURE__ */ jsx("strong", { children: maskedPhone(trace.phone) })] }),
|
|
2518
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", { children: "Reply to" }), /* @__PURE__ */ jsx("strong", { children: /* @__PURE__ */ jsx("code", { children: shortId(trace.replyToMessageId) }) })] }),
|
|
2519
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", { children: "Timing" }), /* @__PURE__ */ jsx("strong", { children: timingLine(trace.fleetMs, trace.llmMs, duration) })] }),
|
|
2520
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", { children: "Session" }), /* @__PURE__ */ jsx("strong", { children: /* @__PURE__ */ jsxs("code", { children: [
|
|
2521
|
+
shortId(trace.activeSessionBefore),
|
|
2522
|
+
" → ",
|
|
2523
|
+
shortId(trace.activeSessionAfter)
|
|
2524
|
+
] }) })] }),
|
|
2525
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", { children: "Message" }), /* @__PURE__ */ jsx("strong", { children: trace.text })] }),
|
|
2526
|
+
trace.reason && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", { children: "Reason" }), /* @__PURE__ */ jsx("strong", { children: trace.reason })] })
|
|
2527
|
+
]
|
|
2528
|
+
}),
|
|
2529
|
+
/* @__PURE__ */ jsx(TraceSteps, { steps })
|
|
2530
|
+
] });
|
|
2531
|
+
}
|
|
1553
2532
|
const DEFAULT_PANELS = [
|
|
2533
|
+
{
|
|
2534
|
+
id: "brain",
|
|
2535
|
+
label: "Chat Brain",
|
|
2536
|
+
render: (c) => /* @__PURE__ */ jsx(BrainRoutingPanel, { client: c })
|
|
2537
|
+
},
|
|
1554
2538
|
{
|
|
1555
2539
|
id: "flags",
|
|
1556
2540
|
label: "Flags",
|
|
@@ -1600,4 +2584,4 @@ function AdminConsole({ client, config, panels = DEFAULT_PANELS }) {
|
|
|
1600
2584
|
});
|
|
1601
2585
|
}
|
|
1602
2586
|
//#endregion
|
|
1603
|
-
export {
|
|
2587
|
+
export { ReportsPanel as a, AdminError as c, PricingPanel as i, STYLE_ID as l, BrainRoutingPanel as n, UsersPanel as o, FlagsPanel as r, AdminClient as s, AdminConsole as t, ensureStyles as u };
|