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

Merge pull request #10180 from weseek/feat/169233-implementation-of-page-selection-method-modal

feat: Implementation of page selection method modal
Shun Miyazawa 8 месяцев назад
Родитель
Сommit
30a65df02e
13 измененных файлов с 158 добавлено и 19 удалено
  1. 3 0
      apps/app/public/static/locales/en_US/translation.json
  2. 3 0
      apps/app/public/static/locales/fr_FR/translation.json
  3. 3 0
      apps/app/public/static/locales/ja_JP/translation.json
  4. 3 0
      apps/app/public/static/locales/zh_CN/translation.json
  5. 1 1
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementEditInstruction.tsx
  6. 1 1
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementEditPages.tsx
  7. 1 1
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementEditShare.tsx
  8. 28 7
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementHeader.tsx
  9. 6 5
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementHome.tsx
  10. 10 2
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementModal.tsx
  11. 20 0
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementPageSelectionMethod.module.scss
  12. 64 0
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementPageSelectionMethod.tsx
  13. 15 2
      apps/app/src/features/openai/client/stores/ai-assistant.tsx

+ 3 - 0
apps/app/public/static/locales/en_US/translation.json

@@ -572,6 +572,9 @@
       "create_failed": "Failed to create assistant",
       "update_failed": "Failed to update assistant"
     },
+    "select_source_pages": "Select pages for the assistant to reference",
+    "search_by_keyword": "Search by keyword",
+    "select_from_page_tree": "Select from page tree",
     "edit_page_description": "Edit pages that the assistant can reference.<br> The assistant can reference up to {{limitLearnablePageCountPerAssistant}} pages including child pages.",
     "default_instruction": "You are the knowledge assistant for this Wiki.\n\n## Multilingual Support:\nRespond in the same language the user uses in their input.\n",
     "add_page_button": "Add page",

+ 3 - 0
apps/app/public/static/locales/fr_FR/translation.json

@@ -566,6 +566,9 @@
       "create_failed": "Échec de la création de l'assistant",
       "update_failed": "Échec de la mise à jour de l'assistant"
     },
+    "select_source_pages": "Sélectionnez les pages que l'assistant doit référencer",
+    "search_by_keyword": "Rechercher par mot-clé",
+    "select_from_page_tree": "Sélectionner depuis l'arborescence des pages",
     "edit_page_description": "Modifier les pages que l'assistant peut référencer.<br> L'assistant peut référencer jusqu'à {{limitLearnablePageCountPerAssistant}} pages, y compris les pages enfants.",
     "default_instruction": "Vous êtes l'assistant de connaissances pour ce Wiki.\n\n## Support multilingue :\nRépondez dans la même langue que celle utilisée par l'utilisateur dans sa requête.\n",
     "add_page_button": "Ajouter une page",

+ 3 - 0
apps/app/public/static/locales/ja_JP/translation.json

@@ -604,6 +604,9 @@
       "create_failed": "アシスタントの作成に失敗しました",
       "update_failed": "アシスタントの更新に失敗しました"
     },
+    "select_source_pages": "アシスタントが参照するページを選択します",
+    "search_by_keyword": "キーワードで検索",
+    "select_from_page_tree": "ページツリーから選択",
     "edit_page_description": " アシスタントが参照するページを編集します。<br> 参照できるページは配下ページも含めて {{limitLearnablePageCountPerAssistant}} ページまでです。",
     "default_instruction": "あなたはこのWikiの知識アシスタントです。\n\n## 多言語サポート:\nユーザーが入力で使用した言語と同じ言語で応答してください。\n",
     "add_page_button": "ページを追加する",

+ 3 - 0
apps/app/public/static/locales/zh_CN/translation.json

@@ -561,6 +561,9 @@
       "create_failed": "创建助手失败",
       "update_failed": "更新助手失败"
     },
+    "select_source_pages": "选择助手要参考的页面",
+    "search_by_keyword": "按关键词搜索",
+    "select_from_page_tree": "从页面树选择",
     "edit_page_description": "编辑助手可以参考的页面。<br> 助手可以参考最多 {{limitLearnablePageCountPerAssistant}} 个页面,包括子页面。",
     "default_instruction": "您是这个Wiki的知识助手。\n\n## 多语言支持:\n请使用用户输入中使用的相同语言进行回复。\n",
     "add_page_button": "添加页面",

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

