From d1145957e381d81781bd79a55569b4461c0df42f Mon Sep 17 00:00:00 2001 From: Ryan Holinshead <> Date: Tue, 2 Jan 2024 11:56:26 -0500 Subject: [PATCH] [editor] Config Name & Description UI [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 --- .../src/aiconfig/editor/client/src/Editor.tsx | 16 ++ .../src/components/ConfigNameDescription.tsx | 137 ++++++++++++++++++ .../client/src/components/EditorContainer.tsx | 70 +++++++-- .../client/src/components/aiconfigReducer.ts | 24 +++ .../aiconfig/editor/client/src/utils/api.ts | 2 + .../editor/client/src/utils/constants.ts | 1 + 6 files changed, 234 insertions(+), 16 deletions(-) create mode 100644 python/src/aiconfig/editor/client/src/components/ConfigNameDescription.tsx create mode 100644 python/src/aiconfig/editor/client/src/utils/constants.ts diff --git a/python/src/aiconfig/editor/client/src/Editor.tsx b/python/src/aiconfig/editor/client/src/Editor.tsx index ce9bd05ef..a87bb6099 100644 --- a/python/src/aiconfig/editor/client/src/Editor.tsx +++ b/python/src/aiconfig/editor/client/src/Editor.tsx @@ -82,6 +82,18 @@ export default function Editor() { [] ); + const setConfigName = useCallback(async (name: string) => { + return await ufetch.post(ROUTE_TABLE.SET_NAME, { + name, + }); + }, []); + + const setConfigDescription = useCallback(async (description: string) => { + return await ufetch.post(ROUTE_TABLE.SET_DESCRIPTION, { + description, + }); + }, []); + const callbacks: AIConfigCallbacks = useMemo( () => ({ addPrompt, @@ -89,6 +101,8 @@ export default function Editor() { getModels, runPrompt, save, + setConfigDescription, + setConfigName, updateModel, updatePrompt, }), @@ -98,6 +112,8 @@ export default function Editor() { getModels, runPrompt, save, + setConfigDescription, + setConfigName, updateModel, updatePrompt, ] diff --git a/python/src/aiconfig/editor/client/src/components/ConfigNameDescription.tsx b/python/src/aiconfig/editor/client/src/components/ConfigNameDescription.tsx new file mode 100644 index 000000000..9da502cfd --- /dev/null +++ b/python/src/aiconfig/editor/client/src/components/ConfigNameDescription.tsx @@ -0,0 +1,137 @@ +import { + createStyles, + Stack, + Text, + Textarea, + TextInput, + Title, +} from "@mantine/core"; +import { useClickOutside } from "@mantine/hooks"; +import { memo, useCallback, useRef, useState } from "react"; + +type Props = { + name?: string; + description?: string | null; + setDescription: (description: string) => void; + setName: (name: string) => 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 ConfigNameDescription({ + name, + description, + setDescription, + setName, +}: Props) { + const { classes } = useStyles(); + + const [isEditing, setIsEditing] = useState(!name); + const [initialFocus, setInitialFocus] = useState< + "name" | "description" | null + >("name"); + + const nameDisplayRef = useRef(null); + const descriptionDisplayRef = useRef(null); + + const inputSectionRef = useClickOutside(() => { + if (name) { + setIsEditing(false); + } + }); + + const onHandleEnter = useCallback( + (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + event.stopPropagation(); + setIsEditing(false); + } + }, + [] + ); + + const onClickEdit = useCallback( + (event: React.MouseEvent) => { + setIsEditing(true); + if (event.target === nameDisplayRef.current) { + setInitialFocus("name"); + } else if (event.target === descriptionDisplayRef.current) { + setInitialFocus("description"); + } + }, + [] + ); + + return ( + + {isEditing ? ( + <> + setName(e.currentTarget.value)} + /> +