Twitter Thread Generator

Twitter Thread Generator

Craft the perfect thread from a single idea. Let AI handle the rest.

Your generated thread will appear here.

Fill in the details on the left and click generate!

Twitter Thread Report

Original Topic

${tweetText}

`; threadContainer.insertAdjacentHTML('beforeend', tweetCard); }); } generateBtn.addEventListener('click', async () => { const userTopic = userTopicEl.value.trim(); const tone = toneOfVoiceEl.value; const count = tweetCountEl.value; if (!userTopic) { errorMessageEl.textContent = 'Please enter a topic or main text.'; errorMessageEl.classList.remove('hidden'); initialState.classList.add('hidden'); threadOutput.classList.add('hidden'); return; } // UI updates for loading state loader.classList.remove('hidden'); initialState.classList.add('hidden'); threadOutput.classList.add('hidden'); errorMessageEl.classList.add('hidden'); generateBtn.disabled = true; generateBtn.classList.add('opacity-60', 'cursor-not-allowed'); const prompt = `Act as a social media expert. Generate a Twitter thread of exactly ${count} tweets based on the following topic. - Topic: "${userTopic}" - Tone: ${tone} - Each tweet MUST be under 280 characters. - Ensure the tweets flow logically from one to the next. - Respond ONLY with a single, minified JSON object with one key, "thread", which holds an array of the tweet strings. Example: {"thread":["First tweet.","Second tweet..."]}`; try { const thread = await generateThreadWithBackoff(prompt); lastGeneratedThread = thread; displayThread(thread); threadOutput.classList.remove('hidden'); } catch (error) { console.error('Failed to generate thread:', error); errorMessageEl.textContent = 'Sorry, an error occurred while generating the thread. Please try again.'; errorMessageEl.classList.remove('hidden'); } finally { loader.classList.add('hidden'); generateBtn.disabled = false; generateBtn.classList.remove('opacity-60', 'cursor-not-allowed'); } }); copyBtn.addEventListener('click', () => { if (lastGeneratedThread.length === 0) return; const threadText = lastGeneratedThread.map((tweet, i) => `(${i+1}/${lastGeneratedThread.length})\n${tweet}`).join('\n\n'); // Using the document.execCommand for broader compatibility within iframes const textArea = document.createElement("textarea"); textArea.value = threadText; document.body.appendChild(textArea); textArea.select(); try { document.execCommand('copy'); copyBtnText.textContent = 'Copied!'; setTimeout(() => { copyBtnText.textContent = 'Copy Thread'; }, 2000); } catch (err) { console.error('Fallback: Oops, unable to copy', err); alert('Could not copy text.'); } document.body.removeChild(textArea); }); downloadPdfBtn.addEventListener('click', () => { if (lastGeneratedThread.length === 0) return; const { jsPDF } = window.jspdf; document.getElementById('pdf-original-topic').textContent = userTopicEl.value; const pdfThreadContent = document.getElementById('pdf-thread-content'); pdfThreadContent.innerHTML = '

Generated Thread

'; lastGeneratedThread.forEach((tweet, index) => { const tweetHtml = `

Tweet ${index + 1} of ${lastGeneratedThread.length}

${tweet}

`; pdfThreadContent.insertAdjacentHTML('beforeend', tweetHtml); }); const contentToPrint = document.getElementById('pdf-output'); contentToPrint.style.display = 'block'; html2canvas(contentToPrint, { scale: 2 }).then(canvas => { const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF({ orientation: 'portrait', unit: 'pt', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); const ratio = canvas.height / canvas.width; const finalImgWidth = pdfWidth - 80; // Margins const finalImgHeight = finalImgWidth * ratio; pdf.addImage(imgData, 'PNG', 40, 40, finalImgWidth, finalImgHeight); pdf.save('Twitter-Thread.pdf'); contentToPrint.style.display = 'none'; }).catch(err => { console.error("Error generating PDF:", err); contentToPrint.style.display = 'none'; }); }); // Initialize Lucide icons lucide.createIcons(); });
Scroll to Top