import useAutosizeTextArea from "../../../hooks/misc/use-autosize-textarea"; import { EditingEditorState, Generator_InsertionOrEditingSuggestion, } from "../../../types/base/autosuggestions-bare-function"; import { SourceSearchBox } from "../../source-search-box/source-search-box"; import { DocumentPointer, useCopilotContext } from "@copilotkit/react-core"; import { Button } from "../../ui/button"; import { Label } from "../../ui/label"; import { useCallback, useEffect, useRef, useState } from "react"; import { streamPromiseFlatten } from "../../../lib/stream-promise-flatten"; import { IncludedFilesPreview } from "./included-files-preview"; import { useHoveringEditorContext } from "../hovering-editor-provider"; export type SuggestionState = { editorState: EditingEditorState; }; export interface HoveringInsertionPromptBoxCoreProps { state: SuggestionState; performInsertion: (insertedText: string) => void; insertionOrEditingFunction: Generator_InsertionOrEditingSuggestion; contextCategories: string[]; } export const HoveringInsertionPromptBoxCore = ({ performInsertion, state, insertionOrEditingFunction, contextCategories, }: HoveringInsertionPromptBoxCoreProps) => { const { getDocumentsContext } = useCopilotContext(); const [editSuggestion, setEditSuggestion] = useState(""); const [suggestionIsLoading, setSuggestionIsLoading] = useState(false); const [adjustmentPrompt, setAdjustmentPrompt] = useState(""); const [generatingSuggestion, setGeneratingSuggestion] = useState | null>(null); const adjustmentTextAreaRef = useRef(null); const suggestionTextAreaRef = useRef(null); const [filePointers, setFilePointers] = useState([]); const [suggestedFiles, setSuggestedFiles] = useState([]); useEffect(() => { setSuggestedFiles(getDocumentsContext(contextCategories)); }, [contextCategories, getDocumentsContext]); useAutosizeTextArea(suggestionTextAreaRef, editSuggestion || ""); useAutosizeTextArea(adjustmentTextAreaRef, adjustmentPrompt || ""); // initially focus on the adjustment prompt text area useEffect(() => { adjustmentTextAreaRef.current?.focus(); }, []); // continuously read the generating suggestion stream and update the edit suggestion useEffect(() => { // if no generating suggestion, do nothing if (!generatingSuggestion) { return; } // Check if the stream is already locked (i.e. already reading from it) if (generatingSuggestion.locked) { return; } // reset the edit suggestion setEditSuggestion(""); // read the generating suggestion stream and continuously update the edit suggestion const reader = generatingSuggestion.getReader(); const read = async () => { setSuggestionIsLoading(true); while (true) { const { done, value } = await reader.read(); if (done) { break; } setEditSuggestion((prev) => { const newSuggestion = prev + value; // Scroll to the bottom of the textarea. We call this here to make sure scroll-to-bottom is synchronous with the state update. if (suggestionTextAreaRef.current) { suggestionTextAreaRef.current.scrollTop = suggestionTextAreaRef.current.scrollHeight; } return newSuggestion; }); } setSuggestionIsLoading(false); }; read(); return () => { // release the lock if the reader is not closed on unmount const releaseLockIfNotClosed = async () => { try { await reader.closed; } catch { reader.releaseLock(); } }; releaseLockIfNotClosed(); }; }, [generatingSuggestion]); // generate an adjustment to the completed text, based on the adjustment prompt const beginGeneratingAdjustment = useCallback(async () => { // don't generate text if the prompt is empty if (!adjustmentPrompt.trim()) { return; } // editor state includes the text being edited, and the text before/after the selection // if the current edit suggestion is not empty, then use *it* as the "selected text" - instead of the editor state's selected text let modificationState = state.editorState; if (editSuggestion !== "") { modificationState.selectedText = editSuggestion; } // generate the adjustment suggestion const adjustmentSuggestionTextStreamPromise = insertionOrEditingFunction( modificationState, adjustmentPrompt, filePointers, new AbortController().signal, ); const adjustmentSuggestionTextStream = streamPromiseFlatten( adjustmentSuggestionTextStreamPromise, ); setGeneratingSuggestion(adjustmentSuggestionTextStream); }, [ adjustmentPrompt, editSuggestion, state.editorState, insertionOrEditingFunction, filePointers, ]); const isLoading = suggestionIsLoading; const textToEdit = editSuggestion || state.editorState.selectedText; const adjustmentLabel = textToEdit === "" ? "Describe the text you want to insert" : "Describe adjustments to the suggested text"; const placeholder = textToEdit === "" ? "e.g. 'summarize the client's top 3 pain-points from @CallTranscript'" : "e.g. 'make it more formal', 'be more specific', ..."; const { setIsDisplayed } = useHoveringEditorContext(); const AdjustmentPromptComponent = ( <>