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.
This commit is contained in:
rcourtman 2025-12-21 11:45:55 +00:00
parent 783537e1d2
commit 420e4960dc
2 changed files with 82 additions and 3 deletions

View file

@ -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 (

View file

@ -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<LearningStatusResponse>(
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;