glancey 2.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.
- package/LICENSE +21 -0
- package/README.md +435 -0
- package/dist/__tests__/ast-chunker.test.d.ts +2 -0
- package/dist/__tests__/ast-chunker.test.d.ts.map +1 -0
- package/dist/__tests__/ast-chunker.test.js +307 -0
- package/dist/__tests__/ast-chunker.test.js.map +1 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +242 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/dashboard/beads.test.d.ts +2 -0
- package/dist/__tests__/dashboard/beads.test.d.ts.map +1 -0
- package/dist/__tests__/dashboard/beads.test.js +151 -0
- package/dist/__tests__/dashboard/beads.test.js.map +1 -0
- package/dist/__tests__/dashboard/index.test.d.ts +2 -0
- package/dist/__tests__/dashboard/index.test.d.ts.map +1 -0
- package/dist/__tests__/dashboard/index.test.js +116 -0
- package/dist/__tests__/dashboard/index.test.js.map +1 -0
- package/dist/__tests__/dashboard/routes.test.d.ts +2 -0
- package/dist/__tests__/dashboard/routes.test.d.ts.map +1 -0
- package/dist/__tests__/dashboard/routes.test.js +125 -0
- package/dist/__tests__/dashboard/routes.test.js.map +1 -0
- package/dist/__tests__/dashboard/server.test.d.ts +2 -0
- package/dist/__tests__/dashboard/server.test.d.ts.map +1 -0
- package/dist/__tests__/dashboard/server.test.js +75 -0
- package/dist/__tests__/dashboard/server.test.js.map +1 -0
- package/dist/__tests__/dashboard/state.test.d.ts +2 -0
- package/dist/__tests__/dashboard/state.test.d.ts.map +1 -0
- package/dist/__tests__/dashboard/state.test.js +124 -0
- package/dist/__tests__/dashboard/state.test.js.map +1 -0
- package/dist/__tests__/dashboard/token-tracking.test.d.ts +2 -0
- package/dist/__tests__/dashboard/token-tracking.test.d.ts.map +1 -0
- package/dist/__tests__/dashboard/token-tracking.test.js +315 -0
- package/dist/__tests__/dashboard/token-tracking.test.js.map +1 -0
- package/dist/__tests__/embeddings/factory.test.d.ts +2 -0
- package/dist/__tests__/embeddings/factory.test.d.ts.map +1 -0
- package/dist/__tests__/embeddings/factory.test.js +157 -0
- package/dist/__tests__/embeddings/factory.test.js.map +1 -0
- package/dist/__tests__/embeddings/gemini.test.d.ts +2 -0
- package/dist/__tests__/embeddings/gemini.test.d.ts.map +1 -0
- package/dist/__tests__/embeddings/gemini.test.js +178 -0
- package/dist/__tests__/embeddings/gemini.test.js.map +1 -0
- package/dist/__tests__/embeddings/ollama.test.d.ts +2 -0
- package/dist/__tests__/embeddings/ollama.test.d.ts.map +1 -0
- package/dist/__tests__/embeddings/ollama.test.js +273 -0
- package/dist/__tests__/embeddings/ollama.test.js.map +1 -0
- package/dist/__tests__/embeddings/rate-limiter.test.d.ts +2 -0
- package/dist/__tests__/embeddings/rate-limiter.test.d.ts.map +1 -0
- package/dist/__tests__/embeddings/rate-limiter.test.js +163 -0
- package/dist/__tests__/embeddings/rate-limiter.test.js.map +1 -0
- package/dist/__tests__/embeddings/retry.test.d.ts +2 -0
- package/dist/__tests__/embeddings/retry.test.d.ts.map +1 -0
- package/dist/__tests__/embeddings/retry.test.js +334 -0
- package/dist/__tests__/embeddings/retry.test.js.map +1 -0
- package/dist/__tests__/embeddings/types.test.d.ts +2 -0
- package/dist/__tests__/embeddings/types.test.d.ts.map +1 -0
- package/dist/__tests__/embeddings/types.test.js +31 -0
- package/dist/__tests__/embeddings/types.test.js.map +1 -0
- package/dist/__tests__/mocks/embedding-backend.mock.d.ts +10 -0
- package/dist/__tests__/mocks/embedding-backend.mock.d.ts.map +1 -0
- package/dist/__tests__/mocks/embedding-backend.mock.js +39 -0
- package/dist/__tests__/mocks/embedding-backend.mock.js.map +1 -0
- package/dist/__tests__/mocks/fetch.mock.d.ts +49 -0
- package/dist/__tests__/mocks/fetch.mock.d.ts.map +1 -0
- package/dist/__tests__/mocks/fetch.mock.js +102 -0
- package/dist/__tests__/mocks/fetch.mock.js.map +1 -0
- package/dist/__tests__/mocks/lancedb.mock.d.ts +38 -0
- package/dist/__tests__/mocks/lancedb.mock.d.ts.map +1 -0
- package/dist/__tests__/mocks/lancedb.mock.js +63 -0
- package/dist/__tests__/mocks/lancedb.mock.js.map +1 -0
- package/dist/__tests__/search/clustering.test.d.ts +2 -0
- package/dist/__tests__/search/clustering.test.d.ts.map +1 -0
- package/dist/__tests__/search/clustering.test.js +230 -0
- package/dist/__tests__/search/clustering.test.js.map +1 -0
- package/dist/__tests__/search/hybrid-search.test.d.ts +2 -0
- package/dist/__tests__/search/hybrid-search.test.d.ts.map +1 -0
- package/dist/__tests__/search/hybrid-search.test.js +279 -0
- package/dist/__tests__/search/hybrid-search.test.js.map +1 -0
- package/dist/__tests__/search/indexer.test.d.ts +2 -0
- package/dist/__tests__/search/indexer.test.d.ts.map +1 -0
- package/dist/__tests__/search/indexer.test.js +1305 -0
- package/dist/__tests__/search/indexer.test.js.map +1 -0
- package/dist/__tests__/search/tree-sitter-chunker.test.d.ts +2 -0
- package/dist/__tests__/search/tree-sitter-chunker.test.d.ts.map +1 -0
- package/dist/__tests__/search/tree-sitter-chunker.test.js +228 -0
- package/dist/__tests__/search/tree-sitter-chunker.test.js.map +1 -0
- package/dist/__tests__/setup.d.ts +2 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +11 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/__tests__/tools/clustering-handlers.test.d.ts +2 -0
- package/dist/__tests__/tools/clustering-handlers.test.d.ts.map +1 -0
- package/dist/__tests__/tools/clustering-handlers.test.js +286 -0
- package/dist/__tests__/tools/clustering-handlers.test.js.map +1 -0
- package/dist/__tests__/tools/commit-handlers.test.d.ts +2 -0
- package/dist/__tests__/tools/commit-handlers.test.d.ts.map +1 -0
- package/dist/__tests__/tools/commit-handlers.test.js +153 -0
- package/dist/__tests__/tools/commit-handlers.test.js.map +1 -0
- package/dist/__tests__/tools/index-handlers.test.d.ts +2 -0
- package/dist/__tests__/tools/index-handlers.test.d.ts.map +1 -0
- package/dist/__tests__/tools/index-handlers.test.js +184 -0
- package/dist/__tests__/tools/index-handlers.test.js.map +1 -0
- package/dist/__tests__/tools/instructions-handlers.test.d.ts +2 -0
- package/dist/__tests__/tools/instructions-handlers.test.d.ts.map +1 -0
- package/dist/__tests__/tools/instructions-handlers.test.js +53 -0
- package/dist/__tests__/tools/instructions-handlers.test.js.map +1 -0
- package/dist/__tests__/tools/memory-handlers.test.d.ts +2 -0
- package/dist/__tests__/tools/memory-handlers.test.d.ts.map +1 -0
- package/dist/__tests__/tools/memory-handlers.test.js +175 -0
- package/dist/__tests__/tools/memory-handlers.test.js.map +1 -0
- package/dist/__tests__/tools/search-handlers.test.d.ts +2 -0
- package/dist/__tests__/tools/search-handlers.test.d.ts.map +1 -0
- package/dist/__tests__/tools/search-handlers.test.js +229 -0
- package/dist/__tests__/tools/search-handlers.test.js.map +1 -0
- package/dist/__tests__/tools/symbol-handlers.test.d.ts +2 -0
- package/dist/__tests__/tools/symbol-handlers.test.d.ts.map +1 -0
- package/dist/__tests__/tools/symbol-handlers.test.js +348 -0
- package/dist/__tests__/tools/symbol-handlers.test.js.map +1 -0
- package/dist/__tests__/tools/worktree-handlers.test.d.ts +2 -0
- package/dist/__tests__/tools/worktree-handlers.test.d.ts.map +1 -0
- package/dist/__tests__/tools/worktree-handlers.test.js +297 -0
- package/dist/__tests__/tools/worktree-handlers.test.js.map +1 -0
- package/dist/__tests__/utils/cache.test.d.ts +2 -0
- package/dist/__tests__/utils/cache.test.d.ts.map +1 -0
- package/dist/__tests__/utils/cache.test.js +147 -0
- package/dist/__tests__/utils/cache.test.js.map +1 -0
- package/dist/__tests__/utils/concurrency.test.d.ts +2 -0
- package/dist/__tests__/utils/concurrency.test.d.ts.map +1 -0
- package/dist/__tests__/utils/concurrency.test.js +83 -0
- package/dist/__tests__/utils/concurrency.test.js.map +1 -0
- package/dist/__tests__/utils/errors.test.d.ts +2 -0
- package/dist/__tests__/utils/errors.test.d.ts.map +1 -0
- package/dist/__tests__/utils/errors.test.js +136 -0
- package/dist/__tests__/utils/errors.test.js.map +1 -0
- package/dist/__tests__/utils/logger.test.d.ts +2 -0
- package/dist/__tests__/utils/logger.test.d.ts.map +1 -0
- package/dist/__tests__/utils/logger.test.js +131 -0
- package/dist/__tests__/utils/logger.test.js.map +1 -0
- package/dist/__tests__/utils/type-guards.test.d.ts +2 -0
- package/dist/__tests__/utils/type-guards.test.d.ts.map +1 -0
- package/dist/__tests__/utils/type-guards.test.js +80 -0
- package/dist/__tests__/utils/type-guards.test.js.map +1 -0
- package/dist/__tests__/worktree/worktree-manager.test.d.ts +2 -0
- package/dist/__tests__/worktree/worktree-manager.test.d.ts.map +1 -0
- package/dist/__tests__/worktree/worktree-manager.test.js +403 -0
- package/dist/__tests__/worktree/worktree-manager.test.js.map +1 -0
- package/dist/config.d.ts +191 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +788 -0
- package/dist/config.js.map +1 -0
- package/dist/dashboard/beads.d.ts +35 -0
- package/dist/dashboard/beads.d.ts.map +1 -0
- package/dist/dashboard/beads.js +102 -0
- package/dist/dashboard/beads.js.map +1 -0
- package/dist/dashboard/events.d.ts +64 -0
- package/dist/dashboard/events.d.ts.map +1 -0
- package/dist/dashboard/events.js +211 -0
- package/dist/dashboard/events.js.map +1 -0
- package/dist/dashboard/index.d.ts +69 -0
- package/dist/dashboard/index.d.ts.map +1 -0
- package/dist/dashboard/index.js +93 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/dashboard/routes.d.ts +6 -0
- package/dist/dashboard/routes.d.ts.map +1 -0
- package/dist/dashboard/routes.js +505 -0
- package/dist/dashboard/routes.js.map +1 -0
- package/dist/dashboard/server.d.ts +27 -0
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/dashboard/server.js +72 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/dashboard/state.d.ts +172 -0
- package/dist/dashboard/state.d.ts.map +1 -0
- package/dist/dashboard/state.js +362 -0
- package/dist/dashboard/state.js.map +1 -0
- package/dist/dashboard/token-tracking.d.ts +143 -0
- package/dist/dashboard/token-tracking.d.ts.map +1 -0
- package/dist/dashboard/token-tracking.js +363 -0
- package/dist/dashboard/token-tracking.js.map +1 -0
- package/dist/dashboard/ui.d.ts +6 -0
- package/dist/dashboard/ui.d.ts.map +1 -0
- package/dist/dashboard/ui.js +2710 -0
- package/dist/dashboard/ui.js.map +1 -0
- package/dist/embeddings/gemini.d.ts +28 -0
- package/dist/embeddings/gemini.d.ts.map +1 -0
- package/dist/embeddings/gemini.js +165 -0
- package/dist/embeddings/gemini.js.map +1 -0
- package/dist/embeddings/index.d.ts +34 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +120 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/embeddings/ollama.d.ts +22 -0
- package/dist/embeddings/ollama.d.ts.map +1 -0
- package/dist/embeddings/ollama.js +179 -0
- package/dist/embeddings/ollama.js.map +1 -0
- package/dist/embeddings/rate-limiter.d.ts +75 -0
- package/dist/embeddings/rate-limiter.d.ts.map +1 -0
- package/dist/embeddings/rate-limiter.js +145 -0
- package/dist/embeddings/rate-limiter.js.map +1 -0
- package/dist/embeddings/retry.d.ts +20 -0
- package/dist/embeddings/retry.d.ts.map +1 -0
- package/dist/embeddings/retry.js +227 -0
- package/dist/embeddings/retry.js.map +1 -0
- package/dist/embeddings/types.d.ts +103 -0
- package/dist/embeddings/types.d.ts.map +1 -0
- package/dist/embeddings/types.js +23 -0
- package/dist/embeddings/types.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2028 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/index.d.ts +63 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +168 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/search/ast-chunker.d.ts +29 -0
- package/dist/search/ast-chunker.d.ts.map +1 -0
- package/dist/search/ast-chunker.js +236 -0
- package/dist/search/ast-chunker.js.map +1 -0
- package/dist/search/chunk-utils.d.ts +24 -0
- package/dist/search/chunk-utils.d.ts.map +1 -0
- package/dist/search/chunk-utils.js +41 -0
- package/dist/search/chunk-utils.js.map +1 -0
- package/dist/search/clustering.d.ts +77 -0
- package/dist/search/clustering.d.ts.map +1 -0
- package/dist/search/clustering.js +455 -0
- package/dist/search/clustering.js.map +1 -0
- package/dist/search/indexer.d.ts +377 -0
- package/dist/search/indexer.d.ts.map +1 -0
- package/dist/search/indexer.js +1557 -0
- package/dist/search/indexer.js.map +1 -0
- package/dist/search/tree-sitter-chunker.d.ts +64 -0
- package/dist/search/tree-sitter-chunker.d.ts.map +1 -0
- package/dist/search/tree-sitter-chunker.js +412 -0
- package/dist/search/tree-sitter-chunker.js.map +1 -0
- package/dist/symbols/index.d.ts +14 -0
- package/dist/symbols/index.d.ts.map +1 -0
- package/dist/symbols/index.js +19 -0
- package/dist/symbols/index.js.map +1 -0
- package/dist/symbols/name-path.d.ts +113 -0
- package/dist/symbols/name-path.d.ts.map +1 -0
- package/dist/symbols/name-path.js +194 -0
- package/dist/symbols/name-path.js.map +1 -0
- package/dist/symbols/pattern-search.d.ts +14 -0
- package/dist/symbols/pattern-search.d.ts.map +1 -0
- package/dist/symbols/pattern-search.js +224 -0
- package/dist/symbols/pattern-search.js.map +1 -0
- package/dist/symbols/reference-finder.d.ts +38 -0
- package/dist/symbols/reference-finder.d.ts.map +1 -0
- package/dist/symbols/reference-finder.js +376 -0
- package/dist/symbols/reference-finder.js.map +1 -0
- package/dist/symbols/symbol-editor.d.ts +81 -0
- package/dist/symbols/symbol-editor.d.ts.map +1 -0
- package/dist/symbols/symbol-editor.js +257 -0
- package/dist/symbols/symbol-editor.js.map +1 -0
- package/dist/symbols/symbol-extractor.d.ts +49 -0
- package/dist/symbols/symbol-extractor.d.ts.map +1 -0
- package/dist/symbols/symbol-extractor.js +593 -0
- package/dist/symbols/symbol-extractor.js.map +1 -0
- package/dist/symbols/symbol-renamer.d.ts +81 -0
- package/dist/symbols/symbol-renamer.d.ts.map +1 -0
- package/dist/symbols/symbol-renamer.js +204 -0
- package/dist/symbols/symbol-renamer.js.map +1 -0
- package/dist/symbols/types.d.ts +234 -0
- package/dist/symbols/types.d.ts.map +1 -0
- package/dist/symbols/types.js +106 -0
- package/dist/symbols/types.js.map +1 -0
- package/dist/tools/clustering-handlers.d.ts +70 -0
- package/dist/tools/clustering-handlers.d.ts.map +1 -0
- package/dist/tools/clustering-handlers.js +150 -0
- package/dist/tools/clustering-handlers.js.map +1 -0
- package/dist/tools/commit-handlers.d.ts +77 -0
- package/dist/tools/commit-handlers.d.ts.map +1 -0
- package/dist/tools/commit-handlers.js +251 -0
- package/dist/tools/commit-handlers.js.map +1 -0
- package/dist/tools/index-handlers.d.ts +31 -0
- package/dist/tools/index-handlers.d.ts.map +1 -0
- package/dist/tools/index-handlers.js +53 -0
- package/dist/tools/index-handlers.js.map +1 -0
- package/dist/tools/instructions-handlers.d.ts +25 -0
- package/dist/tools/instructions-handlers.d.ts.map +1 -0
- package/dist/tools/instructions-handlers.js +36 -0
- package/dist/tools/instructions-handlers.js.map +1 -0
- package/dist/tools/memory-handlers.d.ts +95 -0
- package/dist/tools/memory-handlers.d.ts.map +1 -0
- package/dist/tools/memory-handlers.js +126 -0
- package/dist/tools/memory-handlers.js.map +1 -0
- package/dist/tools/search-handlers.d.ts +51 -0
- package/dist/tools/search-handlers.d.ts.map +1 -0
- package/dist/tools/search-handlers.js +128 -0
- package/dist/tools/search-handlers.js.map +1 -0
- package/dist/tools/symbol-handlers.d.ts +171 -0
- package/dist/tools/symbol-handlers.d.ts.map +1 -0
- package/dist/tools/symbol-handlers.js +456 -0
- package/dist/tools/symbol-handlers.js.map +1 -0
- package/dist/tools/types.d.ts +32 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +17 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/worktree-handlers.d.ts +96 -0
- package/dist/tools/worktree-handlers.d.ts.map +1 -0
- package/dist/tools/worktree-handlers.js +186 -0
- package/dist/tools/worktree-handlers.js.map +1 -0
- package/dist/utils/cache.d.ts +77 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +134 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/concurrency.d.ts +32 -0
- package/dist/utils/concurrency.d.ts.map +1 -0
- package/dist/utils/concurrency.js +57 -0
- package/dist/utils/concurrency.js.map +1 -0
- package/dist/utils/errors.d.ts +36 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +91 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +37 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +114 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/type-guards.d.ts +17 -0
- package/dist/utils/type-guards.d.ts.map +1 -0
- package/dist/utils/type-guards.js +25 -0
- package/dist/utils/type-guards.js.map +1 -0
- package/dist/worktree/index.d.ts +6 -0
- package/dist/worktree/index.d.ts.map +1 -0
- package/dist/worktree/index.js +6 -0
- package/dist/worktree/index.js.map +1 -0
- package/dist/worktree/types.d.ts +101 -0
- package/dist/worktree/types.d.ts.map +1 -0
- package/dist/worktree/types.js +6 -0
- package/dist/worktree/types.js.map +1 -0
- package/dist/worktree/worktree-manager.d.ts +80 -0
- package/dist/worktree/worktree-manager.d.ts.map +1 -0
- package/dist/worktree/worktree-manager.js +407 -0
- package/dist/worktree/worktree-manager.js.map +1 -0
- package/package.json +87 -0
- package/scripts/postinstall.js +48 -0
|
@@ -0,0 +1,2710 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lance logo as base64 PNG (40x36)
|
|
3
|
+
*/
|
|
4
|
+
const LOGO_PNG = `data:image/png;base64,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`;
|
|
5
|
+
/**
|
|
6
|
+
* Favicon as base64 PNG data URL (32x29)
|
|
7
|
+
*/
|
|
8
|
+
const FAVICON_PNG = `data:image/png;base64,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`;
|
|
9
|
+
/**
|
|
10
|
+
* Generate the dashboard HTML page.
|
|
11
|
+
* This is a self-contained HTML page with embedded CSS and JavaScript.
|
|
12
|
+
*/
|
|
13
|
+
export function getDashboardHTML() {
|
|
14
|
+
return `<!DOCTYPE html>
|
|
15
|
+
<html lang="en" data-theme="dark">
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="UTF-8">
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
<title>glancey Dashboard</title>
|
|
20
|
+
<link rel="icon" type="image/png" href="${FAVICON_PNG}">
|
|
21
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/charts.css/dist/charts.min.css">
|
|
22
|
+
<style>
|
|
23
|
+
:root {
|
|
24
|
+
/* Light theme */
|
|
25
|
+
--bg-primary-light: #ffffff;
|
|
26
|
+
--bg-secondary-light: #f6f8fa;
|
|
27
|
+
--bg-tertiary-light: #eaeef2;
|
|
28
|
+
--border-color-light: #d0d7de;
|
|
29
|
+
--text-primary-light: #1f2328;
|
|
30
|
+
--text-secondary-light: #656d76;
|
|
31
|
+
--text-muted-light: #8c959f;
|
|
32
|
+
--accent-blue: #0969da;
|
|
33
|
+
--accent-green: #1a7f37;
|
|
34
|
+
--accent-yellow: #9a6700;
|
|
35
|
+
--accent-red: #cf222e;
|
|
36
|
+
--accent-purple: #8250df;
|
|
37
|
+
|
|
38
|
+
/* Dark theme */
|
|
39
|
+
--bg-primary-dark: #0d1117;
|
|
40
|
+
--bg-secondary-dark: #161b22;
|
|
41
|
+
--bg-tertiary-dark: #21262d;
|
|
42
|
+
--border-color-dark: #30363d;
|
|
43
|
+
--text-primary-dark: #e6edf3;
|
|
44
|
+
--text-secondary-dark: #8b949e;
|
|
45
|
+
--text-muted-dark: #6e7681;
|
|
46
|
+
--accent-blue-dark: #58a6ff;
|
|
47
|
+
--accent-green-dark: #3fb950;
|
|
48
|
+
--accent-yellow-dark: #d29922;
|
|
49
|
+
--accent-red-dark: #f85149;
|
|
50
|
+
--accent-purple-dark: #a371f7;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
[data-theme="dark"] {
|
|
54
|
+
--bg-primary: var(--bg-primary-dark);
|
|
55
|
+
--bg-secondary: var(--bg-secondary-dark);
|
|
56
|
+
--bg-tertiary: var(--bg-tertiary-dark);
|
|
57
|
+
--border-color: var(--border-color-dark);
|
|
58
|
+
--text-primary: var(--text-primary-dark);
|
|
59
|
+
--text-secondary: var(--text-secondary-dark);
|
|
60
|
+
--text-muted: var(--text-muted-dark);
|
|
61
|
+
--accent-blue: var(--accent-blue-dark);
|
|
62
|
+
--accent-green: var(--accent-green-dark);
|
|
63
|
+
--accent-yellow: var(--accent-yellow-dark);
|
|
64
|
+
--accent-red: var(--accent-red-dark);
|
|
65
|
+
--accent-purple: var(--accent-purple-dark);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
[data-theme="light"] {
|
|
69
|
+
--bg-primary: var(--bg-primary-light);
|
|
70
|
+
--bg-secondary: var(--bg-secondary-light);
|
|
71
|
+
--bg-tertiary: var(--bg-tertiary-light);
|
|
72
|
+
--border-color: var(--border-color-light);
|
|
73
|
+
--text-primary: var(--text-primary-light);
|
|
74
|
+
--text-secondary: var(--text-secondary-light);
|
|
75
|
+
--text-muted: var(--text-muted-light);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
* {
|
|
79
|
+
box-sizing: border-box;
|
|
80
|
+
margin: 0;
|
|
81
|
+
padding: 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
body {
|
|
85
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
|
86
|
+
background-color: var(--bg-primary);
|
|
87
|
+
color: var(--text-primary);
|
|
88
|
+
line-height: 1.5;
|
|
89
|
+
min-height: 100vh;
|
|
90
|
+
transition: background-color 0.3s ease, color 0.3s ease;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.container {
|
|
94
|
+
max-width: 1200px;
|
|
95
|
+
margin: 0 auto;
|
|
96
|
+
padding: 24px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
header {
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
justify-content: space-between;
|
|
103
|
+
margin-bottom: 32px;
|
|
104
|
+
padding-bottom: 16px;
|
|
105
|
+
border-bottom: 1px solid var(--border-color);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.header-left {
|
|
109
|
+
display: flex;
|
|
110
|
+
flex-direction: column;
|
|
111
|
+
align-items: flex-start;
|
|
112
|
+
gap: 4px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.header-left h1 {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: 12px;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.project-name {
|
|
122
|
+
font-size: 14px;
|
|
123
|
+
color: var(--text-secondary);
|
|
124
|
+
font-weight: 500;
|
|
125
|
+
padding-left: 52px; /* Align with text after logo */
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.project-name:empty {
|
|
129
|
+
display: none;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.header-right {
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
gap: 16px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
h1 {
|
|
139
|
+
font-size: 24px;
|
|
140
|
+
font-weight: 600;
|
|
141
|
+
display: flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
gap: 12px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.logo {
|
|
147
|
+
width: 40px;
|
|
148
|
+
height: 40px;
|
|
149
|
+
display: flex;
|
|
150
|
+
align-items: center;
|
|
151
|
+
justify-content: center;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.logo img {
|
|
155
|
+
width: 40px;
|
|
156
|
+
height: auto;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.version-badge {
|
|
160
|
+
font-size: 12px;
|
|
161
|
+
font-weight: 400;
|
|
162
|
+
color: var(--text-muted);
|
|
163
|
+
margin-left: 4px;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.theme-toggle {
|
|
167
|
+
display: flex;
|
|
168
|
+
align-items: center;
|
|
169
|
+
gap: 8px;
|
|
170
|
+
padding: 6px 12px;
|
|
171
|
+
background-color: var(--bg-tertiary);
|
|
172
|
+
border: 1px solid var(--border-color);
|
|
173
|
+
border-radius: 6px;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
color: var(--text-secondary);
|
|
176
|
+
font-size: 14px;
|
|
177
|
+
transition: all 0.2s ease;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.theme-toggle:hover {
|
|
181
|
+
background-color: var(--bg-secondary);
|
|
182
|
+
color: var(--text-primary);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.theme-toggle svg {
|
|
186
|
+
width: 16px;
|
|
187
|
+
height: 16px;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.sun-icon, .moon-icon {
|
|
191
|
+
display: none;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
[data-theme="dark"] .moon-icon {
|
|
195
|
+
display: block;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
[data-theme="light"] .sun-icon {
|
|
199
|
+
display: block;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.connection-status {
|
|
203
|
+
display: flex;
|
|
204
|
+
align-items: center;
|
|
205
|
+
gap: 8px;
|
|
206
|
+
font-size: 14px;
|
|
207
|
+
color: var(--text-secondary);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.status-dot {
|
|
211
|
+
width: 8px;
|
|
212
|
+
height: 8px;
|
|
213
|
+
border-radius: 50%;
|
|
214
|
+
background-color: var(--accent-red);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.status-dot.connected {
|
|
218
|
+
background-color: var(--accent-green);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.grid {
|
|
222
|
+
display: grid;
|
|
223
|
+
grid-template-columns: repeat(2, 1fr);
|
|
224
|
+
gap: 16px;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* Mobile: 1 card per row */
|
|
228
|
+
@media (max-width: 768px) {
|
|
229
|
+
.grid {
|
|
230
|
+
grid-template-columns: 1fr;
|
|
231
|
+
}
|
|
232
|
+
.card.double-width,
|
|
233
|
+
.card.half-width {
|
|
234
|
+
grid-column: span 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.card {
|
|
239
|
+
background-color: var(--bg-secondary);
|
|
240
|
+
border: 1px solid var(--border-color);
|
|
241
|
+
border-radius: 8px;
|
|
242
|
+
padding: 20px;
|
|
243
|
+
transition: background-color 0.3s ease, border-color 0.3s ease;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.card-header {
|
|
247
|
+
display: flex;
|
|
248
|
+
align-items: center;
|
|
249
|
+
justify-content: space-between;
|
|
250
|
+
margin-bottom: 16px;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.card-title {
|
|
254
|
+
font-size: 14px;
|
|
255
|
+
font-weight: 600;
|
|
256
|
+
color: var(--text-secondary);
|
|
257
|
+
text-transform: uppercase;
|
|
258
|
+
letter-spacing: 0.5px;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.badge {
|
|
262
|
+
display: inline-flex;
|
|
263
|
+
align-items: center;
|
|
264
|
+
padding: 2px 8px;
|
|
265
|
+
font-size: 12px;
|
|
266
|
+
font-weight: 500;
|
|
267
|
+
border-radius: 12px;
|
|
268
|
+
background-color: var(--bg-tertiary);
|
|
269
|
+
color: var(--text-secondary);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.badge.success {
|
|
273
|
+
background-color: rgba(63, 185, 80, 0.15);
|
|
274
|
+
color: var(--accent-green);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.badge.warning {
|
|
278
|
+
background-color: rgba(210, 153, 34, 0.15);
|
|
279
|
+
color: var(--accent-yellow);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.badge.error {
|
|
283
|
+
background-color: rgba(248, 81, 73, 0.15);
|
|
284
|
+
color: var(--accent-red);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/* Warning banner */
|
|
288
|
+
.warning-banner {
|
|
289
|
+
display: none;
|
|
290
|
+
background-color: rgba(210, 153, 34, 0.15);
|
|
291
|
+
border: 1px solid var(--accent-yellow);
|
|
292
|
+
border-radius: 8px;
|
|
293
|
+
padding: 16px;
|
|
294
|
+
margin-bottom: 24px;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.warning-banner.visible {
|
|
298
|
+
display: block;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.warning-banner-header {
|
|
302
|
+
display: flex;
|
|
303
|
+
align-items: center;
|
|
304
|
+
gap: 8px;
|
|
305
|
+
font-weight: 600;
|
|
306
|
+
color: var(--accent-yellow);
|
|
307
|
+
margin-bottom: 8px;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.warning-banner-header svg {
|
|
311
|
+
width: 20px;
|
|
312
|
+
height: 20px;
|
|
313
|
+
flex-shrink: 0;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.warning-banner-content {
|
|
317
|
+
font-size: 14px;
|
|
318
|
+
color: var(--text-secondary);
|
|
319
|
+
line-height: 1.6;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.warning-banner-content strong {
|
|
323
|
+
color: var(--text-primary);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.warning-banner-content code {
|
|
327
|
+
background-color: var(--bg-tertiary);
|
|
328
|
+
padding: 2px 6px;
|
|
329
|
+
border-radius: 4px;
|
|
330
|
+
font-family: monospace;
|
|
331
|
+
font-size: 13px;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.warning-banner-actions {
|
|
335
|
+
margin-top: 12px;
|
|
336
|
+
font-size: 13px;
|
|
337
|
+
color: var(--text-muted);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/* Form styles */
|
|
341
|
+
.settings-form {
|
|
342
|
+
margin-top: 16px;
|
|
343
|
+
padding-top: 16px;
|
|
344
|
+
border-top: 1px solid var(--border-color);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.form-group {
|
|
348
|
+
margin-bottom: 12px;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.form-group label {
|
|
352
|
+
display: block;
|
|
353
|
+
font-size: 12px;
|
|
354
|
+
color: var(--text-muted);
|
|
355
|
+
margin-bottom: 6px;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.form-select,
|
|
359
|
+
.form-input {
|
|
360
|
+
width: 100%;
|
|
361
|
+
padding: 8px 12px;
|
|
362
|
+
font-size: 14px;
|
|
363
|
+
font-family: inherit;
|
|
364
|
+
background-color: var(--bg-tertiary);
|
|
365
|
+
border: 1px solid var(--border-color);
|
|
366
|
+
border-radius: 6px;
|
|
367
|
+
color: var(--text-primary);
|
|
368
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.form-select:focus,
|
|
372
|
+
.form-input:focus {
|
|
373
|
+
outline: none;
|
|
374
|
+
border-color: var(--accent-blue);
|
|
375
|
+
box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.2);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.form-hint {
|
|
379
|
+
font-size: 11px;
|
|
380
|
+
color: var(--text-muted);
|
|
381
|
+
margin-top: 4px;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.form-hint a {
|
|
385
|
+
color: var(--accent-blue);
|
|
386
|
+
text-decoration: none;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.form-hint a:hover {
|
|
390
|
+
text-decoration: underline;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.form-actions {
|
|
394
|
+
display: flex;
|
|
395
|
+
align-items: center;
|
|
396
|
+
gap: 12px;
|
|
397
|
+
margin-top: 16px;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.btn {
|
|
401
|
+
padding: 8px 16px;
|
|
402
|
+
font-size: 14px;
|
|
403
|
+
font-weight: 500;
|
|
404
|
+
border-radius: 6px;
|
|
405
|
+
border: 1px solid transparent;
|
|
406
|
+
cursor: pointer;
|
|
407
|
+
transition: all 0.2s ease;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.btn-primary {
|
|
411
|
+
background-color: var(--accent-blue);
|
|
412
|
+
color: white;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.btn-primary:hover {
|
|
416
|
+
opacity: 0.9;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.btn-primary:disabled {
|
|
420
|
+
opacity: 0.5;
|
|
421
|
+
cursor: not-allowed;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.btn-secondary {
|
|
425
|
+
background-color: var(--bg-tertiary);
|
|
426
|
+
color: var(--text-primary);
|
|
427
|
+
border: 1px solid var(--border-primary);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.btn-secondary:hover {
|
|
431
|
+
background-color: var(--bg-secondary);
|
|
432
|
+
border-color: var(--accent-blue);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.btn-secondary:disabled {
|
|
436
|
+
opacity: 0.5;
|
|
437
|
+
cursor: not-allowed;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.btn-danger {
|
|
441
|
+
color: var(--accent-red);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.btn-danger:hover {
|
|
445
|
+
background-color: rgba(239, 68, 68, 0.1);
|
|
446
|
+
border-color: var(--accent-red);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.reindex-actions {
|
|
450
|
+
display: flex;
|
|
451
|
+
gap: 8px;
|
|
452
|
+
align-items: center;
|
|
453
|
+
margin-top: 12px;
|
|
454
|
+
padding-top: 12px;
|
|
455
|
+
border-top: 1px solid var(--border-primary);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.save-status {
|
|
459
|
+
font-size: 13px;
|
|
460
|
+
color: var(--text-secondary);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.save-status.success {
|
|
464
|
+
color: var(--accent-green);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.save-status.error {
|
|
468
|
+
color: var(--accent-red);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.stat {
|
|
472
|
+
margin-bottom: 12px;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.stat:last-child {
|
|
476
|
+
margin-bottom: 0;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.stat-label {
|
|
480
|
+
font-size: 12px;
|
|
481
|
+
color: var(--text-muted);
|
|
482
|
+
margin-bottom: 4px;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.stat-value {
|
|
486
|
+
font-size: 24px;
|
|
487
|
+
font-weight: 600;
|
|
488
|
+
color: var(--text-primary);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.stat-value.small {
|
|
492
|
+
font-size: 14px;
|
|
493
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
494
|
+
color: var(--text-secondary);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.progress-container {
|
|
498
|
+
margin-top: 16px;
|
|
499
|
+
display: none;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.progress-container.active {
|
|
503
|
+
display: block;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.progress-bar {
|
|
507
|
+
height: 8px;
|
|
508
|
+
background-color: var(--bg-tertiary);
|
|
509
|
+
border-radius: 4px;
|
|
510
|
+
overflow: hidden;
|
|
511
|
+
margin-bottom: 8px;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.progress-fill {
|
|
515
|
+
height: 100%;
|
|
516
|
+
background: linear-gradient(90deg, var(--accent-blue), var(--accent-purple));
|
|
517
|
+
border-radius: 4px;
|
|
518
|
+
transition: width 0.3s ease;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.progress-text {
|
|
522
|
+
font-size: 12px;
|
|
523
|
+
color: var(--text-secondary);
|
|
524
|
+
white-space: pre-line;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.patterns-list {
|
|
528
|
+
display: flex;
|
|
529
|
+
flex-wrap: wrap;
|
|
530
|
+
gap: 6px;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.pattern-tag {
|
|
534
|
+
display: inline-block;
|
|
535
|
+
padding: 2px 8px;
|
|
536
|
+
font-size: 12px;
|
|
537
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
538
|
+
background-color: var(--bg-tertiary);
|
|
539
|
+
border-radius: 4px;
|
|
540
|
+
color: var(--text-secondary);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.pattern-tag.exclude {
|
|
544
|
+
color: var(--accent-red);
|
|
545
|
+
text-decoration: line-through;
|
|
546
|
+
opacity: 0.7;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.card.full-width {
|
|
550
|
+
grid-column: 1 / -1;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.card.double-width {
|
|
554
|
+
grid-column: span 2;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/* Half-width: takes 1 column (50% on 2-col grid) */
|
|
558
|
+
.card.half-width {
|
|
559
|
+
grid-column: span 1;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/* Compact card style */
|
|
563
|
+
.card.compact {
|
|
564
|
+
padding: 16px;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.card.compact .stat {
|
|
568
|
+
margin-bottom: 8px;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.card.compact .stat-value {
|
|
572
|
+
font-size: 20px;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.card.compact .reindex-actions {
|
|
576
|
+
margin-top: 8px;
|
|
577
|
+
padding-top: 8px;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.empty-state {
|
|
581
|
+
text-align: center;
|
|
582
|
+
padding: 40px 20px;
|
|
583
|
+
color: var(--text-secondary);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.empty-state-icon {
|
|
587
|
+
font-size: 48px;
|
|
588
|
+
margin-bottom: 16px;
|
|
589
|
+
opacity: 0.5;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
@keyframes pulse {
|
|
593
|
+
0%, 100% { opacity: 1; }
|
|
594
|
+
50% { opacity: 0.5; }
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.pulsing {
|
|
598
|
+
animation: pulse 2s ease-in-out infinite;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/* Charts.css Customization - use aspect-ratio per docs */
|
|
602
|
+
#chartWrapper {
|
|
603
|
+
width: 100%;
|
|
604
|
+
max-width: 100%;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
#chartWrapper .column {
|
|
608
|
+
--aspect-ratio: 16 / 4;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
#usage-chart td {
|
|
612
|
+
border-top-left-radius: 6px;
|
|
613
|
+
border-top-right-radius: 6px;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/* Charts.css legend overrides */
|
|
617
|
+
#usageChartContainer .legend {
|
|
618
|
+
margin-top: 16px;
|
|
619
|
+
padding-top: 12px;
|
|
620
|
+
justify-content: center;
|
|
621
|
+
border-radius: 4px;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
#usageChartContainer .legend li {
|
|
625
|
+
font-size: 12px;
|
|
626
|
+
color: var(--text-secondary);
|
|
627
|
+
cursor: pointer;
|
|
628
|
+
transition: opacity 0.15s ease;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/* Apply --color variable to legend squares - override charts.css defaults */
|
|
632
|
+
#chartLegend.legend.legend-square li::before {
|
|
633
|
+
background: var(--color) !important;
|
|
634
|
+
border-color: var(--color) !important;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
#usageChartContainer .legend li:hover {
|
|
638
|
+
opacity: 1;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
#usage-chart tr {
|
|
642
|
+
transition: opacity 0.15s ease;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
#usage-chart.legend-hover tr {
|
|
646
|
+
opacity: 0.3;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
#usage-chart.legend-hover tr.highlight {
|
|
650
|
+
opacity: 1;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/* Bar hover highlighting for legend */
|
|
654
|
+
#chartLegend.bar-hover li {
|
|
655
|
+
opacity: 0.3;
|
|
656
|
+
transition: opacity 0.15s ease;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
#chartLegend.bar-hover li.highlight {
|
|
660
|
+
opacity: 1;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.usage-total {
|
|
664
|
+
display: flex;
|
|
665
|
+
justify-content: space-between;
|
|
666
|
+
align-items: center;
|
|
667
|
+
padding-top: 12px;
|
|
668
|
+
border-top: 1px solid var(--border-color);
|
|
669
|
+
margin-top: 16px;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.usage-total-label {
|
|
673
|
+
font-size: 12px;
|
|
674
|
+
color: var(--text-muted);
|
|
675
|
+
text-transform: uppercase;
|
|
676
|
+
letter-spacing: 0.5px;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.usage-total-count {
|
|
680
|
+
font-size: 18px;
|
|
681
|
+
font-weight: 600;
|
|
682
|
+
color: var(--text-primary);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
.usage-empty {
|
|
686
|
+
text-align: center;
|
|
687
|
+
padding: 20px;
|
|
688
|
+
color: var(--text-muted);
|
|
689
|
+
font-size: 14px;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/* Server Log Panel Styles */
|
|
693
|
+
.log-section {
|
|
694
|
+
margin-top: 32px;
|
|
695
|
+
padding-top: 24px;
|
|
696
|
+
border-top: 1px solid var(--border-color);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
.log-header {
|
|
700
|
+
display: flex;
|
|
701
|
+
align-items: center;
|
|
702
|
+
gap: 8px;
|
|
703
|
+
margin-bottom: 16px;
|
|
704
|
+
cursor: pointer;
|
|
705
|
+
user-select: none;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
.log-header:hover .log-title {
|
|
709
|
+
color: var(--accent-blue);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
.log-toggle {
|
|
713
|
+
transition: transform 0.2s;
|
|
714
|
+
transform: rotate(90deg);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.log-toggle.collapsed {
|
|
718
|
+
transform: rotate(0deg);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
.log-title {
|
|
722
|
+
font-size: 16px;
|
|
723
|
+
font-weight: 600;
|
|
724
|
+
color: var(--text-primary);
|
|
725
|
+
transition: color 0.2s;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
.log-container {
|
|
729
|
+
background: var(--bg-tertiary);
|
|
730
|
+
border: 1px solid var(--border-primary);
|
|
731
|
+
border-radius: 8px;
|
|
732
|
+
max-height: 300px;
|
|
733
|
+
overflow-y: auto;
|
|
734
|
+
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
|
735
|
+
font-size: 12px;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.log-container.collapsed {
|
|
739
|
+
display: none;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
.log-entry {
|
|
743
|
+
padding: 4px 12px;
|
|
744
|
+
border-bottom: 1px solid var(--border-primary);
|
|
745
|
+
display: flex;
|
|
746
|
+
gap: 8px;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
.log-entry:last-child {
|
|
750
|
+
border-bottom: none;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
.log-time {
|
|
754
|
+
color: var(--text-muted);
|
|
755
|
+
flex-shrink: 0;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
.log-message {
|
|
759
|
+
color: var(--text-primary);
|
|
760
|
+
word-break: break-word;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
.log-entry.log-error .log-message {
|
|
764
|
+
color: var(--accent-red);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
.log-entry.log-warn .log-message {
|
|
768
|
+
color: var(--accent-yellow);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.log-empty {
|
|
772
|
+
padding: 20px;
|
|
773
|
+
text-align: center;
|
|
774
|
+
color: var(--text-muted);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
.log-actions {
|
|
778
|
+
display: flex;
|
|
779
|
+
gap: 8px;
|
|
780
|
+
margin-left: auto;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.log-actions button {
|
|
784
|
+
padding: 2px 8px;
|
|
785
|
+
font-size: 11px;
|
|
786
|
+
background: var(--bg-secondary);
|
|
787
|
+
border: 1px solid var(--border-primary);
|
|
788
|
+
border-radius: 4px;
|
|
789
|
+
color: var(--text-secondary);
|
|
790
|
+
cursor: pointer;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
.log-actions button:hover {
|
|
794
|
+
background: var(--bg-tertiary);
|
|
795
|
+
color: var(--text-primary);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/* Beads Section Styles */
|
|
799
|
+
.beads-section {
|
|
800
|
+
/* Now a standalone tab, no border needed */
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.beads-header {
|
|
804
|
+
display: flex;
|
|
805
|
+
align-items: center;
|
|
806
|
+
justify-content: space-between;
|
|
807
|
+
gap: 12px;
|
|
808
|
+
margin-bottom: 16px;
|
|
809
|
+
flex-wrap: wrap;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.beads-header-left {
|
|
813
|
+
display: flex;
|
|
814
|
+
align-items: center;
|
|
815
|
+
gap: 12px;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
.beads-header-right {
|
|
819
|
+
display: flex;
|
|
820
|
+
align-items: center;
|
|
821
|
+
gap: 24px;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
.beads-logo {
|
|
825
|
+
width: 24px;
|
|
826
|
+
height: 24px;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
.beads-title {
|
|
830
|
+
font-size: 18px;
|
|
831
|
+
font-weight: 600;
|
|
832
|
+
color: var(--text-primary);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
.beads-unavailable {
|
|
836
|
+
text-align: center;
|
|
837
|
+
padding: 40px 20px;
|
|
838
|
+
color: var(--text-muted);
|
|
839
|
+
font-size: 14px;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
.beads-stats {
|
|
843
|
+
display: flex;
|
|
844
|
+
gap: 24px;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.beads-stat {
|
|
848
|
+
display: flex;
|
|
849
|
+
flex-direction: column;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.beads-stat-value {
|
|
853
|
+
font-size: 24px;
|
|
854
|
+
font-weight: 600;
|
|
855
|
+
color: var(--text-primary);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
.beads-stat-label {
|
|
859
|
+
font-size: 12px;
|
|
860
|
+
color: var(--text-muted);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
.beads-issues {
|
|
864
|
+
display: flex;
|
|
865
|
+
flex-direction: column;
|
|
866
|
+
gap: 8px;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
.beads-issue {
|
|
870
|
+
display: flex;
|
|
871
|
+
align-items: flex-start;
|
|
872
|
+
gap: 12px;
|
|
873
|
+
padding: 12px;
|
|
874
|
+
background-color: var(--bg-tertiary);
|
|
875
|
+
border-radius: 6px;
|
|
876
|
+
border: 1px solid var(--border-color);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.beads-issue-id {
|
|
880
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
881
|
+
font-size: 12px;
|
|
882
|
+
color: var(--accent-blue);
|
|
883
|
+
white-space: nowrap;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
.beads-issue-content {
|
|
887
|
+
flex: 1;
|
|
888
|
+
min-width: 0;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
.beads-issue-title {
|
|
892
|
+
font-size: 14px;
|
|
893
|
+
font-weight: 500;
|
|
894
|
+
color: var(--text-primary);
|
|
895
|
+
margin-bottom: 4px;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
.beads-issue-meta {
|
|
899
|
+
display: flex;
|
|
900
|
+
gap: 12px;
|
|
901
|
+
font-size: 12px;
|
|
902
|
+
color: var(--text-muted);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
.beads-issue-type {
|
|
906
|
+
display: inline-flex;
|
|
907
|
+
padding: 2px 6px;
|
|
908
|
+
background-color: var(--bg-secondary);
|
|
909
|
+
border-radius: 4px;
|
|
910
|
+
font-size: 11px;
|
|
911
|
+
text-transform: uppercase;
|
|
912
|
+
letter-spacing: 0.5px;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
.beads-issue-priority {
|
|
916
|
+
display: inline-flex;
|
|
917
|
+
align-items: center;
|
|
918
|
+
gap: 4px;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
.priority-dot {
|
|
922
|
+
width: 6px;
|
|
923
|
+
height: 6px;
|
|
924
|
+
border-radius: 50%;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.priority-1 { background-color: var(--accent-red); }
|
|
928
|
+
.priority-2 { background-color: var(--accent-yellow); }
|
|
929
|
+
.priority-3 { background-color: var(--accent-green); }
|
|
930
|
+
|
|
931
|
+
.beads-daemon-status {
|
|
932
|
+
display: flex;
|
|
933
|
+
align-items: center;
|
|
934
|
+
gap: 8px;
|
|
935
|
+
font-size: 12px;
|
|
936
|
+
color: var(--text-secondary);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
.beads-empty {
|
|
940
|
+
text-align: center;
|
|
941
|
+
padding: 20px;
|
|
942
|
+
color: var(--text-muted);
|
|
943
|
+
font-size: 14px;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
.beads-issue {
|
|
947
|
+
cursor: pointer;
|
|
948
|
+
transition: background-color 0.2s ease;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
.beads-issue:hover {
|
|
952
|
+
background-color: var(--bg-secondary);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
.beads-issue-title {
|
|
956
|
+
display: flex;
|
|
957
|
+
align-items: center;
|
|
958
|
+
gap: 8px;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
.beads-issue-expand {
|
|
962
|
+
width: 16px;
|
|
963
|
+
height: 16px;
|
|
964
|
+
color: var(--text-muted);
|
|
965
|
+
transition: transform 0.2s ease;
|
|
966
|
+
flex-shrink: 0;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
.beads-issue.expanded .beads-issue-expand {
|
|
970
|
+
transform: rotate(90deg);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
.beads-issue-description {
|
|
974
|
+
display: none;
|
|
975
|
+
margin-top: 8px;
|
|
976
|
+
padding: 12px;
|
|
977
|
+
background-color: var(--bg-secondary);
|
|
978
|
+
border-radius: 4px;
|
|
979
|
+
font-size: 13px;
|
|
980
|
+
color: var(--text-secondary);
|
|
981
|
+
white-space: pre-wrap;
|
|
982
|
+
word-break: break-word;
|
|
983
|
+
border-left: 3px solid var(--accent-blue);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
.beads-issue.expanded .beads-issue-description {
|
|
987
|
+
display: block;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
.beads-issue-no-description {
|
|
991
|
+
font-style: italic;
|
|
992
|
+
color: var(--text-muted);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/* Tab navigation */
|
|
996
|
+
.tab-container {
|
|
997
|
+
margin-bottom: 24px;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
.tab-nav {
|
|
1001
|
+
display: flex;
|
|
1002
|
+
gap: 4px;
|
|
1003
|
+
border-bottom: 1px solid var(--border-color);
|
|
1004
|
+
padding-bottom: 0;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
.tab-btn {
|
|
1008
|
+
padding: 10px 20px;
|
|
1009
|
+
font-size: 14px;
|
|
1010
|
+
font-weight: 500;
|
|
1011
|
+
color: var(--text-secondary);
|
|
1012
|
+
background: transparent;
|
|
1013
|
+
border: none;
|
|
1014
|
+
border-bottom: 2px solid transparent;
|
|
1015
|
+
cursor: pointer;
|
|
1016
|
+
transition: all 0.2s ease;
|
|
1017
|
+
margin-bottom: -1px;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
.tab-btn:hover {
|
|
1021
|
+
color: var(--text-primary);
|
|
1022
|
+
background-color: var(--bg-tertiary);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
.tab-btn.active {
|
|
1026
|
+
color: var(--accent-blue);
|
|
1027
|
+
border-bottom-color: var(--accent-blue);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
.tab-content {
|
|
1031
|
+
display: none;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
.tab-content.active {
|
|
1035
|
+
display: block;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/* Slider styles */
|
|
1039
|
+
.form-slider {
|
|
1040
|
+
width: 100%;
|
|
1041
|
+
height: 8px;
|
|
1042
|
+
border-radius: 4px;
|
|
1043
|
+
background: var(--bg-tertiary);
|
|
1044
|
+
outline: none;
|
|
1045
|
+
-webkit-appearance: none;
|
|
1046
|
+
appearance: none;
|
|
1047
|
+
cursor: pointer;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
.form-slider::-webkit-slider-thumb {
|
|
1051
|
+
-webkit-appearance: none;
|
|
1052
|
+
appearance: none;
|
|
1053
|
+
width: 20px;
|
|
1054
|
+
height: 20px;
|
|
1055
|
+
border-radius: 50%;
|
|
1056
|
+
background: var(--accent-blue);
|
|
1057
|
+
cursor: pointer;
|
|
1058
|
+
border: 2px solid var(--bg-primary);
|
|
1059
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
.form-slider::-moz-range-thumb {
|
|
1063
|
+
width: 20px;
|
|
1064
|
+
height: 20px;
|
|
1065
|
+
border-radius: 50%;
|
|
1066
|
+
background: var(--accent-blue);
|
|
1067
|
+
cursor: pointer;
|
|
1068
|
+
border: 2px solid var(--bg-primary);
|
|
1069
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
.slider-labels {
|
|
1073
|
+
display: flex;
|
|
1074
|
+
justify-content: space-between;
|
|
1075
|
+
margin-top: 8px;
|
|
1076
|
+
font-size: 12px;
|
|
1077
|
+
color: var(--text-muted);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
.slider-value {
|
|
1081
|
+
text-align: center;
|
|
1082
|
+
font-size: 14px;
|
|
1083
|
+
font-weight: 500;
|
|
1084
|
+
color: var(--text-primary);
|
|
1085
|
+
margin-top: 8px;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/* Chip styles */
|
|
1089
|
+
.chips-container {
|
|
1090
|
+
display: flex;
|
|
1091
|
+
flex-wrap: wrap;
|
|
1092
|
+
gap: 8px;
|
|
1093
|
+
margin-bottom: 12px;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
.chip {
|
|
1097
|
+
display: inline-flex;
|
|
1098
|
+
align-items: center;
|
|
1099
|
+
gap: 6px;
|
|
1100
|
+
padding: 6px 10px;
|
|
1101
|
+
font-size: 13px;
|
|
1102
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
1103
|
+
background-color: var(--bg-tertiary);
|
|
1104
|
+
border: 1px solid var(--border-color);
|
|
1105
|
+
border-radius: 16px;
|
|
1106
|
+
color: var(--text-secondary);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
.chip.exclude {
|
|
1110
|
+
color: var(--accent-red);
|
|
1111
|
+
border-color: rgba(248, 81, 73, 0.3);
|
|
1112
|
+
background-color: rgba(248, 81, 73, 0.1);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
.chip-remove {
|
|
1116
|
+
display: flex;
|
|
1117
|
+
align-items: center;
|
|
1118
|
+
justify-content: center;
|
|
1119
|
+
width: 16px;
|
|
1120
|
+
height: 16px;
|
|
1121
|
+
border-radius: 50%;
|
|
1122
|
+
background: transparent;
|
|
1123
|
+
border: none;
|
|
1124
|
+
color: var(--text-muted);
|
|
1125
|
+
cursor: pointer;
|
|
1126
|
+
padding: 0;
|
|
1127
|
+
font-size: 14px;
|
|
1128
|
+
line-height: 1;
|
|
1129
|
+
transition: all 0.2s ease;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
.chip-remove:hover {
|
|
1133
|
+
background-color: var(--accent-red);
|
|
1134
|
+
color: white;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
.chip-input-group {
|
|
1138
|
+
display: flex;
|
|
1139
|
+
gap: 8px;
|
|
1140
|
+
margin-top: 8px;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
.chip-input-group .form-input {
|
|
1144
|
+
flex: 1;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
.chip-input-group .btn {
|
|
1148
|
+
flex-shrink: 0;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
.chips-empty {
|
|
1152
|
+
color: var(--text-muted);
|
|
1153
|
+
font-size: 13px;
|
|
1154
|
+
font-style: italic;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/* Patterns section layout */
|
|
1158
|
+
.patterns-section {
|
|
1159
|
+
display: flex;
|
|
1160
|
+
flex-direction: column;
|
|
1161
|
+
gap: 24px;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
.patterns-group {
|
|
1165
|
+
display: flex;
|
|
1166
|
+
flex-direction: column;
|
|
1167
|
+
gap: 8px;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
.patterns-group-header {
|
|
1171
|
+
display: flex;
|
|
1172
|
+
align-items: center;
|
|
1173
|
+
gap: 8px;
|
|
1174
|
+
margin-bottom: 4px;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
.patterns-group-title {
|
|
1178
|
+
font-size: 14px;
|
|
1179
|
+
font-weight: 600;
|
|
1180
|
+
color: var(--text-primary);
|
|
1181
|
+
}
|
|
1182
|
+
</style>
|
|
1183
|
+
</head>
|
|
1184
|
+
<body>
|
|
1185
|
+
<div class="container">
|
|
1186
|
+
<header>
|
|
1187
|
+
<div class="header-left">
|
|
1188
|
+
<h1>
|
|
1189
|
+
<div class="logo"><img src="${LOGO_PNG}" alt="glancey logo" width="40" height="36"></div>
|
|
1190
|
+
glancey
|
|
1191
|
+
<span class="version-badge" id="versionBadge"></span>
|
|
1192
|
+
</h1>
|
|
1193
|
+
<div class="project-name" id="projectName"></div>
|
|
1194
|
+
</div>
|
|
1195
|
+
<div class="header-right">
|
|
1196
|
+
<button class="theme-toggle" id="themeToggle" title="Toggle theme">
|
|
1197
|
+
<svg class="sun-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1198
|
+
<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" />
|
|
1199
|
+
</svg>
|
|
1200
|
+
<svg class="moon-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1201
|
+
<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" />
|
|
1202
|
+
</svg>
|
|
1203
|
+
<span class="theme-label">Theme</span>
|
|
1204
|
+
</button>
|
|
1205
|
+
<div class="connection-status">
|
|
1206
|
+
<div class="status-dot" id="connectionDot"></div>
|
|
1207
|
+
<span id="connectionText">Connecting...</span>
|
|
1208
|
+
</div>
|
|
1209
|
+
</div>
|
|
1210
|
+
</header>
|
|
1211
|
+
|
|
1212
|
+
<!-- Tab Navigation -->
|
|
1213
|
+
<div class="tab-container">
|
|
1214
|
+
<nav class="tab-nav">
|
|
1215
|
+
<button class="tab-btn active" data-tab="status">Status</button>
|
|
1216
|
+
<button class="tab-btn" data-tab="beads">Beads</button>
|
|
1217
|
+
<button class="tab-btn" data-tab="settings">Settings</button>
|
|
1218
|
+
</nav>
|
|
1219
|
+
</div>
|
|
1220
|
+
|
|
1221
|
+
<!-- Status Tab -->
|
|
1222
|
+
<div id="tab-status" class="tab-content active">
|
|
1223
|
+
|
|
1224
|
+
<!-- Backend Fallback Warning Banner -->
|
|
1225
|
+
<div class="warning-banner" id="fallbackBanner">
|
|
1226
|
+
<div class="warning-banner-header">
|
|
1227
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1228
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
1229
|
+
</svg>
|
|
1230
|
+
<span>Using Ollama (fallback)</span>
|
|
1231
|
+
</div>
|
|
1232
|
+
<div class="warning-banner-content" id="fallbackContent">
|
|
1233
|
+
<!-- Content populated by JavaScript -->
|
|
1234
|
+
</div>
|
|
1235
|
+
<div class="warning-banner-actions">
|
|
1236
|
+
<strong>To resolve:</strong> Wait for rate limits to reset, check your API key, or update <code>.glancey.json</code> to use <code>"backend": "ollama"</code> permanently.
|
|
1237
|
+
</div>
|
|
1238
|
+
</div>
|
|
1239
|
+
|
|
1240
|
+
<div class="grid">
|
|
1241
|
+
<!-- Command Usage Card -->
|
|
1242
|
+
<div class="card full-width">
|
|
1243
|
+
<div class="card-header">
|
|
1244
|
+
<span class="card-title">Command Usage</span>
|
|
1245
|
+
<span class="badge" id="sessionBadge">This Session</span>
|
|
1246
|
+
</div>
|
|
1247
|
+
<div id="usageChartContainer">
|
|
1248
|
+
<div class="usage-empty" id="usageEmpty">No commands executed yet</div>
|
|
1249
|
+
<div id="chartWrapper">
|
|
1250
|
+
<table class="charts-css column show-primary-axis data-spacing-5" id="usage-chart" style="display: none;">
|
|
1251
|
+
<tbody id="usageChartBody"></tbody>
|
|
1252
|
+
</table>
|
|
1253
|
+
</div>
|
|
1254
|
+
<ul class="charts-css legend legend-inline legend-square" id="chartLegend" style="display: none;"></ul>
|
|
1255
|
+
<div class="usage-total" id="usageTotal" style="display: none;">
|
|
1256
|
+
<span class="usage-total-label">Total Commands</span>
|
|
1257
|
+
<span class="usage-total-count" id="totalCount">0</span>
|
|
1258
|
+
</div>
|
|
1259
|
+
</div>
|
|
1260
|
+
</div>
|
|
1261
|
+
|
|
1262
|
+
<!-- Index Status Card -->
|
|
1263
|
+
<div class="card half-width compact">
|
|
1264
|
+
<div class="card-header">
|
|
1265
|
+
<span class="card-title">Index Status</span>
|
|
1266
|
+
<span class="badge" id="indexBadge">Loading...</span>
|
|
1267
|
+
</div>
|
|
1268
|
+
<div class="stat">
|
|
1269
|
+
<div class="stat-label">Files Indexed</div>
|
|
1270
|
+
<div class="stat-value" id="fileCount">-</div>
|
|
1271
|
+
</div>
|
|
1272
|
+
<div class="stat">
|
|
1273
|
+
<div class="stat-label">Total Chunks</div>
|
|
1274
|
+
<div class="stat-value" id="chunkCount">-</div>
|
|
1275
|
+
</div>
|
|
1276
|
+
<div class="stat">
|
|
1277
|
+
<div class="stat-label">Last Updated</div>
|
|
1278
|
+
<div class="stat-value small" id="lastUpdated">-</div>
|
|
1279
|
+
</div>
|
|
1280
|
+
<div class="progress-container" id="progressContainer">
|
|
1281
|
+
<div class="progress-bar">
|
|
1282
|
+
<div class="progress-fill" id="progressFill" style="width: 0%"></div>
|
|
1283
|
+
</div>
|
|
1284
|
+
<div class="progress-text" id="progressText">Initializing...</div>
|
|
1285
|
+
</div>
|
|
1286
|
+
<div class="reindex-actions">
|
|
1287
|
+
<button type="button" id="reindexBtn" class="btn btn-secondary">Reindex</button>
|
|
1288
|
+
<button type="button" id="forceReindexBtn" class="btn btn-secondary btn-danger">Force Reindex</button>
|
|
1289
|
+
<span id="reindexStatus" class="save-status"></span>
|
|
1290
|
+
</div>
|
|
1291
|
+
</div>
|
|
1292
|
+
|
|
1293
|
+
<!-- Token Savings Card -->
|
|
1294
|
+
<div class="card half-width compact">
|
|
1295
|
+
<div class="card-header">
|
|
1296
|
+
<span class="card-title">Token Savings</span>
|
|
1297
|
+
<span class="badge" id="savingsBadge">This Session</span>
|
|
1298
|
+
</div>
|
|
1299
|
+
<div class="stat">
|
|
1300
|
+
<div class="stat-label">Tokens Saved</div>
|
|
1301
|
+
<div class="stat-value" id="tokensSaved">0</div>
|
|
1302
|
+
</div>
|
|
1303
|
+
<div class="stat">
|
|
1304
|
+
<div class="stat-label">Context Reduction</div>
|
|
1305
|
+
<div class="stat-value" id="savingsEfficiency">0%</div>
|
|
1306
|
+
</div>
|
|
1307
|
+
<div class="stat">
|
|
1308
|
+
<div class="stat-label">File Reads Avoided</div>
|
|
1309
|
+
<div class="stat-value small" id="filesAvoided">0</div>
|
|
1310
|
+
</div>
|
|
1311
|
+
<div class="stat">
|
|
1312
|
+
<div class="stat-label">Tool Calls</div>
|
|
1313
|
+
<div class="stat-value small" id="operationCount">0</div>
|
|
1314
|
+
</div>
|
|
1315
|
+
</div>
|
|
1316
|
+
</div>
|
|
1317
|
+
|
|
1318
|
+
<!-- Server Log Section -->
|
|
1319
|
+
<div class="log-section" id="logSection">
|
|
1320
|
+
<div class="log-header" id="logHeader">
|
|
1321
|
+
<svg class="log-toggle" id="logToggle" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
1322
|
+
<path d="M6 4l4 4-4 4"/>
|
|
1323
|
+
</svg>
|
|
1324
|
+
<span class="log-title">Server Logs</span>
|
|
1325
|
+
<span class="badge" id="logCount">0</span>
|
|
1326
|
+
<div class="log-actions">
|
|
1327
|
+
<button type="button" id="clearLogsBtn">Clear</button>
|
|
1328
|
+
</div>
|
|
1329
|
+
</div>
|
|
1330
|
+
<div class="log-container" id="logContainer">
|
|
1331
|
+
<div class="log-empty" id="logEmpty">No logs yet. Logs will appear when indexing or other server operations occur.</div>
|
|
1332
|
+
</div>
|
|
1333
|
+
</div>
|
|
1334
|
+
|
|
1335
|
+
</div><!-- End Status Tab -->
|
|
1336
|
+
|
|
1337
|
+
<!-- Beads Tab -->
|
|
1338
|
+
<div id="tab-beads" class="tab-content">
|
|
1339
|
+
<div class="beads-section" id="beadsSection">
|
|
1340
|
+
<div class="beads-header">
|
|
1341
|
+
<div class="beads-header-left">
|
|
1342
|
+
<svg class="beads-logo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1343
|
+
<circle cx="12" cy="5" r="3"/>
|
|
1344
|
+
<circle cx="12" cy="12" r="3"/>
|
|
1345
|
+
<circle cx="12" cy="19" r="3"/>
|
|
1346
|
+
<line x1="12" y1="8" x2="12" y2="9"/>
|
|
1347
|
+
<line x1="12" y1="15" x2="12" y2="16"/>
|
|
1348
|
+
</svg>
|
|
1349
|
+
<span class="beads-title">Beads Issue Tracker</span>
|
|
1350
|
+
<span class="badge success" id="beadsBadge" style="display: none;">Active</span>
|
|
1351
|
+
</div>
|
|
1352
|
+
<div class="beads-header-right" id="beadsHeaderStats" style="display: none;">
|
|
1353
|
+
<div class="beads-daemon-status" id="beadsDaemonStatus">
|
|
1354
|
+
<div class="status-dot" id="beadsDaemonDot"></div>
|
|
1355
|
+
<span id="beadsDaemonText">Daemon status unknown</span>
|
|
1356
|
+
</div>
|
|
1357
|
+
<div class="beads-stats">
|
|
1358
|
+
<div class="beads-stat">
|
|
1359
|
+
<span class="beads-stat-value" id="beadsReadyCount">0</span>
|
|
1360
|
+
<span class="beads-stat-label">Ready</span>
|
|
1361
|
+
</div>
|
|
1362
|
+
<div class="beads-stat">
|
|
1363
|
+
<span class="beads-stat-value" id="beadsOpenCount">0</span>
|
|
1364
|
+
<span class="beads-stat-label">Open</span>
|
|
1365
|
+
</div>
|
|
1366
|
+
<div class="beads-stat">
|
|
1367
|
+
<span class="beads-stat-value" id="beadsTotalCount">0</span>
|
|
1368
|
+
<span class="beads-stat-label">Total</span>
|
|
1369
|
+
</div>
|
|
1370
|
+
</div>
|
|
1371
|
+
</div>
|
|
1372
|
+
</div>
|
|
1373
|
+
<div id="beadsUnavailable" class="beads-unavailable">
|
|
1374
|
+
<p>Beads is not configured for this project.</p>
|
|
1375
|
+
<p style="margin-top: 8px; font-size: 13px;">Visit <a href="https://github.com/steveyegge/beads" target="_blank" style="color: var(--accent-blue);">github.com/steveyegge/beads</a> to learn more.</p>
|
|
1376
|
+
</div>
|
|
1377
|
+
<div id="beadsContent" style="display: none;">
|
|
1378
|
+
<div class="card">
|
|
1379
|
+
<div class="card-header">
|
|
1380
|
+
<span class="card-title">Ready Tasks</span>
|
|
1381
|
+
<span class="badge" id="readyTasksBadge">0 tasks</span>
|
|
1382
|
+
</div>
|
|
1383
|
+
<div class="beads-issues" id="beadsIssuesList">
|
|
1384
|
+
<div class="beads-empty">No ready tasks</div>
|
|
1385
|
+
</div>
|
|
1386
|
+
</div>
|
|
1387
|
+
</div>
|
|
1388
|
+
</div>
|
|
1389
|
+
</div><!-- End Beads Tab -->
|
|
1390
|
+
|
|
1391
|
+
<!-- Settings Tab -->
|
|
1392
|
+
<div id="tab-settings" class="tab-content">
|
|
1393
|
+
<div class="grid">
|
|
1394
|
+
<!-- Embedding Backend Card -->
|
|
1395
|
+
<div class="card">
|
|
1396
|
+
<div class="card-header">
|
|
1397
|
+
<span class="card-title">Embedding Backend</span>
|
|
1398
|
+
<span class="badge" id="embeddingStatus">-</span>
|
|
1399
|
+
</div>
|
|
1400
|
+
<div class="stat">
|
|
1401
|
+
<div class="stat-label">Current Backend</div>
|
|
1402
|
+
<div class="stat-value small" id="embeddingBackend">-</div>
|
|
1403
|
+
</div>
|
|
1404
|
+
<div class="stat">
|
|
1405
|
+
<div class="stat-label">Index Path</div>
|
|
1406
|
+
<div class="stat-value small" id="indexPath">-</div>
|
|
1407
|
+
</div>
|
|
1408
|
+
<div class="settings-form" id="embeddingSettingsForm">
|
|
1409
|
+
<div class="form-group">
|
|
1410
|
+
<label for="backendSelect">Select Backend</label>
|
|
1411
|
+
<select id="backendSelect" class="form-select">
|
|
1412
|
+
<option value="ollama">Ollama (local)</option>
|
|
1413
|
+
<option value="gemini" selected>Google Gemini (free - requires API key)</option>
|
|
1414
|
+
</select>
|
|
1415
|
+
</div>
|
|
1416
|
+
<div class="form-group" id="ollamaSettingsGroup">
|
|
1417
|
+
<label for="concurrencySelect">Ollama Concurrency</label>
|
|
1418
|
+
<select id="concurrencySelect" class="form-select">
|
|
1419
|
+
<option value="1" selected>1 (default)</option>
|
|
1420
|
+
<option value="2">2</option>
|
|
1421
|
+
<option value="3">3</option>
|
|
1422
|
+
<option value="5">5</option>
|
|
1423
|
+
<option value="10">10</option>
|
|
1424
|
+
<option value="25">25</option>
|
|
1425
|
+
<option value="50">50</option>
|
|
1426
|
+
<option value="100">100</option>
|
|
1427
|
+
<option value="250">250</option>
|
|
1428
|
+
<option value="500">500</option>
|
|
1429
|
+
<option value="1000">1000</option>
|
|
1430
|
+
</select>
|
|
1431
|
+
</div>
|
|
1432
|
+
<div class="form-group" id="batchSizeGroup">
|
|
1433
|
+
<label for="batchSizeSelect">Batch Size</label>
|
|
1434
|
+
<select id="batchSizeSelect" class="form-select">
|
|
1435
|
+
<option value="32">32</option>
|
|
1436
|
+
<option value="64">64</option>
|
|
1437
|
+
<option value="128">128</option>
|
|
1438
|
+
<option value="256" selected>256 (default)</option>
|
|
1439
|
+
<option value="512">512</option>
|
|
1440
|
+
<option value="1024">1024</option>
|
|
1441
|
+
<option value="2048">2048</option>
|
|
1442
|
+
<option value="4096">4096</option>
|
|
1443
|
+
<option value="8192">8192</option>
|
|
1444
|
+
<option value="16384">16384</option>
|
|
1445
|
+
</select>
|
|
1446
|
+
</div>
|
|
1447
|
+
<div class="form-group" id="apiKeyGroup" style="display: none;">
|
|
1448
|
+
<label for="apiKeyInput" id="apiKeyLabel">API Key</label>
|
|
1449
|
+
<input type="password" id="apiKeyInput" class="form-input" placeholder="" />
|
|
1450
|
+
<div class="form-hint" id="apiKeyHint"></div>
|
|
1451
|
+
</div>
|
|
1452
|
+
<div class="form-actions">
|
|
1453
|
+
<button type="button" id="saveEmbeddingBtn" class="btn btn-primary">Save Settings</button>
|
|
1454
|
+
<span id="saveStatus" class="save-status"></span>
|
|
1455
|
+
</div>
|
|
1456
|
+
</div>
|
|
1457
|
+
</div>
|
|
1458
|
+
|
|
1459
|
+
<!-- Dashboard Settings Card -->
|
|
1460
|
+
<div class="card">
|
|
1461
|
+
<div class="card-header">
|
|
1462
|
+
<span class="card-title">Dashboard Settings</span>
|
|
1463
|
+
<span class="badge" id="dashboardBadge">Enabled</span>
|
|
1464
|
+
</div>
|
|
1465
|
+
<div class="stat">
|
|
1466
|
+
<div class="stat-label">Auto-Start on MCP Launch</div>
|
|
1467
|
+
<div class="stat-value small" id="dashboardEnabled">-</div>
|
|
1468
|
+
</div>
|
|
1469
|
+
<div class="settings-form" id="dashboardSettingsForm">
|
|
1470
|
+
<div class="form-group">
|
|
1471
|
+
<label for="dashboardEnabledSelect">Dashboard Auto-Start</label>
|
|
1472
|
+
<select id="dashboardEnabledSelect" class="form-select">
|
|
1473
|
+
<option value="true">Enabled (auto-start with MCP server)</option>
|
|
1474
|
+
<option value="false">Disabled (manual start only)</option>
|
|
1475
|
+
</select>
|
|
1476
|
+
</div>
|
|
1477
|
+
<div class="form-hint">
|
|
1478
|
+
When disabled, use the <code>open_dashboard</code> MCP tool to start manually.
|
|
1479
|
+
</div>
|
|
1480
|
+
<div class="form-actions">
|
|
1481
|
+
<button type="button" id="saveDashboardBtn" class="btn btn-primary" style="display: none;">Save Settings</button>
|
|
1482
|
+
<span id="saveDashboardStatus" class="save-status"></span>
|
|
1483
|
+
</div>
|
|
1484
|
+
</div>
|
|
1485
|
+
</div>
|
|
1486
|
+
|
|
1487
|
+
<!-- Search Weights Card -->
|
|
1488
|
+
<div class="card">
|
|
1489
|
+
<div class="card-header">
|
|
1490
|
+
<span class="card-title">Search Weights</span>
|
|
1491
|
+
</div>
|
|
1492
|
+
<div class="stat">
|
|
1493
|
+
<div class="stat-label">Semantic vs Keyword Balance</div>
|
|
1494
|
+
<div class="slider-value" id="weightsDisplay">Semantic: 70%, Keyword: 30%</div>
|
|
1495
|
+
</div>
|
|
1496
|
+
<div class="form-group" style="margin-top: 16px;">
|
|
1497
|
+
<input type="range" id="weightsSlider" class="form-slider" min="0" max="100" value="70">
|
|
1498
|
+
<div class="slider-labels">
|
|
1499
|
+
<span>100% Keyword</span>
|
|
1500
|
+
<span>100% Semantic</span>
|
|
1501
|
+
</div>
|
|
1502
|
+
</div>
|
|
1503
|
+
<div class="form-hint" style="margin-top: 12px;">
|
|
1504
|
+
Semantic search finds conceptually similar code. Keyword search matches exact terms.
|
|
1505
|
+
</div>
|
|
1506
|
+
<div class="form-actions">
|
|
1507
|
+
<button type="button" id="saveWeightsBtn" class="btn btn-primary" style="display: none;">Save Weights</button>
|
|
1508
|
+
<span id="saveWeightsStatus" class="save-status"></span>
|
|
1509
|
+
</div>
|
|
1510
|
+
</div>
|
|
1511
|
+
|
|
1512
|
+
<!-- File Patterns Card -->
|
|
1513
|
+
<div class="card">
|
|
1514
|
+
<div class="card-header">
|
|
1515
|
+
<span class="card-title">File Patterns</span>
|
|
1516
|
+
</div>
|
|
1517
|
+
<div class="patterns-section">
|
|
1518
|
+
<div class="patterns-group">
|
|
1519
|
+
<div class="patterns-group-header">
|
|
1520
|
+
<span class="patterns-group-title">Include</span>
|
|
1521
|
+
<span class="badge" id="includePatternsCount">0</span>
|
|
1522
|
+
</div>
|
|
1523
|
+
<div class="stat-label">Files matching these patterns will be indexed</div>
|
|
1524
|
+
<div class="chips-container" id="includePatternsChips">
|
|
1525
|
+
<span class="chips-empty">Loading...</span>
|
|
1526
|
+
</div>
|
|
1527
|
+
<div class="chip-input-group">
|
|
1528
|
+
<input type="text" id="includePatternInput" class="form-input" placeholder="e.g., **/*.ts">
|
|
1529
|
+
<button type="button" id="addIncludePatternBtn" class="btn btn-secondary">Add</button>
|
|
1530
|
+
</div>
|
|
1531
|
+
<span id="includePatternStatus" class="save-status"></span>
|
|
1532
|
+
</div>
|
|
1533
|
+
<div class="patterns-group">
|
|
1534
|
+
<div class="patterns-group-header">
|
|
1535
|
+
<span class="patterns-group-title">Exclude</span>
|
|
1536
|
+
<span class="badge" id="excludePatternsCount">0</span>
|
|
1537
|
+
</div>
|
|
1538
|
+
<div class="stat-label">Files matching these patterns will be skipped</div>
|
|
1539
|
+
<div class="chips-container" id="excludePatternsChips">
|
|
1540
|
+
<span class="chips-empty">Loading...</span>
|
|
1541
|
+
</div>
|
|
1542
|
+
<div class="chip-input-group">
|
|
1543
|
+
<input type="text" id="excludePatternInput" class="form-input" placeholder="e.g., **/node_modules/**">
|
|
1544
|
+
<button type="button" id="addExcludePatternBtn" class="btn btn-secondary">Add</button>
|
|
1545
|
+
</div>
|
|
1546
|
+
<span id="excludePatternStatus" class="save-status"></span>
|
|
1547
|
+
</div>
|
|
1548
|
+
</div>
|
|
1549
|
+
</div>
|
|
1550
|
+
</div>
|
|
1551
|
+
</div><!-- End Settings Tab -->
|
|
1552
|
+
|
|
1553
|
+
</div>
|
|
1554
|
+
|
|
1555
|
+
<script>
|
|
1556
|
+
// Theme management
|
|
1557
|
+
function getStoredTheme() {
|
|
1558
|
+
return localStorage.getItem('glancey-theme') || 'dark';
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
function setTheme(theme) {
|
|
1562
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
1563
|
+
localStorage.setItem('glancey-theme', theme);
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
function toggleTheme() {
|
|
1567
|
+
const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
1568
|
+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
1569
|
+
setTheme(newTheme);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// Initialize theme
|
|
1573
|
+
setTheme(getStoredTheme());
|
|
1574
|
+
|
|
1575
|
+
// Theme toggle button
|
|
1576
|
+
document.getElementById('themeToggle').addEventListener('click', toggleTheme);
|
|
1577
|
+
|
|
1578
|
+
// Tab management
|
|
1579
|
+
function getStoredTab() {
|
|
1580
|
+
return localStorage.getItem('glancey-tab') || 'status';
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
function setActiveTab(tabId) {
|
|
1584
|
+
// Update buttons
|
|
1585
|
+
document.querySelectorAll('.tab-btn').forEach(btn => {
|
|
1586
|
+
btn.classList.toggle('active', btn.getAttribute('data-tab') === tabId);
|
|
1587
|
+
});
|
|
1588
|
+
// Update content
|
|
1589
|
+
document.querySelectorAll('.tab-content').forEach(content => {
|
|
1590
|
+
content.classList.toggle('active', content.id === 'tab-' + tabId);
|
|
1591
|
+
});
|
|
1592
|
+
localStorage.setItem('glancey-tab', tabId);
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
// Initialize tabs
|
|
1596
|
+
setActiveTab(getStoredTab());
|
|
1597
|
+
|
|
1598
|
+
// Tab click handlers
|
|
1599
|
+
document.querySelectorAll('.tab-btn').forEach(btn => {
|
|
1600
|
+
btn.addEventListener('click', () => {
|
|
1601
|
+
setActiveTab(btn.getAttribute('data-tab'));
|
|
1602
|
+
});
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
// State
|
|
1606
|
+
let isConnected = false;
|
|
1607
|
+
let eventSource = null;
|
|
1608
|
+
|
|
1609
|
+
// DOM elements
|
|
1610
|
+
const connectionDot = document.getElementById('connectionDot');
|
|
1611
|
+
const connectionText = document.getElementById('connectionText');
|
|
1612
|
+
const versionBadge = document.getElementById('versionBadge');
|
|
1613
|
+
const projectNameHeader = document.getElementById('projectName');
|
|
1614
|
+
const indexBadge = document.getElementById('indexBadge');
|
|
1615
|
+
const fileCount = document.getElementById('fileCount');
|
|
1616
|
+
const chunkCount = document.getElementById('chunkCount');
|
|
1617
|
+
const lastUpdated = document.getElementById('lastUpdated');
|
|
1618
|
+
const embeddingBackend = document.getElementById('embeddingBackend');
|
|
1619
|
+
const embeddingStatus = document.getElementById('embeddingStatus');
|
|
1620
|
+
const indexPath = document.getElementById('indexPath');
|
|
1621
|
+
const fallbackBanner = document.getElementById('fallbackBanner');
|
|
1622
|
+
const fallbackContent = document.getElementById('fallbackContent');
|
|
1623
|
+
const progressContainer = document.getElementById('progressContainer');
|
|
1624
|
+
const progressFill = document.getElementById('progressFill');
|
|
1625
|
+
const progressText = document.getElementById('progressText');
|
|
1626
|
+
|
|
1627
|
+
// Reindex button elements
|
|
1628
|
+
const reindexBtn = document.getElementById('reindexBtn');
|
|
1629
|
+
const forceReindexBtn = document.getElementById('forceReindexBtn');
|
|
1630
|
+
const reindexStatus = document.getElementById('reindexStatus');
|
|
1631
|
+
|
|
1632
|
+
// Log panel elements
|
|
1633
|
+
const logHeader = document.getElementById('logHeader');
|
|
1634
|
+
const logToggle = document.getElementById('logToggle');
|
|
1635
|
+
const logContainer = document.getElementById('logContainer');
|
|
1636
|
+
const logCount = document.getElementById('logCount');
|
|
1637
|
+
const logEmpty = document.getElementById('logEmpty');
|
|
1638
|
+
const clearLogsBtn = document.getElementById('clearLogsBtn');
|
|
1639
|
+
let logEntryCount = 0;
|
|
1640
|
+
|
|
1641
|
+
// Embedding settings form elements
|
|
1642
|
+
const backendSelect = document.getElementById('backendSelect');
|
|
1643
|
+
const concurrencySelect = document.getElementById('concurrencySelect');
|
|
1644
|
+
const batchSizeSelect = document.getElementById('batchSizeSelect');
|
|
1645
|
+
const ollamaSettingsGroup = document.getElementById('ollamaSettingsGroup');
|
|
1646
|
+
const apiKeyGroup = document.getElementById('apiKeyGroup');
|
|
1647
|
+
const apiKeyInput = document.getElementById('apiKeyInput');
|
|
1648
|
+
const saveEmbeddingBtn = document.getElementById('saveEmbeddingBtn');
|
|
1649
|
+
const saveStatus = document.getElementById('saveStatus');
|
|
1650
|
+
|
|
1651
|
+
// Track saved settings to detect changes
|
|
1652
|
+
let savedSettings = { backend: 'gemini', ollamaConcurrency: '1', batchSize: '256' };
|
|
1653
|
+
|
|
1654
|
+
// Check if current form values differ from saved settings
|
|
1655
|
+
function hasSettingsChanged() {
|
|
1656
|
+
const currentBackend = backendSelect.value;
|
|
1657
|
+
const currentConcurrency = concurrencySelect.value;
|
|
1658
|
+
const currentBatchSize = batchSizeSelect.value;
|
|
1659
|
+
const needsApiKey = currentBackend === 'gemini';
|
|
1660
|
+
const hasNewApiKey = needsApiKey && apiKeyInput.value.trim() !== '';
|
|
1661
|
+
|
|
1662
|
+
return currentBackend !== savedSettings.backend ||
|
|
1663
|
+
currentConcurrency !== savedSettings.ollamaConcurrency ||
|
|
1664
|
+
currentBatchSize !== savedSettings.batchSize ||
|
|
1665
|
+
hasNewApiKey;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
// Update save button visibility based on changes
|
|
1669
|
+
function updateSaveButtonVisibility() {
|
|
1670
|
+
saveEmbeddingBtn.style.display = hasSettingsChanged() ? 'inline-block' : 'none';
|
|
1671
|
+
saveStatus.textContent = '';
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
// Toggle settings visibility based on backend selection
|
|
1675
|
+
function updateBackendVisibility() {
|
|
1676
|
+
const backend = backendSelect.value;
|
|
1677
|
+
const needsApiKey = backend === 'gemini';
|
|
1678
|
+
const apiKeyLabel = document.getElementById('apiKeyLabel');
|
|
1679
|
+
const apiKeyHint = document.getElementById('apiKeyHint');
|
|
1680
|
+
|
|
1681
|
+
apiKeyGroup.style.display = needsApiKey ? 'block' : 'none';
|
|
1682
|
+
ollamaSettingsGroup.style.display = backend === 'ollama' ? 'block' : 'none';
|
|
1683
|
+
|
|
1684
|
+
// Update API key label and hint based on backend (static trusted content)
|
|
1685
|
+
if (backend === 'gemini') {
|
|
1686
|
+
apiKeyLabel.textContent = 'Gemini API Key';
|
|
1687
|
+
apiKeyInput.placeholder = 'AIza...';
|
|
1688
|
+
apiKeyHint.textContent = '';
|
|
1689
|
+
const link = document.createElement('a');
|
|
1690
|
+
link.href = 'https://aistudio.google.com/app/apikey';
|
|
1691
|
+
link.target = '_blank';
|
|
1692
|
+
link.textContent = 'Google AI Studio';
|
|
1693
|
+
apiKeyHint.appendChild(document.createTextNode('Get your free API key at '));
|
|
1694
|
+
apiKeyHint.appendChild(link);
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
updateSaveButtonVisibility();
|
|
1698
|
+
}
|
|
1699
|
+
backendSelect.addEventListener('change', updateBackendVisibility);
|
|
1700
|
+
concurrencySelect.addEventListener('change', updateSaveButtonVisibility);
|
|
1701
|
+
batchSizeSelect.addEventListener('change', updateSaveButtonVisibility);
|
|
1702
|
+
apiKeyInput.addEventListener('input', updateSaveButtonVisibility);
|
|
1703
|
+
|
|
1704
|
+
// Load current embedding settings
|
|
1705
|
+
async function loadEmbeddingSettings() {
|
|
1706
|
+
try {
|
|
1707
|
+
const response = await fetch('/api/settings/embedding');
|
|
1708
|
+
if (response.ok) {
|
|
1709
|
+
const settings = await response.json();
|
|
1710
|
+
const configuredBackend = settings.backend || 'gemini';
|
|
1711
|
+
const concurrency = String(settings.ollamaConcurrency || 1);
|
|
1712
|
+
const batchSize = String(settings.batchSize || 256);
|
|
1713
|
+
|
|
1714
|
+
// If fallback occurred, show the running backend instead of configured
|
|
1715
|
+
const fallbackOccurred = settings.fallback && settings.fallback.occurred;
|
|
1716
|
+
const displayBackend = fallbackOccurred ? settings.runningBackend : configuredBackend;
|
|
1717
|
+
|
|
1718
|
+
// Update saved settings (use configured backend for comparison)
|
|
1719
|
+
savedSettings = { backend: configuredBackend, ollamaConcurrency: concurrency, batchSize };
|
|
1720
|
+
|
|
1721
|
+
// Update form values - show running backend when fallback occurred
|
|
1722
|
+
backendSelect.value = displayBackend || configuredBackend;
|
|
1723
|
+
concurrencySelect.value = concurrency;
|
|
1724
|
+
batchSizeSelect.value = batchSize;
|
|
1725
|
+
updateBackendVisibility();
|
|
1726
|
+
updateSaveButtonVisibility();
|
|
1727
|
+
|
|
1728
|
+
// Update status badge
|
|
1729
|
+
if (fallbackOccurred) {
|
|
1730
|
+
embeddingStatus.textContent = 'Fallback Active';
|
|
1731
|
+
embeddingStatus.className = 'badge warning';
|
|
1732
|
+
} else if (settings.backend === 'gemini') {
|
|
1733
|
+
if (settings.hasApiKey) {
|
|
1734
|
+
embeddingStatus.textContent = 'API Key Set';
|
|
1735
|
+
embeddingStatus.className = 'badge success';
|
|
1736
|
+
} else {
|
|
1737
|
+
embeddingStatus.textContent = 'API Key Required';
|
|
1738
|
+
embeddingStatus.className = 'badge warning';
|
|
1739
|
+
}
|
|
1740
|
+
} else {
|
|
1741
|
+
embeddingStatus.textContent = 'Local';
|
|
1742
|
+
embeddingStatus.className = 'badge';
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
} catch (error) {
|
|
1746
|
+
console.error('Failed to load embedding settings:', error);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// Save embedding settings
|
|
1751
|
+
saveEmbeddingBtn.addEventListener('click', async function() {
|
|
1752
|
+
const backend = backendSelect.value;
|
|
1753
|
+
const apiKey = apiKeyInput.value.trim();
|
|
1754
|
+
const needsApiKey = backend === 'gemini';
|
|
1755
|
+
|
|
1756
|
+
if (needsApiKey && !apiKey) {
|
|
1757
|
+
saveStatus.textContent = 'API key required for Gemini';
|
|
1758
|
+
saveStatus.className = 'save-status error';
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
saveEmbeddingBtn.disabled = true;
|
|
1763
|
+
saveStatus.textContent = 'Saving...';
|
|
1764
|
+
saveStatus.className = 'save-status';
|
|
1765
|
+
|
|
1766
|
+
try {
|
|
1767
|
+
const response = await fetch('/api/settings/embedding', {
|
|
1768
|
+
method: 'POST',
|
|
1769
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1770
|
+
body: JSON.stringify({
|
|
1771
|
+
backend,
|
|
1772
|
+
apiKey: needsApiKey ? apiKey : undefined,
|
|
1773
|
+
ollamaConcurrency: parseInt(concurrencySelect.value, 10),
|
|
1774
|
+
batchSize: parseInt(batchSizeSelect.value, 10)
|
|
1775
|
+
})
|
|
1776
|
+
});
|
|
1777
|
+
|
|
1778
|
+
const result = await response.json();
|
|
1779
|
+
|
|
1780
|
+
if (response.ok) {
|
|
1781
|
+
saveStatus.textContent = 'Saved! Auto-reloading config...';
|
|
1782
|
+
saveStatus.className = 'save-status success';
|
|
1783
|
+
apiKeyInput.value = ''; // Clear the input
|
|
1784
|
+
// Delay reload so user sees the success message
|
|
1785
|
+
setTimeout(loadEmbeddingSettings, 2000);
|
|
1786
|
+
} else {
|
|
1787
|
+
saveStatus.textContent = result.error || 'Failed to save';
|
|
1788
|
+
saveStatus.className = 'save-status error';
|
|
1789
|
+
}
|
|
1790
|
+
} catch (error) {
|
|
1791
|
+
saveStatus.textContent = 'Network error';
|
|
1792
|
+
saveStatus.className = 'save-status error';
|
|
1793
|
+
} finally {
|
|
1794
|
+
saveEmbeddingBtn.disabled = false;
|
|
1795
|
+
}
|
|
1796
|
+
});
|
|
1797
|
+
|
|
1798
|
+
// Load embedding settings on page load
|
|
1799
|
+
loadEmbeddingSettings();
|
|
1800
|
+
|
|
1801
|
+
// Reindex button handlers
|
|
1802
|
+
async function triggerReindex(force) {
|
|
1803
|
+
reindexBtn.disabled = true;
|
|
1804
|
+
forceReindexBtn.disabled = true;
|
|
1805
|
+
reindexStatus.textContent = force ? 'Starting force reindex...' : 'Starting reindex...';
|
|
1806
|
+
reindexStatus.className = 'save-status';
|
|
1807
|
+
|
|
1808
|
+
try {
|
|
1809
|
+
const response = await fetch('/api/reindex', {
|
|
1810
|
+
method: 'POST',
|
|
1811
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1812
|
+
body: JSON.stringify({ force })
|
|
1813
|
+
});
|
|
1814
|
+
|
|
1815
|
+
const result = await response.json();
|
|
1816
|
+
|
|
1817
|
+
if (response.ok) {
|
|
1818
|
+
reindexStatus.textContent = result.message;
|
|
1819
|
+
reindexStatus.className = 'save-status success';
|
|
1820
|
+
} else {
|
|
1821
|
+
reindexStatus.textContent = result.error || 'Failed to start reindex';
|
|
1822
|
+
reindexStatus.className = 'save-status error';
|
|
1823
|
+
reindexBtn.disabled = false;
|
|
1824
|
+
forceReindexBtn.disabled = false;
|
|
1825
|
+
}
|
|
1826
|
+
} catch (error) {
|
|
1827
|
+
reindexStatus.textContent = 'Network error';
|
|
1828
|
+
reindexStatus.className = 'save-status error';
|
|
1829
|
+
reindexBtn.disabled = false;
|
|
1830
|
+
forceReindexBtn.disabled = false;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
reindexBtn.addEventListener('click', () => triggerReindex(false));
|
|
1835
|
+
forceReindexBtn.addEventListener('click', () => triggerReindex(true));
|
|
1836
|
+
|
|
1837
|
+
// Re-enable buttons when indexing completes (SSE event)
|
|
1838
|
+
function enableReindexButtons() {
|
|
1839
|
+
reindexBtn.disabled = false;
|
|
1840
|
+
forceReindexBtn.disabled = false;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
// Log panel handlers
|
|
1844
|
+
let logCollapsed = false;
|
|
1845
|
+
|
|
1846
|
+
logHeader.addEventListener('click', () => {
|
|
1847
|
+
logCollapsed = !logCollapsed;
|
|
1848
|
+
logToggle.classList.toggle('collapsed', logCollapsed);
|
|
1849
|
+
logContainer.classList.toggle('collapsed', logCollapsed);
|
|
1850
|
+
});
|
|
1851
|
+
|
|
1852
|
+
clearLogsBtn.addEventListener('click', (e) => {
|
|
1853
|
+
e.stopPropagation(); // Don't toggle collapse
|
|
1854
|
+
logContainer.innerHTML = '<div class="log-empty" id="logEmpty">No logs yet. Logs will appear when indexing or other server operations occur.</div>';
|
|
1855
|
+
logEntryCount = 0;
|
|
1856
|
+
logCount.textContent = '0';
|
|
1857
|
+
});
|
|
1858
|
+
|
|
1859
|
+
function addLogEntry(level, message, timestamp) {
|
|
1860
|
+
// Remove empty message if present
|
|
1861
|
+
const emptyMsg = logContainer.querySelector('.log-empty');
|
|
1862
|
+
if (emptyMsg) emptyMsg.remove();
|
|
1863
|
+
|
|
1864
|
+
const entry = document.createElement('div');
|
|
1865
|
+
entry.className = 'log-entry log-' + level;
|
|
1866
|
+
|
|
1867
|
+
const time = document.createElement('span');
|
|
1868
|
+
time.className = 'log-time';
|
|
1869
|
+
const date = new Date(timestamp);
|
|
1870
|
+
time.textContent = date.toLocaleTimeString();
|
|
1871
|
+
|
|
1872
|
+
const msg = document.createElement('span');
|
|
1873
|
+
msg.className = 'log-message';
|
|
1874
|
+
msg.textContent = message;
|
|
1875
|
+
|
|
1876
|
+
entry.appendChild(time);
|
|
1877
|
+
entry.appendChild(msg);
|
|
1878
|
+
logContainer.appendChild(entry);
|
|
1879
|
+
|
|
1880
|
+
// Auto-scroll to bottom
|
|
1881
|
+
logContainer.scrollTop = logContainer.scrollHeight;
|
|
1882
|
+
|
|
1883
|
+
// Update count
|
|
1884
|
+
logEntryCount++;
|
|
1885
|
+
logCount.textContent = String(logEntryCount);
|
|
1886
|
+
|
|
1887
|
+
// Keep max 500 entries
|
|
1888
|
+
while (logContainer.children.length > 500) {
|
|
1889
|
+
logContainer.removeChild(logContainer.firstChild);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
// Dashboard settings form elements
|
|
1894
|
+
const dashboardBadge = document.getElementById('dashboardBadge');
|
|
1895
|
+
const dashboardEnabled = document.getElementById('dashboardEnabled');
|
|
1896
|
+
const dashboardEnabledSelect = document.getElementById('dashboardEnabledSelect');
|
|
1897
|
+
const saveDashboardBtn = document.getElementById('saveDashboardBtn');
|
|
1898
|
+
const saveDashboardStatus = document.getElementById('saveDashboardStatus');
|
|
1899
|
+
|
|
1900
|
+
// Track saved dashboard settings
|
|
1901
|
+
let savedDashboardEnabled = true;
|
|
1902
|
+
|
|
1903
|
+
// Check if dashboard settings changed
|
|
1904
|
+
function hasDashboardSettingsChanged() {
|
|
1905
|
+
return dashboardEnabledSelect.value !== String(savedDashboardEnabled);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
// Update dashboard save button visibility
|
|
1909
|
+
function updateDashboardSaveButtonVisibility() {
|
|
1910
|
+
saveDashboardBtn.style.display = hasDashboardSettingsChanged() ? 'inline-block' : 'none';
|
|
1911
|
+
saveDashboardStatus.textContent = '';
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
dashboardEnabledSelect.addEventListener('change', updateDashboardSaveButtonVisibility);
|
|
1915
|
+
|
|
1916
|
+
// Load current dashboard settings
|
|
1917
|
+
async function loadDashboardSettings() {
|
|
1918
|
+
try {
|
|
1919
|
+
const response = await fetch('/api/settings/dashboard');
|
|
1920
|
+
if (response.ok) {
|
|
1921
|
+
const settings = await response.json();
|
|
1922
|
+
savedDashboardEnabled = settings.enabled;
|
|
1923
|
+
|
|
1924
|
+
// Update form and display
|
|
1925
|
+
dashboardEnabledSelect.value = String(settings.enabled);
|
|
1926
|
+
dashboardEnabled.textContent = settings.enabled ? 'Enabled' : 'Disabled';
|
|
1927
|
+
dashboardBadge.textContent = settings.enabled ? 'Enabled' : 'Disabled';
|
|
1928
|
+
dashboardBadge.className = settings.enabled ? 'badge success' : 'badge warning';
|
|
1929
|
+
updateDashboardSaveButtonVisibility();
|
|
1930
|
+
}
|
|
1931
|
+
} catch (error) {
|
|
1932
|
+
console.error('Failed to load dashboard settings:', error);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
// Save dashboard settings
|
|
1937
|
+
saveDashboardBtn.addEventListener('click', async function() {
|
|
1938
|
+
const enabled = dashboardEnabledSelect.value === 'true';
|
|
1939
|
+
|
|
1940
|
+
saveDashboardBtn.disabled = true;
|
|
1941
|
+
saveDashboardStatus.textContent = 'Saving...';
|
|
1942
|
+
saveDashboardStatus.className = 'save-status';
|
|
1943
|
+
|
|
1944
|
+
try {
|
|
1945
|
+
const response = await fetch('/api/settings/dashboard', {
|
|
1946
|
+
method: 'POST',
|
|
1947
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1948
|
+
body: JSON.stringify({ enabled })
|
|
1949
|
+
});
|
|
1950
|
+
|
|
1951
|
+
const result = await response.json();
|
|
1952
|
+
|
|
1953
|
+
if (response.ok) {
|
|
1954
|
+
saveDashboardStatus.textContent = result.message || 'Saved!';
|
|
1955
|
+
saveDashboardStatus.className = 'save-status success';
|
|
1956
|
+
loadDashboardSettings();
|
|
1957
|
+
} else {
|
|
1958
|
+
saveDashboardStatus.textContent = result.error || 'Failed to save';
|
|
1959
|
+
saveDashboardStatus.className = 'save-status error';
|
|
1960
|
+
}
|
|
1961
|
+
} catch (error) {
|
|
1962
|
+
saveDashboardStatus.textContent = 'Network error';
|
|
1963
|
+
saveDashboardStatus.className = 'save-status error';
|
|
1964
|
+
} finally {
|
|
1965
|
+
saveDashboardBtn.disabled = false;
|
|
1966
|
+
}
|
|
1967
|
+
});
|
|
1968
|
+
|
|
1969
|
+
// Load dashboard settings on page load
|
|
1970
|
+
loadDashboardSettings();
|
|
1971
|
+
|
|
1972
|
+
// ===== Search Weights Settings =====
|
|
1973
|
+
const weightsSlider = document.getElementById('weightsSlider');
|
|
1974
|
+
const weightsDisplay = document.getElementById('weightsDisplay');
|
|
1975
|
+
const saveWeightsBtn = document.getElementById('saveWeightsBtn');
|
|
1976
|
+
const saveWeightsStatus = document.getElementById('saveWeightsStatus');
|
|
1977
|
+
let savedSemanticWeight = 70;
|
|
1978
|
+
|
|
1979
|
+
function updateWeightsDisplay() {
|
|
1980
|
+
const semantic = parseInt(weightsSlider.value, 10);
|
|
1981
|
+
const keyword = 100 - semantic;
|
|
1982
|
+
weightsDisplay.textContent = 'Semantic: ' + semantic + '%, Keyword: ' + keyword + '%';
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
function hasWeightsChanged() {
|
|
1986
|
+
return parseInt(weightsSlider.value, 10) !== savedSemanticWeight;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
function updateWeightsSaveButtonVisibility() {
|
|
1990
|
+
saveWeightsBtn.style.display = hasWeightsChanged() ? 'inline-block' : 'none';
|
|
1991
|
+
saveWeightsStatus.textContent = '';
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
weightsSlider.addEventListener('input', function() {
|
|
1995
|
+
updateWeightsDisplay();
|
|
1996
|
+
updateWeightsSaveButtonVisibility();
|
|
1997
|
+
});
|
|
1998
|
+
|
|
1999
|
+
async function loadSearchWeights() {
|
|
2000
|
+
try {
|
|
2001
|
+
const response = await fetch('/api/search-weights');
|
|
2002
|
+
if (response.ok) {
|
|
2003
|
+
const weights = await response.json();
|
|
2004
|
+
const semantic = Math.round(weights.semanticWeight * 100);
|
|
2005
|
+
savedSemanticWeight = semantic;
|
|
2006
|
+
weightsSlider.value = semantic;
|
|
2007
|
+
updateWeightsDisplay();
|
|
2008
|
+
updateWeightsSaveButtonVisibility();
|
|
2009
|
+
}
|
|
2010
|
+
} catch (error) {
|
|
2011
|
+
console.error('Failed to load search weights:', error);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
saveWeightsBtn.addEventListener('click', async function() {
|
|
2016
|
+
const semantic = parseInt(weightsSlider.value, 10) / 100;
|
|
2017
|
+
const keyword = 1 - semantic;
|
|
2018
|
+
|
|
2019
|
+
saveWeightsBtn.disabled = true;
|
|
2020
|
+
saveWeightsStatus.textContent = 'Saving...';
|
|
2021
|
+
saveWeightsStatus.className = 'save-status';
|
|
2022
|
+
|
|
2023
|
+
try {
|
|
2024
|
+
const response = await fetch('/api/search-weights', {
|
|
2025
|
+
method: 'PUT',
|
|
2026
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2027
|
+
body: JSON.stringify({ semanticWeight: semantic, keywordWeight: keyword })
|
|
2028
|
+
});
|
|
2029
|
+
|
|
2030
|
+
const result = await response.json();
|
|
2031
|
+
|
|
2032
|
+
if (response.ok) {
|
|
2033
|
+
savedSemanticWeight = parseInt(weightsSlider.value, 10);
|
|
2034
|
+
saveWeightsStatus.textContent = 'Saved!';
|
|
2035
|
+
saveWeightsStatus.className = 'save-status success';
|
|
2036
|
+
updateWeightsSaveButtonVisibility();
|
|
2037
|
+
fetchData(); // Refresh config display
|
|
2038
|
+
} else {
|
|
2039
|
+
saveWeightsStatus.textContent = result.error || 'Failed to save';
|
|
2040
|
+
saveWeightsStatus.className = 'save-status error';
|
|
2041
|
+
}
|
|
2042
|
+
} catch (error) {
|
|
2043
|
+
saveWeightsStatus.textContent = 'Network error';
|
|
2044
|
+
saveWeightsStatus.className = 'save-status error';
|
|
2045
|
+
} finally {
|
|
2046
|
+
saveWeightsBtn.disabled = false;
|
|
2047
|
+
}
|
|
2048
|
+
});
|
|
2049
|
+
|
|
2050
|
+
loadSearchWeights();
|
|
2051
|
+
|
|
2052
|
+
// ===== Include/Exclude Patterns =====
|
|
2053
|
+
const includePatternsChips = document.getElementById('includePatternsChips');
|
|
2054
|
+
const excludePatternsChips = document.getElementById('excludePatternsChips');
|
|
2055
|
+
const includePatternsCount = document.getElementById('includePatternsCount');
|
|
2056
|
+
const excludePatternsCount = document.getElementById('excludePatternsCount');
|
|
2057
|
+
const includePatternInput = document.getElementById('includePatternInput');
|
|
2058
|
+
const excludePatternInput = document.getElementById('excludePatternInput');
|
|
2059
|
+
const addIncludePatternBtn = document.getElementById('addIncludePatternBtn');
|
|
2060
|
+
const addExcludePatternBtn = document.getElementById('addExcludePatternBtn');
|
|
2061
|
+
const includePatternStatus = document.getElementById('includePatternStatus');
|
|
2062
|
+
const excludePatternStatus = document.getElementById('excludePatternStatus');
|
|
2063
|
+
|
|
2064
|
+
function renderPatternChips(container, patterns, type, countBadge) {
|
|
2065
|
+
// Clear container safely
|
|
2066
|
+
while (container.firstChild) {
|
|
2067
|
+
container.removeChild(container.firstChild);
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
if (!patterns || patterns.length === 0) {
|
|
2071
|
+
const empty = document.createElement('span');
|
|
2072
|
+
empty.className = 'chips-empty';
|
|
2073
|
+
empty.textContent = 'No patterns configured';
|
|
2074
|
+
container.appendChild(empty);
|
|
2075
|
+
countBadge.textContent = '0';
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
countBadge.textContent = String(patterns.length);
|
|
2080
|
+
|
|
2081
|
+
patterns.forEach(function(pattern) {
|
|
2082
|
+
const chip = document.createElement('span');
|
|
2083
|
+
chip.className = type === 'exclude' ? 'chip exclude' : 'chip';
|
|
2084
|
+
|
|
2085
|
+
const patternText = document.createTextNode(pattern);
|
|
2086
|
+
chip.appendChild(patternText);
|
|
2087
|
+
|
|
2088
|
+
const removeBtn = document.createElement('button');
|
|
2089
|
+
removeBtn.type = 'button';
|
|
2090
|
+
removeBtn.className = 'chip-remove';
|
|
2091
|
+
removeBtn.title = 'Remove pattern';
|
|
2092
|
+
removeBtn.textContent = '\\u00d7';
|
|
2093
|
+
|
|
2094
|
+
removeBtn.addEventListener('click', async function(e) {
|
|
2095
|
+
e.stopPropagation();
|
|
2096
|
+
await removePatternFromConfig(pattern, type);
|
|
2097
|
+
});
|
|
2098
|
+
|
|
2099
|
+
chip.appendChild(removeBtn);
|
|
2100
|
+
container.appendChild(chip);
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
async function removePatternFromConfig(pattern, type) {
|
|
2105
|
+
const statusEl = type === 'include' ? includePatternStatus : excludePatternStatus;
|
|
2106
|
+
statusEl.textContent = 'Removing...';
|
|
2107
|
+
statusEl.className = 'save-status';
|
|
2108
|
+
|
|
2109
|
+
try {
|
|
2110
|
+
const response = await fetch('/api/patterns', {
|
|
2111
|
+
method: 'DELETE',
|
|
2112
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2113
|
+
body: JSON.stringify({ pattern, type })
|
|
2114
|
+
});
|
|
2115
|
+
|
|
2116
|
+
const result = await response.json();
|
|
2117
|
+
|
|
2118
|
+
if (response.ok) {
|
|
2119
|
+
statusEl.textContent = 'Removed!';
|
|
2120
|
+
statusEl.className = 'save-status success';
|
|
2121
|
+
setTimeout(function() { statusEl.textContent = ''; }, 2000);
|
|
2122
|
+
fetchData(); // Refresh patterns
|
|
2123
|
+
} else {
|
|
2124
|
+
statusEl.textContent = result.error || 'Failed to remove';
|
|
2125
|
+
statusEl.className = 'save-status error';
|
|
2126
|
+
}
|
|
2127
|
+
} catch (error) {
|
|
2128
|
+
statusEl.textContent = 'Network error';
|
|
2129
|
+
statusEl.className = 'save-status error';
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
async function addPatternToConfig(pattern, type) {
|
|
2134
|
+
const statusEl = type === 'include' ? includePatternStatus : excludePatternStatus;
|
|
2135
|
+
const inputEl = type === 'include' ? includePatternInput : excludePatternInput;
|
|
2136
|
+
|
|
2137
|
+
if (!pattern.trim()) {
|
|
2138
|
+
statusEl.textContent = 'Pattern is required';
|
|
2139
|
+
statusEl.className = 'save-status error';
|
|
2140
|
+
return;
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
statusEl.textContent = 'Adding...';
|
|
2144
|
+
statusEl.className = 'save-status';
|
|
2145
|
+
|
|
2146
|
+
try {
|
|
2147
|
+
const response = await fetch('/api/patterns', {
|
|
2148
|
+
method: 'POST',
|
|
2149
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2150
|
+
body: JSON.stringify({ pattern: pattern.trim(), type })
|
|
2151
|
+
});
|
|
2152
|
+
|
|
2153
|
+
const result = await response.json();
|
|
2154
|
+
|
|
2155
|
+
if (response.ok) {
|
|
2156
|
+
inputEl.value = '';
|
|
2157
|
+
statusEl.textContent = 'Added!';
|
|
2158
|
+
statusEl.className = 'save-status success';
|
|
2159
|
+
setTimeout(function() { statusEl.textContent = ''; }, 2000);
|
|
2160
|
+
fetchData(); // Refresh patterns
|
|
2161
|
+
} else {
|
|
2162
|
+
statusEl.textContent = result.error || 'Failed to add';
|
|
2163
|
+
statusEl.className = 'save-status error';
|
|
2164
|
+
}
|
|
2165
|
+
} catch (error) {
|
|
2166
|
+
statusEl.textContent = 'Network error';
|
|
2167
|
+
statusEl.className = 'save-status error';
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
addIncludePatternBtn.addEventListener('click', function() {
|
|
2172
|
+
addPatternToConfig(includePatternInput.value, 'include');
|
|
2173
|
+
});
|
|
2174
|
+
|
|
2175
|
+
addExcludePatternBtn.addEventListener('click', function() {
|
|
2176
|
+
addPatternToConfig(excludePatternInput.value, 'exclude');
|
|
2177
|
+
});
|
|
2178
|
+
|
|
2179
|
+
// Handle Enter key for pattern inputs
|
|
2180
|
+
includePatternInput.addEventListener('keydown', function(e) {
|
|
2181
|
+
if (e.key === 'Enter') {
|
|
2182
|
+
e.preventDefault();
|
|
2183
|
+
addPatternToConfig(includePatternInput.value, 'include');
|
|
2184
|
+
}
|
|
2185
|
+
});
|
|
2186
|
+
|
|
2187
|
+
excludePatternInput.addEventListener('keydown', function(e) {
|
|
2188
|
+
if (e.key === 'Enter') {
|
|
2189
|
+
e.preventDefault();
|
|
2190
|
+
addPatternToConfig(excludePatternInput.value, 'exclude');
|
|
2191
|
+
}
|
|
2192
|
+
});
|
|
2193
|
+
|
|
2194
|
+
// Format date
|
|
2195
|
+
function formatDate(isoString) {
|
|
2196
|
+
if (!isoString) return 'Never';
|
|
2197
|
+
const date = new Date(isoString);
|
|
2198
|
+
return date.toLocaleString();
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
// Update connection status
|
|
2202
|
+
function setConnected(connected) {
|
|
2203
|
+
isConnected = connected;
|
|
2204
|
+
connectionDot.className = 'status-dot' + (connected ? ' connected' : '');
|
|
2205
|
+
connectionText.textContent = connected ? 'Connected' : 'Disconnected';
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
// Update index status
|
|
2209
|
+
function updateStatus(status) {
|
|
2210
|
+
if (status.indexed) {
|
|
2211
|
+
indexBadge.textContent = 'Indexed';
|
|
2212
|
+
indexBadge.className = 'badge success';
|
|
2213
|
+
} else {
|
|
2214
|
+
indexBadge.textContent = 'Not Indexed';
|
|
2215
|
+
indexBadge.className = 'badge warning';
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
if (status.isIndexing) {
|
|
2219
|
+
indexBadge.textContent = 'Indexing...';
|
|
2220
|
+
indexBadge.className = 'badge warning pulsing';
|
|
2221
|
+
progressContainer.className = 'progress-container active';
|
|
2222
|
+
} else {
|
|
2223
|
+
progressContainer.className = 'progress-container';
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
fileCount.textContent = status.fileCount.toLocaleString();
|
|
2227
|
+
chunkCount.textContent = status.chunkCount.toLocaleString();
|
|
2228
|
+
lastUpdated.textContent = formatDate(status.lastUpdated);
|
|
2229
|
+
embeddingBackend.textContent = status.embeddingBackend || 'Not configured';
|
|
2230
|
+
indexPath.textContent = status.indexPath || '-';
|
|
2231
|
+
|
|
2232
|
+
// Update version badge
|
|
2233
|
+
if (status.version) {
|
|
2234
|
+
versionBadge.textContent = 'v' + status.version;
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
// Show fallback banner if a backend fallback occurred
|
|
2238
|
+
if (status.backendFallback && status.backendFallback.occurred) {
|
|
2239
|
+
const fb = status.backendFallback;
|
|
2240
|
+
// Clear and rebuild content safely using DOM methods
|
|
2241
|
+
fallbackContent.textContent = '';
|
|
2242
|
+
const strong = document.createElement('strong');
|
|
2243
|
+
strong.textContent = fb.originalBackend;
|
|
2244
|
+
fallbackContent.appendChild(strong);
|
|
2245
|
+
fallbackContent.appendChild(document.createTextNode(' failed to initialize: '));
|
|
2246
|
+
const code = document.createElement('code');
|
|
2247
|
+
code.textContent = fb.reason;
|
|
2248
|
+
fallbackContent.appendChild(code);
|
|
2249
|
+
fallbackContent.appendChild(document.createElement('br'));
|
|
2250
|
+
fallbackContent.appendChild(document.createTextNode('Your index may need rebuilding if embedding dimensions differ between backends.'));
|
|
2251
|
+
fallbackBanner.classList.add('visible');
|
|
2252
|
+
} else {
|
|
2253
|
+
fallbackBanner.classList.remove('visible');
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
// Update config display
|
|
2258
|
+
function updateConfig(config) {
|
|
2259
|
+
// Update project name in header
|
|
2260
|
+
if (config.projectName) {
|
|
2261
|
+
projectNameHeader.textContent = config.projectName;
|
|
2262
|
+
} else if (config.projectPath) {
|
|
2263
|
+
// Fallback to extracting from path if projectName not provided
|
|
2264
|
+
projectNameHeader.textContent = config.projectPath.split('/').pop() || config.projectPath;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
// Update Settings tab pattern chips
|
|
2268
|
+
if (config.patterns) {
|
|
2269
|
+
renderPatternChips(includePatternsChips, config.patterns, 'include', includePatternsCount);
|
|
2270
|
+
}
|
|
2271
|
+
if (config.excludePatterns) {
|
|
2272
|
+
renderPatternChips(excludePatternsChips, config.excludePatterns, 'exclude', excludePatternsCount);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
// Format seconds into human-readable time
|
|
2277
|
+
function formatEta(seconds) {
|
|
2278
|
+
if (seconds === undefined || seconds === null || seconds < 0) return '';
|
|
2279
|
+
if (seconds < 60) return seconds + 's';
|
|
2280
|
+
if (seconds < 3600) {
|
|
2281
|
+
const mins = Math.floor(seconds / 60);
|
|
2282
|
+
const secs = seconds % 60;
|
|
2283
|
+
return mins + 'm ' + secs + 's';
|
|
2284
|
+
}
|
|
2285
|
+
const hours = Math.floor(seconds / 3600);
|
|
2286
|
+
const mins = Math.floor((seconds % 3600) / 60);
|
|
2287
|
+
return hours + 'h ' + mins + 'm';
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
// Update progress
|
|
2291
|
+
function updateProgress(progress) {
|
|
2292
|
+
const percent = progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : 0;
|
|
2293
|
+
progressFill.style.width = percent + '%';
|
|
2294
|
+
let text = progress.message;
|
|
2295
|
+
if (progress.etaSeconds !== undefined && progress.etaSeconds > 0) {
|
|
2296
|
+
text += ' (ETA: ' + formatEta(progress.etaSeconds) + ')';
|
|
2297
|
+
}
|
|
2298
|
+
progressText.textContent = text;
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
// Charts.css color mapping - distinct colors for all commands
|
|
2302
|
+
const commandColors = {
|
|
2303
|
+
// Core search
|
|
2304
|
+
'search_code': '#58a6ff', // blue
|
|
2305
|
+
'search_similar': '#39c5cf', // cyan
|
|
2306
|
+
// Indexing
|
|
2307
|
+
'index_codebase': '#3fb950', // green
|
|
2308
|
+
'get_index_status': '#a371f7', // purple
|
|
2309
|
+
'clear_index': '#f85149', // red
|
|
2310
|
+
'get_project_instructions': '#d29922', // orange
|
|
2311
|
+
// Git
|
|
2312
|
+
'commit': '#f778ba', // pink
|
|
2313
|
+
// Symbol analysis
|
|
2314
|
+
'get_symbols_overview': '#79c0ff', // light blue
|
|
2315
|
+
'find_symbol': '#56d364', // bright green
|
|
2316
|
+
'find_referencing_symbols': '#bc8cff', // light purple
|
|
2317
|
+
'search_for_pattern': '#ff9f43', // bright orange
|
|
2318
|
+
'replace_symbol_body': '#ff6b6b', // coral
|
|
2319
|
+
'insert_before_symbol': '#feca57', // yellow
|
|
2320
|
+
'insert_after_symbol': '#48dbfb', // sky blue
|
|
2321
|
+
'rename_symbol': '#ff9ff3', // light pink
|
|
2322
|
+
// Memory
|
|
2323
|
+
'write_memory': '#1dd1a1', // teal
|
|
2324
|
+
'read_memory': '#5f27cd', // deep purple
|
|
2325
|
+
'list_memories': '#ee5a24', // burnt orange
|
|
2326
|
+
'delete_memory': '#c23616', // dark red
|
|
2327
|
+
'edit_memory': '#009432', // forest green
|
|
2328
|
+
// Worktree
|
|
2329
|
+
'create_worktree': '#12CBC4', // turquoise
|
|
2330
|
+
'list_worktrees': '#B53471', // magenta
|
|
2331
|
+
'remove_worktree': '#ED4C67', // watermelon
|
|
2332
|
+
'worktree_status': '#F79F1F', // golden
|
|
2333
|
+
// Clustering
|
|
2334
|
+
'list_concepts': '#A3CB38', // lime
|
|
2335
|
+
'search_by_concept': '#1289A7', // cerulean
|
|
2336
|
+
'summarize_codebase': '#D980FA', // lavender
|
|
2337
|
+
// Dashboard
|
|
2338
|
+
'open_dashboard': '#e17055' // terra cotta
|
|
2339
|
+
};
|
|
2340
|
+
|
|
2341
|
+
// Update usage chart using charts.css
|
|
2342
|
+
const usageEmpty = document.getElementById('usageEmpty');
|
|
2343
|
+
const usageChartEl = document.getElementById('usage-chart');
|
|
2344
|
+
const usageChartBody = document.getElementById('usageChartBody');
|
|
2345
|
+
const chartLegend = document.getElementById('chartLegend');
|
|
2346
|
+
const usageTotal = document.getElementById('usageTotal');
|
|
2347
|
+
const totalCount = document.getElementById('totalCount');
|
|
2348
|
+
|
|
2349
|
+
function updateUsage(data) {
|
|
2350
|
+
const { usage, total } = data;
|
|
2351
|
+
|
|
2352
|
+
if (total === 0) {
|
|
2353
|
+
usageEmpty.style.display = 'block';
|
|
2354
|
+
usageChartEl.style.display = 'none';
|
|
2355
|
+
chartLegend.style.display = 'none';
|
|
2356
|
+
usageTotal.style.display = 'none';
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
usageEmpty.style.display = 'none';
|
|
2361
|
+
usageChartEl.style.display = '';
|
|
2362
|
+
chartLegend.style.display = 'flex';
|
|
2363
|
+
usageTotal.style.display = 'flex';
|
|
2364
|
+
|
|
2365
|
+
// Sort by count descending (most used first)
|
|
2366
|
+
const sortedUsage = usage.slice().sort(function(a, b) { return b.count - a.count; });
|
|
2367
|
+
const maxCount = Math.max(...sortedUsage.map(u => u.count));
|
|
2368
|
+
|
|
2369
|
+
let chartHtml = '';
|
|
2370
|
+
let legendHtml = '';
|
|
2371
|
+
let idx = 0;
|
|
2372
|
+
for (const item of sortedUsage) {
|
|
2373
|
+
if (item.count === 0) continue;
|
|
2374
|
+
|
|
2375
|
+
const percent = maxCount > 0 ? (item.count / maxCount) : 0;
|
|
2376
|
+
const color = commandColors[item.command] || '#58a6ff';
|
|
2377
|
+
|
|
2378
|
+
chartHtml += '<tr data-idx="' + idx + '">';
|
|
2379
|
+
chartHtml += '<th scope="row"></th>';
|
|
2380
|
+
chartHtml += '<td style="--size: ' + percent + '; --color: ' + color + ';"></td>';
|
|
2381
|
+
chartHtml += '</tr>';
|
|
2382
|
+
|
|
2383
|
+
legendHtml += '<li data-idx="' + idx + '" style="--color: ' + color + ';">' + escapeHtml(item.label) + ' (' + item.count + ')</li>';
|
|
2384
|
+
idx++;
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
usageChartBody.innerHTML = chartHtml;
|
|
2388
|
+
chartLegend.innerHTML = legendHtml;
|
|
2389
|
+
totalCount.textContent = total;
|
|
2390
|
+
|
|
2391
|
+
// Legend hover highlighting
|
|
2392
|
+
chartLegend.querySelectorAll('li').forEach(function(li) {
|
|
2393
|
+
li.addEventListener('mouseenter', function() {
|
|
2394
|
+
var idx = this.getAttribute('data-idx');
|
|
2395
|
+
usageChartEl.classList.add('legend-hover');
|
|
2396
|
+
var row = usageChartBody.querySelector('tr[data-idx="' + idx + '"]');
|
|
2397
|
+
if (row) row.classList.add('highlight');
|
|
2398
|
+
});
|
|
2399
|
+
li.addEventListener('mouseleave', function() {
|
|
2400
|
+
usageChartEl.classList.remove('legend-hover');
|
|
2401
|
+
usageChartBody.querySelectorAll('tr').forEach(function(tr) {
|
|
2402
|
+
tr.classList.remove('highlight');
|
|
2403
|
+
});
|
|
2404
|
+
});
|
|
2405
|
+
});
|
|
2406
|
+
|
|
2407
|
+
// Bar hover highlighting (reverse - highlight legend item)
|
|
2408
|
+
usageChartBody.querySelectorAll('td').forEach(function(td) {
|
|
2409
|
+
td.addEventListener('mouseenter', function() {
|
|
2410
|
+
var idx = this.parentElement.getAttribute('data-idx');
|
|
2411
|
+
chartLegend.classList.add('bar-hover');
|
|
2412
|
+
var legendItem = chartLegend.querySelector('li[data-idx="' + idx + '"]');
|
|
2413
|
+
if (legendItem) legendItem.classList.add('highlight');
|
|
2414
|
+
});
|
|
2415
|
+
td.addEventListener('mouseleave', function() {
|
|
2416
|
+
chartLegend.classList.remove('bar-hover');
|
|
2417
|
+
chartLegend.querySelectorAll('li').forEach(function(li) {
|
|
2418
|
+
li.classList.remove('highlight');
|
|
2419
|
+
});
|
|
2420
|
+
});
|
|
2421
|
+
});
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
// Beads section elements
|
|
2425
|
+
const beadsSection = document.getElementById('beadsSection');
|
|
2426
|
+
const beadsReadyCount = document.getElementById('beadsReadyCount');
|
|
2427
|
+
const beadsOpenCount = document.getElementById('beadsOpenCount');
|
|
2428
|
+
const beadsTotalCount = document.getElementById('beadsTotalCount');
|
|
2429
|
+
const beadsDaemonDot = document.getElementById('beadsDaemonDot');
|
|
2430
|
+
const beadsDaemonText = document.getElementById('beadsDaemonText');
|
|
2431
|
+
const beadsIssuesList = document.getElementById('beadsIssuesList');
|
|
2432
|
+
const readyTasksBadge = document.getElementById('readyTasksBadge');
|
|
2433
|
+
|
|
2434
|
+
function updateBeads(data) {
|
|
2435
|
+
const beadsUnavailable = document.getElementById('beadsUnavailable');
|
|
2436
|
+
const beadsContent = document.getElementById('beadsContent');
|
|
2437
|
+
const beadsHeaderStats = document.getElementById('beadsHeaderStats');
|
|
2438
|
+
const beadsBadgeEl = document.getElementById('beadsBadge');
|
|
2439
|
+
|
|
2440
|
+
if (!data.available) {
|
|
2441
|
+
beadsUnavailable.style.display = 'block';
|
|
2442
|
+
beadsContent.style.display = 'none';
|
|
2443
|
+
beadsHeaderStats.style.display = 'none';
|
|
2444
|
+
beadsBadgeEl.style.display = 'none';
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
beadsUnavailable.style.display = 'none';
|
|
2449
|
+
beadsContent.style.display = 'block';
|
|
2450
|
+
beadsHeaderStats.style.display = 'flex';
|
|
2451
|
+
beadsBadgeEl.style.display = 'inline-flex';
|
|
2452
|
+
beadsReadyCount.textContent = data.readyCount;
|
|
2453
|
+
beadsOpenCount.textContent = data.openCount;
|
|
2454
|
+
beadsTotalCount.textContent = data.issueCount;
|
|
2455
|
+
readyTasksBadge.textContent = data.readyCount + ' task' + (data.readyCount !== 1 ? 's' : '');
|
|
2456
|
+
|
|
2457
|
+
// Daemon status
|
|
2458
|
+
if (data.daemonRunning) {
|
|
2459
|
+
beadsDaemonDot.className = 'status-dot connected';
|
|
2460
|
+
beadsDaemonText.textContent = 'Daemon running';
|
|
2461
|
+
} else {
|
|
2462
|
+
beadsDaemonDot.className = 'status-dot';
|
|
2463
|
+
beadsDaemonText.textContent = 'Daemon not running';
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
// Issues list
|
|
2467
|
+
if (data.issues && data.issues.length > 0) {
|
|
2468
|
+
let html = '';
|
|
2469
|
+
for (const issue of data.issues) {
|
|
2470
|
+
const hasDescription = issue.description && issue.description.trim();
|
|
2471
|
+
html += '<div class="beads-issue" onclick="toggleBeadsIssue(this)">';
|
|
2472
|
+
html += '<span class="beads-issue-id">' + escapeHtml(issue.id) + '</span>';
|
|
2473
|
+
html += '<div class="beads-issue-content">';
|
|
2474
|
+
html += '<div class="beads-issue-title">';
|
|
2475
|
+
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>';
|
|
2476
|
+
html += '<span>' + escapeHtml(issue.title) + '</span>';
|
|
2477
|
+
html += '</div>';
|
|
2478
|
+
html += '<div class="beads-issue-meta">';
|
|
2479
|
+
if (issue.issue_type) {
|
|
2480
|
+
html += '<span class="beads-issue-type">' + escapeHtml(issue.issue_type) + '</span>';
|
|
2481
|
+
}
|
|
2482
|
+
if (issue.priority) {
|
|
2483
|
+
html += '<span class="beads-issue-priority">';
|
|
2484
|
+
html += '<span class="priority-dot priority-' + issue.priority + '"></span>';
|
|
2485
|
+
html += 'P' + issue.priority;
|
|
2486
|
+
html += '</span>';
|
|
2487
|
+
}
|
|
2488
|
+
html += '</div>';
|
|
2489
|
+
if (hasDescription) {
|
|
2490
|
+
html += '<div class="beads-issue-description">' + escapeHtml(issue.description) + '</div>';
|
|
2491
|
+
} else {
|
|
2492
|
+
html += '<div class="beads-issue-description beads-issue-no-description">No description available</div>';
|
|
2493
|
+
}
|
|
2494
|
+
html += '</div>';
|
|
2495
|
+
html += '</div>';
|
|
2496
|
+
}
|
|
2497
|
+
beadsIssuesList.innerHTML = html;
|
|
2498
|
+
} else {
|
|
2499
|
+
beadsIssuesList.innerHTML = '<div class="beads-empty">No ready tasks</div>';
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
// Toggle beads issue expansion
|
|
2504
|
+
function toggleBeadsIssue(element) {
|
|
2505
|
+
element.classList.toggle('expanded');
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
// Escape HTML to prevent XSS
|
|
2509
|
+
function escapeHtml(text) {
|
|
2510
|
+
const div = document.createElement('div');
|
|
2511
|
+
div.textContent = text;
|
|
2512
|
+
return div.innerHTML;
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
// Fetch initial data
|
|
2516
|
+
async function fetchData() {
|
|
2517
|
+
// Use Promise.allSettled to handle partial failures gracefully
|
|
2518
|
+
const results = await Promise.allSettled([
|
|
2519
|
+
fetch('/api/status'),
|
|
2520
|
+
fetch('/api/config'),
|
|
2521
|
+
fetch('/api/usage'),
|
|
2522
|
+
fetch('/api/beads'),
|
|
2523
|
+
fetch('/api/token-savings')
|
|
2524
|
+
]);
|
|
2525
|
+
|
|
2526
|
+
let currentStatus = null;
|
|
2527
|
+
let currentConfig = null;
|
|
2528
|
+
|
|
2529
|
+
// Process status result
|
|
2530
|
+
if (results[0].status === 'fulfilled' && results[0].value.ok) {
|
|
2531
|
+
try {
|
|
2532
|
+
currentStatus = await results[0].value.json();
|
|
2533
|
+
updateStatus(currentStatus);
|
|
2534
|
+
} catch (e) {
|
|
2535
|
+
// Silently handle parse errors
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
// Process config result
|
|
2540
|
+
if (results[1].status === 'fulfilled' && results[1].value.ok) {
|
|
2541
|
+
try {
|
|
2542
|
+
currentConfig = await results[1].value.json();
|
|
2543
|
+
updateConfig(currentConfig);
|
|
2544
|
+
} catch (e) {
|
|
2545
|
+
// Silently handle parse errors
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
// Check if configured backend differs from running backend
|
|
2550
|
+
// Don't show "restart needed" if fallback occurred - the difference is expected
|
|
2551
|
+
if (currentStatus && currentConfig) {
|
|
2552
|
+
const runningBackend = currentStatus.embeddingBackend;
|
|
2553
|
+
const configuredBackend = currentConfig.embedding?.backend;
|
|
2554
|
+
const fallbackOccurred = currentStatus.backendFallback && currentStatus.backendFallback.occurred;
|
|
2555
|
+
if (configuredBackend && runningBackend && configuredBackend !== runningBackend && !fallbackOccurred) {
|
|
2556
|
+
embeddingBackend.innerHTML = runningBackend + ' <span class="badge warning" title="Restart required to use ' + configuredBackend + '">\u26a0 restart needed</span>';
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
// Process usage result
|
|
2561
|
+
if (results[2].status === 'fulfilled' && results[2].value.ok) {
|
|
2562
|
+
try {
|
|
2563
|
+
const usage = await results[2].value.json();
|
|
2564
|
+
updateUsage(usage);
|
|
2565
|
+
} catch (e) {
|
|
2566
|
+
console.error('Failed to parse usage:', e);
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
// Process beads result
|
|
2571
|
+
if (results[3].status === 'fulfilled' && results[3].value.ok) {
|
|
2572
|
+
try {
|
|
2573
|
+
const beads = await results[3].value.json();
|
|
2574
|
+
updateBeads(beads);
|
|
2575
|
+
} catch (e) {
|
|
2576
|
+
console.error('Failed to parse beads:', e);
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
// Process token savings result
|
|
2581
|
+
if (results[4].status === 'fulfilled' && results[4].value.ok) {
|
|
2582
|
+
try {
|
|
2583
|
+
const savings = await results[4].value.json();
|
|
2584
|
+
updateTokenSavings(savings);
|
|
2585
|
+
} catch (e) {
|
|
2586
|
+
console.error('Failed to parse token savings:', e);
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
// Update token savings display
|
|
2592
|
+
function updateTokenSavings(savings) {
|
|
2593
|
+
const tokensSaved = document.getElementById('tokensSaved');
|
|
2594
|
+
const savingsEfficiency = document.getElementById('savingsEfficiency');
|
|
2595
|
+
const filesAvoided = document.getElementById('filesAvoided');
|
|
2596
|
+
const operationCount = document.getElementById('operationCount');
|
|
2597
|
+
|
|
2598
|
+
if (tokensSaved) {
|
|
2599
|
+
tokensSaved.textContent = savings.tokensSaved.toLocaleString();
|
|
2600
|
+
}
|
|
2601
|
+
if (savingsEfficiency) {
|
|
2602
|
+
savingsEfficiency.textContent = savings.efficiencyPercent + '%';
|
|
2603
|
+
}
|
|
2604
|
+
if (filesAvoided) {
|
|
2605
|
+
filesAvoided.textContent = savings.filesAvoided.toLocaleString();
|
|
2606
|
+
}
|
|
2607
|
+
if (operationCount) {
|
|
2608
|
+
operationCount.textContent = savings.operationCount.toString();
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
// Console greeting
|
|
2613
|
+
console.log(\`
|
|
2614
|
+
%c _ _ _ _ _ __ _
|
|
2615
|
+
%c _ __ (_) ___| | _____ _ __ ___ (_) |_| |__ ___ ___ / _| |___ ____ _ _ __ ___
|
|
2616
|
+
%c| '_ \\\\| |/ __| |/ / __| '_ \\\` _ \\\\| | __| '_ \\\\ / __|/ _ \\\\| |_| __\\\\ \\\\ /\\\\ / / _\\\` | '__/ _ \\\\
|
|
2617
|
+
%c| | | | | (__| <\\\\__ \\\\ | | | | | | |_| | | |\\\\__ \\\\ (_) | _| |_ \\\\ V V / (_| | | | __/
|
|
2618
|
+
%c|_| |_|_|\\\\___|_|\\\\_\\\\___/_| |_| |_|_|\\\\__|_| |_||___/\\\\___/|_| \\\\__| \\\\_/\\\\_/ \\\\__,_|_| \\\\___|
|
|
2619
|
+
\`,
|
|
2620
|
+
'color: #58a6ff',
|
|
2621
|
+
'color: #58a6ff',
|
|
2622
|
+
'color: #58a6ff',
|
|
2623
|
+
'color: #58a6ff',
|
|
2624
|
+
'color: #58a6ff'
|
|
2625
|
+
);
|
|
2626
|
+
console.log(
|
|
2627
|
+
'%c📧 Get in touch: %cme@nicksmith.software',
|
|
2628
|
+
'color: #666; font-size: 14px;',
|
|
2629
|
+
'color: #58a6ff; font-size: 14px; text-decoration: underline;'
|
|
2630
|
+
);
|
|
2631
|
+
|
|
2632
|
+
// Connect to SSE
|
|
2633
|
+
function connectSSE() {
|
|
2634
|
+
if (eventSource) {
|
|
2635
|
+
eventSource.close();
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
eventSource = new EventSource('/api/events');
|
|
2639
|
+
|
|
2640
|
+
eventSource.addEventListener('connected', (e) => {
|
|
2641
|
+
setConnected(true);
|
|
2642
|
+
fetchData();
|
|
2643
|
+
});
|
|
2644
|
+
|
|
2645
|
+
eventSource.addEventListener('indexing:progress', (e) => {
|
|
2646
|
+
const progress = JSON.parse(e.data);
|
|
2647
|
+
progressContainer.className = 'progress-container active';
|
|
2648
|
+
indexBadge.textContent = 'Indexing...';
|
|
2649
|
+
indexBadge.className = 'badge warning pulsing';
|
|
2650
|
+
updateProgress(progress);
|
|
2651
|
+
});
|
|
2652
|
+
|
|
2653
|
+
eventSource.addEventListener('indexing:start', () => {
|
|
2654
|
+
progressContainer.className = 'progress-container active';
|
|
2655
|
+
indexBadge.textContent = 'Indexing...';
|
|
2656
|
+
indexBadge.className = 'badge warning pulsing';
|
|
2657
|
+
progressFill.style.width = '0%';
|
|
2658
|
+
progressText.textContent = 'Starting...';
|
|
2659
|
+
reindexBtn.disabled = true;
|
|
2660
|
+
forceReindexBtn.disabled = true;
|
|
2661
|
+
});
|
|
2662
|
+
|
|
2663
|
+
eventSource.addEventListener('indexing:complete', () => {
|
|
2664
|
+
progressContainer.className = 'progress-container';
|
|
2665
|
+
enableReindexButtons();
|
|
2666
|
+
reindexStatus.textContent = '';
|
|
2667
|
+
fetchData();
|
|
2668
|
+
});
|
|
2669
|
+
|
|
2670
|
+
eventSource.addEventListener('status:change', (e) => {
|
|
2671
|
+
const status = JSON.parse(e.data);
|
|
2672
|
+
updateStatus(status);
|
|
2673
|
+
});
|
|
2674
|
+
|
|
2675
|
+
eventSource.addEventListener('usage:update', (e) => {
|
|
2676
|
+
const usage = JSON.parse(e.data);
|
|
2677
|
+
// The event data is the usage array, need to compute total
|
|
2678
|
+
const total = usage.reduce((sum, u) => sum + u.count, 0);
|
|
2679
|
+
updateUsage({ usage, total });
|
|
2680
|
+
});
|
|
2681
|
+
|
|
2682
|
+
eventSource.addEventListener('tokenSavings:update', (e) => {
|
|
2683
|
+
const savings = JSON.parse(e.data);
|
|
2684
|
+
updateTokenSavings(savings);
|
|
2685
|
+
});
|
|
2686
|
+
|
|
2687
|
+
eventSource.addEventListener('heartbeat', () => {
|
|
2688
|
+
// Just keep connection alive
|
|
2689
|
+
});
|
|
2690
|
+
|
|
2691
|
+
eventSource.addEventListener('server:log', (e) => {
|
|
2692
|
+
const logData = JSON.parse(e.data);
|
|
2693
|
+
// Add to log panel only (no console output)
|
|
2694
|
+
addLogEntry(logData.level, logData.message, logData.timestamp);
|
|
2695
|
+
});
|
|
2696
|
+
|
|
2697
|
+
eventSource.onerror = (e) => {
|
|
2698
|
+
setConnected(false);
|
|
2699
|
+
// EventSource will automatically reconnect
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
// Initialize
|
|
2704
|
+
fetchData();
|
|
2705
|
+
connectSSE();
|
|
2706
|
+
</script>
|
|
2707
|
+
</body>
|
|
2708
|
+
</html>`;
|
|
2709
|
+
}
|
|
2710
|
+
//# sourceMappingURL=ui.js.map
|