Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ export const persistPromptNameOptions = assign<
LLMFormFieldsMachineContext,
DoneInvokeEvent<any>
>({
promptNameOptions: (_, { data }) => data,
promptNameOptions: (_, { data }) => data ?? [],
promptNamesFetched: true,
});

export const markPromptNamesFetched = assign<LLMFormFieldsMachineContext>({
promptNamesFetched: true,
});

export const persistVectorDbOptions = assign<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const llmFormFieldsMachine = createMachine<
embeddingModelOptions: [],
selectedPromptName: undefined,
selectedPrompt: null,
promptNamesFetched: false,
},
states: {
[LLMFormFieldsMachineStates.DETERMINE_INITIAL_STATE]: {
Expand All @@ -46,6 +47,14 @@ export const llmFormFieldsMachine = createMachine<
(field) => field === UiIntegrationsFieldType.LLM_PROVIDER,
),
},
{
target: LLMFormFieldsMachineStates.FETCH_PROMPT_NAMES,
cond: (context) =>
context.fields.some(
(field) => field === UiIntegrationsFieldType.INSTRUCTIONS,
) && !context.promptNamesFetched,
},
{ target: LLMFormFieldsMachineStates.IDLE },
],
},
[LLMFormFieldsMachineStates.IDLE]: {
Expand Down Expand Up @@ -85,10 +94,20 @@ export const llmFormFieldsMachine = createMachine<
[LLMFormFieldsMachineStates.FETCH_LLM_PROVIDER_OPTIONS]: {
invoke: {
src: "fetchLlmProviderOptionsService",
onDone: {
actions: "persistLlmProviderOptions",
target: LLMFormFieldsMachineStates.IDLE,
},
onDone: [
{
actions: "persistLlmProviderOptions",
target: LLMFormFieldsMachineStates.FETCH_PROMPT_NAMES,
cond: (context) =>
context.fields.some(
(field) => field === UiIntegrationsFieldType.INSTRUCTIONS,
) && !context.promptNamesFetched,
},
{
actions: "persistLlmProviderOptions",
target: LLMFormFieldsMachineStates.IDLE,
},
],
},
},
[LLMFormFieldsMachineStates.FETCH_MODEL_OPTIONS]: {
Expand Down Expand Up @@ -118,7 +137,10 @@ export const llmFormFieldsMachine = createMachine<
actions: "persistPromptNameOptions",
target: LLMFormFieldsMachineStates.IDLE,
},
onError: LLMFormFieldsMachineStates.IDLE,
onError: {
actions: "markPromptNamesFetched",
target: LLMFormFieldsMachineStates.IDLE,
},
},
},
[LLMFormFieldsMachineStates.FETCH_INDEX_OPTIONS]: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ export const fetchForModels = async (
};

