Files
WeHub Mirror 6bf8bebf51
CI / Test and Build (push) Failing after 1s
CI / Migrate Dev DB (push) Has been skipped
CI / Migrate DB (push) Has been skipped
CodeQL / Analyze actions (push) Has been cancelled
CodeQL / Analyze javascript-typescript (push) Has been cancelled
CI / Detect Version (push) Has been cancelled
CI / Detect Desktop Changes (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/cron.Dockerfile, ubuntu-latest, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build AMD64 (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/cron.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/db.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/pii.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/realtime.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-8vcpu-ubuntu-2404-arm, ./docker/app.Dockerfile, linux-arm64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
Helm Chart / Lint, test, and validate chart (push) Has been cancelled
Helm Chart / Chart version bumped (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Build Dev ECR (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core) (push) Has been cancelled
CI / Promote Images (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/cron) (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 / Build Dev ECR (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Check Desktop Signing Secrets (push) Has been cancelled
CI / Desktop Release (push) Has been cancelled
CI / Create Desktop Prerelease (push) Has been cancelled
CI / Desktop Prerelease Build (push) Has been cancelled
CI / Publish Desktop Prerelease (push) Has been cancelled
CI / Prune Desktop Prereleases (push) Has been cancelled
Helm Chart / Install on kind and run helm test (push) Has been cancelled
WeHub snapshot of cb28d14c6f2c081de7a0d8729a8c816c9adef67a
2026-08-10 11:17:50 +08:00

110 lines
3.9 KiB
TypeScript

import { CodeLanguage } from '@/lib/execution/languages'
import { executeInSandbox } from '@/lib/execution/remote-sandbox'
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 executeInSandbox({
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 }
}