Skip to content

Commit

Permalink
FIX: Optimized the Re-rendering of the Prompt Card Component (#696)
Browse files Browse the repository at this point in the history
* Implemented prompt output store along with other prompt output API related optimizations

* Modified the backend response for prompt run API

* Modifications in the components after optimization of the promot output API call

* Minor fix related to single pass extraction outputs

* Optimized the re-rendering of the prompt cards
  • Loading branch information
tahierhussain authored Sep 16, 2024
1 parent 2507592 commit 70bdcf4
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 155 deletions.
124 changes: 22 additions & 102 deletions frontend/src/components/custom-tools/document-parser/DocumentParser.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";

import "./DocumentParser.css";
import {
promptStudioUpdateStatus,
promptType,
} from "../../../helpers/GetStaticData";
import { promptType } from "../../../helpers/GetStaticData";
import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate";
import { useAlertStore } from "../../../store/alert-store";
import { useCustomToolStore } from "../../../store/custom-tool-store";
Expand Down Expand Up @@ -36,11 +33,8 @@ function DocumentParser({
scrollToBottom,
setScrollToBottom,
}) {
const [updateStatus, setUpdateStatus] = useState({
promptId: null,
status: null,
});
const [enforceTypeList, setEnforceTypeList] = useState([]);
const [updatedPromptsCopy, setUpdatedPromptsCopy] = useState({});
const bottomRef = useRef(null);
const { details, isSimplePromptStudio, updateCustomTool, getDropdownItems } =
useCustomToolStore();
Expand All @@ -56,6 +50,22 @@ function DocumentParser({
return { value: outputTypeData[item] };
});
setEnforceTypeList(dropdownList1);

return () => {
// Set the prompts with updated changes when the component is unmounted
const modifiedDetails = { ...details };
const modifiedPrompts = [...(modifiedDetails?.prompts || [])].map(
(item) => {
const itemPromptId = item?.prompt_id;
if (itemPromptId && updatedPromptsCopy[itemPromptId]) {
return updatedPromptsCopy[itemPromptId];
}
return item;
}
);
modifiedDetails["prompts"] = modifiedPrompts;
updateCustomTool({ details: modifiedDetails });
};
}, []);

