-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add multi-agent support to dashboard frontend #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import { CircularProgress, MenuItem, Typography } from '@mui/material'; | ||
| import { styled } from '@mui/material/styles'; | ||
| import Select from '@mui/material/Select'; | ||
| import { useGetTenantsQuery, useGetAgentsQuery } from '../../services/dashboardApi'; | ||
| import type { AgentSelectorProps } from './AgentSelector.types'; | ||
|
|
||
| const CompactSelect = styled(Select)(({ theme }) => ({ | ||
| fontSize: 12, | ||
| fontWeight: 600, | ||
| borderRadius: 8, | ||
| color: theme.palette.mode === 'dark' ? '#67748e' : 'rgba(255,255,255,0.85)', | ||
| '& .MuiOutlinedInput-notchedOutline': { | ||
| borderColor: theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.12)' : 'rgba(255,255,255,0.18)', | ||
| }, | ||
| '&:hover .MuiOutlinedInput-notchedOutline': { | ||
| borderColor: theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.24)' : 'rgba(255,255,255,0.35)', | ||
| }, | ||
| '& .MuiSvgIcon-root': { | ||
| color: theme.palette.mode === 'dark' ? '#67748e' : 'rgba(255,255,255,0.6)', | ||
| fontSize: 18, | ||
| }, | ||
| '& .MuiSelect-select': { | ||
| py: '6px', | ||
| px: '12px', | ||
| }, | ||
| })); | ||
|
|
||
| const NONE_VALUE = '__none__'; | ||
|
|
||
| const AgentSelector = ({ selectedAgentId, onAgentChange }: AgentSelectorProps) => { | ||
| const { data: tenantsData, isLoading: tenantsLoading } = useGetTenantsQuery(); | ||
| const firstTenantId = tenantsData?.data?.[0]?.id; | ||
| const { data: agentsData, isLoading: agentsLoading } = useGetAgentsQuery(firstTenantId!, { | ||
| skip: !firstTenantId, | ||
| }); | ||
|
|
||
| const loading = tenantsLoading || agentsLoading; | ||
| const agents = agentsData?.data ?? []; | ||
|
|
||
| if (loading) { | ||
| return <CircularProgress size={16} sx={{ mx: 'auto', display: 'block', my: 1 }} />; | ||
| } | ||
|
|
||
| if (agents.length === 0) { | ||
| return ( | ||
| <Typography variant="caption" sx={{ fontSize: 11, opacity: 0.5, px: 1.5, py: 1, display: 'block' }}> | ||
| Default agent | ||
| </Typography> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <CompactSelect | ||
| size="small" | ||
| fullWidth | ||
| value={selectedAgentId ?? NONE_VALUE} | ||
| onChange={(e) => { | ||
| const val = e.target.value as string; | ||
| onAgentChange(val === NONE_VALUE ? null : val); | ||
| }} | ||
| > | ||
| <MenuItem value={NONE_VALUE} sx={{ fontSize: 12 }}>Default (all)</MenuItem> | ||
| {agents.map((agent) => ( | ||
| <MenuItem key={agent.id} value={agent.id} sx={{ fontSize: 12 }}> | ||
| {agent.name} | ||
| </MenuItem> | ||
| ))} | ||
| </CompactSelect> | ||
| ); | ||
| }; | ||
|
|
||
| export default AgentSelector; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| export interface AgentSelectorProps { | ||
| selectedAgentId: string | null; | ||
| onAgentChange: (agentId: string | null) => void; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -93,8 +93,9 @@ const MIN_DEGREE_OPTIONS = [ | |
|
|
||
| type PanelTab = 'connections' | 'details'; | ||
|
|
||
| const GraphExplorer = ({ minDegree }: { minDegree: number }) => { | ||
| const { data, isLoading } = useGetFoxmemoryGraphDataQuery(); | ||
| const GraphExplorer = ({ minDegree, agentId }: { minDegree: number; agentId?: string | null }) => { | ||
| const effectiveAgentId = agentId ?? undefined; | ||
| const { data, isLoading } = useGetFoxmemoryGraphDataQuery(effectiveAgentId); | ||
| const [searchGraph, { data: richData, isLoading: richLoading, reset: resetRich }] = useSearchFoxmemoryGraphMutation(); | ||
| const [searchMemories, { data: memoriesData, isLoading: memoriesLoading, reset: resetMemories }] = useSearchFoxmemoryMemoriesMutation(); | ||
| const [selectedNode, setSelectedNode] = useState<FoxmemoryGraphNode | null>(null); | ||
|
Comment on lines
+97
to
101
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This component now changes graph queries by Useful? React with 👍 / 👎. |
||
|
|
@@ -161,12 +162,12 @@ const GraphExplorer = ({ minDegree }: { minDegree: number }) => { | |
| const n = node as RawNode; | ||
| setSelectedNode((prev) => { | ||
| if (prev?.id === n.id) { resetRich(); resetMemories(); return null; } | ||
| searchGraph({ query: n.name }); | ||
| searchMemories({ query: n.name, top_k: 8 }); | ||
| searchGraph({ query: n.name, agentId: effectiveAgentId }); | ||
| searchMemories({ query: n.name, top_k: 8, agentId: effectiveAgentId }); | ||
| setPanelTab('connections'); | ||
| return { id: n.id, name: n.name, degree: n.degree }; | ||
| }); | ||
| }, [searchGraph, resetRich, searchMemories, resetMemories]); | ||
| }, [searchGraph, resetRich, searchMemories, resetMemories, effectiveAgentId]); | ||
|
|
||
| const nodeColor = useCallback((node: object) => { | ||
| const n = node as RawNode; | ||
|
|
@@ -373,7 +374,7 @@ const GraphExplorer = ({ minDegree }: { minDegree: number }) => { | |
|
|
||
| // ── Main component ──────────────────────────────────────────────────────────── | ||
|
|
||
| const FoxMemoryGraphView = ({ stats, diagnostics, loading }: FoxMemoryGraphViewProps) => { | ||
| const FoxMemoryGraphView = ({ stats, diagnostics, loading, agentId }: FoxMemoryGraphViewProps) => { | ||
| const [subView, setSubView] = useState<GraphSubView>('performance'); | ||
| const [minDegree, setMinDegree] = useState(1); | ||
|
|
||
|
|
@@ -461,7 +462,7 @@ const FoxMemoryGraphView = ({ stats, diagnostics, loading }: FoxMemoryGraphViewP | |
|
|
||
|
|
||
| {subView === 'explorer' ? ( | ||
| <GraphExplorer minDegree={minDegree} /> | ||
| <GraphExplorer minDegree={minDegree} agentId={agentId} /> | ||
| ) : ( | ||
| <> | ||
| <Grid container spacing={1.5} mb={2.5}> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,3 @@ | ||
| export interface ModelConfigSectionProps {} | ||
| export interface ModelConfigSectionProps { | ||
| agentId?: string | null; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
agents.length === 0, this branch replaces the selector with static text and never callsonAgentChange(null). SinceselectedAgentIdis persisted and used to scope FoxMemory requests, a previously selected (now deleted/inaccessible) agent can leave the app permanently querying with an invalidagentId, and the user has no UI path back toDefault (all)because the dropdown is hidden in this state.Useful? React with 👍 / 👎.