document.addEventListener('DOMContentLoaded', () => { const spreadsheetDiv = document.getElementById('spreadsheet'); const commandInput = document.getElementById('commandInput'); const sendBtn = document.getElementById('sendBtn'); const exportBtn = document.getElementById('exportBtn'); const loadingOverlay = document.getElementById('loadingOverlay'); const micBtn = document.getElementById('micBtn'); let spreadsheet = null; let recognition = null; let conversationHistory = []; // --- Dropdown Options --- const DOMAINS = ['PMKT', 'BRND', 'EVNT', 'B2B']; const SUBTEAMS = ['ACQ', 'B2B', 'CMKT', 'ENGRT', 'ENT']; const BRANDS = [ 'WSJ', 'WSJ+', 'BAR', 'MW', 'DF', 'DJE', 'FAC', 'FE', 'GRI', 'NWS', 'OA', 'RSK', 'RSKC', 'DJRJ', 'R&C', 'WECR' ]; const EVENTS = [ 'GH', 'DJRJS', 'WECR', 'FEOE', 'FOH', 'GFF', 'JH', 'TL', 'TLQ', 'TLCYB', 'FOE', 'WSJIL', 'BODC', 'CCOC', 'CEOC', 'CFOC', 'CMOC', 'CPOC', 'TECC', 'WSJLI' ]; // --- Initial Load --- loadData(); // --- API Interactions --- async function loadData() { showLoading(true); try { const response = await fetch(`api.php?action=load&t=${Date.now()}`); const rawData = await response.json(); initSpreadsheet(rawData || []); // Check for builder params in URL const params = new URLSearchParams(window.location.search); if (params.get('builder') === '1') { const newRow = [ params.get('OMGID') || '', params.get('Domain') || '', params.get('Subteam') || '', params.get('Brand') || '', params.get('Event') || '', params.get('Initiative') || '', params.get('YY') || '', params.get('Sequence') || '', params.get('AssetName') || '', params.get('Version') || '', '' // Filename - will be generated ]; spreadsheet.insertRow(1, 0, true); // insert at top for (let col = 0; col < newRow.length; col++) { spreadsheet.setValueFromCoords(col, 0, newRow[col], true); } updateFilenameForRow(0); saveData(); // Clear URL params without reload window.history.replaceState({}, '', window.location.pathname); } if (spreadsheet && spreadsheet.getData().length > 0) { updateAllFilenames(); } } catch (error) { console.error('Error loading data:', error); alert('Failed to load data.'); } finally { showLoading(false); } } function initSpreadsheet(data) { if (spreadsheet) { spreadsheet.destroy(); } spreadsheet = jspreadsheet(spreadsheetDiv, { data: data, columns: [ { type: 'text', title: 'OMGID', width: 90, name: 'OMGID' }, { type: 'dropdown', title: 'Domain', width: 90, name: 'Domain', source: DOMAINS }, { type: 'dropdown', title: 'Subteam', width: 90, name: 'Subteam', source: SUBTEAMS }, { type: 'dropdown', title: 'Brand', width: 90, name: 'Brand', source: BRANDS }, { type: 'dropdown', title: 'Event', width: 90, name: 'Event', source: EVENTS }, { type: 'text', title: 'Initiative', width: 100, name: 'Initiative' }, { type: 'text', title: 'YY', width: 50, name: 'YY' }, { type: 'text', title: 'Seq', width: 50, name: 'Sequence' }, { type: 'text', title: 'Asset Name', width: 140, name: 'AssetName' }, { type: 'text', title: 'Ver', width: 50, name: 'Version' }, { type: 'text', title: 'Filename', width: 450, name: 'Filename', readOnly: true } ], defaultColWidth: 100, tableOverflow: true, tableWidth: '100%', tableHeight: '70vh', contextMenu: function (obj, x, y, e) { var items = []; if (y == null) { if (obj.options.allowInsertColumn == true) { items.push({ title: obj.options.text.insertNewColumnBefore, onclick: function () { obj.insertColumn(1, parseInt(x), 1); } }); items.push({ title: obj.options.text.insertNewColumnAfter, onclick: function () { obj.insertColumn(1, parseInt(x), 0); } }); } } else { items.push({ title: 'Delete Selected Rows', onclick: function () { const btn = document.getElementById('deleteSelectedBtn'); if (btn) btn.click(); } }); items.push({ title: 'Insert New Row', onclick: function () { obj.insertRow(1, parseInt(y)); } }); } return items; }, onchange: function (instance, cell, x, y, value) { // Update filename when any editable column changes (cols 0-9) if (x >= 0 && x <= 9) { updateFilenameForRow(y); } saveData(); }, oninsertrow: function () { updateAllFilenames(); saveData(); }, ondeleterow: function () { updateAllFilenames(); saveData(); } }); } function buildFilename(row) { // row is array: [OMGID, Domain, Subteam, Brand, Event, Initiative, YY, Sequence, AssetName, Version, Filename] const omgid = (row[0] || '').trim(); const domain = (row[1] || '').trim(); const subteam = (row[2] || '').trim(); const brand = (row[3] || '').trim(); const event = (row[4] || '').trim(); const initiative = (row[5] || '').trim(); const yy = (row[6] || '').trim(); const seq = (row[7] || '').trim(); const assetName = (row[8] || '').trim(); const version = (row[9] || '').trim(); // Build the middle segment based on domain let middlePart = ''; if (domain === 'EVNT') { // Event format: EVNT-[EventAbbrev]-[YY]-[Seq] const parts = ['EVNT']; if (event) parts.push(event); if (yy) parts.push(yy); if (seq) parts.push(seq); middlePart = parts.join('-'); } else { // Normal format: [Domain]-[Subteam]-[Brand]-[Initiative]-[YY]-[Seq] const parts = []; if (domain) parts.push(domain); if (subteam) parts.push(subteam); if (brand) parts.push(brand); if (initiative) parts.push(initiative); if (yy) parts.push(yy); if (seq) parts.push(seq); middlePart = parts.join('-'); } // Build suffix: _[AssetName]_v[Version] let suffix = ''; if (assetName) suffix += '_' + assetName; if (version) suffix += '_v' + version; // Final: [OMGID] - [middlePart][suffix] if (!omgid && !middlePart) return ''; return (omgid ? omgid + ' - ' : '') + middlePart + suffix; } function updateFilenameForRow(rowIndex) { const row = spreadsheet.getRowData(rowIndex); const filename = buildFilename(row); spreadsheet.setValueFromCoords(10, rowIndex, filename, true); } function updateAllFilenames() { const data = spreadsheet.getData(); data.forEach((row, index) => { const filename = buildFilename(row); spreadsheet.setValueFromCoords(10, index, filename, true); }); } async function saveData(dataOverride = null) { try { const data = dataOverride || spreadsheet.getJson(); const response = await fetch('api.php?action=save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: data }) }); const result = await response.json(); if (result.success) { console.log('Data saved.'); } else { console.error('Save failed:', result.message); } } catch (error) { console.error('Error saving data:', error); } } let lastCommandText = ''; async function sendCommand(overrideCommand = null, forceYolo = false) { const command = overrideCommand || commandInput.value.trim(); if (!command) return; lastCommandText = command; const yoloMode = forceYolo || document.getElementById('yoloToggle').checked; const aiOutput = document.getElementById('aiOutput'); const timestamp = new Date().toLocaleTimeString(); aiOutput.textContent += `\n[${timestamp}] Sending command: "${command}" (YOLO: ${yoloMode})...\n`; if (!overrideCommand) { commandInput.value = ''; } showLoading(true); try { const historyText = conversationHistory.map(h => `${h.role}: ${h.text}`).join('\n'); const campaignName = document.getElementById('campaignName')?.value.trim() || ''; const response = await fetch('api.php?action=command', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ command: command, yolo_mode: yoloMode, history: historyText, campaign_name: campaignName }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const text = await response.text(); let result; try { result = JSON.parse(text); } catch (e) { aiOutput.textContent += `> Error: Invalid JSON response from server.\n`; aiOutput.textContent += `> Raw response: ${text.substring(0, 500)}\n`; showLoading(false); return; } if (result.success) { conversationHistory.push({ role: 'User', text: command }); if (result.question) { conversationHistory.push({ role: 'AI', text: result.question }); aiOutput.textContent += `\n[${new Date().toLocaleTimeString()}] AI Question: ${result.question}\n`; aiOutput.scrollTop = aiOutput.scrollHeight; showAiQuestionModal(result.question); } else { conversationHistory = []; await loadData(); commandInput.value = ''; aiOutput.textContent += `Done! Processed ${result.count || 0} items.\n`; if (result.debug_llm) { aiOutput.textContent += `\n--- AI Response ---\n${result.debug_llm}\n`; } } } else { aiOutput.textContent += `Error: ${result.message}\n`; if (result.debug_llm) { aiOutput.textContent += `\n--- AI Response ---\n${result.debug_llm}\n`; } } aiOutput.scrollTop = aiOutput.scrollHeight; } catch (error) { console.error('Error sending command:', error); const aiOutput = document.getElementById('aiOutput'); aiOutput.textContent += `Critical Error: ${error.message}\n`; aiOutput.scrollTop = aiOutput.scrollHeight; } finally { showLoading(false); } } // Clear Log Button document.getElementById('clearLogBtn').addEventListener('click', () => { document.getElementById('aiOutput').textContent = 'Waiting for command...'; }); function showLoading(show) { if (show) loadingOverlay.classList.add('active'); else loadingOverlay.classList.remove('active'); } // --- Event Listeners --- const clearSheetBtn = document.getElementById('clearSheetBtn'); if (clearSheetBtn) { clearSheetBtn.addEventListener('click', () => { if (confirm('Are you sure you want to clear the ENTIRE sheet? This cannot be undone.')) { spreadsheet.setData([]); saveData([]); document.getElementById('aiOutput').textContent = 'Waiting for command...'; conversationHistory = []; } }); } sendBtn.addEventListener('click', () => stopMicAndSend()); commandInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); stopMicAndSend(); } }); // Custom CSV Export if (exportBtn) { exportBtn.addEventListener('click', function () { const data = spreadsheet.getData(); const headers = ["OMGID", "Domain", "Subteam", "Brand", "Event", "Initiative", "YY", "Sequence", "AssetName", "Version", "Filename"]; let csvContent = headers.map(h => `"${h}"`).join(",") + "\n"; data.forEach(row => { const csvRow = row.map((field, i) => { const val = String(field || ''); if (val.includes('"')) { return `"${val.replace(/"/g, '""')}"`; } return `"${val}"`; }); csvContent += csvRow.join(",") + "\n"; }); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement("a"); const url = URL.createObjectURL(blob); link.setAttribute("href", url); link.setAttribute("download", "dj_jobs_export.csv"); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); }); } // Copy Filenames to Clipboard const copyFilenamesBtn = document.getElementById('copyFilenamesBtn'); if (copyFilenamesBtn) { copyFilenamesBtn.addEventListener('click', () => { const data = spreadsheet.getData(); const filenames = data.map(row => row[10]).filter(f => f).join('\n'); navigator.clipboard.writeText(filenames) .then(() => alert('Filenames copied to clipboard!')) .catch(err => alert('Copy failed. Please try again.')); }); } // Load Campaign const loadCampaignBtn = document.getElementById('loadCampaignBtn'); if (loadCampaignBtn) { loadCampaignBtn.addEventListener('click', async () => { const campaign = prompt('Enter campaign name to load:'); if (!campaign) return; showLoading(true); try { const response = await fetch(`api.php?action=load_campaign&name=${campaign}`); const result = await response.json(); if (result.success) { initSpreadsheet(result.data); const campaignNameInput = document.getElementById('campaignName'); if (campaignNameInput) campaignNameInput.value = campaign; alert(`Campaign "${campaign}" loaded!`); } else { alert('Campaign not found.'); } } catch (error) { console.error('Error loading campaign:', error); alert('Failed to load campaign.'); } finally { showLoading(false); } }); } // Save Campaign const saveCampaignBtn = document.getElementById('saveCampaignBtn'); if (saveCampaignBtn) { saveCampaignBtn.addEventListener('click', async () => { const campaignNameInput = document.getElementById('campaignName'); const campaign = campaignNameInput ? campaignNameInput.value.trim() : ''; if (!campaign) { alert('Please enter a campaign name in the input field above.'); return; } showLoading(true); try { const data = spreadsheet.getJson(); const response = await fetch('api.php?action=save_campaign', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: campaign, data: data }) }); const result = await response.json(); if (result.success) { alert(`Campaign "${campaign}" saved!`); } else { alert('Save failed.'); } } catch (error) { console.error('Error saving campaign:', error); alert('Failed to save campaign.'); } finally { showLoading(false); } }); } // --- Voice Recognition --- if ('webkitSpeechRecognition' in window) { recognition = new webkitSpeechRecognition(); recognition.continuous = true; recognition.interimResults = true; recognition.lang = 'en-US'; micBtn.addEventListener('click', () => { if (micBtn.classList.contains('listening')) { recognition.stop(); } else { try { recognition.start(); } catch (error) { console.error('Error starting voice recognition:', error); alert('Could not start voice recognition. Please check microphone permissions.'); } } }); recognition.onstart = () => { micBtn.classList.add('listening'); commandInput.placeholder = "Listening... (Press Enter to send)"; }; recognition.onend = () => { micBtn.classList.remove('listening'); commandInput.placeholder = 'Type or speak a command...'; }; recognition.onerror = (event) => { console.error('Speech recognition error:', event.error); micBtn.classList.remove('listening'); if (event.error === 'not-allowed' || event.error === 'permission-denied') { alert('Microphone access denied.'); } }; recognition.onresult = (event) => { let finalTranscript = ''; let interimTranscript = ''; for (let i = 0; i < event.results.length; ++i) { if (event.results[i].isFinal) { finalTranscript += event.results[i][0].transcript; } else { interimTranscript += event.results[i][0].transcript; } } commandInput.value = finalTranscript + interimTranscript; }; } else { micBtn.style.display = 'none'; } function stopMicAndSend() { if (recognition && micBtn.classList.contains('listening')) { recognition.stop(); } sendCommand(); } // --- AI Question Modal Logic --- const aiModal = document.getElementById('aiQuestionModal'); const aiModalInput = document.getElementById('aiModalInput'); const aiModalMicBtn = document.getElementById('aiModalMicBtn'); const aiModalSendBtn = document.getElementById('aiModalSendBtn'); const aiModalYoloBtn = document.getElementById('aiModalYoloBtn'); window.showAiQuestionModal = function (question) { document.getElementById('aiQuestionText').textContent = question; aiModal.classList.add('active'); aiModalInput.value = ''; aiModalInput.focus(); }; window.closeAiModal = function () { aiModal.classList.remove('active'); if (recognition) recognition.stop(); }; aiModalSendBtn.addEventListener('click', () => { const answer = aiModalInput.value.trim(); if (answer) { closeAiModal(); sendCommand(answer); } }); aiModalInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); aiModalSendBtn.click(); } }); aiModalYoloBtn.addEventListener('click', () => { closeAiModal(); sendCommand(lastCommandText, true); }); if (recognition) { aiModalMicBtn.addEventListener('click', () => { if (aiModalMicBtn.classList.contains('listening')) { recognition.stop(); } else { recognition.onresult = (event) => { let finalTranscript = ''; for (let i = 0; i < event.results.length; ++i) { if (event.results[i].isFinal) { finalTranscript += event.results[i][0].transcript; } } aiModalInput.value = finalTranscript; }; recognition.onend = () => { aiModalMicBtn.classList.remove('listening'); restoreMainMicHandler(); }; recognition.start(); aiModalMicBtn.classList.add('listening'); } }); } else { aiModalMicBtn.style.display = 'none'; } function restoreMainMicHandler() { recognition.onresult = (event) => { let finalTranscript = ''; let interimTranscript = ''; for (let i = 0; i < event.results.length; ++i) { if (event.results[i].isFinal) { finalTranscript += event.results[i][0].transcript; } else { interimTranscript += event.results[i][0].transcript; } } commandInput.value = finalTranscript + interimTranscript; }; recognition.onend = () => { micBtn.classList.remove('listening'); commandInput.placeholder = "Type or speak a command..."; }; } });