Initial DocsMCP stack

This commit is contained in:
george
2026-06-05 23:02:55 +01:00
commit 421b6f973a
51 changed files with 7414 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Context7 Docs{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', path='style.css') }}">
</head>
<body>
<div class="container">
<header>
<h1>Context7 Docs UI</h1>
<nav>
<a href="/" {% if request.url.path == '/' %}class="active"{% endif %}>Dashboard</a>
<a href="/libraries" {% if request.url.path.startswith('/libraries') %}class="active"{% endif %}>Libraries</a>
<a href="/upload" {% if request.url.path.startswith('/upload') %}class="active"{% endif %}>Upload</a>
<a href="/search" {% if request.url.path.startswith('/search') %}class="active"{% endif %}>Search</a>
<a href="/sources" {% if request.url.path.startswith('/sources') %}class="active"{% endif %}>Sources</a>
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>Context7 Docs WebUI</footer>
</div>
<script src="{{ url_for('static', path='app.js') }}"></script>
{% block scripts %}{% endblock %}
</body>
</html>
+83
View File
@@ -0,0 +1,83 @@
{% extends "base.html" %}
{% block title %}Dashboard - Context7 Docs{% endblock %}
{% block content %}
<h1>Dashboard</h1>
<!-- Status Cards -->
<div class="status-cards">
<div class="status-card" style="{% if health.status == 'ok' %}border-left-color: #00c467{% else %}border-left-color: #f53800{% endif %}">
<h3>Docs API Service</h3>
{% if health.status and health.status == 'ok' %}
<p style="color: #00c467;"><strong>Status:</strong> Online ✓</p>
{% else %}
<p style="color: #f53800;"><strong>Status:</strong> {% if health.status == 'error' %}Error{% else %}Offline{% endif %}</p>
{% endif %}
</div>
<div class="status-card">
<h3>Vectors Stored</h3>
<p>{{ vectors|default(0) }}</p>
</div>
<div class="status-card">
<h3>Libraries Registered</h3>
<p>{{ libraries|length }}</p>
</div>
</div>
<!-- Recent Messages -->
{% if libraries and libraries|length > 0 %}
<div class="message-box" style="background: #e8f4fd;">
<strong>Libraries:</strong> {{ escapeHtml(libraries) }}
</div>
{% endif %}
<!-- Action Buttons -->
<div class="action-buttons">
<form method="post" action="/actions/ingest-all" style="display: inline;">
<button type="submit" name="ingest-all" class="btn btn-primary">
🔄 Ingest All Libraries
</button>
</form>
<form method="post" action="/actions/sync-sources" style="display: inline;">
<input type="hidden" name="override" value="false">
<button type="submit" name="sync-sources" class="btn btn-secondary">
📦 Sync Git Sources
</button>
</form>
</div>
<!-- Links -->
<div class="links-section">
<h2>Navigate to Other Pages</h2>
<a href="/libraries" style="display: inline-block; margin-right: 15px;">View Libraries →</a>
<a href="/upload" style="display: inline-block; margin-right: 15px;">Upload Files →</a>
<a href="/search" style="display: inline-block; margin-right: 15px;">Search Docs →</a>
<a href="/sources" style="display: inline-block;">Git Sources →</a>
</div>
<!-- Script for health refresh on reload -->
<script>
// On page reload, re-fetch and update status if needed
document.addEventListener("DOMContentLoaded", async function() {
try {
const api = window.docsApiClient;
// Refresh health status from server-rendered data
document.querySelector('.status-cards .status-card:first-of-type')?.classList.remove('error');
const newHealth = await api.get("/health");
if (newHealth.status === 'ok') {
document.querySelector('.status-cards .status-card:first-of-type')?.querySelector('p')?.classList.add('online');
} else {
document.querySelector('.status-cards .status-card:first-of-type')?.querySelector('p')?.classList.add('error');
}
} catch (err) {
console.log('Health refresh skipped:', err);
}
});
</script>
{% endblock %}
+74
View File
@@ -0,0 +1,74 @@
{% extends "base.html" %}
{% block title %}Libraries - Context7 Docs{% endblock %}
{% block content %}
<h1>Libraries</h1>
<!-- Create Library Form -->
<div class="create-form">
<form method="post" action="/libraries/create">
<label for="new_library_id">Library ID:</label>
<input type="text" id="new_library_id" name="library_id" placeholder="e.g., foundryvtt" required>
<label for="new_name">Name:</label>
<input type="text" id="new_name" name="name" placeholder="Display name for this library" required>
<label for="new_description">Description (optional):</label>
<input type="text" id="new_description" name="description" placeholder="Brief description...">
<button type="submit" class="btn btn-primary">Create Library</button>
</form>
</div>
<hr>
<!-- Libraries Table -->
<table class="library-table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Description</th>
<th>Source Path</th>
<th>Updated At</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="libraries-body">
{% if data|length > 0 %}
{% for lib in data %}
<tr class="{% if lib.source_path and 'foundry' in (lib.source_path or '').lower() %}highlight{% endif %}">
<td><code>{{ escapeHtml(lib.id) }}</code></td>
<td><strong>{{ escapeHtml(lib.name) }}</strong></td>
<td>{{ escapeHtml(lib.description) or '-' }}</td>
<td><small>{{ escapeHtml(lib.source_path) or '-' }}</small></td>
<td><small>{{ lib.updated_at|default('N/A') }}</small></td>
<td class="actions">
<a href="/libraries/{{ lib.id }}/docs" class="btn btn-sm btn-info">View Docs</a> |
<form method="post" action="/libraries/{{ lib.id }}/ingest" style="display:inline;"
onsubmit="return confirm('Trigger ingestion for this library?');">
<button type="submit" class="btn btn-sm btn-warning">Ingest</button>
</form> |
<form method="post" action="/libraries/{{ lib.id }}/delete"
onsubmit="return confirm('Delete this library and all its contents? This cannot be undone.');">
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
</form>
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="6" style="text-align:center;">No libraries found. Create one above.</td>
</tr>
{% endif %}
</tbody>
</table>
{% if data and data[0] and data[0].get('content') %}
<!-- Docs view mode -->
<pre class="code-block">{% for chunk in data.get('content', []) %}{% if chunk|length > 0 %}{{ chunk.text | default(chunk.content) | default(chunk) }}{% endif %}{% endfor %}</pre>
<a href="/libraries" style="display:block;margin-top:20px;">← Back to Libraries</a>
{% endif %}
{% endblock %}
+71
View File
@@ -0,0 +1,71 @@
{% extends "base.html" %}
{% block title %}Search - Context7 Docs{% endblock %}
{% block content %}
<h2>Search Documentation</h2>
<form method="get" action="/search/results" class="search-form">
<label for="query">Query:</label>
<input type="text" id="query" name="q" required placeholder="Enter your search query..." value="{{ query or '' }}">
<label for="limit">Limit results:</label>
<select id="limit" name="limit">
<option value="5">5</option>
<option value="10" selected>10</option>
<option value="20">20</option>
<option value="50">50</option>
</select>
<button type="submit">Search</button>
</form>
<div id="search-results" class="results-box"></div>
{% if results %}
<div class="results-count">{{ results|length }} results found</div>
{% endif %}
<script>
async function loadResults(query, limit) {
const searchBox = document.getElementById("search-results");
try {
const payload = { query: query || "{{ initial_query or '' }}", library_id: null, limit: parseInt(limit) };
const api = window.docsApiClient;
const result = await api.post("/search", payload);
if (result.results && Array.isArray(result.results)) {
searchBox.className = "results-box";
let html = '<div class="results-count">' + result.results.length + ' results found</div>';
for (const r of result.results) {
const title = r.title || (r.content || '').substring(0, 100);
const content = (r.content || '').substring(0, 500);
html += '<div class="result-card">' +
'<h3>' + escapeHtml(title) + '</h3>' +
'<p>' + escapeHtml(content) + '...</p>' +
'<a href="/docs/' + (r.library_id || '') + '">View Full</a></div>';
}
html += '<a href="/search/form" class="new-search-link">← New Search</a>';
searchBox.innerHTML = html;
}
} catch (err) {
searchBox.innerHTML = '<p class="error">Error loading results: ' + escapeHtml(err.message) + '</p>';
}
}
// Load initial results if query parameter exists in URL
var urlParams = new URLSearchParams(window.location.search);
{% if query %}loadResults(urlParams.get('q') || urlParams.get('q'), urlParams.get('limit'));{% endif %}
function escapeHtml(str) {
if (!str) return "";
var e = document.createElement('div');
e.textContent = str;
return e.innerHTML;
}
</script>
{% endblock %}
+34
View File
@@ -0,0 +1,34 @@
{% extends "base.html" %}
{% block title %}Sources - Context7 Docs{% endblock %}
{% block content %}
<h2>Git Repository Sync</h2>
<div class="status-message">Syncs all git repositories configured in <code>docs_sources.yaml</code>.</div>
<form method="post" action="/sources/sync" class="sync-form">
<label for="override">Override existing repos:</label>
<input type="checkbox" id="override" name="override">
<button type="submit">Sync All Repositories</button>
</form>
<div id="source-list"></div>
{% if sources %}
<h3>Configured Sources</h3>
<div class="source-cards">
{% for src in sources %}
<div class="source-card">
<strong>{{ src.library_id | default('unknown') }}</strong><br>
URL: {{ src.repo_url | default('N/A')[:60] }}<br>
Branch: {{ src.branch | default('main') }}<br>
Include: {{ (src.include_paths | default(['*']) | join(', ')) }}
</div>
{% endfor %}
</div>
{% else %}
<p>No git sources configured. Add repositories to <code>docs_sources.yaml</code>.</p>
{% endif %}
{% endblock %}
+48
View File
@@ -0,0 +1,48 @@
{% extends "base.html" %}
{% block title %}Upload - Context7 Docs{% endblock %}
{% block content %}
<h2>Upload Documentation Files</h2>
<form method="post" enctype="multipart/form-data" class="upload-form">
<!-- Library Selector -->
<label for="library_id">Select Library:</label>
<select id="library_id" name="library_id" required>
<option value="">(New library - will be created from filename)</option>
{% for lib in libraries %}
<option value="{{ lib.id }}" data-name="{{ lib.name or lib.id }}">{{ lib.name or lib.id }}</option>
{% endfor %}
</select>
<!-- File Input (multiple files allowed) -->
<label for="files">Select Files:</label>
<input type="file" name="files" id="files" multiple accept=".md,.txt,.py,.js,.ts,.json,.yaml,.yml,.html,.css,.pdf" required>
<!-- Ingest Checkbox -->
<div style="margin-top: 10px;">
<label>
<input type="checkbox" name="ingest_after_upload" value="on">
Trigger ingestion after upload
</label>
</div>
<button type="submit" class="btn btn-primary">Upload Files</button>
</form>
<!-- Allowed extensions hint -->
<p class="hint">Allowed: .md, .txt, .py, .js, .ts, .json, .yaml, .yml, .html, .css, .pdf (max 5MB each)</p>
<!-- Results Display -->
<div id="upload-result" class="result-box"></div>
{% if results %}
<h3>Upload Results</h3>
<ul>
{% for result in results %}
<li><strong>{{ result.filename }}</strong>: {{ result.status }} - {{ escapeHtml(result.message) }}</li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}