277 lines
15 KiB
JavaScript
277 lines
15 KiB
JavaScript
import React, { useMemo } from 'react';
|
||
import { Link } from 'react-router-dom';
|
||
import {
|
||
LayoutTemplate, Sparkles,
|
||
BookOpen, ArrowRight
|
||
} from 'lucide-react';
|
||
import { usePreferences } from '@/lib/krowHooks';
|
||
import { aiAgentSkills, allSkills, skillsWithFacet } from '@/lib/skills/registry';
|
||
import { allAgents } from '@/lib/agents/registry';
|
||
import { AdminPage } from '@/components/admin/PageShell';
|
||
import OwliverAvatar from '@/components/krow/OwliverAvatar';
|
||
import { cn } from '@/lib/utils';
|
||
|
||
/**
|
||
* Workspace & Skills — AI agent capability governance.
|
||
*
|
||
* This page governs what *Owliver* can do. It used to govern workforce training
|
||
* as well — Training Paths and Skill Progression sat in the right-hand column —
|
||
* and that conflated two unrelated things under one word. A "skill" here is a
|
||
* capability the assistant gains; a "skill" in KROW Forge is something a person
|
||
* learns, proves and is verified in. They have different owners, different
|
||
* lifecycles and different audiences, and putting them side by side made the page
|
||
* read as though enabling a Bartending path taught Owliver to tend bar.
|
||
*
|
||
* Workforce training was not removed, only returned to where it belongs: the
|
||
* definitions, the routes and the pages are untouched, and KROW Forge
|
||
* (`/admin/university`) and Skill Development still read them. See
|
||
* `workforceTrainingSkills` in the registry for the other half of the split.
|
||
*/
|
||
|
||
export default function AdminWorkspace() {
|
||
const preferences = usePreferences();
|
||
/* Read through the agent registry, never by counting skills — one registry
|
||
answers "how many agents", exactly as the skill registry answers its own. */
|
||
const agentCount = useMemo(
|
||
() => allAgents(preferences.customAgents || [], { customSkills: preferences.customSkills || [] }).length,
|
||
[preferences.customAgents, preferences.customSkills]
|
||
);
|
||
|
||
/**
|
||
* AI agent skills only.
|
||
*
|
||
* Deliberately `aiAgentSkills(...)` rather than `allSkills(...)`. The registry
|
||
* holds both kinds, so the unfiltered read counted five workforce training
|
||
* paths among Owliver's capabilities — the hero said ten registered skills when
|
||
* the assistant had five. Naming the kind is also what stops a training path
|
||
* added later from reappearing here on its own.
|
||
*/
|
||
const skills = useMemo(
|
||
() => aiAgentSkills(allSkills(preferences.customSkills || [])),
|
||
[preferences.customSkills]
|
||
);
|
||
const disabledSkills = preferences.disabledSkills || [];
|
||
const activeSkills = skills.filter((s) => !disabledSkills.includes(s.id)).length;
|
||
|
||
/* The two kinds of agent skill — the same two lists the management page is
|
||
split into, read here from the same filtered registry so the landing page and
|
||
the manager can never disagree about what is registered. */
|
||
const owliverSkills = useMemo(() => skillsWithFacet(skills, 'owliver'), [skills]);
|
||
const boardSkills = useMemo(() => skillsWithFacet(skills, 'ui'), [skills]);
|
||
const owliverCount = owliverSkills.length;
|
||
const boardCount = boardSkills.length;
|
||
|
||
return (
|
||
<AdminPage
|
||
title="Workspace"
|
||
subtitle="Govern the AI agent capabilities that extend Owliver and KROW pages."
|
||
>
|
||
<div className="space-y-6 w-full">
|
||
{/* ── 1. Hero Banner (Full Width) ───────────────────────────────── */}
|
||
<div className="relative overflow-hidden rounded-3xl bg-gradient-to-r from-[#0838E0] via-[#1E40AF] to-[#0284C7] p-6 sm:p-8 text-white shadow-xl w-full">
|
||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-white/15 via-transparent to-transparent pointer-events-none" />
|
||
<div className="relative z-10 flex flex-col md:flex-row md:items-center justify-between gap-6">
|
||
<div className="space-y-2">
|
||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/20 backdrop-blur-md text-white text-[12px] font-semibold border border-white/30">
|
||
<Sparkles className="w-3.5 h-3.5 text-sky-300" />
|
||
AI Capability Governance
|
||
</div>
|
||
<h1 className="text-2xl sm:text-3xl font-bold tracking-tight text-white">AI Agent Capabilities</h1>
|
||
<p className="text-[14px] text-blue-100 font-normal max-w-2xl">
|
||
Govern what Owliver can understand, analyze and execute. Workforce training
|
||
paths and talent progression are managed in KROW Forge.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="flex flex-wrap items-center gap-3 sm:flex-nowrap sm:shrink-0">
|
||
<div className="flex-1 sm:flex-none bg-white/10 backdrop-blur-md rounded-2xl p-4 border border-white/15 min-w-[130px] text-center">
|
||
<p className="text-[11px] uppercase tracking-wider text-blue-200 font-semibold">Registered Skills</p>
|
||
<p className="text-2xl font-bold text-white mt-0.5">{skills.length}</p>
|
||
<span className="text-[10px] text-emerald-300 font-medium">{activeSkills} Active</span>
|
||
</div>
|
||
{/* Was "Training Paths". Replaced rather than dropped: the slot is
|
||
useful, and the split of agent skills across the two lists is
|
||
what this page actually governs. */}
|
||
<div className="flex-1 sm:flex-none bg-white/10 backdrop-blur-md rounded-2xl p-4 border border-white/15 min-w-[130px] text-center">
|
||
<p className="text-[11px] uppercase tracking-wider text-blue-200 font-semibold">Skill Kinds</p>
|
||
<p className="text-2xl font-bold text-sky-300 mt-0.5">{owliverCount + boardCount}</p>
|
||
<span className="text-[10px] text-blue-200">{owliverCount} Owliver · {boardCount} Board</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── Agents ─────────────────────────────────────────────────────
|
||
Above the two skill columns because it is the layer over them: an
|
||
agent decides which of these skills Owliver may use on a page. */}
|
||
<div className="bg-white rounded-2xl border border-[#E0E4EA] p-6 shadow-xs space-y-4">
|
||
<div className="flex items-center justify-between border-b border-[#E0E4EA] pb-3">
|
||
<div className="flex items-center gap-2">
|
||
<OwliverAvatar className="w-6 h-6" rounded="rounded-md" />
|
||
<h2 className="text-[16px] font-bold text-[#101820]">Owliver Agents</h2>
|
||
</div>
|
||
<span className="text-[12px] font-semibold text-[#0838E0] bg-blue-50 px-3 py-1 rounded-full border border-blue-200/60">
|
||
{agentCount} Registered
|
||
</span>
|
||
</div>
|
||
|
||
<p className="text-[13px] text-[#687386]">
|
||
An agent is how Owliver answers on a page: which skills it may use, what it knows, and
|
||
how much work an answer is worth. Agents narrow what a page offers — they never widen it.
|
||
</p>
|
||
|
||
<Link
|
||
to="/admin/workspace/agents"
|
||
className="w-full inline-flex items-center justify-center gap-2 px-5 py-3 rounded-xl bg-[#0838E0] hover:bg-[#062BAF] text-white text-[13px] font-semibold transition-all shadow-md shadow-blue-500/20 hover:scale-[1.01]"
|
||
>
|
||
<Sparkles className="w-4 h-4" />
|
||
Manage Agents
|
||
<ArrowRight className="w-4 h-4" />
|
||
</Link>
|
||
</div>
|
||
|
||
{/* ── 2. Full Width 2-Column Grid ───────────────────────────────── */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 w-full">
|
||
{/* ── Left Column: Owliver AI Capabilities ───────────────────── */}
|
||
<div className="space-y-6">
|
||
<div className="bg-white rounded-2xl border border-[#E0E4EA] p-6 shadow-xs space-y-5">
|
||
<div className="flex items-center justify-between border-b border-[#E0E4EA] pb-3">
|
||
<div className="flex items-center gap-2">
|
||
<OwliverAvatar className="w-6 h-6" rounded="rounded-md" />
|
||
<h2 className="text-[16px] font-bold text-[#101820]">Owliver Skills</h2>
|
||
</div>
|
||
<span className="text-[12px] font-semibold text-[#0838E0] bg-blue-50 px-3 py-1 rounded-full border border-blue-200/60">
|
||
{owliverCount} Registered
|
||
</span>
|
||
</div>
|
||
|
||
<p className="text-[13px] text-[#687386]">
|
||
Owliver skills teach the assistant what it can be asked on a page and how to answer —
|
||
screening, matching and contextual analysis. They define what the AI understands, not
|
||
what a candidate learns.
|
||
</p>
|
||
|
||
{/* Skills Sample List */}
|
||
<div className="space-y-3">
|
||
{owliverSkills.slice(0, 4).map((s) => {
|
||
const isActive = !disabledSkills.includes(s.id);
|
||
return (
|
||
<div key={s.id} className="p-3.5 rounded-xl border border-[#E0E4EA] bg-[#F8F8F8]/50 flex items-center justify-between gap-3">
|
||
<div className="min-w-0 flex-1">
|
||
<p className="text-[13px] font-bold text-[#101820]">{s.name || s.id}</p>
|
||
{s.description && <p className="text-[12px] text-[#687386] truncate mt-0.5">{s.description}</p>}
|
||
</div>
|
||
<span className={cn(
|
||
'px-2.5 py-0.5 rounded-full text-[11px] font-semibold shrink-0',
|
||
isActive ? 'bg-emerald-50 text-[#16A765] border border-emerald-200' : 'bg-gray-100 text-gray-500'
|
||
)}>
|
||
{isActive ? 'Active' : 'Disabled'}
|
||
</span>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
{/* CTA Button */}
|
||
<div className="pt-2">
|
||
<Link
|
||
to="/admin/workspace/skills?tab=owliver"
|
||
className="w-full inline-flex items-center justify-center gap-2 px-5 py-3 rounded-xl bg-[#0838E0] hover:bg-[#062BAF] text-white text-[13px] font-semibold transition-all shadow-md shadow-blue-500/20 hover:scale-[1.01]"
|
||
>
|
||
<Sparkles className="w-4 h-4" />
|
||
Manage Owliver Skills
|
||
<ArrowRight className="w-4 h-4" />
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── Right Column: Board Skills ─────────────────────────────────
|
||
The other kind of AI agent skill. This column previously held
|
||
Workforce Training Paths and Skill Progression Tracking, both of
|
||
which are KROW Forge concerns — see the note at the top of the file.
|
||
Board skills belong here because, like Owliver skills, they are
|
||
capabilities the product gains rather than something a person
|
||
learns. */}
|
||
<div className="space-y-6">
|
||
<div className="bg-white rounded-2xl border border-[#E0E4EA] p-6 shadow-xs space-y-5">
|
||
<div className="flex items-center justify-between border-b border-[#E0E4EA] pb-3">
|
||
<div className="flex items-center gap-2">
|
||
<LayoutTemplate className="w-5 h-5 text-[#0838E0]" />
|
||
<h2 className="text-[16px] font-bold text-[#101820]">Board Skills</h2>
|
||
</div>
|
||
<span className="text-[12px] font-semibold text-[#0838E0] bg-blue-50 px-3 py-1 rounded-full border border-blue-200/60">
|
||
{boardCount} Registered
|
||
</span>
|
||
</div>
|
||
|
||
<p className="text-[13px] text-[#687386]">
|
||
Board skills add dynamic sections to KROW pages, read from real page data. They
|
||
extend what a page shows, not what a candidate learns.
|
||
</p>
|
||
|
||
{/* Sample list, from the same filtered registry read as the left
|
||
column — so the two columns can never disagree about what is
|
||
registered. */}
|
||
<div className="space-y-3">
|
||
{boardSkills.slice(0, 4).map((s) => {
|
||
const isActive = !disabledSkills.includes(s.id);
|
||
return (
|
||
<div key={s.id} className="p-3.5 rounded-xl border border-[#E0E4EA] bg-[#F8F8F8]/50 flex items-center justify-between gap-3">
|
||
<div className="min-w-0 flex-1">
|
||
<p className="text-[13px] font-bold text-[#101820]">{s.name || s.id}</p>
|
||
{s.description && <p className="text-[12px] text-[#687386] truncate mt-0.5">{s.description}</p>}
|
||
</div>
|
||
<span className={cn(
|
||
'px-2.5 py-0.5 rounded-full text-[11px] font-semibold shrink-0',
|
||
isActive ? 'bg-emerald-50 text-[#16A765] border border-emerald-200' : 'bg-gray-100 text-gray-500'
|
||
)}>
|
||
{isActive ? 'Active' : 'Disabled'}
|
||
</span>
|
||
</div>
|
||
);
|
||
})}
|
||
|
||
{boardSkills.length === 0 && (
|
||
<p className="text-[13px] text-[#687386] py-2">
|
||
No Board skills registered yet. A Board skill declares a section, where it
|
||
sits and what it reads.
|
||
</p>
|
||
)}
|
||
</div>
|
||
|
||
<div className="pt-2">
|
||
<Link
|
||
to="/admin/workspace/skills?tab=ui"
|
||
className="w-full inline-flex items-center justify-center gap-2 px-5 py-3 rounded-xl border border-[#E0E4EA] bg-white hover:bg-[#F8F8F8] text-[#101820] text-[13px] font-semibold transition-all shadow-xs"
|
||
>
|
||
<LayoutTemplate className="w-4 h-4 text-[#0838E0]" />
|
||
Manage Board Skills
|
||
<ArrowRight className="w-4 h-4" />
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── 3. Unified Registry Architecture Banner ───────────────────── */}
|
||
<div className="flex items-start gap-3.5 p-5 rounded-2xl bg-blue-50/60 border border-blue-200/50 text-[#0838E0] text-[13px] leading-relaxed">
|
||
<BookOpen className="w-5 h-5 shrink-0 text-[#0838E0] mt-0.5" />
|
||
<div>
|
||
<p className="font-bold text-[#101820]">AI skills and workforce training are different things</p>
|
||
<p className="text-[#687386] mt-0.5">
|
||
This page governs <span className="font-semibold">AI agent skills</span> — what Owliver
|
||
understands and what a Board skill draws on a page. <span className="font-semibold">Workforce
|
||
training</span> — training paths, proving challenges, skill verification and talent
|
||
progression — is what a person learns, and it is managed in{' '}
|
||
<Link to="/admin/university" className="font-semibold underline hover:no-underline">KROW Forge</Link>.
|
||
Both are Markdown in one registry, read through separate queries so neither appears in the
|
||
other’s surface.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</AdminPage>
|
||
);
|
||
}
|