ai_qc/frontend/index.html
nickviljoen ed0c362683 Update cost estimate to reflect actual usage (50x more accurate)
Issue: UI showed estimated cost of $0.15 per check, but actual
usage data shows average cost of only $0.003 per check - a 50x
difference!

Analysis from actual usage report:
- L'Oreal: 2 analyses, 6 checks total
- Cost: $0.02 total = $0.003 per check
- Old estimate: $0.15 per check (way too high)
- New estimate: $0.01 per check (conservative but realistic)

Changes:
- Updated cost calculation from 0.15 to 0.01 per check
- Updated display text from "$0.15 each" to "~$0.01 each"
- Applied to both web_ui.html and frontend/index.html

Example:
- Before: 3 checks = $0.45 estimated
- After: 3 checks = $0.03 estimated
- Actual: 3 checks = ~$0.01 (based on real usage)

New estimate is much closer to reality and based on actual
Gemini API usage data from production analyses.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-02 13:43:50 +02:00

2405 lines
109 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI QC - Quality Control Platform</title>
<!-- MSAL Browser Library v2.35.0 (last working CDN version) -->
<script src="https://alcdn.msauth.net/browser/2.35.0/js/msal-browser.min.js" crossorigin="anonymous"></script>
<script>
// Check if MSAL library loaded successfully
if (typeof msal === 'undefined') {
console.error('MSAL library failed to load from CDN');
// Fallback to alternative CDN
document.write('<script src="https://cdn.jsdelivr.net/npm/@azure/msal-browser@2.35.0/lib/msal-browser.min.js"><\/script>');
}
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 40px;
border-bottom: 3px solid #667eea;
padding-bottom: 20px;
}
.header-content {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.logo {
width: 120px;
height: auto;
}
.header-text p {
color: #7f8c8d;
font-size: 1.1em;
margin: 0;
margin-left: 0;
}
.settings-btn {
background: #f8f9fa;
border: 2px solid #dee2e6;
padding: 12px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 1em;
font-weight: 600;
color: #495057;
transition: all 0.3s ease;
text-decoration: none;
display: flex;
align-items: center;
gap: 8px;
}
.settings-btn:hover {
background: #e9ecef;
border-color: #adb5bd;
}
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-bottom: 30px;
}
.form-section {
background: #f8f9fa;
padding: 25px;
border-radius: 15px;
border: 2px solid #e9ecef;
}
.form-section h3 {
color: #495057;
margin-bottom: 20px;
font-size: 1.3em;
display: flex;
align-items: center;
gap: 10px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #495057;
}
input[type="file"], select, textarea {
width: 100%;
padding: 12px;
border: 2px solid #dee2e6;
border-radius: 8px;
font-size: 1em;
transition: border-color 0.3s ease;
}
input[type="file"]:focus, select:focus, textarea:focus {
outline: none;
border-color: #667eea;
}
.file-upload-area {
border: 2px dashed #cbd5e0;
border-radius: 12px;
padding: 30px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
background: #f7fafc;
}
.file-upload-area:hover {
border-color: #667eea;
background: #edf2f7;
}
.file-upload-area.dragover {
border-color: #667eea;
background: #e2e8f0;
}
.file-info {
margin-top: 15px;
padding: 10px;
background: #d4edda;
border-radius: 8px;
display: none;
}
.btn {
padding: 15px 30px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1.1em;
font-weight: 600;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
}
.btn-primary:disabled {
background: #6c757d;
cursor: not-allowed;
transform: none;
}
.progress-container {
margin-top: 30px;
display: none;
background: #2c3e50;
border-radius: 15px;
padding: 40px;
color: white;
text-align: center;
}
.progress-header {
margin-bottom: 30px;
}
.progress-title {
font-size: 1.5em;
font-weight: 600;
margin-bottom: 15px;
color: #ffcb05;
}
.progress-subtitle {
font-size: 1em;
color: #bdc3c7;
margin-bottom: 20px;
}
.progress-bar {
width: 100%;
height: 8px;
background: rgba(255, 255, 255, 0.2);
border-radius: 4px;
overflow: hidden;
margin-bottom: 20px;
}
.progress-fill {
height: 100%;
background: #ffcb05;
width: 0%;
transition: width 0.3s ease;
border-radius: 4px;
}
.current-app {
font-size: 1.1em;
color: #ecf0f1;
font-weight: 500;
}
.processing-spinner {
display: inline-block;
width: 40px;
height: 40px;
border: 4px solid rgba(255, 203, 5, 0.3);
border-top: 4px solid #ffcb05;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 20px auto;
}
.results-container {
margin-top: 30px;
display: none;
}
.results-summary {
background: #f8f9fa;
padding: 25px;
border-radius: 15px;
border-left: 5px solid #28a745;
margin-bottom: 20px;
}
.results-summary h3 {
color: #495057;
margin-bottom: 15px;
}
.score-display {
font-size: 2em;
font-weight: bold;
color: #28a745;
margin-bottom: 10px;
}
.results-details {
background: white;
border-radius: 15px;
padding: 20px;
border: 2px solid #e9ecef;
}
.expandable-section {
margin-bottom: 15px;
border: 1px solid #dee2e6;
border-radius: 8px;
overflow: hidden;
}
.expandable-header {
background: #f8f9fa;
padding: 15px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #dee2e6;
}
.expandable-header:hover {
background: #e9ecef;
}
.expandable-content {
padding: 20px;
display: none;
background: white;
}
.expandable-content.expanded {
display: block;
}
.check-score {
font-weight: bold;
padding: 4px 8px;
border-radius: 4px;
color: white;
margin-left: 10px;
}
.check-score.high { background: #28a745; }
.check-score.medium { background: #ffc107; color: #212529; }
.check-score.low { background: #dc3545; }
.pass-fail-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-weight: bold;
font-size: 0.8em;
margin-left: 10px;
}
.pass-badge {
background: #28a745;
color: white;
}
.fail-badge {
background: #dc3545;
color: white;
}
.overall-pass-fail {
font-size: 1.2em;
font-weight: bold;
margin-left: 15px;
padding: 8px 20px;
border-radius: 25px;
display: inline-block;
}
.overall-pass {
background: #28a745;
color: white;
}
.overall-fail {
background: #dc3545;
color: white;
}
.error-message {
background: #f8d7da;
color: #721c24;
padding: 15px;
border-radius: 8px;
margin: 20px 0;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.icon {
font-size: 1.2em;
margin-right: 8px;
}
.chevron {
transition: transform 0.3s ease;
}
.chevron.expanded {
transform: rotate(180deg);
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="header-content">
<svg class="logo" viewBox="0 0 278.84 109.39" xmlns="http://www.w3.org/2000/svg">
<defs>
<style>
.cls-1 { fill: #ffcb05; }
.cls-2 { fill: #7f5800; }
.cls-3 { fill: #1a1a1a; }
</style>
</defs>
<g>
<path class="cls-3" d="M202.64,54.3c0-23.98,18.41-41.14,43.19-41.14,13.75,0,25.23,5,32.73,14.09l-11.82,10.91c-5.34-6.14-12.05-9.32-20-9.32-14.89,0-25.46,10.46-25.46,25.46s10.57,25.46,25.46,25.46c7.96,0,15.95-6.32,21.29-12.57l10.82,9.91c-7.5,9.21-19.27,18.35-33.13,18.35-24.66,0-43.07-17.16-43.07-41.14Z"/>
<g>
<path class="cls-3" d="M194.45,34.24s-.05-.1-.08-.15l-14.2,14.61c3.03,11.48-2.02,22.54-12.8,28.07-12.64,6.49-26.86,2.18-33.77-11.27-6.91-13.44-2.13-27.52,10.5-34.02,10.08-5.18,21.18-3.47,28.76,4.26l11.78-14.09c-12.52-10.82-30.75-12.84-47.72-4.12-22.34,11.48-30.71,35.46-19.91,56.49,9.87,19.21,32.96,26.13,53.61,18.58,6.83,7.12,8.31,8.77,13.83,12.3,7.58,4.86,20.83,4.94,27.21,4.03-13.71-10.35-21.39-21.99-24.73-27.15,12.57-12.59,16.02-30.98,7.5-47.56Z"/>
<path class="cls-1" d="M172.87,35.75l-11.76,14.06-15.86-8.46-7.5,8.79,23.83,17.69,18.6-19.13c-.52-1.98-1.28-3.97-2.3-5.94-1.39-2.71-3.09-5.05-5.01-7Z"/>
<path class="cls-1" d="M194.37,34.09l24.26-24.96-15.86-9.13-18.12,21.66c3.85,3.33,7.16,7.48,9.72,12.43Z"/>
<path class="cls-2" d="M177.88,42.76c1.02,1.98,1.77,3.97,2.3,5.94l14.2-14.61c-2.56-4.94-5.87-9.1-9.72-12.43l-11.78,14.09c1.92,1.96,3.61,4.29,5.01,7Z"/>
</g>
<g>
<path class="cls-3" d="M62.85,76.6H25.91l-7.05,17.05H0L35.46,14.09l17.18-1,36.57,80.55h-19.32l-7.05-17.05ZM57.05,62.62l-12.62-30.46-12.61,30.46h25.23Z"/>
<path class="cls-3" d="M89.55,13.93l18.41.52v79.55l-18.41-.36V13.93Z"/>
</g>
</g>
</svg>
<div class="header-text">
<p>Quality Control Platform</p>
</div>
</div>
<div id="authSection">
<!-- Authentication loading state -->
<div id="authLoading" style="display: none;">
<div class="settings-btn">
<span>🔄</span>
Checking authentication...
</div>
</div>
<!-- Login button -->
<button class="settings-btn" id="loginBtn" style="display: none;">
<span>🔐</span>
Sign In with Microsoft
</button>
<!-- User info when authenticated -->
<div id="userInfo" style="display: none;">
<div style="display: flex; align-items: center; gap: 15px;">
<div style="text-align: right;">
<div style="font-weight: 600; color: #495057;" id="userName"></div>
<div style="font-size: 0.9em; color: #6c757d;" id="userEmail"></div>
</div>
<div style="display: flex; gap: 10px;">
<button class="settings-btn" id="settingsBtn">
<span>⚙️</span>
Settings
</button>
<button class="settings-btn" id="logoutBtn" style="background: #dc3545; color: white; border-color: #dc3545;">
<span>🚪</span>
Sign Out
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Main application content - hidden until authenticated -->
<div id="mainApp" style="display: none;">
<div class="form-grid">
<div class="form-section">
<h3><span class="icon">📁</span>File Upload</h3>
<div class="form-group">
<label for="file-input">Select or drag your visual assets</label>
<div class="file-upload-area" id="fileUploadArea">
<input type="file" id="file-input" accept="image/*,.pdf" multiple style="display: none;">
<div style="font-size: 3em; margin-bottom: 10px;">📤</div>
<p>Click here or drag and drop your files</p>
<p style="font-size: 0.9em; color: #6c757d; margin-top: 5px;">
Supported formats: JPG, PNG, PDF, GIF, WebP<br>
Multiple files supported
</p>
</div>
<div class="file-info" id="fileInfo"></div>
<!-- Queue Display -->
<div id="fileQueue" style="margin-top: 20px; display: none;">
<h4 style="color: #495057; margin-bottom: 15px;">File Queue (<span id="queueCount">0</span> files)</h4>
<div id="queueList" style="max-height: 300px; overflow-y: auto;">
<!-- Queued files will appear here -->
</div>
<div style="margin-top: 15px; text-align: center;">
<button onclick="clearQueue()" style="background: #6c757d; color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; margin-right: 10px;">Clear All</button>
<button onclick="processQueue()" id="processQueueBtn" style="background: #28a745; color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer;" disabled>Process Queue</button>
</div>
</div>
</div>
</div>
<div class="form-section">
<h3><span class="icon">⚙️</span>QC Configuration</h3>
<div class="form-group">
<label for="profile-select">QC Profile</label>
<select id="profile-select">
<option value="">Loading profiles...</option>
</select>
</div>
<div class="form-group">
<div class="cost-display" id="costDisplay" style="background: #e3f2fd; padding: 12px; border-radius: 8px; border-left: 4px solid #2196f3; display: none;">
<strong>Estimated Cost: <span id="estimatedCost">$0.00</span></strong>
<br>
<small style="color: #666;">Based on <span id="checkCount">0</span> selected checks at ~$0.01 each</small>
</div>
</div>
<div class="form-group">
<label for="output-mode">Output Mode</label>
<select id="output-mode">
<option value="html">HTML Report</option>
<option value="json">JSON Data</option>
</select>
</div>
<div class="form-group">
<label for="model-select">AI Model</label>
<select id="model-select">
<option value="profile">Use Profile Settings</option>
<option value="Gemini">Google Gemini</option>
<option value="OpenAI">OpenAI GPT-4</option>
</select>
</div>
<div class="form-group">
<label for="reference-asset-select">Reference Assets</label>
<select id="reference-asset-select">
<option value="">No reference asset selected</option>
</select>
</div>
</div>
</div>
<div style="text-align: center; margin-bottom: 30px;">
<button class="btn btn-primary" id="analyzeBtn" disabled>
<span class="icon">🔍</span>
Start QC Analysis
</button>
</div>
<div class="progress-container" id="progressContainer">
<div class="progress-header">
<div class="progress-title" id="progressTitle">PROCESSING</div>
<div class="progress-subtitle" id="progressSubtitle">Loading appropriate profile resources...</div>
<div class="processing-spinner"></div>
</div>
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="current-app" id="currentApp">Initializing analysis...</div>
</div>
<div class="results-container" id="resultsContainer">
<div class="results-summary">
<h3>Analysis Results</h3>
<div class="score-display" id="overallScore">--/100</div>
<div id="gradeDisplay">Grade: --</div>
<div id="profileUsed">Profile: --</div>
<div id="checksCompleted">Checks: --</div>
</div>
<div class="results-details" id="resultsDetails">
<!-- Results will be populated here -->
</div>
</div>
<div class="saved-files-container" id="savedFilesContainer" style="display: none;">
<div style="background: #f8f9fa; padding: 25px; border-radius: 15px; border-left: 5px solid #6c757d; margin-bottom: 20px;">
<h3>📁 Saved Output Files</h3>
<div id="savedFilesList">
<!-- Saved files will be populated here -->
</div>
</div>
</div>
</div> <!-- End mainApp -->
<!-- Authentication required screen -->
<div id="authRequired" style="display: block;">
<div style="text-align: center; padding: 60px 20px;">
<div style="font-size: 4em; margin-bottom: 20px;">🔐</div>
<h2 style="color: #495057; margin-bottom: 15px;">Authentication Required</h2>
<p style="color: #6c757d; font-size: 1.1em; margin-bottom: 30px;">
Please sign in with your Microsoft account to access the AI QC Platform
</p>
<button class="settings-btn" id="authRequiredLoginBtn" style="background: #0078d4; color: white; border-color: #0078d4; font-size: 1.1em; padding: 15px 30px;">
<span>🔐</span>
Sign In with Microsoft
</button>
</div>
</div>
</div>
<script>
// Get the base path for API calls
function getBasePath() {
const path = window.location.pathname;
// If we're at /ai_qc/ or /ai_qc/something, extract the base path
if (path.includes('/ai_qc/')) {
return path.substring(0, path.indexOf('/ai_qc/') + 7); // Include /ai_qc/
}
return '/'; // Default to root if no special path
}
const BASE_PATH = getBasePath();
console.log('Base path detected:', BASE_PATH);
// Global variables
let selectedFile = null;
let availableProfiles = {};
let currentSessionId = null;
let fileQueue = [];
// DOM elements (will be set in init function when DOM is ready)
let fileUploadArea, fileInput, fileInfo, profileSelect, outputMode, modelSelect, analyzeBtn, progressContainer, progressFill, currentApp, resultsContainer;
// Initialize the application
async function init() {
console.log('=== INIT FUNCTION STARTED ===');
// Initialize DOM elements now that DOM is ready
fileUploadArea = document.getElementById('fileUploadArea');
fileInput = document.getElementById('file-input');
fileInfo = document.getElementById('fileInfo');
profileSelect = document.getElementById('profile-select');
outputMode = document.getElementById('output-mode');
modelSelect = document.getElementById('model-select');
analyzeBtn = document.getElementById('analyzeBtn');
progressContainer = document.getElementById('progressContainer');
progressFill = document.getElementById('progressFill');
currentApp = document.getElementById('currentApp');
resultsContainer = document.getElementById('resultsContainer');
console.log('DOM elements initialized:', {
profileSelect: profileSelect,
fileInput: fileInput,
analyzeBtn: analyzeBtn
});
try {
console.log('About to load profiles...');
await loadProfiles();
console.log('Profiles loaded successfully');
console.log('About to load saved files...');
await loadSavedFiles();
console.log('Saved files loaded successfully');
console.log('About to setup event listeners...');
setupEventListeners();
console.log('Event listeners setup complete');
} catch (error) {
console.error('Error in init function:', error);
}
console.log('=== INIT FUNCTION COMPLETE ===');
}
// Load available profiles from API
async function loadProfiles() {
try {
console.log('Loading profiles from API...');
console.log('Current URL:', window.location.href);
console.log(`Attempting to fetch: ${BASE_PATH}api/profiles`);
const response = await fetch(`${BASE_PATH}api/profiles`, {
credentials: 'include'
});
console.log('Response received:', response);
console.log('Response status:', response.status);
console.log('Response ok:', response.ok);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Profiles API response:', data);
console.log('Response data.status:', data.status);
console.log('Status check result:', data.status === 'success');
if (data.status === 'success') {
availableProfiles = data.all_profiles;
console.log('Available profiles:', availableProfiles);
console.log('About to call populateProfileSelect()...');
console.log('populateProfileSelect function exists:', typeof populateProfileSelect);
try {
populateProfileSelect();
console.log('populateProfileSelect() called successfully');
} catch (error) {
console.error('Error calling populateProfileSelect():', error);
}
// Direct dropdown update as backup
console.log('Attempting direct dropdown update...');
const dropdown = document.getElementById('profile-select');
if (dropdown) {
console.log('Found dropdown element directly, updating...');
dropdown.innerHTML = '<option value="">Select a profile...</option>';
for (const [profileId, profileInfo] of Object.entries(availableProfiles)) {
const option = document.createElement('option');
option.value = profileId;
option.textContent = `${profileInfo.name} (${profileInfo.enabled_count} checks)`;
dropdown.appendChild(option);
}
console.log('Direct dropdown update complete with', dropdown.children.length - 1, 'profiles');
} else {
console.error('Could not find dropdown element for direct update');
}
} else {
console.error('Failed to load profiles, data.status:', data.status);
console.error('Full response data:', data);
// Show error in dropdown
profileSelect.innerHTML = '<option value="">Error loading profiles</option>';
}
} catch (error) {
console.error('Error loading profiles:', error);
console.error('Error details:', error.message, error.stack);
// Try a direct test fetch
console.log('Attempting direct health check...');
try {
const healthResponse = await fetch(`${BASE_PATH}health`);
console.log('Health check response:', healthResponse.status);
} catch (healthError) {
console.error('Health check failed:', healthError);
}
// Show error in dropdown with more details
profileSelect.innerHTML = `<option value="">Error: ${error.message}</option>`;
// Add a manual refresh option
const refreshOption = document.createElement('option');
refreshOption.value = 'refresh';
refreshOption.textContent = 'Click to retry loading profiles';
profileSelect.appendChild(refreshOption);
// Add event listener for manual retry
profileSelect.addEventListener('change', function(e) {
if (e.target.value === 'refresh') {
loadProfiles();
}
}, { once: true });
}
}
// Load saved output files from API with retry mechanism
async function loadSavedFiles(retryAttempts = 0, maxRetries = 3) {
try {
console.log(`Loading saved files... (attempt ${retryAttempts + 1}/${maxRetries + 1})`);
const response = await fetch(`${BASE_PATH}api/output_files`, {
credentials: 'include'
});
const data = await response.json();
if (data.files && data.files.length > 0) {
console.log(`Found ${data.files.length} saved files`);
displaySavedFiles(data.files);
} else {
console.log('No saved files found');
// If no files found and we're retrying, try again in a moment
if (retryAttempts < maxRetries) {
console.log('Retrying in 2 seconds...');
setTimeout(() => loadSavedFiles(retryAttempts + 1, maxRetries), 2000);
}
}
} catch (error) {
console.error('Error loading saved files:', error);
// Retry on error as well
if (retryAttempts < maxRetries) {
console.log('Retrying due to error in 2 seconds...');
setTimeout(() => loadSavedFiles(retryAttempts + 1, maxRetries), 2000);
}
}
}
// Refresh saved files after analysis with progressive delays
async function refreshSavedFilesAfterAnalysis() {
console.log('Starting post-analysis file refresh...');
// Show loading indicator
showSavedFilesLoading(true);
// Store the current number of files for comparison
let previousFileCount = 0;
try {
const response = await fetch(`${BASE_PATH}api/output_files`, {
credentials: 'include'
});
const data = await response.json();
previousFileCount = data.files ? data.files.length : 0;
console.log(`Current file count: ${previousFileCount}`);
} catch (error) {
console.log('Could not get initial file count, proceeding with refresh');
}
// Attempt refresh multiple times with increasing delays
const refreshAttempts = [1000, 3000, 5000]; // 1s, 3s, 5s delays
for (let i = 0; i < refreshAttempts.length; i++) {
await new Promise(resolve => setTimeout(resolve, refreshAttempts[i]));
try {
console.log(`Refresh attempt ${i + 1} after ${refreshAttempts[i]}ms delay`);
const response = await fetch(`${BASE_PATH}api/output_files`, {
credentials: 'include'
});
const data = await response.json();
if (data.files && data.files.length > 0) {
const newFileCount = data.files.length;
console.log(`Found ${newFileCount} files (was ${previousFileCount})`);
// Highlight latest file if new files were added
const shouldHighlight = newFileCount > previousFileCount;
displaySavedFiles(data.files, shouldHighlight);
// If we found new files, we can stop trying
if (newFileCount > previousFileCount) {
console.log('New file(s) detected, refresh complete');
break;
}
}
} catch (error) {
console.error(`Refresh attempt ${i + 1} failed:`, error);
}
}
// Hide loading indicator
showSavedFilesLoading(false);
}
// Show/hide loading indicator for saved files section
function showSavedFilesLoading(show) {
const savedFilesContainer = document.getElementById('savedFilesContainer');
const savedFilesList = document.getElementById('savedFilesList');
if (show) {
// Show container if hidden
savedFilesContainer.style.display = 'block';
// Add loading indicator
const loadingDiv = document.createElement('div');
loadingDiv.id = 'savedFilesLoading';
loadingDiv.style.cssText = 'text-align: center; padding: 20px; color: #6c757d;';
loadingDiv.innerHTML = '🔄 Checking for new files...';
// Remove existing loading indicator if any
const existingLoading = document.getElementById('savedFilesLoading');
if (existingLoading) {
existingLoading.remove();
}
savedFilesList.appendChild(loadingDiv);
} else {
// Remove loading indicator
const loadingDiv = document.getElementById('savedFilesLoading');
if (loadingDiv) {
loadingDiv.remove();
}
}
}
// Display saved files in the UI
function displaySavedFiles(files, highlightLatest = false) {
const savedFilesContainer = document.getElementById('savedFilesContainer');
const savedFilesList = document.getElementById('savedFilesList');
savedFilesList.innerHTML = '';
files.forEach((file, index) => {
const fileDiv = document.createElement('div');
// Highlight the latest file (first in sorted list) if requested
const isLatest = index === 0 && highlightLatest;
const baseStyle = 'background: white; padding: 15px; margin: 10px 0; border-radius: 8px; border: 1px solid #dee2e6; transition: all 0.3s ease;';
const highlightStyle = isLatest ? 'background: #e8f5e8; border: 2px solid #28a745; box-shadow: 0 4px 8px rgba(40, 167, 69, 0.2);' : '';
fileDiv.style.cssText = baseStyle + highlightStyle;
const fileType = file.filename.endsWith('.html') ? 'HTML Report' : 'JSON Data';
const fileIcon = file.filename.endsWith('.html') ? '🌐' : '📄';
const fileSize = (file.size / 1024).toFixed(1) + ' KB';
const newBadge = isLatest ? '<span style="background: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 0.8em; margin-left: 8px;">NEW</span>' : '';
fileDiv.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<strong>${fileIcon} ${file.filename}${newBadge}</strong><br>
<small style="color: #6c757d;">${fileType}${fileSize} • Created: ${file.created}</small>
</div>
<a href="${BASE_PATH}${file.url.startsWith('/') ? file.url.substring(1) : file.url}" target="_blank" style="background: #007bff; color: white; padding: 8px 16px; text-decoration: none; border-radius: 4px; font-size: 0.9em;">
View/Download
</a>
</div>
`;
savedFilesList.appendChild(fileDiv);
// Remove highlight after 5 seconds for latest file
if (isLatest) {
setTimeout(() => {
fileDiv.style.background = 'white';
fileDiv.style.border = '1px solid #dee2e6';
fileDiv.style.boxShadow = 'none';
// Remove NEW badge
const newBadgeElement = fileDiv.querySelector('[style*="background: #28a745"]');
if (newBadgeElement) {
newBadgeElement.remove();
}
}, 5000);
}
});
savedFilesContainer.style.display = 'block';
}
// Populate profile select dropdown
function populateProfileSelect() {
console.log('FUNCTION START - populateProfileSelect');
console.log('=== POPULATE PROFILE SELECT CALLED ===');
try {
// Use fresh lookup to ensure we have the element
const profileDropdown = profileSelect || document.getElementById('profile-select');
console.log('Global profileSelect:', profileSelect);
console.log('Fresh lookup result:', document.getElementById('profile-select'));
console.log('Profile dropdown element to use:', profileDropdown);
console.log('Available profiles for population:', availableProfiles);
console.log('Available profiles keys:', Object.keys(availableProfiles || {}));
if (!profileDropdown) {
console.error('Profile select element not found!');
return;
}
profileDropdown.innerHTML = '<option value="">Select a profile...</option>';
if (!availableProfiles || Object.keys(availableProfiles).length === 0) {
console.warn('No profiles available to populate');
profileDropdown.innerHTML = '<option value="">No profiles available</option>';
return;
}
for (const [profileId, profileInfo] of Object.entries(availableProfiles)) {
console.log(`Adding profile: ${profileId}`, profileInfo);
const option = document.createElement('option');
option.value = profileId;
option.textContent = `${profileInfo.name} (${profileInfo.enabled_count} checks)`;
profileDropdown.appendChild(option);
}
console.log('Profile select populated with', profileDropdown.children.length - 1, 'profiles');
} catch (error) {
console.error('Error in populateProfileSelect:', error);
}
}
// Setup event listeners
function setupEventListeners() {
console.log('Setting up event listeners...');
console.log('Elements check:', {
fileUploadArea: !!fileUploadArea,
fileInput: !!fileInput,
profileSelect: !!profileSelect,
analyzeBtn: !!analyzeBtn,
settingsBtn: !!document.getElementById('settingsBtn')
});
// File upload area
fileUploadArea.addEventListener('click', () => fileInput.click());
fileUploadArea.addEventListener('dragover', handleDragOver);
fileUploadArea.addEventListener('dragleave', handleDragLeave);
fileUploadArea.addEventListener('drop', handleFileDrop);
// File input change
fileInput.addEventListener('change', handleFileSelect);
// Profile change
profileSelect.addEventListener('change', checkFormValidity);
// Analyze button
analyzeBtn.addEventListener('click', startAnalysis);
// Settings button
const settingsBtn = document.getElementById('settingsBtn');
if (settingsBtn) {
console.log('Adding click listener to settings button');
settingsBtn.addEventListener('click', showSettings);
} else {
console.error('Settings button not found!');
}
// Update cost display when profile changes
profileSelect.addEventListener('change', updateCostDisplay);
console.log('Event listeners setup complete');
}
// Handle drag and drop
function handleDragOver(e) {
e.preventDefault();
fileUploadArea.classList.add('dragover');
}
function handleDragLeave(e) {
e.preventDefault();
fileUploadArea.classList.remove('dragover');
}
function handleFileDrop(e) {
e.preventDefault();
fileUploadArea.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
const filesArray = Array.from(files);
if (filesArray.length === 1) {
selectedFile = filesArray[0];
displayFileInfo(filesArray[0]);
checkFormValidity();
} else {
addFilesToQueue(filesArray);
}
}
}
// Handle file selection
function handleFileSelect(e) {
const files = Array.from(e.target.files);
if (files.length > 0) {
if (files.length === 1) {
// Single file - maintain existing behavior
selectedFile = files[0];
displayFileInfo(files[0]);
checkFormValidity();
} else {
// Multiple files - add to queue
addFilesToQueue(files);
}
}
}
// Display file information
function displayFileInfo(file) {
const fileSize = (file.size / 1024 / 1024).toFixed(2);
fileInfo.innerHTML = `
<div style="background: #d4edda; padding: 15px; border-radius: 8px; border-left: 4px solid #28a745;">
<strong>Selected:</strong> ${file.name}<br>
<strong>Size:</strong> ${fileSize} MB<br>
<strong>Type:</strong> ${file.type || 'Unknown'}<br>
<button onclick="detectBrandAndType()" style="background: #17a2b8; color: white; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; margin-top: 8px; font-size: 0.9em;">🔍 Detect Brand & Type</button>
<div id="detectionResults" style="margin-top: 10px; display: none;"></div>
</div>
`;
fileInfo.style.display = 'block';
}
// Check if form is valid and update cost
function checkFormValidity() {
const hasFiles = selectedFile || fileQueue.length > 0;
const isValid = hasFiles && profileSelect.value;
analyzeBtn.disabled = !isValid;
updateCostDisplay();
// Update queue process button
const processBtn = document.getElementById('processQueueBtn');
if (processBtn) {
const hasAnalyzedFiles = fileQueue.some(item => item.status === 'analyzed' || item.status === 'detection_failed');
processBtn.disabled = !hasAnalyzedFiles || !profileSelect.value;
}
}
// Update cost display based on selected profile
function updateCostDisplay() {
const costDisplay = document.getElementById('costDisplay');
const estimatedCost = document.getElementById('estimatedCost');
const checkCount = document.getElementById('checkCount');
if (profileSelect.value && availableProfiles[profileSelect.value]) {
const profile = availableProfiles[profileSelect.value];
const numChecks = profile.enabled_count;
const cost = numChecks * 0.01; // Updated based on actual usage: ~$0.01 per check
estimatedCost.textContent = `$${cost.toFixed(2)}`;
checkCount.textContent = numChecks;
costDisplay.style.display = 'block';
} else {
costDisplay.style.display = 'none';
}
}
// Start analysis
async function startAnalysis() {
if (!selectedFile || !profileSelect.value) {
alert('Please select a file and profile before starting analysis.');
return;
}
// Show progress
progressContainer.style.display = 'block';
resultsContainer.style.display = 'none';
analyzeBtn.disabled = true;
try {
// Prepare form data
const formData = new FormData();
formData.append('file', selectedFile);
formData.append('profile', profileSelect.value);
console.log('DEBUG: outputMode.value =', outputMode.value);
formData.append('mode', outputMode.value);
// If specific model is selected, add it to the form
if (modelSelect.value !== 'profile') {
formData.append('model', modelSelect.value);
}
// Add selected reference asset if any
const referenceAssetSelect = document.getElementById('reference-asset-select');
if (referenceAssetSelect && referenceAssetSelect.value) {
formData.append('reference_asset', referenceAssetSelect.value);
}
// Start analysis with progress tracking
const analysisResult = await performAnalysisWithProgress(formData);
// Handle the results
await handleAnalysisResults(analysisResult);
} catch (error) {
console.error('Analysis error:', error);
showError('Analysis failed: ' + error.message);
} finally {
progressContainer.style.display = 'none';
analyzeBtn.disabled = false;
}
}
// Perform analysis with progress tracking
async function performAnalysisWithProgress(formData) {
document.getElementById('progressTitle').textContent = 'PROCESSING';
document.getElementById('progressSubtitle').textContent = 'Initializing analysis...';
currentApp.textContent = 'Starting quality control analysis...';
progressFill.style.width = '5%';
try {
console.log('Starting analysis with profile:', formData.get('profile'));
console.log('Output mode:', formData.get('mode'));
// Capture the original output mode for later use
const originalMode = formData.get('mode');
// Convert profile name to brand for the API
const profile = formData.get('profile');
if (profile && profile !== 'general') {
const brand = profile.split('_')[0];
formData.set('brand', brand);
}
// Keep the original mode selection - our async analysis supports progress tracking for both JSON and HTML modes
// Start the analysis and get session ID for progress tracking
progressFill.style.width = '10%';
currentApp.textContent = 'Submitting file for analysis...';
const response = await fetch(`${BASE_PATH}api/start_analysis`, {
method: 'POST',
body: formData,
credentials: 'include'
});
console.log('Analysis response status:', response.status);
if (!response.ok) {
throw new Error(`Analysis failed with status ${response.status}`);
}
// Check if we got a session_id for progress tracking
const responseText = await response.text();
console.log('Raw analysis response:', responseText);
let responseData;
try {
responseData = JSON.parse(responseText);
} catch (parseError) {
console.error('Failed to parse analysis response as JSON:', parseError);
console.error('Response was:', responseText);
throw new Error('Invalid JSON response from analysis endpoint');
}
console.log('Analysis response data:', responseData);
console.log('Session ID in response:', responseData.session_id);
if (responseData.session_id) {
console.log('Using real-time progress tracking for session:', responseData.session_id);
// We have a session ID, use real-time progress tracking
await trackAnalysisProgress(responseData.session_id);
// Set the original output mode back for final handling
responseData.original_mode = originalMode;
// Get final results
return responseData;
} else {
console.log('No session ID, analysis completed immediately');
// No session ID, use the response directly
progressFill.style.width = '100%';
document.getElementById('progressTitle').textContent = 'PROCESSING COMPLETE';
document.getElementById('progressSubtitle').textContent = 'Analysis complete!';
currentApp.textContent = 'Analysis complete!';
// Set the original output mode back for final handling
responseData.original_mode = originalMode;
return responseData;
}
} catch (error) {
console.error('Analysis error in performAnalysisWithProgress:', error);
throw error;
}
}
// Track analysis progress using session ID
async function trackAnalysisProgress(sessionId) {
console.log('Starting progress tracking for session:', sessionId);
return new Promise((resolve, reject) => {
const pollProgress = async () => {
try {
console.log(`Polling progress for session: ${sessionId}`);
const response = await fetch(`${BASE_PATH}api/progress/${sessionId}`, {
credentials: 'include'
});
console.log('Progress API response status:', response.status);
if (!response.ok) {
console.error(`Progress check failed with status: ${response.status}`);
const errorText = await response.text();
console.error('Error response:', errorText);
throw new Error(`Progress check failed: ${response.status}`);
}
const responseText = await response.text();
console.log('Raw progress response:', responseText);
let data;
try {
data = JSON.parse(responseText);
} catch (parseError) {
console.error('Failed to parse progress response as JSON:', parseError);
console.error('Response was:', responseText);
throw new Error('Invalid JSON response from progress endpoint');
}
console.log('Progress data received:', data);
const progress = data.progress;
console.log('Progress update:', progress);
// Update progress display
const percentage = progress.percentage || 0;
const stage = progress.stage || 'processing';
const currentCheck = progress.current_check || 'Processing';
const currentCheckDisplay = progress.current_check_display || currentCheck;
const completedChecks = progress.completed_checks || 0;
const totalChecks = progress.total_checks || 1;
// Update progress bar
progressFill.style.width = `${percentage}%`;
// Update progress text
if (stage === 'complete') {
document.getElementById('progressTitle').textContent = 'PROCESSING COMPLETE';
document.getElementById('progressSubtitle').textContent = 'Analysis complete!';
currentApp.textContent = 'All quality control checks completed successfully!';
// Return the analysis results
resolve(progress.result || data);
} else {
document.getElementById('progressTitle').textContent = 'PROCESSING';
document.getElementById('progressSubtitle').textContent = `Step ${completedChecks + 1} of ${totalChecks}`;
currentApp.textContent = `${currentCheckDisplay}`;
}
// Continue polling if not complete
if (stage !== 'complete' && stage !== 'error') {
setTimeout(pollProgress, 1000); // Poll every second
} else if (stage === 'error') {
reject(new Error(progress.error || 'Analysis failed'));
}
} catch (error) {
console.error('Progress polling error:', error);
reject(error);
}
};
// Start polling immediately
pollProgress();
});
}
// Handle analysis completion - process results and trigger download
async function handleAnalysisResults(analysisResult) {
console.log('=== HANDLING ANALYSIS RESULTS ===');
console.log('Analysis result:', analysisResult);
// Display results in the UI first
console.log('Displaying results in UI...');
displayResults(analysisResult);
// Auto-generate and download HTML report if originally requested HTML mode
if (analysisResult.status === 'success') {
if (analysisResult.original_mode === 'html' || analysisResult.session_id) {
console.log('Analysis successful, generating HTML report...');
await generateHtmlReport(analysisResult);
} else {
console.log('JSON mode requested, skipping HTML report generation');
}
} else {
console.warn('Analysis was not successful, skipping HTML report generation');
}
// Refresh saved files list with delay to ensure file is written
console.log('Refreshing saved files list...');
await refreshSavedFilesAfterAnalysis();
console.log('=== ANALYSIS RESULTS HANDLING COMPLETE ===');
}
// Generate HTML report and trigger download
async function generateHtmlReport(analysisResult) {
try {
console.log('Generating HTML report...');
console.log('Analysis result for HTML generation:', analysisResult);
// Check if the analysis result contains output file info
if (analysisResult.output_file && analysisResult.output_file.auto_saved) {
const outputFile = analysisResult.output_file;
// Show success message to user
showSuccessMessage(`HTML report saved as: ${outputFile.filename}`);
console.log('HTML report auto-saved:', outputFile.path);
} else {
console.log('No auto-save info found in analysis result, report may have been saved server-side');
showSuccessMessage('HTML report has been automatically saved to the output directory.');
}
} catch (error) {
console.error('Error with HTML report:', error);
}
}
// Generate HTML report content from analysis results
function generateHtmlReportContent(analysisResult) {
// This function is no longer needed since reports are generated server-side
return '<html><head><title>Report</title></head><body><h1>Report generated server-side</h1></body></html>';
}
// Display analysis results in the UI
function displayResults(data) {
try {
console.log('=== DISPLAY RESULTS DEBUG START ===');
console.log('Full data received:', JSON.stringify(data, null, 2));
// Validate data structure
if (!data || typeof data !== 'object') {
console.error('Invalid data structure:', data);
showError('Invalid analysis data received');
return;
}
console.log('Data validation passed');
// Extract summary information
const summary = data.summary || data.qc_analysis || {};
const qcAnalysis = data.qc_analysis || {};
const overallScore = summary.overall_score || 0;
const profileName = data.profile_selection?.suggested_profile || 'Unknown Profile';
const totalChecks = qcAnalysis.total_checks || Object.keys(qcAnalysis.check_results || {}).length;
const completedChecks = qcAnalysis.completed_checks || Object.keys(qcAnalysis.check_results || {}).length;
console.log('Extracted values:', { overallScore, profileName, totalChecks, completedChecks });
// Update results section
const resultsSection = document.getElementById('results-section');
if (resultsSection) {
resultsSection.style.display = 'block';
// Update overall score
const scoreElement = document.getElementById('overall-score');
if (scoreElement) {
scoreElement.textContent = overallScore + '/100';
scoreElement.style.color = overallScore >= 80 ? '#28a745' : overallScore >= 60 ? '#ffc107' : '#dc3545';
}
// Update grade
const gradeElement = document.getElementById('grade');
if (gradeElement) {
const grade = overallScore >= 85 ? 'Excellent' : overallScore >= 70 ? 'Good' : overallScore >= 50 ? 'Adequate' : 'Needs Improvement';
gradeElement.textContent = grade;
}
// Update checks count
const checksElement = document.getElementById('checks-count');
if (checksElement) {
checksElement.textContent = completedChecks + '/' + totalChecks;
}
// Update profile name
const profileElement = document.getElementById('profile-used');
if (profileElement) {
profileElement.textContent = profileName;
}
console.log('Results UI updated successfully');
} else {
console.warn('Results section not found in DOM');
}
// Show download button
const downloadBtn = document.getElementById('download-results');
if (downloadBtn) {
downloadBtn.style.display = 'block';
downloadBtn.onclick = () => generateHtmlReport(data, data.session_id, data.filename);
}
console.log('=== DISPLAY RESULTS DEBUG END ===');
} catch (error) {
console.error('Error displaying results:', error);
showError('Failed to display analysis results: ' + error.message);
}
}
// Show error message to user
function showError(message) {
console.error('Error:', message);
// Try to find an error display element
let errorElement = document.getElementById('error-message');
if (!errorElement) {
// Create error element if it doesn't exist
errorElement = document.createElement('div');
errorElement.id = 'error-message';
errorElement.style.cssText = 'background-color: #f8d7da; color: #721c24; padding: 15px; border: 1px solid #f5c6cb; border-radius: 8px; margin: 20px 0; font-weight: bold;';
// Insert at the top of the main container
const container = document.querySelector('.container') || document.body;
container.insertBefore(errorElement, container.firstChild);
}
errorElement.textContent = message;
errorElement.style.display = 'block';
// Hide error after 10 seconds
setTimeout(() => {
if (errorElement) {
errorElement.style.display = 'none';
}
}, 10000);
}
// Show success message to user
function showSuccessMessage(message) {
// Try to find a success display element
let successElement = document.getElementById('success-message');
if (!successElement) {
// Create success element if it doesn't exist
successElement = document.createElement('div');
successElement.id = 'success-message';
successElement.style.cssText = 'background-color: #d4edda; color: #155724; padding: 15px; border: 1px solid #c3e6cb; border-radius: 8px; margin: 20px 0; font-weight: bold;';
// Insert at the top of the main container
const container = document.querySelector('.container') || document.body;
container.insertBefore(successElement, container.firstChild);
}
successElement.textContent = message;
successElement.style.display = 'block';
// Hide success message after 8 seconds
setTimeout(() => {
if (successElement) {
successElement.style.display = 'none';
}
}, 8000);
}
// Settings functionality
function showSettings() {
console.log('Settings button clicked');
// Create modal if it doesn't exist
let modal = document.getElementById('settingsModal');
if (!modal) {
modal = document.createElement('div');
modal.id = 'settingsModal';
modal.innerHTML =
'<div class="modal-overlay" onclick="closeSettings()">' +
'<div class="modal-content" onclick="event.stopPropagation()">' +
'<div class="modal-header">' +
'<h2>Profile Management</h2>' +
'<button onclick="closeSettings()" class="close-btn">×</button>' +
'</div>' +
'<div class="modal-body">' +
'<div class="profile-tabs">' +
'<button class="tab-btn active" onclick="showTab(\'existing\')">Edit Existing Profile</button>' +
'<button class="tab-btn" onclick="showTab(\'new\')">Create New Profile</button>' +
'<button class="tab-btn" onclick="showTab(\'assets\')">Reference Assets</button>' +
'</div>' +
'<div id="existing-tab" class="tab-content active">' +
'<div style="margin-bottom: 15px;">' +
'<label>Select Profile to Edit:</label>' +
'<select id="profileSelect" onchange="loadSelectedProfile()" style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ccc;">' +
'<option value="">Select a profile...</option>' +
'</select>' +
'</div>' +
'<div id="profileEditor" style="display: none;">' +
'<div style="margin-bottom: 15px;">' +
'<label>Profile Name:</label>' +
'<input type="text" id="editProfileName" style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ccc;" readonly>' +
'</div>' +
'<div style="margin-bottom: 15px;">' +
'<label>Profile Description:</label>' +
'<textarea id="editProfileDescription" style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ccc; height: 60px;"></textarea>' +
'</div>' +
'<h4>QC Checks and Weights</h4>' +
'<div id="editQcChecksList" style="max-height: 300px; overflow-y: auto; border: 1px solid #dee2e6; border-radius: 8px; padding: 15px;"></div>' +
'</div>' +
'</div>' +
'<div id="new-tab" class="tab-content">' +
'<div style="margin-bottom: 15px;">' +
'<label>Profile Name:</label>' +
'<input type="text" id="newProfileName" placeholder="Enter profile name..." style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ccc;">' +
'</div>' +
'<div style="margin-bottom: 15px;">' +
'<label>Profile Description:</label>' +
'<textarea id="newProfileDescription" placeholder="Enter profile description..." style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ccc; height: 60px;"></textarea>' +
'</div>' +
'<h4>QC Checks and Weights</h4>' +
'<div id="newQcChecksList" style="max-height: 300px; overflow-y: auto; border: 1px solid #dee2e6; border-radius: 8px; padding: 15px;"></div>' +
'</div>' +
'<div id="assets-tab" class="tab-content">' +
'<div style="margin-bottom: 20px;">' +
'<h4 style="color: #495057; margin-bottom: 10px;">Upload Brand Guidelines</h4>' +
'<p style="color: #6c757d; font-size: 0.9em; margin-bottom: 15px;">Upload brand guidelines to enhance QC analysis for your profiles.</p>' +
'</div>' +
'<div style="border: 2px dashed #cbd5e0; border-radius: 12px; padding: 25px; background: #f7fafc; margin-bottom: 20px;">' +
'<div style="text-align: center; margin-bottom: 20px;">' +
'<div style="font-size: 2.5em; margin-bottom: 10px;">📄</div>' +
'<p style="margin: 0 0 5px 0; font-size: 1em; font-weight: 600;">Upload Brand Guidelines</p>' +
'<p style="margin: 0; color: #6c757d; font-size: 0.85em;">Add guidelines for enhanced analysis</p>' +
'</div>' +
'<form id="settingsBrandGuidelineForm" style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 15px;">' +
'<div>' +
'<label style="display: block; margin-bottom: 5px; font-weight: 600; color: #495057; font-size: 0.9em;">Brand Name *</label>' +
'<input type="text" id="settingsBrandName" placeholder="Enter brand name" style="width: 100%; padding: 8px; border: 2px solid #dee2e6; border-radius: 6px; font-size: 0.9em;">' +
'</div>' +
'<div>' +
'<label style="display: block; margin-bottom: 5px; font-weight: 600; color: #495057; font-size: 0.9em;">Tags (Optional)</label>' +
'<input type="text" id="settingsBrandTags" placeholder="logo, colors, typography" style="width: 100%; padding: 8px; border: 2px solid #dee2e6; border-radius: 6px; font-size: 0.9em;">' +
'</div>' +
'<div style="grid-column: 1 / -1;">' +
'<label style="display: block; margin-bottom: 5px; font-weight: 600; color: #495057; font-size: 0.9em;">Description (Optional)</label>' +
'<textarea id="settingsBrandDescription" placeholder="Brief description of this guideline file" style="width: 100%; padding: 8px; border: 2px solid #dee2e6; border-radius: 6px; height: 50px; resize: vertical; font-size: 0.9em;"></textarea>' +
'</div>' +
'</form>' +
'<div style="text-align: center;">' +
'<input type="file" id="settingsReferenceFileInput" accept=".pdf,.jpg,.png,.gif,.jpeg" style="display: none;">' +
'<button onclick="document.getElementById(\'settingsReferenceFileInput\').click()" style="background: #007bff; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-weight: 600; margin-right: 10px; font-size: 0.9em;">Choose File</button>' +
'<button onclick="uploadSettingsBrandGuideline()" id="settingsUploadBtn" style="background: #28a745; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-weight: 600; font-size: 0.9em;" disabled>Upload Guideline</button>' +
'</div>' +
'<div id="settingsSelectedFileInfo" style="margin-top: 15px; padding: 10px; background: #d4edda; border-radius: 6px; display: none;"></div>' +
'</div>' +
'<div id="settingsGuidelinesList" style="max-height: 300px; overflow-y: auto; border: 1px solid #dee2e6; border-radius: 8px; padding: 15px;">' +
'<p style="color: #6c757d; text-align: center;">Existing brand guidelines will be displayed here</p>' +
'</div>' +
'</div>' +
'<div style="text-align: right; padding-top: 15px; border-top: 1px solid #dee2e6;">' +
'<button onclick="saveProfile()" style="background: #28a745; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; margin-right: 10px;">Save Profile</button>' +
'<button onclick="deleteProfile()" id="deleteProfileBtn" style="background: #dc3545; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; margin-right: 10px; display: none;">Delete Profile</button>' +
'<button onclick="closeSettings()" style="background: #6c757d; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer;">Cancel</button>' +
'</div>' +
'</div>' +
'</div>' +
'</div>';
modal.style.cssText =
'position: fixed;' +
'top: 0;' +
'left: 0;' +
'width: 100%;' +
'height: 100%;' +
'z-index: 1000;' +
'display: none;';
document.body.appendChild(modal);
// Add modal styles
const modalStyles = document.createElement('style');
modalStyles.textContent =
'.modal-overlay {' +
'position: fixed;' +
'top: 0;' +
'left: 0;' +
'width: 100%;' +
'height: 100%;' +
'background: rgba(0,0,0,0.5);' +
'display: flex;' +
'justify-content: center;' +
'align-items: center;' +
'}' +
'.modal-content {' +
'background: white;' +
'border-radius: 8px;' +
'min-width: 600px;' +
'max-width: 800px;' +
'max-height: 80vh;' +
'overflow-y: auto;' +
'}' +
'.modal-header {' +
'display: flex;' +
'justify-content: space-between;' +
'align-items: center;' +
'padding: 20px;' +
'border-bottom: 1px solid #dee2e6;' +
'}' +
'.modal-header h2 {' +
'margin: 0;' +
'color: #333;' +
'}' +
'.close-btn {' +
'background: none;' +
'border: none;' +
'font-size: 24px;' +
'cursor: pointer;' +
'color: #666;' +
'padding: 0;' +
'width: 30px;' +
'height: 30px;' +
'display: flex;' +
'align-items: center;' +
'justify-content: center;' +
'}' +
'.close-btn:hover {' +
'color: #333;' +
'}' +
'.modal-body {' +
'padding: 20px;' +
'}' +
'.profile-tabs {' +
'display: flex;' +
'margin-bottom: 20px;' +
'border-bottom: 1px solid #dee2e6;' +
'}' +
'.tab-btn {' +
'background: none;' +
'border: none;' +
'padding: 10px 20px;' +
'cursor: pointer;' +
'border-bottom: 3px solid transparent;' +
'font-size: 14px;' +
'color: #666;' +
'}' +
'.tab-btn.active {' +
'color: #007bff;' +
'border-bottom-color: #007bff;' +
'}' +
'.tab-btn:hover {' +
'color: #007bff;' +
'}' +
'.tab-content {' +
'display: none;' +
'}' +
'.tab-content.active {' +
'display: block;' +
'}' +
'.check-item {' +
'display: grid;' +
'grid-template-columns: 1fr auto auto auto;' +
'gap: 10px;' +
'align-items: center;' +
'padding: 8px 0;' +
'border-bottom: 1px solid #f0f0f0;' +
'}' +
'.check-item:last-child {' +
'border-bottom: none;' +
'}' +
'.check-name {' +
'font-weight: 500;' +
'}' +
'.weight-input {' +
'width: 60px;' +
'padding: 4px;' +
'border: 1px solid #ccc;' +
'border-radius: 4px;' +
'text-align: center;' +
'}';
document.head.appendChild(modalStyles);
}
// Load profiles and QC apps when showing modal
loadProfileManagement();
modal.style.display = 'block';
}
function closeSettings() {
const modal = document.getElementById('settingsModal');
if (modal) {
modal.style.display = 'none';
}
}
// Profile management functions
let currentProfiles = {};
let currentQcApps = {};
async function loadProfileManagement() {
try {
// Load profiles and QC apps in parallel
const [profilesResponse, qcAppsResponse] = await Promise.all([
fetch(`${BASE_PATH}api/profiles`, { credentials: 'include' }),
fetch(`${BASE_PATH}api/qc-apps`, { credentials: 'include' })
]);
if (!profilesResponse.ok || !qcAppsResponse.ok) {
throw new Error('Failed to load profile management data');
}
const profilesData = await profilesResponse.json();
const qcAppsData = await qcAppsResponse.json();
console.log('Settings modal - profiles data:', profilesData);
console.log('Settings modal - qc apps data:', qcAppsData);
currentProfiles = profilesData.all_profiles || {};
currentQcApps = qcAppsData.qc_apps || {};
console.log('Settings modal - currentProfiles:', currentProfiles);
console.log('Settings modal - currentQcApps:', currentQcApps);
populateProfileSelect();
populateQcChecksLists();
} catch (error) {
console.error('Error loading profile management:', error);
showErrorMessage('Failed to load profile management data');
}
}
function populateProfileSelect() {
const profileSelect = document.getElementById('profileSelect');
if (!profileSelect) return;
profileSelect.innerHTML = '<option value="">Select a profile...</option>';
for (const [profileId, profile] of Object.entries(currentProfiles)) {
const option = document.createElement('option');
option.value = profileId;
option.textContent = profile.name;
profileSelect.appendChild(option);
}
}
function populateQcChecksLists() {
populateChecksForContainer('editQcChecksList');
populateChecksForContainer('newQcChecksList');
}
function populateChecksForContainer(containerId) {
const container = document.getElementById(containerId);
if (!container) return;
container.innerHTML = '';
// Create header
const headerDiv = document.createElement('div');
headerDiv.style.cssText = 'display: grid; grid-template-columns: 1fr auto auto auto; gap: 10px; padding: 10px; background: #f8f9fa; border-radius: 6px; margin-bottom: 10px; font-weight: bold; font-size: 0.9em;';
headerDiv.innerHTML = '<div>QC Check</div><div>Enabled</div><div>Weight</div><div></div>';
container.appendChild(headerDiv);
// Add each QC check
for (const [checkName, checkInfo] of Object.entries(currentQcApps)) {
const checkDiv = document.createElement('div');
checkDiv.className = 'check-item';
checkDiv.innerHTML =
'<div class="check-name">' + checkInfo.display_name + '</div>' +
'<input type="checkbox" class="check-enabled" data-check="' + checkName + '" checked>' +
'<input type="number" class="weight-input" data-check="' + checkName + '" value="0.1" min="0" max="1" step="0.01">' +
'<div></div>';
container.appendChild(checkDiv);
}
}
function showTab(tabName) {
// Update tab buttons
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
document.querySelector(`.tab-btn[onclick="showTab('${tabName}')"]`).classList.add('active');
// Update tab content
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
document.getElementById(tabName + '-tab').classList.add('active');
// Show/hide delete button
const deleteBtn = document.getElementById('deleteProfileBtn');
if (deleteBtn) {
deleteBtn.style.display = tabName === 'existing' ? 'inline-block' : 'none';
}
// Load brand guidelines when assets tab is shown
if (tabName === 'assets') {
loadSettingsBrandGuidelines();
// Set up file input handler for settings
const fileInput = document.getElementById('settingsReferenceFileInput');
const uploadBtn = document.getElementById('settingsUploadBtn');
if (fileInput && uploadBtn) {
fileInput.addEventListener('change', function() {
uploadBtn.disabled = !this.files.length;
const fileInfo = document.getElementById('settingsSelectedFileInfo');
if (this.files.length > 0) {
const file = this.files[0];
fileInfo.innerHTML = `<strong>Selected:</strong> ${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)`;
fileInfo.style.display = 'block';
} else {
fileInfo.style.display = 'none';
}
});
}
}
}
function loadSelectedProfile() {
const profileSelect = document.getElementById('profileSelect');
const selectedProfileId = profileSelect.value;
const profileEditor = document.getElementById('profileEditor');
const deleteBtn = document.getElementById('deleteProfileBtn');
if (!selectedProfileId) {
profileEditor.style.display = 'none';
deleteBtn.style.display = 'none';
return;
}
const profile = currentProfiles[selectedProfileId];
if (!profile) return;
// Show editor
profileEditor.style.display = 'block';
deleteBtn.style.display = 'inline-block';
// Populate profile info
document.getElementById('editProfileName').value = profile.name;
document.getElementById('editProfileDescription').value = profile.description || '';
// Update check weights
const container = document.getElementById('editQcChecksList');
const checkboxes = container.querySelectorAll('.check-enabled');
const weightInputs = container.querySelectorAll('.weight-input');
checkboxes.forEach(checkbox => {
const checkName = checkbox.dataset.check;
checkbox.checked = profile.enabled_checks ? profile.enabled_checks.includes(checkName) : false;
});
weightInputs.forEach(input => {
const checkName = input.dataset.check;
input.value = profile.weights ? (profile.weights[checkName] || 0) : 0;
});
}
async function saveProfile() {
try {
const activeTab = document.querySelector('.tab-content.active');
const isNewProfile = activeTab.id === 'new-tab';
let profileName, profileDescription, enabledChecks, weights;
if (isNewProfile) {
profileName = document.getElementById('newProfileName').value.trim();
profileDescription = document.getElementById('newProfileDescription').value.trim();
const container = document.getElementById('newQcChecksList');
enabledChecks = [];
weights = {};
container.querySelectorAll('.check-enabled').forEach(checkbox => {
if (checkbox.checked) {
enabledChecks.push(checkbox.dataset.check);
}
});
container.querySelectorAll('.weight-input').forEach(input => {
weights[input.dataset.check] = parseFloat(input.value) || 0;
});
} else {
const profileSelect = document.getElementById('profileSelect');
const selectedProfileId = profileSelect.value;
if (!selectedProfileId) {
alert('Please select a profile to edit');
return;
}
profileName = document.getElementById('editProfileName').value.trim();
profileDescription = document.getElementById('editProfileDescription').value.trim();
const container = document.getElementById('editQcChecksList');
enabledChecks = [];
weights = {};
container.querySelectorAll('.check-enabled').forEach(checkbox => {
if (checkbox.checked) {
enabledChecks.push(checkbox.dataset.check);
}
});
container.querySelectorAll('.weight-input').forEach(input => {
weights[input.dataset.check] = parseFloat(input.value) || 0;
});
}
if (!profileName) {
alert('Please enter a profile name');
return;
}
const profileData = {
name: profileName,
description: profileDescription,
enabled_checks: enabledChecks,
weights: weights
};
const endpoint = isNewProfile ? `${BASE_PATH}api/profiles` : `${BASE_PATH}api/profiles/${document.getElementById('profileSelect').value}`;
const method = isNewProfile ? 'POST' : 'PUT';
const response = await fetch(endpoint, {
method: method,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(profileData)
});
if (!response.ok) {
throw new Error(`Failed to save profile: ${response.status}`);
}
alert('Profile saved successfully!');
closeSettings();
// Refresh the profiles dropdown in the main UI
await loadProfiles();
} catch (error) {
console.error('Error saving profile:', error);
alert('Failed to save profile: ' + error.message);
}
}
async function deleteProfile() {
const profileSelect = document.getElementById('profileSelect');
const selectedProfileId = profileSelect.value;
if (!selectedProfileId) {
alert('Please select a profile to delete');
return;
}
if (!confirm('Are you sure you want to delete this profile? This action cannot be undone.')) {
return;
}
try {
const response = await fetch(`${BASE_PATH}api/profiles/${selectedProfileId}`, {
method: 'DELETE',
credentials: 'include'
});
if (!response.ok) {
throw new Error(`Failed to delete profile: ${response.status}`);
}
alert('Profile deleted successfully!');
closeSettings();
// Refresh the profiles dropdown in the main UI
await loadProfiles();
} catch (error) {
console.error('Error deleting profile:', error);
alert('Failed to delete profile: ' + error.message);
}
}
// Brand Guidelines Upload Functions (Settings Modal)
function uploadSettingsBrandGuideline() {
const fileInput = document.getElementById('settingsReferenceFileInput');
const brandName = document.getElementById('settingsBrandName').value;
const tags = document.getElementById('settingsBrandTags').value;
const description = document.getElementById('settingsBrandDescription').value;
if (!fileInput.files.length) {
showError('Please select a file to upload');
return;
}
if (!brandName.trim()) {
showError('Brand name is required');
return;
}
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('brand_name', brandName.trim());
formData.append('tags', tags.trim());
formData.append('description', description.trim());
// Show uploading state
const uploadBtn = document.getElementById('settingsUploadBtn');
const originalText = uploadBtn.textContent;
uploadBtn.textContent = 'Uploading...';
uploadBtn.disabled = true;
fetch(`${BASE_PATH}api/brand_guidelines`, {
method: 'POST',
credentials: 'include',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
showSuccessMessage('Brand guideline uploaded successfully!');
// Clear form
fileInput.value = '';
document.getElementById('settingsBrandName').value = '';
document.getElementById('settingsBrandTags').value = '';
document.getElementById('settingsBrandDescription').value = '';
document.getElementById('settingsSelectedFileInfo').style.display = 'none';
// Refresh brand guidelines list in settings
loadSettingsBrandGuidelines();
// Refresh main page reference assets dropdown to include new file
updateReferenceAssetsDropdown();
} else {
showError('Upload failed: ' + (data.message || 'Unknown error'));
}
})
.catch(error => {
console.error('Upload error:', error);
showError('Upload failed: ' + error.message);
})
.finally(() => {
uploadBtn.textContent = originalText;
uploadBtn.disabled = false;
});
}
// Load brand guidelines for settings modal
async function loadSettingsBrandGuidelines() {
try {
const response = await fetch(`${BASE_PATH}api/brand_guidelines`, {
credentials: 'include'
});
const data = await response.json();
console.log('Settings brand guidelines loaded:', data);
const guidelinesList = document.getElementById('settingsGuidelinesList');
if (!guidelinesList) return;
if (!data.brands || Object.keys(data.brands).length === 0) {
guidelinesList.innerHTML = '<p style="color: #6c757d; text-align: center;">No brand guidelines uploaded yet</p>';
return;
}
let html = '<h4 style="color: #495057; margin-bottom: 15px;">Existing Brand Guidelines</h4>';
for (const [brandName, brandData] of Object.entries(data.brands)) {
html += `<div style="margin-bottom: 20px;">`;
html += `<h5 style="color: #495057; margin-bottom: 10px; text-transform: capitalize;">${brandName} (${brandData.guidelines.length} files)</h5>`;
brandData.guidelines.forEach(guidelineId => {
const guideline = data.files && data.files[guidelineId];
if (guideline) {
html += `
<div style="background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 6px; padding: 10px; margin-bottom: 8px;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<strong>${guideline.original_filename || 'Unknown file'}</strong>
${guideline.description ? `<br><small style="color: #6c757d;">${guideline.description}</small>` : ''}
${guideline.tags && guideline.tags.length ? `<br><small style="color: #007bff;">Tags: ${guideline.tags.join(', ')}</small>` : ''}
</div>
<small style="color: #6c757d;">${new Date(guideline.upload_date).toLocaleDateString()}</small>
</div>
</div>
`;
}
});
html += `</div>`;
}
guidelinesList.innerHTML = html;
} catch (error) {
console.error('Error loading settings brand guidelines:', error);
const guidelinesList = document.getElementById('settingsGuidelinesList');
if (guidelinesList) {
guidelinesList.innerHTML = '<p style="color: #dc3545; text-align: center;">Error loading brand guidelines</p>';
}
}
}
// Update reference assets dropdown to show all uploaded files
async function updateReferenceAssetsDropdown() {
const referenceAssetSelect = document.getElementById('reference-asset-select');
if (!referenceAssetSelect) return;
try {
const response = await fetch(`${BASE_PATH}api/brand_guidelines`, {
credentials: 'include'
});
const data = await response.json();
referenceAssetSelect.innerHTML = '<option value="">No reference asset selected</option>';
// Show all uploaded files regardless of brand/profile
if (data.files && Object.keys(data.files).length > 0) {
// Group files by brand for better organization
const filesByBrand = {};
for (const [fileId, fileData] of Object.entries(data.files)) {
const brandName = fileData.brand_name || 'Unknown Brand';
if (!filesByBrand[brandName]) {
filesByBrand[brandName] = [];
}
filesByBrand[brandName].push({ id: fileId, data: fileData });
}
// Add files organized by brand
for (const [brandName, files] of Object.entries(filesByBrand)) {
// Create optgroup for better organization
const optgroup = document.createElement('optgroup');
optgroup.label = brandName.charAt(0).toUpperCase() + brandName.slice(1);
files.forEach(file => {
const option = document.createElement('option');
option.value = file.id;
option.textContent = file.data.original_filename || 'Unknown file';
if (file.data.description) {
option.title = file.data.description; // Show description on hover
}
optgroup.appendChild(option);
});
referenceAssetSelect.appendChild(optgroup);
}
} else {
// No files available
referenceAssetSelect.innerHTML = '<option value="">No reference assets available</option>';
}
} catch (error) {
console.error('Error loading reference assets:', error);
referenceAssetSelect.innerHTML = '<option value="">Error loading reference assets</option>';
}
}
// MSAL Authentication Configuration
const msalConfig = {
auth: {
clientId: "9079054c-9620-4757-a256-23413042f1ef",
authority: "https://login.microsoftonline.com/e519c2e6-bc6d-4fdf-8d9c-923c2f002385",
redirectUri: window.location.hostname === 'localhost' ? 'http://localhost:7183' : window.location.origin + window.location.pathname.replace(/\/$/, '')
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: true,
}
};
const loginRequest = {
scopes: ["openid", "profile", "email"],
prompt: "select_account"
};
// Initialize MSAL instance
let myMSALObj;
let currentUser = null;
let isAuthenticated = false;
let msalInitialized = false;
try {
if (typeof msal === 'undefined') {
throw new Error('MSAL library is not available. Please check your internet connection.');
}
myMSALObj = new msal.PublicClientApplication(msalConfig);
msalInitialized = true;
console.log('MSAL initialized successfully');
} catch (error) {
console.error('Error initializing MSAL:', error);
msalInitialized = false;
}
// Authentication functions
async function signIn() {
if (!msalInitialized || !myMSALObj) {
console.error('MSAL not initialized properly');
alert('Authentication system not available. Please check your connection and try again.');
showLoginButton();
return;
}
try {
showAuthLoading();
const loginResponse = await myMSALObj.loginPopup(loginRequest);
console.log('Login successful:', loginResponse);
// Send token to server for validation
const idToken = loginResponse.idToken;
const response = await fetch(`${BASE_PATH}auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ token: idToken })
});
const result = await response.json();
if (result.success) {
currentUser = result.user;
isAuthenticated = true;
updateAuthUI();
console.log('Authentication successful:', currentUser);
// Initialize app functionality after successful authentication
init();
updateReferenceAssetsDropdown();
} else {
throw new Error(result.error || 'Authentication failed');
}
} catch (error) {
console.error('Sign-in error:', error);
alert('Authentication failed: ' + error.message);
showLoginButton();
}
}
async function signOut() {
try {
// Clear server-side session
await fetch(`${BASE_PATH}auth/logout`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
// Clear client-side tokens (only if MSAL is available)
if (msalInitialized && myMSALObj) {
await myMSALObj.logoutPopup({
postLogoutRedirectUri: window.location.origin + window.location.pathname
});
}
currentUser = null;
isAuthenticated = false;
// Reload page to reset state
window.location.reload();
} catch (error) {
console.error('Sign-out error:', error);
// Even if there's an error, reset the UI
currentUser = null;
isAuthenticated = false;
window.location.reload();
}
}
async function checkAuthStatus() {
try {
showAuthLoading();
const response = await fetch(`${BASE_PATH}auth/status`);
const status = await response.json();
if (status.authenticated && status.user) {
currentUser = status.user;
isAuthenticated = true;
updateAuthUI();
} else {
isAuthenticated = false;
showAuthRequired();
}
return isAuthenticated;
} catch (error) {
console.error('Auth status check failed:', error);
isAuthenticated = false;
showAuthRequired();
return false;
}
}
// UI update functions
function showAuthLoading() {
document.getElementById('authLoading').style.display = 'block';
document.getElementById('loginBtn').style.display = 'none';
document.getElementById('userInfo').style.display = 'none';
document.getElementById('mainApp').style.display = 'none';
document.getElementById('authRequired').style.display = 'block';
}
function showLoginButton() {
document.getElementById('authLoading').style.display = 'none';
document.getElementById('loginBtn').style.display = 'block';
document.getElementById('userInfo').style.display = 'none';
document.getElementById('mainApp').style.display = 'none';
document.getElementById('authRequired').style.display = 'block';
}
function updateAuthUI() {
document.getElementById('authLoading').style.display = 'none';
document.getElementById('loginBtn').style.display = 'none';
document.getElementById('userInfo').style.display = 'block';
document.getElementById('mainApp').style.display = 'block';
document.getElementById('authRequired').style.display = 'none';
if (currentUser) {
document.getElementById('userName').textContent = currentUser.name || 'User';
document.getElementById('userEmail').textContent = currentUser.email || '';
}
}
function showAuthRequired() {
document.getElementById('authLoading').style.display = 'none';
document.getElementById('loginBtn').style.display = 'none';
document.getElementById('userInfo').style.display = 'none';
document.getElementById('mainApp').style.display = 'none';
document.getElementById('authRequired').style.display = 'block';
}
// Protect form functionality
function requireAuthentication(callback) {
if (!isAuthenticated) {
alert('Please sign in to use this feature.');
showAuthRequired();
return false;
}
return callback();
}
// Block all form interactions until authenticated
function blockUnauthenticatedAccess(event) {
if (!isAuthenticated) {
event.preventDefault();
event.stopPropagation();
alert('Please sign in to access this feature.');
return false;
}
return true;
}
// Initialize the application when the page loads
document.addEventListener('DOMContentLoaded', async function() {
console.log('=== DOM CONTENT LOADED EVENT FIRED ===');
console.log('Document ready state:', document.readyState);
try {
// Set up authentication event listeners
document.getElementById('loginBtn').addEventListener('click', signIn);
document.getElementById('logoutBtn').addEventListener('click', signOut);
document.getElementById('authRequiredLoginBtn').addEventListener('click', signIn);
// Check authentication status first
console.log('Checking authentication status...');
const authenticated = await checkAuthStatus();
// Only initialize app functionality if authenticated
if (authenticated) {
console.log('User authenticated, initializing app...');
init();
updateReferenceAssetsDropdown();
} else {
console.log('User not authenticated, showing auth required screen');
}
console.log('DOMContentLoaded handlers complete');
} catch (error) {
console.error('Error in DOMContentLoaded handler:', error);
}
});
</script>
</body>
</html>