Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4287673
feat(api): expose compression engine catalog + config schema
diegosouzapw Jun 14, 2026
d48ed7f
feat(api): per-engine compression preview
diegosouzapw Jun 14, 2026
ee1d62a
feat(analytics): per-engine compression analytics aggregation + route
diegosouzapw Jun 14, 2026
c2aa5f4
feat(combos): toggle engine in default combo + support new engines in…
diegosouzapw Jun 14, 2026
f20e7bb
feat(compression-ui): EngineConfigForm renders engine config schema
diegosouzapw Jun 14, 2026
f934467
feat(compression-ui): EngineConfigPage rich per-engine config template
diegosouzapw Jun 14, 2026
56dea25
feat(compression-ui): engine config pages + compression menu group
diegosouzapw Jun 14, 2026
85dd60a
feat(compression-ui): pure pipeline reducer for the combos editor
diegosouzapw Jun 14, 2026
09a9083
feat(compression-ui): ordered-layer combos editor with per-engine config
diegosouzapw Jun 14, 2026
f6c0943
feat(compression-ui): surface Compression Settings in the compression…
diegosouzapw Jun 14, 2026
94de481
feat(ws): live dashboard WebSocket default-on (loopback-bound)
diegosouzapw Jun 14, 2026
9f4abc4
fix(compression-ui): review fixes — single-engine activation, error s…
diegosouzapw Jun 14, 2026
48ac312
test(compression-ui): unmount React roots in render-test cleanup to f…
diegosouzapw Jun 14, 2026
6a4b33c
fix(i18n): add sidebar message keys for new compression menu items (H…
diegosouzapw Jun 14, 2026
5879285
perf(compression-ui): parallelize EngineConfigPage initial fetches (s…
diegosouzapw Jun 14, 2026
a49b407
revert(compression-ui): defer combos ordered-layer redesign (Task 8/9…
diegosouzapw Jun 14, 2026
5c52b54
feat(compression-ui): add Compression Hub + Lite/Aggressive/Ultra eng…
diegosouzapw Jun 15, 2026
5869798
Merge remote-tracking branch 'origin/release/v3.8.25' into fix-3860-f…
diegosouzapw Jun 15, 2026
69ec8da
chore(quality): re-baseline file-size for #3860 compression UI growth
diegosouzapw Jun 15, 2026
dbeb07b
test(ws): move live-ws-default test to a collected runner path
diegosouzapw Jun 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/reference/ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari
| `LIVE_WS_PORT` | `20129` | `src/server/ws/liveServer.ts` | Port for the real-time WebSocket live monitoring server. |
| `LIVE_WS_HOST` | `127.0.0.1` | `src/server/ws/liveServer.ts` | Bind address for the live WebSocket server. Set to `0.0.0.0` to expose on LAN (also configure `LIVE_WS_ALLOWED_ORIGINS`). |
| `LIVE_WS_ALLOWED_ORIGINS` | _(unset)_ | `src/server/ws/liveServer.ts` | Comma-separated extra origins allowed to open a live WebSocket. Loopback dashboard origins are already permitted by default. |
| `OMNIROUTE_ENABLE_LIVE_WS` | `false` | `src/server/ws/liveServer.ts` | Set to `1` or `true` to enable the real-time WebSocket server (disabled by default). |
| `OMNIROUTE_ENABLE_LIVE_WS` | `true` | `src/server/ws/liveServer.ts` | Set to `0` or `false` to disable the real-time WebSocket server (enabled by default, loopback-bound). |
| `OMNIROUTE_DISABLE_LIVE_WS` | `false` | `scripts/start-ws-server.mjs` | CI/harness toggle that disables the standalone live WebSocket helper script. |
| `RELAY_IP_PER_MINUTE` | `30` | `src/app/api/v1/relay/chat/completions/route.ts` | Per-(token, IP) relay rate limit, requests/minute. In-memory, per instance. `0` or negative disables the IP-dimension gate (per-token DB limit still applies). |
| `NODE_ENV` | `production` | Next.js core | Controls logging verbosity, caching, error detail exposure, and Next.js optimizations. |
Expand Down
5 changes: 3 additions & 2 deletions file-size-baseline.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"_comment": "Catraca de tamanho (check-file-size.mjs). frozen so pode encolher; arquivos novos <= cap. --update ratcheta.",
"_rebaseline_v3.8.25": "Drift consciente do ciclo v3.8.24->v3.8.25 (features #3799-#3806: free-provider-rankings, plugins menu, proxy IP-family selector). 3 arquivos cresceram por feature legitima, nao por regressao de qualidade: ProxyRegistryManager.tsx 1072->1089, sidebarVisibility.ts 990->1006, schemas.ts 2519->2522. Encolher fica como debt para um refactor dedicado.",
"_rebaseline_2026_06_15_3860_compression_ui": "PR #3860 own growth: sidebarVisibility.ts 1006->1100 (+94 = Compression Hub menu entries: Hub + per-engine Lite/Aggressive/Ultra pages + combos editor) and chatCore.ts 5812->5815 (+3 = compression UI config wiring). Cohesive feature growth, not a quality regression.",
"cap": 800,
"frozen": {
"open-sse/config/providerRegistry.ts": 4703,
Expand All @@ -16,7 +17,7 @@
"open-sse/executors/muse-spark-web.ts": 1284,
"open-sse/executors/perplexity-web.ts": 868,
"open-sse/handlers/audioSpeech.ts": 952,
"open-sse/handlers/chatCore.ts": 5812,
"open-sse/handlers/chatCore.ts": 5815,
"open-sse/handlers/imageGeneration.ts": 3777,
"open-sse/handlers/responseSanitizer.ts": 1103,
"open-sse/handlers/search.ts": 1442,
Expand Down Expand Up @@ -100,7 +101,7 @@
"src/shared/constants/cliTools.ts": 875,
"src/shared/constants/pricing.ts": 1508,
"src/shared/constants/providers.ts": 3147,
"src/shared/constants/sidebarVisibility.ts": 1006,
"src/shared/constants/sidebarVisibility.ts": 1100,
"src/shared/services/cliRuntime.ts": 1090,
"src/shared/validation/schemas.ts": 2523,
"src/sse/handlers/chat.ts": 1425,
Expand Down
5 changes: 4 additions & 1 deletion open-sse/handlers/chatCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2486,7 +2486,10 @@ export async function handleChatCore({
const isStackedCompressionCombo = (
compressionCombo: RuntimeCompressionCombo | null
): compressionCombo is RuntimeCompressionCombo => {
return Boolean(compressionCombo && compressionCombo.pipeline.length > 1);
// >= 1: a single-engine default combo (user enabled exactly one layer via the
// per-engine config page) must still apply. applyCompressionComboConfig already
// guards length === 0.
return Boolean(compressionCombo && compressionCombo.pipeline.length >= 1);
};
if (isCombo && comboName) {
try {
Expand Down
7 changes: 7 additions & 0 deletions src/app/(dashboard)/dashboard/context/aggressive/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use client";

import { EngineConfigPage } from "@/shared/components/compression/EngineConfigPage";

export default function AggressivePage() {
return <EngineConfigPage engineId="aggressive" />;
}
7 changes: 7 additions & 0 deletions src/app/(dashboard)/dashboard/context/ccr/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use client";

import { EngineConfigPage } from "@/shared/components/compression/EngineConfigPage";

export default function CcrPage() {
return <EngineConfigPage engineId="ccr" />;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
"use client";

// Combos screen = Compression Hub (top) + named-combos manager (below).
//
// IMPORTANT (hydration): no `useTranslations` here. The earlier combos redesign
// failed to hydrate on the production build and the only structural difference from
// the engine pages was a page-level `useTranslations`. Strings are hardcoded (pt-BR),
// matching `EngineConfigPage` / `CompressionHub`, both of which hydrate cleanly.

import { useEffect, useState } from "react";
import { useTranslations } from "next-intl";
import CompressionHub from "./CompressionHub";

type PipelineStep = { engine: string; intensity?: string };
type CompressionCombo = {
Expand All @@ -28,10 +35,13 @@ const ENGINE_INTENSITIES: Record<string, string[]> = {
lite: ["lite"],
aggressive: ["standard"],
ultra: ["ultra"],
headroom: ["standard"],
"session-dedup": ["standard"],
ccr: ["standard"],
llmlingua: ["standard"],
};

export default function CompressionCombosPageClient() {
const t = useTranslations("contextCombos");
function NamedCombosManager() {
const [combos, setCombos] = useState<CompressionCombo[]>([]);
const [routingCombos, setRoutingCombos] = useState<RoutingCombo[]>([]);
const [languagePacks, setLanguagePacks] = useState<LanguagePack[]>([]);
Expand Down Expand Up @@ -131,7 +141,7 @@ export default function CompressionCombosPageClient() {
};

const deleteCombo = async (combo: CompressionCombo) => {
if (!confirm(t("deleteConfirm"))) return;
if (!confirm(`Excluir o combo "${combo.name}"?`)) return;
const res = await fetch(`/api/context/combos/${combo.id}`, { method: "DELETE" });
if (res.ok) refresh();
};
Expand Down Expand Up @@ -174,33 +184,40 @@ export default function CompressionCombosPageClient() {
};

return (
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-4">
<div>
<h2 className="text-lg font-semibold text-text-main">Combos nomeados</h2>
<p className="text-sm text-text-muted">
Salve pipelines diferentes e atribua a combos de roteamento específicos.
</p>
</div>

<section className="rounded-lg border border-border bg-surface p-4">
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
<input
value={name}
onChange={(event) => setName(event.target.value)}
placeholder={t("name")}
placeholder="Nome do combo"
className="rounded-lg border border-border bg-bg px-3 py-2 text-sm text-text-main"
/>
<input
value={description}
onChange={(event) => setDescription(event.target.value)}
placeholder={t("descriptionField")}
placeholder="Descrição"
className="rounded-lg border border-border bg-bg px-3 py-2 text-sm text-text-main"
/>
</div>

<div className="mt-4 space-y-3">
<div className="flex items-center justify-between">
<h2 className="text-sm font-semibold text-text-main">{t("pipeline")}</h2>
<h3 className="text-sm font-semibold text-text-main">Pipeline</h3>
<button
onClick={() =>
setPipeline((current) => [...current, { engine: "caveman", intensity: "full" }])
}
className="rounded-lg border border-border px-3 py-1.5 text-xs text-text-main"
>
{t("addStep")}
Adicionar etapa
</button>
</div>
{pipeline.map((step, index) => (
Expand Down Expand Up @@ -232,15 +249,15 @@ export default function CompressionCombosPageClient() {
className="rounded-lg border border-border px-3 py-2 text-sm text-text-main"
disabled={pipeline.length <= 1}
>
{t("removeStep")}
Remover
</button>
</div>
))}
</div>

<div className="mt-4 grid grid-cols-1 gap-4 lg:grid-cols-3">
<div>
<h2 className="mb-2 text-sm font-semibold text-text-main">{t("languagePacks")}</h2>
<h3 className="mb-2 text-sm font-semibold text-text-main">Pacotes de idioma</h3>
<div className="space-y-2 text-sm text-text-main">
{languagePacks.map((pack) => (
<label key={pack.language} className="flex items-center justify-between gap-2">
Expand All @@ -258,14 +275,14 @@ export default function CompressionCombosPageClient() {
</div>
</div>
<div className="space-y-2 text-sm text-text-main">
<h2 className="text-sm font-semibold text-text-main">{t("outputMode")}</h2>
<h3 className="text-sm font-semibold text-text-main">Output mode</h3>
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={outputMode}
onChange={(event) => setOutputMode(event.target.checked)}
/>
{t("enabled")}
Ativado
</label>
<select
value={outputModeIntensity}
Expand All @@ -278,10 +295,10 @@ export default function CompressionCombosPageClient() {
</select>
</div>
<div>
<h2 className="mb-2 text-sm font-semibold text-text-main">{t("assignToRouting")}</h2>
<h3 className="mb-2 text-sm font-semibold text-text-main">Atribuir ao roteamento</h3>
<div className="max-h-44 space-y-2 overflow-auto text-sm text-text-main">
{routingCombos.length === 0 ? (
<p className="text-xs text-text-muted">{t("noAssignments")}</p>
<p className="text-xs text-text-muted">Nenhum combo de roteamento disponível.</p>
) : (
routingCombos.map((combo) => {
const id = combo.id ?? combo.name ?? "";
Expand All @@ -308,14 +325,14 @@ export default function CompressionCombosPageClient() {
disabled={saving}
className="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white"
>
{editingId ? t("save") : t("createCombo")}
{editingId ? "Salvar" : "Criar combo"}
</button>
{editingId && (
<button
onClick={resetForm}
className="rounded-lg border border-border px-4 py-2 text-sm text-text-main"
>
{t("cancel")}
Cancelar
</button>
)}
</div>
Expand All @@ -326,12 +343,12 @@ export default function CompressionCombosPageClient() {
<div key={combo.id} className="rounded-lg border border-border bg-surface p-4">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<h2 className="truncate text-base font-semibold text-text-main">{combo.name}</h2>
<h3 className="truncate text-base font-semibold text-text-main">{combo.name}</h3>
<p className="mt-1 text-sm text-text-muted">{combo.description}</p>
</div>
{combo.isDefault && (
<span className="rounded-full bg-primary/10 px-2 py-1 text-xs font-medium text-primary">
{t("default")}
Padrão
</span>
)}
</div>
Expand All @@ -347,29 +364,29 @@ export default function CompressionCombosPageClient() {
))}
</div>
<p className="mt-3 text-xs text-text-muted">
{t("languagePacks")}: {combo.languagePacks.join(", ")}
Pacotes de idioma: {combo.languagePacks.join(", ")}
</p>
<div className="mt-4 flex flex-wrap gap-2">
<button
onClick={() => editCombo(combo)}
className="rounded-lg border border-border px-3 py-1.5 text-xs text-text-main"
>
{t("editCombo")}
Editar
</button>
{!combo.isDefault && (
<button
onClick={() => setDefault(combo.id)}
className="rounded-lg border border-border px-3 py-1.5 text-xs text-text-main"
>
{t("setAsDefault")}
Definir como padrão
</button>
)}
{!combo.isDefault && (
<button
onClick={() => deleteCombo(combo)}
className="rounded-lg border border-danger/40 px-3 py-1.5 text-xs text-danger"
>
{t("deleteCombo")}
Excluir
</button>
)}
</div>
Expand All @@ -379,3 +396,12 @@ export default function CompressionCombosPageClient() {
</div>
);
}

export default function CompressionCombosPageClient() {
return (
<div className="flex flex-col gap-8">
<CompressionHub />
<NamedCombosManager />
</div>
);
}
Loading