update agents skill design

This commit is contained in:
2026-08-20 18:18:10 +05:30
parent 161b237695
commit b2e6868824
75 changed files with 14586 additions and 98 deletions

View File

@@ -0,0 +1,238 @@
import * as React from 'react';
import {
Activity,
BarChart3,
Clock,
HelpCircle,
MessageSquare,
ThumbsDown,
ThumbsUp,
Wrench,
} from 'lucide-react';
import { Badge, EmptyState } from '@/components/ds';
import { readHistory } from '@/components/ai-assistant/history';
import { conversationStats, reviewRow } from '@/lib/agents/conversationInsights';
import { allSkills } from '@/lib/skills/registry';
import { usePreferences } from '@/lib/krowHooks';
import { cn } from '@/lib/utils';
/**
* What this agent has actually been asked.
*
* Real-time usage analytics computed directly from the local telemetry log.
*/
/** @param {any} props */
function MetricCard({ icon: Icon, label, value, detail, highlight }) {
return (
<div className="rounded-2xl border border-border bg-surface p-5 shadow-xs space-y-2">
<div className="flex items-center justify-between gap-2">
<span className="text-caption font-medium text-ink-4 uppercase tracking-wider">{label}</span>
<span className="flex h-7 w-7 items-center justify-center rounded-lg bg-surface-subtle text-ink-3">
<Icon className="h-3.5 w-3.5" />
</span>
</div>
<div className="flex items-baseline gap-2">
<span className="font-heading text-title-xl font-bold tabular-nums text-ink-1 sm:text-3xl">
{value}
</span>
{highlight && (
<span className="text-caption font-semibold text-krow-blue">{highlight}</span>
)}
</div>
{detail && <p className="text-caption text-ink-4">{detail}</p>}
</div>
);
}
/** @param {any} props */
export function AgentInsightsPanel({ agentId, agentName }) {
const preferences = usePreferences();
const [records] = React.useState(() => readHistory());
const stats = React.useMemo(
() => conversationStats(records, { agentId }),
[records, agentId]
);
const skillName = React.useMemo(() => {
const byId = new Map(allSkills(preferences.customSkills || []).map((s) => [s.id, s.name]));
return (id) => byId.get(id) || id;
}, [preferences.customSkills]);
const conversations = React.useMemo(
() => records
.filter((r) => (agentId ? r.agentId === agentId : true))
.slice(0, 8)
.map(reviewRow),
[records, agentId]
);
if (stats.empty) {
return (
<div className="w-full rounded-2xl border border-border bg-surface p-12 shadow-xs text-center">
<EmptyState
icon={MessageSquare}
title="No conversations with this agent yet"
description={`Insights are counted from conversations held on this device. Ask ${agentName || 'this agent'} something in Owliver and the figures appear here.`}
/>
</div>
);
}
return (
<div className="w-full space-y-4">
{/* ── KPI Metric Cards ───────────────────────────────────────────── */}
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<MetricCard
icon={MessageSquare}
label="Total Conversations"
value={stats.total}
detail={`Active across ${stats.activeDays} day${stats.activeDays === 1 ? '' : 's'}`}
/>
<MetricCard
icon={HelpCircle}
label="Questions Asked"
value={stats.turns}
detail={`${stats.averageTurns} per session average`}
/>
<MetricCard
icon={Activity}
label="Surfaces Utilized"
value={stats.pages}
detail="Operational pages touched"
/>
<MetricCard
icon={ThumbsUp}
label="Feedback Rating"
value={stats.feedback.score === null ? '—' : `${stats.feedback.score}%`}
detail={
stats.feedback.score === null
? 'No ratings submitted'
: `${stats.feedback.up} helpful · ${stats.feedback.down} unhelpful`
}
/>
</div>
{/* ── 2-Column Ranking Breakdowns ────────────────────────────────── */}
<div className="grid gap-4 md:grid-cols-2">
{/* Most Used Skills */}
<div className="rounded-2xl border border-border bg-surface p-5 shadow-xs space-y-3">
<div className="flex items-center gap-2">
<BarChart3 className="h-4 w-4 text-krow-blue" />
<h4 className="font-heading text-body font-semibold text-ink-1">Most Triggered Skills</h4>
</div>
{stats.bySkill.length ? (
<div className="space-y-2">
{stats.bySkill.slice(0, 5).map((row) => (
<div
key={row.id}
className="flex items-center justify-between gap-3 rounded-lg border border-border/60 bg-surface-subtle/30 px-3 py-2 text-body-sm"
>
<span className="truncate font-medium text-ink-2">{skillName(row.id)}</span>
<Badge variant="neutral" size="sm" className="tabular-nums">
{row.count} query{row.count === 1 ? '' : 'ies'}
</Badge>
</div>
))}
</div>
) : (
<div className="rounded-xl border border-border/80 bg-surface-subtle/50 p-4 text-body-sm text-ink-3">
No skill executions logged yet. Conversations were handled directly by native page readers.
</div>
)}
</div>
{/* Actions Executed */}
<div className="rounded-2xl border border-border bg-surface p-5 shadow-xs space-y-3">
<div className="flex items-center gap-2">
<Wrench className="h-4 w-4 text-krow-blue" />
<h4 className="font-heading text-body font-semibold text-ink-1">Actions & Tool Usage</h4>
</div>
{stats.byTool.length ? (
<div className="space-y-2">
{stats.byTool.slice(0, 5).map((row) => (
<div
key={row.id}
className="flex items-center justify-between gap-3 rounded-lg border border-border/60 bg-surface-subtle/30 px-3 py-2 text-body-sm"
>
<span className="truncate font-medium text-ink-2">{row.id.replace(/_/g, ' ')}</span>
<Badge variant="neutral" size="sm" className="tabular-nums">
{row.count} run{row.count === 1 ? '' : 's'}
</Badge>
</div>
))}
</div>
) : (
<div className="rounded-xl border border-border/80 bg-surface-subtle/50 p-4 text-body-sm text-ink-3">
No action tool executions recorded in current audit window.
</div>
)}
</div>
</div>
{/* ── Recent Conversations Table ─────────────────────────────────── */}
<div className="rounded-2xl border border-border bg-surface shadow-xs overflow-hidden">
<div className="border-b border-border/80 bg-surface-subtle/40 px-5 py-4 sm:px-6">
<div className="flex items-center gap-2">
<Clock className="h-4 w-4 text-krow-blue" />
<h4 className="font-heading text-body font-semibold text-ink-1">
Recent Conversation Logs
</h4>
</div>
</div>
<div className="divide-y divide-border/60">
{conversations.map((row) => (
<div
key={row.id}
className="flex flex-wrap items-center justify-between gap-3 px-5 py-3.5 sm:px-6 hover:bg-surface-subtle/30 transition-colors"
>
<div className="min-w-0 flex-1 space-y-0.5">
<p className="truncate text-body-sm font-medium text-ink-1">
{row.title || 'General consultation'}
</p>
<p className="text-caption text-ink-4">
Surface:{' '}
<span className="font-medium text-ink-3">{row.page || 'Direct'}</span>
</p>
</div>
<div className="flex items-center gap-3 shrink-0">
<span className="text-caption tabular-nums text-ink-4">
{row.turns} turn{row.turns === 1 ? '' : 's'}
</span>
{row.feedback && (
<span
className={cn(
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-semibold',
row.feedback.rating === 'up'
? 'bg-emerald-50 text-emerald-700 border border-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-300 dark:border-emerald-800'
: 'bg-amber-50 text-amber-700 border border-amber-200 dark:bg-amber-950/40 dark:text-amber-300 dark:border-amber-800'
)}
>
{row.feedback.rating === 'up' ? (
<>
<ThumbsUp className="h-2.5 w-2.5" /> Helpful
</>
) : (
<>
<ThumbsDown className="h-2.5 w-2.5" /> Unhelpful
</>
)}
</span>
)}
</div>
</div>
))}
</div>
</div>
</div>
);
}
export default AgentInsightsPanel;