first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -1,17 +1,21 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::ai::agent::conversation::AIConversationId;
|
||||
use crate::ai::ambient_agents::AmbientAgentTaskId;
|
||||
use crate::ai::blocklist::agent_view::{AgentViewController, AgentViewControllerEvent};
|
||||
use crate::ai::blocklist::BlocklistAIHistoryModel;
|
||||
use crate::terminal::model::session::active_session::ActiveSession;
|
||||
use galaxyui::{
|
||||
AppContext, Entity, EntityId, ModelContext, ModelHandle, SingletonEntity, WeakModelHandle,
|
||||
WindowId,
|
||||
};
|
||||
|
||||
use crate::ai::agent::conversation::AIConversationId;
|
||||
use crate::ai::agent_conversations_model::{AgentConversationEntry, AgentConversationEntryId};
|
||||
use crate::ai::ambient_agents::AmbientAgentTaskId;
|
||||
use crate::ai::blocklist::agent_view::{AgentViewController, AgentViewControllerEvent};
|
||||
use crate::ai::blocklist::orchestration_event_streamer::{
|
||||
register_agent_event_consumer, unregister_agent_event_consumer,
|
||||
};
|
||||
use crate::ai::blocklist::BlocklistAIHistoryModel;
|
||||
use crate::terminal::model::session::active_session::ActiveSession;
|
||||
|
||||
/// Contains the handles needed to track an active agent view.
|
||||
struct ActiveAgentViewHandles {
|
||||
controller: WeakModelHandle<AgentViewController>,
|
||||
@@ -44,6 +48,17 @@ pub enum ConversationOrTaskId {
|
||||
TaskId(AmbientAgentTaskId),
|
||||
}
|
||||
|
||||
impl From<AgentConversationEntryId> for ConversationOrTaskId {
|
||||
fn from(id: AgentConversationEntryId) -> Self {
|
||||
match id {
|
||||
AgentConversationEntryId::Conversation(conversation_id) => {
|
||||
ConversationOrTaskId::ConversationId(conversation_id)
|
||||
}
|
||||
AgentConversationEntryId::AmbientRun(task_id) => ConversationOrTaskId::TaskId(task_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConversationOrTaskId {
|
||||
pub fn conversation_id(&self) -> Option<AIConversationId> {
|
||||
match self {
|
||||
@@ -94,6 +109,34 @@ impl ActiveAgentViewsModel {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_focused_conversation_for_terminal(
|
||||
&mut self,
|
||||
terminal_view_id: EntityId,
|
||||
conversation_id: Option<ConversationOrTaskId>,
|
||||
) {
|
||||
for focused_terminal_state in self.focused_terminal_states.values_mut() {
|
||||
if focused_terminal_state.focused_terminal_id == terminal_view_id
|
||||
&& !matches!(
|
||||
focused_terminal_state.active_conversation_id,
|
||||
Some(ConversationOrTaskId::TaskId(_))
|
||||
)
|
||||
{
|
||||
focused_terminal_state.active_conversation_id = conversation_id;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(last_focused_terminal_state) = &mut self.last_focused_terminal_state {
|
||||
if last_focused_terminal_state.focused_terminal_id == terminal_view_id
|
||||
&& !matches!(
|
||||
last_focused_terminal_state.active_conversation_id,
|
||||
Some(ConversationOrTaskId::TaskId(_))
|
||||
)
|
||||
{
|
||||
last_focused_terminal_state.active_conversation_id = conversation_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Register an agent view controller to track when the agent view is entered/exited.
|
||||
#[cfg_attr(target_family = "wasm", allow(dead_code))]
|
||||
pub fn register_agent_view_controller(
|
||||
@@ -122,7 +165,19 @@ impl ActiveAgentViewsModel {
|
||||
},
|
||||
);
|
||||
|
||||
ctx.subscribe_to_model(controller, move |model, event, ctx| match event {
|
||||
// On pane re-attach the controller's `agent_view_state` is still
|
||||
// `Active` while the unregister path has already torn down the
|
||||
// streamer consumer. Re-register here; the `EnteredAgentView`
|
||||
// subscription only fires on subsequent state transitions.
|
||||
if let Some(conversation_id) = controller
|
||||
.as_ref(ctx)
|
||||
.agent_view_state()
|
||||
.active_conversation_id()
|
||||
{
|
||||
register_agent_event_consumer(conversation_id, terminal_view_id, ctx);
|
||||
}
|
||||
|
||||
ctx.subscribe_to_model(controller, move |model, _, event, ctx| match event {
|
||||
AgentViewControllerEvent::EnteredAgentView {
|
||||
conversation_id, ..
|
||||
} => {
|
||||
@@ -132,37 +187,31 @@ impl ActiveAgentViewsModel {
|
||||
// Update the focused conversation in whichever window owns this terminal view.
|
||||
// We ignore agent view changes if we are focused on an ambient conversation,
|
||||
// as ambient conversation navigation operates at the task level instead of the conversation level.
|
||||
for focused_terminal_state in model.focused_terminal_states.values_mut() {
|
||||
if focused_terminal_state.focused_terminal_id == terminal_view_id
|
||||
&& !matches!(
|
||||
focused_terminal_state.active_conversation_id,
|
||||
Some(ConversationOrTaskId::TaskId(_))
|
||||
)
|
||||
{
|
||||
focused_terminal_state.active_conversation_id = Some(conv_id);
|
||||
}
|
||||
}
|
||||
model.update_focused_conversation_for_terminal(terminal_view_id, Some(conv_id));
|
||||
// Bridge the controller's lifecycle into the streamer's
|
||||
// per-conversation consumer registry.
|
||||
register_agent_event_consumer(*conversation_id, terminal_view_id, ctx);
|
||||
// Emit so subscribers can move this conversation to the Active section.
|
||||
ctx.emit(ActiveAgentViewsEvent::TerminalViewFocused);
|
||||
}
|
||||
AgentViewControllerEvent::ExitedAgentView {
|
||||
conversation_id, ..
|
||||
conversation_id,
|
||||
is_exit_before_new_entrance,
|
||||
..
|
||||
} => {
|
||||
// Skip if this exit is part of an in-place switch — the follow-up
|
||||
// entrance will register the new conversation's consumer.
|
||||
if *is_exit_before_new_entrance {
|
||||
return;
|
||||
}
|
||||
|
||||
model
|
||||
.last_opened_times
|
||||
.remove(&ConversationOrTaskId::ConversationId(*conversation_id));
|
||||
|
||||
// Clear the focused conversation in whichever window owns this terminal view.
|
||||
for state in model.focused_terminal_states.values_mut() {
|
||||
if state.focused_terminal_id == terminal_view_id
|
||||
&& !matches!(
|
||||
state.active_conversation_id,
|
||||
Some(ConversationOrTaskId::TaskId(_))
|
||||
)
|
||||
{
|
||||
state.active_conversation_id = None;
|
||||
}
|
||||
}
|
||||
model.update_focused_conversation_for_terminal(terminal_view_id, None);
|
||||
unregister_agent_event_consumer(*conversation_id, terminal_view_id, ctx);
|
||||
// Emit so subscribers can move this conversation to the Past section.
|
||||
ctx.emit(ActiveAgentViewsEvent::ConversationClosed {
|
||||
conversation_id: *conversation_id,
|
||||
@@ -198,6 +247,9 @@ impl ActiveAgentViewsModel {
|
||||
}
|
||||
|
||||
if let Some(conversation_id) = closed_conversation_id {
|
||||
// The pane-close path bypasses exit_agent_view_internal, so
|
||||
// unregister the streamer consumer here.
|
||||
unregister_agent_event_consumer(conversation_id, terminal_pane_id, ctx);
|
||||
ctx.emit(ActiveAgentViewsEvent::ConversationClosed { conversation_id });
|
||||
}
|
||||
}
|
||||
@@ -252,6 +304,12 @@ impl ActiveAgentViewsModel {
|
||||
.and_then(|state| state.active_conversation_id)
|
||||
}
|
||||
|
||||
pub fn get_focused_terminal_view_id(&self, window_id: WindowId) -> Option<EntityId> {
|
||||
self.focused_terminal_states
|
||||
.get(&window_id)
|
||||
.map(|state| state.focused_terminal_id)
|
||||
}
|
||||
|
||||
/// Get the last focused terminal view id (persisted across non-terminal focus changes).
|
||||
pub fn get_last_focused_terminal_id(&self) -> Option<EntityId> {
|
||||
self.last_focused_terminal_state
|
||||
@@ -259,6 +317,14 @@ impl ActiveAgentViewsModel {
|
||||
.map(|state| state.focused_terminal_id)
|
||||
}
|
||||
|
||||
/// Get the most recent focused conversation or ambient task ID, persisted
|
||||
/// across non-terminal focus changes.
|
||||
pub fn get_last_focused_conversation(&self) -> Option<ConversationOrTaskId> {
|
||||
self.last_focused_terminal_state
|
||||
.as_ref()
|
||||
.and_then(|state| state.active_conversation_id)
|
||||
}
|
||||
|
||||
/// Returns the focused conversation ID if it's a new/empty conversation view.
|
||||
/// Only returns Some if the focused agent view was just created to start a new
|
||||
/// conversation (i.e. has no exchanges yet).
|
||||
@@ -307,6 +373,8 @@ impl ActiveAgentViewsModel {
|
||||
task_id: AmbientAgentTaskId,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.ambient_sessions
|
||||
.retain(|view_id, id| *view_id == terminal_view_id || *id != task_id);
|
||||
let existing = self.ambient_sessions.insert(terminal_view_id, task_id);
|
||||
if existing != Some(task_id) {
|
||||
self.last_opened_times
|
||||
@@ -323,9 +391,11 @@ impl ActiveAgentViewsModel {
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if let Some(task_id) = self.ambient_sessions.remove(&terminal_view_id) {
|
||||
self.last_opened_times
|
||||
.remove(&ConversationOrTaskId::TaskId(task_id));
|
||||
ctx.emit(ActiveAgentViewsEvent::AmbientSessionClosed { task_id });
|
||||
if !self.ambient_sessions.values().any(|id| *id == task_id) {
|
||||
self.last_opened_times
|
||||
.remove(&ConversationOrTaskId::TaskId(task_id));
|
||||
ctx.emit(ActiveAgentViewsEvent::AmbientSessionClosed { task_id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,6 +513,23 @@ impl ActiveAgentViewsModel {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_terminal_view_id_for_entry(
|
||||
&self,
|
||||
entry: &AgentConversationEntry,
|
||||
ctx: &AppContext,
|
||||
) -> Option<EntityId> {
|
||||
if let Some(task_id) = entry.identity.ambient_agent_task_id {
|
||||
if let Some(terminal_view_id) = self.get_terminal_view_id_for_ambient_task(task_id) {
|
||||
return Some(terminal_view_id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(conversation_id) = entry.identity.local_conversation_id {
|
||||
return self.get_terminal_view_id_for_conversation(conversation_id, ctx);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
/// Get all currently active conversation IDs.
|
||||
/// A conversation is active if it is open and a query has been sent since it was last opened.
|
||||
/// New (empty) conversations and ambient sessions are always considered active when open.
|
||||
|
||||
Reference in New Issue
Block a user