Parcourir la source

Merge pull request #10355 from growilabs/imprv/171534-improve-knowledge-assistant-chat-ui

imprv: Improve KnowledgeAssistant chat UI UX
Yuki Takei il y a 5 mois
Parent
commit
ce0afab3f1

+ 9 - 0
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/AiAssistantSidebar.module.scss

@@ -17,6 +17,15 @@
   .btn-submit {
     font-size: 1.1em;
   }
+
+  .thread-title-sticky {
+    -webkit-backdrop-filter: blur(10px);
+    backdrop-filter: blur(10px);
+  }
+
+  .input-form-area {
+    box-shadow: 0 -10px 20px 10px rgba(var(--bs-body-bg-rgb), 1);
+  }
 }
 
 // == Colors

+ 38 - 34
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/AiAssistantSidebar.tsx

@@ -82,6 +82,7 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
     headerIcon: headerIconForKnowledgeAssistant,
     headerText: headerTextForKnowledgeAssistant,
     placeHolder: placeHolderForKnowledgeAssistant,
+    threadTitleView: threadTitleViewForKnowledgeAssistant,
   } = useKnowledgeAssistant();
 
   const {
@@ -432,47 +433,50 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
             className="h-100"
             autoHide
           >
-            <div className="p-4 d-flex flex-column gap-4 flex-grow-1">
-              { threadData != null
-                ? (
-                  <div className="vstack gap-4 pb-2">
-                    { messageLogs.map(message => (
-                      <>
+            {!isEditorAssistant && threadTitleViewForKnowledgeAssistant}
+            <div className="p-4">
+              <div className="d-flex flex-column gap-4 flex-grow-1">
+                { threadData != null
+                  ? (
+                    <div className="vstack gap-4 pb-2">
+                      { messageLogs.map(message => (
+                        <>
+                          <MessageCard
+                            role={message.isUserMessage ? 'user' : 'assistant'}
+                            additionalItem={messageCardAdditionalItemForGeneratedMessage(message.id)}
+                          >
+                            {message.content}
+                          </MessageCard>
+                        </>
+                      )) }
+                      { generatingAnswerMessage != null && (
                         <MessageCard
-                          role={message.isUserMessage ? 'user' : 'assistant'}
-                          additionalItem={messageCardAdditionalItemForGeneratedMessage(message.id)}
+                          role="assistant"
+                          additionalItem={messageCardAdditionalItemForGeneratingMessage}
                         >
-                          {message.content}
+                          {generatingAnswerMessage.content}
                         </MessageCard>
-                      </>
-                    )) }
-                    { generatingAnswerMessage != null && (
-                      <MessageCard
-                        role="assistant"
-                        additionalItem={messageCardAdditionalItemForGeneratingMessage}
-                      >
-                        {generatingAnswerMessage.content}
-                      </MessageCard>
-                    )}
-                    { isEditorAssistant && partialContentWarnLabel }
-                    { messageLogs.length > 0 && (
-                      <div className="d-flex justify-content-center">
-                        <span className="bg-body-tertiary text-body-secondary rounded-pill px-3 py-1" style={{ fontSize: 'smaller' }}>
-                          {t('sidebar_ai_assistant.caution_against_hallucination')}
-                        </span>
-                      </div>
-                    )}
-                  </div>
-                )
-                : (
-                  <>{ initialView }</>
-                )
-              }
+                      )}
+                      { isEditorAssistant && partialContentWarnLabel }
+                      { messageLogs.length > 0 && (
+                        <div className="d-flex justify-content-center">
+                          <span className="bg-body-tertiary text-body-secondary rounded-pill px-3 py-1" style={{ fontSize: 'smaller' }}>
+                            {t('sidebar_ai_assistant.caution_against_hallucination')}
+                          </span>
+                        </div>
+                      )}
+                    </div>
+                  )
+                  : (
+                    <>{ initialView }</>
+                  )
+                }
+              </div>
             </div>
           </SimpleBar>
         </div>
 
-        <div className="position-sticky bottom-0 bg-body z-2 p-3 border-top">
+        <div className="input-form-area position-sticky bg-body z-2 p-3">
           <form onSubmit={form.handleSubmit(submit)} className="flex-fill vstack gap-1">
             <Controller
               name="input"

+ 32 - 4
apps/app/src/features/openai/client/services/knowledge-assistant.tsx

@@ -61,6 +61,7 @@ type UseKnowledgeAssistant = () => {
   processMessage: ProcessMessage
   form: UseFormReturn<FormData>
   resetForm: () => void
+  threadTitleView: JSX.Element
 
   // Views
   initialView: JSX.Element
@@ -72,8 +73,8 @@ type UseKnowledgeAssistant = () => {
 
 export const useKnowledgeAssistant: UseKnowledgeAssistant = () => {
   // Hooks
-  const { data: aiAssistantSidebarData } = useAiAssistantSidebar();
-  const { aiAssistantData, threadData } = aiAssistantSidebarData ?? {};
+  const { data: aiAssistantSidebarData, refreshThreadData } = useAiAssistantSidebar();
+  const { aiAssistantData } = aiAssistantSidebarData ?? {};
   const { mutate: mutateRecentThreads } = useSWRINFxRecentThreads();
   const { trigger: mutateThreadData } = useSWRMUTxThreads(aiAssistantData?._id);
   const { t } = useTranslation();
@@ -85,6 +86,9 @@ export const useKnowledgeAssistant: UseKnowledgeAssistant = () => {
       extendedThinkingMode: false,
     },
   });
+  const handleBackToInitialView = useCallback(() => {
+    refreshThreadData(undefined);
+  }, [refreshThreadData]);
 
   // Functions
   const resetForm = useCallback(() => {
@@ -141,8 +145,8 @@ export const useKnowledgeAssistant: UseKnowledgeAssistant = () => {
   }, []);
 
   const headerText = useMemo(() => {
-    return <>{threadData?.title ?? aiAssistantData?.name}</>;
-  }, [aiAssistantData?.name, threadData?.title]);
+    return <>{aiAssistantData?.name}</>;
+  }, [aiAssistantData?.name]);
 
   const placeHolder = useMemo(() => { return 'sidebar_ai_assistant.knowledge_assistant_placeholder' }, []);
 
@@ -231,12 +235,36 @@ export const useKnowledgeAssistant: UseKnowledgeAssistant = () => {
     );
   }, [dropdownOpen, toggleDropdown, form, t]);
 
+  const threadTitleView = useMemo(() => {
+    const { threadData } = aiAssistantSidebarData ?? {};
+
+    if (threadData?.title == null) {
+      return <></>;
+    }
+
+    return (
+      <div className="thread-title-sticky sticky-top bg-body bg-opacity-75 py-2 px-3 z-1 ">
+        <div className="d-flex align-items-center gap-2">
+          <button
+            type="button"
+            className="btn btn-sm btn-link p-0 text-secondary"
+            onClick={handleBackToInitialView}
+          >
+            <span className="material-symbols-outlined">chevron_left</span>
+          </button>
+          <span className="text-truncate small">{threadData.title}</span>
+        </div>
+      </div>
+    );
+  }, [aiAssistantSidebarData, handleBackToInitialView]);
+
   return {
     createThread,
     postMessage,
     processMessage,
     form,
     resetForm,
+    threadTitleView,
 
     // Views
     initialView,