mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
Validated in local merge-train @ 8f27177d1 (full parity suite green: typecheck+file-size+complexity+cognitive+changelog+unit shards 1&2+vitest)
This commit is contained in:
committed by
GitHub
parent
c71eeae4ad
commit
f5d705c277
1
changelog.d/features/7530-compression-guidance.md
Normal file
1
changelog.d/features/7530-compression-guidance.md
Normal file
@@ -0,0 +1 @@
|
||||
- **feat(dashboard):** surface in-product guidance for the Settings → Prompt Compression engines — each `engineCatalog.ts` entry now carries a `guidance` block (quality/latency tradeoffs, lossy flag, cache impact) sourced from `docs/compression/*.md`, rendered as an expandable per-engine detail with a "safe default" indicator for lossless engines (Session Dedup, CCR, Lite, Headroom) plus a link to the full compression guide (#7530).
|
||||
@@ -1,3 +1,23 @@
|
||||
// Cache impact is a qualitative estimate of how much an engine's per-request output
|
||||
// variance disrupts upstream prompt-prefix caching (e.g. Anthropic/OpenAI prompt
|
||||
// caching): "none"/"low" = deterministic, cache-friendly; "high" = output shape varies
|
||||
// enough across requests (summarization, query-dependent pruning) that cached prefixes
|
||||
// are less likely to be reused. Source: docs/compression/COMPRESSION_GUIDE.md,
|
||||
// docs/compression/COMPRESSION_ENGINES.md (#7530).
|
||||
export type CacheImpact = "none" | "low" | "moderate" | "high";
|
||||
|
||||
export interface EngineGuidance {
|
||||
// Short, in-product explanation of the quality/latency tradeoff — adapted from
|
||||
// docs/compression/COMPRESSION_GUIDE.md / COMPRESSION_ENGINES.md, not invented.
|
||||
tradeoffs: string;
|
||||
// true = the engine can drop/alter content a later turn might have needed (semantic
|
||||
// condensation, summarization, pruning). false = structural/formatting-only, safe to
|
||||
// leave on. "Safe default" status is DERIVED from this flag (see isSafeDefault) rather
|
||||
// than duplicated as its own field.
|
||||
lossy: boolean;
|
||||
cacheImpact: CacheImpact;
|
||||
}
|
||||
|
||||
export interface EngineMeta {
|
||||
id: string;
|
||||
label: string;
|
||||
@@ -5,6 +25,7 @@ export interface EngineMeta {
|
||||
levels?: string[]; // intensity options; undefined = no level selector
|
||||
isSingleMode: boolean; // can be the effective mode when it is the only engine on
|
||||
description: string;
|
||||
guidance: EngineGuidance;
|
||||
}
|
||||
|
||||
export const ENGINE_CATALOG: Record<string, EngineMeta> = {
|
||||
@@ -14,6 +35,12 @@ export const ENGINE_CATALOG: Record<string, EngineMeta> = {
|
||||
stackPriority: 3,
|
||||
isSingleMode: false,
|
||||
description: "Cross-turn block deduplication.",
|
||||
guidance: {
|
||||
tradeoffs:
|
||||
"Lossless — elides only text already sent earlier in the same session; nothing is summarized or dropped. Negligible latency overhead.",
|
||||
lossy: false,
|
||||
cacheImpact: "low",
|
||||
},
|
||||
},
|
||||
ccr: {
|
||||
id: "ccr",
|
||||
@@ -21,6 +48,12 @@ export const ENGINE_CATALOG: Record<string, EngineMeta> = {
|
||||
stackPriority: 4,
|
||||
isSingleMode: false,
|
||||
description: "Content-addressed retrieval markers.",
|
||||
guidance: {
|
||||
tradeoffs:
|
||||
"Lossless — replaces large repeated/contiguous blocks with content-addressed references instead of deleting them; the original content stays retrievable.",
|
||||
lossy: false,
|
||||
cacheImpact: "low",
|
||||
},
|
||||
},
|
||||
lite: {
|
||||
id: "lite",
|
||||
@@ -28,6 +61,12 @@ export const ENGINE_CATALOG: Record<string, EngineMeta> = {
|
||||
stackPriority: 5,
|
||||
isSingleMode: true,
|
||||
description: "Whitespace/format cleanup.",
|
||||
guidance: {
|
||||
tradeoffs:
|
||||
"Safest mode (~15% savings, <1ms latency): whitespace/dedup/formatting cleanup only, zero semantic change. Always safe to leave on.",
|
||||
lossy: false,
|
||||
cacheImpact: "none",
|
||||
},
|
||||
},
|
||||
rtk: {
|
||||
id: "rtk",
|
||||
@@ -36,6 +75,12 @@ export const ENGINE_CATALOG: Record<string, EngineMeta> = {
|
||||
levels: ["minimal", "standard", "aggressive"],
|
||||
isSingleMode: true,
|
||||
description: "Command-output filtering.",
|
||||
guidance: {
|
||||
tradeoffs:
|
||||
"Strips ANSI noise, progress bars, and repeated lines from command/tool output while preserving failures, warnings, and summaries (60-90% upstream savings). The 'aggressive' level trims more tail context than 'minimal'/'standard'.",
|
||||
lossy: true,
|
||||
cacheImpact: "moderate",
|
||||
},
|
||||
},
|
||||
headroom: {
|
||||
id: "headroom",
|
||||
@@ -43,6 +88,12 @@ export const ENGINE_CATALOG: Record<string, EngineMeta> = {
|
||||
stackPriority: 15,
|
||||
isSingleMode: false,
|
||||
description: "Tabular JSON compaction.",
|
||||
guidance: {
|
||||
tradeoffs:
|
||||
"Lossless columnar compaction (SmartCrusher) of homogeneous JSON-array payloads into a compact '[N rows]' form — no data is discarded.",
|
||||
lossy: false,
|
||||
cacheImpact: "low",
|
||||
},
|
||||
},
|
||||
relevance: {
|
||||
id: "relevance",
|
||||
@@ -50,6 +101,12 @@ export const ENGINE_CATALOG: Record<string, EngineMeta> = {
|
||||
stackPriority: 18,
|
||||
isSingleMode: true,
|
||||
description: "Extractive sentence scoring against the last user query.",
|
||||
guidance: {
|
||||
tradeoffs:
|
||||
"Drops sentences scored as less relevant to the last user query — output depends on the query, so it can omit context a later turn needs.",
|
||||
lossy: true,
|
||||
cacheImpact: "moderate",
|
||||
},
|
||||
},
|
||||
caveman: {
|
||||
id: "caveman",
|
||||
@@ -58,6 +115,12 @@ export const ENGINE_CATALOG: Record<string, EngineMeta> = {
|
||||
levels: ["lite", "full", "ultra"],
|
||||
isSingleMode: true,
|
||||
description: "Rule-based prose compression.",
|
||||
guidance: {
|
||||
tradeoffs:
|
||||
"Rule-based prose condensation (~30% savings at 'full'): strips filler and hedging while preserving meaning, but rewrites text so it is not byte-identical to the original.",
|
||||
lossy: true,
|
||||
cacheImpact: "moderate",
|
||||
},
|
||||
},
|
||||
aggressive: {
|
||||
id: "aggressive",
|
||||
@@ -65,6 +128,12 @@ export const ENGINE_CATALOG: Record<string, EngineMeta> = {
|
||||
stackPriority: 30,
|
||||
isSingleMode: true,
|
||||
description: "Summarize + age old turns.",
|
||||
guidance: {
|
||||
tradeoffs:
|
||||
"Summarizes and progressively ages older turns (~50% savings) — trades older-turn fidelity for context headroom in long sessions; summarized turns can't be perfectly reconstructed.",
|
||||
lossy: true,
|
||||
cacheImpact: "high",
|
||||
},
|
||||
},
|
||||
llmlingua: {
|
||||
id: "llmlingua",
|
||||
@@ -72,6 +141,12 @@ export const ENGINE_CATALOG: Record<string, EngineMeta> = {
|
||||
stackPriority: 35,
|
||||
isSingleMode: false,
|
||||
description: "Semantic pruning (ONNX).",
|
||||
guidance: {
|
||||
tradeoffs:
|
||||
"Semantic token pruning via a small ONNX classifier — removes individual tokens judged low-information. Fail-opens (returns the original text) on any error, so the worst case is no savings, never corruption.",
|
||||
lossy: true,
|
||||
cacheImpact: "high",
|
||||
},
|
||||
},
|
||||
ultra: {
|
||||
id: "ultra",
|
||||
@@ -79,6 +154,12 @@ export const ENGINE_CATALOG: Record<string, EngineMeta> = {
|
||||
stackPriority: 40,
|
||||
isSingleMode: true,
|
||||
description: "Heuristic token pruning (+ optional SLM).",
|
||||
guidance: {
|
||||
tradeoffs:
|
||||
"Maximum-compression mode (~75% savings): heuristic pruning, code-block thinning, and binary-search truncation. Highest risk of losing context a later turn depended on — best reserved for hitting context limits.",
|
||||
lossy: true,
|
||||
cacheImpact: "high",
|
||||
},
|
||||
},
|
||||
omniglyph: {
|
||||
id: "omniglyph",
|
||||
@@ -86,6 +167,12 @@ export const ENGINE_CATALOG: Record<string, EngineMeta> = {
|
||||
stackPriority: 90,
|
||||
isSingleMode: true,
|
||||
description: "Contexto-como-imagem (Claude Fable 5, rota direta).",
|
||||
guidance: {
|
||||
tradeoffs:
|
||||
"Experimental context-as-image encoding routed directly to Claude Fable 5 only — the most aggressive and least broadly compatible option; not recommended as a general-purpose default.",
|
||||
lossy: true,
|
||||
cacheImpact: "high",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -96,3 +183,11 @@ export const ENGINE_IDS: string[] = Object.values(ENGINE_CATALOG)
|
||||
export function engineMeta(id: string): EngineMeta {
|
||||
return ENGINE_CATALOG[id];
|
||||
}
|
||||
|
||||
// "Safe default" = not lossy. Derived rather than a duplicated stored field (open
|
||||
// question resolved in #7530: engineCatalog had no prior lossy-style attribute, so we
|
||||
// added one and compute safe-default from it instead of two independently-maintained
|
||||
// booleans).
|
||||
export function isSafeDefault(id: string): boolean {
|
||||
return !engineMeta(id).guidance.lossy;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
outputStyleMeta,
|
||||
} from "../../../../../../open-sse/services/compression/outputStyles/catalog.ts";
|
||||
import { deriveDefaultPlan } from "../../../../../../open-sse/services/compression/deriveDefaultPlan.ts";
|
||||
import EngineGuidanceDetail from "./EngineGuidanceDetail";
|
||||
import {
|
||||
DEFAULT_CONTEXT_BUDGET,
|
||||
type ContextBudgetConfig,
|
||||
@@ -99,6 +100,9 @@ export default function CompressionPanel() {
|
||||
const uiLang = (useLocale() || "en").split("-")[0];
|
||||
const [config, setConfig] = useState<CompressionConfig>(DEFAULT_CONFIG);
|
||||
const [mcpAccessibility, setMcpAccessibility] = useState(true);
|
||||
// #7530 — per-engine expandable guidance (tradeoffs/lossy/cache-impact); collapsed by
|
||||
// default so the grid stays scannable.
|
||||
const [expandedGuidance, setExpandedGuidance] = useState<Record<string, boolean>>({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [status, setStatus] = useState<"" | "saved" | "error">("");
|
||||
@@ -163,6 +167,10 @@ export default function CompressionPanel() {
|
||||
save({ engines });
|
||||
};
|
||||
|
||||
const toggleGuidance = (id: string) => {
|
||||
setExpandedGuidance((prev) => ({ ...prev, [id]: !prev[id] }));
|
||||
};
|
||||
|
||||
const setOutputStyle = (id: string, patch: { enabled?: boolean; level?: CavemanIntensity }) => {
|
||||
const current = config.outputStyles ?? [];
|
||||
const existing = current.find((s) => s.id === id);
|
||||
@@ -225,6 +233,18 @@ export default function CompressionPanel() {
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">{t("compressionTitle")}</h3>
|
||||
<p className="text-sm text-text-muted">{t("compressionDesc")}</p>
|
||||
<a
|
||||
href="https://github.com/diegosouzapw/OmniRoute/blob/main/docs/compression/COMPRESSION_GUIDE.md"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-testid="compression-guide-link"
|
||||
className="mt-0.5 inline-flex items-center gap-1 text-xs text-primary hover:underline"
|
||||
>
|
||||
{t("compressionGuidanceFullGuideLink")}
|
||||
<span className="material-symbols-outlined text-[12px]" aria-hidden="true">
|
||||
open_in_new
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -290,6 +310,12 @@ export default function CompressionPanel() {
|
||||
</Link>
|
||||
</div>
|
||||
<p className="mt-0.5 text-xs text-text-muted">{meta.description}</p>
|
||||
<EngineGuidanceDetail
|
||||
id={id}
|
||||
guidance={meta.guidance}
|
||||
expanded={Boolean(expandedGuidance[id])}
|
||||
onToggle={() => toggleGuidance(id)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
{levels && (
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
"use client";
|
||||
|
||||
// EngineGuidanceDetail — per-engine expandable guidance block for CompressionPanel
|
||||
// (#7530). Surfaces the `guidance` metadata from engineCatalog.ts (tradeoffs, lossy
|
||||
// flag → "safe default" badge, cache impact) so operators can see quality/latency
|
||||
// tradeoffs without leaving Settings. Extracted into its own component to keep
|
||||
// CompressionPanel's per-row JSX flat (cognitive-complexity ratchet).
|
||||
//
|
||||
// Like the rest of the engine row, the guidance copy itself is catalog-hardcoded
|
||||
// English (not i18n) so it stays deterministic; only the surrounding chrome (toggle
|
||||
// button, badge, "cache impact" label) is translated.
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { CacheImpact, EngineGuidance } from "../../../../../../open-sse/services/compression/engineCatalog.ts";
|
||||
|
||||
const CACHE_IMPACT_LABEL: Record<CacheImpact, string> = {
|
||||
none: "None",
|
||||
low: "Low",
|
||||
moderate: "Moderate",
|
||||
high: "High",
|
||||
};
|
||||
|
||||
interface EngineGuidanceDetailProps {
|
||||
id: string;
|
||||
guidance: EngineGuidance;
|
||||
expanded: boolean;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
export default function EngineGuidanceDetail({
|
||||
id,
|
||||
guidance,
|
||||
expanded,
|
||||
onToggle,
|
||||
}: EngineGuidanceDetailProps) {
|
||||
const t = useTranslations("settings");
|
||||
return (
|
||||
<div className="mt-1 flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
{!guidance.lossy && (
|
||||
<span
|
||||
data-testid={`engine-safe-default-${id}`}
|
||||
className="rounded border border-emerald-500/30 bg-emerald-500/10 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wider text-emerald-500"
|
||||
>
|
||||
{t("compressionGuidanceSafeDefault")}
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
data-testid={`engine-guidance-toggle-${id}`}
|
||||
onClick={onToggle}
|
||||
aria-expanded={expanded}
|
||||
className="text-[10px] uppercase tracking-wider text-primary hover:underline"
|
||||
>
|
||||
{expanded ? t("compressionGuidanceHide") : t("compressionGuidanceShow")}
|
||||
</button>
|
||||
</div>
|
||||
{expanded && (
|
||||
<div
|
||||
data-testid={`engine-guidance-detail-${id}`}
|
||||
className="rounded border border-border/50 bg-bg-subtle p-2 text-xs text-text-muted"
|
||||
>
|
||||
<p>{guidance.tradeoffs}</p>
|
||||
<p className="mt-1">
|
||||
<span className="font-medium text-text-main">
|
||||
{t("compressionGuidanceCacheImpact")}:
|
||||
</span>{" "}
|
||||
{CACHE_IMPACT_LABEL[guidance.cacheImpact]}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5729,6 +5729,11 @@
|
||||
"clearSyncedPricing": "Clear Synced Pricing",
|
||||
"compressionTitle": "Prompt Compression",
|
||||
"compressionDesc": "Reduce token usage by compressing prompts before sending to providers",
|
||||
"compressionGuidanceFullGuideLink": "Full compression guide",
|
||||
"compressionGuidanceShow": "Details",
|
||||
"compressionGuidanceHide": "Hide details",
|
||||
"compressionGuidanceSafeDefault": "Safe default",
|
||||
"compressionGuidanceCacheImpact": "Cache impact",
|
||||
"tokenSaverTitle": "Token Saver",
|
||||
"tokenSaverSubtitle": "Spend fewer tokens on every request.",
|
||||
"tokenSaverToolOutput": "Tool output",
|
||||
|
||||
@@ -5691,6 +5691,11 @@
|
||||
"clearSyncedPricing": "Limpar Sync",
|
||||
"compressionTitle": "Prompt Compression",
|
||||
"compressionDesc": "Reduce token usage by compressing prompts before sending to providers",
|
||||
"compressionGuidanceFullGuideLink": "Guia completo de compressão",
|
||||
"compressionGuidanceShow": "Detalhes",
|
||||
"compressionGuidanceHide": "Ocultar detalhes",
|
||||
"compressionGuidanceSafeDefault": "Padrão seguro",
|
||||
"compressionGuidanceCacheImpact": "Impacto no cache",
|
||||
"tokenSaverTitle": "__MISSING__:Token Saver",
|
||||
"tokenSaverSubtitle": "__MISSING__:Spend fewer tokens on every request.",
|
||||
"tokenSaverToolOutput": "__MISSING__:Tool output",
|
||||
|
||||
54
tests/unit/compression/engine-guidance-7530.test.ts
Normal file
54
tests/unit/compression/engine-guidance-7530.test.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
// #7530 — in-product guidance for Prompt Compression engines.
|
||||
//
|
||||
// Regression guard for the richer `guidance` metadata on every engineCatalog.ts entry
|
||||
// (tradeoffs, lossy flag, cache impact) and the derived "safe default" helper. This is
|
||||
// the TDD-required test (Hard Rule #18): it FAILED before the `guidance` field existed
|
||||
// (every `engineMeta(id).guidance` was `undefined`) and passes now that every catalog
|
||||
// entry carries complete guidance.
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
ENGINE_IDS,
|
||||
engineMeta,
|
||||
isSafeDefault,
|
||||
type CacheImpact,
|
||||
} from "@omniroute/open-sse/services/compression/engineCatalog.ts";
|
||||
|
||||
const VALID_CACHE_IMPACTS: CacheImpact[] = ["none", "low", "moderate", "high"];
|
||||
|
||||
test("every engine in the catalog has a complete guidance entry", () => {
|
||||
for (const id of ENGINE_IDS) {
|
||||
const meta = engineMeta(id);
|
||||
assert.ok(meta.guidance, `${id} is missing a guidance entry`);
|
||||
assert.equal(typeof meta.guidance.tradeoffs, "string", `${id} guidance.tradeoffs must be a string`);
|
||||
assert.ok(
|
||||
meta.guidance.tradeoffs.length >= 20,
|
||||
`${id} guidance.tradeoffs reads as a placeholder (too short): "${meta.guidance.tradeoffs}"`
|
||||
);
|
||||
assert.equal(typeof meta.guidance.lossy, "boolean", `${id} guidance.lossy must be a boolean`);
|
||||
assert.ok(
|
||||
VALID_CACHE_IMPACTS.includes(meta.guidance.cacheImpact),
|
||||
`${id} guidance.cacheImpact "${meta.guidance.cacheImpact}" is not one of ${VALID_CACHE_IMPACTS.join(", ")}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("isSafeDefault is derived from guidance.lossy (no duplicated flag)", () => {
|
||||
for (const id of ENGINE_IDS) {
|
||||
assert.equal(isSafeDefault(id), !engineMeta(id).guidance.lossy, `${id} safe-default mismatch`);
|
||||
}
|
||||
});
|
||||
|
||||
test("lossless structural engines are flagged as safe defaults", () => {
|
||||
for (const id of ["session-dedup", "ccr", "lite", "headroom"]) {
|
||||
assert.equal(isSafeDefault(id), true, `${id} should be a safe default (lossless)`);
|
||||
assert.equal(engineMeta(id).guidance.lossy, false, `${id} should be marked non-lossy`);
|
||||
}
|
||||
});
|
||||
|
||||
test("lossy semantic-condensation engines are NOT flagged as safe defaults", () => {
|
||||
for (const id of ["rtk", "relevance", "caveman", "aggressive", "llmlingua", "ultra", "omniglyph"]) {
|
||||
assert.equal(isSafeDefault(id), false, `${id} should NOT be a safe default (lossy)`);
|
||||
assert.equal(engineMeta(id).guidance.lossy, true, `${id} should be marked lossy`);
|
||||
}
|
||||
});
|
||||
163
tests/unit/ui/compression-guidance-7530.test.tsx
Normal file
163
tests/unit/ui/compression-guidance-7530.test.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// #7530 — in-product guidance for Prompt Compression engines.
|
||||
// Asserts the Settings -> Prompt Compression panel surfaces each engine's guidance
|
||||
// detail (expand/collapse), shows the "safe default" indicator only for lossless
|
||||
// engines, and links out to the full compression guide.
|
||||
import React, { act } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import {
|
||||
ENGINE_IDS,
|
||||
engineMeta,
|
||||
isSafeDefault,
|
||||
} from "../../../open-sse/services/compression/engineCatalog.ts";
|
||||
|
||||
vi.mock("next-intl", () => ({
|
||||
useTranslations: () => (key: string) => key,
|
||||
useLocale: () => "en",
|
||||
}));
|
||||
|
||||
const containers: HTMLElement[] = [];
|
||||
const roots: Array<{ unmount: () => void }> = [];
|
||||
|
||||
function mount(ui: React.ReactElement): HTMLElement {
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
containers.push(container);
|
||||
const root = createRoot(container);
|
||||
roots.push(root);
|
||||
act(() => {
|
||||
root.render(ui);
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
(
|
||||
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
|
||||
).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
vi.restoreAllMocks();
|
||||
await act(async () => {
|
||||
while (roots.length > 0) {
|
||||
roots.pop()?.unmount();
|
||||
}
|
||||
});
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await Promise.resolve();
|
||||
}
|
||||
while (containers.length > 0) {
|
||||
containers.pop()?.remove();
|
||||
}
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
|
||||
async function flush() {
|
||||
await act(async () => {
|
||||
for (let i = 0; i < 10; i++) await Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function setupFetchMock() {
|
||||
const json = (body: unknown, status = 200) =>
|
||||
new Response(JSON.stringify(body), {
|
||||
status,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
const initialConfig = {
|
||||
enabled: true,
|
||||
autoTriggerTokens: 0,
|
||||
preserveSystemPrompt: true,
|
||||
engines: {},
|
||||
activeComboId: null,
|
||||
cavemanOutputMode: { enabled: false, intensity: "full", autoClarity: true },
|
||||
};
|
||||
|
||||
vi.spyOn(globalThis, "fetch").mockImplementation(async (input: RequestInfo | URL) => {
|
||||
const url = input.toString();
|
||||
if (url.includes("/api/settings/compression/mcp-accessibility")) {
|
||||
return json({ enabled: true, maxTextChars: 50000 });
|
||||
}
|
||||
if (url.includes("/api/settings/compression")) {
|
||||
return json(initialConfig);
|
||||
}
|
||||
return json({}, 404);
|
||||
});
|
||||
}
|
||||
|
||||
describe("CompressionPanel — engine guidance (#7530)", () => {
|
||||
it("links to the full compression guide", async () => {
|
||||
setupFetchMock();
|
||||
const { default: CompressionPanel } = await import(
|
||||
"@/app/(dashboard)/dashboard/context/settings/CompressionPanel"
|
||||
);
|
||||
|
||||
let container!: HTMLElement;
|
||||
await act(async () => {
|
||||
container = mount(<CompressionPanel />);
|
||||
});
|
||||
await flush();
|
||||
|
||||
const link = container.querySelector('[data-testid="compression-guide-link"]');
|
||||
expect(link).toBeTruthy();
|
||||
expect(link?.getAttribute("href")).toContain("docs/compression/COMPRESSION_GUIDE.md");
|
||||
});
|
||||
|
||||
it("shows the safe-default badge only for lossless engines", async () => {
|
||||
setupFetchMock();
|
||||
const { default: CompressionPanel } = await import(
|
||||
"@/app/(dashboard)/dashboard/context/settings/CompressionPanel"
|
||||
);
|
||||
|
||||
let container!: HTMLElement;
|
||||
await act(async () => {
|
||||
container = mount(<CompressionPanel />);
|
||||
});
|
||||
await flush();
|
||||
|
||||
for (const id of ENGINE_IDS) {
|
||||
const badge = container.querySelector(`[data-testid="engine-safe-default-${id}"]`);
|
||||
if (isSafeDefault(id)) {
|
||||
expect(badge, `${id} is lossless — expected the safe-default badge`).toBeTruthy();
|
||||
} else {
|
||||
expect(badge, `${id} is lossy — should NOT show the safe-default badge`).toBeFalsy();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("expands an engine's guidance detail (tradeoffs + cache impact) on toggle", async () => {
|
||||
setupFetchMock();
|
||||
const { default: CompressionPanel } = await import(
|
||||
"@/app/(dashboard)/dashboard/context/settings/CompressionPanel"
|
||||
);
|
||||
|
||||
let container!: HTMLElement;
|
||||
await act(async () => {
|
||||
container = mount(<CompressionPanel />);
|
||||
});
|
||||
await flush();
|
||||
|
||||
const engineId = "caveman";
|
||||
expect(
|
||||
container.querySelector(`[data-testid="engine-guidance-detail-${engineId}"]`)
|
||||
).toBeFalsy();
|
||||
|
||||
const toggle = container.querySelector(
|
||||
`[data-testid="engine-guidance-toggle-${engineId}"]`
|
||||
) as HTMLButtonElement | null;
|
||||
expect(toggle, "guidance toggle button must exist").toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
toggle!.click();
|
||||
});
|
||||
await flush();
|
||||
|
||||
const detail = container.querySelector(`[data-testid="engine-guidance-detail-${engineId}"]`);
|
||||
expect(detail).toBeTruthy();
|
||||
expect(detail?.textContent).toContain(engineMeta(engineId).guidance.tradeoffs);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user