use crate::thread::{ LastRestoreCheckpoint, MessageId, MessageSegment, RequestKind, Thread, ThreadError, ThreadEvent, ThreadFeedback,};use crate::thread_store::ThreadStore;use crate::tool_use::{PendingToolUseStatus, ToolUse, ToolUseStatus};use crate::ui::{ContextPill, ToolReadyPopUp, ToolReadyPopupEvent};use crate::AssistantPanel;use assistant_settings::AssistantSettings;use collections::HashMap;use editor::{Editor, MultiBuffer};use gpui::{ linear_color_stop, linear_gradient, list, percentage, pulsating_between, AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, DefiniteLength, EdgesRefinement, Empty, Entity, Focusable, Hsla, Length, ListAlignment, ListOffset, ListState, MouseButton, ScrollHandle, Stateful, StyleRefinement, Subscription, Task, TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity, WindowHandle,};use language::{Buffer, LanguageRegistry};use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role};use markdown::{Markdown, MarkdownStyle};use settings::Settings as _;use std::sync::Arc;use std::time::Duration;use theme::ThemeSettings;use ui::{prelude::*, Disclosure, IconButton, KeyBinding, Scrollbar, ScrollbarState, Tooltip};use util::ResultExt as _;use workspace::{OpenOptions, Workspace}; use crate::context_store::{refresh_context_store_text, ContextStore}; pub struct ActiveThread { language_registry: Arc<LanguageRegistry>, thread_store: Entity<ThreadStore>, thread: Entity<Thread>, context_store: Entity<ContextStore>, workspace: WeakEntity<Workspace>, save_thread_task: Option<Task<()>>, messages: Vec<MessageId>, list_state: ListState, scrollbar_state: ScrollbarState, rendered_messages_by_id: HashMap<MessageId, RenderedMessage>, rendered_tool_use_labels: HashMap<LanguageModelToolUseId, Entity<Markdown>>, editing_message: Option<(MessageId, EditMessageState)>, expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>, expanded_thinking_segments: HashMap<(MessageId, usize), bool>, last_error: Option<ThreadError>, pop_ups: Vec<WindowHandle<ToolReadyPopUp>>, _subscriptions: Vec<Subscription>, pop_up_subscriptions: HashMap<WindowHandle<ToolReadyPopUp>, Vec<Subscription>>,} struct RenderedMessage { language_registry: Arc<LanguageRegistry>, segments: Vec<RenderedMessageSegment>,} impl RenderedMessage { fn from_segments( segments: &[MessageSegment], language_registry: Arc<LanguageRegistry>, window: &Window, cx: &mut App, ) -> Self { let mut this = Self { language_registry, segments: Vec::with_capacity(segments.len()), }; for segment in segments { this.push_segment(segment, window, cx); } this } fn append_thinking(&mut self, text: &String, window: &Window, cx: &mut App) { if let Some(RenderedMessageSegment::Thinking { content, scroll_handle, }) = self.segments.last_mut() { content.update(cx, |markdown, cx| { markdown.append(text, cx); }); scroll_handle.scroll_to_bottom(); } else { self.segments.push(RenderedMessageSegment::Thinking { content: render_markdown(text.into(), self.language_registry.clone(), window, cx), scroll_handle: ScrollHandle::default(), }); } } fn append_text(&mut self, text: &String, window: &Window, cx: &mut App) { if let Some(RenderedMessageSegment::Text(markdown)) = self.segments.last_mut() { markdown.update(cx, |markdown, cx| markdown.append(text, cx)); } else { self.segments .push(RenderedMessageSegment::Text(render_markdown( SharedString::from(text), self.language_registry.clone(), window, cx, ))); } } fn push_segment(&mut self, segment: &MessageSegment, window: &Window, cx: &mut App) { let rendered_segment = match segment { MessageSegment::Thinking(text) => RenderedMessageSegment::Thinking { content: render_markdown(text.into(), self.language_registry.clone(), window, cx), scroll_handle: ScrollHandle::default(), }, MessageSegment::Text(text) => RenderedMessageSegment::Text(render_markdown( text.into(), self.language_registry.clone(), window, cx, )), }; self.segments.push(rendered_segment); }} enum RenderedMessageSegment { Thinking { content: Entity<Markdown>, scroll_handle: ScrollHandle, }, Text(Entity<Markdown>),} fn render_markdown( text: SharedString, language_registry: Arc<LanguageRegistry>, window: &Window, cx: &mut App,) -> Entity<Markdown> { let theme_settings = ThemeSettings::get_global(cx); let colors = cx.theme().colors(); let ui_font_size = TextSize::Default.rems(cx); let buffer_font_size = TextSize::Small.rems(cx); let mut text_style = window.text_style(); text_style.refine(&TextStyleRefinement { font_family: Some(theme_settings.ui_font.family.clone()), font_fallbacks: theme_settings.ui_font.fallbacks.clone(), font_features: Some(theme_settings.ui_font.features.clone()), font_size: Some(ui_font_size.into()), color: Some(cx.theme().colors().text), ..Default::default() }); let markdown_style = MarkdownStyle { base_text_style: text_style, syntax: cx.theme().syntax().clone(), selection_background_color: cx.theme().players().local().selection, code_block_overflow_x_scroll: true, table_overflow_x_scroll: true, code_block: StyleRefinement { margin: EdgesRefinement { top: Some(Length::Definite(rems(0.).into())), left: Some(Length::Definite(rems(0.).into())), right: Some(Length::Definite(rems(0.).into())), bottom: Some(Length::Definite(rems(0.5).into())), }, padding: EdgesRefinement { top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), }, background: Some(colors.editor_background.into()), border_color: Some(colors.border_variant), border_widths: EdgesRefinement { top: Some(AbsoluteLength::Pixels(Pixels(1.))), left: Some(AbsoluteLength::Pixels(Pixels(1.))), right: Some(AbsoluteLength::Pixels(Pixels(1.))), bottom: Some(AbsoluteLength::Pixels(Pixels(1.))), }, text: Some(TextStyleRefinement { font_family: Some(theme_settings.buffer_font.family.clone()), font_fallbacks: theme_settings.buffer_font.fallbacks.clone(), font_features: Some(theme_settings.buffer_font.features.clone()), font_size: Some(buffer_font_size.into()), ..Default::default() }), ..Default::default() }, inline_code: TextStyleRefinement { font_family: Some(theme_settings.buffer_font.family.clone()), font_fallbacks: theme_settings.buffer_font.fallbacks.clone(), font_features: Some(theme_settings.buffer_font.features.clone()), font_size: Some(buffer_font_size.into()), background_color: Some(colors.editor_foreground.opacity(0.1)), ..Default::default() }, link: TextStyleRefinement { background_color: Some(colors.editor_foreground.opacity(0.025)), underline: Some(UnderlineStyle { color: Some(colors.text_accent.opacity(0.5)), thickness: px(1.), ..Default::default() }), ..Default::default() }, ..Default::default() }; cx.new(|cx| Markdown::new(text, markdown_style, Some(language_registry), None, cx))} struct EditMessageState { editor: Entity<Editor>,} impl ActiveThread { pub fn new( thread: Entity<Thread>, thread_store: Entity<ThreadStore>, language_registry: Arc<LanguageRegistry>, context_store: Entity<ContextStore>, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let subscriptions = vec![ cx.observe(&thread, |_, _, cx| cx.notify()), cx.subscribe_in(&thread, window, Self::handle_thread_event), ]; let list_state = ListState::new(0, ListAlignment::Bottom, px(2048.), { let this = cx.entity().downgrade(); move |ix, window: &mut Window, cx: &mut App| { this.update(cx, |this, cx| this.render_message(ix, window, cx)) .unwrap() } }); let mut this = Self { language_registry, thread_store, thread: thread.clone(), context_store, workspace, save_thread_task: None, messages: Vec::new(), rendered_messages_by_id: HashMap::default(), rendered_tool_use_labels: HashMap::default(), expanded_tool_uses: HashMap::default(), expanded_thinking_segments: HashMap::default(), list_state: list_state.clone(), scrollbar_state: ScrollbarState::new(list_state), editing_message: None, last_error: None, pop_ups: Vec::new(), _subscriptions: subscriptions, pop_up_subscriptions: HashMap::default(), }; for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() { this.push_message(&message.id, &message.segments, window, cx); for tool_use in thread.read(cx).tool_uses_for_message(message.id, cx) { this.render_tool_use_label_markdown( tool_use.id.clone(), tool_use.ui_text.clone(), window, cx, ); } } this } pub fn thread(&self) -> &Entity<Thread> { &self.thread } pub fn is_empty(&self) -> bool { self.messages.is_empty() } pub fn summary(&self, cx: &App) -> Option<SharedString> { self.thread.read(cx).summary() } pub fn summary_or_default(&self, cx: &App) -> SharedString { self.thread.read(cx).summary_or_default() } pub fn cancel_last_completion(&mut self, cx: &mut App) -> bool { self.last_error.take(); self.thread .update(cx, |thread, cx| thread.cancel_last_completion(cx)) } pub fn last_error(&self) -> Option<ThreadError> { self.last_error.clone() } pub fn clear_last_error(&mut self) { self.last_error.take(); } fn push_message( &mut self, id: &MessageId, segments: &[MessageSegment], window: &mut Window, cx: &mut Context<Self>, ) { let old_len = self.messages.len(); self.messages.push(*id); self.list_state.splice(old_len..old_len, 1); let rendered_message = RenderedMessage::from_segments(segments, self.language_registry.clone(), window, cx); self.rendered_messages_by_id.insert(*id, rendered_message); self.list_state.scroll_to(ListOffset { item_ix: old_len, offset_in_item: Pixels(0.0), }); } fn edited_message( &mut self, id: &MessageId, segments: &[MessageSegment], window: &mut Window, cx: &mut Context<Self>, ) { let Some(index) = self.messages.iter().position(|message_id| message_id == id) else { return; }; self.list_state.splice(index..index + 1, 1); let rendered_message = RenderedMessage::from_segments(segments, self.language_registry.clone(), window, cx); self.rendered_messages_by_id.insert(*id, rendered_message); } fn deleted_message(&mut self, id: &MessageId) { let Some(index) = self.messages.iter().position(|message_id| message_id == id) else { return; }; self.messages.remove(index); self.list_state.splice(index..index + 1, 0); self.rendered_messages_by_id.remove(id); } fn render_tool_use_label_markdown( &mut self, tool_use_id: LanguageModelToolUseId, tool_label: impl Into<SharedString>, window: &mut Window, cx: &mut Context<Self>, ) { self.rendered_tool_use_labels.insert( tool_use_id, render_markdown( tool_label.into(), self.language_registry.clone(), window, cx, ), ); } fn handle_thread_event( &mut self, _thread: &Entity<Thread>, event: &ThreadEvent, window: &mut Window, cx: &mut Context<Self>, ) { match event { ThreadEvent::ShowError(error) => { self.last_error = Some(error.clone()); } ThreadEvent::StreamedCompletion | ThreadEvent::SummaryChanged => { self.save_thread(cx); } ThreadEvent::DoneStreaming => { if !self.thread().read(cx).is_generating() { self.show_notification( "The assistant response has concluded.", IconName::Check, Color::Success, window, cx, ); } } ThreadEvent::ToolConfirmationNeeded => { self.show_notification( "There's a tool confirmation needed.", IconName::Info, Color::Muted, window, cx, ); } ThreadEvent::StreamedAssistantText(message_id, text) => { if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) { rendered_message.append_text(text, window, cx); } } ThreadEvent::StreamedAssistantThinking(message_id, text) => { if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) { rendered_message.append_thinking(text, window, cx); } } ThreadEvent::MessageAdded(message_id) => { if let Some(message_segments) = self .thread .read(cx) .message(*message_id) .map(|message| message.segments.clone()) { self.push_message(message_id, &message_segments, window, cx); } self.save_thread(cx); cx.notify(); } ThreadEvent::MessageEdited(message_id) => { if let Some(message_segments) = self .thread .read(cx) .message(*message_id) .map(|message| message.segments.clone()) { self.edited_message(message_id, &message_segments, window, cx); } self.save_thread(cx); cx.notify(); } ThreadEvent::MessageDeleted(message_id) => { self.deleted_message(message_id); self.save_thread(cx); cx.notify(); } ThreadEvent::UsePendingTools => { let tool_uses = self .thread .update(cx, |thread, cx| thread.use_pending_tools(cx)); for tool_use in tool_uses { self.render_tool_use_label_markdown( tool_use.id.clone(), tool_use.ui_text.clone(), window, cx, ); } } ThreadEvent::ToolFinished { pending_tool_use, canceled, .. } => { let canceled = *canceled; if let Some(tool_use) = pending_tool_use { self.render_tool_use_label_markdown( tool_use.id.clone(), SharedString::from(tool_use.ui_text.clone()), window, cx, ); } if self.thread.read(cx).all_tools_finished() { let pending_refresh_buffers = self.thread.update(cx, |thread, cx| { thread.action_log().update(cx, |action_log, _cx| { action_log.take_stale_buffers_in_context() }) }); let context_update_task = if !pending_refresh_buffers.is_empty() { let refresh_task = refresh_context_store_text( self.context_store.clone(), &pending_refresh_buffers, cx, ); cx.spawn(async move |this, cx| { let updated_context_ids = refresh_task.await; this.update(cx, |this, cx| { this.context_store.read_with(cx, |context_store, cx| { context_store .context() .iter() .filter(|context| { updated_context_ids.contains(&context.id()) }) .flat_map(|context| context.snapshot(cx)) .collect() })Lines 1–500 of 1825. Load more
use crate::thread::{ LastRestoreCheckpoint, MessageId, MessageSegment, RequestKind, Thread, ThreadError, ThreadEvent, ThreadFeedback, }; use crate::thread_store::ThreadStore; use crate::tool_use::{PendingToolUseStatus, ToolUse, ToolUseStatus}; use crate::ui::{ContextPill, ToolReadyPopUp, ToolReadyPopupEvent}; use crate::AssistantPanel; use assistant_settings::AssistantSettings; use collections::HashMap; use editor::{Editor, MultiBuffer}; use gpui::{ linear_color_stop, linear_gradient, list, percentage, pulsating_between, AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, DefiniteLength, EdgesRefinement, Empty, Entity, Focusable, Hsla, Length, ListAlignment, ListOffset, ListState, MouseButton, ScrollHandle, Stateful, StyleRefinement, Subscription, Task, TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity, WindowHandle, }; use language::{Buffer, LanguageRegistry}; use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role}; use markdown::{Markdown, MarkdownStyle}; use settings::Settings as _; use std::sync::Arc; use std::time::Duration; use theme::ThemeSettings; use ui::{prelude::*, Disclosure, IconButton, KeyBinding, Scrollbar, ScrollbarState, Tooltip}; use util::ResultExt as _; use workspace::{OpenOptions, Workspace}; use crate::context_store::{refresh_context_store_text, ContextStore}; pub struct ActiveThread { language_registry: Arc<LanguageRegistry>, thread_store: Entity<ThreadStore>, thread: Entity<Thread>, context_store: Entity<ContextStore>, workspace: WeakEntity<Workspace>, save_thread_task: Option<Task<()>>, messages: Vec<MessageId>, list_state: ListState, scrollbar_state: ScrollbarState, rendered_messages_by_id: HashMap<MessageId, RenderedMessage>, rendered_tool_use_labels: HashMap<LanguageModelToolUseId, Entity<Markdown>>, editing_message: Option<(MessageId, EditMessageState)>, expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>, expanded_thinking_segments: HashMap<(MessageId, usize), bool>, last_error: Option<ThreadError>, pop_ups: Vec<WindowHandle<ToolReadyPopUp>>, _subscriptions: Vec<Subscription>, pop_up_subscriptions: HashMap<WindowHandle<ToolReadyPopUp>, Vec<Subscription>>, } struct RenderedMessage { language_registry: Arc<LanguageRegistry>, segments: Vec<RenderedMessageSegment>, } impl RenderedMessage { fn from_segments( segments: &[MessageSegment], language_registry: Arc<LanguageRegistry>, window: &Window, cx: &mut App, ) -> Self { let mut this = Self { language_registry, segments: Vec::with_capacity(segments.len()), }; for segment in segments { this.push_segment(segment, window, cx); } this } fn append_thinking(&mut self, text: &String, window: &Window, cx: &mut App) { if let Some(RenderedMessageSegment::Thinking { content, scroll_handle, }) = self.segments.last_mut() { content.update(cx, |markdown, cx| { markdown.append(text, cx); }); scroll_handle.scroll_to_bottom(); } else { self.segments.push(RenderedMessageSegment::Thinking { content: render_markdown(text.into(), self.language_registry.clone(), window, cx), scroll_handle: ScrollHandle::default(), }); } } fn append_text(&mut self, text: &String, window: &Window, cx: &mut App) { if let Some(RenderedMessageSegment::Text(markdown)) = self.segments.last_mut() { markdown.update(cx, |markdown, cx| markdown.append(text, cx)); } else { self.segments .push(RenderedMessageSegment::Text(render_markdown( SharedString::from(text), self.language_registry.clone(), window, cx, ))); } } fn push_segment(&mut self, segment: &MessageSegment, window: &Window, cx: &mut App) { let rendered_segment = match segment { MessageSegment::Thinking(text) => RenderedMessageSegment::Thinking { content: render_markdown(text.into(), self.language_registry.clone(), window, cx), scroll_handle: ScrollHandle::default(), }, MessageSegment::Text(text) => RenderedMessageSegment::Text(render_markdown( text.into(), self.language_registry.clone(), window, cx, )), }; self.segments.push(rendered_segment); } } enum RenderedMessageSegment { Thinking { content: Entity<Markdown>, scroll_handle: ScrollHandle, }, Text(Entity<Markdown>), } fn render_markdown( text: SharedString, language_registry: Arc<LanguageRegistry>, window: &Window, cx: &mut App, ) -> Entity<Markdown> { let theme_settings = ThemeSettings::get_global(cx); let colors = cx.theme().colors(); let ui_font_size = TextSize::Default.rems(cx); let buffer_font_size = TextSize::Small.rems(cx); let mut text_style = window.text_style(); text_style.refine(&TextStyleRefinement { font_family: Some(theme_settings.ui_font.family.clone()), font_fallbacks: theme_settings.ui_font.fallbacks.clone(), font_features: Some(theme_settings.ui_font.features.clone()), font_size: Some(ui_font_size.into()), color: Some(cx.theme().colors().text), ..Default::default() }); let markdown_style = MarkdownStyle { base_text_style: text_style, syntax: cx.theme().syntax().clone(), selection_background_color: cx.theme().players().local().selection, code_block_overflow_x_scroll: true, table_overflow_x_scroll: true, code_block: StyleRefinement { margin: EdgesRefinement { top: Some(Length::Definite(rems(0.).into())), left: Some(Length::Definite(rems(0.).into())), right: Some(Length::Definite(rems(0.).into())), bottom: Some(Length::Definite(rems(0.5).into())), }, padding: EdgesRefinement { top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), }, background: Some(colors.editor_background.into()), border_color: Some(colors.border_variant), border_widths: EdgesRefinement { top: Some(AbsoluteLength::Pixels(Pixels(1.))), left: Some(AbsoluteLength::Pixels(Pixels(1.))), right: Some(AbsoluteLength::Pixels(Pixels(1.))), bottom: Some(AbsoluteLength::Pixels(Pixels(1.))), }, text: Some(TextStyleRefinement { font_family: Some(theme_settings.buffer_font.family.clone()), font_fallbacks: theme_settings.buffer_font.fallbacks.clone(), font_features: Some(theme_settings.buffer_font.features.clone()), font_size: Some(buffer_font_size.into()), ..Default::default() }), ..Default::default() }, inline_code: TextStyleRefinement { font_family: Some(theme_settings.buffer_font.family.clone()), font_fallbacks: theme_settings.buffer_font.fallbacks.clone(), font_features: Some(theme_settings.buffer_font.features.clone()), font_size: Some(buffer_font_size.into()), background_color: Some(colors.editor_foreground.opacity(0.1)), ..Default::default() }, link: TextStyleRefinement { background_color: Some(colors.editor_foreground.opacity(0.025)), underline: Some(UnderlineStyle { color: Some(colors.text_accent.opacity(0.5)), thickness: px(1.), ..Default::default() }), ..Default::default() }, ..Default::default() }; cx.new(|cx| Markdown::new(text, markdown_style, Some(language_registry), None, cx)) } struct EditMessageState { editor: Entity<Editor>, } impl ActiveThread { pub fn new( thread: Entity<Thread>, thread_store: Entity<ThreadStore>, language_registry: Arc<LanguageRegistry>, context_store: Entity<ContextStore>, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let subscriptions = vec![ cx.observe(&thread, |_, _, cx| cx.notify()), cx.subscribe_in(&thread, window, Self::handle_thread_event), ]; let list_state = ListState::new(0, ListAlignment::Bottom, px(2048.), { let this = cx.entity().downgrade(); move |ix, window: &mut Window, cx: &mut App| { this.update(cx, |this, cx| this.render_message(ix, window, cx)) .unwrap() } }); let mut this = Self { language_registry, thread_store, thread: thread.clone(), context_store, workspace, save_thread_task: None, messages: Vec::new(), rendered_messages_by_id: HashMap::default(), rendered_tool_use_labels: HashMap::default(), expanded_tool_uses: HashMap::default(), expanded_thinking_segments: HashMap::default(), list_state: list_state.clone(), scrollbar_state: ScrollbarState::new(list_state), editing_message: None, last_error: None, pop_ups: Vec::new(), _subscriptions: subscriptions, pop_up_subscriptions: HashMap::default(), }; for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() { this.push_message(&message.id, &message.segments, window, cx); for tool_use in thread.read(cx).tool_uses_for_message(message.id, cx) { this.render_tool_use_label_markdown( tool_use.id.clone(), tool_use.ui_text.clone(), window, cx, ); } } this } pub fn thread(&self) -> &Entity<Thread> { &self.thread } pub fn is_empty(&self) -> bool { self.messages.is_empty() } pub fn summary(&self, cx: &App) -> Option<SharedString> { self.thread.read(cx).summary() } pub fn summary_or_default(&self, cx: &App) -> SharedString { self.thread.read(cx).summary_or_default() } pub fn cancel_last_completion(&mut self, cx: &mut App) -> bool { self.last_error.take(); self.thread .update(cx, |thread, cx| thread.cancel_last_completion(cx)) } pub fn last_error(&self) -> Option<ThreadError> { self.last_error.clone() } pub fn clear_last_error(&mut self) { self.last_error.take(); } fn push_message( &mut self, id: &MessageId, segments: &[MessageSegment], window: &mut Window, cx: &mut Context<Self>, ) { let old_len = self.messages.len(); self.messages.push(*id); self.list_state.splice(old_len..old_len, 1); let rendered_message = RenderedMessage::from_segments(segments, self.language_registry.clone(), window, cx); self.rendered_messages_by_id.insert(*id, rendered_message); self.list_state.scroll_to(ListOffset { item_ix: old_len, offset_in_item: Pixels(0.0), }); } fn edited_message( &mut self, id: &MessageId, segments: &[MessageSegment], window: &mut Window, cx: &mut Context<Self>, ) { let Some(index) = self.messages.iter().position(|message_id| message_id == id) else { return; }; self.list_state.splice(index..index + 1, 1); let rendered_message = RenderedMessage::from_segments(segments, self.language_registry.clone(), window, cx); self.rendered_messages_by_id.insert(*id, rendered_message); } fn deleted_message(&mut self, id: &MessageId) { let Some(index) = self.messages.iter().position(|message_id| message_id == id) else { return; }; self.messages.remove(index); self.list_state.splice(index..index + 1, 0); self.rendered_messages_by_id.remove(id); } fn render_tool_use_label_markdown( &mut self, tool_use_id: LanguageModelToolUseId, tool_label: impl Into<SharedString>, window: &mut Window, cx: &mut Context<Self>, ) { self.rendered_tool_use_labels.insert( tool_use_id, render_markdown( tool_label.into(), self.language_registry.clone(), window, cx, ), ); } fn handle_thread_event( &mut self, _thread: &Entity<Thread>, event: &ThreadEvent, window: &mut Window, cx: &mut Context<Self>, ) { match event { ThreadEvent::ShowError(error) => { self.last_error = Some(error.clone()); } ThreadEvent::StreamedCompletion | ThreadEvent::SummaryChanged => { self.save_thread(cx); } ThreadEvent::DoneStreaming => { if !self.thread().read(cx).is_generating() { self.show_notification( "The assistant response has concluded.", IconName::Check, Color::Success, window, cx, ); } } ThreadEvent::ToolConfirmationNeeded => { self.show_notification( "There's a tool confirmation needed.", IconName::Info, Color::Muted, window, cx, ); } ThreadEvent::StreamedAssistantText(message_id, text) => { if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) { rendered_message.append_text(text, window, cx); } } ThreadEvent::StreamedAssistantThinking(message_id, text) => { if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) { rendered_message.append_thinking(text, window, cx); } } ThreadEvent::MessageAdded(message_id) => { if let Some(message_segments) = self .thread .read(cx) .message(*message_id) .map(|message| message.segments.clone()) { self.push_message(message_id, &message_segments, window, cx); } self.save_thread(cx); cx.notify(); } ThreadEvent::MessageEdited(message_id) => { if let Some(message_segments) = self .thread .read(cx) .message(*message_id) .map(|message| message.segments.clone()) { self.edited_message(message_id, &message_segments, window, cx); } self.save_thread(cx); cx.notify(); } ThreadEvent::MessageDeleted(message_id) => { self.deleted_message(message_id); self.save_thread(cx); cx.notify(); } ThreadEvent::UsePendingTools => { let tool_uses = self .thread .update(cx, |thread, cx| thread.use_pending_tools(cx)); for tool_use in tool_uses { self.render_tool_use_label_markdown( tool_use.id.clone(), tool_use.ui_text.clone(), window, cx, ); } } ThreadEvent::ToolFinished { pending_tool_use, canceled, .. } => { let canceled = *canceled; if let Some(tool_use) = pending_tool_use { self.render_tool_use_label_markdown( tool_use.id.clone(), SharedString::from(tool_use.ui_text.clone()), window, cx, ); } if self.thread.read(cx).all_tools_finished() { let pending_refresh_buffers = self.thread.update(cx, |thread, cx| { thread.action_log().update(cx, |action_log, _cx| { action_log.take_stale_buffers_in_context() }) }); let context_update_task = if !pending_refresh_buffers.is_empty() { let refresh_task = refresh_context_store_text( self.context_store.clone(), &pending_refresh_buffers, cx, ); cx.spawn(async move |this, cx| { let updated_context_ids = refresh_task.await; this.update(cx, |this, cx| { this.context_store.read_with(cx, |context_store, cx| { context_store .context() .iter() .filter(|context| { updated_context_ids.contains(&context.id()) }) .flat_map(|context| context.snapshot(cx)) .collect() })