| |
|
|
| |
| |
| |
| const AUTH_CONFIG = { |
| username: "robiul", |
| password: "Robi1234", |
| storageKey: "ncakit_logged_in" |
| }; |
|
|
| |
| |
| |
| function checkAuth() { |
| return localStorage.getItem(AUTH_CONFIG.storageKey) === "true"; |
| } |
|
|
| function showApp() { |
| document.getElementById('loginOverlay').classList.add('hidden'); |
| document.getElementById('mainApp').classList.remove('hidden'); |
| } |
|
|
| function showLogin() { |
| document.getElementById('loginOverlay').classList.remove('hidden'); |
| document.getElementById('mainApp').classList.add('hidden'); |
| } |
|
|
| function handleLogin(e) { |
| e.preventDefault(); |
| const username = document.getElementById('loginUsername').value.trim(); |
| const password = document.getElementById('loginPassword').value; |
| const errorEl = document.getElementById('loginError'); |
|
|
| |
| console.log('Login attempt:', { username, passwordLength: password.length }); |
| console.log('Expected:', { username: AUTH_CONFIG.username, password: AUTH_CONFIG.password }); |
|
|
| if (username === AUTH_CONFIG.username && password === AUTH_CONFIG.password) { |
| console.log('Login SUCCESS!'); |
| localStorage.setItem(AUTH_CONFIG.storageKey, "true"); |
| errorEl.classList.add('hidden'); |
| showApp(); |
| } else { |
| console.log('Login FAILED - credentials mismatch'); |
| errorEl.textContent = "β Incorrect username or password"; |
| errorEl.classList.remove('hidden'); |
| } |
| } |
|
|
| function handleLogout() { |
| localStorage.removeItem(AUTH_CONFIG.storageKey); |
| showLogin(); |
| |
| document.getElementById('loginUsername').value = ''; |
| document.getElementById('loginPassword').value = ''; |
| } |
|
|
| |
| document.addEventListener('DOMContentLoaded', function () { |
|
|
| |
| |
| |
| if (checkAuth()) { |
| showApp(); |
| } else { |
| showLogin(); |
| } |
|
|
| |
| document.getElementById('loginForm').addEventListener('submit', handleLogin); |
|
|
| |
| document.getElementById('logoutBtn').addEventListener('click', handleLogout); |
|
|
| |
| |
| |
| document.querySelectorAll('.tab-btn').forEach(btn => { |
| btn.addEventListener('click', () => { |
| document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); |
| document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); |
| btn.classList.add('active'); |
| document.getElementById(btn.dataset.tab + '-tab').classList.add('active'); |
| }); |
| }); |
|
|
| |
| |
| |
| document.getElementById('storyForm').addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| const status = document.getElementById('storyStatus'); |
| status.className = 'status processing'; |
| status.innerHTML = 'β³ Starting generation...'; |
| status.classList.remove('hidden'); |
|
|
| const data = { |
| script: document.getElementById('storyScript').value, |
| image_style: document.getElementById('storyStyle').value, |
| voice: document.getElementById('storyVoice').value |
| }; |
|
|
| try { |
| const res = await fetch('/api/story/story-reel', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(data) |
| }); |
| const result = await res.json(); |
|
|
| if (result.job_id) { |
| status.innerHTML = `β
Job started! ID: ${result.job_id}<br>Checking status...`; |
| pollStatus(result.job_id, 'story'); |
| } |
| } catch (err) { |
| status.className = 'status error'; |
| status.innerHTML = 'β Error: ' + err.message; |
| } |
| }); |
|
|
| |
| async function pollStatus(jobId, type) { |
| const status = document.getElementById(type + 'Status'); |
| const endpoint = type === 'story' ? '/api/story/story-reel/' : '/api/video/short-video/'; |
|
|
| const check = async () => { |
| try { |
| const res = await fetch(endpoint + jobId + '/status'); |
| const data = await res.json(); |
|
|
| const progress = data.progress || 0; |
| status.innerHTML = ` |
| <div>Status: <strong>${data.status}</strong></div> |
| <div class="progress-bar"><div class="progress-fill" style="width: ${progress}%"></div></div> |
| `; |
|
|
| if (data.status === 'ready') { |
| status.className = 'status success'; |
| const downloadUrl = type === 'story' ? `/api/story/story-reel/${jobId}` : `/api/video/short-video/${jobId}`; |
| status.innerHTML += `<br><a href="${downloadUrl}" class="btn btn-primary" style="margin-top: 1rem; display: inline-block;">π₯ Download Video</a>`; |
| } else if (data.status === 'failed') { |
| status.className = 'status error'; |
| status.innerHTML = 'β Failed: ' + (data.error || 'Unknown error'); |
| } else { |
| setTimeout(check, 2000); |
| } |
| } catch (err) { |
| setTimeout(check, 3000); |
| } |
| }; |
| check(); |
| } |
|
|
| |
| |
| |
|
|
| |
| document.getElementById('addScene').addEventListener('click', () => { |
| const container = document.getElementById('scenesContainer'); |
| const count = container.querySelectorAll('.scene-text').length + 1; |
| container.innerHTML += ` |
| <div class="form-group"> |
| <label>Scene ${count} - Text</label> |
| <textarea class="scene-text" rows="2" placeholder="Enter narration..." required></textarea> |
| </div> |
| <div class="form-group"> |
| <label>Keywords</label> |
| <input type="text" class="scene-keywords" placeholder="nature, forest"> |
| </div> |
| `; |
| }); |
|
|
| |
| document.getElementById('videoForm').addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| const status = document.getElementById('videoStatus'); |
| status.className = 'status processing'; |
| status.innerHTML = 'β³ Creating video...'; |
| status.classList.remove('hidden'); |
|
|
| const scenes = []; |
| document.querySelectorAll('.scene-text').forEach((textarea, i) => { |
| const keywords = document.querySelectorAll('.scene-keywords')[i]?.value || ''; |
| scenes.push({ |
| text: textarea.value, |
| searchTerms: keywords.split(',').map(k => k.trim()).filter(k => k) |
| }); |
| }); |
|
|
| const data = { |
| scenes: scenes, |
| config: { |
| voice: document.getElementById('videoVoice').value, |
| music: document.getElementById('videoMusic').value || null |
| } |
| }; |
|
|
| try { |
| const res = await fetch('/api/video/short-video', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(data) |
| }); |
| const result = await res.json(); |
|
|
| if (result.videoId) { |
| status.innerHTML = `β
Video started! ID: ${result.videoId}`; |
| pollStatus(result.videoId, 'video'); |
| } |
| } catch (err) { |
| status.className = 'status error'; |
| status.innerHTML = 'β Error: ' + err.message; |
| } |
| }); |
|
|
| |
| |
| |
| document.getElementById('factForm').addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| const status = document.getElementById('factStatus'); |
| status.className = 'status processing'; |
| status.innerHTML = 'β³ Generating fact video...'; |
| status.classList.remove('hidden'); |
|
|
| const heading = document.getElementById('factHeading').value.trim(); |
|
|
| const data = { |
| model: document.getElementById('factModel').value, |
| image_prompt: document.getElementById('factImagePrompt').value, |
| fact_text: document.getElementById('factText').value, |
| duration: parseInt(document.getElementById('factDuration').value) |
| }; |
|
|
| if (heading) { |
| data.fact_heading = heading; |
| data.heading_background = { |
| enabled: true, |
| color: document.getElementById('headingBgColor').value, |
| padding: 22, |
| corner_radius: parseInt(document.getElementById('headingBgRadius').value) |
| }; |
| } |
|
|
| try { |
| const res = await fetch('/api/fact-image/', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(data) |
| }); |
| const result = await res.json(); |
|
|
| if (res.ok && result.job_id) { |
| status.innerHTML = `β
Job started! ID: ${result.job_id}<br>Checking status...`; |
| pollFactStatus(result.job_id); |
| } else { |
| status.className = 'status error'; |
| |
| let errorMsg = result.detail || 'Unknown error'; |
| if (Array.isArray(result.detail)) { |
| errorMsg = result.detail.map(e => e.msg).join(', '); |
| } |
| status.innerHTML = 'β Error: ' + errorMsg; |
| } |
| } catch (err) { |
| status.className = 'status error'; |
| status.innerHTML = 'β Error: ' + err.message; |
| } |
| }); |
|
|
| async function pollFactStatus(jobId) { |
| const status = document.getElementById('factStatus'); |
|
|
| const check = async () => { |
| try { |
| const res = await fetch(`/api/fact-image/${jobId}/status`); |
| const data = await res.json(); |
|
|
| const progress = data.progress || 0; |
| status.innerHTML = ` |
| <div>Status: <strong>${data.status}</strong></div> |
| <div class="progress-bar"><div class="progress-fill" style="width: ${progress}%"></div></div> |
| `; |
|
|
| if (data.status === 'ready') { |
| status.className = 'status success'; |
| status.innerHTML += `<br><a href="/api/fact-image/${jobId}" class="btn btn-primary" style="margin-top: 1rem; display: inline-block;">π₯ Download Video</a>`; |
| } else if (data.status === 'failed') { |
| status.className = 'status error'; |
| status.innerHTML = 'β Failed: ' + (data.error || 'Unknown error'); |
| } else { |
| setTimeout(check, 2000); |
| } |
| } catch (err) { |
| setTimeout(check, 3000); |
| } |
| }; |
| check(); |
| } |
|
|
| |
| |
| |
| document.getElementById('trendingForm').addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| const results = document.getElementById('trendingResults'); |
| results.innerHTML = '<p>β³ Loading trends...</p>'; |
|
|
| const data = { |
| country: document.getElementById('trendCountry').value, |
| limit: parseInt(document.getElementById('trendLimit').value) |
| }; |
|
|
| try { |
| const res = await fetch('/api/trends/trending-now', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(data) |
| }); |
| const result = await res.json(); |
|
|
| if (result.success) { |
| let html = '<div style="background: var(--bg-secondary); border-radius: 8px; padding: 1rem; max-height: 400px; overflow-y: auto;">'; |
| result.trends.forEach(t => { |
| html += `<div style="padding: 0.5rem 0; border-bottom: 1px solid var(--border);"> |
| <span style="color: var(--accent); font-weight: bold;">#${t.rank}</span> ${t.topic} |
| </div>`; |
| }); |
| html += '</div>'; |
| results.innerHTML = html; |
| } else { |
| results.innerHTML = '<p style="color: var(--error);">β Error: ' + (result.detail || 'Failed') + '</p>'; |
| } |
| } catch (err) { |
| results.innerHTML = '<p style="color: var(--error);">β Error: ' + err.message + '</p>'; |
| } |
| }); |
|
|
| document.getElementById('keywordForm').addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| const results = document.getElementById('keywordResults'); |
| results.innerHTML = '<p>β³ Analyzing keyword...</p>'; |
|
|
| const data = { |
| keyword: document.getElementById('researchKeyword').value, |
| region: document.getElementById('researchRegion').value, |
| timeframe: document.getElementById('researchTimeframe').value, |
| category: document.getElementById('researchCategory').value, |
| search_type: document.getElementById('researchType').value |
| }; |
|
|
| try { |
| const res = await fetch('/api/trends/keyword-research', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(data) |
| }); |
| const result = await res.json(); |
|
|
| if (result.success) { |
| let html = '<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">'; |
|
|
| html += '<div style="background: var(--bg-secondary); border-radius: 8px; padding: 1rem;">'; |
| html += '<h4 style="margin-bottom: 0.5rem;">π Related Topics</h4>'; |
| if (result.related_topics.top && result.related_topics.top.length > 0) { |
| result.related_topics.top.slice(0, 10).forEach(t => { |
| html += `<div style="padding: 0.25rem 0; font-size: 0.9rem;">${t.topic} <span style="color: var(--text-secondary);">(${t.value})</span></div>`; |
| }); |
| } else { |
| html += '<p style="color: var(--text-secondary); font-size: 0.9rem;">No data</p>'; |
| } |
| html += '</div>'; |
|
|
| html += '<div style="background: var(--bg-secondary); border-radius: 8px; padding: 1rem;">'; |
| html += '<h4 style="margin-bottom: 0.5rem;">π Related Queries</h4>'; |
| if (result.related_queries.top && result.related_queries.top.length > 0) { |
| result.related_queries.top.slice(0, 10).forEach(q => { |
| html += `<div style="padding: 0.25rem 0; font-size: 0.9rem;">${q.query} <span style="color: var(--text-secondary);">(${q.value})</span></div>`; |
| }); |
| } else { |
| html += '<p style="color: var(--text-secondary); font-size: 0.9rem;">No data</p>'; |
| } |
| html += '</div>'; |
|
|
| html += '</div>'; |
| results.innerHTML = html; |
| } else { |
| results.innerHTML = '<p style="color: var(--error);">β Error: ' + (result.detail || 'Failed') + '</p>'; |
| } |
| } catch (err) { |
| results.innerHTML = '<p style="color: var(--error);">β Error: ' + err.message + '</p>'; |
| } |
| }); |
|
|
| |
| |
| |
| let quizCount = 1; |
|
|
| document.getElementById('addQuizBtn').addEventListener('click', () => { |
| quizCount++; |
| const container = document.getElementById('quizContainer'); |
| const quizHtml = ` |
| <div class="quiz-item" style="background: var(--bg-secondary); padding: 1.5rem; border-radius: 12px; margin-bottom: 1rem;"> |
| <h4 style="color: var(--accent); margin-bottom: 1rem;">Quiz ${quizCount}</h4> |
| <div class="form-group"> |
| <label>Hook (Category)</label> |
| <input type="text" class="quiz-hook" placeholder="e.g., IQ TEST, MATH QUIZ"> |
| </div> |
| <div class="form-group"> |
| <label>Question *</label> |
| <input type="text" class="quiz-question" placeholder="Enter your question" required> |
| </div> |
| <div class="form-row"> |
| <div class="form-group"> |
| <label>Option A *</label> |
| <input type="text" class="quiz-option-a" placeholder="Answer A" required> |
| </div> |
| <div class="form-group"> |
| <label>Option B *</label> |
| <input type="text" class="quiz-option-b" placeholder="Answer B" required> |
| </div> |
| <div class="form-group"> |
| <label>Option C *</label> |
| <input type="text" class="quiz-option-c" placeholder="Answer C" required> |
| </div> |
| </div> |
| <div class="form-row"> |
| <div class="form-group"> |
| <label>Correct Answer *</label> |
| <select class="quiz-correct" required> |
| <option value="A">A</option> |
| <option value="B">B</option> |
| <option value="C">C</option> |
| </select> |
| </div> |
| </div> |
| <div class="form-group"> |
| <label>Explain (shown after answer)</label> |
| <input type="text" class="quiz-explain" placeholder="Brief explanation"> |
| </div> |
| </div> |
| `; |
| container.insertAdjacentHTML('beforeend', quizHtml); |
| }); |
|
|
| document.getElementById('removeQuizBtn').addEventListener('click', () => { |
| const container = document.getElementById('quizContainer'); |
| const items = container.querySelectorAll('.quiz-item'); |
| if (items.length > 1) { |
| items[items.length - 1].remove(); |
| quizCount--; |
| } |
| }); |
|
|
| async function pollQuizStatus(jobId, statusDiv) { |
| const checkStatus = async () => { |
| try { |
| const res = await fetch(`/api/quiz/${jobId}/status`); |
| const status = await res.json(); |
|
|
| if (status.status === 'ready') { |
| statusDiv.className = 'status success'; |
| statusDiv.innerHTML = `β
Video Ready! <a href="${status.video_url}" target="_blank" style="color: var(--accent);">Download Video</a>`; |
| return; |
| } else if (status.status === 'failed') { |
| statusDiv.className = 'status error'; |
| statusDiv.innerHTML = `β Failed: ${status.error}`; |
| return; |
| } else { |
| statusDiv.innerHTML = `β³ ${status.status}... ${status.progress}%`; |
| setTimeout(checkStatus, 2000); |
| } |
| } catch (err) { |
| statusDiv.className = 'status error'; |
| statusDiv.innerHTML = `β Error: ${err.message}`; |
| } |
| }; |
| checkStatus(); |
| } |
|
|
| document.getElementById('quizForm').addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| const status = document.getElementById('quizStatus'); |
| status.className = 'status'; |
| status.classList.remove('hidden'); |
| status.innerHTML = 'β³ Starting quiz video generation...'; |
|
|
| const quizItems = document.querySelectorAll('.quiz-item'); |
| const quizzes = []; |
|
|
| quizItems.forEach(item => { |
| quizzes.push({ |
| hook: item.querySelector('.quiz-hook').value || '', |
| question: item.querySelector('.quiz-question').value, |
| options: { |
| A: item.querySelector('.quiz-option-a').value, |
| B: item.querySelector('.quiz-option-b').value, |
| C: item.querySelector('.quiz-option-c').value |
| }, |
| correct: item.querySelector('.quiz-correct').value, |
| explain: item.querySelector('.quiz-explain').value || '' |
| }); |
| }); |
|
|
| const data = { |
| quizzes: quizzes, |
| voice: document.getElementById('quizVoice').value |
| }; |
|
|
| try { |
| const res = await fetch('/api/quiz/generate', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(data) |
| }); |
| const result = await res.json(); |
|
|
| if (result.job_id) { |
| status.innerHTML = `β³ Job started: ${result.job_id} (${quizzes.length} quizzes)`; |
| pollQuizStatus(result.job_id, status); |
| } else { |
| status.className = 'status error'; |
| status.innerHTML = `β Error: ${result.detail || 'Failed to start'}`; |
| } |
| } catch (err) { |
| status.className = 'status error'; |
| status.innerHTML = 'β Error: ' + err.message; |
| } |
| }); |
|
|
| |
| |
| |
| document.getElementById('tsAddMessage').addEventListener('click', () => { |
| const container = document.getElementById('tsMessagesContainer'); |
| const count = container.querySelectorAll('.ts-message-item').length + 1; |
| const html = ` |
| <div class="ts-message-item" style="background: var(--bg-secondary); padding: 1rem; border-radius: 8px; margin-bottom: 0.5rem;"> |
| <div class="form-row" style="align-items: flex-end;"> |
| <div class="form-group" style="flex: 0 0 100px;"> |
| <label>Sender</label> |
| <select class="ts-sender"> |
| <option value="B">B (Other)</option> |
| <option value="A">A (You)</option> |
| </select> |
| </div> |
| <div class="form-group" style="flex: 1;"> |
| <label>Message ${count}</label> |
| <input type="text" class="ts-text" placeholder="Type message..." required> |
| </div> |
| <button type="button" class="btn btn-secondary ts-remove" style="height: 42px;">β</button> |
| </div> |
| </div> |
| `; |
| container.insertAdjacentHTML('beforeend', html); |
| }); |
|
|
| document.getElementById('tsMessagesContainer').addEventListener('click', (e) => { |
| if (e.target.classList.contains('ts-remove')) { |
| const items = document.querySelectorAll('.ts-message-item'); |
| if (items.length > 1) { |
| e.target.closest('.ts-message-item').remove(); |
| } |
| } |
| }); |
|
|
| |
| window.toggleTsMode = function () { |
| const mode = document.querySelector('input[name="tsMode"]:checked').value; |
| document.getElementById('tsAiSection').style.display = mode === 'ai' ? 'block' : 'none'; |
| document.getElementById('tsManualSection').style.display = mode === 'manual' ? 'block' : 'none'; |
| }; |
|
|
| document.getElementById('textStoryForm').addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| const status = document.getElementById('textStoryStatus'); |
| status.className = 'status processing'; |
| status.classList.remove('hidden'); |
|
|
| const mode = document.querySelector('input[name="tsMode"]:checked').value; |
| let messages = []; |
|
|
| if (mode === 'ai') { |
| const prompt = document.getElementById('tsAiPrompt').value.trim(); |
| if (!prompt) { |
| status.className = 'status error'; |
| status.innerHTML = 'β Please enter a prompt for AI!'; |
| return; |
| } |
|
|
| status.innerHTML = 'π€ AI generating conversation...'; |
|
|
| try { |
| const aiRes = await fetch('/api/text-story/ai-generate', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| prompt: prompt, |
| person_a_name: document.getElementById('tsPersonA').value || 'You', |
| person_b_name: document.getElementById('tsPersonB').value || 'My Ex', |
| message_count: parseInt(document.getElementById('tsAiMsgCount').value), |
| tone: document.getElementById('tsAiTone').value |
| }) |
| }); |
| const aiData = await aiRes.json(); |
|
|
| if (aiData.messages) { |
| messages = aiData.messages; |
| status.innerHTML = `π€ Generated ${messages.length} messages. Now creating video...`; |
| } else { |
| status.className = 'status error'; |
| status.innerHTML = 'β AI failed: ' + (aiData.detail || 'Unknown error'); |
| return; |
| } |
| } catch (err) { |
| status.className = 'status error'; |
| status.innerHTML = 'β AI Error: ' + err.message; |
| return; |
| } |
| } else { |
| const messageItems = document.querySelectorAll('.ts-message-item'); |
| messageItems.forEach(item => { |
| const sender = item.querySelector('.ts-sender').value; |
| const text = item.querySelector('.ts-text').value.trim(); |
| if (text) { |
| messages.push({ sender, text }); |
| } |
| }); |
|
|
| if (messages.length < 2) { |
| status.className = 'status error'; |
| status.innerHTML = 'β Need at least 2 messages!'; |
| return; |
| } |
| } |
|
|
| status.innerHTML = 'β³ Starting video generation...'; |
|
|
| const data = { |
| person_a_name: document.getElementById('tsPersonA').value || 'You', |
| person_b_name: document.getElementById('tsPersonB').value || 'My Ex', |
| person_b_avatar: document.getElementById('tsAvatar').value || null, |
| messages: messages, |
| ending_text: document.getElementById('tsEnding').value || null, |
| voice_a: document.getElementById('tsVoiceA').value, |
| voice_b: document.getElementById('tsVoiceB').value |
| }; |
|
|
| try { |
| const res = await fetch('/api/text-story/generate', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(data) |
| }); |
| const result = await res.json(); |
|
|
| if (result.job_id) { |
| status.innerHTML = `β³ Job started: ${result.job_id}`; |
| pollTextStoryStatus(result.job_id); |
| } else { |
| status.className = 'status error'; |
| status.innerHTML = `β Error: ${result.detail || 'Failed to start'}`; |
| } |
| } catch (err) { |
| status.className = 'status error'; |
| status.innerHTML = 'β Error: ' + err.message; |
| } |
| }); |
|
|
| async function pollTextStoryStatus(jobId) { |
| const status = document.getElementById('textStoryStatus'); |
| const poll = async () => { |
| try { |
| const res = await fetch(`/api/text-story/${jobId}/status`); |
| const data = await res.json(); |
|
|
| if (data.status === 'ready') { |
| status.className = 'status success'; |
| status.innerHTML = `β
Video ready! <a href="${data.video_url}" target="_blank" class="btn btn-primary" style="margin-left: 1rem;">π₯ Download</a>`; |
| } else if (data.status === 'failed') { |
| status.className = 'status error'; |
| status.innerHTML = 'β Failed: ' + (data.error || 'Unknown error'); |
| } else { |
| const step = data.current_step || 'Processing'; |
| status.innerHTML = `β³ ${step} (${data.progress}%)`; |
| setTimeout(poll, 2000); |
| } |
| } catch (err) { |
| setTimeout(poll, 3000); |
| } |
| }; |
| poll(); |
| } |
|
|
| |
| |
| |
| const chatBtn = document.getElementById('chatBtn'); |
| const chatModal = document.getElementById('chatModal'); |
| const chatClose = document.getElementById('chatClose'); |
| const chatForm = document.getElementById('chatForm'); |
| const chatInput = document.getElementById('chatInput'); |
| const chatMessages = document.getElementById('chatMessages'); |
|
|
| if (chatBtn) { |
| chatBtn.addEventListener('click', () => { |
| chatModal.classList.toggle('hidden'); |
| }); |
|
|
| chatClose.addEventListener('click', () => { |
| chatModal.classList.add('hidden'); |
| }); |
|
|
| chatForm.addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| const message = chatInput.value.trim(); |
| if (!message) return; |
|
|
| addMessage(message, 'user'); |
| chatInput.value = ''; |
|
|
| const loadingId = addMessage('β³ Thinking...', 'bot'); |
|
|
| try { |
| const res = await fetch('/api/chat', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ message }) |
| }); |
| const data = await res.json(); |
|
|
| document.getElementById(loadingId).remove(); |
|
|
| if (data.reply) { |
| addMessage(data.reply, 'bot'); |
| } else if (data.error) { |
| addMessage('β ' + data.error, 'bot'); |
| } |
| } catch (err) { |
| document.getElementById(loadingId).remove(); |
| addMessage('β Error: ' + err.message, 'bot'); |
| } |
| }); |
|
|
| function addMessage(text, type) { |
| const id = 'msg-' + Date.now(); |
| const div = document.createElement('div'); |
| div.id = id; |
| div.className = 'chat-message ' + type; |
| div.textContent = text; |
| chatMessages.appendChild(div); |
| chatMessages.scrollTop = chatMessages.scrollHeight; |
| return id; |
| } |
| } |
|
|
| }); |
|
|