import { ChevronLeft, Loader2 } from "lucide-react"; import { SkillsCatalogSettings } from "@/components/settings/SkillsCatalogSettings"; import { ImageGenerationSettings } from "@/components/settings/capabilities/ImageGenerationSettings"; import { AdvancedSettings } from "@/components/settings/capabilities/SecuritySettings"; import { TranscriptionSettings } from "@/components/settings/capabilities/TranscriptionSettings"; import { WebSettings } from "@/components/settings/capabilities/WebSettings"; import { ModelPresetDeleteDialog, ModelsSettings, } from "@/components/settings/models/ModelsSettings"; import { ProviderOAuthLoginDialog, ProvidersSettings, providerFormFromRow, } from "@/components/settings/models/ProviderSettings"; import { AppearanceSettings, OverviewSettings } from "@/components/settings/overview/OverviewSettings"; import { SettingsSidebar, standaloneSectionTitle } from "@/components/settings/SettingsSidebar"; import { NanobotFeatureInstallDialog, SettingsGroup, SettingsRow, } from "@/components/settings/shared/SettingsControls"; import { AppsCatalogSettings } from "@/components/settings/system/AppsSettings"; import { AutomationDeleteDialog, AutomationEditDialog, AutomationsSettings, } from "@/components/settings/system/AutomationsSettings"; import { ChannelsSettings } from "@/components/settings/system/ChannelsSettings"; import { RuntimeSettings } from "@/components/settings/system/RuntimeSettings"; import type { SettingsController } from "@/components/settings/useSettingsController"; import type { SkillSummary } from "@/lib/types"; import { cn } from "@/lib/utils"; interface SettingsPageProps { controller: SettingsController; theme: "light" | "dark"; showSidebar: boolean; onToggleTheme: () => void; onBackToChat: () => void; skills: SkillSummary[]; onLogout?: () => void; isRestarting: boolean; hostChromeInset: boolean; } export function SettingsPage({ controller, theme, showSidebar, onToggleTheme, onBackToChat, skills, onLogout, isRestarting, hostChromeInset, }: SettingsPageProps) { const { activeSection, apiService, apiServiceAction, apiServiceError, apiServiceLoading, appsKindFilter, appsQuery, automationAction, automationPendingDelete, automationPendingEdit, automations, automationsError, automationsFilter, automationsLoading, automationsQuery, automationsSort, beginModelPresetCreation, cancelModelPresetCreation, changeModelCallOrder, channelsQuery, cliApps, cliAppsAction, cliAppsError, cliAppsFocusName, cliAppsLoading, cliAppsMessage, closeProviderOAuthFlow, completeProviderOAuthResponse, createCustomProvider, customMcpForm, editingProviderKeys, error, expandedProvider, featureCatalog, form, handleApiServiceAction, handleAutomationAction, handleAutomationEdit, handleCliAppAction, handleDeleteModelConfiguration, handleImportMcpConfig, handleMcpOAuthCancel, handleMcpOAuthComplete, handleMcpOAuthConnect, handleMcpOAuthOpen, handleMcpPresetAction, handleMcpToolsChange, handleMigrateModelConfigurations, handleNanobotFeatureAction, handleSaveCustomMcp, handleToggleProvider, handleWebSearchProviderChange, hasPendingRestart, hostEngineApplying, imageGenerationDirty, imageGenerationForm, imageGenerationSaving, installCapabilities, loading, localPrefs, mcpConfigImport, mcpError, mcpFieldValues, mcpMessage, mcpOAuthCallbackError, mcpOAuthCallbackUrl, mcpOAuthCompleting, mcpOAuthFlow, mcpOAuthPopupBlocked, mcpPresetAction, mcpPresets, mcpPresetsLoading, modelCallOrder, modelCallOrderSaving, modelConfigurationSaving, modelDirty, modelMigrationSaving, modelPresetBeforeCreateRef, modelPresetCreating, modelPresetEditingName, modelPresetNameError, modelPresetPendingDelete, nanobotFeatureAction, nanobotFeatureConfirm, nanobotFeatures, nanobotFeaturesError, nanobotFeaturesLoading, networkSafetyDirty, networkSafetyForm, networkSafetySaving, pendingRestartSections, providerForms, providerOAuthCompleting, providerOAuthDialogError, providerOAuthFlow, providerOAuthResponse, providerSaving, remoteBrowserAccess, resetWebSearchDraft, restartViaSettingsSurface, runProviderOAuth, saveImageGenerationSettings, saveModelSettings, saveNetworkSafetySettings, saveProvider, saveTranscriptionSettings, saveWebSearch, saving, selectSection, setAppsKindFilter, setAppsQuery, setAutomationPendingDelete, setAutomationPendingEdit, setAutomationsFilter, setAutomationsQuery, setAutomationsSort, setChannelsQuery, setCliAppsError, setCliAppsMessage, setCustomMcpForm, setForm, setImageGenerationForm, setLocalPrefs, setMcpConfigImport, setMcpError, setMcpFieldValues, setMcpMessage, setMcpOAuthCallbackError, setMcpOAuthCallbackUrl, setModelPresetCreating, setModelPresetEditingName, setModelPresetNameError, setModelPresetPendingDelete, setNanobotFeatureConfirm, setNanobotFeatures, setNanobotFeaturesError, setNetworkSafetyForm, setProviderForms, setProviderOAuthDialogError, setProviderOAuthResponse, setTranscriptionForm, setWebSearchForm, setWebSearchKeyEditing, setWebSearchKeyVisible, settings, t, toggleProviderKeyEditing, toggleProviderKeyVisibility, token, transcriptionDirty, transcriptionForm, transcriptionSaving, visibleProviderKeys, webSearchForm, webSearchKeyEditing, webSearchKeyVisible, webSearchSaving, } = controller; const renderSection = () => { if (!settings) return null; switch (activeSection) { case "overview": return ( ); case "appearance": return ( ); case "models": return (
runProviderOAuth(provider, "login")} onSave={saveModelSettings} onMigrate={handleMigrateModelConfigurations} onBeginCreate={beginModelPresetCreation} onCancelCreate={cancelModelPresetCreation} onClearPresetNameError={() => setModelPresetNameError(null)} onSelectConfiguration={(name) => { setModelPresetCreating(false); setModelPresetEditingName(name); setModelPresetNameError(null); modelPresetBeforeCreateRef.current = null; }} onDeleteConfiguration={setModelPresetPendingDelete} /> setProviderForms((prev) => ({ ...prev, [provider]: { ...(prev[provider] ?? providerFormFromRow( settings.providers.find((row) => row.name === provider) ?? { name: provider, label: provider, configured: false, }, )), ...value, }, })) } onSaveProvider={saveProvider} onCreateCustomProvider={createCustomProvider} onProviderOAuthLogin={(provider) => runProviderOAuth(provider, "login")} onProviderOAuthLogout={(provider) => runProviderOAuth(provider, "logout")} imageProviderRestartPending={pendingRestartSections.image || pendingRestartSections.runtime} onRestart={restartViaSettingsSurface} isRestarting={isRestarting || hostEngineApplying} />
); case "image": return ( selectSection("models")} showBrandLogos={localPrefs.brandLogos} onRestart={restartViaSettingsSurface} isRestarting={isRestarting || hostEngineApplying} requiresRestartPending={pendingRestartSections.image} /> ); case "voice": return ( selectSection("models")} showBrandLogos={localPrefs.brandLogos} onRestart={restartViaSettingsSurface} isRestarting={isRestarting || hostEngineApplying} requiresRestartPending={pendingRestartSections.browser} /> ); case "browser": return ( setWebSearchKeyVisible((visible) => !visible)} onToggleKeyEditing={() => { setWebSearchKeyEditing((editing) => !editing); setWebSearchKeyVisible(false); setWebSearchForm((prev) => ({ ...prev, apiKey: "" })); }} onReset={resetWebSearchDraft} onSave={saveWebSearch} showBrandLogos={localPrefs.brandLogos} onRestart={restartViaSettingsSurface} isRestarting={isRestarting || hostEngineApplying} requiresRestartPending={pendingRestartSections.browser} olostepFeature={featureCatalog.find((feature) => feature.name === "olostep")} olostepInstalling={nanobotFeatureAction === "enable:olostep"} capabilityError={nanobotFeaturesError} /> ); case "channels": return ( { setNanobotFeaturesError(null); }} onRestart={restartViaSettingsSurface} isRestarting={isRestarting || hostEngineApplying} /> ); case "apps": return ( void handleMcpOAuthCancel()} onMcpOAuthOpen={handleMcpOAuthOpen} onMcpOAuthCallbackUrlChange={(value) => { setMcpOAuthCallbackUrl(value); setMcpOAuthCallbackError(null); }} onMcpOAuthComplete={() => void handleMcpOAuthComplete()} onDismissStatus={() => { setCliAppsMessage(null); setCliAppsError(null); setMcpMessage(null); setMcpError(null); }} onBackToChat={onBackToChat} onMcpFieldChange={(presetName, fieldName, value) => { setMcpFieldValues((prev) => ({ ...prev, [presetName]: { ...(prev[presetName] ?? {}), [fieldName]: value, }, })); }} onCustomMcpFormChange={setCustomMcpForm} onMcpConfigImportChange={setMcpConfigImport} onSaveCustomMcp={handleSaveCustomMcp} onImportMcpConfig={handleImportMcpConfig} onMcpToolsChange={handleMcpToolsChange} onRestart={restartViaSettingsSurface} isRestarting={isRestarting || hostEngineApplying} /> ); case "automations": return ( ); case "skills": return ; case "runtime": return ( feature.name === "langfuse")} capabilitiesLoading={nanobotFeaturesLoading} capabilityAction={nanobotFeatureAction} capabilityError={nanobotFeaturesError} onApiServiceAction={handleApiServiceAction} onInstallCapability={(name) => void installCapabilities([name])} /> ); case "advanced": return ( ); default: return null; } }; return (
{showSidebar ? ( ) : null} { if (!open) setModelPresetPendingDelete(null); }} onConfirm={handleDeleteModelConfiguration} /> provider.name === providerOAuthFlow.provider) ?.label ?? providerOAuthFlow.provider : "" } authorizationResponse={providerOAuthResponse} completing={providerOAuthCompleting} error={providerOAuthDialogError} remoteBrowserAccess={remoteBrowserAccess} onAuthorizationResponseChange={(value) => { setProviderOAuthResponse(value); setProviderOAuthDialogError(null); }} onOpenAuthorization={() => { if (!providerOAuthFlow) return; const opened = window.open( providerOAuthFlow.authorization_url, "_blank", "noopener,noreferrer", ); if (opened) opened.opener = null; }} onComplete={() => void completeProviderOAuthResponse()} onClose={closeProviderOAuthFlow} /> { if (!open) setNanobotFeatureConfirm(null); }} onConfirm={(feature) => handleNanobotFeatureAction("enable", feature.name, true)} /> { if (!open) setAutomationPendingDelete(null); }} onConfirm={(job) => handleAutomationAction("delete", job)} /> { if (!open) setAutomationPendingEdit(null); }} onSave={handleAutomationEdit} />
{!showSidebar ? (

{t(`settings.nav.${activeSection}`, { defaultValue: standaloneSectionTitle(activeSection), })}

) : null} {loading ? (
{t("settings.status.loading")}
) : error && !settings ? ( {error} ) : settings ? (
{error ? (
{error}
) : null} {renderSection()}
) : null}
); }