#!/usr/bin/env python3 """ Research Engine Dashboard โ€” FastAPI web UI Runs on CT 145 port 8000 """ import os import json import httpx from fastapi import FastAPI, Request, Query from fastapi.responses import HTMLResponse, JSONResponse from fastapi.staticfiles import StaticFiles from dotenv import load_dotenv load_dotenv(os.path.expanduser("~/ai-research-engine/.env")) app = FastAPI(title="AI Research Engine") OPENSEARCH_URL = os.getenv("OPENSEARCH_URL", "http://localhost:9200") QDRANT_URL = os.getenv("QDRANT_URL", "http://10.30.20.68:6333") YACY_URL = os.getenv("YACY_URL", "http://localhost:8090") INDEX_NAME = "research_docs" client = httpx.Client(timeout=10.0) @app.get("/", response_class=HTMLResponse) async def dashboard(): return HTMLResponse(""" AI Research Engine

๐Ÿง  AI Research Engine

Self-hosted knowledge acquisition
""") @app.get("/api/status") async def status(): svc = {} # OpenSearch try: r = client.get(f"{OPENSEARCH_URL}/_cluster/health") h = r.json() cnt = client.post(f"{OPENSEARCH_URL}/{INDEX_NAME}/_count").json() svc["opensearch"] = {"status": h.get("status"), "documents": cnt.get("count", 0)} except Exception as e: svc["opensearch"] = {"status": "down", "error": str(e)} # Qdrant try: r = client.get(f"{QDRANT_URL}/health") if r.status_code == 200: col = client.get(f"{QDRANT_URL}/collections/{INDEX_NAME}").json() svc["qdrant"] = {"status": "ok", "points": col.get("result", {}).get("points_count", 0)} except Exception as e: svc["qdrant"] = {"status": "down", "error": str(e)} # YaCy try: r = client.get(f"{YACY_URL}/api/status.json") svc["yacy"] = {"status": r.json().get("status", "unknown")} except Exception as e: svc["yacy"] = {"status": "down", "error": str(e)} # Ollama try: r = httpx.get("http://10.30.20.186:11434/api/tags", timeout=5.0) svc["ollama"] = {"status": "ok", "models": len(r.json().get("models", []))} except Exception as e: svc["ollama"] = {"status": "down", "error": str(e)} return {"services": svc} @app.get("/api/search") async def search(q: str = Query(...), limit: int = 10): try: r = client.post(f"{OPENSEARCH_URL}/{INDEX_NAME}/_search", json={ "size": limit, "query": {"multi_match": {"query": q, "fields": ["title^3", "content", "excerpt"]}}, "highlight": {"fields": {"content": {"fragment_size": 200, "number_of_fragments": 2}}}, }) hits = [] for h in r.json().get("hits", {}).get("hits", []): src = h["_source"] hits.append({ "url": src.get("url"), "title": src.get("title"), "excerpt": h.get("highlight", {}).get("content", [src.get("excerpt", "")])[0], "category": src.get("category"), "crawled_at": src.get("crawled_at"), "score": h["_score"], }) return {"query": q, "total": r.json().get("hits", {}).get("total", {}).get("value", 0), "hits": hits} except Exception as e: return {"query": q, "total": 0, "hits": [], "error": str(e)} @app.get("/api/crawl") async def crawl(url: str = Query(...)): try: r = client.get(f"{YACY_URL}/Crawler_p.json", params={ "crawlingDomMaxPages": 50, "crawlingDepth": 1, "crawlingStart": url, "crawlingQ": "on", "bookmarkTitle": "research", "bookmarkFolder": "/research", "indexText": "on", "indexMedia": "on", "crawlingMode": "url", "cachePolicy": "iffresh", }) return {"status": "crawl_started", "url": url, "yacy": r.json()} except Exception as e: return {"status": "error", "url": url, "error": str(e)} @app.get("/api/research") async def research(topic: str = Query(...)): # Proxy to MCP server's research_topic via command import subprocess, sys result = subprocess.run( [sys.executable, "-c", f""" import json, sys sys.path.insert(0, '/Users/drjones/ai-research-engine') from server import research_topic print(research_topic("{topic}")) """], capture_output=True, text=True, timeout=120, env={**os.environ, "PYTHONPATH": "/Users/drjones/ai-research-engine"} ) try: return json.loads(result.stdout) except: return {"error": result.stderr, "topic": topic} if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)