Skip to content

Commit

Permalink
[editor] Add 'Metadata' Tab to Prompt Side Pane
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryan Holinshead committed Feb 9, 2024
1 parent e6193b3 commit b7eeecc
Show file tree
Hide file tree
Showing 17 changed files with 283 additions and 44 deletions.
94 changes: 76 additions & 18 deletions python/src/aiconfig/editor/client/src/components/AIConfigEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -242,19 +242,21 @@ function AIConfigEditorBase({
async (
promptName: string,
newPrompt: Prompt,
onSuccess: (aiconfigRes: AIConfig) => void,
onError: (err: unknown) => void
callbacks: {
onSuccess?: (aiconfigRes: AIConfig) => void;
onError?: (err: unknown) => void;
}
) => {
try {
const serverConfigRes = await updatePromptCallback(
promptName,
newPrompt
);
if (serverConfigRes?.aiconfig) {
onSuccess(serverConfigRes.aiconfig);
callbacks?.onSuccess?.(serverConfigRes.aiconfig);
}
} catch (err: unknown) {
onError(err);
callbacks?.onError?.(err);
}
},
DEBOUNCE_MS
Expand Down Expand Up @@ -299,13 +301,15 @@ function AIConfigEditorBase({
...prompt,
input: newPromptInput,
},
(config) =>
dispatch({
type: "CONSOLIDATE_AICONFIG",
action,
config,
}),
onError
{
onSuccess: (config) =>
dispatch({
type: "CONSOLIDATE_AICONFIG",
action,
config,
}),
onError,
}
);
} catch (err: unknown) {
onError(err);
Expand Down Expand Up @@ -347,13 +351,15 @@ function AIConfigEditorBase({
// PromptName component maintains local state for the name to show in the UI
// We cannot update client config state until the name is successfully set server-side
// or else we could end up referencing a prompt name that is not set server-side
() =>
dispatch({
type: "UPDATE_PROMPT_NAME",
id: promptId,
name: newName,
}),
onError
{
onSuccess: () =>
dispatch({
type: "UPDATE_PROMPT_NAME",
id: promptId,
name: newName,
}),
onError,
}
);
} catch (err: unknown) {
onError(err);
Expand Down Expand Up @@ -387,6 +393,57 @@ function AIConfigEditorBase({
);
}, [updateModelCallback]);

const onUpdatePromptMetadata = useCallback(
async (promptId: string, newMetadata: JSONObject) => {
if (!debouncedUpdatePrompt) {
// Just no-op if no callback specified. We could technically perform
// client-side updates but that might be confusing
return;
}

dispatch({
type: "UPDATE_PROMPT_METADATA",
id: promptId,
metadata: newMetadata,
});

const onError = (err: unknown) => {
const message = (err as RequestCallbackError).message ?? null;
showNotification({
title: "Error updating prompt name",
message,
type: "error",
});
};

try {
const statePrompt = getPrompt(stateRef.current, promptId);
if (!statePrompt) {
throw new Error(`Could not find prompt with id ${promptId}`);
}
const prompt = clientPromptToAIConfigPrompt(statePrompt);

await debouncedUpdatePrompt(
prompt.name,
{
...prompt,
metadata: {
...newMetadata,
model: prompt.metadata?.model,
parameters: prompt.metadata?.parameters,
},
},
{
onError,
}
);
} catch (err: unknown) {
onError(err);
}
},
[debouncedUpdatePrompt, showNotification]
);

const onUpdatePromptModelSettings = useCallback(
async (promptId: string, newModelSettings: JSONObject) => {
if (!debouncedUpdateModel) {
Expand Down Expand Up @@ -1070,6 +1127,7 @@ function AIConfigEditorBase({
onChangePromptName={onChangePromptName}
onDeletePrompt={onDeletePrompt}
onRunPrompt={onRunPrompt}
onUpdatePromptMetadata={onUpdatePromptMetadata}
onUpdatePromptModel={onUpdatePromptModel}
onUpdatePromptModelSettings={onUpdatePromptModelSettings}
onUpdatePromptParameters={onUpdatePromptParameters}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ export default function SettingsPropertyRenderer({
setAndPropagateValue(event.currentTarget.checked)
}
disabled={readOnly}
styles={{ inner: { alignSelf: "center" } }}
/>
);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Props = {
promptSchema?: PromptSchema;
onUpdateModelSettings: (settings: Record<string, unknown>) => void;
onUpdateParameters: (parameters: JSONObject) => void;
onUpdatePromptMetadata: (metadata: JSONObject) => void;
};

// Don't default to config-level model settings since that could be confusing
Expand All @@ -27,6 +28,22 @@ function getModelSettings(prompt: ClientPrompt) {
}
}

function getMetadata(prompt: ClientPrompt) {
const metadata = { ...prompt.metadata };

// Model and parameters handled as their own tabs
delete metadata.model;
delete metadata.parameters;

// Tags are set to null by default by python sdk. For now, exclude if null
// We may want to add special UI for tags later
if (metadata.tags === null) {
delete metadata.tags;
}

return metadata;
}

function getPromptParameters(prompt: ClientPrompt) {
return prompt.metadata?.parameters;
}
Expand All @@ -45,6 +62,7 @@ export default memo(function PromptActionBar({
promptSchema,
onUpdateModelSettings,
onUpdateParameters,
onUpdatePromptMetadata,
}: Props) {
const [isExpanded, setIsExpanded] = useState(false);
// TODO: Handle drag-to-resize
Expand Down Expand Up @@ -73,6 +91,7 @@ export default memo(function PromptActionBar({
{checkParametersSupported(prompt) && (
<Tabs.Tab value="parameters">Local Parameters</Tabs.Tab>
)}
<Tabs.Tab value="metadata">Metadata</Tabs.Tab>
</Tabs.List>

<Tabs.Panel value="settings" className="actionTabsPanel">
Expand All @@ -86,10 +105,6 @@ export default memo(function PromptActionBar({
schema={modelSettingsSchema}
onUpdateModelSettings={onUpdateModelSettings}
/>
<PromptMetadataRenderer
prompt={prompt}
schema={promptMetadataSchema}
/>
</ScrollArea>
</Tabs.Panel>

Expand All @@ -106,6 +121,13 @@ export default memo(function PromptActionBar({
/>
</Tabs.Panel>
)}
<Tabs.Panel value="metadata" className="actionTabsPanel">
<PromptMetadataRenderer
metadata={getMetadata(prompt)}
onUpdatePromptMetadata={onUpdatePromptMetadata}
schema={promptMetadataSchema}
/>
</Tabs.Panel>
</Tabs>
</Container>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Props = {
promptId: string,
newParameters: Record<string, unknown>
) => void;
onUpdatePromptMetadata: (promptId: string, newMetadata: JSONObject) => void;
defaultConfigModelName?: string;
isRunButtonDisabled?: boolean;
};
Expand All @@ -48,6 +49,7 @@ export default memo(function PromptContainer({
onUpdateModel,
onUpdateModelSettings,
onUpdateParameters,
onUpdatePromptMetadata,
isRunButtonDisabled = false,
}: Props) {
const promptId = prompt._ui.id;
Expand All @@ -72,6 +74,11 @@ export default memo(function PromptContainer({
[promptId, onUpdateParameters]
);

const updatePromptMetadata = useCallback(
(metadata: JSONObject) => onUpdatePromptMetadata(promptId, metadata),
[promptId, onUpdatePromptMetadata]
);

const runPrompt = useCallback(
async () => await onRunPrompt(promptId),
[promptId, onRunPrompt]
Expand Down Expand Up @@ -171,6 +178,7 @@ export default memo(function PromptContainer({
promptSchema={promptSchema}
onUpdateModelSettings={updateModelSettings}
onUpdateParameters={updateParameters}
onUpdatePromptMetadata={updatePromptMetadata}
/>
</div>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ type Props = {
onChangePromptName: (promptId: string, newName: string) => Promise<void>;
onDeletePrompt: (promptId: string) => Promise<void>;
onRunPrompt: (promptId: string) => Promise<void>;
onUpdatePromptMetadata: (
promptId: string,
newMetadata: JSONObject
) => Promise<void>;
onUpdatePromptModel: (promptId: string, newModel?: string) => Promise<void>;
onUpdatePromptModelSettings: (
promptId: string,
Expand Down Expand Up @@ -76,6 +80,7 @@ export default memo(function PromptsContainer(props: Props) {
onUpdateModel={props.onUpdatePromptModel}
onUpdateModelSettings={props.onUpdatePromptModelSettings}
onUpdateParameters={props.onUpdatePromptParameters}
onUpdatePromptMetadata={props.onUpdatePromptMetadata}
defaultConfigModelName={props.defaultModel}
isRunButtonDisabled={isAnotherPromptRunning}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,103 @@
import { ClientPrompt } from "../../../shared/types";
import { GenericPropertiesSchema } from "../../../utils/promptUtils";
import { memo } from "react";
import { memo, useState } from "react";
import JSONRenderer from "../../JSONRenderer";
import { JSONObject } from "aiconfig";
import { Flex, Text, createStyles } from "@mantine/core";
import JSONEditorToggleButton from "../../JSONEditorToggleButton";
import { ErrorBoundary, useErrorBoundary } from "react-error-boundary";
import PromptMetadataSchemaRenderer from "./PromptMetadataSchemaRenderer";

type Props = {
prompt: ClientPrompt;
metadata?: JSONObject;
onUpdatePromptMetadata: (metadata: JSONObject) => void;
schema?: GenericPropertiesSchema;
};

function ModelMetadataConfigRenderer(_props: Props) {
return null; // TODO: Implement
}
const useStyles = createStyles(() => ({
metadataContainer: {
overflow: "auto",
paddingTop: "0.5em",
width: "100%",
},
}));

type ErrorFallbackProps = {
metadata?: JSONObject;
toggleJSONEditor: () => void;
};

function ModelMetadataSchemaRenderer(_props: Props) {
return null; // TODO: Implement
function MetadataErrorFallback({
metadata,
toggleJSONEditor,
}: ErrorFallbackProps) {
const { resetBoundary: clearRenderError } = useErrorBoundary();
return (
<Flex direction="column">
<Text color="red" size="sm">
<Flex justify="flex-end">
<JSONEditorToggleButton
isRawJSON={false}
setIsRawJSON={() => {
clearRenderError();
toggleJSONEditor();
}}
/>
</Flex>
Invalid metadata format for model. Toggle JSON editor to update. Set to
{" {}"} in JSON editor and toggle back to reset.
</Text>
<JSONRenderer content={metadata} />
</Flex>
);
}

export default memo(function PromptMetadataRenderer({ prompt, schema }: Props) {
return schema ? (
<ModelMetadataSchemaRenderer prompt={prompt} schema={schema} />
) : (
<ModelMetadataConfigRenderer prompt={prompt} />
export default memo(function PromptMetadataRenderer({
metadata,
onUpdatePromptMetadata,
schema,
}: Props) {
const { classes } = useStyles();
const [isRawJSON, setIsRawJSON] = useState(schema == null);

const rawJSONToggleButton = (
<Flex justify="flex-end">
<JSONEditorToggleButton
isRawJSON={isRawJSON}
setIsRawJSON={setIsRawJSON}
/>
</Flex>
);

return (
<Flex direction="column" className={classes.metadataContainer}>
{isRawJSON || !schema ? (
<>
{/* // Only show the toggle if there is a schema to toggle between JSON and custom schema renderer */}
{schema && rawJSONToggleButton}
<JSONRenderer
content={metadata}
onChange={(val) =>
onUpdatePromptMetadata(val as Record<string, unknown>)
}
/>
</>
) : (
<ErrorBoundary
fallbackRender={() => (
<MetadataErrorFallback
metadata={metadata}
toggleJSONEditor={() => setIsRawJSON(true)}
/>
)}
>
{rawJSONToggleButton}
<PromptMetadataSchemaRenderer
metadata={metadata}
schema={schema}
onUpdatePromptMetadata={onUpdatePromptMetadata}
/>
</ErrorBoundary>
)}
</Flex>
);
});
Loading

0 comments on commit b7eeecc

Please sign in to comment.