@burdenoff/microfe-vibecontrols 2026.613.1 → 2026.618.1
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/components/agents/AgentConnectionRecovery.js +100 -94
- package/dist/components/agents/AgentConnectionRecovery.js.map +1 -1
- package/dist/components/agents/AgentMaintenanceTab.js +177 -160
- package/dist/components/agents/AgentMaintenanceTab.js.map +1 -1
- package/dist/components/agents/OpenInBrowserEditorButton.js +102 -94
- package/dist/components/agents/OpenInBrowserEditorButton.js.map +1 -1
- package/dist/components/assistant/AssistantChatArea.js +1 -0
- package/dist/components/assistant/AssistantChatArea.js.map +1 -1
- package/dist/components/assistant/AssistantModeSelector.js +20 -19
- package/dist/components/assistant/AssistantModeSelector.js.map +1 -1
- package/dist/components/tunnels/TunnelCard.js +3 -3
- package/dist/components/tunnels/TunnelCard.js.map +1 -1
- package/dist/components/tunnels/TunnelPublicAccessPanel.js +36 -36
- package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
- package/dist/config/assistantUsecases.js +11 -0
- package/dist/config/assistantUsecases.js.map +1 -1
- package/dist/constants/permissions.js +38 -6
- package/dist/constants/permissions.js.map +1 -1
- package/dist/index.js +4 -3
- package/dist/pages/CatalogDetailPage.js +155 -155
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/CatalogPage.js +110 -109
- package/dist/pages/CatalogPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +338 -338
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/ComponentsPage.js +104 -103
- package/dist/pages/ComponentsPage.js.map +1 -1
- package/dist/pages/TargetDetailsPage.js +140 -132
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +201 -199
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/TemplatesPage.js +62 -61
- package/dist/pages/TemplatesPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +568 -555
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/ai/AIBookmarksPage.js +104 -89
- package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
- package/dist/pages/ai/AIPage.js +116 -107
- package/dist/pages/ai/AIPage.js.map +1 -1
- package/dist/pages/ai/AISessionDetailPage.js +46 -34
- package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
- package/dist/pages/ai/AgentManagerPage.js +112 -100
- package/dist/pages/ai/AgentManagerPage.js.map +1 -1
- package/dist/pages/tunnels/TunnelDetailsPage.js +177 -153
- package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
- package/dist/pages/tunnels/TunnelsTab.js +119 -111
- package/dist/pages/tunnels/TunnelsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +24 -24
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +9 -8
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +13 -13
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +19 -15
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
- package/dist/services/assistantApi.js +9 -3
- package/dist/services/assistantApi.js.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js +120 -0
- package/dist/services/createSandboxAssistantTransport.js.map +1 -0
- package/dist/store/assistantStore.js +1 -1
- package/dist/store/assistantStore.js.map +1 -1
- package/package.json +5 -1
|
@@ -10,21 +10,24 @@ import { StatCard as c } from "../../components/shared/StatCard.js";
|
|
|
10
10
|
import { RequireAgentReachable as ee } from "../../components/shared/RequireAgentReachable.js";
|
|
11
11
|
import { useActiveAgentProfile as te } from "../../hooks/useActiveAgentProfile.js";
|
|
12
12
|
import "../../components/shared/index.js";
|
|
13
|
-
import {
|
|
13
|
+
import { VIBE_PERMISSIONS as ne } from "../../constants/permissions.js";
|
|
14
|
+
import { fetchSessionLogs as re, fetchSessionStats as ie, sendPromptToSession as ae, terminateSession as oe, updateSessionConfig as se } from "../../services/aiApi.js";
|
|
14
15
|
import { isSessionNotFoundError as l } from "../../utils/aiSessionNotFound.js";
|
|
15
|
-
import { useQueryBackedTab as
|
|
16
|
+
import { useQueryBackedTab as ce } from "../../hooks/useQueryBackedTab.js";
|
|
16
17
|
import { useCallback as u, useEffect as d, useMemo as f, useState as p } from "react";
|
|
17
|
-
import { ArrowLeft as
|
|
18
|
-
import { useParams as
|
|
18
|
+
import { ArrowLeft as le, BarChart3 as m, MessageSquare as h, RefreshCw as g, ScrollText as _, Send as ue, Settings as v, XCircle as de } from "lucide-react";
|
|
19
|
+
import { useParams as fe } from "react-router-dom";
|
|
20
|
+
import { usePermissions as pe } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
19
21
|
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
22
|
+
import { AccessDenied as me } from "@burdenoff/fe-libs/shared/components";
|
|
20
23
|
import { MotionPage as x } from "@burdenoff/fe-libs/shared/motion";
|
|
21
24
|
//#region src/pages/ai/AISessionDetailPage.tsx
|
|
22
|
-
var
|
|
25
|
+
var he = [
|
|
23
26
|
"chat",
|
|
24
27
|
"logs",
|
|
25
28
|
"stats",
|
|
26
29
|
"config"
|
|
27
|
-
],
|
|
30
|
+
], ge = [
|
|
28
31
|
{
|
|
29
32
|
id: "chat",
|
|
30
33
|
label: "Chat",
|
|
@@ -45,7 +48,7 @@ var fe = [
|
|
|
45
48
|
label: "Config",
|
|
46
49
|
icon: v
|
|
47
50
|
}
|
|
48
|
-
],
|
|
51
|
+
], _e = {
|
|
49
52
|
info: "bg-action-primary-bg/10 text-action-primary-bg",
|
|
50
53
|
warn: "bg-status-warning-bg text-status-warning-text",
|
|
51
54
|
error: "bg-status-error-bg text-status-error-text",
|
|
@@ -54,10 +57,10 @@ var fe = [
|
|
|
54
57
|
response: "bg-action-primary-bg/20 text-action-primary-bg"
|
|
55
58
|
};
|
|
56
59
|
function S() {
|
|
57
|
-
let S = e(), { sessionId: C } =
|
|
60
|
+
let S = e(), { hasPermission: ve, isLoading: ye } = pe(), be = ve(ne.AGENT_READ), { sessionId: C } = fe(), w = t(), { agents: T, loading: xe } = n(), E = f(() => T.find((e) => e.isActive && e.tunnelUrl) ?? null, [T]), D = E?.id ?? "", O = !!D, { active: k } = te(E?.id ?? null), A = f(() => ({
|
|
58
61
|
id: D,
|
|
59
62
|
profile: k
|
|
60
|
-
}), [D, k]), [j,
|
|
63
|
+
}), [D, k]), [j, Se] = ce(he, "chat"), [M, N] = p([]), [P, F] = p(""), [I, L] = p(!1), R = async () => {
|
|
61
64
|
if (!P.trim() || !C || !O) return;
|
|
62
65
|
let e = {
|
|
63
66
|
role: "user",
|
|
@@ -66,7 +69,7 @@ function S() {
|
|
|
66
69
|
};
|
|
67
70
|
N((t) => [...t, e]), F(""), L(!0);
|
|
68
71
|
try {
|
|
69
|
-
let t = await
|
|
72
|
+
let t = await ae(A, C, e.content), n = t.content || t.response || JSON.stringify(t), r = t.inputTokens, i = t.outputTokens, a = {
|
|
70
73
|
role: "assistant",
|
|
71
74
|
content: n,
|
|
72
75
|
tokens: r === void 0 ? void 0 : {
|
|
@@ -86,12 +89,12 @@ function S() {
|
|
|
86
89
|
} finally {
|
|
87
90
|
L(!1);
|
|
88
91
|
}
|
|
89
|
-
}, [z, B] = p([]), [V, H] = p(!1), [U, W] = p(null), [G,
|
|
92
|
+
}, [z, B] = p([]), [V, H] = p(!1), [U, W] = p(null), [G, Ce] = p("all"), K = u(async () => {
|
|
90
93
|
if (!(!C || !O)) {
|
|
91
94
|
H(!0), W(null);
|
|
92
95
|
try {
|
|
93
96
|
let e = {};
|
|
94
|
-
G !== "all" && (e.type = G), B(await
|
|
97
|
+
G !== "all" && (e.type = G), B(await re(A, C, e));
|
|
95
98
|
} catch (e) {
|
|
96
99
|
l(e) ? B([]) : W(e instanceof Error ? e.message : "Failed to load logs");
|
|
97
100
|
} finally {
|
|
@@ -107,11 +110,11 @@ function S() {
|
|
|
107
110
|
d(() => {
|
|
108
111
|
j === "logs" && K();
|
|
109
112
|
}, [j, K]);
|
|
110
|
-
let [q, J] = p(null), [
|
|
113
|
+
let [q, J] = p(null), [we, Y] = p(!1), [X, Z] = p(null), Q = u(async () => {
|
|
111
114
|
if (!(!C || !O)) {
|
|
112
115
|
Y(!0), Z(null);
|
|
113
116
|
try {
|
|
114
|
-
J(await
|
|
117
|
+
J(await ie(A, C));
|
|
115
118
|
} catch (e) {
|
|
116
119
|
l(e) ? J(null) : Z(e instanceof Error ? e.message : "Failed to load stats");
|
|
117
120
|
} finally {
|
|
@@ -126,8 +129,11 @@ function S() {
|
|
|
126
129
|
d(() => {
|
|
127
130
|
j === "stats" && Q();
|
|
128
131
|
}, [j, Q]);
|
|
129
|
-
let [
|
|
130
|
-
return
|
|
132
|
+
let [Te, Ee] = p("{}"), [De, Oe] = p(!1), [ke, Ae] = p(!1), [je, $] = p(!1);
|
|
133
|
+
return ye ? /* @__PURE__ */ y(x, {
|
|
134
|
+
className: "space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen",
|
|
135
|
+
children: /* @__PURE__ */ y(i, { message: S("common.loadingPermissions", "Loading permissions…") })
|
|
136
|
+
}) : be ? xe ? /* @__PURE__ */ y(x, {
|
|
131
137
|
className: "space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen",
|
|
132
138
|
children: /* @__PURE__ */ y(i, { message: "Loading..." })
|
|
133
139
|
}) : O ? /* @__PURE__ */ b(x, {
|
|
@@ -151,7 +157,7 @@ function S() {
|
|
|
151
157
|
type: "button",
|
|
152
158
|
onClick: () => w("/ai/sessions"),
|
|
153
159
|
className: "flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors text-sm",
|
|
154
|
-
children: [/* @__PURE__ */ y(
|
|
160
|
+
children: [/* @__PURE__ */ y(le, { className: "size-4" }), S("vibecontrols.pages.ai.back", "Back")]
|
|
155
161
|
})
|
|
156
162
|
}),
|
|
157
163
|
/* @__PURE__ */ y("div", {
|
|
@@ -159,13 +165,13 @@ function S() {
|
|
|
159
165
|
children: /* @__PURE__ */ y("nav", {
|
|
160
166
|
className: "flex gap-1 overflow-x-auto pb-1",
|
|
161
167
|
role: "tablist",
|
|
162
|
-
children:
|
|
168
|
+
children: ge.map((e) => {
|
|
163
169
|
let t = e.icon, n = j === e.id;
|
|
164
170
|
return /* @__PURE__ */ b("button", {
|
|
165
171
|
type: "button",
|
|
166
172
|
role: "tab",
|
|
167
173
|
"aria-selected": n,
|
|
168
|
-
onClick: () =>
|
|
174
|
+
onClick: () => Se(e.id),
|
|
169
175
|
className: `flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-t-md border-b-2 px-2.5 py-2 text-xs font-medium transition-colors sm:px-3 sm:py-2.5 sm:text-sm ${n ? "border-action-primary-bg text-action-primary-bg" : "border-transparent text-text-secondary hover:text-text-primary hover:border-border-default"}`,
|
|
170
176
|
children: [/* @__PURE__ */ y(t, { className: "size-3.5 sm:h-4 sm:w-4" }), e.label]
|
|
171
177
|
}, e.id);
|
|
@@ -234,7 +240,7 @@ function S() {
|
|
|
234
240
|
onClick: R,
|
|
235
241
|
disabled: I || !P.trim(),
|
|
236
242
|
className: "flex items-center justify-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm text-action-primary-text transition-colors hover:bg-action-primary-bg-hover disabled:opacity-50 sm:self-end",
|
|
237
|
-
children: [/* @__PURE__ */ y(
|
|
243
|
+
children: [/* @__PURE__ */ y(ue, { className: "size-4" }), S("vibecontrols.pages.ai.send", "Send")]
|
|
238
244
|
})]
|
|
239
245
|
})]
|
|
240
246
|
}),
|
|
@@ -244,7 +250,7 @@ function S() {
|
|
|
244
250
|
className: "flex flex-wrap items-center gap-2",
|
|
245
251
|
children: [/* @__PURE__ */ b("select", {
|
|
246
252
|
value: G,
|
|
247
|
-
onChange: (e) =>
|
|
253
|
+
onChange: (e) => Ce(e.target.value),
|
|
248
254
|
className: "px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm",
|
|
249
255
|
children: [
|
|
250
256
|
/* @__PURE__ */ y("option", {
|
|
@@ -299,7 +305,7 @@ function S() {
|
|
|
299
305
|
className: "px-4 py-2 flex items-start gap-3 text-sm",
|
|
300
306
|
children: [
|
|
301
307
|
/* @__PURE__ */ y("span", {
|
|
302
|
-
className: `px-1.5 py-0.5 text-xs rounded font-medium flex-shrink-0 mt-0.5 ${
|
|
308
|
+
className: `px-1.5 py-0.5 text-xs rounded font-medium flex-shrink-0 mt-0.5 ${_e[e.type] ?? "bg-bg-sunken text-text-secondary"}`,
|
|
303
309
|
children: e.type
|
|
304
310
|
}),
|
|
305
311
|
/* @__PURE__ */ y("div", {
|
|
@@ -320,7 +326,7 @@ function S() {
|
|
|
320
326
|
}),
|
|
321
327
|
j === "stats" && /* @__PURE__ */ y("div", {
|
|
322
328
|
className: "space-y-4",
|
|
323
|
-
children:
|
|
329
|
+
children: we ? /* @__PURE__ */ y(i, { message: "Loading stats..." }) : X ? /* @__PURE__ */ y(a, {
|
|
324
330
|
title: "Failed to load stats",
|
|
325
331
|
message: X,
|
|
326
332
|
onRetry: Q
|
|
@@ -367,8 +373,8 @@ function S() {
|
|
|
367
373
|
}),
|
|
368
374
|
/* @__PURE__ */ y("textarea", {
|
|
369
375
|
"aria-label": "control",
|
|
370
|
-
value:
|
|
371
|
-
onChange: (e) =>
|
|
376
|
+
value: Te,
|
|
377
|
+
onChange: (e) => Ee(e.target.value),
|
|
372
378
|
rows: 10,
|
|
373
379
|
className: "w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm font-mono focus:outline-none focus:border-border-strong",
|
|
374
380
|
placeholder: "{\"model\": \"claude-3\", \"temperature\": 0.7, ...}"
|
|
@@ -379,20 +385,20 @@ function S() {
|
|
|
379
385
|
type: "button",
|
|
380
386
|
onClick: async () => {
|
|
381
387
|
if (!(!C || !O)) {
|
|
382
|
-
|
|
388
|
+
Oe(!0);
|
|
383
389
|
try {
|
|
384
|
-
await
|
|
390
|
+
await se(A, C, JSON.parse(Te)), Ae(!0), setTimeout(() => Ae(!1), 3e3);
|
|
385
391
|
} catch (e) {
|
|
386
392
|
console.error("Failed to save config:", e);
|
|
387
393
|
} finally {
|
|
388
|
-
|
|
394
|
+
Oe(!1);
|
|
389
395
|
}
|
|
390
396
|
}
|
|
391
397
|
},
|
|
392
|
-
disabled:
|
|
398
|
+
disabled: De,
|
|
393
399
|
className: "flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 text-sm",
|
|
394
|
-
children: [/* @__PURE__ */ y(v, { className: "size-4" }),
|
|
395
|
-
}),
|
|
400
|
+
children: [/* @__PURE__ */ y(v, { className: "size-4" }), De ? S("vibecontrols.pages.ai.saving", "Saving...") : S("vibecontrols.pages.ai.saveConfig", "Save Config")]
|
|
401
|
+
}), ke && /* @__PURE__ */ y("span", {
|
|
396
402
|
className: "text-sm text-status-success-text",
|
|
397
403
|
children: S("vibecontrols.pages.ai.savedSuccessfully", "Saved successfully")
|
|
398
404
|
})]
|
|
@@ -415,7 +421,7 @@ function S() {
|
|
|
415
421
|
if (!(!C || !O) && window.confirm("Are you sure you want to terminate this session? This cannot be undone.")) {
|
|
416
422
|
$(!0);
|
|
417
423
|
try {
|
|
418
|
-
await
|
|
424
|
+
await oe(A, C), w("/ai/sessions");
|
|
419
425
|
} catch (e) {
|
|
420
426
|
console.error("Failed to terminate session:", e);
|
|
421
427
|
} finally {
|
|
@@ -423,9 +429,9 @@ function S() {
|
|
|
423
429
|
}
|
|
424
430
|
}
|
|
425
431
|
},
|
|
426
|
-
disabled:
|
|
432
|
+
disabled: je,
|
|
427
433
|
className: "flex items-center gap-2 px-4 py-2 bg-status-error-bg text-status-error-text border border-status-error-text/20 rounded-md hover:bg-status-error-bg/80 transition-colors disabled:opacity-50 text-sm",
|
|
428
|
-
children: [/* @__PURE__ */ y(
|
|
434
|
+
children: [/* @__PURE__ */ y(de, { className: "size-4" }), je ? S("vibecontrols.pages.ai.terminating", "Terminating...") : S("vibecontrols.pages.ai.terminateSession", "Terminate Session")]
|
|
429
435
|
})
|
|
430
436
|
]
|
|
431
437
|
})]
|
|
@@ -446,6 +452,12 @@ function S() {
|
|
|
446
452
|
children: S("vibecontrols.pages.ai.goToAgents", "Go to Agents")
|
|
447
453
|
})
|
|
448
454
|
})
|
|
455
|
+
}) : /* @__PURE__ */ y(x, {
|
|
456
|
+
className: "space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen",
|
|
457
|
+
children: /* @__PURE__ */ y(me, {
|
|
458
|
+
title: S("vibecontrols.pages.ai.denied.title", "Access Denied"),
|
|
459
|
+
description: S("vibecontrols.pages.ai.denied.description", "You do not have permission to view AI features.")
|
|
460
|
+
})
|
|
449
461
|
});
|
|
450
462
|
}
|
|
451
463
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AISessionDetailPage.js","names":[],"sources":["../../../src/pages/ai/AISessionDetailPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n MessageSquare,\n ScrollText,\n BarChart3,\n Settings,\n Send,\n ArrowLeft,\n XCircle,\n RefreshCw,\n} from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport {\n PageHeader,\n Card,\n LoadingState,\n ErrorState,\n EmptyState,\n StatCard,\n RequireAgentReachable,\n} from '@/components/shared';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useQueryBackedTab } from '@/hooks/useQueryBackedTab';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport type { AISessionLog, AISessionStats } from '@/services/aiApi';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n sendPromptToSession,\n fetchSessionLogs,\n fetchSessionStats,\n updateSessionConfig,\n terminateSession,\n} from '@/services/aiApi';\nimport { isSessionNotFoundError } from '@/utils/aiSessionNotFound';\n\ntype SessionTab = 'chat' | 'logs' | 'stats' | 'config';\nconst SESSION_TABS: readonly SessionTab[] = ['chat', 'logs', 'stats', 'config'];\n\nconst sessionTabs: { id: SessionTab; label: string; icon: typeof MessageSquare }[] = [\n { id: 'chat', label: 'Chat', icon: MessageSquare },\n { id: 'logs', label: 'Logs', icon: ScrollText },\n { id: 'stats', label: 'Stats', icon: BarChart3 },\n { id: 'config', label: 'Config', icon: Settings },\n];\n\nconst logTypeColors: Record<string, string> = {\n info: 'bg-action-primary-bg/10 text-action-primary-bg',\n warn: 'bg-status-warning-bg text-status-warning-text',\n error: 'bg-status-error-bg text-status-error-text',\n debug: 'bg-bg-sunken text-text-secondary',\n prompt: 'bg-status-success-bg text-status-success-text',\n response: 'bg-action-primary-bg/20 text-action-primary-bg',\n};\n\ninterface ChatMessage {\n role: 'user' | 'assistant';\n content: string;\n tokens?: { input: number; output: number };\n timestamp: string;\n}\n\n/**\n * AI session detail page with Chat, Logs, Stats, and Config tabs.\n */\nexport function AISessionDetailPage() {\n const tr = useTr();\n const { sessionId } = useParams<{ sessionId: string }>();\n const handleNavigate = useVibeNavigate();\n\n const { agents, loading: agentsLoading } = useCachedInitialData();\n const activeAgent = useMemo(\n () => agents.find((agent) => agent.isActive && agent.tunnelUrl) ?? null,\n [agents]\n );\n const agentId = activeAgent?.id ?? '';\n const hasAgent = !!agentId;\n // Canonical agent → profile → plugins/data hierarchy. Every aiApi call\n // takes an AgentRef = { id, profile } so the path can be constructed\n // explicitly at the callsite.\n const { active: activeProfile } = useActiveAgentProfile(activeAgent?.id ?? null);\n const agentRef = useMemo(\n () => ({ id: agentId, profile: activeProfile }),\n [agentId, activeProfile]\n );\n\n const [activeTab, setActiveTab] = useQueryBackedTab<SessionTab>(SESSION_TABS, 'chat');\n\n // ── Chat state ──────────────────────────────────────────────────────\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [promptInput, setPromptInput] = useState('');\n const [sending, setSending] = useState(false);\n\n const handleSendPrompt = async () => {\n if (!promptInput.trim() || !sessionId || !hasAgent) return;\n const userMessage: ChatMessage = {\n role: 'user',\n content: promptInput,\n timestamp: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, userMessage]);\n setPromptInput('');\n setSending(true);\n try {\n const result = await sendPromptToSession(agentRef, sessionId, userMessage.content);\n const content =\n ((result as Record<string, unknown>).content as string) ||\n ((result as Record<string, unknown>).response as string) ||\n JSON.stringify(result);\n const inputTokens = (result as Record<string, unknown>).inputTokens as number | undefined;\n const outputTokens = (result as Record<string, unknown>).outputTokens as number | undefined;\n const assistantMessage: ChatMessage = {\n role: 'assistant',\n content,\n tokens:\n inputTokens !== undefined ? { input: inputTokens, output: outputTokens ?? 0 } : undefined,\n timestamp: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, assistantMessage]);\n } catch (err) {\n const errorMessage: ChatMessage = {\n role: 'assistant',\n content: `Error: ${err instanceof Error ? err.message : 'Failed to get response'}`,\n timestamp: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, errorMessage]);\n } finally {\n setSending(false);\n }\n };\n\n // ── Logs state ──────────────────────────────────────────────────────\n const [logs, setLogs] = useState<AISessionLog[]>([]);\n const [logsLoading, setLogsLoading] = useState(false);\n const [logsError, setLogsError] = useState<string | null>(null);\n const [logTypeFilter, setLogTypeFilter] = useState('all');\n\n const loadLogs = useCallback(async () => {\n if (!sessionId || !hasAgent) return;\n setLogsLoading(true);\n setLogsError(null);\n try {\n const params: Record<string, unknown> = {};\n if (logTypeFilter !== 'all') params.type = logTypeFilter;\n const result = await fetchSessionLogs(agentRef, sessionId, params);\n setLogs(result);\n } catch (err) {\n // Pre-first-message: agent has no record of this session yet —\n // render an empty list, not an error.\n if (isSessionNotFoundError(err)) {\n setLogs([]);\n } else {\n setLogsError(err instanceof Error ? err.message : 'Failed to load logs');\n }\n } finally {\n setLogsLoading(false);\n }\n }, [agentRef, sessionId, logTypeFilter, hasAgent]);\n\n useEffect(() => {\n if (activeTab === 'logs') loadLogs();\n }, [activeTab, loadLogs]);\n\n // ── Stats state ─────────────────────────────────────────────────────\n const [stats, setStats] = useState<AISessionStats | null>(null);\n const [statsLoading, setStatsLoading] = useState(false);\n const [statsError, setStatsError] = useState<string | null>(null);\n\n const loadStats = useCallback(async () => {\n if (!sessionId || !hasAgent) return;\n setStatsLoading(true);\n setStatsError(null);\n try {\n const result = await fetchSessionStats(agentRef, sessionId);\n setStats(result);\n } catch (err) {\n if (isSessionNotFoundError(err)) {\n setStats(null);\n } else {\n setStatsError(err instanceof Error ? err.message : 'Failed to load stats');\n }\n } finally {\n setStatsLoading(false);\n }\n }, [agentRef, sessionId, hasAgent]);\n\n useEffect(() => {\n if (activeTab === 'stats') loadStats();\n }, [activeTab, loadStats]);\n\n // ── Config state ────────────────────────────────────────────────────\n const [configJson, setConfigJson] = useState('{}');\n const [savingConfig, setSavingConfig] = useState(false);\n const [configSaved, setConfigSaved] = useState(false);\n const [terminating, setTerminating] = useState(false);\n\n const handleSaveConfig = async () => {\n if (!sessionId || !hasAgent) return;\n setSavingConfig(true);\n try {\n const config = JSON.parse(configJson);\n await updateSessionConfig(agentRef, sessionId, config);\n setConfigSaved(true);\n setTimeout(() => setConfigSaved(false), 3000);\n } catch (err) {\n console.error('Failed to save config:', err);\n } finally {\n setSavingConfig(false);\n }\n };\n\n const handleTerminate = async () => {\n if (!sessionId || !hasAgent) return;\n if (!window.confirm('Are you sure you want to terminate this session? This cannot be undone.'))\n return;\n setTerminating(true);\n try {\n await terminateSession(agentRef, sessionId);\n handleNavigate('/ai/sessions');\n } catch (err) {\n console.error('Failed to terminate session:', err);\n } finally {\n setTerminating(false);\n }\n };\n\n if (agentsLoading) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message=\"Loading...\" />\n </MotionPage>\n );\n }\n\n if (!hasAgent) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <EmptyState\n icon={<MessageSquare className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.pages.ai.noActiveAgent', 'No active agent')}\n description={tr(\n 'vibecontrols.pages.ai.noActiveAgentDescription',\n 'An active agent with a tunnel is required to view session details.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => handleNavigate('/agents')}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors text-sm\"\n >\n {tr('vibecontrols.pages.ai.goToAgents', 'Go to Agents')}\n </button>\n }\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <PageHeader\n title={`Session ${sessionId?.substring(0, 8) ?? ''}...`}\n description={tr(\n 'vibecontrols.pages.ai.sessionDescription',\n 'AI session details and interaction'\n )}\n breadcrumb={[\n { label: 'AI', path: '#' },\n { label: 'Sessions', path: '#' },\n { label: sessionId?.substring(0, 8) ?? '' },\n ]}\n actions={\n <button\n type=\"button\"\n onClick={() => handleNavigate('/ai/sessions')}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors text-sm\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('vibecontrols.pages.ai.back', 'Back')}\n </button>\n }\n />\n\n {/* Tab Navigation */}\n <div className=\"-mx-4 border-b border-border-default px-4 sm:-mx-6 sm:px-6\">\n <nav className=\"flex gap-1 overflow-x-auto pb-1\" role=\"tablist\">\n {sessionTabs.map((tab) => {\n const Icon = tab.icon;\n const isActive = activeTab === tab.id;\n return (\n <button\n type=\"button\"\n key={tab.id}\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => setActiveTab(tab.id)}\n className={`flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-t-md border-b-2 px-2.5 py-2 text-xs font-medium transition-colors sm:px-3 sm:py-2.5 sm:text-sm ${\n isActive\n ? 'border-action-primary-bg text-action-primary-bg'\n : 'border-transparent text-text-secondary hover:text-text-primary hover:border-border-default'\n }`}\n >\n <Icon className=\"size-3.5 sm:h-4 sm:w-4\" />\n {tab.label}\n </button>\n );\n })}\n </nav>\n </div>\n\n {/* Tab Content */}\n <RequireAgentReachable agentId={agentId || null}>\n {activeTab === 'chat' && (\n <div className=\"space-y-4\">\n {/* Messages */}\n <Card className=\"p-0\">\n <div className=\"divide-y divide-border-default min-h-[200px] max-h-[50vh] overflow-y-auto\">\n {messages.length === 0 ? (\n <div className=\"flex items-center justify-center h-48 text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.pages.ai.sendPromptToStart',\n 'Send a prompt to start the conversation'\n )}\n </div>\n ) : (\n messages.map((msg, idx) => (\n <div\n key={idx}\n className={`px-4 py-3 ${msg.role === 'assistant' ? 'bg-bg-sunken/50' : ''}`}\n >\n <div className=\"mb-1 flex flex-wrap items-center gap-2\">\n <span className=\"text-xs font-semibold text-text-primary uppercase\">\n {msg.role}\n </span>\n <span className=\"text-xs text-text-muted\">\n {new Date(msg.timestamp).toLocaleTimeString()}\n </span>\n {msg.tokens && (\n <span className=\"text-xs text-text-muted sm:ml-auto\">\n {msg.tokens.input + msg.tokens.output} tokens\n </span>\n )}\n </div>\n <div className=\"text-sm text-text-primary whitespace-pre-wrap\">\n {msg.content}\n </div>\n </div>\n ))\n )}\n {sending && (\n <div className=\"px-4 py-3 bg-bg-sunken/50\">\n <div className=\"flex items-center gap-2\">\n <RefreshCw className=\"size-3.5 text-text-secondary animate-spin\" />\n <span className=\"text-sm text-text-secondary\">\n {tr('vibecontrols.pages.ai.thinking', 'Thinking...')}\n </span>\n </div>\n </div>\n )}\n </div>\n </Card>\n\n {/* Prompt Input */}\n <div className=\"flex flex-col gap-2 sm:flex-row\">\n <textarea\n aria-label=\"Type your prompt...\"\n value={promptInput}\n onChange={(e) => setPromptInput(e.target.value)}\n rows={3}\n className=\"min-h-[7rem] flex-1 rounded-md border border-border-default bg-bg-sunken px-3 py-2 text-sm font-mono text-text-primary resize-none focus:outline-none focus:border-border-strong\"\n placeholder=\"Type your prompt...\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleSendPrompt();\n }\n }}\n />\n <button\n type=\"button\"\n onClick={handleSendPrompt}\n disabled={sending || !promptInput.trim()}\n className=\"flex items-center justify-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm text-action-primary-text transition-colors hover:bg-action-primary-bg-hover disabled:opacity-50 sm:self-end\"\n >\n <Send className=\"size-4\" />\n {tr('vibecontrols.pages.ai.send', 'Send')}\n </button>\n </div>\n </div>\n )}\n\n {activeTab === 'logs' && (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <select\n value={logTypeFilter}\n onChange={(e) => setLogTypeFilter(e.target.value)}\n className=\"px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm\"\n >\n <option value=\"all\">All Types</option>\n <option value=\"info\">Info</option>\n <option value=\"warn\">Warning</option>\n <option value=\"error\">Error</option>\n <option value=\"debug\">Debug</option>\n <option value=\"prompt\">Prompt</option>\n <option value=\"response\">Response</option>\n </select>\n <button\n type=\"button\"\n onClick={loadLogs}\n disabled={logsLoading}\n className=\"p-2 hover:bg-bg-sunken rounded-md transition-colors\"\n >\n <RefreshCw\n className={`size-4 text-text-secondary ${logsLoading ? 'animate-spin' : ''}`}\n />\n </button>\n </div>\n\n {logsLoading ? (\n <LoadingState message=\"Loading logs...\" />\n ) : logsError ? (\n <ErrorState title=\"Failed to load logs\" message={logsError} onRetry={loadLogs} />\n ) : logs.length === 0 ? (\n <EmptyState\n icon={<ScrollText className=\"size-6 text-text-secondary\" />}\n title=\"No logs\"\n description=\"Session logs will appear here\"\n />\n ) : (\n <Card className=\"p-0\">\n <div className=\"divide-y divide-border-default max-h-[60vh] overflow-y-auto\">\n {logs.map((log) => (\n <div key={log.id} className=\"px-4 py-2 flex items-start gap-3 text-sm\">\n <span\n className={`px-1.5 py-0.5 text-xs rounded font-medium flex-shrink-0 mt-0.5 ${logTypeColors[log.type] ?? 'bg-bg-sunken text-text-secondary'}`}\n >\n {log.type}\n </span>\n <div className=\"flex-1 min-w-0\">\n <span className=\"text-text-primary\">{log.message}</span>\n </div>\n <span className=\"text-xs text-text-muted flex-shrink-0\">\n {new Date(log.timestamp).toLocaleTimeString()}\n </span>\n </div>\n ))}\n </div>\n </Card>\n )}\n </div>\n )}\n\n {activeTab === 'stats' && (\n <div className=\"space-y-4\">\n {statsLoading ? (\n <LoadingState message=\"Loading stats...\" />\n ) : statsError ? (\n <ErrorState title=\"Failed to load stats\" message={statsError} onRetry={loadStats} />\n ) : stats ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n <StatCard title=\"Total Tokens\" value={stats.totalTokens.toLocaleString()} />\n <StatCard\n title=\"Input Tokens\"\n value={stats.inputTokens.toLocaleString()}\n variant=\"success\"\n />\n <StatCard\n title=\"Output Tokens\"\n value={stats.outputTokens.toLocaleString()}\n variant=\"warning\"\n />\n <StatCard title=\"Prompts\" value={stats.promptCount} />\n {stats.averageLatency !== undefined && (\n <StatCard title=\"Avg Latency\" value={`${stats.averageLatency.toFixed(0)}ms`} />\n )}\n </div>\n ) : (\n <EmptyState\n icon={<BarChart3 className=\"size-6 text-text-secondary\" />}\n title=\"No stats\"\n description=\"Stats will appear after the session processes prompts\"\n />\n )}\n </div>\n )}\n\n {activeTab === 'config' && (\n <div className=\"space-y-4\">\n <Card className=\"p-4\">\n <h3 className=\"font-semibold text-text-primary mb-3\">\n {tr('vibecontrols.pages.ai.sessionConfig', 'Session Configuration')}\n </h3>\n <textarea\n aria-label=\"control\"\n value={configJson}\n onChange={(e) => setConfigJson(e.target.value)}\n rows={10}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm font-mono focus:outline-none focus:border-border-strong\"\n placeholder='{\"model\": \"claude-3\", \"temperature\": 0.7, ...}'\n />\n <div className=\"mt-4 flex flex-col gap-3 sm:flex-row sm:items-center\">\n <button\n type=\"button\"\n onClick={handleSaveConfig}\n disabled={savingConfig}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 text-sm\"\n >\n <Settings className=\"size-4\" />\n {savingConfig\n ? tr('vibecontrols.pages.ai.saving', 'Saving...')\n : tr('vibecontrols.pages.ai.saveConfig', 'Save Config')}\n </button>\n {configSaved && (\n <span className=\"text-sm text-status-success-text\">\n {tr('vibecontrols.pages.ai.savedSuccessfully', 'Saved successfully')}\n </span>\n )}\n </div>\n </Card>\n\n <Card className=\"p-4 border-status-error-text/20\">\n <h3 className=\"font-semibold text-status-error-text mb-2\">\n {tr('vibecontrols.pages.ai.dangerZone', 'Danger Zone')}\n </h3>\n <p className=\"text-sm text-text-secondary mb-3\">\n {tr(\n 'vibecontrols.pages.ai.terminateWarning',\n 'Terminating a session is permanent. All state will be lost.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={handleTerminate}\n disabled={terminating}\n className=\"flex items-center gap-2 px-4 py-2 bg-status-error-bg text-status-error-text border border-status-error-text/20 rounded-md hover:bg-status-error-bg/80 transition-colors disabled:opacity-50 text-sm\"\n >\n <XCircle className=\"size-4\" />\n {terminating\n ? tr('vibecontrols.pages.ai.terminating', 'Terminating...')\n : tr('vibecontrols.pages.ai.terminateSession', 'Terminate Session')}\n </button>\n </Card>\n </div>\n )}\n </RequireAgentReachable>\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AISessionDetailPage.js","names":[],"sources":["../../../src/pages/ai/AISessionDetailPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n MessageSquare,\n ScrollText,\n BarChart3,\n Settings,\n Send,\n ArrowLeft,\n XCircle,\n RefreshCw,\n} from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport {\n PageHeader,\n Card,\n LoadingState,\n ErrorState,\n EmptyState,\n StatCard,\n RequireAgentReachable,\n} from '@/components/shared';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useQueryBackedTab } from '@/hooks/useQueryBackedTab';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport type { AISessionLog, AISessionStats } from '@/services/aiApi';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n sendPromptToSession,\n fetchSessionLogs,\n fetchSessionStats,\n updateSessionConfig,\n terminateSession,\n} from '@/services/aiApi';\nimport { isSessionNotFoundError } from '@/utils/aiSessionNotFound';\n\ntype SessionTab = 'chat' | 'logs' | 'stats' | 'config';\nconst SESSION_TABS: readonly SessionTab[] = ['chat', 'logs', 'stats', 'config'];\n\nconst sessionTabs: { id: SessionTab; label: string; icon: typeof MessageSquare }[] = [\n { id: 'chat', label: 'Chat', icon: MessageSquare },\n { id: 'logs', label: 'Logs', icon: ScrollText },\n { id: 'stats', label: 'Stats', icon: BarChart3 },\n { id: 'config', label: 'Config', icon: Settings },\n];\n\nconst logTypeColors: Record<string, string> = {\n info: 'bg-action-primary-bg/10 text-action-primary-bg',\n warn: 'bg-status-warning-bg text-status-warning-text',\n error: 'bg-status-error-bg text-status-error-text',\n debug: 'bg-bg-sunken text-text-secondary',\n prompt: 'bg-status-success-bg text-status-success-text',\n response: 'bg-action-primary-bg/20 text-action-primary-bg',\n};\n\ninterface ChatMessage {\n role: 'user' | 'assistant';\n content: string;\n tokens?: { input: number; output: number };\n timestamp: string;\n}\n\n/**\n * AI session detail page with Chat, Logs, Stats, and Config tabs.\n */\nexport function AISessionDetailPage() {\n const tr = useTr();\n // Agent-centric page — gate on `agent:read` (defense-in-depth).\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canReadAgents = hasPermission(VIBE_PERMISSIONS.AGENT_READ);\n const { sessionId } = useParams<{ sessionId: string }>();\n const handleNavigate = useVibeNavigate();\n\n const { agents, loading: agentsLoading } = useCachedInitialData();\n const activeAgent = useMemo(\n () => agents.find((agent) => agent.isActive && agent.tunnelUrl) ?? null,\n [agents]\n );\n const agentId = activeAgent?.id ?? '';\n const hasAgent = !!agentId;\n // Canonical agent → profile → plugins/data hierarchy. Every aiApi call\n // takes an AgentRef = { id, profile } so the path can be constructed\n // explicitly at the callsite.\n const { active: activeProfile } = useActiveAgentProfile(activeAgent?.id ?? null);\n const agentRef = useMemo(\n () => ({ id: agentId, profile: activeProfile }),\n [agentId, activeProfile]\n );\n\n const [activeTab, setActiveTab] = useQueryBackedTab<SessionTab>(SESSION_TABS, 'chat');\n\n // ── Chat state ──────────────────────────────────────────────────────\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [promptInput, setPromptInput] = useState('');\n const [sending, setSending] = useState(false);\n\n const handleSendPrompt = async () => {\n if (!promptInput.trim() || !sessionId || !hasAgent) return;\n const userMessage: ChatMessage = {\n role: 'user',\n content: promptInput,\n timestamp: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, userMessage]);\n setPromptInput('');\n setSending(true);\n try {\n const result = await sendPromptToSession(agentRef, sessionId, userMessage.content);\n const content =\n ((result as Record<string, unknown>).content as string) ||\n ((result as Record<string, unknown>).response as string) ||\n JSON.stringify(result);\n const inputTokens = (result as Record<string, unknown>).inputTokens as number | undefined;\n const outputTokens = (result as Record<string, unknown>).outputTokens as number | undefined;\n const assistantMessage: ChatMessage = {\n role: 'assistant',\n content,\n tokens:\n inputTokens !== undefined ? { input: inputTokens, output: outputTokens ?? 0 } : undefined,\n timestamp: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, assistantMessage]);\n } catch (err) {\n const errorMessage: ChatMessage = {\n role: 'assistant',\n content: `Error: ${err instanceof Error ? err.message : 'Failed to get response'}`,\n timestamp: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, errorMessage]);\n } finally {\n setSending(false);\n }\n };\n\n // ── Logs state ──────────────────────────────────────────────────────\n const [logs, setLogs] = useState<AISessionLog[]>([]);\n const [logsLoading, setLogsLoading] = useState(false);\n const [logsError, setLogsError] = useState<string | null>(null);\n const [logTypeFilter, setLogTypeFilter] = useState('all');\n\n const loadLogs = useCallback(async () => {\n if (!sessionId || !hasAgent) return;\n setLogsLoading(true);\n setLogsError(null);\n try {\n const params: Record<string, unknown> = {};\n if (logTypeFilter !== 'all') params.type = logTypeFilter;\n const result = await fetchSessionLogs(agentRef, sessionId, params);\n setLogs(result);\n } catch (err) {\n // Pre-first-message: agent has no record of this session yet —\n // render an empty list, not an error.\n if (isSessionNotFoundError(err)) {\n setLogs([]);\n } else {\n setLogsError(err instanceof Error ? err.message : 'Failed to load logs');\n }\n } finally {\n setLogsLoading(false);\n }\n }, [agentRef, sessionId, logTypeFilter, hasAgent]);\n\n useEffect(() => {\n if (activeTab === 'logs') loadLogs();\n }, [activeTab, loadLogs]);\n\n // ── Stats state ─────────────────────────────────────────────────────\n const [stats, setStats] = useState<AISessionStats | null>(null);\n const [statsLoading, setStatsLoading] = useState(false);\n const [statsError, setStatsError] = useState<string | null>(null);\n\n const loadStats = useCallback(async () => {\n if (!sessionId || !hasAgent) return;\n setStatsLoading(true);\n setStatsError(null);\n try {\n const result = await fetchSessionStats(agentRef, sessionId);\n setStats(result);\n } catch (err) {\n if (isSessionNotFoundError(err)) {\n setStats(null);\n } else {\n setStatsError(err instanceof Error ? err.message : 'Failed to load stats');\n }\n } finally {\n setStatsLoading(false);\n }\n }, [agentRef, sessionId, hasAgent]);\n\n useEffect(() => {\n if (activeTab === 'stats') loadStats();\n }, [activeTab, loadStats]);\n\n // ── Config state ────────────────────────────────────────────────────\n const [configJson, setConfigJson] = useState('{}');\n const [savingConfig, setSavingConfig] = useState(false);\n const [configSaved, setConfigSaved] = useState(false);\n const [terminating, setTerminating] = useState(false);\n\n const handleSaveConfig = async () => {\n if (!sessionId || !hasAgent) return;\n setSavingConfig(true);\n try {\n const config = JSON.parse(configJson);\n await updateSessionConfig(agentRef, sessionId, config);\n setConfigSaved(true);\n setTimeout(() => setConfigSaved(false), 3000);\n } catch (err) {\n console.error('Failed to save config:', err);\n } finally {\n setSavingConfig(false);\n }\n };\n\n const handleTerminate = async () => {\n if (!sessionId || !hasAgent) return;\n if (!window.confirm('Are you sure you want to terminate this session? This cannot be undone.'))\n return;\n setTerminating(true);\n try {\n await terminateSession(agentRef, sessionId);\n handleNavigate('/ai/sessions');\n } catch (err) {\n console.error('Failed to terminate session:', err);\n } finally {\n setTerminating(false);\n }\n };\n\n if (permissionsLoading) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions…')} />\n </MotionPage>\n );\n }\n\n if (!canReadAgents) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <AccessDenied\n title={tr('vibecontrols.pages.ai.denied.title', 'Access Denied')}\n description={tr(\n 'vibecontrols.pages.ai.denied.description',\n 'You do not have permission to view AI features.'\n )}\n />\n </MotionPage>\n );\n }\n\n if (agentsLoading) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message=\"Loading...\" />\n </MotionPage>\n );\n }\n\n if (!hasAgent) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <EmptyState\n icon={<MessageSquare className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.pages.ai.noActiveAgent', 'No active agent')}\n description={tr(\n 'vibecontrols.pages.ai.noActiveAgentDescription',\n 'An active agent with a tunnel is required to view session details.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => handleNavigate('/agents')}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors text-sm\"\n >\n {tr('vibecontrols.pages.ai.goToAgents', 'Go to Agents')}\n </button>\n }\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <PageHeader\n title={`Session ${sessionId?.substring(0, 8) ?? ''}...`}\n description={tr(\n 'vibecontrols.pages.ai.sessionDescription',\n 'AI session details and interaction'\n )}\n breadcrumb={[\n { label: 'AI', path: '#' },\n { label: 'Sessions', path: '#' },\n { label: sessionId?.substring(0, 8) ?? '' },\n ]}\n actions={\n <button\n type=\"button\"\n onClick={() => handleNavigate('/ai/sessions')}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors text-sm\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('vibecontrols.pages.ai.back', 'Back')}\n </button>\n }\n />\n\n {/* Tab Navigation */}\n <div className=\"-mx-4 border-b border-border-default px-4 sm:-mx-6 sm:px-6\">\n <nav className=\"flex gap-1 overflow-x-auto pb-1\" role=\"tablist\">\n {sessionTabs.map((tab) => {\n const Icon = tab.icon;\n const isActive = activeTab === tab.id;\n return (\n <button\n type=\"button\"\n key={tab.id}\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => setActiveTab(tab.id)}\n className={`flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-t-md border-b-2 px-2.5 py-2 text-xs font-medium transition-colors sm:px-3 sm:py-2.5 sm:text-sm ${\n isActive\n ? 'border-action-primary-bg text-action-primary-bg'\n : 'border-transparent text-text-secondary hover:text-text-primary hover:border-border-default'\n }`}\n >\n <Icon className=\"size-3.5 sm:h-4 sm:w-4\" />\n {tab.label}\n </button>\n );\n })}\n </nav>\n </div>\n\n {/* Tab Content */}\n <RequireAgentReachable agentId={agentId || null}>\n {activeTab === 'chat' && (\n <div className=\"space-y-4\">\n {/* Messages */}\n <Card className=\"p-0\">\n <div className=\"divide-y divide-border-default min-h-[200px] max-h-[50vh] overflow-y-auto\">\n {messages.length === 0 ? (\n <div className=\"flex items-center justify-center h-48 text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.pages.ai.sendPromptToStart',\n 'Send a prompt to start the conversation'\n )}\n </div>\n ) : (\n messages.map((msg, idx) => (\n <div\n key={idx}\n className={`px-4 py-3 ${msg.role === 'assistant' ? 'bg-bg-sunken/50' : ''}`}\n >\n <div className=\"mb-1 flex flex-wrap items-center gap-2\">\n <span className=\"text-xs font-semibold text-text-primary uppercase\">\n {msg.role}\n </span>\n <span className=\"text-xs text-text-muted\">\n {new Date(msg.timestamp).toLocaleTimeString()}\n </span>\n {msg.tokens && (\n <span className=\"text-xs text-text-muted sm:ml-auto\">\n {msg.tokens.input + msg.tokens.output} tokens\n </span>\n )}\n </div>\n <div className=\"text-sm text-text-primary whitespace-pre-wrap\">\n {msg.content}\n </div>\n </div>\n ))\n )}\n {sending && (\n <div className=\"px-4 py-3 bg-bg-sunken/50\">\n <div className=\"flex items-center gap-2\">\n <RefreshCw className=\"size-3.5 text-text-secondary animate-spin\" />\n <span className=\"text-sm text-text-secondary\">\n {tr('vibecontrols.pages.ai.thinking', 'Thinking...')}\n </span>\n </div>\n </div>\n )}\n </div>\n </Card>\n\n {/* Prompt Input */}\n <div className=\"flex flex-col gap-2 sm:flex-row\">\n <textarea\n aria-label=\"Type your prompt...\"\n value={promptInput}\n onChange={(e) => setPromptInput(e.target.value)}\n rows={3}\n className=\"min-h-[7rem] flex-1 rounded-md border border-border-default bg-bg-sunken px-3 py-2 text-sm font-mono text-text-primary resize-none focus:outline-none focus:border-border-strong\"\n placeholder=\"Type your prompt...\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleSendPrompt();\n }\n }}\n />\n <button\n type=\"button\"\n onClick={handleSendPrompt}\n disabled={sending || !promptInput.trim()}\n className=\"flex items-center justify-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm text-action-primary-text transition-colors hover:bg-action-primary-bg-hover disabled:opacity-50 sm:self-end\"\n >\n <Send className=\"size-4\" />\n {tr('vibecontrols.pages.ai.send', 'Send')}\n </button>\n </div>\n </div>\n )}\n\n {activeTab === 'logs' && (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <select\n value={logTypeFilter}\n onChange={(e) => setLogTypeFilter(e.target.value)}\n className=\"px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm\"\n >\n <option value=\"all\">All Types</option>\n <option value=\"info\">Info</option>\n <option value=\"warn\">Warning</option>\n <option value=\"error\">Error</option>\n <option value=\"debug\">Debug</option>\n <option value=\"prompt\">Prompt</option>\n <option value=\"response\">Response</option>\n </select>\n <button\n type=\"button\"\n onClick={loadLogs}\n disabled={logsLoading}\n className=\"p-2 hover:bg-bg-sunken rounded-md transition-colors\"\n >\n <RefreshCw\n className={`size-4 text-text-secondary ${logsLoading ? 'animate-spin' : ''}`}\n />\n </button>\n </div>\n\n {logsLoading ? (\n <LoadingState message=\"Loading logs...\" />\n ) : logsError ? (\n <ErrorState title=\"Failed to load logs\" message={logsError} onRetry={loadLogs} />\n ) : logs.length === 0 ? (\n <EmptyState\n icon={<ScrollText className=\"size-6 text-text-secondary\" />}\n title=\"No logs\"\n description=\"Session logs will appear here\"\n />\n ) : (\n <Card className=\"p-0\">\n <div className=\"divide-y divide-border-default max-h-[60vh] overflow-y-auto\">\n {logs.map((log) => (\n <div key={log.id} className=\"px-4 py-2 flex items-start gap-3 text-sm\">\n <span\n className={`px-1.5 py-0.5 text-xs rounded font-medium flex-shrink-0 mt-0.5 ${logTypeColors[log.type] ?? 'bg-bg-sunken text-text-secondary'}`}\n >\n {log.type}\n </span>\n <div className=\"flex-1 min-w-0\">\n <span className=\"text-text-primary\">{log.message}</span>\n </div>\n <span className=\"text-xs text-text-muted flex-shrink-0\">\n {new Date(log.timestamp).toLocaleTimeString()}\n </span>\n </div>\n ))}\n </div>\n </Card>\n )}\n </div>\n )}\n\n {activeTab === 'stats' && (\n <div className=\"space-y-4\">\n {statsLoading ? (\n <LoadingState message=\"Loading stats...\" />\n ) : statsError ? (\n <ErrorState title=\"Failed to load stats\" message={statsError} onRetry={loadStats} />\n ) : stats ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n <StatCard title=\"Total Tokens\" value={stats.totalTokens.toLocaleString()} />\n <StatCard\n title=\"Input Tokens\"\n value={stats.inputTokens.toLocaleString()}\n variant=\"success\"\n />\n <StatCard\n title=\"Output Tokens\"\n value={stats.outputTokens.toLocaleString()}\n variant=\"warning\"\n />\n <StatCard title=\"Prompts\" value={stats.promptCount} />\n {stats.averageLatency !== undefined && (\n <StatCard title=\"Avg Latency\" value={`${stats.averageLatency.toFixed(0)}ms`} />\n )}\n </div>\n ) : (\n <EmptyState\n icon={<BarChart3 className=\"size-6 text-text-secondary\" />}\n title=\"No stats\"\n description=\"Stats will appear after the session processes prompts\"\n />\n )}\n </div>\n )}\n\n {activeTab === 'config' && (\n <div className=\"space-y-4\">\n <Card className=\"p-4\">\n <h3 className=\"font-semibold text-text-primary mb-3\">\n {tr('vibecontrols.pages.ai.sessionConfig', 'Session Configuration')}\n </h3>\n <textarea\n aria-label=\"control\"\n value={configJson}\n onChange={(e) => setConfigJson(e.target.value)}\n rows={10}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm font-mono focus:outline-none focus:border-border-strong\"\n placeholder='{\"model\": \"claude-3\", \"temperature\": 0.7, ...}'\n />\n <div className=\"mt-4 flex flex-col gap-3 sm:flex-row sm:items-center\">\n <button\n type=\"button\"\n onClick={handleSaveConfig}\n disabled={savingConfig}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 text-sm\"\n >\n <Settings className=\"size-4\" />\n {savingConfig\n ? tr('vibecontrols.pages.ai.saving', 'Saving...')\n : tr('vibecontrols.pages.ai.saveConfig', 'Save Config')}\n </button>\n {configSaved && (\n <span className=\"text-sm text-status-success-text\">\n {tr('vibecontrols.pages.ai.savedSuccessfully', 'Saved successfully')}\n </span>\n )}\n </div>\n </Card>\n\n <Card className=\"p-4 border-status-error-text/20\">\n <h3 className=\"font-semibold text-status-error-text mb-2\">\n {tr('vibecontrols.pages.ai.dangerZone', 'Danger Zone')}\n </h3>\n <p className=\"text-sm text-text-secondary mb-3\">\n {tr(\n 'vibecontrols.pages.ai.terminateWarning',\n 'Terminating a session is permanent. All state will be lost.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={handleTerminate}\n disabled={terminating}\n className=\"flex items-center gap-2 px-4 py-2 bg-status-error-bg text-status-error-text border border-status-error-text/20 rounded-md hover:bg-status-error-bg/80 transition-colors disabled:opacity-50 text-sm\"\n >\n <XCircle className=\"size-4\" />\n {terminating\n ? tr('vibecontrols.pages.ai.terminating', 'Terminating...')\n : tr('vibecontrols.pages.ai.terminateSession', 'Terminate Session')}\n </button>\n </Card>\n </div>\n )}\n </RequireAgentReachable>\n </MotionPage>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAyCA,IAAM,KAAsC;CAAC;CAAQ;CAAQ;CAAS;CAAS,EAEzE,KAA+E;CACnF;EAAE,IAAI;EAAQ,OAAO;EAAQ,MAAM;EAAe;CAClD;EAAE,IAAI;EAAQ,OAAO;EAAQ,MAAM;EAAY;CAC/C;EAAE,IAAI;EAAS,OAAO;EAAS,MAAM;EAAW;CAChD;EAAE,IAAI;EAAU,OAAO;EAAU,MAAM;EAAU;CAClD,EAEK,KAAwC;CAC5C,MAAM;CACN,MAAM;CACN,OAAO;CACP,OAAO;CACP,QAAQ;CACR,UAAU;CACX;AAYD,SAAgB,IAAsB;CACpC,IAAM,IAAK,GAAO,EAEZ,EAAE,mBAAe,WAAW,OAAuB,IAAgB,EACnE,KAAgB,GAAc,GAAiB,WAAW,EAC1D,EAAE,iBAAc,IAAkC,EAClD,IAAiB,GAAiB,EAElC,EAAE,WAAQ,SAAS,OAAkB,GAAsB,EAC3D,IAAc,QACZ,EAAO,MAAM,MAAU,EAAM,YAAY,EAAM,UAAU,IAAI,MACnE,CAAC,EAAO,CACT,EACK,IAAU,GAAa,MAAM,IAC7B,IAAW,CAAC,CAAC,GAIb,EAAE,QAAQ,MAAkB,GAAsB,GAAa,MAAM,KAAK,EAC1E,IAAW,SACR;EAAE,IAAI;EAAS,SAAS;EAAe,GAC9C,CAAC,GAAS,EAAc,CACzB,EAEK,CAAC,GAAW,MAAgB,GAA8B,IAAc,OAAO,EAG/E,CAAC,GAAU,KAAe,EAAwB,EAAE,CAAC,EACrD,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAS,KAAc,EAAS,GAAM,EAEvC,IAAmB,YAAY;AACnC,MAAI,CAAC,EAAY,MAAM,IAAI,CAAC,KAAa,CAAC,EAAU;EACpD,IAAM,IAA2B;GAC/B,MAAM;GACN,SAAS;GACT,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC;AAGD,EAFA,GAAa,MAAS,CAAC,GAAG,GAAM,EAAY,CAAC,EAC7C,EAAe,GAAG,EAClB,EAAW,GAAK;AAChB,MAAI;GACF,IAAM,IAAS,MAAM,GAAoB,GAAU,GAAW,EAAY,QAAQ,EAC5E,IACF,EAAmC,WACnC,EAAmC,YACrC,KAAK,UAAU,EAAO,EAClB,IAAe,EAAmC,aAClD,IAAgB,EAAmC,cACnD,IAAgC;IACpC,MAAM;IACN;IACA,QACE,MAAgB,KAAA,IAAgE,KAAA,IAApD;KAAE,OAAO;KAAa,QAAQ,KAAgB;KAAG;IAC/E,4BAAW,IAAI,MAAM,EAAC,aAAa;IACpC;AACD,MAAa,MAAS,CAAC,GAAG,GAAM,EAAiB,CAAC;WAC3C,GAAK;GACZ,IAAM,IAA4B;IAChC,MAAM;IACN,SAAS,UAAU,aAAe,QAAQ,EAAI,UAAU;IACxD,4BAAW,IAAI,MAAM,EAAC,aAAa;IACpC;AACD,MAAa,MAAS,CAAC,GAAG,GAAM,EAAa,CAAC;YACtC;AACR,KAAW,GAAM;;IAKf,CAAC,GAAM,KAAW,EAAyB,EAAE,CAAC,EAC9C,CAAC,GAAa,KAAkB,EAAS,GAAM,EAC/C,CAAC,GAAW,KAAgB,EAAwB,KAAK,EACzD,CAAC,GAAe,MAAoB,EAAS,MAAM,EAEnD,IAAW,EAAY,YAAY;AACnC,SAAC,KAAa,CAAC,IAEnB;GADA,EAAe,GAAK,EACpB,EAAa,KAAK;AAClB,OAAI;IACF,IAAM,IAAkC,EAAE;AAG1C,IAFI,MAAkB,UAAO,EAAO,OAAO,IAE3C,EADe,MAAM,GAAiB,GAAU,GAAW,EAAO,CACnD;YACR,GAAK;AAGZ,IAAI,EAAuB,EAAI,GAC7B,EAAQ,EAAE,CAAC,GAEX,EAAa,aAAe,QAAQ,EAAI,UAAU,sBAAsB;aAElE;AACR,MAAe,GAAM;;;IAEtB;EAAC;EAAU;EAAW;EAAe;EAAS,CAAC;AAElD,SAAgB;AACd,EAAI,MAAc,UAAQ,GAAU;IACnC,CAAC,GAAW,EAAS,CAAC;CAGzB,IAAM,CAAC,GAAO,KAAY,EAAgC,KAAK,EACzD,CAAC,IAAc,KAAmB,EAAS,GAAM,EACjD,CAAC,GAAY,KAAiB,EAAwB,KAAK,EAE3D,IAAY,EAAY,YAAY;AACpC,SAAC,KAAa,CAAC,IAEnB;GADA,EAAgB,GAAK,EACrB,EAAc,KAAK;AACnB,OAAI;AAEF,MADe,MAAM,GAAkB,GAAU,EAAU,CAC3C;YACT,GAAK;AACZ,IAAI,EAAuB,EAAI,GAC7B,EAAS,KAAK,GAEd,EAAc,aAAe,QAAQ,EAAI,UAAU,uBAAuB;aAEpE;AACR,MAAgB,GAAM;;;IAEvB;EAAC;EAAU;EAAW;EAAS,CAAC;AAEnC,SAAgB;AACd,EAAI,MAAc,WAAS,GAAW;IACrC,CAAC,GAAW,EAAU,CAAC;CAG1B,IAAM,CAAC,IAAY,MAAiB,EAAS,KAAK,EAC5C,CAAC,IAAc,MAAmB,EAAS,GAAM,EACjD,CAAC,IAAa,MAAkB,EAAS,GAAM,EAC/C,CAAC,IAAa,KAAkB,EAAS,GAAM;AAsFrD,QAtDI,KAEA,kBAAC,GAAD;EAAY,WAAU;YACpB,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,uBAAuB,EAAI,CAAA;EACvE,CAAA,GAIZ,KAcD,KAEA,kBAAC,GAAD;EAAY,WAAU;YACpB,kBAAC,GAAD,EAAc,SAAQ,cAAe,CAAA;EAC1B,CAAA,GAIZ,IAyBH,kBAAC,GAAD;EAAY,WAAU;YAAtB;GACE,kBAAC,GAAD;IACE,OAAO,WAAW,GAAW,UAAU,GAAG,EAAE,IAAI,GAAG;IACnD,aAAa,EACX,4CACA,qCACD;IACD,YAAY;KACV;MAAE,OAAO;MAAM,MAAM;MAAK;KAC1B;MAAE,OAAO;MAAY,MAAM;MAAK;KAChC,EAAE,OAAO,GAAW,UAAU,GAAG,EAAE,IAAI,IAAI;KAC5C;IACD,SACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAe,eAAe;KAC7C,WAAU;eAHZ,CAKE,kBAAC,IAAD,EAAW,WAAU,UAAW,CAAA,EAC/B,EAAG,8BAA8B,OAAO,CAClC;;IAEX,CAAA;GAGF,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;KAAkC,MAAK;eACnD,GAAY,KAAK,MAAQ;MACxB,IAAM,IAAO,EAAI,MACX,IAAW,MAAc,EAAI;AACnC,aACE,kBAAC,UAAD;OACE,MAAK;OAEL,MAAK;OACL,iBAAe;OACf,eAAe,GAAa,EAAI,GAAG;OACnC,WAAW,+JACT,IACI,oDACA;iBATR,CAYE,kBAAC,GAAD,EAAM,WAAU,0BAA2B,CAAA,EAC1C,EAAI,MACE;SAZF,EAAI,GAYF;OAEX;KACE,CAAA;IACF,CAAA;GAGN,kBAAC,IAAD;IAAuB,SAAS,KAAW;cAA3C;KACG,MAAc,UACb,kBAAC,OAAD;MAAK,WAAU;gBAAf,CAEE,kBAAC,GAAD;OAAM,WAAU;iBACd,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACG,EAAS,WAAW,IACnB,kBAAC,OAAD;SAAK,WAAU;mBACZ,EACC,2CACA,0CACD;SACG,CAAA,GAEN,EAAS,KAAK,GAAK,MACjB,kBAAC,OAAD;SAEE,WAAW,aAAa,EAAI,SAAS,cAAc,oBAAoB;mBAFzE,CAIE,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAI;YACA,CAAA;WACP,kBAAC,QAAD;YAAM,WAAU;sBACb,IAAI,KAAK,EAAI,UAAU,CAAC,oBAAoB;YACxC,CAAA;WACN,EAAI,UACH,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CACG,EAAI,OAAO,QAAQ,EAAI,OAAO,QAAO,UACjC;;WAEL;aACN,kBAAC,OAAD;UAAK,WAAU;oBACZ,EAAI;UACD,CAAA,CACF;WAnBC,EAmBD,CACN,EAEH,KACC,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD,EAAW,WAAU,6CAA8C,CAAA,EACnE,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAG,kCAAkC,cAAc;WAC/C,CAAA,CACH;;SACF,CAAA,CAEJ;;OACD,CAAA,EAGP,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,YAAD;QACE,cAAW;QACX,OAAO;QACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;QAC/C,MAAM;QACN,WAAU;QACV,aAAY;QACZ,YAAY,MAAM;AAChB,SAAI,EAAE,QAAQ,YAAY,EAAE,WAAW,EAAE,aACvC,EAAE,gBAAgB,EAClB,GAAkB;;QAGtB,CAAA,EACF,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,UAAU,KAAW,CAAC,EAAY,MAAM;QACxC,WAAU;kBAJZ,CAME,kBAAC,IAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,EAAG,8BAA8B,OAAO,CAClC;UACL;SACF;;KAGP,MAAc,UACb,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,OAAO;QACP,WAAW,MAAM,GAAiB,EAAE,OAAO,MAAM;QACjD,WAAU;kBAHZ;SAKE,kBAAC,UAAD;UAAQ,OAAM;oBAAM;UAAkB,CAAA;SACtC,kBAAC,UAAD;UAAQ,OAAM;oBAAO;UAAa,CAAA;SAClC,kBAAC,UAAD;UAAQ,OAAM;oBAAO;UAAgB,CAAA;SACrC,kBAAC,UAAD;UAAQ,OAAM;oBAAQ;UAAc,CAAA;SACpC,kBAAC,UAAD;UAAQ,OAAM;oBAAQ;UAAc,CAAA;SACpC,kBAAC,UAAD;UAAQ,OAAM;oBAAS;UAAe,CAAA;SACtC,kBAAC,UAAD;UAAQ,OAAM;oBAAW;UAAiB,CAAA;SACnC;WACT,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,UAAU;QACV,WAAU;kBAEV,kBAAC,GAAD,EACE,WAAW,8BAA8B,IAAc,iBAAiB,MACxE,CAAA;QACK,CAAA,CACL;UAEL,IACC,kBAAC,GAAD,EAAc,SAAQ,mBAAoB,CAAA,GACxC,IACF,kBAAC,GAAD;OAAY,OAAM;OAAsB,SAAS;OAAW,SAAS;OAAY,CAAA,GAC/E,EAAK,WAAW,IAClB,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAY,WAAU,8BAA+B,CAAA;OAC3D,OAAM;OACN,aAAY;OACZ,CAAA,GAEF,kBAAC,GAAD;OAAM,WAAU;iBACd,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAK,KAAK,MACT,kBAAC,OAAD;SAAkB,WAAU;mBAA5B;UACE,kBAAC,QAAD;WACE,WAAW,kEAAkE,GAAc,EAAI,SAAS;qBAEvG,EAAI;WACA,CAAA;UACP,kBAAC,OAAD;WAAK,WAAU;qBACb,kBAAC,QAAD;YAAM,WAAU;sBAAqB,EAAI;YAAe,CAAA;WACpD,CAAA;UACN,kBAAC,QAAD;WAAM,WAAU;qBACb,IAAI,KAAK,EAAI,UAAU,CAAC,oBAAoB;WACxC,CAAA;UACH;WAZI,EAAI,GAYR,CACN;QACE,CAAA;OACD,CAAA,CAEL;;KAGP,MAAc,WACb,kBAAC,OAAD;MAAK,WAAU;gBACZ,KACC,kBAAC,GAAD,EAAc,SAAQ,oBAAqB,CAAA,GACzC,IACF,kBAAC,GAAD;OAAY,OAAM;OAAuB,SAAS;OAAY,SAAS;OAAa,CAAA,GAClF,IACF,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SAAU,OAAM;SAAe,OAAO,EAAM,YAAY,gBAAgB;SAAI,CAAA;QAC5E,kBAAC,GAAD;SACE,OAAM;SACN,OAAO,EAAM,YAAY,gBAAgB;SACzC,SAAQ;SACR,CAAA;QACF,kBAAC,GAAD;SACE,OAAM;SACN,OAAO,EAAM,aAAa,gBAAgB;SAC1C,SAAQ;SACR,CAAA;QACF,kBAAC,GAAD;SAAU,OAAM;SAAU,OAAO,EAAM;SAAe,CAAA;QACrD,EAAM,mBAAmB,KAAA,KACxB,kBAAC,GAAD;SAAU,OAAM;SAAc,OAAO,GAAG,EAAM,eAAe,QAAQ,EAAE,CAAC;SAAO,CAAA;QAE7E;WAEN,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAW,WAAU,8BAA+B,CAAA;OAC1D,OAAM;OACN,aAAY;OACZ,CAAA;MAEA,CAAA;KAGP,MAAc,YACb,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAM,WAAU;iBAAhB;QACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAG,uCAAuC,wBAAwB;SAChE,CAAA;QACL,kBAAC,YAAD;SACE,cAAW;SACX,OAAO;SACP,WAAW,MAAM,GAAc,EAAE,OAAO,MAAM;SAC9C,MAAM;SACN,WAAU;SACV,aAAY;SACZ,CAAA;QACF,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,UAAD;UACE,MAAK;UACL,SA1US,YAAY;AAC/B,kBAAC,KAAa,CAAC,IACnB;eAAgB,GAAK;AACrB,gBAAI;AAIF,aAFA,MAAM,GAAoB,GAAU,GADrB,KAAK,MAAM,GAAW,CACiB,EACtD,GAAe,GAAK,EACpB,iBAAiB,GAAe,GAAM,EAAE,IAAK;qBACtC,GAAK;AACZ,qBAAQ,MAAM,0BAA0B,EAAI;sBACpC;AACR,gBAAgB,GAAM;;;;UAgUV,UAAU;UACV,WAAU;oBAJZ,CAME,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA,EAC9B,KACG,EAAG,gCAAgC,YAAY,GAC/C,EAAG,oCAAoC,cAAc,CAClD;aACR,MACC,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAG,2CAA2C,qBAAqB;UAC/D,CAAA,CAEL;;QACD;UAEP,kBAAC,GAAD;OAAM,WAAU;iBAAhB;QACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAG,oCAAoC,cAAc;SACnD,CAAA;QACL,kBAAC,KAAD;SAAG,WAAU;mBACV,EACC,0CACA,8DACD;SACC,CAAA;QACJ,kBAAC,UAAD;SACE,MAAK;SACL,SAxVU,YAAY;AAC9B,iBAAC,KAAa,CAAC,MACd,OAAO,QAAQ,0EAA0E,EAE9F;aAAe,GAAK;AACpB,eAAI;AAEF,YADA,MAAM,GAAiB,GAAU,EAAU,EAC3C,EAAe,eAAe;oBACvB,GAAK;AACZ,oBAAQ,MAAM,gCAAgC,EAAI;qBAC1C;AACR,cAAe,GAAM;;;;SA8UX,UAAU;SACV,WAAU;mBAJZ,CAME,kBAAC,IAAD,EAAS,WAAU,UAAW,CAAA,EAC7B,KACG,EAAG,qCAAqC,iBAAiB,GACzD,EAAG,0CAA0C,oBAAoB,CAC9D;;QACJ;SACH;;KAEc;;GACb;MAtTX,kBAAC,GAAD;EAAY,WAAU;YACpB,kBAAC,GAAD;GACE,MAAM,kBAAC,GAAD,EAAe,WAAU,8BAA+B,CAAA;GAC9D,OAAO,EAAG,uCAAuC,kBAAkB;GACnE,aAAa,EACX,kDACA,qEACD;GACD,QACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAe,UAAU;IACxC,WAAU;cAET,EAAG,oCAAoC,eAAe;IAChD,CAAA;GAEX,CAAA;EACS,CAAA,GAxCb,kBAAC,GAAD;EAAY,WAAU;YACpB,kBAAC,IAAD;GACE,OAAO,EAAG,sCAAsC,gBAAgB;GAChE,aAAa,EACX,4CACA,kDACD;GACD,CAAA;EACS,CAAA"}
|