"""Read-only web app for the PO brain — stdlib http.server, zero install. GET / chat UI GET /ask?q=... JSON grounded answer (+ rendered html) GET /wiki//.md rendered wiki page GET /wiki/index.md the catalog This app NEVER mutates anything. It only reads the compiled graph + wiki. """ from __future__ import annotations import json import urllib.parse from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import config from query import md as mdlib from query.answer import answer from query.retriever import get_brain PAGE = """ ramco-brain · PO
ramco-brainread-only queryable wiki · Purchase Order · grounded in Ramco artifacts

Ask a structural question about the PO domain. Answers are grounded in the graph and link to wiki pages on the right.

Wiki page

Click any link in an answer to open its page here.

""" def _resolve_wiki_href(href: str) -> str: """Normalise relative wiki links (../table/x.md) to absolute /wiki/table/x.md.""" href = href.replace("../", "") if href.endswith(".md") and not href.startswith("/wiki/"): href = "/wiki/" + href.lstrip("/") return href class Handler(BaseHTTPRequestHandler): def _send(self, code, body, ctype="application/json"): data = body.encode("utf-8") if isinstance(body, str) else body self.send_response(code) self.send_header("Content-Type", ctype) self.send_header("Content-Length", str(len(data))) self.end_headers() self.wfile.write(data) def log_message(self, *args): pass def do_GET(self): parsed = urllib.parse.urlparse(self.path) path = urllib.parse.unquote(parsed.path) qs = urllib.parse.parse_qs(parsed.query) if path == "/" or path == "/index.html": return self._send(200, PAGE, "text/html; charset=utf-8") if path == "/ask": q = (qs.get("q", [""])[0]).strip() res = answer(q) if q else {"answer": "", "citations": []} res["html"] = _answer_html(res) return self._send(200, json.dumps(res)) if path.startswith("/wiki/"): return self._serve_wiki(path) return self._send(404, json.dumps({"error": "not found"})) def _serve_wiki(self, path): rel = path[len("/wiki/"):] if rel in ("", "index", "index.md"): rel = "index.md" if not rel.endswith(".md"): rel += ".md" target = (config.WIKI_DIR / rel).resolve() # path-traversal guard if config.WIKI_DIR.resolve() not in target.parents and target != config.WIKI_DIR.resolve(): return self._send(403, json.dumps({"error": "forbidden"})) if not target.exists(): return self._send(404, json.dumps({"html": "

Page not found.

"})) html_body = mdlib.to_html(target.read_text(encoding="utf-8")) # rewrite relative .md links to absolute /wiki/ ones import re html_body = re.sub(r'href="([^"]+\.md)"', lambda m: f'href="{_resolve_wiki_href(m.group(1))}"', html_body) return self._send(200, json.dumps({"html": html_body})) def _answer_html(res: dict) -> str: body = mdlib.to_html(res.get("answer", "")) import re body = re.sub(r'href="([^"]+\.md)"', lambda m: f'href="{_resolve_wiki_href(m.group(1))}"', body) return body def main(port: int = 8800): get_brain() # warm the index srv = ThreadingHTTPServer(("127.0.0.1", port), Handler) print(f"ramco-brain serving at http://127.0.0.1:{port} (Ctrl-C to stop)") try: srv.serve_forever() except KeyboardInterrupt: srv.shutdown() if __name__ == "__main__": import sys main(int(sys.argv[1]) if len(sys.argv) > 1 else 8800)