7ff0ccb160cdcc9bcb227ec34596755c320a2745
Multi-provider LLM picker for Crema apps. Sits on top of @crema/llm-ui and adds: - A named provider catalog (OpenAI, Anthropic, DeepSeek, Qwen, LM Studio) with default base URLs and suggested models. - buildAdapter(): async factory that resolves the API key from a caller-injected resolveSecret() (direct mode) or assembles an ArcadiaProxyAdapter pointing at /api/v1/ai/llm/chat (proxy mode). - LLMProvidersSettingsCard: provider/model picker, vault-key reference field with "looks like an API key" warning, direct/proxy toggle, context/response budgets, system prompt, optional Test connection. - Persisted settings store at crema.llm-providers.settings, reactive via useSyncExternalStore. The lib is provider-agnostic about how keys are stored; the consuming app injects a resolveSecret() that hits whatever vault it owns. arcadia-admin and vibespace both wire this to /api/v1/secrets/:name. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
lib-llm-providers-ui
Multi-provider LLM picker + settings card for Crema apps. Sits on top of @crema/llm-ui and adds:
- A named provider catalog (OpenAI, Anthropic, DeepSeek, Qwen, LM Studio) with sensible default base URLs and model lists.
- An arcadia-proxy adapter that routes chat completions through
POST /api/v1/ai/llm/chat, so API keys never leave the server. - A direct-mode adapter that resolves the API key from arcadia's Secrets Manager (
GET /api/v1/secrets/:name) and calls the provider directly from the browser. - A settings card (
LLMProvidersSettingsCard) that ties it all together — provider, model, base-URL override, arcadia-secret name, transport toggle, context/response budgets, system prompt. - A persistent settings store with
useSettings()/saveSettings()/resetSettings()and acrema:llm-providers-changeevent for reactive updates across tabs.
Quick start
import {
LLMProvidersSettingsCard,
buildAdapter,
useSettings,
} from "@crema/llm-providers-ui"
import { LLMProvider, useChat } from "@crema/llm-ui"
import { useArcadiaClient } from "@crema/arcadia-client"
import { useEffect, useState } from "react"
function App() {
const arcadia = useArcadiaClient()
const settings = useSettings()
const [adapter, setAdapter] = useState(null)
useEffect(() => {
buildAdapter({
settings,
// Direct mode — fetch the API key from arcadia's vault.
resolveSecret: async (name) => {
const res = await arcadia.GET<{ data: { value: string } }>(`/api/v1/secrets/${name}`)
return res.data.value
},
// Or proxy mode (when the backend endpoint exists):
arcadiaBaseURL: import.meta.env.VITE_ARCADIA_URL,
arcadiaAuthToken: sessionStorage.getItem("arcadia_access_token") ?? undefined,
arcadiaTenantId: import.meta.env.VITE_ARCADIA_TENANT,
}).then(setAdapter)
}, [settings, arcadia])
if (!adapter) return null
return (
<LLMProvider adapter={adapter} model={settings.model}>
<Chat />
</LLMProvider>
)
}
For the settings page:
<LLMProvidersSettingsCard
hideTransportToggle={!proxyAvailable}
onTest={async (s) => {
// Wire to a one-off completion through buildAdapter.
// Return { ok: true, message: "Connected." } or { ok: false, message: "..." }.
}}
/>
Direct vs proxy
| Mode | Where the key lives at call time | When to use |
|---|---|---|
direct |
Briefly in the browser (fetched from arcadia per-call) | When the backend proxy isn't deployed yet, or for local development. |
proxy |
Server-side only (read by arcadia, never sent to the client) | Production. Requires POST /api/v1/ai/llm/chat on arcadia — see LLM_PROXY_CONTRACT.md in the consuming app. |
The settings card lets the user toggle between them. buildAdapter() returns the right adapter automatically.
Provider catalog
Edit PROVIDERS in src/index.tsx to add more. Each entry is:
{
id: "deepseek",
label: "DeepSeek",
baseURL: "https://api.deepseek.com/v1",
transport: "openai-compatible", // or "anthropic"
requiresKey: true,
defaultModels: ["deepseek-chat", "deepseek-reasoner"],
hint: "OpenAI-compatible. Create a key at platform.deepseek.com.",
}
Adding a provider with a different shape (e.g. Google AI) means picking the closest existing transport, or extending @crema/llm-ui with a new adapter and adding a transport string here.
Wiring into a Crema app
- Clone this lib as a sibling.
- Add
@source "../../lib-llm-providers-ui/src";under/* CREMA:SOURCES */inapp/app.css. - Add the path alias under
// CREMA:PATHSintsconfig.json:"@crema/llm-providers-ui": ["../lib-llm-providers-ui/src/index.tsx"], "@crema/llm-providers-ui/*": ["../lib-llm-providers-ui/src/*"], crema adddoes this automatically once the manifest knows about the lib.
Description
Multi-provider LLM picker (OpenAI, Anthropic, DeepSeek, Qwen, LM Studio) on top of @crema/llm-ui, with arcadia-vault key resolution and a settings card.
Languages
TypeScript
100%