// ======================================== // Skill Create Dialog Component // ======================================== // Modal dialog for creating/importing skills with two modes: // - Import: import existing skill folder // - CLI Generate: AI-generated skill from description import { useState, useCallback } from 'react'; import { useIntl } from 'react-intl'; import { Folder, User, FolderInput, Sparkles, CheckCircle, XCircle, Loader2, Info, } from 'lucide-react'; import { Dialog, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, } from '@/components/ui/Dialog'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { Textarea } from '@/components/ui/Textarea'; import { Label } from '@/components/ui/Label'; import { validateSkillImport, createSkill } from '@/lib/api'; import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore'; import { cn } from '@/lib/utils'; export interface SkillCreateDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onCreated: () => void; cliType?: 'claude' | 'codex'; } type CreateMode = 'import' | 'cli-generate'; type SkillLocation = 'project' | 'user'; interface ValidationResult { valid: boolean; errors?: string[]; skillInfo?: { name: string; description: string; version?: string; supportingFiles?: string[] }; } export function SkillCreateDialog({ open, onOpenChange, onCreated, cliType = 'claude' }: SkillCreateDialogProps) { const { formatMessage } = useIntl(); const projectPath = useWorkflowStore(selectProjectPath); const [mode, setMode] = useState('import'); const [location, setLocation] = useState('project'); // Import mode state const [sourcePath, setSourcePath] = useState(''); const [customName, setCustomName] = useState(''); const [validationResult, setValidationResult] = useState(null); const [isValidating, setIsValidating] = useState(false); // CLI Generate mode state const [skillName, setSkillName] = useState(''); const [description, setDescription] = useState(''); const [isCreating, setIsCreating] = useState(false); const resetState = useCallback(() => { setMode('import'); setLocation('project'); setSourcePath(''); setCustomName(''); setValidationResult(null); setIsValidating(false); setSkillName(''); setDescription(''); setIsCreating(false); }, []); const handleOpenChange = useCallback((open: boolean) => { if (!open) { resetState(); } onOpenChange(open); }, [onOpenChange, resetState]); const handleValidate = useCallback(async () => { if (!sourcePath.trim()) return; setIsValidating(true); setValidationResult(null); try { const result = await validateSkillImport(sourcePath.trim()); setValidationResult(result); } catch (err) { setValidationResult({ valid: false, errors: [err instanceof Error ? err.message : String(err)], }); } finally { setIsValidating(false); } }, [sourcePath]); const handleCreate = useCallback(async () => { if (mode === 'import') { if (!sourcePath.trim()) return; if (!validationResult?.valid) return; } else { if (!skillName.trim()) return; if (!description.trim()) return; } setIsCreating(true); try { await createSkill({ mode, location, sourcePath: mode === 'import' ? sourcePath.trim() : undefined, skillName: mode === 'import' ? (customName.trim() || undefined) : skillName.trim(), description: mode === 'cli-generate' ? description.trim() : undefined, generationType: mode === 'cli-generate' ? 'description' : undefined, projectPath, cliType, }); handleOpenChange(false); onCreated(); } catch (err) { console.error('Failed to create skill:', err); if (mode === 'import') { setValidationResult({ valid: false, errors: [err instanceof Error ? err.message : formatMessage({ id: 'skills.create.createError' })], }); } } finally { setIsCreating(false); } }, [mode, location, sourcePath, customName, skillName, description, validationResult, projectPath, handleOpenChange, onCreated, formatMessage]); const canCreate = mode === 'import' ? sourcePath.trim() && validationResult?.valid && !isCreating : skillName.trim() && description.trim() && !isCreating; return ( {formatMessage({ id: 'skills.create.title' })} {formatMessage({ id: 'skills.description' })}
{/* Location Selection */}
{/* Mode Selection */}
{/* Import Mode Content */} {mode === 'import' && (
{ setSourcePath(e.target.value); setValidationResult(null); }} placeholder={formatMessage({ id: 'skills.create.sourcePathPlaceholder' })} className="font-mono text-sm" />

{formatMessage({ id: 'skills.create.sourcePathHint' })}

setCustomName(e.target.value)} placeholder={formatMessage({ id: 'skills.create.customNamePlaceholder' })} />
{/* Validation Result */} {isValidating && (
{formatMessage({ id: 'skills.create.validating' })}
)} {validationResult && !isValidating && ( validationResult.valid ? (
{formatMessage({ id: 'skills.create.validSkill' })}
{validationResult.skillInfo && (
{formatMessage({ id: 'skills.card.description' })}: {validationResult.skillInfo.name}
{validationResult.skillInfo.description && (
{formatMessage({ id: 'skills.card.description' })}: {validationResult.skillInfo.description}
)} {validationResult.skillInfo.version && (
{formatMessage({ id: 'skills.card.version' })}: {validationResult.skillInfo.version}
)}
)}
) : (
{formatMessage({ id: 'skills.create.invalidSkill' })}
{validationResult.errors && (
    {validationResult.errors.map((error, i) => (
  • {error}
  • ))}
)}
) )}
)} {/* CLI Generate Mode Content */} {mode === 'cli-generate' && (
setSkillName(e.target.value)} placeholder={formatMessage({ id: 'skills.create.skillNamePlaceholder' })} />

{formatMessage({ id: 'skills.create.skillNameHint' })}