opencami 1.7.0 → 1.8.3
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 +167 -7
- package/bin/opencami.js +15 -6
- package/dist/client/assets/{CSPContext-a-MQmQQt.js → CSPContext-DeJH85nm.js} +1 -1
- package/dist/client/assets/{DirectionContext-DRcND-Cm.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-BX1MWO82.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-BlC2sH55.js → index-DtGzE-ea.js} +1 -1
- package/dist/client/assets/{index-Dg0mbvtv.js → index-Yo5UhdZV.js} +1 -1
- package/dist/client/assets/keyboard-shortcuts-dialog-BZwd-iyV.js +1 -0
- package/dist/client/assets/{main-CEuT8-Qi.js → main-CgwdHc9W.js} +16 -8
- package/dist/client/assets/{markdown-DKD6ZLRJ.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-DA69yVKc.js → opencami-logo-Bmge6-FB.js} +1 -1
- package/dist/client/assets/popupStateMapping-D0ZbJR_o.js +1 -0
- package/dist/client/assets/{proxy-Cawf6X0W.js → proxy-CYZeDXoy.js} +1 -1
- package/dist/client/assets/{react-K9goXsVv.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-DFL-3BRH.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-Cl9XA2_f.js → useControlled-B5pEEz2V.js} +1 -1
- package/dist/client/assets/{useMutation-C5bTdeC1.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-BBG3ZUlo.js → _sessionKey-C9o7YfxA.js} +878 -755
- 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-BgMPaOsU.js → index-Bw-bA_2M.js} +4 -3
- package/dist/server/assets/{router-Bl2uabfY.js → router-DCjikH21.js} +704 -207
- package/dist/server/assets/{search-dialog-BtSQW9SR.js → search-dialog-BnwiXpdA.js} +5 -4
- package/dist/server/assets/settings-dialog-ClKFnZ1x.js +1511 -0
- package/dist/server/server.js +2 -2
- package/package.json +1 -1
- package/dist/client/assets/_sessionKey-BAmpzUOP.js +0 -23
- package/dist/client/assets/agents-BkeWu_3a.js +0 -2
- package/dist/client/assets/agents-screen-Cb76bcxn.js +0 -1
- package/dist/client/assets/bots-CyJwr-JU.js +0 -2
- package/dist/client/assets/bots-screen-CzNjLsQH.js +0 -1
- package/dist/client/assets/button-DNC5N25i.js +0 -1
- package/dist/client/assets/composite-Bliqcmg4.js +0 -1
- package/dist/client/assets/file-explorer-screen-CpY1O_ag.js +0 -1
- package/dist/client/assets/files-HiN5rXWq.js +0 -2
- package/dist/client/assets/keyboard-shortcuts-dialog-C2Hq19LN.js +0 -1
- package/dist/client/assets/memory-lhzf-8Q4.js +0 -2
- package/dist/client/assets/memory-screen-Zq9qfnJK.js +0 -1
- package/dist/client/assets/menu-47ooFeSm.js +0 -1
- package/dist/client/assets/owner-CFRNz_Tp.js +0 -1
- package/dist/client/assets/popupStateMapping-D5k-jOeY.js +0 -1
- package/dist/client/assets/search-dialog-C5Yae9rb.js +0 -1
- package/dist/client/assets/session-export-dialog-CBeTfbll.js +0 -1
- package/dist/client/assets/settings-dialog-CoeG9M1b.js +0 -1
- package/dist/client/assets/skills-BEkw619A.js +0 -2
- package/dist/client/assets/styles-D4EBtWYc.css +0 -1
- package/dist/client/assets/switch-DAFvLxNX.js +0 -1
- package/dist/client/assets/tabs-B2Y_7MvG.js +0 -1
- package/dist/client/assets/tooltip-D57Pal0B.js +0 -1
- package/dist/client/assets/use-file-explorer-state-DppKEjcl.js +0 -12
- package/dist/client/assets/useButton-DVAfkehQ.js +0 -1
- package/dist/client/assets/useCompositeItem-CzdGhGcj.js +0 -1
- package/dist/client/assets/visuallyHidden-CO3ZD5AQ.js +0 -1
- package/dist/server/assets/_tanstack-start-manifest_v-DmMFarHb.js +0 -4
- package/dist/server/assets/settings-dialog-D3fOAswX.js +0 -1173
|
@@ -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, File01Icon, ArtificialIntelligence02Icon, CommandIcon, Attachment01Icon, 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, u as useFileExplorerState } 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-DCjikH21.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
|
-
}
|
|
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 {
|
|
395
|
+
}
|
|
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
|
|
373
414
|
};
|
|
374
|
-
return JSON.stringify(data, null, 2);
|
|
375
415
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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("");
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
return lines.join("\n");
|
|
416
|
+
let pendingSend = null;
|
|
417
|
+
let pendingGeneration = false;
|
|
418
|
+
let recentSession = null;
|
|
419
|
+
function stashPendingSend(payload) {
|
|
420
|
+
pendingSend = payload;
|
|
393
421
|
}
|
|
394
|
-
function
|
|
395
|
-
|
|
396
|
-
let content;
|
|
397
|
-
let filename;
|
|
398
|
-
let mimeType;
|
|
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);
|
|
422
|
+
function hasPendingSend() {
|
|
423
|
+
return pendingSend !== null;
|
|
419
424
|
}
|
|
420
|
-
function
|
|
421
|
-
|
|
425
|
+
function setPendingGeneration(value) {
|
|
426
|
+
pendingGeneration = value;
|
|
422
427
|
}
|
|
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);
|
|
428
|
+
function hasPendingGeneration() {
|
|
429
|
+
return pendingGeneration;
|
|
433
430
|
}
|
|
434
|
-
function
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
] }) }) });
|
|
431
|
+
function resetPendingSend() {
|
|
432
|
+
pendingSend = null;
|
|
433
|
+
pendingGeneration = false;
|
|
474
434
|
}
|
|
475
|
-
function
|
|
476
|
-
|
|
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
|
+
}
|
|
477
444
|
}
|
|
478
|
-
function
|
|
479
|
-
|
|
480
|
-
/* @__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" }),
|
|
481
|
-
/* @__PURE__ */ jsx(
|
|
482
|
-
AlertDialog.Popup,
|
|
483
|
-
{
|
|
484
|
-
className: cn(
|
|
485
|
-
"fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2",
|
|
486
|
-
"w-[min(400px,92vw)] rounded-xl border border-primary-200 bg-primary-50 p-0 shadow-xl",
|
|
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
|
-
] });
|
|
445
|
+
function setRecentSession(friendlyId) {
|
|
446
|
+
recentSession = { friendlyId, at: Date.now() };
|
|
496
447
|
}
|
|
497
|
-
function
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
...props
|
|
503
|
-
}
|
|
504
|
-
);
|
|
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;
|
|
505
453
|
}
|
|
506
|
-
function
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
return /* @__PURE__ */ jsx(
|
|
520
|
-
AlertDialog.Close,
|
|
521
|
-
{
|
|
522
|
-
render: /* @__PURE__ */ jsx(Button, { variant: "outline", className: cn(className) }),
|
|
523
|
-
...props
|
|
524
|
-
}
|
|
525
|
-
);
|
|
526
|
-
}
|
|
527
|
-
function AlertDialogAction({ className, ...props }) {
|
|
528
|
-
return /* @__PURE__ */ jsx(
|
|
529
|
-
AlertDialog.Close,
|
|
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
|
-
}
|
|
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 }) {
|
|
620
748
|
return /* @__PURE__ */ jsx(
|
|
621
|
-
|
|
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)) {
|
|
@@ -1196,473 +1488,200 @@ ${failedSessionLabels.join("\n")}`
|
|
|
1196
1488
|
selectionMode,
|
|
1197
1489
|
selected: selectedSessionKeys.has(session.key),
|
|
1198
1490
|
onToggleSelect: handleToggleSelect,
|
|
1199
|
-
onSelect,
|
|
1200
|
-
onTogglePin: handleTogglePin,
|
|
1201
|
-
onRename,
|
|
1202
|
-
onDelete,
|
|
1203
|
-
onExport
|
|
1204
|
-
},
|
|
1205
|
-
session.key
|
|
1206
|
-
)) })
|
|
1207
|
-
]
|
|
1208
|
-
}
|
|
1209
|
-
);
|
|
1210
|
-
}
|
|
1211
|
-
return /* @__PURE__ */ jsxs(
|
|
1212
|
-
Collapsible,
|
|
1213
|
-
{
|
|
1214
|
-
className: "flex h-full flex-col flex-1 min-h-0 w-full",
|
|
1215
|
-
defaultOpen,
|
|
1216
|
-
children: [
|
|
1217
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pr-2 shrink-0", children: [
|
|
1218
|
-
/* @__PURE__ */ jsxs(CollapsibleTrigger, { className: "w-fit pl-1.5 text-balance", children: [
|
|
1219
|
-
"Sessions",
|
|
1220
|
-
/* @__PURE__ */ jsx("span", { className: "opacity-0 transition-opacity duration-150 group-hover:opacity-100", children: /* @__PURE__ */ jsx(
|
|
1221
|
-
HugeiconsIcon,
|
|
1222
|
-
{
|
|
1223
|
-
icon: ArrowRight01Icon,
|
|
1224
|
-
className: "size-3 transition-transform duration-150 group-data-panel-open:rotate-90"
|
|
1225
|
-
}
|
|
1226
|
-
) })
|
|
1227
|
-
] }),
|
|
1228
|
-
/* @__PURE__ */ jsx(
|
|
1229
|
-
"button",
|
|
1230
|
-
{
|
|
1231
|
-
type: "button",
|
|
1232
|
-
onClick: handleSelectionModeToggle,
|
|
1233
|
-
className: cn(
|
|
1234
|
-
"text-[11px] font-medium px-1.5 py-0.5 rounded-md transition-colors",
|
|
1235
|
-
selectionMode ? "text-primary-900 bg-primary-200" : "text-primary-500 hover:text-primary-700 hover:bg-primary-100"
|
|
1236
|
-
),
|
|
1237
|
-
children: selectionMode ? "Done" : "Select"
|
|
1238
|
-
}
|
|
1239
|
-
)
|
|
1240
|
-
] }),
|
|
1241
|
-
/* @__PURE__ */ jsx(
|
|
1242
|
-
CollapsiblePanel,
|
|
1243
|
-
{
|
|
1244
|
-
className: "w-full flex-1 min-h-0 h-auto data-starting-style:h-0 data-ending-style:h-0",
|
|
1245
|
-
contentClassName: "flex flex-1 min-h-0 flex-col overflow-y-auto",
|
|
1246
|
-
children: /* @__PURE__ */ jsxs(ScrollAreaRoot, { className: "flex-1 min-h-0", children: [
|
|
1247
|
-
/* @__PURE__ */ jsx(ScrollAreaViewport, { className: "min-h-0", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 pl-2 pr-2", children: [
|
|
1248
|
-
pinnedSessions.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-px", children: pinnedSessions.map((session) => /* @__PURE__ */ jsx(
|
|
1249
|
-
SessionItem,
|
|
1250
|
-
{
|
|
1251
|
-
session,
|
|
1252
|
-
active: isSessionActive(session, activeFriendlyId, activeSessionKey),
|
|
1253
|
-
isGenerating: isSessionGenerating(session, activeFriendlyId, activeSessionKey, isStreaming),
|
|
1254
|
-
isPinned: true,
|
|
1255
|
-
selectionMode,
|
|
1256
|
-
selected: selectedSessionKeys.has(session.key),
|
|
1257
|
-
onToggleSelect: handleToggleSelect,
|
|
1258
|
-
onSelect,
|
|
1259
|
-
onTogglePin: handleTogglePin,
|
|
1260
|
-
onRename,
|
|
1261
|
-
onDelete,
|
|
1262
|
-
onExport
|
|
1263
|
-
},
|
|
1264
|
-
session.key
|
|
1265
|
-
)) }) : null,
|
|
1266
|
-
showDivider ? /* @__PURE__ */ jsx("div", { className: "my-1 border-t border-primary-200/80" }) : null,
|
|
1267
|
-
groupedSessions.chat.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-px", children: [
|
|
1268
|
-
/* @__PURE__ */ jsx(
|
|
1269
|
-
"div",
|
|
1270
|
-
{
|
|
1271
|
-
className: cn(
|
|
1272
|
-
"border-l-2 border-primary-200/70 px-2 py-1 text-[11px] font-medium text-primary-500/80 text-balance"
|
|
1273
|
-
),
|
|
1274
|
-
children: "💬 Chats"
|
|
1275
|
-
}
|
|
1276
|
-
),
|
|
1277
|
-
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-px", children: groupedSessions.chat.map((session) => /* @__PURE__ */ jsx(
|
|
1278
|
-
SessionItem,
|
|
1279
|
-
{
|
|
1280
|
-
session,
|
|
1281
|
-
active: isSessionActive(session, activeFriendlyId, activeSessionKey),
|
|
1282
|
-
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
|
-
);
|
|
1491
|
+
onSelect,
|
|
1492
|
+
onTogglePin: handleTogglePin,
|
|
1493
|
+
onRename,
|
|
1494
|
+
onDelete,
|
|
1495
|
+
onExport
|
|
1496
|
+
},
|
|
1497
|
+
session.key
|
|
1498
|
+
)) })
|
|
1499
|
+
]
|
|
1644
1500
|
}
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
return /* @__PURE__ */ jsxs(
|
|
1504
|
+
Collapsible,
|
|
1505
|
+
{
|
|
1506
|
+
className: "flex h-full flex-col flex-1 min-h-0 w-full",
|
|
1507
|
+
defaultOpen,
|
|
1508
|
+
children: [
|
|
1509
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pr-2 shrink-0", children: [
|
|
1510
|
+
/* @__PURE__ */ jsxs(CollapsibleTrigger, { className: "w-fit pl-1.5 text-balance", children: [
|
|
1511
|
+
"Sessions",
|
|
1512
|
+
/* @__PURE__ */ jsx("span", { className: "opacity-0 transition-opacity duration-150 group-hover:opacity-100", children: /* @__PURE__ */ jsx(
|
|
1513
|
+
HugeiconsIcon,
|
|
1514
|
+
{
|
|
1515
|
+
icon: ArrowRight01Icon,
|
|
1516
|
+
className: "size-3 transition-transform duration-150 group-data-panel-open:rotate-90"
|
|
1517
|
+
}
|
|
1518
|
+
) })
|
|
1519
|
+
] }),
|
|
1520
|
+
/* @__PURE__ */ jsx(
|
|
1521
|
+
"button",
|
|
1522
|
+
{
|
|
1523
|
+
type: "button",
|
|
1524
|
+
onClick: handleSelectionModeToggle,
|
|
1525
|
+
className: cn(
|
|
1526
|
+
"text-[11px] font-medium px-1.5 py-0.5 rounded-md transition-colors",
|
|
1527
|
+
selectionMode ? "text-primary-900 bg-primary-200" : "text-primary-500 hover:text-primary-700 hover:bg-primary-100"
|
|
1528
|
+
),
|
|
1529
|
+
children: selectionMode ? "Done" : "Select"
|
|
1530
|
+
}
|
|
1531
|
+
)
|
|
1532
|
+
] }),
|
|
1533
|
+
/* @__PURE__ */ jsx(
|
|
1534
|
+
CollapsiblePanel,
|
|
1535
|
+
{
|
|
1536
|
+
className: "w-full flex-1 min-h-0 h-auto data-starting-style:h-0 data-ending-style:h-0",
|
|
1537
|
+
contentClassName: "flex flex-1 min-h-0 flex-col overflow-y-auto",
|
|
1538
|
+
children: /* @__PURE__ */ jsxs(ScrollAreaRoot, { className: "flex-1 min-h-0", children: [
|
|
1539
|
+
/* @__PURE__ */ jsx(ScrollAreaViewport, { className: "min-h-0", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 pl-2 pr-2", children: [
|
|
1540
|
+
pinnedSessions.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-px", children: pinnedSessions.map((session) => /* @__PURE__ */ jsx(
|
|
1541
|
+
SessionItem,
|
|
1542
|
+
{
|
|
1543
|
+
session,
|
|
1544
|
+
active: isSessionActive(session, activeFriendlyId, activeSessionKey),
|
|
1545
|
+
isGenerating: isSessionGenerating(session, activeFriendlyId, activeSessionKey, isStreaming),
|
|
1546
|
+
isPinned: true,
|
|
1547
|
+
selectionMode,
|
|
1548
|
+
selected: selectedSessionKeys.has(session.key),
|
|
1549
|
+
onToggleSelect: handleToggleSelect,
|
|
1550
|
+
onSelect,
|
|
1551
|
+
onTogglePin: handleTogglePin,
|
|
1552
|
+
onRename,
|
|
1553
|
+
onDelete,
|
|
1554
|
+
onExport
|
|
1555
|
+
},
|
|
1556
|
+
session.key
|
|
1557
|
+
)) }) : null,
|
|
1558
|
+
showDivider ? /* @__PURE__ */ jsx("div", { className: "my-1 border-t border-primary-200/80" }) : null,
|
|
1559
|
+
groupedSessions.chat.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-px", children: [
|
|
1560
|
+
/* @__PURE__ */ jsx(
|
|
1561
|
+
"div",
|
|
1562
|
+
{
|
|
1563
|
+
className: cn(
|
|
1564
|
+
"border-l-2 border-primary-200/70 px-2 py-1 text-[11px] font-medium text-primary-500/80 text-balance"
|
|
1565
|
+
),
|
|
1566
|
+
children: "💬 Chats"
|
|
1567
|
+
}
|
|
1568
|
+
),
|
|
1569
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-px", children: groupedSessions.chat.map((session) => /* @__PURE__ */ jsx(
|
|
1570
|
+
SessionItem,
|
|
1571
|
+
{
|
|
1572
|
+
session,
|
|
1573
|
+
active: isSessionActive(session, activeFriendlyId, activeSessionKey),
|
|
1574
|
+
isGenerating: isSessionGenerating(session, activeFriendlyId, activeSessionKey, isStreaming),
|
|
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-ClKFnZ1x.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
|
}
|
|
@@ -3378,6 +3439,10 @@ const LLM_PROVIDER_DEFAULTS = {
|
|
|
3378
3439
|
baseUrl: "https://openrouter.ai/api/v1",
|
|
3379
3440
|
model: "openai/gpt-oss-120b"
|
|
3380
3441
|
},
|
|
3442
|
+
kilocode: {
|
|
3443
|
+
baseUrl: "https://api.kilo.ai/api/gateway",
|
|
3444
|
+
model: "google/gemini-2.5-flash"
|
|
3445
|
+
},
|
|
3381
3446
|
ollama: {
|
|
3382
3447
|
baseUrl: "http://localhost:11434/v1",
|
|
3383
3448
|
model: "llama3.2"
|
|
@@ -3409,7 +3474,7 @@ function getEffectiveLlmModel(settings) {
|
|
|
3409
3474
|
function getAvailability(settings, hasEnvKey) {
|
|
3410
3475
|
if (settings.llmProvider === "ollama") return true;
|
|
3411
3476
|
if (settings.llmProvider === "custom") {
|
|
3412
|
-
return Boolean(settings.llmApiKey.trim()) || settings.llmBaseUrl.trim() && settings.llmModel.trim();
|
|
3477
|
+
return Boolean(settings.llmApiKey.trim()) || Boolean(settings.llmBaseUrl.trim() && settings.llmModel.trim());
|
|
3413
3478
|
}
|
|
3414
3479
|
return hasEnvKey || Boolean(settings.llmApiKey.trim());
|
|
3415
3480
|
}
|
|
@@ -3458,6 +3523,7 @@ function useLlmSettings() {
|
|
|
3458
3523
|
const [status, setStatus] = useState({
|
|
3459
3524
|
hasEnvKey: false,
|
|
3460
3525
|
hasOpenRouterKey: false,
|
|
3526
|
+
hasKilocodeKey: false,
|
|
3461
3527
|
hasUserKey: Boolean(settings.llmApiKey),
|
|
3462
3528
|
isAvailable: getAvailability(settings, false),
|
|
3463
3529
|
isLoading: true,
|
|
@@ -3472,10 +3538,11 @@ function useLlmSettings() {
|
|
|
3472
3538
|
const data = await res.json();
|
|
3473
3539
|
if (cancelled) return;
|
|
3474
3540
|
const hasUserKey = Boolean(settings.llmApiKey);
|
|
3475
|
-
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;
|
|
3476
3542
|
setStatus({
|
|
3477
3543
|
hasEnvKey: data.hasEnvKey,
|
|
3478
3544
|
hasOpenRouterKey: Boolean(data.hasOpenRouterKey),
|
|
3545
|
+
hasKilocodeKey: Boolean(data.hasKilocodeKey),
|
|
3479
3546
|
hasUserKey,
|
|
3480
3547
|
isAvailable: getAvailability(settings, hasProviderKey),
|
|
3481
3548
|
isLoading: false,
|
|
@@ -4072,13 +4139,14 @@ function ChatMessageListComponent({
|
|
|
4072
4139
|
return sources;
|
|
4073
4140
|
}, [displayMessages, toolResultsByCallId]);
|
|
4074
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();
|
|
4075
4143
|
const lastUserIndex = displayMessages.map((message, index) => ({ message, index })).filter(({ message }) => message.role === "user").map(({ index }) => index).pop();
|
|
4076
4144
|
const showTypingIndicator = waitingForResponse && (typeof lastUserIndex !== "number" || typeof lastAssistantIndex !== "number" || lastAssistantIndex < lastUserIndex);
|
|
4077
4145
|
const groupStartIndex = typeof lastUserIndex === "number" ? lastUserIndex : -1;
|
|
4078
4146
|
const hasGroup = pinToTop && groupStartIndex >= 0;
|
|
4079
|
-
const lastAssistantMessage = typeof
|
|
4147
|
+
const lastAssistantMessage = typeof lastTextAssistantIndex === "number" ? displayMessages[lastTextAssistantIndex] : void 0;
|
|
4080
4148
|
const lastAssistantText = lastAssistantMessage ? textFromMessage(lastAssistantMessage) : "";
|
|
4081
|
-
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);
|
|
4082
4150
|
useLayoutEffect(() => {
|
|
4083
4151
|
if (loading) return;
|
|
4084
4152
|
if (pinToTop) {
|
|
@@ -6152,13 +6220,56 @@ const INITIAL_STATE = {
|
|
|
6152
6220
|
function useStreaming(options) {
|
|
6153
6221
|
const [state, setState] = useState(INITIAL_STATE);
|
|
6154
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);
|
|
6155
6227
|
const onDoneRef = useRef(options.onDone);
|
|
6156
6228
|
const onErrorRef = useRef(options.onError);
|
|
6157
6229
|
const onAssistantDeltaRef = useRef(options.onAssistantDelta);
|
|
6158
6230
|
onDoneRef.current = options.onDone;
|
|
6159
6231
|
onErrorRef.current = options.onError;
|
|
6160
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
|
+
}
|
|
6161
6270
|
const stop = useCallback((options2) => {
|
|
6271
|
+
doneRef.current = true;
|
|
6272
|
+
clearPolling();
|
|
6162
6273
|
if (eventSourceRef.current) {
|
|
6163
6274
|
eventSourceRef.current.close();
|
|
6164
6275
|
eventSourceRef.current = null;
|
|
@@ -6170,7 +6281,10 @@ function useStreaming(options) {
|
|
|
6170
6281
|
setState(INITIAL_STATE);
|
|
6171
6282
|
}, []);
|
|
6172
6283
|
const start = useCallback(
|
|
6173
|
-
(sessionKey)
|
|
6284
|
+
function start2(sessionKey) {
|
|
6285
|
+
doneRef.current = false;
|
|
6286
|
+
clearPolling();
|
|
6287
|
+
streamStartRef.current = Date.now();
|
|
6174
6288
|
if (eventSourceRef.current) {
|
|
6175
6289
|
eventSourceRef.current.close();
|
|
6176
6290
|
eventSourceRef.current = null;
|
|
@@ -6213,6 +6327,8 @@ function useStreaming(options) {
|
|
|
6213
6327
|
es.addEventListener("done", (e) => {
|
|
6214
6328
|
try {
|
|
6215
6329
|
const data = JSON.parse(e.data);
|
|
6330
|
+
doneRef.current = true;
|
|
6331
|
+
clearPolling();
|
|
6216
6332
|
es.close();
|
|
6217
6333
|
eventSourceRef.current = null;
|
|
6218
6334
|
setState((prev) => ({ ...prev, active: false }));
|
|
@@ -6228,6 +6344,11 @@ function useStreaming(options) {
|
|
|
6228
6344
|
onErrorRef.current?.("Stream connection lost");
|
|
6229
6345
|
}
|
|
6230
6346
|
};
|
|
6347
|
+
pollingTimeoutRef.current = window.setTimeout(() => {
|
|
6348
|
+
if (doneRef.current) return;
|
|
6349
|
+
const startedAt = streamStartRef.current ?? Date.now();
|
|
6350
|
+
startPolling(sessionKey, startedAt);
|
|
6351
|
+
}, 3e3);
|
|
6231
6352
|
},
|
|
6232
6353
|
[]
|
|
6233
6354
|
);
|
|
@@ -6475,7 +6596,7 @@ const KeyboardShortcutsDialog = lazy(
|
|
|
6475
6596
|
}))
|
|
6476
6597
|
);
|
|
6477
6598
|
const SearchDialog = lazy(
|
|
6478
|
-
() => import("./search-dialog-
|
|
6599
|
+
() => import("./search-dialog-BnwiXpdA.js").then((m) => ({
|
|
6479
6600
|
default: m.SearchDialog
|
|
6480
6601
|
}))
|
|
6481
6602
|
);
|
|
@@ -6759,9 +6880,9 @@ function ChatScreen({
|
|
|
6759
6880
|
const hideUi = shouldRedirectToNew || isRedirecting;
|
|
6760
6881
|
const pollingPhaseRef = useRef("fast");
|
|
6761
6882
|
useEffect(() => {
|
|
6762
|
-
const
|
|
6763
|
-
if (!
|
|
6764
|
-
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)}`;
|
|
6765
6886
|
if (signature !== lastAssistantSignature.current) {
|
|
6766
6887
|
lastAssistantSignature.current = signature;
|
|
6767
6888
|
if (pollingPhaseRef.current !== "fast" && streamTimer.current) {
|
|
@@ -6941,7 +7062,6 @@ function ChatScreen({
|
|
|
6941
7062
|
content: a.base64
|
|
6942
7063
|
}));
|
|
6943
7064
|
streamingNotificationTextRef.current = "";
|
|
6944
|
-
startStream(sessionKey);
|
|
6945
7065
|
streamStart();
|
|
6946
7066
|
fetch("/api/send", {
|
|
6947
7067
|
method: "POST",
|
|
@@ -6957,6 +7077,9 @@ function ChatScreen({
|
|
|
6957
7077
|
})
|
|
6958
7078
|
}).then(async (res) => {
|
|
6959
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);
|
|
6960
7083
|
}).catch((err) => {
|
|
6961
7084
|
const messageText = err instanceof Error ? err.message : String(err);
|
|
6962
7085
|
if (isMissingGatewayAuth(messageText)) {
|