From bac4949dfe4f356c424ea55e8701cfcee8b35b3e Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 5 May 2025 23:07:47 -0700 Subject: [PATCH] improvement(ux): format json logs response, fix tts and image proxies, fix airtable block --- sim/app/api/proxy/tts/route.ts | 3 +- .../components/json-view/json-view.tsx | 26 ++----- sim/app/w/logs/components/sidebar/sidebar.tsx | 35 +++++++-- sim/blocks/blocks/airtable.ts | 11 +-- sim/next.config.ts | 2 +- sim/tools/openai/dalle.ts | 73 +++++-------------- 6 files changed, 58 insertions(+), 92 deletions(-) diff --git a/sim/app/api/proxy/tts/route.ts b/sim/app/api/proxy/tts/route.ts index 6a19ec2a17..e70f648821 100644 --- a/sim/app/api/proxy/tts/route.ts +++ b/sim/app/api/proxy/tts/route.ts @@ -27,8 +27,7 @@ export async function POST(request: Request) { text, model_id: modelId, }), - // Set a reasonable timeout - signal: AbortSignal.timeout(20000), + signal: AbortSignal.timeout(60000), }) if (!response.ok) { diff --git a/sim/app/w/[id]/components/panel/components/console/components/json-view/json-view.tsx b/sim/app/w/[id]/components/panel/components/console/components/json-view/json-view.tsx index 56b45dd96c..4b23fbd48f 100644 --- a/sim/app/w/[id]/components/panel/components/console/components/json-view/json-view.tsx +++ b/sim/app/w/[id]/components/panel/components/console/components/json-view/json-view.tsx @@ -191,7 +191,7 @@ const ImagePreview = ({ // Determine the source for the image const imageSrc = isBase64 && imageData && imageData.length > 0 - ? `data:image/pngbase64,${imageData}` + ? `data:image/png;base64,${imageData}` : imageUrl || '' return ( @@ -363,13 +363,6 @@ export const JSONView = ({ data, level = 0, initiallyExpanded = false }: JSONVie {key}:{' '} {isImageKey ? (
- - {hasValidImage && typeof value === 'string' && value.length > 100 ? ( - - ) : ( - '""' - )} - {/* Show image preview within the image field */} {outputKey}:{' '} {isImageSubKey ? (
- - {hasValidImage && outputValue.length > 100 ? ( - - ) : ( - '""' - )} - {/* Show image preview within nested image field */} {key}:{' '} {isImageField ? ( - - - + ) : ( { + try { + // First check if the content looks like JSON (starts with { or [) + const trimmed = content.trim() + if (!(trimmed.startsWith('{') || trimmed.startsWith('[')) || + !(trimmed.endsWith('}') || trimmed.endsWith(']'))) { + return { isJson: false, formatted: content } + } + + // Try to parse the JSON + const parsed = JSON.parse(trimmed) + const prettified = JSON.stringify(parsed, null, 2) + return { isJson: true, formatted: prettified } + } catch (e) { + // If parsing fails, it's not valid JSON + return { isJson: false, formatted: content } + } +} + /** * Formats JSON content for display, handling multiple JSON objects separated by '--' */ @@ -33,14 +55,15 @@ const formatJsonContent = (content: string): React.ReactNode => { if (match) { const systemComment = match[1] const actualContent = content.substring(match[0].length).trim() + const { formatted } = tryPrettifyJson(actualContent) return (
{systemComment}
- +
-            {actualContent}
+            {formatted}
           
@@ -48,11 +71,13 @@ const formatJsonContent = (content: string): React.ReactNode => { } // If no system comment pattern found, show the whole content + const { formatted } = tryPrettifyJson(content) + return (
- +
-        {content}
+        {formatted}
       
) diff --git a/sim/blocks/blocks/airtable.ts b/sim/blocks/blocks/airtable.ts index 98c9a13971..b9812e8b33 100644 --- a/sim/blocks/blocks/airtable.ts +++ b/sim/blocks/blocks/airtable.ts @@ -8,7 +8,6 @@ import { } from '@/tools/airtable/types' import { BlockConfig } from '../types' -// Union type for all possible Airtable responses type AirtableResponse = | AirtableListResponse | AirtableGetResponse @@ -28,7 +27,6 @@ export const AirtableBlock: BlockConfig = { bgColor: '#E0E0E0', icon: AirtableIcon, subBlocks: [ - // Operation selector { id: 'operation', title: 'Operation', @@ -41,7 +39,6 @@ export const AirtableBlock: BlockConfig = { { label: 'Update Record', id: 'update' }, ], }, - // Airtable Credentials { id: 'credential', title: 'Airtable Account', @@ -52,7 +49,6 @@ export const AirtableBlock: BlockConfig = { requiredScopes: ['data.records:read', 'data.records:write'], // Keep both scopes placeholder: 'Select Airtable account', }, - // Base ID { id: 'baseId', title: 'Base ID', @@ -60,7 +56,6 @@ export const AirtableBlock: BlockConfig = { layout: 'full', placeholder: 'Enter your base ID (e.g., appXXXXXXXXXXXXXX)', }, - // Table ID { id: 'tableId', title: 'Table ID', @@ -68,7 +63,6 @@ export const AirtableBlock: BlockConfig = { layout: 'full', placeholder: 'Enter table ID (e.g., tblXXXXXXXXXXXXXX)', }, - // Record ID (For Get/Update Single) { id: 'recordId', title: 'Record ID', @@ -77,7 +71,6 @@ export const AirtableBlock: BlockConfig = { placeholder: 'ID of the record (e.g., recXXXXXXXXXXXXXX)', condition: { field: 'operation', value: ['get', 'update'] }, }, - // List Operation Fields { id: 'maxRecords', title: 'Max Records', @@ -94,7 +87,6 @@ export const AirtableBlock: BlockConfig = { placeholder: 'Airtable formula to filter records (optional)', condition: { field: 'operation', value: 'list' }, }, - // Create / Update Multiple Operation Field: Records (Array) { id: 'records', title: 'Records (JSON Array)', @@ -103,7 +95,6 @@ export const AirtableBlock: BlockConfig = { placeholder: 'For Create: `[{ "fields": { ... } }]`\n', condition: { field: 'operation', value: ['create', 'updateMultiple'] }, }, - // Update Single Operation Field: Fields (Object) { id: 'fields', title: 'Fields (JSON Object)', @@ -181,7 +172,7 @@ export const AirtableBlock: BlockConfig = { baseId: { type: 'string', required: true }, tableId: { type: 'string', required: true }, // Conditional inputs - recordId: { type: 'string', required: false }, // Required for get/update + recordId: { type: 'string', required: true }, // Required for get/update maxRecords: { type: 'number', required: false }, // Optional for list filterFormula: { type: 'string', required: false }, // Optional for list records: { type: 'json', required: false }, // Required for create/updateMultiple diff --git a/sim/next.config.ts b/sim/next.config.ts index 84c2cf7818..3e5b29bb9d 100644 --- a/sim/next.config.ts +++ b/sim/next.config.ts @@ -106,7 +106,7 @@ const nextConfig: NextConfig = { }, { key: 'Content-Security-Policy', - value: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.google.com https://apis.google.com https://*.vercel-insights.com https://vercel.live https://*.vercel.live; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://*.googleusercontent.com https://*.google.com https://*.atlassian.com; font-src 'self' https://fonts.gstatic.com; connect-src 'self' http://localhost:11434 http://host.docker.internal:11434 https://*.googleapis.com https://*.amazonaws.com https://*.s3.amazonaws.com https://*.vercel-insights.com https://*.atlassian.com https://vercel.live https://*.vercel.live; frame-src https://drive.google.com https://*.google.com; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; object-src 'none'", + value: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.google.com https://apis.google.com https://*.vercel-insights.com https://vercel.live https://*.vercel.live; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://*.googleusercontent.com https://*.google.com https://*.atlassian.com; media-src 'self' blob:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' http://localhost:11434 http://host.docker.internal:11434 https://*.googleapis.com https://*.amazonaws.com https://*.s3.amazonaws.com https://*.vercel-insights.com https://*.atlassian.com https://vercel.live https://*.vercel.live; frame-src https://drive.google.com https://*.google.com; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; object-src 'none'", }, ], }, diff --git a/sim/tools/openai/dalle.ts b/sim/tools/openai/dalle.ts index 554cdc0798..a7a3f7a5dc 100644 --- a/sim/tools/openai/dalle.ts +++ b/sim/tools/openai/dalle.ts @@ -81,9 +81,12 @@ export const dalleTool: ToolConfig = { try { // Fetch the image using the proxy/image endpoint instead of direct fetch logger.info('Fetching image from URL via proxy...') - const proxyUrl = `/api/proxy/image?url=${encodeURIComponent(imageUrl)}` + // Get the base URL from environment or use a fallback + const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000' + const proxyUrl = new URL(`/api/proxy/image`, baseUrl) + proxyUrl.searchParams.append('url', imageUrl) - const imageResponse = await fetch(proxyUrl, { + const imageResponse = await fetch(proxyUrl.toString(), { headers: { Accept: 'image/*, */*', }, @@ -102,32 +105,10 @@ export const dalleTool: ToolConfig = { throw new Error('Empty image received') } - const reader = new FileReader() - const base64Promise = new Promise((resolve, reject) => { - reader.onloadend = () => { - try { - const base64data = reader.result as string - if (!base64data) { - reject(new Error('No data read from image')) - return - } - - const base64Content = base64data.split(',')[1] // Remove the data URL prefix - resolve(base64Content) - } catch (err) { - logger.error('Error in FileReader onloadend:', err) - reject(err) - } - } - reader.onerror = (err) => { - logger.error('FileReader error:', err) - reject(new Error('Failed to read image data')) - } - reader.readAsDataURL(imageBlob) - }) - - const base64Image = await base64Promise - + const arrayBuffer = await imageBlob.arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + const base64Image = buffer.toString('base64') + return { success: true, output: { @@ -149,7 +130,7 @@ export const dalleTool: ToolConfig = { cache: 'no-store', headers: { Accept: 'image/*, */*', - 'User-Agent': 'Mozilla/5.0 (compatible; DalleProxy/1.0)', + 'User-Agent': 'Mozilla/5.0 (compatible DalleProxy/1.0)', }, }) @@ -162,31 +143,15 @@ export const dalleTool: ToolConfig = { throw new Error('Empty blob received from direct fetch') } - const reader = new FileReader() - const base64Promise = new Promise((resolve, reject) => { - reader.onloadend = () => { - try { - const base64data = reader.result as string - if (!base64data) { - reject(new Error('No data read from image')) - return - } - - const base64Content = base64data.split(',')[1] - logger.info( - 'Successfully converted image to base64 via direct fetch, length:', - base64Content.length - ) - resolve(base64Content) - } catch (err) { - reject(err) - } - } - reader.onerror = reject - reader.readAsDataURL(imageBlob) - }) - - const base64Image = await base64Promise + // Server-side safe way to convert blob to base64 + const arrayBuffer = await imageBlob.arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + const base64Image = buffer.toString('base64') + + logger.info( + 'Successfully converted image to base64 via direct fetch, length:', + base64Image.length + ) return { success: true,