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

Display spinner while reflecting strings being generated in the editor

Shun Miyazawa 10 месяцев назад
Родитель
Сommit
402f386f56

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

@@ -515,6 +515,7 @@
     "accept": "Accept",
     "accept": "Accept",
     "use_assistant": "Use Assistant",
     "use_assistant": "Use Assistant",
     "remove_assistant": "Deselect the selected assistant",
     "remove_assistant": "Deselect the selected assistant",
+    "text_generation_by_editor_assistant_label": "Editor Assistant is generating text",
     "preset_menu": {
     "preset_menu": {
       "summarize": {
       "summarize": {
         "title": "Summarize this article",
         "title": "Summarize this article",

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

@@ -509,6 +509,7 @@
     "accept": "Accepter",
     "accept": "Accepter",
     "use_assistant": "Utiliser l'assistant",
     "use_assistant": "Utiliser l'assistant",
     "remove_assistant": "Désélectionner l'assistant sélectionné",
     "remove_assistant": "Désélectionner l'assistant sélectionné",
+    "text_generation_by_editor_assistant_label": "L'assistant de rédaction génère du texte",
     "preset_menu": {
     "preset_menu": {
       "summarize": {
       "summarize": {
         "title": "Résumer cet article'",
         "title": "Résumer cet article'",

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

@@ -547,6 +547,7 @@
     "accept": "採用",
     "accept": "採用",
     "use_assistant": "アシスタントを使用する",
     "use_assistant": "アシスタントを使用する",
     "remove_assistant": "選択されているアシスタントの解除",
     "remove_assistant": "選択されているアシスタントの解除",
+    "text_generation_by_editor_assistant_label": "エディターアシスタントが文章を生成中",
     "preset_menu": {
     "preset_menu": {
       "summarize": {
       "summarize": {
         "title": "この記事の要約をつくる",
         "title": "この記事の要約をつくる",

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

@@ -504,6 +504,7 @@
     "accept": "接受",
     "accept": "接受",
     "use_assistant": "使用助手",
     "use_assistant": "使用助手",
     "remove_assistant": "取消选定的助手",
     "remove_assistant": "取消选定的助手",
+    "text_generation_by_editor_assistant_label": "编辑助理正在生成文本",
     "preset_menu": {
     "preset_menu": {
       "summarize": {
       "summarize": {
         "title": "为此文章创建摘要",
         "title": "为此文章创建摘要",

+ 7 - 1
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/AiAssistantSidebar.tsx

@@ -92,6 +92,7 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
     form: formForEditorAssistant,
     form: formForEditorAssistant,
     resetForm: resetFormEditorAssistant,
     resetForm: resetFormEditorAssistant,
     isTextSelected,
     isTextSelected,
+    isGeneratingEditorText,
 
 
     // Views
     // Views
     generateInitialView: generateInitialViewForEditorAssistant,
     generateInitialView: generateInitialViewForEditorAssistant,
@@ -394,7 +395,12 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
                   </>
                   </>
                 )) }
                 )) }
                 { generatingAnswerMessage != null && (
                 { generatingAnswerMessage != null && (
-                  <MessageCard role="assistant">{generatingAnswerMessage.content}</MessageCard>
+                  <MessageCard
+                    isGeneratingEditorText={isGeneratingEditorText}
+                    role="assistant"
+                  >
+                    {generatingAnswerMessage.content}
+                  </MessageCard>
                 )}
                 )}
                 { messageLogs.length > 0 && (
                 { messageLogs.length > 0 && (
                   <div className="d-flex justify-content-center">
                   <div className="d-flex justify-content-center">

+ 13 - 2
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/MessageCard.tsx

@@ -1,5 +1,6 @@
 import { useCallback, useState, type JSX } from 'react';
 import { useCallback, useState, type JSX } from 'react';
 
 
+import { LoadingSpinner } from '@growi/ui/dist/components';
 import type { LinkProps } from 'next/link';
 import type { LinkProps } from 'next/link';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import ReactMarkdown from 'react-markdown';
 import ReactMarkdown from 'react-markdown';
@@ -33,10 +34,11 @@ const NextLinkWrapper = (props: LinkProps & {children: string, href: string}): J
 };
 };
 
 
 const AssistantMessageCard = ({
 const AssistantMessageCard = ({
-  children, showActionButtons, onAccept, onDiscard,
+  children, isGeneratingEditorText, showActionButtons, onAccept, onDiscard,
 }: {
 }: {
   children: string,
   children: string,
   showActionButtons?: boolean
   showActionButtons?: boolean
+  isGeneratingEditorText?: boolean
   onAccept?: () => void,
   onAccept?: () => void,
   onDiscard?: () => void,
   onDiscard?: () => void,
 }): JSX.Element => {
 }): JSX.Element => {
@@ -66,6 +68,13 @@ const AssistantMessageCard = ({
               <>
               <>
                 <ReactMarkdown components={{ a: NextLinkWrapper }}>{children}</ReactMarkdown>
                 <ReactMarkdown components={{ a: NextLinkWrapper }}>{children}</ReactMarkdown>
 
 
+                {isGeneratingEditorText && (
+                  <div className="text-muted mb-3">
+                    <LoadingSpinner />
+                    <span className="ms-2">{t('sidebar_ai_assistant.text_generation_by_editor_assistant_label')}</span>
+                  </div>
+                )}
+
                 {showActionButtons && !isActionButtonClicked && (
                 {showActionButtons && !isActionButtonClicked && (
                   <div className="d-flex mt-2 justify-content-start">
                   <div className="d-flex mt-2 justify-content-start">
                     <button
                     <button
@@ -104,13 +113,14 @@ type Props = {
   role: MessageCardRole,
   role: MessageCardRole,
   children: string,
   children: string,
   showActionButtons?: boolean,
   showActionButtons?: boolean,
+  isGeneratingEditorText?: boolean,
   onDiscard?: () => void,
   onDiscard?: () => void,
   onAccept?: () => void,
   onAccept?: () => void,
 }
 }
 
 
 export const MessageCard = (props: Props): JSX.Element => {
 export const MessageCard = (props: Props): JSX.Element => {
   const {
   const {
-    role, children, showActionButtons, onAccept, onDiscard,
+    role, children, showActionButtons, isGeneratingEditorText, onAccept, onDiscard,
   } = props;
   } = props;
 
 
   return role === 'user'
   return role === 'user'
@@ -118,6 +128,7 @@ export const MessageCard = (props: Props): JSX.Element => {
     : (
     : (
       <AssistantMessageCard
       <AssistantMessageCard
         showActionButtons={showActionButtons}
         showActionButtons={showActionButtons}
+        isGeneratingEditorText={isGeneratingEditorText}
         onAccept={onAccept}
         onAccept={onAccept}
         onDiscard={onDiscard}
         onDiscard={onDiscard}
       >{children}
       >{children}

+ 5 - 3
apps/app/src/features/openai/client/services/editor-assistant.tsx

@@ -78,6 +78,7 @@ type UseEditorAssistant = () => {
   form: UseFormReturn<FormData>
   form: UseFormReturn<FormData>
   resetForm: () => void
   resetForm: () => void
   isTextSelected: boolean,
   isTextSelected: boolean,
+  isGeneratingEditorText: boolean,
 
 
   // Views
   // Views
   generateInitialView: GenerateInitialView,
   generateInitialView: GenerateInitialView,
@@ -144,12 +145,12 @@ export const useEditorAssistant: UseEditorAssistant = () => {
   // Refs
   // Refs
   // const positionRef = useRef<number>(0);
   // const positionRef = useRef<number>(0);
   const lineRef = useRef<number>(0);
   const lineRef = useRef<number>(0);
-  const isApplyingDiffRef = useRef<boolean>(false);
 
 
   // States
   // States
   const [detectedDiff, setDetectedDiff] = useState<DetectedDiff>();
   const [detectedDiff, setDetectedDiff] = useState<DetectedDiff>();
   const [selectedAiAssistant, setSelectedAiAssistant] = useState<AiAssistantHasId>();
   const [selectedAiAssistant, setSelectedAiAssistant] = useState<AiAssistantHasId>();
   const [selectedText, setSelectedText] = useState<string>();
   const [selectedText, setSelectedText] = useState<string>();
+  const [isGeneratingEditorText, setIsGeneratingEditorText] = useState<boolean>(false);
 
 
   const isTextSelected = useMemo(() => selectedText != null && selectedText.length !== 0, [selectedText]);
   const isTextSelected = useMemo(() => selectedText != null && selectedText.length !== 0, [selectedText]);
 
 
@@ -211,10 +212,9 @@ export const useEditorAssistant: UseEditorAssistant = () => {
   const processMessage: ProcessMessage = useCallback((data, handler) => {
   const processMessage: ProcessMessage = useCallback((data, handler) => {
     handleIfSuccessfullyParsed(data, SseMessageSchema, (data: SseMessage) => {
     handleIfSuccessfullyParsed(data, SseMessageSchema, (data: SseMessage) => {
       handler.onMessage(data);
       handler.onMessage(data);
-      isApplyingDiffRef.current = false;
+      setIsGeneratingEditorText(true);
     });
     });
     handleIfSuccessfullyParsed(data, SseDetectedDiffSchema, (data: SseDetectedDiff) => {
     handleIfSuccessfullyParsed(data, SseDetectedDiffSchema, (data: SseDetectedDiff) => {
-      isApplyingDiffRef.current = true;
       mutateIsEnableUnifiedMergeView(true);
       mutateIsEnableUnifiedMergeView(true);
       setDetectedDiff((prev) => {
       setDetectedDiff((prev) => {
         const newData = { data, applied: false, id: crypto.randomUUID() };
         const newData = { data, applied: false, id: crypto.randomUUID() };
@@ -226,6 +226,7 @@ export const useEditorAssistant: UseEditorAssistant = () => {
       handler.onDetectedDiff(data);
       handler.onDetectedDiff(data);
     });
     });
     handleIfSuccessfullyParsed(data, SseFinalizedSchema, (data: SseFinalized) => {
     handleIfSuccessfullyParsed(data, SseFinalizedSchema, (data: SseFinalized) => {
+      setIsGeneratingEditorText(false);
       handler.onFinalized(data);
       handler.onFinalized(data);
     });
     });
   }, [mutateIsEnableUnifiedMergeView]);
   }, [mutateIsEnableUnifiedMergeView]);
@@ -406,6 +407,7 @@ export const useEditorAssistant: UseEditorAssistant = () => {
     form,
     form,
     resetForm,
     resetForm,
     isTextSelected,
     isTextSelected,
+    isGeneratingEditorText,
 
 
     // Views
     // Views
     generateInitialView,
     generateInitialView,