@burdenoff/microfe-vibecontrols 2026.623.2 → 2026.623.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/actions/ActionTypeBadge.js +1 -1
- package/dist/components/actions/ActionTypeBadge.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerChatPane.js +1 -1
- package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerInputArea.js +1 -1
- package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
- package/dist/components/agent-manager/BridleConfigPanel.js +2 -2
- package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/ProfilesPanel.js +1 -1
- package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionLogsPanel.js +2 -2
- package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
- package/dist/components/ai/AIContextsTab.js +1 -1
- package/dist/components/ai/AIContextsTab.js.map +1 -1
- package/dist/components/ai/AIOverviewTab.js +1 -1
- package/dist/components/ai/AIOverviewTab.js.map +1 -1
- package/dist/components/ai/AIPromptsTab.js +1 -1
- package/dist/components/ai/AIPromptsTab.js.map +1 -1
- package/dist/components/ai/AIQueueTab.js +1 -1
- package/dist/components/ai/AIQueueTab.js.map +1 -1
- package/dist/components/ai/AISessionsTab.js +1 -1
- package/dist/components/ai/AISessionsTab.js.map +1 -1
- package/dist/components/audit/AuditLogTable.js +1 -1
- package/dist/components/audit/AuditLogTable.js.map +1 -1
- package/dist/components/docs/DocsPageTree.js +1 -1
- package/dist/components/docs/DocsPageTree.js.map +1 -1
- package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
- package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
- package/dist/components/graph/MeshDeployWizard.js +3 -3
- package/dist/components/graph/MeshDeployWizard.js.map +1 -1
- package/dist/components/graph/MeshRequestPanel.js +2 -2
- package/dist/components/graph/MeshRequestPanel.js.map +1 -1
- package/dist/components/notes/NoteCard.js +1 -1
- package/dist/components/notes/NoteCard.js.map +1 -1
- package/dist/components/notes/NoteEditor.js +1 -1
- package/dist/components/notes/NoteEditor.js.map +1 -1
- package/dist/components/notes/NoteList.js +1 -1
- package/dist/components/notes/NoteList.js.map +1 -1
- package/dist/components/shared/AgentSetupBanner.js +3 -3
- package/dist/components/shared/AgentSetupBanner.js.map +1 -1
- package/dist/components/templates/TemplateCard.js +1 -1
- package/dist/components/templates/TemplateCard.js.map +1 -1
- package/dist/components/vibecalendar/types.js +1 -1
- package/dist/components/vibecalendar/types.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonForm.js +1 -1
- package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckExecutionLog.js +2 -2
- package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckGrid.js +1 -1
- package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
- package/dist/pages/AgentsPage.js +2 -2
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +1 -1
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +1 -1
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/OverviewPage.js +10 -10
- package/dist/pages/OverviewPage.js.map +1 -1
- package/dist/pages/PluginsCatalogPage.js +1 -1
- package/dist/pages/PluginsCatalogPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +3 -3
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/SharedSessionPage.js +2 -2
- package/dist/pages/SharedSessionPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +2 -2
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +2 -2
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/VibesPage.js +2 -2
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/WebhooksPage.js +2 -2
- package/dist/pages/WebhooksPage.js.map +1 -1
- package/dist/pages/ai/AISessionDetailPage.js +2 -2
- package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebhooksPage.js","names":[],"sources":["../../src/pages/WebhooksPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport {\n Plus,\n Webhook,\n Grid,\n List,\n Check,\n X,\n RefreshCw,\n AlertTriangle,\n Play,\n Trash2,\n Eye,\n EyeOff,\n Bot,\n} from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { isNative, nativeImpact, nativeNotify } from '../utils/nativeBridge';\nimport { AnimatePresence } from 'framer-motion';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n EmptyState,\n Card,\n ErrorState,\n ConfirmDialog,\n QuotaExhaustedDialog,\n SelectionCheckbox,\n GetHelpButton,\n} from '@/components/shared';\nimport { useVibeControls, useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport {\n useListWebhooksQuery,\n useCreateWebhookMutation,\n useUpdateWebhookMutation,\n useTestWebhookMutation,\n useDeleteWebhookMutation,\n} from '@/generated/wspace-operations';\nimport type { Webhook as WebhookType } from '@/components/webhooks/types';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { WEBHOOK_EVENTS, isCustomEvent } from '@/components/webhooks/types';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { isQuotaExhaustedError } from '@/utils/quotaUtils';\n\ntype ViewMode = 'grid' | 'list';\n\nconst statusOptions = [\n { value: 'all', label: 'All Statuses' },\n { value: 'active', label: 'Active' },\n { value: 'inactive', label: 'Inactive' },\n { value: 'error', label: 'Has Errors' },\n];\n\n/**\n * Webhooks management page\n */\nexport function WebhooksPage() {\n useVibeControls();\n const navigate = useVibeNavigate();\n const { t } = useI18n();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n // Check permissions\n const canListWebhooks = hasPermission(VIBE_PERMISSIONS.WEBHOOK_LIST);\n const canCreateWebhook = hasPermission(VIBE_PERMISSIONS.WEBHOOK_CREATE);\n\n // Skip queries until permissions are loaded and user has permission\n // This prevents FORBIDDEN errors from triggering auth error handlers\n const shouldSkipQueries = permissionsLoading || !canListWebhooks;\n\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState('all');\n const [viewMode, setViewMode] = useState<ViewMode>('list');\n const [showCreateForm, setShowCreateForm] = useState(false);\n const [testingWebhook, setTestingWebhook] = useState<string | null>(null);\n const [createError, setCreateError] = useState<string | null>(null);\n const [createLoading, setCreateLoading] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [pageError, setPageError] = useState<string | null>(null);\n const [selectedEvents, setSelectedEvents] = useState<string[]>([]);\n const [selectedAgentIds, setSelectedAgentIds] = useState<string[]>([]);\n const [showSigningKey, setShowSigningKey] = useState(false);\n const [customEventInput, setCustomEventInput] = useState('');\n const [selectedWebhookIds, setSelectedWebhookIds] = useState<Set<string>>(new Set());\n const [bulkAction, setBulkAction] = useState<'enable' | 'disable' | 'delete' | null>(null);\n const [quotaDialog, setQuotaDialog] = useState<{ title: string; message: string } | null>(null);\n\n useEffect(() => {\n if (!successMessage) {\n return undefined;\n }\n\n const timeoutId = window.setTimeout(() => setSuccessMessage(null), 4000);\n return () => window.clearTimeout(timeoutId);\n }, [successMessage]);\n\n useEffect(() => {\n if (!showCreateForm) {\n return undefined;\n }\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape' && !createLoading) {\n setShowCreateForm(false);\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [createLoading, showCreateForm]);\n\n // Form refs\n const webhookNameRef = useRef<HTMLInputElement>(null);\n const webhookUrlRef = useRef<HTMLInputElement>(null);\n const webhookSecretRef = useRef<HTMLInputElement>(null);\n const webhookSigningKeyRef = useRef<HTMLInputElement>(null);\n\n const [createWebhook] = useCreateWebhookMutation({ refetchQueries: ['ListWebhooks'] });\n const [updateWebhook] = useUpdateWebhookMutation({ refetchQueries: ['ListWebhooks'] });\n const [testWebhook] = useTestWebhookMutation({ refetchQueries: ['ListWebhooks'] });\n const [deleteWebhookMutation] = useDeleteWebhookMutation({ refetchQueries: ['ListWebhooks'] });\n\n // Use cached initial data for agents dropdown\n const { agents: cachedAgents } = useCachedInitialData();\n const agents: Array<{ id: string; name: string }> = useMemo(\n () =>\n cachedAgents.map((a) => ({\n id: a.id,\n name: a.name,\n })),\n [cachedAgents]\n );\n\n // Fetch webhooks from GraphQL API\n const { data, loading, error, refetch } = useListWebhooksQuery({\n variables: {\n filter: {\n isActive:\n statusFilter === 'active' ? true : statusFilter === 'inactive' ? false : undefined,\n },\n pagination: { limit: 50 },\n },\n fetchPolicy: 'cache-and-network',\n skip: shouldSkipQueries,\n });\n\n // Map GraphQL response to Webhook type\n const webhooks: WebhookType[] = useMemo(() => {\n const rawWebhooks = data?.webhooks?.items ?? [];\n return rawWebhooks.map((webhook: NonNullable<typeof rawWebhooks>[number]) => ({\n id: webhook.id,\n workspaceId: webhook.workspaceId,\n name: webhook.name,\n url: webhook.url,\n events: webhook.events || [],\n agentIds: webhook.agentIds || [],\n secret: null,\n signingKey: webhook.signingKey || null,\n isActive: webhook.isActive,\n lastDelivery: webhook.lastDelivery || null,\n lastError: webhook.lastError || null,\n deliveryCount: webhook.deliveryCount || 0,\n failureCount: webhook.failureCount || 0,\n metadata: {},\n createdAt: webhook.createdAt,\n updatedAt: webhook.updatedAt,\n }));\n }, [data?.webhooks?.items]);\n\n // Filter webhooks based on search and error status (client-side filtering)\n const filteredWebhooks = useMemo(() => {\n return webhooks.filter((webhook) => {\n // Search filter\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n webhook.name.toLowerCase().includes(query) ||\n webhook.url.toLowerCase().includes(query) ||\n webhook.events.some((e) => e.toLowerCase().includes(query));\n if (!matchesSearch) return false;\n }\n\n // Error filter (additional client-side filter)\n if (statusFilter === 'error' && !webhook.lastError) return false;\n\n return true;\n });\n }, [webhooks, searchQuery, statusFilter]);\n\n const selectedWebhooks = useMemo(\n () => webhooks.filter((webhook) => selectedWebhookIds.has(webhook.id)),\n [webhooks, selectedWebhookIds]\n );\n const activeSelectedWebhooks = selectedWebhooks.filter((webhook) => webhook.isActive);\n const inactiveSelectedWebhooks = selectedWebhooks.filter((webhook) => !webhook.isActive);\n const allVisibleSelected =\n filteredWebhooks.length > 0 &&\n filteredWebhooks.every((webhook) => selectedWebhookIds.has(webhook.id));\n const partiallySelected =\n filteredWebhooks.some((webhook) => selectedWebhookIds.has(webhook.id)) && !allVisibleSelected;\n\n const handleCreateWebhook = () => {\n setCreateError(null);\n setPageError(null);\n setSelectedEvents([]);\n setSelectedAgentIds([]);\n setShowSigningKey(false);\n setCustomEventInput('');\n setShowCreateForm(true);\n };\n\n const handleToggleEvent = (event: string) => {\n setSelectedEvents((prev) =>\n prev.includes(event) ? prev.filter((e) => e !== event) : [...prev, event]\n );\n };\n\n const handleSubmitCreateWebhook = async () => {\n const name = webhookNameRef.current?.value?.trim();\n const url = webhookUrlRef.current?.value?.trim();\n const secret = webhookSecretRef.current?.value?.trim() || undefined;\n const signingKey = webhookSigningKeyRef.current?.value?.trim() || undefined;\n\n if (!name) {\n setCreateError(tr('webhooks.nameRequired', 'Name is required'));\n return;\n }\n if (!url) {\n setCreateError(tr('webhooks.urlRequired', 'URL is required'));\n return;\n }\n // Validate URL format\n try {\n const parsedUrl = new URL(url);\n if (!['http:', 'https:'].includes(parsedUrl.protocol)) {\n setCreateError(\n tr('webhooks.urlProtocolRequired', 'URL must use http:// or https:// protocol')\n );\n return;\n }\n } catch {\n setCreateError(\n tr('webhooks.urlInvalid', 'Please enter a valid URL (e.g., https://example.com/webhook)')\n );\n return;\n }\n if (selectedEvents.length === 0) {\n setCreateError(tr('webhooks.eventRequired', 'At least one event must be selected'));\n return;\n }\n\n setCreateLoading(true);\n setCreateError(null);\n setPageError(null);\n try {\n await createWebhook({\n variables: {\n input: {\n name,\n url,\n events: selectedEvents,\n secret,\n ...(signingKey ? { signingKey } : {}),\n ...(selectedAgentIds.length > 0 ? { agentIds: selectedAgentIds } : {}),\n },\n },\n });\n setShowCreateForm(false);\n setSuccessMessage(tr('webhooks.createdSuccess', 'Webhook created successfully'));\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowCreateForm(false);\n setQuotaDialog({\n title: 'Webhook Quota Reached',\n message:\n 'You have reached the webhook limit for your current plan. Upgrade to create more webhooks.',\n });\n return;\n }\n\n setCreateError(\n err instanceof Error ? err.message : tr('webhooks.failedCreate', 'Failed to create webhook')\n );\n } finally {\n setCreateLoading(false);\n }\n };\n\n const handleToggleWebhook = async (webhook: WebhookType) => {\n try {\n setPageError(null);\n await updateWebhook({\n variables: { id: webhook.id, input: { isActive: !webhook.isActive } },\n });\n setSuccessMessage(\n webhook.isActive\n ? tr('webhooks.disabledSuccess', 'Webhook disabled successfully')\n : tr('webhooks.enabledSuccess', 'Webhook enabled successfully')\n );\n } catch (err) {\n console.error('Failed to toggle webhook:', err);\n setPageError(\n err instanceof Error\n ? err.message\n : tr('webhooks.failedToggle', 'Failed to update webhook state')\n );\n }\n };\n\n const handleTestWebhook = async (webhook: WebhookType) => {\n setTestingWebhook(webhook.id);\n try {\n setPageError(null);\n await testWebhook({ variables: { id: webhook.id } });\n setSuccessMessage(tr('webhooks.testSuccess', 'Webhook test delivery completed'));\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setQuotaDialog({\n title: 'Webhook Test Quota Reached',\n message:\n 'You have used the webhook test quota for your current plan. Upgrade to continue testing deliveries.',\n });\n return;\n }\n\n console.error('Failed to test webhook:', err);\n setPageError(\n err instanceof Error ? err.message : tr('webhooks.failedTest', 'Failed to test webhook')\n );\n } finally {\n setTestingWebhook(null);\n }\n };\n\n const handleDeleteWebhook = async (webhook: WebhookType) => {\n try {\n setPageError(null);\n await deleteWebhookMutation({ variables: { id: webhook.id } });\n setSuccessMessage(tr('webhooks.deletedSuccess', 'Webhook deleted successfully'));\n if (isNative()) void nativeNotify('success');\n } catch (err) {\n console.error('Failed to delete webhook:', err);\n setPageError(\n err instanceof Error ? err.message : tr('webhooks.failedDelete', 'Failed to delete webhook')\n );\n if (isNative()) void nativeNotify('error');\n }\n };\n\n const toggleWebhookSelection = (webhookId: string, checked: boolean) => {\n setSelectedWebhookIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(webhookId);\n } else {\n next.delete(webhookId);\n }\n return next;\n });\n };\n\n const toggleAllVisibleWebhooks = (checked: boolean) => {\n setSelectedWebhookIds((prev) => {\n const next = new Set(prev);\n filteredWebhooks.forEach((webhook) => {\n if (checked) {\n next.add(webhook.id);\n } else {\n next.delete(webhook.id);\n }\n });\n return next;\n });\n };\n\n const clearSelection = () => setSelectedWebhookIds(new Set());\n\n const handleBulkToggleWebhooks = async (enable: boolean) => {\n const targets = enable ? inactiveSelectedWebhooks : activeSelectedWebhooks;\n if (targets.length === 0) return;\n\n const results = await Promise.allSettled(\n targets.map((webhook) =>\n updateWebhook({ variables: { id: webhook.id, input: { isActive: enable } } })\n )\n );\n\n const succeeded = results.filter((result) => result.status === 'fulfilled').length;\n const failed = results.length - succeeded;\n\n setSuccessMessage(\n `${enable ? 'Enabled' : 'Disabled'} ${succeeded} webhook${succeeded === 1 ? '' : 's'}${failed > 0 ? ` • ${failed} failed` : ''}`\n );\n setTimeout(() => setSuccessMessage(null), 4000);\n setBulkAction(null);\n clearSelection();\n await refetch();\n };\n\n const handleBulkDeleteWebhooks = async () => {\n if (selectedWebhooks.length === 0) return;\n\n const results = await Promise.allSettled(\n selectedWebhooks.map((webhook) => deleteWebhookMutation({ variables: { id: webhook.id } }))\n );\n\n const succeeded = results.filter((result) => result.status === 'fulfilled').length;\n const failed = results.length - succeeded;\n\n setSuccessMessage(\n `Deleted ${succeeded} webhook${succeeded === 1 ? '' : 's'}${failed > 0 ? ` • ${failed} failed` : ''}`\n );\n setTimeout(() => setSuccessMessage(null), 4000);\n setBulkAction(null);\n clearSelection();\n await refetch();\n };\n\n // Stats\n const stats = {\n total: webhooks.length,\n active: webhooks.filter((w) => w.isActive).length,\n withErrors: webhooks.filter((w) => w.lastError).length,\n };\n\n const formatTimestamp = (timestamp: string | null) => {\n if (!timestamp) return tr('webhooks.never', 'Never');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('webhooks.justNow', 'Just now');\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return `${Math.floor(diff / 86400000)}d ago`;\n };\n\n // Guard states rendered inside MotionPage to prevent unmount/remount animation replay\n // Gate full render until permissions resolve — avoids flashing the page\n // before the AccessDenied check can run.\n if (permissionsLoading) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <PageSkeleton variant=\"rows\" count={6} />\n </MotionPage>\n );\n }\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canListWebhooks) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <AccessDenied\n title={tr('common.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'webhooks.accessDeniedDescription',\n \"You don't have permission to view webhooks.\"\n )}\n details=\"Required permission: webhook:list\"\n />\n </MotionPage>\n );\n }\n\n if (error && webhooks.length === 0) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <ErrorState\n title={tr('webhooks.failedLoad', 'Failed to load webhooks')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n {/* Success Toast */}\n {successMessage && (\n <div className=\"fixed top-4 left-4 right-4 sm:left-auto z-50 bg-status-success-bg text-status-success-text px-4 py-3 rounded-md shadow-lg\">\n {successMessage}\n </div>\n )}\n\n {pageError && (\n <div\n role=\"alert\"\n className=\"rounded-card border border-status-error-border bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text\"\n >\n {pageError}\n </div>\n )}\n\n <PageHeader\n title={tr('nav.webhooks', 'Webhooks')}\n description={`${stats.active} of ${stats.total} webhooks active`}\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title={tr('webhooks.refreshTitle', 'Refresh webhooks')}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n <PermissionButton permission={VIBE_PERMISSIONS.WEBHOOK_CREATE}>\n <button\n type=\"button\"\n onClick={handleCreateWebhook}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('webhooks.newWebhook', 'New Webhook')}\n </button>\n </PermissionButton>\n </div>\n }\n />\n\n {/* Filters Bar */}\n <div className=\"space-y-3\">\n {/* Row 1: Search + View Toggle */}\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('webhooks.searchPlaceholder', 'Search webhooks...')}\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n {/* View Toggle */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('webhooks.gridViewTitle', 'Grid view')}\n >\n <Grid className=\"size-4 text-text-secondary\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('webhooks.listViewTitle', 'List view')}\n >\n <List className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n {/* Row 2: Filters */}\n <div className=\"flex items-center gap-2 sm:gap-3\">\n <FilterDropdown\n label={tr('webhooks.status', 'Status')}\n value={statusFilter}\n options={statusOptions}\n onChange={setStatusFilter}\n />\n </div>\n </div>\n\n {/* Quick Actions */}\n {stats.withErrors > 0 && (\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setStatusFilter('error')}\n className={`flex items-center gap-2 px-3 py-1.5 text-sm rounded-full transition-colors ${\n statusFilter === 'error'\n ? 'bg-status-error-bg text-status-error-text'\n : 'bg-bg-sunken text-text-secondary hover:bg-action-secondary-bg-hover'\n }`}\n >\n <AlertTriangle className=\"size-3\" />\n {tr('webhooks.withErrors', 'With Errors')} ({stats.withErrors})\n </button>\n </div>\n )}\n\n {/* Results Count */}\n {(searchQuery || statusFilter !== 'all') && (\n <p className=\"text-sm text-text-secondary\">\n {tr('webhooks.showingResults', 'Showing')} {filteredWebhooks.length}{' '}\n {tr('webhooks.of', 'of')} {webhooks.length} {tr('nav.webhooks', 'webhooks')}\n {loading && (\n <span className=\"ml-2 text-text-muted\">\n ({tr('webhooks.refreshing', 'refreshing...')})\n </span>\n )}\n </p>\n )}\n\n {filteredWebhooks.length > 0 && (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-4\">\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex items-center gap-2\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisibleWebhooks}\n ariaLabel=\"Select all visible webhooks\"\n />\n <span className=\"text-sm font-medium text-text-primary\">Select all visible</span>\n </div>\n {selectedWebhookIds.size > 0 && (\n <button\n type=\"button\"\n onClick={clearSelection}\n className=\"text-sm text-text-secondary hover:text-text-primary\"\n >\n Clear selection\n </button>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-full bg-bg-sunken px-3 py-1 text-sm font-medium text-text-secondary\">\n {selectedWebhookIds.size} selected\n </span>\n <button\n type=\"button\"\n onClick={() => setBulkAction('enable')}\n disabled={inactiveSelectedWebhooks.length === 0}\n className=\"rounded-md border border-status-success-border px-3 py-2 text-sm font-medium text-status-success-text hover:bg-status-success-bg disabled:cursor-not-allowed disabled:opacity-50\"\n >\n Enable Selected\n {inactiveSelectedWebhooks.length > 0 ? ` (${inactiveSelectedWebhooks.length})` : ''}\n </button>\n <button\n type=\"button\"\n onClick={() => setBulkAction('disable')}\n disabled={activeSelectedWebhooks.length === 0}\n className=\"rounded-md border border-status-warning-border px-3 py-2 text-sm font-medium text-status-warning-text hover:bg-status-warning-bg disabled:cursor-not-allowed disabled:opacity-50\"\n >\n Disable Selected\n {activeSelectedWebhooks.length > 0 ? ` (${activeSelectedWebhooks.length})` : ''}\n </button>\n <button\n type=\"button\"\n onClick={() => setBulkAction('delete')}\n disabled={selectedWebhooks.length === 0}\n className=\"rounded-md border border-status-error-bg px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg disabled:cursor-not-allowed disabled:opacity-50\"\n >\n Delete Selected\n {selectedWebhooks.length > 0 ? ` (${selectedWebhooks.length})` : ''}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Webhooks Display */}\n <AnimatePresence mode=\"wait\">\n {loading && webhooks.length === 0 ? (\n <PageSkeleton variant=\"rows\" count={6} />\n ) : filteredWebhooks.length > 0 ? (\n <MotionList\n key={`webhook-list-${viewMode}`}\n className={viewMode === 'grid' ? 'grid grid-cols-1 gap-4 xl:grid-cols-2' : 'space-y-4'}\n >\n {filteredWebhooks.map((webhook) => (\n <MotionItem key={webhook.id} variant=\"fadeRight\">\n <Card className=\"p-4\">\n <div className=\"flex flex-col sm:flex-row sm:items-start justify-between gap-4\">\n <div className=\"flex items-start gap-4 flex-1 min-w-0\">\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selectedWebhookIds.has(webhook.id)}\n onChange={(checked) => toggleWebhookSelection(webhook.id, checked)}\n ariaLabel={`Select webhook ${webhook.name}`}\n className=\"mt-2\"\n />\n </div>\n <div\n className={`size-10 rounded-lg flex items-center justify-center flex-shrink-0 ${\n webhook.isActive\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-bg-sunken text-text-secondary'\n }`}\n >\n <Webhook className=\"size-5\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary\">{webhook.name}</h3>\n <span\n className={`px-2 py-0.5 text-xs rounded-full ${\n webhook.isActive\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-bg-sunken text-text-secondary'\n }`}\n >\n {webhook.isActive\n ? tr('webhooks.active', 'Active')\n : tr('webhooks.inactive', 'Inactive')}\n </span>\n {webhook.lastError && (\n <span className=\"px-2 py-0.5 text-xs rounded-full bg-status-error-bg text-status-error-text\">\n {tr('webhooks.error', 'Error')}\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mt-1 font-mono truncate\">\n {webhook.url}\n </p>\n <div className=\"flex flex-wrap gap-1 mt-2\">\n {webhook.events.map((event) => (\n <span\n key={event}\n className={`px-2 py-0.5 text-xs rounded ${\n isCustomEvent(event)\n ? 'bg-[var(--color-accent-soft)] text-action-primary-bg border border-dashed border-border-strong'\n : 'bg-bg-sunken text-text-secondary'\n }`}\n >\n {event}\n </span>\n ))}\n </div>\n {webhook.agentIds.length > 0 && (\n <div className=\"flex items-center gap-1 mt-2\">\n <Bot className=\"size-3 text-text-muted\" />\n <span className=\"text-xs text-text-muted\">\n {tr('webhooks.scopedTo', 'Scoped to')} {webhook.agentIds.length}{' '}\n {tr('webhooks.agent', 'agent')}\n {webhook.agentIds.length !== 1 ? 's' : ''}\n </span>\n </div>\n )}\n {webhook.lastError && (\n <p className=\"text-sm text-status-error-text mt-2\">\n {tr('webhooks.lastError', 'Last error')}: {webhook.lastError}\n </p>\n )}\n <div className=\"flex items-center gap-4 mt-3 text-xs text-text-secondary flex-wrap\">\n <span>\n {tr('webhooks.lastDelivery', 'Last delivery')}:{' '}\n {formatTimestamp(webhook.lastDelivery)}\n </span>\n <span>\n {webhook.deliveryCount} {tr('webhooks.deliveries', 'deliveries')},{' '}\n {webhook.failureCount} {tr('webhooks.failures', 'failures')}\n </span>\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => handleTestWebhook(webhook)}\n disabled={testingWebhook === webhook.id}\n className=\"flex items-center gap-1 px-3 py-1.5 text-sm bg-bg-sunken text-text-primary hover:bg-action-secondary-bg-hover rounded-md transition-colors disabled:opacity-50\"\n >\n {testingWebhook === webhook.id ? (\n <RefreshCw className=\"size-4 animate-spin\" />\n ) : (\n <Play className=\"size-4\" />\n )}\n {tr('webhooks.test', 'Test')}\n </button>\n <button\n type=\"button\"\n onClick={() => handleToggleWebhook(webhook)}\n className={`p-2 rounded-button transition-colors ${\n webhook.isActive\n ? 'bg-status-success-bg-subtle text-status-success-text hover:bg-bg-sunken'\n : 'bg-bg-sunken text-text-secondary hover:bg-action-secondary-bg-hover'\n }`}\n title={\n webhook.isActive\n ? tr('webhooks.disable', 'Disable')\n : tr('webhooks.enable', 'Enable')\n }\n >\n {webhook.isActive ? <Check className=\"size-4\" /> : <X className=\"size-4\" />}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n nativeImpact('medium');\n void handleDeleteWebhook(webhook);\n }}\n className=\"p-2 text-status-error-text hover:bg-status-error-bg rounded-md transition-colors\"\n title={tr('common.delete', 'Delete')}\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n </div>\n </Card>\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <EmptyState\n icon={<Webhook className=\"size-8 text-text-secondary\" />}\n title={\n searchQuery || statusFilter !== 'all'\n ? tr('webhooks.emptyFound', 'No webhooks found')\n : tr('webhooks.emptyConfigured', 'No webhooks configured')\n }\n description={\n searchQuery || statusFilter !== 'all'\n ? tr('webhooks.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'webhooks.emptyDescription',\n 'Create a webhook to receive notifications about events in your workspace'\n )\n }\n action={\n !searchQuery && statusFilter === 'all' ? (\n <div className=\"flex items-center gap-2\">\n {canCreateWebhook && (\n <button\n type=\"button\"\n onClick={handleCreateWebhook}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('webhooks.newWebhook', 'New Webhook')}\n </button>\n )}\n <GetHelpButton prompt=\"What is a Webhook in VibeControls and how do I configure one?\" />\n </div>\n ) : undefined\n }\n />\n )}\n </AnimatePresence>\n\n {/* Create Webhook Modal */}\n {showCreateForm && (\n <div className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\">\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"create-webhook-title\"\n className=\"bg-bg-surface rounded-lg p-6 w-full max-w-lg max-h-[90vh] overflow-y-auto\"\n >\n <h2 id=\"create-webhook-title\" className=\"text-lg font-semibold text-text-primary mb-4\">\n {tr('webhooks.newWebhook', 'New Webhook')}\n </h2>\n {createError && (\n <div className=\"mb-4 p-3 bg-status-error-bg text-status-error-text rounded-md text-sm\">\n {createError}\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('webhooks.name', 'Name')}\n </label>\n <input\n aria-label=\"control\"\n ref={webhookNameRef}\n type=\"text\"\n placeholder={tr('webhooks.namePlaceholder', 'Slack Notifications')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('webhooks.url', 'URL')}\n </label>\n <input\n aria-label=\"control\"\n ref={webhookUrlRef}\n type=\"url\"\n placeholder={tr(\n 'webhooks.urlPlaceholder',\n 'https://hooks.slack.com/services/...'\n )}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('webhooks.secret', 'Secret')}\n </label>\n <input\n aria-label=\"control\"\n ref={webhookSecretRef}\n type=\"password\"\n placeholder={tr('webhooks.secretPlaceholder', 'Optional signing secret')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n {tr('webhooks.events', 'Events')}\n </label>\n <div className=\"space-y-2 max-h-40 overflow-y-auto\">\n {WEBHOOK_EVENTS.map((event) => (\n <label key={event} className=\"flex items-center gap-2\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selectedEvents.includes(event)}\n onChange={() => handleToggleEvent(event)}\n className=\"size-4 rounded border-border-default\"\n />\n <span className=\"text-sm text-text-primary\">{event}</span>\n </label>\n ))}\n </div>\n {/* Custom events already selected */}\n {selectedEvents.filter(isCustomEvent).length > 0 && (\n <div className=\"flex flex-wrap gap-1 mt-2\">\n {selectedEvents.filter(isCustomEvent).map((event) => (\n <span\n key={event}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-[var(--color-accent-soft)] text-action-primary-bg border border-dashed border-border-strong rounded\"\n >\n {event}\n <button\n type=\"button\"\n onClick={() =>\n setSelectedEvents((prev) => prev.filter((e) => e !== event))\n }\n className=\"hover:text-status-error-text\"\n >\n <X className=\"size-3\" />\n </button>\n </span>\n ))}\n </div>\n )}\n {/* Custom event input */}\n <div className=\"flex items-center gap-2 mt-2\">\n <input\n aria-label=\"control\"\n type=\"text\"\n value={customEventInput}\n onChange={(e) => setCustomEventInput(e.target.value)}\n placeholder={tr('webhooks.customEventPlaceholder', 'custom:event-name')}\n className=\"flex-1 px-3 py-1.5 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder font-mono\"\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n const val = customEventInput.trim();\n if (\n /^[a-z][a-z0-9_-]*:[a-z][a-z0-9_.-]*$/.test(val) &&\n !selectedEvents.includes(val)\n ) {\n setSelectedEvents((prev) => [...prev, val]);\n setCustomEventInput('');\n }\n }\n }}\n />\n <button\n type=\"button\"\n onClick={() => {\n const val = customEventInput.trim();\n if (\n /^[a-z][a-z0-9_-]*:[a-z][a-z0-9_.-]*$/.test(val) &&\n !selectedEvents.includes(val)\n ) {\n setSelectedEvents((prev) => [...prev, val]);\n setCustomEventInput('');\n }\n }}\n className=\"px-3 py-1.5 text-sm bg-bg-sunken text-text-primary hover:bg-action-secondary-bg-hover rounded-md transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'webhooks.customEventHint',\n 'Add custom events using namespace:event format (e.g., deploy:started)'\n )}\n </p>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('webhooks.signingKey', 'Signing Key')}\n </label>\n <div className=\"relative\">\n <input\n aria-label=\"control\"\n ref={webhookSigningKeyRef}\n type={showSigningKey ? 'text' : 'password'}\n placeholder={tr(\n 'webhooks.signingKeyPlaceholder',\n 'HMAC signing key for delivery verification'\n )}\n className=\"w-full px-3 py-2 pr-10 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder font-mono text-sm\"\n />\n <button\n type=\"button\"\n onClick={() => setShowSigningKey(!showSigningKey)}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 p-1 rounded hover:bg-bg-elevated transition-colors\"\n >\n {showSigningKey ? (\n <EyeOff className=\"size-4 text-text-secondary\" />\n ) : (\n <Eye className=\"size-4 text-text-secondary\" />\n )}\n </button>\n </div>\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'webhooks.signingKeyHint',\n 'Used to sign payloads with HMAC-SHA256. Header: X-Webhook-Signature'\n )}\n </p>\n </div>\n {agents.length > 0 && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n {tr('webhooks.agentScope', 'Agent Scope')}\n </label>\n <p className=\"text-xs text-text-muted mb-2\">\n {tr(\n 'webhooks.agentScopeHint',\n 'Only fire for events from selected agents. Leave empty for all agents.'\n )}\n </p>\n <div className=\"space-y-2 max-h-32 overflow-y-auto\">\n {agents.map((agent: { id: string; name: string }) => (\n <label key={agent.id} className=\"flex items-center gap-2\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selectedAgentIds.includes(agent.id)}\n onChange={() =>\n setSelectedAgentIds((prev) =>\n prev.includes(agent.id)\n ? prev.filter((id) => id !== agent.id)\n : [...prev, agent.id]\n )\n }\n className=\"size-4 rounded border-border-default\"\n />\n <Bot className=\"size-3.5 text-text-secondary\" />\n <span className=\"text-sm text-text-primary\">{agent.name}</span>\n </label>\n ))}\n </div>\n </div>\n )}\n </div>\n <div className=\"flex flex-col-reverse sm:flex-row justify-end gap-3 mt-6\">\n <button\n type=\"button\"\n onClick={() => setShowCreateForm(false)}\n disabled={createLoading}\n className=\"px-4 py-2 text-text-primary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleSubmitCreateWebhook}\n disabled={createLoading}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {createLoading\n ? tr('webhooks.creating', 'Creating...')\n : tr('webhooks.createWebhook', 'Create Webhook')}\n </button>\n </div>\n </div>\n </div>\n )}\n\n <ConfirmDialog\n isOpen={bulkAction === 'enable'}\n onClose={() => setBulkAction(null)}\n onConfirm={() => handleBulkToggleWebhooks(true)}\n title=\"Enable selected webhooks\"\n message={`Enable ${inactiveSelectedWebhooks.length} selected webhook${inactiveSelectedWebhooks.length === 1 ? '' : 's'}?`}\n confirmLabel=\"Enable\"\n variant=\"default\"\n />\n\n <ConfirmDialog\n isOpen={bulkAction === 'disable'}\n onClose={() => setBulkAction(null)}\n onConfirm={() => handleBulkToggleWebhooks(false)}\n title=\"Disable selected webhooks\"\n message={`Disable ${activeSelectedWebhooks.length} selected webhook${activeSelectedWebhooks.length === 1 ? '' : 's'}?`}\n confirmLabel=\"Disable\"\n variant=\"warning\"\n />\n\n <ConfirmDialog\n isOpen={bulkAction === 'delete'}\n onClose={() => setBulkAction(null)}\n onConfirm={handleBulkDeleteWebhooks}\n title=\"Delete selected webhooks\"\n message={`Delete ${selectedWebhooks.length} selected webhook${selectedWebhooks.length === 1 ? '' : 's'}? This cannot be undone.`}\n confirmLabel=\"Delete\"\n variant=\"danger\"\n />\n\n <QuotaExhaustedDialog\n isOpen={quotaDialog !== null}\n onClose={() => setQuotaDialog(null)}\n onConfirm={() => {\n setQuotaDialog(null);\n navigate('/billing/plans');\n }}\n title={quotaDialog?.title || 'Quota Reached'}\n message={quotaDialog?.message || 'Upgrade your plan to continue.'}\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WebhooksPage.js","names":[],"sources":["../../src/pages/WebhooksPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport {\n Plus,\n Webhook,\n Grid,\n List,\n Check,\n X,\n RefreshCw,\n AlertTriangle,\n Play,\n Trash2,\n Eye,\n EyeOff,\n Bot,\n} from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { isNative, nativeImpact, nativeNotify } from '../utils/nativeBridge';\nimport { AnimatePresence } from 'framer-motion';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n EmptyState,\n Card,\n ErrorState,\n ConfirmDialog,\n QuotaExhaustedDialog,\n SelectionCheckbox,\n GetHelpButton,\n} from '@/components/shared';\nimport { useVibeControls, useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport {\n useListWebhooksQuery,\n useCreateWebhookMutation,\n useUpdateWebhookMutation,\n useTestWebhookMutation,\n useDeleteWebhookMutation,\n} from '@/generated/wspace-operations';\nimport type { Webhook as WebhookType } from '@/components/webhooks/types';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { WEBHOOK_EVENTS, isCustomEvent } from '@/components/webhooks/types';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { isQuotaExhaustedError } from '@/utils/quotaUtils';\n\ntype ViewMode = 'grid' | 'list';\n\nconst statusOptions = [\n { value: 'all', label: 'All Statuses' },\n { value: 'active', label: 'Active' },\n { value: 'inactive', label: 'Inactive' },\n { value: 'error', label: 'Has Errors' },\n];\n\n/**\n * Webhooks management page\n */\nexport function WebhooksPage() {\n useVibeControls();\n const navigate = useVibeNavigate();\n const { t } = useI18n();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n // Check permissions\n const canListWebhooks = hasPermission(VIBE_PERMISSIONS.WEBHOOK_LIST);\n const canCreateWebhook = hasPermission(VIBE_PERMISSIONS.WEBHOOK_CREATE);\n\n // Skip queries until permissions are loaded and user has permission\n // This prevents FORBIDDEN errors from triggering auth error handlers\n const shouldSkipQueries = permissionsLoading || !canListWebhooks;\n\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState('all');\n const [viewMode, setViewMode] = useState<ViewMode>('list');\n const [showCreateForm, setShowCreateForm] = useState(false);\n const [testingWebhook, setTestingWebhook] = useState<string | null>(null);\n const [createError, setCreateError] = useState<string | null>(null);\n const [createLoading, setCreateLoading] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [pageError, setPageError] = useState<string | null>(null);\n const [selectedEvents, setSelectedEvents] = useState<string[]>([]);\n const [selectedAgentIds, setSelectedAgentIds] = useState<string[]>([]);\n const [showSigningKey, setShowSigningKey] = useState(false);\n const [customEventInput, setCustomEventInput] = useState('');\n const [selectedWebhookIds, setSelectedWebhookIds] = useState<Set<string>>(new Set());\n const [bulkAction, setBulkAction] = useState<'enable' | 'disable' | 'delete' | null>(null);\n const [quotaDialog, setQuotaDialog] = useState<{ title: string; message: string } | null>(null);\n\n useEffect(() => {\n if (!successMessage) {\n return undefined;\n }\n\n const timeoutId = window.setTimeout(() => setSuccessMessage(null), 4000);\n return () => window.clearTimeout(timeoutId);\n }, [successMessage]);\n\n useEffect(() => {\n if (!showCreateForm) {\n return undefined;\n }\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape' && !createLoading) {\n setShowCreateForm(false);\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [createLoading, showCreateForm]);\n\n // Form refs\n const webhookNameRef = useRef<HTMLInputElement>(null);\n const webhookUrlRef = useRef<HTMLInputElement>(null);\n const webhookSecretRef = useRef<HTMLInputElement>(null);\n const webhookSigningKeyRef = useRef<HTMLInputElement>(null);\n\n const [createWebhook] = useCreateWebhookMutation({ refetchQueries: ['ListWebhooks'] });\n const [updateWebhook] = useUpdateWebhookMutation({ refetchQueries: ['ListWebhooks'] });\n const [testWebhook] = useTestWebhookMutation({ refetchQueries: ['ListWebhooks'] });\n const [deleteWebhookMutation] = useDeleteWebhookMutation({ refetchQueries: ['ListWebhooks'] });\n\n // Use cached initial data for agents dropdown\n const { agents: cachedAgents } = useCachedInitialData();\n const agents: Array<{ id: string; name: string }> = useMemo(\n () =>\n cachedAgents.map((a) => ({\n id: a.id,\n name: a.name,\n })),\n [cachedAgents]\n );\n\n // Fetch webhooks from GraphQL API\n const { data, loading, error, refetch } = useListWebhooksQuery({\n variables: {\n filter: {\n isActive:\n statusFilter === 'active' ? true : statusFilter === 'inactive' ? false : undefined,\n },\n pagination: { limit: 50 },\n },\n fetchPolicy: 'cache-and-network',\n skip: shouldSkipQueries,\n });\n\n // Map GraphQL response to Webhook type\n const webhooks: WebhookType[] = useMemo(() => {\n const rawWebhooks = data?.webhooks?.items ?? [];\n return rawWebhooks.map((webhook: NonNullable<typeof rawWebhooks>[number]) => ({\n id: webhook.id,\n workspaceId: webhook.workspaceId,\n name: webhook.name,\n url: webhook.url,\n events: webhook.events || [],\n agentIds: webhook.agentIds || [],\n secret: null,\n signingKey: webhook.signingKey || null,\n isActive: webhook.isActive,\n lastDelivery: webhook.lastDelivery || null,\n lastError: webhook.lastError || null,\n deliveryCount: webhook.deliveryCount || 0,\n failureCount: webhook.failureCount || 0,\n metadata: {},\n createdAt: webhook.createdAt,\n updatedAt: webhook.updatedAt,\n }));\n }, [data?.webhooks?.items]);\n\n // Filter webhooks based on search and error status (client-side filtering)\n const filteredWebhooks = useMemo(() => {\n return webhooks.filter((webhook) => {\n // Search filter\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n webhook.name.toLowerCase().includes(query) ||\n webhook.url.toLowerCase().includes(query) ||\n webhook.events.some((e) => e.toLowerCase().includes(query));\n if (!matchesSearch) return false;\n }\n\n // Error filter (additional client-side filter)\n if (statusFilter === 'error' && !webhook.lastError) return false;\n\n return true;\n });\n }, [webhooks, searchQuery, statusFilter]);\n\n const selectedWebhooks = useMemo(\n () => webhooks.filter((webhook) => selectedWebhookIds.has(webhook.id)),\n [webhooks, selectedWebhookIds]\n );\n const activeSelectedWebhooks = selectedWebhooks.filter((webhook) => webhook.isActive);\n const inactiveSelectedWebhooks = selectedWebhooks.filter((webhook) => !webhook.isActive);\n const allVisibleSelected =\n filteredWebhooks.length > 0 &&\n filteredWebhooks.every((webhook) => selectedWebhookIds.has(webhook.id));\n const partiallySelected =\n filteredWebhooks.some((webhook) => selectedWebhookIds.has(webhook.id)) && !allVisibleSelected;\n\n const handleCreateWebhook = () => {\n setCreateError(null);\n setPageError(null);\n setSelectedEvents([]);\n setSelectedAgentIds([]);\n setShowSigningKey(false);\n setCustomEventInput('');\n setShowCreateForm(true);\n };\n\n const handleToggleEvent = (event: string) => {\n setSelectedEvents((prev) =>\n prev.includes(event) ? prev.filter((e) => e !== event) : [...prev, event]\n );\n };\n\n const handleSubmitCreateWebhook = async () => {\n const name = webhookNameRef.current?.value?.trim();\n const url = webhookUrlRef.current?.value?.trim();\n const secret = webhookSecretRef.current?.value?.trim() || undefined;\n const signingKey = webhookSigningKeyRef.current?.value?.trim() || undefined;\n\n if (!name) {\n setCreateError(tr('webhooks.nameRequired', 'Name is required'));\n return;\n }\n if (!url) {\n setCreateError(tr('webhooks.urlRequired', 'URL is required'));\n return;\n }\n // Validate URL format\n try {\n const parsedUrl = new URL(url);\n if (!['http:', 'https:'].includes(parsedUrl.protocol)) {\n setCreateError(\n tr('webhooks.urlProtocolRequired', 'URL must use http:// or https:// protocol')\n );\n return;\n }\n } catch {\n setCreateError(\n tr('webhooks.urlInvalid', 'Please enter a valid URL (e.g., https://example.com/webhook)')\n );\n return;\n }\n if (selectedEvents.length === 0) {\n setCreateError(tr('webhooks.eventRequired', 'At least one event must be selected'));\n return;\n }\n\n setCreateLoading(true);\n setCreateError(null);\n setPageError(null);\n try {\n await createWebhook({\n variables: {\n input: {\n name,\n url,\n events: selectedEvents,\n secret,\n ...(signingKey ? { signingKey } : {}),\n ...(selectedAgentIds.length > 0 ? { agentIds: selectedAgentIds } : {}),\n },\n },\n });\n setShowCreateForm(false);\n setSuccessMessage(tr('webhooks.createdSuccess', 'Webhook created successfully'));\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowCreateForm(false);\n setQuotaDialog({\n title: 'Webhook Quota Reached',\n message:\n 'You have reached the webhook limit for your current plan. Upgrade to create more webhooks.',\n });\n return;\n }\n\n setCreateError(\n err instanceof Error ? err.message : tr('webhooks.failedCreate', 'Failed to create webhook')\n );\n } finally {\n setCreateLoading(false);\n }\n };\n\n const handleToggleWebhook = async (webhook: WebhookType) => {\n try {\n setPageError(null);\n await updateWebhook({\n variables: { id: webhook.id, input: { isActive: !webhook.isActive } },\n });\n setSuccessMessage(\n webhook.isActive\n ? tr('webhooks.disabledSuccess', 'Webhook disabled successfully')\n : tr('webhooks.enabledSuccess', 'Webhook enabled successfully')\n );\n } catch (err) {\n console.error('Failed to toggle webhook:', err);\n setPageError(\n err instanceof Error\n ? err.message\n : tr('webhooks.failedToggle', 'Failed to update webhook state')\n );\n }\n };\n\n const handleTestWebhook = async (webhook: WebhookType) => {\n setTestingWebhook(webhook.id);\n try {\n setPageError(null);\n await testWebhook({ variables: { id: webhook.id } });\n setSuccessMessage(tr('webhooks.testSuccess', 'Webhook test delivery completed'));\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setQuotaDialog({\n title: 'Webhook Test Quota Reached',\n message:\n 'You have used the webhook test quota for your current plan. Upgrade to continue testing deliveries.',\n });\n return;\n }\n\n console.error('Failed to test webhook:', err);\n setPageError(\n err instanceof Error ? err.message : tr('webhooks.failedTest', 'Failed to test webhook')\n );\n } finally {\n setTestingWebhook(null);\n }\n };\n\n const handleDeleteWebhook = async (webhook: WebhookType) => {\n try {\n setPageError(null);\n await deleteWebhookMutation({ variables: { id: webhook.id } });\n setSuccessMessage(tr('webhooks.deletedSuccess', 'Webhook deleted successfully'));\n if (isNative()) void nativeNotify('success');\n } catch (err) {\n console.error('Failed to delete webhook:', err);\n setPageError(\n err instanceof Error ? err.message : tr('webhooks.failedDelete', 'Failed to delete webhook')\n );\n if (isNative()) void nativeNotify('error');\n }\n };\n\n const toggleWebhookSelection = (webhookId: string, checked: boolean) => {\n setSelectedWebhookIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(webhookId);\n } else {\n next.delete(webhookId);\n }\n return next;\n });\n };\n\n const toggleAllVisibleWebhooks = (checked: boolean) => {\n setSelectedWebhookIds((prev) => {\n const next = new Set(prev);\n filteredWebhooks.forEach((webhook) => {\n if (checked) {\n next.add(webhook.id);\n } else {\n next.delete(webhook.id);\n }\n });\n return next;\n });\n };\n\n const clearSelection = () => setSelectedWebhookIds(new Set());\n\n const handleBulkToggleWebhooks = async (enable: boolean) => {\n const targets = enable ? inactiveSelectedWebhooks : activeSelectedWebhooks;\n if (targets.length === 0) return;\n\n const results = await Promise.allSettled(\n targets.map((webhook) =>\n updateWebhook({ variables: { id: webhook.id, input: { isActive: enable } } })\n )\n );\n\n const succeeded = results.filter((result) => result.status === 'fulfilled').length;\n const failed = results.length - succeeded;\n\n setSuccessMessage(\n `${enable ? 'Enabled' : 'Disabled'} ${succeeded} webhook${succeeded === 1 ? '' : 's'}${failed > 0 ? ` • ${failed} failed` : ''}`\n );\n setTimeout(() => setSuccessMessage(null), 4000);\n setBulkAction(null);\n clearSelection();\n await refetch();\n };\n\n const handleBulkDeleteWebhooks = async () => {\n if (selectedWebhooks.length === 0) return;\n\n const results = await Promise.allSettled(\n selectedWebhooks.map((webhook) => deleteWebhookMutation({ variables: { id: webhook.id } }))\n );\n\n const succeeded = results.filter((result) => result.status === 'fulfilled').length;\n const failed = results.length - succeeded;\n\n setSuccessMessage(\n `Deleted ${succeeded} webhook${succeeded === 1 ? '' : 's'}${failed > 0 ? ` • ${failed} failed` : ''}`\n );\n setTimeout(() => setSuccessMessage(null), 4000);\n setBulkAction(null);\n clearSelection();\n await refetch();\n };\n\n // Stats\n const stats = {\n total: webhooks.length,\n active: webhooks.filter((w) => w.isActive).length,\n withErrors: webhooks.filter((w) => w.lastError).length,\n };\n\n const formatTimestamp = (timestamp: string | null) => {\n if (!timestamp) return tr('webhooks.never', 'Never');\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return tr('webhooks.justNow', 'Just now');\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return `${Math.floor(diff / 86400000)}d ago`;\n };\n\n // Guard states rendered inside MotionPage to prevent unmount/remount animation replay\n // Gate full render until permissions resolve — avoids flashing the page\n // before the AccessDenied check can run.\n if (permissionsLoading) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <PageSkeleton variant=\"rows\" count={6} />\n </MotionPage>\n );\n }\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canListWebhooks) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <AccessDenied\n title={tr('common.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'webhooks.accessDeniedDescription',\n \"You don't have permission to view webhooks.\"\n )}\n details=\"Required permission: webhook:list\"\n />\n </MotionPage>\n );\n }\n\n if (error && webhooks.length === 0) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <ErrorState\n title={tr('webhooks.failedLoad', 'Failed to load webhooks')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n {/* Success Toast */}\n {successMessage && (\n <div className=\"fixed top-4 left-4 right-4 sm:left-auto z-50 bg-status-success-bg text-status-success-text px-4 py-3 rounded-md shadow-lg\">\n {successMessage}\n </div>\n )}\n\n {pageError && (\n <div\n role=\"alert\"\n className=\"rounded-card border border-status-error-border bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text\"\n >\n {pageError}\n </div>\n )}\n\n <PageHeader\n title={tr('nav.webhooks', 'Webhooks')}\n description={`${stats.active} of ${stats.total} webhooks active`}\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title={tr('webhooks.refreshTitle', 'Refresh webhooks')}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n <PermissionButton permission={VIBE_PERMISSIONS.WEBHOOK_CREATE}>\n <button\n type=\"button\"\n onClick={handleCreateWebhook}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('webhooks.newWebhook', 'New Webhook')}\n </button>\n </PermissionButton>\n </div>\n }\n />\n\n {/* Filters Bar */}\n <div className=\"space-y-3\">\n {/* Row 1: Search + View Toggle */}\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('webhooks.searchPlaceholder', 'Search webhooks...')}\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n {/* View Toggle */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('webhooks.gridViewTitle', 'Grid view')}\n >\n <Grid className=\"size-4 text-text-secondary\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('webhooks.listViewTitle', 'List view')}\n >\n <List className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n {/* Row 2: Filters */}\n <div className=\"flex items-center gap-2 sm:gap-3\">\n <FilterDropdown\n label={tr('webhooks.status', 'Status')}\n value={statusFilter}\n options={statusOptions}\n onChange={setStatusFilter}\n />\n </div>\n </div>\n\n {/* Quick Actions */}\n {stats.withErrors > 0 && (\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setStatusFilter('error')}\n className={`flex items-center gap-2 px-3 py-1.5 text-sm rounded-full transition-colors ${\n statusFilter === 'error'\n ? 'bg-status-error-bg text-status-error-text'\n : 'bg-bg-sunken text-text-secondary hover:bg-action-secondary-bg-hover'\n }`}\n >\n <AlertTriangle className=\"size-3\" />\n {tr('webhooks.withErrors', 'With Errors')} ({stats.withErrors})\n </button>\n </div>\n )}\n\n {/* Results Count */}\n {(searchQuery || statusFilter !== 'all') && (\n <p className=\"text-sm text-text-secondary\">\n {tr('webhooks.showingResults', 'Showing')} {filteredWebhooks.length}{' '}\n {tr('webhooks.of', 'of')} {webhooks.length} {tr('nav.webhooks', 'webhooks')}\n {loading && (\n <span className=\"ml-2 text-text-muted\">\n ({tr('webhooks.refreshing', 'refreshing...')})\n </span>\n )}\n </p>\n )}\n\n {filteredWebhooks.length > 0 && (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-4\">\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex items-center gap-2\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisibleWebhooks}\n ariaLabel=\"Select all visible webhooks\"\n />\n <span className=\"text-sm font-medium text-text-primary\">Select all visible</span>\n </div>\n {selectedWebhookIds.size > 0 && (\n <button\n type=\"button\"\n onClick={clearSelection}\n className=\"text-sm text-text-secondary hover:text-text-primary\"\n >\n Clear selection\n </button>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-full bg-bg-sunken px-3 py-1 text-sm font-medium text-text-secondary\">\n {selectedWebhookIds.size} selected\n </span>\n <button\n type=\"button\"\n onClick={() => setBulkAction('enable')}\n disabled={inactiveSelectedWebhooks.length === 0}\n className=\"rounded-md border border-status-success-border px-3 py-2 text-sm font-medium text-status-success-text hover:bg-status-success-bg disabled:cursor-not-allowed disabled:opacity-50\"\n >\n Enable Selected\n {inactiveSelectedWebhooks.length > 0 ? ` (${inactiveSelectedWebhooks.length})` : ''}\n </button>\n <button\n type=\"button\"\n onClick={() => setBulkAction('disable')}\n disabled={activeSelectedWebhooks.length === 0}\n className=\"rounded-md border border-status-warning-border px-3 py-2 text-sm font-medium text-status-warning-text hover:bg-status-warning-bg disabled:cursor-not-allowed disabled:opacity-50\"\n >\n Disable Selected\n {activeSelectedWebhooks.length > 0 ? ` (${activeSelectedWebhooks.length})` : ''}\n </button>\n <button\n type=\"button\"\n onClick={() => setBulkAction('delete')}\n disabled={selectedWebhooks.length === 0}\n className=\"rounded-md border border-status-error-bg px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg disabled:cursor-not-allowed disabled:opacity-50\"\n >\n Delete Selected\n {selectedWebhooks.length > 0 ? ` (${selectedWebhooks.length})` : ''}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Webhooks Display */}\n <AnimatePresence mode=\"wait\">\n {loading && webhooks.length === 0 ? (\n <PageSkeleton variant=\"rows\" count={6} />\n ) : filteredWebhooks.length > 0 ? (\n <MotionList\n key={`webhook-list-${viewMode}`}\n className={viewMode === 'grid' ? 'grid grid-cols-1 gap-4 xl:grid-cols-2' : 'space-y-4'}\n >\n {filteredWebhooks.map((webhook) => (\n <MotionItem key={webhook.id} variant=\"fadeRight\">\n <Card className=\"p-4\">\n <div className=\"flex flex-col sm:flex-row sm:items-start justify-between gap-4\">\n <div className=\"flex items-start gap-4 flex-1 min-w-0\">\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selectedWebhookIds.has(webhook.id)}\n onChange={(checked) => toggleWebhookSelection(webhook.id, checked)}\n ariaLabel={`Select webhook ${webhook.name}`}\n className=\"mt-2\"\n />\n </div>\n <div\n className={`size-10 rounded-lg flex items-center justify-center flex-shrink-0 ${\n webhook.isActive\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-bg-sunken text-text-secondary'\n }`}\n >\n <Webhook className=\"size-5\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary\">{webhook.name}</h3>\n <span\n className={`px-2 py-0.5 text-xs rounded-full ${\n webhook.isActive\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-bg-sunken text-text-secondary'\n }`}\n >\n {webhook.isActive\n ? tr('webhooks.active', 'Active')\n : tr('webhooks.inactive', 'Inactive')}\n </span>\n {webhook.lastError && (\n <span className=\"px-2 py-0.5 text-xs rounded-full bg-status-error-bg text-status-error-text\">\n {tr('webhooks.error', 'Error')}\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mt-1 font-mono truncate\">\n {webhook.url}\n </p>\n <div className=\"flex flex-wrap gap-1 mt-2\">\n {webhook.events.map((event) => (\n <span\n key={event}\n className={`px-2 py-0.5 text-xs rounded ${\n isCustomEvent(event)\n ? 'bg-[var(--color-accent-soft)] text-text-primary border border-dashed border-border-strong'\n : 'bg-bg-sunken text-text-secondary'\n }`}\n >\n {event}\n </span>\n ))}\n </div>\n {webhook.agentIds.length > 0 && (\n <div className=\"flex items-center gap-1 mt-2\">\n <Bot className=\"size-3 text-text-muted\" />\n <span className=\"text-xs text-text-muted\">\n {tr('webhooks.scopedTo', 'Scoped to')} {webhook.agentIds.length}{' '}\n {tr('webhooks.agent', 'agent')}\n {webhook.agentIds.length !== 1 ? 's' : ''}\n </span>\n </div>\n )}\n {webhook.lastError && (\n <p className=\"text-sm text-status-error-text mt-2\">\n {tr('webhooks.lastError', 'Last error')}: {webhook.lastError}\n </p>\n )}\n <div className=\"flex items-center gap-4 mt-3 text-xs text-text-secondary flex-wrap\">\n <span>\n {tr('webhooks.lastDelivery', 'Last delivery')}:{' '}\n {formatTimestamp(webhook.lastDelivery)}\n </span>\n <span>\n {webhook.deliveryCount} {tr('webhooks.deliveries', 'deliveries')},{' '}\n {webhook.failureCount} {tr('webhooks.failures', 'failures')}\n </span>\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => handleTestWebhook(webhook)}\n disabled={testingWebhook === webhook.id}\n className=\"flex items-center gap-1 px-3 py-1.5 text-sm bg-bg-sunken text-text-primary hover:bg-action-secondary-bg-hover rounded-md transition-colors disabled:opacity-50\"\n >\n {testingWebhook === webhook.id ? (\n <RefreshCw className=\"size-4 animate-spin\" />\n ) : (\n <Play className=\"size-4\" />\n )}\n {tr('webhooks.test', 'Test')}\n </button>\n <button\n type=\"button\"\n onClick={() => handleToggleWebhook(webhook)}\n className={`p-2 rounded-button transition-colors ${\n webhook.isActive\n ? 'bg-status-success-bg-subtle text-status-success-text hover:bg-bg-sunken'\n : 'bg-bg-sunken text-text-secondary hover:bg-action-secondary-bg-hover'\n }`}\n title={\n webhook.isActive\n ? tr('webhooks.disable', 'Disable')\n : tr('webhooks.enable', 'Enable')\n }\n >\n {webhook.isActive ? <Check className=\"size-4\" /> : <X className=\"size-4\" />}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n nativeImpact('medium');\n void handleDeleteWebhook(webhook);\n }}\n className=\"p-2 text-status-error-text hover:bg-status-error-bg rounded-md transition-colors\"\n title={tr('common.delete', 'Delete')}\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n </div>\n </Card>\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <EmptyState\n icon={<Webhook className=\"size-8 text-text-secondary\" />}\n title={\n searchQuery || statusFilter !== 'all'\n ? tr('webhooks.emptyFound', 'No webhooks found')\n : tr('webhooks.emptyConfigured', 'No webhooks configured')\n }\n description={\n searchQuery || statusFilter !== 'all'\n ? tr('webhooks.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'webhooks.emptyDescription',\n 'Create a webhook to receive notifications about events in your workspace'\n )\n }\n action={\n !searchQuery && statusFilter === 'all' ? (\n <div className=\"flex items-center gap-2\">\n {canCreateWebhook && (\n <button\n type=\"button\"\n onClick={handleCreateWebhook}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('webhooks.newWebhook', 'New Webhook')}\n </button>\n )}\n <GetHelpButton prompt=\"What is a Webhook in VibeControls and how do I configure one?\" />\n </div>\n ) : undefined\n }\n />\n )}\n </AnimatePresence>\n\n {/* Create Webhook Modal */}\n {showCreateForm && (\n <div className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\">\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"create-webhook-title\"\n className=\"bg-bg-surface rounded-lg p-6 w-full max-w-lg max-h-[90vh] overflow-y-auto\"\n >\n <h2 id=\"create-webhook-title\" className=\"text-lg font-semibold text-text-primary mb-4\">\n {tr('webhooks.newWebhook', 'New Webhook')}\n </h2>\n {createError && (\n <div className=\"mb-4 p-3 bg-status-error-bg text-status-error-text rounded-md text-sm\">\n {createError}\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('webhooks.name', 'Name')}\n </label>\n <input\n aria-label=\"control\"\n ref={webhookNameRef}\n type=\"text\"\n placeholder={tr('webhooks.namePlaceholder', 'Slack Notifications')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('webhooks.url', 'URL')}\n </label>\n <input\n aria-label=\"control\"\n ref={webhookUrlRef}\n type=\"url\"\n placeholder={tr(\n 'webhooks.urlPlaceholder',\n 'https://hooks.slack.com/services/...'\n )}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('webhooks.secret', 'Secret')}\n </label>\n <input\n aria-label=\"control\"\n ref={webhookSecretRef}\n type=\"password\"\n placeholder={tr('webhooks.secretPlaceholder', 'Optional signing secret')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n {tr('webhooks.events', 'Events')}\n </label>\n <div className=\"space-y-2 max-h-40 overflow-y-auto\">\n {WEBHOOK_EVENTS.map((event) => (\n <label key={event} className=\"flex items-center gap-2\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selectedEvents.includes(event)}\n onChange={() => handleToggleEvent(event)}\n className=\"size-4 rounded border-border-default\"\n />\n <span className=\"text-sm text-text-primary\">{event}</span>\n </label>\n ))}\n </div>\n {/* Custom events already selected */}\n {selectedEvents.filter(isCustomEvent).length > 0 && (\n <div className=\"flex flex-wrap gap-1 mt-2\">\n {selectedEvents.filter(isCustomEvent).map((event) => (\n <span\n key={event}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-[var(--color-accent-soft)] text-text-primary border border-dashed border-border-strong rounded\"\n >\n {event}\n <button\n type=\"button\"\n onClick={() =>\n setSelectedEvents((prev) => prev.filter((e) => e !== event))\n }\n className=\"hover:text-status-error-text\"\n >\n <X className=\"size-3\" />\n </button>\n </span>\n ))}\n </div>\n )}\n {/* Custom event input */}\n <div className=\"flex items-center gap-2 mt-2\">\n <input\n aria-label=\"control\"\n type=\"text\"\n value={customEventInput}\n onChange={(e) => setCustomEventInput(e.target.value)}\n placeholder={tr('webhooks.customEventPlaceholder', 'custom:event-name')}\n className=\"flex-1 px-3 py-1.5 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder font-mono\"\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n const val = customEventInput.trim();\n if (\n /^[a-z][a-z0-9_-]*:[a-z][a-z0-9_.-]*$/.test(val) &&\n !selectedEvents.includes(val)\n ) {\n setSelectedEvents((prev) => [...prev, val]);\n setCustomEventInput('');\n }\n }\n }}\n />\n <button\n type=\"button\"\n onClick={() => {\n const val = customEventInput.trim();\n if (\n /^[a-z][a-z0-9_-]*:[a-z][a-z0-9_.-]*$/.test(val) &&\n !selectedEvents.includes(val)\n ) {\n setSelectedEvents((prev) => [...prev, val]);\n setCustomEventInput('');\n }\n }}\n className=\"px-3 py-1.5 text-sm bg-bg-sunken text-text-primary hover:bg-action-secondary-bg-hover rounded-md transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'webhooks.customEventHint',\n 'Add custom events using namespace:event format (e.g., deploy:started)'\n )}\n </p>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('webhooks.signingKey', 'Signing Key')}\n </label>\n <div className=\"relative\">\n <input\n aria-label=\"control\"\n ref={webhookSigningKeyRef}\n type={showSigningKey ? 'text' : 'password'}\n placeholder={tr(\n 'webhooks.signingKeyPlaceholder',\n 'HMAC signing key for delivery verification'\n )}\n className=\"w-full px-3 py-2 pr-10 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder font-mono text-sm\"\n />\n <button\n type=\"button\"\n onClick={() => setShowSigningKey(!showSigningKey)}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 p-1 rounded hover:bg-bg-elevated transition-colors\"\n >\n {showSigningKey ? (\n <EyeOff className=\"size-4 text-text-secondary\" />\n ) : (\n <Eye className=\"size-4 text-text-secondary\" />\n )}\n </button>\n </div>\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'webhooks.signingKeyHint',\n 'Used to sign payloads with HMAC-SHA256. Header: X-Webhook-Signature'\n )}\n </p>\n </div>\n {agents.length > 0 && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n {tr('webhooks.agentScope', 'Agent Scope')}\n </label>\n <p className=\"text-xs text-text-muted mb-2\">\n {tr(\n 'webhooks.agentScopeHint',\n 'Only fire for events from selected agents. Leave empty for all agents.'\n )}\n </p>\n <div className=\"space-y-2 max-h-32 overflow-y-auto\">\n {agents.map((agent: { id: string; name: string }) => (\n <label key={agent.id} className=\"flex items-center gap-2\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selectedAgentIds.includes(agent.id)}\n onChange={() =>\n setSelectedAgentIds((prev) =>\n prev.includes(agent.id)\n ? prev.filter((id) => id !== agent.id)\n : [...prev, agent.id]\n )\n }\n className=\"size-4 rounded border-border-default\"\n />\n <Bot className=\"size-3.5 text-text-secondary\" />\n <span className=\"text-sm text-text-primary\">{agent.name}</span>\n </label>\n ))}\n </div>\n </div>\n )}\n </div>\n <div className=\"flex flex-col-reverse sm:flex-row justify-end gap-3 mt-6\">\n <button\n type=\"button\"\n onClick={() => setShowCreateForm(false)}\n disabled={createLoading}\n className=\"px-4 py-2 text-text-primary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleSubmitCreateWebhook}\n disabled={createLoading}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {createLoading\n ? tr('webhooks.creating', 'Creating...')\n : tr('webhooks.createWebhook', 'Create Webhook')}\n </button>\n </div>\n </div>\n </div>\n )}\n\n <ConfirmDialog\n isOpen={bulkAction === 'enable'}\n onClose={() => setBulkAction(null)}\n onConfirm={() => handleBulkToggleWebhooks(true)}\n title=\"Enable selected webhooks\"\n message={`Enable ${inactiveSelectedWebhooks.length} selected webhook${inactiveSelectedWebhooks.length === 1 ? '' : 's'}?`}\n confirmLabel=\"Enable\"\n variant=\"default\"\n />\n\n <ConfirmDialog\n isOpen={bulkAction === 'disable'}\n onClose={() => setBulkAction(null)}\n onConfirm={() => handleBulkToggleWebhooks(false)}\n title=\"Disable selected webhooks\"\n message={`Disable ${activeSelectedWebhooks.length} selected webhook${activeSelectedWebhooks.length === 1 ? '' : 's'}?`}\n confirmLabel=\"Disable\"\n variant=\"warning\"\n />\n\n <ConfirmDialog\n isOpen={bulkAction === 'delete'}\n onClose={() => setBulkAction(null)}\n onConfirm={handleBulkDeleteWebhooks}\n title=\"Delete selected webhooks\"\n message={`Delete ${selectedWebhooks.length} selected webhook${selectedWebhooks.length === 1 ? '' : 's'}? This cannot be undone.`}\n confirmLabel=\"Delete\"\n variant=\"danger\"\n />\n\n <QuotaExhaustedDialog\n isOpen={quotaDialog !== null}\n onClose={() => setQuotaDialog(null)}\n onConfirm={() => {\n setQuotaDialog(null);\n navigate('/billing/plans');\n }}\n title={quotaDialog?.title || 'Quota Reached'}\n message={quotaDialog?.message || 'Upgrade your plan to continue.'}\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|
|
@@ -49,12 +49,12 @@ var he = [
|
|
|
49
49
|
icon: v
|
|
50
50
|
}
|
|
51
51
|
], _e = {
|
|
52
|
-
info: "bg-[var(--color-accent-soft)] text-
|
|
52
|
+
info: "bg-[var(--color-accent-soft)] text-text-primary",
|
|
53
53
|
warn: "bg-status-warning-bg-subtle text-status-warning-text",
|
|
54
54
|
error: "bg-status-error-bg-subtle text-status-error-text",
|
|
55
55
|
debug: "bg-bg-sunken text-text-secondary",
|
|
56
56
|
prompt: "bg-status-success-bg-subtle text-status-success-text",
|
|
57
|
-
response: "bg-[var(--color-accent-soft)] text-
|
|
57
|
+
response: "bg-[var(--color-accent-soft)] text-text-primary"
|
|
58
58
|
};
|
|
59
59
|
function S() {
|
|
60
60
|
let S = e(), { hasPermission: ve, isLoading: ye } = pe(), be = ve(ne.AGENT_READ), { sessionId: C } = fe(), w = t(), { agents: T, loading: xe } = n(), E = f(() => T.find((e) => e.isActive && e.tunnelUrl) ?? null, [T]), D = E?.id ?? "", O = !!D, { active: k } = te(E?.id ?? null), A = f(() => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AISessionDetailPage.js","names":[],"sources":["../../../src/pages/ai/AISessionDetailPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n MessageSquare,\n ScrollText,\n BarChart3,\n Settings,\n Send,\n ArrowLeft,\n XCircle,\n RefreshCw,\n} from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport {\n PageHeader,\n Card,\n LoadingState,\n ErrorState,\n EmptyState,\n StatCard,\n RequireAgentReachable,\n} from '@/components/shared';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useQueryBackedTab } from '@/hooks/useQueryBackedTab';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport type { AISessionLog, AISessionStats } from '@/services/aiApi';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n sendPromptToSession,\n fetchSessionLogs,\n fetchSessionStats,\n updateSessionConfig,\n terminateSession,\n} from '@/services/aiApi';\nimport { isSessionNotFoundError } from '@/utils/aiSessionNotFound';\n\ntype SessionTab = 'chat' | 'logs' | 'stats' | 'config';\nconst SESSION_TABS: readonly SessionTab[] = ['chat', 'logs', 'stats', 'config'];\n\nconst sessionTabs: { id: SessionTab; label: string; icon: typeof MessageSquare }[] = [\n { id: 'chat', label: 'Chat', icon: MessageSquare },\n { id: 'logs', label: 'Logs', icon: ScrollText },\n { id: 'stats', label: 'Stats', icon: BarChart3 },\n { id: 'config', label: 'Config', icon: Settings },\n];\n\nconst logTypeColors: Record<string, string> = {\n info: 'bg-[var(--color-accent-soft)] text-action-primary-bg',\n warn: 'bg-status-warning-bg-subtle text-status-warning-text',\n error: 'bg-status-error-bg-subtle text-status-error-text',\n debug: 'bg-bg-sunken text-text-secondary',\n prompt: 'bg-status-success-bg-subtle text-status-success-text',\n response: 'bg-[var(--color-accent-soft)] text-action-primary-bg',\n};\n\ninterface ChatMessage {\n role: 'user' | 'assistant';\n content: string;\n tokens?: { input: number; output: number };\n timestamp: string;\n}\n\n/**\n * AI session detail page with Chat, Logs, Stats, and Config tabs.\n */\nexport function AISessionDetailPage() {\n const tr = useTr();\n // Agent-centric page — gate on `agent:read` (defense-in-depth).\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canReadAgents = hasPermission(VIBE_PERMISSIONS.AGENT_READ);\n const { sessionId } = useParams<{ sessionId: string }>();\n const handleNavigate = useVibeNavigate();\n\n const { agents, loading: agentsLoading } = useCachedInitialData();\n const activeAgent = useMemo(\n () => agents.find((agent) => agent.isActive && agent.tunnelUrl) ?? null,\n [agents]\n );\n const agentId = activeAgent?.id ?? '';\n const hasAgent = !!agentId;\n // Canonical agent → profile → plugins/data hierarchy. Every aiApi call\n // takes an AgentRef = { id, profile } so the path can be constructed\n // explicitly at the callsite.\n const { active: activeProfile } = useActiveAgentProfile(activeAgent?.id ?? null);\n const agentRef = useMemo(\n () => ({ id: agentId, profile: activeProfile }),\n [agentId, activeProfile]\n );\n\n const [activeTab, setActiveTab] = useQueryBackedTab<SessionTab>(SESSION_TABS, 'chat');\n\n // ── Chat state ──────────────────────────────────────────────────────\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [promptInput, setPromptInput] = useState('');\n const [sending, setSending] = useState(false);\n\n const handleSendPrompt = async () => {\n if (!promptInput.trim() || !sessionId || !hasAgent) return;\n const userMessage: ChatMessage = {\n role: 'user',\n content: promptInput,\n timestamp: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, userMessage]);\n setPromptInput('');\n setSending(true);\n try {\n const result = await sendPromptToSession(agentRef, sessionId, userMessage.content);\n const content =\n ((result as Record<string, unknown>).content as string) ||\n ((result as Record<string, unknown>).response as string) ||\n JSON.stringify(result);\n const inputTokens = (result as Record<string, unknown>).inputTokens as number | undefined;\n const outputTokens = (result as Record<string, unknown>).outputTokens as number | undefined;\n const assistantMessage: ChatMessage = {\n role: 'assistant',\n content,\n tokens:\n inputTokens !== undefined ? { input: inputTokens, output: outputTokens ?? 0 } : undefined,\n timestamp: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, assistantMessage]);\n } catch (err) {\n const errorMessage: ChatMessage = {\n role: 'assistant',\n content: `Error: ${err instanceof Error ? err.message : 'Failed to get response'}`,\n timestamp: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, errorMessage]);\n } finally {\n setSending(false);\n }\n };\n\n // ── Logs state ──────────────────────────────────────────────────────\n const [logs, setLogs] = useState<AISessionLog[]>([]);\n const [logsLoading, setLogsLoading] = useState(false);\n const [logsError, setLogsError] = useState<string | null>(null);\n const [logTypeFilter, setLogTypeFilter] = useState('all');\n\n const loadLogs = useCallback(async () => {\n if (!sessionId || !hasAgent) return;\n setLogsLoading(true);\n setLogsError(null);\n try {\n const params: Record<string, unknown> = {};\n if (logTypeFilter !== 'all') params.type = logTypeFilter;\n const result = await fetchSessionLogs(agentRef, sessionId, params);\n setLogs(result);\n } catch (err) {\n // Pre-first-message: agent has no record of this session yet —\n // render an empty list, not an error.\n if (isSessionNotFoundError(err)) {\n setLogs([]);\n } else {\n setLogsError(err instanceof Error ? err.message : 'Failed to load logs');\n }\n } finally {\n setLogsLoading(false);\n }\n }, [agentRef, sessionId, logTypeFilter, hasAgent]);\n\n useEffect(() => {\n if (activeTab === 'logs') loadLogs();\n }, [activeTab, loadLogs]);\n\n // ── Stats state ─────────────────────────────────────────────────────\n const [stats, setStats] = useState<AISessionStats | null>(null);\n const [statsLoading, setStatsLoading] = useState(false);\n const [statsError, setStatsError] = useState<string | null>(null);\n\n const loadStats = useCallback(async () => {\n if (!sessionId || !hasAgent) return;\n setStatsLoading(true);\n setStatsError(null);\n try {\n const result = await fetchSessionStats(agentRef, sessionId);\n setStats(result);\n } catch (err) {\n if (isSessionNotFoundError(err)) {\n setStats(null);\n } else {\n setStatsError(err instanceof Error ? err.message : 'Failed to load stats');\n }\n } finally {\n setStatsLoading(false);\n }\n }, [agentRef, sessionId, hasAgent]);\n\n useEffect(() => {\n if (activeTab === 'stats') loadStats();\n }, [activeTab, loadStats]);\n\n // ── Config state ────────────────────────────────────────────────────\n const [configJson, setConfigJson] = useState('{}');\n const [savingConfig, setSavingConfig] = useState(false);\n const [configSaved, setConfigSaved] = useState(false);\n const [terminating, setTerminating] = useState(false);\n\n const handleSaveConfig = async () => {\n if (!sessionId || !hasAgent) return;\n setSavingConfig(true);\n try {\n const config = JSON.parse(configJson);\n await updateSessionConfig(agentRef, sessionId, config);\n setConfigSaved(true);\n setTimeout(() => setConfigSaved(false), 3000);\n } catch (err) {\n console.error('Failed to save config:', err);\n } finally {\n setSavingConfig(false);\n }\n };\n\n const handleTerminate = async () => {\n if (!sessionId || !hasAgent) return;\n if (!window.confirm('Are you sure you want to terminate this session? This cannot be undone.'))\n return;\n setTerminating(true);\n try {\n await terminateSession(agentRef, sessionId);\n handleNavigate('/ai/sessions');\n } catch (err) {\n console.error('Failed to terminate session:', err);\n } finally {\n setTerminating(false);\n }\n };\n\n if (permissionsLoading) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions…')} />\n </MotionPage>\n );\n }\n\n if (!canReadAgents) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <AccessDenied\n title={tr('vibecontrols.pages.ai.denied.title', 'Access Denied')}\n description={tr(\n 'vibecontrols.pages.ai.denied.description',\n 'You do not have permission to view AI features.'\n )}\n />\n </MotionPage>\n );\n }\n\n if (agentsLoading) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message=\"Loading...\" />\n </MotionPage>\n );\n }\n\n if (!hasAgent) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <EmptyState\n icon={<MessageSquare className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.pages.ai.noActiveAgent', 'No active agent')}\n description={tr(\n 'vibecontrols.pages.ai.noActiveAgentDescription',\n 'An active agent with a tunnel is required to view session details.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => handleNavigate('/agents')}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors text-sm\"\n >\n {tr('vibecontrols.pages.ai.goToAgents', 'Go to Agents')}\n </button>\n }\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <PageHeader\n title={`Session ${sessionId?.substring(0, 8) ?? ''}...`}\n description={tr(\n 'vibecontrols.pages.ai.sessionDescription',\n 'AI session details and interaction'\n )}\n breadcrumb={[\n { label: 'AI', path: '#' },\n { label: 'Sessions', path: '#' },\n { label: sessionId?.substring(0, 8) ?? '' },\n ]}\n actions={\n <button\n type=\"button\"\n onClick={() => handleNavigate('/ai/sessions')}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors text-sm\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('vibecontrols.pages.ai.back', 'Back')}\n </button>\n }\n />\n\n {/* Tab Navigation */}\n <div className=\"-mx-4 border-b border-border-default px-4 sm:-mx-6 sm:px-6\">\n <nav className=\"flex gap-1 overflow-x-auto pb-1\" role=\"tablist\">\n {sessionTabs.map((tab) => {\n const Icon = tab.icon;\n const isActive = activeTab === tab.id;\n return (\n <button\n type=\"button\"\n key={tab.id}\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => setActiveTab(tab.id)}\n className={`flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-t-md border-b-2 px-2.5 py-2 text-xs font-medium transition-colors sm:px-3 sm:py-2.5 sm:text-sm ${\n isActive\n ? 'border-action-primary-bg text-action-primary-bg'\n : 'border-transparent text-text-secondary hover:text-text-primary hover:border-border-default'\n }`}\n >\n <Icon className=\"size-3.5 sm:h-4 sm:w-4\" />\n {tab.label}\n </button>\n );\n })}\n </nav>\n </div>\n\n {/* Tab Content */}\n <RequireAgentReachable agentId={agentId || null}>\n {activeTab === 'chat' && (\n <div className=\"space-y-4\">\n {/* Messages */}\n <Card className=\"p-0\">\n <div className=\"divide-y divide-border-default min-h-[200px] max-h-[50vh] overflow-y-auto\">\n {messages.length === 0 ? (\n <div className=\"flex items-center justify-center h-48 text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.pages.ai.sendPromptToStart',\n 'Send a prompt to start the conversation'\n )}\n </div>\n ) : (\n messages.map((msg, idx) => (\n <div\n key={idx}\n className={`px-4 py-3 ${msg.role === 'assistant' ? 'bg-bg-sunken/50' : ''}`}\n >\n <div className=\"mb-1 flex flex-wrap items-center gap-2\">\n <span className=\"text-xs font-semibold text-text-primary uppercase\">\n {msg.role}\n </span>\n <span className=\"text-xs text-text-muted\">\n {new Date(msg.timestamp).toLocaleTimeString()}\n </span>\n {msg.tokens && (\n <span className=\"text-xs text-text-muted sm:ml-auto\">\n {msg.tokens.input + msg.tokens.output} tokens\n </span>\n )}\n </div>\n <div className=\"text-sm text-text-primary whitespace-pre-wrap\">\n {msg.content}\n </div>\n </div>\n ))\n )}\n {sending && (\n <div className=\"px-4 py-3 bg-bg-sunken/50\">\n <div className=\"flex items-center gap-2\">\n <RefreshCw className=\"size-3.5 text-text-secondary animate-spin\" />\n <span className=\"text-sm text-text-secondary\">\n {tr('vibecontrols.pages.ai.thinking', 'Thinking...')}\n </span>\n </div>\n </div>\n )}\n </div>\n </Card>\n\n {/* Prompt Input */}\n <div className=\"flex flex-col gap-2 sm:flex-row\">\n <textarea\n aria-label=\"Type your prompt...\"\n value={promptInput}\n onChange={(e) => setPromptInput(e.target.value)}\n rows={3}\n className=\"min-h-[7rem] flex-1 rounded-md border border-border-default bg-bg-sunken px-3 py-2 text-sm font-mono text-text-primary resize-none focus:outline-none focus:border-border-strong\"\n placeholder=\"Type your prompt...\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleSendPrompt();\n }\n }}\n />\n <button\n type=\"button\"\n onClick={handleSendPrompt}\n disabled={sending || !promptInput.trim()}\n className=\"flex items-center justify-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm text-action-primary-text transition-colors hover:bg-action-primary-bg-hover disabled:opacity-50 sm:self-end\"\n >\n <Send className=\"size-4\" />\n {tr('vibecontrols.pages.ai.send', 'Send')}\n </button>\n </div>\n </div>\n )}\n\n {activeTab === 'logs' && (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <select\n value={logTypeFilter}\n onChange={(e) => setLogTypeFilter(e.target.value)}\n className=\"px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm\"\n >\n <option value=\"all\">All Types</option>\n <option value=\"info\">Info</option>\n <option value=\"warn\">Warning</option>\n <option value=\"error\">Error</option>\n <option value=\"debug\">Debug</option>\n <option value=\"prompt\">Prompt</option>\n <option value=\"response\">Response</option>\n </select>\n <button\n type=\"button\"\n onClick={loadLogs}\n disabled={logsLoading}\n className=\"p-2 hover:bg-bg-sunken rounded-md transition-colors\"\n >\n <RefreshCw\n className={`size-4 text-text-secondary ${logsLoading ? 'animate-spin' : ''}`}\n />\n </button>\n </div>\n\n {logsLoading ? (\n <LoadingState message=\"Loading logs...\" />\n ) : logsError ? (\n <ErrorState title=\"Failed to load logs\" message={logsError} onRetry={loadLogs} />\n ) : logs.length === 0 ? (\n <EmptyState\n icon={<ScrollText className=\"size-6 text-text-secondary\" />}\n title=\"No logs\"\n description=\"Session logs will appear here\"\n />\n ) : (\n <Card className=\"p-0\">\n <div className=\"divide-y divide-border-default max-h-[60vh] overflow-y-auto\">\n {logs.map((log) => (\n <div key={log.id} className=\"px-4 py-2 flex items-start gap-3 text-sm\">\n <span\n className={`px-1.5 py-0.5 text-xs rounded font-medium flex-shrink-0 mt-0.5 ${logTypeColors[log.type] ?? 'bg-bg-sunken text-text-secondary'}`}\n >\n {log.type}\n </span>\n <div className=\"flex-1 min-w-0\">\n <span className=\"text-text-primary\">{log.message}</span>\n </div>\n <span className=\"text-xs text-text-muted flex-shrink-0\">\n {new Date(log.timestamp).toLocaleTimeString()}\n </span>\n </div>\n ))}\n </div>\n </Card>\n )}\n </div>\n )}\n\n {activeTab === 'stats' && (\n <div className=\"space-y-4\">\n {statsLoading ? (\n <LoadingState message=\"Loading stats...\" />\n ) : statsError ? (\n <ErrorState title=\"Failed to load stats\" message={statsError} onRetry={loadStats} />\n ) : stats ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n <StatCard title=\"Total Tokens\" value={stats.totalTokens.toLocaleString()} />\n <StatCard\n title=\"Input Tokens\"\n value={stats.inputTokens.toLocaleString()}\n variant=\"success\"\n />\n <StatCard\n title=\"Output Tokens\"\n value={stats.outputTokens.toLocaleString()}\n variant=\"warning\"\n />\n <StatCard title=\"Prompts\" value={stats.promptCount} />\n {stats.averageLatency !== undefined && (\n <StatCard title=\"Avg Latency\" value={`${stats.averageLatency.toFixed(0)}ms`} />\n )}\n </div>\n ) : (\n <EmptyState\n icon={<BarChart3 className=\"size-6 text-text-secondary\" />}\n title=\"No stats\"\n description=\"Stats will appear after the session processes prompts\"\n />\n )}\n </div>\n )}\n\n {activeTab === 'config' && (\n <div className=\"space-y-4\">\n <Card className=\"p-4\">\n <h3 className=\"font-semibold text-text-primary mb-3\">\n {tr('vibecontrols.pages.ai.sessionConfig', 'Session Configuration')}\n </h3>\n <textarea\n aria-label=\"control\"\n value={configJson}\n onChange={(e) => setConfigJson(e.target.value)}\n rows={10}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm font-mono focus:outline-none focus:border-border-strong\"\n placeholder='{\"model\": \"claude-3\", \"temperature\": 0.7, ...}'\n />\n <div className=\"mt-4 flex flex-col gap-3 sm:flex-row sm:items-center\">\n <button\n type=\"button\"\n onClick={handleSaveConfig}\n disabled={savingConfig}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 text-sm\"\n >\n <Settings className=\"size-4\" />\n {savingConfig\n ? tr('vibecontrols.pages.ai.saving', 'Saving...')\n : tr('vibecontrols.pages.ai.saveConfig', 'Save Config')}\n </button>\n {configSaved && (\n <span className=\"text-sm text-status-success-text\">\n {tr('vibecontrols.pages.ai.savedSuccessfully', 'Saved successfully')}\n </span>\n )}\n </div>\n </Card>\n\n <Card className=\"p-4 border-status-error-text/20\">\n <h3 className=\"font-semibold text-status-error-text mb-2\">\n {tr('vibecontrols.pages.ai.dangerZone', 'Danger Zone')}\n </h3>\n <p className=\"text-sm text-text-secondary mb-3\">\n {tr(\n 'vibecontrols.pages.ai.terminateWarning',\n 'Terminating a session is permanent. All state will be lost.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={handleTerminate}\n disabled={terminating}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-danger-bg text-action-danger-text border border-status-error-border rounded-button hover:bg-action-danger-bg-hover transition-colors disabled:opacity-50 text-sm\"\n >\n <XCircle className=\"size-4\" />\n {terminating\n ? tr('vibecontrols.pages.ai.terminating', 'Terminating...')\n : tr('vibecontrols.pages.ai.terminateSession', 'Terminate Session')}\n </button>\n </Card>\n </div>\n )}\n </RequireAgentReachable>\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AISessionDetailPage.js","names":[],"sources":["../../../src/pages/ai/AISessionDetailPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n MessageSquare,\n ScrollText,\n BarChart3,\n Settings,\n Send,\n ArrowLeft,\n XCircle,\n RefreshCw,\n} from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport {\n PageHeader,\n Card,\n LoadingState,\n ErrorState,\n EmptyState,\n StatCard,\n RequireAgentReachable,\n} from '@/components/shared';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useQueryBackedTab } from '@/hooks/useQueryBackedTab';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport type { AISessionLog, AISessionStats } from '@/services/aiApi';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n sendPromptToSession,\n fetchSessionLogs,\n fetchSessionStats,\n updateSessionConfig,\n terminateSession,\n} from '@/services/aiApi';\nimport { isSessionNotFoundError } from '@/utils/aiSessionNotFound';\n\ntype SessionTab = 'chat' | 'logs' | 'stats' | 'config';\nconst SESSION_TABS: readonly SessionTab[] = ['chat', 'logs', 'stats', 'config'];\n\nconst sessionTabs: { id: SessionTab; label: string; icon: typeof MessageSquare }[] = [\n { id: 'chat', label: 'Chat', icon: MessageSquare },\n { id: 'logs', label: 'Logs', icon: ScrollText },\n { id: 'stats', label: 'Stats', icon: BarChart3 },\n { id: 'config', label: 'Config', icon: Settings },\n];\n\nconst logTypeColors: Record<string, string> = {\n info: 'bg-[var(--color-accent-soft)] text-text-primary',\n warn: 'bg-status-warning-bg-subtle text-status-warning-text',\n error: 'bg-status-error-bg-subtle text-status-error-text',\n debug: 'bg-bg-sunken text-text-secondary',\n prompt: 'bg-status-success-bg-subtle text-status-success-text',\n response: 'bg-[var(--color-accent-soft)] text-text-primary',\n};\n\ninterface ChatMessage {\n role: 'user' | 'assistant';\n content: string;\n tokens?: { input: number; output: number };\n timestamp: string;\n}\n\n/**\n * AI session detail page with Chat, Logs, Stats, and Config tabs.\n */\nexport function AISessionDetailPage() {\n const tr = useTr();\n // Agent-centric page — gate on `agent:read` (defense-in-depth).\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canReadAgents = hasPermission(VIBE_PERMISSIONS.AGENT_READ);\n const { sessionId } = useParams<{ sessionId: string }>();\n const handleNavigate = useVibeNavigate();\n\n const { agents, loading: agentsLoading } = useCachedInitialData();\n const activeAgent = useMemo(\n () => agents.find((agent) => agent.isActive && agent.tunnelUrl) ?? null,\n [agents]\n );\n const agentId = activeAgent?.id ?? '';\n const hasAgent = !!agentId;\n // Canonical agent → profile → plugins/data hierarchy. Every aiApi call\n // takes an AgentRef = { id, profile } so the path can be constructed\n // explicitly at the callsite.\n const { active: activeProfile } = useActiveAgentProfile(activeAgent?.id ?? null);\n const agentRef = useMemo(\n () => ({ id: agentId, profile: activeProfile }),\n [agentId, activeProfile]\n );\n\n const [activeTab, setActiveTab] = useQueryBackedTab<SessionTab>(SESSION_TABS, 'chat');\n\n // ── Chat state ──────────────────────────────────────────────────────\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [promptInput, setPromptInput] = useState('');\n const [sending, setSending] = useState(false);\n\n const handleSendPrompt = async () => {\n if (!promptInput.trim() || !sessionId || !hasAgent) return;\n const userMessage: ChatMessage = {\n role: 'user',\n content: promptInput,\n timestamp: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, userMessage]);\n setPromptInput('');\n setSending(true);\n try {\n const result = await sendPromptToSession(agentRef, sessionId, userMessage.content);\n const content =\n ((result as Record<string, unknown>).content as string) ||\n ((result as Record<string, unknown>).response as string) ||\n JSON.stringify(result);\n const inputTokens = (result as Record<string, unknown>).inputTokens as number | undefined;\n const outputTokens = (result as Record<string, unknown>).outputTokens as number | undefined;\n const assistantMessage: ChatMessage = {\n role: 'assistant',\n content,\n tokens:\n inputTokens !== undefined ? { input: inputTokens, output: outputTokens ?? 0 } : undefined,\n timestamp: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, assistantMessage]);\n } catch (err) {\n const errorMessage: ChatMessage = {\n role: 'assistant',\n content: `Error: ${err instanceof Error ? err.message : 'Failed to get response'}`,\n timestamp: new Date().toISOString(),\n };\n setMessages((prev) => [...prev, errorMessage]);\n } finally {\n setSending(false);\n }\n };\n\n // ── Logs state ──────────────────────────────────────────────────────\n const [logs, setLogs] = useState<AISessionLog[]>([]);\n const [logsLoading, setLogsLoading] = useState(false);\n const [logsError, setLogsError] = useState<string | null>(null);\n const [logTypeFilter, setLogTypeFilter] = useState('all');\n\n const loadLogs = useCallback(async () => {\n if (!sessionId || !hasAgent) return;\n setLogsLoading(true);\n setLogsError(null);\n try {\n const params: Record<string, unknown> = {};\n if (logTypeFilter !== 'all') params.type = logTypeFilter;\n const result = await fetchSessionLogs(agentRef, sessionId, params);\n setLogs(result);\n } catch (err) {\n // Pre-first-message: agent has no record of this session yet —\n // render an empty list, not an error.\n if (isSessionNotFoundError(err)) {\n setLogs([]);\n } else {\n setLogsError(err instanceof Error ? err.message : 'Failed to load logs');\n }\n } finally {\n setLogsLoading(false);\n }\n }, [agentRef, sessionId, logTypeFilter, hasAgent]);\n\n useEffect(() => {\n if (activeTab === 'logs') loadLogs();\n }, [activeTab, loadLogs]);\n\n // ── Stats state ─────────────────────────────────────────────────────\n const [stats, setStats] = useState<AISessionStats | null>(null);\n const [statsLoading, setStatsLoading] = useState(false);\n const [statsError, setStatsError] = useState<string | null>(null);\n\n const loadStats = useCallback(async () => {\n if (!sessionId || !hasAgent) return;\n setStatsLoading(true);\n setStatsError(null);\n try {\n const result = await fetchSessionStats(agentRef, sessionId);\n setStats(result);\n } catch (err) {\n if (isSessionNotFoundError(err)) {\n setStats(null);\n } else {\n setStatsError(err instanceof Error ? err.message : 'Failed to load stats');\n }\n } finally {\n setStatsLoading(false);\n }\n }, [agentRef, sessionId, hasAgent]);\n\n useEffect(() => {\n if (activeTab === 'stats') loadStats();\n }, [activeTab, loadStats]);\n\n // ── Config state ────────────────────────────────────────────────────\n const [configJson, setConfigJson] = useState('{}');\n const [savingConfig, setSavingConfig] = useState(false);\n const [configSaved, setConfigSaved] = useState(false);\n const [terminating, setTerminating] = useState(false);\n\n const handleSaveConfig = async () => {\n if (!sessionId || !hasAgent) return;\n setSavingConfig(true);\n try {\n const config = JSON.parse(configJson);\n await updateSessionConfig(agentRef, sessionId, config);\n setConfigSaved(true);\n setTimeout(() => setConfigSaved(false), 3000);\n } catch (err) {\n console.error('Failed to save config:', err);\n } finally {\n setSavingConfig(false);\n }\n };\n\n const handleTerminate = async () => {\n if (!sessionId || !hasAgent) return;\n if (!window.confirm('Are you sure you want to terminate this session? This cannot be undone.'))\n return;\n setTerminating(true);\n try {\n await terminateSession(agentRef, sessionId);\n handleNavigate('/ai/sessions');\n } catch (err) {\n console.error('Failed to terminate session:', err);\n } finally {\n setTerminating(false);\n }\n };\n\n if (permissionsLoading) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions…')} />\n </MotionPage>\n );\n }\n\n if (!canReadAgents) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <AccessDenied\n title={tr('vibecontrols.pages.ai.denied.title', 'Access Denied')}\n description={tr(\n 'vibecontrols.pages.ai.denied.description',\n 'You do not have permission to view AI features.'\n )}\n />\n </MotionPage>\n );\n }\n\n if (agentsLoading) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message=\"Loading...\" />\n </MotionPage>\n );\n }\n\n if (!hasAgent) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <EmptyState\n icon={<MessageSquare className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.pages.ai.noActiveAgent', 'No active agent')}\n description={tr(\n 'vibecontrols.pages.ai.noActiveAgentDescription',\n 'An active agent with a tunnel is required to view session details.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => handleNavigate('/agents')}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors text-sm\"\n >\n {tr('vibecontrols.pages.ai.goToAgents', 'Go to Agents')}\n </button>\n }\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <PageHeader\n title={`Session ${sessionId?.substring(0, 8) ?? ''}...`}\n description={tr(\n 'vibecontrols.pages.ai.sessionDescription',\n 'AI session details and interaction'\n )}\n breadcrumb={[\n { label: 'AI', path: '#' },\n { label: 'Sessions', path: '#' },\n { label: sessionId?.substring(0, 8) ?? '' },\n ]}\n actions={\n <button\n type=\"button\"\n onClick={() => handleNavigate('/ai/sessions')}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors text-sm\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('vibecontrols.pages.ai.back', 'Back')}\n </button>\n }\n />\n\n {/* Tab Navigation */}\n <div className=\"-mx-4 border-b border-border-default px-4 sm:-mx-6 sm:px-6\">\n <nav className=\"flex gap-1 overflow-x-auto pb-1\" role=\"tablist\">\n {sessionTabs.map((tab) => {\n const Icon = tab.icon;\n const isActive = activeTab === tab.id;\n return (\n <button\n type=\"button\"\n key={tab.id}\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => setActiveTab(tab.id)}\n className={`flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-t-md border-b-2 px-2.5 py-2 text-xs font-medium transition-colors sm:px-3 sm:py-2.5 sm:text-sm ${\n isActive\n ? 'border-action-primary-bg text-action-primary-bg'\n : 'border-transparent text-text-secondary hover:text-text-primary hover:border-border-default'\n }`}\n >\n <Icon className=\"size-3.5 sm:h-4 sm:w-4\" />\n {tab.label}\n </button>\n );\n })}\n </nav>\n </div>\n\n {/* Tab Content */}\n <RequireAgentReachable agentId={agentId || null}>\n {activeTab === 'chat' && (\n <div className=\"space-y-4\">\n {/* Messages */}\n <Card className=\"p-0\">\n <div className=\"divide-y divide-border-default min-h-[200px] max-h-[50vh] overflow-y-auto\">\n {messages.length === 0 ? (\n <div className=\"flex items-center justify-center h-48 text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.pages.ai.sendPromptToStart',\n 'Send a prompt to start the conversation'\n )}\n </div>\n ) : (\n messages.map((msg, idx) => (\n <div\n key={idx}\n className={`px-4 py-3 ${msg.role === 'assistant' ? 'bg-bg-sunken/50' : ''}`}\n >\n <div className=\"mb-1 flex flex-wrap items-center gap-2\">\n <span className=\"text-xs font-semibold text-text-primary uppercase\">\n {msg.role}\n </span>\n <span className=\"text-xs text-text-muted\">\n {new Date(msg.timestamp).toLocaleTimeString()}\n </span>\n {msg.tokens && (\n <span className=\"text-xs text-text-muted sm:ml-auto\">\n {msg.tokens.input + msg.tokens.output} tokens\n </span>\n )}\n </div>\n <div className=\"text-sm text-text-primary whitespace-pre-wrap\">\n {msg.content}\n </div>\n </div>\n ))\n )}\n {sending && (\n <div className=\"px-4 py-3 bg-bg-sunken/50\">\n <div className=\"flex items-center gap-2\">\n <RefreshCw className=\"size-3.5 text-text-secondary animate-spin\" />\n <span className=\"text-sm text-text-secondary\">\n {tr('vibecontrols.pages.ai.thinking', 'Thinking...')}\n </span>\n </div>\n </div>\n )}\n </div>\n </Card>\n\n {/* Prompt Input */}\n <div className=\"flex flex-col gap-2 sm:flex-row\">\n <textarea\n aria-label=\"Type your prompt...\"\n value={promptInput}\n onChange={(e) => setPromptInput(e.target.value)}\n rows={3}\n className=\"min-h-[7rem] flex-1 rounded-md border border-border-default bg-bg-sunken px-3 py-2 text-sm font-mono text-text-primary resize-none focus:outline-none focus:border-border-strong\"\n placeholder=\"Type your prompt...\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleSendPrompt();\n }\n }}\n />\n <button\n type=\"button\"\n onClick={handleSendPrompt}\n disabled={sending || !promptInput.trim()}\n className=\"flex items-center justify-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm text-action-primary-text transition-colors hover:bg-action-primary-bg-hover disabled:opacity-50 sm:self-end\"\n >\n <Send className=\"size-4\" />\n {tr('vibecontrols.pages.ai.send', 'Send')}\n </button>\n </div>\n </div>\n )}\n\n {activeTab === 'logs' && (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <select\n value={logTypeFilter}\n onChange={(e) => setLogTypeFilter(e.target.value)}\n className=\"px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm\"\n >\n <option value=\"all\">All Types</option>\n <option value=\"info\">Info</option>\n <option value=\"warn\">Warning</option>\n <option value=\"error\">Error</option>\n <option value=\"debug\">Debug</option>\n <option value=\"prompt\">Prompt</option>\n <option value=\"response\">Response</option>\n </select>\n <button\n type=\"button\"\n onClick={loadLogs}\n disabled={logsLoading}\n className=\"p-2 hover:bg-bg-sunken rounded-md transition-colors\"\n >\n <RefreshCw\n className={`size-4 text-text-secondary ${logsLoading ? 'animate-spin' : ''}`}\n />\n </button>\n </div>\n\n {logsLoading ? (\n <LoadingState message=\"Loading logs...\" />\n ) : logsError ? (\n <ErrorState title=\"Failed to load logs\" message={logsError} onRetry={loadLogs} />\n ) : logs.length === 0 ? (\n <EmptyState\n icon={<ScrollText className=\"size-6 text-text-secondary\" />}\n title=\"No logs\"\n description=\"Session logs will appear here\"\n />\n ) : (\n <Card className=\"p-0\">\n <div className=\"divide-y divide-border-default max-h-[60vh] overflow-y-auto\">\n {logs.map((log) => (\n <div key={log.id} className=\"px-4 py-2 flex items-start gap-3 text-sm\">\n <span\n className={`px-1.5 py-0.5 text-xs rounded font-medium flex-shrink-0 mt-0.5 ${logTypeColors[log.type] ?? 'bg-bg-sunken text-text-secondary'}`}\n >\n {log.type}\n </span>\n <div className=\"flex-1 min-w-0\">\n <span className=\"text-text-primary\">{log.message}</span>\n </div>\n <span className=\"text-xs text-text-muted flex-shrink-0\">\n {new Date(log.timestamp).toLocaleTimeString()}\n </span>\n </div>\n ))}\n </div>\n </Card>\n )}\n </div>\n )}\n\n {activeTab === 'stats' && (\n <div className=\"space-y-4\">\n {statsLoading ? (\n <LoadingState message=\"Loading stats...\" />\n ) : statsError ? (\n <ErrorState title=\"Failed to load stats\" message={statsError} onRetry={loadStats} />\n ) : stats ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n <StatCard title=\"Total Tokens\" value={stats.totalTokens.toLocaleString()} />\n <StatCard\n title=\"Input Tokens\"\n value={stats.inputTokens.toLocaleString()}\n variant=\"success\"\n />\n <StatCard\n title=\"Output Tokens\"\n value={stats.outputTokens.toLocaleString()}\n variant=\"warning\"\n />\n <StatCard title=\"Prompts\" value={stats.promptCount} />\n {stats.averageLatency !== undefined && (\n <StatCard title=\"Avg Latency\" value={`${stats.averageLatency.toFixed(0)}ms`} />\n )}\n </div>\n ) : (\n <EmptyState\n icon={<BarChart3 className=\"size-6 text-text-secondary\" />}\n title=\"No stats\"\n description=\"Stats will appear after the session processes prompts\"\n />\n )}\n </div>\n )}\n\n {activeTab === 'config' && (\n <div className=\"space-y-4\">\n <Card className=\"p-4\">\n <h3 className=\"font-semibold text-text-primary mb-3\">\n {tr('vibecontrols.pages.ai.sessionConfig', 'Session Configuration')}\n </h3>\n <textarea\n aria-label=\"control\"\n value={configJson}\n onChange={(e) => setConfigJson(e.target.value)}\n rows={10}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm font-mono focus:outline-none focus:border-border-strong\"\n placeholder='{\"model\": \"claude-3\", \"temperature\": 0.7, ...}'\n />\n <div className=\"mt-4 flex flex-col gap-3 sm:flex-row sm:items-center\">\n <button\n type=\"button\"\n onClick={handleSaveConfig}\n disabled={savingConfig}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 text-sm\"\n >\n <Settings className=\"size-4\" />\n {savingConfig\n ? tr('vibecontrols.pages.ai.saving', 'Saving...')\n : tr('vibecontrols.pages.ai.saveConfig', 'Save Config')}\n </button>\n {configSaved && (\n <span className=\"text-sm text-status-success-text\">\n {tr('vibecontrols.pages.ai.savedSuccessfully', 'Saved successfully')}\n </span>\n )}\n </div>\n </Card>\n\n <Card className=\"p-4 border-status-error-text/20\">\n <h3 className=\"font-semibold text-status-error-text mb-2\">\n {tr('vibecontrols.pages.ai.dangerZone', 'Danger Zone')}\n </h3>\n <p className=\"text-sm text-text-secondary mb-3\">\n {tr(\n 'vibecontrols.pages.ai.terminateWarning',\n 'Terminating a session is permanent. All state will be lost.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={handleTerminate}\n disabled={terminating}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-danger-bg text-action-danger-text border border-status-error-border rounded-button hover:bg-action-danger-bg-hover transition-colors disabled:opacity-50 text-sm\"\n >\n <XCircle className=\"size-4\" />\n {terminating\n ? tr('vibecontrols.pages.ai.terminating', 'Terminating...')\n : tr('vibecontrols.pages.ai.terminateSession', 'Terminate Session')}\n </button>\n </Card>\n </div>\n )}\n </RequireAgentReachable>\n </MotionPage>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAyCA,IAAM,KAAsC;CAAC;CAAQ;CAAQ;CAAS;CAAS,EAEzE,KAA+E;CACnF;EAAE,IAAI;EAAQ,OAAO;EAAQ,MAAM;EAAe;CAClD;EAAE,IAAI;EAAQ,OAAO;EAAQ,MAAM;EAAY;CAC/C;EAAE,IAAI;EAAS,OAAO;EAAS,MAAM;EAAW;CAChD;EAAE,IAAI;EAAU,OAAO;EAAU,MAAM;EAAU;CAClD,EAEK,KAAwC;CAC5C,MAAM;CACN,MAAM;CACN,OAAO;CACP,OAAO;CACP,QAAQ;CACR,UAAU;CACX;AAYD,SAAgB,IAAsB;CACpC,IAAM,IAAK,GAAO,EAEZ,EAAE,mBAAe,WAAW,OAAuB,IAAgB,EACnE,KAAgB,GAAc,GAAiB,WAAW,EAC1D,EAAE,iBAAc,IAAkC,EAClD,IAAiB,GAAiB,EAElC,EAAE,WAAQ,SAAS,OAAkB,GAAsB,EAC3D,IAAc,QACZ,EAAO,MAAM,MAAU,EAAM,YAAY,EAAM,UAAU,IAAI,MACnE,CAAC,EAAO,CACT,EACK,IAAU,GAAa,MAAM,IAC7B,IAAW,CAAC,CAAC,GAIb,EAAE,QAAQ,MAAkB,GAAsB,GAAa,MAAM,KAAK,EAC1E,IAAW,SACR;EAAE,IAAI;EAAS,SAAS;EAAe,GAC9C,CAAC,GAAS,EAAc,CACzB,EAEK,CAAC,GAAW,MAAgB,GAA8B,IAAc,OAAO,EAG/E,CAAC,GAAU,KAAe,EAAwB,EAAE,CAAC,EACrD,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAS,KAAc,EAAS,GAAM,EAEvC,IAAmB,YAAY;AACnC,MAAI,CAAC,EAAY,MAAM,IAAI,CAAC,KAAa,CAAC,EAAU;EACpD,IAAM,IAA2B;GAC/B,MAAM;GACN,SAAS;GACT,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC;AAGD,EAFA,GAAa,MAAS,CAAC,GAAG,GAAM,EAAY,CAAC,EAC7C,EAAe,GAAG,EAClB,EAAW,GAAK;AAChB,MAAI;GACF,IAAM,IAAS,MAAM,GAAoB,GAAU,GAAW,EAAY,QAAQ,EAC5E,IACF,EAAmC,WACnC,EAAmC,YACrC,KAAK,UAAU,EAAO,EAClB,IAAe,EAAmC,aAClD,IAAgB,EAAmC,cACnD,IAAgC;IACpC,MAAM;IACN;IACA,QACE,MAAgB,KAAA,IAAgE,KAAA,IAApD;KAAE,OAAO;KAAa,QAAQ,KAAgB;KAAG;IAC/E,4BAAW,IAAI,MAAM,EAAC,aAAa;IACpC;AACD,MAAa,MAAS,CAAC,GAAG,GAAM,EAAiB,CAAC;WAC3C,GAAK;GACZ,IAAM,IAA4B;IAChC,MAAM;IACN,SAAS,UAAU,aAAe,QAAQ,EAAI,UAAU;IACxD,4BAAW,IAAI,MAAM,EAAC,aAAa;IACpC;AACD,MAAa,MAAS,CAAC,GAAG,GAAM,EAAa,CAAC;YACtC;AACR,KAAW,GAAM;;IAKf,CAAC,GAAM,KAAW,EAAyB,EAAE,CAAC,EAC9C,CAAC,GAAa,KAAkB,EAAS,GAAM,EAC/C,CAAC,GAAW,KAAgB,EAAwB,KAAK,EACzD,CAAC,GAAe,MAAoB,EAAS,MAAM,EAEnD,IAAW,EAAY,YAAY;AACnC,SAAC,KAAa,CAAC,IAEnB;GADA,EAAe,GAAK,EACpB,EAAa,KAAK;AAClB,OAAI;IACF,IAAM,IAAkC,EAAE;AAG1C,IAFI,MAAkB,UAAO,EAAO,OAAO,IAE3C,EADe,MAAM,GAAiB,GAAU,GAAW,EAAO,CACnD;YACR,GAAK;AAGZ,IAAI,EAAuB,EAAI,GAC7B,EAAQ,EAAE,CAAC,GAEX,EAAa,aAAe,QAAQ,EAAI,UAAU,sBAAsB;aAElE;AACR,MAAe,GAAM;;;IAEtB;EAAC;EAAU;EAAW;EAAe;EAAS,CAAC;AAElD,SAAgB;AACd,EAAI,MAAc,UAAQ,GAAU;IACnC,CAAC,GAAW,EAAS,CAAC;CAGzB,IAAM,CAAC,GAAO,KAAY,EAAgC,KAAK,EACzD,CAAC,IAAc,KAAmB,EAAS,GAAM,EACjD,CAAC,GAAY,KAAiB,EAAwB,KAAK,EAE3D,IAAY,EAAY,YAAY;AACpC,SAAC,KAAa,CAAC,IAEnB;GADA,EAAgB,GAAK,EACrB,EAAc,KAAK;AACnB,OAAI;AAEF,MADe,MAAM,GAAkB,GAAU,EAAU,CAC3C;YACT,GAAK;AACZ,IAAI,EAAuB,EAAI,GAC7B,EAAS,KAAK,GAEd,EAAc,aAAe,QAAQ,EAAI,UAAU,uBAAuB;aAEpE;AACR,MAAgB,GAAM;;;IAEvB;EAAC;EAAU;EAAW;EAAS,CAAC;AAEnC,SAAgB;AACd,EAAI,MAAc,WAAS,GAAW;IACrC,CAAC,GAAW,EAAU,CAAC;CAG1B,IAAM,CAAC,IAAY,MAAiB,EAAS,KAAK,EAC5C,CAAC,IAAc,MAAmB,EAAS,GAAM,EACjD,CAAC,IAAa,MAAkB,EAAS,GAAM,EAC/C,CAAC,IAAa,KAAkB,EAAS,GAAM;AAsFrD,QAtDI,KAEA,kBAAC,GAAD;EAAY,WAAU;YACpB,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,uBAAuB,EAAI,CAAA;EACvE,CAAA,GAIZ,KAcD,KAEA,kBAAC,GAAD;EAAY,WAAU;YACpB,kBAAC,GAAD,EAAc,SAAQ,cAAe,CAAA;EAC1B,CAAA,GAIZ,IAyBH,kBAAC,GAAD;EAAY,WAAU;YAAtB;GACE,kBAAC,GAAD;IACE,OAAO,WAAW,GAAW,UAAU,GAAG,EAAE,IAAI,GAAG;IACnD,aAAa,EACX,4CACA,qCACD;IACD,YAAY;KACV;MAAE,OAAO;MAAM,MAAM;MAAK;KAC1B;MAAE,OAAO;MAAY,MAAM;MAAK;KAChC,EAAE,OAAO,GAAW,UAAU,GAAG,EAAE,IAAI,IAAI;KAC5C;IACD,SACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAe,eAAe;KAC7C,WAAU;eAHZ,CAKE,kBAAC,IAAD,EAAW,WAAU,UAAW,CAAA,EAC/B,EAAG,8BAA8B,OAAO,CAClC;;IAEX,CAAA;GAGF,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;KAAkC,MAAK;eACnD,GAAY,KAAK,MAAQ;MACxB,IAAM,IAAO,EAAI,MACX,IAAW,MAAc,EAAI;AACnC,aACE,kBAAC,UAAD;OACE,MAAK;OAEL,MAAK;OACL,iBAAe;OACf,eAAe,GAAa,EAAI,GAAG;OACnC,WAAW,+JACT,IACI,oDACA;iBATR,CAYE,kBAAC,GAAD,EAAM,WAAU,0BAA2B,CAAA,EAC1C,EAAI,MACE;SAZF,EAAI,GAYF;OAEX;KACE,CAAA;IACF,CAAA;GAGN,kBAAC,IAAD;IAAuB,SAAS,KAAW;cAA3C;KACG,MAAc,UACb,kBAAC,OAAD;MAAK,WAAU;gBAAf,CAEE,kBAAC,GAAD;OAAM,WAAU;iBACd,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACG,EAAS,WAAW,IACnB,kBAAC,OAAD;SAAK,WAAU;mBACZ,EACC,2CACA,0CACD;SACG,CAAA,GAEN,EAAS,KAAK,GAAK,MACjB,kBAAC,OAAD;SAEE,WAAW,aAAa,EAAI,SAAS,cAAc,oBAAoB;mBAFzE,CAIE,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAI;YACA,CAAA;WACP,kBAAC,QAAD;YAAM,WAAU;sBACb,IAAI,KAAK,EAAI,UAAU,CAAC,oBAAoB;YACxC,CAAA;WACN,EAAI,UACH,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CACG,EAAI,OAAO,QAAQ,EAAI,OAAO,QAAO,UACjC;;WAEL;aACN,kBAAC,OAAD;UAAK,WAAU;oBACZ,EAAI;UACD,CAAA,CACF;WAnBC,EAmBD,CACN,EAEH,KACC,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD,EAAW,WAAU,6CAA8C,CAAA,EACnE,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAG,kCAAkC,cAAc;WAC/C,CAAA,CACH;;SACF,CAAA,CAEJ;;OACD,CAAA,EAGP,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,YAAD;QACE,cAAW;QACX,OAAO;QACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;QAC/C,MAAM;QACN,WAAU;QACV,aAAY;QACZ,YAAY,MAAM;AAChB,SAAI,EAAE,QAAQ,YAAY,EAAE,WAAW,EAAE,aACvC,EAAE,gBAAgB,EAClB,GAAkB;;QAGtB,CAAA,EACF,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,UAAU,KAAW,CAAC,EAAY,MAAM;QACxC,WAAU;kBAJZ,CAME,kBAAC,IAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,EAAG,8BAA8B,OAAO,CAClC;UACL;SACF;;KAGP,MAAc,UACb,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,OAAO;QACP,WAAW,MAAM,GAAiB,EAAE,OAAO,MAAM;QACjD,WAAU;kBAHZ;SAKE,kBAAC,UAAD;UAAQ,OAAM;oBAAM;UAAkB,CAAA;SACtC,kBAAC,UAAD;UAAQ,OAAM;oBAAO;UAAa,CAAA;SAClC,kBAAC,UAAD;UAAQ,OAAM;oBAAO;UAAgB,CAAA;SACrC,kBAAC,UAAD;UAAQ,OAAM;oBAAQ;UAAc,CAAA;SACpC,kBAAC,UAAD;UAAQ,OAAM;oBAAQ;UAAc,CAAA;SACpC,kBAAC,UAAD;UAAQ,OAAM;oBAAS;UAAe,CAAA;SACtC,kBAAC,UAAD;UAAQ,OAAM;oBAAW;UAAiB,CAAA;SACnC;WACT,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,UAAU;QACV,WAAU;kBAEV,kBAAC,GAAD,EACE,WAAW,8BAA8B,IAAc,iBAAiB,MACxE,CAAA;QACK,CAAA,CACL;UAEL,IACC,kBAAC,GAAD,EAAc,SAAQ,mBAAoB,CAAA,GACxC,IACF,kBAAC,GAAD;OAAY,OAAM;OAAsB,SAAS;OAAW,SAAS;OAAY,CAAA,GAC/E,EAAK,WAAW,IAClB,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAY,WAAU,8BAA+B,CAAA;OAC3D,OAAM;OACN,aAAY;OACZ,CAAA,GAEF,kBAAC,GAAD;OAAM,WAAU;iBACd,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAK,KAAK,MACT,kBAAC,OAAD;SAAkB,WAAU;mBAA5B;UACE,kBAAC,QAAD;WACE,WAAW,kEAAkE,GAAc,EAAI,SAAS;qBAEvG,EAAI;WACA,CAAA;UACP,kBAAC,OAAD;WAAK,WAAU;qBACb,kBAAC,QAAD;YAAM,WAAU;sBAAqB,EAAI;YAAe,CAAA;WACpD,CAAA;UACN,kBAAC,QAAD;WAAM,WAAU;qBACb,IAAI,KAAK,EAAI,UAAU,CAAC,oBAAoB;WACxC,CAAA;UACH;WAZI,EAAI,GAYR,CACN;QACE,CAAA;OACD,CAAA,CAEL;;KAGP,MAAc,WACb,kBAAC,OAAD;MAAK,WAAU;gBACZ,KACC,kBAAC,GAAD,EAAc,SAAQ,oBAAqB,CAAA,GACzC,IACF,kBAAC,GAAD;OAAY,OAAM;OAAuB,SAAS;OAAY,SAAS;OAAa,CAAA,GAClF,IACF,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SAAU,OAAM;SAAe,OAAO,EAAM,YAAY,gBAAgB;SAAI,CAAA;QAC5E,kBAAC,GAAD;SACE,OAAM;SACN,OAAO,EAAM,YAAY,gBAAgB;SACzC,SAAQ;SACR,CAAA;QACF,kBAAC,GAAD;SACE,OAAM;SACN,OAAO,EAAM,aAAa,gBAAgB;SAC1C,SAAQ;SACR,CAAA;QACF,kBAAC,GAAD;SAAU,OAAM;SAAU,OAAO,EAAM;SAAe,CAAA;QACrD,EAAM,mBAAmB,KAAA,KACxB,kBAAC,GAAD;SAAU,OAAM;SAAc,OAAO,GAAG,EAAM,eAAe,QAAQ,EAAE,CAAC;SAAO,CAAA;QAE7E;WAEN,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAW,WAAU,8BAA+B,CAAA;OAC1D,OAAM;OACN,aAAY;OACZ,CAAA;MAEA,CAAA;KAGP,MAAc,YACb,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAM,WAAU;iBAAhB;QACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAG,uCAAuC,wBAAwB;SAChE,CAAA;QACL,kBAAC,YAAD;SACE,cAAW;SACX,OAAO;SACP,WAAW,MAAM,GAAc,EAAE,OAAO,MAAM;SAC9C,MAAM;SACN,WAAU;SACV,aAAY;SACZ,CAAA;QACF,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,UAAD;UACE,MAAK;UACL,SA1US,YAAY;AAC/B,kBAAC,KAAa,CAAC,IACnB;eAAgB,GAAK;AACrB,gBAAI;AAIF,aAFA,MAAM,GAAoB,GAAU,GADrB,KAAK,MAAM,GAAW,CACiB,EACtD,GAAe,GAAK,EACpB,iBAAiB,GAAe,GAAM,EAAE,IAAK;qBACtC,GAAK;AACZ,qBAAQ,MAAM,0BAA0B,EAAI;sBACpC;AACR,gBAAgB,GAAM;;;;UAgUV,UAAU;UACV,WAAU;oBAJZ,CAME,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA,EAC9B,KACG,EAAG,gCAAgC,YAAY,GAC/C,EAAG,oCAAoC,cAAc,CAClD;aACR,MACC,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAG,2CAA2C,qBAAqB;UAC/D,CAAA,CAEL;;QACD;UAEP,kBAAC,GAAD;OAAM,WAAU;iBAAhB;QACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAG,oCAAoC,cAAc;SACnD,CAAA;QACL,kBAAC,KAAD;SAAG,WAAU;mBACV,EACC,0CACA,8DACD;SACC,CAAA;QACJ,kBAAC,UAAD;SACE,MAAK;SACL,SAxVU,YAAY;AAC9B,iBAAC,KAAa,CAAC,MACd,OAAO,QAAQ,0EAA0E,EAE9F;aAAe,GAAK;AACpB,eAAI;AAEF,YADA,MAAM,GAAiB,GAAU,EAAU,EAC3C,EAAe,eAAe;oBACvB,GAAK;AACZ,oBAAQ,MAAM,gCAAgC,EAAI;qBAC1C;AACR,cAAe,GAAM;;;;SA8UX,UAAU;SACV,WAAU;mBAJZ,CAME,kBAAC,IAAD,EAAS,WAAU,UAAW,CAAA,EAC7B,KACG,EAAG,qCAAqC,iBAAiB,GACzD,EAAG,0CAA0C,oBAAoB,CAC9D;;QACJ;SACH;;KAEc;;GACb;MAtTX,kBAAC,GAAD;EAAY,WAAU;YACpB,kBAAC,GAAD;GACE,MAAM,kBAAC,GAAD,EAAe,WAAU,8BAA+B,CAAA;GAC9D,OAAO,EAAG,uCAAuC,kBAAkB;GACnE,aAAa,EACX,kDACA,qEACD;GACD,QACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAe,UAAU;IACxC,WAAU;cAET,EAAG,oCAAoC,eAAe;IAChD,CAAA;GAEX,CAAA;EACS,CAAA,GAxCb,kBAAC,GAAD;EAAY,WAAU;YACpB,kBAAC,IAAD;GACE,OAAO,EAAG,sCAAsC,gBAAgB;GAChE,aAAa,EACX,4CACA,kDACD;GACD,CAAA;EACS,CAAA"}
|