opencami 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/client/assets/{_sessionKey-CaDTbjXx.js → _sessionKey-Z6Wcnj0N.js} +26 -26
- package/dist/client/assets/{agents-CGtqdyBi.js → agents-D8ZHVQ1Z.js} +2 -2
- package/dist/client/assets/{agents-screen-DzIMcmHe.js → agents-screen-BVK0QTRH.js} +1 -1
- package/dist/client/assets/{button-BcnCTjUT.js → button-CuH8u1uR.js} +1 -1
- package/dist/client/assets/{connect-B8sfEQyb.js → connect-CtDn993i.js} +1 -1
- package/dist/client/assets/file-explorer-screen-DMUuR1uG.js +1 -0
- package/dist/client/assets/{files-BHXmS1J5.js → files-CDMLoJ0u.js} +2 -2
- package/dist/client/assets/{index-CxJ4zG_W.js → index-IGP-Igwt.js} +1 -1
- package/dist/client/assets/{index-BfhiL2JN.js → index-N9-2R5hZ.js} +1 -1
- package/dist/client/assets/{keyboard-shortcuts-dialog-DEknElTu.js → keyboard-shortcuts-dialog-DW--4YLF.js} +1 -1
- package/dist/client/assets/{main-BlX7CS2-.js → main-DrVY5UJU.js} +2 -2
- package/dist/client/assets/{opencami-logo-5KynvViW.js → opencami-logo-ChD2XR2j.js} +1 -1
- package/dist/client/assets/{react-ONC2UaeC.js → react-CzqI3gbN.js} +1 -1
- package/dist/client/assets/search-dialog-DYyFYJmw.js +1 -0
- package/dist/client/assets/{session-export-dialog-DSRWU2YO.js → session-export-dialog-BFizBmGb.js} +1 -1
- package/dist/client/assets/settings-dialog-UvgVi2It.js +1 -0
- package/dist/client/assets/{switch-dAvZcYA-.js → switch-DxW2OWPG.js} +1 -1
- package/dist/client/assets/{use-file-explorer-state-C6lX-h0n.js → use-file-explorer-state-Cii59H70.js} +1 -1
- package/dist/client/assets/{useButton-DhneLsMA.js → useButton-hZdvKtl_.js} +1 -1
- package/dist/server/assets/{_sessionKey-ZF1_Jqbt.js → _sessionKey-DVNrEYFh.js} +38 -9
- package/dist/server/assets/_tanstack-start-manifest_v-PwRq_yJS.js +4 -0
- package/dist/server/assets/{file-explorer-screen-BzvgvV8m.js → file-explorer-screen-Cx0jiLRU.js} +72 -15
- package/dist/server/assets/{files-BxvRDIWU.js → files-Trs1M5ba.js} +1 -1
- package/dist/server/assets/{index-CTTNe_Sf.js → index-Dv2RXDa2.js} +1 -1
- package/dist/server/assets/{router-D5D0udHV.js → router-OoQe2c20.js} +3 -3
- package/dist/server/assets/{search-dialog-C8Oy-FBs.js → search-dialog-Bq0Pnxdb.js} +95 -52
- package/dist/server/assets/{settings-dialog-CQFuAt9B.js → settings-dialog-BsJsnMiu.js} +11 -5
- package/dist/server/server.js +2 -2
- package/package.json +1 -1
- package/dist/client/assets/file-explorer-screen-hjggspl-.js +0 -1
- package/dist/client/assets/search-dialog-40gW31ca.js +0 -1
- package/dist/client/assets/settings-dialog-AsvYzSBc.js +0 -1
- package/dist/server/assets/_tanstack-start-manifest_v-C2FijMus.js +0 -4
|
@@ -21,7 +21,7 @@ import { r as resolveLanguage, u as useChatSettingsStore, C as CodeBlock, a as u
|
|
|
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-OoQe2c20.js";
|
|
25
25
|
function deriveFriendlyIdFromKey(key) {
|
|
26
26
|
if (!key) return "main";
|
|
27
27
|
const trimmed = key.trim();
|
|
@@ -1649,7 +1649,7 @@ function useRenameSession() {
|
|
|
1649
1649
|
return { renameSession, renaming, error };
|
|
1650
1650
|
}
|
|
1651
1651
|
const SettingsDialog = lazy(
|
|
1652
|
-
() => import("./settings-dialog-
|
|
1652
|
+
() => import("./settings-dialog-BsJsnMiu.js").then((m) => ({ default: m.SettingsDialog }))
|
|
1653
1653
|
);
|
|
1654
1654
|
const SessionExportDialog = lazy(
|
|
1655
1655
|
() => import("./session-export-dialog-DRVbC8Q-.js").then((m) => ({
|
|
@@ -2353,6 +2353,7 @@ function MessageActionsBar({
|
|
|
2353
2353
|
const [audioState, setAudioState] = useState("idle");
|
|
2354
2354
|
const audioRef = useRef(null);
|
|
2355
2355
|
const objectUrlRef = useRef(null);
|
|
2356
|
+
const ttsAbortControllerRef = useRef(null);
|
|
2356
2357
|
const [ttsEnabled, setTtsEnabled] = useState(() => {
|
|
2357
2358
|
if (typeof window === "undefined") return true;
|
|
2358
2359
|
try {
|
|
@@ -2373,6 +2374,7 @@ function MessageActionsBar({
|
|
|
2373
2374
|
}, []);
|
|
2374
2375
|
useEffect(() => {
|
|
2375
2376
|
return () => {
|
|
2377
|
+
ttsAbortControllerRef.current?.abort();
|
|
2376
2378
|
if (audioRef.current) {
|
|
2377
2379
|
audioRef.current.pause();
|
|
2378
2380
|
audioRef.current = null;
|
|
@@ -2394,6 +2396,7 @@ function MessageActionsBar({
|
|
|
2394
2396
|
};
|
|
2395
2397
|
const handleTTS = async () => {
|
|
2396
2398
|
if (audioState === "playing") {
|
|
2399
|
+
ttsAbortControllerRef.current?.abort();
|
|
2397
2400
|
if (audioRef.current) {
|
|
2398
2401
|
audioRef.current.pause();
|
|
2399
2402
|
audioRef.current = null;
|
|
@@ -2405,12 +2408,16 @@ function MessageActionsBar({
|
|
|
2405
2408
|
setAudioState("idle");
|
|
2406
2409
|
return;
|
|
2407
2410
|
}
|
|
2411
|
+
ttsAbortControllerRef.current?.abort();
|
|
2412
|
+
const controller = new AbortController();
|
|
2413
|
+
ttsAbortControllerRef.current = controller;
|
|
2408
2414
|
setAudioState("loading");
|
|
2409
2415
|
try {
|
|
2410
2416
|
const res = await fetch("/api/tts", {
|
|
2411
2417
|
method: "POST",
|
|
2412
2418
|
headers: { "Content-Type": "application/json" },
|
|
2413
|
-
body: JSON.stringify({ text })
|
|
2419
|
+
body: JSON.stringify({ text }),
|
|
2420
|
+
signal: controller.signal
|
|
2414
2421
|
});
|
|
2415
2422
|
if (!res.ok) throw new Error("TTS failed");
|
|
2416
2423
|
const blob = await res.blob();
|
|
@@ -2436,7 +2443,11 @@ function MessageActionsBar({
|
|
|
2436
2443
|
};
|
|
2437
2444
|
await audio.play();
|
|
2438
2445
|
setAudioState("playing");
|
|
2439
|
-
} catch {
|
|
2446
|
+
} catch (error) {
|
|
2447
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2448
|
+
setAudioState("idle");
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2440
2451
|
setAudioState("idle");
|
|
2441
2452
|
if (objectUrlRef.current) {
|
|
2442
2453
|
URL.revokeObjectURL(objectUrlRef.current);
|
|
@@ -4448,13 +4459,17 @@ function ModelSelector({ className, onModelChange }) {
|
|
|
4448
4459
|
const [selectedModel, setSelectedModel] = useState("");
|
|
4449
4460
|
const [isLoading, setIsLoading] = useState(true);
|
|
4450
4461
|
const [error, setError] = useState(null);
|
|
4462
|
+
const abortControllerRef = useRef(null);
|
|
4451
4463
|
useEffect(() => {
|
|
4452
4464
|
let mounted = true;
|
|
4453
4465
|
async function fetchModels() {
|
|
4466
|
+
abortControllerRef.current?.abort();
|
|
4467
|
+
const controller = new AbortController();
|
|
4468
|
+
abortControllerRef.current = controller;
|
|
4454
4469
|
try {
|
|
4455
4470
|
setIsLoading(true);
|
|
4456
4471
|
setError(null);
|
|
4457
|
-
const response = await fetch("/api/models");
|
|
4472
|
+
const response = await fetch("/api/models", { signal: controller.signal });
|
|
4458
4473
|
if (!response.ok) {
|
|
4459
4474
|
throw new Error("Failed to fetch models");
|
|
4460
4475
|
}
|
|
@@ -4470,6 +4485,7 @@ function ModelSelector({ className, onModelChange }) {
|
|
|
4470
4485
|
throw new Error("No models available");
|
|
4471
4486
|
}
|
|
4472
4487
|
} catch (err) {
|
|
4488
|
+
if (err instanceof Error && err.name === "AbortError") return;
|
|
4473
4489
|
if (!mounted) return;
|
|
4474
4490
|
console.error("[model-selector] Error fetching models:", err);
|
|
4475
4491
|
setError(err instanceof Error ? err.message : "Failed to load models");
|
|
@@ -4484,6 +4500,7 @@ function ModelSelector({ className, onModelChange }) {
|
|
|
4484
4500
|
fetchModels();
|
|
4485
4501
|
return () => {
|
|
4486
4502
|
mounted = false;
|
|
4503
|
+
abortControllerRef.current?.abort();
|
|
4487
4504
|
};
|
|
4488
4505
|
}, [onModelChange]);
|
|
4489
4506
|
function handleModelSelect(modelId) {
|
|
@@ -4598,6 +4615,7 @@ function PersonaPicker({ className, onSelect }) {
|
|
|
4598
4615
|
const [isLoading, setIsLoading] = useState(true);
|
|
4599
4616
|
const [available, setAvailable] = useState(false);
|
|
4600
4617
|
const [enabled, setEnabled] = useState(isPersonasEnabled);
|
|
4618
|
+
const abortControllerRef = useRef(null);
|
|
4601
4619
|
useEffect(() => {
|
|
4602
4620
|
const handleStorage = (e) => {
|
|
4603
4621
|
if (e.key === PERSONAS_ENABLED_KEY) {
|
|
@@ -4610,9 +4628,12 @@ function PersonaPicker({ className, onSelect }) {
|
|
|
4610
4628
|
useEffect(() => {
|
|
4611
4629
|
let mounted = true;
|
|
4612
4630
|
async function fetchPersonas() {
|
|
4631
|
+
abortControllerRef.current?.abort();
|
|
4632
|
+
const controller = new AbortController();
|
|
4633
|
+
abortControllerRef.current = controller;
|
|
4613
4634
|
try {
|
|
4614
4635
|
setIsLoading(true);
|
|
4615
|
-
const response = await fetch("/api/personas");
|
|
4636
|
+
const response = await fetch("/api/personas", { signal: controller.signal });
|
|
4616
4637
|
if (!response.ok) {
|
|
4617
4638
|
setAvailable(false);
|
|
4618
4639
|
return;
|
|
@@ -4626,7 +4647,8 @@ function PersonaPicker({ className, onSelect }) {
|
|
|
4626
4647
|
} else {
|
|
4627
4648
|
setAvailable(false);
|
|
4628
4649
|
}
|
|
4629
|
-
} catch {
|
|
4650
|
+
} catch (error) {
|
|
4651
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
4630
4652
|
if (!mounted) return;
|
|
4631
4653
|
setAvailable(false);
|
|
4632
4654
|
} finally {
|
|
@@ -4638,6 +4660,7 @@ function PersonaPicker({ className, onSelect }) {
|
|
|
4638
4660
|
fetchPersonas();
|
|
4639
4661
|
return () => {
|
|
4640
4662
|
mounted = false;
|
|
4663
|
+
abortControllerRef.current?.abort();
|
|
4641
4664
|
};
|
|
4642
4665
|
}, []);
|
|
4643
4666
|
const handleSelectPersona = useCallback(
|
|
@@ -5250,6 +5273,7 @@ function ChatComposerComponent({
|
|
|
5250
5273
|
const mediaRecorderRef = useRef(null);
|
|
5251
5274
|
const recordingChunksRef = useRef([]);
|
|
5252
5275
|
const recordingTimerRef = useRef(null);
|
|
5276
|
+
const sttAbortControllerRef = useRef(null);
|
|
5253
5277
|
const webSpeechRef = useRef(null);
|
|
5254
5278
|
const promptRef = useRef(null);
|
|
5255
5279
|
const showSlashCommands = useMemo(() => /^\/\S*$/.test(value) && !slashMenuDismissed, [value, slashMenuDismissed]);
|
|
@@ -5401,6 +5425,7 @@ function ChatComposerComponent({
|
|
|
5401
5425
|
);
|
|
5402
5426
|
useEffect(() => {
|
|
5403
5427
|
return () => {
|
|
5428
|
+
sttAbortControllerRef.current?.abort();
|
|
5404
5429
|
if (recordingTimerRef.current) clearInterval(recordingTimerRef.current);
|
|
5405
5430
|
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
|
|
5406
5431
|
mediaRecorderRef.current.stop();
|
|
@@ -5497,7 +5522,10 @@ function ChatComposerComponent({
|
|
|
5497
5522
|
const formData = new FormData();
|
|
5498
5523
|
formData.append("audio", audioBlob, `recording.${mimeType === "audio/webm" ? "webm" : "mp4"}`);
|
|
5499
5524
|
if (provider !== "auto") formData.append("provider", provider);
|
|
5500
|
-
|
|
5525
|
+
sttAbortControllerRef.current?.abort();
|
|
5526
|
+
const controller = new AbortController();
|
|
5527
|
+
sttAbortControllerRef.current = controller;
|
|
5528
|
+
const res = await fetch("/api/stt", { method: "POST", body: formData, signal: controller.signal });
|
|
5501
5529
|
const data = await res.json();
|
|
5502
5530
|
if (data.ok && data.text) {
|
|
5503
5531
|
setValue((prev) => prev + (prev ? " " : "") + data.text);
|
|
@@ -5507,6 +5535,7 @@ function ChatComposerComponent({
|
|
|
5507
5535
|
alert(data.error || "Speech-to-text failed. Try the Browser provider in Settings.");
|
|
5508
5536
|
}
|
|
5509
5537
|
} catch (err) {
|
|
5538
|
+
if (err instanceof Error && err.name === "AbortError") return;
|
|
5510
5539
|
console.warn("STT request failed:", err);
|
|
5511
5540
|
alert("Could not reach speech-to-text service.");
|
|
5512
5541
|
} finally {
|
|
@@ -6241,7 +6270,7 @@ const KeyboardShortcutsDialog = lazy(
|
|
|
6241
6270
|
}))
|
|
6242
6271
|
);
|
|
6243
6272
|
const SearchDialog = lazy(
|
|
6244
|
-
() => import("./search-dialog-
|
|
6273
|
+
() => import("./search-dialog-Bq0Pnxdb.js").then((m) => ({
|
|
6245
6274
|
default: m.SearchDialog
|
|
6246
6275
|
}))
|
|
6247
6276
|
);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "/home/runner/work/opencami/opencami/src/routes/__root.tsx", "children": ["/", "/agents", "/connect", "/files", "/new", "/api/agents", "/api/follow-ups", "/api/history", "/api/llm-features", "/api/models", "/api/paths", "/api/personas", "/api/ping", "/api/send", "/api/sessions", "/api/stream", "/api/stt", "/api/tts", "/chat/$sessionKey", "/api/files/delete", "/api/files/download", "/api/files/info", "/api/files/list", "/api/files/mkdir", "/api/files/read", "/api/files/rename", "/api/files/save", "/api/files/upload"], "preloads": ["/assets/main-DrVY5UJU.js"], "assets": [] }, "/": { "filePath": "/home/runner/work/opencami/opencami/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-N9-2R5hZ.js"] }, "/agents": { "filePath": "/home/runner/work/opencami/opencami/src/routes/agents.tsx", "assets": [], "preloads": ["/assets/agents-D8ZHVQ1Z.js"] }, "/connect": { "filePath": "/home/runner/work/opencami/opencami/src/routes/connect.tsx", "assets": [], "preloads": ["/assets/connect-CtDn993i.js", "/assets/index-IGP-Igwt.js", "/assets/button-CuH8u1uR.js", "/assets/react-CzqI3gbN.js"] }, "/files": { "filePath": "/home/runner/work/opencami/opencami/src/routes/files.tsx", "assets": [], "preloads": ["/assets/files-CDMLoJ0u.js"] }, "/new": { "filePath": "/home/runner/work/opencami/opencami/src/routes/new.tsx", "assets": [], "preloads": ["/assets/new-DLlBm66g.js"] }, "/api/agents": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/agents.ts" }, "/api/follow-ups": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/follow-ups.ts" }, "/api/history": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/history.ts" }, "/api/llm-features": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/llm-features.ts" }, "/api/models": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/models.ts" }, "/api/paths": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/paths.ts" }, "/api/personas": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/personas.ts" }, "/api/ping": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/ping.ts" }, "/api/send": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/send.ts" }, "/api/sessions": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/sessions.ts" }, "/api/stream": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/stream.ts" }, "/api/stt": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/stt.ts" }, "/api/tts": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/tts.ts" }, "/chat/$sessionKey": { "filePath": "/home/runner/work/opencami/opencami/src/routes/chat/$sessionKey.tsx", "assets": [], "preloads": ["/assets/_sessionKey-Z6Wcnj0N.js", "/assets/useButton-hZdvKtl_.js", "/assets/button-CuH8u1uR.js", "/assets/use-file-explorer-state-Cii59H70.js", "/assets/opencami-logo-ChD2XR2j.js", "/assets/index-IGP-Igwt.js", "/assets/react-CzqI3gbN.js"] }, "/api/files/delete": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/delete.ts" }, "/api/files/download": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/download.ts" }, "/api/files/info": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/info.ts" }, "/api/files/list": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/list.ts" }, "/api/files/mkdir": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/mkdir.ts" }, "/api/files/read": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/read.ts" }, "/api/files/rename": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/rename.ts" }, "/api/files/save": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/save.ts" }, "/api/files/upload": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/upload.ts" } }, "clientEntry": "/assets/main-DrVY5UJU.js" });
|
|
2
|
+
export {
|
|
3
|
+
tsrStartManifest
|
|
4
|
+
};
|
package/dist/server/assets/{file-explorer-screen-BzvgvV8m.js → file-explorer-screen-Cx0jiLRU.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { memo, useMemo, useCallback, useEffect, useState
|
|
2
|
+
import { memo, useMemo, useCallback, useRef, useEffect, useState } from "react";
|
|
3
3
|
import { useQuery, useQueryClient, useMutation } from "@tanstack/react-query";
|
|
4
4
|
import { HugeiconsIcon } from "@hugeicons/react";
|
|
5
5
|
import { Home09Icon, SidebarLeft01Icon, ArrowLeft01Icon, Settings01Icon, ArrowRight01Icon, Home02Icon, Menu01Icon, GridViewIcon, ArrowUp01Icon, ArrowDown01Icon, Upload04Icon, FolderAddIcon, FolderOpenIcon, Download04Icon, FileEditIcon, Edit02Icon, Delete02Icon, Folder01Icon, Image01Icon, Video01Icon, MusicNote01Icon, Archive01Icon, Doc01Icon, CodeIcon, File01Icon, FloppyDiskIcon, Cancel01Icon } from "@hugeicons/core-free-icons";
|
|
@@ -653,8 +653,8 @@ const fileQueryKeys = {
|
|
|
653
653
|
function useFileContent(path) {
|
|
654
654
|
return useQuery({
|
|
655
655
|
queryKey: fileQueryKeys.content(path || ""),
|
|
656
|
-
queryFn: async () => {
|
|
657
|
-
const response = await fetch(`/api/files/read?path=${encodeURIComponent(path)}
|
|
656
|
+
queryFn: async ({ signal }) => {
|
|
657
|
+
const response = await fetch(`/api/files/read?path=${encodeURIComponent(path)}`, { signal });
|
|
658
658
|
if (!response.ok) {
|
|
659
659
|
const error = await response.json().catch(() => ({ error: "Failed to read file" }));
|
|
660
660
|
throw new Error(error.error || "Failed to read file");
|
|
@@ -669,12 +669,22 @@ function useFileContent(path) {
|
|
|
669
669
|
}
|
|
670
670
|
function useFileSave() {
|
|
671
671
|
const queryClient = useQueryClient();
|
|
672
|
+
const abortControllerRef = useRef(null);
|
|
673
|
+
useEffect(() => {
|
|
674
|
+
return () => {
|
|
675
|
+
abortControllerRef.current?.abort();
|
|
676
|
+
};
|
|
677
|
+
}, []);
|
|
672
678
|
return useMutation({
|
|
673
679
|
mutationFn: async ({ path, content }) => {
|
|
680
|
+
abortControllerRef.current?.abort();
|
|
681
|
+
const controller = new AbortController();
|
|
682
|
+
abortControllerRef.current = controller;
|
|
674
683
|
const response = await fetch("/api/files/save", {
|
|
675
684
|
method: "POST",
|
|
676
685
|
headers: { "Content-Type": "application/json" },
|
|
677
|
-
body: JSON.stringify({ path, content })
|
|
686
|
+
body: JSON.stringify({ path, content }),
|
|
687
|
+
signal: controller.signal
|
|
678
688
|
});
|
|
679
689
|
if (!response.ok) {
|
|
680
690
|
const error = await response.json().catch(() => ({ error: "Failed to save file" }));
|
|
@@ -696,8 +706,8 @@ function useFileSave() {
|
|
|
696
706
|
function useFileListing(path) {
|
|
697
707
|
return useQuery({
|
|
698
708
|
queryKey: fileQueryKeys.listing(path),
|
|
699
|
-
queryFn: async () => {
|
|
700
|
-
const response = await fetch(`/api/files/list?path=${encodeURIComponent(path)}
|
|
709
|
+
queryFn: async ({ signal }) => {
|
|
710
|
+
const response = await fetch(`/api/files/list?path=${encodeURIComponent(path)}`, { signal });
|
|
701
711
|
if (!response.ok) {
|
|
702
712
|
const error = await response.text();
|
|
703
713
|
throw new Error(`Failed to load files: ${error}`);
|
|
@@ -708,23 +718,25 @@ function useFileListing(path) {
|
|
|
708
718
|
// 5 minutes
|
|
709
719
|
gcTime: 10 * 60 * 1e3,
|
|
710
720
|
// 10 minutes
|
|
711
|
-
retry: 3
|
|
712
|
-
onError: (error) => {
|
|
713
|
-
console.error("Failed to load file listing:", error);
|
|
714
|
-
}
|
|
721
|
+
retry: 3
|
|
715
722
|
});
|
|
716
723
|
}
|
|
717
724
|
function useFileDownload() {
|
|
718
725
|
const urls = /* @__PURE__ */ new Set();
|
|
726
|
+
const abortControllerRef = useRef(null);
|
|
719
727
|
useEffect(() => {
|
|
720
728
|
return () => {
|
|
729
|
+
abortControllerRef.current?.abort();
|
|
721
730
|
urls.forEach((url) => URL.revokeObjectURL(url));
|
|
722
731
|
urls.clear();
|
|
723
732
|
};
|
|
724
733
|
}, []);
|
|
725
734
|
return useMutation({
|
|
726
735
|
mutationFn: async (filePath) => {
|
|
727
|
-
|
|
736
|
+
abortControllerRef.current?.abort();
|
|
737
|
+
const controller = new AbortController();
|
|
738
|
+
abortControllerRef.current = controller;
|
|
739
|
+
const response = await fetch(`/api/files/download?path=${encodeURIComponent(filePath)}`, { signal: controller.signal });
|
|
728
740
|
if (!response.ok) {
|
|
729
741
|
const error = await response.text();
|
|
730
742
|
throw new Error(`Download failed: ${error}`);
|
|
@@ -747,14 +759,24 @@ function useFileDownload() {
|
|
|
747
759
|
}, 100);
|
|
748
760
|
},
|
|
749
761
|
onError: (error) => {
|
|
762
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
750
763
|
console.error("Download failed:", error);
|
|
751
764
|
}
|
|
752
765
|
});
|
|
753
766
|
}
|
|
754
767
|
function useFileUpload() {
|
|
755
768
|
const queryClient = useQueryClient();
|
|
769
|
+
const abortControllerRef = useRef(null);
|
|
770
|
+
useEffect(() => {
|
|
771
|
+
return () => {
|
|
772
|
+
abortControllerRef.current?.abort();
|
|
773
|
+
};
|
|
774
|
+
}, []);
|
|
756
775
|
return useMutation({
|
|
757
776
|
mutationFn: async ({ files, path }) => {
|
|
777
|
+
abortControllerRef.current?.abort();
|
|
778
|
+
const controller = new AbortController();
|
|
779
|
+
abortControllerRef.current = controller;
|
|
758
780
|
const results = [];
|
|
759
781
|
for (let i = 0; i < files.length; i++) {
|
|
760
782
|
const formData = new FormData();
|
|
@@ -762,7 +784,8 @@ function useFileUpload() {
|
|
|
762
784
|
formData.append("file", files[i]);
|
|
763
785
|
const response = await fetch("/api/files/upload", {
|
|
764
786
|
method: "POST",
|
|
765
|
-
body: formData
|
|
787
|
+
body: formData,
|
|
788
|
+
signal: controller.signal
|
|
766
789
|
});
|
|
767
790
|
if (!response.ok) {
|
|
768
791
|
const error = await response.text();
|
|
@@ -778,16 +801,27 @@ function useFileUpload() {
|
|
|
778
801
|
});
|
|
779
802
|
},
|
|
780
803
|
onError: (error) => {
|
|
804
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
781
805
|
console.error("Upload failed:", error);
|
|
782
806
|
}
|
|
783
807
|
});
|
|
784
808
|
}
|
|
785
809
|
function useFileDelete() {
|
|
786
810
|
const queryClient = useQueryClient();
|
|
811
|
+
const abortControllerRef = useRef(null);
|
|
812
|
+
useEffect(() => {
|
|
813
|
+
return () => {
|
|
814
|
+
abortControllerRef.current?.abort();
|
|
815
|
+
};
|
|
816
|
+
}, []);
|
|
787
817
|
return useMutation({
|
|
788
818
|
mutationFn: async (filePath) => {
|
|
819
|
+
abortControllerRef.current?.abort();
|
|
820
|
+
const controller = new AbortController();
|
|
821
|
+
abortControllerRef.current = controller;
|
|
789
822
|
const response = await fetch(`/api/files/delete?path=${encodeURIComponent(filePath)}`, {
|
|
790
|
-
method: "DELETE"
|
|
823
|
+
method: "DELETE",
|
|
824
|
+
signal: controller.signal
|
|
791
825
|
});
|
|
792
826
|
if (!response.ok) {
|
|
793
827
|
const error = await response.text();
|
|
@@ -802,18 +836,29 @@ function useFileDelete() {
|
|
|
802
836
|
});
|
|
803
837
|
},
|
|
804
838
|
onError: (error) => {
|
|
839
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
805
840
|
console.error("Delete failed:", error);
|
|
806
841
|
}
|
|
807
842
|
});
|
|
808
843
|
}
|
|
809
844
|
function useCreateFolder() {
|
|
810
845
|
const queryClient = useQueryClient();
|
|
846
|
+
const abortControllerRef = useRef(null);
|
|
847
|
+
useEffect(() => {
|
|
848
|
+
return () => {
|
|
849
|
+
abortControllerRef.current?.abort();
|
|
850
|
+
};
|
|
851
|
+
}, []);
|
|
811
852
|
return useMutation({
|
|
812
853
|
mutationFn: async ({ path, name }) => {
|
|
854
|
+
abortControllerRef.current?.abort();
|
|
855
|
+
const controller = new AbortController();
|
|
856
|
+
abortControllerRef.current = controller;
|
|
813
857
|
const response = await fetch("/api/files/mkdir", {
|
|
814
858
|
method: "POST",
|
|
815
859
|
headers: { "Content-Type": "application/json" },
|
|
816
|
-
body: JSON.stringify({ path: `${path}/${name}` })
|
|
860
|
+
body: JSON.stringify({ path: `${path}/${name}` }),
|
|
861
|
+
signal: controller.signal
|
|
817
862
|
});
|
|
818
863
|
if (!response.ok) {
|
|
819
864
|
const error = await response.text();
|
|
@@ -827,20 +872,31 @@ function useCreateFolder() {
|
|
|
827
872
|
});
|
|
828
873
|
},
|
|
829
874
|
onError: (error) => {
|
|
875
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
830
876
|
console.error("Create folder failed:", error);
|
|
831
877
|
}
|
|
832
878
|
});
|
|
833
879
|
}
|
|
834
880
|
function useFileRename() {
|
|
835
881
|
const queryClient = useQueryClient();
|
|
882
|
+
const abortControllerRef = useRef(null);
|
|
883
|
+
useEffect(() => {
|
|
884
|
+
return () => {
|
|
885
|
+
abortControllerRef.current?.abort();
|
|
886
|
+
};
|
|
887
|
+
}, []);
|
|
836
888
|
return useMutation({
|
|
837
889
|
mutationFn: async ({ oldPath, newName }) => {
|
|
890
|
+
abortControllerRef.current?.abort();
|
|
891
|
+
const controller = new AbortController();
|
|
892
|
+
abortControllerRef.current = controller;
|
|
838
893
|
const parentPath = oldPath.split("/").slice(0, -1).join("/");
|
|
839
894
|
const newPath = `${parentPath}/${newName}`;
|
|
840
895
|
const response = await fetch("/api/files/rename", {
|
|
841
896
|
method: "POST",
|
|
842
897
|
headers: { "Content-Type": "application/json" },
|
|
843
|
-
body: JSON.stringify({ src: oldPath, dst: newPath })
|
|
898
|
+
body: JSON.stringify({ src: oldPath, dst: newPath }),
|
|
899
|
+
signal: controller.signal
|
|
844
900
|
});
|
|
845
901
|
if (!response.ok) {
|
|
846
902
|
const error = await response.text();
|
|
@@ -855,6 +911,7 @@ function useFileRename() {
|
|
|
855
911
|
});
|
|
856
912
|
},
|
|
857
913
|
onError: (error) => {
|
|
914
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
858
915
|
console.error("Rename failed:", error);
|
|
859
916
|
}
|
|
860
917
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Suspense, lazy } from "react";
|
|
3
|
-
const FileExplorerScreen = lazy(() => import("./file-explorer-screen-
|
|
3
|
+
const FileExplorerScreen = lazy(() => import("./file-explorer-screen-Cx0jiLRU.js").then((m) => ({
|
|
4
4
|
default: m.FileExplorerScreen
|
|
5
5
|
})));
|
|
6
6
|
function FilesRoute() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect } from "react";
|
|
3
|
-
import { R as Route } from "./router-
|
|
3
|
+
import { R as Route } from "./router-OoQe2c20.js";
|
|
4
4
|
import "@tanstack/react-router";
|
|
5
5
|
import "@tanstack/react-query";
|
|
6
6
|
import "node:crypto";
|
|
@@ -196,7 +196,7 @@ const Route$r = createFileRoute("/new")({
|
|
|
196
196
|
},
|
|
197
197
|
component: lazyRouteComponent($$splitComponentImporter$5, "component")
|
|
198
198
|
});
|
|
199
|
-
const $$splitComponentImporter$4 = () => import("./files-
|
|
199
|
+
const $$splitComponentImporter$4 = () => import("./files-Trs1M5ba.js");
|
|
200
200
|
const Route$q = createFileRoute("/files")({
|
|
201
201
|
component: lazyRouteComponent($$splitComponentImporter$4, "component")
|
|
202
202
|
});
|
|
@@ -208,11 +208,11 @@ const $$splitComponentImporter$2 = () => import("./agents-Dz_i76VW.js");
|
|
|
208
208
|
const Route$o = createFileRoute("/agents")({
|
|
209
209
|
component: lazyRouteComponent($$splitComponentImporter$2, "component")
|
|
210
210
|
});
|
|
211
|
-
const $$splitComponentImporter$1 = () => import("./index-
|
|
211
|
+
const $$splitComponentImporter$1 = () => import("./index-Dv2RXDa2.js");
|
|
212
212
|
const Route$n = createFileRoute("/")({
|
|
213
213
|
component: lazyRouteComponent($$splitComponentImporter$1, "component")
|
|
214
214
|
});
|
|
215
|
-
const $$splitComponentImporter = () => import("./_sessionKey-
|
|
215
|
+
const $$splitComponentImporter = () => import("./_sessionKey-DVNrEYFh.js").then((n) => n.$);
|
|
216
216
|
const Route$m = createFileRoute("/chat/$sessionKey")({
|
|
217
217
|
component: lazyRouteComponent($$splitComponentImporter, "component")
|
|
218
218
|
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState,
|
|
2
|
+
import { useState, useRef, useEffect, useCallback, useMemo } from "react";
|
|
3
3
|
import { useNavigate } from "@tanstack/react-router";
|
|
4
4
|
import { HugeiconsIcon } from "@hugeicons/react";
|
|
5
5
|
import { Search01Icon, Cancel01Icon, Loading03Icon } from "@hugeicons/core-free-icons";
|
|
6
6
|
import { D as DialogRoot, a as DialogContent } from "./use-file-explorer-state-DMHdtb7D.js";
|
|
7
7
|
import { useQueryClient } from "@tanstack/react-query";
|
|
8
|
-
import { c as chatQueryKeys } from "./_sessionKey-
|
|
8
|
+
import { c as chatQueryKeys } from "./_sessionKey-DVNrEYFh.js";
|
|
9
9
|
import { c as cn } from "./button-DtQ3rV1m.js";
|
|
10
10
|
import "@base-ui/react/dialog";
|
|
11
11
|
import "@base-ui/react/menu";
|
|
@@ -57,7 +57,7 @@ import "@shikijs/langs/xml";
|
|
|
57
57
|
import "@shikijs/langs/yaml";
|
|
58
58
|
import "zustand/middleware";
|
|
59
59
|
import "react-dom";
|
|
60
|
-
import "./router-
|
|
60
|
+
import "./router-OoQe2c20.js";
|
|
61
61
|
import "node:crypto";
|
|
62
62
|
import "ws";
|
|
63
63
|
import "node:fs";
|
|
@@ -93,6 +93,13 @@ function useSearch({ sessions, currentFriendlyId, currentSessionKey }) {
|
|
|
93
93
|
const [query, setQuery] = useState("");
|
|
94
94
|
const [isSearching, setIsSearching] = useState(false);
|
|
95
95
|
const [globalResults, setGlobalResults] = useState([]);
|
|
96
|
+
const globalSearchControllerRef = useRef(null);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
return () => {
|
|
99
|
+
globalSearchControllerRef.current?.abort();
|
|
100
|
+
globalSearchControllerRef.current = null;
|
|
101
|
+
};
|
|
102
|
+
}, []);
|
|
96
103
|
const searchCurrentConversation = useCallback(
|
|
97
104
|
(searchQuery) => {
|
|
98
105
|
if (!searchQuery.trim() || !currentFriendlyId || !currentSessionKey) {
|
|
@@ -130,66 +137,100 @@ function useSearch({ sessions, currentFriendlyId, currentSessionKey }) {
|
|
|
130
137
|
);
|
|
131
138
|
const searchAllSessions = useCallback(
|
|
132
139
|
async (searchQuery) => {
|
|
133
|
-
|
|
140
|
+
const trimmedQuery = searchQuery.trim();
|
|
141
|
+
if (!trimmedQuery) {
|
|
142
|
+
globalSearchControllerRef.current?.abort();
|
|
143
|
+
globalSearchControllerRef.current = null;
|
|
134
144
|
setGlobalResults([]);
|
|
135
145
|
return [];
|
|
136
146
|
}
|
|
147
|
+
globalSearchControllerRef.current?.abort();
|
|
148
|
+
const controller = new AbortController();
|
|
149
|
+
globalSearchControllerRef.current = controller;
|
|
137
150
|
setIsSearching(true);
|
|
138
|
-
|
|
151
|
+
setGlobalResults([]);
|
|
152
|
+
const normalizedQuery = trimmedQuery.toLowerCase();
|
|
139
153
|
const allResults = [];
|
|
154
|
+
const BATCH_SIZE = 10;
|
|
140
155
|
try {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (res.ok) {
|
|
154
|
-
historyData = await res.json();
|
|
155
|
-
queryClient.setQueryData(historyKey, historyData);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
if (!historyData?.messages) return;
|
|
159
|
-
const sessionTitle = session.label || session.title || session.derivedTitle || session.friendlyId;
|
|
160
|
-
historyData.messages.forEach((message, index) => {
|
|
161
|
-
const text = extractTextFromMessage(message);
|
|
162
|
-
if (!text) return;
|
|
163
|
-
const lowerText = text.toLowerCase();
|
|
164
|
-
const matchIndex = lowerText.indexOf(normalizedQuery);
|
|
165
|
-
if (matchIndex !== -1) {
|
|
166
|
-
allResults.push({
|
|
156
|
+
for (let i = 0; i < sessions.length; i += BATCH_SIZE) {
|
|
157
|
+
if (controller.signal.aborted) {
|
|
158
|
+
throw new DOMException("Search aborted", "AbortError");
|
|
159
|
+
}
|
|
160
|
+
const batch = sessions.slice(i, i + BATCH_SIZE);
|
|
161
|
+
const batchResults = await Promise.all(
|
|
162
|
+
batch.map(async (session) => {
|
|
163
|
+
try {
|
|
164
|
+
const historyKey = chatQueryKeys.history(session.friendlyId, session.key);
|
|
165
|
+
let historyData = queryClient.getQueryData(historyKey);
|
|
166
|
+
if (!historyData) {
|
|
167
|
+
const params = new URLSearchParams({
|
|
167
168
|
sessionKey: session.key,
|
|
168
169
|
friendlyId: session.friendlyId,
|
|
169
|
-
|
|
170
|
-
messageIndex: index,
|
|
171
|
-
messageRole: message.role || "unknown",
|
|
172
|
-
messageText: text,
|
|
173
|
-
matchStart: matchIndex,
|
|
174
|
-
matchEnd: matchIndex + searchQuery.length,
|
|
175
|
-
timestamp: message.timestamp
|
|
170
|
+
limit: "200"
|
|
176
171
|
});
|
|
172
|
+
const res = await fetch(`/api/history?${params.toString()}`, {
|
|
173
|
+
signal: controller.signal
|
|
174
|
+
});
|
|
175
|
+
if (res.ok) {
|
|
176
|
+
historyData = await res.json();
|
|
177
|
+
queryClient.setQueryData(historyKey, historyData);
|
|
178
|
+
}
|
|
177
179
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
180
|
+
if (!historyData?.messages) return [];
|
|
181
|
+
const sessionTitle = session.label || session.title || session.derivedTitle || session.friendlyId;
|
|
182
|
+
const sessionResults = [];
|
|
183
|
+
historyData.messages.forEach((message, index) => {
|
|
184
|
+
const text = extractTextFromMessage(message);
|
|
185
|
+
if (!text) return;
|
|
186
|
+
const lowerText = text.toLowerCase();
|
|
187
|
+
const matchIndex = lowerText.indexOf(normalizedQuery);
|
|
188
|
+
if (matchIndex !== -1) {
|
|
189
|
+
sessionResults.push({
|
|
190
|
+
sessionKey: session.key,
|
|
191
|
+
friendlyId: session.friendlyId,
|
|
192
|
+
sessionTitle,
|
|
193
|
+
messageIndex: index,
|
|
194
|
+
messageRole: message.role || "unknown",
|
|
195
|
+
messageText: text,
|
|
196
|
+
matchStart: matchIndex,
|
|
197
|
+
matchEnd: matchIndex + trimmedQuery.length,
|
|
198
|
+
timestamp: message.timestamp
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
return sessionResults;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
allResults.push(...batchResults.flat());
|
|
212
|
+
allResults.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
|
|
213
|
+
setGlobalResults([...allResults]);
|
|
214
|
+
}
|
|
185
215
|
return allResults;
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (!(error instanceof DOMException && error.name === "AbortError")) {
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
return [];
|
|
186
221
|
} finally {
|
|
187
|
-
|
|
222
|
+
if (globalSearchControllerRef.current === controller) {
|
|
223
|
+
globalSearchControllerRef.current = null;
|
|
224
|
+
setIsSearching(false);
|
|
225
|
+
}
|
|
188
226
|
}
|
|
189
227
|
},
|
|
190
228
|
[sessions, queryClient]
|
|
191
229
|
);
|
|
192
230
|
const clearSearch = useCallback(() => {
|
|
231
|
+
globalSearchControllerRef.current?.abort();
|
|
232
|
+
globalSearchControllerRef.current = null;
|
|
233
|
+
setIsSearching(false);
|
|
193
234
|
setQuery("");
|
|
194
235
|
setGlobalResults([]);
|
|
195
236
|
}, []);
|
|
@@ -272,14 +313,16 @@ function SearchDialog({
|
|
|
272
313
|
return () => clearTimeout(timer);
|
|
273
314
|
}, [localQuery, mode, searchAllSessions, clearSearch]);
|
|
274
315
|
useEffect(() => {
|
|
275
|
-
if (open) {
|
|
276
|
-
setLocalQuery("");
|
|
277
|
-
setSelectedIndex(0);
|
|
316
|
+
if (!open) {
|
|
278
317
|
clearSearch();
|
|
279
|
-
|
|
280
|
-
inputRef.current?.focus();
|
|
281
|
-
}, 50);
|
|
318
|
+
return;
|
|
282
319
|
}
|
|
320
|
+
setLocalQuery("");
|
|
321
|
+
setSelectedIndex(0);
|
|
322
|
+
clearSearch();
|
|
323
|
+
setTimeout(() => {
|
|
324
|
+
inputRef.current?.focus();
|
|
325
|
+
}, 50);
|
|
283
326
|
}, [open, clearSearch]);
|
|
284
327
|
useEffect(() => {
|
|
285
328
|
setSelectedIndex(0);
|