Files
chat/docs/FRONTEND.md
T
2026-09-24 11:02:24 +02:00

107 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Frontend
React 18 + Vite + Tailwind 3 + lucide-react + react-markdown/remark-gfm, Inter
Variable self-hosted (`@fontsource-variable/inter` — no external requests).
Built with `bun run build:frontend` into `public/` (Vite `emptyOutDir`; that
directory is a build artifact, never hand-edit, not committed).
## Routing
No router library — a tiny history wrapper (`lib/route.ts`: `navigate()`,
`useRoute()`, trailing-slash normalization) drives pathname routing:
- `/login` — sign-in card (username + access key + TOTP step). Renders
instantly, **never shows the boot splash**.
- `/register` — full-page self-registration (`RegisterPage.tsx`; key shown
once, then hands username+key to `/login` via `sessionStorage`). No splash.
- `/chat` — the platform itself (auth required; boots behind the splash).
Bare `/chat` is the empty composer (new chat, created lazily on first send).
- `/chat/:id` — one conversation page (deep-linkable, back/forward supported).
Selecting a chat navigates there; unknown ids bounce back to `/chat`.
Archived chats are viewed read-only on bare `/chat` (no per-id URL).
- `/` — redirects to `/chat` when logged in, `/login` otherwise (placeholder
for a future landing page).
- Public routes (unchanged): `/share/:publicId`, `/reset/:token`,
`/review/:id`, `/admin`.
## State & data flow
- `App.tsx` owns: `user`, `models`/`model`, `convs`, `activeId`, `messages`,
`streaming`, modal states. All server state is re-fetched (no client cache
library); conversations themselves live server-side in SQLite.
- `lib/api.ts` — typed `fetch` wrapper: Bearer header from `localStorage`,
`{ error }` parsing, `ApiError` with status, auto logout+reload on 401
(except on `/share/*`). `chatStream()` parses SSE `token`/`done`/`error`.
- Boot shows `LoadingScreen` for at least 1.5 s (`MIN_SPLASH_MS` in `App.tsx`)
so the bloom animation always plays, even when the session restores
instantly — **only on `/` and `/chat`**. `/login` and `/register` skip it.
- Sending: ensure a server conversation exists (created lazily on first send),
optimistic user message, stream tokens into `streaming`, then append the full
assistant message and refresh the list (server auto-titles new chats).
## Components (`components/`)
- `ui.tsx` — design system. `Button` (primary/secondary/ghost/danger),
`IconButton`, `Input`, `Field`, `Modal`, `ConfirmDialog`, `ContextMenu`,
`Avatar` (remote URL or gradient initials), `CopyButton`, `Spinner` and
**`Picker`** — the framework-styled dropdown (pill button + popover, optional
option groups, lucide `ChevronDown`/`Check`, Escape/outside-click to close).
**Never use a native `<select>`**; use `Picker` for model and theme choices.
- `Login.tsx` — username + access-key card (`/login`); the Register button
navigates to `/register`, and a fresh registration prefills the form.
- `RegisterPage.tsx` — full-page registration (`/register`) reusing the login
card style; shows the new key once with a copy button, then continues to
`/login`.
- `Sidebar.tsx` — collapsible conversation list with per-item context menu
(right-click or `···`: Rename / Set topic / Share publicly / Archive /
Delete), user chip (opens settings), Accounts button (admin), logout.
Archived chats leave the list (and search); they live in settings.
- `Chat.tsx` — header (title, topic badge, driver-grouped model `Picker`,
share button), message list (`Markdown` for assistant, emerald bubble for
user), rounded composer with paperclip attach menu, attachment chips
(Enter to send, Shift+Enter for newline). With `readOnly` (archived chat)
the composer and share button are replaced by an unarchive banner.
- `dialogs.tsx` — `ConvEditDialog` (title + topic), `ShareModal` (public link,
copy, unshare), `SettingsModal` (display name, avatar upload box with
drag & drop, avatar URL, theme, plus an "Archived chats" category to
view / unarchive / delete archived conversations).
- `AdminPanel.tsx` — No-KYC account management: create (key shown once),
regenerate (revokes sessions), edit, delete with confirm, plus server-side
search / sort (newest, oldest, A–Z, Z–A) / email filter / pagination.
Renders bare (no modal shell) when embedded in the Admin Center page.
- `AdminCenter.tsx` — `/admin` route (admin gate + locked screen otherwise):
tabbed hub (Accounts / Features / Mail) reusing `AdminPanel`, server
feature `Switch`es (`registrationEnabled`, `ocrEnabled` via
`/api/admin/settings`), SMTP viewer (password blurred by default, eye toggle
to reveal) + connectivity tester (verify + send test mail). Follows the platform theme.
- `SharePage.tsx` — public read-only view, fetches `/api/share/:id` unauthenticated
(`{ title, topic, model, authorName, authorAvatarUrl, archived, sharedAt, messages }`);
user messages render the author's avatar via `Avatar` (URL or initials);
an "Archived" pill shows when the source conversation is archived.
- `Markdown.tsx` — `react-markdown` + GFM; code blocks get a language label, a
copy button and dependency-free syntax highlighting (`lib/highlight.tsx` —
regex tokenizer, no highlight.js, so the Nix offline lockfile is untouched).
- Admin Center tabs are deep-linkable (`/admin`, `/admin/models`,
`/admin/access`, … — `lib/route.ts` `adminTabFromPath`, SPA fallback serves
them all). Models tab: `OllamaNodes.tsx` (pool with weights, per-node live
status + 1h–30d load charts, generation timeout) and `OllamaCatalog.tsx`
(live Hugging Face GGUF search + one-click `hf.co/…` pull to a node).
## Theming
Tailwind `darkMode: "class"`. `applyTheme()` in `App.tsx` toggles the `dark`
class from the account theme (`auto` follows `prefers-color-scheme` with a
live media-query listener; `sunset` follows the browser's local time —
light 07:00–19:00, dark otherwise — re-applied at each boundary via
`lib/solarTheme.ts`). Accent: emerald; shapes: `rounded-2xl/3xl`
everywhere; every surface needs a `dark:` variant. UI text is English only.
## Mobile
Below the `md` breakpoint the sidebar becomes a slide-in drawer (backdrop tap
or swipe-left closes it, hamburger in the chat header opens it). Modals render
as bottom sheets with a drag handle — pull down past the threshold to dismiss
(tap backdrop or Escape works too). Composer respects
`env(safe-area-inset-bottom)`. No separate mobile codebase: same components,
responsive Tailwind classes + minimal touch handlers.