From 420e4960dc040de3f4839879bab651b07280a820 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sun, 21 Dec 2025 11:45:55 +0000 Subject: [PATCH] feat(ui): add learning status hook and enhance AI indicator visibility New useLearningStatus hook: - Polls /api/ai/intelligence/learning every 60 seconds - Provides resourceCount(), metricCount(), learningState() - Convenience accessors: isActive(), isLearning(), isWaiting() Enhanced AIStatusIndicator: - Now shows when ANY baselines exist (not just when Patrol enabled) - Tooltip shows 'X resources baselined' for transparency - Healthy state 45 resources baselined'shows ' - Works even without Pro license since baselines are FREE This makes the AI presence visible from the moment Pulse starts learning, providing immediate value feedback to all users. --- .../src/components/AI/AIStatusIndicator.tsx | 28 ++++++++- .../src/hooks/useLearningStatus.ts | 57 +++++++++++++++++++ 2 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 frontend-modern/src/hooks/useLearningStatus.ts diff --git a/frontend-modern/src/components/AI/AIStatusIndicator.tsx b/frontend-modern/src/components/AI/AIStatusIndicator.tsx index 5c7167a..0d2696d 100644 --- a/frontend-modern/src/components/AI/AIStatusIndicator.tsx +++ b/frontend-modern/src/components/AI/AIStatusIndicator.tsx @@ -9,8 +9,10 @@ import { createResource, Show, createMemo, onCleanup } from 'solid-js'; import { useNavigate } from '@solidjs/router'; import { getPatrolStatus, type PatrolStatus } from '../../api/patrol'; import { useAllAnomalies } from '@/hooks/useAnomalies'; +import { useLearningStatus } from '@/hooks/useLearningStatus'; import './AIStatusIndicator.css'; + export function AIStatusIndicator() { const navigate = useNavigate(); @@ -29,10 +31,14 @@ export function AIStatusIndicator() { // Get anomaly data (also polls every 30 seconds via the hook) const anomalyData = useAllAnomalies(); + // Get learning status (polls every 60 seconds) + const learningStatus = useLearningStatus(); + // Refetch patrol status every 30 seconds with proper cleanup const intervalId = setInterval(() => refetch(), 30000); onCleanup(() => clearInterval(intervalId)); + // Count anomalies by severity const anomalyCounts = createMemo(() => { const anomalies = anomalyData.anomalies(); @@ -96,8 +102,21 @@ export function AIStatusIndicator() { parts.push(`Anomalies: ${anomalyParts.join(', ')}`); } + // Learning status - show when healthy to indicate AI is working + const resourceCount = learningStatus.resourceCount(); + if (parts.length === 0 && resourceCount > 0) { + // Show learning progress when healthy + return `AI: All healthy • ${resourceCount} resources baselined`; + } + if (parts.length === 0) { - if (!s?.enabled) return 'AI Patrol disabled'; + if (!s?.enabled) { + // Show baseline info even when patrol disabled + if (resourceCount > 0) { + return `AI Learning: ${resourceCount} resources baselined`; + } + return 'AI Baseline Learning active'; + } if (s?.license_required) { if (s.license_status === 'active') { return 'AI Patrol is not included in this license tier'; @@ -113,6 +132,7 @@ export function AIStatusIndicator() { return `AI Intelligence: ${parts.join(' | ')}`; }); + const statusClass = createMemo(() => { if (hasIssues() || hasAnomalies()) return 'ai-status--issues'; if (hasWatch() || hasMildAnomalies()) return 'ai-status--watch'; @@ -127,10 +147,12 @@ export function AIStatusIndicator() { // Combined total for badge const badgeCount = createMemo(() => totalFindings() + totalAnomalies()); - // Show indicator if patrol is enabled OR if we have anomalies (anomalies work without patrol) + // Show indicator if patrol is enabled, we have anomalies, OR we have learned baselines + // This makes the AI presence visible even before Patrol is configured const showIndicator = createMemo(() => { const s = status(); - return s?.enabled || totalAnomalies() > 0; + const hasBaselines = learningStatus.resourceCount() > 0; + return s?.enabled || totalAnomalies() > 0 || hasBaselines; }); return ( diff --git a/frontend-modern/src/hooks/useLearningStatus.ts b/frontend-modern/src/hooks/useLearningStatus.ts new file mode 100644 index 0000000..5072891 --- /dev/null +++ b/frontend-modern/src/hooks/useLearningStatus.ts @@ -0,0 +1,57 @@ +/** + * useLearningStatus - Hook for fetching AI learning/baseline status + * + * This shows users how much the AI has learned about their infrastructure, + * providing transparency into the baseline learning process. + * + * FREE feature - no license required. + */ + +import { createResource, onCleanup } from 'solid-js'; +import { AIAPI } from '@/api/ai'; +import type { LearningStatusResponse } from '@/types/aiIntelligence'; + +// Default empty state +const emptyLearningStatus: LearningStatusResponse = { + resources_baselined: 0, + total_metrics: 0, + metric_breakdown: {}, + status: 'waiting', + message: 'Loading learning status...', + license_required: false, +}; + +/** + * Hook to get the current learning/baseline status + * Polls every 60 seconds (learning status changes slowly) + */ +export function useLearningStatus() { + const [learningStatus, { refetch }] = createResource( + async () => { + try { + return await AIAPI.getLearningStatus(); + } catch { + return emptyLearningStatus; + } + }, + { initialValue: emptyLearningStatus } + ); + + // Poll every 60 seconds (learning progress changes slowly) + const intervalId = setInterval(() => refetch(), 60000); + onCleanup(() => clearInterval(intervalId)); + + return { + status: learningStatus, + refetch, + // Convenience accessors + resourceCount: () => learningStatus()?.resources_baselined ?? 0, + metricCount: () => learningStatus()?.total_metrics ?? 0, + learningState: () => learningStatus()?.status ?? 'waiting', + isActive: () => learningStatus()?.status === 'active', + isLearning: () => learningStatus()?.status === 'learning', + isWaiting: () => learningStatus()?.status === 'waiting', + }; +} + +export default useLearningStatus;