chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import { executeInE2B } from '@/lib/execution/e2b'
|
||||
import { CodeLanguage } from '@/lib/execution/languages'
|
||||
|
||||
const RENDER_TIMEOUT_MS = 150_000
|
||||
// Bound the visual-QA cost: cap pages and rasterization DPI so the JPEGs the
|
||||
// file agent inspects stay small enough for vision input.
|
||||
const MAX_RENDER_PAGES = 20
|
||||
const RENDER_DPI = 110
|
||||
|
||||
/** Extensions LibreOffice can render to page images for the visual QA loop. */
|
||||
const RENDERABLE_EXTS = new Set(['pptx', 'docx', 'pdf'])
|
||||
|
||||
export function isRenderableDocExt(ext: string): boolean {
|
||||
return RENDERABLE_EXTS.has(ext.toLowerCase())
|
||||
}
|
||||
|
||||
export interface DocRender {
|
||||
/** A single contact-sheet grid JPEG of all pages, for the agent's visual QA. */
|
||||
grid: Buffer
|
||||
pageCount: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a compiled document binary to a single contact-sheet grid image inside
|
||||
* the E2B doc sandbox (LibreOffice → PDF → poppler `pdftoppm` → Pillow tile).
|
||||
* Works for any compiled binary regardless of which engine produced it
|
||||
* (isolated-vm JS or E2B Python), so the visual QA loop covers pptx/docx/pdf
|
||||
* uniformly. One grid image fits the VFS single-attachment read and gives the
|
||||
* agent the whole deck/doc at a glance (mirrors Anthropic's thumbnail grid).
|
||||
*
|
||||
* Throws on a sandbox/infra failure or when the doc renders to zero pages.
|
||||
*/
|
||||
export async function renderDocToGrid(args: {
|
||||
binary: Buffer
|
||||
ext: string
|
||||
workspaceId: string
|
||||
}): Promise<DocRender> {
|
||||
const ext = args.ext.toLowerCase()
|
||||
if (!isRenderableDocExt(ext)) {
|
||||
throw new Error(`Cannot render .${ext} to images (supported: pptx, docx, pdf)`)
|
||||
}
|
||||
|
||||
const script = `
|
||||
import subprocess, glob, base64, json
|
||||
from PIL import Image
|
||||
|
||||
ext = ${JSON.stringify(ext)}
|
||||
inp = f"/home/user/input.{ext}"
|
||||
pdf = inp if ext == "pdf" else "/home/user/input.pdf"
|
||||
|
||||
if ext != "pdf":
|
||||
subprocess.run(
|
||||
["soffice", "--headless", "--convert-to", "pdf", "--outdir", "/home/user", inp],
|
||||
check=True, timeout=120, capture_output=True,
|
||||
)
|
||||
|
||||
subprocess.run(
|
||||
["pdftoppm", "-jpeg", "-r", "${RENDER_DPI}", "-l", "${MAX_RENDER_PAGES}", pdf, "/home/user/page"],
|
||||
check=True, timeout=120, capture_output=True,
|
||||
)
|
||||
|
||||
paths = sorted(glob.glob("/home/user/page*.jpg"))[:${MAX_RENDER_PAGES}]
|
||||
imgs = [Image.open(p).convert("RGB") for p in paths]
|
||||
n = len(imgs)
|
||||
if n == 0:
|
||||
print("__SIM_RESULT__=" + json.dumps({"grid": None, "pageCount": 0}))
|
||||
else:
|
||||
cols = 1 if n == 1 else (2 if n <= 6 else 3)
|
||||
rows = (n + cols - 1) // cols
|
||||
cell_w = max(i.width for i in imgs)
|
||||
cell_h = max(i.height for i in imgs)
|
||||
pad = 12
|
||||
grid = Image.new("RGB", (cols * cell_w + (cols + 1) * pad, rows * cell_h + (rows + 1) * pad), (240, 240, 240))
|
||||
for idx, im in enumerate(imgs):
|
||||
r, c = divmod(idx, cols)
|
||||
grid.paste(im, (pad + c * (cell_w + pad), pad + r * (cell_h + pad)))
|
||||
# Cap the grid's longest edge so the JPEG stays a reasonable vision input.
|
||||
max_edge = 2200
|
||||
if max(grid.size) > max_edge:
|
||||
scale = max_edge / max(grid.size)
|
||||
grid = grid.resize((int(grid.width * scale), int(grid.height * scale)))
|
||||
grid.save("/home/user/grid.jpg", "JPEG", quality=80)
|
||||
with open("/home/user/grid.jpg", "rb") as f:
|
||||
print("__SIM_RESULT__=" + json.dumps({"grid": base64.b64encode(f.read()).decode(), "pageCount": n}))
|
||||
`.trim()
|
||||
|
||||
const result = await executeInE2B({
|
||||
code: script,
|
||||
language: CodeLanguage.Python,
|
||||
timeoutMs: RENDER_TIMEOUT_MS,
|
||||
sandboxKind: 'doc',
|
||||
sandboxFiles: [
|
||||
{
|
||||
path: `/home/user/input.${ext}`,
|
||||
content: args.binary.toString('base64'),
|
||||
encoding: 'base64',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
if (result.error) {
|
||||
throw new Error(`Document render failed: ${result.error}`)
|
||||
}
|
||||
const payload = result.result as { grid?: string | null; pageCount?: number } | null
|
||||
if (!payload?.grid) {
|
||||
throw new Error('Document render produced no pages')
|
||||
}
|
||||
return { grid: Buffer.from(payload.grid, 'base64'), pageCount: payload.pageCount ?? 0 }
|
||||
}
|
||||
Reference in New Issue
Block a user