mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-02 19:55:18 +02:00
refactor: consolidate MCP connector handling in UI components
- Replaced MCPConnectorListView with ConnectorAccountsListView for better integration and UI consistency. - Updated ConnectorCard to display connector counts for MCP connectors. - Enhanced MCPConnectForm to improve connection testing feedback and error handling. - Streamlined MCPConfig validation logic and improved user feedback for configuration errors. - Adjusted AllConnectorsTab to count and display MCP connectors accurately. - Removed redundant MCP-specific components to simplify the codebase.
This commit is contained in:
parent
48603b993d
commit
72ad558240
10 changed files with 148 additions and 290 deletions
|
|
@ -22,7 +22,6 @@ import { useIndexingConnectors } from "./connector-popup/hooks/use-indexing-conn
|
||||||
import { ActiveConnectorsTab } from "./connector-popup/tabs/active-connectors-tab";
|
import { ActiveConnectorsTab } from "./connector-popup/tabs/active-connectors-tab";
|
||||||
import { AllConnectorsTab } from "./connector-popup/tabs/all-connectors-tab";
|
import { AllConnectorsTab } from "./connector-popup/tabs/all-connectors-tab";
|
||||||
import { ConnectorAccountsListView } from "./connector-popup/views/connector-accounts-list-view";
|
import { ConnectorAccountsListView } from "./connector-popup/views/connector-accounts-list-view";
|
||||||
import { MCPConnectorListView } from "./connector-popup/views/mcp-connector-list-view";
|
|
||||||
import { YouTubeCrawlerView } from "./connector-popup/views/youtube-crawler-view";
|
import { YouTubeCrawlerView } from "./connector-popup/views/youtube-crawler-view";
|
||||||
|
|
||||||
export const ConnectorIndicator: FC = () => {
|
export const ConnectorIndicator: FC = () => {
|
||||||
|
|
@ -177,18 +176,16 @@ export const ConnectorIndicator: FC = () => {
|
||||||
{isYouTubeView && searchSpaceId ? (
|
{isYouTubeView && searchSpaceId ? (
|
||||||
<YouTubeCrawlerView searchSpaceId={searchSpaceId} onBack={handleBackFromYouTube} />
|
<YouTubeCrawlerView searchSpaceId={searchSpaceId} onBack={handleBackFromYouTube} />
|
||||||
) : viewingMCPList ? (
|
) : viewingMCPList ? (
|
||||||
<div className="p-6 sm:p-12 h-full overflow-hidden">
|
<ConnectorAccountsListView
|
||||||
<MCPConnectorListView
|
connectorType="MCP_CONNECTOR"
|
||||||
mcpConnectors={
|
connectorTitle="MCP Connectors"
|
||||||
(allConnectors || []).filter(
|
connectors={(allConnectors || []) as SearchSourceConnector[]}
|
||||||
(c: SearchSourceConnector) => c.connector_type === "MCP_CONNECTOR"
|
indexingConnectorIds={indexingConnectorIds}
|
||||||
) as SearchSourceConnector[]
|
onBack={handleBackFromMCPList}
|
||||||
}
|
onManage={handleStartEdit}
|
||||||
onAddNew={handleAddNewMCPFromList}
|
onAddAccount={handleAddNewMCPFromList}
|
||||||
onManageConnector={handleStartEdit}
|
addButtonText="Add New MCP Server"
|
||||||
onBack={handleBackFromMCPList}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : viewingAccountsType ? (
|
) : viewingAccountsType ? (
|
||||||
<ConnectorAccountsListView
|
<ConnectorAccountsListView
|
||||||
connectorType={viewingAccountsType.connectorType}
|
connectorType={viewingAccountsType.connectorType}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { IconBrandYoutube } from "@tabler/icons-react";
|
||||||
import { FileText, Loader2 } from "lucide-react";
|
import { FileText, Loader2 } from "lucide-react";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useConnectorStatus } from "../hooks/use-connector-status";
|
import { useConnectorStatus } from "../hooks/use-connector-status";
|
||||||
|
|
@ -18,6 +19,7 @@ interface ConnectorCardProps {
|
||||||
isConnecting?: boolean;
|
isConnecting?: boolean;
|
||||||
documentCount?: number;
|
documentCount?: number;
|
||||||
accountCount?: number;
|
accountCount?: number;
|
||||||
|
connectorCount?: number;
|
||||||
isIndexing?: boolean;
|
isIndexing?: boolean;
|
||||||
onConnect?: () => void;
|
onConnect?: () => void;
|
||||||
onManage?: () => void;
|
onManage?: () => void;
|
||||||
|
|
@ -46,10 +48,12 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
|
||||||
isConnecting = false,
|
isConnecting = false,
|
||||||
documentCount,
|
documentCount,
|
||||||
accountCount,
|
accountCount,
|
||||||
|
connectorCount,
|
||||||
isIndexing = false,
|
isIndexing = false,
|
||||||
onConnect,
|
onConnect,
|
||||||
onManage,
|
onManage,
|
||||||
}) => {
|
}) => {
|
||||||
|
const isMCP = connectorType === EnumConnectorName.MCP_CONNECTOR;
|
||||||
// Get connector status
|
// Get connector status
|
||||||
const { getConnectorStatus, isConnectorEnabled, getConnectorStatusMessage, shouldShowWarnings } =
|
const { getConnectorStatus, isConnectorEnabled, getConnectorStatusMessage, shouldShowWarnings } =
|
||||||
useConnectorStatus();
|
useConnectorStatus();
|
||||||
|
|
@ -112,13 +116,21 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
|
||||||
</p>
|
</p>
|
||||||
) : isConnected ? (
|
) : isConnected ? (
|
||||||
<p className="text-[10px] text-muted-foreground mt-1 flex items-center gap-1.5">
|
<p className="text-[10px] text-muted-foreground mt-1 flex items-center gap-1.5">
|
||||||
<span>{formatDocumentCount(documentCount)}</span>
|
{isMCP && connectorCount !== undefined ? (
|
||||||
{accountCount !== undefined && accountCount > 0 && (
|
<span>
|
||||||
|
{connectorCount} {connectorCount === 1 ? "server" : "servers"}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
<>
|
<>
|
||||||
<span className="text-muted-foreground/50">•</span>
|
<span>{formatDocumentCount(documentCount)}</span>
|
||||||
<span>
|
{accountCount !== undefined && accountCount > 0 && (
|
||||||
{accountCount} {accountCount === 1 ? "Account" : "Accounts"}
|
<>
|
||||||
</span>
|
<span className="text-muted-foreground/50">•</span>
|
||||||
|
<span>
|
||||||
|
{accountCount} {accountCount === 1 ? "Account" : "Accounts"}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,18 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { CheckCircle2, ChevronDown, ChevronUp, Server, XCircle } from "lucide-react";
|
import { Server } from "lucide-react";
|
||||||
import { type FC, useRef, useState } from "react";
|
import { type FC, useRef, useState } from "react";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
import { toast } from "sonner";
|
||||||
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
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 { MCPToolDefinition } from "@/contracts/types/mcp.types";
|
|
||||||
import type { ConnectFormProps } from "..";
|
import type { ConnectFormProps } from "..";
|
||||||
import {
|
import {
|
||||||
extractServerName,
|
extractServerName,
|
||||||
parseMCPConfig,
|
parseMCPConfig,
|
||||||
testMCPConnection,
|
testMCPConnection,
|
||||||
type MCPConnectionTestResult,
|
|
||||||
} from "../../utils/mcp-config-validator";
|
} from "../../utils/mcp-config-validator";
|
||||||
|
|
||||||
export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
|
export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
|
||||||
|
|
@ -22,8 +20,6 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
|
||||||
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 [testResult, setTestResult] = useState<MCPConnectionTestResult | null>(null);
|
|
||||||
|
|
||||||
const DEFAULT_CONFIG = JSON.stringify(
|
const DEFAULT_CONFIG = JSON.stringify(
|
||||||
{
|
{
|
||||||
|
|
@ -69,19 +65,26 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
|
||||||
const handleTestConnection = async () => {
|
const handleTestConnection = async () => {
|
||||||
const serverConfig = parseConfig();
|
const serverConfig = parseConfig();
|
||||||
if (!serverConfig) {
|
if (!serverConfig) {
|
||||||
setTestResult({
|
toast.error("Invalid configuration", {
|
||||||
status: "error",
|
description: jsonError || "Please check your MCP server configuration JSON.",
|
||||||
message: jsonError || "Invalid configuration",
|
|
||||||
tools: [],
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsTesting(true);
|
setIsTesting(true);
|
||||||
setTestResult(null);
|
|
||||||
|
|
||||||
const result = await testMCPConnection(serverConfig);
|
const result = await testMCPConnection(serverConfig);
|
||||||
setTestResult(result);
|
|
||||||
|
if (result.status === "success") {
|
||||||
|
toast.success("Connection Successful", {
|
||||||
|
description: result.message,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast.error("Connection Failed", {
|
||||||
|
description: result.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setIsTesting(false);
|
setIsTesting(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -143,7 +146,7 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
|
||||||
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">{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).
|
||||||
|
|
@ -158,72 +161,9 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
{isTesting ? "Testing Connection..." : "Test Connection"}
|
{isTesting ? "Testing Connection" : "Test Connection"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{testResult && (
|
|
||||||
<Alert
|
|
||||||
className={
|
|
||||||
testResult.status === "success"
|
|
||||||
? "border-green-500/50 bg-green-500/10"
|
|
||||||
: "border-red-500/50 bg-red-500/10"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{testResult.status === "success" ? (
|
|
||||||
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
|
||||||
) : (
|
|
||||||
<XCircle className="h-4 w-4 text-red-600" />
|
|
||||||
)}
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<AlertTitle className="text-sm">
|
|
||||||
{testResult.status === "success" ? "Connection Successful" : "Connection Failed"}
|
|
||||||
</AlertTitle>
|
|
||||||
{testResult.tools.length > 0 && (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-6 px-2"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setShowDetails(!showDetails);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{showDetails ? (
|
|
||||||
<>
|
|
||||||
<ChevronUp className="h-3 w-3 mr-1" />
|
|
||||||
Hide Details
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ChevronDown className="h-3 w-3 mr-1" />
|
|
||||||
Show Details
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<AlertDescription className="text-xs mt-1">
|
|
||||||
{testResult.message}
|
|
||||||
{showDetails && testResult.tools.length > 0 && (
|
|
||||||
<div className="mt-3 pt-3 border-t border-green-500/20">
|
|
||||||
<p className="font-semibold mb-2">
|
|
||||||
Available tools:
|
|
||||||
</p>
|
|
||||||
<ul className="list-disc list-inside text-xs space-y-0.5">
|
|
||||||
{testResult.tools.map((tool, i) => (
|
|
||||||
<li key={i}>{tool.name}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</AlertDescription>
|
|
||||||
</div>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -22,23 +22,6 @@ 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);
|
||||||
|
|
@ -63,8 +46,24 @@ 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
|
}, []);
|
||||||
}, []); // Only run on mount to preserve user edits
|
|
||||||
|
// Validate that this is an MCP connector (after hooks)
|
||||||
|
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 handleNameChange = (value: string) => {
|
const handleNameChange = (value: string) => {
|
||||||
setName(value);
|
setName(value);
|
||||||
|
|
@ -126,15 +125,21 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Server Name */}
|
{/* Server Name */}
|
||||||
<div className="space-y-2">
|
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
|
||||||
<Label htmlFor="name">Server Name *</Label>
|
<div className="space-y-2">
|
||||||
<Input
|
<Label htmlFor="name" className="text-xs sm:text-sm">Server Name</Label>
|
||||||
id="name"
|
<Input
|
||||||
value={name}
|
id="name"
|
||||||
onChange={(e) => handleNameChange(e.target.value)}
|
value={name}
|
||||||
placeholder="e.g., Filesystem Server"
|
onChange={(e) => handleNameChange(e.target.value)}
|
||||||
required
|
placeholder="e.g., Filesystem Server"
|
||||||
/>
|
className="border-slate-400/20 focus-visible:border-slate-400/40"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p className="text-[10px] sm:text-xs text-muted-foreground">
|
||||||
|
A friendly name to identify this connector.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Server Configuration */}
|
{/* Server Configuration */}
|
||||||
|
|
@ -168,8 +173,8 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleTestConnection}
|
onClick={handleTestConnection}
|
||||||
disabled={isTesting}
|
disabled={isTesting}
|
||||||
variant="outline"
|
variant="secondary"
|
||||||
className="w-full"
|
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 ? "Testing Connection..." : "Test Connection"}
|
{isTesting ? "Testing Connection..." : "Test Connection"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -228,8 +233,8 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
|
||||||
Available tools:
|
Available tools:
|
||||||
</p>
|
</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>
|
||||||
|
|
|
||||||
|
|
@ -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.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
|
||||||
|
|
|
||||||
|
|
@ -645,7 +645,7 @@ export const useConnectorDialog = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const successMessage = currentConnectorType === "MCP_CONNECTOR"
|
const successMessage = currentConnectorType === "MCP_CONNECTOR"
|
||||||
? `${connector.name} MCP server added successfully`
|
? `${connector.name} added successfully`
|
||||||
: `${connectorTitle} connected and indexing started!`;
|
: `${connectorTitle} connected and indexing started!`;
|
||||||
toast.success(successMessage, {
|
toast.success(successMessage, {
|
||||||
description: periodicEnabledForIndexing
|
description: periodicEnabledForIndexing
|
||||||
|
|
@ -709,7 +709,7 @@ export const useConnectorDialog = () => {
|
||||||
} 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 = currentConnectorType === "MCP_CONNECTOR"
|
||||||
? `${connector.name} MCP server added successfully`
|
? `${connector.name} added successfully`
|
||||||
: `${connectorTitle} connected successfully!`;
|
: `${connectorTitle} connected successfully!`;
|
||||||
toast.success(successMessage);
|
toast.success(successMessage);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
|
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||||
import { ConnectorCard } from "../components/connector-card";
|
import { ConnectorCard } from "../components/connector-card";
|
||||||
import { CRAWLERS, OAUTH_CONNECTORS, OTHER_CONNECTORS } from "../constants/connector-constants";
|
import { CRAWLERS, OAUTH_CONNECTORS, OTHER_CONNECTORS } from "../constants/connector-constants";
|
||||||
|
|
@ -162,6 +163,12 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
||||||
);
|
);
|
||||||
const isIndexing = actualConnector && indexingConnectorIds?.has(actualConnector.id);
|
const isIndexing = actualConnector && indexingConnectorIds?.has(actualConnector.id);
|
||||||
|
|
||||||
|
// For MCP connectors, count total MCP connectors instead of document count
|
||||||
|
const isMCP = connector.connectorType === EnumConnectorName.MCP_CONNECTOR;
|
||||||
|
const mcpConnectorCount = isMCP && allConnectors
|
||||||
|
? allConnectors.filter((c: SearchSourceConnector) => c.connector_type === EnumConnectorName.MCP_CONNECTOR).length
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const handleConnect = onConnectNonOAuth
|
const handleConnect = onConnectNonOAuth
|
||||||
? () => onConnectNonOAuth(connector.connectorType)
|
? () => onConnectNonOAuth(connector.connectorType)
|
||||||
: () => {}; // Fallback - connector popup should handle all connector types
|
: () => {}; // Fallback - connector popup should handle all connector types
|
||||||
|
|
@ -176,7 +183,7 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
||||||
isConnected={isConnected}
|
isConnected={isConnected}
|
||||||
isConnecting={isConnecting}
|
isConnecting={isConnecting}
|
||||||
documentCount={documentCount}
|
documentCount={documentCount}
|
||||||
|
connectorCount={mcpConnectorCount}
|
||||||
isIndexing={isIndexing}
|
isIndexing={isIndexing}
|
||||||
onConnect={handleConnect}
|
onConnect={handleConnect}
|
||||||
onManage={
|
onManage={
|
||||||
|
|
|
||||||
|
|
@ -131,19 +131,24 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult =>
|
||||||
|
|
||||||
// Replace technical error messages with user-friendly ones
|
// Replace technical error messages with user-friendly ones
|
||||||
if (errorMsg.includes("expected string, received undefined")) {
|
if (errorMsg.includes("expected string, received undefined")) {
|
||||||
errorMsg = "This field is required";
|
errorMsg = fieldPath
|
||||||
|
? `The '${fieldPath}' field is required`
|
||||||
|
: "This field is required";
|
||||||
} else if (errorMsg.includes("Invalid input")) {
|
} else if (errorMsg.includes("Invalid input")) {
|
||||||
errorMsg = "Invalid value";
|
errorMsg = fieldPath
|
||||||
|
? `The '${fieldPath}' field has an invalid value`
|
||||||
|
: "Invalid value";
|
||||||
|
} else if (fieldPath && !errorMsg.toLowerCase().includes(fieldPath.toLowerCase())) {
|
||||||
|
// If error message doesn't mention the field name, prepend it
|
||||||
|
errorMsg = `The '${fieldPath}' field: ${errorMsg}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formattedError = fieldPath ? `${fieldPath}: ${errorMsg}` : errorMsg;
|
console.error('[MCP Validator] ❌ Validation error:', errorMsg);
|
||||||
|
|
||||||
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,
|
||||||
error: formattedError,
|
error: errorMsg,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { differenceInDays, differenceInMinutes, format, isToday, isYesterday } from "date-fns";
|
import { differenceInDays, differenceInMinutes, format, isToday, isYesterday } from "date-fns";
|
||||||
import { ArrowLeft, Loader2, Plus } from "lucide-react";
|
import { ArrowLeft, Loader2, Plus, Server } from "lucide-react";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
@ -19,6 +20,7 @@ interface ConnectorAccountsListViewProps {
|
||||||
onManage: (connector: SearchSourceConnector) => void;
|
onManage: (connector: SearchSourceConnector) => void;
|
||||||
onAddAccount: () => void;
|
onAddAccount: () => void;
|
||||||
isConnecting?: boolean;
|
isConnecting?: boolean;
|
||||||
|
addButtonText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -70,6 +72,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
||||||
onManage,
|
onManage,
|
||||||
onAddAccount,
|
onAddAccount,
|
||||||
isConnecting = false,
|
isConnecting = false,
|
||||||
|
addButtonText,
|
||||||
}) => {
|
}) => {
|
||||||
// Get connector status
|
// Get connector status
|
||||||
const { isConnectorEnabled, getConnectorStatusMessage } = useConnectorStatus();
|
const { isConnectorEnabled, getConnectorStatusMessage } = useConnectorStatus();
|
||||||
|
|
@ -79,6 +82,20 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
||||||
|
|
||||||
// Filter connectors to only show those of this type
|
// Filter connectors to only show those of this type
|
||||||
const typeConnectors = connectors.filter((c) => c.connector_type === connectorType);
|
const typeConnectors = connectors.filter((c) => c.connector_type === connectorType);
|
||||||
|
|
||||||
|
// Determine button text - default to "Add Account" unless specified
|
||||||
|
const buttonText = addButtonText || (connectorType === EnumConnectorName.MCP_CONNECTOR ? "Add New MCP Server" : "Add Account");
|
||||||
|
const isMCP = connectorType === EnumConnectorName.MCP_CONNECTOR;
|
||||||
|
|
||||||
|
// Helper to get display name for connector (handles MCP server name extraction)
|
||||||
|
const getDisplayName = (connector: SearchSourceConnector): string => {
|
||||||
|
if (isMCP) {
|
||||||
|
// For MCP, extract server name from config if available
|
||||||
|
const serverName = connector.config?.server_config?.name || connector.name;
|
||||||
|
return serverName;
|
||||||
|
}
|
||||||
|
return getConnectorDisplayName(connector.name);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
|
|
@ -130,7 +147,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[11px] sm:text-[12px] font-medium">
|
<span className="text-[11px] sm:text-[12px] font-medium">
|
||||||
{isConnecting ? "Connecting" : "Add Account"}
|
{isConnecting ? "Connecting" : buttonText}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -139,8 +156,27 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex-1 overflow-y-auto px-6 sm:px-12 pt-0 sm:pt-6 pb-6 sm:pb-8">
|
<div className="flex-1 overflow-y-auto px-6 sm:px-12 pt-0 sm:pt-6 pb-6 sm:pb-8">
|
||||||
{/* Connected Accounts Grid */}
|
{/* Connected Accounts Grid */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
{typeConnectors.length === 0 ? (
|
||||||
{typeConnectors.map((connector) => {
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||||
|
<div className="h-16 w-16 rounded-full bg-slate-400/5 dark:bg-white/5 flex items-center justify-center mb-4">
|
||||||
|
{isMCP ? (
|
||||||
|
<Server className="h-8 w-8 text-muted-foreground" />
|
||||||
|
) : (
|
||||||
|
getConnectorIcon(connectorType, "size-8")
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<h3 className="text-sm font-medium mb-1">
|
||||||
|
{isMCP ? "No MCP Servers" : `No ${connectorTitle} Accounts`}
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-muted-foreground max-w-[280px]">
|
||||||
|
{isMCP
|
||||||
|
? "Get started by adding your first Model Context Protocol server"
|
||||||
|
: `Get started by connecting your first ${connectorTitle} account`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
|
{typeConnectors.map((connector) => {
|
||||||
const isIndexing = indexingConnectorIds.has(connector.id);
|
const isIndexing = indexingConnectorIds.has(connector.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -165,7 +201,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
||||||
</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">
|
||||||
{getConnectorDisplayName(connector.name)}
|
{getDisplayName(connector)}
|
||||||
</p>
|
</p>
|
||||||
{isIndexing ? (
|
{isIndexing ? (
|
||||||
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
|
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
|
||||||
|
|
@ -193,7 +229,8 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { Plus, Server, XCircle } from "lucide-react";
|
|
||||||
import type { FC } from "react";
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { EnumConnectorName } from "@/contracts/enums/connector";
|
|
||||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
|
||||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
|
|
||||||
interface MCPConnectorListViewProps {
|
|
||||||
mcpConnectors: SearchSourceConnector[];
|
|
||||||
onAddNew: () => void;
|
|
||||||
onManageConnector: (connector: SearchSourceConnector) => void;
|
|
||||||
onBack: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MCPConnectorListView: FC<MCPConnectorListViewProps> = ({
|
|
||||||
mcpConnectors,
|
|
||||||
onAddNew,
|
|
||||||
onManageConnector,
|
|
||||||
onBack,
|
|
||||||
}) => {
|
|
||||||
// Validate that all connectors are MCP connectors
|
|
||||||
const invalidConnectors = mcpConnectors.filter(
|
|
||||||
(c) => c.connector_type !== EnumConnectorName.MCP_CONNECTOR
|
|
||||||
);
|
|
||||||
|
|
||||||
if (invalidConnectors.length > 0) {
|
|
||||||
console.error(
|
|
||||||
"MCPConnectorListView received non-MCP connectors:",
|
|
||||||
invalidConnectors.map((c) => c.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 view can only display MCP connectors. Found {invalidConnectors.length} invalid
|
|
||||||
connector(s).
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col h-full">
|
|
||||||
{/* Header */}
|
|
||||||
<div className="flex items-center justify-between mb-6 shrink-0">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
onClick={onBack}
|
|
||||||
className="h-8 w-8"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
>
|
|
||||||
<path d="m15 18-6-6 6-6" />
|
|
||||||
</svg>
|
|
||||||
</Button>
|
|
||||||
<div>
|
|
||||||
<h2 className="text-lg sm:text-xl font-semibold">MCP Connectors</h2>
|
|
||||||
<p className="text-xs sm:text-sm text-muted-foreground">
|
|
||||||
Manage your Model Context Protocol servers
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Add New Button */}
|
|
||||||
<div className="mb-4 shrink-0">
|
|
||||||
<Button
|
|
||||||
onClick={onAddNew}
|
|
||||||
className="w-full"
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
|
||||||
Add New MCP Server
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* MCP Connectors List */}
|
|
||||||
<div className="space-y-3 flex-1 overflow-y-auto">
|
|
||||||
{mcpConnectors.length === 0 ? (
|
|
||||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
||||||
<div className="h-16 w-16 rounded-full bg-slate-400/5 dark:bg-white/5 flex items-center justify-center mb-4">
|
|
||||||
<Server className="h-8 w-8 text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
<h3 className="text-sm font-medium mb-1">No MCP Servers</h3>
|
|
||||||
<p className="text-xs text-muted-foreground max-w-[280px]">
|
|
||||||
Get started by adding your first Model Context Protocol server
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
mcpConnectors.map((connector) => {
|
|
||||||
// Extract server name from config
|
|
||||||
const serverName = connector.config?.server_config?.name || connector.name;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={connector.id}
|
|
||||||
className={cn(
|
|
||||||
"flex items-center gap-4 p-4 rounded-xl border border-border transition-all",
|
|
||||||
"bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex h-12 w-12 items-center justify-center rounded-lg border shrink-0",
|
|
||||||
"bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{getConnectorIcon("MCP_CONNECTOR", "size-6")}
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<p className="text-[14px] font-semibold leading-tight truncate">
|
|
||||||
{serverName}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<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={() => onManageConnector(connector)}
|
|
||||||
>
|
|
||||||
Manage
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue