updates
This commit is contained in:
@@ -25,7 +25,7 @@ const TABS: { id: Tab; label: string; icon: typeof Users }[] = [
|
||||
export function AdminCenter() {
|
||||
const [allowed, setAllowed] = useState<boolean | null>(null);
|
||||
const [tab, setTab] = useState<Tab>("accounts");
|
||||
const [settings, setSettings] = useState<{ registrationEnabled: boolean; ocrEnabled: boolean } | null>(null);
|
||||
const [settings, setSettings] = useState<{ registrationEnabled: boolean; accessRequestEnabled: boolean; ocrEnabled: boolean } | null>(null);
|
||||
const [mail, setMail] = useState<{ recovery: boolean; from?: string } | null>(null);
|
||||
const [error, setError] = useState("");
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -45,7 +45,7 @@ export function AdminCenter() {
|
||||
.catch(() => setAllowed(false));
|
||||
}, []);
|
||||
|
||||
async function toggle(patch: { registrationEnabled?: boolean; ocrEnabled?: boolean }) {
|
||||
async function toggle(patch: { registrationEnabled?: boolean; accessRequestEnabled?: boolean; ocrEnabled?: boolean }) {
|
||||
setSaving(true);
|
||||
setError("");
|
||||
try {
|
||||
@@ -129,10 +129,29 @@ export function AdminCenter() {
|
||||
<div className="flex items-center gap-4 rounded-3xl border border-stone-200/70 bg-white p-5 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium">Public registration</p>
|
||||
<p className="text-sm opacity-60">Show the Register button on the login page. Off = admin-created accounts only.</p>
|
||||
<p className="text-sm opacity-60">Show the Register button on the login page. Off = admin-created accounts only. Turning it on retires the wishlist below.</p>
|
||||
</div>
|
||||
<Switch label="Public registration" checked={settings?.registrationEnabled ?? false} onChange={(v) => toggle({ registrationEnabled: v })} />
|
||||
</div>
|
||||
<div className={cn(
|
||||
"flex items-center gap-4 rounded-3xl border border-stone-200/70 bg-white p-5 shadow-sm dark:border-zinc-800 dark:bg-zinc-900",
|
||||
settings?.registrationEnabled && "opacity-60",
|
||||
)}>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium">Access requests (wishlist)</p>
|
||||
<p className="text-sm opacity-60">
|
||||
{settings?.registrationEnabled
|
||||
? "Unavailable while public registration is on — it replaces the wishlist."
|
||||
: "Show Request access on the login page. Visitors reserve a username and plead their case; you triage them in the Access tab."}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
label="Access requests"
|
||||
checked={settings?.accessRequestEnabled ?? false}
|
||||
disabled={settings?.registrationEnabled ?? false}
|
||||
onChange={(v) => toggle({ accessRequestEnabled: v })}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 rounded-3xl border border-stone-200/70 bg-white p-5 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium">OCR tool</p>
|
||||
|
||||
@@ -301,7 +301,7 @@ export function AdminPanel({ open, onClose, bare }: { open: boolean; onClose: ()
|
||||
)}
|
||||
|
||||
{!showCreate ? (
|
||||
<div className="mb-3 flex flex-wrap gap-2">
|
||||
<div className="mb-5 flex flex-wrap gap-2">
|
||||
<Button variant="secondary" size="sm" onClick={() => setShowCreate(true)}>
|
||||
<UserPlus size={15} /> New account
|
||||
</Button>
|
||||
|
||||
@@ -153,7 +153,7 @@ export function Chat({
|
||||
<div className="mx-auto w-full max-w-3xl space-y-5">
|
||||
{messages.length === 0 && !streaming && (
|
||||
<div className="py-16 text-center">
|
||||
<FlowerMark size={56} className="mx-auto mb-4" />
|
||||
<FlowerMark size={56} dynamic={false} className="mx-auto mb-4" />
|
||||
<h2 className="text-2xl font-bold tracking-tight">How can I help, {user.displayName.split(" ")[0]}?</h2>
|
||||
<p className="mt-1 text-sm opacity-60">Pick a model, then type below. Markdown is supported.</p>
|
||||
</div>
|
||||
|
||||
@@ -22,12 +22,14 @@ export function Login({ onLogin }: { onLogin: (user: UserType) => void }) {
|
||||
const [registerOpen, setRegisterOpen] = useState(false);
|
||||
const [registrationOn, setRegistrationOn] = useState(false);
|
||||
const [requestOpen, setRequestOpen] = useState(false);
|
||||
const [requestOn, setRequestOn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
api.methods().then((m) => {
|
||||
setRecoveryOn(m.recovery);
|
||||
setRecoveryFrom(m.from);
|
||||
setRegistrationOn(m.registration);
|
||||
setRequestOn(m.accessRequest);
|
||||
}).catch(() => {});
|
||||
}, []);
|
||||
|
||||
@@ -115,9 +117,9 @@ export function Login({ onLogin }: { onLogin: (user: UserType) => void }) {
|
||||
onClose={() => setRegisterOpen(false)}
|
||||
onDone={(u, k) => { setUsername(u); setKey(k); }}
|
||||
/>
|
||||
{/* Wishlist path: when open registration is off, visitors can still
|
||||
reserve a username and plead their case; an admin triages it. */}
|
||||
{!registrationOn && (
|
||||
{/* Wishlist path: only when open registration is off AND the admin
|
||||
enabled access requests. They are mutually exclusive server-side. */}
|
||||
{!registrationOn && requestOn && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Flower,
|
||||
LayoutDashboard,
|
||||
MessageSquarePlus,
|
||||
MoreVertical,
|
||||
@@ -15,7 +14,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import type { Conversation, User } from "../lib/types.ts";
|
||||
import { api } from "../lib/api.ts";
|
||||
import { Avatar, Button, ContextMenu, IconButton } from "./ui.tsx";
|
||||
import { Avatar, Button, ContextMenu, FlowerMark, IconButton } from "./ui.tsx";
|
||||
import { cn } from "../lib/cn.ts";
|
||||
|
||||
interface MenuState {
|
||||
@@ -127,9 +126,7 @@ export function Sidebar({
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 px-2 pb-1 pt-0.5">
|
||||
<span className="grid h-9 w-9 shrink-0 place-items-center rounded-2xl bg-accent-600/10 text-accent-600 dark:text-accent-400">
|
||||
<Flower size={20} />
|
||||
</span>
|
||||
<FlowerMark size={36} dynamic={false} />
|
||||
<span className="font-serif text-xl font-bold tracking-tight">KisAssistant</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
|
||||
@@ -491,7 +491,7 @@ export function FlowerMark({ size, className, dynamic = true }: { size?: number;
|
||||
);
|
||||
}
|
||||
|
||||
/** Assistant identity: flower mark on a soft circle (message avatars, headers). */
|
||||
/** Assistant identity: static flower mark on a soft circle (message avatars). */
|
||||
export function AssistantAvatar({ size = 32 }: { size?: number }) {
|
||||
return (
|
||||
<span
|
||||
@@ -499,15 +499,15 @@ export function AssistantAvatar({ size = 32 }: { size?: number }) {
|
||||
style={{ width: size, height: size }}
|
||||
aria-label="KisAssistant"
|
||||
>
|
||||
<FlowerMark size={Math.round(size * 0.78)} />
|
||||
<FlowerMark size={Math.round(size * 0.78)} dynamic={false} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/* ---------- Switch (feature toggle) ---------- */
|
||||
|
||||
export function Switch({ checked, onChange, label }: { checked: boolean; onChange: (v: boolean) => void; label: string }) {
|
||||
return <Toggle checked={checked} onChange={onChange} label={label} />;
|
||||
export function Switch({ checked, onChange, label, disabled = false }: { checked: boolean; onChange: (v: boolean) => void; label: string; disabled?: boolean }) {
|
||||
return <Toggle checked={checked} onChange={onChange} label={label} disabled={disabled} />;
|
||||
}
|
||||
|
||||
/* ---------- Misc ---------- */
|
||||
|
||||
@@ -67,7 +67,7 @@ export const api = {
|
||||
verify: () => req<{ ok: boolean; user: User }>("/api/auth/verify"),
|
||||
logout: () => req<{ ok: boolean }>("/api/auth/logout", { method: "POST" }).catch(() => ({ ok: true as const })),
|
||||
|
||||
methods: () => req<{ recovery: boolean; from?: string; registration: boolean }>("/api/auth/methods", {}, false),
|
||||
methods: () => req<{ recovery: boolean; from?: string; registration: boolean; accessRequest: boolean }>("/api/auth/methods", {}, false),
|
||||
register: (u: { username: string; displayName?: string; email?: string }) =>
|
||||
req<{ user: User; key: string }>("/api/auth/register", { method: "POST", body: JSON.stringify(u) }, false),
|
||||
recover: (username: string) =>
|
||||
@@ -98,9 +98,9 @@ export const api = {
|
||||
adminPatch: (id: string, patch: { displayName?: string; avatarUrl?: string; theme?: string; email?: string }) =>
|
||||
req<{ user: User }>(`/api/admin/users/${id}`, { method: "PATCH", body: JSON.stringify(patch) }),
|
||||
|
||||
adminGetSettings: () => req<{ settings: { registrationEnabled: boolean; ocrEnabled: boolean } }>("/api/admin/settings"),
|
||||
adminPatchSettings: (patch: { registrationEnabled?: boolean; ocrEnabled?: boolean }) =>
|
||||
req<{ settings: { registrationEnabled: boolean; ocrEnabled: boolean } }>("/api/admin/settings", { method: "PATCH", body: JSON.stringify(patch) }),
|
||||
adminGetSettings: () => req<{ settings: { registrationEnabled: boolean; accessRequestEnabled: boolean; ocrEnabled: boolean } }>("/api/admin/settings"),
|
||||
adminPatchSettings: (patch: { registrationEnabled?: boolean; accessRequestEnabled?: boolean; ocrEnabled?: boolean }) =>
|
||||
req<{ settings: { registrationEnabled: boolean; accessRequestEnabled: boolean; ocrEnabled: boolean } }>("/api/admin/settings", { method: "PATCH", body: JSON.stringify(patch) }),
|
||||
|
||||
convs: () => req<{ conversations: Conversation[] }>("/api/conversations"),
|
||||
/** Search your old chats (titles, topics and message content). */
|
||||
|
||||
Reference in New Issue
Block a user