Skip to content

Commit

Permalink
[OPIK-579] [FR]: add output formats as it own fields for prompts (#932)
Browse files Browse the repository at this point in the history
* [OPIK-579] [FR]: add output formats as it own fields for prompts

* - decrease edit prompt version popup for 50 pixels

* - add max-h for popup content
  • Loading branch information
andriidudar authored Dec 19, 2024
1 parent 5906283 commit d2aa0d9
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 142 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { PromptVersion } from "@/types/prompts";
type UseCreatePromptVersionMutationParams = {
name: string;
template: string;
metadata?: object;
onSetActiveVersionId: (versionId: string) => void;
};

Expand All @@ -20,11 +21,13 @@ const useCreatePromptVersionMutation = () => {
mutationFn: async ({
name,
template,
metadata,
}: UseCreatePromptVersionMutationParams) => {
const { data } = await api.post(`${PROMPTS_REST_ENDPOINT}versions`, {
name,
version: {
template,
...(metadata && { metadata }),
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Prompt } from "@/types/prompts";

interface CreatePromptTemplate {
template: string;
metadata?: object;
}

type UsePromptCreateMutationParams = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from "react";
import React, { useCallback, useState } from "react";
import { EditorView } from "@codemirror/view";
import CodeMirror from "@uiw/react-codemirror";
import { jsonLanguage } from "@codemirror/lang-json";
Expand All @@ -19,8 +19,7 @@ import useDatasetItemBatchMutation from "@/api/datasets/useDatasetItemBatchMutat
import { isValidJsonObject, safelyParseJSON } from "@/lib/utils";
import { Alert, AlertTitle } from "@/components/ui/alert";
import { useCodemirrorTheme } from "@/hooks/useCodemirrorTheme";

const ERROR_TIMEOUT = 3000;
import { useBooleanTimeoutState } from "@/hooks/useBooleanTimeoutState";

const DATA_PREFILLED_CONTENT = `{
"input": "<user question>",
Expand Down Expand Up @@ -48,17 +47,7 @@ const AddEditDatasetItemDialog: React.FunctionComponent<
? JSON.stringify(datasetItem.data, null, 2)
: DATA_PREFILLED_CONTENT,
);
const [showInvalidJSON, setShowInvalidJSON] = useState<boolean>(false);

useEffect(() => {
let timer: NodeJS.Timeout;
if (showInvalidJSON) {
timer = setTimeout(() => setShowInvalidJSON(false), ERROR_TIMEOUT);
}
return () => {
clearTimeout(timer);
};
}, [showInvalidJSON]);
const [showInvalidJSON, setShowInvalidJSON] = useBooleanTimeoutState({});

const isValid = Boolean(data.length);
const isEdit = Boolean(datasetItem);
Expand Down Expand Up @@ -96,7 +85,7 @@ const AddEditDatasetItemDialog: React.FunctionComponent<
<div className="max-h-[70vh] overflow-y-auto">
<div className="flex flex-col gap-2 pb-4">
<Label htmlFor="input">Data</Label>
<div className="max-h-52 overflow-y-auto">
<div className="max-h-52 overflow-y-auto rounded-md">
<CodeMirror
theme={theme}
value={data}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from "react";
import { keepPreviousData } from "@tanstack/react-query";
import get from "lodash/get";
import isObject from "lodash/isObject";

import { PromptWithLatestVersion, PromptVersion } from "@/types/prompts";
import Loader from "@/components/shared/Loader/Loader";
Expand Down Expand Up @@ -44,6 +45,16 @@ export const COMMITS_DEFAULT_COLUMNS = [
type: COLUMN_TYPE.dictionary,
cell: CodeCell as never,
},
{
id: "metadata",
label: "Metadata",
type: COLUMN_TYPE.dictionary,
accessorFn: (row) =>
isObject(row.metadata)
? JSON.stringify(row.metadata, null, 2)
: row.metadata,
cell: CodeCell as never,
},
{
id: "created_at",
label: "Created at",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import React, { useState } from "react";
import CodeMirror from "@uiw/react-codemirror";
import { jsonLanguage } from "@codemirror/lang-json";
import { EditorView } from "@codemirror/view";

import {
Dialog,
Expand All @@ -8,12 +11,17 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Alert, AlertTitle } from "@/components/ui/alert";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import useCreatePromptVersionMutation from "@/api/prompts/useCreatePromptVersionMutation";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import TextDiff from "@/components/shared/CodeDiff/TextDiff";
import useCreatePromptVersionMutation from "@/api/prompts/useCreatePromptVersionMutation";
import { useBooleanTimeoutState } from "@/hooks/useBooleanTimeoutState";
import { useCodemirrorTheme } from "@/hooks/useCodemirrorTheme";
import { isValidJsonObject, safelyParseJSON } from "@/lib/utils";

enum PROMPT_PREVIEW_MODE {
write = "write",
Expand All @@ -23,109 +31,160 @@ enum PROMPT_PREVIEW_MODE {
type EditPromptDialogProps = {
open: boolean;
setOpen: (open: boolean) => void;

promptTemplate: string;
template: string;
metadata?: object;
promptName: string;
onSetActiveVersionId: (versionId: string) => void;
};

const EditPromptDialog: React.FunctionComponent<EditPromptDialogProps> = ({
open,
setOpen,
promptTemplate: parentPromptTemplate,
template: promptTemplate,
metadata: promptMetadata,
promptName,
onSetActiveVersionId,
}) => {
const [tab, setTab] = useState("template");
const [previewMode, setPreviewMode] = useState<PROMPT_PREVIEW_MODE>(
PROMPT_PREVIEW_MODE.write,
);
const [promptTemplate, setPromptTemplate] = useState(parentPromptTemplate);
const metadataString = promptMetadata
? JSON.stringify(promptMetadata, null, 2)
: "";
const [template, setTemplate] = useState(promptTemplate);
const [metadata, setMetadata] = useState(metadataString);

const [showInvalidJSON, setShowInvalidJSON] = useBooleanTimeoutState({});
const theme = useCodemirrorTheme({
editable: true,
});

const createPromptVersionMutation = useCreatePromptVersionMutation();
const { mutate } = useCreatePromptVersionMutation();

const handleClickEditPrompt = () => {
createPromptVersionMutation.mutate({
const isMetadataValid = metadata === "" || isValidJsonObject(metadata);

if (!isMetadataValid) {
return setShowInvalidJSON(true);
}

mutate({
name: promptName,
template: promptTemplate,
template,
...(metadata && { metadata: safelyParseJSON(metadata) }),
onSetActiveVersionId,
});

setOpen(false);
};

const templateHasChanges = promptTemplate !== parentPromptTemplate;
const isValid = promptTemplate?.length && templateHasChanges;
const templateHasChanges = template !== promptTemplate;
const metadataHasChanges = metadata !== metadataString;
const isValid =
template?.length && (templateHasChanges || metadataHasChanges);

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="max-w-lg sm:max-w-[720px]">
<DialogHeader>
<DialogTitle>Edit prompt</DialogTitle>
</DialogHeader>
<div className="size-full overflow-y-auto">
<div className="size-full max-h-[80vh] overflow-y-auto">
<p className="comet-body-s text-muted-slate ">
By editing a prompt, a new commit will be created automatically. You
can access older versions of the prompt from the <b>Commits</b> tab.
</p>

<div className="py-4">
<div className="mb-3 flex items-center justify-between">
<Label htmlFor="promptTemplate">Prompt</Label>
<ToggleGroup
type="single"
value={previewMode}
onValueChange={setPreviewMode as never}
size="sm"
>
<ToggleGroupItem
value={PROMPT_PREVIEW_MODE.write}
aria-label="Write"
<Tabs
defaultValue="template"
value={tab}
onValueChange={setTab}
className="my-2"
>
<TabsList variant="underline">
<TabsTrigger variant="underline" value="template">
Template
</TabsTrigger>
<TabsTrigger variant="underline" value="metadata">
Metadata
</TabsTrigger>
</TabsList>
<TabsContent value="template" className="h-[422px]">
<div className="mb-3 flex items-center justify-between">
<Label htmlFor="promptTemplate">Prompt</Label>
<ToggleGroup
type="single"
value={previewMode}
onValueChange={(value) =>
value && setPreviewMode(value as PROMPT_PREVIEW_MODE)
}
size="sm"
>
Write
</ToggleGroupItem>
<ToggleGroupItem
value={PROMPT_PREVIEW_MODE.diff}
aria-label="Preview changes"
disabled={!templateHasChanges}
>
Preview changes
</ToggleGroupItem>
</ToggleGroup>
</div>
{previewMode === PROMPT_PREVIEW_MODE.write ? (
<Textarea
className="comet-code h-[400px] resize-none"
id="promptTemplate"
value={promptTemplate}
onChange={(e) => setPromptTemplate(e.target.value)}
/>
) : (
<div className="comet-code h-[400px] whitespace-pre-line break-words rounded-md border px-3 py-2">
<TextDiff
content1={parentPromptTemplate}
content2={promptTemplate}
<ToggleGroupItem
value={PROMPT_PREVIEW_MODE.write}
aria-label="Write"
>
Write
</ToggleGroupItem>
<ToggleGroupItem
value={PROMPT_PREVIEW_MODE.diff}
aria-label="Preview changes"
disabled={!templateHasChanges}
>
Preview changes
</ToggleGroupItem>
</ToggleGroup>
</div>
{previewMode === PROMPT_PREVIEW_MODE.write ? (
<Textarea
className="comet-code h-[350px] resize-none"
id="promptTemplate"
value={template}
onChange={(e) => setTemplate(e.target.value)}
/>
) : (
<div className="comet-code h-[350px] overflow-y-auto whitespace-pre-line break-words rounded-md border px-2.5 py-1.5">
<TextDiff content1={promptTemplate} content2={template} />
</div>
)}
<p className="comet-body-xs mt-3 text-light-slate">
You can specify variables using the &quot;mustache&quot; syntax:{" "}
{"{{variable}}"}.
</p>
</TabsContent>
<TabsContent value="metadata" className="h-[422px]">
<div className="h-[382px] overflow-y-auto rounded-md">
<CodeMirror
theme={theme}
value={metadata}
onChange={setMetadata}
extensions={[jsonLanguage, EditorView.lineWrapping]}
/>
</div>
)}

<p className="comet-body-xs mt-3 text-light-slate">
You can specify variables using the &quot;mustache&quot; syntax:{" "}
{"{{variable}}"}.
</p>
</div>
<p className="comet-body-xs mt-2 text-light-slate">
You can specify only valid JSON object.
</p>
</TabsContent>
</Tabs>
{showInvalidJSON && (
<Alert variant="destructive">
<AlertTitle>Metadata field is not valid</AlertTitle>
</Alert>
)}
</div>

<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<DialogClose asChild>
<Button
type="submit"
disabled={!isValid}
onClick={handleClickEditPrompt}
>
Edit prompt
</Button>
</DialogClose>
<Button
type="submit"
disabled={!isValid}
onClick={handleClickEditPrompt}
>
Create new commit
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
Expand Down
Loading

0 comments on commit d2aa0d9

Please sign in to comment.