feat: Skills Dashboard, CI Build Automation, Auto-Sync & CI-Ampel MCP
Ampel / ampel (push) Failing after 22s

This commit is contained in:
Hitonabi
2026-08-07 16:08:49 +02:00
parent 1b0184eed8
commit da03de18cb
142 changed files with 267 additions and 816 deletions
+2
View File
@@ -20,6 +20,7 @@ const KonsoleView = lazy(() => import("@/views/KonsoleView").then((m) => ({ defa
const GuideView = lazy(() => import("@/views/GuideView").then((m) => ({ default: m.GuideView })))
const AuftragsbuchView = lazy(() => import("@/views/AuftragsbuchView").then((m) => ({ default: m.AuftragsbuchView })))
const IdeenView = lazy(() => import("@/views/IdeenView").then((m) => ({ default: m.IdeenView })))
const SkillsView = lazy(() => import("@/views/SkillsView").then((m) => ({ default: m.SkillsView })))
const ChronikView = lazy(() => import("@/views/ChronikView").then((m) => ({ default: m.ChronikView })))
const WissenView = lazy(() => import("@/views/WissenView").then((m) => ({ default: m.WissenView })))
@@ -203,6 +204,7 @@ export default function App() {
{view === "dashboard" && <CockpitView onNavigate={(v) => navigate(v as ViewId)} />}
{view === "ideen" && <IdeenView />}
{view === "auftraege" && <AuftragsbuchView />}
{view === "skills" && <SkillsView />}
{view === "models" && <ModelsView />}
{view === "connect" && <ConnectView />}
{view === "wissen" && <WissenView />}
+2 -2
View File
@@ -12,7 +12,7 @@ import {
type LucideIcon,
} from "lucide-react"
export type ViewId = "dashboard" | "ideen" | "auftraege" | "models" | "wissen" | "chronik" | "connect" | "agent" | "konsole" | "guide"
export type ViewId = "dashboard" | "ideen" | "auftraege" | "skills" | "models" | "wissen" | "chronik" | "connect" | "agent" | "konsole" | "guide"
export type NavGroup = "Operativ" | "Wissen" | "Werkzeuge" | "Wartung"
@@ -33,7 +33,7 @@ export const NAV: NavItem[] = [
// Seitenüberschrift „Ideen" — ein langer Satz stand dort dreifach gedoppelt.
{ id: "ideen", label: "Ideen", hint: "Schreib hin, was entstehen soll", icon: Lightbulb, group: "Operativ" },
{ id: "auftraege", label: "Auftragsbuch", hint: "Vorschläge der Box — annehmen oder ablehnen", icon: Inbox, group: "Operativ" },
{ id: "skills", label: "Skills & Jobs", hint: "Autonome Skills manuell auslösen", icon: Bot, group: "Operativ" },
{ id: "chronik", label: "Chronik", hint: "Was die Box von allein getan hat + Zeitmaschine", icon: History, group: "Operativ" },
{ id: "wissen", label: "Wissen", hint: "Lucys Wissens-Vault (Traum-Notizen)", icon: Library, group: "Wissen" },
{ id: "connect", label: "Verbinden", hint: "IDE-/Agent-Configs erzeugen", icon: Plug, group: "Werkzeuge" },
+127
View File
@@ -0,0 +1,127 @@
import { useState } from "react"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import { Bot, Play, Loader2, CheckCircle2, AlertTriangle } from "lucide-react"
import { cn } from "@/lib/utils"
import { api } from "@/lib/api"
import { qk } from "@/lib/queries"
import { JobsBar } from "@/components/models/JobsBar"
interface Skill {
name: string
description: string
}
interface SkillsResp {
skills: Skill[]
}
export function SkillsView() {
const queryClient = useQueryClient()
const { data, isLoading, isError } = useQuery({
queryKey: ["skills-list"],
queryFn: () => api<SkillsResp>("/api/skills"),
refetchInterval: 60000,
})
const [runningSkill, setRunningSkill] = useState<string | null>(null)
const [lastResult, setLastResult] = useState<{name: string, ok: boolean, msg: string} | null>(null)
const mutation = useMutation({
mutationFn: (skillName: string) => api<{ok: boolean, msg?: string, err?: string}>("/api/skills/run", {
method: "POST",
body: JSON.stringify({ skill_name: skillName })
}),
onMutate: (skillName) => {
setRunningSkill(skillName)
setLastResult(null)
},
onSuccess: (res, skillName) => {
setRunningSkill(null)
setLastResult({ name: skillName, ok: res.ok, msg: res.msg || res.err || "Unbekannter Fehler" })
// Jobs neu laden, damit der gestartete Skill direkt in der JobsBar auftaucht
queryClient.invalidateQueries({ queryKey: qk.jobs })
},
onError: (err, skillName) => {
setRunningSkill(null)
setLastResult({ name: skillName, ok: false, msg: String(err) })
}
})
return (
<div className="space-y-6">
<div className="flex flex-col sm:flex-row justify-between sm:items-center gap-4">
<div>
<h1 className="text-2xl font-space font-bold tracking-tight bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text text-transparent">
Skills & Jobs
</h1>
<p className="text-sm text-muted-foreground">
Löst autonome Agent-Skills manuell auf der Box aus. Die Ausführung erfolgt im Hintergrund durch Hermes.
</p>
</div>
</div>
<JobsBar />
{lastResult && (
<div className={cn(
"p-4 rounded-lg border flex gap-3 items-start",
lastResult.ok ? "bg-emerald-500/10 border-emerald-500/20 text-emerald-600 dark:text-emerald-400" : "bg-red-500/10 border-red-500/20 text-red-600 dark:text-red-400"
)}>
{lastResult.ok ? <CheckCircle2 className="h-5 w-5 mt-0.5 shrink-0" /> : <AlertTriangle className="h-5 w-5 mt-0.5 shrink-0" />}
<div>
<div className="font-semibold text-sm">{lastResult.name}</div>
<div className="text-sm opacity-90">{lastResult.msg}</div>
</div>
</div>
)}
{isLoading && (
<div className="flex h-32 items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
)}
{isError && (
<div className="text-red-500 p-4 mc-card text-sm">
Fehler beim Laden der Skills. Ist das Backend erreichbar?
</div>
)}
{data?.skills && (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
{data.skills.map(skill => (
<div key={skill.name} className="mc-card p-5 flex flex-col hover:border-primary/40 transition-colors group">
<div className="flex items-start justify-between gap-3 mb-2">
<div className="flex items-center gap-2 font-semibold text-base font-space">
<Bot className="h-5 w-5 text-primary/80" />
{skill.name}
</div>
<button
onClick={() => mutation.mutate(skill.name)}
disabled={runningSkill === skill.name || mutation.isPending}
className={cn(
"shrink-0 h-8 px-3 rounded-md text-xs font-semibold uppercase tracking-wider flex items-center gap-1.5 transition-all cursor-pointer",
runningSkill === skill.name
? "bg-muted text-muted-foreground cursor-not-allowed"
: "bg-primary/10 text-primary hover:bg-primary hover:text-primary-foreground group-hover:shadow-md group-hover:shadow-primary/20"
)}
>
{runningSkill === skill.name ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Play className="h-3.5 w-3.5" />}
{runningSkill === skill.name ? "Startet" : "Starten"}
</button>
</div>
<div className="text-sm text-muted-foreground flex-1">
{skill.description}
</div>
</div>
))}
{data.skills.length === 0 && (
<div className="col-span-full p-8 text-center text-muted-foreground mc-card border-dashed border-2">
Keine Skills im Ordner <code className="text-xs text-foreground bg-muted px-1.5 py-0.5 rounded">deploy/skills</code> gefunden.
</div>
)}
</div>
)}
</div>
)
}