({
onClose,
context,
setTabsHidden,
onIsSearchModeChange,
contentHeight
}: Props)
| 83 | }); |
| 84 | type SubMenu = 'Theme' | 'Model' | 'TeammateModel' | 'ExternalIncludes' | 'OutputStyle' | 'ChannelDowngrade' | 'Language' | 'EnableAutoUpdates'; |
| 85 | export function Config({ |
| 86 | onClose, |
| 87 | context, |
| 88 | setTabsHidden, |
| 89 | onIsSearchModeChange, |
| 90 | contentHeight |
| 91 | }: Props): React.ReactNode { |
| 92 | const { |
| 93 | headerFocused, |
| 94 | focusHeader |
| 95 | } = useTabHeaderFocus(); |
| 96 | const insideModal = useIsInsideModal(); |
| 97 | const [, setTheme] = useTheme(); |
| 98 | const themeSetting = useThemeSetting(); |
| 99 | const [globalConfig, setGlobalConfig] = useState(getGlobalConfig()); |
| 100 | const initialConfig = React.useRef(getGlobalConfig()); |
| 101 | const [settingsData, setSettingsData] = useState(getInitialSettings()); |
| 102 | const initialSettingsData = React.useRef(getInitialSettings()); |
| 103 | const [currentOutputStyle, setCurrentOutputStyle] = useState<OutputStyle>(settingsData?.outputStyle || DEFAULT_OUTPUT_STYLE_NAME); |
| 104 | const initialOutputStyle = React.useRef(currentOutputStyle); |
| 105 | const [currentLanguage, setCurrentLanguage] = useState<string | undefined>(settingsData?.language); |
| 106 | const initialLanguage = React.useRef(currentLanguage); |
| 107 | const [selectedIndex, setSelectedIndex] = useState(0); |
| 108 | const [scrollOffset, setScrollOffset] = useState(0); |
| 109 | const [isSearchMode, setIsSearchMode] = useState(true); |
| 110 | const isTerminalFocused = useTerminalFocus(); |
| 111 | const { |
| 112 | rows |
| 113 | } = useTerminalSize(); |
| 114 | // contentHeight is set by Settings.tsx (same value passed to Tabs to fix |
| 115 | // pane height across all tabs — prevents layout jank when switching). |
| 116 | // Reserve ~10 rows for chrome (search box, gaps, footer, scroll hints). |
| 117 | // Fallback calc for standalone rendering (tests). |
| 118 | const paneCap = contentHeight ?? Math.min(Math.floor(rows * 0.8), 30); |
| 119 | const maxVisible = Math.max(5, paneCap - 10); |
| 120 | const mainLoopModel = useAppState(s => s.mainLoopModel); |
| 121 | const verbose = useAppState(s_0 => s_0.verbose); |
| 122 | const thinkingEnabled = useAppState(s_1 => s_1.thinkingEnabled); |
| 123 | const isFastMode = useAppState(s_2 => isFastModeEnabled() ? s_2.fastMode : false); |
| 124 | const promptSuggestionEnabled = useAppState(s_3 => s_3.promptSuggestionEnabled); |
| 125 | // Show auto in the default-mode dropdown when the user has opted in OR the |
| 126 | // config is fully 'enabled' — even if currently circuit-broken ('disabled'), |
| 127 | // an opted-in user should still see it in settings (it's a temporary state). |
| 128 | const showAutoInDefaultModePicker = feature('TRANSCRIPT_CLASSIFIER') ? hasAutoModeOptInAnySource() || getAutoModeEnabledState() === 'enabled' : false; |
| 129 | // Chat/Transcript view picker is visible to entitled users (pass the GB |
| 130 | // gate) even if they haven't opted in this session — it IS the persistent |
| 131 | // opt-in. 'chat' written here is read at next startup by main.tsx which |
| 132 | // sets userMsgOptIn if still entitled. |
| 133 | /* eslint-disable @typescript-eslint/no-require-imports */ |
| 134 | const showDefaultViewPicker = feature('KAIROS') || feature('KAIROS_BRIEF') ? (require('../../tools/BriefTool/BriefTool.js') as typeof import('../../tools/BriefTool/BriefTool.js')).isBriefEntitled() : false; |
| 135 | /* eslint-enable @typescript-eslint/no-require-imports */ |
| 136 | const setAppState = useSetAppState(); |
| 137 | const [changes, setChanges] = useState<{ |
| 138 | [key: string]: unknown; |
| 139 | }>({}); |
| 140 | const initialThinkingEnabled = React.useRef(thinkingEnabled); |
| 141 | // Per-source settings snapshots for revert-on-escape. getInitialSettings() |
| 142 | // returns merged-across-sources which can't tell us what to delete vs |
nothing calls this directly
no test coverage detected