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

Merge pull request #9490 from weseek/feat/159143-assistant-create-modal

feat: KnowledgeAssistantManagementModal
mergify[bot] 1 год назад
Родитель
Сommit
f6a83ad51e

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

@@ -152,6 +152,7 @@
   "Page Tree": "Page Tree",
   "Bookmarks": "Bookmarks",
   "In-App Notification": "Notifications",
+  "Knowledge Assistant": "ナレッジアシスタント",
   "original_path": "Original path",
   "new_path": "New path",
   "duplicated_path": "Duplicated path",

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

@@ -152,6 +152,7 @@
   "Page Tree": "Arbre",
   "Bookmarks": "Favoris",
   "In-App Notification": "Notifications",
+  "Knowledge Assistant": "Assistant de Connaissance",
   "original_path": "Chemin originel",
   "new_path": "Nouveau chemin",
   "duplicated_path": "Chemin dupliqué",

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

@@ -153,6 +153,7 @@
   "Page Tree": "ページツリー",
   "Bookmarks": "ブックマーク",
   "In-App Notification": "通知",
+  "Knowledge Assistant": "ナレッジアシスタント",
   "original_path": "元のパス",
   "new_path": "新しいパス",
   "duplicated_path": "重複したパス",

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

@@ -158,6 +158,7 @@
   "Page Tree": "页面树",
   "Bookmarks": "书签",
   "In-App Notification": "通知",
+  "Knowledge Assistant": "知识助手",
   "original_path": "Original path",
   "new_path": "New path",
   "duplicated_path": "Duplicated path",

+ 3 - 0
apps/app/src/client/components/Sidebar/SidebarContents.tsx

@@ -1,5 +1,6 @@
 import React, { memo, useMemo } from 'react';
 
+import { KnowledgeAssistant } from '~/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistant';
 import { SidebarContentsType } from '~/interfaces/ui';
 import { useCollapsedContentsOpened, useCurrentSidebarContents, useSidebarMode } from '~/stores/ui';
 
