Online Personalized Digital Textbook Generator
Create, customize, and generate educational content in minutes.
Configure Your Textbook
Start by defining the core parameters of your digital textbook.
Generated Outline
Here is the proposed outline. You can edit the titles before generating content.
Generating outline... this may take a moment.
Generate Content
Select chapters or sections to generate the detailed textbook content.
Review & Download
Review the complete textbook below. When you're ready, download it as a PDF.
Preparing PDF...
Click "Assemble Full Textbook" to see your generated content here.
Error: The AI returned an invalid format. Please try again.
`; } } else { outlineOutput.innerHTML = `Error: Failed to generate outline. Please check your connection or API key and try again.
`; } generateOutlineBtn.disabled = false; outlineLoader.classList.add('hidden'); outlineLoader.classList.remove('flex'); }); const renderOutline = () => { outlineOutput.innerHTML = ''; if (textbookData.outline.length === 0) { outlineOutput.innerHTML = 'No outline generated yet. Click the button above.
'; return; } textbookData.outline.forEach((chapter, chapterIndex) => { const chapterDiv = document.createElement('div'); chapterDiv.className = 'p-4 border border-gray-200 rounded-lg bg-gray-50'; const chapterTitleInput = document.createElement('input'); chapterTitleInput.type = 'text'; chapterTitleInput.value = chapter.title; chapterTitleInput.className = 'text-lg font-bold w-full p-2 border border-transparent hover:border-gray-300 focus:border-indigo-500 rounded-md bg-transparent'; chapterTitleInput.addEventListener('change', (e) => { textbookData.outline[chapterIndex].title = e.target.value; }); chapterDiv.appendChild(chapterTitleInput); const sectionsList = document.createElement('div'); sectionsList.className = 'mt-3 ml-4 space-y-2'; chapter.sections.forEach((section, sectionIndex) => { const sectionInput = document.createElement('input'); sectionInput.type = 'text'; sectionInput.value = section.title; sectionInput.className = 'w-full p-2 border border-transparent hover:border-gray-300 focus:border-indigo-500 rounded-md bg-transparent'; sectionInput.addEventListener('change', (e) => { textbookData.outline[chapterIndex].sections[sectionIndex].title = e.target.value; }); sectionsList.appendChild(sectionInput); }); chapterDiv.appendChild(sectionsList); outlineOutput.appendChild(chapterDiv); }); }; // --- TAB 3: CONTENT GENERATION --- populateGenerationListBtn.addEventListener('click', () => { if (textbookData.outline.length === 0) { showMessage('Please generate an outline on Tab 2 first.'); return; } renderGenerationList(); showMessage('Outline loaded. You can now generate content for each section.', false); }); const renderGenerationList = () => { generationListContainer.innerHTML = ''; textbookData.outline.forEach(chapter => { const chapterHeader = document.createElement('h3'); chapterHeader.className = 'text-lg font-semibold mt-4 -mb-2 text-gray-700'; chapterHeader.textContent = chapter.title; generationListContainer.appendChild(chapterHeader); chapter.sections.forEach(section => { const key = `${chapter.title}-${section.title}`; const sectionDiv = document.createElement('div'); sectionDiv.className = 'flex items-center justify-between p-3 border-b border-gray-200'; const label = document.createElement('span'); label.textContent = section.title; sectionDiv.appendChild(label); const controlsDiv = document.createElement('div'); controlsDiv.className = 'flex items-center space-x-2'; const loader = document.createElement('div'); loader.className = 'loader hidden'; loader.id = `loader-${key}`; controlsDiv.appendChild(loader); const statusIcon = document.createElement('span'); statusIcon.id = `status-${key}`; statusIcon.className = 'text-green-500 hidden'; statusIcon.innerHTML = '✓'; // Checkmark controlsDiv.appendChild(statusIcon); const btn = document.createElement('button'); btn.type = 'button'; btn.textContent = 'Generate'; btn.className = 'bg-gray-600 text-white text-sm font-semibold py-1 px-3 rounded-md hover:bg-gray-700 disabled:bg-gray-400'; btn.dataset.chapter = chapter.title; btn.dataset.section = section.title; btn.addEventListener('click', handleContentGeneration); controlsDiv.appendChild(btn); sectionDiv.appendChild(controlsDiv); generationListContainer.appendChild(sectionDiv); }); }); }; const handleContentGeneration = async (event) => { const btn = event.target; const chapterTitle = btn.dataset.chapter; const sectionTitle = btn.dataset.section; const key = `${chapterTitle}-${sectionTitle}`; const loader = document.getElementById(`loader-${key}`); const statusIcon = document.getElementById(`status-${key}`); btn.disabled = true; loader.classList.remove('hidden'); statusIcon.classList.add('hidden'); const prompt = `Write a detailed, engaging, and informative textbook section about "${sectionTitle}". This section is part of the chapter "${chapterTitle}" in a textbook on "${textbookData.topic}". The target audience is ${textbookData.audience}. Use clear headings, paragraphs, and bullet points or lists where appropriate. The tone should be educational and accessible. Do not include the section title itself in the output, only the body content.`; const result = await callGeminiAPI(prompt); if (result) { textbookData.content[key] = result; statusIcon.classList.remove('hidden'); showMessage(`Content for "${sectionTitle}" generated!`, false); } else { showMessage(`Failed to generate content for "${sectionTitle}".`); btn.disabled = false; // Allow retry } loader.classList.add('hidden'); }; // --- TAB 4: REVIEW & DOWNLOAD --- assembleContentBtn.addEventListener('click', () => { if (Object.keys(textbookData.content).length === 0) { showMessage('Please generate some content on Tab 3 first.'); return; } renderFinalContent(); downloadPdfBtn.disabled = false; showMessage('Textbook assembled and ready for review.', false); }); const renderFinalContent = () => { pdfContent.innerHTML = ''; // Clear previous content // Title Page const titlePage = document.createElement('div'); titlePage.className = 'text-center mb-12'; titlePage.innerHTML = `${textbookData.topic}
A Personalized Textbook for ${textbookData.audience}
`; pdfContent.appendChild(titlePage); // Table of Contents const toc = document.createElement('div'); toc.className = 'mb-12'; toc.innerHTML = 'Table of Contents
'; const tocList = document.createElement('div'); tocList.className = 'space-y-2'; textbookData.outline.forEach(chapter => { tocList.innerHTML += `${chapter.title}
`; const sectionList = document.createElement('div'); sectionList.className = 'ml-4 space-y-1'; chapter.sections.forEach(section => { sectionList.innerHTML += `${section.title}
`; }); tocList.appendChild(sectionList); }); toc.appendChild(tocList); pdfContent.appendChild(toc); // Main Content textbookData.outline.forEach(chapter => { const chapterHeader = document.createElement('h2'); chapterHeader.className = 'text-3xl font-semibold border-b pb-2 my-6 break-after-page'; chapterHeader.textContent = chapter.title; pdfContent.appendChild(chapterHeader); chapter.sections.forEach(section => { const sectionHeader = document.createElement('h3'); sectionHeader.className = 'text-2xl font-semibold mt-6 mb-3'; sectionHeader.textContent = section.title; pdfContent.appendChild(sectionHeader); const key = `${chapter.title}-${section.title}`; const contentDiv = document.createElement('div'); contentDiv.className = 'prose max-w-none text-base leading-relaxed'; const generatedHtml = textbookData.content[key] ? textbookData.content[key].replace(/\n/g, '') : '
[Content not generated for this section]
'; contentDiv.innerHTML = generatedHtml; pdfContent.appendChild(contentDiv); }); }); }; downloadPdfBtn.addEventListener('click', () => { if (!pdfContent.innerHTML || pdfContent.innerHTML.trim() === '') { showMessage('Please assemble the content first.'); return; } downloadPdfBtn.disabled = true; pdfLoader.classList.remove('hidden'); pdfLoader.classList.add('flex'); const { jsPDF } = window.jspdf; const element = document.getElementById('pdf-content'); const opt = { margin: [0.5, 0.5, 0.5, 0.5], // inches [top, left, bottom, right] filename: `${textbookData.topic.replace(/ /g, '_')}_Textbook.pdf`, image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2, useCORS: true, logging: false }, jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' } }; // Use html2canvas and jsPDF directly for more control html2canvas(element, opt.html2canvas).then(canvas => { const imgData = canvas.toDataURL('image/jpeg', opt.image.quality); const pdf = new jsPDF(opt.jsPDF); const imgProps= pdf.getImageProperties(imgData); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width; let heightLeft = pdfHeight; let position = 0; pdf.addImage(imgData, 'JPEG', 0, position, pdfWidth, pdfHeight); heightLeft -= pdf.internal.pageSize.getHeight(); while (heightLeft >= 0) { position = heightLeft - pdfHeight; pdf.addPage(); pdf.addImage(imgData, 'JPEG', 0, position, pdfWidth, pdfHeight); heightLeft -= pdf.internal.pageSize.getHeight(); } pdf.save(opt.filename); downloadPdfBtn.disabled = false; pdfLoader.classList.add('hidden'); pdfLoader.classList.remove('flex'); showMessage('PDF downloaded successfully!', false); }).catch(err => { console.error("PDF Generation Error:", err); showMessage("An error occurred during PDF generation. See console for details."); downloadPdfBtn.disabled = false; pdfLoader.classList.add('hidden'); pdfLoader.classList.remove('flex'); }); }); // --- INITIALIZATION --- updateNavButtons(); });