| author | |
| committer | |
| log | 4f584da0f84666bda0cd399721115b0001cc0308 |
| tree | c9eeac23b5b3cd9d7de9d71b6bf2f8cce16098df |
| parent | 362f12792254335bfe2a015787d7cbbaa214654e |
| signature | Signed by PGP key B5690EEEBB952194 |
4 files changed, 107 insertions(+), 61 deletions(-)
apps/web/src/components/settings/SettingsPanels.tsx+72-61| ... | ... | @@ -524,58 +524,23 @@ export function useSettingsRestore(onRestored?: () => void) { |
| 524 | 524 | }; |
| 525 | 525 | } |
| 526 | 526 | |
| 527 | export function GeneralSettingsPanel() { | |
| 527 | export function AppearanceSettingsPanel() { | |
| 528 | 528 | const { theme, setTheme } = useTheme(); |
| 529 | 529 | const settings = usePrimarySettings(); |
| 530 | 530 | const updateSettings = useUpdatePrimarySettings(); |
| 531 | 531 | const environmentStageLabel = useEnvironmentStageLabel(); |
| 532 | 532 | const showEnvironmentIdentification = |
| 533 | 533 | resolveEnvironmentIdentificationPillLabel(environmentStageLabel) !== null; |
| 534 | const lastEnabledProjectGroupingMode = useRef<SidebarProjectGroupingMode>( | |
| 535 | readLastEnabledProjectGroupingMode(), | |
| 536 | ); | |
| 537 | const observability = useAtomValue(primaryServerObservabilityAtom); | |
| 538 | const serverProviders = useAtomValue(primaryServerProvidersAtom); | |
| 539 | 534 | const glassOpacityRatio = |
| 540 | 535 | (settings.glassOpacity - MIN_GLASS_OPACITY) / (MAX_GLASS_OPACITY - MIN_GLASS_OPACITY); |
| 541 | 536 | const glassOpacitySliderStyle = { |
| 542 | 537 | "--glass-slider-progress": `${glassOpacityRatio * 100}%`, |
| 543 | 538 | "--glass-slider-fill-offset": `${0.5 - glassOpacityRatio}rem`, |
| 544 | 539 | } as CSSProperties; |
| 545 | const diagnosticsDescription = formatDiagnosticsDescription({ | |
| 546 | localTracingEnabled: observability?.localTracingEnabled ?? false, | |
| 547 | otlpTracesEnabled: observability?.otlpTracesEnabled ?? false, | |
| 548 | otlpTracesUrl: observability?.otlpTracesUrl, | |
| 549 | otlpMetricsEnabled: observability?.otlpMetricsEnabled ?? false, | |
| 550 | otlpMetricsUrl: observability?.otlpMetricsUrl, | |
| 551 | }); | |
| 552 | ||
| 553 | const textGenerationModelSelection = resolveAppModelSelectionState(settings, serverProviders); | |
| 554 | const textGenInstanceId = textGenerationModelSelection.instanceId; | |
| 555 | const textGenModel = textGenerationModelSelection.model; | |
| 556 | const textGenModelOptions = textGenerationModelSelection.options; | |
| 557 | const textGenerationModelInstanceEntries = sortProviderInstanceEntries( | |
| 558 | applyProviderInstanceSettings(deriveProviderInstanceEntries(serverProviders), settings), | |
| 559 | ); | |
| 560 | const textGenInstanceEntry = textGenerationModelInstanceEntries.find( | |
| 561 | (entry) => entry.instanceId === textGenInstanceId, | |
| 562 | ); | |
| 563 | const textGenProvider: ProviderDriverKind = | |
| 564 | textGenInstanceEntry?.driverKind ?? DEFAULT_DRIVER_KIND; | |
| 565 | const textGenerationModelOptionsByInstance = getCustomModelOptionsByInstance( | |
| 566 | settings, | |
| 567 | serverProviders, | |
| 568 | textGenInstanceId, | |
| 569 | textGenModel, | |
| 570 | ); | |
| 571 | const isTextGenerationModelDirty = !Equal.equals( | |
| 572 | settings.textGenerationModelSelection ?? null, | |
| 573 | DEFAULT_UNIFIED_SETTINGS.textGenerationModelSelection ?? null, | |
| 574 | ); | |
| 575 | 540 | |
| 576 | 541 | return ( |
| 577 | 542 | <SettingsPageContainer> |
| 578 | <SettingsSection title="General"> | |
| 543 | <SettingsSection title="Appearance"> | |
| 579 | 544 | <SettingsRow |
| 580 | 545 | title="Theme" |
| 581 | 546 | description="Choose how T3 Code looks across the app." |
| ... | ... | @@ -697,6 +662,76 @@ export function GeneralSettingsPanel() { |
| 697 | 662 | /> |
| 698 | 663 | ) : null} |
| 699 | 664 | |
| 665 | <SettingsRow | |
| 666 | title="Word wrap" | |
| 667 | description="Wrap long lines in code blocks, tables, diffs, and file previews by default." | |
| 668 | resetAction={ | |
| 669 | settings.wordWrap !== DEFAULT_UNIFIED_SETTINGS.wordWrap ? ( | |
| 670 | <SettingResetButton | |
| 671 | label="word wrapping" | |
| 672 | onClick={() => | |
| 673 | updateSettings({ | |
| 674 | wordWrap: DEFAULT_UNIFIED_SETTINGS.wordWrap, | |
| 675 | }) | |
| 676 | } | |
| 677 | /> | |
| 678 | ) : null | |
| 679 | } | |
| 680 | control={ | |
| 681 | <Switch | |
| 682 | checked={settings.wordWrap} | |
| 683 | onCheckedChange={(checked) => updateSettings({ wordWrap: Boolean(checked) })} | |
| 684 | aria-label="Wrap code, tables, diffs, and file previews by default" | |
| 685 | /> | |
| 686 | } | |
| 687 | /> | |
| 688 | </SettingsSection> | |
| 689 | </SettingsPageContainer> | |
| 690 | ); | |
| 691 | } | |
| 692 | ||
| 693 | export function GeneralSettingsPanel() { | |
| 694 | const settings = usePrimarySettings(); | |
| 695 | const updateSettings = useUpdatePrimarySettings(); | |
| 696 | const lastEnabledProjectGroupingMode = useRef<SidebarProjectGroupingMode>( | |
| 697 | readLastEnabledProjectGroupingMode(), | |
| 698 | ); | |
| 699 | const observability = useAtomValue(primaryServerObservabilityAtom); | |
| 700 | const serverProviders = useAtomValue(primaryServerProvidersAtom); | |
| 701 | const diagnosticsDescription = formatDiagnosticsDescription({ | |
| 702 | localTracingEnabled: observability?.localTracingEnabled ?? false, | |
| 703 | otlpTracesEnabled: observability?.otlpTracesEnabled ?? false, | |
| 704 | otlpTracesUrl: observability?.otlpTracesUrl, | |
| 705 | otlpMetricsEnabled: observability?.otlpMetricsEnabled ?? false, | |
| 706 | otlpMetricsUrl: observability?.otlpMetricsUrl, | |
| 707 | }); | |
| 708 | ||
| 709 | const textGenerationModelSelection = resolveAppModelSelectionState(settings, serverProviders); | |
| 710 | const textGenInstanceId = textGenerationModelSelection.instanceId; | |
| 711 | const textGenModel = textGenerationModelSelection.model; | |
| 712 | const textGenModelOptions = textGenerationModelSelection.options; | |
| 713 | const textGenerationModelInstanceEntries = sortProviderInstanceEntries( | |
| 714 | applyProviderInstanceSettings(deriveProviderInstanceEntries(serverProviders), settings), | |
| 715 | ); | |
| 716 | const textGenInstanceEntry = textGenerationModelInstanceEntries.find( | |
| 717 | (entry) => entry.instanceId === textGenInstanceId, | |
| 718 | ); | |
| 719 | const textGenProvider: ProviderDriverKind = | |
| 720 | textGenInstanceEntry?.driverKind ?? DEFAULT_DRIVER_KIND; | |
| 721 | const textGenerationModelOptionsByInstance = getCustomModelOptionsByInstance( | |
| 722 | settings, | |
| 723 | serverProviders, | |
| 724 | textGenInstanceId, | |
| 725 | textGenModel, | |
| 726 | ); | |
| 727 | const isTextGenerationModelDirty = !Equal.equals( | |
| 728 | settings.textGenerationModelSelection ?? null, | |
| 729 | DEFAULT_UNIFIED_SETTINGS.textGenerationModelSelection ?? null, | |
| 730 | ); | |
| 731 | ||
| 732 | return ( | |
| 733 | <SettingsPageContainer> | |
| 734 | <SettingsSection title="General"> | |
| 700 | 735 | <SettingsRow |
| 701 | 736 | title="Project Grouping" |
| 702 | 737 | description="Combine matching repositories across environments." |
| ... | ... | @@ -775,30 +810,6 @@ export function GeneralSettingsPanel() { |
| 775 | 810 | } |
| 776 | 811 | /> |
| 777 | 812 | |
| 778 | <SettingsRow | |
| 779 | title="Word wrap" | |
| 780 | description="Wrap long lines in code blocks, tables, diffs, and file previews by default." | |
| 781 | resetAction={ | |
| 782 | settings.wordWrap !== DEFAULT_UNIFIED_SETTINGS.wordWrap ? ( | |
| 783 | <SettingResetButton | |
| 784 | label="word wrapping" | |
| 785 | onClick={() => | |
| 786 | updateSettings({ | |
| 787 | wordWrap: DEFAULT_UNIFIED_SETTINGS.wordWrap, | |
| 788 | }) | |
| 789 | } | |
| 790 | /> | |
| 791 | ) : null | |
| 792 | } | |
| 793 | control={ | |
| 794 | <Switch | |
| 795 | checked={settings.wordWrap} | |
| 796 | onCheckedChange={(checked) => updateSettings({ wordWrap: Boolean(checked) })} | |
| 797 | aria-label="Wrap code, tables, diffs, and file previews by default" | |
| 798 | /> | |
| 799 | } | |
| 800 | /> | |
| 801 | ||
| 802 | 813 | <SettingsRow |
| 803 | 814 | title="Hide whitespace changes" |
| 804 | 815 | description="Set whether the diff panel ignores whitespace-only edits by default." |
apps/web/src/components/settings/SettingsSidebarNav.tsx+3| ... | ... | @@ -7,6 +7,7 @@ import { |
| 7 | 7 | GitBranchIcon, |
| 8 | 8 | KeyboardIcon, |
| 9 | 9 | Link2Icon, |
| 10 | PaletteIcon, | |
| 10 | 11 | Settings2Icon, |
| 11 | 12 | } from "lucide-react"; |
| 12 | 13 | import { useCanGoBack, useNavigate } from "@tanstack/react-router"; |
| ... | ... | @@ -24,6 +25,7 @@ import { T3ConnectSidebarAvatar, T3ConnectSidebarSignIn } from "../clerk/T3Conne |
| 24 | 25 | |
| 25 | 26 | export type SettingsSectionPath = |
| 26 | 27 | | "/settings/general" |
| 28 | | "/settings/appearance" | |
| 27 | 29 | | "/settings/keybindings" |
| 28 | 30 | | "/settings/providers" |
| 29 | 31 | | "/settings/source-control" |
| ... | ... | @@ -37,6 +39,7 @@ export const SETTINGS_NAV_ITEMS: ReadonlyArray<{ |
| 37 | 39 | icon: ComponentType<{ className?: string }>; |
| 38 | 40 | }> = [ |
| 39 | 41 | { label: "General", to: "/settings/general", icon: Settings2Icon }, |
| 42 | { label: "Appearance", to: "/settings/appearance", icon: PaletteIcon }, | |
| 40 | 43 | { label: "Keybindings", to: "/settings/keybindings", icon: KeyboardIcon }, |
| 41 | 44 | { label: "Providers", to: "/settings/providers", icon: BotIcon }, |
| 42 | 45 | { label: "Source Control", to: "/settings/source-control", icon: GitBranchIcon }, |
apps/web/src/routeTree.gen.ts+21| ... | ... | @@ -22,6 +22,7 @@ import { Route as SettingsDiagnosticsRouteImport } from './routes/settings.diagn |
| 22 | 22 | import { Route as SettingsConnectionsRouteImport } from './routes/settings.connections' |
| 23 | 23 | import { Route as SettingsBetaRouteImport } from './routes/settings.beta' |
| 24 | 24 | import { Route as SettingsArchivedRouteImport } from './routes/settings.archived' |
| 25 | import { Route as SettingsAppearanceRouteImport } from './routes/settings.appearance' | |
| 25 | 26 | import { Route as ConnectCallbackRouteImport } from './routes/connect_.callback' |
| 26 | 27 | import { Route as ChatDraftDraftIdRouteImport } from './routes/_chat.draft.$draftId' |
| 27 | 28 | import { Route as ChatEnvironmentIdThreadIdRouteImport } from './routes/_chat.$environmentId.$threadId' |
| ... | ... | @@ -90,6 +91,11 @@ const SettingsArchivedRoute = SettingsArchivedRouteImport.update({ |
| 90 | 91 | path: '/archived', |
| 91 | 92 | getParentRoute: () => SettingsRoute, |
| 92 | 93 | } as any) |
| 94 | const SettingsAppearanceRoute = SettingsAppearanceRouteImport.update({ | |
| 95 | id: '/appearance', | |
| 96 | path: '/appearance', | |
| 97 | getParentRoute: () => SettingsRoute, | |
| 98 | } as any) | |
| 93 | 99 | const ConnectCallbackRoute = ConnectCallbackRouteImport.update({ |
| 94 | 100 | id: '/connect_/callback', |
| 95 | 101 | path: '/connect/callback', |
| ... | ... | @@ -113,6 +119,7 @@ export interface FileRoutesByFullPath { |
| 113 | 119 | '/pair': typeof PairRoute |
| 114 | 120 | '/settings': typeof SettingsRouteWithChildren |
| 115 | 121 | '/connect/callback': typeof ConnectCallbackRoute |
| 122 | '/settings/appearance': typeof SettingsAppearanceRoute | |
| 116 | 123 | '/settings/archived': typeof SettingsArchivedRoute |
| 117 | 124 | '/settings/beta': typeof SettingsBetaRoute |
| 118 | 125 | '/settings/connections': typeof SettingsConnectionsRoute |
| ... | ... | @@ -129,6 +136,7 @@ export interface FileRoutesByTo { |
| 129 | 136 | '/pair': typeof PairRoute |
| 130 | 137 | '/settings': typeof SettingsRouteWithChildren |
| 131 | 138 | '/connect/callback': typeof ConnectCallbackRoute |
| 139 | '/settings/appearance': typeof SettingsAppearanceRoute | |
| 132 | 140 | '/settings/archived': typeof SettingsArchivedRoute |
| 133 | 141 | '/settings/beta': typeof SettingsBetaRoute |
| 134 | 142 | '/settings/connections': typeof SettingsConnectionsRoute |
| ... | ... | @@ -148,6 +156,7 @@ export interface FileRoutesById { |
| 148 | 156 | '/pair': typeof PairRoute |
| 149 | 157 | '/settings': typeof SettingsRouteWithChildren |
| 150 | 158 | '/connect_/callback': typeof ConnectCallbackRoute |
| 159 | '/settings/appearance': typeof SettingsAppearanceRoute | |
| 151 | 160 | '/settings/archived': typeof SettingsArchivedRoute |
| 152 | 161 | '/settings/beta': typeof SettingsBetaRoute |
| 153 | 162 | '/settings/connections': typeof SettingsConnectionsRoute |
| ... | ... | @@ -168,6 +177,7 @@ export interface FileRouteTypes { |
| 168 | 177 | | '/pair' |
| 169 | 178 | | '/settings' |
| 170 | 179 | | '/connect/callback' |
| 180 | | '/settings/appearance' | |
| 171 | 181 | | '/settings/archived' |
| 172 | 182 | | '/settings/beta' |
| 173 | 183 | | '/settings/connections' |
| ... | ... | @@ -184,6 +194,7 @@ export interface FileRouteTypes { |
| 184 | 194 | | '/pair' |
| 185 | 195 | | '/settings' |
| 186 | 196 | | '/connect/callback' |
| 197 | | '/settings/appearance' | |
| 187 | 198 | | '/settings/archived' |
| 188 | 199 | | '/settings/beta' |
| 189 | 200 | | '/settings/connections' |
| ... | ... | @@ -202,6 +213,7 @@ export interface FileRouteTypes { |
| 202 | 213 | | '/pair' |
| 203 | 214 | | '/settings' |
| 204 | 215 | | '/connect_/callback' |
| 216 | | '/settings/appearance' | |
| 205 | 217 | | '/settings/archived' |
| 206 | 218 | | '/settings/beta' |
| 207 | 219 | | '/settings/connections' |
| ... | ... | @@ -316,6 +328,13 @@ declare module '@tanstack/react-router' { |
| 316 | 328 | preLoaderRoute: typeof SettingsArchivedRouteImport |
| 317 | 329 | parentRoute: typeof SettingsRoute |
| 318 | 330 | } |
| 331 | '/settings/appearance': { | |
| 332 | id: '/settings/appearance' | |
| 333 | path: '/appearance' | |
| 334 | fullPath: '/settings/appearance' | |
| 335 | preLoaderRoute: typeof SettingsAppearanceRouteImport | |
| 336 | parentRoute: typeof SettingsRoute | |
| 337 | } | |
| 319 | 338 | '/connect_/callback': { |
| 320 | 339 | id: '/connect_/callback' |
| 321 | 340 | path: '/connect/callback' |
| ... | ... | @@ -355,6 +374,7 @@ const ChatRouteChildren: ChatRouteChildren = { |
| 355 | 374 | const ChatRouteWithChildren = ChatRoute._addFileChildren(ChatRouteChildren) |
| 356 | 375 | |
| 357 | 376 | interface SettingsRouteChildren { |
| 377 | SettingsAppearanceRoute: typeof SettingsAppearanceRoute | |
| 358 | 378 | SettingsArchivedRoute: typeof SettingsArchivedRoute |
| 359 | 379 | SettingsBetaRoute: typeof SettingsBetaRoute |
| 360 | 380 | SettingsConnectionsRoute: typeof SettingsConnectionsRoute |
| ... | ... | @@ -366,6 +386,7 @@ interface SettingsRouteChildren { |
| 366 | 386 | } |
| 367 | 387 | |
| 368 | 388 | const SettingsRouteChildren: SettingsRouteChildren = { |
| 389 | SettingsAppearanceRoute: SettingsAppearanceRoute, | |
| 369 | 390 | SettingsArchivedRoute: SettingsArchivedRoute, |
| 370 | 391 | SettingsBetaRoute: SettingsBetaRoute, |
| 371 | 392 | SettingsConnectionsRoute: SettingsConnectionsRoute, |
apps/web/src/routes/settings.appearance.tsx created+11| ... | ... | @@ -0,0 +1,11 @@ |
| 1 | import { createFileRoute } from "@tanstack/react-router"; | |
| 2 | ||
| 3 | import { AppearanceSettingsPanel } from "../components/settings/SettingsPanels"; | |
| 4 | ||
| 5 | function SettingsAppearanceRoute() { | |
| 6 | return <AppearanceSettingsPanel />; | |
| 7 | } | |
| 8 | ||
| 9 | export const Route = createFileRoute("/settings/appearance")({ | |
| 10 | component: SettingsAppearanceRoute, | |
| 11 | }); |