feat(04-02): adicionar tab Sugestões (OpenClaw) em /skills com badge contador

- Tab "Sugestões" na view switcher da página /skills
- Renderiza OpenClawPanel com lista de skills emergentes pendentes
- Badge amarelo mostra contagem de sugestões pendentes (atualiza a cada 5min)
- Query /api/openclaw/suggestions já conectada ao PatternDetector backend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jonas Pacheco 2026-03-26 16:54:57 -03:00
parent d4af26e59d
commit 9165ee4c48
2 changed files with 28 additions and 2 deletions

View File

@ -6,9 +6,10 @@
- Phase 1: submodules, tabelas, Neo4j, ReferenceParser
- Phase 2: SkillEngine, API REST, Monaco Editor, /skills, autocomplete, Marketplace, versionamento Git-like
- Phase 3: miroflow_service.py, bridge TS + KG logging, MiroFlowControl.tsx + tab Científico
- Phase 4: PatternDetector (cron 1h), OpenClaw routes (suggestions/patterns/accept/reject), tab Sugestões em /skills + badge contador
## In Progress
- (nenhum — iniciar Phase 4)
- (nenhum — Phase 4 concluída, iniciar Phase 5)
## Roadmap Evolution
- Phase 7 added: Skill Fabric Expandido (compiladores, sandbox, 3 editor modes, validation pipeline)

View File

@ -1,4 +1,5 @@
import { BrowserFrame } from "@/components/Browser/BrowserFrame";
import OpenClawPanel from "@/components/OpenClawPanel";
import { useState, useRef, useEffect } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Button } from "@/components/ui/button";
@ -143,7 +144,7 @@ export default function Skills() {
const [historyOpen, setHistoryOpen] = useState(false);
const [historySkill, setHistorySkill] = useState<Skill | null>(null);
const [view, setView] = useState<"minhas" | "marketplace">("minhas");
const [view, setView] = useState<"minhas" | "marketplace" | "sugestoes">("minhas");
const [mktSearch, setMktSearch] = useState("");
const [mktTag, setMktTag] = useState("all");
const [importedId, setImportedId] = useState<string | null>(null);
@ -187,6 +188,12 @@ export default function Skills() {
enabled: view === "marketplace",
});
const { data: sugData } = useQuery<{ suggestions: unknown[]; total: number }>({
queryKey: ["/api/openclaw/suggestions"],
refetchInterval: 5 * 60 * 1000,
});
const pendingSuggestionsCount = sugData?.total ?? 0;
// ── Mutations ──────────────────────────────────────────────────────────────
const saveMutation = useMutation({
@ -416,6 +423,17 @@ export default function Skills() {
>
<Store className="w-3.5 h-3.5" /> Biblioteca
</button>
<button
onClick={() => setView("sugestoes")}
className={`px-3 py-1.5 text-xs flex items-center gap-1.5 transition-colors relative ${view === "sugestoes" ? "bg-[#c89b3c] text-black font-medium" : "text-white/60 hover:text-white hover:bg-white/5"}`}
>
<Sparkles className="w-3.5 h-3.5" /> Sugestões
{pendingSuggestionsCount > 0 && view !== "sugestoes" && (
<span className="absolute -top-1 -right-1 w-4 h-4 rounded-full bg-yellow-400 text-black text-[9px] font-bold flex items-center justify-center">
{pendingSuggestionsCount}
</span>
)}
</button>
</div>
{view === "minhas" && (
<Button size="sm" onClick={openNew} className="bg-[#c89b3c] hover:bg-[#d4a94a] text-black">
@ -669,6 +687,13 @@ export default function Skills() {
)}
</ScrollArea>
</>)}
{/* ── VIEW: SUGESTÕES (OpenClaw) ──────────────────────────────────────── */}
{view === "sugestoes" && (
<div className="flex-1 min-h-0">
<OpenClawPanel />
</div>
)}
</div>
{/* ── Edit / Create Dialog ────────────────────────────────────────────── */}