diff --git a/src/app/(dashboard)/dashboard/omni-skills/page.tsx b/src/app/(dashboard)/dashboard/omni-skills/page.tsx new file mode 100644 index 0000000000..6312b3aacc --- /dev/null +++ b/src/app/(dashboard)/dashboard/omni-skills/page.tsx @@ -0,0 +1,5 @@ +import { OmniSkillsPageClient } from "./OmniSkillsPageClient"; + +export default function Page() { + return ; +} diff --git a/src/app/(dashboard)/dashboard/skills/page.tsx b/src/app/(dashboard)/dashboard/skills/page.tsx deleted file mode 100644 index 9aa0608cc6..0000000000 --- a/src/app/(dashboard)/dashboard/skills/page.tsx +++ /dev/null @@ -1,871 +0,0 @@ -"use client"; - -import { useState, useEffect, useRef } from "react"; -import { Card } from "@/shared/components"; -import { useTranslations } from "next-intl"; -import type { SkillsProvider } from "@/lib/skills/providerSettings"; - -interface Skill { - id: string; - name: string; - version: string; - description: string; - enabled: boolean; - mode?: "on" | "off" | "auto"; - sourceProvider?: "skillsmp" | "skillssh" | "local"; - tags?: string[]; - installCount?: number; - createdAt: string; -} - -interface Execution { - id: string; - skillId: string; - skillName: string; - status: string; - duration: number; - createdAt: string; -} - -export default function SkillsPage() { - const [skills, setSkills] = useState([]); - const [executions, setExecutions] = useState([]); - const [loading, setLoading] = useState(true); - const [skillsPage, setSkillsPage] = useState(1); - const [skillsTotal, setSkillsTotal] = useState(0); - const [skillsTotalPages, setSkillsTotalPages] = useState(1); - const [popularDefaults, setPopularDefaults] = useState([]); - const [searchTerm, setSearchTerm] = useState(""); - const [modeFilter, setModeFilter] = useState<"all" | "on" | "off" | "auto">("all"); - - const [execPage, setExecPage] = useState(1); - const [execTotal, setExecTotal] = useState(0); - const [execTotalPages, setExecTotalPages] = useState(1); - - const [activeTab, setActiveTab] = useState<"skills" | "executions" | "sandbox" | "marketplace">( - "skills" - ); - const [showInstallModal, setShowInstallModal] = useState(false); - const [installJson, setInstallJson] = useState(""); - const [installStatus, setInstallStatus] = useState<{ - type: "success" | "error"; - message: string; - } | null>(null); - const [installing, setInstalling] = useState(false); - const fileInputRef = useRef(null); - const [mpQuery, setMpQuery] = useState(""); - const [mpResults, setMpResults] = useState< - { - name: string; - description: string; - skillMdContent?: string; - version?: string; - sourceUrl?: string; - }[] - >([]); - const [mpLoading, setMpLoading] = useState(false); - const [mpError, setMpError] = useState(""); - const [mpInstallingId, setMpInstallingId] = useState(null); - const [shQuery, setShQuery] = useState(""); - const [shResults, setShResults] = useState< - { id: string; skillId: string; name: string; installs: number; source: string }[] - >([]); - const [shLoading, setShLoading] = useState(false); - const [shError, setShError] = useState(""); - const [shInstallingId, setShInstallingId] = useState(null); - const [skillsProvider, setSkillsProvider] = useState("skillsmp"); - const t = useTranslations("skills"); - const commonT = useTranslations("common"); - - const fetchSkills = async (page: number) => { - const params = new URLSearchParams({ page: String(page), limit: "20" }); - if (searchTerm.trim()) params.set("q", searchTerm.trim()); - if (modeFilter !== "all") params.set("mode", modeFilter); - - const res = await fetch(`/api/skills?${params.toString()}`).then((r) => r.json()); - setSkills(res.data || []); - setSkillsTotal(res.total || 0); - setSkillsTotalPages(res.totalPages || 1); - setPopularDefaults(Array.isArray(res.popularDefaults) ? res.popularDefaults : []); - }; - - const fetchExecutions = async (page: number) => { - const res = await fetch(`/api/skills/executions?page=${page}&limit=20`).then((r) => r.json()); - setExecutions(res.data || []); - setExecTotal(res.total || 0); - setExecTotalPages(res.totalPages || 1); - }; - - useEffect(() => { - Promise.all([ - fetch("/api/skills?page=1&limit=20").then((r) => r.json()), - fetch("/api/skills/executions?page=1&limit=20").then((r) => r.json()), - fetch("/api/settings").then((r) => (r.ok ? r.json() : null)), - ]) - .then(([skillsData, executionsData, settingsData]) => { - setSkills(skillsData.data || []); - setSkillsTotal(skillsData.total || 0); - setSkillsTotalPages(skillsData.totalPages || 1); - setPopularDefaults( - Array.isArray(skillsData.popularDefaults) ? skillsData.popularDefaults : [] - ); - - setExecutions(executionsData.data || []); - setExecTotal(executionsData.total || 0); - setExecTotalPages(executionsData.totalPages || 1); - - if ( - settingsData?.skillsProvider === "skillsmp" || - settingsData?.skillsProvider === "skillssh" - ) { - setSkillsProvider(settingsData.skillsProvider); - } - - setLoading(false); - }) - .catch(() => setLoading(false)); - }, []); - - const refreshSkills = async () => { - setSkillsPage(1); - await fetchSkills(1); - }; - - const toggleSkill = async (skillId: string, enabled: boolean) => { - await fetch(`/api/skills/${skillId}`, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ enabled: !enabled }), - }); - setSkills(skills.map((s) => (s.id === skillId ? { ...s, enabled: !enabled } : s))); - }; - - const setSkillMode = async (skillId: string, mode: "on" | "off" | "auto") => { - await fetch(`/api/skills/${skillId}`, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ mode }), - }); - - setSkills(skills.map((s) => (s.id === skillId ? { ...s, mode, enabled: mode !== "off" } : s))); - }; - - const deleteSkill = async (skillId: string) => { - const res = await fetch(`/api/skills/${skillId}`, { method: "DELETE" }); - if (res.ok) { - setSkills(skills.filter((s) => s.id !== skillId)); - } - }; - - const handleInstall = async () => { - setInstalling(true); - setInstallStatus(null); - try { - const manifest = JSON.parse(installJson); - const res = await fetch("/api/skills/install", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(manifest), - }); - const data = await res.json(); - if (res.ok && data.success) { - setInstallStatus({ type: "success", message: t("installSuccess", { id: data.id }) }); - setInstallJson(""); - await refreshSkills(); - } else { - setInstallStatus({ - type: "error", - message: data.error || data.message || t("installError"), - }); - } - } catch (err) { - setInstallStatus({ - type: "error", - message: err instanceof Error ? err.message : t("invalidJson"), - }); - } finally { - setInstalling(false); - } - }; - - const handleFileUpload = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; - const reader = new FileReader(); - reader.onload = (ev) => { - setInstallJson((ev.target?.result as string) || ""); - }; - reader.readAsText(file); - }; - - const searchMarketplace = async () => { - setMpLoading(true); - setMpError(""); - setMpResults([]); - try { - const res = await fetch(`/api/skills/marketplace?q=${encodeURIComponent(mpQuery)}`); - const data = await res.json(); - if (!res.ok) { - setMpError(data.error || t("marketplaceError")); - } else { - setMpResults(Array.isArray(data) ? data : data.skills || []); - } - } catch (err) { - setMpError(err instanceof Error ? err.message : t("marketplaceError")); - } finally { - setMpLoading(false); - } - }; - - const installFromMarketplace = async (skill: { - name: string; - description: string; - skillMdContent?: string; - version?: string; - sourceUrl?: string; - }) => { - setMpInstallingId(skill.name); - try { - const res = await fetch("/api/skills/marketplace/install", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - name: skill.name, - description: skill.description, - skillMdContent: skill.skillMdContent || skill.description, - version: skill.version || "1.0.0", - sourceUrl: skill.sourceUrl, - }), - }); - const data = await res.json(); - if (res.ok && data.success) { - await refreshSkills(); - setMpInstallingId(null); - } else { - setMpError(data.error || t("installError")); - setMpInstallingId(null); - } - } catch (err) { - setMpError(err instanceof Error ? err.message : t("installError")); - setMpInstallingId(null); - } - }; - - const searchSkillsSh = async () => { - setShLoading(true); - setShError(""); - setShResults([]); - try { - const res = await fetch(`/api/skills/skillssh?q=${encodeURIComponent(shQuery)}`); - const data = await res.json(); - if (!res.ok) { - setShError(data.error || t("marketplaceError")); - } else { - setShResults(data.skills || []); - } - } catch (err) { - setShError(err instanceof Error ? err.message : t("marketplaceError")); - } finally { - setShLoading(false); - } - }; - - const installFromSkillsSh = async (skill: { - id: string; - skillId: string; - name: string; - installs: number; - source: string; - }) => { - setShInstallingId(skill.id); - try { - const res = await fetch("/api/skills/skillssh/install", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - name: skill.name, - description: `Installed from skills.sh (${skill.source})`, - source: skill.source, - skillId: skill.skillId, - }), - }); - const data = await res.json(); - if (res.ok && data.success) { - await refreshSkills(); - setShInstallingId(null); - } else { - setShError(data.error || t("installError")); - setShInstallingId(null); - } - } catch (err) { - setShError(err instanceof Error ? err.message : t("installError")); - setShInstallingId(null); - } - }; - - if (loading) { - return ( -
-
{t("loading")}...
-
- ); - } - - // ── Stats computation ──────────────────────────────────────────────────── - - const enabledCount = skills.filter((s) => s.enabled).length; - const execSuccessCount = executions.filter((e) => e.status === "success").length; - const successRate = - executions.length > 0 ? Math.round((execSuccessCount / executions.length) * 100) : 0; - - return ( -
- {/* ── Stats Cards ─────────────────────────────────────────────────── */} -
- -

