Get the chat panel wired up again

Max Brunsfeld created

Change summary

crates/collab_ui2/src/chat_panel.rs                | 398 +++++++--------
crates/collab_ui2/src/chat_panel/message_editor.rs |  10 
crates/collab_ui2/src/collab_panel.rs              |  25 
crates/gpui2/src/elements/list.rs                  |   2 
crates/workspace2/src/status_bar.rs                |  10 
crates/zed2/src/zed2.rs                            |  10 
6 files changed, 220 insertions(+), 235 deletions(-)

Detailed changes

crates/collab_ui2/src/chat_panel.rs 🔗

@@ -8,8 +8,8 @@ use db::kvp::KEY_VALUE_STORE;
 use editor::Editor;
 use gpui::{
     actions, div, list, prelude::*, px, serde_json, AnyElement, AppContext, AsyncWindowContext,
-    Div, EventEmitter, FocusableView, ListState, Model, Render, Subscription, Task, View,
-    ViewContext, VisualContext, WeakView,
+    ClickEvent, Div, EventEmitter, FocusableView, ListOffset, ListScrollEvent, ListState, Model,
+    Render, SharedString, Subscription, Task, View, ViewContext, VisualContext, WeakView,
 };
 use language::LanguageRegistry;
 use menu::Confirm;
@@ -20,7 +20,10 @@ use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsStore};
 use std::sync::Arc;
 use time::{OffsetDateTime, UtcOffset};
-use ui::prelude::WindowContext;
+use ui::{
+    h_stack, prelude::WindowContext, v_stack, Avatar, Button, ButtonCommon as _, Clickable, Icon,
+    IconButton, Label, Tooltip,
+};
 use util::{ResultExt, TryFutureExt};
 use workspace::{
     dock::{DockPosition, Panel, PanelEvent},
@@ -47,7 +50,6 @@ pub struct ChatPanel {
     subscriptions: Vec<gpui::Subscription>,
     workspace: WeakView<Workspace>,
     is_scrolled_to_bottom: bool,
-    has_focus: bool,
     markdown_data: HashMap<ChannelMessageId, RichText>,
 }
 
@@ -63,11 +65,10 @@ pub enum Event {
     Dismissed,
 }
 
-actions!(LoadMoreMessages, ToggleFocus, OpenChannelNotes, JoinCall);
+actions!(ToggleFocus, OpenChannelNotes, JoinCall);
 
 // pub fn init(cx: &mut AppContext) {
 // cx.add_action(ChatPanel::send);
-// cx.add_action(ChatPanel::load_more_messages);
 // cx.add_action(ChatPanel::open_notes);
 // cx.add_action(ChatPanel::join_call);
 // }
@@ -121,12 +122,12 @@ impl ChatPanel {
                     view.update(cx, |view, cx| view.render_message(ix, cx))
                 });
 
