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