From ffd4bb991a05f69fa2cc0cb0589ee65e43e04a4d Mon Sep 17 00:00:00 2001 From: waleed Date: Fri, 22 May 2026 13:02:39 -0700 Subject: [PATCH] improvement(kb-connectors): align connector UI surfaces --- .../(landing)/components/footer/footer.tsx | 2 +- .../[workspaceId]/knowledge/[id]/base.tsx | 36 +++-- .../add-connector-modal.tsx | 116 +++++++-------- .../connector-selector-field.tsx | 6 +- .../connectors-section/connectors-section.tsx | 134 ++++++++++-------- .../edit-connector-modal.tsx | 59 ++++---- .../components/base-card/base-card.tsx | 38 +++-- .../[workspaceId]/knowledge/knowledge.tsx | 19 ++- 8 files changed, 249 insertions(+), 161 deletions(-) diff --git a/apps/sim/app/(landing)/components/footer/footer.tsx b/apps/sim/app/(landing)/components/footer/footer.tsx index 775df7afd08..7c06dc0d85e 100644 --- a/apps/sim/app/(landing)/components/footer/footer.tsx +++ b/apps/sim/app/(landing)/components/footer/footer.tsx @@ -14,7 +14,7 @@ interface FooterItem { } const PRODUCT_LINKS: FooterItem[] = [ - { label: 'Mothership', href: 'https://docs.sim.ai', external: true }, + { label: 'Mothership', href: 'https://docs.sim.ai/mothership', external: true }, { label: 'Workflows', href: 'https://docs.sim.ai', external: true }, { label: 'Knowledge Base', href: 'https://docs.sim.ai/knowledgebase', external: true }, { label: 'Tables', href: 'https://docs.sim.ai/tables', external: true }, diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx index 65f71d7f516..301cdd26be0 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx @@ -28,6 +28,7 @@ import { } from '@/components/emcn' import { Database, DatabaseX } from '@/components/emcn/icons' import { SearchHighlight } from '@/components/ui/search-highlight' +import { cn } from '@/lib/core/utils/cn' import { ADD_CONNECTOR_SEARCH_PARAM } from '@/lib/credentials/client-state' import { ALL_TAG_SLOTS, type AllTagSlot, getFieldTypeForSlot } from '@/lib/knowledge/constants' import type { DocumentSortField, SortOrder } from '@/lib/knowledge/documents/types' @@ -920,19 +921,35 @@ export function KnowledgeBase({ const def = CONNECTOR_REGISTRY[connector.connectorType] const ConnectorIcon = def?.icon return ( - + + {connector.status === 'syncing' ? ( + + ) : ( + ConnectorIcon && + )} + {connector.status !== 'active' && connector.status !== 'syncing' && ( + + )} + + {def?.name || connector.connectorType} + ) })} @@ -1317,6 +1334,7 @@ export function KnowledgeBase({ connectors={connectors} isLoading={isLoadingConnectors} canEdit={userPermissions.canEdit} + className='mt-0' /> diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx index 875ff7d6bd4..65e255da49c 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx @@ -240,23 +240,23 @@ export function AddConnectorModal({ : `Configure the ${connectorConfig?.name} connector settings`} - + {step === 'select-type' ? ( -
-
+
+
setSearchTerm(e.target.value)} - className='h-auto flex-1 border-0 bg-transparent p-0 font-base leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0' + className='h-auto flex-1 border-0 bg-transparent p-0 leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0' />
-
-
+
+
{filteredEntries.map(([type, config]) => ( ))} {filteredEntries.length === 0 && ( -
+
{CONNECTOR_ENTRIES.length === 0 ? 'No connectors available.' : `No sources found matching "${searchTerm}"`} @@ -276,7 +276,6 @@ export function AddConnectorModal({
) : connectorConfig ? (
- {/* Auth: API key input or OAuth credential selection */} {isApiKeyMode ? (
)} - {/* Config fields */} {connectorConfig.configFields.map((field) => { if (!isFieldVisible(field)) return null @@ -357,13 +355,14 @@ export function AddConnectorModal({ {field.description && ( - + {field.description} @@ -372,13 +371,14 @@ export function AddConnectorModal({ {hasCanonicalPair && canonicalId && ( - + {field.mode === 'basic' @@ -429,48 +429,50 @@ export function AddConnectorModal({ ) })} - {/* Tag definitions (opt-out) */} {connectorConfig.tagDefinitions && connectorConfig.tagDefinitions.length > 0 && (
- {connectorConfig.tagDefinitions.map((tagDef) => ( -
toggleTagDefinition(tagDef.id)} - onKeyDown={(event) => { - if (event.target !== event.currentTarget) return - handleKeyboardActivation(event, () => toggleTagDefinition(tagDef.id)) - }} - > - e.stopPropagation()} - onCheckedChange={(checked) => { - setDisabledTagIds((prev) => { - const next = new Set(prev) - if (checked) { - next.delete(tagDef.id) - } else { - next.add(tagDef.id) - } - return next - }) +
+ {connectorConfig.tagDefinitions.map((tagDef) => ( +
toggleTagDefinition(tagDef.id)} + onKeyDown={(event) => { + if (event.target !== event.currentTarget) return + handleKeyboardActivation(event, () => toggleTagDefinition(tagDef.id)) }} - /> - {tagDef.displayName} - - ({tagDef.fieldType}) - -
- ))} + > + e.stopPropagation()} + onCheckedChange={(checked) => { + setDisabledTagIds((prev) => { + const next = new Set(prev) + if (checked) { + next.delete(tagDef.id) + } else { + next.add(tagDef.id) + } + return next + }) + }} + /> + + {tagDef.displayName} + + + {tagDef.fieldType} + +
+ ))} +
)} - {/* Sync interval */}
- -
- - {config.name} - +
+ +
+
+ {config.name} {config.description}
diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx index aab56688cad..f4ef2fbd30d 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx @@ -72,7 +72,7 @@ export function ConnectorSelectorField({ if (isLoading && isEnabled) { return ( -
+
Loading…
@@ -84,6 +84,7 @@ export function ConnectorSelectorField({ return ( onChange(values)} @@ -97,6 +98,7 @@ export function ConnectorSelectorField({ : field.placeholder || `Select ${field.title.toLowerCase()}` } disabled={disabled || !credentialId || !depsResolved} + emptyMessage={`No ${field.title.toLowerCase()} found`} /> ) } @@ -104,6 +106,7 @@ export function ConnectorSelectorField({ const singleValue = Array.isArray(value) ? value[0] : value return ( onChange(next)} @@ -117,6 +120,7 @@ export function ConnectorSelectorField({ : field.placeholder || `Select ${field.title.toLowerCase()}` } disabled={disabled || !credentialId || !depsResolved} + emptyMessage={`No ${field.title.toLowerCase()} found`} /> ) } diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.tsx index a505c8496ef..b388ff2b246 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.tsx @@ -1,6 +1,6 @@ 'use client' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react' import { createLogger } from '@sim/logger' import { format, formatDistanceToNow, isPast } from 'date-fns' import { @@ -58,6 +58,7 @@ interface ConnectorsSectionProps { connectors: ConnectorData[] isLoading: boolean canEdit: boolean + className?: string } /** 5-minute cooldown after a manual sync trigger */ @@ -68,19 +69,24 @@ const STATUS_CONFIG = { syncing: { label: 'Syncing', variant: 'amber' as const }, error: { label: 'Error', variant: 'red' as const }, paused: { label: 'Paused', variant: 'gray' as const }, - disabled: { label: 'Disabled', variant: 'amber' as const }, + disabled: { label: 'Disabled', variant: 'orange' as const }, } as const +const CONNECTOR_ACTION_BUTTON_CLASSES = + 'size-7 rounded-lg p-0 text-[var(--text-muted)] hover-hover:bg-[var(--surface-active)] hover-hover:text-[var(--text-primary)]' + export function ConnectorsSection({ workspaceId, knowledgeBaseId, connectors, isLoading, canEdit, + className, }: ConnectorsSectionProps) { const { mutate: triggerSync } = useTriggerSync() const { mutate: updateConnector } = useUpdateConnector() const { mutate: deleteConnector, isPending: isDeleting } = useDeleteConnector() + const deleteDocumentsId = useId() const [deleteTarget, setDeleteTarget] = useState(null) const [deleteDocuments, setDeleteDocuments] = useState(false) @@ -184,16 +190,16 @@ export function ConnectorsSection({ if (connectors.length === 0 && !canEdit && !isLoading) return null return ( -
+
{error &&

{error}

} {isLoading ? ( -
+
{Array.from({ length: 2 }).map((_, i) => ( -
+
- -
+ +
@@ -209,7 +215,7 @@ export function ConnectorsSection({ No connected sources yet. Connect an external source to automatically sync documents.

) : ( -
+
{connectors.map((connector) => ( Remove Connector - + This will disconnect the source and stop future syncs. Documents already synced will remain in the knowledge base unless you choose to delete them.
setDeleteDocuments(checked === true)} /> @@ -355,28 +361,35 @@ function ConnectorCard({ const syncLogs = detail?.syncLogs ?? [] return ( -
-
-
-
- {Icon && } +
+
+
+
+ {Icon && } {connector.status === 'disabled' && ( - + )}
-
-
- - {connectorDef?.name || connector.connectorType} +
+
+ + {connectorDef?.name || connector.connectorType} {(isSyncPending || connector.status === 'syncing') && ( )} - + {statusConfig.label}
-
+
{connector.lastSyncAt && ( Last sync: {format(new Date(connector.lastSyncAt), 'MMM d, h:mm a')} )} @@ -409,14 +422,14 @@ function ConnectorCard({
-
+
{canEdit && ( <> @@ -440,7 +450,11 @@ function ConnectorCard({ - @@ -451,7 +465,7 @@ function ConnectorCard({ @@ -486,11 +504,11 @@ function ConnectorCard({ @@ -500,13 +518,13 @@ function ConnectorCard({
{connector.status === 'disabled' && ( -
-
-
- +
+
+
+ Connector disabled after repeated sync failures
-

+

Syncing has been paused due to {connector.consecutiveFailures} consecutive failures. {serviceId ? ' Reconnect your account to resume syncing.' @@ -529,7 +547,8 @@ function ConnectorCard({ } setShowOAuthModal(true) }} - className='w-full px-2 py-1 font-medium text-caption' + size='sm' + className='w-full' > Reconnect @@ -539,10 +558,10 @@ function ConnectorCard({ )} {missingScopes.length > 0 && connector.status !== 'disabled' && ( -

-
-
- +
+
+
+ Additional permissions required
{canEdit && ( @@ -562,7 +581,8 @@ function ConnectorCard({ } setShowOAuthModal(true) }} - className='w-full px-2 py-1 font-medium text-caption' + size='sm' + className='w-full' > Update access @@ -572,7 +592,7 @@ function ConnectorCard({ )} {expanded && ( -
+
)} @@ -620,7 +640,7 @@ interface SyncHistoryProps { function SyncHistory({ logs, isLoading }: SyncHistoryProps) { if (isLoading) { return ( -
+
Loading sync history…
@@ -628,11 +648,15 @@ function SyncHistory({ logs, isLoading }: SyncHistoryProps) { } if (logs.length === 0) { - return

No sync history yet.

+ return ( +

+ No sync history yet. +

+ ) } return ( -
+
{logs.map((log) => { const isError = log.status === 'error' || log.status === 'failed' const isRunning = log.status === 'running' || log.status === 'syncing' @@ -640,14 +664,14 @@ function SyncHistory({ logs, isLoading }: SyncHistoryProps) { log.docsAdded + log.docsUpdated + log.docsDeleted + (log.docsFailed ?? 0) return ( -
+
{isRunning ? ( ) : isError ? ( ) : ( - + )}
@@ -661,14 +685,12 @@ function SyncHistory({ logs, isLoading }: SyncHistoryProps) { {totalChanges > 0 ? ( <> {log.docsAdded > 0 && ( - +{log.docsAdded} + +{log.docsAdded} )} {log.docsUpdated > 0 && ( <> {log.docsAdded > 0 && ' '} - - ~{log.docsUpdated} - + ~{log.docsUpdated} )} {log.docsDeleted > 0 && ( diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx index 7e2761bbdd2..44e836ca6e8 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx @@ -295,7 +295,7 @@ export function EditConnectorModal({ Documents - + - + {field.description} @@ -408,13 +409,14 @@ function SettingsTab({ {hasCanonicalPair && canonicalId && ( - + {field.mode === 'basic' ? 'Switch to manual input' : 'Switch to selector'} @@ -512,46 +514,55 @@ function DocumentsTab({ knowledgeBaseId, connectorId }: DocumentsTabProps) { if (isLoading) { return (
- - - + + + +
) } return ( -
+
setFilter(val as 'active' | 'excluded')}> Active ({counts.active}) Excluded ({counts.excluded}) -
+
{documents.length === 0 ? ( -

+

{filter === 'excluded' ? 'No excluded documents' : 'No documents yet'}

) : ( -
+
{documents.map((doc) => ( -
+
{doc.filename} {doc.sourceUrl && ( - - - + + + + + + + Open source document + )}