From c18397880a0a90c0ba08ebeae94775188ab9ca62 Mon Sep 17 00:00:00 2001 From: Anais Saraiva Date: Mon, 21 Sep 2026 14:38:00 +0200 Subject: [PATCH] updates --- README.md | 7 +- anoying_promt | 12 ++ docs/OPERATIONS.md | 4 +- frontend/index.html | 37 +++- frontend/public/robots.txt | 4 + frontend/src/App.tsx | 4 +- frontend/src/components/AdminCenter.tsx | 4 +- frontend/src/components/Sidebar.tsx | 1 - frontend/src/components/dialogs.tsx | 232 +++++++++++++++--------- frontend/src/components/ui.tsx | 57 +++--- frontend/src/index.css | 30 +++ 11 files changed, 268 insertions(+), 124 deletions(-) create mode 100644 frontend/public/robots.txt diff --git a/README.md b/README.md index 2a1bb14..66c81f9 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,8 @@ from the **Accounts** panel (each gets a one-time access key to hand over). - Admin creates users (`username`, display name, avatar URL, theme) — the API returns a `ka_…` access key **once**; keys are stored hashed (sha256). - Users log in with `{ username, key }`. Regenerating a key revokes old sessions. -- Per-account: display name, avatar URL (host images e.g. on `catbox.moe` and - paste the link; empty = initials avatar), theme (`auto`/`light`/`dark`). +- Per-account: display name, avatar (uploaded to the built-in CDN from profile + settings), theme (`auto`/`light`/`dark`), accent color, feature flags. ## API @@ -60,6 +60,9 @@ from the **Accounts** panel (each gets a one-time access key to hand over). | POST | `/api/admin/users/:id/regenerate` | admin | new key, old sessions revoked | | GET/PATCH | `/api/admin/settings` | admin | feature flags: registrationEnabled, ocrEnabled | | GET/POST | `/api/conversations` | Bearer | server-persisted chats (title, topic, model) | +| GET | `/api/conversations/search?q=` | Bearer | search titles, topics + old prompts (with snippet) | +| POST | `/api/admin/ollama/pull` | admin | pull a model from the Ollama library (NDJSON progress) | +| DELETE | `/api/admin/ollama/models/:name` | admin | remove a local Ollama model | | GET/PATCH/DELETE | `/api/conversations/:id` | owner | incl. messages on GET | | POST/GET/DELETE | `/api/conversations/:id/share` | owner | public link `/share/:publicId` | | GET | `/api/share/:publicId` | **public** | read-only shared chat JSON | diff --git a/anoying_promt b/anoying_promt index 935df77..3c750f3 100644 --- a/anoying_promt +++ b/anoying_promt @@ -27,3 +27,15 @@ Can refusing with a reason / accepting with reason / reviewing : each new message in the conversations makes a new mail forwarded by (like real ticket) precise that theses mail arent the official way and need to be responded into the official site (chat.kisakay.com/review) makes beautiful simple easy - by design HTML for each type of mail + +--- UX bugs / improvements: +class="flex w-full items-center gap-2.5 rounded-xl p-1.5 text-left transition hover:bg-stone-100 dark:hover:bg-zinc-800" +the actual on:hover makes the two buttons together, seprared it goodly + +Use the new flower svgs built-in for the ai logo. Only one declaration of the logo, not copy-paste everywhere needing it. One constant and imported anywhere +Make the loading screen smoother + + +--- SETTINGS REWAMPING: +Make a paginations system for users into the Admin Center. +Filtering, by account timetamp, alphabetily filtered, unverified/etc... diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md index b24d6c4..d72c636 100644 --- a/docs/OPERATIONS.md +++ b/docs/OPERATIONS.md @@ -47,8 +47,8 @@ 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 (upload box in profile settings, or a - remote URL e.g. from `catbox.moe`) and theme in the profile settings. +4. Users set their display name, avatar (upload a picture in the profile settings — + hosted on the built-in CDN), theme, accent color and feature flags in the settings. 5. Rotate a compromised key with the regenerate button (old sessions die); delete removes the account with all its chats and shares. diff --git a/frontend/index.html b/frontend/index.html index 761a663..0a93baf 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,11 +2,40 @@ - - KisAssistant - - + + KisAssistant — private AI chat + + + + + + + + + + + + + + + + + + + + + +
diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt new file mode 100644 index 0000000..4ab1795 --- /dev/null +++ b/frontend/public/robots.txt @@ -0,0 +1,4 @@ +# KisAssistant — crawlers, LLM agents and scrapers are welcome on public pages. +# Authenticated app routes (/api/* except /api/share/:id) require a Bearer key. +User-agent: * +Allow: / diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 49110c9..65423e9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -9,7 +9,7 @@ import { ConvEditDialog, FilePreviewModal, SettingsModal, ShareModal } from "./c import { AdminCenter } from "./components/AdminCenter.tsx"; import { SharePage } from "./components/SharePage.tsx"; import { ResetPage } from "./components/ResetPage.tsx"; -import { ConfirmDialog } from "./components/ui.tsx"; +import { ConfirmDialog, LoadingScreen } from "./components/ui.tsx"; function shareIdFromPath(): string | null { const m = window.location.pathname.match(/^\/share\/([A-Za-z0-9_-]{6,64})\/?$/); @@ -256,7 +256,7 @@ export function App() { } if (checking) { - return
Loading…
; + return ; } if (!user) { diff --git a/frontend/src/components/AdminCenter.tsx b/frontend/src/components/AdminCenter.tsx index 6c8a8cb..fecd5da 100644 --- a/frontend/src/components/AdminCenter.tsx +++ b/frontend/src/components/AdminCenter.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import { ArrowLeft, LayoutDashboard, Mail, ShieldAlert, SlidersHorizontal, Users } from "lucide-react"; import { api } from "../lib/api.ts"; import { AdminPanel } from "./AdminPanel.tsx"; -import { Logo, Spinner, Switch } from "./ui.tsx"; +import { LoadingScreen, Logo, Spinner, Switch } from "./ui.tsx"; import { cn } from "../lib/cn.ts"; type Tab = "accounts" | "features" | "mail"; @@ -57,7 +57,7 @@ export function AdminCenter() { } if (allowed === null) { - return
Checking admin access…
; + return ; } if (!allowed) { diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index ad829ec..38c5fe4 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -11,7 +11,6 @@ import { Share2, Tag, Trash2, - Users, X, } from "lucide-react"; import type { Conversation, User } from "../lib/types.ts"; diff --git a/frontend/src/components/dialogs.tsx b/frontend/src/components/dialogs.tsx index d89511f..e967dd9 100644 --- a/frontend/src/components/dialogs.tsx +++ b/frontend/src/components/dialogs.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from "react"; -import { Globe, FileText, ImagePlus, Link2, Mail, Palette, Pencil, ScanText, Settings2, Sparkles, Tag, Unplug, UserPlus } from "lucide-react"; +import { Globe, FileText, ImagePlus, Link2, Mail, Palette, Pencil, ScanText, Search, Settings2, Sparkles, Tag, Unplug, User as UserIcon, UserPlus, X } from "lucide-react"; import { api } from "../lib/api.ts"; import { cn } from "../lib/cn.ts"; import { ACCENT_PRESETS, applyAccent, currentAccent, previewAccent, type AccentState } from "../lib/accent.ts"; @@ -354,8 +354,17 @@ export function SettingsModal({ const [uploading, setUploading] = useState(false); const [uploadError, setUploadError] = useState(""); const [dragOver, setDragOver] = useState(false); + const [search, setSearch] = useState(""); const fileRef = useRef(null); + // Section search: each section lists keywords; a section shows when the + // query is empty or matches its title/keywords. + function show(...keywords: string[]): boolean { + const q = search.trim().toLowerCase(); + if (!q) return true; + return keywords.some((k) => k.toLowerCase().includes(q)); + } + useEffect(() => { if (user) { setDisplayName(user.displayName); @@ -364,6 +373,7 @@ export function SettingsModal({ setTheme(user.theme); setError(""); setUploadError(""); + setSearch(""); } }, [user]); @@ -409,83 +419,122 @@ export function SettingsModal({ } return ( - +
-
- -
-

{displayName || "…"}

-

@{user?.username}

-
-
-
- Avatar upload -
fileRef.current?.click()} - onKeyDown={(e) => e.key === "Enter" && fileRef.current?.click()} - onDragOver={(e) => { e.preventDefault(); setDragOver(true); }} - onDragLeave={() => setDragOver(false)} - onDrop={(e) => { - e.preventDefault(); - setDragOver(false); - const f = e.dataTransfer.files?.[0]; - if (f) uploadFile(f); - }} - className={cn( - "flex cursor-pointer items-center gap-3 rounded-3xl border-2 border-dashed px-4 py-3.5 transition", - dragOver - ? "border-accent-500 bg-accent-500/10" - : "border-stone-200 hover:border-accent-500/60 hover:bg-stone-50 dark:border-zinc-700 dark:hover:bg-zinc-800/60", - )} - > - - {uploading ? : } - - - {uploading ? "Uploading…" : dragOver ? "Drop it!" : "Drop an image or click to upload"} - jpg · png · webp — max 5MB — 5 changes per 2h - -
+
+ { - const f = e.target.files?.[0]; - if (f) uploadFile(f); - e.target.value = ""; - }} + value={search} + onChange={(e) => setSearch(e.target.value)} + placeholder="Search settings…" + aria-label="Search settings" + className="w-full rounded-2xl border border-stone-200/70 bg-stone-100 py-2 pl-9 pr-8 text-sm outline-none transition placeholder:text-stone-400 focus:border-accent-500/60 focus:bg-white dark:border-zinc-800 dark:bg-zinc-800/60 dark:placeholder:text-zinc-500 dark:focus:bg-zinc-900" /> - {uploadError &&

{uploadError}

} + {search && ( + + )}
- - setDisplayName(e.target.value)} maxLength={60} required /> - - - setAvatarUrl(e.target.value)} placeholder="https://…" inputMode="url" /> - - - setEmail(e.target.value)} placeholder="you@example.com" inputMode="email" /> - - - - - - + + {show("profile", "avatar", "picture", "photo", "name", "display", "email", "account") && ( + +
+ +
+

{displayName || "…"}

+

@{user?.username}

+
+
+
+ Avatar upload +
fileRef.current?.click()} + onKeyDown={(e) => e.key === "Enter" && fileRef.current?.click()} + onDragOver={(e) => { e.preventDefault(); setDragOver(true); }} + onDragLeave={() => setDragOver(false)} + onDrop={(e) => { + e.preventDefault(); + setDragOver(false); + const f = e.dataTransfer.files?.[0]; + if (f) uploadFile(f); + }} + className={cn( + "flex cursor-pointer items-center gap-3 rounded-3xl border-2 border-dashed px-4 py-3.5 transition", + dragOver + ? "border-accent-500 bg-accent-500/10" + : "border-stone-200 hover:border-accent-500/60 hover:bg-stone-50 dark:border-zinc-700 dark:hover:bg-zinc-800/60", + )} + > + + {uploading ? : } + + + {uploading ? "Uploading…" : dragOver ? "Drop it!" : "Drop an image or click to upload"} + jpg · png · webp — max 5MB — 5 changes per 2h + +
+ { + const f = e.target.files?.[0]; + if (f) uploadFile(f); + e.target.value = ""; + }} + /> + {uploadError &&

{uploadError}

} +
+ + setDisplayName(e.target.value)} maxLength={60} required /> + + + setEmail(e.target.value)} placeholder="you@example.com" inputMode="email" /> + +
+ )} + + {show("appearance", "theme", "dark", "light", "color", "colour", "accent", "palette") && ( + + + + + + + )} + + {show("features", "thinking", "attachments", "search", "deep", "upload", "ocr", "capabilities", "enable", "disable") && ( + + + + )} + + {search.trim() && !show("profile", "avatar", "picture", "photo", "name", "display", "email", "account") + && !show("appearance", "theme", "dark", "light", "color", "colour", "accent", "palette") + && !show("features", "thinking", "attachments", "search", "deep", "upload", "ocr", "capabilities", "enable", "disable") && ( +

No settings match “{search.trim()}”.

+ )} + {error &&

{error}

}
@@ -496,6 +545,31 @@ export function SettingsModal({ ); } +/* ---------- Settings sections ---------- */ + +function SettingSection({ + icon: Icon, + title, + hint, + children, +}: { + icon: typeof Settings2; + title: string; + hint?: string; + children: React.ReactNode; +}) { + return ( +
+

+ + {title} +

+ {hint &&

{hint}

} +
{children}
+
+ ); +} + /* ---------- Accent color (settings) ---------- */ function AccentSection() { @@ -515,10 +589,6 @@ function AccentSection() { return (
- - - Accent color -
{ACCENT_PRESETS.map((p) => (
-

Applied instantly, saved on this device.

); } @@ -568,10 +637,6 @@ function FeaturesSection() { const flags = useFeatures(); return (
- - - Features -
{FEATURES.map((f) => (
-

Applied instantly, saved on this device.

); } diff --git a/frontend/src/components/ui.tsx b/frontend/src/components/ui.tsx index 6dea31c..5fde90e 100644 --- a/frontend/src/components/ui.tsx +++ b/frontend/src/components/ui.tsx @@ -475,26 +475,7 @@ export function Logo({ size = 36, className }: { size?: number; className?: stri /* ---------- Switch (feature toggle) ---------- */ export function Switch({ checked, onChange, label }: { checked: boolean; onChange: (v: boolean) => void; label: string }) { - return ( - - ); + return ; } /* ---------- Misc ---------- */ @@ -503,7 +484,8 @@ export function Spinner({ size = 18 }: { size?: number }) { return ; } -/** Accessible on/off switch (emerald→accent colored when on). */ +/** Accessible on/off switch (accent colored when on). Flex-based so the knob + * is always perfectly centered — no border/padding math to break. */ export function Toggle({ checked, onChange, @@ -524,16 +506,14 @@ export function Toggle({ disabled={disabled} onClick={() => onChange(!checked)} className={cn( - "relative h-6 w-11 shrink-0 rounded-full border transition disabled:opacity-40", - checked - ? "border-transparent bg-accent-600 dark:bg-accent-500" - : "border-stone-300 bg-stone-200 dark:border-zinc-600 dark:bg-zinc-700", + "flex h-6 w-11 shrink-0 items-center rounded-full px-0.5 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-500/50 disabled:cursor-not-allowed disabled:opacity-40", + checked ? "bg-accent-600 dark:bg-accent-500" : "bg-stone-300 dark:bg-zinc-700", )} > @@ -557,3 +537,26 @@ export function CopyButton({ text, label = "Copy" }: { text: string; label?: str ); } + +/* ---------- Loading screen ---------- */ + +/** Animated blooming flower shown while the app boots (themed, mobile-safe). */ +export function LoadingScreen() { + const petals = [0, 60, 120, 180, 240, 300]; + return ( +
+
+ +

KisAssistant

+

Preparing your chats

+
+
+ ); +} diff --git a/frontend/src/index.css b/frontend/src/index.css index aea924b..bf39da8 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -90,6 +90,36 @@ body { 50% { opacity: 0; } } +/* Loading flower: petals bloom one after another, core breathes. */ +.flower-petal { + transform-box: fill-box; + transform-origin: center; + animation: bloom 1.8s ease-in-out infinite; +} +@keyframes bloom { + 0%, 100% { transform: scale(0.55); opacity: 0.3; } + 50% { transform: scale(1); opacity: 1; } +} +.flower-core { + transform-box: fill-box; + transform-origin: center; + animation: core-breathe 1.8s ease-in-out infinite; +} +@keyframes core-breathe { + 0%, 100% { transform: scale(0.85); } + 50% { transform: scale(1.15); } +} +.loading-dots::after { + content: ""; + animation: dots 1.4s steps(4) infinite; +} +@keyframes dots { + 0% { content: ""; } + 25% { content: "."; } + 50% { content: ".."; } + 75% { content: "..."; } +} + /* Kill Chrome's yellow autofill background (login / password fields) */ input:-webkit-autofill, input:-webkit-autofill:hover,