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

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

@@ -76,12 +76,21 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
 
   const { postMessage: postMessageForKnowledgeAssistant, processMessage: processMessageForKnowledgeAssistant } = useKnowledgeAssistant();
   const {
+    isTextSelected,
     postMessage: postMessageForEditorAssistant,
     processMessage: processMessageForEditorAssistant,
     accept,
     reject,
   } = useEditorAssistant();
 
+  const canPostMessageForEditorAssistant = useMemo(() => {
+    if (!isEditorAssistant) {
+      return true;
+    }
+
+    return isTextSelected;
+  }, [isEditorAssistant, isTextSelected]);
+
   const form = useForm<FormData>({
     defaultValues: {
       input: '',
@@ -416,7 +425,7 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
                 <button
                   type="submit"
                   className="btn btn-submit no-border"
-                  disabled={form.formState.isSubmitting || isGenerating}
+                  disabled={form.formState.isSubmitting || isGenerating || !canPostMessageForEditorAssistant}
                 >
                   <span className="material-symbols-outlined">send</span>
                 </button>

+ 30 - 33
apps/app/src/features/openai/client/services/editor-assistant.ts

@@ -3,7 +3,7 @@ import {
 } from 'react';
 
 import { GlobalCodeMirrorEditorKey } from '@growi/editor';
-import { acceptChange, rejectChange } from '@growi/editor/dist/client/services/unified-merge-view';
+import { acceptChange, rejectChange, useTextSelectionEffect } from '@growi/editor/dist/client/services/unified-merge-view';
 import { useCodeMirrorEditorIsolated } from '@growi/editor/dist/client/stores/codemirror-editor';
 import { useSecondaryYdocs } from '@growi/editor/dist/client/stores/use-secondary-ydocs';
 import { type Text as YText } from 'yjs';
@@ -42,6 +42,7 @@ type DetectedDiff = Array<{
 }>
 
 type UseEditorAssistant = () => {
+  isTextSelected: boolean,
   postMessage: PostMessage,
   processMessage: ProcessMessage,
   accept: () => void,
@@ -103,6 +104,9 @@ export const useEditorAssistant: UseEditorAssistant = () => {
 
   // States
   const [detectedDiff, setDetectedDiff] = useState<DetectedDiff>();
+  const [selectedTextFirstLineNumber, setSelectedTextFirstLineNumber] = useState<number>();
+  const [selectedText, setSelectedText] = useState<string>();
+  const isTextSelected = selectedText != null && selectedText.length !== 0;
 
   // SWR Hooks
   const { data: currentPageId } = useCurrentPageId();
@@ -111,46 +115,24 @@ export const useEditorAssistant: UseEditorAssistant = () => {
   const ydocs = useSecondaryYdocs(isEnableUnifiedMergeView ?? false, { pageId: currentPageId ?? undefined, useSecondary: isEnableUnifiedMergeView ?? false });
 
   // Functions
-  const getSelectedText = useCallback(() => {
-    const view = codeMirrorEditor?.view;
-    if (view == null) {
-      return;
-    }
-
-    return view.state.sliceDoc(
-      view.state.selection.main.from,
-      view.state.selection.main.to,
-    );
-  }, [codeMirrorEditor?.view]);
-
-  const getSelectedTextFirstLineNumber = useCallback(() => {
-    const view = codeMirrorEditor?.view;
-    if (view == null) {
-      return;
-    }
-
-    const selectionStart = view.state.selection.main.from;
-
-    const lineInfo = view.state.doc.lineAt(selectionStart);
-
-    return lineInfo.number;
-  }, [codeMirrorEditor?.view]);
-
   const postMessage: PostMessage = useCallback(async(threadId, userMessage) => {
-    lineRef.current = getSelectedTextFirstLineNumber() ?? 0;
+    lineRef.current = selectedTextFirstLineNumber ?? 0;
 
-    const selectedMarkdown = getSelectedText();
+    // const selectedMarkdown = getSelectedText();
     const response = await fetch('/_api/v3/openai/edit', {
       method: 'POST',
       headers: { 'Content-Type': 'application/json' },
       body: JSON.stringify({
         threadId,
         userMessage,
-        markdown: selectedMarkdown,
+        markdown: selectedText,
       }),
     });
+
+    setSelectedText(undefined);
+
     return response;
-  }, [getSelectedText, getSelectedTextFirstLineNumber]);
+  }, [selectedText, selectedTextFirstLineNumber]);
 
   const processMessage: ProcessMessage = useCallback((data, handler) => {
     handleIfSuccessfullyParsed(data, SseMessageSchema, (data: SseMessage) => {
@@ -173,18 +155,32 @@ export const useEditorAssistant: UseEditorAssistant = () => {
   }, [mutateIsEnableUnifiedMergeView]);
 
   const accept = useCallback(() => {
-    acceptChange(codeMirrorEditor?.view);
+    if (codeMirrorEditor?.view == null) {
+      return;
+    }
+
+    acceptChange(codeMirrorEditor.view);
     mutateIsEnableUnifiedMergeView(false);
   }, [codeMirrorEditor?.view, mutateIsEnableUnifiedMergeView]);
 
   const reject = useCallback(() => {
-    rejectChange(codeMirrorEditor?.view);
+    if (codeMirrorEditor?.view == null) {
+      return;
+    }
+
+    rejectChange(codeMirrorEditor.view);
     mutateIsEnableUnifiedMergeView(false);
   }, [codeMirrorEditor?.view, mutateIsEnableUnifiedMergeView]);
 
+  const selectTextHandler = useCallback((selectedText?: string, selectedTextFirstLineNumber?: number) => {
+    setSelectedText(selectedText);
+    setSelectedTextFirstLineNumber(selectedTextFirstLineNumber);
+  }, []);
+
   // Effects
-  useEffect(() => {
+  useTextSelectionEffect(codeMirrorEditor, selectTextHandler);
 
+  useEffect(() => {
     const pendingDetectedDiff: DetectedDiff | undefined = detectedDiff?.filter(diff => diff.applied === false);
     if (ydocs?.secondaryDoc != null && pendingDetectedDiff != null && pendingDetectedDiff.length > 0) {
 
@@ -252,6 +248,7 @@ export const useEditorAssistant: UseEditorAssistant = () => {
   }, [codeMirrorEditor, detectedDiff, ydocs?.secondaryDoc]);
 
   return {
+    isTextSelected,
     postMessage,
     processMessage,
     accept,