7376 lines · 294.5 KB · Rust
RawDownload
use cloud_api_types::{SubmitAgentThreadFeedbackBody, SubmitAgentThreadFeedbackCommentsBody};use gpui::{Corner, List};use language_model::LanguageModelEffortLevel;use settings::update_settings_file;use ui::{ButtonLike, SplitButton, SplitButtonStyle, Tab}; use super::*; #[derive(Default)]struct ThreadFeedbackState {    feedback: Option<ThreadFeedback>,    comments_editor: Option<Entity<Editor>>,} impl ThreadFeedbackState {    pub fn submit(        &mut self,        thread: Entity<AcpThread>,        feedback: ThreadFeedback,        window: &mut Window,        cx: &mut App,    ) {        let Some(telemetry) = thread.read(cx).connection().telemetry() else {            return;        };         let project = thread.read(cx).project().read(cx);        let client = project.client();        let user_store = project.user_store();        let organization = user_store.read(cx).current_organization();         if self.feedback == Some(feedback) {            return;        }         self.feedback = Some(feedback);        match feedback {            ThreadFeedback::Positive => {                self.comments_editor = None;            }            ThreadFeedback::Negative => {                self.comments_editor = Some(Self::build_feedback_comments_editor(window, cx));            }        }        let session_id = thread.read(cx).session_id().clone();        let agent_telemetry_id = thread.read(cx).connection().telemetry_id();        let task = telemetry.thread_data(&session_id, cx);        let rating = match feedback {            ThreadFeedback::Positive => "positive",            ThreadFeedback::Negative => "negative",        };        cx.background_spawn(async move {            let thread = task.await?;             client                .cloud_client()                .submit_agent_feedback(SubmitAgentThreadFeedbackBody {                    organization_id: organization.map(|organization| organization.id.clone()),                    agent: agent_telemetry_id.to_string(),                    session_id: session_id.to_string(),                    rating: rating.to_string(),                    thread,                })                .await?;             anyhow::Ok(())        })        .detach_and_log_err(cx);    }     pub fn submit_comments(&mut self, thread: Entity<AcpThread>, cx: &mut App) {        let Some(telemetry) = thread.read(cx).connection().telemetry() else {            return;        };         let Some(comments) = self            .comments_editor            .as_ref()            .map(|editor| editor.read(cx).text(cx))            .filter(|text| !text.trim().is_empty())        else {            return;        };         self.comments_editor.take();         let project = thread.read(cx).project().read(cx);        let client = project.client();        let user_store = project.user_store();        let organization = user_store.read(cx).current_organization();         let session_id = thread.read(cx).session_id().clone();        let agent_telemetry_id = thread.read(cx).connection().telemetry_id();        let task = telemetry.thread_data(&session_id, cx);        cx.background_spawn(async move {            let thread = task.await?;             client                .cloud_client()                .submit_agent_feedback_comments(SubmitAgentThreadFeedbackCommentsBody {                    organization_id: organization.map(|organization| organization.id.clone()),                    agent: agent_telemetry_id.to_string(),                    session_id: session_id.to_string(),                    comments,                    thread,                })                .await?;             anyhow::Ok(())        })        .detach_and_log_err(cx);    }     pub fn clear(&mut self) {        *self = Self::default()    }     pub fn dismiss_comments(&mut self) {        self.comments_editor.take();    }     fn build_feedback_comments_editor(window: &mut Window, cx: &mut App) -> Entity<Editor> {        let buffer = cx.new(|cx| {            let empty_string = String::new();            MultiBuffer::singleton(cx.new(|cx| Buffer::local(empty_string, cx)), cx)        });         let editor = cx.new(|cx| {            let mut editor = Editor::new(                editor::EditorMode::AutoHeight {                    min_lines: 1,                    max_lines: Some(4),                },                buffer,                None,                window,                cx,            );            editor.set_placeholder_text(                "What went wrong? Share your feedback so we can improve.",                window,                cx,            );            editor        });         editor.read(cx).focus_handle(cx).focus(window, cx);        editor    }} #[derive(Default, Clone, Copy)]struct DiffStats {    lines_added: u32,    lines_removed: u32,} impl DiffStats {    fn single_file(buffer: &Buffer, diff: &BufferDiff, cx: &App) -> Self {        let mut stats = DiffStats::default();        let diff_snapshot = diff.snapshot(cx);        let buffer_snapshot = buffer.snapshot();        let base_text = diff_snapshot.base_text();         for hunk in diff_snapshot.hunks(&buffer_snapshot) {            let added_rows = hunk.range.end.row.saturating_sub(hunk.range.start.row);            stats.lines_added += added_rows;             let base_start = hunk.diff_base_byte_range.start.to_point(base_text).row;            let base_end = hunk.diff_base_byte_range.end.to_point(base_text).row;            let removed_rows = base_end.saturating_sub(base_start);            stats.lines_removed += removed_rows;        }         stats    }     fn all_files(changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>, cx: &App) -> Self {        let mut total = DiffStats::default();        for (buffer, diff) in changed_buffers {            let stats = DiffStats::single_file(buffer.read(cx), diff.read(cx), cx);            total.lines_added += stats.lines_added;            total.lines_removed += stats.lines_removed;        }        total    }} pub struct AcpThreadView {    pub id: acp::SessionId,    pub parent_id: Option<acp::SessionId>,    pub login: Option<task::SpawnInTerminal>, // is some <=> Active | Unauthenticated    pub thread: Entity<AcpThread>,    pub(crate) conversation: Entity<super::Conversation>,    pub server_view: WeakEntity<AcpServerView>,    pub agent_icon: IconName,    pub agent_name: SharedString,    pub focus_handle: FocusHandle,    pub workspace: WeakEntity<Workspace>,    pub entry_view_state: Entity<EntryViewState>,    pub title_editor: Entity<Editor>,    pub config_options_view: Option<Entity<ConfigOptionsView>>,    pub mode_selector: Option<Entity<ModeSelector>>,    pub model_selector: Option<Entity<AcpModelSelectorPopover>>,    pub profile_selector: Option<Entity<ProfileSelector>>,    pub permission_dropdown_handle: PopoverMenuHandle<ContextMenu>,    pub thread_retry_status: Option<RetryStatus>,    pub(super) thread_error: Option<ThreadError>,    pub thread_error_markdown: Option<Entity<Markdown>>,    pub token_limit_callout_dismissed: bool,    pub last_token_limit_telemetry: Option<acp_thread::TokenUsageRatio>,    thread_feedback: ThreadFeedbackState,    pub list_state: ListState,    pub prompt_capabilities: Rc<RefCell<PromptCapabilities>>,    pub available_commands: Rc<RefCell<Vec<agent_client_protocol::AvailableCommand>>>,    /// Tracks which tool calls have their content/output expanded.    /// Used for showing/hiding tool call results, terminal output, etc.    pub expanded_tool_calls: HashSet<agent_client_protocol::ToolCallId>,    pub expanded_tool_call_raw_inputs: HashSet<agent_client_protocol::ToolCallId>,    pub expanded_thinking_blocks: HashSet<(usize, usize)>,    pub subagent_scroll_handles: RefCell<HashMap<agent_client_protocol::SessionId, ScrollHandle>>,    pub edits_expanded: bool,    pub plan_expanded: bool,    pub queue_expanded: bool,    pub editor_expanded: bool,    pub should_be_following: bool,    pub editing_message: Option<usize>,    pub local_queued_messages: Vec<QueuedMessage>,    pub queued_message_editors: Vec<Entity<MessageEditor>>,    pub queued_message_editor_subscriptions: Vec<Subscription>,    pub last_synced_queue_length: usize,    pub turn_fields: TurnFields,    pub discarded_partial_edits: HashSet<agent_client_protocol::ToolCallId>,    pub is_loading_contents: bool,    pub new_server_version_available: Option<SharedString>,    pub resumed_without_history: bool,    /// Tracks the selected granularity index for each tool call's permission dropdown.    /// The index corresponds to the position in the allow_options list.    /// Default is the last option (index pointing to "Only this time").    pub selected_permission_granularity: HashMap<agent_client_protocol::ToolCallId, usize>,    pub resume_thread_metadata: Option<AgentSessionInfo>,    pub _cancel_task: Option<Task<()>>,    pub skip_queue_processing_count: usize,    pub user_interrupted_generation: bool,    pub can_fast_track_queue: bool,    pub hovered_edited_file_buttons: Option<usize>,    pub in_flight_prompt: Option<Vec<acp::ContentBlock>>,    pub _subscriptions: Vec<Subscription>,    pub message_editor: Entity<MessageEditor>,    pub add_context_menu_handle: PopoverMenuHandle<ContextMenu>,    pub thinking_effort_menu_handle: PopoverMenuHandle<ContextMenu>,    pub project: WeakEntity<Project>,    pub recent_history_entries: Vec<AgentSessionInfo>,    pub hovered_recent_history_item: Option<usize>,    pub show_codex_windows_warning: bool,    pub history: Entity<AcpThreadHistory>,    pub _history_subscription: Subscription,}impl Focusable for AcpThreadView {    fn focus_handle(&self, cx: &App) -> FocusHandle {        if self.parent_id.is_some() {            self.focus_handle.clone()        } else {            self.active_editor(cx).focus_handle(cx)        }    }} #[derive(Default)]pub struct TurnFields {    pub _turn_timer_task: Option<Task<()>>,    pub last_turn_duration: Option<Duration>,    pub last_turn_tokens: Option<u64>,    pub turn_generation: usize,    pub turn_started_at: Option<Instant>,    pub turn_tokens: Option<u64>,} impl AcpThreadView {    pub(crate) fn new(        parent_id: Option<acp::SessionId>,        thread: Entity<AcpThread>,        conversation: Entity<super::Conversation>,        login: Option<task::SpawnInTerminal>,        server_view: WeakEntity<AcpServerView>,        agent_icon: IconName,        agent_name: SharedString,        agent_display_name: SharedString,        workspace: WeakEntity<Workspace>,        entry_view_state: Entity<EntryViewState>,        config_options_view: Option<Entity<ConfigOptionsView>>,        mode_selector: Option<Entity<ModeSelector>>,        model_selector: Option<Entity<AcpModelSelectorPopover>>,        profile_selector: Option<Entity<ProfileSelector>>,        list_state: ListState,        prompt_capabilities: Rc<RefCell<PromptCapabilities>>,        available_commands: Rc<RefCell<Vec<agent_client_protocol::AvailableCommand>>>,        resumed_without_history: bool,        resume_thread_metadata: Option<AgentSessionInfo>,        project: WeakEntity<Project>,        thread_store: Option<Entity<ThreadStore>>,        history: Entity<AcpThreadHistory>,        prompt_store: Option<Entity<PromptStore>>,        initial_content: Option<AgentInitialContent>,        mut subscriptions: Vec<Subscription>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let id = thread.read(cx).session_id().clone();         let placeholder = placeholder_text(agent_display_name.as_ref(), false);         let history_subscription = cx.observe(&history, |this, history, cx| {            this.update_recent_history_from_cache(&history, cx);        });         let mut should_auto_submit = false;         let message_editor = cx.new(|cx| {            let mut editor = MessageEditor::new(                workspace.clone(),                project.clone(),                thread_store,                history.downgrade(),                prompt_store,                prompt_capabilities.clone(),                available_commands.clone(),                agent_name.clone(),                &placeholder,                editor::EditorMode::AutoHeight {                    min_lines: AgentSettings::get_global(cx).message_editor_min_lines,                    max_lines: Some(AgentSettings::get_global(cx).set_message_editor_max_lines()),                },                window,                cx,            );            if let Some(content) = initial_content {                match content {                    AgentInitialContent::ThreadSummary(entry) => {                        editor.insert_thread_summary(entry, window, cx);                    }                    AgentInitialContent::ContentBlock {                        blocks,                        auto_submit,                    } => {                        should_auto_submit = auto_submit;                        editor.set_message(blocks, window, cx);                    }                }            }            editor        });         let show_codex_windows_warning = cfg!(windows)            && project.upgrade().is_some_and(|p| p.read(cx).is_local())            && agent_name == "Codex";         let title_editor = {            let can_edit = thread.update(cx, |thread, cx| thread.can_set_title(cx));            let editor = cx.new(|cx| {                let mut editor = Editor::single_line(window, cx);                editor.set_text(thread.read(cx).title(), window, cx);                editor.set_read_only(!can_edit);                editor            });            subscriptions.push(cx.subscribe_in(&editor, window, Self::handle_title_editor_event));            editor        };         subscriptions.push(cx.subscribe_in(            &entry_view_state,            window,            Self::handle_entry_view_event,        ));         subscriptions.push(cx.subscribe_in(            &message_editor,            window,            Self::handle_message_editor_event,        ));         let recent_history_entries = history.read(cx).get_recent_sessions(3);         let mut this = Self {            id,            parent_id,            focus_handle: cx.focus_handle(),            thread,            conversation,            login,            server_view,            agent_icon,            agent_name,            workspace,            entry_view_state,            title_editor,            config_options_view,            mode_selector,            model_selector,            profile_selector,            list_state,            prompt_capabilities,            available_commands,            resumed_without_history,            resume_thread_metadata,            _subscriptions: subscriptions,            permission_dropdown_handle: PopoverMenuHandle::default(),            thread_retry_status: None,            thread_error: None,            thread_error_markdown: None,            token_limit_callout_dismissed: false,            last_token_limit_telemetry: None,            thread_feedback: Default::default(),            expanded_tool_calls: HashSet::default(),            expanded_tool_call_raw_inputs: HashSet::default(),            expanded_thinking_blocks: HashSet::default(),            subagent_scroll_handles: RefCell::new(HashMap::default()),            edits_expanded: false,            plan_expanded: false,            queue_expanded: true,            editor_expanded: false,            should_be_following: false,            editing_message: None,            local_queued_messages: Vec::new(),            queued_message_editors: Vec::new(),            queued_message_editor_subscriptions: Vec::new(),            last_synced_queue_length: 0,            turn_fields: TurnFields::default(),            discarded_partial_edits: HashSet::default(),            is_loading_contents: false,            new_server_version_available: None,            selected_permission_granularity: HashMap::default(),            _cancel_task: None,            skip_queue_processing_count: 0,            user_interrupted_generation: false,            can_fast_track_queue: false,            hovered_edited_file_buttons: None,            in_flight_prompt: None,            message_editor,            add_context_menu_handle: PopoverMenuHandle::default(),            thinking_effort_menu_handle: PopoverMenuHandle::default(),            project,            recent_history_entries,            hovered_recent_history_item: None,            history,            _history_subscription: history_subscription,            show_codex_windows_warning,        };        if should_auto_submit {            this.send(window, cx);        }        this    }     pub fn handle_message_editor_event(        &mut self,        _editor: &Entity<MessageEditor>,        event: &MessageEditorEvent,        window: &mut Window,        cx: &mut Context<Self>,    ) {        match event {            MessageEditorEvent::Send => self.send(window, cx),            MessageEditorEvent::SendImmediately => self.interrupt_and_send(window, cx),            MessageEditorEvent::Cancel => self.cancel_generation(cx),            MessageEditorEvent::Focus => {                self.cancel_editing(&Default::default(), window, cx);            }            MessageEditorEvent::LostFocus => {}        }    }     pub(crate) fn as_native_connection(        &self,        cx: &App,    ) -> Option<Rc<agent::NativeAgentConnection>> {        let acp_thread = self.thread.read(cx);        acp_thread.connection().clone().downcast()    }     pub(crate) fn as_native_thread(&self, cx: &App) -> Option<Entity<agent::Thread>> {        let acp_thread = self.thread.read(cx);        self.as_native_connection(cx)?            .thread(acp_thread.session_id(), cx)    }     pub fn current_model_id(&self, cx: &App) -> Option<String> {        let selector = self.model_selector.as_ref()?;        let model = selector.read(cx).active_model(cx)?;        Some(model.id.to_string())    }     pub fn current_mode_id(&self, cx: &App) -> Option<Arc<str>> {        if let Some(thread) = self.as_native_thread(cx) {            Some(thread.read(cx).profile().0.clone())        } else {            let mode_selector = self.mode_selector.as_ref()?;            Some(mode_selector.read(cx).mode().0)        }    }

Lines 1–500 of 7376.