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

feat: support for assistant updates

Shun Miyazawa 1 год назад
Родитель
Сommit
2060ea0a09

+ 4 - 2
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementHome.tsx

@@ -13,6 +13,7 @@ import { useAiAssistantManagementModal, AiAssistantManagementModalPageMode } fro
 import { ShareScopeWarningModal } from './ShareScopeWarningModal';
 
 type Props = {
+  shouldEdit: boolean;
   name: string;
   description: string;
   instruction: string;
@@ -24,6 +25,7 @@ type Props = {
 
 export const AiAssistantManagementHome = (props: Props): JSX.Element => {
   const {
+    shouldEdit,
     name,
     description,
     instruction,
@@ -61,7 +63,7 @@ export const AiAssistantManagementHome = (props: Props): JSX.Element => {
     <>
       <ModalHeader tag="h4" toggle={closeAiAssistantManagementModal} className="pe-4">
         <span className="growi-custom-icons growi-ai-assistant-icon me-3 fs-4">ai_assistant</span>
-        <span className="fw-bold">新規アシスタントの追加</span> {/* TODO i18n */}
+        <span className="fw-bold">{t(shouldEdit ? 'アシスタントの更新' : '新規アシスタントの追加')}</span> {/* TODO i18n */}
       </ModalHeader>
 
       <div className="px-4">
@@ -137,7 +139,7 @@ export const AiAssistantManagementHome = (props: Props): JSX.Element => {
 
         <ModalFooter>
           <button type="button" className="btn btn-outline-secondary" onClick={closeAiAssistantManagementModal}>キャンセル</button>
-          <button type="button" className="btn btn-primary" onClick={createAiAssistantHandler}>アシスタントを作成する</button>
+          <button type="button" className="btn btn-primary" onClick={createAiAssistantHandler}>{t(shouldEdit ? 'アシスタントを更新する' : 'アシスタントを作成する')}</button>
         </ModalFooter>
       </div>
 

+ 61 - 24
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementModal.tsx

@@ -1,6 +1,6 @@
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useState, useEffect } from 'react';
 
-import type { IGrantedGroup } from '@growi/core';
+import { type IGrantedGroup, isPopulated } from '@growi/core';
 import { useTranslation } from 'react-i18next';
 import { Modal, TabContent, TabPane } from 'reactstrap';
 
@@ -11,7 +11,7 @@ import type { PopulatedGrantedGroup } from '~/interfaces/page-grant';
 import loggerFactory from '~/utils/logger';
 
 import type { SelectedPage } from '../../../../interfaces/selected-page';
-import { createAiAssistant } from '../../../services/ai-assistant';
+import { createAiAssistant, updateAiAssistant } from '../../../services/ai-assistant';
 import { useAiAssistantManagementModal, AiAssistantManagementModalPageMode, useSWRxAiAssistants } from '../../../stores/ai-assistant';
 
 import { AiAssistantManagementEditInstruction } from './AiAssistantManagementEditInstruction';
@@ -33,12 +33,32 @@ const convertToGrantedGroups = (selectedGroups: PopulatedGrantedGroup[]): IGrant
   }));
 };
 
+// IGrantedGroup[] -> PopulatedGrantedGroup[]
+const convertToPopulatedGrantedGroups = (selectedGroups: IGrantedGroup[]): PopulatedGrantedGroup[] => {
+  const populatedGrantedGroups = selectedGroups.filter(group => isPopulated(group.item)) as PopulatedGrantedGroup[];
+  return populatedGrantedGroups;
+};
+
+// string[] -> SelectedPage[]
+const convertToSelectedPages = (pagePathPatterns: string[]): SelectedPage[] => {
+  return pagePathPatterns.map((pagePathPattern) => {
+    const isIncludeSubPage = pagePathPattern.endsWith('/*');
+    const path = isIncludeSubPage ? pagePathPattern.slice(0, -2) : pagePathPattern;
+    return {
+      page: { path },
+      isIncludeSubPage,
+    };
+  });
+};
+
 const AiAssistantManagementModalSubstance = (): JSX.Element => {
   // Hooks
   const { t } = useTranslation();
   const { mutate: mutateAiAssistants } = useSWRxAiAssistants();
   const { data: aiAssistantManagementModalData, close: closeAiAssistantManagementModal } = useAiAssistantManagementModal();
 
+  const aiAssistant = aiAssistantManagementModalData?.aiAssistantData;
+  const shouldEdit = aiAssistant != null;
   const pageMode = aiAssistantManagementModalData?.pageMode ?? AiAssistantManagementModalPageMode.HOME;
 
   // States
@@ -51,6 +71,20 @@ const AiAssistantManagementModalSubstance = (): JSX.Element => {
   const [selectedPages, setSelectedPages] = useState<SelectedPage[]>([]);
   const [instruction, setInstruction] = useState<string>(t('modal_ai_assistant.default_instruction'));
 
+  // Effects
+  useEffect(() => {
+    if (shouldEdit) {
+      setName(aiAssistant.name);
+      setDescription(aiAssistant.description);
+      setInstruction(aiAssistant.additionalInstruction);
+      setSelectedPages(convertToSelectedPages(aiAssistant.pagePathPatterns));
+      setSelectedShareScope(aiAssistant.shareScope);
+      setSelectedAccessScope(aiAssistant.accessScope);
+      setSelectedUserGroupsForShareScope(convertToPopulatedGrantedGroups(aiAssistant.grantedGroupsForShareScope ?? []));
+      setSelectedUserGroupsForAccessScope(convertToPopulatedGrantedGroups(aiAssistant.grantedGroupsForAccessScope ?? []));
+    }
+  // eslint-disable-next-line max-len
+  }, [aiAssistant?.accessScope, aiAssistant?.additionalInstruction, aiAssistant?.description, aiAssistant?.grantedGroupsForAccessScope, aiAssistant?.grantedGroupsForShareScope, aiAssistant?.name, aiAssistant?.pagePathPatterns, aiAssistant?.shareScope, shouldEdit]);
 
   /*
   *  For AiAssistantManagementHome methods
@@ -69,40 +103,42 @@ const AiAssistantManagementModalSubstance = (): JSX.Element => {
         .map(selectedPage => (selectedPage.isIncludeSubPage ? `${selectedPage.page.path}/*` : selectedPage.page.path))
         .filter((path): path is string => path !== undefined && path !== null);
 
-      const grantedGroupsForShareScope = convertToGrantedGroups(selectedUserGroupsForShareScope);
-      const grantedGroupsForAccessScope = convertToGrantedGroups(selectedUserGroupsForAccessScope);
+      const grantedGroupsForShareScope = selectedShareScope === AiAssistantShareScope.GROUPS
+        ? convertToGrantedGroups(selectedUserGroupsForShareScope)
+        : undefined;
+
+      const grantedGroupsForAccessScope = selectedAccessScope === AiAssistantAccessScope.GROUPS
+        ? convertToGrantedGroups(selectedUserGroupsForAccessScope)
+        : undefined;
 
-      await createAiAssistant({
+      const reqBody = {
         name,
         description,
         additionalInstruction: instruction,
         pagePathPatterns,
         shareScope: selectedShareScope,
         accessScope: selectedAccessScope,
-        grantedGroupsForShareScope: selectedShareScope === AiAssistantShareScope.GROUPS ? grantedGroupsForShareScope : undefined,
-        grantedGroupsForAccessScope: selectedAccessScope === AiAssistantAccessScope.GROUPS ? grantedGroupsForAccessScope : undefined,
-      });
-
-      toastSuccess('アシスタントを作成しました');
+        grantedGroupsForShareScope,
+        grantedGroupsForAccessScope,
+      };
+
+      if (shouldEdit) {
+        await updateAiAssistant(aiAssistant._id, reqBody);
+      }
+      else {
+        await createAiAssistant(reqBody);
+      }
+
+      toastSuccess(shouldEdit ? 'アシスタントが更新されました' : 'アシスタントが作成されました');
       mutateAiAssistants();
       closeAiAssistantManagementModal();
     }
     catch (err) {
-      toastError('アシスタントの作成に失敗しました');
+      toastError(shouldEdit ? 'アシスタントの更新に失敗しました' : 'アシスタントの作成に失敗しました');
       logger.error(err);
     }
-  }, [
-    mutateAiAssistants,
-    closeAiAssistantManagementModal,
-    description,
-    instruction,
-    name,
-    selectedAccessScope,
-    selectedPages,
-    selectedShareScope,
-    selectedUserGroupsForAccessScope,
-    selectedUserGroupsForShareScope,
-  ]);
+  // eslint-disable-next-line max-len
+  }, [selectedPages, selectedShareScope, selectedUserGroupsForShareScope, selectedAccessScope, selectedUserGroupsForAccessScope, name, description, instruction, shouldEdit, mutateAiAssistants, closeAiAssistantManagementModal, aiAssistant?._id]);
 
 
   /*
@@ -172,6 +208,7 @@ const AiAssistantManagementModalSubstance = (): JSX.Element => {
       <TabContent activeTab={pageMode}>
         <TabPane tabId={AiAssistantManagementModalPageMode.HOME}>
           <AiAssistantManagementHome
+            shouldEdit={shouldEdit}
             name={name}
             description={description}
             shareScope={selectedShareScope}