Enhance Beryl Agent detail and report views
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { createHash } from "node:crypto";
|
||||
import { checkContent, normalize } from "./ppt-acceptance.mjs";
|
||||
const [action, dir, reference, runtime, skill] = process.argv.slice(2);
|
||||
process.env.RUNTIME_NODE_MODULES = path.join(runtime, "node/node_modules");
|
||||
process.env.RUNTIME_PYTHON = path.join(runtime, "python/bin/python3");
|
||||
const { Presentation, PresentationFile, FileBlob } = await import(pathToFileURL(path.join(runtime, "node/node_modules/@oai/artifact-tool/dist/artifact_tool.mjs")));
|
||||
const { finalizePresentation, resolvePresentationFont } = await import(pathToFileURL(path.join(skill, "container_tools/artifact_tool_utils.mjs")));
|
||||
const input = JSON.parse(await fs.readFile(path.join(dir, "input.json"), "utf8"));
|
||||
const task = await fs.readFile(path.join(dir, "task.json"), "utf8").then(JSON.parse).catch(e => { if (e.code === "ENOENT") return null; throw e; });
|
||||
const purpose = input.purpose ?? "debug";
|
||||
let originalTexts = [];
|
||||
const templated = action === "inspect" || input.template === "swads";
|
||||
const deck = templated ? await PresentationFile.importPptx(await FileBlob.load(reference)) : Presentation.create({ slideSize: { width: 1280, height: 720 } });
|
||||
if (action === "inspect") {
|
||||
const snapshot = await deck.inspect({ kind: "slide,textbox,table", maxChars: 120000 });
|
||||
await fs.writeFile(path.join(dir, "result.json"), JSON.stringify({ template: "swads", snapshot: snapshot.ndjson }));
|
||||
process.exit(0);
|
||||
}
|
||||
if (templated) {
|
||||
if (!input.replacements?.length && !input.tableCells?.length) throw new Error("PPT_REPLACEMENTS_REQUIRED");
|
||||
const snapshot = await deck.inspect({ kind: "textbox,table", maxChars: 1000000 });
|
||||
const records = snapshot.ndjson.split("\n").filter(Boolean).map(line => JSON.parse(line));
|
||||
originalTexts = records.filter(r => r.kind === "textbox").map(r => r.text ?? r.textPreview ?? "").filter(t => normalize(t).length > 12);
|
||||
for (const change of input.replacements ?? []) {
|
||||
const record = records.find(r => r.id === change.id);
|
||||
if (!record || !(record.text ?? record.textPreview ?? "").includes(change.oldText)) throw new Error(`PPT_TEMPLATE_ANCHOR_MISMATCH: ${change.id}. Reinspect this object; do not generate subset test files as final.`);
|
||||
deck.resolve(change.id).text.replace(change.oldText, change.newText);
|
||||
}
|
||||
for (const cell of input.tableCells ?? []) {
|
||||
const record = records.find(r => r.id === cell.id && r.kind === "table");
|
||||
if (!record) throw new Error("PPT_TABLE_ANCHOR_MISMATCH");
|
||||
deck.resolve(cell.id).cells.set(cell.row, cell.column, cell.text);
|
||||
}
|
||||
} else {
|
||||
if (!input.slides?.length) throw new Error("PPT_SLIDES_REQUIRED");
|
||||
const font = resolvePresentationFont();
|
||||
for (const data of input.slides) {
|
||||
const slide = deck.slides.add();
|
||||
slide.background.fill = "#FAFAF7";
|
||||
for (const [text, top, height, size, bold] of [[data.title, 60, 100, 44, true], [data.body, 190, 440, 28, false]]) {
|
||||
const shape = slide.shapes.add({ geometry: "textbox", position: { left: 80, top, width: 1120, height }, fill: "none", line: { fill: "none", width: 0 } });
|
||||
shape.text = text;
|
||||
shape.text.style = { typeface: font, fontSize: size, color: "#17372B", bold, autoFit: "none" };
|
||||
}
|
||||
slide.speakerNotes.textFrame.setText(data.sources ?? "来源由调用方提供;未提供外部来源。");
|
||||
}
|
||||
}
|
||||
await fs.mkdir(path.join(dir, "output"));
|
||||
const candidate = path.join(dir, "candidate.pptx");
|
||||
await (await PresentationFile.exportPptx(deck)).save(candidate);
|
||||
const finalPath = path.join(dir, "output/deck.pptx");
|
||||
await finalizePresentation({ workspaceDir: dir, candidatePath: candidate, finalPath,
|
||||
pythonExecutable: path.join(runtime, "python/bin/python3"),
|
||||
integrityValidatorPath: path.join(skill, "container_tools/inspect_presentation_package_integrity.py"),
|
||||
layoutValidatorPath: path.join(skill, "container_tools/inspect_presentation_layout_geometry.py"),
|
||||
layoutArgs: ["--validate-heading-fit"], verifyArtifactToolImport: true,
|
||||
receiptPath: path.join(dir, "validation.json"),
|
||||
});
|
||||
const finalDeck = await PresentationFile.importPptx(await FileBlob.load(finalPath));
|
||||
let count = 0;
|
||||
for (const slide of finalDeck.slides.items) {
|
||||
const png = await finalDeck.export({ slide, format: "png", scale: 1 });
|
||||
await fs.writeFile(path.join(dir, `slide-${++count}.png`), new Uint8Array(await png.arrayBuffer()));
|
||||
}
|
||||
if (!count) throw new Error("PPT_EMPTY");
|
||||
const bytes = await fs.readFile(finalPath);
|
||||
const finalSnapshot = await finalDeck.inspect({ kind: "textbox,table", maxChars: 1000000 });
|
||||
await fs.writeFile(path.join(dir, "content.ndjson"), finalSnapshot.ndjson);
|
||||
const finalText = normalize(finalSnapshot.ndjson.split("\n").filter(Boolean).map(line => {
|
||||
const r = JSON.parse(line); return r.text ?? r.textPreview ?? JSON.stringify(r);
|
||||
}).join("\n"));
|
||||
const failures = checkContent({ purpose, task, template: input.template, originalTexts, finalText });
|
||||
await fs.writeFile(path.join(dir, "acceptance.json"), JSON.stringify({ purpose, runId: task?.runId, taskRevision: task?.revision,
|
||||
passed: purpose === "final" && failures.length === 0, failures,
|
||||
limits: "Text coverage and explicit task terms only; embedded images and factual accuracy are not semantically verified." }, null, 2));
|
||||
if (failures.length) throw new Error(`PPT_CONTENT_REJECTED: ${failures.slice(0,8).join("; ")}`);
|
||||
await fs.writeFile(path.join(dir, "result.json"), JSON.stringify({ title: input.title, slideCount: count, validated: true,
|
||||
purpose, deliveryAccepted: false, runId: task?.runId, taskRevision: task?.revision,
|
||||
sha256: createHash("sha256").update(bytes).digest("hex"), template: input.template,
|
||||
note: "Package, layout checks and rendering passed. Business facts and visual quality require review." }));
|
||||
Reference in New Issue
Block a user