import { useEffect, useRef, useState } from "react"; import { Fingerprint, Globe, FileText, ImagePlus, KeyRound, Link2, Mail, Palette, Pencil, ScanText, Search, Settings2, ShieldCheck, Sparkles, Tag, Trash2, 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"; import { FEATURES, setFeature, useFeatures } from "../lib/features.ts"; import type { Conversation, FilePreview, User } from "../lib/types.ts"; import { Avatar, bumpCdnVersion, Button, ConfirmDialog, CopyButton, Field, Input, Modal, Picker, Spinner, Toggle } from "./ui.tsx"; /* ---------- Rename + topic ---------- */ export function ConvEditDialog({ conv, onClose, onSaved, }: { conv: Conversation | null; onClose: () => void; onSaved: (c: Conversation) => void; }) { const [title, setTitle] = useState(""); const [topic, setTopic] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); useEffect(() => { if (conv) { setTitle(conv.title); setTopic(conv.topic); setError(""); } }, [conv]); async function save(e: React.FormEvent) { e.preventDefault(); if (!conv || busy) return; setBusy(true); setError(""); try { const res = await api.patchConv(conv.id, { title: title.trim(), topic: topic.trim() }); onSaved(res.conversation); onClose(); } catch (err) { setError(err instanceof Error ? err.message : "Save failed"); } finally { setBusy(false); } } return (
setTitle(e.target.value)} maxLength={120} required />
setTopic(e.target.value)} maxLength={120} placeholder="No topic" />
{error &&

{error}

}
); } /* ---------- Key recovery ---------- */ export function RecoverDialog({ open, onClose, from }: { open: boolean; onClose: () => void; from?: string }) { const [username, setUsername] = useState(""); const [sent, setSent] = useState(false); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); useEffect(() => { if (open) { setUsername(""); setSent(false); setError(""); } }, [open ]); async function submit(e: React.FormEvent) { e.preventDefault(); if (busy) return; setBusy(true); setError(""); try { await api.recover(username.trim().toLowerCase()); setSent(true); } catch (err) { setError(err instanceof Error ? err.message : "Request failed"); } finally { setBusy(false); } } return ( {sent ? (

If @{username.trim().toLowerCase() || "…"} exists and has a recovery email, a reset link is on its way{from ? <> from {from} : ""} (valid 60 minutes, single use).

Check your inbox — and your spam folder, the message may have landed there.

Nothing arrives? Then no recovery email is set on your account and the reset is not possible — please contact the site administrator.

) : (

Enter your username. You'll receive a link that issues a fresh key (the old one stops working).

setUsername(e.target.value)} placeholder="alice" required /> {error &&

{error}

}
)}
); } /* ---------- File preview (pre-transcription review) ---------- */ export function FilePreviewModal({ preview, busy, onClose, onAttach, }: { preview: FilePreview | null; busy: boolean; onClose: () => void; onAttach: (text: string) => void; }) { const [text, setText] = useState(""); useEffect(() => { if (preview) setText(preview.text); }, [preview]); const isOcr = preview?.kind === "ocr"; return ( {busy ? (

{isOcr ? "Transcribing the image on the server (OCR)…" : "Uploading through platform tools…"}

) : (

{isOcr ? <>The image was transcribed to text on the server — the model receives this text, never the image. Fix any reading mistakes below, then attach. : <>The file was uploaded through platform tools. Its text content is attached to your message. Edit below if needed, then attach.}

{preview?.truncated && (

Transcription truncated to the server limit — the end is missing.

)}