import { useEffect, useRef, useState } from "react"; import { Archive, ArchiveRestore, Eye, Fingerprint, Globe, FileText, ImagePlus, KeyRound, Link2, Mail, Palette, Pencil, ScanText, Search, Settings2, ShieldCheck, Sparkles, Tag, Trash2, Unplug, User as UserIcon, 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 { useT, type StringKey } from "../lib/i18n.ts"; import { Avatar, bumpCdnVersion, Button, ConfirmDialog, CopyButton, Field, Input, LangPicker, 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 { t } = useT(); 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 : t("common.saveFailed")); } finally { setBusy(false); } } return (
setTitle(e.target.value)} maxLength={120} required />
setTopic(e.target.value)} maxLength={120} placeholder={t("dlg.noTopic")} />
{error &&

{error}

}
); } /* ---------- Key recovery ---------- */ export function RecoverDialog({ open, onClose, from }: { open: boolean; onClose: () => void; from?: string }) { const { t } = useT(); 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 : t("dlg.reqFailed")); } finally { setBusy(false); } } return ( {sent ? (

{t("dlg.recoverSent", { user: username.trim().toLowerCase() || "…", fromPart: from ? t("dlg.recoverSentFrom", { from }) : "", })}

{t("dlg.spamNote")}

{t("dlg.noMailNote")}

) : (

{t("dlg.recoverIntro")}

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 { t } = useT(); const [text, setText] = useState(""); useEffect(() => { if (preview) setText(preview.text); }, [preview]); const isOcr = preview?.kind === "ocr"; return ( {busy ? (

{isOcr ? t("dlg.transcribing") : t("dlg.uploadingTools")}

) : (

{isOcr ? t("dlg.ocrExpl") : t("dlg.textExpl")}

{preview?.truncated && (

{t("dlg.truncWarn")}

)}