first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -3,15 +3,20 @@
|
||||
use galaxyui::{AppContext, Entity, ModelHandle};
|
||||
use itertools::Itertools;
|
||||
use ordered_float::OrderedFloat;
|
||||
use galaxyui::{AppContext, Entity, ModelHandle, SingletonEntity};
|
||||
|
||||
use crate::ai::agent_conversations_model::{
|
||||
AgentConversationEntry, AgentConversationEntryId, AgentManagementFilters,
|
||||
};
|
||||
use crate::ai::blocklist::agent_view::AgentViewController;
|
||||
use crate::ai::conversation_navigation::ConversationNavigationData;
|
||||
use crate::search::data_source::{Query, QueryFilter, QueryResult};
|
||||
use crate::search::mixer::DataSourceRunErrorWrapper;
|
||||
use crate::search::SyncDataSource;
|
||||
use crate::terminal::input::conversations::search_item::ConversationSearchItem;
|
||||
use crate::terminal::input::conversations::AcceptConversation;
|
||||
use crate::terminal::model::session::active_session::ActiveSession;
|
||||
use crate::workspace::RestoreConversationLayout;
|
||||
use crate::AgentConversationsModel;
|
||||
|
||||
pub struct ConversationMenuDataSource {
|
||||
agent_view_controller: ModelHandle<AgentViewController>,
|
||||
@@ -28,6 +33,16 @@ impl ConversationMenuDataSource {
|
||||
active_session,
|
||||
}
|
||||
}
|
||||
|
||||
fn entries(&self, app: &AppContext) -> Vec<AgentConversationEntry> {
|
||||
AgentConversationsModel::as_ref(app)
|
||||
.get_entries(&AgentManagementFilters::default(), app)
|
||||
.into_iter()
|
||||
.filter(|entry: &AgentConversationEntry| {
|
||||
entry.has_open_action(Some(RestoreConversationLayout::ActivePane), app)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl SyncDataSource for ConversationMenuDataSource {
|
||||
@@ -38,14 +53,14 @@ impl SyncDataSource for ConversationMenuDataSource {
|
||||
query: &Query,
|
||||
app: &AppContext,
|
||||
) -> Result<Vec<QueryResult<Self::Action>>, DataSourceRunErrorWrapper> {
|
||||
let conversation_navigation_data = ConversationNavigationData::all_conversations(app);
|
||||
let conversation_entries = self.entries(app);
|
||||
let query_text = query.text.trim().to_lowercase();
|
||||
|
||||
let active_conversation_id = self
|
||||
let active_item_id = self
|
||||
.agent_view_controller
|
||||
.as_ref(app)
|
||||
.agent_view_state()
|
||||
.active_conversation_id();
|
||||
.active_conversation_id()
|
||||
.map(AgentConversationEntryId::Conversation);
|
||||
|
||||
let filter_by_cwd = query
|
||||
.filters
|
||||
@@ -63,16 +78,17 @@ impl SyncDataSource for ConversationMenuDataSource {
|
||||
// whose most recent directory (falling back to initial directory) matches
|
||||
// the session's current working directory. If we can't determine the
|
||||
// session CWD, leave the results unfiltered.
|
||||
let matches_directory = |data: &ConversationNavigationData| -> bool {
|
||||
let matches_directory = |entry: &AgentConversationEntry| -> bool {
|
||||
if !filter_by_cwd {
|
||||
return true;
|
||||
}
|
||||
let Some(session_pwd) = session_pwd.as_deref() else {
|
||||
return true;
|
||||
};
|
||||
data.latest_working_directory
|
||||
entry
|
||||
.display
|
||||
.working_directory
|
||||
.as_deref()
|
||||
.or(data.initial_working_directory.as_deref())
|
||||
.is_some_and(|dir| {
|
||||
dir.trim_end_matches(std::path::MAIN_SEPARATOR)
|
||||
== session_pwd.trim_end_matches(std::path::MAIN_SEPARATOR)
|
||||
@@ -85,31 +101,31 @@ impl SyncDataSource for ConversationMenuDataSource {
|
||||
|
||||
// In the zero state, sort conversations in the active pane above all other conversations.
|
||||
// Within each segment, sort to reverse chronological order.
|
||||
Ok(conversation_navigation_data
|
||||
Ok(conversation_entries
|
||||
.into_iter()
|
||||
// Don't show the currently open conversation, that's redundant.
|
||||
.filter(|data| Some(data.id()) != active_conversation_id)
|
||||
.filter(|data| matches_directory(data))
|
||||
.sorted_by(|a, b| b.last_updated.cmp(&a.last_updated))
|
||||
.filter(|entry| Some(entry.id) != active_item_id)
|
||||
.filter(|entry| matches_directory(entry))
|
||||
.sorted_by(|a, b| b.display.last_updated.cmp(&a.display.last_updated))
|
||||
.take(DEFAULT_RESULT_COUNT)
|
||||
.map(|navigation_data| {
|
||||
QueryResult::from(ConversationSearchItem::new(navigation_data, app))
|
||||
.map(|conversation_entry| {
|
||||
QueryResult::from(ConversationSearchItem::new(conversation_entry))
|
||||
})
|
||||
.rev()
|
||||
.collect())
|
||||
} else {
|
||||
let mut search_results = conversation_navigation_data
|
||||
let mut search_results = conversation_entries
|
||||
.into_iter()
|
||||
.filter_map(|navigation_data| {
|
||||
if Some(navigation_data.id()) == active_conversation_id {
|
||||
.filter_map(|entry| {
|
||||
if Some(entry.id) == active_item_id {
|
||||
// Don't show the currently open conversation, that's redundant.
|
||||
return None;
|
||||
}
|
||||
if !matches_directory(&navigation_data) {
|
||||
if !matches_directory(&entry) {
|
||||
return None;
|
||||
}
|
||||
let match_result = fuzzy_match::match_indices_case_insensitive(
|
||||
&navigation_data.title,
|
||||
&entry.display.title,
|
||||
&query_text,
|
||||
)?;
|
||||
|
||||
@@ -119,7 +135,7 @@ impl SyncDataSource for ConversationMenuDataSource {
|
||||
}
|
||||
|
||||
Some(QueryResult::from(
|
||||
ConversationSearchItem::new(navigation_data, app)
|
||||
ConversationSearchItem::new(entry)
|
||||
.with_name_match_result(Some(match_result.clone()))
|
||||
.with_score(OrderedFloat(match_result.score as f64)),
|
||||
))
|
||||
|
||||
@@ -4,14 +4,14 @@ mod data_source;
|
||||
mod search_item;
|
||||
mod view;
|
||||
|
||||
pub use view::{InlineConversationMenuEvent, InlineConversationMenuView};
|
||||
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxyui::{keymap::Keystroke, SingletonEntity};
|
||||
use pathfinder_color::ColorU;
|
||||
pub use view::{InlineConversationMenuEvent, InlineConversationMenuView};
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxyui::keymap::Keystroke;
|
||||
use galaxyui::SingletonEntity;
|
||||
|
||||
use crate::ai::active_agent_views_model::{ActiveAgentViewsModel, ConversationOrTaskId};
|
||||
use crate::ai::conversation_navigation::ConversationNavigationData;
|
||||
use crate::ai::agent_conversations_model::AgentConversationEntryId;
|
||||
use crate::terminal::input::inline_menu::{
|
||||
default_navigation_message_items, InlineMenuAction, InlineMenuMessageArgs, InlineMenuRowAction,
|
||||
InlineMenuType,
|
||||
@@ -30,7 +30,7 @@ pub enum InlineConversationMenuTab {
|
||||
/// Action emitted when enter is hit on a conversation the inline conversation menu.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AcceptConversation {
|
||||
pub navigation_data: ConversationNavigationData,
|
||||
pub item_id: AgentConversationEntryId,
|
||||
}
|
||||
|
||||
impl InlineMenuAction for AcceptConversation {
|
||||
@@ -45,11 +45,9 @@ impl InlineMenuAction for AcceptConversation {
|
||||
let mut items = Vec::new();
|
||||
|
||||
if let Some(item) = inline_menu_model.selected_item() {
|
||||
let data = &item.navigation_data;
|
||||
|
||||
let active_ids =
|
||||
ActiveAgentViewsModel::as_ref(app).get_all_active_conversation_ids(app);
|
||||
let is_active = active_ids.contains(&ConversationOrTaskId::ConversationId(data.id));
|
||||
let is_active = active_ids.contains(&ConversationOrTaskId::from(item.item_id));
|
||||
|
||||
let text = if is_active {
|
||||
" go to conversation"
|
||||
@@ -57,7 +55,7 @@ impl InlineMenuAction for AcceptConversation {
|
||||
" continue in this pane"
|
||||
};
|
||||
|
||||
let navigation_data = data.clone();
|
||||
let item_id = item.item_id;
|
||||
items.push(MessageItem::clickable(
|
||||
vec![
|
||||
MessageItem::keystroke(Keystroke {
|
||||
@@ -68,9 +66,7 @@ impl InlineMenuAction for AcceptConversation {
|
||||
],
|
||||
move |ctx| {
|
||||
ctx.dispatch_typed_action(InlineMenuRowAction::Accept {
|
||||
item: AcceptConversation {
|
||||
navigation_data: navigation_data.clone(),
|
||||
},
|
||||
item: AcceptConversation { item_id },
|
||||
cmd_or_ctrl_enter: false,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -12,11 +12,9 @@ use galaxyui::text_layout::ClipConfig;
|
||||
use galaxyui::{AppContext, Element, SingletonEntity};
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use crate::ai::active_agent_views_model::{ActiveAgentViewsModel, ConversationOrTaskId};
|
||||
use crate::ai::agent::conversation::ConversationStatus;
|
||||
use crate::ai::blocklist::BlocklistAIHistoryModel;
|
||||
use crate::ai::conversation_navigation::ConversationNavigationData;
|
||||
use crate::ai::conversation_status_ui::{render_status_element, STATUS_ELEMENT_PADDING};
|
||||
use crate::ai::active_agent_views_model::ActiveAgentViewsModel;
|
||||
use crate::ai::agent_conversations_model::AgentConversationEntry;
|
||||
use crate::ai::conversation_status_ui::render_status_element;
|
||||
use crate::appearance::Appearance;
|
||||
use crate::search::{ItemHighlightState, SearchItem};
|
||||
use crate::terminal::input::conversations::AcceptConversation;
|
||||
@@ -26,24 +24,17 @@ use crate::util::time_format::format_approx_duration_from_now_utc;
|
||||
/// Search item for rendering a conversation in the inline conversation menu.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct ConversationSearchItem {
|
||||
navigation_data: ConversationNavigationData,
|
||||
entry: AgentConversationEntry,
|
||||
name_match_result: Option<FuzzyMatchResult>,
|
||||
score: OrderedFloat<f64>,
|
||||
conversation_status: Option<ConversationStatus>,
|
||||
}
|
||||
|
||||
impl ConversationSearchItem {
|
||||
pub fn new(navigation_data: ConversationNavigationData, app: &AppContext) -> Self {
|
||||
let history_model = BlocklistAIHistoryModel::as_ref(app);
|
||||
let conversation_status = history_model
|
||||
.conversation(&navigation_data.id)
|
||||
.map(|conversation| conversation.status().clone());
|
||||
|
||||
pub fn new(entry: AgentConversationEntry) -> Self {
|
||||
Self {
|
||||
navigation_data,
|
||||
entry,
|
||||
name_match_result: None,
|
||||
score: OrderedFloat(f64::MIN),
|
||||
conversation_status,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,26 +58,7 @@ impl SearchItem for ConversationSearchItem {
|
||||
appearance: &Appearance,
|
||||
) -> Box<dyn Element> {
|
||||
let icon_size = inline_styles::font_size(appearance);
|
||||
let icon = match &self.conversation_status {
|
||||
Some(conversation_status) => {
|
||||
render_status_element(conversation_status, icon_size, appearance)
|
||||
}
|
||||
None => {
|
||||
let icon_color = appearance
|
||||
.theme()
|
||||
.sub_text_color(appearance.theme().background());
|
||||
Container::new(
|
||||
ConstrainedBox::new(Icon::History.to_galaxyui_icon(icon_color).finish())
|
||||
.with_width(icon_size)
|
||||
.with_height(icon_size)
|
||||
.finish(),
|
||||
)
|
||||
.with_uniform_padding(STATUS_ELEMENT_PADDING)
|
||||
.with_background(coloru_with_opacity(icon_color.into(), 10))
|
||||
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.)))
|
||||
.finish()
|
||||
}
|
||||
};
|
||||
let icon = render_status_element(&self.entry.display.status, icon_size, appearance);
|
||||
|
||||
Container::new(icon)
|
||||
.with_margin_right(inline_styles::ICON_MARGIN)
|
||||
@@ -107,15 +79,18 @@ impl SearchItem for ConversationSearchItem {
|
||||
let primary_text_color = inline_styles::primary_text_color(theme, background_color.into());
|
||||
let secondary_text_color = theme.disabled_text_color(background_color.into());
|
||||
|
||||
let open_conversation_ids =
|
||||
ActiveAgentViewsModel::as_ref(app).get_all_open_conversation_ids(app);
|
||||
let is_active = open_conversation_ids.contains(&ConversationOrTaskId::ConversationId(
|
||||
self.navigation_data.id,
|
||||
));
|
||||
let active_agent_views = ActiveAgentViewsModel::as_ref(app);
|
||||
let open_terminal_view_id =
|
||||
active_agent_views.get_terminal_view_id_for_entry(&self.entry, app);
|
||||
let focused_terminal_view_id = app
|
||||
.windows()
|
||||
.active_window()
|
||||
.and_then(|window_id| active_agent_views.get_focused_terminal_view_id(window_id));
|
||||
|
||||
let secondary_suffix = " open in different pane";
|
||||
let title = &self.navigation_data.title;
|
||||
let should_show_suffix = is_active && !self.navigation_data.is_in_active_pane;
|
||||
let title = &self.entry.display.title;
|
||||
let should_show_suffix = open_terminal_view_id
|
||||
.is_some_and(|terminal_view_id| Some(terminal_view_id) != focused_terminal_view_id);
|
||||
let full_text = if should_show_suffix {
|
||||
format!("{title}{secondary_suffix}")
|
||||
} else {
|
||||
@@ -157,7 +132,7 @@ impl SearchItem for ConversationSearchItem {
|
||||
// We want the timestamp 'column' to have fixed width so clipping is consistent,
|
||||
// limit the timestamp width to about 10 chars.
|
||||
let timestamp = Text::new_inline(
|
||||
format_approx_duration_from_now_utc(self.navigation_data.last_updated.to_utc()),
|
||||
format_approx_duration_from_now_utc(self.entry.display.last_updated),
|
||||
appearance.ui_font_family(),
|
||||
font_size,
|
||||
)
|
||||
@@ -191,7 +166,7 @@ impl SearchItem for ConversationSearchItem {
|
||||
|
||||
fn accept_result(&self) -> Self::Action {
|
||||
AcceptConversation {
|
||||
navigation_data: self.navigation_data.clone(),
|
||||
item_id: self.entry.id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,6 +175,6 @@ impl SearchItem for ConversationSearchItem {
|
||||
}
|
||||
|
||||
fn accessibility_label(&self) -> String {
|
||||
format!("Conversation: {}", self.navigation_data.title)
|
||||
format!("Conversation: {}", self.entry.display.title)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ use galaxyui::elements::ChildView;
|
||||
use galaxyui::{Element, Entity, ModelHandle, SingletonEntity, View, ViewContext, ViewHandle};
|
||||
|
||||
use crate::ai::active_agent_views_model::ActiveAgentViewsModel;
|
||||
use crate::ai::agent_conversations_model::AgentConversationEntryId;
|
||||
use crate::ai::blocklist::agent_view::AgentViewController;
|
||||
use crate::ai::conversation_navigation::ConversationNavigationData;
|
||||
use crate::features::FeatureFlag;
|
||||
use crate::search::data_source::{Query, QueryFilter};
|
||||
use crate::search::mixer::SearchMixer;
|
||||
@@ -27,9 +27,7 @@ use crate::terminal::model::session::active_session::ActiveSession;
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum InlineConversationMenuEvent {
|
||||
/// User 'accepted' a conversation (hit enter).
|
||||
NavigateToConversation {
|
||||
conversation_navigation_data: Box<ConversationNavigationData>,
|
||||
},
|
||||
NavigateToConversation { item_id: AgentConversationEntryId },
|
||||
/// User dismissed the menu (escape or click).
|
||||
Dismissed,
|
||||
}
|
||||
@@ -104,7 +102,7 @@ impl InlineConversationMenuView {
|
||||
ctx.subscribe_to_view(&menu_view, |me, _, event, ctx| match event {
|
||||
InlineMenuEvent::AcceptedItem { item, .. } => {
|
||||
ctx.emit(InlineConversationMenuEvent::NavigateToConversation {
|
||||
conversation_navigation_data: Box::new(item.navigation_data.clone()),
|
||||
item_id: item.item_id,
|
||||
});
|
||||
}
|
||||
InlineMenuEvent::SelectedItem { .. } | InlineMenuEvent::NoResults => (),
|
||||
|
||||
Reference in New Issue
Block a user