From 13c92f134c9b6863e4cb52f35182a227b647de5b Mon Sep 17 00:00:00 2001 From: Lucas Massemin Date: Thu, 5 Dec 2024 12:41:45 +0100 Subject: [PATCH] 1725 prompt versioning (#9105) * Basic versioning (not sticking to figma yet) * Remembering edits on latest version * better button positioning * Re-positioning advanced settings * Now remembering all version edits * New date formatter * Fixed scroll area height -> now dynamic --------- Co-authored-by: Lucas --- .../assistant_builder/AssistantBuilder.tsx | 1 + .../assistant_builder/InstructionScreen.tsx | 147 +++++++++++++++++- front/lib/swr/assistants.ts | 31 ++++ .../[aId]/history/index.ts | 87 +++++++++++ .../internal/agent_configuration.ts | 4 + 5 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 front/pages/api/w/[wId]/assistant/agent_configurations/[aId]/history/index.ts diff --git a/front/components/assistant_builder/AssistantBuilder.tsx b/front/components/assistant_builder/AssistantBuilder.tsx index 458ca0a6c9d2..bda114636a67 100644 --- a/front/components/assistant_builder/AssistantBuilder.tsx +++ b/front/components/assistant_builder/AssistantBuilder.tsx @@ -450,6 +450,7 @@ export default function AssistantBuilder({ instructionsError={instructionsError} doTypewriterEffect={doTypewriterEffect} setDoTypewriterEffect={setDoTypewriterEffect} + agentConfigurationId={agentConfigurationId} /> ); case "actions": diff --git a/front/components/assistant_builder/InstructionScreen.tsx b/front/components/assistant_builder/InstructionScreen.tsx index d74da80d643b..d59c1f1baefb 100644 --- a/front/components/assistant_builder/InstructionScreen.tsx +++ b/front/components/assistant_builder/InstructionScreen.tsx @@ -15,6 +15,7 @@ import type { APIError, AssistantCreativityLevel, BuilderSuggestionsType, + LightAgentConfigurationType, ModelConfigurationType, ModelIdType, PlanType, @@ -42,7 +43,13 @@ import { History } from "@tiptap/extension-history"; import Text from "@tiptap/extension-text"; import type { Editor, JSONContent } from "@tiptap/react"; import { EditorContent, useEditor } from "@tiptap/react"; -import React, { useEffect, useMemo, useRef, useState } from "react"; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import type { AssistantBuilderState } from "@app/components/assistant_builder/types"; import { @@ -56,6 +63,7 @@ import { tipTapContentFromPlainText, } from "@app/lib/client/assistant_builder/instructions"; import { isUpgraded } from "@app/lib/plans/plan_codes"; +import { useAgentConfigurationHistory } from "@app/lib/swr/assistants"; import { classNames } from "@app/lib/utils"; import { debounce } from "@app/lib/utils/debounce"; @@ -111,6 +119,7 @@ export function InstructionScreen({ instructionsError, doTypewriterEffect, setDoTypewriterEffect, + agentConfigurationId, }: { owner: WorkspaceType; plan: PlanType; @@ -124,6 +133,7 @@ export function InstructionScreen({ instructionsError: string | null; doTypewriterEffect: boolean; setDoTypewriterEffect: (doTypewriterEffect: boolean) => void; + agentConfigurationId: string | null; }) { const editor = useEditor({ extensions: [ @@ -153,6 +163,44 @@ export function InstructionScreen({ }); const editorService = useInstructionEditorService(editor); + const { agentConfigurationHistory } = useAgentConfigurationHistory({ + workspaceId: owner.sId, + agentConfigurationId, + disabled: !agentConfigurationId, + limit: 30, + }); + // Keep a memory of overriden versions, to not lose them when switching back and forth + const [currentConfig, setCurrentConfig] = + useState(null); + // versionNumber -> instructions + const [overridenConfigInstructions, setOverridenConfigInstructions] = + useState<{ + [key: string]: string; + }>({}); + + // Deduplicate configs based on instructions + const configsWithUniqueInstructions: LightAgentConfigurationType[] = + useMemo(() => { + const uniqueInstructions = new Set(); + const configs: LightAgentConfigurationType[] = []; + agentConfigurationHistory?.forEach((config) => { + if ( + !config.instructions || + uniqueInstructions.has(config.instructions) + ) { + return; + } else { + uniqueInstructions.add(config.instructions); + configs.push(config); + } + }); + return configs; + }, [agentConfigurationHistory]); + + useEffect(() => { + setCurrentConfig(agentConfigurationHistory?.[0] || null); + }, [agentConfigurationHistory]); + const [letterIndex, setLetterIndex] = useState(0); // Beware that using this useEffect will cause a lot of re-rendering until we finished the visual effect @@ -251,6 +299,45 @@ export function InstructionScreen({
+ {configsWithUniqueInstructions && + configsWithUniqueInstructions.length > 1 && + currentConfig && ( +
+ { + // Remember the instructions of the version we're leaving, if overriden + if ( + currentConfig && + currentConfig.instructions !== builderState.instructions + ) { + setOverridenConfigInstructions((prev) => ({ + ...prev, + [currentConfig.version]: builderState.instructions, + })); + } + + // Bring new version's instructions to the editor, fetch overriden instructions if any + setCurrentConfig(config); + editorService.resetContent( + tipTapContentFromPlainText( + overridenConfigInstructions[config.version] || + config.instructions || + "" + ) + ); + setBuilderState((state) => ({ + ...state, + instructions: + overridenConfigInstructions[config.version] || + config.instructions || + "", + })); + }} + currentConfig={currentConfig} + /> +
+ )} void; + currentConfig: LightAgentConfigurationType; +}) { + const [isOpen, setIsOpen] = useState(false); + const latestConfig = history[0]; + + const getStringRepresentation = useCallback( + (config: LightAgentConfigurationType) => { + const dateFormatter = new Intl.DateTimeFormat(navigator.language, { + year: "numeric", + month: "short", + day: "numeric", + hour: "numeric", + minute: "numeric", + }); + return config.version === latestConfig?.version + ? "Latest Version" + : config.versionCreatedAt + ? dateFormatter.format(new Date(config.versionCreatedAt)) + : `v${config.version}`; + }, + [latestConfig] + ); + + return ( + + +