From d6ec115348d0581fc2e6729298db7f31c776d1d6 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Tue, 7 Apr 2026 16:11:31 -0700 Subject: [PATCH 1/2] v0.6.29: login improvements, posthog telemetry (#4026) * feat(posthog): Add tracking on mothership abort (#4023) Co-authored-by: Theodore Li * fix(login): fix captcha headers for manual login (#4025) * fix(signup): fix turnstile key loading * fix(login): fix captcha header passing * Catch user already exists, remove login form captcha --- apps/sim/app/(auth)/signup/signup-form.tsx | 11 +++-------- .../app/workspace/[workspaceId]/home/home.tsx | 12 ++++++++++-- .../w/[workflowId]/components/panel/panel.tsx | 19 ++++++++++++++++++- apps/sim/lib/posthog/events.ts | 5 +++++ 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/apps/sim/app/(auth)/signup/signup-form.tsx b/apps/sim/app/(auth)/signup/signup-form.tsx index 55a0508ec1b..afb27cd729a 100644 --- a/apps/sim/app/(auth)/signup/signup-form.tsx +++ b/apps/sim/app/(auth)/signup/signup-form.tsx @@ -270,10 +270,8 @@ function SignupFormContent({ name: sanitizedName, }, { - fetchOptions: { - headers: { - ...(token ? { 'x-captcha-response': token } : {}), - }, + headers: { + ...(token ? { 'x-captcha-response': token } : {}), }, onError: (ctx) => { logger.error('Signup error:', ctx.error) @@ -282,10 +280,7 @@ function SignupFormContent({ let errorCode = 'unknown' if (ctx.error.code?.includes('USER_ALREADY_EXISTS')) { errorCode = 'user_already_exists' - errorMessage.push( - 'An account with this email already exists. Please sign in instead.' - ) - setEmailError(errorMessage[0]) + setEmailError('An account with this email already exists. Please sign in instead.') } else if ( ctx.error.code?.includes('BAD_REQUEST') || ctx.error.message?.includes('Email and password sign up is not enabled') diff --git a/apps/sim/app/workspace/[workspaceId]/home/home.tsx b/apps/sim/app/workspace/[workspaceId]/home/home.tsx index d76f17ff454..38367339197 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/home.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/home.tsx @@ -223,6 +223,14 @@ export function Home({ chatId }: HomeProps = {}) { posthogRef.current = posthog }, [posthog]) + const handleStopGeneration = useCallback(() => { + captureEvent(posthogRef.current, 'task_generation_aborted', { + workspace_id: workspaceId, + view: 'mothership', + }) + stopGeneration() + }, [stopGeneration, workspaceId]) + const handleSubmit = useCallback( (text: string, fileAttachments?: FileAttachmentForApi[], contexts?: ChatContext[]) => { const trimmed = text.trim() @@ -334,7 +342,7 @@ export function Home({ chatId }: HomeProps = {}) { defaultValue={initialPrompt} onSubmit={handleSubmit} isSending={isSending} - onStopGeneration={stopGeneration} + onStopGeneration={handleStopGeneration} userId={session?.user?.id} onContextAdd={handleContextAdd} /> @@ -359,7 +367,7 @@ export function Home({ chatId }: HomeProps = {}) { isSending={isSending} isReconnecting={isReconnecting} onSubmit={handleSubmit} - onStopGeneration={stopGeneration} + onStopGeneration={handleStopGeneration} messageQueue={messageQueue} onRemoveQueuedMessage={removeFromQueue} onSendQueuedMessage={sendNow} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx index 4d485c763ce..da51910789b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx @@ -4,6 +4,7 @@ import { memo, useCallback, useEffect, useRef, useState } from 'react' import { createLogger } from '@sim/logger' import { History, Plus, Square } from 'lucide-react' import { useParams, useRouter } from 'next/navigation' +import { usePostHog } from 'posthog-js/react' import { useShallow } from 'zustand/react/shallow' import { BubbleChatClose, @@ -33,6 +34,7 @@ import { import { Lock, Unlock, Upload } from '@/components/emcn/icons' import { VariableIcon } from '@/components/icons' import { useSession } from '@/lib/auth/auth-client' +import { captureEvent } from '@/lib/posthog/client' import { generateWorkflowJson } from '@/lib/workflows/operations/import-export' import { ConversationListItem } from '@/app/workspace/[workspaceId]/components' import { MothershipChat } from '@/app/workspace/[workspaceId]/home/components' @@ -101,6 +103,9 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel const params = useParams() const workspaceId = propWorkspaceId ?? (params.workspaceId as string) + const posthog = usePostHog() + const posthogRef = useRef(posthog) + const panelRef = useRef(null) const fileInputRef = useRef(null) const { activeTab, setActiveTab, panelWidth, _hasHydrated, setHasHydrated } = usePanelStore( @@ -264,6 +269,10 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel loadCopilotChats() }, [loadCopilotChats]) + useEffect(() => { + posthogRef.current = posthog + }, [posthog]) + const handleCopilotSelectChat = useCallback((chat: { id: string; title: string | null }) => { setCopilotChatId(chat.id) setCopilotChatTitle(chat.title) @@ -394,6 +403,14 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel [copilotEditQueuedMessage] ) + const handleCopilotStopGeneration = useCallback(() => { + captureEvent(posthogRef.current, 'task_generation_aborted', { + workspace_id: workspaceId, + view: 'copilot', + }) + copilotStopGeneration() + }, [copilotStopGeneration, workspaceId]) + const handleCopilotSubmit = useCallback( (text: string, fileAttachments?: FileAttachmentForApi[], contexts?: ChatContext[]) => { const trimmed = text.trim() @@ -833,7 +850,7 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel isSending={copilotIsSending} isReconnecting={copilotIsReconnecting} onSubmit={handleCopilotSubmit} - onStopGeneration={copilotStopGeneration} + onStopGeneration={handleCopilotStopGeneration} messageQueue={copilotMessageQueue} onRemoveQueuedMessage={copilotRemoveFromQueue} onSendQueuedMessage={copilotSendNow} diff --git a/apps/sim/lib/posthog/events.ts b/apps/sim/lib/posthog/events.ts index 537a9864282..faf9895bf62 100644 --- a/apps/sim/lib/posthog/events.ts +++ b/apps/sim/lib/posthog/events.ts @@ -378,6 +378,11 @@ export interface PostHogEventMap { workspace_id: string } + task_generation_aborted: { + workspace_id: string + view: 'mothership' | 'copilot' + } + task_message_sent: { workspace_id: string has_attachments: boolean From b487934c8f8c54a14e3547fa8f3e5e98fae797c3 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 4 May 2026 13:25:50 -0700 Subject: [PATCH 2/2] feat(image-generator): add gpt-image-2 model support --- apps/sim/blocks/blocks/image_generator.ts | 87 ++++++++++++++++++++++- apps/sim/tools/openai/image.ts | 33 +++++++-- 2 files changed, 110 insertions(+), 10 deletions(-) diff --git a/apps/sim/blocks/blocks/image_generator.ts b/apps/sim/blocks/blocks/image_generator.ts index 6963cd604fd..953cce252bb 100644 --- a/apps/sim/blocks/blocks/image_generator.ts +++ b/apps/sim/blocks/blocks/image_generator.ts @@ -8,7 +8,7 @@ export const ImageGeneratorBlock: BlockConfig = { description: 'Generate images', authMode: AuthMode.ApiKey, longDescription: - 'Integrate Image Generator into the workflow. Can generate images using DALL-E 3 or GPT Image.', + 'Integrate Image Generator into the workflow. Can generate images using DALL-E 3, GPT Image 1, or GPT Image 2.', docsLink: 'https://docs.sim.ai/tools/image_generator', category: 'tools', integrationType: IntegrationType.AI, @@ -22,7 +22,8 @@ export const ImageGeneratorBlock: BlockConfig = { type: 'dropdown', options: [ { label: 'DALL-E 3', id: 'dall-e-3' }, - { label: 'GPT Image', id: 'gpt-image-1' }, + { label: 'GPT Image 1', id: 'gpt-image-1' }, + { label: 'GPT Image 2', id: 'gpt-image-2' }, ], value: () => 'dall-e-3', }, @@ -60,6 +61,22 @@ export const ImageGeneratorBlock: BlockConfig = { condition: { field: 'model', value: 'gpt-image-1' }, dependsOn: ['model'], }, + { + id: 'size', + title: 'Size', + type: 'dropdown', + options: [ + { label: 'Auto', id: 'auto' }, + { label: '1024x1024 (Square)', id: '1024x1024' }, + { label: '1536x1024 (Landscape)', id: '1536x1024' }, + { label: '1024x1536 (Portrait)', id: '1024x1536' }, + { label: '2048x2048 (2K Square)', id: '2048x2048' }, + { label: '3840x2160 (4K Landscape)', id: '3840x2160' }, + ], + value: () => 'auto', + condition: { field: 'model', value: 'gpt-image-2' }, + dependsOn: ['model'], + }, { id: 'quality', title: 'Quality', @@ -72,6 +89,20 @@ export const ImageGeneratorBlock: BlockConfig = { condition: { field: 'model', value: 'dall-e-3' }, dependsOn: ['model'], }, + { + id: 'quality', + title: 'Quality', + type: 'dropdown', + options: [ + { label: 'Auto', id: 'auto' }, + { label: 'Low', id: 'low' }, + { label: 'Medium', id: 'medium' }, + { label: 'High', id: 'high' }, + ], + value: () => 'auto', + condition: { field: 'model', value: ['gpt-image-1', 'gpt-image-2'] }, + dependsOn: ['model'], + }, { id: 'style', title: 'Style', @@ -97,6 +128,43 @@ export const ImageGeneratorBlock: BlockConfig = { condition: { field: 'model', value: 'gpt-image-1' }, dependsOn: ['model'], }, + { + id: 'background', + title: 'Background', + type: 'dropdown', + options: [ + { label: 'Auto', id: 'auto' }, + { label: 'Opaque', id: 'opaque' }, + ], + value: () => 'auto', + condition: { field: 'model', value: 'gpt-image-2' }, + dependsOn: ['model'], + }, + { + id: 'outputFormat', + title: 'Output Format', + type: 'dropdown', + options: [ + { label: 'PNG', id: 'png' }, + { label: 'JPEG', id: 'jpeg' }, + { label: 'WebP', id: 'webp' }, + ], + value: () => 'png', + condition: { field: 'model', value: ['gpt-image-1', 'gpt-image-2'] }, + dependsOn: ['model'], + }, + { + id: 'moderation', + title: 'Moderation', + type: 'dropdown', + options: [ + { label: 'Auto', id: 'auto' }, + { label: 'Low', id: 'low' }, + ], + value: () => 'auto', + condition: { field: 'model', value: 'gpt-image-2' }, + dependsOn: ['model'], + }, { id: 'apiKey', title: 'API Key', @@ -120,7 +188,7 @@ export const ImageGeneratorBlock: BlockConfig = { } const model = params.model || 'dall-e-3' - const size = params.size || (model === 'gpt-image-1' ? 'auto' : '1024x1024') + const size = params.size || (model === 'dall-e-3' ? '1024x1024' : 'auto') const baseParams = { prompt: params.prompt, model, @@ -138,7 +206,18 @@ export const ImageGeneratorBlock: BlockConfig = { if (model === 'gpt-image-1') { return { ...baseParams, + ...(params.quality && { quality: params.quality }), + ...(params.background && { background: params.background }), + ...(params.outputFormat && { outputFormat: params.outputFormat }), + } + } + if (model === 'gpt-image-2') { + return { + ...baseParams, + ...(params.quality && { quality: params.quality }), ...(params.background && { background: params.background }), + ...(params.outputFormat && { outputFormat: params.outputFormat }), + ...(params.moderation && { moderation: params.moderation }), } } @@ -153,6 +232,8 @@ export const ImageGeneratorBlock: BlockConfig = { quality: { type: 'string', description: 'Image quality level' }, style: { type: 'string', description: 'Image style' }, background: { type: 'string', description: 'Background type' }, + outputFormat: { type: 'string', description: 'Output image format (png, jpeg, webp)' }, + moderation: { type: 'string', description: 'Moderation level (auto or low)' }, apiKey: { type: 'string', description: 'OpenAI API key' }, }, outputs: { diff --git a/apps/sim/tools/openai/image.ts b/apps/sim/tools/openai/image.ts index 2e857d153f6..1c844639c78 100644 --- a/apps/sim/tools/openai/image.ts +++ b/apps/sim/tools/openai/image.ts @@ -16,7 +16,7 @@ export const imageTool: ToolConfig = { type: 'string', required: true, visibility: 'user-only', - description: 'The model to use (gpt-image-1 or dall-e-3)', + description: 'The model to use (dall-e-3, gpt-image-1, or gpt-image-2)', }, prompt: { type: 'string', @@ -34,19 +34,33 @@ export const imageTool: ToolConfig = { type: 'string', required: false, visibility: 'user-or-llm', - description: 'The quality of the image (standard or hd)', + description: + 'Quality. dall-e-3: standard|hd. gpt-image-1/gpt-image-2: auto|low|medium|high', }, style: { type: 'string', required: false, visibility: 'user-or-llm', - description: 'The style of the image (vivid or natural)', + description: 'The style of the image (vivid or natural), only for dall-e-3', }, background: { type: 'string', required: false, visibility: 'user-or-llm', - description: 'The background color, only for gpt-image-1', + description: + 'Background. gpt-image-1: auto|transparent|opaque. gpt-image-2: auto|opaque (transparent not supported)', + }, + outputFormat: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Output image format (png, jpeg, webp), only for gpt-image-1 and gpt-image-2', + }, + moderation: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Moderation level (auto or low), only for gpt-image-2', }, n: { type: 'number', @@ -73,15 +87,20 @@ export const imageTool: ToolConfig = { const body: BaseImageRequestBody = { model: params.model, prompt: params.prompt, - size: params.size || '1024x1024', + size: params.size || (params.model === 'dall-e-3' ? '1024x1024' : 'auto'), n: params.n ? Number(params.n) : 1, } if (params.model === 'dall-e-3') { if (params.quality) body.quality = params.quality if (params.style) body.style = params.style - } else if (params.model === 'gpt-image-1') { + } else if (params.model === 'gpt-image-1' || params.model === 'gpt-image-2') { + if (params.quality) body.quality = params.quality if (params.background) body.background = params.background + if (params.outputFormat) body.output_format = params.outputFormat + if (params.model === 'gpt-image-2' && params.moderation) { + body.moderation = params.moderation + } } return body @@ -111,7 +130,7 @@ export const imageTool: ToolConfig = { } else if (data.data?.[0]?.b64_json) { base64Image = data.data[0].b64_json logger.info( - 'Found base64 encoded image in response for GPT-Image-1', + `Found base64 encoded image in response for ${modelName}`, `length: ${base64Image.length}` ) } else {