Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix
  • Loading branch information
Sg312 committed Mar 24, 2026
commit 255d69540354b74eb7270147fbf61321ccb642cb
7 changes: 6 additions & 1 deletion apps/sim/app/api/workspaces/[id]/pptx/preview/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 })
}

const body = await req.json()
let body: unknown
try {
body = await req.json()
} catch {
return NextResponse.json({ error: 'Invalid or missing JSON body' }, { status: 400 })
}
const { code } = body as { code?: string }

if (typeof code !== 'string' || code.trim().length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -546,50 +546,80 @@ function PptxPreview({
const [rendering, setRendering] = useState(false)
const [renderError, setRenderError] = useState<string | null>(null)

// Streaming preview: only re-triggers when the streaming source code or
// workspace changes. Isolated from fileData/dataUpdatedAt so that file-list
// refreshes don't abort the in-flight compilation request.
useEffect(() => {
if (streamingContent === undefined) return

let cancelled = false
const controller = new AbortController()
let debounceTimer: ReturnType<typeof setTimeout> | null = null

async function render() {
const debounceTimer = setTimeout(async () => {
if (cancelled) return
try {
setRendering(true)
setRenderError(null)

if (streamingContent !== undefined) {
const response = await fetch(`/api/workspaces/${workspaceId}/pptx/preview`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: streamingContent }),
signal: controller.signal,
})
if (!response.ok) {
const err = await response.json().catch(() => ({ error: 'Preview failed' }))
throw new Error(err.error || 'Preview failed')
}
if (cancelled) return
const arrayBuffer = await response.arrayBuffer()
if (cancelled) return
const data = new Uint8Array(arrayBuffer)
const images: string[] = []
await renderPptxSlides(
data,
(src) => {
images.push(src)
if (!cancelled) setSlides([...images])
},
() => cancelled
)
return
const response = await fetch(`/api/workspaces/${workspaceId}/pptx/preview`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: streamingContent }),
signal: controller.signal,
})
if (!response.ok) {
const err = await response.json().catch(() => ({ error: 'Preview failed' }))
throw new Error(err.error || 'Preview failed')
}
if (cancelled) return
const arrayBuffer = await response.arrayBuffer()
if (cancelled) return
const data = new Uint8Array(arrayBuffer)
const images: string[] = []
await renderPptxSlides(
data,
(src) => {
images.push(src)
if (!cancelled) setSlides([...images])
},
() => cancelled
)
} catch (err) {
if (!cancelled && !(err instanceof DOMException && err.name === 'AbortError')) {
const msg = err instanceof Error ? err.message : 'Failed to render presentation'
logger.error('PPTX render failed', { error: msg })
setRenderError(msg)
}
} finally {
if (!cancelled) setRendering(false)
}
}, 500)

return () => {
cancelled = true
clearTimeout(debounceTimer)
controller.abort()
}
}, [streamingContent, workspaceId])

// Non-streaming render: uses the fetched binary directly on the client.
// Skipped while streaming is active so it doesn't interfere.
useEffect(() => {
if (streamingContent !== undefined) return

let cancelled = false

async function render() {
if (cancelled) return
try {
if (cached) {
setSlides(cached)
return
}

if (!fileData) return
setRendering(true)
setRenderError(null)
setSlides([])
const data = new Uint8Array(fileData)
const images: string[] = []
Expand All @@ -605,7 +635,7 @@ function PptxPreview({
pptxCacheSet(cacheKey, images)
}
} catch (err) {
if (!cancelled && !(err instanceof DOMException && err.name === 'AbortError')) {
if (!cancelled) {
const msg = err instanceof Error ? err.message : 'Failed to render presentation'
logger.error('PPTX render failed', { error: msg })
setRenderError(msg)
Expand All @@ -615,18 +645,10 @@ function PptxPreview({
}
}

// Debounce streaming renders so rapid SSE updates don't spawn a subprocess
// per event. Non-streaming renders (file load / cache) run immediately.
if (streamingContent !== undefined) {
debounceTimer = setTimeout(render, 500)
} else {
render()
}
render()

return () => {
cancelled = true
if (debounceTimer) clearTimeout(debounceTimer)
controller.abort()
}
}, [fileData, dataUpdatedAt, streamingContent, cacheKey, workspaceId])

Expand Down
Loading