This repository has no description
1#!/usr/bin/env python3
2"""Render the Pensieve demo as a user-facing inspection page."""
3
4from __future__ import annotations
5
6import html
7import json
8from pathlib import Path
9
10
11BASE = Path("/tmp/pensieve-demo")
12
13
14def h(value: object) -> str:
15 return html.escape(str(value), quote=True)
16
17
18def load(name: str):
19 return json.loads((BASE / name).read_text())
20
21
22def stat(label: str, value: object) -> str:
23 return f"<div class='metric'><b>{h(value)}</b><span>{h(label)}</span></div>"
24
25
26def result(row: dict) -> str:
27 text = row.get("text", "")
28 if len(text) > 1200:
29 text = text[:1200] + "\n..."
30 return f"""
31 <article class="result">
32 <div class="result-top">
33 <code>{h(row.get("collection", ""))}</code>
34 <span>distance {float(row.get("$dist", 0)):.3f}</span>
35 </div>
36 <a href="https://bsky.app/profile/{h(row.get("authorDid", ""))}/post/{h(row.get("uri", "").rsplit("/", 1)[-1])}">{h(row.get("uri", ""))}</a>
37 <pre>{h(text)}</pre>
38 </article>
39 """
40
41
42def query_section(title: str, filename: str) -> str:
43 data = load(filename)
44 rows = "\n".join(result(row) for row in data["rows"])
45 return f"""
46 <section>
47 <div class="section-head">
48 <h2>{h(title)}</h2>
49 <p>Semantic query: <code>{h(data["query"])}</code></p>
50 </div>
51 {rows}
52 </section>
53 """
54
55
56def main() -> None:
57 manifest = load("manifest.json")
58 top_collections = "".join(
59 f"<li><span>{h(row['collection'])}</span><b>{h(row['count'])}</b></li>"
60 for row in manifest["topCollections"][:10]
61 )
62
63 page = f"""<!doctype html>
64<html lang="en">
65<head>
66 <meta charset="utf-8">
67 <meta name="viewport" content="width=device-width, initial-scale=1">
68 <title>Pensieve</title>
69 <style>
70 :root {{
71 color-scheme: light dark;
72 font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
73 background: #f6f5f1;
74 color: #181816;
75 }}
76 body {{ margin: 0; background: #f6f5f1; }}
77 main {{ max-width: 1180px; margin: 0 auto; padding: 30px; }}
78 header {{ border-bottom: 1px solid #d8d5cc; padding: 8px 0 24px; }}
79 h1 {{ margin: 0; font-size: 42px; line-height: 1; letter-spacing: 0; }}
80 h2 {{ margin: 0; font-size: 21px; }}
81 p {{ margin: 8px 0 0; color: #5c5a52; }}
82 code {{ font-size: 12px; color: #57544e; }}
83 .metrics {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin: 22px 0; }}
84 .metric, .panel, .result {{ background: #fff; border: 1px solid #dedbd2; border-radius: 8px; }}
85 .metric {{ padding: 14px; }}
86 .metric b {{ display: block; font-size: 24px; }}
87 .metric span {{ display: block; margin-top: 3px; color: #6b675f; font-size: 12px; text-transform: uppercase; }}
88 .layout {{ display: grid; grid-template-columns: minmax(260px, 340px) 1fr; gap: 18px; align-items: start; }}
89 .panel {{ padding: 16px; position: sticky; top: 20px; }}
90 .panel h2 {{ margin-bottom: 12px; }}
91 ul {{ list-style: none; padding: 0; margin: 0; }}
92 li {{ display: flex; justify-content: space-between; gap: 12px; padding: 8px 0; border-top: 1px solid #ece9df; }}
93 li:first-child {{ border-top: 0; }}
94 li span {{ overflow-wrap: anywhere; }}
95 section {{ margin-bottom: 30px; }}
96 .section-head {{ margin-bottom: 12px; }}
97 .result {{ padding: 14px; margin: 10px 0; }}
98 .result-top {{ display: flex; justify-content: space-between; gap: 14px; align-items: baseline; }}
99 .result-top span {{ color: #177062; font-variant-numeric: tabular-nums; }}
100 a {{ display: inline-block; margin-top: 8px; color: #17695d; overflow-wrap: anywhere; }}
101 pre {{ white-space: pre-wrap; overflow-wrap: anywhere; font-size: 13px; line-height: 1.45; margin: 10px 0 0; }}
102 .files {{ margin-top: 16px; color: #5c5a52; font-size: 13px; }}
103 @media (max-width: 820px) {{ .layout {{ grid-template-columns: 1fr; }} .panel {{ position: static; }} }}
104 @media (prefers-color-scheme: dark) {{
105 :root, body {{ background: #171717; color: #f2efe7; }}
106 header {{ border-color: #383630; }}
107 p, .files {{ color: #b9b3a6; }}
108 .metric, .panel, .result {{ background: #20201f; border-color: #383630; }}
109 code {{ color: #bdb7aa; }}
110 li {{ border-color: #35332d; }}
111 a, .result-top span {{ color: #65b8a8; }}
112 }}
113 </style>
114</head>
115<body>
116 <main>
117 <header>
118 <h1>Pensieve</h1>
119 <p>ATProto memory search over an extracted repository corpus.</p>
120 </header>
121 <div class="metrics">
122 {stat("repo records", f"{manifest['records']:,}")}
123 {stat("extracted memories", f"{manifest['items']:,}")}
124 {stat("indexed sample", "200")}
125 {stat("CAR size", f"{manifest['carBytes']:,} bytes")}
126 </div>
127 <div class="layout">
128 <aside class="panel">
129 <h2>Corpus</h2>
130 <p><code>{h(manifest['did'])}</code></p>
131 <p>{h(manifest['pds'])}</p>
132 <h2 style="margin-top:22px">Top Collections</h2>
133 <ul>{top_collections}</ul>
134 <p class="files">Files: <code>corpus.ndjson</code>, <code>manifest.json</code>, and query result JSON in <code>/tmp/pensieve-demo</code>.</p>
135 </aside>
136 <div>
137 {query_section("Structured Outputs Lineage", "query-structured-outputs.json")}
138 {query_section("ATProto Repository Search", "query-atproto-search.json")}
139 </div>
140 </div>
141 </main>
142</body>
143</html>
144"""
145 (BASE / "search.html").write_text(page)
146
147
148if __name__ == "__main__":
149 main()