Skip to content

Commit bc161f4

Browse files
committed
fix(image-generator): preserve saved catalog settings
1 parent 76557f2 commit bc161f4

4 files changed

Lines changed: 65 additions & 25 deletions

File tree

‎apps/sim/blocks/blocks/image_generator.ts‎

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -314,12 +314,13 @@ export const ImageGeneratorV2Block: BlockConfig = {
314314
id: 'model',
315315
title: 'Model',
316316
type: 'dropdown',
317-
options: imageModels
318-
.filter((model) => model.provider === provider)
319-
.map((model) => ({
320-
label: model.label,
321-
id: model.id,
322-
})),
317+
options: (params) =>
318+
imageModels
319+
.filter((model) => !params || model.provider === provider)
320+
.flatMap((model) => [
321+
{ label: model.label, id: model.id },
322+
...(model.aliases ?? []).map((id) => ({ id, label: model.label, hidden: true })),
323+
]),
323324
value: () => imageModelDefaults[provider],
324325
condition: { field: 'provider', value: provider },
325326
dependsOn: ['provider'],
@@ -358,9 +359,10 @@ export const ImageGeneratorV2Block: BlockConfig = {
358359
}),
359360
options: (params) => [
360361
{ id: 'default', label: 'Model default' },
361-
...(
362-
getImageModel(params?.values.provider, params?.values.model)?.options?.[key]?.values ??
363-
[]
362+
...(params
363+
? (getImageModel(params.values.provider, params.values.model)?.options?.[key]?.values ??
364+
[])
365+
: [...new Set(imageModels.flatMap((model) => model.options?.[key]?.values ?? []))]
364366
).map((value) => ({
365367
id: String(value),
366368
label: String(value),
@@ -372,8 +374,8 @@ export const ImageGeneratorV2Block: BlockConfig = {
372374
(key): SubBlockConfig => ({
373375
id: key,
374376
title: key === 'enableSafetyChecker' ? 'Safety Checker' : 'Web Search',
375-
type: 'dropdown',
376-
value: () => 'default',
377+
type: 'switch',
378+
defaultValue: key === 'enableSafetyChecker',
377379
mode: 'advanced',
378380
dependsOn: ['provider', 'model'],
379381
condition: (values) => ({
@@ -382,11 +384,6 @@ export const ImageGeneratorV2Block: BlockConfig = {
382384
? [String(values?.model)]
383385
: [],
384386
}),
385-
options: [
386-
{ id: 'default', label: 'Model default' },
387-
{ id: 'true', label: 'On' },
388-
{ id: 'false', label: 'Off' },
389-
],
390387
})
391388
),
392389
{

‎apps/sim/lib/media/image-catalog.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface ImageModel {
1717
provider: 'openai' | 'gemini' | 'falai'
1818
id: string
1919
label: string
20+
aliases?: readonly string[]
2021
endpoint?: string
2122
output?: string
2223
options?: Partial<Record<ImageOption, { field: string; values: readonly (string | number)[] }>>
@@ -90,6 +91,9 @@ export const imageModels: readonly ImageModel[] = [
9091
provider: 'gemini',
9192
id,
9293
label,
94+
aliases: ['gemini-3-pro-image', 'gemini-3.1-flash-image'].includes(id)
95+
? [`${id}-preview`]
96+
: [],
9397
options: {
9498
aspectRatio: {
9599
field: 'aspectRatio',
@@ -113,21 +117,17 @@ export const imageModels: readonly ImageModel[] = [
113117
provider: 'falai',
114118
endpoint,
115119
id: 'legacyId' in spec ? spec.legacyId : endpoint,
120+
aliases: 'legacyId' in spec ? [endpoint] : [],
116121
})
117122
),
118123
]
119124

120125
const byId = new Map(imageModels.map((model) => [`${model.provider}:${model.id}`, model]))
121126
for (const model of imageModels) {
122-
if (model.endpoint) byId.set(`falai:${model.endpoint}`, model)
123-
}
124-
const geminiAliases: Record<string, string> = {
125-
'gemini-3.1-flash-image-preview': 'gemini-3.1-flash-image',
126-
'gemini-3-pro-image-preview': 'gemini-3-pro-image',
127+
for (const alias of model.aliases ?? []) byId.set(`${model.provider}:${alias}`, model)
127128
}
128129

129130
export function getImageModel(provider: unknown, id: unknown) {
130131
if (typeof provider !== 'string' || typeof id !== 'string') return undefined
131-
const modelId = provider === 'gemini' ? (geminiAliases[id] ?? id) : id
132-
return byId.get(`${provider}:${modelId}`)
132+
return byId.get(`${provider}:${id}`)
133133
}

‎apps/sim/lib/workflows/application/replace-workflow-state.test.ts‎

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import { replaceWorkflowState } from '@/lib/workflows/application/replace-workfl
5454
import { validateInputsForBlock } from '@/lib/workflows/editing/validation'
5555
import { AgentBlock } from '@/blocks/blocks/agent'
5656
import { ExaBlock } from '@/blocks/blocks/exa'
57+
import { ImageGeneratorV2Block } from '@/blocks/blocks/image_generator'
5758
import { getBlock } from '@/blocks/registry'
5859

5960
const mockNeedsRedeployment = workflowDeploymentStatusMockFns.mockCheckNeedsRedeployment
@@ -93,7 +94,13 @@ const input = { workflowId: 'workflow-1', blocks: { 'block-1': BLOCK }, edges: [
9394
describe('replaceWorkflowState', () => {
9495
beforeEach(() => {
9596
vi.mocked(getBlock).mockImplementation((type) =>
96-
type === 'exa' ? ExaBlock : type === 'agent' ? AgentBlock : defaultGetBlock?.(type)
97+
type === 'image_generator_v2'
98+
? ImageGeneratorV2Block
99+
: type === 'exa'
100+
? ExaBlock
101+
: type === 'agent'
102+
? AgentBlock
103+
: defaultGetBlock?.(type)
97104
)
98105
mockResolveContext.mockResolvedValue(context)
99106
mockResolvePermission.mockResolvedValue('write')
@@ -113,6 +120,38 @@ describe('replaceWorkflowState', () => {
113120
mockLoadNormalized.mockResolvedValue({ blocks: {}, edges: [], loops: {}, parallels: {} })
114121
})
115122

123+
it.each([
124+
{ provider: 'openai', model: 'gpt-image-2', size: '1024x1024', quality: 'high' },
125+
{
126+
provider: 'gemini',
127+
model: 'gemini-3-pro-image-preview',
128+
resolution: '2K',
129+
aspectRatio: '16:9',
130+
},
131+
{
132+
provider: 'falai',
133+
model: 'nano-banana-2',
134+
resolution: '1K',
135+
aspectRatio: '1:1',
136+
enableSafetyChecker: true,
137+
enableWebSearch: false,
138+
},
139+
])('saves configured $provider image workflows through the state API', async (settings) => {
140+
const block = {
141+
...BLOCK,
142+
type: 'image_generator_v2',
143+
subBlocks: Object.fromEntries(
144+
Object.entries(settings).map(([id, value]) => [id, { id, type: 'dropdown', value }])
145+
),
146+
}
147+
await expect(
148+
replaceWorkflowState.execute({
149+
principal: sessionPrincipal,
150+
input: { ...input, blocks: { 'block-1': block } },
151+
})
152+
).resolves.toMatchObject({ workflowId: 'workflow-1' })
153+
})
154+
116155
/**
117156
* Two things this pins that a same-shape input and output cannot: the write
118157
* carries the **sanitized** graph, not the caller's body, and the reported

‎apps/sim/scripts/validate-fal-image-models.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,11 @@ for (const model of imageModels.filter((item) => item.provider === 'falai')) {
110110
const wireKey = option?.field ?? model.booleans?.[field.id]
111111
if (!wireKey) continue
112112
const options =
113-
typeof field.options === 'function' ? field.options({ values: base }) : field.options
113+
field.type === 'switch'
114+
? [{ id: 'true' }, { id: 'false' }]
115+
: typeof field.options === 'function'
116+
? field.options({ values: base })
117+
: field.options
114118
assert(options?.length)
115119
for (const value of options) check({ [field.id]: value.id }, wireKey, value.id)
116120
}

0 commit comments

Comments
 (0)