Skip to main content

mobius_cli/frontend/
mod.rs

1//! möbius terminal frontend.
2
3use mobius::Result;
4use mobius::protocol::FrontendBlock;
5use mobius_gateway::client::{GatewayEvents, GatewaySender};
6use mobius_gateway::wire::{ProviderInstance, ReadyPayload, SessionReadyPayload};
7
8mod bots;
9mod catalog;
10mod cloudflare_setup;
11mod dashboard;
12mod extensions;
13mod gateway;
14mod gateway_actions;
15mod headless;
16mod reinitialize;
17mod setup;
18mod terminal;
19mod theme;
20mod tui;
21
22pub use headless::run as run_headless;
23pub use terminal::terminal_text;
24
25pub use cloudflare_setup::{CloudflareInit, run as run_cloudflare_setup};
26pub use dashboard::{run as run_gateway_dashboard, run_provider as run_gateway_provider};
27pub use reinitialize::confirm as confirm_gateway_reinitialize;
28pub use setup::run_gateway_login;
29
30/// Returns the run extensions.
31/// # Errors
32///
33/// Returns an error if validation or an operation required to complete the request fails.
34pub async fn run_extensions(
35    sender: GatewaySender,
36    events: GatewayEvents,
37    gateway: ReadyPayload,
38) -> Result<()> {
39    extensions::standalone(sender, events, gateway).await
40}
41
42fn block_text(block: &FrontendBlock) -> String {
43    let mut text = block.title.clone();
44    if !block.text.is_empty() {
45        if !text.is_empty() {
46            text.push('\n');
47        }
48        text.push_str(&block.text);
49    }
50    for part in &block.content.0 {
51        text.push('\n');
52        match part {
53            mobius::protocol::ContentPart::Text { text: part } => text.push_str(part),
54            mobius::protocol::ContentPart::Image { image } => text.push_str(&format!(
55                "[image] {} · {} × {} · file_id={}",
56                image.file.name, image.width, image.height, image.file.id
57            )),
58            mobius::protocol::ContentPart::File { file } => text.push_str(&format!(
59                "[file] {} · {} bytes · file_id={}",
60                file.name, file.size, file.id
61            )),
62        }
63    }
64    for file in &block.files {
65        if !text.is_empty() {
66            text.push('\n');
67        }
68        text.push_str(&format!(
69            "[file] {} · {} · {} bytes",
70            file.name, file.media_type, file.size
71        ));
72    }
73    text
74}
75
76fn provider_instance_label<'a>(
77    instances: &'a [ProviderInstance],
78    instance: &str,
79) -> Option<&'a str> {
80    instances
81        .iter()
82        .find(|entry| entry.selection.instance == instance)
83        .map(|entry| entry.label.as_str())
84}
85
86/// Runs the interactive terminal frontend.
87/// # Errors
88///
89/// Returns an error if validation or an operation required to complete the request fails.
90pub async fn run(
91    sender: GatewaySender,
92    events: GatewayEvents,
93    gateway: &mut ReadyPayload,
94    session: &mut SessionReadyPayload,
95    gateway_endpoint: String,
96) -> Result<(FrontendExit, GatewaySender, GatewayEvents)> {
97    let workspace = session.workspace.path.clone();
98    let catalog = catalog::UiCatalog::build(&session.contributions, &workspace)?;
99    tui::runtime::run(sender, events, gateway, session, catalog, gateway_endpoint).await
100}
101
102/// Why a frontend returned control to its launcher.
103pub enum FrontendExit {
104    /// Selects the exit case.
105    Exit,
106    /// Selects the resume case.
107    Resume(String),
108    /// Selects the reload case.
109    Reload,
110    /// Selects the reconnect case.
111    Reconnect,
112}