Skip to content

Commit 1e3ec1f

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

File tree

6 files changed

+506
-36
lines changed

6 files changed

+506
-36
lines changed

apps/docs/components/icons.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5127,11 +5127,11 @@ export function SimilarwebIcon(props: SVGProps<SVGSVGElement>) {
51275127
<path
51285128
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'
51295129
fill='currentColor'
5130-
/>
5130+
/>
51315131
</svg>
51325132
)
51335133
}
5134-
5134+
51355135
export function CalComIcon(props: SVGProps<SVGSVGElement>) {
51365136
return (
51375137
<svg

apps/sim/components/icons.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5131,7 +5131,7 @@ export function SimilarwebIcon(props: SVGProps<SVGSVGElement>) {
51315131
</svg>
51325132
)
51335133
}
5134-
5134+
51355135
export function CalComIcon(props: SVGProps<SVGSVGElement>) {
51365136
return (
51375137
<svg

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 & 13 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 { formatValueForDisplay, 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.
@@ -318,19 +322,6 @@ export function generateWorkflowDiffSummary(
318322
return result
319323
}
320324

321-
function formatValueForDisplay(value: unknown): string {
322-
if (value === null || value === undefined) return '(none)'
323-
if (typeof value === 'string') {
324-
if (value.length > 50) return `${value.slice(0, 50)}...`
325-
return value || '(empty)'
326-
}
327-
if (typeof value === 'boolean') return value ? 'enabled' : 'disabled'
328-
if (typeof value === 'number') return String(value)
329-
if (Array.isArray(value)) return `[${value.length} items]`
330-
if (typeof value === 'object') return `${JSON.stringify(value).slice(0, 50)}...`
331-
return String(value)
332-
}
333-
334325
/**
335326
* Convert a WorkflowDiffSummary to a human-readable string for AI description generation
336327
*/
@@ -406,3 +397,130 @@ export function formatDiffSummaryForDescription(summary: WorkflowDiffSummary): s
406397

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

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

Lines changed: 1 addition & 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'

0 commit comments

Comments
 (0)