import { Trans, useLingui } from "@lingui/react/macro"; import type { AvatarStyle } from "@rakazo/contracts"; import { BotAvatar, Button, Field, FieldLabel, Input, Toggle } from "@rakazo/ui-web"; import { ChevronDown } from "lucide-react"; import { type KeyboardEvent as ReactKeyboardEvent, type RefObject, useEffect, useId, useRef, useState, } from "react"; import { Link } from "react-router-dom"; import { ApprovalRulesSettings } from "../components/ApprovalRulesSettings"; import { SuccessPop } from "../components/ai/primitives"; import { ComputersUnavailableHint } from "../components/ComputersUnavailableHint"; import { DesktopUpdateSection } from "../components/DesktopUpdates"; import { SoftwareUpdateSection } from "../components/SoftwareUpdateSection"; import { authClient } from "../lib/auth"; import { getActiveUiLocale, setUiLocale } from "../lib/i18n"; import { type AppearancePreference, getUiAppearancePreference, setUiAppearance, } from "../lib/ui-appearance"; import { UI_LOCALE_LABELS, UI_LOCALES, type UiLocale } from "../lib/ui-locale"; export type SettingsGeneralProps = { email?: string | null; name: string; avatarStyle: AvatarStyle; onAvatarStyleChange: (style: AvatarStyle) => Promise; messagingEnabled?: boolean; onOpenMessaging?: () => void; isDeploymentOwner?: boolean; }; export function GeneralSettingsPanels({ email, name, avatarStyle, onAvatarStyleChange, messagingEnabled = false, onOpenMessaging, isDeploymentOwner = false, }: SettingsGeneralProps) { const { t } = useLingui(); const [locale, setLocale] = useState(() => getActiveUiLocale()); const localeRequestRef = useRef(0); const [appearance, setAppearance] = useState(() => getUiAppearancePreference(), ); const [avatarPending, setAvatarPending] = useState(false); const [avatarError, setAvatarError] = useState(null); function chooseLocale(next: UiLocale) { if (next === locale) return; const requestId = ++localeRequestRef.current; setLocale(next); void setUiLocale(next).then((activated) => { if (requestId !== localeRequestRef.current) return; setLocale(activated); }); } async function chooseAvatarStyle(next: AvatarStyle) { if (avatarPending || next === avatarStyle) return; setAvatarPending(true); setAvatarError(null); try { await onAvatarStyleChange(next); } catch { setAvatarError(t`Couldn't update avatars`); } finally { setAvatarPending(false); } } return (

Account

{name}

{email ?

{email}

: null}
{messagingEnabled && onOpenMessaging ? (

Messaging

Chat apps, group channels, and agent connections.

) : null}

Appearance

{ setAppearance(next); setUiAppearance(next); }} />

Language

Avatars

{(["robot", "organic"] as const).map((style) => ( void chooseAvatarStyle(style)} className="h-auto justify-start gap-3 px-3.5 py-3 text-ui-body font-normal" > {style === "robot" ? Robot : Organic} ))}
{avatarError ? (

{avatarError}

) : null}
{isDeploymentOwner ? ( ) : null}
Advanced Optional controls most people never need
); } export function UsageSettingsPanel({ usage, panelRef, }: { usage?: { runs: number; inputTokens: number; outputTokens: number } | null; panelRef?: RefObject; }) { return (

Usage

{usage ? (

{usage.runs} runs · {usage.inputTokens + usage.outputTokens} tokens

) : null}

Model spend uses your provider keys.

); } export function ComputerSettingsPanel() { return (

Computers

); } export function UpdatesSettingsPanel({ isDeploymentOwner = false, }: { isDeploymentOwner?: boolean; }) { return (
); } function ChangePasswordSection({ email }: { email?: string | null }) { const { t } = useLingui(); const [currentPassword, setCurrentPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); const [confirmation, setConfirmation] = useState(""); const [pending, setPending] = useState(false); const [saved, setSaved] = useState(false); const [error, setError] = useState(null); async function changePassword() { if (pending) return; if (newPassword !== confirmation) { setError(t`Passwords do not match`); return; } setPending(true); setSaved(false); setError(null); try { const result = await authClient.changePassword({ currentPassword, newPassword, revokeOtherSessions: true, }); if (result.error) { setError(result.error.message ?? t`Could not change password`); return; } setCurrentPassword(""); setNewPassword(""); setConfirmation(""); setSaved(true); } catch { setError(t`Could not reach the server`); } finally { setPending(false); } } return (

Password

{error ? (

{error}

) : null}
{saved ? : null}
); } function SettingsPasswordInput({ label, autoComplete, value, onChange, }: { label: string; autoComplete: "current-password" | "new-password"; value: string; onChange: (value: string) => void; }) { const id = useId(); return ( {label} onChange(event.target.value)} /> ); } function AppearancePicker({ value, onChange, }: { value: AppearancePreference; onChange: (next: AppearancePreference) => void; }) { const { t } = useLingui(); const options: { value: AppearancePreference; label: string }[] = [ { value: "system", label: t`System` }, { value: "light", label: t`Light` }, { value: "dark", label: t`Dark` }, ]; return (
{options.map((option) => ( onChange(option.value)} className="text-ui-body-sm aria-pressed:bg-background aria-pressed:shadow-sm" > {option.label} ))}
); } function UiLocalePicker({ value, onChange, }: { value: UiLocale; onChange: (locale: UiLocale) => void; }) { const { t } = useLingui(); const rootRef = useRef(null); const triggerRef = useRef(null); const optionRefs = useRef>([]); const listboxId = useId(); const selectedIndex = Math.max(0, UI_LOCALES.indexOf(value)); const [open, setOpen] = useState(false); const [highlightedIndex, setHighlightedIndex] = useState(selectedIndex); useEffect(() => { setHighlightedIndex(selectedIndex); setOpen(false); }, [selectedIndex, value]); useEffect(() => { if (!open) return; optionRefs.current[highlightedIndex]?.focus(); }, [highlightedIndex, open]); useEffect(() => { if (!open) return; function closeOnOutsidePointer(event: PointerEvent) { if (!rootRef.current?.contains(event.target as Node)) setOpen(false); } document.addEventListener("pointerdown", closeOnOutsidePointer); return () => document.removeEventListener("pointerdown", closeOnOutsidePointer); }, [open]); function choose(index: number) { const next = UI_LOCALES[index]; if (!next) return; onChange(next); setOpen(false); triggerRef.current?.focus(); } function moveHighlight(index: number) { setHighlightedIndex((index + UI_LOCALES.length) % UI_LOCALES.length); } function onTriggerKeyDown(event: ReactKeyboardEvent) { if (event.key === "Escape" && open) { event.preventDefault(); event.stopPropagation(); setOpen(false); return; } if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") { event.preventDefault(); setOpen(true); return; } if (event.key === "ArrowUp") { event.preventDefault(); setOpen(true); setHighlightedIndex(UI_LOCALES.length - 1); } } function onOptionKeyDown(event: ReactKeyboardEvent, index: number) { if (event.key === "ArrowDown") { event.preventDefault(); moveHighlight(index + 1); } else if (event.key === "ArrowUp") { event.preventDefault(); moveHighlight(index - 1); } else if (event.key === "Home") { event.preventDefault(); setHighlightedIndex(0); } else if (event.key === "End") { event.preventDefault(); setHighlightedIndex(UI_LOCALES.length - 1); } else if (event.key === "Enter" || event.key === " ") { event.preventDefault(); choose(index); } else if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); setOpen(false); triggerRef.current?.focus(); } } return (
{open ? (
{UI_LOCALES.map((code, index) => ( ))}
) : null}
); }