Explorar o código

imprv: refactor markdown handling in useEditorAssistant

Shun Miyazawa hai 11 meses
pai
achega
7b2d7b5bd1

+ 27 - 6
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/AiAssistantSidebar.tsx

@@ -19,6 +19,7 @@ import type { IThreadRelationHasId } from '../../../../interfaces/thread-relatio
 import {
   useEditorAssistant,
   useAiAssistantSidebarCloseEffect as useAiAssistantSidebarCloseEffectForEditorAssistant,
+  isEditorAssistantFormData,
   type FormData as FormDataForEditorAssistant,
 } from '../../../services/editor-assistant';
 import {
@@ -88,6 +89,7 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
     processMessage: processMessageForEditorAssistant,
     form: formForForEditorAssistant,
     resetForm: resetFormEditorAssistant,
+    isTextSelected,
 
     // Views
     generateInitialView: generateInitialViewForEditorAssistant,
@@ -124,19 +126,22 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
     return thread;
   }, [aiAssistantData, createThreadForEditorAssistant, createThreadForKnowledgeAssistant, isEditorAssistant]);
 
-  const postMessage = useCallback(async(currentThreadId: string, input: string) => {
+  const postMessage = useCallback(async(currentThreadId: string, formData: FormData) => {
     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) {
-      const response = await postMessageForKnowledgeAssistant(aiAssistantData._id, currentThreadId, input);
+      const response = await postMessageForKnowledgeAssistant(aiAssistantData._id, currentThreadId, formData);
       return response;
     }
   }, [aiAssistantData?._id, isEditorAssistant, postMessageForEditorAssistant, postMessageForKnowledgeAssistant]);
 
   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
     if (isGenerating) {
       return;
@@ -185,7 +190,7 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
         return;
       }
 
-      const response = await postMessage(currentThreadId_, data.input);
+      const response = await postMessage(currentThreadId_, data);
       if (response == null) {
         return;
       }
@@ -284,6 +289,22 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
   // eslint-disable-next-line max-len
   }, [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>) => {
     if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
       form.handleSubmit(submit)();

+ 38 - 26
apps/app/src/features/openai/client/services/editor-assistant.tsx

@@ -44,7 +44,7 @@ interface CreateThread {
   (): Promise<IThreadRelationHasId>;
 }
 interface PostMessage {
-  (threadId: string, userMessage: string): Promise<Response>;
+  (threadId: string, formData: FormData): Promise<Response>;
 }
 interface ProcessMessage {
   (data: unknown, handler: {
@@ -61,7 +61,8 @@ interface GenerateMessageCard {
   (role: MessageCardRole, children: string, messageId: string, messageLogs: MessageLog[], generatingAnswerMessage?: MessageLog): JSX.Element;
 }
 export interface FormData {
-  input: string
+  input: string,
+  markdownType?: 'full' | 'selected' | 'none'
 }
 
 type DetectedDiff = Array<{
@@ -76,6 +77,7 @@ type UseEditorAssistant = () => {
   processMessage: ProcessMessage,
   form: UseFormReturn<FormData>
   resetForm: () => void
+  isTextSelected: boolean,
 
   // Views
   generateInitialView: GenerateInitialView,
@@ -142,12 +144,13 @@ export const useEditorAssistant: UseEditorAssistant = () => {
   // Refs
   // const positionRef = useRef<number>(0);
   const lineRef = useRef<number>(0);
-  const isQuickMenuReqRef = useRef<boolean>(false);
 
   // States
   const [detectedDiff, setDetectedDiff] = useState<DetectedDiff>();
-  const [selectedText, setSelectedText] = useState<string>();
   const [selectedAiAssistant, setSelectedAiAssistant] = useState<AiAssistantHasId>();
+  const [selectedText, setSelectedText] = useState<string>();
+
+  const isTextSelected = useMemo(() => selectedText != null && selectedText.length !== 0, [selectedText]);
 
   // Hooks
   const { t } = useTranslation();
@@ -176,17 +179,19 @@ export const useEditorAssistant: UseEditorAssistant = () => {
     return response.data;
   }, [selectedAiAssistant?._id]);
 
-  const postMessage: PostMessage = useCallback(async(threadId, userMessage) => {
+  const postMessage: PostMessage = useCallback(async(threadId, formData) => {
     const getMarkdown = (): string | undefined => {
-      if (isQuickMenuReqRef.current) {
-        return codeMirrorEditor?.getDoc();
+      if (formData.markdownType === 'none') {
+        return undefined;
       }
 
-      if (selectedText != null && selectedText.length !== 0) {
+      if (formData.markdownType === 'selected') {
         return selectedText;
       }
 
-      return undefined;
+      if (formData.markdownType === 'full') {
+        return codeMirrorEditor?.getDoc();
+      }
     };
 
     const response = await fetch('/_api/v3/openai/edit', {
@@ -194,12 +199,11 @@ export const useEditorAssistant: UseEditorAssistant = () => {
       headers: { 'Content-Type': 'application/json' },
       body: JSON.stringify({
         threadId,
-        userMessage,
+        userMessage: formData.input,
         markdown: getMarkdown(),
       }),
     });
 
-    isQuickMenuReqRef.current = false;
     return response;
   }, [codeMirrorEditor, selectedText]);
 
@@ -259,7 +263,7 @@ export const useEditorAssistant: UseEditorAssistant = () => {
         pendingDetectedDiff.forEach((detectedDiff) => {
           if (isReplaceDiff(detectedDiff.data)) {
 
-            if (selectedText != null && selectedText.length !== 0) {
+            if (isTextSelected) {
               const lineInfo = getLineInfo(yText, lineRef.current);
               if (lineInfo != null && lineInfo.text !== detectedDiff.data.diff.replace) {
                 yText.delete(lineInfo.startIndex, lineInfo.text.length);
@@ -284,26 +288,29 @@ export const useEditorAssistant: UseEditorAssistant = () => {
         });
       });
 
-      // Mark as applied: true after applying to secondaryDoc
+      // Mark items as applied after applying to secondaryDoc
       setDetectedDiff((prev) => {
+        if (!prev) return prev;
         const pendingDetectedDiffIds = pendingDetectedDiff.map(diff => diff.id);
-        prev?.forEach((diff) => {
+        return prev.map((diff) => {
           if (pendingDetectedDiffIds.includes(diff.id)) {
-            diff.applied = true;
+            return { ...diff, applied: true };
           }
+          return diff;
         });
-        return prev;
       });
+    }
+  }, [codeMirrorEditor, detectedDiff, isTextSelected, selectedText, yDocs?.secondaryDoc]);
 
-      // Set detectedDiff to undefined after applying all detectedDiff to secondaryDoc
-      if (detectedDiff?.filter(detectedDiff => detectedDiff.applied === false).length === 0) {
-        setSelectedText(undefined);
-        setDetectedDiff(undefined);
-        lineRef.current = 0;
-        // positionRef.current = 0;
-      }
+  // Set detectedDiff to undefined after applying all detectedDiff to secondaryDoc
+  useEffect(() => {
+    if (detectedDiff?.filter(detectedDiff => detectedDiff.applied === false).length === 0) {
+      setSelectedText(undefined);
+      setDetectedDiff(undefined);
+      lineRef.current = 0;
+      // positionRef.current = 0;
     }
-  }, [codeMirrorEditor, detectedDiff, selectedText, yDocs?.secondaryDoc]);
+  }, [detectedDiff]);
 
 
   // Views
@@ -323,8 +330,7 @@ export const useEditorAssistant: UseEditorAssistant = () => {
     };
 
     const clickQuickMenuHandler = async(quickMenu: string) => {
-      isQuickMenuReqRef.current = true;
-      await onSubmit({ input: quickMenu });
+      await onSubmit({ input: quickMenu, markdownType: 'full' });
     };
 
     return (
@@ -396,6 +402,7 @@ export const useEditorAssistant: UseEditorAssistant = () => {
     processMessage,
     form,
     resetForm,
+    isTextSelected,
 
     // Views
     generateInitialView,
@@ -416,3 +423,8 @@ export const useAiAssistantSidebarCloseEffect = (): void => {
     }
   }, [close, data?.isEditorAssistant, editorMode]);
 };
+
+// type guard
+export const isEditorAssistantFormData = (formData): formData is FormData => {
+  return 'markdownType' in formData;
+};

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

@@ -25,7 +25,7 @@ interface CreateThread {
 }
 
 interface PostMessage {
-  (aiAssistantId: string, threadId: string, userMessage: string): Promise<Response>;
+  (aiAssistantId: string, threadId: string, formData: FormData): Promise<Response>;
 }
 
 interface ProcessMessage {
@@ -103,14 +103,14 @@ export const useKnowledgeAssistant: UseKnowledgeAssistant = () => {
     return thread;
   }, [mutateThreadData]);
 
-  const postMessage: PostMessage = useCallback(async(aiAssistantId, threadId, userMessage) => {
+  const postMessage: PostMessage = useCallback(async(aiAssistantId, threadId, formData) => {
     const response = await fetch('/_api/v3/openai/message', {
       method: 'POST',
       headers: { 'Content-Type': 'application/json' },
       body: JSON.stringify({
         aiAssistantId,
         threadId,
-        userMessage,
+        userMessage: formData.input,
         summaryMode: form.getValues('summaryMode'),
       }),
     });