|
@@ -3,9 +3,9 @@ import {
|
|
|
type FC, memo, useRef, useEffect, useState, useCallback, useMemo,
|
|
type FC, memo, useRef, useEffect, useState, useCallback, useMemo,
|
|
|
} from 'react';
|
|
} from 'react';
|
|
|
|
|
|
|
|
-import { useForm, Controller } from 'react-hook-form';
|
|
|
|
|
|
|
+import { Controller } from 'react-hook-form';
|
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
-import { Collapse, UncontrolledTooltip } from 'reactstrap';
|
|
|
|
|
|
|
+import { Collapse } from 'reactstrap';
|
|
|
import SimpleBar from 'simplebar-react';
|
|
import SimpleBar from 'simplebar-react';
|
|
|
|
|
|
|
|
import { toastError } from '~/client/util/toastr';
|
|
import { toastError } from '~/client/util/toastr';
|
|
@@ -19,11 +19,14 @@ import type { IThreadRelationHasId } from '../../../../interfaces/thread-relatio
|
|
|
import {
|
|
import {
|
|
|
useEditorAssistant,
|
|
useEditorAssistant,
|
|
|
useAiAssistantSidebarCloseEffect as useAiAssistantSidebarCloseEffectForEditorAssistant,
|
|
useAiAssistantSidebarCloseEffect as useAiAssistantSidebarCloseEffectForEditorAssistant,
|
|
|
|
|
+ isEditorAssistantFormData,
|
|
|
|
|
+ type FormData as FormDataForEditorAssistant,
|
|
|
} from '../../../services/editor-assistant';
|
|
} from '../../../services/editor-assistant';
|
|
|
import {
|
|
import {
|
|
|
useKnowledgeAssistant,
|
|
useKnowledgeAssistant,
|
|
|
useFetchAndSetMessageDataEffect,
|
|
useFetchAndSetMessageDataEffect,
|
|
|
useAiAssistantSidebarCloseEffect as useAiAssistantSidebarCloseEffectForKnowledgeAssistant,
|
|
useAiAssistantSidebarCloseEffect as useAiAssistantSidebarCloseEffectForKnowledgeAssistant,
|
|
|
|
|
+ type FormData as FormDataForKnowledgeAssistant,
|
|
|
} from '../../../services/knowledge-assistant';
|
|
} from '../../../services/knowledge-assistant';
|
|
|
import { useAiAssistantSidebar } from '../../../stores/ai-assistant';
|
|
import { useAiAssistantSidebar } from '../../../stores/ai-assistant';
|
|
|
|
|
|
|
@@ -36,10 +39,7 @@ const logger = loggerFactory('growi:openai:client:components:AiAssistantSidebar'
|
|
|
|
|
|
|
|
const moduleClass = styles['grw-ai-assistant-sidebar'] ?? '';
|
|
const moduleClass = styles['grw-ai-assistant-sidebar'] ?? '';
|
|
|
|
|
|
|
|
-export type FormData = {
|
|
|
|
|
- input: string;
|
|
|
|
|
- summaryMode?: boolean;
|
|
|
|
|
-};
|
|
|
|
|
|
|
+type FormData = FormDataForEditorAssistant | FormDataForKnowledgeAssistant;
|
|
|
|
|
|
|
|
type AiAssistantSidebarSubstanceProps = {
|
|
type AiAssistantSidebarSubstanceProps = {
|
|
|
isEditorAssistant: boolean;
|
|
isEditorAssistant: boolean;
|
|
@@ -71,10 +71,13 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
|
|
|
createThread: createThreadForKnowledgeAssistant,
|
|
createThread: createThreadForKnowledgeAssistant,
|
|
|
postMessage: postMessageForKnowledgeAssistant,
|
|
postMessage: postMessageForKnowledgeAssistant,
|
|
|
processMessage: processMessageForKnowledgeAssistant,
|
|
processMessage: processMessageForKnowledgeAssistant,
|
|
|
|
|
+ form: formForKnowledgeAssistant,
|
|
|
|
|
+ resetForm: resetFormForKnowledgeAssistant,
|
|
|
|
|
|
|
|
// Views
|
|
// Views
|
|
|
initialView: initialViewForKnowledgeAssistant,
|
|
initialView: initialViewForKnowledgeAssistant,
|
|
|
generateMessageCard: generateMessageCardForKnowledgeAssistant,
|
|
generateMessageCard: generateMessageCardForKnowledgeAssistant,
|
|
|
|
|
+ generateSummaryModeSwitch: generateSummaryModeSwitchForKnowledgeAssistant,
|
|
|
headerIcon: headerIconForKnowledgeAssistant,
|
|
headerIcon: headerIconForKnowledgeAssistant,
|
|
|
headerText: headerTextForKnowledgeAssistant,
|
|
headerText: headerTextForKnowledgeAssistant,
|
|
|
placeHolder: placeHolderForKnowledgeAssistant,
|
|
placeHolder: placeHolderForKnowledgeAssistant,
|
|
@@ -84,6 +87,9 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
|
|
|
createThread: createThreadForEditorAssistant,
|
|
createThread: createThreadForEditorAssistant,
|
|
|
postMessage: postMessageForEditorAssistant,
|
|
postMessage: postMessageForEditorAssistant,
|
|
|
processMessage: processMessageForEditorAssistant,
|
|
processMessage: processMessageForEditorAssistant,
|
|
|
|
|
+ form: formForEditorAssistant,
|
|
|
|
|
+ resetForm: resetFormEditorAssistant,
|
|
|
|
|
+ isTextSelected,
|
|
|
|
|
|
|
|
// Views
|
|
// Views
|
|
|
generateInitialView: generateInitialViewForEditorAssistant,
|
|
generateInitialView: generateInitialViewForEditorAssistant,
|
|
@@ -93,17 +99,20 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
|
|
|
placeHolder: placeHolderForEditorAssistant,
|
|
placeHolder: placeHolderForEditorAssistant,
|
|
|
} = useEditorAssistant();
|
|
} = useEditorAssistant();
|
|
|
|
|
|
|
|
- const form = useForm<FormData>({
|
|
|
|
|
- defaultValues: {
|
|
|
|
|
- input: '',
|
|
|
|
|
- summaryMode: true,
|
|
|
|
|
- },
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const form = isEditorAssistant ? formForEditorAssistant : formForKnowledgeAssistant;
|
|
|
|
|
|
|
|
// Effects
|
|
// Effects
|
|
|
useFetchAndSetMessageDataEffect(setMessageLogs, threadData?.threadId);
|
|
useFetchAndSetMessageDataEffect(setMessageLogs, threadData?.threadId);
|
|
|
|
|
|
|
|
// Functions
|
|
// Functions
|
|
|
|
|
+ const resetForm = useCallback(() => {
|
|
|
|
|
+ if (isEditorAssistant) {
|
|
|
|
|
+ resetFormEditorAssistant();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ resetFormForKnowledgeAssistant();
|
|
|
|
|
+ }, [isEditorAssistant, resetFormEditorAssistant, resetFormForKnowledgeAssistant]);
|
|
|
|
|
+
|
|
|
const createThread = useCallback(async(initialUserMessage: string) => {
|
|
const createThread = useCallback(async(initialUserMessage: string) => {
|
|
|
if (isEditorAssistant) {
|
|
if (isEditorAssistant) {
|
|
|
const thread = await createThreadForEditorAssistant();
|
|
const thread = await createThreadForEditorAssistant();
|
|
@@ -117,19 +126,22 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
|
|
|
return thread;
|
|
return thread;
|
|
|
}, [aiAssistantData, createThreadForEditorAssistant, createThreadForKnowledgeAssistant, isEditorAssistant]);
|
|
}, [aiAssistantData, createThreadForEditorAssistant, createThreadForKnowledgeAssistant, isEditorAssistant]);
|
|
|
|
|
|
|
|
- const postMessage = useCallback(async(currentThreadId: string, input: string, summaryMode?: boolean) => {
|
|
|
|
|
|
|
+ const postMessage = useCallback(async(currentThreadId: string, formData: FormData) => {
|
|
|
if (isEditorAssistant) {
|
|
if (isEditorAssistant) {
|
|
|
- const response = await postMessageForEditorAssistant(currentThreadId, input);
|
|
|
|
|
- return response;
|
|
|
|
|
|
|
+ if (isEditorAssistantFormData(formData)) {
|
|
|
|
|
+ const response = await postMessageForEditorAssistant(currentThreadId, formData);
|
|
|
|
|
+ return response;
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
if (aiAssistantData?._id != null) {
|
|
if (aiAssistantData?._id != null) {
|
|
|
- const response = postMessageForKnowledgeAssistant(aiAssistantData._id, currentThreadId, input, summaryMode);
|
|
|
|
|
|
|
+ const response = await postMessageForKnowledgeAssistant(aiAssistantData._id, currentThreadId, formData);
|
|
|
return response;
|
|
return response;
|
|
|
}
|
|
}
|
|
|
}, [aiAssistantData?._id, isEditorAssistant, postMessageForEditorAssistant, postMessageForKnowledgeAssistant]);
|
|
}, [aiAssistantData?._id, isEditorAssistant, postMessageForEditorAssistant, postMessageForKnowledgeAssistant]);
|
|
|
|
|
|
|
|
const isGenerating = generatingAnswerMessage != null;
|
|
const isGenerating = generatingAnswerMessage != null;
|
|
|
- const submit = useCallback(async(data: FormData) => {
|
|
|
|
|
|
|
+ const submitSubstance = useCallback(async(data: FormData) => {
|
|
|
// do nothing when the assistant is generating an answer
|
|
// do nothing when the assistant is generating an answer
|
|
|
if (isGenerating) {
|
|
if (isGenerating) {
|
|
|
return;
|
|
return;
|
|
@@ -146,8 +158,8 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
|
|
|
const newUserMessage = { id: logLength.toString(), content: data.input, isUserMessage: true };
|
|
const newUserMessage = { id: logLength.toString(), content: data.input, isUserMessage: true };
|
|
|
setMessageLogs(msgs => [...msgs, newUserMessage]);
|
|
setMessageLogs(msgs => [...msgs, newUserMessage]);
|
|
|
|
|
|
|
|
- // reset form
|
|
|
|
|
- form.reset({ input: '', summaryMode: data.summaryMode });
|
|
|
|
|
|
|
+ resetForm();
|
|
|
|
|
+
|
|
|
setErrorMessage(undefined);
|
|
setErrorMessage(undefined);
|
|
|
|
|
|
|
|
// add an empty assistant message
|
|
// add an empty assistant message
|
|
@@ -178,7 +190,7 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const response = await postMessage(currentThreadId_, data.input, data.summaryMode);
|
|
|
|
|
|
|
+ const response = await postMessage(currentThreadId_, data);
|
|
|
if (response == null) {
|
|
if (response == null) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
@@ -275,7 +287,23 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// eslint-disable-next-line max-len
|
|
// eslint-disable-next-line max-len
|
|
|
- }, [isGenerating, messageLogs, form, currentThreadId, createThread, t, postMessage, processMessageForKnowledgeAssistant, processMessageForEditorAssistant, growiCloudUri]);
|
|
|
|
|
|
|
+ }, [isGenerating, messageLogs, resetForm, currentThreadId, createThread, t, postMessage, form, processMessageForKnowledgeAssistant, processMessageForEditorAssistant, growiCloudUri]);
|
|
|
|
|
+
|
|
|
|
|
+ const submit = useCallback((data: FormData) => {
|
|
|
|
|
+ if (isEditorAssistant) {
|
|
|
|
|
+ const markdownType = (() => {
|
|
|
|
|
+ if (isEditorAssistantFormData(data) && data.markdownType != null) {
|
|
|
|
|
+ return data.markdownType;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return isTextSelected ? 'selected' : 'none';
|
|
|
|
|
+ })();
|
|
|
|
|
+
|
|
|
|
|
+ return submitSubstance({ ...data, markdownType });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return submitSubstance(data);
|
|
|
|
|
+ }, [isEditorAssistant, isTextSelected, submitSubstance]);
|
|
|
|
|
|
|
|
const keyDownHandler = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
const keyDownHandler = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
|
if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
|
|
if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
|
|
@@ -313,6 +341,14 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
|
|
|
return initialViewForKnowledgeAssistant;
|
|
return initialViewForKnowledgeAssistant;
|
|
|
}, [generateInitialViewForEditorAssistant, initialViewForKnowledgeAssistant, isEditorAssistant, submit]);
|
|
}, [generateInitialViewForEditorAssistant, initialViewForKnowledgeAssistant, isEditorAssistant, submit]);
|
|
|
|
|
|
|
|
|
|
+ const additionalInputControl = useMemo(() => {
|
|
|
|
|
+ if (isEditorAssistant) {
|
|
|
|
|
+ return <></>;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return generateSummaryModeSwitchForKnowledgeAssistant(isGenerating);
|
|
|
|
|
+ }, [generateSummaryModeSwitchForKnowledgeAssistant, isEditorAssistant, isGenerating]);
|
|
|
|
|
+
|
|
|
const messageCard = useCallback(
|
|
const messageCard = useCallback(
|
|
|
(role: MessageCardRole, children: string, messageId?: string, messageLogs?: MessageLog[], generatingAnswerMessage?: MessageLog) => {
|
|
(role: MessageCardRole, children: string, messageId?: string, messageLogs?: MessageLog[], generatingAnswerMessage?: MessageLog) => {
|
|
|
if (isEditorAssistant) {
|
|
if (isEditorAssistant) {
|
|
@@ -396,33 +432,7 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
|
|
|
<span className="material-symbols-outlined">send</span>
|
|
<span className="material-symbols-outlined">send</span>
|
|
|
</button>
|
|
</button>
|
|
|
</div>
|
|
</div>
|
|
|
- <div className="form-check form-switch">
|
|
|
|
|
- <input
|
|
|
|
|
- id="swSummaryMode"
|
|
|
|
|
- type="checkbox"
|
|
|
|
|
- role="switch"
|
|
|
|
|
- className="form-check-input"
|
|
|
|
|
- {...form.register('summaryMode')}
|
|
|
|
|
- disabled={form.formState.isSubmitting || isGenerating}
|
|
|
|
|
- />
|
|
|
|
|
- <label className="form-check-label" htmlFor="swSummaryMode">
|
|
|
|
|
- {t('sidebar_ai_assistant.summary_mode_label')}
|
|
|
|
|
- </label>
|
|
|
|
|
-
|
|
|
|
|
- {/* Help */}
|
|
|
|
|
- <a
|
|
|
|
|
- id="tooltipForHelpOfSummaryMode"
|
|
|
|
|
- role="button"
|
|
|
|
|
- className="ms-1"
|
|
|
|
|
- >
|
|
|
|
|
- <span className="material-symbols-outlined fs-6" style={{ lineHeight: 'unset' }}>help</span>
|
|
|
|
|
- </a>
|
|
|
|
|
- <UncontrolledTooltip
|
|
|
|
|
- target="tooltipForHelpOfSummaryMode"
|
|
|
|
|
- >
|
|
|
|
|
- {t('sidebar_ai_assistant.summary_mode_help')}
|
|
|
|
|
- </UncontrolledTooltip>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ { additionalInputControl }
|
|
|
</form>
|
|
</form>
|
|
|
|
|
|
|
|
{form.formState.errors.input != null && (
|
|
{form.formState.errors.input != null && (
|