AI / src /utils /documentGenerator.ts
Harshavardhan
Update src/utils/documentGenerator.ts
d481377
Raw History Blame Contribute Delete
9.27 kB
import { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType } from "docx";
import * as XLSX from "xlsx";
import { jsPDF } from "jspdf";
import pptxgen from "pptxgenjs";
export type DocType = "docx" | "xlsx" | "pdf" | "pptx";
export interface DocxOptions {
title: string;
subtitle?: string;
sections: Array<{
heading?: string;
body: string | string[];
}>;
}
export interface XlsxOptions {
fileName: string;
sheetName?: string;
headers: string[];
rows: Array<Array<string | number | boolean>>;
}
export interface PdfOptions {
title: string;
subtitle?: string;
author?: string;
content: Array<{
heading?: string;
text: string;
}>;
}
export interface PptxSlide {
title: string;
bullets?: string[];
content?: string;
keyTakeaway?: string;
}
export interface PptxOptions {
title: string;
subtitle?: string;
slides: PptxSlide[];
}
export interface GenerateDocResult {
success: boolean;
message: string;
fileName?: string;
fileUrl?: string;
}
// Check Tier Permissions for Document Creation
export function validateDocTierPermission(
docType: DocType,
userTier: "guest" | "free" | "pro" | "tiniagents",
requestedSlidesCount: number = 1
): { allowed: boolean; reason?: string; maxSlidesAllowed?: number } {
// Everything is 100% FREE for all users without charges or restrictions
return { allowed: true, maxSlidesAllowed: 20 };
}
// 1. Generate Word Document (.docx)
export async function createWordDocument(options: DocxOptions): Promise<Blob> {
const children: Paragraph[] = [];
// Document Title
if (options.title) {
children.push(
new Paragraph({
text: options.title,
heading: HeadingLevel.TITLE,
alignment: AlignmentType.CENTER,
spacing: { after: 200 }
})
);
}
// Subtitle
if (options.subtitle) {
children.push(
new Paragraph({
children: [
new TextRun({
text: options.subtitle,
italics: true,
size: 24,
color: "555555"
})
],
alignment: AlignmentType.CENTER,
spacing: { after: 400 }
})
);
}
// Sections
options.sections.forEach((sec) => {
if (sec.heading) {
children.push(
new Paragraph({
text: sec.heading,
heading: HeadingLevel.HEADING_1,
spacing: { before: 300, after: 120 }
})
);
}
if (Array.isArray(sec.body)) {
sec.body.forEach((paraText) => {
children.push(
new Paragraph({
children: [new TextRun({ text: paraText, size: 22 })],
spacing: { after: 150 }
})
);
});
} else if (sec.body) {
// Split by newlines if paragraph string has breaks
const lines = sec.body.split("\n");
lines.forEach((line) => {
if (line.trim().startsWith("- ") || line.trim().startsWith("* ")) {
children.push(
new Paragraph({
text: line.trim().substring(2),
bullet: { level: 0 },
spacing: { after: 100 }
})
);
} else if (line.trim()) {
children.push(
new Paragraph({
children: [new TextRun({ text: line.trim(), size: 22 })],
spacing: { after: 150 }
})
);
}
});
}
});
const doc = new Document({
sections: [
{
properties: {},
children: children
}
]
});
return await Packer.toBlob(doc);
}
// 2. Generate Excel Spreadsheet (.xlsx)
export function createExcelSpreadsheet(options: XlsxOptions): Blob {
const wsData = [options.headers, ...options.rows];
const worksheet = XLSX.utils.aoa_to_sheet(wsData);
// Set header column formatting auto-width
const colWidths = options.headers.map((h, i) => {
let maxLen = h.length;
options.rows.forEach((r) => {
const valStr = String(r[i] ?? "");
if (valStr.length > maxLen) maxLen = valStr.length;
});
return { wch: Math.min(Math.max(maxLen + 4, 12), 40) };
});
worksheet["!cols"] = colWidths;
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, options.sheetName || "Sheet1");
const wbout = XLSX.write(workbook, { bookType: "xlsx", type: "array" });
return new Blob([wbout], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
}
// 3. Generate PDF Document (.pdf)
export function createPdfDocument(options: PdfOptions): Blob {
const doc = new jsPDF({
orientation: "portrait",
unit: "mm",
format: "a4"
});
const pageHeight = doc.internal.pageSize.getHeight();
const pageWidth = doc.internal.pageSize.getWidth();
const margin = 15;
const maxLineWidth = pageWidth - margin * 2;
let y = 20;
// Primary Header Title
doc.setFont("helvetica", "bold");
doc.setFontSize(20);
doc.setTextColor(30, 41, 59); // Slate-800
doc.text(options.title || "Document Report", margin, y);
y += 10;
if (options.subtitle) {
doc.setFont("helvetica", "normal");
doc.setFontSize(12);
doc.setTextColor(100, 116, 139); // Slate-500
doc.text(options.subtitle, margin, y);
y += 10;
}
// Divider Line
doc.setDrawColor(226, 232, 240); // Slate-200
doc.setLineWidth(0.5);
doc.line(margin, y, pageWidth - margin, y);
y += 10;
options.content.forEach((sec) => {
// Check page space for section header
if (y > pageHeight - 25) {
doc.addPage();
y = 20;
}
if (sec.heading) {
doc.setFont("helvetica", "bold");
doc.setFontSize(14);
doc.setTextColor(15, 23, 42); // Slate-900
doc.text(sec.heading, margin, y);
y += 7;
}
doc.setFont("helvetica", "normal");
doc.setFontSize(10);
doc.setTextColor(51, 65, 85); // Slate-700
const lines = doc.splitTextToSize(sec.text || "", maxLineWidth);
lines.forEach((line: string) => {
if (y > pageHeight - 20) {
doc.addPage();
y = 20;
}
doc.text(line, margin, y);
y += 5.5;
});
y += 6; // Spacing after section
});
// Footer Page Numbers
const totalPages = doc.getNumberOfPages();
for (let i = 1; i <= totalPages; i++) {
doc.setPage(i);
doc.setFont("helvetica", "italic");
doc.setFontSize(8);
doc.setTextColor(148, 163, 184);
doc.text(`Page ${i} of ${totalPages} • Generated by Aura AI`, pageWidth / 2, pageHeight - 10, { align: "center" });
}
return doc.output("blob");
}
// 4. Generate PowerPoint Presentation (.pptx)
export async function createPptxPresentation(
options: PptxOptions,
maxAllowedSlides: number = 10
): Promise<Blob> {
const pptx = new pptxgen();
pptx.layout = "LAYOUT_16x9";
pptx.author = "Aura AI";
pptx.company = "Aura Cognitive Assistant";
// Title Slide
const titleSlide = pptx.addSlide();
titleSlide.background = { color: "0F172A" }; // Dark Navy Blue
titleSlide.addText(options.title || "Presentation", {
x: 0.8,
y: 2.0,
w: "80%",
h: 1.5,
fontSize: 36,
bold: true,
color: "38BDF8", // Sky blue accent
align: "left"
});
if (options.subtitle) {
titleSlide.addText(options.subtitle, {
x: 0.8,
y: 3.6,
w: "80%",
h: 1.0,
fontSize: 20,
color: "94A3B8",
align: "left"
});
}
// Filter slides up to maximum allowed by tier (PRO: 5, TiniAgents: 10)
const slidesToRender = (options.slides || []).slice(0, maxAllowedSlides);
slidesToRender.forEach((slideData, idx) => {
const slide = pptx.addSlide();
slide.background = { color: "F8FAFC" }; // Slate 50 background
// Header Title Bar
slide.addText(slideData.title || `Slide ${idx + 2}`, {
x: 0.8,
y: 0.6,
w: "85%",
h: 0.8,
fontSize: 24,
bold: true,
color: "0F172A"
});
// Content or Bullets
if (slideData.bullets && slideData.bullets.length > 0) {
const bulletItems = slideData.bullets.map((b) => ({
text: b,
options: { fontSize: 16, color: "334155", breakLine: true, bullet: true }
}));
slide.addText(bulletItems, {
x: 0.8,
y: 1.6,
w: "85%",
h: 4.2,
margin: 10
});
} else if (slideData.content) {
slide.addText(slideData.content, {
x: 0.8,
y: 1.6,
w: "85%",
h: 4.2,
fontSize: 16,
color: "334155"
});
}
// Key Takeaway Box at bottom if present
if (slideData.keyTakeaway) {
slide.addText(`KEY TAKEAWAY: ${slideData.keyTakeaway}`, {
x: 0.8,
y: 6.0,
w: "85%",
h: 0.8,
fontSize: 13,
bold: true,
color: "0369A1",
fill: { color: "E0F2FE" },
align: "center"
});
}
});
const blob = (await pptx.write({ outputType: "blob" })) as Blob;
return blob;
}
// Download Helper
export function triggerFileDownload(blob: Blob, defaultFileName: string) {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = defaultFileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 1000);
}