chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,663 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useQueries, useQuery } from '@tanstack/react-query'
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import type { KnowledgeBaseData } from '@/lib/api/contracts/knowledge'
|
||||
import {
|
||||
type DiscoverMcpToolsResponse,
|
||||
discoverMcpToolsContract,
|
||||
type ListMcpServersResponse,
|
||||
listMcpServersContract,
|
||||
} from '@/lib/api/contracts/mcp'
|
||||
import {
|
||||
type GetTableResponse,
|
||||
getTableContract,
|
||||
type ListTablesResponse,
|
||||
listTablesContract,
|
||||
} from '@/lib/api/contracts/tables'
|
||||
import {
|
||||
type ListWorkspaceFilesResponse,
|
||||
listWorkspaceFilesContract,
|
||||
} from '@/lib/api/contracts/workspace-files'
|
||||
import { createMcpToolId } from '@/lib/mcp/shared'
|
||||
import type { Credential } from '@/lib/oauth'
|
||||
import {
|
||||
getWorkflowSearchMatchResourceGroupKey,
|
||||
stableStringifyWorkflowSearchValue,
|
||||
} from '@/lib/workflows/search-replace/resources'
|
||||
import type {
|
||||
WorkflowSearchMatch,
|
||||
WorkflowSearchReplacementOption,
|
||||
} from '@/lib/workflows/search-replace/types'
|
||||
import { fetchKnowledgeBase, fetchKnowledgeBases } from '@/hooks/queries/kb/knowledge'
|
||||
import {
|
||||
fetchOAuthCredentialDetail,
|
||||
fetchOAuthCredentials,
|
||||
} from '@/hooks/queries/oauth/oauth-credentials'
|
||||
import { getSelectorDefinition, loadAllSelectorOptions } from '@/hooks/selectors/registry'
|
||||
import type { SelectorKey, SelectorOption } from '@/hooks/selectors/types'
|
||||
|
||||
export interface WorkflowSearchResolvedResource {
|
||||
matchRawValue: string
|
||||
resourceGroupKey?: string
|
||||
label: string
|
||||
resolved: boolean
|
||||
inaccessible: boolean
|
||||
}
|
||||
|
||||
export const workflowSearchReplaceKeys = {
|
||||
all: ['workflow-search-replace'] as const,
|
||||
resourceDetails: () => [...workflowSearchReplaceKeys.all, 'resource-detail'] as const,
|
||||
oauthDetails: (workflowId?: string) =>
|
||||
[...workflowSearchReplaceKeys.resourceDetails(), 'oauth', workflowId ?? ''] as const,
|
||||
oauthDetail: (credentialId?: string, workflowId?: string) =>
|
||||
[...workflowSearchReplaceKeys.oauthDetails(workflowId), credentialId ?? ''] as const,
|
||||
replacementOptions: () => [...workflowSearchReplaceKeys.all, 'replacement-options'] as const,
|
||||
oauthReplacementOptions: (providerId?: string, workspaceId?: string, workflowId?: string) =>
|
||||
[
|
||||
...workflowSearchReplaceKeys.replacementOptions(),
|
||||
'oauth',
|
||||
providerId ?? '',
|
||||
workspaceId ?? '',
|
||||
workflowId ?? '',
|
||||
] as const,
|
||||
knowledgeDetails: () => [...workflowSearchReplaceKeys.resourceDetails(), 'knowledge'] as const,
|
||||
knowledgeDetail: (knowledgeBaseId?: string) =>
|
||||
[...workflowSearchReplaceKeys.knowledgeDetails(), knowledgeBaseId ?? ''] as const,
|
||||
tableDetails: () => [...workflowSearchReplaceKeys.resourceDetails(), 'table'] as const,
|
||||
tableDetail: (workspaceId?: string, tableId?: string) =>
|
||||
[...workflowSearchReplaceKeys.tableDetails(), workspaceId ?? '', tableId ?? ''] as const,
|
||||
tableReplacementOptions: (workspaceId?: string) =>
|
||||
[...workflowSearchReplaceKeys.replacementOptions(), 'table', workspaceId ?? ''] as const,
|
||||
fileDetails: () => [...workflowSearchReplaceKeys.resourceDetails(), 'file'] as const,
|
||||
fileListDetails: (workspaceId?: string) =>
|
||||
[...workflowSearchReplaceKeys.fileDetails(), 'list', workspaceId ?? ''] as const,
|
||||
fileReplacementOptions: (workspaceId?: string) =>
|
||||
[...workflowSearchReplaceKeys.replacementOptions(), 'file', workspaceId ?? ''] as const,
|
||||
mcpServerDetails: () => [...workflowSearchReplaceKeys.resourceDetails(), 'mcp-server'] as const,
|
||||
mcpServerListDetails: (workspaceId?: string) =>
|
||||
[...workflowSearchReplaceKeys.mcpServerDetails(), 'list', workspaceId ?? ''] as const,
|
||||
mcpServerReplacementOptions: (workspaceId?: string) =>
|
||||
[...workflowSearchReplaceKeys.replacementOptions(), 'mcp-server', workspaceId ?? ''] as const,
|
||||
mcpToolDetails: () => [...workflowSearchReplaceKeys.resourceDetails(), 'mcp-tool'] as const,
|
||||
mcpToolListDetails: (workspaceId?: string) =>
|
||||
[...workflowSearchReplaceKeys.mcpToolDetails(), 'list', workspaceId ?? ''] as const,
|
||||
mcpToolReplacementOptions: (workspaceId?: string) =>
|
||||
[...workflowSearchReplaceKeys.replacementOptions(), 'mcp-tool', workspaceId ?? ''] as const,
|
||||
knowledgeReplacementOptions: (workspaceId?: string) =>
|
||||
[...workflowSearchReplaceKeys.replacementOptions(), 'knowledge', workspaceId ?? ''] as const,
|
||||
selectorDetails: () => [...workflowSearchReplaceKeys.resourceDetails(), 'selector'] as const,
|
||||
selectorDetail: (selectorKey?: string, contextKey?: string, value?: string) =>
|
||||
[
|
||||
...workflowSearchReplaceKeys.selectorDetails(),
|
||||
selectorKey ?? '',
|
||||
contextKey ?? '',
|
||||
value ?? '',
|
||||
] as const,
|
||||
selectorReplacementOptions: (selectorKey?: string, contextKey?: string) =>
|
||||
[
|
||||
...workflowSearchReplaceKeys.replacementOptions(),
|
||||
'selector',
|
||||
selectorKey ?? '',
|
||||
contextKey ?? '',
|
||||
] as const,
|
||||
}
|
||||
|
||||
export const WORKFLOW_SEARCH_OAUTH_DETAIL_STALE_TIME = 60 * 1000
|
||||
export const WORKFLOW_SEARCH_KNOWLEDGE_DETAIL_STALE_TIME = 60 * 1000
|
||||
export const WORKFLOW_SEARCH_TABLE_DETAIL_STALE_TIME = 60 * 1000
|
||||
export const WORKFLOW_SEARCH_FILE_LIST_STALE_TIME = 60 * 1000
|
||||
export const WORKFLOW_SEARCH_MCP_SERVER_LIST_STALE_TIME = 60 * 1000
|
||||
export const WORKFLOW_SEARCH_MCP_TOOL_LIST_STALE_TIME = 60 * 1000
|
||||
export const WORKFLOW_SEARCH_SELECTOR_DETAIL_STALE_TIME = 60 * 1000
|
||||
export const WORKFLOW_SEARCH_OAUTH_REPLACEMENT_STALE_TIME = 60 * 1000
|
||||
export const WORKFLOW_SEARCH_KNOWLEDGE_REPLACEMENT_STALE_TIME = 60 * 1000
|
||||
export const WORKFLOW_SEARCH_TABLE_REPLACEMENT_STALE_TIME = 60 * 1000
|
||||
export const WORKFLOW_SEARCH_FILE_REPLACEMENT_STALE_TIME = 60 * 1000
|
||||
export const WORKFLOW_SEARCH_MCP_SERVER_REPLACEMENT_STALE_TIME = 60 * 1000
|
||||
export const WORKFLOW_SEARCH_MCP_TOOL_REPLACEMENT_STALE_TIME = 60 * 1000
|
||||
export const WORKFLOW_SEARCH_SELECTOR_REPLACEMENT_STALE_TIME = 60 * 1000
|
||||
|
||||
function uniqueMatches(
|
||||
matches: WorkflowSearchMatch[],
|
||||
kind: WorkflowSearchMatch['kind']
|
||||
): WorkflowSearchMatch[] {
|
||||
const seen = new Set<string>()
|
||||
return matches.filter((match) => {
|
||||
if (match.kind !== kind || !match.rawValue || seen.has(match.rawValue)) return false
|
||||
seen.add(match.rawValue)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
function selectorContextKey(match: WorkflowSearchMatch): string {
|
||||
return stableStringifyWorkflowSearchValue(match.resource?.selectorContext ?? {})
|
||||
}
|
||||
|
||||
function uniqueSelectorDetailMatches(matches: WorkflowSearchMatch[]): WorkflowSearchMatch[] {
|
||||
const seen = new Set<string>()
|
||||
return matches.filter((match) => {
|
||||
const selectorKey = match.resource?.selectorKey
|
||||
if (!selectorKey || !match.rawValue) return false
|
||||
|
||||
const key = `${selectorKey}:${selectorContextKey(match)}:${match.rawValue}`
|
||||
if (seen.has(key)) return false
|
||||
seen.add(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
function uniqueSelectorOptionGroups(matches: WorkflowSearchMatch[]): WorkflowSearchMatch[] {
|
||||
const seen = new Set<string>()
|
||||
return matches.filter((match) => {
|
||||
const selectorKey = match.resource?.selectorKey
|
||||
if (!selectorKey) return false
|
||||
|
||||
const key = `${match.kind}:${selectorKey}:${selectorContextKey(match)}`
|
||||
if (seen.has(key)) return false
|
||||
seen.add(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
function uniqueResourceOptionGroups(
|
||||
matches: WorkflowSearchMatch[],
|
||||
kind: WorkflowSearchMatch['kind'],
|
||||
predicate?: (match: WorkflowSearchMatch) => boolean
|
||||
): WorkflowSearchMatch[] {
|
||||
const seen = new Set<string>()
|
||||
return matches.filter((match) => {
|
||||
if (match.kind !== kind || predicate?.(match) === false) return false
|
||||
|
||||
const key = getWorkflowSearchMatchResourceGroupKey(match)
|
||||
if (seen.has(key)) return false
|
||||
seen.add(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
export function useWorkflowSearchOAuthCredentialDetails(
|
||||
matches: WorkflowSearchMatch[],
|
||||
workflowId?: string
|
||||
) {
|
||||
const oauthMatches = useMemo(() => uniqueMatches(matches, 'oauth-credential'), [matches])
|
||||
|
||||
return useQueries({
|
||||
queries: oauthMatches.map((match) => ({
|
||||
queryKey: workflowSearchReplaceKeys.oauthDetail(match.rawValue, workflowId),
|
||||
queryFn: ({ signal }: { signal: AbortSignal }) =>
|
||||
fetchOAuthCredentialDetail(match.rawValue, workflowId, signal),
|
||||
enabled: Boolean(match.rawValue),
|
||||
staleTime: WORKFLOW_SEARCH_OAUTH_DETAIL_STALE_TIME,
|
||||
select: (credentials: Credential[]): WorkflowSearchResolvedResource => {
|
||||
const credential = credentials[0]
|
||||
return {
|
||||
matchRawValue: match.rawValue,
|
||||
resourceGroupKey: match.resource?.resourceGroupKey,
|
||||
label: credential?.name ?? `OAuth credential ${match.rawValue.slice(0, 8)}`,
|
||||
resolved: Boolean(credential?.name),
|
||||
inaccessible: credentials.length === 0,
|
||||
}
|
||||
},
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
export function useWorkflowSearchKnowledgeBaseDetails(matches: WorkflowSearchMatch[]) {
|
||||
const knowledgeMatches = useMemo(() => uniqueMatches(matches, 'knowledge-base'), [matches])
|
||||
|
||||
return useQueries({
|
||||
queries: knowledgeMatches.map((match) => ({
|
||||
queryKey: workflowSearchReplaceKeys.knowledgeDetail(match.rawValue),
|
||||
queryFn: ({ signal }: { signal: AbortSignal }) => fetchKnowledgeBase(match.rawValue, signal),
|
||||
enabled: Boolean(match.rawValue),
|
||||
staleTime: WORKFLOW_SEARCH_KNOWLEDGE_DETAIL_STALE_TIME,
|
||||
select: (knowledgeBase: KnowledgeBaseData): WorkflowSearchResolvedResource => ({
|
||||
matchRawValue: match.rawValue,
|
||||
resourceGroupKey: match.resource?.resourceGroupKey,
|
||||
label: knowledgeBase.name,
|
||||
resolved: true,
|
||||
inaccessible: false,
|
||||
}),
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
export function useWorkflowSearchTableDetails(
|
||||
matches: WorkflowSearchMatch[],
|
||||
workspaceId?: string
|
||||
) {
|
||||
const tableMatches = useMemo(() => uniqueMatches(matches, 'table'), [matches])
|
||||
|
||||
return useQueries({
|
||||
queries: tableMatches.map((match) => ({
|
||||
queryKey: workflowSearchReplaceKeys.tableDetail(workspaceId, match.rawValue),
|
||||
queryFn: ({ signal }: { signal: AbortSignal }) =>
|
||||
requestJson(getTableContract, {
|
||||
params: { tableId: match.rawValue },
|
||||
query: { workspaceId: workspaceId as string },
|
||||
signal,
|
||||
}),
|
||||
enabled: Boolean(workspaceId && match.rawValue),
|
||||
staleTime: WORKFLOW_SEARCH_TABLE_DETAIL_STALE_TIME,
|
||||
select: (response: GetTableResponse): WorkflowSearchResolvedResource => ({
|
||||
matchRawValue: match.rawValue,
|
||||
resourceGroupKey: match.resource?.resourceGroupKey,
|
||||
label: response.data.table.name,
|
||||
resolved: true,
|
||||
inaccessible: false,
|
||||
}),
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
export function useWorkflowSearchFileDetails(matches: WorkflowSearchMatch[], workspaceId?: string) {
|
||||
const fileMatches = useMemo(
|
||||
() =>
|
||||
uniqueMatches(
|
||||
matches.filter((match) => !match.resource?.selectorKey),
|
||||
'file'
|
||||
),
|
||||
[matches]
|
||||
)
|
||||
|
||||
const filesQuery = useQuery({
|
||||
queryKey: workflowSearchReplaceKeys.fileListDetails(workspaceId),
|
||||
queryFn: ({ signal }: { signal: AbortSignal }) =>
|
||||
requestJson(listWorkspaceFilesContract, {
|
||||
params: { id: workspaceId as string },
|
||||
query: { scope: 'active' },
|
||||
signal,
|
||||
}),
|
||||
enabled: Boolean(workspaceId && fileMatches.length > 0),
|
||||
staleTime: WORKFLOW_SEARCH_FILE_LIST_STALE_TIME,
|
||||
})
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
fileMatches.map((match) => {
|
||||
const file = filesQuery.data?.files.find((item) =>
|
||||
[item.id, item.key, item.path, item.name].includes(match.rawValue)
|
||||
)
|
||||
return {
|
||||
data: filesQuery.data
|
||||
? {
|
||||
matchRawValue: match.rawValue,
|
||||
resourceGroupKey: match.resource?.resourceGroupKey,
|
||||
label: file?.name ?? match.rawValue,
|
||||
resolved: Boolean(file),
|
||||
inaccessible: false,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
}),
|
||||
[fileMatches, filesQuery.data]
|
||||
)
|
||||
}
|
||||
|
||||
export function useWorkflowSearchMcpServerDetails(
|
||||
matches: WorkflowSearchMatch[],
|
||||
workspaceId?: string
|
||||
) {
|
||||
const serverMatches = useMemo(() => uniqueMatches(matches, 'mcp-server'), [matches])
|
||||
|
||||
const serversQuery = useQuery({
|
||||
queryKey: workflowSearchReplaceKeys.mcpServerListDetails(workspaceId),
|
||||
queryFn: ({ signal }: { signal: AbortSignal }) =>
|
||||
requestJson(listMcpServersContract, {
|
||||
query: { workspaceId: workspaceId as string },
|
||||
signal,
|
||||
}),
|
||||
enabled: Boolean(workspaceId && serverMatches.length > 0),
|
||||
staleTime: WORKFLOW_SEARCH_MCP_SERVER_LIST_STALE_TIME,
|
||||
})
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
serverMatches.map((match) => {
|
||||
const server = serversQuery.data?.data.servers.find((item) => item.id === match.rawValue)
|
||||
return {
|
||||
data: serversQuery.data
|
||||
? {
|
||||
matchRawValue: match.rawValue,
|
||||
resourceGroupKey: match.resource?.resourceGroupKey,
|
||||
label: server?.name ?? match.rawValue,
|
||||
resolved: Boolean(server),
|
||||
inaccessible: false,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
}),
|
||||
[serverMatches, serversQuery.data]
|
||||
)
|
||||
}
|
||||
|
||||
export function useWorkflowSearchMcpToolDetails(
|
||||
matches: WorkflowSearchMatch[],
|
||||
workspaceId?: string
|
||||
) {
|
||||
const toolMatches = useMemo(() => uniqueMatches(matches, 'mcp-tool'), [matches])
|
||||
|
||||
const toolsQuery = useQuery({
|
||||
queryKey: workflowSearchReplaceKeys.mcpToolListDetails(workspaceId),
|
||||
queryFn: ({ signal }: { signal: AbortSignal }) =>
|
||||
requestJson(discoverMcpToolsContract, {
|
||||
query: { workspaceId: workspaceId as string },
|
||||
signal,
|
||||
}),
|
||||
enabled: Boolean(workspaceId && toolMatches.length > 0),
|
||||
staleTime: WORKFLOW_SEARCH_MCP_TOOL_LIST_STALE_TIME,
|
||||
})
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
toolMatches.map((match) => {
|
||||
const tool = toolsQuery.data?.data.tools.find(
|
||||
(item) => createMcpToolId(item.serverId, item.name) === match.rawValue
|
||||
)
|
||||
return {
|
||||
data: toolsQuery.data
|
||||
? {
|
||||
matchRawValue: match.rawValue,
|
||||
resourceGroupKey: match.resource?.resourceGroupKey,
|
||||
label: tool ? `${tool.serverName}: ${tool.name}` : match.rawValue,
|
||||
resolved: Boolean(tool),
|
||||
inaccessible: false,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
}),
|
||||
[toolMatches, toolsQuery.data]
|
||||
)
|
||||
}
|
||||
|
||||
export function useWorkflowSearchSelectorDetails(matches: WorkflowSearchMatch[]) {
|
||||
const selectorMatches = useMemo(() => uniqueSelectorDetailMatches(matches), [matches])
|
||||
|
||||
return useQueries({
|
||||
queries: selectorMatches.map((match) => {
|
||||
const selectorKey = match.resource?.selectorKey as SelectorKey
|
||||
const context = match.resource?.selectorContext ?? {}
|
||||
const contextKey = selectorContextKey(match)
|
||||
const definition = getSelectorDefinition(selectorKey)
|
||||
const queryArgs = { key: selectorKey, context, detailId: match.rawValue }
|
||||
const baseEnabled = definition.enabled ? definition.enabled(queryArgs) : true
|
||||
|
||||
return {
|
||||
queryKey: workflowSearchReplaceKeys.selectorDetail(selectorKey, contextKey, match.rawValue),
|
||||
queryFn: async ({ signal }: { signal: AbortSignal }): Promise<SelectorOption | null> => {
|
||||
if (definition.fetchById) {
|
||||
return definition.fetchById({ ...queryArgs, signal })
|
||||
}
|
||||
|
||||
const options = await loadAllSelectorOptions(definition, {
|
||||
key: selectorKey,
|
||||
context,
|
||||
signal,
|
||||
})
|
||||
return options.find((option) => option.id === match.rawValue) ?? null
|
||||
},
|
||||
enabled: Boolean(selectorKey && match.rawValue && baseEnabled),
|
||||
staleTime: definition.staleTime ?? WORKFLOW_SEARCH_SELECTOR_DETAIL_STALE_TIME,
|
||||
select: (option: SelectorOption | null): WorkflowSearchResolvedResource => ({
|
||||
matchRawValue: match.rawValue,
|
||||
resourceGroupKey: match.resource?.resourceGroupKey,
|
||||
label: option?.label ?? match.rawValue,
|
||||
resolved: Boolean(option),
|
||||
inaccessible: false,
|
||||
}),
|
||||
}
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export function useWorkflowSearchOAuthReplacementOptions(
|
||||
matches: WorkflowSearchMatch[],
|
||||
workspaceId?: string,
|
||||
workflowId?: string
|
||||
) {
|
||||
const providerIds = useMemo(() => {
|
||||
const ids = new Set<string>()
|
||||
matches.forEach((match) => {
|
||||
if (match.kind === 'oauth-credential' && match.resource?.providerId) {
|
||||
ids.add(match.resource.providerId)
|
||||
}
|
||||
})
|
||||
return [...ids].sort()
|
||||
}, [matches])
|
||||
|
||||
return useQueries({
|
||||
queries: providerIds.map((providerId) => ({
|
||||
queryKey: workflowSearchReplaceKeys.oauthReplacementOptions(
|
||||
providerId,
|
||||
workspaceId,
|
||||
workflowId
|
||||
),
|
||||
queryFn: ({ signal }: { signal: AbortSignal }) =>
|
||||
fetchOAuthCredentials({ providerId, workspaceId, workflowId }, signal),
|
||||
enabled: Boolean(providerId && workspaceId),
|
||||
staleTime: WORKFLOW_SEARCH_OAUTH_REPLACEMENT_STALE_TIME,
|
||||
select: (credentials: Credential[]): WorkflowSearchReplacementOption[] =>
|
||||
credentials.map((credential) => ({
|
||||
kind: 'oauth-credential',
|
||||
value: credential.id,
|
||||
label: credential.name,
|
||||
providerId,
|
||||
serviceId: providerId,
|
||||
})),
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
export function useWorkflowSearchKnowledgeReplacementOptions(
|
||||
matches: WorkflowSearchMatch[],
|
||||
workspaceId?: string
|
||||
) {
|
||||
const knowledgeGroups = useMemo(
|
||||
() => uniqueResourceOptionGroups(matches, 'knowledge-base'),
|
||||
[matches]
|
||||
)
|
||||
|
||||
return useQueries({
|
||||
queries: [
|
||||
{
|
||||
queryKey: workflowSearchReplaceKeys.knowledgeReplacementOptions(workspaceId),
|
||||
queryFn: ({ signal }: { signal: AbortSignal }) =>
|
||||
fetchKnowledgeBases(workspaceId, 'active', signal),
|
||||
enabled: Boolean(workspaceId && knowledgeGroups.length > 0),
|
||||
staleTime: WORKFLOW_SEARCH_KNOWLEDGE_REPLACEMENT_STALE_TIME,
|
||||
placeholderData: (previous: KnowledgeBaseData[] | undefined) => previous,
|
||||
select: (knowledgeBases: KnowledgeBaseData[]): WorkflowSearchReplacementOption[] =>
|
||||
knowledgeGroups.flatMap((match) =>
|
||||
knowledgeBases.map((knowledgeBase) => ({
|
||||
kind: 'knowledge-base',
|
||||
value: knowledgeBase.id,
|
||||
label: knowledgeBase.name,
|
||||
resourceGroupKey: match.resource?.resourceGroupKey,
|
||||
}))
|
||||
),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
export function useWorkflowSearchTableReplacementOptions(
|
||||
matches: WorkflowSearchMatch[],
|
||||
workspaceId?: string
|
||||
) {
|
||||
const tableGroups = useMemo(() => uniqueResourceOptionGroups(matches, 'table'), [matches])
|
||||
|
||||
return useQueries({
|
||||
queries: [
|
||||
{
|
||||
queryKey: workflowSearchReplaceKeys.tableReplacementOptions(workspaceId),
|
||||
queryFn: ({ signal }: { signal: AbortSignal }) =>
|
||||
requestJson(listTablesContract, {
|
||||
query: { workspaceId: workspaceId as string, scope: 'active' },
|
||||
signal,
|
||||
}),
|
||||
enabled: Boolean(workspaceId && tableGroups.length > 0),
|
||||
staleTime: WORKFLOW_SEARCH_TABLE_REPLACEMENT_STALE_TIME,
|
||||
select: (response: ListTablesResponse): WorkflowSearchReplacementOption[] =>
|
||||
tableGroups.flatMap((match) =>
|
||||
response.data.tables.map((table) => ({
|
||||
kind: 'table',
|
||||
value: table.id,
|
||||
label: table.name,
|
||||
resourceGroupKey: match.resource?.resourceGroupKey,
|
||||
}))
|
||||
),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
export function useWorkflowSearchFileReplacementOptions(
|
||||
matches: WorkflowSearchMatch[],
|
||||
workspaceId?: string
|
||||
) {
|
||||
const fileGroups = useMemo(
|
||||
() => uniqueResourceOptionGroups(matches, 'file', (match) => !match.resource?.selectorKey),
|
||||
[matches]
|
||||
)
|
||||
|
||||
return useQueries({
|
||||
queries: [
|
||||
{
|
||||
queryKey: workflowSearchReplaceKeys.fileReplacementOptions(workspaceId),
|
||||
queryFn: ({ signal }: { signal: AbortSignal }) =>
|
||||
requestJson(listWorkspaceFilesContract, {
|
||||
params: { id: workspaceId as string },
|
||||
query: { scope: 'active' },
|
||||
signal,
|
||||
}),
|
||||
enabled: Boolean(workspaceId && fileGroups.length > 0),
|
||||
staleTime: WORKFLOW_SEARCH_FILE_REPLACEMENT_STALE_TIME,
|
||||
select: (response: ListWorkspaceFilesResponse): WorkflowSearchReplacementOption[] =>
|
||||
fileGroups.flatMap((match) =>
|
||||
response.files.map((file) => ({
|
||||
kind: 'file',
|
||||
value: JSON.stringify({
|
||||
name: file.name,
|
||||
path: file.path,
|
||||
key: file.key,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
}),
|
||||
label: file.name,
|
||||
resourceGroupKey: match.resource?.resourceGroupKey,
|
||||
}))
|
||||
),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
export function useWorkflowSearchMcpServerReplacementOptions(
|
||||
matches: WorkflowSearchMatch[],
|
||||
workspaceId?: string
|
||||
) {
|
||||
const serverGroups = useMemo(() => uniqueResourceOptionGroups(matches, 'mcp-server'), [matches])
|
||||
|
||||
return useQueries({
|
||||
queries: [
|
||||
{
|
||||
queryKey: workflowSearchReplaceKeys.mcpServerReplacementOptions(workspaceId),
|
||||
queryFn: ({ signal }: { signal: AbortSignal }) =>
|
||||
requestJson(listMcpServersContract, {
|
||||
query: { workspaceId: workspaceId as string },
|
||||
signal,
|
||||
}),
|
||||
enabled: Boolean(workspaceId && serverGroups.length > 0),
|
||||
staleTime: WORKFLOW_SEARCH_MCP_SERVER_REPLACEMENT_STALE_TIME,
|
||||
select: (response: ListMcpServersResponse): WorkflowSearchReplacementOption[] =>
|
||||
serverGroups.flatMap((match) =>
|
||||
response.data.servers.map((server) => ({
|
||||
kind: 'mcp-server',
|
||||
value: server.id,
|
||||
label: server.name,
|
||||
resourceGroupKey: match.resource?.resourceGroupKey,
|
||||
}))
|
||||
),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
export function buildWorkflowSearchMcpToolReplacementOptions(
|
||||
toolGroups: WorkflowSearchMatch[],
|
||||
tools: DiscoverMcpToolsResponse['data']['tools']
|
||||
): WorkflowSearchReplacementOption[] {
|
||||
return toolGroups.flatMap((match) => {
|
||||
const serverId = match.resource?.selectorContext?.mcpServerId
|
||||
return tools
|
||||
.filter((tool) => !serverId || tool.serverId === serverId)
|
||||
.map((tool) => ({
|
||||
kind: 'mcp-tool',
|
||||
value: createMcpToolId(tool.serverId, tool.name),
|
||||
label: `${tool.serverName}: ${tool.name}`,
|
||||
resourceGroupKey: match.resource?.resourceGroupKey,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
export function useWorkflowSearchMcpToolReplacementOptions(
|
||||
matches: WorkflowSearchMatch[],
|
||||
workspaceId?: string
|
||||
) {
|
||||
const toolGroups = useMemo(() => uniqueResourceOptionGroups(matches, 'mcp-tool'), [matches])
|
||||
|
||||
return useQueries({
|
||||
queries: [
|
||||
{
|
||||
queryKey: workflowSearchReplaceKeys.mcpToolReplacementOptions(workspaceId),
|
||||
queryFn: ({ signal }: { signal: AbortSignal }) =>
|
||||
requestJson(discoverMcpToolsContract, {
|
||||
query: { workspaceId: workspaceId as string },
|
||||
signal,
|
||||
}),
|
||||
enabled: Boolean(workspaceId && toolGroups.length > 0),
|
||||
staleTime: WORKFLOW_SEARCH_MCP_TOOL_REPLACEMENT_STALE_TIME,
|
||||
select: (response: DiscoverMcpToolsResponse): WorkflowSearchReplacementOption[] =>
|
||||
buildWorkflowSearchMcpToolReplacementOptions(toolGroups, response.data.tools),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
export function useWorkflowSearchSelectorReplacementOptions(matches: WorkflowSearchMatch[]) {
|
||||
const selectorGroups = useMemo(() => uniqueSelectorOptionGroups(matches), [matches])
|
||||
|
||||
return useQueries({
|
||||
queries: selectorGroups.map((match) => {
|
||||
const selectorKey = match.resource?.selectorKey as SelectorKey
|
||||
const context = match.resource?.selectorContext ?? {}
|
||||
const contextKey = selectorContextKey(match)
|
||||
const definition = getSelectorDefinition(selectorKey)
|
||||
const queryArgs = { key: selectorKey, context }
|
||||
const baseEnabled = definition.enabled ? definition.enabled(queryArgs) : true
|
||||
|
||||
return {
|
||||
queryKey: workflowSearchReplaceKeys.selectorReplacementOptions(selectorKey, contextKey),
|
||||
queryFn: ({ signal }: { signal: AbortSignal }) =>
|
||||
loadAllSelectorOptions(definition, { ...queryArgs, signal }),
|
||||
enabled: Boolean(selectorKey && baseEnabled),
|
||||
staleTime: definition.staleTime ?? WORKFLOW_SEARCH_SELECTOR_REPLACEMENT_STALE_TIME,
|
||||
select: (options: SelectorOption[]): WorkflowSearchReplacementOption[] =>
|
||||
options.map((option) => ({
|
||||
kind: match.kind,
|
||||
value: option.id,
|
||||
label: option.label,
|
||||
selectorKey,
|
||||
selectorContext: context,
|
||||
resourceGroupKey: match.resource?.resourceGroupKey,
|
||||
})),
|
||||
}
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export function flattenWorkflowSearchReplacementOptions(
|
||||
optionGroups: Array<{ data?: WorkflowSearchReplacementOption[] }>
|
||||
): WorkflowSearchReplacementOption[] {
|
||||
return optionGroups.flatMap((group) => group.data ?? [])
|
||||
}
|
||||
Reference in New Issue
Block a user