Newer
Older
cortex-hub / frontend / src / features / skills / components / SkillsPageContent.js
import React from 'react';
import SkillFileTree from './SkillFileTree';
import SkillEditor from './SkillEditor';

export default function SkillsPageContent({ context }) {
    const {
        user,
        Icon,
        loading,
        error,
        searchQuery,
        setSearchQuery,
        activeFilter,
        setActiveFilter,
        isModalOpen,
        openModal,
        closeModal,
        handleSave,
        isAdmin,
        filteredSkills,
        stats,
        editingSkill,
        formData,
        setFormData,
        setConfirmDeleteId,
        skillFiles,
        activeFile,
        activeFileContent,
        setActiveFileContent,
        isSavingFile,
        handleSelectFile,
        handleAddFile,
        handleSaveActiveFile,
        handleDeleteFile
    } = context;

    const SidebarItem = ({ id, label, icon, count, active }) => (
        <button
            onClick={() => setActiveFilter(id)}
            className={`w-full flex items-center px-4 py-3 rounded-xl transition-all duration-200 ${active
                ? 'bg-indigo-600 text-white shadow-lg shadow-indigo-200 dark:shadow-indigo-900/20'
                : 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
                }`}
        >
            <Icon path={icon} className="w-5 h-5 mr-3 flex-none" />
            <span className="font-medium text-sm flex-grow text-left truncate">{label}</span>
            <span className={`text-[10px] font-bold px-2 py-0.5 rounded-full flex-none ml-2 ${active ? 'bg-white/20 text-white' : 'bg-gray-100 dark:bg-gray-700 text-gray-500'}`}>
                {count}
            </span>
        </button>
    );

    return (
        <div className="flex h-full bg-gray-50 dark:bg-gray-900 overflow-hidden">
            {/* --- Far Left Sidebar --- */}
            <div className="w-64 flex-none bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex flex-col z-20">
                <div className="p-8 pb-4">
                    <h1 className="text-2xl font-black bg-clip-text text-transparent bg-gradient-to-br from-indigo-500 to-purple-600 leading-tight py-1">
                        Cortex Skills
                    </h1>
                    <p className="text-[10px] uppercase tracking-[0.2em] font-black text-gray-400 mt-1">Foundational Layer</p>
                </div>

                <div className="px-4 space-y-2 overflow-y-auto flex-grow">
                    <SidebarItem id="all" label="All Libraries" icon="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" count={stats.total} active={activeFilter === 'all'} />
                    <SidebarItem id="mine" label="My Creations" icon="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" count={stats.mine} active={activeFilter === 'mine'} />
                </div>

                <div className="p-6">
                    <button
                        onClick={() => openModal()}
                        className="w-full bg-indigo-600 hover:bg-indigo-700 text-white rounded-[20px] py-4 font-black uppercase text-xs tracking-widest shadow-xl shadow-indigo-600/20 active:scale-95 transition-all flex items-center justify-center gap-2"
                    >
                        <Icon path="M12 4v16m8-8H4" className="w-5 h-5" />
                        New Folder
                    </button>
                </div>
            </div>

            {/* --- Main Content (IDE Layout) --- */}
            <div className="flex-grow flex min-w-0 bg-white dark:bg-[#1e1e1e] overflow-hidden relative">
                
                {/* --- IDE Left Pane: Tool/Folder List --- */}
                <div className="w-72 flex-none border-r border-gray-200 dark:border-[#2d2d2d] flex flex-col bg-gray-50/50 dark:bg-[#252526]">
                    <div className="p-4 border-b border-gray-200 dark:border-[#2d2d2d]">
                        <div className="relative">
                            <Icon path="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
                            <input
                                type="text"
                                placeholder="Search folders..."
                                value={searchQuery}
                                onChange={(e) => setSearchQuery(e.target.value)}
                                className="w-full bg-white dark:bg-[#3c3c3c] border border-gray-200 dark:border-[#3c3c3c] rounded-lg pl-9 pr-4 py-2 text-xs focus:ring-1 focus:border-indigo-500 focus:ring-indigo-500 outline-none placeholder-gray-400 dark:text-gray-200"
                            />
                        </div>
                    </div>

                    <div className="flex-grow overflow-y-auto custom-scrollbar p-2">
                        {loading ? (
                            <div className="flex justify-center p-8"><span className="w-6 h-6 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin"></span></div>
                        ) : error ? (
                            <div className="text-center p-4 text-red-500 text-xs">{error}</div>
                        ) : filteredSkills.length === 0 ? (
                            <div className="text-center p-8 text-gray-400 text-xs">No folders found.</div>
                        ) : (
                            <div className="space-y-1">
                                {filteredSkills.map(skill => {
                                    const isSelected = editingSkill && editingSkill.id === skill.id;
                                    return (
                                        <div key={skill.id} className="flex flex-col">
                                            <button 
                                                onClick={() => {
                                                    openModal(skill);
                                                }}
                                                className={`w-full text-left px-3 py-2 rounded-lg transition-colors flex items-center justify-between group ${isSelected ? 'bg-indigo-100 dark:bg-[#37373d] text-indigo-700 dark:text-gray-100 font-bold' : 'text-gray-700 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-[#2a2d2e]'}`}
                                            >
                                                <div className="flex items-center gap-2 truncate">
                                                    <span className="text-sm flex-none">{skill.extra_metadata?.emoji || "📁"}</span>
                                                    <span className="text-xs truncate">{skill.name}</span>
                                                </div>
                                                {isAdmin || skill.owner_id === user?.id ? (
                                                    <span 
                                                        onClick={(e) => { e.stopPropagation(); setConfirmDeleteId(skill.id); }}
                                                        className="opacity-0 group-hover:opacity-100 text-gray-400 hover:text-red-500 transition-opacity flex-none"
                                                    >
                                                        <Icon path="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" className="w-3.5 h-3.5" />
                                                    </span>
                                                ) : null}
                                            </button>
                                            
                                            {/* Sub-Tree of Files if this folder is selected */}
                                            {isSelected && (
                                                <div className="ml-6 py-1 border-l border-gray-200 dark:border-[#3c3c3c] pl-2 mt-1">
                                                    <SkillFileTree 
                                                        files={skillFiles}
                                                        activeFile={activeFile}
                                                        onSelectFile={handleSelectFile}
                                                        onAddFile={handleAddFile}
                                                        onDeleteFile={handleDeleteFile}
                                                        Icon={Icon}
                                                    />
                                                </div>
                                            )}
                                        </div>
                                    );
                                })}
                            </div>
                        )}
                    </div>
                </div>

                {/* --- IDE Right Pane: Code Editor --- */}
                <div className="flex-grow flex flex-col min-w-0 relative bg-white dark:bg-[#1e1e1e]">
                    {editingSkill ? (
                        <>
                            {/* Editor Tabs / Header */}
                            <div className="h-10 border-b border-gray-200 dark:border-[#2d2d2d] flex flex-wrap items-center px-4 justify-between shrink-0 bg-gray-50/50 dark:bg-[#252526]">
                                <div className="flex items-center gap-3">
                                    <div className="flex items-center gap-2 text-gray-600 dark:text-gray-300 text-xs font-mono">
                                        <Icon path="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" className="w-3 h-3 opacity-70" />
                                        {formData.name} / <span className="text-gray-900 dark:text-white font-bold">{activeFile || 'No file selected'}</span>
                                    </div>
                                    {isSavingFile && <span className="text-green-500 text-[10px] animate-pulse">Autosaving...</span>}
                                </div>
                                
                                <div className="flex items-center gap-2">
                                    {(isAdmin || editingSkill.owner_id === user?.id) && activeFile === 'SKILL.md' && (
                                        <button onClick={() => {
                                            const template = `---\nname: ${formData.name || 'my_custom_tool'}\ndescription: Write a detailed description of what this does.\n---\n\n### Execution Logic (Bash)\n\`\`\`bash\necho "Hello Cortex"\n\`\`\`\n`;
                                            setActiveFileContent(template);
                                            handleSaveActiveFile(template); // autosave trigger
                                        }} className="text-[10px] px-2 py-1 border border-indigo-200 dark:border-indigo-900 bg-indigo-50 dark:bg-indigo-900/30 hover:bg-indigo-100 dark:hover:bg-indigo-800 text-indigo-700 dark:text-indigo-400 rounded transition-colors uppercase tracking-wider font-bold">
                                            Insert Template
                                        </button>
                                    )}
                                </div>
                            </div>
                            
                            {/* Editor Main Content */}
                            <div className="flex-grow flex min-h-0">
                                {activeFile ? (
                                    <div className="flex-grow flex relative">
                                        <SkillEditor 
                                            activeFile={activeFile}
                                            content={activeFileContent}
                                            onChange={(c) => {
                                                setActiveFileContent(c);
                                            }}
                                            onSave={handleSaveActiveFile}
                                            isSaving={isSavingFile}
                                        />
                                    </div>
                                ) : (
                                    <div className="flex-grow flex flex-col items-center justify-center text-gray-400 dark:text-gray-600">
                                        <Icon path="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" className="w-16 h-16 opacity-20 mb-4" />
                                        <p className="font-mono text-xs">Select a file from the explorer to open the editor.</p>
                                    </div>
                                )}
                            </div>
                        </>
                    ) : (
                        <div className="flex-grow flex flex-col items-center justify-center text-gray-400 dark:text-gray-500 bg-gray-50 dark:bg-[#1e1e1e]">
                            <Icon path="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" className="w-24 h-24 opacity-10 mb-6" />
                            <h2 className="text-2xl font-black text-gray-300 dark:text-[#4d4d4d] mb-2 tracking-tight">Cortex Visual Studio</h2>
                            <p className="text-sm opacity-50 font-medium">Create a new tool folder or select one to begin writing capabilities.</p>
                        </div>
                    )}
                </div>
            </div>

            {/* --- Modals for Creation --- */}
            {/* Create New Folder Modal purely prompts for the name */}
            {!editingSkill && isModalOpen && (
                <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-in fade-in duration-300">
                    <div className="bg-white dark:bg-gray-800 rounded-2xl w-full max-w-sm shadow-2xl overflow-hidden border border-gray-200/50 dark:border-gray-700/50">
                        <div className="px-6 py-4 border-b border-gray-100 dark:border-gray-700">
                            <h3 className="text-sm font-black dark:text-white uppercase tracking-widest text-indigo-600 dark:text-indigo-400">New Tool Folder</h3>
                        </div>
                        <div className="p-6 pb-2">
                            <label className="block text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-widest mb-2">Folder / Tool Name</label>
                            <input 
                                type="text"
                                autoFocus
                                value={formData.name || ''}
                                onChange={(e) => setFormData({ ...formData, name: e.target.value.toLowerCase().replace(/[^a-z0-9_-]/g, '') })}
                                className="w-full h-10 bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-lg px-4 font-mono focus:ring-2 focus:ring-indigo-500 outline-none text-sm dark:text-white"
                                placeholder="e.g. aws_lambda_invoker"
                                onKeyDown={(e) => {
                                    if(e.key === 'Enter') handleSave()
                                }}
                            />
                            <p className="text-[10px] text-gray-400 dark:text-gray-500 mt-3 font-semibold">Alphanumeric, underscores, and dashes only. Valid OS paths will be evaluated directly.</p>
                            
                            <label className="block text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-widest mb-2 mt-5">Target System</label>
                            <div className="flex bg-gray-100 border border-gray-200 dark:border-gray-700 dark:bg-[#1a1c1e] rounded-lg p-1">
                                {['swarm_control', 'voice_chat'].map(f => (
                                    <button
                                        key={f}
                                        onClick={() => setFormData({ ...formData, features: [f] })}
                                        className={`flex-1 py-1.5 text-[10px] font-black uppercase tracking-widest rounded-md transition-all ${
                                            (formData.features || []).includes(f) 
                                            ? 'bg-white dark:bg-[#2d2d30] text-indigo-600 dark:text-indigo-400 shadow-sm' 
                                            : 'text-gray-500 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
                                        }`}
                                    >
                                        {f.replace('_', ' ')}
                                    </button>
                                ))}
                            </div>
                        </div>
                        <div className="px-6 py-4 border-t border-gray-100 dark:border-gray-700 flex justify-end gap-3 mt-4 bg-gray-50/50 dark:bg-gray-900/50">
                            <button onClick={closeModal} className="px-4 py-2 text-xs font-bold text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 uppercase tracking-wider transition-colors">Cancel</button>
                            <button onClick={handleSave} className="px-6 py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg text-xs font-black uppercase tracking-widest transition-transform active:scale-95 shadow-md shadow-indigo-600/20">
                                MkDir
                            </button>
                        </div>
                    </div>
                </div>
            )}
        </div>
    );
}