184 lines
6.5 KiB
TypeScript
184 lines
6.5 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { AISkill, ApiConfig, CronJob, defaultSkills, defaultApis, defaultCronJobs } from "@/lib/ai-management/types";
|
|
|
|
const STORAGE_KEYS = {
|
|
skills: "horus:skills",
|
|
apis: "horus:apis",
|
|
crons: "horus:crons",
|
|
};
|
|
|
|
export default function AIManagement() {
|
|
const [skills, setSkills] = useState<AISkill[]>(defaultSkills);
|
|
const [apis, setApis] = useState<ApiConfig[]>(defaultApis);
|
|
const [crons, setCrons] = useState<CronJob[]>(defaultCronJobs);
|
|
const [activeTab, setActiveTab] = useState<"skills" | "apis" | "crons">("skills");
|
|
|
|
// Load from localStorage
|
|
useEffect(() => {
|
|
if (typeof window === "undefined") return;
|
|
|
|
const savedSkills = localStorage.getItem(STORAGE_KEYS.skills);
|
|
if (savedSkills) setSkills(JSON.parse(savedSkills));
|
|
|
|
const savedApis = localStorage.getItem(STORAGE_KEYS.apis);
|
|
if (savedApis) setApis(JSON.parse(savedApis));
|
|
|
|
const savedCrons = localStorage.getItem(STORAGE_KEYS.crons);
|
|
if (savedCrons) setCrons(JSON.parse(savedCrons));
|
|
}, []);
|
|
|
|
// Save changes
|
|
const toggleSkill = (id: string) => {
|
|
const updated = skills.map(s => s.id === id ? { ...s, enabled: !s.enabled } : s);
|
|
setSkills(updated);
|
|
localStorage.setItem(STORAGE_KEYS.skills, JSON.stringify(updated));
|
|
};
|
|
|
|
const toggleCron = (id: string) => {
|
|
const updated = crons.map(c => c.id === id ? { ...c, enabled: !c.enabled } : c);
|
|
setCrons(updated);
|
|
localStorage.setItem(STORAGE_KEYS.crons, JSON.stringify(updated));
|
|
};
|
|
|
|
const categoryColors: Record<string, string> = {
|
|
development: "bg-blue-500/20 text-blue-400",
|
|
research: "bg-purple-500/20 text-purple-400",
|
|
automation: "bg-green-500/20 text-green-400",
|
|
communication: "bg-yellow-500/20 text-yellow-400",
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div>
|
|
<h2 className="text-2xl font-bold">🤖 Horus AI Management</h2>
|
|
<p className="text-white/60 mt-1">Configure skills, APIs, and automation for your AI assistant</p>
|
|
</div>
|
|
|
|
{/* Tabs */}
|
|
<div className="flex gap-2">
|
|
{[
|
|
{ id: "skills", label: "Skills", icon: "🧩" },
|
|
{ id: "apis", label: "APIs", icon: "🔑" },
|
|
{ id: "crons", label: "Automation", icon: "⏰" },
|
|
].map((tab) => (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => setActiveTab(tab.id as any)}
|
|
className={`px-4 py-2 rounded-lg text-sm font-medium transition ${
|
|
activeTab === tab.id
|
|
? "bg-brand-pink text-white"
|
|
: "bg-white/10 text-white/70 hover:bg-white/20"
|
|
}`}
|
|
>
|
|
{tab.icon} {tab.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Skills Tab */}
|
|
{activeTab === "skills" && (
|
|
<div className="grid gap-3">
|
|
{skills.map((skill) => (
|
|
<div
|
|
key={skill.id}
|
|
className="flex items-center justify-between p-4 rounded-xl border border-white/10 bg-white/5"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div className={`px-2 py-1 rounded text-xs font-medium ${categoryColors[skill.category]}`}>
|
|
{skill.category}
|
|
</div>
|
|
<div>
|
|
<p className="font-medium">{skill.name}</p>
|
|
<p className="text-sm text-white/50">{skill.description}</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={() => toggleSkill(skill.id)}
|
|
className={`w-12 h-6 rounded-full transition relative ${
|
|
skill.enabled ? "bg-green-500" : "bg-white/20"
|
|
}`}
|
|
>
|
|
<span
|
|
className={`absolute top-1 w-4 h-4 rounded-full bg-white transition ${
|
|
skill.enabled ? "left-7" : "left-1"
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* APIs Tab */}
|
|
{activeTab === "apis" && (
|
|
<div className="grid gap-3">
|
|
{apis.map((api) => (
|
|
<div
|
|
key={api.id}
|
|
className="flex items-center justify-between p-4 rounded-xl border border-white/10 bg-white/5"
|
|
>
|
|
<div>
|
|
<p className="font-medium flex items-center gap-2">
|
|
{api.name}
|
|
{api.configured && <span className="text-green-400 text-xs">✓ Configured</span>}
|
|
</p>
|
|
<p className="text-sm text-white/50">{api.description}</p>
|
|
</div>
|
|
<button
|
|
className={`px-3 py-1.5 rounded-lg text-xs font-medium ${
|
|
api.configured
|
|
? "bg-green-500/20 text-green-400"
|
|
: "bg-white/10 text-white/60 hover:bg-white/20"
|
|
}`}
|
|
>
|
|
{api.configured ? "Active" : "Configure"}
|
|
</button>
|
|
</div>
|
|
))}
|
|
|
|
<div className="mt-4 p-4 rounded-xl border border-yellow-500/30 bg-yellow-500/10">
|
|
<p className="text-sm text-yellow-200">
|
|
💡 To configure APIs, give me the keys or run: <code className="bg-black/30 px-2 py-1 rounded">openclaw configure --section auth --set KEY=value</code>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Automation/Crons Tab */}
|
|
{activeTab === "crons" && (
|
|
<div className="grid gap-3">
|
|
{crons.map((cron) => (
|
|
<div
|
|
key={cron.id}
|
|
className="flex items-center justify-between p-4 rounded-xl border border-white/10 bg-white/5"
|
|
>
|
|
<div>
|
|
<p className="font-medium">{cron.name}</p>
|
|
<p className="text-sm text-white/50">
|
|
{cron.schedule}
|
|
{cron.nextRun && <span className="ml-2 text-brand-pink">Next: {cron.nextRun}</span>}
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={() => toggleCron(cron.id)}
|
|
className={`w-12 h-6 rounded-full transition relative ${
|
|
cron.enabled ? "bg-green-500" : "bg-white/20"
|
|
}`}
|
|
>
|
|
<span
|
|
className={`absolute top-1 w-4 h-4 rounded-full bg-white transition ${
|
|
cron.enabled ? "left-7" : "left-1"
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|