@devilsdev/rag-pipeline-utils 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +293 -361
  2. package/contracts/loader-contract.json +11 -6
  3. package/contracts/reranker-contract.json +18 -26
  4. package/dist/index.cjs +21190 -47
  5. package/dist/index.d.ts +298 -68
  6. package/dist/index.mjs +9 -76
  7. package/package.json +20 -8
  8. package/src/agentic/agentic-pipeline.js +235 -0
  9. package/src/agentic/iterative-retriever.js +213 -0
  10. package/src/agentic/query-planner.js +208 -0
  11. package/src/agentic/self-critique.js +102 -0
  12. package/src/ai/adaptive-retrieval.js +0 -3
  13. package/src/ai/federated-learning.js +0 -3
  14. package/src/ai/federation.js +1 -6
  15. package/src/ai/index.js +9 -20
  16. package/src/ai/model-training.js +0 -3
  17. package/src/ai/multimodal-processing.js +0 -3
  18. package/src/ai/multimodal.js +1 -6
  19. package/src/ai/orchestrator.js +1 -6
  20. package/src/ai/retrieval-engine.js +1 -6
  21. package/src/chunking/chunking-engine.js +142 -0
  22. package/src/chunking/strategies/fixed-size.js +54 -0
  23. package/src/chunking/strategies/recursive.js +127 -0
  24. package/src/chunking/strategies/semantic.js +128 -0
  25. package/src/chunking/strategies/sentence.js +32 -0
  26. package/src/chunking/strategies/structure-aware.js +138 -0
  27. package/src/citation/citation-tracker.js +161 -0
  28. package/src/citation/hallucination-detector.js +74 -0
  29. package/src/citation/source-mapper.js +95 -0
  30. package/src/cli/commands/ai-ml/adaptive-commands.js +178 -0
  31. package/src/cli/commands/ai-ml/federated-commands.js +207 -0
  32. package/src/cli/commands/ai-ml/index.js +17 -0
  33. package/src/cli/commands/ai-ml/multimodal-commands.js +187 -0
  34. package/src/cli/commands/ai-ml/shared.js +51 -0
  35. package/src/cli/commands/ai-ml/training-commands.js +228 -0
  36. package/src/cli/commands/ai-ml.js +176 -757
  37. package/src/cli/commands/plugin-hub/certification-commands.js +221 -0
  38. package/src/cli/commands/plugin-hub/event-handlers.js +64 -0
  39. package/src/cli/commands/plugin-hub/hub-commands.js +290 -0
  40. package/src/cli/commands/plugin-hub/review-commands.js +78 -0
  41. package/src/cli/commands/plugin-hub.js +106 -782
  42. package/src/cli/diagnostics/config-checker.js +81 -0
  43. package/src/cli/diagnostics/dependency-checker.js +69 -0
  44. package/src/cli/diagnostics/environment-checker.js +45 -0
  45. package/src/cli/diagnostics/performance-checker.js +51 -0
  46. package/src/cli/diagnostics/plugin-checker.js +106 -0
  47. package/src/cli/diagnostics/security-checker.js +61 -0
  48. package/src/cli/doctor-command.js +70 -369
  49. package/src/cli/enhanced-cli-commands.js +162 -706
  50. package/src/cli/handlers/config-handler.js +126 -0
  51. package/src/cli/handlers/diagnostics-handler.js +113 -0
  52. package/src/cli/handlers/ingest-handler.js +42 -0
  53. package/src/cli/handlers/init-handler.js +51 -0
  54. package/src/cli/handlers/query-handler.js +45 -0
  55. package/src/cli/handlers/run-handler.js +181 -0
  56. package/src/cli/interactive-wizard.js +99 -723
  57. package/src/cli/shared/cli-utils.js +89 -0
  58. package/src/cli/wizard/configuration.js +148 -0
  59. package/src/cli/wizard/observability.js +79 -0
  60. package/src/cli/wizard/performance.js +76 -0
  61. package/src/cli/wizard/plugin-selection.js +297 -0
  62. package/src/cli/wizard/preview-save.js +172 -0
  63. package/src/cli/wizard/project-setup.js +66 -0
  64. package/src/config/memory-optimization.js +3 -3
  65. package/src/connectors/anthropic-connector.js +179 -0
  66. package/src/connectors/base-connector.js +94 -0
  67. package/src/connectors/cohere-connector.js +143 -0
  68. package/src/connectors/local-embedder.js +171 -0
  69. package/src/connectors/memory-retriever.js +150 -0
  70. package/src/connectors/ollama-connector.js +205 -0
  71. package/src/connectors/openai-connector.js +173 -0
  72. package/src/core/create-pipeline.js +21 -1
  73. package/src/core/observability/tracing.js +2 -1
  74. package/src/core/pipeline-factory.js +0 -1
  75. package/src/core/plugin-registry.js +14 -14
  76. package/src/cost/cost-calculator.js +146 -0
  77. package/src/cost/cost-tracker.js +260 -0
  78. package/src/cost/token-budget.js +245 -0
  79. package/src/dag/core/execution-scheduler.js +2 -1
  80. package/src/dag/core/topology-validation.js +0 -3
  81. package/src/dag/dag-engine.js +3 -5
  82. package/src/dag/dag-node.js +0 -1
  83. package/src/dag/enhanced-dag-engine.js +171 -99
  84. package/src/dashboard/dashboard-generator.js +642 -0
  85. package/src/dashboard/metrics-aggregator.js +409 -0
  86. package/src/debug/execution-tracer.js +210 -0
  87. package/src/debug/trace-inspector.js +281 -0
  88. package/src/dev/dev-server.js +5 -5
  89. package/src/dev/hot-reload.js +3 -2
  90. package/src/dx/integration-templates.js +0 -3
  91. package/src/ecosystem/plugin-analytics-dashboard.js +2 -2
  92. package/src/ecosystem/plugin-hub.js +2 -2
  93. package/src/enterprise/audit-logging.js +2 -2
  94. package/src/enterprise/sso-integration.js +52 -32
  95. package/src/evaluate/context-metrics.js +127 -0
  96. package/src/evaluate/evaluator.js +4 -2
  97. package/src/evaluate/faithfulness.js +117 -0
  98. package/src/evaluate/groundedness.js +60 -0
  99. package/src/evaluate/pipeline-evaluator.js +156 -0
  100. package/src/evaluate/relevance.js +129 -0
  101. package/src/evaluate/scoring.js +27 -0
  102. package/src/graph/entity-extractor.js +183 -0
  103. package/src/graph/graph-index.js +113 -0
  104. package/src/graph/graph-retriever.js +244 -0
  105. package/src/graph/knowledge-graph.js +256 -0
  106. package/src/guardrails/guardrails-pipeline.js +136 -0
  107. package/src/guardrails/post-generation-guard.js +170 -0
  108. package/src/guardrails/pre-retrieval-guard.js +161 -0
  109. package/src/guardrails/retrieval-guard.js +126 -0
  110. package/src/index.js +255 -9
  111. package/src/loader/html-loader.js +4 -15
  112. package/src/loader/markdown-loader.js +4 -15
  113. package/src/mcp/mcp-server.js +199 -0
  114. package/src/mcp/mcp-tool-builder.js +121 -0
  115. package/src/observability/audit-logger.js +0 -2
  116. package/src/observability/event-logger.js +2 -1
  117. package/src/observability/security-monitor.js +0 -2
  118. package/src/observability/telemetry.js +0 -2
  119. package/src/reranker/cascade-reranker.js +79 -0
  120. package/src/reranker/embedding-reranker.js +124 -0
  121. package/src/reranker/llm-reranker.js +6 -3
  122. package/src/reranker/scoring-reranker.js +123 -0
  123. package/src/retrieval/bm25-search.js +157 -0
  124. package/src/retrieval/hybrid-retriever.js +111 -0
  125. package/src/retrieval/rank-fusion.js +62 -0
  126. package/src/security/jwt-config.js +4 -3
  127. package/src/security/jwt-validator.js +2 -1
  128. package/src/security/plugin-sandbox.js +0 -3
  129. package/src/security/plugin-signature-verifier.js +4 -6
  130. package/src/security/validator.js +0 -2
  131. package/src/streaming/embedding-stream.js +197 -0
  132. package/src/streaming/sse-adapter.js +168 -0
  133. package/src/streaming/stream-router.js +210 -0
  134. package/src/streaming/streaming-embedder.js +231 -0
  135. package/src/streaming/websocket-adapter.js +171 -0
  136. package/src/templates/scaffold.js +147 -0
  137. package/src/templates/template-registry.js +272 -0
  138. package/src/utils/contract-validator.js +2 -1
  139. package/src/utils/input-sanitizer.js +80 -99
  140. package/src/utils/performance-monitor.js +0 -3
  141. package/src/utils/plugin-scaffolder.js +9 -16
  142. package/src/utils/rate-limiter.js +0 -1
  143. package/src/utils/retry-policy.js +0 -3
  144. package/src/utils/safe-expression-evaluator.js +0 -3
  145. package/src/utils/secure-logger-config.js +2 -4
  146. package/src/utils/secure-logging.js +0 -3
  147. package/src/utils/structured-logger.js +0 -3
  148. package/src/ai/index-fixed.js +0 -509
  149. package/src/ai/module-template.js +0 -37
  150. package/src/ci/workflow-analyzer.js +0 -71
  151. package/src/cli/plugin-marketplace-commands.js +0 -869
  152. package/src/dev/README.md +0 -126
  153. package/src/dev/examples/hot-reload-usage.js +0 -355
  154. package/src/mocks/openai-embedder.js +0 -119
  155. package/src/mocks/openai-llm.js +0 -77
  156. package/src/mocks/pdf-loader.js +0 -21
  157. package/src/mocks/pinecone-retriever.js +0 -106
  158. package/src/scripts/script-utilities.js +0 -36
  159. package/src/utils/ci/diagnostic-reporter.js +0 -57
  160. package/src/utils/examples/error-formatter-usage.js +0 -351
