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

optimize AiAssistantManagementModal

Yuki Takei 7 месяцев назад
Родитель
Сommit
929b4ac3b6

+ 97 - 63
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementModal.tsx

@@ -1,5 +1,5 @@
 import React, {
-  useCallback, useState, useEffect, type JSX,
+  useCallback, useState, useEffect, useMemo, type JSX,
 } from 'react';
 
 import type { IPageHasId } from '@growi/core';
@@ -11,7 +11,6 @@ import { useTranslation } from 'react-i18next';
 import { Modal, TabContent, TabPane } from 'reactstrap';
 
 import { toastError, toastSuccess } from '~/client/util/toastr';
-import type { UpsertAiAssistantData } from '~/features/openai/interfaces/ai-assistant';
 import { AiAssistantAccessScope, AiAssistantShareScope } from '~/features/openai/interfaces/ai-assistant';
 import type { IPagePathWithDescendantCount } from '~/interfaces/page';
 import type { PopulatedGrantedGroup } from '~/interfaces/page-grant';
@@ -56,7 +55,11 @@ const convertToPopulatedGrantedGroups = (selectedGroups: IGrantedGroup[]): Popul
   return populatedGrantedGroups;
 };
 
-const convertToSelectedPages = (pagePathPatterns: string[], pagePathsWithDescendantCount: IPagePathWithDescendantCount[]): SelectablePage[] => {
+// Convert page path patterns to selectable pages
+const convertToSelectedPages = (
+    pagePathPatterns: string[],
+    pagePathsWithDescendantCount: IPagePathWithDescendantCount[],
+): SelectablePage[] => {
   return pagePathPatterns.map((pagePathPattern) => {
     const pathWithoutGlob = isGlobPatternPath(pagePathPattern) ? pagePathPattern.slice(0, -2) : pagePathPattern;
     const page = pagePathsWithDescendantCount.find(p => p.path === pathWithoutGlob);
@@ -84,6 +87,20 @@ const AiAssistantManagementModalSubstance = (): JSX.Element => {
   const shouldEdit = aiAssistant != null;
   const pageMode = aiAssistantManagementModalData?.pageMode ?? AiAssistantManagementModalPageMode.HOME;
 
+  // Memoized populated granted groups for access scope
+  const populatedGrantedGroupsForAccessScope = useMemo(() => {
+    return aiAssistant?.grantedGroupsForAccessScope
+      ? convertToPopulatedGrantedGroups(aiAssistant.grantedGroupsForAccessScope)
+      : [];
+  }, [aiAssistant?.grantedGroupsForAccessScope]);
+
+  // Memoized populated granted groups for share scope
+  const populatedGrantedGroupsForShareScope = useMemo(() => {
+    return aiAssistant?.grantedGroupsForShareScope
+      ? convertToPopulatedGrantedGroups(aiAssistant.grantedGroupsForShareScope)
+      : [];
+  }, [aiAssistant?.grantedGroupsForShareScope]);
+
 
   // States
   const [name, setName] = useState<string>('');
@@ -104,11 +121,11 @@ const AiAssistantManagementModalSubstance = (): JSX.Element => {
       setInstruction(aiAssistant.additionalInstruction);
       setSelectedShareScope(aiAssistant.shareScope);
       setSelectedAccessScope(aiAssistant.accessScope);
-      setSelectedUserGroupsForShareScope(convertToPopulatedGrantedGroups(aiAssistant.grantedGroupsForShareScope ?? []));
-      setSelectedUserGroupsForAccessScope(convertToPopulatedGrantedGroups(aiAssistant.grantedGroupsForAccessScope ?? []));
+      setSelectedUserGroupsForShareScope(populatedGrantedGroupsForShareScope);
+      setSelectedUserGroupsForAccessScope(populatedGrantedGroupsForAccessScope);
     }
   // eslint-disable-next-line max-len
-  }, [aiAssistant?.accessScope, aiAssistant?.additionalInstruction, aiAssistant?.description, aiAssistant?.grantedGroupsForAccessScope, aiAssistant?.grantedGroupsForShareScope, aiAssistant?.name, aiAssistant?.pagePathPatterns, aiAssistant?.shareScope, shouldEdit]);
+  }, [aiAssistant?.accessScope, aiAssistant?.additionalInstruction, aiAssistant?.description, aiAssistant?.name, aiAssistant?.pagePathPatterns, aiAssistant?.shareScope, shouldEdit, populatedGrantedGroupsForShareScope, populatedGrantedGroupsForAccessScope]);
 
   useEffect(() => {
     if (shouldEdit && pagePathsWithDescendantCount != null) {
@@ -128,46 +145,57 @@ const AiAssistantManagementModalSubstance = (): JSX.Element => {
   /*
   *  For AiAssistantManagementHome methods
   */
-  const changeNameHandler = useCallback((value: string) => {
+  const changeNameHandler = (value: string) => {
     setName(value);
-  }, []);
+  };
 
-  const changeDescriptionHandler = useCallback((value: string) => {
+  const changeDescriptionHandler = (value: string) => {
     setDescription(value);
-  }, []);
+  };
+
+  // Memoized request body for upsert operation
+  const requestBodyData = useMemo(() => {
+    const pagePathPatterns = selectedPages.map(selectedPage => selectedPage.path);
+
+    const grantedGroupsForShareScope = selectedShareScope === AiAssistantShareScope.GROUPS
+      ? convertToGrantedGroups(selectedUserGroupsForShareScope)
+      : undefined;
+
+    const grantedGroupsForAccessScope = selectedAccessScope === AiAssistantAccessScope.GROUPS
+      ? convertToGrantedGroups(selectedUserGroupsForAccessScope)
+      : undefined;
+
+    return {
+      name,
+      description,
+      additionalInstruction: instruction,
+      pagePathPatterns,
+      shareScope: selectedShareScope,
+      accessScope: selectedAccessScope,
+      grantedGroupsForShareScope,
+      grantedGroupsForAccessScope,
+    };
+  }, [
+    selectedPages,
+    selectedShareScope,
+    selectedUserGroupsForShareScope,
+    selectedAccessScope,
+    selectedUserGroupsForAccessScope,
+    name,
+    description,
+    instruction,
+  ]);
 
   const upsertAiAssistantHandler = useCallback(async() => {
     try {
-      const pagePathPatterns = selectedPages
-        .map(selectedPage => selectedPage.path);
-
-      const grantedGroupsForShareScope = selectedShareScope === AiAssistantShareScope.GROUPS
-        ? convertToGrantedGroups(selectedUserGroupsForShareScope)
-        : undefined;
-
-      const grantedGroupsForAccessScope = selectedAccessScope === AiAssistantAccessScope.GROUPS
-        ? convertToGrantedGroups(selectedUserGroupsForAccessScope)
-        : undefined;
-
-      const reqBody: UpsertAiAssistantData = {
-        name,
-        description,
-        additionalInstruction: instruction,
-        pagePathPatterns,
-        shareScope: selectedShareScope,
-        accessScope: selectedAccessScope,
-        grantedGroupsForShareScope,
-        grantedGroupsForAccessScope,
-      };
-
       if (shouldEdit) {
-        const updatedAiAssistant = await updateAiAssistant(aiAssistant._id, reqBody);
+        const updatedAiAssistant = await updateAiAssistant(aiAssistant._id, requestBodyData);
         if (aiAssistantSidebarData?.aiAssistantData?._id === updatedAiAssistant._id) {
           refreshAiAssistantData(updatedAiAssistant);
         }
       }
       else {
-        await createAiAssistant(reqBody);
+        await createAiAssistant(requestBodyData);
       }
 
       toastSuccess(shouldEdit ? t('modal_ai_assistant.toaster.update_success') : t('modal_ai_assistant.toaster.create_success'));
@@ -179,62 +207,68 @@ const AiAssistantManagementModalSubstance = (): JSX.Element => {
       logger.error(err);
     }
   }, [
-    selectedPages, selectedShareScope, selectedUserGroupsForShareScope, selectedAccessScope,
-    selectedUserGroupsForAccessScope, name, description, instruction, shouldEdit, t, mutateAiAssistants,
-    closeAiAssistantManagementModal, aiAssistant?._id, aiAssistantSidebarData?.aiAssistantData?._id, refreshAiAssistantData,
+    shouldEdit,
+    requestBodyData,
+    aiAssistant?._id,
+    aiAssistantSidebarData?.aiAssistantData?._id,
+    refreshAiAssistantData,
+    t,
+    mutateAiAssistants,
+    closeAiAssistantManagementModal,
   ]);
 
 
   /*
   *  For AiAssistantManagementEditShare methods
   */
-  const selectShareScopeHandler = useCallback((shareScope: AiAssistantShareScope) => {
+  const selectShareScopeHandler = (shareScope: AiAssistantShareScope) => {
     setSelectedShareScope(shareScope);
-  }, []);
+  };
 
-  const selectAccessScopeHandler = useCallback((accessScope: AiAssistantAccessScope) => {
+  const selectAccessScopeHandler = (accessScope: AiAssistantAccessScope) => {
     setSelectedAccessScope(accessScope);
-  }, []);
+  };
 
+  // Memoized user group selection handlers
   const selectShareScopeUserGroups = useCallback((targetUserGroup: PopulatedGrantedGroup) => {
-    const selectedUserGroupIds = selectedUserGroupsForShareScope.map(userGroup => userGroup.item._id);
-    if (selectedUserGroupIds.includes(targetUserGroup.item._id)) {
-      // if selected, remove it
-      setSelectedUserGroupsForShareScope(selectedUserGroupsForShareScope.filter(userGroup => userGroup.item._id !== targetUserGroup.item._id));
-    }
-    else {
+    setSelectedUserGroupsForShareScope((prev) => {
+      const selectedUserGroupIds = prev.map(userGroup => userGroup.item._id);
+      if (selectedUserGroupIds.includes(targetUserGroup.item._id)) {
+        // if selected, remove it
+        return prev.filter(userGroup => userGroup.item._id !== targetUserGroup.item._id);
+      }
       // if not selected, add it
-      setSelectedUserGroupsForShareScope([...selectedUserGroupsForShareScope, targetUserGroup]);
-    }
-  }, [selectedUserGroupsForShareScope]);
+      return [...prev, targetUserGroup];
+    });
+  }, []);
 
   const selectAccessScopeUserGroups = useCallback((targetUserGroup: PopulatedGrantedGroup) => {
-    const selectedUserGroupIds = selectedUserGroupsForAccessScope.map(userGroup => userGroup.item._id);
-    if (selectedUserGroupIds.includes(targetUserGroup.item._id)) {
-      // if selected, remove it
-      setSelectedUserGroupsForAccessScope(selectedUserGroupsForAccessScope.filter(userGroup => userGroup.item._id !== targetUserGroup.item._id));
-    }
-    else {
+    setSelectedUserGroupsForAccessScope((prev) => {
+      const selectedUserGroupIds = prev.map(userGroup => userGroup.item._id);
+      if (selectedUserGroupIds.includes(targetUserGroup.item._id)) {
+        // if selected, remove it
+        return prev.filter(userGroup => userGroup.item._id !== targetUserGroup.item._id);
+      }
       // if not selected, add it
-      setSelectedUserGroupsForAccessScope([...selectedUserGroupsForAccessScope, targetUserGroup]);
-    }
-  }, [selectedUserGroupsForAccessScope]);
+      return [...prev, targetUserGroup];
+    });
+  }, []);
 
 
   /*
   *  For AiAssistantManagementEditPages methods
   */
   const removePageHandler = useCallback((pagePath: string) => {
-    setSelectedPages(selectedPages.filter(selectedPage => selectedPage.path !== pagePath));
-  }, [selectedPages]);
+    setSelectedPages(prev => prev.filter(selectedPage => selectedPage.path !== pagePath));
+  }, []);
 
 
   /*
   *  For AiAssistantManagementEditInstruction methods
   */
-  const changeInstructionHandler = useCallback((value: string) => {
+  const changeInstructionHandler = (value: string) => {
     setInstruction(value);
-  }, []);
+  };
 
   const resetInstructionHandler = useCallback(() => {
     setInstruction(t('modal_ai_assistant.default_instruction'));