lance-context 0.1.0 → 1.0.1

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 (237) hide show
  1. package/README.md +232 -23
  2. package/dist/__tests__/ast-chunker.test.d.ts +2 -0
  3. package/dist/__tests__/ast-chunker.test.d.ts.map +1 -0
  4. package/dist/__tests__/ast-chunker.test.js +307 -0
  5. package/dist/__tests__/ast-chunker.test.js.map +1 -0
  6. package/dist/__tests__/config.test.d.ts +2 -0
  7. package/dist/__tests__/config.test.d.ts.map +1 -0
  8. package/dist/__tests__/config.test.js +242 -0
  9. package/dist/__tests__/config.test.js.map +1 -0
  10. package/dist/__tests__/dashboard/beads.test.d.ts +2 -0
  11. package/dist/__tests__/dashboard/beads.test.d.ts.map +1 -0
  12. package/dist/__tests__/dashboard/beads.test.js +151 -0
  13. package/dist/__tests__/dashboard/beads.test.js.map +1 -0
  14. package/dist/__tests__/dashboard/index.test.d.ts +2 -0
  15. package/dist/__tests__/dashboard/index.test.d.ts.map +1 -0
  16. package/dist/__tests__/dashboard/index.test.js +116 -0
  17. package/dist/__tests__/dashboard/index.test.js.map +1 -0
  18. package/dist/__tests__/dashboard/routes.test.d.ts +2 -0
  19. package/dist/__tests__/dashboard/routes.test.d.ts.map +1 -0
  20. package/dist/__tests__/dashboard/routes.test.js +125 -0
  21. package/dist/__tests__/dashboard/routes.test.js.map +1 -0
  22. package/dist/__tests__/dashboard/server.test.d.ts +2 -0
  23. package/dist/__tests__/dashboard/server.test.d.ts.map +1 -0
  24. package/dist/__tests__/dashboard/server.test.js +75 -0
  25. package/dist/__tests__/dashboard/server.test.js.map +1 -0
  26. package/dist/__tests__/dashboard/state.test.d.ts +2 -0
  27. package/dist/__tests__/dashboard/state.test.d.ts.map +1 -0
  28. package/dist/__tests__/dashboard/state.test.js +124 -0
  29. package/dist/__tests__/dashboard/state.test.js.map +1 -0
  30. package/dist/__tests__/embeddings/factory.test.d.ts +2 -0
  31. package/dist/__tests__/embeddings/factory.test.d.ts.map +1 -0
  32. package/dist/__tests__/embeddings/factory.test.js +100 -0
  33. package/dist/__tests__/embeddings/factory.test.js.map +1 -0
  34. package/dist/__tests__/embeddings/jina.test.d.ts +2 -0
  35. package/dist/__tests__/embeddings/jina.test.d.ts.map +1 -0
  36. package/dist/__tests__/embeddings/jina.test.js +156 -0
  37. package/dist/__tests__/embeddings/jina.test.js.map +1 -0
  38. package/dist/__tests__/embeddings/ollama.test.d.ts +2 -0
  39. package/dist/__tests__/embeddings/ollama.test.d.ts.map +1 -0
  40. package/dist/__tests__/embeddings/ollama.test.js +172 -0
  41. package/dist/__tests__/embeddings/ollama.test.js.map +1 -0
  42. package/dist/__tests__/embeddings/rate-limiter.test.d.ts +2 -0
  43. package/dist/__tests__/embeddings/rate-limiter.test.d.ts.map +1 -0
  44. package/dist/__tests__/embeddings/rate-limiter.test.js +163 -0
  45. package/dist/__tests__/embeddings/rate-limiter.test.js.map +1 -0
  46. package/dist/__tests__/embeddings/retry.test.d.ts +2 -0
  47. package/dist/__tests__/embeddings/retry.test.d.ts.map +1 -0
  48. package/dist/__tests__/embeddings/retry.test.js +260 -0
  49. package/dist/__tests__/embeddings/retry.test.js.map +1 -0
  50. package/dist/__tests__/embeddings/types.test.d.ts +2 -0
  51. package/dist/__tests__/embeddings/types.test.d.ts.map +1 -0
  52. package/dist/__tests__/embeddings/types.test.js +31 -0
  53. package/dist/__tests__/embeddings/types.test.js.map +1 -0
  54. package/dist/__tests__/mocks/embedding-backend.mock.d.ts +10 -0
  55. package/dist/__tests__/mocks/embedding-backend.mock.d.ts.map +1 -0
  56. package/dist/__tests__/mocks/embedding-backend.mock.js +39 -0
  57. package/dist/__tests__/mocks/embedding-backend.mock.js.map +1 -0
  58. package/dist/__tests__/mocks/fetch.mock.d.ts +38 -0
  59. package/dist/__tests__/mocks/fetch.mock.d.ts.map +1 -0
  60. package/dist/__tests__/mocks/fetch.mock.js +74 -0
  61. package/dist/__tests__/mocks/fetch.mock.js.map +1 -0
  62. package/dist/__tests__/mocks/lancedb.mock.d.ts +38 -0
  63. package/dist/__tests__/mocks/lancedb.mock.d.ts.map +1 -0
  64. package/dist/__tests__/mocks/lancedb.mock.js +63 -0
  65. package/dist/__tests__/mocks/lancedb.mock.js.map +1 -0
  66. package/dist/__tests__/search/clustering.test.d.ts +2 -0
  67. package/dist/__tests__/search/clustering.test.d.ts.map +1 -0
  68. package/dist/__tests__/search/clustering.test.js +230 -0
  69. package/dist/__tests__/search/clustering.test.js.map +1 -0
  70. package/dist/__tests__/search/hybrid-search.test.d.ts +2 -0
  71. package/dist/__tests__/search/hybrid-search.test.d.ts.map +1 -0
  72. package/dist/__tests__/search/hybrid-search.test.js +186 -0
  73. package/dist/__tests__/search/hybrid-search.test.js.map +1 -0
  74. package/dist/__tests__/search/indexer.test.d.ts +2 -0
  75. package/dist/__tests__/search/indexer.test.d.ts.map +1 -0
  76. package/dist/__tests__/search/indexer.test.js +878 -0
  77. package/dist/__tests__/search/indexer.test.js.map +1 -0
  78. package/dist/__tests__/search/tree-sitter-chunker.test.d.ts +2 -0
  79. package/dist/__tests__/search/tree-sitter-chunker.test.d.ts.map +1 -0
  80. package/dist/__tests__/search/tree-sitter-chunker.test.js +228 -0
  81. package/dist/__tests__/search/tree-sitter-chunker.test.js.map +1 -0
  82. package/dist/__tests__/setup.d.ts +2 -0
  83. package/dist/__tests__/setup.d.ts.map +1 -0
  84. package/dist/__tests__/setup.js +11 -0
  85. package/dist/__tests__/setup.js.map +1 -0
  86. package/dist/__tests__/utils/concurrency.test.d.ts +2 -0
  87. package/dist/__tests__/utils/concurrency.test.d.ts.map +1 -0
  88. package/dist/__tests__/utils/concurrency.test.js +83 -0
  89. package/dist/__tests__/utils/concurrency.test.js.map +1 -0
  90. package/dist/__tests__/utils/errors.test.d.ts +2 -0
  91. package/dist/__tests__/utils/errors.test.d.ts.map +1 -0
  92. package/dist/__tests__/utils/errors.test.js +136 -0
  93. package/dist/__tests__/utils/errors.test.js.map +1 -0
  94. package/dist/__tests__/utils/type-guards.test.d.ts +2 -0
  95. package/dist/__tests__/utils/type-guards.test.d.ts.map +1 -0
  96. package/dist/__tests__/utils/type-guards.test.js +80 -0
  97. package/dist/__tests__/utils/type-guards.test.js.map +1 -0
  98. package/dist/__tests__/worktree/worktree-manager.test.d.ts +2 -0
  99. package/dist/__tests__/worktree/worktree-manager.test.d.ts.map +1 -0
  100. package/dist/__tests__/worktree/worktree-manager.test.js +403 -0
  101. package/dist/__tests__/worktree/worktree-manager.test.js.map +1 -0
  102. package/dist/config.d.ts +122 -0
  103. package/dist/config.d.ts.map +1 -0
  104. package/dist/config.js +508 -0
  105. package/dist/config.js.map +1 -0
  106. package/dist/dashboard/beads.d.ts +35 -0
  107. package/dist/dashboard/beads.d.ts.map +1 -0
  108. package/dist/dashboard/beads.js +102 -0
  109. package/dist/dashboard/beads.js.map +1 -0
  110. package/dist/dashboard/events.d.ts +46 -0
  111. package/dist/dashboard/events.d.ts.map +1 -0
  112. package/dist/dashboard/events.js +141 -0
  113. package/dist/dashboard/events.js.map +1 -0
  114. package/dist/dashboard/index.d.ts +67 -0
  115. package/dist/dashboard/index.d.ts.map +1 -0
  116. package/dist/dashboard/index.js +90 -0
  117. package/dist/dashboard/index.js.map +1 -0
  118. package/dist/dashboard/routes.d.ts +6 -0
  119. package/dist/dashboard/routes.d.ts.map +1 -0
  120. package/dist/dashboard/routes.js +244 -0
  121. package/dist/dashboard/routes.js.map +1 -0
  122. package/dist/dashboard/server.d.ts +27 -0
  123. package/dist/dashboard/server.d.ts.map +1 -0
  124. package/dist/dashboard/server.js +72 -0
  125. package/dist/dashboard/server.js.map +1 -0
  126. package/dist/dashboard/state.d.ts +116 -0
  127. package/dist/dashboard/state.d.ts.map +1 -0
  128. package/dist/dashboard/state.js +251 -0
  129. package/dist/dashboard/state.js.map +1 -0
  130. package/dist/dashboard/ui.d.ts +6 -0
  131. package/dist/dashboard/ui.d.ts.map +1 -0
  132. package/dist/dashboard/ui.js +1407 -0
  133. package/dist/dashboard/ui.js.map +1 -0
  134. package/dist/embeddings/index.d.ts +20 -2
  135. package/dist/embeddings/index.d.ts.map +1 -1
  136. package/dist/embeddings/index.js +49 -6
  137. package/dist/embeddings/index.js.map +1 -1
  138. package/dist/embeddings/jina.d.ts +9 -0
  139. package/dist/embeddings/jina.d.ts.map +1 -1
  140. package/dist/embeddings/jina.js +42 -2
  141. package/dist/embeddings/jina.js.map +1 -1
  142. package/dist/embeddings/ollama.d.ts +2 -0
  143. package/dist/embeddings/ollama.d.ts.map +1 -1
  144. package/dist/embeddings/ollama.js +21 -5
  145. package/dist/embeddings/ollama.js.map +1 -1
  146. package/dist/embeddings/rate-limiter.d.ts +75 -0
  147. package/dist/embeddings/rate-limiter.d.ts.map +1 -0
  148. package/dist/embeddings/rate-limiter.js +145 -0
  149. package/dist/embeddings/rate-limiter.js.map +1 -0
  150. package/dist/embeddings/retry.d.ts +14 -0
  151. package/dist/embeddings/retry.d.ts.map +1 -0
  152. package/dist/embeddings/retry.js +89 -0
  153. package/dist/embeddings/retry.js.map +1 -0
  154. package/dist/embeddings/types.d.ts +56 -2
  155. package/dist/embeddings/types.d.ts.map +1 -1
  156. package/dist/embeddings/types.js +16 -0
  157. package/dist/embeddings/types.js.map +1 -1
  158. package/dist/index.js +1870 -44
  159. package/dist/index.js.map +1 -1
  160. package/dist/memory/index.d.ts +63 -0
  161. package/dist/memory/index.d.ts.map +1 -0
  162. package/dist/memory/index.js +168 -0
  163. package/dist/memory/index.js.map +1 -0
  164. package/dist/search/ast-chunker.d.ts +34 -0
  165. package/dist/search/ast-chunker.d.ts.map +1 -0
  166. package/dist/search/ast-chunker.js +261 -0
  167. package/dist/search/ast-chunker.js.map +1 -0
  168. package/dist/search/clustering.d.ts +77 -0
  169. package/dist/search/clustering.d.ts.map +1 -0
  170. package/dist/search/clustering.js +455 -0
  171. package/dist/search/clustering.js.map +1 -0
  172. package/dist/search/indexer.d.ts +239 -3
  173. package/dist/search/indexer.d.ts.map +1 -1
  174. package/dist/search/indexer.js +941 -45
  175. package/dist/search/indexer.js.map +1 -1
  176. package/dist/search/tree-sitter-chunker.d.ts +69 -0
  177. package/dist/search/tree-sitter-chunker.d.ts.map +1 -0
  178. package/dist/search/tree-sitter-chunker.js +436 -0
  179. package/dist/search/tree-sitter-chunker.js.map +1 -0
  180. package/dist/symbols/index.d.ts +14 -0
  181. package/dist/symbols/index.d.ts.map +1 -0
  182. package/dist/symbols/index.js +19 -0
  183. package/dist/symbols/index.js.map +1 -0
  184. package/dist/symbols/name-path.d.ts +113 -0
  185. package/dist/symbols/name-path.d.ts.map +1 -0
  186. package/dist/symbols/name-path.js +194 -0
  187. package/dist/symbols/name-path.js.map +1 -0
  188. package/dist/symbols/pattern-search.d.ts +14 -0
  189. package/dist/symbols/pattern-search.d.ts.map +1 -0
  190. package/dist/symbols/pattern-search.js +224 -0
  191. package/dist/symbols/pattern-search.js.map +1 -0
  192. package/dist/symbols/reference-finder.d.ts +38 -0
  193. package/dist/symbols/reference-finder.d.ts.map +1 -0
  194. package/dist/symbols/reference-finder.js +376 -0
  195. package/dist/symbols/reference-finder.js.map +1 -0
  196. package/dist/symbols/symbol-editor.d.ts +81 -0
  197. package/dist/symbols/symbol-editor.d.ts.map +1 -0
  198. package/dist/symbols/symbol-editor.js +257 -0
  199. package/dist/symbols/symbol-editor.js.map +1 -0
  200. package/dist/symbols/symbol-extractor.d.ts +49 -0
  201. package/dist/symbols/symbol-extractor.d.ts.map +1 -0
  202. package/dist/symbols/symbol-extractor.js +593 -0
  203. package/dist/symbols/symbol-extractor.js.map +1 -0
  204. package/dist/symbols/symbol-renamer.d.ts +81 -0
  205. package/dist/symbols/symbol-renamer.d.ts.map +1 -0
  206. package/dist/symbols/symbol-renamer.js +204 -0
  207. package/dist/symbols/symbol-renamer.js.map +1 -0
  208. package/dist/symbols/types.d.ts +234 -0
  209. package/dist/symbols/types.d.ts.map +1 -0
  210. package/dist/symbols/types.js +106 -0
  211. package/dist/symbols/types.js.map +1 -0
  212. package/dist/utils/concurrency.d.ts +32 -0
  213. package/dist/utils/concurrency.d.ts.map +1 -0
  214. package/dist/utils/concurrency.js +57 -0
  215. package/dist/utils/concurrency.js.map +1 -0
  216. package/dist/utils/errors.d.ts +36 -0
  217. package/dist/utils/errors.d.ts.map +1 -0
  218. package/dist/utils/errors.js +91 -0
  219. package/dist/utils/errors.js.map +1 -0
  220. package/dist/utils/type-guards.d.ts +17 -0
  221. package/dist/utils/type-guards.d.ts.map +1 -0
  222. package/dist/utils/type-guards.js +25 -0
  223. package/dist/utils/type-guards.js.map +1 -0
  224. package/dist/worktree/index.d.ts +6 -0
  225. package/dist/worktree/index.d.ts.map +1 -0
  226. package/dist/worktree/index.js +6 -0
  227. package/dist/worktree/index.js.map +1 -0
  228. package/dist/worktree/types.d.ts +101 -0
  229. package/dist/worktree/types.d.ts.map +1 -0
  230. package/dist/worktree/types.js +6 -0
  231. package/dist/worktree/types.js.map +1 -0
  232. package/dist/worktree/worktree-manager.d.ts +80 -0
  233. package/dist/worktree/worktree-manager.d.ts.map +1 -0
  234. package/dist/worktree/worktree-manager.js +407 -0
  235. package/dist/worktree/worktree-manager.js.map +1 -0
  236. package/package.json +39 -5
  237. package/scripts/postinstall.js +48 -0
