[eric] tools: one claude connectors table (Connector/Type/Status), built-ins ride as rows, uninstalled get Connect

This commit is contained in:
ciregenz
2026-08-05 14:20:25 -07:00
parent 746966cc68
commit ec6a0b0397
5 changed files with 217 additions and 230 deletions
+44 -68
View File
@@ -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<ToolsProps> = ({ onBrowseConnectors, expandToolId }) => {
const [expandedBuiltin, setExpandedBuiltin] = useState<string | null>(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<Record<string, boolean>>({ 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<ToolsProps> = ({ onBrowseConnectors, expandToolId }) => {
return (
<Box sx={{ px: 3, pt: 1, pb: 3, height: '100%', overflow: 'auto' }}>
{/* The pane header already says "Tools"; a slim action row beats a second page title. */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', mb: 1.5 }}>
{/* claude.ai's Connectors page grammar: filter pills left, Add right, one table below. */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1.5 }}>
<Box sx={{ display: 'flex', gap: 0.5 }}>
{([['all', 'All'], ['connected', 'Connected'], ['not-connected', 'Not connected']] as const).map(([value, label]) => (
<Box
key={value}
role="button"
onClick={() => 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}
</Box>
))}
</Box>
<ToolsAddMenu
devMode={!!devMode}
onBrowseConnectors={onBrowseConnectors}
@@ -116,20 +128,21 @@ const Tools: React.FC<ToolsProps> = ({ onBrowseConnectors, expandToolId }) => {
/>
</Box>
<Box sx={{ mb: 3 }}>
<Box
onClick={() => 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 } }}
>
<Typography sx={{ color: c.text.muted, fontWeight: 700, fontSize: '0.6875rem', textTransform: 'uppercase', letterSpacing: '0.07em' }}>
Built-in
</Typography>
<Typography sx={{ color: c.text.ghost, fontSize: '0.6875rem', fontWeight: 600 }}>{coreTools.length + deferredTools.length + browserTools.length}</Typography>
{builtinSectionOpen ? <KeyboardArrowDownIcon className="section-arrow" sx={{ fontSize: 15, color: c.text.ghost, transition: 'color 0.15s' }} /> : <KeyboardArrowRightIcon className="section-arrow" sx={{ fontSize: 15, color: c.text.ghost, transition: 'color 0.15s' }} />}
{loading ? (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, mt: 1 }}>
{[0, 1, 2, 3].map((i) => (
<Skeleton key={i} variant="card" height={72} />
))}
</Box>
<Collapse in={builtinSectionOpen} timeout={0} unmountOnExit>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, pl: 1 }}>
) : (
<Box sx={{ border: `1px solid ${c.border.subtle}`, borderRadius: '12px', overflow: 'hidden', bgcolor: c.bg.surface }}>
<Box sx={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) 130px 150px', gap: 2, px: 2, py: 1.1, borderBottom: `1px solid ${c.border.subtle}` }}>
<Typography sx={{ color: c.text.tertiary, fontSize: '0.8125rem' }}>Connector</Typography>
<Typography sx={{ color: c.text.tertiary, fontSize: '0.8125rem' }}>Type</Typography>
<Typography sx={{ color: c.text.tertiary, fontSize: '0.8125rem', textAlign: 'right' }}>Status</Typography>
</Box>
{connFilter === 'all' && (
<>
{coreTools.length > 0 && (
<ToolSection label="Core Tools" icon={<LockIcon sx={{ fontSize: 14, color: c.text.tertiary }} />} 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<ToolsProps> = ({ onBrowseConnectors, expandToolId }) => {
onPermissionChange={a.handleBuiltinPermissionChange}
/>
)}
</Box>
</Collapse>
</Box>
<AgentWorkflowsSection />
<Box sx={{ mb: 2 }}>
<Box onClick={() => 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 } }}>
<Typography sx={{ color: c.text.muted, fontWeight: 700, fontSize: '0.6875rem', textTransform: 'uppercase', letterSpacing: '0.07em' }}>
Connections
</Typography>
<Typography sx={{ color: c.text.ghost, fontSize: '0.6875rem', fontWeight: 600 }}>{tools.length + uninstalledIntegrations.length}</Typography>
{customSectionOpen ? <KeyboardArrowDownIcon className="section-arrow" sx={{ fontSize: 15, color: c.text.ghost, transition: 'color 0.15s' }} /> : <KeyboardArrowRightIcon className="section-arrow" sx={{ fontSize: 15, color: c.text.ghost, transition: 'color 0.15s' }} />}
<Box sx={{ display: 'flex', gap: 0.5, ml: 1 }} onClick={(e: React.MouseEvent) => e.stopPropagation()}>
{([['all', 'All'], ['connected', 'Connected'], ['not-connected', 'Not connected']] as const).map(([value, label]) => (
<Box
key={value}
role="button"
onClick={() => 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}
</Box>
))}
</Box>
</Box>
<Collapse in={customSectionOpen} timeout={0} unmountOnExit>
{loading ? (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, pl: 1, mt: 1 }}>
{[0, 1, 2, 3].map((i) => (
<Skeleton key={i} variant="card" height={72} />
))}
</Box>
) : (tools.length === 0 && uninstalledIntegrations.length === 0) ? (
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', py: 6, color: c.text.ghost, gap: 1.5 }}>
<BuildIcon sx={{ fontSize: 40, opacity: 0.3 }} />
<Typography sx={{ fontSize: '0.875rem' }}>No custom tools defined yet. Create one to get started.</Typography>
</Box>
) : (
<Box sx={{ display: 'flex', flexDirection: 'column', ml: 1, border: `1px solid ${c.border.subtle}`, borderRadius: '12px', overflow: 'hidden', bgcolor: c.bg.surface }}>
</>
)}
{visibleTools.map((tool) => (
<CustomToolCard
key={tool.id}
@@ -237,9 +206,16 @@ const Tools: React.FC<ToolsProps> = ({ onBrowseConnectors, expandToolId }) => {
onToggle={a.handleIntegrationToggle}
/>
))}
{connFilter !== 'all' && visibleTools.length === 0 && visibleGallery.length === 0 && (
<Box sx={{ px: 2, py: 4, textAlign: 'center' }}>
<Typography sx={{ fontSize: '0.875rem', color: c.text.ghost }}>Nothing {connFilter === 'connected' ? 'connected' : 'disconnected'} yet.</Typography>
</Box>
)}
</Collapse>
</Box>
)}
<Box sx={{ mt: 3 }}>
<AgentWorkflowsSection />
</Box>
<ToolDialogs
@@ -48,42 +48,43 @@ const BrowserPermissionCard: React.FC<BrowserPermissionCardProps> = ({
}) => {
const c = useClaudeTokens();
return (
<Card sx={{ bgcolor: c.bg.surface, border: `1px solid ${browserSectionOpen && browserSectionEnabled ? c.accent.primary : c.border.subtle}`, borderRadius: 2, boxShadow: c.shadow.sm, '&:hover': { borderColor: c.accent.primary, boxShadow: '0 0 0 1px rgba(174,86,48,0.12)' }, transition: 'border-color 0.2s, box-shadow 0.2s' }}>
<CardContent sx={{ py: 1.5, px: 2, '&:last-child': { pb: 1.5 } }}>
<Card sx={{ bgcolor: browserSectionOpen && browserSectionEnabled ? c.bg.elevated : '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' }}>
<CardContent sx={{ py: 1.4, px: 2, '&:last-child': { pb: 1.4 } }}>
<Box
onClick={() => 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' }}
>
<Box sx={{
width: 36, height: 36, borderRadius: 2, display: 'flex', alignItems: 'center', justifyContent: 'center',
bgcolor: c.bg.secondary, color: c.text.tertiary, flexShrink: 0,
opacity: browserSectionEnabled ? 1 : 0.4, transition: 'opacity 0.2s',
}}>
<PublicIcon sx={{ fontSize: 18 }} />
</Box>
<Box sx={{ flex: 1, minWidth: 0, opacity: browserSectionEnabled ? 1 : 0.4, transition: 'opacity 0.2s' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.25 }}>
<Typography sx={{ color: c.text.primary, fontWeight: 600, fontSize: '1rem' }}>Browser</Typography>
<Chip label={`${browserTools.length} tools`} size="small" sx={{ bgcolor: c.bg.secondary, color: c.text.muted, fontSize: '0.6875rem', height: 20, '& .MuiChip-label': { px: 0.6 } }} />
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, minWidth: 0 }}>
<Box sx={{
width: 34, height: 34, borderRadius: '9px', display: 'flex', alignItems: 'center', justifyContent: 'center',
border: `1px solid ${c.border.subtle}`, bgcolor: c.bg.surface, color: c.text.tertiary, flexShrink: 0,
opacity: browserSectionEnabled ? 1 : 0.55, transition: 'opacity 0.2s',
}}>
<PublicIcon sx={{ fontSize: 18 }} />
</Box>
<Box sx={{ flex: 1, minWidth: 0 }}>
<Typography sx={{ color: browserSectionEnabled ? c.text.primary : c.text.tertiary, fontWeight: 600, fontSize: '0.9375rem', mb: 0.25 }}>Browser</Typography>
<Typography noWrap sx={{ color: c.text.muted, fontSize: '0.8125rem' }}>Browser automation delegation and individual browser tools</Typography>
<Typography sx={{ color: c.text.ghost, fontSize: '0.75rem', mt: 0.25 }}>{browserTools.length} tools</Typography>
</Box>
<Typography sx={{ color: c.text.muted, fontSize: '0.8125rem' }}>Browser automation delegation and individual browser tools</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexShrink: 0 }} onClick={(e) => e.stopPropagation()}>
<Switch
size="small"
checked={browserSectionEnabled}
onChange={(_, checked) => handleSectionEnabledChange(browserTools, checked)}
sx={{
'& .MuiSwitch-switchBase.Mui-checked': { color: c.accent.primary },
'& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: c.accent.primary },
}}
/>
</Box>
{browserSectionEnabled && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.25, flexShrink: 0 }}>
<Typography sx={{ color: c.text.secondary, fontSize: '0.8125rem' }}>Built-in</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 0.5 }}>
<Box onClick={(e) => e.stopPropagation()} sx={{ display: 'flex', alignItems: 'center' }}>
<Switch
size="small"
checked={browserSectionEnabled}
onChange={(_, checked) => handleSectionEnabledChange(browserTools, checked)}
sx={{
'& .MuiSwitch-switchBase.Mui-checked': { color: c.accent.primary },
'& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: c.accent.primary },
}}
/>
</Box>
{browserSectionEnabled && (
<KeyboardArrowDownIcon sx={{ fontSize: 18, color: c.text.ghost, transition: 'transform 0.2s', transform: browserSectionOpen ? 'rotate(180deg)' : 'rotate(0deg)' }} />
</Box>
)}
)}
</Box>
</Box>
</CardContent>
<Collapse in={browserSectionOpen && browserSectionEnabled} timeout={0} unmountOnExit>
@@ -104,92 +104,99 @@ const CustomToolCard: React.FC<CustomToolCardProps> = ({
>
<CardContent sx={{ py: 1.4, px: 2, '&:last-child': { pb: 1.4 } }}>
<Box
sx={{ display: 'flex', alignItems: 'center', gap: 2, cursor: isDisabled ? 'default' : 'pointer' }}
sx={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) 130px 150px', alignItems: 'center', gap: 2, cursor: isDisabled ? 'default' : 'pointer' }}
data-onboarding={isYoutube ? 'actions-youtube-chevron' : isReddit ? 'actions-reddit-chevron' : undefined}
onClick={() => !isDisabled && onToggleExpand(tool.id, isExpanded)}
>
{ig && (
<Box sx={{
width: 34, height: 34, borderRadius: '9px', display: 'flex', alignItems: 'center', justifyContent: 'center',
border: `1px solid ${c.border.subtle}`, bgcolor: c.bg.surface, fontSize: '1.125rem', fontWeight: 700, color: ig.color, flexShrink: 0,
opacity: isDisabled ? 0.55 : 1, transition: 'opacity 0.2s',
}}>
{ig.icon}
</Box>
)}
<Box sx={{ flex: 1, minWidth: 0 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.25 }}>
<Typography sx={{ color: isDisabled ? c.text.tertiary : c.text.primary, fontWeight: 600, fontSize: '0.9375rem' }}>{tool.name}</Typography>
{tool.command && <Chip icon={<TerminalIcon sx={{ fontSize: 12 }} />} 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' && (
<Typography sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.4, color: c.status.success, fontSize: '0.75rem', fontWeight: 600 }}>
<CheckCircleIcon sx={{ fontSize: 13 }} /> Connected{!ig && tool.connected_account_email ? ` · ${tool.connected_account_email}` : ''}
</Typography>
)}
{tool.auth_status === 'configured' && !ig?.credentialFields && (
<Typography sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.4, color: c.status.warning, fontSize: '0.75rem', fontWeight: 600 }}>
<SettingsIcon sx={{ fontSize: 13 }} /> Configured
</Typography>
)}
</Box>
{tool.description && (
<Typography noWrap sx={{ color: c.text.muted, fontSize: '0.8125rem' }}>{tool.description}</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, minWidth: 0 }}>
{ig && (
<Box sx={{
width: 34, height: 34, borderRadius: '9px', display: 'flex', alignItems: 'center', justifyContent: 'center',
border: `1px solid ${c.border.subtle}`, bgcolor: c.bg.surface, fontSize: '1.125rem', fontWeight: 700, color: ig.color, flexShrink: 0,
opacity: isDisabled ? 0.55 : 1, transition: 'opacity 0.2s',
}}>
{ig.icon}
</Box>
)}
<Typography sx={{ color: c.text.ghost, fontSize: '0.75rem', mt: 0.25 }}>
{[isMcp ? (isStdio ? 'MCP · stdio' : 'MCP') : null, totalToolCount > 0 ? `${totalToolCount} tools` : null].filter(Boolean).join(' · ')}
<Box sx={{ flex: 1, minWidth: 0 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.25 }}>
<Typography sx={{ color: isDisabled ? c.text.tertiary : c.text.primary, fontWeight: 600, fontSize: '0.9375rem' }}>{tool.name}</Typography>
{tool.command && <Chip icon={<TerminalIcon sx={{ fontSize: 12 }} />} label={`/${tool.command}`} size="small" sx={{ bgcolor: 'rgba(174,86,48,0.12)', color: c.accent.hover, fontSize: '0.6875rem', height: 20 }} />}
</Box>
{tool.description && (
<Typography noWrap sx={{ color: c.text.muted, fontSize: '0.8125rem' }}>{tool.description}</Typography>
)}
<Typography sx={{ color: c.text.ghost, fontSize: '0.75rem', mt: 0.25 }}>
{totalToolCount > 0 ? `${totalToolCount} tools` : ''}
{ig && (
<Box component="a" href={ig.website} target="_blank" rel="noreferrer" onClick={(e: React.MouseEvent) => e.stopPropagation()} sx={{ color: c.text.ghost, ml: totalToolCount > 0 ? 1 : 0, textDecoration: 'none', '&:hover': { color: c.text.secondary, textDecoration: 'underline' } }}>
docs<OpenInNewIcon sx={{ fontSize: 10, ml: 0.25, verticalAlign: 'middle' }} />
</Box>
)}
</Typography>
</Box>
</Box>
<Typography sx={{ color: c.text.secondary, fontSize: '0.8125rem' }}>{isMcp ? (isStdio ? 'MCP · stdio' : 'MCP') : 'Custom'}</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 0.25 }}>
{tool.auth_status === 'connected' && (
<Typography sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.4, color: c.status.success, fontSize: '0.75rem', fontWeight: 600 }}>
<CheckCircleIcon sx={{ fontSize: 13 }} /> Connected
</Typography>
)}
{tool.auth_status === 'configured' && !ig?.credentialFields && (
<Typography sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.4, color: c.status.warning, fontSize: '0.75rem', fontWeight: 600 }}>
<SettingsIcon sx={{ fontSize: 13 }} /> Configured
</Typography>
)}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.25 }}>
<CustomToolConnect
tool={tool}
ig={ig}
isDisabled={isDisabled}
onOAuthConnect={handleOAuthConnect}
onDeviceCodeConnect={handleDeviceCodeConnect}
onOpenCredentialsDialog={openCredentialsDialog}
onM365Disconnect={handleM365Disconnect}
onDisconnectIntegration={handleDisconnectIntegration}
/>
{ig && (
<Box component="a" href={ig.website} target="_blank" rel="noreferrer" onClick={(e: React.MouseEvent) => e.stopPropagation()} sx={{ color: c.text.ghost, ml: 1, textDecoration: 'none', '&:hover': { color: c.text.secondary, textDecoration: 'underline' } }}>
docs<OpenInNewIcon sx={{ fontSize: 10, ml: 0.25, verticalAlign: 'middle' }} />
<Box
data-onboarding={
isYoutube
? 'actions-youtube-toggle'
: isReddit
? 'actions-reddit-toggle'
: undefined
}
sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexShrink: 0 }}
onClick={(e) => e.stopPropagation()}
>
{!!integrationLoading[ig.id] && <CircularProgress size={16} sx={{ color: ig.color }} />}
<Switch
size="small"
checked={tool.enabled !== false}
onChange={() => handleIntegrationToggle(ig)}
disabled={!!integrationLoading[ig.id]}
sx={{
'& .MuiSwitch-switchBase.Mui-checked': { color: ig.color },
'& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: ig.color },
}}
/>
</Box>
)}
</Typography>
</Box>
<CustomToolConnect
tool={tool}
ig={ig}
isDisabled={isDisabled}
onOAuthConnect={handleOAuthConnect}
onDeviceCodeConnect={handleDeviceCodeConnect}
onOpenCredentialsDialog={openCredentialsDialog}
onM365Disconnect={handleM365Disconnect}
onDisconnectIntegration={handleDisconnectIntegration}
/>
{ig && (
<Box
data-onboarding={
isYoutube
? 'actions-youtube-toggle'
: isReddit
? 'actions-reddit-toggle'
: undefined
}
sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexShrink: 0 }}
onClick={(e) => e.stopPropagation()}
>
{!!integrationLoading[ig.id] && <CircularProgress size={16} sx={{ color: ig.color }} />}
<Switch
size="small"
checked={tool.enabled !== false}
onChange={() => handleIntegrationToggle(ig)}
disabled={!!integrationLoading[ig.id]}
sx={{
'& .MuiSwitch-switchBase.Mui-checked': { color: ig.color },
'& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: ig.color },
}}
/>
</Box>
)}
{!isDisabled && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.25, flexShrink: 0 }}>
<KeyboardArrowDownIcon sx={{ fontSize: 18, color: c.text.ghost, transition: 'transform 0.2s', transform: isExpanded ? 'rotate(180deg)' : 'rotate(0deg)' }} />
{!ig && (
{!isDisabled && (
<>
<Tooltip title="Edit" placement="left"><IconButton size="small" onClick={(e) => { e.stopPropagation(); openEdit(tool); }} sx={{ color: c.text.ghost, '&:hover': { color: c.accent.primary } }}><EditIcon sx={{ fontSize: 16 }} /></IconButton></Tooltip>
<Tooltip title="Delete" placement="left"><IconButton size="small" onClick={(e) => { e.stopPropagation(); handleDelete(tool.id); }} sx={{ color: c.text.ghost, '&:hover': { color: c.status.error } }}><DeleteIcon sx={{ fontSize: 16 }} /></IconButton></Tooltip>
<KeyboardArrowDownIcon sx={{ fontSize: 18, color: c.text.ghost, transition: 'transform 0.2s', transform: isExpanded ? 'rotate(180deg)' : 'rotate(0deg)' }} />
{!ig && (
<>
<Tooltip title="Edit" placement="left"><IconButton size="small" onClick={(e) => { e.stopPropagation(); openEdit(tool); }} sx={{ color: c.text.ghost, '&:hover': { color: c.accent.primary } }}><EditIcon sx={{ fontSize: 16 }} /></IconButton></Tooltip>
<Tooltip title="Delete" placement="left"><IconButton size="small" onClick={(e) => { e.stopPropagation(); handleDelete(tool.id); }} sx={{ color: c.text.ghost, '&:hover': { color: c.status.error } }}><DeleteIcon sx={{ fontSize: 16 }} /></IconButton></Tooltip>
</>
)}
</>
)}
</Box>
)}
</Box>
</Box>
</CardContent>
@@ -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<IntegrationGalleryCardProps> = ({ 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' }}
>
<CardContent sx={{ py: 1.4, px: 2, '&:last-child': { pb: 1.4 } }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Box sx={{
width: 34, height: 34, borderRadius: '9px', display: 'flex', alignItems: 'center', justifyContent: 'center',
border: `1px solid ${c.border.subtle}`, bgcolor: c.bg.surface, fontSize: '1.125rem', fontWeight: 700, color: c.text.ghost,
}}>
{ig.icon}
</Box>
<Box sx={{ flex: 1, minWidth: 0 }}>
<Typography sx={{ color: c.text.primary, fontWeight: 600, fontSize: '0.9375rem', mb: 0.25 }}>{ig.name}</Typography>
<Typography noWrap sx={{ color: c.text.muted, fontSize: '0.8125rem' }}>{ig.description}</Typography>
<Typography sx={{ color: c.text.ghost, fontSize: '0.75rem', mt: 0.25 }}>
Not installed
<Box component="a" href={ig.website} target="_blank" rel="noreferrer" sx={{ color: c.text.ghost, ml: 1, textDecoration: 'none', '&:hover': { color: c.text.secondary, textDecoration: 'underline' } }}>
docs<OpenInNewIcon sx={{ fontSize: 10, ml: 0.25, verticalAlign: 'middle' }} />
</Box>
</Typography>
<Box sx={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) 130px 150px', alignItems: 'center', gap: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, minWidth: 0 }}>
<Box sx={{
width: 34, height: 34, borderRadius: '9px', display: 'flex', alignItems: 'center', justifyContent: 'center',
border: `1px solid ${c.border.subtle}`, bgcolor: c.bg.surface, fontSize: '1.125rem', fontWeight: 700, color: c.text.ghost,
}}>
{ig.icon}
</Box>
<Box sx={{ flex: 1, minWidth: 0 }}>
<Typography sx={{ color: c.text.primary, fontWeight: 600, fontSize: '0.9375rem', mb: 0.25 }}>{ig.name}</Typography>
<Typography noWrap sx={{ color: c.text.muted, fontSize: '0.8125rem' }}>{ig.description}</Typography>
<Typography sx={{ color: c.text.ghost, fontSize: '0.75rem', mt: 0.25 }}>
<Box component="a" href={ig.website} target="_blank" rel="noreferrer" sx={{ color: c.text.ghost, textDecoration: 'none', '&:hover': { color: c.text.secondary, textDecoration: 'underline' } }}>
docs<OpenInNewIcon sx={{ fontSize: 10, ml: 0.25, verticalAlign: 'middle' }} />
</Box>
</Typography>
</Box>
</Box>
<Typography sx={{ color: c.text.secondary, fontSize: '0.8125rem' }}>MCP</Typography>
<Box
data-onboarding={
ig.id === 'youtube'
@@ -48,19 +50,22 @@ const IntegrationGalleryCard: React.FC<IntegrationGalleryCardProps> = ({ 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 && <CircularProgress size={16} sx={{ color: ig.color }} />}
<Switch
<Button
size="small"
checked={false}
onChange={() => 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
</Button>
</Box>
</Box>
</CardContent>
@@ -94,45 +94,43 @@ const ToolSection: React.FC<ToolSectionProps> = ({
};
return (
<Card sx={{ bgcolor: c.bg.surface, border: `1px solid ${open && enabled ? c.accent.primary : c.border.subtle}`, borderRadius: 2, boxShadow: c.shadow.sm, '&:hover': { borderColor: c.accent.primary, boxShadow: '0 0 0 1px rgba(174,86,48,0.12)' }, transition: 'border-color 0.2s, box-shadow 0.2s' }}>
<CardContent sx={{ py: 1.1, px: 1.75, '&:last-child': { pb: 1.1 } }}>
<Card sx={{ bgcolor: open && enabled ? c.bg.elevated : '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' }}>
<CardContent sx={{ py: 1.4, px: 2, '&:last-child': { pb: 1.4 } }}>
<Box
onClick={() => 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' }}
>
<Box sx={{
width: 30, height: 30, borderRadius: 1.5, display: 'flex', alignItems: 'center', justifyContent: 'center',
bgcolor: c.bg.secondary, color: c.text.tertiary, flexShrink: 0,
opacity: enabled ? 1 : 0.4, transition: 'opacity 0.2s',
}}>
{icon}
</Box>
<Box sx={{ flex: 1, minWidth: 0, opacity: enabled ? 1 : 0.4, transition: 'opacity 0.2s' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.25 }}>
<Typography sx={{ color: c.text.primary, fontWeight: 600, fontSize: '0.9375rem' }}>{label}</Typography>
<Chip label={`${count} tools`} size="small" sx={{ bgcolor: c.bg.secondary, color: c.text.muted, fontSize: '0.6875rem', height: 20, '& .MuiChip-label': { px: 0.6 } }} />
{deferred && (
<Chip label="on-demand" size="small" sx={{ bgcolor: c.status.warningBg, color: c.status.warning, fontSize: '0.625rem', height: 18, '& .MuiChip-label': { px: 0.6 } }} />
)}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, minWidth: 0 }}>
<Box sx={{
width: 34, height: 34, borderRadius: '9px', display: 'flex', alignItems: 'center', justifyContent: 'center',
border: `1px solid ${c.border.subtle}`, bgcolor: c.bg.surface, color: c.text.tertiary, flexShrink: 0,
opacity: enabled ? 1 : 0.55, transition: 'opacity 0.2s',
}}>
{icon}
</Box>
<Box sx={{ flex: 1, minWidth: 0 }}>
<Typography sx={{ color: enabled ? c.text.primary : c.text.tertiary, fontWeight: 600, fontSize: '0.9375rem', mb: 0.25 }}>{label}</Typography>
<Typography noWrap sx={{ color: c.text.muted, fontSize: '0.8125rem' }}>{sectionDescription}</Typography>
<Typography sx={{ color: c.text.ghost, fontSize: '0.75rem', mt: 0.25 }}>{count} tools{deferred ? ' · on-demand' : ''}</Typography>
</Box>
<Typography sx={{ color: c.text.muted, fontSize: '0.8125rem', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{sectionDescription}</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexShrink: 0 }} onClick={(e) => e.stopPropagation()}>
<Switch
size="small"
checked={enabled}
onChange={(_, checked) => onEnabledChange(checked)}
sx={{
'& .MuiSwitch-switchBase.Mui-checked': { color: c.accent.primary },
'& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: c.accent.primary },
}}
/>
</Box>
{enabled && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.25, flexShrink: 0 }}>
<Typography sx={{ color: c.text.secondary, fontSize: '0.8125rem' }}>Built-in</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 0.5 }}>
<Box onClick={(e) => e.stopPropagation()} sx={{ display: 'flex', alignItems: 'center' }}>
<Switch
size="small"
checked={enabled}
onChange={(_, checked) => onEnabledChange(checked)}
sx={{
'& .MuiSwitch-switchBase.Mui-checked': { color: c.accent.primary },
'& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: c.accent.primary },
}}
/>
</Box>
{enabled && (
<KeyboardArrowDownIcon sx={{ fontSize: 18, color: c.text.ghost, transition: 'transform 0.2s', transform: open ? 'rotate(180deg)' : 'rotate(0deg)' }} />
</Box>
)}
)}
</Box>
</Box>
</CardContent>
<Collapse in={open && enabled} timeout={0} unmountOnExit>