Skip to main content
Glama

@arizeai/phoenix-mcp

Official
by Arize-ai
ClonePromptDialog.tsx8.44 kB
import { Suspense } from "react"; import { Controller, useForm } from "react-hook-form"; import { graphql, useMutation } from "react-relay"; import { useNavigate } from "react-router"; import { Button, Dialog, FieldError, Flex, Input, Label, Loading, Text, TextArea, TextField, View, } from "@phoenix/components"; import { CodeEditorFieldWrapper, JSONEditor } from "@phoenix/components/code"; import { DialogCloseButton, DialogContent, DialogHeader, DialogTitle, DialogTitleExtra, } from "@phoenix/components/dialog"; import { useNotifySuccess } from "@phoenix/contexts/NotificationContext"; import { ClonePromptDialogMutation } from "@phoenix/pages/prompt/__generated__/ClonePromptDialogMutation.graphql"; import { getErrorMessagesFromRelayMutationError } from "@phoenix/utils/errorUtils"; import { validateIdentifier } from "@phoenix/utils/identifierUtils"; import { isJSONObjectString } from "@phoenix/utils/jsonUtils"; export const ClonePromptDialog = ({ promptId, promptName, promptDescription, promptMetadata, }: { promptId: string; promptName: string; promptDescription?: string; promptMetadata?: Record<string, unknown>; }) => { const notifySuccess = useNotifySuccess(); const navigate = useNavigate(); const [clonePrompt, isClonePending] = useMutation<ClonePromptDialogMutation>( graphql` mutation ClonePromptDialogMutation($input: ClonePromptInput!) { clonePrompt(input: $input) { id } } ` ); const form = useForm({ disabled: isClonePending, mode: "onChange", defaultValues: { name: `${promptName}-clone`, description: promptDescription, metadata: promptMetadata ? JSON.stringify(promptMetadata, null, 2) : "{}", }, }); const { control, handleSubmit, formState: { isValid, errors }, setError, } = form; const onSubmit = (close: () => void) => handleSubmit((data) => { // Parse metadata, or set to null to clear if empty let metadata: unknown = null; if (data.metadata && data.metadata.trim() !== "") { try { metadata = JSON.parse(data.metadata); } catch (_error) { setError("metadata", { message: "Failed to parse metadata as JSON", }); return; } } clonePrompt({ variables: { input: { promptId, name: data.name, description: data.description, metadata, }, }, onCompleted: (data) => { notifySuccess({ title: "Prompt cloned successfully", action: { text: "View Prompt", onClick: () => { navigate(`/prompts/${data.clonePrompt.id}`); }, }, }); close(); }, onError: (error) => { const message = getErrorMessagesFromRelayMutationError(error); if (message?.[0]?.includes("already exists")) { setError("name", { message: message?.[0], }); } else { setError("root", { message: message?.[0], }); } }, }); }); return ( <Dialog> {({ close }) => ( <DialogContent> <DialogHeader> <DialogTitle>Clone Prompt</DialogTitle> <DialogTitleExtra> <DialogCloseButton slot="close" /> </DialogTitleExtra> </DialogHeader> <Suspense fallback={<Loading />}> <View padding="size-200"> <form onSubmit={onSubmit(close)}> <Flex direction="column" gap="size-100"> <Controller control={control} name="name" rules={{ validate: { differsFromOriginalName: (value) => { if (value.trim() === promptName.trim()) { return "Name must be different from the original prompt name"; } return true; }, isValidIdentifier: validateIdentifier, }, }} render={({ field: { onChange, onBlur, value, disabled }, fieldState: { error }, }) => ( <TextField isInvalid={!!error?.message}> <Label>Name</Label> <Input name="name" type="text" onChange={onChange} onBlur={onBlur} value={value} disabled={disabled} /> {!error && ( <Text slot="description"> A name for the cloned prompt. </Text> )} <FieldError>{error?.message}</FieldError> </TextField> )} /> <Controller control={control} name="description" render={({ field: { onChange, onBlur, value, disabled }, fieldState: { error }, }) => ( <TextField isInvalid={!!error?.message} isDisabled={disabled} value={value} onChange={onChange} onBlur={onBlur} > <Label>Description</Label> <TextArea name="description" /> {!error && ( <Text slot="description"> A description for the cloned prompt. </Text> )} <FieldError>{error?.message}</FieldError> </TextField> )} /> <Controller control={control} name="metadata" rules={{ validate: (value) => { // Allow empty values (will be treated as undefined) if (!value || value.trim() === "") { return true; } if (!isJSONObjectString(value)) { return "metadata must be a valid JSON object"; } return true; }, }} render={({ field: { onChange, onBlur, value }, fieldState: { error }, }) => ( <CodeEditorFieldWrapper label="Metadata" errorMessage={error?.message} description="A JSON object containing metadata for the prompt" > <JSONEditor value={value} onChange={onChange} onBlur={onBlur} /> </CodeEditorFieldWrapper> )} /> </Flex> {errors?.root && ( <Text color="danger">{errors?.root?.message}</Text> )} <Flex direction="row" justifyContent="end" gap="size-100"> <Button slot="close" variant="default" isDisabled={isClonePending} > Cancel </Button> <Button type="submit" variant="primary" isDisabled={!isValid} isPending={isClonePending} > Clone </Button> </Flex> </form> </View> </Suspense> </DialogContent> )} </Dialog> ); };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Arize-ai/phoenix'

If you have feedback or need assistance with the MCP directory API, please join our Discord server