export const fetchForPromptNames = async (
{ authHeaders: headers }: LLMFormFieldsMachineContext,
{ task }: FocusEvent,
{ authHeaders: headers, task: contextTask }: LLMFormFieldsMachineContext,
event: FocusEvent,
) => {
const task = event?.task ?? contextTask;
const maybeLlmProvider = task?.inputParameters?.llmProvider;
const maybeModel = task?.inputParameters?.model;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export interface LLMFormFieldsMachineContext {
authHeaders?: AuthHeaders;
llmProviderOptions: [];
promptNameOptions: PromptDef[];
promptNamesFetched: boolean;
modelOptions: [];
vectorDbOptions: [];
indexOptions: [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { useState } from "react";
import { queryClient } from "queryClient";
import { TaskDef } from "types";
Expand Down Expand Up @@ -107,10 +107,10 @@ describe("LLMInstructionsWithPromptPicker", () => {
expect(screen.getByText("Write custom instructions")).toBeInTheDocument();
});

it("auto-expands custom instructions when prompt registry is empty", () => {
it("auto-expands custom instructions when prompt registry is empty", async () => {
render(<Harness initialTask={{ inputParameters: {} }} />);

expect(screen.getByLabelText("Instructions")).toBeInTheDocument();
expect(await screen.findByLabelText("Instructions")).toBeVisible();
});

it("writes custom instructions and sets allowRawPrompts=true", () => {
Expand Down Expand Up @@ -165,6 +165,75 @@ describe("LLMInstructionsWithPromptPicker", () => {
);
});

it("shows a saved prompt in the picker instead of the custom instructions area", () => {
render(
<Harness
initialTask={{
inputParameters: {
instructions: "my-saved-prompt",
allowRawPrompts: false,
},
}}
/>,
);

expect(screen.getByLabelText("Prompt Template")).toHaveValue(
"my-saved-prompt",
);
expect(screen.getByLabelText("Instructions")).toHaveValue("");
});

it("treats instructions without allowRawPrompts as a saved prompt", () => {
render(
<Harness
initialTask={{
inputParameters: { instructions: "legacy-prompt" },
}}
/>,
);

expect(screen.getByLabelText("Prompt Template")).toHaveValue(
"legacy-prompt",
);
expect(screen.getByLabelText("Instructions")).toHaveValue("");
});

it("shows prompt variables for a saved prompt loaded from a workflow", () => {
render(
<Harness
initialTask={{
inputParameters: {
instructions: "my-saved-prompt",
allowRawPrompts: false,
promptVariables: { topic: "billing" },
},
}}
/>,
);

expect(screen.getByTestId("prompt-variables")).toHaveTextContent(
'{"topic":"billing"}',
);
});

it("fetches prompt names on load so the picker is populated", async () => {
fetchWithContext.mockResolvedValue([{ name: "my-saved-prompt" }]);

render(
<Harness
initialTask={{
inputParameters: {
instructions: "my-saved-prompt",
allowRawPrompts: false,
},
}}
/>,
);

await waitFor(() => expect(fetchWithContext).toHaveBeenCalled());
expect(fetchWithContext.mock.calls[0][0]).toBe("/prompts");
});

it("shows the AI Prompt picker with empty value when no prompt is selected", () => {
render(<Harness initialTask={{ inputParameters: {} }} />);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { ConductorAutocompleteVariables } from "components/FlatMapForm/Conductor
import MuiTypography from "components/ui/MuiTypography";
import PromptVariables from "components/PromptVariables";
import { path as _path } from "lodash/fp";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useCallback, useMemo, useState } from "react";
import { TaskDef } from "types";
import { UiIntegrationsFieldType } from "types/FormFieldTypes";
import { updateField } from "utils/fieldHelpers";
Expand All @@ -47,6 +47,10 @@ export const LLMInstructionsWithPromptPicker = ({
actor,
(state) => state.context.promptNameOptions,
);
const promptNamesFetched = useSelector(
actor,
(state) => state.context.promptNamesFetched,
);
const promptOptions = useMemo(
() => promptNames.map(({ name }: { name: string }) => name),
[promptNames],
Expand All @@ -60,20 +64,21 @@ export const LLMInstructionsWithPromptPicker = ({
) as boolean;
const currentVariables = task.inputParameters?.promptVariables || {};

const isUsingPrompt =
!allowRawPrompts && promptOptions.includes(instructions);
const [customExpanded, setCustomExpanded] = useState(false);

// Auto-expand custom instructions when no prompt registry or using raw text
useEffect(() => {
if (promptOptions.length === 0 || (instructions && !isUsingPrompt)) {
setCustomExpanded(true);
}
}, [promptOptions.length, instructions, isUsingPrompt]);
// The server resolves `instructions` as a registered prompt name unless
// allowRawPrompts is set, so the flag — not the lazily fetched option list —
// decides which control owns the value.
const isUsingPrompt = !!instructions && allowRawPrompts !== true;
const defaultCustomExpanded =
!isUsingPrompt &&
(!!instructions || (promptNamesFetched && promptOptions.length === 0));
const [customExpandedOverride, setCustomExpandedOverride] = useState<
boolean | undefined
>(undefined);
const customExpanded = customExpandedOverride ?? defaultCustomExpanded;

const handleSelectPrompt = useCallback(
(value: unknown) => {
setCustomExpanded(false);
setCustomExpandedOverride(false);
actor.send({
type: LLMFormFieldsMachineEventTypes.SELECT_INSTRUCTIONS,
task: updateField(
Expand All @@ -97,8 +102,8 @@ export const LLMInstructionsWithPromptPicker = ({
);

const toggleCustom = useCallback(() => {
setCustomExpanded((prev) => !prev);
}, []);
setCustomExpandedOverride((prev) => !(prev ?? defaultCustomExpanded));
}, [defaultCustomExpanded]);

return (
<Grid container spacing={3} sx={{ width: "100%" }}>
Expand Down
Loading