@@ -39,6 +39,7 @@ import handleDeepLink from "@/routes/root/deep";
3939import * as api from "@/api/api" ;
4040import InterpreterSelector from "@/lib/blocks/common/InterpreterSelector" ;
4141import AtuinEnv from "@/atuin_env" ;
42+ import { OllamaSettings , useAIProviderSettings } from "@/state/settings_ai" ;
4243
4344async function loadFonts ( ) : Promise < string [ ] > {
4445 const fonts = await invoke < string [ ] > ( "list_fonts" ) ;
@@ -49,7 +50,7 @@ async function loadFonts(): Promise<string[]> {
4950}
5051
5152// Custom hook for managing settings
52- const useSettingsState = (
53+ export const useSettingsState = (
5354 _key : any ,
5455 initialValue : any ,
5556 settingsGetter : any ,
@@ -109,6 +110,7 @@ interface SettingsSwitchProps {
109110 onValueChange : ( e : boolean ) => void ;
110111 description : string ;
111112 className ?: string ;
113+ isDisabled ?: boolean ;
112114}
113115
114116const SettingSwitch = ( {
@@ -117,11 +119,13 @@ const SettingSwitch = ({
117119 onValueChange,
118120 description,
119121 className,
122+ isDisabled,
120123} : SettingsSwitchProps ) => (
121124 < Switch
122125 isSelected = { isSelected }
123126 onValueChange = { onValueChange }
124127 className = { cn ( "flex justify-between items-center w-full" , className ) }
128+ isDisabled = { isDisabled || false }
125129 >
126130 < div className = "flex flex-col" >
127131 < span > { label } </ span >
@@ -1190,29 +1194,122 @@ const AISettings = () => {
11901194 const setAiEnabled = useStore ( ( state ) => state . setAiEnabled ) ;
11911195 const setAiShareContext = useStore ( ( state ) => state . setAiShareContext ) ;
11921196
1197+ return (
1198+ < >
1199+ < Card shadow = "sm" >
1200+ < CardBody className = "flex flex-col gap-4 mb-4" >
1201+ < h2 className = "text-xl font-semibold" > AI</ h2 >
1202+ < p className = "text-sm text-default-500" >
1203+ Configure AI-powered features in runbooks
1204+ </ p >
1205+
1206+ < SettingSwitch
1207+ label = "Enable AI features"
1208+ isSelected = { aiEnabled }
1209+ onValueChange = { setAiEnabled }
1210+ description = "Enable AI block generation and editing (Cmd+Enter, Cmd+K, AI Agent Sidebar)"
1211+ />
1212+
1213+ { aiEnabled && (
1214+ < SettingSwitch
1215+ className = "ml-4"
1216+ label = "Share document context"
1217+ isSelected = { aiShareContext }
1218+ onValueChange = { setAiShareContext }
1219+ description = "Send document content to improve AI suggestions. Disable for sensitive documents."
1220+ />
1221+ ) }
1222+ </ CardBody >
1223+ </ Card >
1224+ { aiEnabled && (
1225+ < >
1226+ < AgentSettings />
1227+ < AIOllamaSettings />
1228+ </ >
1229+ ) }
1230+ </ >
1231+ ) ;
1232+ } ;
1233+
1234+ const AgentSettings = ( ) => {
1235+ const providers = [
1236+ [ "Atuin Hub" , "atuinhub" ] ,
1237+ [ "Ollama" , "ollama" ]
1238+ ]
1239+
1240+ const [ aiProvider , setAiProvider , aiProviderLoading ] = useSettingsState (
1241+ "ai_provider" ,
1242+ "atuinhub" ,
1243+ Settings . aiAgentProvider ,
1244+ Settings . aiAgentProvider ,
1245+ ) ;
1246+
1247+ const handleProviderChange = ( keys : SharedSelection ) => {
1248+ const key = keys . currentKey as string ;
1249+ if ( key ) {
1250+ setAiProvider ( key ) ;
1251+ }
1252+ } ;
1253+
11931254 return (
11941255 < Card shadow = "sm" >
11951256 < CardBody className = "flex flex-col gap-4" >
1196- < h2 className = "text-xl font-semibold" > AI</ h2 >
1197- < p className = "text-sm text-default-500" >
1198- Configure AI-powered features in runbooks
1199- </ p >
1257+ < h2 className = "text-xl font-semibold" > AI Agent</ h2 >
1258+
1259+ < Select
1260+ label = "Default AI provider"
1261+ value = { aiProvider }
1262+ onSelectionChange = { handleProviderChange }
1263+ className = "mt-4"
1264+ placeholder = "Select default AI provider"
1265+ selectedKeys = { [ aiProvider ] }
1266+ items = { providers . map ( ( [ name , id ] ) => ( { label : name , key : id } ) ) }
1267+ isDisabled = { aiProviderLoading }
1268+ >
1269+ { ( item ) => < SelectItem key = { item . key } > { item . label } </ SelectItem > }
1270+ </ Select >
1271+ </ CardBody >
1272+ </ Card >
1273+ ) ;
1274+ } ;
1275+
1276+ const AIOllamaSettings = ( ) => {
1277+ const [ ollamaSettings , setOllamaSettings , isLoading ] = useAIProviderSettings < OllamaSettings > ( "ollama" , {
1278+ enabled : false ,
1279+ endpoint : "http://localhost:11434" ,
1280+ model : "" ,
1281+ } ) ;
1282+
1283+ return (
1284+ < Card shadow = "sm" >
1285+ < CardBody className = "flex flex-col gap-4" >
1286+ < h2 className = "text-xl font-semibold" > Ollama</ h2 >
12001287
12011288 < SettingSwitch
1202- label = "Enable AI features "
1203- isSelected = { aiEnabled }
1204- onValueChange = { setAiEnabled }
1205- description = "Enable AI block generation and editing (Cmd+Enter, Cmd+K) "
1289+ label = "Enable Ollama AI provider "
1290+ isSelected = { ollamaSettings . enabled }
1291+ onValueChange = { ( enabled ) => setOllamaSettings ( { ... ollamaSettings , enabled } ) }
1292+ description = "Toggle to use Ollama as the AI provider. "
12061293 />
12071294
1208- { aiEnabled && (
1209- < SettingSwitch
1210- className = "ml-4"
1211- label = "Share document context"
1212- isSelected = { aiShareContext }
1213- onValueChange = { setAiShareContext }
1214- description = "Send document content to improve AI suggestions. Disable for sensitive documents."
1215- />
1295+ { ollamaSettings . enabled && (
1296+ < div className = "flex flex-col gap-4" >
1297+ < Input
1298+ label = "Endpoint (optional, defaults to http://localhost:11434)"
1299+ placeholder = "Endpoint URL (e.g. http://localhost:11434)"
1300+ value = { ollamaSettings . endpoint }
1301+ onValueChange = { ( value ) => setOllamaSettings ( { ...ollamaSettings , endpoint : value } ) }
1302+ isDisabled = { isLoading }
1303+ />
1304+
1305+ < Input
1306+ label = "Model (required; your chosen model must support tool calling)"
1307+ placeholder = "Model name"
1308+ value = { ollamaSettings . model }
1309+ onValueChange = { ( value ) => setOllamaSettings ( { ...ollamaSettings , model : value } ) }
1310+ isDisabled = { isLoading }
1311+ />
1312+ </ div >
12161313 ) }
12171314 </ CardBody >
12181315 </ Card >
@@ -1235,7 +1332,7 @@ const UserSettings = () => {
12351332 const deepLink = `atuin://register-token/${ token } ` ;
12361333 // token submit deep link doesn't require a runbook activation,
12371334 // so passing an empty function for simplicity
1238- handleDeepLink ( deepLink , ( ) => { } ) ;
1335+ handleDeepLink ( deepLink , ( ) => { } ) ;
12391336 }
12401337
12411338 let content ;
0 commit comments