Skip to content

Commit

Permalink
[editor] Add Output Error Rendering (#803)
Browse files Browse the repository at this point in the history
[editor] Add Output Error Rendering

# [editor] Add Output Error Rendering

Adding some basic rendering for Error output types if they're ever added
to any configs. Also, propagate server run errors through the client as
(client-only) Error outputs to display for the relevant prompt:

## Testing:
- Raise an exception in the api/run method on the server and ensure it
propagates to the output
<img width="992" alt="Screenshot 2024-01-06 at 4 33 55 PM"
src="https://github.com/lastmile-ai/aiconfig/assets/5060851/93309f64-4ce5-47dd-b553-c6fb44daaca0">

---
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with
[ReviewStack](https://reviewstack.dev/lastmile-ai/aiconfig/pull/803).
* __->__ #803
* #802
* #801
* #800
* #799
  • Loading branch information
rholinshead authored Jan 8, 2024
2 parents 54de9cc + c179509 commit 2bc5d60
Show file tree
Hide file tree
Showing 14 changed files with 199 additions and 59 deletions.
1 change: 1 addition & 0 deletions python/src/aiconfig/editor/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"oboe": "^2.1.5",
"react": "^18",
"react-dom": "^18",
"react-error-boundary": "^4.0.12",
"react-markdown": "^8.0.6",
"react-scripts": "5.0.1",
"remark-gfm": "^4.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,6 @@ export default function EditorContainer({
} catch (err: unknown) {
const message = (err as RequestCallbackError).message ?? null;

// TODO: Add ErrorOutput component to show error instead of notification
dispatch({
type: "RUN_PROMPT_ERROR",
id: promptId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ActionIcon, Tooltip } from "@mantine/core";
import { IconBraces, IconBracesOff } from "@tabler/icons-react";

type Props = {
isRawJSON: boolean;
setIsRawJSON: (value: boolean) => void;
};

export default function JSONEditorToggleButton({
isRawJSON,
setIsRawJSON,
}: Props) {
return (
<Tooltip label="Toggle JSON editor" withArrow>
<ActionIcon onClick={() => setIsRawJSON(!isRawJSON)}>
{isRawJSON ? <IconBracesOff size="1rem" /> : <IconBraces size="1rem" />}
</ActionIcon>
</Tooltip>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,14 @@ export default function aiconfigReducer(
...prompt._ui,
isRunning: false,
},
outputs: [
{
output_type: "error",
ename: "Error",
evalue: action.message ?? "Error running prompt",
traceback: [],
},
],
}));
}
case "SAVE_CONFIG_SUCCESS": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, Loader, Text } from "@mantine/core";
import { Button, Flex, Loader, Text } from "@mantine/core";
import { IconPlayerPlayFilled, IconPlayerStop } from "@tabler/icons-react";
import { memo } from "react";

