Merge pull request #711 from AnishSarkar22/fix/SUR-86

fix: UI overlap of thinking step, added animation for thinking step
This commit is contained in:
Rohan Verma 2026-01-19 16:12:26 -08:00 committed by GitHub
commit 7435fdb8f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 201 additions and 192 deletions

View file

@ -102,7 +102,8 @@ export const ConnectorIndicator: FC = () => {
// Fallback to API if Electric is not available or fails // 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 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 // 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 || []; const connectors = useElectricData ? connectorsFromElectric : allConnectors || [];
// Manual refresh function that works with both Electric and API // Manual refresh function that works with both Electric and API
@ -229,7 +230,6 @@ export const ConnectorIndicator: FC = () => {
isDisconnecting={isDisconnecting} isDisconnecting={isDisconnecting}
isIndexing={indexingConnectorIds.has(editingConnector.id)} isIndexing={indexingConnectorIds.has(editingConnector.id)}
searchSpaceId={searchSpaceId?.toString()} searchSpaceId={searchSpaceId?.toString()}
onStartDateChange={setStartDate} onStartDateChange={setStartDate}
onEndDateChange={setEndDate} onEndDateChange={setEndDate}
onPeriodicEnabledChange={setPeriodicEnabled} onPeriodicEnabledChange={setPeriodicEnabled}

View file

@ -122,11 +122,12 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
return ( return (
<div className="space-y-6 pb-6"> <div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3 [&>svg]:top-2 sm:[&>svg]:top-3"> <Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3 [&>svg]:top-2 sm:[&>svg]:top-3">
<Server className="h-4 w-4 shrink-0" /> <Server className="h-4 w-4 shrink-0" />
<AlertDescription className="text-[10px] sm:text-xs"> <AlertDescription className="text-[10px] sm:text-xs">
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
</AlertDescription> connector.
</Alert> </AlertDescription>
</Alert>
<form id="mcp-connect-form" onSubmit={handleSubmit} className="space-y-6"> <form id="mcp-connect-form" onSubmit={handleSubmit} className="space-y-6">
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-4 sm:p-6 space-y-4"> <div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-4 sm:p-6 space-y-4">
@ -140,11 +141,10 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
rows={16} rows={16}
className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`} className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`}
/> />
{jsonError && ( {jsonError && <p className="text-xs text-red-500">JSON Error: {jsonError}</p>}
<p className="text-xs text-red-500">JSON Error: {jsonError}</p>
)}
<p className="text-[10px] sm:text-xs text-muted-foreground"> <p className="text-[10px] sm:text-xs text-muted-foreground">
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).
</p> </p>
</div> </div>
@ -176,7 +176,9 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<AlertTitle className="text-sm"> <AlertTitle className="text-sm">
{testResult.status === "success" ? "Connection Successful" : "Connection Failed"} {testResult.status === "success"
? "Connection Successful"
: "Connection Failed"}
</AlertTitle> </AlertTitle>
{testResult.tools.length > 0 && ( {testResult.tools.length > 0 && (
<Button <Button
@ -208,9 +210,7 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
{testResult.message} {testResult.message}
{showDetails && testResult.tools.length > 0 && ( {showDetails && testResult.tools.length > 0 && (
<div className="mt-3 pt-3 border-t border-green-500/20"> <div className="mt-3 pt-3 border-t border-green-500/20">
<p className="font-semibold mb-2"> <p className="font-semibold mb-2">Available tools:</p>
Available tools:
</p>
<ul className="list-disc list-inside text-xs space-y-0.5"> <ul className="list-disc list-inside text-xs space-y-0.5">
{testResult.tools.map((tool, i) => ( {testResult.tools.map((tool, i) => (
<li key={i}>{tool.name}</li> <li key={i}>{tool.name}</li>

View file

@ -2,14 +2,14 @@
import { CheckCircle2, ChevronDown, ChevronUp, Server, XCircle } from "lucide-react"; import { CheckCircle2, ChevronDown, ChevronUp, Server, XCircle } from "lucide-react";
import type { FC } from "react"; import type { FC } from "react";
import { useEffect, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import type { MCPServerConfig, MCPToolDefinition } from "@/contracts/types/mcp.types"; import type { MCPServerConfig } from "@/contracts/types/mcp.types";
import type { ConnectorConfigProps } from "../index"; import type { ConnectorConfigProps } from "../index";
import { import {
parseMCPConfig, parseMCPConfig,
@ -22,32 +22,24 @@ interface MCPConfigProps extends ConnectorConfigProps {
} }
export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNameChange }) => { export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNameChange }) => {
// Validate that this is an MCP connector
if (connector.connector_type !== EnumConnectorName.MCP_CONNECTOR) {
console.error(
"MCPConfig received non-MCP connector:",
connector.connector_type
);
return (
<Alert className="border-red-500/50 bg-red-500/10">
<XCircle className="h-4 w-4 text-red-600" />
<AlertTitle>Invalid Connector Type</AlertTitle>
<AlertDescription>
This component can only be used with MCP connectors.
</AlertDescription>
</Alert>
);
}
const [name, setName] = useState<string>(""); const [name, setName] = useState<string>("");
const [configJson, setConfigJson] = useState(""); const [configJson, setConfigJson] = useState("");
const [jsonError, setJsonError] = useState<string | null>(null); const [jsonError, setJsonError] = useState<string | null>(null);
const [isTesting, setIsTesting] = useState(false); const [isTesting, setIsTesting] = useState(false);
const [showDetails, setShowDetails] = useState(false); const [showDetails, setShowDetails] = useState(false);
const [testResult, setTestResult] = useState<MCPConnectionTestResult | null>(null); const [testResult, setTestResult] = useState<MCPConnectionTestResult | null>(null);
const initializedRef = useRef(false);
// Check if this is a valid MCP connector
const isValidConnector = connector.connector_type === EnumConnectorName.MCP_CONNECTOR;
// Initialize form from connector config (only on mount) // Initialize form from connector config (only on mount)
// We intentionally only read connector.name and connector.config on initial mount
// to preserve user edits during the session
useEffect(() => { useEffect(() => {
if (!isValidConnector || initializedRef.current) return;
initializedRef.current = true;
if (connector.name) { if (connector.name) {
setName(connector.name); setName(connector.name);
} }
@ -63,17 +55,19 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
}; };
setConfigJson(JSON.stringify(configObj, null, 2)); setConfigJson(JSON.stringify(configObj, null, 2));
} }
// eslint-disable-next-line react-hooks/exhaustive-deps }, [isValidConnector, connector.name, connector.config?.server_config]);
}, []); // Only run on mount to preserve user edits
const handleNameChange = (value: string) => { const handleNameChange = useCallback(
setName(value); (value: string) => {
if (onNameChange) { setName(value);
onNameChange(value); if (onNameChange) {
} onNameChange(value);
}; }
},
[onNameChange]
);
const parseConfig = () => { const parseConfig = useCallback(() => {
const result = parseMCPConfig(configJson); const result = parseMCPConfig(configJson);
if (result.error) { if (result.error) {
setJsonError(result.error); setJsonError(result.error);
@ -81,25 +75,26 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
setJsonError(null); setJsonError(null);
} }
return result.config; return result.config;
}; }, [configJson]);
const handleConfigChange = (value: string) => { const handleConfigChange = useCallback(
setConfigJson(value); (value: string) => {
if (jsonError) { setConfigJson(value);
setJsonError(null); setJsonError(null);
}
// Use shared utility for validation and parsing (with caching) // Use shared utility for validation and parsing (with caching)
const result = parseMCPConfig(value); const result = parseMCPConfig(value);
if (result.config && onConfigChange) { if (result.config && onConfigChange) {
// Valid config - update parent immediately // Valid config - update parent immediately
onConfigChange({ server_config: result.config }); onConfigChange({ server_config: result.config });
} }
// Ignore errors while typing - only show errors when user tests or saves // Ignore errors while typing - only show errors when user tests or saves
}; },
[onConfigChange]
);
const handleTestConnection = async () => { const handleTestConnection = useCallback(async () => {
const serverConfig = parseConfig(); const serverConfig = parseConfig();
if (!serverConfig) { if (!serverConfig) {
setTestResult({ setTestResult({
@ -121,7 +116,19 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
const result = await testMCPConnection(serverConfig); const result = await testMCPConnection(serverConfig);
setTestResult(result); setTestResult(result);
setIsTesting(false); setIsTesting(false);
}; }, [parseConfig, jsonError, onConfigChange]);
// Validate that this is an MCP connector - must be after all hooks
if (!isValidConnector) {
console.error("MCPConfig received non-MCP connector:", connector.connector_type);
return (
<Alert className="border-red-500/50 bg-red-500/10">
<XCircle className="h-4 w-4 text-red-600" />
<AlertTitle>Invalid Connector Type</AlertTitle>
<AlertDescription>This component can only be used with MCP connectors.</AlertDescription>
</Alert>
);
}
return ( return (
<div className="space-y-6"> <div className="space-y-6">
@ -154,11 +161,10 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
rows={16} rows={16}
className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`} className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`}
/> />
{jsonError && ( {jsonError && <p className="text-xs text-red-500">JSON Error: {jsonError}</p>}
<p className="text-xs text-red-500">JSON Error: {jsonError}</p>
)}
<p className="text-[10px] sm:text-xs text-muted-foreground"> <p className="text-[10px] sm:text-xs text-muted-foreground">
Edit your MCP server configuration. Must include: name, command, args (optional), env (optional), transport (optional). Edit your MCP server configuration. Must include: name, command, args (optional), env
(optional), transport (optional).
</p> </p>
</div> </div>
@ -192,7 +198,9 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<AlertTitle className="text-sm"> <AlertTitle className="text-sm">
{testResult.status === "success" ? "Connection Successful" : "Connection Failed"} {testResult.status === "success"
? "Connection Successful"
: "Connection Failed"}
</AlertTitle> </AlertTitle>
{testResult.tools.length > 0 && ( {testResult.tools.length > 0 && (
<Button <Button
@ -224,12 +232,10 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
{testResult.message} {testResult.message}
{showDetails && testResult.tools.length > 0 && ( {showDetails && testResult.tools.length > 0 && (
<div className="mt-3 pt-3 border-t border-green-500/20"> <div className="mt-3 pt-3 border-t border-green-500/20">
<p className="font-semibold mb-2"> <p className="font-semibold mb-2">Available tools:</p>
Available tools:
</p>
<ul className="list-disc list-inside text-xs space-y-0.5"> <ul className="list-disc list-inside text-xs space-y-0.5">
{testResult.tools.map((tool, i) => ( {testResult.tools.map((tool) => (
<li key={i}>{tool.name}</li> <li key={tool.name}>{tool.name}</li>
))} ))}
</ul> </ul>
</div> </div>

View file

@ -99,7 +99,10 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
</div> </div>
<div> <div>
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight"> <h2 className="text-xl sm:text-2xl font-semibold tracking-tight">
Connect {connectorType === "MCP_CONNECTOR" ? "MCP Server" : getConnectorTypeDisplay(connectorType)} Connect{" "}
{connectorType === "MCP_CONNECTOR"
? "MCP Server"
: getConnectorTypeDisplay(connectorType)}
</h2> </h2>
<p className="text-xs sm:text-base text-muted-foreground mt-1"> <p className="text-xs sm:text-base text-muted-foreground mt-1">
Enter your connection details Enter your connection details
@ -139,7 +142,11 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
Connecting Connecting
</> </>
) : ( ) : (
<>{connectorType === "MCP_CONNECTOR" ? "Connect" : `Connect ${getConnectorTypeDisplay(connectorType)}`}</> <>
{connectorType === "MCP_CONNECTOR"
? "Connect"
: `Connect ${getConnectorTypeDisplay(connectorType)}`}
</>
)} )}
</Button> </Button>
</div> </div>

View file

@ -151,7 +151,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight text-wrap whitespace-normal wrap-break-word"> <h2 className="text-xl sm:text-2xl font-semibold tracking-tight text-wrap whitespace-normal wrap-break-word">
{connector.connector_type === "MCP_CONNECTOR" ? "MCP Server" : connector.name} {connector.connector_type === "MCP_CONNECTOR" ? "MCP Server" : connector.name}
</h2> </h2>
<p className="text-xs sm:text-base text-muted-foreground mt-1"> <p className="text-xs sm:text-base text-muted-foreground mt-1">
Manage your connector settings and sync configuration Manage your connector settings and sync configuration
@ -200,7 +200,6 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
onConfigChange={onConfigChange} onConfigChange={onConfigChange}
onNameChange={onNameChange} onNameChange={onNameChange}
searchSpaceId={searchSpaceId} searchSpaceId={searchSpaceId}
/> />
)} )}

View file

@ -540,18 +540,18 @@ export const useConnectorDialog = () => {
data: { data: {
...connectorData, ...connectorData,
connector_type: connectorData.connector_type as EnumConnectorName, connector_type: connectorData.connector_type as EnumConnectorName,
is_active: true, is_active: true,
next_scheduled_at: connectorData.next_scheduled_at as string | null, next_scheduled_at: connectorData.next_scheduled_at as string | null,
}, },
queryParams: { queryParams: {
search_space_id: searchSpaceId, search_space_id: searchSpaceId,
}, },
}); });
// Refetch connectors to get the new one // Refetch connectors to get the new one
const result = await refetchAllConnectors(); const result = await refetchAllConnectors();
if (result.data) { if (result.data) {
const connector = result.data.find( const connector = result.data.find(
(c: SearchSourceConnector) => c.id === newConnector.id (c: SearchSourceConnector) => c.id === newConnector.id
); );
if (connector) { if (connector) {
@ -644,34 +644,35 @@ export const useConnectorDialog = () => {
}, },
}); });
const successMessage = currentConnectorType === "MCP_CONNECTOR" const successMessage =
? `${connector.name} MCP server added successfully` currentConnectorType === "MCP_CONNECTOR"
: `${connectorTitle} connected and indexing started!`; ? `${connector.name} MCP server added successfully`
toast.success(successMessage, { : `${connectorTitle} connected and indexing started!`;
description: periodicEnabledForIndexing toast.success(successMessage, {
? `Periodic sync enabled every ${getFrequencyLabel(frequencyMinutesForIndexing)}.` description: periodicEnabledForIndexing
: "You can continue working while we sync your data.", ? `Periodic sync enabled every ${getFrequencyLabel(frequencyMinutesForIndexing)}.`
}); : "You can continue working while we sync your data.",
});
const url = new URL(window.location.href); const url = new URL(window.location.href);
url.searchParams.delete("modal"); url.searchParams.delete("modal");
url.searchParams.delete("tab"); url.searchParams.delete("tab");
url.searchParams.delete("view"); url.searchParams.delete("view");
url.searchParams.delete("connectorType"); url.searchParams.delete("connectorType");
router.replace(url.pathname + url.search, { scroll: false }); router.replace(url.pathname + url.search, { scroll: false });
// Clear indexing config state since we're not showing the view // Clear indexing config state since we're not showing the view
setIndexingConfig(null); setIndexingConfig(null);
setIndexingConnector(null); setIndexingConnector(null);
setIndexingConnectorConfig(null); setIndexingConnectorConfig(null);
// Invalidate queries to refresh data // Invalidate queries to refresh data
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: cacheKeys.logs.summary(Number(searchSpaceId)), queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
}); });
// Refresh connectors list // Refresh connectors list
await refetchAllConnectors(); await refetchAllConnectors();
} else { } else {
// Non-indexable connector // Non-indexable connector
// For Circleback, transition to edit view to show webhook URL // For Circleback, transition to edit view to show webhook URL
@ -708,9 +709,10 @@ export const useConnectorDialog = () => {
await refetchAllConnectors(); await refetchAllConnectors();
} else { } else {
// Other non-indexable connectors - just show success message and close // Other non-indexable connectors - just show success message and close
const successMessage = currentConnectorType === "MCP_CONNECTOR" const successMessage =
? `${connector.name} MCP server added successfully` currentConnectorType === "MCP_CONNECTOR"
: `${connectorTitle} connected successfully!`; ? `${connector.name} MCP server added successfully`
: `${connectorTitle} connected successfully!`;
toast.success(successMessage); toast.success(successMessage);
// Refresh connectors list before closing modal // Refresh connectors list before closing modal
@ -1252,33 +1254,33 @@ export const useConnectorDialog = () => {
); );
} }
// Generate toast message based on connector type // Generate toast message based on connector type
const toastTitle = `${editingConnector.name} updated successfully`; const toastTitle = `${editingConnector.name} updated successfully`;
toast.success(toastTitle, { toast.success(toastTitle, {
description: periodicEnabled description: periodicEnabled
? `Periodic sync ${frequency ? `enabled every ${getFrequencyLabel(frequencyMinutes)}` : "enabled"}. ${indexingDescription}` ? `Periodic sync ${frequency ? `enabled every ${getFrequencyLabel(frequencyMinutes)}` : "enabled"}. ${indexingDescription}`
: indexingDescription, : indexingDescription,
}); });
// Update URL - the effect will handle closing the modal and clearing state // Update URL - the effect will handle closing the modal and clearing state
const url = new URL(window.location.href); const url = new URL(window.location.href);
url.searchParams.delete("modal"); url.searchParams.delete("modal");
url.searchParams.delete("tab"); url.searchParams.delete("tab");
url.searchParams.delete("view"); url.searchParams.delete("view");
url.searchParams.delete("connectorId"); url.searchParams.delete("connectorId");
router.replace(url.pathname + url.search, { scroll: false }); router.replace(url.pathname + url.search, { scroll: false });
refreshConnectors(); refreshConnectors();
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: cacheKeys.logs.summary(Number(searchSpaceId)), queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
}); });
} catch (error) { } catch (error) {
console.error("Error saving connector:", error); console.error("Error saving connector:", error);
toast.error("Failed to save connector changes"); toast.error("Failed to save connector changes");
} finally { } finally {
setIsSaving(false); setIsSaving(false);
} }
}, },
[ [
editingConnector, editingConnector,

View file

@ -96,9 +96,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
// Separate OAuth and non-OAuth connectors // Separate OAuth and non-OAuth connectors
const oauthConnectors = connectors.filter((c) => oauthConnectorTypes.has(c.connector_type)); const oauthConnectors = connectors.filter((c) => oauthConnectorTypes.has(c.connector_type));
const nonOauthConnectors = connectors.filter( const nonOauthConnectors = connectors.filter((c) => !oauthConnectorTypes.has(c.connector_type));
(c) => !oauthConnectorTypes.has(c.connector_type)
);
// Group OAuth connectors by type // Group OAuth connectors by type
const oauthConnectorsByType = oauthConnectors.reduce( const oauthConnectorsByType = oauthConnectors.reduce(
@ -150,8 +148,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
}); });
const hasActiveConnectors = const hasActiveConnectors =
filteredOAuthConnectorTypes.length > 0 || filteredOAuthConnectorTypes.length > 0 || filteredNonOAuthConnectors.length > 0;
filteredNonOAuthConnectors.length > 0;
return ( return (
<TabsContent value="active" className="m-0"> <TabsContent value="active" className="m-0">

View file

@ -122,7 +122,6 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
isConnecting={isConnecting} isConnecting={isConnecting}
documentCount={documentCount} documentCount={documentCount}
accountCount={accountCount} accountCount={accountCount}
isIndexing={isIndexing} isIndexing={isIndexing}
onConnect={() => onConnectOAuth(connector)} onConnect={() => onConnectOAuth(connector)}
onManage={ onManage={
@ -176,7 +175,6 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
isConnected={isConnected} isConnected={isConnected}
isConnecting={isConnecting} isConnecting={isConnecting}
documentCount={documentCount} documentCount={documentCount}
isIndexing={isIndexing} isIndexing={isIndexing}
onConnect={handleConnect} onConnect={handleConnect}
onManage={ onManage={

View file

@ -95,11 +95,11 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult =>
// Check cache first // Check cache first
const cached = configCache.get(configJson); const cached = configCache.get(configJson);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) { 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 }; 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 // Clean up expired cache entries periodically
if (configCache.size > 100) { if (configCache.size > 100) {
@ -111,7 +111,7 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult =>
// Validate that it's an object, not an array // Validate that it's an object, not an array
if (Array.isArray(parsed)) { 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 { return {
config: null, config: null,
error: "Please provide a single server configuration object, not an array", error: "Please provide a single server configuration object, not an array",
@ -138,8 +138,8 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult =>
const formattedError = fieldPath ? `${fieldPath}: ${errorMsg}` : errorMsg; const formattedError = fieldPath ? `${fieldPath}: ${errorMsg}` : errorMsg;
console.error('[MCP Validator] ❌ Validation error:', formattedError); console.error("[MCP Validator] ❌ Validation error:", formattedError);
console.error('[MCP Validator] Full Zod errors:', result.error.issues); console.error("[MCP Validator] Full Zod errors:", result.error.issues);
return { return {
config: null, config: null,
@ -160,7 +160,7 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult =>
timestamp: Date.now(), timestamp: Date.now(),
}); });
console.log('[MCP Validator] ✅ Config parsed successfully:', config); console.log("[MCP Validator] ✅ Config parsed successfully:", config);
return { return {
config, config,
@ -168,7 +168,7 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult =>
}; };
} catch (error) { } catch (error) {
const errorMsg = error instanceof Error ? error.message : "Invalid JSON"; 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 { return {
config: null, config: null,
error: errorMsg, error: errorMsg,

View file

@ -48,12 +48,7 @@ export const MCPConnectorListView: FC<MCPConnectorListViewProps> = ({
{/* Header */} {/* Header */}
<div className="flex items-center justify-between mb-6 shrink-0"> <div className="flex items-center justify-between mb-6 shrink-0">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Button <Button variant="ghost" size="icon" onClick={onBack} className="h-8 w-8">
variant="ghost"
size="icon"
onClick={onBack}
className="h-8 w-8"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="16" width="16"
@ -79,11 +74,7 @@ export const MCPConnectorListView: FC<MCPConnectorListViewProps> = ({
{/* Add New Button */} {/* Add New Button */}
<div className="mb-4 shrink-0"> <div className="mb-4 shrink-0">
<Button <Button onClick={onAddNew} className="w-full" variant="outline">
onClick={onAddNew}
className="w-full"
variant="outline"
>
<Plus className="h-4 w-4 mr-2" /> <Plus className="h-4 w-4 mr-2" />
Add New MCP Server Add New MCP Server
</Button> </Button>
@ -123,9 +114,7 @@ export const MCPConnectorListView: FC<MCPConnectorListViewProps> = ({
{getConnectorIcon("MCP_CONNECTOR", "size-6")} {getConnectorIcon("MCP_CONNECTOR", "size-6")}
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-[14px] font-semibold leading-tight truncate"> <p className="text-[14px] font-semibold leading-tight truncate">{serverName}</p>
{serverName}
</p>
</div> </div>
<Button <Button
variant="secondary" variant="secondary"

View file

@ -108,7 +108,10 @@ export const ThinkingStepsDisplay: FC<{ steps: ThinkingStep[]; isThreadRunning?:
{/* Step dot - on top of line */} {/* Step dot - on top of line */}
<div className="relative z-10 mt-[7px] flex shrink-0 items-center justify-center"> <div className="relative z-10 mt-[7px] flex shrink-0 items-center justify-center">
{effectiveStatus === "in_progress" ? ( {effectiveStatus === "in_progress" ? (
<span className="size-2 rounded-full bg-muted-foreground/30" /> <span className="relative flex size-2">
<span className="absolute inline-flex size-full animate-ping rounded-full bg-primary/60" />
<span className="relative inline-flex size-2 rounded-full bg-primary" />
</span>
) : ( ) : (
<span className="size-2 rounded-full bg-muted-foreground/30" /> <span className="size-2 rounded-full bg-muted-foreground/30" />
)} )}

View file

@ -105,7 +105,7 @@ const ThreadContent: FC<{ header?: React.ReactNode }> = ({ header }) => {
}} }}
/> />
<ThreadPrimitive.ViewportFooter className="aui-thread-viewport-footer sticky bottom-0 mx-auto mt-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible rounded-t-3xl bg-background pb-4 md:pb-6"> <ThreadPrimitive.ViewportFooter className="aui-thread-viewport-footer sticky bottom-0 z-20 mx-auto mt-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible rounded-t-3xl bg-background pb-4 md:pb-6">
<ThreadScrollToBottom /> <ThreadScrollToBottom />
<AssistantIf condition={({ thread }) => !thread.isEmpty}> <AssistantIf condition={({ thread }) => !thread.isEmpty}>
<div className="fade-in slide-in-from-bottom-4 animate-in duration-500 ease-out fill-mode-both"> <div className="fade-in slide-in-from-bottom-4 animate-in duration-500 ease-out fill-mode-both">

View file

@ -5,7 +5,11 @@ import { documentTypeEnum } from "./document.types";
/** /**
* Notification type enum - matches backend notification types * Notification type enum - matches backend notification types
*/ */
export const notificationTypeEnum = z.enum(["connector_indexing", "document_processing", "new_mention"]); export const notificationTypeEnum = z.enum([
"connector_indexing",
"document_processing",
"new_mention",
]);
/** /**
* Notification status enum - used in metadata * Notification status enum - used in metadata

View file

@ -267,9 +267,13 @@ class ConnectorsApiService {
search_space_id: String(queryParams.search_space_id), search_space_id: String(queryParams.search_space_id),
}).toString(); }).toString();
return baseApiService.post<MCPConnectorRead>(`/api/v1/connectors/mcp?${queryString}`, undefined, { return baseApiService.post<MCPConnectorRead>(
body: data, `/api/v1/connectors/mcp?${queryString}`,
}); undefined,
{
body: data,
}
);
}; };
/** /**