@alextis59/athena 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +147 -0
  3. package/dist/agent/ask.d.ts +7 -0
  4. package/dist/agent/ask.d.ts.map +1 -0
  5. package/dist/agent/ask.js +35 -0
  6. package/dist/agent/ask.js.map +1 -0
  7. package/dist/agent/citations.d.ts +27 -0
  8. package/dist/agent/citations.d.ts.map +1 -0
  9. package/dist/agent/citations.js +190 -0
  10. package/dist/agent/citations.js.map +1 -0
  11. package/dist/agent/fake-provider.d.ts +9 -0
  12. package/dist/agent/fake-provider.d.ts.map +1 -0
  13. package/dist/agent/fake-provider.js +51 -0
  14. package/dist/agent/fake-provider.js.map +1 -0
  15. package/dist/agent/index.d.ts +10 -0
  16. package/dist/agent/index.d.ts.map +1 -0
  17. package/dist/agent/index.js +6 -0
  18. package/dist/agent/index.js.map +1 -0
  19. package/dist/agent/loop.d.ts +11 -0
  20. package/dist/agent/loop.d.ts.map +1 -0
  21. package/dist/agent/loop.js +156 -0
  22. package/dist/agent/loop.js.map +1 -0
  23. package/dist/agent/providers.d.ts +17 -0
  24. package/dist/agent/providers.d.ts.map +1 -0
  25. package/dist/agent/providers.js +171 -0
  26. package/dist/agent/providers.js.map +1 -0
  27. package/dist/agent/tools.d.ts +8 -0
  28. package/dist/agent/tools.d.ts.map +1 -0
  29. package/dist/agent/tools.js +233 -0
  30. package/dist/agent/tools.js.map +1 -0
  31. package/dist/agent/types.d.ts +84 -0
  32. package/dist/agent/types.d.ts.map +1 -0
  33. package/dist/agent/types.js +2 -0
  34. package/dist/agent/types.js.map +1 -0
  35. package/dist/cli/help.d.ts +2 -0
  36. package/dist/cli/help.d.ts.map +1 -0
  37. package/dist/cli/help.js +28 -0
  38. package/dist/cli/help.js.map +1 -0
  39. package/dist/cli/main.d.ts +3 -0
  40. package/dist/cli/main.d.ts.map +1 -0
  41. package/dist/cli/main.js +5 -0
  42. package/dist/cli/main.js.map +1 -0
  43. package/dist/cli/options.d.ts +21 -0
  44. package/dist/cli/options.d.ts.map +1 -0
  45. package/dist/cli/options.js +97 -0
  46. package/dist/cli/options.js.map +1 -0
  47. package/dist/cli/run.d.ts +21 -0
  48. package/dist/cli/run.d.ts.map +1 -0
  49. package/dist/cli/run.js +144 -0
  50. package/dist/cli/run.js.map +1 -0
  51. package/dist/config/index.d.ts +4 -0
  52. package/dist/config/index.d.ts.map +1 -0
  53. package/dist/config/index.js +3 -0
  54. package/dist/config/index.js.map +1 -0
  55. package/dist/config/loader.d.ts +14 -0
  56. package/dist/config/loader.d.ts.map +1 -0
  57. package/dist/config/loader.js +352 -0
  58. package/dist/config/loader.js.map +1 -0
  59. package/dist/config/schema.d.ts +6 -0
  60. package/dist/config/schema.d.ts.map +1 -0
  61. package/dist/config/schema.js +72 -0
  62. package/dist/config/schema.js.map +1 -0
  63. package/dist/config/types.d.ts +53 -0
  64. package/dist/config/types.d.ts.map +1 -0
  65. package/dist/config/types.js +2 -0
  66. package/dist/config/types.js.map +1 -0
  67. package/dist/doctor/doctor.d.ts +47 -0
  68. package/dist/doctor/doctor.d.ts.map +1 -0
  69. package/dist/doctor/doctor.js +159 -0
  70. package/dist/doctor/doctor.js.map +1 -0
  71. package/dist/embeddings/index.d.ts +117 -0
  72. package/dist/embeddings/index.d.ts.map +1 -0
  73. package/dist/embeddings/index.js +515 -0
  74. package/dist/embeddings/index.js.map +1 -0
  75. package/dist/errors.d.ts +19 -0
  76. package/dist/errors.d.ts.map +1 -0
  77. package/dist/errors.js +37 -0
  78. package/dist/errors.js.map +1 -0
  79. package/dist/index.d.ts +17 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +9 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/indexer/discovery.d.ts +10 -0
  84. package/dist/indexer/discovery.d.ts.map +1 -0
  85. package/dist/indexer/discovery.js +38 -0
  86. package/dist/indexer/discovery.js.map +1 -0
  87. package/dist/indexer/hash.d.ts +3 -0
  88. package/dist/indexer/hash.d.ts.map +1 -0
  89. package/dist/indexer/hash.js +11 -0
  90. package/dist/indexer/hash.js.map +1 -0
  91. package/dist/indexer/index.d.ts +7 -0
  92. package/dist/indexer/index.d.ts.map +1 -0
  93. package/dist/indexer/index.js +5 -0
  94. package/dist/indexer/index.js.map +1 -0
  95. package/dist/indexer/kinds.d.ts +4 -0
  96. package/dist/indexer/kinds.d.ts.map +1 -0
  97. package/dist/indexer/kinds.js +38 -0
  98. package/dist/indexer/kinds.js.map +1 -0
  99. package/dist/indexer/run.d.ts +42 -0
  100. package/dist/indexer/run.d.ts.map +1 -0
  101. package/dist/indexer/run.js +563 -0
  102. package/dist/indexer/run.js.map +1 -0
  103. package/dist/logging/logger.d.ts +11 -0
  104. package/dist/logging/logger.d.ts.map +1 -0
  105. package/dist/logging/logger.js +18 -0
  106. package/dist/logging/logger.js.map +1 -0
  107. package/dist/mcp/index.d.ts +35 -0
  108. package/dist/mcp/index.d.ts.map +1 -0
  109. package/dist/mcp/index.js +392 -0
  110. package/dist/mcp/index.js.map +1 -0
  111. package/dist/parsers/chunking.d.ts +4 -0
  112. package/dist/parsers/chunking.d.ts.map +1 -0
  113. package/dist/parsers/chunking.js +112 -0
  114. package/dist/parsers/chunking.js.map +1 -0
  115. package/dist/parsers/index.d.ts +8 -0
  116. package/dist/parsers/index.d.ts.map +1 -0
  117. package/dist/parsers/index.js +5 -0
  118. package/dist/parsers/index.js.map +1 -0
  119. package/dist/parsers/line-parser.d.ts +13 -0
  120. package/dist/parsers/line-parser.d.ts.map +1 -0
  121. package/dist/parsers/line-parser.js +242 -0
  122. package/dist/parsers/line-parser.js.map +1 -0
  123. package/dist/parsers/normalize.d.ts +4 -0
  124. package/dist/parsers/normalize.d.ts.map +1 -0
  125. package/dist/parsers/normalize.js +17 -0
  126. package/dist/parsers/normalize.js.map +1 -0
  127. package/dist/parsers/parser.d.ts +14 -0
  128. package/dist/parsers/parser.d.ts.map +1 -0
  129. package/dist/parsers/parser.js +77 -0
  130. package/dist/parsers/parser.js.map +1 -0
  131. package/dist/parsers/pdf2llm.d.ts +14 -0
  132. package/dist/parsers/pdf2llm.d.ts.map +1 -0
  133. package/dist/parsers/pdf2llm.js +215 -0
  134. package/dist/parsers/pdf2llm.js.map +1 -0
  135. package/dist/parsers/types.d.ts +48 -0
  136. package/dist/parsers/types.d.ts.map +1 -0
  137. package/dist/parsers/types.js +2 -0
  138. package/dist/parsers/types.js.map +1 -0
  139. package/dist/retrieval/index.d.ts +4 -0
  140. package/dist/retrieval/index.d.ts.map +1 -0
  141. package/dist/retrieval/index.js +3 -0
  142. package/dist/retrieval/index.js.map +1 -0
  143. package/dist/retrieval/schemas.d.ts +127 -0
  144. package/dist/retrieval/schemas.d.ts.map +1 -0
  145. package/dist/retrieval/schemas.js +65 -0
  146. package/dist/retrieval/schemas.js.map +1 -0
  147. package/dist/retrieval/tools.d.ts +31 -0
  148. package/dist/retrieval/tools.d.ts.map +1 -0
  149. package/dist/retrieval/tools.js +803 -0
  150. package/dist/retrieval/tools.js.map +1 -0
  151. package/dist/retrieval/types.d.ts +104 -0
  152. package/dist/retrieval/types.d.ts.map +1 -0
  153. package/dist/retrieval/types.js +2 -0
  154. package/dist/retrieval/types.js.map +1 -0
  155. package/dist/server/index.d.ts +3 -0
  156. package/dist/server/index.d.ts.map +1 -0
  157. package/dist/server/index.js +2 -0
  158. package/dist/server/index.js.map +1 -0
  159. package/dist/server/server.d.ts +49 -0
  160. package/dist/server/server.d.ts.map +1 -0
  161. package/dist/server/server.js +517 -0
  162. package/dist/server/server.js.map +1 -0
  163. package/dist/shared/types.d.ts +98 -0
  164. package/dist/shared/types.d.ts.map +1 -0
  165. package/dist/shared/types.js +2 -0
  166. package/dist/shared/types.js.map +1 -0
  167. package/dist/storage/database.d.ts +18 -0
  168. package/dist/storage/database.d.ts.map +1 -0
  169. package/dist/storage/database.js +66 -0
  170. package/dist/storage/database.js.map +1 -0
  171. package/dist/storage/index.d.ts +8 -0
  172. package/dist/storage/index.d.ts.map +1 -0
  173. package/dist/storage/index.js +5 -0
  174. package/dist/storage/index.js.map +1 -0
  175. package/dist/storage/migrations.d.ts +15 -0
  176. package/dist/storage/migrations.d.ts.map +1 -0
  177. package/dist/storage/migrations.js +170 -0
  178. package/dist/storage/migrations.js.map +1 -0
  179. package/dist/storage/paths.d.ts +15 -0
  180. package/dist/storage/paths.d.ts.map +1 -0
  181. package/dist/storage/paths.js +27 -0
  182. package/dist/storage/paths.js.map +1 -0
  183. package/dist/storage/schema.d.ts +11 -0
  184. package/dist/storage/schema.d.ts.map +1 -0
  185. package/dist/storage/schema.js +40 -0
  186. package/dist/storage/schema.js.map +1 -0
  187. package/dist/ui/index.d.ts +2 -0
  188. package/dist/ui/index.d.ts.map +1 -0
  189. package/dist/ui/index.js +2 -0
  190. package/dist/ui/index.js.map +1 -0
  191. package/dist/ui/static-app.d.ts +2 -0
  192. package/dist/ui/static-app.d.ts.map +1 -0
  193. package/dist/ui/static-app.js +735 -0
  194. package/dist/ui/static-app.js.map +1 -0
  195. package/docs/adr/0001-sqlite-driver.md +23 -0
  196. package/docs/agentic-rag-specifications.md +659 -0
  197. package/docs/agentic-rag-study.md +1056 -0
  198. package/docs/implementation-plan.md +1425 -0
  199. package/docs/mcp-integration.md +154 -0
  200. package/docs/validation-corpus.md +71 -0
  201. package/package.json +79 -0
