@tangle-network/sandbox-ui 0.113.6 → 0.114.0
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/WorkflowGraph-B-NsdYz_.js +1961 -0
- package/dist/assets.d.ts +214 -206
- package/dist/assets.js +1057 -908
- package/dist/auth-CWe17Hly.js +37 -0
- package/dist/auth.d.ts +2 -30
- package/dist/auth.js +2 -15
- package/dist/chat-BOvFAe6p.js +903 -0
- package/dist/chat.d.ts +2 -230
- package/dist/chat.js +2 -34
- package/dist/connectors.d.ts +52 -49
- package/dist/connectors.js +595 -643
- package/dist/dashboard-CZ3VgNPR.js +4969 -0
- package/dist/dashboard.d.ts +4 -1297
- package/dist/dashboard.js +4 -152
- package/dist/editor.d.ts +2 -1
- package/dist/editor.js +2 -31
- package/dist/files.d.ts +2 -6
- package/dist/files.js +2 -33
- package/dist/globals.css +63 -3
- package/dist/hooks.d.ts +146 -148
- package/dist/hooks.js +222 -55
- package/dist/index-CKRaj1pM.d.ts +776 -0
- package/dist/index-CXX5IpH2.d.ts +2 -0
- package/dist/index-DPePq7c8.d.ts +1340 -0
- package/dist/index-DiyLLZuJ.d.ts +230 -0
- package/dist/index-DvmZLMT1.d.ts +30 -0
- package/dist/index-I8ZLVtHd.d.ts +330 -0
- package/dist/index-PRM636Ex.d.ts +86 -0
- package/dist/index.d.ts +21 -33
- package/dist/index.js +21 -701
- package/dist/info-panel-C9cqDzOL.js +594 -0
- package/dist/integrations.d.ts +133 -139
- package/dist/integrations.js +491 -673
- package/dist/markdown.d.ts +2 -1
- package/dist/markdown.js +2 -13
- package/dist/model-I-NIxeM-.js +1250 -0
- package/dist/model-brand-B9A4H9pJ.d.ts +98 -0
- package/dist/model-brand-hMyDTUqB.js +624 -0
- package/dist/motion-DkyBRJb9.js +37 -0
- package/dist/openui.d.ts +2 -1
- package/dist/openui.js +2 -7
- package/dist/pages.d.ts +307 -300
- package/dist/pages.js +4404 -4181
- package/dist/primitives-Dh1vG6J3.js +113 -0
- package/dist/primitives.d.ts +2 -74
- package/dist/primitives.js +2 -204
- package/dist/provider-logo-DjqmConF.js +311 -0
- package/dist/run.d.ts +2 -1
- package/dist/run.js +2 -23
- package/dist/sdk-hooks.d.ts +3 -4
- package/dist/sdk-hooks.js +3 -31
- package/dist/sidecar-DgH_vQ6m.d.ts +9 -0
- package/dist/stores.d.ts +14 -15
- package/dist/stores.js +13 -81
- package/dist/styles.css +63 -3
- package/dist/template-card-L-4gmjx2.d.ts +90 -0
- package/dist/terminal-CN9ZgHRt.js +197 -0
- package/dist/terminal.d.ts +60 -60
- package/dist/terminal.js +2 -10
- package/dist/tokens.css +51 -0
- package/dist/types.d.ts +4 -11
- package/dist/types.js +0 -1
- package/dist/use-pty-session-C8cE0adT.js +669 -0
- package/dist/use-sidecar-auth-CCTJcrq7.d.ts +145 -0
- package/dist/use-sidecar-auth-DkdcufOz.js +742 -0
- package/dist/utils-DzWbktWG.js +2 -0
- package/dist/utils.d.ts +2 -1
- package/dist/utils.js +2 -37
- package/dist/workbench-BCaCKl-J.js +8347 -0
- package/dist/workbench.d.ts +2 -310
- package/dist/workbench.js +2 -44
- package/dist/workflows.d.ts +341 -339
- package/dist/workflows.js +74 -109
- package/dist/workspace-B6AtI6ld.js +2510 -0
- package/dist/workspace.d.ts +2 -761
- package/dist/workspace.js +2 -51
- package/package.json +46 -40
- package/dist/WorkflowGraph-2QOORYIR.js +0 -1722
- package/dist/chunk-2BUPSB7O.js +0 -0
- package/dist/chunk-3KBNPQQF.js +0 -2697
- package/dist/chunk-3LIXVEEE.js +0 -279
- package/dist/chunk-72UF2H5H.js +0 -707
- package/dist/chunk-AI25G5MX.js +0 -944
- package/dist/chunk-ANSC5AFC.js +0 -314
- package/dist/chunk-AZ3AWMTM.js +0 -8
- package/dist/chunk-BGVEHENC.js +0 -10
- package/dist/chunk-CZ7CSFO4.js +0 -39
- package/dist/chunk-DYXXYGS4.js +0 -4700
- package/dist/chunk-EI44GEQ5.js +0 -6
- package/dist/chunk-IACRDXER.js +0 -219
- package/dist/chunk-IOB2PW5Z.js +0 -55
- package/dist/chunk-M4K7ZGR2.js +0 -24
- package/dist/chunk-MYZAW4RX.js +0 -665
- package/dist/chunk-OMUIPJQZ.js +0 -555
- package/dist/chunk-RQZRXKMR.js +0 -398
- package/dist/chunk-SOKKTB7W.js +0 -230
- package/dist/chunk-TADGS4Q7.js +0 -1010
- package/dist/chunk-UCQNOUN3.js +0 -7964
- package/dist/chunk-ULPLRBMF.js +0 -34
- package/dist/chunk-WID73FPH.js +0 -89
- package/dist/chunk-X3YXP7FG.js +0 -38
- package/dist/logo-DiAjq759.d.ts +0 -15
- package/dist/model-brand-BNnaUKKZ.d.ts +0 -99
- package/dist/ports-list-CDIvTNnX.d.ts +0 -17
- package/dist/sdk-hooks-Djkjq4cp.d.ts +0 -145
- package/dist/template-card-Bn-Bw5xC.d.ts +0 -87
|
@@ -0,0 +1,2510 @@
|
|
|
1
|
+
import { t as cn$1 } from "./utils-DzWbktWG.js";
|
|
2
|
+
import { t as MOTION_CONTROL } from "./motion-DkyBRJb9.js";
|
|
3
|
+
import { ArtifactPane, ArtifactPane as ArtifactPane$1, Badge, EmptyState, Input } from "@tangle-network/ui/primitives";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
6
|
+
import { focusField, focusRing, focusRingInset } from "@tangle-network/ui/utils";
|
|
7
|
+
import { Markdown } from "@tangle-network/ui/markdown";
|
|
8
|
+
import { Activity, AlertCircle, AlertTriangle, ArrowLeft, Binary, Bot, Boxes, CheckCircle, ChevronDown, ChevronRight, ChevronUp, FileCode2, FileText, FolderTree, LayoutPanelTop, Loader2, LoaderCircle, MessageSquareText, PanelBottomClose, PanelBottomOpen, PanelLeftClose, PanelLeftOpen, PanelRightClose, PanelRightOpen, Plus, RefreshCw, Search, Settings, Shield, ShieldCheck, Sparkles, Terminal, TerminalSquare, Upload, Wifi, X, XCircle, Zap } from "lucide-react";
|
|
9
|
+
import { FileArtifactPane, FileTree, filterFileTree } from "@tangle-network/ui/files";
|
|
10
|
+
import { useActiveSessions, useNavbarSessions, useProjectActivity, useTotalRunningSessions } from "@tangle-network/ui/stores";
|
|
11
|
+
import { ChatContainer } from "@tangle-network/ui/chat";
|
|
12
|
+
import { OpenUIArtifactRenderer } from "@tangle-network/ui/openui";
|
|
13
|
+
//#region src/workspace/workspace-pane-header.tsx
|
|
14
|
+
/**
|
|
15
|
+
* The shared 56px header row for top-level workspace panes and drawers.
|
|
16
|
+
* Bottom runtime panels intentionally use their own compact header.
|
|
17
|
+
*/
|
|
18
|
+
function WorkspacePaneHeader({ className, ...props }) {
|
|
19
|
+
return /* @__PURE__ */ jsx("div", {
|
|
20
|
+
className: cn$1("flex h-14 shrink-0 items-center border-b border-[var(--md3-outline-variant)] bg-surface-container-high px-3", className),
|
|
21
|
+
...props
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/workspace/workspace-layout.tsx
|
|
26
|
+
/**
|
|
27
|
+
* WorkspaceLayout — reusable sandbox shell with desktop resizable panels and
|
|
28
|
+
* mobile overlay drawers.
|
|
29
|
+
*
|
|
30
|
+
* Left: navigation / files / context
|
|
31
|
+
* Center: chat, timeline, or primary workspace
|
|
32
|
+
* Right: artifacts / previews / inspectors
|
|
33
|
+
* Bottom: optional runtime panel
|
|
34
|
+
*/
|
|
35
|
+
const DESKTOP_BREAKPOINT = "(min-width: 1024px)";
|
|
36
|
+
function clamp(value, min, max) {
|
|
37
|
+
return Math.min(Math.max(value, min), max);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* True while a keystroke belongs to a text field. `isContentEditable` is
|
|
41
|
+
* missing from jsdom, so the attribute is checked too — through `closest`,
|
|
42
|
+
* because the target inside an editable region is usually a descendant.
|
|
43
|
+
*/
|
|
44
|
+
function isEditableTarget(target) {
|
|
45
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
46
|
+
if (target.isContentEditable) return true;
|
|
47
|
+
const tag = target.tagName;
|
|
48
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
|
|
49
|
+
const editable = target.closest("[contenteditable]");
|
|
50
|
+
return editable !== null && editable.getAttribute("contenteditable") !== "false";
|
|
51
|
+
}
|
|
52
|
+
function readStoredLayout(key) {
|
|
53
|
+
if (typeof window === "undefined") return null;
|
|
54
|
+
try {
|
|
55
|
+
const raw = window.localStorage.getItem(key);
|
|
56
|
+
if (!raw) return null;
|
|
57
|
+
const parsed = JSON.parse(raw);
|
|
58
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function useDesktopMediaQuery(query) {
|
|
64
|
+
const [matches, setMatches] = useState(() => {
|
|
65
|
+
if (typeof window === "undefined") return true;
|
|
66
|
+
return window.matchMedia(query).matches;
|
|
67
|
+
});
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (typeof window === "undefined") return;
|
|
70
|
+
const media = window.matchMedia(query);
|
|
71
|
+
const handleChange = (event) => {
|
|
72
|
+
setMatches(event.matches);
|
|
73
|
+
};
|
|
74
|
+
setMatches(media.matches);
|
|
75
|
+
media.addEventListener("change", handleChange);
|
|
76
|
+
return () => media.removeEventListener("change", handleChange);
|
|
77
|
+
}, [query]);
|
|
78
|
+
return matches;
|
|
79
|
+
}
|
|
80
|
+
function ResizeHandle({ label, onDragStart, onStep, className }) {
|
|
81
|
+
const handlePointerDown = (event) => {
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
onDragStart(event.clientX);
|
|
84
|
+
};
|
|
85
|
+
const handleKeyDown = (event) => {
|
|
86
|
+
if (event.key === "ArrowLeft") {
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
onStep(-24);
|
|
89
|
+
}
|
|
90
|
+
if (event.key === "ArrowRight") {
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
onStep(24);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
96
|
+
type: "button",
|
|
97
|
+
"aria-label": label,
|
|
98
|
+
role: "separator",
|
|
99
|
+
"aria-orientation": "vertical",
|
|
100
|
+
onPointerDown: handlePointerDown,
|
|
101
|
+
onKeyDown: handleKeyDown,
|
|
102
|
+
className: cn$1("relative hidden w-3 shrink-0 cursor-col-resize lg:flex", "items-stretch justify-center bg-transparent touch-none", focusRing, className),
|
|
103
|
+
children: [/* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-[var(--md3-outline-variant)] transition-colors" }), /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 left-1/2 w-[3px] -translate-x-1/2 rounded-full bg-transparent hover:bg-primary/30 focus-visible:bg-primary/40" })]
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function HorizontalResizeHandle({ label, onDragStart, onStep, className }) {
|
|
107
|
+
const handlePointerDown = (event) => {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
onDragStart(event.clientY);
|
|
110
|
+
};
|
|
111
|
+
const handleKeyDown = (event) => {
|
|
112
|
+
if (event.key === "ArrowUp") {
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
onStep(24);
|
|
115
|
+
}
|
|
116
|
+
if (event.key === "ArrowDown") {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
onStep(-24);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
122
|
+
type: "button",
|
|
123
|
+
"aria-label": label,
|
|
124
|
+
role: "separator",
|
|
125
|
+
"aria-orientation": "horizontal",
|
|
126
|
+
onPointerDown: handlePointerDown,
|
|
127
|
+
onKeyDown: handleKeyDown,
|
|
128
|
+
className: cn$1("relative hidden h-3 shrink-0 cursor-row-resize lg:flex", "items-center justify-center bg-transparent touch-none w-full", focusRing, className),
|
|
129
|
+
children: [/* @__PURE__ */ jsx("span", { className: "absolute inset-x-0 top-1/2 h-px -translate-y-1/2 bg-[var(--md3-outline-variant)] transition-colors" }), /* @__PURE__ */ jsx("span", { className: "absolute inset-x-0 top-1/2 h-[3px] -translate-y-1/2 rounded-full bg-transparent hover:bg-primary/30 focus-visible:bg-primary/40" })]
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function MobileDrawer({ side, title, header, onClose, children }) {
|
|
133
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
134
|
+
className: "fixed inset-0 z-50 flex lg:hidden",
|
|
135
|
+
"aria-modal": "true",
|
|
136
|
+
role: "dialog",
|
|
137
|
+
"aria-label": title,
|
|
138
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
139
|
+
type: "button",
|
|
140
|
+
"aria-label": `Close ${title}`,
|
|
141
|
+
onClick: onClose,
|
|
142
|
+
className: "absolute inset-0 bg-black/80 backdrop-blur-sm"
|
|
143
|
+
}), /* @__PURE__ */ jsxs("aside", {
|
|
144
|
+
className: cn$1("relative flex h-full w-[min(88vw,24rem)] flex-col border-[var(--md3-outline-variant)] bg-surface-container-highest shadow-[0_8px_30px_rgba(0,0,0,0.45)] ring-1 ring-[#ffffff14]", side === "left" ? "border-r" : "ml-auto border-l"),
|
|
145
|
+
children: [/* @__PURE__ */ jsxs(WorkspacePaneHeader, {
|
|
146
|
+
className: "justify-between gap-3",
|
|
147
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
148
|
+
className: "min-w-0 flex-1",
|
|
149
|
+
children: header ?? /* @__PURE__ */ jsx("span", {
|
|
150
|
+
className: "text-[13px] font-medium text-foreground",
|
|
151
|
+
children: title
|
|
152
|
+
})
|
|
153
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
154
|
+
type: "button",
|
|
155
|
+
"aria-label": `Close ${title}`,
|
|
156
|
+
onClick: onClose,
|
|
157
|
+
className: `rounded-[2px] p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
|
|
158
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
159
|
+
})]
|
|
160
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
161
|
+
className: "flex-1 overflow-auto",
|
|
162
|
+
children
|
|
163
|
+
})]
|
|
164
|
+
})]
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function WorkspaceLayout({ left, leftHeader, center, centerHeader, centerFooter, right, rightHeader, bottom, bottomHeader, defaultLeftOpen = true, defaultRightOpen = false, defaultBottomOpen = false, defaultLeftWidth = 280, defaultRightWidth = 480, defaultBottomHeight = 224, minLeftWidth = 220, maxLeftWidth = 420, minRightWidth = 320, maxRightWidth = 720, minCenterWidth = 400, minBottomHeight = 100, maxBottomHeight = 500, persistenceKey, resizable = true, theme, density = "comfortable", leftLabel = "Left workspace panel", rightLabel = "Right workspace panel", bottomLabel = "Bottom runtime panel", leftOpen: leftOpenProp, onLeftOpenChange, rightOpen: rightOpenProp, onRightOpenChange, keyboardShortcuts = false, leftCollapsedControl, leftContentClassName, rightContentClassName, centerHeaderVisibility = "always", className }) {
|
|
168
|
+
const desktop = useDesktopMediaQuery(DESKTOP_BREAKPOINT);
|
|
169
|
+
const dragStateRef = useRef(null);
|
|
170
|
+
const storedLayout = useMemo(() => persistenceKey ? readStoredLayout(persistenceKey) : null, [persistenceKey]);
|
|
171
|
+
const [uncontrolledLeftOpen, setUncontrolledLeftOpen] = useState(storedLayout?.leftOpen ?? defaultLeftOpen);
|
|
172
|
+
const [uncontrolledRightOpen, setUncontrolledRightOpen] = useState(storedLayout?.rightOpen ?? defaultRightOpen);
|
|
173
|
+
const leftControlled = leftOpenProp !== void 0;
|
|
174
|
+
const rightControlled = rightOpenProp !== void 0;
|
|
175
|
+
const leftOpen = leftControlled ? leftOpenProp : uncontrolledLeftOpen;
|
|
176
|
+
const rightOpen = rightControlled ? rightOpenProp : uncontrolledRightOpen;
|
|
177
|
+
const setLeftOpen = useCallback((open) => {
|
|
178
|
+
if (!leftControlled) setUncontrolledLeftOpen(open);
|
|
179
|
+
onLeftOpenChange?.(open);
|
|
180
|
+
}, [leftControlled, onLeftOpenChange]);
|
|
181
|
+
const setRightOpen = useCallback((open) => {
|
|
182
|
+
if (!rightControlled) setUncontrolledRightOpen(open);
|
|
183
|
+
onRightOpenChange?.(open);
|
|
184
|
+
}, [rightControlled, onRightOpenChange]);
|
|
185
|
+
const hasLeft = Boolean(left);
|
|
186
|
+
const hasRight = Boolean(right);
|
|
187
|
+
const [bottomOpen, setBottomOpen] = useState(storedLayout?.bottomOpen ?? defaultBottomOpen);
|
|
188
|
+
const [leftWidth, setLeftWidth] = useState(clamp(storedLayout?.leftWidth ?? defaultLeftWidth, minLeftWidth, maxLeftWidth));
|
|
189
|
+
const [rightWidth, setRightWidth] = useState(clamp(storedLayout?.rightWidth ?? defaultRightWidth, minRightWidth, maxRightWidth));
|
|
190
|
+
const [bottomHeight, setBottomHeight] = useState(clamp(storedLayout?.bottomHeight ?? defaultBottomHeight, minBottomHeight, maxBottomHeight));
|
|
191
|
+
const showCenterHeader = centerHeaderVisibility === "auto" ? Boolean(centerHeader) || hasLeft && !leftOpen || hasRight && !rightOpen || Boolean(bottom) && !bottomOpen : Boolean(centerHeader || left || right || bottom);
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
if (!persistenceKey || typeof window === "undefined") return;
|
|
194
|
+
const payload = {
|
|
195
|
+
leftOpen,
|
|
196
|
+
rightOpen,
|
|
197
|
+
bottomOpen,
|
|
198
|
+
leftWidth,
|
|
199
|
+
rightWidth,
|
|
200
|
+
bottomHeight
|
|
201
|
+
};
|
|
202
|
+
window.localStorage.setItem(persistenceKey, JSON.stringify(payload));
|
|
203
|
+
}, [
|
|
204
|
+
bottomHeight,
|
|
205
|
+
bottomOpen,
|
|
206
|
+
leftOpen,
|
|
207
|
+
leftWidth,
|
|
208
|
+
persistenceKey,
|
|
209
|
+
rightOpen,
|
|
210
|
+
rightWidth
|
|
211
|
+
]);
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
if (!keyboardShortcuts || typeof window === "undefined") return;
|
|
214
|
+
const handleKeyDown = (event) => {
|
|
215
|
+
if (!(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey) return;
|
|
216
|
+
if (isEditableTarget(event.target)) return;
|
|
217
|
+
const key = event.key.toLowerCase();
|
|
218
|
+
if (key === "b" && hasLeft) {
|
|
219
|
+
event.preventDefault();
|
|
220
|
+
setLeftOpen(!leftOpen);
|
|
221
|
+
} else if (key === "e" && hasRight) {
|
|
222
|
+
event.preventDefault();
|
|
223
|
+
setRightOpen(!rightOpen);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
227
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
228
|
+
}, [
|
|
229
|
+
hasLeft,
|
|
230
|
+
hasRight,
|
|
231
|
+
keyboardShortcuts,
|
|
232
|
+
leftOpen,
|
|
233
|
+
rightOpen,
|
|
234
|
+
setLeftOpen,
|
|
235
|
+
setRightOpen
|
|
236
|
+
]);
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
if (!desktop) return;
|
|
239
|
+
const handlePointerMove = (event) => {
|
|
240
|
+
const dragState = dragStateRef.current;
|
|
241
|
+
if (!dragState) return;
|
|
242
|
+
if (dragState.side === "bottom") {
|
|
243
|
+
const delta = dragState.pointerStartY - event.clientY;
|
|
244
|
+
setBottomHeight(clamp(dragState.heightStart + delta, minBottomHeight, maxBottomHeight));
|
|
245
|
+
} else if (dragState.side === "left") {
|
|
246
|
+
const delta = event.clientX - dragState.pointerStartX;
|
|
247
|
+
setLeftWidth(clamp(dragState.widthStart + delta, minLeftWidth, maxLeftWidth));
|
|
248
|
+
} else {
|
|
249
|
+
const delta = dragState.pointerStartX - event.clientX;
|
|
250
|
+
setRightWidth(clamp(dragState.widthStart + delta, minRightWidth, maxRightWidth));
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
const handlePointerUp = () => {
|
|
254
|
+
dragStateRef.current = null;
|
|
255
|
+
};
|
|
256
|
+
window.addEventListener("pointermove", handlePointerMove);
|
|
257
|
+
window.addEventListener("pointerup", handlePointerUp);
|
|
258
|
+
window.addEventListener("pointercancel", handlePointerUp);
|
|
259
|
+
return () => {
|
|
260
|
+
window.removeEventListener("pointermove", handlePointerMove);
|
|
261
|
+
window.removeEventListener("pointerup", handlePointerUp);
|
|
262
|
+
window.removeEventListener("pointercancel", handlePointerUp);
|
|
263
|
+
};
|
|
264
|
+
}, [
|
|
265
|
+
desktop,
|
|
266
|
+
maxBottomHeight,
|
|
267
|
+
maxLeftWidth,
|
|
268
|
+
maxRightWidth,
|
|
269
|
+
minBottomHeight,
|
|
270
|
+
minLeftWidth,
|
|
271
|
+
minRightWidth
|
|
272
|
+
]);
|
|
273
|
+
const shellRef = useRef(null);
|
|
274
|
+
const [shellWidth, setShellWidth] = useState(null);
|
|
275
|
+
useEffect(() => {
|
|
276
|
+
const shell = shellRef.current;
|
|
277
|
+
if (!shell || typeof ResizeObserver === "undefined") return;
|
|
278
|
+
const observer = new ResizeObserver((entries) => {
|
|
279
|
+
const width = entries[0]?.contentRect.width;
|
|
280
|
+
if (typeof width === "number") setShellWidth(width);
|
|
281
|
+
});
|
|
282
|
+
observer.observe(shell);
|
|
283
|
+
return () => observer.disconnect();
|
|
284
|
+
}, []);
|
|
285
|
+
const { leftPx, rightPx } = useMemo(() => {
|
|
286
|
+
const leftShown = desktop && hasLeft && leftOpen;
|
|
287
|
+
const rightShown = desktop && hasRight && rightOpen;
|
|
288
|
+
let leftPx = leftWidth;
|
|
289
|
+
let rightPx = rightWidth;
|
|
290
|
+
if (shellWidth === null) return {
|
|
291
|
+
leftPx,
|
|
292
|
+
rightPx
|
|
293
|
+
};
|
|
294
|
+
const room = () => shellWidth - (leftShown ? leftPx : 0) - (rightShown ? rightPx : 0);
|
|
295
|
+
if (rightShown && room() < minCenterWidth) rightPx = Math.max(minRightWidth, rightPx - (minCenterWidth - room()));
|
|
296
|
+
if (leftShown && room() < minCenterWidth) leftPx = Math.max(minLeftWidth, leftPx - (minCenterWidth - room()));
|
|
297
|
+
return {
|
|
298
|
+
leftPx,
|
|
299
|
+
rightPx
|
|
300
|
+
};
|
|
301
|
+
}, [
|
|
302
|
+
desktop,
|
|
303
|
+
hasLeft,
|
|
304
|
+
hasRight,
|
|
305
|
+
leftOpen,
|
|
306
|
+
leftWidth,
|
|
307
|
+
minCenterWidth,
|
|
308
|
+
minLeftWidth,
|
|
309
|
+
minRightWidth,
|
|
310
|
+
rightOpen,
|
|
311
|
+
rightWidth,
|
|
312
|
+
shellWidth
|
|
313
|
+
]);
|
|
314
|
+
const leftStyle = useMemo(() => ({ width: `${leftPx}px` }), [leftPx]);
|
|
315
|
+
const rightStyle = useMemo(() => ({ width: `${rightPx}px` }), [rightPx]);
|
|
316
|
+
const startResize = (side, pointerStartX) => {
|
|
317
|
+
dragStateRef.current = {
|
|
318
|
+
side,
|
|
319
|
+
pointerStartX,
|
|
320
|
+
pointerStartY: 0,
|
|
321
|
+
widthStart: side === "left" ? leftWidth : rightWidth,
|
|
322
|
+
heightStart: 0
|
|
323
|
+
};
|
|
324
|
+
};
|
|
325
|
+
const startBottomResize = (pointerStartY) => {
|
|
326
|
+
dragStateRef.current = {
|
|
327
|
+
side: "bottom",
|
|
328
|
+
pointerStartX: 0,
|
|
329
|
+
pointerStartY,
|
|
330
|
+
widthStart: 0,
|
|
331
|
+
heightStart: bottomHeight
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
const stepLeftWidth = (delta) => {
|
|
335
|
+
setLeftWidth((current) => clamp(current + delta, minLeftWidth, maxLeftWidth));
|
|
336
|
+
};
|
|
337
|
+
const stepRightWidth = (delta) => {
|
|
338
|
+
setRightWidth((current) => clamp(current + delta, minRightWidth, maxRightWidth));
|
|
339
|
+
};
|
|
340
|
+
const stepBottomHeight = (delta) => {
|
|
341
|
+
setBottomHeight((current) => clamp(current + delta, minBottomHeight, maxBottomHeight));
|
|
342
|
+
};
|
|
343
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
344
|
+
...theme ? {
|
|
345
|
+
"data-sandbox-ui": "true",
|
|
346
|
+
"data-sandbox-theme": theme
|
|
347
|
+
} : {},
|
|
348
|
+
"data-density": density,
|
|
349
|
+
className: cn$1("flex h-screen flex-col overflow-hidden bg-[var(--bg-root)] text-foreground font-sans", className),
|
|
350
|
+
children: [
|
|
351
|
+
/* @__PURE__ */ jsxs("div", {
|
|
352
|
+
ref: shellRef,
|
|
353
|
+
className: "flex min-h-0 flex-1",
|
|
354
|
+
children: [
|
|
355
|
+
desktop && left && leftOpen && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("aside", {
|
|
356
|
+
"aria-label": leftLabel,
|
|
357
|
+
style: leftStyle,
|
|
358
|
+
className: "hidden shrink-0 border-r border-[var(--md3-outline-variant)] bg-surface-container-low lg:flex lg:flex-col",
|
|
359
|
+
children: [leftHeader && /* @__PURE__ */ jsxs(WorkspacePaneHeader, {
|
|
360
|
+
className: "justify-between gap-2",
|
|
361
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
362
|
+
className: "min-w-0 flex-1",
|
|
363
|
+
children: leftHeader
|
|
364
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
365
|
+
type: "button",
|
|
366
|
+
"aria-label": "Collapse left panel",
|
|
367
|
+
onClick: () => setLeftOpen(false),
|
|
368
|
+
className: `rounded-[2px] p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
|
|
369
|
+
children: /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-4 w-4" })
|
|
370
|
+
})]
|
|
371
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
372
|
+
className: cn$1("min-h-0 flex-1 overflow-auto py-1", leftContentClassName),
|
|
373
|
+
children: left
|
|
374
|
+
})]
|
|
375
|
+
}), resizable && /* @__PURE__ */ jsx(ResizeHandle, {
|
|
376
|
+
label: "Resize left panel",
|
|
377
|
+
onDragStart: (clientX) => startResize("left", clientX),
|
|
378
|
+
onStep: stepLeftWidth
|
|
379
|
+
})] }),
|
|
380
|
+
/* @__PURE__ */ jsxs("main", {
|
|
381
|
+
className: "flex min-w-0 flex-1 flex-col",
|
|
382
|
+
children: [
|
|
383
|
+
showCenterHeader && /* @__PURE__ */ jsxs(WorkspacePaneHeader, {
|
|
384
|
+
className: "gap-2",
|
|
385
|
+
children: [
|
|
386
|
+
left && !leftOpen && (leftCollapsedControl ?? /* @__PURE__ */ jsx("button", {
|
|
387
|
+
type: "button",
|
|
388
|
+
"aria-label": "Open left panel",
|
|
389
|
+
onClick: () => setLeftOpen(true),
|
|
390
|
+
className: `rounded-[2px] p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
|
|
391
|
+
children: /* @__PURE__ */ jsx(PanelLeftOpen, { className: "h-4 w-4" })
|
|
392
|
+
})),
|
|
393
|
+
/* @__PURE__ */ jsx("div", {
|
|
394
|
+
className: "min-w-0 flex-1",
|
|
395
|
+
children: centerHeader
|
|
396
|
+
}),
|
|
397
|
+
bottom && !bottomOpen && /* @__PURE__ */ jsx("button", {
|
|
398
|
+
type: "button",
|
|
399
|
+
"aria-label": "Open bottom panel",
|
|
400
|
+
onClick: () => setBottomOpen(true),
|
|
401
|
+
className: `rounded-[2px] p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
|
|
402
|
+
children: /* @__PURE__ */ jsx(PanelBottomOpen, { className: "h-4 w-4" })
|
|
403
|
+
}),
|
|
404
|
+
right && !rightOpen && /* @__PURE__ */ jsx("button", {
|
|
405
|
+
type: "button",
|
|
406
|
+
"aria-label": "Open right panel",
|
|
407
|
+
onClick: () => setRightOpen(true),
|
|
408
|
+
className: `rounded-[2px] p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
|
|
409
|
+
children: /* @__PURE__ */ jsx(PanelRightOpen, { className: "h-4 w-4" })
|
|
410
|
+
})
|
|
411
|
+
]
|
|
412
|
+
}),
|
|
413
|
+
/* @__PURE__ */ jsx("div", {
|
|
414
|
+
className: "min-h-0 flex-1 overflow-auto",
|
|
415
|
+
children: center
|
|
416
|
+
}),
|
|
417
|
+
bottom && bottomOpen && /* @__PURE__ */ jsxs(Fragment, { children: [resizable && /* @__PURE__ */ jsx(HorizontalResizeHandle, {
|
|
418
|
+
label: "Resize bottom panel",
|
|
419
|
+
onDragStart: startBottomResize,
|
|
420
|
+
onStep: stepBottomHeight
|
|
421
|
+
}), /* @__PURE__ */ jsx("section", {
|
|
422
|
+
"aria-label": bottomLabel,
|
|
423
|
+
className: "border-t border-[var(--md3-outline-variant)] bg-surface-container shrink-0",
|
|
424
|
+
style: { height: `${bottomHeight}px` },
|
|
425
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
426
|
+
className: "flex h-full flex-col",
|
|
427
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
428
|
+
className: "flex items-center justify-between gap-2 border-b border-[var(--md3-outline-variant)] px-3 py-1.5 bg-surface-container-high shrink-0",
|
|
429
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
430
|
+
className: "min-w-0 flex-1",
|
|
431
|
+
children: bottomHeader ?? /* @__PURE__ */ jsx("span", {
|
|
432
|
+
className: "text-[11px] font-semibold uppercase tracking-wider text-muted-foreground",
|
|
433
|
+
children: "Runtime"
|
|
434
|
+
})
|
|
435
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
436
|
+
type: "button",
|
|
437
|
+
"aria-label": "Collapse bottom panel",
|
|
438
|
+
onClick: () => setBottomOpen(false),
|
|
439
|
+
className: `rounded-[2px] p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
|
|
440
|
+
children: /* @__PURE__ */ jsx(PanelBottomClose, { className: "h-4 w-4" })
|
|
441
|
+
})]
|
|
442
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
443
|
+
className: "min-h-0 flex-1 overflow-auto",
|
|
444
|
+
children: bottom
|
|
445
|
+
})]
|
|
446
|
+
})
|
|
447
|
+
})] }),
|
|
448
|
+
centerFooter && /* @__PURE__ */ jsx("div", {
|
|
449
|
+
className: "shrink-0 border-t border-[var(--md3-outline-variant)] bg-surface-container-low",
|
|
450
|
+
children: centerFooter
|
|
451
|
+
})
|
|
452
|
+
]
|
|
453
|
+
}),
|
|
454
|
+
desktop && right && rightOpen && /* @__PURE__ */ jsxs(Fragment, { children: [resizable && /* @__PURE__ */ jsx(ResizeHandle, {
|
|
455
|
+
label: "Resize right panel",
|
|
456
|
+
onDragStart: (clientX) => startResize("right", clientX),
|
|
457
|
+
onStep: stepRightWidth
|
|
458
|
+
}), /* @__PURE__ */ jsxs("aside", {
|
|
459
|
+
"aria-label": rightLabel,
|
|
460
|
+
style: rightStyle,
|
|
461
|
+
className: "hidden shrink-0 border-l border-[var(--md3-outline-variant)] bg-surface-container-low lg:flex lg:flex-col",
|
|
462
|
+
children: [/* @__PURE__ */ jsxs(WorkspacePaneHeader, {
|
|
463
|
+
className: "justify-between gap-2",
|
|
464
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
465
|
+
className: "min-w-0 flex-1",
|
|
466
|
+
children: rightHeader ?? /* @__PURE__ */ jsx("span", {
|
|
467
|
+
className: "text-[13px] font-medium text-foreground",
|
|
468
|
+
children: "Artifacts"
|
|
469
|
+
})
|
|
470
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
471
|
+
type: "button",
|
|
472
|
+
"aria-label": "Collapse right panel",
|
|
473
|
+
onClick: () => setRightOpen(false),
|
|
474
|
+
className: `rounded-[2px] p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
|
|
475
|
+
children: /* @__PURE__ */ jsx(PanelRightClose, { className: "h-4 w-4" })
|
|
476
|
+
})]
|
|
477
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
478
|
+
className: cn$1("min-h-0 flex-1 overflow-auto", rightContentClassName),
|
|
479
|
+
children: right
|
|
480
|
+
})]
|
|
481
|
+
})] })
|
|
482
|
+
]
|
|
483
|
+
}),
|
|
484
|
+
!desktop && left && leftOpen && /* @__PURE__ */ jsx(MobileDrawer, {
|
|
485
|
+
side: "left",
|
|
486
|
+
title: leftLabel,
|
|
487
|
+
header: leftHeader,
|
|
488
|
+
onClose: () => setLeftOpen(false),
|
|
489
|
+
children: left
|
|
490
|
+
}),
|
|
491
|
+
!desktop && right && rightOpen && /* @__PURE__ */ jsx(MobileDrawer, {
|
|
492
|
+
side: "right",
|
|
493
|
+
title: rightLabel,
|
|
494
|
+
header: rightHeader,
|
|
495
|
+
onClose: () => setRightOpen(false),
|
|
496
|
+
children: right
|
|
497
|
+
})
|
|
498
|
+
]
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
//#endregion
|
|
502
|
+
//#region src/workspace/directory-pane.tsx
|
|
503
|
+
function countNodes(node) {
|
|
504
|
+
if (node.type === "file") return {
|
|
505
|
+
files: 1,
|
|
506
|
+
directories: 0
|
|
507
|
+
};
|
|
508
|
+
return (node.children ?? []).reduce((totals, child) => {
|
|
509
|
+
const counts = countNodes(child);
|
|
510
|
+
return {
|
|
511
|
+
files: totals.files + counts.files,
|
|
512
|
+
directories: totals.directories + counts.directories
|
|
513
|
+
};
|
|
514
|
+
}, {
|
|
515
|
+
files: 0,
|
|
516
|
+
directories: 1
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
function filterTree(node, query) {
|
|
520
|
+
if (!query) return node;
|
|
521
|
+
const normalized = query.trim().toLowerCase();
|
|
522
|
+
const matches = node.name.toLowerCase().includes(normalized) || node.path.toLowerCase().includes(normalized);
|
|
523
|
+
if (node.type === "file") return matches ? node : null;
|
|
524
|
+
const filteredChildren = (node.children ?? []).map((child) => filterTree(child, normalized)).filter((child) => child !== null);
|
|
525
|
+
if (matches || filteredChildren.length > 0) return {
|
|
526
|
+
...node,
|
|
527
|
+
children: filteredChildren
|
|
528
|
+
};
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* DirectoryPane — searchable file-system navigation surface for sandbox
|
|
533
|
+
* sessions.
|
|
534
|
+
*/
|
|
535
|
+
function DirectoryPane({ root, selectedPath, onSelect, onRefresh, onUpload, title = "Directory", subtitle, defaultExpanded = true, searchPlaceholder = "Search files and folders…", visibility, className }) {
|
|
536
|
+
const [query, setQuery] = useState("");
|
|
537
|
+
const visibleRoot = useMemo(() => filterFileTree(root, visibility), [root, visibility]);
|
|
538
|
+
const filteredRoot = useMemo(() => visibleRoot ? filterTree(visibleRoot, query) : null, [query, visibleRoot]);
|
|
539
|
+
const counts = useMemo(() => visibleRoot ? countNodes(visibleRoot) : {
|
|
540
|
+
files: 0,
|
|
541
|
+
directories: 0
|
|
542
|
+
}, [visibleRoot]);
|
|
543
|
+
return /* @__PURE__ */ jsx(ArtifactPane, {
|
|
544
|
+
eyebrow: "Workspace",
|
|
545
|
+
title,
|
|
546
|
+
subtitle: subtitle ?? root.path,
|
|
547
|
+
className,
|
|
548
|
+
toolbar: /* @__PURE__ */ jsxs("div", {
|
|
549
|
+
className: "flex items-center gap-2",
|
|
550
|
+
children: [
|
|
551
|
+
/* @__PURE__ */ jsx("div", {
|
|
552
|
+
className: "flex-1",
|
|
553
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
554
|
+
value: query,
|
|
555
|
+
onChange: (event) => setQuery(event.target.value),
|
|
556
|
+
placeholder: searchPlaceholder,
|
|
557
|
+
className: "h-7 text-xs rounded-[2px]"
|
|
558
|
+
})
|
|
559
|
+
}),
|
|
560
|
+
onRefresh && /* @__PURE__ */ jsx("button", {
|
|
561
|
+
type: "button",
|
|
562
|
+
"aria-label": "Refresh directory",
|
|
563
|
+
onClick: onRefresh,
|
|
564
|
+
className: `rounded-[2px] p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
|
|
565
|
+
children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4" })
|
|
566
|
+
}),
|
|
567
|
+
onUpload && /* @__PURE__ */ jsx("button", {
|
|
568
|
+
type: "button",
|
|
569
|
+
"aria-label": "Upload files",
|
|
570
|
+
onClick: onUpload,
|
|
571
|
+
className: `rounded-[2px] p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
|
|
572
|
+
children: /* @__PURE__ */ jsx(Upload, { className: "h-4 w-4" })
|
|
573
|
+
})
|
|
574
|
+
]
|
|
575
|
+
}),
|
|
576
|
+
footer: /* @__PURE__ */ jsxs("div", {
|
|
577
|
+
className: "flex items-center justify-between text-xs text-muted-foreground",
|
|
578
|
+
children: [/* @__PURE__ */ jsxs("span", { children: [counts.files, " files"] }), /* @__PURE__ */ jsxs("span", { children: [Math.max(counts.directories - 1, 0), " folders"] })]
|
|
579
|
+
}),
|
|
580
|
+
emptyState: /* @__PURE__ */ jsx(EmptyState, {
|
|
581
|
+
icon: /* @__PURE__ */ jsx(Search, { className: "h-8 w-8" }),
|
|
582
|
+
title: "No matching files",
|
|
583
|
+
description: "Try a different search or clear the current filter."
|
|
584
|
+
}),
|
|
585
|
+
children: filteredRoot ? /* @__PURE__ */ jsx("div", {
|
|
586
|
+
className: "py-2",
|
|
587
|
+
children: /* @__PURE__ */ jsx(FileTree, {
|
|
588
|
+
root: filteredRoot,
|
|
589
|
+
selectedPath,
|
|
590
|
+
onSelect,
|
|
591
|
+
defaultExpanded,
|
|
592
|
+
visibility
|
|
593
|
+
})
|
|
594
|
+
}) : null
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
//#endregion
|
|
598
|
+
//#region src/workspace/status-banner.tsx
|
|
599
|
+
/**
|
|
600
|
+
* StatusBanner — full-width notification banner for connection/provisioning states.
|
|
601
|
+
*/
|
|
602
|
+
const BANNER_STYLES = {
|
|
603
|
+
provisioning: {
|
|
604
|
+
bg: "bg-primary/5",
|
|
605
|
+
border: "border-primary/20",
|
|
606
|
+
text: "text-primary",
|
|
607
|
+
icon: Loader2
|
|
608
|
+
},
|
|
609
|
+
connecting: {
|
|
610
|
+
bg: "bg-[var(--code-number)]/5",
|
|
611
|
+
border: "border-[var(--code-number)]/20",
|
|
612
|
+
text: "text-[var(--code-number)]",
|
|
613
|
+
icon: Wifi
|
|
614
|
+
},
|
|
615
|
+
error: {
|
|
616
|
+
bg: "bg-[var(--code-error)]/5",
|
|
617
|
+
border: "border-[var(--code-error)]/20",
|
|
618
|
+
text: "text-[var(--code-error)]",
|
|
619
|
+
icon: AlertCircle
|
|
620
|
+
},
|
|
621
|
+
warning: {
|
|
622
|
+
bg: "bg-[var(--surface-warning-bg)]",
|
|
623
|
+
border: "border-[var(--surface-warning-border)]",
|
|
624
|
+
text: "text-[var(--surface-warning-text)]",
|
|
625
|
+
icon: AlertTriangle
|
|
626
|
+
},
|
|
627
|
+
success: {
|
|
628
|
+
bg: "bg-[var(--code-success)]/5",
|
|
629
|
+
border: "border-[var(--code-success)]/20",
|
|
630
|
+
text: "text-[var(--code-success)]",
|
|
631
|
+
icon: CheckCircle
|
|
632
|
+
},
|
|
633
|
+
info: {
|
|
634
|
+
bg: "bg-surface-container-high",
|
|
635
|
+
border: "border-[var(--md3-outline-variant)]",
|
|
636
|
+
text: "text-muted-foreground",
|
|
637
|
+
icon: AlertCircle
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
function StatusBanner({ type, message, detail, onDismiss, className }) {
|
|
641
|
+
const style = BANNER_STYLES[type];
|
|
642
|
+
const Icon = style.icon;
|
|
643
|
+
const isAnimated = type === "provisioning" || type === "connecting";
|
|
644
|
+
const role = type === "error" ? "alert" : "status";
|
|
645
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
646
|
+
className: cn$1("flex items-center gap-2.5 px-4 py-2 border-b font-sans text-sm", style.bg, style.border, className),
|
|
647
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
648
|
+
role,
|
|
649
|
+
className: "flex min-w-0 items-center gap-2.5",
|
|
650
|
+
children: [
|
|
651
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
652
|
+
className: cn$1("h-4 w-4 shrink-0", style.text, isAnimated && "animate-spin"),
|
|
653
|
+
...isAnimated ? { "data-motion": "essential" } : {}
|
|
654
|
+
}),
|
|
655
|
+
/* @__PURE__ */ jsx("span", {
|
|
656
|
+
className: "font-medium text-foreground",
|
|
657
|
+
children: message
|
|
658
|
+
}),
|
|
659
|
+
detail && /* @__PURE__ */ jsx("span", {
|
|
660
|
+
className: "font-mono text-xs text-muted-foreground tabular-nums",
|
|
661
|
+
children: detail
|
|
662
|
+
})
|
|
663
|
+
]
|
|
664
|
+
}), onDismiss && /* @__PURE__ */ jsx("button", {
|
|
665
|
+
onClick: onDismiss,
|
|
666
|
+
className: "ml-auto font-sans text-xs font-medium text-muted-foreground transition-colors hover:text-foreground",
|
|
667
|
+
children: "Dismiss"
|
|
668
|
+
})]
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
//#endregion
|
|
672
|
+
//#region src/workspace/status-bar.tsx
|
|
673
|
+
/**
|
|
674
|
+
* StatusBar — compact bottom bar: connection state, file/artifact context
|
|
675
|
+
* badges, credits. The active model lives in the input box, not here.
|
|
676
|
+
*/
|
|
677
|
+
const STATUS_LABELS = {
|
|
678
|
+
connected: {
|
|
679
|
+
label: "Connected",
|
|
680
|
+
color: "bg-[var(--code-success)]"
|
|
681
|
+
},
|
|
682
|
+
connecting: {
|
|
683
|
+
label: "Connecting…",
|
|
684
|
+
color: "bg-[var(--code-number)]"
|
|
685
|
+
},
|
|
686
|
+
disconnected: {
|
|
687
|
+
label: "Disconnected",
|
|
688
|
+
color: "bg-[var(--code-error)]"
|
|
689
|
+
},
|
|
690
|
+
provisioning: {
|
|
691
|
+
label: "Provisioning…",
|
|
692
|
+
color: "bg-[var(--code-number)]"
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
function StatusBar({ credits, contextBadges = [], onRemoveBadge, status = "connected", className }) {
|
|
696
|
+
const statusInfo = STATUS_LABELS[status];
|
|
697
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
698
|
+
className: cn$1("flex flex-wrap items-center gap-3 px-3 py-1 border-t border-[var(--md3-outline-variant)] bg-surface-container-high font-sans text-[12px]", className),
|
|
699
|
+
children: [
|
|
700
|
+
/* @__PURE__ */ jsxs("span", {
|
|
701
|
+
className: "inline-flex items-center gap-1.5 text-muted-foreground",
|
|
702
|
+
children: [/* @__PURE__ */ jsx("span", { className: cn$1("w-1.5 h-1.5 rounded-full", statusInfo.color) }), statusInfo.label]
|
|
703
|
+
}),
|
|
704
|
+
contextBadges.map((badge) => /* @__PURE__ */ jsxs("span", {
|
|
705
|
+
className: "inline-flex items-center gap-1 px-2 py-0.5 rounded-[2px] border border-[var(--md3-outline-variant)] text-foreground bg-[var(--border-accent)]/5",
|
|
706
|
+
children: [
|
|
707
|
+
/* @__PURE__ */ jsx(FileText, { className: "h-3 w-3" }),
|
|
708
|
+
badge.label,
|
|
709
|
+
badge.count !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
710
|
+
className: "text-muted-foreground",
|
|
711
|
+
children: badge.count
|
|
712
|
+
}),
|
|
713
|
+
onRemoveBadge && /* @__PURE__ */ jsx("button", {
|
|
714
|
+
type: "button",
|
|
715
|
+
"aria-label": `Remove ${badge.label} from context`,
|
|
716
|
+
onClick: () => onRemoveBadge(badge.id),
|
|
717
|
+
className: "hover:text-foreground transition-colors",
|
|
718
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-2.5 w-2.5" })
|
|
719
|
+
})
|
|
720
|
+
]
|
|
721
|
+
}, badge.id)),
|
|
722
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
723
|
+
credits !== void 0 && /* @__PURE__ */ jsxs("span", {
|
|
724
|
+
className: "inline-flex items-center gap-1 text-muted-foreground",
|
|
725
|
+
children: [
|
|
726
|
+
/* @__PURE__ */ jsx(Zap, { className: "h-3 w-3 shrink-0" }),
|
|
727
|
+
/* @__PURE__ */ jsx("span", {
|
|
728
|
+
className: "font-mono tabular-nums",
|
|
729
|
+
children: credits.toLocaleString()
|
|
730
|
+
}),
|
|
731
|
+
/* @__PURE__ */ jsx("span", { children: "credits" })
|
|
732
|
+
]
|
|
733
|
+
})
|
|
734
|
+
]
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
//#endregion
|
|
738
|
+
//#region src/workspace/terminal-panel.tsx
|
|
739
|
+
/**
|
|
740
|
+
* Terminal — read-only terminal output panel.
|
|
741
|
+
*
|
|
742
|
+
* Shows agent's bash command output with basic ANSI color support.
|
|
743
|
+
* Collapsible, auto-scrolls to bottom.
|
|
744
|
+
*/
|
|
745
|
+
const LINE_COLORS = {
|
|
746
|
+
command: "text-[var(--code-function)]",
|
|
747
|
+
stdout: "text-foreground",
|
|
748
|
+
stderr: "text-[var(--code-error)]",
|
|
749
|
+
system: "text-muted-foreground"
|
|
750
|
+
};
|
|
751
|
+
const LINE_PREFIXES = {
|
|
752
|
+
command: "$ ",
|
|
753
|
+
stdout: "",
|
|
754
|
+
stderr: "",
|
|
755
|
+
system: "# "
|
|
756
|
+
};
|
|
757
|
+
function TerminalPanel({ lines, title = "Terminal", isCollapsed = false, onToggle, onClose, maxHeight = 200, className }) {
|
|
758
|
+
const scrollRef = useRef(null);
|
|
759
|
+
const contentId = useId();
|
|
760
|
+
useEffect(() => {
|
|
761
|
+
if (!isCollapsed && scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
762
|
+
}, [lines, isCollapsed]);
|
|
763
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
764
|
+
className: cn$1("border-t border-[var(--md3-outline-variant)] bg-surface-container", className),
|
|
765
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
766
|
+
className: "flex items-center px-1",
|
|
767
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
768
|
+
type: "button",
|
|
769
|
+
onClick: onToggle,
|
|
770
|
+
disabled: !onToggle,
|
|
771
|
+
"aria-expanded": !isCollapsed,
|
|
772
|
+
"aria-controls": contentId,
|
|
773
|
+
className: `flex min-w-0 flex-1 items-center gap-2 rounded px-2 py-1.5 text-left text-xs text-muted-foreground transition-colors hover:text-foreground disabled:cursor-default ${focusRing}`,
|
|
774
|
+
children: [
|
|
775
|
+
/* @__PURE__ */ jsx(Terminal, {
|
|
776
|
+
className: "h-3.5 w-3.5",
|
|
777
|
+
"aria-hidden": "true"
|
|
778
|
+
}),
|
|
779
|
+
/* @__PURE__ */ jsx("span", {
|
|
780
|
+
className: "font-medium",
|
|
781
|
+
children: title
|
|
782
|
+
}),
|
|
783
|
+
lines.length > 0 && /* @__PURE__ */ jsx("span", {
|
|
784
|
+
className: "rounded-full bg-surface-container-high px-1.5 py-0.5 text-[10px] tabular-nums",
|
|
785
|
+
children: lines.length
|
|
786
|
+
}),
|
|
787
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
788
|
+
isCollapsed ? /* @__PURE__ */ jsx(ChevronUp, {
|
|
789
|
+
className: "h-3 w-3",
|
|
790
|
+
"aria-hidden": "true"
|
|
791
|
+
}) : /* @__PURE__ */ jsx(ChevronDown, {
|
|
792
|
+
className: "h-3 w-3",
|
|
793
|
+
"aria-hidden": "true"
|
|
794
|
+
})
|
|
795
|
+
]
|
|
796
|
+
}), onClose && /* @__PURE__ */ jsx("button", {
|
|
797
|
+
type: "button",
|
|
798
|
+
"aria-label": `Close ${title}`,
|
|
799
|
+
onClick: onClose,
|
|
800
|
+
className: `rounded p-1.5 text-muted-foreground transition-colors hover:text-foreground ${focusRing}`,
|
|
801
|
+
children: /* @__PURE__ */ jsx(X, {
|
|
802
|
+
className: "h-3 w-3",
|
|
803
|
+
"aria-hidden": "true"
|
|
804
|
+
})
|
|
805
|
+
})]
|
|
806
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
807
|
+
id: contentId,
|
|
808
|
+
hidden: isCollapsed,
|
|
809
|
+
ref: scrollRef,
|
|
810
|
+
className: "overflow-auto px-3 pb-2 font-mono text-xs leading-[1.6] bg-surface-container-lowest",
|
|
811
|
+
style: { maxHeight },
|
|
812
|
+
children: [lines.map((line) => /* @__PURE__ */ jsxs("div", {
|
|
813
|
+
className: cn$1("whitespace-pre-wrap", LINE_COLORS[line.type]),
|
|
814
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
815
|
+
className: "text-muted-foreground select-none",
|
|
816
|
+
children: LINE_PREFIXES[line.type]
|
|
817
|
+
}), line.text]
|
|
818
|
+
}, line.id)), lines.length === 0 && /* @__PURE__ */ jsx("div", {
|
|
819
|
+
className: "text-muted-foreground py-2",
|
|
820
|
+
children: "No output yet"
|
|
821
|
+
})]
|
|
822
|
+
})]
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
//#endregion
|
|
826
|
+
//#region src/workspace/runtime-pane.tsx
|
|
827
|
+
/**
|
|
828
|
+
* RuntimePane — shared operating surface for sandbox session state, terminal
|
|
829
|
+
* output, audit results, and runtime metadata.
|
|
830
|
+
*/
|
|
831
|
+
function RuntimePane({ title = "Runtime", subtitle = "Session state, execution output, and inspection surfaces", statusBanner, statusBar, content, terminal, audit, inspector, footer, className }) {
|
|
832
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
833
|
+
className: cn$1("flex h-full min-h-0 flex-col overflow-hidden bg-surface-container text-foreground", className),
|
|
834
|
+
children: [
|
|
835
|
+
/* @__PURE__ */ jsxs("header", {
|
|
836
|
+
className: "border-b border-[var(--md3-outline-variant)] bg-surface-container-high bg-[linear-gradient(180deg,rgba(255,255,255,0.03),transparent)] px-4 py-3",
|
|
837
|
+
children: [
|
|
838
|
+
/* @__PURE__ */ jsx("div", {
|
|
839
|
+
className: "mb-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-muted-foreground",
|
|
840
|
+
children: "Sandbox"
|
|
841
|
+
}),
|
|
842
|
+
/* @__PURE__ */ jsx("div", {
|
|
843
|
+
className: "text-sm font-semibold text-foreground",
|
|
844
|
+
children: title
|
|
845
|
+
}),
|
|
846
|
+
/* @__PURE__ */ jsx("div", {
|
|
847
|
+
className: "mt-1 text-xs text-muted-foreground",
|
|
848
|
+
children: subtitle
|
|
849
|
+
})
|
|
850
|
+
]
|
|
851
|
+
}),
|
|
852
|
+
statusBanner && /* @__PURE__ */ jsx(StatusBanner, { ...statusBanner }),
|
|
853
|
+
statusBar && /* @__PURE__ */ jsx(StatusBar, { ...statusBar }),
|
|
854
|
+
content ? /* @__PURE__ */ jsx("div", {
|
|
855
|
+
className: "min-h-0 flex-1 overflow-hidden",
|
|
856
|
+
children: content
|
|
857
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
858
|
+
className: "grid min-h-0 flex-1 gap-px bg-[var(--md3-outline-variant)] lg:grid-cols-[minmax(0,1.35fr)_minmax(20rem,0.9fr)]",
|
|
859
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
860
|
+
className: "min-h-0 overflow-auto bg-surface-container",
|
|
861
|
+
children: terminal ? /* @__PURE__ */ jsx(TerminalPanel, {
|
|
862
|
+
...terminal,
|
|
863
|
+
className: cn$1("border-t-0 bg-transparent", terminal.className)
|
|
864
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
865
|
+
className: "flex h-full items-center justify-center p-6 text-sm text-muted-foreground",
|
|
866
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
867
|
+
className: "flex items-center gap-2",
|
|
868
|
+
children: [/* @__PURE__ */ jsx(TerminalSquare, { className: "h-4 w-4" }), "No terminal activity yet"]
|
|
869
|
+
})
|
|
870
|
+
})
|
|
871
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
872
|
+
className: "grid min-h-0 gap-px bg-[var(--md3-outline-variant)]",
|
|
873
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
874
|
+
className: "min-h-0 overflow-auto bg-surface-container",
|
|
875
|
+
children: audit ? audit : /* @__PURE__ */ jsx("div", {
|
|
876
|
+
className: "flex h-full items-center justify-center p-6 text-sm text-muted-foreground",
|
|
877
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
878
|
+
className: "flex items-center gap-2",
|
|
879
|
+
children: [/* @__PURE__ */ jsx(ShieldCheck, { className: "h-4 w-4" }), "No audit data available"]
|
|
880
|
+
})
|
|
881
|
+
})
|
|
882
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
883
|
+
className: "min-h-0 overflow-auto bg-surface-container",
|
|
884
|
+
children: inspector ? inspector : /* @__PURE__ */ jsx("div", {
|
|
885
|
+
className: "flex h-full items-center justify-center p-6 text-sm text-muted-foreground",
|
|
886
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
887
|
+
className: "flex items-center gap-2",
|
|
888
|
+
children: [/* @__PURE__ */ jsx(Binary, { className: "h-4 w-4" }), "No runtime inspector attached"]
|
|
889
|
+
})
|
|
890
|
+
})
|
|
891
|
+
})]
|
|
892
|
+
})]
|
|
893
|
+
}),
|
|
894
|
+
footer && /* @__PURE__ */ jsx("footer", {
|
|
895
|
+
className: "shrink-0 border-t border-[var(--md3-outline-variant)] bg-surface-container-high px-3 py-2",
|
|
896
|
+
children: footer
|
|
897
|
+
})
|
|
898
|
+
]
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
//#endregion
|
|
902
|
+
//#region src/lib/format-relative-age.ts
|
|
903
|
+
const MINUTE = 6e4;
|
|
904
|
+
const HOUR = 60 * MINUTE;
|
|
905
|
+
const DAY = 24 * HOUR;
|
|
906
|
+
const WEEK = 7 * DAY;
|
|
907
|
+
const MONTH = 30 * DAY;
|
|
908
|
+
const YEAR = 365 * DAY;
|
|
909
|
+
/**
|
|
910
|
+
* Compact age for a session rail: `now`, `5m`, `3h`, `2d`, `3w`, `4mo`, `1y`.
|
|
911
|
+
* Unlike `timeAgo` from `@tangle-network/ui/utils` it accepts any date input,
|
|
912
|
+
* rolls past hours into days and beyond, and carries no "ago" suffix, so it
|
|
913
|
+
* fits a "codex · 2h" meta line. A future or unparseable date reads `now` /
|
|
914
|
+
* empty rather than a negative count. `now` is injectable for deterministic tests.
|
|
915
|
+
*/
|
|
916
|
+
function formatRelativeAge(date, now = Date.now()) {
|
|
917
|
+
const timestamp = date instanceof Date ? date.getTime() : new Date(date).getTime();
|
|
918
|
+
if (Number.isNaN(timestamp)) return "";
|
|
919
|
+
const elapsed = Math.max(0, now - timestamp);
|
|
920
|
+
if (elapsed < MINUTE) return "now";
|
|
921
|
+
if (elapsed < HOUR) return `${Math.floor(elapsed / MINUTE)}m`;
|
|
922
|
+
if (elapsed < DAY) return `${Math.floor(elapsed / HOUR)}h`;
|
|
923
|
+
if (elapsed < WEEK) return `${Math.floor(elapsed / DAY)}d`;
|
|
924
|
+
if (elapsed < MONTH) return `${Math.floor(elapsed / WEEK)}w`;
|
|
925
|
+
if (elapsed < YEAR) return `${Math.floor(elapsed / MONTH)}mo`;
|
|
926
|
+
return `${Math.floor(elapsed / YEAR)}y`;
|
|
927
|
+
}
|
|
928
|
+
//#endregion
|
|
929
|
+
//#region src/workspace/session-optimistic.ts
|
|
930
|
+
/**
|
|
931
|
+
* Optimistic session/thread list: reflect a just-created / renamed / deleted
|
|
932
|
+
* item in the sidebar INSTANTLY, before the server loader round-trips.
|
|
933
|
+
*
|
|
934
|
+
* The shell loader is the source of truth and reconciles in the background
|
|
935
|
+
* (typically the app calls `useRevalidator().revalidate()` after its mutation
|
|
936
|
+
* fetch settles). Until that lands, a local mirror of the loader items carries
|
|
937
|
+
* the optimistic edit so a delete / rename / new-thread does not feel broken
|
|
938
|
+
* for the duration of a D1 round-trip.
|
|
939
|
+
*
|
|
940
|
+
* Two ways to drive it, usable together:
|
|
941
|
+
* 1. A window `CustomEvent` (default name {@link SESSION_CREATED_EVENT}) — the
|
|
942
|
+
* surface that creates a thread (often a different route from the sidebar's
|
|
943
|
+
* route) dispatches it; the sidebar prepends the item with no prop wiring.
|
|
944
|
+
* 2. The imperative {@link SessionOptimisticController} returned here — the
|
|
945
|
+
* sidebar's own action handlers (rename / delete / pin) call it directly.
|
|
946
|
+
*/
|
|
947
|
+
/** Default window-event name for "a session/thread was just created". */
|
|
948
|
+
const SESSION_CREATED_EVENT = "sandbox-thread-created";
|
|
949
|
+
/** Dispatch the shared "session created" event so any mounted sidebar updates. */
|
|
950
|
+
function dispatchSessionCreated(detail, eventName = SESSION_CREATED_EVENT) {
|
|
951
|
+
if (typeof window === "undefined") return;
|
|
952
|
+
window.dispatchEvent(new CustomEvent(eventName, { detail }));
|
|
953
|
+
}
|
|
954
|
+
function toItem(detail) {
|
|
955
|
+
return {
|
|
956
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
957
|
+
...detail
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Mirror `items` with optimistic add/update/remove on top. The mirror re-syncs
|
|
962
|
+
* whenever `items` changes (the loader is authoritative), so an optimistic edit
|
|
963
|
+
* is naturally superseded once the server result arrives.
|
|
964
|
+
*/
|
|
965
|
+
function useOptimisticSessionItems(items, options = {}) {
|
|
966
|
+
const { listenForCreate = true, eventName = SESSION_CREATED_EVENT } = options;
|
|
967
|
+
const [optimisticItems, setOptimisticItems] = useState(items);
|
|
968
|
+
useEffect(() => {
|
|
969
|
+
setOptimisticItems(items);
|
|
970
|
+
}, [items]);
|
|
971
|
+
const add = useCallback((detail) => {
|
|
972
|
+
const next = toItem(detail);
|
|
973
|
+
setOptimisticItems((prev) => [next, ...prev.filter((item) => item.id !== next.id)]);
|
|
974
|
+
}, []);
|
|
975
|
+
const update = useCallback((id, patch) => {
|
|
976
|
+
setOptimisticItems((prev) => prev.map((item) => item.id === id ? {
|
|
977
|
+
...item,
|
|
978
|
+
...patch
|
|
979
|
+
} : item));
|
|
980
|
+
}, []);
|
|
981
|
+
const remove = useCallback((id) => {
|
|
982
|
+
setOptimisticItems((prev) => prev.filter((item) => item.id !== id));
|
|
983
|
+
}, []);
|
|
984
|
+
const reset = useCallback(() => setOptimisticItems(items), [items]);
|
|
985
|
+
const addRef = useRef(add);
|
|
986
|
+
addRef.current = add;
|
|
987
|
+
useEffect(() => {
|
|
988
|
+
if (!listenForCreate || typeof window === "undefined") return;
|
|
989
|
+
function onCreated(event) {
|
|
990
|
+
const detail = event.detail;
|
|
991
|
+
if (!detail?.id || !detail.title) return;
|
|
992
|
+
addRef.current(detail);
|
|
993
|
+
}
|
|
994
|
+
window.addEventListener(eventName, onCreated);
|
|
995
|
+
return () => window.removeEventListener(eventName, onCreated);
|
|
996
|
+
}, [listenForCreate, eventName]);
|
|
997
|
+
return {
|
|
998
|
+
items: optimisticItems,
|
|
999
|
+
controller: {
|
|
1000
|
+
add,
|
|
1001
|
+
update,
|
|
1002
|
+
remove,
|
|
1003
|
+
reset
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
//#endregion
|
|
1008
|
+
//#region src/workspace/session-sidebar.tsx
|
|
1009
|
+
const NO_CONTROLLER = {
|
|
1010
|
+
add: () => {},
|
|
1011
|
+
update: () => {},
|
|
1012
|
+
remove: () => {},
|
|
1013
|
+
reset: () => {}
|
|
1014
|
+
};
|
|
1015
|
+
const STATUS_GROUPS = [
|
|
1016
|
+
{
|
|
1017
|
+
key: "attention",
|
|
1018
|
+
label: "Needs input"
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
key: "failed",
|
|
1022
|
+
label: "Failed"
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
key: "working",
|
|
1026
|
+
label: "Working"
|
|
1027
|
+
},
|
|
1028
|
+
{
|
|
1029
|
+
key: "done",
|
|
1030
|
+
label: "Done"
|
|
1031
|
+
}
|
|
1032
|
+
];
|
|
1033
|
+
function groupKeyForStatus(status) {
|
|
1034
|
+
switch (status) {
|
|
1035
|
+
case "attention-needed": return "attention";
|
|
1036
|
+
case "error": return "failed";
|
|
1037
|
+
case "running": return "working";
|
|
1038
|
+
default: return "done";
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
function statusDot(status) {
|
|
1042
|
+
switch (status) {
|
|
1043
|
+
case "running": return "bg-[var(--status-running)]";
|
|
1044
|
+
case "error": return "bg-[var(--surface-danger-text)]";
|
|
1045
|
+
case "attention-needed": return "bg-[var(--surface-warning-text)]";
|
|
1046
|
+
default: return "bg-muted-foreground/40";
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
function iconForLink(icon) {
|
|
1050
|
+
switch (icon) {
|
|
1051
|
+
case "vault": return FolderTree;
|
|
1052
|
+
case "settings": return Settings;
|
|
1053
|
+
case "back": return ArrowLeft;
|
|
1054
|
+
default: return Sparkles;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
function toDate(value) {
|
|
1058
|
+
if (value === void 0) return null;
|
|
1059
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
1060
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
1061
|
+
}
|
|
1062
|
+
function sortItems(items, sessionStatusById) {
|
|
1063
|
+
return [...items].sort((left, right) => {
|
|
1064
|
+
if (Boolean(left.isPinned) !== Boolean(right.isPinned)) return left.isPinned ? -1 : 1;
|
|
1065
|
+
const leftSession = sessionStatusById.get(left.id);
|
|
1066
|
+
const rightSession = sessionStatusById.get(right.id);
|
|
1067
|
+
if (Boolean(leftSession) !== Boolean(rightSession)) return leftSession ? -1 : 1;
|
|
1068
|
+
if (leftSession && rightSession) {
|
|
1069
|
+
if (leftSession.isForeground !== rightSession.isForeground) return Number(rightSession.isForeground) - Number(leftSession.isForeground);
|
|
1070
|
+
const leftRunning = Number(leftSession.status === "running");
|
|
1071
|
+
const rightRunning = Number(rightSession.status === "running");
|
|
1072
|
+
if (leftRunning !== rightRunning) return rightRunning - leftRunning;
|
|
1073
|
+
if (leftSession.lastActivityAt !== rightSession.lastActivityAt) return rightSession.lastActivityAt - leftSession.lastActivityAt;
|
|
1074
|
+
}
|
|
1075
|
+
const leftUpdated = left.updatedAt ? new Date(left.updatedAt).getTime() : 0;
|
|
1076
|
+
return (right.updatedAt ? new Date(right.updatedAt).getTime() : 0) - leftUpdated;
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
function badgeTone(tone = "neutral") {
|
|
1080
|
+
switch (tone) {
|
|
1081
|
+
case "accent": return "border-[var(--border-accent)] bg-[var(--accent-surface-soft)] text-primary";
|
|
1082
|
+
case "success": return "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]";
|
|
1083
|
+
case "warning": return "border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)] text-[var(--surface-warning-text)]";
|
|
1084
|
+
case "danger": return "border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] text-[var(--surface-danger-text)]";
|
|
1085
|
+
default: return "border-[var(--md3-outline-variant)] bg-surface-container-high text-muted-foreground";
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
function navigateToHref(href) {
|
|
1089
|
+
if (!href || typeof window === "undefined") return;
|
|
1090
|
+
window.location.assign(href);
|
|
1091
|
+
}
|
|
1092
|
+
function useResizable(defaultWidth, min, max, onChange) {
|
|
1093
|
+
const [width, setWidth] = useState(defaultWidth);
|
|
1094
|
+
const dragging = useRef(false);
|
|
1095
|
+
const startX = useRef(0);
|
|
1096
|
+
const startW = useRef(0);
|
|
1097
|
+
return {
|
|
1098
|
+
width,
|
|
1099
|
+
onPointerDown: useCallback((e) => {
|
|
1100
|
+
dragging.current = true;
|
|
1101
|
+
startX.current = e.clientX;
|
|
1102
|
+
startW.current = width;
|
|
1103
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
1104
|
+
}, [width]),
|
|
1105
|
+
onPointerMove: useCallback((e) => {
|
|
1106
|
+
if (!dragging.current) return;
|
|
1107
|
+
const delta = e.clientX - startX.current;
|
|
1108
|
+
const next = Math.min(max, Math.max(min, startW.current + delta));
|
|
1109
|
+
setWidth(next);
|
|
1110
|
+
onChange?.(next);
|
|
1111
|
+
}, [
|
|
1112
|
+
min,
|
|
1113
|
+
max,
|
|
1114
|
+
onChange
|
|
1115
|
+
]),
|
|
1116
|
+
onPointerUp: useCallback(() => {
|
|
1117
|
+
dragging.current = false;
|
|
1118
|
+
}, [])
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
function SessionSidebar({ title, subtitle, projectId = null, items, currentItemId, createLabel = "New Session", onCreate, onSelectItem, onSelectLink, links = [], className, emptyMessage = "No sessions yet. Start a conversation.", searchPlaceholder = "Search sessions", enableSearch = true, activityMonitor, filters = [], defaultFilterId, resizable = false, defaultWidth = 256, minWidth = 200, maxWidth = 400, onWidthChange, renderItemActions, optimistic = false, optimisticRef, variant = "default", groupBy = "none", showUpdatedAt = false, onCollapse, fill = false }) {
|
|
1122
|
+
const quiet = variant === "quiet";
|
|
1123
|
+
const [query, setQuery] = useState("");
|
|
1124
|
+
const [activeFilterId, setActiveFilterId] = useState(defaultFilterId ?? filters[0]?.id ?? "all");
|
|
1125
|
+
const optimisticState = useOptimisticSessionItems(items, optimistic === false ? { listenForCreate: false } : typeof optimistic === "object" ? optimistic : void 0);
|
|
1126
|
+
const optimisticEnabled = optimistic !== false;
|
|
1127
|
+
const effectiveItems = optimisticEnabled ? optimisticState.items : items;
|
|
1128
|
+
useImperativeHandle(optimisticRef, () => optimisticEnabled ? optimisticState.controller : NO_CONTROLLER, [optimisticEnabled, optimisticState.controller]);
|
|
1129
|
+
const activeSessions = useNavbarSessions(projectId);
|
|
1130
|
+
const sessionsById = useMemo(() => new Map(activeSessions.map((session) => [session.sessionId, session])), [activeSessions]);
|
|
1131
|
+
const orderedItems = useMemo(() => sortItems(effectiveItems, sessionsById), [effectiveItems, sessionsById]);
|
|
1132
|
+
const visibleItems = useMemo(() => {
|
|
1133
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
1134
|
+
const selectedFilter = filters.find((filter) => filter.id === activeFilterId) ?? null;
|
|
1135
|
+
return orderedItems.filter((item) => {
|
|
1136
|
+
const session = sessionsById.get(item.id) ?? null;
|
|
1137
|
+
const isActive = currentItemId === item.id;
|
|
1138
|
+
if (selectedFilter && !selectedFilter.matches(item, {
|
|
1139
|
+
session,
|
|
1140
|
+
isActive
|
|
1141
|
+
})) return false;
|
|
1142
|
+
if (!normalizedQuery) return true;
|
|
1143
|
+
return `${item.title} ${item.subtitle ?? ""} ${item.category ?? ""} ${(item.badges ?? []).map((badge) => badge.label).join(" ")}`.toLowerCase().includes(normalizedQuery);
|
|
1144
|
+
});
|
|
1145
|
+
}, [
|
|
1146
|
+
activeFilterId,
|
|
1147
|
+
currentItemId,
|
|
1148
|
+
filters,
|
|
1149
|
+
orderedItems,
|
|
1150
|
+
query,
|
|
1151
|
+
sessionsById
|
|
1152
|
+
]);
|
|
1153
|
+
const groups = useMemo(() => {
|
|
1154
|
+
if (groupBy !== "status") return [{
|
|
1155
|
+
key: "all",
|
|
1156
|
+
label: null,
|
|
1157
|
+
items: visibleItems
|
|
1158
|
+
}];
|
|
1159
|
+
return STATUS_GROUPS.map((group) => ({
|
|
1160
|
+
key: group.key,
|
|
1161
|
+
label: group.label,
|
|
1162
|
+
items: visibleItems.filter((item) => groupKeyForStatus(sessionsById.get(item.id)?.status ?? item.status) === group.key)
|
|
1163
|
+
})).filter((group) => group.items.length > 0);
|
|
1164
|
+
}, [
|
|
1165
|
+
groupBy,
|
|
1166
|
+
sessionsById,
|
|
1167
|
+
visibleItems
|
|
1168
|
+
]);
|
|
1169
|
+
const showGroupLabels = groups.length > 1;
|
|
1170
|
+
const runningCount = activeSessions.filter((session) => session.status === "running").length;
|
|
1171
|
+
const filterCounts = useMemo(() => Object.fromEntries(filters.map((filter) => [filter.id, orderedItems.filter((item) => filter.matches(item, {
|
|
1172
|
+
session: sessionsById.get(item.id) ?? null,
|
|
1173
|
+
isActive: currentItemId === item.id
|
|
1174
|
+
})).length])), [
|
|
1175
|
+
currentItemId,
|
|
1176
|
+
filters,
|
|
1177
|
+
orderedItems,
|
|
1178
|
+
sessionsById
|
|
1179
|
+
]);
|
|
1180
|
+
const resize = useResizable(defaultWidth, minWidth, maxWidth, onWidthChange);
|
|
1181
|
+
const fillsParent = fill && !resizable;
|
|
1182
|
+
const renderRow = (item, index) => {
|
|
1183
|
+
const session = sessionsById.get(item.id) ?? null;
|
|
1184
|
+
const isActive = currentItemId === item.id;
|
|
1185
|
+
const status = session?.status ?? item.status;
|
|
1186
|
+
const isWorking = status === "running";
|
|
1187
|
+
const visibleBadges = [...item.isPinned ? [{
|
|
1188
|
+
id: `${item.id}-pinned`,
|
|
1189
|
+
label: "Pinned",
|
|
1190
|
+
tone: "neutral"
|
|
1191
|
+
}] : [], ...item.badges ?? []].filter((badge) => !quiet || (badge.tone ?? "neutral") !== "neutral");
|
|
1192
|
+
const updatedAt = showUpdatedAt && item.meta === void 0 ? toDate(item.updatedAt) : null;
|
|
1193
|
+
const metaLine = item.meta ?? (updatedAt ? /* @__PURE__ */ jsx("time", {
|
|
1194
|
+
dateTime: updatedAt.toISOString(),
|
|
1195
|
+
title: updatedAt.toLocaleString(),
|
|
1196
|
+
children: formatRelativeAge(updatedAt)
|
|
1197
|
+
}) : null);
|
|
1198
|
+
return /* @__PURE__ */ jsx("li", {
|
|
1199
|
+
className: "agent-arrive",
|
|
1200
|
+
style: { "--stagger-index": index },
|
|
1201
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1202
|
+
className: cn$1(quiet ? "group relative flex items-center gap-2 rounded-lg py-2 pl-2 pr-3 transition-colors" : "group relative flex items-center gap-2 rounded-[var(--radius-sm)] px-2.5 py-1.5 transition-colors", MOTION_CONTROL, quiet ? isActive ? "bg-surface-container-high" : "hover:bg-surface-container" : isActive ? "bg-accent font-medium text-foreground" : "text-muted-foreground hover:bg-accent/30"),
|
|
1203
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
1204
|
+
type: "button",
|
|
1205
|
+
onClick: () => {
|
|
1206
|
+
if (onSelectItem) {
|
|
1207
|
+
onSelectItem(item);
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
navigateToHref(item.href);
|
|
1211
|
+
},
|
|
1212
|
+
"aria-current": isActive ? "page" : void 0,
|
|
1213
|
+
className: "min-w-0 flex flex-1 items-center gap-2 text-left",
|
|
1214
|
+
children: [item.icon ? /* @__PURE__ */ jsxs("span", {
|
|
1215
|
+
className: "relative flex h-5 w-4 shrink-0 items-center justify-center",
|
|
1216
|
+
"aria-hidden": "true",
|
|
1217
|
+
children: [item.icon, /* @__PURE__ */ jsx("span", { className: cn$1("absolute -bottom-0.5 -right-0.5 h-1.5 w-1.5 rounded-full", statusDot(status)) })]
|
|
1218
|
+
}) : /* @__PURE__ */ jsx("span", { className: cn$1("h-1.5 w-1.5 shrink-0 rounded-full", statusDot(status)) }), /* @__PURE__ */ jsxs("div", {
|
|
1219
|
+
className: "min-w-0 flex-1",
|
|
1220
|
+
children: [
|
|
1221
|
+
/* @__PURE__ */ jsx("div", {
|
|
1222
|
+
className: cn$1(quiet ? "truncate text-sm leading-5 text-foreground/75 group-hover:text-foreground" : "truncate text-sm font-medium", isActive && "text-foreground", isWorking && "agent-shimmer"),
|
|
1223
|
+
...isWorking ? { "data-motion": "essential" } : {},
|
|
1224
|
+
children: item.title
|
|
1225
|
+
}),
|
|
1226
|
+
item.subtitle && /* @__PURE__ */ jsx("div", {
|
|
1227
|
+
className: "truncate text-xs leading-tight text-muted-foreground",
|
|
1228
|
+
children: item.subtitle
|
|
1229
|
+
}),
|
|
1230
|
+
metaLine && /* @__PURE__ */ jsx("div", {
|
|
1231
|
+
className: "truncate text-xs leading-tight text-muted-foreground",
|
|
1232
|
+
children: metaLine
|
|
1233
|
+
})
|
|
1234
|
+
]
|
|
1235
|
+
})]
|
|
1236
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1237
|
+
className: "flex shrink-0 items-center gap-1",
|
|
1238
|
+
children: [
|
|
1239
|
+
visibleBadges.length > 0 && visibleBadges.slice(0, 1).map((badge) => /* @__PURE__ */ jsx("span", {
|
|
1240
|
+
className: cn$1("rounded-full border px-1.5 py-px text-[8px] font-semibold uppercase", badgeTone(badge.tone)),
|
|
1241
|
+
children: badge.label
|
|
1242
|
+
}, badge.id)),
|
|
1243
|
+
session?.isForeground && /* @__PURE__ */ jsx("span", {
|
|
1244
|
+
className: "h-1.5 w-1.5 rounded-full bg-[var(--status-running)]",
|
|
1245
|
+
title: "Live"
|
|
1246
|
+
}),
|
|
1247
|
+
renderItemActions ? /* @__PURE__ */ jsx("div", {
|
|
1248
|
+
className: cn$1("opacity-0 transition-opacity group-hover:opacity-100", MOTION_CONTROL),
|
|
1249
|
+
onClick: (event) => event.stopPropagation(),
|
|
1250
|
+
children: renderItemActions(item, {
|
|
1251
|
+
session,
|
|
1252
|
+
isActive
|
|
1253
|
+
})
|
|
1254
|
+
}) : null
|
|
1255
|
+
]
|
|
1256
|
+
})]
|
|
1257
|
+
})
|
|
1258
|
+
}, item.id);
|
|
1259
|
+
};
|
|
1260
|
+
let rowIndex = 0;
|
|
1261
|
+
return /* @__PURE__ */ jsxs("aside", {
|
|
1262
|
+
className: cn$1("relative flex shrink-0 flex-col border-r border-[var(--md3-outline-variant)] bg-surface-container-low", fillsParent && "h-full w-full", className),
|
|
1263
|
+
style: fillsParent ? void 0 : { width: resizable ? resize.width : defaultWidth },
|
|
1264
|
+
children: [
|
|
1265
|
+
quiet ? /* @__PURE__ */ jsxs("div", {
|
|
1266
|
+
className: "flex h-14 shrink-0 items-center justify-between gap-2 px-3",
|
|
1267
|
+
children: [
|
|
1268
|
+
/* @__PURE__ */ jsx("span", {
|
|
1269
|
+
className: "sr-only",
|
|
1270
|
+
children: title
|
|
1271
|
+
}),
|
|
1272
|
+
onCreate ? /* @__PURE__ */ jsxs("button", {
|
|
1273
|
+
type: "button",
|
|
1274
|
+
onClick: onCreate,
|
|
1275
|
+
className: cn$1("flex items-center gap-1.5 rounded-md px-1.5 py-1 text-sm text-muted-foreground transition-colors hover:text-foreground", MOTION_CONTROL),
|
|
1276
|
+
children: [/* @__PURE__ */ jsx(Plus, {
|
|
1277
|
+
className: "h-4 w-4",
|
|
1278
|
+
"aria-hidden": "true"
|
|
1279
|
+
}), /* @__PURE__ */ jsx("span", { children: createLabel })]
|
|
1280
|
+
}) : /* @__PURE__ */ jsx("span", {}),
|
|
1281
|
+
onCollapse && /* @__PURE__ */ jsx("button", {
|
|
1282
|
+
type: "button",
|
|
1283
|
+
onClick: onCollapse,
|
|
1284
|
+
"aria-label": "Hide chats",
|
|
1285
|
+
className: cn$1("flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-surface-container hover:text-foreground", "duration-[var(--duration-fast)] ease-[var(--ease-standard)]"),
|
|
1286
|
+
children: /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-4 w-4" })
|
|
1287
|
+
})
|
|
1288
|
+
]
|
|
1289
|
+
}) : /* @__PURE__ */ jsx(WorkspacePaneHeader, { children: /* @__PURE__ */ jsxs("div", {
|
|
1290
|
+
className: "flex w-full items-center justify-between gap-2",
|
|
1291
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1292
|
+
className: "flex min-w-0 items-center gap-2",
|
|
1293
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1294
|
+
className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-[var(--radius-sm)] border border-[var(--border-accent)] bg-[var(--accent-surface-soft)] text-primary",
|
|
1295
|
+
children: /* @__PURE__ */ jsx(MessageSquareText, { className: "h-3.5 w-3.5" })
|
|
1296
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1297
|
+
className: "min-w-0",
|
|
1298
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1299
|
+
className: "truncate text-sm font-semibold text-foreground",
|
|
1300
|
+
children: title
|
|
1301
|
+
}), subtitle && /* @__PURE__ */ jsx("div", {
|
|
1302
|
+
className: "truncate text-xs text-muted-foreground",
|
|
1303
|
+
children: subtitle
|
|
1304
|
+
})]
|
|
1305
|
+
})]
|
|
1306
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1307
|
+
className: "flex shrink-0 items-center gap-1.5",
|
|
1308
|
+
children: [runningCount > 0 && /* @__PURE__ */ jsx("span", {
|
|
1309
|
+
className: "rounded-full border border-[var(--border-accent)] bg-[var(--accent-surface-soft)] px-1.5 py-px text-xs font-medium text-primary",
|
|
1310
|
+
children: runningCount
|
|
1311
|
+
}), onCreate && /* @__PURE__ */ jsx("button", {
|
|
1312
|
+
type: "button",
|
|
1313
|
+
onClick: onCreate,
|
|
1314
|
+
title: createLabel,
|
|
1315
|
+
className: cn$1("flex h-7 w-7 items-center justify-center rounded-[var(--radius-sm)] border border-[var(--md3-outline-variant)] text-muted-foreground transition-colors hover:bg-[var(--accent-surface-soft)] hover:text-foreground", "duration-[var(--duration-fast)] ease-[var(--ease-standard)]"),
|
|
1316
|
+
children: /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" })
|
|
1317
|
+
})]
|
|
1318
|
+
})]
|
|
1319
|
+
}) }),
|
|
1320
|
+
(enableSearch && items.length > 0 || filters.length > 0) && /* @__PURE__ */ jsxs("div", {
|
|
1321
|
+
className: cn$1("shrink-0", quiet ? "px-3 pb-1" : "border-b border-[var(--md3-outline-variant)] px-3 py-2.5"),
|
|
1322
|
+
children: [enableSearch && effectiveItems.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
1323
|
+
className: "relative",
|
|
1324
|
+
children: [/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }), /* @__PURE__ */ jsx("input", {
|
|
1325
|
+
value: query,
|
|
1326
|
+
onChange: (event) => setQuery(event.target.value),
|
|
1327
|
+
placeholder: searchPlaceholder,
|
|
1328
|
+
"aria-label": searchPlaceholder,
|
|
1329
|
+
className: cn$1(quiet ? `h-8 w-full rounded-md border-0 bg-transparent pl-7 pr-2 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus-visible:bg-surface-container ${focusRing}` : `h-8 w-full rounded-[var(--radius-sm)] border bg-surface-container-lowest pl-7 pr-2 text-sm text-foreground placeholder:text-muted-foreground ${focusField}`, "duration-[var(--duration-fast)] ease-[var(--ease-standard)]")
|
|
1330
|
+
})]
|
|
1331
|
+
}), filters.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
1332
|
+
className: cn$1("flex flex-wrap gap-1", enableSearch && items.length > 0 && "mt-2"),
|
|
1333
|
+
children: filters.map((filter) => {
|
|
1334
|
+
const isSelected = activeFilterId === filter.id;
|
|
1335
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
1336
|
+
type: "button",
|
|
1337
|
+
onClick: () => setActiveFilterId(filter.id),
|
|
1338
|
+
className: cn$1("inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-colors", "duration-[var(--duration-fast)] ease-[var(--ease-standard)]", isSelected ? "border-[var(--border-accent)] bg-[var(--accent-surface-soft)] text-primary" : "border-[var(--md3-outline-variant)] text-muted-foreground hover:text-foreground"),
|
|
1339
|
+
children: [/* @__PURE__ */ jsx("span", { children: filter.label }), /* @__PURE__ */ jsx("span", {
|
|
1340
|
+
className: "text-[11px] opacity-60",
|
|
1341
|
+
children: filterCounts[filter.id] ?? 0
|
|
1342
|
+
})]
|
|
1343
|
+
}, filter.id);
|
|
1344
|
+
})
|
|
1345
|
+
})]
|
|
1346
|
+
}),
|
|
1347
|
+
/* @__PURE__ */ jsx("nav", {
|
|
1348
|
+
"aria-label": "Sessions",
|
|
1349
|
+
className: "flex-1 overflow-y-auto px-1.5 py-1.5",
|
|
1350
|
+
children: visibleItems.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
1351
|
+
className: "flex flex-col items-center justify-center gap-2 px-4 py-10 text-center text-sm text-muted-foreground",
|
|
1352
|
+
children: [/* @__PURE__ */ jsx(MessageSquareText, {
|
|
1353
|
+
className: "h-5 w-5 opacity-50",
|
|
1354
|
+
"aria-hidden": "true"
|
|
1355
|
+
}), /* @__PURE__ */ jsx("p", { children: query.trim() ? `No sessions match "${query.trim()}".` : emptyMessage })]
|
|
1356
|
+
}) : showGroupLabels ? groups.map((group, groupIndex) => /* @__PURE__ */ jsxs("section", {
|
|
1357
|
+
"aria-label": group.label ?? void 0,
|
|
1358
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1359
|
+
className: cn$1("px-2 pb-1 text-[11px] font-medium uppercase tracking-wider text-muted-foreground", groupIndex === 0 ? "pt-1" : "pt-3"),
|
|
1360
|
+
children: group.label
|
|
1361
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
1362
|
+
className: "space-y-px",
|
|
1363
|
+
children: group.items.map((item) => renderRow(item, rowIndex++))
|
|
1364
|
+
})]
|
|
1365
|
+
}, group.key)) : /* @__PURE__ */ jsx("ul", {
|
|
1366
|
+
className: "space-y-px",
|
|
1367
|
+
children: visibleItems.map((item) => renderRow(item, rowIndex++))
|
|
1368
|
+
})
|
|
1369
|
+
}),
|
|
1370
|
+
activityMonitor && /* @__PURE__ */ jsx("div", {
|
|
1371
|
+
className: "border-t border-[var(--md3-outline-variant)] px-2 py-1.5",
|
|
1372
|
+
children: activityMonitor
|
|
1373
|
+
}),
|
|
1374
|
+
links.length > 0 && /* @__PURE__ */ jsx("nav", {
|
|
1375
|
+
"aria-label": "Workspace sections",
|
|
1376
|
+
className: "border-t border-[var(--md3-outline-variant)] px-1.5 py-1.5",
|
|
1377
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1378
|
+
className: "space-y-px",
|
|
1379
|
+
children: links.map((link) => {
|
|
1380
|
+
const Icon = iconForLink(link.icon);
|
|
1381
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
1382
|
+
type: "button",
|
|
1383
|
+
onClick: () => {
|
|
1384
|
+
if (onSelectLink) {
|
|
1385
|
+
onSelectLink(link);
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
navigateToHref(link.href);
|
|
1389
|
+
},
|
|
1390
|
+
className: cn$1("flex w-full items-center gap-2 rounded-[var(--radius-sm)] px-3 py-2 text-left text-sm text-muted-foreground transition-colors hover:bg-accent/30 hover:text-foreground", "duration-[var(--duration-fast)] ease-[var(--ease-standard)]"),
|
|
1391
|
+
children: [/* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0" }), /* @__PURE__ */ jsx("span", {
|
|
1392
|
+
className: "truncate",
|
|
1393
|
+
children: link.label
|
|
1394
|
+
})]
|
|
1395
|
+
}, link.id);
|
|
1396
|
+
})
|
|
1397
|
+
})
|
|
1398
|
+
}),
|
|
1399
|
+
resizable && /* @__PURE__ */ jsx("div", {
|
|
1400
|
+
className: cn$1("absolute top-0 right-0 bottom-0 w-1 cursor-col-resize hover:bg-[var(--accent-surface-soft)] active:bg-[var(--border-accent)] transition-colors", "duration-[var(--duration-fast)] ease-[var(--ease-standard)]"),
|
|
1401
|
+
onPointerDown: resize.onPointerDown,
|
|
1402
|
+
onPointerMove: resize.onPointerMove,
|
|
1403
|
+
onPointerUp: resize.onPointerUp,
|
|
1404
|
+
title: "Drag to resize"
|
|
1405
|
+
})
|
|
1406
|
+
]
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
//#endregion
|
|
1410
|
+
//#region src/workspace/shell-revalidation.ts
|
|
1411
|
+
/**
|
|
1412
|
+
* Build a `shouldRevalidate` function for the shell route under `appPathPrefix`.
|
|
1413
|
+
* Use this when the app mounts under a non-default prefix; otherwise prefer the
|
|
1414
|
+
* ready-made {@link shellShouldRevalidate} export.
|
|
1415
|
+
*/
|
|
1416
|
+
function createShellShouldRevalidate(options = {}) {
|
|
1417
|
+
const appPathPrefix = options.appPathPrefix ?? "/app";
|
|
1418
|
+
return function shouldRevalidate(args) {
|
|
1419
|
+
const { currentUrl, nextUrl, formMethod, defaultShouldRevalidate } = args;
|
|
1420
|
+
if (formMethod && formMethod !== "GET") return true;
|
|
1421
|
+
if (currentUrl.pathname !== nextUrl.pathname && currentUrl.pathname.startsWith(appPathPrefix) && nextUrl.pathname.startsWith(appPathPrefix)) return false;
|
|
1422
|
+
return defaultShouldRevalidate;
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Ready-made shell `shouldRevalidate` for apps mounted under `/app`. Wire it as
|
|
1427
|
+
* a one-line route export:
|
|
1428
|
+
*
|
|
1429
|
+
* ```ts
|
|
1430
|
+
* export const shouldRevalidate = shellShouldRevalidate;
|
|
1431
|
+
* ```
|
|
1432
|
+
*
|
|
1433
|
+
* For a non-`/app` prefix, use {@link createShellShouldRevalidate}.
|
|
1434
|
+
*/
|
|
1435
|
+
const shellShouldRevalidate = createShellShouldRevalidate();
|
|
1436
|
+
//#endregion
|
|
1437
|
+
//#region src/workspace/session-activity-monitor.tsx
|
|
1438
|
+
function SessionStatusDot({ session }) {
|
|
1439
|
+
if (session.status === "error") return /* @__PURE__ */ jsx(AlertCircle, { className: "h-3 w-3 text-[var(--surface-danger-text)]" });
|
|
1440
|
+
if (session.status === "running") return /* @__PURE__ */ jsx(LoaderCircle, {
|
|
1441
|
+
className: "h-3 w-3 animate-spin text-primary",
|
|
1442
|
+
"data-motion": "essential"
|
|
1443
|
+
});
|
|
1444
|
+
if (session.status === "attention-needed") return /* @__PURE__ */ jsx(Activity, { className: "h-3 w-3 text-[var(--surface-warning-text)]" });
|
|
1445
|
+
return /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-muted-foreground/40" });
|
|
1446
|
+
}
|
|
1447
|
+
function navigateToSession(session) {
|
|
1448
|
+
if (!session.href || typeof window === "undefined") return;
|
|
1449
|
+
window.location.assign(session.href);
|
|
1450
|
+
}
|
|
1451
|
+
function SessionActivityMonitor({ className, compact = false, sessionsById = {}, emptyMessage = "No active sessions", resolveProjectLabel, onSelectSession }) {
|
|
1452
|
+
const trackedSessions = useActiveSessions();
|
|
1453
|
+
const projectActivity = useProjectActivity();
|
|
1454
|
+
const totalRunning = useTotalRunningSessions();
|
|
1455
|
+
const sessionLookup = Object.keys(sessionsById).length > 0 ? sessionsById : Object.fromEntries(trackedSessions.map((session) => [session.sessionId, session]));
|
|
1456
|
+
if (projectActivity.length === 0) {
|
|
1457
|
+
if (compact) return null;
|
|
1458
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1459
|
+
className: cn$1("rounded-[var(--radius-lg)] border border-[var(--md3-outline-variant)] bg-surface-container-high px-3 py-2.5 text-xs text-muted-foreground", className),
|
|
1460
|
+
children: emptyMessage
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1464
|
+
className: cn$1("space-y-1.5", className),
|
|
1465
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1466
|
+
className: "flex items-center justify-between",
|
|
1467
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
1468
|
+
className: "flex items-center gap-1.5 text-[10px] font-medium uppercase tracking-wider text-muted-foreground",
|
|
1469
|
+
children: [/* @__PURE__ */ jsx(Activity, { className: "h-3 w-3" }), "Active"]
|
|
1470
|
+
}), totalRunning > 0 && /* @__PURE__ */ jsx("span", {
|
|
1471
|
+
className: "rounded-full border border-primary/30 bg-[var(--accent-surface-soft)] px-1.5 py-px text-[10px] font-medium text-primary",
|
|
1472
|
+
children: totalRunning
|
|
1473
|
+
})]
|
|
1474
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1475
|
+
className: "space-y-1",
|
|
1476
|
+
children: projectActivity.map((project) => {
|
|
1477
|
+
const label = resolveProjectLabel?.(project.projectId, project.projectLabel) ?? project.projectLabel ?? String(project.projectId);
|
|
1478
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1479
|
+
className: "overflow-hidden rounded-[var(--radius-lg)] border border-[var(--md3-outline-variant)] bg-surface-container",
|
|
1480
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1481
|
+
className: "flex items-center justify-between gap-2 px-2.5 py-1.5",
|
|
1482
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1483
|
+
className: "min-w-0",
|
|
1484
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1485
|
+
className: "truncate text-xs font-medium text-foreground",
|
|
1486
|
+
children: label
|
|
1487
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1488
|
+
className: "text-[10px] text-muted-foreground",
|
|
1489
|
+
children: [
|
|
1490
|
+
project.activeSessionCount,
|
|
1491
|
+
" session",
|
|
1492
|
+
project.activeSessionCount === 1 ? "" : "s"
|
|
1493
|
+
]
|
|
1494
|
+
})]
|
|
1495
|
+
}), project.runningSessionIds.length > 0 && /* @__PURE__ */ jsxs("span", {
|
|
1496
|
+
className: "shrink-0 text-[10px] text-muted-foreground",
|
|
1497
|
+
children: [project.runningSessionIds.length, " live"]
|
|
1498
|
+
})]
|
|
1499
|
+
}), !compact && project.runningSessionIds.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
1500
|
+
className: "border-t border-[var(--md3-outline-variant)] bg-surface-container-high px-1 py-0.5",
|
|
1501
|
+
children: project.runningSessionIds.map((sessionId) => {
|
|
1502
|
+
const session = sessionLookup[sessionId];
|
|
1503
|
+
if (!session) return null;
|
|
1504
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
1505
|
+
type: "button",
|
|
1506
|
+
onClick: () => {
|
|
1507
|
+
if (onSelectSession) {
|
|
1508
|
+
onSelectSession(session);
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
navigateToSession(session);
|
|
1512
|
+
},
|
|
1513
|
+
className: "flex w-full items-center gap-2 rounded-[var(--radius-sm)] px-2 py-1 text-left transition-colors hover:bg-[var(--accent-surface-soft)]",
|
|
1514
|
+
children: [
|
|
1515
|
+
/* @__PURE__ */ jsx(SessionStatusDot, { session }),
|
|
1516
|
+
/* @__PURE__ */ jsx("span", {
|
|
1517
|
+
className: "min-w-0 truncate text-xs text-muted-foreground",
|
|
1518
|
+
children: session.title ?? "Untitled"
|
|
1519
|
+
}),
|
|
1520
|
+
/* @__PURE__ */ jsx(MessageSquareText, { className: "ml-auto h-3 w-3 shrink-0 text-muted-foreground" })
|
|
1521
|
+
]
|
|
1522
|
+
}, sessionId);
|
|
1523
|
+
})
|
|
1524
|
+
})]
|
|
1525
|
+
}, String(project.projectId));
|
|
1526
|
+
})
|
|
1527
|
+
})]
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
//#endregion
|
|
1531
|
+
//#region src/workspace/sandbox-workbench.tsx
|
|
1532
|
+
function getArtifactTabIcon(kind) {
|
|
1533
|
+
switch (kind) {
|
|
1534
|
+
case "file": return FileCode2;
|
|
1535
|
+
case "markdown": return FileText;
|
|
1536
|
+
case "openui": return LayoutPanelTop;
|
|
1537
|
+
case "custom": return Boxes;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
function artifactTabLabel(artifact) {
|
|
1541
|
+
if (typeof artifact.title === "string") return artifact.title;
|
|
1542
|
+
if (artifact.kind === "file") return artifact.filename;
|
|
1543
|
+
return "Artifact";
|
|
1544
|
+
}
|
|
1545
|
+
function sortPinnedFirst(artifacts) {
|
|
1546
|
+
if (!artifacts.some((artifact) => artifact.pinned)) return artifacts;
|
|
1547
|
+
return [...artifacts.filter((artifact) => artifact.pinned), ...artifacts.filter((artifact) => !artifact.pinned)];
|
|
1548
|
+
}
|
|
1549
|
+
function ArtifactTabs({ artifacts, activeArtifactId, onSelect, onClose, tabIdPrefix, panelId }) {
|
|
1550
|
+
const tabRefs = useRef(/* @__PURE__ */ new Map());
|
|
1551
|
+
if (artifacts.length === 0) return null;
|
|
1552
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1553
|
+
role: "tablist",
|
|
1554
|
+
"aria-label": "Artifacts",
|
|
1555
|
+
className: "flex items-center overflow-x-auto border-b border-[var(--md3-outline-variant)] bg-surface-container-high",
|
|
1556
|
+
children: artifacts.map((artifact) => {
|
|
1557
|
+
const Icon = artifact.icon ?? getArtifactTabIcon(artifact.kind);
|
|
1558
|
+
const isActive = artifact.id === activeArtifactId;
|
|
1559
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1560
|
+
className: cn$1("group flex shrink-0 items-center border-r border-[var(--md3-outline-variant)] bg-surface-container relative", isActive ? "text-foreground after:absolute after:top-0 after:left-0 after:right-0 after:h-[2px] after:bg-primary" : "text-muted-foreground hover:bg-surface-container-high cursor-pointer"),
|
|
1561
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
1562
|
+
type: "button",
|
|
1563
|
+
ref: (element) => {
|
|
1564
|
+
if (element) tabRefs.current.set(artifact.id, element);
|
|
1565
|
+
else tabRefs.current.delete(artifact.id);
|
|
1566
|
+
},
|
|
1567
|
+
id: `${tabIdPrefix}-${artifact.id}`,
|
|
1568
|
+
role: "tab",
|
|
1569
|
+
"aria-controls": panelId,
|
|
1570
|
+
"aria-selected": isActive,
|
|
1571
|
+
tabIndex: isActive ? 0 : -1,
|
|
1572
|
+
onClick: () => onSelect(artifact.id),
|
|
1573
|
+
onKeyDown: (event) => {
|
|
1574
|
+
const index = artifacts.findIndex((entry) => entry.id === artifact.id);
|
|
1575
|
+
let nextIndex;
|
|
1576
|
+
switch (event.key) {
|
|
1577
|
+
case "ArrowRight":
|
|
1578
|
+
nextIndex = (index + 1) % artifacts.length;
|
|
1579
|
+
break;
|
|
1580
|
+
case "ArrowLeft":
|
|
1581
|
+
nextIndex = (index - 1 + artifacts.length) % artifacts.length;
|
|
1582
|
+
break;
|
|
1583
|
+
case "Home":
|
|
1584
|
+
nextIndex = 0;
|
|
1585
|
+
break;
|
|
1586
|
+
case "End":
|
|
1587
|
+
nextIndex = artifacts.length - 1;
|
|
1588
|
+
break;
|
|
1589
|
+
default: return;
|
|
1590
|
+
}
|
|
1591
|
+
event.preventDefault();
|
|
1592
|
+
const next = artifacts[nextIndex];
|
|
1593
|
+
onSelect(next.id);
|
|
1594
|
+
tabRefs.current.get(next.id)?.focus();
|
|
1595
|
+
},
|
|
1596
|
+
className: `flex min-w-0 items-center gap-2 px-3 py-2 text-[12px] uppercase tracking-wider font-medium transition-colors hover:text-foreground ${focusRingInset}`,
|
|
1597
|
+
children: [/* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0" }), /* @__PURE__ */ jsx("span", {
|
|
1598
|
+
className: "max-w-[14rem] truncate",
|
|
1599
|
+
children: artifactTabLabel(artifact)
|
|
1600
|
+
})]
|
|
1601
|
+
}), onClose && !artifact.pinned && /* @__PURE__ */ jsx("button", {
|
|
1602
|
+
type: "button",
|
|
1603
|
+
"aria-label": `Close ${artifactTabLabel(artifact)}`,
|
|
1604
|
+
tabIndex: isActive ? 0 : -1,
|
|
1605
|
+
onClick: () => onClose(artifact.id),
|
|
1606
|
+
className: `mr-1 rounded-[2px] p-1 opacity-0 transition-opacity hover:bg-accent hover:text-foreground focus-visible:opacity-100 group-hover:opacity-100 ${focusRing}`,
|
|
1607
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
|
|
1608
|
+
})]
|
|
1609
|
+
}, artifact.id);
|
|
1610
|
+
})
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
function renderArtifact(artifact) {
|
|
1614
|
+
switch (artifact.kind) {
|
|
1615
|
+
case "file": return /* @__PURE__ */ jsx(FileArtifactPane, {
|
|
1616
|
+
path: artifact.path,
|
|
1617
|
+
filename: artifact.filename,
|
|
1618
|
+
content: artifact.content,
|
|
1619
|
+
blobUrl: artifact.blobUrl,
|
|
1620
|
+
mimeType: artifact.mimeType,
|
|
1621
|
+
onDownload: artifact.onDownload,
|
|
1622
|
+
tabs: artifact.tabs,
|
|
1623
|
+
activeTabId: artifact.activeTabId,
|
|
1624
|
+
onTabSelect: artifact.onTabSelect,
|
|
1625
|
+
onTabClose: artifact.onTabClose,
|
|
1626
|
+
eyebrow: artifact.eyebrow,
|
|
1627
|
+
meta: artifact.meta,
|
|
1628
|
+
toolbar: artifact.toolbar,
|
|
1629
|
+
footer: artifact.footer
|
|
1630
|
+
});
|
|
1631
|
+
case "markdown": return /* @__PURE__ */ jsx(ArtifactPane, {
|
|
1632
|
+
eyebrow: artifact.eyebrow ?? "Document",
|
|
1633
|
+
title: artifact.title,
|
|
1634
|
+
subtitle: artifact.subtitle,
|
|
1635
|
+
meta: artifact.meta,
|
|
1636
|
+
headerActions: artifact.headerActions,
|
|
1637
|
+
toolbar: artifact.toolbar,
|
|
1638
|
+
footer: artifact.footer,
|
|
1639
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1640
|
+
className: "p-5",
|
|
1641
|
+
children: /* @__PURE__ */ jsx(Markdown, {
|
|
1642
|
+
className: "prose-sm max-w-none",
|
|
1643
|
+
children: artifact.content
|
|
1644
|
+
})
|
|
1645
|
+
})
|
|
1646
|
+
});
|
|
1647
|
+
case "openui": return /* @__PURE__ */ jsx(ArtifactPane, {
|
|
1648
|
+
eyebrow: artifact.eyebrow ?? "Structured Artifact",
|
|
1649
|
+
title: artifact.title,
|
|
1650
|
+
subtitle: artifact.subtitle,
|
|
1651
|
+
meta: artifact.meta,
|
|
1652
|
+
headerActions: artifact.headerActions,
|
|
1653
|
+
toolbar: artifact.toolbar,
|
|
1654
|
+
footer: artifact.footer,
|
|
1655
|
+
children: /* @__PURE__ */ jsx(OpenUIArtifactRenderer, {
|
|
1656
|
+
schema: artifact.schema,
|
|
1657
|
+
onAction: artifact.onAction
|
|
1658
|
+
})
|
|
1659
|
+
});
|
|
1660
|
+
case "custom": return /* @__PURE__ */ jsx(ArtifactPane, {
|
|
1661
|
+
eyebrow: artifact.eyebrow ?? "Artifact",
|
|
1662
|
+
title: artifact.title,
|
|
1663
|
+
subtitle: artifact.subtitle,
|
|
1664
|
+
meta: artifact.meta,
|
|
1665
|
+
headerActions: artifact.headerActions,
|
|
1666
|
+
toolbar: artifact.toolbar,
|
|
1667
|
+
footer: artifact.footer,
|
|
1668
|
+
children: artifact.content
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
function regionHeader(sections, fallback) {
|
|
1673
|
+
if (sections.length !== 1) return fallback;
|
|
1674
|
+
switch (sections[0]?.key) {
|
|
1675
|
+
case "rail": return;
|
|
1676
|
+
case "directory": return /* @__PURE__ */ jsxs("div", {
|
|
1677
|
+
className: "flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.12em] text-muted-foreground",
|
|
1678
|
+
children: [/* @__PURE__ */ jsx(FolderTree, { className: "h-3.5 w-3.5" }), "Directory"]
|
|
1679
|
+
});
|
|
1680
|
+
case "artifacts": return /* @__PURE__ */ jsxs("div", {
|
|
1681
|
+
className: "flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.12em] text-muted-foreground",
|
|
1682
|
+
children: [/* @__PURE__ */ jsx(LayoutPanelTop, { className: "h-3.5 w-3.5" }), "Artifacts"]
|
|
1683
|
+
});
|
|
1684
|
+
case "runtime": return /* @__PURE__ */ jsxs("div", {
|
|
1685
|
+
className: "flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.12em] text-muted-foreground",
|
|
1686
|
+
children: [/* @__PURE__ */ jsx(Bot, { className: "h-3.5 w-3.5" }), "Runtime"]
|
|
1687
|
+
});
|
|
1688
|
+
default: return fallback;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
function renderRegion(sections, region) {
|
|
1692
|
+
if (sections.length === 0) return void 0;
|
|
1693
|
+
if (sections.length === 1) return sections[0]?.content;
|
|
1694
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1695
|
+
className: cn$1("flex min-h-0 h-full gap-3 p-3", region === "bottom" ? "flex-col" : "flex-col"),
|
|
1696
|
+
children: sections.map((section) => /* @__PURE__ */ jsx("div", {
|
|
1697
|
+
className: "min-h-0 flex-1 overflow-hidden",
|
|
1698
|
+
children: section.content
|
|
1699
|
+
}, section.key))
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* SandboxWorkbench — high-level composition that turns sandbox-ui primitives
|
|
1704
|
+
* into a complete session surface: directory, agent timeline, artifacts, and
|
|
1705
|
+
* runtime in one reusable shell.
|
|
1706
|
+
*/
|
|
1707
|
+
function SandboxWorkbench({ title = "Sandbox session", subtitle, status, centerHeader: centerHeaderProp, composer, rail, directory, session, artifacts: artifactsProp = [], activeArtifactId, onArtifactChange, onArtifactClose, runtime, layout, emptyArtifactState, className }) {
|
|
1708
|
+
const artifacts = useMemo(() => sortPinnedFirst(artifactsProp), [artifactsProp]);
|
|
1709
|
+
const artifactTabsId = useId();
|
|
1710
|
+
const artifactPanelId = `${artifactTabsId}-panel`;
|
|
1711
|
+
const [uncontrolledArtifactId, setUncontrolledArtifactId] = useState(activeArtifactId ?? artifacts[0]?.id);
|
|
1712
|
+
useEffect(() => {
|
|
1713
|
+
if (activeArtifactId !== void 0) return;
|
|
1714
|
+
setUncontrolledArtifactId((current) => {
|
|
1715
|
+
if (artifacts.length === 0) return void 0;
|
|
1716
|
+
if (current && artifacts.some((artifact) => artifact.id === current)) return current;
|
|
1717
|
+
return artifacts[0]?.id;
|
|
1718
|
+
});
|
|
1719
|
+
}, [activeArtifactId, artifacts]);
|
|
1720
|
+
const resolvedArtifactId = activeArtifactId ?? uncontrolledArtifactId;
|
|
1721
|
+
const activeArtifact = useMemo(() => artifacts.find((artifact) => artifact.id === resolvedArtifactId), [artifacts, resolvedArtifactId]);
|
|
1722
|
+
const handleArtifactChange = (artifactId) => {
|
|
1723
|
+
if (activeArtifactId === void 0) setUncontrolledArtifactId(artifactId);
|
|
1724
|
+
onArtifactChange?.(artifactId);
|
|
1725
|
+
};
|
|
1726
|
+
const brandedHeader = /* @__PURE__ */ jsxs("div", {
|
|
1727
|
+
className: "flex min-w-0 items-start justify-between gap-4 rounded-[var(--radius-xl)] border border-[var(--md3-outline-variant)] bg-surface-container px-4 py-3.5",
|
|
1728
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1729
|
+
className: "min-w-0",
|
|
1730
|
+
children: [
|
|
1731
|
+
/* @__PURE__ */ jsx("div", {
|
|
1732
|
+
className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-primary",
|
|
1733
|
+
children: "Tangle Sandbox"
|
|
1734
|
+
}),
|
|
1735
|
+
/* @__PURE__ */ jsx("div", {
|
|
1736
|
+
className: "truncate text-[17px] font-semibold tracking-[0.01em] text-foreground",
|
|
1737
|
+
children: title
|
|
1738
|
+
}),
|
|
1739
|
+
subtitle && /* @__PURE__ */ jsx("div", {
|
|
1740
|
+
className: "truncate text-sm text-muted-foreground",
|
|
1741
|
+
children: subtitle
|
|
1742
|
+
})
|
|
1743
|
+
]
|
|
1744
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1745
|
+
className: "flex shrink-0 flex-wrap items-center justify-end gap-2",
|
|
1746
|
+
children: [status, artifacts.length > 0 && /* @__PURE__ */ jsxs(Badge, {
|
|
1747
|
+
variant: "outline",
|
|
1748
|
+
children: [artifacts.length, " artifacts"]
|
|
1749
|
+
})]
|
|
1750
|
+
})]
|
|
1751
|
+
});
|
|
1752
|
+
const quiet = centerHeaderProp === null;
|
|
1753
|
+
const centerHeader = centerHeaderProp === void 0 ? brandedHeader : centerHeaderProp ?? void 0;
|
|
1754
|
+
const { composerControls, ...chatSession } = session;
|
|
1755
|
+
const transcript = /* @__PURE__ */ jsxs("div", {
|
|
1756
|
+
className: cn$1("flex h-full min-h-0 flex-col", quiet && "bg-surface"),
|
|
1757
|
+
children: [
|
|
1758
|
+
/* @__PURE__ */ jsx(ChatContainer, {
|
|
1759
|
+
...chatSession,
|
|
1760
|
+
className: "min-h-0 flex-1",
|
|
1761
|
+
presentation: session.presentation ?? "timeline"
|
|
1762
|
+
}),
|
|
1763
|
+
composerControls && /* @__PURE__ */ jsx("div", {
|
|
1764
|
+
className: "shrink-0 border-t border-[var(--md3-outline-variant)] bg-surface-container-high px-3 py-2",
|
|
1765
|
+
children: composerControls
|
|
1766
|
+
}),
|
|
1767
|
+
composer && /* @__PURE__ */ jsx("div", {
|
|
1768
|
+
className: "shrink-0",
|
|
1769
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1770
|
+
className: "mx-auto w-full max-w-3xl px-3 pb-3",
|
|
1771
|
+
children: composer
|
|
1772
|
+
})
|
|
1773
|
+
})
|
|
1774
|
+
]
|
|
1775
|
+
});
|
|
1776
|
+
const center = quiet ? transcript : /* @__PURE__ */ jsx(ArtifactPane, {
|
|
1777
|
+
eyebrow: session.eyebrow ?? "Agent Session",
|
|
1778
|
+
title: session.title ?? "Execution timeline",
|
|
1779
|
+
subtitle: session.subtitle,
|
|
1780
|
+
meta: session.meta,
|
|
1781
|
+
headerActions: session.headerActions,
|
|
1782
|
+
className: "h-full",
|
|
1783
|
+
contentClassName: "bg-surface-container",
|
|
1784
|
+
children: transcript
|
|
1785
|
+
});
|
|
1786
|
+
const artifactPanel = artifacts.length > 0 || emptyArtifactState ? /* @__PURE__ */ jsxs("section", {
|
|
1787
|
+
className: "flex h-full min-h-0 flex-col bg-surface-container",
|
|
1788
|
+
children: [/* @__PURE__ */ jsx(ArtifactTabs, {
|
|
1789
|
+
artifacts,
|
|
1790
|
+
activeArtifactId: resolvedArtifactId,
|
|
1791
|
+
onSelect: handleArtifactChange,
|
|
1792
|
+
onClose: onArtifactClose,
|
|
1793
|
+
tabIdPrefix: artifactTabsId,
|
|
1794
|
+
panelId: artifactPanelId
|
|
1795
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1796
|
+
id: artifactPanelId,
|
|
1797
|
+
role: "tabpanel",
|
|
1798
|
+
"aria-labelledby": activeArtifact ? `${artifactTabsId}-${activeArtifact.id}` : void 0,
|
|
1799
|
+
"aria-label": activeArtifact ? void 0 : "Artifact content",
|
|
1800
|
+
tabIndex: 0,
|
|
1801
|
+
className: `min-h-0 flex-1 overflow-auto bg-surface-container ${focusRingInset}`,
|
|
1802
|
+
children: activeArtifact ? renderArtifact(activeArtifact) : /* @__PURE__ */ jsx("div", {
|
|
1803
|
+
className: "flex h-full items-center justify-center p-6",
|
|
1804
|
+
children: emptyArtifactState ?? /* @__PURE__ */ jsx(EmptyState, {
|
|
1805
|
+
icon: /* @__PURE__ */ jsx(Boxes, { className: "h-8 w-8" }),
|
|
1806
|
+
title: "No artifact selected",
|
|
1807
|
+
description: "Select a generated artifact, file preview, or OpenUI panel to inspect it here."
|
|
1808
|
+
})
|
|
1809
|
+
})
|
|
1810
|
+
})]
|
|
1811
|
+
}) : null;
|
|
1812
|
+
const directoryPlacement = layout?.directoryPlacement ?? (directory ? "left" : "hidden");
|
|
1813
|
+
const artifactPlacement = layout?.artifactPlacement ?? (artifactPanel ? "right" : "hidden");
|
|
1814
|
+
const runtimePlacement = layout?.runtimePlacement ?? (runtime ? "bottom" : "hidden");
|
|
1815
|
+
const regionSections = {
|
|
1816
|
+
left: [],
|
|
1817
|
+
right: [],
|
|
1818
|
+
bottom: []
|
|
1819
|
+
};
|
|
1820
|
+
if (rail) regionSections.left.push({
|
|
1821
|
+
key: "rail",
|
|
1822
|
+
content: rail
|
|
1823
|
+
});
|
|
1824
|
+
if (directory && directoryPlacement !== "hidden") regionSections[directoryPlacement].push({
|
|
1825
|
+
key: "directory",
|
|
1826
|
+
content: /* @__PURE__ */ jsx(DirectoryPane, {
|
|
1827
|
+
...directory,
|
|
1828
|
+
className: "h-full"
|
|
1829
|
+
})
|
|
1830
|
+
});
|
|
1831
|
+
if (artifactPanel && artifactPlacement !== "hidden") regionSections[artifactPlacement].push({
|
|
1832
|
+
key: "artifacts",
|
|
1833
|
+
content: artifactPanel
|
|
1834
|
+
});
|
|
1835
|
+
if (runtime && runtimePlacement !== "hidden") regionSections[runtimePlacement].push({
|
|
1836
|
+
key: "runtime",
|
|
1837
|
+
content: /* @__PURE__ */ jsx(RuntimePane, {
|
|
1838
|
+
...runtime,
|
|
1839
|
+
className: "h-full"
|
|
1840
|
+
})
|
|
1841
|
+
});
|
|
1842
|
+
const left = renderRegion(regionSections.left, "left");
|
|
1843
|
+
const right = renderRegion(regionSections.right, "right");
|
|
1844
|
+
const bottom = renderRegion(regionSections.bottom, "bottom");
|
|
1845
|
+
const railOnly = regionSections.left.length === 1 && regionSections.left[0]?.key === "rail";
|
|
1846
|
+
const genericPanelsHeader = /* @__PURE__ */ jsx("span", {
|
|
1847
|
+
className: "text-xs font-semibold uppercase tracking-[0.12em] text-muted-foreground",
|
|
1848
|
+
children: "Workspace Panels"
|
|
1849
|
+
});
|
|
1850
|
+
return /* @__PURE__ */ jsx(WorkspaceLayout, {
|
|
1851
|
+
left,
|
|
1852
|
+
leftHeader: left ? regionHeader(regionSections.left, genericPanelsHeader) : void 0,
|
|
1853
|
+
center,
|
|
1854
|
+
centerHeader,
|
|
1855
|
+
right,
|
|
1856
|
+
rightHeader: right ? regionHeader(regionSections.right, genericPanelsHeader) : void 0,
|
|
1857
|
+
bottom,
|
|
1858
|
+
bottomHeader: bottom ? regionHeader(regionSections.bottom, genericPanelsHeader) : void 0,
|
|
1859
|
+
theme: layout?.theme,
|
|
1860
|
+
density: layout?.density ?? "comfortable",
|
|
1861
|
+
persistenceKey: layout?.persistenceKey,
|
|
1862
|
+
defaultLeftOpen: layout?.defaultLeftOpen ?? Boolean(left),
|
|
1863
|
+
defaultRightOpen: layout?.defaultRightOpen ?? Boolean(right),
|
|
1864
|
+
defaultBottomOpen: layout?.defaultBottomOpen ?? Boolean(bottom),
|
|
1865
|
+
defaultLeftWidth: layout?.defaultLeftWidth,
|
|
1866
|
+
defaultRightWidth: layout?.defaultRightWidth,
|
|
1867
|
+
minLeftWidth: layout?.minLeftWidth,
|
|
1868
|
+
maxLeftWidth: layout?.maxLeftWidth,
|
|
1869
|
+
minRightWidth: layout?.minRightWidth,
|
|
1870
|
+
maxRightWidth: layout?.maxRightWidth,
|
|
1871
|
+
resizable: layout?.resizable,
|
|
1872
|
+
leftOpen: layout?.leftOpen,
|
|
1873
|
+
onLeftOpenChange: layout?.onLeftOpenChange,
|
|
1874
|
+
rightOpen: layout?.rightOpen,
|
|
1875
|
+
onRightOpenChange: layout?.onRightOpenChange,
|
|
1876
|
+
keyboardShortcuts: layout?.keyboardShortcuts,
|
|
1877
|
+
leftCollapsedControl: layout?.leftCollapsedControl,
|
|
1878
|
+
leftContentClassName: railOnly ? "py-0" : void 0,
|
|
1879
|
+
centerHeaderVisibility: quiet ? "auto" : "always",
|
|
1880
|
+
className: cn$1("p-3 lg:p-4", className)
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
function AgentWorkbench(props) {
|
|
1884
|
+
return /* @__PURE__ */ jsx(SandboxWorkbench, {
|
|
1885
|
+
...props,
|
|
1886
|
+
session: {
|
|
1887
|
+
...props.session,
|
|
1888
|
+
eyebrow: props.session.eyebrow ?? "Agent Session",
|
|
1889
|
+
title: props.session.title ?? /* @__PURE__ */ jsxs("span", {
|
|
1890
|
+
className: "inline-flex items-center gap-2",
|
|
1891
|
+
children: [/* @__PURE__ */ jsx(Bot, { className: "h-4 w-4 text-primary" }), "Execution timeline"]
|
|
1892
|
+
})
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
//#endregion
|
|
1897
|
+
//#region src/workspace/audit-results.tsx
|
|
1898
|
+
/**
|
|
1899
|
+
* AuditResults — structured pass/fail display for form audits.
|
|
1900
|
+
*/
|
|
1901
|
+
function AuditResults({ forms, crossFormChecks = [], overallScore, className }) {
|
|
1902
|
+
const totalPassed = forms.reduce((s, f) => s + f.passed, 0);
|
|
1903
|
+
const totalChecks = forms.reduce((s, f) => s + f.passed + f.failed, 0);
|
|
1904
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1905
|
+
className: cn$1("space-y-3 p-3", className),
|
|
1906
|
+
children: [
|
|
1907
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1908
|
+
className: "flex items-center gap-3 px-3 py-2 rounded-[var(--radius-md)] bg-surface-container-high border border-[var(--md3-outline-variant)]",
|
|
1909
|
+
children: [/* @__PURE__ */ jsx(Shield, { className: cn$1("h-5 w-5", totalChecks === totalPassed ? "text-[var(--code-success)]" : "text-[var(--code-number)]") }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
|
|
1910
|
+
className: "text-sm font-semibold text-foreground",
|
|
1911
|
+
children: [
|
|
1912
|
+
totalPassed,
|
|
1913
|
+
"/",
|
|
1914
|
+
totalChecks,
|
|
1915
|
+
" checks passed"
|
|
1916
|
+
]
|
|
1917
|
+
}), overallScore !== void 0 && /* @__PURE__ */ jsxs("div", {
|
|
1918
|
+
className: "text-xs text-muted-foreground",
|
|
1919
|
+
children: [
|
|
1920
|
+
"Score: ",
|
|
1921
|
+
overallScore,
|
|
1922
|
+
"/100"
|
|
1923
|
+
]
|
|
1924
|
+
})] })]
|
|
1925
|
+
}),
|
|
1926
|
+
forms.map((form) => /* @__PURE__ */ jsx(FormAuditCard, { form }, form.formId)),
|
|
1927
|
+
crossFormChecks.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
|
|
1928
|
+
className: "text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-1",
|
|
1929
|
+
children: "Cross-Form Checks"
|
|
1930
|
+
}), crossFormChecks.map((check, i) => /* @__PURE__ */ jsx(CheckRow, { check }, i))] })
|
|
1931
|
+
]
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
function FormAuditCard({ form }) {
|
|
1935
|
+
const [expanded, setExpanded] = useState(form.failed > 0);
|
|
1936
|
+
const allPassed = form.failed === 0 && form.found;
|
|
1937
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1938
|
+
className: "rounded-[var(--radius-md)] border border-[var(--md3-outline-variant)] bg-surface-container-high overflow-hidden",
|
|
1939
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
1940
|
+
onClick: () => setExpanded(!expanded),
|
|
1941
|
+
className: "flex items-center gap-2 w-full px-3 py-2 text-left hover:bg-accent transition-colors",
|
|
1942
|
+
children: [
|
|
1943
|
+
allPassed ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 text-[var(--code-success)] shrink-0" }) : !form.found ? /* @__PURE__ */ jsx(XCircle, { className: "h-4 w-4 text-[var(--code-error)] shrink-0" }) : /* @__PURE__ */ jsx(XCircle, { className: "h-4 w-4 text-[var(--code-number)] shrink-0" }),
|
|
1944
|
+
/* @__PURE__ */ jsx("span", {
|
|
1945
|
+
className: "text-sm font-medium text-foreground flex-1",
|
|
1946
|
+
children: form.formName || form.formId
|
|
1947
|
+
}),
|
|
1948
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1949
|
+
className: cn$1("text-xs tabular-nums", allPassed ? "text-[var(--code-success)]" : "text-muted-foreground"),
|
|
1950
|
+
children: [
|
|
1951
|
+
form.passed,
|
|
1952
|
+
"/",
|
|
1953
|
+
form.passed + form.failed
|
|
1954
|
+
]
|
|
1955
|
+
}),
|
|
1956
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: cn$1("h-3 w-3 text-muted-foreground transition-transform", expanded && "rotate-90") })
|
|
1957
|
+
]
|
|
1958
|
+
}), expanded && /* @__PURE__ */ jsx("div", {
|
|
1959
|
+
className: "border-t border-[var(--md3-outline-variant)] px-3 py-1.5 space-y-0.5",
|
|
1960
|
+
children: form.checks.map((check, i) => /* @__PURE__ */ jsx(CheckRow, { check }, i))
|
|
1961
|
+
})]
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
function CheckRow({ check }) {
|
|
1965
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1966
|
+
className: "flex items-center gap-2 py-1 text-xs",
|
|
1967
|
+
children: [
|
|
1968
|
+
check.passed ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-3 w-3 text-[var(--code-success)] shrink-0" }) : /* @__PURE__ */ jsx(XCircle, { className: "h-3 w-3 text-[var(--code-error)] shrink-0" }),
|
|
1969
|
+
/* @__PURE__ */ jsx("span", {
|
|
1970
|
+
className: "text-foreground flex-1 truncate",
|
|
1971
|
+
children: check.label
|
|
1972
|
+
}),
|
|
1973
|
+
/* @__PURE__ */ jsx("span", {
|
|
1974
|
+
className: "text-muted-foreground tabular-nums shrink-0",
|
|
1975
|
+
children: check.passed ? String(check.actual ?? check.expected) : `${check.actual ?? "missing"} ≠ ${check.expected}`
|
|
1976
|
+
})
|
|
1977
|
+
]
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
//#endregion
|
|
1981
|
+
//#region src/workspace/task-board.tsx
|
|
1982
|
+
const INTERACTIVE_SELECTOR = "button, a, input, textarea, select, [role=\"button\"], [role=\"link\"]";
|
|
1983
|
+
function TaskBoard({ items, columns, className, onMoveItem, onClickItem, renderItemMeta, renderColumnAction, renderBadge, columnEmptyState, header, renderColumnBody, renderItemWrapper }) {
|
|
1984
|
+
const grouped = useMemo(() => {
|
|
1985
|
+
const map = /* @__PURE__ */ new Map();
|
|
1986
|
+
for (const col of columns) map.set(col.id, []);
|
|
1987
|
+
for (const item of items) {
|
|
1988
|
+
const list = map.get(item.status);
|
|
1989
|
+
if (list) list.push(item);
|
|
1990
|
+
else map.get(columns[columns.length - 1]?.id)?.push(item);
|
|
1991
|
+
}
|
|
1992
|
+
return map;
|
|
1993
|
+
}, [items, columns]);
|
|
1994
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1995
|
+
className: cn$1("flex flex-1 flex-col overflow-hidden", className),
|
|
1996
|
+
children: [header, /* @__PURE__ */ jsx("div", {
|
|
1997
|
+
className: "flex flex-1 gap-3 overflow-x-auto p-4",
|
|
1998
|
+
children: columns.map((col) => {
|
|
1999
|
+
const colItems = grouped.get(col.id) ?? [];
|
|
2000
|
+
const itemCards = /* @__PURE__ */ jsxs(Fragment, { children: [colItems.length === 0 && columnEmptyState, colItems.map((item, index) => {
|
|
2001
|
+
const card = /* @__PURE__ */ jsxs("div", {
|
|
2002
|
+
role: onClickItem ? "button" : void 0,
|
|
2003
|
+
tabIndex: onClickItem ? 0 : void 0,
|
|
2004
|
+
onClick: (event) => {
|
|
2005
|
+
if (!onClickItem) return;
|
|
2006
|
+
const interactive = event.target.closest(INTERACTIVE_SELECTOR);
|
|
2007
|
+
if (interactive && interactive !== event.currentTarget) return;
|
|
2008
|
+
onClickItem(item);
|
|
2009
|
+
},
|
|
2010
|
+
onKeyDown: (event) => {
|
|
2011
|
+
if (!onClickItem || event.currentTarget !== event.target) return;
|
|
2012
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
2013
|
+
if (event.repeat) return;
|
|
2014
|
+
event.preventDefault();
|
|
2015
|
+
onClickItem(item);
|
|
2016
|
+
},
|
|
2017
|
+
className: "group w-full rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container-high p-3 text-left transition-colors hover:border-accent/50",
|
|
2018
|
+
children: [
|
|
2019
|
+
/* @__PURE__ */ jsx("p", {
|
|
2020
|
+
className: "text-sm font-medium text-foreground",
|
|
2021
|
+
children: item.title
|
|
2022
|
+
}),
|
|
2023
|
+
item.description && /* @__PURE__ */ jsx("p", {
|
|
2024
|
+
className: "mt-1 text-xs text-muted-foreground line-clamp-2",
|
|
2025
|
+
children: item.description
|
|
2026
|
+
}),
|
|
2027
|
+
(item.priority || item.tags?.length) && /* @__PURE__ */ jsxs("div", {
|
|
2028
|
+
className: "mt-2 flex flex-wrap gap-1.5",
|
|
2029
|
+
children: [item.priority && (renderBadge ? renderBadge(item.priority, "priority") : /* @__PURE__ */ jsx("span", {
|
|
2030
|
+
className: "rounded-full border border-[var(--md3-outline-variant)] px-2 py-0.5 text-[10px] font-medium text-muted-foreground",
|
|
2031
|
+
children: item.priority
|
|
2032
|
+
})), item.tags?.map((tag) => renderBadge ? /* @__PURE__ */ jsx("span", { children: renderBadge(tag, "tag") }, tag) : /* @__PURE__ */ jsx("span", {
|
|
2033
|
+
className: "rounded-full border border-[var(--md3-outline-variant)] px-2 py-0.5 text-[10px] text-muted-foreground",
|
|
2034
|
+
children: tag
|
|
2035
|
+
}, tag))]
|
|
2036
|
+
}),
|
|
2037
|
+
renderItemMeta?.(item)
|
|
2038
|
+
]
|
|
2039
|
+
}, item.id);
|
|
2040
|
+
return renderItemWrapper ? /* @__PURE__ */ jsx("span", { children: renderItemWrapper(item, index, card) }, item.id) : card;
|
|
2041
|
+
})] });
|
|
2042
|
+
const columnBody = /* @__PURE__ */ jsx("div", {
|
|
2043
|
+
className: "flex flex-1 flex-col gap-2 overflow-y-auto px-2 pb-2 min-h-[80px]",
|
|
2044
|
+
children: renderColumnBody ? renderColumnBody(col.id, itemCards, colItems) : itemCards
|
|
2045
|
+
});
|
|
2046
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2047
|
+
className: cn$1("flex w-72 shrink-0 flex-col rounded-xl border border-[var(--md3-outline-variant)] bg-surface-container border-t-2", col.accent ?? "border-t-muted-foreground/30"),
|
|
2048
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2049
|
+
className: "flex items-center justify-between px-4 py-3",
|
|
2050
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2051
|
+
className: "flex items-center gap-2",
|
|
2052
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
2053
|
+
className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground",
|
|
2054
|
+
children: col.label
|
|
2055
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2056
|
+
className: "inline-flex h-5 min-w-[20px] items-center justify-center rounded-full border border-[var(--md3-outline-variant)] px-1.5 text-[10px] font-medium text-muted-foreground",
|
|
2057
|
+
children: colItems.length
|
|
2058
|
+
})]
|
|
2059
|
+
}), renderColumnAction?.(col)]
|
|
2060
|
+
}), columnBody]
|
|
2061
|
+
}, col.id);
|
|
2062
|
+
})
|
|
2063
|
+
})]
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
//#endregion
|
|
2067
|
+
//#region src/workspace/calendar-view.tsx
|
|
2068
|
+
function toDateKey(d) {
|
|
2069
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
2070
|
+
}
|
|
2071
|
+
function getMonthDays(year, month) {
|
|
2072
|
+
const startDay = new Date(year, month, 1).getDay();
|
|
2073
|
+
const days = [];
|
|
2074
|
+
for (let i = startDay - 1; i >= 0; i--) {
|
|
2075
|
+
const d = new Date(year, month, -i);
|
|
2076
|
+
days.push({
|
|
2077
|
+
date: d,
|
|
2078
|
+
key: toDateKey(d),
|
|
2079
|
+
inMonth: false
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
2083
|
+
for (let i = 1; i <= daysInMonth; i++) {
|
|
2084
|
+
const d = new Date(year, month, i);
|
|
2085
|
+
days.push({
|
|
2086
|
+
date: d,
|
|
2087
|
+
key: toDateKey(d),
|
|
2088
|
+
inMonth: true
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
2091
|
+
const remaining = 7 - days.length % 7;
|
|
2092
|
+
if (remaining < 7) for (let i = 1; i <= remaining; i++) {
|
|
2093
|
+
const d = new Date(year, month + 1, i);
|
|
2094
|
+
days.push({
|
|
2095
|
+
date: d,
|
|
2096
|
+
key: toDateKey(d),
|
|
2097
|
+
inMonth: false
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
2100
|
+
return days;
|
|
2101
|
+
}
|
|
2102
|
+
/**
|
|
2103
|
+
* CalendarView — month grid calendar with event chips and optional day detail panel.
|
|
2104
|
+
*
|
|
2105
|
+
* Stateless by default (controlled month/selectedDay). Falls back to internal
|
|
2106
|
+
* state when uncontrolled. All rendering is customizable via render props.
|
|
2107
|
+
*/
|
|
2108
|
+
function CalendarView({ events, className, month: controlledMonth, year: controlledYear, onMonthChange, selectedDay: controlledSelectedDay, onSelectDay, onDoubleClickDay, renderEventChip, renderDayDetail, headerLeft, headerRight, showDayPanel = true }) {
|
|
2109
|
+
const today = /* @__PURE__ */ new Date();
|
|
2110
|
+
const todayKey = toDateKey(today);
|
|
2111
|
+
const [internalMonth, setInternalMonth] = useState({
|
|
2112
|
+
year: today.getFullYear(),
|
|
2113
|
+
month: today.getMonth()
|
|
2114
|
+
});
|
|
2115
|
+
const [internalSelectedDay, setInternalSelectedDay] = useState(todayKey);
|
|
2116
|
+
const viewYear = controlledYear ?? internalMonth.year;
|
|
2117
|
+
const viewMonth = controlledMonth ?? internalMonth.month;
|
|
2118
|
+
const selectedDay = controlledSelectedDay ?? internalSelectedDay;
|
|
2119
|
+
function setMonth(y, m) {
|
|
2120
|
+
if (onMonthChange) onMonthChange(y, m);
|
|
2121
|
+
else setInternalMonth({
|
|
2122
|
+
year: y,
|
|
2123
|
+
month: m
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
2126
|
+
function selectDay(key) {
|
|
2127
|
+
if (onSelectDay) onSelectDay(key);
|
|
2128
|
+
else setInternalSelectedDay(key);
|
|
2129
|
+
}
|
|
2130
|
+
function prevMonth() {
|
|
2131
|
+
const m = viewMonth === 0 ? 11 : viewMonth - 1;
|
|
2132
|
+
setMonth(viewMonth === 0 ? viewYear - 1 : viewYear, m);
|
|
2133
|
+
}
|
|
2134
|
+
function nextMonth() {
|
|
2135
|
+
const m = viewMonth === 11 ? 0 : viewMonth + 1;
|
|
2136
|
+
setMonth(viewMonth === 11 ? viewYear + 1 : viewYear, m);
|
|
2137
|
+
}
|
|
2138
|
+
const eventsByDate = useMemo(() => {
|
|
2139
|
+
const map = {};
|
|
2140
|
+
for (const evt of events) {
|
|
2141
|
+
const d = typeof evt.startAt === "string" ? evt.startAt.slice(0, 10) : toDateKey(evt.startAt);
|
|
2142
|
+
if (!map[d]) map[d] = [];
|
|
2143
|
+
map[d].push(evt);
|
|
2144
|
+
}
|
|
2145
|
+
return map;
|
|
2146
|
+
}, [events]);
|
|
2147
|
+
const monthDays = useMemo(() => getMonthDays(viewYear, viewMonth), [viewYear, viewMonth]);
|
|
2148
|
+
const monthLabel = new Date(viewYear, viewMonth).toLocaleString("en-US", {
|
|
2149
|
+
month: "long",
|
|
2150
|
+
year: "numeric"
|
|
2151
|
+
});
|
|
2152
|
+
const selectedDayEvents = selectedDay ? eventsByDate[selectedDay] ?? [] : [];
|
|
2153
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2154
|
+
className: cn$1("flex flex-1 min-h-0 overflow-hidden", className),
|
|
2155
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2156
|
+
className: "flex-1 flex flex-col overflow-hidden border-r border-[var(--md3-outline-variant)]",
|
|
2157
|
+
children: [
|
|
2158
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2159
|
+
className: "flex items-center justify-between border-b border-[var(--md3-outline-variant)] px-4 py-2.5 shrink-0",
|
|
2160
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2161
|
+
className: "flex items-center gap-2",
|
|
2162
|
+
children: [
|
|
2163
|
+
headerLeft,
|
|
2164
|
+
/* @__PURE__ */ jsx("button", {
|
|
2165
|
+
type: "button",
|
|
2166
|
+
onClick: prevMonth,
|
|
2167
|
+
className: "h-7 w-7 rounded-md hover:bg-surface-container-high flex items-center justify-center text-muted-foreground",
|
|
2168
|
+
children: /* @__PURE__ */ jsx("svg", {
|
|
2169
|
+
width: "16",
|
|
2170
|
+
height: "16",
|
|
2171
|
+
viewBox: "0 0 24 24",
|
|
2172
|
+
fill: "none",
|
|
2173
|
+
stroke: "currentColor",
|
|
2174
|
+
strokeWidth: "2",
|
|
2175
|
+
children: /* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" })
|
|
2176
|
+
})
|
|
2177
|
+
}),
|
|
2178
|
+
/* @__PURE__ */ jsx("span", {
|
|
2179
|
+
className: "text-sm font-semibold text-foreground min-w-[160px] text-center",
|
|
2180
|
+
children: monthLabel
|
|
2181
|
+
}),
|
|
2182
|
+
/* @__PURE__ */ jsx("button", {
|
|
2183
|
+
type: "button",
|
|
2184
|
+
onClick: nextMonth,
|
|
2185
|
+
className: "h-7 w-7 rounded-md hover:bg-surface-container-high flex items-center justify-center text-muted-foreground",
|
|
2186
|
+
children: /* @__PURE__ */ jsx("svg", {
|
|
2187
|
+
width: "16",
|
|
2188
|
+
height: "16",
|
|
2189
|
+
viewBox: "0 0 24 24",
|
|
2190
|
+
fill: "none",
|
|
2191
|
+
stroke: "currentColor",
|
|
2192
|
+
strokeWidth: "2",
|
|
2193
|
+
children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" })
|
|
2194
|
+
})
|
|
2195
|
+
}),
|
|
2196
|
+
/* @__PURE__ */ jsx("button", {
|
|
2197
|
+
type: "button",
|
|
2198
|
+
onClick: () => {
|
|
2199
|
+
setMonth(today.getFullYear(), today.getMonth());
|
|
2200
|
+
selectDay(todayKey);
|
|
2201
|
+
},
|
|
2202
|
+
className: "h-7 px-2 rounded-md border border-[var(--md3-outline-variant)] text-xs font-medium text-muted-foreground hover:text-foreground",
|
|
2203
|
+
children: "Today"
|
|
2204
|
+
})
|
|
2205
|
+
]
|
|
2206
|
+
}), headerRight]
|
|
2207
|
+
}),
|
|
2208
|
+
/* @__PURE__ */ jsx("div", {
|
|
2209
|
+
className: "grid grid-cols-7 border-b border-[var(--md3-outline-variant)] shrink-0",
|
|
2210
|
+
children: [
|
|
2211
|
+
"Sun",
|
|
2212
|
+
"Mon",
|
|
2213
|
+
"Tue",
|
|
2214
|
+
"Wed",
|
|
2215
|
+
"Thu",
|
|
2216
|
+
"Fri",
|
|
2217
|
+
"Sat"
|
|
2218
|
+
].map((d) => /* @__PURE__ */ jsx("div", {
|
|
2219
|
+
className: "px-2 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground text-center",
|
|
2220
|
+
children: d
|
|
2221
|
+
}, d))
|
|
2222
|
+
}),
|
|
2223
|
+
/* @__PURE__ */ jsx("div", {
|
|
2224
|
+
className: "flex-1 grid grid-cols-7 auto-rows-fr overflow-y-auto",
|
|
2225
|
+
children: monthDays.map(({ date, key, inMonth }) => {
|
|
2226
|
+
const dayEvents = eventsByDate[key] ?? [];
|
|
2227
|
+
const isToday = key === todayKey;
|
|
2228
|
+
const isSelected = key === selectedDay;
|
|
2229
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
2230
|
+
type: "button",
|
|
2231
|
+
onClick: () => selectDay(key),
|
|
2232
|
+
onDoubleClick: () => onDoubleClickDay?.(key),
|
|
2233
|
+
className: cn$1("flex flex-col items-start p-1.5 border-b border-r border-[var(--md3-outline-variant)] text-left transition-colors min-h-[72px]", !inMonth && "bg-muted/30", isSelected && "bg-primary/5 ring-1 ring-inset ring-primary/20", !isSelected && "hover:bg-surface-container-high"),
|
|
2234
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2235
|
+
className: cn$1("text-xs font-medium w-6 h-6 flex items-center justify-center rounded-full mb-0.5", isToday && "bg-primary text-primary-foreground", !isToday && !inMonth && "text-muted-foreground/50", !isToday && inMonth && "text-foreground"),
|
|
2236
|
+
children: date.getDate()
|
|
2237
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
2238
|
+
className: "flex flex-col gap-0.5 w-full min-w-0",
|
|
2239
|
+
children: [dayEvents.slice(0, 3).map((evt) => renderEventChip ? /* @__PURE__ */ jsx("div", {
|
|
2240
|
+
className: "w-full min-w-0",
|
|
2241
|
+
children: renderEventChip(evt)
|
|
2242
|
+
}, evt.id) : /* @__PURE__ */ jsx("div", {
|
|
2243
|
+
className: "w-full truncate text-[9px] px-1 py-0.5 rounded bg-primary/10 text-primary",
|
|
2244
|
+
title: evt.title,
|
|
2245
|
+
children: evt.title
|
|
2246
|
+
}, evt.id)), dayEvents.length > 3 && /* @__PURE__ */ jsxs("span", {
|
|
2247
|
+
className: "text-[9px] text-muted-foreground px-1",
|
|
2248
|
+
children: [
|
|
2249
|
+
"+",
|
|
2250
|
+
dayEvents.length - 3,
|
|
2251
|
+
" more"
|
|
2252
|
+
]
|
|
2253
|
+
})]
|
|
2254
|
+
})]
|
|
2255
|
+
}, key);
|
|
2256
|
+
})
|
|
2257
|
+
})
|
|
2258
|
+
]
|
|
2259
|
+
}), showDayPanel && /* @__PURE__ */ jsx("div", {
|
|
2260
|
+
className: "w-80 shrink-0 flex flex-col overflow-hidden",
|
|
2261
|
+
children: renderDayDetail ? renderDayDetail(selectedDay ?? todayKey, selectedDayEvents) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
2262
|
+
className: "px-4 py-3 border-b border-[var(--md3-outline-variant)] shrink-0",
|
|
2263
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
2264
|
+
className: "text-sm font-semibold text-foreground",
|
|
2265
|
+
children: selectedDay === todayKey ? "Today" : selectedDay ? (/* @__PURE__ */ new Date(selectedDay + "T00:00:00")).toLocaleDateString("en-US", {
|
|
2266
|
+
weekday: "long",
|
|
2267
|
+
month: "long",
|
|
2268
|
+
day: "numeric"
|
|
2269
|
+
}) : "Select a day"
|
|
2270
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
2271
|
+
className: "text-xs text-muted-foreground",
|
|
2272
|
+
children: [
|
|
2273
|
+
selectedDayEvents.length,
|
|
2274
|
+
" event",
|
|
2275
|
+
selectedDayEvents.length !== 1 ? "s" : ""
|
|
2276
|
+
]
|
|
2277
|
+
})]
|
|
2278
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
2279
|
+
className: "flex-1 overflow-y-auto p-2 space-y-2",
|
|
2280
|
+
children: [selectedDayEvents.length === 0 && /* @__PURE__ */ jsx("div", {
|
|
2281
|
+
className: "px-2 py-8 text-center",
|
|
2282
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
2283
|
+
className: "text-xs text-muted-foreground",
|
|
2284
|
+
children: "No events this day"
|
|
2285
|
+
})
|
|
2286
|
+
}), selectedDayEvents.map((evt) => /* @__PURE__ */ jsxs("div", {
|
|
2287
|
+
className: "rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container p-3",
|
|
2288
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
2289
|
+
className: "text-sm font-medium text-foreground",
|
|
2290
|
+
children: evt.title
|
|
2291
|
+
}), evt.type && /* @__PURE__ */ jsx("span", {
|
|
2292
|
+
className: "mt-1 inline-block rounded-full border border-[var(--md3-outline-variant)] px-2 py-0.5 text-[10px] text-muted-foreground",
|
|
2293
|
+
children: evt.type
|
|
2294
|
+
})]
|
|
2295
|
+
}, evt.id))]
|
|
2296
|
+
})] })
|
|
2297
|
+
})]
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
//#endregion
|
|
2301
|
+
//#region src/workspace/approval-queue.tsx
|
|
2302
|
+
/**
|
|
2303
|
+
* ApprovalQueue — review queue for agent-proposed actions.
|
|
2304
|
+
*
|
|
2305
|
+
* Shows pending items with approve/reject controls, confidence stats per type,
|
|
2306
|
+
* and a collapsible resolved history. Designed for the agent approval feedback
|
|
2307
|
+
* loop: agent proposes → user approves/rejects → rejection reasons feed back.
|
|
2308
|
+
*/
|
|
2309
|
+
function ApprovalQueue({ items, className, onApprove, onReject, renderItemDetail, renderTypeBadge, renderStats, canResolve = true, header, emptyState, showResolved: controlledShowResolved }) {
|
|
2310
|
+
const [rejectingId, setRejectingId] = useState(null);
|
|
2311
|
+
const [rejectReason, setRejectReason] = useState("");
|
|
2312
|
+
const [showResolved, setShowResolved] = useState(controlledShowResolved ?? false);
|
|
2313
|
+
const pending = useMemo(() => items.filter((a) => a.status === "pending"), [items]);
|
|
2314
|
+
const resolved = useMemo(() => items.filter((a) => a.status !== "pending"), [items]);
|
|
2315
|
+
const stats = useMemo(() => {
|
|
2316
|
+
const map = {};
|
|
2317
|
+
for (const item of items) {
|
|
2318
|
+
if (!map[item.type]) map[item.type] = {
|
|
2319
|
+
type: item.type,
|
|
2320
|
+
approved: 0,
|
|
2321
|
+
rejected: 0,
|
|
2322
|
+
total: 0,
|
|
2323
|
+
rate: 0
|
|
2324
|
+
};
|
|
2325
|
+
map[item.type].total++;
|
|
2326
|
+
if (item.status === "approved" || item.status === "executed") map[item.type].approved++;
|
|
2327
|
+
if (item.status === "rejected") map[item.type].rejected++;
|
|
2328
|
+
}
|
|
2329
|
+
for (const s of Object.values(map)) s.rate = s.total > 0 ? Math.round(s.approved / s.total * 100) : 0;
|
|
2330
|
+
return Object.values(map);
|
|
2331
|
+
}, [items]);
|
|
2332
|
+
if (items.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2333
|
+
className: cn$1("flex-1 flex items-center justify-center", className),
|
|
2334
|
+
children: emptyState ?? /* @__PURE__ */ jsx("div", {
|
|
2335
|
+
className: "text-center py-20",
|
|
2336
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
2337
|
+
className: "text-sm text-muted-foreground",
|
|
2338
|
+
children: "No proposals yet"
|
|
2339
|
+
})
|
|
2340
|
+
})
|
|
2341
|
+
});
|
|
2342
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2343
|
+
className: cn$1("flex-1 overflow-y-auto", className),
|
|
2344
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2345
|
+
className: "max-w-3xl mx-auto p-6",
|
|
2346
|
+
children: [
|
|
2347
|
+
header,
|
|
2348
|
+
stats.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
2349
|
+
className: "flex gap-3 mb-6 flex-wrap",
|
|
2350
|
+
children: renderStats ? renderStats(stats) : stats.map((s) => /* @__PURE__ */ jsxs("div", {
|
|
2351
|
+
className: "flex-1 min-w-[120px] rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container p-3",
|
|
2352
|
+
children: [
|
|
2353
|
+
/* @__PURE__ */ jsx("div", {
|
|
2354
|
+
className: "flex items-center gap-2 mb-1",
|
|
2355
|
+
children: renderTypeBadge ? renderTypeBadge(s.type) : /* @__PURE__ */ jsx("span", {
|
|
2356
|
+
className: "rounded-full border border-[var(--md3-outline-variant)] px-2 py-0.5 text-[10px] font-medium text-muted-foreground",
|
|
2357
|
+
children: s.type
|
|
2358
|
+
})
|
|
2359
|
+
}),
|
|
2360
|
+
/* @__PURE__ */ jsx("div", {
|
|
2361
|
+
className: "flex items-baseline gap-1",
|
|
2362
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
2363
|
+
className: "text-xl font-semibold text-foreground",
|
|
2364
|
+
children: [s.rate, "%"]
|
|
2365
|
+
})
|
|
2366
|
+
}),
|
|
2367
|
+
/* @__PURE__ */ jsxs("p", {
|
|
2368
|
+
className: "text-[10px] text-muted-foreground",
|
|
2369
|
+
children: [
|
|
2370
|
+
s.approved,
|
|
2371
|
+
"/",
|
|
2372
|
+
s.total,
|
|
2373
|
+
" approved"
|
|
2374
|
+
]
|
|
2375
|
+
})
|
|
2376
|
+
]
|
|
2377
|
+
}, s.type))
|
|
2378
|
+
}),
|
|
2379
|
+
pending.length > 0 && /* @__PURE__ */ jsxs("section", {
|
|
2380
|
+
className: "mb-8",
|
|
2381
|
+
children: [/* @__PURE__ */ jsxs("h3", {
|
|
2382
|
+
className: "text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3",
|
|
2383
|
+
children: [
|
|
2384
|
+
"Pending (",
|
|
2385
|
+
pending.length,
|
|
2386
|
+
")"
|
|
2387
|
+
]
|
|
2388
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2389
|
+
className: "space-y-3",
|
|
2390
|
+
children: pending.map((item) => /* @__PURE__ */ jsxs("div", {
|
|
2391
|
+
className: "rounded-lg border border-primary/20 bg-surface-container p-4",
|
|
2392
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2393
|
+
className: "flex items-start gap-3",
|
|
2394
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2395
|
+
className: "flex-1 min-w-0",
|
|
2396
|
+
children: [
|
|
2397
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2398
|
+
className: "flex items-center gap-2 mb-1",
|
|
2399
|
+
children: [/* @__PURE__ */ jsx("h4", {
|
|
2400
|
+
className: "text-sm font-medium text-foreground",
|
|
2401
|
+
children: item.title
|
|
2402
|
+
}), renderTypeBadge ? renderTypeBadge(item.type) : /* @__PURE__ */ jsx("span", {
|
|
2403
|
+
className: "rounded-full border border-[var(--md3-outline-variant)] px-2 py-0.5 text-[10px] text-muted-foreground",
|
|
2404
|
+
children: item.type
|
|
2405
|
+
})]
|
|
2406
|
+
}),
|
|
2407
|
+
item.description && /* @__PURE__ */ jsx("p", {
|
|
2408
|
+
className: "text-xs text-muted-foreground line-clamp-2",
|
|
2409
|
+
children: item.description
|
|
2410
|
+
}),
|
|
2411
|
+
renderItemDetail?.(item)
|
|
2412
|
+
]
|
|
2413
|
+
}), canResolve && /* @__PURE__ */ jsxs("div", {
|
|
2414
|
+
className: "flex gap-1.5 shrink-0",
|
|
2415
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
2416
|
+
type: "button",
|
|
2417
|
+
onClick: () => onApprove?.(item),
|
|
2418
|
+
className: "h-8 px-3 rounded-md border border-[var(--md3-outline-variant)] text-xs font-medium text-emerald-500 hover:bg-emerald-500/10 hover:border-emerald-500/30 transition-colors",
|
|
2419
|
+
children: "Approve"
|
|
2420
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
2421
|
+
type: "button",
|
|
2422
|
+
onClick: () => {
|
|
2423
|
+
setRejectingId(rejectingId === item.id ? null : item.id);
|
|
2424
|
+
setRejectReason("");
|
|
2425
|
+
},
|
|
2426
|
+
className: "h-8 px-3 rounded-md border border-[var(--md3-outline-variant)] text-xs font-medium text-destructive hover:bg-destructive/10 hover:border-destructive/30 transition-colors",
|
|
2427
|
+
children: "Reject"
|
|
2428
|
+
})]
|
|
2429
|
+
})]
|
|
2430
|
+
}), rejectingId === item.id && /* @__PURE__ */ jsxs("div", {
|
|
2431
|
+
className: "mt-3 border-t border-[var(--md3-outline-variant)] pt-3",
|
|
2432
|
+
children: [/* @__PURE__ */ jsx("textarea", {
|
|
2433
|
+
placeholder: "Why are you rejecting this?",
|
|
2434
|
+
value: rejectReason,
|
|
2435
|
+
onChange: (e) => setRejectReason(e.target.value),
|
|
2436
|
+
className: `w-full rounded-md border bg-surface-container-low px-3 py-2 text-sm placeholder:text-muted-foreground mb-2 ${focusField}`,
|
|
2437
|
+
rows: 2
|
|
2438
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
2439
|
+
className: "flex gap-2 justify-end",
|
|
2440
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
2441
|
+
type: "button",
|
|
2442
|
+
onClick: () => {
|
|
2443
|
+
setRejectingId(null);
|
|
2444
|
+
setRejectReason("");
|
|
2445
|
+
},
|
|
2446
|
+
className: "h-7 px-3 rounded-md text-xs text-muted-foreground hover:text-foreground",
|
|
2447
|
+
children: "Cancel"
|
|
2448
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
2449
|
+
type: "button",
|
|
2450
|
+
onClick: () => {
|
|
2451
|
+
onReject?.(item, rejectReason.trim() || void 0);
|
|
2452
|
+
setRejectingId(null);
|
|
2453
|
+
setRejectReason("");
|
|
2454
|
+
},
|
|
2455
|
+
className: "h-7 px-3 rounded-md bg-destructive text-destructive-foreground text-xs font-medium",
|
|
2456
|
+
children: "Submit Rejection"
|
|
2457
|
+
})]
|
|
2458
|
+
})]
|
|
2459
|
+
})]
|
|
2460
|
+
}, item.id))
|
|
2461
|
+
})]
|
|
2462
|
+
}),
|
|
2463
|
+
resolved.length > 0 && /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsxs("button", {
|
|
2464
|
+
type: "button",
|
|
2465
|
+
onClick: () => setShowResolved(!showResolved),
|
|
2466
|
+
className: "flex items-center gap-2 text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3 hover:text-foreground transition-colors",
|
|
2467
|
+
children: [
|
|
2468
|
+
/* @__PURE__ */ jsx("svg", {
|
|
2469
|
+
width: "12",
|
|
2470
|
+
height: "12",
|
|
2471
|
+
viewBox: "0 0 24 24",
|
|
2472
|
+
fill: "none",
|
|
2473
|
+
stroke: "currentColor",
|
|
2474
|
+
strokeWidth: "2",
|
|
2475
|
+
className: cn$1("transition-transform", showResolved && "rotate-180"),
|
|
2476
|
+
children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
|
|
2477
|
+
}),
|
|
2478
|
+
"Resolved (",
|
|
2479
|
+
resolved.length,
|
|
2480
|
+
")"
|
|
2481
|
+
]
|
|
2482
|
+
}), showResolved && /* @__PURE__ */ jsx("div", {
|
|
2483
|
+
className: "space-y-2",
|
|
2484
|
+
children: resolved.map((item) => /* @__PURE__ */ jsxs("div", {
|
|
2485
|
+
className: "rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container p-4 opacity-60",
|
|
2486
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2487
|
+
className: "flex items-center gap-2 mb-1",
|
|
2488
|
+
children: [
|
|
2489
|
+
/* @__PURE__ */ jsx("h4", {
|
|
2490
|
+
className: "text-sm font-medium text-foreground",
|
|
2491
|
+
children: item.title
|
|
2492
|
+
}),
|
|
2493
|
+
renderTypeBadge?.(item.type),
|
|
2494
|
+
/* @__PURE__ */ jsx("span", {
|
|
2495
|
+
className: cn$1("rounded-full px-2 py-0.5 text-[10px] font-medium", item.status === "approved" && "bg-emerald-500/10 text-emerald-500", item.status === "rejected" && "bg-destructive/10 text-destructive", item.status === "executed" && "bg-primary/10 text-primary"),
|
|
2496
|
+
children: item.status
|
|
2497
|
+
})
|
|
2498
|
+
]
|
|
2499
|
+
}), item.meta?.rejectionReason != null && /* @__PURE__ */ jsxs("p", {
|
|
2500
|
+
className: "mt-1 text-xs text-destructive",
|
|
2501
|
+
children: ["Reason: ", String(item.meta.rejectionReason)]
|
|
2502
|
+
})]
|
|
2503
|
+
}, item.id))
|
|
2504
|
+
})] })
|
|
2505
|
+
]
|
|
2506
|
+
})
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
//#endregion
|
|
2510
|
+
export { WorkspacePaneHeader as S, TerminalPanel as _, AuditResults as a, DirectoryPane as b, SessionActivityMonitor as c, SessionSidebar as d, SESSION_CREATED_EVENT as f, RuntimePane as g, formatRelativeAge as h, TaskBoard as i, createShellShouldRevalidate as l, useOptimisticSessionItems as m, ApprovalQueue as n, AgentWorkbench as o, dispatchSessionCreated as p, CalendarView as r, SandboxWorkbench as s, ArtifactPane$1 as t, shellShouldRevalidate as u, StatusBar as v, WorkspaceLayout as x, StatusBanner as y };
|