Skip to content

Commit

Permalink
[editor] JSON Renderer for Prompt Input (#686)
Browse files Browse the repository at this point in the history
[editor] JSON Renderer for Prompt Input

# [editor] JSON Renderer for Prompt Input

Add a toggle-able JSON editor for prompt inputs so that users can
see/modify the input JSON directly. This sets up the JSONEditor
component which can be reused in other places (e.g. prompt model
settings). This component takes in a schema object to provide validation
of the input -- we should investigate and update the implementation to
just take a reference to a definition in our config schema instead of
needing to duplicate the schema definition for PromptInput but I wasn't
able to get that working within a reasonable time box.



https://github.com/lastmile-ai/aiconfig/assets/5060851/c5e14ac0-0771-41f5-adea-4369b886ef00

---
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with
[ReviewStack](https://reviewstack.dev/lastmile-ai/aiconfig/pull/686).
* __->__ #686
* #682
* #681
* #680
  • Loading branch information
rholinshead authored Jan 2, 2024
2 parents 7917449 + 23c951a commit acca45d
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 24 deletions.
3 changes: 2 additions & 1 deletion python/src/aiconfig/editor/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@mantine/modals": "^6.0.7",
"@mantine/notifications": "^6.0.7",
"@mantine/prism": "^6.0.7",
"@monaco-editor/react": "^4.6.0",
"@tabler/icons-react": "^2.44.0",
"aiconfig": "../../../../../typescript",
"lodash": "^4.17.21",
Expand All @@ -57,4 +58,4 @@
"eslint-plugin-react-hooks": "^4.6.0",
"typescript": "^5"
}
}
}
78 changes: 78 additions & 0 deletions python/src/aiconfig/editor/client/src/components/JSONEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { createStyles, useMantineTheme } from "@mantine/core";
import { Editor, Monaco } from "@monaco-editor/react";
import { JSONObject, JSONValue } from "aiconfig";
import { memo } from "react";

type Props = {
content: JSONValue;
onChangeContent: (value: JSONValue) => void;
schema?: JSONObject;
};

const useStyles = createStyles(() => ({
monacoEditor: {
minHeight: "300px",
},
}));

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function configureEditor(
_editor: Monaco["editor"]["IStandaloneCodeEditor"],
monaco: Monaco,
schema?: JSONObject
) {
// Validate the text against PromptInput schema
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
schemas: [
{
uri: "https://json.schemastore.org/aiconfig-1.0",
fileMatch: ["*"],
// schema: {
// $ref: "#/definitions/PromptInput",
// }
// TODO: Figure out how to reference PromptInput definition from the uri schema
// Getting the following error:
// $ref '/definitions/PromptInput' in 'https://json.schemastore.org/aiconfig-1.0' can not be resolved.(768)

schema,
},
],
enableSchemaRequest: true,
});
}

export default memo(function JSONEditor({
content,
onChangeContent,
schema,
}: Props) {
const theme = useMantineTheme();
const { classes } = useStyles();

return (
<Editor
defaultLanguage="json"
value={JSON.stringify(content, null, 2)}
onChange={(value) => {
if (!value) {
return;
}
try {
const updatedContent = JSON.parse(value);
onChangeContent(updatedContent);
} catch (e) {
return;
}
}}
theme={theme.colorScheme === "dark" ? "vs-dark" : undefined}
className={classes.monacoEditor}
options={{
lineNumbers: false,
minimap: { enabled: false },
wordWrap: "on",
}}
onMount={(editor, monaco) => configureEditor(editor, monaco, schema)}
/>
);
});
24 changes: 24 additions & 0 deletions python/src/aiconfig/editor/client/src/components/JSONRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Prism } from "@mantine/prism";
import { JSONObject, JSONValue } from "aiconfig";
import { memo } from "react";
import JSONEditor from "./JSONEditor";

type Props = {
content: JSONValue;
onChange?: (value: JSONValue) => void;
schema?: JSONObject;
};

