Storytelling Assistant

Storytelling Assistant

Build your world, one idea at a time.

Story Foundation

Generate Elements

Generated Idea

Your next idea will appear here.

My Storyboard

${p}

`).join('') .replace(/

/g, '

').replace(/<\/h4><\/p>/g, '

') .replace(/

/g, '

').replace(/<\/h3><\/p>/g, '

'); } async function handleGeneration(e) { const btn = e.currentTarget; const type = btn.dataset.type; const genre = genreSelect.value; // UI State placeholder.classList.add('hidden'); generatedOutput.innerHTML = ''; loader.classList.remove('hidden'); generateBtns.forEach(b => b.disabled = true); currentGeneratedElement = null; const systemPrompt = "You are an expert storyteller and creative writing assistant. Your goal is to provide rich, imaginative, and detailed content to help a writer build their story. Respond only with the requested element, formatted in simple markdown as requested, without any extra conversational text."; const userQuery = prompts[type].replace('[GENRE]', genre); const apiKey = ""; const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent?key=${apiKey}`; const payload = { contents: [{ parts: [{ text: userQuery }] }], systemInstruction: { parts: [{ text: systemPrompt }] }, }; try { const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) throw new Error(`API Error: ${response.status}`); const result = await response.json(); const candidate = result.candidates?.[0]; if (candidate && candidate.content?.parts?.[0]?.text) { const content = candidate.content.parts[0].text; currentGeneratedElement = { type, content, html: simpleMarkdownToHtml(content) }; displayGeneratedElement(); } else { throw new Error("Invalid API response."); } } catch (error) { console.error(`Error generating ${type}:`, error); generatedOutput.innerHTML = `
Error: Failed to generate idea. Please try again.
`; } finally { loader.classList.add('hidden'); generateBtns.forEach(b => b.disabled = false); } } function displayGeneratedElement() { if (!currentGeneratedElement) return; generatedOutput.innerHTML = `
${currentGeneratedElement.html}
`; lucide.createIcons(); document.getElementById('save-btn').addEventListener('click', handleSave); } function handleSave() { if (currentGeneratedElement) { storyboardElements.push(currentGeneratedElement); renderStoryboard(); generatedOutput.innerHTML = '

Idea saved to storyboard!

'; currentGeneratedElement = null; } } function renderStoryboard() { storyboardSection.classList.remove('hidden'); storyboardList.innerHTML = storyboardElements.map((el, index) => `
${el.html}
`).join(''); lucide.createIcons(); document.querySelectorAll('.remove-btn').forEach(btn => btn.addEventListener('click', handleRemove)); } function handleRemove(e) { const index = parseInt(e.currentTarget.dataset.index, 10); storyboardElements.splice(index, 1); renderStoryboard(); if (storyboardElements.length === 0) { storyboardSection.classList.add('hidden'); } } async function handlePdfDownload() { const pdfContent = document.getElementById('pdf-content'); pdfContent.innerHTML = storyboardElements.map(el => `

${el.type}

${el.html.replace(/

/g, '

').replace(/<\/h4>/g, '

').replace(/

/g, '

').replace(/<\/h3>/g, '

')}
`).join(''); const contentToPrint = document.getElementById('pdf-output'); contentToPrint.style.display = 'block'; try { const canvas = await html2canvas(contentToPrint, { scale: 2 }); const { jsPDF } = window.jspdf; const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF({ orientation: 'portrait', unit: 'pt', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = (canvas.height * pdfWidth) / canvas.width; pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight); pdf.save('My-Storyboard.pdf'); } catch (err) { console.error("Error generating PDF:", err); alert("Could not generate PDF."); } finally { contentToPrint.style.display = 'none'; } } // --- EVENT LISTENERS --- generateBtns.forEach(btn => btn.addEventListener('click', handleGeneration)); downloadPdfBtn.addEventListener('click', handlePdfDownload); // --- INITIALIZATION --- lucide.createIcons(); });
Scroll to Top