Download frontend/settings.html from Britzzy/joblin: direct link, hf CLI and curl.
- Browser
- Download file 9.61 kB
-
https://huggingface.co/spaces/Britzzy/joblin/resolve/main/frontend/settings.html
- Command line
-
hf download hf://spaces/Britzzy/joblin/frontend/settings.html
-
curl -L -o settings.html https://huggingface.co/spaces/Britzzy/joblin/resolve/main/frontend/settings.html
9.61 kB
| <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> | |