This commit is contained in:
k
2026-09-21 12:01:35 +02:00
parent 0e44bf6364
commit 06447062be
18 changed files with 523 additions and 47 deletions
+17 -2
View File
@@ -63,9 +63,24 @@ conversations → sessions → user) inside a transaction.
**returned once** at creation/regeneration. Regeneration also wipes the
account's sessions.
## Drivers
## Local file CDN
`src/drivers/`: `LLMDriver` interface (`listModels`, `chat`, `chatStream`),
`src/cdn.ts` + routes in `index.ts`. Generic namespaced layout
(`PUT /cdn/<ns>/<key>` authed, `GET /cdn/<ns>/<key>.<ext>` public) so future
file kinds plug into `CDN_NAMESPACES` without new routes. Today only `avatar`
exists: key = account id (`/cdn/avatar/<id>.png`), ownership enforced.
- Type safety by **magic bytes** (JPEG `FF D8 FF`, PNG signature, `RIFF…WEBP`);
only `jpg`/`png`/`webp` are representable, the stored extension is always
detected, everything else is `415`. Route regex charsets make traversal
unmatchable (attempts fall through to the SPA fallback).
- Per-namespace limits: `avatar` = 5 MB max (`413`), 5 uploads / 2 h /
account (`429` + `Retry-After`), counted on success. Re-upload replaces all
stored variants for the key.
- Served with detected `Content-Type`, `X-Content-Type-Options: nosniff`,
`Cache-Control: public, max-age=3600`.
## Drivers`src/drivers/`: `LLMDriver` interface (`listModels`, `chat`, `chatStream`),
`DriverRegistry` with global priority
`ollama → mistral → puppeteer-openai (stub) → deepseek → anthropic → openai`.
Models are addressed as `"driver:model"` (split on the first `:`).
+11 -1
View File
@@ -39,7 +39,8 @@ read-only `SharePage` (no auth), everything else renders the authenticated app.
share button), message list (`Markdown` for assistant, emerald bubble for
user), rounded composer (Enter to send, Shift+Enter for newline).
- `dialogs.tsx` — `ConvEditDialog` (title + topic), `ShareModal` (public link,
copy, unshare), `SettingsModal` (display name, avatar URL, theme).
copy, unshare), `SettingsModal` (display name, avatar upload box with
drag & drop, avatar URL, theme).
- `AdminPanel.tsx` — No-KYC account management: create (key shown once),
regenerate (revokes sessions), edit, delete with confirm.
- `SharePage.tsx` — public read-only view, fetches `/api/share/:id` unauthenticated.
@@ -52,3 +53,12 @@ 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). 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.
+17 -2
View File
@@ -10,6 +10,7 @@ Copy `.env.example` to `.env`. Full list:
| `APP_PASSWORD` | *(required)* | **admin** access key — generate with `openssl rand -base64 24` |
| `SESSION_TTL_HOURS` | `720` | Bearer session lifetime (30 days) |
| `DATA_DIR` | `./data` | holds `kisassistant.db` (SQLite) |
| `CDN_DIR` | `./cdn` | local file CDN storage (avatars; NixOS: under state dir) |
| `WIKI_URL` | `https://git.kisakay.com/k/chat/wiki` | remote docs wiki — `GET /wiki` redirects (302) there |
| `RATE_LIMIT_MAX` / `RATE_LIMIT_WINDOW_MIN` | `5` / `10` | login rate limit per IP |
| `OLLAMA_HOST` / `OLLAMA_ENABLED` | `http://10.66.66.4:11434` / `true` | local-model driver |
@@ -35,11 +36,25 @@ unshare. See `AGENTS.md`.
1. Log in as `admin` with the `APP_PASSWORD` key.
2. Open **Accounts** → *New account* → username (+ display name).
3. Copy the one-time `ka_…` key and hand it to the owner (chat, QR code, …).
4. Users set their display name, avatar URL (upload e.g. to `catbox.moe`, paste
the direct link) and theme in the profile settings.
4. Users set their display name, avatar (upload box in profile settings, or a
remote URL e.g. from `catbox.moe`) and theme in the profile settings.
5. Rotate a compromised key with the regenerate button (old sessions die);
delete removes the account with all its chats and shares.
## Local file CDN (avatars)
`PUT /cdn/<ns>/<key>` (Bearer) / `GET /cdn/<ns>/<key>.<ext>` (public), e.g.
`/cdn/avatar/<account-id>.png`. Namespaces live in `CDN_NAMESPACES`
(`src/cdn.ts`) — add one to extend (future usage).
- Allowed types **only** `jpg`/`png`/`webp`, verified by **magic bytes**, never
by extension (client filename is ignored; the stored extension comes from
detection). Everything else → `415`, unknown namespace/ext → `404`.
- `avatar` rules: 5 MB max (`413` above), **5 uploads max per 2 h per account**
(`429` + `Retry-After`), ownership enforced (own id, or admin).
- Served with fixed `Content-Type`, `nosniff`, `Cache-Control: public,
max-age=3600`. No listing, no traversal (strict charsets + SPA fallback).
## NixOS deployment (`chat.kisakay.com`)
`flake.nix` exposes `packages.<system>.default` (backend + prebuilt frontend)