|
|
import { |
|
|
Logout as LogoutIcon, |
|
|
Refresh as RefreshIcon, |
|
|
UploadFile as UploadFileIcon, |
|
|
Warning as WarningIcon, |
|
|
} from "@mui/icons-material"; |
|
|
import { |
|
|
Alert, |
|
|
AppBar, |
|
|
Box, |
|
|
Button, |
|
|
CircularProgress, |
|
|
Collapse, |
|
|
Divider, |
|
|
Stack, |
|
|
Toolbar, |
|
|
Tooltip, |
|
|
Typography, |
|
|
} from "@mui/material"; |
|
|
import React, { useCallback, useEffect, useState } from "react"; |
|
|
import { FormData } from "../types"; |
|
|
import { ModelProviderSelector } from "./ModelProviderSelector"; |
|
|
import { McpConfigurationWarningDialog } from "./dialogs"; |
|
|
import { ServerLoadingIndicator } from "./ServerLoadingIndicator"; |
|
|
|
|
|
interface DemoViewProps { |
|
|
iframeUrl: string; |
|
|
iframeLoading: boolean; |
|
|
healthCheckProgress?: { |
|
|
attempt: number; |
|
|
maxAttempts: number; |
|
|
} | null; |
|
|
formData: FormData; |
|
|
defaultMcpFile: File | null; |
|
|
onIframeLoad: () => void; |
|
|
onRestart: () => void; |
|
|
onFormChange: (formData: FormData) => void; |
|
|
onSettingsRestart: () => void; |
|
|
} |
|
|
|
|
|
export const DemoView: React.FC<DemoViewProps> = ({ |
|
|
iframeUrl, |
|
|
iframeLoading, |
|
|
healthCheckProgress, |
|
|
formData, |
|
|
onIframeLoad, |
|
|
onRestart, |
|
|
onFormChange, |
|
|
onSettingsRestart, |
|
|
}) => { |
|
|
const [showWarning, setShowWarning] = useState(false); |
|
|
const [originalFormData, setOriginalFormData] = useState<FormData>(formData); |
|
|
const [dialogOpen, setDialogOpen] = useState(false); |
|
|
const [pendingFile, setPendingFile] = useState<File | null>(null); |
|
|
const [isMcpTooltipOpen, setMcpTooltipOpen] = useState(false); |
|
|
|
|
|
|
|
|
const isFormValid = Boolean( |
|
|
formData.model.trim() && formData.provider.trim(), |
|
|
); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
setOriginalFormData(formData); |
|
|
}, [iframeUrl]); |
|
|
|
|
|
const handleModelChange = useCallback( |
|
|
(value: string) => { |
|
|
onFormChange({ ...formData, model: value }); |
|
|
setShowWarning(true); |
|
|
}, |
|
|
[formData, onFormChange], |
|
|
); |
|
|
|
|
|
const handleProviderChange = useCallback( |
|
|
(value: string) => { |
|
|
|
|
|
onFormChange({ ...formData, provider: value, model: "" }); |
|
|
setShowWarning(true); |
|
|
}, |
|
|
[formData, onFormChange], |
|
|
); |
|
|
|
|
|
const handleFileChange = useCallback( |
|
|
(event: React.ChangeEvent<HTMLInputElement>) => { |
|
|
const file = event.target.files?.[0] || null; |
|
|
if (file) { |
|
|
setPendingFile(file); |
|
|
setDialogOpen(true); |
|
|
} else { |
|
|
onFormChange({ ...formData, mcpFile: null }); |
|
|
setShowWarning(true); |
|
|
} |
|
|
}, |
|
|
[formData, onFormChange], |
|
|
); |
|
|
|
|
|
const handleDialogClose = useCallback(() => { |
|
|
setDialogOpen(false); |
|
|
setPendingFile(null); |
|
|
|
|
|
const fileInput = document.querySelector( |
|
|
'input[type="file"]', |
|
|
) as HTMLInputElement; |
|
|
if (fileInput) { |
|
|
fileInput.value = ""; |
|
|
} |
|
|
}, []); |
|
|
|
|
|
const handleDialogConfirm = useCallback(() => { |
|
|
if (pendingFile) { |
|
|
onFormChange({ ...formData, mcpFile: pendingFile }); |
|
|
setShowWarning(true); |
|
|
} |
|
|
setDialogOpen(false); |
|
|
setPendingFile(null); |
|
|
}, [formData, onFormChange, pendingFile]); |
|
|
|
|
|
const handleSettingsRestartClick = () => { |
|
|
setShowWarning(false); |
|
|
|
|
|
setOriginalFormData(formData); |
|
|
onSettingsRestart(); |
|
|
}; |
|
|
|
|
|
const handleWarningDismiss = () => { |
|
|
setShowWarning(false); |
|
|
|
|
|
onFormChange(originalFormData); |
|
|
}; |
|
|
|
|
|
return ( |
|
|
<Box sx={{ minHeight: "100vh", bgcolor: "background.default" }}> |
|
|
{/* Top Bar with Settings and Restart Button */} |
|
|
<AppBar position="static" color="default" elevation={1}> |
|
|
<Toolbar sx={{ px: 2, py: 1 }}> |
|
|
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}> |
|
|
<Typography |
|
|
variant="body1" |
|
|
component="div" |
|
|
sx={{ fontWeight: 500 }} |
|
|
> |
|
|
Meta Agents Research Environments |
|
|
</Typography> |
|
|
</Box> |
|
|
|
|
|
{/* Spacer to push form to the right */} |
|
|
<Box sx={{ flexGrow: 1 }} /> |
|
|
|
|
|
{/* Settings Form */} |
|
|
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}> |
|
|
{/* Model and Provider Selection */} |
|
|
<ModelProviderSelector |
|
|
model={formData.model} |
|
|
provider={formData.provider} |
|
|
onModelChange={handleModelChange} |
|
|
onProviderChange={handleProviderChange} |
|
|
variant="toolbar" |
|
|
size="small" |
|
|
showValidation={true} |
|
|
/> |
|
|
|
|
|
{/* MCP File Input with Tooltip */} |
|
|
<Box |
|
|
sx={{ |
|
|
display: "flex", |
|
|
alignItems: "center", |
|
|
gap: 0.5, |
|
|
width: "100%", |
|
|
maxWidth: 500, |
|
|
}} |
|
|
> |
|
|
<Tooltip |
|
|
title="Upload an MCP (Model Context Protocol) file (.json) that defines the tools and capabilities for your agent." |
|
|
placement="left" |
|
|
open={isMcpTooltipOpen} |
|
|
> |
|
|
<span |
|
|
style={{ width: "200px" }} |
|
|
onMouseEnter={() => setMcpTooltipOpen(true)} |
|
|
onMouseLeave={() => setMcpTooltipOpen(false)} |
|
|
onClick={() => setMcpTooltipOpen(false)} |
|
|
> |
|
|
<Button |
|
|
variant="outlined" |
|
|
component="label" |
|
|
startIcon={<UploadFileIcon fontSize="inherit" />} |
|
|
fullWidth |
|
|
sx={{ |
|
|
justifyContent: "flex-start", |
|
|
textAlign: "left", |
|
|
height: 40, |
|
|
borderColor: (theme) => theme.palette.grey[700], |
|
|
"&:hover": { |
|
|
borderColor: (theme) => theme.palette.action.active, |
|
|
}, |
|
|
}} |
|
|
color="inherit" |
|
|
> |
|
|
<Box |
|
|
sx={{ |
|
|
overflow: "hidden", |
|
|
textOverflow: "ellipsis", |
|
|
whiteSpace: "nowrap", |
|
|
width: "100%", |
|
|
}} |
|
|
> |
|
|
{formData.mcpFile |
|
|
? `${formData.mcpFile.name}` |
|
|
: "MCP File"} |
|
|
</Box> |
|
|
<input |
|
|
type="file" |
|
|
hidden |
|
|
accept=".json" |
|
|
onChange={handleFileChange} |
|
|
/> |
|
|
</Button> |
|
|
</span> |
|
|
</Tooltip> |
|
|
</Box> |
|
|
<Divider orientation="vertical" flexItem /> |
|
|
{/* Restart Button */} |
|
|
|
|
|
<Button |
|
|
variant="outlined" |
|
|
size="small" |
|
|
startIcon={<LogoutIcon />} |
|
|
onClick={onRestart} |
|
|
color="inherit" |
|
|
sx={{ height: 40, opacity: 0.7 }} |
|
|
fullWidth |
|
|
> |
|
|
Exit demo |
|
|
</Button> |
|
|
</Box> |
|
|
</Toolbar> |
|
|
</AppBar> |
|
|
|
|
|
{/* Warning Alert */} |
|
|
<Collapse in={showWarning}> |
|
|
<Alert |
|
|
severity="warning" |
|
|
variant="filled" |
|
|
icon={<WarningIcon />} |
|
|
action={ |
|
|
<Stack spacing={1} direction="row" alignItems={"center"}> |
|
|
<Button |
|
|
variant="text" |
|
|
size="small" |
|
|
onClick={handleWarningDismiss} |
|
|
color="inherit" |
|
|
> |
|
|
Cancel |
|
|
</Button> |
|
|
<Button |
|
|
variant="contained" |
|
|
size="small" |
|
|
startIcon={<RefreshIcon />} |
|
|
onClick={handleSettingsRestartClick} |
|
|
color="warning" |
|
|
disabled={!isFormValid} |
|
|
> |
|
|
Restart demo with changes |
|
|
</Button> |
|
|
</Stack> |
|
|
} |
|
|
sx={{ borderTopLeftRadius: 0, borderTopRightRadius: 0, pl: 3, pr: 4 }} |
|
|
> |
|
|
You've made changes to the configuration. Click "Restart demo with |
|
|
changes" to apply them. |
|
|
</Alert> |
|
|
</Collapse> |
|
|
|
|
|
{/* Iframe Content */} |
|
|
<Box |
|
|
sx={{ |
|
|
height: showWarning ? "calc(100vh - 112px)" : "calc(100vh - 64px)", |
|
|
position: "relative", |
|
|
transition: "height 0.3s ease", |
|
|
}} |
|
|
> |
|
|
{iframeLoading ? ( |
|
|
<Box |
|
|
sx={{ |
|
|
position: "absolute", |
|
|
top: "50%", |
|
|
left: "50%", |
|
|
transform: "translate(-50%, -50%)", |
|
|
zIndex: 3, |
|
|
}} |
|
|
> |
|
|
<ServerLoadingIndicator |
|
|
progress={healthCheckProgress} |
|
|
message="Waiting for server to start..." |
|
|
/> |
|
|
</Box> |
|
|
) : ( |
|
|
<iframe |
|
|
src={iframeUrl} |
|
|
style={{ |
|
|
width: "100%", |
|
|
height: "100%", |
|
|
border: "none", |
|
|
display: "block", |
|
|
}} |
|
|
onLoad={onIframeLoad} |
|
|
title="Demo Application" |
|
|
/> |
|
|
)} |
|
|
</Box> |
|
|
|
|
|
{/* MCP File Upload Warning Dialog */} |
|
|
<McpConfigurationWarningDialog |
|
|
open={dialogOpen} |
|
|
onClose={handleDialogClose} |
|
|
onConfirm={handleDialogConfirm} |
|
|
/> |
|
|
</Box> |
|
|
); |
|
|
}; |
|
|
|