/* NCAkit - Main JavaScript */
// ============================================
// 🔐 LOGIN CREDENTIALS (Easy to find if forgotten!)
// ============================================
const AUTH_CONFIG = {
username: "robiul",
password: "Robi1234",
storageKey: "ncakit_logged_in"
};
// ============================================
// AUTHENTICATION FUNCTIONS
// ============================================
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');
// Debug logging
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();
// Clear form
document.getElementById('loginUsername').value = '';
document.getElementById('loginPassword').value = '';
}
// Wait for DOM to be ready
document.addEventListener('DOMContentLoaded', function () {
// ============================================
// AUTHENTICATION CHECK ON PAGE LOAD
// ============================================
if (checkAuth()) {
showApp();
} else {
showLogin();
}
// Login form handler
document.getElementById('loginForm').addEventListener('submit', handleLogin);
// Logout button handler
document.getElementById('logoutBtn').addEventListener('click', handleLogout);
// ============================================
// TAB SWITCHING
// ============================================
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');
});
});
// ============================================
// STORY REELS MODULE
// ============================================
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}
Checking status...`;
pollStatus(result.job_id, 'story');
}
} catch (err) {
status.className = 'status error';
status.innerHTML = '❌ Error: ' + err.message;
}
});
// Generic poll status for story/video
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 = `
Status: ${data.status}
`;
if (data.status === 'ready') {
status.className = 'status success';
const downloadUrl = type === 'story' ? `/api/story/story-reel/${jobId}` : `/api/video/short-video/${jobId}`;
status.innerHTML += `
📥 Download Video`;
} 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();
}
// ============================================
// VIDEO CREATOR MODULE
// ============================================
// Add scene
document.getElementById('addScene').addEventListener('click', () => {
const container = document.getElementById('scenesContainer');
const count = container.querySelectorAll('.scene-text').length + 1;
container.innerHTML += `
`;
});
// Video Form
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;
}
});
// ============================================
// FACT IMAGE MODULE
// ============================================
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}
Checking status...`;
pollFactStatus(result.job_id);
} else {
status.className = 'status error';
// Show validation errors
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 = `
Status: ${data.status}
`;
if (data.status === 'ready') {
status.className = 'status success';
status.innerHTML += `
📥 Download Video`;
} 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();
}
// ============================================
// TRENDS MODULE
// ============================================
document.getElementById('trendingForm').addEventListener('submit', async (e) => {
e.preventDefault();
const results = document.getElementById('trendingResults');
results.innerHTML = '⏳ Loading trends...
';
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 = '';
result.trends.forEach(t => {
html += `
#${t.rank} ${t.topic}
`;
});
html += '
';
results.innerHTML = html;
} else {
results.innerHTML = '❌ Error: ' + (result.detail || 'Failed') + '
';
}
} catch (err) {
results.innerHTML = '❌ Error: ' + err.message + '
';
}
});
document.getElementById('keywordForm').addEventListener('submit', async (e) => {
e.preventDefault();
const results = document.getElementById('keywordResults');
results.innerHTML = '⏳ Analyzing keyword...
';
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 = '';
html += '
';
html += '
📌 Related Topics
';
if (result.related_topics.top && result.related_topics.top.length > 0) {
result.related_topics.top.slice(0, 10).forEach(t => {
html += `
${t.topic} (${t.value})
`;
});
} else {
html += '
No data
';
}
html += '
';
html += '
';
html += '
🔍 Related Queries
';
if (result.related_queries.top && result.related_queries.top.length > 0) {
result.related_queries.top.slice(0, 10).forEach(q => {
html += `
${q.query} (${q.value})
`;
});
} else {
html += '
No data
';
}
html += '
';
html += '
';
results.innerHTML = html;
} else {
results.innerHTML = '❌ Error: ' + (result.detail || 'Failed') + '
';
}
} catch (err) {
results.innerHTML = '❌ Error: ' + err.message + '
';
}
});
// ============================================
// QUIZ REEL MODULE
// ============================================
let quizCount = 1;
document.getElementById('addQuizBtn').addEventListener('click', () => {
quizCount++;
const container = document.getElementById('quizContainer');
const quizHtml = `
`;
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! Download Video`;
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;
}
});
// ============================================
// TEXT STORY MODULE
// ============================================
document.getElementById('tsAddMessage').addEventListener('click', () => {
const container = document.getElementById('tsMessagesContainer');
const count = container.querySelectorAll('.ts-message-item').length + 1;
const html = `
`;
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();
}
}
});
// Toggle Manual/AI mode - make global
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! 📥 Download`;
} 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();
}
// ============================================
// GEMINI CHATBOT
// ============================================
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;
}
}
}); // End DOMContentLoaded