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,