import { ArchiveIcon, ArchiveX, ChevronDownIcon, InfoIcon, LoaderIcon, PlusIcon, RefreshCwIcon, Undo2Icon, XIcon,} from "lucide-react";import { useQueryClient } from "@tanstack/react-query";import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";import { PROVIDER_DISPLAY_NAMES, type ProviderKind, type ServerProvider, type ServerProviderModel, ThreadId,} from "@t3tools/contracts";import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings";import { normalizeModelSlug } from "@t3tools/shared/model";import { Equal } from "effect";import { APP_VERSION } from "../../branding";import { canCheckForUpdate, getDesktopUpdateButtonTooltip, getDesktopUpdateInstallConfirmationMessage, isDesktopUpdateButtonDisabled, resolveDesktopUpdateButtonAction,} from "../../components/desktopUpdate.logic";import { ProviderModelPicker } from "../chat/ProviderModelPicker";import { TraitsPicker } from "../chat/TraitsPicker";import { resolveAndPersistPreferredEditor } from "../../editorPreferences";import { isElectron } from "../../env";import { useTheme } from "../../hooks/useTheme";import { useSettings, useUpdateSettings } from "../../hooks/useSettings";import { useThreadActions } from "../../hooks/useThreadActions";import { setDesktopUpdateStateQueryData, useDesktopUpdateState,} from "../../lib/desktopUpdateReactQuery";import { MAX_CUSTOM_MODEL_LENGTH, getCustomModelOptionsByProvider, resolveAppModelSelectionState,} from "../../modelSelection";import { ensureNativeApi, readNativeApi } from "../../nativeApi";import { useStore } from "../../store";import { formatRelativeTime, formatRelativeTimeLabel } from "../../timestampFormat";import { cn } from "../../lib/utils";import { Button } from "../ui/button";import { Collapsible, CollapsibleContent } from "../ui/collapsible";import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty";import { Input } from "../ui/input";import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select";import { Switch } from "../ui/switch";import { toastManager } from "../ui/toast";import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";import { ProjectFavicon } from "../ProjectFavicon";import { useServerAvailableEditors, useServerKeybindingsConfigPath, useServerProviders,} from "../../rpc/serverState"; const THEME_OPTIONS = [ { value: "system", label: "System", }, { value: "light", label: "Light", }, { value: "dark", label: "Dark", },] as const; const TIMESTAMP_FORMAT_LABELS = { locale: "System default", "12-hour": "12-hour", "24-hour": "24-hour",} as const; type InstallProviderSettings = { provider: ProviderKind; title: string; binaryPlaceholder: string; binaryDescription: ReactNode; homePathKey?: "codexHomePath"; homePlaceholder?: string; homeDescription?: ReactNode;}; const PROVIDER_SETTINGS: readonly InstallProviderSettings[] = [ { provider: "codex", title: "Codex", binaryPlaceholder: "Codex binary path", binaryDescription: "Path to the Codex binary", homePathKey: "codexHomePath", homePlaceholder: "CODEX_HOME", homeDescription: "Optional custom Codex home and config directory.", }, { provider: "claudeAgent", title: "Claude", binaryPlaceholder: "Claude binary path", binaryDescription: "Path to the Claude binary", },] as const; const PROVIDER_STATUS_STYLES = { disabled: { dot: "bg-amber-400", }, error: { dot: "bg-destructive", }, ready: { dot: "bg-success", }, warning: { dot: "bg-warning", },} as const; function getProviderSummary(provider: ServerProvider | undefined) { if (!provider) { return { headline: "Checking provider status", detail: "Waiting for the server to report installation and authentication details.", }; } if (!provider.enabled) { return { headline: "Disabled", detail: provider.message ?? "This provider is installed but disabled for new sessions in T3 Code.", }; } if (!provider.installed) { return { headline: "Not found", detail: provider.message ?? "CLI not detected on PATH.", }; } if (provider.auth.status === "authenticated") { const authLabel = provider.auth.label ?? provider.auth.type; return { headline: authLabel ? `Authenticated · ${authLabel}` : "Authenticated", detail: provider.message ?? null, }; } if (provider.auth.status === "unauthenticated") { return { headline: "Not authenticated", detail: provider.message ?? null, }; } if (provider.status === "warning") { return { headline: "Needs attention", detail: provider.message ?? "The provider is installed, but the server could not fully verify it.", }; } if (provider.status === "error") { return { headline: "Unavailable", detail: provider.message ?? "The provider failed its startup checks.", }; } return { headline: "Available", detail: provider.message ?? "Installed and ready, but authentication could not be verified.", };} function getProviderVersionLabel(version: string | null | undefined) { if (!version) return null; return version.startsWith("v") ? version : `v${version}`;} function useRelativeTimeTick(intervalMs = 1_000) { const [tick, setTick] = useState(() => Date.now()); useEffect(() => { const id = setInterval(() => setTick(Date.now()), intervalMs); return () => clearInterval(id); }, [intervalMs]); return tick;} function ProviderLastChecked({ lastCheckedAt }: { lastCheckedAt: string | null }) { useRelativeTimeTick(); const lastCheckedRelative = lastCheckedAt ? formatRelativeTime(lastCheckedAt) : null; if (!lastCheckedRelative) { return null; } return ( <span className="text-[11px] text-muted-foreground/60"> {lastCheckedRelative.suffix ? ( <> Checked <span className="font-mono tabular-nums">{lastCheckedRelative.value}</span>{" "} {lastCheckedRelative.suffix} </> ) : ( <>Checked {lastCheckedRelative.value}</> )} </span> );} function SettingsSection({ title, icon, headerAction, children,}: { title: string; icon?: ReactNode; headerAction?: ReactNode; children: ReactNode;}) { return ( <section className="space-y-3"> <div className="flex items-center justify-between"> <h2 className="flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-[0.14em] text-muted-foreground"> {icon} {title} </h2> {headerAction} </div> <div className="relative overflow-hidden rounded-2xl border bg-card text-card-foreground shadow-xs/5 not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]"> {children} </div> </section> );} function SettingsRow({ title, description, status, resetAction, control, children,}: { title: ReactNode; description: string; status?: ReactNode; resetAction?: ReactNode; control?: ReactNode; children?: ReactNode;}) { return ( <div className="border-t border-border px-4 py-4 first:border-t-0 sm:px-5"> <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> <div className="min-w-0 flex-1 space-y-1"> <div className="flex min-h-5 items-center gap-1.5"> <h3 className="text-sm font-medium text-foreground">{title}</h3> <span className="inline-flex h-5 w-5 shrink-0 items-center justify-center"> {resetAction} </span> </div> <p className="text-xs text-muted-foreground">{description}</p> {status ? <div className="pt-1 text-[11px] text-muted-foreground">{status}</div> : null} </div> {control ? ( <div className="flex w-full shrink-0 items-center gap-2 sm:w-auto sm:justify-end"> {control} </div> ) : null} </div> {children} </div> );} function SettingResetButton({ label, onClick }: { label: string; onClick: () => void }) { return ( <Tooltip> <TooltipTrigger render={ <Button size="icon-xs" variant="ghost" aria-label={`Reset ${label} to default`} className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground" onClick={(event) => { event.stopPropagation(); onClick(); }} > <Undo2Icon className="size-3" /> </Button> } /> <TooltipPopup side="top">Reset to default</TooltipPopup> </Tooltip> );} function SettingsPageContainer({ children }: { children: ReactNode }) { return ( <div className="flex-1 overflow-y-auto p-6"> <div className="mx-auto flex w-full max-w-4xl flex-col gap-6">{children}</div> </div> );} function AboutVersionTitle() { return ( <span className="inline-flex items-center gap-2"> <span>Version</span> <code className="text-[11px] font-medium text-muted-foreground">{APP_VERSION}</code> </span> );} function AboutVersionSection() { const queryClient = useQueryClient(); const updateStateQuery = useDesktopUpdateState(); const updateState = updateStateQuery.data ?? null; const handleButtonClick = useCallback(() => { const bridge = window.desktopBridge; if (!bridge) return; const action = updateState ? resolveDesktopUpdateButtonAction(updateState) : "none"; if (action === "download") { void bridge .downloadUpdate() .then((result) => { setDesktopUpdateStateQueryData(queryClient, result.state); }) .catch((error: unknown) => { toastManager.add({ type: "error", title: "Could not download update", description: error instanceof Error ? error.message : "Download failed.", }); }); return; } if (action === "install") { const confirmed = window.confirm( getDesktopUpdateInstallConfirmationMessage( updateState ?? { availableVersion: null, downloadedVersion: null }, ), ); if (!confirmed) return; void bridge .installUpdate() .then((result) => { setDesktopUpdateStateQueryData(queryClient, result.state); }) .catch((error: unknown) => { toastManager.add({ type: "error", title: "Could not install update", description: error instanceof Error ? error.message : "Install failed.", }); }); return; } if (typeof bridge.checkForUpdate !== "function") return; void bridge .checkForUpdate() .then((result) => { setDesktopUpdateStateQueryData(queryClient, result.state); if (!result.checked) { toastManager.add({ type: "error", title: "Could not check for updates", description: result.state.message ?? "Automatic updates are not available in this build.", }); } }) .catch((error: unknown) => { toastManager.add({ type: "error", title: "Could not check for updates", description: error instanceof Error ? error.message : "Update check failed.", }); }); }, [queryClient, updateState]); const action = updateState ? resolveDesktopUpdateButtonAction(updateState) : "none"; const buttonTooltip = updateState ? getDesktopUpdateButtonTooltip(updateState) : null; const buttonDisabled = action === "none" ? !canCheckForUpdate(updateState) : isDesktopUpdateButtonDisabled(updateState); const actionLabel: Record<string, string> = { download: "Download", install: "Install" }; const statusLabel: Record<string, string> = { checking: "Checking…", downloading: "Downloading…", "up-to-date": "Up to Date", }; const buttonLabel = actionLabel[action] ?? statusLabel[updateState?.status ?? ""] ?? "Check for Updates"; const description = action === "download" || action === "install" ? "Update available." : "Current version of the application."; return ( <SettingsRow title={<AboutVersionTitle />} description={description} control={ <Tooltip> <TooltipTrigger render={ <Button size="xs" variant={action === "install" ? "default" : "outline"} disabled={buttonDisabled} onClick={handleButtonClick} > {buttonLabel} </Button> } /> {buttonTooltip ? <TooltipPopup>{buttonTooltip}</TooltipPopup> : null} </Tooltip> } /> );} export function useSettingsRestore(onRestored?: () => void) { const { theme, setTheme } = useTheme(); const settings = useSettings(); const { resetSettings } = useUpdateSettings(); const isGitWritingModelDirty = !Equal.equals( settings.textGenerationModelSelection ?? null, DEFAULT_UNIFIED_SETTINGS.textGenerationModelSelection ?? null, ); const areProviderSettingsDirty = PROVIDER_SETTINGS.some((providerSettings) => { const currentSettings = settings.providers[providerSettings.provider]; const defaultSettings = DEFAULT_UNIFIED_SETTINGS.providers[providerSettings.provider]; return !Equal.equals(currentSettings, defaultSettings); }); const changedSettingLabels = useMemo( () => [ ...(theme !== "system" ? ["Theme"] : []), ...(settings.timestampFormat !== DEFAULT_UNIFIED_SETTINGS.timestampFormat ? ["Time format"] : []), ...(settings.diffWordWrap !== DEFAULT_UNIFIED_SETTINGS.diffWordWrap ? ["Diff line wrapping"] : []), ...(settings.enableAssistantStreaming !== DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming ? ["Assistant output"] : []), ...(settings.defaultThreadEnvMode !== DEFAULT_UNIFIED_SETTINGS.defaultThreadEnvMode ? ["New thread mode"] : []), ...(settings.confirmThreadArchive !== DEFAULT_UNIFIED_SETTINGS.confirmThreadArchive ? ["Archive confirmation"] : []), ...(settings.confirmThreadDelete !== DEFAULT_UNIFIED_SETTINGS.confirmThreadDelete ? ["Delete confirmation"] : []), ...(isGitWritingModelDirty ? ["Git writing model"] : []), ...(areProviderSettingsDirty ? ["Providers"] : []), ], [ areProviderSettingsDirty, isGitWritingModelDirty, settings.confirmThreadArchive, settings.confirmThreadDelete, settings.defaultThreadEnvMode, settings.diffWordWrap, settings.enableAssistantStreaming, settings.timestampFormat, theme, ], ); const restoreDefaults = useCallback(async () => { if (changedSettingLabels.length === 0) return; const api = readNativeApi(); const confirmed = await (api ?? ensureNativeApi()).dialogs.confirm( ["Restore default settings?", `This will reset: ${changedSettingLabels.join(", ")}.`].join(Lines 1–500 of 1534. Load more
import { ArchiveIcon, ArchiveX, ChevronDownIcon, InfoIcon, LoaderIcon, PlusIcon, RefreshCwIcon, Undo2Icon, XIcon, } from "lucide-react"; import { useQueryClient } from "@tanstack/react-query"; import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { PROVIDER_DISPLAY_NAMES, type ProviderKind, type ServerProvider, type ServerProviderModel, ThreadId, } from "@t3tools/contracts"; import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; import { normalizeModelSlug } from "@t3tools/shared/model"; import { Equal } from "effect"; import { APP_VERSION } from "../../branding"; import { canCheckForUpdate, getDesktopUpdateButtonTooltip, getDesktopUpdateInstallConfirmationMessage, isDesktopUpdateButtonDisabled, resolveDesktopUpdateButtonAction, } from "../../components/desktopUpdate.logic"; import { ProviderModelPicker } from "../chat/ProviderModelPicker"; import { TraitsPicker } from "../chat/TraitsPicker"; import { resolveAndPersistPreferredEditor } from "../../editorPreferences"; import { isElectron } from "../../env"; import { useTheme } from "../../hooks/useTheme"; import { useSettings, useUpdateSettings } from "../../hooks/useSettings"; import { useThreadActions } from "../../hooks/useThreadActions"; import { setDesktopUpdateStateQueryData, useDesktopUpdateState, } from "../../lib/desktopUpdateReactQuery"; import { MAX_CUSTOM_MODEL_LENGTH, getCustomModelOptionsByProvider, resolveAppModelSelectionState, } from "../../modelSelection"; import { ensureNativeApi, readNativeApi } from "../../nativeApi"; import { useStore } from "../../store"; import { formatRelativeTime, formatRelativeTimeLabel } from "../../timestampFormat"; import { cn } from "../../lib/utils"; import { Button } from "../ui/button"; import { Collapsible, CollapsibleContent } from "../ui/collapsible"; import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty"; import { Input } from "../ui/input"; import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select"; import { Switch } from "../ui/switch"; import { toastManager } from "../ui/toast"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { ProjectFavicon } from "../ProjectFavicon"; import { useServerAvailableEditors, useServerKeybindingsConfigPath, useServerProviders, } from "../../rpc/serverState"; const THEME_OPTIONS = [ { value: "system", label: "System", }, { value: "light", label: "Light", }, { value: "dark", label: "Dark", }, ] as const; const TIMESTAMP_FORMAT_LABELS = { locale: "System default", "12-hour": "12-hour", "24-hour": "24-hour", } as const; type InstallProviderSettings = { provider: ProviderKind; title: string; binaryPlaceholder: string; binaryDescription: ReactNode; homePathKey?: "codexHomePath"; homePlaceholder?: string; homeDescription?: ReactNode; }; const PROVIDER_SETTINGS: readonly InstallProviderSettings[] = [ { provider: "codex", title: "Codex", binaryPlaceholder: "Codex binary path", binaryDescription: "Path to the Codex binary", homePathKey: "codexHomePath", homePlaceholder: "CODEX_HOME", homeDescription: "Optional custom Codex home and config directory.", }, { provider: "claudeAgent", title: "Claude", binaryPlaceholder: "Claude binary path", binaryDescription: "Path to the Claude binary", }, ] as const; const PROVIDER_STATUS_STYLES = { disabled: { dot: "bg-amber-400", }, error: { dot: "bg-destructive", }, ready: { dot: "bg-success", }, warning: { dot: "bg-warning", }, } as const; function getProviderSummary(provider: ServerProvider | undefined) { if (!provider) { return { headline: "Checking provider status", detail: "Waiting for the server to report installation and authentication details.", }; } if (!provider.enabled) { return { headline: "Disabled", detail: provider.message ?? "This provider is installed but disabled for new sessions in T3 Code.", }; } if (!provider.installed) { return { headline: "Not found", detail: provider.message ?? "CLI not detected on PATH.", }; } if (provider.auth.status === "authenticated") { const authLabel = provider.auth.label ?? provider.auth.type; return { headline: authLabel ? `Authenticated · ${authLabel}` : "Authenticated", detail: provider.message ?? null, }; } if (provider.auth.status === "unauthenticated") { return { headline: "Not authenticated", detail: provider.message ?? null, }; } if (provider.status === "warning") { return { headline: "Needs attention", detail: provider.message ?? "The provider is installed, but the server could not fully verify it.", }; } if (provider.status === "error") { return { headline: "Unavailable", detail: provider.message ?? "The provider failed its startup checks.", }; } return { headline: "Available", detail: provider.message ?? "Installed and ready, but authentication could not be verified.", }; } function getProviderVersionLabel(version: string | null | undefined) { if (!version) return null; return version.startsWith("v") ? version : `v${version}`; } function useRelativeTimeTick(intervalMs = 1_000) { const [tick, setTick] = useState(() => Date.now()); useEffect(() => { const id = setInterval(() => setTick(Date.now()), intervalMs); return () => clearInterval(id); }, [intervalMs]); return tick; } function ProviderLastChecked({ lastCheckedAt }: { lastCheckedAt: string | null }) { useRelativeTimeTick(); const lastCheckedRelative = lastCheckedAt ? formatRelativeTime(lastCheckedAt) : null; if (!lastCheckedRelative) { return null; } return ( <span className="text-[11px] text-muted-foreground/60"> {lastCheckedRelative.suffix ? ( <> Checked <span className="font-mono tabular-nums">{lastCheckedRelative.value}</span>{" "} {lastCheckedRelative.suffix} </> ) : ( <>Checked {lastCheckedRelative.value}</> )} </span> ); } function SettingsSection({ title, icon, headerAction, children, }: { title: string; icon?: ReactNode; headerAction?: ReactNode; children: ReactNode; }) { return ( <section className="space-y-3"> <div className="flex items-center justify-between"> <h2 className="flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-[0.14em] text-muted-foreground"> {icon} {title} </h2> {headerAction} </div> <div className="relative overflow-hidden rounded-2xl border bg-card text-card-foreground shadow-xs/5 not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]"> {children} </div> </section> ); } function SettingsRow({ title, description, status, resetAction, control, children, }: { title: ReactNode; description: string; status?: ReactNode; resetAction?: ReactNode; control?: ReactNode; children?: ReactNode; }) { return ( <div className="border-t border-border px-4 py-4 first:border-t-0 sm:px-5"> <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> <div className="min-w-0 flex-1 space-y-1"> <div className="flex min-h-5 items-center gap-1.5"> <h3 className="text-sm font-medium text-foreground">{title}</h3> <span className="inline-flex h-5 w-5 shrink-0 items-center justify-center"> {resetAction} </span> </div> <p className="text-xs text-muted-foreground">{description}</p> {status ? <div className="pt-1 text-[11px] text-muted-foreground">{status}</div> : null} </div> {control ? ( <div className="flex w-full shrink-0 items-center gap-2 sm:w-auto sm:justify-end"> {control} </div> ) : null} </div> {children} </div> ); } function SettingResetButton({ label, onClick }: { label: string; onClick: () => void }) { return ( <Tooltip> <TooltipTrigger render={ <Button size="icon-xs" variant="ghost" aria-label={`Reset ${label} to default`} className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground" onClick={(event) => { event.stopPropagation(); onClick(); }} > <Undo2Icon className="size-3" /> </Button> } /> <TooltipPopup side="top">Reset to default</TooltipPopup> </Tooltip> ); } function SettingsPageContainer({ children }: { children: ReactNode }) { return ( <div className="flex-1 overflow-y-auto p-6"> <div className="mx-auto flex w-full max-w-4xl flex-col gap-6">{children}</div> </div> ); } function AboutVersionTitle() { return ( <span className="inline-flex items-center gap-2"> <span>Version</span> <code className="text-[11px] font-medium text-muted-foreground">{APP_VERSION}</code> </span> ); } function AboutVersionSection() { const queryClient = useQueryClient(); const updateStateQuery = useDesktopUpdateState(); const updateState = updateStateQuery.data ?? null; const handleButtonClick = useCallback(() => { const bridge = window.desktopBridge; if (!bridge) return; const action = updateState ? resolveDesktopUpdateButtonAction(updateState) : "none"; if (action === "download") { void bridge .downloadUpdate() .then((result) => { setDesktopUpdateStateQueryData(queryClient, result.state); }) .catch((error: unknown) => { toastManager.add({ type: "error", title: "Could not download update", description: error instanceof Error ? error.message : "Download failed.", }); }); return; } if (action === "install") { const confirmed = window.confirm( getDesktopUpdateInstallConfirmationMessage( updateState ?? { availableVersion: null, downloadedVersion: null }, ), ); if (!confirmed) return; void bridge .installUpdate() .then((result) => { setDesktopUpdateStateQueryData(queryClient, result.state); }) .catch((error: unknown) => { toastManager.add({ type: "error", title: "Could not install update", description: error instanceof Error ? error.message : "Install failed.", }); }); return; } if (typeof bridge.checkForUpdate !== "function") return; void bridge .checkForUpdate() .then((result) => { setDesktopUpdateStateQueryData(queryClient, result.state); if (!result.checked) { toastManager.add({ type: "error", title: "Could not check for updates", description: result.state.message ?? "Automatic updates are not available in this build.", }); } }) .catch((error: unknown) => { toastManager.add({ type: "error", title: "Could not check for updates", description: error instanceof Error ? error.message : "Update check failed.", }); }); }, [queryClient, updateState]); const action = updateState ? resolveDesktopUpdateButtonAction(updateState) : "none"; const buttonTooltip = updateState ? getDesktopUpdateButtonTooltip(updateState) : null; const buttonDisabled = action === "none" ? !canCheckForUpdate(updateState) : isDesktopUpdateButtonDisabled(updateState); const actionLabel: Record<string, string> = { download: "Download", install: "Install" }; const statusLabel: Record<string, string> = { checking: "Checking…", downloading: "Downloading…", "up-to-date": "Up to Date", }; const buttonLabel = actionLabel[action] ?? statusLabel[updateState?.status ?? ""] ?? "Check for Updates"; const description = action === "download" || action === "install" ? "Update available." : "Current version of the application."; return ( <SettingsRow title={<AboutVersionTitle />} description={description} control={ <Tooltip> <TooltipTrigger render={ <Button size="xs" variant={action === "install" ? "default" : "outline"} disabled={buttonDisabled} onClick={handleButtonClick} > {buttonLabel} </Button> } /> {buttonTooltip ? <TooltipPopup>{buttonTooltip}</TooltipPopup> : null} </Tooltip> } /> ); } export function useSettingsRestore(onRestored?: () => void) { const { theme, setTheme } = useTheme(); const settings = useSettings(); const { resetSettings } = useUpdateSettings(); const isGitWritingModelDirty = !Equal.equals( settings.textGenerationModelSelection ?? null, DEFAULT_UNIFIED_SETTINGS.textGenerationModelSelection ?? null, ); const areProviderSettingsDirty = PROVIDER_SETTINGS.some((providerSettings) => { const currentSettings = settings.providers[providerSettings.provider]; const defaultSettings = DEFAULT_UNIFIED_SETTINGS.providers[providerSettings.provider]; return !Equal.equals(currentSettings, defaultSettings); }); const changedSettingLabels = useMemo( () => [ ...(theme !== "system" ? ["Theme"] : []), ...(settings.timestampFormat !== DEFAULT_UNIFIED_SETTINGS.timestampFormat ? ["Time format"] : []), ...(settings.diffWordWrap !== DEFAULT_UNIFIED_SETTINGS.diffWordWrap ? ["Diff line wrapping"] : []), ...(settings.enableAssistantStreaming !== DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming ? ["Assistant output"] : []), ...(settings.defaultThreadEnvMode !== DEFAULT_UNIFIED_SETTINGS.defaultThreadEnvMode ? ["New thread mode"] : []), ...(settings.confirmThreadArchive !== DEFAULT_UNIFIED_SETTINGS.confirmThreadArchive ? ["Archive confirmation"] : []), ...(settings.confirmThreadDelete !== DEFAULT_UNIFIED_SETTINGS.confirmThreadDelete ? ["Delete confirmation"] : []), ...(isGitWritingModelDirty ? ["Git writing model"] : []), ...(areProviderSettingsDirty ? ["Providers"] : []), ], [ areProviderSettingsDirty, isGitWritingModelDirty, settings.confirmThreadArchive, settings.confirmThreadDelete, settings.defaultThreadEnvMode, settings.diffWordWrap, settings.enableAssistantStreaming, settings.timestampFormat, theme, ], ); const restoreDefaults = useCallback(async () => { if (changedSettingLabels.length === 0) return; const api = readNativeApi(); const confirmed = await (api ?? ensureNativeApi()).dialogs.confirm( ["Restore default settings?", `This will reset: ${changedSettingLabels.join(", ")}.`].join(