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