Files
krow_talent_app/src/pages/admin/Workspace.jsx
2026-08-24 13:16:15 +05:30

277 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}