webcodelab's picture
Update app/templates/api_docs.html
c2906d4 verified
Raw
History Blame Contribute Delete
19 kB
{% 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 %}