Spaces:
Running
Running
| /** | |
| * analyze.js — content generation engine for CreatorKit | |
| * All processing is local — no external API needed. | |
| */ | |
| // ── Stop words (PT + EN) ────────────────────────────────────── | |
| const STOP_WORDS = new Set([ | |
| // PT | |
| 'a','ao','aos','aquela','aquelas','aquele','aqueles','aquilo','as','até','com', | |
| 'como','da','das','de','dela','delas','dele','deles','depois','do','dos','e', | |
| 'ela','elas','ele','eles','em','entre','era','eram','essa','essas','esse', | |
| 'esses','esta','estas','este','estes','eu','foi','for','foram','há','isso', | |
| 'isto','já','lhe','lhes','mais','mas','me','mesmo','meu','minha','muito', | |
| 'na','nas','nem','no','nos','nós','não','num','numa','o','os','ou','para', | |
| 'pela','pelas','pelo','pelos','por','que','qual','quando','quem','são','se', | |
| 'sem','ser','seu','seus','si','sua','suas','também','te','tem','ter','teu', | |
| 'tua','tuas','tudo','um','uma','uns','umas','vai','vem','você','vocês','à', | |
| 'às','é','isso','assim','então','agora','aqui','ali','lá','ainda','logo', | |
| 'porque','então','pois','portanto','todavia','porém','entanto','contudo', | |
| 'apenas','só','todo','toda','todos','todas','cada','outro','outra','outros', | |
| 'outras','tal','tais','tanto','tantos','tanta','tantas','bem','mal','muito', | |
| 'pouco','sempre','nunca','jamais','hoje','ontem','amanhã','quando','onde', | |
| // EN | |
| 'a','an','the','and','or','but','in','on','at','to','for','of','with', | |
| 'as','is','was','are','were','be','been','being','have','has','had', | |
| 'do','does','did','will','would','could','should','may','might','shall', | |
| 'can','this','that','these','those','i','we','you','he','she','they','it', | |
| 'my','our','your','his','her','their','its','me','us','him','them','who', | |
| 'what','which','how','when','where','why','all','any','both','each', | |
| 'few','more','most','other','some','such','no','not','only','same','so', | |
| 'than','too','very','just','about','above','after','again','also','before', | |
| 'between','here','into','like','now','out','over','own','then','there', | |
| 'through','under','until','up','while','from','if', | |
| ]); | |
| // ── Tokenize & extract keywords ─────────────────────────────── | |
| function tokenize(text) { | |
| return text | |
| .toLowerCase() | |
| .replace(/[^a-záàãâäéèêëíìîïóòõôöúùûüçñ\s]/gi, ' ') | |
| .split(/\s+/) | |
| .filter(w => w.length > 3 && !STOP_WORDS.has(w)); | |
| } | |
| function extractKeywords(text, topN = 20) { | |
| const words = tokenize(text); | |
| const freq = {}; | |
| for (const w of words) freq[w] = (freq[w] || 0) + 1; | |
| return Object.entries(freq) | |
| .sort((a, b) => b[1] - a[1]) | |
| .slice(0, topN) | |
| .map(([word, count]) => ({ word, count })); | |
| } | |
| // ── Extractive summary ──────────────────────────────────────── | |
| function summarize(text, numSentences = 4) { | |
| const sentences = text.match(/[^.!?]+[.!?]+/g) || [text]; | |
| if (sentences.length <= numSentences) return text.trim(); | |
| const keywords = new Set(extractKeywords(text, 15).map(k => k.word)); | |
| const scored = sentences.map(s => { | |
| const words = tokenize(s); | |
| const score = words.filter(w => keywords.has(w)).length / (words.length || 1); | |
| return { s: s.trim(), score }; | |
| }); | |
| return scored | |
| .sort((a, b) => b.score - a.score) | |
| .slice(0, numSentences) | |
| .sort((a, b) => sentences.indexOf(a.s) - sentences.indexOf(b.s)) | |
| .map(x => x.s) | |
| .join(' '); | |
| } | |
| // ── Hashtag generator ───────────────────────────────────────── | |
| function generateHashtags(text, keywords) { | |
| const tags = keywords | |
| .slice(0, 12) | |
| .map(k => '#' + k.word.replace(/\s+/g, '').normalize('NFD').replace(/[̀-ͯ]/g, '')); | |
| // Detect topics and add contextual tags | |
| const lower = text.toLowerCase(); | |
| const contextual = []; | |
| if (/negócio|empresa|empreend|startup|produto|vend/.test(lower)) contextual.push('#empreendedorismo','#negócios','#business'); | |
| if (/saúde|treino|exercício|academia|fitness|dieta/.test(lower)) contextual.push('#saúde','#fitness','#bemestar'); | |
| if (/tecnologia|tech|software|programação|código|ia|inteligência/.test(lower)) contextual.push('#tecnologia','#tech','#inovação'); | |
| if (/motivação|sucesso|crescimento|mindset|foco/.test(lower)) contextual.push('#motivação','#sucesso','#mindset'); | |
| if (/receita|cozinha|comida|culinária|alimento/.test(lower)) contextual.push('#gastronomia','#culinária','#foodlover'); | |
| if (/moda|roupa|estilo|fashion|look/.test(lower)) contextual.push('#moda','#fashion','#estilo'); | |
| if (/viagem|destino|turismo|lugar|cidade/.test(lower)) contextual.push('#viagem','#turismo','#travel'); | |
| if (/financ|invest|dinheiro|renda|rico/.test(lower)) contextual.push('#finanças','#investimento','#dinheiro'); | |
| if (/relacionamento|amor|família|casal|vida/.test(lower)) contextual.push('#relacionamentos','#vidapessoal'); | |
| const all = [...new Set([...tags, ...contextual, '#conteúdo', '#influencer', '#viral'])]; | |
| return all.slice(0, 25); | |
| } | |
| // ── Instagram Caption ───────────────────────────────────────── | |
| function generateInstagramCaption(text, keywords, hashtags, lang) { | |
| const summary = summarize(text, 2); | |
| const sentences = text.match(/[^.!?]+[.!?]+/g) || [text]; | |
| const hook = sentences[0]?.trim() || summary.split('.')[0]; | |
| const kws = keywords.slice(0, 3).map(k => k.word); | |
| const ctas = [ | |
| '💬 Me conta o que você achou nos comentários!', | |
| '👇 Qual foi o ponto que mais te chamou atenção?', | |
| '🔁 Salva esse conteúdo para não perder!', | |
| '📲 Manda pra um amigo que precisa ver isso!', | |
| '✨ Segue para mais conteúdo como esse!', | |
| ]; | |
| const cta = ctas[Math.floor(Math.random() * ctas.length)]; | |
| const topHashtags = hashtags.slice(0, 15).join(' '); | |
| return `${hook} | |
| ✨ Neste conteúdo você vai ver: | |
| ${kws.map(k => `▸ ${k.charAt(0).toUpperCase() + k.slice(1)}`).join('\n')} | |
| ${summary} | |
| ${cta} | |
| . | |
| . | |
| . | |
| ${topHashtags}`; | |
| } | |
| // ── YouTube Kit ─────────────────────────────────────────────── | |
| function generateYouTubeKit(text, keywords, segments) { | |
| const kws = keywords.slice(0, 5).map(k => k.word); | |
| // Title variations | |
| const firstSentence = (text.match(/[^.!?]+[.!?]+/)?.[0] || '').trim().slice(0, 60); | |
| const titles = [ | |
| `${firstSentence || kws.slice(0,2).join(' e ')} | Conteúdo Completo`, | |
| `TUDO sobre ${kws[0] || 'esse tema'} que você precisa saber`, | |
| `${kws.slice(0,2).join(', ')} e muito mais — Veja agora!`, | |
| ]; | |
| // Description | |
| const summary = summarize(text, 3); | |
| const tags = keywords.slice(0, 15).map(k => k.word).join(', '); | |
| // Timestamps from segments | |
| let timestampBlock = ''; | |
| if (segments && segments.length > 0) { | |
| const interval = Math.max(1, Math.floor(segments.length / 8)); | |
| const picks = segments.filter((_, i) => i % interval === 0).slice(0, 8); | |
| timestampBlock = '\n\n⏱️ CAPÍTULOS:\n' + picks.map(s => `${fmtTime(s.start)} ${s.text.slice(0, 50)}`).join('\n'); | |
| } | |
| const description = `📌 ${summary} | |
| ${timestampBlock} | |
| 🔔 Inscreva-se no canal e ative o sininho para não perder nenhum vídeo! | |
| 👍 Se o conteúdo foi útil, deixa o like — ajuda muito! | |
| 💬 Tem alguma dúvida? Comenta aqui embaixo! | |
| ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| 🔗 Minhas redes sociais: | |
| ▸ Instagram: @seuinstagram | |
| ▸ TikTok: @seutiktok | |
| ▸ Twitter: @seutwitter | |
| ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| #${kws.join(' #')}`; | |
| return { titles, description, tags }; | |
| } | |
| // ── Twitter/X Thread ────────────────────────────────────────── | |
| function generateTwitterThread(text, keywords) { | |
| const sentences = (text.match(/[^.!?]+[.!?]+/g) || [text]).map(s => s.trim()).filter(Boolean); | |
| const summary = summarize(text, 6); | |
| const sumSentences = (summary.match(/[^.!?]+[.!?]+/g) || [summary]).map(s => s.trim()).filter(Boolean); | |
| const tweets = []; | |
| const kws = keywords.slice(0, 3).map(k => k.word); | |
| // Tweet 1: hook | |
| tweets.push(`🧵 ${sentences[0] || kws.join(', ')}\n\nThread completa 👇`); | |
| // Middle tweets: pack sentences into 250 chars each | |
| let current = ''; | |
| for (const s of sumSentences) { | |
| if ((current + ' ' + s).length > 250) { | |
| if (current) tweets.push(current.trim()); | |
| current = s; | |
| } else { | |
| current = current ? current + ' ' + s : s; | |
| } | |
| } | |
| if (current) tweets.push(current.trim()); | |
| // Last tweet: CTA | |
| tweets.push(`📌 Gostou desse conteúdo?\n\n↩️ RT para compartilhar\n❤️ Like se foi útil\n🔔 Siga para mais\n\n#${kws.slice(0,3).join(' #')}`); | |
| return tweets.map((t, i) => `${i + 1}/${tweets.length}\n\n${t}`); | |
| } | |
| // ── LinkedIn Post ───────────────────────────────────────────── | |
| function generateLinkedIn(text, keywords) { | |
| const sentences = (text.match(/[^.!?]+[.!?]+/g) || [text]).map(s => s.trim()); | |
| const kws = keywords.slice(0, 5); | |
| const summary = summarize(text, 4); | |
| return `💡 ${sentences[0] || 'Reflexão importante'} | |
| ${summary} | |
| 3 pontos principais sobre ${kws[0]?.word || 'esse tema'}: | |
| → ${sentences[1]?.trim() || kws[0]?.word || ''} | |
| → ${sentences[2]?.trim() || kws[1]?.word || ''} | |
| → ${sentences[3]?.trim() || kws[2]?.word || ''} | |
| O que você pensa sobre isso? Compartilhe nos comentários. 👇 | |
| ━━━━━━━━━━━━━━━━━━━━━ | |
| ${kws.slice(0, 5).map(k => `#${k.word}`).join(' ')} #linkedin #networking #conteúdo`; | |
| } | |
| // ── Blog Post ───────────────────────────────────────────────── | |
| function generateBlogPost(text, keywords, title) { | |
| const sentences = (text.match(/[^.!?]+[.!?]+/g) || [text]).map(s => s.trim()).filter(Boolean); | |
| const kws = keywords.slice(0, 6).map(k => k.word); | |
| const postTitle = title || `${kws[0]?.charAt(0).toUpperCase() + kws[0]?.slice(1) || 'Conteúdo'}: tudo o que você precisa saber`; | |
| // Split into chunks of ~3 sentences per section | |
| const sections = []; | |
| let chunk = []; | |
| for (const s of sentences) { | |
| chunk.push(s); | |
| if (chunk.length === 3) { sections.push(chunk.join(' ')); chunk = []; } | |
| } | |
| if (chunk.length) sections.push(chunk.join(' ')); | |
| const sectionTitles = [ | |
| 'Introdução', 'Por que isso importa', 'Principais pontos', | |
| 'Como aplicar na prática', 'Dicas e insights', 'Conclusão', | |
| ]; | |
| let post = `# ${postTitle}\n\n`; | |
| post += `*Tempo de leitura: ~${Math.ceil(text.split(/\s+/).length / 200)} min*\n\n`; | |
| post += `---\n\n`; | |
| sections.slice(0, 5).forEach((section, i) => { | |
| post += `## ${sectionTitles[i] || `Parte ${i + 1}`}\n\n${section}\n\n`; | |
| }); | |
| post += `## Conclusão\n\n${summarize(text, 2)}\n\n`; | |
| post += `---\n\n**Tags:** ${kws.join(', ')}`; | |
| return post; | |
| } | |
| // ── Main analyze function ───────────────────────────────────── | |
| function analyze(text, segments = [], videoTitle = '') { | |
| if (!text || text.trim().length < 10) throw new Error('Texto muito curto para análise.'); | |
| const keywords = extractKeywords(text, 20); | |
| const hashtags = generateHashtags(text, keywords); | |
| const summary = summarize(text, 4); | |
| const instagram = generateInstagramCaption(text, keywords, hashtags); | |
| const yt = generateYouTubeKit(text, keywords, segments); | |
| const thread = generateTwitterThread(text, keywords); | |
| const linkedin = generateLinkedIn(text, keywords); | |
| const blog = generateBlogPost(text, keywords, videoTitle); | |
| const wordCount = text.trim().split(/\s+/).length; | |
| const readingTime = Math.ceil(wordCount / 200); | |
| return { | |
| summary, | |
| keywords: keywords.slice(0, 12), | |
| hashtags, | |
| instagram, | |
| youtube: yt, | |
| twitter: thread, | |
| linkedin, | |
| blog, | |
| stats: { wordCount, readingTime, sentenceCount: (text.match(/[.!?]+/g) || []).length }, | |
| }; | |
| } | |
| function fmtTime(s) { | |
| const m = Math.floor(s / 60), sec = Math.floor(s % 60).toString().padStart(2,'0'); | |
| return `${m}:${sec}`; | |
| } | |
| // ══════════════════════════════════════════════════════════════ | |
| // STANDALONE CREATOR TOOLS (topic-based, no video needed) | |
| // ══════════════════════════════════════════════════════════════ | |
| function pick(arr, n) { | |
| const copy = [...arr]; | |
| const out = []; | |
| while (out.length < n && copy.length) { | |
| out.push(copy.splice(Math.floor(Math.random() * copy.length), 1)[0]); | |
| } | |
| return out; | |
| } | |
| const cap = s => s ? s.charAt(0).toUpperCase() + s.slice(1) : s; | |
| // ── 🪝 HOOKS ────────────────────────────────────────────────── | |
| function generateHooks(topic) { | |
| if (!topic || !topic.trim()) throw new Error('Informe um tópico.'); | |
| const t = topic.trim().toLowerCase(); | |
| const T = cap(t); | |
| const templates = [ | |
| `Pare de errar em ${t} — faça assim a partir de hoje.`, | |
| `Ninguém te conta a verdade sobre ${t}...`, | |
| `Eu perdi anos errando em ${t}. Não cometa o mesmo erro.`, | |
| `3 verdades sobre ${t} que mudaram tudo pra mim.`, | |
| `O segredo de ${t} que os experts não compartilham.`, | |
| `Como eu dominei ${t} em apenas 30 dias.`, | |
| `A maioria das pessoas erra em ${t}. Veja como acertar.`, | |
| `${T} não é o que você imagina. Olha só isso.`, | |
| `Faça ISSO antes de tentar qualquer coisa com ${t}.`, | |
| `O maior mito sobre ${t} — desmascarado.`, | |
| `Se você quer resultado com ${t}, assista até o final.`, | |
| `Eu testei tudo sobre ${t} pra você não precisar.`, | |
| `Esse erro em ${t} está te custando caro (e você nem percebe).`, | |
| `${T} em 60 segundos: o guia que você precisava.`, | |
| `Por que 90% das pessoas falham em ${t}?`, | |
| `O que eu faria se tivesse que começar do zero em ${t}.`, | |
| ]; | |
| return { topic, hooks: pick(templates, 10) }; | |
| } | |
| // ── 💡 CONTENT IDEAS ────────────────────────────────────────── | |
| function generateIdeas(niche) { | |
| if (!niche || !niche.trim()) throw new Error('Informe seu nicho.'); | |
| const n = niche.trim().toLowerCase(); | |
| const N = cap(n); | |
| const ideas = [ | |
| { fmt: 'Reels', icon: '🎬', title: `Top 5 erros que iniciantes cometem em ${n}` }, | |
| { fmt: 'Carrossel', icon: '🖼️', title: `Mitos vs verdades sobre ${n}` }, | |
| { fmt: 'Reels', icon: '🎬', title: `Um dia na vida de quem trabalha com ${n}` }, | |
| { fmt: 'YouTube', icon: '▶️', title: `Tutorial passo a passo de ${n} para iniciantes` }, | |
| { fmt: 'Carrossel', icon: '🖼️', title: `O que eu gostaria de saber antes de começar em ${n}` }, | |
| { fmt: 'Story', icon: '📲', title: `Caixinha de perguntas: tudo sobre ${n}` }, | |
| { fmt: 'Reels', icon: '🎬', title: `Antes e depois: minha evolução em ${n}` }, | |
| { fmt: 'Carrossel', icon: '🖼️', title: `5 ferramentas essenciais para ${n}` }, | |
| { fmt: 'YouTube', icon: '▶️', title: `Reagindo às maiores tendências de ${n}` }, | |
| { fmt: 'Reels', icon: '🎬', title: `Desafio de 30 dias de ${n} — resultados reais` }, | |
| { fmt: 'Carrossel', icon: '🖼️', title: `Guia rápido: ${n} do zero ao avançado` }, | |
| { fmt: 'Reels', icon: '🎬', title: `3 hábitos que transformaram meu ${n}` }, | |
| { fmt: 'Story', icon: '📲', title: `Bastidores: como eu produzo conteúdo de ${n}` }, | |
| { fmt: 'YouTube', icon: '▶️', title: `${N}: vale a pena em 2026? Minha opinião sincera` }, | |
| { fmt: 'Carrossel', icon: '🖼️', title: `Os 7 termos de ${n} que todo mundo deveria saber` }, | |
| ]; | |
| return { niche, ideas: pick(ideas, 9) }; | |
| } | |
| // ── ✍️ BIO GENERATOR ────────────────────────────────────────── | |
| function generateBio({ name = '', niche = '', cta = '' }) { | |
| if (!niche.trim()) throw new Error('Informe seu nicho.'); | |
| const n = niche.trim(); | |
| const link = cta.trim() || '👇 Link abaixo'; | |
| const bios = [ | |
| `✨ ${name || 'Criador(a)'} | ${cap(n)}\n📍 Conteúdo diário sobre ${n.toLowerCase()}\n💡 Dicas que realmente funcionam\n${link}`, | |
| `${cap(n)} descomplicado 🚀\nAjudo você a evoluir em ${n.toLowerCase()}\n🎯 Novo conteúdo toda semana\n${link}`, | |
| `${name ? name + ' • ' : ''}Apaixonado(a) por ${n.toLowerCase()} 💜\nTransformando conhecimento em resultado\n📲 Me segue pra não perder nada\n${link}`, | |
| `Seu cantinho de ${n.toLowerCase()} 🌟\n✅ Conteúdo prático e direto\n✅ Sem enrolação\n${link}`, | |
| ]; | |
| return { bios }; | |
| } | |
| // ── 📅 CONTENT CALENDAR ─────────────────────────────────────── | |
| function generateCalendar({ niche = '', perWeek = 5 }) { | |
| if (!niche.trim()) throw new Error('Informe seu nicho.'); | |
| const n = niche.trim().toLowerCase(); | |
| const days = ['Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado', 'Domingo']; | |
| const blocks = [ | |
| { icon: '🎬', fmt: 'Reels', theme: `Dica rápida de ${n}`, best: '12h ou 19h' }, | |
| { icon: '🖼️', fmt: 'Carrossel', theme: `Passo a passo sobre ${n}`, best: '18h' }, | |
| { icon: '📲', fmt: 'Story', theme: `Bastidores + enquete de ${n}`, best: 'Ao longo do dia' }, | |
| { icon: '🎬', fmt: 'Reels', theme: `Mito vs verdade de ${n}`, best: '20h' }, | |
| { icon: '▶️', fmt: 'YouTube', theme: `Vídeo aprofundado de ${n}`, best: 'Sábado 10h' }, | |
| { icon: '🖼️', fmt: 'Carrossel', theme: `Lista: top ferramentas de ${n}`, best: '17h' }, | |
| { icon: '📲', fmt: 'Story', theme: `Caixinha de perguntas de ${n}`, best: 'Domingo' }, | |
| ]; | |
| const count = Math.min(Math.max(parseInt(perWeek) || 5, 1), 7); | |
| const plan = days.slice(0, count).map((d, i) => ({ day: d, ...blocks[i % blocks.length] })); | |
| return { niche, plan }; | |
| } | |
| // ── 📸 CAPTION FROM TOPIC ───────────────────────────────────── | |
| function generateCaptionFromTopic(topic) { | |
| if (!topic || !topic.trim()) throw new Error('Informe um tópico.'); | |
| const t = topic.trim(); | |
| const keywords = extractKeywords(t + ' ' + t, 10); | |
| const hashtags = generateHashtags(t, keywords); | |
| const ctas = [ | |
| '💬 Comenta aqui o que você achou!', | |
| '🔁 Salva esse post para não esquecer!', | |
| '📲 Marca um amigo que precisa ver isso!', | |
| '✨ Segue pra mais conteúdo como esse!', | |
| ]; | |
| const caption = `${cap(t)} 🚀\n\nVocê sabia disso? Deixa eu te explicar de um jeito simples 👇\n\n▸ Ponto 1 sobre ${t.toLowerCase()}\n▸ Ponto 2 que faz a diferença\n▸ Ponto 3 que ninguém te conta\n\n${pick(ctas, 1)[0]}\n\n.\n.\n.\n${hashtags.slice(0, 15).join(' ')}`; | |
| return { caption, hashtags }; | |
| } | |
| // ── 📝 SCRIPT / ROTEIRO ─────────────────────────────────────── | |
| function generateScript({ topic = '', format = 'reels' }) { | |
| if (!topic.trim()) throw new Error('Informe o tema do vídeo.'); | |
| const t = topic.trim().toLowerCase(); | |
| const T = cap(t); | |
| const hooks = [ | |
| `Pare tudo: se você se interessa por ${t}, precisa ver isso.`, | |
| `Ninguém te conta a verdade sobre ${t}...`, | |
| `Eu errei MUITO em ${t} até descobrir isso.`, | |
| `Esse erro em ${t} está te custando caro.`, | |
| `${T} em 30 segundos — presta atenção.`, | |
| ]; | |
| const ctas = [ | |
| `Salva esse vídeo pra não esquecer e segue pra mais conteúdo sobre ${t}! 🚀`, | |
| `Comenta aqui embaixo a sua maior dúvida sobre ${t}. Eu respondo todo mundo! 💬`, | |
| `Marca aquele amigo que precisa ver isso e me segue pra mais dicas! 📲`, | |
| ]; | |
| const isYT = format === 'youtube'; | |
| const sections = [ | |
| { label: '🎣 Gancho (0–3s)', tip: 'Fale isso olhando pra câmera, com energia.', text: pick(hooks, 1)[0] }, | |
| { label: '🎬 Contexto (3–10s)', tip: 'Mostre que você entende a dor de quem assiste.', text: `Se você sente que ${t} é complicado ou já tentou e não deu certo, relaxa — eu vou simplificar tudo pra você agora.` }, | |
| { label: '📍 Ponto 1', tip: 'Primeira ideia prática.', text: `O primeiro passo em ${t} é entender o fundamento. A maioria pula essa parte e é por isso que trava.` }, | |
| { label: '📍 Ponto 2', tip: 'Segunda ideia, aprofunde.', text: `Depois disso, foque em consistência. Pequenas ações repetidas valem mais do que grandes esforços isolados.` }, | |
| { label: '📍 Ponto 3', tip: 'Terceira ideia, a mais forte.', text: `E o pulo do gato: ${t} funciona muito melhor quando você acompanha os resultados e ajusta no caminho.` }, | |
| { label: '🔥 Virada', tip: 'O insight que prende até o fim.', text: `Mas o que quase ninguém te conta é que ${t} não é sobre fazer mais — é sobre fazer o certo, de forma constante.` }, | |
| { label: '📢 Chamada pra ação', tip: 'Diga exatamente o que fazer agora.', text: pick(ctas, 1)[0] }, | |
| ]; | |
| if (isYT) { | |
| sections.splice(2, 0, { label: '👋 Intro do canal (10–20s)', tip: 'Só no YouTube — apresente-se rápido.', text: `Se é a primeira vez aqui no canal, seja muito bem-vindo! Aqui a gente fala sobre ${t} de um jeito direto e sem enrolação. Bora?` }); | |
| } | |
| const full = sections.map(s => `${s.label}\n${s.text}`).join('\n\n'); | |
| const wordCount = full.split(/\s+/).length; | |
| const estSeconds = Math.round(wordCount / 2.5); // ~150 wpm falado | |
| return { topic, format, sections, full, estSeconds }; | |
| } | |
| // ── #️⃣ HASHTAGS FROM TOPIC ──────────────────────────────────── | |
| function generateHashtagsFromTopic(topic) { | |
| if (!topic || !topic.trim()) throw new Error('Informe um tópico.'); | |
| const keywords = extractKeywords(topic + ' ' + topic, 12); | |
| const hashtags = generateHashtags(topic, keywords); | |
| return { hashtags }; | |
| } | |
| // ── DISPATCHER ──────────────────────────────────────────────── | |
| function generateTools(type, params = {}) { | |
| switch (type) { | |
| case 'hooks': return generateHooks(params.topic); | |
| case 'ideas': return generateIdeas(params.niche); | |
| case 'bio': return generateBio(params); | |
| case 'calendar': return generateCalendar(params); | |
| case 'caption': return generateCaptionFromTopic(params.topic); | |
| case 'script': return generateScript(params); | |
| case 'hashtags': return generateHashtagsFromTopic(params.topic); | |
| default: throw new Error('Ferramenta desconhecida: ' + type); | |
| } | |
| } | |
| module.exports = { analyze, generateTools }; | |