assistant2.rs

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