Skip to content

Commit

Permalink
Add prompt parameters renderer (#616)
Browse files Browse the repository at this point in the history
  • Loading branch information
saqadri authored Dec 26, 2023
2 parents 4b2b727 + 2eae526 commit a643700
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ export default function EditorContainer({
[dispatch]
);

const onUpdatePromptParameters = useCallback(
async (promptIndex: number, newParameters: any) => {
dispatch({
type: "UPDATE_PROMPT_PARAMETERS",
index: promptIndex,
parameters: newParameters,
});
// TODO: Call server-side endpoint to update prompt parameters
},
[dispatch]
);

const { classes } = useStyles();

// TODO: Implement editor context for callbacks, readonly state, etc.
Expand All @@ -94,6 +106,7 @@ export default function EditorContainer({
key={prompt.name}
onChangePromptInput={onChangePromptInput}
onUpdateModelSettings={onUpdatePromptModelSettings}
onUpdateParameters={onUpdatePromptParameters}
defaultConfigModelName={aiconfigState.metadata.default_model}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
import {
Group,
Text,
TextInput,
Textarea,
ActionIcon,
Stack,
useMantineTheme,
Tooltip,
} from "@mantine/core";
import { IconTrash, IconPlus } from "@tabler/icons-react";
import { debounce, uniqueId } from "lodash";
import { useState, useCallback, memo, useMemo } from "react";

interface JSONArray extends Array<JSONValue> {}

interface JSONObject {
[x: string]: JSONValue;
}

type JSONValue = string | number | boolean | JSONObject | JSONArray | unknown;

type Parameter = { parameterName: string; parameterValue: JSONValue };

/**
* Parameter name must start with a letter (a-z, A-Z) or an underscore (_). The rest of the
* name can contain letters, digits (0-9), underscores, and dollar signs ($).
*/
export function isValidParameterName(name: string): boolean {
const validNamePattern = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
return validNamePattern.test(name);
}

const ParameterInput = memo(function ParameterInput(props: {
onUpdateParameter: (data: {
promptName?: string;
parameterName: string;
oldParameterName?: string;
parameterValue?: string;
}) => void;
initialItemValue?: Parameter;
removeParameter: (parameterName?: string) => Promise<void>;
}) {
const { initialItemValue, removeParameter, onUpdateParameter } = props;
// TODO: saqadri - update this once we have a readonly mode
const { isReadonly } = { isReadonly: false };

const [parameterName, setParameterName] = useState(
initialItemValue?.parameterName ?? ""
);
const [lastParameterName, setLastParameterName] =
useState<string>(parameterName);

const parameterValue = initialItemValue?.parameterValue;

const [parameterValueString, setParameterValueString] = useState(
typeof parameterValue === "string"
? parameterValue
: JSON.stringify(parameterValue)
);

const debouncedCellParameterUpdate = useMemo(
() =>
debounce((newParameterName: string, newParameterValue: string) => {
if (!isValidParameterName(newParameterName)) {
return;
}

onUpdateParameter({
oldParameterName: lastParameterName,
parameterName: newParameterName,
parameterValue: newParameterValue,
});

setLastParameterName(newParameterName);
}, 250),
[lastParameterName, onUpdateParameter]
);

const theme = useMantineTheme();
const border =
theme.colorScheme === "dark" ? "1px solid #2C2E33" : "1px solid #e9ecef";

return (
<Group>
<Stack p="xs" spacing="xs" style={{ flexGrow: 1, borderBottom: border }}>
<TextInput
placeholder="Enter parameter name"
disabled={isReadonly}
error={
parameterName && !isValidParameterName(parameterName)
? "Name must contain only letters, numbers, and underscores"
: null
}
radius="md"
size="xs"
value={parameterName}
onChange={(event) => {
setParameterName(event.target.value);
if (event.target.value) {
debouncedCellParameterUpdate(
event.target.value,
parameterValueString
);
}
}}
/>
<Textarea
placeholder="Enter parameter value"
disabled={isReadonly}
radius="md"
value={parameterValueString}
autosize
size="xs"
maxRows={5}
onChange={(event) => {
setParameterValueString(event.target.value);
debouncedCellParameterUpdate(parameterName, event.target.value);
}}
/>
</Stack>
<ActionIcon
onClick={() => removeParameter(parameterName)}
style={{ marginTop: -50 }}
disabled={isReadonly}
>
<IconTrash size={16} color={isReadonly ? "grey" : "red"} />
</ActionIcon>
</Group>
);
});

export type ParametersArray = {
parameterName: string;
parameterValue: JSONValue;
key: string;
}[];

export default memo(function ParametersRenderer(props: {
initialValue?: JSONObject;
onUpdateParameters: (data: {
promptName?: string;
newParameters: ParametersArray;
}) => void;
customDescription?: React.ReactNode;
maxHeight?: string | number;
}) {
const { initialValue, onUpdateParameters } = props;
// TODO: saqadri - update this when we have a readonly mode
const { isReadonly } = { isReadonly: false }; //useContext(WorkbookContext);

const [parameters, setParameters] = useState<ParametersArray>(
initialValue && Object.keys(initialValue).length > 0
? Object.keys(initialValue).map((parameterName) => {
return {
key: parameterName,
parameterName,
parameterValue: initialValue[parameterName],
};
})
: [
{
key: uniqueId(),
parameterName: "",
parameterValue: "",
},
]
);

const removeParameter = useCallback(
async (key: string, parameterName?: string) => {
setParameters((prev) => {
const newParameters = prev.filter((item) => item.key !== key);
onUpdateParameters({ newParameters });
return newParameters;
});
},
[setParameters, onUpdateParameters]
);

const addParameter = useCallback(async () => {
setParameters((prev) => {
const newParameters = [
...prev,
{
key: uniqueId(),
parameterName: "",
parameterValue: "",
},
];
onUpdateParameters({ newParameters });
return newParameters;
});
}, [onUpdateParameters]);

// TODO: saqadri - add MantineProvider wrapper inside EditorContainer in order for this to pick up context
const theme = useMantineTheme();

return (
<div
style={{
maxHeight: props.maxHeight ?? "300px",
overflow: "auto",
width: "100%",
}}
>
{props.customDescription ?? (
<Text
color="dimmed"
size="sm"
p="xs"
style={{ display: "block", margin: "0 auto", textAlign: "right" }}
>
Use parameters in your prompt or system prompt with {"{{parameter}}"}
</Text>
)}
<Stack>
{parameters.map((parameter, i) => {
return (
<ParameterInput
onUpdateParameter={({ parameterName, parameterValue }) => {
setParameters((prev) => {
const newParameters = [...prev];
const currentElement = newParameters[i];
currentElement.parameterName = parameterName;
currentElement.parameterValue = parameterValue ?? "";

onUpdateParameters({ newParameters });

return newParameters;
});
}}
removeParameter={(parameterName) =>
removeParameter(parameter.key, parameterName)
}
initialItemValue={{
parameterName: parameter.parameterName,
parameterValue: parameter.parameterValue,
}}
key={parameter.key}
/>
);
})}
</Stack>
{isReadonly ? null : (
<Tooltip label="Add parameter">
<ActionIcon onClick={addParameter}>
<IconPlus size={16} />
</ActionIcon>
</Tooltip>
)}
</div>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { AIConfig, JSONObject, PromptInput } from "aiconfig";

type AIConfigReducerAction =
| UpdatePromptInputAction
| UpdatePromptModelSettingsAction;
| UpdatePromptModelSettingsAction
| UpdatePromptParametersAction;

export type UpdatePromptInputAction = {
type: "UPDATE_PROMPT_INPUT";
Expand All @@ -18,6 +19,13 @@ export type UpdatePromptModelSettingsAction = {
modelSettings: JSONObject;
};

// TODO: saqadri - can likely use this same action for global parameters update
export type UpdatePromptParametersAction = {
type: "UPDATE_PROMPT_PARAMETERS";
index: number;
parameters: JSONObject;
};

function reduceReplacePrompt(
state: ClientAIConfig,
index: number,
Expand Down Expand Up @@ -67,5 +75,14 @@ export default function aiconfigReducer(
},
}));
}
case "UPDATE_PROMPT_PARAMETERS": {
return reduceReplacePrompt(state, action.index, (prompt) => ({
...prompt,
metadata: {
...prompt.metadata,
parameters: action.parameters,
},
}));
}
}
}
Loading

0 comments on commit a643700

Please sign in to comment.