diff --git a/README.md b/README.md index 91e1ea84..048157c4 100644 --- a/README.md +++ b/README.md @@ -211,23 +211,70 @@ If you'd like to modify the .dmg background, here is the [Google Drawings file u [AppImage]: https://appimage.org/ [pacman]: https://pacman.archlinux.page/pacman.8.html -# Mofa +## Running Moly with MoFa -Currently, here in `dev` branch, Mofa is still in development. It's UI is hidden -and its backend is faked by default. This can be changed by setting some -provisional environment variables. +[MoFa](https://github.com/moxin-org/mofa) is a software framework for building AI agents. Moly supports connecting to MoFa servers to interact with AI agents in the same way it does with local LLMs. -To run Moly with Mofa UI enabled but still using the fake backend use: +To run Moly with a local MoFa server, you can follow these steps: -```sh -MOFA_FRONTEND=visible cargo run +### 1. Install Dora + +https://github.com/dora-rs/dora?tab=readme-ov-file#installation + +### 2. Install MoFa + +Requires python ^3.10 + +```bash +git clone https://github.com/moxin-org/mofa.git +``` +Install the required Python libraries, and mainly, +the mofa library itself +```bash +cd python && pip install -r requirements.txt && pip install -e . +pip install dora-rs ``` -To also enable the backend use: +### 3. Run the Moly client (MoFa server for Moly) -```sh -MOFA_FRONTEND=visible MOFA_BACKEND=real cargo run +Folder of the Dora node that implements the http server +```bash +# +cd examples/moly_client +Run MoFa with +``` +dora up +dora build dataflow.yml +dora start dataflow.yml ``` +If there's any error when doing dora start, you can restart dora +```bash +dora destroy && dora up +``` + +At this point the server should be up +You can verify it with a request for chat completion: +```bash +curl http://localhost:8000/v1/chat/completions \ +-v -H "Content-Type: application/json" \ +-d '{ +"model": "moly-chat", +"messages": [ +{ "role": "system", "content": "Use positive language and offer helpful solutions to their problems." }, +{ "role": "user", "content": "What is the currency used in Spain?" } +], +"temperature": 0.7, +"stream": true +}' +``` +This should return a JSON response with the completion. + +## Connect Moly to MoFa -> Note: Enabling the backend requires running Dora and configuring its URL in -> Moly settings. +Go to Settings and make sure there's MoFa server listed with the URL [`http://localhost:8000`](http://localhost:8000/) (should be there by default). + +> [!NOTE] +> For development, if you want to avoid running the MoFa server, you can fake it by setting the `MOFA_BACKEND` environment variable to `fake` (default is `real`): +> ``` +> MOFA_BACKEND=fake cargo run +> ``` diff --git a/moly-mofa/src/lib.rs b/moly-mofa/src/lib.rs index c1961001..c0c712d2 100644 --- a/moly-mofa/src/lib.rs +++ b/moly-mofa/src/lib.rs @@ -319,12 +319,8 @@ impl MofaClient { } } -pub fn should_be_visible() -> bool { - std::env::var("MOFA_FRONTEND").unwrap_or_default() == "visible" -} - pub fn should_be_real() -> bool { - std::env::var("MOFA_BACKEND").unwrap_or_default() == "real" + std::env::var("MOFA_BACKEND").as_deref().unwrap_or("real") != "fake" } #[derive(Clone, Debug)] diff --git a/src/chat/chat_history.rs b/src/chat/chat_history.rs index 29b414c6..18bdcdd4 100644 --- a/src/chat/chat_history.rs +++ b/src/chat/chat_history.rs @@ -120,17 +120,15 @@ impl Widget for ChatHistory { let mut items: Vec = Vec::new(); - if moly_mofa::should_be_visible() { - items.push(Item::AgentsHeader); - let agents_availability = store.chats.agents_availability(); - match agents_availability { - AgentsAvailability::NoServers => items.push(Item::NoAgentsWarning(agents_availability.to_human_readable())), - AgentsAvailability::ServersNotConnected => items.push(Item::NoAgentsWarning(agents_availability.to_human_readable())), - AgentsAvailability::NoAgents => items.push(Item::NoAgentsWarning(agents_availability.to_human_readable())), - AgentsAvailability::Available => { - for agent in &agents { - items.push(Item::AgentButton(agent)); - } + items.push(Item::AgentsHeader); + let agents_availability = store.chats.agents_availability(); + match agents_availability { + AgentsAvailability::NoServers => items.push(Item::NoAgentsWarning(agents_availability.to_human_readable())), + AgentsAvailability::ServersNotConnected => items.push(Item::NoAgentsWarning(agents_availability.to_human_readable())), + AgentsAvailability::NoAgents => items.push(Item::NoAgentsWarning(agents_availability.to_human_readable())), + AgentsAvailability::Available => { + for agent in &agents { + items.push(Item::AgentButton(agent)); } } } diff --git a/src/chat/model_selector_list.rs b/src/chat/model_selector_list.rs index 8239a791..d1ecb45e 100644 --- a/src/chat/model_selector_list.rs +++ b/src/chat/model_selector_list.rs @@ -166,46 +166,42 @@ impl ModelSelectorList { let separator_widget = self.items.get_or_insert(cx, separator_id, |cx| { WidgetRef::new_from_ptr(cx, self.separator_template) }); - if moly_mofa::should_be_visible() { - let _ = separator_widget.draw_all(cx, &mut Scope::empty()); - total_height += separator_widget.as_view().area().rect(cx).size.y; - } + let _ = separator_widget.draw_all(cx, &mut Scope::empty()); + total_height += separator_widget.as_view().area().rect(cx).size.y; } - if moly_mofa::should_be_visible() { - let agents = store.chats.get_agents_list(); - for (i, agent) in agents.iter().enumerate() { - let item_id = LiveId((models_count + 1 + i) as u64).into(); - let item_widget = self.items.get_or_insert(cx, item_id, |cx| { - WidgetRef::new_from_ptr(cx, self.agent_template) - }); - - let agent_name = &agent.name; - let current_agent_name = match &chat_entity { - Some(ChatEntityId::Agent(agent_id)) => { - store.chats.available_agents.get(agent_id).map(|a| &a.name) - }, - _ => None, - }; - let icon_tick_visible = current_agent_name == Some(agent_name); - - item_widget.apply_over( - cx, - live! { - content = { - label = { text: (agent_name) } - icon_tick_tag = { visible: (icon_tick_visible) } - } - }, - ); - - item_widget - .as_model_selector_item() - .set_agent(agent.clone()); - - let _ = item_widget.draw_all(cx, &mut Scope::empty()); - total_height += item_widget.view(id!(content)).area().rect(cx).size.y; - } + let agents = store.chats.get_agents_list(); + for (i, agent) in agents.iter().enumerate() { + let item_id = LiveId((models_count + 1 + i) as u64).into(); + let item_widget = self.items.get_or_insert(cx, item_id, |cx| { + WidgetRef::new_from_ptr(cx, self.agent_template) + }); + + let agent_name = &agent.name; + let current_agent_name = match &chat_entity { + Some(ChatEntityId::Agent(agent_id)) => { + store.chats.available_agents.get(agent_id).map(|a| &a.name) + }, + _ => None, + }; + let icon_tick_visible = current_agent_name == Some(agent_name); + + item_widget.apply_over( + cx, + live! { + content = { + label = { text: (agent_name) } + icon_tick_tag = { visible: (icon_tick_visible) } + } + }, + ); + + item_widget + .as_model_selector_item() + .set_agent(agent.clone()); + + let _ = item_widget.draw_all(cx, &mut Scope::empty()); + total_height += item_widget.view(id!(content)).area().rect(cx).size.y; } self.total_height = Some(total_height); diff --git a/src/chat/prompt_input.rs b/src/chat/prompt_input.rs index 23eb9867..0742e434 100644 --- a/src/chat/prompt_input.rs +++ b/src/chat/prompt_input.rs @@ -344,16 +344,14 @@ impl PromptInput { impl LiveHook for PromptInput { fn after_new_from_doc(&mut self, cx: &mut Cx) { - if moly_mofa::should_be_visible() { - let prompt = self.command_text_input(id!(prompt)); - prompt.apply_over(cx, live! { trigger: "@" }); - prompt.text_input_ref().apply_over( - cx, - live! { - empty_message: "Start typing or tag @model or @agent" - }, - ); - } + let prompt = self.command_text_input(id!(prompt)); + prompt.apply_over(cx, live! { trigger: "@" }); + prompt.text_input_ref().apply_over( + cx, + live! { + empty_message: "Start typing or tag @model or @agent" + }, + ); } } diff --git a/src/landing/model_list.rs b/src/landing/model_list.rs index 26b2fcc8..0bb56b1e 100644 --- a/src/landing/model_list.rs +++ b/src/landing/model_list.rs @@ -174,27 +174,25 @@ impl Widget for ModelList { let mut items = Vec::new(); if store.search.keyword.is_none() { - if moly_mofa::should_be_visible() { - items.push(Item::Header("Featured Agents")); - let agents_availability = store.chats.agents_availability(); - match agents_availability { - AgentsAvailability::NoServers => items.push(Item::NoAgentsWarning( - agents_availability.to_human_readable(), - )), - AgentsAvailability::ServersNotConnected => items.push(Item::NoAgentsWarning( - agents_availability.to_human_readable(), - )), - AgentsAvailability::NoAgents => items.push(Item::NoAgentsWarning( - agents_availability.to_human_readable(), - )), - AgentsAvailability::Available => { - items.extend(agents.chunks(3).map(|chunk| Item::AgentRow { - agents: chunk, - margin_bottom: 8.0, - })); - if let Some(Item::AgentRow { margin_bottom, .. }) = items.last_mut() { - *margin_bottom = 0.0; - } + items.push(Item::Header("Featured Agents")); + let agents_availability = store.chats.agents_availability(); + match agents_availability { + AgentsAvailability::NoServers => items.push(Item::NoAgentsWarning( + agents_availability.to_human_readable(), + )), + AgentsAvailability::ServersNotConnected => items.push(Item::NoAgentsWarning( + agents_availability.to_human_readable(), + )), + AgentsAvailability::NoAgents => items.push(Item::NoAgentsWarning( + agents_availability.to_human_readable(), + )), + AgentsAvailability::Available => { + items.extend(agents.chunks(3).map(|chunk| Item::AgentRow { + agents: chunk, + margin_bottom: 8.0, + })); + if let Some(Item::AgentRow { margin_bottom, .. }) = items.last_mut() { + *margin_bottom = 0.0; } } } diff --git a/src/settings/settings_screen.rs b/src/settings/settings_screen.rs index c56272bc..c962fdeb 100644 --- a/src/settings/settings_screen.rs +++ b/src/settings/settings_screen.rs @@ -200,7 +200,7 @@ enum ServerPortState { Editable, } -#[derive(Widget, Live)] +#[derive(Widget, Live, LiveHook)] pub struct SettingsScreen { #[deref] view: View, @@ -333,11 +333,3 @@ impl WidgetMatchEvent for SettingsScreen { } } } - -impl LiveHook for SettingsScreen { - fn after_new_from_doc(&mut self, cx: &mut Cx) { - self.view - .view(id!(mofa_section)) - .set_visible(cx, moly_mofa::should_be_visible()); - } -}