Expand All @@ -23,14 +23,10 @@ export default memo(function RunPromptButton({
className="runPromptButton"
>
{isRunning ? (
<div>
<Loader
style={{ position: "absolute", top: 5, left: 8 }}
size="xs"
color="white"
/>
<Flex align="center" justify="center">
<Loader style={{ position: "absolute" }} size="xs" color="white" />
<IconPlayerStop fill="white" size={14} />
</div>
</Flex>
) : (
<>
<IconPlayerPlayFilled size="16" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import ModelSettingsSchemaRenderer from "./ModelSettingsSchemaRenderer";
import { GenericPropertiesSchema } from "../../../utils/promptUtils";
import { ActionIcon, Flex, Tooltip, createStyles } from "@mantine/core";
import { Flex, Text, createStyles } from "@mantine/core";
import { JSONObject } from "aiconfig";
import { memo, useState } from "react";
import { IconBraces, IconBracesOff } from "@tabler/icons-react";
import JSONRenderer from "../../JSONRenderer";
import JSONEditorToggleButton from "../../JSONEditorToggleButton";
import { ErrorBoundary, useErrorBoundary } from "react-error-boundary";

type Props = {
settings?: JSONObject;
Expand All @@ -22,6 +23,35 @@ const useStyles = createStyles(() => ({
},
}));

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

function SettingsErrorFallback({
settings,
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 settings format for model. Toggle JSON editor to update
</Text>
<JSONRenderer content={settings} />
</Flex>
);
}

export default memo(function ModelSettingsRenderer({
settings,
schema,
Expand All @@ -30,37 +60,45 @@ export default memo(function ModelSettingsRenderer({
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.settingsContainer}>
{/* // TODO: Refactor this out to a generic wrapper for toggling JSONRenderer or children component */}
{/* // Only show the toggle if there is a schema to toggle between JSON and custom schema renderer */}
{schema && (
<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 || !schema ? (
<JSONRenderer
content={settings}
onChange={(val) =>
onUpdateModelSettings(val as Record<string, unknown>)
}
// schema={schema} TODO: Add schema after fixing z-index issue
/>
<>
{/* // Only show the toggle if there is a schema to toggle between JSON and custom schema renderer */}
{schema && rawJSONToggleButton}
<JSONRenderer
content={settings}
onChange={(val) =>
onUpdateModelSettings(val as Record<string, unknown>)
}
// schema={schema} TODO: Add schema after fixing z-index issue
/>
</>
) : (
<ModelSettingsSchemaRenderer
settings={settings}
schema={schema}
onUpdateModelSettings={onUpdateModelSettings}
/>
<ErrorBoundary
fallbackRender={() => (
<SettingsErrorFallback
settings={settings}
toggleJSONEditor={() => setIsRawJSON(true)}
/>
)}
>
{rawJSONToggleButton}
<ModelSettingsSchemaRenderer
settings={settings}
schema={schema}
onUpdateModelSettings={onUpdateModelSettings}
/>
</ErrorBoundary>
)}
</Flex>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,63 @@ 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 { Flex } from "@mantine/core";
import PromptInputJSONRenderer from "./PromptInputJSONRenderer";
import { ErrorBoundary, useErrorBoundary } from "react-error-boundary";
import { Text } from "@mantine/core";
import JSONRenderer from "../../JSONRenderer";
import JSONEditorToggleButton from "../../JSONEditorToggleButton";

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

type ErrorFallbackProps = {
input: PromptInput;
toggleJSONEditor: () => void;
};

function InputErrorFallback({ input, toggleJSONEditor }: ErrorFallbackProps) {
const { resetBoundary: clearRenderError } = useErrorBoundary();
return (
<Flex direction="column">
<Text color="red" size="sm">
Invalid input format for model. Toggle JSON editor to update
</Text>
<JSONRenderer content={input} />
<Flex justify="flex-end">
<JSONEditorToggleButton
isRawJSON={false}
setIsRawJSON={() => {
clearRenderError();
toggleJSONEditor();
}}
/>
</Flex>
</Flex>
);
}

export default memo(function PromptInputRenderer({
input,
schema,
onChangeInput,
}: Props) {
const [isRawJSON, setIsRawJSON] = useState(false);
return (
const rawJSONToggleButton = (
<Flex justify="flex-end">
<JSONEditorToggleButton
isRawJSON={isRawJSON}
setIsRawJSON={setIsRawJSON}
/>
</Flex>
);

const nonJSONRenderer = (
<>
{isRawJSON ? (
<PromptInputJSONRenderer input={input} onChangeInput={onChangeInput} />
) : schema ? (
{schema ? (
<PromptInputSchemaRenderer
input={input}
schema={schema}
Expand All @@ -35,17 +71,34 @@ export default memo(function PromptInputRenderer({
onChangeInput={onChangeInput}
/>
)}
<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>
{rawJSONToggleButton}
</>
);

return (
<>
{isRawJSON ? (
<>
<PromptInputJSONRenderer
input={input}
onChangeInput={onChangeInput}
/>
{rawJSONToggleButton}
</>
) : (
<ErrorBoundary
fallbackRender={() => (
<InputErrorFallback
input={input}
// Fallback is only shown when an error occurs in non-JSON renderer
// so toggle must be to JSON editor
toggleJSONEditor={() => setIsRawJSON(true)}
/>
)}
>
{nonJSONRenderer}
</ErrorBoundary>
)}
</>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ function SchemaRenderer({ input, schema, onChangeInput }: SchemaRendererProps) {
} = schema.properties;

if (typeof input === "string") {
return null;
// TODO: Add ErrorBoundary handling and throw error here
throw new Error("Expected input type object but got string");
}

const { data, attachments, ..._restData } = input;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@ import { TextRenderer } from "../TextRenderer";
import PromptOutputWrapper from "./PromptOutputWrapper";
import MimeTypeRenderer from "../../MimeTypeRenderer";
import JSONRenderer from "../../JSONRenderer";
import { Alert, Flex } from "@mantine/core";

type Props = {
outputs: Output[];
};

function ErrorOutput({ output }: { output: Error }) {
return <div>{output.evalue}</div>;
return (
<Flex direction="column">
<Alert color="red" title={output.ename}>
<TextRenderer content={output.evalue} />
<TextRenderer content={output.traceback.join("\n")} />
</Alert>
</Flex>
);
}

const ExecuteResultOutput = memo(function ExecuteResultOutput({
Expand Down Expand Up @@ -86,7 +94,6 @@ const ExecuteResultOutput = memo(function ExecuteResultOutput({
});

const OutputRenderer = memo(function Output({ output }: { output: Output }) {
// TODO: Add toggle for raw JSON renderer
switch (output.output_type) {
case "execute_result":
return <ExecuteResultOutput output={output} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export const DalleImageGenerationParserPromptSchema: PromptSchema = {
model_settings: {
type: "object",
properties: {
model: {
type: "string",
},
n: {
type: "integer",
minimum: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export const OpenAIChatModelParserPromptSchema: PromptSchema = {
model_settings: {
type: "object",
properties: {
model: {
type: "string",
},
system_prompt: {
type: "string",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export const PaLMChatParserPromptSchema: PromptSchema = {
model_settings: {
type: "object",
properties: {
model: {
type: "string",
},
context: {
type: "string",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export const PaLMTextParserPromptSchema: PromptSchema = {
model_settings: {
type: "object",
properties: {
model: {
type: "string",
},
candidate_count: {
type: "integer",
minimum: 1,
Expand Down
Loading

0 comments on commit 2bc5d60

Please sign in to comment.