Merge PR #2019 and resolve conflicts

This commit is contained in:
diegosouzapw
2026-05-08 17:50:18 -03:00
parent a52c9ceb45
commit 276e5da137
55 changed files with 1948 additions and 197 deletions

View File

@@ -11,6 +11,7 @@ interface ToggleProps {
size?: "sm" | "md" | "lg";
className?: string;
title?: string;
ariaLabel?: string;
}
export default function Toggle({
@@ -21,6 +22,8 @@ export default function Toggle({
disabled = false,
size = "md",
className,
title,
ariaLabel,
}: ToggleProps) {
const sizes = {
sm: {
@@ -58,7 +61,8 @@ export default function Toggle({
type="button"
role="switch"
aria-checked={checked}
aria-label={!label ? description || "Toggle" : undefined}
aria-label={ariaLabel || label || description || title || "Toggle"}
title={title}
disabled={disabled}
onClick={handleClick}
className={cn(

View File

@@ -18,6 +18,7 @@ import {
ProviderCostDonut,
ModelOverTimeChart,
ProviderTable,
ServiceTierBreakdown,
ApiKeyFilterDropdown,
CustomRangePicker,
} from "./analytics";
@@ -310,10 +311,10 @@ export default function UsageAnalytics() {
color: "text-violet-500",
},
{
icon: "swap_horiz",
label: "Fallback Rate",
value: `${Number(s.fallbackRatePct || 0).toFixed(1)}%`,
color: "text-amber-500",
icon: "bolt",
label: "Fast Requests",
value: fmt(s.fastRequests || 0),
color: "text-sky-500",
},
],
},
@@ -330,6 +331,12 @@ export default function UsageAnalytics() {
value: `${providerDiversity.toFixed(1)}%`,
color: "text-sky-500",
},
{
icon: "swap_horiz",
label: "Fallback Rate",
value: `${Number(s.fallbackRatePct || 0).toFixed(1)}%`,
color: "text-amber-500",
},
],
},
]}
@@ -350,6 +357,9 @@ export default function UsageAnalytics() {
<ProviderCostDonut byProvider={analytics?.byProvider} />
</div>
{/* Fast / Standard service tier split */}
<ServiceTierBreakdown byServiceTier={analytics?.byServiceTier} summary={s} />
{/* Model Usage Over Time (stacked area) */}
<ModelOverTimeChart
dailyByModel={analytics?.dailyByModel}

View File

@@ -1081,6 +1081,71 @@ export function ModelTable({ byModel, summary }) {
);
}
export function ServiceTierBreakdown({ byServiceTier, summary }) {
const data = useMemo(() => byServiceTier || [], [byServiceTier]);
const totalRequests = Number(summary?.totalRequests || 0);
const totalCost = Number(summary?.totalCost || 0);
if (!data.length) {
return null;
}
return (
<Card className="overflow-hidden">
<div className="p-4 border-b border-border flex items-center justify-between gap-3">
<h3 className="text-sm font-semibold text-text-muted uppercase tracking-wider">
Service Tier
</h3>
<span className="text-[11px] text-text-muted">Fast / Standard cost split</span>
</div>
<div className="divide-y divide-border">
{data.map((tier) => {
const isFast = tier.serviceTier === "priority";
const requestPct =
totalRequests > 0
? ((Number(tier.requests || 0) / totalRequests) * 100).toFixed(1)
: "0";
const costPct =
totalCost > 0 ? ((Number(tier.cost || 0) / totalCost) * 100).toFixed(1) : "0";
return (
<div key={tier.serviceTier} className="p-4 flex flex-col gap-3">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2">
<span
className={`material-symbols-outlined text-[18px] ${
isFast ? "text-sky-500" : "text-text-muted"
}`}
>
{isFast ? "bolt" : "speed"}
</span>
<div>
<div className="text-sm font-semibold text-text-main">{tier.label}</div>
<div className="text-xs text-text-muted">
{fmtFull(tier.requests)} requests · {fmt(tier.totalTokens)} tokens
</div>
</div>
</div>
<div className="text-right">
<div className="font-mono text-sm font-semibold text-amber-500">
{fmtCost(tier.cost)}
</div>
<div className="text-xs text-text-muted">{costPct}% of cost</div>
</div>
</div>
<div className="h-1.5 rounded-full bg-black/5 dark:bg-white/10 overflow-hidden">
<div
className={`h-full rounded-full ${isFast ? "bg-sky-500" : "bg-text-muted/50"}`}
style={{ width: `${requestPct}%` }}
/>
</div>
</div>
);
})}
</div>
</Card>
);
}
// ── UsageDetail ────────────────────────────────────────────────────────────
export function UsageDetail({ summary }) {

View File

@@ -25,6 +25,7 @@ export {
ProviderCostDonut,
ModelOverTimeChart,
ProviderTable,
ServiceTierBreakdown,
} from "./charts";
export { default as ApiKeyFilterDropdown } from "./ApiKeyFilterDropdown";

View File

@@ -1849,6 +1849,7 @@ export const USAGE_SUPPORTED_PROVIDERS = [
"claude",
"kimi-coding",
"glm",
"zai",
"glmt",
"minimax",
"minimax-cn",

View File

@@ -8,6 +8,7 @@ export const ROUTING_STRATEGY_VALUES = [
"random",
"least-used",
"cost-optimized",
"reset-aware",
"strict-random",
"auto",
"lkgp",
@@ -123,6 +124,13 @@ export const ROUTING_STRATEGIES: RoutingStrategyOption[] = [
settingsDescKey: "costOptDesc",
icon: "savings",
},
{
value: "reset-aware",
labelKey: "resetAware",
combosDescKey: "resetAwareDesc",
settingsDescKey: "resetAwareDesc",
icon: "event_repeat",
},
{
value: "strict-random",
labelKey: "strictRandom",

View File

@@ -8,6 +8,7 @@ type ReadTimeoutOptions = {
export const DEFAULT_FETCH_TIMEOUT_MS = 600_000;
export const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 600_000;
export const DEFAULT_STREAM_READINESS_TIMEOUT_MS = 30_000;
export const DEFAULT_FETCH_CONNECT_TIMEOUT_MS = 30_000;
export const DEFAULT_FETCH_KEEPALIVE_TIMEOUT_MS = 4_000;
export const DEFAULT_API_BRIDGE_PROXY_TIMEOUT_MS = 30_000;
@@ -24,6 +25,7 @@ function hasEnvValue(env: EnvSource, name: string): boolean {
export type UpstreamTimeoutConfig = {
fetchTimeoutMs: number;
streamIdleTimeoutMs: number;
streamReadinessTimeoutMs: number;
fetchHeadersTimeoutMs: number;
fetchBodyTimeoutMs: number;
fetchConnectTimeoutMs: number;
@@ -89,10 +91,20 @@ export function getUpstreamTimeoutConfig(
logger,
}
);
const streamReadinessTimeoutMs = readTimeoutMs(
env,
"STREAM_READINESS_TIMEOUT_MS",
DEFAULT_STREAM_READINESS_TIMEOUT_MS,
{
allowZero: true,
logger,
}
);
return {
fetchTimeoutMs,
streamIdleTimeoutMs,
streamReadinessTimeoutMs,
fetchHeadersTimeoutMs: readTimeoutMs(env, "FETCH_HEADERS_TIMEOUT_MS", fetchTimeoutMs, {
allowZero: true,
logger,

View File

@@ -375,6 +375,7 @@ const comboRuntimeConfigSchema = z
strategy: comboStrategySchema.optional(),
maxRetries: z.coerce.number().int().min(0).max(10).optional(),
retryDelayMs: z.coerce.number().int().min(0).max(60000).optional(),
fallbackDelayMs: z.coerce.number().int().min(0).max(60000).optional(),
timeoutMs: z.coerce.number().int().min(1000).optional(),
concurrencyPerModel: z.coerce.number().int().min(1).max(20).optional(),
queueTimeoutMs: z.coerce.number().int().min(1000).max(120000).optional(),
@@ -395,6 +396,10 @@ const comboRuntimeConfigSchema = z
explorationRate: z.number().min(0).max(1).optional(),
routerStrategy: z.string().optional(),
compositeTiers: compositeTiersSchema.optional(),
resetAwareSessionWeight: z.coerce.number().min(0).max(100).optional(),
resetAwareWeeklyWeight: z.coerce.number().min(0).max(100).optional(),
resetAwareTieBandPercent: z.coerce.number().min(0).max(100).optional(),
resetAwareExhaustionGuardPercent: z.coerce.number().min(0).max(100).optional(),
})
.strict();
@@ -439,6 +444,7 @@ export const updateSettingsSchema = z.object({
bruteForceProtection: z.boolean().optional(),
hiddenSidebarItems: z.array(z.enum(HIDEABLE_SIDEBAR_ITEM_IDS)).optional(),
comboConfigMode: z.enum(COMBO_CONFIG_MODES).optional(),
codexServiceTier: z.object({ enabled: z.boolean() }).optional(),
// Routing settings (#134)
fallbackStrategy: settingsFallbackStrategySchema.optional(),
wildcardAliases: z.array(z.object({ pattern: z.string(), target: z.string() })).optional(),

View File

@@ -37,6 +37,7 @@ export const updateSettingsSchema = z.object({
debugMode: z.boolean().optional(),
hiddenSidebarItems: z.array(z.enum(HIDEABLE_SIDEBAR_ITEM_IDS)).optional(),
comboConfigMode: z.enum(COMBO_CONFIG_MODES).optional(),
codexServiceTier: z.object({ enabled: z.boolean() }).optional(),
// Routing settings (#134)
fallbackStrategy: z.enum(ACCOUNT_FALLBACK_STRATEGY_VALUES).optional(),
wildcardAliases: z.array(z.object({ pattern: z.string(), target: z.string() })).optional(),