mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 22:32:12 +03:00
This commit is contained in:
committed by
GitHub
parent
bfcfc9bf3f
commit
24ae0b32f8
@@ -10,6 +10,8 @@
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
||||
- **dashboard (routing):** selecting the **fusion** strategy on the Global Routing defaults tab now reveals fusion-specific config instead of only the generic resilience fields. Fusion's engine knobs — `judgeModel` (the model that synthesizes the panel answers) and `fusionTuning` (`minPanel` / `stragglerGraceMs` / `panelHardTimeoutMs`) — already existed in the schema and the per-combo editor, but the Global Routing tab never surfaced them, so picking "fusion" there was effectively a no-op. The fields are now shown (extracted into a new `FusionDefaultsFields` component). Voting / aggregation-mode / per-provider-weight are intentionally not shown — those don't exist in the fusion engine. Regression guard: `tests/unit/ui/combo-defaults-fusion-5598.test.tsx`. ([#5598](https://github.com/diegosouzapw/OmniRoute/issues/5598))
|
||||
|
||||
- **dashboard (free proxy pool):** the free proxy pool "Sync All" no longer fails silently with `Total: 0`. Three fixes: (1) the **IPLocate** source fetched `…/protocols/<proto>.json` and parsed it as JSON, but the upstream list is plain text (`<proto>.txt`, one `ip:port` per line) — every protocol 404'd / failed to parse; it now fetches `.txt` and parses the line list. (2) The sync route **isolates each source** in its own try/catch, so one provider throwing (e.g. a TLS handshake failure) no longer aborts the whole sync — the working sources still populate the pool. (3) The UI now **surfaces the per-source errors** the route already returns, instead of discarding the response, so a partial/empty sync explains itself. Regression guards: `tests/unit/free-proxy-providers.test.ts`, `tests/unit/proxy-pool-sync-4878.test.ts`, `tests/unit/free-pool-tab.test.tsx`. ([#5595](https://github.com/diegosouzapw/OmniRoute/issues/5595))
|
||||
|
||||
- **dashboard (memory engine):** the memory engine status page no longer mixes English and Portuguese. The embedding / vector-store / rerank **status detail strings** were hardcoded in Portuguese in the backend (`resolveEmbeddingSource`, `engineStatus`), e.g. `auto: nenhuma fonte de embedding disponível` and `sqlite-vec ativo, dim=…`, while the surrounding UI labels render from the English i18n bundle — so an English user saw a half-translated page. The backend detail strings are now English (`auto: no embedding source available`, `sqlite-vec active, dim=…`, etc.), matching the rest of the page. Regression guard: `tests/unit/memory-engine-status.test.ts`. ([#5596](https://github.com/diegosouzapw/OmniRoute/issues/5596))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"_comment": "Catraca de tamanho (check-file-size.mjs). frozen so pode encolher; arquivos novos <= cap. --update ratcheta.",
|
||||
"_rebaseline_2026_06_30_5598_fusion_defaults": "PR #5598 own growth: ComboDefaultsTab.tsx 846->851 (+5 = import + the `strategy === \"fusion\"` conditional that mounts the new FusionDefaultsFields component). The bulk of the fusion-defaults UI (judgeModel + fusionTuning inputs) was EXTRACTED to a new src/app/(dashboard)/dashboard/settings/components/FusionDefaultsFields.tsx (92 LOC, <cap) to keep the frozen god-file's growth to the minimal wiring. Mirrors the per-combo Fusion editor already rebaselined under #5074 (combos/page.tsx +109). Covered by tests/unit/ui/combo-defaults-fusion-5598.test.tsx.",
|
||||
"_rebaseline_2026_06_29_v3841_release": "callLogs 985->997 + chat 1632->1635 + chatHelpers 811->842 (#5351 opencode visible-rotation-logs/ProxyEgress/SQL-vars), base.ts 1475->1497 + openai-to-claude 805->823 (#5352 thinking hydrate/redacted-replay + #5342 empty-messages guard), accountFallback 1777->1783 (#5346 402 auto-disable depleted key) + account-fallback-service.test 1569->1572 (#5346 test), provider-validation-specialty.test 2801->2843 (#5358 grok cf_clearance + #5337 gemini catalog). Cycle-close drift from merged campaign PRs absorbed at release close per generate-release Phase 0 (the PR->release fast-gates do NOT run check:file-size); all irreducible chokepoint/feature growth next to existing branches, covered by per-PR tests. Structural shrink tracked under decomposition roadmap #3501.",
|
||||
"_rebaseline_2026_06_26_v3838_ownerprs_batch": "Lote /review-prs v3.8.38 (PRs do dono + contribuidores): src/lib/db/providers.ts 1093->1107 (+14 = #5121 cookie-dedup branch extraido para o helper findExistingCookieConnection — a extracao reduz a complexidade ciclomatica de createProviderConnection mas adiciona ~14 linhas ao arquivo; trade-off consciente complexity-vs-file-size) e src/lib/usage/usageHistory.ts 934->983 (+49 = #4940 dedup guard SELECT-before-INSERT + endpoint backfill + scheduleStatsEvent debounce). Crescimento de feature/fix legitimo recem-TDD'd; o fast-path PR->release nao roda check:file-size, entao o ramo acumula sem rebaselinar. Reducao estrutural fica como debt (#3501).",
|
||||
"_rebaseline_2026_06_26_relgreen_db_test": "Release-green follow-up: tests/unit/db-core-init.test.ts 867->877 (+10 = the invalid-DATA_DIR test now captures the rejection and asserts both Error type AND message — restores net-neutral assert count after #5117's consolidation, satisfying check:test-masking — instead of a single assert.rejects).",
|
||||
@@ -201,7 +202,7 @@
|
||||
"src/app/(dashboard)/dashboard/providers/page.tsx": 1927,
|
||||
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1201,
|
||||
"src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx": 819,
|
||||
"src/app/(dashboard)/dashboard/settings/components/ComboDefaultsTab.tsx": 846,
|
||||
"src/app/(dashboard)/dashboard/settings/components/ComboDefaultsTab.tsx": 851,
|
||||
"src/app/(dashboard)/dashboard/settings/components/CompressionSettingsTab.tsx": 974,
|
||||
"src/app/(dashboard)/dashboard/settings/components/MemorySkillsTab.tsx": 898,
|
||||
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": 1012,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { Card, Button, Input, Toggle } from "@/shared/components";
|
||||
import { cn } from "@/shared/utils/cn";
|
||||
import FusionDefaultsFields from "./FusionDefaultsFields";
|
||||
import {
|
||||
ROUTING_STRATEGIES,
|
||||
SETTINGS_FALLBACK_STRATEGY_VALUES,
|
||||
@@ -647,6 +648,10 @@ export default function ComboDefaultsTab() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{comboDefaults.strategy === "fusion" && (
|
||||
<FusionDefaultsFields comboDefaults={comboDefaults} setComboDefaults={setComboDefaults} />
|
||||
)}
|
||||
|
||||
{/* Toggles */}
|
||||
<div className="flex flex-col gap-3 pt-3 border-t border-border/50">
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { Input } from "@/shared/components";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
function translateOrFallback(
|
||||
t: ReturnType<typeof useTranslations>,
|
||||
key: string,
|
||||
fallback: string
|
||||
): string {
|
||||
return typeof t.has === "function" && t.has(key) ? t(key) : fallback;
|
||||
}
|
||||
|
||||
interface FusionDefaultsFieldsProps {
|
||||
comboDefaults: any;
|
||||
setComboDefaults: (updater: (prev: any) => any) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* #5598 — Fusion-specific defaults for the Global Routing tab. Selecting the
|
||||
* "fusion" strategy previously showed only the generic resilience fields even
|
||||
* though fusion has real engine knobs (`open-sse/services/fusion.ts`, schema in
|
||||
* `src/shared/validation/schemas/combo.ts`): `judgeModel` synthesizes the final
|
||||
* answer and `fusionTuning` controls the quorum-grace panel collection. The
|
||||
* per-combo editor already exposes these; this surfaces the same knobs as global
|
||||
* defaults. (Voting / aggregation-mode / per-provider-weight do not exist in the
|
||||
* engine, so they are intentionally not shown.)
|
||||
*/
|
||||
export default function FusionDefaultsFields({
|
||||
comboDefaults,
|
||||
setComboDefaults,
|
||||
}: FusionDefaultsFieldsProps) {
|
||||
const t = useTranslations("settings");
|
||||
const setTuning = (patch: Record<string, number | undefined>) =>
|
||||
setComboDefaults((prev: any) => ({
|
||||
...prev,
|
||||
fusionTuning: { ...prev.fusionTuning, ...patch },
|
||||
}));
|
||||
const num = (value: string) => (value ? Number(value) : undefined);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 pt-3 border-t border-border/50">
|
||||
<Input
|
||||
label={translateOrFallback(t, "fusionJudgeModel", "Judge Model")}
|
||||
type="text"
|
||||
value={comboDefaults.judgeModel ?? ""}
|
||||
placeholder="openai/gpt-5.5"
|
||||
onChange={(e) =>
|
||||
setComboDefaults((prev: any) => ({ ...prev, judgeModel: e.target.value || undefined }))
|
||||
}
|
||||
className="text-sm md:col-span-2"
|
||||
/>
|
||||
<Input
|
||||
label={translateOrFallback(t, "fusionMinPanel", "Min Panel")}
|
||||
type="number"
|
||||
min={1}
|
||||
max={50}
|
||||
value={comboDefaults.fusionTuning?.minPanel ?? ""}
|
||||
placeholder="2"
|
||||
onChange={(e) => setTuning({ minPanel: num(e.target.value) })}
|
||||
className="text-sm"
|
||||
/>
|
||||
<Input
|
||||
label={translateOrFallback(t, "fusionStragglerGraceMs", "Straggler Grace (ms)")}
|
||||
type="number"
|
||||
min={0}
|
||||
max={120000}
|
||||
value={comboDefaults.fusionTuning?.stragglerGraceMs ?? ""}
|
||||
placeholder="8000"
|
||||
onChange={(e) => setTuning({ stragglerGraceMs: num(e.target.value) })}
|
||||
className="text-sm"
|
||||
/>
|
||||
<Input
|
||||
label={translateOrFallback(t, "fusionPanelHardTimeoutMs", "Panel Hard Timeout (ms)")}
|
||||
type="number"
|
||||
min={1000}
|
||||
max={600000}
|
||||
value={comboDefaults.fusionTuning?.panelHardTimeoutMs ?? ""}
|
||||
placeholder="90000"
|
||||
onChange={(e) => setTuning({ panelHardTimeoutMs: num(e.target.value) })}
|
||||
className="text-sm md:col-span-2"
|
||||
/>
|
||||
<div className="md:col-span-2 rounded-lg border border-blue-500/20 bg-blue-500/5 p-3">
|
||||
<p className="text-xs text-blue-700 dark:text-blue-300">
|
||||
{translateOrFallback(
|
||||
t,
|
||||
"fusionDefaultsNote",
|
||||
"Fusion fans out to all of a combo's models and a judge model synthesizes the final answer (defaults to the first panel model when unset). These are global defaults for new or unconfigured combos."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
84
tests/unit/ui/combo-defaults-fusion-5598.test.tsx
Normal file
84
tests/unit/ui/combo-defaults-fusion-5598.test.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// #5598 — Selecting the "fusion" routing strategy on the Global Routing defaults
|
||||
// tab previously revealed no fusion-specific config (only the generic resilience
|
||||
// fields). Fusion's engine knobs (judgeModel + fusionTuning) exist in the schema
|
||||
// and the per-combo editor, but were never surfaced as global defaults. This
|
||||
// asserts they now appear when fusion is the selected strategy.
|
||||
import React, { act } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
|
||||
vi.mock("next-intl", () => ({
|
||||
// identity translator with no `has`, so translateOrFallback uses the English fallbacks
|
||||
useTranslations: () => (key: string) => key,
|
||||
}));
|
||||
|
||||
const { default: ComboDefaultsTab } = await import(
|
||||
"../../../src/app/(dashboard)/dashboard/settings/components/ComboDefaultsTab"
|
||||
);
|
||||
|
||||
function okJson(data: unknown) {
|
||||
return Promise.resolve({ ok: true, json: () => Promise.resolve(data) } as Response);
|
||||
}
|
||||
|
||||
function setupFetch(strategy: string) {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn((url: string) => {
|
||||
if (String(url).includes("/combo-defaults")) return okJson({ comboDefaults: { strategy } });
|
||||
if (String(url).includes("/api/providers")) return okJson({ connections: [] });
|
||||
return okJson({}); // /api/settings
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const containers: Array<{ root: ReturnType<typeof createRoot>; el: HTMLDivElement }> = [];
|
||||
|
||||
function renderTab() {
|
||||
const el = document.createElement("div");
|
||||
document.body.appendChild(el);
|
||||
const root = createRoot(el);
|
||||
act(() => {
|
||||
root.render(<ComboDefaultsTab />);
|
||||
});
|
||||
containers.push({ root, el });
|
||||
return el;
|
||||
}
|
||||
|
||||
async function waitFor(fn: () => boolean, timeoutMs = 2000) {
|
||||
const start = Date.now();
|
||||
while (!fn()) {
|
||||
if (Date.now() - start > timeoutMs) throw new Error("waitFor timed out");
|
||||
await new Promise((r) => setTimeout(r, 20));
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const { root, el } of containers.splice(0)) {
|
||||
act(() => root.unmount());
|
||||
el.remove();
|
||||
}
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe("ComboDefaultsTab fusion config (#5598)", () => {
|
||||
it("shows fusion-specific fields when the fusion strategy is selected", async () => {
|
||||
setupFetch("fusion");
|
||||
const el = renderTab();
|
||||
// RED before the fix: fusion had no config block → these labels never render.
|
||||
await waitFor(() => el.textContent?.includes("Judge Model") === true);
|
||||
expect(el.textContent).toContain("Judge Model");
|
||||
expect(el.textContent).toContain("Min Panel");
|
||||
expect(el.textContent).toContain("Straggler Grace (ms)");
|
||||
expect(el.textContent).toContain("Panel Hard Timeout (ms)");
|
||||
});
|
||||
|
||||
it("does not show fusion fields for a non-fusion strategy", async () => {
|
||||
setupFetch("priority");
|
||||
const el = renderTab();
|
||||
// Let the component settle (load + render).
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
expect(el.textContent).not.toContain("Judge Model");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user