Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
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
4 changes: 2 additions & 2 deletions apps/sim/lib/execution/isolated-vm-worker.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ async function executeCode(request, executionId) {
const externalCopies = []

try {
isolate = new ivm.Isolate({ memoryLimit: 128 })
isolate = new ivm.Isolate({ memoryLimit: 256 })
if (executionId !== undefined) activeIsolates.set(executionId, isolate)
context = await isolate.createContext()
const jail = context.global
Expand Down Expand Up @@ -511,7 +511,7 @@ async function executeTask(request, executionId) {
let tPhase = tStart

try {
isolate = new ivm.Isolate({ memoryLimit: 128 })
isolate = new ivm.Isolate({ memoryLimit: 256 })
if (executionId !== undefined) activeIsolates.set(executionId, isolate)
context = await isolate.createContext()
const jail = context.global
Expand Down
53 changes: 52 additions & 1 deletion apps/sim/sandbox-tasks/docx-generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,61 @@ export const docxGenerateTask = defineSandboxTask<SandboxTaskInput>({
globalThis.addSection = (section) => {
globalThis.__docxSections.push(section);
};
globalThis.getFileBase64 = async (fileId) => {

// Page geometry constants (twips, 1 twip = 1/1440 inch) for US Letter
globalThis.PAGE_W = 12240; // 8.5"
globalThis.PAGE_H = 15840; // 11"
globalThis.MARGIN = 1440; // 1" margins
globalThis.CONTENT_W = 9360; // PAGE_W - 2 * MARGIN

// 6 MB raw ≈ 8 MB base64; reject above this to avoid sandbox OOM.
const _MAX_IMG_B64 = 8 * 1024 * 1024;

/**
* getFileBase64(fileId) — load a workspace file as a full data URI string.
* Returns the complete "data:image/png;base64,..." string.
* Use addImage() rather than passing this directly to ImageRun.
*/
globalThis.getFileBase64 = async function getFileBase64(fileId) {
if (!fileId || typeof fileId !== 'string') {
throw new Error('getFileBase64: fileId must be a non-empty string');
}
const res = await globalThis.__brokers.workspaceFile({ fileId });
if (!res || !res.dataUri) {
throw new Error('getFileBase64: broker returned no data for file ' + fileId);
}
if (res.dataUri.length > _MAX_IMG_B64) {
throw new Error(
'getFileBase64: image exceeds the 6 MB embed limit (~8 MB base64). Use a smaller/compressed image.'
);
}
return res.dataUri;
};

/**
* addImage(fileId, opts) — fetch a workspace file and return a docx.ImageRun.
* Required opts: width, height (pixels or EMUs via transformation option).
* Example:
* new docx.Paragraph({ children: [await addImage('abc123', { width: 200, height: 100 })] })
*/
globalThis.addImage = async function addImage(fileId, opts) {
const dataUri = await globalThis.getFileBase64(fileId);
const comma = dataUri.indexOf(',');
if (comma === -1) throw new Error('addImage: invalid data URI (no comma separator)');
const header = dataUri.slice(0, comma);
const base64 = dataUri.slice(comma + 1);
const mime = header.split(';')[0].replace('data:', '') || 'image/png';
Comment thread
waleedlatif1 marked this conversation as resolved.
Outdated
const extMap = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/jpg': 'jpg', 'image/gif': 'gif', 'image/bmp': 'bmp', 'image/svg+xml': 'svg' };
const ext = extMap[mime];
if (!ext) throw new Error('addImage: unsupported image type "' + mime + '". Use PNG, JPEG, GIF, BMP, or SVG.');
Comment thread
waleedlatif1 marked this conversation as resolved.
if (!globalThis.Buffer) throw new Error('addImage: Buffer polyfill missing — ensure docx bundle is loaded');
const { width, height, type: _t, data: _d, transformation: userTransform, ...passThrough } = opts || {};
return new globalThis.docx.ImageRun(Object.assign(passThrough, {
data: globalThis.Buffer.from(base64, 'base64'),
type: ext,
transformation: Object.assign({ width: width ?? 200, height: height ?? 200 }, userTransform || {}),
}));
};
`,
// JSZip's browser build doesn't support nodebuffer output, so we go through
// base64 and decode back to bytes inside the isolate (avoids DataURL / Blob).
Expand Down
64 changes: 60 additions & 4 deletions apps/sim/sandbox-tasks/pdf-generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,76 @@ export const pdfGenerateTask = defineSandboxTask<SandboxTaskInput>({
if (!PDFLib) throw new Error('pdf-lib bundle not loaded');
globalThis.PDFLib = PDFLib;
globalThis.pdf = await PDFLib.PDFDocument.create();
globalThis.embedImage = async (dataUri) => {

// Convenience shortcuts — avoids verbose PDFLib.rgb() / PDFLib.StandardFonts.Helvetica
globalThis.rgb = PDFLib.rgb;
globalThis.StandardFonts = PDFLib.StandardFonts;

// Page-size constants in points (1pt = 1/72 inch)
globalThis.LETTER = [612, 792]; // 8.5" × 11"
globalThis.A4 = [595.28, 841.89]; // 210mm × 297mm

// 6 MB raw ≈ 8 MB base64; reject above this to avoid sandbox OOM.
const _MAX_IMG_B64 = 8 * 1024 * 1024;

/**
* embedImage(dataUri) — embed a data-URI image into the active PDF document.
* Dispatches to embedPng or embedJpg based on MIME type.
*/
globalThis.embedImage = async function embedImage(dataUri) {
if (!dataUri || typeof dataUri !== 'string') {
throw new Error('embedImage: dataUri must be a non-empty string');
}
if (dataUri.length > _MAX_IMG_B64) {
throw new Error(
'embedImage: image exceeds the 6 MB embed limit (~8 MB base64). Use a smaller/compressed image.'
);
}
const comma = dataUri.indexOf(',');
if (comma === -1) throw new Error('embedImage: invalid data URI (no comma separator)');
const header = dataUri.slice(0, comma);
const base64 = dataUri.slice(comma + 1);
const binary = globalThis.Buffer ? globalThis.Buffer.from(base64, 'base64') : null;
if (!binary) throw new Error('Buffer polyfill missing');
const mime = header.split(';')[0].split(':')[1] || '';
if (mime.includes('png')) return globalThis.pdf.embedPng(binary);
return globalThis.pdf.embedJpg(binary);
// image/jpg is non-standard but tolerated; the canonical MIME is image/jpeg
if (mime === 'image/png') return globalThis.pdf.embedPng(binary);
if (mime === 'image/jpeg' || mime === 'image/jpg') return globalThis.pdf.embedJpg(binary);
throw new Error('embedImage: only PNG and JPEG are supported (got ' + (mime || 'unknown — check data URI header') + ')');
};
globalThis.getFileBase64 = async (fileId) => {

/**
* getFileBase64(fileId) — load a workspace file as a data URI string.
*/
globalThis.getFileBase64 = async function getFileBase64(fileId) {
if (!fileId || typeof fileId !== 'string') {
throw new Error('getFileBase64: fileId must be a non-empty string');
}
const res = await globalThis.__brokers.workspaceFile({ fileId });
if (!res || !res.dataUri) {
throw new Error('getFileBase64: broker returned no data for file ' + fileId);
}
if (res.dataUri.length > _MAX_IMG_B64) {
throw new Error(
'getFileBase64: image exceeds the 6 MB embed limit (~8 MB base64). Use a smaller/compressed image.'
);
}
return res.dataUri;
};

/**
* drawImage(page, fileId, opts) — fetch a workspace file and draw it on the given page.
* Required opts: x, y, width, height (points).
* Example: await drawImage(page, 'abc123', { x: 50, y: 700, width: 200, height: 100 });
*/
globalThis.drawImage = async function drawImage(page, fileId, opts) {
if (!opts || opts.x == null || opts.y == null || opts.width == null || opts.height == null) {
throw new Error('drawImage: opts must include x, y, width, and height (in points)');
}
const dataUri = await globalThis.getFileBase64(fileId);
const img = await globalThis.embedImage(dataUri);
page.drawImage(img, opts);
};
Comment thread
waleedlatif1 marked this conversation as resolved.
`,
finalize: `
const pdf = globalThis.pdf;
Expand Down
47 changes: 45 additions & 2 deletions apps/sim/sandbox-tasks/pptx-generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,52 @@ export const pptxGenerateTask = defineSandboxTask<SandboxTaskInput>({
const PptxGenJS = globalThis.__bundles['pptxgenjs'];
if (!PptxGenJS) throw new Error('pptxgenjs bundle not loaded');
globalThis.pptx = new PptxGenJS();
globalThis.getFileBase64 = async (fileId) => {
globalThis.pptx.layout = 'LAYOUT_16x9';

// Slide geometry for LAYOUT_16x9 (inches)
globalThis.SLIDE_W = 10;
globalThis.SLIDE_H = 5.625;
globalThis.MARGIN = 0.5;
globalThis.CONTENT_W = 9; // SLIDE_W - 2 * MARGIN
globalThis.CONTENT_H = 3.8; // usable body height below a standard title row

// ── Image helpers ──────────────────────────────────────────────────────────
// 6 MB raw ≈ 8 MB base64; reject above this to avoid sandbox OOM.
const _MAX_IMG_B64 = 8 * 1024 * 1024;

/**
* getFileBase64(fileId) — load a workspace file as a data URI string.
* PptxGenJS data format: "image/png;base64,<data>" (no "data:" prefix).
* Use as: slide.addImage({ data: await getFileBase64(fileId), x, y, w, h })
*/
globalThis.getFileBase64 = async function getFileBase64(fileId) {
if (!fileId || typeof fileId !== 'string') {
throw new Error('getFileBase64: fileId must be a non-empty string');
}
const res = await globalThis.__brokers.workspaceFile({ fileId });
return res.dataUri;
if (!res || !res.dataUri) {
throw new Error('getFileBase64: broker returned no data for file ' + fileId);
}
if (res.dataUri.length > _MAX_IMG_B64) {
throw new Error(
'getFileBase64: image exceeds the 6 MB embed limit (~8 MB base64). Use a smaller/compressed image.'
);
}
// PptxGenJS expects "image/png;base64,..." — strip the leading "data:" if present
return res.dataUri.replace(/^data:/, '');
};

/**
* addImage(slide, fileId, opts) — fetch a workspace file and embed it.
* Required opts: x, y, w, h (inches).
* Example: await addImage(slide, 'abc123', { x: 0.5, y: 1, w: 2, h: 1 });
*/
globalThis.addImage = async function addImage(slide, fileId, opts) {
if (!opts || opts.x == null || opts.y == null || opts.w == null || opts.h == null) {
throw new Error('addImage: opts must include x, y, w, and h (in inches)');
}
const data = await globalThis.getFileBase64(fileId);
slide.addImage(Object.assign({}, opts, { data }));
};
Comment thread
waleedlatif1 marked this conversation as resolved.
`,
finalize: `
Expand Down
Loading