Skip to content

Commit 303c61a

Browse files
committed
feat(deployments): human-readable version descriptions
1 parent dd2f0c6 commit 303c61a

File tree

4 files changed

+516
-20
lines changed

4 files changed

+516
-20
lines changed

apps/sim/hooks/queries/deployments.ts

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -421,28 +421,27 @@ interface GenerateVersionDescriptionVariables {
421421
onStreamChunk?: (accumulated: string) => void
422422
}
423423

424-
const VERSION_DESCRIPTION_SYSTEM_PROMPT = `You are a technical writer generating concise deployment version descriptions.
424+
const VERSION_DESCRIPTION_SYSTEM_PROMPT = `You are writing deployment version descriptions for a workflow automation platform.
425425
426-
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.
426+
Write a brief, factual description (1-3 sentences, under 400 characters) that states what changed between versions.
427427
428-
RULES:
429-
- State specific values when provided (e.g. "model changed from X to Y")
430-
- Do NOT wrap your response in quotes
431-
- Do NOT add filler phrases like "streamlining the workflow", "for improved efficiency"
432-
- Do NOT use markdown formatting
433-
- Do NOT include version numbers
434-
- Do NOT start with "This version" or similar phrases
428+
Guidelines:
429+
- Use the specific values provided (credential names, channel names, model names)
430+
- Be precise: "Changes Slack channel from #general to #alerts" not "Updates channel configuration"
431+
- Combine related changes: "Updates Agent model to claude-sonnet-4-5 and increases temperature to 0.8"
432+
- For added/removed blocks, mention their purpose if clear from the type
435433
436-
Good examples:
437-
- Changes model in Agent 1 from gpt-4o to claude-sonnet-4-20250514.
438-
- Adds Slack notification block. Updates webhook URL to production endpoint.
439-
- Removes Function block and its connection to Router.
434+
Format rules:
435+
- Plain text only, no quotes around the response
436+
- No markdown formatting
437+
- No filler phrases ("for improved efficiency", "streamlining the workflow")
438+
- No version numbers or "This version" prefixes
440439
441-
Bad examples:
442-
- "Changes model..." (NO - don't wrap in quotes)
443-
- Changes model, streamlining the workflow. (NO - don't add filler)
444-
445-
Respond with ONLY the plain text description.`
440+
Examples:
441+
- Switches Agent model from gpt-4o to claude-sonnet-4-5. Changes Slack credential to Production OAuth.
442+
- Adds Gmail notification block for sending alerts. Removes unused Function block. Updates Router conditions.
443+
- Updates system prompt for more concise responses. Reduces temperature from 0.7 to 0.3.
444+
- Connects Slack block to Router. Adds 2 new workflow connections. Configures error handling path.`
446445

447446
/**
448447
* Hook for generating a version description using AI based on workflow diff
@@ -454,7 +453,7 @@ export function useGenerateVersionDescription() {
454453
version,
455454
onStreamChunk,
456455
}: GenerateVersionDescriptionVariables): Promise<string> => {
457-
const { generateWorkflowDiffSummary, formatDiffSummaryForDescription } = await import(
456+
const { generateWorkflowDiffSummary, formatDiffSummaryForDescriptionAsync } = await import(
458457
'@/lib/workflows/comparison/compare'
459458
)
460459

@@ -470,7 +469,11 @@ export function useGenerateVersionDescription() {
470469
}
471470

472471
const diffSummary = generateWorkflowDiffSummary(currentState, previousState)
473-
const diffText = formatDiffSummaryForDescription(diffSummary)
472+
const diffText = await formatDiffSummaryForDescriptionAsync(
473+
diffSummary,
474+
currentState,
475+
workflowId
476+
)
474477

475478
const wandResponse = await fetch('/api/wand', {
476479
method: 'POST',

apps/sim/lib/workflows/comparison/compare.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createLogger } from '@sim/logger'
12
import type { WorkflowState } from '@/stores/workflows/workflow/types'
23
import {
34
extractBlockFieldsForComparison,
@@ -12,6 +13,9 @@ import {
1213
normalizeVariables,
1314
sanitizeVariable,
1415
} from './normalize'
16+
import { resolveValueForDisplay } from './resolve-values'
17+
18+
const logger = createLogger('WorkflowComparison')
1519

1620
/**
1721
* Compare the current workflow state with the deployed state to detect meaningful changes.
@@ -406,3 +410,130 @@ export function formatDiffSummaryForDescription(summary: WorkflowDiffSummary): s
406410

407411
return changes.join('\n')
408412
}
413+
414+
/**
415+
* Converts a WorkflowDiffSummary to a human-readable string with resolved display names.
416+
* Resolves IDs (credentials, channels, workflows, etc.) to human-readable names using
417+
* the selector registry infrastructure.
418+
*
419+
* @param summary - The diff summary to format
420+
* @param currentState - The current workflow state for context extraction
421+
* @param workflowId - The workflow ID for API calls
422+
* @returns A formatted string describing the changes with resolved names
423+
*/
424+
export async function formatDiffSummaryForDescriptionAsync(
425+
summary: WorkflowDiffSummary,
426+
currentState: WorkflowState,
427+
workflowId: string
428+
): Promise<string> {
429+
if (!summary.hasChanges) {
430+
return 'No structural changes detected (configuration may have changed)'
431+
}
432+
433+
const changes: string[] = []
434+
435+
for (const block of summary.addedBlocks) {
436+
const name = block.name || block.type
437+
changes.push(`Added block: ${name} (${block.type})`)
438+
}
439+
440+
for (const block of summary.removedBlocks) {
441+
const name = block.name || block.type
442+
changes.push(`Removed block: ${name} (${block.type})`)
443+
}
444+
445+
const modifiedBlockPromises = summary.modifiedBlocks.map(async (block) => {
446+
const name = block.name || block.type
447+
const blockChanges: string[] = []
448+
449+
const changesToProcess = block.changes.slice(0, 3)
450+
const resolvedChanges = await Promise.all(
451+
changesToProcess.map(async (change) => {
452+
const context = {
453+
blockType: block.type,
454+
subBlockId: change.field,
455+
workflowId,
456+
currentState,
457+
blockId: block.id,
458+
}
459+
460+
const [oldResolved, newResolved] = await Promise.all([
461+
resolveValueForDisplay(change.oldValue, context),
462+
resolveValueForDisplay(change.newValue, context),
463+
])
464+
465+
return {
466+
field: change.field,
467+
oldLabel: oldResolved.displayLabel,
468+
newLabel: newResolved.displayLabel,
469+
}
470+
})
471+
)
472+
473+
for (const resolved of resolvedChanges) {
474+
blockChanges.push(
475+
`Modified ${name}: ${resolved.field} changed from "${resolved.oldLabel}" to "${resolved.newLabel}"`
476+
)
477+
}
478+
479+
if (block.changes.length > 3) {
480+
blockChanges.push(` ...and ${block.changes.length - 3} more changes in ${name}`)
481+
}
482+
483+
return blockChanges
484+
})
485+
486+
const allModifiedBlockChanges = await Promise.all(modifiedBlockPromises)
487+
for (const blockChanges of allModifiedBlockChanges) {
488+
changes.push(...blockChanges)
489+
}
490+
491+
if (summary.edgeChanges.added > 0) {
492+
changes.push(`Added ${summary.edgeChanges.added} connection(s)`)
493+
}
494+
if (summary.edgeChanges.removed > 0) {
495+
changes.push(`Removed ${summary.edgeChanges.removed} connection(s)`)
496+
}
497+
498+
if (summary.loopChanges.added > 0) {
499+
changes.push(`Added ${summary.loopChanges.added} loop(s)`)
500+
}
501+
if (summary.loopChanges.removed > 0) {
502+
changes.push(`Removed ${summary.loopChanges.removed} loop(s)`)
503+
}
504+
if (summary.loopChanges.modified > 0) {
505+
changes.push(`Modified ${summary.loopChanges.modified} loop(s)`)
506+
}
507+
508+
if (summary.parallelChanges.added > 0) {
509+
changes.push(`Added ${summary.parallelChanges.added} parallel group(s)`)
510+
}
511+
if (summary.parallelChanges.removed > 0) {
512+
changes.push(`Removed ${summary.parallelChanges.removed} parallel group(s)`)
513+
}
514+
if (summary.parallelChanges.modified > 0) {
515+
changes.push(`Modified ${summary.parallelChanges.modified} parallel group(s)`)
516+
}
517+
518+
const varChanges: string[] = []
519+
if (summary.variableChanges.added > 0) {
520+
varChanges.push(`${summary.variableChanges.added} added`)
521+
}
522+
if (summary.variableChanges.removed > 0) {
523+
varChanges.push(`${summary.variableChanges.removed} removed`)
524+
}
525+
if (summary.variableChanges.modified > 0) {
526+
varChanges.push(`${summary.variableChanges.modified} modified`)
527+
}
528+
if (varChanges.length > 0) {
529+
changes.push(`Variables: ${varChanges.join(', ')}`)
530+
}
531+
532+
logger.info('Generated async diff description', {
533+
workflowId,
534+
changeCount: changes.length,
535+
modifiedBlocks: summary.modifiedBlocks.length,
536+
})
537+
538+
return changes.join('\n')
539+
}

apps/sim/lib/workflows/comparison/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export type { FieldChange, WorkflowDiffSummary } from './compare'
22
export {
33
formatDiffSummaryForDescription,
4+
formatDiffSummaryForDescriptionAsync,
45
generateWorkflowDiffSummary,
56
hasWorkflowChanged,
67
} from './compare'
@@ -28,3 +29,9 @@ export {
2829
sanitizeVariable,
2930
sortEdges,
3031
} from './normalize'
32+
export type { ResolutionContext, ResolvedValue } from './resolve-values'
33+
export {
34+
extractExtendedContext,
35+
getBlockCredentialId,
36+
resolveValueForDisplay,
37+
} from './resolve-values'

0 commit comments

Comments
 (0)