|
|
@@ -2,7 +2,10 @@ import type { KeyboardEvent } from 'react';
|
|
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
|
|
|
|
import { useForm, Controller } from 'react-hook-form';
|
|
|
-import { Modal, ModalBody, ModalHeader } from 'reactstrap';
|
|
|
+import { useTranslation } from 'react-i18next';
|
|
|
+import {
|
|
|
+ Modal, ModalBody, ModalFooter, ModalHeader,
|
|
|
+} from 'reactstrap';
|
|
|
|
|
|
import { apiv3Post } from '~/client/util/apiv3-client';
|
|
|
import { useRagSearchModal } from '~/stores/rag-search';
|
|
|
@@ -11,7 +14,7 @@ import loggerFactory from '~/utils/logger';
|
|
|
import { MessageCard } from './MessageCard';
|
|
|
import { ResizableTextarea } from './ResizableTextArea';
|
|
|
|
|
|
-import styles from './RagSearchModal.module.scss';
|
|
|
+import styles from './AiChatModal.module.scss';
|
|
|
|
|
|
const moduleClass = styles['rag-search-modal'];
|
|
|
|
|
|
@@ -28,7 +31,9 @@ type FormData = {
|
|
|
input: string;
|
|
|
};
|
|
|
|
|
|
-const RagSearchModal = (): JSX.Element => {
|
|
|
+const AiChatModalSubstance = (): JSX.Element => {
|
|
|
+
|
|
|
+ const { t } = useTranslation();
|
|
|
|
|
|
const form = useForm<FormData>({
|
|
|
defaultValues: {
|
|
|
@@ -40,21 +45,9 @@ const RagSearchModal = (): JSX.Element => {
|
|
|
const [messageLogs, setMessageLogs] = useState<Message[]>([]);
|
|
|
const [lastMessage, setLastMessage] = useState<Message>();
|
|
|
|
|
|
- const { data: ragSearchModalData, close: closeRagSearchModal } = useRagSearchModal();
|
|
|
-
|
|
|
- const isOpened = ragSearchModalData?.isOpened ?? false;
|
|
|
-
|
|
|
- useEffect(() => {
|
|
|
- // clear states when the modal is closed
|
|
|
- if (!isOpened) {
|
|
|
- setMessageLogs([]);
|
|
|
- setThreadId(undefined);
|
|
|
- }
|
|
|
- }, [isOpened]);
|
|
|
-
|
|
|
useEffect(() => {
|
|
|
// do nothing when the modal is closed or threadId is already set
|
|
|
- if (!isOpened || threadId != null) {
|
|
|
+ if (threadId != null) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -72,7 +65,7 @@ const RagSearchModal = (): JSX.Element => {
|
|
|
};
|
|
|
|
|
|
createThread();
|
|
|
- }, [isOpened, threadId]);
|
|
|
+ }, [threadId]);
|
|
|
|
|
|
const submit = useCallback(async(data: FormData) => {
|
|
|
const { length: logLength } = messageLogs;
|
|
|
@@ -164,54 +157,81 @@ const RagSearchModal = (): JSX.Element => {
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
- <Modal size="lg" isOpen={isOpened} toggle={closeRagSearchModal} className={moduleClass}>
|
|
|
- <ModalHeader tag="h4" toggle={closeRagSearchModal} className="pe-4">
|
|
|
- <span className="material-symbols-outlined text-primary">psychology</span>
|
|
|
- GROWI Assistant
|
|
|
- </ModalHeader>
|
|
|
- <ModalBody className="px-lg-5 py-4">
|
|
|
- <div className="vstack gap-4">
|
|
|
+ <>
|
|
|
+ <ModalBody className="pb-0 pt-3 pt-lg-4 px-3 px-lg-4">
|
|
|
+ <div className="vstack gap-4 pb-4">
|
|
|
{ messageLogs.map(message => (
|
|
|
- <MessageCard key={message.id} right={message.isUserMessage}>{message.content}</MessageCard>
|
|
|
+ <MessageCard key={message.id} role={message.isUserMessage ? 'user' : 'assistant'}>{message.content}</MessageCard>
|
|
|
)) }
|
|
|
{ lastMessage != null && (
|
|
|
- <MessageCard>{lastMessage.content}</MessageCard>
|
|
|
+ <MessageCard role="assistant">{lastMessage.content}</MessageCard>
|
|
|
)}
|
|
|
- </div>
|
|
|
-
|
|
|
- <div>
|
|
|
- <form onSubmit={form.handleSubmit(submit)} className="hstack gap-2 align-items-end mt-4">
|
|
|
- <Controller
|
|
|
- name="input"
|
|
|
- control={form.control}
|
|
|
- render={({ field }) => (
|
|
|
- <ResizableTextarea
|
|
|
- {...field}
|
|
|
- required
|
|
|
- className="form-control textarea-ask"
|
|
|
- style={{ resize: 'none' }}
|
|
|
- rows={1}
|
|
|
- placeholder="ききたいことを入力してください"
|
|
|
- onKeyDown={keyDownHandler}
|
|
|
- />
|
|
|
- )}
|
|
|
- />
|
|
|
- <button
|
|
|
- type="submit"
|
|
|
- className="btn btn-submit no-border"
|
|
|
- disabled={form.formState.isSubmitting}
|
|
|
- >
|
|
|
- <span className="material-symbols-outlined">send</span>
|
|
|
- </button>
|
|
|
- </form>
|
|
|
-
|
|
|
- {form.formState.errors.input != null && (
|
|
|
- <span className="text-danger small">{form.formState.errors.input?.message}</span>
|
|
|
+ { messageLogs.length > 0 && (
|
|
|
+ <div className="d-flex justify-content-center">
|
|
|
+ <span className="bg-body-tertiary text-body-secondary rounded-pill px-3 py-1" style={{ fontSize: 'smaller' }}>
|
|
|
+ {t('modal_aichat.caution_against_hallucination')}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
)}
|
|
|
</div>
|
|
|
</ModalBody>
|
|
|
- </Modal>
|
|
|
+
|
|
|
+ <ModalFooter className="pt-0 pb-3 pb-lg-4 px-3 px-lg-4">
|
|
|
+ <form onSubmit={form.handleSubmit(submit)} className="flex-fill hstack gap-2 align-items-end m-0">
|
|
|
+ <Controller
|
|
|
+ name="input"
|
|
|
+ control={form.control}
|
|
|
+ render={({ field }) => (
|
|
|
+ <ResizableTextarea
|
|
|
+ {...field}
|
|
|
+ required
|
|
|
+ className="form-control textarea-ask"
|
|
|
+ style={{ resize: 'none' }}
|
|
|
+ rows={1}
|
|
|
+ placeholder={t('modal_aichat.placeholder')}
|
|
|
+ onKeyDown={keyDownHandler}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ type="submit"
|
|
|
+ className="btn btn-submit no-border"
|
|
|
+ disabled={form.formState.isSubmitting}
|
|
|
+ >
|
|
|
+ <span className="material-symbols-outlined">send</span>
|
|
|
+ </button>
|
|
|
+ </form>
|
|
|
+
|
|
|
+ {form.formState.errors.input != null && (
|
|
|
+ <span className="text-danger small">{form.formState.errors.input?.message}</span>
|
|
|
+ )}
|
|
|
+ </ModalFooter>
|
|
|
+ </>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
-export default RagSearchModal;
|
|
|
+
|
|
|
+export const AiChatModal = (): JSX.Element => {
|
|
|
+
|
|
|
+ const { t } = useTranslation();
|
|
|
+
|
|
|
+ const { data: ragSearchModalData, close: closeRagSearchModal } = useRagSearchModal();
|
|
|
+
|
|
|
+ const isOpened = ragSearchModalData?.isOpened ?? false;
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Modal size="lg" isOpen={isOpened} toggle={closeRagSearchModal} className={moduleClass} scrollable>
|
|
|
+
|
|
|
+ <ModalHeader tag="h4" toggle={closeRagSearchModal} className="pe-4">
|
|
|
+ <span className="material-symbols-outlined growi-ai-chat-icon me-3">chat</span>
|
|
|
+ <span className="fw-bold">{t('modal_aichat.title')}</span>
|
|
|
+ <span className="fs-5 text-body-secondary ms-3">{t('modal_aichat.title_beta_label')}</span>
|
|
|
+ </ModalHeader>
|
|
|
+
|
|
|
+ { isOpened && (
|
|
|
+ <AiChatModalSubstance />
|
|
|
+ ) }
|
|
|
+
|
|
|
+ </Modal>
|
|
|
+ );
|
|
|
+};
|