@aku11i/phantom 6.2.0 → 6.3.0-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/app/.output/nitro.json +17 -0
- package/app/.output/public/assets/index-CI6IWFKa.css +2 -0
- package/app/.output/public/assets/index-DHMHkew5.js +12 -0
- package/app/.output/public/assets/jsx-runtime-CUBmso4R.js +1 -0
- package/app/.output/public/assets/routes-Boii8-De.js +1 -0
- package/app/.output/server/__23tanstack-start-plugin-adapters-y_fshQDY.mjs +5 -0
- package/app/.output/server/_chunks/ssr-renderer.mjs +15 -0
- package/app/.output/server/_libs/@tanstack/react-router+[...].mjs +14637 -0
- package/app/.output/server/_libs/h3+rou3+srvx.mjs +1210 -0
- package/app/.output/server/_libs/hookable.mjs +41 -0
- package/app/.output/server/_libs/lucide-react.mjs +351 -0
- package/app/.output/server/_libs/tanstack__history.mjs +342 -0
- package/app/.output/server/_libs/tanstack__router-core.mjs +6 -0
- package/app/.output/server/_libs/ufo.mjs +64 -0
- package/app/.output/server/_libs/zod.mjs +3745 -0
- package/app/.output/server/_runtime.mjs +26 -0
- package/app/.output/server/_ssr/router-C0zvMxvt.mjs +3475 -0
- package/app/.output/server/_ssr/routes-DpnTz1lw.mjs +1450 -0
- package/app/.output/server/_ssr/ssr.mjs +5174 -0
- package/app/.output/server/_ssr/start-DaFzyN3q.mjs +4 -0
- package/app/.output/server/_tanstack-start-manifest_v-CWxREUaR.mjs +35 -0
- package/app/.output/server/index.mjs +326 -0
- package/package.json +6 -1
- package/phantom.js +3757 -3476
|
@@ -0,0 +1,1450 @@
|
|
|
1
|
+
import { r as __toESM } from "../_runtime.mjs";
|
|
2
|
+
import { d as require_react, u as require_jsx_runtime } from "../_libs/@tanstack/react-router+[...].mjs";
|
|
3
|
+
import { _ as ChevronRight, a as Send, b as Bot, c as PanelLeft, d as Inbox, f as GitBranch, g as ChevronsUpDown, h as Clock3, i as Sparkles, l as MessageSquare, m as FileText, n as TriangleAlert, o as Search, p as FolderGit2, r as Square, s as Plus, t as X, u as MessageSquarePlus, v as Check, y as Brain } from "../_libs/lucide-react.mjs";
|
|
4
|
+
//#region node_modules/.nitro/vite/services/ssr/assets/routes-DpnTz1lw.js
|
|
5
|
+
var import_react = /* @__PURE__ */ __toESM(require_react());
|
|
6
|
+
var import_jsx_runtime = require_jsx_runtime();
|
|
7
|
+
function cn(...classes) {
|
|
8
|
+
return classes.filter(Boolean).join(" ");
|
|
9
|
+
}
|
|
10
|
+
var variants$1 = {
|
|
11
|
+
default: "border-transparent bg-primary text-primary-foreground",
|
|
12
|
+
danger: "border-[var(--semantic-danger-border)] bg-[var(--semantic-danger-bg)] text-[var(--semantic-danger-fg)]",
|
|
13
|
+
info: "border-[var(--semantic-info-border)] bg-[var(--semantic-info-bg)] text-[var(--semantic-info-fg)]",
|
|
14
|
+
outline: "border-border bg-transparent text-foreground",
|
|
15
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
|
16
|
+
success: "border-[var(--semantic-success-border)] bg-[var(--semantic-success-bg)] text-[var(--semantic-success-fg)]",
|
|
17
|
+
warning: "border-[var(--semantic-warning-border)] bg-[var(--semantic-warning-bg)] text-[var(--semantic-warning-fg)]"
|
|
18
|
+
};
|
|
19
|
+
function Badge({ className, variant = "default", ...props }) {
|
|
20
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
21
|
+
className: cn("inline-flex items-center gap-1 rounded-[var(--radius-xs)] border px-2 py-0.5 text-[length:var(--font-size-xs)] font-medium whitespace-nowrap transition-colors", variants$1[variant], className),
|
|
22
|
+
...props
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
var variants = {
|
|
26
|
+
default: "bg-primary text-primary-foreground shadow-[var(--shadow-xs)] hover:bg-[var(--color-gray-800)]",
|
|
27
|
+
destructive: "bg-destructive text-destructive-foreground shadow-[var(--shadow-xs)] hover:bg-[var(--color-rose-500)] focus-visible:ring-[var(--semantic-danger-border)]/40",
|
|
28
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
29
|
+
outline: "border border-input bg-[var(--surface-card)] shadow-[var(--shadow-xs)] hover:bg-accent hover:text-accent-foreground",
|
|
30
|
+
secondary: "bg-secondary text-secondary-foreground shadow-[var(--shadow-xs)] hover:bg-[var(--color-gray-150)]"
|
|
31
|
+
};
|
|
32
|
+
var sizes = {
|
|
33
|
+
default: "h-9 px-4 py-2",
|
|
34
|
+
icon: "size-8",
|
|
35
|
+
sm: "h-8 gap-1.5 px-3"
|
|
36
|
+
};
|
|
37
|
+
function Button({ className, size = "default", variant = "default", ...props }) {
|
|
38
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
39
|
+
className: cn("inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius-sm)] text-[length:var(--font-size-sm)] font-medium outline-none transition-colors duration-[var(--motion-duration-fast)] ease-[var(--motion-ease-standard)] focus-visible:border-ring focus-visible:shadow-[var(--state-focus-ring)] disabled:pointer-events-none disabled:opacity-[var(--opacity-disabled)] [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", variants[variant], sizes[size], className),
|
|
40
|
+
...props
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function Input({ className, ...props }) {
|
|
44
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
45
|
+
className: cn("flex h-9 w-full min-w-0 rounded-[var(--radius-sm)] border border-input bg-[var(--surface-input)] px-3 py-1 text-[length:var(--font-size-md)] shadow-[var(--shadow-xs)] outline-none transition-[border-color,box-shadow] duration-[var(--motion-duration-fast)] placeholder:text-[var(--text-tertiary)] focus-visible:border-ring focus-visible:shadow-[var(--state-focus-ring)] disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-[var(--opacity-disabled)]", className),
|
|
46
|
+
...props
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function Combobox({ "aria-label": ariaLabel, align = "start", className, disabled = false, emptyMessage = "No results", icon, onQueryChange, onValueChange, options, placeholder, query, searchPlaceholder = "Search", shouldFilter = true, side = "bottom", triggerClassName, value }) {
|
|
50
|
+
const [isOpen, setIsOpen] = (0, import_react.useState)(false);
|
|
51
|
+
const [activeOptionIndex, setActiveOptionIndex] = (0, import_react.useState)(-1);
|
|
52
|
+
const [internalQuery, setInternalQuery] = (0, import_react.useState)("");
|
|
53
|
+
const rootRef = (0, import_react.useRef)(null);
|
|
54
|
+
const listboxId = (0, import_react.useId)();
|
|
55
|
+
const searchQuery = query ?? internalQuery;
|
|
56
|
+
const selectedOption = options.find((option) => option.value === value);
|
|
57
|
+
const filteredOptions = (0, import_react.useMemo)(() => {
|
|
58
|
+
if (!shouldFilter || !searchQuery.trim()) return options;
|
|
59
|
+
const normalizedQuery = searchQuery.trim().toLowerCase();
|
|
60
|
+
return options.filter((option) => {
|
|
61
|
+
return [
|
|
62
|
+
option.label,
|
|
63
|
+
option.description,
|
|
64
|
+
...option.keywords ?? []
|
|
65
|
+
].filter(Boolean).join(" ").toLowerCase().includes(normalizedQuery);
|
|
66
|
+
});
|
|
67
|
+
}, [
|
|
68
|
+
options,
|
|
69
|
+
searchQuery,
|
|
70
|
+
shouldFilter
|
|
71
|
+
]);
|
|
72
|
+
(0, import_react.useEffect)(() => {
|
|
73
|
+
if (!isOpen) return;
|
|
74
|
+
const handlePointerDown = (event) => {
|
|
75
|
+
if (!rootRef.current?.contains(event.target)) setIsOpen(false);
|
|
76
|
+
};
|
|
77
|
+
const handleKeyDown = (event) => {
|
|
78
|
+
if (event.key === "Escape") setIsOpen(false);
|
|
79
|
+
};
|
|
80
|
+
document.addEventListener("pointerdown", handlePointerDown);
|
|
81
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
82
|
+
return () => {
|
|
83
|
+
document.removeEventListener("pointerdown", handlePointerDown);
|
|
84
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
85
|
+
};
|
|
86
|
+
}, [isOpen]);
|
|
87
|
+
(0, import_react.useEffect)(() => {
|
|
88
|
+
if (!isOpen && query === void 0) setInternalQuery("");
|
|
89
|
+
}, [isOpen, query]);
|
|
90
|
+
(0, import_react.useEffect)(() => {
|
|
91
|
+
if (!isOpen) {
|
|
92
|
+
setActiveOptionIndex(-1);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
setActiveOptionIndex(getNextEnabledIndex(filteredOptions, -1, 1));
|
|
96
|
+
}, [filteredOptions, isOpen]);
|
|
97
|
+
function updateQuery(nextQuery) {
|
|
98
|
+
if (onQueryChange) onQueryChange(nextQuery);
|
|
99
|
+
else setInternalQuery(nextQuery);
|
|
100
|
+
}
|
|
101
|
+
function selectOption(option) {
|
|
102
|
+
if (option.disabled) return;
|
|
103
|
+
onValueChange(option.value);
|
|
104
|
+
setIsOpen(false);
|
|
105
|
+
updateQuery("");
|
|
106
|
+
}
|
|
107
|
+
function handleSearchKeyDown(event) {
|
|
108
|
+
if (event.key === "ArrowDown") {
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
setActiveOptionIndex((current) => getNextEnabledIndex(filteredOptions, current, 1));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (event.key === "ArrowUp") {
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
setActiveOptionIndex((current) => getNextEnabledIndex(filteredOptions, current, -1));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (event.key === "Home") {
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
setActiveOptionIndex(getNextEnabledIndex(filteredOptions, -1, 1));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (event.key === "End") {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
setActiveOptionIndex(getNextEnabledIndex(filteredOptions, filteredOptions.length, -1));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (event.key === "Enter") {
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
if (activeOptionIndex >= 0) {
|
|
131
|
+
const option = filteredOptions[activeOptionIndex];
|
|
132
|
+
if (!option) return;
|
|
133
|
+
selectOption(option);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
138
|
+
className: cn("relative min-w-0", className),
|
|
139
|
+
ref: rootRef,
|
|
140
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
141
|
+
"aria-controls": listboxId,
|
|
142
|
+
"aria-expanded": isOpen,
|
|
143
|
+
"aria-label": ariaLabel,
|
|
144
|
+
className: cn("inline-flex h-8 max-w-full items-center gap-1.5 rounded-[var(--radius-sm)] border border-input bg-[var(--surface-card)] px-2.5 text-[length:var(--font-size-sm)] font-medium text-[var(--text-secondary)] shadow-[var(--shadow-xs)] outline-none transition-colors hover:bg-accent focus-visible:border-ring focus-visible:shadow-[var(--state-focus-ring)] disabled:pointer-events-none disabled:opacity-[var(--opacity-disabled)]", triggerClassName),
|
|
145
|
+
disabled,
|
|
146
|
+
onClick: () => setIsOpen((current) => !current),
|
|
147
|
+
role: "combobox",
|
|
148
|
+
type: "button",
|
|
149
|
+
children: [
|
|
150
|
+
icon,
|
|
151
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
152
|
+
className: "min-w-0 truncate",
|
|
153
|
+
children: selectedOption?.label ?? placeholder
|
|
154
|
+
}),
|
|
155
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronsUpDown, { className: "size-3.5 shrink-0 text-[var(--icon-color-muted)]" })
|
|
156
|
+
]
|
|
157
|
+
}), isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
158
|
+
className: cn("absolute z-50 w-80 max-w-[calc(100vw-2rem)] overflow-hidden rounded-[var(--radius-md)] border border-border bg-popover text-popover-foreground shadow-[var(--shadow-md)]", side === "top" ? "bottom-full mb-1" : "top-full mt-1", align === "end" ? "right-0" : "left-0"),
|
|
159
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
160
|
+
className: "flex items-center gap-2 border-b border-[var(--border-divider)] px-2 py-2",
|
|
161
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Search, { className: "size-3.5 shrink-0 text-[var(--icon-color-muted)]" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
|
|
162
|
+
"aria-activedescendant": activeOptionIndex >= 0 ? `${listboxId}-option-${activeOptionIndex}` : void 0,
|
|
163
|
+
"aria-autocomplete": "list",
|
|
164
|
+
"aria-controls": listboxId,
|
|
165
|
+
autoFocus: true,
|
|
166
|
+
className: "h-7 border-0 bg-transparent px-0 py-0 shadow-none focus-visible:shadow-none",
|
|
167
|
+
placeholder: searchPlaceholder,
|
|
168
|
+
value: searchQuery,
|
|
169
|
+
onChange: (event) => updateQuery(event.target.value),
|
|
170
|
+
onKeyDown: handleSearchKeyDown
|
|
171
|
+
})]
|
|
172
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
173
|
+
className: "max-h-64 overflow-y-auto p-1",
|
|
174
|
+
id: listboxId,
|
|
175
|
+
role: "listbox",
|
|
176
|
+
children: filteredOptions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
177
|
+
className: "px-2 py-3 text-[length:var(--font-size-sm)] text-[var(--text-tertiary)]",
|
|
178
|
+
children: emptyMessage
|
|
179
|
+
}) : filteredOptions.map((option, index) => {
|
|
180
|
+
const isSelected = option.value === value;
|
|
181
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
182
|
+
"aria-selected": isSelected,
|
|
183
|
+
className: cn("flex w-full min-w-0 items-start gap-2 rounded-[var(--radius-sm)] px-2 py-2 text-left outline-none transition-colors hover:bg-accent focus-visible:shadow-[var(--state-focus-ring)] disabled:pointer-events-none disabled:opacity-[var(--opacity-disabled)]", (index === activeOptionIndex || isSelected) && "bg-[var(--state-selected-bg)]"),
|
|
184
|
+
disabled: option.disabled,
|
|
185
|
+
id: `${listboxId}-option-${index}`,
|
|
186
|
+
onClick: () => selectOption(option),
|
|
187
|
+
onMouseEnter: () => setActiveOptionIndex(index),
|
|
188
|
+
role: "option",
|
|
189
|
+
type: "button",
|
|
190
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: cn("mt-0.5 size-3.5 shrink-0 text-[var(--icon-color-active)]", !isSelected && "opacity-0") }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
191
|
+
className: "min-w-0 flex-1",
|
|
192
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
193
|
+
className: "block truncate text-[length:var(--font-size-sm)] font-medium text-[var(--text-primary)]",
|
|
194
|
+
children: option.label
|
|
195
|
+
}), option.description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
196
|
+
className: "mt-0.5 block truncate text-[length:var(--font-size-xs)] text-[var(--text-tertiary)]",
|
|
197
|
+
children: option.description
|
|
198
|
+
})]
|
|
199
|
+
})]
|
|
200
|
+
}, option.value);
|
|
201
|
+
})
|
|
202
|
+
})]
|
|
203
|
+
})]
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function getNextEnabledIndex(options, currentIndex, direction) {
|
|
207
|
+
if (options.length === 0) return -1;
|
|
208
|
+
let nextIndex = currentIndex;
|
|
209
|
+
for (let offset = 0; offset < options.length; offset += 1) {
|
|
210
|
+
nextIndex = (nextIndex + direction + options.length) % options.length;
|
|
211
|
+
if (!options[nextIndex]?.disabled) return nextIndex;
|
|
212
|
+
}
|
|
213
|
+
return -1;
|
|
214
|
+
}
|
|
215
|
+
var focusableSelector = [
|
|
216
|
+
"a[href]",
|
|
217
|
+
"button:not([disabled])",
|
|
218
|
+
"input:not([disabled])",
|
|
219
|
+
"select:not([disabled])",
|
|
220
|
+
"textarea:not([disabled])",
|
|
221
|
+
"[tabindex]:not([tabindex='-1'])"
|
|
222
|
+
].join(",");
|
|
223
|
+
function Dialog({ children, onOpenChange, open }) {
|
|
224
|
+
const contentRef = (0, import_react.useRef)(null);
|
|
225
|
+
(0, import_react.useEffect)(() => {
|
|
226
|
+
if (!open) return;
|
|
227
|
+
const previousActiveElement = document.activeElement;
|
|
228
|
+
getFocusableElements(contentRef.current)[0]?.focus();
|
|
229
|
+
return () => {
|
|
230
|
+
if (previousActiveElement instanceof HTMLElement) previousActiveElement.focus();
|
|
231
|
+
};
|
|
232
|
+
}, [open]);
|
|
233
|
+
function handleKeyDown(event) {
|
|
234
|
+
if (event.key === "Escape") {
|
|
235
|
+
event.preventDefault();
|
|
236
|
+
onOpenChange(false);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (event.key !== "Tab") return;
|
|
240
|
+
const focusableElements = getFocusableElements(contentRef.current);
|
|
241
|
+
if (focusableElements.length === 0) {
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const firstElement = focusableElements[0];
|
|
246
|
+
const lastElement = focusableElements[focusableElements.length - 1];
|
|
247
|
+
if (!firstElement || !lastElement) return;
|
|
248
|
+
if (event.shiftKey && document.activeElement === firstElement) {
|
|
249
|
+
event.preventDefault();
|
|
250
|
+
lastElement.focus();
|
|
251
|
+
}
|
|
252
|
+
if (!event.shiftKey && document.activeElement === lastElement) {
|
|
253
|
+
event.preventDefault();
|
|
254
|
+
firstElement.focus();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (!open) return null;
|
|
258
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
259
|
+
className: "fixed inset-0 z-50 flex items-center justify-center p-4",
|
|
260
|
+
onKeyDown: handleKeyDown,
|
|
261
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
262
|
+
"aria-label": "Close dialog",
|
|
263
|
+
className: "absolute inset-0 bg-[var(--surface-overlay)]",
|
|
264
|
+
onClick: () => onOpenChange(false),
|
|
265
|
+
tabIndex: -1,
|
|
266
|
+
type: "button"
|
|
267
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
268
|
+
ref: contentRef,
|
|
269
|
+
className: "contents",
|
|
270
|
+
children
|
|
271
|
+
})]
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function DialogContent({ className, ...props }) {
|
|
275
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
276
|
+
className: cn("relative z-10 grid w-full max-w-md gap-4 rounded-[var(--radius-lg)] border border-border bg-card p-5 text-card-foreground shadow-[var(--shadow-lg)]", className),
|
|
277
|
+
"aria-modal": "true",
|
|
278
|
+
role: "dialog",
|
|
279
|
+
...props
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
function DialogHeader({ className, ...props }) {
|
|
283
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
284
|
+
className: cn("grid gap-1.5", className),
|
|
285
|
+
...props
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function DialogTitle({ className, ...props }) {
|
|
289
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
|
|
290
|
+
className: cn("text-[length:var(--font-size-xl)] font-semibold leading-none", className),
|
|
291
|
+
...props
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
function DialogDescription({ className, ...props }) {
|
|
295
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
296
|
+
className: cn("text-[length:var(--font-size-md)] text-muted-foreground", className),
|
|
297
|
+
...props
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
function DialogFooter({ className, ...props }) {
|
|
301
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
302
|
+
className: cn("flex justify-end gap-2", className),
|
|
303
|
+
...props
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
function getFocusableElements(root) {
|
|
307
|
+
if (!root) return [];
|
|
308
|
+
return Array.from(root.querySelectorAll(focusableSelector)).filter((element) => !element.hasAttribute("disabled") && element.getAttribute("aria-hidden") !== "true");
|
|
309
|
+
}
|
|
310
|
+
function Label({ className, ...props }) {
|
|
311
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
312
|
+
className: cn("text-[length:var(--font-size-sm)] font-medium leading-none text-foreground", className),
|
|
313
|
+
...props
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
var SIDEBAR_KEYBOARD_SHORTCUT = "b";
|
|
317
|
+
var SidebarContext = (0, import_react.createContext)(null);
|
|
318
|
+
function useSidebar() {
|
|
319
|
+
const context = (0, import_react.useContext)(SidebarContext);
|
|
320
|
+
if (!context) throw new Error("useSidebar must be used within a SidebarProvider.");
|
|
321
|
+
return context;
|
|
322
|
+
}
|
|
323
|
+
function SidebarProvider({ children, className, defaultOpen = true, open: openProp, onOpenChange, style, ...props }) {
|
|
324
|
+
const [_open, _setOpen] = (0, import_react.useState)(defaultOpen);
|
|
325
|
+
const open = openProp ?? _open;
|
|
326
|
+
const setOpen = (0, import_react.useCallback)((value) => {
|
|
327
|
+
const nextOpen = typeof value === "function" ? value(open) : value;
|
|
328
|
+
onOpenChange?.(nextOpen);
|
|
329
|
+
if (openProp === void 0) _setOpen(nextOpen);
|
|
330
|
+
}, [
|
|
331
|
+
onOpenChange,
|
|
332
|
+
open,
|
|
333
|
+
openProp
|
|
334
|
+
]);
|
|
335
|
+
const toggleSidebar = (0, import_react.useCallback)(() => {
|
|
336
|
+
setOpen((current) => !current);
|
|
337
|
+
}, [setOpen]);
|
|
338
|
+
(0, import_react.useEffect)(() => {
|
|
339
|
+
const handleKeyDown = (event) => {
|
|
340
|
+
if (event.key.toLowerCase() === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
toggleSidebar();
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
346
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
347
|
+
}, [toggleSidebar]);
|
|
348
|
+
const contextValue = (0, import_react.useMemo)(() => ({
|
|
349
|
+
open,
|
|
350
|
+
setOpen,
|
|
351
|
+
state: open ? "expanded" : "collapsed",
|
|
352
|
+
toggleSidebar
|
|
353
|
+
}), [
|
|
354
|
+
open,
|
|
355
|
+
setOpen,
|
|
356
|
+
toggleSidebar
|
|
357
|
+
]);
|
|
358
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarContext.Provider, {
|
|
359
|
+
value: contextValue,
|
|
360
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
361
|
+
className: cn("group/sidebar-wrapper flex min-h-svh w-full bg-background text-foreground has-[[data-variant=inset]]:bg-background", className),
|
|
362
|
+
style: {
|
|
363
|
+
"--sidebar-width": "var(--layout-sidebar-width)",
|
|
364
|
+
...style
|
|
365
|
+
},
|
|
366
|
+
...props,
|
|
367
|
+
children
|
|
368
|
+
})
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
function Sidebar({ children, className, collapsible = "offcanvas", variant = "sidebar", ...props }) {
|
|
372
|
+
const { setOpen, state } = useSidebar();
|
|
373
|
+
const isOffcanvasCollapsed = state === "collapsed" && collapsible === "offcanvas";
|
|
374
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [!isOffcanvasCollapsed && collapsible === "offcanvas" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
375
|
+
"aria-label": "Close sidebar",
|
|
376
|
+
className: "fixed inset-0 z-30 bg-[var(--surface-overlay)] md:hidden",
|
|
377
|
+
onClick: () => setOpen(false),
|
|
378
|
+
type: "button"
|
|
379
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("aside", {
|
|
380
|
+
"data-collapsible": state === "collapsed" ? collapsible : "",
|
|
381
|
+
"data-state": state,
|
|
382
|
+
"data-variant": variant,
|
|
383
|
+
className: cn("group/sidebar fixed inset-y-0 left-0 z-40 flex h-svh w-[var(--sidebar-width)] shrink-0 flex-col overflow-hidden text-sidebar-foreground transition-transform duration-[var(--motion-duration-normal)] ease-[var(--motion-ease-standard)] md:relative md:z-auto", isOffcanvasCollapsed && "hidden", !isOffcanvasCollapsed && "translate-x-0", !isOffcanvasCollapsed && variant === "sidebar" && "border-r border-sidebar-border bg-sidebar", !isOffcanvasCollapsed && variant === "inset" && "border-r border-sidebar-border bg-sidebar", className),
|
|
384
|
+
...props,
|
|
385
|
+
children: !isOffcanvasCollapsed && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
386
|
+
className: "flex h-full min-h-0 flex-col bg-sidebar",
|
|
387
|
+
children
|
|
388
|
+
})
|
|
389
|
+
})] });
|
|
390
|
+
}
|
|
391
|
+
function SidebarHeader({ className, ...props }) {
|
|
392
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
393
|
+
className: cn("flex min-h-[var(--layout-topbar-height)] items-center gap-2 border-b border-sidebar-border px-3 group-data-[state=collapsed]/sidebar:justify-center group-data-[state=collapsed]/sidebar:px-2", className),
|
|
394
|
+
...props
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
function SidebarInset({ className, ...props }) {
|
|
398
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("main", {
|
|
399
|
+
className: cn("relative flex min-w-0 flex-1 flex-col bg-[var(--surface-panel)]", className),
|
|
400
|
+
...props
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
function SidebarTrigger({ className, ...props }) {
|
|
404
|
+
const { toggleSidebar } = useSidebar();
|
|
405
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
406
|
+
className: cn("inline-flex size-8 shrink-0 items-center justify-center rounded-[var(--radius-sm)] text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:shadow-[var(--state-focus-ring)] disabled:pointer-events-none disabled:opacity-[var(--opacity-disabled)]", className),
|
|
407
|
+
onClick: toggleSidebar,
|
|
408
|
+
title: "Toggle sidebar",
|
|
409
|
+
type: "button",
|
|
410
|
+
...props,
|
|
411
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PanelLeft, { className: "size-4" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
412
|
+
className: "sr-only",
|
|
413
|
+
children: "Toggle sidebar"
|
|
414
|
+
})]
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
function SidebarRail({ className, ...props }) {
|
|
418
|
+
const { toggleSidebar } = useSidebar();
|
|
419
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
420
|
+
"aria-label": "Toggle sidebar",
|
|
421
|
+
className: cn("absolute inset-y-0 -right-3 z-20 hidden w-6 -translate-x-px transition-colors after:absolute after:inset-y-0 after:left-1/2 after:w-px hover:after:bg-sidebar-border sm:flex", className),
|
|
422
|
+
onClick: toggleSidebar,
|
|
423
|
+
tabIndex: -1,
|
|
424
|
+
type: "button",
|
|
425
|
+
...props
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
function SidebarContent({ className, ...props }) {
|
|
429
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
430
|
+
className: cn("min-h-0 flex-1 overflow-y-auto p-2 group-data-[state=collapsed]/sidebar:px-2", className),
|
|
431
|
+
...props
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
function SidebarGroup({ className, ...props }) {
|
|
435
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("section", {
|
|
436
|
+
className: cn("relative space-y-1 group-data-[state=collapsed]/sidebar:space-y-2", className),
|
|
437
|
+
...props
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
function SidebarGroupHeader({ className, ...props }) {
|
|
441
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
442
|
+
className: cn("flex items-center gap-2 group-data-[state=collapsed]/sidebar:hidden", className),
|
|
443
|
+
...props
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
function SidebarGroupLabel({ className, ...props }) {
|
|
447
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
448
|
+
className: cn("min-w-0 flex-1 px-2 py-1 text-[length:var(--font-size-xs)] font-medium text-[var(--text-secondary)]", className),
|
|
449
|
+
...props
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
function SidebarGroupAction({ className, ...props }) {
|
|
453
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
454
|
+
className: cn("inline-flex size-7 shrink-0 items-center justify-center rounded-[var(--radius-sm)] text-[var(--icon-color-default)] outline-none transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:shadow-[var(--state-focus-ring)]", className),
|
|
455
|
+
type: "button",
|
|
456
|
+
...props
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
function SidebarGroupContent({ className, ...props }) {
|
|
460
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
461
|
+
className: cn("w-full", className),
|
|
462
|
+
...props
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
function SidebarMenu({ className, ...props }) {
|
|
466
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
|
|
467
|
+
className: cn("space-y-1", className),
|
|
468
|
+
...props
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
function SidebarMenuItem({ className, ...props }) {
|
|
472
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", {
|
|
473
|
+
className: cn("min-w-0", className),
|
|
474
|
+
...props
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
function SidebarMenuButton({ className, isActive, ...props }) {
|
|
478
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
479
|
+
className: cn("flex min-h-8 w-full min-w-0 items-center gap-2 rounded-[var(--radius-sm)] px-2 py-1.5 text-left text-[length:var(--font-size-sm)] outline-none transition-colors duration-[var(--motion-duration-fast)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:shadow-[var(--state-focus-ring)] group-data-[state=collapsed]/sidebar:justify-center group-data-[state=collapsed]/sidebar:px-0", isActive && "bg-sidebar-accent text-sidebar-accent-foreground", className),
|
|
480
|
+
...props
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
function SidebarMenuSub({ className, ...props }) {
|
|
484
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
|
|
485
|
+
className: cn("ml-4 mt-1 space-y-1 border-l border-sidebar-border pl-2 group-data-[state=collapsed]/sidebar:hidden", className),
|
|
486
|
+
...props
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
function SidebarMenuSubItem({ className, ...props }) {
|
|
490
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", {
|
|
491
|
+
className: cn("min-w-0", className),
|
|
492
|
+
...props
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
function SidebarMenuSubButton({ className, isActive, ...props }) {
|
|
496
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
497
|
+
className: cn("flex min-h-7 w-full min-w-0 items-center gap-2 rounded-[var(--radius-sm)] px-2 py-1 text-left text-[length:var(--font-size-sm)] outline-none transition-colors duration-[var(--motion-duration-fast)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:shadow-[var(--state-focus-ring)]", isActive && "bg-sidebar-accent text-sidebar-accent-foreground", className),
|
|
498
|
+
...props
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
function Textarea({ className, ...props }) {
|
|
502
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", {
|
|
503
|
+
className: cn("flex min-h-16 w-full min-w-0 resize-none rounded-[var(--radius-sm)] border border-input bg-[var(--surface-input)] px-3 py-2 text-[length:var(--font-size-md)] shadow-[var(--shadow-xs)] outline-none transition-[border-color,box-shadow] duration-[var(--motion-duration-fast)] placeholder:text-[var(--text-tertiary)] focus-visible:border-ring focus-visible:shadow-[var(--state-focus-ring)] disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-[var(--opacity-disabled)]", className),
|
|
504
|
+
...props
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
var chatEventNames = [
|
|
508
|
+
"chat.created",
|
|
509
|
+
"chat.updated",
|
|
510
|
+
"chat.message.created",
|
|
511
|
+
"agent.thread.started",
|
|
512
|
+
"agent.turn.started",
|
|
513
|
+
"agent.item.updated",
|
|
514
|
+
"agent.item.delta",
|
|
515
|
+
"agent.approval.requested",
|
|
516
|
+
"agent.approval.resolved",
|
|
517
|
+
"agent.turn.completed",
|
|
518
|
+
"agent.error",
|
|
519
|
+
"agent.event",
|
|
520
|
+
"auth.updated"
|
|
521
|
+
];
|
|
522
|
+
var statusMeta = {
|
|
523
|
+
archived: {
|
|
524
|
+
badge: "secondary",
|
|
525
|
+
dot: "bg-[var(--color-gray-400)]",
|
|
526
|
+
label: "Archived"
|
|
527
|
+
},
|
|
528
|
+
failed: {
|
|
529
|
+
badge: "danger",
|
|
530
|
+
dot: "bg-[var(--semantic-danger-fg)]",
|
|
531
|
+
label: "Failed"
|
|
532
|
+
},
|
|
533
|
+
idle: {
|
|
534
|
+
badge: "secondary",
|
|
535
|
+
dot: "bg-[var(--color-gray-500)]",
|
|
536
|
+
label: "Idle"
|
|
537
|
+
},
|
|
538
|
+
running: {
|
|
539
|
+
badge: "info",
|
|
540
|
+
dot: "bg-[var(--semantic-info-fg)]",
|
|
541
|
+
label: "Running"
|
|
542
|
+
},
|
|
543
|
+
waitingForApproval: {
|
|
544
|
+
badge: "warning",
|
|
545
|
+
dot: "bg-[var(--semantic-warning-fg)]",
|
|
546
|
+
label: "Approval"
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
function firstProjectWorktree(projectId, worktreesByProject) {
|
|
550
|
+
if (!projectId) return null;
|
|
551
|
+
return worktreesByProject[projectId]?.[0] ?? null;
|
|
552
|
+
}
|
|
553
|
+
function formatLeadingEllipsisPath(path, maxLength = 44) {
|
|
554
|
+
if (path.length <= maxLength) return path;
|
|
555
|
+
const suffixLength = maxLength - 3;
|
|
556
|
+
const suffix = path.slice(-suffixLength);
|
|
557
|
+
const slashIndex = suffix.indexOf("/");
|
|
558
|
+
return `...${slashIndex > 0 ? suffix.slice(slashIndex) : suffix}`;
|
|
559
|
+
}
|
|
560
|
+
function dedupeChatThreads(chats) {
|
|
561
|
+
const chatsWithThreads = chats.filter((chat) => chat.codexThreadId);
|
|
562
|
+
const source = chatsWithThreads.length > 0 ? chatsWithThreads : chats;
|
|
563
|
+
const chatsByThread = /* @__PURE__ */ new Map();
|
|
564
|
+
for (const chat of source) {
|
|
565
|
+
const key = chat.codexThreadId ?? chat.id;
|
|
566
|
+
const current = chatsByThread.get(key);
|
|
567
|
+
if (!current || chat.updatedAt.localeCompare(current.updatedAt) > 0) chatsByThread.set(key, chat);
|
|
568
|
+
}
|
|
569
|
+
return [...chatsByThread.values()].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
570
|
+
}
|
|
571
|
+
function Home() {
|
|
572
|
+
const [projects, setProjects] = (0, import_react.useState)([]);
|
|
573
|
+
const [selectedProjectId, setSelectedProjectId] = (0, import_react.useState)(null);
|
|
574
|
+
const [chatsByProject, setChatsByProject] = (0, import_react.useState)({});
|
|
575
|
+
const [worktreesByProject, setWorktreesByProject] = (0, import_react.useState)({});
|
|
576
|
+
const [expandedProjectIds, setExpandedProjectIds] = (0, import_react.useState)(() => /* @__PURE__ */ new Set());
|
|
577
|
+
const [selectedWorktreePath, setSelectedWorktreePath] = (0, import_react.useState)(null);
|
|
578
|
+
const [selectedChatId, setSelectedChatId] = (0, import_react.useState)(null);
|
|
579
|
+
const [messages, setMessages] = (0, import_react.useState)([]);
|
|
580
|
+
const [isAddProjectOpen, setIsAddProjectOpen] = (0, import_react.useState)(false);
|
|
581
|
+
const [projectPath, setProjectPath] = (0, import_react.useState)("");
|
|
582
|
+
const [composerText, setComposerText] = (0, import_react.useState)("");
|
|
583
|
+
const [models, setModels] = (0, import_react.useState)([]);
|
|
584
|
+
const [selectedModelId, setSelectedModelId] = (0, import_react.useState)(null);
|
|
585
|
+
const [selectedEffort, setSelectedEffort] = (0, import_react.useState)(null);
|
|
586
|
+
const [skills, setSkills] = (0, import_react.useState)([]);
|
|
587
|
+
const [selectedSkillPaths, setSelectedSkillPaths] = (0, import_react.useState)(() => /* @__PURE__ */ new Set());
|
|
588
|
+
const [fileSearchQuery, setFileSearchQuery] = (0, import_react.useState)("");
|
|
589
|
+
const fileSearchRequestIdRef = (0, import_react.useRef)(0);
|
|
590
|
+
const [fileSearchResults, setFileSearchResults] = (0, import_react.useState)([]);
|
|
591
|
+
const [selectedFiles, setSelectedFiles] = (0, import_react.useState)([]);
|
|
592
|
+
const [status, setStatus] = (0, import_react.useState)("Starting");
|
|
593
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
594
|
+
const [isBusy, setIsBusy] = (0, import_react.useState)(false);
|
|
595
|
+
const createChatInFlightRef = (0, import_react.useRef)(false);
|
|
596
|
+
const selectedChatIdRef = (0, import_react.useRef)(null);
|
|
597
|
+
const selectedChatVersionRef = (0, import_react.useRef)(0);
|
|
598
|
+
const sendMessageRequestIdRef = (0, import_react.useRef)(0);
|
|
599
|
+
const [pendingApproval, setPendingApproval] = (0, import_react.useState)(null);
|
|
600
|
+
const selectedProject = (0, import_react.useMemo)(() => projects.find((project) => project.id === selectedProjectId) ?? null, [projects, selectedProjectId]);
|
|
601
|
+
const selectedChat = (0, import_react.useMemo)(() => Object.values(chatsByProject).flat().find((chat) => chat.id === selectedChatId) ?? null, [chatsByProject, selectedChatId]);
|
|
602
|
+
const isChatRunning = Boolean(selectedChat?.activeTurnId);
|
|
603
|
+
const selectedModel = (0, import_react.useMemo)(() => models.find((model) => model.id === selectedModelId) ?? models.find((model) => model.isDefault) ?? models[0] ?? null, [models, selectedModelId]);
|
|
604
|
+
const selectedSkills = (0, import_react.useMemo)(() => skills.filter((skill) => selectedSkillPaths.has(skill.path)), [selectedSkillPaths, skills]);
|
|
605
|
+
const modelOptions = (0, import_react.useMemo)(() => models.map((model) => ({
|
|
606
|
+
value: model.id,
|
|
607
|
+
label: model.displayName,
|
|
608
|
+
description: model.description || model.model,
|
|
609
|
+
keywords: [model.model]
|
|
610
|
+
})), [models]);
|
|
611
|
+
const effortOptions = (0, import_react.useMemo)(() => {
|
|
612
|
+
const supportedEfforts = selectedModel?.supportedReasoningEfforts.length ? selectedModel.supportedReasoningEfforts : [
|
|
613
|
+
"low",
|
|
614
|
+
"medium",
|
|
615
|
+
"high",
|
|
616
|
+
"xhigh"
|
|
617
|
+
];
|
|
618
|
+
return [{
|
|
619
|
+
value: "auto",
|
|
620
|
+
label: "Auto",
|
|
621
|
+
description: selectedModel?.defaultReasoningEffort ? `Default: ${selectedModel.defaultReasoningEffort}` : "Use model default"
|
|
622
|
+
}, ...supportedEfforts.map((effort) => ({
|
|
623
|
+
value: effort,
|
|
624
|
+
label: formatReasoningEffort(effort)
|
|
625
|
+
}))];
|
|
626
|
+
}, [selectedModel]);
|
|
627
|
+
const skillOptions = (0, import_react.useMemo)(() => skills.filter((skill) => skill.enabled && !selectedSkillPaths.has(skill.path)).map((skill) => ({
|
|
628
|
+
value: skill.path,
|
|
629
|
+
label: skill.displayName,
|
|
630
|
+
description: skill.shortDescription ?? skill.description,
|
|
631
|
+
keywords: [skill.name]
|
|
632
|
+
})), [selectedSkillPaths, skills]);
|
|
633
|
+
const fileOptions = (0, import_react.useMemo)(() => fileSearchResults.filter((file) => !selectedFiles.some((selectedFile) => selectedFile.path === file.path)).map((file) => ({
|
|
634
|
+
value: file.path,
|
|
635
|
+
label: file.relativePath,
|
|
636
|
+
description: file.root,
|
|
637
|
+
keywords: [file.name]
|
|
638
|
+
})), [fileSearchResults, selectedFiles]);
|
|
639
|
+
const selectedWorktree = (0, import_react.useMemo)(() => {
|
|
640
|
+
if (!selectedProjectId || !selectedWorktreePath) return null;
|
|
641
|
+
return (worktreesByProject[selectedProjectId] ?? []).find((worktree) => worktree.path === selectedWorktreePath) ?? null;
|
|
642
|
+
}, [
|
|
643
|
+
selectedProjectId,
|
|
644
|
+
selectedWorktreePath,
|
|
645
|
+
worktreesByProject
|
|
646
|
+
]);
|
|
647
|
+
const selectedWorktreeChats = (0, import_react.useMemo)(() => {
|
|
648
|
+
if (!selectedProjectId || !selectedWorktree) return [];
|
|
649
|
+
return dedupeChatThreads((chatsByProject[selectedProjectId] ?? []).filter((chat) => chat.worktreePath === selectedWorktree.path));
|
|
650
|
+
}, [
|
|
651
|
+
chatsByProject,
|
|
652
|
+
selectedProjectId,
|
|
653
|
+
selectedWorktree
|
|
654
|
+
]);
|
|
655
|
+
const visibleMessages = (0, import_react.useMemo)(() => messages.filter((message) => message.role !== "event"), [messages]);
|
|
656
|
+
(0, import_react.useEffect)(() => {
|
|
657
|
+
refreshProjects();
|
|
658
|
+
refreshModels();
|
|
659
|
+
fetchJson("/api/auth").then(() => setStatus("Ready")).catch((err) => setStatus(err.message));
|
|
660
|
+
}, []);
|
|
661
|
+
(0, import_react.useEffect)(() => {
|
|
662
|
+
if (!selectedProjectId) {
|
|
663
|
+
setSelectedChatId(null);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
setExpandedProjectIds((current) => {
|
|
667
|
+
const next = new Set(current);
|
|
668
|
+
next.add(selectedProjectId);
|
|
669
|
+
return next;
|
|
670
|
+
});
|
|
671
|
+
refreshChats(selectedProjectId, { sync: true });
|
|
672
|
+
}, [selectedProjectId]);
|
|
673
|
+
(0, import_react.useEffect)(() => {
|
|
674
|
+
selectedChatIdRef.current = selectedChatId;
|
|
675
|
+
selectedChatVersionRef.current += 1;
|
|
676
|
+
if (!selectedChatId) {
|
|
677
|
+
setMessages([]);
|
|
678
|
+
setPendingApproval(null);
|
|
679
|
+
setSelectedFiles([]);
|
|
680
|
+
setSelectedSkillPaths(/* @__PURE__ */ new Set());
|
|
681
|
+
setFileSearchQuery("");
|
|
682
|
+
setFileSearchResults([]);
|
|
683
|
+
setSkills([]);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
setSelectedFiles([]);
|
|
687
|
+
setSelectedSkillPaths(/* @__PURE__ */ new Set());
|
|
688
|
+
setFileSearchQuery("");
|
|
689
|
+
setFileSearchResults([]);
|
|
690
|
+
setSkills([]);
|
|
691
|
+
const chatContextController = new AbortController();
|
|
692
|
+
refreshMessages(selectedChatId);
|
|
693
|
+
refreshSelectedChat(selectedChatId);
|
|
694
|
+
refreshChatContext(selectedChatId, chatContextController.signal);
|
|
695
|
+
const source = new EventSource(`/api/chats/${selectedChatId}/events`);
|
|
696
|
+
const handleEvent = (event) => {
|
|
697
|
+
const phantomEvent = JSON.parse(event.data);
|
|
698
|
+
if (phantomEvent.type === "agent.approval.requested") {
|
|
699
|
+
const data = phantomEvent.data;
|
|
700
|
+
setPendingApproval(data);
|
|
701
|
+
}
|
|
702
|
+
if (phantomEvent.type === "agent.approval.resolved") setPendingApproval(null);
|
|
703
|
+
refreshMessages(selectedChatId);
|
|
704
|
+
refreshSelectedChat(selectedChatId);
|
|
705
|
+
if (selectedProjectId) refreshChats(selectedProjectId);
|
|
706
|
+
};
|
|
707
|
+
for (const eventName of chatEventNames) source.addEventListener(eventName, handleEvent);
|
|
708
|
+
source.onerror = () => setStatus("Event stream disconnected");
|
|
709
|
+
return () => {
|
|
710
|
+
chatContextController.abort();
|
|
711
|
+
for (const eventName of chatEventNames) source.removeEventListener(eventName, handleEvent);
|
|
712
|
+
source.close();
|
|
713
|
+
};
|
|
714
|
+
}, [selectedChatId, selectedProjectId]);
|
|
715
|
+
(0, import_react.useEffect)(() => {
|
|
716
|
+
if (!selectedEffort || selectedEffort === "auto") return;
|
|
717
|
+
const supportedEfforts = selectedModel?.supportedReasoningEfforts ?? [];
|
|
718
|
+
if (supportedEfforts.length > 0 && !supportedEfforts.includes(selectedEffort)) setSelectedEffort(null);
|
|
719
|
+
}, [selectedEffort, selectedModel]);
|
|
720
|
+
(0, import_react.useEffect)(() => {
|
|
721
|
+
const requestId = fileSearchRequestIdRef.current + 1;
|
|
722
|
+
fileSearchRequestIdRef.current = requestId;
|
|
723
|
+
if (!selectedChatId || !fileSearchQuery.trim()) {
|
|
724
|
+
setFileSearchResults([]);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const controller = new AbortController();
|
|
728
|
+
const query = fileSearchQuery.trim();
|
|
729
|
+
const timeout = setTimeout(() => {
|
|
730
|
+
fetchJson(`/api/chats/${selectedChatId}?fileQuery=${encodeURIComponent(query)}`, { signal: controller.signal }).then((data) => {
|
|
731
|
+
if (controller.signal.aborted || fileSearchRequestIdRef.current !== requestId) return;
|
|
732
|
+
setFileSearchResults(data.files);
|
|
733
|
+
}).catch((err) => {
|
|
734
|
+
if (err.name !== "AbortError") setError(err.message);
|
|
735
|
+
});
|
|
736
|
+
}, 160);
|
|
737
|
+
return () => {
|
|
738
|
+
clearTimeout(timeout);
|
|
739
|
+
controller.abort();
|
|
740
|
+
};
|
|
741
|
+
}, [fileSearchQuery, selectedChatId]);
|
|
742
|
+
(0, import_react.useEffect)(() => {
|
|
743
|
+
if (selectedWorktreeChats.length === 0 || selectedWorktreeChats.some((chat) => chat.id === selectedChatId)) return;
|
|
744
|
+
setSelectedChatId(selectedWorktreeChats[0]?.id ?? null);
|
|
745
|
+
}, [selectedChatId, selectedWorktreeChats]);
|
|
746
|
+
async function refreshProjects() {
|
|
747
|
+
const data = await fetchJson("/api/projects");
|
|
748
|
+
setProjects(data.projects);
|
|
749
|
+
const projectDataEntries = await Promise.all(data.projects.map(async (project) => [project.id, await loadProjectData(project.id, { sync: true })]));
|
|
750
|
+
const nextChatsByProject = Object.fromEntries(projectDataEntries.map(([projectId, projectData]) => [projectId, projectData.chats]));
|
|
751
|
+
const nextWorktreesByProject = Object.fromEntries(projectDataEntries.map(([projectId, projectData]) => [projectId, projectData.worktrees]));
|
|
752
|
+
setChatsByProject(nextChatsByProject);
|
|
753
|
+
setWorktreesByProject(nextWorktreesByProject);
|
|
754
|
+
const fallbackProjectId = selectedProjectId ?? data.projects[0]?.id ?? null;
|
|
755
|
+
const fallbackWorktree = firstProjectWorktree(fallbackProjectId, nextWorktreesByProject);
|
|
756
|
+
setSelectedProjectId((current) => {
|
|
757
|
+
const nextProjectId = current ?? data.projects[0]?.id ?? null;
|
|
758
|
+
if (nextProjectId) setExpandedProjectIds((expanded) => {
|
|
759
|
+
const next = new Set(expanded);
|
|
760
|
+
next.add(nextProjectId);
|
|
761
|
+
return next;
|
|
762
|
+
});
|
|
763
|
+
return nextProjectId;
|
|
764
|
+
});
|
|
765
|
+
setSelectedWorktreePath((current) => {
|
|
766
|
+
if (fallbackProjectId && current && (nextWorktreesByProject[fallbackProjectId] ?? []).some((worktree) => worktree.path === current)) return current;
|
|
767
|
+
return fallbackWorktree?.path ?? null;
|
|
768
|
+
});
|
|
769
|
+
setSelectedChatId((current) => Object.values(nextChatsByProject).flat().some((chat) => chat.id === current) ? current : fallbackWorktree?.chatId ?? null);
|
|
770
|
+
}
|
|
771
|
+
async function refreshModels() {
|
|
772
|
+
try {
|
|
773
|
+
const data = await fetchJson("/api/models");
|
|
774
|
+
setModels(data.models);
|
|
775
|
+
setSelectedModelId((current) => {
|
|
776
|
+
if (current && data.models.some((model) => model.id === current)) return current;
|
|
777
|
+
return data.models.find((model) => model.isDefault)?.id ?? data.models[0]?.id ?? null;
|
|
778
|
+
});
|
|
779
|
+
} catch (err) {
|
|
780
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
async function refreshChatContext(chatId, signal) {
|
|
784
|
+
try {
|
|
785
|
+
const data = await fetchJson(`/api/chats/${chatId}?context=skills`, { signal });
|
|
786
|
+
if (signal?.aborted) return;
|
|
787
|
+
setSkills(data.skills);
|
|
788
|
+
} catch (err) {
|
|
789
|
+
if (err instanceof Error && err.name === "AbortError") return;
|
|
790
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
async function loadProjectData(projectId, options = {}) {
|
|
794
|
+
return await fetchJson(`/api/projects/${projectId}/chats${options.sync ? "?sync=1" : ""}`);
|
|
795
|
+
}
|
|
796
|
+
async function refreshChats(projectId, options = {}) {
|
|
797
|
+
const projectData = await loadProjectData(projectId, options);
|
|
798
|
+
setChatsByProject((current) => ({
|
|
799
|
+
...current,
|
|
800
|
+
[projectId]: projectData.chats
|
|
801
|
+
}));
|
|
802
|
+
setWorktreesByProject((current) => ({
|
|
803
|
+
...current,
|
|
804
|
+
[projectId]: projectData.worktrees
|
|
805
|
+
}));
|
|
806
|
+
const fallbackWorktree = firstProjectWorktree(projectId, {
|
|
807
|
+
...worktreesByProject,
|
|
808
|
+
[projectId]: projectData.worktrees
|
|
809
|
+
});
|
|
810
|
+
setSelectedWorktreePath((current) => current && projectData.worktrees.some((worktree) => worktree.path === current) ? current : fallbackWorktree?.path ?? null);
|
|
811
|
+
setSelectedChatId((current) => projectData.chats.some((chat) => chat.id === current) ? current : fallbackWorktree?.chatId ?? null);
|
|
812
|
+
}
|
|
813
|
+
async function refreshSelectedChat(chatId) {
|
|
814
|
+
const data = await fetchJson(`/api/chats/${chatId}`);
|
|
815
|
+
setChatsByProject((current) => {
|
|
816
|
+
const projectChats = current[data.chat.projectId] ?? [];
|
|
817
|
+
return {
|
|
818
|
+
...current,
|
|
819
|
+
[data.chat.projectId]: projectChats.map((chat) => chat.id === chatId ? data.chat : chat)
|
|
820
|
+
};
|
|
821
|
+
});
|
|
822
|
+
setWorktreesByProject((current) => ({
|
|
823
|
+
...current,
|
|
824
|
+
[data.chat.projectId]: (current[data.chat.projectId] ?? []).map((worktree) => worktree.chatId === chatId ? {
|
|
825
|
+
...worktree,
|
|
826
|
+
chatStatus: data.chat.status,
|
|
827
|
+
chatTitle: data.chat.title
|
|
828
|
+
} : worktree)
|
|
829
|
+
}));
|
|
830
|
+
}
|
|
831
|
+
async function refreshMessages(chatId) {
|
|
832
|
+
setMessages((await fetchJson(`/api/chats/${chatId}/messages`)).messages);
|
|
833
|
+
}
|
|
834
|
+
async function addProject(event) {
|
|
835
|
+
event.preventDefault();
|
|
836
|
+
const trimmedProjectPath = projectPath.trim();
|
|
837
|
+
if (!trimmedProjectPath) return;
|
|
838
|
+
setError(null);
|
|
839
|
+
setIsBusy(true);
|
|
840
|
+
try {
|
|
841
|
+
const data = await fetchJson("/api/projects", {
|
|
842
|
+
method: "POST",
|
|
843
|
+
body: JSON.stringify({ path: trimmedProjectPath })
|
|
844
|
+
});
|
|
845
|
+
setProjectPath("");
|
|
846
|
+
setIsAddProjectOpen(false);
|
|
847
|
+
await refreshProjects();
|
|
848
|
+
setSelectedProjectId(data.project.id);
|
|
849
|
+
} catch (err) {
|
|
850
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
851
|
+
} finally {
|
|
852
|
+
setIsBusy(false);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
async function createChat(projectId) {
|
|
856
|
+
if (isBusy || createChatInFlightRef.current) return;
|
|
857
|
+
setError(null);
|
|
858
|
+
createChatInFlightRef.current = true;
|
|
859
|
+
setIsBusy(true);
|
|
860
|
+
try {
|
|
861
|
+
const data = await fetchJson(`/api/projects/${projectId}/chats`, {
|
|
862
|
+
method: "POST",
|
|
863
|
+
body: JSON.stringify({})
|
|
864
|
+
});
|
|
865
|
+
setSelectedProjectId(projectId);
|
|
866
|
+
setExpandedProjectIds((current) => new Set(current).add(projectId));
|
|
867
|
+
await refreshChats(projectId, { sync: true });
|
|
868
|
+
setSelectedWorktreePath(data.chat.worktreePath);
|
|
869
|
+
setSelectedChatId(data.chat.id);
|
|
870
|
+
} catch (err) {
|
|
871
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
872
|
+
} finally {
|
|
873
|
+
createChatInFlightRef.current = false;
|
|
874
|
+
setIsBusy(false);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
async function sendMessage(event) {
|
|
878
|
+
event.preventDefault();
|
|
879
|
+
if (!selectedChatId || !composerText.trim()) return;
|
|
880
|
+
setError(null);
|
|
881
|
+
const requestChatId = selectedChatId;
|
|
882
|
+
const requestChatVersion = selectedChatVersionRef.current;
|
|
883
|
+
const requestId = sendMessageRequestIdRef.current + 1;
|
|
884
|
+
sendMessageRequestIdRef.current = requestId;
|
|
885
|
+
const isCurrentSendRequest = () => selectedChatIdRef.current === requestChatId && selectedChatVersionRef.current === requestChatVersion && sendMessageRequestIdRef.current === requestId;
|
|
886
|
+
const text = composerText;
|
|
887
|
+
setComposerText("");
|
|
888
|
+
const turnModel = selectedModel?.id ?? selectedModel?.model;
|
|
889
|
+
const turnEffort = selectedEffort === "auto" ? null : selectedEffort;
|
|
890
|
+
const files = selectedFiles.map((file) => ({
|
|
891
|
+
name: file.relativePath,
|
|
892
|
+
path: file.path
|
|
893
|
+
}));
|
|
894
|
+
const selectedSkillItems = selectedSkills.map((skill) => ({
|
|
895
|
+
name: skill.name,
|
|
896
|
+
path: skill.path
|
|
897
|
+
}));
|
|
898
|
+
try {
|
|
899
|
+
await fetchJson(`/api/chats/${requestChatId}/messages`, {
|
|
900
|
+
method: "POST",
|
|
901
|
+
body: JSON.stringify({
|
|
902
|
+
effort: turnEffort,
|
|
903
|
+
files,
|
|
904
|
+
model: turnModel,
|
|
905
|
+
skills: selectedSkillItems,
|
|
906
|
+
text
|
|
907
|
+
})
|
|
908
|
+
});
|
|
909
|
+
if (!isCurrentSendRequest()) return;
|
|
910
|
+
setSelectedFiles([]);
|
|
911
|
+
setSelectedSkillPaths(/* @__PURE__ */ new Set());
|
|
912
|
+
setFileSearchQuery("");
|
|
913
|
+
await refreshMessages(requestChatId);
|
|
914
|
+
} catch (err) {
|
|
915
|
+
if (!isCurrentSendRequest()) return;
|
|
916
|
+
setComposerText(text);
|
|
917
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
function handleComposerKeyDown(event) {
|
|
921
|
+
const isImeComposing = event.nativeEvent.isComposing || event.keyCode === 229;
|
|
922
|
+
if (event.key !== "Enter" || event.shiftKey || event.metaKey || event.ctrlKey || event.altKey || isImeComposing) return;
|
|
923
|
+
if (!selectedChatId || !composerText.trim() || isChatRunning) return;
|
|
924
|
+
event.preventDefault();
|
|
925
|
+
event.currentTarget.form?.requestSubmit();
|
|
926
|
+
}
|
|
927
|
+
async function interruptChat() {
|
|
928
|
+
if (!selectedChatId) return;
|
|
929
|
+
setError(null);
|
|
930
|
+
try {
|
|
931
|
+
await fetchJson(`/api/chats/${selectedChatId}/interrupt`, { method: "POST" });
|
|
932
|
+
} catch (err) {
|
|
933
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
async function answerApproval(decision) {
|
|
937
|
+
if (!selectedChatId || !pendingApproval) return;
|
|
938
|
+
setError(null);
|
|
939
|
+
try {
|
|
940
|
+
await fetchJson(`/api/chats/${encodeURIComponent(selectedChatId)}/approvals/${encodeURIComponent(pendingApproval.requestId)}`, {
|
|
941
|
+
method: "POST",
|
|
942
|
+
body: JSON.stringify({ decision })
|
|
943
|
+
});
|
|
944
|
+
setPendingApproval(null);
|
|
945
|
+
} catch (err) {
|
|
946
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function toggleProject(projectId) {
|
|
950
|
+
setExpandedProjectIds((current) => {
|
|
951
|
+
const next = new Set(current);
|
|
952
|
+
if (next.has(projectId)) next.delete(projectId);
|
|
953
|
+
else next.add(projectId);
|
|
954
|
+
return next;
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
function selectWorktree(projectId, worktree) {
|
|
958
|
+
setSelectedProjectId(projectId);
|
|
959
|
+
setSelectedWorktreePath(worktree.path);
|
|
960
|
+
setSelectedChatId(worktree.chatId);
|
|
961
|
+
setExpandedProjectIds((current) => new Set(current).add(projectId));
|
|
962
|
+
}
|
|
963
|
+
function selectFile(path) {
|
|
964
|
+
const file = fileSearchResults.find((candidate) => candidate.path === path);
|
|
965
|
+
if (!file) return;
|
|
966
|
+
setSelectedFiles((current) => current.some((selectedFile) => selectedFile.path === file.path) ? current : [...current, file]);
|
|
967
|
+
setFileSearchQuery("");
|
|
968
|
+
}
|
|
969
|
+
function selectSkill(path) {
|
|
970
|
+
setSelectedSkillPaths((current) => new Set(current).add(path));
|
|
971
|
+
}
|
|
972
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SidebarProvider, {
|
|
973
|
+
className: "h-screen min-h-0",
|
|
974
|
+
children: [
|
|
975
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Sidebar, {
|
|
976
|
+
collapsible: "offcanvas",
|
|
977
|
+
variant: "inset",
|
|
978
|
+
children: [
|
|
979
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SidebarHeader, { children: [
|
|
980
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
981
|
+
className: "flex size-8 shrink-0 items-center justify-center rounded-[var(--radius-sm)] bg-[var(--color-gray-900)] text-primary-foreground",
|
|
982
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderGit2, { className: "size-4" })
|
|
983
|
+
}),
|
|
984
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
985
|
+
className: "min-w-0 flex-1 group-data-[state=collapsed]/sidebar:hidden",
|
|
986
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
|
|
987
|
+
className: "truncate text-[length:var(--font-size-lg)] font-semibold leading-tight",
|
|
988
|
+
children: "Phantom"
|
|
989
|
+
})
|
|
990
|
+
}),
|
|
991
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
|
|
992
|
+
className: "max-w-24 truncate group-data-[state=collapsed]/sidebar:hidden",
|
|
993
|
+
variant: status === "Ready" ? "success" : "warning",
|
|
994
|
+
children: status
|
|
995
|
+
})
|
|
996
|
+
] }),
|
|
997
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SidebarGroup, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SidebarGroupHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarGroupLabel, { children: "Projects" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarGroupAction, {
|
|
998
|
+
"aria-label": "Add project",
|
|
999
|
+
onClick: () => setIsAddProjectOpen(true),
|
|
1000
|
+
title: "Add project",
|
|
1001
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, { className: "size-4" })
|
|
1002
|
+
})] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarGroupContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarMenu, { children: projects.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", {
|
|
1003
|
+
className: "px-2 py-4 group-data-[state=collapsed]/sidebar:hidden",
|
|
1004
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1005
|
+
className: "rounded-[var(--radius-md)] border border-dashed border-sidebar-border bg-[var(--surface-card)] px-3 py-3 text-[length:var(--font-size-sm)] text-muted-foreground",
|
|
1006
|
+
children: "Add a Git project to begin."
|
|
1007
|
+
})
|
|
1008
|
+
}) : projects.map((project) => {
|
|
1009
|
+
const isProjectExpanded = expandedProjectIds.has(project.id);
|
|
1010
|
+
const projectWorktrees = worktreesByProject[project.id] ?? [];
|
|
1011
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SidebarMenuItem, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1012
|
+
className: "group/project flex items-center rounded-[var(--radius-sm)]",
|
|
1013
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SidebarMenuButton, {
|
|
1014
|
+
"aria-expanded": isProjectExpanded,
|
|
1015
|
+
className: "min-h-8 flex-1 group-data-[state=collapsed]/sidebar:flex-none",
|
|
1016
|
+
onClick: () => toggleProject(project.id),
|
|
1017
|
+
title: project.name,
|
|
1018
|
+
type: "button",
|
|
1019
|
+
children: [
|
|
1020
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: cn("size-4 shrink-0 text-[var(--icon-color-default)] transition-transform duration-[var(--motion-duration-fast)] group-data-[state=collapsed]/sidebar:hidden", isProjectExpanded && "rotate-90") }),
|
|
1021
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderGit2, { className: "size-4 text-[var(--icon-color-default)]" }),
|
|
1022
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1023
|
+
className: "min-w-0 flex-1 group-data-[state=collapsed]/sidebar:hidden",
|
|
1024
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1025
|
+
className: "block truncate font-medium",
|
|
1026
|
+
children: project.name
|
|
1027
|
+
})
|
|
1028
|
+
})
|
|
1029
|
+
]
|
|
1030
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
1031
|
+
"aria-label": `Create worktree in ${project.name}`,
|
|
1032
|
+
className: "mr-1 size-7 text-[var(--icon-color-default)] group-data-[state=collapsed]/sidebar:hidden",
|
|
1033
|
+
disabled: isBusy,
|
|
1034
|
+
onClick: () => void createChat(project.id),
|
|
1035
|
+
size: "icon",
|
|
1036
|
+
title: "Create worktree",
|
|
1037
|
+
type: "button",
|
|
1038
|
+
variant: "ghost",
|
|
1039
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageSquarePlus, { className: "size-4" })
|
|
1040
|
+
})]
|
|
1041
|
+
}), isProjectExpanded && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarMenuSub, { children: projectWorktrees.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", {
|
|
1042
|
+
className: "px-2 py-1.5 text-[length:var(--font-size-xs)] text-[var(--text-tertiary)]",
|
|
1043
|
+
children: "No worktrees"
|
|
1044
|
+
}) : projectWorktrees.map((worktree) => {
|
|
1045
|
+
const title = `${worktree.name} (${worktree.path})${worktree.isClean ? "" : " [dirty]"}`;
|
|
1046
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarMenuSubItem, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SidebarMenuSubButton, {
|
|
1047
|
+
disabled: !worktree.chatId,
|
|
1048
|
+
isActive: worktree.path === selectedWorktreePath,
|
|
1049
|
+
onClick: () => selectWorktree(project.id, worktree),
|
|
1050
|
+
title,
|
|
1051
|
+
type: "button",
|
|
1052
|
+
children: [
|
|
1053
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitBranch, { className: "size-3.5 text-[var(--icon-color-default)]" }),
|
|
1054
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1055
|
+
className: "min-w-0 flex-1",
|
|
1056
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1057
|
+
className: "block truncate font-medium",
|
|
1058
|
+
children: worktree.name
|
|
1059
|
+
})
|
|
1060
|
+
}),
|
|
1061
|
+
!worktree.isClean && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "size-1.5 shrink-0 rounded-full bg-[var(--semantic-warning-fg)]" })
|
|
1062
|
+
]
|
|
1063
|
+
}) }, worktree.path);
|
|
1064
|
+
}) })] }, project.id);
|
|
1065
|
+
}) }) })] }) }),
|
|
1066
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarRail, {})
|
|
1067
|
+
]
|
|
1068
|
+
}),
|
|
1069
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog, {
|
|
1070
|
+
open: isAddProjectOpen,
|
|
1071
|
+
onOpenChange: setIsAddProjectOpen,
|
|
1072
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
|
|
1073
|
+
"aria-labelledby": "add-project-title",
|
|
1074
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, {
|
|
1075
|
+
id: "add-project-title",
|
|
1076
|
+
children: "Add project"
|
|
1077
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogDescription, { children: "Add a local Git project to the Phantom sidebar." })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
|
|
1078
|
+
className: "grid gap-4",
|
|
1079
|
+
onSubmit: addProject,
|
|
1080
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1081
|
+
className: "grid gap-2",
|
|
1082
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
|
|
1083
|
+
htmlFor: "project-path",
|
|
1084
|
+
children: "Project path"
|
|
1085
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
|
|
1086
|
+
id: "project-path",
|
|
1087
|
+
placeholder: "/Users/me/project",
|
|
1088
|
+
value: projectPath,
|
|
1089
|
+
onChange: (event) => setProjectPath(event.target.value)
|
|
1090
|
+
})]
|
|
1091
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
1092
|
+
onClick: () => setIsAddProjectOpen(false),
|
|
1093
|
+
type: "button",
|
|
1094
|
+
variant: "outline",
|
|
1095
|
+
children: "Cancel"
|
|
1096
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
1097
|
+
disabled: isBusy || !projectPath.trim(),
|
|
1098
|
+
type: "submit",
|
|
1099
|
+
children: "Add project"
|
|
1100
|
+
})] })]
|
|
1101
|
+
})]
|
|
1102
|
+
})
|
|
1103
|
+
}),
|
|
1104
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SidebarInset, { children: [
|
|
1105
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
|
|
1106
|
+
className: "flex min-h-[var(--layout-topbar-height)] items-center gap-3 border-b border-border bg-[var(--surface-panel)] px-4",
|
|
1107
|
+
children: [
|
|
1108
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarTrigger, { className: "-ml-1" }),
|
|
1109
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-5 w-px bg-[var(--border-divider)]" }),
|
|
1110
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1111
|
+
className: "min-w-0 flex-1",
|
|
1112
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1113
|
+
className: "flex min-w-0 items-center gap-2",
|
|
1114
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
1115
|
+
className: "truncate text-[length:var(--font-size-xl)] font-semibold leading-tight",
|
|
1116
|
+
children: selectedWorktree?.name ?? selectedProject?.name ?? "Workspace"
|
|
1117
|
+
}), selectedChat && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusBadge, { status: selectedChat.status })]
|
|
1118
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
1119
|
+
className: "flex min-w-0 text-[length:var(--font-size-xs)] text-muted-foreground",
|
|
1120
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1121
|
+
className: "shrink-0",
|
|
1122
|
+
children: [selectedProject?.name ?? "No project selected", selectedWorktree ? " / " : ""]
|
|
1123
|
+
}), selectedWorktree && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LeadingEllipsisText, { text: selectedWorktree.path })]
|
|
1124
|
+
})]
|
|
1125
|
+
})
|
|
1126
|
+
]
|
|
1127
|
+
}),
|
|
1128
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SystemBanner, {
|
|
1129
|
+
tone: "danger",
|
|
1130
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, { className: "size-4" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: error })]
|
|
1131
|
+
}),
|
|
1132
|
+
pendingApproval && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1133
|
+
className: "border-b border-[var(--semantic-warning-border)] bg-[var(--semantic-warning-bg)] px-4 py-3 text-[var(--semantic-warning-fg)]",
|
|
1134
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1135
|
+
className: "mx-auto flex max-w-[var(--layout-max-content-width)] flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
|
|
1136
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1137
|
+
className: "min-w-0",
|
|
1138
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
1139
|
+
className: "flex items-center gap-2 text-[length:var(--font-size-md)] font-semibold",
|
|
1140
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Clock3, { className: "size-4" }), "Approval requested"]
|
|
1141
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
1142
|
+
className: "mt-1 truncate font-mono text-[length:var(--font-size-xs)]",
|
|
1143
|
+
children: pendingApproval.method
|
|
1144
|
+
})]
|
|
1145
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1146
|
+
className: "flex shrink-0 flex-wrap gap-2",
|
|
1147
|
+
children: [
|
|
1148
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
1149
|
+
onClick: () => void answerApproval("accept"),
|
|
1150
|
+
size: "sm",
|
|
1151
|
+
type: "button",
|
|
1152
|
+
children: "Accept"
|
|
1153
|
+
}),
|
|
1154
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
1155
|
+
onClick: () => void answerApproval("acceptForSession"),
|
|
1156
|
+
size: "sm",
|
|
1157
|
+
type: "button",
|
|
1158
|
+
variant: "outline",
|
|
1159
|
+
children: "Accept for session"
|
|
1160
|
+
}),
|
|
1161
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
1162
|
+
onClick: () => void answerApproval("decline"),
|
|
1163
|
+
size: "sm",
|
|
1164
|
+
type: "button",
|
|
1165
|
+
variant: "outline",
|
|
1166
|
+
children: "Decline"
|
|
1167
|
+
})
|
|
1168
|
+
]
|
|
1169
|
+
})]
|
|
1170
|
+
})
|
|
1171
|
+
}),
|
|
1172
|
+
selectedWorktree && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChatHistoryBar, {
|
|
1173
|
+
chats: selectedWorktreeChats,
|
|
1174
|
+
selectedChatId,
|
|
1175
|
+
onSelectChat: setSelectedChatId
|
|
1176
|
+
}),
|
|
1177
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("section", {
|
|
1178
|
+
className: "min-h-0 flex-1 overflow-y-auto px-4 py-4",
|
|
1179
|
+
children: visibleMessages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyTimeline, {
|
|
1180
|
+
hasChat: Boolean(selectedChat),
|
|
1181
|
+
hasWorktree: Boolean(selectedWorktree),
|
|
1182
|
+
selectedProject,
|
|
1183
|
+
onOpenProjectDialog: () => setIsAddProjectOpen(true)
|
|
1184
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1185
|
+
className: "mx-auto flex max-w-[var(--layout-max-content-width)] flex-col gap-2",
|
|
1186
|
+
children: visibleMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageCard, { message }, message.id))
|
|
1187
|
+
})
|
|
1188
|
+
}),
|
|
1189
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("form", {
|
|
1190
|
+
className: "border-t border-border bg-[var(--surface-floating)] p-3 backdrop-blur",
|
|
1191
|
+
onSubmit: sendMessage,
|
|
1192
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1193
|
+
className: "mx-auto flex max-w-[var(--layout-max-content-width)] flex-col gap-2",
|
|
1194
|
+
children: [
|
|
1195
|
+
(selectedFiles.length > 0 || selectedSkills.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1196
|
+
className: "flex min-h-8 flex-wrap items-center gap-2 px-1",
|
|
1197
|
+
children: [selectedFiles.map((file) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContextChip, {
|
|
1198
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileText, { className: "size-3.5" }),
|
|
1199
|
+
label: file.relativePath,
|
|
1200
|
+
onRemove: () => setSelectedFiles((current) => current.filter((selectedFile) => selectedFile.path !== file.path))
|
|
1201
|
+
}, file.path)), selectedSkills.map((skill) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContextChip, {
|
|
1202
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sparkles, { className: "size-3.5" }),
|
|
1203
|
+
label: skill.displayName,
|
|
1204
|
+
onRemove: () => setSelectedSkillPaths((current) => {
|
|
1205
|
+
const next = new Set(current);
|
|
1206
|
+
next.delete(skill.path);
|
|
1207
|
+
return next;
|
|
1208
|
+
})
|
|
1209
|
+
}, skill.path))]
|
|
1210
|
+
}),
|
|
1211
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1212
|
+
className: "flex items-end gap-2",
|
|
1213
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1214
|
+
className: "min-w-0 flex-1",
|
|
1215
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
|
|
1216
|
+
className: "sr-only",
|
|
1217
|
+
htmlFor: "composer",
|
|
1218
|
+
children: "Message"
|
|
1219
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Textarea, {
|
|
1220
|
+
className: "min-h-12 border-0 bg-transparent px-2 py-2 shadow-none focus-visible:shadow-none",
|
|
1221
|
+
disabled: !selectedChatId,
|
|
1222
|
+
id: "composer",
|
|
1223
|
+
placeholder: selectedChatId ? "Ask Codex to work in this worktree" : "Create or select a worktree to start",
|
|
1224
|
+
rows: 2,
|
|
1225
|
+
value: composerText,
|
|
1226
|
+
onChange: (event) => setComposerText(event.target.value),
|
|
1227
|
+
onKeyDown: handleComposerKeyDown
|
|
1228
|
+
})]
|
|
1229
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
1230
|
+
"aria-label": isChatRunning ? "Stop turn" : "Send message",
|
|
1231
|
+
className: "size-10",
|
|
1232
|
+
disabled: isChatRunning ? !selectedChat?.activeTurnId : !selectedChatId || !composerText.trim(),
|
|
1233
|
+
onClick: isChatRunning ? interruptChat : void 0,
|
|
1234
|
+
size: "icon",
|
|
1235
|
+
title: isChatRunning ? "Stop turn" : "Send",
|
|
1236
|
+
type: isChatRunning ? "button" : "submit",
|
|
1237
|
+
variant: isChatRunning ? "destructive" : "default",
|
|
1238
|
+
children: isChatRunning ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Square, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Send, {})
|
|
1239
|
+
})]
|
|
1240
|
+
}),
|
|
1241
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1242
|
+
className: "flex min-h-8 flex-wrap items-center gap-2 border-t border-[var(--border-divider)] px-1 pt-2",
|
|
1243
|
+
children: [
|
|
1244
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Combobox, {
|
|
1245
|
+
"aria-label": "Select model",
|
|
1246
|
+
className: "w-36 max-w-full sm:w-40",
|
|
1247
|
+
disabled: models.length === 0 || isChatRunning,
|
|
1248
|
+
emptyMessage: "No models",
|
|
1249
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Bot, { className: "size-3.5" }),
|
|
1250
|
+
options: modelOptions,
|
|
1251
|
+
placeholder: "Model",
|
|
1252
|
+
searchPlaceholder: "Search models",
|
|
1253
|
+
side: "top",
|
|
1254
|
+
triggerClassName: "w-full justify-between",
|
|
1255
|
+
value: selectedModel?.id ?? null,
|
|
1256
|
+
onValueChange: setSelectedModelId
|
|
1257
|
+
}),
|
|
1258
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Combobox, {
|
|
1259
|
+
"aria-label": "Select reasoning effort",
|
|
1260
|
+
className: "w-28 max-w-full",
|
|
1261
|
+
disabled: !selectedModel || isChatRunning,
|
|
1262
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Brain, { className: "size-3.5" }),
|
|
1263
|
+
options: effortOptions,
|
|
1264
|
+
placeholder: "Effort",
|
|
1265
|
+
searchPlaceholder: "Search effort",
|
|
1266
|
+
side: "top",
|
|
1267
|
+
triggerClassName: "w-full justify-between",
|
|
1268
|
+
value: selectedEffort ?? "auto",
|
|
1269
|
+
onValueChange: (value) => setSelectedEffort(value === "auto" ? null : value)
|
|
1270
|
+
}),
|
|
1271
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Combobox, {
|
|
1272
|
+
"aria-label": "Attach file",
|
|
1273
|
+
className: "w-32 max-w-full",
|
|
1274
|
+
disabled: !selectedChatId || isChatRunning,
|
|
1275
|
+
emptyMessage: fileSearchQuery.trim() ? "No files" : "Type to search",
|
|
1276
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileText, { className: "size-3.5" }),
|
|
1277
|
+
options: fileOptions,
|
|
1278
|
+
placeholder: "Files",
|
|
1279
|
+
query: fileSearchQuery,
|
|
1280
|
+
searchPlaceholder: "Search files",
|
|
1281
|
+
shouldFilter: false,
|
|
1282
|
+
side: "top",
|
|
1283
|
+
triggerClassName: "w-full justify-between",
|
|
1284
|
+
value: null,
|
|
1285
|
+
onQueryChange: setFileSearchQuery,
|
|
1286
|
+
onValueChange: selectFile
|
|
1287
|
+
}),
|
|
1288
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Combobox, {
|
|
1289
|
+
"aria-label": "Select skill",
|
|
1290
|
+
align: "end",
|
|
1291
|
+
className: "w-32 max-w-full",
|
|
1292
|
+
disabled: !selectedChatId || isChatRunning,
|
|
1293
|
+
emptyMessage: "No skills",
|
|
1294
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sparkles, { className: "size-3.5" }),
|
|
1295
|
+
options: skillOptions,
|
|
1296
|
+
placeholder: "Skills",
|
|
1297
|
+
searchPlaceholder: "Search skills",
|
|
1298
|
+
side: "top",
|
|
1299
|
+
triggerClassName: "w-full justify-between",
|
|
1300
|
+
value: null,
|
|
1301
|
+
onValueChange: selectSkill
|
|
1302
|
+
})
|
|
1303
|
+
]
|
|
1304
|
+
})
|
|
1305
|
+
]
|
|
1306
|
+
})
|
|
1307
|
+
})
|
|
1308
|
+
] })
|
|
1309
|
+
]
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
function ChatHistoryBar({ chats, onSelectChat, selectedChatId }) {
|
|
1313
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1314
|
+
className: "border-b border-border bg-[var(--surface-panel)] px-4 py-2",
|
|
1315
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1316
|
+
className: "mx-auto flex max-w-[var(--layout-max-content-width)] items-center gap-2",
|
|
1317
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1318
|
+
className: "shrink-0 text-[length:var(--font-size-xs)] font-medium text-[var(--text-secondary)]",
|
|
1319
|
+
children: "Chat history"
|
|
1320
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1321
|
+
"aria-label": "Chat history",
|
|
1322
|
+
className: "flex min-w-0 flex-1 gap-1 overflow-x-auto",
|
|
1323
|
+
role: "tablist",
|
|
1324
|
+
children: chats.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1325
|
+
className: "px-2 py-1 text-[length:var(--font-size-xs)] text-[var(--text-tertiary)]",
|
|
1326
|
+
children: "No chat history"
|
|
1327
|
+
}) : chats.map((chat) => {
|
|
1328
|
+
const isSelected = chat.id === selectedChatId;
|
|
1329
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
1330
|
+
"aria-selected": isSelected,
|
|
1331
|
+
className: cn("inline-flex max-w-44 shrink-0 items-center gap-1.5 rounded-[var(--radius-sm)] px-2 py-1 text-[length:var(--font-size-xs)] outline-none transition-colors hover:bg-sidebar-accent focus-visible:shadow-[var(--state-focus-ring)]", isSelected ? "bg-sidebar-accent text-sidebar-accent-foreground" : "text-[var(--text-secondary)]"),
|
|
1332
|
+
onClick: () => onSelectChat(chat.id),
|
|
1333
|
+
role: "tab",
|
|
1334
|
+
title: chat.title,
|
|
1335
|
+
type: "button",
|
|
1336
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageSquare, { className: "size-3.5 shrink-0" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1337
|
+
className: "truncate",
|
|
1338
|
+
children: chat.title
|
|
1339
|
+
})]
|
|
1340
|
+
}, chat.id);
|
|
1341
|
+
})
|
|
1342
|
+
})]
|
|
1343
|
+
})
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
function StatusBadge({ status }) {
|
|
1347
|
+
const meta = statusMeta[status];
|
|
1348
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
|
|
1349
|
+
variant: meta.badge,
|
|
1350
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: cn("size-1.5 rounded-full", meta.dot) }), meta.label]
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
function LeadingEllipsisText({ text }) {
|
|
1354
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1355
|
+
className: "block min-w-0 truncate",
|
|
1356
|
+
title: text,
|
|
1357
|
+
children: formatLeadingEllipsisPath(text)
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
function ContextChip({ icon, label, onRemove }) {
|
|
1361
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1362
|
+
className: "inline-flex h-8 max-w-52 items-center gap-1.5 rounded-[var(--radius-sm)] border border-[var(--border-divider)] bg-[var(--surface-code)] px-2 text-[length:var(--font-size-sm)] text-[var(--text-secondary)]",
|
|
1363
|
+
children: [
|
|
1364
|
+
icon,
|
|
1365
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1366
|
+
className: "min-w-0 truncate",
|
|
1367
|
+
children: label
|
|
1368
|
+
}),
|
|
1369
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1370
|
+
"aria-label": `Remove ${label}`,
|
|
1371
|
+
className: "rounded-[var(--radius-xs)] text-[var(--icon-color-muted)] outline-none transition-colors hover:bg-[var(--state-hover-bg)] hover:text-[var(--icon-color-active)] focus-visible:shadow-[var(--state-focus-ring)]",
|
|
1372
|
+
onClick: onRemove,
|
|
1373
|
+
title: `Remove ${label}`,
|
|
1374
|
+
type: "button",
|
|
1375
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "size-3.5" })
|
|
1376
|
+
})
|
|
1377
|
+
]
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
function formatReasoningEffort(effort) {
|
|
1381
|
+
return effort.split(/[-_\s]+/).filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join(" ");
|
|
1382
|
+
}
|
|
1383
|
+
function SystemBanner({ children, tone }) {
|
|
1384
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1385
|
+
className: cn("border-b px-4 py-2 text-[length:var(--font-size-sm)]", tone === "danger" ? "border-[var(--semantic-danger-border)] bg-[var(--semantic-danger-bg)] text-[var(--semantic-danger-fg)]" : "border-[var(--semantic-info-border)] bg-[var(--semantic-info-bg)] text-[var(--semantic-info-fg)]"),
|
|
1386
|
+
role: "status",
|
|
1387
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1388
|
+
className: "mx-auto flex max-w-[var(--layout-max-content-width)] items-center gap-2",
|
|
1389
|
+
children
|
|
1390
|
+
})
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
function EmptyTimeline({ hasChat, hasWorktree, onOpenProjectDialog, selectedProject }) {
|
|
1394
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1395
|
+
className: "mx-auto flex h-full max-w-[var(--layout-max-content-width)] items-center justify-center py-8",
|
|
1396
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
|
|
1397
|
+
className: "grid w-full max-w-xl gap-4 px-5 py-6 text-center",
|
|
1398
|
+
children: [
|
|
1399
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1400
|
+
className: "mx-auto flex size-10 items-center justify-center rounded-[var(--radius-md)] bg-[var(--surface-code)] text-[var(--icon-color-default)]",
|
|
1401
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Inbox, { className: "size-5" })
|
|
1402
|
+
}),
|
|
1403
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
|
|
1404
|
+
className: "text-[length:var(--font-size-xl)] font-semibold",
|
|
1405
|
+
children: hasChat ? "No messages yet" : hasWorktree ? "Select chat history" : "Select a worktree"
|
|
1406
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
1407
|
+
className: "mt-1 text-[length:var(--font-size-md)] text-muted-foreground",
|
|
1408
|
+
children: hasChat ? "Send a message to start a focused Codex session." : hasWorktree ? "Choose a chat history for this worktree." : "Create a worktree under a project to begin a Codex session."
|
|
1409
|
+
})] }),
|
|
1410
|
+
!selectedProject && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1411
|
+
className: "flex justify-center gap-2",
|
|
1412
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
|
|
1413
|
+
onClick: onOpenProjectDialog,
|
|
1414
|
+
type: "button",
|
|
1415
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, { className: "size-4" }), "Add project"]
|
|
1416
|
+
})
|
|
1417
|
+
})
|
|
1418
|
+
]
|
|
1419
|
+
})
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
function MessageCard({ message }) {
|
|
1423
|
+
const isUser = message.role === "user";
|
|
1424
|
+
const isError = message.role === "error";
|
|
1425
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("article", {
|
|
1426
|
+
className: cn("rounded-[var(--radius-lg)] border px-4 py-3 shadow-[var(--shadow-xs)]", isUser && "ml-auto max-w-[78%] border-transparent bg-[var(--color-gray-900)] text-primary-foreground", message.role === "assistant" && "mr-auto max-w-[82%] border-border bg-card text-card-foreground", isError && "border-[var(--semantic-danger-border)] bg-[var(--semantic-danger-bg)] text-[var(--semantic-danger-fg)]"),
|
|
1427
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
|
|
1428
|
+
className: "whitespace-pre-wrap break-words font-sans text-[length:var(--font-size-md)] leading-[var(--line-height-relaxed)]",
|
|
1429
|
+
children: message.text
|
|
1430
|
+
})
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
async function fetchJson(input, init = {}) {
|
|
1434
|
+
const response = await fetch(input, {
|
|
1435
|
+
headers: {
|
|
1436
|
+
"Content-Type": "application/json",
|
|
1437
|
+
...init.headers
|
|
1438
|
+
},
|
|
1439
|
+
...init
|
|
1440
|
+
});
|
|
1441
|
+
const data = await response.json().catch(() => ({}));
|
|
1442
|
+
if (!response.ok) {
|
|
1443
|
+
const errorBody = data;
|
|
1444
|
+
const message = errorBody.error?.message ? errorBody.error.message : `Request failed with status ${response.status}`;
|
|
1445
|
+
throw new Error(message);
|
|
1446
|
+
}
|
|
1447
|
+
return data;
|
|
1448
|
+
}
|
|
1449
|
+
//#endregion
|
|
1450
|
+
export { Home as component };
|