Skip to content

Commit

Permalink
Make MoFa integration a permanently visible feature (#356)
Browse files Browse the repository at this point in the history
* Remove redundant visibility checks
* Update README with detailed setup instructions
joulei authored Jan 28, 2025
1 parent 9fb2cb5 commit 3aeebd6
Showing 7 changed files with 131 additions and 106 deletions.
71 changes: 59 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
> ```
6 changes: 1 addition & 5 deletions moly-mofa/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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)]
20 changes: 9 additions & 11 deletions src/chat/chat_history.rs
Original file line number Diff line number Diff line change
@@ -120,17 +120,15 @@ impl Widget for ChatHistory {

let mut items: Vec<Item> = 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));
}
}
}
72 changes: 34 additions & 38 deletions src/chat/model_selector_list.rs
Original file line number Diff line number Diff line change
@@ -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);
18 changes: 8 additions & 10 deletions src/chat/prompt_input.rs
Original file line number Diff line number Diff line change
@@ -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"
},
);
}
}

40 changes: 19 additions & 21 deletions src/landing/model_list.rs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
10 changes: 1 addition & 9 deletions src/settings/settings_screen.rs
Original file line number Diff line number Diff line change
@@ -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());
}
}

0 comments on commit 3aeebd6

Please sign in to comment.