Shyamnath's picture
Push UI dashboard and deployment files
c40c75a
import React from 'react';
import { Typography, Space, Button, Divider, message } from 'antd';
import { WarningOutlined, InfoCircleOutlined, CopyOutlined } from '@ant-design/icons';
import { testConnectionRequest } from "../networking";
import { prepareModelAddRequest } from "./handle_add_model_submit";
const { Text } = Typography;
interface ModelConnectionTestProps {
formValues: Record<string, any>;
accessToken: string;
testMode: string;
modelName?: string;
onClose?: () => void;
onTestComplete?: () => void;
}
const ModelConnectionTest: React.FC<ModelConnectionTestProps> = ({
formValues,
accessToken,
testMode,
modelName = "this model",
onClose,
onTestComplete
}) => {
const [error, setError] = React.useState<Error | string | null>(null);
const [rawRequest, setRawRequest] = React.useState<any>(null);
const [rawResponse, setRawResponse] = React.useState<any>(null);
const [isLoading, setIsLoading] = React.useState<boolean>(true);
const [isSuccess, setIsSuccess] = React.useState<boolean>(false);
const [showDetails, setShowDetails] = React.useState<boolean>(false);
const testModelConnection = async () => {
setIsLoading(true);
setShowDetails(false);
setError(null);
setRawRequest(null);
setRawResponse(null);
setIsSuccess(false);
// Add a small delay to ensure form values are fully populated
await new Promise(resolve => setTimeout(resolve, 100));
try {
console.log("Testing connection with form values:", formValues);
const result = await prepareModelAddRequest(formValues, accessToken, null);
if (!result) {
console.log("No result from prepareModelAddRequest");
setError("Failed to prepare model data. Please check your form inputs.");
setIsSuccess(false);
setIsLoading(false);
return;
}
console.log("Result from prepareModelAddRequest:", result);
const { litellmParamsObj, modelInfoObj, modelName: returnedModelName } = result[0];
const response = await testConnectionRequest(accessToken, litellmParamsObj, modelInfoObj?.mode);
if (response.status === "success") {
message.success("Connection test successful!");
setError(null);
setIsSuccess(true);
} else {
const errorMessage = response.result?.error || response.message || "Unknown error";
setError(errorMessage);
setRawRequest(litellmParamsObj);
setRawResponse(response.result?.raw_request_typed_dict);
setIsSuccess(false);
}
} catch (error) {
console.error("Test connection error:", error);
setError(error instanceof Error ? error.message : String(error));
setIsSuccess(false);
} finally {
setIsLoading(false);
if (onTestComplete) onTestComplete();
}
};
React.useEffect(() => {
// Run the test once when component mounts
// Add a small timeout to ensure form values are ready
const timer = setTimeout(() => {
testModelConnection();
}, 200);
return () => clearTimeout(timer);
}, []); // Empty dependency array means this runs once on mount
const getCleanErrorMessage = (errorMsg: string) => {
if (!errorMsg) return "Unknown error";
const mainError = errorMsg.split('stack trace:')[0].trim();
const cleanedError = mainError.replace(/^litellm\.(.*?)Error: /, '');
return cleanedError;
};
const errorMessage = typeof error === 'string'
? getCleanErrorMessage(error)
: error?.message ? getCleanErrorMessage(error.message) : "Unknown error";
const formatCurlCommand = (apiBase: string, requestBody: Record<string, any>, requestHeaders: Record<string, string>) => {
const formattedBody = JSON.stringify(requestBody, null, 2)
.split('\n')
.map(line => ` ${line}`)
.join('\n');
const headerString = Object.entries(requestHeaders)
.map(([key, value]) => `-H '${key}: ${value}'`)
.join(' \\\n ');
return `curl -X POST \\
${apiBase} \\
${headerString ? `${headerString} \\\n ` : ''}-H 'Content-Type: application/json' \\
-d '{
${formattedBody}
}'`;
};
const curlCommand = rawResponse ? formatCurlCommand(
rawResponse.raw_request_api_base,
rawResponse.raw_request_body,
rawResponse.raw_request_headers || {}
) : '';
return (
<div style={{ padding: '24px', borderRadius: '8px', backgroundColor: '#fff' }}>
{isLoading ? (
<div style={{ textAlign: 'center', padding: '32px 20px' }}>
<div className="loading-spinner" style={{ marginBottom: '16px' }}>
{/* Simple CSS spinner */}
<div style={{
border: '3px solid #f3f3f3',
borderTop: '3px solid #1890ff',
borderRadius: '50%',
width: '30px',
height: '30px',
animation: 'spin 1s linear infinite',
margin: '0 auto'
}} />
</div>
<Text style={{ fontSize: '16px' }}>Testing connection to {modelName}...</Text>
<style jsx>{`
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`}</style>
</div>
) : isSuccess ? (
<div style={{ textAlign: 'center', padding: '32px 20px' }}>
<div style={{ color: '#52c41a', fontSize: '32px', marginBottom: '16px' }}>
<svg viewBox="64 64 896 896" focusable="false" data-icon="check-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true">
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"></path>
</svg>
</div>
<Text type="success" style={{ fontSize: '18px', fontWeight: 500 }}>
Connection to {modelName} successful!
</Text>
</div>
) : (
<>
<div>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '20px' }}>
<WarningOutlined style={{ color: '#ff4d4f', fontSize: '24px', marginRight: '12px' }} />
<Text type="danger" style={{ fontSize: '18px', fontWeight: 500 }}>Connection to {modelName} failed</Text>
</div>
<div style={{
backgroundColor: '#fff2f0',
border: '1px solid #ffccc7',
borderRadius: '8px',
padding: '16px',
marginBottom: '20px',
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.03)'
}}>
<Text strong style={{ display: 'block', marginBottom: '8px' }}>Error: </Text>
<Text type="danger" style={{ fontSize: '14px', lineHeight: '1.5' }}>{errorMessage}</Text>
{error && (
<div style={{ marginTop: '12px' }}>
<Button
type="link"
onClick={() => setShowDetails(!showDetails)}
style={{ paddingLeft: 0, height: 'auto' }}
>
{showDetails ? 'Hide Details' : 'Show Details'}
</Button>
</div>
)}
</div>
{showDetails && (
<div style={{ marginBottom: '20px' }}>
<Text strong style={{ display: 'block', marginBottom: '8px', fontSize: '15px' }}>Troubleshooting Details</Text>
<pre style={{
backgroundColor: '#f5f5f5',
padding: '16px',
borderRadius: '8px',
fontSize: '13px',
maxHeight: '200px',
overflow: 'auto',
border: '1px solid #e8e8e8',
lineHeight: '1.5'
}}>
{typeof error === 'string' ? error : JSON.stringify(error, null, 2)}
</pre>
</div>
)}
<div>
<Text strong style={{ display: 'block', marginBottom: '8px', fontSize: '15px' }}>API Request</Text>
<pre style={{
backgroundColor: '#f5f5f5',
padding: '16px',
borderRadius: '8px',
fontSize: '13px',
maxHeight: '250px',
overflow: 'auto',
border: '1px solid #e8e8e8',
lineHeight: '1.5'
}}>
{curlCommand || "No request data available"}
</pre>
<Button
style={{ marginTop: '8px' }}
icon={<CopyOutlined />}
onClick={() => {
navigator.clipboard.writeText(curlCommand || '');
message.success('Copied to clipboard');
}}
>
Copy to Clipboard
</Button>
</div>
</div>
</>
)}
<Divider style={{ margin: '24px 0 16px' }} />
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Button
type="link"
href="https://docs.litellm.ai/docs/providers"
target="_blank"
icon={<InfoCircleOutlined />}
>
View Documentation
</Button>
</div>
</div>
);
};
export default ModelConnectionTest;