Spaces:
Runtime error
Runtime error
| {% extends "base.html" %} | |
| {% block title %}API Documentation β Resume Ranker API{% endblock %} | |
| {% block extra_head %} | |
| {# No extra head elements are needed here. Your base.html or dashboard.css should handle the main CSS. #} | |
| <style> | |
| /* Specific styling for the documentation content within the main content area */ | |
| .content-padding h2, | |
| .content-padding h4 { | |
| color: var(--heading-color); /* Ensure consistent heading colors */ | |
| margin-bottom: 1rem; | |
| } | |
| .content-padding p { | |
| color: var(--text-color); | |
| line-height: 1.6; | |
| margin-bottom: 1rem; | |
| } | |
| .content-padding pre { | |
| background-color: var(--code-bg); | |
| color: var(--code-text); | |
| padding: 1rem; | |
| border-radius: 0.5rem; | |
| overflow-x: auto; | |
| position: relative; /* For copy button positioning */ | |
| margin-bottom: 1.5rem; | |
| border: 1px solid var(--border); | |
| } | |
| .content-padding code { | |
| font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; | |
| font-size: 0.9em; | |
| } | |
| .content-padding ul { | |
| list-style: disc; | |
| margin-left: 1.5rem; | |
| margin-bottom: 1rem; | |
| color: var(--text-color); | |
| } | |
| .content-padding ul li { | |
| margin-bottom: 0.5rem; | |
| } | |
| .content-padding .text-muted { | |
| font-size: 0.85rem; | |
| color: var(--text-muted); /* Define in your variables */ | |
| } | |
| /* Copy button specific styles */ | |
| .content-padding .btn.copy-btn-docs { | |
| position: absolute; | |
| top: 0.5rem; | |
| right: 0.5rem; | |
| padding: 0.3rem 0.7rem; | |
| font-size: 0.75rem; | |
| background-color: var(--primary-color); | |
| color: var(--btn-text-color); | |
| border: none; | |
| border-radius: 0.3rem; | |
| cursor: pointer; | |
| transition: background-color 0.2s ease; | |
| z-index: 1; /* Ensure button is above text */ | |
| } | |
| .content-padding .btn.copy-btn-docs:hover { | |
| background-color: var(--primary-hover); | |
| } | |
| /* Form specific styles (reusing .form-group, .form-label, .form-input) */ | |
| .content-padding .form-group { | |
| margin-bottom: 1.5rem; | |
| } | |
| .content-padding textarea.form-input { | |
| min-height: 120px; | |
| resize: vertical; | |
| } | |
| /* Table specific styles (reusing .table.table-bordered) */ | |
| .content-padding .table-bordered { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin-top: 1.5rem; | |
| } | |
| .content-padding .table-bordered th, | |
| .content-padding .table-bordered td { | |
| border: 1px solid var(--border); | |
| padding: 0.8rem 1rem; | |
| text-align: left; | |
| } | |
| .content-padding .table-bordered th { | |
| background-color: var(--table-header-bg); | |
| color: var(--table-header-text); | |
| } | |
| .content-padding .table-bordered tbody tr:nth-child(even) { | |
| background-color: var(--table-row-even-bg); | |
| } | |
| .content-padding .table-bordered tbody tr:hover { | |
| background-color: var(--table-row-hover-bg); | |
| } | |
| /* Responsive table for API docs */ | |
| @media (max-width: 768px) { | |
| .content-padding .table-bordered, | |
| .content-padding .table-bordered tbody, | |
| .content-padding .table-bordered tr, | |
| .content-padding .table-bordered td { | |
| display: block; | |
| width: 100%; | |
| } | |
| .content-padding .table-bordered thead { | |
| display: none; | |
| } | |
| .content-padding .table-bordered tr { | |
| margin-bottom: 1rem; | |
| border: 1px solid var(--border); | |
| border-radius: 0.5rem; | |
| overflow: hidden; | |
| } | |
| .content-padding .table-bordered td { | |
| border: none; | |
| border-bottom: 1px solid var(--border-light); | |
| position: relative; | |
| padding-left: 50%; | |
| text-align: right; | |
| } | |
| .content-padding .table-bordered td::before { | |
| content: attr(data-label); | |
| position: absolute; | |
| left: 0; | |
| width: 45%; | |
| padding-left: 1rem; | |
| font-weight: 600; | |
| text-align: left; | |
| color: var(--text-muted); | |
| } | |
| } | |
| /* Margin adjustments */ | |
| .content-padding .mt-3 { margin-top: 1rem; } | |
| .content-padding .mt-4 { margin-top: 1.5rem; } | |
| .content-padding .mb-4 { margin-bottom: 1.5rem; } | |
| .content-padding .my-5 { margin-top: 3rem; margin-bottom: 3rem; } | |
| </style> | |
| {% endblock %} | |
| {% block content %} | |
| <div class="dashboard-wrapper"> | |
| <aside class="sidebar" id="sidebar"> | |
| <div class="sidebar-header"> | |
| <div class="logo">ResumeRanker<span style="color:var(--accent)">.API</span></div> | |
| <button class="close-sidebar" id="closeSidebar" aria-label="Close menu"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg> | |
| </button> | |
| </div> | |
| <nav class="sidebar-nav"> | |
| {# Use Flask's request.endpoint to set 'active' class #} | |
| <a href="{{ url_for('auth.dashboard') }}" class="nav-item {% if request.endpoint == 'auth.dashboard' %}active{% endif %}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-grid"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg> | |
| <span>Dashboard</span> | |
| </a> | |
| <a href="{{ url_for('auth.api_keys') }}" class="nav-item {% if request.endpoint == 'auth.api_keys' %}active{% endif %}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-key"><path d="M19 10H5a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2z"></path><circle cx="12" cy="16" r="2"></circle><path d="M15 10V4.5a2.5 2.5 0 0 0-5 0V10"></path></svg> | |
| <span>My API Keys</span> | |
| </a> | |
| <a href="#" class="nav-item {% if request.endpoint == 'auth.resume_rankings' %}active{% endif %}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-award"><circle cx="12" cy="8" r="7"></circle><polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.89"></polyline></svg> | |
| <span>Resume Rankings</span> | |
| </a> | |
| <a href="{{ url_for('auth.try_api') }}" class="nav-item {% if request.endpoint == 'auth.try_api' %}active{% endif %}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-play-circle"><circle cx="12" cy="12" r="10"></circle><polygon points="10 8 16 12 10 16 10 8"></polygon></svg> | |
| <span>Try It Now</span> | |
| </a> | |
| <a href="{{ url_for('auth.api_docs') }}" class="nav-item"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-book-open"> | |
| <path d="M2 3h6a4 4 0 0 1 4 4v13H6a4 4 0 0 0-4-4z"></path> | |
| <path d="M22 3h-6a4 4 0 0 0-4 4v13h6a4 4 0 0 1 4-4z"></path> | |
| </svg> | |
| <span>API Docs</span> | |
| </a> | |
| <a href="{{ url_for('auth.settings') }}" class="nav-item {% if request.endpoint == 'auth.settings' %}active{% endif %}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-settings"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l-.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg> | |
| <span>Settings</span> | |
| </a> | |
| <a href="{{ url_for('auth.logout') }}" class="nav-item logout-item"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-log-out"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="17 16 22 12 17 8"></polyline><line x1="22" y1="12" x2="10" y2="12"></line></svg> | |
| <span>Logout</span> | |
| </a> | |
| </nav> | |
| </aside> | |
| <div class="main-content-area" id="mainContentArea"> | |
| <header class="dashboard-header"> | |
| <button class="hamburger-dashboard" id="hamburgerDashboard" aria-label="Open sidebar menu"> | |
| <span></span><span></span><span></span> | |
| </button> | |
| <div class="user-settings"> | |
| <span class="user-name">{{ user.username }}</span> | |
| <div class="user-avatar"> | |
| <img src="https://ui-avatars.com/api/?name={{ user.username | urlencode }}&background=2563eb&color=fff&size=30" alt="User Avatar"> | |
| </div> | |
| <button class="theme-toggle" id="themeToggleDashboard" aria-label="Toggle dark mode">π</button> | |
| </div> | |
| </header> | |
| <div class="content-padding"> | |
| <h1 class="welcome-title">API Documentation</h1> {# Changed from welcome-title to just a title for the page #} | |
| <p class="subtitle">Detailed guide on how to integrate and use the Resume Ranker API.</p> | |
| {% if error %} | |
| <div class="error-message">{{ error }}</div> | |
| {% endif %} | |
| {% if success %} | |
| <div class="success-message">{{ success }}</div> | |
| {% endif %} | |
| <div class="section-card api-docs-section"> {# Wrap documentation in a card for consistent look #} | |
| <h2 class="mb-4">π Resume Ranker API Details</h2> | |
| <h4>π Endpoint</h4> | |
| <pre><code class="copyable">POST https://rankapply-api.hf.space/rank-resumes</code></pre> | |
| <h4>π Authentication</h4> | |
| <p>Include your API key in the <code>Authorization</code> header:</p> | |
| <pre><code class="copyable">Authorization: Bearer your_api_key_here</code></pre> | |
| <p class="text-muted"><small>Never share your API key publicly. Treat it like a password.</small></p> | |
| <h4>Request Parameters</h4> | |
| <ul> | |
| <li><strong>resumes</strong> (file[], required): One or more PDF or DOCX resumes.</li> | |
| <li><strong>job_description</strong> (text, required): The job description to match against.</li> | |
| </ul> | |
| <h4>π₯ Example Request (cURL)</h4> | |
| <pre><code class="copyable">curl -X POST https://rankapply-api.hf.space/rank-resumes \ | |
| -H "Authorization: Bearer your_api_key_here" \ | |
| -F "resumes=@cv1.pdf" \ | |
| -F "resumes=@cv2.pdf" \ | |
| -F "job_description=We are hiring a backend engineer..."</code></pre> | |
| <h4>π₯ Example Request (Python)</h4> | |
| <pre><code class="copyable">import requests | |
| headers = { | |
| "Authorization": "Bearer your_api_key_here" | |
| } | |
| files = [ | |
| ('resumes', ('cv1.pdf', open('cv1.pdf', 'rb'), 'application/pdf')), | |
| ('resumes', ('cv2.pdf', open('cv2.pdf', 'rb'), 'application/pdf')), | |
| ] | |
| data = { | |
| "job_description": "We are hiring a backend engineer..." | |
| } | |
| response = requests.post("https://rankapply-api.hf.space/rank-resumes", headers=headers, files=files, data=data) | |
| print(response.json())</code></pre> | |
| <h4> Sample Response</h4> | |
| <pre><code class="copyable">{ | |
| "job_description": "We are hiring a backend engineer...", | |
| "ranked_resumes": [ | |
| { | |
| "filename": "cv2.pdf", | |
| "score": 0.8745 | |
| }, | |
| { | |
| "filename": "cv1.pdf", | |
| "score": 0.6721 | |
| } | |
| ] | |
| }</code></pre> | |
| </div> | |
| <hr class="my-5"> | |
| <div class="section-card api-tester-form"> {# Reusing the card for the "Try It Now" section #} | |
| <h3 class="section-title">Try It Now</h3> | |
| <p class="section-description">Use the form below to quickly test the API directly from your browser.</p> | |
| <form method="POST" action="{{ url_for('auth.try_api') }}" enctype="multipart/form-data"> | |
| <div class="form-group"> | |
| <label for="api_key" class="form-label">API Key</label> | |
| <input type="text" class="form-input" id="api_key" name="api_key" | |
| placeholder="your_api_key_here" value="{{ user.api_key | default('') }}" required> | |
| </div> | |
| <div class="form-group"> | |
| <label for="resumes" class="form-label">Upload Resumes (PDF, DOCX)</label> | |
| <input type="file" class="form-input" id="resumes" name="resumes" multiple accept=".pdf,.doc,.docx" required> | |
| </div> | |
| <div class="form-group"> | |
| <label for="job_description" class="form-label">Job Description</label> | |
| <textarea class="form-input" id="job_description" name="job_description" rows="4" required></textarea> | |
| </div> | |
| <button type="submit" class="btn btn-success">Submit Test Request</button> | |
| </form> | |
| </div> | |
| {% if response %} | |
| <div class="section-card api-results"> | |
| <h3 class="section-title">π Resume Ranking Results</h3> | |
| <p class="section-description"><strong>Job Description:</strong> {{ response.job_description }}</p> | |
| <div class="table-responsive"> | |
| <table class="table table-bordered"> | |
| <thead> | |
| <tr> | |
| <th>Filename</th> | |
| <th>Score</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {% for r in response.ranked_resumes %} | |
| <tr> | |
| <td data-label="Filename">{{ r.filename }}</td> | |
| <td data-label="Score">{{ r.score }}</td> | |
| </tr> | |
| {% endfor %} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| {% endif %} | |
| <p class="mt-4"><a href="{{ url_for('auth.dashboard') }}" class="btn btn-secondary">β Back to Dashboard</a></p> | |
| </div> | |
| </div> | |
| </div> | |
| {% endblock %} | |
| {% block extra_scripts %} | |
| <script> | |
| // Theme toggle (specific to dashboard header) | |
| const themeToggleDashboard = document.getElementById('themeToggleDashboard'); | |
| const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; | |
| function setTheme(theme) { | |
| document.documentElement.setAttribute('data-theme', theme); | |
| localStorage.setItem('theme', theme); | |
| themeToggleDashboard.textContent = theme === 'dark' ? 'βοΈ' : 'π'; | |
| } | |
| const savedTheme = localStorage.getItem('theme'); | |
| setTheme(savedTheme || (prefersDark ? 'dark' : 'light')); | |
| themeToggleDashboard.onclick = () => { | |
| const current = document.documentElement.getAttribute('data-theme'); | |
| setTheme(current === 'dark' ? 'light' : 'dark'); | |
| }; | |
| // Sidebar toggle for mobile | |
| const hamburgerDashboard = document.getElementById('hamburgerDashboard'); | |
| const sidebar = document.getElementById('sidebar'); | |
| const mainContentArea = document.getElementById('mainContentArea'); | |
| const closeSidebarBtn = document.getElementById('closeSidebar'); | |
| hamburgerDashboard.onclick = () => { | |
| sidebar.classList.add('open'); | |
| mainContentArea.classList.add('shifted'); | |
| }; | |
| closeSidebarBtn.onclick = () => { | |
| sidebar.classList.remove('open'); | |
| mainContentArea.classList.remove('shifted'); | |
| }; | |
| mainContentArea.onclick = (event) => { | |
| if (sidebar.classList.contains('open') && !sidebar.contains(event.target) && !hamburgerDashboard.contains(event.target)) { | |
| sidebar.classList.remove('open'); | |
| mainContentArea.classList.remove('shifted'); | |
| } | |
| }; | |
| Array.from(sidebar.getElementsByClassName('nav-item')).forEach(item => { | |
| item.onclick = () => { | |
| if (window.innerWidth <= 900) { | |
| sidebar.classList.remove('open'); | |
| mainContentArea.classList.remove('shifted'); | |
| } | |
| }; | |
| }); | |
| // Copy Button Script for code blocks | |
| document.addEventListener('DOMContentLoaded', function() { | |
| document.querySelectorAll('pre').forEach(preBlock => { | |
| const codeBlock = preBlock.querySelector('code.copyable'); | |
| if (codeBlock) { | |
| const button = document.createElement('button'); | |
| button.innerText = 'Copy'; | |
| button.className = 'btn copy-btn-docs'; | |
| button.addEventListener('click', () => { | |
| navigator.clipboard.writeText(codeBlock.innerText).then(() => { | |
| button.innerText = 'Copied!'; | |
| setTimeout(() => button.innerText = 'Copy', 1500); | |
| }).catch(err => { | |
| console.error('Failed to copy text: ', err); | |
| alert('Failed to copy text. Please copy manually.'); | |
| }); | |
| }); | |
| preBlock.appendChild(button); | |
| } | |
| }); | |
| }); | |
| </script> | |
| {% endblock %} | |