@tangle-network/sandbox-ui 0.96.0 → 0.99.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-OPLQFAJK.js → WorkflowGraph-DB5F6ZSF.js} +1 -1
- package/dist/chat.d.ts +83 -257
- package/dist/chat.js +1 -10
- package/dist/{chunk-I5HGKZCB.js → chunk-G7CFCZQ7.js} +860 -1382
- package/dist/{chunk-22JEBRUV.js → chunk-JIZCCGD2.js} +1 -1
- package/dist/{chunk-5XRAKVIV.js → chunk-RQZRXKMR.js} +1 -5
- package/dist/{chunk-VQZ6XDRR.js → chunk-Y4OTBMZS.js} +730 -444
- package/dist/dashboard.d.ts +8 -86
- package/dist/dashboard.js +8 -24
- package/dist/globals.css +20 -104
- package/dist/index.d.ts +4 -4
- package/dist/index.js +10 -32
- package/dist/{model-brand-CuXKfTMh.d.ts → model-brand-BNnaUKKZ.d.ts} +2 -4
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1 -2
- package/dist/styles.css +20 -104
- package/dist/workbench.js +3 -5
- package/dist/workflows.d.ts +1 -1
- package/dist/workflows.js +2 -2
- package/dist/workspace.d.ts +1 -1
- package/package.json +2 -1
- package/dist/chunk-WMVUJ5SA.js +0 -529
- package/dist/chunk-WSN2RZC6.js +0 -292
|
@@ -1,29 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
formatBytes
|
|
3
3
|
} from "./chunk-TK46XFLM.js";
|
|
4
|
-
import {
|
|
5
|
-
HARNESS_OPTIONS,
|
|
6
|
-
HarnessLogo,
|
|
7
|
-
chatCapableHarnesses
|
|
8
|
-
} from "./chunk-WSN2RZC6.js";
|
|
9
|
-
import {
|
|
10
|
-
ModelPicker,
|
|
11
|
-
PickerMenuFooter,
|
|
12
|
-
PickerMenuMeta,
|
|
13
|
-
PickerMenuSearch,
|
|
14
|
-
PickerMenuSection,
|
|
15
|
-
PickerMenuTag,
|
|
16
|
-
isTextChatModel,
|
|
17
|
-
pickerMenuBodyClass,
|
|
18
|
-
pickerMenuContentClass,
|
|
19
|
-
pickerMenuItemClass,
|
|
20
|
-
stripBrandPrefix
|
|
21
|
-
} from "./chunk-WMVUJ5SA.js";
|
|
22
|
-
import {
|
|
23
|
-
ModelBrandStack,
|
|
24
|
-
canonicalModelId,
|
|
25
|
-
resolveModelBrandIdentity
|
|
26
|
-
} from "./chunk-5XRAKVIV.js";
|
|
27
4
|
import {
|
|
28
5
|
cn
|
|
29
6
|
} from "./chunk-EI44GEQ5.js";
|
|
@@ -46,22 +23,6 @@ import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
|
|
46
23
|
import { Brain, ChevronDown, Sparkles } from "lucide-react";
|
|
47
24
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
48
25
|
var REASONING_LADDER = reasoningLadder;
|
|
49
|
-
function clampReasoningLevel(value, available) {
|
|
50
|
-
if (value === "auto" || !available || available.includes(value)) {
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
const requestedRank = REASONING_LADDER.indexOf(value);
|
|
54
|
-
let closest;
|
|
55
|
-
let closestRank = -1;
|
|
56
|
-
for (const effort of available) {
|
|
57
|
-
const rank = REASONING_LADDER.indexOf(effort);
|
|
58
|
-
if (rank <= requestedRank && rank > closestRank) {
|
|
59
|
-
closest = effort;
|
|
60
|
-
closestRank = rank;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return closest ?? "auto";
|
|
64
|
-
}
|
|
65
26
|
function ReasoningGlyph({ level }) {
|
|
66
27
|
if (level === "auto") {
|
|
67
28
|
return /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5 text-muted-foreground" });
|
|
@@ -184,370 +145,579 @@ function ReasoningLevelPicker({
|
|
|
184
145
|
] });
|
|
185
146
|
}
|
|
186
147
|
|
|
187
|
-
// src/chat/agent-
|
|
148
|
+
// src/chat/agent-composer.tsx
|
|
188
149
|
import {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
150
|
+
ArrowUp,
|
|
151
|
+
Folder,
|
|
152
|
+
Loader2,
|
|
153
|
+
Paperclip,
|
|
154
|
+
RotateCcw,
|
|
155
|
+
Square,
|
|
156
|
+
Upload,
|
|
157
|
+
X
|
|
158
|
+
} from "lucide-react";
|
|
198
159
|
import * as React from "react";
|
|
199
|
-
function useClickOutside(onOutside) {
|
|
200
|
-
const ref = React.useRef(null);
|
|
201
|
-
React.useEffect(() => {
|
|
202
|
-
function handler(event) {
|
|
203
|
-
if (ref.current && !ref.current.contains(event.target)) {
|
|
204
|
-
onOutside();
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
document.addEventListener("mousedown", handler);
|
|
208
|
-
return () => document.removeEventListener("mousedown", handler);
|
|
209
|
-
}, [onOutside]);
|
|
210
|
-
return ref;
|
|
211
|
-
}
|
|
212
160
|
|
|
213
|
-
// src/chat/
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return
|
|
161
|
+
// src/chat/attachment-validation.ts
|
|
162
|
+
function isAcceptedType(file, accept) {
|
|
163
|
+
if (!accept || accept.trim().length === 0) return true;
|
|
164
|
+
const patterns = accept.split(",").map((pattern) => pattern.trim()).filter((pattern) => pattern.length > 0);
|
|
165
|
+
if (patterns.length === 0) return true;
|
|
166
|
+
const name = file.name.toLowerCase();
|
|
167
|
+
const type = (file.type || "").toLowerCase();
|
|
168
|
+
return patterns.some((pattern) => {
|
|
169
|
+
const lowerPattern = pattern.toLowerCase();
|
|
170
|
+
if (lowerPattern.startsWith(".")) {
|
|
171
|
+
return name.endsWith(lowerPattern);
|
|
172
|
+
}
|
|
173
|
+
if (lowerPattern.endsWith("/*")) {
|
|
174
|
+
const prefix = lowerPattern.slice(0, -1);
|
|
175
|
+
return type.startsWith(prefix);
|
|
176
|
+
}
|
|
177
|
+
return type === lowerPattern;
|
|
178
|
+
});
|
|
221
179
|
}
|
|
222
|
-
function
|
|
223
|
-
|
|
180
|
+
function validateComposerFiles(files, config) {
|
|
181
|
+
const { maxSizeBytes, maxCount, currentCount = 0, accept } = config;
|
|
182
|
+
const list = Array.isArray(files) ? files : Array.from(files);
|
|
183
|
+
const accepted = [];
|
|
184
|
+
const rejected = [];
|
|
185
|
+
for (const file of list) {
|
|
186
|
+
if (!isAcceptedType(file, accept)) {
|
|
187
|
+
rejected.push({
|
|
188
|
+
file,
|
|
189
|
+
reason: `"${file.name}" is not an accepted file type (${accept}).`
|
|
190
|
+
});
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (maxSizeBytes !== void 0 && file.size > maxSizeBytes) {
|
|
194
|
+
rejected.push({
|
|
195
|
+
file,
|
|
196
|
+
reason: `"${file.name}" (${formatBytes(file.size)}) exceeds the ${formatBytes(maxSizeBytes)} limit.`
|
|
197
|
+
});
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (maxCount !== void 0 && currentCount + accepted.length >= maxCount) {
|
|
201
|
+
rejected.push({
|
|
202
|
+
file,
|
|
203
|
+
reason: `"${file.name}" was not added \u2014 the ${maxCount}-file limit is already reached.`
|
|
204
|
+
});
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
accepted.push(file);
|
|
208
|
+
}
|
|
209
|
+
return { accepted, rejected };
|
|
224
210
|
}
|
|
225
211
|
|
|
226
|
-
// src/chat/agent-
|
|
227
|
-
import {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
212
|
+
// src/chat/agent-composer.tsx
|
|
213
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
214
|
+
var IMAGE_EXTENSION_BY_MIME = {
|
|
215
|
+
"image/png": "png",
|
|
216
|
+
"image/jpeg": "jpg",
|
|
217
|
+
"image/jpg": "jpg",
|
|
218
|
+
"image/gif": "gif",
|
|
219
|
+
"image/webp": "webp",
|
|
220
|
+
"image/bmp": "bmp",
|
|
221
|
+
"image/svg+xml": "svg"
|
|
222
|
+
};
|
|
223
|
+
function isGenericImageName(name) {
|
|
224
|
+
return /^(image)?(\.[a-z0-9]+)?$/i.test(name.trim());
|
|
234
225
|
}
|
|
235
|
-
function
|
|
226
|
+
function imageExtension(file) {
|
|
227
|
+
const fromMime = IMAGE_EXTENSION_BY_MIME[file.type.toLowerCase()];
|
|
228
|
+
if (fromMime) return fromMime;
|
|
229
|
+
const fromName = /\.([a-z0-9]+)$/i.exec(file.name)?.[1];
|
|
230
|
+
return fromName?.toLowerCase() ?? "png";
|
|
231
|
+
}
|
|
232
|
+
var MentionEditor = React.lazy(() => import("./mention-editor-3Q6MYOIU.js"));
|
|
233
|
+
function AgentComposer({
|
|
236
234
|
value,
|
|
237
235
|
onChange,
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
onCreate,
|
|
241
|
-
onUpdate,
|
|
242
|
-
onDelete,
|
|
236
|
+
onSubmit,
|
|
237
|
+
placeholder = "Send a message\u2026",
|
|
243
238
|
disabled,
|
|
239
|
+
busy,
|
|
240
|
+
onCancel,
|
|
241
|
+
controls,
|
|
242
|
+
trailing,
|
|
243
|
+
onAttach,
|
|
244
|
+
onAttachFolder,
|
|
245
|
+
attachments = [],
|
|
246
|
+
onRemoveFile,
|
|
247
|
+
onRetryFile,
|
|
248
|
+
accept,
|
|
249
|
+
onRejectFiles,
|
|
250
|
+
dropTitle = "Drop files to add context",
|
|
251
|
+
dropDescription = "They attach to your next message.",
|
|
252
|
+
canSubmitWhileBusy = false,
|
|
253
|
+
canSubmitAttachmentsOnly = false,
|
|
254
|
+
mention,
|
|
255
|
+
focusShortcut = false,
|
|
256
|
+
minRows = 2,
|
|
257
|
+
maxHeight = 200,
|
|
244
258
|
className,
|
|
245
|
-
|
|
259
|
+
sendLabel = "Send",
|
|
260
|
+
autoFocus
|
|
246
261
|
}) {
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
|
|
262
|
+
const textareaRef = React.useRef(null);
|
|
263
|
+
const richFocusRef = React.useRef(null);
|
|
264
|
+
const fileInputRef = React.useRef(null);
|
|
265
|
+
const folderInputRef = React.useRef(null);
|
|
266
|
+
const [dragOver, setDragOver] = React.useState(false);
|
|
267
|
+
const dragDepth = React.useRef(0);
|
|
268
|
+
const pastedImageCount = React.useRef(0);
|
|
269
|
+
const resize = React.useCallback(
|
|
270
|
+
(el) => {
|
|
271
|
+
el.style.height = "auto";
|
|
272
|
+
el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`;
|
|
273
|
+
},
|
|
274
|
+
[maxHeight]
|
|
250
275
|
);
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
276
|
+
React.useEffect(() => {
|
|
277
|
+
if (textareaRef.current) resize(textareaRef.current);
|
|
278
|
+
}, [value, resize]);
|
|
279
|
+
React.useEffect(() => {
|
|
280
|
+
if (!focusShortcut || disabled) return;
|
|
281
|
+
function onKeyDown(event) {
|
|
282
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "l") {
|
|
283
|
+
event.preventDefault();
|
|
284
|
+
if (mention) richFocusRef.current?.();
|
|
285
|
+
else textareaRef.current?.focus();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
document.addEventListener("keydown", onKeyDown);
|
|
289
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
290
|
+
}, [focusShortcut, disabled, mention]);
|
|
291
|
+
const canSend = !disabled && (!busy || canSubmitWhileBusy) && (value.trim().length > 0 || canSubmitAttachmentsOnly && attachments.length > 0);
|
|
292
|
+
const submit = () => {
|
|
293
|
+
if (!canSend) return;
|
|
294
|
+
onSubmit();
|
|
295
|
+
};
|
|
296
|
+
const registerRichFocus = React.useCallback((focus) => {
|
|
297
|
+
richFocusRef.current = focus;
|
|
298
|
+
}, []);
|
|
299
|
+
const dropEnabled = Boolean(onAttach);
|
|
300
|
+
const handleDragEnter = (event) => {
|
|
301
|
+
if (!dropEnabled) return;
|
|
302
|
+
event.preventDefault();
|
|
303
|
+
event.stopPropagation();
|
|
304
|
+
dragDepth.current++;
|
|
305
|
+
if (event.dataTransfer?.types.includes("Files")) setDragOver(true);
|
|
306
|
+
};
|
|
307
|
+
const handleDragLeave = (event) => {
|
|
308
|
+
if (!dropEnabled) return;
|
|
309
|
+
event.preventDefault();
|
|
310
|
+
event.stopPropagation();
|
|
311
|
+
dragDepth.current--;
|
|
312
|
+
if (dragDepth.current <= 0) {
|
|
313
|
+
dragDepth.current = 0;
|
|
314
|
+
setDragOver(false);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
const handleDragOver = (event) => {
|
|
318
|
+
if (!dropEnabled) return;
|
|
319
|
+
event.preventDefault();
|
|
320
|
+
event.stopPropagation();
|
|
321
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = "copy";
|
|
322
|
+
};
|
|
323
|
+
const deliverFiles = (files) => {
|
|
324
|
+
if (!onAttach || files.length === 0) return;
|
|
325
|
+
const { accepted, rejected } = validateComposerFiles(files, { accept });
|
|
326
|
+
if (rejected.length > 0) onRejectFiles?.(rejected);
|
|
327
|
+
if (accepted.length === 0) return;
|
|
328
|
+
const dt = new DataTransfer();
|
|
329
|
+
for (const file of accepted) dt.items.add(file);
|
|
330
|
+
onAttach(dt.files);
|
|
331
|
+
};
|
|
332
|
+
const handleDrop = (event) => {
|
|
333
|
+
if (!dropEnabled) return;
|
|
334
|
+
event.preventDefault();
|
|
335
|
+
event.stopPropagation();
|
|
336
|
+
dragDepth.current = 0;
|
|
337
|
+
setDragOver(false);
|
|
338
|
+
const files = event.dataTransfer?.files;
|
|
339
|
+
if (files?.length) deliverFiles(Array.from(files));
|
|
340
|
+
};
|
|
341
|
+
const renamePastedFiles = (files) => files.map((file) => {
|
|
342
|
+
if (file.type.startsWith("image/") && isGenericImageName(file.name)) {
|
|
343
|
+
pastedImageCount.current += 1;
|
|
344
|
+
return new File(
|
|
345
|
+
[file],
|
|
346
|
+
`pasted-image-${pastedImageCount.current}.${imageExtension(file)}`,
|
|
347
|
+
{ type: file.type }
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
return file;
|
|
254
351
|
});
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
352
|
+
const handlePaste = (event) => {
|
|
353
|
+
if (!onAttach) return;
|
|
354
|
+
const clipboardFiles = event.clipboardData?.files;
|
|
355
|
+
if (!clipboardFiles || clipboardFiles.length === 0) return;
|
|
356
|
+
event.preventDefault();
|
|
357
|
+
deliverFiles(renamePastedFiles(Array.from(clipboardFiles)));
|
|
358
|
+
};
|
|
359
|
+
const handleEditorPasteFiles = (clipboardFiles) => {
|
|
360
|
+
if (!onAttach || clipboardFiles.length === 0) return false;
|
|
361
|
+
deliverFiles(renamePastedFiles(Array.from(clipboardFiles)));
|
|
362
|
+
return true;
|
|
363
|
+
};
|
|
364
|
+
const folderChips = attachments.filter((file) => file.kind === "folder");
|
|
365
|
+
const fileChips = attachments.filter((file) => file.kind !== "folder");
|
|
366
|
+
const attachButtonClass = cn(
|
|
367
|
+
"flex size-8 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors",
|
|
368
|
+
"hover:bg-surface-container-highest hover:text-foreground focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
369
|
+
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
370
|
+
);
|
|
371
|
+
return /* @__PURE__ */ jsxs2("div", { className: cn("relative", className), children: [
|
|
372
|
+
dragOver && /* @__PURE__ */ jsx2("div", { className: "pointer-events-none absolute inset-0 z-10 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary/50 bg-surface-container-high/95", children: /* @__PURE__ */ jsxs2("div", { className: "text-center", children: [
|
|
373
|
+
/* @__PURE__ */ jsx2("span", { className: "mx-auto mb-2 flex size-11 items-center justify-center rounded-xl bg-primary/10 text-primary", children: /* @__PURE__ */ jsx2(Upload, { className: "size-5" }) }),
|
|
374
|
+
/* @__PURE__ */ jsx2("p", { className: "font-semibold text-foreground text-sm", children: dropTitle }),
|
|
375
|
+
/* @__PURE__ */ jsx2("p", { className: "mt-0.5 text-muted-foreground text-xs", children: dropDescription })
|
|
376
|
+
] }) }),
|
|
269
377
|
/* @__PURE__ */ jsxs2(
|
|
270
|
-
"
|
|
378
|
+
"div",
|
|
271
379
|
{
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
380
|
+
"data-testid": "agent-composer",
|
|
381
|
+
onDragEnter: handleDragEnter,
|
|
382
|
+
onDragLeave: handleDragLeave,
|
|
383
|
+
onDragOver: handleDragOver,
|
|
384
|
+
onDrop: handleDrop,
|
|
275
385
|
className: cn(
|
|
276
|
-
"
|
|
277
|
-
"
|
|
278
|
-
|
|
279
|
-
"data-[state=open]:border-[var(--md3-outline-variant)] data-[state=open]:bg-surface-container-high",
|
|
280
|
-
"disabled:cursor-not-allowed disabled:opacity-60"
|
|
386
|
+
"flex flex-col gap-2 rounded-2xl border border-[var(--md3-outline-variant)] bg-surface-container-high p-2.5 shadow-sm transition-colors",
|
|
387
|
+
"focus-within:border-primary/60",
|
|
388
|
+
disabled && "opacity-60"
|
|
281
389
|
),
|
|
282
|
-
"data-state": open ? "open" : "closed",
|
|
283
|
-
"aria-label": "Agent profile",
|
|
284
390
|
children: [
|
|
285
|
-
/* @__PURE__ */ jsx2(
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
391
|
+
attachments.length > 0 && /* @__PURE__ */ jsx2("div", { className: "flex flex-wrap gap-1.5", children: [...folderChips, ...fileChips].map((file) => {
|
|
392
|
+
const isError = file.status === "error";
|
|
393
|
+
const isPending = file.status === "pending";
|
|
394
|
+
return /* @__PURE__ */ jsxs2(
|
|
395
|
+
"span",
|
|
396
|
+
{
|
|
397
|
+
title: isError ? file.errorMessage : void 0,
|
|
398
|
+
className: cn(
|
|
399
|
+
"inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs",
|
|
400
|
+
isError ? "border-[var(--status-error,#ff4d6d)]/40 text-[var(--status-error,#ff4d6d)]" : "border-[var(--md3-outline-variant)] bg-surface-container text-foreground",
|
|
401
|
+
isPending && "opacity-60"
|
|
402
|
+
),
|
|
403
|
+
children: [
|
|
404
|
+
file.kind !== "folder" && file.previewUrl ? /* @__PURE__ */ jsx2(
|
|
405
|
+
"img",
|
|
406
|
+
{
|
|
407
|
+
src: file.previewUrl,
|
|
408
|
+
alt: "",
|
|
409
|
+
className: "size-8 rounded object-cover"
|
|
410
|
+
}
|
|
411
|
+
) : file.kind === "folder" ? /* @__PURE__ */ jsx2(Folder, { className: "size-3 shrink-0" }) : /* @__PURE__ */ jsx2(Paperclip, { className: "size-3 shrink-0" }),
|
|
412
|
+
/* @__PURE__ */ jsx2("span", { className: "max-w-[150px] truncate", children: file.name }),
|
|
413
|
+
file.fileCount !== void 0 && /* @__PURE__ */ jsxs2("span", { className: "text-muted-foreground", children: [
|
|
414
|
+
"(",
|
|
415
|
+
file.fileCount,
|
|
416
|
+
")"
|
|
417
|
+
] }),
|
|
418
|
+
file.status === "uploading" && /* @__PURE__ */ jsx2(Loader2, { className: "size-3 animate-spin" }),
|
|
419
|
+
isError && file.errorMessage && /* @__PURE__ */ jsx2("span", { className: "max-w-[150px] truncate text-[var(--status-error,#ff4d6d)]/80", children: file.errorMessage }),
|
|
420
|
+
isError && onRetryFile && /* @__PURE__ */ jsx2(
|
|
421
|
+
"button",
|
|
422
|
+
{
|
|
423
|
+
type: "button",
|
|
424
|
+
"aria-label": `Retry upload ${file.name}`,
|
|
425
|
+
onClick: () => onRetryFile(file.id),
|
|
426
|
+
className: "rounded p-0.5 text-muted-foreground transition-colors hover:text-foreground focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
427
|
+
children: /* @__PURE__ */ jsx2(RotateCcw, { className: "size-3" })
|
|
428
|
+
}
|
|
429
|
+
),
|
|
430
|
+
onRemoveFile && /* @__PURE__ */ jsx2(
|
|
431
|
+
"button",
|
|
432
|
+
{
|
|
433
|
+
type: "button",
|
|
434
|
+
"aria-label": `Remove ${file.name}`,
|
|
435
|
+
onClick: () => onRemoveFile(file.id),
|
|
436
|
+
className: "rounded p-0.5 text-muted-foreground transition-colors hover:text-foreground focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
437
|
+
children: /* @__PURE__ */ jsx2(X, { className: "size-3" })
|
|
438
|
+
}
|
|
439
|
+
)
|
|
440
|
+
]
|
|
441
|
+
},
|
|
442
|
+
file.id
|
|
443
|
+
);
|
|
444
|
+
}) }),
|
|
445
|
+
mention ? /* @__PURE__ */ jsx2(
|
|
446
|
+
React.Suspense,
|
|
322
447
|
{
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
448
|
+
fallback: /* @__PURE__ */ jsx2(
|
|
449
|
+
"textarea",
|
|
450
|
+
{
|
|
451
|
+
rows: minRows,
|
|
452
|
+
value,
|
|
453
|
+
disabled: true,
|
|
454
|
+
placeholder,
|
|
455
|
+
"aria-label": "Message input",
|
|
456
|
+
className: cn(
|
|
457
|
+
"w-full resize-none bg-transparent px-2 py-1 text-sm leading-relaxed text-foreground outline-none",
|
|
458
|
+
"placeholder:text-muted-foreground"
|
|
459
|
+
)
|
|
460
|
+
}
|
|
461
|
+
),
|
|
462
|
+
children: /* @__PURE__ */ jsx2(
|
|
463
|
+
MentionEditor,
|
|
464
|
+
{
|
|
465
|
+
value,
|
|
466
|
+
onChange,
|
|
467
|
+
onSubmit: submit,
|
|
468
|
+
placeholder,
|
|
469
|
+
disabled,
|
|
470
|
+
autoFocus,
|
|
471
|
+
minRows,
|
|
472
|
+
maxHeight,
|
|
473
|
+
mention,
|
|
474
|
+
registerFocus: registerRichFocus,
|
|
475
|
+
onPasteFiles: onAttach ? handleEditorPasteFiles : void 0
|
|
476
|
+
}
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
) : /* @__PURE__ */ jsx2(
|
|
480
|
+
"textarea",
|
|
334
481
|
{
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
482
|
+
ref: textareaRef,
|
|
483
|
+
rows: minRows,
|
|
484
|
+
value,
|
|
485
|
+
disabled,
|
|
486
|
+
autoFocus,
|
|
487
|
+
onChange: (event) => onChange(event.target.value),
|
|
488
|
+
onInput: (event) => resize(event.currentTarget),
|
|
489
|
+
onPaste: handlePaste,
|
|
490
|
+
onKeyDown: (event) => {
|
|
491
|
+
if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing && event.keyCode !== 229) {
|
|
492
|
+
event.preventDefault();
|
|
493
|
+
submit();
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
placeholder,
|
|
497
|
+
"aria-label": "Message input",
|
|
498
|
+
className: cn(
|
|
499
|
+
"w-full resize-none bg-transparent px-2 py-1 text-sm leading-relaxed text-foreground outline-none",
|
|
500
|
+
"placeholder:text-muted-foreground"
|
|
501
|
+
)
|
|
342
502
|
}
|
|
343
|
-
)
|
|
344
|
-
|
|
503
|
+
),
|
|
504
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-end gap-2", children: [
|
|
505
|
+
onAttach && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
506
|
+
/* @__PURE__ */ jsx2(
|
|
507
|
+
"button",
|
|
508
|
+
{
|
|
509
|
+
type: "button",
|
|
510
|
+
onClick: () => fileInputRef.current?.click(),
|
|
511
|
+
disabled,
|
|
512
|
+
"aria-label": "Attach files",
|
|
513
|
+
title: "Attach files",
|
|
514
|
+
className: attachButtonClass,
|
|
515
|
+
children: /* @__PURE__ */ jsx2(Paperclip, { className: "size-4" })
|
|
516
|
+
}
|
|
517
|
+
),
|
|
518
|
+
/* @__PURE__ */ jsx2(
|
|
519
|
+
"input",
|
|
520
|
+
{
|
|
521
|
+
ref: fileInputRef,
|
|
522
|
+
type: "file",
|
|
523
|
+
multiple: true,
|
|
524
|
+
className: "hidden",
|
|
525
|
+
accept,
|
|
526
|
+
onChange: (event) => {
|
|
527
|
+
if (event.target.files?.length)
|
|
528
|
+
deliverFiles(Array.from(event.target.files));
|
|
529
|
+
event.target.value = "";
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
)
|
|
533
|
+
] }),
|
|
534
|
+
onAttachFolder && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
535
|
+
/* @__PURE__ */ jsx2(
|
|
536
|
+
"button",
|
|
537
|
+
{
|
|
538
|
+
type: "button",
|
|
539
|
+
onClick: () => folderInputRef.current?.click(),
|
|
540
|
+
disabled,
|
|
541
|
+
"aria-label": "Attach folder",
|
|
542
|
+
title: "Attach folder",
|
|
543
|
+
className: attachButtonClass,
|
|
544
|
+
children: /* @__PURE__ */ jsx2(Folder, { className: "size-4" })
|
|
545
|
+
}
|
|
546
|
+
),
|
|
547
|
+
/* @__PURE__ */ jsx2(
|
|
548
|
+
"input",
|
|
549
|
+
{
|
|
550
|
+
ref: folderInputRef,
|
|
551
|
+
type: "file",
|
|
552
|
+
multiple: true,
|
|
553
|
+
className: "hidden",
|
|
554
|
+
onChange: (event) => {
|
|
555
|
+
if (event.target.files?.length)
|
|
556
|
+
(onAttachFolder ?? onAttach)?.(event.target.files);
|
|
557
|
+
event.target.value = "";
|
|
558
|
+
},
|
|
559
|
+
...{ webkitdirectory: "" }
|
|
560
|
+
}
|
|
561
|
+
)
|
|
562
|
+
] }),
|
|
563
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-1 items-center gap-2 overflow-x-auto overscroll-x-contain [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", children: [
|
|
564
|
+
controls,
|
|
565
|
+
trailing && /* @__PURE__ */ jsx2("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: trailing })
|
|
566
|
+
] }),
|
|
567
|
+
busy && onCancel ? /* @__PURE__ */ jsx2(
|
|
568
|
+
"button",
|
|
569
|
+
{
|
|
570
|
+
type: "button",
|
|
571
|
+
onClick: onCancel,
|
|
572
|
+
"aria-label": "Stop response",
|
|
573
|
+
className: cn(
|
|
574
|
+
"flex size-9 shrink-0 items-center justify-center rounded-full",
|
|
575
|
+
"bg-[var(--status-error,#ff4d6d)]/15 text-[var(--status-error,#ff4d6d)] transition-colors",
|
|
576
|
+
"hover:bg-[var(--status-error,#ff4d6d)]/25"
|
|
577
|
+
),
|
|
578
|
+
children: /* @__PURE__ */ jsx2(Square, { className: "size-3.5 fill-current" })
|
|
579
|
+
}
|
|
580
|
+
) : /* @__PURE__ */ jsx2(
|
|
581
|
+
"button",
|
|
582
|
+
{
|
|
583
|
+
type: "button",
|
|
584
|
+
onClick: submit,
|
|
585
|
+
disabled: !canSend,
|
|
586
|
+
"aria-label": sendLabel,
|
|
587
|
+
className: cn(
|
|
588
|
+
"flex size-9 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground",
|
|
589
|
+
"transition-opacity hover:opacity-90 disabled:opacity-40"
|
|
590
|
+
),
|
|
591
|
+
children: busy ? /* @__PURE__ */ jsx2(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx2(ArrowUp, { className: "size-4" })
|
|
592
|
+
}
|
|
593
|
+
)
|
|
594
|
+
] })
|
|
595
|
+
]
|
|
345
596
|
}
|
|
346
|
-
)
|
|
597
|
+
),
|
|
598
|
+
focusShortcut && /* @__PURE__ */ jsx2("div", { className: "mt-1.5 flex justify-end px-1", children: /* @__PURE__ */ jsxs2("span", { className: "text-muted-foreground text-xs", children: [
|
|
599
|
+
/* @__PURE__ */ jsx2("kbd", { className: "rounded border border-[var(--md3-outline-variant)] bg-surface-container px-1 py-0.5 text-[10px]", children: "Cmd" }),
|
|
600
|
+
/* @__PURE__ */ jsx2("kbd", { className: "ml-0.5 rounded border border-[var(--md3-outline-variant)] bg-surface-container px-1 py-0.5 text-[10px]", children: "L" }),
|
|
601
|
+
/* @__PURE__ */ jsx2("span", { className: "ml-1", children: "to focus" })
|
|
602
|
+
] }) })
|
|
347
603
|
] });
|
|
348
604
|
}
|
|
349
|
-
|
|
350
|
-
|
|
605
|
+
|
|
606
|
+
// src/chat/agent-profile-picker.tsx
|
|
607
|
+
import { Bot, Check, ChevronDown as ChevronDown2, Pencil, Plus as Plus2, Trash2 } from "lucide-react";
|
|
608
|
+
import * as React5 from "react";
|
|
609
|
+
|
|
610
|
+
// src/lib/use-click-outside.ts
|
|
611
|
+
import * as React2 from "react";
|
|
612
|
+
function useClickOutside(onOutside, extras) {
|
|
613
|
+
const ref = React2.useRef(null);
|
|
614
|
+
const extrasRef = React2.useRef(extras);
|
|
615
|
+
extrasRef.current = extras;
|
|
616
|
+
React2.useEffect(() => {
|
|
617
|
+
function handler(event) {
|
|
618
|
+
const target = event.target;
|
|
619
|
+
if (!ref.current || ref.current.contains(target)) return;
|
|
620
|
+
if (extrasRef.current?.some((extra) => extra.current?.contains(target))) return;
|
|
621
|
+
onOutside();
|
|
622
|
+
}
|
|
623
|
+
document.addEventListener("mousedown", handler);
|
|
624
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
625
|
+
}, [onOutside]);
|
|
626
|
+
return ref;
|
|
351
627
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
}
|
|
393
|
-
),
|
|
394
|
-
onDelete && /* @__PURE__ */ jsx2(
|
|
395
|
-
"button",
|
|
396
|
-
{
|
|
397
|
-
type: "button",
|
|
398
|
-
onClick: onDelete,
|
|
399
|
-
title: "Delete",
|
|
400
|
-
className: "rounded p-1 text-muted-foreground hover:text-[var(--status-error,#ff4d6d)]",
|
|
401
|
-
children: /* @__PURE__ */ jsx2(Trash2, { className: "h-3 w-3" })
|
|
402
|
-
}
|
|
403
|
-
)
|
|
404
|
-
] })
|
|
405
|
-
]
|
|
406
|
-
}
|
|
407
|
-
);
|
|
408
|
-
}
|
|
409
|
-
function ProfileForm({
|
|
410
|
-
capabilities,
|
|
411
|
-
initial,
|
|
412
|
-
onCancel,
|
|
413
|
-
onSave
|
|
414
|
-
}) {
|
|
415
|
-
const [name, setName] = React2.useState(initial?.name ?? "");
|
|
416
|
-
const [caps, setCaps] = React2.useState(
|
|
417
|
-
initial?.capabilities ? [...initial.capabilities] : capabilities.map((c) => c.id)
|
|
418
|
-
);
|
|
419
|
-
const [instructions, setInstructions] = React2.useState(initial?.instructions ?? "");
|
|
420
|
-
const toggle = (id) => setCaps(
|
|
421
|
-
(prev) => prev.includes(id) ? prev.filter((c) => c !== id) : [...prev, id]
|
|
422
|
-
);
|
|
423
|
-
return /* @__PURE__ */ jsxs2("div", { className: "space-y-3 p-2.5", children: [
|
|
424
|
-
/* @__PURE__ */ jsx2(
|
|
425
|
-
"input",
|
|
426
|
-
{
|
|
427
|
-
autoFocus: true,
|
|
428
|
-
value: name,
|
|
429
|
-
onChange: (event) => setName(event.target.value),
|
|
430
|
-
placeholder: "Agent name (e.g. Tax Reviewer)",
|
|
431
|
-
className: "w-full rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-3 py-2 text-sm outline-none focus:border-primary"
|
|
432
|
-
}
|
|
433
|
-
),
|
|
434
|
-
capabilities.length > 0 && /* @__PURE__ */ jsxs2("div", { children: [
|
|
435
|
-
/* @__PURE__ */ jsx2("p", { className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/70", children: "Tools" }),
|
|
436
|
-
/* @__PURE__ */ jsx2("div", { className: "space-y-0.5", children: capabilities.map((cap) => /* @__PURE__ */ jsxs2(
|
|
437
|
-
"label",
|
|
438
|
-
{
|
|
439
|
-
className: "flex cursor-pointer items-start gap-2 rounded-md px-2 py-1.5 hover:bg-accent/40",
|
|
440
|
-
children: [
|
|
441
|
-
/* @__PURE__ */ jsx2(
|
|
442
|
-
"input",
|
|
443
|
-
{
|
|
444
|
-
type: "checkbox",
|
|
445
|
-
checked: caps.includes(cap.id),
|
|
446
|
-
onChange: () => toggle(cap.id),
|
|
447
|
-
className: "mt-0.5"
|
|
448
|
-
}
|
|
449
|
-
),
|
|
450
|
-
/* @__PURE__ */ jsxs2("span", { className: "min-w-0", children: [
|
|
451
|
-
/* @__PURE__ */ jsx2("span", { className: "block text-sm font-medium", children: cap.label }),
|
|
452
|
-
cap.description && /* @__PURE__ */ jsx2("span", { className: "block text-xs text-muted-foreground", children: cap.description })
|
|
453
|
-
] })
|
|
454
|
-
]
|
|
455
|
-
},
|
|
456
|
-
cap.id
|
|
457
|
-
)) })
|
|
458
|
-
] }),
|
|
459
|
-
/* @__PURE__ */ jsx2(
|
|
460
|
-
"textarea",
|
|
461
|
-
{
|
|
462
|
-
value: instructions,
|
|
463
|
-
onChange: (event) => setInstructions(event.target.value),
|
|
464
|
-
rows: 2,
|
|
465
|
-
placeholder: "Instructions (optional) \u2014 how this agent should behave",
|
|
466
|
-
className: "w-full resize-none rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-3 py-2 text-sm outline-none focus:border-primary"
|
|
467
|
-
}
|
|
468
|
-
),
|
|
469
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex justify-end gap-2", children: [
|
|
470
|
-
/* @__PURE__ */ jsx2(
|
|
471
|
-
"button",
|
|
628
|
+
|
|
629
|
+
// src/chat/anchored-popover.tsx
|
|
630
|
+
import * as React3 from "react";
|
|
631
|
+
import { createPortal } from "react-dom";
|
|
632
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
633
|
+
var AnchoredPopover = React3.forwardRef(
|
|
634
|
+
function AnchoredPopover2({ anchorRef, side, children, className, style, ...rest }, forwardedRef) {
|
|
635
|
+
const innerRef = React3.useRef(null);
|
|
636
|
+
const setRefs = (node) => {
|
|
637
|
+
innerRef.current = node;
|
|
638
|
+
if (typeof forwardedRef === "function") forwardedRef(node);
|
|
639
|
+
else if (forwardedRef) forwardedRef.current = node;
|
|
640
|
+
};
|
|
641
|
+
const [position, setPosition] = React3.useState(null);
|
|
642
|
+
const place = React3.useCallback(() => {
|
|
643
|
+
const anchor = anchorRef.current;
|
|
644
|
+
if (!anchor) return;
|
|
645
|
+
const rect = anchor.getBoundingClientRect();
|
|
646
|
+
const width = innerRef.current?.offsetWidth ?? 0;
|
|
647
|
+
const margin = 8;
|
|
648
|
+
const left = Math.max(
|
|
649
|
+
margin,
|
|
650
|
+
Math.min(rect.left, window.innerWidth - width - margin)
|
|
651
|
+
);
|
|
652
|
+
setPosition(
|
|
653
|
+
side === "top" ? { left, bottom: window.innerHeight - rect.top + margin } : { left, top: rect.bottom + margin }
|
|
654
|
+
);
|
|
655
|
+
}, [anchorRef, side]);
|
|
656
|
+
React3.useLayoutEffect(() => {
|
|
657
|
+
place();
|
|
658
|
+
window.addEventListener("resize", place);
|
|
659
|
+
window.addEventListener("scroll", place, true);
|
|
660
|
+
return () => {
|
|
661
|
+
window.removeEventListener("resize", place);
|
|
662
|
+
window.removeEventListener("scroll", place, true);
|
|
663
|
+
};
|
|
664
|
+
}, [place]);
|
|
665
|
+
return createPortal(
|
|
666
|
+
/* @__PURE__ */ jsx3(
|
|
667
|
+
"div",
|
|
472
668
|
{
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
className: "
|
|
476
|
-
|
|
669
|
+
...rest,
|
|
670
|
+
ref: setRefs,
|
|
671
|
+
className: cn("fixed z-50", className),
|
|
672
|
+
style: {
|
|
673
|
+
...style,
|
|
674
|
+
pointerEvents: "auto",
|
|
675
|
+
...position ?? { visibility: "hidden", left: 0, top: 0 }
|
|
676
|
+
},
|
|
677
|
+
children
|
|
477
678
|
}
|
|
478
679
|
),
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
disabled: !name.trim(),
|
|
484
|
-
onClick: () => onSave({
|
|
485
|
-
id: initial?.id,
|
|
486
|
-
name: name.trim(),
|
|
487
|
-
capabilities: caps,
|
|
488
|
-
instructions: instructions.trim()
|
|
489
|
-
}),
|
|
490
|
-
className: "rounded-lg bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40",
|
|
491
|
-
children: "Save agent"
|
|
492
|
-
}
|
|
493
|
-
)
|
|
494
|
-
] })
|
|
495
|
-
] });
|
|
496
|
-
}
|
|
680
|
+
document.body
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
);
|
|
497
684
|
|
|
498
|
-
// src/chat/
|
|
499
|
-
import {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const ownEffort = !harnessHonorsEffort(harness);
|
|
503
|
-
if (ownModel && ownEffort) return "own model + thinking";
|
|
504
|
-
if (ownModel) return "own model";
|
|
505
|
-
if (ownEffort) return "own thinking";
|
|
506
|
-
return null;
|
|
507
|
-
}
|
|
508
|
-
var HARNESS_GROUP_LABELS = {
|
|
509
|
-
steerable: "Uses your model & thinking",
|
|
510
|
-
fixed: "Brings its own setup",
|
|
511
|
-
"no-agent": "No agent"
|
|
512
|
-
};
|
|
513
|
-
var HARNESS_GROUP_ORDER = [
|
|
514
|
-
"steerable",
|
|
515
|
-
"fixed",
|
|
516
|
-
"no-agent"
|
|
517
|
-
];
|
|
518
|
-
function harnessGroup(option) {
|
|
519
|
-
if (!option.chatCapable) return "no-agent";
|
|
520
|
-
return harnessAutonomyNote(option.type) === null ? "steerable" : "fixed";
|
|
521
|
-
}
|
|
522
|
-
var HARNESS_SEARCH_THRESHOLD = 7;
|
|
685
|
+
// src/chat/informative-lock.tsx
|
|
686
|
+
import { Lock, Plus } from "lucide-react";
|
|
687
|
+
import * as React4 from "react";
|
|
688
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
523
689
|
function InformativeLock({
|
|
524
|
-
|
|
525
|
-
|
|
690
|
+
children,
|
|
691
|
+
ariaLabel,
|
|
526
692
|
lockTitle,
|
|
527
693
|
lockBody,
|
|
528
694
|
newChatLabel,
|
|
529
695
|
onNewChat,
|
|
530
|
-
|
|
696
|
+
className,
|
|
697
|
+
triggerClassName,
|
|
698
|
+
popoverClassName,
|
|
699
|
+
side = "top"
|
|
531
700
|
}) {
|
|
532
|
-
const [open, setOpen] =
|
|
533
|
-
const closeTimer =
|
|
534
|
-
const
|
|
535
|
-
const
|
|
701
|
+
const [open, setOpen] = React4.useState(false);
|
|
702
|
+
const closeTimer = React4.useRef(null);
|
|
703
|
+
const popoverRef = React4.useRef(null);
|
|
704
|
+
const ref = useClickOutside(() => setOpen(false), [popoverRef]);
|
|
705
|
+
const cancelClose = React4.useCallback(() => {
|
|
536
706
|
if (closeTimer.current) {
|
|
537
707
|
clearTimeout(closeTimer.current);
|
|
538
708
|
closeTimer.current = null;
|
|
539
709
|
}
|
|
540
710
|
}, []);
|
|
541
|
-
const scheduleClose =
|
|
711
|
+
const scheduleClose = React4.useCallback(() => {
|
|
542
712
|
cancelClose();
|
|
543
713
|
closeTimer.current = setTimeout(() => setOpen(false), 150);
|
|
544
714
|
}, [cancelClose]);
|
|
545
|
-
|
|
715
|
+
React4.useEffect(() => cancelClose, [cancelClose]);
|
|
546
716
|
return /* @__PURE__ */ jsxs3(
|
|
547
717
|
"div",
|
|
548
718
|
{
|
|
549
719
|
ref,
|
|
550
|
-
className: "relative inline-flex",
|
|
720
|
+
className: cn("relative inline-flex", className),
|
|
551
721
|
onMouseEnter: () => {
|
|
552
722
|
cancelClose();
|
|
553
723
|
setOpen(true);
|
|
@@ -557,45 +727,48 @@ function InformativeLock({
|
|
|
557
727
|
if (event.key === "Escape") setOpen(false);
|
|
558
728
|
},
|
|
559
729
|
children: [
|
|
560
|
-
/* @__PURE__ */
|
|
730
|
+
/* @__PURE__ */ jsx4(
|
|
561
731
|
"button",
|
|
562
732
|
{
|
|
563
733
|
type: "button",
|
|
564
734
|
onClick: () => setOpen(true),
|
|
565
735
|
"data-state": open ? "open" : "closed",
|
|
566
|
-
"aria-label":
|
|
736
|
+
"aria-label": ariaLabel,
|
|
567
737
|
"aria-haspopup": "dialog",
|
|
568
738
|
"aria-expanded": open,
|
|
569
739
|
className: cn(
|
|
570
|
-
// Dimmed to read as locked (the harness is bound to this session),
|
|
571
|
-
// matching the disabled model picker — the logo stays so the chip is
|
|
572
|
-
// still recognizably this harness; the popover explains the lock.
|
|
573
740
|
"inline-flex h-8 items-center gap-1.5 rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-2.5 opacity-60",
|
|
574
741
|
"text-xs font-medium text-foreground shadow-sm transition-colors",
|
|
575
742
|
"hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
576
743
|
"data-[state=open]:border-[var(--md3-outline-variant)] data-[state=open]:bg-surface-container-high",
|
|
577
744
|
triggerClassName
|
|
578
745
|
),
|
|
579
|
-
children
|
|
580
|
-
/* @__PURE__ */ jsx3(HarnessLogo, { type, size: 16 }),
|
|
581
|
-
/* @__PURE__ */ jsx3("span", { children: label })
|
|
582
|
-
]
|
|
746
|
+
children
|
|
583
747
|
}
|
|
584
748
|
),
|
|
585
749
|
open && /* @__PURE__ */ jsxs3(
|
|
586
|
-
|
|
750
|
+
AnchoredPopover,
|
|
587
751
|
{
|
|
752
|
+
ref: popoverRef,
|
|
753
|
+
anchorRef: ref,
|
|
754
|
+
side,
|
|
588
755
|
role: "dialog",
|
|
756
|
+
onMouseEnter: () => {
|
|
757
|
+
cancelClose();
|
|
758
|
+
setOpen(true);
|
|
759
|
+
},
|
|
760
|
+
onMouseLeave: scheduleClose,
|
|
589
761
|
className: cn(
|
|
590
|
-
"
|
|
591
|
-
"shadow-[0_8px_30px_rgba(0,0,0,0.45)] ring-1 ring-[#ffffff14]"
|
|
762
|
+
"w-72 rounded-[var(--radius-md)] border border-[var(--md3-outline-variant)] bg-surface-container-highest p-3",
|
|
763
|
+
"shadow-[0_8px_30px_rgba(0,0,0,0.45)] ring-1 ring-[#ffffff14]",
|
|
764
|
+
popoverClassName
|
|
592
765
|
),
|
|
593
766
|
children: [
|
|
594
767
|
/* @__PURE__ */ jsxs3("p", { className: "flex items-start gap-2 text-sm font-medium text-foreground", children: [
|
|
595
|
-
/* @__PURE__ */
|
|
596
|
-
/* @__PURE__ */
|
|
768
|
+
/* @__PURE__ */ jsx4(Lock, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
|
|
769
|
+
/* @__PURE__ */ jsx4("span", { children: lockTitle })
|
|
597
770
|
] }),
|
|
598
|
-
/* @__PURE__ */
|
|
771
|
+
/* @__PURE__ */ jsx4("p", { className: "mt-1.5 text-xs text-muted-foreground", children: lockBody }),
|
|
599
772
|
/* @__PURE__ */ jsxs3(
|
|
600
773
|
"button",
|
|
601
774
|
{
|
|
@@ -610,8 +783,8 @@ function InformativeLock({
|
|
|
610
783
|
"hover:bg-primary/15 focus:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
611
784
|
),
|
|
612
785
|
children: [
|
|
613
|
-
/* @__PURE__ */
|
|
614
|
-
newChatLabel
|
|
786
|
+
/* @__PURE__ */ jsx4(Plus, { className: "h-4 w-4" }),
|
|
787
|
+
newChatLabel
|
|
615
788
|
]
|
|
616
789
|
}
|
|
617
790
|
)
|
|
@@ -622,1015 +795,323 @@ function InformativeLock({
|
|
|
622
795
|
}
|
|
623
796
|
);
|
|
624
797
|
}
|
|
625
|
-
|
|
798
|
+
|
|
799
|
+
// src/chat/agent-profile-picker.tsx
|
|
800
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
801
|
+
function profileSubtitle(profile) {
|
|
802
|
+
if (profile.description) return profile.description;
|
|
803
|
+
const count = profile.capabilities?.length ?? 0;
|
|
804
|
+
return `${count} ${count === 1 ? "tool group" : "tool groups"}`;
|
|
805
|
+
}
|
|
806
|
+
function AgentProfilePicker({
|
|
626
807
|
value,
|
|
627
808
|
onChange,
|
|
628
|
-
|
|
809
|
+
profiles,
|
|
810
|
+
capabilities,
|
|
811
|
+
onCreate,
|
|
812
|
+
onUpdate,
|
|
813
|
+
onDelete,
|
|
629
814
|
disabled,
|
|
630
815
|
locked,
|
|
631
816
|
lockReason,
|
|
632
817
|
onNewChat,
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
side = "
|
|
637
|
-
align = "start",
|
|
638
|
-
avoidCollisions,
|
|
639
|
-
triggerClassName
|
|
818
|
+
className,
|
|
819
|
+
triggerClassName,
|
|
820
|
+
popoverClassName,
|
|
821
|
+
side = "top"
|
|
640
822
|
}) {
|
|
641
|
-
const
|
|
642
|
-
|
|
823
|
+
const [open, setOpen] = React5.useState(false);
|
|
824
|
+
const [composing, setComposing] = React5.useState(
|
|
825
|
+
null
|
|
643
826
|
);
|
|
644
|
-
const
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
}, [filtered]);
|
|
827
|
+
const popoverRef = React5.useRef(null);
|
|
828
|
+
const ref = useClickOutside(() => {
|
|
829
|
+
setOpen(false);
|
|
830
|
+
setComposing(null);
|
|
831
|
+
}, [popoverRef]);
|
|
832
|
+
const canAuthor = Boolean(capabilities && (onCreate || onUpdate));
|
|
833
|
+
const builtins = profiles.filter((profile) => profile.builtin);
|
|
834
|
+
const custom = profiles.filter((profile) => !profile.builtin);
|
|
835
|
+
const selected = profiles.find((profile) => profile.id === value);
|
|
836
|
+
const selectedName = selected?.name ?? value;
|
|
837
|
+
React5.useEffect(() => {
|
|
838
|
+
if (!locked) return;
|
|
839
|
+
setOpen(false);
|
|
840
|
+
setComposing(null);
|
|
841
|
+
}, [locked]);
|
|
842
|
+
function select(id) {
|
|
843
|
+
onChange(id);
|
|
844
|
+
setOpen(false);
|
|
845
|
+
}
|
|
846
|
+
async function save(draft) {
|
|
847
|
+
if (draft.id) await onUpdate?.(draft.id, draft);
|
|
848
|
+
else await onCreate?.(draft);
|
|
849
|
+
setComposing(null);
|
|
850
|
+
}
|
|
669
851
|
if (locked && onNewChat) {
|
|
670
|
-
return /* @__PURE__ */
|
|
852
|
+
return /* @__PURE__ */ jsxs4(
|
|
671
853
|
InformativeLock,
|
|
672
854
|
{
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
newChatLabel,
|
|
855
|
+
ariaLabel: "Agent profile (locked)",
|
|
856
|
+
lockTitle: `Profile fixed to ${selectedName} for this conversation.`,
|
|
857
|
+
lockBody: "Conversations stay on one profile.",
|
|
858
|
+
newChatLabel: "New chat to switch profile",
|
|
678
859
|
onNewChat,
|
|
679
|
-
triggerClassName
|
|
680
|
-
}
|
|
681
|
-
);
|
|
682
|
-
}
|
|
683
|
-
return /* @__PURE__ */ jsxs3(
|
|
684
|
-
DropdownMenu2.Root,
|
|
685
|
-
{
|
|
686
|
-
onOpenChange: (nextOpen) => {
|
|
687
|
-
if (!nextOpen) setQuery("");
|
|
688
|
-
},
|
|
689
|
-
children: [
|
|
690
|
-
/* @__PURE__ */ jsx3(DropdownMenu2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs3(
|
|
691
|
-
"button",
|
|
692
|
-
{
|
|
693
|
-
type: "button",
|
|
694
|
-
disabled: disabled || locked,
|
|
695
|
-
title: locked ? lockReason : void 0,
|
|
696
|
-
className: cn(
|
|
697
|
-
"inline-flex h-8 items-center gap-1.5 rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-2.5",
|
|
698
|
-
"text-xs font-medium text-foreground shadow-sm transition-colors",
|
|
699
|
-
"hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
700
|
-
"data-[state=open]:border-[var(--md3-outline-variant)] data-[state=open]:bg-surface-container-high",
|
|
701
|
-
"disabled:cursor-not-allowed disabled:opacity-60",
|
|
702
|
-
triggerClassName
|
|
703
|
-
),
|
|
704
|
-
"aria-label": "Agent harness",
|
|
705
|
-
children: [
|
|
706
|
-
/* @__PURE__ */ jsx3(HarnessLogo, { type: value, size: 16, className: "shrink-0" }),
|
|
707
|
-
/* @__PURE__ */ jsx3("span", { className: "truncate", children: selected?.label ?? value }),
|
|
708
|
-
!locked && /* @__PURE__ */ jsx3(ChevronDown3, { className: "ml-auto h-3.5 w-3.5 shrink-0 text-muted-foreground" })
|
|
709
|
-
]
|
|
710
|
-
}
|
|
711
|
-
) }),
|
|
712
|
-
/* @__PURE__ */ jsx3(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsxs3(
|
|
713
|
-
DropdownMenu2.Content,
|
|
714
|
-
{
|
|
715
|
-
side,
|
|
716
|
-
align,
|
|
717
|
-
avoidCollisions,
|
|
718
|
-
collisionPadding: 24,
|
|
719
|
-
sideOffset: 6,
|
|
720
|
-
className: pickerMenuContentClass,
|
|
721
|
-
children: [
|
|
722
|
-
showSearch && /* @__PURE__ */ jsx3(
|
|
723
|
-
PickerMenuSearch,
|
|
724
|
-
{
|
|
725
|
-
value: query,
|
|
726
|
-
onChange: setQuery,
|
|
727
|
-
placeholder: "Search agents..."
|
|
728
|
-
}
|
|
729
|
-
),
|
|
730
|
-
/* @__PURE__ */ jsx3("div", { className: pickerMenuBodyClass, children: sections.length === 0 ? /* @__PURE__ */ jsx3("div", { className: "px-3 py-8 text-center text-xs text-muted-foreground", children: "No agents match." }) : sections.map((section) => /* @__PURE__ */ jsx3(PickerMenuSection, { label: section.label, children: section.items.map((option) => /* @__PURE__ */ jsxs3(
|
|
731
|
-
DropdownMenu2.Item,
|
|
732
|
-
{
|
|
733
|
-
onSelect: (event) => {
|
|
734
|
-
event.preventDefault();
|
|
735
|
-
onChange(option.type);
|
|
736
|
-
},
|
|
737
|
-
className: pickerMenuItemClass({
|
|
738
|
-
active: option.type === value
|
|
739
|
-
}),
|
|
740
|
-
children: [
|
|
741
|
-
/* @__PURE__ */ jsx3(
|
|
742
|
-
HarnessLogo,
|
|
743
|
-
{
|
|
744
|
-
type: option.type,
|
|
745
|
-
size: 20,
|
|
746
|
-
className: "mt-0.5"
|
|
747
|
-
}
|
|
748
|
-
),
|
|
749
|
-
/* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
|
|
750
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1.5", children: [
|
|
751
|
-
/* @__PURE__ */ jsx3("span", { className: "min-w-0 truncate text-sm font-medium", children: option.label }),
|
|
752
|
-
harnessAutonomyNote(option.type) && /* @__PURE__ */ jsx3(PickerMenuTag, { children: harnessAutonomyNote(option.type) })
|
|
753
|
-
] }),
|
|
754
|
-
/* @__PURE__ */ jsx3(PickerMenuMeta, { parts: [option.description] })
|
|
755
|
-
] })
|
|
756
|
-
]
|
|
757
|
-
},
|
|
758
|
-
option.type
|
|
759
|
-
)) }, section.key)) }),
|
|
760
|
-
showSearch && /* @__PURE__ */ jsxs3(PickerMenuFooter, { children: [
|
|
761
|
-
filtered.length,
|
|
762
|
-
" of ",
|
|
763
|
-
options.length,
|
|
764
|
-
" agent",
|
|
765
|
-
options.length === 1 ? "" : "s"
|
|
766
|
-
] })
|
|
767
|
-
]
|
|
768
|
-
}
|
|
769
|
-
) })
|
|
770
|
-
]
|
|
771
|
-
}
|
|
772
|
-
);
|
|
773
|
-
}
|
|
774
|
-
function modelReasoningCapability(models, canonicalId) {
|
|
775
|
-
if (!models || !canonicalId) return null;
|
|
776
|
-
const entry = models.find((m) => canonicalModelId(m) === canonicalId);
|
|
777
|
-
return entry ? {
|
|
778
|
-
supportsReasoning: entry.supportsReasoning,
|
|
779
|
-
maxEffort: entry.maxReasoningEffort
|
|
780
|
-
} : null;
|
|
781
|
-
}
|
|
782
|
-
function AgentSessionControls({
|
|
783
|
-
harness,
|
|
784
|
-
profile,
|
|
785
|
-
model,
|
|
786
|
-
reasoning,
|
|
787
|
-
filterModelsToHarness,
|
|
788
|
-
trailing,
|
|
789
|
-
className,
|
|
790
|
-
context = "chat",
|
|
791
|
-
layout = "inline",
|
|
792
|
-
menuPlacement = "auto"
|
|
793
|
-
}) {
|
|
794
|
-
if (!harness && !profile && !model && !reasoning && !trailing) return null;
|
|
795
|
-
const restrictModelsToHarness = Boolean(
|
|
796
|
-
harness && (harness.locked || filterModelsToHarness)
|
|
797
|
-
);
|
|
798
|
-
const selectorHonorsModel = !harness || harnessHonorsModel(harness.value);
|
|
799
|
-
const selectorHonorsEffort = !harness || harnessHonorsEffort(harness.value);
|
|
800
|
-
const clampEffort = (harnessValue, modelId) => {
|
|
801
|
-
if (!reasoning) return;
|
|
802
|
-
const available = reasoningEffortsFor(
|
|
803
|
-
harnessValue,
|
|
804
|
-
modelReasoningCapability(model?.models, modelId)
|
|
805
|
-
);
|
|
806
|
-
const clamped = clampReasoningLevel(reasoning.value, available);
|
|
807
|
-
if (clamped !== reasoning.value) reasoning.onChange(clamped);
|
|
808
|
-
};
|
|
809
|
-
const handleHarnessChange = (next) => {
|
|
810
|
-
harness?.onChange(next);
|
|
811
|
-
let nextModelId = model?.value;
|
|
812
|
-
if (model) {
|
|
813
|
-
const snapped = snapModelToHarness(next, model.value, model.models);
|
|
814
|
-
if (snapped !== model.value) {
|
|
815
|
-
model.onChange(snapped);
|
|
816
|
-
nextModelId = snapped;
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
clampEffort(next, nextModelId);
|
|
820
|
-
};
|
|
821
|
-
const handleModelChange = (nextModelId) => {
|
|
822
|
-
model?.onChange(nextModelId);
|
|
823
|
-
let nextHarness = harness?.value;
|
|
824
|
-
if (harness && !restrictModelsToHarness) {
|
|
825
|
-
const snapped = snapHarnessToModel(harness.value, nextModelId);
|
|
826
|
-
if (snapped !== harness.value) {
|
|
827
|
-
harness.onChange(snapped);
|
|
828
|
-
nextHarness = snapped;
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
if (nextHarness) clampEffort(nextHarness, nextModelId);
|
|
832
|
-
};
|
|
833
|
-
const chatModels = model && context === "chat" && model.modalities === void 0 ? model.models.filter(isTextChatModel) : model?.models;
|
|
834
|
-
const visibleModels = model && restrictModelsToHarness && harness ? (chatModels ?? []).filter(
|
|
835
|
-
(entry) => isModelCompatibleWithHarness(
|
|
836
|
-
harness.value,
|
|
837
|
-
canonicalModelId(entry)
|
|
838
|
-
)
|
|
839
|
-
) : chatModels;
|
|
840
|
-
const harnessAvailable = harness?.available ?? (context === "chat" ? chatCapableHarnesses : void 0);
|
|
841
|
-
const collapsed = layout === "gear" || layout === "combined";
|
|
842
|
-
const menuSide = collapsed ? "left" : "bottom";
|
|
843
|
-
const menuAlign = collapsed ? "end" : "start";
|
|
844
|
-
const collapsedTriggerClass = layout === "combined" ? "w-full bg-transparent shadow-none hover:bg-muted/60 data-[state=open]:bg-muted/60" : collapsed ? "w-full" : void 0;
|
|
845
|
-
const secondaryTriggerClass = collapsed ? collapsedTriggerClass : cn(
|
|
846
|
-
"border-transparent bg-transparent text-muted-foreground shadow-none",
|
|
847
|
-
"hover:border-transparent hover:bg-muted/50 hover:text-foreground",
|
|
848
|
-
"data-[state=open]:border-transparent data-[state=open]:bg-muted/50 data-[state=open]:text-foreground"
|
|
849
|
-
);
|
|
850
|
-
const modelTriggerClass = collapsed ? cn(collapsedTriggerClass, "h-8 rounded-lg px-2.5 text-xs") : void 0;
|
|
851
|
-
const inlineForceDown = menuPlacement === "down" && layout === "inline";
|
|
852
|
-
const avoidCollisions = inlineForceDown ? false : void 0;
|
|
853
|
-
const harnessNode = harness && /* @__PURE__ */ jsx3(
|
|
854
|
-
HarnessDropdown,
|
|
855
|
-
{
|
|
856
|
-
...harness,
|
|
857
|
-
available: harnessAvailable,
|
|
858
|
-
onChange: handleHarnessChange,
|
|
859
|
-
side: menuSide,
|
|
860
|
-
align: menuAlign,
|
|
861
|
-
avoidCollisions,
|
|
862
|
-
triggerClassName: secondaryTriggerClass
|
|
863
|
-
}
|
|
864
|
-
);
|
|
865
|
-
const profileNode = profile && /* @__PURE__ */ jsx3(
|
|
866
|
-
AgentProfilePicker,
|
|
867
|
-
{
|
|
868
|
-
value: profile.value,
|
|
869
|
-
onChange: profile.onChange,
|
|
870
|
-
profiles: profile.profiles,
|
|
871
|
-
capabilities: profile.capabilities,
|
|
872
|
-
onCreate: profile.onCreate,
|
|
873
|
-
onUpdate: profile.onUpdate,
|
|
874
|
-
onDelete: profile.onDelete,
|
|
875
|
-
disabled: profile.disabled,
|
|
876
|
-
side: inlineForceDown || collapsed ? "bottom" : "top"
|
|
877
|
-
}
|
|
878
|
-
);
|
|
879
|
-
const modelNode = model && /* @__PURE__ */ jsx3(
|
|
880
|
-
ModelPicker,
|
|
881
|
-
{
|
|
882
|
-
variant: "pill",
|
|
883
|
-
label: "",
|
|
884
|
-
value: model.value,
|
|
885
|
-
onChange: handleModelChange,
|
|
886
|
-
models: visibleModels ?? [],
|
|
887
|
-
loading: model.loading,
|
|
888
|
-
popular: model.popular,
|
|
889
|
-
recents: model.recents,
|
|
890
|
-
modalities: model.modalities,
|
|
891
|
-
excludeProviders: model.excludeProviders,
|
|
892
|
-
disabled: model.disabled || (visibleModels ?? []).length === 0 || !selectorHonorsModel,
|
|
893
|
-
side: collapsed ? void 0 : "bottom",
|
|
894
|
-
avoidCollisions,
|
|
895
|
-
triggerClassName: modelTriggerClass
|
|
896
|
-
}
|
|
897
|
-
);
|
|
898
|
-
const modelReasoning = modelReasoningCapability(model?.models, model?.value);
|
|
899
|
-
const reasoningOptions = (harness && HARNESS_REASONING_OPTIONS[harness.value]) ?? reasoning?.options;
|
|
900
|
-
const reasoningNode = reasoning && /* @__PURE__ */ jsx3(
|
|
901
|
-
ReasoningLevelPicker,
|
|
902
|
-
{
|
|
903
|
-
value: reasoning.value,
|
|
904
|
-
onChange: reasoning.onChange,
|
|
905
|
-
options: reasoningOptions,
|
|
906
|
-
disabled: reasoning.disabled || !selectorHonorsEffort,
|
|
907
|
-
available: harness ? reasoningEffortsFor(harness.value, modelReasoning) : reasoning.available,
|
|
908
|
-
side: collapsed ? void 0 : "bottom",
|
|
909
|
-
avoidCollisions,
|
|
910
|
-
triggerClassName: secondaryTriggerClass
|
|
911
|
-
}
|
|
912
|
-
);
|
|
913
|
-
if (layout === "gear") {
|
|
914
|
-
return /* @__PURE__ */ jsx3(
|
|
915
|
-
GearControls,
|
|
916
|
-
{
|
|
917
860
|
className,
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
861
|
+
triggerClassName,
|
|
862
|
+
popoverClassName,
|
|
863
|
+
side,
|
|
864
|
+
children: [
|
|
865
|
+
/* @__PURE__ */ jsx5(Bot, { className: "h-3.5 w-3.5 text-muted-foreground" }),
|
|
866
|
+
/* @__PURE__ */ jsx5("span", { className: "max-w-[140px] truncate", children: selectedName })
|
|
867
|
+
]
|
|
923
868
|
}
|
|
924
869
|
);
|
|
925
870
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
const harnessLabel = HARNESS_OPTIONS.find((option) => option.type === harness.value)?.label ?? harness.value;
|
|
930
|
-
segments.push({
|
|
931
|
-
key: "harness",
|
|
932
|
-
label: harnessLabel,
|
|
933
|
-
node: /* @__PURE__ */ jsxs3("span", { className: "flex items-center gap-1.5", children: [
|
|
934
|
-
/* @__PURE__ */ jsx3(HarnessLogo, { type: harness.value, size: 16, className: "shrink-0" }),
|
|
935
|
-
harnessLabel
|
|
936
|
-
] })
|
|
937
|
-
});
|
|
938
|
-
}
|
|
939
|
-
if (model && selectorHonorsModel) {
|
|
940
|
-
const current = (model.models ?? []).find(
|
|
941
|
-
(entry) => canonicalModelId(entry) === model.value
|
|
942
|
-
);
|
|
943
|
-
const identity = current ? resolveModelBrandIdentity(current) : null;
|
|
944
|
-
const modelLabel = stripBrandPrefix(
|
|
945
|
-
current?.name ?? current?.id ?? model.value,
|
|
946
|
-
identity
|
|
947
|
-
);
|
|
948
|
-
segments.push({
|
|
949
|
-
key: "model",
|
|
950
|
-
label: modelLabel,
|
|
951
|
-
node: /* @__PURE__ */ jsxs3("span", { className: "flex items-center gap-1.5", children: [
|
|
952
|
-
identity && /* @__PURE__ */ jsx3(ModelBrandStack, { identity, size: "sm" }),
|
|
953
|
-
modelLabel
|
|
954
|
-
] })
|
|
955
|
-
});
|
|
956
|
-
}
|
|
957
|
-
if (reasoning && selectorHonorsEffort) {
|
|
958
|
-
const effortLabel = (reasoningOptions ?? DEFAULT_REASONING_LEVEL_OPTIONS).find(
|
|
959
|
-
(option) => option.value === reasoning.value
|
|
960
|
-
)?.label ?? "Auto";
|
|
961
|
-
segments.push({
|
|
962
|
-
key: "effort",
|
|
963
|
-
label: effortLabel,
|
|
964
|
-
node: /* @__PURE__ */ jsxs3("span", { className: "flex items-center gap-1.5", children: [
|
|
965
|
-
/* @__PURE__ */ jsx3("span", { className: "flex w-3.5 shrink-0 justify-center", children: /* @__PURE__ */ jsx3(ReasoningGlyph, { level: reasoning.value }) }),
|
|
966
|
-
effortLabel
|
|
967
|
-
] })
|
|
968
|
-
});
|
|
969
|
-
}
|
|
970
|
-
const summary = /* @__PURE__ */ jsx3("span", { className: "flex items-center gap-1.5", children: segments.map((segment, index) => /* @__PURE__ */ jsxs3(React3.Fragment, { children: [
|
|
971
|
-
index > 0 && /* @__PURE__ */ jsx3(
|
|
972
|
-
"span",
|
|
973
|
-
{
|
|
974
|
-
className: "text-base leading-none text-muted-foreground/60",
|
|
975
|
-
"aria-hidden": true,
|
|
976
|
-
children: "\xB7"
|
|
977
|
-
}
|
|
978
|
-
),
|
|
979
|
-
segment.node
|
|
980
|
-
] }, segment.key)) });
|
|
981
|
-
return /* @__PURE__ */ jsx3(
|
|
982
|
-
SummaryControls,
|
|
871
|
+
return /* @__PURE__ */ jsxs4("div", { ref, className: cn("relative inline-flex", className), children: [
|
|
872
|
+
/* @__PURE__ */ jsxs4(
|
|
873
|
+
"button",
|
|
983
874
|
{
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
title:
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
harnessNode,
|
|
1004
|
-
modelNode,
|
|
1005
|
-
reasoningNode,
|
|
1006
|
-
trailing && /* @__PURE__ */ jsx3("div", { className: "ml-auto flex items-center gap-2", children: trailing })
|
|
1007
|
-
]
|
|
1008
|
-
}
|
|
1009
|
-
);
|
|
1010
|
-
}
|
|
1011
|
-
function GearControls({
|
|
1012
|
-
className,
|
|
1013
|
-
profileNode,
|
|
1014
|
-
harnessNode,
|
|
1015
|
-
modelNode,
|
|
1016
|
-
reasoningNode,
|
|
1017
|
-
trailing
|
|
1018
|
-
}) {
|
|
1019
|
-
return /* @__PURE__ */ jsx3(
|
|
1020
|
-
CollapsibleControls,
|
|
1021
|
-
{
|
|
1022
|
-
className,
|
|
1023
|
-
trigger: /* @__PURE__ */ jsx3(SlidersHorizontal, { className: "h-4 w-4 text-muted-foreground" }),
|
|
1024
|
-
triggerClassName: cn(
|
|
1025
|
-
"inline-flex h-8 w-8 items-center justify-center rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container",
|
|
1026
|
-
"text-foreground shadow-sm transition-colors",
|
|
1027
|
-
"hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
1028
|
-
"data-[state=open]:border-[var(--md3-outline-variant)] data-[state=open]:bg-surface-container-high"
|
|
1029
|
-
),
|
|
1030
|
-
contentSide: "top",
|
|
1031
|
-
contentClassName: cn(
|
|
1032
|
-
"w-56 gap-2 border-[var(--md3-outline-variant)] bg-surface-container-highest p-2",
|
|
1033
|
-
"shadow-[0_8px_30px_rgba(0,0,0,0.45)] ring-1 ring-[#ffffff14]"
|
|
1034
|
-
),
|
|
1035
|
-
profileNode,
|
|
1036
|
-
harnessNode,
|
|
1037
|
-
modelNode,
|
|
1038
|
-
reasoningNode,
|
|
1039
|
-
trailing
|
|
1040
|
-
}
|
|
1041
|
-
);
|
|
1042
|
-
}
|
|
1043
|
-
function GearSection({
|
|
1044
|
-
label,
|
|
1045
|
-
children
|
|
1046
|
-
}) {
|
|
1047
|
-
return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-1", children: [
|
|
1048
|
-
/* @__PURE__ */ jsx3("span", { className: "px-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground/70", children: label }),
|
|
1049
|
-
children
|
|
1050
|
-
] });
|
|
1051
|
-
}
|
|
1052
|
-
function SummaryControls({
|
|
1053
|
-
className,
|
|
1054
|
-
summary,
|
|
1055
|
-
title,
|
|
1056
|
-
contentSide,
|
|
1057
|
-
profileNode,
|
|
1058
|
-
harnessNode,
|
|
1059
|
-
modelNode,
|
|
1060
|
-
reasoningNode,
|
|
1061
|
-
trailing
|
|
1062
|
-
}) {
|
|
1063
|
-
return /* @__PURE__ */ jsx3(
|
|
1064
|
-
CollapsibleControls,
|
|
1065
|
-
{
|
|
1066
|
-
className,
|
|
1067
|
-
trigger: /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
1068
|
-
summary,
|
|
1069
|
-
/* @__PURE__ */ jsx3(ChevronDown3, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" })
|
|
1070
|
-
] }),
|
|
1071
|
-
triggerTitle: title,
|
|
1072
|
-
triggerClassName: cn(
|
|
1073
|
-
// Container-less trigger (Codex-style): bare label + chevron docked in
|
|
1074
|
-
// the composer footer, no border/card/shadow. Only a subtle rounded
|
|
1075
|
-
// hover/open tint marks it as interactive.
|
|
1076
|
-
"inline-flex h-8 items-center gap-1.5 rounded-md px-1.5",
|
|
1077
|
-
"whitespace-nowrap text-xs font-medium text-foreground transition-colors",
|
|
1078
|
-
"hover:bg-muted/50 focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
1079
|
-
"data-[state=open]:bg-muted/50"
|
|
1080
|
-
),
|
|
1081
|
-
contentSide,
|
|
1082
|
-
contentClassName: "w-64 gap-2.5 border-border bg-popover p-2.5 text-popover-foreground shadow-lg",
|
|
1083
|
-
profileNode,
|
|
1084
|
-
harnessNode,
|
|
1085
|
-
modelNode,
|
|
1086
|
-
reasoningNode,
|
|
1087
|
-
trailing
|
|
1088
|
-
}
|
|
1089
|
-
);
|
|
1090
|
-
}
|
|
1091
|
-
function CollapsibleControls({
|
|
1092
|
-
className,
|
|
1093
|
-
trigger,
|
|
1094
|
-
triggerTitle,
|
|
1095
|
-
triggerClassName,
|
|
1096
|
-
contentSide,
|
|
1097
|
-
contentClassName,
|
|
1098
|
-
profileNode,
|
|
1099
|
-
harnessNode,
|
|
1100
|
-
modelNode,
|
|
1101
|
-
reasoningNode,
|
|
1102
|
-
trailing
|
|
1103
|
-
}) {
|
|
1104
|
-
return /* @__PURE__ */ jsxs3(
|
|
1105
|
-
"div",
|
|
1106
|
-
{
|
|
1107
|
-
className: cn("flex items-center gap-2", className),
|
|
1108
|
-
"data-testid": "agent-session-controls",
|
|
1109
|
-
children: [
|
|
1110
|
-
trailing && /* @__PURE__ */ jsx3("div", { className: "flex items-center gap-2", children: trailing }),
|
|
1111
|
-
/* @__PURE__ */ jsxs3(DropdownMenu2.Root, { children: [
|
|
1112
|
-
/* @__PURE__ */ jsx3(DropdownMenu2.Trigger, { asChild: true, children: /* @__PURE__ */ jsx3(
|
|
1113
|
-
"button",
|
|
1114
|
-
{
|
|
1115
|
-
type: "button",
|
|
1116
|
-
"aria-label": "Session controls",
|
|
1117
|
-
title: triggerTitle,
|
|
1118
|
-
className: triggerClassName,
|
|
1119
|
-
children: trigger
|
|
1120
|
-
}
|
|
1121
|
-
) }),
|
|
1122
|
-
/* @__PURE__ */ jsx3(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsxs3(
|
|
1123
|
-
DropdownMenu2.Content,
|
|
1124
|
-
{
|
|
1125
|
-
side: contentSide,
|
|
1126
|
-
align: "end",
|
|
1127
|
-
sideOffset: 8,
|
|
1128
|
-
onInteractOutside: (event) => {
|
|
1129
|
-
const target = event.target;
|
|
1130
|
-
if (target?.closest(
|
|
1131
|
-
"[data-radix-popper-content-wrapper],[data-radix-menu-content],[role=menu],[role=listbox]"
|
|
1132
|
-
)) {
|
|
1133
|
-
event.preventDefault();
|
|
1134
|
-
}
|
|
1135
|
-
},
|
|
1136
|
-
className: cn(
|
|
1137
|
-
"z-50 flex flex-col rounded-[var(--radius-md)] border",
|
|
1138
|
-
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
1139
|
-
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
1140
|
-
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
1141
|
-
contentClassName
|
|
1142
|
-
),
|
|
1143
|
-
children: [
|
|
1144
|
-
profileNode && /* @__PURE__ */ jsx3(GearSection, { label: "Agent", children: profileNode }),
|
|
1145
|
-
harnessNode && /* @__PURE__ */ jsx3(GearSection, { label: "Harness", children: harnessNode }),
|
|
1146
|
-
modelNode && /* @__PURE__ */ jsx3(GearSection, { label: "Model", children: modelNode }),
|
|
1147
|
-
reasoningNode && /* @__PURE__ */ jsx3(GearSection, { label: "Effort", children: reasoningNode })
|
|
1148
|
-
]
|
|
1149
|
-
}
|
|
1150
|
-
) })
|
|
1151
|
-
] })
|
|
1152
|
-
]
|
|
1153
|
-
}
|
|
1154
|
-
);
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
// src/chat/agent-composer.tsx
|
|
1158
|
-
import {
|
|
1159
|
-
ArrowUp,
|
|
1160
|
-
Folder,
|
|
1161
|
-
Loader2,
|
|
1162
|
-
Paperclip,
|
|
1163
|
-
RotateCcw,
|
|
1164
|
-
Square,
|
|
1165
|
-
Upload,
|
|
1166
|
-
X
|
|
1167
|
-
} from "lucide-react";
|
|
1168
|
-
import * as React4 from "react";
|
|
1169
|
-
|
|
1170
|
-
// src/chat/attachment-validation.ts
|
|
1171
|
-
function isAcceptedType(file, accept) {
|
|
1172
|
-
if (!accept || accept.trim().length === 0) return true;
|
|
1173
|
-
const patterns = accept.split(",").map((pattern) => pattern.trim()).filter((pattern) => pattern.length > 0);
|
|
1174
|
-
if (patterns.length === 0) return true;
|
|
1175
|
-
const name = file.name.toLowerCase();
|
|
1176
|
-
const type = (file.type || "").toLowerCase();
|
|
1177
|
-
return patterns.some((pattern) => {
|
|
1178
|
-
const lowerPattern = pattern.toLowerCase();
|
|
1179
|
-
if (lowerPattern.startsWith(".")) {
|
|
1180
|
-
return name.endsWith(lowerPattern);
|
|
1181
|
-
}
|
|
1182
|
-
if (lowerPattern.endsWith("/*")) {
|
|
1183
|
-
const prefix = lowerPattern.slice(0, -1);
|
|
1184
|
-
return type.startsWith(prefix);
|
|
1185
|
-
}
|
|
1186
|
-
return type === lowerPattern;
|
|
1187
|
-
});
|
|
1188
|
-
}
|
|
1189
|
-
function validateComposerFiles(files, config) {
|
|
1190
|
-
const { maxSizeBytes, maxCount, currentCount = 0, accept } = config;
|
|
1191
|
-
const list = Array.isArray(files) ? files : Array.from(files);
|
|
1192
|
-
const accepted = [];
|
|
1193
|
-
const rejected = [];
|
|
1194
|
-
for (const file of list) {
|
|
1195
|
-
if (!isAcceptedType(file, accept)) {
|
|
1196
|
-
rejected.push({
|
|
1197
|
-
file,
|
|
1198
|
-
reason: `"${file.name}" is not an accepted file type (${accept}).`
|
|
1199
|
-
});
|
|
1200
|
-
continue;
|
|
1201
|
-
}
|
|
1202
|
-
if (maxSizeBytes !== void 0 && file.size > maxSizeBytes) {
|
|
1203
|
-
rejected.push({
|
|
1204
|
-
file,
|
|
1205
|
-
reason: `"${file.name}" (${formatBytes(file.size)}) exceeds the ${formatBytes(maxSizeBytes)} limit.`
|
|
1206
|
-
});
|
|
1207
|
-
continue;
|
|
1208
|
-
}
|
|
1209
|
-
if (maxCount !== void 0 && currentCount + accepted.length >= maxCount) {
|
|
1210
|
-
rejected.push({
|
|
1211
|
-
file,
|
|
1212
|
-
reason: `"${file.name}" was not added \u2014 the ${maxCount}-file limit is already reached.`
|
|
1213
|
-
});
|
|
1214
|
-
continue;
|
|
1215
|
-
}
|
|
1216
|
-
accepted.push(file);
|
|
1217
|
-
}
|
|
1218
|
-
return { accepted, rejected };
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
// src/chat/agent-composer.tsx
|
|
1222
|
-
import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1223
|
-
var IMAGE_EXTENSION_BY_MIME = {
|
|
1224
|
-
"image/png": "png",
|
|
1225
|
-
"image/jpeg": "jpg",
|
|
1226
|
-
"image/jpg": "jpg",
|
|
1227
|
-
"image/gif": "gif",
|
|
1228
|
-
"image/webp": "webp",
|
|
1229
|
-
"image/bmp": "bmp",
|
|
1230
|
-
"image/svg+xml": "svg"
|
|
1231
|
-
};
|
|
1232
|
-
function isGenericImageName(name) {
|
|
1233
|
-
return /^(image)?(\.[a-z0-9]+)?$/i.test(name.trim());
|
|
1234
|
-
}
|
|
1235
|
-
function imageExtension(file) {
|
|
1236
|
-
const fromMime = IMAGE_EXTENSION_BY_MIME[file.type.toLowerCase()];
|
|
1237
|
-
if (fromMime) return fromMime;
|
|
1238
|
-
const fromName = /\.([a-z0-9]+)$/i.exec(file.name)?.[1];
|
|
1239
|
-
return fromName?.toLowerCase() ?? "png";
|
|
1240
|
-
}
|
|
1241
|
-
var MentionEditor = React4.lazy(() => import("./mention-editor-3Q6MYOIU.js"));
|
|
1242
|
-
function AgentComposer({
|
|
1243
|
-
value,
|
|
1244
|
-
onChange,
|
|
1245
|
-
onSubmit,
|
|
1246
|
-
placeholder = "Send a message\u2026",
|
|
1247
|
-
disabled,
|
|
1248
|
-
busy,
|
|
1249
|
-
onCancel,
|
|
1250
|
-
harness,
|
|
1251
|
-
profile,
|
|
1252
|
-
model,
|
|
1253
|
-
reasoning,
|
|
1254
|
-
context = "chat",
|
|
1255
|
-
controls,
|
|
1256
|
-
trailing,
|
|
1257
|
-
onAttach,
|
|
1258
|
-
onAttachFolder,
|
|
1259
|
-
attachments = [],
|
|
1260
|
-
onRemoveFile,
|
|
1261
|
-
onRetryFile,
|
|
1262
|
-
accept,
|
|
1263
|
-
onRejectFiles,
|
|
1264
|
-
dropTitle = "Drop files to add context",
|
|
1265
|
-
dropDescription = "They attach to your next message.",
|
|
1266
|
-
canSubmitWhileBusy = false,
|
|
1267
|
-
canSubmitAttachmentsOnly = false,
|
|
1268
|
-
mention,
|
|
1269
|
-
focusShortcut = false,
|
|
1270
|
-
minRows = 2,
|
|
1271
|
-
maxHeight = 200,
|
|
1272
|
-
className,
|
|
1273
|
-
sendLabel = "Send",
|
|
1274
|
-
autoFocus
|
|
1275
|
-
}) {
|
|
1276
|
-
const textareaRef = React4.useRef(null);
|
|
1277
|
-
const richFocusRef = React4.useRef(null);
|
|
1278
|
-
const fileInputRef = React4.useRef(null);
|
|
1279
|
-
const folderInputRef = React4.useRef(null);
|
|
1280
|
-
const [dragOver, setDragOver] = React4.useState(false);
|
|
1281
|
-
const dragDepth = React4.useRef(0);
|
|
1282
|
-
const pastedImageCount = React4.useRef(0);
|
|
1283
|
-
const resize = React4.useCallback(
|
|
1284
|
-
(el) => {
|
|
1285
|
-
el.style.height = "auto";
|
|
1286
|
-
el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`;
|
|
1287
|
-
},
|
|
1288
|
-
[maxHeight]
|
|
1289
|
-
);
|
|
1290
|
-
React4.useEffect(() => {
|
|
1291
|
-
if (textareaRef.current) resize(textareaRef.current);
|
|
1292
|
-
}, [value, resize]);
|
|
1293
|
-
React4.useEffect(() => {
|
|
1294
|
-
if (!focusShortcut || disabled) return;
|
|
1295
|
-
function onKeyDown(event) {
|
|
1296
|
-
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "l") {
|
|
1297
|
-
event.preventDefault();
|
|
1298
|
-
if (mention) richFocusRef.current?.();
|
|
1299
|
-
else textareaRef.current?.focus();
|
|
875
|
+
type: "button",
|
|
876
|
+
disabled: disabled || locked,
|
|
877
|
+
title: locked ? lockReason : void 0,
|
|
878
|
+
onClick: () => setOpen((v) => !v),
|
|
879
|
+
className: cn(
|
|
880
|
+
"inline-flex h-8 items-center gap-1.5 rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-2.5",
|
|
881
|
+
"text-xs font-medium text-foreground shadow-sm transition-colors",
|
|
882
|
+
"hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
883
|
+
"data-[state=open]:border-[var(--md3-outline-variant)] data-[state=open]:bg-surface-container-high",
|
|
884
|
+
"disabled:cursor-not-allowed disabled:opacity-60",
|
|
885
|
+
triggerClassName
|
|
886
|
+
),
|
|
887
|
+
"data-state": open ? "open" : "closed",
|
|
888
|
+
"aria-label": "Agent profile",
|
|
889
|
+
children: [
|
|
890
|
+
/* @__PURE__ */ jsx5(Bot, { className: "h-3.5 w-3.5 text-muted-foreground" }),
|
|
891
|
+
/* @__PURE__ */ jsx5("span", { className: "max-w-[140px] truncate", children: selectedName }),
|
|
892
|
+
!locked && /* @__PURE__ */ jsx5(ChevronDown2, { className: "ml-auto h-3.5 w-3.5 shrink-0 text-muted-foreground" })
|
|
893
|
+
]
|
|
1300
894
|
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
}, [focusShortcut, disabled, mention]);
|
|
1305
|
-
const canSend = !disabled && (!busy || canSubmitWhileBusy) && (value.trim().length > 0 || canSubmitAttachmentsOnly && attachments.length > 0);
|
|
1306
|
-
const submit = () => {
|
|
1307
|
-
if (!canSend) return;
|
|
1308
|
-
onSubmit();
|
|
1309
|
-
};
|
|
1310
|
-
const registerRichFocus = React4.useCallback((focus) => {
|
|
1311
|
-
richFocusRef.current = focus;
|
|
1312
|
-
}, []);
|
|
1313
|
-
const dropEnabled = Boolean(onAttach);
|
|
1314
|
-
const handleDragEnter = (event) => {
|
|
1315
|
-
if (!dropEnabled) return;
|
|
1316
|
-
event.preventDefault();
|
|
1317
|
-
event.stopPropagation();
|
|
1318
|
-
dragDepth.current++;
|
|
1319
|
-
if (event.dataTransfer?.types.includes("Files")) setDragOver(true);
|
|
1320
|
-
};
|
|
1321
|
-
const handleDragLeave = (event) => {
|
|
1322
|
-
if (!dropEnabled) return;
|
|
1323
|
-
event.preventDefault();
|
|
1324
|
-
event.stopPropagation();
|
|
1325
|
-
dragDepth.current--;
|
|
1326
|
-
if (dragDepth.current <= 0) {
|
|
1327
|
-
dragDepth.current = 0;
|
|
1328
|
-
setDragOver(false);
|
|
1329
|
-
}
|
|
1330
|
-
};
|
|
1331
|
-
const handleDragOver = (event) => {
|
|
1332
|
-
if (!dropEnabled) return;
|
|
1333
|
-
event.preventDefault();
|
|
1334
|
-
event.stopPropagation();
|
|
1335
|
-
if (event.dataTransfer) event.dataTransfer.dropEffect = "copy";
|
|
1336
|
-
};
|
|
1337
|
-
const deliverFiles = (files) => {
|
|
1338
|
-
if (!onAttach || files.length === 0) return;
|
|
1339
|
-
const { accepted, rejected } = validateComposerFiles(files, { accept });
|
|
1340
|
-
if (rejected.length > 0) onRejectFiles?.(rejected);
|
|
1341
|
-
if (accepted.length === 0) return;
|
|
1342
|
-
const dt = new DataTransfer();
|
|
1343
|
-
for (const file of accepted) dt.items.add(file);
|
|
1344
|
-
onAttach(dt.files);
|
|
1345
|
-
};
|
|
1346
|
-
const handleDrop = (event) => {
|
|
1347
|
-
if (!dropEnabled) return;
|
|
1348
|
-
event.preventDefault();
|
|
1349
|
-
event.stopPropagation();
|
|
1350
|
-
dragDepth.current = 0;
|
|
1351
|
-
setDragOver(false);
|
|
1352
|
-
const files = event.dataTransfer?.files;
|
|
1353
|
-
if (files?.length) deliverFiles(Array.from(files));
|
|
1354
|
-
};
|
|
1355
|
-
const renamePastedFiles = (files) => files.map((file) => {
|
|
1356
|
-
if (file.type.startsWith("image/") && isGenericImageName(file.name)) {
|
|
1357
|
-
pastedImageCount.current += 1;
|
|
1358
|
-
return new File(
|
|
1359
|
-
[file],
|
|
1360
|
-
`pasted-image-${pastedImageCount.current}.${imageExtension(file)}`,
|
|
1361
|
-
{ type: file.type }
|
|
1362
|
-
);
|
|
1363
|
-
}
|
|
1364
|
-
return file;
|
|
1365
|
-
});
|
|
1366
|
-
const handlePaste = (event) => {
|
|
1367
|
-
if (!onAttach) return;
|
|
1368
|
-
const clipboardFiles = event.clipboardData?.files;
|
|
1369
|
-
if (!clipboardFiles || clipboardFiles.length === 0) return;
|
|
1370
|
-
event.preventDefault();
|
|
1371
|
-
deliverFiles(renamePastedFiles(Array.from(clipboardFiles)));
|
|
1372
|
-
};
|
|
1373
|
-
const handleEditorPasteFiles = (clipboardFiles) => {
|
|
1374
|
-
if (!onAttach || clipboardFiles.length === 0) return false;
|
|
1375
|
-
deliverFiles(renamePastedFiles(Array.from(clipboardFiles)));
|
|
1376
|
-
return true;
|
|
1377
|
-
};
|
|
1378
|
-
const folderChips = attachments.filter((file) => file.kind === "folder");
|
|
1379
|
-
const fileChips = attachments.filter((file) => file.kind !== "folder");
|
|
1380
|
-
const attachButtonClass = cn(
|
|
1381
|
-
"flex size-8 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors",
|
|
1382
|
-
"hover:bg-surface-container-highest hover:text-foreground focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
1383
|
-
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
1384
|
-
);
|
|
1385
|
-
return /* @__PURE__ */ jsxs4("div", { className: cn("relative", className), children: [
|
|
1386
|
-
dragOver && /* @__PURE__ */ jsx4("div", { className: "pointer-events-none absolute inset-0 z-10 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary/50 bg-surface-container-high/95", children: /* @__PURE__ */ jsxs4("div", { className: "text-center", children: [
|
|
1387
|
-
/* @__PURE__ */ jsx4("span", { className: "mx-auto mb-2 flex size-11 items-center justify-center rounded-xl bg-primary/10 text-primary", children: /* @__PURE__ */ jsx4(Upload, { className: "size-5" }) }),
|
|
1388
|
-
/* @__PURE__ */ jsx4("p", { className: "font-semibold text-foreground text-sm", children: dropTitle }),
|
|
1389
|
-
/* @__PURE__ */ jsx4("p", { className: "mt-0.5 text-muted-foreground text-xs", children: dropDescription })
|
|
1390
|
-
] }) }),
|
|
1391
|
-
/* @__PURE__ */ jsxs4(
|
|
1392
|
-
"div",
|
|
895
|
+
),
|
|
896
|
+
open && !locked && /* @__PURE__ */ jsx5(
|
|
897
|
+
AnchoredPopover,
|
|
1393
898
|
{
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
onDragOver: handleDragOver,
|
|
1398
|
-
onDrop: handleDrop,
|
|
899
|
+
ref: popoverRef,
|
|
900
|
+
anchorRef: ref,
|
|
901
|
+
side,
|
|
1399
902
|
className: cn(
|
|
1400
|
-
"
|
|
1401
|
-
"
|
|
1402
|
-
|
|
903
|
+
"w-[min(320px,calc(100vw-2rem))] overflow-hidden rounded-[var(--radius-md)] border border-[var(--md3-outline-variant)] bg-surface-container-highest p-1",
|
|
904
|
+
"shadow-[0_8px_30px_rgba(0,0,0,0.45)] ring-1 ring-[#ffffff14]",
|
|
905
|
+
popoverClassName
|
|
1403
906
|
),
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
"aria-label": `Remove ${file.name}`,
|
|
1449
|
-
onClick: () => onRemoveFile(file.id),
|
|
1450
|
-
className: "rounded p-0.5 text-muted-foreground transition-colors hover:text-foreground focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
1451
|
-
children: /* @__PURE__ */ jsx4(X, { className: "size-3" })
|
|
1452
|
-
}
|
|
1453
|
-
)
|
|
1454
|
-
]
|
|
1455
|
-
},
|
|
1456
|
-
file.id
|
|
1457
|
-
);
|
|
1458
|
-
}) }),
|
|
1459
|
-
mention ? /* @__PURE__ */ jsx4(
|
|
1460
|
-
React4.Suspense,
|
|
1461
|
-
{
|
|
1462
|
-
fallback: /* @__PURE__ */ jsx4(
|
|
1463
|
-
"textarea",
|
|
1464
|
-
{
|
|
1465
|
-
rows: minRows,
|
|
1466
|
-
value,
|
|
1467
|
-
disabled: true,
|
|
1468
|
-
placeholder,
|
|
1469
|
-
"aria-label": "Message input",
|
|
1470
|
-
className: cn(
|
|
1471
|
-
"w-full resize-none bg-transparent px-2 py-1 text-sm leading-relaxed text-foreground outline-none",
|
|
1472
|
-
"placeholder:text-muted-foreground"
|
|
1473
|
-
)
|
|
1474
|
-
}
|
|
1475
|
-
),
|
|
1476
|
-
children: /* @__PURE__ */ jsx4(
|
|
1477
|
-
MentionEditor,
|
|
1478
|
-
{
|
|
1479
|
-
value,
|
|
1480
|
-
onChange,
|
|
1481
|
-
onSubmit: submit,
|
|
1482
|
-
placeholder,
|
|
1483
|
-
disabled,
|
|
1484
|
-
autoFocus,
|
|
1485
|
-
minRows,
|
|
1486
|
-
maxHeight,
|
|
1487
|
-
mention,
|
|
1488
|
-
registerFocus: registerRichFocus,
|
|
1489
|
-
onPasteFiles: onAttach ? handleEditorPasteFiles : void 0
|
|
1490
|
-
}
|
|
1491
|
-
)
|
|
907
|
+
"data-testid": "agent-profile-popover",
|
|
908
|
+
children: composing ? /* @__PURE__ */ jsx5(
|
|
909
|
+
ProfileForm,
|
|
910
|
+
{
|
|
911
|
+
capabilities: capabilities ?? [],
|
|
912
|
+
initial: composing === "new" ? void 0 : composing,
|
|
913
|
+
onCancel: () => setComposing(null),
|
|
914
|
+
onSave: save
|
|
915
|
+
}
|
|
916
|
+
) : /* @__PURE__ */ jsxs4("div", { className: "max-h-[min(60vh,420px)] overflow-y-auto", children: [
|
|
917
|
+
/* @__PURE__ */ jsx5(SectionHeader, { children: "Modes" }),
|
|
918
|
+
builtins.map((profile) => /* @__PURE__ */ jsx5(
|
|
919
|
+
ProfileRow,
|
|
920
|
+
{
|
|
921
|
+
active: profile.id === value,
|
|
922
|
+
title: profile.name,
|
|
923
|
+
subtitle: profileSubtitle(profile),
|
|
924
|
+
onSelect: () => select(profile.id)
|
|
925
|
+
},
|
|
926
|
+
profile.id
|
|
927
|
+
)),
|
|
928
|
+
custom.length > 0 && /* @__PURE__ */ jsx5(SectionHeader, { children: "Your agents" }),
|
|
929
|
+
custom.map((profile) => /* @__PURE__ */ jsx5(
|
|
930
|
+
ProfileRow,
|
|
931
|
+
{
|
|
932
|
+
active: profile.id === value,
|
|
933
|
+
title: profile.name,
|
|
934
|
+
subtitle: profileSubtitle(profile),
|
|
935
|
+
onSelect: () => select(profile.id),
|
|
936
|
+
onEdit: canAuthor ? () => setComposing(profile) : void 0,
|
|
937
|
+
onDelete: onDelete ? () => void onDelete(profile.id) : void 0
|
|
938
|
+
},
|
|
939
|
+
profile.id
|
|
940
|
+
)),
|
|
941
|
+
canAuthor && /* @__PURE__ */ jsxs4(
|
|
942
|
+
"button",
|
|
943
|
+
{
|
|
944
|
+
type: "button",
|
|
945
|
+
onClick: () => setComposing("new"),
|
|
946
|
+
className: "mt-1 flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left text-sm text-[var(--accent-text)] transition-colors hover:bg-accent/40",
|
|
947
|
+
children: [
|
|
948
|
+
/* @__PURE__ */ jsx5(Plus2, { className: "h-4 w-4" }),
|
|
949
|
+
" New agent\u2026"
|
|
950
|
+
]
|
|
1492
951
|
}
|
|
1493
|
-
)
|
|
1494
|
-
|
|
952
|
+
)
|
|
953
|
+
] })
|
|
954
|
+
}
|
|
955
|
+
)
|
|
956
|
+
] });
|
|
957
|
+
}
|
|
958
|
+
function SectionHeader({ children }) {
|
|
959
|
+
return /* @__PURE__ */ jsx5("div", { className: "px-2.5 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/70", children });
|
|
960
|
+
}
|
|
961
|
+
function ProfileRow({
|
|
962
|
+
active,
|
|
963
|
+
title,
|
|
964
|
+
subtitle,
|
|
965
|
+
onSelect,
|
|
966
|
+
onEdit,
|
|
967
|
+
onDelete
|
|
968
|
+
}) {
|
|
969
|
+
return /* @__PURE__ */ jsxs4(
|
|
970
|
+
"div",
|
|
971
|
+
{
|
|
972
|
+
className: cn(
|
|
973
|
+
"group flex items-center gap-2 rounded-md px-2.5 py-2 text-sm transition-colors",
|
|
974
|
+
active ? "bg-[var(--accent-surface-soft)] text-[var(--accent-text)]" : "hover:bg-accent/40"
|
|
975
|
+
),
|
|
976
|
+
children: [
|
|
977
|
+
/* @__PURE__ */ jsxs4(
|
|
978
|
+
"button",
|
|
979
|
+
{
|
|
980
|
+
type: "button",
|
|
981
|
+
onClick: onSelect,
|
|
982
|
+
className: "flex min-w-0 flex-1 items-center gap-2 text-left",
|
|
983
|
+
children: [
|
|
984
|
+
/* @__PURE__ */ jsx5("span", { className: "flex h-4 w-4 shrink-0 items-center justify-center", children: active && /* @__PURE__ */ jsx5(Check, { className: "h-3.5 w-3.5" }) }),
|
|
985
|
+
/* @__PURE__ */ jsxs4("span", { className: "min-w-0", children: [
|
|
986
|
+
/* @__PURE__ */ jsx5("span", { className: "block truncate font-medium", children: title }),
|
|
987
|
+
/* @__PURE__ */ jsx5("span", { className: "block truncate text-xs text-muted-foreground", children: subtitle })
|
|
988
|
+
] })
|
|
989
|
+
]
|
|
990
|
+
}
|
|
991
|
+
),
|
|
992
|
+
(onEdit || onDelete) && /* @__PURE__ */ jsxs4("span", { className: "flex shrink-0 items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100", children: [
|
|
993
|
+
onEdit && /* @__PURE__ */ jsx5(
|
|
994
|
+
"button",
|
|
1495
995
|
{
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
onChange: (event) => onChange(event.target.value),
|
|
1502
|
-
onInput: (event) => resize(event.currentTarget),
|
|
1503
|
-
onPaste: handlePaste,
|
|
1504
|
-
onKeyDown: (event) => {
|
|
1505
|
-
if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing && event.keyCode !== 229) {
|
|
1506
|
-
event.preventDefault();
|
|
1507
|
-
submit();
|
|
1508
|
-
}
|
|
1509
|
-
},
|
|
1510
|
-
placeholder,
|
|
1511
|
-
"aria-label": "Message input",
|
|
1512
|
-
className: cn(
|
|
1513
|
-
"w-full resize-none bg-transparent px-2 py-1 text-sm leading-relaxed text-foreground outline-none",
|
|
1514
|
-
"placeholder:text-muted-foreground"
|
|
1515
|
-
)
|
|
996
|
+
type: "button",
|
|
997
|
+
onClick: onEdit,
|
|
998
|
+
title: "Edit",
|
|
999
|
+
className: "rounded p-1 text-muted-foreground hover:text-foreground",
|
|
1000
|
+
children: /* @__PURE__ */ jsx5(Pencil, { className: "h-3 w-3" })
|
|
1516
1001
|
}
|
|
1517
1002
|
),
|
|
1518
|
-
/* @__PURE__ */
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
className: "hidden",
|
|
1568
|
-
onChange: (event) => {
|
|
1569
|
-
if (event.target.files?.length)
|
|
1570
|
-
(onAttachFolder ?? onAttach)?.(event.target.files);
|
|
1571
|
-
event.target.value = "";
|
|
1572
|
-
},
|
|
1573
|
-
...{ webkitdirectory: "" }
|
|
1574
|
-
}
|
|
1575
|
-
)
|
|
1576
|
-
] }),
|
|
1577
|
-
/* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-1 items-center gap-2 overflow-x-auto overscroll-x-contain [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", children: [
|
|
1578
|
-
controls ?? /* @__PURE__ */ jsx4(
|
|
1579
|
-
AgentSessionControls,
|
|
1580
|
-
{
|
|
1581
|
-
context,
|
|
1582
|
-
harness,
|
|
1583
|
-
profile,
|
|
1584
|
-
model,
|
|
1585
|
-
reasoning
|
|
1586
|
-
}
|
|
1587
|
-
),
|
|
1588
|
-
trailing && /* @__PURE__ */ jsx4("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: trailing })
|
|
1589
|
-
] }),
|
|
1590
|
-
busy && onCancel ? /* @__PURE__ */ jsx4(
|
|
1591
|
-
"button",
|
|
1592
|
-
{
|
|
1593
|
-
type: "button",
|
|
1594
|
-
onClick: onCancel,
|
|
1595
|
-
"aria-label": "Stop response",
|
|
1596
|
-
className: cn(
|
|
1597
|
-
"flex size-9 shrink-0 items-center justify-center rounded-full",
|
|
1598
|
-
"bg-[var(--status-error,#ff4d6d)]/15 text-[var(--status-error,#ff4d6d)] transition-colors",
|
|
1599
|
-
"hover:bg-[var(--status-error,#ff4d6d)]/25"
|
|
1600
|
-
),
|
|
1601
|
-
children: /* @__PURE__ */ jsx4(Square, { className: "size-3.5 fill-current" })
|
|
1602
|
-
}
|
|
1603
|
-
) : /* @__PURE__ */ jsx4(
|
|
1604
|
-
"button",
|
|
1003
|
+
onDelete && /* @__PURE__ */ jsx5(
|
|
1004
|
+
"button",
|
|
1005
|
+
{
|
|
1006
|
+
type: "button",
|
|
1007
|
+
onClick: onDelete,
|
|
1008
|
+
title: "Delete",
|
|
1009
|
+
className: "rounded p-1 text-muted-foreground hover:text-[var(--status-error,#ff4d6d)]",
|
|
1010
|
+
children: /* @__PURE__ */ jsx5(Trash2, { className: "h-3 w-3" })
|
|
1011
|
+
}
|
|
1012
|
+
)
|
|
1013
|
+
] })
|
|
1014
|
+
]
|
|
1015
|
+
}
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
function ProfileForm({
|
|
1019
|
+
capabilities,
|
|
1020
|
+
initial,
|
|
1021
|
+
onCancel,
|
|
1022
|
+
onSave
|
|
1023
|
+
}) {
|
|
1024
|
+
const [name, setName] = React5.useState(initial?.name ?? "");
|
|
1025
|
+
const [caps, setCaps] = React5.useState(
|
|
1026
|
+
initial?.capabilities ? [...initial.capabilities] : capabilities.map((c) => c.id)
|
|
1027
|
+
);
|
|
1028
|
+
const [instructions, setInstructions] = React5.useState(initial?.instructions ?? "");
|
|
1029
|
+
const toggle = (id) => setCaps(
|
|
1030
|
+
(prev) => prev.includes(id) ? prev.filter((c) => c !== id) : [...prev, id]
|
|
1031
|
+
);
|
|
1032
|
+
return /* @__PURE__ */ jsxs4("div", { className: "space-y-3 p-2.5", children: [
|
|
1033
|
+
/* @__PURE__ */ jsx5(
|
|
1034
|
+
"input",
|
|
1035
|
+
{
|
|
1036
|
+
autoFocus: true,
|
|
1037
|
+
value: name,
|
|
1038
|
+
onChange: (event) => setName(event.target.value),
|
|
1039
|
+
placeholder: "Agent name (e.g. Tax Reviewer)",
|
|
1040
|
+
className: "w-full rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-3 py-2 text-sm outline-none focus:border-primary"
|
|
1041
|
+
}
|
|
1042
|
+
),
|
|
1043
|
+
capabilities.length > 0 && /* @__PURE__ */ jsxs4("div", { children: [
|
|
1044
|
+
/* @__PURE__ */ jsx5("p", { className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/70", children: "Tools" }),
|
|
1045
|
+
/* @__PURE__ */ jsx5("div", { className: "space-y-0.5", children: capabilities.map((cap) => /* @__PURE__ */ jsxs4(
|
|
1046
|
+
"label",
|
|
1047
|
+
{
|
|
1048
|
+
className: "flex cursor-pointer items-start gap-2 rounded-md px-2 py-1.5 hover:bg-accent/40",
|
|
1049
|
+
children: [
|
|
1050
|
+
/* @__PURE__ */ jsx5(
|
|
1051
|
+
"input",
|
|
1605
1052
|
{
|
|
1606
|
-
type: "
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
"
|
|
1610
|
-
className: cn(
|
|
1611
|
-
"flex size-9 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground",
|
|
1612
|
-
"transition-opacity hover:opacity-90 disabled:opacity-40"
|
|
1613
|
-
),
|
|
1614
|
-
children: busy ? /* @__PURE__ */ jsx4(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx4(ArrowUp, { className: "size-4" })
|
|
1053
|
+
type: "checkbox",
|
|
1054
|
+
checked: caps.includes(cap.id),
|
|
1055
|
+
onChange: () => toggle(cap.id),
|
|
1056
|
+
className: "mt-0.5"
|
|
1615
1057
|
}
|
|
1616
|
-
)
|
|
1617
|
-
|
|
1618
|
-
|
|
1058
|
+
),
|
|
1059
|
+
/* @__PURE__ */ jsxs4("span", { className: "min-w-0", children: [
|
|
1060
|
+
/* @__PURE__ */ jsx5("span", { className: "block text-sm font-medium", children: cap.label }),
|
|
1061
|
+
cap.description && /* @__PURE__ */ jsx5("span", { className: "block text-xs text-muted-foreground", children: cap.description })
|
|
1062
|
+
] })
|
|
1063
|
+
]
|
|
1064
|
+
},
|
|
1065
|
+
cap.id
|
|
1066
|
+
)) })
|
|
1067
|
+
] }),
|
|
1068
|
+
/* @__PURE__ */ jsx5(
|
|
1069
|
+
"textarea",
|
|
1070
|
+
{
|
|
1071
|
+
value: instructions,
|
|
1072
|
+
onChange: (event) => setInstructions(event.target.value),
|
|
1073
|
+
rows: 2,
|
|
1074
|
+
placeholder: "Instructions (optional) \u2014 how this agent should behave",
|
|
1075
|
+
className: "w-full resize-none rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-3 py-2 text-sm outline-none focus:border-primary"
|
|
1619
1076
|
}
|
|
1620
1077
|
),
|
|
1621
|
-
|
|
1622
|
-
/* @__PURE__ */
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1078
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex justify-end gap-2", children: [
|
|
1079
|
+
/* @__PURE__ */ jsx5(
|
|
1080
|
+
"button",
|
|
1081
|
+
{
|
|
1082
|
+
type: "button",
|
|
1083
|
+
onClick: onCancel,
|
|
1084
|
+
className: "rounded-lg px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent/40",
|
|
1085
|
+
children: "Cancel"
|
|
1086
|
+
}
|
|
1087
|
+
),
|
|
1088
|
+
/* @__PURE__ */ jsx5(
|
|
1089
|
+
"button",
|
|
1090
|
+
{
|
|
1091
|
+
type: "button",
|
|
1092
|
+
disabled: !name.trim(),
|
|
1093
|
+
onClick: () => onSave({
|
|
1094
|
+
id: initial?.id,
|
|
1095
|
+
name: name.trim(),
|
|
1096
|
+
capabilities: caps,
|
|
1097
|
+
instructions: instructions.trim()
|
|
1098
|
+
}),
|
|
1099
|
+
className: "rounded-lg bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40",
|
|
1100
|
+
children: "Save agent"
|
|
1101
|
+
}
|
|
1102
|
+
)
|
|
1103
|
+
] })
|
|
1626
1104
|
] });
|
|
1627
1105
|
}
|
|
1628
1106
|
|
|
1107
|
+
// src/chat/harness-model-compat.ts
|
|
1108
|
+
import { modelProvider, snapHarnessToModel } from "@tangle-network/agent-interface";
|
|
1109
|
+
|
|
1629
1110
|
// src/chat/artifact-agent-dock.tsx
|
|
1630
|
-
import * as
|
|
1111
|
+
import * as React6 from "react";
|
|
1631
1112
|
import { Sparkles as Sparkles2, Send, Square as Square2, X as X2 } from "lucide-react";
|
|
1632
1113
|
import { ChatMessage } from "@tangle-network/ui/chat";
|
|
1633
|
-
import { jsx as
|
|
1114
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1634
1115
|
function defaultScopeLabel(scope) {
|
|
1635
1116
|
if (scope.label) return scope.label;
|
|
1636
1117
|
switch (scope.kind) {
|
|
@@ -1676,23 +1157,23 @@ function ArtifactAgentDock({
|
|
|
1676
1157
|
onError,
|
|
1677
1158
|
className
|
|
1678
1159
|
}) {
|
|
1679
|
-
const [threadId, setThreadId] =
|
|
1680
|
-
const [messages, setMessages] =
|
|
1681
|
-
const [composer, setComposer] =
|
|
1682
|
-
const [streamContent, setStreamContent] =
|
|
1683
|
-
const [sending, setSending] =
|
|
1684
|
-
const [loading, setLoading] =
|
|
1685
|
-
const abortRef =
|
|
1686
|
-
const scrollRef =
|
|
1687
|
-
const scopeTokenRef =
|
|
1688
|
-
const reportError =
|
|
1160
|
+
const [threadId, setThreadId] = React6.useState(null);
|
|
1161
|
+
const [messages, setMessages] = React6.useState([]);
|
|
1162
|
+
const [composer, setComposer] = React6.useState(defaultPrompt ?? "");
|
|
1163
|
+
const [streamContent, setStreamContent] = React6.useState("");
|
|
1164
|
+
const [sending, setSending] = React6.useState(false);
|
|
1165
|
+
const [loading, setLoading] = React6.useState(false);
|
|
1166
|
+
const abortRef = React6.useRef(null);
|
|
1167
|
+
const scrollRef = React6.useRef(null);
|
|
1168
|
+
const scopeTokenRef = React6.useRef(0);
|
|
1169
|
+
const reportError = React6.useCallback(
|
|
1689
1170
|
(message) => {
|
|
1690
1171
|
if (onError) onError(message);
|
|
1691
1172
|
else if (typeof console !== "undefined") console.error("[ArtifactAgentDock]", message);
|
|
1692
1173
|
},
|
|
1693
1174
|
[onError]
|
|
1694
1175
|
);
|
|
1695
|
-
|
|
1176
|
+
React6.useEffect(() => {
|
|
1696
1177
|
if (!open) return;
|
|
1697
1178
|
scopeTokenRef.current += 1;
|
|
1698
1179
|
abortRef.current?.abort();
|
|
@@ -1728,11 +1209,11 @@ function ArtifactAgentDock({
|
|
|
1728
1209
|
cancelled = true;
|
|
1729
1210
|
};
|
|
1730
1211
|
}, [open, scope.kind, scope.key, transport, reportError]);
|
|
1731
|
-
|
|
1212
|
+
React6.useEffect(() => {
|
|
1732
1213
|
if (!scrollRef.current) return;
|
|
1733
1214
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
1734
1215
|
}, [messages.length, streamContent]);
|
|
1735
|
-
const send =
|
|
1216
|
+
const send = React6.useCallback(async () => {
|
|
1736
1217
|
const text = composer.trim();
|
|
1737
1218
|
if (!text || !threadId || sending) return;
|
|
1738
1219
|
const token = scopeTokenRef.current;
|
|
@@ -1782,7 +1263,7 @@ function ArtifactAgentDock({
|
|
|
1782
1263
|
if (abortRef.current === controller) abortRef.current = null;
|
|
1783
1264
|
}
|
|
1784
1265
|
}, [composer, threadId, sending, transport, reportError]);
|
|
1785
|
-
const cancel =
|
|
1266
|
+
const cancel = React6.useCallback(() => {
|
|
1786
1267
|
if (threadId && transport.cancel) {
|
|
1787
1268
|
try {
|
|
1788
1269
|
void Promise.resolve(transport.cancel({ threadId })).catch(() => {
|
|
@@ -1807,36 +1288,36 @@ function ArtifactAgentDock({
|
|
|
1807
1288
|
children: [
|
|
1808
1289
|
/* @__PURE__ */ jsxs5("header", { className: "flex items-center justify-between border-b border-[var(--md3-outline-variant)] bg-surface-container-low px-4 py-3", children: [
|
|
1809
1290
|
/* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
1810
|
-
/* @__PURE__ */
|
|
1291
|
+
/* @__PURE__ */ jsx6(Sparkles2, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
1811
1292
|
/* @__PURE__ */ jsxs5("div", { className: "min-w-0", children: [
|
|
1812
|
-
/* @__PURE__ */
|
|
1813
|
-
/* @__PURE__ */
|
|
1293
|
+
/* @__PURE__ */ jsx6("div", { className: "text-[10px] font-medium uppercase tracking-wider text-muted-foreground", children: "Discussing" }),
|
|
1294
|
+
/* @__PURE__ */ jsx6("div", { className: "truncate text-sm font-semibold text-foreground", children: heading })
|
|
1814
1295
|
] })
|
|
1815
1296
|
] }),
|
|
1816
|
-
/* @__PURE__ */
|
|
1297
|
+
/* @__PURE__ */ jsx6(
|
|
1817
1298
|
"button",
|
|
1818
1299
|
{
|
|
1819
1300
|
type: "button",
|
|
1820
1301
|
onClick: () => onOpenChange(false),
|
|
1821
1302
|
"aria-label": "Close artifact chat",
|
|
1822
1303
|
className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-surface-container-high hover:text-foreground",
|
|
1823
|
-
children: /* @__PURE__ */
|
|
1304
|
+
children: /* @__PURE__ */ jsx6(X2, { className: "h-4 w-4" })
|
|
1824
1305
|
}
|
|
1825
1306
|
)
|
|
1826
1307
|
] }),
|
|
1827
|
-
/* @__PURE__ */
|
|
1828
|
-
/* @__PURE__ */
|
|
1308
|
+
/* @__PURE__ */ jsx6("div", { ref: scrollRef, className: "flex-1 overflow-y-auto px-3 py-4", children: loading ? /* @__PURE__ */ jsx6("div", { className: "px-3 py-8 text-center text-xs text-muted-foreground", children: "Loading conversation\u2026" }) : messages.length === 0 && !streamContent ? /* @__PURE__ */ jsxs5("div", { className: "px-3 py-12 text-center", children: [
|
|
1309
|
+
/* @__PURE__ */ jsx6(Sparkles2, { className: "mx-auto mb-3 h-5 w-5 text-muted-foreground/40" }),
|
|
1829
1310
|
/* @__PURE__ */ jsxs5("p", { className: "text-sm text-foreground", children: [
|
|
1830
1311
|
"Ask the agent about this ",
|
|
1831
1312
|
scope.kind === "vault-file" ? "document" : scope.kind,
|
|
1832
1313
|
"."
|
|
1833
1314
|
] }),
|
|
1834
|
-
/* @__PURE__ */
|
|
1315
|
+
/* @__PURE__ */ jsx6("p", { className: "mt-1 text-xs text-muted-foreground", children: "It can read the current contents and propose edits, rewrites, or follow-ups." })
|
|
1835
1316
|
] }) : /* @__PURE__ */ jsxs5("div", { className: "space-y-3", children: [
|
|
1836
|
-
messages.map((m) => /* @__PURE__ */
|
|
1837
|
-
streamContent && /* @__PURE__ */
|
|
1317
|
+
messages.map((m) => /* @__PURE__ */ jsx6(ChatMessage, { role: m.role, content: m.content, timestamp: m.createdAt }, m.id)),
|
|
1318
|
+
streamContent && /* @__PURE__ */ jsx6(ChatMessage, { role: "assistant", content: streamContent, isStreaming: true })
|
|
1838
1319
|
] }) }),
|
|
1839
|
-
/* @__PURE__ */
|
|
1320
|
+
/* @__PURE__ */ jsx6(
|
|
1840
1321
|
"form",
|
|
1841
1322
|
{
|
|
1842
1323
|
className: "border-t border-[var(--md3-outline-variant)] bg-surface-container-low p-3",
|
|
@@ -1845,7 +1326,7 @@ function ArtifactAgentDock({
|
|
|
1845
1326
|
void send();
|
|
1846
1327
|
},
|
|
1847
1328
|
children: /* @__PURE__ */ jsxs5("div", { className: "flex items-end gap-2", children: [
|
|
1848
|
-
/* @__PURE__ */
|
|
1329
|
+
/* @__PURE__ */ jsx6(
|
|
1849
1330
|
"textarea",
|
|
1850
1331
|
{
|
|
1851
1332
|
value: composer,
|
|
@@ -1862,23 +1343,23 @@ function ArtifactAgentDock({
|
|
|
1862
1343
|
className: "min-h-[44px] flex-1 resize-none rounded-[20px] border border-[var(--md3-outline-variant)] bg-surface-dim px-3.5 py-2 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:outline-none focus:border-[var(--border-accent)] disabled:opacity-50"
|
|
1863
1344
|
}
|
|
1864
1345
|
),
|
|
1865
|
-
sending ? /* @__PURE__ */
|
|
1346
|
+
sending ? /* @__PURE__ */ jsx6(
|
|
1866
1347
|
"button",
|
|
1867
1348
|
{
|
|
1868
1349
|
type: "button",
|
|
1869
1350
|
onClick: cancel,
|
|
1870
1351
|
"aria-label": "Cancel",
|
|
1871
1352
|
className: "inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-surface-container-high hover:text-foreground",
|
|
1872
|
-
children: /* @__PURE__ */
|
|
1353
|
+
children: /* @__PURE__ */ jsx6(Square2, { className: "h-4 w-4" })
|
|
1873
1354
|
}
|
|
1874
|
-
) : /* @__PURE__ */
|
|
1355
|
+
) : /* @__PURE__ */ jsx6(
|
|
1875
1356
|
"button",
|
|
1876
1357
|
{
|
|
1877
1358
|
type: "submit",
|
|
1878
1359
|
disabled: !composer.trim() || !threadId,
|
|
1879
1360
|
"aria-label": "Send",
|
|
1880
1361
|
className: "inline-flex h-9 w-9 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50",
|
|
1881
|
-
children: /* @__PURE__ */
|
|
1362
|
+
children: /* @__PURE__ */ jsx6(Send, { className: "h-4 w-4" })
|
|
1882
1363
|
}
|
|
1883
1364
|
)
|
|
1884
1365
|
] })
|
|
@@ -1968,15 +1449,12 @@ export {
|
|
|
1968
1449
|
DEFAULT_REASONING_LEVEL_OPTIONS,
|
|
1969
1450
|
HARNESS_REASONING_OPTIONS,
|
|
1970
1451
|
ReasoningLevelPicker,
|
|
1971
|
-
isModelCompatibleWithHarness,
|
|
1972
|
-
snapModelToHarness,
|
|
1973
|
-
modelProvider,
|
|
1974
|
-
snapHarnessToModel,
|
|
1975
|
-
AgentProfilePicker,
|
|
1976
|
-
AgentSessionControls,
|
|
1977
1452
|
isAcceptedType,
|
|
1978
1453
|
validateComposerFiles,
|
|
1979
1454
|
AgentComposer,
|
|
1455
|
+
AgentProfilePicker,
|
|
1456
|
+
modelProvider,
|
|
1457
|
+
snapHarnessToModel,
|
|
1980
1458
|
ArtifactAgentDock,
|
|
1981
1459
|
createFetchTransport,
|
|
1982
1460
|
AgentTimeline,
|