Reuben_OS / lib /documentGenerators.ts
Reubencf's picture
feat: Complete session management system with automatic session creation
16a4dbd
import { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType, Table, TableRow, TableCell, WidthType } from 'docx';
import PDFDocument from 'pdfkit';
import ExcelJS from 'exceljs';
// @ts-ignore
import officegen from 'officegen';
import fs from 'fs';
import path from 'path';
export interface DocumentContent {
title?: string;
content: string | any[];
metadata?: Record<string, any>;
}
export interface TableData {
headers: string[];
rows: string[][];
}
export interface SlideContent {
title: string;
content: string | string[];
bullets?: string[];
}
export class DocumentGenerator {
// Generate DOCX file
static async generateDOCX(content: DocumentContent): Promise<Buffer> {
const doc = new Document({
sections: [{
properties: {},
children: this.parseContentToParagraphs(content.content, content.title)
}]
});
return await Packer.toBuffer(doc);
}
private static parseContentToParagraphs(content: string | any, title?: string): Paragraph[] {
const paragraphs: Paragraph[] = [];
// Add title if provided
if (title) {
paragraphs.push(new Paragraph({
text: title,
heading: HeadingLevel.HEADING_1,
spacing: { after: 200 }
}));
}
// Handle complex nested structures (like the letter example)
if (typeof content === 'object' && content !== null) {
// Check if it has sections array
if (content.sections && Array.isArray(content.sections)) {
for (const section of content.sections) {
if (section.content) {
// Add section content
if (typeof section.content === 'string') {
const lines = section.content.split('\n');
for (const line of lines) {
if (line.trim()) {
paragraphs.push(new Paragraph({
children: [new TextRun(line)],
spacing: { after: 100 }
}));
}
}
}
}
// Handle paragraphs array in section
if (section.paragraphs && Array.isArray(section.paragraphs)) {
for (const para of section.paragraphs) {
paragraphs.push(new Paragraph({
children: [new TextRun(para)],
spacing: { after: 100 }
}));
}
}
}
return paragraphs;
}
// Handle direct content field
if (content.content) {
return this.parseContentToParagraphs(content.content, title);
}
}
if (typeof content === 'string') {
const lines = content.split('\n');
for (const line of lines) {
if (line.startsWith('# ')) {
// H1 heading
paragraphs.push(new Paragraph({
text: line.substring(2),
heading: HeadingLevel.HEADING_1
}));
} else if (line.startsWith('## ')) {
// H2 heading
paragraphs.push(new Paragraph({
text: line.substring(3),
heading: HeadingLevel.HEADING_2
}));
} else if (line.startsWith('### ')) {
// H3 heading
paragraphs.push(new Paragraph({
text: line.substring(4),
heading: HeadingLevel.HEADING_3
}));
} else if (line.startsWith('- ') || line.startsWith('* ')) {
// Bullet point
paragraphs.push(new Paragraph({
text: line.substring(2),
bullet: {
level: 0
}
}));
} else if (line.startsWith('1. ') || /^\d+\. /.test(line)) {
// Numbered list
paragraphs.push(new Paragraph({
text: line.replace(/^\d+\. /, ''),
numbering: {
reference: "default-numbering",
level: 0
}
}));
} else {
// Regular paragraph
paragraphs.push(new Paragraph({
children: [new TextRun(line)]
}));
}
}
} else if (Array.isArray(content)) {
for (const item of content) {
if (typeof item === 'string') {
paragraphs.push(new Paragraph({
children: [new TextRun(item)]
}));
} else if (item.type === 'heading') {
paragraphs.push(new Paragraph({
text: item.text,
heading: item.level || HeadingLevel.HEADING_1
}));
} else if (item.type === 'paragraph') {
paragraphs.push(new Paragraph({
children: [new TextRun(item.text)]
}));
} else if (item.type === 'bullet') {
paragraphs.push(new Paragraph({
text: item.text,
bullet: {
level: item.level || 0
}
}));
}
}
}
return paragraphs;
}
// Generate PDF file
static async generatePDF(content: DocumentContent): Promise<Buffer> {
return new Promise((resolve, reject) => {
const doc = new PDFDocument();
const buffers: Buffer[] = [];
doc.on('data', buffers.push.bind(buffers));
doc.on('end', () => {
const pdfData = Buffer.concat(buffers);
resolve(pdfData);
});
doc.on('error', reject);
// Add title if provided
if (content.title) {
doc.fontSize(20).text(content.title, { align: 'center' });
doc.moveDown();
}
// Add content
if (typeof content.content === 'string') {
const lines = content.content.split('\n');
for (const line of lines) {
if (line.startsWith('# ')) {
doc.fontSize(18).text(line.substring(2));
} else if (line.startsWith('## ')) {
doc.fontSize(16).text(line.substring(3));
} else if (line.startsWith('### ')) {
doc.fontSize(14).text(line.substring(4));
} else if (line.startsWith('- ') || line.startsWith('* ')) {
doc.fontSize(12).text(`• ${line.substring(2)}`, { indent: 20 });
} else {
doc.fontSize(12).text(line);
}
doc.moveDown(0.5);
}
}
doc.end();
});
}
// Generate PowerPoint file
static async generatePowerPoint(slides: SlideContent[]): Promise<Buffer> {
return new Promise((resolve, reject) => {
const pptx = officegen('pptx');
const buffers: Buffer[] = [];
pptx.on('data', (data: Buffer) => buffers.push(data));
pptx.on('end', () => {
const pptData = Buffer.concat(buffers);
resolve(pptData);
});
pptx.on('error', reject);
// Add title slide
if (slides.length > 0 && slides[0].title) {
const titleSlide = pptx.makeNewSlide();
titleSlide.title = slides[0].title;
if (slides[0].content) {
titleSlide.addText(slides[0].content.toString(), {
x: 'c',
y: 'c',
cx: '80%',
cy: '30%',
font_size: 18
});
}
}
// Add content slides
for (let i = 1; i < slides.length; i++) {
const slide = pptx.makeNewSlide();
slide.title = slides[i].title;
if (slides[i].bullets && slides[i].bullets!.length > 0) {
// Add bullet points
const bullets = slides[i].bullets!.map(bullet => ({
text: bullet,
options: { font_size: 14 }
}));
slide.addText(bullets, {
x: 0.5,
y: 1.5,
cx: '90%',
cy: '70%'
});
} else if (slides[i].content) {
// Add regular content
slide.addText(slides[i].content.toString(), {
x: 0.5,
y: 1.5,
cx: '90%',
cy: '70%',
font_size: 14
});
}
}
pptx.generate();
});
}
// Generate Excel file
static async generateExcel(data: { sheets: { name: string; data: TableData }[] }): Promise<Buffer> {
const workbook = new ExcelJS.Workbook();
workbook.creator = 'ReubenOS';
workbook.created = new Date();
workbook.modified = new Date();
for (const sheetData of data.sheets) {
const worksheet = workbook.addWorksheet(sheetData.name);
// Add headers
worksheet.addRow(sheetData.data.headers);
// Style headers
worksheet.getRow(1).font = { bold: true };
worksheet.getRow(1).fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFE0E0E0' }
};
// Add data rows
for (const row of sheetData.data.rows) {
worksheet.addRow(row);
}
// Auto-fit columns
worksheet.columns.forEach((column) => {
let maxLength = 0;
if (column && column.eachCell) {
column.eachCell({ includeEmpty: true }, (cell) => {
const length = cell.value ? cell.value.toString().length : 10;
if (length > maxLength) {
maxLength = length;
}
});
column.width = maxLength + 2;
}
});
// Add borders to all cells with data
const rowCount = worksheet.rowCount;
const colCount = worksheet.columnCount;
for (let row = 1; row <= rowCount; row++) {
for (let col = 1; col <= colCount; col++) {
const cell = worksheet.getCell(row, col);
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
};
}
}
}
const buffer = await workbook.xlsx.writeBuffer();
return Buffer.from(buffer);
}
// Generate LaTeX and compile to PDF (requires latex installation)
static async generateLatexPDF(latexContent: string): Promise<Buffer> {
// For now, we'll use PDFKit as LaTeX compilation requires external tools
// In production, you'd use node-latex or similar with a LaTeX installation
return this.generatePDF({
content: this.convertLatexToPlainText(latexContent)
});
}
private static convertLatexToPlainText(latex: string): string {
// Basic LaTeX to plain text conversion
return latex
.replace(/\\documentclass{.*?}/g, '')
.replace(/\\usepackage{.*?}/g, '')
.replace(/\\begin{document}/g, '')
.replace(/\\end{document}/g, '')
.replace(/\\section{(.*?)}/g, '# $1')
.replace(/\\subsection{(.*?)}/g, '## $1')
.replace(/\\subsubsection{(.*?)}/g, '### $1')
.replace(/\\textbf{(.*?)}/g, '**$1**')
.replace(/\\textit{(.*?)}/g, '*$1*')
.replace(/\\item/g, '- ')
.replace(/\\begin{itemize}/g, '')
.replace(/\\end{itemize}/g, '')
.replace(/\\begin{enumerate}/g, '')
.replace(/\\end{enumerate}/g, '')
.replace(/\$/g, '')
.replace(/\\/g, '\n')
.trim();
}
// Convert markdown to structured content for document generation
static parseMarkdown(markdown: string): any[] {
const lines = markdown.split('\n');
const content = [];
for (const line of lines) {
if (line.startsWith('# ')) {
content.push({ type: 'heading', text: line.substring(2), level: HeadingLevel.HEADING_1 });
} else if (line.startsWith('## ')) {
content.push({ type: 'heading', text: line.substring(3), level: HeadingLevel.HEADING_2 });
} else if (line.startsWith('### ')) {
content.push({ type: 'heading', text: line.substring(4), level: HeadingLevel.HEADING_3 });
} else if (line.startsWith('- ') || line.startsWith('* ')) {
content.push({ type: 'bullet', text: line.substring(2) });
} else if (line.trim() !== '') {
content.push({ type: 'paragraph', text: line });
}
}
return content;
}
}