Storytelling Assistant
Build your world, one idea at a time.
Story Foundation
Generate Elements
Generated Idea
Your next idea will appear here.
Brewing a fresh idea...
My Storyboard
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}
${el.type}
${el.html.replace(//g, '
/g, '
').replace(/<\/h4>/g, '
').replace(/