opencami 1.5.0 → 1.6.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/README.md +4 -1
- package/dist/client/assets/{CSPContext-EgWK8bIJ.js → CSPContext-Bq8j4nl9.js} +1 -1
- package/dist/client/assets/DirectionContext-BdX86BHP.js +1 -0
- package/dist/client/assets/_sessionKey-DsjnpErt.js +14 -0
- package/dist/client/assets/agents-DwxKcpP6.js +2 -0
- package/dist/client/assets/agents-screen-DwIY8hze.js +1 -0
- package/dist/client/assets/bots-CRlm-3-d.js +2 -0
- package/dist/client/assets/bots-screen-c78I920d.js +1 -0
- package/dist/client/assets/button-Dg7VFQQn.js +1 -0
- package/dist/client/assets/c-BIGW1oBm.js +1 -0
- package/dist/client/assets/composite-DBl8R3ae.js +1 -0
- package/dist/client/assets/{connect-w4lLOqiJ.js → connect-NYvOqiBJ.js} +1 -1
- package/dist/client/assets/core-BrHBc0Zp.js +12 -0
- package/dist/client/assets/cpp-B-lmv-kZ.js +1 -0
- package/dist/client/assets/csharp-K5feNrxe.js +1 -0
- package/dist/client/assets/css-DPfMkruS.js +1 -0
- package/dist/client/assets/diff-D97Zzqfu.js +1 -0
- package/dist/client/assets/dockerfile-BcOcwvcX.js +1 -0
- package/dist/client/assets/engine-javascript-PwbX1GN8.js +141 -0
- package/dist/client/assets/file-explorer-screen-BSMbs0vi.js +1 -0
- package/dist/client/assets/files-BJbMx0_w.js +2 -0
- package/dist/client/assets/go-Dn2_MT6a.js +1 -0
- package/dist/client/assets/graphql-ChdNCCLP.js +1 -0
- package/dist/client/assets/html-GMplVEZG.js +1 -0
- package/dist/client/assets/index-CMATW8VA.js +3 -0
- package/dist/client/assets/index-T4TOjvD0.js +1 -0
- package/dist/client/assets/{index-36G0WCxU.js → index-rOIRO-8E.js} +1 -1
- package/dist/client/assets/java-CylS5w8V.js +1 -0
- package/dist/client/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/client/assets/json-Cp-IABpG.js +1 -0
- package/dist/client/assets/jsx-g9-lgVsj.js +1 -0
- package/dist/client/assets/keyboard-shortcuts-dialog-BTGWdJMl.js +1 -0
- package/dist/client/assets/kotlin-BdnUsdx6.js +1 -0
- package/dist/client/assets/{main-B3N0eQFg.js → main-B_dlfHME.js} +9 -9
- package/dist/client/assets/markdown-BVzT7z4x.js +87 -0
- package/dist/client/assets/markdown-Cvjx9yec.js +1 -0
- package/dist/client/assets/memory-S3Yws6a5.js +2 -0
- package/dist/client/assets/memory-screen-C-Z9o31m.js +1 -0
- package/dist/client/assets/menu-DHNgWk_8.js +1 -0
- package/dist/client/assets/{opencami-logo-DD0DPFRQ.js → opencami-logo-BQQETnJG.js} +1 -1
- package/dist/client/assets/owner-CpRnf1fI.js +1 -0
- package/dist/client/assets/php-CDn_0X-4.js +1 -0
- package/dist/client/assets/popupStateMapping-BRPDXnjv.js +1 -0
- package/dist/client/assets/proxy-BcUh9kMA.js +9 -0
- package/dist/client/assets/python-B6aJPvgy.js +1 -0
- package/dist/client/assets/react-irH8OzhB.js +1 -0
- package/dist/client/assets/regexp-CDVJQ6XC.js +1 -0
- package/dist/client/assets/ruby-FDmvQDUv.js +1 -0
- package/dist/client/assets/rust-B1yitclQ.js +1 -0
- package/dist/client/assets/search-dialog-B96zx_ng.js +1 -0
- package/dist/client/assets/session-export-dialog-DPuHnhgv.js +1 -0
- package/dist/client/assets/settings-dialog-DZcRCaPj.js +1 -0
- package/dist/client/assets/shell-DfDnw5Jg.js +1 -0
- package/dist/client/assets/skills-YZe3I63y.js +2 -0
- package/dist/client/assets/{skills-panel-BLUjzfjJ.js → skills-panel-WDUfIwnI.js} +2 -2
- package/dist/client/assets/sql-BLtJtn59.js +1 -0
- package/dist/client/assets/styles-Bwo-K6Y4.css +1 -0
- package/dist/client/assets/swift-Dg5xB15N.js +1 -0
- package/dist/client/assets/switch-DPocNFRG.js +1 -0
- package/dist/client/assets/tabs-B0cro1hL.js +1 -0
- package/dist/client/assets/toml-vGWfd6FD.js +1 -0
- package/dist/client/assets/tooltip-Dg9fy-vT.js +1 -0
- package/dist/client/assets/tsx-COt5Ahok.js +1 -0
- package/dist/client/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/client/assets/use-file-explorer-state-DzT0bksg.js +12 -0
- package/dist/client/assets/useButton-Cbl_9oFG.js +1 -0
- package/dist/client/assets/useCompositeItem-BDAzTxVe.js +1 -0
- package/dist/client/assets/{useControlled-BhUuiHAm.js → useControlled-Dscz_s4f.js} +1 -1
- package/dist/client/assets/{useMutation-CFmVaBag.js → useMutation-B1FlDsNN.js} +1 -1
- package/dist/client/assets/visuallyHidden-ONmQ-0U2.js +1 -0
- package/dist/client/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/dist/client/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/dist/client/assets/xml-sdJ4AIDG.js +1 -0
- package/dist/client/assets/yaml-Buea-lGh.js +1 -0
- package/dist/server/assets/{_sessionKey-tRze5NLR.js → _sessionKey-B0ZlLAjH.js} +184 -575
- package/dist/server/assets/_tanstack-start-manifest_v-D5UVTs1o.js +4 -0
- package/dist/server/assets/{connect-d3AqjAqe.js → connect-CbgijWz4.js} +1 -34
- package/dist/server/assets/{file-explorer-screen-CVlFiAFu.js → file-explorer-screen-DH4UFK03.js} +3 -2
- package/dist/server/assets/{files-BIEcSPGp.js → files-DYdXlQDr.js} +1 -1
- package/dist/server/assets/{index-CRfLKh30.js → index-CiUjUD0t.js} +1 -1
- package/dist/server/assets/{index-CNIATlJ9.js → index-Dl2BOKP7.js} +94 -79
- package/dist/server/assets/{keyboard-shortcuts-dialog-CsNP85q8.js → keyboard-shortcuts-dialog-Cr6fOqHz.js} +1 -2
- package/dist/server/assets/markdown-BFE5y9YH.js +565 -0
- package/dist/server/assets/memory-BqZOoD7Q.js +11 -0
- package/dist/server/assets/memory-screen-BK5phS8K.js +235 -0
- package/dist/server/assets/menu-D90CDTi2.js +45 -0
- package/dist/server/assets/{router-rn7pJO_D.js → router-Uuagl6O7.js} +56 -46
- package/dist/server/assets/{search-dialog-Bz4Cu0KW.js → search-dialog-DZTS5SEi.js} +49 -83
- package/dist/server/assets/{session-export-dialog-CwclV0Aj.js → session-export-dialog-C53RRAah.js} +1 -2
- package/dist/server/assets/{settings-dialog-BBM7jCjE.js → settings-dialog-CSYDj2qm.js} +82 -56
- package/dist/server/assets/{use-file-explorer-state-Il1LlBAe.js → use-file-explorer-state-s7CS50ho.js} +0 -41
- package/dist/server/server.js +2 -2
- package/package.json +2 -1
- package/dist/client/assets/DirectionContext-DXtY05YF.js +0 -1
- package/dist/client/assets/_sessionKey-B89e7G3y.js +0 -100
- package/dist/client/assets/agents-screen-1BiEZ9od.js +0 -1
- package/dist/client/assets/agents-x54ocA9z.js +0 -2
- package/dist/client/assets/bots-screen-BNQciUeJ.js +0 -1
- package/dist/client/assets/bots-x86ZHG4b.js +0 -2
- package/dist/client/assets/button-nDcsaNPl.js +0 -1
- package/dist/client/assets/file-explorer-screen-CAsjd3w8.js +0 -1
- package/dist/client/assets/files-Bype5Mnb.js +0 -2
- package/dist/client/assets/index-BXkRE220.js +0 -153
- package/dist/client/assets/keyboard-shortcuts-dialog-BdCeXRjD.js +0 -1
- package/dist/client/assets/react-B16OrBeM.js +0 -1
- package/dist/client/assets/search-dialog-BjTPceEl.js +0 -1
- package/dist/client/assets/session-export-dialog-DtHKG2zW.js +0 -1
- package/dist/client/assets/settings-dialog-hiqdk_UD.js +0 -1
- package/dist/client/assets/skills-DhwyFq3y.js +0 -2
- package/dist/client/assets/styles-CHP4l6vZ.css +0 -1
- package/dist/client/assets/switch-J6wLIVu2.js +0 -1
- package/dist/client/assets/tabs-DvPgTz5I.js +0 -1
- package/dist/client/assets/tooltip-C14vdXHK.js +0 -1
- package/dist/client/assets/use-file-explorer-state-BnaJEqRP.js +0 -12
- package/dist/client/assets/useButton-Bnnac1eR.js +0 -1
- package/dist/client/assets/useCompositeItem-BgiEMKAt.js +0 -1
- package/dist/client/assets/visuallyHidden-DCCICp6T.js +0 -9
- package/dist/server/assets/_tanstack-start-manifest_v-CyfoMvUa.js +0 -4
|
@@ -1,27 +1,25 @@
|
|
|
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, { memo, useDeferredValue, useState, useMemo, useCallback, Suspense, lazy, useRef, useEffect,
|
|
4
|
+
import React__default, { memo, useDeferredValue, useState, useMemo, useCallback, 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
7
|
import { HugeiconsIcon } from "@hugeicons/react";
|
|
8
|
-
import { Tick01Icon, Cancel01Icon, MoreHorizontalIcon, Pen01Icon, Upload01Icon, Delete01Icon, BotIcon, Clock01Icon, Chat01Icon, ArrowRight01Icon, SidebarLeft01Icon, PencilEdit02Icon, Folder01Icon,
|
|
8
|
+
import { Tick01Icon, Cancel01Icon, MoreHorizontalIcon, Pen01Icon, Upload01Icon, Delete01Icon, BotIcon, Clock01Icon, Chat01Icon, ArrowRight01Icon, SidebarLeft01Icon, PencilEdit02Icon, Folder01Icon, AiBrain01Icon, PackageOpenIcon, SmartPhone01Icon, Search01Icon, Settings01Icon, Menu01Icon, Tick02Icon, Copy01Icon, Loading02Icon, StopIcon, VolumeHighIcon, ArrowDown01Icon, Loading03Icon, ArtificialIntelligence02Icon, CommandIcon, Attachment01Icon, File01Icon, Mic02Icon, ArrowUp02Icon } from "@hugeicons/core-free-icons";
|
|
9
9
|
import { motion, AnimatePresence } from "motion/react";
|
|
10
|
-
import { D as DialogRoot, a as DialogContent, b as DialogTitle, c as DialogDescription, d as DialogClose
|
|
10
|
+
import { D as DialogRoot, a as DialogContent, b as DialogTitle, c as DialogDescription, d as DialogClose } from "./use-file-explorer-state-s7CS50ho.js";
|
|
11
11
|
import { B as Button, c as cn, b as buttonVariants } from "./button-CwY2OHFj.js";
|
|
12
12
|
import { AlertDialog } from "@base-ui/react/alert-dialog";
|
|
13
13
|
import { Collapsible as Collapsible$1 } from "@base-ui/react/collapsible";
|
|
14
14
|
import { ScrollArea } from "@base-ui/react/scroll-area";
|
|
15
|
+
import { M as MenuRoot, a as MenuTrigger, b as MenuContent, c as MenuItem } from "./menu-D90CDTi2.js";
|
|
15
16
|
import { O as OpenCamiLogo, a as OpenCamiText } from "./opencami-logo-C-43FL3R.js";
|
|
16
|
-
import {
|
|
17
|
-
import
|
|
18
|
-
import remarkBreaks from "remark-breaks";
|
|
19
|
-
import remarkGfm from "remark-gfm";
|
|
20
|
-
import { r as resolveLanguage, C as CodeBlock, u as useChatSettings$1 } from "./index-CNIATlJ9.js";
|
|
17
|
+
import { M as Markdown } from "./markdown-BFE5y9YH.js";
|
|
18
|
+
import { u as useChatSettings$1 } from "./index-Dl2BOKP7.js";
|
|
21
19
|
import { create } from "zustand";
|
|
22
20
|
import { persist } from "zustand/middleware";
|
|
23
21
|
import { createPortal } from "react-dom";
|
|
24
|
-
import { a as Route } from "./router-
|
|
22
|
+
import { a as Route } from "./router-Uuagl6O7.js";
|
|
25
23
|
function deriveFriendlyIdFromKey(key) {
|
|
26
24
|
if (!key) return "main";
|
|
27
25
|
const trimmed = key.trim();
|
|
@@ -1664,10 +1662,10 @@ function useRenameSession() {
|
|
|
1664
1662
|
return { renameSession, renaming, error };
|
|
1665
1663
|
}
|
|
1666
1664
|
const SettingsDialog = lazy(
|
|
1667
|
-
() => import("./settings-dialog-
|
|
1665
|
+
() => import("./settings-dialog-CSYDj2qm.js").then((m) => ({ default: m.SettingsDialog }))
|
|
1668
1666
|
);
|
|
1669
1667
|
const SessionExportDialog = lazy(
|
|
1670
|
-
() => import("./session-export-dialog-
|
|
1668
|
+
() => import("./session-export-dialog-C53RRAah.js").then((m) => ({
|
|
1671
1669
|
default: m.SessionExportDialog
|
|
1672
1670
|
}))
|
|
1673
1671
|
);
|
|
@@ -1872,7 +1870,14 @@ function ChatSidebarComponent({
|
|
|
1872
1870
|
]
|
|
1873
1871
|
}
|
|
1874
1872
|
),
|
|
1875
|
-
|
|
1873
|
+
(() => {
|
|
1874
|
+
try {
|
|
1875
|
+
const value = localStorage.getItem("opencami-file-explorer");
|
|
1876
|
+
return value === null ? true : value === "true";
|
|
1877
|
+
} catch {
|
|
1878
|
+
return true;
|
|
1879
|
+
}
|
|
1880
|
+
})() && /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(TooltipRoot, { children: [
|
|
1876
1881
|
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
1877
1882
|
Link,
|
|
1878
1883
|
{
|
|
@@ -1908,6 +1913,49 @@ function ChatSidebarComponent({
|
|
|
1908
1913
|
) }),
|
|
1909
1914
|
isCollapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Files" })
|
|
1910
1915
|
] }) }),
|
|
1916
|
+
(() => {
|
|
1917
|
+
try {
|
|
1918
|
+
const value = localStorage.getItem("opencami-memory-viewer");
|
|
1919
|
+
return value === null ? true : value === "true";
|
|
1920
|
+
} catch {
|
|
1921
|
+
return true;
|
|
1922
|
+
}
|
|
1923
|
+
})() && /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(TooltipRoot, { children: [
|
|
1924
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
1925
|
+
Link,
|
|
1926
|
+
{
|
|
1927
|
+
to: "/memory",
|
|
1928
|
+
className: cn(
|
|
1929
|
+
buttonVariants({ variant: "ghost", size: "sm" }),
|
|
1930
|
+
"w-full pl-1.5 justify-start"
|
|
1931
|
+
),
|
|
1932
|
+
onClick: onSelectSession,
|
|
1933
|
+
children: [
|
|
1934
|
+
/* @__PURE__ */ jsx(
|
|
1935
|
+
HugeiconsIcon,
|
|
1936
|
+
{
|
|
1937
|
+
icon: AiBrain01Icon,
|
|
1938
|
+
size: 20,
|
|
1939
|
+
strokeWidth: 1.5,
|
|
1940
|
+
className: "min-w-5"
|
|
1941
|
+
}
|
|
1942
|
+
),
|
|
1943
|
+
/* @__PURE__ */ jsx(AnimatePresence, { initial: false, mode: "wait", children: !isCollapsed && /* @__PURE__ */ jsx(
|
|
1944
|
+
motion.span,
|
|
1945
|
+
{
|
|
1946
|
+
initial: { opacity: 0 },
|
|
1947
|
+
animate: { opacity: 1 },
|
|
1948
|
+
exit: { opacity: 0 },
|
|
1949
|
+
transition,
|
|
1950
|
+
className: "overflow-hidden whitespace-nowrap",
|
|
1951
|
+
children: "Memory"
|
|
1952
|
+
}
|
|
1953
|
+
) })
|
|
1954
|
+
]
|
|
1955
|
+
}
|
|
1956
|
+
) }),
|
|
1957
|
+
isCollapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Memory" })
|
|
1958
|
+
] }) }),
|
|
1911
1959
|
(() => {
|
|
1912
1960
|
try {
|
|
1913
1961
|
return localStorage.getItem("opencami-skills-browser") === "true";
|
|
@@ -2624,558 +2672,6 @@ function MessageActionsBar({
|
|
|
2624
2672
|
}
|
|
2625
2673
|
);
|
|
2626
2674
|
}
|
|
2627
|
-
const KNOWN_FILE_EXTENSIONS = [
|
|
2628
|
-
"md",
|
|
2629
|
-
"txt",
|
|
2630
|
-
"json",
|
|
2631
|
-
"yaml",
|
|
2632
|
-
"yml",
|
|
2633
|
-
"toml",
|
|
2634
|
-
"py",
|
|
2635
|
-
"js",
|
|
2636
|
-
"ts",
|
|
2637
|
-
"tsx",
|
|
2638
|
-
"jsx",
|
|
2639
|
-
"css",
|
|
2640
|
-
"scss",
|
|
2641
|
-
"html",
|
|
2642
|
-
"xml",
|
|
2643
|
-
"sh",
|
|
2644
|
-
"bash",
|
|
2645
|
-
"go",
|
|
2646
|
-
"rs",
|
|
2647
|
-
"rb",
|
|
2648
|
-
"php",
|
|
2649
|
-
"java",
|
|
2650
|
-
"kt",
|
|
2651
|
-
"swift",
|
|
2652
|
-
"c",
|
|
2653
|
-
"cpp",
|
|
2654
|
-
"h",
|
|
2655
|
-
"hpp",
|
|
2656
|
-
"sql",
|
|
2657
|
-
"graphql",
|
|
2658
|
-
"dockerfile",
|
|
2659
|
-
"env",
|
|
2660
|
-
"conf",
|
|
2661
|
-
"cfg",
|
|
2662
|
-
"ini",
|
|
2663
|
-
"log",
|
|
2664
|
-
"csv"
|
|
2665
|
-
];
|
|
2666
|
-
const EXTENSION_PATTERN = KNOWN_FILE_EXTENSIONS.join("|");
|
|
2667
|
-
const ABSOLUTE_OR_HOME_PATH_PATTERN = "(?:~\\/[A-Za-z0-9._\\-\\/]*[A-Za-z0-9._\\-\\/]|\\/(?:[\\w.\\-]+\\/)*[\\w.\\-]+\\/?)";
|
|
2668
|
-
const RELATIVE_PATH_PATTERN = "(?:[A-Za-z0-9._\\-]+(?:\\/[A-Za-z0-9._\\-]+)+\\/?)";
|
|
2669
|
-
const BARE_FILENAME_PATTERN = `(?:[A-Za-z0-9_-][A-Za-z0-9._-]*[A-Za-z0-9_-]\\.(?:${EXTENSION_PATTERN})|dockerfile)`;
|
|
2670
|
-
const FILE_PATH_REGEX = new RegExp(
|
|
2671
|
-
`(^|[\\s"'(,;:])(${ABSOLUTE_OR_HOME_PATH_PATTERN}|${RELATIVE_PATH_PATTERN}|${BARE_FILENAME_PATTERN})(?=$|[\\s"'),;:!?])`,
|
|
2672
|
-
"gi"
|
|
2673
|
-
);
|
|
2674
|
-
function trimTrailingPunctuation(path) {
|
|
2675
|
-
if (!path) return { path: path ?? "", trailing: "" };
|
|
2676
|
-
const match = path.match(/^(.*?)([),.;:!?]+)?$/);
|
|
2677
|
-
if (!match) return { path, trailing: "" };
|
|
2678
|
-
return { path: match[1] || path, trailing: match[2] || "" };
|
|
2679
|
-
}
|
|
2680
|
-
function isLikelyFilePath(text) {
|
|
2681
|
-
if (!text) return false;
|
|
2682
|
-
FILE_PATH_REGEX.lastIndex = 0;
|
|
2683
|
-
const match = FILE_PATH_REGEX.exec(text);
|
|
2684
|
-
if (!match) return false;
|
|
2685
|
-
const prefix = match[1] || "";
|
|
2686
|
-
const value = match[2] || "";
|
|
2687
|
-
const matchStart = match.index + prefix.length;
|
|
2688
|
-
const matchEnd = matchStart + value.length;
|
|
2689
|
-
return matchStart === 0 && matchEnd === text.length;
|
|
2690
|
-
}
|
|
2691
|
-
function splitTextByFilePaths(text) {
|
|
2692
|
-
if (!text) return [{ type: "text", value: text }];
|
|
2693
|
-
const segments = [];
|
|
2694
|
-
let lastIndex = 0;
|
|
2695
|
-
FILE_PATH_REGEX.lastIndex = 0;
|
|
2696
|
-
let match;
|
|
2697
|
-
while ((match = FILE_PATH_REGEX.exec(text)) !== null) {
|
|
2698
|
-
const prefix = match[1] || "";
|
|
2699
|
-
const rawPath = match[2] || "";
|
|
2700
|
-
const fullMatch = match[0];
|
|
2701
|
-
const start = match.index;
|
|
2702
|
-
const prefixStart = start;
|
|
2703
|
-
const pathStart = start + prefix.length;
|
|
2704
|
-
if (prefixStart > lastIndex) {
|
|
2705
|
-
segments.push({ type: "text", value: text.slice(lastIndex, prefixStart) });
|
|
2706
|
-
}
|
|
2707
|
-
if (prefix) {
|
|
2708
|
-
segments.push({ type: "text", value: prefix });
|
|
2709
|
-
}
|
|
2710
|
-
const { path, trailing } = trimTrailingPunctuation(rawPath);
|
|
2711
|
-
if (path.length > 1) {
|
|
2712
|
-
segments.push({ type: "path", value: path });
|
|
2713
|
-
if (trailing) {
|
|
2714
|
-
segments.push({ type: "text", value: trailing });
|
|
2715
|
-
}
|
|
2716
|
-
} else {
|
|
2717
|
-
segments.push({ type: "text", value: fullMatch });
|
|
2718
|
-
}
|
|
2719
|
-
lastIndex = pathStart + rawPath.length;
|
|
2720
|
-
}
|
|
2721
|
-
if (lastIndex < text.length) {
|
|
2722
|
-
segments.push({ type: "text", value: text.slice(lastIndex) });
|
|
2723
|
-
}
|
|
2724
|
-
return segments.length > 0 ? segments : [{ type: "text", value: text }];
|
|
2725
|
-
}
|
|
2726
|
-
function filePathToMarkdownHref(path) {
|
|
2727
|
-
return `openclaw-file://${encodeURIComponent(path)}`;
|
|
2728
|
-
}
|
|
2729
|
-
function markdownHrefToFilePath(href) {
|
|
2730
|
-
if (!href?.startsWith("openclaw-file://")) return null;
|
|
2731
|
-
try {
|
|
2732
|
-
return decodeURIComponent(href.slice("openclaw-file://".length));
|
|
2733
|
-
} catch {
|
|
2734
|
-
return null;
|
|
2735
|
-
}
|
|
2736
|
-
}
|
|
2737
|
-
function remarkFilePathLinks() {
|
|
2738
|
-
return (tree) => {
|
|
2739
|
-
function visit(node, parent) {
|
|
2740
|
-
if (!node) return;
|
|
2741
|
-
if (node.type === "text" && parent && parent.type !== "link" && parent.type !== "inlineCode" && parent.type !== "code") {
|
|
2742
|
-
const segments = splitTextByFilePaths(String(node.value || ""));
|
|
2743
|
-
const hasPaths = segments.some((segment) => segment.type === "path");
|
|
2744
|
-
if (!hasPaths) return;
|
|
2745
|
-
const replacement = segments.map((segment) => {
|
|
2746
|
-
if (segment.type === "text") {
|
|
2747
|
-
return { type: "text", value: segment.value };
|
|
2748
|
-
}
|
|
2749
|
-
return {
|
|
2750
|
-
type: "link",
|
|
2751
|
-
url: filePathToMarkdownHref(segment.value),
|
|
2752
|
-
children: [{ type: "text", value: segment.value }]
|
|
2753
|
-
};
|
|
2754
|
-
});
|
|
2755
|
-
const index = parent.children.indexOf(node);
|
|
2756
|
-
if (index >= 0) {
|
|
2757
|
-
parent.children.splice(index, 1, ...replacement);
|
|
2758
|
-
}
|
|
2759
|
-
return;
|
|
2760
|
-
}
|
|
2761
|
-
if (Array.isArray(node.children)) {
|
|
2762
|
-
for (const child of [...node.children]) {
|
|
2763
|
-
visit(child, node);
|
|
2764
|
-
}
|
|
2765
|
-
}
|
|
2766
|
-
}
|
|
2767
|
-
visit(tree, null);
|
|
2768
|
-
};
|
|
2769
|
-
}
|
|
2770
|
-
const EXTENSION_LANGUAGE_MAP = {
|
|
2771
|
-
py: "python",
|
|
2772
|
-
ts: "typescript",
|
|
2773
|
-
js: "javascript",
|
|
2774
|
-
jsx: "jsx",
|
|
2775
|
-
tsx: "tsx",
|
|
2776
|
-
json: "json",
|
|
2777
|
-
md: "markdown",
|
|
2778
|
-
yml: "yaml",
|
|
2779
|
-
yaml: "yaml",
|
|
2780
|
-
sh: "bash",
|
|
2781
|
-
bash: "bash",
|
|
2782
|
-
zsh: "bash",
|
|
2783
|
-
html: "html",
|
|
2784
|
-
css: "css",
|
|
2785
|
-
sql: "sql",
|
|
2786
|
-
xml: "xml",
|
|
2787
|
-
toml: "toml",
|
|
2788
|
-
rs: "rust",
|
|
2789
|
-
go: "go",
|
|
2790
|
-
java: "java",
|
|
2791
|
-
c: "c",
|
|
2792
|
-
cpp: "cpp",
|
|
2793
|
-
cs: "csharp",
|
|
2794
|
-
php: "php",
|
|
2795
|
-
rb: "ruby",
|
|
2796
|
-
graphql: "graphql",
|
|
2797
|
-
diff: "diff",
|
|
2798
|
-
patch: "diff",
|
|
2799
|
-
env: "text"
|
|
2800
|
-
};
|
|
2801
|
-
function languageFromFilePath(path) {
|
|
2802
|
-
if (!path) return "text";
|
|
2803
|
-
const filename = path.split("/").pop() || "";
|
|
2804
|
-
const lower = filename.toLowerCase();
|
|
2805
|
-
if (lower === "dockerfile") return "dockerfile";
|
|
2806
|
-
if (lower === "makefile") return "text";
|
|
2807
|
-
const parts = lower.split(".");
|
|
2808
|
-
const extension = parts.length > 1 ? parts.pop() || "" : "";
|
|
2809
|
-
const mapped = EXTENSION_LANGUAGE_MAP[extension] || extension || "text";
|
|
2810
|
-
return resolveLanguage(mapped);
|
|
2811
|
-
}
|
|
2812
|
-
const INLINE_PREVIEW_MAX_BYTES = 100 * 1024;
|
|
2813
|
-
function normalizeClickedPath(path) {
|
|
2814
|
-
if (!path) return "/";
|
|
2815
|
-
return path.includes("/") ? path : `/${path}`;
|
|
2816
|
-
}
|
|
2817
|
-
function toWorkspacePath(path) {
|
|
2818
|
-
let p = normalizeClickedPath(path);
|
|
2819
|
-
const prefixes = ["/root/clawd/", "/root/"];
|
|
2820
|
-
for (const prefix of prefixes) {
|
|
2821
|
-
if (p.startsWith(prefix)) {
|
|
2822
|
-
p = "/" + p.slice(prefix.length);
|
|
2823
|
-
break;
|
|
2824
|
-
}
|
|
2825
|
-
}
|
|
2826
|
-
return p.startsWith("/") ? p : `/${p}`;
|
|
2827
|
-
}
|
|
2828
|
-
function hasExtension(path) {
|
|
2829
|
-
const trimmed = path.replace(/\/+$/, "");
|
|
2830
|
-
const name = trimmed.split("/").pop() || "";
|
|
2831
|
-
if (!name || name.startsWith(".")) return false;
|
|
2832
|
-
return name.includes(".");
|
|
2833
|
-
}
|
|
2834
|
-
function isDirectoryPathHeuristic(path) {
|
|
2835
|
-
if (!path) return false;
|
|
2836
|
-
return path.endsWith("/") || !hasExtension(path);
|
|
2837
|
-
}
|
|
2838
|
-
function isDirectoryError(code, message) {
|
|
2839
|
-
const normalizedCode = String(code || "").toUpperCase();
|
|
2840
|
-
const normalizedMessage = String(message || "").toLowerCase();
|
|
2841
|
-
return normalizedCode.includes("DIRECTORY") || normalizedMessage.includes("is a directory") || normalizedMessage.includes("directory");
|
|
2842
|
-
}
|
|
2843
|
-
function parseMarkdownIntoBlocks(markdown) {
|
|
2844
|
-
const tokens = marked.lexer(markdown);
|
|
2845
|
-
return tokens.map((token) => token.raw);
|
|
2846
|
-
}
|
|
2847
|
-
function extractLanguage(className) {
|
|
2848
|
-
if (!className) return "text";
|
|
2849
|
-
const match = className.match(/language-([\w-]+)/);
|
|
2850
|
-
return match ? match[1] : "text";
|
|
2851
|
-
}
|
|
2852
|
-
function extractFilenameFromMeta(meta) {
|
|
2853
|
-
const value = meta?.trim();
|
|
2854
|
-
if (!value) return void 0;
|
|
2855
|
-
const firstToken = value.split(/\s+/)[0];
|
|
2856
|
-
return firstToken || void 0;
|
|
2857
|
-
}
|
|
2858
|
-
const BASE_COMPONENTS = {
|
|
2859
|
-
code: function CodeComponent({ className, children, node }) {
|
|
2860
|
-
const isInline = !className?.includes("language-");
|
|
2861
|
-
if (isInline) {
|
|
2862
|
-
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 });
|
|
2863
|
-
}
|
|
2864
|
-
const language = extractLanguage(className);
|
|
2865
|
-
const filename = extractFilenameFromMeta(
|
|
2866
|
-
node?.data?.meta
|
|
2867
|
-
);
|
|
2868
|
-
return /* @__PURE__ */ jsx(
|
|
2869
|
-
CodeBlock,
|
|
2870
|
-
{
|
|
2871
|
-
content: String(children ?? ""),
|
|
2872
|
-
language,
|
|
2873
|
-
filename,
|
|
2874
|
-
className: "w-full"
|
|
2875
|
-
}
|
|
2876
|
-
);
|
|
2877
|
-
},
|
|
2878
|
-
pre: function PreComponent({ children }) {
|
|
2879
|
-
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
2880
|
-
},
|
|
2881
|
-
h1: function H1Component({ children }) {
|
|
2882
|
-
return /* @__PURE__ */ jsx("h1", { className: "text-xl font-medium text-primary-950", children });
|
|
2883
|
-
},
|
|
2884
|
-
h2: function H2Component({ children }) {
|
|
2885
|
-
return /* @__PURE__ */ jsx("h2", { className: "text-lg font-medium text-primary-900", children });
|
|
2886
|
-
},
|
|
2887
|
-
h3: function H3Component({ children }) {
|
|
2888
|
-
return /* @__PURE__ */ jsx("h3", { className: "font-medium text-primary-900", children });
|
|
2889
|
-
},
|
|
2890
|
-
p: function PComponent({ children }) {
|
|
2891
|
-
return /* @__PURE__ */ jsx("p", { className: "text-primary-950 text-pretty leading-relaxed", children });
|
|
2892
|
-
},
|
|
2893
|
-
ul: function UlComponent({ children }) {
|
|
2894
|
-
return /* @__PURE__ */ jsx("ul", { className: "ml-4 list-disc text-primary-950 marker:text-primary-400", children });
|
|
2895
|
-
},
|
|
2896
|
-
ol: function OlComponent({ children }) {
|
|
2897
|
-
return /* @__PURE__ */ jsx("ol", { className: "ml-4 list-decimal text-primary-950 marker:text-primary-500", children });
|
|
2898
|
-
},
|
|
2899
|
-
li: function LiComponent({ children }) {
|
|
2900
|
-
return /* @__PURE__ */ jsx("li", { className: "leading-relaxed", children });
|
|
2901
|
-
},
|
|
2902
|
-
blockquote: function BlockquoteComponent({ children }) {
|
|
2903
|
-
return /* @__PURE__ */ jsx("blockquote", { className: "border-l-2 border-primary-300 pl-4 text-primary-900 italic", children });
|
|
2904
|
-
},
|
|
2905
|
-
strong: function StrongComponent({ children }) {
|
|
2906
|
-
return /* @__PURE__ */ jsx("strong", { className: "font-medium text-primary-950", children });
|
|
2907
|
-
},
|
|
2908
|
-
em: function EmComponent({ children }) {
|
|
2909
|
-
return /* @__PURE__ */ jsx("em", { className: "italic text-primary-950", children });
|
|
2910
|
-
},
|
|
2911
|
-
hr: function HrComponent() {
|
|
2912
|
-
return /* @__PURE__ */ jsx("hr", { className: "my-3 border-primary-200" });
|
|
2913
|
-
},
|
|
2914
|
-
table: function TableComponent({ children }) {
|
|
2915
|
-
return /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsx("table", { className: "w-full border-collapse text-sm", children }) });
|
|
2916
|
-
},
|
|
2917
|
-
thead: function TheadComponent({ children }) {
|
|
2918
|
-
return /* @__PURE__ */ jsx("thead", { className: "border-b border-primary-200 bg-primary-50", children });
|
|
2919
|
-
},
|
|
2920
|
-
tbody: function TbodyComponent({ children }) {
|
|
2921
|
-
return /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-primary-100", children });
|
|
2922
|
-
},
|
|
2923
|
-
tr: function TrComponent({ children }) {
|
|
2924
|
-
return /* @__PURE__ */ jsx("tr", { className: "transition-colors hover:bg-primary-50/50", children });
|
|
2925
|
-
},
|
|
2926
|
-
th: function ThComponent({ children }) {
|
|
2927
|
-
return /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left font-medium text-primary-950", children });
|
|
2928
|
-
},
|
|
2929
|
-
td: function TdComponent({ children }) {
|
|
2930
|
-
return /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-primary-950", children });
|
|
2931
|
-
}
|
|
2932
|
-
};
|
|
2933
|
-
function createDefaultComponents(onOpenFilePreview) {
|
|
2934
|
-
return {
|
|
2935
|
-
...BASE_COMPONENTS,
|
|
2936
|
-
a: function AComponent({ children, href }) {
|
|
2937
|
-
const filePath = markdownHrefToFilePath(href);
|
|
2938
|
-
if (filePath) {
|
|
2939
|
-
return /* @__PURE__ */ jsx(
|
|
2940
|
-
"button",
|
|
2941
|
-
{
|
|
2942
|
-
type: "button",
|
|
2943
|
-
onClick: () => onOpenFilePreview(filePath),
|
|
2944
|
-
className: "font-mono text-[var(--opencami-accent)] underline decoration-[var(--opencami-accent-light)] underline-offset-4 hover:opacity-90 cursor-pointer",
|
|
2945
|
-
children
|
|
2946
|
-
}
|
|
2947
|
-
);
|
|
2948
|
-
}
|
|
2949
|
-
return /* @__PURE__ */ jsx(
|
|
2950
|
-
"a",
|
|
2951
|
-
{
|
|
2952
|
-
href,
|
|
2953
|
-
onClick: (event) => {
|
|
2954
|
-
if (href?.startsWith("openclaw-file://")) event.preventDefault();
|
|
2955
|
-
},
|
|
2956
|
-
className: "text-[var(--opencami-accent)] underline decoration-[var(--opencami-accent-light)] underline-offset-4 transition-opacity hover:opacity-90",
|
|
2957
|
-
target: "_blank",
|
|
2958
|
-
rel: "noopener noreferrer",
|
|
2959
|
-
children
|
|
2960
|
-
}
|
|
2961
|
-
);
|
|
2962
|
-
},
|
|
2963
|
-
code: function InlineCodeComponent({ children, className, node }) {
|
|
2964
|
-
if (className?.includes("language-")) {
|
|
2965
|
-
const language = extractLanguage(className);
|
|
2966
|
-
const filename = extractFilenameFromMeta(
|
|
2967
|
-
node?.data?.meta
|
|
2968
|
-
);
|
|
2969
|
-
return /* @__PURE__ */ jsx(
|
|
2970
|
-
CodeBlock,
|
|
2971
|
-
{
|
|
2972
|
-
content: String(children ?? ""),
|
|
2973
|
-
language,
|
|
2974
|
-
filename,
|
|
2975
|
-
className: "w-full"
|
|
2976
|
-
}
|
|
2977
|
-
);
|
|
2978
|
-
}
|
|
2979
|
-
const text = typeof children === "string" ? children : Array.isArray(children) ? children.filter((c) => typeof c === "string").join("") : String(children ?? "");
|
|
2980
|
-
if (text && isLikelyFilePath(text)) {
|
|
2981
|
-
return /* @__PURE__ */ jsx(
|
|
2982
|
-
"button",
|
|
2983
|
-
{
|
|
2984
|
-
type: "button",
|
|
2985
|
-
onClick: () => onOpenFilePreview(text),
|
|
2986
|
-
className: "font-mono text-sm bg-primary-100 rounded px-1.5 py-0.5 text-primary-900 underline decoration-primary-300 underline-offset-4 hover:decoration-primary-600 cursor-pointer",
|
|
2987
|
-
children
|
|
2988
|
-
}
|
|
2989
|
-
);
|
|
2990
|
-
}
|
|
2991
|
-
return /* @__PURE__ */ jsx("code", { className: "font-mono text-sm bg-primary-100 rounded px-1.5 py-0.5 text-primary-900", children });
|
|
2992
|
-
}
|
|
2993
|
-
};
|
|
2994
|
-
}
|
|
2995
|
-
const MemoizedMarkdownBlock = memo(
|
|
2996
|
-
function MarkdownBlock({
|
|
2997
|
-
content,
|
|
2998
|
-
components
|
|
2999
|
-
}) {
|
|
3000
|
-
return /* @__PURE__ */ jsx(
|
|
3001
|
-
ReactMarkdown,
|
|
3002
|
-
{
|
|
3003
|
-
remarkPlugins: [remarkGfm, remarkBreaks, remarkFilePathLinks],
|
|
3004
|
-
components,
|
|
3005
|
-
children: content
|
|
3006
|
-
}
|
|
3007
|
-
);
|
|
3008
|
-
},
|
|
3009
|
-
function propsAreEqual(prevProps, nextProps) {
|
|
3010
|
-
return prevProps.content === nextProps.content;
|
|
3011
|
-
}
|
|
3012
|
-
);
|
|
3013
|
-
MemoizedMarkdownBlock.displayName = "MemoizedMarkdownBlock";
|
|
3014
|
-
function fileErrorMessageFromResponse(status, code) {
|
|
3015
|
-
if (code === "NOT_FOUND" || status === 404) return "File not found";
|
|
3016
|
-
if (code === "UNSUPPORTED_TYPE") return "Binary file";
|
|
3017
|
-
if (code === "FILE_TOO_LARGE" || status === 413) return "File too large";
|
|
3018
|
-
return "Failed to load file preview";
|
|
3019
|
-
}
|
|
3020
|
-
function MarkdownComponent({
|
|
3021
|
-
children,
|
|
3022
|
-
id,
|
|
3023
|
-
className,
|
|
3024
|
-
components
|
|
3025
|
-
}) {
|
|
3026
|
-
const generatedId = useId();
|
|
3027
|
-
const blockId = id ?? generatedId;
|
|
3028
|
-
const blocks = useMemo(() => parseMarkdownIntoBlocks(children), [children]);
|
|
3029
|
-
const [filePreview, setFilePreview] = useState({ status: "idle" });
|
|
3030
|
-
const navigate = useNavigate();
|
|
3031
|
-
const openDirectoryInExplorer = useCallback((path) => {
|
|
3032
|
-
const workspacePath = toWorkspacePath(path);
|
|
3033
|
-
useFileExplorerState.getState().navigateTo(workspacePath);
|
|
3034
|
-
setFilePreview({ status: "idle" });
|
|
3035
|
-
navigate({ to: "/files" });
|
|
3036
|
-
}, [navigate]);
|
|
3037
|
-
const onOpenFilePreview = useCallback((path) => {
|
|
3038
|
-
const resolvedPath = normalizeClickedPath(path);
|
|
3039
|
-
if (isDirectoryPathHeuristic(resolvedPath)) {
|
|
3040
|
-
openDirectoryInExplorer(resolvedPath);
|
|
3041
|
-
return;
|
|
3042
|
-
}
|
|
3043
|
-
setFilePreview({ status: "loading", path: resolvedPath });
|
|
3044
|
-
}, [openDirectoryInExplorer]);
|
|
3045
|
-
const defaultComponents = useMemo(
|
|
3046
|
-
() => createDefaultComponents(onOpenFilePreview),
|
|
3047
|
-
[onOpenFilePreview]
|
|
3048
|
-
);
|
|
3049
|
-
const mergedComponents = useMemo(
|
|
3050
|
-
() => ({ ...defaultComponents, ...components || {} }),
|
|
3051
|
-
[defaultComponents, components]
|
|
3052
|
-
);
|
|
3053
|
-
useEffect(() => {
|
|
3054
|
-
if (filePreview.status !== "loading") return;
|
|
3055
|
-
const path = filePreview.path;
|
|
3056
|
-
const controller = new AbortController();
|
|
3057
|
-
fetch(`/api/files/read?path=${encodeURIComponent(path)}`, {
|
|
3058
|
-
signal: controller.signal
|
|
3059
|
-
}).then(async (response) => {
|
|
3060
|
-
const payload = await response.json().catch(() => ({}));
|
|
3061
|
-
if (!response.ok) {
|
|
3062
|
-
if (isDirectoryError(payload.code, payload.message)) {
|
|
3063
|
-
openDirectoryInExplorer(path);
|
|
3064
|
-
return;
|
|
3065
|
-
}
|
|
3066
|
-
const error = fileErrorMessageFromResponse(response.status, payload.code);
|
|
3067
|
-
setFilePreview({ status: "error", path, message: error });
|
|
3068
|
-
return;
|
|
3069
|
-
}
|
|
3070
|
-
const size = Number(payload.size ?? 0);
|
|
3071
|
-
if (size > INLINE_PREVIEW_MAX_BYTES) {
|
|
3072
|
-
setFilePreview({ status: "error", path, message: "File too large" });
|
|
3073
|
-
return;
|
|
3074
|
-
}
|
|
3075
|
-
const content = String(payload.content ?? "");
|
|
3076
|
-
setFilePreview({
|
|
3077
|
-
status: "success",
|
|
3078
|
-
path,
|
|
3079
|
-
content,
|
|
3080
|
-
language: languageFromFilePath(path)
|
|
3081
|
-
});
|
|
3082
|
-
}).catch(() => {
|
|
3083
|
-
if (controller.signal.aborted) return;
|
|
3084
|
-
setFilePreview({ status: "error", path, message: "Failed to load file preview" });
|
|
3085
|
-
});
|
|
3086
|
-
return () => controller.abort();
|
|
3087
|
-
}, [filePreview, openDirectoryInExplorer]);
|
|
3088
|
-
const previewOpen = filePreview.status !== "idle";
|
|
3089
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3090
|
-
/* @__PURE__ */ jsx(
|
|
3091
|
-
"div",
|
|
3092
|
-
{
|
|
3093
|
-
className: cn("flex min-w-0 max-w-full flex-col gap-2 overflow-x-hidden", className),
|
|
3094
|
-
onClickCapture: (event) => {
|
|
3095
|
-
const target = event.target;
|
|
3096
|
-
const anchor = target?.closest?.('a[href^="openclaw-file://"]');
|
|
3097
|
-
if (!anchor) return;
|
|
3098
|
-
const filePath = markdownHrefToFilePath(anchor.getAttribute("href") ?? void 0);
|
|
3099
|
-
if (!filePath) return;
|
|
3100
|
-
event.preventDefault();
|
|
3101
|
-
event.stopPropagation();
|
|
3102
|
-
onOpenFilePreview(filePath);
|
|
3103
|
-
},
|
|
3104
|
-
children: blocks.map((block, index) => /* @__PURE__ */ jsx(
|
|
3105
|
-
MemoizedMarkdownBlock,
|
|
3106
|
-
{
|
|
3107
|
-
content: block,
|
|
3108
|
-
components: mergedComponents
|
|
3109
|
-
},
|
|
3110
|
-
`${blockId}-block-${index}`
|
|
3111
|
-
))
|
|
3112
|
-
}
|
|
3113
|
-
),
|
|
3114
|
-
/* @__PURE__ */ jsx(
|
|
3115
|
-
DialogRoot,
|
|
3116
|
-
{
|
|
3117
|
-
open: previewOpen,
|
|
3118
|
-
onOpenChange: (open) => {
|
|
3119
|
-
if (!open) setFilePreview({ status: "idle" });
|
|
3120
|
-
},
|
|
3121
|
-
children: /* @__PURE__ */ jsxs(DialogContent, { className: "w-[min(1000px,95vw)] max-h-[88vh] overflow-hidden p-0", children: [
|
|
3122
|
-
/* @__PURE__ */ jsxs("div", { className: "border-b border-primary-200 px-4 py-3 flex items-start justify-between gap-3", children: [
|
|
3123
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
3124
|
-
/* @__PURE__ */ jsx(DialogTitle, { className: "text-base", children: "File Preview" }),
|
|
3125
|
-
filePreview.status !== "idle" && /* @__PURE__ */ jsx("p", { className: "text-xs text-primary-600 font-mono truncate", children: filePreview.path })
|
|
3126
|
-
] }),
|
|
3127
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3128
|
-
filePreview.status !== "idle" && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: /* @__PURE__ */ jsx(
|
|
3129
|
-
Link,
|
|
3130
|
-
{
|
|
3131
|
-
to: "/files",
|
|
3132
|
-
onClick: () => {
|
|
3133
|
-
const p = filePreview.status !== "idle" ? toWorkspacePath(filePreview.path) : "";
|
|
3134
|
-
if (p) {
|
|
3135
|
-
const dir = p.includes("/") ? p.slice(0, p.lastIndexOf("/")) || "/" : "/";
|
|
3136
|
-
useFileExplorerState.getState().navigateTo(dir);
|
|
3137
|
-
}
|
|
3138
|
-
setFilePreview({ status: "idle" });
|
|
3139
|
-
},
|
|
3140
|
-
children: "Open in Explorer"
|
|
3141
|
-
}
|
|
3142
|
-
) }),
|
|
3143
|
-
filePreview.status !== "idle" && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: /* @__PURE__ */ jsx(
|
|
3144
|
-
Link,
|
|
3145
|
-
{
|
|
3146
|
-
to: "/files",
|
|
3147
|
-
onClick: () => {
|
|
3148
|
-
const p = filePreview.status !== "idle" ? toWorkspacePath(filePreview.path) : "";
|
|
3149
|
-
if (p) {
|
|
3150
|
-
useFileExplorerState.getState().openInEditor(p);
|
|
3151
|
-
}
|
|
3152
|
-
setFilePreview({ status: "idle" });
|
|
3153
|
-
},
|
|
3154
|
-
children: "Open in Editor"
|
|
3155
|
-
}
|
|
3156
|
-
) }),
|
|
3157
|
-
/* @__PURE__ */ jsx(DialogClose, { children: "Close" })
|
|
3158
|
-
] })
|
|
3159
|
-
] }),
|
|
3160
|
-
/* @__PURE__ */ jsxs("div", { className: "p-4 overflow-auto max-h-[calc(88vh-72px)]", children: [
|
|
3161
|
-
filePreview.status === "loading" && /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-600", children: "Loading preview…" }),
|
|
3162
|
-
filePreview.status === "error" && /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600", children: filePreview.message }),
|
|
3163
|
-
filePreview.status === "success" && /* @__PURE__ */ jsx(
|
|
3164
|
-
CodeBlock,
|
|
3165
|
-
{
|
|
3166
|
-
content: filePreview.content,
|
|
3167
|
-
language: filePreview.language,
|
|
3168
|
-
className: "w-full"
|
|
3169
|
-
}
|
|
3170
|
-
)
|
|
3171
|
-
] })
|
|
3172
|
-
] })
|
|
3173
|
-
}
|
|
3174
|
-
)
|
|
3175
|
-
] });
|
|
3176
|
-
}
|
|
3177
|
-
const Markdown = memo(MarkdownComponent);
|
|
3178
|
-
Markdown.displayName = "Markdown";
|
|
3179
2675
|
function Message({ children, className, ...props }) {
|
|
3180
2676
|
return /* @__PURE__ */ jsx("div", { className: cn("flex gap-3 w-full", className), ...props, children });
|
|
3181
2677
|
}
|
|
@@ -5988,13 +5484,13 @@ function ChatComposerComponent({
|
|
|
5988
5484
|
/* @__PURE__ */ jsx(CommandHelp, { onCommandSelect: (cmd) => handleValueChange(cmd + " ") })
|
|
5989
5485
|
] }),
|
|
5990
5486
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
5991
|
-
/* @__PURE__ */ jsx(
|
|
5487
|
+
/* @__PURE__ */ jsx(
|
|
5992
5488
|
AttachmentButton,
|
|
5993
5489
|
{
|
|
5994
5490
|
onFileSelect: handleFileSelect,
|
|
5995
5491
|
disabled
|
|
5996
5492
|
}
|
|
5997
|
-
)
|
|
5493
|
+
),
|
|
5998
5494
|
/* @__PURE__ */ jsx(PromptInputAction, { tooltip: isRecording ? "Stop recording" : "Voice input", children: /* @__PURE__ */ jsx(
|
|
5999
5495
|
Button,
|
|
6000
5496
|
{
|
|
@@ -6328,7 +5824,8 @@ function useSmartTitle() {
|
|
|
6328
5824
|
if (controller.signal.aborted) {
|
|
6329
5825
|
throw new Error("Aborted");
|
|
6330
5826
|
}
|
|
6331
|
-
const
|
|
5827
|
+
const rawTitle = data.title || message.slice(0, 50);
|
|
5828
|
+
const title = rawTitle.length > 64 ? rawTitle.slice(0, 61) + "..." : rawTitle;
|
|
6332
5829
|
const source = data.source || "heuristic";
|
|
6333
5830
|
setLastTitle(title);
|
|
6334
5831
|
setLastSource(source);
|
|
@@ -6405,8 +5902,10 @@ function useStreaming(options) {
|
|
|
6405
5902
|
const eventSourceRef = useRef(null);
|
|
6406
5903
|
const onDoneRef = useRef(options.onDone);
|
|
6407
5904
|
const onErrorRef = useRef(options.onError);
|
|
5905
|
+
const onAssistantDeltaRef = useRef(options.onAssistantDelta);
|
|
6408
5906
|
onDoneRef.current = options.onDone;
|
|
6409
5907
|
onErrorRef.current = options.onError;
|
|
5908
|
+
onAssistantDeltaRef.current = options.onAssistantDelta;
|
|
6410
5909
|
const stop = useCallback((options2) => {
|
|
6411
5910
|
if (eventSourceRef.current) {
|
|
6412
5911
|
eventSourceRef.current.close();
|
|
@@ -6439,6 +5938,7 @@ function useStreaming(options) {
|
|
|
6439
5938
|
...prev,
|
|
6440
5939
|
text: prev.text + data.text
|
|
6441
5940
|
}));
|
|
5941
|
+
onAssistantDeltaRef.current?.({ text: data.text, sessionKey: data.sessionKey });
|
|
6442
5942
|
} catch {
|
|
6443
5943
|
}
|
|
6444
5944
|
});
|
|
@@ -6621,13 +6121,109 @@ function useSwipeGesture(options) {
|
|
|
6621
6121
|
onTouchEnd: handleTouchEnd
|
|
6622
6122
|
};
|
|
6623
6123
|
}
|
|
6124
|
+
const ENABLED_KEY = "opencami-browser-notifications-enabled";
|
|
6125
|
+
const ASKED_KEY = "opencami-browser-notifications-permission-asked";
|
|
6126
|
+
const NOTIFICATION_DEBOUNCE_MS = 5e3;
|
|
6127
|
+
function isSupported() {
|
|
6128
|
+
return typeof window !== "undefined" && "Notification" in window;
|
|
6129
|
+
}
|
|
6130
|
+
function getNotificationsEnabled() {
|
|
6131
|
+
if (typeof window === "undefined") return false;
|
|
6132
|
+
try {
|
|
6133
|
+
return localStorage.getItem(ENABLED_KEY) === "true";
|
|
6134
|
+
} catch {
|
|
6135
|
+
return false;
|
|
6136
|
+
}
|
|
6137
|
+
}
|
|
6138
|
+
function useNotifications() {
|
|
6139
|
+
const navigate = useNavigate();
|
|
6140
|
+
const lastNotificationAtRef = useRef(0);
|
|
6141
|
+
const [enabled, setEnabledState] = useState(() => getNotificationsEnabled());
|
|
6142
|
+
const requestPermission = useCallback(async () => {
|
|
6143
|
+
if (!isSupported()) return "denied";
|
|
6144
|
+
try {
|
|
6145
|
+
return await Notification.requestPermission();
|
|
6146
|
+
} catch {
|
|
6147
|
+
return "denied";
|
|
6148
|
+
}
|
|
6149
|
+
}, []);
|
|
6150
|
+
const setEnabled = useCallback(async (nextEnabled) => {
|
|
6151
|
+
setEnabledState(nextEnabled);
|
|
6152
|
+
try {
|
|
6153
|
+
localStorage.setItem(ENABLED_KEY, String(nextEnabled));
|
|
6154
|
+
} catch {
|
|
6155
|
+
}
|
|
6156
|
+
if (nextEnabled && isSupported() && Notification.permission === "default") {
|
|
6157
|
+
await requestPermission();
|
|
6158
|
+
try {
|
|
6159
|
+
localStorage.setItem(ASKED_KEY, "true");
|
|
6160
|
+
} catch {
|
|
6161
|
+
}
|
|
6162
|
+
}
|
|
6163
|
+
}, [requestPermission]);
|
|
6164
|
+
const maybeNotifyAssistantMessage = useCallback((payload) => {
|
|
6165
|
+
if (!enabled || !isSupported()) return;
|
|
6166
|
+
if (document.hidden !== true) return;
|
|
6167
|
+
if (Notification.permission !== "granted") return;
|
|
6168
|
+
const now = Date.now();
|
|
6169
|
+
if (now - lastNotificationAtRef.current < NOTIFICATION_DEBOUNCE_MS) return;
|
|
6170
|
+
lastNotificationAtRef.current = now;
|
|
6171
|
+
const body = payload.text.slice(0, 100);
|
|
6172
|
+
const notification = new Notification("OpenCami", {
|
|
6173
|
+
body,
|
|
6174
|
+
icon: "/pwa-192x192.png"
|
|
6175
|
+
});
|
|
6176
|
+
notification.onclick = () => {
|
|
6177
|
+
window.focus();
|
|
6178
|
+
void navigate({ to: "/chat/$sessionKey", params: { sessionKey: payload.sessionFriendlyId } });
|
|
6179
|
+
notification.close();
|
|
6180
|
+
};
|
|
6181
|
+
}, [enabled, navigate]);
|
|
6182
|
+
useEffect(() => {
|
|
6183
|
+
if (!isSupported()) return;
|
|
6184
|
+
const onFirstInteraction = () => {
|
|
6185
|
+
if (Notification.permission !== "default") return;
|
|
6186
|
+
let asked = false;
|
|
6187
|
+
try {
|
|
6188
|
+
asked = localStorage.getItem(ASKED_KEY) === "true";
|
|
6189
|
+
} catch {
|
|
6190
|
+
asked = false;
|
|
6191
|
+
}
|
|
6192
|
+
if (asked) return;
|
|
6193
|
+
void requestPermission().finally(() => {
|
|
6194
|
+
try {
|
|
6195
|
+
localStorage.setItem(ASKED_KEY, "true");
|
|
6196
|
+
} catch {
|
|
6197
|
+
}
|
|
6198
|
+
});
|
|
6199
|
+
};
|
|
6200
|
+
const options = { once: true, passive: true };
|
|
6201
|
+
window.addEventListener("pointerdown", onFirstInteraction, options);
|
|
6202
|
+
window.addEventListener("keydown", onFirstInteraction, { once: true });
|
|
6203
|
+
return () => {
|
|
6204
|
+
window.removeEventListener("pointerdown", onFirstInteraction);
|
|
6205
|
+
window.removeEventListener("keydown", onFirstInteraction);
|
|
6206
|
+
};
|
|
6207
|
+
}, [requestPermission]);
|
|
6208
|
+
useEffect(() => {
|
|
6209
|
+
const sync = () => setEnabledState(getNotificationsEnabled());
|
|
6210
|
+
window.addEventListener("storage", sync);
|
|
6211
|
+
return () => window.removeEventListener("storage", sync);
|
|
6212
|
+
}, []);
|
|
6213
|
+
return {
|
|
6214
|
+
notificationsEnabled: enabled,
|
|
6215
|
+
setNotificationsEnabled: setEnabled,
|
|
6216
|
+
requestNotificationPermission: requestPermission,
|
|
6217
|
+
maybeNotifyAssistantMessage
|
|
6218
|
+
};
|
|
6219
|
+
}
|
|
6624
6220
|
const KeyboardShortcutsDialog = lazy(
|
|
6625
|
-
() => import("./keyboard-shortcuts-dialog-
|
|
6221
|
+
() => import("./keyboard-shortcuts-dialog-Cr6fOqHz.js").then((m) => ({
|
|
6626
6222
|
default: m.KeyboardShortcutsDialog
|
|
6627
6223
|
}))
|
|
6628
6224
|
);
|
|
6629
6225
|
const SearchDialog = lazy(
|
|
6630
|
-
() => import("./search-dialog-
|
|
6226
|
+
() => import("./search-dialog-DZTS5SEi.js").then((m) => ({
|
|
6631
6227
|
default: m.SearchDialog
|
|
6632
6228
|
}))
|
|
6633
6229
|
);
|
|
@@ -6657,6 +6253,7 @@ function ChatScreen({
|
|
|
6657
6253
|
const [searchJumpMessageId, setSearchJumpMessageId] = useState(null);
|
|
6658
6254
|
const [isStreaming, setIsStreaming] = useState(false);
|
|
6659
6255
|
const thinkingLevel = useThinkingLevelStore((state) => state.level);
|
|
6256
|
+
const { maybeNotifyAssistantMessage } = useNotifications();
|
|
6660
6257
|
const inputRef = useRef(null);
|
|
6661
6258
|
const streamTimer = useRef(null);
|
|
6662
6259
|
const streamIdleTimer = useRef(null);
|
|
@@ -6664,6 +6261,7 @@ function ChatScreen({
|
|
|
6664
6261
|
const refreshHistoryRef = useRef(() => {
|
|
6665
6262
|
});
|
|
6666
6263
|
const pendingStartRef = useRef(false);
|
|
6264
|
+
const streamingNotificationTextRef = useRef("");
|
|
6667
6265
|
const { isMobile } = useChatMobile(queryClient);
|
|
6668
6266
|
const {
|
|
6669
6267
|
sessionsQuery,
|
|
@@ -6780,7 +6378,14 @@ function ChatScreen({
|
|
|
6780
6378
|
});
|
|
6781
6379
|
const { streaming, startStream, stopStream } = useStreaming({
|
|
6782
6380
|
onDone: (sk) => handleStreamDoneRef.current(sk),
|
|
6783
|
-
onError: (err) => handleStreamErrorRef.current(err)
|
|
6381
|
+
onError: (err) => handleStreamErrorRef.current(err),
|
|
6382
|
+
onAssistantDelta: ({ text }) => {
|
|
6383
|
+
streamingNotificationTextRef.current += text;
|
|
6384
|
+
maybeNotifyAssistantMessage({
|
|
6385
|
+
text: streamingNotificationTextRef.current,
|
|
6386
|
+
sessionFriendlyId: activeFriendlyId
|
|
6387
|
+
});
|
|
6388
|
+
}
|
|
6784
6389
|
});
|
|
6785
6390
|
handleStreamDoneRef.current = useCallback(
|
|
6786
6391
|
async (_sk) => {
|
|
@@ -6818,12 +6423,15 @@ function ChatScreen({
|
|
|
6818
6423
|
}, [streaming.text, streaming.tools]);
|
|
6819
6424
|
const messagesWithStreaming = useMemo(() => {
|
|
6820
6425
|
if (!streamingMessage) return displayMessages;
|
|
6821
|
-
const
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6426
|
+
const lastAssistantIndex = [...displayMessages].map((message, index) => ({ message, index })).filter(({ message }) => message.role === "assistant").map(({ index }) => index).pop();
|
|
6427
|
+
const lastUserIndex = [...displayMessages].map((message, index) => ({ message, index })).filter(({ message }) => message.role === "user").map(({ index }) => index).pop();
|
|
6428
|
+
const assistantIsLatestTurn = typeof lastAssistantIndex === "number" && (typeof lastUserIndex !== "number" || lastAssistantIndex > lastUserIndex);
|
|
6429
|
+
if (assistantIsLatestTurn && typeof lastAssistantIndex === "number") {
|
|
6430
|
+
const historyAssistant = displayMessages[lastAssistantIndex];
|
|
6431
|
+
const historyText = textFromMessage(historyAssistant);
|
|
6432
|
+
if (historyText.length >= streaming.text.length) return displayMessages;
|
|
6825
6433
|
const msgs = [...displayMessages];
|
|
6826
|
-
msgs[
|
|
6434
|
+
msgs[lastAssistantIndex] = streamingMessage;
|
|
6827
6435
|
return msgs;
|
|
6828
6436
|
}
|
|
6829
6437
|
return [...displayMessages, streamingMessage];
|
|
@@ -7080,6 +6688,7 @@ function ChatScreen({
|
|
|
7080
6688
|
mimeType: a.file.type,
|
|
7081
6689
|
content: a.base64
|
|
7082
6690
|
}));
|
|
6691
|
+
streamingNotificationTextRef.current = "";
|
|
7083
6692
|
startStream(sessionKey);
|
|
7084
6693
|
streamStart();
|
|
7085
6694
|
fetch("/api/send", {
|