+ {/* Header */}
+
+
+ {/* Main content */}
+
+ {/* Editor / Preview (3 cols) */}
+
+ {/* Desktop split */}
+
+
+ }>
+ { setMarkdown(v); if (!editing) setEditing(true); }}
+ readOnly={!editing}
+ />
+
+
+
+ {markdown}
+
+
+ {/* Mobile tabs */}
+
+
+
+ Editor
+ Preview
+
+
+
+ }>
+ { setMarkdown(v); if (!editing) setEditing(true); }}
+ readOnly={!editing}
+ />
+
+
+
+
+
+ {markdown}
+
+
+
+
+
+
+ {/* Timeline sidebar (1 col) */}
+
+
+
Versões
+ {versions.data?.map((v) => (
+
selectVersion(v)}
+ className={`w-full text-left rounded-lg p-3 transition-colors text-sm ${
+ currentVersion?.id === v.id
+ ? "bg-primary/10 border border-primary/30"
+ : "hover:bg-muted border border-transparent"
+ }`}
+ >
+
+ v{v.version_number}
+ {v.is_live && (
+
+ Live
+
+ )}
+
+
+ {formatDistanceToNow(new Date(v.created_at), { addSuffix: true, locale: ptBR })}
+
+ {!v.is_live && currentVersion?.id !== v.id && (
+ {
+ e.stopPropagation();
+ publishVersion.mutate(v.id);
+ }}
+ >
+ Restaurar
+
+ )}
+
+ ))}
+
+
+
+
+ {/* Test panel */}
+ {testOpen && currentVersion && (
+
+ )}
+
+ {/* Archive dialog */}
+
+
+
+ Arquivar esta skill?
+
+ A skill ficará invisível para o agente. Você poderá restaurá-la depois.
+
+
+
+ Cancelar
+ archiveSkill.mutate()}>Arquivar
+
+
+
+
+ );
+}
diff --git a/src/routes/painel.skills.index.tsx b/src/routes/painel.skills.index.tsx
new file mode 100644
index 0000000..5c5f32b
--- /dev/null
+++ b/src/routes/painel.skills.index.tsx
@@ -0,0 +1,128 @@
+"use client";
+
+import { createFileRoute, Link } from "@tanstack/react-router";
+import { useState } from "react";
+import { Plus, Sparkles } from "lucide-react";
+import { useSkills } from "@/hooks/use-skills";
+import { useUserSkillLimits } from "@/hooks/use-user-skill-limits";
+import { useAgentInstance } from "@/hooks/use-agent-instance";
+import { NoSubscriptionState } from "@/components/mika/skills/NoSubscriptionState";
+import { AgentProvisioningState } from "@/components/mika/skills/AgentProvisioningState";
+import { EmptySkillsState } from "@/components/mika/skills/EmptySkillsState";
+import { SkillCard } from "@/components/mika/skills/SkillCard";
+import { Button } from "@/components/ui/button";
+import { Skeleton } from "@/components/ui/skeleton";
+import { Switch } from "@/components/ui/switch";
+import { Label } from "@/components/ui/label";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+
+export const Route = createFileRoute("/painel/skills/")({
+ component: SkillsPage,
+});
+
+function SkillsPage() {
+ const [showArchived, setShowArchived] = useState(false);
+ const limits = useUserSkillLimits();
+ const agent = useAgentInstance();
+ const skills = useSkills(showArchived);
+
+ const loading = limits.isLoading || agent.isLoading || skills.isLoading;
+ const noSub = !limits.isLoading && (limits.data?.max_skills == null);
+ const agentNotReady =
+ !agent.isLoading &&
+ (!agent.data || agent.data.status === "provisioning");
+ const atLimit =
+ limits.data != null &&
+ limits.data.max_skills != null &&
+ limits.data.current_skills_count >= limits.data.max_skills;
+
+ if (loading) {
+ return (
+
+
+
+ {noSub ? (
+
+ ) : agentNotReady ? (
+
+ ) : skills.data && skills.data.length === 0 ? (
+
+ ) : (
+
+ {skills.data?.map((skill) => (
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/src/routes/painel.skills.nova.tsx b/src/routes/painel.skills.nova.tsx
new file mode 100644
index 0000000..6eabb60
--- /dev/null
+++ b/src/routes/painel.skills.nova.tsx
@@ -0,0 +1,267 @@
+"use client";
+
+import { createFileRoute, useNavigate } from "@tanstack/react-router";
+import { useState } from "react";
+import { useForm, Controller } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { ArrowLeft, Check, CircleDashed, Loader2, Sparkles, X } from "lucide-react";
+import { Link } from "@tanstack/react-router";
+import { toast } from "sonner";
+import { supabase } from "@/integrations/supabase/client";
+import { skillFormSchema, AVAILABLE_TOOLS, type SkillFormValues } from "@/lib/skill-schema";
+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 { Badge } from "@/components/ui/badge";
+import { cn } from "@/lib/utils";
+
+export const Route = createFileRoute("/painel/skills/nova")({
+ component: NovaSkillPage,
+});
+
+const FIELDS_META: { key: keyof SkillFormValues; label: string; required: boolean }[] = [
+ { key: "name", label: "Nome", required: true },
+ { key: "description", label: "Descrição", required: true },
+ { key: "trigger_keywords", label: "Gatilhos", required: true },
+ { key: "expected_inputs", label: "Inputs esperados", required: false },
+ { key: "steps", label: "Passo a passo", required: true },
+ { key: "required_tools", label: "Ferramentas", required: true },
+ { key: "success_criteria", label: "Critério de sucesso", required: true },
+ { key: "example_use_case", label: "Exemplo de uso", required: false },
+];
+
+function NovaSkillPage() {
+ const navigate = useNavigate();
+ const [generating, setGenerating] = useState(false);
+
+ const form = useForm