Browse Source

Merge pull request #9763 from weseek/fix/163120-assistant-remains-default-when-made-unpublished-3

fix: Assistant remains default when made unpublished
Shun Miyazawa 1 year ago
parent
commit
0ebef8a0af

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

@@ -9,6 +9,7 @@ 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, IPageForItem } from '~/interfaces/page';
 import type { PopulatedGrantedGroup } from '~/interfaces/page-grant';
@@ -131,7 +132,7 @@ const AiAssistantManagementModalSubstance = (): JSX.Element => {
         ? convertToGrantedGroups(selectedUserGroupsForAccessScope)
         : undefined;
 
-      const reqBody = {
+      const reqBody: UpsertAiAssistantData = {
         name,
         description,
         additionalInstruction: instruction,
@@ -140,7 +141,6 @@ const AiAssistantManagementModalSubstance = (): JSX.Element => {
         accessScope: selectedAccessScope,
         grantedGroupsForShareScope,
         grantedGroupsForAccessScope,
-        isDefault: shouldEdit ? aiAssistant.isDefault : false,
       };
 
       if (shouldEdit) {
@@ -159,7 +159,7 @@ const AiAssistantManagementModalSubstance = (): JSX.Element => {
       logger.error(err);
     }
   // eslint-disable-next-line max-len
-  }, [selectedPages, selectedShareScope, selectedUserGroupsForShareScope, selectedAccessScope, selectedUserGroupsForAccessScope, name, description, instruction, shouldEdit, aiAssistant?.isDefault, aiAssistant?._id, mutateAiAssistants, closeAiAssistantManagementModal]);
+  }, [t, selectedPages, selectedShareScope, selectedUserGroupsForShareScope, selectedAccessScope, selectedUserGroupsForAccessScope, name, description, instruction, shouldEdit, aiAssistant?._id, mutateAiAssistants, closeAiAssistantManagementModal]);
 
 
   /*

+ 1 - 1
apps/app/src/features/openai/interfaces/ai-assistant.ts

@@ -42,7 +42,7 @@ export interface AiAssistant {
 
 export type AiAssistantHasId = AiAssistant & HasObjectId
 
-export type UpsertAiAssistantData = Omit<AiAssistant, 'owner' | 'vectorStore'>
+export type UpsertAiAssistantData = Omit<AiAssistant, 'owner' | 'vectorStore' | 'isDefault'>
 
 export type AccessibleAiAssistants = {
   myAiAssistants: AiAssistant[],

+ 25 - 9
apps/app/src/features/openai/server/models/ai-assistant.ts

@@ -1,5 +1,4 @@
 import { type IGrantedGroup, GroupType } from '@growi/core';
-import createError from 'http-errors';
 import { type Model, type Document, Schema } from 'mongoose';
 
 import { getOrCreateModel } from '~/server/util/mongoose-utils';
@@ -112,16 +111,33 @@ const schema = new Schema<AiAssistantDocument>(
 
 
 schema.statics.setDefault = async function(id: string, isDefault: boolean): Promise<AiAssistantDocument> {
-  const aiAssistant = await this.findOne({ _id: id, shareScope: AiAssistantAccessScope.PUBLIC_ONLY });
-  if (aiAssistant == null) {
-    throw createError(404, 'AiAssistant document does not exist');
+  if (isDefault) {
+    await this.bulkWrite([
+      {
+        updateOne: {
+          filter: {
+            _id: id,
+            shareScope:  AiAssistantShareScope.PUBLIC_ONLY,
+          },
+          update: { $set: { isDefault: true } },
+        },
+      },
+      {
+        updateMany: {
+          filter: {
+            _id: { $ne: id },
+            isDefault: true,
+          },
+          update: { $set: { isDefault: false } },
+        },
+      },
+    ]);
+  }
+  else {
+    await this.findByIdAndUpdate(id, { isDefault: false });
   }
 
-  await this.updateMany({ isDefault: true }, { isDefault: false });
-
-  aiAssistant.isDefault = isDefault;
-  const updatedAiAssistant = await aiAssistant.save();
-
+  const updatedAiAssistant = await this.findById(id);
   return updatedAiAssistant;
 };
 

+ 1 - 1
apps/app/src/features/openai/server/routes/ai-assistant.ts

@@ -43,7 +43,7 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => {
           return res.apiv3Err(new ErrorV3('The number of learnable pages exceeds the limit'), 400);
         }
 
-        const aiAssistant = await openaiService.createAiAssistant(aiAssistantData);
+        const aiAssistant = await openaiService.createAiAssistant(req.body, req.user);
 
         return res.apiv3({ aiAssistant });
       }

+ 2 - 4
apps/app/src/features/openai/server/routes/update-ai-assistant.ts

@@ -50,14 +50,12 @@ export const updateAiAssistantsFactory: UpdateAiAssistantsFactory = (crowi) => {
       }
 
       try {
-        const aiAssistantData = { ...req.body, owner: user._id };
-
-        const isLearnablePageLimitExceeded = await openaiService.isLearnablePageLimitExceeded(user, aiAssistantData.pagePathPatterns);
+        const isLearnablePageLimitExceeded = await openaiService.isLearnablePageLimitExceeded(user, req.body.pagePathPatterns);
         if (isLearnablePageLimitExceeded) {
           return res.apiv3Err(new ErrorV3('The number of learnable pages exceeds the limit'), 400);
         }
 
-        const updatedAiAssistant = await openaiService.updateAiAssistant(id, aiAssistantData);
+        const updatedAiAssistant = await openaiService.updateAiAssistant(id, req.body, user);
 
         return res.apiv3({ updatedAiAssistant });
       }

+ 16 - 11
apps/app/src/features/openai/server/services/openai.ts

@@ -29,6 +29,7 @@ import { createBatchStream } from '~/server/util/batch-stream';
 import loggerFactory from '~/utils/logger';
 
 import { OpenaiServiceTypes } from '../../interfaces/ai';
+import type { UpsertAiAssistantData } from '../../interfaces/ai-assistant';
 import {
   type AccessibleAiAssistants, type AiAssistant, AiAssistantAccessScope, AiAssistantShareScope,
 } from '../../interfaces/ai-assistant';
@@ -79,8 +80,8 @@ export interface IOpenaiService {
   deleteVectorStoreFilesByPageIds(pageIds: Types.ObjectId[]): Promise<void>;
   deleteObsoleteVectorStoreFile(limit: number, apiCallInterval: number): Promise<void>; // for CronJob
   isAiAssistantUsable(aiAssistantId: string, user: IUserHasId): Promise<boolean>;
-  createAiAssistant(data: Omit<AiAssistant, 'vectorStore'>): Promise<AiAssistantDocument>;
-  updateAiAssistant(aiAssistantId: string, data: Omit<AiAssistant, 'vectorStore'>): Promise<AiAssistantDocument>;
+  createAiAssistant(data: UpsertAiAssistantData, user: IUserHasId): Promise<AiAssistantDocument>;
+  updateAiAssistant(aiAssistantId: string, data: UpsertAiAssistantData, user: IUserHasId): Promise<AiAssistantDocument>;
   getAccessibleAiAssistants(user: IUserHasId): Promise<AccessibleAiAssistants>
   isLearnablePageLimitExceeded(user: IUserHasId, pagePathPatterns: string[]): Promise<boolean>;
 }
@@ -758,9 +759,9 @@ class OpenaiService implements IOpenaiService {
     return false;
   }
 
-  async createAiAssistant(data: Omit<AiAssistant, 'vectorStore'>): Promise<AiAssistantDocument> {
+  async createAiAssistant(data: UpsertAiAssistantData, user: IUserHasId): Promise<AiAssistantDocument> {
     await this.validateGrantedUserGroupsForAiAssistant(
-      data.owner,
+      user,
       data.shareScope,
       data.accessScope,
       data.grantedGroupsForShareScope,
@@ -768,7 +769,7 @@ class OpenaiService implements IOpenaiService {
     );
 
     const conditions = await this.createConditionForCreateVectorStoreFile(
-      data.owner,
+      user,
       data.accessScope,
       data.grantedGroupsForAccessScope,
       data.pagePathPatterns,
@@ -776,7 +777,7 @@ class OpenaiService implements IOpenaiService {
 
     const vectorStoreRelation = await this.createVectorStore(data.name);
     const aiAssistant = await AiAssistantModel.create({
-      ...data, vectorStore: vectorStoreRelation,
+      ...data, owner: user, vectorStore: vectorStoreRelation,
     });
 
     // VectorStore creation process does not await
@@ -785,14 +786,14 @@ class OpenaiService implements IOpenaiService {
     return aiAssistant;
   }
 
-  async updateAiAssistant(aiAssistantId: string, data: Omit<AiAssistant, 'vectorStore'>): Promise<AiAssistantDocument> {
-    const aiAssistant = await AiAssistantModel.findOne({ owner: data.owner, _id: aiAssistantId });
+  async updateAiAssistant(aiAssistantId: string, data: UpsertAiAssistantData, user: IUserHasId): Promise<AiAssistantDocument> {
+    const aiAssistant = await AiAssistantModel.findOne({ owner: user, _id: aiAssistantId });
     if (aiAssistant == null) {
       throw createError(404, 'AiAssistant document does not exist');
     }
 
     await this.validateGrantedUserGroupsForAiAssistant(
-      data.owner,
+      user,
       data.shareScope,
       data.accessScope,
       data.grantedGroupsForShareScope,
@@ -810,7 +811,7 @@ class OpenaiService implements IOpenaiService {
     let newVectorStoreRelation: VectorStoreDocument | undefined;
     if (shouldRebuildVectorStore) {
       const conditions = await this.createConditionForCreateVectorStoreFile(
-        data.owner,
+        user,
         data.accessScope,
         data.grantedGroupsForAccessScope,
         data.pagePathPatterns,
@@ -834,7 +835,11 @@ class OpenaiService implements IOpenaiService {
     };
 
     aiAssistant.set({ ...newData });
-    const updatedAiAssistant = await aiAssistant.save();
+    let updatedAiAssistant: AiAssistantDocument = await aiAssistant.save();
+
+    if (data.shareScope !== AiAssistantShareScope.PUBLIC_ONLY && aiAssistant.isDefault) {
+      updatedAiAssistant = await AiAssistantModel.setDefault(aiAssistant._id, false);
+    }
 
     return updatedAiAssistant;
   }