@@ -4,7 +4,11 @@ import { createLogger } from '@sim/logger'
44import type { NextRequest } from 'next/server'
55import { NextResponse } from 'next/server'
66import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
7- import { generatePptxFromCode } from '@/lib/execution/pptx-vm'
7+ import {
8+ generateDocxFromCode ,
9+ generatePdfFromCode ,
10+ generatePptxFromCode ,
11+ } from '@/lib/execution/doc-vm'
812import { CopilotFiles , isUsingCloudStorage } from '@/lib/uploads'
913import type { StorageContext } from '@/lib/uploads/config'
1014import { parseWorkspaceFileKey } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
@@ -22,47 +26,73 @@ import {
2226const logger = createLogger ( 'FilesServeAPI' )
2327
2428const ZIP_MAGIC = Buffer . from ( [ 0x50 , 0x4b , 0x03 , 0x04 ] )
29+ const PDF_MAGIC = Buffer . from ( [ 0x25 , 0x50 , 0x44 , 0x46 , 0x2d ] ) // %PDF-
30+
31+ interface CompilableFormat {
32+ magic : Buffer
33+ compile : ( code : string , workspaceId : string ) => Promise < Buffer >
34+ contentType : string
35+ }
36+
37+ const COMPILABLE_FORMATS : Record < string , CompilableFormat > = {
38+ '.pptx' : {
39+ magic : ZIP_MAGIC ,
40+ compile : generatePptxFromCode ,
41+ contentType : 'application/vnd.openxmlformats-officedocument.presentationml.presentation' ,
42+ } ,
43+ '.docx' : {
44+ magic : ZIP_MAGIC ,
45+ compile : generateDocxFromCode ,
46+ contentType : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ,
47+ } ,
48+ '.pdf' : {
49+ magic : PDF_MAGIC ,
50+ compile : generatePdfFromCode ,
51+ contentType : 'application/pdf' ,
52+ } ,
53+ }
2554
26- const MAX_COMPILED_PPTX_CACHE = 10
27- const compiledPptxCache = new Map < string , Buffer > ( )
55+ const MAX_COMPILED_DOC_CACHE = 10
56+ const compiledDocCache = new Map < string , Buffer > ( )
2857
2958function compiledCacheSet ( key : string , buffer : Buffer ) : void {
30- if ( compiledPptxCache . size >= MAX_COMPILED_PPTX_CACHE ) {
31- compiledPptxCache . delete ( compiledPptxCache . keys ( ) . next ( ) . value as string )
59+ if ( compiledDocCache . size >= MAX_COMPILED_DOC_CACHE ) {
60+ compiledDocCache . delete ( compiledDocCache . keys ( ) . next ( ) . value as string )
3261 }
33- compiledPptxCache . set ( key , buffer )
62+ compiledDocCache . set ( key , buffer )
3463}
3564
36- async function compilePptxIfNeeded (
65+ async function compileDocumentIfNeeded (
3766 buffer : Buffer ,
3867 filename : string ,
3968 workspaceId ?: string ,
4069 raw ?: boolean
4170) : Promise < { buffer : Buffer ; contentType : string } > {
42- const isPptx = filename . toLowerCase ( ) . endsWith ( '.pptx' )
43- if ( raw || ! isPptx || buffer . subarray ( 0 , 4 ) . equals ( ZIP_MAGIC ) ) {
71+ if ( raw ) return { buffer, contentType : getContentType ( filename ) }
72+
73+ const ext = filename . slice ( filename . lastIndexOf ( '.' ) ) . toLowerCase ( )
74+ const format = COMPILABLE_FORMATS [ ext ]
75+ if ( ! format ) return { buffer, contentType : getContentType ( filename ) }
76+
77+ const magicLen = format . magic . length
78+ if ( buffer . length >= magicLen && buffer . subarray ( 0 , magicLen ) . equals ( format . magic ) ) {
4479 return { buffer, contentType : getContentType ( filename ) }
4580 }
4681
4782 const code = buffer . toString ( 'utf-8' )
4883 const cacheKey = createHash ( 'sha256' )
84+ . update ( ext )
4985 . update ( code )
5086 . update ( workspaceId ?? '' )
5187 . digest ( 'hex' )
52- const cached = compiledPptxCache . get ( cacheKey )
88+ const cached = compiledDocCache . get ( cacheKey )
5389 if ( cached ) {
54- return {
55- buffer : cached ,
56- contentType : 'application/vnd.openxmlformats-officedocument.presentationml.presentation' ,
57- }
90+ return { buffer : cached , contentType : format . contentType }
5891 }
5992
60- const compiled = await generatePptxFromCode ( code , workspaceId || '' )
93+ const compiled = await format . compile ( code , workspaceId || '' )
6194 compiledCacheSet ( cacheKey , compiled )
62- return {
63- buffer : compiled ,
64- contentType : 'application/vnd.openxmlformats-officedocument.presentationml.presentation' ,
65- }
95+ return { buffer : compiled , contentType : format . contentType }
6696}
6797
6898const STORAGE_KEY_PREFIX_RE = / ^ \d { 13 } - [ a - z 0 - 9 ] { 7 } - /
@@ -169,7 +199,7 @@ async function handleLocalFile(
169199 const segment = filename . split ( '/' ) . pop ( ) || filename
170200 const displayName = stripStorageKeyPrefix ( segment )
171201 const workspaceId = getWorkspaceIdForCompile ( filename )
172- const { buffer : fileBuffer , contentType } = await compilePptxIfNeeded (
202+ const { buffer : fileBuffer , contentType } = await compileDocumentIfNeeded (
173203 rawBuffer ,
174204 displayName ,
175205 workspaceId ,
@@ -226,7 +256,7 @@ async function handleCloudProxy(
226256 const segment = cloudKey . split ( '/' ) . pop ( ) || 'download'
227257 const displayName = stripStorageKeyPrefix ( segment )
228258 const workspaceId = getWorkspaceIdForCompile ( cloudKey )
229- const { buffer : fileBuffer , contentType } = await compilePptxIfNeeded (
259+ const { buffer : fileBuffer , contentType } = await compileDocumentIfNeeded (
230260 rawBuffer ,
231261 displayName ,
232262 workspaceId ,
0 commit comments