ai_api / src /app /(dashboard) /dashboard /context /settings /CompressionPanel.tsx
Yogesh
initial deploy
cd8bd0a
Raw
History Blame Contribute Delete
18.8 kB
"use client";
// CompressionPanel — the single-source engine-grid UI for compression.
//
// Renders the master on/off switch, one row per catalog engine (on/off + level +
// link to its detail page), the cavemanOutput intensity row, the mcpAccessibility
// toggle (its own endpoint / separate store), a read-only derived-pipeline preview,
// and the general settings (auto-trigger tokens + preserve-system-prompt).
//
// Engine rows use the catalog label/description (hardcoded English) directly — NOT
// i18n — so they stay deterministic. Human-facing chrome (master, general) keeps the
// app's i18n via useTranslations("settings").
import Link from "next/link";
import { useEffect, useState } from "react";
import { useTranslations, useLocale } from "next-intl";
// Import Card/Toggle from their direct module paths rather than the @/shared/components
// barrel: the barrel transitively pulls a heavy/Node-only module that hangs the
// vitest/jsdom component test. Direct imports resolve identically under Next.js.
import Card from "@/shared/components/Card";
import Toggle from "@/shared/components/Toggle";
import {
ENGINE_IDS,
engineMeta,
} from "../../../../../../open-sse/services/compression/engineCatalog.ts";
import {
OUTPUT_STYLE_IDS,
outputStyleMeta,
} from "../../../../../../open-sse/services/compression/outputStyles/catalog.ts";
import { deriveDefaultPlan } from "../../../../../../open-sse/services/compression/deriveDefaultPlan.ts";
import {
DEFAULT_CONTEXT_BUDGET,
type ContextBudgetConfig,
} from "../../../../../../open-sse/services/compression/adaptiveCompression/types.ts";
import { formatAdaptiveTarget } from "./adaptiveTargetLabel.ts";
type CavemanIntensity = "lite" | "full" | "ultra";
interface EngineToggle {
enabled: boolean;
level?: string;
}
interface CavemanOutputModeConfig {
enabled: boolean;
intensity: CavemanIntensity;
autoClarity: boolean;
}
interface CompressionConfig {
enabled: boolean;
autoTriggerTokens: number;
preserveSystemPrompt: boolean;
engines: Record<string, EngineToggle>;
activeComboId: string | null;
cavemanOutputMode?: CavemanOutputModeConfig;
outputStyles?: Array<{ id: string; level: CavemanIntensity }>;
// Phase 4 (B): two-tier `ultra` mode controls.
// ultraEngine "heuristic" = Tier-A token pruner (default, byte-identical to pre-B);
// "slm" = Tier-B LLMLingua-2 ONNX worker when available, else fail-open to Tier-A.
ultraEngine?: "heuristic" | "slm";
// Best-effort pre-warm of the SLM model on enable / cold restart. Default false.
ultraSlmPrewarm?: boolean;
// Phase 4 (C): adaptive context-budget. Absent / mode:"off" = legacy auto-trigger.
// The panel currently surfaces the computed target read-only; mode/policy editors are a
// follow-up (the load/save path does not yet populate this field).
contextBudget?: ContextBudgetConfig;
}
const CAVEMAN_OUTPUT_LEVELS: CavemanIntensity[] = ["lite", "full", "ultra"];
const DEFAULT_CONFIG: CompressionConfig = {
enabled: false,
autoTriggerTokens: 0,
preserveSystemPrompt: true,
engines: {},
activeComboId: null,
cavemanOutputMode: { enabled: false, intensity: "full", autoClarity: true },
outputStyles: [],
ultraEngine: "heuristic",
ultraSlmPrewarm: false,
};
function normalizeEngines(raw: unknown): Record<string, EngineToggle> {
const engines: Record<string, EngineToggle> = {};
const source = (raw && typeof raw === "object" ? raw : {}) as Record<string, EngineToggle>;
for (const id of ENGINE_IDS) {
const cur = source[id];
engines[id] = cur ? { enabled: cur.enabled === true, ...(cur.level ? { level: cur.level } : {}) } : { enabled: false };
}
return engines;
}
export default function CompressionPanel() {
const t = useTranslations("settings");
// D-A6/§7: locale-gated styles (e.g. terse-cjk → zh) are only OFFERED under their locale.
// Compare the UI language base ("zh-CN" → "zh") against the style's `locale`.
const uiLang = (useLocale() || "en").split("-")[0];
const [config, setConfig] = useState<CompressionConfig>(DEFAULT_CONFIG);
const [mcpAccessibility, setMcpAccessibility] = useState(true);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [status, setStatus] = useState<"" | "saved" | "error">("");
useEffect(() => {
fetch("/api/settings/compression")
.then((r) => (r.ok ? r.json() : null))
.then((data: Partial<CompressionConfig> | null) => {
if (data) {
setConfig({
...DEFAULT_CONFIG,
...data,
engines: normalizeEngines(data.engines),
cavemanOutputMode: data.cavemanOutputMode ?? DEFAULT_CONFIG.cavemanOutputMode,
outputStyles: data.outputStyles ?? DEFAULT_CONFIG.outputStyles,
});
}
})
.catch(() => {})
.finally(() => setLoading(false));
fetch("/api/settings/compression/mcp-accessibility")
.then((r) => (r.ok ? r.json() : null))
.then((data: { enabled?: boolean } | null) => {
if (data && typeof data.enabled === "boolean") setMcpAccessibility(data.enabled);
})
.catch(() => {});
}, []);
// Persist a merge-patch. The DB persists `engines` as one whole row, so callers that
// touch an engine pass the full engines map to avoid dropping the other engines.
const save = async (updates: Partial<CompressionConfig>) => {
const next = { ...config, ...updates };
setConfig(next);
setSaving(true);
setStatus("");
try {
const res = await fetch("/api/settings/compression", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(updates),
});
if (res.ok) {
setStatus("saved");
setTimeout(() => setStatus(""), 2000);
} else {
setStatus("error");
}
} catch {
setStatus("error");
} finally {
setSaving(false);
}
};
const setEngine = (id: string, patch: Partial<EngineToggle>) => {
const engines = {
...config.engines,
[id]: { ...(config.engines[id] ?? { enabled: false }), ...patch },
};
// Send the full engines map — the persistence layer stores it as one JSON row.
save({ engines });
};
const setOutputStyle = (id: string, patch: { enabled?: boolean; level?: CavemanIntensity }) => {
const current = config.outputStyles ?? [];
const existing = current.find((s) => s.id === id);
let next = current;
if (patch.enabled === false) {
next = current.filter((s) => s.id !== id);
} else {
const level = patch.level ?? existing?.level ?? "full";
next = existing
? current.map((s) => (s.id === id ? { id, level } : s))
: [...current, { id, level }];
}
// Persist in catalog order so injection order is stable.
const ordered = OUTPUT_STYLE_IDS.flatMap((sid) => {
const hit = next.find((s) => s.id === sid);
return hit ? [hit] : [];
});
save({ outputStyles: ordered });
};
const toggleMcpAccessibility = async (enabled: boolean) => {
setMcpAccessibility(enabled);
try {
await fetch("/api/settings/compression/mcp-accessibility", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ enabled }),
});
} catch {
// Surface nothing — the row reflects optimistic local state; the next mount re-reads.
}
};
const derived = deriveDefaultPlan(config.engines, config.enabled);
const derivedText =
derived.mode === "off"
? "off"
: derived.stackedPipeline.length > 0
? `runs: ${derived.stackedPipeline.map((s) => s.engine).join(" → ")}`
: `mode: ${derived.mode}`;
if (loading) {
return (
<Card className="p-6">
<p className="text-sm text-text-muted">{t("loading")}</p>
</Card>
);
}
return (
<Card className="p-6" data-testid="compression-panel">
{/* Master */}
<div className="mb-5 flex items-start justify-between gap-4">
<div className="flex items-center gap-3">
<div className="rounded-lg bg-blue-500/10 p-2 text-blue-500">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
compress
</span>
</div>
<div>
<h3 className="text-lg font-semibold">{t("compressionTitle")}</h3>
<p className="text-sm text-text-muted">{t("compressionDesc")}</p>
</div>
</div>
<div className="flex items-center gap-3">
{status === "saved" && (
<span className="flex items-center gap-1 text-xs font-medium text-emerald-500">
<span className="material-symbols-outlined text-[14px]">check_circle</span>{" "}
{t("saved")}
</span>
)}
{status === "error" && (
<span className="flex items-center gap-1 text-xs font-medium text-red-500">
<span className="material-symbols-outlined text-[14px]">error</span>{" "}
{t("saveFailed")}
</span>
)}
<Toggle
size="md"
checked={config.enabled}
onChange={(enabled) => save({ enabled })}
disabled={saving}
ariaLabel={t("compressionTitle")}
/>
</div>
</div>
{/* Derived pipeline preview */}
<div
data-testid="derived-pipeline-preview"
className="mb-4 rounded-md border border-border/60 bg-bg-subtle px-3 py-2 text-xs text-text-muted"
>
<span className="font-medium text-text-main">Effective pipeline:</span> {derivedText}
</div>
{/* Adaptive context-budget — read-only computed target (Phase 4C, D-C1 transparency) */}
<div
data-testid="adaptive-target-preview"
className="mb-4 rounded-md border border-border/60 bg-bg-subtle px-3 py-2 text-xs text-text-muted"
>
{formatAdaptiveTarget(config.contextBudget ?? DEFAULT_CONTEXT_BUDGET, 200000)}
</div>
{/* Engine grid */}
<div className={`divide-y divide-border ${config.enabled ? "" : "opacity-60"}`}>
{ENGINE_IDS.map((id) => {
const meta = engineMeta(id);
const engine = config.engines[id] ?? { enabled: false };
const levels = meta.levels;
const level = engine.level ?? levels?.[0] ?? "";
return (
<div
key={id}
data-testid={`engine-row-${id}`}
className="flex flex-col gap-2 py-3 sm:flex-row sm:items-center sm:justify-between"
>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 text-sm font-medium text-text-main">
{meta.label}
<Link
href={`/dashboard/context/${id}`}
className="rounded border border-border bg-bg-subtle px-1.5 py-0.5 text-[10px] uppercase tracking-wider text-text-muted hover:border-primary/40 hover:text-primary"
>
{id}
</Link>
</div>
<p className="mt-0.5 text-xs text-text-muted">{meta.description}</p>
</div>
<div className="flex shrink-0 items-center gap-2">
{levels && (
<select
value={level}
onChange={(e) => setEngine(id, { level: e.target.value })}
disabled={!config.enabled || !engine.enabled || saving}
className="w-28 rounded border border-border bg-surface px-2 py-1 text-xs text-text-main"
>
{levels.map((lvl) => (
<option key={lvl} value={lvl}>
{lvl}
</option>
))}
</select>
)}
<span data-testid={`engine-toggle-${id}`}>
<Toggle
size="sm"
checked={engine.enabled}
onChange={(enabled) => setEngine(id, { enabled })}
disabled={!config.enabled || saving}
ariaLabel={meta.label}
/>
</span>
</div>
</div>
);
})}
</div>
{/* Output Styles — response-output instruction injection (Phase 4A, catalog-driven) */}
<div className="mt-2 flex flex-col gap-3 border-t border-border/30 py-3">
<div className="min-w-0 flex-1">
<p className="text-sm font-medium text-text-main">
{t("compressionSettingsOutputStyles")}
</p>
<p className="mt-0.5 text-xs text-text-muted">
Inject response-shaping instructions without rewriting provider output. Combine freely.
</p>
</div>
{OUTPUT_STYLE_IDS.filter((id) => {
const m = outputStyleMeta(id);
return !m?.locale || m.locale === uiLang;
}).map((id) => {
const meta = outputStyleMeta(id);
const sel = config.outputStyles?.find((s) => s.id === id);
return (
<div
key={id}
data-testid={`output-style-row-${id}`}
className="flex items-center justify-between gap-2"
>
<div className="min-w-0">
<p className="text-sm text-text-main">{meta.label}</p>
{meta.description && (
<p className="text-xs text-text-muted">{meta.description}</p>
)}
</div>
<div className="flex shrink-0 items-center gap-2">
<select
data-testid={`output-style-level-${id}`}
value={sel?.level ?? "full"}
onChange={(e) =>
setOutputStyle(id, { level: e.target.value as CavemanIntensity })
}
disabled={!sel || saving}
className="w-28 rounded border border-border bg-surface px-2 py-1 text-xs text-text-main"
>
{CAVEMAN_OUTPUT_LEVELS.map((lvl) => (
<option key={lvl} value={lvl}>
{lvl}
</option>
))}
</select>
<span data-testid={`output-style-toggle-${id}`}>
<Toggle
size="sm"
checked={Boolean(sel)}
onChange={(enabled) => setOutputStyle(id, { enabled })}
disabled={saving}
ariaLabel={meta.label}
/>
</span>
</div>
</div>
);
})}
</div>
{/* Ultra SLM tier — Phase 4 (B): pick the `ultra`-mode engine (heuristic Tier-A
or the opt-in LLMLingua-2 SLM Tier-B) + best-effort pre-warm. */}
<div className="mt-2 flex flex-col gap-3 border-t border-border/30 py-3">
<label className="flex items-center justify-between">
<span className="text-sm font-medium text-text-main">
{t("compressionUltraEngine")}
</span>
<select
data-testid="ultra-engine-select"
value={config.ultraEngine ?? "heuristic"}
onChange={(e) =>
save({ ultraEngine: e.target.value === "slm" ? "slm" : "heuristic" })
}
disabled={saving}
className="w-44 rounded border border-border bg-surface px-2 py-1 text-sm text-text-main"
>
<option value="heuristic">{t("compressionUltraEngineHeuristic")}</option>
<option value="slm">{t("compressionUltraEngineSlm")}</option>
</select>
</label>
{config.ultraEngine === "slm" && (
<>
<p className="text-xs text-text-muted">{t("compressionUltraSlmHint")}</p>
<label className="flex items-center justify-between">
<span className="text-sm text-text-muted">
{t("compressionUltraSlmPrewarm")}
</span>
<span data-testid="ultra-slm-prewarm-toggle">
<Toggle
size="sm"
checked={config.ultraSlmPrewarm ?? false}
onChange={(ultraSlmPrewarm) => save({ ultraSlmPrewarm })}
disabled={saving}
ariaLabel={t("compressionUltraSlmPrewarm")}
/>
</span>
</label>
</>
)}
</div>
{/* mcpAccessibility — writes its own endpoint / separate store */}
<div className="flex flex-col gap-2 border-t border-border/30 py-3 sm:flex-row sm:items-center sm:justify-between">
<div className="min-w-0 flex-1">
<p className="text-sm font-medium text-text-main">{t("mcpAccessibilityTitle")}</p>
<p className="mt-0.5 text-xs text-text-muted">
Scopes MCP tool outputs (separate store).
</p>
</div>
<span data-testid="mcp-accessibility-toggle">
<Toggle
size="sm"
checked={mcpAccessibility}
onChange={toggleMcpAccessibility}
ariaLabel={t("mcpAccessibilityTitle")}
/>
</span>
</div>
{/* General */}
<div className="space-y-3 border-t border-border/30 pt-4">
<h4 className="text-sm font-medium text-text-main">{t("compressionGeneral")}</h4>
<label className="flex items-center justify-between">
<span className="text-sm text-text-muted">{t("compressionAutoTrigger")}</span>
<div className="flex items-center gap-2">
<input
type="number"
min={0}
max={100000}
value={config.autoTriggerTokens}
onChange={(e) => save({ autoTriggerTokens: parseInt(e.target.value) || 0 })}
className="w-24 rounded border border-border bg-surface px-2 py-1 text-sm text-text-main"
/>
<span className="text-xs text-text-muted">{t("tokens")}</span>
</div>
</label>
<label className="flex items-center justify-between">
<span className="text-sm text-text-muted">{t("compressionPreserveSystem")}</span>
<span data-testid="preserve-system-toggle">
<Toggle
size="sm"
checked={config.preserveSystemPrompt}
onChange={(preserveSystemPrompt) => save({ preserveSystemPrompt })}
disabled={saving}
ariaLabel={t("compressionPreserveSystem")}
/>
</span>
</label>
</div>
</Card>
);
}