assistant2.rs

  1mod assistant_settings;
  2mod attachments;
  3mod completion_provider;
  4mod tools;
  5pub mod ui;
  6
  7use crate::{
  8    attachments::ActiveEditorAttachmentTool,
  9    tools::{CreateBufferTool, ProjectIndexTool},
 10    ui::UserOrAssistant,
 11};
 12use ::ui::{div, prelude::*, Color, ViewContext};
 13use anyhow::{Context, Result};
 14use assistant_tooling::{
 15    AttachmentRegistry, ProjectContext, ToolFunctionCall, ToolRegistry, UserAttachment,
 16};
 17use client::{proto, Client, UserStore};
 18use collections::HashMap;
 19use completion_provider::*;
 20use editor::Editor;
 21use feature_flags::FeatureFlagAppExt as _;
 22use futures::{future::join_all, StreamExt};
 23use gpui::{
 24    list, AnyElement, AppContext, AsyncWindowContext, ClickEvent, EventEmitter, FocusHandle,
 25    FocusableView, ListAlignment, ListState, Model, Render, Task, View, WeakView,
 26};
 27use language::{language_settings::SoftWrap, LanguageRegistry};
 28use open_ai::{FunctionContent, ToolCall, ToolCallContent};
 29use rich_text::RichText;
 30use semantic_index::{CloudEmbeddingProvider, ProjectIndex, ProjectIndexDebugView, SemanticIndex};
 31use serde::Deserialize;
 32use settings::Settings;
 33use std::sync::Arc;
 34use tools::OpenBufferTool;
 35use ui::{ActiveFileButton, Composer, ProjectIndexButton};
 36use util::{maybe, paths::EMBEDDINGS_DIR, ResultExt};
 37use workspace::{
 38    dock::{DockPosition, Panel, PanelEvent},
 39    Workspace,
 40};
 41
 42pub use assistant_settings::AssistantSettings;
 43
 44const MAX_COMPLETION_CALLS_PER_SUBMISSION: usize = 5;
 45
 46#[derive(Eq, PartialEq, Copy, Clone, Deserialize)]
 47pub struct Submit(SubmitMode);
 48
 49/// There are multiple different ways to submit a model request, represented by this enum.
 50#[derive(Eq, PartialEq, Copy, Clone, Deserialize)]
 51pub enum SubmitMode {
 52    /// Only include the conversation.
 53    Simple,
 54    /// Send the current file as context.
 55    CurrentFile,
 56    /// Search the codebase and send relevant excerpts.
 57    Codebase,
 58}
 59
 60gpui::actions!(assistant2, [Cancel, ToggleFocus, DebugProjectIndex]);
 61gpui::impl_actions!(assistant2, [Submit]);
 62
 63pub fn init(client: Arc<Client>, cx: &mut AppContext) {
 64    AssistantSettings::register(cx);
 65
 66    cx.spawn(|mut cx| {
 67        let client = client.clone();
 68        async move {
 69            let embedding_provider = CloudEmbeddingProvider::new(client.clone());
 70            let semantic_index = SemanticIndex::new(
 71                EMBEDDINGS_DIR.join("semantic-index-db.0.mdb"),
 72                Arc::new(embedding_provider),
 73                &mut cx,
 74            )
 75            .await?;
 76            cx.update(|cx| cx.set_global(semantic_index))
 77        }
 78    })
 79    .detach();
 80
 81    cx.set_global(CompletionProvider::new(CloudCompletionProvider::new(
 82        client,
 83    )));
 84
 85    cx.observe_new_views(
 86        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
 87            workspace.register_action(|workspace, _: &ToggleFocus, cx| {
 88                workspace.toggle_panel_focus::<AssistantPanel>(cx);
 89            });
 90            workspace.register_action(|workspace, _: &DebugProjectIndex, cx| {
 91                if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
 92                    let index = panel.read(cx).chat.read(cx).project_index.clone();
 93                    let view = cx.new_view(|cx| ProjectIndexDebugView::new(index, cx));
 94                    workspace.add_item_to_center(Box::new(view), cx);
 95                }
 96            });
 97        },
 98    )
 99    .detach();
