Просмотр исходного кода

Merge pull request #23237 from overleaf/mj-ide-chat

[web] Add existing chat pane to the editor redesign

GitOrigin-RevId: 21b4c80b18ec1dc554f1eb2fac95994f0352fefc
Mathias Jakobsen 1 год назад
Родитель
Сommit
d0c94a0a56

+ 108 - 0
services/web/frontend/js/features/ide-redesign/components/chat.tsx

@@ -0,0 +1,108 @@
+import ChatFallbackError from '@/features/chat/components/chat-fallback-error'
+import InfiniteScroll from '@/features/chat/components/infinite-scroll'
+import MessageInput from '@/features/chat/components/message-input'
+import { useChatContext } from '@/features/chat/context/chat-context'
+import OLBadge from '@/features/ui/components/ol/ol-badge'
+import { FetchError } from '@/infrastructure/fetch-json'
+import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
+import MaterialIcon from '@/shared/components/material-icon'
+import { useUserContext } from '@/shared/context/user-context'
+import { lazy, Suspense, useEffect } from 'react'
+import { useTranslation } from 'react-i18next'
+
+const MessageList = lazy(() => import('../../chat/components/message-list'))
+
+export const ChatIndicator = () => {
+  const { unreadMessageCount } = useChatContext()
+  if (unreadMessageCount === 0) {
+    return null
+  }
+  return <OLBadge bg="info">{unreadMessageCount}</OLBadge>
+}
+
+const Loading = () => <FullSizeLoadingSpinner delay={500} className="pt-4" />
+
+export const ChatPane = () => {
+  const { t } = useTranslation()
+
+  const user = useUserContext()
+  const {
+    status,
+    messages,
+    initialMessagesLoaded,
+    atEnd,
+    loadInitialMessages,
+    loadMoreMessages,
+    reset,
+    sendMessage,
+    markMessagesAsRead,
+    error,
+  } = useChatContext()
+
+  useEffect(() => {
+    if (!initialMessagesLoaded) {
+      loadInitialMessages()
+    }
+  }, [loadInitialMessages, initialMessagesLoaded])
+
+  const shouldDisplayPlaceholder = status !== 'pending' && messages.length === 0
+
+  const messageContentCount = messages.reduce(
+    (acc, { contents }) => acc + contents.length,
+    0
+  )
+
+  if (error) {
+    // let user try recover from fetch errors
+    if (error instanceof FetchError) {
+      return <ChatFallbackError reconnect={reset} />
+    }
+    throw error
+  }
+
+  if (!user) {
+    return null
+  }
+
+  return (
+    <aside className="chat">
+      <InfiniteScroll
+        atEnd={atEnd}
+        className="messages"
+        fetchData={loadMoreMessages}
+        isLoading={status === 'pending'}
+        itemCount={messageContentCount}
+      >
+        <div>
+          <h2 className="visually-hidden">{t('chat')}</h2>
+          <Suspense fallback={<Loading />}>
+            {status === 'pending' && <Loading />}
+            {shouldDisplayPlaceholder && <Placeholder />}
+            <MessageList
+              messages={messages}
+              resetUnreadMessages={markMessagesAsRead}
+            />
+          </Suspense>
+        </div>
+      </InfiniteScroll>
+      <MessageInput
+        resetUnreadMessages={markMessagesAsRead}
+        sendMessage={sendMessage}
+      />
+    </aside>
+  )
+}
+
+function Placeholder() {
+  const { t } = useTranslation()
+  return (
+    <>
+      <div className="no-messages text-center small">{t('no_messages')}</div>
+      <div className="first-message text-center">
+        {t('send_first_message')}
+        <br />
+        <MaterialIcon type="arrow_downward" />
+      </div>
+    </>
+  )
+}

+ 18 - 11
services/web/frontend/js/features/ide-redesign/components/rail.tsx

@@ -8,12 +8,15 @@ import { useLayoutContext } from '@/shared/context/layout-context'
 import { ErrorIndicator, ErrorPane } from './errors'
 import { RailTabKey, useRailTabContext } from '../contexts/rail-tab-context'
 import FileTreeOutlinePanel from './file-tree-outline-panel'
+import { ChatIndicator, ChatPane } from './chat'
+import getMeta from '@/utils/meta'
 
 type RailElement = {
   icon: AvailableUnfilledIcon
   key: RailTabKey
   component: ReactElement
   indicator?: ReactElement
+  hide?: boolean
 }
 
 type RailActionLink = { key: string; icon: AvailableUnfilledIcon; href: string }
@@ -43,7 +46,9 @@ const RAIL_TABS: RailElement[] = [
   {
     key: 'chat',
     icon: 'forum',
-    component: <>Chat</>,
+    component: <ChatPane />,
+    indicator: <ChatIndicator />,
+    hide: !getMeta('ol-chatEnabled'),
   },
   {
     key: 'errors',
@@ -86,15 +91,17 @@ export const RailLayout = () => {
           activeKey={selectedTab}
           className="d-flex flex-column ide-rail-tabs-nav"
         >
-          {RAIL_TABS.map(({ icon, key, indicator }) => (
-            <RailTab
-              active={selectedTab === key}
-              key={key}
-              eventKey={key}
-              icon={icon}
-              indicator={indicator}
-            />
-          ))}
+          {RAIL_TABS.filter(({ hide }) => !hide).map(
+            ({ icon, key, indicator }) => (
+              <RailTab
+                active={selectedTab === key}
+                key={key}
+                eventKey={key}
+                icon={icon}
+                indicator={indicator}
+              />
+            )
+          )}
           <div className="flex-grow-1" />
           {railActions?.map(action => (
             <RailActionElement key={action.key} action={action} />
@@ -110,7 +117,7 @@ export const RailLayout = () => {
       >
         <div className="ide-rail-content">
           <Tab.Content>
-            {RAIL_TABS.map(({ key, component }) => (
+            {RAIL_TABS.filter(({ hide }) => !hide).map(({ key, component }) => (
               <Tab.Pane eventKey={key} key={key}>
                 {component}
               </Tab.Pane>

+ 11 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/editor/chat.scss

@@ -20,6 +20,17 @@
   --chat-message-name-color: var(--neutral-70);
 }
 
+.ide-redesign-main {
+  --chat-bg: var(--white);
+  --chat-color: var(--neutral-70);
+  --chat-instructions-color: var(--neutral-70);
+  --chat-new-message-bg: var(--neutral-10);
+  --chat-new-message-textarea-color: var(--neutral-90);
+  --chat-new-message-textarea-bg: var(--white);
+  --chat-message-date-color: var(--neutral-70);
+  --chat-message-name-color: var(--neutral-70);
+}
+
 .chat {
   $new-message-height: 80px;