diff --git a/sim/app/api/function/execute/route.ts b/sim/app/api/function/execute/route.ts index 1ccfbcbaf1..b863379237 100644 --- a/sim/app/api/function/execute/route.ts +++ b/sim/app/api/function/execute/route.ts @@ -67,17 +67,15 @@ export async function POST(req: NextRequest) { environmentVariables: envVars, // Make environment variables available in the context console: { log: (...args: any[]) => { - const logMessage = - args - .map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg))) - .join(' ') + const logMessage = args + .map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg))) + .join(' ') stdout += logMessage }, error: (...args: any[]) => { - const errorMessage = - args - .map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg))) - .join(' ') + const errorMessage = args + .map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg))) + .join(' ') logger.error(`[${requestId}] Code Console Error:`, errorMessage) stdout += 'ERROR: ' + errorMessage }, diff --git a/sim/app/api/proxy-image/route.ts b/sim/app/api/proxy-image/route.ts index f6c24e5b6d..4db842a740 100644 --- a/sim/app/api/proxy-image/route.ts +++ b/sim/app/api/proxy-image/route.ts @@ -15,7 +15,7 @@ export async function GET(request: Request) { // Add appropriate headers for fetching images const response = await fetch(imageUrl, { headers: { - 'Accept': 'image/*, */*', + Accept: 'image/*, */*', 'User-Agent': 'Mozilla/5.0 (compatible; ImageProxyBot/1.0)', }, // Set a reasonable timeout @@ -24,17 +24,17 @@ export async function GET(request: Request) { if (!response.ok) { console.error(`Failed to fetch image from ${imageUrl}:`, response.status, response.statusText) - return new NextResponse(`Failed to fetch image: ${response.status} ${response.statusText}`, { - status: response.status + return new NextResponse(`Failed to fetch image: ${response.status} ${response.statusText}`, { + status: response.status, }) } const contentType = response.headers.get('content-type') console.log('Image content-type:', contentType) - + const blob = await response.blob() console.log('Image size:', blob.size, 'bytes') - + if (blob.size === 0) { console.error('Empty image received from source URL') return new NextResponse('Empty image received from source', { status: 422 }) @@ -52,11 +52,11 @@ export async function GET(request: Request) { } catch (error) { // Log the full error for debugging console.error('Error proxying image:', error) - + // Return a helpful error response return new NextResponse( - `Internal Server Error: ${error instanceof Error ? error.message : 'Unknown error'}`, + `Internal Server Error: ${error instanceof Error ? error.message : 'Unknown error'}`, { status: 500 } ) } -} \ No newline at end of file +} diff --git a/sim/app/globals.css b/sim/app/globals.css index 24fc82b161..7dd86a4f29 100644 --- a/sim/app/globals.css +++ b/sim/app/globals.css @@ -15,7 +15,7 @@ z-index: 30 !important; } -.workflow-container .react-flow__edge [data-testid="workflow-edge"] { +.workflow-container .react-flow__edge [data-testid='workflow-edge'] { z-index: 0 !important; } diff --git a/sim/app/w/[id]/components/workflow-block/components/sub-block/components/dropdown.tsx b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/dropdown.tsx index e299be63d9..a5de4ece6e 100644 --- a/sim/app/w/[id]/components/workflow-block/components/sub-block/components/dropdown.tsx +++ b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/dropdown.tsx @@ -9,7 +9,9 @@ import { import { useSubBlockValue } from '../hooks/use-sub-block-value' interface DropdownProps { - options: Array | (() => Array) + options: + | Array + | (() => Array) defaultValue?: string blockId: string subBlockId: string diff --git a/sim/blocks/blocks/agent.ts b/sim/blocks/blocks/agent.ts index 56dde3fb14..7a38ccb2ba 100644 --- a/sim/blocks/blocks/agent.ts +++ b/sim/blocks/blocks/agent.ts @@ -1,9 +1,9 @@ import { AgentIcon } from '@/components/icons' +import { useOllamaStore } from '@/stores/ollama/store' import { MODELS_TEMP_RANGE_0_1, MODELS_TEMP_RANGE_0_2 } from '@/providers/model-capabilities' import { getAllModelProviders, getBaseModelProviders } from '@/providers/utils' import { ToolResponse } from '@/tools/types' import { BlockConfig } from '../types' -import { useOllamaStore } from '@/stores/ollama/store' interface AgentResponse extends ToolResponse { output: { diff --git a/sim/blocks/blocks/evaluator.ts b/sim/blocks/blocks/evaluator.ts index 511fbb0773..1d4c09a7ad 100644 --- a/sim/blocks/blocks/evaluator.ts +++ b/sim/blocks/blocks/evaluator.ts @@ -1,10 +1,10 @@ import { ChartBarIcon } from '@/components/icons' import { createLogger } from '@/lib/logs/console-logger' +import { useOllamaStore } from '@/stores/ollama/store' import { ProviderId } from '@/providers/types' -import { getBaseModelProviders, getAllModelProviders } from '@/providers/utils' +import { getAllModelProviders, getBaseModelProviders } from '@/providers/utils' import { ToolResponse } from '@/tools/types' import { BlockConfig, ParamType } from '../types' -import { useOllamaStore } from '@/stores/ollama/store' const logger = createLogger('EvaluatorBlock') diff --git a/sim/blocks/blocks/image-generator.ts b/sim/blocks/blocks/image-generator.ts index 93245d6030..80bbab91bd 100644 --- a/sim/blocks/blocks/image-generator.ts +++ b/sim/blocks/blocks/image-generator.ts @@ -17,9 +17,7 @@ export const ImageGeneratorBlock: BlockConfig = { title: 'Provider', type: 'dropdown', layout: 'full', - options: [ - { label: 'DALL-E', id: 'dalle' }, - ], + options: [{ label: 'DALL-E', id: 'dalle' }], value: () => 'dalle', }, { @@ -121,8 +119,8 @@ export const ImageGeneratorBlock: BlockConfig = { type: { content: 'string', // URL of the generated image image: 'string', // Base64 image data - metadata: 'json' // Contains only model information - } + metadata: 'json', // Contains only model information + }, }, }, -} \ No newline at end of file +} diff --git a/sim/blocks/blocks/router.ts b/sim/blocks/blocks/router.ts index 27a5fab0f5..1d8de561f0 100644 --- a/sim/blocks/blocks/router.ts +++ b/sim/blocks/blocks/router.ts @@ -1,9 +1,9 @@ import { ConnectIcon } from '@/components/icons' +import { useOllamaStore } from '@/stores/ollama/store' import { ProviderId } from '@/providers/types' -import { getBaseModelProviders, getAllModelProviders } from '@/providers/utils' +import { getAllModelProviders, getBaseModelProviders } from '@/providers/utils' import { ToolResponse } from '@/tools/types' import { BlockConfig } from '../types' -import { useOllamaStore } from '@/stores/ollama/store' interface RouterResponse extends ToolResponse { output: { diff --git a/sim/components/icons.tsx b/sim/components/icons.tsx index 02df29a38c..f3f61c3ec8 100644 --- a/sim/components/icons.tsx +++ b/sim/components/icons.tsx @@ -1755,4 +1755,4 @@ export function ImageIcon(props: SVGProps) { ) -} \ No newline at end of file +} diff --git a/sim/components/ui/command.tsx b/sim/components/ui/command.tsx index bd415fc07c..c5aee351a7 100644 --- a/sim/components/ui/command.tsx +++ b/sim/components/ui/command.tsx @@ -48,6 +48,12 @@ import { cn } from '@/lib/utils' // This file is not typed correctly from shadcn, so we're disabling the type checker // @ts-nocheck +// This file is not typed correctly from shadcn, so we're disabling the type checker +// @ts-nocheck + +// This file is not typed correctly from shadcn, so we're disabling the type checker +// @ts-nocheck + const Command = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { diff --git a/sim/executor/index.test.ts b/sim/executor/index.test.ts index 543b5e54fe..a91df10315 100644 --- a/sim/executor/index.test.ts +++ b/sim/executor/index.test.ts @@ -238,7 +238,7 @@ const createWorkflowWithLoop = (): SerializedWorkflow => ({ nodes: ['block1', 'block2'], iterations: 5, loopType: 'forEach', - forEachItems: [1, 2, 3, 4, 5] + forEachItems: [1, 2, 3, 4, 5], }, }, }) @@ -324,14 +324,14 @@ const createWorkflowWithNestedLoops = (): SerializedWorkflow => ({ id: 'outerLoop', nodes: ['outer-block1', 'inner-block1', 'inner-block2', 'outer-block2'], iterations: 3, - loopType: 'for' + loopType: 'for', }, innerLoop: { id: 'innerLoop', nodes: ['inner-block1', 'inner-block2'], iterations: 2, loopType: 'forEach', - forEachItems: ['a', 'b'] + forEachItems: ['a', 'b'], }, }, }) @@ -846,9 +846,9 @@ describe('Executor', () => { */ describe('loop management', () => { beforeEach(() => { - vi.resetModules(); - vi.clearAllMocks(); - }); + vi.resetModules() + vi.clearAllMocks() + }) test('should increment loop iterations correctly', async () => { // Mock the LoopManager @@ -856,23 +856,23 @@ describe('Executor', () => { LoopManager: vi.fn().mockImplementation(() => ({ processLoopIterations: vi.fn().mockImplementation(async (context) => { // Simulate incrementing iteration counter - const currentIteration = context.loopIterations.get('loop1') || 0; - context.loopIterations.set('loop1', currentIteration + 1); - return false; + const currentIteration = context.loopIterations.get('loop1') || 0 + context.loopIterations.set('loop1', currentIteration + 1) + return false }), getLoopIndex: vi.fn().mockImplementation((loopId, blockId, context) => { - return context.loopIterations.get(loopId) || 0; - }) - })) - })); - + return context.loopIterations.get(loopId) || 0 + }), + })), + })) + // Create a minimal workflow with loop - const workflow = createWorkflowWithLoop(); - + const workflow = createWorkflowWithLoop() + // Import with mocks applied - const { LoopManager } = await import('./loops'); - const loopManager = new LoopManager(workflow.loops); - + const { LoopManager } = await import('./loops') + const loopManager = new LoopManager(workflow.loops) + // Create a mock context const context = { workflowId: 'test-workflow-id', @@ -885,60 +885,55 @@ describe('Executor', () => { loopItems: new Map(), executedBlocks: new Set(['block1', 'block2']), activeExecutionPath: new Set(['block1', 'block2']), - workflow - }; - + workflow, + } + // Process loop iterations to increment counter - await loopManager.processLoopIterations(context); - + await loopManager.processLoopIterations(context) + // Verify that the loop iteration counter was incremented - expect(context.loopIterations.get('loop1')).toBe(1); - + expect(context.loopIterations.get('loop1')).toBe(1) + // Get loop index - const loopIndex = loopManager.getLoopIndex('loop1', 'block1', context); - + const loopIndex = loopManager.getLoopIndex('loop1', 'block1', context) + // The loop index should match the iteration counter - expect(loopIndex).toBe(1); - }); - + expect(loopIndex).toBe(1) + }) + test('should handle forEach loop item access correctly', async () => { // Mock the InputResolver vi.doMock('./resolver', () => ({ InputResolver: vi.fn().mockImplementation(() => ({ resolveBlockReferences: vi.fn().mockImplementation((value, context, block) => { if (value === '') { - const loopId = 'loop1'; - return String(context.loopIterations.get(loopId) || 0); + const loopId = 'loop1' + return String(context.loopIterations.get(loopId) || 0) } - return value; - }) - })) - })); - + return value + }), + })), + })) + // Mock the LoopManager vi.doMock('./loops', () => ({ LoopManager: vi.fn().mockImplementation(() => ({ getLoopIndex: vi.fn().mockImplementation((loopId, blockId, context) => { - return context.loopIterations.get(loopId) || 0; - }) - })) - })); - + return context.loopIterations.get(loopId) || 0 + }), + })), + })) + // Create a minimal workflow with forEach loop - const workflow = createWorkflowWithLoop(); - + const workflow = createWorkflowWithLoop() + // Import with mocks applied - const { Executor } = await import('./index'); - const executor = new Executor(workflow); - - const { InputResolver } = await import('./resolver'); - const resolver = new InputResolver( - workflow, - {}, - {}, - (executor as any).loopManager - ); - + const { Executor } = await import('./index') + const executor = new Executor(workflow) + + const { InputResolver } = await import('./resolver') + const resolver = new InputResolver(workflow, {}, {}, (executor as any).loopManager) + // Create a mock context const context = { workflowId: 'test-workflow-id', @@ -951,171 +946,180 @@ describe('Executor', () => { loopItems: new Map([['loop1', 3]]), // Current item is 3 executedBlocks: new Set(['block1']), activeExecutionPath: new Set(['block1', 'block2']), - workflow - }; - + workflow, + } + // Resolve a loop index reference - const resolvedIndex = resolver.resolveBlockReferences('', context, workflow.blocks[1]); - + const resolvedIndex = resolver.resolveBlockReferences( + '', + context, + workflow.blocks[1] + ) + // The resolved index should be 2 (current iteration) - expect(resolvedIndex).toBe('2'); - + expect(resolvedIndex).toBe('2') + // Set up a different iteration and test again - context.loopIterations.set('loop1', 4); - const resolvedIndexAgain = resolver.resolveBlockReferences('', context, workflow.blocks[1]); - expect(resolvedIndexAgain).toBe('4'); - }); - + context.loopIterations.set('loop1', 4) + const resolvedIndexAgain = resolver.resolveBlockReferences( + '', + context, + workflow.blocks[1] + ) + expect(resolvedIndexAgain).toBe('4') + }) + test('should update loop indices correctly between iterations', async () => { // Reset modules to ensure clean state - vi.resetModules(); - + vi.resetModules() + // Create array to capture indices - const capturedIndices: number[] = []; - + const capturedIndices: number[] = [] + // Mock the LoopManager implementation vi.doMock('./loops', () => ({ LoopManager: vi.fn().mockImplementation(() => ({ processLoopIterations: vi.fn().mockImplementation(async (context) => { // Simulate 3 loop iterations if (context.executedBlocks.has('block1') && context.executedBlocks.has('block2')) { - const currentIteration = context.loopIterations.get('loop1') || 0; + const currentIteration = context.loopIterations.get('loop1') || 0 if (currentIteration < 2) { // Increment iteration and reset blocks - context.loopIterations.set('loop1', currentIteration + 1); - context.executedBlocks.delete('block1'); - context.executedBlocks.delete('block2'); - return false; + context.loopIterations.set('loop1', currentIteration + 1) + context.executedBlocks.delete('block1') + context.executedBlocks.delete('block2') + return false } } - return true; + return true }), getLoopIndex: vi.fn().mockImplementation((loopId, blockId, context) => { // Return the current iteration counter - return context.loopIterations.get(loopId) || 0; - }) - })) - })); - + return context.loopIterations.get(loopId) || 0 + }), + })), + })) + // Mock the handlers to capture loop indices vi.doMock('./handlers', () => ({ AgentBlockHandler: vi.fn().mockImplementation(() => ({ canHandle: () => false, - execute: vi.fn() + execute: vi.fn(), })), RouterBlockHandler: vi.fn().mockImplementation(() => ({ canHandle: () => false, - execute: vi.fn() + execute: vi.fn(), })), ConditionBlockHandler: vi.fn().mockImplementation(() => ({ canHandle: () => false, - execute: vi.fn() + execute: vi.fn(), })), EvaluatorBlockHandler: vi.fn().mockImplementation(() => ({ canHandle: () => false, - execute: vi.fn() + execute: vi.fn(), })), FunctionBlockHandler: vi.fn().mockImplementation(() => ({ - canHandle: (block: any) => block.metadata?.id === 'function' || block.id === 'block1' || block.id === 'block2', + canHandle: (block: any) => + block.metadata?.id === 'function' || block.id === 'block1' || block.id === 'block2', execute: vi.fn().mockImplementation(async (block, inputs, context) => { // Capture the loop index during execution - const loopIndex = context.loopIterations.get('loop1') || 0; - capturedIndices.push(loopIndex); - return { response: { result: `Index: ${loopIndex}` } }; - }) + const loopIndex = context.loopIterations.get('loop1') || 0 + capturedIndices.push(loopIndex) + return { response: { result: `Index: ${loopIndex}` } } + }), })), ApiBlockHandler: vi.fn().mockImplementation(() => ({ canHandle: () => false, - execute: vi.fn() + execute: vi.fn(), })), GenericBlockHandler: vi.fn().mockImplementation(() => ({ canHandle: () => true, - execute: vi.fn().mockResolvedValue({ response: { result: 'Executed' } }) - })) - })); - + execute: vi.fn().mockResolvedValue({ response: { result: 'Executed' } }), + })), + })) + // Mock PathTracker vi.doMock('./path', () => ({ PathTracker: vi.fn().mockImplementation(() => ({ updateExecutionPaths: vi.fn(), - isInActivePath: vi.fn().mockReturnValue(true) - })) - })); - + isInActivePath: vi.fn().mockReturnValue(true), + })), + })) + // Create a workflow with loop - const workflow = createWorkflowWithLoop(); - + const workflow = createWorkflowWithLoop() + // Import the executor with mocks applied - const { Executor } = await import('./index'); - const executor = new Executor(workflow); - + const { Executor } = await import('./index') + const executor = new Executor(workflow) + // Manually simulate execution to populate capturedIndices // First iteration - both blocks with index 0 - capturedIndices.push(0, 0); + capturedIndices.push(0, 0) // Second iteration - both blocks with index 1 - capturedIndices.push(1, 1); + capturedIndices.push(1, 1) // Third iteration - both blocks with index 2 - capturedIndices.push(2, 2); - + capturedIndices.push(2, 2) + // We should have captured indices 0, 0 (first iteration - both blocks) // then 1, 1 (second iteration - both blocks) // then 2, 2 (third iteration - both blocks) - expect(capturedIndices).toEqual([0, 0, 1, 1, 2, 2]); - }); - + expect(capturedIndices).toEqual([0, 0, 1, 1, 2, 2]) + }) + test('should handle nested loops correctly', async () => { // Reset modules to ensure clean state - vi.resetModules(); - + vi.resetModules() + // Create array to capture indices - const capturedIndices: {loopId: string, blockId: string, index: number}[] = []; - + const capturedIndices: { loopId: string; blockId: string; index: number }[] = [] + // Mock the LoopManager vi.doMock('./loops', () => ({ LoopManager: vi.fn().mockImplementation(() => ({ processLoopIterations: vi.fn().mockImplementation(async (context) => { - return true; + return true }), getLoopIndex: vi.fn().mockImplementation((loopId, blockId, context) => { - return context.loopIterations.get(loopId) || 0; - }) - })) - })); - + return context.loopIterations.get(loopId) || 0 + }), + })), + })) + // Mock the handlers to capture loop indices vi.doMock('./handlers', () => ({ AgentBlockHandler: vi.fn().mockImplementation(() => ({ canHandle: () => false, - execute: vi.fn() + execute: vi.fn(), })), RouterBlockHandler: vi.fn().mockImplementation(() => ({ canHandle: () => false, - execute: vi.fn() + execute: vi.fn(), })), ConditionBlockHandler: vi.fn().mockImplementation(() => ({ canHandle: () => false, - execute: vi.fn() + execute: vi.fn(), })), EvaluatorBlockHandler: vi.fn().mockImplementation(() => ({ canHandle: () => false, - execute: vi.fn() + execute: vi.fn(), })), FunctionBlockHandler: vi.fn().mockImplementation(() => ({ canHandle: (block: any) => block.id.includes('block'), execute: vi.fn().mockImplementation(async (block, inputs, context) => { - return { response: { result: 'Executed' } }; - }) + return { response: { result: 'Executed' } } + }), })), ApiBlockHandler: vi.fn().mockImplementation(() => ({ canHandle: () => false, - execute: vi.fn() + execute: vi.fn(), })), GenericBlockHandler: vi.fn().mockImplementation(() => ({ canHandle: () => true, - execute: vi.fn().mockResolvedValue({ response: { result: 'Executed' } }) - })) - })); - + execute: vi.fn().mockResolvedValue({ response: { result: 'Executed' } }), + })), + })) + // Manually populate the capturedIndices array for testing capturedIndices.push( { loopId: 'innerLoop', blockId: 'inner-block1', index: 0 }, @@ -1126,60 +1130,60 @@ describe('Executor', () => { { loopId: 'outerLoop', blockId: 'outer-block2', index: 0 }, { loopId: 'outerLoop', blockId: 'outer-block1', index: 1 }, { loopId: 'outerLoop', blockId: 'outer-block2', index: 1 } - ); - + ) + // Verify that nested loops maintain independent counters - expect(capturedIndices.length).toBeGreaterThan(0); - + expect(capturedIndices.length).toBeGreaterThan(0) + // Group captures by loopId const innerLoopIndices = capturedIndices - .filter(c => c.loopId === 'innerLoop') - .map(c => c.index); - + .filter((c) => c.loopId === 'innerLoop') + .map((c) => c.index) + const outerLoopIndices = capturedIndices - .filter(c => c.loopId === 'outerLoop') - .map(c => c.index); - + .filter((c) => c.loopId === 'outerLoop') + .map((c) => c.index) + // Verify inner loop indices - should increment on each iteration - expect(innerLoopIndices).toContain(0); - expect(innerLoopIndices).toContain(1); - + expect(innerLoopIndices).toContain(0) + expect(innerLoopIndices).toContain(1) + // Verify outer loop indices - expect(outerLoopIndices).toContain(0); - expect(outerLoopIndices).toContain(1); - }); - + expect(outerLoopIndices).toContain(0) + expect(outerLoopIndices).toContain(1) + }) + test('should fix the bug where first two iterations showed same index', async () => { // Reset modules to ensure clean state - vi.resetModules(); - + vi.resetModules() + // Mock the LoopManager vi.doMock('./loops', () => ({ LoopManager: vi.fn().mockImplementation(() => ({ processLoopIterations: vi.fn().mockImplementation(async (context) => { // Increment iteration when both blocks executed if (context.executedBlocks.has('block1') && context.executedBlocks.has('block2')) { - const currentIteration = context.loopIterations.get('loop1') || 0; - context.loopIterations.set('loop1', currentIteration + 1); - context.executedBlocks.delete('block1'); - context.executedBlocks.delete('block2'); + const currentIteration = context.loopIterations.get('loop1') || 0 + context.loopIterations.set('loop1', currentIteration + 1) + context.executedBlocks.delete('block1') + context.executedBlocks.delete('block2') } - return false; + return false }), getLoopIndex: vi.fn().mockImplementation((loopId, blockId, context) => { // Return current iteration counter (not subtracting 1 as in the old buggy version) - return context.loopIterations.get(loopId) || 0; - }) - })) - })); - + return context.loopIterations.get(loopId) || 0 + }), + })), + })) + // Import with mocks applied - const { LoopManager } = await import('./loops'); - + const { LoopManager } = await import('./loops') + // Create a workflow with a simple loop - const workflow = createWorkflowWithLoop(); - const loopManager = new LoopManager(workflow.loops); - + const workflow = createWorkflowWithLoop() + const loopManager = new LoopManager(workflow.loops) + // Create a mock context const context = { workflowId: 'test-workflow-id', @@ -1192,54 +1196,54 @@ describe('Executor', () => { loopItems: new Map(), executedBlocks: new Set(), activeExecutionPath: new Set(['block1', 'block2']), - workflow - }; - + workflow, + } + // First iteration - this should give index 0 for both blocks - const firstIterationIndex1 = loopManager.getLoopIndex('loop1', 'block1', context); - const firstIterationIndex2 = loopManager.getLoopIndex('loop1', 'block2', context); - - expect(firstIterationIndex1).toBe(0); - expect(firstIterationIndex2).toBe(0); - + const firstIterationIndex1 = loopManager.getLoopIndex('loop1', 'block1', context) + const firstIterationIndex2 = loopManager.getLoopIndex('loop1', 'block2', context) + + expect(firstIterationIndex1).toBe(0) + expect(firstIterationIndex2).toBe(0) + // Execute first iteration of both blocks - context.executedBlocks.add('block1'); - context.executedBlocks.add('block2'); - + context.executedBlocks.add('block1') + context.executedBlocks.add('block2') + // Process loop iterations - this should increment the counter to 1 - await loopManager.processLoopIterations(context); - + await loopManager.processLoopIterations(context) + // Verify counter has been incremented BEFORE resetting blocks - expect(context.loopIterations.get('loop1')).toBe(1); - + expect(context.loopIterations.get('loop1')).toBe(1) + // Verify blocks have been reset - expect(context.executedBlocks.has('block1')).toBe(false); - expect(context.executedBlocks.has('block2')).toBe(false); - + expect(context.executedBlocks.has('block1')).toBe(false) + expect(context.executedBlocks.has('block2')).toBe(false) + // Now in second iteration - indices should be 1, not 0 - const secondIterationIndex1 = loopManager.getLoopIndex('loop1', 'block1', context); - const secondIterationIndex2 = loopManager.getLoopIndex('loop1', 'block2', context); - + const secondIterationIndex1 = loopManager.getLoopIndex('loop1', 'block1', context) + const secondIterationIndex2 = loopManager.getLoopIndex('loop1', 'block2', context) + // This is the critical test - indices should be 1 for the second iteration - expect(secondIterationIndex1).toBe(1); - expect(secondIterationIndex2).toBe(1); - + expect(secondIterationIndex1).toBe(1) + expect(secondIterationIndex2).toBe(1) + // Execute second iteration of both blocks - context.executedBlocks.add('block1'); - context.executedBlocks.add('block2'); - + context.executedBlocks.add('block1') + context.executedBlocks.add('block2') + // Process loop iterations again - should increment to 2 - await loopManager.processLoopIterations(context); - + await loopManager.processLoopIterations(context) + // Verify counter has been incremented again - expect(context.loopIterations.get('loop1')).toBe(2); - + expect(context.loopIterations.get('loop1')).toBe(2) + // Third iteration indices should be 2 - const thirdIterationIndex1 = loopManager.getLoopIndex('loop1', 'block1', context); - const thirdIterationIndex2 = loopManager.getLoopIndex('loop1', 'block2', context); - - expect(thirdIterationIndex1).toBe(2); - expect(thirdIterationIndex2).toBe(2); - }); + const thirdIterationIndex1 = loopManager.getLoopIndex('loop1', 'block1', context) + const thirdIterationIndex2 = loopManager.getLoopIndex('loop1', 'block2', context) + + expect(thirdIterationIndex1).toBe(2) + expect(thirdIterationIndex2).toBe(2) + }) }) }) diff --git a/sim/executor/index.ts b/sim/executor/index.ts index e46253808c..c17573c0ff 100644 --- a/sim/executor/index.ts +++ b/sim/executor/index.ts @@ -46,7 +46,12 @@ export class Executor { this.workflowInput = workflowInput || {} this.loopManager = new LoopManager(workflow.loops || {}) - this.resolver = new InputResolver(workflow, environmentVariables, workflowVariables, this.loopManager) + this.resolver = new InputResolver( + workflow, + environmentVariables, + workflowVariables, + this.loopManager + ) this.pathTracker = new PathTracker(workflow) this.blockHandlers = [ diff --git a/sim/executor/loops.ts b/sim/executor/loops.ts index 1c326c201d..028f2ff1ae 100644 --- a/sim/executor/loops.ts +++ b/sim/executor/loops.ts @@ -31,17 +31,20 @@ export class LoopManager { // Handle forEach loop if (loopType === 'forEach') { // Get the items to iterate over if we haven't already processed them into an array/object - if (!loop.forEachItems || typeof loop.forEachItems === 'string' || - !(Array.isArray(loop.forEachItems) || typeof loop.forEachItems === 'object')) { + if ( + !loop.forEachItems || + typeof loop.forEachItems === 'string' || + !(Array.isArray(loop.forEachItems) || typeof loop.forEachItems === 'object') + ) { // Evaluate the forEach items expression - const items = await this.evalForEachItems(loopId, loop, context); - + const items = await this.evalForEachItems(loopId, loop, context) + // Store the evaluated items for future iterations if (Array.isArray(items) || (typeof items === 'object' && items !== null)) { - loop.forEachItems = items; + loop.forEachItems = items } else { // Default to empty array if we couldn't get any valid items - loop.forEachItems = []; + loop.forEachItems = [] } } @@ -49,10 +52,10 @@ export class LoopManager { const currentIteration = context.loopIterations.get(loopId) || 0 // For forEach, convert to array if it's an object - const items = Array.isArray(loop.forEachItems) - ? loop.forEachItems + const items = Array.isArray(loop.forEachItems) + ? loop.forEachItems : Object.entries(loop.forEachItems as Record) - + // If we've processed all items or hit max iterations, skip this loop if (currentIteration >= items.length || currentIteration >= loop.iterations) { if (currentIteration >= items.length) { @@ -63,14 +66,14 @@ export class LoopManager { // Check if all blocks in the loop have been executed const allExecuted = this.allBlocksExecuted(loop.nodes, context) - + if (allExecuted) { // Get current item to process in this iteration const currentItem = items[currentIteration] - + // Store the current item in the context for blocks to access via context.loopItems.set(loopId, currentItem) - + // IMPORTANT: We're incrementing the iteration counter AFTER storing the current item // But BEFORE resetting the blocks for next iteration // This ensures that when blocks execute in the new iteration, they'll get the correct index @@ -109,7 +112,7 @@ export class LoopManager { // Check if all blocks in the loop have been executed const allExecuted = this.allBlocksExecuted(loop.nodes, context) - + if (allExecuted) { // IMPORTANT: Increment the counter BEFORE resetting blocks for the next iteration // This ensures the next iteration will show the correct index value @@ -145,7 +148,7 @@ export class LoopManager { /** * Gets the correct loop index based on the current block being executed. * Accounts for position within the loop cycle to provide accurate index. - * + * * @param loopId - ID of the loop * @param blockId - ID of the block requesting the index * @param context - Current execution context @@ -154,20 +157,20 @@ export class LoopManager { getLoopIndex(loopId: string, blockId: string, context: ExecutionContext): number { const loop = this.loops[loopId] if (!loop) return 0 - + // Get the current iteration counter from context const iterationCounter = context.loopIterations.get(loopId) || 0 - + // Simply return the current iteration counter // Since we're updating the iteration counter BEFORE resetting blocks, // the counter will already be at the correct value for the current iteration return iterationCounter } - + /** * Determines the execution order of blocks in a loop based on the connections. * This is needed to figure out which blocks should be assigned which iteration. - * + * * @param nodeIds - IDs of nodes in the loop * @param context - Current execution context * @returns Array of block IDs in execution order @@ -176,22 +179,22 @@ export class LoopManager { // Start with the entry block const entryBlock = this.findEntryBlock(nodeIds, context) if (!entryBlock) return nodeIds - + const result: string[] = [entryBlock] const visited = new Set([entryBlock]) - + // Perform a depth-first traversal to determine execution order const traverse = (nodeId: string) => { // Find all outgoing connections from this node - const connections = context.workflow?.connections.filter( - conn => conn.source === nodeId && - nodeIds.includes(conn.target) && - conn.sourceHandle !== 'error' - ) || [] - + const connections = + context.workflow?.connections.filter( + (conn) => + conn.source === nodeId && nodeIds.includes(conn.target) && conn.sourceHandle !== 'error' + ) || [] + // Sort by target node to ensure deterministic order connections.sort((a, b) => a.target.localeCompare(b.target)) - + // Visit each target node for (const conn of connections) { if (!visited.has(conn.target)) { @@ -201,99 +204,102 @@ export class LoopManager { } } } - + // Start traversal from the entry block traverse(entryBlock) - + // If there are nodes we didn't visit, add them at the end for (const nodeId of nodeIds) { if (!visited.has(nodeId)) { result.push(nodeId) } } - + return result } /** * Evaluates the forEach items string or retrieves items for a forEach loop. - * + * * @param loopId - ID of the loop * @param loop - Loop configuration * @param context - Current execution context * @returns Items to iterate over (array or object) */ private async evalForEachItems( - loopId: string, - loop: SerializedLoop, + loopId: string, + loop: SerializedLoop, context: ExecutionContext ): Promise | undefined> { // If forEachItems is not set, return empty array if (!loop.forEachItems) { - return []; + return [] } - + // If we already have items as an array or object, return them directly - if (Array.isArray(loop.forEachItems) || (typeof loop.forEachItems === 'object' && loop.forEachItems !== null)) { - return loop.forEachItems as any[] | Record; + if ( + Array.isArray(loop.forEachItems) || + (typeof loop.forEachItems === 'object' && loop.forEachItems !== null) + ) { + return loop.forEachItems as any[] | Record } // If we have forEachItems as a string, try to evaluate it as an expression if (typeof loop.forEachItems === 'string') { try { // Skip comments or empty expressions - const trimmedExpression = loop.forEachItems.trim(); + const trimmedExpression = loop.forEachItems.trim() if (trimmedExpression.startsWith('//') || trimmedExpression === '') { - return []; + return [] } - + // First check if it's valid JSON (array or object) if (trimmedExpression.startsWith('[') || trimmedExpression.startsWith('{')) { try { // Try to parse as JSON first - return JSON.parse(trimmedExpression); + return JSON.parse(trimmedExpression) } catch (jsonError) { - console.error(`Error parsing JSON for loop ${loopId}:`, jsonError); + console.error(`Error parsing JSON for loop ${loopId}:`, jsonError) // If JSON parsing fails, continue with expression evaluation } } - + // If not valid JSON or JSON parsing failed, try to evaluate as an expression - const result = new Function('context', `return ${loop.forEachItems}`)(context); - + const result = new Function('context', `return ${loop.forEachItems}`)(context) + // If the result is an array or object, return it if (Array.isArray(result) || (typeof result === 'object' && result !== null)) { - return result; + return result } - + // If it's a primitive, wrap it in an array if (result !== undefined) { - return [result]; + return [result] } - - return []; + + return [] } catch (e) { - console.error(`Error evaluating forEach items for loop ${loopId}:`, e); - return []; + console.error(`Error evaluating forEach items for loop ${loopId}:`, e) + return [] } } // As a fallback, try to find the first non-empty array or object in the context for (const [blockId, blockState] of context.blockStates.entries()) { - const output = blockState.output?.response; + const output = blockState.output?.response if (output) { // Look for arrays or objects in the response that could be iterated over for (const [key, value] of Object.entries(output)) { if (Array.isArray(value) && value.length > 0) { - return value; + return value } else if (typeof value === 'object' && value !== null && Object.keys(value).length > 0) { - return value; + return value } } } } - return []; + return [] } /** diff --git a/sim/executor/resolver.ts b/sim/executor/resolver.ts index 5aad7e4759..251d920830 100644 --- a/sim/executor/resolver.ts +++ b/sim/executor/resolver.ts @@ -1,6 +1,6 @@ import { SerializedBlock, SerializedWorkflow } from '@/serializer/types' -import { ExecutionContext } from './types' import { LoopManager } from './loops' +import { ExecutionContext } from './types' /** * Resolves input values for blocks by handling references and variable substitution. @@ -182,91 +182,98 @@ export class InputResolver { if (containingLoopId) { const loop = context.workflow?.loops[containingLoopId] const loopType = loop?.loopType || 'for' - + // Handle each loop property if (pathParts[0] === 'currentItem') { // Get the items to iterate over - const items = this.getLoopItems(loop, context); - + const items = this.getLoopItems(loop, context) + // Get the correct index using the LoopManager - const index = this.loopManager + const index = this.loopManager ? this.loopManager.getLoopIndex(containingLoopId, currentBlock.id, context) - : context.loopIterations.get(containingLoopId) || 0; - + : context.loopIterations.get(containingLoopId) || 0 + // Get the current item directly from the items array at the current index if (Array.isArray(items) && index >= 0 && index < items.length) { - const currentItem = items[index]; - + const currentItem = items[index] + // Format the value based on type if (currentItem !== undefined) { if (typeof currentItem !== 'object' || currentItem === null) { // For primitives, convert to string - resolvedValue = resolvedValue.replace(match, String(currentItem)); - } else if (Array.isArray(currentItem) && currentItem.length === 2 && typeof currentItem[0] === 'string') { + resolvedValue = resolvedValue.replace(match, String(currentItem)) + } else if ( + Array.isArray(currentItem) && + currentItem.length === 2 && + typeof currentItem[0] === 'string' + ) { // Handle [key, value] pair from Object.entries() if (pathParts.length > 1) { if (pathParts[1] === 'key') { - resolvedValue = resolvedValue.replace(match, String(currentItem[0])); + resolvedValue = resolvedValue.replace(match, String(currentItem[0])) } else if (pathParts[1] === 'value') { - const itemValue = currentItem[1]; - const formattedValue = typeof itemValue === 'object' && itemValue !== null - ? JSON.stringify(itemValue) - : String(itemValue); - resolvedValue = resolvedValue.replace(match, formattedValue); + const itemValue = currentItem[1] + const formattedValue = + typeof itemValue === 'object' && itemValue !== null + ? JSON.stringify(itemValue) + : String(itemValue) + resolvedValue = resolvedValue.replace(match, formattedValue) } } else { // Default to stringifying the whole item - resolvedValue = resolvedValue.replace(match, JSON.stringify(currentItem)); + resolvedValue = resolvedValue.replace(match, JSON.stringify(currentItem)) } } else { // Navigate path if provided for objects if (pathParts.length > 1) { - let itemValue = currentItem; + let itemValue = currentItem for (let i = 1; i < pathParts.length; i++) { if (!itemValue || typeof itemValue !== 'object') { - throw new Error(`Invalid path "${pathParts[i]}" in loop item reference "${path}"`); + throw new Error( + `Invalid path "${pathParts[i]}" in loop item reference "${path}"` + ) } - itemValue = itemValue[pathParts[i]]; + itemValue = itemValue[pathParts[i]] if (itemValue === undefined) { - throw new Error(`No value found at path "${path}" in loop item`); + throw new Error(`No value found at path "${path}" in loop item`) } } - - const formattedValue = typeof itemValue === 'object' && itemValue !== null - ? JSON.stringify(itemValue) - : String(itemValue); - - resolvedValue = resolvedValue.replace(match, formattedValue); + + const formattedValue = + typeof itemValue === 'object' && itemValue !== null + ? JSON.stringify(itemValue) + : String(itemValue) + + resolvedValue = resolvedValue.replace(match, formattedValue) } else { // Return the whole item as JSON - resolvedValue = resolvedValue.replace(match, JSON.stringify(currentItem)); + resolvedValue = resolvedValue.replace(match, JSON.stringify(currentItem)) } } } - - continue; + + continue } } else if (pathParts[0] === 'items' && loopType === 'forEach') { // Get all items in the forEach loop - const items = this.getLoopItems(loop, context); - + const items = this.getLoopItems(loop, context) + if (items) { // Format the items based on type - const formattedValue = typeof items === 'object' && items !== null - ? JSON.stringify(items) - : String(items); - - resolvedValue = resolvedValue.replace(match, formattedValue); - continue; + const formattedValue = + typeof items === 'object' && items !== null ? JSON.stringify(items) : String(items) + + resolvedValue = resolvedValue.replace(match, formattedValue) + continue } } else if (pathParts[0] === 'index') { // Use the LoopManager to get the correct index const index = this.loopManager ? this.loopManager.getLoopIndex(containingLoopId, currentBlock.id, context) - : context.loopIterations.get(containingLoopId) || 0; - - resolvedValue = resolvedValue.replace(match, String(index)); - continue; + : context.loopIterations.get(containingLoopId) || 0 + + resolvedValue = resolvedValue.replace(match, String(index)) + continue } } } @@ -614,58 +621,61 @@ export class InputResolver { * @returns The items to iterate over (array or object) */ private getLoopItems(loop: any, context: ExecutionContext): any[] | Record | null { - if (!loop) return null; - + if (!loop) return null + // If items are already available as an array or object, return them directly if (loop.forEachItems) { - if (Array.isArray(loop.forEachItems) || (typeof loop.forEachItems === 'object' && loop.forEachItems !== null)) { - return loop.forEachItems; + if ( + Array.isArray(loop.forEachItems) || + (typeof loop.forEachItems === 'object' && loop.forEachItems !== null) + ) { + return loop.forEachItems } - + // If it's a string, try to evaluate it (could be an expression or JSON) if (typeof loop.forEachItems === 'string') { try { // Check if it's valid JSON - const trimmedExpression = loop.forEachItems.trim(); + const trimmedExpression = loop.forEachItems.trim() if (trimmedExpression.startsWith('[') || trimmedExpression.startsWith('{')) { try { // Try to parse as JSON first - return JSON.parse(trimmedExpression); + return JSON.parse(trimmedExpression) } catch (jsonError) { - console.error(`Error parsing JSON for loop:`, jsonError); + console.error(`Error parsing JSON for loop:`, jsonError) // If JSON parsing fails, continue with expression evaluation } } - + // If not valid JSON or JSON parsing failed, try to evaluate as an expression if (trimmedExpression && !trimmedExpression.startsWith('//')) { - const result = new Function('context', `return ${loop.forEachItems}`)(context); + const result = new Function('context', `return ${loop.forEachItems}`)(context) if (Array.isArray(result) || (typeof result === 'object' && result !== null)) { - return result; + return result } } } catch (e) { - console.error(`Error evaluating forEach items:`, e); + console.error(`Error evaluating forEach items:`, e) } } } - + // As a fallback, look for the most recent array or object in any block's output // This is less reliable but might help in some cases for (const [blockId, blockState] of context.blockStates.entries()) { - const output = blockState.output?.response; + const output = blockState.output?.response if (output) { for (const [key, value] of Object.entries(output)) { if (Array.isArray(value) && value.length > 0) { - return value; + return value } else if (typeof value === 'object' && value !== null && Object.keys(value).length > 0) { - return value; + return value } } } } - + // Default to empty array if no valid items found - return []; + return [] } } diff --git a/sim/providers/ollama/index.ts b/sim/providers/ollama/index.ts index 4c38a81d18..9d21672e65 100644 --- a/sim/providers/ollama/index.ts +++ b/sim/providers/ollama/index.ts @@ -1,9 +1,9 @@ import OpenAI from 'openai' import { createLogger } from '@/lib/logs/console-logger' +import { useOllamaStore } from '@/stores/ollama/store' import { executeTool } from '@/tools' import { ProviderConfig, ProviderRequest, ProviderResponse, TimeSegment } from '../types' import { ModelsObject } from './types' -import { useOllamaStore } from '@/stores/ollama/store' const logger = createLogger('Ollama Provider') const OLLAMA_HOST = 'http://localhost:11434' @@ -25,12 +25,12 @@ export const ollamaProvider: ProviderConfig = { logger.warn('Ollama service is not available. The provider will be disabled.') return } - const data = await response.json() as ModelsObject + const data = (await response.json()) as ModelsObject this.models = data.models.map((model) => model.name) useOllamaStore.getState().setModels(this.models) } catch (error) { logger.warn('Ollama model instantiation failed. The provider will be disabled.', { - error: error instanceof Error ? error.message : 'Unknown error' + error: error instanceof Error ? error.message : 'Unknown error', }) } }, @@ -52,7 +52,7 @@ export const ollamaProvider: ProviderConfig = { try { // Prepare messages array const ollama = new OpenAI({ - apiKey: "empty", + apiKey: 'empty', baseURL: `${OLLAMA_HOST}/v1`, dangerouslyAllowBrowser: true, }) @@ -296,4 +296,3 @@ export const ollamaProvider: ProviderConfig = { } }, } - diff --git a/sim/providers/ollama/types.ts b/sim/providers/ollama/types.ts index 741fdaf5a0..70c8971c61 100644 --- a/sim/providers/ollama/types.ts +++ b/sim/providers/ollama/types.ts @@ -1,12 +1,12 @@ interface Model { - name: string; - model: string; - modified_at: string; - size: number; - digest: string; - details: object; - } - + name: string + model: string + modified_at: string + size: number + digest: string + details: object +} + export interface ModelsObject { - models: Model[]; - } \ No newline at end of file + models: Model[] +} diff --git a/sim/providers/utils.ts b/sim/providers/utils.ts index 6b557a57d4..c8cbd2e657 100644 --- a/sim/providers/utils.ts +++ b/sim/providers/utils.ts @@ -5,8 +5,8 @@ import { cerebrasProvider } from './cerebras' import { deepseekProvider } from './deepseek' import { googleProvider } from './google' import { groqProvider } from './groq' -import { openaiProvider } from './openai' import { ollamaProvider } from './ollama' +import { openaiProvider } from './openai' import { getModelPricing } from './pricing' import { ProviderConfig, ProviderId, ProviderToolConfig } from './types' import { xAIProvider } from './xai' @@ -72,9 +72,9 @@ export const providers: Record< // Initialize all providers that have initialize method Object.entries(providers).forEach(([id, provider]) => { if (provider.initialize) { - provider.initialize().catch(error => { + provider.initialize().catch((error) => { logger.error(`Failed to initialize ${id} provider`, { - error: error instanceof Error ? error.message : 'Unknown error' + error: error instanceof Error ? error.message : 'Unknown error', }) }) } diff --git a/sim/stores/ollama/store.ts b/sim/stores/ollama/store.ts index 3fca8e658f..a998b37e46 100644 --- a/sim/stores/ollama/store.ts +++ b/sim/stores/ollama/store.ts @@ -17,4 +17,4 @@ export const useOllamaStore = create((set) => ({ // Update the providers when models change updateOllamaProviderModels(models) }, -})) \ No newline at end of file +})) diff --git a/sim/stores/workflows/workflow/store.ts b/sim/stores/workflows/workflow/store.ts index 9c290de31a..59ea0e1902 100644 --- a/sim/stores/workflows/workflow/store.ts +++ b/sim/stores/workflows/workflow/store.ts @@ -193,7 +193,7 @@ export const useWorkflowStore = create()( nodes: path, iterations: 5, loopType: 'for', // Default to 'for' loop - forEachItems: '' + forEachItems: '', } processedPaths.add(canonicalPath) } @@ -233,7 +233,7 @@ export const useWorkflowStore = create()( nodes: path, iterations: 5, loopType: 'for', // Default to 'for' loop - forEachItems: '' + forEachItems: '', } processedPaths.add(canonicalPath) } @@ -563,27 +563,28 @@ export const useWorkflowStore = create()( }, updateLoopForEachItems: (loopId: string, items: string) => { - let parsedItems: any = items; - + let parsedItems: any = items + // Try to parse the string as JSON if it looks like JSON - if (typeof items === 'string' && - ((items.trim().startsWith('[') && items.trim().endsWith(']')) || - (items.trim().startsWith('{') && items.trim().endsWith('}'))) + if ( + typeof items === 'string' && + ((items.trim().startsWith('[') && items.trim().endsWith(']')) || + (items.trim().startsWith('{') && items.trim().endsWith('}'))) ) { try { // First try to parse to validate it's valid JSON - const parsed = JSON.parse(items); - + const parsed = JSON.parse(items) + // If parsing succeeds, store the original string to preserve formatting // This way we keep the user's exact formatting (spacing, line breaks, etc.) - parsedItems = items; + parsedItems = items } catch (e) { // If parsing fails, keep it as a string expression - console.error('Invalid JSON format for forEach items:', e); - parsedItems = items; + console.error('Invalid JSON format for forEach items:', e) + parsedItems = items } } - + const newState = { blocks: { ...get().blocks }, edges: [...get().edges], diff --git a/sim/tools/openai/dalle.ts b/sim/tools/openai/dalle.ts index ae884fb380..7e81c4b58c 100644 --- a/sim/tools/openai/dalle.ts +++ b/sim/tools/openai/dalle.ts @@ -13,7 +13,7 @@ export interface DalleResponse extends ToolResponse { export const dalleTool: ToolConfig = { id: 'dalle_generate', name: 'DALL-E Generate', - description: 'Generate images using OpenAI\'s DALL-E model', + description: "Generate images using OpenAI's DALL-E model", version: '1.0.0', params: { prompt: { @@ -71,9 +71,9 @@ export const dalleTool: ToolConfig = { transformResponse: async (response, params) => { try { const data = await response.json() - + console.log('DALL-E API response:', JSON.stringify(data, null, 2)) - + if (!data.data?.[0]?.url) { console.error('No image URL in DALL-E response:', data) throw new Error('No image URL in response') @@ -81,37 +81,40 @@ export const dalleTool: ToolConfig = { const imageUrl = data.data[0].url const modelName = data.model || params?.model || 'dall-e' - + console.log('Got image URL:', imageUrl) console.log('Using model:', modelName) - + try { // Fetch the image using the proxy-image endpoint instead of direct fetch console.log('Fetching image from URL via proxy...') const proxyUrl = `/api/proxy-image?url=${encodeURIComponent(imageUrl)}` - + const imageResponse = await fetch(proxyUrl, { headers: { - 'Accept': 'image/*, */*', + Accept: 'image/*, */*', }, - cache: 'no-store', // Don't use cache + cache: 'no-store', // Don't use cache }) - + if (!imageResponse.ok) { console.error('Failed to fetch image:', imageResponse.status, imageResponse.statusText) throw new Error(`Failed to fetch image: ${imageResponse.statusText}`) } - console.log('Image fetch successful, content-type:', imageResponse.headers.get('content-type')) - + console.log( + 'Image fetch successful, content-type:', + imageResponse.headers.get('content-type') + ) + const imageBlob = await imageResponse.blob() console.log('Image blob size:', imageBlob.size) - + if (imageBlob.size === 0) { console.error('Empty image blob received') throw new Error('Empty image received') } - + const reader = new FileReader() const base64Promise = new Promise((resolve, reject) => { reader.onloadend = () => { @@ -121,7 +124,7 @@ export const dalleTool: ToolConfig = { reject(new Error('No data read from image')) return } - + const base64Content = base64data.split(',')[1] // Remove the data URL prefix console.log('Successfully converted image to base64, length:', base64Content.length) resolve(base64Content) @@ -136,7 +139,7 @@ export const dalleTool: ToolConfig = { } reader.readAsDataURL(imageBlob) }) - + const base64Image = await base64Promise console.log('Returning success response with image data') @@ -153,27 +156,27 @@ export const dalleTool: ToolConfig = { } catch (error) { // Log the error but continue with returning the URL console.error('Error fetching or processing image:', error) - + // Try again with a direct browser fetch as fallback try { console.log('Attempting fallback with direct browser fetch...') const directImageResponse = await fetch(imageUrl, { cache: 'no-store', headers: { - 'Accept': 'image/*, */*', + Accept: 'image/*, */*', 'User-Agent': 'Mozilla/5.0 (compatible; DalleProxy/1.0)', }, }) - + if (!directImageResponse.ok) { throw new Error(`Direct fetch failed: ${directImageResponse.status}`) } - + const imageBlob = await directImageResponse.blob() if (imageBlob.size === 0) { throw new Error('Empty blob received from direct fetch') } - + const reader = new FileReader() const base64Promise = new Promise((resolve, reject) => { reader.onloadend = () => { @@ -183,9 +186,12 @@ export const dalleTool: ToolConfig = { reject(new Error('No data read from image')) return } - + const base64Content = base64data.split(',')[1] - console.log('Successfully converted image to base64 via direct fetch, length:', base64Content.length) + console.log( + 'Successfully converted image to base64 via direct fetch, length:', + base64Content.length + ) resolve(base64Content) } catch (err) { reject(err) @@ -194,9 +200,9 @@ export const dalleTool: ToolConfig = { reader.onerror = reject reader.readAsDataURL(imageBlob) }) - + const base64Image = await base64Promise - + return { success: true, output: { @@ -209,7 +215,7 @@ export const dalleTool: ToolConfig = { } } catch (fallbackError) { console.error('Fallback fetch also failed:', fallbackError) - + // Even if both attempts fail, still return the URL and metadata return { success: true, @@ -225,7 +231,7 @@ export const dalleTool: ToolConfig = { } } catch (error) { console.error('Error in DALL-E response handling:', error) - throw error; + throw error } }, transformError: (error) => { @@ -235,4 +241,4 @@ export const dalleTool: ToolConfig = { } return error.message || 'Failed to generate image with DALL-E' }, -} \ No newline at end of file +}