100}
101
102pub fn enabled(cx: &AppContext) -> bool {
103    cx.is_staff()
104}
105
106pub struct AssistantPanel {
107    chat: View<AssistantChat>,
108    width: Option<Pixels>,
109}
110
111impl AssistantPanel {
112    pub fn load(
113        workspace: WeakView<Workspace>,
114        cx: AsyncWindowContext,
115    ) -> Task<Result<View<Self>>> {
116        cx.spawn(|mut cx| async move {
117            let (app_state, project) = workspace.update(&mut cx, |workspace, _| {
118                (workspace.app_state().clone(), workspace.project().clone())
119            })?;
120
121            cx.new_view(|cx| {
122                let project_index = cx.update_global(|semantic_index: &mut SemanticIndex, cx| {
123                    semantic_index.project_index(project.clone(), cx)
124                });
125
126                let mut tool_registry = ToolRegistry::new();
127                tool_registry
128                    .register(ProjectIndexTool::new(project_index.clone()), cx)
129                    .unwrap();
130                tool_registry
131                    .register(
132                        CreateBufferTool::new(workspace.clone(), project.clone()),
133                        cx,
134                    )
135                    .unwrap();
136                tool_registry
137                    .register(OpenBufferTool::new(workspace.clone(), project.clone()), cx)
138                    .unwrap();
139
140                let mut attachment_registry = AttachmentRegistry::new();
141                attachment_registry
142                    .register(ActiveEditorAttachmentTool::new(workspace.clone(), cx));
143
144                Self::new(
145                    app_state.languages.clone(),
146                    Arc::new(tool_registry),
147                    Arc::new(attachment_registry),
148                    app_state.user_store.clone(),
149                    project_index,
150                    workspace,
151                    cx,
152                )
153            })
154        })
155    }
156
157    pub fn new(
158        language_registry: Arc<LanguageRegistry>,
159        tool_registry: Arc<ToolRegistry>,
160        attachment_registry: Arc<AttachmentRegistry>,
161        user_store: Model<UserStore>,
162        project_index: Model<ProjectIndex>,
163        workspace: WeakView<Workspace>,
164        cx: &mut ViewContext<Self>,
165    ) -> Self {
166        let chat = cx.new_view(|cx| {
167            AssistantChat::new(
168                language_registry,
169                tool_registry.clone(),
170                attachment_registry,
171                user_store,
172                project_index,
173                workspace,
174                cx,
175            )
176        });
177
178        Self { width: None, chat }
179    }
180}
181
182impl Render for AssistantPanel {
183    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
184        div()
185            .size_full()
186            .v_flex()
187            .bg(cx.theme().colors().panel_background)
188            .child(self.chat.clone())
189    }
190}
191
192impl Panel for AssistantPanel {
193    fn persistent_name() -> &'static str {
194        "AssistantPanelv2"
195    }
196
197    fn position(&self, _cx: &WindowContext) -> workspace::dock::DockPosition {
198        // todo!("Add a setting / use assistant settings")
199        DockPosition::Right
200    }
201
202    fn position_is_valid(&self, position: workspace::dock::DockPosition) -> bool {
203        matches!(position, DockPosition::Right)
204    }
205
206    fn set_position(&mut self, _: workspace::dock::DockPosition, _: &mut ViewContext<Self>) {
207        // Do nothing until we have a setting for this
208    }
209
210    fn size(&self, _cx: &WindowContext) -> Pixels {
211        self.width.unwrap_or(px(400.))
212    }
213
214    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
215        self.width = size;
216        cx.notify();
217    }
218
219    fn icon(&self, _cx: &WindowContext) -> Option<::ui::IconName> {
220        Some(IconName::ZedAssistant)
221    }
222
223    fn icon_tooltip(&self, _: &WindowContext) -> Option<&'static str> {
224        Some("Assistant Panel ✨")
225    }
226
227    fn toggle_action(&self) -> Box<dyn gpui::Action> {
228        Box::new(ToggleFocus)
229    }
230}
231
232impl EventEmitter<PanelEvent> for AssistantPanel {}
233
234impl FocusableView for AssistantPanel {
235    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
236        self.chat.read(cx).composer_editor.read(cx).focus_handle(cx)
237    }
238}
239
240pub struct AssistantChat {
241    model: String,
242    messages: Vec<ChatMessage>,
243    list_state: ListState,
244    language_registry: Arc<LanguageRegistry>,
245    composer_editor: View<Editor>,
246    project_index_button: View<ProjectIndexButton>,
247    active_file_button: Option<View<ActiveFileButton>>,
248    user_store: Model<UserStore>,
249    next_message_id: MessageId,
250    collapsed_messages: HashMap<MessageId, bool>,
251    editing_message: Option<EditingMessage>,
252    pending_completion: Option<Task<()>>,
253    tool_registry: Arc<ToolRegistry>,
254    attachment_registry: Arc<AttachmentRegistry>,
255    project_index: Model<ProjectIndex>,
256}
257
258struct EditingMessage {
259    id: MessageId,
260    old_body: Arc<str>,
261    body: View<Editor>,
262}
263
264impl AssistantChat {
265    fn new(
266        language_registry: Arc<LanguageRegistry>,
267        tool_registry: Arc<ToolRegistry>,
268        attachment_registry: Arc<AttachmentRegistry>,
269        user_store: Model<UserStore>,
270        project_index: Model<ProjectIndex>,
271        workspace: WeakView<Workspace>,
272        cx: &mut ViewContext<Self>,
273    ) -> Self {
274        let model = CompletionProvider::get(cx).default_model();
275        let view = cx.view().downgrade();
276        let list_state = ListState::new(
277            0,
278            ListAlignment::Bottom,
279            px(1024.),
280            move |ix, cx: &mut WindowContext| {
281                view.update(cx, |this, cx| this.render_message(ix, cx))
282                    .unwrap()
283            },
284        );
285
286        let project_index_button = cx.new_view(|cx| {
287            ProjectIndexButton::new(project_index.clone(), tool_registry.clone(), cx)
288        });
289
290        let active_file_button = match workspace.upgrade() {
291            Some(workspace) => {
292                Some(cx.new_view(
293                    |cx| ActiveFileButton::new(attachment_registry.clone(), workspace, cx), //
294                ))
295            }
296            _ => None,
297        };
298
299        Self {
300            model,
301            messages: Vec::new(),
302            composer_editor: cx.new_view(|cx| {
303                let mut editor = Editor::auto_height(80, cx);
304                editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
305                editor.set_placeholder_text("Send a message…", cx);
306                editor
307            }),
308            list_state,
309            user_store,
310            language_registry,
311            project_index_button,
312            active_file_button,
313            project_index,
314            next_message_id: MessageId(0),
315            editing_message: None,
316            collapsed_messages: HashMap::default(),
317            pending_completion: None,
318            attachment_registry,
319            tool_registry,
320        }
321    }
322
323    fn editing_message_id(&self) -> Option<MessageId> {
324        self.editing_message.as_ref().map(|message| message.id)
325    }
326
327    fn focused_message_id(&self, cx: &WindowContext) -> Option<MessageId> {
328        self.messages.iter().find_map(|message| match message {
329            ChatMessage::User(message) => message
330                .body
331                .focus_handle(cx)
332                .contains_focused(cx)
333                .then_some(message.id),
334            ChatMessage::Assistant(_) => None,
335        })
336    }
337
338    fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
339        // If we're currently editing a message, cancel the edit.
340        if let Some(editing_message) = self.editing_message.take() {
341            editing_message
342                .body
343                .update(cx, |body, cx| body.set_text(editing_message.old_body, cx));
344            return;
345        }
346
347        if self.pending_completion.take().is_some() {
348            if let Some(ChatMessage::Assistant(grouping)) = self.messages.last() {
349                if grouping.messages.is_empty() {
350                    self.pop_message(cx);
351                }
352            }
353            return;
354        }
355
356        cx.propagate();
357    }
358
359    fn submit(&mut self, Submit(mode): &Submit, cx: &mut ViewContext<Self>) {
360        if let Some(focused_message_id) = self.focused_message_id(cx) {
361            self.truncate_messages(focused_message_id, cx);
362            self.pending_completion.take();
363            self.composer_editor.focus_handle(cx).focus(cx);
364            if self.editing_message_id() == Some(focused_message_id) {
365                self.editing_message.take();
366            }
367        } else if self.composer_editor.focus_handle(cx).is_focused(cx) {
368            // Don't allow multiple concurrent completions.
369            if self.pending_completion.is_some() {
370                cx.propagate();
371                return;
372            }
373
374            let message = self.composer_editor.update(cx, |composer_editor, cx| {
375                let text = composer_editor.text(cx);
376                let id = self.next_message_id.post_inc();
377                let body = cx.new_view(|cx| {
378                    let mut editor = Editor::auto_height(80, cx);
379                    editor.set_text(text, cx);
380                    editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
381                    editor
382                });
383                composer_editor.clear(cx);
384
385                ChatMessage::User(UserMessage {
386                    id,
387                    body,
388                    attachments: Vec::new(),
389                })
390            });
391            self.push_message(message, cx);
392        } else {
393            log::error!("unexpected state: no user message editor is focused.");
394            return;
395        }
396
397        let mode = *mode;
398        self.pending_completion = Some(cx.spawn(move |this, mut cx| async move {
399            let attachments_task = this.update(&mut cx, |this, cx| {
400                let attachment_registry = this.attachment_registry.clone();
401                attachment_registry.call_all_attachment_tools(cx)
402            });
403
404            let attachments = maybe!(async {
405                let attachments_task = attachments_task?;
406                let attachments = attachments_task.await?;
407
408                anyhow::Ok(attachments)
409            })
410            .await
411            .log_err()
412            .unwrap_or_default();
413
414            // Set the attachments to the _last_ user message
415            this.update(&mut cx, |this, _cx| {
416                if let Some(ChatMessage::User(message)) = this.messages.last_mut() {
417                    message.attachments = attachments;
418                }
419            })
420            .log_err();
421
422            Self::request_completion(
423                this.clone(),
424                mode,
425                MAX_COMPLETION_CALLS_PER_SUBMISSION,
426                &mut cx,
427            )
428            .await
429            .log_err();
430
431            this.update(&mut cx, |this, _cx| {
432                this.pending_completion = None;
433            })
434            .context("Failed to push new user message")
435            .log_err();
436        }));
437    }
438
439    async fn request_completion(
440        this: WeakView<Self>,
441        mode: SubmitMode,
442        limit: usize,
443        cx: &mut AsyncWindowContext,
444    ) -> Result<()> {
445        let mut call_count = 0;
446        loop {
447            let complete = async {
448                let (tool_definitions, model_name, messages) = this.update(cx, |this, cx| {
449                    this.push_new_assistant_message(cx);
450
451                    let definitions = if call_count < limit
452                        && matches!(mode, SubmitMode::Codebase | SubmitMode::Simple)
453                    {
454                        this.tool_registry.definitions()
455                    } else {
456                        Vec::new()
457                    };
458                    call_count += 1;
459
460                    (
461                        definitions,
462                        this.model.clone(),
463                        this.completion_messages(cx),
464                    )
465                })?;
466
467                let messages = messages.await?;
468
469                let completion = cx.update(|cx| {
470                    CompletionProvider::get(cx).complete(
471                        model_name,
472                        messages,
473                        Vec::new(),
474                        1.0,
475                        tool_definitions,
476                    )
477                });
478
479                let mut stream = completion?.await?;
480                let mut body = String::new();
481                while let Some(delta) = stream.next().await {
482                    let delta = delta?;
483                    this.update(cx, |this, cx| {
484                        if let Some(ChatMessage::Assistant(GroupedAssistantMessage {
485                            messages,
486                            ..
487                        })) = this.messages.last_mut()
488                        {
489                            if messages.is_empty() {
490                                messages.push(AssistantMessage {
491                                    body: RichText::default(),
492                                    tool_calls: Vec::new(),
493                                })
494                            }
495
496                            let message = messages.last_mut().unwrap();
497
498                            if let Some(content) = &delta.content {
499                                body.push_str(content);
500                            }
501
502                            for tool_call in delta.tool_calls {
503                                let index = tool_call.index as usize;
504                                if index >= message.tool_calls.len() {
505                                    message.tool_calls.resize_with(index + 1, Default::default);
506                                }
507                                let call = &mut message.tool_calls[index];
508
509                                if let Some(id) = &tool_call.id {
510                                    call.id.push_str(id);
511                                }
512
513                                match tool_call.variant {
514                                    Some(proto::tool_call_delta::Variant::Function(tool_call)) => {
515                                        if let Some(name) = &tool_call.name {
516                                            call.name.push_str(name);
517                                        }
518                                        if let Some(arguments) = &tool_call.arguments {
519                                            call.arguments.push_str(arguments);
520                                        }
521                                    }
522                                    None => {}
523                                }
524                            }
525
526                            message.body =
527                                RichText::new(body.clone(), &[], &this.language_registry);
528                            cx.notify();
529                        } else {
530                            unreachable!()
531                        }
532                    })?;
533                }
534
535                anyhow::Ok(())
536            }
537            .await;
538
539            let mut tool_tasks = Vec::new();
540            this.update(cx, |this, cx| {
541                if let Some(ChatMessage::Assistant(GroupedAssistantMessage {
542                    error: message_error,
543                    messages,
544                    ..
545                })) = this.messages.last_mut()
546                {
547                    if let Err(error) = complete {
548                        message_error.replace(SharedString::from(error.to_string()));
549                        cx.notify();
550                    } else {
551                        if let Some(current_message) = messages.last_mut() {
552                            for tool_call in current_message.tool_calls.iter() {
553                                tool_tasks.push(this.tool_registry.call(tool_call, cx));
554                            }
555                        }
556                    }
557                }
558            })?;
559
560            if tool_tasks.is_empty() {
561                return Ok(());
562            }
563
564            let tools = join_all(tool_tasks.into_iter()).await;
565            // If the WindowContext went away for any tool's view we don't include it
566            // especially since the below call would fail for the same reason.
567            let tools = tools.into_iter().filter_map(|tool| tool.ok()).collect();
568
569            this.update(cx, |this, cx| {
570                if let Some(ChatMessage::Assistant(GroupedAssistantMessage { messages, .. })) =
571                    this.messages.last_mut()
572                {
573                    if let Some(current_message) = messages.last_mut() {
574                        current_message.tool_calls = tools;
575                        cx.notify();
576                    } else {
577                        unreachable!()
578                    }
579                }
580            })?;
581        }
582    }
583
584    fn push_new_assistant_message(&mut self, cx: &mut ViewContext<Self>) {
585        // If the last message is a grouped assistant message, add to the grouped message
586        if let Some(ChatMessage::Assistant(GroupedAssistantMessage { messages, .. })) =
587            self.messages.last_mut()
588        {
589            messages.push(AssistantMessage {
590                body: RichText::default(),
591                tool_calls: Vec::new(),
592            });
593            return;
594        }
595
596        let message = ChatMessage::Assistant(GroupedAssistantMessage {
597            id: self.next_message_id.post_inc(),
598            messages: vec![AssistantMessage {
599                body: RichText::default(),
600                tool_calls: Vec::new(),
601            }],
602            error: None,
603        });
604        self.push_message(message, cx);
605    }
606
607    fn push_message(&mut self, message: ChatMessage, cx: &mut ViewContext<Self>) {
608        let old_len = self.messages.len();
609        let focus_handle = Some(message.focus_handle(cx));
610        self.messages.push(message);
611        self.list_state
612            .splice_focusable(old_len..old_len, focus_handle);
613        cx.notify();
614    }
615
616    fn pop_message(&mut self, cx: &mut ViewContext<Self>) {
617        if self.messages.is_empty() {
618            return;
619        }
620
621        self.messages.pop();
622        self.list_state
623            .splice(self.messages.len()..self.messages.len() + 1, 0);
624        cx.notify();
625    }
626
627    fn truncate_messages(&mut self, last_message_id: MessageId, cx: &mut ViewContext<Self>) {
628        if let Some(index) = self.messages.iter().position(|message| match message {
629            ChatMessage::User(message) => message.id == last_message_id,
630            ChatMessage::Assistant(message) => message.id == last_message_id,
631        }) {
632            self.list_state.splice(index + 1..self.messages.len(), 0);
633            self.messages.truncate(index + 1);
634            cx.notify();
635        }
636    }
637
638    fn is_message_collapsed(&self, id: &MessageId) -> bool {
639        self.collapsed_messages.get(id).copied().unwrap_or_default()
640    }
641
642    fn toggle_message_collapsed(&mut self, id: MessageId) {
643        let entry = self.collapsed_messages.entry(id).or_insert(false);
644        *entry = !*entry;
645    }
646
647    fn render_error(
648        &self,
649        error: Option<SharedString>,
650        _ix: usize,
651        cx: &mut ViewContext<Self>,
652    ) -> AnyElement {
653        let theme = cx.theme();
654
655        if let Some(error) = error {
656            div()
657                .py_1()
658                .px_2()
659                .mx_neg_1()
660                .rounded_md()
661                .border_1()
662                .border_color(theme.status().error_border)
663                // .bg(theme.status().error_background)
664                .text_color(theme.status().error)
665                .child(error.clone())
666                .into_any_element()
667        } else {
668            div().into_any_element()
669        }
670    }
671
672    fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
673        let is_first = ix == 0;
674        let is_last = ix == self.messages.len() - 1;
675
676        let padding = Spacing::Large.rems(cx);
677
678        // Whenever there's a run of assistant messages, group as one Assistant UI element
679
680        match &self.messages[ix] {
681            ChatMessage::User(UserMessage {
682                id,
683                body,
684                attachments,
685            }) => div()
686                .id(SharedString::from(format!("message-{}-container", id.0)))
687                .when(is_first, |this| this.pt(padding))
688                .map(|element| {
689                    if self.editing_message_id() == Some(*id) {
690                        element.child(Composer::new(
691                            body.clone(),
692                            self.project_index_button.clone(),
693                            self.active_file_button.clone(),
694                            crate::ui::ModelSelector::new(
695                                cx.view().downgrade(),
696                                self.model.clone(),
697                            )
698                            .into_any_element(),
699                        ))
700                    } else {
701                        element
702                            .on_click(cx.listener({
703                                let id = *id;
704                                let body = body.clone();
705                                move |assistant_chat, event: &ClickEvent, cx| {
706                                    if event.up.click_count == 2 {
707                                        assistant_chat.editing_message = Some(EditingMessage {
708                                            id,
709                                            body: body.clone(),
710                                            old_body: body.read(cx).text(cx).into(),
711                                        });
712                                        body.focus_handle(cx).focus(cx);
713                                    }
714                                }
715                            }))
716                            .child(
717                                crate::ui::ChatMessage::new(
718                                    *id,
719                                    UserOrAssistant::User(self.user_store.read(cx).current_user()),
720                                    // todo!(): clean up the vec usage
721                                    vec![
722                                        RichText::new(
723                                            body.read(cx).text(cx),
724                                            &[],
725                                            &self.language_registry,
726                                        )
727                                        .element(ElementId::from(id.0), cx),
728                                        h_flex()
729                                            .gap_2()
730                                            .children(
731                                                attachments
732                                                    .iter()
733                                                    .map(|attachment| attachment.view.clone()),
734                                            )
735                                            .into_any_element(),
736                                    ],
737                                    self.is_message_collapsed(id),
738                                    Box::new(cx.listener({
739                                        let id = *id;
740                                        move |assistant_chat, _event, _cx| {
741                                            assistant_chat.toggle_message_collapsed(id)
742                                        }
743                                    })),
744                                )
745                                // TODO: Wire up selections.
746                                .selected(is_last),
747                            )
748                    }
749                })
750                .into_any(),
751            ChatMessage::Assistant(GroupedAssistantMessage {
752                id,
753                messages,
754                error,
755                ..
756            }) => {
757                let mut message_elements = Vec::new();
758
759                for message in messages {
760                    if !message.body.text.is_empty() {
761                        message_elements.push(
762                            div()
763                                // todo!(): The element Id will need to be a combo of the base ID and the index within the grouping
764                                .child(message.body.element(ElementId::from(id.0), cx))
765                                .into_any_element(),
766                        )
767                    }
768
769                    let tools = message
770                        .tool_calls
771                        .iter()
772                        .map(|tool_call| self.tool_registry.render_tool_call(tool_call, cx))
773                        .collect::<Vec<AnyElement>>();
774
775                    if !tools.is_empty() {
776                        message_elements.push(div().children(tools).into_any_element())
777                    }
778                }
779
780                div()
781                    .when(is_first, |this| this.pt(padding))
782                    .child(
783                        crate::ui::ChatMessage::new(
784                            *id,
785                            UserOrAssistant::Assistant,
786                            message_elements,
787                            self.is_message_collapsed(id),
788                            Box::new(cx.listener({
789                                let id = *id;
790                                move |assistant_chat, _event, _cx| {
791                                    assistant_chat.toggle_message_collapsed(id)
792                                }
793                            })),
794                        )
795                        // TODO: Wire up selections.
796                        .selected(is_last),
797                    )
798                    .child(self.render_error(error.clone(), ix, cx))
799                    .into_any()
800            }
801        }
802    }
803
804    fn completion_messages(&self, cx: &mut WindowContext) -> Task<Result<Vec<CompletionMessage>>> {
805        let project_index = self.project_index.read(cx);
806        let project = project_index.project();
807        let fs = project_index.fs();
808
809        let mut project_context = ProjectContext::new(project, fs);
810        let mut completion_messages = Vec::new();
811
812        for message in &self.messages {
813            match message {
814                ChatMessage::User(UserMessage {
815                    body, attachments, ..
816                }) => {
817                    for attachment in attachments {
818                        if let Some(content) = attachment.generate(&mut project_context, cx) {
819                            completion_messages.push(CompletionMessage::System { content });
820                        }
821                    }
822
823                    // Show user's message last so that the assistant is grounded in the user's request
824                    completion_messages.push(CompletionMessage::User {
825                        content: body.read(cx).text(cx),
826                    });
827                }
828                ChatMessage::Assistant(GroupedAssistantMessage { messages, .. }) => {
829                    for message in messages {
830                        let body = message.body.clone();
831
832                        if body.text.is_empty() && message.tool_calls.is_empty() {
833                            continue;
834                        }
835
836                        let tool_calls_from_assistant = message
837                            .tool_calls
838                            .iter()
839                            .map(|tool_call| ToolCall {
840                                content: ToolCallContent::Function {
841                                    function: FunctionContent {
842                                        name: tool_call.name.clone(),
843                                        arguments: tool_call.arguments.clone(),
844                                    },
845                                },
846                                id: tool_call.id.clone(),
847                            })
848                            .collect();
849
850                        completion_messages.push(CompletionMessage::Assistant {
851                            content: Some(body.text.to_string()),
852                            tool_calls: tool_calls_from_assistant,
853                        });
854
855                        for tool_call in &message.tool_calls {
856                            // Every tool call _must_ have a result by ID, otherwise OpenAI will error.
857                            let content = match &tool_call.result {
858                                Some(result) => {
859                                    result.generate(&tool_call.name, &mut project_context, cx)
860                                }
861                                None => "".to_string(),
862                            };
863
864                            completion_messages.push(CompletionMessage::Tool {
865                                content,
866                                tool_call_id: tool_call.id.clone(),
867                            });
868                        }
869                    }
870                }
871            }
872        }
873
874        let system_message = project_context.generate_system_message(cx);
875
876        cx.background_executor().spawn(async move {
877            let content = system_message.await?;
878            completion_messages.insert(0, CompletionMessage::System { content });
879            Ok(completion_messages)
880        })
881    }
882}
883
884impl Render for AssistantChat {
885    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
886        div()
887            .relative()
888            .flex_1()
889            .v_flex()
890            .key_context("AssistantChat")
891            .on_action(cx.listener(Self::submit))
892            .on_action(cx.listener(Self::cancel))
893            .text_color(Color::Default.color(cx))
894            .child(list(self.list_state.clone()).flex_1())
895            .child(Composer::new(
896                self.composer_editor.clone(),
897                self.project_index_button.clone(),
898                self.active_file_button.clone(),
899                crate::ui::ModelSelector::new(cx.view().downgrade(), self.model.clone())
900                    .into_any_element(),
901            ))
902    }
903}
904
905#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
906pub struct MessageId(usize);
907
908impl MessageId {
909    fn post_inc(&mut self) -> Self {
910        let id = *self;
911        self.0 += 1;
912        id
913    }
914}
915
916enum ChatMessage {
917    User(UserMessage),
918    Assistant(GroupedAssistantMessage),
919}
920
921impl ChatMessage {
922    fn focus_handle(&self, cx: &AppContext) -> Option<FocusHandle> {
923        match self {
924            ChatMessage::User(UserMessage { body, .. }) => Some(body.focus_handle(cx)),
925            ChatMessage::Assistant(_) => None,
926        }
927    }
928}
929
930struct UserMessage {
931    id: MessageId,
932    body: View<Editor>,
933    attachments: Vec<UserAttachment>,
934}
935
936struct AssistantMessage {
937    body: RichText,
938    tool_calls: Vec<ToolFunctionCall>,
939}
940
941struct GroupedAssistantMessage {
942    id: MessageId,
943    messages: Vec<AssistantMessage>,
944    error: Option<SharedString>,
945}