{t("totalSkills")}

-

{skillsTotal}

-
- -

{t("enabledSkills")}

-

{enabledCount}

-
- -

{t("totalExecutions")}

-

{execTotal}

-
- -

{t("successRate")}

-

{successRate}%

-
-
- -
- -
- -
- - - - -
- - {activeTab === "skills" && ( -
- -
- setSearchTerm(e.target.value)} - placeholder={t("filterSkillsPlaceholder")} - className="px-3 py-2 rounded-lg bg-background border border-border text-sm focus:outline-none focus:ring-1 focus:ring-violet-500" - /> - - -
- - {popularDefaults.length > 0 && ( -
-

{t("popularDefaultsLabel")}

-
- {popularDefaults.map((name) => ( - - {name} - - ))} -
-
- )} -
- - {skills.length === 0 ? ( - -
{t("noSkills")}
-
- ) : ( - skills.map((skill) => ( - -
-
-
-

{skill.name}

- - v{skill.version} - - - {(skill.sourceProvider || "local").toUpperCase()} - - - {t("mode")}: {skill.mode || (skill.enabled ? "on" : "off")} - -
-

{skill.description}

- {Array.isArray(skill.tags) && skill.tags.length > 0 && ( -
- {skill.tags.map((tag) => ( - - {tag} - - ))} -
- )} -
-
-
- - - -
- - -
-
-
- )) - )} -
- - {t("pageInfo", { - page: skillsPage, - totalPages: skillsTotalPages, - total: skillsTotal, - })} - -
- - -
-
-
- )} - - {activeTab === "executions" && ( - -
- - - - - - - - - - - {executions.length === 0 ? ( - - - - ) : ( - executions.map((exec) => ( - - - - - - - )) - )} - -
{t("skill")}{t("status")}{t("duration")}{t("time")}
- {t("noExecutions")} -
{exec.skillName} - - {exec.status} - - {exec.duration}ms - {new Date(exec.createdAt).toLocaleString()} -
-
-
- - {t("pageInfo", { page: execPage, totalPages: execTotalPages, total: execTotal }) || - `Page ${execPage} of ${execTotalPages} (${execTotal} total)`} - -
- - -
-
-
- )} - - {activeTab === "sandbox" && ( -
- -

{t("sandboxConfig")}

-
-
-
-

{t("cpuLimit")}

-

{t("cpuLimitDesc")}

-
- 100ms -
-
-
-

{t("memoryLimit")}

-

{t("memoryLimitDesc")}

-
- 256MB -
-
-
-

{t("timeout")}

-

{t("timeoutDesc")}

-
- 30s -
-
-
-

{t("networkAccess")}

-

{t("networkAccessDesc")}

-
- {t("disabled")} -
-
-
-
- )} - - {activeTab === "marketplace" && ( -
- -

{t("skillsMarketplace")}

-

- {t("activeProvider")}{" "} - - {skillsProvider === "skillsmp" ? "SkillsMP" : "skills.sh"} - - . {t("changeInSettings")} -

-
- - skillsProvider === "skillsmp" - ? setMpQuery(e.target.value) - : setShQuery(e.target.value) - } - onKeyDown={(e) => - e.key === "Enter" && - (skillsProvider === "skillsmp" ? searchMarketplace() : searchSkillsSh()) - } - placeholder={t("searchMarketplacePlaceholder")} - className="flex-1 px-3 py-2 rounded-lg bg-background border border-border text-sm focus:outline-none focus:ring-1 focus:ring-violet-500" - /> - -
- {(skillsProvider === "skillsmp" ? mpError : shError) && ( -
- {skillsProvider === "skillsmp" ? mpError : shError} -
- )} -
- - {skillsProvider === "skillsmp" && mpResults.length > 0 && ( -
- {mpResults.map((skill) => ( - -
-
-

{skill.name}

-

{skill.description}

-
- -
-
- ))} -
- )} - - {skillsProvider === "skillssh" && shResults.length > 0 && ( -
- {shResults.map((skill) => ( - -
-
-

{skill.name}

-

- {skill.source} · {skill.installs.toLocaleString()} {t("installs")} -

-
- -
-
- ))} -
- )} - - {skillsProvider === "skillsmp" && !mpLoading && mpResults.length === 0 && !mpError && ( - -
{t("marketplaceSkillsMpHint")}
-
- )} - {skillsProvider === "skillssh" && !shLoading && shResults.length === 0 && !shError && ( - -
{t("marketplaceSkillsShHint")}
-
- )} -
- )} - - {showInstallModal && ( -
-
-
-

{t("installSkillModalTitle")}

- -
-

{t("installSkillModalDesc")}

-