From 60fecd5c43656a37119230a042ee8f60c46e4b44 Mon Sep 17 00:00:00 2001 From: jagadeeswaran-zipstack Date: Wed, 11 Dec 2024 16:17:38 +0530 Subject: [PATCH 1/3] added highlight feature in prompt studio --- ...romptstudiooutputmanager_highlight_data.py | 20 + .../prompt_studio_output_manager_v2/models.py | 3 + .../output_manager_helper.py | 7 + .../document-manager/DocumentManager.jsx | 4 +- .../custom-tools/pdf-viewer/PdfViewer.jsx | 51 ++- .../custom-tools/prompt-card/PromptCard.css | 13 + .../custom-tools/prompt-card/PromptCard.jsx | 26 +- .../prompt-card/PromptCardItems.jsx | 13 +- .../custom-tools/prompt-card/PromptOutput.jsx | 369 ++++++++++-------- frontend/src/store/custom-tool-store.js | 1 + 10 files changed, 313 insertions(+), 194 deletions(-) create mode 100644 backend/prompt_studio/prompt_studio_output_manager_v2/migrations/0002_promptstudiooutputmanager_highlight_data.py diff --git a/backend/prompt_studio/prompt_studio_output_manager_v2/migrations/0002_promptstudiooutputmanager_highlight_data.py b/backend/prompt_studio/prompt_studio_output_manager_v2/migrations/0002_promptstudiooutputmanager_highlight_data.py new file mode 100644 index 000000000..a3c14683b --- /dev/null +++ b/backend/prompt_studio/prompt_studio_output_manager_v2/migrations/0002_promptstudiooutputmanager_highlight_data.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.1 on 2024-12-05 10:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("prompt_studio_output_manager_v2", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="promptstudiooutputmanager", + name="highlight_data", + field=models.JSONField( + blank=True, db_comment="Field to store highlight data", null=True + ), + ), + ] diff --git a/backend/prompt_studio/prompt_studio_output_manager_v2/models.py b/backend/prompt_studio/prompt_studio_output_manager_v2/models.py index ab946820b..70efd1746 100644 --- a/backend/prompt_studio/prompt_studio_output_manager_v2/models.py +++ b/backend/prompt_studio/prompt_studio_output_manager_v2/models.py @@ -27,6 +27,9 @@ class PromptStudioOutputManager(BaseModel): challenge_data = models.JSONField( db_comment="Field to store challenge data", editable=True, null=True, blank=True ) + highlight_data = models.JSONField( + db_comment="Field to store highlight data", editable=True, null=True, blank=True + ) eval_metrics = models.JSONField( db_column="eval_metrics", null=False, diff --git a/backend/prompt_studio/prompt_studio_output_manager_v2/output_manager_helper.py b/backend/prompt_studio/prompt_studio_output_manager_v2/output_manager_helper.py index a82ed95bf..e7dd78468 100644 --- a/backend/prompt_studio/prompt_studio_output_manager_v2/output_manager_helper.py +++ b/backend/prompt_studio/prompt_studio_output_manager_v2/output_manager_helper.py @@ -60,6 +60,7 @@ def update_or_create_prompt_output( tool: CustomTool, context: str, challenge_data: Optional[dict[str, Any]], + highlight_data: Optional[dict[str, Any]], ) -> PromptStudioOutputManager: """Handles creating or updating a single prompt output and returns the instance.""" @@ -76,6 +77,7 @@ def update_or_create_prompt_output( "eval_metrics": eval_metrics, "context": context, "challenge_data": challenge_data, + "highlight_data": highlight_data, }, ) ) @@ -97,6 +99,7 @@ def update_or_create_prompt_output( "eval_metrics": eval_metrics, "context": context, "challenge_data": challenge_data, + "highlight_data": highlight_data, } PromptStudioOutputManager.objects.filter( document_manager=document_manager, @@ -118,6 +121,7 @@ def update_or_create_prompt_output( serialized_data: list[dict[str, Any]] = [] context = metadata.get("context") challenge_data = metadata.get("challenge_data") + highlight_data = metadata.get("highlight_data") if not prompts: return serialized_data @@ -134,6 +138,8 @@ def update_or_create_prompt_output( if not is_single_pass_extract: context = context.get(prompt.prompt_key) + if highlight_data: + highlight_data = highlight_data.get(prompt.prompt_key) if challenge_data: challenge_data = challenge_data.get(prompt.prompt_key) @@ -156,6 +162,7 @@ def update_or_create_prompt_output( tool=tool, context=json.dumps(context), challenge_data=challenge_data, + highlight_data=highlight_data, ) # Serialize the instance diff --git a/frontend/src/components/custom-tools/document-manager/DocumentManager.jsx b/frontend/src/components/custom-tools/document-manager/DocumentManager.jsx index 0435a0101..057bd8f35 100644 --- a/frontend/src/components/custom-tools/document-manager/DocumentManager.jsx +++ b/frontend/src/components/custom-tools/document-manager/DocumentManager.jsx @@ -95,11 +95,13 @@ function DocumentManager({ generateIndex, handleUpdateTool, handleDocChange }) { isSimplePromptStudio, isPublicSource, refreshRawView, + selectedHighlight, } = useCustomToolStore(); const { sessionDetails } = useSessionStore(); const axiosPrivate = useAxiosPrivate(); const { setPostHogCustomEvent } = usePostHogEvents(); const { id } = useParams(); + const highlightData = selectedHighlight?.highlight || [[0, 0, 0, 1111]]; useEffect(() => { if (isSimplePromptStudio) { @@ -386,7 +388,7 @@ function DocumentManager({ generateIndex, handleUpdateTool, handleDocChange }) { setOpenManageDocsModal={setOpenManageDocsModal} errMsg={fileErrMsg} > - + )} {activeKey === "2" && ( diff --git a/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx b/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx index 765e468b7..986d5c433 100644 --- a/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx +++ b/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx @@ -20,32 +20,47 @@ function PdfViewer({ fileUrl, highlightData }) { const pageNavigationPluginInstance = pageNavigationPlugin(); const { jumpToPage } = pageNavigationPluginInstance; const parentRef = useRef(null); - function removeZerosAndDeleteIfAllZero(highlightData) { - return highlightData.filter((innerArray) => + + const removeZerosAndDeleteIfAllZero = (data) => { + return data?.filter((innerArray) => innerArray.some((value) => value !== 0) - ); // Keep arrays that contain at least one non-zero value - } - let highlightPluginInstance = ""; - if (RenderHighlights && highlightData) { - highlightPluginInstance = highlightPlugin({ - renderHighlights: (props) => ( - - ), - }); - } + ); + }; + + const processHighlightData = highlightData + ? removeZerosAndDeleteIfAllZero(highlightData) + : []; + + const processedHighlightData = + processHighlightData?.length > 0 + ? processHighlightData + : [[0, 0, 0, 11111]]; + + const highlightPluginInstance = + RenderHighlights && + Array.isArray(processedHighlightData) && + processedHighlightData?.length > 0 + ? highlightPlugin({ + renderHighlights: (props) => ( + + ), + }) + : null; // Jump to page when highlightData changes useEffect(() => { - if (highlightData && highlightData.length > 0) { - highlightData = removeZerosAndDeleteIfAllZero(highlightData); - const pageNumber = highlightData[0][0]; // Assume highlightData[0][0] is the page number + if (processedHighlightData && processedHighlightData?.length > 0) { + const pageNumber = processedHighlightData[0][0]; // Assume highlightData[0][0] is the page number if (pageNumber !== null && jumpToPage) { setTimeout(() => { - jumpToPage(pageNumber); // jumpToPage is 0-indexed, so subtract 1 + jumpToPage(pageNumber); // jumpToPage is 0-indexed, so subtract 1 if necessary }, 100); // Add a slight delay to ensure proper page rendering } } - }, [highlightData, jumpToPage]); + }, [processedHighlightData, jumpToPage]); return (
@@ -55,7 +70,7 @@ function PdfViewer({ fileUrl, highlightData }) { plugins={[ newPlugin, pageNavigationPluginInstance, - highlightPluginInstance, + ...(highlightPluginInstance ? [highlightPluginInstance] : []), ]} /> diff --git a/frontend/src/components/custom-tools/prompt-card/PromptCard.css b/frontend/src/components/custom-tools/prompt-card/PromptCard.css index f217c417f..31926462a 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptCard.css +++ b/frontend/src/components/custom-tools/prompt-card/PromptCard.css @@ -299,3 +299,16 @@ .info-circle-colored { color: #f0ad4e; } + +.highlighted-prompt { + border-width: 1.2px; + border-style: solid; + border-color: #4096ff; + box-shadow: 4px 4px 12.5px 0px rgba(0, 0, 0, 0.08); +} + +.highlighted-prompt-cell { + border-width: 1.2px; + border-style: solid; + border-color: #ffb400; +} diff --git a/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx b/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx index 1cda03555..794a450cc 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx +++ b/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx @@ -43,8 +43,13 @@ const PromptCard = memo( const [openOutputForDoc, setOpenOutputForDoc] = useState(false); const [progressMsg, setProgressMsg] = useState({}); const [spsLoading, setSpsLoading] = useState({}); - const { llmProfiles, selectedDoc, details, summarizeIndexStatus } = - useCustomToolStore(); + const { + llmProfiles, + selectedDoc, + details, + summarizeIndexStatus, + updateCustomTool, + } = useCustomToolStore(); const { messages } = useSocketCustomToolStore(); const { setAlertDetails } = useAlertStore(); const { setPostHogCustomEvent } = usePostHogEvents(); @@ -161,6 +166,22 @@ const PromptCard = memo( ); }; + const handleSelectHighlight = ( + highlightData, + highlightedPrompt, + highlightedProfile + ) => { + if (details?.enable_highlight) { + updateCustomTool({ + selectedHighlight: { + highlight: highlightData, + highlightedPrompt: highlightedPrompt, + highlightedProfile: highlightedProfile, + }, + }); + } + }; + const handleTypeChange = (value) => { handleChange(value, promptDetailsState?.prompt_id, "enforce_type", true); }; @@ -261,6 +282,7 @@ const PromptCard = memo( promptRunStatus={promptRunStatus} coverageCountData={coverageCountData} isChallenge={isChallenge} + handleSelectHighlight={handleSelectHighlight} /> +
@@ -302,6 +312,7 @@ PromptCardItems.propTypes = { promptRunStatus: PropTypes.object.isRequired, coverageCountData: PropTypes.object.isRequired, isChallenge: PropTypes.bool.isRequired, + handleSelectHighlight: PropTypes.func.isRequired, }; export { PromptCardItems }; diff --git a/frontend/src/components/custom-tools/prompt-card/PromptOutput.jsx b/frontend/src/components/custom-tools/prompt-card/PromptOutput.jsx index e5a181635..70580f192 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptOutput.jsx +++ b/frontend/src/components/custom-tools/prompt-card/PromptOutput.jsx @@ -67,6 +67,7 @@ function PromptOutput({ promptOutputs, promptRunStatus, isChallenge, + handleSelectHighlight, }) { const { width: windowWidth } = useWindowDimensions(); const componentWidth = windowWidth * 0.4; @@ -76,6 +77,8 @@ function PromptOutput({ isSimplePromptStudio, isPublicSource, defaultLlmProfile, + selectedHighlight, + details, } = useCustomToolStore(); const { setAlertDetails } = useAlertStore(); const { generatePromptOutputKey } = usePromptOutput(); @@ -200,191 +203,212 @@ function PromptOutput({ x: profileId === selectedLlmProfileId && index !== 0 ? -10 : 0, }} transition={{ duration: 0.5, ease: "linear" }} - className="prompt-card-llm" + className={`prompt-card-llm ${ + details?.enable_highlight && + selectedHighlight?.highlightedPrompt === promptId && + selectedHighlight?.highlightedProfile === profileId && + "highlighted-prompt-cell" + }`} > - - - -
-
- - - {profile?.conf?.LLM} - -
-
- - - - - - { - setIsIndexOpen(true); - setOpenIndexProfile(promptOutputData?.context); - }} - className="prompt-card-actions-head" - /> - - {ChallengeModal && isChallenge && ( - - )} - {isNotSingleLlmProfile && ( - - handleSelectDefaultLLM(profileId)} - disabled={isPublicSource} + + + { + handleSelectHighlight( + promptOutputData?.highlightData, + promptId, + profileId + ); + }} + > +
+
+ + + {profile?.conf?.LLM} + +
+
+ + + + + + { + setIsIndexOpen(true); + setOpenIndexProfile(promptOutputData?.context); + }} + className="prompt-card-actions-head" /> - )} - + {ChallengeModal && isChallenge && ( + + )} + {isNotSingleLlmProfile && ( + + + handleSelectDefaultLLM(profileId) + } + disabled={isPublicSource} + /> + + )} + +
-
-
- - Tokens:{" "} - {!singlePassExtractMode && ( - + + Tokens:{" "} + {!singlePassExtractMode && ( + + )} + + + - )} - - - - - - - -
-
-
- - handleTagChange(checked, profileId) - } - disabled={isPublicSource} - className={isChecked ? "checked" : "unchecked"} - > - {isChecked ? ( - - Enabled - - - ) : ( - - Disabled - - - )} - + + + +
-
- - - - - - - -
-
- - <> - -
- {isTableExtraction && TableOutput ? ( - - ) : ( - <> - -
- - copyOutputToClipboard( - displayPromptResult( - promptOutputData?.output, - true - ) + {isChecked ? ( + + Enabled + + + ) : ( + + Disabled + + + )} + +
+
+ + + + + + + +
+
+ + <> + +
+ {isTableExtraction && TableOutput ? ( + + ) : ( + <> + -
- - )} -
- - +
+ + copyOutputToClipboard( + displayPromptResult( + promptOutputData?.output, + true + ) + ) + } + /> +
+ + )} +
+ + + ); })} @@ -407,6 +431,7 @@ PromptOutput.propTypes = { promptOutputs: PropTypes.object.isRequired, promptRunStatus: PropTypes.object.isRequired, isChallenge: PropTypes.bool, + handleSelectHighlight: PropTypes.func.isRequired, }; export { PromptOutput }; diff --git a/frontend/src/store/custom-tool-store.js b/frontend/src/store/custom-tool-store.js index b61ff918e..288686828 100644 --- a/frontend/src/store/custom-tool-store.js +++ b/frontend/src/store/custom-tool-store.js @@ -22,6 +22,7 @@ const defaultState = { isPublicSource: false, isChallengeEnabled: false, adapters: [], + selectedHighlight: null, }; const defaultPromptInstance = { From 416912c6cefb82b80a88615eafe8325db26a9624 Mon Sep 17 00:00:00 2001 From: jagadeeswaran-zipstack Date: Wed, 11 Dec 2024 16:24:26 +0530 Subject: [PATCH 2/3] fixed hightligh UI --- .../components/custom-tools/settings-modal/SettingsModal.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/custom-tools/settings-modal/SettingsModal.jsx b/frontend/src/components/custom-tools/settings-modal/SettingsModal.jsx index 203af58bd..ea73f8606 100644 --- a/frontend/src/components/custom-tools/settings-modal/SettingsModal.jsx +++ b/frontend/src/components/custom-tools/settings-modal/SettingsModal.jsx @@ -106,7 +106,7 @@ function SettingsModal({ open, setOpen, handleUpdateTool }) { position++; } if (HighlightManager) { - items.push(getMenuItem("Highlight Manager", 8, )); + items.push(getMenuItem("Highlighting", 8, )); listOfComponents[8] = ( Date: Thu, 12 Dec 2024 01:35:13 +0530 Subject: [PATCH 3/3] code refactor/optimisation --- .../document-manager/DocumentManager.jsx | 2 +- .../custom-tools/pdf-viewer/PdfViewer.jsx | 35 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/custom-tools/document-manager/DocumentManager.jsx b/frontend/src/components/custom-tools/document-manager/DocumentManager.jsx index 057bd8f35..e5e724db5 100644 --- a/frontend/src/components/custom-tools/document-manager/DocumentManager.jsx +++ b/frontend/src/components/custom-tools/document-manager/DocumentManager.jsx @@ -101,7 +101,7 @@ function DocumentManager({ generateIndex, handleUpdateTool, handleDocChange }) { const axiosPrivate = useAxiosPrivate(); const { setPostHogCustomEvent } = usePostHogEvents(); const { id } = useParams(); - const highlightData = selectedHighlight?.highlight || [[0, 0, 0, 1111]]; + const highlightData = selectedHighlight?.highlight || []; useEffect(() => { if (isSimplePromptStudio) { diff --git a/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx b/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx index 986d5c433..1ea828fe0 100644 --- a/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx +++ b/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useMemo } from "react"; import { Viewer, Worker } from "@react-pdf-viewer/core"; import { defaultLayoutPlugin } from "@react-pdf-viewer/default-layout"; import { pageNavigationPlugin } from "@react-pdf-viewer/page-navigation"; @@ -32,23 +32,22 @@ function PdfViewer({ fileUrl, highlightData }) { : []; const processedHighlightData = - processHighlightData?.length > 0 - ? processHighlightData - : [[0, 0, 0, 11111]]; + processHighlightData?.length > 0 ? processHighlightData : [[0, 0, 0, 0]]; - const highlightPluginInstance = - RenderHighlights && - Array.isArray(processedHighlightData) && - processedHighlightData?.length > 0 - ? highlightPlugin({ - renderHighlights: (props) => ( - - ), - }) - : null; + const highlightPluginInstance = useMemo(() => { + if ( + RenderHighlights && + Array.isArray(processedHighlightData) && + processedHighlightData?.length > 0 + ) { + return highlightPlugin({ + renderHighlights: (props) => ( + + ), + }); + } + return ""; + }, [RenderHighlights, processedHighlightData]); // Jump to page when highlightData changes useEffect(() => { @@ -70,7 +69,7 @@ function PdfViewer({ fileUrl, highlightData }) { plugins={[ newPlugin, pageNavigationPluginInstance, - ...(highlightPluginInstance ? [highlightPluginInstance] : []), + highlightPluginInstance, ]} />