Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 845787ab7f | |||
| 1db948e9bb | |||
| f0d00bcee5 |
@@ -11,6 +11,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
---
|
||||
|
||||
## [2.3.3] — 2026-03-12
|
||||
|
||||
> ### Providers Test All Fix
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Providers page crash when clicking 'Test All'** — Clicking 'Testar Todos' could trigger the error boundary ('Failed to load providers') when the batch test timed out or returned a non-JSON response. Fixed with:
|
||||
- `AbortController` (90s timeout) on the `handleBatchTest` fetch
|
||||
- Inner `try/catch` for `res.json()` — truncated/non-JSON responses no longer crash the component
|
||||
- Null/type guard in `ProviderTestResultsView` — malformed results can no longer trigger a render exception
|
||||
- New i18n key `providerTestTimeout` for friendly timeout message (PR #330)
|
||||
|
||||
---
|
||||
|
||||
## [2.3.2] — 2026-03-12
|
||||
|
||||
> ### Claude 1M Context, Postinstall Fix, New Models & OAuth Remote Docs
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "2.3.2",
|
||||
"version": "2.3.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "omniroute",
|
||||
"version": "2.3.2",
|
||||
"version": "2.3.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "2.3.2",
|
||||
"version": "2.3.3",
|
||||
"description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
||||
@@ -189,23 +189,35 @@ export default function ProvidersPage() {
|
||||
if (testingMode) return;
|
||||
setTestingMode(mode === "provider" ? providerId : mode);
|
||||
setTestResults(null);
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 90_000); // 90s max
|
||||
try {
|
||||
const res = await fetch("/api/providers/test-batch", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ mode, providerId }),
|
||||
signal: controller.signal,
|
||||
});
|
||||
const data = await res.json();
|
||||
let data: any;
|
||||
try {
|
||||
data = await res.json();
|
||||
} catch {
|
||||
// Response body is not valid JSON (e.g. truncated due to timeout)
|
||||
data = { error: t("providerTestFailed"), results: [], summary: null };
|
||||
}
|
||||
setTestResults(data);
|
||||
if (data.summary) {
|
||||
if (data?.summary) {
|
||||
const { passed, failed, total } = data.summary;
|
||||
if (failed === 0) notify.success(t("allTestsPassed", { total }));
|
||||
else notify.warning(t("testSummary", { passed, failed, total }));
|
||||
}
|
||||
} catch (error) {
|
||||
setTestResults({ error: t("providerTestFailed") });
|
||||
notify.error(t("providerTestFailed"));
|
||||
} catch (error: any) {
|
||||
const isAbort = error?.name === "AbortError";
|
||||
const msg = isAbort ? t("providerTestTimeout") : t("providerTestFailed");
|
||||
setTestResults({ error: msg, results: [], summary: null });
|
||||
notify.error(msg);
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
setTestingMode(null);
|
||||
}
|
||||
};
|
||||
@@ -1041,17 +1053,23 @@ function ProviderTestResultsView({ results }) {
|
||||
const t = useTranslations("providers");
|
||||
const tc = useTranslations("common");
|
||||
|
||||
if (results.error && !results.results) {
|
||||
// Guard: never crash on malformed/null results (would trigger error boundary)
|
||||
if (!results || typeof results !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (results.error && (!results.results || results.results.length === 0)) {
|
||||
return (
|
||||
<div className="text-center py-6">
|
||||
<span className="material-symbols-outlined text-red-500 text-[32px] mb-2 block">error</span>
|
||||
<p className="text-sm text-red-400">{results.error}</p>
|
||||
<p className="text-sm text-red-400">{String(results.error)}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const { summary, mode } = results;
|
||||
const items = results.results || [];
|
||||
const summary = results.summary ?? null;
|
||||
const mode = results.mode ?? "";
|
||||
const items = Array.isArray(results.results) ? results.results : [];
|
||||
|
||||
const modeLabel =
|
||||
{
|
||||
|
||||
@@ -1184,6 +1184,7 @@
|
||||
"clearing": "Clearing...",
|
||||
"until": "Until {time}",
|
||||
"providerTestFailed": "Provider test failed",
|
||||
"providerTestTimeout": "Provider test timed out — too many connections to test at once",
|
||||
"modeTest": "{mode} Test",
|
||||
"passedCount": "{count} passed",
|
||||
"failedCount": "{count} failed",
|
||||
|
||||
Reference in New Issue
Block a user