diff --git a/webui/app/main.py b/webui/app/main.py index 9aefd13..acc9335 100644 --- a/webui/app/main.py +++ b/webui/app/main.py @@ -1,8 +1,11 @@ """WebUI FastAPI application.""" +import asyncio import html import os +import uuid +from datetime import datetime, timezone from pathlib import Path -from typing import List, Optional +from typing import Any, Dict, List, Optional from fastapi import FastAPI, File, Form, Request, UploadFile from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse @@ -23,6 +26,8 @@ templates.env.globals["escapeHtml"] = lambda value: html.escape(str(value or "") app.mount("/static", StaticFiles(directory=os.path.join(os.path.dirname(__file__), "static")), name="static") _client: Optional[DocsAPIClient] = None +_sync_jobs: Dict[str, Dict[str, Any]] = {} +_sync_tasks: set[asyncio.Task] = set() def get_client() -> DocsAPIClient: @@ -35,6 +40,25 @@ def get_client() -> DocsAPIClient: return _client +def utc_now() -> str: + return datetime.now(timezone.utc).isoformat() + + +async def run_sync_job(job_id: str, override: bool) -> None: + job = _sync_jobs[job_id] + job["status"] = "running" + job["started_at"] = utc_now() + try: + result = await get_client().post("/sources/sync", json={"override": override}) + job["result"] = result + job["status"] = "succeeded" if result.get("success") else "failed" + except Exception as exc: + job["status"] = "failed" + job["error"] = str(exc) + finally: + job["finished_at"] = utc_now() + + @app.on_event("shutdown") async def shutdown() -> None: if _client is not None: @@ -300,10 +324,33 @@ async def add_source( @app.post("/sources/sync") async def sync_sources(override: bool = Form(False)): - client = get_client() - try: - result = await client.post("/sources/sync", json={"override": override}) - body = f"

Git Sync Complete

{html.escape(str(result))}
Back" - except Exception as e: - body = f"

Git Sync Failed

{html.escape(str(e))}
Back" - return page("Git Sync", body) + job_id = uuid.uuid4().hex + _sync_jobs[job_id] = { + "id": job_id, + "status": "queued", + "created_at": utc_now(), + "started_at": None, + "finished_at": None, + "result": None, + "error": None, + } + task = asyncio.create_task(run_sync_job(job_id, override)) + _sync_tasks.add(task) + task.add_done_callback(_sync_tasks.discard) + return RedirectResponse(url=f"/sources/jobs/{job_id}", status_code=303) + + +@app.get("/sources/jobs/{job_id}") +async def sync_job_page(request: Request, job_id: str): + job = _sync_jobs.get(job_id) + if job is None: + return page("Git Sync Not Found", "

Git Sync Not Found

Back") + return templates.TemplateResponse("sync_job.html", {"request": request, "job": job}) + + +@app.get("/sources/jobs/{job_id}/status") +async def sync_job_status(job_id: str): + job = _sync_jobs.get(job_id) + if job is None: + return JSONResponse(status_code=404, content={"error": "Sync job not found"}) + return job diff --git a/webui/app/templates/sync_job.html b/webui/app/templates/sync_job.html new file mode 100644 index 0000000..d95f2fc --- /dev/null +++ b/webui/app/templates/sync_job.html @@ -0,0 +1,51 @@ +{% extends "base.html" %} + +{% block title %}Git Sync Status - Context7 Docs{% endblock %} + +{% block content %} +

Git Sync Status

+ +
+ Status: {{ job.status }} +
+ +{% if job.status in ['queued', 'running'] %} +

Cloning, reading, embedding, and indexing documents. Large repositories can take several minutes.

+

This page updates automatically. You can leave it open or return later using the same URL.

+{% elif job.error %} +

Sync Failed

+
{{ job.error }}
+{% elif job.result %} +

Summary

+ + +{% for result in job.result.results | default([]) %} +
+ {{ result.library_id | default('unknown') }}
+ Status: {{ 'succeeded' if result.success else 'failed' }}
+ {% if result.success %} + Files discovered: {{ result.files_discovered | default(0) }}
+ Chunks created: {{ result.chunks_created | default(0) }}
+ Vectors added: {{ result.vectors_added | default(0) }}
+ View indexed documents + {% else %} + Error: {{ result.error | default('Unknown ingestion error') }} + {% endif %} +
+{% endfor %} +{% endif %} + +

Back to sources | View libraries | Test search

+{% endblock %} + +{% block scripts %} +{% if job.status in ['queued', 'running'] %} + +{% endif %} +{% endblock %}