From 146130b936f82ad994cbaeb90c127eda31e3ba19 Mon Sep 17 00:00:00 2001 From: Tahier Hussain <89440263+tahierhussain@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:09:27 +0530 Subject: [PATCH] FEAT: Added "Run All Prompts" with Concurrent API Request Queuing Mechanism (#749) * Refactored the code to support concurrent prompt run API calls in the FE * Code quality improvement * Sonar Fix: Removed redundant jump * Optimization after load testing * Fixed sonar issues * Optimized and fixed sonar issues in usePromptRun custom hook * Fixed sonar issues * Disabled the feature to persist the prompt run details --------- Co-authored-by: Neha <115609453+nehabagdia@users.noreply.github.com> --- .../document-manager/DocumentManager.jsx | 6 +- .../editable-text/EditableText.jsx | 23 +- .../manage-docs-modal/ManageDocsModal.jsx | 12 +- .../OutputForDocModal.jsx | 16 +- .../prompt-card/DisplayPromptResult.jsx | 41 +- .../custom-tools/prompt-card/Header.jsx | 30 +- .../custom-tools/prompt-card/PrompDnd.jsx | 17 +- .../custom-tools/prompt-card/PromptCard.jsx | 863 +++++------------- .../prompt-card/PromptCardItems.jsx | 41 +- .../custom-tools/prompt-card/PromptOutput.jsx | 104 +-- .../prompt-card/PromptOutputExpandBtn.jsx | 5 +- .../prompt-card/PromptOutputsModal.jsx | 14 +- .../custom-tools/prompt-card/PromptRun.jsx | 77 ++ .../prompt-card/PromptRunCost.jsx | 4 +- .../prompt-card/PromptRunTimer.jsx | 8 +- .../prompt-card/RunAllPrompts.jsx | 46 + .../custom-tools/token-usage/TokenUsage.jsx | 4 +- .../custom-tools/tool-ide/ToolIde.jsx | 4 +- .../custom-tools/tools-main/ToolsMain.jsx | 4 +- .../tools-main/ToolsMainActionBtns.jsx | 8 +- .../helpers/auth/PersistentLogin.js | 2 + frontend/src/helpers/GetStaticData.js | 21 + frontend/src/hooks/usePromptOutput.js | 3 +- frontend/src/hooks/usePromptRun.js | 219 +++++ frontend/src/store/custom-tool-store.js | 4 +- frontend/src/store/prompt-run-queue-store.js | 43 + frontend/src/store/prompt-run-status-store.js | 50 + 27 files changed, 850 insertions(+), 819 deletions(-) create mode 100644 frontend/src/components/custom-tools/prompt-card/PromptRun.jsx create mode 100644 frontend/src/components/custom-tools/prompt-card/RunAllPrompts.jsx create mode 100644 frontend/src/hooks/usePromptRun.js create mode 100644 frontend/src/store/prompt-run-queue-store.js create mode 100644 frontend/src/store/prompt-run-status-store.js diff --git a/frontend/src/components/custom-tools/document-manager/DocumentManager.jsx b/frontend/src/components/custom-tools/document-manager/DocumentManager.jsx index b34cbd3ff..0435a0101 100644 --- a/frontend/src/components/custom-tools/document-manager/DocumentManager.jsx +++ b/frontend/src/components/custom-tools/document-manager/DocumentManager.jsx @@ -88,7 +88,7 @@ function DocumentManager({ generateIndex, handleUpdateTool, handleDocChange }) { const { selectedDoc, listOfDocs, - disableLlmOrDocChange, + isMultiPassExtractLoading, details, indexDocs, isSinglePassExtractLoading, @@ -353,7 +353,7 @@ function DocumentManager({ generateIndex, handleUpdateTool, handleDocChange }) { size="small" disabled={ !selectedDoc || - disableLlmOrDocChange?.length > 0 || + isMultiPassExtractLoading || isSinglePassExtractLoading || page <= 1 } @@ -366,7 +366,7 @@ function DocumentManager({ generateIndex, handleUpdateTool, handleDocChange }) { size="small" disabled={ !selectedDoc || - disableLlmOrDocChange?.length > 0 || + isMultiPassExtractLoading || isSinglePassExtractLoading || page >= listOfDocs?.length } diff --git a/frontend/src/components/custom-tools/editable-text/EditableText.jsx b/frontend/src/components/custom-tools/editable-text/EditableText.jsx index b2f90bd4c..a212f0ede 100644 --- a/frontend/src/components/custom-tools/editable-text/EditableText.jsx +++ b/frontend/src/components/custom-tools/editable-text/EditableText.jsx @@ -15,18 +15,13 @@ function EditableText({ handleChange, isTextarea, placeHolder, + isCoverageLoading, }) { const name = isTextarea ? "prompt" : "prompt_key"; const [triggerHandleChange, setTriggerHandleChange] = useState(false); const [isHovered, setIsHovered] = useState(false); const divRef = useRef(null); - const { - disableLlmOrDocChange, - indexDocs, - selectedDoc, - isSinglePassExtractLoading, - isPublicSource, - } = useCustomToolStore(); + const { isSinglePassExtractLoading, isPublicSource } = useCustomToolStore(); useEffect(() => { setText(defaultText); @@ -91,9 +86,7 @@ function EditableText({ onBlur={handleBlur} onClick={() => setIsEditing(true)} disabled={ - disableLlmOrDocChange.includes(promptId) || - isSinglePassExtractLoading || - isPublicSource + isCoverageLoading || isSinglePassExtractLoading || isPublicSource } /> ); @@ -115,10 +108,7 @@ function EditableText({ onBlur={handleBlur} onClick={() => setIsEditing(true)} disabled={ - disableLlmOrDocChange.includes(promptId) || - indexDocs.includes(selectedDoc?.document_id) || - isSinglePassExtractLoading || - isPublicSource + isCoverageLoading || isSinglePassExtractLoading || isPublicSource } /> ); @@ -129,11 +119,12 @@ EditableText.propTypes = { setIsEditing: PropTypes.func.isRequired, text: PropTypes.string, setText: PropTypes.func.isRequired, - promptId: PropTypes.string.isRequired, - defaultText: PropTypes.string.isRequired, + promptId: PropTypes.string, + defaultText: PropTypes.string, handleChange: PropTypes.func.isRequired, isTextarea: PropTypes.bool, placeHolder: PropTypes.string, + isCoverageLoading: PropTypes.bool.isRequired, }; export { EditableText }; diff --git a/frontend/src/components/custom-tools/manage-docs-modal/ManageDocsModal.jsx b/frontend/src/components/custom-tools/manage-docs-modal/ManageDocsModal.jsx index b7569282b..3d9d53e7d 100644 --- a/frontend/src/components/custom-tools/manage-docs-modal/ManageDocsModal.jsx +++ b/frontend/src/components/custom-tools/manage-docs-modal/ManageDocsModal.jsx @@ -79,7 +79,7 @@ function ManageDocsModal({ updateCustomTool, details, defaultLlmProfile, - disableLlmOrDocChange, + isMultiPassExtractLoading, indexDocs, rawIndexStatus, summarizeIndexStatus, @@ -392,7 +392,7 @@ function ManageDocsModal({ icon={} onClick={() => handleReIndexBtnClick(item)} disabled={ - disableLlmOrDocChange?.length > 0 || + isMultiPassExtractLoading || isSinglePassExtractLoading || indexDocs.includes(item?.document_id) || isUploading || @@ -418,7 +418,7 @@ function ManageDocsModal({ size="small" className="display-flex-align-center" disabled={ - disableLlmOrDocChange?.length > 0 || + isMultiPassExtractLoading || isSinglePassExtractLoading || indexDocs.includes(item?.document_id) || isUploading || @@ -435,7 +435,7 @@ function ManageDocsModal({ checked={selectedDoc?.document_id === item?.document_id} onClick={() => handleDocChange(item)} disabled={ - disableLlmOrDocChange?.length > 0 || + isMultiPassExtractLoading || isSinglePassExtractLoading || indexDocs.includes(item?.document_id) } @@ -447,7 +447,7 @@ function ManageDocsModal({ }, [ listOfDocs, selectedDoc, - disableLlmOrDocChange, + isMultiPassExtractLoading, rawIndexStatus, summarizeIndexStatus, indexDocs, @@ -602,7 +602,7 @@ function ManageDocsModal({ loading={isUploading} disabled={ !defaultLlmProfile || - disableLlmOrDocChange?.length > 0 || + isMultiPassExtractLoading || isSinglePassExtractLoading || isPublicSource } diff --git a/frontend/src/components/custom-tools/output-for-doc-modal/OutputForDocModal.jsx b/frontend/src/components/custom-tools/output-for-doc-modal/OutputForDocModal.jsx index ef69983c9..b1743a5d6 100644 --- a/frontend/src/components/custom-tools/output-for-doc-modal/OutputForDocModal.jsx +++ b/frontend/src/components/custom-tools/output-for-doc-modal/OutputForDocModal.jsx @@ -50,7 +50,7 @@ function OutputForDocModal({ promptId, promptKey, profileManagerId, - docOutputs, + docOutputs = [], }) { const [promptOutputs, setPromptOutputs] = useState([]); const [rows, setRows] = useState([]); @@ -60,7 +60,7 @@ function OutputForDocModal({ details, listOfDocs, selectedDoc, - disableLlmOrDocChange, + isMultiPassExtractLoading, singlePassExtractMode, isSinglePassExtractLoading, isPublicSource, @@ -89,10 +89,6 @@ function OutputForDocModal({ isSinglePassExtractLoading, ]); - useEffect(() => { - updatePromptOutput(docOutputs); - }, [docOutputs]); - useEffect(() => { handleRowsGeneration(promptOutputs); }, [promptOutputs, tokenUsage]); @@ -360,9 +356,7 @@ function OutputForDocModal({ @@ -391,8 +385,8 @@ function OutputForDocModal({ OutputForDocModal.propTypes = { open: PropTypes.bool.isRequired, setOpen: PropTypes.func.isRequired, - promptId: PropTypes.string.isRequired, - promptKey: PropTypes.string.isRequired, + promptId: PropTypes.string, + promptKey: PropTypes.string, profileManagerId: PropTypes.string, docOutputs: PropTypes.object, }; diff --git a/frontend/src/components/custom-tools/prompt-card/DisplayPromptResult.jsx b/frontend/src/components/custom-tools/prompt-card/DisplayPromptResult.jsx index a73bc34fc..f5bba6460 100644 --- a/frontend/src/components/custom-tools/prompt-card/DisplayPromptResult.jsx +++ b/frontend/src/components/custom-tools/prompt-card/DisplayPromptResult.jsx @@ -1,11 +1,41 @@ -import { Typography } from "antd"; +import { Spin, Typography } from "antd"; import PropTypes from "prop-types"; import { InfoCircleFilled } from "@ant-design/icons"; -import { displayPromptResult } from "../../../helpers/GetStaticData"; +import { + displayPromptResult, + generateApiRunStatusId, + PROMPT_RUN_API_STATUSES, +} from "../../../helpers/GetStaticData"; import "./PromptCard.css"; +import { useCustomToolStore } from "../../../store/custom-tool-store"; +import { useEffect, useState } from "react"; +import { SpinnerLoader } from "../../widgets/spinner-loader/SpinnerLoader"; + +function DisplayPromptResult({ output, profileId, docId, promptRunStatus }) { + const [isLoading, setIsLoading] = useState(false); + const { singlePassExtractMode, isSinglePassExtractLoading } = + useCustomToolStore(); + + useEffect(() => { + if (singlePassExtractMode && isSinglePassExtractLoading) { + setIsLoading(true); + return; + } + + const key = generateApiRunStatusId(docId, profileId); + if (promptRunStatus?.[key] === PROMPT_RUN_API_STATUSES.RUNNING) { + setIsLoading(true); + return; + } + + setIsLoading(false); + }, [promptRunStatus, isSinglePassExtractLoading]); + + if (isLoading) { + return } />; + } -function DisplayPromptResult({ output }) { if (output === undefined) { return ( @@ -25,7 +55,10 @@ function DisplayPromptResult({ output }) { } DisplayPromptResult.propTypes = { - output: PropTypes.any.isRequired, + output: PropTypes.any, + profileId: PropTypes.string, + docId: PropTypes.string, + promptRunStatus: PropTypes.object, }; export { DisplayPromptResult }; diff --git a/frontend/src/components/custom-tools/prompt-card/Header.jsx b/frontend/src/components/custom-tools/prompt-card/Header.jsx index 906434ce1..f72a36083 100644 --- a/frontend/src/components/custom-tools/prompt-card/Header.jsx +++ b/frontend/src/components/custom-tools/prompt-card/Header.jsx @@ -11,7 +11,10 @@ import { useEffect, useState } from "react"; import { Button, Checkbox, Col, Dropdown, Row, Tag, Tooltip } from "antd"; import PropTypes from "prop-types"; -import { promptStudioUpdateStatus } from "../../../helpers/GetStaticData"; +import { + PROMPT_RUN_TYPES, + promptStudioUpdateStatus, +} from "../../../helpers/GetStaticData"; import { ConfirmModal } from "../../widgets/confirm-modal/ConfirmModal"; import { EditableText } from "../editable-text/EditableText"; import { useCustomToolStore } from "../../../store/custom-tool-store"; @@ -40,13 +43,11 @@ function Header({ setIsEditingTitle, expandCard, setExpandCard, - enabledProfiles, spsLoading, handleSpsLoading, }) { const { selectedDoc, - disableLlmOrDocChange, singlePassExtractMode, isSinglePassExtractLoading, indexDocs, @@ -58,9 +59,9 @@ function Header({ const [isDisablePrompt, setIsDisablePrompt] = useState(null); - const handleRunBtnClick = (profileManager = null, coverAllDoc = true) => { + const handleRunBtnClick = (promptRunType, docId = null) => { setExpandCard(true); - handleRun(profileManager, coverAllDoc, enabledProfiles, true); + handleRun(promptRunType, promptDetails?.prompt_id, null, docId); }; const handleDisablePrompt = (event) => { @@ -97,7 +98,7 @@ function Header({ ), key: "delete", disabled: - disableLlmOrDocChange?.includes(promptDetails?.prompt_id) || + isCoverageLoading || isSinglePassExtractLoading || indexDocs?.includes(selectedDoc?.document_id) || isPublicSource, @@ -122,6 +123,7 @@ function Header({ defaultText={promptDetails?.prompt_key} handleChange={handleChange} placeHolder={updatePlaceHolder} + isCoverageLoading={isCoverageLoading} /> @@ -186,13 +188,16 @@ function Header({ type="text" className="prompt-card-action-button" onClick={() => - handleRunBtnClick(promptDetails?.profile_manager, false) + handleRunBtnClick( + PROMPT_RUN_TYPES.RUN_ONE_PROMPT_ALL_LLMS_ONE_DOC, + selectedDoc?.document_id + ) } disabled={ (updateStatus?.promptId === promptDetails?.prompt_id && updateStatus?.status === promptStudioUpdateStatus?.isUpdating) || - disableLlmOrDocChange?.includes(promptDetails?.prompt_id) || + isCoverageLoading || indexDocs?.includes(selectedDoc?.document_id) || isPublicSource || spsLoading[selectedDoc?.document_id] @@ -206,12 +211,16 @@ function Header({ size="small" type="text" className="prompt-card-action-button" - onClick={handleRunBtnClick} + onClick={() => + handleRunBtnClick( + PROMPT_RUN_TYPES.RUN_ONE_PROMPT_ALL_LLMS_ALL_DOCS + ) + } disabled={ (updateStatus?.promptId === promptDetails?.prompt_id && updateStatus?.status === promptStudioUpdateStatus?.isUpdating) || - disableLlmOrDocChange?.includes(promptDetails?.prompt_id) || + isCoverageLoading || indexDocs?.includes(selectedDoc?.document_id) || isPublicSource } @@ -259,7 +268,6 @@ Header.propTypes = { setIsEditingTitle: PropTypes.func.isRequired, expandCard: PropTypes.bool.isRequired, setExpandCard: PropTypes.func.isRequired, - enabledProfiles: PropTypes.array.isRequired, spsLoading: PropTypes.object, handleSpsLoading: PropTypes.func.isRequired, }; diff --git a/frontend/src/components/custom-tools/prompt-card/PrompDnd.jsx b/frontend/src/components/custom-tools/prompt-card/PrompDnd.jsx index 9fbd3ad65..3f1bd2d29 100644 --- a/frontend/src/components/custom-tools/prompt-card/PrompDnd.jsx +++ b/frontend/src/components/custom-tools/prompt-card/PrompDnd.jsx @@ -1,13 +1,15 @@ import PropTypes from "prop-types"; import { useDrag, useDrop } from "react-dnd"; -import { useRef } from "react"; +import { memo, useRef } from "react"; import { NotesCard } from "../notes-card/NotesCard"; import { PromptCard } from "./PromptCard"; import { promptType } from "../../../helpers/GetStaticData"; import { useCustomToolStore } from "../../../store/custom-tool-store"; +import usePromptRun from "../../../hooks/usePromptRun"; +import { usePromptRunStatusStore } from "../../../store/prompt-run-status-store"; -function PromptDnd({ +const PromptDnd = memo(function PromptDnd({ item, index, handleChangePromptCard, @@ -19,6 +21,10 @@ function PromptDnd({ }) { const ref = useRef(null); const { isSimplePromptStudio } = useCustomToolStore(); + const { handlePromptRunRequest } = usePromptRun(); + const promptRunStatus = usePromptRunStatusStore( + (state) => state?.promptRunStatus?.[item?.prompt_id] || {} + ); const [, drop] = useDrop({ accept: "PROMPT_CARD", @@ -34,6 +40,7 @@ function PromptDnd({ isDragging: monitor.isDragging(), }), }); + drag(drop(ref)); return ( @@ -47,6 +54,8 @@ function PromptDnd({ promptOutputs={outputs} enforceTypeList={enforceTypeList} setUpdatedPromptsCopy={setUpdatedPromptsCopy} + handlePromptRunRequest={handlePromptRunRequest} + promptRunStatus={promptRunStatus} /> )} {item.prompt_type === promptType.notes && ( @@ -60,7 +69,9 @@ function PromptDnd({ )} ); -} +}); + +PromptDnd.displayName = "PromptDnd"; PromptDnd.propTypes = { item: PropTypes.object.isRequired, diff --git a/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx b/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx index a8063e7e8..62b53147f 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx +++ b/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx @@ -1,525 +1,221 @@ import PropTypes from "prop-types"; -import { useEffect, useState } from "react"; +import { memo, useEffect, useState } from "react"; import { - generateUUID, - pollForCompletion, + PROMPT_RUN_API_STATUSES, promptStudioUpdateStatus, } from "../../../helpers/GetStaticData"; -import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate"; -import { useExceptionHandler } from "../../../hooks/useExceptionHandler"; import { useAlertStore } from "../../../store/alert-store"; import { useCustomToolStore } from "../../../store/custom-tool-store"; -import { useSessionStore } from "../../../store/session-store"; import { useSocketCustomToolStore } from "../../../store/socket-custom-tool"; import { OutputForDocModal } from "../output-for-doc-modal/OutputForDocModal"; import usePostHogEvents from "../../../hooks/usePostHogEvents"; import { PromptCardItems } from "./PromptCardItems"; import "./PromptCard.css"; -import usePromptOutput from "../../../hooks/usePromptOutput"; import { handleUpdateStatus } from "./constants"; - -let promptRunApiSps; -try { - promptRunApiSps = - require("../../../plugins/simple-prompt-studio/helper").promptRunApiSps; -} catch { - // The component will remain null of it is not available -} - -function PromptCard({ - promptDetails, - handleChangePromptCard, - handleDelete, - updatePlaceHolder, - promptOutputs, - enforceTypeList, - setUpdatedPromptsCopy, -}) { - const [promptDetailsState, setPromptDetailsState] = useState({}); - const [isPromptDetailsStateUpdated, setIsPromptDetailsStateUpdated] = - useState(false); - const [updateStatus, setUpdateStatus] = useState({ - promptId: null, - status: null, - }); - const [isRunLoading, setIsRunLoading] = useState({}); - const [promptKey, setPromptKey] = useState(""); - const [promptText, setPromptText] = useState(""); - const [selectedLlmProfileId, setSelectedLlmProfileId] = useState(null); - const [coverage, setCoverage] = useState({}); - const [coverageTotal, setCoverageTotal] = useState(0); - const [isCoverageLoading, setIsCoverageLoading] = useState(false); - const [openOutputForDoc, setOpenOutputForDoc] = useState(false); - const [progressMsg, setProgressMsg] = useState({}); - const [docOutputs, setDocOutputs] = useState([]); - const [timers, setTimers] = useState({}); // Prompt run timer - const [spsLoading, setSpsLoading] = useState({}); - const { - llmProfiles, - selectedDoc, - listOfDocs, - updateCustomTool, - details, - defaultLlmProfile, - disableLlmOrDocChange, - summarizeIndexStatus, - singlePassExtractMode, - isSinglePassExtractLoading, - isSimplePromptStudio, - } = useCustomToolStore(); - const { messages } = useSocketCustomToolStore(); - const { sessionDetails } = useSessionStore(); - const { setAlertDetails } = useAlertStore(); - const axiosPrivate = useAxiosPrivate(); - const handleException = useExceptionHandler(); - const { setPostHogCustomEvent } = usePostHogEvents(); - const { updatePromptOutputState } = usePromptOutput(); - - useEffect(() => { - if ( - isPromptDetailsStateUpdated || - !Object.keys(promptDetails || {})?.length - ) - return; - setPromptDetailsState(promptDetails); - setIsPromptDetailsStateUpdated(true); - }, [promptDetails]); - - useEffect(() => { - // Find the latest message that matches the criteria - const msg = [...messages] - .reverse() - .find( - (item) => - (item?.component?.prompt_id === promptDetailsState?.prompt_id || - item?.component?.prompt_key === promptKey) && - (item?.level === "INFO" || item?.level === "ERROR") - ); - - // If no matching message is found, return early - if (!msg) { - return; - } - - // Set the progress message state with the found message - setProgressMsg({ - message: msg?.message || "", - level: msg?.level || "INFO", +// import { usePromptRunStatusStore } from "../../../store/prompt-run-status-store"; + +const PromptCard = memo( + ({ + promptDetails, + handleChangePromptCard, + handleDelete, + updatePlaceHolder, + promptOutputs, + enforceTypeList, + setUpdatedPromptsCopy, + handlePromptRunRequest, + promptRunStatus, + }) => { + const [promptDetailsState, setPromptDetailsState] = useState({}); + const [isPromptDetailsStateUpdated, setIsPromptDetailsStateUpdated] = + useState(false); + const [updateStatus, setUpdateStatus] = useState({ + promptId: null, + status: null, }); - }, [messages]); - - useEffect(() => { - setSelectedLlmProfileId( - promptDetailsState?.profile_manager || llmProfiles[0]?.profile_id - ); - }, [promptDetailsState]); - - useEffect(() => { - resetInfoMsgs(); - }, [ - selectedLlmProfileId, - selectedDoc, - listOfDocs, - singlePassExtractMode, - isSinglePassExtractLoading, - defaultLlmProfile, - ]); - - useEffect(() => { - let listOfIds = [...disableLlmOrDocChange]; - const promptId = promptDetailsState?.prompt_id; - const isIncluded = listOfIds.includes(promptId); - - const isDocLoading = docOutputs?.some( - (doc) => doc?.key?.startsWith(`${promptId}__`) && doc?.isLoading - ); - - if ((isIncluded && isDocLoading) || (!isIncluded && !isDocLoading)) { - return; - } - - if (isIncluded && !isDocLoading) { - listOfIds = listOfIds.filter((item) => item !== promptId); - } - - if (!isIncluded && isDocLoading) { - listOfIds.push(promptId); - } - - updateCustomTool({ disableLlmOrDocChange: listOfIds }); - }, [docOutputs]); - - useEffect(() => { - if (isCoverageLoading && coverageTotal === listOfDocs?.length) { - setIsCoverageLoading(false); - setCoverageTotal(0); - } - }, [coverageTotal]); - - const resetInfoMsgs = () => { - setProgressMsg({}); // Reset Progress Message - }; - - useEffect(() => { - const isProfilePresent = llmProfiles?.some( - (profile) => profile?.profile_id === defaultLlmProfile - ); - - // If selectedLlmProfileId is not present, set it to null - if (!isProfilePresent) { - setSelectedLlmProfileId(null); - } - }, [llmProfiles]); - - const handleChange = async ( - event, - promptId, - dropdownItem, - isUpdateStatus = false - ) => { - let name = ""; - let value = ""; - if (dropdownItem?.length) { - name = dropdownItem; - value = event; - } else { - name = event.target.name; - value = event.target.value; - } - - const prevPromptDetailsState = { ...promptDetailsState }; - - const updatedPromptDetailsState = { ...promptDetailsState }; - updatedPromptDetailsState[name] = value; - - handleUpdateStatus( - isUpdateStatus, - promptId, - promptStudioUpdateStatus.isUpdating, - setUpdateStatus - ); - setPromptDetailsState(updatedPromptDetailsState); - return handleChangePromptCard(name, value, promptId) - .then((res) => { - const data = res?.data; - setUpdatedPromptsCopy((prev) => { - prev[promptId] = data; - return prev; - }); - handleUpdateStatus( - isUpdateStatus, - promptId, - promptStudioUpdateStatus.done, - setUpdateStatus + const [promptKey, setPromptKey] = useState(""); + const [promptText, setPromptText] = useState(""); + const [selectedLlmProfileId, setSelectedLlmProfileId] = useState(null); + + const [isCoverageLoading, setIsCoverageLoading] = useState(false); + const [openOutputForDoc, setOpenOutputForDoc] = useState(false); + const [progressMsg, setProgressMsg] = useState({}); + const [spsLoading, setSpsLoading] = useState({}); + const { llmProfiles, selectedDoc, details, summarizeIndexStatus } = + useCustomToolStore(); + const { messages } = useSocketCustomToolStore(); + const { setAlertDetails } = useAlertStore(); + const { setPostHogCustomEvent } = usePostHogEvents(); + + useEffect(() => { + if ( + isPromptDetailsStateUpdated || + !Object.keys(promptDetails || {})?.length + ) + return; + setPromptDetailsState(promptDetails); + setIsPromptDetailsStateUpdated(true); + }, [promptDetails]); + + useEffect(() => { + // Find the latest message that matches the criteria + const msg = [...messages] + .reverse() + .find( + (item) => + (item?.component?.prompt_id === promptDetailsState?.prompt_id || + item?.component?.prompt_key === promptKey) && + (item?.level === "INFO" || item?.level === "ERROR") ); - }) - .catch(() => { - handleUpdateStatus(isUpdateStatus, promptId, null, setUpdateStatus); - setPromptDetailsState(prevPromptDetailsState); - }) - .finally(() => { - if (isUpdateStatus) { - setTimeout(() => { - handleUpdateStatus(true, promptId, null, setUpdateStatus); - }, 3000); - } - }); - }; - - // Function to update loading state for a specific document and profile - const handleIsRunLoading = (docId, profileId, isLoading) => { - setIsRunLoading((prevLoadingProfiles) => ({ - ...prevLoadingProfiles, - [`${docId}_${profileId}`]: isLoading, - })); - }; - - const handleSelectDefaultLLM = (llmProfileId) => { - setSelectedLlmProfileId(llmProfileId); - handleChange( - llmProfileId, - promptDetailsState?.prompt_id, - "profile_manager" - ); - }; - const handleTypeChange = (value) => { - handleChange(value, promptDetailsState?.prompt_id, "enforce_type", true); - }; + // If no matching message is found, return early + if (!msg) { + return; + } - const handleDocOutputs = (docId, promptId, profileId, isLoading, output) => { - if (isSimplePromptStudio) { - return; - } - setDocOutputs((prev) => { - const updatedDocOutputs = [...prev]; - const key = `${promptId}__${docId}__${profileId}`; - // Update the entry for the provided docId with isLoading and output - const newData = { - key, - isLoading, - output, - }; - const index = updatedDocOutputs.findIndex((item) => item.key === key); + // Set the progress message state with the found message + setProgressMsg({ + message: msg?.message || "", + level: msg?.level || "INFO", + }); + }, [messages]); - if (index !== -1) { - // Update the existing object - updatedDocOutputs[index] = newData; - } else { - // Append the new object - updatedDocOutputs.push(newData); - } - return updatedDocOutputs; - }); - }; + useEffect(() => { + setSelectedLlmProfileId( + promptDetailsState?.profile_manager || llmProfiles[0]?.profile_id + ); + }, [promptDetailsState]); - const handleSpsLoading = (docId, isLoadingStatus) => { - setSpsLoading((prev) => ({ - ...prev, - [docId]: isLoadingStatus, - })); - }; + useEffect(() => { + const promptRunStatusKeys = Object.keys(promptRunStatus); - // Generate the result for the currently selected document - const handleRun = ( - profileManagerId, - coverAllDoc = true, - selectedLlmProfiles = [], - runAllLLM = false - ) => { - try { - setPostHogCustomEvent("ps_prompt_run", { - info: "Click on 'Run Prompt' button (Multi Pass)", - }); - } catch (err) { - // If an error occurs while setting custom posthog event, ignore it and continue - } + const coverageLoading = promptRunStatusKeys.some( + (key) => promptRunStatus[key] === PROMPT_RUN_API_STATUSES.RUNNING + ); + setIsCoverageLoading(coverageLoading); + }, [promptRunStatus]); - const validateInputs = ( - profileManagerId, - selectedLlmProfiles, - coverAllDoc + const handleChange = async ( + event, + promptId, + dropdownItem, + isUpdateStatus = false ) => { - if ( - !profileManagerId && - !promptDetailsState?.profile_manager?.length && - !(!coverAllDoc && selectedLlmProfiles?.length > 0) && - !isSimplePromptStudio - ) { - setAlertDetails({ - type: "error", - content: "LLM Profile is not selected", - }); - return true; + let name = ""; + let value = ""; + if (dropdownItem?.length) { + name = dropdownItem; + value = event; + } else { + name = event.target.name; + value = event.target.value; } - if (!selectedDoc) { - setAlertDetails({ - type: "error", - content: "Document not selected", - }); - return true; - } + const prevPromptDetailsState = { ...promptDetailsState }; - if (!promptKey) { - setAlertDetails({ - type: "error", - content: "Prompt key cannot be empty", - }); - return true; - } + const updatedPromptDetailsState = { ...promptDetailsState }; + updatedPromptDetailsState[name] = value; - if (!promptText) { - setAlertDetails({ - type: "error", - content: "Prompt cannot be empty", + handleUpdateStatus( + isUpdateStatus, + promptId, + promptStudioUpdateStatus.isUpdating, + setUpdateStatus + ); + setPromptDetailsState(updatedPromptDetailsState); + return handleChangePromptCard(name, value, promptId) + .then((res) => { + const data = res?.data; + setUpdatedPromptsCopy((prev) => { + prev[promptId] = data; + return prev; + }); + handleUpdateStatus( + isUpdateStatus, + promptId, + promptStudioUpdateStatus.done, + setUpdateStatus + ); + }) + .catch(() => { + handleUpdateStatus(isUpdateStatus, promptId, null, setUpdateStatus); + setPromptDetailsState(prevPromptDetailsState); + }) + .finally(() => { + if (isUpdateStatus) { + setTimeout(() => { + handleUpdateStatus(true, promptId, null, setUpdateStatus); + }, 3000); + } }); - return true; - } - - return false; }; - if (validateInputs(profileManagerId, selectedLlmProfiles, coverAllDoc)) { - return; - } - setIsCoverageLoading(true); - setCoverageTotal(0); - resetInfoMsgs(); + const handleSelectDefaultLLM = (llmProfileId) => { + setSelectedLlmProfileId(llmProfileId); + handleChange( + llmProfileId, + promptDetailsState?.prompt_id, + "profile_manager" + ); + }; - const docId = selectedDoc?.document_id; - const isSummaryIndexed = [...summarizeIndexStatus].find( - (item) => item?.docId === docId && item?.isIndexed === true - ); + const handleTypeChange = (value) => { + handleChange(value, promptDetailsState?.prompt_id, "enforce_type", true); + }; - if ( - !isSummaryIndexed && - details?.summarize_as_source && - details?.summarize_llm_profile - ) { - // Summary needs to be indexed before running the prompt - handleIsRunLoading(selectedDoc?.document_id, selectedLlmProfileId, false); - setCoverageTotal(1); - handleCoverage(selectedLlmProfileId); - setAlertDetails({ - type: "error", - content: `Summary needs to be indexed before running the prompt - ${selectedDoc?.document_name}.`, - }); - return; - } + const handleSpsLoading = (docId, isLoadingStatus) => { + setSpsLoading((prev) => ({ + ...prev, + [docId]: isLoadingStatus, + })); + }; - if (runAllLLM) { - let selectedProfiles = llmProfiles; - if (selectedLlmProfiles?.length) { - selectedProfiles = llmProfiles.filter((profile) => - selectedLlmProfiles.includes(profile?.profile_id) - ); + // Generate the result for the currently selected document + const handleRun = (promptRunType, promptId, profileId, documentId) => { + try { + setPostHogCustomEvent("ps_prompt_run", { + info: "Click on 'Run Prompt' button (Multi Pass)", + }); + } catch (err) { + // If an error occurs while setting custom posthog event, ignore it and continue } - for (const profile of selectedProfiles) { - handleDocOutputs( - docId, - promptDetailsState?.prompt_id, - profile?.profile_id, - true, - null - ); - setIsCoverageLoading(true); - - handleIsRunLoading(selectedDoc?.document_id, profile?.profile_id, true); - - const startTime = Date.now(); - handleRunApiRequest(docId, profile?.profile_id) - .then((res) => { - const data = res?.data || []; - const value = data[0]?.output; - - updatePromptOutputState(data, false); - handleDocOutputs( - docId, - promptDetailsState?.prompt_id, - profile?.profile_id, - false, - value - ); + const validateInputs = () => { + if (!selectedDoc) { + setAlertDetails({ + type: "error", + content: "Document not selected", + }); + return true; + } - updateDocCoverage( - promptDetailsState?.prompt_id, - profile?.profile_id, - docId - ); - }) - .catch((err) => { - handleDocOutputs( - docId, - promptDetailsState?.prompt_id, - profile?.profile_id, - false, - null - ); - setAlertDetails( - handleException(err, `Failed to generate output for ${docId}`) - ); - }) - .finally(() => { - handleIsRunLoading(docId, profile?.profile_id, false); - setIsCoverageLoading(false); - const endTime = Date.now(); - updateTimers(startTime, endTime, profile?.profile_id); + if (!promptKey) { + setAlertDetails({ + type: "error", + content: "Prompt key cannot be empty", }); - runCoverageForAllDoc(coverAllDoc, profile.profile_id); - } - } else { - handleIsRunLoading(selectedDoc?.document_id, profileManagerId, true); - handleDocOutputs( - docId, - promptDetailsState?.prompt_id, - profileManagerId, - true, - null - ); - const startTime = Date.now(); - handleRunApiRequest(docId, profileManagerId) - .then((res) => { - const data = res?.data || []; - const value = data[0]?.output; + return true; + } - updatePromptOutputState(data, false); - updateDocCoverage( - promptDetailsState?.prompt_id, - profileManagerId, - docId - ); - handleDocOutputs( - docId, - promptDetailsState?.prompt_id, - profileManagerId, - false, - value - ); - setCoverageTotal(1); - }) - .catch((err) => { - handleIsRunLoading( - selectedDoc?.document_id, - selectedLlmProfileId, - false - ); - handleDocOutputs( - docId, - promptDetailsState?.prompt_id, - profileManagerId, - false, - null - ); - setAlertDetails( - handleException(err, `Failed to generate output for ${docId}`) - ); - }) - .finally(() => { - setIsCoverageLoading(false); - handleIsRunLoading(selectedDoc?.document_id, profileManagerId, false); - const endTime = Date.now(); - updateTimers(startTime, endTime, profileManagerId); - }); - runCoverageForAllDoc(coverAllDoc, profileManagerId); - } - }; + if (!promptText) { + setAlertDetails({ + type: "error", + content: "Prompt cannot be empty", + }); + return true; + } - const updateTimers = (startTime, endTime, profileManagerId) => { - setTimers((prev) => { - prev[profileManagerId] = { - startTime, - endTime, + return false; }; - return prev; - }); - }; - - const runCoverageForAllDoc = (coverAllDoc, profileManagerId) => { - if (coverAllDoc) { - handleCoverage(profileManagerId); - } - }; - - // Get the coverage for all the documents except the one that's currently selected - const handleCoverage = (profileManagerId) => { - const listOfDocsToProcess = [...listOfDocs].filter( - (item) => item?.document_id !== selectedDoc?.document_id - ); - if (listOfDocsToProcess?.length === 0) { - setIsCoverageLoading(false); - return; - } + if (validateInputs()) { + return; + } - let totalCoverageValue = 1; - listOfDocsToProcess.forEach((item) => { - const docId = item?.document_id; + const docId = selectedDoc?.document_id; const isSummaryIndexed = [...summarizeIndexStatus].find( - (indexStatus) => - indexStatus?.docId === docId && indexStatus?.isIndexed === true + (item) => item?.docId === docId && item?.isIndexed === true ); if ( @@ -528,181 +224,54 @@ function PromptCard({ details?.summarize_llm_profile ) { // Summary needs to be indexed before running the prompt - totalCoverageValue++; - setCoverageTotal(totalCoverageValue); setAlertDetails({ type: "error", - content: `Summary needs to be indexed before running the prompt - ${item?.document_name}.`, + content: `Summary needs to be indexed before running the prompt - ${selectedDoc?.document_name}.`, }); return; } - setIsCoverageLoading(true); - handleDocOutputs( - docId, - promptDetailsState?.prompt_id, - profileManagerId, - true, - null - ); - handleRunApiRequest(docId, profileManagerId) - .then((res) => { - const data = res?.data || []; - const outputValue = data[0]?.output; - updateDocCoverage( - promptDetailsState?.prompt_id, - profileManagerId, - docId - ); - handleDocOutputs( - docId, - promptDetailsState?.prompt_id, - profileManagerId, - false, - outputValue - ); - }) - .catch((err) => { - handleDocOutputs( - docId, - promptDetailsState?.prompt_id, - profileManagerId, - false, - null - ); - setAlertDetails( - handleException(err, `Failed to generate output for ${docId}`) - ); - }) - .finally(() => { - totalCoverageValue++; - if (listOfDocsToProcess?.length >= totalCoverageValue) { - setIsCoverageLoading(false); - return; - } - setCoverageTotal(totalCoverageValue); - }); - }); - }; - - const updateDocCoverage = (promptId, profileManagerId, docId) => { - setCoverage((prevCoverage) => { - const keySuffix = `${promptId}_${profileManagerId}`; - const key = singlePassExtractMode ? `singlepass_${keySuffix}` : keySuffix; - - // Create a shallow copy of the previous coverage state - const updatedCoverage = { ...prevCoverage }; - - // If the key exists in the updated coverage object, update the docs_covered array - if (updatedCoverage[key]) { - if (!updatedCoverage[key].docs_covered.includes(docId)) { - updatedCoverage[key].docs_covered = [ - ...updatedCoverage[key].docs_covered, - docId, - ]; - } - } else { - // Otherwise, add the key to the updated coverage object with the new entry - updatedCoverage[key] = { - prompt_id: promptId, - profile_manager: profileManagerId, - docs_covered: [docId], - }; - } - - return updatedCoverage; - }); - }; - - const handleRunApiRequest = async (docId, profileManagerId) => { - const promptId = promptDetailsState?.prompt_id; - const runId = generateUUID(); - const maxWaitTime = 30 * 1000; // 30 seconds - const pollingInterval = 5000; // 5 seconds - - const body = { - document_id: docId, - id: promptId, + handlePromptRunRequest(promptRunType, promptId, profileId, documentId); }; - if (profileManagerId) { - body.profile_manager = profileManagerId; - let url = `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/fetch_response/${details?.tool_id}`; - if (!isSimplePromptStudio) { - body["run_id"] = runId; - } else { - body["sps_id"] = details?.tool_id; - url = promptRunApiSps; - } - - const requestOptions = { - method: "POST", - url, - headers: { - "X-CSRFToken": sessionDetails?.csrfToken, - "Content-Type": "application/json", - }, - data: body, - }; - - const makeApiRequest = (requestOptions) => { - return axiosPrivate(requestOptions); - }; - const startTime = Date.now(); - return pollForCompletion( - startTime, - requestOptions, - maxWaitTime, - pollingInterval, - makeApiRequest - ) - .then((response) => { - return response; - }) - .catch((err) => { - throw err; - }); - } - }; + return ( + <> + + + + ); + } +); - return ( - <> - - - - ); -} +PromptCard.displayName = "PromptCard"; PromptCard.propTypes = { promptDetails: PropTypes.object.isRequired, @@ -712,6 +281,8 @@ PromptCard.propTypes = { promptOutputs: PropTypes.object.isRequired, enforceTypeList: PropTypes.array.isRequired, setUpdatedPromptsCopy: PropTypes.func.isRequired, + handlePromptRunRequest: PropTypes.func.isRequired, + promptRunStatus: PropTypes.object.isRequired, }; export { PromptCard }; diff --git a/frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx b/frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx index 1f8463a9c..e279f76bd 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx +++ b/frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx @@ -31,12 +31,10 @@ try { function PromptCardItems({ promptDetails, enforceTypeList, - isRunLoading, promptKey, setPromptKey, promptText, setPromptText, - coverage, progressMsg, handleRun, handleChange, @@ -48,30 +46,27 @@ function PromptCardItems({ setOpenOutputForDoc, selectedLlmProfileId, handleSelectDefaultLLM, - timers, spsLoading, handleSpsLoading, promptOutputs, + promptRunStatus, }) { const { llmProfiles, selectedDoc, listOfDocs, - disableLlmOrDocChange, - singlePassExtractMode, isSinglePassExtractLoading, indexDocs, isSimplePromptStudio, isPublicSource, adapters, - defaultLlmProfile, } = useCustomToolStore(); const [isEditingPrompt, setIsEditingPrompt] = useState(false); const [isEditingTitle, setIsEditingTitle] = useState(false); const [expandCard, setExpandCard] = useState(true); const [llmProfileDetails, setLlmProfileDetails] = useState([]); const [openIndexProfile, setOpenIndexProfile] = useState(null); - const [coverageCount, setCoverageCount] = useState(0); + const [coverageCount] = useState(0); const [enabledProfiles, setEnabledProfiles] = useState( llmProfiles.map((profile) => profile.profile_id) ); @@ -137,23 +132,10 @@ function PromptCardItems({ ); }; - const getCoverageData = () => { - const profileId = singlePassExtractMode - ? defaultLlmProfile - : selectedLlmProfileId; - const keySuffix = `${promptDetails?.prompt_id}_${profileId}`; - const key = singlePassExtractMode ? `singlepass_${keySuffix}` : keySuffix; - return coverage[key]?.docs_covered?.length || 0; - }; - useEffect(() => { setExpandCard(true); }, [isSinglePassExtractLoading]); - useEffect(() => { - setCoverageCount(getCoverageData()); - }, [singlePassExtractMode, coverage]); - useEffect(() => { getAdapterInfo(adapters); }, [llmProfiles, selectedLlmProfileId, enabledProfiles]); @@ -200,18 +182,14 @@ function PromptCardItems({ handleChange={handleChange} isTextarea={true} placeHolder={updatePlaceHolder} + isCoverageLoading={isCoverageLoading} /> <> {!isSimplePromptStudio && ( <> - +
@@ -328,21 +312,12 @@ function PromptOutput({ {isTableExtraction ? (
) : ( - <> - {isRunLoading[ - `${selectedDoc?.document_id}_${profileId}` - ] ? ( - } /> - ) : ( - -
- -
-
- )} - + )}
@@ -350,12 +325,15 @@ function PromptOutput({ size="small" type="text" className="prompt-card-action-button" - onClick={() => handleRun(profileId, false)} - disabled={ - isRunLoading[ - `${selectedDoc?.document_id}_${profileId}` - ] || isPublicSource + onClick={() => + handleRun( + PROMPT_RUN_TYPES.RUN_ONE_PROMPT_ONE_LLM_ONE_DOC, + promptDetails?.prompt_id, + profileId, + selectedDoc?.document_id + ) } + disabled={isPromptLoading || isPublicSource} > @@ -365,12 +343,15 @@ function PromptOutput({ size="small" type="text" className="prompt-card-action-button" - onClick={() => handleRun(profileId, true)} - disabled={ - isRunLoading[ - `${selectedDoc?.document_id}_${profileId}` - ] || isPublicSource + onClick={() => + handleRun( + PROMPT_RUN_TYPES.RUN_ONE_PROMPT_ONE_LLM_ALL_DOCS, + promptDetails?.prompt_id, + profileId, + null + ) } + disabled={isPromptLoading || isPublicSource} > @@ -389,6 +370,7 @@ function PromptOutput({ enforceType={enforceType} displayLlmProfile={true} promptOutputs={promptOutputs} + promptRunStatus={promptRunStatus} />
@@ -406,20 +388,18 @@ function PromptOutput({ PromptOutput.propTypes = { promptDetails: PropTypes.object.isRequired, - isRunLoading: PropTypes.object, handleRun: PropTypes.func.isRequired, handleSelectDefaultLLM: PropTypes.func.isRequired, selectedLlmProfileId: PropTypes.string, - timers: PropTypes.object.isRequired, - spsLoading: PropTypes.object, llmProfileDetails: PropTypes.array.isRequired, setOpenIndexProfile: PropTypes.func.isRequired, enabledProfiles: PropTypes.array.isRequired, setEnabledProfiles: PropTypes.func.isRequired, isNotSingleLlmProfile: PropTypes.bool.isRequired, setIsIndexOpen: PropTypes.func.isRequired, - enforceType: PropTypes.string.isRequired, + enforceType: PropTypes.string, promptOutputs: PropTypes.object.isRequired, + promptRunStatus: PropTypes.object.isRequired, }; export { PromptOutput }; diff --git a/frontend/src/components/custom-tools/prompt-card/PromptOutputExpandBtn.jsx b/frontend/src/components/custom-tools/prompt-card/PromptOutputExpandBtn.jsx index 97f80fc93..4063c601a 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptOutputExpandBtn.jsx +++ b/frontend/src/components/custom-tools/prompt-card/PromptOutputExpandBtn.jsx @@ -11,6 +11,7 @@ function PromptOutputExpandBtn({ enforceType, displayLlmProfile, promptOutputs, + promptRunStatus, }) { const [openModal, setOpenModal] = useState(false); @@ -34,6 +35,7 @@ function PromptOutputExpandBtn({ enforceType={enforceType} displayLlmProfile={displayLlmProfile} promptOutputs={promptOutputs} + promptRunStatus={promptRunStatus} /> ); @@ -42,9 +44,10 @@ function PromptOutputExpandBtn({ PromptOutputExpandBtn.propTypes = { promptId: PropTypes.string.isRequired, llmProfiles: PropTypes.array.isRequired, - enforceType: PropTypes.string.isRequired, + enforceType: PropTypes.string, displayLlmProfile: PropTypes.bool.isRequired, promptOutputs: PropTypes.object.isRequired, + promptRunStatus: PropTypes.object.isRequired, }; export { PromptOutputExpandBtn }; diff --git a/frontend/src/components/custom-tools/prompt-card/PromptOutputsModal.jsx b/frontend/src/components/custom-tools/prompt-card/PromptOutputsModal.jsx index 2e80ebc03..939277735 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptOutputsModal.jsx +++ b/frontend/src/components/custom-tools/prompt-card/PromptOutputsModal.jsx @@ -22,6 +22,7 @@ function PromptOutputsModal({ enforceType, displayLlmProfile, promptOutputs, + promptRunStatus, }) { const { singlePassExtractMode, selectedDoc } = useCustomToolStore(); const { generatePromptOutputKey } = usePromptOutput(); @@ -45,9 +46,10 @@ function PromptOutputsModal({ {llmProfiles.map((profile, index) => { const profileId = profile?.profile_id; + const docId = selectedDoc?.document_id; const promptOutputKey = generatePromptOutputKey( promptId, - selectedDoc?.document_id, + docId, profileId, singlePassExtractMode, true @@ -87,7 +89,12 @@ function PromptOutputsModal({ pagination={10} /> ) : ( - + )} @@ -105,9 +112,10 @@ PromptOutputsModal.propTypes = { setOpen: PropTypes.func.isRequired, promptId: PropTypes.string.isRequired, llmProfiles: PropTypes.array.isRequired, - enforceType: PropTypes.string.isRequired, + enforceType: PropTypes.string, displayLlmProfile: PropTypes.bool.isRequired, promptOutputs: PropTypes.object.isRequired, + promptRunStatus: PropTypes.object.isRequired, }; export { PromptOutputsModal }; diff --git a/frontend/src/components/custom-tools/prompt-card/PromptRun.jsx b/frontend/src/components/custom-tools/prompt-card/PromptRun.jsx new file mode 100644 index 000000000..b73b5ad1e --- /dev/null +++ b/frontend/src/components/custom-tools/prompt-card/PromptRun.jsx @@ -0,0 +1,77 @@ +import { useEffect } from "react"; +import Cookies from "js-cookie"; +import { usePromptRunQueueStore } from "../../../store/prompt-run-queue-store"; +import usePromptRun from "../../../hooks/usePromptRun"; +import { useCustomToolStore } from "../../../store/custom-tool-store"; +import { usePromptRunStatusStore } from "../../../store/prompt-run-status-store"; + +const MAX_ACTIVE_APIS = 5; +/* + Change this to 'true' to allow persistence of the prompt run state + Right now, this feature cannot be support as the prompt studio details + are not persisted accoss the entire application. +*/ +const PROMPT_RUN_STATE_PERSISTENCE = false; + +function PromptRun() { + const activeApis = usePromptRunQueueStore((state) => state.activeApis); + const queue = usePromptRunQueueStore((state) => state.queue); + const setPromptRunQueue = usePromptRunQueueStore( + (state) => state.setPromptRunQueue + ); + const { runPrompt, syncPromptRunApisAndStatus } = usePromptRun(); + const promptRunStatus = usePromptRunStatusStore( + (state) => state.promptRunStatus + ); + const updateCustomTool = useCustomToolStore( + (state) => state.updateCustomTool + ); + + useEffect(() => { + // Retrieve queue from cookies on component load + const queueData = Cookies.get("promptRunQueue"); + if (queueData && JSON.parse(queueData)?.length) { + const promptApis = JSON.parse(queueData); + syncPromptRunApisAndStatus(promptApis); + } + + // Setup the beforeunload event handler to store queue in cookies + const handleBeforeUnload = () => { + if (!PROMPT_RUN_STATE_PERSISTENCE) return; + const { queue } = usePromptRunQueueStore.getState(); // Get the latest state dynamically + if (queue?.length) { + Cookies.set("promptRunQueue", JSON.stringify(queue), { + expires: 5 / 1440, // Expire in 5 minutes + }); + } + }; + + window.addEventListener("beforeunload", handleBeforeUnload); + + return () => { + window.removeEventListener("beforeunload", handleBeforeUnload); // Clean up event listener + }; + }, [syncPromptRunApisAndStatus]); + + useEffect(() => { + if (!queue?.length || activeApis >= MAX_ACTIVE_APIS) return; + + const canRunApis = MAX_ACTIVE_APIS - activeApis; + const apisToRun = queue.slice(0, canRunApis); + + setPromptRunQueue({ + activeApis: activeApis + apisToRun.length, + queue: queue.slice(apisToRun.length), + }); + runPrompt(apisToRun); + }, [activeApis, queue, setPromptRunQueue, runPrompt]); + + useEffect(() => { + const isMultiPassExtractLoading = !!Object.keys(promptRunStatus).length; + updateCustomTool({ isMultiPassExtractLoading }); + }, [promptRunStatus, updateCustomTool]); + + return null; +} + +export { PromptRun }; diff --git a/frontend/src/components/custom-tools/prompt-card/PromptRunCost.jsx b/frontend/src/components/custom-tools/prompt-card/PromptRunCost.jsx index 01438442f..e91a9837a 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptRunCost.jsx +++ b/frontend/src/components/custom-tools/prompt-card/PromptRunCost.jsx @@ -16,8 +16,8 @@ const PromptRunCost = memo(({ tokenUsage, isLoading }) => { PromptRunCost.displayName = "PromptRunCost"; PromptRunCost.propTypes = { - tokenUsage: PropTypes.number, - isLoading: PropTypes.bool.isRequired, + tokenUsage: PropTypes.object, + isLoading: PropTypes.bool, }; export { PromptRunCost }; diff --git a/frontend/src/components/custom-tools/prompt-card/PromptRunTimer.jsx b/frontend/src/components/custom-tools/prompt-card/PromptRunTimer.jsx index 0e2e2ef63..c0da7b152 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptRunTimer.jsx +++ b/frontend/src/components/custom-tools/prompt-card/PromptRunTimer.jsx @@ -2,18 +2,18 @@ import { memo } from "react"; import PropTypes from "prop-types"; const PromptRunTimer = memo(({ timer, isLoading }) => { - if (!timer?.startTime || !timer?.endTime || isLoading) { + if (!timer || isLoading) { return "Time: 0s"; } - return `Time: ${Math.floor((timer?.endTime - timer?.startTime) / 1000)}s`; + return `Time: ${timer}s`; }); PromptRunTimer.displayName = "PromptRunTimer"; PromptRunTimer.propTypes = { - timer: PropTypes.object, - isLoading: PropTypes.bool.isRequired, + timer: PropTypes.number, + isLoading: PropTypes.bool, }; export { PromptRunTimer }; diff --git a/frontend/src/components/custom-tools/prompt-card/RunAllPrompts.jsx b/frontend/src/components/custom-tools/prompt-card/RunAllPrompts.jsx new file mode 100644 index 000000000..cf1cc5b27 --- /dev/null +++ b/frontend/src/components/custom-tools/prompt-card/RunAllPrompts.jsx @@ -0,0 +1,46 @@ +import { PlayCircleFilled, PlayCircleOutlined } from "@ant-design/icons"; +import { Button, Space, Tooltip } from "antd"; +import { useCustomToolStore } from "../../../store/custom-tool-store"; +import usePromptRun from "../../../hooks/usePromptRun"; +import { PROMPT_RUN_TYPES } from "../../../helpers/GetStaticData"; + +function RunAllPrompts() { + const { selectedDoc, isMultiPassExtractLoading, isPublicSource } = + useCustomToolStore(); + const { handlePromptRunRequest } = usePromptRun(); + + return ( + + +