demo / frontend /src /components /InitialForm.tsx
mlcu's picture
Update landing page
79629a0
import {
Article as ArticleIcon,
Assessment as AssessmentIcon,
Book as BookIcon,
Download as DownloadIcon,
Hub as HubIcon,
EmojiEvents as LeaderboardIcon,
RestartAlt as RestartIcon,
RocketLaunch as RocketLaunchIcon,
} from "@mui/icons-material";
import {
alpha,
Box,
Button,
Chip,
CircularProgress,
IconButton,
Link,
Paper,
Stack,
Tooltip,
Typography,
} from "@mui/material";
import React, { useCallback, useState } from "react";
import { FormData, UserInfo } from "../types";
import { DevModeLoginButton } from "./DevModeLoginButton";
import { HuggingFaceLoginButton } from "./HuggingFaceLoginButton";
import { ModelProviderSelector } from "./ModelProviderSelector";
import { TooltipIcon } from "./TooltipIcon";
import { McpConfigurationWarningDialog } from "./dialogs";
const TRANSITION = "all 0.3s ease-in-out";
interface InitialFormProps {
formData: FormData;
userInfo: UserInfo | null;
accessToken: string | null;
loginLabel: string;
isStarting: boolean;
defaultMcpFile: File | null;
onFormChange: (formData: FormData) => void;
onSubmit: () => void;
onLoginStateChange: (
userInfo: UserInfo | null,
accessToken: string | null,
loginLabel: string,
) => void;
}
export const InitialForm: React.FC<InitialFormProps> = ({
formData,
userInfo,
accessToken,
loginLabel,
isStarting,
defaultMcpFile,
onFormChange,
onSubmit,
onLoginStateChange,
}) => {
const [dialogOpen, setDialogOpen] = useState(false);
const [pendingFile, setPendingFile] = useState<File | null>(null);
const [selectedOption, setSelectedOption] = useState<"suggested" | "custom">(
"suggested",
);
const [selectedSuggestion, setSelectedSuggestion] = useState("0");
const [customProvider, setCustomProvider] = useState("");
const [customModel, setCustomModel] = useState("");
// Define the suggested models
const suggestedModels = [
{
provider: "novita",
model: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
label: "Llama-4-Maverick-17B-128E-Instruct-FP8",
},
{
provider: "novita",
model: "zai-org/GLM-4.5",
label: "GLM-4.5",
},
{
provider: "novita",
model: "moonshotai/Kimi-K2-Instruct-0905",
label: "Kimi-K2-Instruct-0905",
},
{
provider: "novita",
model: "deepseek-ai/DeepSeek-V3.1",
label: "DeepSeek-V3.1",
},
{
provider: "novita",
model: "Qwen/Qwen3-Next-80B-A3B-Instruct",
label: "Qwen3-Next-80B-A3B-Instruct",
},
{
provider: "novita",
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
label: "Qwen3-Coder-480B-A35B-Instruct",
},
];
// Initialize form with first suggested model only when form is empty on mount
React.useEffect(() => {
if (
selectedOption === "suggested" &&
suggestedModels[0] &&
!formData.provider &&
!formData.model
) {
onFormChange({
...formData,
provider: suggestedModels[0].provider,
model: suggestedModels[0].model,
});
}
}, []); // Only run once on mount
const isFormValid = Boolean(
formData.model.trim() && formData.provider.trim() && userInfo?.sub,
);
const isLoggedIn = Boolean(userInfo?.sub);
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 });
}
},
[formData, onFormChange],
);
const resetFile = useCallback(() => {
setPendingFile(null);
onFormChange({ ...formData, mcpFile: defaultMcpFile });
}, [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 });
}
setDialogOpen(false);
setPendingFile(null);
}, [formData, onFormChange, pendingFile]);
return (
<>
<Box
sx={{
minHeight: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
bgcolor: "background.paper",
p: 2,
transition: TRANSITION,
}}
>
<Stack
direction={"row"}
spacing={4}
alignItems={"center"}
sx={{
transition: TRANSITION,
}}
>
<Box
sx={{
height: "100%",
display: "flex",
p: 4,
maxWidth: 600,
width: "100%",
flexDirection: "column",
transition: TRANSITION,
}}
>
<Typography variant="h4" component="h1" gutterBottom sx={{ mb: 3 }}>
Meta Agents Research Environments
</Typography>
<Typography sx={{ mb: 2 }}>
Welcome to the Meta ARE (Agents Research Environments) and Gaia2
demo! ARE is a research platform to easily interact with and
evaluate agents. In this demo, you can:
</Typography>
<Stack component="ul" spacing={2} sx={{ mb: 3, pl: 3 }}>
<Typography component="li">
Test a simulated universe with apps representing a smartphone
agent, similar to Gaia2. Find out which agent is the best
assistant by trying different models!
</Typography>
<Typography component="li">
Visualize Gaia2 scenarios, to better understand the benchmark
and debug your agent! Check out the{" "}
<Link
href="https://facebookresearch.github.io/meta-agents-research-environments/"
target="_blank"
rel="noopener noreferrer"
color="info"
sx={{
fontWeight: 500,
textDecoration: "none",
"&:hover": { textDecoration: "underline" },
}}
>
documentation
</Link>{" "}
to find out how to run the Gaia2 benchmark with ARE.
</Typography>
</Stack>
{/* Mobile warning message - only shown on xs screens */}
<Box
sx={{
display: { xs: "block", sm: "none" },
mb: 3,
p: 2,
bgcolor: "info.light",
borderRadius: 1,
border: "1px solid",
borderColor: "info.main",
}}
>
<Typography
variant="body2"
color="info.dark"
align="center"
sx={{ fontWeight: 500 }}
>
📱 This demo is not optimized for mobile devices. Please use a
desktop or tablet for the best experience.
</Typography>
</Box>
{/* Informational links */}
<Typography variant="overline" color="textSecondary" sx={{ mb: 1 }}>
Additional links
</Typography>
<Stack spacing={1} direction={"row"}>
<Chip
icon={<BookIcon fontSize="inherit" />}
label="Docs"
component="a"
href="https://facebookresearch.github.io/meta-agents-research-environments/"
target="_blank"
variant="outlined"
clickable
sx={{
pl: 0.5,
}}
/>
<Chip
icon={<HubIcon fontSize="inherit" />}
label="Gaia2"
component="a"
href="https://huggingface.co/datasets/meta-agents-research-environments/gaia2"
target="_blank"
variant="outlined"
clickable
sx={{
pl: 0.5,
}}
/>
<Chip
icon={<AssessmentIcon fontSize="inherit" />}
label="Paper"
component="a"
href="https://arxiv.org/abs/2509.17158"
target="_blank"
variant="outlined"
clickable
sx={{
pl: 0.5,
}}
/>
<Chip
icon={<LeaderboardIcon fontSize="inherit" />}
label="Leaderboard"
component="a"
href="https://huggingface.co/spaces/meta-agents-research-environments/leaderboard"
target="_blank"
variant="outlined"
clickable
sx={{
pl: 0.5,
}}
/>
<Chip
icon={<ArticleIcon fontSize="inherit" />}
label="Blog"
component="a"
href="https://huggingface.co/blog/gaia2"
target="_blank"
variant="outlined"
clickable
sx={{
pl: 0.5,
}}
/>
</Stack>
</Box>
<Paper
elevation={3}
sx={{
p: 1,
maxWidth: 400,
width: "100%",
borderRadius: 2,
transition: TRANSITION,
}}
>
<Stack
sx={{
height: "100%",
transition: TRANSITION,
}}
>
<Typography variant="h5" sx={{ p: 1 }}>
Get started
</Typography>
{/* Login Section */}
<Stack
spacing={2}
sx={{
p: 1.5,
m: 1,
border: "2px solid",
borderColor: (theme) =>
isLoggedIn
? alpha(theme.palette.action.disabled, 0.1)
: "primary.main",
borderRadius: 1.5,
transition: TRANSITION,
}}
>
<Typography variant="body2" color="text.info" sx={{ mb: 2 }}>
Sign in with your Hugging Face account to access the inference
providers.{" "}
<strong>
This demo is free to use - we've provided credits to cover
the inference costs for your agent runs.
</strong>{" "}
We only use your Hugging Face account for authentication and do not access any of your other data or resources.
</Typography>
{process.env.NODE_ENV === "development" ? (
<DevModeLoginButton
userInfo={userInfo}
accessToken={accessToken}
loginLabel={loginLabel}
onLoginStateChange={onLoginStateChange}
isDisabled={isStarting}
/>
) : (
<HuggingFaceLoginButton
userInfo={userInfo}
accessToken={accessToken}
loginLabel={loginLabel}
onLoginStateChange={onLoginStateChange}
isDisabled={isStarting}
/>
)}
</Stack>
{/* Model and Provider Selection */}
<Stack spacing={0.5} sx={{ p: 1, mt: 0 }}>
{/* Model Suggestions with two boxes */}
<ModelProviderSelector
variant="suggestions"
model={formData.model}
provider={formData.provider}
onModelChange={() => {}}
onProviderChange={() => {}}
suggestedModels={suggestedModels}
selectedSuggestion={selectedSuggestion}
onSuggestionChange={(value, provider, model) => {
setSelectedSuggestion(value);
onFormChange({ ...formData, provider, model });
}}
customProvider={customProvider}
customModel={customModel}
onCustomProviderChange={(provider) => {
setCustomProvider(provider);
if (customModel) {
onFormChange({
...formData,
provider,
model: customModel,
});
}
}}
onCustomModelChange={(model) => {
setCustomModel(model);
if (customProvider) {
onFormChange({
...formData,
provider: customProvider,
model,
});
}
}}
selectedOption={selectedOption}
onOptionChange={(option) => {
setSelectedOption(option);
if (option === "suggested") {
// Use currently selected suggestion, or default to first one if none selected
const selectedIndex =
selectedSuggestion !== ""
? parseInt(selectedSuggestion)
: 0;
const model = suggestedModels[selectedIndex];
if (model) {
onFormChange({
...formData,
provider: model.provider,
model: model.model,
});
}
}
}}
isDisabled={!isLoggedIn || isStarting}
/>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
mb: 1,
mt: 0,
}}
>
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
<Typography>Upload an MCP configuration</Typography>
<Typography variant="body2" color="textDisabled">
Optional
</Typography>
</Box>
<Stack spacing={1} direction="row" alignItems="center">
<TooltipIcon
title="This demo comes with preloaded simulated apps like Messages,
Shopping and more. Optionally upload an MCP (Model Context
Protocol) file (.json) that defines the tools and capabilities
for your agent. If the value 'HF_TOKEN' appears in headers, it
will be replaced with your token automatically."
/>
</Stack>
</Box>
<Button
variant="outlined"
component="label"
fullWidth
disabled={!isLoggedIn || isStarting}
sx={{
py: 1.5,
px: 2,
justifyContent: "flex-start",
textAlign: "left",
borderWidth: "2px !important",
borderColor: (theme) => theme.palette.grey[700],
"&:hover": {
borderColor: (theme) => theme.palette.action.active,
},
borderRadius: 1.5,
}}
color="inherit"
>
<Box
sx={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
width: "100%",
}}
>
{formData.mcpFile
? formData.mcpFile.name
: "Click to upload MCP file (.json)"}
</Box>
<input
type="file"
hidden
accept=".json"
onChange={handleFileChange}
disabled={!isLoggedIn || isStarting}
/>
</Button>
<Stack
direction="row"
alignItems="center"
justifyContent={"flex-end"}
>
<Tooltip title="Reset to demo MCP configuration file">
<span>
<IconButton
size="small"
onClick={resetFile}
color="inherit"
disabled={formData.mcpFile === defaultMcpFile}
>
<RestartIcon fontSize="inherit" />
</IconButton>
</span>
</Tooltip>
<Tooltip title="Download demo MCP configuration file">
<span>
<IconButton href="/demo-mcp.json" download size="small">
<DownloadIcon fontSize="inherit" />
</IconButton>
</span>
</Tooltip>
</Stack>
</Stack>
{/* Start Button */}
<Box sx={{ p: 2, mt: 0, pt: 1 }}>
<Button
fullWidth
variant="contained"
color="secondary"
size="large"
startIcon={
isStarting ? (
<CircularProgress size={20} color="inherit" />
) : (
<RocketLaunchIcon />
)
}
onClick={onSubmit}
disabled={!isFormValid || isStarting}
sx={{ py: 1.5 }}
>
{isStarting ? "Launching demo…" : "Launch demo"}
</Button>
</Box>
</Stack>
</Paper>
</Stack>
</Box>
{/* MCP File Upload Warning Dialog */}
<McpConfigurationWarningDialog
open={dialogOpen}
onClose={handleDialogClose}
onConfirm={handleDialogConfirm}
/>
</>
);
};