mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
uniform connector UX across all connector types
This commit is contained in:
parent
dfa40b8801
commit
a4bc621c2a
8 changed files with 82 additions and 61 deletions
|
|
@ -8,6 +8,7 @@ import { Spinner } from "@/components/ui/spinner";
|
|||
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { LIVE_CONNECTOR_TYPES } from "../constants/connector-constants";
|
||||
import { useConnectorStatus } from "../hooks/use-connector-status";
|
||||
import { ConnectorStatusBadge } from "./connector-status-badge";
|
||||
|
||||
|
|
@ -55,6 +56,7 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
|
|||
onManage,
|
||||
}) => {
|
||||
const isMCP = connectorType === EnumConnectorName.MCP_CONNECTOR;
|
||||
const isLive = !!connectorType && LIVE_CONNECTOR_TYPES.has(connectorType);
|
||||
// Get connector status
|
||||
const { getConnectorStatus, isConnectorEnabled, getConnectorStatusMessage, shouldShowWarnings } =
|
||||
useConnectorStatus();
|
||||
|
|
@ -123,14 +125,14 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
|
|||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span>{formatDocumentCount(documentCount)}</span>
|
||||
{!isLive && <span>{formatDocumentCount(documentCount)}</span>}
|
||||
{!isLive && accountCount !== undefined && accountCount > 0 && (
|
||||
<span className="text-muted-foreground/50">•</span>
|
||||
)}
|
||||
{accountCount !== undefined && accountCount > 0 && (
|
||||
<>
|
||||
<span className="text-muted-foreground/50">•</span>
|
||||
<span>
|
||||
{accountCount} {accountCount === 1 ? "Account" : "Accounts"}
|
||||
</span>
|
||||
</>
|
||||
<span>
|
||||
{accountCount} {accountCount === 1 ? "Account" : "Accounts"}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -53,8 +53,7 @@ export const DiscordConfig: FC<DiscordConfigProps> = ({ connector }) => {
|
|||
return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||
}, [connector?.id, fetchChannels]);
|
||||
|
||||
// Separate channels by indexing capability
|
||||
const readyToIndex = channels.filter((ch) => ch.can_index);
|
||||
const accessible = channels.filter((ch) => ch.can_index);
|
||||
const needsPermissions = channels.filter((ch) => !ch.can_index);
|
||||
|
||||
// Format last fetched time
|
||||
|
|
@ -80,7 +79,7 @@ export const DiscordConfig: FC<DiscordConfigProps> = ({ connector }) => {
|
|||
</div>
|
||||
<div className="text-xs sm:text-sm">
|
||||
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
|
||||
The bot needs "Read Message History" permission to index channels. Ask a
|
||||
The bot needs "Read Message History" permission to access channels. Ask a
|
||||
server admin to grant this permission for channels shown below.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -127,18 +126,18 @@ export const DiscordConfig: FC<DiscordConfigProps> = ({ connector }) => {
|
|||
</div>
|
||||
) : (
|
||||
<div className="rounded-xl bg-slate-400/5 dark:bg-white/5 overflow-hidden">
|
||||
{/* Ready to index */}
|
||||
{readyToIndex.length > 0 && (
|
||||
{/* Accessible channels */}
|
||||
{accessible.length > 0 && (
|
||||
<div className={cn("p-3", needsPermissions.length > 0 && "border-b border-border")}>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<CheckCircle2 className="size-3.5 text-emerald-500" />
|
||||
<span className="text-[11px] font-medium">Ready to index</span>
|
||||
<span className="text-[11px] font-medium">Accessible</span>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{readyToIndex.length} {readyToIndex.length === 1 ? "channel" : "channels"}
|
||||
{accessible.length} {accessible.length === 1 ? "channel" : "channels"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{readyToIndex.map((channel) => (
|
||||
{accessible.map((channel) => (
|
||||
<ChannelPill key={channel.id} channel={channel} />
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -150,7 +149,7 @@ export const DiscordConfig: FC<DiscordConfigProps> = ({ connector }) => {
|
|||
<div className="p-3">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<AlertCircle className="size-3.5 text-amber-500" />
|
||||
<span className="text-[11px] font-medium">Grant permissions to index</span>
|
||||
<span className="text-[11px] font-medium">Needs permissions</span>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{needsPermissions.length}{" "}
|
||||
{needsPermissions.length === 1 ? "channel" : "channels"}
|
||||
|
|
|
|||
|
|
@ -6,25 +6,23 @@ import type { ConnectorConfigProps } from "../index";
|
|||
|
||||
export const MCPServiceConfig: FC<ConnectorConfigProps> = ({ connector }) => {
|
||||
const serviceName = connector.config?.mcp_service as string | undefined;
|
||||
const displayName = serviceName
|
||||
? serviceName.charAt(0).toUpperCase() + serviceName.slice(1)
|
||||
: "this service";
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-xl border border-border bg-primary/5 p-4 flex items-start gap-3">
|
||||
<div className="rounded-xl border border-border bg-emerald-500/5 p-4 flex items-start gap-3">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-500/10 shrink-0 mt-0.5">
|
||||
<CheckCircle2 className="size-4 text-emerald-500" />
|
||||
</div>
|
||||
<div className="text-xs sm:text-sm">
|
||||
<p className="font-medium text-xs sm:text-sm">Connected via MCP</p>
|
||||
<p className="font-medium text-xs sm:text-sm">Connected</p>
|
||||
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
|
||||
Your agent can search, read, and take actions in{" "}
|
||||
{serviceName
|
||||
? serviceName.charAt(0).toUpperCase() + serviceName.slice(1)
|
||||
: "this service"}{" "}
|
||||
in real time. No background indexing needed.
|
||||
Your agent can search, read, and take actions in {displayName}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</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">
|
||||
SurfSense will index messages from Teams channels that you have access to. The app can
|
||||
only read messages from teams and channels where you are a member. Make sure you're a
|
||||
member of the teams you want to index before connecting.
|
||||
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,6 +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 } from "../../constants/connector-constants";
|
||||
import { getConnectorDisplayName } from "../../tabs/all-connectors-tab";
|
||||
import { type ConnectorConfigProps, getConnectorConfigComponent } from "../index";
|
||||
|
||||
|
|
@ -119,6 +120,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
}, [searchSpaceId, searchSpaceIdAtom, reauthEndpoint, connector.id]);
|
||||
|
||||
const isMCPBacked = Boolean(connector.config?.server_config);
|
||||
const isLive = isMCPBacked || LIVE_CONNECTOR_TYPES.has(connector.connector_type);
|
||||
|
||||
// Get connector-specific config component (MCP-backed connectors use a generic view)
|
||||
const ConnectorConfigComponent = useMemo(() => {
|
||||
|
|
@ -228,8 +230,8 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
{getConnectorDisplayName(connector.name)}
|
||||
</h2>
|
||||
<p className="text-xs sm:text-base text-muted-foreground mt-1">
|
||||
{isMCPBacked
|
||||
? "Connected — your agent can interact with this service in real time"
|
||||
{isLive
|
||||
? "Manage your connected account"
|
||||
: "Manage your connector settings and sync configuration"}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -381,10 +383,12 @@ 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">
|
||||
Are you sure?
|
||||
{isLive
|
||||
? "Your agent will lose access to this service."
|
||||
: "This will remove all indexed data."}
|
||||
</span>
|
||||
<div className="flex items-center gap-2 sm:gap-3">
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -1,5 +1,22 @@
|
|||
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||
|
||||
/**
|
||||
* Connectors that operate in real time (no background indexing).
|
||||
* Used to adjust UI: hide sync controls, show "Connected" instead of doc counts.
|
||||
*/
|
||||
export const LIVE_CONNECTOR_TYPES = new Set<string>([
|
||||
EnumConnectorName.LINEAR_CONNECTOR,
|
||||
EnumConnectorName.SLACK_CONNECTOR,
|
||||
EnumConnectorName.JIRA_CONNECTOR,
|
||||
EnumConnectorName.CLICKUP_CONNECTOR,
|
||||
EnumConnectorName.AIRTABLE_CONNECTOR,
|
||||
EnumConnectorName.DISCORD_CONNECTOR,
|
||||
EnumConnectorName.TEAMS_CONNECTOR,
|
||||
EnumConnectorName.GOOGLE_CALENDAR_CONNECTOR,
|
||||
EnumConnectorName.GOOGLE_GMAIL_CONNECTOR,
|
||||
EnumConnectorName.LUMA_CONNECTOR,
|
||||
]);
|
||||
|
||||
// OAuth Connectors (Quick Connect)
|
||||
export const OAUTH_CONNECTORS = [
|
||||
{
|
||||
|
|
@ -13,7 +30,7 @@ export const OAUTH_CONNECTORS = [
|
|||
{
|
||||
id: "google-gmail-connector",
|
||||
title: "Gmail",
|
||||
description: "Search through your emails",
|
||||
description: "Search and read your emails",
|
||||
connectorType: EnumConnectorName.GOOGLE_GMAIL_CONNECTOR,
|
||||
authEndpoint: "/api/v1/auth/google/gmail/connector/add/",
|
||||
selfHostedOnly: true,
|
||||
|
|
@ -21,7 +38,7 @@ export const OAUTH_CONNECTORS = [
|
|||
{
|
||||
id: "google-calendar-connector",
|
||||
title: "Google Calendar",
|
||||
description: "Search through your events",
|
||||
description: "Search and manage your events",
|
||||
connectorType: EnumConnectorName.GOOGLE_CALENDAR_CONNECTOR,
|
||||
authEndpoint: "/api/v1/auth/google/calendar/connector/add/",
|
||||
selfHostedOnly: true,
|
||||
|
|
@ -29,7 +46,7 @@ export const OAUTH_CONNECTORS = [
|
|||
{
|
||||
id: "airtable-connector",
|
||||
title: "Airtable",
|
||||
description: "Search your Airtable bases",
|
||||
description: "Search, read, and manage records",
|
||||
connectorType: EnumConnectorName.AIRTABLE_CONNECTOR,
|
||||
authEndpoint: "/api/v1/auth/mcp/airtable/connector/add/",
|
||||
},
|
||||
|
|
@ -43,21 +60,21 @@ export const OAUTH_CONNECTORS = [
|
|||
{
|
||||
id: "linear-connector",
|
||||
title: "Linear",
|
||||
description: "Search issues & projects",
|
||||
description: "Search, read, and manage issues & projects",
|
||||
connectorType: EnumConnectorName.LINEAR_CONNECTOR,
|
||||
authEndpoint: "/api/v1/auth/mcp/linear/connector/add/",
|
||||
},
|
||||
{
|
||||
id: "slack-connector",
|
||||
title: "Slack",
|
||||
description: "Search Slack messages",
|
||||
description: "Search, read, and send messages",
|
||||
connectorType: EnumConnectorName.SLACK_CONNECTOR,
|
||||
authEndpoint: "/api/v1/auth/mcp/slack/connector/add/",
|
||||
},
|
||||
{
|
||||
id: "teams-connector",
|
||||
title: "Microsoft Teams",
|
||||
description: "Search Teams messages",
|
||||
description: "Search, read, and send messages",
|
||||
connectorType: EnumConnectorName.TEAMS_CONNECTOR,
|
||||
authEndpoint: "/api/v1/auth/teams/connector/add/",
|
||||
},
|
||||
|
|
@ -78,14 +95,14 @@ export const OAUTH_CONNECTORS = [
|
|||
{
|
||||
id: "discord-connector",
|
||||
title: "Discord",
|
||||
description: "Search Discord messages",
|
||||
description: "Search, read, and send messages",
|
||||
connectorType: EnumConnectorName.DISCORD_CONNECTOR,
|
||||
authEndpoint: "/api/v1/auth/discord/connector/add/",
|
||||
},
|
||||
{
|
||||
id: "jira-connector",
|
||||
title: "Jira",
|
||||
description: "Search Jira issues",
|
||||
description: "Search, read, and manage issues",
|
||||
connectorType: EnumConnectorName.JIRA_CONNECTOR,
|
||||
authEndpoint: "/api/v1/auth/mcp/jira/connector/add/",
|
||||
},
|
||||
|
|
@ -99,7 +116,7 @@ export const OAUTH_CONNECTORS = [
|
|||
{
|
||||
id: "clickup-connector",
|
||||
title: "ClickUp",
|
||||
description: "Search ClickUp tasks",
|
||||
description: "Search, read, and manage tasks",
|
||||
connectorType: EnumConnectorName.CLICKUP_CONNECTOR,
|
||||
authEndpoint: "/api/v1/auth/mcp/clickup/connector/add/",
|
||||
},
|
||||
|
|
@ -138,7 +155,7 @@ export const OTHER_CONNECTORS = [
|
|||
{
|
||||
id: "luma-connector",
|
||||
title: "Luma",
|
||||
description: "Search Luma events",
|
||||
description: "Search and manage events",
|
||||
connectorType: EnumConnectorName.LUMA_CONNECTOR,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ 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, 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";
|
||||
|
||||
|
|
@ -156,6 +156,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
|
|||
{/* OAuth Connectors - Grouped by Type */}
|
||||
{filteredOAuthConnectorTypes.map(([connectorType, typeConnectors]) => {
|
||||
const { title } = getOAuthConnectorTypeInfo(connectorType);
|
||||
const isLive = LIVE_CONNECTOR_TYPES.has(connectorType);
|
||||
const isAnyIndexing = typeConnectors.some((c: SearchSourceConnector) =>
|
||||
indexingConnectorIds.has(c.id)
|
||||
);
|
||||
|
|
@ -202,8 +203,12 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
|
|||
</p>
|
||||
) : (
|
||||
<p className="text-[10px] text-muted-foreground mt-1 flex items-center gap-1.5">
|
||||
<span>{formatDocumentCount(documentCount)}</span>
|
||||
<span className="text-muted-foreground/50">•</span>
|
||||
{!isLive && (
|
||||
<>
|
||||
<span>{formatDocumentCount(documentCount)}</span>
|
||||
<span className="text-muted-foreground/50">•</span>
|
||||
</>
|
||||
)}
|
||||
<span>
|
||||
{accountCount} {accountCount === 1 ? "Account" : "Accounts"}
|
||||
</span>
|
||||
|
|
@ -230,6 +235,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
|
|||
documentTypeCounts
|
||||
);
|
||||
const isMCPConnector = connector.connector_type === "MCP_CONNECTOR";
|
||||
const isLive = LIVE_CONNECTOR_TYPES.has(connector.connector_type);
|
||||
return (
|
||||
<div
|
||||
key={`connector-${connector.id}`}
|
||||
|
|
@ -261,7 +267,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
|
|||
<Spinner size="xs" />
|
||||
Syncing
|
||||
</p>
|
||||
) : !isMCPConnector ? (
|
||||
) : !isLive && !isMCPConnector ? (
|
||||
<p className="text-[10px] text-muted-foreground mt-1">
|
||||
{formatDocumentCount(documentCount)}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -13,6 +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 } from "../constants/connector-constants";
|
||||
import { useConnectorStatus } from "../hooks/use-connector-status";
|
||||
import { getConnectorDisplayName } from "../tabs/all-connectors-tab";
|
||||
|
||||
|
|
@ -43,12 +44,8 @@ interface ConnectorAccountsListViewProps {
|
|||
addButtonText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a connector type is indexable
|
||||
*/
|
||||
function isIndexableConnector(connectorType: string): boolean {
|
||||
const nonIndexableTypes = ["MCP_CONNECTOR"];
|
||||
return !nonIndexableTypes.includes(connectorType);
|
||||
function isLiveConnector(connectorType: string): boolean {
|
||||
return LIVE_CONNECTOR_TYPES.has(connectorType) || connectorType === "MCP_CONNECTOR";
|
||||
}
|
||||
|
||||
export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
||||
|
|
@ -149,7 +146,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
{connectorTitle}
|
||||
</h2>
|
||||
<p className="text-xs sm:text-base text-muted-foreground mt-1">
|
||||
{statusMessage || "Manage your connector settings and sync configuration"}
|
||||
{statusMessage || "Manage your connected accounts"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -234,15 +231,13 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
<Spinner size="xs" />
|
||||
Syncing
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-[10px] text-muted-foreground mt-1 whitespace-nowrap truncate">
|
||||
{isIndexableConnector(connector.connector_type)
|
||||
? connector.last_indexed_at
|
||||
? `Last indexed: ${formatRelativeDate(connector.last_indexed_at)}`
|
||||
: "Never indexed"
|
||||
: "Active"}
|
||||
) : !isLiveConnector(connector.connector_type) ? (
|
||||
<p className="text-[10px] mt-1 whitespace-nowrap truncate text-muted-foreground">
|
||||
{connector.last_indexed_at
|
||||
? `Last indexed: ${formatRelativeDate(connector.last_indexed_at)}`
|
||||
: "Never indexed"}
|
||||
</p>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
{isAuthExpired ? (
|
||||
<Button
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue