mirror of
https://github.com/Mintplex-Labs/anything-llm
synced 2026-04-25 17:15:37 +02:00
* Powerpoint File Creation (#5278) * wip * download card * UI for downloading * move to fs system with endpoint to pull files * refactor UI * final-pass * remove save-file-browser skill and refactor * remove fileDownload event * reset * reset file * reset timeout * persist toggle * Txt creation (#5279) * wip * download card * UI for downloading * move to fs system with endpoint to pull files * refactor UI * final-pass * remove save-file-browser skill and refactor * remove fileDownload event * reset * reset file * reset timeout * wip * persist toggle * add arbitrary text creation file * Add PDF document generation with markdown formatting (#5283) add support for branding in bottom right corner refactor core utils and frontend rendering * Xlsx document creation (#5284) add Excel doc & sheet creation * Basic docx creation (#5285) * Basic docx creation * add test theme support + styling and title pages * simplify skill selection * handle TG attachments * send documents over tg * lazy import * pin deps * fix lock * i18n for file creation (#5286) i18n for file-creation connect #5280 * theme overhaul * Add PPTX subagent for better results * forgot files * Add PPTX subagent for better results (#5287) * Add PPTX subagent for better results * forgot files * make sub-agent use proper tool calling if it can and better UI hints
379 lines
7.9 KiB
JavaScript
379 lines
7.9 KiB
JavaScript
const createFilesLib = require("../lib.js");
|
||
|
||
// All positioning assumes LAYOUT_16x9: 10 × 5.625 in.
|
||
const MARGIN_X = 0.7;
|
||
const CONTENT_W = 8.6; // 10 - 2 × MARGIN_X
|
||
const SLIDE_H = 5.625;
|
||
|
||
function isDarkColor(hexColor) {
|
||
const hex = (hexColor || "FFFFFF").replace("#", "");
|
||
const r = parseInt(hex.substr(0, 2), 16);
|
||
const g = parseInt(hex.substr(2, 2), 16);
|
||
const b = parseInt(hex.substr(4, 2), 16);
|
||
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 < 0.5;
|
||
}
|
||
|
||
function addBranding(slide, bgColor) {
|
||
const isDark = isDarkColor(bgColor);
|
||
const textColor = isDark ? "FFFFFF" : "000000";
|
||
const logo = createFilesLib.getLogo({
|
||
forDarkBackground: isDark,
|
||
format: "dataUri",
|
||
});
|
||
|
||
slide.addText("Created with", {
|
||
x: 7.85,
|
||
y: 5.06,
|
||
w: 1.85,
|
||
h: 0.12,
|
||
fontSize: 5.5,
|
||
color: textColor,
|
||
transparency: 78,
|
||
fontFace: "Calibri",
|
||
align: "center",
|
||
italic: true,
|
||
});
|
||
|
||
if (logo) {
|
||
slide.addImage({
|
||
data: logo,
|
||
x: 8.025,
|
||
y: 5.17,
|
||
w: 1.5,
|
||
h: 0.24,
|
||
transparency: 78,
|
||
});
|
||
} else {
|
||
slide.addText("AnythingLLM", {
|
||
x: 7.85,
|
||
y: 5.17,
|
||
w: 1.85,
|
||
h: 0.24,
|
||
fontSize: 8,
|
||
color: textColor,
|
||
transparency: 78,
|
||
fontFace: "Calibri",
|
||
align: "center",
|
||
});
|
||
}
|
||
}
|
||
|
||
function addTopAccentBar(slide, pptx, theme) {
|
||
slide.addShape(pptx.ShapeType.rect, {
|
||
x: 0,
|
||
y: 0,
|
||
w: "100%",
|
||
h: 0.05,
|
||
fill: { color: theme.accentColor },
|
||
line: { color: theme.accentColor },
|
||
});
|
||
}
|
||
|
||
function addAccentUnderline(slide, pptx, x, y, color) {
|
||
slide.addShape(pptx.ShapeType.rect, {
|
||
x,
|
||
y,
|
||
w: 1.5,
|
||
h: 0.035,
|
||
fill: { color },
|
||
line: { color },
|
||
});
|
||
}
|
||
|
||
function addSlideFooter(slide, pptx, theme, slideNumber, totalSlides) {
|
||
slide.addShape(pptx.ShapeType.rect, {
|
||
x: MARGIN_X,
|
||
y: 5.0,
|
||
w: CONTENT_W,
|
||
h: 0.007,
|
||
fill: { color: theme.footerLineColor },
|
||
line: { color: theme.footerLineColor },
|
||
});
|
||
|
||
slide.addText(`${slideNumber} / ${totalSlides}`, {
|
||
x: MARGIN_X,
|
||
y: 5.07,
|
||
w: 1.2,
|
||
h: 0.25,
|
||
fontSize: 8,
|
||
color: theme.footerColor,
|
||
fontFace: theme.fontBody,
|
||
align: "left",
|
||
});
|
||
}
|
||
|
||
function renderTitleSlide(slide, pptx, { title, author }, theme) {
|
||
slide.background = { color: theme.titleSlideBackground };
|
||
|
||
slide.addText(title || "Untitled", {
|
||
x: 1.0,
|
||
y: 1.3,
|
||
w: 8.0,
|
||
h: 1.4,
|
||
fontSize: 36,
|
||
bold: true,
|
||
color: theme.titleSlideTitleColor,
|
||
fontFace: theme.fontTitle,
|
||
align: "center",
|
||
valign: "bottom",
|
||
});
|
||
|
||
addAccentUnderline(slide, pptx, 4.25, 2.9, theme.titleSlideAccentColor);
|
||
|
||
if (author) {
|
||
slide.addText(author, {
|
||
x: 1.5,
|
||
y: 3.15,
|
||
w: 7.0,
|
||
h: 0.45,
|
||
fontSize: 14,
|
||
color: theme.titleSlideSubtitleColor,
|
||
fontFace: theme.fontBody,
|
||
align: "center",
|
||
italic: true,
|
||
});
|
||
}
|
||
|
||
// Bottom accent strip
|
||
slide.addShape(pptx.ShapeType.rect, {
|
||
x: 0,
|
||
y: SLIDE_H - 0.1,
|
||
w: "100%",
|
||
h: 0.1,
|
||
fill: { color: theme.titleSlideAccentColor },
|
||
line: { color: theme.titleSlideAccentColor },
|
||
});
|
||
|
||
addBranding(slide, theme.titleSlideBackground);
|
||
}
|
||
|
||
function renderSectionSlide(
|
||
slide,
|
||
pptx,
|
||
slideData,
|
||
theme,
|
||
slideNumber,
|
||
totalSlides
|
||
) {
|
||
slide.background = { color: theme.titleSlideBackground };
|
||
|
||
slide.addText(slideData.title || "", {
|
||
x: 1.0,
|
||
y: 1.5,
|
||
w: 8.0,
|
||
h: 1.2,
|
||
fontSize: 32,
|
||
bold: true,
|
||
color: theme.titleSlideTitleColor,
|
||
fontFace: theme.fontTitle,
|
||
align: "center",
|
||
valign: "bottom",
|
||
});
|
||
|
||
addAccentUnderline(slide, pptx, 4.25, 2.9, theme.titleSlideAccentColor);
|
||
|
||
if (slideData.subtitle) {
|
||
slide.addText(slideData.subtitle, {
|
||
x: 1.5,
|
||
y: 3.1,
|
||
w: 7.0,
|
||
h: 0.5,
|
||
fontSize: 16,
|
||
color: theme.titleSlideSubtitleColor,
|
||
fontFace: theme.fontBody,
|
||
align: "center",
|
||
});
|
||
}
|
||
|
||
const numColor = isDarkColor(theme.titleSlideBackground)
|
||
? "FFFFFF"
|
||
: "000000";
|
||
slide.addText(`${slideNumber} / ${totalSlides}`, {
|
||
x: MARGIN_X,
|
||
y: 5.1,
|
||
w: 1.2,
|
||
h: 0.25,
|
||
fontSize: 8,
|
||
color: numColor,
|
||
transparency: 65,
|
||
fontFace: theme.fontBody,
|
||
align: "left",
|
||
});
|
||
|
||
addBranding(slide, theme.titleSlideBackground);
|
||
|
||
if (slideData.notes) slide.addNotes(slideData.notes);
|
||
}
|
||
|
||
function renderContentSlide(
|
||
slide,
|
||
pptx,
|
||
slideData,
|
||
theme,
|
||
slideNumber,
|
||
totalSlides
|
||
) {
|
||
slide.background = { color: theme.background };
|
||
|
||
addTopAccentBar(slide, pptx, theme);
|
||
|
||
let contentStartY = 0.4;
|
||
|
||
if (slideData.title) {
|
||
slide.addText(slideData.title, {
|
||
x: MARGIN_X,
|
||
y: 0.3,
|
||
w: CONTENT_W,
|
||
h: 0.65,
|
||
fontSize: 24,
|
||
bold: true,
|
||
color: theme.titleColor,
|
||
fontFace: theme.fontTitle,
|
||
valign: "bottom",
|
||
});
|
||
contentStartY = 1.0;
|
||
|
||
if (slideData.subtitle) {
|
||
slide.addText(slideData.subtitle, {
|
||
x: MARGIN_X,
|
||
y: 1.0,
|
||
w: CONTENT_W,
|
||
h: 0.3,
|
||
fontSize: 13,
|
||
color: theme.subtitleColor,
|
||
fontFace: theme.fontBody,
|
||
});
|
||
contentStartY = 1.35;
|
||
}
|
||
|
||
addAccentUnderline(
|
||
slide,
|
||
pptx,
|
||
MARGIN_X,
|
||
contentStartY + 0.05,
|
||
theme.accentColor
|
||
);
|
||
contentStartY += 0.25;
|
||
}
|
||
|
||
const footerY = 5.0;
|
||
const contentHeight = footerY - contentStartY - 0.15;
|
||
|
||
if (slideData.table) {
|
||
addTableContent(slide, pptx, slideData.table, theme, contentStartY);
|
||
} else {
|
||
addBulletContent(
|
||
slide,
|
||
slideData.content,
|
||
theme,
|
||
contentStartY,
|
||
contentHeight
|
||
);
|
||
}
|
||
|
||
addSlideFooter(slide, pptx, theme, slideNumber, totalSlides);
|
||
addBranding(slide, theme.background);
|
||
|
||
if (slideData.notes) slide.addNotes(slideData.notes);
|
||
}
|
||
|
||
function renderBlankSlide(slide, pptx, theme, slideNumber, totalSlides) {
|
||
slide.background = { color: theme.background };
|
||
addSlideFooter(slide, pptx, theme, slideNumber, totalSlides);
|
||
addBranding(slide, theme.background);
|
||
}
|
||
|
||
function addBulletContent(slide, content, theme, startY, maxHeight) {
|
||
if (!Array.isArray(content) || content.length === 0) return;
|
||
|
||
const bulletPoints = content.map((text) => ({
|
||
text,
|
||
options: {
|
||
fontSize: 15,
|
||
color: theme.bodyColor,
|
||
fontFace: theme.fontBody,
|
||
bullet: { code: "25AA", color: theme.bulletColor },
|
||
paraSpaceAfter: 10,
|
||
},
|
||
}));
|
||
|
||
slide.addText(bulletPoints, {
|
||
x: MARGIN_X,
|
||
y: startY,
|
||
w: CONTENT_W,
|
||
h: maxHeight,
|
||
valign: "top",
|
||
});
|
||
}
|
||
|
||
function addTableContent(slide, pptx, tableData, theme, startY) {
|
||
if (!tableData) return;
|
||
|
||
const rows = [];
|
||
|
||
if (tableData.headers?.length > 0) {
|
||
rows.push(
|
||
tableData.headers.map((header) => ({
|
||
text: header,
|
||
options: {
|
||
bold: true,
|
||
fontSize: 12,
|
||
fontFace: theme.fontBody,
|
||
color: theme.tableHeaderColor,
|
||
fill: { color: theme.tableHeaderBg },
|
||
align: "left",
|
||
valign: "middle",
|
||
margin: [4, 8, 4, 8],
|
||
},
|
||
}))
|
||
);
|
||
}
|
||
|
||
if (tableData.rows?.length > 0) {
|
||
tableData.rows.forEach((row, idx) => {
|
||
rows.push(
|
||
row.map((cell) => ({
|
||
text: cell,
|
||
options: {
|
||
fontSize: 11,
|
||
fontFace: theme.fontBody,
|
||
color: theme.bodyColor,
|
||
fill: {
|
||
color: idx % 2 === 1 ? theme.tableAltRowBg : theme.background,
|
||
},
|
||
align: "left",
|
||
valign: "middle",
|
||
margin: [4, 8, 4, 8],
|
||
},
|
||
}))
|
||
);
|
||
});
|
||
}
|
||
|
||
if (rows.length === 0) return;
|
||
|
||
const colCount = rows[0].length;
|
||
slide.addTable(rows, {
|
||
x: MARGIN_X,
|
||
y: startY,
|
||
w: CONTENT_W,
|
||
colW: CONTENT_W / colCount,
|
||
rowH: 0.4,
|
||
border: { type: "solid", pt: 0.5, color: theme.tableBorderColor },
|
||
});
|
||
}
|
||
|
||
module.exports = {
|
||
isDarkColor,
|
||
addBranding,
|
||
addTopAccentBar,
|
||
addAccentUnderline,
|
||
addSlideFooter,
|
||
renderTitleSlide,
|
||
renderSectionSlide,
|
||
renderContentSlide,
|
||
renderBlankSlide,
|
||
addBulletContent,
|
||
addTableContent,
|
||
};
|