Skip to content

Commit

Permalink
[editor] Config Name & Description UI
Browse files Browse the repository at this point in the history
[editor] Config Name & Description UI

# [editor] Config Name & Description UI

Add the UI for changing config name / description, similar to workbook title UI:


https://github.com/lastmile-ai/aiconfig/assets/5060851/f0f49638-d0c5-4d88-8edd-c0bbb0294f7c



If config name is not set, keep the name / description in Edit state until the name is set
  • Loading branch information
Ryan Holinshead committed Dec 30, 2023
1 parent 526d45e commit 4e57693
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 14 deletions.
12 changes: 12 additions & 0 deletions python/src/aiconfig/editor/client/src/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,24 @@ export default function Editor() {
[]
);

const setNameDescription = useCallback(
async (_val: { name?: string; description?: string | null }) => {
// return await ufetch.post(ROUTE_TABLE.SET_NAME_DESCRIPTION, {
// name: val.name,
// description: val.description,
// });
},
[]
);

const callbacks: AIConfigCallbacks = useMemo(
() => ({
addPrompt,
deletePrompt,
getModels,
runPrompt,
save,
setNameDescription,
updateModel,
updatePrompt,
}),
Expand All @@ -98,6 +109,7 @@ export default function Editor() {
getModels,
runPrompt,
save,
setNameDescription,
updateModel,
updatePrompt,
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import {
createStyles,
Stack,
Text,
Textarea,
TextInput,
Title,
} from "@mantine/core";
import { useClickOutside } from "@mantine/hooks";
import { ChangeEvent, memo, useCallback, useRef, useState } from "react";

type Props = {
name?: string;
description?: string | null;
setNameDescription: (val: {
name?: string;
description?: string | null;
}) => void;
};

const useStyles = createStyles((theme) => ({
// Match name input style to corresponding Title element styles
// See https://github.com/mantinedev/mantine/blob/master/src/mantine-core/src/Title/Title.styles.ts
nameInput: {
...theme.fn.fontStyles(),
fontFamily: theme.headings.fontFamily,
fontWeight: theme.headings.fontWeight as number,
fontSize: theme.headings.sizes.h1.fontSize,
lineHeight: theme.headings.sizes.h1.lineHeight,
width: "-webkit-fill-available",
letterSpacing: "-1px",
height: "44px",
},
hoverContainer: {
"&:hover": {
backgroundColor:
theme.colorScheme === "dark"
? "rgba(255, 255, 255, 0.1)"
: theme.colors.gray[1],
},
borderRadius: theme.radius.sm,
width: "-webkit-fill-available",
},
}));

export default memo(function ConfigTitleDescription({
name,
description,
setNameDescription,
}: Props) {
const { classes } = useStyles();

const [isEditing, setIsEditing] = useState(!name);
const [initialFocus, setInitialFocus] = useState<
"name" | "description" | null
>("name");

const nameDisplayRef = useRef<HTMLHeadingElement | null>(null);
const descriptionDisplayRef = useRef<HTMLDivElement | null>(null);

const inputSectionRef = useClickOutside(() => {
if (name) {
setIsEditing(false);
}
});

const onHandleEnter = useCallback(
(event: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
if (event.key === "Enter") {
event.stopPropagation();
setIsEditing(false);
}
},
[]
);

const onClickEdit = useCallback(
(event: React.MouseEvent<HTMLHeadingElement | HTMLDivElement>) => {
setIsEditing(true);
if (event.target === nameDisplayRef.current) {
setInitialFocus("name");
} else if (event.target === descriptionDisplayRef.current) {
setInitialFocus("description");
}
},
[]
);

const setName = (e: ChangeEvent<HTMLInputElement>) => {
setNameDescription({ name: e.target.value });
};

const setDescription = (e: ChangeEvent<HTMLTextAreaElement>) => {
// If description is empty, set it to null; it will be removed from the config
setNameDescription({ description: e.target.value || null });
};

return (
<Stack
ref={isEditing ? inputSectionRef : undefined}
spacing="xs"
w="100%"
ml="1em"
mr="0.5em"
>
{isEditing ? (
<>
<TextInput
classNames={{ input: classes.nameInput }}
placeholder={"Config name"}
value={name}
onKeyDown={onHandleEnter}
autoFocus={initialFocus === "name"}
onChange={setName}
/>
<Textarea
placeholder="Config description"
value={description ?? undefined}
onKeyDown={onHandleEnter}
autoFocus={initialFocus === "description"}
onChange={setDescription}
autosize
minRows={2}
/>
</>
) : (
<div>
<Title
ref={nameDisplayRef}
onClick={onClickEdit}
className={classes.hoverContainer}
>
{name}
</Title>
<Text
ref={descriptionDisplayRef}
onClick={onClickEdit}
style={{ whiteSpace: "pre-wrap" }}
className={classes.hoverContainer}
>
{description}
</Text>
</div>
)}
</Stack>
);
});
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import PromptContainer from "./prompt/PromptContainer";
import {
Container,
Group,
Button,
createStyles,
Stack,
Flex,
} from "@mantine/core";
import { Container, Button, createStyles, Stack, Flex } from "@mantine/core";
import { showNotification } from "@mantine/notifications";
import {
AIConfig,
Expand All @@ -32,6 +25,7 @@ import { debounce, uniqueId } from "lodash";
import PromptMenuButton from "./prompt/PromptMenuButton";
import GlobalParametersContainer from "./GlobalParametersContainer";
import AIConfigContext from "./AIConfigContext";
import ConfigNameDescription from "./ConfigNameDescription";

type Props = {
aiconfig: AIConfig;
Expand All @@ -48,6 +42,10 @@ export type AIConfigCallbacks = {
getModels: (search: string) => Promise<string[]>;
runPrompt: (promptName: string) => Promise<{ aiconfig: AIConfig }>;
save: (aiconfig: AIConfig) => Promise<void>;
setNameDescription: (val: {
name?: string;
description?: string | null;
}) => Promise<void>;
updateModel: (
promptName?: string,
modelData?: string | ModelMetadata
Expand Down Expand Up @@ -379,6 +377,25 @@ export default function EditorContainer({
[runPromptCallback]
);

const setNameDescriptionCallback = callbacks.setNameDescription;
const onSetNameDescription = useCallback(
async ({
name,
description,
}: {
name?: string;
description?: string | null;
}) => {
dispatch({
type: "SET_NAME_DESCRIPTION",
name,
description,
});
await setNameDescriptionCallback({ name, description });
},
[setNameDescriptionCallback]
);

const { classes } = useStyles();

const getState = useCallback(() => stateRef.current, []);
Expand All @@ -392,14 +409,16 @@ export default function EditorContainer({
return (
<AIConfigContext.Provider value={contextValue}>
<Container maw="80rem">
<Group grow m="sm">
{/* <Text sx={{ textOverflow: "ellipsis", overflow: "hidden" }} size={14}>
{path || "No path specified"}
</Text> */}
<Button loading={isSaving} ml="lg" onClick={onSave}>
<Flex justify="flex-end" mt="md" mb="xs">
<Button loading={isSaving} onClick={onSave}>
Save
</Button>
</Group>
</Flex>
<ConfigNameDescription
name={aiconfigState.name}
description={aiconfigState.description}
setNameDescription={onSetNameDescription}
/>
</Container>
<GlobalParametersContainer
initialValue={aiconfigState?.metadata?.parameters ?? {}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type MutateAIConfigAction =
| AddPromptAction
| DeletePromptAction
| RunPromptAction
| SetNameDescriptionAction
| UpdatePromptInputAction
| UpdatePromptNameAction
| UpdatePromptModelAction
Expand Down Expand Up @@ -39,6 +40,12 @@ export type RunPromptAction = {
id: string;
};

export type SetNameDescriptionAction = {
type: "SET_NAME_DESCRIPTION";
name?: string;
description?: string | null;
};

export type UpdatePromptInputAction = {
type: "UPDATE_PROMPT_INPUT";
id: string;
Expand Down Expand Up @@ -192,6 +199,16 @@ export default function aiconfigReducer(
},
}));
}
case "SET_NAME_DESCRIPTION": {
return {
...state,
name: action.name ?? state.name, // Don't allow setting name to empty string
description:
action.description === null
? undefined
: action.description ?? state.description,
};
}
case "UPDATE_PROMPT_INPUT": {
return reduceReplaceInput(state, action.id, () => action.input);
}
Expand Down
1 change: 1 addition & 0 deletions python/src/aiconfig/editor/client/src/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const ROUTE_TABLE = {
ADD_PROMPT: urlJoin(API_ENDPOINT, "/add_prompt"),
DELETE_PROMPT: urlJoin(API_ENDPOINT, "/delete_prompt"),
SAVE: urlJoin(API_ENDPOINT, "/save"),
SET_NAME_DESCRIPTION: urlJoin(API_ENDPOINT, "/update_name_description"),
LOAD: urlJoin(API_ENDPOINT, "/load"),
LIST_MODELS: urlJoin(API_ENDPOINT, "/list_models"),
RUN_PROMPT: urlJoin(API_ENDPOINT, "/run"),
Expand Down

0 comments on commit 4e57693

Please sign in to comment.