diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx
index 90cababca5b..a8d6a80ca83 100644
--- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx
@@ -900,7 +900,11 @@ export function KnowledgeBase({
onClick={() => setShowConnectorsModal(true)}
className='flex shrink-0 cursor-pointer items-center gap-1.5 rounded-md px-2 py-1 text-[var(--text-secondary)] text-caption shadow-[inset_0_0_0_1px_var(--border)] transition-colors hover-hover:bg-[var(--surface-3)]'
>
- {ConnectorIcon && }
+ {connector.status === 'syncing' ? (
+
+ ) : (
+ ConnectorIcon &&
+ )}
{def?.name || connector.connectorType}
)
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 5144ce5d8ab..9e266ec4e80 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
@@ -19,21 +19,17 @@ import {
ModalHeader,
Tooltip,
} from '@/components/emcn'
-import { useSession } from '@/lib/auth/auth-client'
-import { consumeOAuthReturnContext, writeOAuthReturnContext } from '@/lib/credentials/client-state'
-import {
- getCanonicalScopesForProvider,
- getProviderIdFromServiceId,
- type OAuthProvider,
-} from '@/lib/oauth'
+import { consumeOAuthReturnContext } from '@/lib/credentials/client-state'
+import { getProviderIdFromServiceId, type OAuthProvider } from '@/lib/oauth'
import { ConnectorSelectorField } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/components/connector-selector-field'
-import { OAuthRequiredModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal'
+import { ConnectCredentialModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/connect-credential-modal'
import { getDependsOnFields } from '@/blocks/utils'
import { CONNECTOR_REGISTRY } from '@/connectors/registry'
import type { ConnectorConfig, ConnectorConfigField } from '@/connectors/types'
import { useCreateConnector } from '@/hooks/queries/kb/connectors'
import { useOAuthCredentials } from '@/hooks/queries/oauth/oauth-credentials'
import type { SelectorKey } from '@/hooks/selectors/types'
+import { useCredentialRefreshTriggers } from '@/hooks/use-credential-refresh-triggers'
const SYNC_INTERVALS = [
{ label: 'Every hour', value: 60 },
@@ -69,7 +65,6 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo
const [searchTerm, setSearchTerm] = useState('')
const { workspaceId } = useParams<{ workspaceId: string }>()
- const { data: session } = useSession()
const { mutate: createConnector, isPending: isCreating } = useCreateConnector()
const connectorConfig = selectedType ? CONNECTOR_REGISTRY[selectedType] : null
@@ -82,10 +77,16 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo
[connectorConfig]
)
- const { data: credentials = [], isLoading: credentialsLoading } = useOAuthCredentials(
- connectorProviderId ?? undefined,
- { enabled: Boolean(connectorConfig) && !isApiKeyMode, workspaceId }
- )
+ const {
+ data: credentials = [],
+ isLoading: credentialsLoading,
+ refetch: refetchCredentials,
+ } = useOAuthCredentials(connectorProviderId ?? undefined, {
+ enabled: Boolean(connectorConfig) && !isApiKeyMode,
+ workspaceId,
+ })
+
+ useCredentialRefreshTriggers(refetchCredentials, connectorProviderId ?? '', workspaceId)
const effectiveCredentialId =
selectedCredentialId ?? (credentials.length === 1 ? credentials[0].id : null)
@@ -263,51 +264,9 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo
)
}
- const handleConnectNewAccount = useCallback(async () => {
- if (!connectorConfig || !connectorProviderId || !workspaceId) return
-
- const userName = session?.user?.name
- const integrationName = connectorConfig.name
- const displayName = userName ? `${userName}'s ${integrationName}` : integrationName
-
- try {
- const res = await fetch('/api/credentials/draft', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- workspaceId,
- providerId: connectorProviderId,
- displayName,
- }),
- })
- if (!res.ok) {
- setError('Failed to prepare credential. Please try again.')
- return
- }
- } catch {
- setError('Failed to prepare credential. Please try again.')
- return
- }
-
- writeOAuthReturnContext({
- origin: 'kb-connectors',
- knowledgeBaseId,
- displayName,
- providerId: connectorProviderId,
- preCount: credentials.length,
- workspaceId,
- requestedAt: Date.now(),
- })
-
+ const handleConnectNewAccount = useCallback(() => {
setShowOAuthModal(true)
- }, [
- connectorConfig,
- connectorProviderId,
- workspaceId,
- session?.user?.name,
- knowledgeBaseId,
- credentials.length,
- ])
+ }, [])
const filteredEntries = useMemo(() => {
const term = searchTerm.toLowerCase().trim()
@@ -396,40 +355,40 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo
) : (
- {credentialsLoading ? (
-
-
- Loading credentials...
-
- ) : (
-
({
- label: cred.name || cred.provider,
- value: cred.id,
- icon: connectorConfig.icon,
- })
- ),
- {
- label: 'Connect new account',
- value: '__connect_new__',
- icon: Plus,
- onSelect: () => {
- void handleConnectNewAccount()
- },
+ ({
+ label: cred.name || cred.provider,
+ value: cred.id,
+ icon: connectorConfig.icon,
+ })
+ ),
+ {
+ label:
+ credentials.length > 0
+ ? `Connect another ${connectorConfig.name} account`
+ : `Connect ${connectorConfig.name} account`,
+ value: '__connect_new__',
+ icon: Plus,
+ onSelect: () => {
+ void handleConnectNewAccount()
},
- ]}
- value={effectiveCredentialId ?? undefined}
- onChange={(value) => setSelectedCredentialId(value)}
- placeholder={
- credentials.length === 0
- ? `No ${connectorConfig.name} accounts`
- : 'Select account'
- }
- />
- )}
+ },
+ ]}
+ value={effectiveCredentialId ?? undefined}
+ onChange={(value) => setSelectedCredentialId(value)}
+ onOpenChange={(isOpen) => {
+ if (isOpen) void refetchCredentials()
+ }}
+ placeholder={
+ credentials.length === 0
+ ? `No ${connectorConfig.name} accounts`
+ : 'Select account'
+ }
+ isLoading={credentialsLoading}
+ />
)}
@@ -590,20 +549,23 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo
)}
- {connectorConfig && connectorConfig.auth.mode === 'oauth' && connectorProviderId && (
- {
- consumeOAuthReturnContext()
- setShowOAuthModal(false)
- }}
- provider={connectorProviderId}
- toolName={connectorConfig.name}
- requiredScopes={getCanonicalScopesForProvider(connectorProviderId)}
- newScopes={[]}
- serviceId={connectorConfig.auth.provider}
- />
- )}
+ {showOAuthModal &&
+ connectorConfig &&
+ connectorConfig.auth.mode === 'oauth' &&
+ connectorProviderId && (
+ {
+ consumeOAuthReturnContext()
+ setShowOAuthModal(false)
+ }}
+ provider={connectorProviderId}
+ serviceId={connectorConfig.auth.provider}
+ workspaceId={workspaceId}
+ knowledgeBaseId={knowledgeBaseId}
+ credentialCount={credentials.length}
+ />
+ )}
>
)
}
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 f7eeac07c8b..37049a212d2 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
@@ -36,6 +36,7 @@ import {
} from '@/lib/oauth'
import { getMissingRequiredScopes } from '@/lib/oauth/utils'
import { EditConnectorModal } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal'
+import { ConnectCredentialModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/connect-credential-modal'
import { OAuthRequiredModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal'
import { CONNECTOR_REGISTRY } from '@/connectors/registry'
import type { ConnectorData, SyncLogData } from '@/hooks/queries/kb/connectors'
@@ -46,6 +47,7 @@ import {
useUpdateConnector,
} from '@/hooks/queries/kb/connectors'
import { useOAuthCredentials } from '@/hooks/queries/oauth/oauth-credentials'
+import { useCredentialRefreshTriggers } from '@/hooks/use-credential-refresh-triggers'
const logger = createLogger('ConnectorsSection')
@@ -328,11 +330,16 @@ function ConnectorCard({
const requiredScopes =
connectorDef?.auth.mode === 'oauth' ? (connectorDef.auth.requiredScopes ?? []) : []
- const { data: credentials } = useOAuthCredentials(providerId, { workspaceId })
+ const { data: credentials, refetch: refetchCredentials } = useOAuthCredentials(providerId, {
+ workspaceId,
+ })
+
+ useCredentialRefreshTriggers(refetchCredentials, providerId ?? '', workspaceId)
const missingScopes = useMemo(() => {
if (!credentials || !connector.credentialId) return []
const credential = credentials.find((c) => c.id === connector.credentialId)
+ if (!credential) return []
return getMissingRequiredScopes(credential, requiredScopes)
}, [credentials, connector.credentialId, requiredScopes])
@@ -484,15 +491,17 @@ function ConnectorCard({