@@ -18,7 +18,7 @@ export const AiAssistantManagementEditInstruction = (props: Props): JSX.Element
 
   return (
     <>
-      <AiAssistantManagementHeader />
+      <AiAssistantManagementHeader labelTranslationKey="modal_ai_assistant.page_mode_title.instruction" />
 
       <ModalBody className="px-4">
         <p className="text-secondary py-1">

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

@@ -33,7 +33,7 @@ export const AiAssistantManagementEditPages = (props: Props): JSX.Element => {
 
   return (
     <>
-      <AiAssistantManagementHeader />
+      <AiAssistantManagementHeader labelTranslationKey="modal_ai_assistant.page_mode_title.pages" />
 
       <ModalBody className="px-4">
         <p

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

@@ -98,7 +98,7 @@ export const AiAssistantManagementEditShare = (props: Props): JSX.Element => {
 
   return (
     <>
-      <AiAssistantManagementHeader />
+      <AiAssistantManagementHeader labelTranslationKey="modal_ai_assistant.page_mode_title.share" />
 
       <ModalBody className="px-4">
         <div className="form-check form-switch mb-4">

+ 28 - 7
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementHeader.tsx

@@ -1,17 +1,31 @@
-import type { JSX } from 'react';
+import { type JSX } from 'react';
 
 import { useTranslation } from 'react-i18next';
 import { ModalHeader } from 'reactstrap';
 
 import { useAiAssistantManagementModal, AiAssistantManagementModalPageMode } from '../../../stores/ai-assistant';
 
+type Props = {
+  labelTranslationKey: string;
+  hideBackButton?: boolean;
+  backButtonColor?: 'primary' | 'secondary';
+  backToPageMode?: AiAssistantManagementModalPageMode;
+}
+
+export const AiAssistantManagementHeader = (props: Props): JSX.Element => {
+  const {
+    labelTranslationKey,
+    hideBackButton,
+    backButtonColor = 'primary',
+    backToPageMode = AiAssistantManagementModalPageMode.HOME,
+  } = props;
 
-export const AiAssistantManagementHeader = (): JSX.Element => {
   const { t } = useTranslation();
-  const { data, close, changePageMode } = useAiAssistantManagementModal();
+  const { close, changePageMode } = useAiAssistantManagementModal();
 
   return (
     <ModalHeader
+      tag="h4"
       close={(
         <button type="button" className="btn p-0" onClick={close}>
           <span className="material-symbols-outlined">close</span>
@@ -19,10 +33,17 @@ export const AiAssistantManagementHeader = (): JSX.Element => {
       )}
     >
       <div className="d-flex align-items-center">
-        <button type="button" className="btn p-0 me-3" onClick={() => changePageMode(AiAssistantManagementModalPageMode.HOME)}>
-          <span className="material-symbols-outlined text-primary">chevron_left</span>
-        </button>
-        <span>{t(`modal_ai_assistant.page_mode_title.${data?.pageMode}`)}</span>
+        { hideBackButton
+          ? (
+            <span className="growi-custom-icons growi-ai-assistant-icon me-3 fs-4">growi_ai</span>
+          )
+          : (
+            <button type="button" className="btn p-0 me-3" onClick={() => changePageMode(backToPageMode)}>
+              <span className={`material-symbols-outlined text-${backButtonColor}`}>chevron_left</span>
+            </button>
+          )
+        }
+        <span className="fw-bold">{t(labelTranslationKey)}</span>
       </div>
     </ModalHeader>
   );

+ 6 - 5
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementHome.tsx

@@ -4,7 +4,7 @@ import React, {
 
 import { useTranslation } from 'react-i18next';
 import {
-  ModalHeader, ModalBody, ModalFooter, Input,
+  ModalBody, ModalFooter, Input,
 } from 'reactstrap';
 
 import { AiAssistantShareScope, AiAssistantAccessScope } from '~/features/openai/interfaces/ai-assistant';
@@ -15,6 +15,7 @@ import type { SelectedPage } from '../../../../interfaces/selected-page';
 import { determineShareScope } from '../../../../utils/determine-share-scope';
 import { useAiAssistantManagementModal, AiAssistantManagementModalPageMode } from '../../../stores/ai-assistant';
 
+import { AiAssistantManagementHeader } from './AiAssistantManagementHeader';
 import { ShareScopeWarningModal } from './ShareScopeWarningModal';
 
 type Props = {
@@ -116,10 +117,10 @@ export const AiAssistantManagementHome = (props: Props): JSX.Element => {
 
   return (
     <>
-      <ModalHeader tag="h4" toggle={closeAiAssistantManagementModal} className="pe-4">
-        <span className="growi-custom-icons growi-ai-assistant-icon me-3 fs-4">growi_ai</span>
-        <span className="fw-bold">{t(shouldEdit ? 'modal_ai_assistant.header.update_assistant' : 'modal_ai_assistant.header.add_new_assistant')}</span>
-      </ModalHeader>
+      <AiAssistantManagementHeader
+        hideBackButton
+        labelTranslationKey={shouldEdit ? 'modal_ai_assistant.header.update_assistant' : 'modal_ai_assistant.header.add_new_assistant'}
+      />
 
       <div className="px-4">
         <ModalBody>

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

@@ -30,6 +30,7 @@ import { AiAssistantManagementEditInstruction } from './AiAssistantManagementEdi
 import { AiAssistantManagementEditPages } from './AiAssistantManagementEditPages';
 import { AiAssistantManagementEditShare } from './AiAssistantManagementEditShare';
 import { AiAssistantManagementHome } from './AiAssistantManagementHome';
+import { AiAssistantManagementPageSelectionMethod } from './AiAssistantManagementPageSelectionMethod';
 
 import styles from './AiAssistantManagementModal.module.scss';
 
@@ -167,8 +168,11 @@ const AiAssistantManagementModalSubstance = (): JSX.Element => {
       toastError(shouldEdit ? t('modal_ai_assistant.toaster.update_failed') : t('modal_ai_assistant.toaster.create_failed'));
       logger.error(err);
     }
-  // eslint-disable-next-line max-len
-  }, [t, selectedPages, selectedShareScope, selectedUserGroupsForShareScope, selectedAccessScope, selectedUserGroupsForAccessScope, name, description, instruction, shouldEdit, aiAssistant?._id, mutateAiAssistants, closeAiAssistantManagementModal]);
+  }, [
+    selectedPages, selectedShareScope, selectedUserGroupsForShareScope, selectedAccessScope,
+    selectedUserGroupsForAccessScope, name, description, instruction, shouldEdit, t, mutateAiAssistants,
+    closeAiAssistantManagementModal, aiAssistant?._id, aiAssistantSidebarData?.aiAssistantData?._id, refreshAiAssistantData,
+  ]);
 
 
   /*
@@ -274,6 +278,10 @@ const AiAssistantManagementModalSubstance = (): JSX.Element => {
           />
         </TabPane>
 
+        <TabPane tabId={AiAssistantManagementModalPageMode.PAGE_SELECTION_METHOD}>
+          <AiAssistantManagementPageSelectionMethod />
+        </TabPane>
+
         <TabPane tabId={AiAssistantManagementModalPageMode.INSTRUCTION}>
           <AiAssistantManagementEditInstruction
             instruction={instruction}

+ 20 - 0
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementPageSelectionMethod.module.scss

@@ -0,0 +1,20 @@
+@use '@growi/core-styles/scss/bootstrap/init' as bs;
+
+.grw-ai-assistant-management-page-selection-method :global {
+  .page-selection-method-btn {
+      width: 280px;
+  }
+}
+
+
+// == Colors
+.grw-ai-assistant-management-page-selection-method :global {
+  .page-selection-method-btn {
+      border: 2px solid bs.$gray-300;
+      &:hover {
+        color: var(--bs-primary);
+        background-color: rgba(var(--bs-primary-rgb), 0.1);
+        border-color: var(--bs-primary)
+      }
+    }
+}

+ 64 - 0
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementPageSelectionMethod.tsx

@@ -0,0 +1,64 @@
+import React from 'react';
+
+import { useTranslation } from 'react-i18next';
+import {
+  ModalBody,
+} from 'reactstrap';
+
+import { useAiAssistantManagementModal } from '../../../stores/ai-assistant';
+
+import { AiAssistantManagementHeader } from './AiAssistantManagementHeader';
+
+import styles from './AiAssistantManagementPageSelectionMethod.module.scss';
+
+const moduleClass = styles['grw-ai-assistant-management-page-selection-method'] ?? '';
+
+const SelectionButton = (props: { icon: string, label: string, onClick: () => void }): JSX.Element => {
+  const { icon, label, onClick } = props;
+
+  return (
+    <button
+      type="button"
+      className="btn p-4 text-center page-selection-method-btn"
+      onClick={onClick}
+    >
+      <span
+        className="material-symbols-outlined d-block mb-3 fs-1"
+      >
+        {icon}
+      </span>
+      <div>{label}</div>
+    </button>
+  );
+};
+
+
+export const AiAssistantManagementPageSelectionMethod = (): JSX.Element => {
+  const { t } = useTranslation();
+  const { data: aiAssistantManagementModalData } = useAiAssistantManagementModal();
+  const isNewAiAssistant = aiAssistantManagementModalData?.aiAssistantData == null;
+
+  return (
+    <div className={moduleClass}>
+      <AiAssistantManagementHeader
+        hideBackButton={isNewAiAssistant}
+        labelTranslationKey={isNewAiAssistant ? 'modal_ai_assistant.header.add_new_assistant' : 'modal_ai_assistant.header.update_assistant'}
+      />
+
+      <ModalBody className="px-4">
+        <h4 className="text-center mb-4 fw-bold">
+          {t('modal_ai_assistant.select_source_pages')}
+        </h4>
+        <div className="row justify-content-center">
+          <div className="col-auto">
+            <SelectionButton icon="manage_search" label={t('modal_ai_assistant.search_by_keyword')} onClick={() => {}} />
+          </div>
+
+          <div className="col-auto">
+            <SelectionButton icon="account_tree" label={t('modal_ai_assistant.select_from_page_tree')} onClick={() => {}} />
+          </div>
+        </div>
+      </ModalBody>
+    </div>
+  );
+};

+ 15 - 2
apps/app/src/features/openai/client/stores/ai-assistant.tsx

@@ -9,14 +9,19 @@ import { apiv3Get } from '~/client/util/apiv3-client';
 import { type AccessibleAiAssistantsHasId, type AiAssistantHasId } from '../../interfaces/ai-assistant';
 import type { IThreadRelationHasId } from '../../interfaces/thread-relation'; // IThreadHasId を削除
 
+
+/*
+*  useAiAssistantManagementModal
+*/
 export const AiAssistantManagementModalPageMode = {
   HOME: 'home',
   SHARE: 'share',
   PAGES: 'pages',
   INSTRUCTION: 'instruction',
+  PAGE_SELECTION_METHOD: 'page-selection-method',
 } as const;
 
-type AiAssistantManagementModalPageMode = typeof AiAssistantManagementModalPageMode[keyof typeof AiAssistantManagementModalPageMode];
+export type AiAssistantManagementModalPageMode = typeof AiAssistantManagementModalPageMode[keyof typeof AiAssistantManagementModalPageMode];
 
 type AiAssistantManagementModalStatus = {
   isOpened: boolean,
@@ -38,7 +43,15 @@ export const useAiAssistantManagementModal = (
 
   return {
     ...swrResponse,
-    open: useCallback((aiAssistantData) => { swrResponse.mutate({ isOpened: true, aiAssistantData }) }, [swrResponse]),
+    open: useCallback((aiAssistantData) => {
+      swrResponse.mutate({
+        isOpened: true,
+        aiAssistantData,
+        pageMode: aiAssistantData != null
+          ? AiAssistantManagementModalPageMode.HOME
+          : AiAssistantManagementModalPageMode.PAGE_SELECTION_METHOD,
+      });
+    }, [swrResponse]),
     close: useCallback(() => swrResponse.mutate({ isOpened: false, aiAssistantData: undefined }), [swrResponse]),
     changePageMode: useCallback((pageMode: AiAssistantManagementModalPageMode) => {
       swrResponse.mutate({ isOpened: swrResponse.data?.isOpened ?? false, pageMode, aiAssistantData: swrResponse.data?.aiAssistantData });