@hongmaple0820/scale-engine 0.50.1 → 0.50.2

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 (50) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/api/http.js +3 -1
  4. package/dist/api/http.js.map +1 -1
  5. package/dist/cli/cortexCommands.d.ts +16 -0
  6. package/dist/cli/cortexCommands.js +47 -4
  7. package/dist/cli/cortexCommands.js.map +1 -1
  8. package/dist/cortex/InstinctStore.d.ts +13 -1
  9. package/dist/cortex/InstinctStore.js +90 -11
  10. package/dist/cortex/InstinctStore.js.map +1 -1
  11. package/dist/cortex/SessionInjector.js +39 -2
  12. package/dist/cortex/SessionInjector.js.map +1 -1
  13. package/dist/dashboard/DashboardServer.d.ts +158 -0
  14. package/dist/dashboard/DashboardServer.js +753 -13
  15. package/dist/dashboard/DashboardServer.js.map +1 -1
  16. package/dist/dashboard/spa/assets/index-VYBCLBje.js +11 -0
  17. package/dist/dashboard/spa/assets/index-VhwY_ac1.css +1 -0
  18. package/dist/dashboard/spa/assets/naive-ui-BQy2AJkt.js +3340 -0
  19. package/dist/dashboard/spa/assets/vendor-BPU6aOYA.js +3 -0
  20. package/dist/dashboard/spa/assets/vue-CQQMb5Wi.js +17 -0
  21. package/dist/dashboard/spa/index.html +15 -462
  22. package/dist/memory/MemoryFabric.d.ts +13 -1
  23. package/dist/memory/MemoryFabric.js +60 -0
  24. package/dist/memory/MemoryFabric.js.map +1 -1
  25. package/dist/version.d.ts +1 -1
  26. package/dist/version.js +1 -1
  27. package/docs/workflow/ASSESSMENT_INDEX.md +326 -0
  28. package/docs/workflow/COMPARATIVE_ANALYSIS.md +422 -0
  29. package/docs/workflow/EXECUTIVE_SUMMARY.md +310 -0
  30. package/docs/workflow/IMPROVEMENT_CHECKLIST.md +518 -0
  31. package/docs/workflow/IMPROVEMENT_ROADMAP.md +707 -0
  32. package/docs/workflow/README.md +8 -0
  33. package/package.json +6 -2
  34. package/dist/dashboard/spa/app.js +0 -515
  35. package/dist/dashboard/spa/components/DataTable.js +0 -53
  36. package/dist/dashboard/spa/components/EventStream.js +0 -66
  37. package/dist/dashboard/spa/components/LoadingState.js +0 -39
  38. package/dist/dashboard/spa/components/MetricCard.js +0 -30
  39. package/dist/dashboard/spa/components/Panel.js +0 -27
  40. package/dist/dashboard/spa/components/StatusBadge.js +0 -51
  41. package/dist/dashboard/spa/i18n.js +0 -767
  42. package/dist/dashboard/spa/pages/costs.js +0 -522
  43. package/dist/dashboard/spa/pages/documents.js +0 -540
  44. package/dist/dashboard/spa/pages/knowledge.js +0 -457
  45. package/dist/dashboard/spa/pages/monitoring.js +0 -361
  46. package/dist/dashboard/spa/pages/overview.js +0 -301
  47. package/dist/dashboard/spa/pages/topology-renderers.js +0 -251
  48. package/dist/dashboard/spa/pages/topology.js +0 -370
  49. package/dist/dashboard/spa/pages/workflow-renderers.js +0 -239
  50. package/dist/dashboard/spa/pages/workflow.js +0 -217
