1534 lines · 55.7 KB · TypeScript
RawDownload
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.