@@ -32,6 +33,8 @@ export const SidebarContents = memo(() => {
         return Bookmarks;
       case SidebarContentsType.NOTIFICATION:
         return InAppNotification;
+      case SidebarContentsType.KNOWNLEDGE_ASSISTANT:
+        return KnowledgeAssistant;
       default:
         return PageTree;
     }

+ 6 - 2
apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItem.tsx

@@ -22,6 +22,7 @@ export type PrimaryItemProps = {
   label: string,
   iconName: string,
   sidebarMode: SidebarMode,
+  isCustomIcon?: boolean,
   badgeContents?: number,
   onHover?: (contents: SidebarContentsType) => void,
   onClick?: () => void,
@@ -29,7 +30,7 @@ export type PrimaryItemProps = {
 
 export const PrimaryItem = (props: PrimaryItemProps): JSX.Element => {
   const {
-    contents, label, iconName, sidebarMode, badgeContents,
+    contents, label, iconName, sidebarMode, badgeContents, isCustomIcon,
     onClick, onHover,
   } = props;
 
@@ -80,7 +81,10 @@ export const PrimaryItem = (props: PrimaryItemProps): JSX.Element => {
           { badgeContents != null && (
             <span className="position-absolute badge rounded-pill bg-primary">{badgeContents}</span>
           )}
-          <span className="material-symbols-outlined">{iconName}</span>
+          { isCustomIcon
+            ? (<span className="growi-custom-icons fs-4 align-middle">{iconName}</span>)
+            : (<span className="material-symbols-outlined">{iconName}</span>)
+          }
         </div>
       </button>
       {

+ 12 - 0
apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx

@@ -3,6 +3,7 @@ import { memo } from 'react';
 import dynamic from 'next/dynamic';
 
 import { SidebarContentsType } from '~/interfaces/ui';
+import { useIsAiEnabled } from '~/stores-universal/context';
 import { useSidebarMode } from '~/stores/ui';
 
 import { PrimaryItem } from './PrimaryItem';
@@ -22,6 +23,7 @@ export const PrimaryItems = memo((props: Props) => {
   const { onItemHover } = props;
 
   const { data: sidebarMode } = useSidebarMode();
+  const { data: isAiEnabled } = useIsAiEnabled();
 
   if (sidebarMode == null) {
     return <></>;
@@ -35,6 +37,16 @@ export const PrimaryItems = memo((props: Props) => {
       <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.BOOKMARKS} label="Bookmarks" iconName="bookmarks" onHover={onItemHover} />
       <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.TAG} label="Tags" iconName="local_offer" onHover={onItemHover} />
       <PrimaryItemForNotification sidebarMode={sidebarMode} onHover={onItemHover} />
+      {isAiEnabled && (
+        <PrimaryItem
+          sidebarMode={sidebarMode}
+          contents={SidebarContentsType.KNOWNLEDGE_ASSISTANT}
+          label="Knowledge Assistant"
+          iconName="knowledge_assistant"
+          isCustomIcon
+          onHover={onItemHover}
+        />
+      )}
     </div>
   );
 });

+ 5 - 0
apps/app/src/components/Layout/BasicLayout.tsx

@@ -35,6 +35,10 @@ const DeleteBookmarkFolderModal = dynamic(
 );
 const SearchModal = dynamic(() => import('../../features/search/client/components/SearchModal'), { ssr: false });
 const AiChatModal = dynamic(() => import('~/features/openai/chat/components/AiChatModal').then(mod => mod.AiChatModal), { ssr: false });
+const KnowledgeAssistantManegementModal = dynamic(
+  () => import('~/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal')
+    .then(mod => mod.KnowledgeAssistantManegementModal), { ssr: false },
+);
 
 type Props = {
   children?: ReactNode
@@ -68,6 +72,7 @@ export const BasicLayout = ({ children, className }: Props): JSX.Element => {
       <PutbackPageModal />
       <SearchModal />
       <AiChatModal />
+      <KnowledgeAssistantManegementModal />
 
       <PagePresentationModal />
       <HotkeysManager />

+ 8 - 0
apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.module.scss

@@ -0,0 +1,8 @@
+@use '@growi/core-styles/scss/variables/growi-official-colors';
+
+// == Colors
+.grw-knowledge-assistant-manegement :global {
+  .growi-knowledge-assistant-icon {
+    color: growi-official-colors.$growi-ai-purple;
+  }
+}

+ 120 - 0
apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx

@@ -0,0 +1,120 @@
+import React from 'react';
+
+import { useTranslation } from 'react-i18next';
+import {
+  Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input,
+} from 'reactstrap';
+
+import { useKnowledgeAssistantModal } from '../../stores/knowledge-assistant';
+
+import styles from './KnowledgeAssistantManegementModal.module.scss';
+
+const moduleClass = styles['grw-knowledge-assistant-manegement'] ?? '';
+
+
+const KnowledgeAssistantManegementModalSubstance = (): JSX.Element => {
+  return (
+    <div className="px-4">
+      <ModalBody>
+        <Form>
+          <FormGroup className="mb-4">
+            <Label className="mb-2 ">アシスタント名</Label>
+            <Input
+              type="text"
+              placeholder="アシスタント名を入力"
+              className="border rounded"
+            />
+          </FormGroup>
+
+          <FormGroup className="mb-4">
+            <div className="d-flex align-items-center mb-2">
+              <Label className="mb-0">アシスタントの種類</Label>
+              <span className="ms-1 fs-5 material-symbols-outlined text-secondary">help</span>
+            </div>
+            <div className="d-flex gap-4">
+              <FormGroup check>
+                <Input type="checkbox" defaultChecked />
+                <Label check>ナレッジアシスタント</Label>
+              </FormGroup>
+              <FormGroup check>
+                <Input type="checkbox" />
+                <Label check>エディタアシスタント</Label>
+              </FormGroup>
+              <FormGroup check>
+                <Input type="checkbox" />
+                <Label check>ラーニングアシスタント</Label>
+              </FormGroup>
+            </div>
+          </FormGroup>
+
+          <FormGroup className="mb-4">
+            <div className="d-flex align-items-center mb-2">
+              <Label className="mb-0">共有範囲</Label>
+              <span className="ms-1 fs-5 material-symbols-outlined text-secondary">help</span>
+            </div>
+            <Input type="select" className="border rounded w-50">
+              <option>自分のみ</option>
+            </Input>
+          </FormGroup>
+
+          <FormGroup className="mb-4">
+            <div className="d-flex align-items-center mb-2">
+              <Label className="mb-0">参照するページ</Label>
+              <span className="ms-1 fs-5 material-symbols-outlined text-secondary">help</span>
+            </div>
+            <button
+              type="button"
+              className="btn btn-outline-primary d-flex align-items-center gap-1"
+              onClick={() => {}}
+            >
+              <span>+</span>
+              追加する
+            </button>
+          </FormGroup>
+
+          <FormGroup>
+            <div className="d-flex align-items-center mb-2">
+              <Label className="mb-0">アシスタントの役割</Label>
+              <span className="ms-1 fs-5 material-symbols-outlined text-secondary">help</span>
+            </div>
+            <Input
+              type="textarea"
+              placeholder="アシスタントの役割をいれてください"
+              className="border rounded"
+              rows={4}
+            />
+          </FormGroup>
+        </Form>
+      </ModalBody>
+
+      <ModalFooter className="border-0 pt-0 mb-3">
+        <button type="button" className="btn btn-outline-secondary" onClick={() => {}}>キャンセル</button>
+        <button type="button" className="btn btn-primary" onClick={() => {}}>作成</button>
+      </ModalFooter>
+    </div>
+  );
+};
+
+
+export const KnowledgeAssistantManegementModal = (): JSX.Element => {
+  const { t } = useTranslation();
+
+  const { data: knowledgeAssistantModalData, close: closeKnowledgeAssistantModal } = useKnowledgeAssistantModal();
+
+  const isOpened = knowledgeAssistantModalData?.isOpened ?? false;
+
+  return (
+    <Modal size="lg" isOpen={isOpened} toggle={closeKnowledgeAssistantModal} className={moduleClass} scrollable>
+
+      <ModalHeader tag="h4" toggle={closeKnowledgeAssistantModal} className="pe-4">
+        <span className="growi-custom-icons growi-knowledge-assistant-icon me-3 fs-4">knowledge_assistant</span>
+        <span className="fw-bold">新規アシスタントの追加</span> {/* TODO i18n */}
+      </ModalHeader>
+
+      { isOpened && (
+        <KnowledgeAssistantManegementModalSubstance />
+      ) }
+
+    </Modal>
+  );
+};

+ 25 - 0
apps/app/src/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistant.tsx

@@ -0,0 +1,25 @@
+import React, { Suspense } from 'react';
+
+import dynamic from 'next/dynamic';
+import { useTranslation } from 'react-i18next';
+
+import ItemsTreeContentSkeleton from '~/client/components/ItemsTree/ItemsTreeContentSkeleton';
+
+const KnowledgeAssistantContent = dynamic(() => import('./KnowledgeAssistantSubstance').then(mod => mod.KnowledgeAssistantContent), { ssr: false });
+
+export const KnowledgeAssistant = (): JSX.Element => {
+  const { t } = useTranslation();
+
+  return (
+    <div className="px-3">
+      <div className="grw-sidebar-content-header py-4 d-flex">
+        <h3 className="fs-6 fw-bold mb-0">
+          {t('Knowledge Assistant')}
+        </h3>
+      </div>
+      <Suspense fallback={<ItemsTreeContentSkeleton />}>
+        <KnowledgeAssistantContent />
+      </Suspense>
+    </div>
+  );
+};

+ 16 - 0
apps/app/src/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistantSubstance.tsx

@@ -0,0 +1,16 @@
+import React from 'react';
+
+import { useKnowledgeAssistantModal } from '../../../stores/knowledge-assistant';
+
+export const KnowledgeAssistantContent = (): JSX.Element => {
+  const { open } = useKnowledgeAssistantModal();
+
+  return (
+    <div>
+      <button type="button" className="btn btn-primary" onClick={open}>
+        アシスタントを追加する
+        {/* TODO i18n */}
+      </button>
+    </div>
+  );
+};

+ 28 - 0
apps/app/src/features/openai/client/stores/knowledge-assistant.tsx

@@ -0,0 +1,28 @@
+import { useCallback } from 'react';
+
+import { useSWRStatic } from '@growi/core/dist/swr';
+import type { SWRResponse } from 'swr';
+
+
+type KnowledgeAssistantMoldalStatus = {
+  isOpened: boolean,
+}
+
+type KnowledgeAssistantUtils = {
+  open(): void
+  close(): void
+}
+export const useKnowledgeAssistantModal = (
+    status?: KnowledgeAssistantMoldalStatus,
+): SWRResponse<KnowledgeAssistantMoldalStatus, Error> & KnowledgeAssistantUtils => {
+  const initialStatus = { isOpened: false };
+  const swrResponse = useSWRStatic<KnowledgeAssistantMoldalStatus, Error>('KnowledgeAssistantModal', status, { fallbackData: initialStatus });
+
+  return {
+    ...swrResponse,
+    open: useCallback(() => {
+      swrResponse.mutate({ isOpened: true });
+    }, [swrResponse]),
+    close: useCallback(() => swrResponse.mutate({ isOpened: false }), [swrResponse]),
+  };
+};

+ 1 - 0
apps/app/src/interfaces/ui.ts

@@ -15,6 +15,7 @@ export const SidebarContentsType = {
   TAG: 'tag',
   BOOKMARKS: 'bookmarks',
   NOTIFICATION: 'notification',
+  KNOWNLEDGE_ASSISTANT: 'knowledgeAssistant',
 } as const;
 export const AllSidebarContentsType = Object.values(SidebarContentsType);
 export type SidebarContentsType = typeof SidebarContentsType[keyof typeof SidebarContentsType];