opencami 1.6.1 → 1.8.2
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/README.md +4 -2
- package/dist/client/assets/{CSPContext-CCZJblsW.js → CSPContext-DeJH85nm.js} +1 -1
- package/dist/client/assets/{DirectionContext-CAS2SgNN.js → DirectionContext-CxhRpXkm.js} +1 -1
- package/dist/client/assets/_sessionKey-CQE0brGK.js +23 -0
- package/dist/client/assets/agents-CMTFd_sG.js +2 -0
- package/dist/client/assets/agents-screen-BNQGEqcW.js +1 -0
- package/dist/client/assets/bots-B6oGzCxP.js +2 -0
- package/dist/client/assets/bots-screen-Be3cfGgq.js +1 -0
- package/dist/client/assets/button-D9Plv7hu.js +1 -0
- package/dist/client/assets/composite-B2KCZKKL.js +1 -0
- package/dist/client/assets/{connect-9Vns9nqk.js → connect-DuJfnyNK.js} +1 -1
- package/dist/client/assets/dashboard-00GpXm5V.js +1 -0
- package/dist/client/assets/event-DD8Cz4O9.js +1 -0
- package/dist/client/assets/file-explorer-screen-CxwemBES.js +1 -0
- package/dist/client/assets/files-DyBJVXBu.js +2 -0
- package/dist/client/assets/{index-BvN0Cj0s.js → index-DtGzE-ea.js} +1 -1
- package/dist/client/assets/{index-CRgPm0pb.js → index-Yo5UhdZV.js} +1 -1
- package/dist/client/assets/keyboard-shortcuts-dialog-BZwd-iyV.js +1 -0
- package/dist/client/assets/{main-ZPHfbCuz.js → main-CgwdHc9W.js} +27 -10
- package/dist/client/assets/{markdown-1-xJ1B1H.js → markdown-DtWnt4NA.js} +1 -1
- package/dist/client/assets/memory-l756yiNq.js +2 -0
- package/dist/client/assets/memory-screen-BQtVRuzE.js +1 -0
- package/dist/client/assets/menu-BsS6CDf_.js +1 -0
- package/dist/client/assets/{opencami-logo-CxC-KcS9.js → opencami-logo-Bmge6-FB.js} +1 -1
- package/dist/client/assets/popupStateMapping-D0ZbJR_o.js +1 -0
- package/dist/client/assets/{proxy-aJlrsYWj.js → proxy-CYZeDXoy.js} +1 -1
- package/dist/client/assets/{react-DuiuJSA1.js → react-DODKNyyU.js} +1 -1
- package/dist/client/assets/search-dialog-DW91SK30.js +1 -0
- package/dist/client/assets/session-export-dialog-CliO9Ob-.js +1 -0
- package/dist/client/assets/settings-dialog-C1u52aju.js +1 -0
- package/dist/client/assets/skills-8T_avaVb.js +2 -0
- package/dist/client/assets/{skills-panel-oNmWCyiv.js → skills-panel-DSiH-DLs.js} +1 -1
- package/dist/client/assets/styles-DvaLh0o1.css +1 -0
- package/dist/client/assets/switch-DbgQPO6i.js +1 -0
- package/dist/client/assets/tabs-BsAvZnlD.js +1 -0
- package/dist/client/assets/tooltip-DLmutB5C.js +1 -0
- package/dist/client/assets/use-file-explorer-state-Cg_yDYJl.js +12 -0
- package/dist/client/assets/useBaseUiId-KQTzRPLp.js +1 -0
- package/dist/client/assets/useCompositeItem-BPY2_hF_.js +1 -0
- package/dist/client/assets/{useControlled-byIifl1i.js → useControlled-B5pEEz2V.js} +1 -1
- package/dist/client/assets/{useMutation-D5JCmjGc.js → useMutation-BsQD6FKe.js} +1 -1
- package/dist/client/assets/useQuery-CmAJuY2W.js +1 -0
- package/dist/client/assets/visuallyHidden-COI6QeQH.js +1 -0
- package/dist/client/sw.js +5 -164
- package/dist/server/assets/{_sessionKey-BCdHKOq7.js → _sessionKey-Bq_fl7uv.js} +1070 -695
- package/dist/server/assets/_tanstack-start-manifest_v-BMCAWon2.js +4 -0
- package/dist/server/assets/dashboard-GCKodTiJ.js +214 -0
- package/dist/server/assets/{index-r4KOEzK3.js → index-C2hVqxBl.js} +2 -1
- package/dist/server/assets/{router-CkcOXH0V.js → router-bN_iTo0B.js} +518 -193
- package/dist/server/assets/{search-dialog-CuuZvlyq.js → search-dialog-DReM5ZD2.js} +3 -2
- package/dist/server/assets/settings-dialog-BUOrQN3Z.js +1511 -0
- package/dist/server/server.js +2 -2
- package/package.json +1 -1
- package/dist/client/assets/_sessionKey-f5UgmhlK.js +0 -14
- package/dist/client/assets/agents-DBx41wBT.js +0 -2
- package/dist/client/assets/agents-screen-CjqM2uOb.js +0 -1
- package/dist/client/assets/bots-BqD0EQcs.js +0 -2
- package/dist/client/assets/bots-screen-ivWEgfTv.js +0 -1
- package/dist/client/assets/button-DzGUU5Tc.js +0 -1
- package/dist/client/assets/composite-afaLtloO.js +0 -1
- package/dist/client/assets/file-explorer-screen-Di0232pk.js +0 -1
- package/dist/client/assets/files-BlMyTMb7.js +0 -2
- package/dist/client/assets/keyboard-shortcuts-dialog-C5kjcplH.js +0 -1
- package/dist/client/assets/memory-rR6pG57i.js +0 -2
- package/dist/client/assets/memory-screen-BV4YRKim.js +0 -1
- package/dist/client/assets/menu-9GT8MQlt.js +0 -1
- package/dist/client/assets/owner-Bm20thei.js +0 -1
- package/dist/client/assets/popupStateMapping-C797EAip.js +0 -1
- package/dist/client/assets/search-dialog-DD_ESAph.js +0 -1
- package/dist/client/assets/session-export-dialog-5X5WKL4z.js +0 -1
- package/dist/client/assets/settings-dialog-Bd0RXSk3.js +0 -1
- package/dist/client/assets/skills-DAKbQw5l.js +0 -2
- package/dist/client/assets/styles-Ecf_rLDJ.css +0 -1
- package/dist/client/assets/switch-BJY4FNiL.js +0 -1
- package/dist/client/assets/tabs-DBIBgGyG.js +0 -1
- package/dist/client/assets/tooltip-BoKzJ7ag.js +0 -1
- package/dist/client/assets/use-file-explorer-state-EK2HTABk.js +0 -12
- package/dist/client/assets/useButton-Bh6gEhdL.js +0 -1
- package/dist/client/assets/useCompositeItem-BR34_3gK.js +0 -1
- package/dist/client/assets/visuallyHidden-ChUPUs-q.js +0 -1
- package/dist/server/assets/_tanstack-start-manifest_v-XVyva8_b.js +0 -4
- package/dist/server/assets/settings-dialog-S2HICL7l.js +0 -1166
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Link, useNavigate } from "@tanstack/react-router";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import React__default, {
|
|
4
|
+
import React__default, { useState, useCallback, memo, useDeferredValue, useMemo, Suspense, lazy, useRef, useEffect, useLayoutEffect, createContext, useContext } from "react";
|
|
5
5
|
import { useQueryClient, useMutation, useQuery } from "@tanstack/react-query";
|
|
6
6
|
import { T as TooltipProvider, a as TooltipRoot, b as TooltipTrigger, c as TooltipContent, s as setChatUiState, d as chatUiQueryKey, g as getChatUiState } from "./tooltip-DgsSPocE.js";
|
|
7
|
+
import { Tick01Icon, Cancel01Icon, MoreHorizontalIcon, Pen01Icon, Upload01Icon, Delete01Icon, BotIcon, Clock01Icon, Chat01Icon, ArrowRight01Icon, SidebarLeft01Icon, PencilEdit02Icon, Folder01Icon, AiBrain01Icon, PackageOpenIcon, SmartPhone01Icon, DashboardCircleIcon, Search01Icon, Settings01Icon, Menu01Icon, Tick02Icon, Copy01Icon, Loading02Icon, StopIcon, VolumeHighIcon, ArrowDown01Icon, Loading03Icon, File01Icon, ArtificialIntelligence02Icon, CommandIcon, Attachment01Icon, Mic02Icon, ArrowUp02Icon } from "@hugeicons/core-free-icons";
|
|
7
8
|
import { HugeiconsIcon } from "@hugeicons/react";
|
|
8
|
-
import { Tick01Icon, Cancel01Icon, MoreHorizontalIcon, Pen01Icon, Upload01Icon, Delete01Icon, BotIcon, Clock01Icon, Chat01Icon, ArrowRight01Icon, SidebarLeft01Icon, PencilEdit02Icon, Folder01Icon, AiBrain01Icon, PackageOpenIcon, SmartPhone01Icon, Search01Icon, Settings01Icon, Menu01Icon, Tick02Icon, Copy01Icon, Loading02Icon, StopIcon, VolumeHighIcon, ArrowDown01Icon, Loading03Icon, ArtificialIntelligence02Icon, CommandIcon, Attachment01Icon, File01Icon, Mic02Icon, ArrowUp02Icon } from "@hugeicons/core-free-icons";
|
|
9
9
|
import { motion, AnimatePresence } from "motion/react";
|
|
10
|
-
import { D as DialogRoot, a as DialogContent, b as DialogTitle, c as DialogDescription, d as DialogClose } from "./use-file-explorer-state-s7CS50ho.js";
|
|
11
|
-
import { B as Button, c as cn, b as buttonVariants } from "./button-CwY2OHFj.js";
|
|
12
10
|
import { AlertDialog } from "@base-ui/react/alert-dialog";
|
|
11
|
+
import { c as cn, B as Button, b as buttonVariants } from "./button-CwY2OHFj.js";
|
|
12
|
+
import { D as DialogRoot, a as DialogContent, b as DialogTitle, c as DialogDescription, d as DialogClose, u as useFileExplorerState } from "./use-file-explorer-state-s7CS50ho.js";
|
|
13
13
|
import { Collapsible as Collapsible$1 } from "@base-ui/react/collapsible";
|
|
14
14
|
import { ScrollArea } from "@base-ui/react/scroll-area";
|
|
15
15
|
import { M as MenuRoot, a as MenuTrigger, b as MenuContent, c as MenuItem } from "./menu-D90CDTi2.js";
|
|
@@ -19,7 +19,7 @@ import { u as useChatSettings$1 } from "./index-Dl2BOKP7.js";
|
|
|
19
19
|
import { create } from "zustand";
|
|
20
20
|
import { persist } from "zustand/middleware";
|
|
21
21
|
import { createPortal } from "react-dom";
|
|
22
|
-
import { a as Route } from "./router-
|
|
22
|
+
import { a as Route } from "./router-bN_iTo0B.js";
|
|
23
23
|
function deriveFriendlyIdFromKey(key) {
|
|
24
24
|
if (!key) return "main";
|
|
25
25
|
const trimmed = key.trim();
|
|
@@ -29,9 +29,27 @@ function deriveFriendlyIdFromKey(key) {
|
|
|
29
29
|
const tailTrimmed = tail.trim();
|
|
30
30
|
return tailTrimmed.length > 0 ? tailTrimmed : trimmed;
|
|
31
31
|
}
|
|
32
|
+
const INBOUND_META_FENCED_REGEX = /^Conversation info \(untrusted metadata\):\s*```(?:json)?\s*[\s\S]*?```\s*/;
|
|
33
|
+
const INBOUND_META_INLINE_REGEX = /^Conversation info \(untrusted metadata\):\s*\{[\s\S]*?\}\s*/;
|
|
34
|
+
const INBOUND_META_TIMESTAMP_REGEX = /^\[(?:[A-Za-z]{3}\s+)?\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}\s+GMT[+-]?\d+\]\s*/;
|
|
35
|
+
function stripInboundMeta(text) {
|
|
36
|
+
let s = text;
|
|
37
|
+
const hasFenced = INBOUND_META_FENCED_REGEX.test(s);
|
|
38
|
+
const hasInline = !hasFenced && INBOUND_META_INLINE_REGEX.test(s);
|
|
39
|
+
if (hasFenced) {
|
|
40
|
+
s = s.replace(INBOUND_META_FENCED_REGEX, "");
|
|
41
|
+
} else if (hasInline) {
|
|
42
|
+
s = s.replace(INBOUND_META_INLINE_REGEX, "");
|
|
43
|
+
}
|
|
44
|
+
if (hasFenced || hasInline) {
|
|
45
|
+
s = s.replace(INBOUND_META_TIMESTAMP_REGEX, "");
|
|
46
|
+
}
|
|
47
|
+
return s.trim();
|
|
48
|
+
}
|
|
32
49
|
function textFromMessage(msg) {
|
|
33
50
|
const parts = Array.isArray(msg.content) ? msg.content : [];
|
|
34
|
-
|
|
51
|
+
const raw = parts.map((part) => part.type === "text" ? String(part.text ?? "") : "").join("").trim();
|
|
52
|
+
return stripInboundMeta(raw);
|
|
35
53
|
}
|
|
36
54
|
function getToolCallsFromMessage(msg) {
|
|
37
55
|
const parts = Array.isArray(msg.content) ? msg.content : [];
|
|
@@ -337,332 +355,606 @@ async function updateSessionLabel(queryClient, sessionKey, friendlyId, label) {
|
|
|
337
355
|
console.error("[updateSessionLabel] Network error:", err);
|
|
338
356
|
}
|
|
339
357
|
}
|
|
340
|
-
function
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
358
|
+
function useChatSettings() {
|
|
359
|
+
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
360
|
+
const [pathsLoading, setPathsLoading] = useState(false);
|
|
361
|
+
const [pathsError, setPathsError] = useState(null);
|
|
362
|
+
const [paths, setPaths] = useState(null);
|
|
363
|
+
const openSettings = useCallback(async () => {
|
|
364
|
+
setSettingsOpen(true);
|
|
365
|
+
setPathsError(null);
|
|
366
|
+
if (pathsLoading || paths) return;
|
|
367
|
+
setPathsLoading(true);
|
|
368
|
+
try {
|
|
369
|
+
const res = await fetch("/api/paths");
|
|
370
|
+
if (!res.ok) throw new Error(await readError(res));
|
|
371
|
+
const data = await res.json();
|
|
372
|
+
setPaths({
|
|
373
|
+
agentId: String(data.agentId ?? "main"),
|
|
374
|
+
stateDir: String(data.stateDir ?? ""),
|
|
375
|
+
sessionsDir: String(data.sessionsDir ?? ""),
|
|
376
|
+
storePath: String(data.storePath ?? "")
|
|
377
|
+
});
|
|
378
|
+
} catch (err) {
|
|
379
|
+
setPathsError(err instanceof Error ? err.message : String(err));
|
|
380
|
+
} finally {
|
|
381
|
+
setPathsLoading(false);
|
|
360
382
|
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
};
|
|
374
|
-
return JSON.stringify(data, null, 2);
|
|
375
|
-
}
|
|
376
|
-
function formatAsPlainText(title, messages, date) {
|
|
377
|
-
const lines = [];
|
|
378
|
-
lines.push(`Conversation: ${title}`);
|
|
379
|
-
lines.push(`Date: ${date.toLocaleString()}`);
|
|
380
|
-
lines.push("─".repeat(60));
|
|
381
|
-
lines.push("");
|
|
382
|
-
for (const message of messages) {
|
|
383
|
-
const role = message.role || "unknown";
|
|
384
|
-
const text = extractMessageText(message);
|
|
385
|
-
if (text) {
|
|
386
|
-
const displayRole = role === "user" ? "User" : "Assistant";
|
|
387
|
-
lines.push(`${displayRole}:`);
|
|
388
|
-
lines.push(text);
|
|
389
|
-
lines.push("");
|
|
383
|
+
}, [paths, pathsLoading]);
|
|
384
|
+
const handleOpenSettings = useCallback(() => {
|
|
385
|
+
void openSettings();
|
|
386
|
+
}, [openSettings]);
|
|
387
|
+
const closeSettings = useCallback(() => {
|
|
388
|
+
setSettingsOpen(false);
|
|
389
|
+
}, []);
|
|
390
|
+
const copySessionsDir = useCallback(() => {
|
|
391
|
+
if (!paths?.sessionsDir) return;
|
|
392
|
+
try {
|
|
393
|
+
void navigator.clipboard.writeText(paths.sessionsDir);
|
|
394
|
+
} catch {
|
|
390
395
|
}
|
|
391
|
-
}
|
|
392
|
-
|
|
396
|
+
}, [paths]);
|
|
397
|
+
const copyStorePath = useCallback(() => {
|
|
398
|
+
if (!paths?.storePath) return;
|
|
399
|
+
try {
|
|
400
|
+
void navigator.clipboard.writeText(paths.storePath);
|
|
401
|
+
} catch {
|
|
402
|
+
}
|
|
403
|
+
}, [paths]);
|
|
404
|
+
return {
|
|
405
|
+
settingsOpen,
|
|
406
|
+
setSettingsOpen,
|
|
407
|
+
pathsLoading,
|
|
408
|
+
pathsError,
|
|
409
|
+
paths,
|
|
410
|
+
handleOpenSettings,
|
|
411
|
+
closeSettings,
|
|
412
|
+
copySessionsDir,
|
|
413
|
+
copyStorePath
|
|
414
|
+
};
|
|
393
415
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
switch (format) {
|
|
400
|
-
case "markdown":
|
|
401
|
-
content = formatAsMarkdown(title, messages, date);
|
|
402
|
-
filename = `${sanitizeFilename(title)}.md`;
|
|
403
|
-
mimeType = "text/markdown";
|
|
404
|
-
break;
|
|
405
|
-
case "json":
|
|
406
|
-
content = formatAsJson(title, messages, date);
|
|
407
|
-
filename = `${sanitizeFilename(title)}.json`;
|
|
408
|
-
mimeType = "application/json";
|
|
409
|
-
break;
|
|
410
|
-
case "txt":
|
|
411
|
-
content = formatAsPlainText(title, messages, date);
|
|
412
|
-
filename = `${sanitizeFilename(title)}.txt`;
|
|
413
|
-
mimeType = "text/plain";
|
|
414
|
-
break;
|
|
415
|
-
default:
|
|
416
|
-
throw new Error(`Unsupported format: ${format}`);
|
|
417
|
-
}
|
|
418
|
-
downloadFile(content, filename, mimeType);
|
|
416
|
+
let pendingSend = null;
|
|
417
|
+
let pendingGeneration = false;
|
|
418
|
+
let recentSession = null;
|
|
419
|
+
function stashPendingSend(payload) {
|
|
420
|
+
pendingSend = payload;
|
|
419
421
|
}
|
|
420
|
-
function
|
|
421
|
-
return
|
|
422
|
+
function hasPendingSend() {
|
|
423
|
+
return pendingSend !== null;
|
|
422
424
|
}
|
|
423
|
-
function
|
|
424
|
-
|
|
425
|
-
const url = URL.createObjectURL(blob);
|
|
426
|
-
const link = document.createElement("a");
|
|
427
|
-
link.href = url;
|
|
428
|
-
link.download = filename;
|
|
429
|
-
document.body.appendChild(link);
|
|
430
|
-
link.click();
|
|
431
|
-
document.body.removeChild(link);
|
|
432
|
-
URL.revokeObjectURL(url);
|
|
425
|
+
function setPendingGeneration(value) {
|
|
426
|
+
pendingGeneration = value;
|
|
433
427
|
}
|
|
434
|
-
function
|
|
435
|
-
|
|
436
|
-
onOpenChange,
|
|
437
|
-
sessionTitle,
|
|
438
|
-
onSave,
|
|
439
|
-
onCancel
|
|
440
|
-
}) {
|
|
441
|
-
return /* @__PURE__ */ jsx(DialogRoot, { open, onOpenChange, children: /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
|
|
442
|
-
/* @__PURE__ */ jsx(DialogTitle, { className: "mb-1", children: "Rename" }),
|
|
443
|
-
/* @__PURE__ */ jsx(DialogDescription, { className: "mb-4", children: "Enter a new name for this session." }),
|
|
444
|
-
/* @__PURE__ */ jsx(
|
|
445
|
-
"input",
|
|
446
|
-
{
|
|
447
|
-
type: "text",
|
|
448
|
-
defaultValue: sessionTitle,
|
|
449
|
-
onKeyDown: (e) => {
|
|
450
|
-
if (e.key === "Enter") {
|
|
451
|
-
e.preventDefault();
|
|
452
|
-
onSave(e.currentTarget.value);
|
|
453
|
-
}
|
|
454
|
-
},
|
|
455
|
-
className: "w-full rounded-lg border border-primary-200 bg-primary-50 px-3 py-2 text-sm text-primary-900 outline-none focus:border-primary-400",
|
|
456
|
-
placeholder: "Session name",
|
|
457
|
-
autoFocus: true
|
|
458
|
-
}
|
|
459
|
-
),
|
|
460
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
|
|
461
|
-
/* @__PURE__ */ jsx(DialogClose, { onClick: onCancel, children: "Cancel" }),
|
|
462
|
-
/* @__PURE__ */ jsx(
|
|
463
|
-
Button,
|
|
464
|
-
{
|
|
465
|
-
onClick: (e) => {
|
|
466
|
-
const input = e.currentTarget.parentElement?.previousElementSibling;
|
|
467
|
-
onSave(input.value);
|
|
468
|
-
},
|
|
469
|
-
children: "Save"
|
|
470
|
-
}
|
|
471
|
-
)
|
|
472
|
-
] })
|
|
473
|
-
] }) }) });
|
|
428
|
+
function hasPendingGeneration() {
|
|
429
|
+
return pendingGeneration;
|
|
474
430
|
}
|
|
475
|
-
function
|
|
476
|
-
|
|
431
|
+
function resetPendingSend() {
|
|
432
|
+
pendingSend = null;
|
|
433
|
+
pendingGeneration = false;
|
|
477
434
|
}
|
|
478
|
-
function
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
"transition-all duration-150",
|
|
488
|
-
"data-[state=open]:opacity-100 data-[state=closed]:opacity-0",
|
|
489
|
-
"data-[state=open]:scale-100 data-[state=closed]:scale-95",
|
|
490
|
-
className
|
|
491
|
-
),
|
|
492
|
-
children
|
|
493
|
-
}
|
|
494
|
-
)
|
|
495
|
-
] });
|
|
435
|
+
function clearPendingSendForSession(sessionKey, friendlyId) {
|
|
436
|
+
if (!pendingSend) return;
|
|
437
|
+
if (sessionKey && pendingSend.sessionKey === sessionKey) {
|
|
438
|
+
resetPendingSend();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (friendlyId && pendingSend.friendlyId === friendlyId) {
|
|
442
|
+
resetPendingSend();
|
|
443
|
+
}
|
|
496
444
|
}
|
|
497
|
-
function
|
|
498
|
-
|
|
499
|
-
AlertDialog.Title,
|
|
500
|
-
{
|
|
501
|
-
className: cn("text-lg font-medium text-primary-900", className),
|
|
502
|
-
...props
|
|
503
|
-
}
|
|
504
|
-
);
|
|
445
|
+
function setRecentSession(friendlyId) {
|
|
446
|
+
recentSession = { friendlyId, at: Date.now() };
|
|
505
447
|
}
|
|
506
|
-
function
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
return
|
|
511
|
-
AlertDialog.Description,
|
|
512
|
-
{
|
|
513
|
-
className: cn("text-sm text-primary-600", className),
|
|
514
|
-
...props
|
|
515
|
-
}
|
|
516
|
-
);
|
|
448
|
+
function isRecentSession(friendlyId, maxAgeMs = 15e3) {
|
|
449
|
+
if (!recentSession) return false;
|
|
450
|
+
if (recentSession.friendlyId !== friendlyId) return false;
|
|
451
|
+
if (Date.now() - recentSession.at > maxAgeMs) return false;
|
|
452
|
+
return true;
|
|
517
453
|
}
|
|
518
|
-
function
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
)
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
render: /* @__PURE__ */ jsx(Button, { variant: "destructive", className: cn(className) }),
|
|
532
|
-
...props
|
|
533
|
-
}
|
|
534
|
-
);
|
|
454
|
+
function consumePendingSend(sessionKey, friendlyId) {
|
|
455
|
+
if (!pendingSend) return null;
|
|
456
|
+
if (sessionKey && pendingSend.sessionKey === sessionKey) {
|
|
457
|
+
const payload = pendingSend;
|
|
458
|
+
pendingSend = null;
|
|
459
|
+
return payload;
|
|
460
|
+
}
|
|
461
|
+
if (friendlyId && pendingSend.friendlyId === friendlyId) {
|
|
462
|
+
const payload = pendingSend;
|
|
463
|
+
pendingSend = null;
|
|
464
|
+
return payload;
|
|
465
|
+
}
|
|
466
|
+
return null;
|
|
535
467
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
onCancel
|
|
542
|
-
}) {
|
|
543
|
-
return /* @__PURE__ */ jsx(AlertDialogRoot, { open, onOpenChange, children: /* @__PURE__ */ jsx(AlertDialogContent, { children: /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
|
|
544
|
-
/* @__PURE__ */ jsx(AlertDialogTitle, { className: "mb-1", children: "Delete Session" }),
|
|
545
|
-
/* @__PURE__ */ jsxs(AlertDialogDescription, { className: "mb-4", children: [
|
|
546
|
-
'Are you sure you want to delete "',
|
|
547
|
-
sessionTitle,
|
|
548
|
-
'"? This action cannot be undone.'
|
|
549
|
-
] }),
|
|
550
|
-
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
551
|
-
/* @__PURE__ */ jsx(AlertDialogCancel, { onClick: onCancel, children: "Cancel" }),
|
|
552
|
-
/* @__PURE__ */ jsx(AlertDialogAction, { onClick: onConfirm, children: "Delete" })
|
|
553
|
-
] })
|
|
554
|
-
] }) }) });
|
|
468
|
+
const TOMBSTONE_TTL_MS = 8e3;
|
|
469
|
+
const tombstones = /* @__PURE__ */ new Map();
|
|
470
|
+
function markSessionDeleted(id) {
|
|
471
|
+
if (!id) return;
|
|
472
|
+
tombstones.set(id, { id, expiresAt: Date.now() + TOMBSTONE_TTL_MS });
|
|
555
473
|
}
|
|
556
|
-
function
|
|
557
|
-
|
|
474
|
+
function clearSessionDeleted(id) {
|
|
475
|
+
if (!id) return;
|
|
476
|
+
tombstones.delete(id);
|
|
558
477
|
}
|
|
559
|
-
function
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
478
|
+
function filterSessionsWithTombstones(sessions) {
|
|
479
|
+
if (tombstones.size === 0) return sessions;
|
|
480
|
+
const now = Date.now();
|
|
481
|
+
let changed = false;
|
|
482
|
+
const next = sessions.filter((session) => {
|
|
483
|
+
const keyTombstone = tombstones.get(session.key);
|
|
484
|
+
const friendlyTombstone = tombstones.get(session.friendlyId);
|
|
485
|
+
const isExpired = keyTombstone && keyTombstone.expiresAt <= now || friendlyTombstone && friendlyTombstone.expiresAt <= now;
|
|
486
|
+
if (isExpired) {
|
|
487
|
+
if (keyTombstone && keyTombstone.expiresAt <= now) {
|
|
488
|
+
tombstones.delete(session.key);
|
|
489
|
+
}
|
|
490
|
+
if (friendlyTombstone && friendlyTombstone.expiresAt <= now) {
|
|
491
|
+
tombstones.delete(session.friendlyId);
|
|
492
|
+
}
|
|
493
|
+
return true;
|
|
571
494
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
className,
|
|
576
|
-
contentClassName,
|
|
577
|
-
children,
|
|
578
|
-
...props
|
|
579
|
-
}) {
|
|
580
|
-
return /* @__PURE__ */ jsx(
|
|
581
|
-
Collapsible$1.Panel,
|
|
582
|
-
{
|
|
583
|
-
className: cn(
|
|
584
|
-
'flex h-(--collapsible-panel-height) flex-col overflow-hidden text-sm transition-all duration-150 ease-out data-ending-style:h-0 data-starting-style:h-0 [&[hidden]:not([hidden="until-found"])]:hidden',
|
|
585
|
-
className
|
|
586
|
-
),
|
|
587
|
-
...props,
|
|
588
|
-
children: /* @__PURE__ */ jsx("div", { className: cn("pt-1", contentClassName), children })
|
|
495
|
+
if (keyTombstone || friendlyTombstone) {
|
|
496
|
+
changed = true;
|
|
497
|
+
return false;
|
|
589
498
|
}
|
|
590
|
-
|
|
499
|
+
return true;
|
|
500
|
+
});
|
|
501
|
+
return changed ? next : sessions;
|
|
591
502
|
}
|
|
592
|
-
function
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
),
|
|
600
|
-
|
|
503
|
+
function useDeleteSession() {
|
|
504
|
+
const queryClient = useQueryClient();
|
|
505
|
+
const [deleting, setDeleting] = useState(false);
|
|
506
|
+
const [error, setError] = useState(null);
|
|
507
|
+
const mutation = useMutation({
|
|
508
|
+
mutationFn: async function deleteSessionRequest2(payload) {
|
|
509
|
+
const query = new URLSearchParams();
|
|
510
|
+
if (payload.sessionKey) query.set("sessionKey", payload.sessionKey);
|
|
511
|
+
if (payload.friendlyId) query.set("friendlyId", payload.friendlyId);
|
|
512
|
+
const res = await fetch(`/api/sessions?${query.toString()}`, {
|
|
513
|
+
method: "DELETE"
|
|
514
|
+
});
|
|
515
|
+
if (!res.ok) throw new Error(await readError(res));
|
|
516
|
+
return payload;
|
|
517
|
+
},
|
|
518
|
+
onMutate: async function onMutate(payload) {
|
|
519
|
+
setError(null);
|
|
520
|
+
markSessionDeleted(payload.sessionKey || payload.friendlyId);
|
|
521
|
+
clearPendingSendForSession(payload.sessionKey, payload.friendlyId);
|
|
522
|
+
await queryClient.cancelQueries({ queryKey: chatQueryKeys.sessions });
|
|
523
|
+
const previousSessions = queryClient.getQueryData(chatQueryKeys.sessions);
|
|
524
|
+
removeSessionFromCache(
|
|
525
|
+
queryClient,
|
|
526
|
+
payload.sessionKey,
|
|
527
|
+
payload.friendlyId
|
|
528
|
+
);
|
|
529
|
+
if (payload.isActive && (payload.sessionKey || payload.friendlyId)) {
|
|
530
|
+
clearHistoryMessages(
|
|
531
|
+
queryClient,
|
|
532
|
+
payload.friendlyId || payload.sessionKey,
|
|
533
|
+
payload.sessionKey || payload.friendlyId
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
return { previousSessions, isActive: payload.isActive };
|
|
537
|
+
},
|
|
538
|
+
onError: function onError(err, _payload, context) {
|
|
539
|
+
if (context?.previousSessions) {
|
|
540
|
+
queryClient.setQueryData(
|
|
541
|
+
chatQueryKeys.sessions,
|
|
542
|
+
context.previousSessions
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
clearSessionDeleted(_payload.sessionKey || _payload.friendlyId);
|
|
546
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
547
|
+
},
|
|
548
|
+
onSuccess: function onSuccess(payload) {
|
|
549
|
+
if (payload.isActive) {
|
|
550
|
+
resetPendingSend();
|
|
551
|
+
}
|
|
552
|
+
queryClient.invalidateQueries({ queryKey: chatQueryKeys.sessions });
|
|
553
|
+
},
|
|
554
|
+
onSettled: function onSettled() {
|
|
555
|
+
setDeleting(false);
|
|
601
556
|
}
|
|
557
|
+
});
|
|
558
|
+
const deleteSession = useCallback(
|
|
559
|
+
async (sessionKey, friendlyId, isActive) => {
|
|
560
|
+
if (!sessionKey && !friendlyId) return;
|
|
561
|
+
setDeleting(true);
|
|
562
|
+
await mutation.mutateAsync({ sessionKey, friendlyId, isActive });
|
|
563
|
+
},
|
|
564
|
+
[mutation]
|
|
602
565
|
);
|
|
566
|
+
return { deleteSession, deleting, error };
|
|
603
567
|
}
|
|
604
|
-
function
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
568
|
+
function useRenameSession() {
|
|
569
|
+
const queryClient = useQueryClient();
|
|
570
|
+
const [renaming, setRenaming] = useState(false);
|
|
571
|
+
const [error, setError] = useState(null);
|
|
572
|
+
const mutation = useMutation({
|
|
573
|
+
mutationFn: async function renameSessionRequest(payload) {
|
|
574
|
+
const res = await fetch("/api/sessions", {
|
|
575
|
+
method: "PATCH",
|
|
576
|
+
headers: { "content-type": "application/json" },
|
|
577
|
+
body: JSON.stringify({
|
|
578
|
+
sessionKey: payload.sessionKey,
|
|
579
|
+
label: payload.newTitle
|
|
580
|
+
})
|
|
581
|
+
});
|
|
582
|
+
if (!res.ok) throw new Error(await readError(res));
|
|
583
|
+
return payload;
|
|
584
|
+
},
|
|
585
|
+
onMutate: async function onMutate(payload) {
|
|
586
|
+
setError(null);
|
|
587
|
+
await queryClient.cancelQueries({ queryKey: chatQueryKeys.sessions });
|
|
588
|
+
const previousSessions = queryClient.getQueryData(chatQueryKeys.sessions);
|
|
589
|
+
queryClient.setQueryData(
|
|
590
|
+
chatQueryKeys.sessions,
|
|
591
|
+
function update(sessions) {
|
|
592
|
+
if (!Array.isArray(sessions)) return sessions;
|
|
593
|
+
return sessions.map((session) => {
|
|
594
|
+
if (session.key !== payload.sessionKey) return session;
|
|
595
|
+
return {
|
|
596
|
+
...session,
|
|
597
|
+
label: payload.newTitle,
|
|
598
|
+
title: payload.newTitle
|
|
599
|
+
};
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
);
|
|
603
|
+
return { previousSessions };
|
|
604
|
+
},
|
|
605
|
+
onError: function onError(err, _payload, context) {
|
|
606
|
+
if (context?.previousSessions) {
|
|
607
|
+
queryClient.setQueryData(
|
|
608
|
+
chatQueryKeys.sessions,
|
|
609
|
+
context.previousSessions
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
613
|
+
},
|
|
614
|
+
onSuccess: function onSuccess() {
|
|
615
|
+
queryClient.invalidateQueries({ queryKey: chatQueryKeys.sessions });
|
|
616
|
+
},
|
|
617
|
+
onSettled: function onSettled() {
|
|
618
|
+
setRenaming(false);
|
|
613
619
|
}
|
|
620
|
+
});
|
|
621
|
+
const renameSession = useCallback(
|
|
622
|
+
async (sessionKey, newTitle) => {
|
|
623
|
+
if (!sessionKey || !newTitle.trim()) return;
|
|
624
|
+
setRenaming(true);
|
|
625
|
+
await mutation.mutateAsync({ sessionKey, newTitle: newTitle.trim() });
|
|
626
|
+
},
|
|
627
|
+
[mutation]
|
|
614
628
|
);
|
|
629
|
+
return { renameSession, renaming, error };
|
|
615
630
|
}
|
|
616
|
-
function
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
}
|
|
620
|
-
return
|
|
621
|
-
|
|
631
|
+
function extractMessageText(message) {
|
|
632
|
+
if (!message.content || !Array.isArray(message.content)) {
|
|
633
|
+
return "";
|
|
634
|
+
}
|
|
635
|
+
return message.content.filter((item) => item.type === "text").map((item) => item.text || "").join("\n");
|
|
636
|
+
}
|
|
637
|
+
function formatAsMarkdown(title, messages, date) {
|
|
638
|
+
const lines = [];
|
|
639
|
+
lines.push(`# Conversation: ${title}`);
|
|
640
|
+
lines.push(`Date: ${date.toLocaleString()}`);
|
|
641
|
+
lines.push("");
|
|
642
|
+
for (const message of messages) {
|
|
643
|
+
const role = message.role || "unknown";
|
|
644
|
+
const text = extractMessageText(message);
|
|
645
|
+
if (text) {
|
|
646
|
+
const displayRole = role === "user" ? "User" : "Assistant";
|
|
647
|
+
lines.push(`## ${displayRole}`);
|
|
648
|
+
lines.push("");
|
|
649
|
+
lines.push(text);
|
|
650
|
+
lines.push("");
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return lines.join("\n");
|
|
654
|
+
}
|
|
655
|
+
function formatAsJson(title, messages, date) {
|
|
656
|
+
const data = {
|
|
657
|
+
title,
|
|
658
|
+
exportDate: date.toISOString(),
|
|
659
|
+
messages: messages.map((message) => ({
|
|
660
|
+
role: message.role,
|
|
661
|
+
content: message.content,
|
|
662
|
+
timestamp: message.timestamp
|
|
663
|
+
}))
|
|
664
|
+
};
|
|
665
|
+
return JSON.stringify(data, null, 2);
|
|
666
|
+
}
|
|
667
|
+
function formatAsPlainText(title, messages, date) {
|
|
668
|
+
const lines = [];
|
|
669
|
+
lines.push(`Conversation: ${title}`);
|
|
670
|
+
lines.push(`Date: ${date.toLocaleString()}`);
|
|
671
|
+
lines.push("─".repeat(60));
|
|
672
|
+
lines.push("");
|
|
673
|
+
for (const message of messages) {
|
|
674
|
+
const role = message.role || "unknown";
|
|
675
|
+
const text = extractMessageText(message);
|
|
676
|
+
if (text) {
|
|
677
|
+
const displayRole = role === "user" ? "User" : "Assistant";
|
|
678
|
+
lines.push(`${displayRole}:`);
|
|
679
|
+
lines.push(text);
|
|
680
|
+
lines.push("");
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return lines.join("\n");
|
|
684
|
+
}
|
|
685
|
+
function exportConversation(title, messages, format) {
|
|
686
|
+
const date = /* @__PURE__ */ new Date();
|
|
687
|
+
let content;
|
|
688
|
+
let filename;
|
|
689
|
+
let mimeType;
|
|
690
|
+
switch (format) {
|
|
691
|
+
case "markdown":
|
|
692
|
+
content = formatAsMarkdown(title, messages, date);
|
|
693
|
+
filename = `${sanitizeFilename(title)}.md`;
|
|
694
|
+
mimeType = "text/markdown";
|
|
695
|
+
break;
|
|
696
|
+
case "json":
|
|
697
|
+
content = formatAsJson(title, messages, date);
|
|
698
|
+
filename = `${sanitizeFilename(title)}.json`;
|
|
699
|
+
mimeType = "application/json";
|
|
700
|
+
break;
|
|
701
|
+
case "txt":
|
|
702
|
+
content = formatAsPlainText(title, messages, date);
|
|
703
|
+
filename = `${sanitizeFilename(title)}.txt`;
|
|
704
|
+
mimeType = "text/plain";
|
|
705
|
+
break;
|
|
706
|
+
default:
|
|
707
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
708
|
+
}
|
|
709
|
+
downloadFile(content, filename, mimeType);
|
|
710
|
+
}
|
|
711
|
+
function sanitizeFilename(filename) {
|
|
712
|
+
return filename.replace(/[^a-z0-9-_\s]/gi, "").replace(/\s+/g, "-").toLowerCase().slice(0, 50) || "conversation";
|
|
713
|
+
}
|
|
714
|
+
function downloadFile(content, filename, mimeType) {
|
|
715
|
+
const blob = new Blob([content], { type: mimeType });
|
|
716
|
+
const url = URL.createObjectURL(blob);
|
|
717
|
+
const link = document.createElement("a");
|
|
718
|
+
link.href = url;
|
|
719
|
+
link.download = filename;
|
|
720
|
+
document.body.appendChild(link);
|
|
721
|
+
link.click();
|
|
722
|
+
document.body.removeChild(link);
|
|
723
|
+
URL.revokeObjectURL(url);
|
|
724
|
+
}
|
|
725
|
+
function AlertDialogRoot({ children, ...props }) {
|
|
726
|
+
return /* @__PURE__ */ jsx(AlertDialog.Root, { ...props, children });
|
|
727
|
+
}
|
|
728
|
+
function AlertDialogContent({ className, children }) {
|
|
729
|
+
return /* @__PURE__ */ jsxs(AlertDialog.Portal, { children: [
|
|
730
|
+
/* @__PURE__ */ jsx(AlertDialog.Backdrop, { className: "fixed inset-0 bg-primary-950/20 transition-all duration-150 data-[state=open]:opacity-100 data-[state=closed]:opacity-0" }),
|
|
731
|
+
/* @__PURE__ */ jsx(
|
|
732
|
+
AlertDialog.Popup,
|
|
733
|
+
{
|
|
734
|
+
className: cn(
|
|
735
|
+
"fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2",
|
|
736
|
+
"w-[min(400px,92vw)] rounded-xl border border-primary-200 bg-primary-50 p-0 shadow-xl",
|
|
737
|
+
"transition-all duration-150",
|
|
738
|
+
"data-[state=open]:opacity-100 data-[state=closed]:opacity-0",
|
|
739
|
+
"data-[state=open]:scale-100 data-[state=closed]:scale-95",
|
|
740
|
+
className
|
|
741
|
+
),
|
|
742
|
+
children
|
|
743
|
+
}
|
|
744
|
+
)
|
|
745
|
+
] });
|
|
746
|
+
}
|
|
747
|
+
function AlertDialogTitle({ className, ...props }) {
|
|
748
|
+
return /* @__PURE__ */ jsx(
|
|
749
|
+
AlertDialog.Title,
|
|
750
|
+
{
|
|
751
|
+
className: cn("text-lg font-medium text-primary-900", className),
|
|
752
|
+
...props
|
|
753
|
+
}
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
function AlertDialogDescription({
|
|
757
|
+
className,
|
|
758
|
+
...props
|
|
759
|
+
}) {
|
|
760
|
+
return /* @__PURE__ */ jsx(
|
|
761
|
+
AlertDialog.Description,
|
|
762
|
+
{
|
|
763
|
+
className: cn("text-sm text-primary-600", className),
|
|
764
|
+
...props
|
|
765
|
+
}
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
function AlertDialogCancel({ className, ...props }) {
|
|
769
|
+
return /* @__PURE__ */ jsx(
|
|
770
|
+
AlertDialog.Close,
|
|
771
|
+
{
|
|
772
|
+
render: /* @__PURE__ */ jsx(Button, { variant: "outline", className: cn(className) }),
|
|
773
|
+
...props
|
|
774
|
+
}
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
function AlertDialogAction({ className, ...props }) {
|
|
778
|
+
return /* @__PURE__ */ jsx(
|
|
779
|
+
AlertDialog.Close,
|
|
780
|
+
{
|
|
781
|
+
render: /* @__PURE__ */ jsx(Button, { variant: "destructive", className: cn(className) }),
|
|
782
|
+
...props
|
|
783
|
+
}
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
function SessionDeleteDialog({
|
|
787
|
+
open,
|
|
788
|
+
onOpenChange,
|
|
789
|
+
sessionTitle,
|
|
790
|
+
onConfirm,
|
|
791
|
+
onCancel
|
|
792
|
+
}) {
|
|
793
|
+
return /* @__PURE__ */ jsx(AlertDialogRoot, { open, onOpenChange, children: /* @__PURE__ */ jsx(AlertDialogContent, { children: /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
|
|
794
|
+
/* @__PURE__ */ jsx(AlertDialogTitle, { className: "mb-1", children: "Delete Session" }),
|
|
795
|
+
/* @__PURE__ */ jsxs(AlertDialogDescription, { className: "mb-4", children: [
|
|
796
|
+
'Are you sure you want to delete "',
|
|
797
|
+
sessionTitle,
|
|
798
|
+
'"? This action cannot be undone.'
|
|
799
|
+
] }),
|
|
800
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
801
|
+
/* @__PURE__ */ jsx(AlertDialogCancel, { onClick: onCancel, children: "Cancel" }),
|
|
802
|
+
/* @__PURE__ */ jsx(AlertDialogAction, { onClick: onConfirm, children: "Delete" })
|
|
803
|
+
] })
|
|
804
|
+
] }) }) });
|
|
805
|
+
}
|
|
806
|
+
function SessionRenameDialog({
|
|
807
|
+
open,
|
|
808
|
+
onOpenChange,
|
|
809
|
+
sessionTitle,
|
|
810
|
+
onSave,
|
|
811
|
+
onCancel
|
|
812
|
+
}) {
|
|
813
|
+
return /* @__PURE__ */ jsx(DialogRoot, { open, onOpenChange, children: /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
|
|
814
|
+
/* @__PURE__ */ jsx(DialogTitle, { className: "mb-1", children: "Rename" }),
|
|
815
|
+
/* @__PURE__ */ jsx(DialogDescription, { className: "mb-4", children: "Enter a new name for this session." }),
|
|
816
|
+
/* @__PURE__ */ jsx(
|
|
817
|
+
"input",
|
|
818
|
+
{
|
|
819
|
+
type: "text",
|
|
820
|
+
defaultValue: sessionTitle,
|
|
821
|
+
onKeyDown: (e) => {
|
|
822
|
+
if (e.key === "Enter") {
|
|
823
|
+
e.preventDefault();
|
|
824
|
+
onSave(e.currentTarget.value);
|
|
825
|
+
}
|
|
826
|
+
},
|
|
827
|
+
className: "w-full rounded-lg border border-primary-200 bg-primary-50 px-3 py-2 text-sm text-primary-900 outline-none focus:border-primary-400",
|
|
828
|
+
placeholder: "Session name",
|
|
829
|
+
autoFocus: true
|
|
830
|
+
}
|
|
831
|
+
),
|
|
832
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
|
|
833
|
+
/* @__PURE__ */ jsx(DialogClose, { onClick: onCancel, children: "Cancel" }),
|
|
834
|
+
/* @__PURE__ */ jsx(
|
|
835
|
+
Button,
|
|
836
|
+
{
|
|
837
|
+
onClick: (e) => {
|
|
838
|
+
const input = e.currentTarget.parentElement?.previousElementSibling;
|
|
839
|
+
onSave(input.value);
|
|
840
|
+
},
|
|
841
|
+
children: "Save"
|
|
842
|
+
}
|
|
843
|
+
)
|
|
844
|
+
] })
|
|
845
|
+
] }) }) });
|
|
846
|
+
}
|
|
847
|
+
function Collapsible(props) {
|
|
848
|
+
return /* @__PURE__ */ jsx(Collapsible$1.Root, { ...props });
|
|
849
|
+
}
|
|
850
|
+
function CollapsibleTrigger({
|
|
851
|
+
className,
|
|
852
|
+
...props
|
|
853
|
+
}) {
|
|
854
|
+
return /* @__PURE__ */ jsx(
|
|
855
|
+
Collapsible$1.Trigger,
|
|
622
856
|
{
|
|
623
857
|
className: cn(
|
|
624
|
-
"flex
|
|
625
|
-
"opacity-0 transition-opacity duration-150",
|
|
626
|
-
"data-hovering:opacity-100 data-scrolling:opacity-100 group-hover/scroll-area:opacity-100",
|
|
858
|
+
"group inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-left text-xs font-medium text-primary-500 transition-colors hover:bg-primary-100 hover:text-primary-700 data-panel-open:text-primary-700",
|
|
627
859
|
className
|
|
628
860
|
),
|
|
629
861
|
...props
|
|
630
862
|
}
|
|
631
863
|
);
|
|
632
864
|
}
|
|
633
|
-
function
|
|
865
|
+
function CollapsiblePanel({
|
|
866
|
+
className,
|
|
867
|
+
contentClassName,
|
|
868
|
+
children,
|
|
869
|
+
...props
|
|
870
|
+
}) {
|
|
634
871
|
return /* @__PURE__ */ jsx(
|
|
635
|
-
|
|
872
|
+
Collapsible$1.Panel,
|
|
636
873
|
{
|
|
637
874
|
className: cn(
|
|
638
|
-
|
|
875
|
+
'flex h-(--collapsible-panel-height) flex-col overflow-hidden text-sm transition-all duration-150 ease-out data-ending-style:h-0 data-starting-style:h-0 [&[hidden]:not([hidden="until-found"])]:hidden',
|
|
639
876
|
className
|
|
640
877
|
),
|
|
641
|
-
...props
|
|
878
|
+
...props,
|
|
879
|
+
children: /* @__PURE__ */ jsx("div", { className: cn("pt-1", contentClassName), children })
|
|
642
880
|
}
|
|
643
881
|
);
|
|
644
882
|
}
|
|
645
|
-
function
|
|
883
|
+
function ScrollAreaRoot({ className, ...props }) {
|
|
646
884
|
return /* @__PURE__ */ jsx(
|
|
647
|
-
ScrollArea.
|
|
885
|
+
ScrollArea.Root,
|
|
648
886
|
{
|
|
649
887
|
className: cn(
|
|
650
|
-
"
|
|
888
|
+
"group/scroll-area relative outline-none focus-visible:outline-none",
|
|
651
889
|
className
|
|
652
890
|
),
|
|
653
891
|
...props
|
|
654
892
|
}
|
|
655
893
|
);
|
|
656
894
|
}
|
|
657
|
-
function
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
895
|
+
function ScrollAreaViewport({ className, ...props }) {
|
|
896
|
+
return /* @__PURE__ */ jsx(
|
|
897
|
+
ScrollArea.Viewport,
|
|
898
|
+
{
|
|
899
|
+
className: cn(
|
|
900
|
+
"h-full w-full outline-none focus-visible:outline-none",
|
|
901
|
+
className
|
|
902
|
+
),
|
|
903
|
+
...props
|
|
904
|
+
}
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
function ScrollAreaScrollbar({
|
|
908
|
+
className,
|
|
909
|
+
...props
|
|
910
|
+
}) {
|
|
911
|
+
return /* @__PURE__ */ jsx(
|
|
912
|
+
ScrollArea.Scrollbar,
|
|
913
|
+
{
|
|
914
|
+
className: cn(
|
|
915
|
+
"flex w-2 touch-none select-none p-0.5 outline-none focus-visible:outline-none",
|
|
916
|
+
"opacity-0 transition-opacity duration-150",
|
|
917
|
+
"data-hovering:opacity-100 data-scrolling:opacity-100 group-hover/scroll-area:opacity-100",
|
|
918
|
+
className
|
|
919
|
+
),
|
|
920
|
+
...props
|
|
921
|
+
}
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
function ScrollAreaThumb({ className, ...props }) {
|
|
925
|
+
return /* @__PURE__ */ jsx(
|
|
926
|
+
ScrollArea.Thumb,
|
|
927
|
+
{
|
|
928
|
+
className: cn(
|
|
929
|
+
"flex-1 rounded-full bg-primary-500 outline-none focus-visible:outline-none",
|
|
930
|
+
className
|
|
931
|
+
),
|
|
932
|
+
...props
|
|
933
|
+
}
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
function ScrollAreaCorner({ className, ...props }) {
|
|
937
|
+
return /* @__PURE__ */ jsx(
|
|
938
|
+
ScrollArea.Corner,
|
|
939
|
+
{
|
|
940
|
+
className: cn(
|
|
941
|
+
"bg-primary-100 outline-none focus-visible:outline-none",
|
|
942
|
+
className
|
|
943
|
+
),
|
|
944
|
+
...props
|
|
945
|
+
}
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
function getKindIcon(kind) {
|
|
949
|
+
if (kind === "subagent") return BotIcon;
|
|
950
|
+
if (kind === "cron") return Clock01Icon;
|
|
951
|
+
return Chat01Icon;
|
|
952
|
+
}
|
|
953
|
+
function previewFromMessage(message) {
|
|
954
|
+
if (!message || !Array.isArray(message.content)) return "";
|
|
955
|
+
const text = message.content.map((part) => part.type === "text" ? String(part.text ?? "") : "").join(" ");
|
|
956
|
+
const cleaned = stripInboundMeta(text).replace(/\s+/g, " ").trim();
|
|
957
|
+
return cleaned;
|
|
666
958
|
}
|
|
667
959
|
function normalizeTimestamp$1(value) {
|
|
668
960
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
@@ -1280,389 +1572,116 @@ ${failedSessionLabels.join("\n")}`
|
|
|
1280
1572
|
session,
|
|
1281
1573
|
active: isSessionActive(session, activeFriendlyId, activeSessionKey),
|
|
1282
1574
|
isGenerating: isSessionGenerating(session, activeFriendlyId, activeSessionKey, isStreaming),
|
|
1283
|
-
isPinned: false,
|
|
1284
|
-
selectionMode,
|
|
1285
|
-
selected: selectedSessionKeys.has(session.key),
|
|
1286
|
-
onToggleSelect: handleToggleSelect,
|
|
1287
|
-
onSelect,
|
|
1288
|
-
onTogglePin: handleTogglePin,
|
|
1289
|
-
onRename,
|
|
1290
|
-
onDelete,
|
|
1291
|
-
onExport
|
|
1292
|
-
},
|
|
1293
|
-
session.key
|
|
1294
|
-
)) })
|
|
1295
|
-
] }) : null,
|
|
1296
|
-
renderFolderGroup(
|
|
1297
|
-
"webchat",
|
|
1298
|
-
"🦎 OpenCami",
|
|
1299
|
-
groupedSessions.webchat
|
|
1300
|
-
),
|
|
1301
|
-
renderFolderGroup(
|
|
1302
|
-
"subagent",
|
|
1303
|
-
"🤖 Sub-agents",
|
|
1304
|
-
groupedSessions.subagent
|
|
1305
|
-
),
|
|
1306
|
-
renderFolderGroup(
|
|
1307
|
-
"cron",
|
|
1308
|
-
"⏰ Cron / Isolated",
|
|
1309
|
-
groupedSessions.cron
|
|
1310
|
-
),
|
|
1311
|
-
renderFolderGroup("other", "📁 Other", groupedSessions.other)
|
|
1312
|
-
] }) }),
|
|
1313
|
-
/* @__PURE__ */ jsx(ScrollAreaScrollbar, { orientation: "vertical", children: /* @__PURE__ */ jsx(ScrollAreaThumb, {}) })
|
|
1314
|
-
] })
|
|
1315
|
-
}
|
|
1316
|
-
),
|
|
1317
|
-
selectionMode ? /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-t border-primary-200 bg-surface px-2 py-2 flex flex-col gap-1.5", children: [
|
|
1318
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1319
|
-
/* @__PURE__ */ jsxs("span", { className: "text-xs text-primary-600 font-medium", children: [
|
|
1320
|
-
selectedCount,
|
|
1321
|
-
" selected"
|
|
1322
|
-
] }),
|
|
1323
|
-
/* @__PURE__ */ jsx(
|
|
1324
|
-
"button",
|
|
1325
|
-
{
|
|
1326
|
-
type: "button",
|
|
1327
|
-
onClick: handleSelectAll,
|
|
1328
|
-
className: "text-[11px] font-medium text-primary-600 hover:text-primary-800",
|
|
1329
|
-
children: "Select all"
|
|
1330
|
-
}
|
|
1331
|
-
)
|
|
1332
|
-
] }),
|
|
1333
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
|
|
1334
|
-
/* @__PURE__ */ jsxs(
|
|
1335
|
-
"button",
|
|
1336
|
-
{
|
|
1337
|
-
type: "button",
|
|
1338
|
-
onClick: handleDeleteSelected,
|
|
1339
|
-
disabled: selectedCount === 0,
|
|
1340
|
-
className: cn(
|
|
1341
|
-
"flex-1 inline-flex items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-medium transition-colors",
|
|
1342
|
-
selectedCount > 0 ? "bg-red-100 text-red-700 hover:bg-red-200" : "bg-primary-100 text-primary-400 cursor-not-allowed"
|
|
1343
|
-
),
|
|
1344
|
-
children: [
|
|
1345
|
-
/* @__PURE__ */ jsx(HugeiconsIcon, { icon: Delete01Icon, size: 14, strokeWidth: 1.5 }),
|
|
1346
|
-
"Delete"
|
|
1347
|
-
]
|
|
1348
|
-
}
|
|
1349
|
-
),
|
|
1350
|
-
/* @__PURE__ */ jsx(
|
|
1351
|
-
"button",
|
|
1352
|
-
{
|
|
1353
|
-
type: "button",
|
|
1354
|
-
onClick: handleCancelSelection,
|
|
1355
|
-
className: "flex-1 inline-flex items-center justify-center rounded-md px-2 py-1.5 text-xs font-medium bg-primary-100 text-primary-700 hover:bg-primary-200 transition-colors",
|
|
1356
|
-
children: "Cancel"
|
|
1357
|
-
}
|
|
1358
|
-
)
|
|
1359
|
-
] })
|
|
1360
|
-
] }) : null
|
|
1361
|
-
]
|
|
1362
|
-
}
|
|
1363
|
-
);
|
|
1364
|
-
}, areSidebarSessionsEqual);
|
|
1365
|
-
function areSidebarSessionsEqual(prev, next) {
|
|
1366
|
-
if (prev.activeFriendlyId !== next.activeFriendlyId) return false;
|
|
1367
|
-
if (prev.activeSessionKey !== next.activeSessionKey) return false;
|
|
1368
|
-
if (prev.isStreaming !== next.isStreaming) return false;
|
|
1369
|
-
if (prev.defaultOpen !== next.defaultOpen) return false;
|
|
1370
|
-
if (prev.onSelect !== next.onSelect) return false;
|
|
1371
|
-
if (prev.onRename !== next.onRename) return false;
|
|
1372
|
-
if (prev.onDelete !== next.onDelete) return false;
|
|
1373
|
-
if (prev.onExport !== next.onExport) return false;
|
|
1374
|
-
if (prev.sessions === next.sessions) return true;
|
|
1375
|
-
if (prev.sessions.length !== next.sessions.length) return false;
|
|
1376
|
-
for (let i = 0; i < prev.sessions.length; i += 1) {
|
|
1377
|
-
const prevSession = prev.sessions[i];
|
|
1378
|
-
const nextSession = next.sessions[i];
|
|
1379
|
-
if (prevSession.key !== nextSession.key) return false;
|
|
1380
|
-
if (prevSession.friendlyId !== nextSession.friendlyId) return false;
|
|
1381
|
-
if (prevSession.label !== nextSession.label) return false;
|
|
1382
|
-
if (prevSession.title !== nextSession.title) return false;
|
|
1383
|
-
if (prevSession.derivedTitle !== nextSession.derivedTitle) return false;
|
|
1384
|
-
if (prevSession.updatedAt !== nextSession.updatedAt) return false;
|
|
1385
|
-
if (prevSession.kind !== nextSession.kind) return false;
|
|
1386
|
-
if (prevSession.status !== nextSession.status) return false;
|
|
1387
|
-
if (prevSession.lastMessage !== nextSession.lastMessage) return false;
|
|
1388
|
-
}
|
|
1389
|
-
return true;
|
|
1390
|
-
}
|
|
1391
|
-
function useChatSettings() {
|
|
1392
|
-
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
1393
|
-
const [pathsLoading, setPathsLoading] = useState(false);
|
|
1394
|
-
const [pathsError, setPathsError] = useState(null);
|
|
1395
|
-
const [paths, setPaths] = useState(null);
|
|
1396
|
-
const openSettings = useCallback(async () => {
|
|
1397
|
-
setSettingsOpen(true);
|
|
1398
|
-
setPathsError(null);
|
|
1399
|
-
if (pathsLoading || paths) return;
|
|
1400
|
-
setPathsLoading(true);
|
|
1401
|
-
try {
|
|
1402
|
-
const res = await fetch("/api/paths");
|
|
1403
|
-
if (!res.ok) throw new Error(await readError(res));
|
|
1404
|
-
const data = await res.json();
|
|
1405
|
-
setPaths({
|
|
1406
|
-
agentId: String(data.agentId ?? "main"),
|
|
1407
|
-
stateDir: String(data.stateDir ?? ""),
|
|
1408
|
-
sessionsDir: String(data.sessionsDir ?? ""),
|
|
1409
|
-
storePath: String(data.storePath ?? "")
|
|
1410
|
-
});
|
|
1411
|
-
} catch (err) {
|
|
1412
|
-
setPathsError(err instanceof Error ? err.message : String(err));
|
|
1413
|
-
} finally {
|
|
1414
|
-
setPathsLoading(false);
|
|
1415
|
-
}
|
|
1416
|
-
}, [paths, pathsLoading]);
|
|
1417
|
-
const handleOpenSettings = useCallback(() => {
|
|
1418
|
-
void openSettings();
|
|
1419
|
-
}, [openSettings]);
|
|
1420
|
-
const closeSettings = useCallback(() => {
|
|
1421
|
-
setSettingsOpen(false);
|
|
1422
|
-
}, []);
|
|
1423
|
-
const copySessionsDir = useCallback(() => {
|
|
1424
|
-
if (!paths?.sessionsDir) return;
|
|
1425
|
-
try {
|
|
1426
|
-
void navigator.clipboard.writeText(paths.sessionsDir);
|
|
1427
|
-
} catch {
|
|
1428
|
-
}
|
|
1429
|
-
}, [paths]);
|
|
1430
|
-
const copyStorePath = useCallback(() => {
|
|
1431
|
-
if (!paths?.storePath) return;
|
|
1432
|
-
try {
|
|
1433
|
-
void navigator.clipboard.writeText(paths.storePath);
|
|
1434
|
-
} catch {
|
|
1435
|
-
}
|
|
1436
|
-
}, [paths]);
|
|
1437
|
-
return {
|
|
1438
|
-
settingsOpen,
|
|
1439
|
-
setSettingsOpen,
|
|
1440
|
-
pathsLoading,
|
|
1441
|
-
pathsError,
|
|
1442
|
-
paths,
|
|
1443
|
-
handleOpenSettings,
|
|
1444
|
-
closeSettings,
|
|
1445
|
-
copySessionsDir,
|
|
1446
|
-
copyStorePath
|
|
1447
|
-
};
|
|
1448
|
-
}
|
|
1449
|
-
let pendingSend = null;
|
|
1450
|
-
let pendingGeneration = false;
|
|
1451
|
-
let recentSession = null;
|
|
1452
|
-
function stashPendingSend(payload) {
|
|
1453
|
-
pendingSend = payload;
|
|
1454
|
-
}
|
|
1455
|
-
function hasPendingSend() {
|
|
1456
|
-
return pendingSend !== null;
|
|
1457
|
-
}
|
|
1458
|
-
function setPendingGeneration(value) {
|
|
1459
|
-
pendingGeneration = value;
|
|
1460
|
-
}
|
|
1461
|
-
function hasPendingGeneration() {
|
|
1462
|
-
return pendingGeneration;
|
|
1463
|
-
}
|
|
1464
|
-
function resetPendingSend() {
|
|
1465
|
-
pendingSend = null;
|
|
1466
|
-
pendingGeneration = false;
|
|
1467
|
-
}
|
|
1468
|
-
function clearPendingSendForSession(sessionKey, friendlyId) {
|
|
1469
|
-
if (!pendingSend) return;
|
|
1470
|
-
if (sessionKey && pendingSend.sessionKey === sessionKey) {
|
|
1471
|
-
resetPendingSend();
|
|
1472
|
-
return;
|
|
1473
|
-
}
|
|
1474
|
-
if (friendlyId && pendingSend.friendlyId === friendlyId) {
|
|
1475
|
-
resetPendingSend();
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
function setRecentSession(friendlyId) {
|
|
1479
|
-
recentSession = { friendlyId, at: Date.now() };
|
|
1480
|
-
}
|
|
1481
|
-
function isRecentSession(friendlyId, maxAgeMs = 15e3) {
|
|
1482
|
-
if (!recentSession) return false;
|
|
1483
|
-
if (recentSession.friendlyId !== friendlyId) return false;
|
|
1484
|
-
if (Date.now() - recentSession.at > maxAgeMs) return false;
|
|
1485
|
-
return true;
|
|
1486
|
-
}
|
|
1487
|
-
function consumePendingSend(sessionKey, friendlyId) {
|
|
1488
|
-
if (!pendingSend) return null;
|
|
1489
|
-
if (sessionKey && pendingSend.sessionKey === sessionKey) {
|
|
1490
|
-
const payload = pendingSend;
|
|
1491
|
-
pendingSend = null;
|
|
1492
|
-
return payload;
|
|
1493
|
-
}
|
|
1494
|
-
if (friendlyId && pendingSend.friendlyId === friendlyId) {
|
|
1495
|
-
const payload = pendingSend;
|
|
1496
|
-
pendingSend = null;
|
|
1497
|
-
return payload;
|
|
1498
|
-
}
|
|
1499
|
-
return null;
|
|
1500
|
-
}
|
|
1501
|
-
const TOMBSTONE_TTL_MS = 8e3;
|
|
1502
|
-
const tombstones = /* @__PURE__ */ new Map();
|
|
1503
|
-
function markSessionDeleted(id) {
|
|
1504
|
-
if (!id) return;
|
|
1505
|
-
tombstones.set(id, { id, expiresAt: Date.now() + TOMBSTONE_TTL_MS });
|
|
1506
|
-
}
|
|
1507
|
-
function clearSessionDeleted(id) {
|
|
1508
|
-
if (!id) return;
|
|
1509
|
-
tombstones.delete(id);
|
|
1510
|
-
}
|
|
1511
|
-
function filterSessionsWithTombstones(sessions) {
|
|
1512
|
-
if (tombstones.size === 0) return sessions;
|
|
1513
|
-
const now = Date.now();
|
|
1514
|
-
let changed = false;
|
|
1515
|
-
const next = sessions.filter((session) => {
|
|
1516
|
-
const keyTombstone = tombstones.get(session.key);
|
|
1517
|
-
const friendlyTombstone = tombstones.get(session.friendlyId);
|
|
1518
|
-
const isExpired = keyTombstone && keyTombstone.expiresAt <= now || friendlyTombstone && friendlyTombstone.expiresAt <= now;
|
|
1519
|
-
if (isExpired) {
|
|
1520
|
-
if (keyTombstone && keyTombstone.expiresAt <= now) {
|
|
1521
|
-
tombstones.delete(session.key);
|
|
1522
|
-
}
|
|
1523
|
-
if (friendlyTombstone && friendlyTombstone.expiresAt <= now) {
|
|
1524
|
-
tombstones.delete(session.friendlyId);
|
|
1525
|
-
}
|
|
1526
|
-
return true;
|
|
1527
|
-
}
|
|
1528
|
-
if (keyTombstone || friendlyTombstone) {
|
|
1529
|
-
changed = true;
|
|
1530
|
-
return false;
|
|
1531
|
-
}
|
|
1532
|
-
return true;
|
|
1533
|
-
});
|
|
1534
|
-
return changed ? next : sessions;
|
|
1535
|
-
}
|
|
1536
|
-
function useDeleteSession() {
|
|
1537
|
-
const queryClient = useQueryClient();
|
|
1538
|
-
const [deleting, setDeleting] = useState(false);
|
|
1539
|
-
const [error, setError] = useState(null);
|
|
1540
|
-
const mutation = useMutation({
|
|
1541
|
-
mutationFn: async function deleteSessionRequest2(payload) {
|
|
1542
|
-
const query = new URLSearchParams();
|
|
1543
|
-
if (payload.sessionKey) query.set("sessionKey", payload.sessionKey);
|
|
1544
|
-
if (payload.friendlyId) query.set("friendlyId", payload.friendlyId);
|
|
1545
|
-
const res = await fetch(`/api/sessions?${query.toString()}`, {
|
|
1546
|
-
method: "DELETE"
|
|
1547
|
-
});
|
|
1548
|
-
if (!res.ok) throw new Error(await readError(res));
|
|
1549
|
-
return payload;
|
|
1550
|
-
},
|
|
1551
|
-
onMutate: async function onMutate(payload) {
|
|
1552
|
-
setError(null);
|
|
1553
|
-
markSessionDeleted(payload.sessionKey || payload.friendlyId);
|
|
1554
|
-
clearPendingSendForSession(payload.sessionKey, payload.friendlyId);
|
|
1555
|
-
await queryClient.cancelQueries({ queryKey: chatQueryKeys.sessions });
|
|
1556
|
-
const previousSessions = queryClient.getQueryData(chatQueryKeys.sessions);
|
|
1557
|
-
removeSessionFromCache(
|
|
1558
|
-
queryClient,
|
|
1559
|
-
payload.sessionKey,
|
|
1560
|
-
payload.friendlyId
|
|
1561
|
-
);
|
|
1562
|
-
if (payload.isActive && (payload.sessionKey || payload.friendlyId)) {
|
|
1563
|
-
clearHistoryMessages(
|
|
1564
|
-
queryClient,
|
|
1565
|
-
payload.friendlyId || payload.sessionKey,
|
|
1566
|
-
payload.sessionKey || payload.friendlyId
|
|
1567
|
-
);
|
|
1568
|
-
}
|
|
1569
|
-
return { previousSessions, isActive: payload.isActive };
|
|
1570
|
-
},
|
|
1571
|
-
onError: function onError(err, _payload, context) {
|
|
1572
|
-
if (context?.previousSessions) {
|
|
1573
|
-
queryClient.setQueryData(
|
|
1574
|
-
chatQueryKeys.sessions,
|
|
1575
|
-
context.previousSessions
|
|
1576
|
-
);
|
|
1577
|
-
}
|
|
1578
|
-
clearSessionDeleted(_payload.sessionKey || _payload.friendlyId);
|
|
1579
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
1580
|
-
},
|
|
1581
|
-
onSuccess: function onSuccess(payload) {
|
|
1582
|
-
if (payload.isActive) {
|
|
1583
|
-
resetPendingSend();
|
|
1584
|
-
}
|
|
1585
|
-
queryClient.invalidateQueries({ queryKey: chatQueryKeys.sessions });
|
|
1586
|
-
},
|
|
1587
|
-
onSettled: function onSettled() {
|
|
1588
|
-
setDeleting(false);
|
|
1589
|
-
}
|
|
1590
|
-
});
|
|
1591
|
-
const deleteSession = useCallback(
|
|
1592
|
-
async (sessionKey, friendlyId, isActive) => {
|
|
1593
|
-
if (!sessionKey && !friendlyId) return;
|
|
1594
|
-
setDeleting(true);
|
|
1595
|
-
await mutation.mutateAsync({ sessionKey, friendlyId, isActive });
|
|
1596
|
-
},
|
|
1597
|
-
[mutation]
|
|
1598
|
-
);
|
|
1599
|
-
return { deleteSession, deleting, error };
|
|
1600
|
-
}
|
|
1601
|
-
function useRenameSession() {
|
|
1602
|
-
const queryClient = useQueryClient();
|
|
1603
|
-
const [renaming, setRenaming] = useState(false);
|
|
1604
|
-
const [error, setError] = useState(null);
|
|
1605
|
-
const mutation = useMutation({
|
|
1606
|
-
mutationFn: async function renameSessionRequest(payload) {
|
|
1607
|
-
const res = await fetch("/api/sessions", {
|
|
1608
|
-
method: "PATCH",
|
|
1609
|
-
headers: { "content-type": "application/json" },
|
|
1610
|
-
body: JSON.stringify({
|
|
1611
|
-
sessionKey: payload.sessionKey,
|
|
1612
|
-
label: payload.newTitle
|
|
1613
|
-
})
|
|
1614
|
-
});
|
|
1615
|
-
if (!res.ok) throw new Error(await readError(res));
|
|
1616
|
-
return payload;
|
|
1617
|
-
},
|
|
1618
|
-
onMutate: async function onMutate(payload) {
|
|
1619
|
-
setError(null);
|
|
1620
|
-
await queryClient.cancelQueries({ queryKey: chatQueryKeys.sessions });
|
|
1621
|
-
const previousSessions = queryClient.getQueryData(chatQueryKeys.sessions);
|
|
1622
|
-
queryClient.setQueryData(
|
|
1623
|
-
chatQueryKeys.sessions,
|
|
1624
|
-
function update(sessions) {
|
|
1625
|
-
if (!Array.isArray(sessions)) return sessions;
|
|
1626
|
-
return sessions.map((session) => {
|
|
1627
|
-
if (session.key !== payload.sessionKey) return session;
|
|
1628
|
-
return {
|
|
1629
|
-
...session,
|
|
1630
|
-
label: payload.newTitle,
|
|
1631
|
-
title: payload.newTitle
|
|
1632
|
-
};
|
|
1633
|
-
});
|
|
1634
|
-
}
|
|
1635
|
-
);
|
|
1636
|
-
return { previousSessions };
|
|
1637
|
-
},
|
|
1638
|
-
onError: function onError(err, _payload, context) {
|
|
1639
|
-
if (context?.previousSessions) {
|
|
1640
|
-
queryClient.setQueryData(
|
|
1641
|
-
chatQueryKeys.sessions,
|
|
1642
|
-
context.previousSessions
|
|
1643
|
-
);
|
|
1644
|
-
}
|
|
1645
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
1646
|
-
},
|
|
1647
|
-
onSuccess: function onSuccess() {
|
|
1648
|
-
queryClient.invalidateQueries({ queryKey: chatQueryKeys.sessions });
|
|
1649
|
-
},
|
|
1650
|
-
onSettled: function onSettled() {
|
|
1651
|
-
setRenaming(false);
|
|
1575
|
+
isPinned: false,
|
|
1576
|
+
selectionMode,
|
|
1577
|
+
selected: selectedSessionKeys.has(session.key),
|
|
1578
|
+
onToggleSelect: handleToggleSelect,
|
|
1579
|
+
onSelect,
|
|
1580
|
+
onTogglePin: handleTogglePin,
|
|
1581
|
+
onRename,
|
|
1582
|
+
onDelete,
|
|
1583
|
+
onExport
|
|
1584
|
+
},
|
|
1585
|
+
session.key
|
|
1586
|
+
)) })
|
|
1587
|
+
] }) : null,
|
|
1588
|
+
renderFolderGroup(
|
|
1589
|
+
"webchat",
|
|
1590
|
+
"🦎 OpenCami",
|
|
1591
|
+
groupedSessions.webchat
|
|
1592
|
+
),
|
|
1593
|
+
renderFolderGroup(
|
|
1594
|
+
"subagent",
|
|
1595
|
+
"🤖 Sub-agents",
|
|
1596
|
+
groupedSessions.subagent
|
|
1597
|
+
),
|
|
1598
|
+
renderFolderGroup(
|
|
1599
|
+
"cron",
|
|
1600
|
+
"⏰ Cron / Isolated",
|
|
1601
|
+
groupedSessions.cron
|
|
1602
|
+
),
|
|
1603
|
+
renderFolderGroup("other", "📁 Other", groupedSessions.other)
|
|
1604
|
+
] }) }),
|
|
1605
|
+
/* @__PURE__ */ jsx(ScrollAreaScrollbar, { orientation: "vertical", children: /* @__PURE__ */ jsx(ScrollAreaThumb, {}) })
|
|
1606
|
+
] })
|
|
1607
|
+
}
|
|
1608
|
+
),
|
|
1609
|
+
selectionMode ? /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-t border-primary-200 bg-surface px-2 py-2 flex flex-col gap-1.5", children: [
|
|
1610
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1611
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-primary-600 font-medium", children: [
|
|
1612
|
+
selectedCount,
|
|
1613
|
+
" selected"
|
|
1614
|
+
] }),
|
|
1615
|
+
/* @__PURE__ */ jsx(
|
|
1616
|
+
"button",
|
|
1617
|
+
{
|
|
1618
|
+
type: "button",
|
|
1619
|
+
onClick: handleSelectAll,
|
|
1620
|
+
className: "text-[11px] font-medium text-primary-600 hover:text-primary-800",
|
|
1621
|
+
children: "Select all"
|
|
1622
|
+
}
|
|
1623
|
+
)
|
|
1624
|
+
] }),
|
|
1625
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
|
|
1626
|
+
/* @__PURE__ */ jsxs(
|
|
1627
|
+
"button",
|
|
1628
|
+
{
|
|
1629
|
+
type: "button",
|
|
1630
|
+
onClick: handleDeleteSelected,
|
|
1631
|
+
disabled: selectedCount === 0,
|
|
1632
|
+
className: cn(
|
|
1633
|
+
"flex-1 inline-flex items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-medium transition-colors",
|
|
1634
|
+
selectedCount > 0 ? "bg-red-100 text-red-700 hover:bg-red-200" : "bg-primary-100 text-primary-400 cursor-not-allowed"
|
|
1635
|
+
),
|
|
1636
|
+
children: [
|
|
1637
|
+
/* @__PURE__ */ jsx(HugeiconsIcon, { icon: Delete01Icon, size: 14, strokeWidth: 1.5 }),
|
|
1638
|
+
"Delete"
|
|
1639
|
+
]
|
|
1640
|
+
}
|
|
1641
|
+
),
|
|
1642
|
+
/* @__PURE__ */ jsx(
|
|
1643
|
+
"button",
|
|
1644
|
+
{
|
|
1645
|
+
type: "button",
|
|
1646
|
+
onClick: handleCancelSelection,
|
|
1647
|
+
className: "flex-1 inline-flex items-center justify-center rounded-md px-2 py-1.5 text-xs font-medium bg-primary-100 text-primary-700 hover:bg-primary-200 transition-colors",
|
|
1648
|
+
children: "Cancel"
|
|
1649
|
+
}
|
|
1650
|
+
)
|
|
1651
|
+
] })
|
|
1652
|
+
] }) : null
|
|
1653
|
+
]
|
|
1652
1654
|
}
|
|
1653
|
-
});
|
|
1654
|
-
const renameSession = useCallback(
|
|
1655
|
-
async (sessionKey, newTitle) => {
|
|
1656
|
-
if (!sessionKey || !newTitle.trim()) return;
|
|
1657
|
-
setRenaming(true);
|
|
1658
|
-
await mutation.mutateAsync({ sessionKey, newTitle: newTitle.trim() });
|
|
1659
|
-
},
|
|
1660
|
-
[mutation]
|
|
1661
1655
|
);
|
|
1662
|
-
|
|
1656
|
+
}, areSidebarSessionsEqual);
|
|
1657
|
+
function areSidebarSessionsEqual(prev, next) {
|
|
1658
|
+
if (prev.activeFriendlyId !== next.activeFriendlyId) return false;
|
|
1659
|
+
if (prev.activeSessionKey !== next.activeSessionKey) return false;
|
|
1660
|
+
if (prev.isStreaming !== next.isStreaming) return false;
|
|
1661
|
+
if (prev.defaultOpen !== next.defaultOpen) return false;
|
|
1662
|
+
if (prev.onSelect !== next.onSelect) return false;
|
|
1663
|
+
if (prev.onRename !== next.onRename) return false;
|
|
1664
|
+
if (prev.onDelete !== next.onDelete) return false;
|
|
1665
|
+
if (prev.onExport !== next.onExport) return false;
|
|
1666
|
+
if (prev.sessions === next.sessions) return true;
|
|
1667
|
+
if (prev.sessions.length !== next.sessions.length) return false;
|
|
1668
|
+
for (let i = 0; i < prev.sessions.length; i += 1) {
|
|
1669
|
+
const prevSession = prev.sessions[i];
|
|
1670
|
+
const nextSession = next.sessions[i];
|
|
1671
|
+
if (prevSession.key !== nextSession.key) return false;
|
|
1672
|
+
if (prevSession.friendlyId !== nextSession.friendlyId) return false;
|
|
1673
|
+
if (prevSession.label !== nextSession.label) return false;
|
|
1674
|
+
if (prevSession.title !== nextSession.title) return false;
|
|
1675
|
+
if (prevSession.derivedTitle !== nextSession.derivedTitle) return false;
|
|
1676
|
+
if (prevSession.updatedAt !== nextSession.updatedAt) return false;
|
|
1677
|
+
if (prevSession.kind !== nextSession.kind) return false;
|
|
1678
|
+
if (prevSession.status !== nextSession.status) return false;
|
|
1679
|
+
if (prevSession.lastMessage !== nextSession.lastMessage) return false;
|
|
1680
|
+
}
|
|
1681
|
+
return true;
|
|
1663
1682
|
}
|
|
1664
1683
|
const SettingsDialog = lazy(
|
|
1665
|
-
() => import("./settings-dialog-
|
|
1684
|
+
() => import("./settings-dialog-BUOrQN3Z.js").then((m) => ({ default: m.SettingsDialog }))
|
|
1666
1685
|
);
|
|
1667
1686
|
const SessionExportDialog = lazy(
|
|
1668
1687
|
() => import("./session-export-dialog-C53RRAah.js").then((m) => ({
|
|
@@ -2081,6 +2100,48 @@ function ChatSidebarComponent({
|
|
|
2081
2100
|
}
|
|
2082
2101
|
) }),
|
|
2083
2102
|
isCollapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Cron Jobs" })
|
|
2103
|
+
] }) }),
|
|
2104
|
+
(() => {
|
|
2105
|
+
try {
|
|
2106
|
+
return localStorage.getItem("feature_dashboard") === "true";
|
|
2107
|
+
} catch {
|
|
2108
|
+
return false;
|
|
2109
|
+
}
|
|
2110
|
+
})() && /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(TooltipRoot, { children: [
|
|
2111
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
2112
|
+
Link,
|
|
2113
|
+
{
|
|
2114
|
+
to: "/dashboard",
|
|
2115
|
+
className: cn(
|
|
2116
|
+
buttonVariants({ variant: "ghost", size: "sm" }),
|
|
2117
|
+
"w-full pl-1.5 justify-start"
|
|
2118
|
+
),
|
|
2119
|
+
onClick: onSelectSession,
|
|
2120
|
+
children: [
|
|
2121
|
+
/* @__PURE__ */ jsx(
|
|
2122
|
+
HugeiconsIcon,
|
|
2123
|
+
{
|
|
2124
|
+
icon: DashboardCircleIcon,
|
|
2125
|
+
size: 20,
|
|
2126
|
+
strokeWidth: 1.5,
|
|
2127
|
+
className: "min-w-5"
|
|
2128
|
+
}
|
|
2129
|
+
),
|
|
2130
|
+
/* @__PURE__ */ jsx(AnimatePresence, { initial: false, mode: "wait", children: !isCollapsed && /* @__PURE__ */ jsx(
|
|
2131
|
+
motion.span,
|
|
2132
|
+
{
|
|
2133
|
+
initial: { opacity: 0 },
|
|
2134
|
+
animate: { opacity: 1 },
|
|
2135
|
+
exit: { opacity: 0 },
|
|
2136
|
+
transition,
|
|
2137
|
+
className: "overflow-hidden whitespace-nowrap",
|
|
2138
|
+
children: "Dashboard"
|
|
2139
|
+
}
|
|
2140
|
+
) })
|
|
2141
|
+
]
|
|
2142
|
+
}
|
|
2143
|
+
) }),
|
|
2144
|
+
isCollapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Dashboard" })
|
|
2084
2145
|
] }) })
|
|
2085
2146
|
]
|
|
2086
2147
|
}
|
|
@@ -2998,6 +3059,73 @@ function imagesFromMessage(msg) {
|
|
|
2998
3059
|
}
|
|
2999
3060
|
return images;
|
|
3000
3061
|
}
|
|
3062
|
+
const UPLOADED_FILE_LINE_REGEX = /^📎 Uploaded file:\s*(\/uploads\/\S+)\s*$/;
|
|
3063
|
+
function formatFileSize$1(bytes) {
|
|
3064
|
+
if (bytes === null || Number.isNaN(bytes)) return "Unknown size";
|
|
3065
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
3066
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
3067
|
+
let value = bytes / 1024;
|
|
3068
|
+
let unitIndex = 0;
|
|
3069
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
3070
|
+
value /= 1024;
|
|
3071
|
+
unitIndex++;
|
|
3072
|
+
}
|
|
3073
|
+
return `${value.toFixed(1)} ${units[unitIndex]}`;
|
|
3074
|
+
}
|
|
3075
|
+
function getLeadingUploadedFileLines(text) {
|
|
3076
|
+
if (!text) return [];
|
|
3077
|
+
const trimmedStart = text.trimStart();
|
|
3078
|
+
if (trimmedStart.startsWith(">") || trimmedStart.startsWith("```")) {
|
|
3079
|
+
return [];
|
|
3080
|
+
}
|
|
3081
|
+
const lines = text.split("\n");
|
|
3082
|
+
const uploadedLines = [];
|
|
3083
|
+
for (const line of lines) {
|
|
3084
|
+
if (UPLOADED_FILE_LINE_REGEX.test(line)) {
|
|
3085
|
+
uploadedLines.push(line);
|
|
3086
|
+
continue;
|
|
3087
|
+
}
|
|
3088
|
+
if (uploadedLines.length > 0 && line.trim() === "") {
|
|
3089
|
+
uploadedLines.push(line);
|
|
3090
|
+
continue;
|
|
3091
|
+
}
|
|
3092
|
+
break;
|
|
3093
|
+
}
|
|
3094
|
+
const firstLine = lines[0] ?? "";
|
|
3095
|
+
if (!UPLOADED_FILE_LINE_REGEX.test(firstLine)) {
|
|
3096
|
+
return [];
|
|
3097
|
+
}
|
|
3098
|
+
return uploadedLines.filter((line) => UPLOADED_FILE_LINE_REGEX.test(line));
|
|
3099
|
+
}
|
|
3100
|
+
function parseUploadedFileReferences(text) {
|
|
3101
|
+
const refs = [];
|
|
3102
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3103
|
+
for (const line of getLeadingUploadedFileLines(text)) {
|
|
3104
|
+
const match = line.match(UPLOADED_FILE_LINE_REGEX);
|
|
3105
|
+
const filePath = match?.[1];
|
|
3106
|
+
if (!filePath || seen.has(filePath)) continue;
|
|
3107
|
+
seen.add(filePath);
|
|
3108
|
+
refs.push({
|
|
3109
|
+
path: filePath,
|
|
3110
|
+
filename: filePath.split("/").pop() || filePath
|
|
3111
|
+
});
|
|
3112
|
+
}
|
|
3113
|
+
return refs;
|
|
3114
|
+
}
|
|
3115
|
+
function stripUploadedFileLines(text) {
|
|
3116
|
+
const lines = text.split("\n");
|
|
3117
|
+
if (!UPLOADED_FILE_LINE_REGEX.test(lines[0] ?? "")) {
|
|
3118
|
+
return text.trim();
|
|
3119
|
+
}
|
|
3120
|
+
let index = 0;
|
|
3121
|
+
while (index < lines.length && UPLOADED_FILE_LINE_REGEX.test(lines[index])) {
|
|
3122
|
+
index++;
|
|
3123
|
+
}
|
|
3124
|
+
while (index < lines.length && lines[index].trim() === "") {
|
|
3125
|
+
index++;
|
|
3126
|
+
}
|
|
3127
|
+
return lines.slice(index).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
3128
|
+
}
|
|
3001
3129
|
function MessageItemComponent({
|
|
3002
3130
|
message,
|
|
3003
3131
|
toolResultsByCallId,
|
|
@@ -3018,6 +3146,47 @@ function MessageItemComponent({
|
|
|
3018
3146
|
const images = imagesFromMessage(message);
|
|
3019
3147
|
const isUser = role === "user";
|
|
3020
3148
|
const timestamp = getMessageTimestamp(message);
|
|
3149
|
+
const navigate = useNavigate();
|
|
3150
|
+
const openInEditor = useFileExplorerState((state) => state.openInEditor);
|
|
3151
|
+
const uploadedFileRefs = useMemo(() => parseUploadedFileReferences(text), [text]);
|
|
3152
|
+
const [fileSizes, setFileSizes] = useState({});
|
|
3153
|
+
const displayText = useMemo(() => stripUploadedFileLines(text), [text]);
|
|
3154
|
+
useEffect(() => {
|
|
3155
|
+
let cancelled = false;
|
|
3156
|
+
async function loadSizes() {
|
|
3157
|
+
const nextSizes = {};
|
|
3158
|
+
await Promise.all(
|
|
3159
|
+
uploadedFileRefs.map(async (ref) => {
|
|
3160
|
+
try {
|
|
3161
|
+
const response = await fetch(`/api/files/info?path=${encodeURIComponent(ref.path)}`);
|
|
3162
|
+
if (!response.ok) {
|
|
3163
|
+
nextSizes[ref.path] = null;
|
|
3164
|
+
return;
|
|
3165
|
+
}
|
|
3166
|
+
const data = await response.json();
|
|
3167
|
+
nextSizes[ref.path] = typeof data.size === "number" ? data.size : null;
|
|
3168
|
+
} catch {
|
|
3169
|
+
nextSizes[ref.path] = null;
|
|
3170
|
+
}
|
|
3171
|
+
})
|
|
3172
|
+
);
|
|
3173
|
+
if (!cancelled) {
|
|
3174
|
+
setFileSizes(nextSizes);
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
if (uploadedFileRefs.length > 0) {
|
|
3178
|
+
void loadSizes();
|
|
3179
|
+
} else {
|
|
3180
|
+
setFileSizes({});
|
|
3181
|
+
}
|
|
3182
|
+
return () => {
|
|
3183
|
+
cancelled = true;
|
|
3184
|
+
};
|
|
3185
|
+
}, [uploadedFileRefs]);
|
|
3186
|
+
const handleOpenFile = async (filePath) => {
|
|
3187
|
+
openInEditor(filePath);
|
|
3188
|
+
await navigate({ to: "/files" });
|
|
3189
|
+
};
|
|
3021
3190
|
const toolCalls = role === "assistant" ? getToolCallsFromMessage(message) : [];
|
|
3022
3191
|
const hasToolCalls = toolCalls.length > 0;
|
|
3023
3192
|
const searchSources = isLastAssistant && !isStreaming && settings.showSearchSources && aggregatedSearchSources ? aggregatedSearchSources : [];
|
|
@@ -3054,6 +3223,24 @@ function MessageItemComponent({
|
|
|
3054
3223
|
},
|
|
3055
3224
|
idx
|
|
3056
3225
|
)) }),
|
|
3226
|
+
uploadedFileRefs.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("mb-2 flex w-full flex-col gap-2", isUser ? "items-end" : "items-start"), children: uploadedFileRefs.map((fileRef) => /* @__PURE__ */ jsxs(
|
|
3227
|
+
"button",
|
|
3228
|
+
{
|
|
3229
|
+
type: "button",
|
|
3230
|
+
onClick: () => {
|
|
3231
|
+
void handleOpenFile(fileRef.path);
|
|
3232
|
+
},
|
|
3233
|
+
className: "flex max-w-full items-center gap-3 rounded-xl border border-primary-200 bg-primary-50 px-3 py-2 text-left hover:bg-primary-100",
|
|
3234
|
+
children: [
|
|
3235
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-primary-100", children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: File01Icon, size: 18, className: "text-primary-600" }) }),
|
|
3236
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
3237
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-primary-900", children: fileRef.filename }),
|
|
3238
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-primary-600", children: formatFileSize$1(fileSizes[fileRef.path] ?? null) })
|
|
3239
|
+
] })
|
|
3240
|
+
]
|
|
3241
|
+
},
|
|
3242
|
+
fileRef.path
|
|
3243
|
+
)) }),
|
|
3057
3244
|
/* @__PURE__ */ jsx(Message, { className: cn("min-w-0 max-w-full", isUser ? "flex-row-reverse" : ""), children: /* @__PURE__ */ jsx(
|
|
3058
3245
|
MessageContent,
|
|
3059
3246
|
{
|
|
@@ -3063,7 +3250,7 @@ function MessageItemComponent({
|
|
|
3063
3250
|
isUser ? "opencami-message-user bg-primary-100 px-4 py-[var(--opencami-user-bubble-py)] max-w-[85%]" : "opencami-message-assistant bg-transparent w-full",
|
|
3064
3251
|
!isUser && isStreaming && "stream-fade-in"
|
|
3065
3252
|
),
|
|
3066
|
-
children:
|
|
3253
|
+
children: displayText
|
|
3067
3254
|
}
|
|
3068
3255
|
) }),
|
|
3069
3256
|
hasToolCalls && settings.showToolMessages && /* @__PURE__ */ jsx("div", { className: "mt-2 flex w-full min-w-0 max-w-[var(--opencami-chat-width)] flex-col gap-3 overflow-x-hidden", children: toolCalls.map((toolCall) => {
|
|
@@ -3252,6 +3439,10 @@ const LLM_PROVIDER_DEFAULTS = {
|
|
|
3252
3439
|
baseUrl: "https://openrouter.ai/api/v1",
|
|
3253
3440
|
model: "openai/gpt-oss-120b"
|
|
3254
3441
|
},
|
|
3442
|
+
kilocode: {
|
|
3443
|
+
baseUrl: "https://api.kilo.ai/api/gateway",
|
|
3444
|
+
model: "google/gemini-2.5-flash"
|
|
3445
|
+
},
|
|
3255
3446
|
ollama: {
|
|
3256
3447
|
baseUrl: "http://localhost:11434/v1",
|
|
3257
3448
|
model: "llama3.2"
|
|
@@ -3283,7 +3474,7 @@ function getEffectiveLlmModel(settings) {
|
|
|
3283
3474
|
function getAvailability(settings, hasEnvKey) {
|
|
3284
3475
|
if (settings.llmProvider === "ollama") return true;
|
|
3285
3476
|
if (settings.llmProvider === "custom") {
|
|
3286
|
-
return Boolean(settings.llmApiKey.trim()) || settings.llmBaseUrl.trim() && settings.llmModel.trim();
|
|
3477
|
+
return Boolean(settings.llmApiKey.trim()) || Boolean(settings.llmBaseUrl.trim() && settings.llmModel.trim());
|
|
3287
3478
|
}
|
|
3288
3479
|
return hasEnvKey || Boolean(settings.llmApiKey.trim());
|
|
3289
3480
|
}
|
|
@@ -3332,6 +3523,7 @@ function useLlmSettings() {
|
|
|
3332
3523
|
const [status, setStatus] = useState({
|
|
3333
3524
|
hasEnvKey: false,
|
|
3334
3525
|
hasOpenRouterKey: false,
|
|
3526
|
+
hasKilocodeKey: false,
|
|
3335
3527
|
hasUserKey: Boolean(settings.llmApiKey),
|
|
3336
3528
|
isAvailable: getAvailability(settings, false),
|
|
3337
3529
|
isLoading: true,
|
|
@@ -3346,10 +3538,11 @@ function useLlmSettings() {
|
|
|
3346
3538
|
const data = await res.json();
|
|
3347
3539
|
if (cancelled) return;
|
|
3348
3540
|
const hasUserKey = Boolean(settings.llmApiKey);
|
|
3349
|
-
const hasProviderKey = settings.llmProvider === "openrouter" ? Boolean(data.hasOpenRouterKey) : data.hasEnvKey;
|
|
3541
|
+
const hasProviderKey = settings.llmProvider === "openrouter" ? Boolean(data.hasOpenRouterKey) : settings.llmProvider === "kilocode" ? Boolean(data.hasKilocodeKey) : data.hasEnvKey;
|
|
3350
3542
|
setStatus({
|
|
3351
3543
|
hasEnvKey: data.hasEnvKey,
|
|
3352
3544
|
hasOpenRouterKey: Boolean(data.hasOpenRouterKey),
|
|
3545
|
+
hasKilocodeKey: Boolean(data.hasKilocodeKey),
|
|
3353
3546
|
hasUserKey,
|
|
3354
3547
|
isAvailable: getAvailability(settings, hasProviderKey),
|
|
3355
3548
|
isLoading: false,
|
|
@@ -3946,13 +4139,14 @@ function ChatMessageListComponent({
|
|
|
3946
4139
|
return sources;
|
|
3947
4140
|
}, [displayMessages, toolResultsByCallId]);
|
|
3948
4141
|
const lastAssistantIndex = displayMessages.map((message, index) => ({ message, index })).filter(({ message }) => message.role !== "user").map(({ index }) => index).pop();
|
|
4142
|
+
const lastTextAssistantIndex = displayMessages.map((message, index) => ({ message, index })).filter(({ message }) => message.role === "assistant").map(({ index }) => index).pop();
|
|
3949
4143
|
const lastUserIndex = displayMessages.map((message, index) => ({ message, index })).filter(({ message }) => message.role === "user").map(({ index }) => index).pop();
|
|
3950
4144
|
const showTypingIndicator = waitingForResponse && (typeof lastUserIndex !== "number" || typeof lastAssistantIndex !== "number" || lastAssistantIndex < lastUserIndex);
|
|
3951
4145
|
const groupStartIndex = typeof lastUserIndex === "number" ? lastUserIndex : -1;
|
|
3952
4146
|
const hasGroup = pinToTop && groupStartIndex >= 0;
|
|
3953
|
-
const lastAssistantMessage = typeof
|
|
4147
|
+
const lastAssistantMessage = typeof lastTextAssistantIndex === "number" ? displayMessages[lastTextAssistantIndex] : void 0;
|
|
3954
4148
|
const lastAssistantText = lastAssistantMessage ? textFromMessage(lastAssistantMessage) : "";
|
|
3955
|
-
const showFollowUps = !waitingForResponse && !isStreaming && lastAssistantText.length > 0 && onFollowUpClick !== void 0 && (typeof lastUserIndex !== "number" || typeof
|
|
4149
|
+
const showFollowUps = !waitingForResponse && !isStreaming && lastAssistantText.length > 0 && onFollowUpClick !== void 0 && (typeof lastUserIndex !== "number" || typeof lastTextAssistantIndex !== "number" || lastTextAssistantIndex > lastUserIndex);
|
|
3956
4150
|
useLayoutEffect(() => {
|
|
3957
4151
|
if (loading) return;
|
|
3958
4152
|
if (pinToTop) {
|
|
@@ -4690,7 +4884,30 @@ const MAX_IMAGE_DIMENSION = 1280;
|
|
|
4690
4884
|
const IMAGE_QUALITY = 0.75;
|
|
4691
4885
|
const TARGET_IMAGE_SIZE = 300 * 1024;
|
|
4692
4886
|
const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
4693
|
-
const
|
|
4887
|
+
const ACCEPTED_NON_IMAGE_EXTENSIONS = [
|
|
4888
|
+
"pdf",
|
|
4889
|
+
"txt",
|
|
4890
|
+
"md",
|
|
4891
|
+
"csv",
|
|
4892
|
+
"json",
|
|
4893
|
+
"xml",
|
|
4894
|
+
"yaml",
|
|
4895
|
+
"yml",
|
|
4896
|
+
"log",
|
|
4897
|
+
"py",
|
|
4898
|
+
"js",
|
|
4899
|
+
"ts",
|
|
4900
|
+
"html",
|
|
4901
|
+
"css"
|
|
4902
|
+
];
|
|
4903
|
+
const ACCEPTED_EXTENSIONS = [
|
|
4904
|
+
".png",
|
|
4905
|
+
".jpg",
|
|
4906
|
+
".jpeg",
|
|
4907
|
+
".gif",
|
|
4908
|
+
".webp",
|
|
4909
|
+
...ACCEPTED_NON_IMAGE_EXTENSIONS.map((ext) => `.${ext}`)
|
|
4910
|
+
].join(",");
|
|
4694
4911
|
function isCanvasSupported() {
|
|
4695
4912
|
if (typeof document === "undefined") return false;
|
|
4696
4913
|
try {
|
|
@@ -4766,6 +4983,10 @@ async function compressImage(file) {
|
|
|
4766
4983
|
function isAcceptedImage(file) {
|
|
4767
4984
|
return ACCEPTED_IMAGE_TYPES.includes(file.type);
|
|
4768
4985
|
}
|
|
4986
|
+
function isAcceptedNonImage(file) {
|
|
4987
|
+
const extension = file.name.includes(".") ? file.name.split(".").pop()?.toLowerCase() : "";
|
|
4988
|
+
return Boolean(extension && ACCEPTED_NON_IMAGE_EXTENSIONS.includes(extension));
|
|
4989
|
+
}
|
|
4769
4990
|
async function createAttachmentFromFile(file) {
|
|
4770
4991
|
const id = crypto.randomUUID();
|
|
4771
4992
|
if (!isAcceptedImage(file)) {
|
|
@@ -4773,9 +4994,9 @@ async function createAttachmentFromFile(file) {
|
|
|
4773
4994
|
id,
|
|
4774
4995
|
file,
|
|
4775
4996
|
preview: null,
|
|
4776
|
-
type: "
|
|
4997
|
+
type: "file",
|
|
4777
4998
|
base64: null,
|
|
4778
|
-
error: "Unsupported
|
|
4999
|
+
error: "Unsupported image type. Please use PNG, JPG, GIF, or WebP images."
|
|
4779
5000
|
};
|
|
4780
5001
|
}
|
|
4781
5002
|
if (file.size > MAX_FILE_SIZE) {
|
|
@@ -4810,7 +5031,7 @@ async function createAttachmentFromFile(file) {
|
|
|
4810
5031
|
}
|
|
4811
5032
|
}
|
|
4812
5033
|
function AttachmentButton({
|
|
4813
|
-
|
|
5034
|
+
onFilesSelect,
|
|
4814
5035
|
disabled = false,
|
|
4815
5036
|
className
|
|
4816
5037
|
}) {
|
|
@@ -4820,13 +5041,12 @@ function AttachmentButton({
|
|
|
4820
5041
|
}, []);
|
|
4821
5042
|
const handleFileChange = useCallback(
|
|
4822
5043
|
async (event) => {
|
|
4823
|
-
const
|
|
4824
|
-
if (
|
|
5044
|
+
const files = Array.from(event.target.files ?? []);
|
|
5045
|
+
if (files.length === 0) return;
|
|
4825
5046
|
event.target.value = "";
|
|
4826
|
-
|
|
4827
|
-
onFileSelect(attachment);
|
|
5047
|
+
onFilesSelect(files);
|
|
4828
5048
|
},
|
|
4829
|
-
[
|
|
5049
|
+
[onFilesSelect]
|
|
4830
5050
|
);
|
|
4831
5051
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4832
5052
|
/* @__PURE__ */ jsx(
|
|
@@ -4835,6 +5055,7 @@ function AttachmentButton({
|
|
|
4835
5055
|
ref: inputRef,
|
|
4836
5056
|
type: "file",
|
|
4837
5057
|
accept: ACCEPTED_EXTENSIONS,
|
|
5058
|
+
multiple: true,
|
|
4838
5059
|
onChange: handleFileChange,
|
|
4839
5060
|
className: "hidden",
|
|
4840
5061
|
"aria-hidden": "true"
|
|
@@ -4848,7 +5069,7 @@ function AttachmentButton({
|
|
|
4848
5069
|
onClick: handleClick,
|
|
4849
5070
|
disabled,
|
|
4850
5071
|
className,
|
|
4851
|
-
"aria-label": "Attach
|
|
5072
|
+
"aria-label": "Attach file",
|
|
4852
5073
|
type: "button",
|
|
4853
5074
|
children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Attachment01Icon, size: 18, strokeWidth: 1.8 })
|
|
4854
5075
|
}
|
|
@@ -4877,6 +5098,36 @@ function AttachmentPreview({
|
|
|
4877
5098
|
};
|
|
4878
5099
|
}, [attachment.preview]);
|
|
4879
5100
|
const hasError = Boolean(attachment.error);
|
|
5101
|
+
if (attachment.type === "file" && !hasError) {
|
|
5102
|
+
return /* @__PURE__ */ jsxs(
|
|
5103
|
+
"div",
|
|
5104
|
+
{
|
|
5105
|
+
className: cn(
|
|
5106
|
+
"inline-flex max-w-full items-center gap-2 rounded-full border border-primary-200 bg-primary-50 px-3 py-1.5 text-xs text-primary-800",
|
|
5107
|
+
className
|
|
5108
|
+
),
|
|
5109
|
+
children: [
|
|
5110
|
+
/* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
5111
|
+
"📄 ",
|
|
5112
|
+
attachment.file.name,
|
|
5113
|
+
" (",
|
|
5114
|
+
formatFileSize(attachment.file.size),
|
|
5115
|
+
")"
|
|
5116
|
+
] }),
|
|
5117
|
+
/* @__PURE__ */ jsx(
|
|
5118
|
+
"button",
|
|
5119
|
+
{
|
|
5120
|
+
onClick: () => onRemove(attachment.id),
|
|
5121
|
+
className: "shrink-0 text-primary-500 hover:text-primary-800",
|
|
5122
|
+
"aria-label": "Remove attachment",
|
|
5123
|
+
type: "button",
|
|
5124
|
+
children: "✕"
|
|
5125
|
+
}
|
|
5126
|
+
)
|
|
5127
|
+
]
|
|
5128
|
+
}
|
|
5129
|
+
);
|
|
5130
|
+
}
|
|
4880
5131
|
return /* @__PURE__ */ jsxs(
|
|
4881
5132
|
"div",
|
|
4882
5133
|
{
|
|
@@ -4932,7 +5183,7 @@ function AttachmentPreviewList({
|
|
|
4932
5183
|
className
|
|
4933
5184
|
}) {
|
|
4934
5185
|
if (attachments.length === 0) return null;
|
|
4935
|
-
return /* @__PURE__ */ jsx("div", { className: cn("flex flex-
|
|
5186
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap gap-2 px-4", className), children: attachments.map((attachment) => /* @__PURE__ */ jsx(
|
|
4936
5187
|
AttachmentPreview,
|
|
4937
5188
|
{
|
|
4938
5189
|
attachment,
|
|
@@ -5162,9 +5413,82 @@ function ChatComposerComponent({
|
|
|
5162
5413
|
setSlashMenuDismissed(false);
|
|
5163
5414
|
focusPrompt();
|
|
5164
5415
|
}, [focusPrompt]);
|
|
5165
|
-
const
|
|
5166
|
-
|
|
5416
|
+
const appendUploadedFilePrompt = useCallback((uploadedPath) => {
|
|
5417
|
+
setValue((prev) => {
|
|
5418
|
+
const template = `📎 Uploaded file: ${uploadedPath}
|
|
5419
|
+
|
|
5420
|
+
Please analyze this file.`;
|
|
5421
|
+
if (!prev.trim()) return template;
|
|
5422
|
+
return `${prev.trim()}
|
|
5423
|
+
|
|
5424
|
+
${template}`;
|
|
5425
|
+
});
|
|
5167
5426
|
}, []);
|
|
5427
|
+
const ensureUploadsDirectory = useCallback(async () => {
|
|
5428
|
+
await fetch("/api/files/mkdir", {
|
|
5429
|
+
method: "POST",
|
|
5430
|
+
headers: { "Content-Type": "application/json" },
|
|
5431
|
+
body: JSON.stringify({ path: "/uploads" })
|
|
5432
|
+
});
|
|
5433
|
+
}, []);
|
|
5434
|
+
const uploadAttachmentFile = useCallback(async (file) => {
|
|
5435
|
+
const id = crypto.randomUUID();
|
|
5436
|
+
if (!isAcceptedNonImage(file)) {
|
|
5437
|
+
return {
|
|
5438
|
+
id,
|
|
5439
|
+
file,
|
|
5440
|
+
preview: null,
|
|
5441
|
+
type: "file",
|
|
5442
|
+
base64: null,
|
|
5443
|
+
error: "Unsupported file type for upload."
|
|
5444
|
+
};
|
|
5445
|
+
}
|
|
5446
|
+
try {
|
|
5447
|
+
await ensureUploadsDirectory();
|
|
5448
|
+
const formData = new FormData();
|
|
5449
|
+
formData.append("path", "/uploads");
|
|
5450
|
+
formData.append("file", file);
|
|
5451
|
+
const response = await fetch("/api/files/upload", {
|
|
5452
|
+
method: "POST",
|
|
5453
|
+
body: formData
|
|
5454
|
+
});
|
|
5455
|
+
if (!response.ok) {
|
|
5456
|
+
const message = await response.text();
|
|
5457
|
+
throw new Error(message || "File upload failed");
|
|
5458
|
+
}
|
|
5459
|
+
const data = await response.json();
|
|
5460
|
+
const uploadedPath = data.files?.[0]?.path;
|
|
5461
|
+
if (!uploadedPath) {
|
|
5462
|
+
throw new Error("Upload succeeded but no path was returned");
|
|
5463
|
+
}
|
|
5464
|
+
appendUploadedFilePrompt(uploadedPath);
|
|
5465
|
+
return {
|
|
5466
|
+
id,
|
|
5467
|
+
file,
|
|
5468
|
+
preview: null,
|
|
5469
|
+
type: "file",
|
|
5470
|
+
base64: null,
|
|
5471
|
+
uploadedPath
|
|
5472
|
+
};
|
|
5473
|
+
} catch (err) {
|
|
5474
|
+
return {
|
|
5475
|
+
id,
|
|
5476
|
+
file,
|
|
5477
|
+
preview: null,
|
|
5478
|
+
type: "file",
|
|
5479
|
+
base64: null,
|
|
5480
|
+
error: err instanceof Error ? err.message : "File upload failed"
|
|
5481
|
+
};
|
|
5482
|
+
}
|
|
5483
|
+
}, [appendUploadedFilePrompt, ensureUploadsDirectory]);
|
|
5484
|
+
const handleFilesSelect = useCallback(async (files) => {
|
|
5485
|
+
if (files.length === 0) return;
|
|
5486
|
+
const nextAttachments = await Promise.all(
|
|
5487
|
+
files.map((file) => isAcceptedImage(file) ? createAttachmentFromFile(file) : uploadAttachmentFile(file))
|
|
5488
|
+
);
|
|
5489
|
+
setAttachments((prev) => [...prev, ...nextAttachments]);
|
|
5490
|
+
focusPrompt();
|
|
5491
|
+
}, [focusPrompt, uploadAttachmentFile]);
|
|
5168
5492
|
const handleRemoveAttachment = useCallback((id) => {
|
|
5169
5493
|
setAttachments((prev) => prev.filter((a) => a.id !== id));
|
|
5170
5494
|
}, []);
|
|
@@ -5186,12 +5510,8 @@ function ChatComposerComponent({
|
|
|
5186
5510
|
setIsDragActive(false);
|
|
5187
5511
|
const files = Array.from(event.dataTransfer.files ?? []);
|
|
5188
5512
|
if (files.length === 0) return;
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
const newAttachments = await Promise.all(imageFiles.map((file) => createAttachmentFromFile(file)));
|
|
5192
|
-
setAttachments((prev) => [...prev, ...newAttachments]);
|
|
5193
|
-
focusPrompt();
|
|
5194
|
-
}, [focusPrompt]);
|
|
5513
|
+
await handleFilesSelect(files);
|
|
5514
|
+
}, [handleFilesSelect]);
|
|
5195
5515
|
const setComposerValue = useCallback(
|
|
5196
5516
|
(nextValue) => {
|
|
5197
5517
|
setValue(nextValue);
|
|
@@ -5443,7 +5763,7 @@ function ChatComposerComponent({
|
|
|
5443
5763
|
onDragLeave: handleDragLeave,
|
|
5444
5764
|
onDrop: handleDrop,
|
|
5445
5765
|
children: [
|
|
5446
|
-
isDragActive && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-2 z-20 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary-400 bg-primary-50/90 text-sm font-medium text-primary-700", children: "Drop
|
|
5766
|
+
isDragActive && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-2 z-20 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary-400 bg-primary-50/90 text-sm font-medium text-primary-700", children: "Drop files here" }),
|
|
5447
5767
|
/* @__PURE__ */ jsxs(
|
|
5448
5768
|
PromptInput,
|
|
5449
5769
|
{
|
|
@@ -5487,7 +5807,7 @@ function ChatComposerComponent({
|
|
|
5487
5807
|
/* @__PURE__ */ jsx(
|
|
5488
5808
|
AttachmentButton,
|
|
5489
5809
|
{
|
|
5490
|
-
|
|
5810
|
+
onFilesSelect: handleFilesSelect,
|
|
5491
5811
|
disabled
|
|
5492
5812
|
}
|
|
5493
5813
|
),
|
|
@@ -5900,13 +6220,56 @@ const INITIAL_STATE = {
|
|
|
5900
6220
|
function useStreaming(options) {
|
|
5901
6221
|
const [state, setState] = useState(INITIAL_STATE);
|
|
5902
6222
|
const eventSourceRef = useRef(null);
|
|
6223
|
+
const pollingRef = useRef(null);
|
|
6224
|
+
const pollingTimeoutRef = useRef(null);
|
|
6225
|
+
const streamStartRef = useRef(null);
|
|
6226
|
+
const doneRef = useRef(false);
|
|
5903
6227
|
const onDoneRef = useRef(options.onDone);
|
|
5904
6228
|
const onErrorRef = useRef(options.onError);
|
|
5905
6229
|
const onAssistantDeltaRef = useRef(options.onAssistantDelta);
|
|
5906
6230
|
onDoneRef.current = options.onDone;
|
|
5907
6231
|
onErrorRef.current = options.onError;
|
|
5908
6232
|
onAssistantDeltaRef.current = options.onAssistantDelta;
|
|
6233
|
+
function clearPolling() {
|
|
6234
|
+
if (pollingTimeoutRef.current) {
|
|
6235
|
+
window.clearTimeout(pollingTimeoutRef.current);
|
|
6236
|
+
pollingTimeoutRef.current = null;
|
|
6237
|
+
}
|
|
6238
|
+
if (pollingRef.current) {
|
|
6239
|
+
window.clearInterval(pollingRef.current);
|
|
6240
|
+
pollingRef.current = null;
|
|
6241
|
+
}
|
|
6242
|
+
}
|
|
6243
|
+
function startPolling(sessionKey, startedAt) {
|
|
6244
|
+
if (pollingRef.current) return;
|
|
6245
|
+
pollingRef.current = window.setInterval(async () => {
|
|
6246
|
+
try {
|
|
6247
|
+
const res = await fetch(
|
|
6248
|
+
`/api/history?sessionKey=${encodeURIComponent(sessionKey)}`
|
|
6249
|
+
);
|
|
6250
|
+
if (!res.ok) return;
|
|
6251
|
+
const data = await res.json();
|
|
6252
|
+
const messages = Array.isArray(data.messages) ? data.messages : [];
|
|
6253
|
+
const hasNewAssistant = messages.some((message) => {
|
|
6254
|
+
if (!message || message.role !== "assistant") return false;
|
|
6255
|
+
if (typeof message.timestamp !== "number") return false;
|
|
6256
|
+
return message.timestamp > startedAt - 3e3;
|
|
6257
|
+
});
|
|
6258
|
+
if (!hasNewAssistant) return;
|
|
6259
|
+
if (eventSourceRef.current) {
|
|
6260
|
+
eventSourceRef.current.close();
|
|
6261
|
+
eventSourceRef.current = null;
|
|
6262
|
+
}
|
|
6263
|
+
clearPolling();
|
|
6264
|
+
setState((prev) => ({ ...prev, active: false }));
|
|
6265
|
+
onDoneRef.current(sessionKey);
|
|
6266
|
+
} catch {
|
|
6267
|
+
}
|
|
6268
|
+
}, 2e3);
|
|
6269
|
+
}
|
|
5909
6270
|
const stop = useCallback((options2) => {
|
|
6271
|
+
doneRef.current = true;
|
|
6272
|
+
clearPolling();
|
|
5910
6273
|
if (eventSourceRef.current) {
|
|
5911
6274
|
eventSourceRef.current.close();
|
|
5912
6275
|
eventSourceRef.current = null;
|
|
@@ -5918,7 +6281,10 @@ function useStreaming(options) {
|
|
|
5918
6281
|
setState(INITIAL_STATE);
|
|
5919
6282
|
}, []);
|
|
5920
6283
|
const start = useCallback(
|
|
5921
|
-
(sessionKey)
|
|
6284
|
+
function start2(sessionKey) {
|
|
6285
|
+
doneRef.current = false;
|
|
6286
|
+
clearPolling();
|
|
6287
|
+
streamStartRef.current = Date.now();
|
|
5922
6288
|
if (eventSourceRef.current) {
|
|
5923
6289
|
eventSourceRef.current.close();
|
|
5924
6290
|
eventSourceRef.current = null;
|
|
@@ -5961,6 +6327,8 @@ function useStreaming(options) {
|
|
|
5961
6327
|
es.addEventListener("done", (e) => {
|
|
5962
6328
|
try {
|
|
5963
6329
|
const data = JSON.parse(e.data);
|
|
6330
|
+
doneRef.current = true;
|
|
6331
|
+
clearPolling();
|
|
5964
6332
|
es.close();
|
|
5965
6333
|
eventSourceRef.current = null;
|
|
5966
6334
|
setState((prev) => ({ ...prev, active: false }));
|
|
@@ -5976,6 +6344,11 @@ function useStreaming(options) {
|
|
|
5976
6344
|
onErrorRef.current?.("Stream connection lost");
|
|
5977
6345
|
}
|
|
5978
6346
|
};
|
|
6347
|
+
pollingTimeoutRef.current = window.setTimeout(() => {
|
|
6348
|
+
if (doneRef.current) return;
|
|
6349
|
+
const startedAt = streamStartRef.current ?? Date.now();
|
|
6350
|
+
startPolling(sessionKey, startedAt);
|
|
6351
|
+
}, 3e3);
|
|
5979
6352
|
},
|
|
5980
6353
|
[]
|
|
5981
6354
|
);
|
|
@@ -6223,7 +6596,7 @@ const KeyboardShortcutsDialog = lazy(
|
|
|
6223
6596
|
}))
|
|
6224
6597
|
);
|
|
6225
6598
|
const SearchDialog = lazy(
|
|
6226
|
-
() => import("./search-dialog-
|
|
6599
|
+
() => import("./search-dialog-DReM5ZD2.js").then((m) => ({
|
|
6227
6600
|
default: m.SearchDialog
|
|
6228
6601
|
}))
|
|
6229
6602
|
);
|
|
@@ -6507,9 +6880,9 @@ function ChatScreen({
|
|
|
6507
6880
|
const hideUi = shouldRedirectToNew || isRedirecting;
|
|
6508
6881
|
const pollingPhaseRef = useRef("fast");
|
|
6509
6882
|
useEffect(() => {
|
|
6510
|
-
const
|
|
6511
|
-
if (!
|
|
6512
|
-
const signature = `${historyMessages.length}:${textFromMessage(
|
|
6883
|
+
const lastAssistantMessage = [...historyMessages].reverse().find((message) => message.role === "assistant");
|
|
6884
|
+
if (!lastAssistantMessage) return;
|
|
6885
|
+
const signature = `${historyMessages.length}:${textFromMessage(lastAssistantMessage).slice(-64)}`;
|
|
6513
6886
|
if (signature !== lastAssistantSignature.current) {
|
|
6514
6887
|
lastAssistantSignature.current = signature;
|
|
6515
6888
|
if (pollingPhaseRef.current !== "fast" && streamTimer.current) {
|
|
@@ -6689,7 +7062,6 @@ function ChatScreen({
|
|
|
6689
7062
|
content: a.base64
|
|
6690
7063
|
}));
|
|
6691
7064
|
streamingNotificationTextRef.current = "";
|
|
6692
|
-
startStream(sessionKey);
|
|
6693
7065
|
streamStart();
|
|
6694
7066
|
fetch("/api/send", {
|
|
6695
7067
|
method: "POST",
|
|
@@ -6705,6 +7077,9 @@ function ChatScreen({
|
|
|
6705
7077
|
})
|
|
6706
7078
|
}).then(async (res) => {
|
|
6707
7079
|
if (!res.ok) throw new Error(await readError(res));
|
|
7080
|
+
const data = await res.json();
|
|
7081
|
+
const resolvedKey = data.sessionKey || sessionKey || friendlyId;
|
|
7082
|
+
startStream(resolvedKey);
|
|
6708
7083
|
}).catch((err) => {
|
|
6709
7084
|
const messageText = err instanceof Error ? err.message : String(err);
|
|
6710
7085
|
if (isMissingGatewayAuth(messageText)) {
|