mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
fix(knowledge): fix document processing stuck in processing state (#3857)
* fix(knowledge): fix document processing stuck in processing state * fix(knowledge): use Promise.allSettled for document dispatch and fix Copilot OAuth context - Change Promise.all to Promise.allSettled in processDocumentsWithQueue so one failed dispatch doesn't abort the entire batch - Add writeOAuthReturnContext before showing LazyOAuthRequiredModal from Copilot tools so useOAuthReturnForWorkflow can handle the return - Add consumeOAuthReturnContext on modal close to clean up stale context * fix(knowledge): fix type error in useCredentialRefreshTriggers call Pass empty string instead of undefined for connectorProviderId fallback to match the hook's string parameter type. * upgrade turbo * fix(knowledge): fix type error in connectors-section useCredentialRefreshTriggers call Same string narrowing fix as add-connector-modal — pass empty string fallback for providerId.
This commit is contained in:
@@ -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 && <ConnectorIcon className='h-[14px] w-[14px]' />}
|
||||
{connector.status === 'syncing' ? (
|
||||
<Loader2 className='h-[14px] w-[14px] animate-spin' />
|
||||
) : (
|
||||
ConnectorIcon && <ConnectorIcon className='h-[14px] w-[14px]' />
|
||||
)}
|
||||
{def?.name || connector.connectorType}
|
||||
</button>
|
||||
)
|
||||
|
||||
@@ -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
|
||||
) : (
|
||||
<div className='flex flex-col gap-2'>
|
||||
<Label>Account</Label>
|
||||
{credentialsLoading ? (
|
||||
<div className='flex items-center gap-2 text-[var(--text-muted)] text-small'>
|
||||
<Loader2 className='h-4 w-4 animate-spin' />
|
||||
Loading credentials...
|
||||
</div>
|
||||
) : (
|
||||
<Combobox
|
||||
size='sm'
|
||||
options={[
|
||||
...credentials.map(
|
||||
(cred): ComboboxOption => ({
|
||||
label: cred.name || cred.provider,
|
||||
value: cred.id,
|
||||
icon: connectorConfig.icon,
|
||||
})
|
||||
),
|
||||
{
|
||||
label: 'Connect new account',
|
||||
value: '__connect_new__',
|
||||
icon: Plus,
|
||||
onSelect: () => {
|
||||
void handleConnectNewAccount()
|
||||
},
|
||||
<Combobox
|
||||
size='sm'
|
||||
options={[
|
||||
...credentials.map(
|
||||
(cred): ComboboxOption => ({
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -590,20 +549,23 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
{connectorConfig && connectorConfig.auth.mode === 'oauth' && connectorProviderId && (
|
||||
<OAuthRequiredModal
|
||||
isOpen={showOAuthModal}
|
||||
onClose={() => {
|
||||
consumeOAuthReturnContext()
|
||||
setShowOAuthModal(false)
|
||||
}}
|
||||
provider={connectorProviderId}
|
||||
toolName={connectorConfig.name}
|
||||
requiredScopes={getCanonicalScopesForProvider(connectorProviderId)}
|
||||
newScopes={[]}
|
||||
serviceId={connectorConfig.auth.provider}
|
||||
/>
|
||||
)}
|
||||
{showOAuthModal &&
|
||||
connectorConfig &&
|
||||
connectorConfig.auth.mode === 'oauth' &&
|
||||
connectorProviderId && (
|
||||
<ConnectCredentialModal
|
||||
isOpen={showOAuthModal}
|
||||
onClose={() => {
|
||||
consumeOAuthReturnContext()
|
||||
setShowOAuthModal(false)
|
||||
}}
|
||||
provider={connectorProviderId}
|
||||
serviceId={connectorConfig.auth.provider}
|
||||
workspaceId={workspaceId}
|
||||
knowledgeBaseId={knowledgeBaseId}
|
||||
credentialCount={credentials.length}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
<Button
|
||||
variant='active'
|
||||
onClick={() => {
|
||||
writeOAuthReturnContext({
|
||||
origin: 'kb-connectors',
|
||||
knowledgeBaseId,
|
||||
displayName: connectorDef?.name ?? connector.connectorType,
|
||||
providerId: providerId!,
|
||||
preCount: credentials?.length ?? 0,
|
||||
workspaceId,
|
||||
requestedAt: Date.now(),
|
||||
})
|
||||
if (connector.credentialId) {
|
||||
writeOAuthReturnContext({
|
||||
origin: 'kb-connectors',
|
||||
knowledgeBaseId,
|
||||
displayName: connectorDef?.name ?? connector.connectorType,
|
||||
providerId: providerId!,
|
||||
preCount: credentials?.length ?? 0,
|
||||
workspaceId,
|
||||
requestedAt: Date.now(),
|
||||
})
|
||||
}
|
||||
setShowOAuthModal(true)
|
||||
}}
|
||||
className='w-full px-2 py-1 font-medium text-caption'
|
||||
@@ -510,7 +519,22 @@ function ConnectorCard({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showOAuthModal && serviceId && providerId && (
|
||||
{showOAuthModal && serviceId && providerId && !connector.credentialId && (
|
||||
<ConnectCredentialModal
|
||||
isOpen={showOAuthModal}
|
||||
onClose={() => {
|
||||
consumeOAuthReturnContext()
|
||||
setShowOAuthModal(false)
|
||||
}}
|
||||
provider={providerId as OAuthProvider}
|
||||
serviceId={serviceId}
|
||||
workspaceId={workspaceId}
|
||||
knowledgeBaseId={knowledgeBaseId}
|
||||
credentialCount={credentials?.length ?? 0}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showOAuthModal && serviceId && providerId && connector.credentialId && (
|
||||
<OAuthRequiredModal
|
||||
isOpen={showOAuthModal}
|
||||
onClose={() => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
ModalHeader,
|
||||
} from '@/components/emcn'
|
||||
import { client } from '@/lib/auth/auth-client'
|
||||
import type { OAuthReturnContext } from '@/lib/credentials/client-state'
|
||||
import { writeOAuthReturnContext } from '@/lib/credentials/client-state'
|
||||
import {
|
||||
getCanonicalScopesForProvider,
|
||||
@@ -27,17 +28,22 @@ import { useCreateCredentialDraft } from '@/hooks/queries/credentials'
|
||||
|
||||
const logger = createLogger('ConnectCredentialModal')
|
||||
|
||||
export interface ConnectCredentialModalProps {
|
||||
interface ConnectCredentialModalBaseProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
provider: OAuthProvider
|
||||
serviceId: string
|
||||
workspaceId: string
|
||||
workflowId: string
|
||||
/** Number of existing credentials for this provider — used to detect a successful new connection. */
|
||||
credentialCount: number
|
||||
}
|
||||
|
||||
export type ConnectCredentialModalProps = ConnectCredentialModalBaseProps &
|
||||
(
|
||||
| { workflowId: string; knowledgeBaseId?: never }
|
||||
| { workflowId?: never; knowledgeBaseId: string }
|
||||
)
|
||||
|
||||
export function ConnectCredentialModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
@@ -45,6 +51,7 @@ export function ConnectCredentialModal({
|
||||
serviceId,
|
||||
workspaceId,
|
||||
workflowId,
|
||||
knowledgeBaseId,
|
||||
credentialCount,
|
||||
}: ConnectCredentialModalProps) {
|
||||
const [displayName, setDisplayName] = useState('')
|
||||
@@ -97,15 +104,19 @@ export function ConnectCredentialModal({
|
||||
try {
|
||||
await createDraft.mutateAsync({ workspaceId, providerId, displayName: trimmedName })
|
||||
|
||||
writeOAuthReturnContext({
|
||||
origin: 'workflow',
|
||||
workflowId,
|
||||
const baseContext = {
|
||||
displayName: trimmedName,
|
||||
providerId,
|
||||
preCount: credentialCount,
|
||||
workspaceId,
|
||||
requestedAt: Date.now(),
|
||||
})
|
||||
}
|
||||
|
||||
const returnContext: OAuthReturnContext = knowledgeBaseId
|
||||
? { ...baseContext, origin: 'kb-connectors' as const, knowledgeBaseId }
|
||||
: { ...baseContext, origin: 'workflow' as const, workflowId: workflowId! }
|
||||
|
||||
writeOAuthReturnContext(returnContext)
|
||||
|
||||
if (providerId === 'trello') {
|
||||
window.location.href = '/api/auth/trello/authorize'
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Button, Combobox } from '@/components/emcn/components'
|
||||
import { getSubscriptionAccessState } from '@/lib/billing/client'
|
||||
import { getEnv, isTruthy } from '@/lib/core/config/env'
|
||||
import { getPollingProviderFromOAuth } from '@/lib/credential-sets/providers'
|
||||
import { consumeOAuthReturnContext, writeOAuthReturnContext } from '@/lib/credentials/client-state'
|
||||
import {
|
||||
getCanonicalScopesForProvider,
|
||||
getProviderIdFromServiceId,
|
||||
@@ -357,7 +358,18 @@ export function CredentialSelector({
|
||||
</div>
|
||||
<Button
|
||||
variant='active'
|
||||
onClick={() => setShowOAuthModal(true)}
|
||||
onClick={() => {
|
||||
writeOAuthReturnContext({
|
||||
origin: 'workflow',
|
||||
workflowId: activeWorkflowId || '',
|
||||
displayName: selectedCredential?.name ?? getProviderName(provider),
|
||||
providerId: effectiveProviderId,
|
||||
preCount: credentials.length,
|
||||
workspaceId,
|
||||
requestedAt: Date.now(),
|
||||
})
|
||||
setShowOAuthModal(true)
|
||||
}}
|
||||
className='w-full px-2 py-1 font-medium text-caption'
|
||||
>
|
||||
Update access
|
||||
@@ -380,7 +392,10 @@ export function CredentialSelector({
|
||||
{showOAuthModal && (
|
||||
<OAuthRequiredModal
|
||||
isOpen={showOAuthModal}
|
||||
onClose={() => setShowOAuthModal(false)}
|
||||
onClose={() => {
|
||||
consumeOAuthReturnContext()
|
||||
setShowOAuthModal(false)
|
||||
}}
|
||||
provider={provider}
|
||||
toolName={getProviderName(provider)}
|
||||
requiredScopes={getCanonicalScopesForProvider(effectiveProviderId)}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createElement, useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { Button, Combobox } from '@/components/emcn/components'
|
||||
import { consumeOAuthReturnContext, writeOAuthReturnContext } from '@/lib/credentials/client-state'
|
||||
import {
|
||||
getCanonicalScopesForProvider,
|
||||
getProviderIdFromServiceId,
|
||||
@@ -222,7 +223,18 @@ export function ToolCredentialSelector({
|
||||
</div>
|
||||
<Button
|
||||
variant='active'
|
||||
onClick={() => setShowOAuthModal(true)}
|
||||
onClick={() => {
|
||||
writeOAuthReturnContext({
|
||||
origin: 'workflow',
|
||||
workflowId: effectiveWorkflowId || '',
|
||||
displayName: selectedCredential?.name ?? getProviderName(provider),
|
||||
providerId: effectiveProviderId,
|
||||
preCount: credentials.length,
|
||||
workspaceId,
|
||||
requestedAt: Date.now(),
|
||||
})
|
||||
setShowOAuthModal(true)
|
||||
}}
|
||||
className='w-full px-2 py-1 font-medium text-caption'
|
||||
>
|
||||
Update access
|
||||
@@ -245,7 +257,10 @@ export function ToolCredentialSelector({
|
||||
{showOAuthModal && (
|
||||
<OAuthRequiredModal
|
||||
isOpen={showOAuthModal}
|
||||
onClose={() => setShowOAuthModal(false)}
|
||||
onClose={() => {
|
||||
consumeOAuthReturnContext()
|
||||
setShowOAuthModal(false)
|
||||
}}
|
||||
provider={provider}
|
||||
toolName={getProviderName(provider)}
|
||||
requiredScopes={getCanonicalScopesForProvider(effectiveProviderId)}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useSession } from '@/lib/auth/auth-client'
|
||||
import type { OAuthConnectEventDetail } from '@/lib/copilot/tools/client/base-tool'
|
||||
import { consumeOAuthReturnContext, writeOAuthReturnContext } from '@/lib/credentials/client-state'
|
||||
import type { OAuthProvider } from '@/lib/oauth'
|
||||
import { BLOCK_DIMENSIONS, CONTAINER_DIMENSIONS } from '@/lib/workflows/blocks/block-dimensions'
|
||||
import { TriggerUtils } from '@/lib/workflows/triggers/triggers'
|
||||
@@ -478,6 +479,17 @@ const WorkflowContent = React.memo(
|
||||
const handleOpenOAuthConnect = (event: Event) => {
|
||||
const detail = (event as CustomEvent<OAuthConnectEventDetail>).detail
|
||||
if (!detail) return
|
||||
|
||||
writeOAuthReturnContext({
|
||||
origin: 'workflow',
|
||||
workflowId: workflowIdParam,
|
||||
displayName: detail.providerName,
|
||||
providerId: detail.providerId,
|
||||
preCount: 0,
|
||||
workspaceId,
|
||||
requestedAt: Date.now(),
|
||||
})
|
||||
|
||||
setOauthModal({
|
||||
provider: detail.providerId as OAuthProvider,
|
||||
serviceId: detail.serviceId,
|
||||
@@ -490,7 +502,7 @@ const WorkflowContent = React.memo(
|
||||
window.addEventListener('open-oauth-connect', handleOpenOAuthConnect as EventListener)
|
||||
return () =>
|
||||
window.removeEventListener('open-oauth-connect', handleOpenOAuthConnect as EventListener)
|
||||
}, [])
|
||||
}, [workflowIdParam, workspaceId])
|
||||
|
||||
const { diffAnalysis, isShowingDiff, isDiffReady, reapplyDiffMarkers, hasActiveDiff } =
|
||||
useWorkflowDiffStore(
|
||||
@@ -4103,7 +4115,10 @@ const WorkflowContent = React.memo(
|
||||
<Suspense fallback={null}>
|
||||
<LazyOAuthRequiredModal
|
||||
isOpen={true}
|
||||
onClose={() => setOauthModal(null)}
|
||||
onClose={() => {
|
||||
consumeOAuthReturnContext()
|
||||
setOauthModal(null)
|
||||
}}
|
||||
provider={oauthModal.provider}
|
||||
toolName={oauthModal.providerName}
|
||||
serviceId={oauthModal.serviceId}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { db } from '@sim/db'
|
||||
import {
|
||||
document,
|
||||
embedding,
|
||||
knowledgeBase,
|
||||
knowledgeConnector,
|
||||
knowledgeConnectorSyncLog,
|
||||
@@ -658,6 +659,23 @@ export async function executeSync(
|
||||
if (stuckDocs.length > 0) {
|
||||
logger.info(`Retrying ${stuckDocs.length} stuck documents`, { connectorId })
|
||||
try {
|
||||
const stuckDocIds = stuckDocs.map((doc) => doc.id)
|
||||
|
||||
await db.delete(embedding).where(inArray(embedding.documentId, stuckDocIds))
|
||||
|
||||
await db
|
||||
.update(document)
|
||||
.set({
|
||||
processingStatus: 'pending',
|
||||
processingStartedAt: null,
|
||||
processingCompletedAt: null,
|
||||
processingError: null,
|
||||
chunkCount: 0,
|
||||
tokenCount: 0,
|
||||
characterCount: 0,
|
||||
})
|
||||
.where(inArray(document.id, stuckDocIds))
|
||||
|
||||
await processDocumentsWithQueue(
|
||||
stuckDocs.map((doc) => ({
|
||||
documentId: doc.id,
|
||||
|
||||
@@ -156,17 +156,12 @@ export async function dispatchDocumentProcessingJob(payload: DocumentJobData): P
|
||||
return
|
||||
}
|
||||
|
||||
void processDocumentAsync(
|
||||
await processDocumentAsync(
|
||||
payload.knowledgeBaseId,
|
||||
payload.documentId,
|
||||
payload.docData,
|
||||
payload.processingOptions
|
||||
).catch((error) => {
|
||||
logger.error(`[${payload.requestId}] Direct document processing failed`, {
|
||||
documentId: payload.documentId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface DocumentTagData {
|
||||
@@ -385,9 +380,27 @@ export async function processDocumentsWithQueue(
|
||||
}
|
||||
)
|
||||
|
||||
await Promise.all(jobPayloads.map((payload) => dispatchDocumentProcessingJob(payload)))
|
||||
const results = await Promise.allSettled(
|
||||
jobPayloads.map((payload) => dispatchDocumentProcessingJob(payload))
|
||||
)
|
||||
|
||||
const failures = results.filter((r): r is PromiseRejectedResult => r.status === 'rejected')
|
||||
if (failures.length > 0) {
|
||||
logger.error(`[${requestId}] ${failures.length}/${results.length} document dispatches failed`, {
|
||||
errors: failures.map((f) =>
|
||||
f.reason instanceof Error ? f.reason.message : String(f.reason)
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Document dispatch complete: ${results.length - failures.length}/${results.length} succeeded`
|
||||
)
|
||||
|
||||
if (failures.length === results.length) {
|
||||
throw new Error(`All ${failures.length} document processing dispatches failed`)
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] All documents dispatched for processing`)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -434,6 +447,7 @@ export async function processDocumentAsync(
|
||||
.set({
|
||||
processingStatus: 'processing',
|
||||
processingStartedAt: new Date(),
|
||||
processingCompletedAt: null,
|
||||
processingError: null,
|
||||
})
|
||||
.where(
|
||||
@@ -624,8 +638,9 @@ export async function processDocumentAsync(
|
||||
logger.info(`[${documentId}] Successfully processed document in ${processingTime}ms`)
|
||||
} catch (error) {
|
||||
const processingTime = Date.now() - startTime
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
||||
logger.error(`[${documentId}] Failed to process document after ${processingTime}ms:`, {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
error: errorMessage,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
filename: docData.filename,
|
||||
fileUrl: docData.fileUrl,
|
||||
@@ -636,10 +651,12 @@ export async function processDocumentAsync(
|
||||
.update(document)
|
||||
.set({
|
||||
processingStatus: 'failed',
|
||||
processingError: error instanceof Error ? error.message : 'Unknown error',
|
||||
processingError: errorMessage,
|
||||
processingCompletedAt: new Date(),
|
||||
})
|
||||
.where(eq(document.id, documentId))
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1527,7 +1544,7 @@ export async function markDocumentAsFailedTimeout(
|
||||
.update(document)
|
||||
.set({
|
||||
processingStatus: 'failed',
|
||||
processingError: 'Processing timed out - background process may have been terminated',
|
||||
processingError: 'Processing timed out. Please retry or re-sync the connector.',
|
||||
processingCompletedAt: now,
|
||||
})
|
||||
.where(eq(document.id, documentId))
|
||||
|
||||
@@ -101,6 +101,8 @@ async function getEmbeddingConfig(
|
||||
}
|
||||
}
|
||||
|
||||
const EMBEDDING_REQUEST_TIMEOUT_MS = 60_000
|
||||
|
||||
async function callEmbeddingAPI(inputs: string[], config: EmbeddingConfig): Promise<number[][]> {
|
||||
return retryWithExponentialBackoff(
|
||||
async () => {
|
||||
@@ -119,11 +121,15 @@ async function callEmbeddingAPI(inputs: string[], config: EmbeddingConfig): Prom
|
||||
...(useDimensions && { dimensions: EMBEDDING_DIMENSIONS }),
|
||||
}
|
||||
|
||||
const controller = new AbortController()
|
||||
const timeout = setTimeout(() => controller.abort(), EMBEDDING_REQUEST_TIMEOUT_MS)
|
||||
|
||||
const response = await fetch(config.apiUrl, {
|
||||
method: 'POST',
|
||||
headers: config.headers,
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
signal: controller.signal,
|
||||
}).finally(() => clearTimeout(timeout))
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
|
||||
@@ -1504,7 +1504,9 @@ export async function refreshOAuthToken(
|
||||
refreshToken: newRefreshToken || refreshToken, // Return new refresh token if available
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error refreshing token:', { error })
|
||||
logger.error('Error refreshing token:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
16
bun.lock
16
bun.lock
@@ -10,7 +10,7 @@
|
||||
"glob": "13.0.0",
|
||||
"husky": "9.1.7",
|
||||
"lint-staged": "16.0.0",
|
||||
"turbo": "2.8.20",
|
||||
"turbo": "2.9.1",
|
||||
},
|
||||
},
|
||||
"apps/docs": {
|
||||
@@ -1483,17 +1483,17 @@
|
||||
|
||||
"@trigger.dev/sdk": ["@trigger.dev/sdk@4.4.3", "", { "dependencies": { "@opentelemetry/api": "1.9.0", "@opentelemetry/semantic-conventions": "1.36.0", "@trigger.dev/core": "4.4.3", "chalk": "^5.2.0", "cronstrue": "^2.21.0", "debug": "^4.3.4", "evt": "^2.4.13", "slug": "^6.0.0", "ulid": "^2.3.0", "uncrypto": "^0.1.3", "uuid": "^9.0.0", "ws": "^8.11.0" }, "peerDependencies": { "ai": "^4.2.0 || ^5.0.0 || ^6.0.0", "zod": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["ai"] }, "sha512-ghJkak+PTBJJ9HiHMcnahJmzjsgCzYiIHu5Qj5R7I9q5LS6i7mkx169rB/tOE9HLadd4HSu3yYA5DrH4wXhZuw=="],
|
||||
|
||||
"@turbo/darwin-64": ["@turbo/darwin-64@2.8.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-FQ9EX1xMU5nbwjxXxM3yU88AQQ6Sqc6S44exPRroMcx9XZHqqppl5ymJF0Ig/z3nvQNwDmz1Gsnvxubo+nXWjQ=="],
|
||||
"@turbo/darwin-64": ["@turbo/darwin-64@2.9.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-d1zTcIf6VWT7cdfjhi0X36C2PRsUi2HdEwYzVgkLHmuuYtL+1Y1Zu3JdlouoB/NjG2vX3q4NnKLMNhDOEweoIg=="],
|
||||
|
||||
"@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.8.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gpyh9ATFGThD6/s9L95YWY54cizg/VRWl2B67h0yofG8BpHf67DFAh9nuJVKG7bY0+SBJDAo5cMur+wOl9YOYw=="],
|
||||
"@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.9.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-AwJ4mA++Kpem33Lcov093hS1LrgqbKxqq5FCReoqsA8ayEG6eAJAo8ItDd9qQTdBiXxZH8GHCspLAMIe1t3Xyw=="],
|
||||
|
||||
"@turbo/linux-64": ["@turbo/linux-64@2.8.20", "", { "os": "linux", "cpu": "x64" }, "sha512-p2QxWUYyYUgUFG0b0kR+pPi8t7c9uaVlRtjTTI1AbCvVqkpjUfCcReBn6DgG/Hu8xrWdKLuyQFaLYFzQskZbcA=="],
|
||||
"@turbo/linux-64": ["@turbo/linux-64@2.9.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HT9SjKkjEw9uvlgly/qwCGEm4wOXOwQPSPS+wkg+/O1Qan3F1uU/0PFYzxl3m4lfuV3CP9wr2Dq5dPrUX+B9Ag=="],
|
||||
|
||||
"@turbo/linux-arm64": ["@turbo/linux-arm64@2.8.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-Gn5yjlZGLRZWarLWqdQzv0wMqyBNIdq1QLi48F1oY5Lo9kiohuf7BPQWtWxeNVS2NgJ1+nb/DzK1JduYC4AWOA=="],
|
||||
"@turbo/linux-arm64": ["@turbo/linux-arm64@2.9.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+4s5GZs3kjxc1KMhLBhoQy4UBkXjOhgidA9ipNllkA4JLivSqUCuOgU1Xbyp6vzYrsqHJ9vvwo/2mXgEtD6ZHg=="],
|
||||
|
||||
"@turbo/windows-64": ["@turbo/windows-64@2.8.20", "", { "os": "win32", "cpu": "x64" }, "sha512-vyaDpYk/8T6Qz5V/X+ihKvKFEZFUoC0oxYpC1sZanK6gaESJlmV3cMRT3Qhcg4D2VxvtC2Jjs9IRkrZGL+exLw=="],
|
||||
"@turbo/windows-64": ["@turbo/windows-64@2.9.1", "", { "os": "win32", "cpu": "x64" }, "sha512-ZO7GCyQd5HV564XWHc9KysjanFfM3DmnWquyEByu+hQMq42g9OMU/fYOCfHS6Xj2aXkIg2FHJeRV+iAck2YrbQ=="],
|
||||
|
||||
"@turbo/windows-arm64": ["@turbo/windows-arm64@2.8.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-voicVULvUV5yaGXo0Iue13BcHGYW3u0VgqSbfQwBaHbpj1zLjYV4KIe+7fYIo6DO8FVUJzxFps3ODCQG/Wy2Qw=="],
|
||||
"@turbo/windows-arm64": ["@turbo/windows-arm64@2.9.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-BjX2fdz38mBb/H94JXrD5cJ+mEq8NmsCbYdC42JzQebJ0X8EdNgyFoEhOydPGViOmaRmhhdZnPZKKn6wahSpcA=="],
|
||||
|
||||
"@tweenjs/tween.js": ["@tweenjs/tween.js@23.1.3", "", {}, "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA=="],
|
||||
|
||||
@@ -3629,7 +3629,7 @@
|
||||
|
||||
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
|
||||
|
||||
"turbo": ["turbo@2.8.20", "", { "optionalDependencies": { "@turbo/darwin-64": "2.8.20", "@turbo/darwin-arm64": "2.8.20", "@turbo/linux-64": "2.8.20", "@turbo/linux-arm64": "2.8.20", "@turbo/windows-64": "2.8.20", "@turbo/windows-arm64": "2.8.20" }, "bin": { "turbo": "bin/turbo" } }, "sha512-Rb4qk5YT8RUwwdXtkLpkVhNEe/lor6+WV7S5tTlLpxSz6MjV5Qi8jGNn4gS6NAvrYGA/rNrE6YUQM85sCZUDbQ=="],
|
||||
"turbo": ["turbo@2.9.1", "", { "optionalDependencies": { "@turbo/darwin-64": "2.9.1", "@turbo/darwin-arm64": "2.9.1", "@turbo/linux-64": "2.9.1", "@turbo/linux-arm64": "2.9.1", "@turbo/windows-64": "2.9.1", "@turbo/windows-arm64": "2.9.1" }, "bin": { "turbo": "bin/turbo" } }, "sha512-TO9du8MwLTAKoXcGezekh9cPJabJUb0+8KxtpMR6kXdRASrmJ8qXf2GkVbCREgzbMQakzfNcux9cZtxheDY4RQ=="],
|
||||
|
||||
"tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"glob": "13.0.0",
|
||||
"husky": "9.1.7",
|
||||
"lint-staged": "16.0.0",
|
||||
"turbo": "2.8.20"
|
||||
"turbo": "2.9.1"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx,json,css,scss}": [
|
||||
|
||||
Reference in New Issue
Block a user