Merge pull request #1756 from oyi77/feat/compression-phase5

feat(compression): Phase 5 — Dashboard UI & Analytics (#1590)
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-04-30 17:56:51 -03:00
committed by GitHub
56 changed files with 1421 additions and 49 deletions

View File

@@ -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(",")})`

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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}

View 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>
);
}

View File

@@ -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>
);
}

View 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 });
}
}

View File

@@ -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,

View 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 });
}
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View 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,
};
}

View 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
);

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;
}
});

View 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");
});
});
});