This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

pensieve / scripts / render_demo_report.py
5.5 kB 149 lines
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()