568 lines
22 KiB
Python
568 lines
22 KiB
Python
"""WebUI Views for Context7 Docs using Jinja2 templates."""
|
|
import os
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any, Optional
|
|
from fastapi import Request
|
|
from fastapi.responses import HTML, JSONResponse
|
|
import requests
|
|
|
|
# Internal API base URL
|
|
DOCS_API_URL = os.environ.get("DOCS_API_URL", "http://docs-api:8787")
|
|
|
|
|
|
def api_request(method: str, endpoint: str, data: Optional[dict] = None) -> dict:
|
|
"""Make internal API request to docs-api."""
|
|
url = f"{DOCS_API_URL}{endpoint}"
|
|
headers = {}
|
|
if os.environ.get("WEBUI_API_KEY"):
|
|
headers["X-API-Key"] = os.environ.get("WEBUI_API_KEY")
|
|
|
|
resp = requests.request(method, url, headers=headers, json=data)
|
|
return resp.json()
|
|
|
|
|
|
def navbar_html(current: str) -> str:
|
|
"""Generate navigation bar HTML."""
|
|
links = [
|
|
("/health", "Health"),
|
|
("/libraries", "Libraries"),
|
|
("/upload", "Upload"),
|
|
("/ingest/all", "Ingest All"),
|
|
("/sources/git", "Git Sources"),
|
|
("/search", "Search"),
|
|
]
|
|
items = []
|
|
for path, label in links:
|
|
cls = "active" if current == path else ""
|
|
items.append(f'<a href="{path}" class="{cls}">{label}</a>')
|
|
return f"""<nav>
|
|
{' '.join(items)}
|
|
</nav>""".strip()
|
|
|
|
|
|
def footer_html() -> str:
|
|
"""Generate footer HTML."""
|
|
return "<footer>Context7 Docs WebUI</footer>"
|
|
|
|
|
|
def health(request: Request) -> HTML:
|
|
"""System health dashboard."""
|
|
try:
|
|
data = api_request("GET", "/health")
|
|
status = data.get("status", "unknown")
|
|
service = data.get("service", "Service")
|
|
except Exception as e:
|
|
status = "error"
|
|
service = str(e)
|
|
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Context7 Docs - Health</title>
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/health")}</header>
|
|
<main><h2>System Health</h2>
|
|
<div class="status-card" data-status="{status}"><h3>{service}</h3>
|
|
<p>Status: <span class="status-ok">{status}</span></p></div>
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
|
|
|
|
def libraries(request: Request) -> HTML:
|
|
"""List all libraries."""
|
|
try:
|
|
data = api_request("GET", "/libraries")
|
|
libs = data.get("libraries", [])
|
|
except Exception as e:
|
|
libs = [{"id": "error", "name": str(e)}]
|
|
|
|
table_rows = []
|
|
for lib in libs:
|
|
if lib.get("id") != "error":
|
|
table_rows.append(
|
|
f"""<tr><td>{lib.get('id')}</td>
|
|
<td>{lib.get('name', '')}</td>
|
|
<td>{lib.get('description', '') or '(no description)'}</td>
|
|
<td><a href="/docs/{lib.get('id')}">View Docs</a></td></tr>"""
|
|
)
|
|
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Context7 Docs - Libraries</title>
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/libraries")}</header>
|
|
<main>
|
|
<h2>Libraries ({len(libs)})</h2>
|
|
<div class="actions-bar">
|
|
<form action="/folders/create" method="post" style="display:inline;">
|
|
<input type="text" name="name" placeholder="New library folder name" required>
|
|
<button type="submit">Create Folder</button>
|
|
</form>
|
|
</div>
|
|
<table class="library-table">
|
|
<thead><tr><th>ID</th><th>Name</th><th>Description</th><th>Actions</th></tr></thead>
|
|
<tbody>{"".join(table_rows)}</tbody>
|
|
</table>
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
|
|
|
|
def upload(request: Request) -> HTML:
|
|
"""File upload form."""
|
|
if "file" in request.files:
|
|
uploaded_file = request.files["file"]
|
|
try:
|
|
content = uploaded_file.read().decode("utf-8")[:5000]
|
|
# Escape HTML
|
|
safe_content = content.replace("&", "&").replace("<", "<").replace(">", ">")
|
|
truncated = safe_content[:1000] + "..." if len(safe_content) > 1000 else safe_content
|
|
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Context7 Docs - Upload</title>
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/upload")}</header>
|
|
<main>
|
|
<h2>Upload Complete!</h2>
|
|
<pre class="content-preview">{truncated}</pre>
|
|
<form method="post" action="/ingest/uploaded">
|
|
<input type="hidden" name="content" value="{safe_content[:5000]}">
|
|
<label for="library_id">Library (optional):</label>
|
|
<input type="text" id="library_id" name="library_id" placeholder="e.g., my-docs">
|
|
<button type="submit">Ingest</button>
|
|
</form>
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
except Exception:
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Context7 Docs - Upload</title>
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/upload")}</header>
|
|
<main>
|
|
<h2>File too large!</h2>
|
|
<p>Please upload smaller text files (limit: ~5MB).</p>
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
else:
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Context7 Docs - Upload</title>
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/upload")}</header>
|
|
<main>
|
|
<h2>Upload Documentation Files</h2>
|
|
<form method="post" enctype="multipart/form-data">
|
|
<label for="file">Select file:</label>
|
|
<input type="file" name="file" id="file" accept=".txt,.md,.json,.py,.js,.html,.css,.yaml,.yml" required>
|
|
<button type="submit">Upload</button>
|
|
</form>
|
|
<p class="hint">Supported formats: .txt, .md, .json, .py, .js, .html, .css, .yaml</p>
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
|
|
|
|
def ingest_all(request: Request) -> JSONResponse:
|
|
"""Trigger ingestion for all libraries."""
|
|
try:
|
|
result = api_request("POST", "/ingest")
|
|
return JSONResponse(content={"status": "ok", "message": f"Processed {result.get('chunks', 0)} chunks"})
|
|
except Exception as e:
|
|
return JSONResponse(status_code=500, content={"error": str(e)})
|
|
|
|
|
|
def ingest_library(request: Request, library_id: str) -> HTML:
|
|
"""Ingest for specific library."""
|
|
if "content" in request.form:
|
|
content = request.form.get("content")[:10000]
|
|
safe_content = content.replace("&", "&").replace("<", "<").replace(">", ">")
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Context7 Docs - Ingest</title>
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/ingest/{library_id}")}</header>
|
|
<main>
|
|
<h2>Ingest for Library: {library_id}</h2>
|
|
<form method="post" action="/ingest/{library_id}">
|
|
<label for="content">Content (text):</label>
|
|
<textarea id="content" name="content" rows="10" maxlength="10000"></textarea>
|
|
<button type="submit">Ingest</button>
|
|
</form>
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
else:
|
|
try:
|
|
result = api_request("POST", f"/ingest/{library_id}")
|
|
safe_msg = result.get('message', '') or ''
|
|
safe_json = json.dumps(result, indent=2).replace("&", "&").replace("<", "<").replace(">", ">")
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Context7 Docs - Ingest Result</title>
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/ingest/{library_id}")}</header>
|
|
<main>
|
|
<h2>Ingestion Complete!</h2>
|
|
<p>{safe_msg}</p>
|
|
<pre>{safe_json}</pre>
|
|
<a href="/libraries">← Back to Libraries</a>
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
except Exception as e:
|
|
safe_error = str(e).replace("&", "&").replace("<", "<").replace(">", ">")
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Context7 Docs - Error</title>
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/ingest/{library_id}")}</header>
|
|
<main>
|
|
<h2>Error</h2>
|
|
<pre>{safe_error}</pre>
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
|
|
|
|
async def folders_create(request: Request) -> JSONResponse:
|
|
"""Create a new library folder."""
|
|
name = request.form.get("name", "").strip()
|
|
try:
|
|
from backend.app.db import upsert_library
|
|
await upsert_library(library_id=name, name=name, description=None, source_path=f"/docs/{name}")
|
|
return JSONResponse(content={"status": "ok", "message": f"Created folder '{name}'"})
|
|
except Exception as e:
|
|
return JSONResponse(status_code=500, content={"error": str(e)})
|
|
|
|
|
|
async def folders_delete(request: Request) -> JSONResponse:
|
|
"""Delete a library."""
|
|
library_id = request.query_params.get("id", "").strip()
|
|
try:
|
|
from backend.app.db import delete_library
|
|
await delete_library(library_id)
|
|
return JSONResponse(content={"status": "ok", "message": f"Deleted library '{library_id}'"})
|
|
except Exception as e:
|
|
return JSONResponse(status_code=500, content={"error": str(e)})
|
|
|
|
|
|
async def ingest_uploaded(request: Request) -> HTML:
|
|
"""Ingest uploaded file content."""
|
|
content = request.form.get("content", "")[:10000]
|
|
library_id = request.form.get("library_id", "uploaded")
|
|
|
|
try:
|
|
result = api_request("POST", f"/ingest/{library_id}", data={"content": content})
|
|
safe_msg = result.get('message', '') or ''
|
|
safe_json = json.dumps(result, indent=2).replace("&", "&").replace("<", "<").replace(">", ">")
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Context7 Docs - Upload Result</title>
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/upload")}</header>
|
|
<main>
|
|
<h2>Ingestion Complete!</h2>
|
|
<p>{safe_msg}</p>
|
|
<pre>{safe_json}</pre>
|
|
<a href="/upload">← Upload Another</a>
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
except Exception as e:
|
|
safe_error = str(e).replace("&", "&").replace("<", "<").replace(">", ">")
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head><meta charset="UTF-8"><title>Error</title></head>
|
|
<body><h1>Upload Ingest Error</h1><pre>{safe_error}</pre><a href="/upload">← Try Again</a></body>
|
|
</html>""", media_type="text/html")
|
|
|
|
|
|
def docs(request: Request, library_id: str, topic: Optional[str] = None, tokens: int = 8000) -> HTML:
|
|
"""View docs from a library."""
|
|
try:
|
|
data = api_request("GET", f"/libraries/{library_id}/docs", params={"topic": topic, "tokens": tokens})
|
|
content = data.get("content", "")
|
|
except Exception as e:
|
|
content = str(e)
|
|
|
|
safe_content = content.replace("&", "&").replace("<", "<").replace(">", ">")[:10000]
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Context7 Docs - Library: {library_id}</title>
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/docs/{}".format(library_id))}</header>
|
|
<main>
|
|
<h2>Library: {library_id}</h2>
|
|
<p><strong>Topic:</strong> {topic or '(all)'} | <strong>Tokens:</strong> {tokens}</p>
|
|
<pre class="docs-content">{safe_content}</pre>
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
|
|
|
|
def search_redirect(request: Request) -> JSONResponse:
|
|
"""Redirect to search form."""
|
|
return JSONResponse(content={"redirect": "/search/form"})
|
|
|
|
|
|
def search_form(request: Request) -> HTML:
|
|
"""Search form page."""
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Context7 Docs - Search</title>
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/search")}</header>
|
|
<main>
|
|
<h2>Search Docs</h2>
|
|
<form method="post" action="/search">
|
|
<label for="query">Query:</label>
|
|
<input type="text" id="query" name="query" required placeholder="Enter your search query...">
|
|
<label for="library_id">Library (optional):</label>
|
|
<input type="text" id="library_id" name="library_id" placeholder="e.g., foundryvtt">
|
|
<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>
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
|
|
|
|
def search_results(request: Request) -> HTML:
|
|
"""Display search results."""
|
|
try:
|
|
query = request.query_params.get("q", "")
|
|
limit = int(request.query_params.get("limit", "10"))
|
|
payload = {"query": query, "library_id": None, "limit": limit}
|
|
result = api_request("POST", "/search", data=payload)
|
|
results = result.get("results", [])
|
|
except Exception as e:
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head><meta charset="UTF-8"><title>Error</title></head>
|
|
<body><h1>Error</h1><pre>{str(e)}</pre><a href="/search/form">← Try Again</a></body>
|
|
</html>""", media_type="text/html")
|
|
|
|
cards = []
|
|
for r in results:
|
|
title = r.get("title", "Untitled") or (r.get("content", "")[:100] + "...")[:200]
|
|
content = (r.get("content", "") or r.get("chunk", ""))[:500]
|
|
cards.append(f"""<div class="result-card" data-id="{r.get('id')}"><h3>{title}</h3>
|
|
<p>{content}...</p><a href="/docs/{r.get('library_id')}">View Full</a></div>""")
|
|
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Context7 Docs - Search Results</title>
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/search")}</header>
|
|
<main>
|
|
<h2>Search Results for "{query}"</h2>
|
|
<div class="results-count">{len(results)} results found</div>
|
|
{''.join(cards)}
|
|
<a href="/search/form">← New Search</a>
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
|
|
|
|
def sync_sources(request: Request) -> HTML:
|
|
"""Sync git sources."""
|
|
if request.method == "POST":
|
|
try:
|
|
data = api_request("POST", "/sources/sync")
|
|
safe_json = json.dumps(data, indent=2).replace("&", "&").replace("<", "<").replace(">", ">")
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head><meta charset="UTF-8"><title>Sync Result</title></head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/sync/sources")}</header>
|
|
<main><h2>Git Sync Complete!</h2><pre>{safe_json}</pre>
|
|
<form method="post"><button type="submit">Sync Again</button></form>
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
except Exception as e:
|
|
safe_error = str(e).replace("&", "&").replace("<", "<").replace(">", ">")
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head><meta charset="UTF-8"><title>Error</title></head>
|
|
<body><h1>Sync Error</h1><pre>{safe_error}</pre><a href="/sources/git">← Try Again</a></body>
|
|
</html>""", media_type="text/html")
|
|
else:
|
|
try:
|
|
data = api_request("GET", "/libraries")
|
|
libs = [l.get("id") for l in data.get("libraries", []) if l.get("id") != "error"]
|
|
except Exception:
|
|
libs = []
|
|
|
|
lib_list = ", ".join(libs) if libs else "(none)"
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Context7 Docs - Git Sync</title>
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/sources/git")}</header>
|
|
<main>
|
|
<h2>Sync Git Repositories</h2>
|
|
<p>Syncs all git repositories configured in <code>docs_sources.yaml</code>.</p>
|
|
<form method="post" action="/sync/sources">
|
|
<label for="override">Override existing repos:</label>
|
|
<input type="checkbox" id="override" name="override">
|
|
<button type="submit">Sync All Repositories</button>
|
|
</form>
|
|
<h3>Libraries Found: {lib_list}</h3>
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
|
|
|
|
def git_sources(request: Request) -> HTML:
|
|
"""List configured git sources."""
|
|
import yaml
|
|
config_path = Path(__file__).parent.parent.parent / "docs_sources.yaml"
|
|
|
|
try:
|
|
with open(config_path) as f:
|
|
data = yaml.safe_load(f)
|
|
sources = data.get("sources", [])
|
|
|
|
source_blocks = []
|
|
for src in sources:
|
|
url = src.get("repo_url", "")[:50] + "..." if len(src.get("repo_url", "")) > 50 else src.get("repo_url", "")
|
|
branch = src.get("branch", "main")
|
|
include = src.get("include_paths", ["*"])
|
|
exclude = src.get("exclude_paths", [])
|
|
source_blocks.append(f"""<div class="source-card">
|
|
<strong>{src.get('library_id', 'unknown')}</strong><br>
|
|
URL: {url}<br>
|
|
Branch: {branch}<br>
|
|
Include: {', '.join(include)}{' | Exclude: ' + ', '.join(exclude) if exclude else ''}
|
|
</div>""")
|
|
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Context7 Docs - Git Sources</title>
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/sources/git")}</header>
|
|
<main>
|
|
<h2>Configured Git Sources ({len(sources)})</h2>
|
|
{''.join(source_blocks)}
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
except Exception as e:
|
|
safe_error = str(e).replace("&", "&").replace("<", "<").replace(">", ">")
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head><meta charset="UTF-8"><title>Error</title></head>
|
|
<body><h1>Git Sources Error</h1><pre>{safe_error}</pre></body>
|
|
</html>""", media_type="text/html")
|
|
|
|
|
|
def logs(request: Request) -> HTML:
|
|
"""Logs/status page."""
|
|
return HTML(f"""<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Context7 Docs - Logs</title>
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header><h1>Context7 Docs UI</h1>{navbar_html("/logs")}</header>
|
|
<main>
|
|
<h2>Status Messages</h2>
|
|
<div class="status-message">Docs API: {DOCS_API_URL}</div>
|
|
<div class="status-message">Qdrant Health: healthy | MCP OK: yes</div>
|
|
<p class="hint">Logs are printed to container stdout/stderr. For full logs, inspect Docker containers directly.</p>
|
|
</main>{footer_html()}</div>
|
|
</body></html>""", media_type="text/html")
|
|
|
|
|
|
# Register all routes
|
|
__all__ = [
|
|
"health", "libraries", "upload", "ingest_all", "ingest_library",
|
|
"folders_create", "folders_delete", "docs", "search_redirect",
|
|
"search_form", "search_results", "sync_sources", "git_sources", "logs"
|
|
] |