refactor(settings-modal): move agent settings tab content to separate components

This commit is contained in:
Haileyesus
2026-02-18 13:16:04 +03:00
parent 637cf51100
commit a61567a000
8 changed files with 327 additions and 196 deletions

View File

@@ -1,52 +1,9 @@
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import AgentListItem from './AgentListItem';
import AccountContent from './AccountContent';
import McpServersContent from './McpServersContent';
import PermissionsContent from './PermissionsContent';
import type {
AgentCategory,
AgentProvider,
AuthStatus,
ClaudePermissionsState,
CodexPermissionMode,
CursorPermissionsState,
McpServer,
McpToolsResult,
McpTestResult,
} from '../../../types/types';
type AgentsSettingsTabProps = {
claudeAuthStatus: AuthStatus;
cursorAuthStatus: AuthStatus;
codexAuthStatus: AuthStatus;
onClaudeLogin: () => void;
onCursorLogin: () => void;
onCodexLogin: () => void;
claudePermissions: ClaudePermissionsState;
onClaudePermissionsChange: (value: ClaudePermissionsState) => void;
cursorPermissions: CursorPermissionsState;
onCursorPermissionsChange: (value: CursorPermissionsState) => void;
codexPermissionMode: CodexPermissionMode;
onCodexPermissionModeChange: (value: CodexPermissionMode) => void;
mcpServers: McpServer[];
cursorMcpServers: McpServer[];
codexMcpServers: McpServer[];
mcpTestResults: Record<string, McpTestResult>;
mcpServerTools: Record<string, McpToolsResult>;
mcpToolsLoading: Record<string, boolean>;
onOpenMcpForm: (server?: McpServer) => void;
onDeleteMcpServer: (serverId: string, scope?: string) => void;
onTestMcpServer: (serverId: string, scope?: string) => void;
onDiscoverMcpTools: (serverId: string, scope?: string) => void;
onOpenCodexMcpForm: (server?: McpServer) => void;
onDeleteCodexMcpServer: (serverId: string) => void;
};
type AgentContext = {
authStatus: AuthStatus;
onLogin: () => void;
};
import type { AgentCategory, AgentProvider } from '../../../types/types';
import AgentCategoryContentSection from './sections/AgentCategoryContentSection';
import AgentCategoryTabsSection from './sections/AgentCategoryTabsSection';
import AgentSelectorSection from './sections/AgentSelectorSection';
import type { AgentContext, AgentsSettingsTabProps } from './types';
export default function AgentsSettingsTab({
claudeAuthStatus,
@@ -74,11 +31,8 @@ export default function AgentsSettingsTab({
onOpenCodexMcpForm,
onDeleteCodexMcpServer,
}: AgentsSettingsTabProps) {
const { t } = useTranslation('settings');
const [selectedAgent, setSelectedAgent] = useState<AgentProvider>('claude');
const [selectedCategory, setSelectedCategory] = useState<AgentCategory>('account');
// Cursor MCP add/edit/delete was previously a placeholder and is intentionally preserved.
const noopCursorMcpAction = () => {};
const agentContextById = useMemo<Record<AgentProvider, AgentContext>>(() => ({
claude: {
@@ -104,144 +58,41 @@ export default function AgentsSettingsTab({
return (
<div className="flex flex-col md:flex-row h-full min-h-[400px] md:min-h-[500px]">
<div className="md:hidden border-b border-gray-200 dark:border-gray-700 flex-shrink-0">
<div className="flex">
{(['claude', 'cursor', 'codex'] as AgentProvider[]).map((agent) => (
<AgentListItem
key={`mobile-${agent}`}
agentId={agent}
authStatus={agentContextById[agent].authStatus}
isSelected={selectedAgent === agent}
onClick={() => setSelectedAgent(agent)}
isMobile
/>
))}
</div>
</div>
<div className="hidden md:block w-48 border-r border-gray-200 dark:border-gray-700 flex-shrink-0">
<div className="p-2">
{(['claude', 'cursor', 'codex'] as AgentProvider[]).map((agent) => (
<AgentListItem
key={`desktop-${agent}`}
agentId={agent}
authStatus={agentContextById[agent].authStatus}
isSelected={selectedAgent === agent}
onClick={() => setSelectedAgent(agent)}
/>
))}
</div>
</div>
<AgentSelectorSection
selectedAgent={selectedAgent}
onSelectAgent={setSelectedAgent}
agentContextById={agentContextById}
/>
<div className="flex-1 flex flex-col overflow-hidden">
<div className="border-b border-gray-200 dark:border-gray-700 flex-shrink-0">
<div className="flex px-2 md:px-4 overflow-x-auto">
{(['account', 'permissions', 'mcp'] as AgentCategory[]).map((category) => (
<button
key={category}
onClick={() => setSelectedCategory(category)}
className={`px-3 md:px-4 py-2 md:py-3 text-xs md:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
selectedCategory === category
? 'border-blue-600 text-blue-600 dark:text-blue-400'
: 'border-transparent text-muted-foreground hover:text-foreground'
}`}
>
{category === 'account' && t('tabs.account')}
{category === 'permissions' && t('tabs.permissions')}
{category === 'mcp' && t('tabs.mcpServers')}
</button>
))}
</div>
</div>
<AgentCategoryTabsSection
selectedCategory={selectedCategory}
onSelectCategory={setSelectedCategory}
/>
<div className="flex-1 overflow-y-auto p-3 md:p-4">
{selectedCategory === 'account' && (
<AccountContent
agent={selectedAgent}
authStatus={agentContextById[selectedAgent].authStatus}
onLogin={agentContextById[selectedAgent].onLogin}
/>
)}
{selectedCategory === 'permissions' && selectedAgent === 'claude' && (
<PermissionsContent
agent="claude"
skipPermissions={claudePermissions.skipPermissions}
onSkipPermissionsChange={(value) => {
onClaudePermissionsChange({ ...claudePermissions, skipPermissions: value });
}}
allowedTools={claudePermissions.allowedTools}
onAllowedToolsChange={(value) => {
onClaudePermissionsChange({ ...claudePermissions, allowedTools: value });
}}
disallowedTools={claudePermissions.disallowedTools}
onDisallowedToolsChange={(value) => {
onClaudePermissionsChange({ ...claudePermissions, disallowedTools: value });
}}
/>
)}
{selectedCategory === 'permissions' && selectedAgent === 'cursor' && (
<PermissionsContent
agent="cursor"
skipPermissions={cursorPermissions.skipPermissions}
onSkipPermissionsChange={(value) => {
onCursorPermissionsChange({ ...cursorPermissions, skipPermissions: value });
}}
allowedCommands={cursorPermissions.allowedCommands}
onAllowedCommandsChange={(value) => {
onCursorPermissionsChange({ ...cursorPermissions, allowedCommands: value });
}}
disallowedCommands={cursorPermissions.disallowedCommands}
onDisallowedCommandsChange={(value) => {
onCursorPermissionsChange({ ...cursorPermissions, disallowedCommands: value });
}}
/>
)}
{selectedCategory === 'permissions' && selectedAgent === 'codex' && (
<PermissionsContent
agent="codex"
permissionMode={codexPermissionMode}
onPermissionModeChange={onCodexPermissionModeChange}
/>
)}
{selectedCategory === 'mcp' && selectedAgent === 'claude' && (
<McpServersContent
agent="claude"
servers={mcpServers}
onAdd={() => onOpenMcpForm()}
onEdit={(server) => onOpenMcpForm(server)}
onDelete={onDeleteMcpServer}
onTest={onTestMcpServer}
onDiscoverTools={onDiscoverMcpTools}
testResults={mcpTestResults}
serverTools={mcpServerTools}
toolsLoading={mcpToolsLoading}
/>
)}
{selectedCategory === 'mcp' && selectedAgent === 'cursor' && (
<McpServersContent
agent="cursor"
servers={cursorMcpServers}
onAdd={noopCursorMcpAction}
onEdit={noopCursorMcpAction}
onDelete={noopCursorMcpAction}
/>
)}
{selectedCategory === 'mcp' && selectedAgent === 'codex' && (
<McpServersContent
agent="codex"
servers={codexMcpServers}
onAdd={() => onOpenCodexMcpForm()}
onEdit={(server) => onOpenCodexMcpForm(server)}
onDelete={(serverId) => onDeleteCodexMcpServer(serverId)}
/>
)}
</div>
<AgentCategoryContentSection
selectedAgent={selectedAgent}
selectedCategory={selectedCategory}
agentContextById={agentContextById}
claudePermissions={claudePermissions}
onClaudePermissionsChange={onClaudePermissionsChange}
cursorPermissions={cursorPermissions}
onCursorPermissionsChange={onCursorPermissionsChange}
codexPermissionMode={codexPermissionMode}
onCodexPermissionModeChange={onCodexPermissionModeChange}
mcpServers={mcpServers}
cursorMcpServers={cursorMcpServers}
codexMcpServers={codexMcpServers}
mcpTestResults={mcpTestResults}
mcpServerTools={mcpServerTools}
mcpToolsLoading={mcpToolsLoading}
onOpenMcpForm={onOpenMcpForm}
onDeleteMcpServer={onDeleteMcpServer}
onTestMcpServer={onTestMcpServer}
onDiscoverMcpTools={onDiscoverMcpTools}
onOpenCodexMcpForm={onOpenCodexMcpForm}
onDeleteCodexMcpServer={onDeleteCodexMcpServer}
/>
</div>
</div>
);

View File

@@ -0,0 +1,122 @@
import AccountContent from './content/AccountContent';
import McpServersContent from './content/McpServersContent';
import PermissionsContent from './content/PermissionsContent';
import type { AgentCategoryContentSectionProps } from '../types';
export default function AgentCategoryContentSection({
selectedAgent,
selectedCategory,
agentContextById,
claudePermissions,
onClaudePermissionsChange,
cursorPermissions,
onCursorPermissionsChange,
codexPermissionMode,
onCodexPermissionModeChange,
mcpServers,
cursorMcpServers,
codexMcpServers,
mcpTestResults,
mcpServerTools,
mcpToolsLoading,
onOpenMcpForm,
onDeleteMcpServer,
onTestMcpServer,
onDiscoverMcpTools,
onOpenCodexMcpForm,
onDeleteCodexMcpServer,
}: AgentCategoryContentSectionProps) {
// Cursor MCP add/edit/delete was previously a placeholder and is intentionally preserved.
const noopCursorMcpAction = () => {};
return (
<div className="flex-1 overflow-y-auto p-3 md:p-4">
{selectedCategory === 'account' && (
<AccountContent
agent={selectedAgent}
authStatus={agentContextById[selectedAgent].authStatus}
onLogin={agentContextById[selectedAgent].onLogin}
/>
)}
{selectedCategory === 'permissions' && selectedAgent === 'claude' && (
<PermissionsContent
agent="claude"
skipPermissions={claudePermissions.skipPermissions}
onSkipPermissionsChange={(value) => {
onClaudePermissionsChange({ ...claudePermissions, skipPermissions: value });
}}
allowedTools={claudePermissions.allowedTools}
onAllowedToolsChange={(value) => {
onClaudePermissionsChange({ ...claudePermissions, allowedTools: value });
}}
disallowedTools={claudePermissions.disallowedTools}
onDisallowedToolsChange={(value) => {
onClaudePermissionsChange({ ...claudePermissions, disallowedTools: value });
}}
/>
)}
{selectedCategory === 'permissions' && selectedAgent === 'cursor' && (
<PermissionsContent
agent="cursor"
skipPermissions={cursorPermissions.skipPermissions}
onSkipPermissionsChange={(value) => {
onCursorPermissionsChange({ ...cursorPermissions, skipPermissions: value });
}}
allowedCommands={cursorPermissions.allowedCommands}
onAllowedCommandsChange={(value) => {
onCursorPermissionsChange({ ...cursorPermissions, allowedCommands: value });
}}
disallowedCommands={cursorPermissions.disallowedCommands}
onDisallowedCommandsChange={(value) => {
onCursorPermissionsChange({ ...cursorPermissions, disallowedCommands: value });
}}
/>
)}
{selectedCategory === 'permissions' && selectedAgent === 'codex' && (
<PermissionsContent
agent="codex"
permissionMode={codexPermissionMode}
onPermissionModeChange={onCodexPermissionModeChange}
/>
)}
{selectedCategory === 'mcp' && selectedAgent === 'claude' && (
<McpServersContent
agent="claude"
servers={mcpServers}
onAdd={() => onOpenMcpForm()}
onEdit={(server) => onOpenMcpForm(server)}
onDelete={onDeleteMcpServer}
onTest={onTestMcpServer}
onDiscoverTools={onDiscoverMcpTools}
testResults={mcpTestResults}
serverTools={mcpServerTools}
toolsLoading={mcpToolsLoading}
/>
)}
{selectedCategory === 'mcp' && selectedAgent === 'cursor' && (
<McpServersContent
agent="cursor"
servers={cursorMcpServers}
onAdd={noopCursorMcpAction}
onEdit={noopCursorMcpAction}
onDelete={noopCursorMcpAction}
/>
)}
{selectedCategory === 'mcp' && selectedAgent === 'codex' && (
<McpServersContent
agent="codex"
servers={codexMcpServers}
onAdd={() => onOpenCodexMcpForm()}
onEdit={(server) => onOpenCodexMcpForm(server)}
onDelete={(serverId) => onDeleteCodexMcpServer(serverId)}
/>
)}
</div>
);
}

View File

@@ -0,0 +1,34 @@
import { useTranslation } from 'react-i18next';
import type { AgentCategory } from '../../../../types/types';
import type { AgentCategoryTabsSectionProps } from '../types';
const AGENT_CATEGORIES: AgentCategory[] = ['account', 'permissions', 'mcp'];
export default function AgentCategoryTabsSection({
selectedCategory,
onSelectCategory,
}: AgentCategoryTabsSectionProps) {
const { t } = useTranslation('settings');
return (
<div className="border-b border-gray-200 dark:border-gray-700 flex-shrink-0">
<div className="flex px-2 md:px-4 overflow-x-auto">
{AGENT_CATEGORIES.map((category) => (
<button
key={category}
onClick={() => onSelectCategory(category)}
className={`px-3 md:px-4 py-2 md:py-3 text-xs md:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
selectedCategory === category
? 'border-blue-600 text-blue-600 dark:text-blue-400'
: 'border-transparent text-muted-foreground hover:text-foreground'
}`}
>
{category === 'account' && t('tabs.account')}
{category === 'permissions' && t('tabs.permissions')}
{category === 'mcp' && t('tabs.mcpServers')}
</button>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,44 @@
import type { AgentProvider } from '../../../../types/types';
import AgentListItem from '../AgentListItem';
import type { AgentSelectorSectionProps } from '../types';
const AGENT_PROVIDERS: AgentProvider[] = ['claude', 'cursor', 'codex'];
export default function AgentSelectorSection({
selectedAgent,
onSelectAgent,
agentContextById,
}: AgentSelectorSectionProps) {
return (
<>
<div className="md:hidden border-b border-gray-200 dark:border-gray-700 flex-shrink-0">
<div className="flex">
{AGENT_PROVIDERS.map((agent) => (
<AgentListItem
key={`mobile-${agent}`}
agentId={agent}
authStatus={agentContextById[agent].authStatus}
isSelected={selectedAgent === agent}
onClick={() => onSelectAgent(agent)}
isMobile
/>
))}
</div>
</div>
<div className="hidden md:block w-48 border-r border-gray-200 dark:border-gray-700 flex-shrink-0">
<div className="p-2">
{AGENT_PROVIDERS.map((agent) => (
<AgentListItem
key={`desktop-${agent}`}
agentId={agent}
authStatus={agentContextById[agent].authStatus}
isSelected={selectedAgent === agent}
onClick={() => onSelectAgent(agent)}
/>
))}
</div>
</div>
</>
);
}

View File

@@ -1,9 +1,9 @@
import { LogIn } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Badge } from '../../../../ui/badge';
import { Button } from '../../../../ui/button';
import SessionProviderLogo from '../../../../SessionProviderLogo';
import type { AgentProvider, AuthStatus } from '../../../types/types';
import { Badge } from '../../../../../../ui/badge';
import { Button } from '../../../../../../ui/button';
import SessionProviderLogo from '../../../../../../SessionProviderLogo';
import type { AgentProvider, AuthStatus } from '../../../../../types/types';
type AccountContentProps = {
agent: AgentProvider;

View File

@@ -1,8 +1,8 @@
import { Edit3, Globe, Plus, Server, Terminal, Trash2, Zap } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Badge } from '../../../../ui/badge';
import { Button } from '../../../../ui/button';
import type { McpServer, McpToolsResult, McpTestResult } from '../../../types/types';
import { Badge } from '../../../../../../ui/badge';
import { Button } from '../../../../../../ui/button';
import type { McpServer, McpToolsResult, McpTestResult } from '../../../../../types/types';
const getTransportIcon = (type: string | undefined) => {
if (type === 'stdio') {

View File

@@ -1,9 +1,9 @@
import { useState } from 'react';
import { AlertTriangle, Plus, Shield, X } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Button } from '../../../../ui/button';
import { Input } from '../../../../ui/input';
import type { CodexPermissionMode } from '../../../types/types';
import { Button } from '../../../../../../ui/button';
import { Input } from '../../../../../../ui/input';
import type { CodexPermissionMode } from '../../../../../types/types';
const COMMON_CLAUDE_TOOLS = [
'Bash(git log:*)',

View File

@@ -0,0 +1,80 @@
import type {
AgentProvider,
AuthStatus,
AgentCategory,
ClaudePermissionsState,
CodexPermissionMode,
CursorPermissionsState,
McpServer,
McpToolsResult,
McpTestResult,
} from '../../../types/types';
export type AgentContext = {
authStatus: AuthStatus;
onLogin: () => void;
};
export type AgentContextByProvider = Record<AgentProvider, AgentContext>;
export type AgentsSettingsTabProps = {
claudeAuthStatus: AuthStatus;
cursorAuthStatus: AuthStatus;
codexAuthStatus: AuthStatus;
onClaudeLogin: () => void;
onCursorLogin: () => void;
onCodexLogin: () => void;
claudePermissions: ClaudePermissionsState;
onClaudePermissionsChange: (value: ClaudePermissionsState) => void;
cursorPermissions: CursorPermissionsState;
onCursorPermissionsChange: (value: CursorPermissionsState) => void;
codexPermissionMode: CodexPermissionMode;
onCodexPermissionModeChange: (value: CodexPermissionMode) => void;
mcpServers: McpServer[];
cursorMcpServers: McpServer[];
codexMcpServers: McpServer[];
mcpTestResults: Record<string, McpTestResult>;
mcpServerTools: Record<string, McpToolsResult>;
mcpToolsLoading: Record<string, boolean>;
onOpenMcpForm: (server?: McpServer) => void;
onDeleteMcpServer: (serverId: string, scope?: string) => void;
onTestMcpServer: (serverId: string, scope?: string) => void;
onDiscoverMcpTools: (serverId: string, scope?: string) => void;
onOpenCodexMcpForm: (server?: McpServer) => void;
onDeleteCodexMcpServer: (serverId: string) => void;
};
export type AgentCategoryTabsSectionProps = {
selectedCategory: AgentCategory;
onSelectCategory: (category: AgentCategory) => void;
};
export type AgentSelectorSectionProps = {
selectedAgent: AgentProvider;
onSelectAgent: (agent: AgentProvider) => void;
agentContextById: AgentContextByProvider;
};
export type AgentCategoryContentSectionProps = {
selectedAgent: AgentProvider;
selectedCategory: AgentCategory;
agentContextById: AgentContextByProvider;
claudePermissions: ClaudePermissionsState;
onClaudePermissionsChange: (value: ClaudePermissionsState) => void;
cursorPermissions: CursorPermissionsState;
onCursorPermissionsChange: (value: CursorPermissionsState) => void;
codexPermissionMode: CodexPermissionMode;
onCodexPermissionModeChange: (value: CodexPermissionMode) => void;
mcpServers: McpServer[];
cursorMcpServers: McpServer[];
codexMcpServers: McpServer[];
mcpTestResults: Record<string, McpTestResult>;
mcpServerTools: Record<string, McpToolsResult>;
mcpToolsLoading: Record<string, boolean>;
onOpenMcpForm: (server?: McpServer) => void;
onDeleteMcpServer: (serverId: string, scope?: string) => void;
onTestMcpServer: (serverId: string, scope?: string) => void;
onDiscoverMcpTools: (serverId: string, scope?: string) => void;
onOpenCodexMcpForm: (server?: McpServer) => void;
onDeleteCodexMcpServer: (serverId: string) => void;
};