@@ -0,0 +1,735 @@
1
+ export function renderAppHtml() {
2
+ return `<!doctype html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <title>Athena</title>
8
+ <style>
9
+ :root {
10
+ color-scheme: light;
11
+ --bg: #f6f6f2;
12
+ --panel: #ffffff;
13
+ --ink: #1d2522;
14
+ --muted: #5d6863;
15
+ --line: #d8ded8;
16
+ --accent: #1d6f5f;
17
+ --accent-weak: #dceee9;
18
+ font-family:
19
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
20
+ "Segoe UI", sans-serif;
21
+ }
22
+
23
+ * {
24
+ box-sizing: border-box;
25
+ }
26
+
27
+ body {
28
+ margin: 0;
29
+ min-height: 100vh;
30
+ background: var(--bg);
31
+ color: var(--ink);
32
+ }
33
+
34
+ .shell {
35
+ display: grid;
36
+ grid-template-columns: minmax(220px, 280px) minmax(0, 1fr) minmax(280px, 420px);
37
+ min-height: 100vh;
38
+ }
39
+
40
+ aside,
41
+ main,
42
+ section {
43
+ min-width: 0;
44
+ }
45
+
46
+ aside {
47
+ border-right: 1px solid var(--line);
48
+ padding: 18px;
49
+ background: #eef3ef;
50
+ }
51
+
52
+ main {
53
+ padding: 18px;
54
+ }
55
+
56
+ section {
57
+ border-left: 1px solid var(--line);
58
+ padding: 18px;
59
+ background: #fbfbf8;
60
+ }
61
+
62
+ h1,
63
+ h2 {
64
+ margin: 0 0 12px;
65
+ font-size: 18px;
66
+ letter-spacing: 0;
67
+ }
68
+
69
+ h2 {
70
+ margin-top: 18px;
71
+ font-size: 14px;
72
+ color: var(--muted);
73
+ text-transform: uppercase;
74
+ }
75
+
76
+ .status,
77
+ .result,
78
+ .trace,
79
+ pre {
80
+ border: 1px solid var(--line);
81
+ border-radius: 8px;
82
+ background: var(--panel);
83
+ }
84
+
85
+ .status,
86
+ .trace {
87
+ padding: 12px;
88
+ }
89
+
90
+ .status {
91
+ display: grid;
92
+ gap: 8px;
93
+ }
94
+
95
+ .status[data-status-state="ready"] {
96
+ border-color: #8bc4a9;
97
+ }
98
+
99
+ .status[data-status-state="stale"],
100
+ .status[data-status-state="indexing"] {
101
+ border-color: #c7a64a;
102
+ }
103
+
104
+ .status[data-status-state="error"] {
105
+ border-color: #cf7b72;
106
+ }
107
+
108
+ .status-head {
109
+ display: flex;
110
+ align-items: center;
111
+ gap: 8px;
112
+ font-weight: 700;
113
+ }
114
+
115
+ .status-dot {
116
+ width: 10px;
117
+ height: 10px;
118
+ border-radius: 999px;
119
+ background: #8b9691;
120
+ flex: 0 0 auto;
121
+ }
122
+
123
+ .status[data-status-state="ready"] .status-dot {
124
+ background: #1d6f5f;
125
+ }
126
+
127
+ .status[data-status-state="stale"] .status-dot {
128
+ background: #b17d11;
129
+ }
130
+
131
+ .status[data-status-state="indexing"] .status-dot {
132
+ background: #2864b4;
133
+ }
134
+
135
+ .status[data-status-state="error"] .status-dot {
136
+ background: #b43d32;
137
+ }
138
+
139
+ .status-detail {
140
+ color: var(--muted);
141
+ font-size: 12px;
142
+ line-height: 1.4;
143
+ }
144
+
145
+ .metric {
146
+ display: flex;
147
+ justify-content: space-between;
148
+ gap: 12px;
149
+ padding: 6px 0;
150
+ color: var(--muted);
151
+ font-size: 13px;
152
+ }
153
+
154
+ form {
155
+ display: flex;
156
+ gap: 8px;
157
+ margin-bottom: 14px;
158
+ }
159
+
160
+ label {
161
+ display: grid;
162
+ gap: 6px;
163
+ margin-bottom: 12px;
164
+ color: var(--muted);
165
+ font-size: 12px;
166
+ }
167
+
168
+ input,
169
+ select,
170
+ button {
171
+ height: 38px;
172
+ border: 1px solid var(--line);
173
+ border-radius: 8px;
174
+ font: inherit;
175
+ }
176
+
177
+ input,
178
+ select {
179
+ flex: 1;
180
+ min-width: 0;
181
+ padding: 0 10px;
182
+ background: var(--panel);
183
+ color: var(--ink);
184
+ }
185
+
186
+ button {
187
+ padding: 0 14px;
188
+ background: var(--accent);
189
+ color: white;
190
+ border-color: var(--accent);
191
+ cursor: pointer;
192
+ }
193
+
194
+ .results {
195
+ display: grid;
196
+ gap: 10px;
197
+ }
198
+
199
+ .empty {
200
+ color: var(--muted);
201
+ padding: 14px 0;
202
+ }
203
+
204
+ .result {
205
+ padding: 12px;
206
+ }
207
+
208
+ .result-actions {
209
+ display: flex;
210
+ flex-wrap: wrap;
211
+ gap: 8px;
212
+ }
213
+
214
+ .badges,
215
+ .asset-links {
216
+ display: flex;
217
+ flex-wrap: wrap;
218
+ gap: 6px;
219
+ margin-top: 8px;
220
+ }
221
+
222
+ .badge {
223
+ border: 1px solid var(--line);
224
+ border-radius: 999px;
225
+ padding: 3px 8px;
226
+ background: #f8faf8;
227
+ color: var(--muted);
228
+ font-size: 12px;
229
+ }
230
+
231
+ .badge-warning {
232
+ border-color: #e0b85e;
233
+ background: #fff7df;
234
+ color: #7d540c;
235
+ }
236
+
237
+ .result button {
238
+ height: auto;
239
+ min-height: 32px;
240
+ margin-top: 8px;
241
+ background: var(--accent-weak);
242
+ border-color: var(--accent-weak);
243
+ color: var(--accent);
244
+ }
245
+
246
+ .path {
247
+ font-weight: 700;
248
+ overflow-wrap: anywhere;
249
+ }
250
+
251
+ .meta {
252
+ margin-top: 4px;
253
+ color: var(--muted);
254
+ font-size: 12px;
255
+ }
256
+
257
+ .snippet {
258
+ margin-top: 6px;
259
+ color: var(--muted);
260
+ line-height: 1.45;
261
+ }
262
+
263
+ pre {
264
+ min-height: 240px;
265
+ max-height: calc(100vh - 160px);
266
+ margin: 0;
267
+ overflow: auto;
268
+ padding: 12px;
269
+ white-space: pre-wrap;
270
+ }
271
+
272
+ .asset-links button {
273
+ height: auto;
274
+ min-height: 30px;
275
+ background: var(--accent-weak);
276
+ border-color: var(--accent-weak);
277
+ color: var(--accent);
278
+ }
279
+
280
+ @media (max-width: 900px) {
281
+ .shell {
282
+ grid-template-columns: 1fr;
283
+ }
284
+
285
+ aside,
286
+ section {
287
+ border: 0;
288
+ border-bottom: 1px solid var(--line);
289
+ }
290
+ }
291
+ </style>
292
+ </head>
293
+ <body>
294
+ <div class="shell" data-athena-chat>
295
+ <aside>
296
+ <h1>Athena</h1>
297
+ <div class="status" id="status"></div>
298
+ <h2>Filters</h2>
299
+ <label>
300
+ Path prefix
301
+ <input id="path-prefix" autocomplete="off" placeholder="docs/" />
302
+ </label>
303
+ <label>
304
+ Source kind
305
+ <select id="kind">
306
+ <option value="">All kinds</option>
307
+ <option value="markdown">Markdown</option>
308
+ <option value="mdx">MDX</option>
309
+ <option value="text">Text</option>
310
+ <option value="rst">reStructuredText</option>
311
+ <option value="asciidoc">AsciiDoc</option>
312
+ <option value="html">HTML</option>
313
+ <option value="pdf">PDF</option>
314
+ </select>
315
+ </label>
316
+ </aside>
317
+ <main>
318
+ <h2>Search</h2>
319
+ <form id="search-form">
320
+ <input id="query" name="query" autocomplete="off" placeholder="Ask local docs" />
321
+ <button type="submit">Search</button>
322
+ </form>
323
+ <div class="results" id="results"></div>
324
+ </main>
325
+ <section>
326
+ <h2>Source</h2>
327
+ <pre id="preview"></pre>
328
+ <div class="asset-links" id="asset-links"></div>
329
+ <h2>Trace</h2>
330
+ <div class="trace" id="trace"></div>
331
+ </section>
332
+ </div>
333
+ <script>
334
+ const statusEl = document.querySelector("#status");
335
+ const resultsEl = document.querySelector("#results");
336
+ const previewEl = document.querySelector("#preview");
337
+ const assetLinksEl = document.querySelector("#asset-links");
338
+ const traceEl = document.querySelector("#trace");
339
+ const form = document.querySelector("#search-form");
340
+ const queryEl = document.querySelector("#query");
341
+ const pathPrefixEl = document.querySelector("#path-prefix");
342
+ const kindEl = document.querySelector("#kind");
343
+ let latestResults = [];
344
+
345
+ async function getJson(url) {
346
+ const response = await fetch(url);
347
+ const payload = await response.json();
348
+ if (!response.ok) {
349
+ throw new Error(payload.error || "Request failed");
350
+ }
351
+ return payload;
352
+ }
353
+
354
+ async function refreshStatus() {
355
+ const status = await getJson("/api/status");
356
+ renderStatus(status);
357
+ }
358
+
359
+ function renderStatus(status) {
360
+ const indicator = status.indicator || {
361
+ state: "error",
362
+ label: "Status unavailable",
363
+ detail: "The index status endpoint returned an unknown response.",
364
+ tone: "danger"
365
+ };
366
+ const head = document.createElement("div");
367
+ const dot = document.createElement("span");
368
+ const label = document.createElement("span");
369
+ const detail = document.createElement("div");
370
+
371
+ statusEl.replaceChildren();
372
+ statusEl.dataset.statusState = indicator.state;
373
+ statusEl.dataset.statusTone = indicator.tone;
374
+
375
+ head.className = "status-head";
376
+ dot.className = "status-dot";
377
+ dot.setAttribute("aria-hidden", "true");
378
+ label.textContent = indicator.label;
379
+ head.append(dot, label);
380
+
381
+ detail.className = "status-detail";
382
+ detail.textContent = indicator.detail;
383
+
384
+ statusEl.append(head, detail);
385
+
386
+ [
387
+ ["State", status.state],
388
+ ["Schema", status.database.schemaVersion ?? "missing"],
389
+ ["Sources", status.counts.sources],
390
+ ["Chunks", status.counts.chunks],
391
+ ["Last run", status.lastRun?.status ?? "none"]
392
+ ].forEach(([label, value]) => {
393
+ const row = document.createElement("div");
394
+ const name = document.createElement("span");
395
+ const strong = document.createElement("strong");
396
+ row.className = "metric";
397
+ name.textContent = label;
398
+ strong.textContent = String(value);
399
+ row.append(name, strong);
400
+ statusEl.append(row);
401
+ });
402
+ }
403
+
404
+ async function search(query) {
405
+ const params = new URLSearchParams({ q: query });
406
+ if (pathPrefixEl.value.trim()) {
407
+ params.set("pathPrefix", pathPrefixEl.value.trim());
408
+ }
409
+ if (kindEl.value) {
410
+ params.set("kind", kindEl.value);
411
+ }
412
+ const payload = await getJson("/api/search?" + params.toString());
413
+ latestResults = payload.results;
414
+ renderTrace(payload.trace);
415
+ renderResults(payload.results);
416
+ }
417
+
418
+ function renderResults(results) {
419
+ resultsEl.replaceChildren();
420
+ if (results.length === 0) {
421
+ const empty = document.createElement("div");
422
+ empty.className = "empty";
423
+ empty.textContent = "No indexed source matched this query.";
424
+ resultsEl.append(empty);
425
+ return;
426
+ }
427
+
428
+ for (const item of results) {
429
+ const node = document.createElement("article");
430
+ const pathNode = document.createElement("div");
431
+ const metaNode = document.createElement("div");
432
+ const badgesNode = document.createElement("div");
433
+ const snippetNode = document.createElement("div");
434
+ const actions = document.createElement("div");
435
+ const openButton = document.createElement("button");
436
+ const sourceButton = document.createElement("button");
437
+ const copyButton = document.createElement("button");
438
+
439
+ node.className = "result";
440
+ pathNode.className = "path";
441
+ metaNode.className = "meta";
442
+ badgesNode.className = "badges";
443
+ snippetNode.className = "snippet";
444
+ actions.className = "result-actions";
445
+
446
+ pathNode.textContent = item.path;
447
+ metaNode.textContent = resultMeta(item);
448
+ snippetNode.textContent = item.snippet;
449
+ renderLocatorBadges(item.locator, badgesNode);
450
+
451
+ openButton.type = "button";
452
+ openButton.textContent =
453
+ item.locator.type === "pdf" ? "Open PDF preview" : "Open lines";
454
+ openButton.addEventListener("click", async () => {
455
+ if (item.locator.type === "pdf") {
456
+ await openPdfPreview(item);
457
+ return;
458
+ }
459
+
460
+ await openTextRange(item);
461
+ });
462
+
463
+ sourceButton.type = "button";
464
+ sourceButton.textContent = "Open source";
465
+ sourceButton.disabled = item.locator.type === "pdf";
466
+ sourceButton.addEventListener("click", async () => {
467
+ const params = new URLSearchParams({ path: item.path });
468
+ const source = await getJson("/api/source?" + params.toString());
469
+ previewEl.textContent = source.text;
470
+ assetLinksEl.replaceChildren();
471
+ });
472
+
473
+ copyButton.type = "button";
474
+ copyButton.textContent = "Copy citation";
475
+ copyButton.addEventListener("click", async () => {
476
+ await writeClipboard(formatCopiedResult(item));
477
+ });
478
+
479
+ actions.append(openButton, sourceButton, copyButton);
480
+ node.append(pathNode, metaNode, badgesNode, snippetNode, actions);
481
+ resultsEl.append(node);
482
+ }
483
+ }
484
+
485
+ async function openTextRange(item) {
486
+ const params = new URLSearchParams({
487
+ path: item.path,
488
+ startLine: String(item.locator.startLine),
489
+ endLine: String(item.locator.endLine)
490
+ });
491
+ const source = await getJson("/api/read?" + params.toString());
492
+ previewEl.textContent = source.text;
493
+ assetLinksEl.replaceChildren();
494
+ }
495
+
496
+ async function openPdfPreview(item) {
497
+ const params = new URLSearchParams({
498
+ path: item.path,
499
+ page: String(item.locator.page)
500
+ });
501
+
502
+ if (item.locator.regionId) {
503
+ params.set("regionId", item.locator.regionId);
504
+ }
505
+
506
+ const payload = await getJson("/api/pdf?" + params.toString());
507
+ previewEl.textContent = formatPdfPreview(payload.pdf);
508
+ renderPdfAssets(payload.pdf.assets || []);
509
+ renderTrace(payload.trace);
510
+ }
511
+
512
+ function renderLocatorBadges(locator, target) {
513
+ target.replaceChildren();
514
+
515
+ if (locator.type !== "pdf") {
516
+ return;
517
+ }
518
+
519
+ if (isUncertainPdfLocator(locator)) {
520
+ target.append(createBadge("Uncertain", true));
521
+ }
522
+
523
+ if (typeof locator.confidence === "number") {
524
+ target.append(createBadge("Confidence " + formatPercent(locator.confidence), locator.confidence < 0.7));
525
+ }
526
+
527
+ if (Array.isArray(locator.warningCodes) && locator.warningCodes.length > 0) {
528
+ target.append(createBadge(locator.warningCodes.join(", "), true));
529
+ }
530
+
531
+ if (locator.regionId) {
532
+ target.append(createBadge("Region " + locator.regionId, false));
533
+ }
534
+ }
535
+
536
+ function createBadge(text, warning) {
537
+ const badge = document.createElement("span");
538
+ badge.className = warning ? "badge badge-warning" : "badge";
539
+ badge.textContent = text;
540
+ return badge;
541
+ }
542
+
543
+ function renderPdfAssets(assets) {
544
+ assetLinksEl.replaceChildren();
545
+
546
+ for (const asset of assets) {
547
+ if (!asset || typeof asset !== "object" || typeof asset.athenaBlobPath !== "string") {
548
+ continue;
549
+ }
550
+
551
+ const button = document.createElement("button");
552
+ button.type = "button";
553
+ button.textContent =
554
+ typeof asset.path === "string" ? asset.path : asset.athenaBlobPath;
555
+ button.addEventListener("click", async () => {
556
+ const params = new URLSearchParams({
557
+ blobPath: asset.athenaBlobPath
558
+ });
559
+ const payload = await getJson("/api/pdf-asset?" + params.toString());
560
+ previewEl.textContent = payload.text;
561
+ });
562
+ assetLinksEl.append(button);
563
+ }
564
+ }
565
+
566
+ function formatPdfPreview(pdf) {
567
+ const lines = [
568
+ pdf.path + " p." + pdf.page,
569
+ "Confidence: " + formatPdfConfidence(pdf),
570
+ "Warnings: " + formatPdfWarnings(pdf),
571
+ "Region: " + formatPdfRegion(pdf.locator && pdf.locator.region),
572
+ "",
573
+ pdf.markdown
574
+ ];
575
+
576
+ if (Array.isArray(pdf.assets) && pdf.assets.length > 0) {
577
+ lines.push("", "Assets:");
578
+ for (const asset of pdf.assets) {
579
+ if (asset && typeof asset === "object") {
580
+ lines.push("- " + (asset.path || asset.id || "asset"));
581
+ }
582
+ }
583
+ }
584
+
585
+ return lines.join("\\n");
586
+ }
587
+
588
+ function formatPdfConfidence(pdf) {
589
+ if (pdf.confidence && typeof pdf.confidence.overall === "number") {
590
+ return formatPercent(pdf.confidence.overall);
591
+ }
592
+
593
+ if (pdf.locator && typeof pdf.locator.confidence === "number") {
594
+ return formatPercent(pdf.locator.confidence);
595
+ }
596
+
597
+ return "unknown";
598
+ }
599
+
600
+ function formatPdfWarnings(pdf) {
601
+ const codes =
602
+ pdf.locator && Array.isArray(pdf.locator.warningCodes)
603
+ ? pdf.locator.warningCodes
604
+ : [];
605
+
606
+ if (codes.length > 0) {
607
+ return codes.join(", ");
608
+ }
609
+
610
+ if (Array.isArray(pdf.warnings) && pdf.warnings.length > 0) {
611
+ return pdf.warnings
612
+ .map((warning) => warning && typeof warning === "object" ? warning.code : "")
613
+ .filter(Boolean)
614
+ .join(", ");
615
+ }
616
+
617
+ return "none";
618
+ }
619
+
620
+ function formatPdfRegion(region) {
621
+ if (!region) {
622
+ return "none";
623
+ }
624
+
625
+ return [
626
+ "source " + region.source,
627
+ "x " + formatRegionNumber(region.x),
628
+ "y " + formatRegionNumber(region.y),
629
+ "w " + formatRegionNumber(region.width),
630
+ "h " + formatRegionNumber(region.height)
631
+ ].join(", ");
632
+ }
633
+
634
+ function formatRegionNumber(value) {
635
+ return typeof value === "number" ? String(value) : "unknown";
636
+ }
637
+
638
+ function resultMeta(item) {
639
+ const parts = [
640
+ item.kind,
641
+ locatorLabel(item.locator),
642
+ "score " + item.score.toFixed(3)
643
+ ];
644
+
645
+ if (item.locator.type === "pdf" && typeof item.locator.confidence === "number") {
646
+ parts.push("confidence " + formatPercent(item.locator.confidence));
647
+ }
648
+
649
+ return parts.join(" | ");
650
+ }
651
+
652
+ function renderTrace(trace) {
653
+ traceEl.textContent = JSON.stringify(
654
+ {
655
+ resultCount: latestResults.length,
656
+ seenChunkIds: trace.seenChunkIds,
657
+ searchScores: trace.searchScores,
658
+ readLocators: trace.readLocators
659
+ },
660
+ null,
661
+ 2
662
+ );
663
+ }
664
+
665
+ function locatorLabel(locator) {
666
+ if (locator.type === "pdf") {
667
+ return "page " + locator.page + (locator.regionId ? " region" : "");
668
+ }
669
+ return "lines " + locator.startLine + "-" + locator.endLine;
670
+ }
671
+
672
+ function formatCitation(item) {
673
+ if (item.locator.type === "pdf") {
674
+ return (
675
+ item.path +
676
+ " p." +
677
+ item.locator.page +
678
+ (item.locator.regionId ? "#" + item.locator.regionId : "")
679
+ );
680
+ }
681
+ return item.path + ":" + item.locator.startLine + "-" + item.locator.endLine;
682
+ }
683
+
684
+ function isUncertainPdfLocator(locator) {
685
+ return (
686
+ locator.type === "pdf" &&
687
+ ((typeof locator.confidence === "number" && locator.confidence < 0.7) ||
688
+ (Array.isArray(locator.warningCodes) && locator.warningCodes.length > 0))
689
+ );
690
+ }
691
+
692
+ function formatPercent(value) {
693
+ return Math.round(value * 100) + "%";
694
+ }
695
+
696
+ function formatCitationLabel(item) {
697
+ return "[" + formatCitation(item) + "]";
698
+ }
699
+
700
+ function formatCopiedResult(item) {
701
+ const snippet = item.snippet.trim();
702
+
703
+ if (snippet === "") {
704
+ return formatCitationLabel(item);
705
+ }
706
+
707
+ return snippet + "\\n\\n" + formatCitationLabel(item);
708
+ }
709
+
710
+ async function writeClipboard(text) {
711
+ if (navigator.clipboard) {
712
+ await navigator.clipboard.writeText(text);
713
+ } else {
714
+ previewEl.textContent = text;
715
+ }
716
+ }
717
+
718
+ form.addEventListener("submit", (event) => {
719
+ event.preventDefault();
720
+ const query = queryEl.value.trim();
721
+ if (query) {
722
+ search(query).catch((error) => {
723
+ traceEl.textContent = String(error);
724
+ });
725
+ }
726
+ });
727
+
728
+ refreshStatus().catch((error) => {
729
+ statusEl.textContent = String(error);
730
+ });
731
+ </script>
732
+ </body>
733
+ </html>`;
734
+ }
735
+ //# sourceMappingURL=static-app.js.map