mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 14:52:09 +03:00
Merge pull request #1756 from oyi77/feat/compression-phase5
feat(compression): Phase 5 — Dashboard UI & Analytics (#1590)
This commit is contained in:
@@ -1569,14 +1569,83 @@ export async function handleChatCore({
|
||||
const { selectCompressionStrategy, applyCompression } =
|
||||
await import("../services/compression/strategySelector.ts");
|
||||
const { trackCompressionStats } = await import("../services/compression/stats.ts");
|
||||
const config = await getCompressionSettings();
|
||||
const mode = selectCompressionStrategy(config, comboName ?? null, estimatedTokens);
|
||||
let config = await getCompressionSettings();
|
||||
let compressionComboKey = comboName ?? null;
|
||||
if (isCombo && comboName) {
|
||||
try {
|
||||
const { getComboByName } = await import("../../src/lib/localDb");
|
||||
let comboConfig = await getComboByName(comboName);
|
||||
if (!comboConfig && comboName.startsWith("combo/")) {
|
||||
comboConfig = await getComboByName(comboName.substring(6));
|
||||
}
|
||||
const comboRuntimeConfig =
|
||||
comboConfig?.config && typeof comboConfig.config === "object"
|
||||
? (comboConfig.config as Record<string, unknown>)
|
||||
: {};
|
||||
const comboMode =
|
||||
typeof comboRuntimeConfig.compressionMode === "string"
|
||||
? comboRuntimeConfig.compressionMode
|
||||
: typeof comboConfig?.compressionOverride === "string"
|
||||
? comboConfig.compressionOverride
|
||||
: null;
|
||||
if (
|
||||
comboMode === "off" ||
|
||||
comboMode === "lite" ||
|
||||
comboMode === "standard" ||
|
||||
comboMode === "aggressive" ||
|
||||
comboMode === "ultra"
|
||||
) {
|
||||
config = {
|
||||
...config,
|
||||
comboOverrides: {
|
||||
...(config.comboOverrides ?? {}),
|
||||
...(comboName ? { [comboName]: comboMode } : {}),
|
||||
...(comboConfig?.id ? { [comboConfig.id]: comboMode } : {}),
|
||||
},
|
||||
};
|
||||
compressionComboKey = comboName;
|
||||
}
|
||||
} catch (err) {
|
||||
log?.debug?.(
|
||||
"COMPRESSION",
|
||||
"Combo compression override lookup skipped: " +
|
||||
(err instanceof Error ? err.message : String(err))
|
||||
);
|
||||
}
|
||||
}
|
||||
const mode = selectCompressionStrategy(config, compressionComboKey, estimatedTokens);
|
||||
if (mode !== "off") {
|
||||
const result = applyCompression(body, mode, { model: effectiveModel, config });
|
||||
if (result.compressed && result.stats) {
|
||||
body = result.body as typeof body;
|
||||
estimatedTokens = result.stats.compressedTokens;
|
||||
trackCompressionStats(result.stats);
|
||||
void (async () => {
|
||||
try {
|
||||
const { insertCompressionAnalyticsRow } =
|
||||
await import("../../src/lib/db/compressionAnalytics.ts");
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: new Date().toISOString(),
|
||||
combo_id: comboName ?? null,
|
||||
provider: provider ?? null,
|
||||
mode,
|
||||
original_tokens: result.stats.originalTokens,
|
||||
compressed_tokens: result.stats.compressedTokens,
|
||||
tokens_saved: Math.max(
|
||||
0,
|
||||
result.stats.originalTokens - result.stats.compressedTokens
|
||||
),
|
||||
duration_ms: result.stats.durationMs ?? null,
|
||||
request_id: skillRequestId,
|
||||
});
|
||||
} catch (err) {
|
||||
log?.debug?.(
|
||||
"COMPRESSION",
|
||||
"Compression analytics write skipped: " +
|
||||
(err instanceof Error ? err.message : String(err))
|
||||
);
|
||||
}
|
||||
})();
|
||||
log?.info?.(
|
||||
"COMPRESSION",
|
||||
`Prompt compressed (${mode}): ${result.stats.originalTokens} -> ${result.stats.compressedTokens} tokens (${result.stats.savingsPercent}% saved, techniques: ${result.stats.techniquesUsed.join(",")})`
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* Compression Analytics Tab
|
||||
*
|
||||
* Shows compression request stats from call_logs (request_type = 'compression'),
|
||||
* mode breakdown, provider breakdown, and cost/token savings summary.
|
||||
*/
|
||||
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface CompressionAnalyticsSummary {
|
||||
totalRequests: number;
|
||||
totalTokensSaved: number;
|
||||
avgSavingsPct: number;
|
||||
avgDurationMs: number;
|
||||
byMode: Record<string, { count: number; tokensSaved: number; avgSavingsPct: number }>;
|
||||
byProvider: Record<string, { count: number; tokensSaved: number }>;
|
||||
last24h: Array<{ hour: string; count: number; tokensSaved: number }>;
|
||||
}
|
||||
|
||||
function StatCard({
|
||||
icon,
|
||||
label,
|
||||
value,
|
||||
sub,
|
||||
}: {
|
||||
icon: string;
|
||||
label: string;
|
||||
value: string | number;
|
||||
sub?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="card p-4 flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2 text-text-muted text-sm">
|
||||
<span className="material-symbols-outlined text-[18px]">{icon}</span>
|
||||
{label}
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-text">{value}</div>
|
||||
{sub && <div className="text-xs text-text-muted">{sub}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ModeBar({
|
||||
mode,
|
||||
count,
|
||||
total,
|
||||
tokensSaved,
|
||||
}: {
|
||||
mode: string;
|
||||
count: number;
|
||||
total: number;
|
||||
tokensSaved: number;
|
||||
}) {
|
||||
const pct = total > 0 ? Math.round((count / total) * 100) : 0;
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="font-medium text-text capitalize">{mode}</span>
|
||||
<span className="text-text-muted">
|
||||
{count} requests · {tokensSaved.toLocaleString()} tokens saved
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 rounded-full bg-bg-muted overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full bg-primary transition-all"
|
||||
style={{ width: `${pct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs text-text-muted text-right">{pct}%</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProviderBar({
|
||||
provider,
|
||||
count,
|
||||
total,
|
||||
tokensSaved,
|
||||
}: {
|
||||
provider: string;
|
||||
count: number;
|
||||
total: number;
|
||||
tokensSaved: number;
|
||||
}) {
|
||||
const pct = total > 0 ? Math.round((count / total) * 100) : 0;
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="font-medium text-text">{provider}</span>
|
||||
<span className="text-text-muted">
|
||||
{count} requests · {tokensSaved.toLocaleString()} tokens saved
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 rounded-full bg-bg-muted overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full bg-primary transition-all"
|
||||
style={{ width: `${pct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs text-text-muted text-right">{pct}%</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CompressionAnalyticsTab() {
|
||||
const [stats, setStats] = useState<CompressionAnalyticsSummary | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [since, setSince] = useState<"24h" | "7d" | "30d" | "all">("24h");
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`/api/analytics/compression?since=${since}`)
|
||||
.then((r) => r.json())
|
||||
.then((d) => {
|
||||
setStats(d);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
setError(e.message);
|
||||
setLoading(false);
|
||||
});
|
||||
}, [since]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-16 text-text-muted">
|
||||
<span className="material-symbols-outlined animate-spin mr-2">progress_activity</span>
|
||||
Loading compression analytics…
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !stats) {
|
||||
return (
|
||||
<div className="card p-6 text-center text-text-muted">
|
||||
<span className="material-symbols-outlined text-[32px] mb-2 block">compress</span>
|
||||
{error || "No compression data yet."}
|
||||
<p className="text-xs mt-2">
|
||||
Compression requests will appear here after the first request via /v1/chat/completions
|
||||
with compression enabled.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const modes = Object.entries(stats.byMode).sort(([, a], [, b]) => b.count - a.count);
|
||||
const providers = Object.entries(stats.byProvider).sort(([, a], [, b]) => b.count - a.count);
|
||||
|
||||
// Calculate max tokens for hourly chart scaling
|
||||
const maxTokensPerHour = Math.max(...stats.last24h.map((h) => h.tokensSaved), 1);
|
||||
const maxCountPerHour = Math.max(...stats.last24h.map((h) => h.count), 1);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* Time Range Selector */}
|
||||
<div className="flex gap-2">
|
||||
{(["24h", "7d", "30d", "all"] as const).map((range) => (
|
||||
<button
|
||||
key={range}
|
||||
onClick={() => setSince(range)}
|
||||
className={`px-3 py-1 rounded text-sm transition-colors ${
|
||||
since === range
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-bg-muted text-text-muted hover:bg-bg-muted/80"
|
||||
}`}
|
||||
>
|
||||
{range === "24h"
|
||||
? "Last 24h"
|
||||
: range === "7d"
|
||||
? "Last 7d"
|
||||
: range === "30d"
|
||||
? "Last 30d"
|
||||
: "All time"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* KPI Cards */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<StatCard
|
||||
icon="compress"
|
||||
label="Total Requests"
|
||||
value={stats.totalRequests.toLocaleString()}
|
||||
/>
|
||||
<StatCard
|
||||
icon="token"
|
||||
label="Tokens Saved"
|
||||
value={stats.totalTokensSaved.toLocaleString()}
|
||||
/>
|
||||
<StatCard icon="percent" label="Avg Savings" value={`${stats.avgSavingsPct}%`} />
|
||||
<StatCard icon="timer" label="Avg Duration" value={`${stats.avgDurationMs}ms`} />
|
||||
</div>
|
||||
|
||||
{/* Mode Breakdown */}
|
||||
{modes.length > 0 && (
|
||||
<div className="card p-5">
|
||||
<h3 className="font-semibold text-text mb-4 flex items-center gap-2">
|
||||
<span className="material-symbols-outlined text-primary text-[20px]">tune</span>
|
||||
Mode Breakdown
|
||||
</h3>
|
||||
<div className="flex flex-col gap-4">
|
||||
{modes.map(([mode, data]) => (
|
||||
<ModeBar
|
||||
key={mode}
|
||||
mode={mode}
|
||||
count={data.count}
|
||||
total={stats.totalRequests}
|
||||
tokensSaved={data.tokensSaved}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Provider Breakdown */}
|
||||
{providers.length > 0 && (
|
||||
<div className="card p-5">
|
||||
<h3 className="font-semibold text-text mb-4 flex items-center gap-2">
|
||||
<span className="material-symbols-outlined text-primary text-[20px]">hub</span>
|
||||
Provider Breakdown
|
||||
</h3>
|
||||
<div className="flex flex-col gap-4">
|
||||
{providers.map(([prov, data]) => (
|
||||
<ProviderBar
|
||||
key={prov}
|
||||
provider={prov}
|
||||
count={data.count}
|
||||
total={stats.totalRequests}
|
||||
tokensSaved={data.tokensSaved}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Last 24h Hourly Chart (CSS-only height-based bars) */}
|
||||
{stats.last24h.length > 0 && (
|
||||
<div className="card p-5">
|
||||
<h3 className="font-semibold text-text mb-4 flex items-center gap-2">
|
||||
<span className="material-symbols-outlined text-primary text-[20px]">show_chart</span>
|
||||
Last 24 Hours
|
||||
</h3>
|
||||
<div className="flex items-end gap-2 h-48">
|
||||
{stats.last24h.map((entry, idx) => {
|
||||
const countPct = (entry.count / maxCountPerHour) * 100;
|
||||
const tokenPct = (entry.tokensSaved / maxTokensPerHour) * 100;
|
||||
return (
|
||||
<div key={idx} className="flex-1 flex flex-col items-center gap-2">
|
||||
<div
|
||||
className="w-full rounded-t-sm bg-gradient-to-b from-primary to-primary/70 transition-all hover:opacity-80 cursor-pointer group relative"
|
||||
style={{ height: `${Math.max(countPct, 5)}%` }}
|
||||
title={`${entry.hour}: ${entry.count} requests, ${entry.tokensSaved.toLocaleString()} tokens saved`}
|
||||
>
|
||||
<div className="absolute -top-6 left-0 right-0 opacity-0 group-hover:opacity-100 transition-opacity text-xs text-text-muted whitespace-nowrap text-center">
|
||||
{entry.count}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-text-muted rotate-45 origin-left">
|
||||
{entry.hour.substring(0, 2)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-4 grid grid-cols-2 gap-2 text-xs text-text-muted">
|
||||
<div>Max requests/hour: {maxCountPerHour}</div>
|
||||
<div>Max tokens/hour: {maxTokensPerHour.toLocaleString()}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Empty state */}
|
||||
{stats.totalRequests === 0 && (
|
||||
<div className="card p-8 text-center text-text-muted">
|
||||
<span className="material-symbols-outlined text-[48px] mb-3 block text-primary opacity-50">
|
||||
compress
|
||||
</span>
|
||||
<p className="font-medium text-text">No compression data yet</p>
|
||||
<p className="text-sm mt-1">
|
||||
Use <code className="bg-bg-muted px-1 rounded">POST /v1/chat/completions</code> with
|
||||
compression configuration to start tracking compression analytics.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info note */}
|
||||
<div className="text-xs text-text-muted border border-border rounded-lg p-3 flex items-start gap-2">
|
||||
<span className="material-symbols-outlined text-[16px] text-blue-500 mt-0.5">info</span>
|
||||
<span>
|
||||
<strong>Compression analytics:</strong> Token savings tracked per mode (off, lite,
|
||||
standard, aggressive, ultra) and provider. Hover over charts for details. Use the time
|
||||
selector to view different time periods.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { useState, Suspense } from "react";
|
||||
import { UsageAnalytics, CardSkeleton, SegmentedControl } from "@/shared/components";
|
||||
import EvalsTab from "../usage/components/EvalsTab";
|
||||
import SearchAnalyticsTab from "./SearchAnalyticsTab";
|
||||
import CompressionAnalyticsTab from "./CompressionAnalyticsTab";
|
||||
import DiversityScoreCard from "./components/DiversityScoreCard";
|
||||
import ProviderUtilizationTab from "./ProviderUtilizationTab";
|
||||
import ComboHealthTab from "./ComboHealthTab";
|
||||
@@ -19,6 +20,7 @@ export default function AnalyticsPage() {
|
||||
search: "Search request analytics — provider breakdown, cache hit rate, and cost tracking.",
|
||||
utilization: t("utilizationDescription"),
|
||||
comboHealth: t("comboHealthDescription"),
|
||||
compression: t("compressionAnalyticsDescription"),
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -39,6 +41,7 @@ export default function AnalyticsPage() {
|
||||
{ value: "search", label: "Search" },
|
||||
{ value: "utilization", label: t("utilization") },
|
||||
{ value: "comboHealth", label: t("comboHealth") },
|
||||
{ value: "compression", label: t("compressionAnalyticsTitle") },
|
||||
]}
|
||||
value={activeTab}
|
||||
onChange={setActiveTab}
|
||||
@@ -56,6 +59,7 @@ export default function AnalyticsPage() {
|
||||
{activeTab === "search" && <SearchAnalyticsTab />}
|
||||
{activeTab === "utilization" && <ProviderUtilizationTab />}
|
||||
{activeTab === "comboHealth" && <ComboHealthTab />}
|
||||
{activeTab === "compression" && <CompressionAnalyticsTab />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1524,6 +1524,36 @@ function ComboCard({
|
||||
const tc = useTranslations("common");
|
||||
const emailsVisible = useEmailPrivacyStore((s) => s.emailsVisible);
|
||||
const strategyDescription = getStrategyDescription(t, strategy);
|
||||
const initialCompressionMode = combo?.config?.compressionMode || combo.compressionOverride || "";
|
||||
const [compressionOverride, setCompressionOverride] = useState(initialCompressionMode);
|
||||
const [isSavingCompression, setIsSavingCompression] = useState(false);
|
||||
|
||||
const handleCompressionOverrideChange = async (value) => {
|
||||
setCompressionOverride(value);
|
||||
setIsSavingCompression(true);
|
||||
const nextConfig = { ...(combo.config || {}) };
|
||||
if (value) {
|
||||
nextConfig.compressionMode = value;
|
||||
} else {
|
||||
delete nextConfig.compressionMode;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/combos/${combo.id}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ config: nextConfig }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.error("Failed to update compression override");
|
||||
setCompressionOverride(initialCompressionMode);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating compression override:", error);
|
||||
setCompressionOverride(initialCompressionMode);
|
||||
} finally {
|
||||
setIsSavingCompression(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -1656,7 +1686,21 @@ function ComboCard({
|
||||
{isDisabled ? "Disabled" : "Active"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 transition-opacity">
|
||||
<div className="flex items-center gap-1.5 transition-opacity">
|
||||
<select
|
||||
value={compressionOverride}
|
||||
onChange={(e) => handleCompressionOverrideChange(e.target.value)}
|
||||
disabled={isSavingCompression}
|
||||
className="text-xs py-1 px-2 rounded border border-black/10 dark:border-white/10 bg-white dark:bg-bg-main text-text-main focus:border-primary focus:outline-none transition-colors disabled:opacity-50 max-w-[130px] md:max-w-none"
|
||||
title="Compression Override"
|
||||
>
|
||||
<option value="">Default</option>
|
||||
<option value="off">Off</option>
|
||||
<option value="lite">Lite</option>
|
||||
<option value="standard">Standard</option>
|
||||
<option value="aggressive">Aggressive</option>
|
||||
<option value="ultra">Ultra</option>
|
||||
</select>
|
||||
<button
|
||||
onClick={onTest}
|
||||
disabled={testing}
|
||||
|
||||
20
src/app/(dashboard)/dashboard/compression/page.tsx
Normal file
20
src/app/(dashboard)/dashboard/compression/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import CompressionSettingsTab from "@/app/(dashboard)/dashboard/settings/components/CompressionSettingsTab";
|
||||
|
||||
export default function CompressionPage() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold flex items-center gap-2">
|
||||
<span className="material-symbols-outlined text-primary text-[28px]">compress</span>
|
||||
Compression
|
||||
</h1>
|
||||
<p className="text-sm text-text-muted mt-1">
|
||||
Configure context compression settings to reduce token usage and costs.
|
||||
</p>
|
||||
</div>
|
||||
<CompressionSettingsTab />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,15 @@ import dynamic from "next/dynamic";
|
||||
|
||||
const Editor = dynamic(() => import("@monaco-editor/react"), { ssr: false });
|
||||
|
||||
interface CompressionPreviewResult {
|
||||
originalTokens: number;
|
||||
compressedTokens: number;
|
||||
tokensSaved: number;
|
||||
savingsPct: number;
|
||||
techniquesUsed: string[];
|
||||
durationMs: number;
|
||||
}
|
||||
|
||||
export default function PlaygroundMode() {
|
||||
const t = useTranslations("translator");
|
||||
const tc = useTranslations("common");
|
||||
@@ -22,6 +31,14 @@ export default function PlaygroundMode() {
|
||||
const [translating, setTranslating] = useState(false);
|
||||
const [detecting, setDetecting] = useState(false);
|
||||
const [activeTemplate, setActiveTemplate] = useState(null);
|
||||
|
||||
// Compression preview state
|
||||
const [compressionMode, setCompressionMode] = useState<string>("standard");
|
||||
const [compressionResult, setCompressionResult] = useState<CompressionPreviewResult | null>(null);
|
||||
const [compressionLoading, setCompressionLoading] = useState(false);
|
||||
const [compressionError, setCompressionError] = useState<string | null>(null);
|
||||
const [showCompressionPanel, setShowCompressionPanel] = useState(false);
|
||||
|
||||
const templates = useMemo(() => getExampleTemplates(t), [t]);
|
||||
|
||||
// Auto-detect format when input changes
|
||||
@@ -152,6 +169,33 @@ export default function PlaygroundMode() {
|
||||
setDetectedFormat(null);
|
||||
};
|
||||
|
||||
const handleCompressionPreview = async () => {
|
||||
if (!inputContent.trim()) return;
|
||||
let messages;
|
||||
try {
|
||||
const parsed = JSON.parse(inputContent);
|
||||
messages = parsed.messages ?? [{ role: "user", content: inputContent }];
|
||||
} catch {
|
||||
messages = [{ role: "user", content: inputContent }];
|
||||
}
|
||||
setCompressionLoading(true);
|
||||
setCompressionError(null);
|
||||
try {
|
||||
const res = await fetch("/api/compression/preview", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ messages, mode: compressionMode }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error ?? "Preview failed");
|
||||
setCompressionResult(data);
|
||||
} catch (e: unknown) {
|
||||
setCompressionError(e instanceof Error ? e.message : String(e));
|
||||
} finally {
|
||||
setCompressionLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const srcMeta = FORMAT_META[sourceFormat] || FORMAT_META.openai;
|
||||
const tgtMeta = FORMAT_META[targetFormat] || FORMAT_META.openai;
|
||||
|
||||
@@ -461,6 +505,85 @@ export default function PlaygroundMode() {
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Compression Preview Panel */}
|
||||
<Card>
|
||||
<button
|
||||
className="flex items-center gap-2 w-full text-left p-4 font-medium text-text"
|
||||
onClick={() => setShowCompressionPanel((v) => !v)}
|
||||
>
|
||||
<span className="material-symbols-outlined text-primary text-[20px]">compress</span>
|
||||
Compression Preview
|
||||
<span className="material-symbols-outlined ml-auto text-text-muted text-[18px]">
|
||||
{showCompressionPanel ? "expand_less" : "expand_more"}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{showCompressionPanel && (
|
||||
<div className="p-4 space-y-4 border-t border-border">
|
||||
<div className="flex items-center gap-3">
|
||||
<Select
|
||||
value={compressionMode}
|
||||
onChange={(e) => setCompressionMode(e.target.value)}
|
||||
options={[
|
||||
{ value: "off", label: "Off" },
|
||||
{ value: "lite", label: "Lite" },
|
||||
{ value: "standard", label: "Standard" },
|
||||
{ value: "aggressive", label: "Aggressive" },
|
||||
{ value: "ultra", label: "Ultra" },
|
||||
]}
|
||||
className="text-sm"
|
||||
/>
|
||||
<Button
|
||||
icon="play_arrow"
|
||||
onClick={handleCompressionPreview}
|
||||
loading={compressionLoading}
|
||||
disabled={compressionLoading || !inputContent.trim()}
|
||||
className="text-sm"
|
||||
>
|
||||
{compressionLoading ? "Previewing…" : "Preview Compression"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{compressionError && <div className="text-sm text-red-500">{compressionError}</div>}
|
||||
|
||||
{compressionResult && (
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<div className="card p-3 text-center bg-black/5 dark:bg-white/5 rounded-lg border border-border">
|
||||
<div className="text-xs text-text-muted">Original</div>
|
||||
<div className="text-lg font-bold">{compressionResult.originalTokens}</div>
|
||||
<div className="text-xs text-text-muted">tokens</div>
|
||||
</div>
|
||||
<div className="card p-3 text-center bg-black/5 dark:bg-white/5 rounded-lg border border-border">
|
||||
<div className="text-xs text-text-muted">Compressed</div>
|
||||
<div className="text-lg font-bold">{compressionResult.compressedTokens}</div>
|
||||
<div className="text-xs text-text-muted">tokens</div>
|
||||
</div>
|
||||
<div className="card p-3 text-center bg-black/5 dark:bg-white/5 rounded-lg border border-border">
|
||||
<div className="text-xs text-text-muted">Saved</div>
|
||||
<div className="text-lg font-bold text-green-500">
|
||||
{compressionResult.tokensSaved}
|
||||
</div>
|
||||
<div className="text-xs text-text-muted">{compressionResult.savingsPct}%</div>
|
||||
</div>
|
||||
<div className="card p-3 text-center bg-black/5 dark:bg-white/5 rounded-lg border border-border">
|
||||
<div className="text-xs text-text-muted">Duration</div>
|
||||
<div className="text-lg font-bold">{compressionResult.durationMs}</div>
|
||||
<div className="text-xs text-text-muted">ms</div>
|
||||
</div>
|
||||
</div>
|
||||
{compressionResult.techniquesUsed.length > 0 && (
|
||||
<div className="text-xs text-text-muted">
|
||||
<span className="font-semibold">Techniques:</span>{" "}
|
||||
{compressionResult.techniquesUsed.join(", ")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
29
src/app/api/analytics/compression/route.ts
Normal file
29
src/app/api/analytics/compression/route.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* GET /api/analytics/compression
|
||||
*
|
||||
* Returns aggregated compression analytics from the compression_analytics table.
|
||||
* Supports ?since=24h|7d|30d|all (default: 24h).
|
||||
*/
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { enforceApiKeyPolicy } from "@/shared/utils/apiKeyPolicy";
|
||||
import { getCompressionAnalyticsSummary } from "@/lib/db/compressionAnalytics";
|
||||
|
||||
export async function GET(req: Request) {
|
||||
const policy = await enforceApiKeyPolicy(req, "analytics");
|
||||
if (policy.rejection) return policy.rejection;
|
||||
|
||||
try {
|
||||
const url = new URL(req.url);
|
||||
const sinceParam = url.searchParams.get("since") ?? "24h";
|
||||
const validSince = ["24h", "7d", "30d", "all"].includes(sinceParam) ? sinceParam : "24h";
|
||||
|
||||
const summary = getCompressionAnalyticsSummary(validSince === "all" ? undefined : validSince);
|
||||
|
||||
return NextResponse.json(summary);
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
console.error("[/api/analytics/compression]", msg);
|
||||
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -69,15 +69,33 @@ export async function PUT(request, { params }) {
|
||||
const allCombos = await getCombos();
|
||||
|
||||
const comboName = validation.data.name || currentCombo.name;
|
||||
const body = validation.data.models
|
||||
const normalizedUpdate = { ...validation.data };
|
||||
if (normalizedUpdate.compressionOverride !== undefined) {
|
||||
const legacyCompressionOverride = normalizedUpdate.compressionOverride;
|
||||
const nextConfig =
|
||||
currentCombo.config &&
|
||||
typeof currentCombo.config === "object" &&
|
||||
!Array.isArray(currentCombo.config)
|
||||
? { ...currentCombo.config }
|
||||
: {};
|
||||
if (legacyCompressionOverride) {
|
||||
nextConfig.compressionMode = legacyCompressionOverride;
|
||||
} else {
|
||||
delete nextConfig.compressionMode;
|
||||
}
|
||||
normalizedUpdate.config = nextConfig;
|
||||
delete normalizedUpdate.compressionOverride;
|
||||
}
|
||||
|
||||
const body = normalizedUpdate.models
|
||||
? {
|
||||
...validation.data,
|
||||
models: normalizeComboModels(validation.data.models, {
|
||||
...normalizedUpdate,
|
||||
models: normalizeComboModels(normalizedUpdate.models, {
|
||||
comboName,
|
||||
allCombos,
|
||||
}),
|
||||
}
|
||||
: validation.data;
|
||||
: normalizedUpdate;
|
||||
const nextComboState = {
|
||||
...currentCombo,
|
||||
...body,
|
||||
|
||||
86
src/app/api/compression/preview/route.ts
Normal file
86
src/app/api/compression/preview/route.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { enforceApiKeyPolicy } from "@/shared/utils/apiKeyPolicy";
|
||||
import { applyCompression } from "@omniroute/open-sse/services/compression/strategySelector";
|
||||
import type { CompressionMode } from "@omniroute/open-sse/services/compression/types";
|
||||
|
||||
const PreviewRequestSchema = z.object({
|
||||
messages: z
|
||||
.array(
|
||||
z.object({
|
||||
role: z.string(),
|
||||
content: z.union([z.string(), z.array(z.unknown())]),
|
||||
})
|
||||
)
|
||||
.min(1),
|
||||
mode: z.enum(["off", "lite", "standard", "aggressive", "ultra"]),
|
||||
});
|
||||
|
||||
function countTokens(text: string): number {
|
||||
return Math.ceil(text.split(/\s+/).filter(Boolean).length * 1.33);
|
||||
}
|
||||
|
||||
function messagesToText(messages: Array<{ role: string; content: unknown }>): string {
|
||||
return messages
|
||||
.map((m) => {
|
||||
const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
||||
return `${m.role}: ${content}`;
|
||||
})
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const policy = await enforceApiKeyPolicy(req, "settings");
|
||||
if (policy.rejection) return policy.rejection;
|
||||
|
||||
let body: unknown;
|
||||
try {
|
||||
body = await req.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const parsed = PreviewRequestSchema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid request", details: parsed.error.issues },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const { messages, mode } = parsed.data;
|
||||
const originalText = messagesToText(messages);
|
||||
const originalTokens = countTokens(originalText);
|
||||
|
||||
try {
|
||||
const start = Date.now();
|
||||
const requestBody = { messages };
|
||||
const result = await applyCompression(requestBody as Record<string, unknown>, mode);
|
||||
const durationMs = Date.now() - start;
|
||||
|
||||
const compressedMessages = (result.body.messages ?? messages) as Array<{
|
||||
role: string;
|
||||
content: unknown;
|
||||
}>;
|
||||
const compressedText = messagesToText(compressedMessages);
|
||||
const compressedTokens = countTokens(compressedText);
|
||||
const tokensSaved = Math.max(0, originalTokens - compressedTokens);
|
||||
const savingsPct = originalTokens > 0 ? Math.round((tokensSaved / originalTokens) * 100) : 0;
|
||||
const techniquesUsed: string[] = result.stats?.techniquesUsed ?? [];
|
||||
|
||||
return NextResponse.json({
|
||||
original: originalText,
|
||||
compressed: compressedText,
|
||||
originalTokens,
|
||||
compressedTokens,
|
||||
tokensSaved,
|
||||
savingsPct,
|
||||
techniquesUsed,
|
||||
durationMs,
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
console.error("[/api/compression/preview]", msg);
|
||||
return NextResponse.json({ error: "Compression failed", details: msg }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "الاستخدام",
|
||||
"utilizationDescription": "اتجاهات استخدام حصة المزود وتتبع حدود المعدل",
|
||||
"comboHealth": "صحة المجموعة",
|
||||
"comboHealthDescription": "الحصة على مستوى المجموعة وتوزيع الاستخدام ومقاييس الأداء"
|
||||
"comboHealthDescription": "الحصة على مستوى المجموعة وتوزيع الاستخدام ومقاييس الأداء",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "مفاتيح واجهة برمجة التطبيقات",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Използване",
|
||||
"utilizationDescription": "Тенденции в използването на квотата на доставчика и проследяване на ограниченията на скоростта",
|
||||
"comboHealth": "Здраве на комбинацията",
|
||||
"comboHealthDescription": "Квота на ниво комбинация, разпределение на използването и метрики на производителността"
|
||||
"comboHealthDescription": "Квота на ниво комбинация, разпределение на използването и метрики на производителността",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API ключове",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -826,7 +826,9 @@
|
||||
"modelStatusUnavailable": "Unavailable",
|
||||
"modelStatusError": "Error",
|
||||
"comboHealth": "Combo Health",
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics"
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics",
|
||||
"compressionAnalyticsTitle": "Compression",
|
||||
"compressionAnalyticsDescription": "Compression analytics - token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API Keys",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Využití",
|
||||
"utilizationDescription": "Trendy využití kvót poskytovatele a sledování limitů rychlosti",
|
||||
"comboHealth": "Zdraví Combo",
|
||||
"comboHealthDescription": "Kvóta na úrovni kombinace, distribuce využití a metriky výkonu"
|
||||
"comboHealthDescription": "Kvóta na úrovni kombinace, distribuce využití a metriky výkonu",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API Klíče",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Udnyttelse",
|
||||
"utilizationDescription": "Leverandørkvoteforbrugstendenser og hastighedsgrænseovervågning",
|
||||
"comboHealth": "Kombosundhed",
|
||||
"comboHealthDescription": "Komboniveaudkvote, fordeling af brug og ydeevnemålinger"
|
||||
"comboHealthDescription": "Komboniveaudkvote, fordeling af brug og ydeevnemålinger",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API nøgler",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Auslastung",
|
||||
"utilizationDescription": "Anbieter-Kontingent-Nutzungstrends und Ratenlimit-Verfolgung",
|
||||
"comboHealth": "Combo-Gesundheit",
|
||||
"comboHealthDescription": "Combo-level Kontingent, Nutzungsverteilung und Leistungsmetriken"
|
||||
"comboHealthDescription": "Combo-level Kontingent, Nutzungsverteilung und Leistungsmetriken",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API-Schlüssel",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -924,7 +924,9 @@
|
||||
"modelStatusUnavailable": "Unavailable",
|
||||
"modelStatusError": "Error",
|
||||
"comboHealth": "Combo Health",
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics"
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API Keys",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Utilización",
|
||||
"utilizationDescription": "Tendencias de uso de cuota del proveedor y seguimiento de límites de tasa",
|
||||
"comboHealth": "Salud del combo",
|
||||
"comboHealthDescription": "Cuota a nivel de combo, distribución de uso y métricas de rendimiento"
|
||||
"comboHealthDescription": "Cuota a nivel de combo, distribución de uso y métricas de rendimiento",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "Claves API",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -826,7 +826,9 @@
|
||||
"modelStatusUnavailable": "Unavailable",
|
||||
"modelStatusError": "Error",
|
||||
"comboHealth": "Combo Health",
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics"
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics",
|
||||
"compressionAnalyticsTitle": "Compression",
|
||||
"compressionAnalyticsDescription": "Compression analytics - token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API Keys",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Käyttöaste",
|
||||
"utilizationDescription": "Palveluntarjoajan kiintiön käyttötrendit ja nopeusrajoitusten seuranta",
|
||||
"comboHealth": "Yhdistelmän kunto",
|
||||
"comboHealthDescription": "Yhdistelmätason kiintiö, käytön jakautuminen ja suorituskykymittarit"
|
||||
"comboHealthDescription": "Yhdistelmätason kiintiö, käytön jakautuminen ja suorituskykymittarit",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API-avaimet",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Utilisation",
|
||||
"utilizationDescription": "Tendances d'utilisation du quota fournisseur et suivi des limites de débit",
|
||||
"comboHealth": "Santé du combo",
|
||||
"comboHealthDescription": "Quota au niveau combo, distribution de l'utilisation et métriques de performance"
|
||||
"comboHealthDescription": "Quota au niveau combo, distribution de l'utilisation et métriques de performance",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "Clés API",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -826,7 +826,9 @@
|
||||
"modelStatusUnavailable": "Unavailable",
|
||||
"modelStatusError": "Error",
|
||||
"comboHealth": "Combo Health",
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics"
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics",
|
||||
"compressionAnalyticsTitle": "Compression",
|
||||
"compressionAnalyticsDescription": "Compression analytics - token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API Keys",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "ניצול",
|
||||
"utilizationDescription": "מגמות שימוש במכסת הספק ומעקב אחר מגבלות קצב",
|
||||
"comboHealth": "בריאות הקומבו",
|
||||
"comboHealthDescription": "מכסה ברמת הקומבו, הפצת שימוש ומדדי ביצוע"
|
||||
"comboHealthDescription": "מכסה ברמת הקומבו, הפצת שימוש ומדדי ביצוע",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "מפתחות API",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "उपयोग",
|
||||
"utilizationDescription": "प्रदाता कोटा उपयोग रुझान और दर सीमा ट्रैकिंग",
|
||||
"comboHealth": "कॉम्बो स्वास्थ्य",
|
||||
"comboHealthDescription": "कॉम्बो-स्तरीय कोटा, उपयोग वितरण और प्रदर्शन मेट्रिक्स"
|
||||
"comboHealthDescription": "कॉम्बो-स्तरीय कोटा, उपयोग वितरण और प्रदर्शन मेट्रिक्स",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "एपीआई कुंजी",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Kihasználtság",
|
||||
"utilizationDescription": "Szolgáltatói kvótahasználati trendek és sebességkorlát-követés",
|
||||
"comboHealth": "Kombo egészsége",
|
||||
"comboHealthDescription": "Kombó szintű kvóta, használateloszlás és teljesítménymutatók"
|
||||
"comboHealthDescription": "Kombó szintű kvóta, használateloszlás és teljesítménymutatók",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API kulcsok",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Pemanfaatan",
|
||||
"utilizationDescription": "Tren penggunaan kuota penyedia dan pelacakan batas tarif",
|
||||
"comboHealth": "Kesehatan Kombinasi",
|
||||
"comboHealthDescription": "Kuota tingkat kombinasi, distribusi penggunaan, dan metrik kinerja"
|
||||
"comboHealthDescription": "Kuota tingkat kombinasi, distribusi penggunaan, dan metrik kinerja",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "Kunci API",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -826,7 +826,9 @@
|
||||
"modelStatusUnavailable": "Unavailable",
|
||||
"modelStatusError": "Error",
|
||||
"comboHealth": "Combo Health",
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics"
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics",
|
||||
"compressionAnalyticsTitle": "Compression",
|
||||
"compressionAnalyticsDescription": "Compression analytics - token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API Keys",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Utilizzo",
|
||||
"utilizationDescription": "Tendenze di utilizzo della quota del provider e monitoraggio dei limiti di frequenza",
|
||||
"comboHealth": "Salute Combo",
|
||||
"comboHealthDescription": "Quota a livello combo, distribuzione dell'utilizzo e metriche delle prestazioni"
|
||||
"comboHealthDescription": "Quota a livello combo, distribuzione dell'utilizzo e metriche delle prestazioni",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "Chiavi API",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "活用率",
|
||||
"utilizationDescription": "プロバイダーのクォータ使用傾向とレート制限の追跡",
|
||||
"comboHealth": "コンボ健全性",
|
||||
"comboHealthDescription": "コンボレベルのクォータ、使用分布、パフォーマンスメトリクス"
|
||||
"comboHealthDescription": "コンボレベルのクォータ、使用分布、パフォーマンスメトリクス",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "APIキー",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "활용률",
|
||||
"utilizationDescription": "공급자 할당량 사용 추세 및 속도 제한 추적",
|
||||
"comboHealth": "콤보 건강 상태",
|
||||
"comboHealthDescription": "콤보 수준 할당량, 사용 분포 및 성능 메트릭"
|
||||
"comboHealthDescription": "콤보 수준 할당량, 사용 분포 및 성능 메트릭",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API 키",
|
||||
@@ -3261,6 +3263,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -826,7 +826,9 @@
|
||||
"modelStatusUnavailable": "Unavailable",
|
||||
"modelStatusError": "Error",
|
||||
"comboHealth": "Combo Health",
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics"
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics",
|
||||
"compressionAnalyticsTitle": "Compression",
|
||||
"compressionAnalyticsDescription": "Compression analytics - token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API Keys",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Pemanfaatan",
|
||||
"utilizationDescription": "Tren penggunaan kuota pembekal dan penjejakan had kadar",
|
||||
"comboHealth": "Kesihatan Combo",
|
||||
"comboHealthDescription": "Kuota peringkat combo, pengagihan penggunaan dan metrik prestasi"
|
||||
"comboHealthDescription": "Kuota peringkat combo, pengagihan penggunaan dan metrik prestasi",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "Kunci API",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Benutting",
|
||||
"utilizationDescription": "Trends in quotagebruik van de provider en bijhouden van snelheidslimieten",
|
||||
"comboHealth": "Combo-gezondheid",
|
||||
"comboHealthDescription": "Quota op combo-niveau, gebruiksdistributie en prestatiegegevens"
|
||||
"comboHealthDescription": "Quota op combo-niveau, gebruiksdistributie en prestatiegegevens",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API-sleutels",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Utnyttelse",
|
||||
"utilizationDescription": "Leverandørkvotebrukstrender og hastighetsgrensesporing",
|
||||
"comboHealth": "Kombohelse",
|
||||
"comboHealthDescription": "Kombonivåkvote, bruksfordeling og ytelsesmålinger"
|
||||
"comboHealthDescription": "Kombonivåkvote, bruksfordeling og ytelsesmålinger",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API-nøkler",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Paggamit",
|
||||
"utilizationDescription": "Mga trend sa paggamit ng quota ng provider at pagsubaybay sa rate limit",
|
||||
"comboHealth": "Kalusugan ng Combo",
|
||||
"comboHealthDescription": "Quota sa antas ng combo, pamamahagi ng paggamit, at mga metrics ng pagganap"
|
||||
"comboHealthDescription": "Quota sa antas ng combo, pamamahagi ng paggamit, at mga metrics ng pagganap",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "Mga API Key",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Wykorzystanie",
|
||||
"utilizationDescription": "Trendy wykorzystania kwoty dostawcy i śledzenie limitów szybkości",
|
||||
"comboHealth": "Zdrowie Kombinacji",
|
||||
"comboHealthDescription": "Kwota na poziomie kombinacji, dystrybucja użycia i metryki wydajności"
|
||||
"comboHealthDescription": "Kwota na poziomie kombinacji, dystrybucja użycia i metryki wydajności",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "Klucze API",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Utilização",
|
||||
"utilizationDescription": "Tendências de uso de cota do provedor e rastreamento de limites de taxa",
|
||||
"comboHealth": "Saúde do Combo",
|
||||
"comboHealthDescription": "Cota em nível de combo, distribuição de uso e métricas de desempenho"
|
||||
"comboHealthDescription": "Cota em nível de combo, distribuição de uso e métricas de desempenho",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "Chaves de API",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Utilização",
|
||||
"utilizationDescription": "Tendências de uso de quota do provedor e rastreamento de limites de taxa",
|
||||
"comboHealth": "Saúde do Combo",
|
||||
"comboHealthDescription": "Quota ao nível do combo, distribuição de uso e métricas de desempenho"
|
||||
"comboHealthDescription": "Quota ao nível do combo, distribuição de uso e métricas de desempenho",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "Chaves de API",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Utilizare",
|
||||
"utilizationDescription": "Tendințe de utilizare a cotelor furnizorului și urmărirea limitelor de rată",
|
||||
"comboHealth": "Sănătatea Combo-ului",
|
||||
"comboHealthDescription": "Cotă la nivel de combo, distribuția utilizării și metricile de performanță"
|
||||
"comboHealthDescription": "Cotă la nivel de combo, distribuția utilizării și metricile de performanță",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "Chei API",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Использование",
|
||||
"utilizationDescription": "Тенденции использования квоты поставщика и отслеживание ограничений скорости",
|
||||
"comboHealth": "Здоровье комбо",
|
||||
"comboHealthDescription": "Квота на уровне комбо, распределение использования и метрики производительности"
|
||||
"comboHealthDescription": "Квота на уровне комбо, распределение использования и метрики производительности",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API-ключи",
|
||||
@@ -3283,6 +3285,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Využitie",
|
||||
"utilizationDescription": "Trendy využitia kvót poskytovateľa a sledovanie limitov rýchlosti",
|
||||
"comboHealth": "Zdravie Combo",
|
||||
"comboHealthDescription": "Kvóta na úrovni combo, distribúcia používania a metríky výkonu"
|
||||
"comboHealthDescription": "Kvóta na úrovni combo, distribúcia používania a metríky výkonu",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API kľúče",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Användning",
|
||||
"utilizationDescription": "Leverantörskvotanvändningstrender och hastighetsgränsuppföljning",
|
||||
"comboHealth": "Kombohälsa",
|
||||
"comboHealthDescription": "Kombonivåkvot, användningsfördelning och prestandamått"
|
||||
"comboHealthDescription": "Kombonivåkvot, användningsfördelning och prestandamått",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API-nycklar",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -826,7 +826,9 @@
|
||||
"modelStatusUnavailable": "Unavailable",
|
||||
"modelStatusError": "Error",
|
||||
"comboHealth": "Combo Health",
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics"
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics",
|
||||
"compressionAnalyticsTitle": "Compression",
|
||||
"compressionAnalyticsDescription": "Compression analytics - token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API Keys",
|
||||
|
||||
@@ -826,7 +826,9 @@
|
||||
"modelStatusUnavailable": "Unavailable",
|
||||
"modelStatusError": "Error",
|
||||
"comboHealth": "Combo Health",
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics"
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics",
|
||||
"compressionAnalyticsTitle": "Compression",
|
||||
"compressionAnalyticsDescription": "Compression analytics - token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API Keys",
|
||||
|
||||
@@ -826,7 +826,9 @@
|
||||
"modelStatusUnavailable": "Unavailable",
|
||||
"modelStatusError": "Error",
|
||||
"comboHealth": "Combo Health",
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics"
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics",
|
||||
"compressionAnalyticsTitle": "Compression",
|
||||
"compressionAnalyticsDescription": "Compression analytics - token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API Keys",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "การใช้งาน",
|
||||
"utilizationDescription": "แนวโน้มการใช้โควต้าของผู้ให้บริการและการติดตามขีดจำกัดอัตรา",
|
||||
"comboHealth": "สุขภาพคอมโบ",
|
||||
"comboHealthDescription": "โควต้าระดับคอมโบ การกระจายการใช้งาน และตัวชั่งวัดประสิทธิภาพ"
|
||||
"comboHealthDescription": "โควต้าระดับคอมโบ การกระจายการใช้งาน และตัวชั่งวัดประสิทธิภาพ",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "คีย์ API",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Kullanım",
|
||||
"utilizationDescription": "Sağlayıcı kota kullanım trendleri ve hız limiti takibi",
|
||||
"comboHealth": "Kombo Sağlığı",
|
||||
"comboHealthDescription": "Kombo düzeyinde kota, kullanım dağılımı ve performans metrikleri"
|
||||
"comboHealthDescription": "Kombo düzeyinde kota, kullanım dağılımı ve performans metrikleri",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API Anahtarları",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Використання",
|
||||
"utilizationDescription": "Тренди використання квоти постачальника та відстеження обмежень швидкості",
|
||||
"comboHealth": "Здоров'я комбінації",
|
||||
"comboHealthDescription": "Квота на рівні комбінації, розподіл використання та метрики продуктивності"
|
||||
"comboHealthDescription": "Квота на рівні комбінації, розподіл використання та метрики продуктивності",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "Ключі API",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -826,7 +826,9 @@
|
||||
"modelStatusUnavailable": "Unavailable",
|
||||
"modelStatusError": "Error",
|
||||
"comboHealth": "Combo Health",
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics"
|
||||
"comboHealthDescription": "Combo-level quota, usage distribution, and performance metrics",
|
||||
"compressionAnalyticsTitle": "Compression",
|
||||
"compressionAnalyticsDescription": "Compression analytics - token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API Keys",
|
||||
|
||||
@@ -822,7 +822,9 @@
|
||||
"utilization": "Tỷ lệ sử dụng",
|
||||
"utilizationDescription": "Xu hướng sử dụng hạn ngạch nhà cung cấp và theo dõi giới hạn tốc độ",
|
||||
"comboHealth": "Sức khỏe Combo",
|
||||
"comboHealthDescription": "Hạn ngạch cấp combo, phân phối sử dụng và số liệu hiệu suất"
|
||||
"comboHealthDescription": "Hạn ngạch cấp combo, phân phối sử dụng và số liệu hiệu suất",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "Khóa API",
|
||||
@@ -3259,6 +3261,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
@@ -826,7 +826,9 @@
|
||||
"modelStatusUnavailable": "不可用",
|
||||
"modelStatusError": "错误",
|
||||
"comboHealth": "组合健康状况",
|
||||
"comboHealthDescription": "组合级别配额、使用分布和性能指标"
|
||||
"comboHealthDescription": "组合级别配额、使用分布和性能指标",
|
||||
"compressionAnalyticsTitle": "Compression Analytics",
|
||||
"compressionAnalyticsDescription": "Compression analytics — token savings, mode breakdown, and provider stats."
|
||||
},
|
||||
"apiManager": {
|
||||
"title": "API 密钥",
|
||||
@@ -3368,6 +3370,10 @@
|
||||
"compressionModeLiteDesc": "Whitespace and blank line reduction",
|
||||
"compressionModeStandard": "Standard (Caveman)",
|
||||
"compressionModeStandardDesc": "Rule-based compression with 30+ patterns, preserves code blocks and URLs",
|
||||
"compressionModeAggressive": "Aggressive",
|
||||
"compressionModeAggressiveDesc": "Summarization + tool result compression + progressive aging for maximum savings",
|
||||
"compressionModeUltra": "Ultra",
|
||||
"compressionModeUltraDesc": "Maximum compression with all techniques including semantic deduplication",
|
||||
"compressionGeneral": "General Settings",
|
||||
"compressionAutoTrigger": "Auto-Trigger Threshold",
|
||||
"compressionCacheTTL": "Cache TTL",
|
||||
|
||||
134
src/lib/db/compressionAnalytics.ts
Normal file
134
src/lib/db/compressionAnalytics.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { getDbInstance } from "./core";
|
||||
|
||||
export interface CompressionAnalyticsRow {
|
||||
id?: number;
|
||||
timestamp: string;
|
||||
combo_id?: string | null;
|
||||
provider?: string | null;
|
||||
mode: string;
|
||||
original_tokens: number;
|
||||
compressed_tokens: number;
|
||||
tokens_saved: number;
|
||||
duration_ms?: number | null;
|
||||
request_id?: string | null;
|
||||
}
|
||||
|
||||
export interface CompressionAnalyticsSummary {
|
||||
totalRequests: number;
|
||||
totalTokensSaved: number;
|
||||
avgSavingsPct: number;
|
||||
avgDurationMs: number;
|
||||
byMode: Record<string, { count: number; tokensSaved: number; avgSavingsPct: number }>;
|
||||
byProvider: Record<string, { count: number; tokensSaved: number }>;
|
||||
last24h: Array<{ hour: string; count: number; tokensSaved: number }>;
|
||||
}
|
||||
|
||||
export function insertCompressionAnalyticsRow(row: CompressionAnalyticsRow): void {
|
||||
const db = getDbInstance();
|
||||
db.prepare(
|
||||
`
|
||||
INSERT INTO compression_analytics (timestamp, combo_id, provider, mode, original_tokens, compressed_tokens, tokens_saved, duration_ms, request_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`
|
||||
).run(
|
||||
row.timestamp,
|
||||
row.combo_id ?? null,
|
||||
row.provider ?? null,
|
||||
row.mode,
|
||||
row.original_tokens,
|
||||
row.compressed_tokens,
|
||||
row.tokens_saved,
|
||||
row.duration_ms ?? null,
|
||||
row.request_id ?? null
|
||||
);
|
||||
}
|
||||
|
||||
export function getCompressionAnalyticsSummary(since?: string): CompressionAnalyticsSummary {
|
||||
const db = getDbInstance();
|
||||
|
||||
let cutoff: string | null = null;
|
||||
if (since === "24h") {
|
||||
cutoff = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
||||
} else if (since === "7d") {
|
||||
cutoff = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
||||
} else if (since === "30d") {
|
||||
cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
|
||||
}
|
||||
|
||||
const whereClause = cutoff ? "WHERE timestamp >= ?" : "";
|
||||
const params = cutoff ? [cutoff] : [];
|
||||
|
||||
type ScalarRow = { total: number; totalSaved: number; avgPct: number; avgDur: number };
|
||||
const scalar = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
COALESCE(SUM(tokens_saved), 0) as totalSaved,
|
||||
COALESCE(AVG(CASE WHEN original_tokens > 0 THEN CAST(tokens_saved AS REAL) / original_tokens * 100 ELSE 0 END), 0) as avgPct,
|
||||
COALESCE(AVG(duration_ms), 0) as avgDur
|
||||
FROM compression_analytics ${whereClause}
|
||||
`
|
||||
)
|
||||
.get(...params) as ScalarRow;
|
||||
|
||||
const modeRows = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT mode, COUNT(*) as cnt, COALESCE(SUM(tokens_saved), 0) as saved,
|
||||
COALESCE(AVG(CASE WHEN original_tokens > 0 THEN CAST(tokens_saved AS REAL) / original_tokens * 100 ELSE 0 END), 0) as avgPct
|
||||
FROM compression_analytics ${whereClause}
|
||||
GROUP BY mode
|
||||
`
|
||||
)
|
||||
.all(...params) as Array<{ mode: string; cnt: number; saved: number; avgPct: number }>;
|
||||
|
||||
const byMode: Record<string, { count: number; tokensSaved: number; avgSavingsPct: number }> = {};
|
||||
for (const r of modeRows) {
|
||||
byMode[r.mode] = { count: r.cnt, tokensSaved: r.saved, avgSavingsPct: Math.round(r.avgPct) };
|
||||
}
|
||||
|
||||
const provRows = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT provider, COUNT(*) as cnt, COALESCE(SUM(tokens_saved), 0) as saved
|
||||
FROM compression_analytics ${whereClause}
|
||||
GROUP BY provider ORDER BY cnt DESC
|
||||
`
|
||||
)
|
||||
.all(...params) as Array<{ provider: string | null; cnt: number; saved: number }>;
|
||||
|
||||
const byProvider: Record<string, { count: number; tokensSaved: number }> = {};
|
||||
for (const r of provRows) {
|
||||
const key = r.provider ?? "unknown";
|
||||
byProvider[key] = { count: r.cnt, tokensSaved: r.saved };
|
||||
}
|
||||
|
||||
const hourRows = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT strftime('%Y-%m-%dT%H:00:00Z', timestamp) as hour,
|
||||
COUNT(*) as cnt, COALESCE(SUM(tokens_saved), 0) as saved
|
||||
FROM compression_analytics
|
||||
WHERE timestamp >= ?
|
||||
GROUP BY hour ORDER BY hour ASC
|
||||
`
|
||||
)
|
||||
.all(new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()) as Array<{
|
||||
hour: string;
|
||||
cnt: number;
|
||||
saved: number;
|
||||
}>;
|
||||
|
||||
const last24h = hourRows.map((r) => ({ hour: r.hour, count: r.cnt, tokensSaved: r.saved }));
|
||||
|
||||
return {
|
||||
totalRequests: scalar.total,
|
||||
totalTokensSaved: scalar.totalSaved,
|
||||
avgSavingsPct: Math.round(scalar.avgPct),
|
||||
avgDurationMs: Math.round(scalar.avgDur),
|
||||
byMode,
|
||||
byProvider,
|
||||
last24h,
|
||||
};
|
||||
}
|
||||
13
src/lib/db/migrations/038_compression_analytics.sql
Normal file
13
src/lib/db/migrations/038_compression_analytics.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
-- 038: Compression analytics table for Phase 5 feature
|
||||
CREATE TABLE IF NOT EXISTS compression_analytics (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
timestamp TEXT NOT NULL,
|
||||
combo_id TEXT,
|
||||
provider TEXT,
|
||||
mode TEXT NOT NULL,
|
||||
original_tokens INTEGER NOT NULL,
|
||||
compressed_tokens INTEGER NOT NULL,
|
||||
tokens_saved INTEGER NOT NULL,
|
||||
duration_ms INTEGER,
|
||||
request_id TEXT
|
||||
);
|
||||
@@ -323,6 +323,16 @@ export {
|
||||
persistCreditBalance,
|
||||
} from "./db/creditBalance";
|
||||
|
||||
export {
|
||||
insertCompressionAnalyticsRow,
|
||||
getCompressionAnalyticsSummary,
|
||||
} from "./db/compressionAnalytics";
|
||||
|
||||
export type {
|
||||
CompressionAnalyticsRow,
|
||||
CompressionAnalyticsSummary,
|
||||
} from "./db/compressionAnalytics";
|
||||
|
||||
export {
|
||||
// Reasoning Replay Cache (#1628)
|
||||
setReasoningCache,
|
||||
|
||||
@@ -360,6 +360,9 @@ const compositeTiersSchema = z
|
||||
})
|
||||
.strict();
|
||||
|
||||
const compressionModeSchema = z.enum(["off", "lite", "standard", "aggressive", "ultra"]);
|
||||
const comboCompressionOverrideSchema = z.union([z.literal(""), compressionModeSchema]);
|
||||
|
||||
const comboRuntimeConfigSchema = z
|
||||
.object({
|
||||
strategy: comboStrategySchema.optional(),
|
||||
@@ -376,6 +379,7 @@ const comboRuntimeConfigSchema = z
|
||||
maxMessagesForSummary: z.coerce.number().int().min(5).max(100).optional(),
|
||||
maxComboDepth: z.coerce.number().int().min(1).max(10).optional(),
|
||||
trackMetrics: z.boolean().optional(),
|
||||
compressionMode: compressionModeSchema.optional(),
|
||||
// Auto-Combo / LKGP Extensions
|
||||
candidatePool: z.array(z.string().min(1)).optional(),
|
||||
weights: scoringWeightsSchema.optional(),
|
||||
@@ -1321,6 +1325,7 @@ export const updateComboSchema = z
|
||||
tool_filter_regex: z.string().max(1000).optional(),
|
||||
context_cache_protection: z.boolean().optional(),
|
||||
context_length: z.number().int().min(1000).max(2000000).optional(),
|
||||
compressionOverride: comboCompressionOverrideSchema.optional(),
|
||||
})
|
||||
.superRefine((value, ctx) => {
|
||||
if (
|
||||
@@ -1333,7 +1338,8 @@ export const updateComboSchema = z
|
||||
value.system_message === undefined &&
|
||||
value.tool_filter_regex === undefined &&
|
||||
value.context_cache_protection === undefined &&
|
||||
value.context_length === undefined
|
||||
value.context_length === undefined &&
|
||||
value.compressionOverride === undefined
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
|
||||
@@ -13,6 +13,8 @@ const core = await import("../../src/lib/db/core.ts");
|
||||
const providersDb = await import("../../src/lib/db/providers.ts");
|
||||
const readCacheDb = await import("../../src/lib/db/readCache.ts");
|
||||
const combosDb = await import("../../src/lib/db/combos.ts");
|
||||
const compressionDb = await import("../../src/lib/db/compression.ts");
|
||||
const compressionAnalyticsDb = await import("../../src/lib/db/compressionAnalytics.ts");
|
||||
const { handleChatCore } = await import("../../open-sse/handlers/chatCore.ts");
|
||||
const { estimateTokens, getTokenLimit } = await import("../../open-sse/services/contextManager.ts");
|
||||
const { resetAllCircuitBreakers } = await import("../../src/shared/utils/circuitBreaker.ts");
|
||||
@@ -434,3 +436,69 @@ test("chatCore integration: combo requests run proactive compression before Kiro
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test("chatCore integration: modular compression records analytics row best-effort", async () => {
|
||||
const provider = "openai";
|
||||
const model = "gpt-4";
|
||||
|
||||
await compressionDb.updateCompressionSettings({
|
||||
enabled: true,
|
||||
defaultMode: "lite",
|
||||
autoTriggerTokens: 0,
|
||||
});
|
||||
|
||||
const connection = await providersDb.createProviderConnection({
|
||||
provider,
|
||||
apiKey: "test-key",
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
const body = {
|
||||
model,
|
||||
stream: false,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: `Please help with this request. ${"Keep spacing. ".repeat(400)}\n\n\n\nFinal line.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
globalThis.fetch = async () =>
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
choices: [{ message: { role: "assistant", content: "ok" } }],
|
||||
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
const result = await handleChatCore({
|
||||
body,
|
||||
modelInfo: { provider, model },
|
||||
credentials: { apiKey: "test-key" },
|
||||
log: { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} },
|
||||
clientRawRequest: { endpoint: "/v1/chat/completions", headers: new Map() },
|
||||
connectionId: connection.id,
|
||||
});
|
||||
|
||||
assert.ok(result.success, "Request should succeed");
|
||||
|
||||
let summary = compressionAnalyticsDb.getCompressionAnalyticsSummary();
|
||||
for (let attempt = 0; attempt < 20 && summary.totalRequests === 0; attempt += 1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
summary = compressionAnalyticsDb.getCompressionAnalyticsSummary();
|
||||
}
|
||||
|
||||
assert.equal(summary.totalRequests, 1);
|
||||
assert.ok(summary.totalTokensSaved > 0, "Analytics should record token savings");
|
||||
assert.equal(summary.byMode.lite.count, 1);
|
||||
assert.equal(summary.byProvider.openai.count, 1);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
251
tests/unit/compression/compressionAnalytics.test.ts
Normal file
251
tests/unit/compression/compressionAnalytics.test.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
import { describe, it, before, beforeEach, after } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const tmpDir = mkdtempSync(join(tmpdir(), "omniroute-test-"));
|
||||
process.env.DATA_DIR = tmpDir;
|
||||
|
||||
import {
|
||||
insertCompressionAnalyticsRow,
|
||||
getCompressionAnalyticsSummary,
|
||||
} from "../../../src/lib/db/compressionAnalytics.js";
|
||||
import { getDbInstance } from "../../../src/lib/db/core.js";
|
||||
|
||||
describe("compressionAnalytics", () => {
|
||||
before(() => {
|
||||
const db = getDbInstance();
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS compression_analytics (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
timestamp TEXT NOT NULL,
|
||||
combo_id TEXT,
|
||||
provider TEXT,
|
||||
mode TEXT NOT NULL,
|
||||
original_tokens INTEGER NOT NULL,
|
||||
compressed_tokens INTEGER NOT NULL,
|
||||
tokens_saved INTEGER NOT NULL,
|
||||
duration_ms INTEGER,
|
||||
request_id TEXT
|
||||
)
|
||||
`);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Clear table before each test for full isolation
|
||||
const db = getDbInstance();
|
||||
db.exec("DELETE FROM compression_analytics");
|
||||
});
|
||||
|
||||
after(() => {
|
||||
rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("empty table returns zeroed summary", () => {
|
||||
const summary = getCompressionAnalyticsSummary();
|
||||
assert.deepEqual(summary, {
|
||||
totalRequests: 0,
|
||||
totalTokensSaved: 0,
|
||||
avgSavingsPct: 0,
|
||||
avgDurationMs: 0,
|
||||
byMode: {},
|
||||
byProvider: {},
|
||||
last24h: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("insert single row does not throw", () => {
|
||||
const row = {
|
||||
timestamp: new Date().toISOString(),
|
||||
mode: "lite",
|
||||
original_tokens: 1000,
|
||||
compressed_tokens: 800,
|
||||
tokens_saved: 200,
|
||||
};
|
||||
assert.doesNotThrow(() => insertCompressionAnalyticsRow(row));
|
||||
const summary = getCompressionAnalyticsSummary();
|
||||
assert.equal(summary.totalRequests, 1);
|
||||
});
|
||||
|
||||
it("summary counts correctly after multiple inserts", () => {
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: new Date().toISOString(),
|
||||
mode: "lite",
|
||||
original_tokens: 1000,
|
||||
compressed_tokens: 800,
|
||||
tokens_saved: 200,
|
||||
});
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: new Date().toISOString(),
|
||||
mode: "standard",
|
||||
original_tokens: 300,
|
||||
compressed_tokens: 280,
|
||||
tokens_saved: 20,
|
||||
});
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: new Date().toISOString(),
|
||||
mode: "aggressive",
|
||||
original_tokens: 500,
|
||||
compressed_tokens: 400,
|
||||
tokens_saved: 100,
|
||||
});
|
||||
const summary = getCompressionAnalyticsSummary();
|
||||
assert.equal(summary.totalRequests, 3);
|
||||
});
|
||||
|
||||
it("totalTokensSaved sums correctly", () => {
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: new Date().toISOString(),
|
||||
mode: "lite",
|
||||
original_tokens: 1000,
|
||||
compressed_tokens: 800,
|
||||
tokens_saved: 200,
|
||||
});
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: new Date().toISOString(),
|
||||
mode: "standard",
|
||||
original_tokens: 300,
|
||||
compressed_tokens: 280,
|
||||
tokens_saved: 20,
|
||||
});
|
||||
const summary = getCompressionAnalyticsSummary();
|
||||
assert.equal(summary.totalTokensSaved, 220);
|
||||
});
|
||||
|
||||
it("byMode groups correctly", () => {
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: new Date().toISOString(),
|
||||
mode: "lite",
|
||||
original_tokens: 1000,
|
||||
compressed_tokens: 800,
|
||||
tokens_saved: 200,
|
||||
});
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: new Date().toISOString(),
|
||||
mode: "lite",
|
||||
original_tokens: 500,
|
||||
compressed_tokens: 400,
|
||||
tokens_saved: 100,
|
||||
});
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: new Date().toISOString(),
|
||||
mode: "standard",
|
||||
original_tokens: 300,
|
||||
compressed_tokens: 270,
|
||||
tokens_saved: 30,
|
||||
});
|
||||
const summary = getCompressionAnalyticsSummary();
|
||||
assert.equal(summary.byMode["lite"].count, 2);
|
||||
assert.equal(summary.byMode["lite"].tokensSaved, 300);
|
||||
assert.equal(summary.byMode["standard"].count, 1);
|
||||
});
|
||||
|
||||
it("byProvider groups correctly", () => {
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: new Date().toISOString(),
|
||||
mode: "lite",
|
||||
original_tokens: 1000,
|
||||
compressed_tokens: 800,
|
||||
tokens_saved: 200,
|
||||
provider: "ProviderA",
|
||||
});
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: new Date().toISOString(),
|
||||
mode: "lite",
|
||||
original_tokens: 500,
|
||||
compressed_tokens: 400,
|
||||
tokens_saved: 100,
|
||||
provider: "ProviderB",
|
||||
});
|
||||
const summary = getCompressionAnalyticsSummary();
|
||||
assert.deepEqual(summary.byProvider["ProviderA"], { count: 1, tokensSaved: 200 });
|
||||
assert.deepEqual(summary.byProvider["ProviderB"], { count: 1, tokensSaved: 100 });
|
||||
});
|
||||
|
||||
it("since=24h filters rows older than 24h", () => {
|
||||
const oldTimestamp = new Date(Date.now() - 48 * 60 * 60 * 1000).toISOString();
|
||||
const recentTimestamp = new Date().toISOString();
|
||||
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: oldTimestamp,
|
||||
mode: "lite",
|
||||
original_tokens: 2000,
|
||||
compressed_tokens: 1800,
|
||||
tokens_saved: 200,
|
||||
});
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: recentTimestamp,
|
||||
mode: "lite",
|
||||
original_tokens: 1000,
|
||||
compressed_tokens: 900,
|
||||
tokens_saved: 100,
|
||||
});
|
||||
|
||||
const summary24h = getCompressionAnalyticsSummary("24h");
|
||||
assert.equal(summary24h.totalRequests, 1);
|
||||
assert.equal(summary24h.totalTokensSaved, 100);
|
||||
});
|
||||
|
||||
it("since=undefined returns all rows including old ones", () => {
|
||||
const oldTimestamp = new Date(Date.now() - 48 * 60 * 60 * 1000).toISOString();
|
||||
const recentTimestamp = new Date().toISOString();
|
||||
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: oldTimestamp,
|
||||
mode: "lite",
|
||||
original_tokens: 2000,
|
||||
compressed_tokens: 1800,
|
||||
tokens_saved: 200,
|
||||
});
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: recentTimestamp,
|
||||
mode: "lite",
|
||||
original_tokens: 1000,
|
||||
compressed_tokens: 900,
|
||||
tokens_saved: 100,
|
||||
});
|
||||
|
||||
const summaryAll = getCompressionAnalyticsSummary();
|
||||
assert.equal(summaryAll.totalRequests, 2);
|
||||
assert.equal(summaryAll.totalTokensSaved, 300);
|
||||
});
|
||||
|
||||
it("avgSavingsPct calculates correctly", () => {
|
||||
// 200/1000 = 20%, 100/500 = 20% → avg = 20%
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: new Date().toISOString(),
|
||||
mode: "lite",
|
||||
original_tokens: 1000,
|
||||
compressed_tokens: 800,
|
||||
tokens_saved: 200,
|
||||
});
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: new Date().toISOString(),
|
||||
mode: "standard",
|
||||
original_tokens: 500,
|
||||
compressed_tokens: 400,
|
||||
tokens_saved: 100,
|
||||
});
|
||||
const summary = getCompressionAnalyticsSummary();
|
||||
assert.equal(summary.avgSavingsPct, 20);
|
||||
});
|
||||
|
||||
it("last24h hourly buckets have correct shape", () => {
|
||||
insertCompressionAnalyticsRow({
|
||||
timestamp: new Date().toISOString(),
|
||||
mode: "lite",
|
||||
original_tokens: 1000,
|
||||
compressed_tokens: 800,
|
||||
tokens_saved: 200,
|
||||
});
|
||||
const hourly = getCompressionAnalyticsSummary("24h").last24h;
|
||||
assert(Array.isArray(hourly));
|
||||
assert(hourly.length <= 24);
|
||||
hourly.forEach((bucket) => {
|
||||
assert(typeof bucket.hour === "string");
|
||||
assert(typeof bucket.count === "number");
|
||||
assert(typeof bucket.tokensSaved === "number");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user