diff --git a/app/(protected)/project/[project_id]/prompts/[prompt_id]/page.tsx b/app/(protected)/project/[project_id]/prompts/[prompt_id]/page.tsx
index 99a5cc95..167d8831 100644
--- a/app/(protected)/project/[project_id]/prompts/[prompt_id]/page.tsx
+++ b/app/(protected)/project/[project_id]/prompts/[prompt_id]/page.tsx
@@ -20,6 +20,7 @@ export default function Page() {
const router = useRouter();
const [prompts, setPrompts] = useState([]);
const [selectedPrompt, setSelectedPrompt] = useState();
+ const [createDialogOpen, setCreateDialogOpen] = useState(false);
const [live, setLive] = useState(false);
const queryClient = useQueryClient();
@@ -72,6 +73,8 @@ export default function Page() {
@@ -93,11 +96,15 @@ export default function Page() {
currentPrompt={selectedPrompt}
promptsetId={promptsetId}
version={prompts.length + 1}
+ open={createDialogOpen}
+ setOpen={setCreateDialogOpen}
/>
) : (
)}
@@ -247,7 +254,6 @@ function PageLoading() {
Back
-
diff --git a/components/evaluate/evaluation-table.tsx b/components/evaluate/evaluation-table.tsx
index 9f86b601..46af7b3f 100644
--- a/components/evaluate/evaluation-table.tsx
+++ b/components/evaluate/evaluation-table.tsx
@@ -164,17 +164,21 @@ export default function EvaluationTable({
!currentData ? (
) : (
- currentData.map((span: any, i: number) => (
-
- ))
+ currentData.map((span: any, i: number) => {
+ if (span.status_code !== "ERROR") {
+ return (
+
+ );
+ }
+ })
)}
{showLoader && (
diff --git a/components/playground/common.tsx b/components/playground/common.tsx
index e68fef71..6d31b5f8 100644
--- a/components/playground/common.tsx
+++ b/components/playground/common.tsx
@@ -1,3 +1,6 @@
+"use client";
+
+import PromptRegistryDialog from "@/components/playground/prompt-registry-dialog";
import LLMPicker from "@/components/shared/llm-picker";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
@@ -9,6 +12,8 @@ import {
import { cn } from "@/lib/utils";
import { MinusCircleIcon, PlusIcon } from "lucide-react";
import { useEffect, useRef, useState } from "react";
+import { useQuery } from "react-query";
+import { toast } from "sonner";
export function RoleBadge({
role,
@@ -34,15 +39,24 @@ export function ExpandingTextArea({
value,
onChange,
setFocusing,
+ saveButtonRef,
+ handleSave,
}: {
value: string;
onChange: any;
setFocusing?: any;
+ saveButtonRef: React.RefObject
;
+ handleSave: (open: boolean) => void;
}) {
const textAreaRef = useRef(null);
const handleClickOutside = (event: any) => {
- if (textAreaRef.current && !textAreaRef.current.contains(event.target)) {
+ if (
+ textAreaRef.current &&
+ !textAreaRef.current.contains(event.target) &&
+ saveButtonRef.current &&
+ !saveButtonRef.current.contains(event.target)
+ ) {
setFocusing(false);
}
};
@@ -63,13 +77,25 @@ export function ExpandingTextArea({
};
return (
-
+
+
+
+
+
+
);
}
@@ -107,6 +133,34 @@ export function Message({
}
};
const [editing, setEditing] = useState(false);
+ const [dialogOpen, setDialogOpen] = useState(false);
+ const [selectedPromptRegistry, setSelectedPromptRegistry] =
+ useState(null);
+ const saveButtonRef = useRef(null);
+ const [currentPrompt, setCurrentPrompt] = useState(undefined);
+
+ useQuery({
+ queryKey: ["fetch-prompts-query", selectedPromptRegistry?.id],
+ queryFn: async () => {
+ const response = await fetch(
+ `/api/promptset?promptset_id=${selectedPromptRegistry?.id}`
+ );
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error?.message || "Failed to fetch tests");
+ }
+ const result = await response.json();
+ setCurrentPrompt(result?.promptsets?.prompts[0] || undefined);
+ return result;
+ },
+ enabled: !!selectedPromptRegistry,
+ onError: (error) => {
+ toast.error("Failed to fetch prompts", {
+ description: error instanceof Error ? error.message : String(error),
+ });
+ },
+ });
+
return (
<>
@@ -129,13 +183,17 @@ export function Message({
)}
{editing && (
-
{
- setMessage({ ...message, content: value });
- }}
- value={message.content}
- setFocusing={setEditing}
- />
+
+ {
+ setMessage({ ...message, content: value });
+ }}
+ value={message.content}
+ setFocusing={setEditing}
+ saveButtonRef={saveButtonRef}
+ handleSave={setDialogOpen}
+ />
+
)}
@@ -151,6 +209,11 @@ export function Message({
+
>
);
}
diff --git a/components/playground/prompt-registry-dialog.tsx b/components/playground/prompt-registry-dialog.tsx
new file mode 100644
index 00000000..b6a99253
--- /dev/null
+++ b/components/playground/prompt-registry-dialog.tsx
@@ -0,0 +1,206 @@
+"use client";
+
+import { CreatePromptset } from "@/components/project/dataset/create";
+import { Button } from "@/components/ui/button";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+} from "@/components/ui/command";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Label } from "@/components/ui/label";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { cn } from "@/lib/utils";
+import { Check, ChevronsUpDown } from "lucide-react";
+import { usePathname } from "next/navigation";
+import * as React from "react";
+import { useQuery } from "react-query";
+import { toast } from "sonner";
+import CreatePromptDialog from "../shared/create-prompt-dialog";
+
+import { Promptset } from "@prisma/client";
+
+export interface PromptRegistryDialogProps {
+ openDialog?: boolean;
+ setOpenDialog: (open: boolean) => void;
+ passedPrompt: string;
+}
+
+export default function PromptRegistryDialog({
+ openDialog = false,
+ setOpenDialog,
+ passedPrompt,
+}: PromptRegistryDialogProps) {
+ const [selectedPromptsetId, setSelectedPromptsetId] = React.useState("");
+ const [busy, setBusy] = React.useState(false);
+ const [open, setOpen] = React.useState(false);
+ const [prompts, setPrompts] = React.useState([]);
+
+ const fetchPrompts = async () => {
+ setBusy(true);
+ try {
+ const response = await fetch(
+ `/api/promptset?promptset_id=${selectedPromptsetId}`
+ );
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error?.message || "Failed to fetch prompts");
+ }
+ const result = await response.json();
+ setPrompts(result?.promptsets?.prompts || []);
+ setBusy(false);
+ } catch (error) {
+ toast.error("Failed to fetch prompts", {
+ description: error instanceof Error ? error.message : String(error),
+ });
+ setBusy(false);
+ }
+ };
+
+ return (
+
+ );
+}
+
+export function PromptRegistryCombobox({
+ selectedPromptsetId,
+ setSelectedPromptsetId,
+}: {
+ selectedPromptsetId: string;
+ setSelectedPromptsetId: (promptsetId: string) => void;
+}) {
+ const pathname = usePathname();
+ const projectId = pathname.split("/")[2];
+ const [open, setOpen] = React.useState(false);
+
+ const {
+ data: promptsets,
+ isLoading: promptsetsLoading,
+ error: promptsetsError,
+ } = useQuery({
+ queryKey: ["fetch-promptsets-query", projectId],
+ queryFn: async () => {
+ const response = await fetch(`/api/promptset?id=${projectId}`);
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error?.message || "Failed to fetch prompt sets");
+ }
+ const result = await response.json();
+
+ return result;
+ },
+ onError: (error) => {
+ toast.error("Failed to fetch prompt sets", {
+ description: error instanceof Error ? error.message : String(error),
+ });
+ },
+ });
+
+ if (promptsetsLoading) {
+ return Loading...
;
+ }
+
+ return (
+
+
+
+
+
+
+
+ No promptset found.
+
+ {promptsets?.promptsets?.map((promptset: Promptset) => (
+ {
+ setSelectedPromptsetId(currentValue);
+ setOpen(false);
+ }}
+ >
+
+ {promptset.name}
+
+ ))}
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/project/dataset/create.tsx b/components/project/dataset/create.tsx
index 573c7db7..2f5f1b13 100644
--- a/components/project/dataset/create.tsx
+++ b/components/project/dataset/create.tsx
@@ -160,11 +160,13 @@ export function CreateDataset({
export function CreatePromptset({
projectId,
+ onCreate,
disabled = false,
variant = "default",
className = "",
}: {
projectId?: string;
+ onCreate?: () => void;
disabled?: boolean;
variant?: any;
className?: string;
@@ -218,6 +220,9 @@ export function CreatePromptset({
toast("Prompt registry created!", {
description: "Your prompt registry has been created.",
});
+ if (onCreate) {
+ onCreate();
+ }
setOpen(false);
CreatePromptsetForm.reset();
} catch (error: any) {
diff --git a/components/project/traces/logs-view.tsx b/components/project/traces/logs-view.tsx
index a942d2b2..f6cad5f4 100644
--- a/components/project/traces/logs-view.tsx
+++ b/components/project/traces/logs-view.tsx
@@ -5,6 +5,8 @@ import { correctTimestampFormat } from "@/lib/trace_utils";
import { cn, formatDateTime, parseNestedJsonFields } from "@/lib/utils";
import { ChevronDown, ChevronRight } from "lucide-react";
import { useState } from "react";
+import { JsonView, allExpanded, defaultStyles } from "react-json-view-lite";
+import "react-json-view-lite/dist/index.css";
import {
serviceTypeColor,
vendorBadgeColor,
@@ -88,9 +90,11 @@ export const LogsView = ({
)}
{!collapsed && (
-
- {parseNestedJsonFields(span.attributes)}
-
+
)}
diff --git a/components/project/traces/trace-row.tsx b/components/project/traces/trace-row.tsx
index 0ed7084f..48891c9f 100644
--- a/components/project/traces/trace-row.tsx
+++ b/components/project/traces/trace-row.tsx
@@ -9,6 +9,8 @@ import {
import { calculatePriceFromUsage, formatDateTime } from "@/lib/utils";
import { ChevronDown, ChevronRight } from "lucide-react";
import { useState } from "react";
+import { JsonView, allExpanded, defaultStyles } from "react-json-view-lite";
+import "react-json-view-lite/dist/index.css";
import { HoverCell } from "../../shared/hover-cell";
import { LLMView } from "../../shared/llm-view";
import TraceGraph from "../../traces/trace_graph";
@@ -36,6 +38,7 @@ export const TraceRow = ({
let userId: string = "";
let prompts: any[] = [];
let responses: any[] = [];
+ let events: any[] = [];
let cost = { total: 0, input: 0, output: 0 };
let langgraph = false;
for (const span of trace) {
@@ -78,6 +81,10 @@ export const TraceRow = ({
cost.output += currentcost.output;
}
}
+
+ if (span.events) {
+ events = JSON.parse(span.events);
+ }
}
// Sort the trace based on start_time, then end_time
@@ -115,13 +122,13 @@ export const TraceRow = ({
- {traceHierarchy[0].status !== "ERROR" && (
+ {traceHierarchy[0].status_code !== "ERROR" && (
)}
- {traceHierarchy[0].status === "ERROR" && (
+ {traceHierarchy[0].status_code === "ERROR" && (
)}
+
)}
+ {selectedTab === "events" && (
+
+ {events.map((event: any, i: number) => {
+ return (
+
+ );
+ })}
+
+ )}
{selectedTab === "llm" && (
diff --git a/components/project/traces/traces.tsx b/components/project/traces/traces.tsx
index 0d3474b0..71a609b7 100644
--- a/components/project/traces/traces.tsx
+++ b/components/project/traces/traces.tsx
@@ -172,8 +172,8 @@ export default function Traces({ email }: { email: string }) {
- Time ↓ ({utcTime ? "UTC" : "Local"})
-
+ Time
↓ ({utcTime ? "UTC" : "Local"})
+
Namespace
Model
Input
diff --git a/components/shared/create-prompt-dialog.tsx b/components/shared/create-prompt-dialog.tsx
index f97ca21f..2cb3ebd8 100644
--- a/components/shared/create-prompt-dialog.tsx
+++ b/components/shared/create-prompt-dialog.tsx
@@ -1,4 +1,5 @@
"use client";
+
import DiffView from "@/components/shared/diff-view";
import {
AlertDialog,
@@ -35,14 +36,24 @@ export default function CreatePromptDialog({
promptsetId,
currentPrompt,
version,
+ passedPrompt,
variant = "default",
disabled = false,
+ open,
+ setOpen,
+ showButton = true,
+ setOpenDialog,
}: {
promptsetId: string;
currentPrompt?: Prompt;
+ passedPrompt?: string;
version?: number;
variant?: any;
disabled?: boolean;
+ open: boolean;
+ setOpen: (open: boolean) => void;
+ showButton?: boolean;
+ setOpenDialog?: (open: boolean) => void;
}) {
const schema = z.object({
prompt: z.string(),
@@ -54,15 +65,23 @@ export default function CreatePromptDialog({
const CreatePromptForm = useForm({
resolver: zodResolver(schema),
+ defaultValues: {
+ prompt: passedPrompt || currentPrompt?.value || "",
+ note: currentPrompt?.note || "",
+ live: currentPrompt?.live || false,
+ model: currentPrompt?.model || "",
+ modelSettings: JSON.stringify(currentPrompt?.modelSettings) || "",
+ },
});
const queryClient = useQueryClient();
- const [prompt, setPrompt] = useState
(currentPrompt?.value || "");
+ const [prompt, setPrompt] = useState(
+ passedPrompt || currentPrompt?.value || ""
+ );
const [variables, setVariables] = useState(
currentPrompt?.variables || []
);
const [confirmAndSaveView, setConfirmAndSaveView] = useState(false);
- const [open, setOpen] = useState(false);
const [busy, setBusy] = useState(false);
const extractVariables = (prompt: string) => {
@@ -81,167 +100,228 @@ export default function CreatePromptDialog({
};
useEffect(() => {
- if (currentPrompt?.value) {
- const vars = extractVariables(currentPrompt.value);
- setVariables(vars);
+ if (passedPrompt) {
+ setPrompt(passedPrompt);
+ } else if (currentPrompt?.value) {
setPrompt(currentPrompt.value);
}
- }, [currentPrompt]);
+ }, [passedPrompt, currentPrompt]);
return (
- {
- setOpen(val);
- CreatePromptForm.reset();
- }}
- >
-
-
-
-
-
-
- {confirmAndSaveView ? "Review and Save" : "Create new prompt"}
-
-
-
-
-
-
-
+
+ Cancel
+ {!confirmAndSaveView && (
+
+ )}
+ {confirmAndSaveView && (
+
+ )}
+ {confirmAndSaveView && (
+
+ )}
+
+
+
+
+
+
+
+ >
);
}
diff --git a/lib/utils.ts b/lib/utils.ts
index effd61e2..205aabb8 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -444,6 +444,26 @@ export function safeStringify(value: any): string {
if (typeof value === "string") {
return value;
}
+
+ // if its a list, check type and stringify
+ if (Array.isArray(value)) {
+ const stringifiedList = value.map((item) => {
+ if (item?.type === "text") {
+ return item?.text;
+ } else if (item?.type === "function") {
+ return prettyPrintJson.toHtml(item);
+ } else if (item?.type === "image_url") {
+ if (typeof item?.image_url === "string") {
+ return ``;
+ } else if (typeof item?.image_url === "object") {
+ return ``;
+ }
+ }
+ return item;
+ });
+ return stringifiedList.join("");
+ }
+
// If it's not a string, stringify it
return prettyPrintJson.toHtml(value);
}
diff --git a/package-lock.json b/package-lock.json
index 3788e87a..52d6ac46 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -81,6 +81,7 @@
"react-diff-view": "^3.2.1",
"react-dom": "^18",
"react-hook-form": "^7.49.3",
+ "react-json-view-lite": "^1.4.0",
"react-markdown": "^9.0.1",
"react-query": "^3.39.3",
"react-syntax-highlighter": "^15.5.0",
@@ -12368,6 +12369,17 @@
"version": "16.13.1",
"license": "MIT"
},
+ "node_modules/react-json-view-lite": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.4.0.tgz",
+ "integrity": "sha512-wh6F6uJyYAmQ4fK0e8dSQMEWuvTs2Wr3el3sLD9bambX1+pSWUVXIz1RFaoy3TI1mZ0FqdpKq9YgbgTTgyrmXA==",
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": "^16.13.1 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/react-markdown": {
"version": "9.0.1",
"license": "MIT",
diff --git a/package.json b/package.json
index 5c048f95..2c8ba3d6 100644
--- a/package.json
+++ b/package.json
@@ -89,6 +89,7 @@
"react-diff-view": "^3.2.1",
"react-dom": "^18",
"react-hook-form": "^7.49.3",
+ "react-json-view-lite": "^1.4.0",
"react-markdown": "^9.0.1",
"react-query": "^3.39.3",
"react-syntax-highlighter": "^15.5.0",