Skip to content

Commit

Permalink
Support @ model from prompt input (#320)
Browse files Browse the repository at this point in the history
* try mixing models and agents in autocomplete

* loading model from at selection

* wip maybe model selector not the same as invoked model

* unify more chat entities and fix keyboard selection of entity

* fix model button dsl with latest makepad syntax

* address fragile todo

* remove agent button tighly coupled properties

* unify model button into agent button

* remove ChatHandler and favor chatentityid

* remove previously necessary steps to convert chat entity id to chat handler

* remove unnecessary owned ChatEntity type

* avoid unnecessary premature string allocations and cloning

* tidy many details

* remove ChatEntityRef::from_chain in favor of explicit sorting

* move chat entity types to their own file

* hide loader indicator for at model messages

* take adventage of list items() to dedup handling of prompt input items

* remove ChatEntityRef::from as enum variants can be mapped directly

* allow set_agent directly in entity button

* fix type name after rebase

* Add autocomplete section headers + text updates

- Added section labels for "Agents" and "Models" in the autocomplete list.
- Updated the empty message to clarify search options.
- Keyboard focus handling skips section headers.
- Minor layout and padding adjustments

---------

Co-authored-by: Julian Montes de Oca <[email protected]>
  • Loading branch information
noxware and joulei authored Dec 3, 2024
1 parent 1a6bfa1 commit 1161384
Show file tree
Hide file tree
Showing 18 changed files with 515 additions and 322 deletions.
22 changes: 10 additions & 12 deletions moly-mofa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,23 +59,21 @@ pub enum MofaAgent {
}

impl MofaAgent {
pub fn name(&self) -> String {
pub fn name(&self) -> &str {
match self {
MofaAgent::Example => "Example Agent".to_string(),
MofaAgent::Reasoner => "Reasoner Agent".to_string(),
MofaAgent::SearchAssistant => "Search Assistant".to_string(),
MofaAgent::ResearchScholar => "Research Scholar".to_string(),
MofaAgent::Example => "Example Agent",
MofaAgent::Reasoner => "Reasoner Agent",
MofaAgent::SearchAssistant => "Search Assistant",
MofaAgent::ResearchScholar => "Research Scholar",
}
}

pub fn short_description(&self) -> String {
pub fn short_description(&self) -> &str {
match self {
MofaAgent::Example => "This is an example agent implemented by MoFa".to_string(),
MofaAgent::Reasoner => "Helps to find good questions about any topic".to_string(),
MofaAgent::SearchAssistant => {
"Your assistant to find information on the web".to_string()
}
MofaAgent::ResearchScholar => "Expert in academic research".to_string(),
MofaAgent::Example => "This is an example agent implemented by MoFa",
MofaAgent::Reasoner => "Helps to find good questions about any topic",
MofaAgent::SearchAssistant => "Your assistant to find information on the web",
MofaAgent::ResearchScholar => "Expert in academic research",
}
}
}
Expand Down
21 changes: 17 additions & 4 deletions src/chat/chat_history.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use super::agent_button::AgentButtonWidgetRefExt;
use super::chat_history_card::{ChatHistoryCardAction, ChatHistoryCardWidgetRefExt};
use crate::chat::entity_button::EntityButtonWidgetRefExt;
use crate::data::chats::chat::ChatID;
use crate::data::store::Store;
use crate::shared::actions::ChatAction;
use makepad_widgets::*;
use moly_mofa::{MofaAgent, MofaBackend};

Expand All @@ -15,7 +16,7 @@ live_design! {
use crate::chat::shared::ChatAgentAvatar;
use crate::chat::chat_history_card::ChatHistoryCard;
use crate::chat::shared::ChatModelAvatar;
use crate::chat::agent_button::*;
use crate::chat::entity_button::*;

ICON_NEW_CHAT = dep("crate://self/resources/icons/new_chat.svg")

Expand Down Expand Up @@ -47,7 +48,7 @@ live_design! {
list = <PortalList> {
drag_scrolling: false,
AgentHeading = <HeadingLabel> { text: "AGENTS" }
Agent = <AgentButton> { create_new_chat: true }
Agent = <EntityButton> {}
ChatsHeading = <HeadingLabel> { text: "CHATS", margin: {top: 10}, }
ChatHistoryCard = <ChatHistoryCard> {
padding: {top: 4}
Expand Down Expand Up @@ -147,7 +148,7 @@ impl Widget for ChatHistory {
}
Item::AgentButton(agent) => {
let item = list.item(cx, item_id, live_id!(Agent));
item.as_agent_button().set_agent(agent, false);
item.as_entity_button().set_agent(agent);
item.draw_all(cx, scope);
}
Item::ChatButton(chat_id) => {
Expand All @@ -170,6 +171,18 @@ impl WidgetMatchEvent for ChatHistory {
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions, scope: &mut Scope) {
let store = scope.data.get_mut::<Store>().unwrap();

let clicked_entity_button = self
.portal_list(id!(list))
.items_with_actions(actions)
.iter()
.map(|(_, item)| item.as_entity_button())
.find(|eb| eb.clicked(actions));

if let Some(entity_button) = clicked_entity_button {
let entity_id = entity_button.get_entity_id().unwrap().clone();
cx.action(ChatAction::Start(entity_id));
}

if self.button(id!(new_chat_button)).clicked(&actions) {
store.chats.create_empty_chat();

Expand Down
18 changes: 12 additions & 6 deletions src/chat/chat_history_card.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
use crate::{
data::{chats::chat::{ChatEntity, ChatID}, store::Store},
data::{
chats::{chat::ChatID, chat_entity::ChatEntityId},
store::Store,
},
shared::{modal::ModalWidgetExt, utils::human_readable_name},
};

use makepad_widgets::*;

use super::{delete_chat_modal::DeleteChatModalWidgetExt, shared::ChatAgentAvatarWidgetExt};
use super::{
chat_history_card_options::ChatHistoryCardOptionsWidgetExt,
delete_chat_modal::DeleteChatModalAction,
};
use super::{delete_chat_modal::DeleteChatModalWidgetExt, shared::ChatAgentAvatarWidgetExt};

live_design! {
use link::theme::*;
Expand Down Expand Up @@ -358,14 +361,17 @@ impl Widget for ChatHistoryCard {
.to_string();

match chat.borrow().associated_entity {
Some(ChatEntity::Agent(agent)) => {
Some(ChatEntityId::Agent(agent)) => {
self.view(id!(avatar_section.model)).set_visible(false);
self.chat_agent_avatar(id!(avatar_section.agent)).set_visible(true);
self.chat_agent_avatar(id!(avatar_section.agent)).set_agent(&agent);
self.chat_agent_avatar(id!(avatar_section.agent))
.set_visible(true);
self.chat_agent_avatar(id!(avatar_section.agent))
.set_agent(&agent);
}
_ => {
self.view(id!(avatar_section.model)).set_visible(true);
self.chat_agent_avatar(id!(avatar_section.agent)).set_visible(false);
self.chat_agent_avatar(id!(avatar_section.agent))
.set_visible(false);
self.view.label(id!(avatar_label)).set_text(&initial_letter);
}
}
Expand Down
31 changes: 17 additions & 14 deletions src/chat/chat_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ use crate::{
model_selector_item::ModelSelectorAction,
},
data::{
chats::chat::{Chat, ChatEntity, ChatEntityAction, ChatID, ChatMessage},
chats::{
chat::{Chat, ChatEntityAction, ChatID, ChatMessage},
chat_entity::ChatEntityId,
},
store::Store,
},
shared::actions::{ChatAction, ChatHandler},
shared::actions::ChatAction,
};

use super::{
Expand Down Expand Up @@ -460,7 +463,7 @@ impl WidgetMatchEvent for ChatPanel {

if let Some(chat) = store.chats.get_current_chat() {
chat.borrow_mut().associated_entity =
Some(ChatEntity::ModelFile(downloaded_file.file.id.clone()));
Some(ChatEntityId::ModelFile(downloaded_file.file.id.clone()));
chat.borrow().save();
}

Expand All @@ -469,7 +472,7 @@ impl WidgetMatchEvent for ChatPanel {
}
ModelSelectorAction::AgentSelected(agent) => {
if let Some(chat) = store.chats.get_current_chat() {
chat.borrow_mut().associated_entity = Some(ChatEntity::Agent(agent));
chat.borrow_mut().associated_entity = Some(ChatEntityId::Agent(agent));
chat.borrow().save();
}

Expand All @@ -493,13 +496,13 @@ impl WidgetMatchEvent for ChatPanel {

match action.cast() {
ChatAction::Start(handler) => match handler {
ChatHandler::Model(file_id) => {
ChatEntityId::ModelFile(file_id) => {
if let Some(file) = store.downloads.get_file(&file_id) {
store.chats.create_empty_chat_and_load_file(file);
self.focus_on_prompt_input_pending = true;
}
}
ChatHandler::Agent(agent) => {
ChatEntityId::Agent(agent) => {
store.chats.create_empty_chat_with_agent(agent);
self.focus_on_prompt_input_pending = true;
}
Expand Down Expand Up @@ -769,15 +772,15 @@ impl ChatPanel {
..
} | State::ModelSelectedWithEmptyChat { is_loading: false }
) {
if let Some(agent_selected) = self
if let Some(entity_selected) = &self
.prompt_input(id!(main_prompt_input))
.borrow()
.unwrap()
.agent_selected
.entity_selected
{
store.send_agent_message(agent_selected, prompt.clone(), regenerate_from);
store.send_entity_message(entity_selected, prompt, regenerate_from);
} else {
store.send_message_to_current_entity(prompt.clone(), regenerate_from);
store.send_message_to_current_entity(prompt, regenerate_from);
}

self.prompt_input(id!(main_prompt_input)).reset_text(false);
Expand All @@ -798,7 +801,7 @@ impl ChatPanel {

let chat_entity = &get_chat(store).unwrap().borrow().associated_entity;
match chat_entity {
Some(ChatEntity::Agent(agent)) => {
Some(ChatEntityId::Agent(agent)) => {
let empty_view = self.view(id!(empty_conversation));
empty_view
.view(id!(avatar_section.model))
Expand All @@ -809,7 +812,7 @@ impl ChatPanel {

empty_view
.chat_agent_avatar(id!(avatar_section.agent))
.set_agent(agent);
.set_agent(&agent);
}
_ => {
let empty_view = self.view(id!(empty_conversation));
Expand Down Expand Up @@ -896,10 +899,10 @@ impl ChatPanel {
chat_line_item.set_regenerate_button_visible(false);

match chat_line_data.entity {
Some(ChatEntity::Agent(agent)) => {
Some(ChatEntityId::Agent(agent)) => {
chat_line_item.set_model_avatar(&agent);
}
Some(ChatEntity::ModelFile(_)) => {
Some(ChatEntityId::ModelFile(_)) => {
chat_line_item.set_model_avatar_text(
&get_model_initial_letter(store).unwrap().to_string(),
);
Expand Down
115 changes: 75 additions & 40 deletions src/chat/agent_button.rs → src/chat/entity_button.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::data::chats::chat_entity::{ChatEntityId, ChatEntityRef};

use super::shared::ChatAgentAvatarWidgetExt;
use makepad_widgets::*;
use moly_mofa::MofaAgent;

use crate::shared::actions::{ChatAction, ChatHandler};

use super::{prompt_input::PromptInputAction, shared::ChatAgentAvatarWidgetExt};
use std::cell::Ref;

live_design!(
use link::theme::*;
Expand All @@ -14,7 +15,7 @@ live_design!(
use crate::shared::widgets::*;
use crate::chat::shared::ChatAgentAvatar;

pub AgentButton = {{AgentButton}} <RoundedView> {
pub EntityButton = {{EntityButton}} <RoundedView> {
flow: Right,
width: Fill,
visible: false,
Expand Down Expand Up @@ -100,71 +101,105 @@ live_design!(
);

#[derive(Live, Widget, LiveHook)]
pub struct AgentButton {
pub struct EntityButton {
#[deref]
view: View,

#[rust]
agent: Option<MofaAgent>,

#[live(false)]
create_new_chat: bool,

#[live(false)]
select_agent_on_prompt: bool,
entity: Option<ChatEntityId>,
}

impl Widget for AgentButton {
impl Widget for EntityButton {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
self.view.handle_event(cx, event, scope);
self.widget_match_event(cx, event, scope);
}

fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
self.view.draw_walk(cx, scope, walk)
}
}

impl WidgetMatchEvent for AgentButton {
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions, _scope: &mut Scope) {
let Some(agent) = self.agent else { return };

impl EntityButton {
pub fn clicked(&self, actions: &Actions) -> bool {
if let Some(item) = actions.find_widget_action(self.view.widget_uid()) {
if let ViewAction::FingerDown(fd) = item.cast() {
if fd.tap_count == 1 {
if self.create_new_chat {
cx.action(ChatAction::Start(ChatHandler::Agent(agent)));
}

if self.select_agent_on_prompt {
cx.action(PromptInputAction::AgentSelected(agent));
}
}
return fd.tap_count == 1;
}
}

false
}
}

impl AgentButton {
pub fn set_agent(&mut self, agent: &MofaAgent, show_description: bool) {
pub fn get_entity_id(&self) -> Option<&ChatEntityId> {
self.entity.as_ref()
}

pub fn set_agent(&mut self, agent: &MofaAgent) {
self.set_entity(ChatEntityRef::Agent(agent));
}

pub fn set_entity(&mut self, entity: ChatEntityRef) {
self.visible = true;
self.label(id!(caption)).set_text(&agent.name());
self.chat_agent_avatar(id!(agent_avatar)).set_agent(agent);

self.view(id!(description)).set_visible(show_description);
if show_description {
self.view(id!(description.label))
.set_text(&agent.short_description());
let name_label = self.label(id!(caption));
let description_label = self.label(id!(description.label));
let mut avatar = self.chat_agent_avatar(id!(agent_avatar));

name_label.set_text(&entity.name());

if let ChatEntityRef::Agent(agent) = entity {
avatar.set_visible(true);
avatar.set_agent(agent);
description_label.set_text(&agent.short_description());
} else {
avatar.set_visible(false);
description_label.set_text("");
}

self.agent = Some(*agent);
self.entity = Some(entity.id());
}

pub fn set_description_visible(&mut self, visible: bool) {
self.view(id!(description)).set_visible(visible);
}
}

impl AgentButtonRef {
pub fn set_agent(&mut self, agent: &MofaAgent, show_description: bool) {
impl EntityButtonRef {
pub fn set_description_visible(&mut self, visible: bool) {
if let Some(mut inner) = self.borrow_mut() {
inner.set_description_visible(visible);
}
}

pub fn set_agent(&mut self, agent: &MofaAgent) {
if let Some(mut inner) = self.borrow_mut() {
inner.set_agent(agent);
}
}

pub fn set_entity(&mut self, entity: ChatEntityRef) {
if let Some(mut inner) = self.borrow_mut() {
inner.set_agent(agent, show_description);
inner.set_entity(entity);
}
}

pub fn get_entity_id(&self) -> Option<Ref<ChatEntityId>> {
if let Some(inner) = self.borrow() {
if inner.entity.is_none() {
return None;
}

Some(Ref::map(inner, |inner| inner.entity.as_ref().unwrap()))
} else {
None
}
}

pub fn clicked(&self, actions: &Actions) -> bool {
if let Some(inner) = self.borrow() {
inner.clicked(actions)
} else {
false
}
}
}
Loading

0 comments on commit 1161384

Please sign in to comment.