Foundation for 1.0 upgrade: design tokens, mobile shell (bottom nav, drawer, keyboard inset), sheet primitives, brand (Prism P, favicon, OG, PWA manifest), workspace schema, router & token estimators, upgrade plan
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
41 changed files +6,051 −204
added
docs/UPGRADE-PLAN.md
+159 −0
@@ -0,0 +1,159 @@ | ||
| 1 | +# PolyLLM 1.0 — "AI operating system" upgrade (2026-09-11) | |
| 2 | + | |
| 3 | +Massive product/UX/UI/mobile-first upgrade of www.polyllm.io. Goal: a premium, app-like multi-model workspace | |
| 4 | +("One interface. Every model.") that feels native on iPhone/Android and excellent on desktop, without breaking any | |
| 5 | +existing backend behaviour (auth, encrypted keys, chat history, Arena, presets, usage, exports, folders, attachments). | |
| 6 | + | |
| 7 | +This document is the shared contract between the parallel workstreams. Read it fully before touching code. | |
| 8 | + | |
| 9 | +## Ground rules (all workstreams) | |
| 10 | + | |
| 11 | +1. **Do not break existing functionality.** Reuse services in `src/lib/*`; extend, don't rewrite. Provider quirks stay in | |
| 12 | + `src/lib/ai/providers/*`; components only see `PolyModel` capabilities (see `CLAUDE.md`). | |
| 13 | +2. **Ownership.** Edit only the files/dirs listed for your workstream (plus new files you create there). If you need a change | |
| 14 | + in someone else's file, add a small **adapter in your own area** and leave a `// TODO(integration: <area>)` comment, or | |
| 15 | + put the request in `docs/upgrade-notes/<your-area>.md`. Exception: `src/lib/client/types.ts` — append-only. | |
| 16 | +3. **Schema.** Tables for the upgrade live in `src/db/schema-workspace.ts` (already migrated: `drizzle/0001_workspace_upgrade.sql`). | |
| 17 | + If you need more columns/tables: append to `schema-workspace.ts` (or create `src/db/schema-<area>.ts`, re-export it in | |
| 18 | + `src/db/index.ts`), then run `pnpm db:generate --name <area>` **and** `pnpm db:migrate` locally. Never edit existing | |
| 19 | + migration files. Never drop/rename columns. | |
| 20 | +4. **No dev server in this phase.** Multiple `next dev` in the same tree conflict on `.next`. Validate with | |
| 21 | + `pnpm typecheck`, `pnpm lint`, `pnpm test` (vitest) and, for server logic, small vitest units in `tests/unit`. Visual QA | |
| 22 | + happens in the integration phase against a single dev server. | |
| 23 | +5. **No fake functionality.** Every button works or is explicitly labelled "Coming soon" (use `<ComingSoon />` badge from | |
| 24 | + `components/ui/misc` — add it if missing). No placeholder handlers. | |
| 25 | +6. **Mobile first.** Design for 375 / 390 / 393 / 430 px widths first, then tablet, then desktop. Rules: | |
| 26 | + - Phones: bottom sheets instead of centered modals (`ResponsiveDialog`, `BottomSheet`, `ActionSheet` in `components/ui/sheet.tsx`), | |
| 27 | + long-press for message/row actions (`useLongPress`), swipe carousels (`.snap-row` + `useSnapCarousel`), 44 px touch | |
| 28 | + targets (`.tap` helper or explicit min-h), 16 px inputs, `100dvh`/`.h-app`, safe areas (`pb-safe`, `pt-safe`, `--sab`). | |
| 29 | + - Never `window.prompt/confirm/alert` — use `PromptDialog` / `ConfirmDialog` (`components/common`). | |
| 30 | + - No desktop tables overflowing on phones: use stacked rows/cards below `md`, tables from `md` up. | |
| 31 | + - No horizontal overflow, no clipped text, no fixed element blocking content. Respect `prefers-reduced-motion`. | |
| 32 | + - Hover-only controls must also be reachable on touch (`.hover-reveal` shows them on coarse pointers). | |
| 33 | +7. **Design language** (`src/app/globals.css` tokens): neutral warm graphite palette, iris accent, fewer borders, spatial | |
| 34 | + grouping (`.panel`, hairlines) over boxed cards, restrained 150–300 ms motion, `text-balance` headlines, tabular numbers | |
| 35 | + for metrics. Use `Segmented`/`ChipRow` (`components/ui/segmented.tsx`) for filters. Icons: lucide only. | |
| 36 | +8. **Performance.** No new heavy dependencies without a note in your area doc (allowed if < ~40 KB gz and clearly justified — | |
| 37 | + e.g. `katex` for LaTeX). Virtualize long lists (`@tanstack/react-virtual` is installed). Memoize streaming renders. | |
| 38 | +9. **Accessibility.** Labels/aria on icon buttons, focus-visible, keyboard nav for lists/palettes, semantic elements. | |
| 39 | +10. **Commit nothing.** The integrator commits. Leave the tree compiling (`pnpm typecheck && pnpm lint` green) when you finish. | |
| 40 | +11. **Final report.** End with: files created/changed, new API routes (method, path, body → response), anything left as | |
| 41 | + "Coming soon", and QA notes (what to check visually) in `docs/upgrade-notes/<area>.md`. | |
| 42 | + | |
| 43 | +## Shared primitives (already implemented — use them) | |
| 44 | + | |
| 45 | +| What | Where | Notes | | |
| 46 | +| --- | --- | --- | | |
| 47 | +| Tokens, utilities | `src/app/globals.css` | `.h-app`, `.pb-nav`, `.pb-safe`, `.tap`, `.hover-reveal`, `.snap-row`, `.panel`, `.glass-strong`, `.mask-x`, `.marquee`, `coarse:`/`fine:` variants | | |
| 48 | +| Hooks | `src/lib/client/hooks.ts` | `useIsMobile`, `useIsTablet`, `useIsCoarsePointer`, `useMounted`, `useKeyboardInset`, `useLongPress`, `useSwipe`, `useLocalStorage`, `useDebounced`, `useCopy`, `useSnapCarousel`, `useStandalone` | | |
| 49 | +| Sheets | `src/components/ui/sheet.tsx` | `BottomSheet` (handle, snap `content|half|full`, swipe-dismiss), `ResponsiveDialog` (sheet on phone, dialog/panel on desktop), `ActionSheet` (iOS-style list) | | |
| 50 | +| Segmented / chips | `src/components/ui/segmented.tsx` | `Segmented`, `ChipRow` | | |
| 51 | +| Dialog replacements | `src/components/common/prompt-dialog.tsx`, `confirm-dialog.tsx` | | | |
| 52 | +| Shell | `src/components/app/shell.tsx`, `bottom-nav.tsx`, `sidebar.tsx`, `store.tsx` | Mobile bottom nav (Chat/Arena/Models/Usage/Account), drawer with edge-swipe, keyboard inset. Store adds `labels`, `setLabel`, `activeProjectId`, `searchOpen`, `AUTO_MODEL_KEY`. | | |
| 53 | +| Token/cost estimator | `src/lib/client/tokens.ts` | `estimateTextTokens`, `estimateAttachmentTokens`, `estimateContext`, `estimateCost`, `formatEstimate`, `COST_CONFIRM_THRESHOLD_USD` | | |
| 54 | +| Smart router | `src/lib/client/router.ts` | `analyzePrompt`, `routeModels`, `explainRoute`, `ROUTER_MODES` (pure, transparent) | | |
| 55 | +| Brand | `src/components/brand/logo.tsx`, `public/icon.svg`, `public/og.png`, `scripts/brand-assets.sh`, `src/app/manifest.ts` | "Prism P" mark; `Wordmark` | | |
| 56 | +| Schema | `src/db/schema-workspace.ts` | `projects`, `project_files`, `prompts`, `user_model_labels`, `custom_endpoints`, `arena_votes`, `shared_arena_sessions`; `conversations.project_id` | | |
| 57 | +| Cross-area stubs | `components/projects/sidebar-section.tsx`, `components/library/file-library-picker.tsx` | Baselines; the Projects workstream owns/finishes them. Chat imports `FileLibraryPicker` from that path. | | |
| 58 | + | |
| 59 | +## Workstreams & ownership | |
| 60 | + | |
| 61 | +### A — Chat & composer (`components/chat/*` except `model-selector.tsx`, `model-config.tsx`, `model-badges.tsx`; `components/markdown/*`; `app/app/chat/*`; `lib/chat/*`; `app/api/chat/*`; `app/api/attachments/*`) | |
| 62 | +Mobile composer (+ sheet: upload image/document, camera, paste, from library, tools, web search, structured output, system | |
| 63 | +prompt, temporary chat), auto-grow 1→6 lines, voice dictation (Web Speech API where available, hidden otherwise), active model | |
| 64 | +pill above composer (opens the model picker via `ModelSelector` — keep its public props), attachments (drag/drop, paste, | |
| 65 | +camera, chips, multiple), context indicator (`43K / 200K`, warnings at 80/95 %), cost estimate + confirm above | |
| 66 | +`COST_CONFIRM_THRESHOLD_USD` with one-tap model alternatives, Smart Router AUTO mode (`selectedModelKey === AUTO_MODEL_KEY` → | |
| 67 | +recommendation card before sending: Use / Choose another / Always auto-route), temporary chat (server: `ephemeral: true` in | |
| 68 | +`chatRequestSchema` → stream without persisting; badge "Not stored in history"), message toolbar (desktop hover / mobile | |
| 69 | +long-press ActionSheet: copy, retry, edit, branch, compare, change model from here, save as prompt, export, rate, more), | |
| 70 | +expandable message metadata (model, provider, tokens, cost, TTFT, tok/s, reasoning level), deprecated-model warning with | |
| 71 | +replacement suggestion, humanized provider errors with Retry / Switch model / Details, streaming performance (memoized | |
| 72 | +blocks, stable markdown, code block Copy/Download/Wrap/Expand, LaTeX via `katex` + `rehype-katex` allowed), inline | |
| 73 | +**Compare with…** (2–4 models; desktop side-by-side, mobile swipeable panels with sticky tabs; reuse `/api/arena` + | |
| 74 | +`/api/arena/stream` to run, store as Arena session, "Continue with this model"), empty state with quick actions | |
| 75 | +(Write / Code / Research / Analyze a file / Compare models), project instructions applied when `activeProjectId` is set | |
| 76 | +(fetch `/api/projects/:id` — Projects workstream provides it; degrade gracefully if 404). | |
| 77 | + | |
| 78 | +### B — Models (`components/chat/model-selector.tsx`, `model-config.tsx`, `model-badges.tsx`; `app/app/models/*`; new `app/(marketing)/models/*`, `app/(marketing)/compare/*`; `components/models/*`; `lib/ai/registry/*` (additive); `app/api/models/*` (additive); `components/presets/*`, `app/app/presets/*`; `lib/models/*`) | |
| 79 | +Model picker redesign (bottom sheet on phones; desktop dialog), fast search with intent parsing ("cheap vision model", | |
| 80 | +"1M context", "under $1/M", "fastest gemini", "json schema"), sections (Favorites, Recent, Recommended, Fast, Best | |
| 81 | +reasoning, Cheapest, Largest context, Vision, Coding, Open source, New), compact metadata rows, star favorites, custom labels | |
| 82 | +(`store.setLabel`), AUTO entry (Smart Router) with mode selector, model badges (NEW/FAST/CHEAP/REASONING/VISION/CODING/LONG | |
| 83 | +CONTEXT), lifecycle badges (New/Active/Deprecated/Retiring/Unavailable), model profile sheet (all fields; show "—" for | |
| 84 | +unknown — never invent release dates or cutoffs), `/app/models` catalog (sortable, filters, mobile stacked rows, select → | |
| 85 | +Compare), public `/models` and `/compare/[slug]` (`gpt-5.5-vs-claude-sonnet-5`) pages with SEO metadata reading the registry | |
| 86 | +server-side (no auth), capability-aware settings grouped Generation / Reasoning / Output / Tools / Advanced with Reset and | |
| 87 | +Save preset (presets apply across compatible models). | |
| 88 | + | |
| 89 | +### C — Arena (`components/arena/*`; `app/app/arena/*`; `lib/arena/*`; `app/api/arena/*`; new `app/share/arena/*`) | |
| 90 | +Up to 4 models: desktop adaptive 2×2 / side-by-side, phone one panel at a time (`.snap-row`, sticky model tabs, swipe), | |
| 91 | +live metrics (status, TTFT, tok/s, in/out tokens, cost), votes (Best / Most accurate / Best writing / Best coding / Best value | |
| 92 | +/ Fastest + custom criteria) stored in `arena_votes`, Arena Winner summary with comparative metrics, Blind Arena (Model A/B/C | |
| 93 | +until vote; reveal), history, personal scoreboard `/app/arena/scoreboard` (win rate per model, filters Coding/Research/ | |
| 94 | +Writing/Reasoning/Cost efficiency), exports (Markdown/JSON incl. models, parameters, metrics, ratings), public share of an | |
| 95 | +Arena comparison (`shared_arena_sessions`, `/share/arena/[id]`, revoke). | |
| 96 | + | |
| 97 | +### D — Projects, prompt library, context library (`components/projects/*`, `components/library/*`, `components/prompts/*`; `app/app/projects/*`, `app/app/library/*`, `app/app/prompts/*`; `lib/projects/*`, `lib/library/*`, `lib/prompts/*`; `app/api/projects/*`, `app/api/library/*`, `app/api/prompts/*`) | |
| 98 | +Projects CRUD (`/api/projects`, `/api/projects/[id]` returning `{ project, conversations, files, prompts }`), project page | |
| 99 | +(instructions, preferred models, notes, files, conversations), `activeProjectId` selector, conversations filtered by project | |
| 100 | +(add `projectId` to `ListFilter`/PATCH in `lib/conversations/service.ts` — small additive change allowed), file library | |
| 101 | +(`/api/library/files` GET/POST/DELETE with `estimatedTokens`, `/api/library/files/attach` POST `{fileIds}` → `{attachments}` | |
| 102 | +copying into `message_attachments`), prompt library with `{{variables}}` (parse, defaults, fill-in sheet before insert into | |
| 103 | +composer via `?prompt=` / `router.push('/app/chat?promptId=…')` and a `usePromptInsert` event on `window`), folders/tags/ | |
| 104 | +favorites, keep existing `prompt_presets`/`model_presets` pages working (migrate the `/app/prompts` page to the new library | |
| 105 | +while still listing legacy presets). Finish the two stubs listed above. | |
| 106 | + | |
| 107 | +### E — Usage analytics, providers, custom endpoints (`components/usage/*`; `app/app/usage/*`; `lib/usage/*`; `app/api/usage/*`; `app/app/settings/*`; `components/providers/*`; `lib/providers/*`; `app/api/providers/*`; new `lib/ai/providers/custom/*`, `app/api/endpoints/*`) | |
| 108 | +Usage dashboard (KPIs: requests, tokens, cost, avg TTFT, tok/s, avg context, error rate; charts: cost/tokens/requests over | |
| 109 | +time, cost by provider/model, requests by model, latency by model; filters Today/7d/30d/90d/Custom + provider/model/project; | |
| 110 | +projections "Estimated monthly cost"; savings opportunities computed from real usage_records + pricing; mobile: stacked | |
| 111 | +KPI tiles, chart cards full-width, tables → rows). Follow `dataviz` skill guidance (palette from tokens, accessible). | |
| 112 | +Provider management redesign (status Connected/Not connected/Validation failed, last validated, models available, Test | |
| 113 | +connection with latency, key rotation, remove), extensible provider list. Custom OpenAI-compatible endpoints (Ollama, LM | |
| 114 | +Studio, vLLM, llama.cpp, MLX…): `custom_endpoints` table exists; implement adapter via `createOpenAICompatAdapter` with | |
| 115 | +per-endpoint baseURL/headers, discovery (`GET {baseUrl}/models`) or manual models, models keyed `custom/<endpointId>:<modelId>` | |
| 116 | +surfaced in `/api/models`, chat routing in `lib/chat/service.ts` resolution (coordinate via a `resolveCustomEndpoint()` | |
| 117 | +helper you export; document the one-line hook needed in the chat service in your notes if you can't wire it without | |
| 118 | +editing A's files — the integrator will wire it). Add `"custom"` to `PROVIDER_IDS` only if every switch over providers stays | |
| 119 | +exhaustive-safe (grep first). Settings pages mobile-first (full-screen sections, bottom sheets). | |
| 120 | + | |
| 121 | +### F — Marketing, onboarding, auth, PWA polish (`components/marketing/*`; `app/(marketing)/*` except `models`/`compare`; `app/(auth)/*`; `components/app/onboarding.tsx`; `app/app/onboarding/*`; `app/sitemap.ts`; `public/*` (additive)) | |
| 122 | +Homepage redesign: hero "One interface. / Every model." + sub-line (Bring your own keys. Compare models. Control every | |
| 123 | +parameter. Track every token.), CTAs "Start using PolyLLM" / "Try Arena", animated **real product UI** in the hero (no abstract | |
| 124 | +art), live model strip (public `GET /api/public/models` summary — create it; marquee with provider marks, context, price, | |
| 125 | +status), interactive demo switchable Chat/Arena/Models/Usage with simulated streaming and a phone-shaped mobile demo that | |
| 126 | +mirrors the real mobile UI, features, security page `/security` (Browser → PolyLLM encrypted server layer → Provider; key | |
| 127 | +lifecycle; AES-256-GCM, Argon2id, HttpOnly cookies, CSP, rate limits, audit logs — technically accurate only, verify in code), | |
| 128 | +FAQ, footer with **contact@spboucher.ai · Simon-Pierre Boucher · Hosted on MacLustr (www.maclustr.io)**, `/contact` optional. | |
| 129 | +Onboarding 6 steps (account → providers → first key → validate → favorite models → first prompt; skippable; progress; | |
| 130 | +swipeable on phone) as a full page `/app/onboarding` + first-run redirect logic in `components/app/onboarding.tsx` (keep the | |
| 131 | +existing `Onboarding` export working). Auth pages polish (mobile-first). Keep all metadata/OG (already set in `app/layout.tsx`). | |
| 132 | + | |
| 133 | +### G — Search, command palette, share/export (`components/app/command-palette.tsx`; new `components/search/*`; `app/api/search/*`; `lib/search/*`; `app/share/[id]/*`; `lib/conversations/service.ts` export/share functions (additive); `app/api/conversations/[id]/actions/route.ts` (additive)) | |
| 134 | +Full-text search API with filter syntax `model:claude project:research after:2026-08-01 before: provider: role:` over titles | |
| 135 | ++ message content (Postgres `ilike`/`to_tsvector` — add a GIN index via a new migration if you use tsvector), grouped results, | |
| 136 | +snippets with highlights; search UI: desktop inside ⌘K (search mode), phone full-screen sheet (`store.searchOpen`); universal | |
| 137 | +command palette (New chat, Search, Switch model, Open Arena/Usage/Providers/Project, Upload file, Toggle theme, New temporary | |
| 138 | +chat (`/app/chat?temporary=1`), Copy conversation URL, keyboard-first). Share: options Entire conversation / Selected messages | |
| 139 | +(`messageIds`) with privacy warning sheet, revoke, list of active links in Settings → Data; share page redesign (mobile-first, | |
| 140 | +brand). Export: Markdown, TXT, JSON, PDF (print-optimized route `/share/print/[id]` + `window.print()` is acceptable — label it | |
| 141 | +"PDF (print)"). | |
| 142 | + | |
| 143 | +## Navigation architecture | |
| 144 | + | |
| 145 | +- Desktop: sidebar (New chat, Search, Chat/Arena/Models/Prompts/Presets/Library/Usage, Projects, Pinned, Folders, Recent, | |
| 146 | + account menu). Collapsible ⌘B. | |
| 147 | +- Phone: bottom nav Chat / Arena / Models / Usage / Account; drawer (hamburger or edge-swipe) for conversations, projects, | |
| 148 | + search; page headers use a compact 48 px bar with a hamburger on the left (`setSidebarOpen(true)`). | |
| 149 | +- Every app page must reserve space for the bottom nav (the shell adds `padding-bottom`; pages use `h-full min-h-0 flex-col` | |
| 150 | + layouts and scroll inside, never the document). | |
| 151 | + | |
| 152 | +## Integration phase (integrator) | |
| 153 | + | |
| 154 | +1. Wire cross-area hooks (custom endpoint resolution in chat service, project instructions, prompt insert). | |
| 155 | +2. `pnpm typecheck && pnpm lint && pnpm test`, `pnpm build`. | |
| 156 | +3. Single dev server → Playwright QA at 375×812, 390×844, 393×852, 430×932 + desktop 1440 for every screen listed in the brief | |
| 157 | + (login, signup, onboarding, chat, composer+keyboard, attachments, model switcher, Arena + swipe, drawer, search, projects, | |
| 158 | + catalog, usage, providers, account, sheets). Fix overflow/clipping/tap targets. | |
| 159 | +4. Migration on prod via mld hook (`pnpm db:migrate`), deploy `mld stage . polyllm && mld deploy polyllm --node M3U96a`. | |
modified
drizzle.config.ts
+1 −1
@@ -2,7 +2,7 @@ import { defineConfig } from "drizzle-kit"; | ||
| 2 | 2 | |
| 3 | 3 | export default defineConfig({ |
| 4 | 4 | dialect: "postgresql", |
| 5 | − schema: "./src/db/schema.ts", | |
| 5 | + schema: ["./src/db/schema.ts", "./src/db/schema-*.ts"], | |
| 6 | 6 | out: "./drizzle", |
| 7 | 7 | dbCredentials: { url: process.env.DATABASE_URL ?? "postgres://localhost:5432/polyllm" }, |
| 8 | 8 | strict: true, |
added
drizzle/0001_workspace_upgrade.sql
+122 −0
@@ -0,0 +1,122 @@ | ||
| 1 | +CREATE TABLE "arena_votes" ( | |
| 2 | + "id" text PRIMARY KEY NOT NULL, | |
| 3 | + "user_id" text NOT NULL, | |
| 4 | + "session_id" text NOT NULL, | |
| 5 | + "response_id" text NOT NULL, | |
| 6 | + "model_key" text NOT NULL, | |
| 7 | + "criterion" text NOT NULL, | |
| 8 | + "category" text, | |
| 9 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 10 | +); | |
| 11 | +--> statement-breakpoint | |
| 12 | +CREATE TABLE "custom_endpoints" ( | |
| 13 | + "id" text PRIMARY KEY NOT NULL, | |
| 14 | + "user_id" text NOT NULL, | |
| 15 | + "name" text NOT NULL, | |
| 16 | + "base_url" text NOT NULL, | |
| 17 | + "encrypted_key" text, | |
| 18 | + "key_hint" text, | |
| 19 | + "encrypted_headers" text, | |
| 20 | + "models_path" text DEFAULT '/models' NOT NULL, | |
| 21 | + "manual_models" jsonb DEFAULT '[]'::jsonb NOT NULL, | |
| 22 | + "status" text DEFAULT 'unverified' NOT NULL, | |
| 23 | + "last_validated_at" timestamp with time zone, | |
| 24 | + "last_validation_error" text, | |
| 25 | + "last_latency_ms" integer, | |
| 26 | + "models_available" integer, | |
| 27 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 28 | + "updated_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 29 | +); | |
| 30 | +--> statement-breakpoint | |
| 31 | +CREATE TABLE "project_files" ( | |
| 32 | + "id" text PRIMARY KEY NOT NULL, | |
| 33 | + "user_id" text NOT NULL, | |
| 34 | + "project_id" text, | |
| 35 | + "kind" text NOT NULL, | |
| 36 | + "name" text NOT NULL, | |
| 37 | + "mime_type" text NOT NULL, | |
| 38 | + "size_bytes" integer NOT NULL, | |
| 39 | + "data_base64" text NOT NULL, | |
| 40 | + "width" integer, | |
| 41 | + "height" integer, | |
| 42 | + "estimated_tokens" integer, | |
| 43 | + "description" text, | |
| 44 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 45 | +); | |
| 46 | +--> statement-breakpoint | |
| 47 | +CREATE TABLE "projects" ( | |
| 48 | + "id" text PRIMARY KEY NOT NULL, | |
| 49 | + "user_id" text NOT NULL, | |
| 50 | + "name" text NOT NULL, | |
| 51 | + "description" text, | |
| 52 | + "icon" text, | |
| 53 | + "color" text, | |
| 54 | + "instructions" text, | |
| 55 | + "preferred_model_keys" jsonb DEFAULT '[]'::jsonb NOT NULL, | |
| 56 | + "default_settings" jsonb DEFAULT '{}'::jsonb NOT NULL, | |
| 57 | + "notes" text, | |
| 58 | + "archived" boolean DEFAULT false NOT NULL, | |
| 59 | + "sort_order" integer DEFAULT 0 NOT NULL, | |
| 60 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 61 | + "updated_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 62 | +); | |
| 63 | +--> statement-breakpoint | |
| 64 | +CREATE TABLE "prompts" ( | |
| 65 | + "id" text PRIMARY KEY NOT NULL, | |
| 66 | + "user_id" text NOT NULL, | |
| 67 | + "project_id" text, | |
| 68 | + "kind" text DEFAULT 'user' NOT NULL, | |
| 69 | + "name" text NOT NULL, | |
| 70 | + "description" text, | |
| 71 | + "content" text NOT NULL, | |
| 72 | + "variables" jsonb DEFAULT '[]'::jsonb NOT NULL, | |
| 73 | + "schema" jsonb, | |
| 74 | + "folder" text, | |
| 75 | + "tags" jsonb DEFAULT '[]'::jsonb NOT NULL, | |
| 76 | + "favorite" boolean DEFAULT false NOT NULL, | |
| 77 | + "default_model_key" text, | |
| 78 | + "uses" integer DEFAULT 0 NOT NULL, | |
| 79 | + "last_used_at" timestamp with time zone, | |
| 80 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 81 | + "updated_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 82 | +); | |
| 83 | +--> statement-breakpoint | |
| 84 | +CREATE TABLE "shared_arena_sessions" ( | |
| 85 | + "id" text PRIMARY KEY NOT NULL, | |
| 86 | + "session_id" text NOT NULL, | |
| 87 | + "user_id" text NOT NULL, | |
| 88 | + "snapshot" jsonb NOT NULL, | |
| 89 | + "is_public" boolean DEFAULT true NOT NULL, | |
| 90 | + "view_count" integer DEFAULT 0 NOT NULL, | |
| 91 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 92 | + "revoked_at" timestamp with time zone | |
| 93 | +); | |
| 94 | +--> statement-breakpoint | |
| 95 | +CREATE TABLE "user_model_labels" ( | |
| 96 | + "user_id" text NOT NULL, | |
| 97 | + "model_key" text NOT NULL, | |
| 98 | + "label" text NOT NULL, | |
| 99 | + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 100 | + CONSTRAINT "user_model_labels_user_id_model_key_pk" PRIMARY KEY("user_id","model_key") | |
| 101 | +); | |
| 102 | +--> statement-breakpoint | |
| 103 | +ALTER TABLE "conversations" ADD COLUMN "project_id" text;--> statement-breakpoint | |
| 104 | +ALTER TABLE "arena_votes" ADD CONSTRAINT "arena_votes_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 105 | +ALTER TABLE "custom_endpoints" ADD CONSTRAINT "custom_endpoints_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 106 | +ALTER TABLE "project_files" ADD CONSTRAINT "project_files_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 107 | +ALTER TABLE "project_files" ADD CONSTRAINT "project_files_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 108 | +ALTER TABLE "projects" ADD CONSTRAINT "projects_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 109 | +ALTER TABLE "prompts" ADD CONSTRAINT "prompts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 110 | +ALTER TABLE "prompts" ADD CONSTRAINT "prompts_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint | |
| 111 | +ALTER TABLE "shared_arena_sessions" ADD CONSTRAINT "shared_arena_sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 112 | +ALTER TABLE "user_model_labels" ADD CONSTRAINT "user_model_labels_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 113 | +CREATE UNIQUE INDEX "arena_votes_session_criterion_uq" ON "arena_votes" USING btree ("session_id","criterion");--> statement-breakpoint | |
| 114 | +CREATE INDEX "arena_votes_user_model_idx" ON "arena_votes" USING btree ("user_id","model_key");--> statement-breakpoint | |
| 115 | +CREATE INDEX "custom_endpoints_user_idx" ON "custom_endpoints" USING btree ("user_id");--> statement-breakpoint | |
| 116 | +CREATE INDEX "project_files_user_idx" ON "project_files" USING btree ("user_id");--> statement-breakpoint | |
| 117 | +CREATE INDEX "project_files_project_idx" ON "project_files" USING btree ("project_id");--> statement-breakpoint | |
| 118 | +CREATE INDEX "projects_user_idx" ON "projects" USING btree ("user_id");--> statement-breakpoint | |
| 119 | +CREATE INDEX "prompts_user_idx" ON "prompts" USING btree ("user_id");--> statement-breakpoint | |
| 120 | +CREATE INDEX "prompts_user_folder_idx" ON "prompts" USING btree ("user_id","folder");--> statement-breakpoint | |
| 121 | +CREATE INDEX "shared_arena_session_idx" ON "shared_arena_sessions" USING btree ("session_id");--> statement-breakpoint | |
| 122 | +CREATE INDEX "conversations_user_project_idx" ON "conversations" USING btree ("user_id","project_id"); | |
| \ No newline at end of file | ||
added
drizzle/meta/0001_snapshot.json
+3661 −0
Large diff (3,662 lines) is not loaded by default to keep the page fast.Load diff
modified
drizzle/meta/_journal.json
+7 −0
Large diff (14 lines) is not loaded by default to keep the page fast.Load diff
modified
package.json
+2 −1
Large diff (14 lines) is not loaded by default to keep the page fast.Load diff
added
public/apple-icon.png
+0 −0
Binary file not shown.
added
public/brand/icon-maskable.svg
+1 −0
Large diff (1 lines) is not loaded by default to keep the page fast.Load diff
added
public/brand/og.svg
+32 −0
Large diff (32 lines) is not loaded by default to keep the page fast.Load diff
added
public/favicon-16.png
+0 −0
Binary file not shown.
added
public/favicon-32.png
+0 −0
Binary file not shown.
added
public/favicon.ico
+0 −0
Binary file not shown.
added
public/icon-192.png
+0 −0
Binary file not shown.
added
public/icon-512.png
+0 −0
Binary file not shown.
added
public/icon-maskable-512.png
+0 −0
Binary file not shown.
modified
public/icon.svg
+1 −1
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
added
public/og.png
+0 −0
Binary file not shown.
added
scripts/brand-assets.sh
+20 −0
Large diff (20 lines) is not loaded by default to keep the page fast.Load diff
modified
src/app/api/models/route.ts
+16 −6
Large diff (35 lines) is not loaded by default to keep the page fast.Load diff
modified
src/app/globals.css
+178 −41
Large diff (339 lines) is not loaded by default to keep the page fast.Load diff
modified
src/app/layout.tsx
+21 −5
Large diff (45 lines) is not loaded by default to keep the page fast.Load diff
added
src/app/manifest.ts
+30 −0
Large diff (30 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/app/bottom-nav.tsx
+52 −0
Large diff (52 lines) is not loaded by default to keep the page fast.Load diff
modified
src/components/app/shell.tsx
+76 −23
Large diff (149 lines) is not loaded by default to keep the page fast.Load diff
modified
src/components/app/sidebar.tsx
+155 −108
Large diff (410 lines) is not loaded by default to keep the page fast.Load diff
modified
src/components/app/store.tsx
+38 −2
Large diff (125 lines) is not loaded by default to keep the page fast.Load diff
modified
src/components/brand/logo.tsx
+44 −15
Large diff (79 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/common/prompt-dialog.tsx
+86 −0
Large diff (86 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/library/file-library-picker.tsx
+110 −0
Large diff (110 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/projects/sidebar-section.tsx
+46 −0
Large diff (46 lines) is not loaded by default to keep the page fast.Load diff
modified
src/components/ui/misc.tsx
+5 −0
Large diff (8 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/ui/segmented.tsx
+90 −0
Large diff (90 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/ui/sheet.tsx
+265 −0
Large diff (265 lines) is not loaded by default to keep the page fast.Load diff
modified
src/db/index.ts
+4 −1
Large diff (13 lines) is not loaded by default to keep the page fast.Load diff
added
src/db/schema-workspace.ts
+201 −0
Large diff (201 lines) is not loaded by default to keep the page fast.Load diff
modified
src/db/schema.ts
+3 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added
src/lib/ai/registry/user-models.ts
+22 −0
Large diff (22 lines) is not loaded by default to keep the page fast.Load diff
added
src/lib/client/hooks.ts
+295 −0
Large diff (295 lines) is not loaded by default to keep the page fast.Load diff
added
src/lib/client/router.ts
+215 −0
Large diff (215 lines) is not loaded by default to keep the page fast.Load diff
added
src/lib/client/tokens.ts
+90 −0
Large diff (90 lines) is not loaded by default to keep the page fast.Load diff
modified
src/lib/client/types.ts
+3 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff