/** * PermissionDialog Component * * Shows permission confirmation dialogs for bash commands, file operations, etc. * Integrates with WebSocket for real-time permission requests from the server. */ import { useState } from 'react'; import { Shield, Terminal, FileEdit, GitBranch, Globe, X, Check, AlertTriangle, } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { cn } from '../utils/cn'; import { modalOverlay, modalContent, smoothTransition } from '../utils/animations'; import { Button } from '../primitives/Button'; // Permission types export type PermissionType = 'bash' | 'file' | 'git' | 'web'; // Permission request context export interface PermissionRequestContext { command?: string; operation?: string; path?: string; gitOperation?: string; query?: string; patterns?: string[]; externalPaths?: string[]; } // Diff line for file operations interface DiffLine { type: 'add' | 'remove' | 'context'; lineNumber: number | null; content: string; } // Diff hunk interface DiffHunk { oldStart: number; oldCount: number; newStart: number; newCount: number; lines: DiffLine[]; } // Diff info for file operations export interface DiffInfo { isNew: boolean; additions: number; deletions: number; hunks: DiffHunk[]; } // Permission request payload export interface PermissionRequest { requestId: string; permissionType: PermissionType; context: PermissionRequestContext; diff?: DiffInfo; } interface PermissionDialogProps { request: PermissionRequest; onAllow: (requestId: string, remember: boolean) => void; onDeny: (requestId: string, remember: boolean) => void; responsive?: boolean; } // Icon component based on permission type function getPermissionIcon(type: PermissionType) { switch (type) { case 'bash': return ; case 'file': return ; case 'git': return ; case 'web': return ; default: return ; } } // Title based on permission type function getPermissionTitle(type: PermissionType) { switch (type) { case 'bash': return 'Execute Command'; case 'file': return 'File Operation'; case 'git': return 'Git Operation'; case 'web': return 'Web Access'; default: return 'Permission Required'; } } // Render diff content function DiffViewer({ diff }: { diff: DiffInfo }) { if (!diff.hunks || diff.hunks.length === 0) { return null; } return (
{diff.isNew ? 'New file' : 'Changes'}
+{diff.additions} -{diff.deletions}
          {diff.hunks.map((hunk, hunkIndex) => (
            
@@ -{hunk.oldStart},{hunk.oldCount} +{hunk.newStart},{hunk.newCount} @@
{hunk.lines.map((line, lineIndex) => { let className = 'px-3 py-0.5 '; let prefix = ' '; if (line.type === 'add') { className += 'bg-green-500/10 text-green-400'; prefix = '+'; } else if (line.type === 'remove') { className += 'bg-red-500/10 text-red-400'; prefix = '-'; } else { className += 'text-fg-muted'; } return (
{prefix} {line.content}
); })}
))}
); } export function PermissionDialog({ request, onAllow, onDeny, responsive = false, }: PermissionDialogProps) { const [remember, setRemember] = useState(false); const { requestId, permissionType, context, diff } = request; const handleAllow = () => { onAllow(requestId, remember); }; const handleDeny = () => { onDeny(requestId, remember); }; // Format context for display const renderContext = () => { switch (permissionType) { case 'bash': return (
Command:
{context.command} {context.externalPaths && context.externalPaths.length > 0 && (
External paths detected:
{context.externalPaths.map((p, i) => (
{p}
))}
)}
); case 'file': return (
Operation: {context.operation?.toUpperCase()}
Path:
{context.path} {diff && }
); case 'git': return (
Git operation: {context.gitOperation?.toUpperCase()}
{context.command && ( <>
Command:
{context.command} )}
); case 'web': return (
Request:
{context.query || context.command}
); default: return (
              {JSON.stringify(context, null, 2)}
            
); } }; return ( {/* Header */}
{responsive && (
)}
{getPermissionIcon(permissionType)}

{getPermissionTitle(permissionType)}

AI is requesting permission

{/* Content */}
{renderContext()}
{/* Footer */}
{/* Remember checkbox */} {/* Action buttons */}
); }