|
|
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(""); |
|
|
|
|
|
|
|
|
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", |
|
|
}, |
|
|
]; |
|
|
|
|
|
|
|
|
React.useEffect(() => { |
|
|
if ( |
|
|
selectedOption === "suggested" && |
|
|
suggestedModels[0] && |
|
|
!formData.provider && |
|
|
!formData.model |
|
|
) { |
|
|
onFormChange({ |
|
|
...formData, |
|
|
provider: suggestedModels[0].provider, |
|
|
model: suggestedModels[0].model, |
|
|
}); |
|
|
} |
|
|
}, []); |
|
|
|
|
|
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); |
|
|
|
|
|
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} |
|
|
/> |
|
|
</> |
|
|
); |
|
|
}; |
|
|
|