# 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 a `crema:llm-providers-change` event for reactive updates across tabs. ## Quick start ```tsx import { LLMProvidersSettingsCard, buildAdapter, useSettings, } from "@crema/llm-providers-ui" import { LLMProvider, useChat } from "@crema/llm-ui" import { useArcadiaClient } from "@crema/arcadia-core-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 ( ) } ``` For the settings page: ```tsx { // 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: ```ts { 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 1. Clone this lib as a sibling. 2. Add `@source "../../lib-llm-providers-ui/src";` under `/* CREMA:SOURCES */` in `app/app.css`. 3. Add the path alias under `// CREMA:PATHS` in `tsconfig.json`: ```json "@crema/llm-providers-ui": ["../lib-llm-providers-ui/src/index.tsx"], "@crema/llm-providers-ui/*": ["../lib-llm-providers-ui/src/*"], ``` 4. `crema add` does this automatically once the manifest knows about the lib.