omniroute 3.0.5 → 3.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/.next/BUILD_ID +1 -1
- package/app/.next/build-manifest.json +2 -2
- package/app/.next/prerender-manifest.json +3 -3
- package/app/.next/server/app/(dashboard)/dashboard/a2a/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/agents/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/analytics/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/api-manager/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/audit-log/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/auto-combo/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/cli-tools/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/combos/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/costs/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/endpoint/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/health/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/limits/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/logs/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/mcp/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/media/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/onboarding/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/playground/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/profile/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/providers/[id]/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/providers/new/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/providers/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/search-tools/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/settings/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/settings/pricing/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/translator/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/(dashboard)/dashboard/usage/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/400/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/401/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/403/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/408/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/429/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/500/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/502/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/503/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/_global-error.html +2 -2
- package/app/.next/server/app/_global-error.rsc +1 -1
- package/app/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/app/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/app/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/app/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/app/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/app/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/callback/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/docs/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/forbidden/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/forgot-password/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/landing/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/maintenance/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/offline/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/privacy/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/status/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/terms/page_client-reference-manifest.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__051203a6._.js +2 -2
- package/app/.next/server/chunks/[root-of-the-server]__0891af92._.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__1f2b0d89._.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__46e00e59._.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__5dcab57b._.js +2 -2
- package/app/.next/server/chunks/[root-of-the-server]__61d78f9d._.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__6e52619e._.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__7ace0fcd._.js +1 -1
- package/app/.next/server/chunks/[root-of-the-server]__7fa4d14e._.js +1 -1
- package/app/.next/server/chunks/_05c48915._.js +1 -1
- package/app/.next/server/chunks/_06515a8a._.js +1 -1
- package/app/.next/server/chunks/_2115d8de._.js +1 -1
- package/app/.next/server/chunks/_3ac953eb._.js +1 -1
- package/app/.next/server/chunks/_4b8fd853._.js +1 -1
- package/app/.next/server/chunks/_68683848._.js +1 -1
- package/app/.next/server/chunks/_ee9b677b._.js +1 -1
- package/app/.next/server/chunks/open-sse_config_providerModels_ts_04541468._.js +1 -1
- package/app/.next/server/chunks/open-sse_config_providerRegistry_ts_2f74ec2a._.js +1 -1
- package/app/.next/server/chunks/open-sse_config_providerRegistry_ts_dec0f840._.js +1 -1
- package/app/.next/server/chunks/src_6320c728._.js +1 -1
- package/app/.next/server/chunks/ssr/[root-of-the-server]__9ef96d20._.js +1 -1
- package/app/.next/server/chunks/ssr/[root-of-the-server]__a6942102._.js +1 -1
- package/app/.next/server/chunks/ssr/_19b3d5b1._.js +1 -1
- package/app/.next/server/chunks/ssr/open-sse_config_providerModels_ts_4cac55e2._.js +1 -1
- package/app/.next/server/chunks/ssr/src_app_(dashboard)_dashboard_playground_page_tsx_06c08266._.js +2 -2
- package/app/.next/server/chunks/ssr/src_app_(dashboard)_dashboard_settings_9e20fb8d._.js +1 -1
- package/app/.next/server/pages/500.html +2 -2
- package/app/.next/server/server-reference-manifest.js +1 -1
- package/app/.next/server/server-reference-manifest.json +1 -1
- package/app/.next/static/chunks/0e39e279bf7e4cd9.js +2 -0
- package/app/.next/static/chunks/{25a3e05da8e1a7ec.js → 339b27cecfee41d1.js} +1 -1
- package/app/.next/static/chunks/{37063ea02716537b.js → 98a065e472ead1c5.js} +1 -1
- package/app/.next/static/chunks/{b7c366e286771d50.js → f62c550263d8bca8.js} +1 -1
- package/app/CHANGELOG.md +18 -0
- package/app/docs/openapi.yaml +1 -1
- package/app/open-sse/config/providerRegistry.ts +1 -4
- package/app/package-lock.json +2 -2
- package/app/package.json +1 -1
- package/app/src/app/(dashboard)/dashboard/playground/page.tsx +79 -4
- package/app/src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx +7 -7
- package/app/src/app/api/usage/[connectionId]/route.ts +42 -23
- package/app/src/lib/providers/validation.ts +1 -1
- package/app/tests/integration/v1-contracts-behavior.test.mjs +8 -6
- package/package.json +1 -1
- package/app/.next/static/chunks/f3640b442f6de1d4.js +0 -2
- /package/app/.next/static/{_Ub91T2wbB96WfkKKwhDH → 4vzx2qUM_tiJPdpx_Hiqj}/_buildManifest.js +0 -0
- /package/app/.next/static/{_Ub91T2wbB96WfkKKwhDH → 4vzx2qUM_tiJPdpx_Hiqj}/_clientMiddlewareManifest.json +0 -0
- /package/app/.next/static/{_Ub91T2wbB96WfkKKwhDH → 4vzx2qUM_tiJPdpx_Hiqj}/_ssgManifest.js +0 -0
package/app/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [3.0.6] — 2026-03-25
|
|
8
|
+
|
|
9
|
+
### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **Limits/Proxy:** Fixed Codex limit fetching for accounts behind SOCKS5 proxies — token refresh now runs inside proxy context
|
|
12
|
+
- **CI:** Fixed integration test `v1/models` assertion failure in CI environments without provider connections
|
|
13
|
+
- **Settings:** Proxy test button now shows success/failure results immediately (previously hidden behind health data)
|
|
14
|
+
|
|
15
|
+
### ✨ New Features
|
|
16
|
+
|
|
17
|
+
- **Playground:** Added Account selector dropdown — test specific connections individually when a provider has multiple accounts
|
|
18
|
+
|
|
19
|
+
### 🔧 Maintenance
|
|
20
|
+
|
|
21
|
+
- Merged PR #623 — LongCat API base URL path correction
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
7
25
|
## [3.0.5] — 2026-03-25
|
|
8
26
|
|
|
9
27
|
### ✨ New Features
|
package/app/docs/openapi.yaml
CHANGED
|
@@ -1275,10 +1275,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
|
|
1275
1275
|
alias: "lc",
|
|
1276
1276
|
format: "openai",
|
|
1277
1277
|
executor: "default",
|
|
1278
|
-
|
|
1279
|
-
// which is the chat endpoint directly, not the base. Key validation and routing must
|
|
1280
|
-
// use https://api.longcat.chat/openai which resolves /v1/models and /v1/chat/completions
|
|
1281
|
-
baseUrl: "https://api.longcat.chat/openai",
|
|
1278
|
+
baseUrl: "https://api.longcat.chat/openai/v1/chat/completions",
|
|
1282
1279
|
authType: "apikey",
|
|
1283
1280
|
authHeader: "Authorization",
|
|
1284
1281
|
authPrefix: "Bearer",
|
package/app/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omniroute",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.6",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "omniroute",
|
|
9
|
-
"version": "3.0.
|
|
9
|
+
"version": "3.0.6",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"workspaces": [
|
package/app/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omniroute",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.6",
|
|
4
4
|
"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.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,6 +20,13 @@ interface ProviderOption {
|
|
|
20
20
|
label: string;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
interface ConnectionOption {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
provider: string;
|
|
27
|
+
authType: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
const ENDPOINT_OPTIONS = [
|
|
24
31
|
{ value: "chat", label: "Chat Completions" },
|
|
25
32
|
{ value: "responses", label: "Responses" },
|
|
@@ -182,8 +189,10 @@ function ImageResultsInline({ data }: { data: any }) {
|
|
|
182
189
|
export default function PlaygroundPage() {
|
|
183
190
|
const [models, setModels] = useState<ModelInfo[]>([]);
|
|
184
191
|
const [providers, setProviders] = useState<ProviderOption[]>([]);
|
|
192
|
+
const [connections, setConnections] = useState<ConnectionOption[]>([]);
|
|
185
193
|
const [selectedProvider, setSelectedProvider] = useState("");
|
|
186
194
|
const [selectedModel, setSelectedModel] = useState("");
|
|
195
|
+
const [selectedConnection, setSelectedConnection] = useState("");
|
|
187
196
|
const [selectedEndpoint, setSelectedEndpoint] = useState("chat");
|
|
188
197
|
const [requestBody, setRequestBody] = useState("");
|
|
189
198
|
const [responseBody, setResponseBody] = useState("");
|
|
@@ -205,7 +214,38 @@ export default function PlaygroundPage() {
|
|
|
205
214
|
const isImageEndpoint = selectedEndpoint === "images";
|
|
206
215
|
const supportsVision = isChatEndpoint && isVisionModel(selectedModel);
|
|
207
216
|
|
|
208
|
-
//
|
|
217
|
+
// Load connections for a given provider (called imperatively)
|
|
218
|
+
const loadConnections = useCallback((provider: string) => {
|
|
219
|
+
if (!provider) {
|
|
220
|
+
setConnections([]);
|
|
221
|
+
setSelectedConnection("");
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
fetch("/api/providers/client")
|
|
225
|
+
.then((res) => res.json())
|
|
226
|
+
.then((data) => {
|
|
227
|
+
const allConns: ConnectionOption[] = [];
|
|
228
|
+
for (const p of data?.providers || []) {
|
|
229
|
+
if (p.id !== provider) continue;
|
|
230
|
+
for (const conn of p.connections || []) {
|
|
231
|
+
allConns.push({
|
|
232
|
+
id: conn.id,
|
|
233
|
+
name: conn.name || conn.id,
|
|
234
|
+
provider: p.id,
|
|
235
|
+
authType: conn.authType || "apiKey",
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
setConnections(allConns);
|
|
240
|
+
setSelectedConnection("");
|
|
241
|
+
})
|
|
242
|
+
.catch(() => {
|
|
243
|
+
setConnections([]);
|
|
244
|
+
setSelectedConnection("");
|
|
245
|
+
});
|
|
246
|
+
}, []);
|
|
247
|
+
|
|
248
|
+
// Fetch models and initialize first provider
|
|
209
249
|
useEffect(() => {
|
|
210
250
|
fetch("/v1/models")
|
|
211
251
|
.then((res) => res.json())
|
|
@@ -222,10 +262,13 @@ export default function PlaygroundPage() {
|
|
|
222
262
|
.sort()
|
|
223
263
|
.map((p) => ({ value: p, label: p }));
|
|
224
264
|
setProviders(providerOpts);
|
|
225
|
-
if (providerOpts.length > 0)
|
|
265
|
+
if (providerOpts.length > 0) {
|
|
266
|
+
setSelectedProvider(providerOpts[0].value);
|
|
267
|
+
loadConnections(providerOpts[0].value);
|
|
268
|
+
}
|
|
226
269
|
})
|
|
227
270
|
.catch(() => {});
|
|
228
|
-
}, []);
|
|
271
|
+
}, [loadConnections]);
|
|
229
272
|
|
|
230
273
|
const filteredModels = models
|
|
231
274
|
.filter((m) => !selectedProvider || m.id.startsWith(selectedProvider + "/"))
|
|
@@ -241,6 +284,8 @@ export default function PlaygroundPage() {
|
|
|
241
284
|
|
|
242
285
|
const handleProviderChange = (newProvider: string) => {
|
|
243
286
|
setSelectedProvider(newProvider);
|
|
287
|
+
setSelectedConnection("");
|
|
288
|
+
loadConnections(newProvider);
|
|
244
289
|
const providerModels = models
|
|
245
290
|
.filter((m) => !newProvider || m.id.startsWith(newProvider + "/"))
|
|
246
291
|
.map((m) => m.id);
|
|
@@ -334,8 +379,13 @@ export default function PlaygroundPage() {
|
|
|
334
379
|
} catch {
|
|
335
380
|
/* ignore parse errors */
|
|
336
381
|
}
|
|
382
|
+
const fetchHeaders: Record<string, string> = {};
|
|
383
|
+
if (selectedConnection) {
|
|
384
|
+
fetchHeaders["X-OmniRoute-Connection"] = selectedConnection;
|
|
385
|
+
}
|
|
337
386
|
res = await fetch(`/api${path}`, {
|
|
338
387
|
method: "POST",
|
|
388
|
+
headers: fetchHeaders,
|
|
339
389
|
body: form,
|
|
340
390
|
signal: controller.signal,
|
|
341
391
|
});
|
|
@@ -345,9 +395,13 @@ export default function PlaygroundPage() {
|
|
|
345
395
|
if (supportsVision && uploadedImages.length > 0) {
|
|
346
396
|
parsed = buildChatBodyWithImages(parsed, uploadedImages);
|
|
347
397
|
}
|
|
398
|
+
const fetchHeaders: Record<string, string> = { "Content-Type": "application/json" };
|
|
399
|
+
if (selectedConnection) {
|
|
400
|
+
fetchHeaders["X-OmniRoute-Connection"] = selectedConnection;
|
|
401
|
+
}
|
|
348
402
|
res = await fetch(`/api${path}`, {
|
|
349
403
|
method: "POST",
|
|
350
|
-
headers:
|
|
404
|
+
headers: fetchHeaders,
|
|
351
405
|
body: JSON.stringify(parsed),
|
|
352
406
|
signal: controller.signal,
|
|
353
407
|
});
|
|
@@ -473,6 +527,27 @@ export default function PlaygroundPage() {
|
|
|
473
527
|
</div>
|
|
474
528
|
)}
|
|
475
529
|
|
|
530
|
+
{/* Account/Connection — shown when provider has multiple connections */}
|
|
531
|
+
{!isSearchEndpoint && connections.length > 1 && (
|
|
532
|
+
<div className="flex-1 w-full">
|
|
533
|
+
<label className="block text-xs font-medium text-text-muted mb-1.5 uppercase tracking-wider">
|
|
534
|
+
Account
|
|
535
|
+
</label>
|
|
536
|
+
<Select
|
|
537
|
+
value={selectedConnection}
|
|
538
|
+
onChange={(e: any) => setSelectedConnection(e.target.value)}
|
|
539
|
+
options={[
|
|
540
|
+
{ value: "", label: `All (${connections.length} accounts)` },
|
|
541
|
+
...connections.map((c) => ({
|
|
542
|
+
value: c.id,
|
|
543
|
+
label: c.name,
|
|
544
|
+
})),
|
|
545
|
+
]}
|
|
546
|
+
className="w-full"
|
|
547
|
+
/>
|
|
548
|
+
</div>
|
|
549
|
+
)}
|
|
550
|
+
|
|
476
551
|
{/* Send Button — hidden in search mode (SearchPlayground has its own) */}
|
|
477
552
|
{!isSearchEndpoint && (
|
|
478
553
|
<div className="shrink-0">
|
|
@@ -467,12 +467,7 @@ export default function ProxyRegistryManager() {
|
|
|
467
467
|
</td>
|
|
468
468
|
<td className="py-2 pr-3 text-xs text-text-muted">
|
|
469
469
|
<div className="flex flex-col gap-0.5">
|
|
470
|
-
{
|
|
471
|
-
<>
|
|
472
|
-
<span>{health.successRate ?? 0}% success</span>
|
|
473
|
-
<span>{health.avgLatencyMs ?? "-"} ms avg</span>
|
|
474
|
-
</>
|
|
475
|
-
) : testById[item.id] ? (
|
|
470
|
+
{testById[item.id] ? (
|
|
476
471
|
testById[item.id]!.success ? (
|
|
477
472
|
<>
|
|
478
473
|
<span className="text-emerald-400">
|
|
@@ -484,9 +479,14 @@ export default function ProxyRegistryManager() {
|
|
|
484
479
|
</>
|
|
485
480
|
) : (
|
|
486
481
|
<span className="text-red-400">
|
|
487
|
-
{testById[item.id]!.error || "failed"}
|
|
482
|
+
✗ {testById[item.id]!.error || "failed"}
|
|
488
483
|
</span>
|
|
489
484
|
)
|
|
485
|
+
) : health ? (
|
|
486
|
+
<>
|
|
487
|
+
<span>{health.successRate ?? 0}% success</span>
|
|
488
|
+
<span>{health.avgLatencyMs ?? "-"} ms avg</span>
|
|
489
|
+
</>
|
|
490
490
|
) : (
|
|
491
491
|
<span>—</span>
|
|
492
492
|
)}
|
|
@@ -131,35 +131,54 @@ export async function GET(
|
|
|
131
131
|
return Response.json({ message: "Usage not available for API key connections" });
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
134
|
+
// Resolve proxy for this connection FIRST (key → combo → provider → global → direct)
|
|
135
|
+
// so that both credential refresh AND usage fetch go through the proxy.
|
|
136
|
+
const proxyInfo = await resolveProxyForConnection(connectionId);
|
|
137
|
+
|
|
138
|
+
// Wrap BOTH credential refresh and usage fetch inside proxy context.
|
|
139
|
+
// Codex accounts behind SOCKS5 proxies need the proxy active during token refresh too.
|
|
140
|
+
const { usage, refreshed } = (await runWithProxyContext(proxyInfo?.proxy || null, async () => {
|
|
141
|
+
let conn = connection;
|
|
142
|
+
let wasRefreshed = false;
|
|
143
|
+
|
|
144
|
+
// Refresh credentials if needed using executor
|
|
145
|
+
try {
|
|
146
|
+
const result = await refreshAndUpdateCredentials(conn);
|
|
147
|
+
conn = result.connection;
|
|
148
|
+
wasRefreshed = result.refreshed;
|
|
149
|
+
|
|
150
|
+
// Sync to cloud only if token was refreshed
|
|
151
|
+
if (wasRefreshed) {
|
|
152
|
+
await syncToCloudIfEnabled();
|
|
153
|
+
}
|
|
154
|
+
} catch (refreshError) {
|
|
155
|
+
console.error("[Usage API] Credential refresh failed:", refreshError);
|
|
156
|
+
throw refreshError;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Fetch usage from provider API
|
|
160
|
+
const usageData = await getUsageForProvider(conn);
|
|
161
|
+
connection = conn; // propagate updated connection for status sync below
|
|
162
|
+
return { usage: usageData, refreshed: wasRefreshed };
|
|
163
|
+
}).catch((refreshError: any) => {
|
|
164
|
+
// If error originated from credential refresh, return 401
|
|
165
|
+
if (
|
|
166
|
+
refreshError?.message?.includes?.("refresh") ||
|
|
167
|
+
refreshError?.message?.includes?.("Credential")
|
|
168
|
+
) {
|
|
169
|
+
return { __authError: true, message: refreshError.message };
|
|
144
170
|
}
|
|
145
|
-
|
|
146
|
-
|
|
171
|
+
throw refreshError;
|
|
172
|
+
})) as any;
|
|
173
|
+
|
|
174
|
+
// Handle auth errors from credential refresh
|
|
175
|
+
if (usage?.__authError) {
|
|
147
176
|
return Response.json(
|
|
148
|
-
{
|
|
149
|
-
error: `Credential refresh failed: ${(refreshError as any).message}`,
|
|
150
|
-
},
|
|
177
|
+
{ error: `Credential refresh failed: ${usage.message}` },
|
|
151
178
|
{ status: 401 }
|
|
152
179
|
);
|
|
153
180
|
}
|
|
154
181
|
|
|
155
|
-
// Resolve proxy for this connection (key → combo → provider → global → direct)
|
|
156
|
-
const proxyInfo = await resolveProxyForConnection(connectionId);
|
|
157
|
-
|
|
158
|
-
// Fetch usage from provider API, wrapped in proxy context
|
|
159
|
-
const usage = await runWithProxyContext(proxyInfo?.proxy || null, () =>
|
|
160
|
-
getUsageForProvider(connection)
|
|
161
|
-
);
|
|
162
|
-
|
|
163
182
|
// Populate quota cache for quota-aware account selection
|
|
164
183
|
if (isRecord(usage?.quotas)) {
|
|
165
184
|
setQuotaCache(
|
|
@@ -655,7 +655,7 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
|
|
|
655
655
|
// LongCat AI — does not expose /v1/models; validate via chat completions directly (#592)
|
|
656
656
|
longcat: async ({ apiKey }: any) => {
|
|
657
657
|
try {
|
|
658
|
-
const res = await fetch("https://longcat.chat/
|
|
658
|
+
const res = await fetch("https://api.longcat.chat/openai/v1/chat/completions", {
|
|
659
659
|
method: "POST",
|
|
660
660
|
headers: buildBearerHeaders(apiKey),
|
|
661
661
|
body: JSON.stringify({
|
|
@@ -59,13 +59,15 @@ test("contract: /api/v1/models returns OpenAI-compatible model shape", async ()
|
|
|
59
59
|
|
|
60
60
|
assert.equal(body.object, "list");
|
|
61
61
|
assert.ok(Array.isArray(body.data));
|
|
62
|
-
assert.ok(body.data.length > 0, "models list should not be empty");
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
// In CI environments without provider connections, models list may be empty — skip shape check
|
|
64
|
+
if (body.data.length > 0) {
|
|
65
|
+
const first = body.data[0];
|
|
66
|
+
assert.equal(typeof first.id, "string");
|
|
67
|
+
assert.equal(first.object, "model");
|
|
68
|
+
assert.equal(typeof first.created, "number");
|
|
69
|
+
assert.equal(typeof first.owned_by, "string");
|
|
70
|
+
}
|
|
69
71
|
});
|
|
70
72
|
|
|
71
73
|
test("contract: /api/v1/embeddings GET returns embedding model listing shape", async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omniroute",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.6",
|
|
4
4
|
"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.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,11477,e=>{"use strict";var t=e.i(393718);e.s(["Card",()=>t.default])},565650,e=>{"use strict";e.i(342923),e.i(80173),e.i(907714),e.i(393718),e.i(703166),e.i(454149),e.i(822124),e.i(969353),e.i(635055),e.i(897844),e.i(450222),e.i(647829),e.i(6998),e.i(982131),e.i(116016),e.i(279743),e.i(193464),e.i(548036),e.i(566770),e.i(953789),e.i(8839),e.i(227404),e.i(621745),e.i(567955),e.i(17517),e.i(419947),e.i(323060),e.i(642440),e.i(629342),e.i(816062),e.i(690777),e.i(563201),e.i(115243),e.i(165149),e.i(359505),e.s([],565650)},474078,e=>{"use strict";var t=e.i(342923);e.s(["Button",()=>t.default])},961430,e=>{"use strict";var t=e.i(907714);e.s(["Select",()=>t.default])},628029,e=>{"use strict";var t=e.i(969353);e.s(["Badge",()=>t.default])},667585,(e,t,a)=>{"use strict";Object.defineProperty(a,"__esModule",{value:!0}),Object.defineProperty(a,"BailoutToCSR",{enumerable:!0,get:function(){return l}});let s=e.r(132061);function l({reason:e,children:t}){if("u"<typeof window)throw Object.defineProperty(new s.BailoutToCSRError(e),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0});return t}},309885,(e,t,a)=>{"use strict";function s(e){return e.split("/").map(e=>encodeURIComponent(e)).join("/")}Object.defineProperty(a,"__esModule",{value:!0}),Object.defineProperty(a,"encodeURIPath",{enumerable:!0,get:function(){return s}})},652157,(e,t,a)=>{"use strict";Object.defineProperty(a,"__esModule",{value:!0}),Object.defineProperty(a,"PreloadChunks",{enumerable:!0,get:function(){return o}});let s=e.r(843476),l=e.r(174080),r=e.r(563599),i=e.r(309885),n=e.r(543369);function o({moduleIds:e}){if("u">typeof window)return null;let t=r.workAsyncStorage.getStore();if(void 0===t)return null;let a=[];if(t.reactLoadableManifest&&e){let s=t.reactLoadableManifest;for(let t of e){if(!s[t])continue;let e=s[t].files;a.push(...e)}}if(0===a.length)return null;let o=(0,n.getDeploymentIdQueryOrEmptyString)();return(0,s.jsx)(s.Fragment,{children:a.map(e=>{let a=`${t.assetPrefix}/_next/${(0,i.encodeURIPath)(e)}${o}`;return e.endsWith(".css")?(0,s.jsx)("link",{precedence:"dynamic",href:a,rel:"stylesheet",as:"style",nonce:t.nonce},e):((0,l.preload)(a,{as:"script",fetchPriority:"low",nonce:t.nonce}),null)})})}},869093,(e,t,a)=>{"use strict";Object.defineProperty(a,"__esModule",{value:!0}),Object.defineProperty(a,"default",{enumerable:!0,get:function(){return d}});let s=e.r(843476),l=e.r(271645),r=e.r(667585),i=e.r(652157);function n(e){return{default:e&&"default"in e?e.default:e}}let o={loader:()=>Promise.resolve(n(()=>null)),loading:null,ssr:!0},d=function(e){let t={...o,...e},a=(0,l.lazy)(()=>t.loader().then(n)),d=t.loading;function c(e){let n=d?(0,s.jsx)(d,{isLoading:!0,pastDelay:!0,error:null}):null,o=!t.ssr||!!t.loading,c=o?l.Suspense:l.Fragment,u=t.ssr?(0,s.jsxs)(s.Fragment,{children:["u"<typeof window?(0,s.jsx)(i.PreloadChunks,{moduleIds:t.modules}):null,(0,s.jsx)(a,{...e})]}):(0,s.jsx)(r.BailoutToCSR,{reason:"next/dynamic",children:(0,s.jsx)(a,{...e})});return(0,s.jsx)(c,{...o?{fallback:n}:{},children:u})}return c.displayName="LoadableComponent",c}},770703,(e,t,a)=>{"use strict";Object.defineProperty(a,"__esModule",{value:!0}),Object.defineProperty(a,"default",{enumerable:!0,get:function(){return l}});let s=e.r(563141)._(e.r(869093));function l(e,t){let a={};"function"==typeof e&&(a.loader=e);let l={...a,...t};return(0,s.default)({...l,modules:l.loadableGenerated?.modules})}("function"==typeof a.default||"object"==typeof a.default&&null!==a.default)&&void 0===a.default.__esModule&&(Object.defineProperty(a.default,"__esModule",{value:!0}),Object.assign(a.default,a),t.exports=a.default)},988689,e=>{"use strict";var t=e.i(843476),a=e.i(271645);e.i(565650);var s=e.i(11477),l=e.i(474078),r=e.i(961430),i=e.i(628029),n=e.i(770703);let o=(0,n.default)(()=>e.A(653096),{loadableGenerated:{modules:[467211]},ssr:!1}),d=(0,n.default)(()=>e.A(724595),{loadableGenerated:{modules:[896009]},ssr:!1}),c=[{value:"chat",label:"Chat Completions"},{value:"responses",label:"Responses"},{value:"images",label:"Image Generation"},{value:"embeddings",label:"Embeddings"},{value:"speech",label:"Text to Speech"},{value:"transcription",label:"Audio Transcription"},{value:"video",label:"Video Generation"},{value:"music",label:"Music Generation"},{value:"rerank",label:"Rerank"},{value:"search",label:"Web Search"}],u={chat:{model:"",messages:[{role:"user",content:"Hello! Say hi in one sentence."}],max_tokens:100,stream:!1},responses:{model:"",input:"Hello! Say hi in one sentence.",stream:!1},images:{model:"",prompt:"A beautiful sunset over mountains",n:1,size:"1024x1024"},embeddings:{model:"",input:"Hello world",encoding_format:"float"},speech:{model:"openai/tts-1",input:"Hello, this is a test of the text-to-speech endpoint.",voice:"alloy",response_format:"mp3"},transcription:{model:"deepgram/nova-3",language:"en"},video:{model:"comfyui/animatediff",prompt:"A timelapse of a sunset over the ocean",n:1},music:{model:"comfyui/stable-audio",prompt:"Calm ambient piano music with soft reverb",duration:10},rerank:{model:"cohere/rerank-english-v3.0",query:"What is the capital of France?",documents:["Paris is the capital of France.","London is the capital of England.","Berlin is the capital of Germany."],top_n:2},search:{query:"latest AI developments",max_results:5,search_type:"web"}},m={chat:"/v1/chat/completions",responses:"/v1/responses",images:"/v1/images/generations",embeddings:"/v1/embeddings",speech:"/v1/audio/speech",transcription:"/v1/audio/transcriptions",video:"/v1/videos/generations",music:"/v1/music/generations",rerank:"/v1/rerank",search:"/v1/search"},p=["gpt-4o","gpt-4o-mini","gpt-4-turbo","gpt-4-vision","claude-3","claude-sonnet","claude-opus","claude-haiku","gemini","llava","bakllava","pixtral","qwen-vl","qvq","mistral-pixtral"];async function x(e){return new Promise((t,a)=>{let s=new FileReader;s.onload=()=>t(s.result),s.onerror=a,s.readAsDataURL(e)})}function f({data:e}){let a=e?.data||[];return 0===a.length?null:(0,t.jsxs)("div",{className:"p-4 space-y-3",children:[(0,t.jsxs)("p",{className:"text-xs text-text-muted font-medium uppercase tracking-wider",children:[a.length," image",a.length>1?"s":""," generated"]}),(0,t.jsx)("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-3",children:a.map((e,a)=>{let s=e.url||(e.b64_json?`data:image/png;base64,${e.b64_json}`:null);return s?(0,t.jsxs)("div",{className:"relative group rounded-lg overflow-hidden border border-border",children:[(0,t.jsx)("img",{src:s,alt:e.revised_prompt||`Generated image ${a+1}`,className:"w-full"}),(0,t.jsxs)("a",{href:s,download:`image-${a+1}.png`,className:"absolute bottom-2 right-2 bg-black/60 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity flex items-center gap-1",children:[(0,t.jsx)("span",{className:"material-symbols-outlined text-[13px]",children:"download"}),"Save"]})]},a):null})})]})}function h(){let e,[n,h]=(0,a.useState)([]),[g,b]=(0,a.useState)([]),[v,y]=(0,a.useState)(""),[j,N]=(0,a.useState)(""),[w,k]=(0,a.useState)("chat"),[S,C]=(0,a.useState)(""),[_,O]=(0,a.useState)(""),[P,R]=(0,a.useState)(null),[A,T]=(0,a.useState)(null),[B,L]=(0,a.useState)(null),[E,M]=(0,a.useState)(!1),[z,D]=(0,a.useState)(null),[F,$]=(0,a.useState)(null),J=(0,a.useRef)(null),[U,G]=(0,a.useState)(null),[I,W]=(0,a.useState)([]),q="search"===w,H="transcription"===w,K="images"===w,V="chat"===w&&(e=j.toLowerCase(),p.some(t=>e.includes(t)));(0,a.useEffect)(()=>{fetch("/v1/models").then(e=>e.json()).then(e=>{let t=e?.data||[];h(t);let a=new Set;t.forEach(e=>{let t=e.id.split("/");t.length>=2&&a.add(t[0])});let s=Array.from(a).sort().map(e=>({value:e,label:e}));b(s),s.length>0&&y(s[0].value)}).catch(()=>{})},[]);let Q=n.filter(e=>!v||e.id.startsWith(v+"/")).map(e=>({value:e.id,label:e.id})),X=(e,t)=>{let a={...u[e]};return"model"in a&&(a.model=t),JSON.stringify(a,null,2)},Y=()=>{O(""),D(null),$(null),R(null),T(null),L(null)},Z=async e=>{let t=Array.from(e.target.files||[]),a=await Promise.all(t.map(x));W(e=>[...e,...a].slice(0,4))},ee=async()=>{if(!S.trim()&&!H)return;M(!0),Y();let e=new AbortController;J.current=e;let t=Date.now();try{let a,s=m[w];if(H){let t=new FormData;U&&t.append("file",U);try{let e=JSON.parse(S||"{}");for(let[a,s]of Object.entries(e))"file"!==a&&t.append(a,String(s))}catch{}a=await fetch(`/api${s}`,{method:"POST",body:t,signal:e.signal})}else{let t=JSON.parse(S);V&&I.length>0&&(t=((e,t)=>{if(!t.length)return e;let a=[...e.messages||[]];if(0===a.length)return e;let s=a[a.length-1],l="string"==typeof s.content?s.content:"";return a[a.length-1]={...s,content:[{type:"text",text:l},...t.map(e=>({type:"image_url",image_url:{url:e}}))]},{...e,messages:a}})(t,I)),a=await fetch(`/api${s}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t),signal:e.signal})}D(a.status),$(Date.now()-t);let l=a.headers.get("content-type")||"";if(l.startsWith("audio/")){let e=await a.blob(),t=URL.createObjectURL(e);R(t),O(`// Audio response (${l})
|
|
2
|
-
// Click play below to listen.`)}else if(l.includes("text/event-stream")){let e=a.body?.getReader(),t=new TextDecoder,s="";if(e)for(;;){let{done:a,value:l}=await e.read();if(a)break;s+=t.decode(l,{stream:!0}),O(s)}}else{let e=await a.json();O(JSON.stringify(e,null,2)),K&&e?.data&&Array.isArray(e.data)&&a.ok&&T(e),H&&"string"==typeof e?.text&&L(e.text||"(empty result — check provider credentials)")}}catch(e){"AbortError"===e.name?O(JSON.stringify({cancelled:!0},null,2)):O(JSON.stringify({error:e.message},null,2)),$(Date.now()-t)}M(!1)},et=async e=>{try{await navigator.clipboard.writeText(e)}catch{}};return(0,t.jsxs)("div",{className:"space-y-5",children:[(0,t.jsxs)("div",{className:"flex items-start gap-3 px-4 py-3 rounded-lg bg-primary/5 border border-primary/10 text-sm text-text-muted",children:[(0,t.jsx)("span",{className:"material-symbols-outlined text-primary text-[20px] mt-0.5 shrink-0",children:"science"}),(0,t.jsxs)("div",{children:[(0,t.jsx)("p",{className:"font-medium text-text-main mb-0.5",children:"Model Playground"}),(0,t.jsx)("p",{children:"Test any model directly from the dashboard. Pick a provider, model, and endpoint type, then send a request to see the raw response."})]})]}),(0,t.jsx)(s.Card,{children:(0,t.jsxs)("div",{className:"p-4 flex flex-col sm:flex-row items-end gap-4",children:[(0,t.jsxs)("div",{className:"flex-1 w-full",children:[(0,t.jsx)("label",{className:"block text-xs font-medium text-text-muted mb-1.5 uppercase tracking-wider",children:"Endpoint"}),(0,t.jsx)(r.Select,{value:w,onChange:e=>{var t;k(t=e.target.value),C(X(t,j)),G(null),W([]),Y()},options:c,className:"w-full"})]}),!q&&(0,t.jsxs)("div",{className:"flex-1 w-full",children:[(0,t.jsx)("label",{className:"block text-xs font-medium text-text-muted mb-1.5 uppercase tracking-wider",children:"Provider"}),(0,t.jsx)(r.Select,{value:v,onChange:e=>{var t;let a;return y(t=e.target.value),void(N(a=n.filter(e=>!t||e.id.startsWith(t+"/")).map(e=>e.id)[0]||""),C(X(w,a)),Y())},options:g,className:"w-full"})]}),!q&&(0,t.jsxs)("div",{className:"flex-1 w-full",children:[(0,t.jsx)("label",{className:"block text-xs font-medium text-text-muted mb-1.5 uppercase tracking-wider",children:"Model"}),(0,t.jsx)(r.Select,{value:j,onChange:e=>{var t;N(t=e.target.value),C(X(w,t)),Y()},options:Q,className:"w-full"})]}),!q&&(0,t.jsx)("div",{className:"shrink-0",children:E?(0,t.jsx)(l.Button,{icon:"stop",variant:"secondary",onClick:()=>{J.current&&J.current.abort()},children:"Cancel"}):(0,t.jsx)(l.Button,{icon:"send",onClick:ee,disabled:!S.trim()&&!H||!j&&!H,children:"Send"})})]})}),q?(0,t.jsx)(d,{}):(0,t.jsxs)(t.Fragment,{children:[(H||V)&&(0,t.jsx)(s.Card,{children:(0,t.jsxs)("div",{className:"p-4 space-y-3",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"material-symbols-outlined text-[18px] text-text-muted",children:"attach_file"}),(0,t.jsx)("h3",{className:"text-sm font-semibold text-text-main",children:H?"Audio File":"Attach Images (Vision)"}),H&&(0,t.jsx)(i.Badge,{variant:"info",size:"sm",children:"multipart/form-data"}),V&&(0,t.jsx)(i.Badge,{variant:"info",size:"sm",children:"up to 4 images"})]}),H&&(0,t.jsxs)("div",{children:[(0,t.jsx)("input",{type:"file",accept:"audio/*,video/*",onChange:e=>{G(e.target.files?.[0]??null)},className:"w-full px-3 py-2 rounded-lg bg-surface border border-border text-text-main text-sm focus:outline-none focus:ring-2 focus:ring-primary/30 file:mr-3 file:py-1 file:px-3 file:rounded file:border-0 file:bg-primary/10 file:text-primary file:text-sm"}),U&&(0,t.jsxs)("p",{className:"text-xs text-text-muted mt-1 flex items-center gap-1",children:[(0,t.jsx)("span",{className:"material-symbols-outlined text-[12px] text-green-500",children:"check_circle"}),U.name," (",(U.size/1024).toFixed(0)," KB)"]}),!U&&(0,t.jsxs)("p",{className:"text-xs text-amber-500 mt-1 flex items-center gap-1",children:[(0,t.jsx)("span",{className:"material-symbols-outlined text-[12px]",children:"info"}),"Select an audio file to transcribe (mp3, wav, m4a, ogg, flac…)"]})]}),V&&(0,t.jsxs)("div",{children:[(0,t.jsx)("input",{type:"file",accept:"image/*",multiple:!0,onChange:Z,className:"w-full px-3 py-2 rounded-lg bg-surface border border-border text-text-main text-sm focus:outline-none focus:ring-2 focus:ring-primary/30 file:mr-3 file:py-1 file:px-3 file:rounded file:border-0 file:bg-primary/10 file:text-primary file:text-sm"}),I.length>0&&(0,t.jsxs)("div",{className:"flex gap-2 mt-2 flex-wrap",children:[I.map((e,a)=>(0,t.jsxs)("div",{className:"relative group size-16 rounded overflow-hidden border border-border",children:[(0,t.jsx)("img",{src:e,alt:`Attached ${a+1}`,className:"w-full h-full object-cover"}),(0,t.jsx)("button",{onClick:()=>W(e=>e.filter((e,t)=>t!==a)),className:"absolute inset-0 bg-black/50 text-white opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center",children:(0,t.jsx)("span",{className:"material-symbols-outlined text-[16px]",children:"close"})})]},a)),(0,t.jsx)("button",{onClick:()=>W([]),className:"text-xs text-text-muted hover:text-red-500 self-center ml-1",children:"Clear all"})]})]})]})}),(0,t.jsxs)("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-4",children:[(0,t.jsx)(s.Card,{children:(0,t.jsxs)("div",{className:"p-4 space-y-3",children:[(0,t.jsxs)("div",{className:"flex items-center justify-between",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"material-symbols-outlined text-[18px] text-text-muted",children:"upload"}),(0,t.jsx)("h3",{className:"text-sm font-semibold text-text-main",children:"Request"}),(0,t.jsxs)(i.Badge,{variant:"info",size:"sm",children:["POST ",m[w]]})]}),(0,t.jsxs)("div",{className:"flex items-center gap-1",children:[(0,t.jsx)("button",{onClick:()=>et(S),className:"p-1.5 rounded hover:bg-black/5 dark:hover:bg-white/5 text-text-muted hover:text-text-main transition-colors",title:"Copy",children:(0,t.jsx)("span",{className:"material-symbols-outlined text-[16px]",children:"content_copy"})}),(0,t.jsx)("button",{onClick:()=>{let e={...u[w]};"model"in e&&(e.model=j),C(JSON.stringify(e,null,2))},className:"p-1.5 rounded hover:bg-black/5 dark:hover:bg-white/5 text-text-muted hover:text-text-main transition-colors",title:"Reset to default",children:(0,t.jsx)("span",{className:"material-symbols-outlined text-[16px]",children:"restart_alt"})})]})]}),H&&(0,t.jsxs)("p",{className:"text-xs text-text-muted bg-amber-500/10 border border-amber-500/20 rounded px-2 py-1.5 flex items-start gap-1",children:[(0,t.jsx)("span",{className:"material-symbols-outlined text-[12px] text-amber-500 mt-0.5",children:"info"}),"Transcription uses multipart/form-data. Upload the audio file above — JSON below controls extra params (model, language)."]}),(0,t.jsx)("div",{className:"border border-border rounded-lg overflow-hidden",children:(0,t.jsx)(o,{height:"400px",defaultLanguage:"json",value:S,onChange:e=>C(e||""),theme:"vs-dark",options:{minimap:{enabled:!1},fontSize:12,lineNumbers:"on",scrollBeyondLastLine:!1,wordWrap:"on",automaticLayout:!0,formatOnPaste:!0}})})]})}),(0,t.jsx)(s.Card,{children:(0,t.jsxs)("div",{className:"p-4 space-y-3",children:[(0,t.jsxs)("div",{className:"flex items-center justify-between",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"material-symbols-outlined text-[18px] text-text-muted",children:"download"}),(0,t.jsx)("h3",{className:"text-sm font-semibold text-text-main",children:"Response"}),null!==z&&(0,t.jsx)(i.Badge,{variant:z>=200&&z<300?"success":"error",size:"sm",children:z}),null!==F&&(0,t.jsxs)("span",{className:"text-xs text-text-muted",children:[F,"ms"]}),E&&(0,t.jsx)("span",{className:"material-symbols-outlined text-[14px] text-primary animate-spin",children:"progress_activity"})]}),(0,t.jsx)("div",{className:"flex items-center gap-1",children:(0,t.jsx)("button",{onClick:()=>et(_),className:"p-1.5 rounded hover:bg-black/5 dark:hover:bg-white/5 text-text-muted hover:text-text-main transition-colors",title:"Copy",children:(0,t.jsx)("span",{className:"material-symbols-outlined text-[16px]",children:"content_copy"})})})]}),(0,t.jsx)("div",{className:"border border-border rounded-lg overflow-hidden",children:P?(0,t.jsxs)("div",{className:"p-4 space-y-3",children:[(0,t.jsx)("audio",{controls:!0,src:P,className:"w-full rounded-lg",autoPlay:!0}),(0,t.jsxs)("a",{href:P,download:"speech.mp3",className:"inline-flex items-center gap-2 text-sm text-primary hover:underline",children:[(0,t.jsx)("span",{className:"material-symbols-outlined text-[16px]",children:"download"}),"Download audio"]})]}):A?(0,t.jsx)(f,{data:A}):null!==B?(0,t.jsxs)("div",{className:"p-4 space-y-2",children:[(0,t.jsx)("p",{className:"text-xs text-text-muted font-medium uppercase tracking-wider",children:"Transcription"}),(0,t.jsx)("div",{className:"bg-surface/50 rounded p-3 text-sm text-text-main leading-relaxed whitespace-pre-wrap",children:B}),(0,t.jsxs)("button",{onClick:()=>et(B),className:"text-xs text-primary hover:underline flex items-center gap-1",children:[(0,t.jsx)("span",{className:"material-symbols-outlined text-[12px]",children:"content_copy"}),"Copy text"]})]}):(0,t.jsx)(o,{height:"400px",defaultLanguage:"json",value:_,theme:"vs-dark",options:{minimap:{enabled:!1},fontSize:12,lineNumbers:"on",scrollBeyondLastLine:!1,wordWrap:"on",automaticLayout:!0,readOnly:!0}})})]})})]})]})]})}e.s(["default",()=>h])},653096,e=>{e.v(t=>Promise.all(["static/chunks/54afb78f5db263b9.js"].map(t=>e.l(t))).then(()=>t(467211)))},724595,e=>{e.v(t=>Promise.all(["static/chunks/56bb30976d06ea37.js"].map(t=>e.l(t))).then(()=>t(896009)))}]);
|
|
File without changes
|
|
File without changes
|
|
File without changes
|