## Agents Added - Alateya: R&D, biotech, innovations - Clan (Spirit): Community spirit agent - Eonarch: Consciousness evolution agent ## Changes - docker-compose.node1.yml: Added tokens for all 3 new agents - gateway-bot/http_api.py: Added configs and webhook endpoints - gateway-bot/clan_prompt.txt: New prompt file - gateway-bot/eonarch_prompt.txt: New prompt file ## Fixes - Fixed ROUTER_URL from :9102 to :8000 (internal container port) - All 9 Telegram agents now working ## Documentation - Created PROJECT-MASTER-INDEX.md - single entry point - Added various status documents and scripts Tokens configured: - Helion, NUTRA, Agromatrix (existing) - Alateya, Clan, Eonarch (new) - Druid, GreenFood, DAARWIZZ (configured)
120 lines
3.9 KiB
JavaScript
120 lines
3.9 KiB
JavaScript
import fs from "node:fs";
|
|
import crypto from "node:crypto";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
import axios from "axios";
|
|
import { connect } from "nats";
|
|
import { Client as Minio } from "minio";
|
|
import PptxGenJS from "pptxgenjs";
|
|
|
|
const NATS_URL = process.env.NATS_URL || "nats://nats:4222";
|
|
const REGISTRY_URL = (process.env.ARTIFACT_REGISTRY_URL || "http://artifact-registry:9220").replace(/\/$/, "");
|
|
const MINIO_ENDPOINT = process.env.MINIO_ENDPOINT || "minio:9000";
|
|
const MINIO_ACCESS_KEY = process.env.MINIO_ACCESS_KEY || "minioadmin";
|
|
const MINIO_SECRET_KEY = process.env.MINIO_SECRET_KEY || "minioadmin";
|
|
const MINIO_BUCKET = process.env.MINIO_BUCKET || "artifacts";
|
|
const MINIO_SECURE = (process.env.MINIO_SECURE || "false").toLowerCase() === "true";
|
|
|
|
const minioClient = new Minio({
|
|
endPoint: MINIO_ENDPOINT.split(":")[0],
|
|
port: Number(MINIO_ENDPOINT.split(":")[1] || 9000),
|
|
useSSL: MINIO_SECURE,
|
|
accessKey: MINIO_ACCESS_KEY,
|
|
secretKey: MINIO_SECRET_KEY,
|
|
});
|
|
|
|
const toBuffer = async (stream) => {
|
|
const chunks = [];
|
|
for await (const chunk of stream) {
|
|
chunks.push(chunk);
|
|
}
|
|
return Buffer.concat(chunks);
|
|
};
|
|
|
|
const sha256 = (buf) => crypto.createHash("sha256").update(buf).digest("hex");
|
|
|
|
const renderPptx = async (slidespec, outPath) => {
|
|
const pptx = new PptxGenJS();
|
|
pptx.layout = "LAYOUT_WIDE";
|
|
pptx.author = "DAARION Artifact Worker";
|
|
|
|
const slides = slidespec.slides || [];
|
|
if (!slides.length) {
|
|
throw new Error("No slides in slidespec");
|
|
}
|
|
|
|
slides.forEach((slide, idx) => {
|
|
const s = pptx.addSlide();
|
|
const title = slide.title || slidespec.title || `Slide ${idx + 1}`;
|
|
s.addText(title, { x: 0.6, y: 0.6, w: 12, h: 0.8, fontSize: idx === 0 ? 36 : 28, bold: true });
|
|
|
|
if (slide.subtitle) {
|
|
s.addText(slide.subtitle, { x: 0.6, y: 1.6, w: 12, h: 0.5, fontSize: 16, color: "666666" });
|
|
}
|
|
|
|
if (slide.bullets && Array.isArray(slide.bullets) && slide.bullets.length) {
|
|
s.addText(slide.bullets.map((b) => ({ text: b, options: { bullet: { indent: 18 } } })), {
|
|
x: 0.8,
|
|
y: 2.0,
|
|
w: 11.5,
|
|
h: 4.5,
|
|
fontSize: 18,
|
|
color: "333333",
|
|
});
|
|
}
|
|
});
|
|
|
|
await pptx.writeFile({ fileName: outPath });
|
|
};
|
|
|
|
const handleJob = async (msg) => {
|
|
const data = JSON.parse(msg.data.toString());
|
|
const { job_id, storage_key, theme_id, artifact_id, input_version_id } = data;
|
|
const slidespecKey = storage_key || `artifacts/${artifact_id}/versions/${input_version_id}/slidespec.json`;
|
|
|
|
try {
|
|
const objectStream = await minioClient.getObject(MINIO_BUCKET, slidespecKey);
|
|
const buf = await toBuffer(objectStream);
|
|
const slidespec = JSON.parse(buf.toString("utf-8"));
|
|
|
|
const tmpFile = path.join(os.tmpdir(), `${job_id}.pptx`);
|
|
await renderPptx(slidespec, tmpFile, theme_id);
|
|
|
|
const pptxBuf = fs.readFileSync(tmpFile);
|
|
const pptxSha = sha256(pptxBuf);
|
|
const pptxKey = `artifacts/${artifact_id}/versions/${input_version_id}/presentation.pptx`;
|
|
|
|
await minioClient.putObject(MINIO_BUCKET, pptxKey, pptxBuf, pptxBuf.length, {
|
|
"Content-Type": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
});
|
|
|
|
await axios.post(`${REGISTRY_URL}/jobs/${job_id}/complete`, {
|
|
output_storage_key: pptxKey,
|
|
mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
size_bytes: pptxBuf.length,
|
|
sha256: pptxSha,
|
|
label: "pptx",
|
|
});
|
|
|
|
fs.unlinkSync(tmpFile);
|
|
} catch (err) {
|
|
await axios.post(`${REGISTRY_URL}/jobs/${job_id}/fail`, {
|
|
error_text: String(err?.message || err),
|
|
});
|
|
}
|
|
};
|
|
|
|
const main = async () => {
|
|
const nc = await connect({ servers: [NATS_URL] });
|
|
const sub = nc.subscribe("artifact.job.render_pptx.requested");
|
|
for await (const msg of sub) {
|
|
await handleJob(msg);
|
|
}
|
|
};
|
|
|
|
main().catch((err) => {
|
|
console.error("worker failed", err);
|
|
process.exit(1);
|
|
});
|