Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
508edf6
improvement(sandbox): upgrade pptx/docx/pdf bootstrap with image help…
waleedlatif1 May 8, 2026
9e38939
fix(sandbox): strict MIME allowlist and nullish coalescing in docx ad…
waleedlatif1 May 8, 2026
f3f1a45
fix(sandbox): validate required opts in pdf drawImage to prevent sile…
waleedlatif1 May 8, 2026
86c6ba8
fix(sandbox): throw on malformed data URI in docx addImage
waleedlatif1 May 8, 2026
918954a
fix(sandbox): prevent opts from clobbering computed ImageRun data/typ…
waleedlatif1 May 8, 2026
449ed27
fix(sandbox): prevent opts from clobbering fetched data in pptx addImage
waleedlatif1 May 8, 2026
20c2341
fix(sandbox): validate required opts in pptx addImage
waleedlatif1 May 8, 2026
925d2ba
fix(sandbox): remove silent image/png fallback in docx addImage MIME …
waleedlatif1 May 8, 2026
b7db38a
fix(sandbox): consistency and cleanup pass on doc-gen tasks and worker
waleedlatif1 May 8, 2026
9e9b5fc
fix(sandbox): move OOM check before isDisposed guard so MemoryLimitEr…
waleedlatif1 May 8, 2026
9d71d99
fix(files): add preview accuracy disclaimer to pptx viewer
waleedlatif1 May 8, 2026
f230ca8
revert(files): remove approximate preview disclaimer from pptx viewer
waleedlatif1 May 8, 2026
e149fab
improvement(style): expand style extraction — DOCX inheritance/defaul…
waleedlatif1 May 8, 2026
ced273f
improvement(style): remove noise from style extraction output
waleedlatif1 May 8, 2026
3310321
improvement(style-api): fix auth order, add size guard, extract pptx …
waleedlatif1 May 8, 2026
8d0ae09
fix(sandbox): align docx/pptx/pdf task guards, fix bold detection and…
waleedlatif1 May 8, 2026
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
37 changes: 21 additions & 16 deletions apps/sim/app/api/workspaces/[id]/files/[fileId]/style/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,23 @@ const logger = createLogger('WorkspaceFileStyleAPI')

/**
* GET /api/workspaces/[id]/files/[fileId]/style
* Extract a compact JSON style summary from an uploaded .docx or .pptx file.
* Uses OOXML theme XML to return theme colors, font pair, and named styles.
* Only works on binary OOXML files (ZIP format) — not on JS source files.
* Extract a compact JSON style summary from an uploaded .docx, .pptx, or .pdf file.
* OOXML files return theme colors, font pair, and named styles.
* PDF files return page dimensions and embedded font names.
*/
const MAX_STYLE_FILE_BYTES = 100 * 1024 * 1024 // 100 MB

export const GET = withRouteHandler(
async (request: NextRequest, context: { params: Promise<{ id: string; fileId: string }> }) => {
const parsed = await parseRequest(workspaceFileStyleContract, request, context)
if (!parsed.success) return parsed.response
const { id: workspaceId, fileId } = parsed.data.params

const session = await getSession()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

const parsed = await parseRequest(workspaceFileStyleContract, request, context)
if (!parsed.success) return parsed.response
const { id: workspaceId, fileId } = parsed.data.params

const membership = await verifyWorkspaceMembership(session.user.id, workspaceId)
if (!membership) {
return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 })
Expand All @@ -42,13 +44,20 @@ export const GET = withRouteHandler(
}

const rawExt = fileRecord.name.split('.').pop()?.toLowerCase()
if (rawExt !== 'docx' && rawExt !== 'pptx') {
if (rawExt !== 'docx' && rawExt !== 'pptx' && rawExt !== 'pdf') {
return NextResponse.json(
{ error: 'Style extraction only supports .docx and .pptx files' },
{ error: 'Style extraction supports .docx, .pptx, and .pdf files' },
{ status: 422 }
)
}
const ext: 'docx' | 'pptx' | 'pdf' = rawExt

if (fileRecord.size > MAX_STYLE_FILE_BYTES) {
return NextResponse.json(
{ error: 'File is too large for style extraction (limit: 100 MB)' },
{ status: 422 }
)
}
const ext: 'docx' | 'pptx' = rawExt

let buffer: Buffer
try {
Expand All @@ -66,17 +75,13 @@ export const GET = withRouteHandler(
return NextResponse.json(
{
error:
'File is not a compiled binary document — style extraction requires an uploaded or compiled .docx/.pptx file',
'Could not extract style — file may be encrypted, corrupt, image-only, or contain no parseable style information',
},
{ status: 422 }
)
}

logger.info('Extracted style summary via API', {
fileId,
format: ext,
themeName: summary.theme.name,
})
logger.info('Extracted style summary via API', { fileId, format: ext })

return NextResponse.json(summary, {
headers: { 'Cache-Control': 'private, max-age=300' },
Expand Down
21 changes: 18 additions & 3 deletions apps/sim/lib/api/contracts/workspace-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,30 @@ export const updateWorkspaceFileContentContract = defineRouteContract({

const documentStyleSummarySchema = z
.object({
format: z.enum(['docx', 'pptx']),
format: z.enum(['docx', 'pptx', 'pdf']),
// OOXML theme — present for pptx, present for docx when theme1.xml exists, absent for pdf
theme: z
.object({
name: z.string(),
colors: z.record(z.string(), z.string()),
fonts: z.object({ major: z.string(), minor: z.string() }),
})
.passthrough(),
.optional(),
// docx only
styles: z.array(z.object({}).passthrough()).optional(),
defaults: z.object({ fontSize: z.number().optional(), font: z.string().optional() }).optional(),
// pdf only
pageSize: z
.object({
preset: z.enum(['A4', 'letter', 'custom']),
widthPt: z.number().optional(),
heightPt: z.number().optional(),
})
.optional(),
fonts: z.array(z.string()).optional(),
// pptx only
slideCount: z.number().optional(),
aspectRatio: z.enum(['16:9', '4:3', 'custom']).optional(),
background: z.string().optional(),
})
.passthrough()

Expand Down
Loading