# PolyLLM — UI brief for contributors (humans and agents) PolyLLM (www.polyllm.io) is a **universal control center for AI models**: users bring their own API keys (OpenAI, Anthropic, Gemini, xAI), discover current models, configure them precisely, chat with real streaming, compare models in the Arena and track usage/costs. Tagline: **"Your models. Your keys. One workspace."** ## Stack & conventions - Next.js 16 App Router, React 19, TypeScript strict, Tailwind v4 (tokens in `src/app/globals.css`), shadcn-style primitives in `src/components/ui/*` (button, input/textarea/label/field, badge, dialog, dropdown-menu, popover, select, slider, switch, tabs, tooltip, toast (`toast.success/error/info`), misc: Card/CardHeader/CardBody, Stat, PageHeader, EmptyState, Skeleton, CopyButton, Kbd, Spinner, Divider). Icons: `lucide-react`. Motion: `motion/react` (sparingly). - Client data: `useApi(url)` (SWR) and `api(url, { method, json })` from `src/lib/client/api.ts`; `streamEvents(url, body, onEvent, signal)` for SSE. Types: `src/lib/client/types.ts`. Provider metadata: `src/lib/client/providers.ts` (`PROVIDERS`, `PROVIDER_ORDER`, `providerName`). Brand: `src/components/brand/logo.tsx` (`Logo`, `LogoMark`), `src/components/brand/provider-icon.tsx` (`ProviderIcon`). - App state: `useApp()` from `src/components/app/store.tsx` → `{ user, preferences, updatePreferences, models, modelsByKey, favorites, recents, connectedProviders, connections, folders, toggleFavorite, refreshModels, refreshConnections, selectedModelKey, setSelectedModelKey, setPaletteOpen }`. Everything under `/app/*` is wrapped by the shell (sidebar + command palette) in `src/app/app/layout.tsx`; pages render inside `
`. A page should typically be `
…
`. - Auth client: `authClient`, `signIn`, `signUp`, `signOut`, `useSession` from `src/lib/auth-client.ts` (Better Auth 1.7). Server: `requireUser()` / `getSession()` in `src/lib/session.ts`. - Formatting helpers: `formatUsd`, `formatTokens`, `formatMs`, `formatRelative`, `formatNumber`, `cn` in `src/lib/utils.ts`. - Never render key material. Connections only expose `keyHint` like `sk-••••••••9A2K`. - React Compiler lint rules apply: no setState synchronously in effects without the eslint-disable, no components defined inside render, derive state with useMemo. Run `pnpm lint` and `pnpm typecheck`. ## Design language (must feel premium, unique, not a template) - Minimal, dense-when-useful, excellent typography (Geist Sans/Mono via `font-sans`/`font-mono`), subtle borders (`border-border`), surfaces `bg-bg / bg-bg-subtle / bg-bg-muted / bg-bg-elevated`, text `text-fg / text-fg-muted / text-fg-subtle`, accent `text-accent / bg-accent / bg-accent-soft`, semantic `success/warning/danger/info` (+ `-soft`). Radii `rounded-md/lg/xl`. Provider colors: `text-openai`, `text-anthropic`, `text-gemini`, `text-xai` or `style={{color: PROVIDERS[p].colorVar}}`. - Dark and light must both look great. No cheap gradients, no childish AI imagery, no huge empty whitespace, no boxed-card overload. A single restrained gradient (`.text-gradient`) is allowed for hero words. Use `.dot-grid` / `.glass` utilities sparingly. - Mobile first: no horizontal overflow, 16px inputs on mobile (`text-[15px] sm:text-sm` is already in Input), safe-area aware. - Keyboard-first: ⌘K palette, ⌘N new chat, ⌘Enter send, Esc stop, ⌘/ model selector, ⌘B sidebar. - Accessibility: semantic HTML, labels, visible focus, aria on icon buttons, reduced motion respected. ## API routes (all JSON unless noted; all under a verified session) - `GET /api/models` → `{ models: PolyModel[], favorites: string[], recents: string[], connectedProviders }`; `POST /api/models {action:"toggle-favorite", modelKey}`; `POST /api/models/sync {provider?}` → `{results: SyncResult[]}`. - `GET /api/providers` → `{connections: PublicConnection[]}`; `PUT /api/providers {provider, apiKey}` → `{ok, error?, modelsAvailable?, connections}`; `POST /api/providers {action:"validate", provider}`; `DELETE /api/providers?provider=`. - `GET /api/conversations?q&provider&model&folder&archived=1&pinned=1&since&until&limit&cursor`; `POST /api/conversations`; `GET|PATCH|DELETE /api/conversations/:id`; `POST /api/conversations/:id/actions {action: duplicate|branch|export|share|unshare|share-status|delete-message}`. - `POST /api/chat` (SSE) body `ChatRequestInput` → events `ChatStreamEvent` (see `src/lib/client/types.ts`). - `GET /api/folders`, `POST {name,color}`, `PATCH {id,…}`, `DELETE ?id=`. - `POST /api/attachments` (multipart `file`) → `{attachment}`; `GET /api/attachments?id=` streams the file. - `GET /api/usage?range=today|7d|30d|all` → `{ totals, series[{bucket,requests,inputTokens,outputTokens,costUsd}], byProvider[], byModel[], recent[] }`. - `GET /api/presets` → `{modelPresets, promptPresets}`; `POST /api/presets?kind=model|prompt`; `PATCH ?kind&id`; `DELETE ?kind&id`. - `GET /api/preferences` / `PATCH /api/preferences` (theme, defaultModelKey, defaultSystemPrompt, enterToSend, streaming, codeWrap, showReasoning, showCosts, autoTitle, onboardingCompleted). - `GET /api/arena` → `{sessions}`; `POST /api/arena {prompt, systemPrompt?, modelKeys[1..4], settings?, attachmentIds?}` → `{session}`; `POST /api/arena/stream {sessionId, modelKey}` (SSE: meta → text-delta/reasoning-delta/citation → done|error); `PATCH /api/arena {responseId, ratings}`. - `GET /api/account` → `{audit}`; `GET /api/account?export=1` downloads the full account export. - `GET /api/search?q=`. - Admin: `GET /api/admin/providers`, `POST /api/admin/providers {action:"sync", provider?}` (owner only, `user.isAdmin`). - Better Auth: `/api/auth/*` (client SDK). Email verification is mandatory: unverified users are redirected to `/verify-email`. ## Model capability sheet (drives every configuration UI) `PolyModel.capabilities` {text, vision, audioInput, audioOutput, imageGeneration, video, reasoning, tools, structuredOutput, streaming, files, webSearch}; `parameters` {temperature, topP, topK, maxTokens, reasoningEffort, reasoningEffortLevels[], thinkingBudget, thinkingBudgetRange, stop, seed, frequencyPenalty, presencePenalty, verbosity, temperatureRange}; `limits` {contextTokens, maxOutputTokens}; `pricing` {inputPerMillion, cachedInputPerMillion, outputPerMillion}; `status` active|preview|deprecated|unknown; `metadata.shutdownDate`, `metadata.aliases`, etc. **Never show a control the model does not support.**