opencami 1.5.1 → 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-BBLAL_m_.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/composite-DBl8R3ae.js +1 -0
- package/dist/client/assets/{connect-DHr3hhUR.js → connect-NYvOqiBJ.js} +1 -1
- 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/{index-B2iG4EM1.js → index-CMATW8VA.js} +1 -1
- package/dist/client/assets/{index-GTR-Xzl2.js → index-rOIRO-8E.js} +1 -1
- package/dist/client/assets/keyboard-shortcuts-dialog-BTGWdJMl.js +1 -0
- package/dist/client/assets/{main-mIHr_ble.js → main-B_dlfHME.js} +9 -9
- package/dist/client/assets/markdown-BVzT7z4x.js +87 -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-CRIdKbbZ.js → opencami-logo-BQQETnJG.js} +1 -1
- package/dist/client/assets/owner-CpRnf1fI.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/{react-Cfq4ot0g.js → react-irH8OzhB.js} +1 -1
- 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/skills-YZe3I63y.js +2 -0
- package/dist/client/assets/{skills-panel-Cv-N_MDk.js → skills-panel-WDUfIwnI.js} +2 -2
- package/dist/client/assets/styles-Bwo-K6Y4.css +1 -0
- package/dist/client/assets/{switch-Bh9tVOYh.js → switch-DPocNFRG.js} +1 -1
- package/dist/client/assets/tabs-B0cro1hL.js +1 -0
- package/dist/client/assets/tooltip-Dg9fy-vT.js +1 -0
- package/dist/client/assets/use-file-explorer-state-DzT0bksg.js +12 -0
- package/dist/client/assets/{useButton-DsMdJPGn.js → useButton-Cbl_9oFG.js} +1 -1
- package/dist/client/assets/useCompositeItem-BDAzTxVe.js +1 -0
- package/dist/client/assets/{useControlled-wOKVgKF4.js → useControlled-Dscz_s4f.js} +1 -1
- package/dist/client/assets/{useMutation-fJnleJAb.js → useMutation-B1FlDsNN.js} +1 -1
- package/dist/client/assets/visuallyHidden-ONmQ-0U2.js +1 -0
- package/dist/server/assets/{_sessionKey-B5UHBd2U.js → _sessionKey-B0ZlLAjH.js} +172 -567
- package/dist/server/assets/_tanstack-start-manifest_v-D5UVTs1o.js +4 -0
- 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--_jH_0mX.js → index-CiUjUD0t.js} +1 -1
- 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-DJA7GtMo.js → router-Uuagl6O7.js} +55 -45
- package/dist/server/assets/{search-dialog-C2a3OYm_.js → search-dialog-DZTS5SEi.js} +6 -4
- package/dist/server/assets/{session-export-dialog-CwclV0Aj.js → session-export-dialog-C53RRAah.js} +1 -2
- package/dist/server/assets/{settings-dialog-CHVzrou9.js → settings-dialog-CSYDj2qm.js} +75 -20
- 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 +1 -1
- package/dist/client/assets/DirectionContext-DXnZc0zz.js +0 -1
- package/dist/client/assets/_sessionKey-BidmO1-D.js +0 -100
- package/dist/client/assets/agents-CtZs_u1j.js +0 -2
- package/dist/client/assets/agents-screen-Basce5qo.js +0 -1
- package/dist/client/assets/bots-C_dWjy3z.js +0 -2
- package/dist/client/assets/bots-screen-n_xhYOEE.js +0 -1
- package/dist/client/assets/button-BaHefIXU.js +0 -1
- package/dist/client/assets/file-explorer-screen-8t6M4Xco.js +0 -1
- package/dist/client/assets/files-BdlpK3Cy.js +0 -2
- package/dist/client/assets/keyboard-shortcuts-dialog-CcKSlK52.js +0 -1
- package/dist/client/assets/search-dialog-D19x_xaG.js +0 -1
- package/dist/client/assets/session-export-dialog-DRlJwhMa.js +0 -1
- package/dist/client/assets/settings-dialog-BA5FjiyP.js +0 -1
- package/dist/client/assets/skills-lmNPZksG.js +0 -2
- package/dist/client/assets/styles-JgjN_ZCd.css +0 -1
- package/dist/client/assets/tabs-BfaEc9zS.js +0 -1
- package/dist/client/assets/tooltip-w9D-e_R-.js +0 -1
- package/dist/client/assets/use-file-explorer-state-CLaDuI9X.js +0 -12
- package/dist/client/assets/useCompositeItem-CaYygSfB.js +0 -1
- package/dist/client/assets/visuallyHidden-CqGRL_Oq.js +0 -9
- package/dist/server/assets/_tanstack-start-manifest_v-D11xMFUx.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-Dl2BOKP7.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
|
}
|
|
@@ -6406,8 +5902,10 @@ function useStreaming(options) {
|
|
|
6406
5902
|
const eventSourceRef = useRef(null);
|
|
6407
5903
|
const onDoneRef = useRef(options.onDone);
|
|
6408
5904
|
const onErrorRef = useRef(options.onError);
|
|
5905
|
+
const onAssistantDeltaRef = useRef(options.onAssistantDelta);
|
|
6409
5906
|
onDoneRef.current = options.onDone;
|
|
6410
5907
|
onErrorRef.current = options.onError;
|
|
5908
|
+
onAssistantDeltaRef.current = options.onAssistantDelta;
|
|
6411
5909
|
const stop = useCallback((options2) => {
|
|
6412
5910
|
if (eventSourceRef.current) {
|
|
6413
5911
|
eventSourceRef.current.close();
|
|
@@ -6440,6 +5938,7 @@ function useStreaming(options) {
|
|
|
6440
5938
|
...prev,
|
|
6441
5939
|
text: prev.text + data.text
|
|
6442
5940
|
}));
|
|
5941
|
+
onAssistantDeltaRef.current?.({ text: data.text, sessionKey: data.sessionKey });
|
|
6443
5942
|
} catch {
|
|
6444
5943
|
}
|
|
6445
5944
|
});
|
|
@@ -6622,13 +6121,109 @@ function useSwipeGesture(options) {
|
|
|
6622
6121
|
onTouchEnd: handleTouchEnd
|
|
6623
6122
|
};
|
|
6624
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
|
+
}
|
|
6625
6220
|
const KeyboardShortcutsDialog = lazy(
|
|
6626
|
-
() => import("./keyboard-shortcuts-dialog-
|
|
6221
|
+
() => import("./keyboard-shortcuts-dialog-Cr6fOqHz.js").then((m) => ({
|
|
6627
6222
|
default: m.KeyboardShortcutsDialog
|
|
6628
6223
|
}))
|
|
6629
6224
|
);
|
|
6630
6225
|
const SearchDialog = lazy(
|
|
6631
|
-
() => import("./search-dialog-
|
|
6226
|
+
() => import("./search-dialog-DZTS5SEi.js").then((m) => ({
|
|
6632
6227
|
default: m.SearchDialog
|
|
6633
6228
|
}))
|
|
6634
6229
|
);
|
|
@@ -6658,6 +6253,7 @@ function ChatScreen({
|
|
|
6658
6253
|
const [searchJumpMessageId, setSearchJumpMessageId] = useState(null);
|
|
6659
6254
|
const [isStreaming, setIsStreaming] = useState(false);
|
|
6660
6255
|
const thinkingLevel = useThinkingLevelStore((state) => state.level);
|
|
6256
|
+
const { maybeNotifyAssistantMessage } = useNotifications();
|
|
6661
6257
|
const inputRef = useRef(null);
|
|
6662
6258
|
const streamTimer = useRef(null);
|
|
6663
6259
|
const streamIdleTimer = useRef(null);
|
|
@@ -6665,6 +6261,7 @@ function ChatScreen({
|
|
|
6665
6261
|
const refreshHistoryRef = useRef(() => {
|
|
6666
6262
|
});
|
|
6667
6263
|
const pendingStartRef = useRef(false);
|
|
6264
|
+
const streamingNotificationTextRef = useRef("");
|
|
6668
6265
|
const { isMobile } = useChatMobile(queryClient);
|
|
6669
6266
|
const {
|
|
6670
6267
|
sessionsQuery,
|
|
@@ -6781,7 +6378,14 @@ function ChatScreen({
|
|
|
6781
6378
|
});
|
|
6782
6379
|
const { streaming, startStream, stopStream } = useStreaming({
|
|
6783
6380
|
onDone: (sk) => handleStreamDoneRef.current(sk),
|
|
6784
|
-
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
|
+
}
|
|
6785
6389
|
});
|
|
6786
6390
|
handleStreamDoneRef.current = useCallback(
|
|
6787
6391
|
async (_sk) => {
|
|
@@ -7084,6 +6688,7 @@ function ChatScreen({
|
|
|
7084
6688
|
mimeType: a.file.type,
|
|
7085
6689
|
content: a.base64
|
|
7086
6690
|
}));
|
|
6691
|
+
streamingNotificationTextRef.current = "";
|
|
7087
6692
|
startStream(sessionKey);
|
|
7088
6693
|
streamStart();
|
|
7089
6694
|
fetch("/api/send", {
|