@@ -0,0 +1,1407 @@
1
+ /**
2
+ * Lance logo SVG (embedded)
3
+ */
4
+ const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="40" height="40">
5
+ <circle cx="100" cy="100" r="95" fill="#1a1a2e"/>
6
+ <rect x="45" y="55" width="110" height="100" rx="15" ry="15" fill="#4a90d9"/>
7
+ <line x1="100" y1="55" x2="100" y2="30" stroke="#4a90d9" stroke-width="6" stroke-linecap="round"/>
8
+ <circle cx="100" cy="25" r="10" fill="#ff6b6b"/>
9
+ <rect x="30" y="80" width="15" height="40" rx="5" ry="5" fill="#357abd"/>
10
+ <rect x="155" y="80" width="15" height="40" rx="5" ry="5" fill="#357abd"/>
11
+ <rect x="55" y="70" width="90" height="70" rx="10" ry="10" fill="#5ba3ec"/>
12
+ <rect x="50" y="82" width="45" height="35" rx="8" ry="8" fill="none" stroke="#1a1a2e" stroke-width="6"/>
13
+ <rect x="105" y="82" width="45" height="35" rx="8" ry="8" fill="none" stroke="#1a1a2e" stroke-width="6"/>
14
+ <line x1="95" y1="100" x2="105" y2="100" stroke="#1a1a2e" stroke-width="6"/>
15
+ <line x1="50" y1="95" x2="35" y2="90" stroke="#1a1a2e" stroke-width="5" stroke-linecap="round"/>
16
+ <line x1="150" y1="95" x2="165" y2="90" stroke="#1a1a2e" stroke-width="5" stroke-linecap="round"/>
17
+ <ellipse cx="62" cy="92" rx="8" ry="5" fill="rgba(255,255,255,0.3)"/>
18
+ <ellipse cx="117" cy="92" rx="8" ry="5" fill="rgba(255,255,255,0.3)"/>
19
+ <circle cx="72" cy="100" r="12" fill="#1a1a2e"/>
20
+ <circle cx="128" cy="100" r="12" fill="#1a1a2e"/>
21
+ <circle cx="75" cy="97" r="5" fill="#ffffff"/>
22
+ <circle cx="131" cy="97" r="5" fill="#ffffff"/>
23
+ <path d="M 75 130 Q 100 145 125 130" fill="none" stroke="#1a1a2e" stroke-width="4" stroke-linecap="round"/>
24
+ <ellipse cx="60" cy="120" rx="8" ry="5" fill="rgba(255,107,107,0.4)"/>
25
+ <ellipse cx="140" cy="120" rx="8" ry="5" fill="rgba(255,107,107,0.4)"/>
26
+ <circle cx="55" cy="145" r="5" fill="#357abd"/>
27
+ <circle cx="145" cy="145" r="5" fill="#357abd"/>
28
+ </svg>`;
29
+ /**
30
+ * Favicon as base64 data URL
31
+ */
32
+ const FAVICON_SVG = `data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><circle cx="100" cy="100" r="95" fill="#1a1a2e"/><rect x="45" y="55" width="110" height="100" rx="15" ry="15" fill="#4a90d9"/><line x1="100" y1="55" x2="100" y2="30" stroke="#4a90d9" stroke-width="6" stroke-linecap="round"/><circle cx="100" cy="25" r="10" fill="#ff6b6b"/><rect x="30" y="80" width="15" height="40" rx="5" ry="5" fill="#357abd"/><rect x="155" y="80" width="15" height="40" rx="5" ry="5" fill="#357abd"/><rect x="55" y="70" width="90" height="70" rx="10" ry="10" fill="#5ba3ec"/><rect x="50" y="82" width="45" height="35" rx="8" ry="8" fill="none" stroke="#1a1a2e" stroke-width="6"/><rect x="105" y="82" width="45" height="35" rx="8" ry="8" fill="none" stroke="#1a1a2e" stroke-width="6"/><line x1="95" y1="100" x2="105" y2="100" stroke="#1a1a2e" stroke-width="6"/><circle cx="72" cy="100" r="12" fill="#1a1a2e"/><circle cx="128" cy="100" r="12" fill="#1a1a2e"/><circle cx="75" cy="97" r="5" fill="#fff"/><circle cx="131" cy="97" r="5" fill="#fff"/><path d="M 75 130 Q 100 145 125 130" fill="none" stroke="#1a1a2e" stroke-width="4" stroke-linecap="round"/></svg>`)}`;
33
+ /**
34
+ * Generate the dashboard HTML page.
35
+ * This is a self-contained HTML page with embedded CSS and JavaScript.
36
+ */
37
+ export function getDashboardHTML() {
38
+ return `<!DOCTYPE html>
39
+ <html lang="en" data-theme="dark">
40
+ <head>
41
+ <meta charset="UTF-8">
42
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
43
+ <title>lance-context Dashboard</title>
44
+ <link rel="icon" type="image/svg+xml" href="${FAVICON_SVG}">
45
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/charts.css/dist/charts.min.css">
46
+ <style>
47
+ :root {
48
+ /* Light theme */
49
+ --bg-primary-light: #ffffff;
50
+ --bg-secondary-light: #f6f8fa;
51
+ --bg-tertiary-light: #eaeef2;
52
+ --border-color-light: #d0d7de;
53
+ --text-primary-light: #1f2328;
54
+ --text-secondary-light: #656d76;
55
+ --text-muted-light: #8c959f;
56
+ --accent-blue: #0969da;
57
+ --accent-green: #1a7f37;
58
+ --accent-yellow: #9a6700;
59
+ --accent-red: #cf222e;
60
+ --accent-purple: #8250df;
61
+
62
+ /* Dark theme */
63
+ --bg-primary-dark: #0d1117;
64
+ --bg-secondary-dark: #161b22;
65
+ --bg-tertiary-dark: #21262d;
66
+ --border-color-dark: #30363d;
67
+ --text-primary-dark: #e6edf3;
68
+ --text-secondary-dark: #8b949e;
69
+ --text-muted-dark: #6e7681;
70
+ --accent-blue-dark: #58a6ff;
71
+ --accent-green-dark: #3fb950;
72
+ --accent-yellow-dark: #d29922;
73
+ --accent-red-dark: #f85149;
74
+ --accent-purple-dark: #a371f7;
75
+ }
76
+
77
+ [data-theme="dark"] {
78
+ --bg-primary: var(--bg-primary-dark);
79
+ --bg-secondary: var(--bg-secondary-dark);
80
+ --bg-tertiary: var(--bg-tertiary-dark);
81
+ --border-color: var(--border-color-dark);
82
+ --text-primary: var(--text-primary-dark);
83
+ --text-secondary: var(--text-secondary-dark);
84
+ --text-muted: var(--text-muted-dark);
85
+ --accent-blue: var(--accent-blue-dark);
86
+ --accent-green: var(--accent-green-dark);
87
+ --accent-yellow: var(--accent-yellow-dark);
88
+ --accent-red: var(--accent-red-dark);
89
+ --accent-purple: var(--accent-purple-dark);
90
+ }
91
+
92
+ [data-theme="light"] {
93
+ --bg-primary: var(--bg-primary-light);
94
+ --bg-secondary: var(--bg-secondary-light);
95
+ --bg-tertiary: var(--bg-tertiary-light);
96
+ --border-color: var(--border-color-light);
97
+ --text-primary: var(--text-primary-light);
98
+ --text-secondary: var(--text-secondary-light);
99
+ --text-muted: var(--text-muted-light);
100
+ }
101
+
102
+ * {
103
+ box-sizing: border-box;
104
+ margin: 0;
105
+ padding: 0;
106
+ }
107
+
108
+ body {
109
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
110
+ background-color: var(--bg-primary);
111
+ color: var(--text-primary);
112
+ line-height: 1.5;
113
+ min-height: 100vh;
114
+ transition: background-color 0.3s ease, color 0.3s ease;
115
+ }
116
+
117
+ .container {
118
+ max-width: 1200px;
119
+ margin: 0 auto;
120
+ padding: 24px;
121
+ }
122
+
123
+ header {
124
+ display: flex;
125
+ align-items: center;
126
+ justify-content: space-between;
127
+ margin-bottom: 32px;
128
+ padding-bottom: 16px;
129
+ border-bottom: 1px solid var(--border-color);
130
+ }
131
+
132
+ .header-left {
133
+ display: flex;
134
+ align-items: center;
135
+ gap: 12px;
136
+ }
137
+
138
+ .header-right {
139
+ display: flex;
140
+ align-items: center;
141
+ gap: 16px;
142
+ }
143
+
144
+ h1 {
145
+ font-size: 24px;
146
+ font-weight: 600;
147
+ display: flex;
148
+ align-items: center;
149
+ gap: 12px;
150
+ }
151
+
152
+ .logo {
153
+ width: 40px;
154
+ height: 40px;
155
+ display: flex;
156
+ align-items: center;
157
+ justify-content: center;
158
+ }
159
+
160
+ .logo svg {
161
+ width: 40px;
162
+ height: 40px;
163
+ }
164
+
165
+ .theme-toggle {
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 8px;
169
+ padding: 6px 12px;
170
+ background-color: var(--bg-tertiary);
171
+ border: 1px solid var(--border-color);
172
+ border-radius: 6px;
173
+ cursor: pointer;
174
+ color: var(--text-secondary);
175
+ font-size: 14px;
176
+ transition: all 0.2s ease;
177
+ }
178
+
179
+ .theme-toggle:hover {
180
+ background-color: var(--bg-secondary);
181
+ color: var(--text-primary);
182
+ }
183
+
184
+ .theme-toggle svg {
185
+ width: 16px;
186
+ height: 16px;
187
+ }
188
+
189
+ .sun-icon, .moon-icon {
190
+ display: none;
191
+ }
192
+
193
+ [data-theme="dark"] .moon-icon {
194
+ display: block;
195
+ }
196
+
197
+ [data-theme="light"] .sun-icon {
198
+ display: block;
199
+ }
200
+
201
+ .connection-status {
202
+ display: flex;
203
+ align-items: center;
204
+ gap: 8px;
205
+ font-size: 14px;
206
+ color: var(--text-secondary);
207
+ }
208
+
209
+ .status-dot {
210
+ width: 8px;
211
+ height: 8px;
212
+ border-radius: 50%;
213
+ background-color: var(--accent-red);
214
+ }
215
+
216
+ .status-dot.connected {
217
+ background-color: var(--accent-green);
218
+ }
219
+
220
+ .grid {
221
+ display: grid;
222
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
223
+ gap: 16px;
224
+ }
225
+
226
+ .card {
227
+ background-color: var(--bg-secondary);
228
+ border: 1px solid var(--border-color);
229
+ border-radius: 8px;
230
+ padding: 20px;
231
+ transition: background-color 0.3s ease, border-color 0.3s ease;
232
+ }
233
+
234
+ .card-header {
235
+ display: flex;
236
+ align-items: center;
237
+ justify-content: space-between;
238
+ margin-bottom: 16px;
239
+ }
240
+
241
+ .card-title {
242
+ font-size: 14px;
243
+ font-weight: 600;
244
+ color: var(--text-secondary);
245
+ text-transform: uppercase;
246
+ letter-spacing: 0.5px;
247
+ }
248
+
249
+ .badge {
250
+ display: inline-flex;
251
+ align-items: center;
252
+ padding: 2px 8px;
253
+ font-size: 12px;
254
+ font-weight: 500;
255
+ border-radius: 12px;
256
+ background-color: var(--bg-tertiary);
257
+ color: var(--text-secondary);
258
+ }
259
+
260
+ .badge.success {
261
+ background-color: rgba(63, 185, 80, 0.15);
262
+ color: var(--accent-green);
263
+ }
264
+
265
+ .badge.warning {
266
+ background-color: rgba(210, 153, 34, 0.15);
267
+ color: var(--accent-yellow);
268
+ }
269
+
270
+ .badge.error {
271
+ background-color: rgba(248, 81, 73, 0.15);
272
+ color: var(--accent-red);
273
+ }
274
+
275
+ /* Form styles */
276
+ .settings-form {
277
+ margin-top: 16px;
278
+ padding-top: 16px;
279
+ border-top: 1px solid var(--border-color);
280
+ }
281
+
282
+ .form-group {
283
+ margin-bottom: 12px;
284
+ }
285
+
286
+ .form-group label {
287
+ display: block;
288
+ font-size: 12px;
289
+ color: var(--text-muted);
290
+ margin-bottom: 6px;
291
+ }
292
+
293
+ .form-select,
294
+ .form-input {
295
+ width: 100%;
296
+ padding: 8px 12px;
297
+ font-size: 14px;
298
+ font-family: inherit;
299
+ background-color: var(--bg-tertiary);
300
+ border: 1px solid var(--border-color);
301
+ border-radius: 6px;
302
+ color: var(--text-primary);
303
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
304
+ }
305
+
306
+ .form-select:focus,
307
+ .form-input:focus {
308
+ outline: none;
309
+ border-color: var(--accent-blue);
310
+ box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.2);
311
+ }
312
+
313
+ .form-hint {
314
+ font-size: 11px;
315
+ color: var(--text-muted);
316
+ margin-top: 4px;
317
+ }
318
+
319
+ .form-hint a {
320
+ color: var(--accent-blue);
321
+ text-decoration: none;
322
+ }
323
+
324
+ .form-hint a:hover {
325
+ text-decoration: underline;
326
+ }
327
+
328
+ .form-actions {
329
+ display: flex;
330
+ align-items: center;
331
+ gap: 12px;
332
+ margin-top: 16px;
333
+ }
334
+
335
+ .btn {
336
+ padding: 8px 16px;
337
+ font-size: 14px;
338
+ font-weight: 500;
339
+ border-radius: 6px;
340
+ border: 1px solid transparent;
341
+ cursor: pointer;
342
+ transition: all 0.2s ease;
343
+ }
344
+
345
+ .btn-primary {
346
+ background-color: var(--accent-blue);
347
+ color: white;
348
+ }
349
+
350
+ .btn-primary:hover {
351
+ opacity: 0.9;
352
+ }
353
+
354
+ .btn-primary:disabled {
355
+ opacity: 0.5;
356
+ cursor: not-allowed;
357
+ }
358
+
359
+ .save-status {
360
+ font-size: 13px;
361
+ color: var(--text-secondary);
362
+ }
363
+
364
+ .save-status.success {
365
+ color: var(--accent-green);
366
+ }
367
+
368
+ .save-status.error {
369
+ color: var(--accent-red);
370
+ }
371
+
372
+ .stat {
373
+ margin-bottom: 12px;
374
+ }
375
+
376
+ .stat:last-child {
377
+ margin-bottom: 0;
378
+ }
379
+
380
+ .stat-label {
381
+ font-size: 12px;
382
+ color: var(--text-muted);
383
+ margin-bottom: 4px;
384
+ }
385
+
386
+ .stat-value {
387
+ font-size: 24px;
388
+ font-weight: 600;
389
+ color: var(--text-primary);
390
+ }
391
+
392
+ .stat-value.small {
393
+ font-size: 14px;
394
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
395
+ color: var(--text-secondary);
396
+ }
397
+
398
+ .progress-container {
399
+ margin-top: 16px;
400
+ display: none;
401
+ }
402
+
403
+ .progress-container.active {
404
+ display: block;
405
+ }
406
+
407
+ .progress-bar {
408
+ height: 8px;
409
+ background-color: var(--bg-tertiary);
410
+ border-radius: 4px;
411
+ overflow: hidden;
412
+ margin-bottom: 8px;
413
+ }
414
+
415
+ .progress-fill {
416
+ height: 100%;
417
+ background: linear-gradient(90deg, var(--accent-blue), var(--accent-purple));
418
+ border-radius: 4px;
419
+ transition: width 0.3s ease;
420
+ }
421
+
422
+ .progress-text {
423
+ font-size: 12px;
424
+ color: var(--text-secondary);
425
+ }
426
+
427
+ .patterns-list {
428
+ display: flex;
429
+ flex-wrap: wrap;
430
+ gap: 6px;
431
+ }
432
+
433
+ .pattern-tag {
434
+ display: inline-block;
435
+ padding: 2px 8px;
436
+ font-size: 12px;
437
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
438
+ background-color: var(--bg-tertiary);
439
+ border-radius: 4px;
440
+ color: var(--text-secondary);
441
+ }
442
+
443
+ .pattern-tag.exclude {
444
+ color: var(--accent-red);
445
+ text-decoration: line-through;
446
+ opacity: 0.7;
447
+ }
448
+
449
+ .card.full-width {
450
+ grid-column: 1 / -1;
451
+ }
452
+
453
+ .card.double-width {
454
+ grid-column: span 2;
455
+ }
456
+
457
+ .empty-state {
458
+ text-align: center;
459
+ padding: 40px 20px;
460
+ color: var(--text-secondary);
461
+ }
462
+
463
+ .empty-state-icon {
464
+ font-size: 48px;
465
+ margin-bottom: 16px;
466
+ opacity: 0.5;
467
+ }
468
+
469
+ @keyframes pulse {
470
+ 0%, 100% { opacity: 1; }
471
+ 50% { opacity: 0.5; }
472
+ }
473
+
474
+ .pulsing {
475
+ animation: pulse 2s ease-in-out infinite;
476
+ }
477
+
478
+ /* Charts.css Customization */
479
+ .charts-css {
480
+ --color-1: #58a6ff;
481
+ --color-2: #3fb950;
482
+ --color-3: #a371f7;
483
+ --color-4: #f85149;
484
+ --color-5: #d29922;
485
+ }
486
+
487
+ #chartWrapper {
488
+ width: 100%;
489
+ max-width: 100%;
490
+ overflow: hidden;
491
+ }
492
+
493
+ #chartWrapper #usage-chart {
494
+ --aspect-ratio: 21 / 9;
495
+ width: 100%;
496
+ max-width: 100%;
497
+ margin: 0 auto;
498
+ }
499
+
500
+ #usage-chart th {
501
+ font-size: 11px;
502
+ color: var(--text-secondary);
503
+ }
504
+
505
+ #usage-chart td {
506
+ border-top-left-radius: 6px;
507
+ border-top-right-radius: 6px;
508
+ }
509
+
510
+ /* Charts.css legend overrides */
511
+ #usageChartContainer .legend {
512
+ margin-top: 16px;
513
+ padding-top: 12px;
514
+ justify-content: center;
515
+ border-radius: 4px;
516
+ }
517
+
518
+ #usageChartContainer .legend li {
519
+ font-size: 12px;
520
+ color: var(--text-secondary);
521
+ }
522
+
523
+ .usage-total {
524
+ display: flex;
525
+ justify-content: space-between;
526
+ align-items: center;
527
+ padding-top: 12px;
528
+ border-top: 1px solid var(--border-color);
529
+ margin-top: 16px;
530
+ }
531
+
532
+ .usage-total-label {
533
+ font-size: 12px;
534
+ color: var(--text-muted);
535
+ text-transform: uppercase;
536
+ letter-spacing: 0.5px;
537
+ }
538
+
539
+ .usage-total-count {
540
+ font-size: 18px;
541
+ font-weight: 600;
542
+ color: var(--text-primary);
543
+ }
544
+
545
+ .usage-empty {
546
+ text-align: center;
547
+ padding: 20px;
548
+ color: var(--text-muted);
549
+ font-size: 14px;
550
+ }
551
+
552
+ /* Beads Section Styles */
553
+ .beads-section {
554
+ margin-top: 32px;
555
+ padding-top: 24px;
556
+ border-top: 1px solid var(--border-color);
557
+ }
558
+
559
+ .beads-header {
560
+ display: flex;
561
+ align-items: center;
562
+ gap: 12px;
563
+ margin-bottom: 16px;
564
+ }
565
+
566
+ .beads-logo {
567
+ width: 24px;
568
+ height: 24px;
569
+ }
570
+
571
+ .beads-title {
572
+ font-size: 18px;
573
+ font-weight: 600;
574
+ color: var(--text-primary);
575
+ }
576
+
577
+ .beads-unavailable {
578
+ text-align: center;
579
+ padding: 40px 20px;
580
+ color: var(--text-muted);
581
+ font-size: 14px;
582
+ }
583
+
584
+ .beads-stats {
585
+ display: flex;
586
+ gap: 24px;
587
+ margin-bottom: 16px;
588
+ }
589
+
590
+ .beads-stat {
591
+ display: flex;
592
+ flex-direction: column;
593
+ }
594
+
595
+ .beads-stat-value {
596
+ font-size: 24px;
597
+ font-weight: 600;
598
+ color: var(--text-primary);
599
+ }
600
+
601
+ .beads-stat-label {
602
+ font-size: 12px;
603
+ color: var(--text-muted);
604
+ }
605
+
606
+ .beads-issues {
607
+ display: flex;
608
+ flex-direction: column;
609
+ gap: 8px;
610
+ }
611
+
612
+ .beads-issue {
613
+ display: flex;
614
+ align-items: flex-start;
615
+ gap: 12px;
616
+ padding: 12px;
617
+ background-color: var(--bg-tertiary);
618
+ border-radius: 6px;
619
+ border: 1px solid var(--border-color);
620
+ }
621
+
622
+ .beads-issue-id {
623
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
624
+ font-size: 12px;
625
+ color: var(--accent-blue);
626
+ white-space: nowrap;
627
+ }
628
+
629
+ .beads-issue-content {
630
+ flex: 1;
631
+ min-width: 0;
632
+ }
633
+
634
+ .beads-issue-title {
635
+ font-size: 14px;
636
+ font-weight: 500;
637
+ color: var(--text-primary);
638
+ margin-bottom: 4px;
639
+ }
640
+
641
+ .beads-issue-meta {
642
+ display: flex;
643
+ gap: 12px;
644
+ font-size: 12px;
645
+ color: var(--text-muted);
646
+ }
647
+
648
+ .beads-issue-type {
649
+ display: inline-flex;
650
+ padding: 2px 6px;
651
+ background-color: var(--bg-secondary);
652
+ border-radius: 4px;
653
+ font-size: 11px;
654
+ text-transform: uppercase;
655
+ letter-spacing: 0.5px;
656
+ }
657
+
658
+ .beads-issue-priority {
659
+ display: inline-flex;
660
+ align-items: center;
661
+ gap: 4px;
662
+ }
663
+
664
+ .priority-dot {
665
+ width: 6px;
666
+ height: 6px;
667
+ border-radius: 50%;
668
+ }
669
+
670
+ .priority-1 { background-color: var(--accent-red); }
671
+ .priority-2 { background-color: var(--accent-yellow); }
672
+ .priority-3 { background-color: var(--accent-green); }
673
+
674
+ .beads-daemon-status {
675
+ display: flex;
676
+ align-items: center;
677
+ gap: 8px;
678
+ font-size: 12px;
679
+ color: var(--text-secondary);
680
+ margin-top: 12px;
681
+ }
682
+
683
+ .beads-empty {
684
+ text-align: center;
685
+ padding: 20px;
686
+ color: var(--text-muted);
687
+ font-size: 14px;
688
+ }
689
+
690
+ .beads-issue {
691
+ cursor: pointer;
692
+ transition: background-color 0.2s ease;
693
+ }
694
+
695
+ .beads-issue:hover {
696
+ background-color: var(--bg-secondary);
697
+ }
698
+
699
+ .beads-issue-title {
700
+ display: flex;
701
+ align-items: center;
702
+ gap: 8px;
703
+ }
704
+
705
+ .beads-issue-expand {
706
+ width: 16px;
707
+ height: 16px;
708
+ color: var(--text-muted);
709
+ transition: transform 0.2s ease;
710
+ flex-shrink: 0;
711
+ }
712
+
713
+ .beads-issue.expanded .beads-issue-expand {
714
+ transform: rotate(90deg);
715
+ }
716
+
717
+ .beads-issue-description {
718
+ display: none;
719
+ margin-top: 8px;
720
+ padding: 12px;
721
+ background-color: var(--bg-secondary);
722
+ border-radius: 4px;
723
+ font-size: 13px;
724
+ color: var(--text-secondary);
725
+ white-space: pre-wrap;
726
+ word-break: break-word;
727
+ border-left: 3px solid var(--accent-blue);
728
+ }
729
+
730
+ .beads-issue.expanded .beads-issue-description {
731
+ display: block;
732
+ }
733
+
734
+ .beads-issue-no-description {
735
+ font-style: italic;
736
+ color: var(--text-muted);
737
+ }
738
+ </style>
739
+ </head>
740
+ <body>
741
+ <div class="container">
742
+ <header>
743
+ <div class="header-left">
744
+ <h1>
745
+ <div class="logo">${LOGO_SVG}</div>
746
+ lance-context
747
+ </h1>
748
+ </div>
749
+ <div class="header-right">
750
+ <button class="theme-toggle" id="themeToggle" title="Toggle theme">
751
+ <svg class="sun-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
752
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
753
+ </svg>
754
+ <svg class="moon-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
755
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
756
+ </svg>
757
+ <span class="theme-label">Theme</span>
758
+ </button>
759
+ <div class="connection-status">
760
+ <div class="status-dot" id="connectionDot"></div>
761
+ <span id="connectionText">Connecting...</span>
762
+ </div>
763
+ </div>
764
+ </header>
765
+
766
+ <div class="grid">
767
+ <!-- Index Status Card -->
768
+ <div class="card">
769
+ <div class="card-header">
770
+ <span class="card-title">Index Status</span>
771
+ <span class="badge" id="indexBadge">Loading...</span>
772
+ </div>
773
+ <div class="stat">
774
+ <div class="stat-label">Files Indexed</div>
775
+ <div class="stat-value" id="fileCount">-</div>
776
+ </div>
777
+ <div class="stat">
778
+ <div class="stat-label">Total Chunks</div>
779
+ <div class="stat-value" id="chunkCount">-</div>
780
+ </div>
781
+ <div class="stat">
782
+ <div class="stat-label">Last Updated</div>
783
+ <div class="stat-value small" id="lastUpdated">-</div>
784
+ </div>
785
+ <div class="progress-container" id="progressContainer">
786
+ <div class="progress-bar">
787
+ <div class="progress-fill" id="progressFill" style="width: 0%"></div>
788
+ </div>
789
+ <div class="progress-text" id="progressText">Initializing...</div>
790
+ </div>
791
+ </div>
792
+
793
+ <!-- Embedding Backend Card -->
794
+ <div class="card">
795
+ <div class="card-header">
796
+ <span class="card-title">Embedding Backend</span>
797
+ <span class="badge" id="embeddingStatus">-</span>
798
+ </div>
799
+ <div class="stat">
800
+ <div class="stat-label">Current Backend</div>
801
+ <div class="stat-value small" id="embeddingBackend">-</div>
802
+ </div>
803
+ <div class="stat">
804
+ <div class="stat-label">Index Path</div>
805
+ <div class="stat-value small" id="indexPath">-</div>
806
+ </div>
807
+ <div class="settings-form" id="embeddingSettingsForm">
808
+ <div class="form-group">
809
+ <label for="backendSelect">Select Backend</label>
810
+ <select id="backendSelect" class="form-select">
811
+ <option value="auto">Auto (detect available)</option>
812
+ <option value="jina">Jina AI (cloud)</option>
813
+ <option value="ollama">Ollama (local)</option>
814
+ </select>
815
+ </div>
816
+ <div class="form-group" id="apiKeyGroup" style="display: none;">
817
+ <label for="apiKeyInput">Jina API Key</label>
818
+ <input type="password" id="apiKeyInput" class="form-input" placeholder="jina_..." />
819
+ <div class="form-hint">Get your free API key at <a href="https://jina.ai/" target="_blank">jina.ai</a></div>
820
+ </div>
821
+ <div class="form-actions">
822
+ <button type="button" id="saveEmbeddingBtn" class="btn btn-primary">Save Settings</button>
823
+ <span id="saveStatus" class="save-status"></span>
824
+ </div>
825
+ </div>
826
+ </div>
827
+
828
+ <!-- Configuration Card -->
829
+ <div class="card">
830
+ <div class="card-header">
831
+ <span class="card-title">Configuration</span>
832
+ </div>
833
+ <div class="stat">
834
+ <div class="stat-label">Project Path</div>
835
+ <div class="stat-value small" id="projectPath">-</div>
836
+ </div>
837
+ <div class="stat">
838
+ <div class="stat-label">Chunk Size</div>
839
+ <div class="stat-value small" id="chunkSize">-</div>
840
+ </div>
841
+ <div class="stat">
842
+ <div class="stat-label">Search Weights</div>
843
+ <div class="stat-value small" id="searchWeights">-</div>
844
+ </div>
845
+ </div>
846
+
847
+ <!-- Patterns Card -->
848
+ <div class="card">
849
+ <div class="card-header">
850
+ <span class="card-title">File Patterns</span>
851
+ </div>
852
+ <div class="stat">
853
+ <div class="stat-label">Include</div>
854
+ <div class="patterns-list" id="includePatterns">
855
+ <span class="pattern-tag">Loading...</span>
856
+ </div>
857
+ </div>
858
+ <div class="stat" style="margin-top: 12px;">
859
+ <div class="stat-label">Exclude</div>
860
+ <div class="patterns-list" id="excludePatterns">
861
+ <span class="pattern-tag exclude">Loading...</span>
862
+ </div>
863
+ </div>
864
+ </div>
865
+
866
+ <!-- Command Usage Card -->
867
+ <div class="card double-width">
868
+ <div class="card-header">
869
+ <span class="card-title">Command Usage</span>
870
+ <span class="badge" id="sessionBadge">This Session</span>
871
+ </div>
872
+ <div id="usageChartContainer">
873
+ <div class="usage-empty" id="usageEmpty">No commands executed yet</div>
874
+ <div id="chartWrapper">
875
+ <table class="charts-css column show-labels show-primary-axis show-data data-spacing-20" id="usage-chart" style="display: none;">
876
+ <tbody id="usageChartBody"></tbody>
877
+ </table>
878
+ </div>
879
+ <ul class="charts-css legend legend-inline legend-square" id="chartLegend" style="display: none;">
880
+ <li style="--color: #58a6ff;">Search Code</li>
881
+ <li style="--color: #3fb950;">Index Codebase</li>
882
+ <li style="--color: #a371f7;">Get Status</li>
883
+ <li style="--color: #f85149;">Clear Index</li>
884
+ <li style="--color: #d29922;">Get Instructions</li>
885
+ </ul>
886
+ <div class="usage-total" id="usageTotal" style="display: none;">
887
+ <span class="usage-total-label">Total Commands</span>
888
+ <span class="usage-total-count" id="totalCount">0</span>
889
+ </div>
890
+ </div>
891
+ </div>
892
+ </div>
893
+
894
+ <!-- Beads Section -->
895
+ <div class="beads-section" id="beadsSection" style="display: none;">
896
+ <div class="beads-header">
897
+ <svg class="beads-logo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
898
+ <circle cx="12" cy="5" r="3"/>
899
+ <circle cx="12" cy="12" r="3"/>
900
+ <circle cx="12" cy="19" r="3"/>
901
+ <line x1="12" y1="8" x2="12" y2="9"/>
902
+ <line x1="12" y1="15" x2="12" y2="16"/>
903
+ </svg>
904
+ <span class="beads-title">Beads Issue Tracker</span>
905
+ </div>
906
+ <div class="grid">
907
+ <div class="card">
908
+ <div class="card-header">
909
+ <span class="card-title">Status</span>
910
+ <span class="badge success" id="beadsBadge">Active</span>
911
+ </div>
912
+ <div class="beads-stats">
913
+ <div class="beads-stat">
914
+ <span class="beads-stat-value" id="beadsReadyCount">0</span>
915
+ <span class="beads-stat-label">Ready</span>
916
+ </div>
917
+ <div class="beads-stat">
918
+ <span class="beads-stat-value" id="beadsOpenCount">0</span>
919
+ <span class="beads-stat-label">Open</span>
920
+ </div>
921
+ <div class="beads-stat">
922
+ <span class="beads-stat-value" id="beadsTotalCount">0</span>
923
+ <span class="beads-stat-label">Total</span>
924
+ </div>
925
+ </div>
926
+ <div class="beads-daemon-status" id="beadsDaemonStatus">
927
+ <div class="status-dot" id="beadsDaemonDot"></div>
928
+ <span id="beadsDaemonText">Daemon status unknown</span>
929
+ </div>
930
+ <div class="stat" style="margin-top: 12px;" id="beadsSyncBranchStat">
931
+ <div class="stat-label">Sync Branch</div>
932
+ <div class="stat-value small" id="beadsSyncBranch">-</div>
933
+ </div>
934
+ </div>
935
+
936
+ <div class="card" style="grid-column: span 2;">
937
+ <div class="card-header">
938
+ <span class="card-title">Ready Tasks</span>
939
+ <span class="badge" id="readyTasksBadge">0 tasks</span>
940
+ </div>
941
+ <div class="beads-issues" id="beadsIssuesList">
942
+ <div class="beads-empty">No ready tasks</div>
943
+ </div>
944
+ </div>
945
+ </div>
946
+ </div>
947
+ </div>
948
+
949
+ <script>
950
+ // Theme management
951
+ function getStoredTheme() {
952
+ return localStorage.getItem('lance-context-theme') || 'dark';
953
+ }
954
+
955
+ function setTheme(theme) {
956
+ document.documentElement.setAttribute('data-theme', theme);
957
+ localStorage.setItem('lance-context-theme', theme);
958
+ }
959
+
960
+ function toggleTheme() {
961
+ const currentTheme = document.documentElement.getAttribute('data-theme');
962
+ const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
963
+ setTheme(newTheme);
964
+ }
965
+
966
+ // Initialize theme
967
+ setTheme(getStoredTheme());
968
+
969
+ // Theme toggle button
970
+ document.getElementById('themeToggle').addEventListener('click', toggleTheme);
971
+
972
+ // State
973
+ let isConnected = false;
974
+ let eventSource = null;
975
+
976
+ // DOM elements
977
+ const connectionDot = document.getElementById('connectionDot');
978
+ const connectionText = document.getElementById('connectionText');
979
+ const indexBadge = document.getElementById('indexBadge');
980
+ const fileCount = document.getElementById('fileCount');
981
+ const chunkCount = document.getElementById('chunkCount');
982
+ const lastUpdated = document.getElementById('lastUpdated');
983
+ const embeddingBackend = document.getElementById('embeddingBackend');
984
+ const embeddingStatus = document.getElementById('embeddingStatus');
985
+ const indexPath = document.getElementById('indexPath');
986
+ const projectPath = document.getElementById('projectPath');
987
+ const chunkSize = document.getElementById('chunkSize');
988
+ const searchWeights = document.getElementById('searchWeights');
989
+ const includePatterns = document.getElementById('includePatterns');
990
+ const excludePatterns = document.getElementById('excludePatterns');
991
+ const progressContainer = document.getElementById('progressContainer');
992
+ const progressFill = document.getElementById('progressFill');
993
+ const progressText = document.getElementById('progressText');
994
+
995
+ // Embedding settings form elements
996
+ const backendSelect = document.getElementById('backendSelect');
997
+ const apiKeyGroup = document.getElementById('apiKeyGroup');
998
+ const apiKeyInput = document.getElementById('apiKeyInput');
999
+ const saveEmbeddingBtn = document.getElementById('saveEmbeddingBtn');
1000
+ const saveStatus = document.getElementById('saveStatus');
1001
+
1002
+ // Toggle API key input visibility based on backend selection
1003
+ backendSelect.addEventListener('change', function() {
1004
+ apiKeyGroup.style.display = this.value === 'jina' ? 'block' : 'none';
1005
+ });
1006
+
1007
+ // Load current embedding settings
1008
+ async function loadEmbeddingSettings() {
1009
+ try {
1010
+ const response = await fetch('/api/settings/embedding');
1011
+ if (response.ok) {
1012
+ const settings = await response.json();
1013
+ backendSelect.value = settings.backend || 'auto';
1014
+ apiKeyGroup.style.display = settings.backend === 'jina' ? 'block' : 'none';
1015
+
1016
+ // Update status badge
1017
+ if (settings.hasApiKey) {
1018
+ embeddingStatus.textContent = 'API Key Set';
1019
+ embeddingStatus.className = 'badge success';
1020
+ } else if (settings.backend === 'ollama') {
1021
+ embeddingStatus.textContent = 'Local';
1022
+ embeddingStatus.className = 'badge';
1023
+ } else {
1024
+ embeddingStatus.textContent = 'Not Configured';
1025
+ embeddingStatus.className = 'badge warning';
1026
+ }
1027
+ }
1028
+ } catch (error) {
1029
+ console.error('Failed to load embedding settings:', error);
1030
+ }
1031
+ }
1032
+
1033
+ // Save embedding settings
1034
+ saveEmbeddingBtn.addEventListener('click', async function() {
1035
+ const backend = backendSelect.value;
1036
+ const apiKey = apiKeyInput.value.trim();
1037
+
1038
+ if (backend === 'jina' && !apiKey) {
1039
+ saveStatus.textContent = 'API key required for Jina';
1040
+ saveStatus.className = 'save-status error';
1041
+ return;
1042
+ }
1043
+
1044
+ saveEmbeddingBtn.disabled = true;
1045
+ saveStatus.textContent = 'Saving...';
1046
+ saveStatus.className = 'save-status';
1047
+
1048
+ try {
1049
+ const response = await fetch('/api/settings/embedding', {
1050
+ method: 'POST',
1051
+ headers: { 'Content-Type': 'application/json' },
1052
+ body: JSON.stringify({
1053
+ backend: backend === 'auto' ? 'ollama' : backend,
1054
+ apiKey: backend === 'jina' ? apiKey : undefined
1055
+ })
1056
+ });
1057
+
1058
+ const result = await response.json();
1059
+
1060
+ if (response.ok) {
1061
+ saveStatus.textContent = 'Saved! Restart server to apply.';
1062
+ saveStatus.className = 'save-status success';
1063
+ apiKeyInput.value = ''; // Clear the input
1064
+ loadEmbeddingSettings(); // Reload to update status
1065
+ } else {
1066
+ saveStatus.textContent = result.error || 'Failed to save';
1067
+ saveStatus.className = 'save-status error';
1068
+ }
1069
+ } catch (error) {
1070
+ saveStatus.textContent = 'Network error';
1071
+ saveStatus.className = 'save-status error';
1072
+ } finally {
1073
+ saveEmbeddingBtn.disabled = false;
1074
+ }
1075
+ });
1076
+
1077
+ // Load embedding settings on page load
1078
+ loadEmbeddingSettings();
1079
+
1080
+ // Format date
1081
+ function formatDate(isoString) {
1082
+ if (!isoString) return 'Never';
1083
+ const date = new Date(isoString);
1084
+ return date.toLocaleString();
1085
+ }
1086
+
1087
+ // Update connection status
1088
+ function setConnected(connected) {
1089
+ isConnected = connected;
1090
+ connectionDot.className = 'status-dot' + (connected ? ' connected' : '');
1091
+ connectionText.textContent = connected ? 'Connected' : 'Disconnected';
1092
+ }
1093
+
1094
+ // Update index status
1095
+ function updateStatus(status) {
1096
+ if (status.indexed) {
1097
+ indexBadge.textContent = 'Indexed';
1098
+ indexBadge.className = 'badge success';
1099
+ } else {
1100
+ indexBadge.textContent = 'Not Indexed';
1101
+ indexBadge.className = 'badge warning';
1102
+ }
1103
+
1104
+ if (status.isIndexing) {
1105
+ indexBadge.textContent = 'Indexing...';
1106
+ indexBadge.className = 'badge warning pulsing';
1107
+ progressContainer.className = 'progress-container active';
1108
+ } else {
1109
+ progressContainer.className = 'progress-container';
1110
+ }
1111
+
1112
+ fileCount.textContent = status.fileCount.toLocaleString();
1113
+ chunkCount.textContent = status.chunkCount.toLocaleString();
1114
+ lastUpdated.textContent = formatDate(status.lastUpdated);
1115
+ embeddingBackend.textContent = status.embeddingBackend || 'Not configured';
1116
+ indexPath.textContent = status.indexPath || '-';
1117
+ }
1118
+
1119
+ // Update config display
1120
+ function updateConfig(config) {
1121
+ projectPath.textContent = config.projectPath || '-';
1122
+
1123
+ if (config.chunking) {
1124
+ chunkSize.textContent = config.chunking.maxLines + ' lines (overlap: ' + config.chunking.overlap + ')';
1125
+ }
1126
+
1127
+ if (config.search) {
1128
+ searchWeights.textContent = 'Semantic: ' + (config.search.semanticWeight * 100) + '%, Keyword: ' + (config.search.keywordWeight * 100) + '%';
1129
+ }
1130
+
1131
+ // Update patterns
1132
+ if (config.patterns) {
1133
+ includePatterns.innerHTML = config.patterns
1134
+ .slice(0, 10)
1135
+ .map(p => '<span class="pattern-tag">' + escapeHtml(p) + '</span>')
1136
+ .join('');
1137
+ if (config.patterns.length > 10) {
1138
+ includePatterns.innerHTML += '<span class="pattern-tag">+' + (config.patterns.length - 10) + ' more</span>';
1139
+ }
1140
+ }
1141
+
1142
+ if (config.excludePatterns) {
1143
+ excludePatterns.innerHTML = config.excludePatterns
1144
+ .slice(0, 6)
1145
+ .map(p => '<span class="pattern-tag exclude">' + escapeHtml(p) + '</span>')
1146
+ .join('');
1147
+ if (config.excludePatterns.length > 6) {
1148
+ excludePatterns.innerHTML += '<span class="pattern-tag exclude">+' + (config.excludePatterns.length - 6) + ' more</span>';
1149
+ }
1150
+ }
1151
+ }
1152
+
1153
+ // Update progress
1154
+ function updateProgress(progress) {
1155
+ const percent = progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : 0;
1156
+ progressFill.style.width = percent + '%';
1157
+ progressText.textContent = progress.message;
1158
+ }
1159
+
1160
+ // Charts.css color mapping
1161
+ const commandColors = {
1162
+ 'search_code': '#58a6ff',
1163
+ 'search_similar': '#79c0ff',
1164
+ 'index_codebase': '#3fb950',
1165
+ 'get_index_status': '#a371f7',
1166
+ 'clear_index': '#f85149',
1167
+ 'get_project_instructions': '#d29922',
1168
+ 'commit': '#56d364'
1169
+ };
1170
+
1171
+ // Update usage chart using charts.css
1172
+ const usageEmpty = document.getElementById('usageEmpty');
1173
+ const usageChartEl = document.getElementById('usage-chart');
1174
+ const usageChartBody = document.getElementById('usageChartBody');
1175
+ const chartLegend = document.getElementById('chartLegend');
1176
+ const usageTotal = document.getElementById('usageTotal');
1177
+ const totalCount = document.getElementById('totalCount');
1178
+
1179
+ function updateUsage(data) {
1180
+ const { usage, total } = data;
1181
+
1182
+ if (total === 0) {
1183
+ usageEmpty.style.display = 'block';
1184
+ usageChartEl.style.display = 'none';
1185
+ chartLegend.style.display = 'none';
1186
+ usageTotal.style.display = 'none';
1187
+ return;
1188
+ }
1189
+
1190
+ usageEmpty.style.display = 'none';
1191
+ usageChartEl.style.display = 'block';
1192
+ chartLegend.style.display = 'flex';
1193
+ usageTotal.style.display = 'flex';
1194
+
1195
+ const maxCount = Math.max(...usage.map(u => u.count));
1196
+
1197
+ let html = '';
1198
+ for (const item of usage) {
1199
+ if (item.count === 0) continue;
1200
+
1201
+ const percent = maxCount > 0 ? (item.count / maxCount) : 0;
1202
+ const color = commandColors[item.command] || '#58a6ff';
1203
+
1204
+ html += '<tr>';
1205
+ html += '<th scope="row">' + escapeHtml(item.label) + '</th>';
1206
+ html += '<td style="--size: ' + percent + '; --color: ' + color + ';"><span class="data">' + item.count + '</span></td>';
1207
+ html += '</tr>';
1208
+ }
1209
+
1210
+ usageChartBody.innerHTML = html;
1211
+ totalCount.textContent = total;
1212
+ }
1213
+
1214
+ // Beads section elements
1215
+ const beadsSection = document.getElementById('beadsSection');
1216
+ const beadsReadyCount = document.getElementById('beadsReadyCount');
1217
+ const beadsOpenCount = document.getElementById('beadsOpenCount');
1218
+ const beadsTotalCount = document.getElementById('beadsTotalCount');
1219
+ const beadsDaemonDot = document.getElementById('beadsDaemonDot');
1220
+ const beadsDaemonText = document.getElementById('beadsDaemonText');
1221
+ const beadsSyncBranch = document.getElementById('beadsSyncBranch');
1222
+ const beadsIssuesList = document.getElementById('beadsIssuesList');
1223
+ const readyTasksBadge = document.getElementById('readyTasksBadge');
1224
+
1225
+ function updateBeads(data) {
1226
+ if (!data.available) {
1227
+ beadsSection.style.display = 'none';
1228
+ return;
1229
+ }
1230
+
1231
+ beadsSection.style.display = 'block';
1232
+ beadsReadyCount.textContent = data.readyCount;
1233
+ beadsOpenCount.textContent = data.openCount;
1234
+ beadsTotalCount.textContent = data.issueCount;
1235
+ readyTasksBadge.textContent = data.readyCount + ' task' + (data.readyCount !== 1 ? 's' : '');
1236
+
1237
+ // Daemon status
1238
+ if (data.daemonRunning) {
1239
+ beadsDaemonDot.className = 'status-dot connected';
1240
+ beadsDaemonText.textContent = 'Daemon running';
1241
+ } else {
1242
+ beadsDaemonDot.className = 'status-dot';
1243
+ beadsDaemonText.textContent = 'Daemon not running';
1244
+ }
1245
+
1246
+ // Sync branch
1247
+ beadsSyncBranch.textContent = data.syncBranch || 'Not configured';
1248
+
1249
+ // Issues list
1250
+ if (data.issues && data.issues.length > 0) {
1251
+ let html = '';
1252
+ for (const issue of data.issues) {
1253
+ const hasDescription = issue.description && issue.description.trim();
1254
+ html += '<div class="beads-issue" onclick="toggleBeadsIssue(this)">';
1255
+ html += '<span class="beads-issue-id">' + escapeHtml(issue.id) + '</span>';
1256
+ html += '<div class="beads-issue-content">';
1257
+ html += '<div class="beads-issue-title">';
1258
+ html += '<svg class="beads-issue-expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>';
1259
+ html += '<span>' + escapeHtml(issue.title) + '</span>';
1260
+ html += '</div>';
1261
+ html += '<div class="beads-issue-meta">';
1262
+ if (issue.issue_type) {
1263
+ html += '<span class="beads-issue-type">' + escapeHtml(issue.issue_type) + '</span>';
1264
+ }
1265
+ if (issue.priority) {
1266
+ html += '<span class="beads-issue-priority">';
1267
+ html += '<span class="priority-dot priority-' + issue.priority + '"></span>';
1268
+ html += 'P' + issue.priority;
1269
+ html += '</span>';
1270
+ }
1271
+ html += '</div>';
1272
+ if (hasDescription) {
1273
+ html += '<div class="beads-issue-description">' + escapeHtml(issue.description) + '</div>';
1274
+ } else {
1275
+ html += '<div class="beads-issue-description beads-issue-no-description">No description available</div>';
1276
+ }
1277
+ html += '</div>';
1278
+ html += '</div>';
1279
+ }
1280
+ beadsIssuesList.innerHTML = html;
1281
+ } else {
1282
+ beadsIssuesList.innerHTML = '<div class="beads-empty">No ready tasks</div>';
1283
+ }
1284
+ }
1285
+
1286
+ // Toggle beads issue expansion
1287
+ function toggleBeadsIssue(element) {
1288
+ element.classList.toggle('expanded');
1289
+ }
1290
+
1291
+ // Escape HTML to prevent XSS
1292
+ function escapeHtml(text) {
1293
+ const div = document.createElement('div');
1294
+ div.textContent = text;
1295
+ return div.innerHTML;
1296
+ }
1297
+
1298
+ // Fetch initial data
1299
+ async function fetchData() {
1300
+ try {
1301
+ const [statusRes, configRes, usageRes, beadsRes] = await Promise.all([
1302
+ fetch('/api/status'),
1303
+ fetch('/api/config'),
1304
+ fetch('/api/usage'),
1305
+ fetch('/api/beads')
1306
+ ]);
1307
+
1308
+ let currentStatus = null;
1309
+ let currentConfig = null;
1310
+
1311
+ if (statusRes.ok) {
1312
+ currentStatus = await statusRes.json();
1313
+ updateStatus(currentStatus);
1314
+ }
1315
+
1316
+ if (configRes.ok) {
1317
+ currentConfig = await configRes.json();
1318
+ updateConfig(currentConfig);
1319
+ }
1320
+
1321
+ // Check if configured backend differs from running backend
1322
+ if (currentStatus && currentConfig) {
1323
+ const runningBackend = currentStatus.embeddingBackend;
1324
+ const configuredBackend = currentConfig.embedding?.backend;
1325
+ if (configuredBackend && runningBackend && configuredBackend !== runningBackend) {
1326
+ embeddingBackend.innerHTML = runningBackend + ' <span class="badge warning" title="Restart required to use ' + configuredBackend + '">\u26a0 restart needed</span>';
1327
+ }
1328
+ }
1329
+
1330
+ if (usageRes.ok) {
1331
+ const usage = await usageRes.json();
1332
+ updateUsage(usage);
1333
+ }
1334
+
1335
+ if (beadsRes.ok) {
1336
+ const beads = await beadsRes.json();
1337
+ updateBeads(beads);
1338
+ }
1339
+ } catch (error) {
1340
+ console.error('Failed to fetch data:', error);
1341
+ }
1342
+ }
1343
+
1344
+ // Connect to SSE
1345
+ function connectSSE() {
1346
+ if (eventSource) {
1347
+ eventSource.close();
1348
+ }
1349
+
1350
+ eventSource = new EventSource('/api/events');
1351
+
1352
+ eventSource.addEventListener('connected', (e) => {
1353
+ setConnected(true);
1354
+ fetchData();
1355
+ });
1356
+
1357
+ eventSource.addEventListener('indexing:progress', (e) => {
1358
+ const progress = JSON.parse(e.data);
1359
+ progressContainer.className = 'progress-container active';
1360
+ indexBadge.textContent = 'Indexing...';
1361
+ indexBadge.className = 'badge warning pulsing';
1362
+ updateProgress(progress);
1363
+ });
1364
+
1365
+ eventSource.addEventListener('indexing:start', () => {
1366
+ progressContainer.className = 'progress-container active';
1367
+ indexBadge.textContent = 'Indexing...';
1368
+ indexBadge.className = 'badge warning pulsing';
1369
+ progressFill.style.width = '0%';
1370
+ progressText.textContent = 'Starting...';
1371
+ });
1372
+
1373
+ eventSource.addEventListener('indexing:complete', () => {
1374
+ progressContainer.className = 'progress-container';
1375
+ fetchData();
1376
+ });
1377
+
1378
+ eventSource.addEventListener('status:change', (e) => {
1379
+ const status = JSON.parse(e.data);
1380
+ updateStatus(status);
1381
+ });
1382
+
1383
+ eventSource.addEventListener('usage:update', (e) => {
1384
+ const usage = JSON.parse(e.data);
1385
+ // The event data is the usage array, need to compute total
1386
+ const total = usage.reduce((sum, u) => sum + u.count, 0);
1387
+ updateUsage({ usage, total });
1388
+ });
1389
+
1390
+ eventSource.addEventListener('heartbeat', () => {
1391
+ // Just keep connection alive
1392
+ });
1393
+
1394
+ eventSource.onerror = () => {
1395
+ setConnected(false);
1396
+ // EventSource will automatically reconnect
1397
+ };
1398
+ }
1399
+
1400
+ // Initialize
1401
+ fetchData();
1402
+ connectSSE();
1403
+ </script>
1404
+ </body>
1405
+ </html>`;
1406
+ }
1407
+ //# sourceMappingURL=ui.js.map