Some checks failed
ci/woodpecker/push/web Pipeline failed
- Fix Tooltip formatter/labelFormatter type overload conflicts - Fix Pie label render props type mismatch - Fix telemetry.ts date split array access type Refs #375 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
188 lines
6.1 KiB
TypeScript
188 lines
6.1 KiB
TypeScript
/**
|
|
* Telemetry API Client
|
|
* Handles telemetry data fetching for the usage dashboard.
|
|
*
|
|
* NOTE: Currently returns mock/placeholder data since the telemetry API
|
|
* aggregation endpoints don't exist yet. The important thing is the UI structure.
|
|
* When the backend endpoints are ready, replace mock calls with real apiGet() calls.
|
|
*/
|
|
|
|
import { apiGet, type ApiResponse } from "./client";
|
|
|
|
// ─── Types ───────────────────────────────────────────────────────────
|
|
|
|
export type TimeRange = "7d" | "30d" | "90d";
|
|
|
|
export interface UsageSummary {
|
|
totalTokens: number;
|
|
totalCost: number;
|
|
taskCount: number;
|
|
avgQualityGatePassRate: number;
|
|
}
|
|
|
|
export interface TokenUsagePoint {
|
|
date: string;
|
|
inputTokens: number;
|
|
outputTokens: number;
|
|
totalTokens: number;
|
|
}
|
|
|
|
export interface CostBreakdownItem {
|
|
model: string;
|
|
provider: string;
|
|
cost: number;
|
|
taskCount: number;
|
|
}
|
|
|
|
export interface TaskOutcomeItem {
|
|
outcome: string;
|
|
count: number;
|
|
color: string;
|
|
}
|
|
|
|
export interface EstimateParams {
|
|
taskType: string;
|
|
model: string;
|
|
provider: string;
|
|
complexity: string;
|
|
}
|
|
|
|
export interface EstimateResponse {
|
|
prediction: {
|
|
input_tokens: { median: number; p75: number; p90: number };
|
|
output_tokens: { median: number; p75: number; p90: number };
|
|
cost_usd_micros: Record<string, number>;
|
|
quality: { gate_pass_rate: number; success_rate: number };
|
|
} | null;
|
|
metadata: {
|
|
sample_size: number;
|
|
confidence: "none" | "low" | "medium" | "high";
|
|
};
|
|
}
|
|
|
|
// ─── Mock Data Generators ────────────────────────────────────────────
|
|
|
|
function generateDateRange(range: TimeRange): string[] {
|
|
const days = range === "7d" ? 7 : range === "30d" ? 30 : 90;
|
|
const dates: string[] = [];
|
|
const now = new Date();
|
|
|
|
for (let i = days - 1; i >= 0; i--) {
|
|
const d = new Date(now);
|
|
d.setDate(d.getDate() - i);
|
|
dates.push(d.toISOString().split("T")[0] ?? "");
|
|
}
|
|
|
|
return dates;
|
|
}
|
|
|
|
function generateMockTokenUsage(range: TimeRange): TokenUsagePoint[] {
|
|
const dates = generateDateRange(range);
|
|
|
|
return dates.map((date) => {
|
|
const baseInput = 8000 + Math.floor(Math.random() * 12000);
|
|
const baseOutput = 3000 + Math.floor(Math.random() * 7000);
|
|
return {
|
|
date,
|
|
inputTokens: baseInput,
|
|
outputTokens: baseOutput,
|
|
totalTokens: baseInput + baseOutput,
|
|
};
|
|
});
|
|
}
|
|
|
|
function generateMockSummary(range: TimeRange): UsageSummary {
|
|
const multiplier = range === "7d" ? 1 : range === "30d" ? 4 : 12;
|
|
return {
|
|
totalTokens: 245_800 * multiplier,
|
|
totalCost: 3.42 * multiplier,
|
|
taskCount: 47 * multiplier,
|
|
avgQualityGatePassRate: 0.87,
|
|
};
|
|
}
|
|
|
|
function generateMockCostBreakdown(): CostBreakdownItem[] {
|
|
return [
|
|
{ model: "claude-sonnet-4-5", provider: "anthropic", cost: 18.5, taskCount: 124 },
|
|
{ model: "gpt-4o", provider: "openai", cost: 12.3, taskCount: 89 },
|
|
{ model: "claude-haiku-3.5", provider: "anthropic", cost: 4.2, taskCount: 156 },
|
|
{ model: "llama-3.3-70b", provider: "ollama", cost: 0, taskCount: 67 },
|
|
{ model: "gemini-2.0-flash", provider: "google", cost: 2.8, taskCount: 42 },
|
|
];
|
|
}
|
|
|
|
// PDA-friendly colors: calm, no aggressive reds
|
|
function generateMockTaskOutcomes(): TaskOutcomeItem[] {
|
|
return [
|
|
{ outcome: "Success", count: 312, color: "#6EBF8B" },
|
|
{ outcome: "Partial", count: 48, color: "#F5C862" },
|
|
{ outcome: "Timeout", count: 18, color: "#94A3B8" },
|
|
{ outcome: "Incomplete", count: 22, color: "#C4A5DE" },
|
|
];
|
|
}
|
|
|
|
// ─── API Functions ───────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Fetch usage summary data (total tokens, cost, task count, quality rate)
|
|
*/
|
|
export async function fetchUsageSummary(timeRange: TimeRange): Promise<UsageSummary> {
|
|
// TODO: Replace with real API call when backend aggregation endpoints are ready
|
|
// const response = await apiGet<ApiResponse<UsageSummary>>(`/api/telemetry/summary?range=${timeRange}`);
|
|
// return response.data;
|
|
void apiGet; // suppress unused import warning in the meantime
|
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
return generateMockSummary(timeRange);
|
|
}
|
|
|
|
/**
|
|
* Fetch token usage time series for charts
|
|
*/
|
|
export async function fetchTokenUsage(timeRange: TimeRange): Promise<TokenUsagePoint[]> {
|
|
// TODO: Replace with real API call
|
|
// const response = await apiGet<ApiResponse<TokenUsagePoint[]>>(`/api/telemetry/tokens?range=${timeRange}`);
|
|
// return response.data;
|
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
return generateMockTokenUsage(timeRange);
|
|
}
|
|
|
|
/**
|
|
* Fetch cost breakdown by model
|
|
*/
|
|
export async function fetchCostBreakdown(timeRange: TimeRange): Promise<CostBreakdownItem[]> {
|
|
// TODO: Replace with real API call
|
|
// const response = await apiGet<ApiResponse<CostBreakdownItem[]>>(`/api/telemetry/costs?range=${timeRange}`);
|
|
// return response.data;
|
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
void timeRange;
|
|
return generateMockCostBreakdown();
|
|
}
|
|
|
|
/**
|
|
* Fetch task outcome distribution
|
|
*/
|
|
export async function fetchTaskOutcomes(timeRange: TimeRange): Promise<TaskOutcomeItem[]> {
|
|
// TODO: Replace with real API call
|
|
// const response = await apiGet<ApiResponse<TaskOutcomeItem[]>>(`/api/telemetry/outcomes?range=${timeRange}`);
|
|
// return response.data;
|
|
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
void timeRange;
|
|
return generateMockTaskOutcomes();
|
|
}
|
|
|
|
/**
|
|
* Fetch cost/token estimate for a given task configuration.
|
|
* Uses the real GET /api/telemetry/estimate endpoint from TEL-006.
|
|
*/
|
|
export async function fetchEstimate(params: EstimateParams): Promise<EstimateResponse> {
|
|
const query = new URLSearchParams({
|
|
taskType: params.taskType,
|
|
model: params.model,
|
|
provider: params.provider,
|
|
complexity: params.complexity,
|
|
}).toString();
|
|
|
|
const response = await apiGet<ApiResponse<EstimateResponse>>(`/api/telemetry/estimate?${query}`);
|
|
return response.data;
|
|
}
|