mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-30 09:18:01 -05:00
Compare commits
1 Commits
staging
...
feat/deplo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74c0ba4ec8 |
@@ -5127,11 +5127,11 @@ export function SimilarwebIcon(props: SVGProps<SVGSVGElement>) {
|
||||
<path
|
||||
d='M22.099 5.781c-1.283 -2 -3.14 -3.67 -5.27 -4.52l-0.63 -0.213a7.433 7.433 0 0 0 -2.15 -0.331c-2.307 0.01 -4.175 1.92 -4.175 4.275a4.3 4.3 0 0 0 0.867 2.602l-0.26 -0.342c0.124 0.186 0.26 0.37 0.417 0.556 0.663 0.802 1.604 1.635 2.822 2.58 2.999 2.32 4.943 4.378 5.104 6.93 0.038 0.344 0.062 0.696 0.062 1.051 0 1.297 -0.283 2.67 -0.764 3.635h0.005s-0.207 0.377 -0.077 0.487c0.066 0.057 0.21 0.1 0.46 -0.053a12.104 12.104 0 0 0 3.4 -3.33 12.111 12.111 0 0 0 2.088 -6.635 12.098 12.098 0 0 0 -1.9 -6.692zm-9.096 8.718 -1.878 -1.55c-3.934 -2.87 -5.98 -5.966 -4.859 -9.783a8.73 8.73 0 0 1 0.37 -1.016v-0.004s0.278 -0.583 -0.327 -0.295a12.067 12.067 0 0 0 -6.292 9.975 12.11 12.11 0 0 0 2.053 7.421 9.394 9.394 0 0 0 2.154 2.168H4.22c4.148 3.053 7.706 1.446 7.706 1.446h0.003a4.847 4.847 0 0 0 2.962 -4.492 4.855 4.855 0 0 0 -1.889 -3.87z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export function CalComIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -5131,7 +5131,7 @@ export function SimilarwebIcon(props: SVGProps<SVGSVGElement>) {
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export function CalComIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -421,28 +421,27 @@ interface GenerateVersionDescriptionVariables {
|
||||
onStreamChunk?: (accumulated: string) => void
|
||||
}
|
||||
|
||||
const VERSION_DESCRIPTION_SYSTEM_PROMPT = `You are a technical writer generating concise deployment version descriptions.
|
||||
const VERSION_DESCRIPTION_SYSTEM_PROMPT = `You are writing deployment version descriptions for a workflow automation platform.
|
||||
|
||||
Given a diff of changes between two workflow versions, write a brief, factual description (1-2 sentences, under 300 characters) that states ONLY what changed.
|
||||
Write a brief, factual description (1-3 sentences, under 400 characters) that states what changed between versions.
|
||||
|
||||
RULES:
|
||||
- State specific values when provided (e.g. "model changed from X to Y")
|
||||
- Do NOT wrap your response in quotes
|
||||
- Do NOT add filler phrases like "streamlining the workflow", "for improved efficiency"
|
||||
- Do NOT use markdown formatting
|
||||
- Do NOT include version numbers
|
||||
- Do NOT start with "This version" or similar phrases
|
||||
Guidelines:
|
||||
- Use the specific values provided (credential names, channel names, model names)
|
||||
- Be precise: "Changes Slack channel from #general to #alerts" not "Updates channel configuration"
|
||||
- Combine related changes: "Updates Agent model to claude-sonnet-4-5 and increases temperature to 0.8"
|
||||
- For added/removed blocks, mention their purpose if clear from the type
|
||||
|
||||
Good examples:
|
||||
- Changes model in Agent 1 from gpt-4o to claude-sonnet-4-20250514.
|
||||
- Adds Slack notification block. Updates webhook URL to production endpoint.
|
||||
- Removes Function block and its connection to Router.
|
||||
Format rules:
|
||||
- Plain text only, no quotes around the response
|
||||
- No markdown formatting
|
||||
- No filler phrases ("for improved efficiency", "streamlining the workflow")
|
||||
- No version numbers or "This version" prefixes
|
||||
|
||||
Bad examples:
|
||||
- "Changes model..." (NO - don't wrap in quotes)
|
||||
- Changes model, streamlining the workflow. (NO - don't add filler)
|
||||
|
||||
Respond with ONLY the plain text description.`
|
||||
Examples:
|
||||
- Switches Agent model from gpt-4o to claude-sonnet-4-5. Changes Slack credential to Production OAuth.
|
||||
- Adds Gmail notification block for sending alerts. Removes unused Function block. Updates Router conditions.
|
||||
- Updates system prompt for more concise responses. Reduces temperature from 0.7 to 0.3.
|
||||
- Connects Slack block to Router. Adds 2 new workflow connections. Configures error handling path.`
|
||||
|
||||
/**
|
||||
* Hook for generating a version description using AI based on workflow diff
|
||||
@@ -454,7 +453,7 @@ export function useGenerateVersionDescription() {
|
||||
version,
|
||||
onStreamChunk,
|
||||
}: GenerateVersionDescriptionVariables): Promise<string> => {
|
||||
const { generateWorkflowDiffSummary, formatDiffSummaryForDescription } = await import(
|
||||
const { generateWorkflowDiffSummary, formatDiffSummaryForDescriptionAsync } = await import(
|
||||
'@/lib/workflows/comparison/compare'
|
||||
)
|
||||
|
||||
@@ -470,7 +469,11 @@ export function useGenerateVersionDescription() {
|
||||
}
|
||||
|
||||
const diffSummary = generateWorkflowDiffSummary(currentState, previousState)
|
||||
const diffText = formatDiffSummaryForDescription(diffSummary)
|
||||
const diffText = await formatDiffSummaryForDescriptionAsync(
|
||||
diffSummary,
|
||||
currentState,
|
||||
workflowId
|
||||
)
|
||||
|
||||
const wandResponse = await fetch('/api/wand', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
import {
|
||||
extractBlockFieldsForComparison,
|
||||
@@ -12,6 +13,9 @@ import {
|
||||
normalizeVariables,
|
||||
sanitizeVariable,
|
||||
} from './normalize'
|
||||
import { formatValueForDisplay, resolveValueForDisplay } from './resolve-values'
|
||||
|
||||
const logger = createLogger('WorkflowComparison')
|
||||
|
||||
/**
|
||||
* Compare the current workflow state with the deployed state to detect meaningful changes.
|
||||
@@ -318,19 +322,6 @@ export function generateWorkflowDiffSummary(
|
||||
return result
|
||||
}
|
||||
|
||||
function formatValueForDisplay(value: unknown): string {
|
||||
if (value === null || value === undefined) return '(none)'
|
||||
if (typeof value === 'string') {
|
||||
if (value.length > 50) return `${value.slice(0, 50)}...`
|
||||
return value || '(empty)'
|
||||
}
|
||||
if (typeof value === 'boolean') return value ? 'enabled' : 'disabled'
|
||||
if (typeof value === 'number') return String(value)
|
||||
if (Array.isArray(value)) return `[${value.length} items]`
|
||||
if (typeof value === 'object') return `${JSON.stringify(value).slice(0, 50)}...`
|
||||
return String(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a WorkflowDiffSummary to a human-readable string for AI description generation
|
||||
*/
|
||||
@@ -406,3 +397,130 @@ export function formatDiffSummaryForDescription(summary: WorkflowDiffSummary): s
|
||||
|
||||
return changes.join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a WorkflowDiffSummary to a human-readable string with resolved display names.
|
||||
* Resolves IDs (credentials, channels, workflows, etc.) to human-readable names using
|
||||
* the selector registry infrastructure.
|
||||
*
|
||||
* @param summary - The diff summary to format
|
||||
* @param currentState - The current workflow state for context extraction
|
||||
* @param workflowId - The workflow ID for API calls
|
||||
* @returns A formatted string describing the changes with resolved names
|
||||
*/
|
||||
export async function formatDiffSummaryForDescriptionAsync(
|
||||
summary: WorkflowDiffSummary,
|
||||
currentState: WorkflowState,
|
||||
workflowId: string
|
||||
): Promise<string> {
|
||||
if (!summary.hasChanges) {
|
||||
return 'No structural changes detected (configuration may have changed)'
|
||||
}
|
||||
|
||||
const changes: string[] = []
|
||||
|
||||
for (const block of summary.addedBlocks) {
|
||||
const name = block.name || block.type
|
||||
changes.push(`Added block: ${name} (${block.type})`)
|
||||
}
|
||||
|
||||
for (const block of summary.removedBlocks) {
|
||||
const name = block.name || block.type
|
||||
changes.push(`Removed block: ${name} (${block.type})`)
|
||||
}
|
||||
|
||||
const modifiedBlockPromises = summary.modifiedBlocks.map(async (block) => {
|
||||
const name = block.name || block.type
|
||||
const blockChanges: string[] = []
|
||||
|
||||
const changesToProcess = block.changes.slice(0, 3)
|
||||
const resolvedChanges = await Promise.all(
|
||||
changesToProcess.map(async (change) => {
|
||||
const context = {
|
||||
blockType: block.type,
|
||||
subBlockId: change.field,
|
||||
workflowId,
|
||||
currentState,
|
||||
blockId: block.id,
|
||||
}
|
||||
|
||||
const [oldResolved, newResolved] = await Promise.all([
|
||||
resolveValueForDisplay(change.oldValue, context),
|
||||
resolveValueForDisplay(change.newValue, context),
|
||||
])
|
||||
|
||||
return {
|
||||
field: change.field,
|
||||
oldLabel: oldResolved.displayLabel,
|
||||
newLabel: newResolved.displayLabel,
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
for (const resolved of resolvedChanges) {
|
||||
blockChanges.push(
|
||||
`Modified ${name}: ${resolved.field} changed from "${resolved.oldLabel}" to "${resolved.newLabel}"`
|
||||
)
|
||||
}
|
||||
|
||||
if (block.changes.length > 3) {
|
||||
blockChanges.push(` ...and ${block.changes.length - 3} more changes in ${name}`)
|
||||
}
|
||||
|
||||
return blockChanges
|
||||
})
|
||||
|
||||
const allModifiedBlockChanges = await Promise.all(modifiedBlockPromises)
|
||||
for (const blockChanges of allModifiedBlockChanges) {
|
||||
changes.push(...blockChanges)
|
||||
}
|
||||
|
||||
if (summary.edgeChanges.added > 0) {
|
||||
changes.push(`Added ${summary.edgeChanges.added} connection(s)`)
|
||||
}
|
||||
if (summary.edgeChanges.removed > 0) {
|
||||
changes.push(`Removed ${summary.edgeChanges.removed} connection(s)`)
|
||||
}
|
||||
|
||||
if (summary.loopChanges.added > 0) {
|
||||
changes.push(`Added ${summary.loopChanges.added} loop(s)`)
|
||||
}
|
||||
if (summary.loopChanges.removed > 0) {
|
||||
changes.push(`Removed ${summary.loopChanges.removed} loop(s)`)
|
||||
}
|
||||
if (summary.loopChanges.modified > 0) {
|
||||
changes.push(`Modified ${summary.loopChanges.modified} loop(s)`)
|
||||
}
|
||||
|
||||
if (summary.parallelChanges.added > 0) {
|
||||
changes.push(`Added ${summary.parallelChanges.added} parallel group(s)`)
|
||||
}
|
||||
if (summary.parallelChanges.removed > 0) {
|
||||
changes.push(`Removed ${summary.parallelChanges.removed} parallel group(s)`)
|
||||
}
|
||||
if (summary.parallelChanges.modified > 0) {
|
||||
changes.push(`Modified ${summary.parallelChanges.modified} parallel group(s)`)
|
||||
}
|
||||
|
||||
const varChanges: string[] = []
|
||||
if (summary.variableChanges.added > 0) {
|
||||
varChanges.push(`${summary.variableChanges.added} added`)
|
||||
}
|
||||
if (summary.variableChanges.removed > 0) {
|
||||
varChanges.push(`${summary.variableChanges.removed} removed`)
|
||||
}
|
||||
if (summary.variableChanges.modified > 0) {
|
||||
varChanges.push(`${summary.variableChanges.modified} modified`)
|
||||
}
|
||||
if (varChanges.length > 0) {
|
||||
changes.push(`Variables: ${varChanges.join(', ')}`)
|
||||
}
|
||||
|
||||
logger.info('Generated async diff description', {
|
||||
workflowId,
|
||||
changeCount: changes.length,
|
||||
modifiedBlocks: summary.modifiedBlocks.length,
|
||||
})
|
||||
|
||||
return changes.join('\n')
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export type { FieldChange, WorkflowDiffSummary } from './compare'
|
||||
export {
|
||||
formatDiffSummaryForDescription,
|
||||
formatDiffSummaryForDescriptionAsync,
|
||||
generateWorkflowDiffSummary,
|
||||
hasWorkflowChanged,
|
||||
} from './compare'
|
||||
|
||||
343
apps/sim/lib/workflows/comparison/resolve-values.ts
Normal file
343
apps/sim/lib/workflows/comparison/resolve-values.ts
Normal file
@@ -0,0 +1,343 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { getBlock } from '@/blocks/registry'
|
||||
import { SELECTOR_TYPES_HYDRATION_REQUIRED, type SubBlockConfig } from '@/blocks/types'
|
||||
import { CREDENTIAL_SET, isUuid } from '@/executor/constants'
|
||||
import { fetchCredentialSetById } from '@/hooks/queries/credential-sets'
|
||||
import { fetchOAuthCredentialDetail } from '@/hooks/queries/oauth-credentials'
|
||||
import { getSelectorDefinition } from '@/hooks/selectors/registry'
|
||||
import { resolveSelectorForSubBlock } from '@/hooks/selectors/resolution'
|
||||
import type { SelectorKey } from '@/hooks/selectors/types'
|
||||
import type { WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
|
||||
const logger = createLogger('ResolveValues')
|
||||
|
||||
/**
|
||||
* Result of resolving a value for display
|
||||
*/
|
||||
export interface ResolvedValue {
|
||||
/** The original value before resolution */
|
||||
original: unknown
|
||||
/** Human-readable label for display */
|
||||
displayLabel: string
|
||||
/** Whether the value was successfully resolved to a name */
|
||||
resolved: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Context needed to resolve values for display
|
||||
*/
|
||||
export interface ResolutionContext {
|
||||
/** The block type (e.g., 'slack', 'gmail') */
|
||||
blockType: string
|
||||
/** The subBlock field ID (e.g., 'channel', 'credential') */
|
||||
subBlockId: string
|
||||
/** The workflow ID for API calls */
|
||||
workflowId: string
|
||||
/** The current workflow state for extracting additional context */
|
||||
currentState: WorkflowState
|
||||
/** The block ID being resolved */
|
||||
blockId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended context extracted from block subBlocks for selector resolution
|
||||
*/
|
||||
interface ExtendedSelectorContext {
|
||||
credentialId?: string
|
||||
domain?: string
|
||||
projectId?: string
|
||||
planId?: string
|
||||
teamId?: string
|
||||
knowledgeBaseId?: string
|
||||
siteId?: string
|
||||
collectionId?: string
|
||||
spreadsheetId?: string
|
||||
}
|
||||
|
||||
function isResolvableValue(value: unknown): value is string {
|
||||
if (typeof value !== 'string' || !value) return false
|
||||
if (value.startsWith(CREDENTIAL_SET.PREFIX)) return true
|
||||
if (isUuid(value)) return true
|
||||
if (/^C[A-Z0-9]{8,}$/.test(value)) return true
|
||||
if (/^[UW][A-Z0-9]{8,}$/.test(value)) return true
|
||||
return false
|
||||
}
|
||||
|
||||
function getSemanticFallback(subBlockId: string, subBlockConfig?: SubBlockConfig): string {
|
||||
if (subBlockConfig?.title) {
|
||||
return subBlockConfig.title.toLowerCase()
|
||||
}
|
||||
|
||||
const patterns: Record<string, string> = {
|
||||
credential: 'credential',
|
||||
channel: 'channel',
|
||||
channelId: 'channel',
|
||||
user: 'user',
|
||||
userId: 'user',
|
||||
workflow: 'workflow',
|
||||
workflowId: 'workflow',
|
||||
file: 'file',
|
||||
fileId: 'file',
|
||||
folder: 'folder',
|
||||
folderId: 'folder',
|
||||
project: 'project',
|
||||
projectId: 'project',
|
||||
team: 'team',
|
||||
teamId: 'team',
|
||||
sheet: 'sheet',
|
||||
sheetId: 'sheet',
|
||||
document: 'document',
|
||||
documentId: 'document',
|
||||
knowledgeBase: 'knowledge base',
|
||||
knowledgeBaseId: 'knowledge base',
|
||||
server: 'server',
|
||||
serverId: 'server',
|
||||
tool: 'tool',
|
||||
toolId: 'tool',
|
||||
calendar: 'calendar',
|
||||
calendarId: 'calendar',
|
||||
label: 'label',
|
||||
labelId: 'label',
|
||||
site: 'site',
|
||||
siteId: 'site',
|
||||
collection: 'collection',
|
||||
collectionId: 'collection',
|
||||
item: 'item',
|
||||
itemId: 'item',
|
||||
contact: 'contact',
|
||||
contactId: 'contact',
|
||||
task: 'task',
|
||||
taskId: 'task',
|
||||
chat: 'chat',
|
||||
chatId: 'chat',
|
||||
}
|
||||
|
||||
return patterns[subBlockId] || 'value'
|
||||
}
|
||||
|
||||
async function resolveCredential(credentialId: string, workflowId: string): Promise<string | null> {
|
||||
try {
|
||||
if (credentialId.startsWith(CREDENTIAL_SET.PREFIX)) {
|
||||
const setId = credentialId.slice(CREDENTIAL_SET.PREFIX.length)
|
||||
const credentialSet = await fetchCredentialSetById(setId)
|
||||
return credentialSet?.name ?? null
|
||||
}
|
||||
|
||||
const credentials = await fetchOAuthCredentialDetail(credentialId, workflowId)
|
||||
if (credentials.length > 0) {
|
||||
return credentials[0].name ?? null
|
||||
}
|
||||
|
||||
return null
|
||||
} catch (error) {
|
||||
logger.warn('Failed to resolve credential', { credentialId, error })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveWorkflow(workflowId: string): Promise<string | null> {
|
||||
try {
|
||||
const definition = getSelectorDefinition('sim.workflows')
|
||||
if (definition.fetchById) {
|
||||
const result = await definition.fetchById({
|
||||
key: 'sim.workflows',
|
||||
context: {},
|
||||
detailId: workflowId,
|
||||
})
|
||||
return result?.label ?? null
|
||||
}
|
||||
return null
|
||||
} catch (error) {
|
||||
logger.warn('Failed to resolve workflow', { workflowId, error })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveSelectorValue(
|
||||
value: string,
|
||||
selectorKey: SelectorKey,
|
||||
extendedContext: ExtendedSelectorContext,
|
||||
workflowId: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const definition = getSelectorDefinition(selectorKey)
|
||||
const selectorContext = {
|
||||
workflowId,
|
||||
credentialId: extendedContext.credentialId,
|
||||
domain: extendedContext.domain,
|
||||
projectId: extendedContext.projectId,
|
||||
planId: extendedContext.planId,
|
||||
teamId: extendedContext.teamId,
|
||||
knowledgeBaseId: extendedContext.knowledgeBaseId,
|
||||
siteId: extendedContext.siteId,
|
||||
collectionId: extendedContext.collectionId,
|
||||
spreadsheetId: extendedContext.spreadsheetId,
|
||||
}
|
||||
|
||||
if (definition.fetchById) {
|
||||
const result = await definition.fetchById({
|
||||
key: selectorKey,
|
||||
context: selectorContext,
|
||||
detailId: value,
|
||||
})
|
||||
if (result?.label) {
|
||||
return result.label
|
||||
}
|
||||
}
|
||||
|
||||
const options = await definition.fetchList({
|
||||
key: selectorKey,
|
||||
context: selectorContext,
|
||||
})
|
||||
const match = options.find((opt) => opt.id === value)
|
||||
return match?.label ?? null
|
||||
} catch (error) {
|
||||
logger.warn('Failed to resolve selector value', { value, selectorKey, error })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function extractMcpToolName(toolId: string): string {
|
||||
const withoutPrefix = toolId.startsWith('mcp-') ? toolId.slice(4) : toolId
|
||||
const parts = withoutPrefix.split('_')
|
||||
if (parts.length >= 2) {
|
||||
return parts[parts.length - 1]
|
||||
}
|
||||
return withoutPrefix
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a value for display in diff descriptions.
|
||||
*/
|
||||
export function formatValueForDisplay(value: unknown): string {
|
||||
if (value === null || value === undefined) return '(none)'
|
||||
if (typeof value === 'string') {
|
||||
if (value.length > 50) return `${value.slice(0, 50)}...`
|
||||
return value || '(empty)'
|
||||
}
|
||||
if (typeof value === 'boolean') return value ? 'enabled' : 'disabled'
|
||||
if (typeof value === 'number') return String(value)
|
||||
if (Array.isArray(value)) return `[${value.length} items]`
|
||||
if (typeof value === 'object') return `${JSON.stringify(value).slice(0, 50)}...`
|
||||
return String(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts extended context from a block's subBlocks for selector resolution.
|
||||
* This mirrors the context extraction done in the UI components.
|
||||
*/
|
||||
function extractExtendedContext(
|
||||
blockId: string,
|
||||
currentState: WorkflowState
|
||||
): ExtendedSelectorContext {
|
||||
const block = currentState.blocks?.[blockId]
|
||||
if (!block?.subBlocks) return {}
|
||||
|
||||
const getStringValue = (id: string): string | undefined => {
|
||||
const subBlock = block.subBlocks[id] as { value?: unknown } | undefined
|
||||
const val = subBlock?.value
|
||||
return typeof val === 'string' ? val : undefined
|
||||
}
|
||||
|
||||
return {
|
||||
credentialId: getStringValue('credential'),
|
||||
domain: getStringValue('domain'),
|
||||
projectId: getStringValue('projectId'),
|
||||
planId: getStringValue('planId'),
|
||||
teamId: getStringValue('teamId'),
|
||||
knowledgeBaseId: getStringValue('knowledgeBaseId'),
|
||||
siteId: getStringValue('siteId'),
|
||||
collectionId: getStringValue('collectionId'),
|
||||
spreadsheetId: getStringValue('spreadsheetId') || getStringValue('fileId'),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a value to a human-readable display label.
|
||||
* Uses the selector registry infrastructure to resolve IDs to names.
|
||||
*
|
||||
* @param value - The value to resolve (credential ID, channel ID, UUID, etc.)
|
||||
* @param context - Context needed for resolution (block type, subBlock ID, workflow state)
|
||||
* @returns ResolvedValue with the display label and resolution status
|
||||
*/
|
||||
export async function resolveValueForDisplay(
|
||||
value: unknown,
|
||||
context: ResolutionContext
|
||||
): Promise<ResolvedValue> {
|
||||
if (!isResolvableValue(value)) {
|
||||
return {
|
||||
original: value,
|
||||
displayLabel: formatValueForDisplay(value),
|
||||
resolved: false,
|
||||
}
|
||||
}
|
||||
|
||||
const blockConfig = getBlock(context.blockType)
|
||||
const subBlockConfig = blockConfig?.subBlocks.find((sb) => sb.id === context.subBlockId)
|
||||
const semanticFallback = getSemanticFallback(context.subBlockId, subBlockConfig)
|
||||
|
||||
const extendedContext = context.blockId
|
||||
? extractExtendedContext(context.blockId, context.currentState)
|
||||
: {}
|
||||
|
||||
const isCredentialField =
|
||||
subBlockConfig?.type === 'oauth-input' || context.subBlockId === 'credential'
|
||||
|
||||
if (isCredentialField) {
|
||||
const label = await resolveCredential(value, context.workflowId)
|
||||
if (label) {
|
||||
return { original: value, displayLabel: label, resolved: true }
|
||||
}
|
||||
return { original: value, displayLabel: semanticFallback, resolved: true }
|
||||
}
|
||||
|
||||
if (subBlockConfig?.type === 'workflow-selector') {
|
||||
const label = await resolveWorkflow(value)
|
||||
if (label) {
|
||||
return { original: value, displayLabel: label, resolved: true }
|
||||
}
|
||||
return { original: value, displayLabel: semanticFallback, resolved: true }
|
||||
}
|
||||
|
||||
if (subBlockConfig?.type === 'mcp-tool-selector') {
|
||||
const toolName = extractMcpToolName(value)
|
||||
return { original: value, displayLabel: toolName, resolved: true }
|
||||
}
|
||||
|
||||
if (subBlockConfig && SELECTOR_TYPES_HYDRATION_REQUIRED.includes(subBlockConfig.type)) {
|
||||
const resolution = resolveSelectorForSubBlock(subBlockConfig, {
|
||||
workflowId: context.workflowId,
|
||||
credentialId: extendedContext.credentialId,
|
||||
domain: extendedContext.domain,
|
||||
projectId: extendedContext.projectId,
|
||||
planId: extendedContext.planId,
|
||||
teamId: extendedContext.teamId,
|
||||
knowledgeBaseId: extendedContext.knowledgeBaseId,
|
||||
siteId: extendedContext.siteId,
|
||||
collectionId: extendedContext.collectionId,
|
||||
spreadsheetId: extendedContext.spreadsheetId,
|
||||
})
|
||||
|
||||
if (resolution?.key) {
|
||||
const label = await resolveSelectorValue(
|
||||
value,
|
||||
resolution.key,
|
||||
extendedContext,
|
||||
context.workflowId
|
||||
)
|
||||
if (label) {
|
||||
return { original: value, displayLabel: label, resolved: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isUuid(value)) {
|
||||
return { original: value, displayLabel: semanticFallback, resolved: true }
|
||||
}
|
||||
|
||||
return {
|
||||
original: value,
|
||||
displayLabel: formatValueForDisplay(value),
|
||||
resolved: false,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user