From a28962a4294ca8aa9b0e0a0091a7db83c5464277 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 19 Jan 2026 22:16:10 +0530 Subject: [PATCH 1/4] fix: the ui overlap --- surfsense_web/components/assistant-ui/thread.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 9229e25be..18f45f810 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -96,7 +96,7 @@ export const Thread: FC = ({ messageThinkingSteps = new Map(), head }} /> - + !thread.isEmpty}>
From a16cc50466b4ddbb77e3bcaf5fd14bfe08d234e7 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 19 Jan 2026 22:20:09 +0530 Subject: [PATCH 2/4] feat: enhance loading indicator in ThinkingStepsDisplay --- surfsense_web/components/assistant-ui/thinking-steps.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/surfsense_web/components/assistant-ui/thinking-steps.tsx b/surfsense_web/components/assistant-ui/thinking-steps.tsx index ce6fef6f4..47e2a8f56 100644 --- a/surfsense_web/components/assistant-ui/thinking-steps.tsx +++ b/surfsense_web/components/assistant-ui/thinking-steps.tsx @@ -108,7 +108,10 @@ export const ThinkingStepsDisplay: FC<{ steps: ThinkingStep[]; isThreadRunning?: {/* Step dot - on top of line */}
{effectiveStatus === "in_progress" ? ( - + + + + ) : ( )} From af0349e36cc938a0026b757188906328b07f1a7b Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 20 Jan 2026 03:30:12 +0530 Subject: [PATCH 3/4] chore: ran frontend linting --- .../assistant-ui/connector-popup.tsx | 6 +- .../components/mcp-connect-form.tsx | 32 ++--- .../components/mcp-config.tsx | 30 ++-- .../views/connector-connect-view.tsx | 11 +- .../views/connector-edit-view.tsx | 3 +- .../hooks/use-connector-dialog.ts | 130 +++++++++--------- .../tabs/active-connectors-tab.tsx | 7 +- .../tabs/all-connectors-tab.tsx | 2 - .../utils/mcp-config-validator.ts | 42 +++--- .../views/mcp-connector-list-view.tsx | 19 +-- .../components/assistant-ui/thread.tsx | 2 +- .../contracts/types/notification.types.ts | 6 +- .../lib/apis/connectors-api.service.ts | 10 +- 13 files changed, 147 insertions(+), 153 deletions(-) diff --git a/surfsense_web/components/assistant-ui/connector-popup.tsx b/surfsense_web/components/assistant-ui/connector-popup.tsx index 90113707e..b946c153f 100644 --- a/surfsense_web/components/assistant-ui/connector-popup.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup.tsx @@ -102,7 +102,8 @@ export const ConnectorIndicator: FC = () => { // Fallback to API if Electric is not available or fails // Use Electric data if: 1) we have data, or 2) still loading without error // Use API data if: Electric failed (has error) or finished loading with no data - const useElectricData = connectorsFromElectric.length > 0 || (connectorsLoading && !connectorsError); + const useElectricData = + connectorsFromElectric.length > 0 || (connectorsLoading && !connectorsError); const connectors = useElectricData ? connectorsFromElectric : allConnectors || []; // Manual refresh function that works with both Electric and API @@ -130,7 +131,7 @@ export const ConnectorIndicator: FC = () => { const hasConnectors = connectors.length > 0; const hasSources = hasConnectors || activeDocumentTypes.length > 0; const totalSourceCount = connectors.length + activeDocumentTypes.length; - + const activeConnectorsCount = connectors.length; // Check which connectors are already connected @@ -229,7 +230,6 @@ export const ConnectorIndicator: FC = () => { isDisconnecting={isDisconnecting} isIndexing={indexingConnectorIds.has(editingConnector.id)} searchSpaceId={searchSpaceId?.toString()} - onStartDateChange={setStartDate} onEndDateChange={setEndDate} onPeriodicEnabledChange={setPeriodicEnabled} diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/mcp-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/mcp-connect-form.tsx index a671c91e8..b0d5fba5a 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/mcp-connect-form.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/mcp-connect-form.tsx @@ -51,12 +51,12 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) const handleConfigChange = (value: string) => { setConfigJson(value); - + // Clear previous error if (jsonError) { setJsonError(null); } - + // Validate immediately to show errors as user types (with debouncing via parseMCPConfig cache) if (value.trim()) { const result = parseMCPConfig(value); @@ -122,11 +122,12 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) return (
- - - Connect to an MCP (Model Context Protocol) server. Each MCP server is added as a separate connector. - - + + + Connect to an MCP (Model Context Protocol) server. Each MCP server is added as a separate + connector. + +
@@ -140,11 +141,10 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) rows={16} className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`} /> - {jsonError && ( -

JSON Error: {jsonError}

- )} + {jsonError &&

JSON Error: {jsonError}

}

- Paste a single MCP server configuration. Must include: name, command, args (optional), env (optional), transport (optional). + Paste a single MCP server configuration. Must include: name, command, args (optional), + env (optional), transport (optional).

@@ -176,7 +176,9 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting })
- {testResult.status === "success" ? "Connection Successful" : "Connection Failed"} + {testResult.status === "success" + ? "Connection Successful" + : "Connection Failed"} {testResult.tools.length > 0 && (
@@ -192,7 +186,9 @@ export const MCPConfig: FC = ({ connector, onConfigChange, onNam
- {testResult.status === "success" ? "Connection Successful" : "Connection Failed"} + {testResult.status === "success" + ? "Connection Successful" + : "Connection Failed"} {testResult.tools.length > 0 && (
diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx index 515a3a47b..89c36ffc2 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx @@ -151,7 +151,7 @@ export const ConnectorEditView: FC = ({

- {connector.connector_type === "MCP_CONNECTOR" ? "MCP Server" : connector.name} + {connector.connector_type === "MCP_CONNECTOR" ? "MCP Server" : connector.name}

Manage your connector settings and sync configuration @@ -200,7 +200,6 @@ export const ConnectorEditView: FC = ({ onConfigChange={onConfigChange} onNameChange={onNameChange} searchSpaceId={searchSpaceId} - /> )} diff --git a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts index 7ac0d3e0f..7a2243705 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts @@ -540,18 +540,18 @@ export const useConnectorDialog = () => { data: { ...connectorData, connector_type: connectorData.connector_type as EnumConnectorName, - is_active: true, - next_scheduled_at: connectorData.next_scheduled_at as string | null, - }, - queryParams: { - search_space_id: searchSpaceId, - }, - }); + is_active: true, + next_scheduled_at: connectorData.next_scheduled_at as string | null, + }, + queryParams: { + search_space_id: searchSpaceId, + }, + }); - // Refetch connectors to get the new one - const result = await refetchAllConnectors(); - if (result.data) { - const connector = result.data.find( + // Refetch connectors to get the new one + const result = await refetchAllConnectors(); + if (result.data) { + const connector = result.data.find( (c: SearchSourceConnector) => c.id === newConnector.id ); if (connector) { @@ -644,34 +644,35 @@ export const useConnectorDialog = () => { }, }); - const successMessage = currentConnectorType === "MCP_CONNECTOR" - ? `${connector.name} MCP server added successfully` - : `${connectorTitle} connected and indexing started!`; - toast.success(successMessage, { - description: periodicEnabledForIndexing - ? `Periodic sync enabled every ${getFrequencyLabel(frequencyMinutesForIndexing)}.` - : "You can continue working while we sync your data.", - }); + const successMessage = + currentConnectorType === "MCP_CONNECTOR" + ? `${connector.name} MCP server added successfully` + : `${connectorTitle} connected and indexing started!`; + toast.success(successMessage, { + description: periodicEnabledForIndexing + ? `Periodic sync enabled every ${getFrequencyLabel(frequencyMinutesForIndexing)}.` + : "You can continue working while we sync your data.", + }); - const url = new URL(window.location.href); - url.searchParams.delete("modal"); - url.searchParams.delete("tab"); - url.searchParams.delete("view"); - url.searchParams.delete("connectorType"); - router.replace(url.pathname + url.search, { scroll: false }); + const url = new URL(window.location.href); + url.searchParams.delete("modal"); + url.searchParams.delete("tab"); + url.searchParams.delete("view"); + url.searchParams.delete("connectorType"); + router.replace(url.pathname + url.search, { scroll: false }); - // Clear indexing config state since we're not showing the view - setIndexingConfig(null); - setIndexingConnector(null); - setIndexingConnectorConfig(null); + // Clear indexing config state since we're not showing the view + setIndexingConfig(null); + setIndexingConnector(null); + setIndexingConnectorConfig(null); - // Invalidate queries to refresh data - queryClient.invalidateQueries({ - queryKey: cacheKeys.logs.summary(Number(searchSpaceId)), - }); + // Invalidate queries to refresh data + queryClient.invalidateQueries({ + queryKey: cacheKeys.logs.summary(Number(searchSpaceId)), + }); - // Refresh connectors list - await refetchAllConnectors(); + // Refresh connectors list + await refetchAllConnectors(); } else { // Non-indexable connector // For Circleback, transition to edit view to show webhook URL @@ -708,9 +709,10 @@ export const useConnectorDialog = () => { await refetchAllConnectors(); } else { // Other non-indexable connectors - just show success message and close - const successMessage = currentConnectorType === "MCP_CONNECTOR" - ? `${connector.name} MCP server added successfully` - : `${connectorTitle} connected successfully!`; + const successMessage = + currentConnectorType === "MCP_CONNECTOR" + ? `${connector.name} MCP server added successfully` + : `${connectorTitle} connected successfully!`; toast.success(successMessage); // Refresh connectors list before closing modal @@ -758,7 +760,7 @@ export const useConnectorDialog = () => { const handleBackFromConnect = useCallback(() => { const url = new URL(window.location.href); url.searchParams.set("modal", "connectors"); - + // If we're connecting an MCP and came from list view, go back to list if (connectingConnectorType === "MCP_CONNECTOR" && viewingMCPList) { url.searchParams.set("view", "mcp-list"); @@ -766,7 +768,7 @@ export const useConnectorDialog = () => { url.searchParams.set("tab", "all"); url.searchParams.delete("view"); } - + url.searchParams.delete("connectorType"); router.replace(url.pathname + url.search, { scroll: false }); }, [router, connectingConnectorType, viewingMCPList]); @@ -1252,33 +1254,33 @@ export const useConnectorDialog = () => { ); } - // Generate toast message based on connector type - const toastTitle = `${editingConnector.name} updated successfully`; + // Generate toast message based on connector type + const toastTitle = `${editingConnector.name} updated successfully`; - toast.success(toastTitle, { - description: periodicEnabled - ? `Periodic sync ${frequency ? `enabled every ${getFrequencyLabel(frequencyMinutes)}` : "enabled"}. ${indexingDescription}` - : indexingDescription, - }); + toast.success(toastTitle, { + description: periodicEnabled + ? `Periodic sync ${frequency ? `enabled every ${getFrequencyLabel(frequencyMinutes)}` : "enabled"}. ${indexingDescription}` + : indexingDescription, + }); - // Update URL - the effect will handle closing the modal and clearing state - const url = new URL(window.location.href); - url.searchParams.delete("modal"); - url.searchParams.delete("tab"); - url.searchParams.delete("view"); - url.searchParams.delete("connectorId"); - router.replace(url.pathname + url.search, { scroll: false }); + // Update URL - the effect will handle closing the modal and clearing state + const url = new URL(window.location.href); + url.searchParams.delete("modal"); + url.searchParams.delete("tab"); + url.searchParams.delete("view"); + url.searchParams.delete("connectorId"); + router.replace(url.pathname + url.search, { scroll: false }); - refreshConnectors(); - queryClient.invalidateQueries({ - queryKey: cacheKeys.logs.summary(Number(searchSpaceId)), - }); - } catch (error) { - console.error("Error saving connector:", error); - toast.error("Failed to save connector changes"); - } finally { - setIsSaving(false); - } + refreshConnectors(); + queryClient.invalidateQueries({ + queryKey: cacheKeys.logs.summary(Number(searchSpaceId)), + }); + } catch (error) { + console.error("Error saving connector:", error); + toast.error("Failed to save connector changes"); + } finally { + setIsSaving(false); + } }, [ editingConnector, diff --git a/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx b/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx index 1e25d24a0..a518d63a6 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx @@ -96,9 +96,7 @@ export const ActiveConnectorsTab: FC = ({ // Separate OAuth and non-OAuth connectors const oauthConnectors = connectors.filter((c) => oauthConnectorTypes.has(c.connector_type)); - const nonOauthConnectors = connectors.filter( - (c) => !oauthConnectorTypes.has(c.connector_type) - ); + const nonOauthConnectors = connectors.filter((c) => !oauthConnectorTypes.has(c.connector_type)); // Group OAuth connectors by type const oauthConnectorsByType = oauthConnectors.reduce( @@ -150,8 +148,7 @@ export const ActiveConnectorsTab: FC = ({ }); const hasActiveConnectors = - filteredOAuthConnectorTypes.length > 0 || - filteredNonOAuthConnectors.length > 0; + filteredOAuthConnectorTypes.length > 0 || filteredNonOAuthConnectors.length > 0; return ( diff --git a/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx b/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx index d358037c2..6152504fc 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx @@ -122,7 +122,6 @@ export const AllConnectorsTab: FC = ({ isConnecting={isConnecting} documentCount={documentCount} accountCount={accountCount} - isIndexing={isIndexing} onConnect={() => onConnectOAuth(connector)} onManage={ @@ -176,7 +175,6 @@ export const AllConnectorsTab: FC = ({ isConnected={isConnected} isConnecting={isConnecting} documentCount={documentCount} - isIndexing={isIndexing} onConnect={handleConnect} onManage={ diff --git a/surfsense_web/components/assistant-ui/connector-popup/utils/mcp-config-validator.ts b/surfsense_web/components/assistant-ui/connector-popup/utils/mcp-config-validator.ts index dea547be7..c2999642d 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/utils/mcp-config-validator.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/utils/mcp-config-validator.ts @@ -1,14 +1,14 @@ /** * MCP Configuration Validator Utility - * + * * Shared validation and parsing logic for MCP (Model Context Protocol) server configurations. - * + * * Features: * - Zod schema validation for runtime type safety * - Configuration caching to avoid repeated parsing (5-minute TTL) * - Standardized error messages * - Connection testing utilities - * + * * Usage: * ```typescript * // Parse and validate config @@ -18,14 +18,14 @@ * } else { * // Show result.error to user * } - * + * * // Test connection * const testResult = await testMCPConnection(config); * if (testResult.status === "success") { * console.log(`Found ${testResult.tools.length} tools`); * } * ``` - * + * * @module mcp-config-validator */ @@ -36,7 +36,7 @@ import { connectorsApiService } from "@/lib/apis/connectors-api.service"; /** * Zod schema for MCP server configuration * Provides compile-time and runtime type safety - * + * * Exported for advanced use cases (e.g., form builders) */ export const MCPServerConfigSchema = z.object({ @@ -95,11 +95,11 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult => // Check cache first const cached = configCache.get(configJson); if (cached && Date.now() - cached.timestamp < CACHE_TTL) { - console.log('[MCP Validator] ✅ Using cached config'); + console.log("[MCP Validator] ✅ Using cached config"); return { config: cached.config, error: null }; } - console.log('[MCP Validator] 🔍 Parsing new config...'); + console.log("[MCP Validator] 🔍 Parsing new config..."); // Clean up expired cache entries periodically if (configCache.size > 100) { @@ -111,7 +111,7 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult => // Validate that it's an object, not an array if (Array.isArray(parsed)) { - console.error('[MCP Validator] ❌ Error: Config is an array, expected object'); + console.error("[MCP Validator] ❌ Error: Config is an array, expected object"); return { config: null, error: "Please provide a single server configuration object, not an array", @@ -125,22 +125,22 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult => // Format Zod validation errors for user-friendly display const firstError = result.error.issues[0]; const fieldPath = firstError.path.join("."); - + // Clean up error message - remove technical Zod jargon let errorMsg = firstError.message; - + // Replace technical error messages with user-friendly ones if (errorMsg.includes("expected string, received undefined")) { errorMsg = "This field is required"; } else if (errorMsg.includes("Invalid input")) { errorMsg = "Invalid value"; } - + const formattedError = fieldPath ? `${fieldPath}: ${errorMsg}` : errorMsg; - - console.error('[MCP Validator] ❌ Validation error:', formattedError); - console.error('[MCP Validator] Full Zod errors:', result.error.issues); - + + console.error("[MCP Validator] ❌ Validation error:", formattedError); + console.error("[MCP Validator] Full Zod errors:", result.error.issues); + return { config: null, error: formattedError, @@ -159,8 +159,8 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult => config, timestamp: Date.now(), }); - - console.log('[MCP Validator] ✅ Config parsed successfully:', config); + + console.log("[MCP Validator] ✅ Config parsed successfully:", config); return { config, @@ -168,7 +168,7 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult => }; } catch (error) { const errorMsg = error instanceof Error ? error.message : "Invalid JSON"; - console.error('[MCP Validator] ❌ JSON parse error:', errorMsg); + console.error("[MCP Validator] ❌ JSON parse error:", errorMsg); return { config: null, error: errorMsg, @@ -217,11 +217,11 @@ export const testMCPConnection = async ( export const extractServerName = (configJson: string): string => { try { const parsed = JSON.parse(configJson); - + // Use Zod to validate and extract name field safely const nameSchema = z.object({ name: z.string().optional() }); const result = nameSchema.safeParse(parsed); - + if (result.success && result.data.name) { return result.data.name; } diff --git a/surfsense_web/components/assistant-ui/connector-popup/views/mcp-connector-list-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/views/mcp-connector-list-view.tsx index c826e9fc4..78a0b0b0c 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/views/mcp-connector-list-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/views/mcp-connector-list-view.tsx @@ -48,12 +48,7 @@ export const MCPConnectorListView: FC = ({ {/* Header */}

- @@ -105,7 +96,7 @@ export const MCPConnectorListView: FC = ({ mcpConnectors.map((connector) => { // Extract server name from config const serverName = connector.config?.server_config?.name || connector.name; - + return (
= ({ {getConnectorIcon("MCP_CONNECTOR", "size-6")}
-

- {serverName} -

+

{serverName}