| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Joblin - Settings</title> |
| <link rel="stylesheet" href="styles.css"> |
| </head> |
| <body> |
| <div class="app-layout"> |
| <aside class="sidebar" id="sidebar"> |
| <a href="dashboard.html" class="sidebar-brand"> |
| <div class="brand-icon">J</div> |
| <h1>Job<span>lin</span></h1> |
| </a> |
| <nav class="sidebar-nav"> |
| <div class="sidebar-section-title">Main</div> |
| <a href="dashboard.html" class="nav-item"> |
| <span class="nav-icon">■</span> |
| <span class="nav-label">Dashboard</span> |
| </a> |
| <a href="jobs.html" class="nav-item"> |
| <span class="nav-icon">▶</span> |
| <span class="nav-label">All Jobs</span> |
| </a> |
| <a href="manual-job.html" class="nav-item"> |
| <span class="nav-icon">+</span> |
| <span class="nav-label">Manual Entry</span> |
| </a> |
| <div class="sidebar-section-title">My Profile</div> |
| <a href="make-cv.html" class="nav-item"> |
| <span class="nav-icon">✍</span> |
| <span class="nav-label">Make My CV</span> |
| </a> |
| <a href="cv-editor.html" class="nav-item"> |
| <span class="nav-icon">✎</span> |
| <span class="nav-label">Edit CV</span> |
| </a> |
| <a href="settings.html" class="nav-item active"> |
| <span class="nav-icon">⚙</span> |
| <span class="nav-label">Settings</span> |
| </a> |
| <a href="about.html" class="nav-item"> |
| <span class="nav-icon"> |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg> |
| </span> |
| <span class="nav-label">About</span> |
| </a> |
| <div class="admin-only" style="display:none"> |
| <div class="sidebar-section-title">Admin</div> |
| <a href="dashboard.html" class="nav-item"> |
| <span class="nav-icon"> |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg> |
| </span> |
| <span class="nav-label">Admin Panel</span> |
| </a> |
| </div> |
| </nav> |
| <div class="sidebar-footer"> |
| <div class="sidebar-user"> |
| <div class="user-avatar" id="avatarInitial">U</div> |
| <div class="user-details"> |
| <span class="user-name" id="user-name"></span> |
| <span class="user-email" id="user-email"></span> |
| </div> |
| </div> |
| <button class="logout-btn" id="adminToggle" onclick="toggleAdminMode()" style="display:none"> |
| <span> |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg> |
| </span> |
| <span id="adminToggleLabel">Admin View</span> |
| </button> |
| <button class="logout-btn" onclick="logout()"> |
| <span>➜</span> |
| <span>Sign Out</span> |
| </button> |
| </div> |
| </aside> |
|
|
| <div class="main-content"> |
| <div class="top-bar"> |
| <button class="mobile-nav-toggle" onclick="document.getElementById('sidebar').classList.toggle('open')">☰</button> |
| </div> |
|
|
| <div class="page-content"> |
| <div class="page-header"> |
| <div> |
| <h2>Settings</h2> |
| <p class="subtitle">Configure your API keys and account</p> |
| </div> |
| </div> |
|
|
| <div class="card"> |
| <div class="card-title"> |
| <div class="card-icon">🔑</div> |
| AI Provider |
| </div> |
|
|
| <div class="form-group"> |
| <label class="toggle-label"> |
| <input type="checkbox" id="useDefaultApi" checked> |
| <span class="toggle-slider"></span> |
| <span><strong>Use default Nemotron AI (free)</strong><br> |
| <span style="font-weight:400;color:var(--text-subtle);font-size:12px">NVIDIA Nemotron 3 Ultra — free CV rewriting, editing, making & cover letters. No API key needed.</span></span> |
| </label> |
| </div> |
|
|
| <div id="personalKeysSection"> |
| <p style="font-size:12px;color:var(--text-muted);margin-bottom:14px"> |
| Provide your own API keys for AI-powered CV tailoring. Get free keys from |
| <a href="https://console.groq.com/keys" target="_blank">Groq</a>, |
| <a href="https://build.nvidia.com/" target="_blank">NVIDIA NIM</a>, or |
| <a href="https://aistudio.google.com/app/apikey" target="_blank">Google AI Studio</a>. |
| Your keys are stored encrypted and never shared. |
| </p> |
|
|
| <div class="form-group"> |
| <label>Groq API Key <span style="font-weight:400;color:var(--text-subtle)">(fastest, free 14,400 req/day)</span></label> |
| <div class="key-input"> |
| <span class="key-status" id="groqStatus"></span> |
| <input type="password" id="groqKey" placeholder="gsk_..."> |
| </div> |
| </div> |
|
|
| <div class="form-group"> |
| <label>NVIDIA NIM API Key <span style="font-weight:400;color:var(--text-subtle)">(fallback)</span></label> |
| <div class="key-input"> |
| <span class="key-status" id="nvidiaStatus"></span> |
| <input type="password" id="nvidiaKey" placeholder="nvapi-..."> |
| </div> |
| </div> |
|
|
| <div class="form-group"> |
| <label>Gemini API Key <span style="font-weight:400;color:var(--text-subtle)">(2nd fallback)</span></label> |
| <div class="key-input"> |
| <span class="key-status" id="geminiStatus"></span> |
| <input type="password" id="geminiKey" placeholder="AIza..."> |
| </div> |
| </div> |
| </div> |
|
|
| <button class="btn btn-primary" onclick="saveKeys()" id="keysBtn">Save Settings</button> |
| </div> |
|
|
| <div class="card"> |
| <div class="card-title"> |
| <div class="card-icon">👤</div> |
| Account |
| </div> |
| <p style="font-size:13px;color:var(--text-muted)">Logged in as <strong id="accountEmail"></strong></p> |
| <p style="font-size:12px;color:var(--text-subtle);margin-top:4px">When "Use default Nemotron AI" is ON, all CV features use the free shared Nemotron model. Toggle off to use your personal API keys instead.</p> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script src="app.js"></script> |
| <script> |
| async function loadKeys() { |
| try { |
| const keys = await apiFetch("GET", "/api/keys"); |
| document.getElementById("groqKey").value = keys.groq || ""; |
| document.getElementById("nvidiaKey").value = keys.nvidia || ""; |
| document.getElementById("geminiKey").value = keys.gemini || ""; |
| document.getElementById("useDefaultApi").checked = keys.use_default_api !== false; |
| updateStatus("groqStatus", !!keys.groq); |
| updateStatus("nvidiaStatus", !!keys.nvidia); |
| updateStatus("geminiStatus", !!keys.gemini); |
| togglePersonalSection(); |
| } catch (err) { showToast(err.message, "error"); } |
| } |
| |
| function updateStatus(elId, isSet) { |
| const el = document.getElementById(elId); |
| el.className = `key-status ${isSet ? "set" : "unset"}`; |
| el.title = isSet ? "Key is set" : "No key configured"; |
| } |
| |
| function togglePersonalSection() { |
| const useDefault = document.getElementById("useDefaultApi").checked; |
| const inputs = document.querySelectorAll("#personalKeysSection input"); |
| for (const inp of inputs) inp.disabled = useDefault; |
| const section = document.getElementById("personalKeysSection"); |
| section.style.opacity = useDefault ? "0.4" : "1"; |
| section.style.pointerEvents = useDefault ? "none" : "auto"; |
| } |
| |
| document.getElementById("useDefaultApi").addEventListener("change", togglePersonalSection); |
| |
| async function saveKeys() { |
| const btn = document.getElementById("keysBtn"); |
| btn.disabled = true; btn.innerHTML = '<span class="spinner"></span> Saving...'; |
| try { |
| const res = await apiFetch("PUT", "/api/keys", { |
| groq: document.getElementById("groqKey").value.trim(), |
| nvidia: document.getElementById("nvidiaKey").value.trim(), |
| gemini: document.getElementById("geminiKey").value.trim(), |
| use_default_api: document.getElementById("useDefaultApi").checked, |
| }); |
| document.getElementById("groqKey").value = res.groq || ""; |
| document.getElementById("nvidiaKey").value = res.nvidia || ""; |
| document.getElementById("geminiKey").value = res.gemini || ""; |
| document.getElementById("useDefaultApi").checked = res.use_default_api !== false; |
| updateStatus("groqStatus", !!res.groq); |
| updateStatus("nvidiaStatus", !!res.nvidia); |
| updateStatus("geminiStatus", !!res.gemini); |
| togglePersonalSection(); |
| showToast("Settings saved"); |
| } catch (err) { showToast(err.message, "error"); } |
| btn.disabled = false; btn.textContent = "Save Settings"; |
| } |
| |
| if (!getToken()) window.location.href = "login.html"; |
| updateHeader(); |
| const user = getCurrentUser(); |
| if (user) document.getElementById("accountEmail").textContent = user.email || user.name; |
| loadKeys(); |
| </script> |
| </body> |
| </html> |
|
|