opencami 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/_sessionKey-DB95zj1L.js +97 -0
- package/dist/client/assets/agents-LFrWe-HX.js +2 -0
- package/dist/client/assets/agents-screen-CEBBk1yO.js +1 -0
- package/dist/client/assets/bots-CxDwf_WK.js +2 -0
- package/dist/client/assets/bots-screen-D6qma1wK.js +1 -0
- package/dist/client/assets/button-Il3CHIzX.js +1 -0
- package/dist/client/assets/{connect-B8sfEQyb.js → connect-D3baVDFL.js} +1 -1
- package/dist/client/assets/file-explorer-screen-rtV6n-5_.js +1 -0
- package/dist/client/assets/files-DMemuq9D.js +2 -0
- package/dist/client/assets/{index-BfhiL2JN.js → index-ByIsZcHh.js} +1 -1
- package/dist/client/assets/index-CVV4XiZo.js +153 -0
- package/dist/client/assets/keyboard-shortcuts-dialog-DXC0YHoy.js +1 -0
- package/dist/client/assets/{main-BlX7CS2-.js → main-CkIF0soY.js} +9 -9
- package/dist/client/assets/opencami-logo-CIxSO1oo.js +1 -0
- package/dist/client/assets/{react-ONC2UaeC.js → react-BhVdgA5r.js} +1 -1
- package/dist/client/assets/search-dialog-I1jJplIh.js +1 -0
- package/dist/client/assets/session-export-dialog-CH5unryw.js +1 -0
- package/dist/client/assets/settings-dialog-B8v-GVJ8.js +1 -0
- package/dist/client/assets/styles-BaTVzdPa.css +1 -0
- package/dist/client/assets/switch-sQnv1YsK.js +1 -0
- package/dist/client/assets/tooltip-j_viC_EE.js +1 -0
- package/dist/client/assets/use-file-explorer-state-BUH-u7Jv.js +12 -0
- package/dist/client/assets/useButton-9VAzplAB.js +9 -0
- package/dist/client/assets/useControlled-Y306krcC.js +1 -0
- package/dist/client/assets/useMutation-0WgW4xQJ.js +1 -0
- package/dist/server/assets/{_sessionKey-ZF1_Jqbt.js → _sessionKey-BhFH4uWY.js} +360 -62
- package/dist/server/assets/_tanstack-start-manifest_v-BaIrL1VQ.js +4 -0
- package/dist/server/assets/bots-6ryCIgKh.js +11 -0
- package/dist/server/assets/bots-screen-DS_ZF9Ec.js +417 -0
- package/dist/server/assets/{connect-CIDOw12K.js → connect-B8jpGQGK.js} +1 -1
- package/dist/server/assets/{file-explorer-screen-BzvgvV8m.js → file-explorer-screen-DCfS_Ajx.js} +76 -20
- package/dist/server/assets/{files-BxvRDIWU.js → files-D2GIrPF4.js} +1 -1
- package/dist/server/assets/{index-CTTNe_Sf.js → index-B2JHn34C.js} +1 -1
- package/dist/server/assets/{index-CmbNTqa2.js → index-BNSsDaLb.js} +71 -35
- package/dist/server/assets/{keyboard-shortcuts-dialog-7OEtXUlW.js → keyboard-shortcuts-dialog-CqIm8aYF.js} +1 -1
- package/dist/server/assets/{router-D5D0udHV.js → router-Dme7USeO.js} +219 -74
- package/dist/server/assets/{search-dialog-C8Oy-FBs.js → search-dialog-DG0D9KRN.js} +97 -54
- package/dist/server/assets/{session-export-dialog-DRVbC8Q-.js → session-export-dialog-DLPZVlQV.js} +1 -1
- package/dist/server/assets/{settings-dialog-CQFuAt9B.js → settings-dialog-BaGT4e5l.js} +36 -7
- package/dist/server/assets/{use-file-explorer-state-DMHdtb7D.js → use-file-explorer-state-DfAKF2gZ.js} +12 -0
- package/dist/server/server.js +2 -2
- package/package.json +1 -1
- package/dist/client/assets/_sessionKey-CaDTbjXx.js +0 -97
- package/dist/client/assets/agents-CGtqdyBi.js +0 -2
- package/dist/client/assets/agents-screen-DzIMcmHe.js +0 -1
- package/dist/client/assets/button-BcnCTjUT.js +0 -1
- package/dist/client/assets/file-explorer-screen-hjggspl-.js +0 -1
- package/dist/client/assets/files-BHXmS1J5.js +0 -2
- package/dist/client/assets/index-CxJ4zG_W.js +0 -153
- package/dist/client/assets/keyboard-shortcuts-dialog-DEknElTu.js +0 -1
- package/dist/client/assets/opencami-logo-5KynvViW.js +0 -1
- package/dist/client/assets/search-dialog-40gW31ca.js +0 -1
- package/dist/client/assets/session-export-dialog-DSRWU2YO.js +0 -1
- package/dist/client/assets/settings-dialog-AsvYzSBc.js +0 -1
- package/dist/client/assets/styles-BGTCU8mq.css +0 -1
- package/dist/client/assets/switch-dAvZcYA-.js +0 -1
- package/dist/client/assets/use-file-explorer-state-C6lX-h0n.js +0 -12
- package/dist/client/assets/useButton-DhneLsMA.js +0 -9
- package/dist/server/assets/_tanstack-start-manifest_v-C2FijMus.js +0 -4
|
@@ -5,9 +5,9 @@ import React__default, { memo, useDeferredValue, useState, useMemo, useCallback,
|
|
|
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-gbV6rEVv.js";
|
|
7
7
|
import { HugeiconsIcon } from "@hugeicons/react";
|
|
8
|
-
import { Tick01Icon, Cancel01Icon, MoreHorizontalIcon, Pen01Icon, Upload01Icon, Delete01Icon, BotIcon, Clock01Icon, Chat01Icon, ArrowRight01Icon, SidebarLeft01Icon, PencilEdit02Icon, Folder01Icon, AiBrain01Icon, Search01Icon, Settings01Icon, Menu01Icon, Tick02Icon, Copy01Icon, Loading02Icon, StopIcon, VolumeHighIcon, ArrowDown01Icon, Loading03Icon, ArtificialIntelligence02Icon, CommandIcon, Attachment01Icon, File01Icon, Mic02Icon, ArrowUp02Icon } from "@hugeicons/core-free-icons";
|
|
8
|
+
import { Tick01Icon, Cancel01Icon, MoreHorizontalIcon, Pen01Icon, Upload01Icon, Delete01Icon, BotIcon, Clock01Icon, Chat01Icon, ArrowRight01Icon, SidebarLeft01Icon, PencilEdit02Icon, Folder01Icon, AiBrain01Icon, 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, M as MenuRoot, e as MenuTrigger, f as MenuContent, g as MenuItem, u as useFileExplorerState } from "./use-file-explorer-state-
|
|
10
|
+
import { D as DialogRoot, a as DialogContent, b as DialogTitle, c as DialogDescription, d as DialogClose, M as MenuRoot, e as MenuTrigger, f as MenuContent, g as MenuItem, u as useFileExplorerState } from "./use-file-explorer-state-DfAKF2gZ.js";
|
|
11
11
|
import { B as Button, c as cn, b as buttonVariants } from "./button-DtQ3rV1m.js";
|
|
12
12
|
import { AlertDialog } from "@base-ui/react/alert-dialog";
|
|
13
13
|
import { Collapsible as Collapsible$1 } from "@base-ui/react/collapsible";
|
|
@@ -17,11 +17,11 @@ import { marked } from "marked";
|
|
|
17
17
|
import ReactMarkdown from "react-markdown";
|
|
18
18
|
import remarkBreaks from "remark-breaks";
|
|
19
19
|
import remarkGfm from "remark-gfm";
|
|
20
|
-
import { r as resolveLanguage,
|
|
20
|
+
import { r as resolveLanguage, C as CodeBlock, u as useChatSettings$1 } from "./index-BNSsDaLb.js";
|
|
21
21
|
import { create } from "zustand";
|
|
22
22
|
import { persist } from "zustand/middleware";
|
|
23
23
|
import { createPortal } from "react-dom";
|
|
24
|
-
import { a as Route } from "./router-
|
|
24
|
+
import { a as Route } from "./router-Dme7USeO.js";
|
|
25
25
|
function deriveFriendlyIdFromKey(key) {
|
|
26
26
|
if (!key) return "main";
|
|
27
27
|
const trimmed = key.trim();
|
|
@@ -784,7 +784,7 @@ function SessionItemComponent({
|
|
|
784
784
|
className: "text-primary-500/70 shrink-0"
|
|
785
785
|
}
|
|
786
786
|
),
|
|
787
|
-
/* @__PURE__ */ jsxs("div", { className: "text-sm font-[450]
|
|
787
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 truncate text-sm font-[450]", title: label, children: [
|
|
788
788
|
isPinned ? /* @__PURE__ */ jsx("span", { className: "mr-1 text-xs text-primary-700", "aria-hidden": "true", children: "📌" }) : null,
|
|
789
789
|
label
|
|
790
790
|
] }),
|
|
@@ -1649,10 +1649,10 @@ function useRenameSession() {
|
|
|
1649
1649
|
return { renameSession, renaming, error };
|
|
1650
1650
|
}
|
|
1651
1651
|
const SettingsDialog = lazy(
|
|
1652
|
-
() => import("./settings-dialog-
|
|
1652
|
+
() => import("./settings-dialog-BaGT4e5l.js").then((m) => ({ default: m.SettingsDialog }))
|
|
1653
1653
|
);
|
|
1654
1654
|
const SessionExportDialog = lazy(
|
|
1655
|
-
() => import("./session-export-dialog-
|
|
1655
|
+
() => import("./session-export-dialog-DLPZVlQV.js").then((m) => ({
|
|
1656
1656
|
default: m.SessionExportDialog
|
|
1657
1657
|
}))
|
|
1658
1658
|
);
|
|
@@ -1934,6 +1934,48 @@ function ChatSidebarComponent({
|
|
|
1934
1934
|
}
|
|
1935
1935
|
) }),
|
|
1936
1936
|
isCollapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Agents" })
|
|
1937
|
+
] }) }),
|
|
1938
|
+
(() => {
|
|
1939
|
+
try {
|
|
1940
|
+
return localStorage.getItem("opencami-cron-manager") === "true";
|
|
1941
|
+
} catch {
|
|
1942
|
+
return false;
|
|
1943
|
+
}
|
|
1944
|
+
})() && /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(TooltipRoot, { children: [
|
|
1945
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
1946
|
+
Link,
|
|
1947
|
+
{
|
|
1948
|
+
to: "/bots",
|
|
1949
|
+
className: cn(
|
|
1950
|
+
buttonVariants({ variant: "ghost", size: "sm" }),
|
|
1951
|
+
"w-full pl-1.5 justify-start"
|
|
1952
|
+
),
|
|
1953
|
+
onClick: onSelectSession,
|
|
1954
|
+
children: [
|
|
1955
|
+
/* @__PURE__ */ jsx(
|
|
1956
|
+
HugeiconsIcon,
|
|
1957
|
+
{
|
|
1958
|
+
icon: SmartPhone01Icon,
|
|
1959
|
+
size: 20,
|
|
1960
|
+
strokeWidth: 1.5,
|
|
1961
|
+
className: "min-w-5"
|
|
1962
|
+
}
|
|
1963
|
+
),
|
|
1964
|
+
/* @__PURE__ */ jsx(AnimatePresence, { initial: false, mode: "wait", children: !isCollapsed && /* @__PURE__ */ jsx(
|
|
1965
|
+
motion.span,
|
|
1966
|
+
{
|
|
1967
|
+
initial: { opacity: 0 },
|
|
1968
|
+
animate: { opacity: 1 },
|
|
1969
|
+
exit: { opacity: 0 },
|
|
1970
|
+
transition,
|
|
1971
|
+
className: "overflow-hidden whitespace-nowrap",
|
|
1972
|
+
children: "Cron Jobs"
|
|
1973
|
+
}
|
|
1974
|
+
) })
|
|
1975
|
+
]
|
|
1976
|
+
}
|
|
1977
|
+
) }),
|
|
1978
|
+
isCollapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Cron Jobs" })
|
|
1937
1979
|
] }) })
|
|
1938
1980
|
]
|
|
1939
1981
|
}
|
|
@@ -2281,7 +2323,7 @@ function ChatHeaderComponent({
|
|
|
2281
2323
|
"div",
|
|
2282
2324
|
{
|
|
2283
2325
|
ref: wrapperRef,
|
|
2284
|
-
className: "border-b border-primary-200 px-4 h-12 flex items-center bg-surface",
|
|
2326
|
+
className: "border-b border-primary-200 px-4 h-12 flex min-w-0 items-center overflow-x-hidden bg-surface",
|
|
2285
2327
|
children: [
|
|
2286
2328
|
showSidebarButton ? /* @__PURE__ */ jsx(
|
|
2287
2329
|
Button,
|
|
@@ -2294,7 +2336,7 @@ function ChatHeaderComponent({
|
|
|
2294
2336
|
children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Menu01Icon, size: 18, strokeWidth: 1.6 })
|
|
2295
2337
|
}
|
|
2296
2338
|
) : null,
|
|
2297
|
-
/* @__PURE__ */ jsx("div", { className: "
|
|
2339
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0 overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium", children: activeTitle }) }),
|
|
2298
2340
|
/* @__PURE__ */ jsx(
|
|
2299
2341
|
MemoizedContextMeter,
|
|
2300
2342
|
{
|
|
@@ -2353,6 +2395,7 @@ function MessageActionsBar({
|
|
|
2353
2395
|
const [audioState, setAudioState] = useState("idle");
|
|
2354
2396
|
const audioRef = useRef(null);
|
|
2355
2397
|
const objectUrlRef = useRef(null);
|
|
2398
|
+
const ttsAbortControllerRef = useRef(null);
|
|
2356
2399
|
const [ttsEnabled, setTtsEnabled] = useState(() => {
|
|
2357
2400
|
if (typeof window === "undefined") return true;
|
|
2358
2401
|
try {
|
|
@@ -2373,6 +2416,7 @@ function MessageActionsBar({
|
|
|
2373
2416
|
}, []);
|
|
2374
2417
|
useEffect(() => {
|
|
2375
2418
|
return () => {
|
|
2419
|
+
ttsAbortControllerRef.current?.abort();
|
|
2376
2420
|
if (audioRef.current) {
|
|
2377
2421
|
audioRef.current.pause();
|
|
2378
2422
|
audioRef.current = null;
|
|
@@ -2394,6 +2438,7 @@ function MessageActionsBar({
|
|
|
2394
2438
|
};
|
|
2395
2439
|
const handleTTS = async () => {
|
|
2396
2440
|
if (audioState === "playing") {
|
|
2441
|
+
ttsAbortControllerRef.current?.abort();
|
|
2397
2442
|
if (audioRef.current) {
|
|
2398
2443
|
audioRef.current.pause();
|
|
2399
2444
|
audioRef.current = null;
|
|
@@ -2405,12 +2450,16 @@ function MessageActionsBar({
|
|
|
2405
2450
|
setAudioState("idle");
|
|
2406
2451
|
return;
|
|
2407
2452
|
}
|
|
2453
|
+
ttsAbortControllerRef.current?.abort();
|
|
2454
|
+
const controller = new AbortController();
|
|
2455
|
+
ttsAbortControllerRef.current = controller;
|
|
2408
2456
|
setAudioState("loading");
|
|
2409
2457
|
try {
|
|
2410
2458
|
const res = await fetch("/api/tts", {
|
|
2411
2459
|
method: "POST",
|
|
2412
2460
|
headers: { "Content-Type": "application/json" },
|
|
2413
|
-
body: JSON.stringify({ text })
|
|
2461
|
+
body: JSON.stringify({ text }),
|
|
2462
|
+
signal: controller.signal
|
|
2414
2463
|
});
|
|
2415
2464
|
if (!res.ok) throw new Error("TTS failed");
|
|
2416
2465
|
const blob = await res.blob();
|
|
@@ -2436,7 +2485,11 @@ function MessageActionsBar({
|
|
|
2436
2485
|
};
|
|
2437
2486
|
await audio.play();
|
|
2438
2487
|
setAudioState("playing");
|
|
2439
|
-
} catch {
|
|
2488
|
+
} catch (error) {
|
|
2489
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2490
|
+
setAudioState("idle");
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2440
2493
|
setAudioState("idle");
|
|
2441
2494
|
if (objectUrlRef.current) {
|
|
2442
2495
|
URL.revokeObjectURL(objectUrlRef.current);
|
|
@@ -2512,6 +2565,108 @@ function MessageActionsBar({
|
|
|
2512
2565
|
}
|
|
2513
2566
|
);
|
|
2514
2567
|
}
|
|
2568
|
+
const KNOWN_FILE_EXTENSIONS = [
|
|
2569
|
+
"md",
|
|
2570
|
+
"txt",
|
|
2571
|
+
"json",
|
|
2572
|
+
"yaml",
|
|
2573
|
+
"yml",
|
|
2574
|
+
"toml",
|
|
2575
|
+
"py",
|
|
2576
|
+
"js",
|
|
2577
|
+
"ts",
|
|
2578
|
+
"tsx",
|
|
2579
|
+
"jsx",
|
|
2580
|
+
"css",
|
|
2581
|
+
"scss",
|
|
2582
|
+
"html",
|
|
2583
|
+
"xml",
|
|
2584
|
+
"sh",
|
|
2585
|
+
"bash",
|
|
2586
|
+
"go",
|
|
2587
|
+
"rs",
|
|
2588
|
+
"rb",
|
|
2589
|
+
"php",
|
|
2590
|
+
"java",
|
|
2591
|
+
"kt",
|
|
2592
|
+
"swift",
|
|
2593
|
+
"c",
|
|
2594
|
+
"cpp",
|
|
2595
|
+
"h",
|
|
2596
|
+
"hpp",
|
|
2597
|
+
"sql",
|
|
2598
|
+
"graphql",
|
|
2599
|
+
"dockerfile",
|
|
2600
|
+
"env",
|
|
2601
|
+
"conf",
|
|
2602
|
+
"cfg",
|
|
2603
|
+
"ini",
|
|
2604
|
+
"log",
|
|
2605
|
+
"csv"
|
|
2606
|
+
];
|
|
2607
|
+
const EXTENSION_PATTERN = KNOWN_FILE_EXTENSIONS.join("|");
|
|
2608
|
+
const ABSOLUTE_OR_HOME_PATH_PATTERN = "(?:~\\/[A-Za-z0-9._\\-\\/]*[A-Za-z0-9._\\-\\/]|\\/(?:[\\w.\\-]+\\/)*[\\w.\\-]+\\/?)";
|
|
2609
|
+
const RELATIVE_PATH_PATTERN = "(?:[A-Za-z0-9._\\-]+(?:\\/[A-Za-z0-9._\\-]+)+\\/?)";
|
|
2610
|
+
const BARE_FILENAME_PATTERN = `(?:[A-Za-z0-9_-][A-Za-z0-9._-]*[A-Za-z0-9_-]\\.(?:${EXTENSION_PATTERN})|dockerfile)`;
|
|
2611
|
+
const FILE_PATH_REGEX = new RegExp(
|
|
2612
|
+
`(^|[\\s"'(,;:])(${ABSOLUTE_OR_HOME_PATH_PATTERN}|${RELATIVE_PATH_PATTERN}|${BARE_FILENAME_PATTERN})(?=$|[\\s"'),;:!?])`,
|
|
2613
|
+
"gi"
|
|
2614
|
+
);
|
|
2615
|
+
function trimTrailingPunctuation(path) {
|
|
2616
|
+
if (!path) return { path: path ?? "", trailing: "" };
|
|
2617
|
+
const match = path.match(/^(.*?)([),.;:!?]+)?$/);
|
|
2618
|
+
if (!match) return { path, trailing: "" };
|
|
2619
|
+
return { path: match[1] || path, trailing: match[2] || "" };
|
|
2620
|
+
}
|
|
2621
|
+
function isLikelyFilePath(text) {
|
|
2622
|
+
if (!text) return false;
|
|
2623
|
+
FILE_PATH_REGEX.lastIndex = 0;
|
|
2624
|
+
const match = FILE_PATH_REGEX.exec(text);
|
|
2625
|
+
if (!match) return false;
|
|
2626
|
+
const prefix = match[1] || "";
|
|
2627
|
+
const value = match[2] || "";
|
|
2628
|
+
const matchStart = match.index + prefix.length;
|
|
2629
|
+
const matchEnd = matchStart + value.length;
|
|
2630
|
+
return matchStart === 0 && matchEnd === text.length;
|
|
2631
|
+
}
|
|
2632
|
+
function splitTextByFilePaths(text) {
|
|
2633
|
+
if (!text) return [{ type: "text", value: text }];
|
|
2634
|
+
const segments = [];
|
|
2635
|
+
let lastIndex = 0;
|
|
2636
|
+
FILE_PATH_REGEX.lastIndex = 0;
|
|
2637
|
+
let match;
|
|
2638
|
+
while ((match = FILE_PATH_REGEX.exec(text)) !== null) {
|
|
2639
|
+
const prefix = match[1] || "";
|
|
2640
|
+
const rawPath = match[2] || "";
|
|
2641
|
+
const fullMatch = match[0];
|
|
2642
|
+
const start = match.index;
|
|
2643
|
+
const prefixStart = start;
|
|
2644
|
+
const pathStart = start + prefix.length;
|
|
2645
|
+
if (prefixStart > lastIndex) {
|
|
2646
|
+
segments.push({ type: "text", value: text.slice(lastIndex, prefixStart) });
|
|
2647
|
+
}
|
|
2648
|
+
if (prefix) {
|
|
2649
|
+
segments.push({ type: "text", value: prefix });
|
|
2650
|
+
}
|
|
2651
|
+
const { path, trailing } = trimTrailingPunctuation(rawPath);
|
|
2652
|
+
if (path.length > 1) {
|
|
2653
|
+
segments.push({ type: "path", value: path });
|
|
2654
|
+
if (trailing) {
|
|
2655
|
+
segments.push({ type: "text", value: trailing });
|
|
2656
|
+
}
|
|
2657
|
+
} else {
|
|
2658
|
+
segments.push({ type: "text", value: fullMatch });
|
|
2659
|
+
}
|
|
2660
|
+
lastIndex = pathStart + rawPath.length;
|
|
2661
|
+
}
|
|
2662
|
+
if (lastIndex < text.length) {
|
|
2663
|
+
segments.push({ type: "text", value: text.slice(lastIndex) });
|
|
2664
|
+
}
|
|
2665
|
+
return segments.length > 0 ? segments : [{ type: "text", value: text }];
|
|
2666
|
+
}
|
|
2667
|
+
function filePathToMarkdownHref(path) {
|
|
2668
|
+
return `openclaw-file://${encodeURIComponent(path)}`;
|
|
2669
|
+
}
|
|
2515
2670
|
function markdownHrefToFilePath(href) {
|
|
2516
2671
|
if (!href?.startsWith("openclaw-file://")) return null;
|
|
2517
2672
|
try {
|
|
@@ -2520,6 +2675,39 @@ function markdownHrefToFilePath(href) {
|
|
|
2520
2675
|
return null;
|
|
2521
2676
|
}
|
|
2522
2677
|
}
|
|
2678
|
+
function remarkFilePathLinks() {
|
|
2679
|
+
return (tree) => {
|
|
2680
|
+
function visit(node, parent) {
|
|
2681
|
+
if (!node) return;
|
|
2682
|
+
if (node.type === "text" && parent && parent.type !== "link" && parent.type !== "inlineCode" && parent.type !== "code") {
|
|
2683
|
+
const segments = splitTextByFilePaths(String(node.value || ""));
|
|
2684
|
+
const hasPaths = segments.some((segment) => segment.type === "path");
|
|
2685
|
+
if (!hasPaths) return;
|
|
2686
|
+
const replacement = segments.map((segment) => {
|
|
2687
|
+
if (segment.type === "text") {
|
|
2688
|
+
return { type: "text", value: segment.value };
|
|
2689
|
+
}
|
|
2690
|
+
return {
|
|
2691
|
+
type: "link",
|
|
2692
|
+
url: filePathToMarkdownHref(segment.value),
|
|
2693
|
+
children: [{ type: "text", value: segment.value }]
|
|
2694
|
+
};
|
|
2695
|
+
});
|
|
2696
|
+
const index = parent.children.indexOf(node);
|
|
2697
|
+
if (index >= 0) {
|
|
2698
|
+
parent.children.splice(index, 1, ...replacement);
|
|
2699
|
+
}
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
2702
|
+
if (Array.isArray(node.children)) {
|
|
2703
|
+
for (const child of [...node.children]) {
|
|
2704
|
+
visit(child, node);
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
visit(tree, null);
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2523
2711
|
const EXTENSION_LANGUAGE_MAP = {
|
|
2524
2712
|
py: "python",
|
|
2525
2713
|
ts: "typescript",
|
|
@@ -2563,27 +2751,67 @@ function languageFromFilePath(path) {
|
|
|
2563
2751
|
return resolveLanguage(mapped);
|
|
2564
2752
|
}
|
|
2565
2753
|
const INLINE_PREVIEW_MAX_BYTES = 100 * 1024;
|
|
2754
|
+
function normalizeClickedPath(path) {
|
|
2755
|
+
if (!path) return "/";
|
|
2756
|
+
return path.includes("/") ? path : `/${path}`;
|
|
2757
|
+
}
|
|
2758
|
+
function toWorkspacePath(path) {
|
|
2759
|
+
let p = normalizeClickedPath(path);
|
|
2760
|
+
const prefixes = ["/root/clawd/", "/root/"];
|
|
2761
|
+
for (const prefix of prefixes) {
|
|
2762
|
+
if (p.startsWith(prefix)) {
|
|
2763
|
+
p = "/" + p.slice(prefix.length);
|
|
2764
|
+
break;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
return p.startsWith("/") ? p : `/${p}`;
|
|
2768
|
+
}
|
|
2769
|
+
function hasExtension(path) {
|
|
2770
|
+
const trimmed = path.replace(/\/+$/, "");
|
|
2771
|
+
const name = trimmed.split("/").pop() || "";
|
|
2772
|
+
if (!name || name.startsWith(".")) return false;
|
|
2773
|
+
return name.includes(".");
|
|
2774
|
+
}
|
|
2775
|
+
function isDirectoryPathHeuristic(path) {
|
|
2776
|
+
if (!path) return false;
|
|
2777
|
+
return path.endsWith("/") || !hasExtension(path);
|
|
2778
|
+
}
|
|
2779
|
+
function isDirectoryError(code, message) {
|
|
2780
|
+
const normalizedCode = String(code || "").toUpperCase();
|
|
2781
|
+
const normalizedMessage = String(message || "").toLowerCase();
|
|
2782
|
+
return normalizedCode.includes("DIRECTORY") || normalizedMessage.includes("is a directory") || normalizedMessage.includes("directory");
|
|
2783
|
+
}
|
|
2566
2784
|
function parseMarkdownIntoBlocks(markdown) {
|
|
2567
2785
|
const tokens = marked.lexer(markdown);
|
|
2568
2786
|
return tokens.map((token) => token.raw);
|
|
2569
2787
|
}
|
|
2570
2788
|
function extractLanguage(className) {
|
|
2571
2789
|
if (!className) return "text";
|
|
2572
|
-
const match = className.match(/language-(\w+)/);
|
|
2790
|
+
const match = className.match(/language-([\w-]+)/);
|
|
2573
2791
|
return match ? match[1] : "text";
|
|
2574
2792
|
}
|
|
2793
|
+
function extractFilenameFromMeta(meta) {
|
|
2794
|
+
const value = meta?.trim();
|
|
2795
|
+
if (!value) return void 0;
|
|
2796
|
+
const firstToken = value.split(/\s+/)[0];
|
|
2797
|
+
return firstToken || void 0;
|
|
2798
|
+
}
|
|
2575
2799
|
const BASE_COMPONENTS = {
|
|
2576
|
-
code: function CodeComponent({ className, children }) {
|
|
2800
|
+
code: function CodeComponent({ className, children, node }) {
|
|
2577
2801
|
const isInline = !className?.includes("language-");
|
|
2578
2802
|
if (isInline) {
|
|
2579
2803
|
return /* @__PURE__ */ jsx("code", { className: "rounded bg-primary-100 px-1.5 py-1 text-sm font-mono text-primary-900 border border-primary-200", children });
|
|
2580
2804
|
}
|
|
2581
2805
|
const language = extractLanguage(className);
|
|
2806
|
+
const filename = extractFilenameFromMeta(
|
|
2807
|
+
node?.data?.meta
|
|
2808
|
+
);
|
|
2582
2809
|
return /* @__PURE__ */ jsx(
|
|
2583
2810
|
CodeBlock,
|
|
2584
2811
|
{
|
|
2585
2812
|
content: String(children ?? ""),
|
|
2586
2813
|
language,
|
|
2814
|
+
filename,
|
|
2587
2815
|
className: "w-full"
|
|
2588
2816
|
}
|
|
2589
2817
|
);
|
|
@@ -2643,13 +2871,12 @@ const BASE_COMPONENTS = {
|
|
|
2643
2871
|
return /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-primary-950", children });
|
|
2644
2872
|
}
|
|
2645
2873
|
};
|
|
2646
|
-
function createDefaultComponents(onOpenFilePreview
|
|
2647
|
-
const FILE_PATH_RE = /^(?:~\/[\w.\-\/]+|\/(?:[\w.\-]+\/)+[\w.\-]+)$/;
|
|
2874
|
+
function createDefaultComponents(onOpenFilePreview) {
|
|
2648
2875
|
return {
|
|
2649
2876
|
...BASE_COMPONENTS,
|
|
2650
2877
|
a: function AComponent({ children, href }) {
|
|
2651
2878
|
const filePath = markdownHrefToFilePath(href);
|
|
2652
|
-
if (
|
|
2879
|
+
if (filePath) {
|
|
2653
2880
|
return /* @__PURE__ */ jsx(
|
|
2654
2881
|
"button",
|
|
2655
2882
|
{
|
|
@@ -2664,6 +2891,9 @@ function createDefaultComponents(onOpenFilePreview, inlineFilePreviewEnabled) {
|
|
|
2664
2891
|
"a",
|
|
2665
2892
|
{
|
|
2666
2893
|
href,
|
|
2894
|
+
onClick: (event) => {
|
|
2895
|
+
if (href?.startsWith("openclaw-file://")) event.preventDefault();
|
|
2896
|
+
},
|
|
2667
2897
|
className: "text-primary-950 underline decoration-primary-300 underline-offset-4 transition-colors hover:text-primary-950 hover:decoration-primary-500",
|
|
2668
2898
|
target: "_blank",
|
|
2669
2899
|
rel: "noopener noreferrer",
|
|
@@ -2671,10 +2901,24 @@ function createDefaultComponents(onOpenFilePreview, inlineFilePreviewEnabled) {
|
|
|
2671
2901
|
}
|
|
2672
2902
|
);
|
|
2673
2903
|
},
|
|
2674
|
-
code: function InlineCodeComponent({ children, className }) {
|
|
2675
|
-
if (className
|
|
2904
|
+
code: function InlineCodeComponent({ children, className, node }) {
|
|
2905
|
+
if (className?.includes("language-")) {
|
|
2906
|
+
const language = extractLanguage(className);
|
|
2907
|
+
const filename = extractFilenameFromMeta(
|
|
2908
|
+
node?.data?.meta
|
|
2909
|
+
);
|
|
2910
|
+
return /* @__PURE__ */ jsx(
|
|
2911
|
+
CodeBlock,
|
|
2912
|
+
{
|
|
2913
|
+
content: String(children ?? ""),
|
|
2914
|
+
language,
|
|
2915
|
+
filename,
|
|
2916
|
+
className: "w-full"
|
|
2917
|
+
}
|
|
2918
|
+
);
|
|
2919
|
+
}
|
|
2676
2920
|
const text = typeof children === "string" ? children : Array.isArray(children) ? children.filter((c) => typeof c === "string").join("") : String(children ?? "");
|
|
2677
|
-
if (
|
|
2921
|
+
if (text && isLikelyFilePath(text)) {
|
|
2678
2922
|
return /* @__PURE__ */ jsx(
|
|
2679
2923
|
"button",
|
|
2680
2924
|
{
|
|
@@ -2697,7 +2941,7 @@ const MemoizedMarkdownBlock = memo(
|
|
|
2697
2941
|
return /* @__PURE__ */ jsx(
|
|
2698
2942
|
ReactMarkdown,
|
|
2699
2943
|
{
|
|
2700
|
-
remarkPlugins: [remarkGfm, remarkBreaks],
|
|
2944
|
+
remarkPlugins: [remarkGfm, remarkBreaks, remarkFilePathLinks],
|
|
2701
2945
|
components,
|
|
2702
2946
|
children: content
|
|
2703
2947
|
}
|
|
@@ -2724,15 +2968,24 @@ function MarkdownComponent({
|
|
|
2724
2968
|
const blockId = id ?? generatedId;
|
|
2725
2969
|
const blocks = useMemo(() => parseMarkdownIntoBlocks(children), [children]);
|
|
2726
2970
|
const [filePreview, setFilePreview] = useState({ status: "idle" });
|
|
2727
|
-
const
|
|
2728
|
-
|
|
2729
|
-
|
|
2971
|
+
const navigate = useNavigate();
|
|
2972
|
+
const openDirectoryInExplorer = useCallback((path) => {
|
|
2973
|
+
const workspacePath = toWorkspacePath(path);
|
|
2974
|
+
useFileExplorerState.getState().navigateTo(workspacePath);
|
|
2975
|
+
setFilePreview({ status: "idle" });
|
|
2976
|
+
navigate({ to: "/files" });
|
|
2977
|
+
}, [navigate]);
|
|
2978
|
+
const onOpenFilePreview = useCallback((path) => {
|
|
2979
|
+
const resolvedPath = normalizeClickedPath(path);
|
|
2980
|
+
if (isDirectoryPathHeuristic(resolvedPath)) {
|
|
2981
|
+
openDirectoryInExplorer(resolvedPath);
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
setFilePreview({ status: "loading", path: resolvedPath });
|
|
2985
|
+
}, [openDirectoryInExplorer]);
|
|
2730
2986
|
const defaultComponents = useMemo(
|
|
2731
|
-
() => createDefaultComponents(
|
|
2732
|
-
|
|
2733
|
-
inlineFilePreviewEnabled
|
|
2734
|
-
),
|
|
2735
|
-
[inlineFilePreviewEnabled]
|
|
2987
|
+
() => createDefaultComponents(onOpenFilePreview),
|
|
2988
|
+
[onOpenFilePreview]
|
|
2736
2989
|
);
|
|
2737
2990
|
const mergedComponents = useMemo(
|
|
2738
2991
|
() => ({ ...defaultComponents, ...components || {} }),
|
|
@@ -2747,6 +3000,10 @@ function MarkdownComponent({
|
|
|
2747
3000
|
}).then(async (response) => {
|
|
2748
3001
|
const payload = await response.json().catch(() => ({}));
|
|
2749
3002
|
if (!response.ok) {
|
|
3003
|
+
if (isDirectoryError(payload.code, payload.message)) {
|
|
3004
|
+
openDirectoryInExplorer(path);
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
2750
3007
|
const error = fileErrorMessageFromResponse(response.status, payload.code);
|
|
2751
3008
|
setFilePreview({ status: "error", path, message: error });
|
|
2752
3009
|
return;
|
|
@@ -2768,17 +3025,33 @@ function MarkdownComponent({
|
|
|
2768
3025
|
setFilePreview({ status: "error", path, message: "Failed to load file preview" });
|
|
2769
3026
|
});
|
|
2770
3027
|
return () => controller.abort();
|
|
2771
|
-
}, [filePreview]);
|
|
3028
|
+
}, [filePreview, openDirectoryInExplorer]);
|
|
2772
3029
|
const previewOpen = filePreview.status !== "idle";
|
|
2773
3030
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2774
|
-
/* @__PURE__ */ jsx(
|
|
2775
|
-
|
|
3031
|
+
/* @__PURE__ */ jsx(
|
|
3032
|
+
"div",
|
|
2776
3033
|
{
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
3034
|
+
className: cn("flex min-w-0 max-w-full flex-col gap-2 overflow-x-hidden", className),
|
|
3035
|
+
onClickCapture: (event) => {
|
|
3036
|
+
const target = event.target;
|
|
3037
|
+
const anchor = target?.closest?.('a[href^="openclaw-file://"]');
|
|
3038
|
+
if (!anchor) return;
|
|
3039
|
+
const filePath = markdownHrefToFilePath(anchor.getAttribute("href") ?? void 0);
|
|
3040
|
+
if (!filePath) return;
|
|
3041
|
+
event.preventDefault();
|
|
3042
|
+
event.stopPropagation();
|
|
3043
|
+
onOpenFilePreview(filePath);
|
|
3044
|
+
},
|
|
3045
|
+
children: blocks.map((block, index) => /* @__PURE__ */ jsx(
|
|
3046
|
+
MemoizedMarkdownBlock,
|
|
3047
|
+
{
|
|
3048
|
+
content: block,
|
|
3049
|
+
components: mergedComponents
|
|
3050
|
+
},
|
|
3051
|
+
`${blockId}-block-${index}`
|
|
3052
|
+
))
|
|
3053
|
+
}
|
|
3054
|
+
),
|
|
2782
3055
|
/* @__PURE__ */ jsx(
|
|
2783
3056
|
DialogRoot,
|
|
2784
3057
|
{
|
|
@@ -2798,21 +3071,28 @@ function MarkdownComponent({
|
|
|
2798
3071
|
{
|
|
2799
3072
|
to: "/files",
|
|
2800
3073
|
onClick: () => {
|
|
2801
|
-
|
|
3074
|
+
const p = filePreview.status !== "idle" ? toWorkspacePath(filePreview.path) : "";
|
|
2802
3075
|
if (p) {
|
|
2803
|
-
const prefixes = ["/root/clawd/", "/root/"];
|
|
2804
|
-
for (const prefix of prefixes) {
|
|
2805
|
-
if (p.startsWith(prefix)) {
|
|
2806
|
-
p = "/" + p.slice(prefix.length);
|
|
2807
|
-
break;
|
|
2808
|
-
}
|
|
2809
|
-
}
|
|
2810
3076
|
const dir = p.includes("/") ? p.slice(0, p.lastIndexOf("/")) || "/" : "/";
|
|
2811
3077
|
useFileExplorerState.getState().navigateTo(dir);
|
|
2812
3078
|
}
|
|
2813
3079
|
setFilePreview({ status: "idle" });
|
|
2814
3080
|
},
|
|
2815
|
-
children: "Open in
|
|
3081
|
+
children: "Open in Explorer"
|
|
3082
|
+
}
|
|
3083
|
+
) }),
|
|
3084
|
+
filePreview.status !== "idle" && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: /* @__PURE__ */ jsx(
|
|
3085
|
+
Link,
|
|
3086
|
+
{
|
|
3087
|
+
to: "/files",
|
|
3088
|
+
onClick: () => {
|
|
3089
|
+
const p = filePreview.status !== "idle" ? toWorkspacePath(filePreview.path) : "";
|
|
3090
|
+
if (p) {
|
|
3091
|
+
useFileExplorerState.getState().openInEditor(p);
|
|
3092
|
+
}
|
|
3093
|
+
setFilePreview({ status: "idle" });
|
|
3094
|
+
},
|
|
3095
|
+
children: "Open in Editor"
|
|
2816
3096
|
}
|
|
2817
3097
|
) }),
|
|
2818
3098
|
/* @__PURE__ */ jsx(DialogClose, { children: "Close" })
|
|
@@ -2847,7 +3127,7 @@ function MessageContent({
|
|
|
2847
3127
|
...props
|
|
2848
3128
|
}) {
|
|
2849
3129
|
const classNames = cn(
|
|
2850
|
-
"rounded-[12px] break-words whitespace-normal min-w-0",
|
|
3130
|
+
"rounded-[12px] break-words whitespace-normal min-w-0 max-w-full overflow-x-hidden",
|
|
2851
3131
|
className
|
|
2852
3132
|
);
|
|
2853
3133
|
return markdown ? /* @__PURE__ */ jsx(Markdown, { className: classNames, ...props, children }) : /* @__PURE__ */ jsx("div", { className: classNames, ...props, children });
|
|
@@ -3214,19 +3494,19 @@ function MessageItemComponent({
|
|
|
3214
3494
|
},
|
|
3215
3495
|
idx
|
|
3216
3496
|
)) }),
|
|
3217
|
-
/* @__PURE__ */ jsx(Message, { className: cn(isUser ? "flex-row-reverse" : ""), children: /* @__PURE__ */ jsx(
|
|
3497
|
+
/* @__PURE__ */ jsx(Message, { className: cn("min-w-0 max-w-full", isUser ? "flex-row-reverse" : ""), children: /* @__PURE__ */ jsx(
|
|
3218
3498
|
MessageContent,
|
|
3219
3499
|
{
|
|
3220
3500
|
markdown: !isUser,
|
|
3221
3501
|
className: cn(
|
|
3222
|
-
"text-primary-900 opencami-text-size",
|
|
3502
|
+
"text-primary-900 opencami-text-size min-w-0 max-w-full",
|
|
3223
3503
|
!isUser ? "bg-transparent w-full" : "bg-primary-100 px-4 py-2.5 max-w-[85%]",
|
|
3224
3504
|
!isUser && isStreaming && "stream-fade-in"
|
|
3225
3505
|
),
|
|
3226
3506
|
children: text
|
|
3227
3507
|
}
|
|
3228
3508
|
) }),
|
|
3229
|
-
hasToolCalls && settings.showToolMessages && /* @__PURE__ */ jsx("div", { className: "w-full max-w-[900px]
|
|
3509
|
+
hasToolCalls && settings.showToolMessages && /* @__PURE__ */ jsx("div", { className: "mt-2 flex w-full min-w-0 max-w-[900px] flex-col gap-3 overflow-x-hidden", children: toolCalls.map((toolCall) => {
|
|
3230
3510
|
const resultMessage = toolCall.id ? toolResultsByCallId?.get(toolCall.id) : void 0;
|
|
3231
3511
|
const toolPart = mapToolCallToToolPart(toolCall, resultMessage);
|
|
3232
3512
|
return /* @__PURE__ */ jsx(
|
|
@@ -3843,12 +4123,12 @@ function ChatContainerShell({
|
|
|
3843
4123
|
/* @__PURE__ */ jsx(
|
|
3844
4124
|
ScrollAreaViewport,
|
|
3845
4125
|
{
|
|
3846
|
-
className: "relative will-change-transform",
|
|
4126
|
+
className: "relative will-change-transform overflow-x-hidden",
|
|
3847
4127
|
ref: viewportRef,
|
|
3848
4128
|
...viewportProps
|
|
3849
4129
|
}
|
|
3850
4130
|
),
|
|
3851
|
-
/* @__PURE__ */ jsx("div", { className: "relative mx-auto w-full max-w-full px-5 sm:max-w-[768px]
|
|
4131
|
+
/* @__PURE__ */ jsx("div", { className: "relative mx-auto w-full min-w-0 max-w-full px-5 sm:max-w-[768px]", children: /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute bottom-10 right-10 z-50", children: /* @__PURE__ */ jsx(ScrollButton, { scrollRef }) }) }),
|
|
3852
4132
|
/* @__PURE__ */ jsx(ScrollAreaScrollbar, { orientation: "vertical", children: /* @__PURE__ */ jsx(ScrollAreaThumb, {}) }),
|
|
3853
4133
|
/* @__PURE__ */ jsx(ScrollAreaCorner, {})
|
|
3854
4134
|
]
|
|
@@ -3886,7 +4166,7 @@ function ChatContainerPortal({
|
|
|
3886
4166
|
}) {
|
|
3887
4167
|
if (!viewportNode) return null;
|
|
3888
4168
|
return createPortal(
|
|
3889
|
-
/* @__PURE__ */ jsx("div", { className: "relative flex w-full flex-col", children }),
|
|
4169
|
+
/* @__PURE__ */ jsx("div", { className: "relative flex w-full min-w-0 max-w-full flex-col overflow-x-hidden", children }),
|
|
3890
4170
|
viewportNode
|
|
3891
4171
|
);
|
|
3892
4172
|
}
|
|
@@ -3935,9 +4215,9 @@ function ChatContainerContent({
|
|
|
3935
4215
|
return /* @__PURE__ */ jsx(
|
|
3936
4216
|
"div",
|
|
3937
4217
|
{
|
|
3938
|
-
className: cn("flex w-full flex-col min-h-full", className),
|
|
4218
|
+
className: cn("flex w-full min-w-0 max-w-full flex-col min-h-full overflow-x-hidden", className),
|
|
3939
4219
|
...props,
|
|
3940
|
-
children: /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-full px-2 md:px-5 sm:max-w-[768px]
|
|
4220
|
+
children: /* @__PURE__ */ jsx("div", { className: "mx-auto w-full min-w-0 max-w-full px-2 md:px-5 sm:max-w-[768px] flex flex-col flex-1 min-h-full overflow-x-hidden", children: /* @__PURE__ */ jsx("div", { className: "flex min-w-0 max-w-full flex-col space-y-3 md:space-y-6", children }) })
|
|
3941
4221
|
}
|
|
3942
4222
|
);
|
|
3943
4223
|
}
|
|
@@ -4448,13 +4728,17 @@ function ModelSelector({ className, onModelChange }) {
|
|
|
4448
4728
|
const [selectedModel, setSelectedModel] = useState("");
|
|
4449
4729
|
const [isLoading, setIsLoading] = useState(true);
|
|
4450
4730
|
const [error, setError] = useState(null);
|
|
4731
|
+
const abortControllerRef = useRef(null);
|
|
4451
4732
|
useEffect(() => {
|
|
4452
4733
|
let mounted = true;
|
|
4453
4734
|
async function fetchModels() {
|
|
4735
|
+
abortControllerRef.current?.abort();
|
|
4736
|
+
const controller = new AbortController();
|
|
4737
|
+
abortControllerRef.current = controller;
|
|
4454
4738
|
try {
|
|
4455
4739
|
setIsLoading(true);
|
|
4456
4740
|
setError(null);
|
|
4457
|
-
const response = await fetch("/api/models");
|
|
4741
|
+
const response = await fetch("/api/models", { signal: controller.signal });
|
|
4458
4742
|
if (!response.ok) {
|
|
4459
4743
|
throw new Error("Failed to fetch models");
|
|
4460
4744
|
}
|
|
@@ -4470,6 +4754,7 @@ function ModelSelector({ className, onModelChange }) {
|
|
|
4470
4754
|
throw new Error("No models available");
|
|
4471
4755
|
}
|
|
4472
4756
|
} catch (err) {
|
|
4757
|
+
if (err instanceof Error && err.name === "AbortError") return;
|
|
4473
4758
|
if (!mounted) return;
|
|
4474
4759
|
console.error("[model-selector] Error fetching models:", err);
|
|
4475
4760
|
setError(err instanceof Error ? err.message : "Failed to load models");
|
|
@@ -4484,6 +4769,7 @@ function ModelSelector({ className, onModelChange }) {
|
|
|
4484
4769
|
fetchModels();
|
|
4485
4770
|
return () => {
|
|
4486
4771
|
mounted = false;
|
|
4772
|
+
abortControllerRef.current?.abort();
|
|
4487
4773
|
};
|
|
4488
4774
|
}, [onModelChange]);
|
|
4489
4775
|
function handleModelSelect(modelId) {
|
|
@@ -4598,6 +4884,7 @@ function PersonaPicker({ className, onSelect }) {
|
|
|
4598
4884
|
const [isLoading, setIsLoading] = useState(true);
|
|
4599
4885
|
const [available, setAvailable] = useState(false);
|
|
4600
4886
|
const [enabled, setEnabled] = useState(isPersonasEnabled);
|
|
4887
|
+
const abortControllerRef = useRef(null);
|
|
4601
4888
|
useEffect(() => {
|
|
4602
4889
|
const handleStorage = (e) => {
|
|
4603
4890
|
if (e.key === PERSONAS_ENABLED_KEY) {
|
|
@@ -4610,9 +4897,12 @@ function PersonaPicker({ className, onSelect }) {
|
|
|
4610
4897
|
useEffect(() => {
|
|
4611
4898
|
let mounted = true;
|
|
4612
4899
|
async function fetchPersonas() {
|
|
4900
|
+
abortControllerRef.current?.abort();
|
|
4901
|
+
const controller = new AbortController();
|
|
4902
|
+
abortControllerRef.current = controller;
|
|
4613
4903
|
try {
|
|
4614
4904
|
setIsLoading(true);
|
|
4615
|
-
const response = await fetch("/api/personas");
|
|
4905
|
+
const response = await fetch("/api/personas", { signal: controller.signal });
|
|
4616
4906
|
if (!response.ok) {
|
|
4617
4907
|
setAvailable(false);
|
|
4618
4908
|
return;
|
|
@@ -4626,7 +4916,8 @@ function PersonaPicker({ className, onSelect }) {
|
|
|
4626
4916
|
} else {
|
|
4627
4917
|
setAvailable(false);
|
|
4628
4918
|
}
|
|
4629
|
-
} catch {
|
|
4919
|
+
} catch (error) {
|
|
4920
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
4630
4921
|
if (!mounted) return;
|
|
4631
4922
|
setAvailable(false);
|
|
4632
4923
|
} finally {
|
|
@@ -4638,6 +4929,7 @@ function PersonaPicker({ className, onSelect }) {
|
|
|
4638
4929
|
fetchPersonas();
|
|
4639
4930
|
return () => {
|
|
4640
4931
|
mounted = false;
|
|
4932
|
+
abortControllerRef.current?.abort();
|
|
4641
4933
|
};
|
|
4642
4934
|
}, []);
|
|
4643
4935
|
const handleSelectPersona = useCallback(
|
|
@@ -5250,6 +5542,7 @@ function ChatComposerComponent({
|
|
|
5250
5542
|
const mediaRecorderRef = useRef(null);
|
|
5251
5543
|
const recordingChunksRef = useRef([]);
|
|
5252
5544
|
const recordingTimerRef = useRef(null);
|
|
5545
|
+
const sttAbortControllerRef = useRef(null);
|
|
5253
5546
|
const webSpeechRef = useRef(null);
|
|
5254
5547
|
const promptRef = useRef(null);
|
|
5255
5548
|
const showSlashCommands = useMemo(() => /^\/\S*$/.test(value) && !slashMenuDismissed, [value, slashMenuDismissed]);
|
|
@@ -5401,6 +5694,7 @@ function ChatComposerComponent({
|
|
|
5401
5694
|
);
|
|
5402
5695
|
useEffect(() => {
|
|
5403
5696
|
return () => {
|
|
5697
|
+
sttAbortControllerRef.current?.abort();
|
|
5404
5698
|
if (recordingTimerRef.current) clearInterval(recordingTimerRef.current);
|
|
5405
5699
|
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
|
|
5406
5700
|
mediaRecorderRef.current.stop();
|
|
@@ -5497,7 +5791,10 @@ function ChatComposerComponent({
|
|
|
5497
5791
|
const formData = new FormData();
|
|
5498
5792
|
formData.append("audio", audioBlob, `recording.${mimeType === "audio/webm" ? "webm" : "mp4"}`);
|
|
5499
5793
|
if (provider !== "auto") formData.append("provider", provider);
|
|
5500
|
-
|
|
5794
|
+
sttAbortControllerRef.current?.abort();
|
|
5795
|
+
const controller = new AbortController();
|
|
5796
|
+
sttAbortControllerRef.current = controller;
|
|
5797
|
+
const res = await fetch("/api/stt", { method: "POST", body: formData, signal: controller.signal });
|
|
5501
5798
|
const data = await res.json();
|
|
5502
5799
|
if (data.ok && data.text) {
|
|
5503
5800
|
setValue((prev) => prev + (prev ? " " : "") + data.text);
|
|
@@ -5507,6 +5804,7 @@ function ChatComposerComponent({
|
|
|
5507
5804
|
alert(data.error || "Speech-to-text failed. Try the Browser provider in Settings.");
|
|
5508
5805
|
}
|
|
5509
5806
|
} catch (err) {
|
|
5807
|
+
if (err instanceof Error && err.name === "AbortError") return;
|
|
5510
5808
|
console.warn("STT request failed:", err);
|
|
5511
5809
|
alert("Could not reach speech-to-text service.");
|
|
5512
5810
|
} finally {
|
|
@@ -6236,12 +6534,12 @@ function useSwipeGesture(options) {
|
|
|
6236
6534
|
};
|
|
6237
6535
|
}
|
|
6238
6536
|
const KeyboardShortcutsDialog = lazy(
|
|
6239
|
-
() => import("./keyboard-shortcuts-dialog-
|
|
6537
|
+
() => import("./keyboard-shortcuts-dialog-CqIm8aYF.js").then((m) => ({
|
|
6240
6538
|
default: m.KeyboardShortcutsDialog
|
|
6241
6539
|
}))
|
|
6242
6540
|
);
|
|
6243
6541
|
const SearchDialog = lazy(
|
|
6244
|
-
() => import("./search-dialog-
|
|
6542
|
+
() => import("./search-dialog-DG0D9KRN.js").then((m) => ({
|
|
6245
6543
|
default: m.SearchDialog
|
|
6246
6544
|
}))
|
|
6247
6545
|
);
|
|
@@ -6916,7 +7214,7 @@ function ChatScreen({
|
|
|
6916
7214
|
{
|
|
6917
7215
|
className: cn(
|
|
6918
7216
|
"h-full overflow-hidden",
|
|
6919
|
-
isMobile ? "relative" : "grid grid-cols-[
|
|
7217
|
+
isMobile ? "relative" : "grid grid-cols-[auto_minmax(0,1fr)]"
|
|
6920
7218
|
),
|
|
6921
7219
|
children: [
|
|
6922
7220
|
hideUi ? null : isMobile ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -6946,7 +7244,7 @@ function ChatScreen({
|
|
|
6946
7244
|
/* @__PURE__ */ jsxs(
|
|
6947
7245
|
"main",
|
|
6948
7246
|
{
|
|
6949
|
-
className: "flex flex-col h-full min-h-0",
|
|
7247
|
+
className: "flex flex-col h-full min-h-0 min-w-0 overflow-x-hidden",
|
|
6950
7248
|
ref: mainRef,
|
|
6951
7249
|
...sidebarSwipeHandlers,
|
|
6952
7250
|
children: [
|