11import { Box , useApp } from 'ink' ;
22import { useCallback , useEffect , useRef , useState } from 'react' ;
33
4- import { MODE } from '../constants' ;
5- import type { Config , Mode } from '../types' ;
4+ import { MODE , THEME } from '../constants' ;
5+ import type { Config , Mode , ThemeId } from '../types' ;
66import { agents , config , ollama , screen , session , terminal } from '../utils' ;
77import { Chat } from './Chat' ;
88import { Footer } from './Footer' ;
@@ -11,12 +11,14 @@ import { TURN_ABORTED_MESSAGE } from './Messages/constants';
1111import { ModelPicker } from './ModelPicker' ;
1212import { SearchSettings } from './SearchSettings' ;
1313import { SessionManager } from './SessionManager' ;
14+ import { ThemeSettings } from './ThemeSettings' ;
1415
1516enum SCREEN {
1617 CHAT = 'chat' ,
1718 MODEL_PICKER = 'model-picker' ,
1819 SEARCH_SETTINGS = 'search-settings' ,
1920 SESSION_MANAGER = 'session-manager' ,
21+ THEME_SETTINGS = 'theme-settings' ,
2022}
2123
2224interface Props {
@@ -35,18 +37,26 @@ function createSession(
3537export function App ( { sessionId } : Props ) {
3638 const { exit } = useApp ( ) ;
3739 const [ appConfig , setConfig ] = useState ( ( ) => config . loadConfig ( ) ) ;
40+ const [ previewThemeId , setPreviewThemeId ] = useState < ThemeId | null > ( null ) ;
3841 const [ currentScreen , setScreen ] = useState < SCREEN > ( SCREEN . CHAT ) ;
3942 const [ mode , setMode ] = useState < Mode > ( MODE . SAFE ) ;
4043 const [ activeSession , setSession ] = useState ( ( ) =>
4144 createSession ( sessionId , config . loadConfig ( ) . model ) ,
4245 ) ;
4346 const [ isHeaderLoaded , setIsHeaderLoaded ] = useState ( false ) ;
4447 const sessionRef = useRef ( activeSession ) ;
48+ const activeThemeId = previewThemeId ?? appConfig . theme ;
49+ const activeTheme = THEME . getTheme ( activeThemeId ) ;
50+ const commandColorRef = useRef ( activeTheme . colors . command ) ;
4551
4652 useEffect ( ( ) => {
4753 sessionRef . current = activeSession ;
4854 } , [ activeSession ] ) ;
4955
56+ useEffect ( ( ) => {
57+ commandColorRef . current = activeTheme . colors . command ;
58+ } , [ activeTheme . colors . command ] ) ;
59+
5060 useEffect ( ( ) => {
5161 return ( ) => {
5262 const currentSession = sessionRef . current ;
@@ -55,7 +65,7 @@ export function App({ sessionId }: Props) {
5565 if ( ! deleted && currentSession . messages . length > 0 ) {
5666 const resumeCommand = `code-ollama resume ${ currentSession . metadata . id } ` ;
5767 terminal . write (
58- `Resume session: ${ terminal . color ( resumeCommand , 'cyan' ) } \n` ,
68+ `Resume session: ${ terminal . color ( resumeCommand , commandColorRef . current ) } \n` ,
5969 ) ;
6070 }
6171 } ;
@@ -156,6 +166,11 @@ export function App({ sessionId }: Props) {
156166 setScreen ( SCREEN . SEARCH_SETTINGS ) ;
157167 break ;
158168
169+ case '/theme' :
170+ setPreviewThemeId ( appConfig . theme ) ;
171+ setScreen ( SCREEN . THEME_SETTINGS ) ;
172+ break ;
173+
159174 case '/clear' : {
160175 agents . resetSystemMessage ( ) ;
161176 setScreen ( SCREEN . CHAT ) ;
@@ -170,7 +185,7 @@ export function App({ sessionId }: Props) {
170185 break ;
171186 }
172187 } ,
173- [ appConfig . model , exit , setActiveSession ] ,
188+ [ appConfig . model , appConfig . theme , exit , setActiveSession ] ,
174189 ) ;
175190
176191 const handleUpdateConfig = useCallback ( ( update : Partial < Config > ) => {
@@ -195,6 +210,23 @@ export function App({ sessionId }: Props) {
195210 setScreen ( SCREEN . CHAT ) ;
196211 } , [ ] ) ;
197212
213+ const handleThemePreview = useCallback ( ( themeId : ThemeId ) => {
214+ setPreviewThemeId ( themeId ) ;
215+ } , [ ] ) ;
216+
217+ const handleThemeClose = useCallback ( ( ) => {
218+ setPreviewThemeId ( null ) ;
219+ setScreen ( SCREEN . CHAT ) ;
220+ } , [ ] ) ;
221+
222+ const handleThemeSave = useCallback (
223+ ( themeId : ThemeId ) => {
224+ setPreviewThemeId ( null ) ;
225+ handleUpdateConfig ( { theme : themeId } ) ;
226+ } ,
227+ [ handleUpdateConfig ] ,
228+ ) ;
229+
198230 const handleToggleMode = useCallback ( ( ) => {
199231 setMode ( ( mode ) => {
200232 // Cycle: safe -> auto -> plan -> safe
@@ -221,6 +253,7 @@ export function App({ sessionId }: Props) {
221253 currentModel = { appConfig . model }
222254 onSelect = { handleUpdateConfig }
223255 onClose = { handleClose }
256+ theme = { activeTheme }
224257 />
225258 ) ;
226259 break ;
@@ -231,6 +264,7 @@ export function App({ sessionId }: Props) {
231264 currentUrl = { appConfig . searxngBaseUrl }
232265 onSave = { handleUpdateConfig }
233266 onClose = { handleClose }
267+ theme = { activeTheme }
234268 />
235269 ) ;
236270 break ;
@@ -243,6 +277,18 @@ export function App({ sessionId }: Props) {
243277 onDelete = { handleDeleteSession }
244278 onNew = { handleCreateSession }
245279 onOpen = { handleOpenSession }
280+ theme = { activeTheme }
281+ />
282+ ) ;
283+ break ;
284+
285+ case SCREEN . THEME_SETTINGS :
286+ screenContent = (
287+ < ThemeSettings
288+ currentTheme = { appConfig . theme }
289+ onClose = { handleThemeClose }
290+ onPreview = { handleThemePreview }
291+ onSave = { handleThemeSave }
246292 />
247293 ) ;
248294 break ;
@@ -257,21 +303,27 @@ export function App({ sessionId }: Props) {
257303 mode = { mode }
258304 onModeChange = { setMode }
259305 sessionId = { activeSession . metadata . id }
306+ theme = { activeTheme }
260307 />
261308 ) ;
262309 break ;
263310 }
264311
265312 return (
266313 < Box flexDirection = "column" >
267- < Header model = { appConfig . model } onLoad = { handleHeaderLoad } />
314+ < Header
315+ model = { appConfig . model }
316+ onLoad = { handleHeaderLoad }
317+ theme = { activeTheme }
318+ />
268319
269320 { isHeaderLoaded && screenContent }
270321
271322 < Footer
272323 mode = { mode }
273324 model = { appConfig . model }
274325 onToggleMode = { handleToggleMode }
326+ theme = { activeTheme }
275327 />
276328 </ Box >
277329 ) ;
0 commit comments