Revert examples (#110)

We'll introduce this with release 0.0.3
This commit is contained in:
Eugene Yurtsev
2024-03-25 15:34:40 -04:00
committed by GitHub
parent 58cc2d3a1a
commit fc85d25117
4 changed files with 148 additions and 434 deletions
-183
View File
@@ -1,183 +0,0 @@
"use client";
import {
AbsoluteCenter,
Box,
Button,
Divider,
IconButton,
Table,
TableCaption,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
} from "@chakra-ui/react";
import { TrashIcon } from "@heroicons/react/24/outline";
import Form from "@rjsf/chakra-ui";
import validator from "@rjsf/validator-ajv8";
import { docco } from "react-syntax-highlighter/dist/esm/styles/hljs";
import SyntaxHighlighter from "react-syntax-highlighter";
import {
useCreateExample,
useDeleteExample,
useGetExtractor,
useListExamples,
} from "../utils/api";
type ExtractorProps = {
extractorId: string;
isShared: boolean;
};
/**
* Component to view and manage an extractor.
*/
const Examples = ({ extractorId }: { extractorId: string }) => {
const listExamplesQuery = useListExamples({
extractor_id: extractorId,
limit: 10,
offset: 0, // Hard code pagination for now
});
const useDeleteMutation = useDeleteExample();
if (listExamplesQuery.isLoading) {
return <div>Loading...</div>;
}
if (listExamplesQuery.isError) {
return (
<div>Unable to load examples for extractor with ID: {extractorId}</div>
);
}
const data = listExamplesQuery.data;
return (
<div className="w-5/5">
{data && data.length === 0 ? (
<Text>Use the form below to add examples to the extractor</Text>
) : (
<Table variant="simple">
<TableCaption placement={"top"}>
View and Delete Examples
</TableCaption>
<Thead>
<Tr>
<Th>Content</Th>
<Th>Output</Th>
<Th>Action</Th>
</Tr>
</Thead>
<Tbody>
{data &&
data.map((example) => (
<Tr key={example.uuid}>
<Td>{example.content}</Td>
<Td>
<SyntaxHighlighter language="json" style={docco}>
{JSON.stringify(example.output, null, 2)}
</SyntaxHighlighter>
</Td>
<Td>
<IconButton
onClick={() =>
useDeleteMutation.mutate({ uuid: example.uuid })
}
variant={"outline"}
isLoading={useDeleteMutation.isPending}
colorScheme="red"
aria-label="Delete example"
icon={<TrashIcon />}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
)}
</div>
);
};
export const ExampleEditor = ({ extractorId, isShared }: ExtractorProps) => {
const { data, isLoading, isError } = useGetExtractor(extractorId, isShared);
const createExampleMutation = useCreateExample();
if (isLoading) {
return <div>Loading...</div>;
}
if (isError) {
return <div>Unable to load extractor with ID: {extractorId}</div>;
}
if (data === undefined) {
throw new Error("Data is undefined");
}
const newSchema = {
type: "object",
properties: {
content: {
type: "string",
title: "Content",
},
output: {
// Array of data.schema
type: "array",
items: {
...data.schema,
title: undefined,
description: undefined,
},
},
},
required: ["content"],
};
const UISchema = {
content: {
"ui:autoFocus": true,
"ui:placeholder": "Enter contact to extract",
"ui:widget": "textarea",
"ui:description": "Enter the text to extract information from",
},
output: {
"ui:autoFocus": false,
"ui:title": "Outputs",
},
};
const handleAddExample = (
/* eslint-disable @typescript-eslint/no-explicit-any */
formData: any,
) => {
createExampleMutation.mutate({
extractor_id: extractorId,
content: formData.formData.content,
output: formData.formData.output,
});
};
return (
<div className="mr-auto">
<Examples extractorId={extractorId} />
<Box position="relative" padding="10">
<Divider />
<AbsoluteCenter bg="white" px="4">
<Text>Add</Text>
</AbsoluteCenter>
</Box>
<Form
// @ts-expect-error - Need to investigate
schema={newSchema}
uiSchema={UISchema}
validator={validator}
onSubmit={(formData) => handleAddExample(formData)}
>
<div>
<Button type="submit">Add Example</Button>
</div>
</Form>
</div>
);
};
+62
View File
@@ -0,0 +1,62 @@
"use client";
import {
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
Text,
} from "@chakra-ui/react";
import Form from "@rjsf/chakra-ui";
import validator from "@rjsf/validator-ajv8";
import { docco } from "react-syntax-highlighter/dist/esm/styles/hljs";
import SyntaxHighlighter from "react-syntax-highlighter";
import { useGetExtractor } from "../utils/api";
type ExtractorProps = {
extractorId: string;
isShared: boolean;
};
export const Extractor = ({ extractorId, isShared }: ExtractorProps) => {
const { data, isLoading, isError } = useGetExtractor(extractorId, isShared);
if (isLoading) {
return <div>Loading...</div>;
}
if (isError) {
return <div>Unable to load extractor with ID: {extractorId}</div>;
}
if (data === undefined) {
throw new Error("Data is undefined");
}
return (
<div className="mr-auto">
<Tabs className="mt-5" variant={"enclosed"} colorScheme="blue" size="sm">
<TabList>
<Tab>Form</Tab>
<Tab>Code</Tab>
</TabList>
<TabPanels>
<TabPanel>
<Form schema={data.schema} validator={validator}>
{true} {/* Disables the submit button */}
</Form>
</TabPanel>
<TabPanel>
<Text className="mt-1 mb-5">
This shows the raw JSON Schema that describes what information the
extractor will be extracting from the content.
</Text>
<SyntaxHighlighter language="json" style={docco}>
{JSON.stringify(data.schema, null, 2)}
</SyntaxHighlighter>
</TabPanel>
</TabPanels>
</Tabs>
</div>
);
};
+86 -158
View File
@@ -1,33 +1,29 @@
"use client";
import {
AbsoluteCenter,
Badge,
Box,
Button,
Divider,
FormControl,
FormLabel,
HStack,
Heading,
Radio,
RadioGroup,
Spinner,
Tab,
Box,
Divider,
AbsoluteCenter,
TabList,
TabPanel,
TabPanels,
Tabs,
Text,
Textarea,
FormControl,
FormLabel,
HStack,
Radio,
RadioGroup,
} from "@chakra-ui/react";
import Form from "@rjsf/chakra-ui";
import validator from "@rjsf/validator-ajv8";
import { useMutation } from "@tanstack/react-query";
import React from "react";
import SyntaxHighlighter from "react-syntax-highlighter";
import { docco } from "react-syntax-highlighter/dist/esm/styles/hljs";
import { runExtraction, useConfiguration, useGetExtractor } from "../utils/api";
import { ExampleEditor } from "./ExampleEditor";
import { runExtraction, useConfiguration } from "../utils/api";
import { Extractor } from "./Extractor";
import { ResultsTable } from "./ResultsTable";
interface PlaygroundProps {
@@ -48,8 +44,6 @@ export const Playground = (props: PlaygroundProps) => {
mutationFn: runExtraction,
});
const extractorQuery = useGetExtractor(extractorId, isShared);
const requestServerConfig = useConfiguration();
const [isDisabled, setIsDisabled] = React.useState(true);
@@ -103,163 +97,97 @@ export const Playground = (props: PlaygroundProps) => {
setIsDisabled(false);
};
if (extractorQuery.isLoading) {
return (
<Spinner
thickness="4px"
speed="0.65s"
emptyColor="gray.200"
color="blue.500"
size="xl"
/>
);
}
if (extractorQuery.isError) {
return <div>Unable to load extractor with ID: {extractorId}</div>;
}
if (extractorQuery.data === undefined) {
throw new Error("Data is undefined");
}
// Project the schema without the title, name, description
const cleanedSchema = {
...extractorQuery.data.schema,
title: undefined,
description: undefined,
};
return (
<div className="w-full flex-col justify-between">
<div className="m-auto">
<Heading size="md" className="m-auto w-4/5" textAlign="center">
{extractorQuery.data?.name}
{isShared && (
<Badge
colorScheme="orange"
fontSize="xl"
fontWeight={"bold"}
marginLeft={2}
>
SHARED
</Badge>
{isShared && <Heading>Using a shared exractor</Heading>}
<div>
<Extractor extractorId={extractorId} isShared={isShared} />
</div>
<Heading>Extract</Heading>
<form
className="m-auto flex flex-col content-between gap-5 mt-10 mb-10"
onSubmit={handleSubmit}
onChange={handleChange}
>
{requestServerConfig.isFetched && (
<FormControl as="fieldset">
<FormLabel as="legend">Extraction Model</FormLabel>
<RadioGroup
name="modelId"
defaultValue={requestServerConfig.data?.models[0].name}
>
<HStack spacing="24px">
{requestServerConfig.data?.models.map((model) => (
<Radio value={model.name} key={model.name}>
{model.description}
</Radio>
))}
</HStack>
</RadioGroup>
</FormControl>
)}
</Heading>
<Text className="m-auto w-4/5" textAlign="center">
{extractorQuery.data?.description}
</Text>
<Tabs variant={"enclosed"} colorScheme="blue" size="sm" marginTop={2}>
{requestServerConfig.isFetched && (
<>
<input
type="file"
name="file"
accept={requestServerConfig.data?.accepted_mimetypes.join(", ")}
color="blue"
className="border-2 border-dashed border-gray-300 rounded-md p-4 w-full file:mr-4"
/>
<Text fontSize="xs">
Max file size is: {requestServerConfig.data?.max_file_size_mb}MB
</Text>
<Text fontSize="xs">
Supported mimetypes:{" "}
{requestServerConfig.data?.accepted_mimetypes.join(", ")}
</Text>
</>
)}
<Box position="relative" padding="10">
<Divider />
<AbsoluteCenter bg="white" px="4">
OR
</AbsoluteCenter>
</Box>
<Textarea
placeholder="Enter text to extract information from..."
name="text"
className="textarea textarea-bordered h-3/4"
autoFocus
onKeyDown={handleKeyDown}
/>
<Button type="submit" isDisabled={isDisabled}>
Run
</Button>
</form>
</div>
<div className="m-auto">
{data?.content_too_long && (
<Text color={"red"} margin={5}>
The content was too long to be processed. Extraction was run on a
truncated version of the content.
</Text>
)}
<Tabs variant={"enclosed"} colorScheme="blue" size="sm">
<TabList>
<Tab>Extract</Tab>
<Tab>JSON Schema</Tab>
<Tab isDisabled={isShared}>Examples</Tab>
<Tab>Table</Tab>
<Tab>JSON</Tab>
</TabList>
<TabPanels>
<TabPanel>
<Form schema={cleanedSchema} validator={validator}>
{true} {/* Disables the submit button */}
</Form>
<form
className="m-auto flex flex-col content-between gap-5 mt-2 mb-10"
onSubmit={handleSubmit}
onChange={handleChange}
>
{requestServerConfig.isFetched && (
<FormControl as="fieldset" marginTop={5}>
<FormLabel as="legend">Model For Extraction</FormLabel>
<RadioGroup
name="modelId"
defaultValue={requestServerConfig.data?.models[0].name}
>
<HStack spacing="24px">
{requestServerConfig.data?.models.map((model) => (
<Radio value={model.name} key={model.name}>
{model.description}
</Radio>
))}
</HStack>
</RadioGroup>
</FormControl>
)}
{requestServerConfig.isFetched && (
<>
<input
type="file"
name="file"
accept={requestServerConfig.data?.accepted_mimetypes.join(
", ",
)}
color="blue"
className="border-2 border-dashed border-gray-300 rounded-md p-4 w-full file:mr-4"
/>
<Text fontSize="xs">
Max file size is:{" "}
{requestServerConfig.data?.max_file_size_mb}MB
</Text>
<Text fontSize="xs">
Supported mimetypes:{" "}
{requestServerConfig.data?.accepted_mimetypes.join(", ")}
</Text>
</>
)}
<Box position="relative" padding="10">
<Divider />
<AbsoluteCenter bg="white" px="4">
OR
</AbsoluteCenter>
</Box>
<Textarea
placeholder="Enter text to extract information from..."
name="text"
className="textarea textarea-bordered h-3/4"
autoFocus
onKeyDown={handleKeyDown}
/>
<Button type="submit" isDisabled={isDisabled}>
Run
</Button>
</form>
<div className="m-auto">
{data?.content_too_long && (
<Text color={"red"} margin={5}>
The content was too long to be processed. Extraction was run
on a truncated version of the content.
</Text>
)}
<Tabs variant={"enclosed"} colorScheme="blue" size="sm">
<TabList>
<Tab>Table</Tab>
<Tab>JSON</Tab>
</TabList>
<TabPanels>
<TabPanel>
<ResultsTable data={data} isPending={isPending} />
</TabPanel>
<TabPanel>
<Text className="mt-1 mb-5">
Shows the output from the extractor in JSON format.
</Text>
<SyntaxHighlighter language="json" style={docco}>
{JSON.stringify(data, null, 2)}
</SyntaxHighlighter>
</TabPanel>
</TabPanels>
</Tabs>
</div>
<ResultsTable data={data} isPending={isPending} />
</TabPanel>
<TabPanel>
<Text className="mt-1 mb-5">
The raw JSON schema for the extractor.
Shows the output from the extractor in JSON format.
</Text>
<SyntaxHighlighter language="json" style={docco}>
{JSON.stringify(extractorQuery.data.schema, null, 2)}
{JSON.stringify(data, null, 2)}
</SyntaxHighlighter>
</TabPanel>
<TabPanel>
<ExampleEditor extractorId={extractorId} isShared={isShared} />
</TabPanel>
</TabPanels>
</Tabs>
</div>
-93
View File
@@ -181,96 +181,3 @@ export const useCreateExtractor = ({
},
});
};
type CreateExampleRequest = {
extractor_id: string;
content: string;
// Any can be any JSON serializable object
/* eslint-disable @typescript-eslint/no-explicit-any */
output: any[];
};
type CreateExampleResponse = {
uuid: string;
};
const createExample: MutationFunction<
CreateExampleResponse,
CreateExampleRequest
> = async (example) => {
const baseUrl = getBaseApiUrl();
const response = await axiosClient.post(`${baseUrl}/examples`, example);
return response.data;
};
export const useCreateExample = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createExample,
onSuccess: () => {
// TDOO: invalidate only for the extractor ID asscoiated with the example
// that was deleted.
queryClient.invalidateQueries({ queryKey: ["listExamples"] });
},
});
};
type DeleteExampleParams = {
uuid: string;
};
const deleteExample = async ({ uuid }: DeleteExampleParams): Promise<void> => {
const baseUrl: string = getBaseApiUrl();
await axiosClient.delete(`${baseUrl}/examples/${uuid}`);
};
export const useDeleteExample = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: deleteExample,
onSuccess: () => {
// TDOO: invalidate only for the extractor ID asscoiated with the example
// that was deleted.
queryClient.invalidateQueries({ queryKey: ["listExamples"] });
},
});
};
type Example = {
uuid: string;
content: string;
output: any[];
};
type ListExamplesParams = {
extractor_id: string;
limit: number;
offset: number;
};
const fetchExamples = async ({
queryKey,
}: {
queryKey: [string, string, number, number];
}): Promise<Example[]> => {
const [, extractor_id, limit = 10, offset = 0] = queryKey;
const baseUrl = getBaseApiUrl();
const queryParams: string = new URLSearchParams({
extractor_id,
limit: limit.toString(),
offset: offset.toString(),
}).toString();
const response = await axiosClient.get(`${baseUrl}/examples?${queryParams}`);
return response.data;
};
export const useListExamples = (params: ListExamplesParams) => {
return useQuery({
queryKey: [
"listExamples",
params.extractor_id,
params.limit,
params.offset,
],
queryFn: fetchExamples,
});
};