useEffect(() => {
Expand All @@ -70,45 +80,14 @@ function DocumentParser({
return `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/prompt/${urlPath}`;
};

const handleChange = async (
event,
promptId,
dropdownItem,
isUpdateStatus = false,
isPromptUpdate = false
) => {
const handleChangePromptCard = async (name, value, promptId) => {
const promptsAndNotes = details?.prompts || [];
let name = "";
let value = "";
if (dropdownItem?.length) {
name = dropdownItem;
value = event;
} else {
name = event.target.name;
value = event.target.value;
}

if (name === "prompt_key") {
// Return if the prompt or the prompt key is empty
if (!value) {
return;
}
if (!isValidJsonKey(value)) {
handleUpdateStatus(
isUpdateStatus,
promptId,
promptStudioUpdateStatus.validationError
);
return;
}
}

function isValidJsonKey(key) {
// Check for Prompt-Key
// Allowed case, contains alphanumeric characters and underscores,
// and doesn't start with a number.
const regex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
return regex.test(key);
}

const index = promptsAndNotes.findIndex(
Expand Down Expand Up @@ -143,72 +122,13 @@ function DocumentParser({
data: body,
};

const modifiedDetails = { ...details };
const modifiedPrompts = [...(modifiedDetails?.prompts || [])].map(
(item) => {
if (item?.prompt_id === promptId) {
return {
...item,
[name]: value, // Update the specific field instantly
};
}
return item;
}
);
modifiedDetails["prompts"] = modifiedPrompts;
updateCustomTool({ details: modifiedDetails });

handleUpdateStatus(
isUpdateStatus,
promptId,
promptStudioUpdateStatus.isUpdating
);

return axiosPrivate(requestOptions)
.then((res) => {
const data = res?.data;
const modifiedPrompts = [...(modifiedDetails?.prompts || [])].map(
(item) => {
if (item?.prompt_id === data?.prompt_id) {
return data;
}
return item;
}
);
modifiedDetails["prompts"] = modifiedPrompts;
if (!isPromptUpdate) {
updateCustomTool({ details: modifiedDetails });
}
handleUpdateStatus(
isUpdateStatus,
promptId,
promptStudioUpdateStatus.done
);
})
.then((res) => res)
.catch((err) => {
setAlertDetails(handleException(err, "Failed to update"));
updateCustomTool({ details });
handleUpdateStatus(isUpdateStatus, promptId, null);
})
.finally(() => {
if (isUpdateStatus) {
setTimeout(() => {
handleUpdateStatus(true, promptId, null);
}, 3000);
}
});
};

const handleUpdateStatus = (isUpdate, promptId, value) => {
if (!isUpdate) {
return;
}
setUpdateStatus({
promptId: promptId,
status: value,
});
};

const handleDelete = (promptId) => {
let url = promptUrl(promptId + "/");
if (isSimplePromptStudio) {
Expand Down Expand Up @@ -351,12 +271,12 @@ function DocumentParser({
<PromptDnd
item={item}
index={index}
handleChange={handleChange}
handleChangePromptCard={handleChangePromptCard}
handleDelete={handleDelete}
updateStatus={updateStatus}
moveItem={moveItem}
outputs={getPromptOutputs(item?.prompt_id)}
enforceTypeList={enforceTypeList}
setUpdatedPromptsCopy={setUpdatedPromptsCopy}
/>
<div ref={bottomRef} className="doc-parser-pad-bottom" />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function EditableText({

useEffect(() => {
setText(defaultText);
}, []);
}, [defaultText]);

useEffect(() => {
// Attach the event listener when the component mounts
Expand Down
90 changes: 77 additions & 13 deletions frontend/src/components/custom-tools/notes-card/NotesCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,96 @@ import {
import { Button, Card, Col, Collapse, Row, Space, Tag, Tooltip } from "antd";
import PropTypes from "prop-types";
import "./NotesCard.css";
import { useState } from "react";
import { useEffect, useState } from "react";

import { EditableText } from "../editable-text/EditableText";
import { ConfirmModal } from "../../widgets/confirm-modal/ConfirmModal";
import { promptStudioUpdateStatus } from "../../../helpers/GetStaticData";
import { ExpandCardBtn } from "../prompt-card/ExpandCardBtn";
import { handleUpdateStatus } from "../prompt-card/constants";

function NotesCard({
details,
handleChange,
promptDetails,
handleChangePromptCard,
handleDelete,
updateStatus,
updatePlaceHolder,
setUpdatedPromptsCopy,
}) {
const [promptDetailsState, setPromptDetailsState] = useState({});
const [isPromptDetailsStateUpdated, setIsPromptDetailsStateUpdated] =
useState(false);
const [updateStatus, setUpdateStatus] = useState({
promptId: null,
status: null,
});
const [promptKey, setPromptKey] = useState("");
const [promptText, setPromptText] = useState("");
const [isEditingTitle, setIsEditingTitle] = useState(false);
const [isEditingNote, setIsEditingNote] = useState(false);
const [expandCard, setExpandCard] = useState(true);

useEffect(() => {
if (
isPromptDetailsStateUpdated ||
!Object.keys(promptDetails || {})?.length
)
return;
setPromptDetailsState(promptDetails);
setIsPromptDetailsStateUpdated(true);
}, [promptDetails]);

const enableEdit = (event) => {
event.stopPropagation();
setIsEditingTitle(true);
setIsEditingNote(true);
};

const handleChange = async (
value,
promptId,
name,
isUpdateStatus = false
) => {
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
);
})
.catch(() => {
handleUpdateStatus(isUpdateStatus, promptId, null, setUpdateStatus);
setPromptDetailsState(prevPromptDetailsState);
})
.finally(() => {
if (isUpdateStatus) {
setTimeout(() => {
handleUpdateStatus(true, promptId, null, setUpdateStatus);
}, 3000);
}
});
};

return (
<Card className="tool-ide-notes-card">
<Space
Expand All @@ -47,14 +111,14 @@ function NotesCard({
setIsEditing={setIsEditingTitle}
text={promptKey}
setText={setPromptKey}
promptId={details?.prompt_id}
defaultText={details?.prompt_key}
promptId={promptDetailsState?.prompt_id}
defaultText={promptDetailsState?.prompt_key}
handleChange={handleChange}
placeHolder={updatePlaceHolder}
/>
</Col>
<Col span={6} className="display-flex-right">
{updateStatus?.promptId === details?.prompt_id && (
{updateStatus?.promptId === promptDetailsState?.prompt_id && (
<>
{updateStatus?.status ===
promptStudioUpdateStatus.isUpdating && (
Expand Down Expand Up @@ -92,7 +156,7 @@ function NotesCard({
</Button>
</Tooltip>
<ConfirmModal
handleConfirm={() => handleDelete(details?.prompt_id)}
handleConfirm={() => handleDelete(promptDetailsState?.prompt_id)}
content="The note will be permanently deleted."
>
<Tooltip title="Delete">
Expand All @@ -114,8 +178,8 @@ function NotesCard({
setIsEditing={setIsEditingNote}
text={promptText}
setText={setPromptText}
promptId={details?.prompt_id}
defaultText={details?.prompt}
promptId={promptDetailsState?.prompt_id}
defaultText={promptDetailsState?.prompt}
handleChange={handleChange}
isTextarea={true}
placeHolder={updatePlaceHolder}
Expand All @@ -128,11 +192,11 @@ function NotesCard({
}

NotesCard.propTypes = {
details: PropTypes.object.isRequired,
handleChange: PropTypes.func.isRequired,
promptDetails: PropTypes.object.isRequired,
handleChangePromptCard: PropTypes.func.isRequired,
handleDelete: PropTypes.func.isRequired,
updateStatus: PropTypes.object.isRequired,
updatePlaceHolder: PropTypes.string,
setUpdatedPromptsCopy: PropTypes.func.isRequired,
};

export { NotesCard };
Loading

0 comments on commit 70bdcf4

Please sign in to comment.