Newer
Older
cortex-hub / frontend / src / features / skills / pages / SkillsPage.js
import React, { useState, useEffect, useMemo } from 'react';
import SkillsPageContent from '../components/SkillsPageContent';
import { getSkills, createSkill, updateSkill, deleteSkill } from '../../../services/apiService';

export default function SkillsPage({ user, Icon }) {
    const [skills, setSkills] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    const [searchQuery, setSearchQuery] = useState('');
    const [activeFilter, setActiveFilter] = useState('all'); // all, system, mine, group
    const [showSystemSkills, setShowSystemSkills] = useState(false);
    const [viewingDoc, setViewingDoc] = useState(null);
    const [showRawDoc, setShowRawDoc] = useState(false);

    const [isModalOpen, setIsModalOpen] = useState(false);
    const [editingSkill, setEditingSkill] = useState(null);

    const [formData, setFormData] = useState({
        name: '',
        description: '',
        skill_type: 'local',
        config: '{}',
        is_system: false,
        system_prompt: '',
        is_enabled: true,
        features: ['chat'],
        extra_metadata: { emoji: '⚙️' },
        preview_markdown: ''
    });
    const [showAdvanced, setShowAdvanced] = useState(false);

    const fetchSkills = async () => {
        try {
            setLoading(true);
            const data = await getSkills();
            setSkills(data);
            setError(null);
        } catch (err) {
            setError("Failed to load skills.");
        } finally {
            setLoading(false);
        }
    };

    useEffect(() => {
        fetchSkills();
    }, []);

    const filteredSkills = useMemo(() => {
        return skills.filter(skill => {
            const matchesSearch = skill.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
                (skill.description || '').toLowerCase().includes(searchQuery.toLowerCase());

            if (!matchesSearch) return false;
            if (!showSystemSkills && skill.is_system && activeFilter !== 'system') return false;

            if (activeFilter === 'all') return true;
            if (activeFilter === 'system') return skill.is_system;
            if (activeFilter === 'mine') return !skill.is_system && skill.owner_id === user?.id;
            if (activeFilter === 'group') return skill.group_id && !skill.is_system;
            return true;
        });
    }, [skills, searchQuery, activeFilter, user, showSystemSkills]);

    const stats = useMemo(() => ({
        total: skills.filter(s => showSystemSkills || activeFilter === 'system' || !s.is_system).length,
        system: skills.filter(s => s.is_system).length,
        mine: skills.filter(s => !s.is_system && s.owner_id === user?.id).length,
        group: skills.filter(s => s.group_id).length,
        enabled: skills.filter(s => s.is_enabled).length
    }), [skills, user, showSystemSkills, activeFilter]);

    const openModal = (skill = null) => {
        if (skill) {
            setEditingSkill(skill);
            setFormData({
                name: skill.name,
                description: skill.description || '',
                skill_type: skill.skill_type,
                config: JSON.stringify(skill.config, null, 2),
                is_system: skill.is_system,
                system_prompt: skill.system_prompt || '',
                is_enabled: skill.is_enabled ?? true,
                features: skill.features || ['chat'],
                extra_metadata: skill.extra_metadata || { emoji: '⚙️' },
                preview_markdown: skill.preview_markdown || ''
            });
        } else {
            setEditingSkill(null);
            setFormData({
                name: '',
                description: '',
                skill_type: 'local',
                config: '{}',
                is_system: false,
                system_prompt: '',
                is_enabled: true,
                features: ['chat'],
                extra_metadata: { emoji: '⚙️' },
                preview_markdown: ''
            });
        }
        setIsModalOpen(true);
    };

    const handleClone = (skill) => {
        setEditingSkill(null);
        setFormData({
            name: `${skill.name}_clone`,
            description: skill.description || '',
            skill_type: skill.skill_type,
            config: JSON.stringify(skill.config, null, 2),
            is_system: false,
            system_prompt: skill.system_prompt || '',
            is_enabled: true,
            features: skill.features || ['chat'],
            extra_metadata: skill.extra_metadata || { emoji: '⚙️' },
            preview_markdown: skill.preview_markdown || ''
        });
        setIsModalOpen(true);
    };

    const closeModal = () => {
        setIsModalOpen(false);
        setEditingSkill(null);
        setViewingDoc(null);
        setShowRawDoc(false);
    };

    const handleSave = async () => {
        try {
            let configObj = {};
            try {
                configObj = JSON.parse(formData.config);
            } catch (e) {
                alert("Invalid JSON in config");
                return;
            }

            const payload = {
                ...formData,
                config: configObj
            };

            if (editingSkill) {
                await updateSkill(editingSkill.id, payload);
            } else {
                await createSkill(payload);
            }
            closeModal();
            fetchSkills();
        } catch (err) {
            alert("Error saving skill");
        }
    };

    const handleDelete = async (id) => {
        if (!window.confirm("Are you sure you want to delete this skill?")) return;
        try {
            await deleteSkill(id);
            fetchSkills();
        } catch (err) {
            alert("Error deleting skill");
        }
    };

    const isAdmin = user?.role === 'admin';

    const context = {
        user,
        Icon,
        loading,
        error,
        searchQuery,
        setSearchQuery,
        activeFilter,
        setActiveFilter,
        showSystemSkills,
        setShowSystemSkills,
        viewingDoc,
        setViewingDoc,
        showRawDoc,
        setShowRawDoc,
        isModalOpen,
        openModal,
        closeModal,
        handleClone,
        handleSave,
        handleDelete,
        isAdmin,
        filteredSkills,
        stats,
        editingSkill,
        formData,
        setFormData,
        showAdvanced,
        setShowAdvanced
    };

    return <SkillsPageContent context={context} />;
}