@burdenoff/microfe-vibecontrols 2026.623.1 → 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/ActionCard.js +2 -2
- package/dist/components/actions/ActionCard.js.map +1 -1
- package/dist/components/actions/ActionTypeBadge.js +1 -1
- package/dist/components/actions/ActionTypeBadge.js.map +1 -1
- package/dist/components/actions/ExecutionHistory.js +1 -1
- package/dist/components/actions/ExecutionHistory.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 +2 -2
- package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerInputArea.js +4 -4
- package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerLayout.js +1 -1
- package/dist/components/agent-manager/AgentManagerLayout.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerMessageItem.js +1 -1
- package/dist/components/agent-manager/AgentManagerMessageItem.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerSidebar.js +5 -5
- package/dist/components/agent-manager/AgentManagerSidebar.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerTab.js +1 -1
- package/dist/components/agent-manager/AgentManagerTab.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerTabBar.js +1 -1
- package/dist/components/agent-manager/AgentManagerTabBar.js.map +1 -1
- package/dist/components/agent-manager/BridleConfigPanel.js +24 -24
- package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/PluginInstallModal.js +1 -1
- package/dist/components/agent-manager/PluginInstallModal.js.map +1 -1
- package/dist/components/agent-manager/ProfilesPanel.js +5 -5
- 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/SessionConfigPanel.js +6 -6
- package/dist/components/agent-manager/detail/SessionConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionLogsPanel.js +5 -5
- package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionToolsPanel.js +1 -1
- package/dist/components/agent-manager/detail/SessionToolsPanel.js.map +1 -1
- package/dist/components/agent-manager/dialogs/NewSessionDialog.js +2 -2
- package/dist/components/agent-manager/dialogs/NewSessionDialog.js.map +1 -1
- package/dist/components/agent-manager/dialogs/RenameSessionDialog.js +1 -1
- package/dist/components/agent-manager/dialogs/RenameSessionDialog.js.map +1 -1
- package/dist/components/agents/AgentCard.js +14 -11
- package/dist/components/agents/AgentCard.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 +4 -4
- package/dist/components/ai/AIOverviewTab.js.map +1 -1
- package/dist/components/ai/AIPlanPanel.js +4 -4
- package/dist/components/ai/AIPlanPanel.js.map +1 -1
- package/dist/components/ai/AIPlaygroundTab.js +1 -1
- package/dist/components/ai/AIPlaygroundTab.js.map +1 -1
- package/dist/components/ai/AIPromptsTab.js +2 -2
- package/dist/components/ai/AIPromptsTab.js.map +1 -1
- package/dist/components/ai/AIProviderCredentialsCard.js +4 -4
- package/dist/components/ai/AIProviderCredentialsCard.js.map +1 -1
- package/dist/components/ai/AIQueueTab.js +4 -4
- package/dist/components/ai/AIQueueTab.js.map +1 -1
- package/dist/components/ai/AISessionsTab.js +4 -4
- package/dist/components/ai/AISessionsTab.js.map +1 -1
- package/dist/components/ai/AITasksTab.js +1 -1
- package/dist/components/ai/AITasksTab.js.map +1 -1
- package/dist/components/ai/ClaudeCredentialsCard.js +3 -3
- package/dist/components/ai/ClaudeCredentialsCard.js.map +1 -1
- package/dist/components/ai/PluginHarnessPicker.js +3 -3
- package/dist/components/ai/PluginHarnessPicker.js.map +1 -1
- package/dist/components/analytics/AnalyticsBreakdownChart.js +8 -8
- package/dist/components/analytics/AnalyticsBreakdownChart.js.map +1 -1
- package/dist/components/analytics/AnalyticsTimeSeriesChart.js +5 -5
- package/dist/components/analytics/AnalyticsTimeSeriesChart.js.map +1 -1
- package/dist/components/audit/AuditDeleteDialog.js +2 -2
- package/dist/components/audit/AuditDeleteDialog.js.map +1 -1
- package/dist/components/audit/AuditExportButton.js +5 -5
- package/dist/components/audit/AuditExportButton.js.map +1 -1
- package/dist/components/audit/AuditLogDetail.js +9 -9
- package/dist/components/audit/AuditLogDetail.js.map +1 -1
- package/dist/components/audit/AuditLogFilters.js +1 -1
- package/dist/components/audit/AuditLogFilters.js.map +1 -1
- package/dist/components/audit/AuditLogStats.js +1 -1
- package/dist/components/audit/AuditLogStats.js.map +1 -1
- package/dist/components/audit/AuditLogTable.js +28 -28
- package/dist/components/audit/AuditLogTable.js.map +1 -1
- package/dist/components/catalog/CatalogCard.js +3 -3
- package/dist/components/catalog/CatalogCard.js.map +1 -1
- package/dist/components/catalog/CatalogForm.js +1 -1
- package/dist/components/catalog/CatalogForm.js.map +1 -1
- package/dist/components/common/ActionAgentTargetNotice.js +6 -6
- package/dist/components/common/ActionAgentTargetNotice.js.map +1 -1
- package/dist/components/common/CommandEditor.js +1 -1
- package/dist/components/common/CommandEditor.js.map +1 -1
- package/dist/components/docs/DocsPageTree.js +1 -1
- package/dist/components/docs/DocsPageTree.js.map +1 -1
- package/dist/components/docs/DocsPreviewPanel.js +1 -1
- package/dist/components/docs/DocsPreviewPanel.js.map +1 -1
- package/dist/components/docs/DocsRevisionHistory.js +4 -4
- package/dist/components/docs/DocsRevisionHistory.js.map +1 -1
- package/dist/components/docs/DocsSettingsPanel.js +7 -7
- package/dist/components/docs/DocsSettingsPanel.js.map +1 -1
- package/dist/components/docs/DocsTabPanel.js +1 -1
- package/dist/components/docs/DocsTabPanel.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/idp-components/ComponentCard.js +5 -5
- package/dist/components/idp-components/ComponentCard.js.map +1 -1
- package/dist/components/idp-components/ComponentForm.js +1 -1
- package/dist/components/idp-components/ComponentForm.js.map +1 -1
- package/dist/components/idp-components/DependencyGraphTab.js +7 -7
- package/dist/components/idp-components/DependencyGraphTab.js.map +1 -1
- package/dist/components/logs/LogViewer.js +14 -14
- package/dist/components/logs/LogViewer.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 +2 -2
- 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/notes/NotesTabPanel.js +1 -1
- package/dist/components/notes/NotesTabPanel.js.map +1 -1
- package/dist/components/scorecard/ScorecardEmptyState.js +1 -1
- package/dist/components/scorecard/ScorecardEmptyState.js.map +1 -1
- package/dist/components/scorecard/ScorecardPanel.js +3 -3
- package/dist/components/scorecard/ScorecardPanel.js.map +1 -1
- package/dist/components/security/SecurityScanList.js +11 -11
- package/dist/components/security/SecurityScanList.js.map +1 -1
- package/dist/components/sessions/SessionCard.js +26 -19
- package/dist/components/sessions/SessionCard.js.map +1 -1
- package/dist/components/sessions/SessionStatusBadge.js +7 -7
- package/dist/components/sessions/SessionStatusBadge.js.map +1 -1
- package/dist/components/shared/AgentSelector.js +4 -4
- package/dist/components/shared/AgentSelector.js.map +1 -1
- package/dist/components/shared/AgentSetupBanner.js +4 -4
- package/dist/components/shared/AgentSetupBanner.js.map +1 -1
- package/dist/components/shared/Card.js +4 -4
- package/dist/components/shared/Card.js.map +1 -1
- package/dist/components/shared/ErrorState.js +15 -9
- package/dist/components/shared/ErrorState.js.map +1 -1
- package/dist/components/shared/FilterDropdown.js +2 -2
- package/dist/components/shared/FilterDropdown.js.map +1 -1
- package/dist/components/shared/PageSkeleton.js +6 -6
- package/dist/components/shared/PageSkeleton.js.map +1 -1
- package/dist/components/shared/SearchInput.js +1 -1
- package/dist/components/shared/SearchInput.js.map +1 -1
- package/dist/components/shared/StatCard.js +7 -7
- package/dist/components/shared/StatCard.js.map +1 -1
- package/dist/components/templates/PublishTemplateForm.js +2 -2
- package/dist/components/templates/PublishTemplateForm.js.map +1 -1
- package/dist/components/templates/TemplateCard.js +5 -5
- package/dist/components/templates/TemplateCard.js.map +1 -1
- package/dist/components/templates/TemplateWizard.js +2 -2
- package/dist/components/templates/TemplateWizard.js.map +1 -1
- package/dist/components/tunnels/DomainVerificationBadge.js +4 -4
- package/dist/components/tunnels/DomainVerificationBadge.js.map +1 -1
- package/dist/components/tunnels/ProviderBadge.js +1 -1
- package/dist/components/tunnels/ProviderBadge.js.map +1 -1
- package/dist/components/tunnels/TunnelCard.js +16 -16
- package/dist/components/tunnels/TunnelCard.js.map +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
- package/dist/components/tunnels/TunnelPublicAccessPanel.js +1 -1
- package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
- package/dist/components/tunnels/TunnelStatusBadge.js +4 -4
- package/dist/components/tunnels/TunnelStatusBadge.js.map +1 -1
- package/dist/components/vibecalendar/LogsDrawer.js +2 -2
- package/dist/components/vibecalendar/LogsDrawer.js.map +1 -1
- package/dist/components/vibecalendar/ScheduleTaskModal.js +5 -5
- package/dist/components/vibecalendar/ScheduleTaskModal.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalAgendaView.js +2 -2
- package/dist/components/vibecalendar/VibeCalAgendaView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalDayView.js +3 -3
- package/dist/components/vibecalendar/VibeCalDayView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalMonthView.js +1 -1
- package/dist/components/vibecalendar/VibeCalMonthView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalWeekView.js +4 -4
- package/dist/components/vibecalendar/VibeCalWeekView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalendarHeader.js +4 -4
- package/dist/components/vibecalendar/VibeCalendarHeader.js.map +1 -1
- package/dist/components/vibecalendar/VibeTaskBlock.js +2 -2
- package/dist/components/vibecalendar/VibeTaskBlock.js.map +1 -1
- package/dist/components/vibecalendar/types.js +7 -7
- package/dist/components/vibecalendar/types.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonForm.js +9 -9
- package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonTile.js +6 -6
- package/dist/components/vibedeck/VibeDeckButtonTile.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckExecutionLog.js +10 -10
- package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js +8 -8
- package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckGrid.js +2 -2
- package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js +2 -2
- package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckSwitcher.js +8 -8
- package/dist/components/vibedeck/VibeDeckSwitcher.js.map +1 -1
- package/dist/components/vibes/VibeCard.js +22 -18
- package/dist/components/vibes/VibeCard.js.map +1 -1
- package/dist/components/vibes/VibeList.js +16 -12
- package/dist/components/vibes/VibeList.js.map +1 -1
- package/dist/pages/ActionsPage.js +4 -3
- package/dist/pages/ActionsPage.js.map +1 -1
- package/dist/pages/AgentDetailsPage.js +9 -9
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/AgentsLandingPage.js +4 -4
- package/dist/pages/AgentsLandingPage.js.map +1 -1
- package/dist/pages/AgentsPage.js +14 -14
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/AnalyticsPage.js +8 -8
- package/dist/pages/AnalyticsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +11 -11
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/CatalogPage.js +12 -9
- package/dist/pages/CatalogPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +7 -7
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/ComponentsPage.js +12 -9
- package/dist/pages/ComponentsPage.js.map +1 -1
- package/dist/pages/EnvTopologyPage.js +2 -2
- package/dist/pages/EnvTopologyPage.js.map +1 -1
- package/dist/pages/GettingStartedPage.js +12 -12
- package/dist/pages/GettingStartedPage.js.map +1 -1
- package/dist/pages/HealthPage.js +12 -12
- package/dist/pages/HealthPage.js.map +1 -1
- package/dist/pages/LogsPage.js +4 -4
- package/dist/pages/LogsPage.js.map +1 -1
- package/dist/pages/NoteDetailsPage.js +5 -5
- package/dist/pages/NoteDetailsPage.js.map +1 -1
- package/dist/pages/OverviewPage.js +76 -43
- package/dist/pages/OverviewPage.js.map +1 -1
- package/dist/pages/PluginsCatalogPage.js +7 -4
- package/dist/pages/PluginsCatalogPage.js.map +1 -1
- package/dist/pages/ScheduledJobDetailPage.js +7 -7
- package/dist/pages/ScheduledJobDetailPage.js.map +1 -1
- package/dist/pages/SessionDetailsPage.js +21 -21
- package/dist/pages/SessionDetailsPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +20 -20
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/SettingsPage.js +64 -52
- package/dist/pages/SettingsPage.js.map +1 -1
- package/dist/pages/SharedSessionPage.js +21 -21
- package/dist/pages/SharedSessionPage.js.map +1 -1
- package/dist/pages/SharedVibeDeckPage.js +5 -5
- package/dist/pages/SharedVibeDeckPage.js.map +1 -1
- package/dist/pages/TargetDetailsPage.js +56 -65
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +4 -4
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +10 -10
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/TemplatesPage.js +6 -5
- package/dist/pages/TemplatesPage.js.map +1 -1
- package/dist/pages/VibeAuditPage.js +3 -3
- package/dist/pages/VibeAuditPage.js.map +1 -1
- package/dist/pages/VibeCalendarPage.js +3 -3
- package/dist/pages/VibeCalendarPage.js.map +1 -1
- package/dist/pages/VibeDeckPage.js +3 -3
- package/dist/pages/VibeDeckPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +15 -15
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/VibeTaskDetailPage.js +5 -5
- package/dist/pages/VibeTaskDetailPage.js.map +1 -1
- package/dist/pages/VibesPage.js +18 -18
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/WebhooksPage.js +11 -11
- package/dist/pages/WebhooksPage.js.map +1 -1
- package/dist/pages/ai/AIBookmarksPage.js +1 -1
- package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
- package/dist/pages/ai/AISessionDetailPage.js +6 -6
- package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
- package/dist/pages/tunnels/AuditTab.js +1 -1
- package/dist/pages/tunnels/AuditTab.js.map +1 -1
- package/dist/pages/tunnels/DomainsTab.js +1 -1
- package/dist/pages/tunnels/DomainsTab.js.map +1 -1
- package/dist/pages/tunnels/ProvidersTab.js +1 -1
- package/dist/pages/tunnels/ProvidersTab.js.map +1 -1
- package/dist/pages/tunnels/SessionsTab.js +1 -1
- package/dist/pages/tunnels/SessionsTab.js.map +1 -1
- package/dist/pages/tunnels/TunnelDetailsPage.js +3 -3
- package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
- package/dist/pages/tunnels/UsageTab.js +1 -1
- package/dist/pages/tunnels/UsageTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +16 -16
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +1 -1
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +52 -49
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +9 -9
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIQueueTab.js","names":[],"sources":["../../../src/components/ai/AIQueueTab.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport {\n ListChecks,\n RefreshCw,\n X,\n AlertCircle,\n Clock,\n Loader2,\n CheckCircle2,\n Ban,\n} from 'lucide-react';\nimport { Card, LoadingState, ErrorState, EmptyState } from '@/components/shared';\nimport type { AIQueueItem, AgentRef } from '@/services/aiApi';\nimport { fetchQueueItems, cancelQueueItem } from '@/services/aiApi';\nimport { notify } from '@/lib/toast';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AIQueueTabProps {\n agent: AgentRef;\n onNavigateToSession?: (sessionId: string) => void;\n}\n\nconst STATUS_META: Record<\n AIQueueItem['status'],\n { label: string; cls: string; icon: typeof Clock }\n> = {\n pending: { label: 'Pending', cls: 'bg-status-warning-bg text-status-warning-text', icon: Clock },\n processing: {\n label: 'Processing',\n cls: 'bg-action-primary-bg/20 text-action-primary-bg',\n icon: Loader2,\n },\n completed: {\n label: 'Completed',\n cls: 'bg-status-success-bg text-status-success-text',\n icon: CheckCircle2,\n },\n failed: { label: 'Failed', cls: 'bg-status-error-bg text-status-error-text', icon: AlertCircle },\n cancelled: { label: 'Cancelled', cls: 'bg-bg-sunken text-text-secondary', icon: Ban },\n};\n\n/**\n * AIQueueTab — the \"queue overnight, review in the morning\" surface. Lists the queued AI prompts on\n * the active agent (the agent runs them in the background), with per-item status, attempts, and\n * errors, plus cancel for anything still pending. Auto-refreshes so a morning check shows the latest\n * results. Backed by the agent's `/ai/queue` API (fetchQueueItems / cancelQueueItem) — the same\n * governed agent surface the rest of AI mode uses.\n */\nexport function AIQueueTab({ agent, onNavigateToSession }: AIQueueTabProps): React.ReactElement {\n const tr = useTr();\n const [items, setItems] = useState<AIQueueItem[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [cancelling, setCancelling] = useState<string | null>(null);\n\n const load = useCallback(\n async (showSpinner: boolean): Promise<void> => {\n if (!agent.id) return;\n if (showSpinner) setLoading(true);\n try {\n const data = await fetchQueueItems(agent);\n setItems(data);\n setError(null);\n } catch (err) {\n setError((err as Error).message);\n } finally {\n if (showSpinner) setLoading(false);\n }\n },\n [agent]\n );\n\n useEffect(() => {\n void load(true);\n // Poll while items are still in flight so a left-open tab updates itself overnight.\n const timer = window.setInterval(() => void load(false), 15000);\n return () => window.clearInterval(timer);\n }, [load]);\n\n const handleCancel = async (id: string): Promise<void> => {\n setCancelling(id);\n try {\n await cancelQueueItem(agent, id);\n notify.success(tr('aiQueue.cancelled', 'Queued item cancelled'));\n await load(false);\n } catch (err) {\n notify.error(tr('aiQueue.cancelFailed', 'Could not cancel'), (err as Error).message);\n } finally {\n setCancelling(null);\n }\n };\n\n if (!agent.id) {\n return (\n <EmptyState\n icon={<ListChecks className=\"size-10\" />}\n title={tr('aiQueue.noAgentTitle', 'No active agent')}\n description={tr(\n 'aiQueue.noAgentDesc',\n 'Start or attach an agent to queue AI prompts for background processing.'\n )}\n />\n );\n }\n\n if (loading && items.length === 0) return <LoadingState />;\n if (error && items.length === 0) {\n return <ErrorState message={error} onRetry={() => void load(true)} />;\n }\n\n const pendingCount = items.filter(\n (i) => i.status === 'pending' || i.status === 'processing'\n ).length;\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"flex items-center gap-2 text-base font-semibold text-text-primary\">\n <ListChecks className=\"size-5\" />\n {tr('aiQueue.title', 'AI Queue')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'aiQueue.subtitle',\n 'Prompts queued for background processing on this agent — queue at night, review in the morning.'\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => void load(true)}\n className=\"flex items-center gap-1.5 rounded-md border border-border-default px-3 py-1.5 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n {tr('common.refresh', 'Refresh')}\n </button>\n </div>\n\n {items.length === 0 ? (\n <EmptyState\n icon={<ListChecks className=\"size-10\" />}\n title={tr('aiQueue.emptyTitle', 'Queue is empty')}\n description={tr(\n 'aiQueue.emptyDesc',\n 'Dispatch a prompt with \"Queue\" instead of \"Run\" to have the agent process it in the background.'\n )}\n />\n ) : (\n <>\n {pendingCount > 0 && (\n <p className=\"text-xs text-text-tertiary\">\n {pendingCount} {tr('aiQueue.inFlight', 'in flight')} · {items.length}{' '}\n {tr('aiQueue.total', 'total')}\n </p>\n )}\n <Card>\n <ul className=\"divide-y divide-border-subtle\">\n {items.map((item) => {\n const meta = STATUS_META[item.status];\n const StatusIcon = meta.icon;\n return (\n <li key={item.id} className=\"flex items-center gap-3 px-4 py-3\">\n <span\n className={`inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium ${meta.cls}`}\n >\n <StatusIcon\n className={`size-3.5 ${item.status === 'processing' ? 'animate-spin' : ''}`}\n />\n {meta.label}\n </span>\n <div className=\"min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={() => onNavigateToSession?.(item.sessionId)}\n className=\"truncate text-left font-mono text-sm text-text-primary hover:underline\"\n title={tr('aiQueue.openSession', 'Open the session this prompt runs in')}\n >\n {item.sessionId}\n </button>\n <div className=\"text-xs text-text-tertiary\">\n {tr('aiQueue.attempt', 'attempt')} {item.attempts}/{item.maxAttempts}\n {item.createdAt ? ` · ${new Date(item.createdAt).toLocaleString()}` : ''}\n </div>\n {item.lastError && (\n <div className=\"mt-0.5 truncate text-xs text-status-error-text\">\n {item.lastError}\n </div>\n )}\n </div>\n {item.status === 'pending' && (\n <button\n type=\"button\"\n onClick={() => void handleCancel(item.id)}\n disabled={cancelling === item.id}\n className=\"shrink-0 rounded p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-status-error-text disabled:opacity-50\"\n aria-label={tr('common.cancel', 'Cancel')}\n >\n <X className=\"size-4\" />\n </button>\n )}\n </li>\n );\n })}\n </ul>\n </Card>\n </>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAsBA,IAAM,IAGF;CACF,SAAS;EAAE,OAAO;EAAW,KAAK;EAAiD,MAAM;EAAO;CAChG,YAAY;EACV,OAAO;EACP,KAAK;EACL,MAAM;EACP;CACD,WAAW;EACT,OAAO;EACP,KAAK;EACL,MAAM;EACP;CACD,QAAQ;EAAE,OAAO;EAAU,KAAK;EAA6C,MAAM;EAAa;CAChG,WAAW;EAAE,OAAO;EAAa,KAAK;EAAoC,MAAM;EAAK;CACtF;AASD,SAAgB,EAAW,EAAE,UAAO,0BAA4D;CAC9F,IAAM,IAAK,GAAO,EACZ,CAAC,GAAO,KAAY,EAAwB,EAAE,CAAC,EAC/C,CAAC,GAAS,KAAc,EAAS,GAAK,EACtC,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAY,KAAiB,EAAwB,KAAK,EAE3D,IAAO,EACX,OAAO,MAAwC;AACxC,QAAM,IACX;GAAI,KAAa,EAAW,GAAK;AACjC,OAAI;AAGF,IADA,EADa,MAAM,EAAgB,EAAM,CAC3B,EACd,EAAS,KAAK;YACP,GAAK;AACZ,MAAU,EAAc,QAAQ;aACxB;AACR,IAAI,KAAa,EAAW,GAAM;;;IAGtC,CAAC,EAAM,CACR;AAED,SAAgB;AACT,IAAK,GAAK;EAEf,IAAM,IAAQ,OAAO,kBAAkB,KAAK,EAAK,GAAM,EAAE,KAAM;AAC/D,eAAa,OAAO,cAAc,EAAM;IACvC,CAAC,EAAK,CAAC;CAEV,IAAM,IAAe,OAAO,MAA8B;AACxD,IAAc,EAAG;AACjB,MAAI;AAGF,GAFA,MAAM,EAAgB,GAAO,EAAG,EAChC,EAAO,QAAQ,EAAG,qBAAqB,wBAAwB,CAAC,EAChE,MAAM,EAAK,GAAM;WACV,GAAK;AACZ,KAAO,MAAM,EAAG,wBAAwB,mBAAmB,EAAG,EAAc,QAAQ;YAC5E;AACR,KAAc,KAAK;;;AAIvB,KAAI,CAAC,EAAM,GACT,QACE,kBAAC,GAAD;EACE,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;EACxC,OAAO,EAAG,wBAAwB,kBAAkB;EACpD,aAAa,EACX,uBACA,0EACD;EACD,CAAA;AAIN,KAAI,KAAW,EAAM,WAAW,EAAG,QAAO,kBAAC,GAAD,EAAgB,CAAA;AAC1D,KAAI,KAAS,EAAM,WAAW,EAC5B,QAAO,kBAAC,GAAD;EAAY,SAAS;EAAO,eAAe,KAAK,EAAK,GAAK;EAAI,CAAA;CAGvE,IAAM,IAAe,EAAM,QACxB,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW,aAC/C,CAAC;AAEF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cAAd,CACE,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA,EAChC,EAAG,iBAAiB,WAAW,CAC7B;OACL,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,oBACA,kGACD;IACC,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,KAAK,EAAK,GAAK;IAC9B,WAAU;cAHZ,CAKE,kBAAC,GAAD,EAAW,WAAW,UAAU,IAAU,iBAAiB,MAAQ,CAAA,EAClE,EAAG,kBAAkB,UAAU,CACzB;MACL;MAEL,EAAM,WAAW,IAChB,kBAAC,GAAD;GACE,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;GACxC,OAAO,EAAG,sBAAsB,iBAAiB;GACjD,aAAa,EACX,qBACA,sGACD;GACD,CAAA,GAEF,kBAAA,GAAA,EAAA,UAAA,CACG,IAAe,KACd,kBAAC,KAAD;GAAG,WAAU;aAAb;IACG;IAAa;IAAE,EAAG,oBAAoB,YAAY;IAAC;IAAI,EAAM;IAAQ;IACrE,EAAG,iBAAiB,QAAQ;IAC3B;MAEN,kBAAC,GAAD,EAAA,UACE,kBAAC,MAAD;GAAI,WAAU;aACX,EAAM,KAAK,MAAS;IACnB,IAAM,IAAO,EAAY,EAAK,SACxB,IAAa,EAAK;AACxB,WACE,kBAAC,MAAD;KAAkB,WAAU;eAA5B;MACE,kBAAC,QAAD;OACE,WAAW,wFAAwF,EAAK;iBAD1G,CAGE,kBAAC,GAAD,EACE,WAAW,YAAY,EAAK,WAAW,eAAe,iBAAiB,MACvE,CAAA,EACD,EAAK,MACD;;MACP,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,IAAsB,EAAK,UAAU;SACpD,WAAU;SACV,OAAO,EAAG,uBAAuB,uCAAuC;mBAEvE,EAAK;SACC,CAAA;QACT,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACG,EAAG,mBAAmB,UAAU;UAAC;UAAE,EAAK;UAAS;UAAE,EAAK;UACxD,EAAK,YAAY,MAAM,IAAI,KAAK,EAAK,UAAU,CAAC,gBAAgB,KAAK;UAClE;;QACL,EAAK,aACJ,kBAAC,OAAD;SAAK,WAAU;mBACZ,EAAK;SACF,CAAA;QAEJ;;MACL,EAAK,WAAW,aACf,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,KAAK,EAAa,EAAK,GAAG;OACzC,UAAU,MAAe,EAAK;OAC9B,WAAU;OACV,cAAY,EAAG,iBAAiB,SAAS;iBAEzC,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;OACjB,CAAA;MAER;OAvCI,EAAK,GAuCT;KAEP;GACC,CAAA,EACA,CAAA,CACN,EAAA,CAAA,CAED"}
|
|
1
|
+
{"version":3,"file":"AIQueueTab.js","names":[],"sources":["../../../src/components/ai/AIQueueTab.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport {\n ListChecks,\n RefreshCw,\n X,\n AlertCircle,\n Clock,\n Loader2,\n CheckCircle2,\n Ban,\n} from 'lucide-react';\nimport { Card, LoadingState, ErrorState, EmptyState } from '@/components/shared';\nimport type { AIQueueItem, AgentRef } from '@/services/aiApi';\nimport { fetchQueueItems, cancelQueueItem } from '@/services/aiApi';\nimport { notify } from '@/lib/toast';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AIQueueTabProps {\n agent: AgentRef;\n onNavigateToSession?: (sessionId: string) => void;\n}\n\nconst STATUS_META: Record<\n AIQueueItem['status'],\n { label: string; cls: string; icon: typeof Clock }\n> = {\n pending: {\n label: 'Pending',\n cls: 'bg-status-warning-bg-subtle text-status-warning-text',\n icon: Clock,\n },\n processing: {\n label: 'Processing',\n cls: 'bg-[var(--color-accent-soft)] text-text-primary',\n icon: Loader2,\n },\n completed: {\n label: 'Completed',\n cls: 'bg-status-success-bg-subtle text-status-success-text',\n icon: CheckCircle2,\n },\n failed: {\n label: 'Failed',\n cls: 'bg-status-error-bg-subtle text-status-error-text',\n icon: AlertCircle,\n },\n cancelled: { label: 'Cancelled', cls: 'bg-bg-sunken text-text-secondary', icon: Ban },\n};\n\n/**\n * AIQueueTab — the \"queue overnight, review in the morning\" surface. Lists the queued AI prompts on\n * the active agent (the agent runs them in the background), with per-item status, attempts, and\n * errors, plus cancel for anything still pending. Auto-refreshes so a morning check shows the latest\n * results. Backed by the agent's `/ai/queue` API (fetchQueueItems / cancelQueueItem) — the same\n * governed agent surface the rest of AI mode uses.\n */\nexport function AIQueueTab({ agent, onNavigateToSession }: AIQueueTabProps): React.ReactElement {\n const tr = useTr();\n const [items, setItems] = useState<AIQueueItem[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [cancelling, setCancelling] = useState<string | null>(null);\n\n const load = useCallback(\n async (showSpinner: boolean): Promise<void> => {\n if (!agent.id) return;\n if (showSpinner) setLoading(true);\n try {\n const data = await fetchQueueItems(agent);\n setItems(data);\n setError(null);\n } catch (err) {\n setError((err as Error).message);\n } finally {\n if (showSpinner) setLoading(false);\n }\n },\n [agent]\n );\n\n useEffect(() => {\n void load(true);\n // Poll while items are still in flight so a left-open tab updates itself overnight.\n const timer = window.setInterval(() => void load(false), 15000);\n return () => window.clearInterval(timer);\n }, [load]);\n\n const handleCancel = async (id: string): Promise<void> => {\n setCancelling(id);\n try {\n await cancelQueueItem(agent, id);\n notify.success(tr('aiQueue.cancelled', 'Queued item cancelled'));\n await load(false);\n } catch (err) {\n notify.error(tr('aiQueue.cancelFailed', 'Could not cancel'), (err as Error).message);\n } finally {\n setCancelling(null);\n }\n };\n\n if (!agent.id) {\n return (\n <EmptyState\n icon={<ListChecks className=\"size-10\" />}\n title={tr('aiQueue.noAgentTitle', 'No active agent')}\n description={tr(\n 'aiQueue.noAgentDesc',\n 'Start or attach an agent to queue AI prompts for background processing.'\n )}\n />\n );\n }\n\n if (loading && items.length === 0) return <LoadingState />;\n if (error && items.length === 0) {\n return <ErrorState message={error} onRetry={() => void load(true)} />;\n }\n\n const pendingCount = items.filter(\n (i) => i.status === 'pending' || i.status === 'processing'\n ).length;\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"flex items-center gap-2 text-base font-semibold text-text-primary\">\n <ListChecks className=\"size-5\" />\n {tr('aiQueue.title', 'AI Queue')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'aiQueue.subtitle',\n 'Prompts queued for background processing on this agent — queue at night, review in the morning.'\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => void load(true)}\n className=\"flex items-center gap-1.5 rounded-md border border-border-default px-3 py-1.5 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n {tr('common.refresh', 'Refresh')}\n </button>\n </div>\n\n {items.length === 0 ? (\n <EmptyState\n icon={<ListChecks className=\"size-10\" />}\n title={tr('aiQueue.emptyTitle', 'Queue is empty')}\n description={tr(\n 'aiQueue.emptyDesc',\n 'Dispatch a prompt with \"Queue\" instead of \"Run\" to have the agent process it in the background.'\n )}\n />\n ) : (\n <>\n {pendingCount > 0 && (\n <p className=\"text-xs text-text-tertiary\">\n {pendingCount} {tr('aiQueue.inFlight', 'in flight')} · {items.length}{' '}\n {tr('aiQueue.total', 'total')}\n </p>\n )}\n <Card>\n <ul className=\"divide-y divide-border-subtle\">\n {items.map((item) => {\n const meta = STATUS_META[item.status];\n const StatusIcon = meta.icon;\n return (\n <li key={item.id} className=\"flex items-center gap-3 px-4 py-3\">\n <span\n className={`inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium ${meta.cls}`}\n >\n <StatusIcon\n className={`size-3.5 ${item.status === 'processing' ? 'animate-spin' : ''}`}\n />\n {meta.label}\n </span>\n <div className=\"min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={() => onNavigateToSession?.(item.sessionId)}\n className=\"truncate text-left font-mono text-sm text-text-primary hover:underline\"\n title={tr('aiQueue.openSession', 'Open the session this prompt runs in')}\n >\n {item.sessionId}\n </button>\n <div className=\"text-xs text-text-tertiary\">\n {tr('aiQueue.attempt', 'attempt')} {item.attempts}/{item.maxAttempts}\n {item.createdAt ? ` · ${new Date(item.createdAt).toLocaleString()}` : ''}\n </div>\n {item.lastError && (\n <div className=\"mt-0.5 truncate text-xs text-status-error-text\">\n {item.lastError}\n </div>\n )}\n </div>\n {item.status === 'pending' && (\n <button\n type=\"button\"\n onClick={() => void handleCancel(item.id)}\n disabled={cancelling === item.id}\n className=\"shrink-0 rounded p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-status-error-text disabled:opacity-50\"\n aria-label={tr('common.cancel', 'Cancel')}\n >\n <X className=\"size-4\" />\n </button>\n )}\n </li>\n );\n })}\n </ul>\n </Card>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -10,10 +10,10 @@ import { MessageSquare as u, Plus as d, X as f } from "lucide-react";
|
|
|
10
10
|
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
11
11
|
//#region src/components/ai/AISessionsTab.tsx
|
|
12
12
|
var h = {
|
|
13
|
-
active: "bg-status-success-bg text-status-success-text",
|
|
14
|
-
idle: "bg-status-warning-bg text-status-warning-text",
|
|
15
|
-
processing: "bg-
|
|
16
|
-
error: "bg-status-error-bg text-status-error-text",
|
|
13
|
+
active: "bg-status-success-bg-subtle text-status-success-text",
|
|
14
|
+
idle: "bg-status-warning-bg-subtle text-status-warning-text",
|
|
15
|
+
processing: "bg-[var(--color-accent-soft)] text-text-primary",
|
|
16
|
+
error: "bg-status-error-bg-subtle text-status-error-text",
|
|
17
17
|
terminated: "bg-bg-sunken text-text-secondary"
|
|
18
18
|
}, g = [
|
|
19
19
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AISessionsTab.js","names":[],"sources":["../../../src/components/ai/AISessionsTab.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport { Plus, MessageSquare, X } from 'lucide-react';\nimport { Card, LoadingState, ErrorState, EmptyState } from '@/components/shared';\nimport type { AISession, AgentRef } from '@/services/aiApi';\nimport { fetchSessions, createSession } from '@/services/aiApi';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AISessionsTabProps {\n agent: AgentRef;\n onNavigateToSession?: (sessionId: string) => void;\n}\n\nconst statusColors: Record<string, string> = {\n active: 'bg-status-success-bg text-status-success-text',\n idle: 'bg-status-warning-bg text-status-warning-text',\n processing: 'bg-action-primary-bg/20 text-action-primary-bg',\n error: 'bg-status-error-bg text-status-error-text',\n terminated: 'bg-bg-sunken text-text-secondary',\n};\n\nconst agentTypes = [\n { value: 'claude', label: 'Claude' },\n { value: 'codex', label: 'Codex' },\n { value: 'opencode', label: 'OpenCode' },\n { value: 'gemini', label: 'Gemini' },\n { value: 'cursor', label: 'Cursor' },\n { value: 'openrouter', label: 'OpenRouter' },\n { value: 'minimax', label: 'Minimax' },\n { value: 'ollama', label: 'Ollama' },\n { value: 'openai-compat', label: 'OpenAI-compatible' },\n { value: 'copilot', label: 'Copilot' },\n { value: 'aider', label: 'Aider' },\n { value: 'amp', label: 'Amp' },\n { value: 'crush', label: 'Crush' },\n { value: 'goose', label: 'Goose' },\n { value: 'pi', label: 'Pi' },\n { value: 'plandex', label: 'Plandex' },\n];\n\nexport function AISessionsTab({ agent, onNavigateToSession }: AISessionsTabProps) {\n const tr = useTr();\n const [sessions, setSessions] = useState<AISession[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [showCreate, setShowCreate] = useState(false);\n const [creating, setCreating] = useState(false);\n const [newName, setNewName] = useState('');\n const [newAgentType, setNewAgentType] = useState('claude');\n\n const loadSessions = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const result = await fetchSessions(agent);\n setSessions(result);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load sessions');\n } finally {\n setLoading(false);\n }\n }, [agent]);\n\n useEffect(() => {\n loadSessions();\n }, [loadSessions]);\n\n const handleCreate = async () => {\n if (!newName.trim()) return;\n setCreating(true);\n try {\n const session = await createSession(agent, {\n name: newName,\n agentType: newAgentType,\n });\n setShowCreate(false);\n setNewName('');\n setNewAgentType('claude');\n await loadSessions();\n onNavigateToSession?.(session.id);\n } catch (err) {\n console.error('Failed to create session:', err);\n } finally {\n setCreating(false);\n }\n };\n\n if (loading)\n return <LoadingState message={tr('vibecontrols.ai.sessions.loading', 'Loading sessions...')} />;\n if (error)\n return (\n <ErrorState\n title={tr('vibecontrols.ai.sessions.loadFailed', 'Failed to load sessions')}\n message={error}\n onRetry={loadSessions}\n />\n );\n\n return (\n <div className=\"space-y-4\">\n {/* Toolbar */}\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n <p className=\"text-sm text-text-secondary\">{sessions.length} session(s)</p>\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"flex w-full 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 sm:w-auto\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.ai.sessions.newSession', 'New Session')}\n </button>\n </div>\n\n {/* Session Grid */}\n {sessions.length === 0 ? (\n <EmptyState\n icon={<MessageSquare className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.ai.sessions.emptyTitle', 'No AI sessions')}\n description={tr(\n 'vibecontrols.ai.sessions.emptyDescription',\n 'Create a new session to start interacting with an AI agent'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\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('vibecontrols.ai.sessions.newSession', 'New Session')}\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {sessions.map((session) => (\n <Card\n key={session.id}\n hoverable\n onClick={() => onNavigateToSession?.(session.id)}\n className=\"p-4 cursor-pointer\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0 flex-1\">\n <h4 className=\"font-semibold text-text-primary truncate\">{session.name}</h4>\n <p className=\"text-xs text-text-secondary mt-0.5\">{session.agentType}</p>\n </div>\n <span\n className={`px-2 py-0.5 text-xs rounded-full font-medium flex-shrink-0 ${statusColors[session.status] ?? 'bg-bg-sunken text-text-secondary'}`}\n >\n {session.status}\n </span>\n </div>\n\n {session.stats && (\n <div className=\"mt-3 grid grid-cols-2 gap-2 text-xs\">\n <div className=\"bg-bg-sunken rounded p-2\">\n <div className=\"text-text-secondary\">Total Tokens</div>\n <div className=\"font-semibold text-text-primary\">\n {session.stats.totalTokens?.toLocaleString() ?? 0}\n </div>\n </div>\n <div className=\"bg-bg-sunken rounded p-2\">\n <div className=\"text-text-secondary\">Input / Output</div>\n <div className=\"font-semibold text-text-primary\">\n {session.stats.inputTokens?.toLocaleString() ?? 0} /{' '}\n {session.stats.outputTokens?.toLocaleString() ?? 0}\n </div>\n </div>\n </div>\n )}\n\n {session.createdAt && (\n <p className=\"mt-2 text-xs text-text-muted\">\n Created {new Date(session.createdAt).toLocaleDateString()}\n </p>\n )}\n </Card>\n ))}\n </div>\n )}\n\n {/* Create Session Dialog */}\n {showCreate && (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => setShowCreate(false))();\n }\n }}\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\"\n onClick={() => setShowCreate(false)}\n >\n <div\n className=\"bg-bg-surface border border-border-default rounded-lg shadow-xl w-full max-w-sm p-6\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {tr('vibecontrols.ai.sessions.newAISession', 'New AI Session')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setShowCreate(false)}\n className=\"p-1 hover:bg-bg-sunken rounded-md\"\n >\n <X className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.ai.sessions.name', 'Name')}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm focus:outline-none focus:border-border-strong\"\n placeholder={tr(\n 'vibecontrols.ai.sessions.sessionNamePlaceholder',\n 'Session name'\n )}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.ai.sessions.agentType', 'Agent Type')}\n </label>\n <select\n value={newAgentType}\n onChange={(e) => setNewAgentType(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm\"\n >\n {agentTypes.map((t) => (\n <option key={t.value} value={t.value}>\n {t.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"flex justify-end gap-2 mt-6\">\n <button\n type=\"button\"\n onClick={() => setShowCreate(false)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors\"\n >\n {tr('vibecontrols.ai.sessions.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleCreate}\n disabled={creating || !newName.trim()}\n className=\"px-4 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {creating\n ? tr('vibecontrols.ai.sessions.creating', 'Creating...')\n : tr('vibecontrols.ai.sessions.create', 'Create')}\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AISessionsTab.js","names":[],"sources":["../../../src/components/ai/AISessionsTab.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport { Plus, MessageSquare, X } from 'lucide-react';\nimport { Card, LoadingState, ErrorState, EmptyState } from '@/components/shared';\nimport type { AISession, AgentRef } from '@/services/aiApi';\nimport { fetchSessions, createSession } from '@/services/aiApi';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AISessionsTabProps {\n agent: AgentRef;\n onNavigateToSession?: (sessionId: string) => void;\n}\n\nconst statusColors: Record<string, string> = {\n active: 'bg-status-success-bg-subtle text-status-success-text',\n idle: 'bg-status-warning-bg-subtle text-status-warning-text',\n processing: 'bg-[var(--color-accent-soft)] text-text-primary',\n error: 'bg-status-error-bg-subtle text-status-error-text',\n terminated: 'bg-bg-sunken text-text-secondary',\n};\n\nconst agentTypes = [\n { value: 'claude', label: 'Claude' },\n { value: 'codex', label: 'Codex' },\n { value: 'opencode', label: 'OpenCode' },\n { value: 'gemini', label: 'Gemini' },\n { value: 'cursor', label: 'Cursor' },\n { value: 'openrouter', label: 'OpenRouter' },\n { value: 'minimax', label: 'Minimax' },\n { value: 'ollama', label: 'Ollama' },\n { value: 'openai-compat', label: 'OpenAI-compatible' },\n { value: 'copilot', label: 'Copilot' },\n { value: 'aider', label: 'Aider' },\n { value: 'amp', label: 'Amp' },\n { value: 'crush', label: 'Crush' },\n { value: 'goose', label: 'Goose' },\n { value: 'pi', label: 'Pi' },\n { value: 'plandex', label: 'Plandex' },\n];\n\nexport function AISessionsTab({ agent, onNavigateToSession }: AISessionsTabProps) {\n const tr = useTr();\n const [sessions, setSessions] = useState<AISession[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [showCreate, setShowCreate] = useState(false);\n const [creating, setCreating] = useState(false);\n const [newName, setNewName] = useState('');\n const [newAgentType, setNewAgentType] = useState('claude');\n\n const loadSessions = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const result = await fetchSessions(agent);\n setSessions(result);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load sessions');\n } finally {\n setLoading(false);\n }\n }, [agent]);\n\n useEffect(() => {\n loadSessions();\n }, [loadSessions]);\n\n const handleCreate = async () => {\n if (!newName.trim()) return;\n setCreating(true);\n try {\n const session = await createSession(agent, {\n name: newName,\n agentType: newAgentType,\n });\n setShowCreate(false);\n setNewName('');\n setNewAgentType('claude');\n await loadSessions();\n onNavigateToSession?.(session.id);\n } catch (err) {\n console.error('Failed to create session:', err);\n } finally {\n setCreating(false);\n }\n };\n\n if (loading)\n return <LoadingState message={tr('vibecontrols.ai.sessions.loading', 'Loading sessions...')} />;\n if (error)\n return (\n <ErrorState\n title={tr('vibecontrols.ai.sessions.loadFailed', 'Failed to load sessions')}\n message={error}\n onRetry={loadSessions}\n />\n );\n\n return (\n <div className=\"space-y-4\">\n {/* Toolbar */}\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n <p className=\"text-sm text-text-secondary\">{sessions.length} session(s)</p>\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"flex w-full 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 sm:w-auto\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.ai.sessions.newSession', 'New Session')}\n </button>\n </div>\n\n {/* Session Grid */}\n {sessions.length === 0 ? (\n <EmptyState\n icon={<MessageSquare className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.ai.sessions.emptyTitle', 'No AI sessions')}\n description={tr(\n 'vibecontrols.ai.sessions.emptyDescription',\n 'Create a new session to start interacting with an AI agent'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\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('vibecontrols.ai.sessions.newSession', 'New Session')}\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {sessions.map((session) => (\n <Card\n key={session.id}\n hoverable\n onClick={() => onNavigateToSession?.(session.id)}\n className=\"p-4 cursor-pointer\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0 flex-1\">\n <h4 className=\"font-semibold text-text-primary truncate\">{session.name}</h4>\n <p className=\"text-xs text-text-secondary mt-0.5\">{session.agentType}</p>\n </div>\n <span\n className={`px-2 py-0.5 text-xs rounded-full font-medium flex-shrink-0 ${statusColors[session.status] ?? 'bg-bg-sunken text-text-secondary'}`}\n >\n {session.status}\n </span>\n </div>\n\n {session.stats && (\n <div className=\"mt-3 grid grid-cols-2 gap-2 text-xs\">\n <div className=\"bg-bg-sunken rounded p-2\">\n <div className=\"text-text-secondary\">Total Tokens</div>\n <div className=\"font-semibold text-text-primary\">\n {session.stats.totalTokens?.toLocaleString() ?? 0}\n </div>\n </div>\n <div className=\"bg-bg-sunken rounded p-2\">\n <div className=\"text-text-secondary\">Input / Output</div>\n <div className=\"font-semibold text-text-primary\">\n {session.stats.inputTokens?.toLocaleString() ?? 0} /{' '}\n {session.stats.outputTokens?.toLocaleString() ?? 0}\n </div>\n </div>\n </div>\n )}\n\n {session.createdAt && (\n <p className=\"mt-2 text-xs text-text-muted\">\n Created {new Date(session.createdAt).toLocaleDateString()}\n </p>\n )}\n </Card>\n ))}\n </div>\n )}\n\n {/* Create Session Dialog */}\n {showCreate && (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => setShowCreate(false))();\n }\n }}\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\"\n onClick={() => setShowCreate(false)}\n >\n <div\n className=\"bg-bg-surface border border-border-default rounded-lg shadow-xl w-full max-w-sm p-6\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {tr('vibecontrols.ai.sessions.newAISession', 'New AI Session')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setShowCreate(false)}\n className=\"p-1 hover:bg-bg-sunken rounded-md\"\n >\n <X className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.ai.sessions.name', 'Name')}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm focus:outline-none focus:border-border-strong\"\n placeholder={tr(\n 'vibecontrols.ai.sessions.sessionNamePlaceholder',\n 'Session name'\n )}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.ai.sessions.agentType', 'Agent Type')}\n </label>\n <select\n value={newAgentType}\n onChange={(e) => setNewAgentType(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm\"\n >\n {agentTypes.map((t) => (\n <option key={t.value} value={t.value}>\n {t.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"flex justify-end gap-2 mt-6\">\n <button\n type=\"button\"\n onClick={() => setShowCreate(false)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors\"\n >\n {tr('vibecontrols.ai.sessions.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleCreate}\n disabled={creating || !newName.trim()}\n className=\"px-4 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {creating\n ? tr('vibecontrols.ai.sessions.creating', 'Creating...')\n : tr('vibecontrols.ai.sessions.create', 'Create')}\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAYA,IAAM,IAAuC;CAC3C,QAAQ;CACR,MAAM;CACN,YAAY;CACZ,OAAO;CACP,YAAY;CACb,EAEK,IAAa;CACjB;EAAE,OAAO;EAAU,OAAO;EAAU;CACpC;EAAE,OAAO;EAAS,OAAO;EAAS;CAClC;EAAE,OAAO;EAAY,OAAO;EAAY;CACxC;EAAE,OAAO;EAAU,OAAO;EAAU;CACpC;EAAE,OAAO;EAAU,OAAO;EAAU;CACpC;EAAE,OAAO;EAAc,OAAO;EAAc;CAC5C;EAAE,OAAO;EAAW,OAAO;EAAW;CACtC;EAAE,OAAO;EAAU,OAAO;EAAU;CACpC;EAAE,OAAO;EAAiB,OAAO;EAAqB;CACtD;EAAE,OAAO;EAAW,OAAO;EAAW;CACtC;EAAE,OAAO;EAAS,OAAO;EAAS;CAClC;EAAE,OAAO;EAAO,OAAO;EAAO;CAC9B;EAAE,OAAO;EAAS,OAAO;EAAS;CAClC;EAAE,OAAO;EAAS,OAAO;EAAS;CAClC;EAAE,OAAO;EAAM,OAAO;EAAM;CAC5B;EAAE,OAAO;EAAW,OAAO;EAAW;CACvC;AAED,SAAgB,EAAc,EAAE,UAAO,0BAA2C;CAChF,IAAM,IAAK,GAAO,EACZ,CAAC,GAAU,KAAe,EAAsB,EAAE,CAAC,EACnD,CAAC,GAAS,KAAc,EAAS,GAAK,EACtC,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAU,KAAe,EAAS,GAAM,EACzC,CAAC,GAAS,KAAc,EAAS,GAAG,EACpC,CAAC,GAAc,KAAmB,EAAS,SAAS,EAEpD,IAAe,EAAY,YAAY;AAE3C,EADA,EAAW,GAAK,EAChB,EAAS,KAAK;AACd,MAAI;AAEF,KADe,MAAM,EAAc,EAAM,CACtB;WACZ,GAAK;AACZ,KAAS,aAAe,QAAQ,EAAI,UAAU,0BAA0B;YAChE;AACR,KAAW,GAAM;;IAElB,CAAC,EAAM,CAAC;AAqCX,QAnCA,QAAgB;AACd,KAAc;IACb,CAAC,EAAa,CAAC,EAsBd,IACK,kBAAC,GAAD,EAAc,SAAS,EAAG,oCAAoC,sBAAsB,EAAI,CAAA,GAC7F,IAEA,kBAAC,GAAD;EACE,OAAO,EAAG,uCAAuC,0BAA0B;EAC3E,SAAS;EACT,SAAS;EACT,CAAA,GAIJ,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAAb,CAA4C,EAAS,QAAO,cAAe;QAC3E,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAc,GAAK;KAClC,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,EAAG,uCAAuC,cAAc,CAClD;OACL;;GAGL,EAAS,WAAW,IACnB,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAe,WAAU,8BAA+B,CAAA;IAC9D,OAAO,EAAG,uCAAuC,iBAAiB;IAClE,aAAa,EACX,6CACA,6DACD;IACD,QACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAc,GAAK;KAClC,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,EAAG,uCAAuC,cAAc,CAClD;;IAEX,CAAA,GAEF,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAS,KAAK,MACb,kBAAC,GAAD;KAEE,WAAA;KACA,eAAe,IAAsB,EAAQ,GAAG;KAChD,WAAU;eAJZ;MAME,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA4C,EAAQ;SAAU,CAAA,EAC5E,kBAAC,KAAD;SAAG,WAAU;mBAAsC,EAAQ;SAAc,CAAA,CACrE;WACN,kBAAC,QAAD;QACE,WAAW,8DAA8D,EAAa,EAAQ,WAAW;kBAExG,EAAQ;QACJ,CAAA,CACH;;MAEL,EAAQ,SACP,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAsB;SAAkB,CAAA,EACvD,kBAAC,OAAD;SAAK,WAAU;mBACZ,EAAQ,MAAM,aAAa,gBAAgB,IAAI;SAC5C,CAAA,CACF;WACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAsB;SAAoB,CAAA,EACzD,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACG,EAAQ,MAAM,aAAa,gBAAgB,IAAI;UAAE;UAAG;UACpD,EAAQ,MAAM,cAAc,gBAAgB,IAAI;UAC7C;WACF;UACF;;MAGP,EAAQ,aACP,kBAAC,KAAD;OAAG,WAAU;iBAAb,CAA4C,YACjC,IAAI,KAAK,EAAQ,UAAU,CAAC,oBAAoB,CACvD;;MAED;OAxCA,EAAQ,GAwCR,CACP;IACE,CAAA;GAIP,KACC,kBAAC,OAAD;IACE,MAAK;IACL,UAAU;IACV,YAAY,MAAM;AAChB,MAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EACX,EAAc,GAAM;;IAG/B,WAAU;IACV,eAAe,EAAc,GAAM;cAEnC,kBAAC,OAAD;KACE,WAAU;KACV,UAAU,MAAM,EAAE,iBAAiB;eAFrC;MAIE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,yCAAyC,iBAAiB;QAC3D,CAAA,EACL,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAc,GAAM;QACnC,WAAU;kBAEV,kBAAC,GAAD,EAAG,WAAU,8BAA+B,CAAA;QACrC,CAAA,CACL;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAU;kBACd,EAAG,iCAAiC,OAAO;QACtC,CAAA,EACR,kBAAC,SAAD;QACE,cAAW;QACX,MAAK;QACL,OAAO;QACP,WAAW,MAAM,EAAW,EAAE,OAAO,MAAM;QAC3C,WAAU;QACV,aAAa,EACX,mDACA,eACD;QACD,CAAA,CACE,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAU;kBACd,EAAG,sCAAsC,aAAa;QACjD,CAAA,EACR,kBAAC,UAAD;QACE,OAAO;QACP,WAAW,MAAM,EAAgB,EAAE,OAAO,MAAM;QAChD,WAAU;kBAET,EAAW,KAAK,MACf,kBAAC,UAAD;SAAsB,OAAO,EAAE;mBAC5B,EAAE;SACI,EAFI,EAAE,MAEN,CACT;QACK,CAAA,CACL,EAAA,CAAA,CACF;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAc,GAAM;QACnC,WAAU;kBAET,EAAG,mCAAmC,SAAS;QACzC,CAAA,EACT,kBAAC,UAAD;QACE,MAAK;QACL,SA7LO,YAAY;AAC1B,eAAQ,MAAM,EACnB;YAAY,GAAK;AACjB,cAAI;WACF,IAAM,IAAU,MAAM,EAAc,GAAO;YACzC,MAAM;YACN,WAAW;YACZ,CAAC;AAKF,WAJA,EAAc,GAAM,EACpB,EAAW,GAAG,EACd,EAAgB,SAAS,EACzB,MAAM,GAAc,EACpB,IAAsB,EAAQ,GAAG;mBAC1B,GAAK;AACZ,mBAAQ,MAAM,6BAA6B,EAAI;oBACvC;AACR,aAAY,GAAM;;;;QA8KR,UAAU,KAAY,CAAC,EAAQ,MAAM;QACrC,WAAU;kBAET,IACG,EAAG,qCAAqC,cAAc,GACtD,EAAG,mCAAmC,SAAS;QAC5C,CAAA,CACL;;MACF;;IACF,CAAA;GAEJ"}
|
|
@@ -334,7 +334,7 @@ function y() {
|
|
|
334
334
|
type: "button",
|
|
335
335
|
onClick: be,
|
|
336
336
|
disabled: R.length === 0 || fe,
|
|
337
|
-
className: "rounded-md border border-
|
|
337
|
+
className: "rounded-md border border-[var(--color-accent-soft)] px-3 py-2 text-sm font-medium text-action-primary-bg hover:bg-[var(--color-accent-soft)] disabled:cursor-not-allowed disabled:opacity-50",
|
|
338
338
|
children: [y("vibecontrols.ai.tasks.moveSelected", "Move Selected"), R.length > 0 ? ` (${R.length})` : ""]
|
|
339
339
|
}),
|
|
340
340
|
/* @__PURE__ */ p("button", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AITasksTab.js","names":[],"sources":["../../../src/components/ai/AITasksTab.tsx"],"sourcesContent":["/**\n * AI Tasks Tab — Kanban board backed by PlanMagnet's WorkItem model\n */\n\nimport { useState, useCallback, useEffect, useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useApolloClient } from '@apollo/client/react';\nimport { Plus, X, GripVertical, RefreshCw, AlertCircle } from 'lucide-react';\nimport {\n Card,\n EmptyState,\n LoadingState,\n ErrorState,\n ConfirmDialog,\n SelectionCheckbox,\n} from '@/components/shared';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\nimport { getProfileScopedKey } from '@burdenoff/fe-libs/shared/utils';\nimport { useTr } from '../../shared/hooks/useTr';\n\nconst AI_TASK_LABEL = 'ai-task';\nconst AI_PROJECT_KEY = 'AI';\nconst AI_PROJECT_STORAGE_KEY = 'vibecontrols:ai-project-id';\n\ntype TaskStatus = 'BACKLOG' | 'TODO' | 'IN_PROGRESS' | 'IN_REVIEW' | 'DONE';\ntype TaskPriority = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';\n\ninterface WorkItem {\n id: string;\n key: string;\n title: string;\n description?: string | null;\n type: string;\n status: TaskStatus;\n priority: TaskPriority;\n assigneeId?: string | null;\n storyPoints?: number | null;\n dueDate?: string | null;\n labels: string[];\n tags: string[];\n metadata?: Record<string, unknown> | null;\n createdBy: string;\n createdAt: string;\n updatedAt: string;\n}\n\nconst columns: { id: TaskStatus; label: string; color: string }[] = [\n { id: 'BACKLOG', label: 'Backlog', color: 'border-t-text-secondary' },\n { id: 'TODO', label: 'To Do', color: 'border-t-action-primary-bg' },\n { id: 'IN_PROGRESS', label: 'In Progress', color: 'border-t-status-warning-text' },\n { id: 'IN_REVIEW', label: 'In Review', color: 'border-t-action-primary-bg' },\n { id: 'DONE', label: 'Done', color: 'border-t-status-success-text' },\n];\n\nconst priorityLabels: Record<string, { label: string; color: string }> = {\n CRITICAL: { label: 'Critical', color: 'text-status-error-text' },\n HIGH: { label: 'High', color: 'text-status-warning-text' },\n MEDIUM: { label: 'Medium', color: 'text-action-primary-bg' },\n LOW: { label: 'Low', color: 'text-text-secondary' },\n};\n\nconst WORKITEM_FIELDS = `id key title description type status priority assigneeId storyPoints dueDate labels tags metadata createdBy createdAt updatedAt`;\n\nexport function AITasksTab() {\n const tr = useTr();\n const apolloClient = useApolloClient();\n const { workspaceId: ctxWorkspaceId } = useVibeControls();\n // Fall back to URL param if context doesn't have workspaceId\n const workspaceId =\n ctxWorkspaceId || new URLSearchParams(window.location.search).get('workspace') || '';\n\n const [projectId, setProjectId] = useState<string | null>(() => {\n try {\n return localStorage.getItem(getProfileScopedKey(AI_PROJECT_STORAGE_KEY));\n } catch {\n return null;\n }\n });\n const [items, setItems] = useState<WorkItem[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [selectedTaskIds, setSelectedTaskIds] = useState<Set<string>>(new Set());\n const [bulkMoveStatus, setBulkMoveStatus] = useState<TaskStatus>('TODO');\n const [bulkDeleting, setBulkDeleting] = useState(false);\n const [bulkMoving, setBulkMoving] = useState(false);\n const [showBulkDeleteDialog, setShowBulkDeleteDialog] = useState(false);\n\n const gqlFetch = useCallback(\n async <T,>(query: string, variables?: Record<string, unknown>): Promise<T> => {\n const document = gql(query);\n const trimmed = query.trimStart();\n\n if (trimmed.startsWith('mutation')) {\n const result = await apolloClient.mutate<T>({ mutation: document, variables });\n if (result.error) throw new Error(result.error.message || 'Mutation failed');\n if (!result.data) throw new Error('Mutation returned no data');\n return result.data;\n }\n\n const result = await apolloClient.query<T>({\n query: document,\n variables,\n fetchPolicy: 'network-only',\n });\n if (result.error) throw new Error(result.error.message || 'Query failed');\n if (!result.data) throw new Error('Query returned no data');\n return result.data;\n },\n [apolloClient]\n );\n\n // ── Project auto-discovery ─────────────────────────────────────────\n useEffect(() => {\n if (projectId || !workspaceId) return;\n let cancelled = false;\n (async () => {\n try {\n const data = await gqlFetch<{\n planmagnetProjects: { items: { id: string; key: string }[] };\n }>(`query($wsId: ID!) { planmagnetProjects(workspaceId: $wsId) { items { id key } } }`, {\n wsId: workspaceId,\n });\n const existing = data.planmagnetProjects.items.find((p) => p.key === AI_PROJECT_KEY);\n if (existing && !cancelled) {\n setProjectId(existing.id);\n try {\n localStorage.setItem(getProfileScopedKey(AI_PROJECT_STORAGE_KEY), existing.id);\n } catch {\n // localStorage unavailable\n }\n return;\n }\n const createData = await gqlFetch<{ planmagnetCreateProject: { id: string } }>(\n `mutation($wsId: ID!, $input: PlanmagnetCreateProjectInput!) { planmagnetCreateProject(workspaceId: $wsId, input: $input) { id } }`,\n {\n wsId: workspaceId,\n input: {\n name: 'AI Tasks',\n key: AI_PROJECT_KEY,\n ownerId: 'system',\n methodology: 'KANBAN',\n tags: [AI_TASK_LABEL],\n },\n }\n );\n if (createData.planmagnetCreateProject.id && !cancelled) {\n setProjectId(createData.planmagnetCreateProject.id);\n try {\n localStorage.setItem(\n getProfileScopedKey(AI_PROJECT_STORAGE_KEY),\n createData.planmagnetCreateProject.id\n );\n } catch {\n // localStorage unavailable\n }\n }\n } catch (err) {\n console.error('Failed to find/create AI project:', err);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [workspaceId, projectId, apolloClient]);\n\n // ── Fetch tasks ────────────────────────────────────────────────────\n const fetchTasks = useCallback(async () => {\n if (!workspaceId) return;\n setLoading(true);\n setError(null);\n try {\n const data = await gqlFetch<{\n planmagnetWorkItems: { items: WorkItem[]; totalCount: number };\n }>(\n `query($wsId: ID!, $filter: PlanmagnetWorkItemFilter, $pagination: PlanmagnetPaginationInput) {\n planmagnetWorkItems(workspaceId: $wsId, filter: $filter, pagination: $pagination) {\n items { ${WORKITEM_FIELDS} }\n totalCount\n }\n }`,\n { wsId: workspaceId, filter: { type: 'TASK' }, pagination: { first: 200 } }\n );\n const all = data.planmagnetWorkItems.items ?? [];\n setItems(\n all.filter((i) => i.labels?.includes(AI_TASK_LABEL) || i.tags?.includes(AI_TASK_LABEL))\n );\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load tasks');\n } finally {\n setLoading(false);\n }\n }, [workspaceId, apolloClient]);\n\n useEffect(() => {\n fetchTasks();\n }, [fetchTasks]);\n\n const selectedTasks = useMemo(\n () => items.filter((item) => selectedTaskIds.has(item.id)),\n [items, selectedTaskIds]\n );\n const allVisibleSelected =\n items.length > 0 && items.every((item) => selectedTaskIds.has(item.id));\n const partiallySelected =\n items.some((item) => selectedTaskIds.has(item.id)) && !allVisibleSelected;\n\n // ── Create ─────────────────────────────────────────────────────────\n const [showCreate, setShowCreate] = useState(false);\n const [newTitle, setNewTitle] = useState('');\n const [newDescription, setNewDescription] = useState('');\n const [newStatus, setNewStatus] = useState<TaskStatus>('BACKLOG');\n const [newPriority, setNewPriority] = useState<TaskPriority>('MEDIUM');\n const [creating, setCreating] = useState(false);\n\n const handleCreate = async () => {\n if (!newTitle.trim() || !workspaceId || !projectId) return;\n setCreating(true);\n try {\n await gqlFetch(\n `mutation($wsId: ID!, $input: PlanmagnetCreateWorkItemInput!) { planmagnetCreateWorkItem(workspaceId: $wsId, input: $input) { id } }`,\n {\n wsId: workspaceId,\n input: {\n title: newTitle.trim(),\n description: newDescription.trim() || undefined,\n type: 'TASK',\n status: newStatus,\n priority: newPriority,\n labels: [AI_TASK_LABEL],\n projectId,\n },\n }\n );\n setNewTitle('');\n setNewDescription('');\n setNewStatus('BACKLOG');\n setNewPriority('MEDIUM');\n setShowCreate(false);\n fetchTasks();\n } catch (err) {\n console.error('Failed to create task:', err);\n } finally {\n setCreating(false);\n }\n };\n\n // ── Move (drag-drop status change) ─────────────────────────────────\n const [draggedId, setDraggedId] = useState<string | null>(null);\n\n const handleStatusChange = useCallback(\n async (taskId: string, targetStatus: TaskStatus) => {\n // Optimistic update\n setItems((prev) => prev.map((i) => (i.id === taskId ? { ...i, status: targetStatus } : i)));\n try {\n await gqlFetch(\n 'mutation($id: ID!, $input: PlanmagnetUpdateWorkItemInput!) { planmagnetUpdateWorkItem(id: $id, input: $input) { id } }',\n { id: taskId, input: { status: targetStatus } }\n );\n } catch (err) {\n console.error('Failed to move task:', err);\n fetchTasks(); // revert on failure\n }\n },\n [fetchTasks, gqlFetch]\n );\n\n const handleDrop = useCallback(\n (targetStatus: TaskStatus) => {\n if (!draggedId) return;\n const item = items.find((i) => i.id === draggedId);\n setDraggedId(null);\n if (!item || item.status === targetStatus) return;\n handleStatusChange(draggedId, targetStatus);\n },\n [draggedId, items, handleStatusChange]\n );\n\n // ── Delete ─────────────────────────────────────────────────────────\n const handleDelete = useCallback(\n async (id: string) => {\n setItems((prev) => prev.filter((i) => i.id !== id)); // optimistic\n try {\n await gqlFetch('mutation($id: ID!) { planmagnetDeleteWorkItem(id: $id) }', { id });\n } catch (err) {\n console.error('Failed to delete task:', err);\n fetchTasks(); // revert\n }\n },\n [fetchTasks, gqlFetch]\n );\n\n const handleBulkDelete = useCallback(async () => {\n if (selectedTasks.length === 0) return;\n setBulkDeleting(true);\n try {\n await Promise.allSettled(\n selectedTasks.map((task) =>\n gqlFetch('mutation($id: ID!) { planmagnetDeleteWorkItem(id: $id) }', { id: task.id })\n )\n );\n setSelectedTaskIds(new Set());\n setShowBulkDeleteDialog(false);\n await fetchTasks();\n } catch (err) {\n console.error('Failed to bulk delete tasks:', err);\n } finally {\n setBulkDeleting(false);\n }\n }, [fetchTasks, gqlFetch, selectedTasks]);\n\n const handleBulkMove = useCallback(async () => {\n if (selectedTasks.length === 0) return;\n setBulkMoving(true);\n try {\n setItems((prev) =>\n prev.map((item) =>\n selectedTaskIds.has(item.id) ? { ...item, status: bulkMoveStatus } : item\n )\n );\n await Promise.allSettled(\n selectedTasks.map((task) =>\n gqlFetch(\n 'mutation($id: ID!, $input: PlanmagnetUpdateWorkItemInput!) { planmagnetUpdateWorkItem(id: $id, input: $input) { id } }',\n { id: task.id, input: { status: bulkMoveStatus } }\n )\n )\n );\n setSelectedTaskIds(new Set());\n await fetchTasks();\n } catch (err) {\n console.error('Failed to bulk move tasks:', err);\n fetchTasks();\n } finally {\n setBulkMoving(false);\n }\n }, [bulkMoveStatus, fetchTasks, gqlFetch, selectedTaskIds, selectedTasks]);\n\n const toggleTaskSelection = useCallback((taskId: string, checked: boolean) => {\n setSelectedTaskIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(taskId);\n } else {\n next.delete(taskId);\n }\n return next;\n });\n }, []);\n\n const toggleAllVisibleTasks = useCallback(\n (checked: boolean) => {\n setSelectedTaskIds((prev) => {\n const next = new Set(prev);\n items.forEach((item) => {\n if (checked) {\n next.add(item.id);\n } else {\n next.delete(item.id);\n }\n });\n return next;\n });\n },\n [items]\n );\n\n const clearSelection = () => setSelectedTaskIds(new Set());\n\n // ── Render ─────────────────────────────────────────────────────────\n if (!workspaceId) {\n return (\n <EmptyState\n icon={<AlertCircle className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.ai.tasks.noWorkspace', 'No workspace selected')}\n description={tr(\n 'vibecontrols.ai.tasks.noWorkspaceDesc',\n 'Select a workspace to manage AI tasks'\n )}\n />\n );\n }\n\n if (loading && items.length === 0) {\n return (\n <LoadingState\n message={tr('vibecontrols.ai.tasks.loading', 'Loading AI tasks from PlanMagnet...')}\n />\n );\n }\n\n if (error && items.length === 0) {\n return (\n <ErrorState\n title={tr('vibecontrols.ai.tasks.loadFailed', 'Failed to load tasks')}\n message={error}\n action={\n <button\n type=\"button\"\n onClick={fetchTasks}\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 text-sm\"\n >\n <RefreshCw className=\"size-4\" />\n {tr('vibecontrols.ai.tasks.retry', 'Retry')}\n </button>\n }\n />\n );\n }\n\n return (\n <div className=\"space-y-4\">\n {/* Toolbar */}\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-2\">\n <p className=\"text-sm text-text-secondary\">\n {items.length} task{items.length !== 1 ? 's' : ''}\n </p>\n <button\n type=\"button\"\n onClick={fetchTasks}\n className=\"p-1 hover:bg-bg-sunken rounded-md text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Refresh\"\n >\n <RefreshCw className=\"size-3.5\" />\n </button>\n </div>\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"flex w-full 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 sm:w-auto\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.ai.tasks.createTask', 'Create Task')}\n </button>\n </div>\n\n {items.length > 0 && (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-4\">\n <div className=\"flex flex-col gap-3 xl:flex-row xl:items-center xl: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={toggleAllVisibleTasks}\n ariaLabel=\"Select all visible AI tasks\"\n />\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('vibecontrols.ai.tasks.selectAllVisible', 'Select all visible')}\n </span>\n </div>\n {selectedTaskIds.size > 0 && (\n <button\n type=\"button\"\n onClick={clearSelection}\n className=\"text-sm text-text-secondary hover:text-text-primary\"\n >\n {tr('vibecontrols.ai.tasks.clearSelection', '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 {selectedTaskIds.size} {tr('vibecontrols.ai.tasks.selected', 'selected')}\n </span>\n <select\n value={bulkMoveStatus}\n onChange={(event) => setBulkMoveStatus(event.target.value as TaskStatus)}\n className=\"rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n {columns.map((column) => (\n <option key={column.id} value={column.id}>\n Move to {column.label}\n </option>\n ))}\n </select>\n <button\n type=\"button\"\n onClick={handleBulkMove}\n disabled={selectedTasks.length === 0 || bulkMoving}\n className=\"rounded-md border border-action-primary-bg/40 px-3 py-2 text-sm font-medium text-action-primary-bg hover:bg-action-primary-bg/10 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {tr('vibecontrols.ai.tasks.moveSelected', 'Move Selected')}\n {selectedTasks.length > 0 ? ` (${selectedTasks.length})` : ''}\n </button>\n <button\n type=\"button\"\n onClick={() => setShowBulkDeleteDialog(true)}\n disabled={selectedTasks.length === 0 || bulkDeleting}\n className=\"rounded-md border border-status-error-border 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 {tr('vibecontrols.ai.tasks.deleteSelected', 'Delete Selected')}\n {selectedTasks.length > 0 ? ` (${selectedTasks.length})` : ''}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Kanban Board */}\n {items.length === 0 ? (\n <EmptyState\n icon={<GripVertical className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.ai.tasks.emptyTitle', 'No AI tasks')}\n description={tr(\n 'vibecontrols.ai.tasks.emptyDescription',\n 'Create tasks to track and organize your AI vibecoding work. Tasks are stored in PlanMagnet.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\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('vibecontrols.ai.tasks.createTask', 'Create Task')}\n </button>\n }\n />\n ) : (\n <div className=\"flex gap-3 overflow-x-auto pb-2\">\n {columns.map((col) => {\n const colItems = items.filter((i) => i.status === col.id);\n return (\n <div\n key={col.id}\n className={`flex-shrink-0 w-56 bg-bg-sunken rounded-lg border-t-2 ${col.color}`}\n onDragOver={(e) => e.preventDefault()}\n onDrop={(e) => {\n e.preventDefault();\n handleDrop(col.id);\n }}\n >\n <div className=\"px-3 py-2 flex items-center justify-between\">\n <h4 className=\"text-xs font-semibold text-text-primary uppercase tracking-wide\">\n {col.label}\n </h4>\n <span className=\"text-xs text-text-secondary bg-bg-surface rounded-full px-1.5 py-0.5\">\n {colItems.length}\n </span>\n </div>\n <div className=\"px-2 pb-2 space-y-2 min-h-[100px]\">\n {colItems.map((item) => (\n <Card\n key={item.id}\n className=\"p-0 cursor-grab active:cursor-grabbing\"\n padding=\"none\"\n >\n <div draggable onDragStart={() => setDraggedId(item.id)} className=\"p-2\">\n <div className=\"flex items-start justify-between gap-1\">\n <div className=\"flex items-start gap-2 min-w-0 flex-1\">\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selectedTaskIds.has(item.id)}\n onChange={(checked) => toggleTaskSelection(item.id, checked)}\n ariaLabel={`Select task ${item.title}`}\n />\n </div>\n <div className=\"min-w-0 flex-1\">\n {item.key && (\n <span className=\"text-[10px] font-mono text-text-secondary\">\n {item.key}\n </span>\n )}\n <span className=\"block text-sm font-medium text-text-primary line-clamp-2\">\n {item.title}\n </span>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleDelete(item.id);\n }}\n className=\"p-0.5 hover:bg-status-error-bg rounded flex-shrink-0\"\n >\n <X className=\"size-3 text-text-secondary hover:text-status-error-text\" />\n </button>\n </div>\n <div className=\"flex items-center gap-1.5 mt-1.5\">\n {item.priority && priorityLabels[item.priority] && (\n <span\n className={`text-[10px] font-medium ${priorityLabels[item.priority].color}`}\n >\n {priorityLabels[item.priority].label}\n </span>\n )}\n {item.storyPoints != null && (\n <span className=\"text-[10px] text-text-secondary bg-bg-surface rounded px-1\">\n {item.storyPoints}pt\n </span>\n )}\n </div>\n </div>\n </Card>\n ))}\n </div>\n </div>\n );\n })}\n </div>\n )}\n\n {/* Create Task Dialog */}\n {showCreate && (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => setShowCreate(false))();\n }\n }}\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\"\n onClick={() => setShowCreate(false)}\n >\n <div\n className=\"bg-bg-surface border border-border-default rounded-lg shadow-xl w-full max-w-sm p-6\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {tr('vibecontrols.ai.tasks.newAITask', 'New AI Task')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setShowCreate(false)}\n className=\"p-1 hover:bg-bg-sunken rounded-md\"\n >\n <X className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.ai.tasks.title', 'Title')}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={newTitle}\n onChange={(e) => setNewTitle(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm focus:outline-none focus:border-border-strong\"\n placeholder={tr('vibecontrols.ai.tasks.titlePlaceholder', 'Task title')}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && !e.shiftKey) handleCreate();\n }}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.ai.tasks.description', 'Description')}\n </label>\n <textarea\n aria-label=\"control\"\n value={newDescription}\n onChange={(e) => setNewDescription(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm focus:outline-none focus:border-border-strong resize-none\"\n placeholder={tr(\n 'vibecontrols.ai.tasks.descriptionPlaceholder',\n 'Optional description'\n )}\n rows={3}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label\n htmlFor=\"aitaskstab-7\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Status\n </label>\n <select\n id=\"aitaskstab-7\"\n value={newStatus}\n onChange={(e) => setNewStatus(e.target.value as TaskStatus)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm\"\n >\n {columns.map((c) => (\n <option key={c.id} value={c.id}>\n {c.label}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label\n htmlFor=\"aitaskstab-6\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Priority\n </label>\n <select\n id=\"aitaskstab-6\"\n value={newPriority}\n onChange={(e) => setNewPriority(e.target.value as TaskPriority)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm\"\n >\n <option value=\"CRITICAL\">Critical</option>\n <option value=\"HIGH\">High</option>\n <option value=\"MEDIUM\">Medium</option>\n <option value=\"LOW\">Low</option>\n </select>\n </div>\n </div>\n </div>\n <div className=\"flex justify-end gap-2 mt-6\">\n <button\n type=\"button\"\n onClick={() => setShowCreate(false)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleCreate}\n disabled={!newTitle.trim() || creating || !projectId}\n className=\"px-4 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {creating ? 'Creating...' : 'Create'}\n </button>\n </div>\n </div>\n </div>\n )}\n\n <ConfirmDialog\n isOpen={showBulkDeleteDialog}\n onClose={() => setShowBulkDeleteDialog(false)}\n onConfirm={handleBulkDelete}\n title=\"Delete AI Tasks\"\n message={`Delete ${selectedTasks.length} selected AI task${selectedTasks.length === 1 ? '' : 's'}? This cannot be undone.`}\n confirmLabel={bulkDeleting ? 'Deleting...' : 'Delete'}\n variant=\"danger\"\n isLoading={bulkDeleting}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AITasksTab.js","names":[],"sources":["../../../src/components/ai/AITasksTab.tsx"],"sourcesContent":["/**\n * AI Tasks Tab — Kanban board backed by PlanMagnet's WorkItem model\n */\n\nimport { useState, useCallback, useEffect, useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useApolloClient } from '@apollo/client/react';\nimport { Plus, X, GripVertical, RefreshCw, AlertCircle } from 'lucide-react';\nimport {\n Card,\n EmptyState,\n LoadingState,\n ErrorState,\n ConfirmDialog,\n SelectionCheckbox,\n} from '@/components/shared';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\nimport { getProfileScopedKey } from '@burdenoff/fe-libs/shared/utils';\nimport { useTr } from '../../shared/hooks/useTr';\n\nconst AI_TASK_LABEL = 'ai-task';\nconst AI_PROJECT_KEY = 'AI';\nconst AI_PROJECT_STORAGE_KEY = 'vibecontrols:ai-project-id';\n\ntype TaskStatus = 'BACKLOG' | 'TODO' | 'IN_PROGRESS' | 'IN_REVIEW' | 'DONE';\ntype TaskPriority = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';\n\ninterface WorkItem {\n id: string;\n key: string;\n title: string;\n description?: string | null;\n type: string;\n status: TaskStatus;\n priority: TaskPriority;\n assigneeId?: string | null;\n storyPoints?: number | null;\n dueDate?: string | null;\n labels: string[];\n tags: string[];\n metadata?: Record<string, unknown> | null;\n createdBy: string;\n createdAt: string;\n updatedAt: string;\n}\n\nconst columns: { id: TaskStatus; label: string; color: string }[] = [\n { id: 'BACKLOG', label: 'Backlog', color: 'border-t-text-secondary' },\n { id: 'TODO', label: 'To Do', color: 'border-t-action-primary-bg' },\n { id: 'IN_PROGRESS', label: 'In Progress', color: 'border-t-status-warning-text' },\n { id: 'IN_REVIEW', label: 'In Review', color: 'border-t-action-primary-bg' },\n { id: 'DONE', label: 'Done', color: 'border-t-status-success-text' },\n];\n\nconst priorityLabels: Record<string, { label: string; color: string }> = {\n CRITICAL: { label: 'Critical', color: 'text-status-error-text' },\n HIGH: { label: 'High', color: 'text-status-warning-text' },\n MEDIUM: { label: 'Medium', color: 'text-action-primary-bg' },\n LOW: { label: 'Low', color: 'text-text-secondary' },\n};\n\nconst WORKITEM_FIELDS = `id key title description type status priority assigneeId storyPoints dueDate labels tags metadata createdBy createdAt updatedAt`;\n\nexport function AITasksTab() {\n const tr = useTr();\n const apolloClient = useApolloClient();\n const { workspaceId: ctxWorkspaceId } = useVibeControls();\n // Fall back to URL param if context doesn't have workspaceId\n const workspaceId =\n ctxWorkspaceId || new URLSearchParams(window.location.search).get('workspace') || '';\n\n const [projectId, setProjectId] = useState<string | null>(() => {\n try {\n return localStorage.getItem(getProfileScopedKey(AI_PROJECT_STORAGE_KEY));\n } catch {\n return null;\n }\n });\n const [items, setItems] = useState<WorkItem[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [selectedTaskIds, setSelectedTaskIds] = useState<Set<string>>(new Set());\n const [bulkMoveStatus, setBulkMoveStatus] = useState<TaskStatus>('TODO');\n const [bulkDeleting, setBulkDeleting] = useState(false);\n const [bulkMoving, setBulkMoving] = useState(false);\n const [showBulkDeleteDialog, setShowBulkDeleteDialog] = useState(false);\n\n const gqlFetch = useCallback(\n async <T,>(query: string, variables?: Record<string, unknown>): Promise<T> => {\n const document = gql(query);\n const trimmed = query.trimStart();\n\n if (trimmed.startsWith('mutation')) {\n const result = await apolloClient.mutate<T>({ mutation: document, variables });\n if (result.error) throw new Error(result.error.message || 'Mutation failed');\n if (!result.data) throw new Error('Mutation returned no data');\n return result.data;\n }\n\n const result = await apolloClient.query<T>({\n query: document,\n variables,\n fetchPolicy: 'network-only',\n });\n if (result.error) throw new Error(result.error.message || 'Query failed');\n if (!result.data) throw new Error('Query returned no data');\n return result.data;\n },\n [apolloClient]\n );\n\n // ── Project auto-discovery ─────────────────────────────────────────\n useEffect(() => {\n if (projectId || !workspaceId) return;\n let cancelled = false;\n (async () => {\n try {\n const data = await gqlFetch<{\n planmagnetProjects: { items: { id: string; key: string }[] };\n }>(`query($wsId: ID!) { planmagnetProjects(workspaceId: $wsId) { items { id key } } }`, {\n wsId: workspaceId,\n });\n const existing = data.planmagnetProjects.items.find((p) => p.key === AI_PROJECT_KEY);\n if (existing && !cancelled) {\n setProjectId(existing.id);\n try {\n localStorage.setItem(getProfileScopedKey(AI_PROJECT_STORAGE_KEY), existing.id);\n } catch {\n // localStorage unavailable\n }\n return;\n }\n const createData = await gqlFetch<{ planmagnetCreateProject: { id: string } }>(\n `mutation($wsId: ID!, $input: PlanmagnetCreateProjectInput!) { planmagnetCreateProject(workspaceId: $wsId, input: $input) { id } }`,\n {\n wsId: workspaceId,\n input: {\n name: 'AI Tasks',\n key: AI_PROJECT_KEY,\n ownerId: 'system',\n methodology: 'KANBAN',\n tags: [AI_TASK_LABEL],\n },\n }\n );\n if (createData.planmagnetCreateProject.id && !cancelled) {\n setProjectId(createData.planmagnetCreateProject.id);\n try {\n localStorage.setItem(\n getProfileScopedKey(AI_PROJECT_STORAGE_KEY),\n createData.planmagnetCreateProject.id\n );\n } catch {\n // localStorage unavailable\n }\n }\n } catch (err) {\n console.error('Failed to find/create AI project:', err);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [workspaceId, projectId, apolloClient]);\n\n // ── Fetch tasks ────────────────────────────────────────────────────\n const fetchTasks = useCallback(async () => {\n if (!workspaceId) return;\n setLoading(true);\n setError(null);\n try {\n const data = await gqlFetch<{\n planmagnetWorkItems: { items: WorkItem[]; totalCount: number };\n }>(\n `query($wsId: ID!, $filter: PlanmagnetWorkItemFilter, $pagination: PlanmagnetPaginationInput) {\n planmagnetWorkItems(workspaceId: $wsId, filter: $filter, pagination: $pagination) {\n items { ${WORKITEM_FIELDS} }\n totalCount\n }\n }`,\n { wsId: workspaceId, filter: { type: 'TASK' }, pagination: { first: 200 } }\n );\n const all = data.planmagnetWorkItems.items ?? [];\n setItems(\n all.filter((i) => i.labels?.includes(AI_TASK_LABEL) || i.tags?.includes(AI_TASK_LABEL))\n );\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load tasks');\n } finally {\n setLoading(false);\n }\n }, [workspaceId, apolloClient]);\n\n useEffect(() => {\n fetchTasks();\n }, [fetchTasks]);\n\n const selectedTasks = useMemo(\n () => items.filter((item) => selectedTaskIds.has(item.id)),\n [items, selectedTaskIds]\n );\n const allVisibleSelected =\n items.length > 0 && items.every((item) => selectedTaskIds.has(item.id));\n const partiallySelected =\n items.some((item) => selectedTaskIds.has(item.id)) && !allVisibleSelected;\n\n // ── Create ─────────────────────────────────────────────────────────\n const [showCreate, setShowCreate] = useState(false);\n const [newTitle, setNewTitle] = useState('');\n const [newDescription, setNewDescription] = useState('');\n const [newStatus, setNewStatus] = useState<TaskStatus>('BACKLOG');\n const [newPriority, setNewPriority] = useState<TaskPriority>('MEDIUM');\n const [creating, setCreating] = useState(false);\n\n const handleCreate = async () => {\n if (!newTitle.trim() || !workspaceId || !projectId) return;\n setCreating(true);\n try {\n await gqlFetch(\n `mutation($wsId: ID!, $input: PlanmagnetCreateWorkItemInput!) { planmagnetCreateWorkItem(workspaceId: $wsId, input: $input) { id } }`,\n {\n wsId: workspaceId,\n input: {\n title: newTitle.trim(),\n description: newDescription.trim() || undefined,\n type: 'TASK',\n status: newStatus,\n priority: newPriority,\n labels: [AI_TASK_LABEL],\n projectId,\n },\n }\n );\n setNewTitle('');\n setNewDescription('');\n setNewStatus('BACKLOG');\n setNewPriority('MEDIUM');\n setShowCreate(false);\n fetchTasks();\n } catch (err) {\n console.error('Failed to create task:', err);\n } finally {\n setCreating(false);\n }\n };\n\n // ── Move (drag-drop status change) ─────────────────────────────────\n const [draggedId, setDraggedId] = useState<string | null>(null);\n\n const handleStatusChange = useCallback(\n async (taskId: string, targetStatus: TaskStatus) => {\n // Optimistic update\n setItems((prev) => prev.map((i) => (i.id === taskId ? { ...i, status: targetStatus } : i)));\n try {\n await gqlFetch(\n 'mutation($id: ID!, $input: PlanmagnetUpdateWorkItemInput!) { planmagnetUpdateWorkItem(id: $id, input: $input) { id } }',\n { id: taskId, input: { status: targetStatus } }\n );\n } catch (err) {\n console.error('Failed to move task:', err);\n fetchTasks(); // revert on failure\n }\n },\n [fetchTasks, gqlFetch]\n );\n\n const handleDrop = useCallback(\n (targetStatus: TaskStatus) => {\n if (!draggedId) return;\n const item = items.find((i) => i.id === draggedId);\n setDraggedId(null);\n if (!item || item.status === targetStatus) return;\n handleStatusChange(draggedId, targetStatus);\n },\n [draggedId, items, handleStatusChange]\n );\n\n // ── Delete ─────────────────────────────────────────────────────────\n const handleDelete = useCallback(\n async (id: string) => {\n setItems((prev) => prev.filter((i) => i.id !== id)); // optimistic\n try {\n await gqlFetch('mutation($id: ID!) { planmagnetDeleteWorkItem(id: $id) }', { id });\n } catch (err) {\n console.error('Failed to delete task:', err);\n fetchTasks(); // revert\n }\n },\n [fetchTasks, gqlFetch]\n );\n\n const handleBulkDelete = useCallback(async () => {\n if (selectedTasks.length === 0) return;\n setBulkDeleting(true);\n try {\n await Promise.allSettled(\n selectedTasks.map((task) =>\n gqlFetch('mutation($id: ID!) { planmagnetDeleteWorkItem(id: $id) }', { id: task.id })\n )\n );\n setSelectedTaskIds(new Set());\n setShowBulkDeleteDialog(false);\n await fetchTasks();\n } catch (err) {\n console.error('Failed to bulk delete tasks:', err);\n } finally {\n setBulkDeleting(false);\n }\n }, [fetchTasks, gqlFetch, selectedTasks]);\n\n const handleBulkMove = useCallback(async () => {\n if (selectedTasks.length === 0) return;\n setBulkMoving(true);\n try {\n setItems((prev) =>\n prev.map((item) =>\n selectedTaskIds.has(item.id) ? { ...item, status: bulkMoveStatus } : item\n )\n );\n await Promise.allSettled(\n selectedTasks.map((task) =>\n gqlFetch(\n 'mutation($id: ID!, $input: PlanmagnetUpdateWorkItemInput!) { planmagnetUpdateWorkItem(id: $id, input: $input) { id } }',\n { id: task.id, input: { status: bulkMoveStatus } }\n )\n )\n );\n setSelectedTaskIds(new Set());\n await fetchTasks();\n } catch (err) {\n console.error('Failed to bulk move tasks:', err);\n fetchTasks();\n } finally {\n setBulkMoving(false);\n }\n }, [bulkMoveStatus, fetchTasks, gqlFetch, selectedTaskIds, selectedTasks]);\n\n const toggleTaskSelection = useCallback((taskId: string, checked: boolean) => {\n setSelectedTaskIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(taskId);\n } else {\n next.delete(taskId);\n }\n return next;\n });\n }, []);\n\n const toggleAllVisibleTasks = useCallback(\n (checked: boolean) => {\n setSelectedTaskIds((prev) => {\n const next = new Set(prev);\n items.forEach((item) => {\n if (checked) {\n next.add(item.id);\n } else {\n next.delete(item.id);\n }\n });\n return next;\n });\n },\n [items]\n );\n\n const clearSelection = () => setSelectedTaskIds(new Set());\n\n // ── Render ─────────────────────────────────────────────────────────\n if (!workspaceId) {\n return (\n <EmptyState\n icon={<AlertCircle className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.ai.tasks.noWorkspace', 'No workspace selected')}\n description={tr(\n 'vibecontrols.ai.tasks.noWorkspaceDesc',\n 'Select a workspace to manage AI tasks'\n )}\n />\n );\n }\n\n if (loading && items.length === 0) {\n return (\n <LoadingState\n message={tr('vibecontrols.ai.tasks.loading', 'Loading AI tasks from PlanMagnet...')}\n />\n );\n }\n\n if (error && items.length === 0) {\n return (\n <ErrorState\n title={tr('vibecontrols.ai.tasks.loadFailed', 'Failed to load tasks')}\n message={error}\n action={\n <button\n type=\"button\"\n onClick={fetchTasks}\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 text-sm\"\n >\n <RefreshCw className=\"size-4\" />\n {tr('vibecontrols.ai.tasks.retry', 'Retry')}\n </button>\n }\n />\n );\n }\n\n return (\n <div className=\"space-y-4\">\n {/* Toolbar */}\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-2\">\n <p className=\"text-sm text-text-secondary\">\n {items.length} task{items.length !== 1 ? 's' : ''}\n </p>\n <button\n type=\"button\"\n onClick={fetchTasks}\n className=\"p-1 hover:bg-bg-sunken rounded-md text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Refresh\"\n >\n <RefreshCw className=\"size-3.5\" />\n </button>\n </div>\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"flex w-full 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 sm:w-auto\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.ai.tasks.createTask', 'Create Task')}\n </button>\n </div>\n\n {items.length > 0 && (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-4\">\n <div className=\"flex flex-col gap-3 xl:flex-row xl:items-center xl: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={toggleAllVisibleTasks}\n ariaLabel=\"Select all visible AI tasks\"\n />\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('vibecontrols.ai.tasks.selectAllVisible', 'Select all visible')}\n </span>\n </div>\n {selectedTaskIds.size > 0 && (\n <button\n type=\"button\"\n onClick={clearSelection}\n className=\"text-sm text-text-secondary hover:text-text-primary\"\n >\n {tr('vibecontrols.ai.tasks.clearSelection', '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 {selectedTaskIds.size} {tr('vibecontrols.ai.tasks.selected', 'selected')}\n </span>\n <select\n value={bulkMoveStatus}\n onChange={(event) => setBulkMoveStatus(event.target.value as TaskStatus)}\n className=\"rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n {columns.map((column) => (\n <option key={column.id} value={column.id}>\n Move to {column.label}\n </option>\n ))}\n </select>\n <button\n type=\"button\"\n onClick={handleBulkMove}\n disabled={selectedTasks.length === 0 || bulkMoving}\n className=\"rounded-md border border-[var(--color-accent-soft)] px-3 py-2 text-sm font-medium text-action-primary-bg hover:bg-[var(--color-accent-soft)] disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {tr('vibecontrols.ai.tasks.moveSelected', 'Move Selected')}\n {selectedTasks.length > 0 ? ` (${selectedTasks.length})` : ''}\n </button>\n <button\n type=\"button\"\n onClick={() => setShowBulkDeleteDialog(true)}\n disabled={selectedTasks.length === 0 || bulkDeleting}\n className=\"rounded-md border border-status-error-border 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 {tr('vibecontrols.ai.tasks.deleteSelected', 'Delete Selected')}\n {selectedTasks.length > 0 ? ` (${selectedTasks.length})` : ''}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Kanban Board */}\n {items.length === 0 ? (\n <EmptyState\n icon={<GripVertical className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.ai.tasks.emptyTitle', 'No AI tasks')}\n description={tr(\n 'vibecontrols.ai.tasks.emptyDescription',\n 'Create tasks to track and organize your AI vibecoding work. Tasks are stored in PlanMagnet.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\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('vibecontrols.ai.tasks.createTask', 'Create Task')}\n </button>\n }\n />\n ) : (\n <div className=\"flex gap-3 overflow-x-auto pb-2\">\n {columns.map((col) => {\n const colItems = items.filter((i) => i.status === col.id);\n return (\n <div\n key={col.id}\n className={`flex-shrink-0 w-56 bg-bg-sunken rounded-lg border-t-2 ${col.color}`}\n onDragOver={(e) => e.preventDefault()}\n onDrop={(e) => {\n e.preventDefault();\n handleDrop(col.id);\n }}\n >\n <div className=\"px-3 py-2 flex items-center justify-between\">\n <h4 className=\"text-xs font-semibold text-text-primary uppercase tracking-wide\">\n {col.label}\n </h4>\n <span className=\"text-xs text-text-secondary bg-bg-surface rounded-full px-1.5 py-0.5\">\n {colItems.length}\n </span>\n </div>\n <div className=\"px-2 pb-2 space-y-2 min-h-[100px]\">\n {colItems.map((item) => (\n <Card\n key={item.id}\n className=\"p-0 cursor-grab active:cursor-grabbing\"\n padding=\"none\"\n >\n <div draggable onDragStart={() => setDraggedId(item.id)} className=\"p-2\">\n <div className=\"flex items-start justify-between gap-1\">\n <div className=\"flex items-start gap-2 min-w-0 flex-1\">\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selectedTaskIds.has(item.id)}\n onChange={(checked) => toggleTaskSelection(item.id, checked)}\n ariaLabel={`Select task ${item.title}`}\n />\n </div>\n <div className=\"min-w-0 flex-1\">\n {item.key && (\n <span className=\"text-[10px] font-mono text-text-secondary\">\n {item.key}\n </span>\n )}\n <span className=\"block text-sm font-medium text-text-primary line-clamp-2\">\n {item.title}\n </span>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleDelete(item.id);\n }}\n className=\"p-0.5 hover:bg-status-error-bg rounded flex-shrink-0\"\n >\n <X className=\"size-3 text-text-secondary hover:text-status-error-text\" />\n </button>\n </div>\n <div className=\"flex items-center gap-1.5 mt-1.5\">\n {item.priority && priorityLabels[item.priority] && (\n <span\n className={`text-[10px] font-medium ${priorityLabels[item.priority].color}`}\n >\n {priorityLabels[item.priority].label}\n </span>\n )}\n {item.storyPoints != null && (\n <span className=\"text-[10px] text-text-secondary bg-bg-surface rounded px-1\">\n {item.storyPoints}pt\n </span>\n )}\n </div>\n </div>\n </Card>\n ))}\n </div>\n </div>\n );\n })}\n </div>\n )}\n\n {/* Create Task Dialog */}\n {showCreate && (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => setShowCreate(false))();\n }\n }}\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\"\n onClick={() => setShowCreate(false)}\n >\n <div\n className=\"bg-bg-surface border border-border-default rounded-lg shadow-xl w-full max-w-sm p-6\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {tr('vibecontrols.ai.tasks.newAITask', 'New AI Task')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setShowCreate(false)}\n className=\"p-1 hover:bg-bg-sunken rounded-md\"\n >\n <X className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.ai.tasks.title', 'Title')}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={newTitle}\n onChange={(e) => setNewTitle(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm focus:outline-none focus:border-border-strong\"\n placeholder={tr('vibecontrols.ai.tasks.titlePlaceholder', 'Task title')}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && !e.shiftKey) handleCreate();\n }}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.ai.tasks.description', 'Description')}\n </label>\n <textarea\n aria-label=\"control\"\n value={newDescription}\n onChange={(e) => setNewDescription(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm focus:outline-none focus:border-border-strong resize-none\"\n placeholder={tr(\n 'vibecontrols.ai.tasks.descriptionPlaceholder',\n 'Optional description'\n )}\n rows={3}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label\n htmlFor=\"aitaskstab-7\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Status\n </label>\n <select\n id=\"aitaskstab-7\"\n value={newStatus}\n onChange={(e) => setNewStatus(e.target.value as TaskStatus)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm\"\n >\n {columns.map((c) => (\n <option key={c.id} value={c.id}>\n {c.label}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label\n htmlFor=\"aitaskstab-6\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Priority\n </label>\n <select\n id=\"aitaskstab-6\"\n value={newPriority}\n onChange={(e) => setNewPriority(e.target.value as TaskPriority)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm\"\n >\n <option value=\"CRITICAL\">Critical</option>\n <option value=\"HIGH\">High</option>\n <option value=\"MEDIUM\">Medium</option>\n <option value=\"LOW\">Low</option>\n </select>\n </div>\n </div>\n </div>\n <div className=\"flex justify-end gap-2 mt-6\">\n <button\n type=\"button\"\n onClick={() => setShowCreate(false)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleCreate}\n disabled={!newTitle.trim() || creating || !projectId}\n className=\"px-4 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {creating ? 'Creating...' : 'Create'}\n </button>\n </div>\n </div>\n </div>\n )}\n\n <ConfirmDialog\n isOpen={showBulkDeleteDialog}\n onClose={() => setShowBulkDeleteDialog(false)}\n onConfirm={handleBulkDelete}\n title=\"Delete AI Tasks\"\n message={`Delete ${selectedTasks.length} selected AI task${selectedTasks.length === 1 ? '' : 's'}? This cannot be undone.`}\n confirmLabel={bulkDeleting ? 'Deleting...' : 'Delete'}\n variant=\"danger\"\n isLoading={bulkDeleting}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -49,11 +49,11 @@ function h({ agent: s }) {
|
|
|
49
49
|
})]
|
|
50
50
|
}),
|
|
51
51
|
S ? /* @__PURE__ */ p("div", {
|
|
52
|
-
className: "rounded-lg border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text",
|
|
52
|
+
className: "rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
53
53
|
children: S
|
|
54
54
|
}) : null,
|
|
55
55
|
w ? /* @__PURE__ */ p("div", {
|
|
56
|
-
className: "rounded-lg border border-status-success-border bg-status-success-bg px-3 py-2 text-sm text-status-success-text",
|
|
56
|
+
className: "rounded-lg border border-status-success-border bg-status-success-bg-subtle px-3 py-2 text-sm text-status-success-text",
|
|
57
57
|
children: w
|
|
58
58
|
}) : null,
|
|
59
59
|
v ? /* @__PURE__ */ m("div", {
|
|
@@ -86,7 +86,7 @@ function h({ agent: s }) {
|
|
|
86
86
|
}
|
|
87
87
|
function g({ label: e, value: t, onChange: n, saving: r, visible: i, onToggleVisible: a, onSave: o, onClear: l }) {
|
|
88
88
|
return /* @__PURE__ */ m("div", {
|
|
89
|
-
className: "space-y-2 rounded-lg border border-border-default bg-bg-
|
|
89
|
+
className: "space-y-2 rounded-lg border border-border-default bg-bg-surface p-3",
|
|
90
90
|
children: [/* @__PURE__ */ p("label", {
|
|
91
91
|
className: "block text-xs font-medium text-text-secondary",
|
|
92
92
|
children: e
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClaudeCredentialsCard.js","names":[],"sources":["../../../src/components/ai/ClaudeCredentialsCard.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport { Eye, EyeOff, KeyRound, Loader2, Save, Trash2 } from 'lucide-react';\n\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n deleteAgentConfigValue,\n getAgentConfigValue,\n setAgentConfigValue,\n type AgentRef,\n} from '@/services/aiApi';\n\ninterface ClaudeCredentialsCardProps {\n agent: AgentRef;\n}\n\nexport function ClaudeCredentialsCard({ agent }: ClaudeCredentialsCardProps) {\n const tr = useTr();\n const [apiKey, setApiKey] = useState('');\n const [authToken, setAuthToken] = useState('');\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState<'apiKey' | 'authToken' | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState<string | null>(null);\n const [showApiKey, setShowApiKey] = useState(false);\n const [showAuthToken, setShowAuthToken] = useState(false);\n\n const loadValues = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const [nextApiKey, nextAuthToken] = await Promise.all([\n getAgentConfigValue(agent, 'ANTHROPIC_API_KEY'),\n getAgentConfigValue(agent, 'ANTHROPIC_AUTH_TOKEN'),\n ]);\n setApiKey(nextApiKey ?? '');\n setAuthToken(nextAuthToken ?? '');\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.claudeCredentials.loadFailed', 'Failed to load Claude credentials')\n );\n } finally {\n setLoading(false);\n }\n }, [agent, tr]);\n\n useEffect(() => {\n void loadValues();\n }, [loadValues]);\n\n const persistValue = useCallback(\n async (key: 'ANTHROPIC_API_KEY' | 'ANTHROPIC_AUTH_TOKEN', value: string) => {\n setSaving(key === 'ANTHROPIC_API_KEY' ? 'apiKey' : 'authToken');\n setError(null);\n setSuccess(null);\n try {\n if (value.trim()) {\n await setAgentConfigValue(agent, key, value.trim());\n } else {\n await deleteAgentConfigValue(agent, key);\n }\n setSuccess(\n value.trim()\n ? tr('ai.claudeCredentials.saved', 'Credential saved')\n : tr('ai.claudeCredentials.cleared', 'Credential cleared')\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.claudeCredentials.saveFailed', 'Failed to save Claude credential')\n );\n } finally {\n setSaving(null);\n }\n },\n [agent, tr]\n );\n\n return (\n <div className=\"rounded-xl border border-border-default bg-bg-surface p-4 space-y-4\">\n <div className=\"flex items-start gap-3\">\n <div className=\"rounded-lg bg-bg-sunken p-2 text-text-secondary\">\n <KeyRound className=\"size-4\" />\n </div>\n <div className=\"space-y-1\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('ai.claudeCredentials.title', 'Claude SDK credentials')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'ai.claudeCredentials.description',\n 'Claude SDK sessions require either ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN on the agent. Save one of them here to make SDK sessions work.'\n )}\n </p>\n </div>\n </div>\n\n {error ? (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n\n {success ? (\n <div className=\"rounded-lg border border-status-success-border bg-status-success-bg px-3 py-2 text-sm text-status-success-text\">\n {success}\n </div>\n ) : null}\n\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"size-4 animate-spin\" />\n {tr('ai.claudeCredentials.loading', 'Loading Claude credential settings...')}\n </div>\n ) : (\n <div className=\"space-y-4\">\n <CredentialField\n label=\"ANTHROPIC_API_KEY\"\n value={apiKey}\n onChange={setApiKey}\n saving={saving === 'apiKey'}\n visible={showApiKey}\n onToggleVisible={() => setShowApiKey((current) => !current)}\n onSave={() => void persistValue('ANTHROPIC_API_KEY', apiKey)}\n onClear={() => void persistValue('ANTHROPIC_API_KEY', '')}\n />\n <CredentialField\n label=\"ANTHROPIC_AUTH_TOKEN\"\n value={authToken}\n onChange={setAuthToken}\n saving={saving === 'authToken'}\n visible={showAuthToken}\n onToggleVisible={() => setShowAuthToken((current) => !current)}\n onSave={() => void persistValue('ANTHROPIC_AUTH_TOKEN', authToken)}\n onClear={() => void persistValue('ANTHROPIC_AUTH_TOKEN', '')}\n />\n </div>\n )}\n </div>\n );\n}\n\ninterface CredentialFieldProps {\n label: string;\n value: string;\n onChange: (value: string) => void;\n saving: boolean;\n visible: boolean;\n onToggleVisible: () => void;\n onSave: () => void;\n onClear: () => void;\n}\n\nfunction CredentialField({\n label,\n value,\n onChange,\n saving,\n visible,\n onToggleVisible,\n onSave,\n onClear,\n}: CredentialFieldProps) {\n return (\n <div className=\"space-y-2 rounded-lg border border-border-default bg-bg-primary p-3\">\n <label className=\"block text-xs font-medium text-text-secondary\">{label}</label>\n <div className=\"flex gap-2\">\n <div className=\"relative flex-1\">\n <input\n aria-label=\"control\"\n type={visible ? 'text' : 'password'}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 pr-10 text-sm text-text-primary\"\n />\n <button\n type=\"button\"\n onClick={onToggleVisible}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 text-text-secondary hover:text-text-primary\"\n >\n {visible ? <EyeOff className=\"size-4\" /> : <Eye className=\"size-4\" />}\n </button>\n </div>\n <button\n type=\"button\"\n onClick={onSave}\n disabled={saving}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {saving ? <Loader2 className=\"size-4 animate-spin\" /> : <Save className=\"size-4\" />}\n Save\n </button>\n <button\n type=\"button\"\n onClick={onClear}\n disabled={saving || !value}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-default px-3 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:border-border-strong disabled:opacity-50\"\n >\n <Trash2 className=\"size-4\" />\n Clear\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ClaudeCredentialsCard.js","names":[],"sources":["../../../src/components/ai/ClaudeCredentialsCard.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport { Eye, EyeOff, KeyRound, Loader2, Save, Trash2 } from 'lucide-react';\n\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n deleteAgentConfigValue,\n getAgentConfigValue,\n setAgentConfigValue,\n type AgentRef,\n} from '@/services/aiApi';\n\ninterface ClaudeCredentialsCardProps {\n agent: AgentRef;\n}\n\nexport function ClaudeCredentialsCard({ agent }: ClaudeCredentialsCardProps) {\n const tr = useTr();\n const [apiKey, setApiKey] = useState('');\n const [authToken, setAuthToken] = useState('');\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState<'apiKey' | 'authToken' | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState<string | null>(null);\n const [showApiKey, setShowApiKey] = useState(false);\n const [showAuthToken, setShowAuthToken] = useState(false);\n\n const loadValues = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const [nextApiKey, nextAuthToken] = await Promise.all([\n getAgentConfigValue(agent, 'ANTHROPIC_API_KEY'),\n getAgentConfigValue(agent, 'ANTHROPIC_AUTH_TOKEN'),\n ]);\n setApiKey(nextApiKey ?? '');\n setAuthToken(nextAuthToken ?? '');\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.claudeCredentials.loadFailed', 'Failed to load Claude credentials')\n );\n } finally {\n setLoading(false);\n }\n }, [agent, tr]);\n\n useEffect(() => {\n void loadValues();\n }, [loadValues]);\n\n const persistValue = useCallback(\n async (key: 'ANTHROPIC_API_KEY' | 'ANTHROPIC_AUTH_TOKEN', value: string) => {\n setSaving(key === 'ANTHROPIC_API_KEY' ? 'apiKey' : 'authToken');\n setError(null);\n setSuccess(null);\n try {\n if (value.trim()) {\n await setAgentConfigValue(agent, key, value.trim());\n } else {\n await deleteAgentConfigValue(agent, key);\n }\n setSuccess(\n value.trim()\n ? tr('ai.claudeCredentials.saved', 'Credential saved')\n : tr('ai.claudeCredentials.cleared', 'Credential cleared')\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.claudeCredentials.saveFailed', 'Failed to save Claude credential')\n );\n } finally {\n setSaving(null);\n }\n },\n [agent, tr]\n );\n\n return (\n <div className=\"rounded-xl border border-border-default bg-bg-surface p-4 space-y-4\">\n <div className=\"flex items-start gap-3\">\n <div className=\"rounded-lg bg-bg-sunken p-2 text-text-secondary\">\n <KeyRound className=\"size-4\" />\n </div>\n <div className=\"space-y-1\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('ai.claudeCredentials.title', 'Claude SDK credentials')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'ai.claudeCredentials.description',\n 'Claude SDK sessions require either ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN on the agent. Save one of them here to make SDK sessions work.'\n )}\n </p>\n </div>\n </div>\n\n {error ? (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n\n {success ? (\n <div className=\"rounded-lg border border-status-success-border bg-status-success-bg-subtle px-3 py-2 text-sm text-status-success-text\">\n {success}\n </div>\n ) : null}\n\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"size-4 animate-spin\" />\n {tr('ai.claudeCredentials.loading', 'Loading Claude credential settings...')}\n </div>\n ) : (\n <div className=\"space-y-4\">\n <CredentialField\n label=\"ANTHROPIC_API_KEY\"\n value={apiKey}\n onChange={setApiKey}\n saving={saving === 'apiKey'}\n visible={showApiKey}\n onToggleVisible={() => setShowApiKey((current) => !current)}\n onSave={() => void persistValue('ANTHROPIC_API_KEY', apiKey)}\n onClear={() => void persistValue('ANTHROPIC_API_KEY', '')}\n />\n <CredentialField\n label=\"ANTHROPIC_AUTH_TOKEN\"\n value={authToken}\n onChange={setAuthToken}\n saving={saving === 'authToken'}\n visible={showAuthToken}\n onToggleVisible={() => setShowAuthToken((current) => !current)}\n onSave={() => void persistValue('ANTHROPIC_AUTH_TOKEN', authToken)}\n onClear={() => void persistValue('ANTHROPIC_AUTH_TOKEN', '')}\n />\n </div>\n )}\n </div>\n );\n}\n\ninterface CredentialFieldProps {\n label: string;\n value: string;\n onChange: (value: string) => void;\n saving: boolean;\n visible: boolean;\n onToggleVisible: () => void;\n onSave: () => void;\n onClear: () => void;\n}\n\nfunction CredentialField({\n label,\n value,\n onChange,\n saving,\n visible,\n onToggleVisible,\n onSave,\n onClear,\n}: CredentialFieldProps) {\n return (\n <div className=\"space-y-2 rounded-lg border border-border-default bg-bg-surface p-3\">\n <label className=\"block text-xs font-medium text-text-secondary\">{label}</label>\n <div className=\"flex gap-2\">\n <div className=\"relative flex-1\">\n <input\n aria-label=\"control\"\n type={visible ? 'text' : 'password'}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 pr-10 text-sm text-text-primary\"\n />\n <button\n type=\"button\"\n onClick={onToggleVisible}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 text-text-secondary hover:text-text-primary\"\n >\n {visible ? <EyeOff className=\"size-4\" /> : <Eye className=\"size-4\" />}\n </button>\n </div>\n <button\n type=\"button\"\n onClick={onSave}\n disabled={saving}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {saving ? <Loader2 className=\"size-4 animate-spin\" /> : <Save className=\"size-4\" />}\n Save\n </button>\n <button\n type=\"button\"\n onClick={onClear}\n disabled={saving || !value}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-default px-3 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:border-border-strong disabled:opacity-50\"\n >\n <Trash2 className=\"size-4\" />\n Clear\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -79,7 +79,7 @@ function _({ packages: _, agentId: v, title: y, description: b, onChange: x, ren
|
|
|
79
79
|
})] }),
|
|
80
80
|
O && /* @__PURE__ */ h("div", {
|
|
81
81
|
role: "alert",
|
|
82
|
-
className: "rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text",
|
|
82
|
+
className: "rounded-md border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text",
|
|
83
83
|
children: O
|
|
84
84
|
}),
|
|
85
85
|
/* @__PURE__ */ h("div", {
|
|
@@ -104,7 +104,7 @@ function _({ packages: _, agentId: v, title: y, description: b, onChange: x, ren
|
|
|
104
104
|
className: "font-medium text-text-primary break-words max-w-[100ch]",
|
|
105
105
|
children: n.displayName
|
|
106
106
|
}), i && /* @__PURE__ */ g("span", {
|
|
107
|
-
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs rounded bg-status-success-bg text-status-success-text shrink-0",
|
|
107
|
+
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs rounded bg-status-success-bg-subtle text-status-success-text shrink-0",
|
|
108
108
|
children: [/* @__PURE__ */ h(c, { className: "size-3" }), " installed"]
|
|
109
109
|
})]
|
|
110
110
|
}),
|
|
@@ -126,7 +126,7 @@ function _({ packages: _, agentId: v, title: y, description: b, onChange: x, ren
|
|
|
126
126
|
i && _ ? /* @__PURE__ */ g("button", {
|
|
127
127
|
type: "button",
|
|
128
128
|
disabled: !0,
|
|
129
|
-
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-status-success-bg text-status-success-text border border-status-success-border cursor-default",
|
|
129
|
+
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-status-success-bg-subtle text-status-success-text border border-status-success-border cursor-default",
|
|
130
130
|
"data-testid": `plugin-installed-${e}`,
|
|
131
131
|
"aria-live": "polite",
|
|
132
132
|
children: [/* @__PURE__ */ h(l, { className: "size-3.5" }), "Installed"]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginHarnessPicker.js","names":[],"sources":["../../../src/components/ai/PluginHarnessPicker.tsx"],"sourcesContent":["/**\n * <PluginHarnessPicker />\n *\n * Grid of installable VibeControls agent plugins with per-card\n * Install / Installed / Remove state. Driven by the existing\n * `usePluginAvailability` hook (same code path as `<PluginGate>`), so\n * the Plugins tab on the Agent details page and this picker stay in\n * sync automatically.\n *\n * The default use-case is the AI workbench: once the base\n * `@vibecontrols/vibe-plugin-ai` plugin is installed, this picker lists\n * every AI provider harness (claude, codex, copilot, gemini, …) so the\n * user can add / remove harnesses without leaving the AI page.\n *\n * The component is feature-neutral — pass any `packages` array (e.g.\n * session providers, tunnel providers) to reuse the same UI.\n */\n\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';\nimport { Check, CheckCircle2, Download, Loader2, Puzzle, Trash2 } from 'lucide-react';\nimport { useRemoveAgentPluginMutation } from '@/generated/wspace-operations';\nimport { usePluginAvailability } from '@/hooks/usePluginAvailability';\nimport { getPluginMeta } from '@/constants/pluginCatalog';\nimport { Card } from '../shared/Card';\n\nexport interface PluginHarnessPickerProps {\n /** Package names to offer. Metadata is resolved via `getPluginMeta`. */\n packages: readonly string[];\n /** Active agent id. When null, the picker renders in a disabled state. */\n agentId: string | null;\n /** Optional section heading (rendered above the grid). */\n title?: string;\n /** Optional section description. */\n description?: string;\n /** Fired after a successful install or remove so parents can refetch. */\n onChange?: () => void;\n /**\n * Optional per-card action slot, rendered alongside the Remove button when\n * a package is installed. Lets a feature (e.g. the Security tab) attach a\n * contextual action — like \"Run scan\" — without coupling this generic\n * picker to that feature. Receives the installed package name.\n */\n renderInstalledAction?: (pkg: string) => ReactNode;\n}\n\nexport function PluginHarnessPicker({\n packages,\n agentId,\n title,\n description,\n onChange,\n renderInstalledAction,\n}: PluginHarnessPickerProps) {\n // Plugins are agent-wide (BOFF-2612); no profile threading needed.\n const { installed, installing, install, refetch, loading, error } = usePluginAvailability(\n packages,\n agentId\n );\n const [removePlugin] = useRemoveAgentPluginMutation();\n const [removingPkg, setRemovingPkg] = useState<string | null>(null);\n const [removeError, setRemoveError] = useState<Record<string, string>>({});\n // Per-package \"just installed\" pulse — flips on for ~1.5s after a\n // successful install so the button shows a clear success affordance\n // (checkmark + \"Installed\") before the card transitions to its\n // installed/Remove state. Without this the button silently flips from\n // spinner to Remove and users miss the success signal.\n const [justInstalled, setJustInstalled] = useState<Record<string, boolean>>({});\n const justInstalledTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());\n\n useEffect(() => {\n const timers = justInstalledTimers.current;\n return () => {\n for (const t of timers.values()) clearTimeout(t);\n timers.clear();\n };\n }, []);\n\n const handleInstall = useCallback(\n async (pkg: string) => {\n const ok = await install(pkg);\n if (ok) {\n setJustInstalled((prev) => ({ ...prev, [pkg]: true }));\n const existing = justInstalledTimers.current.get(pkg);\n if (existing) clearTimeout(existing);\n const timer = setTimeout(() => {\n setJustInstalled((prev) => {\n if (!prev[pkg]) return prev;\n const next = { ...prev };\n delete next[pkg];\n return next;\n });\n justInstalledTimers.current.delete(pkg);\n }, 1500);\n justInstalledTimers.current.set(pkg, timer);\n onChange?.();\n }\n },\n [install, onChange]\n );\n\n const handleRemove = useCallback(\n async (pkg: string) => {\n if (!agentId) return;\n setRemovingPkg(pkg);\n setRemoveError((e) => ({ ...e, [pkg]: '' }));\n try {\n const { data } = await removePlugin({\n variables: { agentId, packageName: pkg },\n });\n if (!data?.removeAgentPlugin.success) {\n setRemoveError((e) => ({\n ...e,\n [pkg]: data?.removeAgentPlugin.error || 'Remove failed',\n }));\n return;\n }\n await refetch();\n onChange?.();\n } catch (e) {\n setRemoveError((err) => ({\n ...err,\n [pkg]: e instanceof Error ? e.message : String(e),\n }));\n } finally {\n setRemovingPkg(null);\n }\n },\n [agentId, removePlugin, refetch, onChange]\n );\n\n if (packages.length === 0) return null;\n\n return (\n <section className=\"flex flex-col gap-3\" data-testid=\"plugin-harness-picker\">\n {(title || description) && (\n <div>\n {title && <h3 className=\"text-base font-semibold text-text-primary\">{title}</h3>}\n {description && <p className=\"text-sm text-text-secondary mt-1\">{description}</p>}\n </div>\n )}\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text\"\n >\n {error}\n </div>\n )}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {packages.map((pkg) => {\n const meta = getPluginMeta(pkg);\n const isInstalled = !!installed[pkg];\n const isInstalling = installing === pkg;\n const isRemoving = removingPkg === pkg;\n const isBusy = isInstalling || isRemoving || loading;\n const showJustInstalled = !!justInstalled[pkg];\n const err = removeError[pkg];\n return (\n <Card key={pkg} padding=\"md\" className=\"flex flex-col gap-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-shrink-0 size-9 rounded-md bg-bg-sunken flex items-center justify-center\">\n <Puzzle className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-start gap-2 flex-wrap\">\n {/*\n Plugin display name must wrap freely up to a\n reasonable hard cap so the column never blows up\n the modal layout. break-words allows wrapping on\n arbitrary characters when a name has no spaces;\n max-w bound corresponds to ~100 char-cells (a\n generous upper bound on real-world display names).\n */}\n <h4 className=\"font-medium text-text-primary break-words max-w-[100ch]\">\n {meta.displayName}\n </h4>\n {isInstalled && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs rounded bg-status-success-bg text-status-success-text shrink-0\">\n <Check className=\"size-3\" /> installed\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mt-0.5\">{meta.description}</p>\n <p\n className=\"text-[10px] font-mono text-text-placeholder mt-1 break-all\"\n title={pkg}\n >\n {pkg}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {isInstalled && showJustInstalled ? (\n <button\n type=\"button\"\n disabled\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-status-success-bg text-status-success-text border border-status-success-border cursor-default\"\n data-testid={`plugin-installed-${pkg}`}\n aria-live=\"polite\"\n >\n <CheckCircle2 className=\"size-3.5\" />\n Installed\n </button>\n ) : isInstalled ? (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleRemove(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md border border-border-default text-text-secondary hover:border-status-error-border hover:text-status-error-text transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-remove-${pkg}`}\n >\n {isRemoving ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"size-3.5\" />\n )}\n Remove\n </button>\n ) : null}\n {isInstalled && !showJustInstalled && renderInstalledAction?.(pkg)}\n {!isInstalled && (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleInstall(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-install-${pkg}`}\n aria-busy={isInstalling}\n >\n {isInstalling ? (\n <>\n <Loader2 className=\"size-3.5 animate-spin\" />\n Installing…\n </>\n ) : (\n <>\n <Download className=\"size-3.5\" />\n Install\n </>\n )}\n </button>\n )}\n </div>\n {err && <p className=\"text-xs text-status-error-text\">{err}</p>}\n </Card>\n );\n })}\n </div>\n {!agentId && <p className=\"text-xs text-text-muted\">Select an agent to manage plugins.</p>}\n </section>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PluginHarnessPicker.js","names":[],"sources":["../../../src/components/ai/PluginHarnessPicker.tsx"],"sourcesContent":["/**\n * <PluginHarnessPicker />\n *\n * Grid of installable VibeControls agent plugins with per-card\n * Install / Installed / Remove state. Driven by the existing\n * `usePluginAvailability` hook (same code path as `<PluginGate>`), so\n * the Plugins tab on the Agent details page and this picker stay in\n * sync automatically.\n *\n * The default use-case is the AI workbench: once the base\n * `@vibecontrols/vibe-plugin-ai` plugin is installed, this picker lists\n * every AI provider harness (claude, codex, copilot, gemini, …) so the\n * user can add / remove harnesses without leaving the AI page.\n *\n * The component is feature-neutral — pass any `packages` array (e.g.\n * session providers, tunnel providers) to reuse the same UI.\n */\n\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';\nimport { Check, CheckCircle2, Download, Loader2, Puzzle, Trash2 } from 'lucide-react';\nimport { useRemoveAgentPluginMutation } from '@/generated/wspace-operations';\nimport { usePluginAvailability } from '@/hooks/usePluginAvailability';\nimport { getPluginMeta } from '@/constants/pluginCatalog';\nimport { Card } from '../shared/Card';\n\nexport interface PluginHarnessPickerProps {\n /** Package names to offer. Metadata is resolved via `getPluginMeta`. */\n packages: readonly string[];\n /** Active agent id. When null, the picker renders in a disabled state. */\n agentId: string | null;\n /** Optional section heading (rendered above the grid). */\n title?: string;\n /** Optional section description. */\n description?: string;\n /** Fired after a successful install or remove so parents can refetch. */\n onChange?: () => void;\n /**\n * Optional per-card action slot, rendered alongside the Remove button when\n * a package is installed. Lets a feature (e.g. the Security tab) attach a\n * contextual action — like \"Run scan\" — without coupling this generic\n * picker to that feature. Receives the installed package name.\n */\n renderInstalledAction?: (pkg: string) => ReactNode;\n}\n\nexport function PluginHarnessPicker({\n packages,\n agentId,\n title,\n description,\n onChange,\n renderInstalledAction,\n}: PluginHarnessPickerProps) {\n // Plugins are agent-wide (BOFF-2612); no profile threading needed.\n const { installed, installing, install, refetch, loading, error } = usePluginAvailability(\n packages,\n agentId\n );\n const [removePlugin] = useRemoveAgentPluginMutation();\n const [removingPkg, setRemovingPkg] = useState<string | null>(null);\n const [removeError, setRemoveError] = useState<Record<string, string>>({});\n // Per-package \"just installed\" pulse — flips on for ~1.5s after a\n // successful install so the button shows a clear success affordance\n // (checkmark + \"Installed\") before the card transitions to its\n // installed/Remove state. Without this the button silently flips from\n // spinner to Remove and users miss the success signal.\n const [justInstalled, setJustInstalled] = useState<Record<string, boolean>>({});\n const justInstalledTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());\n\n useEffect(() => {\n const timers = justInstalledTimers.current;\n return () => {\n for (const t of timers.values()) clearTimeout(t);\n timers.clear();\n };\n }, []);\n\n const handleInstall = useCallback(\n async (pkg: string) => {\n const ok = await install(pkg);\n if (ok) {\n setJustInstalled((prev) => ({ ...prev, [pkg]: true }));\n const existing = justInstalledTimers.current.get(pkg);\n if (existing) clearTimeout(existing);\n const timer = setTimeout(() => {\n setJustInstalled((prev) => {\n if (!prev[pkg]) return prev;\n const next = { ...prev };\n delete next[pkg];\n return next;\n });\n justInstalledTimers.current.delete(pkg);\n }, 1500);\n justInstalledTimers.current.set(pkg, timer);\n onChange?.();\n }\n },\n [install, onChange]\n );\n\n const handleRemove = useCallback(\n async (pkg: string) => {\n if (!agentId) return;\n setRemovingPkg(pkg);\n setRemoveError((e) => ({ ...e, [pkg]: '' }));\n try {\n const { data } = await removePlugin({\n variables: { agentId, packageName: pkg },\n });\n if (!data?.removeAgentPlugin.success) {\n setRemoveError((e) => ({\n ...e,\n [pkg]: data?.removeAgentPlugin.error || 'Remove failed',\n }));\n return;\n }\n await refetch();\n onChange?.();\n } catch (e) {\n setRemoveError((err) => ({\n ...err,\n [pkg]: e instanceof Error ? e.message : String(e),\n }));\n } finally {\n setRemovingPkg(null);\n }\n },\n [agentId, removePlugin, refetch, onChange]\n );\n\n if (packages.length === 0) return null;\n\n return (\n <section className=\"flex flex-col gap-3\" data-testid=\"plugin-harness-picker\">\n {(title || description) && (\n <div>\n {title && <h3 className=\"text-base font-semibold text-text-primary\">{title}</h3>}\n {description && <p className=\"text-sm text-text-secondary mt-1\">{description}</p>}\n </div>\n )}\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text\"\n >\n {error}\n </div>\n )}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {packages.map((pkg) => {\n const meta = getPluginMeta(pkg);\n const isInstalled = !!installed[pkg];\n const isInstalling = installing === pkg;\n const isRemoving = removingPkg === pkg;\n const isBusy = isInstalling || isRemoving || loading;\n const showJustInstalled = !!justInstalled[pkg];\n const err = removeError[pkg];\n return (\n <Card key={pkg} padding=\"md\" className=\"flex flex-col gap-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-shrink-0 size-9 rounded-md bg-bg-sunken flex items-center justify-center\">\n <Puzzle className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-start gap-2 flex-wrap\">\n {/*\n Plugin display name must wrap freely up to a\n reasonable hard cap so the column never blows up\n the modal layout. break-words allows wrapping on\n arbitrary characters when a name has no spaces;\n max-w bound corresponds to ~100 char-cells (a\n generous upper bound on real-world display names).\n */}\n <h4 className=\"font-medium text-text-primary break-words max-w-[100ch]\">\n {meta.displayName}\n </h4>\n {isInstalled && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs rounded bg-status-success-bg-subtle text-status-success-text shrink-0\">\n <Check className=\"size-3\" /> installed\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mt-0.5\">{meta.description}</p>\n <p\n className=\"text-[10px] font-mono text-text-placeholder mt-1 break-all\"\n title={pkg}\n >\n {pkg}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {isInstalled && showJustInstalled ? (\n <button\n type=\"button\"\n disabled\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-status-success-bg-subtle text-status-success-text border border-status-success-border cursor-default\"\n data-testid={`plugin-installed-${pkg}`}\n aria-live=\"polite\"\n >\n <CheckCircle2 className=\"size-3.5\" />\n Installed\n </button>\n ) : isInstalled ? (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleRemove(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md border border-border-default text-text-secondary hover:border-status-error-border hover:text-status-error-text transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-remove-${pkg}`}\n >\n {isRemoving ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"size-3.5\" />\n )}\n Remove\n </button>\n ) : null}\n {isInstalled && !showJustInstalled && renderInstalledAction?.(pkg)}\n {!isInstalled && (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleInstall(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-install-${pkg}`}\n aria-busy={isInstalling}\n >\n {isInstalling ? (\n <>\n <Loader2 className=\"size-3.5 animate-spin\" />\n Installing…\n </>\n ) : (\n <>\n <Download className=\"size-3.5\" />\n Install\n </>\n )}\n </button>\n )}\n </div>\n {err && <p className=\"text-xs text-status-error-text\">{err}</p>}\n </Card>\n );\n })}\n </div>\n {!agentId && <p className=\"text-xs text-text-muted\">Select an agent to manage plugins.</p>}\n </section>\n );\n}\n"],"mappings":"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"}
|
|
@@ -9,14 +9,14 @@ import { useMemo as s } from "react";
|
|
|
9
9
|
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
10
10
|
//#region src/components/analytics/AnalyticsBreakdownChart.tsx
|
|
11
11
|
var u = [
|
|
12
|
-
"var(--color-
|
|
13
|
-
"var(--color-
|
|
14
|
-
"var(--color-
|
|
15
|
-
"var(--color-
|
|
16
|
-
"var(--color-
|
|
17
|
-
"var(--color-
|
|
18
|
-
"var(--color-
|
|
19
|
-
"var(--color-
|
|
12
|
+
"var(--color-chart-1)",
|
|
13
|
+
"var(--color-chart-2)",
|
|
14
|
+
"var(--color-chart-3)",
|
|
15
|
+
"var(--color-chart-4)",
|
|
16
|
+
"var(--color-chart-5)",
|
|
17
|
+
"var(--color-chart-6)",
|
|
18
|
+
"var(--color-chart-7)",
|
|
19
|
+
"var(--color-chart-8)"
|
|
20
20
|
];
|
|
21
21
|
function d({ title: d, data: f, height: p = 250 }) {
|
|
22
22
|
let m = s(() => !f || f.length === 0 ? [] : f.map((e) => ({
|