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

+ 5 - 1
apps/app/src/client/components/PageControls/PageControls.tsx

@@ -36,6 +36,7 @@ import {
 
 
 import { BookmarkButtons } from './BookmarkButtons';
 import { BookmarkButtons } from './BookmarkButtons';
 import LikeButtons from './LikeButtons';
 import LikeButtons from './LikeButtons';
+import RagPromptButton from './RagPromptButton';
 import SearchButton from './SearchButton';
 import SearchButton from './SearchButton';
 import SeenUserInfo from './SeenUserInfo';
 import SeenUserInfo from './SeenUserInfo';
 import SubscribeButton from './SubscribeButton';
 import SubscribeButton from './SubscribeButton';
@@ -282,7 +283,10 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
   return (
   return (
     <div className={`${styles['grw-page-controls']} hstack gap-2`} ref={pageControlsRef}>
     <div className={`${styles['grw-page-controls']} hstack gap-2`} ref={pageControlsRef}>
       { isViewMode && isDeviceLargerThanMd && !isSearchPage && !isSearchPage && (
       { isViewMode && isDeviceLargerThanMd && !isSearchPage && !isSearchPage && (
-        <SearchButton />
+        <>
+          <SearchButton />
+          <RagPromptButton />
+        </>
       )}
       )}
 
 
       {revisionId != null && !isViewMode && _isIPageInfoForOperation && (
       {revisionId != null && !isViewMode && _isIPageInfoForOperation && (

+ 12 - 0
apps/app/src/client/components/PageControls/RagPromptButton.module.scss

@@ -0,0 +1,12 @@
+@use '@growi/core-styles/scss/bootstrap/init' as bs;
+@use '@growi/ui/scss/atoms/btn-muted';
+@use './button-styles';
+
+.btn-rag-prompt :global {
+  @extend %btn-basis;
+}
+
+// == Colors
+.btn-rag-prompt {
+  @include btn-muted.colorize(bs.$success);
+}

+ 28 - 0
apps/app/src/client/components/PageControls/RagPromptButton.tsx

@@ -0,0 +1,28 @@
+import React, { useCallback } from 'react';
+
+import { useRagPromptModal } from '~/stores/rag-prompt';
+
+import styles from './RagPromptButton.module.scss';
+
+const RagPromptButton = (): JSX.Element => {
+
+  const { open: openRagPromptModal } = useRagPromptModal();
+
+  const ragPromptButtonClickHandler = useCallback(() => {
+    openRagPromptModal();
+  }, [openRagPromptModal]);
+
+
+  return (
+    <button
+      type="button"
+      className={`btn btn-search ${styles['btn-rag-prompt']}`}
+      onClick={ragPromptButtonClickHandler}
+      data-testid="open-search-modal-button"
+    >
+      <span className="material-symbols-outlined">chat</span>
+    </button>
+  );
+};
+
+export default RagPromptButton;

+ 59 - 0
apps/app/src/client/components/RagPrompt/RagPromptModal.tsx

@@ -0,0 +1,59 @@
+
+import React, { useState } from 'react';
+
+import { Modal, ModalBody, ModalHeader } from 'reactstrap';
+
+import { apiv3Post } from '~/client/util/apiv3-client';
+import { useRagPromptModal } from '~/stores/rag-prompt';
+
+const RagPromptModal = (): JSX.Element => {
+  const [userMessage, setUserMessage] = useState('');
+  const [assistantMessage, setAssistantMessage] = useState('');
+
+  const { data: ragPromptModalData, close: closeRagPromptModal } = useRagPromptModal();
+
+  const onClickSubmitUserMessageHandler = async() => {
+    try {
+      const res = await apiv3Post('/openai/chat', { userMessage });
+      setAssistantMessage(res.data.assistantMessage);
+    }
+    catch (err) {
+      //
+    }
+  };
+
+  return (
+    <Modal size="lg" isOpen={ragPromptModalData?.isOpened ?? false} toggle={closeRagPromptModal} data-testid="search-modal">
+      <ModalBody>
+        <ModalHeader tag="h4" className="mb-3 p-0">
+          Chat
+        </ModalHeader>
+
+        <p>
+          { assistantMessage }
+        </p>
+
+        <div className="input-group mb-2">
+          <input
+            type="text"
+            className="form-control"
+            placeholder="検索結果を生成するためのメッセージを入力してください"
+            aria-label="Recipient's username"
+            aria-describedby="button-addon2"
+            onChange={e => setUserMessage(e.target.value)}
+          />
+          <button
+            type="button"
+            id="button-addon2"
+            className="btn btn-outline-secondary"
+            onClick={onClickSubmitUserMessageHandler}
+          >
+            <span className="material-symbols-outlined">arrow_upward</span>
+          </button>
+        </div>
+      </ModalBody>
+    </Modal>
+  );
+};
+
+export default RagPromptModal;

+ 2 - 1
apps/app/src/components/Layout/BasicLayout.tsx

@@ -34,7 +34,7 @@ const DeleteBookmarkFolderModal = dynamic(
   () => import('~/client/components/DeleteBookmarkFolderModal').then(mod => mod.DeleteBookmarkFolderModal), { ssr: false },
   () => import('~/client/components/DeleteBookmarkFolderModal').then(mod => mod.DeleteBookmarkFolderModal), { ssr: false },
 );
 );
 const SearchModal = dynamic(() => import('../../features/search/client/components/SearchModal'), { ssr: false });
 const SearchModal = dynamic(() => import('../../features/search/client/components/SearchModal'), { ssr: false });
-
+const RagPromptModal = dynamic(() => import('~/client/components/RagPrompt/RagPromptModal'), { ssr: false });
 
 
 type Props = {
 type Props = {
   children?: ReactNode
   children?: ReactNode
@@ -67,6 +67,7 @@ export const BasicLayout = ({ children, className }: Props): JSX.Element => {
       <DeleteBookmarkFolderModal />
       <DeleteBookmarkFolderModal />
       <PutbackPageModal />
       <PutbackPageModal />
       <SearchModal />
       <SearchModal />
+      <RagPromptModal />
 
 
       <PagePresentationModal />
       <PagePresentationModal />
       <HotkeysManager />
       <HotkeysManager />

+ 26 - 0
apps/app/src/stores/rag-prompt.ts

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