{"version":3,"sources":["../src/components/hovering-toolbar/text-insertion-prompt-box/hovering-insertion-prompt-box-core.tsx"],"sourcesContent":["import useAutosizeTextArea from \"../../../hooks/misc/use-autosize-textarea\";\nimport {\n  EditingEditorState,\n  Generator_InsertionOrEditingSuggestion,\n} from \"../../../types/base/autosuggestions-bare-function\";\nimport { SourceSearchBox } from \"../../source-search-box/source-search-box\";\nimport { DocumentPointer, useAiContext } from \"@vn-sdk/react-core\";\nimport { Button } from \"../../ui/button\";\nimport { Label } from \"../../ui/label\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { streamPromiseFlatten } from \"../../../lib/stream-promise-flatten\";\nimport { IncludedFilesPreview } from \"./included-files-preview\";\nimport { useHoveringEditorContext } from \"../hovering-editor-provider\";\n\nexport type SuggestionState = {\n  editorState: EditingEditorState;\n};\n\nexport interface HoveringInsertionPromptBoxCoreProps {\n  state: SuggestionState;\n  performInsertion: (insertedText: string) => void;\n  insertionOrEditingFunction: Generator_InsertionOrEditingSuggestion;\n  contextCategories: string[];\n}\n\nexport const HoveringInsertionPromptBoxCore = ({\n  performInsertion,\n  state,\n  insertionOrEditingFunction,\n  contextCategories,\n}: HoveringInsertionPromptBoxCoreProps) => {\n  const { getDocumentsContext } = useAiContext();\n\n  const [editSuggestion, setEditSuggestion] = useState<string>(\"\");\n  const [suggestionIsLoading, setSuggestionIsLoading] = useState<boolean>(false);\n\n  const [adjustmentPrompt, setAdjustmentPrompt] = useState<string>(\"\");\n\n  const [generatingSuggestion, setGeneratingSuggestion] = useState<ReadableStream<string> | null>(\n    null,\n  );\n\n  const adjustmentTextAreaRef = useRef<HTMLTextAreaElement>(null);\n  const suggestionTextAreaRef = useRef<HTMLTextAreaElement>(null);\n\n  const [filePointers, setFilePointers] = useState<DocumentPointer[]>([]);\n\n  const [suggestedFiles, setSuggestedFiles] = useState<DocumentPointer[]>([]);\n  useEffect(() => {\n    setSuggestedFiles(getDocumentsContext(contextCategories));\n  }, [contextCategories, getDocumentsContext]);\n\n  useAutosizeTextArea(suggestionTextAreaRef, editSuggestion || \"\");\n  useAutosizeTextArea(adjustmentTextAreaRef, adjustmentPrompt || \"\");\n\n  // initially focus on the adjustment prompt text area\n  useEffect(() => {\n    adjustmentTextAreaRef.current?.focus();\n  }, []);\n\n  // continuously read the generating suggestion stream and update the edit suggestion\n  useEffect(() => {\n    // if no generating suggestion, do nothing\n    if (!generatingSuggestion) {\n      return;\n    }\n\n    // Check if the stream is already locked (i.e. already reading from it)\n    if (generatingSuggestion.locked) {\n      return;\n    }\n\n    // reset the edit suggestion\n    setEditSuggestion(\"\");\n\n    // read the generating suggestion stream and continuously update the edit suggestion\n    const reader = generatingSuggestion.getReader();\n    const read = async () => {\n      setSuggestionIsLoading(true);\n      while (true) {\n        const { done, value } = await reader.read();\n        if (done) {\n          break;\n        }\n        setEditSuggestion((prev) => {\n          const newSuggestion = prev + value;\n\n          // Scroll to the bottom of the textarea. We call this here to make sure scroll-to-bottom is synchronous with the state update.\n          if (suggestionTextAreaRef.current) {\n            suggestionTextAreaRef.current.scrollTop = suggestionTextAreaRef.current.scrollHeight;\n          }\n          return newSuggestion;\n        });\n      }\n\n      setSuggestionIsLoading(false);\n    };\n    read();\n\n    return () => {\n      // release the lock if the reader is not closed on unmount\n      const releaseLockIfNotClosed = async () => {\n        try {\n          await reader.closed;\n        } catch {\n          reader.releaseLock();\n        }\n      };\n\n      releaseLockIfNotClosed();\n    };\n  }, [generatingSuggestion]);\n\n  // generate an adjustment to the completed text, based on the adjustment prompt\n  const beginGeneratingAdjustment = useCallback(async () => {\n    // don't generate text if the prompt is empty\n    if (!adjustmentPrompt.trim()) {\n      return;\n    }\n\n    // editor state includes the text being edited, and the text before/after the selection\n    // if the current edit suggestion is not empty, then use *it* as the \"selected text\" - instead of the editor state's selected text\n    let modificationState = state.editorState;\n    if (editSuggestion !== \"\") {\n      modificationState.selectedText = editSuggestion;\n    }\n\n    // generate the adjustment suggestion\n    const adjustmentSuggestionTextStreamPromise = insertionOrEditingFunction(\n      modificationState,\n      adjustmentPrompt,\n      filePointers,\n      new AbortController().signal,\n    );\n    const adjustmentSuggestionTextStream = streamPromiseFlatten(\n      adjustmentSuggestionTextStreamPromise,\n    );\n\n    setGeneratingSuggestion(adjustmentSuggestionTextStream);\n  }, [\n    adjustmentPrompt,\n    editSuggestion,\n    state.editorState,\n    insertionOrEditingFunction,\n    filePointers,\n  ]);\n\n  const isLoading = suggestionIsLoading;\n\n  const textToEdit = editSuggestion || state.editorState.selectedText;\n  const adjustmentLabel =\n    textToEdit === \"\"\n      ? \"Describe the text you want to insert\"\n      : \"Describe adjustments to the suggested text\";\n  const placeholder =\n    textToEdit === \"\"\n      ? \"e.g. 'summarize the client's top 3 pain-points from @CallTranscript'\"\n      : \"e.g. 'make it more formal', 'be more specific', ...\";\n\n  const { setIsDisplayed } = useHoveringEditorContext();\n\n  const AdjustmentPromptComponent = (\n    <>\n      <Label className=\"\">{adjustmentLabel}</Label>\n      <div className=\"relative w-full flex items-center\">\n        <textarea\n          data-testid=\"adjustment-prompt\"\n          disabled={suggestionIsLoading}\n          ref={adjustmentTextAreaRef}\n          value={adjustmentPrompt}\n          onChange={(e) => setAdjustmentPrompt(e.target.value)}\n          onKeyDown={(e) => {\n            if (e.key === \"Enter\" && e.shiftKey) {\n              e.preventDefault();\n              setAdjustmentPrompt(adjustmentPrompt + \"\\n\");\n            } else if (e.key === \"Enter\") {\n              e.preventDefault();\n              beginGeneratingAdjustment();\n            }\n          }}\n          placeholder={placeholder}\n          style={{ minHeight: \"3rem\" }}\n          className=\"w-full bg-slate-100 h-auto h-min-14 text-sm p-2 rounded-md resize-none overflow-visible focus:outline-none focus:ring-0 focus:border-non pr-[3rem]\"\n          rows={1}\n        />\n        <button\n          onClick={beginGeneratingAdjustment}\n          className=\"absolute right-2 bg-blue-500 text-white w-8 h-8 rounded-full flex items-center justify-center\"\n          data-testid=\"generate-button\"\n        >\n          <i className=\"material-icons\">arrow_forward</i>\n        </button>\n      </div>\n    </>\n  );\n\n  const SuggestionComponent = (\n    <>\n      <div className=\"flex justify-between items-end w-full\">\n        <Label className=\"mt-4\">Suggested:</Label>\n        <div className=\"ml-auto\">\n          {isLoading && (\n            <div className=\"flex justify-center items-center\">\n              <div\n                className=\"inline-block h-4 w-4 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]\"\n                role=\"status\"\n              >\n                <span className=\"!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]\">\n                  Loading...\n                </span>\n              </div>\n            </div>\n          )}\n        </div>\n      </div>\n      <textarea\n        data-testid=\"suggestion-result\"\n        ref={suggestionTextAreaRef}\n        value={editSuggestion}\n        disabled={suggestionIsLoading}\n        onChange={(e) => setEditSuggestion(e.target.value)}\n        className=\"w-full text-base p-2 border border-gray-300 rounded-md resize-none bg-green-50\"\n        style={{ overflow: \"auto\", maxHeight: \"10em\" }}\n      />\n    </>\n  );\n\n  const SubmitComponent = (\n    <div className=\"flex w-full gap-4 justify-start\">\n      <Button\n        data-testid=\"insert-button\"\n        className=\" bg-green-700 text-white\"\n        onClick={() => {\n          performInsertion(editSuggestion);\n        }}\n      >\n        Insert <i className=\"material-icons\">check</i>\n      </Button>\n    </div>\n  );\n\n  // show source search if the last word in the adjustment prompt BEGINS with an @\n  const sourceSearchCandidate = adjustmentPrompt.split(\" \").pop();\n  // if the candidate is @someCandidate, then 'someCandidate', otherwise undefined\n  const sourceSearchWord = sourceSearchCandidate?.startsWith(\"@\")\n    ? sourceSearchCandidate.slice(1)\n    : undefined;\n\n  return (\n    <div className=\"w-full flex flex-col items-start relative gap-2\">\n      {AdjustmentPromptComponent}\n      {filePointers.length > 0 && (\n        <IncludedFilesPreview includedFiles={filePointers} setIncludedFiles={setFilePointers} />\n      )}\n      {sourceSearchWord !== undefined && (\n        <SourceSearchBox\n          searchTerm={sourceSearchWord}\n          suggestedFiles={suggestedFiles}\n          onSelectedFile={(filePointer) => {\n            setAdjustmentPrompt(adjustmentPrompt.replace(new RegExp(`@${sourceSearchWord}$`), \"\"));\n            setFilePointers((prev) => [...prev, filePointer]);\n\n            // focus back on the adjustment prompt, and move the cursor to the end\n            adjustmentTextAreaRef.current?.focus();\n          }}\n        />\n      )}\n      {generatingSuggestion ? SuggestionComponent : null}\n      {generatingSuggestion ? SubmitComponent : null}\n    </div>\n  );\n};\n"],"mappings":"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","names":[]}