diff --git a/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/types.ts b/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/types.ts
index 4953c9b9437..37e73d0169b 100644
--- a/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/types.ts
+++ b/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/types.ts
@@ -20,6 +20,7 @@ export interface PromptType {
tools: Tool[];
developerMessage: string;
messages: Message[];
+ environment: string;
}
export interface PromptEditorViewProps {
diff --git a/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/utils.ts b/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/utils.ts
index 523470e34d0..a8dd7f60eb1 100644
--- a/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/utils.ts
+++ b/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/utils.ts
@@ -246,6 +246,7 @@ export const parseExistingPrompt = (apiResponse: any): PromptType => {
parsedBody.messages.length > 0
? parsedBody.messages
: [{ role: "user", content: "Enter task specifics. Use {{template_variables}} for dynamic inputs" }],
+ environment: apiResponse?.prompt_spec?.environment || apiResponse?.prompt_spec?.prompt_info?.environment || "development",
};
};
diff --git a/ui/litellm-dashboard/src/components/prompts/prompt_info.tsx b/ui/litellm-dashboard/src/components/prompts/prompt_info.tsx
index dec0baa9505..ccc3560b849 100644
--- a/ui/litellm-dashboard/src/components/prompts/prompt_info.tsx
+++ b/ui/litellm-dashboard/src/components/prompts/prompt_info.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from "react";
+import React, { useState, useEffect, useRef } from "react";
import {
Card,
Title,
@@ -11,19 +11,25 @@ import {
TabList,
TabPanel,
TabPanels,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeaderCell,
+ TableRow,
} from "@tremor/react";
import { Button, Modal } from "antd";
import { ArrowLeftIcon, TrashIcon, PencilIcon } from "@heroicons/react/outline";
-import { getPromptInfo, PromptSpec, PromptTemplateBase, deletePromptCall } from "@/components/networking";
+import { getPromptInfo, getPromptVersions, PromptSpec, PromptTemplateBase, deletePromptCall } from "@/components/networking";
import { copyToClipboard as utilCopyToClipboard } from "@/utils/dataUtils";
import { CheckIcon, CopyIcon } from "lucide-react";
import NotificationsManager from "../molecules/notifications_manager";
import PromptCodeSnippets from "./prompt_editor_view/PromptCodeSnippets";
-import {
- extractModel,
- extractTemplateVariables,
- getBasePromptId,
- getCurrentVersion
+import {
+ extractModel,
+ extractTemplateVariables,
+ getBasePromptId,
+ getCurrentVersion
} from "./prompt_utils";
export interface PromptInfoProps {
@@ -44,14 +50,31 @@ const PromptInfoView: React.FC
= ({ promptId, onClose, accessTo
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
- const fetchPromptInfo = async () => {
+ // Environment and version state
+ const [environments, setEnvironments] = useState([]);
+ const [selectedEnv, setSelectedEnv] = useState(null);
+ const [versionHistory, setVersionHistory] = useState([]);
+ const [selectedVersion, setSelectedVersion] = useState(null);
+ const [loadingVersions, setLoadingVersions] = useState(false);
+
+ // Initial fetch — no environment filter, gets default + all environments list
+ const fetchPromptInfo = async (environment?: string) => {
try {
setLoading(true);
if (!accessToken) return;
- const response = await getPromptInfo(accessToken, promptId);
+ const response = await getPromptInfo(accessToken, promptId, environment);
setPromptData(response.prompt_spec);
setPromptTemplate(response.raw_prompt_template);
- setRawApiResponse(response); // Store the raw response for the Raw JSON tab
+ setRawApiResponse(response);
+
+ // Set environments from response
+ if (response.environments && response.environments.length > 0) {
+ setEnvironments(response.environments);
+ if (!selectedEnv) {
+ setSelectedEnv(response.prompt_spec.environment || response.environments[0]);
+ }
+ }
+ setSelectedVersion(response.prompt_spec.version || null);
} catch (error) {
NotificationsManager.fromBackend("Failed to load prompt information");
console.error("Error fetching prompt info:", error);
@@ -60,11 +83,46 @@ const PromptInfoView: React.FC = ({ promptId, onClose, accessTo
}
};
+ // Fetch version history for selected environment
+ const fetchVersionHistory = async (env: string) => {
+ if (!accessToken) return;
+ setLoadingVersions(true);
+ try {
+ const response = await getPromptVersions(accessToken, promptId, env);
+ setVersionHistory(response.prompts || []);
+ } catch {
+ setVersionHistory([]);
+ } finally {
+ setLoadingVersions(false);
+ }
+ };
+
+ const isInitialMount = useRef(true);
+
useEffect(() => {
+ setSelectedEnv(null);
+ setEnvironments([]);
+ setVersionHistory([]);
fetchPromptInfo();
}, [promptId, accessToken]);
- if (loading) {
+ // When environment changes (user clicks tab), re-fetch — skip initial mount
+ useEffect(() => {
+ if (isInitialMount.current) {
+ isInitialMount.current = false;
+ // Still fetch version history on initial mount once selectedEnv is set
+ if (selectedEnv && accessToken) {
+ fetchVersionHistory(selectedEnv);
+ }
+ return;
+ }
+ if (selectedEnv && accessToken) {
+ fetchPromptInfo(selectedEnv);
+ fetchVersionHistory(selectedEnv);
+ }
+ }, [selectedEnv]);
+
+ if (loading && !promptData) {
return Loading...
;
}
@@ -72,7 +130,6 @@ const PromptInfoView: React.FC = ({ promptId, onClose, accessTo
return Prompt not found
;
}
- // Format date helper function
const formatDate = (dateString?: string) => {
if (!dateString) return "-";
const date = new Date(dateString);
@@ -95,13 +152,12 @@ const PromptInfoView: React.FC = ({ promptId, onClose, accessTo
const handleDeleteConfirm = async () => {
if (!accessToken || !promptData) return;
-
setIsDeleting(true);
try {
await deletePromptCall(accessToken, basePromptId);
NotificationsManager.success(`Prompt "${basePromptId}" deleted successfully`);
- onDelete?.(); // Call the callback to refresh the parent component
- onClose(); // Close the info view
+ onDelete?.();
+ onClose();
} catch (error) {
console.error("Error deleting prompt:", error);
NotificationsManager.fromBackend("Failed to delete prompt");
@@ -115,10 +171,27 @@ const PromptInfoView: React.FC = ({ promptId, onClose, accessTo
setShowDeleteConfirm(false);
};
- // Use utility functions to extract prompt data
+ const handleVersionClick = async (version: PromptSpec) => {
+ if (!accessToken || !selectedEnv) return;
+ // Fetch specific version's info
+ const versionNum = version.version || 1;
+ setSelectedVersion(versionNum);
+ try {
+ const versionedId = `${promptId}.v${versionNum}`;
+ const response = await getPromptInfo(accessToken, versionedId, selectedEnv);
+ setPromptData(response.prompt_spec);
+ setPromptTemplate(response.raw_prompt_template);
+ setRawApiResponse(response);
+ } catch {
+ NotificationsManager.fromBackend(`Failed to load version v${versionNum}`);
+ }
+ };
+
const promptModel = promptData ? extractModel(promptData) || "gpt-4o" : "gpt-4o";
const basePromptId = getBasePromptId(promptData);
const currentVersion = getCurrentVersion(promptData);
+ const latestVersion = versionHistory.length > 0 ? Math.max(...versionHistory.map(v => v.version || 1)) : null;
+ const isViewingOldVersion = latestVersion !== null && selectedVersion !== null && selectedVersion < latestVersion;
return (
@@ -174,32 +247,75 @@ const PromptInfoView: React.FC
= ({ promptId, onClose, accessTo
+ {/* Environment Tabs */}
+ {environments.length > 0 && (
+