litellm/ui/litellm-dashboard/src/components/transform_request.tsx
2025-10-10 10:24:57 -07:00

301 lines
9.0 KiB
TypeScript

import React, { useState } from "react";
import { Button } from "antd";
import { CopyOutlined } from "@ant-design/icons";
import { Title } from "@tremor/react";
import { transformRequestCall } from "./networking";
import NotificationsManager from "./molecules/notifications_manager";
interface TransformRequestPanelProps {
accessToken: string | null;
}
interface TransformResponse {
raw_request_api_base: string;
raw_request_body: Record<string, any>;
raw_request_headers: Record<string, string>;
}
const TransformRequestPanel: React.FC<TransformRequestPanelProps> = ({ accessToken }) => {
const [originalRequestJSON, setOriginalRequestJSON] = useState(`{
"model": "openai/gpt-4o",
"messages": [
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "Explain quantum computing in simple terms"
}
],
"temperature": 0.7,
"max_tokens": 500,
"stream": true
}`);
const [transformedResponse, setTransformedResponse] = useState("");
const [isLoading, setIsLoading] = useState(false);
// Function to format curl command from API response parts
const formatCurlCommand = (
apiBase: string,
requestBody: Record<string, any>,
requestHeaders: Record<string, string>,
) => {
// Format the request body as nicely indented JSON with 2 spaces
const formattedBody = JSON.stringify(requestBody, null, 2)
// Add additional indentation for the entire body
.split("\n")
.map((line) => ` ${line}`)
.join("\n");
// Build headers string with consistent indentation
const headerString = Object.entries(requestHeaders)
.map(([key, value]) => `-H '${key}: ${value}'`)
.join(" \\\n ");
// Build the curl command with consistent indentation
return `curl -X POST \\
${apiBase} \\
${headerString ? `${headerString} \\\n ` : ""}-H 'Content-Type: application/json' \\
-d '{
${formattedBody}
}'`;
};
// Function to handle the transform request
const handleTransform = async () => {
setIsLoading(true);
try {
// Parse the JSON from the textarea
let requestBody;
try {
requestBody = JSON.parse(originalRequestJSON);
} catch (e) {
NotificationsManager.fromBackend("Invalid JSON in request body");
setIsLoading(false);
return;
}
// Create the request payload
const payload = {
call_type: "completion",
request_body: requestBody,
};
// Make the API call using fetch
if (!accessToken) {
NotificationsManager.fromBackend("No access token found");
setIsLoading(false);
return;
}
const data = await transformRequestCall(accessToken, payload);
// Check if the response has the expected fields
if (data.raw_request_api_base && data.raw_request_body) {
// Format the curl command with the separate parts
const formattedCurl = formatCurlCommand(
data.raw_request_api_base,
data.raw_request_body,
data.raw_request_headers || {},
);
// Update state with the formatted curl command
setTransformedResponse(formattedCurl);
NotificationsManager.success("Request transformed successfully");
} else {
// Handle the case where the API returns a different format
// Try to extract the parts from a string response if needed
const rawText = typeof data === "string" ? data : JSON.stringify(data);
setTransformedResponse(rawText);
NotificationsManager.info("Transformed request received in unexpected format");
}
} catch (err) {
console.error("Error transforming request:", err);
NotificationsManager.fromBackend("Failed to transform request");
} finally {
setIsLoading(false);
}
};
// Add this handler function near your other handlers
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
e.preventDefault(); // Prevent default behavior
handleTransform();
}
};
return (
<div className="w-full m-2" style={{ overflow: "hidden" }}>
<Title>Playground</Title>
<p className="text-sm text-gray-500">See how LiteLLM transforms your request for the specified provider.</p>
<div
style={{
display: "flex",
gap: "16px",
width: "100%",
minWidth: 0,
overflow: "hidden",
}}
className="mt-4"
>
{/* Original Request Panel */}
<div
style={{
flex: "1 1 50%",
display: "flex",
flexDirection: "column",
border: "1px solid #e8e8e8",
borderRadius: "8px",
padding: "24px",
overflow: "hidden",
maxHeight: "600px",
minWidth: 0,
}}
>
<div style={{ marginBottom: "24px" }}>
<h2 style={{ fontSize: "24px", fontWeight: "bold", margin: "0 0 4px 0" }}>Original Request</h2>
<p style={{ color: "#666", margin: 0 }}>
The request you would send to LiteLLM /chat/completions endpoint.
</p>
</div>
<textarea
style={{
flex: "1 1 auto",
width: "100%",
minHeight: "240px",
padding: "16px",
border: "1px solid #e8e8e8",
borderRadius: "6px",
fontFamily: "monospace",
fontSize: "14px",
resize: "none",
marginBottom: "24px",
overflow: "auto",
}}
value={originalRequestJSON}
onChange={(e) => setOriginalRequestJSON(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Press Cmd/Ctrl + Enter to transform"
/>
<div
style={{
display: "flex",
justifyContent: "flex-end",
marginTop: "auto",
}}
>
<Button
type="primary"
style={{
backgroundColor: "#000",
display: "flex",
alignItems: "center",
gap: "8px",
}}
onClick={handleTransform}
loading={isLoading}
>
<span>Transform</span>
<span></span>
</Button>
</div>
</div>
{/* Transformed Request Panel */}
<div
style={{
flex: "1 1 50%",
display: "flex",
flexDirection: "column",
border: "1px solid #e8e8e8",
borderRadius: "8px",
padding: "24px",
overflow: "hidden",
maxHeight: "800px",
minWidth: 0,
}}
>
<div style={{ marginBottom: "24px" }}>
<h2 style={{ fontSize: "24px", fontWeight: "bold", margin: "0 0 4px 0" }}>Transformed Request</h2>
<p style={{ color: "#666", margin: 0 }}>How LiteLLM transforms your request for the specified provider.</p>
<br />
<p style={{ color: "#666", margin: 0 }} className="text-xs">
Note: Sensitive headers are not shown.
</p>
</div>
<div
style={{
position: "relative",
backgroundColor: "#f5f5f5",
borderRadius: "6px",
flex: "1 1 auto",
display: "flex",
flexDirection: "column",
overflow: "hidden",
}}
>
<pre
style={{
padding: "16px",
fontFamily: "monospace",
fontSize: "14px",
margin: 0,
overflow: "auto",
flex: "1 1 auto",
}}
>
{transformedResponse ||
`curl -X POST \\
https://api.openai.com/v1/chat/completions \\
-H 'Authorization: Bearer sk-xxx' \\
-H 'Content-Type: application/json' \\
-d '{
"model": "gpt-4",
"messages": [
{
"role": "system",
"content": "You are a helpful assistant."
}
],
"temperature": 0.7
}'`}
</pre>
<Button
type="text"
icon={<CopyOutlined />}
style={{
position: "absolute",
right: "8px",
top: "8px",
}}
size="small"
onClick={() => {
navigator.clipboard.writeText(transformedResponse || "");
NotificationsManager.success("Copied to clipboard");
}}
/>
</div>
</div>
</div>
<div className="mt-4 text-right w-full">
<p className="text-sm text-gray-500">
Found an error? File an issue{" "}
<a href="https://github.com/BerriAI/litellm/issues" target="_blank" rel="noopener noreferrer">
here
</a>
.
</p>
</div>
</div>
);
};
export default TransformRequestPanel;