#!/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
๐ก๏ธ
NO KYC EVER
ยท
๐
NO LOGS
ยท
โ
UNLIMITED POSSIBILITIES
ยท
Privacy by design. No identity checks. No tracking. Just research.
""")
@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)