first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
@@ -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)),
))
+9 -13
View File
@@ -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)
}
}
+3 -5
View File
@@ -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 => (),