SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
6.6 KB

# 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<T>(url) (SWR) and api<T>(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 <div className="relative flex min-w-0 flex-1 flex-col">. A page should typically be <main className="min-h-0 flex-1 overflow-y-auto scrollbar-thin"><div className="mx-auto w-full max-w-5xl px-4 py-6 sm:px-6 lg:px-8">…</div></main>.
  • 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.