export default memo(function JSONRenderer({
content,
onChange,
schema,
}: Props) {
return !onChange ? (
<Prism language="json" styles={{ code: { textWrap: "pretty" } }}>
{JSON.stringify(content, null, 2)}
</Prism>
) : (
<JSONEditor content={content} onChangeContent={onChange} schema={schema} />
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { JSONValue, PromptInput } from "aiconfig";
import { memo, useCallback } from "react";
import JSONRenderer from "../../JSONRenderer";

type Props = {
input: PromptInput;
onChangeInput: (value: PromptInput) => void;
};

const PROMPT_INPUT_SCHEMA = {
anyOf: [
{
type: "object",
additionalProperties: {},
properties: {
data: {
description:
"Input to the model. This can represent a single input, or multiple inputs.\nThe structure of the data object is up to the ModelParser. Attachments field should be leveraged for non-text inputs (e.g. image, audio).",
},
attachments: {
description:
"Used to include non-text inputs (e.g. image, audio) of specified MIME types in the prompt",
type: "array",
items: {
$ref: "#/definitions/Attachment",
},
},
},
},
{
type: "string",
},
],
definitions: {
Attachment: {
description: "Data of specified MIME type to attach to a prompt",
type: "object",
required: ["data"],
properties: {
mime_type: {
description:
"MIME type of the attachment. If not specified, the MIME type will be assumed to be text/plain.",
type: "string",
},
data: {
description: "Data representing the attachment",
},
metadata: {
description: "Attachment metadata.",
type: "object",
additionalProperties: {},
},
},
},
},
};

export default memo(function PromptInputJSONRenderer({
input,
onChangeInput,
}: Props) {
const onChange = useCallback(
(value: JSONValue) => {
onChangeInput(value as PromptInput);
},
[onChangeInput]
);

return (
<JSONRenderer
content={input}
onChange={onChange}
schema={PROMPT_INPUT_SCHEMA}
/>
);
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { PromptInput } from "aiconfig";
import { memo } from "react";
import { memo, useState } from "react";
import { PromptInputSchema } from "../../../utils/promptUtils";
import PromptInputSchemaRenderer from "./schema_renderer/PromptInputSchemaRenderer";
import PromptInputConfigRenderer from "./PromptInputConfigRenderer";
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
import { IconBraces, IconBracesOff } from "@tabler/icons-react";
import PromptInputJSONRenderer from "./PromptInputJSONRenderer";

type Props = {
input: PromptInput;
Expand All @@ -15,13 +18,34 @@ export default memo(function PromptInputRenderer({
schema,
onChangeInput,
}: Props) {
return schema ? (
<PromptInputSchemaRenderer
input={input}
schema={schema}
onChangeInput={onChangeInput}
/>
) : (
<PromptInputConfigRenderer input={input} onChangeInput={onChangeInput} />
const [isRawJSON, setIsRawJSON] = useState(false);
return (
<>
<Flex justify="flex-end">
<Tooltip label="Toggle JSON editor" withArrow>
<ActionIcon onClick={() => setIsRawJSON((curr) => !curr)}>
{isRawJSON ? (
<IconBracesOff size="1rem" />
) : (
<IconBraces size="1rem" />
)}
</ActionIcon>
</Tooltip>
</Flex>
{isRawJSON ? (
<PromptInputJSONRenderer input={input} onChangeInput={onChangeInput} />
) : schema ? (
<PromptInputSchemaRenderer
input={input}
schema={schema}
onChangeInput={onChangeInput}
/>
) : (
<PromptInputConfigRenderer
input={input}
onChangeInput={onChangeInput}
/>
)}
</>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import {
} from "../../../../utils/promptUtils";
import DataRenderer from "./PromptInputDataSchemaRenderer";
import AttachmentsRenderer from "./PromptInputAttachmentsSchemaRenderer";
import { Flex, Textarea } from "@mantine/core";
import { Flex, Text, Textarea } from "@mantine/core";
import { Attachment, JSONValue, PromptInput } from "aiconfig";
import { memo } from "react";
import JSONRenderer from "../../../JSONRenderer";

type Props = {
input: PromptInput;
Expand Down Expand Up @@ -61,15 +62,17 @@ function SchemaRenderer({ input, schema, onChangeInput }: SchemaRendererProps) {

export default memo(function PromptInputSchemaRenderer(props: Props) {
if (props.schema.type === "string") {
// TODO: Add ErrorBoundary handling
// if (props.input && typeof props.input !== "string") {
// throw new Error(
// `Expected input to be a string, but got ${typeof props.input}`
// );
// }
if (props.input && typeof props.input !== "string") {
return (
<>
<Text color="red">Expected input type string</Text>
<JSONRenderer content={props.input} />
</>
);
}
return (
<Textarea
value={props.input as string}
value={props.input}
onChange={(e) => props.onChangeInput(e.target.value)}
placeholder="Type a prompt"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { Prism } from "@mantine/prism";
import { JSONValue } from "aiconfig";
import { memo } from "react";
import JSONRenderer from "../../JSONRenderer";

export default memo(function JSONOutput({ content }: { content: JSONValue }) {
return (
<Prism language="json" styles={{ code: { textWrap: "pretty" } }}>
{JSON.stringify(content, null, 2)}
</Prism>
);
return <JSONRenderer content={content} />;
});
19 changes: 19 additions & 0 deletions python/src/aiconfig/editor/client/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1856,6 +1856,20 @@
resolved "https://registry.yarnpkg.com/@mantine/utils/-/utils-6.0.21.tgz#6185506e91cba3e308aaa8ea9ababc8e767995d6"
integrity sha512-33RVDRop5jiWFao3HKd3Yp7A9mEq4HAJxJPTuYm1NkdqX6aTKOQK7wT8v8itVodBp+sb4cJK6ZVdD1UurK/txQ==

"@monaco-editor/loader@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.4.0.tgz#f08227057331ec890fa1e903912a5b711a2ad558"
integrity sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==
dependencies:
state-local "^1.0.6"

"@monaco-editor/react@^4.6.0":
version "4.6.0"
resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.6.0.tgz#bcc68671e358a21c3814566b865a54b191e24119"
integrity sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==
dependencies:
"@monaco-editor/loader" "^1.4.0"

"@next/[email protected]":
version "14.0.2"
resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-14.0.2.tgz#421799f46116d8032f1739ce5ce89822453c8f03"
Expand Down Expand Up @@ -10417,6 +10431,11 @@ stackframe@^1.3.4:
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310"
integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==

state-local@^1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/state-local/-/state-local-1.0.7.tgz#da50211d07f05748d53009bee46307a37db386d5"
integrity sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==

[email protected]:
version "2.0.2"
resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-2.0.2.tgz#2d1759306b1befa688938454c546b7871f806a42"
Expand Down

0 comments on commit acca45d

Please sign in to comment.