mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-04 22:02:16 +02:00
chore: linting
Some checks failed
Obsidian Plugin Lint / lint (push) Has been cancelled
Some checks failed
Obsidian Plugin Lint / lint (push) Has been cancelled
This commit is contained in:
parent
f607636ba6
commit
8d50f90060
74 changed files with 1135 additions and 693 deletions
|
|
@ -123,9 +123,9 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
|
|||
handleSkipIndexing,
|
||||
handleStartEdit,
|
||||
handleSaveConnector,
|
||||
handleDisconnectConnector,
|
||||
handleDisconnectFromList,
|
||||
handleBackFromEdit,
|
||||
handleDisconnectConnector,
|
||||
handleDisconnectFromList,
|
||||
handleBackFromEdit,
|
||||
handleBackFromConnect,
|
||||
handleBackFromYouTube,
|
||||
handleViewAccountsList,
|
||||
|
|
@ -226,27 +226,31 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
|
|||
{isYouTubeView && searchSpaceId ? (
|
||||
<YouTubeCrawlerView searchSpaceId={searchSpaceId} onBack={handleBackFromYouTube} />
|
||||
) : viewingMCPList ? (
|
||||
<ConnectorAccountsListView
|
||||
connectorType="MCP_CONNECTOR"
|
||||
connectorTitle="MCP Connectors"
|
||||
connectors={(allConnectors || []) as SearchSourceConnector[]}
|
||||
indexingConnectorIds={indexingConnectorIds}
|
||||
onBack={handleBackFromMCPList}
|
||||
onManage={handleStartEdit}
|
||||
onDisconnect={(connector) => handleDisconnectFromList(connector, () => refreshConnectors())}
|
||||
onAddAccount={handleAddNewMCPFromList}
|
||||
addButtonText="Add New MCP Server"
|
||||
/>
|
||||
<ConnectorAccountsListView
|
||||
connectorType="MCP_CONNECTOR"
|
||||
connectorTitle="MCP Connectors"
|
||||
connectors={(allConnectors || []) as SearchSourceConnector[]}
|
||||
indexingConnectorIds={indexingConnectorIds}
|
||||
onBack={handleBackFromMCPList}
|
||||
onManage={handleStartEdit}
|
||||
onDisconnect={(connector) =>
|
||||
handleDisconnectFromList(connector, () => refreshConnectors())
|
||||
}
|
||||
onAddAccount={handleAddNewMCPFromList}
|
||||
addButtonText="Add New MCP Server"
|
||||
/>
|
||||
) : viewingAccountsType ? (
|
||||
<ConnectorAccountsListView
|
||||
connectorType={viewingAccountsType.connectorType}
|
||||
connectorTitle={viewingAccountsType.connectorTitle}
|
||||
connectors={(connectors || []) as SearchSourceConnector[]}
|
||||
indexingConnectorIds={indexingConnectorIds}
|
||||
onBack={handleBackFromAccountsList}
|
||||
onManage={handleStartEdit}
|
||||
onDisconnect={(connector) => handleDisconnectFromList(connector, () => refreshConnectors())}
|
||||
onAddAccount={() => {
|
||||
<ConnectorAccountsListView
|
||||
connectorType={viewingAccountsType.connectorType}
|
||||
connectorTitle={viewingAccountsType.connectorTitle}
|
||||
connectors={(connectors || []) as SearchSourceConnector[]}
|
||||
indexingConnectorIds={indexingConnectorIds}
|
||||
onBack={handleBackFromAccountsList}
|
||||
onManage={handleStartEdit}
|
||||
onDisconnect={(connector) =>
|
||||
handleDisconnectFromList(connector, () => refreshConnectors())
|
||||
}
|
||||
onAddAccount={() => {
|
||||
// Check both OAUTH_CONNECTORS and COMPOSIO_CONNECTORS
|
||||
const oauthConnector =
|
||||
OAUTH_CONNECTORS.find(
|
||||
|
|
|
|||
|
|
@ -213,13 +213,13 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
|
|||
className="w-full h-8 text-[13px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80"
|
||||
>
|
||||
{isTesting ? (
|
||||
<>
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
Testing Connection...
|
||||
</>
|
||||
) : (
|
||||
"Test Connection"
|
||||
)}
|
||||
<>
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
Testing Connection...
|
||||
</>
|
||||
) : (
|
||||
"Test Connection"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -218,13 +218,13 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
|
|||
className="w-full h-8 text-[13px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80"
|
||||
>
|
||||
{isTesting ? (
|
||||
<>
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
Testing Connection...
|
||||
</>
|
||||
) : (
|
||||
"Test Connection"
|
||||
)}
|
||||
<>
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
Testing Connection...
|
||||
</>
|
||||
) : (
|
||||
"Test Connection"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ export const TeamsConfig: FC<TeamsConfigProps> = () => {
|
|||
<div className="text-xs sm:text-sm">
|
||||
<p className="font-medium text-xs sm:text-sm">Microsoft Teams Access</p>
|
||||
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
|
||||
Your agent can search and read messages from Teams channels you have access to,
|
||||
and send messages on your behalf. Make sure you're a member of the teams
|
||||
you want to interact with.
|
||||
Your agent can search and read messages from Teams channels you have access to, and send
|
||||
messages on your behalf. Make sure you're a member of the teams you want to interact
|
||||
with.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { DateRangeSelector } from "../../components/date-range-selector";
|
|||
import { PeriodicSyncConfig } from "../../components/periodic-sync-config";
|
||||
import { SummaryConfig } from "../../components/summary-config";
|
||||
import { VisionLLMConfig } from "../../components/vision-llm-config";
|
||||
import { LIVE_CONNECTOR_TYPES, getReauthEndpoint } from "../../constants/connector-constants";
|
||||
import { getReauthEndpoint, LIVE_CONNECTOR_TYPES } from "../../constants/connector-constants";
|
||||
import { getConnectorDisplayName } from "../../tabs/all-connectors-tab";
|
||||
import { MCPServiceConfig } from "../components/mcp-service-config";
|
||||
import { getConnectorConfigComponent } from "../index";
|
||||
|
|
@ -314,8 +314,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
|
||||
{connector.is_indexable &&
|
||||
(() => {
|
||||
const isGoogleDrive =
|
||||
connector.connector_type === "GOOGLE_DRIVE_CONNECTOR";
|
||||
const isGoogleDrive = connector.connector_type === "GOOGLE_DRIVE_CONNECTOR";
|
||||
const isComposioGoogleDrive =
|
||||
connector.connector_type === "COMPOSIO_GOOGLE_DRIVE_CONNECTOR";
|
||||
const requiresFolderSelection = isGoogleDrive || isComposioGoogleDrive;
|
||||
|
|
@ -327,8 +326,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
(connector.config?.selected_files as
|
||||
| Array<{ id: string; name: string }>
|
||||
| undefined) || [];
|
||||
const hasItemsSelected =
|
||||
selectedFolders.length > 0 || selectedFiles.length > 0;
|
||||
const hasItemsSelected = selectedFolders.length > 0 || selectedFiles.length > 0;
|
||||
const isDisabled = requiresFolderSelection && !hasItemsSelected;
|
||||
|
||||
return (
|
||||
|
|
@ -380,8 +378,8 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
|
||||
{/* Fixed Footer - Action buttons */}
|
||||
<div className="flex-shrink-0 flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3 sm:gap-0 px-6 sm:px-12 py-6 sm:py-6 bg-muted border-t border-border">
|
||||
{showDisconnectConfirm ? (
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 flex-1 sm:flex-initial">
|
||||
{showDisconnectConfirm ? (
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 flex-1 sm:flex-initial">
|
||||
<span className="text-xs sm:text-sm text-muted-foreground sm:whitespace-nowrap">
|
||||
{isLive
|
||||
? "Your agent will lose access to this service."
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ import { DateRangeSelector } from "../../components/date-range-selector";
|
|||
import { PeriodicSyncConfig } from "../../components/periodic-sync-config";
|
||||
import { SummaryConfig } from "../../components/summary-config";
|
||||
import { VisionLLMConfig } from "../../components/vision-llm-config";
|
||||
import { LIVE_CONNECTOR_TYPES, type IndexingConfigState } from "../../constants/connector-constants";
|
||||
import {
|
||||
type IndexingConfigState,
|
||||
LIVE_CONNECTOR_TYPES,
|
||||
} from "../../constants/connector-constants";
|
||||
import { getConnectorDisplayName } from "../../tabs/all-connectors-tab";
|
||||
import { getConnectorConfigComponent } from "../index";
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@ import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
|||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||
import { getDocumentTypeLabel } from "@/lib/documents/document-type-labels";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { COMPOSIO_CONNECTORS, LIVE_CONNECTOR_TYPES, OAUTH_CONNECTORS } from "../constants/connector-constants";
|
||||
import {
|
||||
COMPOSIO_CONNECTORS,
|
||||
LIVE_CONNECTOR_TYPES,
|
||||
OAUTH_CONNECTORS,
|
||||
} from "../constants/connector-constants";
|
||||
import { getDocumentCountForConnector } from "../utils/connector-document-mapping";
|
||||
import { getConnectorDisplayName } from "./all-connectors-tab";
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
|||
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||
import { formatRelativeDate } from "@/lib/format-date";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { LIVE_CONNECTOR_TYPES, getReauthEndpoint } from "../constants/connector-constants";
|
||||
import { getReauthEndpoint, LIVE_CONNECTOR_TYPES } from "../constants/connector-constants";
|
||||
import { useConnectorStatus } from "../hooks/use-connector-status";
|
||||
import { getConnectorDisplayName } from "../tabs/all-connectors-tab";
|
||||
|
||||
|
|
@ -182,11 +182,14 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{typeConnectors.map((connector) => {
|
||||
const isIndexing = indexingConnectorIds.has(connector.id);
|
||||
const connectorReauthEndpoint = getReauthEndpoint(connector);
|
||||
const isAuthExpired = !!connectorReauthEndpoint && connector.config?.auth_expired === true;
|
||||
const isLive = LIVE_CONNECTOR_TYPES.has(connector.connector_type) || Boolean(connector.config?.server_config);
|
||||
{typeConnectors.map((connector) => {
|
||||
const isIndexing = indexingConnectorIds.has(connector.id);
|
||||
const connectorReauthEndpoint = getReauthEndpoint(connector);
|
||||
const isAuthExpired =
|
||||
!!connectorReauthEndpoint && connector.config?.auth_expired === true;
|
||||
const isLive =
|
||||
LIVE_CONNECTOR_TYPES.has(connector.connector_type) ||
|
||||
Boolean(connector.config?.server_config);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -225,73 +228,73 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
{isAuthExpired ? (
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-amber-600 hover:bg-amber-700 text-white border-0 shadow-xs shrink-0"
|
||||
onClick={() => handleReauth(connector)}
|
||||
disabled={reauthingId === connector.id}
|
||||
>
|
||||
<RefreshCw
|
||||
className={cn("size-3.5", reauthingId === connector.id && "animate-spin")}
|
||||
/>
|
||||
Re-authenticate
|
||||
</Button>
|
||||
) : isLive && onDisconnect ? (
|
||||
confirmDisconnectId === connector.id ? (
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
{isAuthExpired ? (
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-amber-600 hover:bg-amber-700 text-white border-0 shadow-xs shrink-0"
|
||||
onClick={() => handleReauth(connector)}
|
||||
disabled={reauthingId === connector.id}
|
||||
>
|
||||
<RefreshCw
|
||||
className={cn("size-3.5", reauthingId === connector.id && "animate-spin")}
|
||||
/>
|
||||
Re-authenticate
|
||||
</Button>
|
||||
) : isLive && onDisconnect ? (
|
||||
confirmDisconnectId === connector.id ? (
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium shadow-xs"
|
||||
onClick={async () => {
|
||||
setDisconnectingId(connector.id);
|
||||
setConfirmDisconnectId(null);
|
||||
try {
|
||||
await onDisconnect(connector);
|
||||
} finally {
|
||||
setDisconnectingId(null);
|
||||
}
|
||||
}}
|
||||
disabled={disconnectingId === connector.id}
|
||||
>
|
||||
{disconnectingId === connector.id ? (
|
||||
<RefreshCw className="size-3.5 animate-spin" />
|
||||
) : (
|
||||
"Confirm"
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-2 rounded-lg"
|
||||
onClick={() => setConfirmDisconnectId(null)}
|
||||
disabled={disconnectingId === connector.id}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
variant="destructive"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium shadow-xs"
|
||||
onClick={async () => {
|
||||
setDisconnectingId(connector.id);
|
||||
setConfirmDisconnectId(null);
|
||||
try {
|
||||
await onDisconnect(connector);
|
||||
} finally {
|
||||
setDisconnectingId(null);
|
||||
}
|
||||
}}
|
||||
disabled={disconnectingId === connector.id}
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-red-50 hover:text-red-700 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-red-950 dark:hover:text-red-400 shrink-0"
|
||||
onClick={() => setConfirmDisconnectId(connector.id)}
|
||||
>
|
||||
{disconnectingId === connector.id ? (
|
||||
<RefreshCw className="size-3.5 animate-spin" />
|
||||
) : (
|
||||
"Confirm"
|
||||
)}
|
||||
<Trash2 className="size-3.5" />
|
||||
Disconnect
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-2 rounded-lg"
|
||||
onClick={() => setConfirmDisconnectId(null)}
|
||||
disabled={disconnectingId === connector.id}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-red-50 hover:text-red-700 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-red-950 dark:hover:text-red-400 shrink-0"
|
||||
onClick={() => setConfirmDisconnectId(connector.id)}
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80 shrink-0"
|
||||
onClick={() => onManage(connector)}
|
||||
>
|
||||
<Trash2 className="size-3.5" />
|
||||
Disconnect
|
||||
Manage
|
||||
</Button>
|
||||
)
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80 shrink-0"
|
||||
onClick={() => onManage(connector)}
|
||||
>
|
||||
Manage
|
||||
</Button>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import { openEditorPanelAtom } from "@/atoms/editor/editor-panel.atom";
|
|||
import { ImagePreview, ImageRoot, ImageZoom } from "@/components/assistant-ui/image";
|
||||
import "katex/dist/katex.min.css";
|
||||
import { InlineCitation, UrlCitation } from "@/components/assistant-ui/inline-citation";
|
||||
import { useElectronAPI } from "@/hooks/use-platform";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import {
|
||||
Table,
|
||||
|
|
@ -30,6 +29,7 @@ import {
|
|||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { useElectronAPI } from "@/hooks/use-platform";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function MarkdownCodeBlockSkeleton() {
|
||||
|
|
@ -493,10 +493,7 @@ const defaultComponents = memoizeMarkdownComponents({
|
|||
const mounts = (await electronAPI.getAgentFilesystemMounts(
|
||||
resolvedSearchSpaceId
|
||||
)) as AgentFilesystemMount[];
|
||||
resolvedLocalPath = normalizeLocalVirtualPathForEditor(
|
||||
inlineValue,
|
||||
mounts
|
||||
);
|
||||
resolvedLocalPath = normalizeLocalVirtualPathForEditor(inlineValue, mounts);
|
||||
} catch {
|
||||
// Fall back to the raw inline path if mount lookup fails.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -248,7 +248,15 @@ export function EditorPanelContent({
|
|||
|
||||
doFetch().catch(() => {});
|
||||
return () => controller.abort();
|
||||
}, [documentId, electronAPI, isLocalFileMode, localFilePath, resolveLocalVirtualPath, searchSpaceId, title]);
|
||||
}, [
|
||||
documentId,
|
||||
electronAPI,
|
||||
isLocalFileMode,
|
||||
localFilePath,
|
||||
resolveLocalVirtualPath,
|
||||
searchSpaceId,
|
||||
title,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
|
@ -282,69 +290,77 @@ export function EditorPanelContent({
|
|||
}
|
||||
}, [editorDoc?.source_markdown]);
|
||||
|
||||
const handleSave = useCallback(async (_options?: { silent?: boolean }) => {
|
||||
setSaving(true);
|
||||
try {
|
||||
if (isLocalFileMode) {
|
||||
if (!localFilePath) {
|
||||
throw new Error("Missing local file path");
|
||||
const handleSave = useCallback(
|
||||
async (_options?: { silent?: boolean }) => {
|
||||
setSaving(true);
|
||||
try {
|
||||
if (isLocalFileMode) {
|
||||
if (!localFilePath) {
|
||||
throw new Error("Missing local file path");
|
||||
}
|
||||
if (!electronAPI?.writeAgentLocalFileText) {
|
||||
throw new Error("Local file editor is available only in desktop mode.");
|
||||
}
|
||||
const resolvedLocalPath = await resolveLocalVirtualPath(localFilePath);
|
||||
const contentToSave = markdownRef.current;
|
||||
const writeResult = await electronAPI.writeAgentLocalFileText(
|
||||
resolvedLocalPath,
|
||||
contentToSave,
|
||||
searchSpaceId
|
||||
);
|
||||
if (!writeResult.ok) {
|
||||
throw new Error(writeResult.error || "Failed to save local file");
|
||||
}
|
||||
setEditorDoc((prev) => (prev ? { ...prev, source_markdown: contentToSave } : prev));
|
||||
setEditedMarkdown(markdownRef.current === contentToSave ? null : markdownRef.current);
|
||||
return true;
|
||||
}
|
||||
if (!electronAPI?.writeAgentLocalFileText) {
|
||||
throw new Error("Local file editor is available only in desktop mode.");
|
||||
if (!searchSpaceId || !documentId) {
|
||||
throw new Error("Missing document context");
|
||||
}
|
||||
const resolvedLocalPath = await resolveLocalVirtualPath(localFilePath);
|
||||
const contentToSave = markdownRef.current;
|
||||
const writeResult = await electronAPI.writeAgentLocalFileText(
|
||||
resolvedLocalPath,
|
||||
contentToSave,
|
||||
searchSpaceId
|
||||
const token = getBearerToken();
|
||||
if (!token) {
|
||||
toast.error("Please login to save");
|
||||
redirectToLogin();
|
||||
return;
|
||||
}
|
||||
const response = await authenticatedFetch(
|
||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/save`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ source_markdown: markdownRef.current }),
|
||||
}
|
||||
);
|
||||
if (!writeResult.ok) {
|
||||
throw new Error(writeResult.error || "Failed to save local file");
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response
|
||||
.json()
|
||||
.catch(() => ({ detail: "Failed to save document" }));
|
||||
throw new Error(errorData.detail || "Failed to save document");
|
||||
}
|
||||
setEditorDoc((prev) =>
|
||||
prev ? { ...prev, source_markdown: contentToSave } : prev
|
||||
);
|
||||
setEditedMarkdown(markdownRef.current === contentToSave ? null : markdownRef.current);
|
||||
|
||||
setEditorDoc((prev) => (prev ? { ...prev, source_markdown: markdownRef.current } : prev));
|
||||
setEditedMarkdown(null);
|
||||
toast.success("Document saved! Reindexing in background...");
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("Error saving document:", err);
|
||||
toast.error(err instanceof Error ? err.message : "Failed to save document");
|
||||
return false;
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
if (!searchSpaceId || !documentId) {
|
||||
throw new Error("Missing document context");
|
||||
}
|
||||
const token = getBearerToken();
|
||||
if (!token) {
|
||||
toast.error("Please login to save");
|
||||
redirectToLogin();
|
||||
return;
|
||||
}
|
||||
const response = await authenticatedFetch(
|
||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/save`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ source_markdown: markdownRef.current }),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response
|
||||
.json()
|
||||
.catch(() => ({ detail: "Failed to save document" }));
|
||||
throw new Error(errorData.detail || "Failed to save document");
|
||||
}
|
||||
|
||||
setEditorDoc((prev) => (prev ? { ...prev, source_markdown: markdownRef.current } : prev));
|
||||
setEditedMarkdown(null);
|
||||
toast.success("Document saved! Reindexing in background...");
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("Error saving document:", err);
|
||||
toast.error(err instanceof Error ? err.message : "Failed to save document");
|
||||
return false;
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}, [documentId, electronAPI, isLocalFileMode, localFilePath, resolveLocalVirtualPath, searchSpaceId]);
|
||||
},
|
||||
[
|
||||
documentId,
|
||||
electronAPI,
|
||||
isLocalFileMode,
|
||||
localFilePath,
|
||||
resolveLocalVirtualPath,
|
||||
searchSpaceId,
|
||||
]
|
||||
);
|
||||
|
||||
const isEditableType = editorDoc
|
||||
? (editorRenderMode === "source_code" ||
|
||||
|
|
@ -594,9 +610,7 @@ export function EditorPanelContent({
|
|||
}
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
|
|
@ -626,7 +640,7 @@ export function EditorPanelContent({
|
|||
</div>
|
||||
) : isEditableType ? (
|
||||
<PlateEditor
|
||||
key={`${isLocalFileMode ? localFilePath ?? "local-file" : documentId}-${isEditing ? "editing" : "viewing"}`}
|
||||
key={`${isLocalFileMode ? (localFilePath ?? "local-file") : documentId}-${isEditing ? "editing" : "viewing"}`}
|
||||
preset="full"
|
||||
markdown={editorDoc.source_markdown}
|
||||
onMarkdownChange={handleMarkdownChange}
|
||||
|
|
@ -746,7 +760,8 @@ export function MobileEditorPanel() {
|
|||
? !!panelState.documentId && !!panelState.searchSpaceId
|
||||
: !!panelState.localFilePath;
|
||||
|
||||
if (isDesktop || !panelState.isOpen || !hasTarget || panelState.kind === "local_file") return null;
|
||||
if (isDesktop || !panelState.isOpen || !hasTarget || panelState.kind === "local_file")
|
||||
return null;
|
||||
|
||||
return <MobileEditorDrawer />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { createPlatePlugin } from "platejs/react";
|
||||
import { useEditorReadOnly } from "platejs/react";
|
||||
import { createPlatePlugin, useEditorReadOnly } from "platejs/react";
|
||||
|
||||
import { useEditorSave } from "@/components/editor/editor-save-context";
|
||||
import { FixedToolbar } from "@/components/ui/fixed-toolbar";
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
|
||||
const MonacoEditor = dynamic(() => import("@monaco-editor/react"), {
|
||||
|
|
|
|||
|
|
@ -72,9 +72,7 @@ export function RightPanelExpandButton() {
|
|||
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;
|
||||
|
||||
|
|
@ -116,9 +114,7 @@ export function RightPanel({ documentsPanel }: RightPanelProps) {
|
|||
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;
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ 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 { anonymousChatApiService } from "@/lib/apis/anonymous-chat-api.service";
|
||||
import { documentsApiService } from "@/lib/apis/documents-api.service";
|
||||
import { foldersApiService } from "@/lib/apis/folders-api.service";
|
||||
|
|
@ -208,7 +208,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;
|
||||
|
|
@ -250,10 +251,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]
|
||||
|
|
@ -282,10 +286,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]
|
||||
|
|
@ -293,19 +300,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]
|
||||
|
|
@ -552,7 +565,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;
|
||||
|
|
@ -582,7 +597,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;
|
||||
|
|
@ -1015,7 +1032,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");
|
||||
|
|
@ -1331,8 +1349,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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import {
|
|||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronUp,
|
||||
Pencil,
|
||||
ImageIcon,
|
||||
Layers,
|
||||
Pencil,
|
||||
Plus,
|
||||
ScanEye,
|
||||
Search,
|
||||
|
|
@ -741,9 +741,7 @@ export function ModelSelector({
|
|||
<div
|
||||
className={cn(
|
||||
"shrink-0 border-border/50 flex relative",
|
||||
isMobile
|
||||
? "flex-row items-center border-b border-border/40"
|
||||
: "flex-col w-10 border-r"
|
||||
isMobile ? "flex-row items-center border-b border-border/40" : "flex-col w-10 border-r"
|
||||
)}
|
||||
>
|
||||
{!isMobile && (
|
||||
|
|
@ -769,9 +767,7 @@ export function ModelSelector({
|
|||
<div
|
||||
className={cn(
|
||||
"absolute left-0 top-0 bottom-0 z-10 w-5 flex items-center justify-center transition-all duration-200 ease-out pointer-events-none",
|
||||
sidebarScrollPos === "top"
|
||||
? "opacity-0 -translate-x-1"
|
||||
: "opacity-100 translate-x-0"
|
||||
sidebarScrollPos === "top" ? "opacity-0 -translate-x-1" : "opacity-100 translate-x-0"
|
||||
)}
|
||||
>
|
||||
<ChevronLeft className="size-3 text-muted-foreground" />
|
||||
|
|
|
|||
|
|
@ -398,7 +398,8 @@ export function ReportPanelContent({
|
|||
</Button>
|
||||
);
|
||||
|
||||
const editingActions = showReportEditingTier &&
|
||||
const editingActions =
|
||||
showReportEditingTier &&
|
||||
!isReadOnly &&
|
||||
(isEditing ? (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useAtomValue } from "jotai";
|
||||
import {
|
||||
AlertCircle,
|
||||
Dot,
|
||||
FileText,
|
||||
Info,
|
||||
Pencil,
|
||||
RefreshCw,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { AlertCircle, Dot, FileText, Info, Pencil, RefreshCw, Trash2 } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { membersAtom, myAccessAtom } from "@/atoms/members/members-query.atoms";
|
||||
import { deleteNewLLMConfigMutationAtom } from "@/atoms/new-llm-config/new-llm-config-mutation.atoms";
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@ import { useAtomValue } from "jotai";
|
|||
import {
|
||||
Bot,
|
||||
ChevronRight,
|
||||
ScanEye,
|
||||
Pencil,
|
||||
FileText,
|
||||
Earth,
|
||||
FileText,
|
||||
Image,
|
||||
Logs,
|
||||
type LucideIcon,
|
||||
|
|
@ -16,11 +14,13 @@ import {
|
|||
MessageSquare,
|
||||
Mic,
|
||||
MoreHorizontal,
|
||||
Unplug,
|
||||
Pencil,
|
||||
ScanEye,
|
||||
Settings,
|
||||
Shield,
|
||||
SlidersHorizontal,
|
||||
Trash2,
|
||||
Unplug,
|
||||
Users,
|
||||
Video,
|
||||
} from "lucide-react";
|
||||
|
|
@ -462,9 +462,19 @@ function RolesContent({
|
|||
|
||||
return (
|
||||
<div key={role.id} className="rounded-lg border border-border/60 overflow-hidden">
|
||||
{/* biome-ignore lint/a11y/useSemanticElements: row contains nested interactive elements (DropdownMenu); using a <button> would produce invalid nested-button markup */}
|
||||
<div
|
||||
className="flex items-center gap-4 p-4 transition-colors hover:bg-muted/30 cursor-pointer"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-expanded={isExpanded}
|
||||
className="flex items-center gap-4 p-4 transition-colors hover:bg-muted/30 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onClick={() => setExpandedRoleId(isExpanded ? null : role.id)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
setExpandedRoleId(isExpanded ? null : role.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex-1 min-w-0 text-left">
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -682,9 +692,19 @@ function PermissionsEditor({
|
|||
|
||||
return (
|
||||
<div key={category} className="rounded-lg border border-border/60 overflow-hidden">
|
||||
{/* biome-ignore lint/a11y/useSemanticElements: row contains a nested interactive Checkbox; using a <button> would produce invalid nested-button markup */}
|
||||
<div
|
||||
className="flex items-center justify-between px-3 py-2.5 hover:bg-muted/40 transition-colors cursor-pointer"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-expanded={isExpanded}
|
||||
className="flex items-center justify-between px-3 py-2.5 hover:bg-muted/40 transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onClick={() => toggleCategoryExpanded(category)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
toggleCategoryExpanded(category);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex-1 flex items-center gap-2.5">
|
||||
<IconComponent className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,16 @@
|
|||
"use client";
|
||||
|
||||
import { useAtom } from "jotai";
|
||||
import { Brain, CircleUser, Globe, Keyboard, KeyRound, Monitor, ReceiptText, Sparkles } from "lucide-react";
|
||||
import {
|
||||
Brain,
|
||||
CircleUser,
|
||||
Globe,
|
||||
Keyboard,
|
||||
KeyRound,
|
||||
Monitor,
|
||||
ReceiptText,
|
||||
Sparkles,
|
||||
} from "lucide-react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useMemo } from "react";
|
||||
|
|
@ -53,9 +62,9 @@ const DesktopContent = dynamic(
|
|||
);
|
||||
const DesktopShortcutsContent = dynamic(
|
||||
() =>
|
||||
import("@/app/dashboard/[search_space_id]/user-settings/components/DesktopShortcutsContent").then(
|
||||
(m) => ({ default: m.DesktopShortcutsContent })
|
||||
),
|
||||
import(
|
||||
"@/app/dashboard/[search_space_id]/user-settings/components/DesktopShortcutsContent"
|
||||
).then((m) => ({ default: m.DesktopShortcutsContent })),
|
||||
{ ssr: false }
|
||||
);
|
||||
const MemoryContent = dynamic(
|
||||
|
|
|
|||
|
|
@ -118,7 +118,9 @@ function GenericApprovalCard({
|
|||
setProcessing();
|
||||
onDecision({ type: "approve" });
|
||||
connectorsApiService.trustMCPTool(mcpConnectorId, toolName).catch(() => {
|
||||
toast.error("Failed to save 'Always Allow' preference. The tool will still require approval next time.");
|
||||
toast.error(
|
||||
"Failed to save 'Always Allow' preference. The tool will still require approval next time."
|
||||
);
|
||||
});
|
||||
}, [phase, setProcessing, onDecision, isMCPTool, mcpConnectorId, toolName]);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,14 @@
|
|||
|
||||
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { ClockIcon, CornerDownLeftIcon, GlobeIcon, MapPinIcon, Pencil, UsersIcon } from "lucide-react";
|
||||
import {
|
||||
ClockIcon,
|
||||
CornerDownLeftIcon,
|
||||
GlobeIcon,
|
||||
MapPinIcon,
|
||||
Pencil,
|
||||
UsersIcon,
|
||||
} from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import type { ExtraField } from "@/atoms/chat/hitl-edit-panel.atom";
|
||||
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue