diff --git a/frontend/src/api/platform.ts b/frontend/src/api/platform.ts new file mode 100644 index 0000000..5812747 --- /dev/null +++ b/frontend/src/api/platform.ts @@ -0,0 +1,153 @@ +/** + * Platform API hooks — Phase I.6/I.7 (I-ONB, I-DASH, I-COST, I-USE). + * + * Provides hooks for the setup wizard (onboarding) and the platform dashboard + * (agent status, workflow stats, search metrics, knowledge coverage, cost + * tracking, system health). Backend routes may not be wired yet, so all hooks + * degrade gracefully to an "unavailable" state instead of throwing. + */ + +import { useQuery } from '@tanstack/react-query'; +import { apiGet } from './client'; + +// ── Onboarding ────────────────────────────────────────────────────────────── + +export interface OnboardingStepStatus { + completed: boolean; + required: boolean; +} + +export interface OnboardingStatus { + steps: Record; + progress_pct: number; +} + +export interface OnboardingGuideStep { + id: string; + title: string; + description: string; + icon?: string; + action_url?: string; +} + +export interface OnboardingGuide { + steps: OnboardingGuideStep[]; +} + +export function useOnboardingStatus() { + return useQuery({ + queryKey: ['onboardingStatus'], + queryFn: () => apiGet('/onboarding/status'), + staleTime: 60 * 1000, + retry: false, + }); +} + +export function useOnboardingGuide() { + return useQuery({ + queryKey: ['onboardingGuide'], + queryFn: () => apiGet('/onboarding/guide'), + staleTime: 60 * 1000, + retry: false, + }); +} + +// ── Platform dashboard ────────────────────────────────────────────────────── + +export interface PlatformDashboardData { + agents?: { + active_agents?: number; + total_runs?: number; + recent_runs_7d?: number; + error?: string; + }; + workflows?: { + active_workflows?: number; + running_instances?: number; + completed_instances?: number; + error?: string; + }; + search?: { + total_queries?: number; + avg_latency_ms?: number; + error?: string; + }; + knowledge?: { + wiki_articles?: number; + coverage_pct?: number; + error?: string; + }; + workstream?: { + messages?: number; + error?: string; + }; + system_health?: { + redis?: string; + status?: string; + error?: string; + }; + generated_at?: string; +} + +export function usePlatformDashboard() { + return useQuery({ + queryKey: ['platformDashboard'], + queryFn: () => apiGet('/dashboard/platform'), + staleTime: 60 * 1000, + retry: false, + }); +} + +// ── Cost tracking ─────────────────────────────────────────────────────────── + +export interface CostDashboardData { + period_days?: number; + total_cost_usd?: number; + by_agent?: Record; + budget?: { + monthly_limit_usd?: number; + utilization_pct?: number; + }; + error?: string; +} + +export function useCostDashboard(days = 30) { + return useQuery({ + queryKey: ['costDashboard', days], + queryFn: () => apiGet(`/dashboard/cost?days=${days}`), + staleTime: 60 * 1000, + retry: false, + }); +} + +// ── Usage analytics ───────────────────────────────────────────────────────── + +export interface UsageAnalyticsData { + period_days?: number; + agent_runs?: { + total?: number; + completed?: number; + failed?: number; + success_rate?: number; + error?: string; + }; + workflow_executions?: { + total?: number; + completed?: number; + error?: string; + }; + search_queries?: { + total?: number; + error?: string; + }; + error?: string; +} + +export function useUsageAnalytics(days = 30) { + return useQuery({ + queryKey: ['usageAnalytics', days], + queryFn: () => apiGet(`/dashboard/usage?days=${days}`), + staleTime: 60 * 1000, + retry: false, + }); +} diff --git a/frontend/src/components/onboarding/SetupWizard.tsx b/frontend/src/components/onboarding/SetupWizard.tsx new file mode 100644 index 0000000..3ce5931 --- /dev/null +++ b/frontend/src/components/onboarding/SetupWizard.tsx @@ -0,0 +1,136 @@ +/** + * SetupWizard — Phase I.6 (I-ONB) setup wizard. + * + * Guides users through 4 steps: Welcome, Create first Agent, + * Create first Workflow, Enable Knowledge & Workstream. + * + * Uses lucide-react icons, Tailwind, and i18n via `t()`. + */ + +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Sparkles, + Bot, + Workflow, + BookOpen, + ChevronLeft, + ChevronRight, + X, +} from 'lucide-react'; +import { Button } from '@/components/ui/Button'; +import { Card } from '@/components/ui/Card'; + +interface SetupWizardProps { + open: boolean; + onClose: () => void; + onComplete?: () => void; +} + +const STEP_ICONS = [Sparkles, Bot, Workflow, BookOpen]; + +/** + * Setup wizard with 4 steps. Each step shows a title, description and an + * optional action button that deep-links to the relevant setup page. + */ +export function SetupWizard({ open, onClose, onComplete }: SetupWizardProps) { + const { t } = useTranslation(); + const [step, setStep] = useState(0); + + if (!open) return null; + + const totalSteps = 4; + const isLast = step === totalSteps - 1; + const Icon = STEP_ICONS[step]; + + const handleNext = () => { + if (isLast) { + onComplete?.(); + onClose(); + return; + } + setStep((s) => s + 1); + }; + + const handleBack = () => { + if (step > 0) setStep((s) => s - 1); + }; + + const stepKey = `setupWizard.step${step + 1}`; + + return ( +
+ +
+

+ {t('setupWizard.title')} +

+ +
+ +
+ {/* Progress indicator */} + + +
+ + +
+ +
+ ); +} + +export default SetupWizard; diff --git a/frontend/src/components/workstream/WorkstreamBlockRenderer.tsx b/frontend/src/components/workstream/WorkstreamBlockRenderer.tsx new file mode 100644 index 0000000..da20871 --- /dev/null +++ b/frontend/src/components/workstream/WorkstreamBlockRenderer.tsx @@ -0,0 +1,260 @@ +/** + * WorkstreamBlockRenderer — Phase I.7 (I-UI) universal block renderer. + * + * Renders every workstream block type (text, entity_card, action_card, + * evidence_card, approval_card, miniapp, workflow_status, workflow_handoff, + * error) with consistent loading / error / empty states. + * + * Reuses MiniAppBlock and the MiniAppSDK building blocks. + * + * Backend: app/ai/workstream_contract.py + */ + +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Link2, + AlertTriangle, + Workflow, + ArrowRight, + Loader2, + MessageSquare, +} from 'lucide-react'; +import { Badge } from '@/components/ui/Badge'; +import { EmptyState } from '@/components/ui/EmptyState'; +import { MiniAppBlock } from '@/components/workstream/MiniAppBlock'; +import { + EntityCard, + ActionButtons, + ApprovalCard, + ProgressIndicator, + DeepLink, + type ActionButtonItem, +} from '@/components/workstream/MiniAppSDK'; +import type { WorkstreamBlock } from '@/api/miniapps'; + +interface WorkstreamBlockRendererProps { + block: WorkstreamBlock; + onApprove?: (approvalId: string) => void; + onReject?: (approvalId: string) => void; + onAction?: (action: ActionButtonItem) => void; +} + +/** + * Render a single workstream block by type. + */ +export function WorkstreamBlockRenderer({ + block, + onApprove, + onReject, + onAction, +}: WorkstreamBlockRendererProps) { + const { t } = useTranslation(); + const meta = block.metadata ?? {}; + + switch (block.type) { + case 'text': + return ( +
+ {block.content || String(meta.text ?? '')} +
+ ); + + case 'entity_card': + return ( + + ); + + case 'action_card': { + const actions: ActionButtonItem[] = Array.isArray(meta.actions) + ? (meta.actions as ActionButtonItem[]) + : []; + return ( +
+

+ {String(meta.title ?? block.content)} +

+ {meta.description ? ( +

+ {String(meta.description)} +

+ ) : null} + +
+ ); + } + + case 'evidence_card': + return ( +
+
+
+ {meta.snippet ? ( +

+ {String(meta.snippet)} +

+ ) : null} + {meta.url ? ( +
+ +
+ ) : null} +
+ ); + + case 'approval_card': + return ( + + ); + + case 'miniapp': + return ; + + case 'workflow_status': + return ( +
+
+
+ {meta.step_name ? ( +

+ {t('workstream.block.step')}: {String(meta.step_name)} +

+ ) : null} + {typeof meta.step_index === 'number' && ( +
+ +
+ )} +
+ ); + + case 'workflow_handoff': + return ( +
+
+
+ {meta.description ? ( +

+ {String(meta.description)} +

+ ) : null} +
+ ); + + case 'error': + return ( +
+
+ ); + + default: + return ( +
+ {t('workstream.block.unknown', { type: block.type })} +
+ ); + } +} + +interface WorkstreamBlockListProps { + blocks: WorkstreamBlock[]; + isLoading?: boolean; + onApprove?: (approvalId: string) => void; + onReject?: (approvalId: string) => void; + onAction?: (action: ActionButtonItem) => void; +} + +/** + * Render a list of blocks with consistent loading / empty states. + */ +export function WorkstreamBlockList({ + blocks, + isLoading = false, + onApprove, + onReject, + onAction, +}: WorkstreamBlockListProps) { + const { t } = useTranslation(); + + if (isLoading) { + return ( +
+
+ ); + } + + if (!blocks || blocks.length === 0) { + return ( +
); } + +function MetricRow({ + icon, + label, + value, +}: { + icon: React.ReactNode; + label: string; + value: string | number; +}) { + return ( +
+ + + {label} + + {value} +
+ ); +} + +function healthStatusVariant(status: string): 'success' | 'warning' | 'danger' | 'secondary' { + if (status === 'healthy') return 'success'; + if (status === 'degraded') return 'warning'; + if (status === 'down') return 'danger'; + return 'secondary'; +} + +export default DashboardPage;