-            // message_list.set_scroll_handler(cx.listener(|this, event: &ListScrollEvent, cx| {
-            //     if event.visible_range.start < MESSAGE_LOADING_THRESHOLD {
-            //         this.load_more_messages(cx);
-            //     }
-            //     this.is_scrolled_to_bottom = event.visible_range.end == event.count;
-            // }));
+            message_list.set_scroll_handler(cx.listener(|this, event: &ListScrollEvent, cx| {
+                if event.visible_range.start < MESSAGE_LOADING_THRESHOLD {
+                    this.load_more_messages(cx);
+                }
+                this.is_scrolled_to_bottom = event.visible_range.end == event.count;
+            }));
 
             let mut this = Self {
                 fs,
@@ -138,7 +139,6 @@ impl ChatPanel {
                 pending_serialization: Task::ready(None),
                 input_editor,
                 local_timezone: cx.local_timezone(),
-                has_focus: false,
                 subscriptions: Vec::new(),
                 workspace: workspace_handle,
                 is_scrolled_to_bottom: true,
@@ -311,19 +311,39 @@ impl ChatPanel {
     }
 
     fn render_channel(&self, cx: &mut ViewContext<Self>) -> AnyElement {
-        todo!()
-        // v_stack()
-        //     .child(Label::new(
-        //         self.active_chat.map_or(Default::default(), |c| {
-        //             c.0.read(cx).channel(cx)?.name.clone()
-        //         }),
-        //     ))
-        //     .child(self.render_active_channel_messages(cx))
-        //     .child(self.input_editor.clone())
-        //     .into_any()
-    }
-
-    fn render_active_channel_messages(&self, cx: &mut ViewContext<Self>) -> AnyElement {
+        v_stack()
+            .full()
+            .on_action(cx.listener(Self::send))
+            .child(
+                h_stack()
+                    .w_full()
+                    .justify_between()
+                    .child(Label::new(
+                        self.active_chat
+                            .as_ref()
+                            .and_then(|c| Some(c.0.read(cx).channel(cx)?.name.clone()))
+                            .unwrap_or_default(),
+                    ))
+                    .child(
+                        h_stack()
+                            .child(
+                                IconButton::new("notes", Icon::File)
+                                    .on_click(cx.listener(Self::open_notes))
+                                    .tooltip(|cx| Tooltip::text("Open notes", cx)),
+                            )
+                            .child(
+                                IconButton::new("call", Icon::AudioOn)
+                                    .on_click(cx.listener(Self::join_call))
+                                    .tooltip(|cx| Tooltip::text("Join call", cx)),
+                            ),
+                    ),
+            )
+            .child(div().grow().child(self.render_active_channel_messages(cx)))
+            .child(self.input_editor.clone())
+            .into_any()
+    }
+
+    fn render_active_channel_messages(&self, _cx: &mut ViewContext<Self>) -> AnyElement {
         if self.active_chat.is_some() {
             list(self.message_list.clone()).into_any_element()
         } else {
@@ -332,88 +352,81 @@ impl ChatPanel {
     }
 
     fn render_message(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
-        todo!()
-        // let (message, is_continuation, is_last, is_admin) = self
-        //     .active_chat
-        //     .as_ref()
-        //     .unwrap()
-        //     .0
-        //     .update(cx, |active_chat, cx| {
-        //         let is_admin = self
-        //             .channel_store
-        //             .read(cx)
-        //             .is_channel_admin(active_chat.channel_id);
-
-        //         let last_message = active_chat.message(ix.saturating_sub(1));
-        //         let this_message = active_chat.message(ix).clone();
-        //         let is_continuation = last_message.id != this_message.id
-        //             && this_message.sender.id == last_message.sender.id;
-
-        //         if let ChannelMessageId::Saved(id) = this_message.id {
-        //             if this_message
-        //                 .mentions
-        //                 .iter()
-        //                 .any(|(_, user_id)| Some(*user_id) == self.client.user_id())
-        //             {
-        //                 active_chat.acknowledge_message(id);
-        //             }
-        //         }
+        let active_chat = &self.active_chat.as_ref().unwrap().0;
+        let (message, is_continuation, is_admin) = active_chat.update(cx, |active_chat, cx| {
+            let is_admin = self
+                .channel_store
+                .read(cx)
+                .is_channel_admin(active_chat.channel_id);
+
+            let last_message = active_chat.message(ix.saturating_sub(1));
+            let this_message = active_chat.message(ix).clone();
+            let is_continuation = last_message.id != this_message.id
+                && this_message.sender.id == last_message.sender.id;
+
+            if let ChannelMessageId::Saved(id) = this_message.id {
+                if this_message
+                    .mentions
+                    .iter()
+                    .any(|(_, user_id)| Some(*user_id) == self.client.user_id())
+                {
+                    active_chat.acknowledge_message(id);
+                }
+            }
 
-        //         (
-        //             this_message,
-        //             is_continuation,
-        //             active_chat.message_count() == ix + 1,
-        //             is_admin,
-        //         )
-        //     });
-
-        // let is_pending = message.is_pending();
-        // let text = self.markdown_data.entry(message.id).or_insert_with(|| {
-        //     Self::render_markdown_with_mentions(&self.languages, self.client.id(), &message)
-        // });
+            (this_message, is_continuation, is_admin)
+        });
+
+        let _is_pending = message.is_pending();
+        let text = self.markdown_data.entry(message.id).or_insert_with(|| {
+            Self::render_markdown_with_mentions(&self.languages, self.client.id(), &message)
+        });
+
+        let now = OffsetDateTime::now_utc();
 
-        // let now = OffsetDateTime::now_utc();
-
-        // let belongs_to_user = Some(message.sender.id) == self.client.user_id();
-        // let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) =
-        //     (message.id, belongs_to_user || is_admin)
-        // {
-        //     Some(id)
-        // } else {
-        //     None
-        // };
-
-        // if is_continuation {
-        //     h_stack()
-        //         .child(text.element(cx))
-        //         .child(render_remove(message_id_to_remove, cx))
-        //         .mb_1()
-        //         .into_any()
-        // } else {
-        //     v_stack()
-        //         .child(
-        //             h_stack()
-        //                 .child(Avatar::data(message.sender.avatar.clone()))
-        //                 .child(Label::new(message.sender.github_login.clone()))
-        //                 .child(
-        //                     Label::new(format_timestamp(
-        //                         message.timestamp,
-        //                         now,
-        //                         self.local_timezone,
-        //                     ))
-        //                     .flex(1., true),
-        //                 )
-        //                 .child(render_remove(message_id_to_remove, cx))
-        //                 .align_children_center(),
-        //         )
-        //         .child(
-        //             h_stack()
-        //                 .child(text.element(cx))
-        //                 .child(render_remove(None, cx)),
-        //         )
-        //         .mb_1()
-        //         .into_any()
-        // }
+        let belongs_to_user = Some(message.sender.id) == self.client.user_id();
+        let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) =
+            (message.id, belongs_to_user || is_admin)
+        {
+            Some(id)
+        } else {
+            None
+        };
+
+        // todo!("render the text with markdown formatting")
+        if is_continuation {
+            h_stack()
+                .child(SharedString::from(text.text.clone()))
+                .child(render_remove(message_id_to_remove, cx))
+                .mb_1()
+                .into_any()
+        } else {
+            v_stack()
+                .child(
+                    h_stack()
+                        .children(
+                            message
+                                .sender
+                                .avatar
+                                .clone()
+                                .map(|avatar| Avatar::data(avatar)),
+                        )
+                        .child(Label::new(message.sender.github_login.clone()))
+                        .child(Label::new(format_timestamp(
+                            message.timestamp,
+                            now,
+                            self.local_timezone,
+                        )))
+                        .child(render_remove(message_id_to_remove, cx)),
+                )
+                .child(
+                    h_stack()
+                        .child(SharedString::from(text.text.clone()))
+                        .child(render_remove(None, cx)),
+                )
+                .mb_1()
+                .into_any()
+        }
     }
 
     fn render_markdown_with_mentions(
@@ -509,31 +522,25 @@ impl ChatPanel {
     // }
 
     fn render_sign_in_prompt(&self, cx: &mut ViewContext<Self>) -> AnyElement {
-        todo!()
-        // enum SignInPromptLabel {}
-
-        // Button::new("sign-in", "Sign in to use chat")
-        //     .on_click(move |_, this, cx| {
-        //         let client = this.client.clone();
-        //         cx.spawn(|this, mut cx| async move {
-        //             if client
-        //                 .authenticate_and_connect(true, &cx)
-        //                 .log_err()
-        //                 .await
-        //                 .is_some()
-        //             {
-        //                 this.update(&mut cx, |this, cx| {
-        //                     if cx.handle().is_focused(cx) {
-        //                         cx.focus(&this.input_editor);
-        //                     }
-        //                 })
-        //                 .ok();
-        //             }
-        //         })
-        //         .detach();
-        //     })
-        //     .aligned()
-        //     .into_any()
+        Button::new("sign-in", "Sign in to use chat")
+            .on_click(cx.listener(move |this, _, cx| {
+                let client = this.client.clone();
+                cx.spawn(|this, mut cx| async move {
+                    if client
+                        .authenticate_and_connect(true, &cx)
+                        .log_err()
+                        .await
+                        .is_some()
+                    {
+                        this.update(&mut cx, |_, cx| {
+                            cx.focus_self();
+                        })
+                        .ok();
+                    }
+                })
+                .detach();
+            }))
+            .into_any_element()
     }
 
     fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
@@ -557,7 +564,7 @@ impl ChatPanel {
         }
     }
 
-    fn load_more_messages(&mut self, _: &LoadMoreMessages, cx: &mut ViewContext<Self>) {
+    fn load_more_messages(&mut self, cx: &mut ViewContext<Self>) {
         if let Some((chat, _)) = self.active_chat.as_ref() {
             chat.update(cx, |channel, cx| {
                 if let Some(task) = channel.load_more_messages(cx) {
@@ -573,48 +580,47 @@ impl ChatPanel {
         scroll_to_message_id: Option<u64>,
         cx: &mut ViewContext<ChatPanel>,
     ) -> Task<Result<()>> {
-        todo!()
-        // let open_chat = self
-        //     .active_chat
-        //     .as_ref()
-        //     .and_then(|(chat, _)| {
-        //         (chat.read(cx).channel_id == selected_channel_id)
-        //             .then(|| Task::ready(anyhow::Ok(chat.clone())))
-        //     })
-        //     .unwrap_or_else(|| {
-        //         self.channel_store.update(cx, |store, cx| {
-        //             store.open_channel_chat(selected_channel_id, cx)
-        //         })
-        //     });
-
-        // cx.spawn(|this, mut cx| async move {
-        //     let chat = open_chat.await?;
-        //     this.update(&mut cx, |this, cx| {
-        //         this.set_active_chat(chat.clone(), cx);
-        //     })?;
-
-        //     if let Some(message_id) = scroll_to_message_id {
-        //         if let Some(item_ix) =
-        //             ChannelChat::load_history_since_message(chat.clone(), message_id, cx.clone())
-        //                 .await
-        //         {
-        //             this.update(&mut cx, |this, cx| {
-        //                 if this.active_chat.as_ref().map_or(false, |(c, _)| *c == chat) {
-        //                     this.message_list.scroll_to(ListOffset {
-        //                         item_ix,
-        //                         offset_in_item: px(0.0),
-        //                     });
-        //                     cx.notify();
-        //                 }
-        //             })?;
-        //         }
-        //     }
+        let open_chat = self
+            .active_chat
+            .as_ref()
+            .and_then(|(chat, _)| {
+                (chat.read(cx).channel_id == selected_channel_id)
+                    .then(|| Task::ready(anyhow::Ok(chat.clone())))
+            })
+            .unwrap_or_else(|| {
+                self.channel_store.update(cx, |store, cx| {
+                    store.open_channel_chat(selected_channel_id, cx)
+                })
+            });
+
+        cx.spawn(|this, mut cx| async move {
+            let chat = open_chat.await?;
+            this.update(&mut cx, |this, cx| {
+                this.set_active_chat(chat.clone(), cx);
+            })?;
+
+            if let Some(message_id) = scroll_to_message_id {
+                if let Some(item_ix) =
+                    ChannelChat::load_history_since_message(chat.clone(), message_id, (*cx).clone())
+                        .await
+                {
+                    this.update(&mut cx, |this, cx| {
+                        if this.active_chat.as_ref().map_or(false, |(c, _)| *c == chat) {
+                            this.message_list.scroll_to(ListOffset {
+                                item_ix,
+                                offset_in_item: px(0.0),
+                            });
+                            cx.notify();
+                        }
+                    })?;
+                }
+            }
 
-        //     Ok(())
-        // })
+            Ok(())
+        })
     }
 
-    fn open_notes(&mut self, _: &OpenChannelNotes, cx: &mut ViewContext<Self>) {
+    fn open_notes(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
         if let Some((chat, _)) = &self.active_chat {
             let channel_id = chat.read(cx).channel_id;
             if let Some(workspace) = self.workspace.upgrade() {
@@ -623,7 +629,7 @@ impl ChatPanel {
         }
     }
 
-    fn join_call(&mut self, _: &JoinCall, cx: &mut ViewContext<Self>) {
+    fn join_call(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
         if let Some((chat, _)) = &self.active_chat {
             let channel_id = chat.read(cx).channel_id;
             ActiveCall::global(cx)
@@ -634,40 +640,15 @@ impl ChatPanel {
 }
 
 fn render_remove(message_id_to_remove: Option<u64>, cx: &mut ViewContext<ChatPanel>) -> AnyElement {
-    todo!()
-    // enum DeleteMessage {}
-
-    // message_id_to_remove
-    //     .map(|id| {
-    //         MouseEventHandler::new::<DeleteMessage, _>(id as usize, cx, |mouse_state, _| {
-    //             let button_style = theme.chat_panel.icon_button.style_for(mouse_state);
-    //             render_icon_button(button_style, "icons/x.svg")
-    //                 .aligned()
-    //                 .into_any()
-    //         })
-    //         .with_padding(Padding::uniform(2.))
-    //         .with_cursor_style(CursorStyle::PointingHand)
-    //         .on_click(MouseButton::Left, move |_, this, cx| {
-    //             this.remove_message(id, cx);
-    //         })
-    //         .flex_float()
-    //         .into_any()
-    //     })
-    //     .unwrap_or_else(|| {
-    //         let style = theme.chat_panel.icon_button.default;
-
-    //         Empty::new()
-    //             .constrained()
-    //             .with_width(style.icon_width)
-    //             .aligned()
-    //             .constrained()
-    //             .with_width(style.button_width)
-    //             .with_height(style.button_width)
-    //             .contained()
-    //             .with_uniform_padding(2.)
-    //             .flex_float()
-    //             .into_any()
-    //     })
+    if let Some(message_id) = message_id_to_remove {
+        IconButton::new(("remove", message_id), Icon::XCircle)
+            .on_click(cx.listener(move |this, _, cx| {
+                this.remove_message(message_id, cx);
+            }))
+            .into_any_element()
+    } else {
+        div().into_any_element()
+    }
 }
 
 impl EventEmitter<Event> for ChatPanel {}
@@ -677,6 +658,7 @@ impl Render for ChatPanel {
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         div()
+            .full()
             .child(if self.client.user_id().is_some() {
                 self.render_channel(cx)
             } else {
@@ -729,7 +711,7 @@ impl Panel for ChatPanel {
     }
 
     fn persistent_name() -> &'static str {
-        todo!()
+        "ChatPanel"
     }
 
     fn icon(&self, _cx: &WindowContext) -> Option<ui::Icon> {
@@ -737,7 +719,7 @@ impl Panel for ChatPanel {
     }
 
     fn toggle_action(&self) -> Box<dyn gpui::Action> {
-        todo!()
+        Box::new(ToggleFocus)
     }
 }
 

crates/collab_ui2/src/chat_panel/message_editor.rs 🔗

@@ -3,7 +3,8 @@ use client::UserId;
 use collections::HashMap;
 use editor::{AnchorRangeExt, Editor};
 use gpui::{
-    AnyView, AsyncWindowContext, Model, Render, SharedString, Task, View, ViewContext, WeakView,
+    AnyView, AsyncWindowContext, FocusableView, Model, Render, SharedString, Task, View,
+    ViewContext, WeakView,
 };
 use language::{language_settings::SoftWrap, Buffer, BufferSnapshot, LanguageRegistry};
 use lazy_static::lazy_static;
@@ -52,8 +53,7 @@ impl MessageEditor {
             let markdown = markdown.await?;
             buffer.update(&mut cx, |buffer, cx| {
                 buffer.set_language(Some(markdown), cx)
-            });
-            anyhow::Ok(())
+            })
         })
         .detach_and_log_err(cx);
 
@@ -191,14 +191,14 @@ impl MessageEditor {
     }
 
     pub(crate) fn focus_handle(&self, cx: &gpui::AppContext) -> gpui::FocusHandle {
-        todo!()
+        self.editor.read(cx).focus_handle(cx)
     }
 }
 
 impl Render for MessageEditor {
     type Element = AnyView;
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
         self.editor.to_any()
     }
 }

crates/collab_ui2/src/collab_panel.rs 🔗

@@ -192,6 +192,7 @@ use workspace::{
 };
 
 use crate::channel_view::ChannelView;
+use crate::chat_panel::ChatPanel;
 use crate::{face_pile::FacePile, CollaborationPanelSettings};
 
 use self::channel_modal::ChannelModal;
@@ -2102,14 +2103,13 @@ impl CollabPanel {
         };
         cx.window_context().defer(move |cx| {
             workspace.update(cx, |workspace, cx| {
-                todo!();
-                // if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
-                //     panel.update(cx, |panel, cx| {
-                //         panel
-                //             .select_channel(channel_id, None, cx)
-                //             .detach_and_log_err(cx);
-                //     });
-                // }
+                if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
+                    panel.update(cx, |panel, cx| {
+                        panel
+                            .select_channel(channel_id, None, cx)
+                            .detach_and_log_err(cx);
+                    });
+                }
             });
         });
     }
@@ -2603,9 +2603,14 @@ impl CollabPanel {
                                                     Color::Default
                                                 } else {
                                                     Color::Muted
+                                                })
+                                                .on_click(cx.listener(move |this, _, cx| {
+                                                    this.join_channel_chat(channel_id, cx)
+                                                }))
+                                                .tooltip(|cx| {
+                                                    Tooltip::text("Open channel chat", cx)
                                                 }),
-                                            )
-                                            .tooltip(|cx| Tooltip::text("Open channel chat", cx)),
+                                            ),
                                     )
                                     .child(
                                         div()

crates/gpui2/src/elements/list.rs 🔗

@@ -132,7 +132,7 @@ impl ListState {
     }
 
     pub fn set_scroll_handler(
-        &mut self,
+        &self,
         handler: impl FnMut(&ListScrollEvent, &mut WindowContext) + 'static,
     ) {
         self.0.borrow_mut().scroll_handler = Some(Box::new(handler))

crates/workspace2/src/status_bar.rs 🔗

@@ -1,12 +1,10 @@
-use std::any::TypeId;
-
 use crate::{ItemHandle, Pane};
 use gpui::{
     div, AnyView, Div, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
     WindowContext,
 };
-use ui::h_stack;
-use ui::prelude::*;
+use std::any::TypeId;
+use ui::{h_stack, prelude::*};
 use util::ResultExt;
 
 pub trait StatusItemView: Render {
@@ -47,8 +45,8 @@ impl Render for StatusBar {
             .w_full()
             .h_8()
             .bg(cx.theme().colors().status_bar_background)
-            .child(h_stack().gap_1().child(self.render_left_tools(cx)))
-            .child(h_stack().gap_4().child(self.render_right_tools(cx)))
+            .child(self.render_left_tools(cx))
+            .child(self.render_right_tools(cx))
     }
 }
 

crates/zed2/src/zed2.rs 🔗

@@ -160,8 +160,8 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
             let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone());
             let channels_panel =
                 collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone());
-            // let chat_panel =
-            //     collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone());
+            let chat_panel =
+                collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone());
             // let notification_panel = collab_ui::notification_panel::NotificationPanel::load(
             //     workspace_handle.clone(),
             //     cx.clone(),
@@ -171,14 +171,14 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
                 terminal_panel,
                 assistant_panel,
                 channels_panel,
-                //     chat_panel,
+                chat_panel,
                 //     notification_panel,
             ) = futures::try_join!(
                 project_panel,
                 terminal_panel,
                 assistant_panel,
                 channels_panel,
-                //     chat_panel,
+                chat_panel,
                 //     notification_panel,
             )?;
 
@@ -188,7 +188,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
                 workspace.add_panel(terminal_panel, cx);
                 workspace.add_panel(assistant_panel, cx);
                 workspace.add_panel(channels_panel, cx);
-                //     workspace.add_panel(chat_panel, cx);
+                workspace.add_panel(chat_panel, cx);
                 //     workspace.add_panel(notification_panel, cx);
 
                 // if !was_deserialized