feat(a2ui): Implement A2UI backend with question handling and WebSocket support

- Added A2UITypes for defining question structures and answers.
- Created A2UIWebSocketHandler for managing WebSocket connections and message handling.
- Developed ask-question tool for interactive user questions via A2UI.
- Introduced platformUtils for platform detection and shell command handling.
- Centralized TypeScript types in index.ts for better organization.
- Implemented compatibility checks for hook templates based on platform requirements.
This commit is contained in:
catlog22
2026-01-31 15:27:12 +08:00
parent 4e009bb03a
commit 715ef12c92
163 changed files with 19495 additions and 715 deletions

View File

@@ -0,0 +1,152 @@
// ========================================
// Codex MCP Card Component
// ========================================
// Read-only display card for Codex MCP servers (no edit/delete)
import { useIntl } from 'react-intl';
import {
Server,
Power,
PowerOff,
ChevronDown,
ChevronUp,
Lock,
} from 'lucide-react';
import { Card } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
import { cn } from '@/lib/utils';
import type { McpServer } from '@/lib/api';
// ========== Types ==========
export interface CodexMcpCardProps {
server: McpServer;
enabled: boolean;
isExpanded: boolean;
onToggleExpand: () => void;
}
// ========== Component ==========
export function CodexMcpCard({
server,
enabled,
isExpanded,
onToggleExpand,
}: CodexMcpCardProps) {
const { formatMessage } = useIntl();
return (
<Card className={cn('overflow-hidden', !enabled && 'opacity-60')}>
{/* Header */}
<div
className="p-4 cursor-pointer hover:bg-muted/50 transition-colors"
onClick={onToggleExpand}
>
<div className="flex items-start justify-between gap-2">
<div className="flex items-start gap-3">
<div className={cn(
'p-2 rounded-lg',
enabled ? 'bg-primary/10' : 'bg-muted'
)}>
<Server className={cn(
'w-5 h-5',
enabled ? 'text-primary' : 'text-muted-foreground'
)} />
</div>
<div>
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-foreground">
{server.name}
</span>
{/* Read-only badge */}
<Badge variant="secondary" className="text-xs flex items-center gap-1">
<Lock className="w-3 h-3" />
{formatMessage({ id: 'mcp.codex.readOnly' })}
</Badge>
{enabled && (
<Badge variant="outline" className="text-xs text-green-600">
<Power className="w-3 h-3 mr-1" />
{formatMessage({ id: 'mcp.status.enabled' })}
</Badge>
)}
</div>
<p className="text-sm text-muted-foreground mt-1 font-mono">
{server.command} {server.args?.join(' ') || ''}
</p>
</div>
</div>
<div className="flex items-center gap-2">
{/* Disabled toggle button (visual only, no edit capability) */}
<div className={cn(
'w-8 h-8 rounded-md flex items-center justify-center',
enabled ? 'bg-green-100 text-green-600' : 'bg-muted text-muted-foreground'
)}>
{enabled ? <Power className="w-4 h-4" /> : <PowerOff className="w-4 h-4" />}
</div>
{isExpanded ? (
<ChevronUp className="w-5 h-5 text-muted-foreground" />
) : (
<ChevronDown className="w-5 h-5 text-muted-foreground" />
)}
</div>
</div>
</div>
{/* Expanded Content */}
{isExpanded && (
<div className="border-t border-border p-4 space-y-3 bg-muted/30">
{/* Command details */}
<div>
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.command' })}</p>
<code className="text-sm bg-background px-2 py-1 rounded block overflow-x-auto">
{server.command}
</code>
</div>
{/* Args */}
{server.args && server.args.length > 0 && (
<div>
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.args' })}</p>
<div className="flex flex-wrap gap-1">
{server.args.map((arg, idx) => (
<Badge key={idx} variant="outline" className="font-mono text-xs">
{arg}
</Badge>
))}
</div>
</div>
)}
{/* Environment variables */}
{server.env && Object.keys(server.env).length > 0 && (
<div>
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.env' })}</p>
<div className="space-y-1">
{Object.entries(server.env).map(([key, value]) => (
<div key={key} className="flex items-center gap-2 text-sm">
<Badge variant="secondary" className="font-mono">{key}</Badge>
<span className="text-muted-foreground">=</span>
<code className="text-xs bg-background px-2 py-1 rounded flex-1 overflow-x-auto">
{value as string}
</code>
</div>
))}
</div>
</div>
)}
{/* Read-only notice */}
<div className="flex items-center gap-2 px-3 py-2 bg-muted/50 rounded-md border border-border">
<Lock className="w-4 h-4 text-muted-foreground" />
<p className="text-xs text-muted-foreground">
{formatMessage({ id: 'mcp.codex.readOnlyNotice' })}
</p>
</div>
</div>
)}
</Card>
);
}
export default CodexMcpCard;