@burdenoff/microfe-vibecontrols 2026.522.1 → 2026.522.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
- package/dist/components/analytics/index.js +2 -2
- package/dist/components/gitops/CIDurationChart.js +11 -11
- package/dist/components/shared/PageSkeleton.js +1 -1
- package/dist/pages/AgentsPage.js +78 -80
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/AnalyticsPage.js +425 -420
- package/dist/pages/AnalyticsPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +91 -86
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/VibesPage.js +333 -320
- package/dist/pages/VibesPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentManagerChatPane.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerChatPane.tsx"],"sourcesContent":["/**\n * Main chat interface for a single Agent Manager session.\n *\n * Renders message list + input area, manages the send/receive flow,\n * fetches existing conversation logs on mount, and provides\n * auto-scroll, cancel, and in-session search support.\n */\n\nimport { useEffect, useRef, useState, useCallback, useMemo, type FC } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Bookmark, PackagePlus, Search, X } from 'lucide-react';\nimport { useAgentManagerStore } from '@/store/agentManagerStore';\nimport { useAgentManagerStreaming } from '@/hooks/useAgentManagerStreaming';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { fetchSessionLogs } from '@/services/aiApi';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport { useToggleAgentBookmark } from '@/hooks/useToggleAgentBookmark';\nimport { AgentManagerMessageList } from './AgentManagerMessageList';\nimport { AgentManagerInputArea } from './AgentManagerInputArea';\nimport { AgentManagerBookmarksDrawer } from './AgentManagerBookmarksDrawer';\nimport { PluginInstallModal } from './PluginInstallModal';\nimport { AIProviderCredentialsCard } from '@/components/ai/AIProviderCredentialsCard';\nimport type { AgentManagerMessage } from '@/types/agentManager';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AgentManagerChatPaneProps {\n sessionId: string;\n agentId: string;\n}\n\nexport const AgentManagerChatPane: FC<AgentManagerChatPaneProps> = ({ sessionId, agentId }) => {\n const [loadingLogs, setLoadingLogs] = useState(false);\n const [searchOpen, setSearchOpen] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n // Provider-install modal triggered from the header. Lets users add a\n // claude/codex/gemini/opencode plugin without leaving the conversation.\n const [installModalOpen, setInstallModalOpen] = useState(false);\n const [bookmarksDrawerOpen, setBookmarksDrawerOpen] = useState(false);\n const [flashMessageId, setFlashMessageId] = useState<string | null>(null);\n const [searchParams, setSearchParams] = useSearchParams();\n const pendingScrollMessageId = searchParams.get('messageId');\n const consumedScrollTargetRef = useRef<string | null>(null);\n const tr = useTr();\n\n const { agents } = useCachedInitialData();\n const activeAgent = useMemo(\n () => agents.find((a) => a.id === agentId) ?? null,\n [agents, agentId]\n );\n\n const sessionState = useAgentManagerStore((s) => s.sessionStates[sessionId]);\n const addMessage = useAgentManagerStore((s) => s.addMessage);\n\n const messages = useMemo(\n () => sessionState?.messages ?? [],\n [sessionState?.messages]\n );\n\n const { active: activeProfile } = useActiveAgentProfile(activeAgent?.id ?? null);\n const agentRef = useMemo(\n () => ({ id: agentId, profile: activeProfile }),\n [agentId, activeProfile]\n );\n\n const { sendMessage, cancelStream, isStreaming } = useAgentManagerStreaming({\n agent: agentRef,\n sessionId,\n });\n\n const { bookmarkedIds, toggle: toggleBookmark } = useToggleAgentBookmark(sessionId);\n\n const jumpToMessage = useCallback((messageId: string) => {\n const el = document.querySelector(`[data-message-id=\"${messageId}\"]`);\n if (!el) return;\n el.scrollIntoView({ behavior: 'smooth', block: 'center' });\n setFlashMessageId(messageId);\n window.setTimeout(() => {\n setFlashMessageId((current) => (current === messageId ? null : current));\n }, 1500);\n }, []);\n\n const handleToggleBookmark = useCallback(\n (messageId: string) => {\n void toggleBookmark({ agentId, sessionId, messageId });\n },\n [toggleBookmark, agentId, sessionId]\n );\n\n const toggleSearch = useCallback(() => {\n setSearchOpen((prev) => {\n if (prev) setSearchQuery('');\n return !prev;\n });\n }, []);\n\n // Fetch existing conversation logs on mount\n useEffect(() => {\n let cancelled = false;\n\n const loadExistingLogs = async () => {\n // Only load if session has no messages yet\n if (messages.length > 0) return;\n\n setLoadingLogs(true);\n try {\n const logs = await fetchSessionLogs(agentRef, sessionId, {\n types: ['input', 'output'],\n });\n\n if (cancelled || logs.length === 0) return;\n\n const seenLogKeys = new Set<string>();\n\n // Convert session logs to AgentManagerMessage format. The core AI\n // route owns logging, but older provider builds also wrote the same\n // input/output records; collapse same-second duplicates on restore.\n for (const log of logs) {\n const content = log.message || log.content || '';\n const timestamp = log.timestamp || log.createdAt;\n const timestampSecond = timestamp ? Math.floor(new Date(timestamp).getTime() / 1000) : 0;\n const logKey = [log.type, content, log.model ?? '', timestampSecond].join('\\u0000');\n if (seenLogKeys.has(logKey)) continue;\n seenLogKeys.add(logKey);\n\n const msg: AgentManagerMessage = {\n id: log.id,\n role: log.type === 'input' ? 'user' : 'assistant',\n content,\n timestamp,\n tokens: log.tokenCount ? { input: 0, output: log.tokenCount } : undefined,\n model: log.model,\n };\n addMessage(sessionId, msg);\n }\n } catch {\n // Silent fail -- session may not have logs yet\n } finally {\n if (!cancelled) setLoadingLogs(false);\n }\n };\n\n void loadExistingLogs();\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- only run on mount\n }, [agentId, sessionId]);\n\n // Auto-scroll to a message linked via ?messageId=... (from drawer / cross-session page).\n // Waits for logs to finish loading and the target to be in the DOM before scrolling.\n useEffect(() => {\n if (!pendingScrollMessageId || loadingLogs) return;\n if (consumedScrollTargetRef.current === pendingScrollMessageId) return;\n\n const exists = messages.some((m) => m.id === pendingScrollMessageId);\n if (!exists) return;\n\n consumedScrollTargetRef.current = pendingScrollMessageId;\n jumpToMessage(pendingScrollMessageId);\n\n // Strip the query param so a manual refresh doesn't re-trigger the flash.\n const next = new URLSearchParams(searchParams);\n next.delete('messageId');\n setSearchParams(next, { replace: true });\n }, [pendingScrollMessageId, loadingLogs, messages, jumpToMessage, searchParams, setSearchParams]);\n\n return (\n <div className=\"flex flex-col h-full min-h-0\" data-testid=\"agent-manager-chat-pane\">\n {/* In-session search bar.\n Layout: [search controls (flex-1)] ... [Manage AI providers].\n The search input expands inside the LEFT cluster only, so the\n Manage button stays anchored on the right and never shifts. */}\n <div className=\"flex items-center gap-1 px-3 py-1 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <div className=\"flex items-center gap-1.5 min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={toggleSearch}\n className={`p-1 rounded transition-colors shrink-0 ${\n searchOpen\n ? 'bg-action-primary-bg/10 text-action-primary-text'\n : 'text-text-tertiary hover:text-text-secondary hover:bg-bg-elevated'\n }`}\n title={tr(\n 'vibecontrols.agentManager.chatPane.searchInSession',\n 'Search in session (Ctrl+F)'\n )}\n aria-label={tr(\n 'vibecontrols.agentManager.chatPane.searchInSession',\n 'Search in session'\n )}\n >\n <Search className=\"w-3.5 h-3.5\" />\n </button>\n {searchOpen && (\n <div className=\"flex items-center gap-1.5 flex-1 min-w-0\">\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder={tr(\n 'vibecontrols.agentManager.chatPane.searchPlaceholder',\n 'Search in conversation...'\n )}\n autoFocus\n className=\"flex-1 min-w-0 bg-transparent text-xs text-text-primary placeholder:text-text-tertiary focus:outline-none\"\n />\n {searchQuery && (\n <button\n type=\"button\"\n onClick={() => setSearchQuery('')}\n className=\"p-0.5 rounded hover:bg-bg-elevated transition-colors\"\n >\n <X className=\"w-3 h-3 text-text-tertiary\" />\n </button>\n )}\n </div>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => setBookmarksDrawerOpen(true)}\n className=\"shrink-0 p-1 rounded transition-colors text-text-tertiary hover:text-text-secondary hover:bg-bg-elevated\"\n title={tr(\n 'vibecontrols.agentManager.chatPane.bookmarksTooltip',\n 'View bookmarked messages'\n )}\n aria-label={tr('vibecontrols.agentManager.chatPane.bookmarksLabel', 'Bookmarks')}\n data-testid=\"agent-manager-bookmarks-trigger\"\n >\n <Bookmark className=\"w-3.5 h-3.5\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setInstallModalOpen(true)}\n className=\"shrink-0 inline-flex items-center gap-1.5 rounded-md border border-border-default bg-bg-elevated px-2 py-1 text-xs font-medium text-text-primary transition-colors hover:bg-bg-sunken hover:border-action-primary-bg/40\"\n title={tr(\n 'vibecontrols.agentManager.chatPane.manageProvidersTooltip',\n 'Install or remove AI providers (Claude, Codex, Gemini, OpenCode)'\n )}\n aria-label={tr(\n 'vibecontrols.agentManager.chatPane.manageProviders',\n 'Manage AI providers'\n )}\n data-testid=\"agent-manager-manage-providers\"\n >\n <PackagePlus className=\"w-3.5 h-3.5\" />\n <span className=\"hidden sm:inline\">\n {tr('vibecontrols.agentManager.chatPane.manageProviders', 'Manage AI providers')}\n </span>\n </button>\n </div>\n\n {/* Message list */}\n {loadingLogs ? (\n <div className=\"flex-1 flex items-center justify-center\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.agentManager.chatPane.loadingConversation',\n 'Loading conversation...'\n )}\n </p>\n </div>\n ) : (\n <AgentManagerMessageList\n messages={messages}\n isStreaming={isStreaming}\n onCancelStream={cancelStream}\n searchQuery={searchQuery}\n bookmarkedIds={bookmarkedIds}\n onToggleBookmark={handleToggleBookmark}\n flashMessageId={flashMessageId}\n />\n )}\n\n {/* Rich input area with controls */}\n <AgentManagerInputArea\n agentId={agentId}\n sessionId={sessionId}\n onSend={(text) => void sendMessage(text)}\n onCancel={cancelStream}\n agentTunnelUrl={activeAgent?.tunnelUrl ?? undefined}\n agentApiKey={activeAgent?.agentApiKey ?? undefined}\n />\n\n {/* Per-session bookmarks drawer */}\n <AgentManagerBookmarksDrawer\n open={bookmarksDrawerOpen}\n onClose={() => setBookmarksDrawerOpen(false)}\n sessionId={sessionId}\n messages={messages}\n onJump={jumpToMessage}\n />\n\n {/* Header-triggered provider install/remove. Sits below the chat\n tree so it can render full-screen without disturbing layout. */}\n <PluginInstallModal\n open={installModalOpen}\n agentId={agentId}\n onClose={() => setInstallModalOpen(false)}\n />\n\n {/* SDK auth prompt: opens when the streaming hook detects a missing\n ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN, so the user can save the\n credential inline instead of being told to go look elsewhere.\n The session's currently-selected SDK is forwarded so the picker\n can preselect the right provider. */}\n <SdkAuthPromptModal\n agentId={agentId}\n agentRef={agentRef}\n initialProviderId={sessionState?.sdk ?? undefined}\n />\n </div>\n );\n};\n\ninterface SdkAuthPromptModalProps {\n agentId: string;\n agentRef: { id: string; profile: string };\n initialProviderId?: string;\n}\n\nconst SdkAuthPromptModal: FC<SdkAuthPromptModalProps> = ({\n agentId,\n agentRef,\n initialProviderId,\n}) => {\n const sdkAuthPrompt = useAgentManagerStore((s) => s.sdkAuthPrompt);\n const setSdkAuthPrompt = useAgentManagerStore((s) => s.setSdkAuthPrompt);\n const tr = useTr();\n\n if (!sdkAuthPrompt || sdkAuthPrompt.agentId !== agentId) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center p-4\">\n <div\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={() => setSdkAuthPrompt(null)}\n aria-hidden\n />\n {/* Constrain height to the viewport and let the inner body scroll so the\n modal never escapes the screen no matter how many providers/fields\n render inside AIProviderCredentialsCard. The header stays sticky so\n the close button is always reachable. */}\n <div className=\"relative w-full max-w-xl max-h-[85vh] flex flex-col rounded-lg border border-border-default bg-bg-surface shadow-xl overflow-hidden\">\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <h3 className=\"text-base font-semibold text-text-primary truncate pr-2\">\n {tr('vibecontrols.ai.sdkAuth.title', 'Configure AI provider credentials')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setSdkAuthPrompt(null)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors flex-shrink-0\"\n aria-label={tr('vibecontrols.common.close', 'Close')}\n >\n <X className=\"w-5 h-5 text-text-secondary\" />\n </button>\n </div>\n <div className=\"flex-1 min-h-0 overflow-y-auto px-4 py-3 space-y-3\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.ai.sdkAuth.body',\n 'The agent does not have a credential configured for this provider. Pick a provider and save one to continue the conversation.'\n )}\n </p>\n <AIProviderCredentialsCard agent={agentRef} initialProviderId={initialProviderId} />\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AgentManagerChatPane.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerChatPane.tsx"],"sourcesContent":["/**\n * Main chat interface for a single Agent Manager session.\n *\n * Renders message list + input area, manages the send/receive flow,\n * fetches existing conversation logs on mount, and provides\n * auto-scroll, cancel, and in-session search support.\n */\n\nimport { useEffect, useRef, useState, useCallback, useMemo, type FC } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Bookmark, PackagePlus, Search, X } from 'lucide-react';\nimport { useAgentManagerStore } from '@/store/agentManagerStore';\nimport { useAgentManagerStreaming } from '@/hooks/useAgentManagerStreaming';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { fetchSessionLogs } from '@/services/aiApi';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport { useToggleAgentBookmark } from '@/hooks/useToggleAgentBookmark';\nimport { AgentManagerMessageList } from './AgentManagerMessageList';\nimport { AgentManagerInputArea } from './AgentManagerInputArea';\nimport { AgentManagerBookmarksDrawer } from './AgentManagerBookmarksDrawer';\nimport { PluginInstallModal } from './PluginInstallModal';\nimport { AIProviderCredentialsCard } from '@/components/ai/AIProviderCredentialsCard';\nimport type { AgentManagerMessage } from '@/types/agentManager';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AgentManagerChatPaneProps {\n sessionId: string;\n agentId: string;\n}\n\nexport const AgentManagerChatPane: FC<AgentManagerChatPaneProps> = ({ sessionId, agentId }) => {\n const [loadingLogs, setLoadingLogs] = useState(false);\n const [searchOpen, setSearchOpen] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n // Provider-install modal triggered from the header. Lets users add a\n // claude/codex/gemini/opencode plugin without leaving the conversation.\n const [installModalOpen, setInstallModalOpen] = useState(false);\n const [bookmarksDrawerOpen, setBookmarksDrawerOpen] = useState(false);\n const [flashMessageId, setFlashMessageId] = useState<string | null>(null);\n const [searchParams, setSearchParams] = useSearchParams();\n const pendingScrollMessageId = searchParams.get('messageId');\n const consumedScrollTargetRef = useRef<string | null>(null);\n const tr = useTr();\n\n const { agents } = useCachedInitialData();\n const activeAgent = useMemo(\n () => agents.find((a) => a.id === agentId) ?? null,\n [agents, agentId]\n );\n\n const sessionState = useAgentManagerStore((s) => s.sessionStates[sessionId]);\n const addMessage = useAgentManagerStore((s) => s.addMessage);\n\n const messages = useMemo(() => sessionState?.messages ?? [], [sessionState?.messages]);\n\n const { active: activeProfile } = useActiveAgentProfile(activeAgent?.id ?? null);\n const agentRef = useMemo(\n () => ({ id: agentId, profile: activeProfile }),\n [agentId, activeProfile]\n );\n\n const { sendMessage, cancelStream, isStreaming } = useAgentManagerStreaming({\n agent: agentRef,\n sessionId,\n });\n\n const { bookmarkedIds, toggle: toggleBookmark } = useToggleAgentBookmark(sessionId);\n\n const jumpToMessage = useCallback((messageId: string) => {\n const el = document.querySelector(`[data-message-id=\"${messageId}\"]`);\n if (!el) return;\n el.scrollIntoView({ behavior: 'smooth', block: 'center' });\n setFlashMessageId(messageId);\n window.setTimeout(() => {\n setFlashMessageId((current) => (current === messageId ? null : current));\n }, 1500);\n }, []);\n\n const handleToggleBookmark = useCallback(\n (messageId: string) => {\n void toggleBookmark({ agentId, sessionId, messageId });\n },\n [toggleBookmark, agentId, sessionId]\n );\n\n const toggleSearch = useCallback(() => {\n setSearchOpen((prev) => {\n if (prev) setSearchQuery('');\n return !prev;\n });\n }, []);\n\n // Fetch existing conversation logs on mount\n useEffect(() => {\n let cancelled = false;\n\n const loadExistingLogs = async () => {\n // Only load if session has no messages yet\n if (messages.length > 0) return;\n\n setLoadingLogs(true);\n try {\n const logs = await fetchSessionLogs(agentRef, sessionId, {\n types: ['input', 'output'],\n });\n\n if (cancelled || logs.length === 0) return;\n\n const seenLogKeys = new Set<string>();\n\n // Convert session logs to AgentManagerMessage format. The core AI\n // route owns logging, but older provider builds also wrote the same\n // input/output records; collapse same-second duplicates on restore.\n for (const log of logs) {\n const content = log.message || log.content || '';\n const timestamp = log.timestamp || log.createdAt;\n const timestampSecond = timestamp ? Math.floor(new Date(timestamp).getTime() / 1000) : 0;\n const logKey = [log.type, content, log.model ?? '', timestampSecond].join('\\u0000');\n if (seenLogKeys.has(logKey)) continue;\n seenLogKeys.add(logKey);\n\n const msg: AgentManagerMessage = {\n id: log.id,\n role: log.type === 'input' ? 'user' : 'assistant',\n content,\n timestamp,\n tokens: log.tokenCount ? { input: 0, output: log.tokenCount } : undefined,\n model: log.model,\n };\n addMessage(sessionId, msg);\n }\n } catch {\n // Silent fail -- session may not have logs yet\n } finally {\n if (!cancelled) setLoadingLogs(false);\n }\n };\n\n void loadExistingLogs();\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- only run on mount\n }, [agentId, sessionId]);\n\n // Auto-scroll to a message linked via ?messageId=... (from drawer / cross-session page).\n // Waits for logs to finish loading and the target to be in the DOM before scrolling.\n useEffect(() => {\n if (!pendingScrollMessageId || loadingLogs) return;\n if (consumedScrollTargetRef.current === pendingScrollMessageId) return;\n\n const exists = messages.some((m) => m.id === pendingScrollMessageId);\n if (!exists) return;\n\n consumedScrollTargetRef.current = pendingScrollMessageId;\n jumpToMessage(pendingScrollMessageId);\n\n // Strip the query param so a manual refresh doesn't re-trigger the flash.\n const next = new URLSearchParams(searchParams);\n next.delete('messageId');\n setSearchParams(next, { replace: true });\n }, [pendingScrollMessageId, loadingLogs, messages, jumpToMessage, searchParams, setSearchParams]);\n\n return (\n <div className=\"flex flex-col h-full min-h-0\" data-testid=\"agent-manager-chat-pane\">\n {/* In-session search bar.\n Layout: [search controls (flex-1)] ... [Manage AI providers].\n The search input expands inside the LEFT cluster only, so the\n Manage button stays anchored on the right and never shifts. */}\n <div className=\"flex items-center gap-1 px-3 py-1 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <div className=\"flex items-center gap-1.5 min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={toggleSearch}\n className={`p-1 rounded transition-colors shrink-0 ${\n searchOpen\n ? 'bg-action-primary-bg/10 text-action-primary-text'\n : 'text-text-tertiary hover:text-text-secondary hover:bg-bg-elevated'\n }`}\n title={tr(\n 'vibecontrols.agentManager.chatPane.searchInSession',\n 'Search in session (Ctrl+F)'\n )}\n aria-label={tr(\n 'vibecontrols.agentManager.chatPane.searchInSession',\n 'Search in session'\n )}\n >\n <Search className=\"w-3.5 h-3.5\" />\n </button>\n {searchOpen && (\n <div className=\"flex items-center gap-1.5 flex-1 min-w-0\">\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder={tr(\n 'vibecontrols.agentManager.chatPane.searchPlaceholder',\n 'Search in conversation...'\n )}\n autoFocus\n className=\"flex-1 min-w-0 bg-transparent text-xs text-text-primary placeholder:text-text-tertiary focus:outline-none\"\n />\n {searchQuery && (\n <button\n type=\"button\"\n onClick={() => setSearchQuery('')}\n className=\"p-0.5 rounded hover:bg-bg-elevated transition-colors\"\n >\n <X className=\"w-3 h-3 text-text-tertiary\" />\n </button>\n )}\n </div>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => setBookmarksDrawerOpen(true)}\n className=\"shrink-0 p-1 rounded transition-colors text-text-tertiary hover:text-text-secondary hover:bg-bg-elevated\"\n title={tr(\n 'vibecontrols.agentManager.chatPane.bookmarksTooltip',\n 'View bookmarked messages'\n )}\n aria-label={tr('vibecontrols.agentManager.chatPane.bookmarksLabel', 'Bookmarks')}\n data-testid=\"agent-manager-bookmarks-trigger\"\n >\n <Bookmark className=\"w-3.5 h-3.5\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setInstallModalOpen(true)}\n className=\"shrink-0 inline-flex items-center gap-1.5 rounded-md border border-border-default bg-bg-elevated px-2 py-1 text-xs font-medium text-text-primary transition-colors hover:bg-bg-sunken hover:border-action-primary-bg/40\"\n title={tr(\n 'vibecontrols.agentManager.chatPane.manageProvidersTooltip',\n 'Install or remove AI providers (Claude, Codex, Gemini, OpenCode)'\n )}\n aria-label={tr(\n 'vibecontrols.agentManager.chatPane.manageProviders',\n 'Manage AI providers'\n )}\n data-testid=\"agent-manager-manage-providers\"\n >\n <PackagePlus className=\"w-3.5 h-3.5\" />\n <span className=\"hidden sm:inline\">\n {tr('vibecontrols.agentManager.chatPane.manageProviders', 'Manage AI providers')}\n </span>\n </button>\n </div>\n\n {/* Message list */}\n {loadingLogs ? (\n <div className=\"flex-1 flex items-center justify-center\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.agentManager.chatPane.loadingConversation',\n 'Loading conversation...'\n )}\n </p>\n </div>\n ) : (\n <AgentManagerMessageList\n messages={messages}\n isStreaming={isStreaming}\n onCancelStream={cancelStream}\n searchQuery={searchQuery}\n bookmarkedIds={bookmarkedIds}\n onToggleBookmark={handleToggleBookmark}\n flashMessageId={flashMessageId}\n />\n )}\n\n {/* Rich input area with controls */}\n <AgentManagerInputArea\n agentId={agentId}\n sessionId={sessionId}\n onSend={(text) => void sendMessage(text)}\n onCancel={cancelStream}\n agentTunnelUrl={activeAgent?.tunnelUrl ?? undefined}\n agentApiKey={activeAgent?.agentApiKey ?? undefined}\n />\n\n {/* Per-session bookmarks drawer */}\n <AgentManagerBookmarksDrawer\n open={bookmarksDrawerOpen}\n onClose={() => setBookmarksDrawerOpen(false)}\n sessionId={sessionId}\n messages={messages}\n onJump={jumpToMessage}\n />\n\n {/* Header-triggered provider install/remove. Sits below the chat\n tree so it can render full-screen without disturbing layout. */}\n <PluginInstallModal\n open={installModalOpen}\n agentId={agentId}\n onClose={() => setInstallModalOpen(false)}\n />\n\n {/* SDK auth prompt: opens when the streaming hook detects a missing\n ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN, so the user can save the\n credential inline instead of being told to go look elsewhere.\n The session's currently-selected SDK is forwarded so the picker\n can preselect the right provider. */}\n <SdkAuthPromptModal\n agentId={agentId}\n agentRef={agentRef}\n initialProviderId={sessionState?.sdk ?? undefined}\n />\n </div>\n );\n};\n\ninterface SdkAuthPromptModalProps {\n agentId: string;\n agentRef: { id: string; profile: string };\n initialProviderId?: string;\n}\n\nconst SdkAuthPromptModal: FC<SdkAuthPromptModalProps> = ({\n agentId,\n agentRef,\n initialProviderId,\n}) => {\n const sdkAuthPrompt = useAgentManagerStore((s) => s.sdkAuthPrompt);\n const setSdkAuthPrompt = useAgentManagerStore((s) => s.setSdkAuthPrompt);\n const tr = useTr();\n\n if (!sdkAuthPrompt || sdkAuthPrompt.agentId !== agentId) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center p-4\">\n <div\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={() => setSdkAuthPrompt(null)}\n aria-hidden\n />\n {/* Constrain height to the viewport and let the inner body scroll so the\n modal never escapes the screen no matter how many providers/fields\n render inside AIProviderCredentialsCard. The header stays sticky so\n the close button is always reachable. */}\n <div className=\"relative w-full max-w-xl max-h-[85vh] flex flex-col rounded-lg border border-border-default bg-bg-surface shadow-xl overflow-hidden\">\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <h3 className=\"text-base font-semibold text-text-primary truncate pr-2\">\n {tr('vibecontrols.ai.sdkAuth.title', 'Configure AI provider credentials')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setSdkAuthPrompt(null)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors flex-shrink-0\"\n aria-label={tr('vibecontrols.common.close', 'Close')}\n >\n <X className=\"w-5 h-5 text-text-secondary\" />\n </button>\n </div>\n <div className=\"flex-1 min-h-0 overflow-y-auto px-4 py-3 space-y-3\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.ai.sdkAuth.body',\n 'The agent does not have a credential configured for this provider. Pick a provider and save one to continue the conversation.'\n )}\n </p>\n <AIProviderCredentialsCard agent={agentRef} initialProviderId={initialProviderId} />\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import "./AnalyticsBreakdownChart.js";
|
|
2
|
+
import "./AnalyticsTimeSeriesChart.js";
|
|
1
3
|
import "./types.js";
|
|
2
4
|
import "./AnalyticsFilterBar.js";
|
|
3
5
|
import "./AnalyticsKpiGrid.js";
|
|
4
|
-
import "./AnalyticsTimeSeriesChart.js";
|
|
5
|
-
import "./AnalyticsBreakdownChart.js";
|
|
6
6
|
import "./AnalyticsBreakdownTable.js";
|
|
7
7
|
import "./AnalyticsEntityTabs.js";
|
|
8
8
|
import "./SandboxAnalyticsSection.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { Tooltip as t } from "../../node_modules/recharts/es6/component/Tooltip.js";
|
|
3
|
+
import { ResponsiveContainer as n } from "../../node_modules/recharts/es6/component/ResponsiveContainer.js";
|
|
4
|
+
import { Area as r } from "../../node_modules/recharts/es6/cartesian/Area.js";
|
|
5
|
+
import { AreaChart as i } from "../../node_modules/recharts/es6/chart/AreaChart.js";
|
|
6
|
+
import { formatDuration as a } from "../../utils/relativeTime.js";
|
|
7
7
|
import { useMemo as o } from "react";
|
|
8
8
|
import { Timer as s } from "lucide-react";
|
|
9
9
|
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
@@ -35,7 +35,7 @@ var u = "var(--color-text-link)", d = ({ averageDuration: d }) => {
|
|
|
35
35
|
className: "mb-3",
|
|
36
36
|
children: [/* @__PURE__ */ c("p", {
|
|
37
37
|
className: "text-3xl font-bold text-text-primary",
|
|
38
|
-
children:
|
|
38
|
+
children: a(d)
|
|
39
39
|
}), /* @__PURE__ */ c("p", {
|
|
40
40
|
className: "text-xs text-text-secondary mt-0.5",
|
|
41
41
|
children: f("vibecontrols.gitops.ciTab.perPipelineRun", "per pipeline run")
|
|
@@ -43,10 +43,10 @@ var u = "var(--color-text-link)", d = ({ averageDuration: d }) => {
|
|
|
43
43
|
}),
|
|
44
44
|
/* @__PURE__ */ c("div", {
|
|
45
45
|
className: "h-16",
|
|
46
|
-
children: /* @__PURE__ */ c(
|
|
46
|
+
children: /* @__PURE__ */ c(n, {
|
|
47
47
|
width: "100%",
|
|
48
48
|
height: "100%",
|
|
49
|
-
children: /* @__PURE__ */ l(
|
|
49
|
+
children: /* @__PURE__ */ l(i, {
|
|
50
50
|
data: p,
|
|
51
51
|
margin: {
|
|
52
52
|
top: 0,
|
|
@@ -71,17 +71,17 @@ var u = "var(--color-text-link)", d = ({ averageDuration: d }) => {
|
|
|
71
71
|
stopOpacity: 0
|
|
72
72
|
})]
|
|
73
73
|
}) }),
|
|
74
|
-
/* @__PURE__ */ c(
|
|
74
|
+
/* @__PURE__ */ c(t, {
|
|
75
75
|
contentStyle: {
|
|
76
76
|
background: "var(--color-bg-surface)",
|
|
77
77
|
border: "1px solid var(--color-border-default)",
|
|
78
78
|
borderRadius: "6px",
|
|
79
79
|
fontSize: "11px"
|
|
80
80
|
},
|
|
81
|
-
formatter: (e) => [
|
|
81
|
+
formatter: (e) => [a(Math.round(e)), "Duration"],
|
|
82
82
|
labelFormatter: () => ""
|
|
83
83
|
}),
|
|
84
|
-
/* @__PURE__ */ c(
|
|
84
|
+
/* @__PURE__ */ c(r, {
|
|
85
85
|
type: "monotone",
|
|
86
86
|
dataKey: "v",
|
|
87
87
|
stroke: u,
|
package/dist/pages/AgentsPage.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useAddAgentFromTunnelMutation as e, useAgentHeartbeatMutation as t, useCreateAgentMutation as n, useCreateTargetMutation as r, useDeactivateAgentMutation as i, useDeleteAgentMutation as ee, useListAgentsQuery as te, useStartVibecontrolsAgentMutation as ne, useStopVibecontrolsAgentMutation as re, useUpdateAgentMutation as ie, useVibecontrolsAgentHealthProbeStreamSubscription as ae,
|
|
1
|
+
import { useAddAgentFromTunnelMutation as e, useAgentHeartbeatMutation as t, useCreateAgentMutation as n, useCreateTargetMutation as r, useDeactivateAgentMutation as i, useDeleteAgentMutation as ee, useListAgentsQuery as te, useStartVibecontrolsAgentMutation as ne, useStopVibecontrolsAgentMutation as re, useUpdateAgentMutation as ie, useVibecontrolsAgentHealthProbeStreamSubscription as ae, useVibecontrolsAgentHealthProbesLazyQuery as oe } from "../generated/wspace-operations.js";
|
|
2
2
|
import { useVibeControls as se, useVibeNavigate as ce } from "../providers/VibeControlsProvider.js";
|
|
3
3
|
import { useVibeControlsStore as a } from "../store/vibeControlsStore.js";
|
|
4
4
|
import le from "../hooks/useCachedInitialData.js";
|
|
@@ -122,7 +122,7 @@ function y() {
|
|
|
122
122
|
At,
|
|
123
123
|
wt,
|
|
124
124
|
b
|
|
125
|
-
]), { data: Mt, loading: G, error:
|
|
125
|
+
]), { data: Mt, loading: G, error: Nt, refetch: K } = te({
|
|
126
126
|
variables: {
|
|
127
127
|
filter: {
|
|
128
128
|
isActive: w === "ACTIVE" ? !0 : w === "OFFLINE" ? !1 : void 0,
|
|
@@ -132,20 +132,18 @@ function y() {
|
|
|
132
132
|
},
|
|
133
133
|
fetchPolicy: "cache-first",
|
|
134
134
|
skip: !b
|
|
135
|
-
}),
|
|
136
|
-
fetchPolicy: "
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}), { data: Y } = ae({ skip: !b }), Nt = J.data === void 0 && Y === void 0, X = c(() => {
|
|
141
|
-
let e = /* @__PURE__ */ new Map(), t = Y?.vibecontrolsAgentHealthProbeStream ?? J.data?.vibecontrolsAgentHealthProbes ?? [];
|
|
135
|
+
}), [Pt, q] = oe({
|
|
136
|
+
fetchPolicy: "network-only",
|
|
137
|
+
notifyOnNetworkStatusChange: !0
|
|
138
|
+
}), { data: J } = ae({ skip: !b }), Ft = q.data === void 0 && J === void 0, Y = c(() => {
|
|
139
|
+
let e = /* @__PURE__ */ new Map(), t = J?.vibecontrolsAgentHealthProbeStream ?? q.data?.vibecontrolsAgentHealthProbes ?? [];
|
|
142
140
|
for (let n of t) e.set(n.agentId, {
|
|
143
141
|
agentId: n.agentId,
|
|
144
142
|
healthy: n.healthy,
|
|
145
143
|
errorMessage: n.errorMessage
|
|
146
144
|
});
|
|
147
145
|
return e;
|
|
148
|
-
}, [
|
|
146
|
+
}, [q.data, J]), X = c(() => (Mt?.vibecontrolsAgents ?? []).map((e) => ({
|
|
149
147
|
id: e.id,
|
|
150
148
|
workspaceId: e.workspaceId,
|
|
151
149
|
name: e.name,
|
|
@@ -158,8 +156,8 @@ function y() {
|
|
|
158
156
|
id: e.id,
|
|
159
157
|
isActive: e.isActive,
|
|
160
158
|
metadata: e.metadata
|
|
161
|
-
},
|
|
162
|
-
healthError:
|
|
159
|
+
}, Y, { probesLoading: Ft }),
|
|
160
|
+
healthError: Y.get(e.id)?.errorMessage ?? (e.lastGatewayAuthPushStatus === "failed" ? e.lastGatewayAuthPushError ?? "Gateway-auth push failed" : null),
|
|
163
161
|
lastHeartbeat: e.lastHeartbeat || null,
|
|
164
162
|
tunnelUrl: e.tunnelUrl ?? null,
|
|
165
163
|
tunnelStatus: e.tunnelStatus ?? null,
|
|
@@ -184,8 +182,8 @@ function y() {
|
|
|
184
182
|
}))
|
|
185
183
|
})), [
|
|
186
184
|
Mt?.vibecontrolsAgents,
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
Y,
|
|
186
|
+
Ft,
|
|
189
187
|
at
|
|
190
188
|
]);
|
|
191
189
|
s(() => {
|
|
@@ -194,46 +192,46 @@ function y() {
|
|
|
194
192
|
let t = Date.now(), n = Infinity;
|
|
195
193
|
for (let r of e) {
|
|
196
194
|
let e = j[r];
|
|
197
|
-
e !== void 0 && (
|
|
195
|
+
e !== void 0 && (Y.get(r)?.healthy === !0 && t >= e ? M(r) : e < n && (n = e));
|
|
198
196
|
}
|
|
199
197
|
if (n === Infinity) return;
|
|
200
198
|
let r = Math.max(0, n - t) + 50, i = setTimeout(() => {
|
|
201
199
|
let e = a.getState().connectingAgents;
|
|
202
200
|
for (let t of Object.keys(e)) {
|
|
203
201
|
let n = e[t];
|
|
204
|
-
n !== void 0 &&
|
|
202
|
+
n !== void 0 && Y.get(t)?.healthy === !0 && Date.now() >= n && M(t);
|
|
205
203
|
}
|
|
206
204
|
}, r);
|
|
207
205
|
return () => clearTimeout(i);
|
|
208
206
|
}, [
|
|
209
|
-
|
|
207
|
+
Y,
|
|
210
208
|
j,
|
|
211
209
|
M
|
|
212
210
|
]);
|
|
213
|
-
let
|
|
211
|
+
let Z = c(() => X.filter((e) => {
|
|
214
212
|
if (C) {
|
|
215
213
|
let t = C.toLowerCase();
|
|
216
214
|
if (!(e.name.toLowerCase().includes(t) || e.hostname?.toLowerCase().includes(t) || e.id.toLowerCase().includes(t))) return !1;
|
|
217
215
|
}
|
|
218
216
|
return !(E !== "all" && !(e.tagAssignments ?? []).some((e) => e.tagId === E));
|
|
219
217
|
}), [
|
|
220
|
-
|
|
218
|
+
X,
|
|
221
219
|
C,
|
|
222
220
|
E
|
|
223
221
|
]);
|
|
224
222
|
s(() => () => {
|
|
225
223
|
H.current && clearTimeout(H.current);
|
|
226
224
|
}, []);
|
|
227
|
-
let
|
|
225
|
+
let It = (e) => {
|
|
228
226
|
x(`/agents/${e.id}`), y?.({
|
|
229
227
|
id: e.id,
|
|
230
228
|
name: e.name,
|
|
231
229
|
type: e.platform || "unknown",
|
|
232
230
|
status: e.status || (e.isActive ? "ACTIVE" : "OFFLINE")
|
|
233
231
|
});
|
|
234
|
-
},
|
|
232
|
+
}, Q = () => {
|
|
235
233
|
O(!0);
|
|
236
|
-
},
|
|
234
|
+
}, Lt = async (e) => {
|
|
237
235
|
try {
|
|
238
236
|
tt(!0);
|
|
239
237
|
let t = e.tunnelUrl?.trim(), n = e.agentApiKey?.trim();
|
|
@@ -271,7 +269,7 @@ function y() {
|
|
|
271
269
|
} finally {
|
|
272
270
|
tt(!1);
|
|
273
271
|
}
|
|
274
|
-
},
|
|
272
|
+
}, Rt = async (e) => {
|
|
275
273
|
try {
|
|
276
274
|
let t = (await bt({ variables: { agentId: e.id } })).data?.startVibecontrolsAgent;
|
|
277
275
|
if (!t?.success) throw Error(t?.error || "Failed to start agent");
|
|
@@ -279,7 +277,7 @@ function y() {
|
|
|
279
277
|
} catch (e) {
|
|
280
278
|
console.error("Failed to start agent:", e), W(e instanceof Error ? e.message : "Failed to start agent");
|
|
281
279
|
}
|
|
282
|
-
},
|
|
280
|
+
}, zt = async (e) => {
|
|
283
281
|
try {
|
|
284
282
|
let t = (await xt({ variables: { agentId: e.id } })).data?.stopVibecontrolsAgent;
|
|
285
283
|
if (!t?.success) throw Error(t?.error || "Failed to stop agent");
|
|
@@ -287,9 +285,9 @@ function y() {
|
|
|
287
285
|
} catch (e) {
|
|
288
286
|
console.error("Failed to stop agent:", e), W(e instanceof Error ? e.message : "Failed to stop agent");
|
|
289
287
|
}
|
|
290
|
-
},
|
|
288
|
+
}, $ = (e) => {
|
|
291
289
|
P(e);
|
|
292
|
-
},
|
|
290
|
+
}, Bt = async () => {
|
|
293
291
|
if (!N) return;
|
|
294
292
|
let e = N;
|
|
295
293
|
I(!0);
|
|
@@ -299,7 +297,7 @@ function y() {
|
|
|
299
297
|
refetchQueries: []
|
|
300
298
|
});
|
|
301
299
|
} catch (e) {
|
|
302
|
-
console.error("Failed to deactivate agent:", e), W(e instanceof Error ? e.message : "Failed to disconnect agent"), I(!1), P(null), await
|
|
300
|
+
console.error("Failed to deactivate agent:", e), W(e instanceof Error ? e.message : "Failed to disconnect agent"), I(!1), P(null), await K();
|
|
303
301
|
return;
|
|
304
302
|
}
|
|
305
303
|
try {
|
|
@@ -317,12 +315,12 @@ function y() {
|
|
|
317
315
|
} catch (e) {
|
|
318
316
|
console.error("Failed to clear tunnel fields:", e), W("Agent marked offline, but failed to clear connection fields — clear them manually in Settings.");
|
|
319
317
|
}
|
|
320
|
-
I(!1), P(null), await
|
|
321
|
-
}, Bt = (e) => {
|
|
322
|
-
x(`/agents/${e.id}`);
|
|
318
|
+
I(!1), P(null), await K();
|
|
323
319
|
}, Vt = (e) => {
|
|
320
|
+
x(`/agents/${e.id}`);
|
|
321
|
+
}, Ht = (e) => {
|
|
324
322
|
A(e);
|
|
325
|
-
},
|
|
323
|
+
}, Ut = async () => {
|
|
326
324
|
if (!k) return;
|
|
327
325
|
let e = k.id, t = st.current?.value?.trim() || "", n = ct.current?.value?.trim() || "";
|
|
328
326
|
try {
|
|
@@ -342,16 +340,16 @@ function y() {
|
|
|
342
340
|
} finally {
|
|
343
341
|
rt(!1);
|
|
344
342
|
}
|
|
345
|
-
},
|
|
343
|
+
}, Wt = o((e) => {
|
|
346
344
|
R((t) => {
|
|
347
345
|
let n = new Set(t);
|
|
348
346
|
return n.has(e) ? n.delete(e) : n.add(e), n;
|
|
349
347
|
});
|
|
350
|
-
}, []),
|
|
351
|
-
R(new Set(
|
|
352
|
-
}, [
|
|
348
|
+
}, []), Gt = o(() => {
|
|
349
|
+
R(new Set(Z.map((e) => e.id)));
|
|
350
|
+
}, [Z]), Kt = o(() => {
|
|
353
351
|
R(/* @__PURE__ */ new Set());
|
|
354
|
-
}, []),
|
|
352
|
+
}, []), qt = Z.length > 0 && L.size === Z.length, Jt = L.size > 0, Yt = o(async () => {
|
|
355
353
|
if (L.size !== 0 && window.confirm(S("agentsPage.bulkDeleteConfirm", "Are you sure you want to remove {{count}} agent(s)? This cannot be undone.", { count: L.size }))) {
|
|
356
354
|
ut(!0);
|
|
357
355
|
try {
|
|
@@ -368,10 +366,10 @@ function y() {
|
|
|
368
366
|
kt,
|
|
369
367
|
W,
|
|
370
368
|
U
|
|
371
|
-
]),
|
|
372
|
-
total:
|
|
373
|
-
active:
|
|
374
|
-
offline:
|
|
369
|
+
]), Xt = {
|
|
370
|
+
total: X.length,
|
|
371
|
+
active: X.filter((e) => e.isActive).length,
|
|
372
|
+
offline: X.filter((e) => !e.isActive).length
|
|
375
373
|
};
|
|
376
374
|
return He ? /* @__PURE__ */ m(v, {
|
|
377
375
|
className: "space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen",
|
|
@@ -382,12 +380,12 @@ function y() {
|
|
|
382
380
|
title: S("agentsPage.accessDeniedTitle", "Access Denied"),
|
|
383
381
|
description: S("agentsPage.accessDeniedDescription", "You don't have permission to view agents. Contact your workspace administrator for access.")
|
|
384
382
|
})
|
|
385
|
-
}) :
|
|
383
|
+
}) : Nt && X.length === 0 ? /* @__PURE__ */ m(v, {
|
|
386
384
|
className: "space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen",
|
|
387
385
|
children: /* @__PURE__ */ m(fe, {
|
|
388
386
|
title: S("agentsPage.failedLoad", "Failed to load agents"),
|
|
389
|
-
message:
|
|
390
|
-
onRetry: () =>
|
|
387
|
+
message: Nt.message,
|
|
388
|
+
onRetry: () => K()
|
|
391
389
|
})
|
|
392
390
|
}) : /* @__PURE__ */ h(v, {
|
|
393
391
|
className: "space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen",
|
|
@@ -403,40 +401,40 @@ function y() {
|
|
|
403
401
|
/* @__PURE__ */ m(ue, {
|
|
404
402
|
title: S("agentsPage.title", "Agents"),
|
|
405
403
|
description: S("agentsPage.descriptionOnline", "{{active}} of {{total}} agents online", {
|
|
406
|
-
active:
|
|
407
|
-
total:
|
|
404
|
+
active: Xt.active,
|
|
405
|
+
total: Xt.total
|
|
408
406
|
}),
|
|
409
407
|
actions: /* @__PURE__ */ h("div", {
|
|
410
408
|
className: "flex w-full flex-col items-end gap-2 sm:w-auto sm:flex-row sm:items-center",
|
|
411
409
|
children: [/* @__PURE__ */ m("button", {
|
|
412
410
|
onClick: () => {
|
|
413
|
-
|
|
411
|
+
K(), Pt();
|
|
414
412
|
},
|
|
415
|
-
disabled: G ||
|
|
413
|
+
disabled: G || q.loading,
|
|
416
414
|
className: "flex items-center justify-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50",
|
|
417
415
|
title: S("agentsPage.refreshTitle", "Refresh agents"),
|
|
418
|
-
children: /* @__PURE__ */ m(Pe, { className: `w-4 h-4 ${G ||
|
|
416
|
+
children: /* @__PURE__ */ m(Pe, { className: `w-4 h-4 ${G || q.loading ? "animate-spin" : ""}` })
|
|
419
417
|
}), Dt ? /* @__PURE__ */ m(Be, {
|
|
420
418
|
permission: Ce.AGENT_CREATE,
|
|
421
419
|
deniedBehavior: "disable",
|
|
422
420
|
deniedTooltip: S("agentsPage.deniedCreateTooltip", "You don't have permission to create agents"),
|
|
423
421
|
children: /* @__PURE__ */ h("button", {
|
|
424
|
-
onClick:
|
|
422
|
+
onClick: Q,
|
|
425
423
|
className: "flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
426
424
|
children: [/* @__PURE__ */ m(d, { className: "w-4 h-4" }), S("agentsPage.addAgent", "Add Agent")]
|
|
427
425
|
})
|
|
428
426
|
}) : /* @__PURE__ */ h("button", {
|
|
429
|
-
onClick:
|
|
427
|
+
onClick: Q,
|
|
430
428
|
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",
|
|
431
429
|
children: [/* @__PURE__ */ m(d, { className: "w-4 h-4" }), S("agentsPage.addAgent", "Add Agent")]
|
|
432
430
|
})]
|
|
433
431
|
})
|
|
434
432
|
}),
|
|
435
|
-
|
|
433
|
+
X.length === 0 && !G && /* @__PURE__ */ m(be, {
|
|
436
434
|
variant: "full",
|
|
437
435
|
dismissKey: "vibecontrols-agents-setup-banner"
|
|
438
436
|
}),
|
|
439
|
-
|
|
437
|
+
X.length > 0 && /* @__PURE__ */ m(be, {
|
|
440
438
|
variant: "compact",
|
|
441
439
|
dismissKey: "vibecontrols-agents-compact-banner"
|
|
442
440
|
}),
|
|
@@ -490,18 +488,18 @@ function y() {
|
|
|
490
488
|
]
|
|
491
489
|
})]
|
|
492
490
|
}),
|
|
493
|
-
|
|
491
|
+
Z.length > 0 && /* @__PURE__ */ m("div", {
|
|
494
492
|
className: "flex items-center gap-2 flex-wrap",
|
|
495
493
|
children: /* @__PURE__ */ m("div", {
|
|
496
494
|
className: "ml-auto flex items-center gap-2",
|
|
497
495
|
children: /* @__PURE__ */ h("button", {
|
|
498
|
-
onClick:
|
|
496
|
+
onClick: qt ? Kt : Gt,
|
|
499
497
|
className: "flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors",
|
|
500
|
-
children: [/* @__PURE__ */ m(je, { className: "w-3.5 h-3.5" }),
|
|
498
|
+
children: [/* @__PURE__ */ m(je, { className: "w-3.5 h-3.5" }), qt ? S("agentsPage.deselectAll", "Deselect All") : S("agentsPage.selectAll", "Select All")]
|
|
501
499
|
})
|
|
502
500
|
})
|
|
503
501
|
}),
|
|
504
|
-
|
|
502
|
+
Jt && /* @__PURE__ */ h("div", {
|
|
505
503
|
className: "flex flex-col sm:flex-row items-start sm:items-center gap-3 px-4 py-2 bg-action-primary-bg/10 border border-action-primary-bg/20 rounded-lg",
|
|
506
504
|
children: [/* @__PURE__ */ m("span", {
|
|
507
505
|
className: "text-sm font-medium text-text-primary",
|
|
@@ -509,7 +507,7 @@ function y() {
|
|
|
509
507
|
}), /* @__PURE__ */ h("div", {
|
|
510
508
|
className: "flex items-center gap-1.5 sm:ml-auto",
|
|
511
509
|
children: [/* @__PURE__ */ h("button", {
|
|
512
|
-
onClick:
|
|
510
|
+
onClick: Yt,
|
|
513
511
|
disabled: lt,
|
|
514
512
|
className: "flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-status-error-text bg-status-error-bg border border-status-error-text/20 rounded-md hover:bg-status-error-bg/80 transition-colors disabled:opacity-50",
|
|
515
513
|
children: [
|
|
@@ -518,7 +516,7 @@ function y() {
|
|
|
518
516
|
S("agentsPage.remove", "Remove")
|
|
519
517
|
]
|
|
520
518
|
}), /* @__PURE__ */ h("button", {
|
|
521
|
-
onClick:
|
|
519
|
+
onClick: Kt,
|
|
522
520
|
className: "flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors",
|
|
523
521
|
children: [
|
|
524
522
|
/* @__PURE__ */ m(Ie, { className: "w-3 h-3" }),
|
|
@@ -531,31 +529,31 @@ function y() {
|
|
|
531
529
|
C || w !== "all" || T !== "all" ? /* @__PURE__ */ h("p", {
|
|
532
530
|
className: "text-sm text-text-secondary",
|
|
533
531
|
children: [S("agentsPage.showingResults", "Showing {{filtered}} of {{total}} agents", {
|
|
534
|
-
filtered:
|
|
535
|
-
total:
|
|
532
|
+
filtered: Z.length,
|
|
533
|
+
total: X.length
|
|
536
534
|
}), G && /* @__PURE__ */ m("span", {
|
|
537
535
|
className: "ml-2 text-text-muted",
|
|
538
536
|
children: S("agentsPage.refreshing", "(refreshing...)")
|
|
539
537
|
})]
|
|
540
538
|
}) : null,
|
|
541
|
-
G &&
|
|
539
|
+
G && X.length === 0 ? /* @__PURE__ */ m(we, {
|
|
542
540
|
variant: "rows",
|
|
543
541
|
count: 6
|
|
544
|
-
}) :
|
|
542
|
+
}) : Z.length > 0 ? /* @__PURE__ */ m(f, {
|
|
545
543
|
mode: "wait",
|
|
546
544
|
children: D === "grid" ? /* @__PURE__ */ m(_, {
|
|
547
545
|
stagger: .04,
|
|
548
546
|
className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",
|
|
549
|
-
children:
|
|
547
|
+
children: Z.map((e) => /* @__PURE__ */ m(g, { children: /* @__PURE__ */ m(Te, {
|
|
550
548
|
agent: e,
|
|
551
|
-
onSelect:
|
|
552
|
-
onStart:
|
|
553
|
-
onStop:
|
|
554
|
-
onDisconnect:
|
|
555
|
-
onConfigure:
|
|
556
|
-
onEditTunnelUrl:
|
|
549
|
+
onSelect: It,
|
|
550
|
+
onStart: Rt,
|
|
551
|
+
onStop: zt,
|
|
552
|
+
onDisconnect: $,
|
|
553
|
+
onConfigure: Vt,
|
|
554
|
+
onEditTunnelUrl: Ht,
|
|
557
555
|
selected: L.has(e.id),
|
|
558
|
-
onToggleSelect: () =>
|
|
556
|
+
onToggleSelect: () => Wt(e.id),
|
|
559
557
|
healthErrorMessage: e.healthError ?? null
|
|
560
558
|
}) }, e.id))
|
|
561
559
|
}, "grid-view") : /* @__PURE__ */ m(_, {
|
|
@@ -563,12 +561,12 @@ function y() {
|
|
|
563
561
|
children: /* @__PURE__ */ m(g, {
|
|
564
562
|
variant: "fadeRight",
|
|
565
563
|
children: /* @__PURE__ */ m(Ee, {
|
|
566
|
-
agents:
|
|
567
|
-
onSelect:
|
|
568
|
-
onStart:
|
|
569
|
-
onStop:
|
|
570
|
-
onDisconnect:
|
|
571
|
-
onSettings:
|
|
564
|
+
agents: Z,
|
|
565
|
+
onSelect: It,
|
|
566
|
+
onStart: Rt,
|
|
567
|
+
onStop: zt,
|
|
568
|
+
onDisconnect: $,
|
|
569
|
+
onSettings: Vt
|
|
572
570
|
})
|
|
573
571
|
})
|
|
574
572
|
}, "list-view")
|
|
@@ -580,7 +578,7 @@ function y() {
|
|
|
580
578
|
className: "flex flex-wrap items-center gap-3",
|
|
581
579
|
children: [
|
|
582
580
|
/* @__PURE__ */ h("button", {
|
|
583
|
-
onClick:
|
|
581
|
+
onClick: Q,
|
|
584
582
|
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",
|
|
585
583
|
children: [/* @__PURE__ */ m(d, { className: "w-4 h-4" }), S("agentsPage.addAgent", "Add Agent")]
|
|
586
584
|
}),
|
|
@@ -594,7 +592,7 @@ function y() {
|
|
|
594
592
|
}) : void 0
|
|
595
593
|
}),
|
|
596
594
|
$e && /* @__PURE__ */ m(De, {
|
|
597
|
-
onSubmit:
|
|
595
|
+
onSubmit: Lt,
|
|
598
596
|
onCancel: () => O(!1),
|
|
599
597
|
isLoading: et,
|
|
600
598
|
onNavigate: (e) => {
|
|
@@ -661,7 +659,7 @@ function y() {
|
|
|
661
659
|
className: "px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50",
|
|
662
660
|
children: "Cancel"
|
|
663
661
|
}), /* @__PURE__ */ m("button", {
|
|
664
|
-
onClick:
|
|
662
|
+
onClick: Bt,
|
|
665
663
|
disabled: F,
|
|
666
664
|
className: "px-4 py-2 text-sm bg-action-danger-bg text-action-danger-text rounded-md hover:bg-action-danger-bg-hover transition-colors disabled:opacity-50",
|
|
667
665
|
children: F ? "Disconnecting..." : "Disconnect"
|
|
@@ -742,7 +740,7 @@ function y() {
|
|
|
742
740
|
className: "px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors",
|
|
743
741
|
children: S("agentsPage.cancel", "Cancel")
|
|
744
742
|
}), /* @__PURE__ */ m("button", {
|
|
745
|
-
onClick:
|
|
743
|
+
onClick: Ut,
|
|
746
744
|
disabled: nt,
|
|
747
745
|
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",
|
|
748
746
|
children: nt ? S("agentsPage.saving", "Saving...") : S("agentsPage.save", "Save")
|