diff --git a/changelog.d/features/9752-one-click-free-providers.md b/changelog.d/features/9752-one-click-free-providers.md new file mode 100644 index 0000000000..f8c06d4603 --- /dev/null +++ b/changelog.d/features/9752-one-click-free-providers.md @@ -0,0 +1,4 @@ +- **Onboarding:** add an explicit, reviewable one-click setup for eligible no-auth LLM providers, + with per-provider caution links, selectable confirmation, idempotent creation, and safe partial + retries. Existing provider connections are never changed and setup completion never enables + providers silently. ([#9752](https://github.com/diegosouzapw/OmniRoute/issues/9752)) diff --git a/docs/getting-started/PROVIDERS-GUIDE.md b/docs/getting-started/PROVIDERS-GUIDE.md index c1c237862e..f725baa6df 100644 --- a/docs/getting-started/PROVIDERS-GUIDE.md +++ b/docs/getting-started/PROVIDERS-GUIDE.md @@ -30,6 +30,18 @@ See **[WEB-COOKIE-GUIDE.md](./WEB-COOKIE-GUIDE.md)** for general setup instructi ## Quick Start: Connect Your First Provider +### Optional first-run free-provider setup + +The first-run wizard offers an explicit **Set up free providers** card. It derives the current +eligible list from OmniRoute's no-auth provider registry, then lets you review and deselect each +provider before confirming. OmniRoute shows the provider's caution notice and a link to its site +so you can review third-party terms, privacy, availability, and rate limits first. + +This action is optional: finishing the wizard never creates free-provider connections silently. +It creates only providers that are still missing, leaves existing customized connections +untouched, and reports created, already-configured, and failed providers individually. You can +safely retry only the failures after a partial result. + ### Option A: Free Provider (No Credit Card) 1. Open the dashboard at `http://localhost:20128` diff --git a/src/app/(dashboard)/dashboard/onboarding/page.tsx b/src/app/(dashboard)/dashboard/onboarding/page.tsx index 7385bc0a86..f0164d63ac 100644 --- a/src/app/(dashboard)/dashboard/onboarding/page.tsx +++ b/src/app/(dashboard)/dashboard/onboarding/page.tsx @@ -4,6 +4,7 @@ import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useDisplayBaseUrl } from "@/shared/hooks"; +import { FreeProviderOnboardingCard } from "./steps/FreeProviderOnboardingCard"; import { TierTour } from "./steps/TierTour"; const STEP_IDS = ["welcome", "tiers", "security", "provider", "test", "done"]; @@ -357,6 +358,12 @@ export default function OnboardingWizard() { {currentStep.id === "provider" && (

{t("providerDesc")}

+ +
+ + {t("freeProviders.orUseApiKey")} + +
{COMMON_PROVIDERS.map((p) => ( + {failedIds.length > 0 && ( + + )} +
+ + ); +} diff --git a/src/app/api/providers/free-onboarding/route.ts b/src/app/api/providers/free-onboarding/route.ts new file mode 100644 index 0000000000..dd22cb99ca --- /dev/null +++ b/src/app/api/providers/free-onboarding/route.ts @@ -0,0 +1,69 @@ +import { NextResponse } from "next/server"; +import { z } from "zod"; + +import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; +import { createProviderConnection, getProviderConnections } from "@/lib/db/providers"; +import { + getEligibleFreeOnboardingProviders, + selectUnconfiguredFreeOnboardingProviders, + setupFreeProviderConnections, + withFreeProviderSetupLock, +} from "@/lib/providers/freeOnboarding"; +import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; + +const setupSchema = z.object({ + providerIds: z.array(z.string().trim().min(1)).min(1).max(20), + confirmed: z.literal(true), +}); + +export async function GET(request: Request) { + const authError = await requireManagementAuth(request); + if (authError) return authError; + + try { + const candidates = getEligibleFreeOnboardingProviders(); + const connections = await getProviderConnections(); + return NextResponse.json({ + providers: selectUnconfiguredFreeOnboardingProviders(candidates, connections), + }); + } catch { + return NextResponse.json({ error: "Failed to load free providers" }, { status: 500 }); + } +} + +export async function POST(request: Request) { + const authError = await requireManagementAuth(request); + if (authError) return authError; + + let rawBody: unknown; + try { + rawBody = await request.json(); + } catch { + return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); + } + + const validation = validateBody(setupSchema, rawBody); + if (isValidationFailure(validation)) { + return NextResponse.json({ error: validation.error }, { status: 400 }); + } + + try { + const result = await withFreeProviderSetupLock(() => + setupFreeProviderConnections({ + requestedIds: validation.data.providerIds, + candidates: getEligibleFreeOnboardingProviders(), + listExisting: () => getProviderConnections(), + create: (input) => createProviderConnection(input), + }) + ); + return NextResponse.json(result); + } catch (error) { + if (error instanceof Error && error.message.startsWith("Ineligible free provider IDs:")) { + return NextResponse.json( + { error: "One or more providers are not eligible" }, + { status: 400 } + ); + } + return NextResponse.json({ error: "Failed to set up free providers" }, { status: 500 }); + } +} diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 93bfb067ef..6c98e5d525 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -4744,7 +4744,26 @@ "configure": "تكوين مقدمي الخدمات" }, "tierFlowDiagramAlt": "OmniRoute مخطط احتياطي ثلاثي الطبقات", - "apiKeyMgmt": "إدارة مفتاح واجهة برمجة التطبيقات (API)" + "apiKeyMgmt": "إدارة مفتاح واجهة برمجة التطبيقات (API)", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "المزودون", diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json index 7f1c6e9cb2..56d431f998 100644 --- a/src/i18n/messages/az.json +++ b/src/i18n/messages/az.json @@ -4744,7 +4744,26 @@ "configure": "Provayderləri konfiqurasiya edin" }, "tierFlowDiagramAlt": "OmniRoute 3 səviyyəli ehtiyat diaqramı", - "apiKeyMgmt": "API Açar İdarəetmə" + "apiKeyMgmt": "API Açar İdarəetmə", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Providers", diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index f9cfd6e34f..e6ca4d2dac 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -4744,7 +4744,26 @@ "configure": "Конфигурирайте доставчици" }, "tierFlowDiagramAlt": "3-степенна резервна диаграма на OmniRoute", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Доставчици", diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json index 58146ad7d0..5335d58074 100644 --- a/src/i18n/messages/bn.json +++ b/src/i18n/messages/bn.json @@ -4744,7 +4744,26 @@ "configure": "প্রদানকারী কনফিগার করুন" }, "tierFlowDiagramAlt": "OmniRoute 3-স্তরের ফলব্যাক ডায়াগ্রাম", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Providers", diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index bfb6f40bd7..457eccc154 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -4744,7 +4744,26 @@ "configure": "Nakonfigurujte poskytovatele" }, "tierFlowDiagramAlt": "Třívrstvý záložní diagram OmniRoute", - "apiKeyMgmt": "Správa API klíčů" + "apiKeyMgmt": "Správa API klíčů", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Poskytovatelé", diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index 8c5d27f542..cfb3d08689 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -4744,7 +4744,26 @@ "configure": "Konfigurer udbydere" }, "tierFlowDiagramAlt": "OmniRoute 3-tiers fallback diagram", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Udbydere", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index eb410b6107..54de1c4c4a 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -4744,7 +4744,26 @@ "configure": "Anbieter konfigurieren" }, "tierFlowDiagramAlt": "OmniRoute 3-Stufen-Fallback-Diagramm", - "apiKeyMgmt": "API-Schlüsselverwaltung" + "apiKeyMgmt": "API-Schlüsselverwaltung", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Anbieter", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 3dd99218d4..0e35cfcc8c 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -4750,7 +4750,26 @@ "configure": "Configure providers" }, "tierFlowDiagramAlt": "OmniRoute 3-tier fallback diagram", - "apiKeyMgmt": "API Key Management" + "apiKeyMgmt": "API Key Management", + "freeProviders": { + "title": "Set up free providers", + "description": "Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "Loading free providers...", + "loadFailed": "Could not load free providers.", + "alreadyConfigured": "All eligible free providers are already configured.", + "reviewProviderSite": "Review provider site and terms", + "confirmation": "I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "Set up selected providers", + "settingUp": "Setting up...", + "setupFailed": "Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "Retry failed providers", + "orUseApiKey": "or connect with an API key", + "result": { + "created": "created", + "skipped": "already configured", + "failed": "failed" + } + } }, "providers": { "title": "Providers", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 5a8e4533b2..753428fee9 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -4744,7 +4744,26 @@ "configure": "Configurar proveedores" }, "tierFlowDiagramAlt": "Diagrama alternativo de 3 niveles de OmniRoute", - "apiKeyMgmt": "Gestión de claves API" + "apiKeyMgmt": "Gestión de claves API", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Proveedores", diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json index a685d117ba..e14d12dc1f 100644 --- a/src/i18n/messages/fa.json +++ b/src/i18n/messages/fa.json @@ -4744,7 +4744,26 @@ "configure": "ارائه دهندگان را پیکربندی کنید" }, "tierFlowDiagramAlt": "نمودار بازگشتی 3 لایه OmniRoute", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Providers", diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index a969f27b86..7772b6c7a8 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -4744,7 +4744,26 @@ "configure": "Määritä palveluntarjoajat" }, "tierFlowDiagramAlt": "OmniRoute 3-tasoinen varakaavio", - "apiKeyMgmt": "API-avainhallinta" + "apiKeyMgmt": "API-avainhallinta", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Palveluntarjoajat", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index d0a23dc6fb..72977a8ac9 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -4744,7 +4744,26 @@ "configure": "Configurer les fournisseurs" }, "tierFlowDiagramAlt": "Diagramme de secours OmniRoute à 3 niveaux", - "apiKeyMgmt": "Gestion des clés API" + "apiKeyMgmt": "Gestion des clés API", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Fournisseurs", diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json index 4a4f3ca7d0..fc080fbbb0 100644 --- a/src/i18n/messages/gu.json +++ b/src/i18n/messages/gu.json @@ -4744,7 +4744,26 @@ "configure": "પ્રદાતાઓને ગોઠવો" }, "tierFlowDiagramAlt": "OmniRoute 3-ટાયર ફોલબેક ડાયાગ્રામ", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Providers", diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index 1117b62ec0..54633d032f 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -4744,7 +4744,26 @@ "configure": "הגדר ספקים" }, "tierFlowDiagramAlt": "דיאגרמת OmniRoute 3-tier fallback", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "ספקים", diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index 10cd712a10..6b2c5b4ea3 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -4744,7 +4744,26 @@ "configure": "प्रदाताओं को कॉन्फ़िगर करें" }, "tierFlowDiagramAlt": "ओम्निरूट 3-स्तरीय फ़ॉलबैक आरेख", - "apiKeyMgmt": "एपीआई कुंजी प्रबंधन" + "apiKeyMgmt": "एपीआई कुंजी प्रबंधन", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "प्रदाता", diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index 4fb34eb273..8bf0feabc1 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -4744,7 +4744,26 @@ "configure": "Konfigurálja a szolgáltatókat" }, "tierFlowDiagramAlt": "OmniRoute 3-szintű tartalék diagram", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Szolgáltatók", diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index e07abb08e2..f7d2a08e36 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -4744,7 +4744,26 @@ "configure": "Konfigurasikan penyedia" }, "tierFlowDiagramAlt": "Diagram cadangan 3 tingkat OmniRoute", - "apiKeyMgmt": "Manajemen Kunci API" + "apiKeyMgmt": "Manajemen Kunci API", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Penyedia", diff --git a/src/i18n/messages/in.json b/src/i18n/messages/in.json index 068f6ab2df..af4cfddf38 100644 --- a/src/i18n/messages/in.json +++ b/src/i18n/messages/in.json @@ -4744,7 +4744,26 @@ "configure": "Konfigurasikan penyedia" }, "tierFlowDiagramAlt": "Diagram cadangan 3 tingkat OmniRoute", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Providers", diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index 1ba03c3cce..4b24a63806 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -4744,7 +4744,26 @@ "configure": "Configura i fornitori" }, "tierFlowDiagramAlt": "Diagramma di fallback a 3 livelli di OmniRoute", - "apiKeyMgmt": "Gestione chiave API" + "apiKeyMgmt": "Gestione chiave API", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Fornitori", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index c8e3ab75cd..fa2d728ce9 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -4744,7 +4744,26 @@ "configure": "プロバイダーの構成" }, "tierFlowDiagramAlt": "OmniRoute 3 層フォールバック図", - "apiKeyMgmt": "APIキー管理" + "apiKeyMgmt": "APIキー管理", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "プロバイダー", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 3465d4dd4d..4d5d2bc422 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -4744,7 +4744,26 @@ "configure": "공급자 구성" }, "tierFlowDiagramAlt": "OmniRoute 3계층 대체 다이어그램", - "apiKeyMgmt": "API 키 관리" + "apiKeyMgmt": "API 키 관리", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "공급자", diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index 181829d6e5..c1bf3dbb77 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -4744,7 +4744,26 @@ "configure": "प्रदाते कॉन्फिगर करा" }, "tierFlowDiagramAlt": "OmniRoute 3-स्तरीय फॉलबॅक आकृती", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Providers", diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index fab7dc22c3..20af75597c 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -4744,7 +4744,26 @@ "configure": "Konfigurasikan pembekal" }, "tierFlowDiagramAlt": "Gambar rajah sandar 3 peringkat OmniRoute", - "apiKeyMgmt": "Mgmt Kunci API" + "apiKeyMgmt": "Mgmt Kunci API", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Pembekal", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index 834c8aae6d..6675e33a45 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -4744,7 +4744,26 @@ "configure": "Configureer aanbieders" }, "tierFlowDiagramAlt": "OmniRoute fallback-diagram met 3 niveaus", - "apiKeyMgmt": "API-sleutelbeheer" + "apiKeyMgmt": "API-sleutelbeheer", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Aanbieders", diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index 58789e2514..b3aa989d89 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -4744,7 +4744,26 @@ "configure": "Konfigurer leverandører" }, "tierFlowDiagramAlt": "OmniRoute 3-lags reservediagram", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Leverandører", diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index 4fd30f026c..9efe7facb6 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -4744,7 +4744,26 @@ "configure": "I-configure ang mga provider" }, "tierFlowDiagramAlt": "OmniRoute 3-tier fallback diagram", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Mga provider", diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index f0604529d2..22b7fcd8df 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -4744,7 +4744,26 @@ "configure": "Skonfiguruj providers" }, "tierFlowDiagramAlt": "Schemat fallback 3-poziomowego OmniRoute", - "apiKeyMgmt": "Zarządzanie kluczami API" + "apiKeyMgmt": "Zarządzanie kluczami API", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Providers", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index ecd5691af5..b1f281d9a8 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -4750,7 +4750,26 @@ "configure": "Configurar provedores" }, "tierFlowDiagramAlt": "Diagrama de fallback de 3 camadas do OmniRoute", - "apiKeyMgmt": "Ger. de Chaves API" + "apiKeyMgmt": "Ger. de Chaves API", + "freeProviders": { + "title": "Configurar provedores gratuitos", + "description": "Opcionalmente, ative provedores selecionados que não exigem cadastro. Revise o aviso e o site de cada provedor antes de continuar; disponibilidade e limites são controlados por terceiros.", + "loading": "Carregando provedores gratuitos...", + "loadFailed": "Não foi possível carregar os provedores gratuitos.", + "alreadyConfigured": "Todos os provedores gratuitos elegíveis já estão configurados.", + "reviewProviderSite": "Revisar site e termos do provedor", + "confirmation": "Revisei estes provedores de terceiros e quero que o OmniRoute crie as conexões selecionadas.", + "setupSelected": "Configurar provedores selecionados", + "settingUp": "Configurando...", + "setupFailed": "Não foi possível configurar os provedores selecionados. Nenhuma conexão existente foi alterada.", + "retryFailed": "Tentar novamente os provedores com falha", + "orUseApiKey": "ou conectar com uma chave de API", + "result": { + "created": "criado", + "skipped": "já configurado", + "failed": "falhou" + } + } }, "providers": { "title": "Provedores", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 9fbfc1c65b..6e0922cbe1 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -4744,7 +4744,26 @@ "configure": "Configurar provedores" }, "tierFlowDiagramAlt": "Diagrama de fallback de 3 camadas do OmniRoute", - "apiKeyMgmt": "Gerenciamento de chave de API" + "apiKeyMgmt": "Gerenciamento de chave de API", + "freeProviders": { + "title": "Configurar fornecedores gratuitos", + "description": "Opcionalmente, ative fornecedores selecionados que não exigem registo. Reveja o aviso e o site de cada fornecedor antes de continuar; a disponibilidade e os limites são controlados por terceiros.", + "loading": "A carregar fornecedores gratuitos...", + "loadFailed": "Não foi possível carregar os fornecedores gratuitos.", + "alreadyConfigured": "Todos os fornecedores gratuitos elegíveis já estão configurados.", + "reviewProviderSite": "Rever site e termos do fornecedor", + "confirmation": "Revi estes fornecedores de terceiros e quero que o OmniRoute crie as ligações selecionadas.", + "setupSelected": "Configurar fornecedores selecionados", + "settingUp": "A configurar...", + "setupFailed": "Não foi possível configurar os fornecedores selecionados. Nenhuma ligação existente foi alterada.", + "retryFailed": "Tentar novamente os fornecedores com falha", + "orUseApiKey": "ou ligar com uma chave de API", + "result": { + "created": "criado", + "skipped": "já configurado", + "failed": "falhou" + } + } }, "providers": { "title": "Provedores", diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 2fa29eebaf..bcb3f5f21c 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -4744,7 +4744,26 @@ "configure": "Configurați furnizorii" }, "tierFlowDiagramAlt": "Diagrama de rezervă pe 3 niveluri OmniRoute", - "apiKeyMgmt": "Gestiunea cheii API" + "apiKeyMgmt": "Gestiunea cheii API", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Furnizorii", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 85f1156799..88ee259461 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -4744,7 +4744,26 @@ "configure": "Настройка поставщиков" }, "tierFlowDiagramAlt": "Трехуровневая резервная схема OmniRoute", - "apiKeyMgmt": "Управление ключами API" + "apiKeyMgmt": "Управление ключами API", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Провайдеры", diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index 7d939a5de8..a77c8b24f7 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -4744,7 +4744,26 @@ "configure": "Nakonfigurujte poskytovateľov" }, "tierFlowDiagramAlt": "3-vrstvový záložný diagram OmniRoute", - "apiKeyMgmt": "API kľúč Mgmt" + "apiKeyMgmt": "API kľúč Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Poskytovatelia", diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index 7f08d920c0..389aa28c8d 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -4744,7 +4744,26 @@ "configure": "Konfigurera leverantörer" }, "tierFlowDiagramAlt": "OmniRoute 3-nivå reservdiagram", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Leverantörer", diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json index ec8b508d9b..ae054603a4 100644 --- a/src/i18n/messages/sw.json +++ b/src/i18n/messages/sw.json @@ -4744,7 +4744,26 @@ "configure": "Sanidi watoa huduma" }, "tierFlowDiagramAlt": "Mchoro wa kurudi nyuma wa ngazi 3 wa OmniRoute", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Providers", diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json index 1e75091675..50e26bed31 100644 --- a/src/i18n/messages/ta.json +++ b/src/i18n/messages/ta.json @@ -4744,7 +4744,26 @@ "configure": "வழங்குநர்களை உள்ளமைக்கவும்" }, "tierFlowDiagramAlt": "OmniRoute 3-அடுக்கு ஃபால்பேக் வரைபடம்", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Providers", diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json index 39ca6eaa71..5c8323aa78 100644 --- a/src/i18n/messages/te.json +++ b/src/i18n/messages/te.json @@ -4744,7 +4744,26 @@ "configure": "ప్రొవైడర్లను కాన్ఫిగర్ చేయండి" }, "tierFlowDiagramAlt": "OmniRoute 3-టైర్ ఫాల్‌బ్యాక్ రేఖాచిత్రం", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Providers", diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 3e8d03dc7f..8236930553 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -4744,7 +4744,26 @@ "configure": "กำหนดค่าผู้ให้บริการ" }, "tierFlowDiagramAlt": "แผนภาพทางเลือก OmniRoute 3 ระดับ", - "apiKeyMgmt": "การจัดการคีย์ API" + "apiKeyMgmt": "การจัดการคีย์ API", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "ผู้ให้บริการ", diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index 62ca2a4517..46e4a89ec8 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -4744,7 +4744,26 @@ "configure": "Sağlayıcıları yapılandırma" }, "tierFlowDiagramAlt": "OmniRoute 3 katmanlı geri dönüş diyagramı", - "apiKeyMgmt": "API Anahtarı Yönetimi" + "apiKeyMgmt": "API Anahtarı Yönetimi", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Sağlayıcılar", diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index 1931707ada..08f004aa06 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -4744,7 +4744,26 @@ "configure": "Налаштувати провайдерів" }, "tierFlowDiagramAlt": "3-рівнева резервна схема OmniRoute", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Провайдери", diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json index 5bc9e630a0..22aad14287 100644 --- a/src/i18n/messages/ur.json +++ b/src/i18n/messages/ur.json @@ -4744,7 +4744,26 @@ "configure": "فراہم کنندگان کو ترتیب دیں۔" }, "tierFlowDiagramAlt": "OmniRoute 3 درجے کا فال بیک ڈایاگرام", - "apiKeyMgmt": "API Key Mgmt" + "apiKeyMgmt": "API Key Mgmt", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Providers", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index 0a52f43c79..de62323a3c 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -4744,7 +4744,26 @@ "configure": "Cấu hình nhà cung cấp" }, "tierFlowDiagramAlt": "Sơ đồ dự phòng 3 cấp của OmniRoute", - "apiKeyMgmt": "Quản lý khóa API" + "apiKeyMgmt": "Quản lý khóa API", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "Nhà cung cấp", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 478f5f2b49..a8e39e279a 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -4744,7 +4744,26 @@ "configure": "配置提供者" }, "tierFlowDiagramAlt": "OmniRoute 3 层回退图", - "apiKeyMgmt": "API密钥管理" + "apiKeyMgmt": "API密钥管理", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "提供者", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index cfa907d4c5..408738fc13 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -4744,7 +4744,26 @@ "configure": "設定提供者" }, "tierFlowDiagramAlt": "OmniRoute 3 層回退圖", - "apiKeyMgmt": "API金鑰管理" + "apiKeyMgmt": "API金鑰管理", + "freeProviders": { + "title": "__MISSING__:Set up free providers", + "description": "__MISSING__:Optionally enable selected no-signup providers. Review each provider's notice and site before continuing; availability and rate limits are controlled by third parties.", + "loading": "__MISSING__:Loading free providers...", + "loadFailed": "__MISSING__:Could not load free providers.", + "alreadyConfigured": "__MISSING__:All eligible free providers are already configured.", + "reviewProviderSite": "__MISSING__:Review provider site and terms", + "confirmation": "__MISSING__:I reviewed these third-party providers and want OmniRoute to create the selected connections.", + "setupSelected": "__MISSING__:Set up selected providers", + "settingUp": "__MISSING__:Setting up...", + "setupFailed": "__MISSING__:Could not set up the selected providers. Nothing existing was changed.", + "retryFailed": "__MISSING__:Retry failed providers", + "orUseApiKey": "__MISSING__:or connect with an API key", + "result": { + "created": "__MISSING__:created", + "skipped": "__MISSING__:already configured", + "failed": "__MISSING__:failed" + } + } }, "providers": { "title": "提供者", diff --git a/src/lib/providers/freeOnboarding.ts b/src/lib/providers/freeOnboarding.ts new file mode 100644 index 0000000000..6c69c396f3 --- /dev/null +++ b/src/lib/providers/freeOnboarding.ts @@ -0,0 +1,164 @@ +import { NOAUTH_PROVIDERS } from "@/shared/constants/providers"; +import { REGISTRY } from "@omniroute/open-sse/config/providerRegistry"; + +interface NoAuthOnboardingMetadata { + id: string; + name: string; + website?: string; + noAuth?: boolean; + hasFree?: boolean; + isLocalCli?: boolean; + serviceKinds?: string[]; + authHint?: string; + freeNote?: string; + notice?: { text?: string }; + deprecated?: boolean; + disabled?: boolean; + unsafe?: boolean; + hiddenFromDashboard?: boolean; +} + +export interface FreeOnboardingProvider { + id: string; + name: string; + website: string; + caution: string; + defaultModel?: string; +} + +export interface ExistingProviderConnection { + provider?: unknown; +} + +export interface FreeProviderConnectionInput { + provider: string; + authType: "no-auth"; + name: string; + isActive: true; + testStatus: "unknown"; + defaultModel?: string; +} + +export type FreeProviderSetupResult = + | { providerId: string; status: "created"; connectionId: string } + | { providerId: string; status: "skipped"; reason: "already-configured" } + | { providerId: string; status: "failed"; reason: "Failed to create provider" }; + +function isEligibleProvider(provider: NoAuthOnboardingMetadata): boolean { + return ( + provider.noAuth === true && + provider.hasFree === true && + provider.isLocalCli !== true && + provider.serviceKinds?.includes("llm") === true && + provider.deprecated !== true && + provider.disabled !== true && + provider.unsafe !== true && + provider.hiddenFromDashboard !== true + ); +} + +function toOnboardingProvider(provider: NoAuthOnboardingMetadata): FreeOnboardingProvider { + const defaultModel = REGISTRY[provider.id]?.models?.[0]?.id; + return { + id: provider.id, + name: provider.name, + website: provider.website || "", + caution: + provider.notice?.text || + provider.freeNote || + provider.authHint || + "This provider is operated by a third party and may enforce its own terms and limits.", + ...(defaultModel ? { defaultModel } : {}), + }; +} + +export function getEligibleFreeOnboardingProviders(): FreeOnboardingProvider[] { + return Object.values(NOAUTH_PROVIDERS as Record) + .filter(isEligibleProvider) + .map(toOnboardingProvider) + .sort((left, right) => left.id.localeCompare(right.id)); +} + +export function selectUnconfiguredFreeOnboardingProviders( + candidates: FreeOnboardingProvider[], + connections: ExistingProviderConnection[] +): FreeOnboardingProvider[] { + const configured = new Set( + connections + .map((connection) => connection.provider) + .filter((provider): provider is string => typeof provider === "string") + ); + return candidates.filter((candidate) => !configured.has(candidate.id)); +} + +interface SetupFreeProviderConnectionsOptions { + requestedIds: string[]; + candidates: FreeOnboardingProvider[]; + listExisting: () => Promise; + create: (input: FreeProviderConnectionInput) => Promise<{ id?: unknown } | null>; +} + +function validateRequestedIds( + requestedIds: string[], + candidates: FreeOnboardingProvider[] +): Map { + const candidatesById = new Map(candidates.map((candidate) => [candidate.id, candidate])); + const invalidIds = [...new Set(requestedIds)] + .filter((providerId) => !candidatesById.has(providerId)) + .sort((left, right) => left.localeCompare(right)); + if (invalidIds.length > 0) { + throw new Error(`Ineligible free provider IDs: ${invalidIds.join(", ")}`); + } + return candidatesById; +} + +export async function setupFreeProviderConnections( + options: SetupFreeProviderConnectionsOptions +): Promise<{ results: FreeProviderSetupResult[] }> { + const candidatesById = validateRequestedIds(options.requestedIds, options.candidates); + const results: FreeProviderSetupResult[] = []; + + for (const providerId of [...new Set(options.requestedIds)]) { + const existing = await options.listExisting(); + if (existing.some((connection) => connection.provider === providerId)) { + results.push({ providerId, status: "skipped", reason: "already-configured" }); + continue; + } + + const candidate = candidatesById.get(providerId)!; + try { + const connection = await options.create({ + provider: providerId, + authType: "no-auth", + name: candidate.name, + isActive: true, + testStatus: "unknown", + ...(candidate.defaultModel ? { defaultModel: candidate.defaultModel } : {}), + }); + if (!connection || typeof connection.id !== "string") { + throw new Error("Provider connection was not persisted"); + } + results.push({ providerId, status: "created", connectionId: connection.id }); + } catch { + results.push({ providerId, status: "failed", reason: "Failed to create provider" }); + } + } + + return { results }; +} + +let freeProviderSetupQueue: Promise = Promise.resolve(); + +export async function withFreeProviderSetupLock(operation: () => Promise): Promise { + const previous = freeProviderSetupQueue; + let release: () => void = () => {}; + freeProviderSetupQueue = new Promise((resolve) => { + release = resolve; + }); + await previous; + try { + return await operation(); + } finally { + release(); + } +} diff --git a/tests/unit/free-provider-onboarding-selector.test.ts b/tests/unit/free-provider-onboarding-selector.test.ts new file mode 100644 index 0000000000..3bd45bc50f --- /dev/null +++ b/tests/unit/free-provider-onboarding-selector.test.ts @@ -0,0 +1,43 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + getEligibleFreeOnboardingProviders, + selectUnconfiguredFreeOnboardingProviders, +} from "../../src/lib/providers/freeOnboarding.ts"; + +test("free onboarding candidates come from the no-auth registry and exclude local/non-LLM entries", () => { + const providers = getEligibleFreeOnboardingProviders(); + const ids = providers.map((provider) => provider.id); + + assert.deepEqual( + ids, + [...ids].sort((a, b) => a.localeCompare(b)) + ); + assert.ok(ids.includes("opencode")); + assert.ok(ids.includes("duckduckgo-web")); + assert.ok(ids.includes("felo-web")); + assert.ok(ids.includes("theoldllm")); + assert.ok(ids.includes("chipotle")); + assert.ok(ids.includes("mimocode")); + assert.ok(ids.includes("aihorde")); + assert.ok(!ids.includes("devin-cli-agentic")); + assert.ok(!ids.includes("auggie")); + assert.ok(!ids.includes("veoaifree-web")); + assert.ok(providers.every((provider) => provider.caution.length > 0)); + assert.ok(providers.every((provider) => provider.website.startsWith("https://"))); +}); + +test("already configured providers are removed without changing registry candidates", () => { + const all = getEligibleFreeOnboardingProviders(); + const available = selectUnconfiguredFreeOnboardingProviders(all, [ + { provider: "opencode" }, + { provider: "openai" }, + ]); + + assert.ok(!available.some((provider) => provider.id === "opencode")); + assert.equal( + all.some((provider) => provider.id === "opencode"), + true + ); +}); diff --git a/tests/unit/free-provider-onboarding-setup.test.ts b/tests/unit/free-provider-onboarding-setup.test.ts new file mode 100644 index 0000000000..3afdbe8fd7 --- /dev/null +++ b/tests/unit/free-provider-onboarding-setup.test.ts @@ -0,0 +1,89 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + getEligibleFreeOnboardingProviders, + setupFreeProviderConnections, +} from "../../src/lib/providers/freeOnboarding.ts"; + +test("batch setup creates missing providers, skips existing ones, and is retry-safe", async () => { + const existing = [{ provider: "opencode", name: "My customized OpenCode" }]; + const created: Array<{ provider: string; name: string }> = []; + const candidates = getEligibleFreeOnboardingProviders(); + const requestedIds = ["opencode", "mimocode"]; + + const first = await setupFreeProviderConnections({ + requestedIds, + candidates, + listExisting: async () => [...existing, ...created], + create: async (input) => { + created.push({ provider: input.provider, name: input.name }); + return { id: `created-${input.provider}` }; + }, + }); + const second = await setupFreeProviderConnections({ + requestedIds, + candidates, + listExisting: async () => [...existing, ...created], + create: async (input) => { + created.push({ provider: input.provider, name: input.name }); + return { id: `created-${input.provider}` }; + }, + }); + + assert.deepEqual(first.results, [ + { providerId: "opencode", status: "skipped", reason: "already-configured" }, + { providerId: "mimocode", status: "created", connectionId: "created-mimocode" }, + ]); + assert.deepEqual(second.results, [ + { providerId: "opencode", status: "skipped", reason: "already-configured" }, + { providerId: "mimocode", status: "skipped", reason: "already-configured" }, + ]); + assert.deepEqual(existing, [{ provider: "opencode", name: "My customized OpenCode" }]); + assert.deepEqual(created, [{ provider: "mimocode", name: "MiMoCode (Free)" }]); +}); + +test("batch setup rejects unknown or ineligible IDs before creating anything", async () => { + let createCalls = 0; + + await assert.rejects( + setupFreeProviderConnections({ + requestedIds: ["openai", "missing-provider"], + candidates: getEligibleFreeOnboardingProviders(), + listExisting: async () => [], + create: async () => { + createCalls += 1; + return { id: "unexpected" }; + }, + }), + /Ineligible free provider IDs: missing-provider, openai/ + ); + assert.equal(createCalls, 0); +}); + +test("partial failures are reported per provider and can be retried", async () => { + const created = new Set(); + let mimocodeAttempts = 0; + const input = { + requestedIds: ["opencode", "mimocode"], + candidates: getEligibleFreeOnboardingProviders(), + listExisting: async () => [...created].map((provider) => ({ provider })), + create: async ({ provider }: { provider: string }) => { + if (provider === "mimocode" && mimocodeAttempts++ === 0) throw new Error("upstream detail"); + created.add(provider); + return { id: `created-${provider}` }; + }, + }; + + const first = await setupFreeProviderConnections(input); + const retry = await setupFreeProviderConnections(input); + + assert.deepEqual(first.results, [ + { providerId: "opencode", status: "created", connectionId: "created-opencode" }, + { providerId: "mimocode", status: "failed", reason: "Failed to create provider" }, + ]); + assert.deepEqual(retry.results, [ + { providerId: "opencode", status: "skipped", reason: "already-configured" }, + { providerId: "mimocode", status: "created", connectionId: "created-mimocode" }, + ]); +}); diff --git a/tests/unit/ui/free-provider-onboarding.test.tsx b/tests/unit/ui/free-provider-onboarding.test.tsx new file mode 100644 index 0000000000..1d43eaa68f --- /dev/null +++ b/tests/unit/ui/free-provider-onboarding.test.tsx @@ -0,0 +1,128 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const translate = (key: string) => key; + +vi.mock("next-intl", () => ({ + useTranslations: () => translate, +})); + +describe("FreeProviderOnboardingCard", () => { + let container: HTMLDivElement; + let root: Root; + + beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ + providers: [ + { + id: "opencode", + name: "OpenCode Free", + website: "https://opencode.ai", + caution: "Public endpoint. Rate limits apply.", + defaultModel: "big-pickle", + }, + ], + }), + }) + ); + ( + globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } + ).IS_REACT_ACT_ENVIRONMENT = true; + }); + + afterEach(async () => { + await act(async () => root.unmount()); + container.remove(); + vi.unstubAllGlobals(); + vi.clearAllMocks(); + }); + + it("shows caution metadata and requires explicit confirmation before setup", async () => { + const { FreeProviderOnboardingCard } = + await import("../../../src/app/(dashboard)/dashboard/onboarding/steps/FreeProviderOnboardingCard"); + + await act(async () => root.render()); + await act(async () => Promise.resolve()); + + expect(container.textContent).toContain("OpenCode Free"); + expect(container.textContent).toContain("Public endpoint. Rate limits apply."); + expect(container.querySelector('a[href="https://opencode.ai"]')).not.toBeNull(); + + const setupButton = container.querySelector( + '[data-testid="setup-free-providers"]' + ); + expect(setupButton?.disabled).toBe(true); + + const confirmation = container.querySelector( + '[data-testid="free-provider-confirmation"]' + ); + await act(async () => confirmation?.click()); + expect(setupButton?.disabled).toBe(false); + }); + + it("posts selected IDs only after confirmation and renders partial results for retry", async () => { + const fetchMock = vi.mocked(fetch); + fetchMock + .mockResolvedValueOnce({ + ok: true, + json: async () => ({ + providers: [ + { + id: "opencode", + name: "OpenCode Free", + website: "https://opencode.ai", + caution: "Rate limits apply.", + }, + { + id: "mimocode", + name: "MiMoCode", + website: "https://mimo.mi.com", + caution: "Bootstrap authentication.", + }, + ], + }), + } as Response) + .mockResolvedValueOnce({ + ok: true, + json: async () => ({ + results: [ + { providerId: "opencode", status: "created", connectionId: "one" }, + { providerId: "mimocode", status: "failed", reason: "Failed to create provider" }, + ], + }), + } as Response); + const { FreeProviderOnboardingCard } = + await import("../../../src/app/(dashboard)/dashboard/onboarding/steps/FreeProviderOnboardingCard"); + + await act(async () => root.render()); + await act(async () => Promise.resolve()); + await act(async () => + container + .querySelector('[data-testid="free-provider-confirmation"]') + ?.click() + ); + await act(async () => + container.querySelector('[data-testid="setup-free-providers"]')?.click() + ); + + expect(fetchMock).toHaveBeenLastCalledWith( + "/api/providers/free-onboarding", + expect.objectContaining({ + method: "POST", + body: JSON.stringify({ providerIds: ["mimocode", "opencode"], confirmed: true }), + }) + ); + expect(container.textContent).toContain("result.created"); + expect(container.textContent).toContain("result.failed"); + expect(container.textContent).toContain("retryFailed"); + }); +});