Merge remote-tracking branch 'upstream/dev' into feat/ui-revamp

This commit is contained in:
Anish Sarkar 2026-04-29 12:43:30 +05:30
commit 4f3914b058
302 changed files with 22318 additions and 6067 deletions

View file

@ -5,6 +5,7 @@ import { usePathname } from "next/navigation";
import { currentThreadAtom } from "@/atoms/chat/current-thread.atom";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeTabAtom } from "@/atoms/tabs/tabs.atom";
import { ActionLogButton } from "@/components/agent-action-log/action-log-button";
import { ChatHeader } from "@/components/new-chat/chat-header";
import { ChatShareButton } from "@/components/new-chat/chat-share-button";
import type { ChatVisibility, ThreadRecord } from "@/lib/chat/thread-persistence";
@ -64,6 +65,7 @@ export function Header({ mobileMenuTrigger }: HeaderProps) {
{/* Right side - Actions */}
<div className="ml-auto flex items-center gap-2">
{hasThread && <ActionLogButton threadId={currentThreadState.id} />}
{hasThread && (
<ChatShareButton thread={threadForButton} onVisibilityChange={handleVisibilityChange} />
)}

View file

@ -6,6 +6,7 @@ import dynamic from "next/dynamic";
import { startTransition, useEffect } from "react";
import { closeHitlEditPanelAtom, hitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { closeReportPanelAtom, reportPanelAtom } from "@/atoms/chat/report-panel.atom";
import { citationPanelAtom, closeCitationPanelAtom } from "@/atoms/citation/citation-panel.atom";
import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms";
import { closeEditorPanelAtom, editorPanelAtom } from "@/atoms/editor/editor-panel.atom";
import { rightPanelCollapsedAtom, rightPanelTabAtom } from "@/atoms/layout/right-panel.atom";
@ -21,6 +22,14 @@ const EditorPanelContent = dynamic(
{ ssr: false, loading: () => null }
);
const CitationPanelContent = dynamic(
() =>
import("@/components/citation-panel/citation-panel").then((m) => ({
default: m.CitationPanelContent,
})),
{ ssr: false, loading: () => null }
);
const HitlEditPanelContent = dynamic(
() =>
import("@/components/hitl-edit-panel/hitl-edit-panel").then((m) => ({
@ -74,14 +83,14 @@ export function RightPanelExpandButton() {
const reportState = useAtomValue(reportPanelAtom);
const editorState = useAtomValue(editorPanelAtom);
const hitlEditState = useAtomValue(hitlEditPanelAtom);
const citationState = useAtomValue(citationPanelAtom);
const reportOpen = reportState.isOpen && !!reportState.reportId;
const editorOpen =
editorState.isOpen &&
(editorState.kind === "document"
? !!editorState.documentId
: !!editorState.localFilePath);
(editorState.kind === "document" ? !!editorState.documentId : !!editorState.localFilePath);
const hitlEditOpen = hitlEditState.isOpen && !!hitlEditState.onSave;
const hasContent = documentsOpen || reportOpen || editorOpen || hitlEditOpen;
const citationOpen = citationState.isOpen && citationState.chunkId != null;
const hasContent = documentsOpen || reportOpen || editorOpen || hitlEditOpen || citationOpen;
if (!collapsed || !hasContent) return null;
@ -105,7 +114,13 @@ export function RightPanelExpandButton() {
);
}
const PANEL_WIDTHS = { sources: 420, report: 640, editor: 640, "hitl-edit": 640 } as const;
const PANEL_WIDTHS = {
sources: 420,
report: 640,
editor: 640,
"hitl-edit": 640,
citation: 560,
} as const;
export function RightPanel({ documentsPanel }: RightPanelProps) {
const [activeTab] = useAtom(rightPanelTabAtom);
@ -115,47 +130,69 @@ export function RightPanel({ documentsPanel }: RightPanelProps) {
const closeEditor = useSetAtom(closeEditorPanelAtom);
const hitlEditState = useAtomValue(hitlEditPanelAtom);
const closeHitlEdit = useSetAtom(closeHitlEditPanelAtom);
const citationState = useAtomValue(citationPanelAtom);
const closeCitation = useSetAtom(closeCitationPanelAtom);
const [collapsed, setCollapsed] = useAtom(rightPanelCollapsedAtom);
const documentsOpen = documentsPanel?.open ?? false;
const reportOpen = reportState.isOpen && !!reportState.reportId;
const editorOpen =
editorState.isOpen &&
(editorState.kind === "document"
? !!editorState.documentId
: !!editorState.localFilePath);
(editorState.kind === "document" ? !!editorState.documentId : !!editorState.localFilePath);
const hitlEditOpen = hitlEditState.isOpen && !!hitlEditState.onSave;
const citationOpen = citationState.isOpen && citationState.chunkId != null;
useEffect(() => {
if (!reportOpen && !editorOpen && !hitlEditOpen) return;
if (!reportOpen && !editorOpen && !hitlEditOpen && !citationOpen) return;
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
if (hitlEditOpen) closeHitlEdit();
else if (citationOpen) closeCitation();
else if (editorOpen) closeEditor();
else if (reportOpen) closeReport();
}
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [reportOpen, editorOpen, hitlEditOpen, closeReport, closeEditor, closeHitlEdit]);
}, [
reportOpen,
editorOpen,
hitlEditOpen,
citationOpen,
closeReport,
closeEditor,
closeHitlEdit,
closeCitation,
]);
const isVisible = (documentsOpen || reportOpen || editorOpen || hitlEditOpen) && !collapsed;
const isVisible =
(documentsOpen || reportOpen || editorOpen || hitlEditOpen || citationOpen) && !collapsed;
let effectiveTab = activeTab;
if (effectiveTab === "hitl-edit" && !hitlEditOpen) {
effectiveTab = editorOpen ? "editor" : reportOpen ? "report" : "sources";
} else if (effectiveTab === "editor" && !editorOpen) {
effectiveTab = reportOpen ? "report" : "sources";
} else if (effectiveTab === "report" && !reportOpen) {
effectiveTab = editorOpen ? "editor" : "sources";
} else if (effectiveTab === "sources" && !documentsOpen) {
effectiveTab = hitlEditOpen
? "hitl-edit"
effectiveTab = citationOpen
? "citation"
: editorOpen
? "editor"
: reportOpen
? "report"
: "sources";
} else if (effectiveTab === "citation" && !citationOpen) {
effectiveTab = editorOpen ? "editor" : reportOpen ? "report" : "sources";
} else if (effectiveTab === "editor" && !editorOpen) {
effectiveTab = citationOpen ? "citation" : reportOpen ? "report" : "sources";
} else if (effectiveTab === "report" && !reportOpen) {
effectiveTab = citationOpen ? "citation" : editorOpen ? "editor" : "sources";
} else if (effectiveTab === "sources" && !documentsOpen) {
effectiveTab = hitlEditOpen
? "hitl-edit"
: citationOpen
? "citation"
: editorOpen
? "editor"
: reportOpen
? "report"
: "sources";
}
const targetWidth = PANEL_WIDTHS[effectiveTab];
@ -214,6 +251,11 @@ export function RightPanel({ documentsPanel }: RightPanelProps) {
/>
</div>
)}
{effectiveTab === "citation" && citationOpen && citationState.chunkId != null && (
<div className="h-full flex flex-col">
<CitationPanelContent chunkId={citationState.chunkId} onClose={closeCitation} />
</div>
)}
</div>
</aside>
);

View file

@ -1,11 +1,9 @@
"use client";
import { Folder, FolderPlus, Search, X } from "lucide-react";
import { useAtom } from "jotai";
import { Folder, FolderPlus, Search, X } from "lucide-react";
import { useCallback, useMemo, useRef, useState } from "react";
import { localExpandedFolderKeysAtom } from "@/atoms/documents/folder.atoms";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import {
DropdownMenu,
DropdownMenuContent,
@ -14,6 +12,8 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { useDebouncedValue } from "@/hooks/use-debounced-value";
import { LocalFilesystemBrowser } from "./LocalFilesystemBrowser";

View file

@ -24,7 +24,7 @@ import type React from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import {
sidebarSelectedDocumentsAtom,
mentionedDocumentsAtom,
} from "@/atoms/chat/mentioned-documents.atom";
import { connectorDialogOpenAtom } from "@/atoms/connector-dialog/connector-dialog.atoms";
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
@ -73,7 +73,8 @@ import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import type { DocumentTypeEnum } from "@/contracts/types/document.types";
import { useDebouncedValue } from "@/hooks/use-debounced-value";
import { useMediaQuery } from "@/hooks/use-media-query";
import { usePlatform, useElectronAPI } from "@/hooks/use-platform";
import { useElectronAPI, usePlatform } from "@/hooks/use-platform";
import { getMentionDocKey } from "@/lib/chat/mention-doc-key";
import { anonymousChatApiService } from "@/lib/apis/anonymous-chat-api.service";
import { documentsApiService } from "@/lib/apis/documents-api.service";
import { foldersApiService } from "@/lib/apis/folders-api.service";
@ -210,7 +211,8 @@ function AuthenticatedDocumentsSidebarBase({
const [watchedFolderIds, setWatchedFolderIds] = useState<Set<number>>(new Set());
const [folderWatchOpen, setFolderWatchOpen] = useAtom(folderWatchDialogOpenAtom);
const [watchInitialFolder, setWatchInitialFolder] = useAtom(folderWatchInitialFolderAtom);
const isElectron = desktopFeaturesEnabled && typeof window !== "undefined" && !!window.electronAPI;
const isElectron =
desktopFeaturesEnabled && typeof window !== "undefined" && !!window.electronAPI;
useEffect(() => {
if (!electronAPI?.getAgentFilesystemSettings) return;
@ -252,10 +254,13 @@ function AuthenticatedDocumentsSidebarBase({
.filter((rootPath, index, allPaths) => allPaths.indexOf(rootPath) === index)
.slice(0, MAX_LOCAL_FILESYSTEM_ROOTS);
if (nextLocalRootPaths.length === localRootPaths.length) return;
const updated = await electronAPI.setAgentFilesystemSettings({
mode: "desktop_local_folder",
localRootPaths: nextLocalRootPaths,
}, searchSpaceId);
const updated = await electronAPI.setAgentFilesystemSettings(
{
mode: "desktop_local_folder",
localRootPaths: nextLocalRootPaths,
},
searchSpaceId
);
setFilesystemSettings(updated);
},
[electronAPI, localRootPaths, searchSpaceId]
@ -284,10 +289,13 @@ function AuthenticatedDocumentsSidebarBase({
const handleRemoveFilesystemRoot = useCallback(
async (rootPathToRemove: string) => {
if (!electronAPI?.setAgentFilesystemSettings) return;
const updated = await electronAPI.setAgentFilesystemSettings({
mode: "desktop_local_folder",
localRootPaths: localRootPaths.filter((rootPath) => rootPath !== rootPathToRemove),
}, searchSpaceId);
const updated = await electronAPI.setAgentFilesystemSettings(
{
mode: "desktop_local_folder",
localRootPaths: localRootPaths.filter((rootPath) => rootPath !== rootPathToRemove),
},
searchSpaceId
);
setFilesystemSettings(updated);
},
[electronAPI, localRootPaths, searchSpaceId]
@ -295,19 +303,25 @@ function AuthenticatedDocumentsSidebarBase({
const handleClearFilesystemRoots = useCallback(async () => {
if (!electronAPI?.setAgentFilesystemSettings) return;
const updated = await electronAPI.setAgentFilesystemSettings({
mode: "desktop_local_folder",
localRootPaths: [],
}, searchSpaceId);
const updated = await electronAPI.setAgentFilesystemSettings(
{
mode: "desktop_local_folder",
localRootPaths: [],
},
searchSpaceId
);
setFilesystemSettings(updated);
}, [electronAPI, searchSpaceId]);
const handleFilesystemTabChange = useCallback(
async (tab: "cloud" | "local") => {
if (!electronAPI?.setAgentFilesystemSettings) return;
const updated = await electronAPI.setAgentFilesystemSettings({
mode: tab === "cloud" ? "cloud" : "desktop_local_folder",
}, searchSpaceId);
const updated = await electronAPI.setAgentFilesystemSettings(
{
mode: tab === "cloud" ? "cloud" : "desktop_local_folder",
},
searchSpaceId
);
setFilesystemSettings(updated);
},
[electronAPI, searchSpaceId]
@ -414,8 +428,11 @@ function AuthenticatedDocumentsSidebarBase({
}, [refreshWatchedIds]);
const { mutateAsync: deleteDocumentMutation } = useAtomValue(deleteDocumentMutationAtom);
const [sidebarDocs, setSidebarDocs] = useAtom(sidebarSelectedDocumentsAtom);
const mentionedDocIds = useMemo(() => new Set(sidebarDocs.map((d) => d.id)), [sidebarDocs]);
const [sidebarDocs, setSidebarDocs] = useAtom(mentionedDocumentsAtom);
const mentionedDocKeys = useMemo(
() => new Set(sidebarDocs.map((d) => getMentionDocKey(d))),
[sidebarDocs]
);
// Folder state
const [expandedFolderMap, setExpandedFolderMap] = useAtom(expandedFolderIdsAtom);
@ -554,7 +571,9 @@ function AuthenticatedDocumentsSidebarBase({
if (!electronAPI) return;
const watchedFolders = (await electronAPI.getWatchedFolders()) as WatchedFolderEntry[];
const matched = watchedFolders.find((wf: WatchedFolderEntry) => wf.rootFolderId === folder.id);
const matched = watchedFolders.find(
(wf: WatchedFolderEntry) => wf.rootFolderId === folder.id
);
if (!matched) {
toast.error("This folder is not being watched");
return;
@ -584,7 +603,9 @@ function AuthenticatedDocumentsSidebarBase({
if (!electronAPI) return;
const watchedFolders = (await electronAPI.getWatchedFolders()) as WatchedFolderEntry[];
const matched = watchedFolders.find((wf: WatchedFolderEntry) => wf.rootFolderId === folder.id);
const matched = watchedFolders.find(
(wf: WatchedFolderEntry) => wf.rootFolderId === folder.id
);
if (!matched) {
toast.error("This folder is not being watched");
return;
@ -859,12 +880,12 @@ function AuthenticatedDocumentsSidebarBase({
const handleToggleChatMention = useCallback(
(doc: { id: number; title: string; document_type: string }, isMentioned: boolean) => {
const key = `${doc.document_type}:${doc.id}`;
const key = getMentionDocKey(doc);
if (isMentioned) {
setSidebarDocs((prev) => prev.filter((d) => `${d.document_type}:${d.id}` !== key));
setSidebarDocs((prev) => prev.filter((d) => getMentionDocKey(d) !== key));
} else {
setSidebarDocs((prev) => {
if (prev.some((d) => `${d.document_type}:${d.id}` === key)) return prev;
if (prev.some((d) => getMentionDocKey(d) === key)) return prev;
return [
...prev,
{ id: doc.id, title: doc.title, document_type: doc.document_type as DocumentTypeEnum },
@ -895,9 +916,9 @@ function AuthenticatedDocumentsSidebarBase({
if (selectAll) {
setSidebarDocs((prev) => {
const existingDocKeys = new Set(prev.map((d) => `${d.document_type}:${d.id}`));
const existingDocKeys = new Set(prev.map((d) => getMentionDocKey(d)));
const newDocs = subtreeDocs
.filter((d) => !existingDocKeys.has(`${d.document_type}:${d.id}`))
.filter((d) => !existingDocKeys.has(getMentionDocKey(d)))
.map((d) => ({
id: d.id,
title: d.title,
@ -906,10 +927,8 @@ function AuthenticatedDocumentsSidebarBase({
return newDocs.length > 0 ? [...prev, ...newDocs] : prev;
});
} else {
const keysToRemove = new Set(subtreeDocs.map((d) => `${d.document_type}:${d.id}`));
setSidebarDocs((prev) =>
prev.filter((d) => !keysToRemove.has(`${d.document_type}:${d.id}`))
);
const keysToRemove = new Set(subtreeDocs.map((d) => getMentionDocKey(d)));
setSidebarDocs((prev) => prev.filter((d) => !keysToRemove.has(getMentionDocKey(d))));
}
},
[treeDocuments, foldersByParent, setSidebarDocs]
@ -1020,7 +1039,8 @@ function AuthenticatedDocumentsSidebarBase({
}, [open, onOpenChange, isMobile, setRightPanelCollapsed]);
const showFilesystemTabs = !isMobile && !!electronAPI && !!filesystemSettings;
const currentFilesystemTab = filesystemSettings?.mode === "desktop_local_folder" ? "local" : "cloud";
const currentFilesystemTab =
filesystemSettings?.mode === "desktop_local_folder" ? "local" : "cloud";
const showCloudSkeleton =
currentFilesystemTab === "cloud" &&
(zeroFoldersResult.type !== "complete" || zeroAllDocsResult.type !== "complete");
@ -1144,7 +1164,7 @@ function AuthenticatedDocumentsSidebarBase({
documents={searchFilteredDocuments}
expandedIds={expandedIds}
onToggleExpand={toggleFolderExpand}
mentionedDocIds={mentionedDocIds}
mentionedDocKeys={mentionedDocKeys}
onToggleChatMention={handleToggleChatMention}
onToggleFolderSelect={handleToggleFolderSelect}
onRenameFolder={handleRenameFolder}
@ -1336,8 +1356,8 @@ function AuthenticatedDocumentsSidebarBase({
<AlertDialogHeader>
<AlertDialogTitle>Trust this workspace?</AlertDialogTitle>
<AlertDialogDescription>
Local mode can read and edit files inside the folders you select. Continue only if
you trust this workspace and its contents.
Local mode can read and edit files inside the folders you select. Continue only if you
trust this workspace and its contents.
</AlertDialogDescription>
{pendingLocalPath && (
<AlertDialogDescription className="mt-1 whitespace-pre-wrap break-words font-mono text-xs">
@ -1572,17 +1592,20 @@ function AnonymousDocumentsSidebar({
const [isUploading, setIsUploading] = useState(false);
const [search, setSearch] = useState("");
const [sidebarDocs, setSidebarDocs] = useAtom(sidebarSelectedDocumentsAtom);
const mentionedDocIds = useMemo(() => new Set(sidebarDocs.map((d) => d.id)), [sidebarDocs]);
const [sidebarDocs, setSidebarDocs] = useAtom(mentionedDocumentsAtom);
const mentionedDocKeys = useMemo(
() => new Set(sidebarDocs.map((d) => getMentionDocKey(d))),
[sidebarDocs]
);
const handleToggleChatMention = useCallback(
(doc: { id: number; title: string; document_type: string }, isMentioned: boolean) => {
const key = `${doc.document_type}:${doc.id}`;
const key = getMentionDocKey(doc);
if (isMentioned) {
setSidebarDocs((prev) => prev.filter((d) => `${d.document_type}:${d.id}` !== key));
setSidebarDocs((prev) => prev.filter((d) => getMentionDocKey(d) !== key));
} else {
setSidebarDocs((prev) => {
if (prev.some((d) => `${d.document_type}:${d.id}` === key)) return prev;
if (prev.some((d) => getMentionDocKey(d) === key)) return prev;
return [
...prev,
{ id: doc.id, title: doc.title, document_type: doc.document_type as DocumentTypeEnum },
@ -1802,7 +1825,7 @@ function AnonymousDocumentsSidebar({
documents={searchFilteredDocs}
expandedIds={new Set()}
onToggleExpand={() => {}}
mentionedDocIds={mentionedDocIds}
mentionedDocKeys={mentionedDocKeys}
onToggleChatMention={handleToggleChatMention}
onToggleFolderSelect={() => {}}
onRenameFolder={() => gate("rename folders")}

View file

@ -141,7 +141,9 @@ export function LocalFilesystemBrowser({
}: LocalFilesystemBrowserProps) {
const electronAPI = useElectronAPI();
const [rootStateMap, setRootStateMap] = useState<Record<string, RootLoadState>>({});
const [internalExpandedFolderKeys, setInternalExpandedFolderKeys] = useState<Set<string>>(new Set());
const [internalExpandedFolderKeys, setInternalExpandedFolderKeys] = useState<Set<string>>(
new Set()
);
const [mountByRootKey, setMountByRootKey] = useState<Map<string, string>>(new Map());
const [mountStatus, setMountStatus] = useState<MountLoadStatus>("idle");
const [mountRefreshInFlight, setMountRefreshInFlight] = useState(false);
@ -188,10 +190,7 @@ export function LocalFilesystemBrowser({
}
for (const { rootKey } of rootsToReload) {
const nonce = reloadNonceByRoot[rootKey] ?? 0;
lastLoadedSignatureByRootRef.current.set(
rootKey,
`${searchSpaceId}:${rootKey}:${nonce}`
);
lastLoadedSignatureByRootRef.current.set(rootKey, `${searchSpaceId}:${rootKey}:${nonce}`);
}
let cancelled = false;
@ -257,35 +256,37 @@ export function LocalFilesystemBrowser({
return;
}
const unsubscribe = electronAPI.onAgentFilesystemTreeDirty((event: {
searchSpaceId: number | null;
reason: "watcher_event" | "safety_poll";
rootPath: string;
changedPath: string | null;
timestamp: number;
}) => {
if ((event.searchSpaceId ?? null) !== (searchSpaceId ?? null)) {
return;
const unsubscribe = electronAPI.onAgentFilesystemTreeDirty(
(event: {
searchSpaceId: number | null;
reason: "watcher_event" | "safety_poll";
rootPath: string;
changedPath: string | null;
timestamp: number;
}) => {
if ((event.searchSpaceId ?? null) !== (searchSpaceId ?? null)) {
return;
}
const eventRootKey = normalizeRootPathForLookup(event.rootPath, isWindowsPlatform);
const knownRootKeys = new Set(
rootPaths.map((rootPath) => normalizeRootPathForLookup(rootPath, isWindowsPlatform))
);
if (!knownRootKeys.has(eventRootKey)) {
setReloadNonceByRoot((prev) => {
const next = { ...prev };
for (const rootKey of knownRootKeys) {
next[rootKey] = (prev[rootKey] ?? 0) + 1;
}
return next;
});
return;
}
setReloadNonceByRoot((prev) => ({
...prev,
[eventRootKey]: (prev[eventRootKey] ?? 0) + 1,
}));
}
const eventRootKey = normalizeRootPathForLookup(event.rootPath, isWindowsPlatform);
const knownRootKeys = new Set(
rootPaths.map((rootPath) => normalizeRootPathForLookup(rootPath, isWindowsPlatform))
);
if (!knownRootKeys.has(eventRootKey)) {
setReloadNonceByRoot((prev) => {
const next = { ...prev };
for (const rootKey of knownRootKeys) {
next[rootKey] = (prev[rootKey] ?? 0) + 1;
}
return next;
});
return;
}
setReloadNonceByRoot((prev) => ({
...prev,
[eventRootKey]: (prev[eventRootKey] ?? 0) + 1,
}));
});
);
void electronAPI.startAgentFilesystemTreeWatch({
searchSpaceId,
rootPaths,
@ -378,22 +379,25 @@ export function LocalFilesystemBrowser({
});
}, [rootPaths, rootStateMap, searchQuery]);
const toggleFolder = useCallback((folderKey: string) => {
const update = (prev: Set<string>) => {
const next = new Set(prev);
if (next.has(folderKey)) {
next.delete(folderKey);
} else {
next.add(folderKey);
const toggleFolder = useCallback(
(folderKey: string) => {
const update = (prev: Set<string>) => {
const next = new Set(prev);
if (next.has(folderKey)) {
next.delete(folderKey);
} else {
next.add(folderKey);
}
return next;
};
if (onExpandedFolderKeysChange) {
onExpandedFolderKeysChange(update(effectiveExpandedFolderKeys));
return;
}
return next;
};
if (onExpandedFolderKeysChange) {
onExpandedFolderKeysChange(update(effectiveExpandedFolderKeys));
return;
}
setInternalExpandedFolderKeys(update);
}, [effectiveExpandedFolderKeys, onExpandedFolderKeysChange]);
setInternalExpandedFolderKeys(update);
},
[effectiveExpandedFolderKeys, onExpandedFolderKeysChange]
);
const renderFolder = useCallback(
(folder: LocalFolderNode, depth: number, mount: string) => {
@ -436,9 +440,7 @@ export function LocalFilesystemBrowser({
: undefined
}
className={`flex h-8 w-full items-center gap-1.5 rounded-md px-2 text-left text-sm transition-colors ${
isOpenable
? "hover:bg-muted/60"
: "cursor-not-allowed opacity-60"
isOpenable ? "hover:bg-muted/60" : "cursor-not-allowed opacity-60"
}`}
style={{ paddingInlineStart: `${(depth + 1) * 12 + 22}px` }}
title={
@ -528,7 +530,10 @@ export function LocalFilesystemBrowser({
}
if (state.error) {
return (
<div key={rootPath} className="rounded-md border border-destructive/20 bg-destructive/5 p-3">
<div
key={rootPath}
className="rounded-md border border-destructive/20 bg-destructive/5 p-3"
>
<p className="text-sm font-medium text-destructive">Failed to load local folder</p>
<p className="mt-1 text-xs text-muted-foreground">{state.error}</p>
</div>

View file

@ -308,9 +308,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
}
}}
>
<span
className={`flex items-center gap-1.5 ${downloading ? "opacity-0" : ""}`}
>
<span className={`flex items-center gap-1.5 ${downloading ? "opacity-0" : ""}`}>
<Download className="size-3.5" />
Download .md
</span>