@burdenoff/microfe-vibecontrols 2026.623.1 → 2026.623.2
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/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 +23 -23
- 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 +3 -3
- 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 +6 -6
- 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 +74 -41
- package/dist/pages/OverviewPage.js.map +1 -1
- package/dist/pages/PluginsCatalogPage.js +6 -3
- 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":"AIPromptsTab.js","names":[],"sources":["../../../src/components/ai/AIPromptsTab.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport { Send, FileText, Wand2, Clock } from 'lucide-react';\nimport { Card, LoadingState, ErrorState, EmptyState } from '@/components/shared';\nimport type { AITemplate, AIContext, AISession, AIDispatch, AgentRef } from '@/services/aiApi';\nimport {\n fetchTemplates,\n fetchContexts,\n fetchSessions,\n fetchDispatchHistory,\n renderTemplate,\n recordTemplateUsage,\n sendDispatch,\n} from '@/services/aiApi';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AIPromptsTabProps {\n agent: AgentRef;\n}\n\nexport function AIPromptsTab({ agent }: AIPromptsTabProps) {\n const tr = useTr();\n const [promptContent, setPromptContent] = useState('');\n const [selectedContextIds, setSelectedContextIds] = useState<string[]>([]);\n const [targetSessionId, setTargetSessionId] = useState('');\n const [sending, setSending] = useState(false);\n const [successMsg, setSuccessMsg] = useState<string | null>(null);\n\n // Generate from template state\n const [showTemplateGen, setShowTemplateGen] = useState(false);\n const [selectedTemplateId, setSelectedTemplateId] = useState('');\n const [sourceTemplateId, setSourceTemplateId] = useState<string | null>(null);\n const [templateVars, setTemplateVars] = useState<Record<string, string>>({});\n const [rendering, setRendering] = useState(false);\n\n // Data\n const [templates, setTemplates] = useState<AITemplate[]>([]);\n const [contexts, setContexts] = useState<AIContext[]>([]);\n const [sessions, setSessions] = useState<AISession[]>([]);\n const [history, setHistory] = useState<AIDispatch[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const loadData = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const [t, c, s, h] = await Promise.all([\n fetchTemplates(agent.id),\n fetchContexts(agent),\n fetchSessions(agent),\n fetchDispatchHistory(agent, { limit: 20 }),\n ]);\n setTemplates(t);\n setContexts(c);\n setSessions(s);\n setHistory(h);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load data');\n } finally {\n setLoading(false);\n }\n }, [agent]);\n\n useEffect(() => {\n loadData();\n }, [loadData]);\n\n const handleTemplateSelect = (templateId: string) => {\n setSelectedTemplateId(templateId);\n const template = templates.find((t) => t.id === templateId);\n if (template?.variables) {\n const vars: Record<string, string> = {};\n for (const key of Object.keys(template.variables)) {\n vars[key] = String(template.variables[key] ?? '');\n }\n setTemplateVars(vars);\n } else {\n setTemplateVars({});\n }\n };\n\n const handleGenerateFromTemplate = async () => {\n if (!selectedTemplateId) return;\n setRendering(true);\n try {\n const result = await renderTemplate(agent.id, selectedTemplateId, templateVars);\n setPromptContent(result.rendered);\n setSourceTemplateId(selectedTemplateId);\n setShowTemplateGen(false);\n } catch (err) {\n console.error('Failed to render template:', err);\n } finally {\n setRendering(false);\n }\n };\n\n const toggleContext = (contextId: string) => {\n setSelectedContextIds((prev) =>\n prev.includes(contextId) ? prev.filter((id) => id !== contextId) : [...prev, contextId]\n );\n };\n\n const handleSendToSession = async () => {\n if (!promptContent.trim() || !targetSessionId) return;\n setSending(true);\n setSuccessMsg(null);\n try {\n await sendDispatch(agent, {\n prompt: promptContent,\n sessionId: targetSessionId,\n contextIds: selectedContextIds.length > 0 ? selectedContextIds : undefined,\n });\n if (sourceTemplateId) {\n await recordTemplateUsage(agent.id, sourceTemplateId);\n }\n setSuccessMsg('Prompt dispatched successfully');\n setPromptContent('');\n setSourceTemplateId(null);\n setSelectedContextIds([]);\n setTimeout(() => setSuccessMsg(null), 3000);\n // Refresh history\n const h = await fetchDispatchHistory(agent, { limit: 20 });\n setHistory(h);\n } catch (err) {\n console.error('Failed to dispatch prompt:', err);\n } finally {\n setSending(false);\n }\n };\n\n if (loading)\n return <LoadingState message={tr('vibecontrols.ai.prompts.loading', 'Loading prompts...')} />;\n if (error)\n return (\n <ErrorState\n title={tr('vibecontrols.ai.prompts.loadFailed', 'Failed to load data')}\n message={error}\n onRetry={loadData}\n />\n );\n\n return (\n <div className=\"space-y-6\">\n {successMsg && (\n <div className=\"bg-status-success-bg text-status-success-text px-4 py-2 rounded-md text-sm\">\n {successMsg}\n </div>\n )}\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Prompt Editor */}\n <div className=\"lg:col-span-2 space-y-4\">\n <Card className=\"p-4\">\n <div className=\"flex items-center justify-between mb-3\">\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibecontrols.ai.prompts.composePrompt', 'Compose Prompt')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setShowTemplateGen(!showTemplateGen)}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-action-primary-bg bg-action-primary-bg/10 rounded-md hover:bg-action-primary-bg/20 transition-colors\"\n >\n <Wand2 className=\"size-3.5\" />\n {tr('vibecontrols.ai.prompts.generateFromTemplate', 'Generate from Template')}\n </button>\n </div>\n\n {/* Template Generator */}\n {showTemplateGen && (\n <div className=\"mb-4 p-3 bg-bg-sunken rounded-md space-y-3\">\n <select\n value={selectedTemplateId}\n onChange={(e) => handleTemplateSelect(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-surface border border-border-default rounded-md text-text-primary text-sm\"\n >\n <option value=\"\">\n {tr('vibecontrols.ai.prompts.selectTemplate', 'Select a template...')}\n </option>\n {templates.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n {Object.keys(templateVars).length > 0 && (\n <div className=\"space-y-2\">\n {Object.entries(templateVars).map(([key, val]) => (\n <div key={key}>\n <label className=\"block text-xs font-medium text-text-secondary mb-0.5\">\n {key}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={val}\n onChange={(e) =>\n setTemplateVars((prev) => ({ ...prev, [key]: e.target.value }))\n }\n className=\"w-full px-2 py-1.5 bg-bg-surface border border-border-default rounded text-sm text-text-primary focus:outline-none focus:border-border-strong\"\n />\n </div>\n ))}\n </div>\n )}\n <button\n type=\"button\"\n onClick={handleGenerateFromTemplate}\n disabled={rendering || !selectedTemplateId}\n className=\"w-full px-3 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 {rendering\n ? tr('vibecontrols.ai.prompts.generating', 'Generating...')\n : tr('vibecontrols.ai.prompts.generate', 'Generate')}\n </button>\n </div>\n )}\n\n <textarea\n aria-label=\"control\"\n value={promptContent}\n onChange={(e) => {\n setPromptContent(e.target.value);\n setSourceTemplateId(null);\n }}\n rows={8}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm font-mono focus:outline-none focus:border-border-strong\"\n placeholder={tr(\n 'vibecontrols.ai.prompts.promptPlaceholder',\n 'Write your prompt here... (Markdown supported)'\n )}\n />\n\n {/* Session Target & Send */}\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-end gap-3 mt-3\">\n <div className=\"flex-1\">\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">\n {tr('vibecontrols.ai.prompts.targetSession', 'Target Session')}\n </label>\n <select\n value={targetSessionId}\n onChange={(e) => setTargetSessionId(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 <option value=\"\">\n {tr('vibecontrols.ai.prompts.selectSession', 'Select session...')}\n </option>\n {sessions\n .filter((s) => s.status !== 'terminated')\n .map((s) => (\n <option key={s.id} value={s.id}>\n {s.name} ({s.status})\n </option>\n ))}\n </select>\n </div>\n <button\n type=\"button\"\n onClick={handleSendToSession}\n disabled={sending || !promptContent.trim() || !targetSessionId}\n className=\"flex items-center justify-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 text-sm\"\n >\n <Send className=\"size-4\" />\n {sending\n ? tr('vibecontrols.ai.prompts.sending', 'Sending...')\n : tr('vibecontrols.ai.prompts.sendToSession', 'Send to Session')}\n </button>\n </div>\n </Card>\n </div>\n\n {/* Context Picker */}\n <div className=\"space-y-4\">\n <Card className=\"p-4\">\n <h3 className=\"font-semibold text-text-primary mb-3\">\n {tr('vibecontrols.ai.prompts.attachContexts', 'Attach Contexts')}\n </h3>\n {contexts.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('vibecontrols.ai.prompts.noContexts', 'No contexts available')}\n </p>\n ) : (\n <div className=\"space-y-2 max-h-64 overflow-y-auto\">\n {contexts.map((ctx) => (\n <label\n key={ctx.id}\n className=\"flex items-start gap-2 p-2 rounded hover:bg-bg-sunken cursor-pointer\"\n >\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selectedContextIds.includes(ctx.id)}\n onChange={() => toggleContext(ctx.id)}\n className=\"mt-0.5 rounded border-border-default\"\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"text-sm font-medium text-text-primary truncate\">\n {ctx.name}\n </div>\n <div className=\"text-xs text-text-secondary\">{ctx.type}</div>\n </div>\n </label>\n ))}\n </div>\n )}\n </Card>\n </div>\n </div>\n\n {/* Dispatch History */}\n <Card className=\"p-0\">\n <div className=\"px-4 py-3 border-b border-border-default flex items-center gap-2\">\n <Clock className=\"size-4 text-text-secondary\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibecontrols.ai.prompts.dispatchHistory', 'Dispatch History')}\n </h3>\n </div>\n <div className=\"divide-y divide-border-default\">\n {history.length === 0 ? (\n <EmptyState\n icon={<FileText className=\"size-6 text-text-secondary\" />}\n title={tr('vibecontrols.ai.prompts.noDispatched', 'No dispatched prompts')}\n description={tr(\n 'vibecontrols.ai.prompts.noDispatchedDesc',\n 'Send a prompt to a session to see history here'\n )}\n />\n ) : (\n history.map((d) => (\n <div key={d.id} className=\"px-4 py-3\">\n <div className=\"text-sm text-text-primary line-clamp-2\">{d.prompt}</div>\n <div className=\"mt-1 flex items-center gap-3 text-xs text-text-secondary\">\n <span className=\"font-medium\">{d.status}</span>\n <span>Session: {d.sessionId ? `${d.sessionId.substring(0, 8)}...` : '—'}</span>\n {d.createdAt && <span>{new Date(d.createdAt).toLocaleString()}</span>}\n </div>\n </div>\n ))\n )}\n </div>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AIPromptsTab.js","names":[],"sources":["../../../src/components/ai/AIPromptsTab.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport { Send, FileText, Wand2, Clock } from 'lucide-react';\nimport { Card, LoadingState, ErrorState, EmptyState } from '@/components/shared';\nimport type { AITemplate, AIContext, AISession, AIDispatch, AgentRef } from '@/services/aiApi';\nimport {\n fetchTemplates,\n fetchContexts,\n fetchSessions,\n fetchDispatchHistory,\n renderTemplate,\n recordTemplateUsage,\n sendDispatch,\n} from '@/services/aiApi';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AIPromptsTabProps {\n agent: AgentRef;\n}\n\nexport function AIPromptsTab({ agent }: AIPromptsTabProps) {\n const tr = useTr();\n const [promptContent, setPromptContent] = useState('');\n const [selectedContextIds, setSelectedContextIds] = useState<string[]>([]);\n const [targetSessionId, setTargetSessionId] = useState('');\n const [sending, setSending] = useState(false);\n const [successMsg, setSuccessMsg] = useState<string | null>(null);\n\n // Generate from template state\n const [showTemplateGen, setShowTemplateGen] = useState(false);\n const [selectedTemplateId, setSelectedTemplateId] = useState('');\n const [sourceTemplateId, setSourceTemplateId] = useState<string | null>(null);\n const [templateVars, setTemplateVars] = useState<Record<string, string>>({});\n const [rendering, setRendering] = useState(false);\n\n // Data\n const [templates, setTemplates] = useState<AITemplate[]>([]);\n const [contexts, setContexts] = useState<AIContext[]>([]);\n const [sessions, setSessions] = useState<AISession[]>([]);\n const [history, setHistory] = useState<AIDispatch[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const loadData = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const [t, c, s, h] = await Promise.all([\n fetchTemplates(agent.id),\n fetchContexts(agent),\n fetchSessions(agent),\n fetchDispatchHistory(agent, { limit: 20 }),\n ]);\n setTemplates(t);\n setContexts(c);\n setSessions(s);\n setHistory(h);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load data');\n } finally {\n setLoading(false);\n }\n }, [agent]);\n\n useEffect(() => {\n loadData();\n }, [loadData]);\n\n const handleTemplateSelect = (templateId: string) => {\n setSelectedTemplateId(templateId);\n const template = templates.find((t) => t.id === templateId);\n if (template?.variables) {\n const vars: Record<string, string> = {};\n for (const key of Object.keys(template.variables)) {\n vars[key] = String(template.variables[key] ?? '');\n }\n setTemplateVars(vars);\n } else {\n setTemplateVars({});\n }\n };\n\n const handleGenerateFromTemplate = async () => {\n if (!selectedTemplateId) return;\n setRendering(true);\n try {\n const result = await renderTemplate(agent.id, selectedTemplateId, templateVars);\n setPromptContent(result.rendered);\n setSourceTemplateId(selectedTemplateId);\n setShowTemplateGen(false);\n } catch (err) {\n console.error('Failed to render template:', err);\n } finally {\n setRendering(false);\n }\n };\n\n const toggleContext = (contextId: string) => {\n setSelectedContextIds((prev) =>\n prev.includes(contextId) ? prev.filter((id) => id !== contextId) : [...prev, contextId]\n );\n };\n\n const handleSendToSession = async () => {\n if (!promptContent.trim() || !targetSessionId) return;\n setSending(true);\n setSuccessMsg(null);\n try {\n await sendDispatch(agent, {\n prompt: promptContent,\n sessionId: targetSessionId,\n contextIds: selectedContextIds.length > 0 ? selectedContextIds : undefined,\n });\n if (sourceTemplateId) {\n await recordTemplateUsage(agent.id, sourceTemplateId);\n }\n setSuccessMsg('Prompt dispatched successfully');\n setPromptContent('');\n setSourceTemplateId(null);\n setSelectedContextIds([]);\n setTimeout(() => setSuccessMsg(null), 3000);\n // Refresh history\n const h = await fetchDispatchHistory(agent, { limit: 20 });\n setHistory(h);\n } catch (err) {\n console.error('Failed to dispatch prompt:', err);\n } finally {\n setSending(false);\n }\n };\n\n if (loading)\n return <LoadingState message={tr('vibecontrols.ai.prompts.loading', 'Loading prompts...')} />;\n if (error)\n return (\n <ErrorState\n title={tr('vibecontrols.ai.prompts.loadFailed', 'Failed to load data')}\n message={error}\n onRetry={loadData}\n />\n );\n\n return (\n <div className=\"space-y-6\">\n {successMsg && (\n <div className=\"bg-status-success-bg-subtle text-status-success-text px-4 py-2 rounded-md text-sm\">\n {successMsg}\n </div>\n )}\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Prompt Editor */}\n <div className=\"lg:col-span-2 space-y-4\">\n <Card className=\"p-4\">\n <div className=\"flex items-center justify-between mb-3\">\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibecontrols.ai.prompts.composePrompt', 'Compose Prompt')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setShowTemplateGen(!showTemplateGen)}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-action-primary-bg bg-[var(--color-accent-soft)] rounded-md hover:bg-[var(--color-accent-soft)] transition-colors\"\n >\n <Wand2 className=\"size-3.5\" />\n {tr('vibecontrols.ai.prompts.generateFromTemplate', 'Generate from Template')}\n </button>\n </div>\n\n {/* Template Generator */}\n {showTemplateGen && (\n <div className=\"mb-4 p-3 bg-bg-sunken rounded-md space-y-3\">\n <select\n value={selectedTemplateId}\n onChange={(e) => handleTemplateSelect(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-surface border border-border-default rounded-md text-text-primary text-sm\"\n >\n <option value=\"\">\n {tr('vibecontrols.ai.prompts.selectTemplate', 'Select a template...')}\n </option>\n {templates.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n {Object.keys(templateVars).length > 0 && (\n <div className=\"space-y-2\">\n {Object.entries(templateVars).map(([key, val]) => (\n <div key={key}>\n <label className=\"block text-xs font-medium text-text-secondary mb-0.5\">\n {key}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={val}\n onChange={(e) =>\n setTemplateVars((prev) => ({ ...prev, [key]: e.target.value }))\n }\n className=\"w-full px-2 py-1.5 bg-bg-surface border border-border-default rounded text-sm text-text-primary focus:outline-none focus:border-border-strong\"\n />\n </div>\n ))}\n </div>\n )}\n <button\n type=\"button\"\n onClick={handleGenerateFromTemplate}\n disabled={rendering || !selectedTemplateId}\n className=\"w-full px-3 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 {rendering\n ? tr('vibecontrols.ai.prompts.generating', 'Generating...')\n : tr('vibecontrols.ai.prompts.generate', 'Generate')}\n </button>\n </div>\n )}\n\n <textarea\n aria-label=\"control\"\n value={promptContent}\n onChange={(e) => {\n setPromptContent(e.target.value);\n setSourceTemplateId(null);\n }}\n rows={8}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm font-mono focus:outline-none focus:border-border-strong\"\n placeholder={tr(\n 'vibecontrols.ai.prompts.promptPlaceholder',\n 'Write your prompt here... (Markdown supported)'\n )}\n />\n\n {/* Session Target & Send */}\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-end gap-3 mt-3\">\n <div className=\"flex-1\">\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">\n {tr('vibecontrols.ai.prompts.targetSession', 'Target Session')}\n </label>\n <select\n value={targetSessionId}\n onChange={(e) => setTargetSessionId(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 <option value=\"\">\n {tr('vibecontrols.ai.prompts.selectSession', 'Select session...')}\n </option>\n {sessions\n .filter((s) => s.status !== 'terminated')\n .map((s) => (\n <option key={s.id} value={s.id}>\n {s.name} ({s.status})\n </option>\n ))}\n </select>\n </div>\n <button\n type=\"button\"\n onClick={handleSendToSession}\n disabled={sending || !promptContent.trim() || !targetSessionId}\n className=\"flex items-center justify-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 text-sm\"\n >\n <Send className=\"size-4\" />\n {sending\n ? tr('vibecontrols.ai.prompts.sending', 'Sending...')\n : tr('vibecontrols.ai.prompts.sendToSession', 'Send to Session')}\n </button>\n </div>\n </Card>\n </div>\n\n {/* Context Picker */}\n <div className=\"space-y-4\">\n <Card className=\"p-4\">\n <h3 className=\"font-semibold text-text-primary mb-3\">\n {tr('vibecontrols.ai.prompts.attachContexts', 'Attach Contexts')}\n </h3>\n {contexts.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('vibecontrols.ai.prompts.noContexts', 'No contexts available')}\n </p>\n ) : (\n <div className=\"space-y-2 max-h-64 overflow-y-auto\">\n {contexts.map((ctx) => (\n <label\n key={ctx.id}\n className=\"flex items-start gap-2 p-2 rounded hover:bg-bg-sunken cursor-pointer\"\n >\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selectedContextIds.includes(ctx.id)}\n onChange={() => toggleContext(ctx.id)}\n className=\"mt-0.5 rounded border-border-default\"\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"text-sm font-medium text-text-primary truncate\">\n {ctx.name}\n </div>\n <div className=\"text-xs text-text-secondary\">{ctx.type}</div>\n </div>\n </label>\n ))}\n </div>\n )}\n </Card>\n </div>\n </div>\n\n {/* Dispatch History */}\n <Card className=\"p-0\">\n <div className=\"px-4 py-3 border-b border-border-default flex items-center gap-2\">\n <Clock className=\"size-4 text-text-secondary\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibecontrols.ai.prompts.dispatchHistory', 'Dispatch History')}\n </h3>\n </div>\n <div className=\"divide-y divide-border-default\">\n {history.length === 0 ? (\n <EmptyState\n icon={<FileText className=\"size-6 text-text-secondary\" />}\n title={tr('vibecontrols.ai.prompts.noDispatched', 'No dispatched prompts')}\n description={tr(\n 'vibecontrols.ai.prompts.noDispatchedDesc',\n 'Send a prompt to a session to see history here'\n )}\n />\n ) : (\n history.map((d) => (\n <div key={d.id} className=\"px-4 py-3\">\n <div className=\"text-sm text-text-primary line-clamp-2\">{d.prompt}</div>\n <div className=\"mt-1 flex items-center gap-3 text-xs text-text-secondary\">\n <span className=\"font-medium\">{d.status}</span>\n <span>Session: {d.sessionId ? `${d.sessionId.substring(0, 8)}...` : '—'}</span>\n {d.createdAt && <span>{new Date(d.createdAt).toLocaleString()}</span>}\n </div>\n </div>\n ))\n )}\n </div>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -137,15 +137,15 @@ function b({ agent: i, provider: c, onAuthSatisfiedChange: l }) {
|
|
|
137
137
|
]
|
|
138
138
|
}),
|
|
139
139
|
M ? /* @__PURE__ */ g("div", {
|
|
140
|
-
className: "rounded-lg border border-status-success-border bg-status-success-bg px-3 py-2 text-xs text-status-success-text",
|
|
140
|
+
className: "rounded-lg border border-status-success-border bg-status-success-bg-subtle px-3 py-2 text-xs text-status-success-text",
|
|
141
141
|
children: u("ai.providerCredentials.authSatisfied", `${c.label} is ready. The agent will use ${j.join(" / ")} on the next SDK session.`)
|
|
142
142
|
}) : null,
|
|
143
143
|
E ? /* @__PURE__ */ g("div", {
|
|
144
|
-
className: "rounded-lg border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text",
|
|
144
|
+
className: "rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
145
145
|
children: E
|
|
146
146
|
}) : null,
|
|
147
147
|
O ? /* @__PURE__ */ g("div", {
|
|
148
|
-
className: "rounded-lg border border-status-success-border bg-status-success-bg px-3 py-2 text-sm text-status-success-text",
|
|
148
|
+
className: "rounded-lg border border-status-success-border bg-status-success-bg-subtle px-3 py-2 text-sm text-status-success-text",
|
|
149
149
|
children: O
|
|
150
150
|
}) : null,
|
|
151
151
|
S ? /* @__PURE__ */ _("div", {
|
|
@@ -181,7 +181,7 @@ function b({ agent: i, provider: c, onAuthSatisfiedChange: l }) {
|
|
|
181
181
|
}
|
|
182
182
|
function x({ label: e, secret: t, value: n, saved: r, placeholder: i, onChange: a, saving: o, visible: s, onToggleVisible: c, onSave: l, onClear: f }) {
|
|
183
183
|
return /* @__PURE__ */ _("div", {
|
|
184
|
-
className: "space-y-2 rounded-lg border border-border-default bg-bg-
|
|
184
|
+
className: "space-y-2 rounded-lg border border-border-default bg-bg-surface p-3",
|
|
185
185
|
children: [/* @__PURE__ */ g("label", {
|
|
186
186
|
className: "block text-xs font-medium text-text-secondary",
|
|
187
187
|
children: e
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIProviderCredentialsCard.js","names":[],"sources":["../../../src/components/ai/AIProviderCredentialsCard.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport {\n ArrowLeft,\n ChevronRight,\n Eye,\n EyeOff,\n KeyRound,\n Loader2,\n Save,\n Trash2,\n} 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\nimport { AI_PROVIDER_CREDENTIALS, type AIProviderCredentialDef } from './aiProviderCredentials';\n\ninterface AIProviderCredentialsCardProps {\n agent: AgentRef;\n /**\n * Optional filter — when present, only render providers whose providerId\n * appears in the list. Lets a future caller scope the card to \"only the\n * providers I have plugins installed for\" without changing the catalog.\n */\n providerIds?: string[];\n /**\n * When provided, the staged picker preselects this provider so the\n * caller (e.g. the SDK-auth-missing modal) can drop the user straight\n * onto the right form without an extra click.\n */\n initialProviderId?: string;\n /**\n * Fires whenever the currently-selected provider's saved-state changes\n * on disk. `satisfied` is true when at least one primary (auth-conferring)\n * field has a saved non-empty value. Used by the SDK-auth-missing modal\n * to enable a Continue affordance the moment the user persists any\n * acceptable credential, without forcing them to also fill the others.\n */\n onProviderAuthSatisfiedChange?: (satisfied: boolean) => void;\n}\n\n/**\n * Staged credentials manager: shows a provider picker first, then on\n * select renders one card with that provider's config keys plus a back\n * button. Keeps the rendered DOM small (and the surrounding modal\n * viewport-friendly) regardless of how many providers are in the catalog.\n *\n * Replaces the older Claude-only ClaudeCredentialsCard.\n */\nexport function AIProviderCredentialsCard({\n agent,\n providerIds,\n initialProviderId,\n onProviderAuthSatisfiedChange,\n}: AIProviderCredentialsCardProps) {\n const tr = useTr();\n const providers = providerIds\n ? AI_PROVIDER_CREDENTIALS.filter((p) => providerIds.includes(p.providerId))\n : AI_PROVIDER_CREDENTIALS;\n\n // Preselect when the catalog narrows to exactly one provider so the\n // single-provider case (e.g. claude-only filter) skips the picker.\n const [selectedId, setSelectedId] = useState<string | null>(() => {\n if (initialProviderId && providers.some((p) => p.providerId === initialProviderId)) {\n return initialProviderId;\n }\n return providers.length === 1 ? (providers[0]?.providerId ?? null) : null;\n });\n\n const selected = providers.find((p) => p.providerId === selectedId) ?? null;\n\n if (!selected) {\n return (\n <div className=\"space-y-3\">\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.providerCredentials.pickTitle', 'Choose a provider')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'ai.providerCredentials.pickDescription',\n 'Pick the provider whose credentials you want to manage. Keys are stored on the agent and read at session start.'\n )}\n </p>\n </div>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n {providers.map((p) => (\n <button\n key={p.providerId}\n type=\"button\"\n onClick={() => setSelectedId(p.providerId)}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-default bg-bg-surface px-3 py-2 text-left hover:border-border-strong hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"min-w-0\">\n <div className=\"text-sm font-medium text-text-primary truncate\">{p.label}</div>\n <div className=\"text-xs text-text-secondary truncate\">\n {p.fields.map((f) => f.key).join(', ')}\n </div>\n </div>\n <ChevronRight className=\"size-4 flex-shrink-0 text-text-secondary\" />\n </button>\n ))}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n {providers.length > 1 ? (\n <button\n type=\"button\"\n onClick={() => setSelectedId(null)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-default px-2 py-1 text-xs text-text-secondary hover:text-text-primary hover:border-border-strong\"\n aria-label={tr('ai.providerCredentials.back', 'Back to providers')}\n >\n <ArrowLeft className=\"size-3.5\" />\n {tr('ai.providerCredentials.back', 'Back')}\n </button>\n ) : null}\n <div className=\"rounded-lg bg-bg-sunken p-2 text-text-secondary\">\n <KeyRound className=\"size-4\" />\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary truncate\">{selected.label}</h3>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'ai.providerCredentials.scopedDescription',\n 'Keys are stored on the agent and read at session start.'\n )}\n </p>\n </div>\n </div>\n <ProviderCard\n agent={agent}\n provider={selected}\n onAuthSatisfiedChange={onProviderAuthSatisfiedChange}\n />\n </div>\n );\n}\n\nfunction isPrimaryField(f: AIProviderCredentialDef['fields'][number]): boolean {\n // Defaults: secret fields gate auth; URL/host overrides don't.\n return f.primary ?? f.secret ?? false;\n}\n\nfunction ProviderCard({\n agent,\n provider,\n onAuthSatisfiedChange,\n}: {\n agent: AgentRef;\n provider: AIProviderCredentialDef;\n onAuthSatisfiedChange?: (satisfied: boolean) => void;\n}) {\n const tr = useTr();\n const [values, setValues] = useState<Record<string, string>>({});\n // savedValues tracks what's actually on the agent (last successful\n // load/persist), not the unsaved input buffer. This is what drives the\n // \"auth satisfied\" indicator — typing an API key in the field doesn't\n // count until the user hits Save.\n const [savedValues, setSavedValues] = useState<Record<string, string>>({});\n const [visible, setVisible] = useState<Record<string, boolean>>({});\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState<string | null>(null);\n\n const primaryKeys = provider.fields.filter(isPrimaryField).map((f) => f.key);\n const satisfiedPrimaryKeys = primaryKeys.filter((k) => (savedValues[k] ?? '').trim().length > 0);\n const authSatisfied = satisfiedPrimaryKeys.length > 0;\n\n // Notify parent whenever satisfied flips. Effect (not inline call) so\n // children don't render-during-render of the parent.\n useEffect(() => {\n onAuthSatisfiedChange?.(authSatisfied);\n }, [authSatisfied, onAuthSatisfiedChange]);\n\n const loadValues = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const entries = await Promise.all(\n provider.fields.map(async (f) => [f.key, (await getAgentConfigValue(agent, f.key)) ?? ''])\n );\n const next = Object.fromEntries(entries);\n // Don't seed inputs with masked secrets (\"[redacted]\") — Save would re-persist the mask. Non-secret fields (URLs/hosts) return real values, so keep those editable.\n setValues(\n Object.fromEntries(\n provider.fields.filter((f) => !f.secret).map((f) => [f.key, next[f.key] ?? ''])\n )\n );\n setSavedValues(next);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.providerCredentials.loadFailed', 'Failed to load provider credentials')\n );\n } finally {\n setLoading(false);\n }\n }, [agent, provider.fields, tr]);\n\n useEffect(() => {\n void loadValues();\n }, [loadValues]);\n\n const persistValue = useCallback(\n async (key: string, value: string) => {\n setSaving(key);\n setError(null);\n setSuccess(null);\n const trimmed = value.trim();\n try {\n if (trimmed) {\n await setAgentConfigValue(agent, key, trimmed);\n } else {\n await deleteAgentConfigValue(agent, key);\n }\n // Mirror the persisted result into savedValues so the auth-\n // satisfied banner / Continue button update immediately.\n setSavedValues((cur) => ({ ...cur, [key]: trimmed }));\n setSuccess(\n trimmed\n ? tr('ai.providerCredentials.saved', `${key} saved`)\n : tr('ai.providerCredentials.cleared', `${key} cleared`)\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.providerCredentials.saveFailed', 'Failed to save 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-3\">\n <div className=\"space-y-1\">\n <h4 className=\"text-sm font-semibold text-text-primary\">{provider.label}</h4>\n <p className=\"text-xs text-text-secondary\">{provider.description}</p>\n {primaryKeys.length > 1 ? (\n <p className=\"text-xs text-text-tertiary\">\n {tr(\n 'ai.providerCredentials.eitherOr',\n `Save any one of: ${primaryKeys.join(' · ')}. You don't need to fill all of them.`\n )}\n </p>\n ) : null}\n </div>\n\n {/* Auth-satisfied banner — surfaces the moment any primary credential\n is persisted on the agent. The SDK-auth-missing modal pairs this\n with a Continue button so the user knows they can proceed. */}\n {authSatisfied ? (\n <div className=\"rounded-lg border border-status-success-border bg-status-success-bg px-3 py-2 text-xs text-status-success-text\">\n {tr(\n 'ai.providerCredentials.authSatisfied',\n `${provider.label} is ready. The agent will use ${satisfiedPrimaryKeys.join(' / ')} on the next SDK session.`\n )}\n </div>\n ) : null}\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 {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.providerCredentials.loading', 'Loading…')}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {provider.fields.map((f) => {\n const isSecret = f.secret ?? false;\n const isSaved = Boolean((savedValues[f.key] ?? '').trim());\n return (\n <CredentialField\n key={f.key}\n label={f.key}\n secret={isSecret}\n value={values[f.key] ?? ''}\n saved={isSaved}\n placeholder={\n isSaved\n ? isSecret\n ? tr(\n 'ai.providerCredentials.storedSecretPlaceholder',\n '•••••••• stored — enter a new value to replace'\n )\n : tr(\n 'ai.providerCredentials.storedPlaceholder',\n 'stored — enter a new value to replace'\n )\n : ''\n }\n onChange={(v) => setValues((cur) => ({ ...cur, [f.key]: v }))}\n saving={saving === f.key}\n visible={visible[f.key] ?? false}\n onToggleVisible={() => setVisible((cur) => ({ ...cur, [f.key]: !cur[f.key] }))}\n onSave={() => void persistValue(f.key, values[f.key] ?? '')}\n onClear={() => void persistValue(f.key, '')}\n />\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\ninterface CredentialFieldProps {\n label: string;\n secret: boolean;\n value: string;\n /** True when a value is already stored on the agent (read back masked). */\n saved: boolean;\n /** Resolved (i18n) placeholder shown when the input is empty but a value is stored. */\n placeholder: 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 secret,\n value,\n saved,\n placeholder,\n onChange,\n saving,\n visible,\n onToggleVisible,\n onSave,\n onClear,\n}: CredentialFieldProps) {\n const showAsPassword = secret && !visible;\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={showAsPassword ? 'password' : 'text'}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n placeholder={placeholder}\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 {secret ? (\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 ) : null}\n </div>\n <button\n type=\"button\"\n onClick={onSave}\n disabled={saving || !value.trim()}\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 && !saved)}\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":"AIProviderCredentialsCard.js","names":[],"sources":["../../../src/components/ai/AIProviderCredentialsCard.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport {\n ArrowLeft,\n ChevronRight,\n Eye,\n EyeOff,\n KeyRound,\n Loader2,\n Save,\n Trash2,\n} 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\nimport { AI_PROVIDER_CREDENTIALS, type AIProviderCredentialDef } from './aiProviderCredentials';\n\ninterface AIProviderCredentialsCardProps {\n agent: AgentRef;\n /**\n * Optional filter — when present, only render providers whose providerId\n * appears in the list. Lets a future caller scope the card to \"only the\n * providers I have plugins installed for\" without changing the catalog.\n */\n providerIds?: string[];\n /**\n * When provided, the staged picker preselects this provider so the\n * caller (e.g. the SDK-auth-missing modal) can drop the user straight\n * onto the right form without an extra click.\n */\n initialProviderId?: string;\n /**\n * Fires whenever the currently-selected provider's saved-state changes\n * on disk. `satisfied` is true when at least one primary (auth-conferring)\n * field has a saved non-empty value. Used by the SDK-auth-missing modal\n * to enable a Continue affordance the moment the user persists any\n * acceptable credential, without forcing them to also fill the others.\n */\n onProviderAuthSatisfiedChange?: (satisfied: boolean) => void;\n}\n\n/**\n * Staged credentials manager: shows a provider picker first, then on\n * select renders one card with that provider's config keys plus a back\n * button. Keeps the rendered DOM small (and the surrounding modal\n * viewport-friendly) regardless of how many providers are in the catalog.\n *\n * Replaces the older Claude-only ClaudeCredentialsCard.\n */\nexport function AIProviderCredentialsCard({\n agent,\n providerIds,\n initialProviderId,\n onProviderAuthSatisfiedChange,\n}: AIProviderCredentialsCardProps) {\n const tr = useTr();\n const providers = providerIds\n ? AI_PROVIDER_CREDENTIALS.filter((p) => providerIds.includes(p.providerId))\n : AI_PROVIDER_CREDENTIALS;\n\n // Preselect when the catalog narrows to exactly one provider so the\n // single-provider case (e.g. claude-only filter) skips the picker.\n const [selectedId, setSelectedId] = useState<string | null>(() => {\n if (initialProviderId && providers.some((p) => p.providerId === initialProviderId)) {\n return initialProviderId;\n }\n return providers.length === 1 ? (providers[0]?.providerId ?? null) : null;\n });\n\n const selected = providers.find((p) => p.providerId === selectedId) ?? null;\n\n if (!selected) {\n return (\n <div className=\"space-y-3\">\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.providerCredentials.pickTitle', 'Choose a provider')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'ai.providerCredentials.pickDescription',\n 'Pick the provider whose credentials you want to manage. Keys are stored on the agent and read at session start.'\n )}\n </p>\n </div>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n {providers.map((p) => (\n <button\n key={p.providerId}\n type=\"button\"\n onClick={() => setSelectedId(p.providerId)}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-default bg-bg-surface px-3 py-2 text-left hover:border-border-strong hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"min-w-0\">\n <div className=\"text-sm font-medium text-text-primary truncate\">{p.label}</div>\n <div className=\"text-xs text-text-secondary truncate\">\n {p.fields.map((f) => f.key).join(', ')}\n </div>\n </div>\n <ChevronRight className=\"size-4 flex-shrink-0 text-text-secondary\" />\n </button>\n ))}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n {providers.length > 1 ? (\n <button\n type=\"button\"\n onClick={() => setSelectedId(null)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-default px-2 py-1 text-xs text-text-secondary hover:text-text-primary hover:border-border-strong\"\n aria-label={tr('ai.providerCredentials.back', 'Back to providers')}\n >\n <ArrowLeft className=\"size-3.5\" />\n {tr('ai.providerCredentials.back', 'Back')}\n </button>\n ) : null}\n <div className=\"rounded-lg bg-bg-sunken p-2 text-text-secondary\">\n <KeyRound className=\"size-4\" />\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary truncate\">{selected.label}</h3>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'ai.providerCredentials.scopedDescription',\n 'Keys are stored on the agent and read at session start.'\n )}\n </p>\n </div>\n </div>\n <ProviderCard\n agent={agent}\n provider={selected}\n onAuthSatisfiedChange={onProviderAuthSatisfiedChange}\n />\n </div>\n );\n}\n\nfunction isPrimaryField(f: AIProviderCredentialDef['fields'][number]): boolean {\n // Defaults: secret fields gate auth; URL/host overrides don't.\n return f.primary ?? f.secret ?? false;\n}\n\nfunction ProviderCard({\n agent,\n provider,\n onAuthSatisfiedChange,\n}: {\n agent: AgentRef;\n provider: AIProviderCredentialDef;\n onAuthSatisfiedChange?: (satisfied: boolean) => void;\n}) {\n const tr = useTr();\n const [values, setValues] = useState<Record<string, string>>({});\n // savedValues tracks what's actually on the agent (last successful\n // load/persist), not the unsaved input buffer. This is what drives the\n // \"auth satisfied\" indicator — typing an API key in the field doesn't\n // count until the user hits Save.\n const [savedValues, setSavedValues] = useState<Record<string, string>>({});\n const [visible, setVisible] = useState<Record<string, boolean>>({});\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState<string | null>(null);\n\n const primaryKeys = provider.fields.filter(isPrimaryField).map((f) => f.key);\n const satisfiedPrimaryKeys = primaryKeys.filter((k) => (savedValues[k] ?? '').trim().length > 0);\n const authSatisfied = satisfiedPrimaryKeys.length > 0;\n\n // Notify parent whenever satisfied flips. Effect (not inline call) so\n // children don't render-during-render of the parent.\n useEffect(() => {\n onAuthSatisfiedChange?.(authSatisfied);\n }, [authSatisfied, onAuthSatisfiedChange]);\n\n const loadValues = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const entries = await Promise.all(\n provider.fields.map(async (f) => [f.key, (await getAgentConfigValue(agent, f.key)) ?? ''])\n );\n const next = Object.fromEntries(entries);\n // Don't seed inputs with masked secrets (\"[redacted]\") — Save would re-persist the mask. Non-secret fields (URLs/hosts) return real values, so keep those editable.\n setValues(\n Object.fromEntries(\n provider.fields.filter((f) => !f.secret).map((f) => [f.key, next[f.key] ?? ''])\n )\n );\n setSavedValues(next);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.providerCredentials.loadFailed', 'Failed to load provider credentials')\n );\n } finally {\n setLoading(false);\n }\n }, [agent, provider.fields, tr]);\n\n useEffect(() => {\n void loadValues();\n }, [loadValues]);\n\n const persistValue = useCallback(\n async (key: string, value: string) => {\n setSaving(key);\n setError(null);\n setSuccess(null);\n const trimmed = value.trim();\n try {\n if (trimmed) {\n await setAgentConfigValue(agent, key, trimmed);\n } else {\n await deleteAgentConfigValue(agent, key);\n }\n // Mirror the persisted result into savedValues so the auth-\n // satisfied banner / Continue button update immediately.\n setSavedValues((cur) => ({ ...cur, [key]: trimmed }));\n setSuccess(\n trimmed\n ? tr('ai.providerCredentials.saved', `${key} saved`)\n : tr('ai.providerCredentials.cleared', `${key} cleared`)\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.providerCredentials.saveFailed', 'Failed to save 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-3\">\n <div className=\"space-y-1\">\n <h4 className=\"text-sm font-semibold text-text-primary\">{provider.label}</h4>\n <p className=\"text-xs text-text-secondary\">{provider.description}</p>\n {primaryKeys.length > 1 ? (\n <p className=\"text-xs text-text-tertiary\">\n {tr(\n 'ai.providerCredentials.eitherOr',\n `Save any one of: ${primaryKeys.join(' · ')}. You don't need to fill all of them.`\n )}\n </p>\n ) : null}\n </div>\n\n {/* Auth-satisfied banner — surfaces the moment any primary credential\n is persisted on the agent. The SDK-auth-missing modal pairs this\n with a Continue button so the user knows they can proceed. */}\n {authSatisfied ? (\n <div className=\"rounded-lg border border-status-success-border bg-status-success-bg-subtle px-3 py-2 text-xs text-status-success-text\">\n {tr(\n 'ai.providerCredentials.authSatisfied',\n `${provider.label} is ready. The agent will use ${satisfiedPrimaryKeys.join(' / ')} on the next SDK session.`\n )}\n </div>\n ) : null}\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 {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.providerCredentials.loading', 'Loading…')}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {provider.fields.map((f) => {\n const isSecret = f.secret ?? false;\n const isSaved = Boolean((savedValues[f.key] ?? '').trim());\n return (\n <CredentialField\n key={f.key}\n label={f.key}\n secret={isSecret}\n value={values[f.key] ?? ''}\n saved={isSaved}\n placeholder={\n isSaved\n ? isSecret\n ? tr(\n 'ai.providerCredentials.storedSecretPlaceholder',\n '•••••••• stored — enter a new value to replace'\n )\n : tr(\n 'ai.providerCredentials.storedPlaceholder',\n 'stored — enter a new value to replace'\n )\n : ''\n }\n onChange={(v) => setValues((cur) => ({ ...cur, [f.key]: v }))}\n saving={saving === f.key}\n visible={visible[f.key] ?? false}\n onToggleVisible={() => setVisible((cur) => ({ ...cur, [f.key]: !cur[f.key] }))}\n onSave={() => void persistValue(f.key, values[f.key] ?? '')}\n onClear={() => void persistValue(f.key, '')}\n />\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\ninterface CredentialFieldProps {\n label: string;\n secret: boolean;\n value: string;\n /** True when a value is already stored on the agent (read back masked). */\n saved: boolean;\n /** Resolved (i18n) placeholder shown when the input is empty but a value is stored. */\n placeholder: 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 secret,\n value,\n saved,\n placeholder,\n onChange,\n saving,\n visible,\n onToggleVisible,\n onSave,\n onClear,\n}: CredentialFieldProps) {\n const showAsPassword = secret && !visible;\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={showAsPassword ? 'password' : 'text'}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n placeholder={placeholder}\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 {secret ? (\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 ) : null}\n </div>\n <button\n type=\"button\"\n onClick={onSave}\n disabled={saving || !value.trim()}\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 && !saved)}\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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"}
|
|
@@ -13,22 +13,22 @@ import { Fragment as y, jsx as b, jsxs as x } from "react/jsx-runtime";
|
|
|
13
13
|
var S = {
|
|
14
14
|
pending: {
|
|
15
15
|
label: "Pending",
|
|
16
|
-
cls: "bg-status-warning-bg text-status-warning-text",
|
|
16
|
+
cls: "bg-status-warning-bg-subtle text-status-warning-text",
|
|
17
17
|
icon: m
|
|
18
18
|
},
|
|
19
19
|
processing: {
|
|
20
20
|
label: "Processing",
|
|
21
|
-
cls: "bg-
|
|
21
|
+
cls: "bg-[var(--color-accent-soft)] text-action-primary-bg",
|
|
22
22
|
icon: g
|
|
23
23
|
},
|
|
24
24
|
completed: {
|
|
25
25
|
label: "Completed",
|
|
26
|
-
cls: "bg-status-success-bg text-status-success-text",
|
|
26
|
+
cls: "bg-status-success-bg-subtle text-status-success-text",
|
|
27
27
|
icon: p
|
|
28
28
|
},
|
|
29
29
|
failed: {
|
|
30
30
|
label: "Failed",
|
|
31
|
-
cls: "bg-status-error-bg text-status-error-text",
|
|
31
|
+
cls: "bg-status-error-bg-subtle text-status-error-text",
|
|
32
32
|
icon: d
|
|
33
33
|
},
|
|
34
34
|
cancelled: {
|
|
@@ -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":"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"}
|
|
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-action-primary-bg',\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-action-primary-bg",
|
|
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-action-primary-bg',\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":"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"}
|
|
@@ -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", {
|