diff --git a/app/src/components/settings/panels/AIPanel.tsx b/app/src/components/settings/panels/AIPanel.tsx index 49b340b95..05fb6d91a 100644 --- a/app/src/components/settings/panels/AIPanel.tsx +++ b/app/src/components/settings/panels/AIPanel.tsx @@ -112,6 +112,10 @@ const BUILTIN_PROVIDER_META: Record = { label: 'OpenRouter', tone: 'bg-slate-100 dark:bg-slate-500/15 ring-slate-300 text-slate-900 dark:text-slate-100', }, + orcarouter: { + label: 'OrcaRouter', + tone: 'bg-sky-50 dark:bg-sky-500/10 ring-sky-200 text-sky-900 dark:text-sky-100', + }, custom: { label: 'Custom', tone: 'bg-stone-100 dark:bg-neutral-800 ring-stone-300 text-stone-900 dark:text-neutral-100', @@ -548,7 +552,9 @@ const ProviderKeyDialog = ({ ? 'sk-ant-...' : slug === 'openrouter' ? 'sk-or-...' - : 'your-api-key'; + : slug === 'orcarouter' + ? 'sk-orca-...' + : 'your-api-key'; const fieldLabel = isLocalRuntime ? 'Endpoint URL' : t('settings.ai.apiKeyFieldLabel'); const helper = isLocalRuntime @@ -2063,46 +2069,50 @@ const AIPanel = ({ embedded = false }: AIPanelProps = {}) => { )}
- {/* Built-in cloud providers — openai/anthropic/openrouter/custom */} - {(['openai', 'anthropic', 'openrouter', 'custom'] as const).map(slug => { - const meta = BUILTIN_PROVIDER_META[slug]; - const label = meta?.label ?? slug; - const existing = draft.cloudProviders.find(cp => cp.slug === slug); - const enabled = !!existing; - return ( - { - if (enabled && existing) { - // Toggle OFF: remove the provider + scrub any - // routing entries that pin to it. - const remaining = draft.cloudProviders.filter(cp => cp.id !== existing.id); - const nextRouting = Object.fromEntries( - Object.entries(draft.routing).map(([wid, ref]) => [ - wid, - ref.kind === 'cloud' && ref.providerSlug === existing.slug - ? ({ kind: 'openhuman' } as const) - : ref, - ]) - ) as typeof draft.routing; - setDraft({ ...draft, cloudProviders: remaining, routing: nextRouting }); - } else if (slug === 'custom') { - // Custom providers need slug + endpoint + label, not - // just an API key — defer to the full editor modal. - setEditing('new'); - } else { - // Toggle ON: open the API-key popup. The chip - // only flips after the dialog saves. - setKeyDialogFor(slug); - } - }} - /> - ); - })} + {/* Built-in cloud providers — openai/anthropic/openrouter/orcarouter/custom */} + {(['openai', 'anthropic', 'openrouter', 'orcarouter', 'custom'] as const).map( + slug => { + const meta = BUILTIN_PROVIDER_META[slug]; + const label = meta?.label ?? slug; + const existing = draft.cloudProviders.find(cp => cp.slug === slug); + const enabled = !!existing; + return ( + { + if (enabled && existing) { + // Toggle OFF: remove the provider + scrub any + // routing entries that pin to it. + const remaining = draft.cloudProviders.filter( + cp => cp.id !== existing.id + ); + const nextRouting = Object.fromEntries( + Object.entries(draft.routing).map(([wid, ref]) => [ + wid, + ref.kind === 'cloud' && ref.providerSlug === existing.slug + ? ({ kind: 'openhuman' } as const) + : ref, + ]) + ) as typeof draft.routing; + setDraft({ ...draft, cloudProviders: remaining, routing: nextRouting }); + } else if (slug === 'custom') { + // Custom providers need slug + endpoint + label, not + // just an API key — defer to the full editor modal. + setEditing('new'); + } else { + // Toggle ON: open the API-key popup. The chip + // only flips after the dialog saves. + setKeyDialogFor(slug); + } + }} + /> + ); + } + )} {/* LM Studio + Ollama — local runtimes stored with a slug of "lmstudio" / "ollama" so they're distinct from generic custom. */} @@ -2508,7 +2518,7 @@ const CloudProviderEditor = ({ const { t } = useT(); const defaultSlug: string = initial?.slug ?? - (['openai', 'anthropic', 'openrouter', 'custom'] as const).find( + (['openai', 'anthropic', 'openrouter', 'orcarouter', 'custom'] as const).find( s => !existingSlugs.includes(s) ) ?? 'custom'; @@ -2554,7 +2564,7 @@ const CloudProviderEditor = ({ }} disabled={!!initial} className="mt-1 w-full rounded-lg border border-stone-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 px-3 py-2 text-sm text-stone-900 dark:text-neutral-100 disabled:opacity-60 focus:border-primary-400 focus:outline-none focus:ring-1 focus:ring-primary-200"> - {(['openai', 'anthropic', 'openrouter', 'custom'] as const) + {(['openai', 'anthropic', 'openrouter', 'orcarouter', 'custom'] as const) .filter(s => s === slug || !existingSlugs.includes(s)) .map(s => (