From ec6a0b0397f1f364ca13ffa87cf1696673431eaa Mon Sep 17 00:00:00 2001 From: ciregenz Date: Wed, 5 Aug 2026 14:20:25 -0700 Subject: [PATCH] [eric] tools: one claude connectors table (Connector/Type/Status), built-ins ride as rows, uninstalled get Connect --- frontend/src/app/pages/Tools/Tools.tsx | 112 +++++-------- .../Tools/cards/BrowserPermissionCard.tsx | 61 +++---- .../app/pages/Tools/cards/CustomToolCard.tsx | 157 +++++++++--------- .../Tools/cards/IntegrationGalleryCard.tsx | 53 +++--- .../src/app/pages/Tools/cards/ToolSection.tsx | 64 ++++--- 5 files changed, 217 insertions(+), 230 deletions(-) diff --git a/frontend/src/app/pages/Tools/Tools.tsx b/frontend/src/app/pages/Tools/Tools.tsx index 79f0ad52..031c7838 100644 --- a/frontend/src/app/pages/Tools/Tools.tsx +++ b/frontend/src/app/pages/Tools/Tools.tsx @@ -3,13 +3,9 @@ import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import Button from '@mui/material/Button'; import Chip from '@mui/material/Chip'; -import Collapse from '@mui/material/Collapse'; import Snackbar from '@mui/material/Snackbar'; import Alert from '@mui/material/Alert'; -import BuildIcon from '@mui/icons-material/Build'; import LockIcon from '@mui/icons-material/Lock'; -import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; -import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty'; import { useAppDispatch, useAppSelector } from '@/shared/hooks'; import { @@ -70,10 +66,8 @@ const Tools: React.FC = ({ onBrowseConnectors, expandToolId }) => { const [expandedBuiltin, setExpandedBuiltin] = useState(null); const [coreSectionOpen, setCoreSectionOpen] = useState(false); const [deferredSectionOpen, setDeferredSectionOpen] = useState(false); - const [customSectionOpen, setCustomSectionOpen] = useState(true); const [browserSectionOpen, setBrowserSectionOpen] = useState(false); const [browserCollapsed, setBrowserCollapsed] = useState>({ browser_delegation: true, browser_action: true }); - const [builtinSectionOpen, setBuiltinSectionOpen] = useState(true); // claude.ai's Connectors page tabs: All / Connected / Not connected. const [connFilter, setConnFilter] = useState<'all' | 'connected' | 'not-connected'>('all'); const visibleTools = useMemo(() => { @@ -105,8 +99,26 @@ const Tools: React.FC = ({ onBrowseConnectors, expandToolId }) => { return ( - {/* The pane header already says "Tools"; a slim action row beats a second page title. */} - + {/* claude.ai's Connectors page grammar: filter pills left, Add right, one table below. */} + + + {([['all', 'All'], ['connected', 'Connected'], ['not-connected', 'Not connected']] as const).map(([value, label]) => ( + setConnFilter(value)} + sx={{ + px: 1.5, py: 0.4, borderRadius: 999, cursor: 'pointer', userSelect: 'none', + fontSize: '0.8125rem', fontWeight: 600, lineHeight: 1.6, + color: connFilter === value ? c.text.primary : c.text.tertiary, + bgcolor: connFilter === value ? c.bg.secondary : 'transparent', + '&:hover': { color: c.text.primary }, + }} + > + {label} + + ))} + = ({ onBrowseConnectors, expandToolId }) => { /> - - setBuiltinSectionOpen((v) => !v)} - sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mb: 1, px: 0.5, cursor: 'pointer', userSelect: 'none', '&:hover .section-arrow': { color: c.text.secondary } }} - > - - Built-in - - {coreTools.length + deferredTools.length + browserTools.length} - {builtinSectionOpen ? : } + {loading ? ( + + {[0, 1, 2, 3].map((i) => ( + + ))} - - - + ) : ( + + + Connector + Type + Status + + {connFilter === 'all' && ( + <> {coreTools.length > 0 && ( } count={coreTools.length} open={coreSectionOpen} onToggle={() => setCoreSectionOpen((v) => !v)} grouped={groupedCore} collapsedCategories={collapsedCategories} toggleCategory={toggleCategory} expandedBuiltin={expandedBuiltin} toggleBuiltinExpand={toggleBuiltinExpand} builtinPermissions={builtinPermissions} onPermissionChange={a.handleBuiltinPermissionChange} onCategoryPermissionChange={a.handleBuiltinCategoryPermissionChange} enabled={coreSectionEnabled} onEnabledChange={(v) => a.handleSectionEnabledChange(coreTools, v)} /> )} @@ -154,52 +167,8 @@ const Tools: React.FC = ({ onBrowseConnectors, expandToolId }) => { onPermissionChange={a.handleBuiltinPermissionChange} /> )} - - - - - - - - setCustomSectionOpen((v) => !v)} sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mb: 1, px: 0.5, cursor: 'pointer', userSelect: 'none', '&:hover .section-arrow': { color: c.text.secondary } }}> - - Connections - - {tools.length + uninstalledIntegrations.length} - {customSectionOpen ? : } - e.stopPropagation()}> - {([['all', 'All'], ['connected', 'Connected'], ['not-connected', 'Not connected']] as const).map(([value, label]) => ( - setConnFilter(value)} - sx={{ - px: 1.25, py: 0.3, borderRadius: 999, cursor: 'pointer', userSelect: 'none', - fontSize: '0.75rem', fontWeight: 600, lineHeight: 1.6, - color: connFilter === value ? c.text.primary : c.text.tertiary, - bgcolor: connFilter === value ? c.bg.secondary : 'transparent', - '&:hover': { color: c.text.primary }, - }} - > - {label} - - ))} - - - - {loading ? ( - - {[0, 1, 2, 3].map((i) => ( - - ))} - - ) : (tools.length === 0 && uninstalledIntegrations.length === 0) ? ( - - - No custom tools defined yet. Create one to get started. - - ) : ( - + + )} {visibleTools.map((tool) => ( = ({ onBrowseConnectors, expandToolId }) => { onToggle={a.handleIntegrationToggle} /> ))} + {connFilter !== 'all' && visibleTools.length === 0 && visibleGallery.length === 0 && ( + + Nothing {connFilter === 'connected' ? 'connected' : 'disconnected'} yet. )} - + + )} + + + = ({ }) => { const c = useClaudeTokens(); return ( - - + + browserSectionEnabled && onToggleOpen()} - sx={{ display: 'flex', alignItems: 'center', gap: 2, cursor: browserSectionEnabled ? 'pointer' : 'default' }} + sx={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) 130px 150px', alignItems: 'center', gap: 2, cursor: browserSectionEnabled ? 'pointer' : 'default' }} > - - - - - - Browser - + + + + + + Browser + Browser automation delegation and individual browser tools + {browserTools.length} tools - Browser automation delegation and individual browser tools - e.stopPropagation()}> - handleSectionEnabledChange(browserTools, checked)} - sx={{ - '& .MuiSwitch-switchBase.Mui-checked': { color: c.accent.primary }, - '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: c.accent.primary }, - }} - /> - - {browserSectionEnabled && ( - + Built-in + + e.stopPropagation()} sx={{ display: 'flex', alignItems: 'center' }}> + handleSectionEnabledChange(browserTools, checked)} + sx={{ + '& .MuiSwitch-switchBase.Mui-checked': { color: c.accent.primary }, + '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: c.accent.primary }, + }} + /> + + {browserSectionEnabled && ( - - )} + )} + diff --git a/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx b/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx index fff4acf2..68a53d2b 100644 --- a/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx +++ b/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx @@ -104,92 +104,99 @@ const CustomToolCard: React.FC = ({ > !isDisabled && onToggleExpand(tool.id, isExpanded)} > - {ig && ( - - {ig.icon} - - )} - - - {tool.name} - {tool.command && } label={`/${tool.command}`} size="small" sx={{ bgcolor: 'rgba(174,86,48,0.12)', color: c.accent.hover, fontSize: '0.6875rem', height: 20 }} />} - {tool.auth_status === 'connected' && ( - - Connected{!ig && tool.connected_account_email ? ` · ${tool.connected_account_email}` : ''} - - )} - {tool.auth_status === 'configured' && !ig?.credentialFields && ( - - Configured - - )} - - {tool.description && ( - {tool.description} + + {ig && ( + + {ig.icon} + )} - - {[isMcp ? (isStdio ? 'MCP · stdio' : 'MCP') : null, totalToolCount > 0 ? `${totalToolCount} tools` : null].filter(Boolean).join(' · ')} + + + {tool.name} + {tool.command && } label={`/${tool.command}`} size="small" sx={{ bgcolor: 'rgba(174,86,48,0.12)', color: c.accent.hover, fontSize: '0.6875rem', height: 20 }} />} + + {tool.description && ( + {tool.description} + )} + + {totalToolCount > 0 ? `${totalToolCount} tools` : ''} + {ig && ( + e.stopPropagation()} sx={{ color: c.text.ghost, ml: totalToolCount > 0 ? 1 : 0, textDecoration: 'none', '&:hover': { color: c.text.secondary, textDecoration: 'underline' } }}> + docs + + )} + + + + {isMcp ? (isStdio ? 'MCP · stdio' : 'MCP') : 'Custom'} + + {tool.auth_status === 'connected' && ( + + Connected + + )} + {tool.auth_status === 'configured' && !ig?.credentialFields && ( + + Configured + + )} + + {ig && ( - e.stopPropagation()} sx={{ color: c.text.ghost, ml: 1, textDecoration: 'none', '&:hover': { color: c.text.secondary, textDecoration: 'underline' } }}> - docs + e.stopPropagation()} + > + {!!integrationLoading[ig.id] && } + handleIntegrationToggle(ig)} + disabled={!!integrationLoading[ig.id]} + sx={{ + '& .MuiSwitch-switchBase.Mui-checked': { color: ig.color }, + '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: ig.color }, + }} + /> )} - - - - {ig && ( - e.stopPropagation()} - > - {!!integrationLoading[ig.id] && } - handleIntegrationToggle(ig)} - disabled={!!integrationLoading[ig.id]} - sx={{ - '& .MuiSwitch-switchBase.Mui-checked': { color: ig.color }, - '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: ig.color }, - }} - /> - - )} - {!isDisabled && ( - - - {!ig && ( + {!isDisabled && ( <> - { e.stopPropagation(); openEdit(tool); }} sx={{ color: c.text.ghost, '&:hover': { color: c.accent.primary } }}> - { e.stopPropagation(); handleDelete(tool.id); }} sx={{ color: c.text.ghost, '&:hover': { color: c.status.error } }}> + + {!ig && ( + <> + { e.stopPropagation(); openEdit(tool); }} sx={{ color: c.text.ghost, '&:hover': { color: c.accent.primary } }}> + { e.stopPropagation(); handleDelete(tool.id); }} sx={{ color: c.text.ghost, '&:hover': { color: c.status.error } }}> + + )} )} - )} + diff --git a/frontend/src/app/pages/Tools/cards/IntegrationGalleryCard.tsx b/frontend/src/app/pages/Tools/cards/IntegrationGalleryCard.tsx index 9fb65293..5a89cb47 100644 --- a/frontend/src/app/pages/Tools/cards/IntegrationGalleryCard.tsx +++ b/frontend/src/app/pages/Tools/cards/IntegrationGalleryCard.tsx @@ -4,7 +4,7 @@ import Typography from '@mui/material/Typography'; import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; import CircularProgress from '@mui/material/CircularProgress'; -import Switch from '@mui/material/Switch'; +import Button from '@mui/material/Button'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; import { Integration } from '../integrations'; @@ -23,23 +23,25 @@ const IntegrationGalleryCard: React.FC = ({ integra sx={{ bgcolor: 'transparent', border: 'none', borderRadius: 0, boxShadow: 'none', borderBottom: `1px solid ${c.border.subtle}`, '&:last-of-type': { borderBottom: 'none' }, '&:hover': { bgcolor: c.bg.elevated }, transition: 'background-color 0.12s' }} > - - - {ig.icon} - - - {ig.name} - {ig.description} - - Not installed - - docs - - + + + + {ig.icon} + + + {ig.name} + {ig.description} + + + docs + + + + MCP = ({ integra ? 'actions-reddit-toggle' : undefined } - sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexShrink: 0 }} + sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 0.75 }} > {isLoading && } - handleIntegrationToggle(ig)} + variant="outlined" disabled={isLoading} + onClick={() => handleIntegrationToggle(ig)} sx={{ - '& .MuiSwitch-switchBase.Mui-checked': { color: ig.color }, - '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: ig.color }, + textTransform: 'none', fontSize: '0.8125rem', fontWeight: 600, px: 1.75, py: 0.4, + color: c.text.primary, borderColor: c.border.medium, borderRadius: 999, + '&:hover': { borderColor: c.border.strong, bgcolor: c.bg.elevated }, }} - /> + > + Connect + diff --git a/frontend/src/app/pages/Tools/cards/ToolSection.tsx b/frontend/src/app/pages/Tools/cards/ToolSection.tsx index 72257235..ba9f434f 100644 --- a/frontend/src/app/pages/Tools/cards/ToolSection.tsx +++ b/frontend/src/app/pages/Tools/cards/ToolSection.tsx @@ -94,45 +94,43 @@ const ToolSection: React.FC = ({ }; return ( - - + + enabled && onToggle()} - sx={{ display: 'flex', alignItems: 'center', gap: 2, cursor: enabled ? 'pointer' : 'default' }} + sx={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) 130px 150px', alignItems: 'center', gap: 2, cursor: enabled ? 'pointer' : 'default' }} > - - {icon} - - - - {label} - - {deferred && ( - - )} + + + {icon} + + + {label} + {sectionDescription} + {count} tools{deferred ? ' · on-demand' : ''} - {sectionDescription} - e.stopPropagation()}> - onEnabledChange(checked)} - sx={{ - '& .MuiSwitch-switchBase.Mui-checked': { color: c.accent.primary }, - '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: c.accent.primary }, - }} - /> - - {enabled && ( - + Built-in + + e.stopPropagation()} sx={{ display: 'flex', alignItems: 'center' }}> + onEnabledChange(checked)} + sx={{ + '& .MuiSwitch-switchBase.Mui-checked': { color: c.accent.primary }, + '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: c.accent.primary }, + }} + /> + + {enabled && ( - - )} + )} +