@@ -1,522 +0,0 @@
1
- /**
2
- * Costs Page v2 — Model comparison radar, efficiency gauge, token waterfall, optimization tips
3
- */
4
- ;(() => {
5
- 'use strict'
6
-
7
- const { fetchJSON, formatNumber, registerChart, getTheme, t, $, $$, dom } = window.Dashboard
8
- const { chartContainer, dataTable, el, emptyState, metricCard, panel, renderText } = dom
9
-
10
- // Model pricing (USD per 1M tokens, approximate)
11
- const MODEL_PRICING = {
12
- 'claude-opus-4': { input: 15, output: 75 },
13
- 'claude-sonnet-4': { input: 3, output: 15 },
14
- 'claude-haiku-3.5': { input: 0.80, output: 4 },
15
- 'gpt-4o': { input: 2.50, output: 10 },
16
- 'gpt-4o-mini': { input: 0.15, output: 0.60 },
17
- 'default': { input: 3, output: 15 },
18
- }
19
-
20
- async function renderCosts() {
21
- const app = $('#app')
22
- app.replaceChildren(
23
- el('div', { className: 'metrics-row', id: 'cost-metrics' }),
24
- el('div', { className: 'tabs', id: 'cost-tabs' }, [
25
- el('div', { className: 'tab active', text: t('costs.overview'), dataset: { tab: 'overview' } }),
26
- el('div', { className: 'tab', text: t('costs.modelComparison'), dataset: { tab: 'models' } }),
27
- el('div', { className: 'tab', text: t('costs.optimization'), dataset: { tab: 'optimization' } }),
28
- ]),
29
- el('div', { id: 'cost-content' })
30
- )
31
-
32
- const metrics = await fetchJSON('/api/metrics')
33
- renderCostMetrics(metrics)
34
-
35
- let currentTab = 'overview'
36
- $('#cost-tabs').addEventListener('click', (e) => {
37
- const tab = e.target.dataset?.tab
38
- if (!tab) return
39
- currentTab = tab
40
- $$('#cost-tabs .tab').forEach(el => el.classList.toggle('active', el.dataset.tab === tab))
41
- renderCostTab(tab, metrics)
42
- })
43
-
44
- renderCostTab('overview', metrics)
45
- }
46
-
47
- function renderCostMetrics(metrics) {
48
- const container = $('#cost-metrics')
49
- const cmd = metrics?.commandRuns
50
- const model = metrics?.modelUsage
51
-
52
- const totalTokens = (model?.totalInputTokens ?? 0) + (model?.totalOutputTokens ?? 0)
53
- const savedTokens = cmd?.savedEstimatedTokens ?? 0
54
- const efficiency = cmd?.rawEstimatedTokens > 0
55
- ? ((savedTokens / cmd.rawEstimatedTokens) * 100).toFixed(1) + '%'
56
- : '0%'
57
- const estimatedCost = estimateCost(model)
58
-
59
- const cards = [
60
- { label: t('costs.totalTokens'), value: formatNumber(totalTokens), cls: '' },
61
- { label: t('costs.rawEstTokens'), value: formatNumber(cmd?.rawEstimatedTokens ?? 0), cls: '' },
62
- { label: t('costs.tokensSaved'), value: formatNumber(savedTokens), cls: 'accent' },
63
- { label: t('costs.compressionRate'), value: efficiency, cls: 'accent' },
64
- { label: t('costs.estCost'), value: '$' + estimatedCost.toFixed(2), cls: '' },
65
- ]
66
- container.replaceChildren(...cards.map(c => metricCard(c.label, c.value, c.cls)))
67
- }
68
-
69
- function renderCostTab(tab, metrics) {
70
- const container = $('#cost-content')
71
- switch (tab) {
72
- case 'overview': renderOverview(container, metrics); break
73
- case 'models': renderModelComparison(container, metrics); break
74
- case 'optimization': renderOptimization(container, metrics); break
75
- }
76
- }
77
-
78
- // ── Overview ───────────────────────────────────────────────────────
79
-
80
- function renderOverview(container, metrics) {
81
- const cmd = metrics?.commandRuns
82
-
83
- container.replaceChildren(
84
- el('div', { className: 'grid-2 mb-24' }, [
85
- chartContainer(t('costs.tokenUsage'), 'cost-token-chart'),
86
- chartContainer(t('costs.compressionEfficiency'), 'cost-gauge'),
87
- ]),
88
- el('div', { className: 'grid-2' }, [
89
- chartContainer(t('costs.tokenWaterfall'), 'cost-waterfall'),
90
- panel(t('costs.costBreakdownByModel'), 'cost-model-table'),
91
- ])
92
- )
93
-
94
- renderTokenChart(metrics)
95
- renderEfficiencyGauge(metrics)
96
- renderWaterfall(metrics)
97
- renderModelTable(metrics)
98
- }
99
-
100
- function renderTokenChart(metrics) {
101
- const el = $('#cost-token-chart')
102
- const cmd = metrics?.commandRuns
103
- if (!cmd || cmd.total === 0) {
104
- el.replaceChildren(emptyState(t('costs.noTokenData')))
105
- return
106
- }
107
-
108
- const chart = echarts.init(el, getTheme() === 'dark' ? 'dark' : null)
109
- registerChart(chart)
110
-
111
- chart.setOption({
112
- tooltip: { trigger: 'axis', formatter: (p) => p.map(s => `${s.seriesName}: ${formatNumber(s.value)}`).join('<br/>') },
113
- legend: { textStyle: { color: '#a1a1a1' }, bottom: 0 },
114
- grid: { left: 80, right: 20, top: 20, bottom: 40 },
115
- xAxis: {
116
- type: 'category',
117
- data: [t('costs.rawEst'), t('costs.afterCompression'), t('costs.saved')],
118
- axisLabel: { color: '#a1a1a1' },
119
- },
120
- yAxis: {
121
- type: 'value',
122
- axisLabel: { color: '#a1a1a1', formatter: (v) => formatNumber(v) },
123
- splitLine: { lineStyle: { color: '#2a2a2a' } },
124
- },
125
- series: [{
126
- name: t('costs.tokens'),
127
- type: 'bar', barWidth: 50,
128
- data: [
129
- { value: cmd.rawEstimatedTokens, itemStyle: { color: '#5588ff', borderRadius: [4, 4, 0, 0] } },
130
- { value: cmd.compressedEstimatedTokens, itemStyle: { color: '#ffaa00', borderRadius: [4, 4, 0, 0] } },
131
- { value: cmd.savedEstimatedTokens, itemStyle: { color: '#00dc82', borderRadius: [4, 4, 0, 0] } },
132
- ],
133
- label: {
134
- show: true, position: 'top',
135
- formatter: (p) => formatNumber(p.value),
136
- color: '#a1a1a1', fontSize: 11,
137
- },
138
- }],
139
- })
140
- }
141
-
142
- function renderEfficiencyGauge(metrics) {
143
- const el = $('#cost-gauge')
144
- const cmd = metrics?.commandRuns
145
- if (!cmd || cmd.rawEstimatedTokens === 0) {
146
- el.replaceChildren(emptyState(t('common.noData')))
147
- return
148
- }
149
-
150
- const efficiency = (cmd.savedEstimatedTokens / cmd.rawEstimatedTokens) * 100
151
- const chart = echarts.init(el, getTheme() === 'dark' ? 'dark' : null)
152
- registerChart(chart)
153
-
154
- chart.setOption({
155
- series: [{
156
- type: 'gauge',
157
- startAngle: 200, endAngle: -20,
158
- min: 0, max: 100,
159
- progress: { show: true, width: 18, itemStyle: { color: '#00dc82' } },
160
- axisLine: { lineStyle: { width: 18, color: [[1, '#2a2a2a']] } },
161
- axisTick: { show: false },
162
- splitLine: { show: false },
163
- axisLabel: { show: false },
164
- pointer: { show: false },
165
- title: { show: true, offsetCenter: [0, '30%'], color: '#a1a1a1', fontSize: 13 },
166
- detail: {
167
- valueAnimation: true, fontSize: 32, fontWeight: 'bold',
168
- color: '#00dc82', offsetCenter: [0, '-10%'],
169
- formatter: '{value}%',
170
- },
171
- data: [{ value: efficiency.toFixed(1), name: t('costs.compressionEfficiency') }],
172
- }],
173
- })
174
- }
175
-
176
- function renderWaterfall(metrics) {
177
- const el = $('#cost-waterfall')
178
- const cmd = metrics?.commandRuns
179
- const model = metrics?.modelUsage
180
- if (!cmd || !model) {
181
- el.replaceChildren(emptyState(t('common.noData')))
182
- return
183
- }
184
-
185
- const chart = echarts.init(el, getTheme() === 'dark' ? 'dark' : null)
186
- registerChart(chart)
187
-
188
- const inputTokens = model.totalInputTokens ?? 0
189
- const outputTokens = model.totalOutputTokens ?? 0
190
-
191
- chart.setOption({
192
- tooltip: { trigger: 'axis' },
193
- grid: { left: 80, right: 20, top: 20, bottom: 30 },
194
- xAxis: {
195
- type: 'category',
196
- data: [t('costs.input'), t('costs.output'), t('costs.total'), t('costs.compressed'), t('costs.saved')],
197
- axisLabel: { color: '#a1a1a1' },
198
- },
199
- yAxis: {
200
- type: 'value',
201
- axisLabel: { color: '#a1a1a1', formatter: (v) => formatNumber(v) },
202
- splitLine: { lineStyle: { color: '#2a2a2a' } },
203
- },
204
- series: [{
205
- type: 'bar', barWidth: 40,
206
- data: [
207
- { value: inputTokens, itemStyle: { color: '#5588ff', borderRadius: [4, 4, 0, 0] } },
208
- { value: outputTokens, itemStyle: { color: '#aa88ff', borderRadius: [4, 4, 0, 0] } },
209
- { value: inputTokens + outputTokens, itemStyle: { color: '#888', borderRadius: [4, 4, 0, 0] } },
210
- { value: cmd.compressedEstimatedTokens, itemStyle: { color: '#ffaa00', borderRadius: [4, 4, 0, 0] } },
211
- { value: cmd.savedEstimatedTokens, itemStyle: { color: '#00dc82', borderRadius: [4, 4, 0, 0] } },
212
- ],
213
- label: { show: true, position: 'top', formatter: (p) => formatNumber(p.value), color: '#a1a1a1', fontSize: 10 },
214
- }],
215
- })
216
- }
217
-
218
- function renderModelTable(metrics) {
219
- const container = $('#cost-model-table')
220
- const model = metrics?.modelUsage
221
- if (!model?.models?.length) {
222
- renderText(container, t('costs.noModelUsage'))
223
- return
224
- }
225
-
226
- const rows = model.models.map(m => {
227
- const cost = estimateModelCost(m.model, m.inputTokens, m.outputTokens)
228
- return el('tr', {}, [
229
- el('td', { text: m.model, style: { fontWeight: '500' } }),
230
- el('td', { text: formatNumber(m.inputTokens) }),
231
- el('td', { text: formatNumber(m.outputTokens) }),
232
- el('td', { text: m.requestCount }),
233
- el('td', { text: '$' + cost.toFixed(2), style: { color: '#00dc82' } }),
234
- ])
235
- })
236
- container.replaceChildren(dataTable([
237
- t('costs.model'),
238
- t('costs.inputTokens'),
239
- t('costs.outputTokens'),
240
- t('costs.requests'),
241
- t('costs.estCost'),
242
- ], rows))
243
- }
244
-
245
- // ── Model Comparison ───────────────────────────────────────────────
246
-
247
- function renderModelComparison(container, metrics) {
248
- const model = metrics?.modelUsage
249
- if (!model?.models?.length) {
250
- container.replaceChildren(emptyState(t('costs.noModelData')))
251
- return
252
- }
253
-
254
- container.replaceChildren(
255
- el('div', { className: 'grid-2 mb-24' }, [
256
- chartContainer(t('costs.modelUsageRadar'), 'cost-radar'),
257
- chartContainer(t('costs.tokenDistByModel'), 'cost-model-pie'),
258
- ]),
259
- chartContainer(t('costs.costPerRequest'), 'cost-per-req')
260
- )
261
-
262
- // Radar
263
- const radarEl = $('#cost-radar')
264
- if (radarEl) {
265
- const chart = echarts.init(radarEl, getTheme() === 'dark' ? 'dark' : null)
266
- registerChart(chart)
267
- const models = model.models.slice(0, 5)
268
- const maxInput = Math.max(...models.map(m => m.inputTokens), 1)
269
- const maxOutput = Math.max(...models.map(m => m.outputTokens), 1)
270
- const maxReqs = Math.max(...models.map(m => m.requestCount), 1)
271
-
272
- chart.setOption({
273
- tooltip: {},
274
- legend: { data: models.map(m => m.model), textStyle: { color: '#a1a1a1', fontSize: 10 }, bottom: 0 },
275
- radar: {
276
- indicator: [
277
- { name: t('costs.inputTokens'), max: maxInput },
278
- { name: t('costs.outputTokens'), max: maxOutput },
279
- { name: t('costs.requests'), max: maxReqs },
280
- { name: t('costs.estCost'), max: Math.max(...models.map(m => estimateModelCost(m.model, m.inputTokens, m.outputTokens)), 0.01) },
281
- ],
282
- axisName: { color: '#a1a1a1', fontSize: 10 },
283
- },
284
- series: [{
285
- type: 'radar',
286
- data: models.map((m, i) => ({
287
- name: m.model,
288
- value: [
289
- m.inputTokens, m.outputTokens, m.requestCount,
290
- estimateModelCost(m.model, m.inputTokens, m.outputTokens),
291
- ],
292
- areaStyle: { opacity: 0.1 },
293
- })),
294
- }],
295
- })
296
- }
297
-
298
- // Pie
299
- const pieEl = $('#cost-model-pie')
300
- if (pieEl) {
301
- const chart = echarts.init(pieEl, getTheme() === 'dark' ? 'dark' : null)
302
- registerChart(chart)
303
- chart.setOption({
304
- tooltip: { trigger: 'item' },
305
- series: [{
306
- type: 'pie', radius: ['35%', '65%'],
307
- label: { color: '#a1a1a1', formatter: '{b}\n{d}%' },
308
- data: model.models.map(m => ({
309
- name: m.model,
310
- value: m.inputTokens + m.outputTokens,
311
- })),
312
- }],
313
- })
314
- }
315
-
316
- // Cost per request
317
- const cprEl = $('#cost-per-req')
318
- if (cprEl) {
319
- const chart = echarts.init(cprEl, getTheme() === 'dark' ? 'dark' : null)
320
- registerChart(chart)
321
- const modelsWithCost = model.models.map(m => ({
322
- name: m.model,
323
- cost: m.requestCount > 0 ? estimateModelCost(m.model, m.inputTokens, m.outputTokens) / m.requestCount : 0,
324
- })).sort((a, b) => b.cost - a.cost)
325
-
326
- chart.setOption({
327
- tooltip: { trigger: 'axis' },
328
- grid: { left: 150, right: 40, top: 10, bottom: 30 },
329
- xAxis: { type: 'value', axisLabel: { color: '#a1a1a1', formatter: (v) => '$' + v.toFixed(3) }, splitLine: { lineStyle: { color: '#2a2a2a' } } },
330
- yAxis: { type: 'category', data: modelsWithCost.map(m => m.name), axisLabel: { color: '#a1a1a1', fontSize: 11 } },
331
- series: [{
332
- type: 'bar',
333
- data: modelsWithCost.map(m => ({
334
- value: m.cost,
335
- itemStyle: { color: '#5588ff', borderRadius: [0, 4, 4, 0] },
336
- })),
337
- barWidth: 20,
338
- label: { show: true, position: 'right', formatter: (p) => '$' + p.value.toFixed(4), color: '#a1a1a1', fontSize: 10 },
339
- }],
340
- })
341
- }
342
- }
343
-
344
- // ── Optimization ───────────────────────────────────────────────────
345
-
346
- function renderOptimization(container, metrics) {
347
- const tips = generateOptimizationTips(metrics)
348
- const score = getOptimizationScore(metrics)
349
- const recommendationsTitle = el('div', { className: 'panel-title' }, [
350
- document.createTextNode(t('costs.recommendations') + ' '),
351
- el('span', { className: 'count', text: `(${tips.length})` }),
352
- ])
353
- const recommendationBody = tips.length === 0
354
- ? el('div', { className: 'text-muted text-sm', text: t('costs.noOptTips') })
355
- : el('div', { style: { display: 'flex', flexDirection: 'column', gap: '12px' } }, tips.map(tip => {
356
- const details = [
357
- el('div', { text: tip.title, style: { fontSize: '14px', fontWeight: '500', marginBottom: '4px' } }),
358
- el('div', { text: tip.description, style: { fontSize: '13px', color: 'var(--text-1)' } }),
359
- ]
360
- if (tip.impact) {
361
- details.push(el('div', {
362
- text: t('costs.potentialSavings', { impact: tip.impact }),
363
- style: { fontSize: '12px', color: 'var(--accent)', marginTop: '4px' },
364
- }))
365
- }
366
- return el('div', {
367
- style: {
368
- display: 'flex',
369
- gap: '12px',
370
- padding: '12px',
371
- background: 'var(--bg-2)',
372
- borderRadius: 'var(--radius)',
373
- borderLeft: `3px solid ${tip.color}`,
374
- },
375
- }, [
376
- el('div', { text: tip.icon, style: { fontSize: '16px', flexShrink: '0' } }),
377
- el('div', {}, details),
378
- ])
379
- }))
380
-
381
- container.replaceChildren(
382
- el('div', { className: 'panel mb-24' }, [
383
- el('div', { className: 'panel-title', text: t('costs.optimizationScore') }),
384
- el('div', { style: { display: 'flex', alignItems: 'center', gap: '24px', padding: '12px 0' } }, [
385
- el('div', { text: score, style: { fontSize: '48px', fontWeight: '700', color: getScoreColor(score) } }),
386
- el('div', {}, [
387
- el('div', { text: getScoreLabel(score), style: { fontSize: '14px', color: 'var(--text-1)', marginBottom: '4px' } }),
388
- ]),
389
- ]),
390
- ]),
391
- el('div', { className: 'panel' }, [
392
- recommendationsTitle,
393
- recommendationBody,
394
- ])
395
- )
396
- }
397
-
398
- function generateOptimizationTips(metrics) {
399
- const tips = []
400
- const cmd = metrics?.commandRuns
401
- const model = metrics?.modelUsage
402
-
403
- if (!cmd || !model) return tips
404
-
405
- const efficiency = cmd.rawEstimatedTokens > 0 ? cmd.savedEstimatedTokens / cmd.rawEstimatedTokens : 0
406
-
407
- // Compression tips
408
- if (efficiency < 0.3) {
409
- tips.push({
410
- icon: '\uD83D\uDCE6',
411
- title: t('costs.tipLowCompression'),
412
- description: t('costs.tipLowCompressionDesc'),
413
- color: '#ff4444',
414
- impact: t('costs.tipLowCompressionImpact'),
415
- })
416
- }
417
-
418
- // Model selection tips
419
- for (const m of model.models ?? []) {
420
- if (m.model.includes('opus') && m.requestCount > 10) {
421
- tips.push({
422
- icon: '\uD83D\uDCB0',
423
- title: t('costs.tipDowngrade', { model: m.model }),
424
- description: t('costs.tipDowngradeDesc', { count: m.requestCount }),
425
- color: '#ffaa00',
426
- impact: t('costs.tipDowngradeImpact', { cost: (estimateModelCost(m.model, m.inputTokens, m.outputTokens) * 0.7).toFixed(2) }),
427
- })
428
- }
429
- }
430
-
431
- // Failed commands
432
- if (cmd.failed > 0 && cmd.total > 0) {
433
- const failRate = cmd.failed / cmd.total
434
- if (failRate > 0.1) {
435
- tips.push({
436
- icon: '\u26A0',
437
- title: t('costs.tipHighFailRate'),
438
- description: t('costs.tipHighFailRateDesc', { rate: (failRate * 100).toFixed(0) }),
439
- color: '#ff4444',
440
- impact: t('costs.tipHighFailRateImpact', { count: formatNumber(cmd.failed * 1000) }),
441
- })
442
- }
443
- }
444
-
445
- // Large output tokens
446
- if (model.totalOutputTokens > model.totalInputTokens * 2) {
447
- tips.push({
448
- icon: '\uD83D\uDCDD',
449
- title: t('costs.tipHighOutputRatio'),
450
- description: t('costs.tipHighOutputRatioDesc'),
451
- color: '#5588ff',
452
- impact: t('costs.tipHighOutputRatioImpact'),
453
- })
454
- }
455
-
456
- // General tips
457
- if (tips.length === 0) {
458
- tips.push({
459
- icon: '\u2713',
460
- title: t('costs.tipLookingGood'),
461
- description: t('costs.tipLookingGoodDesc'),
462
- color: '#00dc82',
463
- })
464
- }
465
-
466
- return tips
467
- }
468
-
469
- function getOptimizationScore(metrics) {
470
- const cmd = metrics?.commandRuns
471
- if (!cmd || cmd.rawEstimatedTokens === 0) return 0
472
-
473
- let score = 0
474
- const efficiency = cmd.savedEstimatedTokens / cmd.rawEstimatedTokens
475
- score += Math.min(efficiency * 40, 40) // max 40 points for compression
476
-
477
- const passRate = cmd.total > 0 ? cmd.passed / cmd.total : 0
478
- score += passRate * 30 // max 30 points for pass rate
479
-
480
- const model = metrics?.modelUsage
481
- if (model?.models?.length) {
482
- const hasCheapModels = model.models.some(m => m.model.includes('haiku') || m.model.includes('mini'))
483
- if (hasCheapModels) score += 15 // bonus for using cheap models
484
- }
485
-
486
- score += 15 // base score for having metrics
487
- return Math.min(Math.round(score), 100)
488
- }
489
-
490
- function getScoreColor(score) {
491
- if (score >= 80) return '#00dc82'
492
- if (score >= 50) return '#ffaa00'
493
- return '#ff4444'
494
- }
495
-
496
- function getScoreLabel(score) {
497
- if (score >= 80) return t('costs.scoreExcellent')
498
- if (score >= 60) return t('costs.scoreGood')
499
- if (score >= 40) return t('costs.scoreFair')
500
- return t('costs.scoreNeedsAttention')
501
- }
502
-
503
- // ── Cost Estimation ────────────────────────────────────────────────
504
-
505
- function estimateCost(model) {
506
- if (!model?.models?.length) return 0
507
- let total = 0
508
- for (const m of model.models) {
509
- total += estimateModelCost(m.model, m.inputTokens, m.outputTokens)
510
- }
511
- return total
512
- }
513
-
514
- function estimateModelCost(modelName, inputTokens, outputTokens) {
515
- const pricing = Object.entries(MODEL_PRICING).find(([k]) => modelName.toLowerCase().includes(k))?.[1]
516
- ?? MODEL_PRICING.default
517
- return (inputTokens / 1e6 * pricing.input) + (outputTokens / 1e6 * pricing.output)
518
- }
519
-
520
- window.DashboardPages = window.DashboardPages || {}
521
- window.DashboardPages.costs = renderCosts
522
- })()