1mod runtime;
4mod state;
5pub(in crate::frontend) mod view;
6
7use std::io;
8
9use mobius::{Error, Result};
10use mobius_gateway::client::{GatewayEvents, GatewaySender};
11use mobius_gateway::config::{
12 MAX_PROVIDER_API_KEY_BYTES as MAX_API_KEY_BYTES, MAX_PROVIDER_LABEL_BYTES,
13};
14use mobius_gateway::wire::{ReadyPayload, SessionReadyPayload};
15use ratatui::Terminal;
16use ratatui::backend::CrosstermBackend;
17
18use self::runtime::{apply, apply_gateway, edit};
19use self::state::SetupState;
20use super::terminal::TerminalGuard;
21
22const MAX_ENDPOINT_BYTES: usize = 4 * 1024;
23const MAX_MODEL_IDS_BYTES: usize = 16 * 1024;
24const MIN_INLINE_DESCRIPTION_WIDTH: usize = 20;
25
26const UPDATE_BOT_LABEL: &str = "Update this Bot";
27const UPDATE_BOT_DESCRIPTION: &str = "Apply these settings to every chat owned by this Bot";
28const SAVE_DEFAULT_LABEL: &str = "Save Bot template";
29const SAVE_DEFAULT_DESCRIPTION: &str = "Use these settings when creating Bots";
30
31pub(in crate::frontend) type SetupTerminal = Terminal<CrosstermBackend<io::Stdout>>;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub(crate) enum SetupMode {
36 Login,
37 Bot,
38 BotModel,
39}
40
41pub(crate) async fn run(
43 terminal: &mut SetupTerminal,
44 mode: SetupMode,
45 preferred_provider: Option<&str>,
46 sender: &GatewaySender,
47 events: &mut GatewayEvents,
48 gateway: &mut ReadyPayload,
49 session: &mut SessionReadyPayload,
50) -> Result<()> {
51 run_bot(
52 terminal,
53 mode,
54 preferred_provider,
55 sender,
56 events,
57 gateway,
58 &session.session.context.owner_id,
59 )
60 .await
61}
62
63pub(in crate::frontend) async fn run_bot(
65 terminal: &mut SetupTerminal,
66 mode: SetupMode,
67 preferred_provider: Option<&str>,
68 sender: &GatewaySender,
69 events: &mut GatewayEvents,
70 gateway: &mut ReadyPayload,
71 bot_id: &str,
72) -> Result<()> {
73 let bot = gateway
74 .bots
75 .iter()
76 .find(|bot| bot.id == bot_id)
77 .ok_or_else(|| Error::Config("the selected Bot is not in the gateway catalog".into()))?;
78 let mut state = SetupState::new(
79 mode,
80 preferred_provider,
81 gateway,
82 bot.config.config.clone(),
83 false,
84 )?;
85 terminal.clear()?;
86
87 while edit(terminal, &mut state, sender, events, gateway).await? {
88 match apply(terminal, &mut state, sender, events, gateway, bot_id).await {
89 Ok(()) => return Ok(()),
90 Err(error) => state.recover_error(error)?,
91 }
92 }
93 Ok(())
94}
95
96pub(crate) async fn run_gateway(
98 terminal: &mut SetupTerminal,
99 mode: SetupMode,
100 preferred_provider: Option<&str>,
101 sender: &GatewaySender,
102 events: &mut GatewayEvents,
103 gateway: &mut ReadyPayload,
104) -> Result<()> {
105 let original = gateway
106 .bot_defaults
107 .as_ref()
108 .map(|default| default.config.clone())
109 .unwrap_or_default();
110 if mode == SetupMode::Bot && gateway.bot_defaults.is_none() {
111 return Err(Error::Config(
112 "configure a provider before changing the Bot template".into(),
113 ));
114 }
115 let mut state = SetupState::new(mode, preferred_provider, gateway, original, true)?;
116 terminal.clear()?;
117 while edit(terminal, &mut state, sender, events, gateway).await? {
118 match apply_gateway(terminal, &mut state, sender, events, gateway).await {
119 Ok(()) => return Ok(()),
120 Err(error) => state.recover_error(error)?,
121 }
122 }
123 Ok(())
124}
125
126pub async fn run_gateway_login(
132 sender: &GatewaySender,
133 events: &mut GatewayEvents,
134 gateway: &mut ReadyPayload,
135) -> Result<bool> {
136 let mut guard = TerminalGuard::alternate()?;
137 guard.set_mouse_capture(false)?;
138 let mut terminal = Terminal::new(CrosstermBackend::new(io::stdout()))?;
139 let mut state = SetupState::new(
140 SetupMode::Login,
141 None,
142 gateway,
143 gateway
144 .bot_defaults
145 .as_ref()
146 .map(|defaults| defaults.config.clone())
147 .unwrap_or_default(),
148 true,
149 )?;
150 state.start_chat = true;
151 terminal.clear()?;
152 while edit(&mut terminal, &mut state, sender, events, gateway).await? {
153 match apply_gateway(&mut terminal, &mut state, sender, events, gateway).await {
154 Ok(()) => return Ok(true),
155 Err(error) => state.recover_error(error)?,
156 }
157 }
158 Ok(false)
159}
160
161#[cfg(test)]
162mod tests;