demo / frontend /src /components /DemoView.tsx
Pierre Andrews
Initial commit
f52d137
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);
// Form validation
const isFormValid = Boolean(
formData.model.trim() && formData.provider.trim(),
);
// Update original form data when component first loads or when iframe URL changes (successful restart)
useEffect(() => {
setOriginalFormData(formData);
}, [iframeUrl]); // Update when iframe URL changes, indicating successful restart
const handleModelChange = useCallback(
(value: string) => {
onFormChange({ ...formData, model: value });
setShowWarning(true);
},
[formData, onFormChange],
);
const handleProviderChange = useCallback(
(value: string) => {
// When provider changes, clear the model as well to avoid stale selections
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);
// Clear the file input element to reset the UI
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);
// Update the original form data to the current form data after restart
setOriginalFormData(formData);
onSettingsRestart();
};
const handleWarningDismiss = () => {
setShowWarning(false);
// Reset form data back to original values
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>
);
};