diff --git a/app/src/components/templateEditor/TemplateEditor.tsx b/app/src/components/templateEditor/TemplateEditor.tsx index 4bb1111ad4..f506e49334 100644 --- a/app/src/components/templateEditor/TemplateEditor.tsx +++ b/app/src/components/templateEditor/TemplateEditor.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { githubLight } from "@uiw/codemirror-theme-github"; import { nord } from "@uiw/codemirror-theme-nord"; import CodeMirror, { @@ -16,8 +16,9 @@ import { MustacheLikeTemplating } from "./language/mustacheLike"; import { TemplateLanguages } from "./constants"; import { TemplateLanguage } from "./types"; -type TemplateEditorProps = ReactCodeMirrorProps & { +type TemplateEditorProps = Omit & { templateLanguage: TemplateLanguage; + defaultValue: string; }; const basicSetupOptions: BasicSetupOptions = { @@ -28,10 +29,22 @@ const basicSetupOptions: BasicSetupOptions = { bracketMatching: false, }; +/** + * A template editor that is used to edit the template of a tool. + * + * This is an uncontrolled editor. + * You can only reset the value of the editor by triggering a re-mount, like with the `key` prop, + * or, when the readOnly prop is true, the editor will reset on all value changes. + * This is necessary because controlled react-codemirror editors incessantly reset + * cursor position when value is updated. + */ export const TemplateEditor = ({ templateLanguage, + defaultValue, + readOnly, ...props }: TemplateEditorProps) => { + const [value, setValue] = useState(() => defaultValue); const { theme } = useTheme(); const codeMirrorTheme = theme === "light" ? githubLight : nord; const extensions = useMemo(() => { @@ -51,12 +64,20 @@ export const TemplateEditor = ({ return ext; }, [templateLanguage]); + useEffect(() => { + if (readOnly) { + setValue(defaultValue); + } + }, [readOnly, defaultValue]); + return ( ); }; diff --git a/app/src/pages/playground/Playground.tsx b/app/src/pages/playground/Playground.tsx index 8491de15b9..a9488e22f9 100644 --- a/app/src/pages/playground/Playground.tsx +++ b/app/src/pages/playground/Playground.tsx @@ -1,4 +1,4 @@ -import React, { Suspense, useCallback, useEffect } from "react"; +import React, { Fragment, Suspense, useCallback, useEffect } from "react"; import { graphql, useLazyLoadQuery } from "react-relay"; import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; import { BlockerFunction, useBlocker, useSearchParams } from "react-router-dom"; @@ -205,6 +205,9 @@ const playgroundInputOutputPanelContentCSS = css` */ const PLAYGROUND_PROMPT_PANEL_MIN_WIDTH = 632; +const DEFAULT_EXPANDED_PROMPTS = ["prompts"]; +const DEFAULT_EXPANDED_PARAMS = ["input", "output"]; + function PlaygroundContent() { const instances = usePlaygroundContext((state) => state.instances); const templateLanguage = usePlaygroundContext( @@ -246,7 +249,7 @@ function PlaygroundContent() { }, [isRunning, anyDirtyInstances]); return ( - <> +
- + ( @@ -301,7 +303,7 @@ function PlaygroundContent() { ) : (
- + {templateLanguage !== TemplateLanguages.NONE ? ( @@ -321,10 +323,9 @@ function PlaygroundContent() { - {instances.map((instance, i) => ( - + {instances.map((instance) => ( + @@ -348,6 +349,6 @@ function PlaygroundContent() { } /> )} - + ); } diff --git a/app/src/pages/playground/PlaygroundChatTemplate.tsx b/app/src/pages/playground/PlaygroundChatTemplate.tsx index 66f2b89ffa..40f17f254a 100644 --- a/app/src/pages/playground/PlaygroundChatTemplate.tsx +++ b/app/src/pages/playground/PlaygroundChatTemplate.tsx @@ -199,6 +199,12 @@ function MessageEditor({ updateMessage: (patch: Partial) => void; messageMode: MessageMode; }) { + const onChange = useCallback( + (val: string) => { + updateMessage({ content: val }); + }, + [updateMessage] + ); if (messageMode === "toolCalls") { return ( ); } + return ( updateMessage({ content: val })} + onChange={onChange} placeholder={ message.role === "system" ? "You are a helpful assistant" diff --git a/app/src/pages/playground/PlaygroundInput.tsx b/app/src/pages/playground/PlaygroundInput.tsx index 167a42abbb..cbb68db9a6 100644 --- a/app/src/pages/playground/PlaygroundInput.tsx +++ b/app/src/pages/playground/PlaygroundInput.tsx @@ -56,7 +56,7 @@ export function PlaygroundInput() { // change rapidly for a given variable key={i} label={variableKey} - value={variablesMap[variableKey]} + defaultValue={variablesMap[variableKey] ?? ""} onChange={(value) => setVariableValue(variableKey, value)} /> ); diff --git a/app/src/pages/playground/PlaygroundTool.tsx b/app/src/pages/playground/PlaygroundTool.tsx index a5490d26fe..e8efd17b8e 100644 --- a/app/src/pages/playground/PlaygroundTool.tsx +++ b/app/src/pages/playground/PlaygroundTool.tsx @@ -32,12 +32,20 @@ import { PlaygroundInstanceProps } from "./types"; */ const TOOL_EDITOR_PRE_INIT_HEIGHT = 400; +/** + * A tool editor that is used to edit the definition of a tool. + * + * This is a mostly un-controlled editor that re-mounts when the tool definition changes externally. + * This is necessary because controlled react-codemirror editors incessantly remount and reset + * cursor position when value is updated. + */ export function PlaygroundTool({ playgroundInstanceId, toolId, }: PlaygroundInstanceProps & { toolId: Tool["id"]; }) { + const [version, setVersion] = useState(0); const updateInstance = usePlaygroundContext((state) => state.updateInstance); const instance = usePlaygroundContext((state) => @@ -56,7 +64,7 @@ export function PlaygroundTool({ throw new Error(`Tool ${toolId} not found`); } - const [editorValue, setEditorValue] = useState( + const [initialEditorValue, setEditorValue] = useState( JSON.stringify(tool.definition, null, 2) ); @@ -71,12 +79,15 @@ export function PlaygroundTool({ ) { setLastValidDefinition(tool.definition); setEditorValue(JSON.stringify(tool.definition, null, 2)); + setVersion((prev) => prev + 1); } }, [tool.definition, lastValidDefinition]); const onChange = useCallback( (value: string) => { - setEditorValue(value); + // note that we do not update initialEditorValue here, we only want to update it when + // we are okay with the editor state resetting, which is basically only when the provider + // changes const { json: definition } = safelyParseJSON(value); if (definition == null) { return; @@ -141,7 +152,7 @@ export function PlaygroundTool({ bodyStyle={{ padding: 0 }} extra={ - +