feat: Skills Dashboard, CI Build Automation, Auto-Sync & CI-Ampel MCP
Ampel / ampel (push) Failing after 22s
Ampel / ampel (push) Failing after 22s
This commit is contained in:
@@ -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
@@ -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" },
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user