@@ -0,0 +1,642 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Dashboard Generator
5
+ *
6
+ * Generates standalone HTML dashboards and plain-text reports from
7
+ * pipeline metrics snapshots. The HTML output is fully self-contained
8
+ * with inline CSS and SVG charts -- no external dependencies required.
9
+ */
10
+
11
+ const { EventEmitter } = require('events');
12
+
13
+ /**
14
+ * @typedef {Object} DashboardGeneratorConfig
15
+ * @property {string} title - Dashboard title (default: 'RAG Pipeline Dashboard')
16
+ * @property {number} refreshInterval - Auto-refresh interval in ms (default: 5000)
17
+ * @property {'dark'|'light'} theme - Color theme (default: 'dark')
18
+ */
19
+ const DEFAULT_CONFIG = {
20
+ title: 'RAG Pipeline Dashboard',
21
+ refreshInterval: 5000,
22
+ theme: 'dark',
23
+ };
24
+
25
+ /**
26
+ * Generates standalone HTML dashboards from pipeline metrics snapshots.
27
+ * @extends EventEmitter
28
+ */
29
+ class DashboardGenerator extends EventEmitter {
30
+ /**
31
+ * @param {Partial<DashboardGeneratorConfig>} options
32
+ */
33
+ constructor(options = {}) {
34
+ super();
35
+ this.config = { ...DEFAULT_CONFIG, ...options };
36
+ }
37
+
38
+ /**
39
+ * Generate a complete standalone HTML dashboard page.
40
+ * @param {Object} metricsSnapshot - Snapshot from MetricsAggregator.getSnapshot()
41
+ * @returns {string} Complete HTML document string
42
+ */
43
+ generate(metricsSnapshot) {
44
+ const snapshot = metricsSnapshot || this._emptySnapshot();
45
+ const theme = this.config.theme || 'dark';
46
+ const styles = this._generateStyles(theme);
47
+
48
+ const cards = [
49
+ this._generateCard(
50
+ 'Total Queries',
51
+ this._formatNumber(snapshot.counters.queries),
52
+ `${this._formatNumber(snapshot.counters.embeddings)} embeddings | ${this._formatNumber(snapshot.counters.retrievals)} retrievals`,
53
+ '#4fc3f7',
54
+ ),
55
+ this._generateCard(
56
+ 'Avg Latency',
57
+ `${snapshot.latency.avg.toFixed(1)}ms`,
58
+ `p50: ${snapshot.latency.p50.toFixed(1)}ms | p95: ${snapshot.latency.p95.toFixed(1)}ms | p99: ${snapshot.latency.p99.toFixed(1)}ms`,
59
+ '#81c784',
60
+ ),
61
+ this._generateCard(
62
+ 'Error Rate',
63
+ `${(snapshot.errorRate * 100).toFixed(2)}%`,
64
+ `${this._formatNumber(snapshot.counters.errors)} total errors`,
65
+ snapshot.errorRate > 0.05 ? '#ef5350' : '#66bb6a',
66
+ ),
67
+ this._generateCard(
68
+ 'Total Cost',
69
+ `$${snapshot.cost.total.toFixed(4)}`,
70
+ `Last 24h: $${snapshot.cost.last24h.toFixed(4)} | Avg/query: $${snapshot.cost.avgPerQuery.toFixed(6)}`,
71
+ '#ffb74d',
72
+ ),
73
+ this._generateCard(
74
+ 'Memory Usage',
75
+ `${(snapshot.memory.current / 1024 / 1024).toFixed(1)} MB`,
76
+ `Peak: ${(snapshot.memory.peak / 1024 / 1024).toFixed(1)} MB | Trend: ${snapshot.memory.trend}`,
77
+ '#ce93d8',
78
+ ),
79
+ this._generateCard(
80
+ 'Throughput',
81
+ `${snapshot.throughput.queriesPerSec.toFixed(2)} q/s`,
82
+ `${snapshot.throughput.embeddingsPerSec.toFixed(2)} embeddings/s`,
83
+ '#4dd0e1',
84
+ ),
85
+ ];
86
+
87
+ const latencyData = [
88
+ { label: 'Min', value: snapshot.latency.min },
89
+ { label: 'p50', value: snapshot.latency.p50 },
90
+ { label: 'Avg', value: snapshot.latency.avg },
91
+ { label: 'p95', value: snapshot.latency.p95 },
92
+ { label: 'p99', value: snapshot.latency.p99 },
93
+ { label: 'Max', value: snapshot.latency.max },
94
+ ];
95
+ const chart = this._generateBarChart(latencyData, 600, 200);
96
+
97
+ const tableRows = [
98
+ {
99
+ label: 'Queries',
100
+ value: this._formatNumber(snapshot.counters.queries),
101
+ unit: 'count',
102
+ },
103
+ {
104
+ label: 'Embeddings',
105
+ value: this._formatNumber(snapshot.counters.embeddings),
106
+ unit: 'count',
107
+ },
108
+ {
109
+ label: 'Retrievals',
110
+ value: this._formatNumber(snapshot.counters.retrievals),
111
+ unit: 'count',
112
+ },
113
+ {
114
+ label: 'Generations',
115
+ value: this._formatNumber(snapshot.counters.generations),
116
+ unit: 'count',
117
+ },
118
+ {
119
+ label: 'Errors',
120
+ value: this._formatNumber(snapshot.counters.errors),
121
+ unit: 'count',
122
+ },
123
+ {
124
+ label: 'Error Rate',
125
+ value: (snapshot.errorRate * 100).toFixed(2),
126
+ unit: '%',
127
+ },
128
+ {
129
+ label: 'Avg Latency',
130
+ value: snapshot.latency.avg.toFixed(2),
131
+ unit: 'ms',
132
+ },
133
+ {
134
+ label: 'p50 Latency',
135
+ value: snapshot.latency.p50.toFixed(2),
136
+ unit: 'ms',
137
+ },
138
+ {
139
+ label: 'p95 Latency',
140
+ value: snapshot.latency.p95.toFixed(2),
141
+ unit: 'ms',
142
+ },
143
+ {
144
+ label: 'p99 Latency',
145
+ value: snapshot.latency.p99.toFixed(2),
146
+ unit: 'ms',
147
+ },
148
+ {
149
+ label: 'Min Latency',
150
+ value: snapshot.latency.min.toFixed(2),
151
+ unit: 'ms',
152
+ },
153
+ {
154
+ label: 'Max Latency',
155
+ value: snapshot.latency.max.toFixed(2),
156
+ unit: 'ms',
157
+ },
158
+ {
159
+ label: 'Total Cost',
160
+ value: snapshot.cost.total.toFixed(6),
161
+ unit: 'USD',
162
+ },
163
+ {
164
+ label: 'Cost (24h)',
165
+ value: snapshot.cost.last24h.toFixed(6),
166
+ unit: 'USD',
167
+ },
168
+ {
169
+ label: 'Avg Cost/Query',
170
+ value: snapshot.cost.avgPerQuery.toFixed(6),
171
+ unit: 'USD',
172
+ },
173
+ {
174
+ label: 'Memory (Current)',
175
+ value: (snapshot.memory.current / 1024 / 1024).toFixed(2),
176
+ unit: 'MB',
177
+ },
178
+ {
179
+ label: 'Memory (Peak)',
180
+ value: (snapshot.memory.peak / 1024 / 1024).toFixed(2),
181
+ unit: 'MB',
182
+ },
183
+ { label: 'Memory Trend', value: snapshot.memory.trend, unit: '' },
184
+ {
185
+ label: 'Throughput',
186
+ value: snapshot.throughput.queriesPerSec.toFixed(3),
187
+ unit: 'q/s',
188
+ },
189
+ { label: 'Uptime', value: this._formatUptime(snapshot.uptime), unit: '' },
190
+ ];
191
+ const table = this._generateTable(tableRows);
192
+
193
+ const timestamp = new Date(snapshot.timestamp).toISOString();
194
+
195
+ const html = `<!DOCTYPE html>
196
+ <html lang="en">
197
+ <head>
198
+ <meta charset="UTF-8">
199
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
200
+ <title>${this._escapeHtml(this.config.title)}</title>
201
+ <style>${styles}</style>
202
+ </head>
203
+ <body>
204
+ <header class="dashboard-header">
205
+ <h1>${this._escapeHtml(this.config.title)}</h1>
206
+ <span class="timestamp">Generated: ${timestamp}</span>
207
+ </header>
208
+
209
+ <main class="dashboard-main">
210
+ <section class="cards-grid">
211
+ ${cards.join('\n ')}
212
+ </section>
213
+
214
+ <section class="chart-section">
215
+ <h2>Latency Distribution</h2>
216
+ <div class="chart-container">
217
+ ${chart}
218
+ </div>
219
+ </section>
220
+
221
+ <section class="table-section">
222
+ <h2>Detailed Metrics</h2>
223
+ ${table}
224
+ </section>
225
+ </main>
226
+
227
+ <footer class="dashboard-footer">
228
+ <span>RAG Pipeline Utils &mdash; Performance Dashboard</span>
229
+ <span>Snapshot: ${timestamp}</span>
230
+ </footer>
231
+ </body>
232
+ </html>`;
233
+
234
+ this.emit('generated', { html, timestamp: snapshot.timestamp });
235
+ return html;
236
+ }
237
+
238
+ /**
239
+ * Generate a single metric card HTML snippet.
240
+ * @param {string} title - Card title
241
+ * @param {string} value - Primary display value
242
+ * @param {string} subtitle - Secondary info line
243
+ * @param {string} color - Accent color (CSS value)
244
+ * @returns {string} HTML string
245
+ */
246
+ _generateCard(title, value, subtitle, color) {
247
+ return `<div class="metric-card" style="border-top: 3px solid ${color};">
248
+ <div class="card-title">${this._escapeHtml(title)}</div>
249
+ <div class="card-value" style="color: ${color};">${this._escapeHtml(value)}</div>
250
+ <div class="card-subtitle">${this._escapeHtml(subtitle)}</div>
251
+ </div>`;
252
+ }
253
+
254
+ /**
255
+ * Generate an inline SVG bar chart.
256
+ * @param {Array<{label: string, value: number}>} data - Chart data points
257
+ * @param {number} width - SVG width in pixels
258
+ * @param {number} height - SVG height in pixels
259
+ * @returns {string} SVG markup string
260
+ */
261
+ _generateBarChart(data, width, height) {
262
+ if (!data || data.length === 0) {
263
+ return '<svg></svg>';
264
+ }
265
+
266
+ const maxVal = Math.max(...data.map((d) => d.value), 1);
267
+ const padding = { top: 20, right: 20, bottom: 40, left: 60 };
268
+ const chartWidth = width - padding.left - padding.right;
269
+ const chartHeight = height - padding.top - padding.bottom;
270
+ const barWidth = Math.floor(chartWidth / data.length) - 4;
271
+ const colors = [
272
+ '#4fc3f7',
273
+ '#81c784',
274
+ '#ffb74d',
275
+ '#ef5350',
276
+ '#ce93d8',
277
+ '#4dd0e1',
278
+ ];
279
+
280
+ let bars = '';
281
+ let labels = '';
282
+ let valueLabels = '';
283
+
284
+ data.forEach((d, i) => {
285
+ const barHeight = maxVal > 0 ? (d.value / maxVal) * chartHeight : 0;
286
+ const x = padding.left + i * (barWidth + 4) + 2;
287
+ const y = padding.top + chartHeight - barHeight;
288
+ const color = colors[i % colors.length];
289
+
290
+ bars += `<rect x="${x}" y="${y}" width="${barWidth}" height="${barHeight}" fill="${color}" rx="2" />`;
291
+ labels += `<text x="${x + barWidth / 2}" y="${height - 8}" text-anchor="middle" class="chart-label">${this._escapeHtml(d.label)}</text>`;
292
+
293
+ if (d.value > 0) {
294
+ valueLabels += `<text x="${x + barWidth / 2}" y="${y - 4}" text-anchor="middle" class="chart-value">${d.value.toFixed(1)}</text>`;
295
+ }
296
+ });
297
+
298
+ // Y-axis grid lines
299
+ let gridLines = '';
300
+ const gridCount = 4;
301
+ for (let i = 0; i <= gridCount; i++) {
302
+ const y = padding.top + (chartHeight / gridCount) * i;
303
+ const val = maxVal - (maxVal / gridCount) * i;
304
+ gridLines += `<line x1="${padding.left}" y1="${y}" x2="${width - padding.right}" y2="${y}" class="grid-line" />`;
305
+ gridLines += `<text x="${padding.left - 8}" y="${y + 4}" text-anchor="end" class="axis-label">${val.toFixed(0)}</text>`;
306
+ }
307
+
308
+ return `<svg viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg" class="bar-chart">
309
+ <style>
310
+ .chart-label { font-family: monospace; font-size: 11px; fill: #aaa; }
311
+ .chart-value { font-family: monospace; font-size: 10px; fill: #ddd; }
312
+ .axis-label { font-family: monospace; font-size: 10px; fill: #888; }
313
+ .grid-line { stroke: #333; stroke-width: 0.5; stroke-dasharray: 3,3; }
314
+ </style>
315
+ ${gridLines}
316
+ ${bars}
317
+ ${labels}
318
+ ${valueLabels}
319
+ <text x="${width / 2}" y="${height - 24}" text-anchor="middle" class="chart-label" style="font-size: 9px;">Latency (ms)</text>
320
+ </svg>`;
321
+ }
322
+
323
+ /**
324
+ * Generate CSS styles for the specified theme.
325
+ * @param {'dark'|'light'} theme
326
+ * @returns {string} CSS string
327
+ */
328
+ _generateStyles(theme) {
329
+ const isDark = theme === 'dark';
330
+ const bg = isDark ? '#1a1a2e' : '#f5f5f5';
331
+ const cardBg = isDark ? '#16213e' : '#ffffff';
332
+ const textPrimary = isDark ? '#e0e0e0' : '#212121';
333
+ const textSecondary = isDark ? '#9e9e9e' : '#757575';
334
+ const headerBg = isDark ? '#0f3460' : '#1565c0';
335
+ const footerBg = isDark ? '#0a0a1a' : '#e0e0e0';
336
+ const footerText = isDark ? '#666' : '#999';
337
+ const tableBorder = isDark ? '#2a2a4a' : '#e0e0e0';
338
+ const tableStripe = isDark ? '#1e1e3a' : '#fafafa';
339
+
340
+ return `
341
+ * { margin: 0; padding: 0; box-sizing: border-box; }
342
+ body {
343
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
344
+ background: ${bg};
345
+ color: ${textPrimary};
346
+ min-height: 100vh;
347
+ display: flex;
348
+ flex-direction: column;
349
+ }
350
+ .dashboard-header {
351
+ background: ${headerBg};
352
+ color: #fff;
353
+ padding: 20px 32px;
354
+ display: flex;
355
+ justify-content: space-between;
356
+ align-items: center;
357
+ flex-wrap: wrap;
358
+ gap: 12px;
359
+ }
360
+ .dashboard-header h1 {
361
+ font-size: 22px;
362
+ font-weight: 600;
363
+ letter-spacing: 0.5px;
364
+ }
365
+ .timestamp {
366
+ font-family: 'Courier New', monospace;
367
+ font-size: 13px;
368
+ opacity: 0.8;
369
+ }
370
+ .dashboard-main {
371
+ flex: 1;
372
+ padding: 24px 32px;
373
+ max-width: 1200px;
374
+ margin: 0 auto;
375
+ width: 100%;
376
+ }
377
+ .cards-grid {
378
+ display: grid;
379
+ grid-template-columns: repeat(3, 1fr);
380
+ gap: 16px;
381
+ margin-bottom: 32px;
382
+ }
383
+ @media (max-width: 900px) {
384
+ .cards-grid { grid-template-columns: repeat(2, 1fr); }
385
+ }
386
+ @media (max-width: 600px) {
387
+ .cards-grid { grid-template-columns: 1fr; }
388
+ .dashboard-main { padding: 16px; }
389
+ }
390
+ .metric-card {
391
+ background: ${cardBg};
392
+ border-radius: 8px;
393
+ padding: 20px;
394
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
395
+ transition: transform 0.15s ease;
396
+ }
397
+ .metric-card:hover {
398
+ transform: translateY(-2px);
399
+ }
400
+ .card-title {
401
+ font-size: 12px;
402
+ text-transform: uppercase;
403
+ letter-spacing: 1px;
404
+ color: ${textSecondary};
405
+ margin-bottom: 8px;
406
+ }
407
+ .card-value {
408
+ font-family: 'Courier New', monospace;
409
+ font-size: 28px;
410
+ font-weight: 700;
411
+ margin-bottom: 6px;
412
+ }
413
+ .card-subtitle {
414
+ font-size: 11px;
415
+ color: ${textSecondary};
416
+ font-family: 'Courier New', monospace;
417
+ }
418
+ .chart-section, .table-section {
419
+ margin-bottom: 32px;
420
+ }
421
+ .chart-section h2, .table-section h2 {
422
+ font-size: 16px;
423
+ font-weight: 600;
424
+ margin-bottom: 16px;
425
+ color: ${textSecondary};
426
+ text-transform: uppercase;
427
+ letter-spacing: 1px;
428
+ }
429
+ .chart-container {
430
+ background: ${cardBg};
431
+ border-radius: 8px;
432
+ padding: 20px;
433
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
434
+ overflow: hidden;
435
+ }
436
+ .bar-chart {
437
+ width: 100%;
438
+ height: auto;
439
+ max-width: 600px;
440
+ }
441
+ .metrics-table {
442
+ width: 100%;
443
+ border-collapse: collapse;
444
+ background: ${cardBg};
445
+ border-radius: 8px;
446
+ overflow: hidden;
447
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
448
+ }
449
+ .metrics-table th,
450
+ .metrics-table td {
451
+ padding: 10px 16px;
452
+ text-align: left;
453
+ border-bottom: 1px solid ${tableBorder};
454
+ }
455
+ .metrics-table th {
456
+ background: ${isDark ? '#1e1e3e' : '#eeeeee'};
457
+ font-size: 11px;
458
+ text-transform: uppercase;
459
+ letter-spacing: 1px;
460
+ color: ${textSecondary};
461
+ font-weight: 600;
462
+ }
463
+ .metrics-table td {
464
+ font-family: 'Courier New', monospace;
465
+ font-size: 13px;
466
+ }
467
+ .metrics-table tr:nth-child(even) td {
468
+ background: ${tableStripe};
469
+ }
470
+ .metrics-table tr:last-child td {
471
+ border-bottom: none;
472
+ }
473
+ .dashboard-footer {
474
+ background: ${footerBg};
475
+ color: ${footerText};
476
+ padding: 12px 32px;
477
+ font-size: 11px;
478
+ display: flex;
479
+ justify-content: space-between;
480
+ font-family: 'Courier New', monospace;
481
+ }
482
+ `;
483
+ }
484
+
485
+ /**
486
+ * Generate an HTML table for detailed metrics.
487
+ * @param {Array<{label: string, value: string|number, unit: string}>} rows
488
+ * @returns {string} HTML table string
489
+ */
490
+ _generateTable(rows) {
491
+ const rowsHtml = rows
492
+ .map(
493
+ (r) =>
494
+ `<tr><td>${this._escapeHtml(r.label)}</td><td>${this._escapeHtml(String(r.value))}</td><td>${this._escapeHtml(r.unit)}</td></tr>`,
495
+ )
496
+ .join('\n ');
497
+
498
+ return `<table class="metrics-table">
499
+ <thead>
500
+ <tr><th>Metric</th><th>Value</th><th>Unit</th></tr>
501
+ </thead>
502
+ <tbody>
503
+ ${rowsHtml}
504
+ </tbody>
505
+ </table>`;
506
+ }
507
+
508
+ /**
509
+ * Generate a plain-text summary report (for CLI/logs).
510
+ * @param {Object} metricsSnapshot - Snapshot from MetricsAggregator.getSnapshot()
511
+ * @returns {string} Formatted text report
512
+ */
513
+ generateReport(metricsSnapshot) {
514
+ const s = metricsSnapshot || this._emptySnapshot();
515
+ const divider = '='.repeat(52);
516
+ const line = '-'.repeat(52);
517
+
518
+ const lines = [
519
+ divider,
520
+ ` ${this.config.title} - Report`,
521
+ ` ${new Date(s.timestamp).toISOString()}`,
522
+ divider,
523
+ '',
524
+ ' COUNTERS',
525
+ line,
526
+ ` Queries: ${this._padLeft(s.counters.queries, 12)}`,
527
+ ` Embeddings: ${this._padLeft(s.counters.embeddings, 12)}`,
528
+ ` Retrievals: ${this._padLeft(s.counters.retrievals, 12)}`,
529
+ ` Generations: ${this._padLeft(s.counters.generations, 12)}`,
530
+ ` Errors: ${this._padLeft(s.counters.errors, 12)}`,
531
+ '',
532
+ ' LATENCY (ms)',
533
+ line,
534
+ ` Average: ${this._padLeft(s.latency.avg.toFixed(2), 12)}`,
535
+ ` p50: ${this._padLeft(s.latency.p50.toFixed(2), 12)}`,
536
+ ` p95: ${this._padLeft(s.latency.p95.toFixed(2), 12)}`,
537
+ ` p99: ${this._padLeft(s.latency.p99.toFixed(2), 12)}`,
538
+ ` Min: ${this._padLeft(s.latency.min.toFixed(2), 12)}`,
539
+ ` Max: ${this._padLeft(s.latency.max.toFixed(2), 12)}`,
540
+ '',
541
+ ' COST (USD)',
542
+ line,
543
+ ` Total: ${this._padLeft('$' + s.cost.total.toFixed(6), 12)}`,
544
+ ` Last 24h: ${this._padLeft('$' + s.cost.last24h.toFixed(6), 12)}`,
545
+ ` Avg/Query: ${this._padLeft('$' + s.cost.avgPerQuery.toFixed(6), 12)}`,
546
+ '',
547
+ ' SYSTEM',
548
+ line,
549
+ ` Memory: ${this._padLeft((s.memory.current / 1024 / 1024).toFixed(2) + ' MB', 12)}`,
550
+ ` Memory Peak: ${this._padLeft((s.memory.peak / 1024 / 1024).toFixed(2) + ' MB', 12)}`,
551
+ ` Memory Trend: ${this._padLeft(s.memory.trend, 12)}`,
552
+ ` Throughput: ${this._padLeft(s.throughput.queriesPerSec.toFixed(3) + ' q/s', 12)}`,
553
+ ` Error Rate: ${this._padLeft((s.errorRate * 100).toFixed(2) + '%', 12)}`,
554
+ ` Uptime: ${this._padLeft(this._formatUptime(s.uptime), 12)}`,
555
+ '',
556
+ divider,
557
+ ];
558
+
559
+ const report = lines.join('\n');
560
+ this.emit('report', { report, timestamp: s.timestamp });
561
+ return report;
562
+ }
563
+
564
+ /**
565
+ * Return an empty metrics snapshot with zeroed values.
566
+ * @returns {Object}
567
+ * @private
568
+ */
569
+ _emptySnapshot() {
570
+ return {
571
+ counters: {
572
+ queries: 0,
573
+ embeddings: 0,
574
+ retrievals: 0,
575
+ generations: 0,
576
+ errors: 0,
577
+ },
578
+ latency: { avg: 0, p50: 0, p95: 0, p99: 0, min: 0, max: 0 },
579
+ throughput: { queriesPerSec: 0, embeddingsPerSec: 0 },
580
+ cost: { total: 0, last24h: 0, avgPerQuery: 0 },
581
+ memory: { current: 0, peak: 0, trend: 'stable' },
582
+ errorRate: 0,
583
+ uptime: 0,
584
+ timestamp: Date.now(),
585
+ };
586
+ }
587
+
588
+ /**
589
+ * Escape HTML special characters.
590
+ * @param {string} str
591
+ * @returns {string}
592
+ * @private
593
+ */
594
+ _escapeHtml(str) {
595
+ return String(str)
596
+ .replace(/&/g, '&amp;')
597
+ .replace(/</g, '&lt;')
598
+ .replace(/>/g, '&gt;')
599
+ .replace(/"/g, '&quot;');
600
+ }
601
+
602
+ /**
603
+ * Format a number with locale-aware separators.
604
+ * @param {number} n
605
+ * @returns {string}
606
+ * @private
607
+ */
608
+ _formatNumber(n) {
609
+ return Number(n).toLocaleString('en-US');
610
+ }
611
+
612
+ /**
613
+ * Format uptime duration to human-readable string.
614
+ * @param {number} ms - Uptime in milliseconds
615
+ * @returns {string}
616
+ * @private
617
+ */
618
+ _formatUptime(ms) {
619
+ const seconds = Math.floor(ms / 1000);
620
+ const minutes = Math.floor(seconds / 60);
621
+ const hours = Math.floor(minutes / 60);
622
+ const days = Math.floor(hours / 24);
623
+
624
+ if (days > 0) return `${days}d ${hours % 24}h ${minutes % 60}m`;
625
+ if (hours > 0) return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
626
+ if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
627
+ return `${seconds}s`;
628
+ }
629
+
630
+ /**
631
+ * Right-align a value within a given width.
632
+ * @param {string|number} val
633
+ * @param {number} width
634
+ * @returns {string}
635
+ * @private
636
+ */
637
+ _padLeft(val, width) {
638
+ return String(val).padStart(width);
639
+ }
640
+ }
641
+
642
+ module.exports = { DashboardGenerator, DEFAULT_CONFIG };