tlc-claude-code 1.4.9 → 1.5.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.
- package/CLAUDE.md +23 -0
- package/CODING-STANDARDS.md +408 -0
- package/bin/install.js +2 -0
- package/dashboard/dist/components/QualityGatePane.d.ts +38 -0
- package/dashboard/dist/components/QualityGatePane.js +31 -0
- package/dashboard/dist/components/QualityGatePane.test.d.ts +1 -0
- package/dashboard/dist/components/QualityGatePane.test.js +147 -0
- package/dashboard/dist/components/orchestration/AgentCard.d.ts +26 -0
- package/dashboard/dist/components/orchestration/AgentCard.js +60 -0
- package/dashboard/dist/components/orchestration/AgentCard.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentCard.test.js +63 -0
- package/dashboard/dist/components/orchestration/AgentControls.d.ts +11 -0
- package/dashboard/dist/components/orchestration/AgentControls.js +20 -0
- package/dashboard/dist/components/orchestration/AgentControls.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentControls.test.js +52 -0
- package/dashboard/dist/components/orchestration/AgentDetail.d.ts +35 -0
- package/dashboard/dist/components/orchestration/AgentDetail.js +37 -0
- package/dashboard/dist/components/orchestration/AgentDetail.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentDetail.test.js +79 -0
- package/dashboard/dist/components/orchestration/AgentList.d.ts +31 -0
- package/dashboard/dist/components/orchestration/AgentList.js +47 -0
- package/dashboard/dist/components/orchestration/AgentList.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentList.test.js +64 -0
- package/dashboard/dist/components/orchestration/CostMeter.d.ts +11 -0
- package/dashboard/dist/components/orchestration/CostMeter.js +28 -0
- package/dashboard/dist/components/orchestration/CostMeter.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/CostMeter.test.js +50 -0
- package/dashboard/dist/components/orchestration/ModelSelector.d.ts +20 -0
- package/dashboard/dist/components/orchestration/ModelSelector.js +12 -0
- package/dashboard/dist/components/orchestration/ModelSelector.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/ModelSelector.test.js +56 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.d.ts +28 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.js +28 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.js +56 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.d.ts +11 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.js +37 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.test.js +52 -0
- package/dashboard/dist/components/orchestration/index.d.ts +8 -0
- package/dashboard/dist/components/orchestration/index.js +8 -0
- package/package.json +1 -1
- package/server/lib/access-control.js +352 -0
- package/server/lib/access-control.test.js +322 -0
- package/server/lib/agents-cancel-command.js +139 -0
- package/server/lib/agents-cancel-command.test.js +180 -0
- package/server/lib/agents-get-command.js +159 -0
- package/server/lib/agents-get-command.test.js +167 -0
- package/server/lib/agents-list-command.js +150 -0
- package/server/lib/agents-list-command.test.js +149 -0
- package/server/lib/agents-logs-command.js +126 -0
- package/server/lib/agents-logs-command.test.js +198 -0
- package/server/lib/agents-retry-command.js +117 -0
- package/server/lib/agents-retry-command.test.js +192 -0
- package/server/lib/budget-limits.js +222 -0
- package/server/lib/budget-limits.test.js +214 -0
- package/server/lib/code-generator.js +291 -0
- package/server/lib/code-generator.test.js +307 -0
- package/server/lib/cost-command.js +290 -0
- package/server/lib/cost-command.test.js +202 -0
- package/server/lib/cost-optimizer.js +404 -0
- package/server/lib/cost-optimizer.test.js +232 -0
- package/server/lib/cost-projections.js +302 -0
- package/server/lib/cost-projections.test.js +217 -0
- package/server/lib/cost-reports.js +277 -0
- package/server/lib/cost-reports.test.js +254 -0
- package/server/lib/cost-tracker.js +216 -0
- package/server/lib/cost-tracker.test.js +302 -0
- package/server/lib/crypto-patterns.js +433 -0
- package/server/lib/crypto-patterns.test.js +346 -0
- package/server/lib/design-command.js +385 -0
- package/server/lib/design-command.test.js +249 -0
- package/server/lib/design-parser.js +237 -0
- package/server/lib/design-parser.test.js +290 -0
- package/server/lib/gemini-vision.js +377 -0
- package/server/lib/gemini-vision.test.js +282 -0
- package/server/lib/input-validator.js +360 -0
- package/server/lib/input-validator.test.js +295 -0
- package/server/lib/litellm-client.js +232 -0
- package/server/lib/litellm-client.test.js +267 -0
- package/server/lib/litellm-command.js +291 -0
- package/server/lib/litellm-command.test.js +260 -0
- package/server/lib/litellm-config.js +273 -0
- package/server/lib/litellm-config.test.js +212 -0
- package/server/lib/model-pricing.js +189 -0
- package/server/lib/model-pricing.test.js +178 -0
- package/server/lib/models-command.js +223 -0
- package/server/lib/models-command.test.js +193 -0
- package/server/lib/optimize-command.js +197 -0
- package/server/lib/optimize-command.test.js +193 -0
- package/server/lib/orchestration-integration.js +206 -0
- package/server/lib/orchestration-integration.test.js +235 -0
- package/server/lib/output-encoder.js +308 -0
- package/server/lib/output-encoder.test.js +312 -0
- package/server/lib/quality-evaluator.js +396 -0
- package/server/lib/quality-evaluator.test.js +337 -0
- package/server/lib/quality-gate-command.js +340 -0
- package/server/lib/quality-gate-command.test.js +321 -0
- package/server/lib/quality-gate-scorer.js +378 -0
- package/server/lib/quality-gate-scorer.test.js +376 -0
- package/server/lib/quality-history.js +265 -0
- package/server/lib/quality-history.test.js +359 -0
- package/server/lib/quality-presets.js +288 -0
- package/server/lib/quality-presets.test.js +269 -0
- package/server/lib/quality-retry.js +323 -0
- package/server/lib/quality-retry.test.js +325 -0
- package/server/lib/quality-thresholds.js +255 -0
- package/server/lib/quality-thresholds.test.js +237 -0
- package/server/lib/secure-auth.js +333 -0
- package/server/lib/secure-auth.test.js +288 -0
- package/server/lib/secure-code-command.js +540 -0
- package/server/lib/secure-code-command.test.js +309 -0
- package/server/lib/secure-errors.js +521 -0
- package/server/lib/secure-errors.test.js +298 -0
- package/server/lib/vision-command.js +372 -0
- package/server/lib/vision-command.test.js +255 -0
- package/server/lib/visual-command.js +350 -0
- package/server/lib/visual-command.test.js +256 -0
- package/server/lib/visual-testing.js +315 -0
- package/server/lib/visual-testing.test.js +357 -0
- package/server/package-lock.json +2 -2
- package/server/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
export function ModelSelector({ models, selected, currentOverride, showCapabilities, showPricing, allowOneTime, allowPersistent, onSelect, onClear, }) {
|
|
4
|
+
if (models.length === 0) {
|
|
5
|
+
return (_jsx(Box, { padding: 1, children: _jsx(Text, { dimColor: true, children: "No models available" }) }));
|
|
6
|
+
}
|
|
7
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, children: "Model Selection" }), currentOverride && (_jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: "cyan", children: ["Override: ", currentOverride] }), onClear && (_jsx(Text, { color: "gray", children: " [Clear]" }))] })), _jsx(Box, { marginTop: 1, flexDirection: "column", children: models.map((model) => {
|
|
8
|
+
const isSelected = selected === model.id;
|
|
9
|
+
const isOverride = currentOverride === model.id;
|
|
10
|
+
return (_jsxs(Box, { marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? 'cyan' : 'gray', children: isSelected ? '●' : '○' }), _jsxs(Text, { color: model.available ? 'white' : 'gray', dimColor: !model.available, children: [' ', model.name] }), isOverride && (_jsx(Text, { color: "yellow", children: " (override)" })), !model.available && (_jsx(Text, { color: "red", children: " (unavailable)" }))] }), showPricing && model.pricing !== undefined && (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { dimColor: true, children: "Price: " }), _jsxs(Text, { children: ["$", model.pricing.toFixed(4), "/1K"] })] })), showCapabilities && model.capabilities && (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { dimColor: true, children: "Capabilities: " }), _jsx(Text, { children: model.capabilities.join(', ') })] }))] }, model.id));
|
|
11
|
+
}) }), (allowOneTime || allowPersistent) && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Options: " }), allowOneTime && _jsx(Text, { children: "[One-time] " }), allowPersistent && _jsx(Text, { children: "[Persistent]" })] }))] }));
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { render } from 'ink-testing-library';
|
|
4
|
+
import { ModelSelector } from './ModelSelector.js';
|
|
5
|
+
describe('ModelSelector', () => {
|
|
6
|
+
const defaultModels = [
|
|
7
|
+
{ id: 'gpt-4', name: 'GPT-4', available: true, pricing: 0.03 },
|
|
8
|
+
{ id: 'gpt-3.5-turbo', name: 'GPT-3.5 Turbo', available: true, pricing: 0.002 },
|
|
9
|
+
{ id: 'claude-3-opus', name: 'Claude 3 Opus', available: false, pricing: 0.06 },
|
|
10
|
+
];
|
|
11
|
+
it('renders model list', () => {
|
|
12
|
+
const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels }));
|
|
13
|
+
expect(lastFrame()?.toLowerCase()).toMatch(/gpt-4/);
|
|
14
|
+
});
|
|
15
|
+
it('capabilities shown per model', () => {
|
|
16
|
+
const models = [
|
|
17
|
+
{ ...defaultModels[0], capabilities: ['code', 'chat', 'vision'] },
|
|
18
|
+
];
|
|
19
|
+
const { lastFrame } = render(_jsx(ModelSelector, { models: models, showCapabilities: true }));
|
|
20
|
+
expect(lastFrame()).toBeDefined();
|
|
21
|
+
});
|
|
22
|
+
it('pricing shown per model', () => {
|
|
23
|
+
const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels, showPricing: true }));
|
|
24
|
+
expect(lastFrame()).toBeDefined();
|
|
25
|
+
});
|
|
26
|
+
it('availability indicator shown', () => {
|
|
27
|
+
const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels }));
|
|
28
|
+
expect(lastFrame()).toBeDefined();
|
|
29
|
+
});
|
|
30
|
+
it('select triggers callback', () => {
|
|
31
|
+
const onSelect = vi.fn();
|
|
32
|
+
const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels, onSelect: onSelect }));
|
|
33
|
+
expect(lastFrame()).toBeDefined();
|
|
34
|
+
});
|
|
35
|
+
it('one-time override option available', () => {
|
|
36
|
+
const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels, allowOneTime: true }));
|
|
37
|
+
expect(lastFrame()).toBeDefined();
|
|
38
|
+
});
|
|
39
|
+
it('persistent override option available', () => {
|
|
40
|
+
const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels, allowPersistent: true }));
|
|
41
|
+
expect(lastFrame()).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
it('clear override button works', () => {
|
|
44
|
+
const onClear = vi.fn();
|
|
45
|
+
const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels, currentOverride: "gpt-4", onClear: onClear }));
|
|
46
|
+
expect(lastFrame()).toBeDefined();
|
|
47
|
+
});
|
|
48
|
+
it('unavailable models indicated', () => {
|
|
49
|
+
const { lastFrame } = render(_jsx(ModelSelector, { models: defaultModels }));
|
|
50
|
+
expect(lastFrame()).toBeDefined();
|
|
51
|
+
});
|
|
52
|
+
it('handles empty model list', () => {
|
|
53
|
+
const { lastFrame } = render(_jsx(ModelSelector, { models: [] }));
|
|
54
|
+
expect(lastFrame()).toBeDefined();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
type AgentStatus = 'running' | 'queued' | 'completed' | 'failed' | 'paused' | 'cancelled';
|
|
2
|
+
interface Agent {
|
|
3
|
+
id: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
model: string;
|
|
6
|
+
status: AgentStatus;
|
|
7
|
+
startTime: Date;
|
|
8
|
+
tokens: {
|
|
9
|
+
input: number;
|
|
10
|
+
output: number;
|
|
11
|
+
};
|
|
12
|
+
cost: number;
|
|
13
|
+
}
|
|
14
|
+
interface CostData {
|
|
15
|
+
spent: number;
|
|
16
|
+
budget: number;
|
|
17
|
+
breakdown?: Record<string, number>;
|
|
18
|
+
}
|
|
19
|
+
interface OrchestrationDashboardProps {
|
|
20
|
+
agents: Agent[];
|
|
21
|
+
cost: CostData;
|
|
22
|
+
width?: number;
|
|
23
|
+
error?: string;
|
|
24
|
+
onConnect?: () => void;
|
|
25
|
+
onSelectAgent?: (agentId: string) => void;
|
|
26
|
+
}
|
|
27
|
+
export declare function OrchestrationDashboard({ agents, cost, width, error, onConnect, onSelectAgent, }: OrchestrationDashboardProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { AgentList } from './AgentList.js';
|
|
5
|
+
import { CostMeter } from './CostMeter.js';
|
|
6
|
+
export function OrchestrationDashboard({ agents, cost, width = 120, error, onConnect, onSelectAgent, }) {
|
|
7
|
+
const [selectedAgent, setSelectedAgent] = useState();
|
|
8
|
+
// Handle error state
|
|
9
|
+
if (error) {
|
|
10
|
+
return (_jsxs(Box, { padding: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, color: "red", children: "Dashboard error" }), _jsx(Text, { color: "red", children: error })] }));
|
|
11
|
+
}
|
|
12
|
+
// Calculate summary stats
|
|
13
|
+
const stats = {
|
|
14
|
+
total: agents.length,
|
|
15
|
+
running: agents.filter((a) => a.status === 'running').length,
|
|
16
|
+
completed: agents.filter((a) => a.status === 'completed').length,
|
|
17
|
+
failed: agents.filter((a) => a.status === 'failed').length,
|
|
18
|
+
queued: agents.filter((a) => a.status === 'queued').length,
|
|
19
|
+
};
|
|
20
|
+
const totalTokens = agents.reduce((sum, a) => sum + a.tokens.input + a.tokens.output, 0);
|
|
21
|
+
const handleSelect = (agentId) => {
|
|
22
|
+
setSelectedAgent(agentId);
|
|
23
|
+
onSelectAgent?.(agentId);
|
|
24
|
+
};
|
|
25
|
+
// Responsive layout
|
|
26
|
+
const isNarrow = width < 100;
|
|
27
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Orchestration Dashboard" }), _jsxs(Text, { dimColor: true, children: [" - ", stats.total, " agents"] })] }), _jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: "blue", children: [stats.running, " running"] }), _jsx(Text, { children: " | " }), _jsxs(Text, { color: "yellow", children: [stats.queued, " queued"] }), _jsx(Text, { children: " | " }), _jsxs(Text, { color: "green", children: [stats.completed, " completed"] }), stats.failed > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " | " }), _jsxs(Text, { color: "red", children: [stats.failed, " failed"] })] })), _jsxs(Text, { dimColor: true, children: [" | ", totalTokens.toLocaleString(), " tokens"] })] }), _jsxs(Box, { flexDirection: isNarrow ? 'column' : 'row', children: [_jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsx(AgentList, { agents: agents, selected: selectedAgent, onSelect: handleSelect }) }), _jsx(Box, { flexDirection: "column", marginLeft: isNarrow ? 0 : 2, marginTop: isNarrow ? 1 : 0, width: isNarrow ? '100%' : 30, children: _jsx(CostMeter, { spent: cost.spent, budget: cost.budget, breakdown: cost.breakdown }) })] }), agents.length === 0 && !error && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "No agents currently running. Start an orchestration to see activity." }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/\u2193 Select | Enter View | P Pause | C Cancel | R Retry | Q Quit" }) })] }));
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { render } from 'ink-testing-library';
|
|
4
|
+
import { OrchestrationDashboard } from './OrchestrationDashboard.js';
|
|
5
|
+
describe('OrchestrationDashboard', () => {
|
|
6
|
+
const mockAgents = [
|
|
7
|
+
{ id: 'agent-1', name: 'Test 1', model: 'gpt-4', status: 'running', startTime: new Date(), tokens: { input: 100, output: 50 }, cost: 0.01 },
|
|
8
|
+
{ id: 'agent-2', name: 'Test 2', model: 'gpt-3.5', status: 'completed', startTime: new Date(), tokens: { input: 200, output: 100 }, cost: 0.005 },
|
|
9
|
+
];
|
|
10
|
+
const mockCost = {
|
|
11
|
+
spent: 50,
|
|
12
|
+
budget: 100,
|
|
13
|
+
breakdown: { 'gpt-4': 40, 'gpt-3.5-turbo': 10 },
|
|
14
|
+
};
|
|
15
|
+
it('renders without error', () => {
|
|
16
|
+
const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost }));
|
|
17
|
+
expect(lastFrame()).toBeDefined();
|
|
18
|
+
});
|
|
19
|
+
it('summary stats show totals', () => {
|
|
20
|
+
const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost }));
|
|
21
|
+
expect(lastFrame()).toBeDefined();
|
|
22
|
+
});
|
|
23
|
+
it('agent list is visible', () => {
|
|
24
|
+
const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost }));
|
|
25
|
+
expect(lastFrame()).toBeDefined();
|
|
26
|
+
});
|
|
27
|
+
it('cost sidebar visible', () => {
|
|
28
|
+
const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost }));
|
|
29
|
+
expect(lastFrame()).toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
it('handles WebSocket connection', () => {
|
|
32
|
+
const onConnect = vi.fn();
|
|
33
|
+
const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost, onConnect: onConnect }));
|
|
34
|
+
expect(lastFrame()).toBeDefined();
|
|
35
|
+
});
|
|
36
|
+
it('updates on agent change', () => {
|
|
37
|
+
const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost }));
|
|
38
|
+
expect(lastFrame()).toBeDefined();
|
|
39
|
+
});
|
|
40
|
+
it('handles mobile layout', () => {
|
|
41
|
+
const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost, width: 80 }));
|
|
42
|
+
expect(lastFrame()).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
it('keyboard navigation works', () => {
|
|
45
|
+
const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: mockAgents, cost: mockCost }));
|
|
46
|
+
expect(lastFrame()).toBeDefined();
|
|
47
|
+
});
|
|
48
|
+
it('error boundary catches errors', () => {
|
|
49
|
+
const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: [], cost: mockCost, error: "Test error" }));
|
|
50
|
+
expect(lastFrame()).toBeDefined();
|
|
51
|
+
});
|
|
52
|
+
it('empty state shows message', () => {
|
|
53
|
+
const { lastFrame } = render(_jsx(OrchestrationDashboard, { agents: [], cost: mockCost }));
|
|
54
|
+
expect(lastFrame()).toBeDefined();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface QualityIndicatorProps {
|
|
2
|
+
score?: number;
|
|
3
|
+
threshold?: number;
|
|
4
|
+
dimensions?: Record<string, number>;
|
|
5
|
+
history?: number[];
|
|
6
|
+
showThreshold?: boolean;
|
|
7
|
+
showRetryHint?: boolean;
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function QualityIndicator({ score, threshold, dimensions, history, showThreshold, showRetryHint, loading, }: QualityIndicatorProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
export function QualityIndicator({ score, threshold = 70, dimensions, history, showThreshold, showRetryHint, loading, }) {
|
|
4
|
+
if (loading) {
|
|
5
|
+
return (_jsx(Box, { padding: 1, children: _jsx(Text, { dimColor: true, children: "Loading quality data..." }) }));
|
|
6
|
+
}
|
|
7
|
+
if (score === undefined) {
|
|
8
|
+
return (_jsx(Box, { padding: 1, children: _jsx(Text, { dimColor: true, children: "No quality data" }) }));
|
|
9
|
+
}
|
|
10
|
+
const pass = score >= threshold;
|
|
11
|
+
const getScoreColor = (s, t) => {
|
|
12
|
+
if (s >= t + 10)
|
|
13
|
+
return 'green';
|
|
14
|
+
if (s >= t)
|
|
15
|
+
return 'yellow';
|
|
16
|
+
return 'red';
|
|
17
|
+
};
|
|
18
|
+
// Create simple gauge
|
|
19
|
+
const gaugeLength = 20;
|
|
20
|
+
const filledLength = Math.round((score / 100) * gaugeLength);
|
|
21
|
+
const thresholdPos = Math.round((threshold / 100) * gaugeLength);
|
|
22
|
+
const gauge = '█'.repeat(filledLength) + '░'.repeat(gaugeLength - filledLength);
|
|
23
|
+
// Create sparkline from history
|
|
24
|
+
const renderSparkline = (data) => {
|
|
25
|
+
if (!data || data.length === 0)
|
|
26
|
+
return '';
|
|
27
|
+
const blocks = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
|
|
28
|
+
const min = Math.min(...data);
|
|
29
|
+
const max = Math.max(...data);
|
|
30
|
+
const range = max - min || 1;
|
|
31
|
+
return data.map((v) => {
|
|
32
|
+
const idx = Math.round(((v - min) / range) * 7);
|
|
33
|
+
return blocks[idx];
|
|
34
|
+
}).join('');
|
|
35
|
+
};
|
|
36
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, children: "Quality Score" }), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: getScoreColor(score, threshold), bold: true, children: [score, "%"] }), _jsx(Text, { children: " " }), _jsx(Text, { color: pass ? 'green' : 'red', children: pass ? '✓ PASS' : '✗ FAIL' })] }), _jsx(Box, { children: _jsxs(Text, { color: getScoreColor(score, threshold), children: ["[", gauge, "]"] }) }), showThreshold && (_jsx(Box, { children: _jsxs(Text, { dimColor: true, children: ["Threshold: ", threshold, "%"] }) })), dimensions && Object.keys(dimensions).length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: "Dimensions:" }), Object.entries(dimensions).map(([dim, dimScore]) => (_jsxs(Box, { children: [_jsxs(Text, { dimColor: true, children: [" ", dim, ": "] }), _jsxs(Text, { color: getScoreColor(dimScore, threshold), children: [dimScore, "%"] })] }, dim)))] })), history && history.length > 0 && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Trend: " }), _jsx(Text, { color: "cyan", children: renderSparkline(history) })] })), !pass && showRetryHint && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: "Consider retry with a better model" }) }))] }));
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { render } from 'ink-testing-library';
|
|
4
|
+
import { QualityIndicator } from './QualityIndicator.js';
|
|
5
|
+
describe('QualityIndicator', () => {
|
|
6
|
+
it('gauge shows score', () => {
|
|
7
|
+
const { lastFrame } = render(_jsx(QualityIndicator, { score: 85, threshold: 70 }));
|
|
8
|
+
expect(lastFrame()).toContain('85');
|
|
9
|
+
});
|
|
10
|
+
it('gauge colored by threshold - pass', () => {
|
|
11
|
+
const { lastFrame } = render(_jsx(QualityIndicator, { score: 85, threshold: 70 }));
|
|
12
|
+
expect(lastFrame()).toBeDefined();
|
|
13
|
+
});
|
|
14
|
+
it('gauge colored by threshold - fail', () => {
|
|
15
|
+
const { lastFrame } = render(_jsx(QualityIndicator, { score: 50, threshold: 70 }));
|
|
16
|
+
expect(lastFrame()).toBeDefined();
|
|
17
|
+
});
|
|
18
|
+
it('dimension breakdown expandable', () => {
|
|
19
|
+
const dimensions = {
|
|
20
|
+
style: 80,
|
|
21
|
+
completeness: 90,
|
|
22
|
+
correctness: 85,
|
|
23
|
+
};
|
|
24
|
+
const { lastFrame } = render(_jsx(QualityIndicator, { score: 85, threshold: 70, dimensions: dimensions }));
|
|
25
|
+
expect(lastFrame()).toBeDefined();
|
|
26
|
+
});
|
|
27
|
+
it('pass indicator shows check', () => {
|
|
28
|
+
const { lastFrame } = render(_jsx(QualityIndicator, { score: 85, threshold: 70 }));
|
|
29
|
+
expect(lastFrame()).toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
it('fail indicator shows x', () => {
|
|
32
|
+
const { lastFrame } = render(_jsx(QualityIndicator, { score: 50, threshold: 70 }));
|
|
33
|
+
expect(lastFrame()).toBeDefined();
|
|
34
|
+
});
|
|
35
|
+
it('threshold line visible', () => {
|
|
36
|
+
const { lastFrame } = render(_jsx(QualityIndicator, { score: 85, threshold: 70, showThreshold: true }));
|
|
37
|
+
expect(lastFrame()).toBeDefined();
|
|
38
|
+
});
|
|
39
|
+
it('trend sparkline shows history', () => {
|
|
40
|
+
const history = [60, 65, 70, 75, 80, 85];
|
|
41
|
+
const { lastFrame } = render(_jsx(QualityIndicator, { score: 85, threshold: 70, history: history }));
|
|
42
|
+
expect(lastFrame()).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
it('retry recommendation on fail', () => {
|
|
45
|
+
const { lastFrame } = render(_jsx(QualityIndicator, { score: 50, threshold: 70, showRetryHint: true }));
|
|
46
|
+
expect(lastFrame()).toBeDefined();
|
|
47
|
+
});
|
|
48
|
+
it('loading state handled', () => {
|
|
49
|
+
const { lastFrame } = render(_jsx(QualityIndicator, { loading: true }));
|
|
50
|
+
expect(lastFrame()).toBeDefined();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { AgentCard } from './AgentCard.js';
|
|
2
|
+
export { AgentControls } from './AgentControls.js';
|
|
3
|
+
export { AgentDetail } from './AgentDetail.js';
|
|
4
|
+
export { AgentList } from './AgentList.js';
|
|
5
|
+
export { CostMeter } from './CostMeter.js';
|
|
6
|
+
export { ModelSelector } from './ModelSelector.js';
|
|
7
|
+
export { OrchestrationDashboard } from './OrchestrationDashboard.js';
|
|
8
|
+
export { QualityIndicator } from './QualityIndicator.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { AgentCard } from './AgentCard.js';
|
|
2
|
+
export { AgentControls } from './AgentControls.js';
|
|
3
|
+
export { AgentDetail } from './AgentDetail.js';
|
|
4
|
+
export { AgentList } from './AgentList.js';
|
|
5
|
+
export { CostMeter } from './CostMeter.js';
|
|
6
|
+
export { ModelSelector } from './ModelSelector.js';
|
|
7
|
+
export { OrchestrationDashboard } from './OrchestrationDashboard.js';
|
|
8
|
+
export { QualityIndicator } from './QualityIndicator.js';
|