@burdenoff/microfe-vibecontrols 2026.514.6 → 2026.515.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/agent-manager/AgentManagerTabBar.js.map +1 -1
- package/dist/components/ai/AIProviderCredentialsCard.js.map +1 -1
- package/dist/components/assistant/AssistantChatArea.js +4 -1
- package/dist/components/assistant/AssistantChatArea.js.map +1 -1
- package/dist/components/notes/NoteEditor.js +166 -165
- package/dist/components/notes/NoteEditor.js.map +1 -1
- package/dist/components/sessions/SessionForm.js +1 -0
- package/dist/components/vibes/VibeForm.js +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentManagerTabBar.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerTabBar.tsx"],"sourcesContent":["/**\n * Browser-style tab bar for the Agent Manager.\n *\n * Horizontal scrolling tabs with a \"+\" button to create new sessions.\n * Supports drag-to-reorder via simple index swap.\n */\n\nimport { useRef, useState, useCallback, useEffect, type FC, type DragEvent } from 'react';\nimport {\n Plus,\n Download,\n ChevronLeft,\n ChevronRight,\n Search,\n HelpCircle,\n Maximize2,\n Minimize2,\n} from 'lucide-react';\nimport { useAgentManagerStore } from '@/store/agentManagerStore';\nimport { renameSession, createSession, type AgentRef } from '@/services/aiApi';\nimport { AgentManagerTab } from './AgentManagerTab';\nimport { RenameSessionDialog } from './dialogs/RenameSessionDialog';\nimport { ExportSessionDialog } from './dialogs/ExportSessionDialog';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AgentManagerTabBarProps {\n agent: AgentRef;\n onSearch?: () => void;\n onShowShortcuts?: () => void;\n}\n\nexport const AgentManagerTabBar: FC<AgentManagerTabBarProps> = ({\n agent,\n onSearch,\n onShowShortcuts,\n}) => {\n const tabs = useAgentManagerStore((s) => s.tabs);\n const activeTabSessionId = useAgentManagerStore((s) => s.activeTabSessionId);\n const sessionStates = useAgentManagerStore((s) => s.sessionStates);\n const setActiveTab = useAgentManagerStore((s) => s.setActiveTab);\n const closeTab = useAgentManagerStore((s) => s.closeTab);\n const reorderTabs = useAgentManagerStore((s) => s.reorderTabs);\n const openSession = useAgentManagerStore((s) => s.openSession);\n const renameTab = useAgentManagerStore((s) => s.renameTab);\n const isFullscreen = useAgentManagerStore((s) => s.isFullscreen);\n const toggleFullscreen = useAgentManagerStore((s) => s.toggleFullscreen);\n const scrollRef = useRef<HTMLDivElement>(null);\n const tr = useTr();\n const [dragIndex, setDragIndex] = useState<number | null>(null);\n const [renamingSessionId, setRenamingSessionId] = useState<string | null>(null);\n const [exportOpen, setExportOpen] = useState(false);\n const [showLeftArrow, setShowLeftArrow] = useState(false);\n const [showRightArrow, setShowRightArrow] = useState(false);\n\n const checkScroll = useCallback(() => {\n const el = scrollRef.current;\n if (!el) return;\n setShowLeftArrow(el.scrollLeft > 0);\n setShowRightArrow(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);\n }, []);\n\n const scrollTabs = useCallback(\n (direction: 'left' | 'right') => {\n const el = scrollRef.current;\n if (!el) return;\n el.scrollBy({ left: direction === 'left' ? -200 : 200, behavior: 'smooth' });\n // Update arrow visibility after scroll animation\n setTimeout(checkScroll, 300);\n },\n [checkScroll]\n );\n\n useEffect(() => {\n checkScroll();\n }, [tabs.length, checkScroll]);\n\n // Re-check scroll whenever the container is resized\n useEffect(() => {\n const el = scrollRef.current;\n if (!el) return;\n const ro = new ResizeObserver(() => checkScroll());\n ro.observe(el);\n return () => ro.disconnect();\n }, [checkScroll]);\n\n const bumpSessionsRefresh = useAgentManagerStore((s) => s.bumpSessionsRefresh);\n const handleNewSession = useCallback(async () => {\n try {\n const session = await createSession(agent, {\n name: `Session ${tabs.length + 1}`,\n agentType: 'claude',\n config: { model: 'default', mode: 'cli', tags: [] },\n });\n openSession(session.id, session.name);\n bumpSessionsRefresh();\n } catch (err) {\n console.error('[AgentManagerTabBar] createSession failed:', err);\n }\n }, [agent, tabs.length, openSession, bumpSessionsRefresh]);\n\n const handleDragStart = (e: DragEvent, index: number) => {\n setDragIndex(index);\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', String(index));\n };\n\n const handleDragOver = (e: DragEvent) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n };\n\n const handleDrop = (e: DragEvent, dropIndex: number) => {\n e.preventDefault();\n if (dragIndex === null || dragIndex === dropIndex) return;\n\n const reordered = [...tabs];\n const [moved] = reordered.splice(dragIndex, 1);\n reordered.splice(dropIndex, 0, moved);\n\n // Update order values\n const updated = reordered.map((tab, i) => ({ ...tab, order: i }));\n reorderTabs(updated);\n setDragIndex(null);\n };\n\n const handleDragEnd = () => {\n setDragIndex(null);\n };\n\n const handleRename = useCallback(\n async (sessionId: string, newName: string) => {\n renameTab(sessionId, newName);\n try {\n await renameSession(agent, sessionId, newName);\n } catch {\n // Best-effort API sync -- local rename already applied\n }\n },\n [agent, renameTab]\n );\n\n const renamingTab = renamingSessionId\n ? tabs.find((t) => t.sessionId === renamingSessionId)\n : null;\n\n return (\n <div className=\"flex items-stretch bg-bg-primary border-b border-border-default w-full\">\n {showLeftArrow && (\n <button\n type=\"button\"\n onClick={() => scrollTabs('left')}\n className=\"flex-shrink-0 px-1 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-r border-border-default\"\n aria-label=\"Scroll tabs left\"\n >\n <ChevronLeft className=\"w-3.5 h-3.5\" />\n </button>\n )}\n {/* Clip wrapper hides the browser scrollbar while keeping scroll functional */}\n <div className=\"flex-1 min-w-0 overflow-hidden\">\n <div\n ref={scrollRef}\n onScroll={checkScroll}\n className=\"flex items-stretch overflow-x-auto h-full\"\n style={{ scrollbarWidth: 'none' }}\n >\n {tabs.map((tab, index) => {\n const sessionState = sessionStates[tab.sessionId];\n const isProcessing =\n sessionState?.status === 'streaming' || sessionState?.status === 'processing';\n\n return (\n <div\n key={tab.sessionId}\n draggable\n onDragStart={(e) => handleDragStart(e, index)}\n onDragOver={handleDragOver}\n onDrop={(e) => handleDrop(e, index)}\n onDragEnd={handleDragEnd}\n className={`flex-shrink-0${dragIndex === index ? ' opacity-50' : ''}`}\n >\n <AgentManagerTab\n sessionId={tab.sessionId}\n name={tab.name}\n isActive={tab.sessionId === activeTabSessionId}\n isPinned={tab.pinned}\n isProcessing={isProcessing}\n onActivate={() => setActiveTab(tab.sessionId)}\n onClose={() => closeTab(tab.sessionId)}\n onRename={setRenamingSessionId}\n />\n </div>\n );\n })}\n </div>\n </div>\n {showRightArrow && (\n <button\n type=\"button\"\n onClick={() => scrollTabs('right')}\n className=\"flex-shrink-0 px-1 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n aria-label=\"Scroll tabs right\"\n >\n <ChevronRight className=\"w-3.5 h-3.5\" />\n </button>\n )}\n\n {/* Export session button */}\n {activeTabSessionId && (\n <button\n onClick={() => setExportOpen(true)}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr('vibecontrols.agentManager.tabBar.exportSession', 'Export session')}\n aria-label={tr('vibecontrols.agentManager.tabBar.exportSession', 'Export session')}\n >\n <Download className=\"w-4 h-4\" />\n </button>\n )}\n\n {/* New session button */}\n <button\n onClick={handleNewSession}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr(\n 'vibecontrols.agentManager.tabBar.newSession',\n 'New session (Ctrl/Cmd+Shift+N)'\n )}\n aria-label={tr('vibecontrols.agentManager.tabBar.createNewSession', 'Create new session')}\n >\n <Plus className=\"w-4 h-4\" />\n </button>\n\n {/* Search sessions */}\n {onSearch && (\n <button\n onClick={onSearch}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr(\n 'vibecontrols.agentManager.tabBar.searchSessions',\n 'Search sessions (Ctrl/Cmd+Shift+K)'\n )}\n aria-label={tr('vibecontrols.agentManager.tabBar.searchSessions', 'Search sessions')}\n >\n <Search className=\"w-4 h-4\" />\n </button>\n )}\n\n {/* Keyboard shortcuts */}\n {onShowShortcuts && (\n <button\n onClick={onShowShortcuts}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr('vibecontrols.agentManager.tabBar.keyboardShortcuts', 'Keyboard shortcuts')}\n aria-label={tr(\n 'vibecontrols.agentManager.tabBar.keyboardShortcuts',\n 'Keyboard shortcuts'\n )}\n >\n <HelpCircle className=\"w-4 h-4\" />\n </button>\n )}\n\n {/* Fullscreen toggle */}\n <button\n onClick={toggleFullscreen}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={\n isFullscreen\n ? tr('vibecontrols.agentManager.tabBar.exitFullscreen', 'Exit fullscreen')\n : tr('vibecontrols.agentManager.tabBar.enterFullscreen', 'Enter fullscreen')\n }\n aria-label={\n isFullscreen\n ? tr('vibecontrols.agentManager.tabBar.exitFullscreen', 'Exit fullscreen')\n : tr('vibecontrols.agentManager.tabBar.enterFullscreen', 'Enter fullscreen')\n }\n >\n {isFullscreen ? <Minimize2 className=\"w-4 h-4\" /> : <Maximize2 className=\"w-4 h-4\" />}\n </button>\n\n {/* Export session dialog */}\n <ExportSessionDialog\n isOpen={exportOpen}\n onClose={() => setExportOpen(false)}\n sessionId={activeTabSessionId ?? ''}\n agent={agent}\n />\n\n {/* Rename session dialog */}\n <RenameSessionDialog\n isOpen={renamingSessionId !== null}\n sessionId={renamingSessionId ?? ''}\n currentName={renamingTab?.name ?? ''}\n onClose={() => setRenamingSessionId(null)}\n onRename={(sessionId, newName) => void handleRename(sessionId, newName)}\n />\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AgentManagerTabBar.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerTabBar.tsx"],"sourcesContent":["/**\n * Browser-style tab bar for the Agent Manager.\n *\n * Horizontal scrolling tabs with a \"+\" button to create new sessions.\n * Supports drag-to-reorder via simple index swap.\n */\n\nimport { useRef, useState, useCallback, useEffect, type FC, type DragEvent } from 'react';\nimport {\n Plus,\n Download,\n ChevronLeft,\n ChevronRight,\n Search,\n HelpCircle,\n Maximize2,\n Minimize2,\n} from 'lucide-react';\nimport { useAgentManagerStore } from '@/store/agentManagerStore';\nimport { renameSession, createSession, type AgentRef } from '@/services/aiApi';\nimport { AgentManagerTab } from './AgentManagerTab';\nimport { RenameSessionDialog } from './dialogs/RenameSessionDialog';\nimport { ExportSessionDialog } from './dialogs/ExportSessionDialog';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AgentManagerTabBarProps {\n agent: AgentRef;\n onSearch?: () => void;\n onShowShortcuts?: () => void;\n}\n\nexport const AgentManagerTabBar: FC<AgentManagerTabBarProps> = ({\n agent,\n onSearch,\n onShowShortcuts,\n}) => {\n const tabs = useAgentManagerStore((s) => s.tabs);\n const activeTabSessionId = useAgentManagerStore((s) => s.activeTabSessionId);\n const sessionStates = useAgentManagerStore((s) => s.sessionStates);\n const setActiveTab = useAgentManagerStore((s) => s.setActiveTab);\n const closeTab = useAgentManagerStore((s) => s.closeTab);\n const reorderTabs = useAgentManagerStore((s) => s.reorderTabs);\n const openSession = useAgentManagerStore((s) => s.openSession);\n const renameTab = useAgentManagerStore((s) => s.renameTab);\n const isFullscreen = useAgentManagerStore((s) => s.isFullscreen);\n const toggleFullscreen = useAgentManagerStore((s) => s.toggleFullscreen);\n const scrollRef = useRef<HTMLDivElement>(null);\n const tr = useTr();\n const [dragIndex, setDragIndex] = useState<number | null>(null);\n const [renamingSessionId, setRenamingSessionId] = useState<string | null>(null);\n const [exportOpen, setExportOpen] = useState(false);\n const [showLeftArrow, setShowLeftArrow] = useState(false);\n const [showRightArrow, setShowRightArrow] = useState(false);\n\n const checkScroll = useCallback(() => {\n const el = scrollRef.current;\n if (!el) return;\n setShowLeftArrow(el.scrollLeft > 0);\n setShowRightArrow(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);\n }, []);\n\n const scrollTabs = useCallback(\n (direction: 'left' | 'right') => {\n const el = scrollRef.current;\n if (!el) return;\n el.scrollBy({ left: direction === 'left' ? -200 : 200, behavior: 'smooth' });\n // Update arrow visibility after scroll animation\n setTimeout(checkScroll, 300);\n },\n [checkScroll]\n );\n\n useEffect(() => {\n checkScroll();\n }, [tabs.length, checkScroll]);\n\n // Re-check scroll whenever the container is resized\n useEffect(() => {\n const el = scrollRef.current;\n if (!el) return;\n const ro = new ResizeObserver(() => checkScroll());\n ro.observe(el);\n return () => ro.disconnect();\n }, [checkScroll]);\n\n const bumpSessionsRefresh = useAgentManagerStore((s) => s.bumpSessionsRefresh);\n const handleNewSession = useCallback(async () => {\n try {\n const session = await createSession(agent, {\n name: `Session ${tabs.length + 1}`,\n agentType: 'claude',\n config: { model: 'default', mode: 'cli', tags: [] },\n });\n openSession(session.id, session.name);\n bumpSessionsRefresh();\n } catch (err) {\n console.error('[AgentManagerTabBar] createSession failed:', err);\n }\n }, [agent, tabs.length, openSession, bumpSessionsRefresh]);\n\n const handleDragStart = (e: DragEvent, index: number) => {\n setDragIndex(index);\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', String(index));\n };\n\n const handleDragOver = (e: DragEvent) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n };\n\n const handleDrop = (e: DragEvent, dropIndex: number) => {\n e.preventDefault();\n if (dragIndex === null || dragIndex === dropIndex) return;\n\n const reordered = [...tabs];\n const [moved] = reordered.splice(dragIndex, 1);\n reordered.splice(dropIndex, 0, moved);\n\n // Update order values\n const updated = reordered.map((tab, i) => ({ ...tab, order: i }));\n reorderTabs(updated);\n setDragIndex(null);\n };\n\n const handleDragEnd = () => {\n setDragIndex(null);\n };\n\n const handleRename = useCallback(\n async (sessionId: string, newName: string) => {\n renameTab(sessionId, newName);\n try {\n await renameSession(agent, sessionId, newName);\n } catch {\n // Best-effort API sync -- local rename already applied\n }\n },\n [agent, renameTab]\n );\n\n const renamingTab = renamingSessionId\n ? tabs.find((t) => t.sessionId === renamingSessionId)\n : null;\n\n return (\n <div className=\"flex items-stretch bg-bg-primary border-b border-border-default w-full\">\n {showLeftArrow && (\n <button\n type=\"button\"\n onClick={() => scrollTabs('left')}\n className=\"flex-shrink-0 px-1 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-r border-border-default\"\n aria-label=\"Scroll tabs left\"\n >\n <ChevronLeft className=\"w-3.5 h-3.5\" />\n </button>\n )}\n {/* Clip wrapper hides the browser scrollbar while keeping scroll functional */}\n <div className=\"flex-1 min-w-0 overflow-hidden\">\n <div\n ref={scrollRef}\n onScroll={checkScroll}\n className=\"flex items-stretch overflow-x-auto h-full\"\n style={{ scrollbarWidth: 'none' }}\n >\n {tabs.map((tab, index) => {\n const sessionState = sessionStates[tab.sessionId];\n const isProcessing =\n sessionState?.status === 'streaming' || sessionState?.status === 'processing';\n\n return (\n <div\n key={tab.sessionId}\n draggable\n onDragStart={(e) => handleDragStart(e, index)}\n onDragOver={handleDragOver}\n onDrop={(e) => handleDrop(e, index)}\n onDragEnd={handleDragEnd}\n className={`flex-shrink-0${dragIndex === index ? ' opacity-50' : ''}`}\n >\n <AgentManagerTab\n sessionId={tab.sessionId}\n name={tab.name}\n isActive={tab.sessionId === activeTabSessionId}\n isPinned={tab.pinned}\n isProcessing={isProcessing}\n onActivate={() => setActiveTab(tab.sessionId)}\n onClose={() => closeTab(tab.sessionId)}\n onRename={setRenamingSessionId}\n />\n </div>\n );\n })}\n </div>\n </div>\n {showRightArrow && (\n <button\n type=\"button\"\n onClick={() => scrollTabs('right')}\n className=\"flex-shrink-0 px-1 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n aria-label=\"Scroll tabs right\"\n >\n <ChevronRight className=\"w-3.5 h-3.5\" />\n </button>\n )}\n\n {/* Export session button */}\n {activeTabSessionId && (\n <button\n onClick={() => setExportOpen(true)}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr('vibecontrols.agentManager.tabBar.exportSession', 'Export session')}\n aria-label={tr('vibecontrols.agentManager.tabBar.exportSession', 'Export session')}\n >\n <Download className=\"w-4 h-4\" />\n </button>\n )}\n\n {/* New session button */}\n <button\n onClick={handleNewSession}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr('vibecontrols.agentManager.tabBar.newSession', 'New session (Ctrl/Cmd+Shift+N)')}\n aria-label={tr('vibecontrols.agentManager.tabBar.createNewSession', 'Create new session')}\n >\n <Plus className=\"w-4 h-4\" />\n </button>\n\n {/* Search sessions */}\n {onSearch && (\n <button\n onClick={onSearch}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr(\n 'vibecontrols.agentManager.tabBar.searchSessions',\n 'Search sessions (Ctrl/Cmd+Shift+K)'\n )}\n aria-label={tr('vibecontrols.agentManager.tabBar.searchSessions', 'Search sessions')}\n >\n <Search className=\"w-4 h-4\" />\n </button>\n )}\n\n {/* Keyboard shortcuts */}\n {onShowShortcuts && (\n <button\n onClick={onShowShortcuts}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr('vibecontrols.agentManager.tabBar.keyboardShortcuts', 'Keyboard shortcuts')}\n aria-label={tr(\n 'vibecontrols.agentManager.tabBar.keyboardShortcuts',\n 'Keyboard shortcuts'\n )}\n >\n <HelpCircle className=\"w-4 h-4\" />\n </button>\n )}\n\n {/* Fullscreen toggle */}\n <button\n onClick={toggleFullscreen}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={\n isFullscreen\n ? tr('vibecontrols.agentManager.tabBar.exitFullscreen', 'Exit fullscreen')\n : tr('vibecontrols.agentManager.tabBar.enterFullscreen', 'Enter fullscreen')\n }\n aria-label={\n isFullscreen\n ? tr('vibecontrols.agentManager.tabBar.exitFullscreen', 'Exit fullscreen')\n : tr('vibecontrols.agentManager.tabBar.enterFullscreen', 'Enter fullscreen')\n }\n >\n {isFullscreen ? <Minimize2 className=\"w-4 h-4\" /> : <Maximize2 className=\"w-4 h-4\" />}\n </button>\n\n {/* Export session dialog */}\n <ExportSessionDialog\n isOpen={exportOpen}\n onClose={() => setExportOpen(false)}\n sessionId={activeTabSessionId ?? ''}\n agent={agent}\n />\n\n {/* Rename session dialog */}\n <RenameSessionDialog\n isOpen={renamingSessionId !== null}\n sessionId={renamingSessionId ?? ''}\n currentName={renamingTab?.name ?? ''}\n onClose={() => setRenamingSessionId(null)}\n onRename={(sessionId, newName) => void handleRename(sessionId, newName)}\n />\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIProviderCredentialsCard.js","names":[],"sources":["../../../src/components/ai/AIProviderCredentialsCard.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport { ArrowLeft, ChevronRight, Eye, EyeOff, KeyRound, Loader2, Save, Trash2 } from 'lucide-react';\n\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n deleteAgentConfigValue,\n getAgentConfigValue,\n setAgentConfigValue,\n type AgentRef,\n} from '@/services/aiApi';\n\n/**\n * One provider definition — what env-var-style keys the agent stores under\n * its config bag and what to call the provider in the UI. Kept as a flat\n * static list rather than discovered from the plugin manifest because the\n * config keys are part of the upstream SDK contract (ANTHROPIC_API_KEY,\n * OPENAI_API_KEY, etc.) and don't change with each plugin release.\n */\nexport interface AIProviderCredentialDef {\n /** Stable provider id — usually matches the plugin's pluginName. */\n providerId: string;\n /** Display name shown in the card heading. */\n label: string;\n /** Short helper text under the heading. */\n description: string;\n /**\n * One row per key. The `kind` distinguishes a primary credential (for\n * which the SDK refuses to start without ONE of the listed keys) from\n * an optional URL/host override.\n */\n fields: Array<{\n /** Agent-config key name, also rendered as the input label. */\n key: string;\n /**\n * Whether the field is a secret (rendered as password with eye toggle)\n * or a plain string (URL / host overrides — visible by default).\n */\n secret?: boolean;\n }>;\n}\n\n/**\n * Catalog of every AI provider whose plugin reads credentials from the\n * agent's config bag (POST /api/config). CLI-only providers (Aider, Amp,\n * Copilot, Crush, Goose, Plandex, Pi) authenticate via their own harness\n * binary (~/.claude/, ~/.config/opencode/ etc.) — Bridle handles those\n * and they don't appear here.\n */\nexport const AI_PROVIDER_CREDENTIALS: AIProviderCredentialDef[] = [\n {\n providerId: 'claude',\n label: 'Anthropic Claude',\n description:\n 'Claude SDK sessions require either ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN on the agent. Save one of them to make SDK sessions work.',\n fields: [\n { key: 'ANTHROPIC_API_KEY', secret: true },\n { key: 'ANTHROPIC_AUTH_TOKEN', secret: true },\n ],\n },\n {\n providerId: 'codex',\n label: 'OpenAI Codex',\n description:\n 'Codex SDK / CLI sessions read OPENAI_API_KEY from the agent. Set it here to enable Codex-backed sessions.',\n fields: [{ key: 'OPENAI_API_KEY', secret: true }],\n },\n {\n providerId: 'gemini',\n label: 'Google Gemini',\n description:\n 'Gemini SDK reads GEMINI_API_KEY (preferred) or GOOGLE_API_KEY. Either one works — both shown so an existing GOOGLE_API_KEY in your environment keeps working.',\n fields: [\n { key: 'GEMINI_API_KEY', secret: true },\n { key: 'GOOGLE_API_KEY', secret: true },\n ],\n },\n {\n providerId: 'openai-compat',\n label: 'OpenAI-Compatible',\n description:\n 'Generic OpenAI-compatible provider (Together, Groq, Mistral, etc.). Set the API key and the base URL the SDK should hit.',\n fields: [{ key: 'OPENAI_COMPAT_API_KEY', secret: true }, { key: 'OPENAI_COMPAT_BASE_URL' }],\n },\n {\n providerId: 'cursor',\n label: 'Cursor',\n description:\n 'Cursor agent (CLI + SDK) reads CURSOR_API_KEY from the agent. Set it here to enable both modes.',\n fields: [{ key: 'CURSOR_API_KEY', secret: true }],\n },\n {\n providerId: 'openrouter',\n label: 'OpenRouter',\n description: 'OpenRouter aggregator (300+ models, SDK only). Set OPENROUTER_API_KEY to enable.',\n fields: [{ key: 'OPENROUTER_API_KEY', secret: true }],\n },\n {\n providerId: 'minimax',\n label: 'Minimax',\n description:\n 'Minimax provider (CLI + Anthropic-compatible SDK). Set MINIMAX_API_KEY to enable.',\n fields: [{ key: 'MINIMAX_API_KEY', secret: true }],\n },\n {\n providerId: 'ollama',\n label: 'Ollama',\n description:\n 'Ollama Cloud + self-hosted (CLI + SDK). OLLAMA_API_KEY for Cloud; OLLAMA_HOST optional override (defaults: https://ollama.com when key present, http://localhost:11434 otherwise).',\n fields: [{ key: 'OLLAMA_API_KEY', secret: true }, { key: 'OLLAMA_HOST' }],\n },\n];\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\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}: 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=\"h-4 w-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=\"h-4 w-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=\"h-3.5 w-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=\"h-4 w-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 agent={agent} provider={selected} />\n </div>\n );\n}\n\nfunction ProviderCard({ agent, provider }: { agent: AgentRef; provider: AIProviderCredentialDef }) {\n const tr = useTr();\n const [values, setValues] = 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 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 setValues(Object.fromEntries(entries));\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 try {\n if (value.trim()) {\n await setAgentConfigValue(agent, key, value.trim());\n } else {\n await deleteAgentConfigValue(agent, key);\n }\n setSuccess(\n value.trim()\n ? tr('ai.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 </div>\n\n {error ? (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n {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=\"h-4 w-4 animate-spin\" />\n {tr('ai.providerCredentials.loading', 'Loading…')}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {provider.fields.map((f) => (\n <CredentialField\n key={f.key}\n label={f.key}\n secret={f.secret ?? false}\n value={values[f.key] ?? ''}\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 </div>\n )}\n </div>\n );\n}\n\ninterface CredentialFieldProps {\n label: string;\n secret: boolean;\n value: string;\n onChange: (value: string) => void;\n saving: boolean;\n visible: boolean;\n onToggleVisible: () => void;\n onSave: () => void;\n onClear: () => void;\n}\n\nfunction CredentialField({\n label,\n secret,\n value,\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 type={showAsPassword ? 'password' : 'text'}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 pr-10 text-sm text-text-primary\"\n />\n {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=\"h-4 w-4\" /> : <Eye className=\"h-4 w-4\" />}\n </button>\n ) : null}\n </div>\n <button\n type=\"button\"\n onClick={onSave}\n disabled={saving}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {saving ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Save className=\"h-4 w-4\" />}\n Save\n </button>\n <button\n type=\"button\"\n onClick={onClear}\n disabled={saving || !value}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-default px-3 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:border-border-strong disabled:opacity-50\"\n >\n <Trash2 className=\"h-4 w-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\n/**\n * One provider definition — what env-var-style keys the agent stores under\n * its config bag and what to call the provider in the UI. Kept as a flat\n * static list rather than discovered from the plugin manifest because the\n * config keys are part of the upstream SDK contract (ANTHROPIC_API_KEY,\n * OPENAI_API_KEY, etc.) and don't change with each plugin release.\n */\nexport interface AIProviderCredentialDef {\n /** Stable provider id — usually matches the plugin's pluginName. */\n providerId: string;\n /** Display name shown in the card heading. */\n label: string;\n /** Short helper text under the heading. */\n description: string;\n /**\n * One row per key. The `kind` distinguishes a primary credential (for\n * which the SDK refuses to start without ONE of the listed keys) from\n * an optional URL/host override.\n */\n fields: Array<{\n /** Agent-config key name, also rendered as the input label. */\n key: string;\n /**\n * Whether the field is a secret (rendered as password with eye toggle)\n * or a plain string (URL / host overrides — visible by default).\n */\n secret?: boolean;\n }>;\n}\n\n/**\n * Catalog of every AI provider whose plugin reads credentials from the\n * agent's config bag (POST /api/config). CLI-only providers (Aider, Amp,\n * Copilot, Crush, Goose, Plandex, Pi) authenticate via their own harness\n * binary (~/.claude/, ~/.config/opencode/ etc.) — Bridle handles those\n * and they don't appear here.\n */\nexport const AI_PROVIDER_CREDENTIALS: AIProviderCredentialDef[] = [\n {\n providerId: 'claude',\n label: 'Anthropic Claude',\n description:\n 'Claude SDK sessions require either ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN on the agent. Save one of them to make SDK sessions work.',\n fields: [\n { key: 'ANTHROPIC_API_KEY', secret: true },\n { key: 'ANTHROPIC_AUTH_TOKEN', secret: true },\n ],\n },\n {\n providerId: 'codex',\n label: 'OpenAI Codex',\n description:\n 'Codex SDK / CLI sessions read OPENAI_API_KEY from the agent. Set it here to enable Codex-backed sessions.',\n fields: [{ key: 'OPENAI_API_KEY', secret: true }],\n },\n {\n providerId: 'gemini',\n label: 'Google Gemini',\n description:\n 'Gemini SDK reads GEMINI_API_KEY (preferred) or GOOGLE_API_KEY. Either one works — both shown so an existing GOOGLE_API_KEY in your environment keeps working.',\n fields: [\n { key: 'GEMINI_API_KEY', secret: true },\n { key: 'GOOGLE_API_KEY', secret: true },\n ],\n },\n {\n providerId: 'openai-compat',\n label: 'OpenAI-Compatible',\n description:\n 'Generic OpenAI-compatible provider (Together, Groq, Mistral, etc.). Set the API key and the base URL the SDK should hit.',\n fields: [{ key: 'OPENAI_COMPAT_API_KEY', secret: true }, { key: 'OPENAI_COMPAT_BASE_URL' }],\n },\n {\n providerId: 'cursor',\n label: 'Cursor',\n description:\n 'Cursor agent (CLI + SDK) reads CURSOR_API_KEY from the agent. Set it here to enable both modes.',\n fields: [{ key: 'CURSOR_API_KEY', secret: true }],\n },\n {\n providerId: 'openrouter',\n label: 'OpenRouter',\n description: 'OpenRouter aggregator (300+ models, SDK only). Set OPENROUTER_API_KEY to enable.',\n fields: [{ key: 'OPENROUTER_API_KEY', secret: true }],\n },\n {\n providerId: 'minimax',\n label: 'Minimax',\n description:\n 'Minimax provider (CLI + Anthropic-compatible SDK). Set MINIMAX_API_KEY to enable.',\n fields: [{ key: 'MINIMAX_API_KEY', secret: true }],\n },\n {\n providerId: 'ollama',\n label: 'Ollama',\n description:\n 'Ollama Cloud + self-hosted (CLI + SDK). OLLAMA_API_KEY for Cloud; OLLAMA_HOST optional override (defaults: https://ollama.com when key present, http://localhost:11434 otherwise).',\n fields: [{ key: 'OLLAMA_API_KEY', secret: true }, { key: 'OLLAMA_HOST' }],\n },\n];\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\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}: 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=\"h-4 w-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=\"h-4 w-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=\"h-3.5 w-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=\"h-4 w-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 agent={agent} provider={selected} />\n </div>\n );\n}\n\nfunction ProviderCard({ agent, provider }: { agent: AgentRef; provider: AIProviderCredentialDef }) {\n const tr = useTr();\n const [values, setValues] = 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 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 setValues(Object.fromEntries(entries));\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 try {\n if (value.trim()) {\n await setAgentConfigValue(agent, key, value.trim());\n } else {\n await deleteAgentConfigValue(agent, key);\n }\n setSuccess(\n value.trim()\n ? tr('ai.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 </div>\n\n {error ? (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n {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=\"h-4 w-4 animate-spin\" />\n {tr('ai.providerCredentials.loading', 'Loading…')}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {provider.fields.map((f) => (\n <CredentialField\n key={f.key}\n label={f.key}\n secret={f.secret ?? false}\n value={values[f.key] ?? ''}\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 </div>\n )}\n </div>\n );\n}\n\ninterface CredentialFieldProps {\n label: string;\n secret: boolean;\n value: string;\n onChange: (value: string) => void;\n saving: boolean;\n visible: boolean;\n onToggleVisible: () => void;\n onSave: () => void;\n onClear: () => void;\n}\n\nfunction CredentialField({\n label,\n secret,\n value,\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 type={showAsPassword ? 'password' : 'text'}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 pr-10 text-sm text-text-primary\"\n />\n {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=\"h-4 w-4\" /> : <Eye className=\"h-4 w-4\" />}\n </button>\n ) : null}\n </div>\n <button\n type=\"button\"\n onClick={onSave}\n disabled={saving}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {saving ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Save className=\"h-4 w-4\" />}\n Save\n </button>\n <button\n type=\"button\"\n onClick={onClear}\n disabled={saving || !value}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-default px-3 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:border-border-strong disabled:opacity-50\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Clear\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;AAyDA,IAAa,IAAqD;CAChE;EACE,YAAY;EACZ,OAAO;EACP,aACE;EACF,QAAQ,CACN;GAAE,KAAK;GAAqB,QAAQ;GAAM,EAC1C;GAAE,KAAK;GAAwB,QAAQ;GAAM,CAC9C;EACF;CACD;EACE,YAAY;EACZ,OAAO;EACP,aACE;EACF,QAAQ,CAAC;GAAE,KAAK;GAAkB,QAAQ;GAAM,CAAC;EAClD;CACD;EACE,YAAY;EACZ,OAAO;EACP,aACE;EACF,QAAQ,CACN;GAAE,KAAK;GAAkB,QAAQ;GAAM,EACvC;GAAE,KAAK;GAAkB,QAAQ;GAAM,CACxC;EACF;CACD;EACE,YAAY;EACZ,OAAO;EACP,aACE;EACF,QAAQ,CAAC;GAAE,KAAK;GAAyB,QAAQ;GAAM,EAAE,EAAE,KAAK,0BAA0B,CAAC;EAC5F;CACD;EACE,YAAY;EACZ,OAAO;EACP,aACE;EACF,QAAQ,CAAC;GAAE,KAAK;GAAkB,QAAQ;GAAM,CAAC;EAClD;CACD;EACE,YAAY;EACZ,OAAO;EACP,aAAa;EACb,QAAQ,CAAC;GAAE,KAAK;GAAsB,QAAQ;GAAM,CAAC;EACtD;CACD;EACE,YAAY;EACZ,OAAO;EACP,aACE;EACF,QAAQ,CAAC;GAAE,KAAK;GAAmB,QAAQ;GAAM,CAAC;EACnD;CACD;EACE,YAAY;EACZ,OAAO;EACP,aACE;EACF,QAAQ,CAAC;GAAE,KAAK;GAAkB,QAAQ;GAAM,EAAE,EAAE,KAAK,eAAe,CAAC;EAC1E;CACF;AA0BD,SAAgB,EAA0B,EACxC,UACA,gBACA,wBACiC;CACjC,IAAM,IAAK,GAAO,EACZ,IAAY,IACd,EAAwB,QAAQ,MAAM,EAAY,SAAS,EAAE,WAAW,CAAC,GACzE,GAIE,CAAC,GAAY,KAAiB,QAC9B,KAAqB,EAAU,MAAM,MAAM,EAAE,eAAe,EAAkB,GACzE,IAEF,EAAU,WAAW,IAAK,EAAU,IAAI,cAAc,OAAQ,KACrE,EAEI,IAAW,EAAU,MAAM,MAAM,EAAE,eAAe,EAAW,IAAI;AA2CvE,QAzCK,IA0CH,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,EAAU,SAAS,IAClB,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAc,KAAK;KAClC,WAAU;KACV,cAAY,EAAG,+BAA+B,oBAAoB;eAJpE,CAME,kBAAC,GAAD,EAAW,WAAU,eAAgB,CAAA,EACpC,EAAG,+BAA+B,OAAO,CACnC;SACP;IACJ,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;KAC5B,CAAA;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAAoD,EAAS;MAAW,CAAA,EACtF,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,4CACA,0DACD;MACC,CAAA,CACA;;IACF;MACN,kBAAC,GAAD;GAAqB;GAAO,UAAU;GAAY,CAAA,CAC9C;MAnEJ,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;IAC5B,CAAA,EACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,oCAAoC,oBAAoB;KACzD,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,0CACA,kHACD;KACC,CAAA,CACA;MACF;MACN,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAU,KAAK,MACd,kBAAC,UAAD;IAEE,MAAK;IACL,eAAe,EAAc,EAAE,WAAW;IAC1C,WAAU;cAJZ,CAME,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAkD,EAAE;MAAY,CAAA,EAC/E,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAE,OAAO,KAAK,MAAM,EAAE,IAAI,CAAC,KAAK,KAAK;MAClC,CAAA,CACF;QACN,kBAAC,GAAD,EAAc,WAAU,6CAA8C,CAAA,CAC/D;MAZF,EAAE,WAYA,CACT;GACE,CAAA,CACF;;;AAoCZ,SAAS,EAAa,EAAE,UAAO,eAAoE;CACjG,IAAM,IAAK,GAAO,EACZ,CAAC,GAAQ,KAAa,EAAiC,EAAE,CAAC,EAC1D,CAAC,GAAS,KAAc,EAAkC,EAAE,CAAC,EAC7D,CAAC,GAAS,KAAc,EAAS,GAAK,EACtC,CAAC,GAAQ,KAAa,EAAwB,KAAK,EACnD,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAS,KAAc,EAAwB,KAAK,EAErD,IAAa,EAAY,YAAY;AAEzC,EADA,EAAW,GAAK,EAChB,EAAS,KAAK;AACd,MAAI;GACF,IAAM,IAAU,MAAM,QAAQ,IAC5B,EAAS,OAAO,IAAI,OAAO,MAAM,CAAC,EAAE,KAAM,MAAM,EAAoB,GAAO,EAAE,IAAI,IAAK,GAAG,CAAC,CAC3F;AACD,KAAU,OAAO,YAAY,EAAQ,CAAC;WAC/B,GAAK;AACZ,KACE,aAAe,QACX,EAAI,UACJ,EAAG,qCAAqC,sCAAsC,CACnF;YACO;AACR,KAAW,GAAM;;IAElB;EAAC;EAAO,EAAS;EAAQ;EAAG,CAAC;AAEhC,SAAgB;AACT,KAAY;IAChB,CAAC,EAAW,CAAC;CAEhB,IAAM,IAAe,EACnB,OAAO,GAAa,MAAkB;AAGpC,EAFA,EAAU,EAAI,EACd,EAAS,KAAK,EACd,EAAW,KAAK;AAChB,MAAI;AAMF,GALI,EAAM,MAAM,GACd,MAAM,EAAoB,GAAO,GAAK,EAAM,MAAM,CAAC,GAEnD,MAAM,EAAuB,GAAO,EAAI,EAE1C,EACE,EAAM,MAAM,GACR,EAAG,gCAAgC,GAAG,EAAI,QAAQ,GAClD,EAAG,kCAAkC,GAAG,EAAI,UAAU,CAC3D;WACM,GAAK;AACZ,KACE,aAAe,QACX,EAAI,UACJ,EAAG,qCAAqC,4BAA4B,CACzE;YACO;AACR,KAAU,KAAK;;IAGnB,CAAC,GAAO,EAAG,CACZ;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAA2C,EAAS;KAAW,CAAA,EAC7E,kBAAC,KAAD;KAAG,WAAU;eAA+B,EAAS;KAAgB,CAAA,CACjE;;GAEL,IACC,kBAAC,OAAD;IAAK,WAAU;cACZ;IACG,CAAA,GACJ;GACH,IACC,kBAAC,OAAD;IAAK,WAAU;cACZ;IACG,CAAA,GACJ;GAEH,IACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,EAC3C,EAAG,kCAAkC,WAAW,CAC7C;QAEN,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAS,OAAO,KAAK,MACpB,kBAAC,GAAD;KAEE,OAAO,EAAE;KACT,QAAQ,EAAE,UAAU;KACpB,OAAO,EAAO,EAAE,QAAQ;KACxB,WAAW,MAAM,GAAW,OAAS;MAAE,GAAG;OAAM,EAAE,MAAM;MAAG,EAAE;KAC7D,QAAQ,MAAW,EAAE;KACrB,SAAS,EAAQ,EAAE,QAAQ;KAC3B,uBAAuB,GAAY,OAAS;MAAE,GAAG;OAAM,EAAE,MAAM,CAAC,EAAI,EAAE;MAAM,EAAE;KAC9E,cAAc,KAAK,EAAa,EAAE,KAAK,EAAO,EAAE,QAAQ,GAAG;KAC3D,eAAe,KAAK,EAAa,EAAE,KAAK,GAAG;KAC3C,EAVK,EAAE,IAUP,CACF;IACE,CAAA;GAEJ;;;AAgBV,SAAS,EAAgB,EACvB,UACA,WACA,UACA,aACA,WACA,YACA,oBACA,WACA,cACuB;AAEvB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,SAAD;GAAO,WAAU;aAAiD;GAAc,CAAA,EAChF,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,SAAD;MACE,MAPa,KAAU,CAAC,IAOD,aAAa;MAC7B;MACP,WAAW,MAAU,EAAS,EAAM,OAAO,MAAM;MACjD,cAAa;MACb,YAAY;MACZ,WAAU;MACV,CAAA,EACD,IACC,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;gBAEC,EAAV,IAAW,IAAiC,GAAlC,EAAQ,WAAU,WAAY,CAA8B;MAChE,CAAA,GACP,KACA;;IACN,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,UAAU;KACV,WAAU;eAJZ,CAMG,IAAS,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAAG,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAC,OAE/E;;IACT,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,UAAU,KAAU,CAAC;KACrB,WAAU;eAJZ,CAME,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAAA,QAEvB;;IACL;KACF"}
|
|
@@ -200,7 +200,10 @@ var j = () => {
|
|
|
200
200
|
G,
|
|
201
201
|
Y
|
|
202
202
|
]), Q = g(async (e) => {
|
|
203
|
-
if (M
|
|
203
|
+
if (M) {
|
|
204
|
+
let e = P[M]?.status;
|
|
205
|
+
if (e === "provisioning" || e === "connecting" || e === "streaming") return;
|
|
206
|
+
}
|
|
204
207
|
let n = t.getState().mode, r = M, a = r ? t.getState().conversations.find((e) => e.id === r) : void 0;
|
|
205
208
|
if (r ? a && a.mode !== n && (a.messages.length > 0 || a.backendSessionId) ? r = R(void 0, n) : a && a.mode !== n && I(r, n) : r = R(void 0, n), !r) return;
|
|
206
209
|
let c = S(), u = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantChatArea.js","names":[],"sources":["../../../src/components/assistant/AssistantChatArea.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, type FC } from 'react';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { useAuthToken } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { AssistantChatInput } from './AssistantChatInput';\nimport { AssistantMessageList } from './AssistantMessageList';\nimport { AssistantModeSelector } from './AssistantModeSelector';\nimport { AssistantWelcome } from './AssistantWelcome';\nimport { useAssistantStore } from '@/store/assistantStore';\nimport {\n createAssistantSandbox,\n createAssistantSession,\n extendAssistantSandboxTTL,\n findExistingSandbox,\n getAssistantArtifacts,\n getAssistantMessages,\n getAssistantSession,\n sendAssistantPromptAsync,\n waitForAssistantServiceReady,\n waitForSandboxReady,\n type AssistantRawMessage,\n type AssistantSandboxAuthContext,\n} from '@/services/assistantApi';\nimport { gatherPageContext } from '@/utils/pageContext';\nimport { useTr } from '../../shared/hooks/useTr';\nimport type {\n AssistantArtifact,\n AssistantConversationStatus,\n AssistantMode,\n Message,\n} from '@/types/assistant';\n\nfunction generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\nfunction getProfileContextValue(key: 'workspaceId' | 'organizationId'): string {\n try {\n const activeContextKey =\n key === 'workspaceId'\n ? 'burdenoff-active-context-workspace'\n : 'burdenoff-active-context-organization';\n const activeContextValue = localStorage.getItem(activeContextKey);\n if (activeContextValue) return activeContextValue;\n\n const activeProfileId = sessionStorage.getItem('bf-active-profile');\n if (!activeProfileId) return '';\n const raw = localStorage.getItem(`bf-p-${activeProfileId}-context`);\n if (!raw) return '';\n const context = JSON.parse(raw) as {\n workspaceId?: string;\n organizationId?: string;\n };\n return context[key] ?? '';\n } catch {\n return '';\n }\n}\n\nfunction getWorkspaceId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('workspace') ?? getProfileContextValue('workspaceId');\n}\n\nfunction getOrganizationId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('org') ?? getProfileContextValue('organizationId');\n}\n\nfunction isRecoverableAssistantError(error: unknown): boolean {\n const message =\n error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();\n\n if (\n message.includes('rate limit exceeded') ||\n message.includes('unauthorized') ||\n message.includes('k8s api error 401')\n ) {\n return false;\n }\n\n return [\n 'sandbox not found',\n 'sandbox is not running',\n 'sandbox service not available yet',\n 'sandbox failed',\n 'sandbox startup timed out',\n 'assistant service did not become healthy',\n 'proxy error: 404',\n 'proxy error: 502',\n 'proxy error: 503',\n 'session mode mismatch',\n ].some((fragment) => message.includes(fragment));\n}\n\nfunction sanitizeAssistantResponse(response: string): string {\n return response\n .replace(/(Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/(X-Workspace-Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9._-]+\\.[A-Za-z0-9._-]+\\b/g, '[REDACTED_JWT]')\n .replace(/\\bsk-ant-[A-Za-z0-9-]+\\b/g, '[REDACTED_API_KEY]');\n}\n\nconst VIBECONTROLS_HELP_TOPICS: Record<string, string> = {\n action:\n 'An Action is a reusable operation VibeControls can run against a target, vibe, session, or automation flow. Create one from VibeControls > Actions, choose the action type, configure the inputs and permissions, then save and run it from the relevant page.',\n component:\n 'A Component represents a software unit such as a service, frontend, library, API, database, or infrastructure asset. Add one from VibeControls > Components, fill in the metadata, lifecycle, owner, language/framework, and tags, then attach it to catalogs or vibes as needed.',\n vibe: 'A Vibe is a workspace project/repository context managed by VibeControls. Create one from VibeControls > Vibes, select the type and path, optionally attach an agent, then use its detail tabs for sessions, docs, catalogs, GitOps, and related resources.',\n session:\n 'A Session is a managed terminal/workflow runtime connected to an agent. Open VibeControls > Sessions, create or start a session, then use terminal tabs to interact with it, share it, restart it, or bookmark a specific session tab from the URL.',\n target:\n 'A Target is a machine or runtime endpoint that an agent can manage. Register or inspect targets from VibeControls > Targets & Agents, then use them for sessions, actions, tunnels, and vibe operations.',\n};\n\nfunction resolveVibeControlsHelpPrompt(prompt: string): string | null {\n const normalized = prompt.toLowerCase();\n if (!normalized.includes('vibecontrols') && !normalized.includes('vibe controls')) {\n return null;\n }\n\n const topic = Object.keys(VIBECONTROLS_HELP_TOPICS).find((key) =>\n new RegExp(`\\\\b${key}s?\\\\b`).test(normalized)\n );\n\n if (!topic) return null;\n return VIBECONTROLS_HELP_TOPICS[topic] ?? null;\n}\n\nfunction getRawMessageCreatedAt(message: AssistantRawMessage): number {\n return message.info?.time?.created ?? 0;\n}\n\nfunction getAssistantText(parts: AssistantRawMessage['parts']): string {\n return (parts ?? [])\n .filter((part) => part.type === 'text' && typeof part.text === 'string')\n .map((part) => part.text?.trim() ?? '')\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction getToolProgress(parts: AssistantRawMessage['parts']): string[] {\n return (parts ?? [])\n .filter((part) => part.type === 'tool' && part.tool)\n .map((part) => {\n const status = part.state?.status ?? 'running';\n const tool = part.tool ?? 'tool';\n if (status === 'completed') return `Completed: ${tool}`;\n if (status === 'failed') return `Failed: ${tool}`;\n return `Running: ${tool}`;\n });\n}\n\nfunction buildAssistantProgress(\n messages: AssistantRawMessage[],\n sinceMs: number\n): {\n content: string;\n done: boolean;\n tokens?: { input: number; output: number };\n} {\n const relevant = messages\n .filter(\n (message) => message.info?.role === 'assistant' && getRawMessageCreatedAt(message) >= sinceMs\n )\n .sort((left, right) => getRawMessageCreatedAt(left) - getRawMessageCreatedAt(right));\n\n if (relevant.length === 0) {\n return { content: 'Thinking...', done: false };\n }\n\n const latest = relevant[relevant.length - 1]!;\n const text = getAssistantText(latest.parts);\n const toolProgress = getToolProgress(latest.parts);\n const content = [text, ...toolProgress].filter(Boolean).join('\\n\\n') || 'Thinking...';\n\n return {\n content,\n done: Boolean(latest.info?.time?.completed) && (Boolean(text) || toolProgress.length > 0),\n tokens: latest.info?.tokens,\n };\n}\n\nfunction getConversationStatusMessage(status: AssistantConversationStatus): string {\n switch (status) {\n case 'provisioning':\n return 'Starting assistant environment...';\n case 'connecting':\n return 'Connecting to assistant...';\n case 'streaming':\n return 'Thinking...';\n default:\n return '';\n }\n}\n\nfunction isWorkspaceListPrompt(prompt: string): boolean {\n const normalized = prompt.toLowerCase();\n return (\n normalized.includes('workspace') &&\n (normalized.includes('access') ||\n normalized.includes('authorized') ||\n normalized.includes('list'))\n );\n}\n\nfunction isApiCapabilitiesPrompt(prompt: string): boolean {\n const normalized = prompt.toLowerCase();\n return (\n normalized.includes('what api calls') ||\n normalized.includes('what can you do') ||\n normalized.includes('which api calls')\n );\n}\n\nfunction isProfileNamePrompt(prompt: string): boolean {\n const normalized = prompt.toLowerCase();\n return normalized.includes('what is my name') || normalized.includes('my profile name');\n}\n\nfunction extractTargetNamePrompt(prompt: string): string | null {\n const match = prompt.match(/create\\s+a\\s+target\\s+named\\s+(.+)/i);\n return match?.[1]?.trim() ?? null;\n}\n\nfunction getLocalProfileName(): { name?: string; email?: string } {\n try {\n const profileId = sessionStorage.getItem('bf-active-profile');\n if (!profileId) return {};\n const raw = localStorage.getItem(`bf-p-${profileId}-user`);\n if (!raw) return {};\n const user = JSON.parse(raw) as {\n username?: string;\n name?: string;\n firstName?: string;\n email?: string;\n };\n return {\n name: user.name || user.username || user.firstName,\n email: user.email,\n };\n } catch {\n return {};\n }\n}\n\nfunction isGeneralCapabilitiesPrompt(prompt: string): boolean {\n return prompt.toLowerCase().trim() === 'what can you do';\n}\n\nexport const AssistantChatArea: FC = () => {\n const tr = useTr();\n const apollo = useGatewayClient('workspace');\n const { getAccessToken, getWorkspaceToken, userId } = useAuthToken();\n\n const {\n conversations,\n activeConversationId,\n mode,\n processingByConversationId,\n setMode,\n setConversationMode,\n setActiveConversation,\n createConversation,\n addMessage,\n updateMessage,\n patchMessage,\n setConversationProcessing,\n clearConversationProcessing,\n setBackendSessionId,\n clearBackendSessionId,\n setSandbox,\n clearSandbox,\n } = useAssistantStore();\n\n const activeConversation = conversations.find(\n (conversation) => conversation.id === activeConversationId\n );\n const messages = activeConversation?.messages ?? [];\n const activeProcessing = activeConversationId\n ? processingByConversationId[activeConversationId]\n : undefined;\n const activeStreamingMessageId = activeProcessing?.messageId ?? null;\n const activeIsProcessing =\n activeProcessing?.status === 'provisioning' ||\n activeProcessing?.status === 'connecting' ||\n activeProcessing?.status === 'streaming';\n\n const updateConversationStage = useCallback(\n (\n conversationId: string,\n assistantMessageId: string,\n status: AssistantConversationStatus,\n content?: string\n ) => {\n setConversationProcessing(conversationId, status, assistantMessageId, null);\n if (content) {\n updateMessage(conversationId, assistantMessageId, content);\n } else {\n updateMessage(conversationId, assistantMessageId, getConversationStatusMessage(status));\n }\n },\n [setConversationProcessing, updateMessage]\n );\n\n const resolveApiCallsPromptLocally = useCallback(\n async (prompt: string): Promise<string | null> => {\n if (isWorkspaceListPrompt(prompt)) {\n const workspaceId = getWorkspaceId();\n const organizationId = getOrganizationId();\n\n if (!workspaceId) {\n return 'No authorized workspace is attached to this assistant session yet.';\n }\n\n const publicUrl = organizationId\n ? `https://api.${organizationId}.burdenoff.com/workspaces/graphql`\n : '';\n\n return `This assistant sandbox is authorized for 1 workspace:\\n\\n1. Current active workspace — ${workspaceId}${publicUrl ? `\\n Public URL: ${publicUrl}` : ''}`;\n }\n\n if (isProfileNamePrompt(prompt)) {\n const profile = getLocalProfileName();\n if (!profile.name && !profile.email) {\n return 'I could not determine your profile name from the local session.';\n }\n\n const nameLine = profile.name ? `Your profile name is ${profile.name}.` : '';\n const emailLine = profile.email ? `Your profile email is ${profile.email}.` : '';\n return [nameLine, emailLine].filter(Boolean).join('\\n');\n }\n\n const requestedTargetName = extractTargetNamePrompt(prompt);\n if (requestedTargetName) {\n return [\n `I can create the target \"${requestedTargetName}\", but I still need the target connection details before I can execute the mutation.`,\n '',\n 'Please provide at least:',\n '- target type (SSH, RDP, VNC, or DIRECT)',\n '- host or IP address',\n '- port if it is not the default for that protocol',\n '- any credentials or auth method the target requires',\n '',\n `For example: create a target named ${requestedTargetName} using SSH at 10.0.0.15:22 with username ubuntu.`,\n ].join('\\n');\n }\n\n if (isApiCapabilitiesPrompt(prompt)) {\n return [\n 'I can query or mutate both Burdenoff GraphQL gateways on your behalf:',\n '',\n '- Global gateway: organizations, workspaces, apps, billing, notifications, store, support, and dev portal operations',\n '- Workspace gateway: workspace-scoped modules like vibecontrols, files, tags, conversations, calendar, sandbox, and related resources',\n '',\n 'Ask me for a specific action like listing your workspaces, fetching a resource, or creating/updating something through the APIs.',\n ].join('\\n');\n }\n\n return null;\n },\n []\n );\n\n const resolveLocalPromptByMode = useCallback(\n async (currentMode: AssistantMode, prompt: string): Promise<string | null> => {\n if (currentMode === 'api-calls') {\n return resolveApiCallsPromptLocally(prompt);\n }\n\n if (currentMode === 'general' && isGeneralCapabilitiesPrompt(prompt)) {\n return [\n 'I can help explain VibeControls concepts, navigation, sessions, targets, tunnels, and platform workflows.',\n '',\n 'I can also guide you through the UI, describe features, and help you understand how different parts of the product fit together.',\n ].join('\\n');\n }\n\n if (currentMode === 'vibe-plugins' && isGeneralCapabilitiesPrompt(prompt)) {\n return [\n 'I can help design, generate, explain, and package Vibe Plugins.',\n '',\n 'I can create plugin scaffolds, explain manifests and lifecycle hooks, and provide downloadable plugin artifacts for this session.',\n ].join('\\n');\n }\n\n const helpResponse = resolveVibeControlsHelpPrompt(prompt);\n if (helpResponse) return helpResponse;\n\n return null;\n },\n [resolveApiCallsPromptLocally]\n );\n\n const ensureAssistantRuntime = useCallback(\n async (\n conversationId: string,\n assistantMessageId: string,\n currentMode: AssistantMode,\n workspaceId: string,\n authContext: AssistantSandboxAuthContext\n ): Promise<{ sandboxId: string; sessionId: string }> => {\n let currentSandboxId =\n useAssistantStore.getState().sandboxesByMode[currentMode]?.sandboxId ?? null;\n\n if (!currentSandboxId) {\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Looking for existing environment...'\n );\n const existingId = await findExistingSandbox(apollo, workspaceId, currentMode, authContext);\n\n if (existingId) {\n currentSandboxId = existingId;\n setSandbox(currentMode, currentSandboxId, 'provisioning');\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Reusing assistant environment...'\n );\n await waitForSandboxReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 300_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'provisioning', message)\n );\n await waitForAssistantServiceReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 90_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'connecting', message)\n );\n setSandbox(currentMode, currentSandboxId, 'running');\n } else {\n if (currentMode === 'api-calls' && !getAccessToken()) {\n throw new Error(\n 'API Calls mode requires an authenticated session. Please sign in again.'\n );\n }\n\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Starting assistant environment...'\n );\n setSandbox(currentMode, null, 'provisioning');\n currentSandboxId = await createAssistantSandbox(\n apollo,\n currentMode,\n workspaceId,\n authContext\n );\n\n setSandbox(currentMode, currentSandboxId, 'provisioning');\n await waitForSandboxReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 300_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'provisioning', message)\n );\n await waitForAssistantServiceReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 90_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'connecting', message)\n );\n setSandbox(currentMode, currentSandboxId, 'running');\n }\n }\n\n let sessionId =\n useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === conversationId)\n ?.backendSessionId ?? null;\n\n if (sessionId) {\n try {\n const session = await getAssistantSession(\n apollo,\n currentSandboxId,\n workspaceId,\n sessionId,\n authContext\n );\n if (session.mode !== currentMode) {\n throw new Error(\n `Session mode mismatch: expected ${currentMode}, received ${session.mode}`\n );\n }\n } catch {\n clearBackendSessionId(conversationId);\n sessionId = null;\n }\n }\n\n if (!sessionId) {\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'connecting',\n 'Preparing session...'\n );\n const result = await createAssistantSession(\n apollo,\n currentSandboxId,\n workspaceId,\n currentMode,\n authContext\n );\n sessionId = result.sessionId;\n setBackendSessionId(conversationId, sessionId);\n }\n\n return {\n sandboxId: currentSandboxId,\n sessionId,\n };\n },\n [\n apollo,\n clearBackendSessionId,\n getAccessToken,\n setBackendSessionId,\n setSandbox,\n updateConversationStage,\n ]\n );\n\n const handleSend = useCallback(\n async (content: string) => {\n if (activeConversationId && processingByConversationId[activeConversationId]) return;\n\n const selectedMode = useAssistantStore.getState().mode;\n let conversationId = activeConversationId;\n const currentConversation = conversationId\n ? useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === conversationId)\n : undefined;\n\n if (!conversationId) {\n conversationId = createConversation(undefined, selectedMode);\n } else if (\n currentConversation &&\n currentConversation.mode !== selectedMode &&\n (currentConversation.messages.length > 0 || currentConversation.backendSessionId)\n ) {\n conversationId = createConversation(undefined, selectedMode);\n } else if (currentConversation && currentConversation.mode !== selectedMode) {\n setConversationMode(conversationId, selectedMode);\n }\n\n if (!conversationId) return;\n\n const workspaceId = getWorkspaceId();\n const authContext: AssistantSandboxAuthContext = {\n accessToken: getAccessToken(),\n workspaceToken: getWorkspaceToken(),\n userId,\n organizationId: getOrganizationId(),\n };\n\n const userMessage: Message = {\n id: generateId(),\n role: 'user',\n content,\n timestamp: new Date().toISOString(),\n };\n addMessage(conversationId, userMessage);\n\n const assistantMessageId = generateId();\n addMessage(conversationId, {\n id: assistantMessageId,\n role: 'assistant',\n content: '',\n timestamp: new Date().toISOString(),\n });\n\n const applyAssistantResponse = (\n response: string,\n artifacts?: AssistantArtifact[],\n sessionId?: string,\n sandboxId?: string\n ) => {\n patchMessage(conversationId, assistantMessageId, {\n content: sanitizeAssistantResponse(response),\n metadata:\n artifacts?.length || sessionId || sandboxId\n ? {\n artifacts,\n mode: selectedMode,\n sessionId,\n sandboxId,\n }\n : undefined,\n });\n };\n\n const sendWithRealBackend = async (attempt: 1 | 2 = 1): Promise<void> => {\n try {\n const { sandboxId, sessionId } = await ensureAssistantRuntime(\n conversationId,\n assistantMessageId,\n selectedMode,\n workspaceId,\n authContext\n );\n\n updateConversationStage(conversationId, assistantMessageId, 'streaming', 'Thinking...');\n const promptStartedAt = Date.now();\n await sendAssistantPromptAsync(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n content,\n selectedMode,\n gatherPageContext(),\n authContext\n );\n\n let result = buildAssistantProgress([], promptStartedAt);\n const timeoutAt = Date.now() + 180_000;\n let lastTtlExtensionAt = Date.now();\n\n while (Date.now() < timeoutAt) {\n const sessionMessages = await getAssistantMessages(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n authContext,\n 50\n );\n\n result = buildAssistantProgress(sessionMessages, promptStartedAt);\n patchMessage(conversationId, assistantMessageId, {\n content: sanitizeAssistantResponse(result.content),\n });\n\n if (result.done) break;\n\n if (Date.now() - lastTtlExtensionAt > 30_000) {\n await extendAssistantSandboxTTL(\n apollo,\n sandboxId,\n workspaceId,\n 600,\n authContext\n ).catch(() => undefined);\n lastTtlExtensionAt = Date.now();\n }\n\n await new Promise((resolve) => setTimeout(resolve, 1500));\n }\n\n if (!result.done) {\n throw new Error('Assistant response timed out while streaming progress');\n }\n\n const artifacts =\n selectedMode === 'vibe-plugins'\n ? await getAssistantArtifacts(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n authContext\n ).catch(() => [])\n : undefined;\n\n applyAssistantResponse(result.content, artifacts, sessionId, sandboxId);\n\n if (result.tokens) {\n patchMessage(conversationId, assistantMessageId, { tokens: result.tokens });\n }\n setConversationProcessing(conversationId, 'complete', assistantMessageId, null);\n } catch (error) {\n if (attempt === 1 && isRecoverableAssistantError(error)) {\n clearBackendSessionId(conversationId);\n clearSandbox(selectedMode);\n return sendWithRealBackend(2);\n }\n\n throw error;\n }\n };\n\n try {\n if (!workspaceId) {\n throw new Error('Assistant backend is unavailable without an active workspace context.');\n }\n\n {\n const localResponse = await resolveLocalPromptByMode(selectedMode, content);\n if (localResponse) {\n applyAssistantResponse(localResponse);\n setConversationProcessing(conversationId, 'complete', assistantMessageId, null);\n return;\n }\n }\n\n await sendWithRealBackend();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Something went wrong';\n\n {\n const localResponse = await resolveLocalPromptByMode(selectedMode, content).catch(\n () => null\n );\n if (localResponse) {\n applyAssistantResponse(localResponse);\n setConversationProcessing(conversationId, 'complete', assistantMessageId, null);\n return;\n }\n }\n\n setConversationProcessing(conversationId, 'error', assistantMessageId, errorMessage);\n updateMessage(\n conversationId,\n assistantMessageId,\n `Sorry, an error occurred: ${errorMessage}. Please try again.`\n );\n }\n },\n [\n activeConversationId,\n addMessage,\n apollo,\n clearBackendSessionId,\n clearSandbox,\n conversations,\n createConversation,\n ensureAssistantRuntime,\n getAccessToken,\n getWorkspaceToken,\n patchMessage,\n processingByConversationId,\n setBackendSessionId,\n setConversationMode,\n setConversationProcessing,\n resolveLocalPromptByMode,\n updateConversationStage,\n updateMessage,\n userId,\n ]\n );\n\n const handleSuggestionClick = useCallback(\n (suggestion: string) => {\n void handleSend(suggestion);\n },\n [handleSend]\n );\n\n // Auto-send any prompt that was queued by `useOpenAssistantHelp` (from\n // the shared GetHelpButton). We keep the latest handleSend in a ref so\n // the subscription effect below doesn't need to tear down on every\n // render — the store subscription lives for the component lifetime and\n // always calls the freshest handler.\n const handleSendRef = useRef(handleSend);\n useEffect(() => {\n handleSendRef.current = handleSend;\n }, [handleSend]);\n\n useEffect(() => {\n // Flush any prompt that was queued before this component mounted.\n const initial = useAssistantStore.getState().consumePendingPrompt();\n if (initial) {\n void handleSendRef.current(initial);\n }\n\n // Watch for new prompts queued while we're already mounted.\n const unsubscribe = useAssistantStore.subscribe((state, prev) => {\n if (state.pendingPrompt && state.pendingPrompt !== prev.pendingPrompt) {\n const queued = useAssistantStore.getState().consumePendingPrompt();\n if (queued) {\n void handleSendRef.current(queued);\n }\n }\n });\n return unsubscribe;\n }, []);\n\n const handleModeChange = useCallback(\n (newMode: AssistantMode) => {\n const currentConversation = activeConversationId\n ? useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === activeConversationId)\n : undefined;\n\n if (\n currentConversation &&\n (currentConversation.messages.length > 0 || currentConversation.backendSessionId)\n ) {\n if (currentConversation.mode !== newMode) {\n const reusableDraft = useAssistantStore\n .getState()\n .conversations.find(\n (conversation) =>\n conversation.mode === newMode &&\n conversation.messages.length === 0 &&\n !conversation.backendSessionId &&\n !useAssistantStore.getState().processingByConversationId[conversation.id]\n );\n\n if (reusableDraft) {\n setActiveConversation(reusableDraft.id);\n } else {\n createConversation(undefined, newMode);\n }\n }\n } else if (activeConversationId) {\n setConversationMode(activeConversationId, newMode);\n }\n\n setMode(newMode);\n },\n [activeConversationId, createConversation, setActiveConversation, setConversationMode, setMode]\n );\n\n return (\n <div className=\"flex flex-col flex-1 overflow-hidden pt-3\">\n {messages.length === 0 ? (\n <AssistantWelcome mode={mode} onSuggestionClick={handleSuggestionClick} />\n ) : (\n <AssistantMessageList messages={messages} streamingMessageId={activeStreamingMessageId} />\n )}\n\n <div>\n <div className=\"px-3 pb-1\">\n <AssistantModeSelector mode={mode} onModeChange={handleModeChange} />\n </div>\n <AssistantChatInput\n onSend={(value) => void handleSend(value)}\n disabled={activeIsProcessing}\n placeholder={tr(\n 'vibecontrols.assistant.chatArea.placeholder',\n `Ask about ${mode.replace(/-/g, ' ')}...`\n )}\n />\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;AA+BA,SAAS,IAAqB;AAC5B,QAAO,GAAG,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE;;AAGhE,SAAS,EAAuB,GAA+C;AAC7E,KAAI;EACF,IAAM,IACJ,MAAQ,gBACJ,uCACA,yCACA,IAAqB,aAAa,QAAQ,EAAiB;AACjE,MAAI,EAAoB,QAAO;EAE/B,IAAM,IAAkB,eAAe,QAAQ,oBAAoB;AACnE,MAAI,CAAC,EAAiB,QAAO;EAC7B,IAAM,IAAM,aAAa,QAAQ,QAAQ,EAAgB,UAAU;AAMnE,SALK,IACW,KAAK,MAAM,EAAI,CAIhB,MAAQ,KALN;SAMX;AACN,SAAO;;;AAIX,SAAS,IAAyB;AAEhC,QADe,IAAI,gBAAgB,OAAO,SAAS,OAAO,CAC5C,IAAI,YAAY,IAAI,EAAuB,cAAc;;AAGzE,SAAS,IAA4B;AAEnC,QADe,IAAI,gBAAgB,OAAO,SAAS,OAAO,CAC5C,IAAI,MAAM,IAAI,EAAuB,iBAAiB;;AAGtE,SAAS,GAA4B,GAAyB;CAC5D,IAAM,IACJ,aAAiB,QAAQ,EAAM,QAAQ,aAAa,GAAG,OAAO,EAAM,CAAC,aAAa;AAUpF,QAPE,EAAQ,SAAS,sBAAsB,IACvC,EAAQ,SAAS,eAAe,IAChC,EAAQ,SAAS,oBAAoB,GAE9B,KAGF;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,MAAM,MAAa,EAAQ,SAAS,EAAS,CAAC;;AAGlD,SAAS,EAA0B,GAA0B;AAC3D,QAAO,EACJ,QAAQ,6CAA6C,eAAe,CACpE,QAAQ,yDAAyD,eAAe,CAChF,QAAQ,4DAA4D,iBAAiB,CACrF,QAAQ,6BAA6B,qBAAqB;;AAG/D,IAAM,IAAmD;CACvD,QACE;CACF,WACE;CACF,MAAM;CACN,SACE;CACF,QACE;CACH;AAED,SAAS,GAA8B,GAA+B;CACpE,IAAM,IAAa,EAAO,aAAa;AACvC,KAAI,CAAC,EAAW,SAAS,eAAe,IAAI,CAAC,EAAW,SAAS,gBAAgB,CAC/E,QAAO;CAGT,IAAM,IAAQ,OAAO,KAAK,EAAyB,CAAC,MAAM,MACpD,OAAO,MAAM,EAAI,OAAO,CAAC,KAAK,EAAW,CAC9C;AAGD,QADK,IACE,EAAyB,MAAU,OADvB;;AAIrB,SAAS,EAAuB,GAAsC;AACpE,QAAO,EAAQ,MAAM,MAAM,WAAW;;AAGxC,SAAS,EAAiB,GAA6C;AACrE,SAAQ,KAAS,EAAE,EAChB,QAAQ,MAAS,EAAK,SAAS,UAAU,OAAO,EAAK,QAAS,SAAS,CACvE,KAAK,MAAS,EAAK,MAAM,MAAM,IAAI,GAAG,CACtC,OAAO,QAAQ,CACf,KAAK,KAAK;;AAGf,SAAS,EAAgB,GAA+C;AACtE,SAAQ,KAAS,EAAE,EAChB,QAAQ,MAAS,EAAK,SAAS,UAAU,EAAK,KAAK,CACnD,KAAK,MAAS;EACb,IAAM,IAAS,EAAK,OAAO,UAAU,WAC/B,IAAO,EAAK,QAAQ;AAG1B,SAFI,MAAW,cAAoB,cAAc,MAC7C,MAAW,WAAiB,WAAW,MACpC,YAAY;GACnB;;AAGN,SAAS,EACP,GACA,GAKA;CACA,IAAM,IAAW,EACd,QACE,MAAY,EAAQ,MAAM,SAAS,eAAe,EAAuB,EAAQ,IAAI,EACvF,CACA,MAAM,GAAM,MAAU,EAAuB,EAAK,GAAG,EAAuB,EAAM,CAAC;AAEtF,KAAI,EAAS,WAAW,EACtB,QAAO;EAAE,SAAS;EAAe,MAAM;EAAO;CAGhD,IAAM,IAAS,EAAS,EAAS,SAAS,IACpC,IAAO,EAAiB,EAAO,MAAM,EACrC,IAAe,EAAgB,EAAO,MAAM;AAGlD,QAAO;EACL,SAHc,CAAC,GAAM,GAAG,EAAa,CAAC,OAAO,QAAQ,CAAC,KAAK,OAAO,IAAI;EAItE,MAAM,EAAQ,EAAO,MAAM,MAAM,cAAe,EAAQ,KAAS,EAAa,SAAS;EACvF,QAAQ,EAAO,MAAM;EACtB;;AAGH,SAAS,GAA6B,GAA6C;AACjF,SAAQ,GAAR;EACE,KAAK,eACH,QAAO;EACT,KAAK,aACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,QACE,QAAO;;;AAIb,SAAS,GAAsB,GAAyB;CACtD,IAAM,IAAa,EAAO,aAAa;AACvC,QACE,EAAW,SAAS,YAAY,KAC/B,EAAW,SAAS,SAAS,IAC5B,EAAW,SAAS,aAAa,IACjC,EAAW,SAAS,OAAO;;AAIjC,SAAS,GAAwB,GAAyB;CACxD,IAAM,IAAa,EAAO,aAAa;AACvC,QACE,EAAW,SAAS,iBAAiB,IACrC,EAAW,SAAS,kBAAkB,IACtC,EAAW,SAAS,kBAAkB;;AAI1C,SAAS,GAAoB,GAAyB;CACpD,IAAM,IAAa,EAAO,aAAa;AACvC,QAAO,EAAW,SAAS,kBAAkB,IAAI,EAAW,SAAS,kBAAkB;;AAGzF,SAAS,GAAwB,GAA+B;AAE9D,QADc,EAAO,MAAM,sCAAsC,GAClD,IAAI,MAAM,IAAI;;AAG/B,SAAS,KAAyD;AAChE,KAAI;EACF,IAAM,IAAY,eAAe,QAAQ,oBAAoB;AAC7D,MAAI,CAAC,EAAW,QAAO,EAAE;EACzB,IAAM,IAAM,aAAa,QAAQ,QAAQ,EAAU,OAAO;AAC1D,MAAI,CAAC,EAAK,QAAO,EAAE;EACnB,IAAM,IAAO,KAAK,MAAM,EAAI;AAM5B,SAAO;GACL,MAAM,EAAK,QAAQ,EAAK,YAAY,EAAK;GACzC,OAAO,EAAK;GACb;SACK;AACN,SAAO,EAAE;;;AAIb,SAAS,EAA4B,GAAyB;AAC5D,QAAO,EAAO,aAAa,CAAC,MAAM,KAAK;;AAGzC,IAAa,UAA8B;CACzC,IAAM,IAAK,GAAO,EACZ,IAAS,GAAiB,YAAY,EACtC,EAAE,mBAAgB,sBAAmB,cAAW,IAAc,EAE9D,EACJ,kBACA,yBACA,SACA,+BACA,YACA,wBACA,0BACA,uBACA,eACA,kBACA,iBACA,8BACA,iCACA,wBACA,0BACA,eACA,oBACE,GAAmB,EAKjB,IAHqB,EAAc,MACtC,MAAiB,EAAa,OAAO,EACvC,EACoC,YAAY,EAAE,EAC7C,IAAmB,IACrB,EAA2B,KAC3B,KAAA,GACE,KAA2B,GAAkB,aAAa,MAC1D,KACJ,GAAkB,WAAW,kBAC7B,GAAkB,WAAW,gBAC7B,GAAkB,WAAW,aAEzB,IAA0B,GAE5B,GACA,GACA,GACA,MACG;AAEH,EADA,EAA0B,GAAgB,GAAQ,GAAoB,KAAK,EACvE,IACF,EAAc,GAAgB,GAAoB,EAAQ,GAE1D,EAAc,GAAgB,GAAoB,GAA6B,EAAO,CAAC;IAG3F,CAAC,GAA2B,EAAc,CAC3C,EAEK,KAA+B,EACnC,OAAO,MAA2C;AAChD,MAAI,GAAsB,EAAO,EAAE;GACjC,IAAM,IAAc,GAAgB,EAC9B,IAAiB,GAAmB;AAE1C,OAAI,CAAC,EACH,QAAO;GAGT,IAAM,IAAY,IACd,eAAe,EAAe,qCAC9B;AAEJ,UAAO,0FAA0F,IAAc,IAAY,oBAAoB,MAAc;;AAG/J,MAAI,GAAoB,EAAO,EAAE;GAC/B,IAAM,IAAU,IAAqB;AAOrC,UANI,CAAC,EAAQ,QAAQ,CAAC,EAAQ,QACrB,oEAKF,CAFU,EAAQ,OAAO,wBAAwB,EAAQ,KAAK,KAAK,IACxD,EAAQ,QAAQ,yBAAyB,EAAQ,MAAM,KAAK,GAClD,CAAC,OAAO,QAAQ,CAAC,KAAK,KAAK;;EAGzD,IAAM,IAAsB,GAAwB,EAAO;AA0B3D,SAzBI,IACK;GACL,4BAA4B,EAAoB;GAChD;GACA;GACA;GACA;GACA;GACA;GACA;GACA,sCAAsC,EAAoB;GAC3D,CAAC,KAAK,KAAK,GAGV,GAAwB,EAAO,GAC1B;GACL;GACA;GACA;GACA;GACA;GACA;GACD,CAAC,KAAK,KAAK,GAGP;IAET,EAAE,CACH,EAEK,IAA2B,EAC/B,OAAO,GAA4B,MAC7B,MAAgB,cACX,GAA6B,EAAO,GAGzC,MAAgB,aAAa,EAA4B,EAAO,GAC3D;EACL;EACA;EACA;EACD,CAAC,KAAK,KAAK,GAGV,MAAgB,kBAAkB,EAA4B,EAAO,GAChE;EACL;EACA;EACA;EACD,CAAC,KAAK,KAAK,GAGO,GAA8B,EAAO,IAGnD,MAET,CAAC,GAA6B,CAC/B,EAEK,IAAyB,EAC7B,OACE,GACA,GACA,GACA,GACA,MACsD;EACtD,IAAI,IACF,EAAkB,UAAU,CAAC,gBAAgB,IAAc,aAAa;AAE1E,MAAI,CAAC,GAAkB;AACrB,KACE,GACA,GACA,gBACA,sCACD;GACD,IAAM,IAAa,MAAM,EAAoB,GAAQ,GAAa,GAAa,EAAY;AAE3F,OAAI,EA2BF,CA1BA,IAAmB,GACnB,EAAW,GAAa,GAAkB,eAAe,EACzD,EACE,GACA,GACA,gBACA,mCACD,EACD,MAAM,EACJ,GACA,GACA,GACA,GACA,MACC,MACC,EAAwB,GAAgB,GAAoB,gBAAgB,EAAQ,CACvF,EACD,MAAM,EACJ,GACA,GACA,GACA,GACA,MACC,MACC,EAAwB,GAAgB,GAAoB,cAAc,EAAQ,CACrF,EACD,EAAW,GAAa,GAAkB,UAAU;QAC/C;AACL,QAAI,MAAgB,eAAe,CAAC,GAAgB,CAClD,OAAU,MACR,0EACD;AAoCH,IAjCA,EACE,GACA,GACA,gBACA,oCACD,EACD,EAAW,GAAa,MAAM,eAAe,EAC7C,IAAmB,MAAM,EACvB,GACA,GACA,GACA,EACD,EAED,EAAW,GAAa,GAAkB,eAAe,EACzD,MAAM,EACJ,GACA,GACA,GACA,GACA,MACC,MACC,EAAwB,GAAgB,GAAoB,gBAAgB,EAAQ,CACvF,EACD,MAAM,EACJ,GACA,GACA,GACA,GACA,MACC,MACC,EAAwB,GAAgB,GAAoB,cAAc,EAAQ,CACrF,EACD,EAAW,GAAa,GAAkB,UAAU;;;EAIxD,IAAI,IACF,EACG,UAAU,CACV,cAAc,MAAM,MAAiB,EAAa,OAAO,EAAe,EACvE,oBAAoB;AAE1B,MAAI,EACF,KAAI;GACF,IAAM,IAAU,MAAM,EACpB,GACA,GACA,GACA,GACA,EACD;AACD,OAAI,EAAQ,SAAS,EACnB,OAAU,MACR,mCAAmC,EAAY,aAAa,EAAQ,OACrE;UAEG;AAEN,GADA,EAAsB,EAAe,EACrC,IAAY;;AAsBhB,SAlBK,MACH,EACE,GACA,GACA,cACA,uBACD,EAQD,KAPe,MAAM,EACnB,GACA,GACA,GACA,GACA,EACD,EACkB,WACnB,EAAoB,GAAgB,EAAU,GAGzC;GACL,WAAW;GACX;GACD;IAEH;EACE;EACA;EACA;EACA;EACA;EACA;EACD,CACF,EAEK,IAAa,EACjB,OAAO,MAAoB;AACzB,MAAI,KAAwB,EAA2B,GAAuB;EAE9E,IAAM,IAAe,EAAkB,UAAU,CAAC,MAC9C,IAAiB,GACf,IAAsB,IACxB,EACG,UAAU,CACV,cAAc,MAAM,MAAiB,EAAa,OAAO,EAAe,GAC3E,KAAA;AAcJ,MAZK,IAGH,KACA,EAAoB,SAAS,MAC5B,EAAoB,SAAS,SAAS,KAAK,EAAoB,oBAEhE,IAAiB,EAAmB,KAAA,GAAW,EAAa,GACnD,KAAuB,EAAoB,SAAS,KAC7D,EAAoB,GAAgB,EAAa,GARjD,IAAiB,EAAmB,KAAA,GAAW,EAAa,EAW1D,CAAC,EAAgB;EAErB,IAAM,IAAc,GAAgB,EAC9B,IAA2C;GAC/C,aAAa,GAAgB;GAC7B,gBAAgB,GAAmB;GACnC;GACA,gBAAgB,GAAmB;GACpC,EAEK,IAAuB;GAC3B,IAAI,GAAY;GAChB,MAAM;GACN;GACA,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC;AACD,IAAW,GAAgB,EAAY;EAEvC,IAAM,IAAqB,GAAY;AACvC,IAAW,GAAgB;GACzB,IAAI;GACJ,MAAM;GACN,SAAS;GACT,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC,CAAC;EAEF,IAAM,KACJ,GACA,GACA,GACA,MACG;AACH,KAAa,GAAgB,GAAoB;IAC/C,SAAS,EAA0B,EAAS;IAC5C,UACE,GAAW,UAAU,KAAa,IAC9B;KACE;KACA,MAAM;KACN;KACA;KACD,GACD,KAAA;IACP,CAAC;KAGE,IAAsB,OAAO,IAAiB,MAAqB;AACvE,OAAI;IACF,IAAM,EAAE,cAAW,iBAAc,MAAM,EACrC,GACA,GACA,GACA,GACA,EACD;AAED,MAAwB,GAAgB,GAAoB,aAAa,cAAc;IACvF,IAAM,IAAkB,KAAK,KAAK;AAClC,UAAM,EACJ,GACA,GACA,GACA,GACA,GACA,GACA,IAAmB,EACnB,EACD;IAED,IAAI,IAAS,EAAuB,EAAE,EAAE,EAAgB,EAClD,IAAY,KAAK,KAAK,GAAG,MAC3B,IAAqB,KAAK,KAAK;AAEnC,WAAO,KAAK,KAAK,GAAG,MAUlB,IAAS,EATe,MAAM,EAC5B,GACA,GACA,GACA,GACA,GACA,GACD,EAEgD,EAAgB,EACjE,EAAa,GAAgB,GAAoB,EAC/C,SAAS,EAA0B,EAAO,QAAQ,EACnD,CAAC,EAEE,GAAO,OAaX,CAXI,KAAK,KAAK,GAAG,IAAqB,QACpC,MAAM,EACJ,GACA,GACA,GACA,KACA,EACD,CAAC,YAAY,KAAA,EAAU,EACxB,IAAqB,KAAK,KAAK,GAGjC,MAAM,IAAI,SAAS,MAAY,WAAW,GAAS,KAAK,CAAC;AAG3D,QAAI,CAAC,EAAO,KACV,OAAU,MAAM,wDAAwD;IAG1E,IAAM,IACJ,MAAiB,iBACb,MAAM,EACJ,GACA,GACA,GACA,GACA,EACD,CAAC,YAAY,EAAE,CAAC,GACjB,KAAA;AAON,IALA,EAAuB,EAAO,SAAS,GAAW,GAAW,EAAU,EAEnE,EAAO,UACT,EAAa,GAAgB,GAAoB,EAAE,QAAQ,EAAO,QAAQ,CAAC,EAE7E,EAA0B,GAAgB,YAAY,GAAoB,KAAK;YACxE,GAAO;AACd,QAAI,MAAY,KAAK,GAA4B,EAAM,CAGrD,QAFA,EAAsB,EAAe,EACrC,EAAa,EAAa,EACnB,EAAoB,EAAE;AAG/B,UAAM;;;AAIV,MAAI;AACF,OAAI,CAAC,EACH,OAAU,MAAM,wEAAwE;GAG1F;IACE,IAAM,IAAgB,MAAM,EAAyB,GAAc,EAAQ;AAC3E,QAAI,GAAe;AAEjB,KADA,EAAuB,EAAc,EACrC,EAA0B,GAAgB,YAAY,GAAoB,KAAK;AAC/E;;;AAIJ,SAAM,GAAqB;WACpB,GAAO;GACd,IAAM,IAAe,aAAiB,QAAQ,EAAM,UAAU;GAE9D;IACE,IAAM,IAAgB,MAAM,EAAyB,GAAc,EAAQ,CAAC,YACpE,KACP;AACD,QAAI,GAAe;AAEjB,KADA,EAAuB,EAAc,EACrC,EAA0B,GAAgB,YAAY,GAAoB,KAAK;AAC/E;;;AAKJ,GADA,EAA0B,GAAgB,SAAS,GAAoB,EAAa,EACpF,EACE,GACA,GACA,6BAA6B,EAAa,qBAC3C;;IAGL;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF,EAEK,KAAwB,GAC3B,MAAuB;AACjB,IAAW,EAAW;IAE7B,CAAC,EAAW,CACb,EAOK,IAAgB,GAAO,EAAW;AAKxC,CAJA,QAAgB;AACd,IAAc,UAAU;IACvB,CAAC,EAAW,CAAC,EAEhB,QAAgB;EAEd,IAAM,IAAU,EAAkB,UAAU,CAAC,sBAAsB;AAcnE,SAbI,KACG,EAAc,QAAQ,EAAQ,EAIjB,EAAkB,WAAW,GAAO,MAAS;AAC/D,OAAI,EAAM,iBAAiB,EAAM,kBAAkB,EAAK,eAAe;IACrE,IAAM,IAAS,EAAkB,UAAU,CAAC,sBAAsB;AAClE,IAAI,KACG,EAAc,QAAQ,EAAO;;IAGtC;IAED,EAAE,CAAC;CAEN,IAAM,KAAmB,GACtB,MAA2B;EAC1B,IAAM,IAAsB,IACxB,EACG,UAAU,CACV,cAAc,MAAM,MAAiB,EAAa,OAAO,EAAqB,GACjF,KAAA;AAEJ,MACE,MACC,EAAoB,SAAS,SAAS,KAAK,EAAoB;OAE5D,EAAoB,SAAS,GAAS;IACxC,IAAM,IAAgB,EACnB,UAAU,CACV,cAAc,MACZ,MACC,EAAa,SAAS,KACtB,EAAa,SAAS,WAAW,KACjC,CAAC,EAAa,oBACd,CAAC,EAAkB,UAAU,CAAC,2BAA2B,EAAa,IACzE;AAEH,IAAI,IACF,EAAsB,EAAc,GAAG,GAEvC,EAAmB,KAAA,GAAW,EAAQ;;SAGjC,KACT,EAAoB,GAAsB,EAAQ;AAGpD,IAAQ,EAAQ;IAElB;EAAC;EAAsB;EAAoB;EAAuB;EAAqB;EAAQ,CAChG;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACG,EAAS,WAAW,IACnB,kBAAC,GAAD;GAAwB;GAAM,mBAAmB;GAAyB,CAAA,GAE1E,kBAAC,GAAD;GAAgC;GAAU,oBAAoB;GAA4B,CAAA,EAG5F,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IAA6B;IAAM,cAAc;IAAoB,CAAA;GACjE,CAAA,EACN,kBAAC,GAAD;GACE,SAAS,MAAU,KAAK,EAAW,EAAM;GACzC,UAAU;GACV,aAAa,EACX,+CACA,aAAa,EAAK,QAAQ,MAAM,IAAI,CAAC,KACtC;GACD,CAAA,CACE,EAAA,CAAA,CACF"}
|
|
1
|
+
{"version":3,"file":"AssistantChatArea.js","names":[],"sources":["../../../src/components/assistant/AssistantChatArea.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, type FC } from 'react';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { useAuthToken } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { AssistantChatInput } from './AssistantChatInput';\nimport { AssistantMessageList } from './AssistantMessageList';\nimport { AssistantModeSelector } from './AssistantModeSelector';\nimport { AssistantWelcome } from './AssistantWelcome';\nimport { useAssistantStore } from '@/store/assistantStore';\nimport {\n createAssistantSandbox,\n createAssistantSession,\n extendAssistantSandboxTTL,\n findExistingSandbox,\n getAssistantArtifacts,\n getAssistantMessages,\n getAssistantSession,\n sendAssistantPromptAsync,\n waitForAssistantServiceReady,\n waitForSandboxReady,\n type AssistantRawMessage,\n type AssistantSandboxAuthContext,\n} from '@/services/assistantApi';\nimport { gatherPageContext } from '@/utils/pageContext';\nimport { useTr } from '../../shared/hooks/useTr';\nimport type {\n AssistantArtifact,\n AssistantConversationStatus,\n AssistantMode,\n Message,\n} from '@/types/assistant';\n\nfunction generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\nfunction getProfileContextValue(key: 'workspaceId' | 'organizationId'): string {\n try {\n const activeContextKey =\n key === 'workspaceId'\n ? 'burdenoff-active-context-workspace'\n : 'burdenoff-active-context-organization';\n const activeContextValue = localStorage.getItem(activeContextKey);\n if (activeContextValue) return activeContextValue;\n\n const activeProfileId = sessionStorage.getItem('bf-active-profile');\n if (!activeProfileId) return '';\n const raw = localStorage.getItem(`bf-p-${activeProfileId}-context`);\n if (!raw) return '';\n const context = JSON.parse(raw) as {\n workspaceId?: string;\n organizationId?: string;\n };\n return context[key] ?? '';\n } catch {\n return '';\n }\n}\n\nfunction getWorkspaceId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('workspace') ?? getProfileContextValue('workspaceId');\n}\n\nfunction getOrganizationId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('org') ?? getProfileContextValue('organizationId');\n}\n\nfunction isRecoverableAssistantError(error: unknown): boolean {\n const message =\n error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();\n\n if (\n message.includes('rate limit exceeded') ||\n message.includes('unauthorized') ||\n message.includes('k8s api error 401')\n ) {\n return false;\n }\n\n return [\n 'sandbox not found',\n 'sandbox is not running',\n 'sandbox service not available yet',\n 'sandbox failed',\n 'sandbox startup timed out',\n 'assistant service did not become healthy',\n 'proxy error: 404',\n 'proxy error: 502',\n 'proxy error: 503',\n 'session mode mismatch',\n ].some((fragment) => message.includes(fragment));\n}\n\nfunction sanitizeAssistantResponse(response: string): string {\n return response\n .replace(/(Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/(X-Workspace-Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9._-]+\\.[A-Za-z0-9._-]+\\b/g, '[REDACTED_JWT]')\n .replace(/\\bsk-ant-[A-Za-z0-9-]+\\b/g, '[REDACTED_API_KEY]');\n}\n\nconst VIBECONTROLS_HELP_TOPICS: Record<string, string> = {\n action:\n 'An Action is a reusable operation VibeControls can run against a target, vibe, session, or automation flow. Create one from VibeControls > Actions, choose the action type, configure the inputs and permissions, then save and run it from the relevant page.',\n component:\n 'A Component represents a software unit such as a service, frontend, library, API, database, or infrastructure asset. Add one from VibeControls > Components, fill in the metadata, lifecycle, owner, language/framework, and tags, then attach it to catalogs or vibes as needed.',\n vibe: 'A Vibe is a workspace project/repository context managed by VibeControls. Create one from VibeControls > Vibes, select the type and path, optionally attach an agent, then use its detail tabs for sessions, docs, catalogs, GitOps, and related resources.',\n session:\n 'A Session is a managed terminal/workflow runtime connected to an agent. Open VibeControls > Sessions, create or start a session, then use terminal tabs to interact with it, share it, restart it, or bookmark a specific session tab from the URL.',\n target:\n 'A Target is a machine or runtime endpoint that an agent can manage. Register or inspect targets from VibeControls > Targets & Agents, then use them for sessions, actions, tunnels, and vibe operations.',\n};\n\nfunction resolveVibeControlsHelpPrompt(prompt: string): string | null {\n const normalized = prompt.toLowerCase();\n if (!normalized.includes('vibecontrols') && !normalized.includes('vibe controls')) {\n return null;\n }\n\n const topic = Object.keys(VIBECONTROLS_HELP_TOPICS).find((key) =>\n new RegExp(`\\\\b${key}s?\\\\b`).test(normalized)\n );\n\n if (!topic) return null;\n return VIBECONTROLS_HELP_TOPICS[topic] ?? null;\n}\n\nfunction getRawMessageCreatedAt(message: AssistantRawMessage): number {\n return message.info?.time?.created ?? 0;\n}\n\nfunction getAssistantText(parts: AssistantRawMessage['parts']): string {\n return (parts ?? [])\n .filter((part) => part.type === 'text' && typeof part.text === 'string')\n .map((part) => part.text?.trim() ?? '')\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction getToolProgress(parts: AssistantRawMessage['parts']): string[] {\n return (parts ?? [])\n .filter((part) => part.type === 'tool' && part.tool)\n .map((part) => {\n const status = part.state?.status ?? 'running';\n const tool = part.tool ?? 'tool';\n if (status === 'completed') return `Completed: ${tool}`;\n if (status === 'failed') return `Failed: ${tool}`;\n return `Running: ${tool}`;\n });\n}\n\nfunction buildAssistantProgress(\n messages: AssistantRawMessage[],\n sinceMs: number\n): {\n content: string;\n done: boolean;\n tokens?: { input: number; output: number };\n} {\n const relevant = messages\n .filter(\n (message) => message.info?.role === 'assistant' && getRawMessageCreatedAt(message) >= sinceMs\n )\n .sort((left, right) => getRawMessageCreatedAt(left) - getRawMessageCreatedAt(right));\n\n if (relevant.length === 0) {\n return { content: 'Thinking...', done: false };\n }\n\n const latest = relevant[relevant.length - 1]!;\n const text = getAssistantText(latest.parts);\n const toolProgress = getToolProgress(latest.parts);\n const content = [text, ...toolProgress].filter(Boolean).join('\\n\\n') || 'Thinking...';\n\n return {\n content,\n done: Boolean(latest.info?.time?.completed) && (Boolean(text) || toolProgress.length > 0),\n tokens: latest.info?.tokens,\n };\n}\n\nfunction getConversationStatusMessage(status: AssistantConversationStatus): string {\n switch (status) {\n case 'provisioning':\n return 'Starting assistant environment...';\n case 'connecting':\n return 'Connecting to assistant...';\n case 'streaming':\n return 'Thinking...';\n default:\n return '';\n }\n}\n\nfunction isWorkspaceListPrompt(prompt: string): boolean {\n const normalized = prompt.toLowerCase();\n return (\n normalized.includes('workspace') &&\n (normalized.includes('access') ||\n normalized.includes('authorized') ||\n normalized.includes('list'))\n );\n}\n\nfunction isApiCapabilitiesPrompt(prompt: string): boolean {\n const normalized = prompt.toLowerCase();\n return (\n normalized.includes('what api calls') ||\n normalized.includes('what can you do') ||\n normalized.includes('which api calls')\n );\n}\n\nfunction isProfileNamePrompt(prompt: string): boolean {\n const normalized = prompt.toLowerCase();\n return normalized.includes('what is my name') || normalized.includes('my profile name');\n}\n\nfunction extractTargetNamePrompt(prompt: string): string | null {\n const match = prompt.match(/create\\s+a\\s+target\\s+named\\s+(.+)/i);\n return match?.[1]?.trim() ?? null;\n}\n\nfunction getLocalProfileName(): { name?: string; email?: string } {\n try {\n const profileId = sessionStorage.getItem('bf-active-profile');\n if (!profileId) return {};\n const raw = localStorage.getItem(`bf-p-${profileId}-user`);\n if (!raw) return {};\n const user = JSON.parse(raw) as {\n username?: string;\n name?: string;\n firstName?: string;\n email?: string;\n };\n return {\n name: user.name || user.username || user.firstName,\n email: user.email,\n };\n } catch {\n return {};\n }\n}\n\nfunction isGeneralCapabilitiesPrompt(prompt: string): boolean {\n return prompt.toLowerCase().trim() === 'what can you do';\n}\n\nexport const AssistantChatArea: FC = () => {\n const tr = useTr();\n const apollo = useGatewayClient('workspace');\n const { getAccessToken, getWorkspaceToken, userId } = useAuthToken();\n\n const {\n conversations,\n activeConversationId,\n mode,\n processingByConversationId,\n setMode,\n setConversationMode,\n setActiveConversation,\n createConversation,\n addMessage,\n updateMessage,\n patchMessage,\n setConversationProcessing,\n clearConversationProcessing,\n setBackendSessionId,\n clearBackendSessionId,\n setSandbox,\n clearSandbox,\n } = useAssistantStore();\n\n const activeConversation = conversations.find(\n (conversation) => conversation.id === activeConversationId\n );\n const messages = activeConversation?.messages ?? [];\n const activeProcessing = activeConversationId\n ? processingByConversationId[activeConversationId]\n : undefined;\n const activeStreamingMessageId = activeProcessing?.messageId ?? null;\n const activeIsProcessing =\n activeProcessing?.status === 'provisioning' ||\n activeProcessing?.status === 'connecting' ||\n activeProcessing?.status === 'streaming';\n\n const updateConversationStage = useCallback(\n (\n conversationId: string,\n assistantMessageId: string,\n status: AssistantConversationStatus,\n content?: string\n ) => {\n setConversationProcessing(conversationId, status, assistantMessageId, null);\n if (content) {\n updateMessage(conversationId, assistantMessageId, content);\n } else {\n updateMessage(conversationId, assistantMessageId, getConversationStatusMessage(status));\n }\n },\n [setConversationProcessing, updateMessage]\n );\n\n const resolveApiCallsPromptLocally = useCallback(\n async (prompt: string): Promise<string | null> => {\n if (isWorkspaceListPrompt(prompt)) {\n const workspaceId = getWorkspaceId();\n const organizationId = getOrganizationId();\n\n if (!workspaceId) {\n return 'No authorized workspace is attached to this assistant session yet.';\n }\n\n const publicUrl = organizationId\n ? `https://api.${organizationId}.burdenoff.com/workspaces/graphql`\n : '';\n\n return `This assistant sandbox is authorized for 1 workspace:\\n\\n1. Current active workspace — ${workspaceId}${publicUrl ? `\\n Public URL: ${publicUrl}` : ''}`;\n }\n\n if (isProfileNamePrompt(prompt)) {\n const profile = getLocalProfileName();\n if (!profile.name && !profile.email) {\n return 'I could not determine your profile name from the local session.';\n }\n\n const nameLine = profile.name ? `Your profile name is ${profile.name}.` : '';\n const emailLine = profile.email ? `Your profile email is ${profile.email}.` : '';\n return [nameLine, emailLine].filter(Boolean).join('\\n');\n }\n\n const requestedTargetName = extractTargetNamePrompt(prompt);\n if (requestedTargetName) {\n return [\n `I can create the target \"${requestedTargetName}\", but I still need the target connection details before I can execute the mutation.`,\n '',\n 'Please provide at least:',\n '- target type (SSH, RDP, VNC, or DIRECT)',\n '- host or IP address',\n '- port if it is not the default for that protocol',\n '- any credentials or auth method the target requires',\n '',\n `For example: create a target named ${requestedTargetName} using SSH at 10.0.0.15:22 with username ubuntu.`,\n ].join('\\n');\n }\n\n if (isApiCapabilitiesPrompt(prompt)) {\n return [\n 'I can query or mutate both Burdenoff GraphQL gateways on your behalf:',\n '',\n '- Global gateway: organizations, workspaces, apps, billing, notifications, store, support, and dev portal operations',\n '- Workspace gateway: workspace-scoped modules like vibecontrols, files, tags, conversations, calendar, sandbox, and related resources',\n '',\n 'Ask me for a specific action like listing your workspaces, fetching a resource, or creating/updating something through the APIs.',\n ].join('\\n');\n }\n\n return null;\n },\n []\n );\n\n const resolveLocalPromptByMode = useCallback(\n async (currentMode: AssistantMode, prompt: string): Promise<string | null> => {\n if (currentMode === 'api-calls') {\n return resolveApiCallsPromptLocally(prompt);\n }\n\n if (currentMode === 'general' && isGeneralCapabilitiesPrompt(prompt)) {\n return [\n 'I can help explain VibeControls concepts, navigation, sessions, targets, tunnels, and platform workflows.',\n '',\n 'I can also guide you through the UI, describe features, and help you understand how different parts of the product fit together.',\n ].join('\\n');\n }\n\n if (currentMode === 'vibe-plugins' && isGeneralCapabilitiesPrompt(prompt)) {\n return [\n 'I can help design, generate, explain, and package Vibe Plugins.',\n '',\n 'I can create plugin scaffolds, explain manifests and lifecycle hooks, and provide downloadable plugin artifacts for this session.',\n ].join('\\n');\n }\n\n const helpResponse = resolveVibeControlsHelpPrompt(prompt);\n if (helpResponse) return helpResponse;\n\n return null;\n },\n [resolveApiCallsPromptLocally]\n );\n\n const ensureAssistantRuntime = useCallback(\n async (\n conversationId: string,\n assistantMessageId: string,\n currentMode: AssistantMode,\n workspaceId: string,\n authContext: AssistantSandboxAuthContext\n ): Promise<{ sandboxId: string; sessionId: string }> => {\n let currentSandboxId =\n useAssistantStore.getState().sandboxesByMode[currentMode]?.sandboxId ?? null;\n\n if (!currentSandboxId) {\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Looking for existing environment...'\n );\n const existingId = await findExistingSandbox(apollo, workspaceId, currentMode, authContext);\n\n if (existingId) {\n currentSandboxId = existingId;\n setSandbox(currentMode, currentSandboxId, 'provisioning');\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Reusing assistant environment...'\n );\n await waitForSandboxReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 300_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'provisioning', message)\n );\n await waitForAssistantServiceReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 90_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'connecting', message)\n );\n setSandbox(currentMode, currentSandboxId, 'running');\n } else {\n if (currentMode === 'api-calls' && !getAccessToken()) {\n throw new Error(\n 'API Calls mode requires an authenticated session. Please sign in again.'\n );\n }\n\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Starting assistant environment...'\n );\n setSandbox(currentMode, null, 'provisioning');\n currentSandboxId = await createAssistantSandbox(\n apollo,\n currentMode,\n workspaceId,\n authContext\n );\n\n setSandbox(currentMode, currentSandboxId, 'provisioning');\n await waitForSandboxReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 300_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'provisioning', message)\n );\n await waitForAssistantServiceReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 90_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'connecting', message)\n );\n setSandbox(currentMode, currentSandboxId, 'running');\n }\n }\n\n let sessionId =\n useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === conversationId)\n ?.backendSessionId ?? null;\n\n if (sessionId) {\n try {\n const session = await getAssistantSession(\n apollo,\n currentSandboxId,\n workspaceId,\n sessionId,\n authContext\n );\n if (session.mode !== currentMode) {\n throw new Error(\n `Session mode mismatch: expected ${currentMode}, received ${session.mode}`\n );\n }\n } catch {\n clearBackendSessionId(conversationId);\n sessionId = null;\n }\n }\n\n if (!sessionId) {\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'connecting',\n 'Preparing session...'\n );\n const result = await createAssistantSession(\n apollo,\n currentSandboxId,\n workspaceId,\n currentMode,\n authContext\n );\n sessionId = result.sessionId;\n setBackendSessionId(conversationId, sessionId);\n }\n\n return {\n sandboxId: currentSandboxId,\n sessionId,\n };\n },\n [\n apollo,\n clearBackendSessionId,\n getAccessToken,\n setBackendSessionId,\n setSandbox,\n updateConversationStage,\n ]\n );\n\n const handleSend = useCallback(\n async (content: string) => {\n // Only block re-entry while a turn is genuinely in-flight. Earlier code\n // checked for *any* entry in the map, which stuck around as `complete`\n // / `error` after the first turn — making every subsequent message\n // silently disappear (the input cleared but handleSend bailed before\n // adding the user message to the conversation).\n if (activeConversationId) {\n const inflightStatus = processingByConversationId[activeConversationId]?.status;\n if (\n inflightStatus === 'provisioning' ||\n inflightStatus === 'connecting' ||\n inflightStatus === 'streaming'\n ) {\n return;\n }\n }\n\n const selectedMode = useAssistantStore.getState().mode;\n let conversationId = activeConversationId;\n const currentConversation = conversationId\n ? useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === conversationId)\n : undefined;\n\n if (!conversationId) {\n conversationId = createConversation(undefined, selectedMode);\n } else if (\n currentConversation &&\n currentConversation.mode !== selectedMode &&\n (currentConversation.messages.length > 0 || currentConversation.backendSessionId)\n ) {\n conversationId = createConversation(undefined, selectedMode);\n } else if (currentConversation && currentConversation.mode !== selectedMode) {\n setConversationMode(conversationId, selectedMode);\n }\n\n if (!conversationId) return;\n\n const workspaceId = getWorkspaceId();\n const authContext: AssistantSandboxAuthContext = {\n accessToken: getAccessToken(),\n workspaceToken: getWorkspaceToken(),\n userId,\n organizationId: getOrganizationId(),\n };\n\n const userMessage: Message = {\n id: generateId(),\n role: 'user',\n content,\n timestamp: new Date().toISOString(),\n };\n addMessage(conversationId, userMessage);\n\n const assistantMessageId = generateId();\n addMessage(conversationId, {\n id: assistantMessageId,\n role: 'assistant',\n content: '',\n timestamp: new Date().toISOString(),\n });\n\n const applyAssistantResponse = (\n response: string,\n artifacts?: AssistantArtifact[],\n sessionId?: string,\n sandboxId?: string\n ) => {\n patchMessage(conversationId, assistantMessageId, {\n content: sanitizeAssistantResponse(response),\n metadata:\n artifacts?.length || sessionId || sandboxId\n ? {\n artifacts,\n mode: selectedMode,\n sessionId,\n sandboxId,\n }\n : undefined,\n });\n };\n\n const sendWithRealBackend = async (attempt: 1 | 2 = 1): Promise<void> => {\n try {\n const { sandboxId, sessionId } = await ensureAssistantRuntime(\n conversationId,\n assistantMessageId,\n selectedMode,\n workspaceId,\n authContext\n );\n\n updateConversationStage(conversationId, assistantMessageId, 'streaming', 'Thinking...');\n const promptStartedAt = Date.now();\n await sendAssistantPromptAsync(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n content,\n selectedMode,\n gatherPageContext(),\n authContext\n );\n\n let result = buildAssistantProgress([], promptStartedAt);\n const timeoutAt = Date.now() + 180_000;\n let lastTtlExtensionAt = Date.now();\n\n while (Date.now() < timeoutAt) {\n const sessionMessages = await getAssistantMessages(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n authContext,\n 50\n );\n\n result = buildAssistantProgress(sessionMessages, promptStartedAt);\n patchMessage(conversationId, assistantMessageId, {\n content: sanitizeAssistantResponse(result.content),\n });\n\n if (result.done) break;\n\n if (Date.now() - lastTtlExtensionAt > 30_000) {\n await extendAssistantSandboxTTL(\n apollo,\n sandboxId,\n workspaceId,\n 600,\n authContext\n ).catch(() => undefined);\n lastTtlExtensionAt = Date.now();\n }\n\n await new Promise((resolve) => setTimeout(resolve, 1500));\n }\n\n if (!result.done) {\n throw new Error('Assistant response timed out while streaming progress');\n }\n\n const artifacts =\n selectedMode === 'vibe-plugins'\n ? await getAssistantArtifacts(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n authContext\n ).catch(() => [])\n : undefined;\n\n applyAssistantResponse(result.content, artifacts, sessionId, sandboxId);\n\n if (result.tokens) {\n patchMessage(conversationId, assistantMessageId, { tokens: result.tokens });\n }\n setConversationProcessing(conversationId, 'complete', assistantMessageId, null);\n } catch (error) {\n if (attempt === 1 && isRecoverableAssistantError(error)) {\n clearBackendSessionId(conversationId);\n clearSandbox(selectedMode);\n return sendWithRealBackend(2);\n }\n\n throw error;\n }\n };\n\n try {\n if (!workspaceId) {\n throw new Error('Assistant backend is unavailable without an active workspace context.');\n }\n\n {\n const localResponse = await resolveLocalPromptByMode(selectedMode, content);\n if (localResponse) {\n applyAssistantResponse(localResponse);\n setConversationProcessing(conversationId, 'complete', assistantMessageId, null);\n return;\n }\n }\n\n await sendWithRealBackend();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Something went wrong';\n\n {\n const localResponse = await resolveLocalPromptByMode(selectedMode, content).catch(\n () => null\n );\n if (localResponse) {\n applyAssistantResponse(localResponse);\n setConversationProcessing(conversationId, 'complete', assistantMessageId, null);\n return;\n }\n }\n\n setConversationProcessing(conversationId, 'error', assistantMessageId, errorMessage);\n updateMessage(\n conversationId,\n assistantMessageId,\n `Sorry, an error occurred: ${errorMessage}. Please try again.`\n );\n }\n },\n [\n activeConversationId,\n addMessage,\n apollo,\n clearBackendSessionId,\n clearSandbox,\n conversations,\n createConversation,\n ensureAssistantRuntime,\n getAccessToken,\n getWorkspaceToken,\n patchMessage,\n processingByConversationId,\n setBackendSessionId,\n setConversationMode,\n setConversationProcessing,\n resolveLocalPromptByMode,\n updateConversationStage,\n updateMessage,\n userId,\n ]\n );\n\n const handleSuggestionClick = useCallback(\n (suggestion: string) => {\n void handleSend(suggestion);\n },\n [handleSend]\n );\n\n // Auto-send any prompt that was queued by `useOpenAssistantHelp` (from\n // the shared GetHelpButton). We keep the latest handleSend in a ref so\n // the subscription effect below doesn't need to tear down on every\n // render — the store subscription lives for the component lifetime and\n // always calls the freshest handler.\n const handleSendRef = useRef(handleSend);\n useEffect(() => {\n handleSendRef.current = handleSend;\n }, [handleSend]);\n\n useEffect(() => {\n // Flush any prompt that was queued before this component mounted.\n const initial = useAssistantStore.getState().consumePendingPrompt();\n if (initial) {\n void handleSendRef.current(initial);\n }\n\n // Watch for new prompts queued while we're already mounted.\n const unsubscribe = useAssistantStore.subscribe((state, prev) => {\n if (state.pendingPrompt && state.pendingPrompt !== prev.pendingPrompt) {\n const queued = useAssistantStore.getState().consumePendingPrompt();\n if (queued) {\n void handleSendRef.current(queued);\n }\n }\n });\n return unsubscribe;\n }, []);\n\n const handleModeChange = useCallback(\n (newMode: AssistantMode) => {\n const currentConversation = activeConversationId\n ? useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === activeConversationId)\n : undefined;\n\n if (\n currentConversation &&\n (currentConversation.messages.length > 0 || currentConversation.backendSessionId)\n ) {\n if (currentConversation.mode !== newMode) {\n const reusableDraft = useAssistantStore\n .getState()\n .conversations.find(\n (conversation) =>\n conversation.mode === newMode &&\n conversation.messages.length === 0 &&\n !conversation.backendSessionId &&\n !useAssistantStore.getState().processingByConversationId[conversation.id]\n );\n\n if (reusableDraft) {\n setActiveConversation(reusableDraft.id);\n } else {\n createConversation(undefined, newMode);\n }\n }\n } else if (activeConversationId) {\n setConversationMode(activeConversationId, newMode);\n }\n\n setMode(newMode);\n },\n [activeConversationId, createConversation, setActiveConversation, setConversationMode, setMode]\n );\n\n return (\n <div className=\"flex flex-col flex-1 overflow-hidden pt-3\">\n {messages.length === 0 ? (\n <AssistantWelcome mode={mode} onSuggestionClick={handleSuggestionClick} />\n ) : (\n <AssistantMessageList messages={messages} streamingMessageId={activeStreamingMessageId} />\n )}\n\n <div>\n <div className=\"px-3 pb-1\">\n <AssistantModeSelector mode={mode} onModeChange={handleModeChange} />\n </div>\n <AssistantChatInput\n onSend={(value) => void handleSend(value)}\n disabled={activeIsProcessing}\n placeholder={tr(\n 'vibecontrols.assistant.chatArea.placeholder',\n `Ask about ${mode.replace(/-/g, ' ')}...`\n )}\n />\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
2
|
import { useAvailableNoteTagsQuery as t, useCreateNoteTagMutation as n } from "../../generated/wspace-operations.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { useVibeControls as r } from "../../providers/VibeControlsProvider.js";
|
|
4
|
+
import { useEffect as i, useState as a } from "react";
|
|
5
|
+
import { Check as o, Edit as s, Eye as c, Plus as l, Save as u, Tag as d, X as f } from "lucide-react";
|
|
6
|
+
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
6
7
|
//#region src/components/notes/NoteEditor.tsx
|
|
7
|
-
var
|
|
8
|
+
var h = [
|
|
8
9
|
"var(--color-status-error-text)",
|
|
9
10
|
"var(--color-status-warning-text)",
|
|
10
11
|
"var(--color-status-success-text)",
|
|
@@ -13,84 +14,84 @@ var m = [
|
|
|
13
14
|
"var(--color-accent-purple)",
|
|
14
15
|
"var(--color-text-tertiary)"
|
|
15
16
|
];
|
|
16
|
-
function
|
|
17
|
-
let
|
|
18
|
-
title:
|
|
19
|
-
content:
|
|
20
|
-
vibeId:
|
|
21
|
-
isPinned:
|
|
22
|
-
}), [
|
|
17
|
+
function g({ note: g, vibeOptions: _ = [], defaultVibeId: v, hideVibeSelect: y, onSubmit: b, onCancel: x, isLoading: S }) {
|
|
18
|
+
let C = e(), { workspaceId: w } = r(), [T, E] = a(!1), [D, O] = a({
|
|
19
|
+
title: g?.title || "",
|
|
20
|
+
content: g?.content || "",
|
|
21
|
+
vibeId: g?.vibeId || v || "",
|
|
22
|
+
isPinned: g?.isPinned ?? !1
|
|
23
|
+
}), [k, A] = a(/* @__PURE__ */ new Set()), [j, M] = a(!1), [N, P] = a(!1), { data: F, refetch: I } = t(), L = (F?.availableNoteTags ?? []).map((e) => ({
|
|
23
24
|
id: e.id,
|
|
24
25
|
key: e.key,
|
|
25
26
|
label: e.label,
|
|
26
27
|
color: e.color ?? null
|
|
27
28
|
}));
|
|
28
|
-
|
|
29
|
-
if (
|
|
29
|
+
i(() => {
|
|
30
|
+
if (j || L.length === 0) return;
|
|
30
31
|
let e = /* @__PURE__ */ new Set();
|
|
31
|
-
if (
|
|
32
|
-
else if (
|
|
33
|
-
let n =
|
|
32
|
+
if (g?.tagAssignments && g.tagAssignments.length > 0) g.tagAssignments.forEach((t) => e.add(t.tagId));
|
|
33
|
+
else if (g?.tags && g.tags.length > 0) for (let t of g.tags) {
|
|
34
|
+
let n = L.find((e) => e.label.toLowerCase() === t.toLowerCase() || e.key === t.toLowerCase());
|
|
34
35
|
n && e.add(n.id);
|
|
35
36
|
}
|
|
36
|
-
e.size > 0 &&
|
|
37
|
+
e.size > 0 && A(e), M(!0);
|
|
37
38
|
}, [
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
L,
|
|
40
|
+
g,
|
|
41
|
+
j
|
|
41
42
|
]);
|
|
42
|
-
let [
|
|
43
|
-
let e =
|
|
44
|
-
if (!(!e ||
|
|
45
|
-
|
|
43
|
+
let [R] = n(), [z, B] = a(""), [V, H] = a("var(--color-accent-purple)"), [U, W] = a(!1), G = async () => {
|
|
44
|
+
let e = z.trim();
|
|
45
|
+
if (!(!e || U)) {
|
|
46
|
+
W(!0);
|
|
46
47
|
try {
|
|
47
|
-
let t = (await
|
|
48
|
+
let t = (await R({ variables: { input: {
|
|
48
49
|
label: e,
|
|
49
|
-
color:
|
|
50
|
+
color: V
|
|
50
51
|
} } })).data?.createNoteTag;
|
|
51
|
-
t && (
|
|
52
|
+
t && (A((e) => new Set(e).add(t.id)), await I()), B("");
|
|
52
53
|
} catch (e) {
|
|
53
54
|
console.error("Failed to create tag:", e);
|
|
54
55
|
} finally {
|
|
55
|
-
|
|
56
|
+
W(!1);
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
|
-
},
|
|
59
|
-
|
|
59
|
+
}, K = (e) => {
|
|
60
|
+
A((t) => {
|
|
60
61
|
let n = new Set(t);
|
|
61
62
|
return n.has(e) ? n.delete(e) : n.add(e), n;
|
|
62
63
|
});
|
|
63
|
-
},
|
|
64
|
-
e.preventDefault(),
|
|
65
|
-
...
|
|
66
|
-
workspaceId: "
|
|
67
|
-
title:
|
|
68
|
-
content:
|
|
69
|
-
vibeId:
|
|
70
|
-
isPinned:
|
|
71
|
-
selectedTagIds: Array.from(
|
|
64
|
+
}, q = (e) => {
|
|
65
|
+
e.preventDefault(), b({
|
|
66
|
+
...g?.id ? { id: g.id } : {},
|
|
67
|
+
workspaceId: w ?? "",
|
|
68
|
+
title: D.title,
|
|
69
|
+
content: D.content,
|
|
70
|
+
vibeId: D.vibeId || void 0,
|
|
71
|
+
isPinned: D.isPinned,
|
|
72
|
+
selectedTagIds: Array.from(k)
|
|
72
73
|
});
|
|
73
|
-
},
|
|
74
|
+
}, J = (e) => {
|
|
74
75
|
let t = [], n = e, r = 0;
|
|
75
76
|
for (; n.length > 0;) {
|
|
76
77
|
let e = n.match(/^(\*\*|__)(.+?)\1/);
|
|
77
78
|
if (e) {
|
|
78
|
-
t.push(/* @__PURE__ */
|
|
79
|
+
t.push(/* @__PURE__ */ p("strong", { children: J(e[2]) }, r++)), n = n.slice(e[0].length);
|
|
79
80
|
continue;
|
|
80
81
|
}
|
|
81
82
|
let i = n.match(/^(\*|_)([^*_]+?)\1/);
|
|
82
83
|
if (i) {
|
|
83
|
-
t.push(/* @__PURE__ */
|
|
84
|
+
t.push(/* @__PURE__ */ p("em", { children: J(i[2]) }, r++)), n = n.slice(i[0].length);
|
|
84
85
|
continue;
|
|
85
86
|
}
|
|
86
87
|
let a = n.match(/^~~(.+?)~~/);
|
|
87
88
|
if (a) {
|
|
88
|
-
t.push(/* @__PURE__ */
|
|
89
|
+
t.push(/* @__PURE__ */ p("del", { children: J(a[1]) }, r++)), n = n.slice(a[0].length);
|
|
89
90
|
continue;
|
|
90
91
|
}
|
|
91
92
|
let o = n.match(/^`([^`]+)`/);
|
|
92
93
|
if (o) {
|
|
93
|
-
t.push(/* @__PURE__ */
|
|
94
|
+
t.push(/* @__PURE__ */ p("code", {
|
|
94
95
|
className: "bg-bg-elevated px-1.5 py-0.5 rounded text-sm font-mono text-text-primary",
|
|
95
96
|
children: o[1]
|
|
96
97
|
}, r++)), n = n.slice(o[0].length);
|
|
@@ -99,13 +100,13 @@ function h({ note: h, vibeOptions: g = [], defaultVibeId: _, hideVibeSelect: v,
|
|
|
99
100
|
let s = n.match(/^\[([^\]]+)\]\(([^)]+)\)/);
|
|
100
101
|
if (s) {
|
|
101
102
|
let e = s[2];
|
|
102
|
-
/^(https?:\/\/|mailto:|\/|#)/i.test(e) ? t.push(/* @__PURE__ */
|
|
103
|
+
/^(https?:\/\/|mailto:|\/|#)/i.test(e) ? t.push(/* @__PURE__ */ p("a", {
|
|
103
104
|
href: e,
|
|
104
105
|
target: "_blank",
|
|
105
106
|
rel: "noopener noreferrer",
|
|
106
107
|
className: "text-action-primary-bg hover:underline",
|
|
107
108
|
children: s[1]
|
|
108
|
-
}, r++)) : t.push(/* @__PURE__ */
|
|
109
|
+
}, r++)) : t.push(/* @__PURE__ */ p("span", {
|
|
109
110
|
className: "text-text-secondary",
|
|
110
111
|
children: s[1]
|
|
111
112
|
}, r++)), n = n.slice(s[0].length);
|
|
@@ -115,155 +116,155 @@ function h({ note: h, vibeOptions: g = [], defaultVibeId: _, hideVibeSelect: v,
|
|
|
115
116
|
}
|
|
116
117
|
return t;
|
|
117
118
|
};
|
|
118
|
-
return /* @__PURE__ */
|
|
119
|
+
return /* @__PURE__ */ p("div", {
|
|
119
120
|
className: "fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay",
|
|
120
|
-
children: /* @__PURE__ */
|
|
121
|
+
children: /* @__PURE__ */ m("div", {
|
|
121
122
|
className: "w-full max-w-3xl bg-bg-surface rounded-lg shadow-xl max-h-[90vh] flex flex-col",
|
|
122
|
-
children: [/* @__PURE__ */
|
|
123
|
+
children: [/* @__PURE__ */ m("div", {
|
|
123
124
|
className: "flex items-center justify-between px-6 py-4 border-b border-border-default",
|
|
124
|
-
children: [/* @__PURE__ */
|
|
125
|
+
children: [/* @__PURE__ */ p("h2", {
|
|
125
126
|
className: "text-lg font-semibold text-text-primary",
|
|
126
|
-
children:
|
|
127
|
-
}), /* @__PURE__ */
|
|
127
|
+
children: g ? C("vibecontrols.notes.editNote", "Edit Note") : C("vibecontrols.notes.newNote", "New Note")
|
|
128
|
+
}), /* @__PURE__ */ m("div", {
|
|
128
129
|
className: "flex items-center gap-2",
|
|
129
|
-
children: [/* @__PURE__ */
|
|
130
|
+
children: [/* @__PURE__ */ m("button", {
|
|
130
131
|
type: "button",
|
|
131
|
-
onClick: () =>
|
|
132
|
-
className: `inline-flex items-center gap-2 px-3 py-1.5 text-sm rounded-md transition-colors ${
|
|
133
|
-
children: [
|
|
134
|
-
}), /* @__PURE__ */
|
|
135
|
-
onClick:
|
|
132
|
+
onClick: () => E(!T),
|
|
133
|
+
className: `inline-flex items-center gap-2 px-3 py-1.5 text-sm rounded-md transition-colors ${T ? "bg-action-primary-bg text-action-primary-text" : "bg-bg-sunken text-text-primary hover:bg-bg-elevated"}`,
|
|
134
|
+
children: [p(T ? s : c, { className: "w-4 h-4" }), T ? C("vibecontrols.notes.edit", "Edit") : C("vibecontrols.notes.preview", "Preview")]
|
|
135
|
+
}), /* @__PURE__ */ p("button", {
|
|
136
|
+
onClick: x,
|
|
136
137
|
className: "p-1 rounded hover:bg-bg-sunken transition-colors",
|
|
137
|
-
children: /* @__PURE__ */ f
|
|
138
|
+
children: /* @__PURE__ */ p(f, { className: "w-5 h-5 text-text-secondary" })
|
|
138
139
|
})]
|
|
139
140
|
})]
|
|
140
|
-
}), /* @__PURE__ */
|
|
141
|
-
onSubmit:
|
|
141
|
+
}), /* @__PURE__ */ m("form", {
|
|
142
|
+
onSubmit: q,
|
|
142
143
|
className: "flex-1 flex flex-col overflow-hidden",
|
|
143
|
-
children: [/* @__PURE__ */
|
|
144
|
+
children: [/* @__PURE__ */ m("div", {
|
|
144
145
|
className: "p-6 space-y-4 flex-1 overflow-y-auto",
|
|
145
146
|
children: [
|
|
146
|
-
/* @__PURE__ */
|
|
147
|
+
/* @__PURE__ */ p("div", { children: /* @__PURE__ */ p("input", {
|
|
147
148
|
type: "text",
|
|
148
|
-
value:
|
|
149
|
-
onChange: (e) =>
|
|
150
|
-
...
|
|
149
|
+
value: D.title,
|
|
150
|
+
onChange: (e) => O({
|
|
151
|
+
...D,
|
|
151
152
|
title: e.target.value
|
|
152
153
|
}),
|
|
153
|
-
placeholder:
|
|
154
|
+
placeholder: C("vibecontrols.notes.titlePlaceholder", "Note title..."),
|
|
154
155
|
required: !0,
|
|
155
156
|
className: "w-full px-0 py-2 text-lg sm:text-2xl font-bold bg-transparent border-0 border-b border-border-default text-text-primary placeholder:text-text-placeholder focus:outline-none focus:border-action-primary-bg"
|
|
156
157
|
}) }),
|
|
157
|
-
/* @__PURE__ */
|
|
158
|
-
className: `grid ${
|
|
159
|
-
children: [!
|
|
158
|
+
/* @__PURE__ */ m("div", {
|
|
159
|
+
className: `grid ${y ? "grid-cols-1" : "grid-cols-2"} gap-4`,
|
|
160
|
+
children: [!y && /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
160
161
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
161
|
-
children:
|
|
162
|
-
}), /* @__PURE__ */
|
|
163
|
-
value:
|
|
164
|
-
onChange: (e) =>
|
|
165
|
-
...
|
|
162
|
+
children: C("vibecontrols.notes.relatedVibe", "Related Vibe (optional)")
|
|
163
|
+
}), /* @__PURE__ */ m("select", {
|
|
164
|
+
value: D.vibeId,
|
|
165
|
+
onChange: (e) => O({
|
|
166
|
+
...D,
|
|
166
167
|
vibeId: e.target.value
|
|
167
168
|
}),
|
|
168
169
|
className: "w-full px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg",
|
|
169
|
-
children: [/* @__PURE__ */
|
|
170
|
+
children: [/* @__PURE__ */ p("option", {
|
|
170
171
|
value: "",
|
|
171
172
|
children: "No vibe"
|
|
172
|
-
}),
|
|
173
|
+
}), _.map((e) => /* @__PURE__ */ p("option", {
|
|
173
174
|
value: e.id,
|
|
174
175
|
children: e.name
|
|
175
176
|
}, e.id))]
|
|
176
|
-
})] }), /* @__PURE__ */
|
|
177
|
-
/* @__PURE__ */
|
|
177
|
+
})] }), /* @__PURE__ */ m("div", { children: [
|
|
178
|
+
/* @__PURE__ */ p("label", {
|
|
178
179
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
179
|
-
children:
|
|
180
|
+
children: C("vibecontrols.notes.tags", "Tags")
|
|
180
181
|
}),
|
|
181
|
-
/* @__PURE__ */
|
|
182
|
+
/* @__PURE__ */ m("div", {
|
|
182
183
|
className: "flex flex-wrap gap-1.5 min-h-[38px] px-3 py-2 bg-bg-sunken border border-border-default rounded-md",
|
|
183
|
-
children: [
|
|
184
|
+
children: [L.filter((e) => k.has(e.id)).map((e) => /* @__PURE__ */ m("span", {
|
|
184
185
|
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-action-primary-bg/10 text-action-primary-bg rounded",
|
|
185
186
|
children: [
|
|
186
|
-
/* @__PURE__ */
|
|
187
|
+
/* @__PURE__ */ p(d, { className: "w-3 h-3" }),
|
|
187
188
|
e.label,
|
|
188
|
-
/* @__PURE__ */
|
|
189
|
+
/* @__PURE__ */ p("button", {
|
|
189
190
|
type: "button",
|
|
190
|
-
onClick: () =>
|
|
191
|
+
onClick: () => K(e.id),
|
|
191
192
|
className: "ml-0.5 hover:text-status-error-text",
|
|
192
|
-
children: /* @__PURE__ */ f
|
|
193
|
+
children: /* @__PURE__ */ p(f, { className: "w-3 h-3" })
|
|
193
194
|
})
|
|
194
195
|
]
|
|
195
|
-
}, e.id)), /* @__PURE__ */
|
|
196
|
+
}, e.id)), /* @__PURE__ */ m("button", {
|
|
196
197
|
type: "button",
|
|
197
|
-
onClick: () =>
|
|
198
|
+
onClick: () => P(!N),
|
|
198
199
|
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs text-text-secondary hover:text-text-primary hover:bg-bg-elevated rounded transition-colors",
|
|
199
|
-
children: [/* @__PURE__ */
|
|
200
|
+
children: [/* @__PURE__ */ p(d, { className: "w-3 h-3" }), k.size === 0 ? "Add tags..." : "+"]
|
|
200
201
|
})]
|
|
201
202
|
}),
|
|
202
|
-
|
|
203
|
+
N && /* @__PURE__ */ m("div", {
|
|
203
204
|
className: "mt-1 bg-bg-surface border border-border-default rounded-md shadow-lg",
|
|
204
205
|
children: [
|
|
205
|
-
/* @__PURE__ */
|
|
206
|
+
/* @__PURE__ */ m("div", {
|
|
206
207
|
className: "flex items-center gap-2 p-2 border-b border-border-subtle",
|
|
207
208
|
children: [
|
|
208
|
-
/* @__PURE__ */
|
|
209
|
+
/* @__PURE__ */ p("span", {
|
|
209
210
|
"aria-hidden": "true",
|
|
210
211
|
className: "w-6 h-6 rounded-full border-2 border-border-default",
|
|
211
|
-
style: { backgroundColor:
|
|
212
|
+
style: { backgroundColor: V }
|
|
212
213
|
}),
|
|
213
|
-
/* @__PURE__ */
|
|
214
|
+
/* @__PURE__ */ p("input", {
|
|
214
215
|
type: "text",
|
|
215
|
-
value:
|
|
216
|
-
onChange: (e) =>
|
|
216
|
+
value: z,
|
|
217
|
+
onChange: (e) => B(e.target.value),
|
|
217
218
|
onKeyDown: (e) => {
|
|
218
|
-
e.key === "Enter" && (e.preventDefault(),
|
|
219
|
+
e.key === "Enter" && (e.preventDefault(), G());
|
|
219
220
|
},
|
|
220
221
|
placeholder: "New tag name... (Enter to create)",
|
|
221
|
-
disabled:
|
|
222
|
+
disabled: U,
|
|
222
223
|
className: "flex-1 px-2 py-1 text-sm bg-bg-sunken border border-border-default rounded text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50"
|
|
223
224
|
}),
|
|
224
|
-
/* @__PURE__ */
|
|
225
|
+
/* @__PURE__ */ p("button", {
|
|
225
226
|
type: "button",
|
|
226
|
-
onClick:
|
|
227
|
-
disabled: !
|
|
227
|
+
onClick: G,
|
|
228
|
+
disabled: !z.trim() || U,
|
|
228
229
|
className: "p-1 text-action-primary-bg hover:bg-action-primary-bg/10 rounded transition-colors disabled:opacity-30",
|
|
229
|
-
children: /* @__PURE__ */
|
|
230
|
+
children: /* @__PURE__ */ p(l, { className: "w-4 h-4" })
|
|
230
231
|
})
|
|
231
232
|
]
|
|
232
233
|
}),
|
|
233
|
-
/* @__PURE__ */
|
|
234
|
+
/* @__PURE__ */ p("div", {
|
|
234
235
|
className: "flex items-center gap-1 px-2 py-1.5 border-b border-border-subtle",
|
|
235
|
-
children:
|
|
236
|
+
children: h.map((e) => /* @__PURE__ */ p("button", {
|
|
236
237
|
type: "button",
|
|
237
|
-
onClick: () =>
|
|
238
|
-
className: `w-4 h-4 rounded-full transition-transform ${
|
|
238
|
+
onClick: () => H(e),
|
|
239
|
+
className: `w-4 h-4 rounded-full transition-transform ${V === e ? "ring-2 ring-action-primary-bg scale-125" : "hover:scale-110"}`,
|
|
239
240
|
style: { backgroundColor: e }
|
|
240
241
|
}, e))
|
|
241
242
|
}),
|
|
242
|
-
/* @__PURE__ */
|
|
243
|
+
/* @__PURE__ */ p("div", {
|
|
243
244
|
className: "max-h-32 overflow-y-auto p-1",
|
|
244
|
-
children:
|
|
245
|
+
children: L.length === 0 ? /* @__PURE__ */ p("p", {
|
|
245
246
|
className: "text-xs text-text-muted px-2 py-1",
|
|
246
247
|
children: "No tags yet — create one above"
|
|
247
|
-
}) :
|
|
248
|
+
}) : L.map((e) => /* @__PURE__ */ m("button", {
|
|
248
249
|
type: "button",
|
|
249
|
-
onClick: () =>
|
|
250
|
+
onClick: () => K(e.id),
|
|
250
251
|
className: "flex items-center justify-between w-full px-2 py-1.5 text-sm text-text-primary hover:bg-bg-sunken rounded transition-colors",
|
|
251
|
-
children: [/* @__PURE__ */
|
|
252
|
+
children: [/* @__PURE__ */ m("span", {
|
|
252
253
|
className: "flex items-center gap-2",
|
|
253
|
-
children: [e.color && /* @__PURE__ */
|
|
254
|
+
children: [e.color && /* @__PURE__ */ p("span", {
|
|
254
255
|
className: "w-2.5 h-2.5 rounded-full",
|
|
255
256
|
style: { backgroundColor: e.color }
|
|
256
257
|
}), e.label]
|
|
257
|
-
}),
|
|
258
|
+
}), k.has(e.id) && /* @__PURE__ */ p(o, { className: "w-4 h-4 text-action-primary-bg" })]
|
|
258
259
|
}, e.id))
|
|
259
260
|
})
|
|
260
261
|
]
|
|
261
262
|
})
|
|
262
263
|
] })]
|
|
263
264
|
}),
|
|
264
|
-
/* @__PURE__ */
|
|
265
|
+
/* @__PURE__ */ p("div", {
|
|
265
266
|
className: "flex-1 min-h-[300px]",
|
|
266
|
-
children:
|
|
267
|
+
children: T ? /* @__PURE__ */ p("div", {
|
|
267
268
|
className: "h-full p-4 bg-bg-sunken rounded-md prose prose-sm dark:prose-invert max-w-none overflow-y-auto",
|
|
268
269
|
children: ((e) => {
|
|
269
270
|
let t = e.split("\n"), n = [], r = 0;
|
|
@@ -272,11 +273,11 @@ function h({ note: h, vibeOptions: g = [], defaultVibeId: _, hideVibeSelect: v,
|
|
|
272
273
|
if (e.startsWith("```")) {
|
|
273
274
|
let i = e.slice(3).trim(), a = [];
|
|
274
275
|
for (r++; r < t.length && !t[r].startsWith("```");) a.push(t[r]), r++;
|
|
275
|
-
n.push(/* @__PURE__ */
|
|
276
|
+
n.push(/* @__PURE__ */ p("pre", {
|
|
276
277
|
className: "bg-bg-elevated rounded-md p-3 my-2 overflow-x-auto",
|
|
277
|
-
children: /* @__PURE__ */
|
|
278
|
+
children: /* @__PURE__ */ m("code", {
|
|
278
279
|
className: "text-sm font-mono text-text-primary",
|
|
279
|
-
children: [i && /* @__PURE__ */
|
|
280
|
+
children: [i && /* @__PURE__ */ p("span", {
|
|
280
281
|
className: "text-xs text-text-secondary block mb-2",
|
|
281
282
|
children: i
|
|
282
283
|
}), a.join("\n")]
|
|
@@ -285,40 +286,40 @@ function h({ note: h, vibeOptions: g = [], defaultVibeId: _, hideVibeSelect: v,
|
|
|
285
286
|
continue;
|
|
286
287
|
}
|
|
287
288
|
if (e.startsWith("# ")) {
|
|
288
|
-
n.push(/* @__PURE__ */
|
|
289
|
+
n.push(/* @__PURE__ */ p("h1", {
|
|
289
290
|
className: "text-2xl font-bold mb-2 mt-4",
|
|
290
|
-
children:
|
|
291
|
+
children: J(e.slice(2))
|
|
291
292
|
}, n.length)), r++;
|
|
292
293
|
continue;
|
|
293
294
|
}
|
|
294
295
|
if (e.startsWith("## ")) {
|
|
295
|
-
n.push(/* @__PURE__ */
|
|
296
|
+
n.push(/* @__PURE__ */ p("h2", {
|
|
296
297
|
className: "text-xl font-bold mb-2 mt-3",
|
|
297
|
-
children:
|
|
298
|
+
children: J(e.slice(3))
|
|
298
299
|
}, n.length)), r++;
|
|
299
300
|
continue;
|
|
300
301
|
}
|
|
301
302
|
if (e.startsWith("### ")) {
|
|
302
|
-
n.push(/* @__PURE__ */
|
|
303
|
+
n.push(/* @__PURE__ */ p("h3", {
|
|
303
304
|
className: "text-lg font-bold mb-2 mt-2",
|
|
304
|
-
children:
|
|
305
|
+
children: J(e.slice(4))
|
|
305
306
|
}, n.length)), r++;
|
|
306
307
|
continue;
|
|
307
308
|
}
|
|
308
309
|
if (e.startsWith("> ")) {
|
|
309
|
-
n.push(/* @__PURE__ */
|
|
310
|
+
n.push(/* @__PURE__ */ p("blockquote", {
|
|
310
311
|
className: "border-l-4 border-border-default pl-4 my-2 italic text-text-secondary",
|
|
311
|
-
children:
|
|
312
|
+
children: J(e.slice(2))
|
|
312
313
|
}, n.length)), r++;
|
|
313
314
|
continue;
|
|
314
315
|
}
|
|
315
316
|
if (e.match(/^[-*] /)) {
|
|
316
317
|
let e = [];
|
|
317
|
-
for (; r < t.length && t[r].match(/^[-*] /);) e.push(/* @__PURE__ */
|
|
318
|
+
for (; r < t.length && t[r].match(/^[-*] /);) e.push(/* @__PURE__ */ p("li", {
|
|
318
319
|
className: "list-disc",
|
|
319
|
-
children:
|
|
320
|
+
children: J(t[r].slice(2))
|
|
320
321
|
}, e.length)), r++;
|
|
321
|
-
n.push(/* @__PURE__ */
|
|
322
|
+
n.push(/* @__PURE__ */ p("ul", {
|
|
322
323
|
className: "ml-6 my-2",
|
|
323
324
|
children: e
|
|
324
325
|
}, n.length));
|
|
@@ -329,19 +330,19 @@ function h({ note: h, vibeOptions: g = [], defaultVibeId: _, hideVibeSelect: v,
|
|
|
329
330
|
for (; r < t.length;) {
|
|
330
331
|
let n = t[r].match(/^(\d+)\. /);
|
|
331
332
|
if (!n) break;
|
|
332
|
-
e.push(/* @__PURE__ */
|
|
333
|
+
e.push(/* @__PURE__ */ p("li", {
|
|
333
334
|
className: "list-decimal",
|
|
334
|
-
children:
|
|
335
|
+
children: J(t[r].slice(n[0].length))
|
|
335
336
|
}, e.length)), r++;
|
|
336
337
|
}
|
|
337
|
-
n.push(/* @__PURE__ */
|
|
338
|
+
n.push(/* @__PURE__ */ p("ol", {
|
|
338
339
|
className: "ml-6 my-2",
|
|
339
340
|
children: e
|
|
340
341
|
}, n.length));
|
|
341
342
|
continue;
|
|
342
343
|
}
|
|
343
344
|
if (e.match(/^(-{3,}|\*{3,})$/)) {
|
|
344
|
-
n.push(/* @__PURE__ */
|
|
345
|
+
n.push(/* @__PURE__ */ p("hr", { className: "my-4 border-border-default" }, n.length)), r++;
|
|
345
346
|
continue;
|
|
346
347
|
}
|
|
347
348
|
if (e.startsWith("|") && e.endsWith("|")) {
|
|
@@ -350,19 +351,19 @@ function h({ note: h, vibeOptions: g = [], defaultVibeId: _, hideVibeSelect: v,
|
|
|
350
351
|
let a = i.filter((e) => !e.match(/^\|[\s\-:|]+\|$/)).map((e) => e.slice(1, -1).split("|").map((e) => e.trim()));
|
|
351
352
|
if (a.length > 0) {
|
|
352
353
|
let [e, ...t] = a;
|
|
353
|
-
n.push(/* @__PURE__ */
|
|
354
|
+
n.push(/* @__PURE__ */ m("table", {
|
|
354
355
|
className: "w-full my-2 border-collapse text-sm",
|
|
355
|
-
children: [/* @__PURE__ */
|
|
356
|
+
children: [/* @__PURE__ */ p("thead", { children: /* @__PURE__ */ p("tr", {
|
|
356
357
|
className: "border-b border-border-default",
|
|
357
|
-
children: e.map((e, t) => /* @__PURE__ */
|
|
358
|
+
children: e.map((e, t) => /* @__PURE__ */ p("th", {
|
|
358
359
|
className: "text-left py-2 px-3 font-semibold text-text-primary",
|
|
359
|
-
children:
|
|
360
|
+
children: J(e)
|
|
360
361
|
}, t))
|
|
361
|
-
}) }), /* @__PURE__ */
|
|
362
|
+
}) }), /* @__PURE__ */ p("tbody", { children: t.map((e, t) => /* @__PURE__ */ p("tr", {
|
|
362
363
|
className: "border-b border-border-subtle",
|
|
363
|
-
children: e.map((e, t) => /* @__PURE__ */
|
|
364
|
+
children: e.map((e, t) => /* @__PURE__ */ p("td", {
|
|
364
365
|
className: "py-2 px-3 text-text-primary",
|
|
365
|
-
children:
|
|
366
|
+
children: J(e)
|
|
366
367
|
}, t))
|
|
367
368
|
}, t)) })]
|
|
368
369
|
}, n.length));
|
|
@@ -370,57 +371,57 @@ function h({ note: h, vibeOptions: g = [], defaultVibeId: _, hideVibeSelect: v,
|
|
|
370
371
|
continue;
|
|
371
372
|
}
|
|
372
373
|
if (e === "") {
|
|
373
|
-
n.push(/* @__PURE__ */
|
|
374
|
+
n.push(/* @__PURE__ */ p("br", {}, n.length)), r++;
|
|
374
375
|
continue;
|
|
375
376
|
}
|
|
376
|
-
n.push(/* @__PURE__ */
|
|
377
|
+
n.push(/* @__PURE__ */ p("p", {
|
|
377
378
|
className: "mb-1",
|
|
378
|
-
children:
|
|
379
|
+
children: J(e)
|
|
379
380
|
}, n.length)), r++;
|
|
380
381
|
}
|
|
381
382
|
return n;
|
|
382
|
-
})(
|
|
383
|
-
}) : /* @__PURE__ */
|
|
384
|
-
value:
|
|
385
|
-
onChange: (e) =>
|
|
386
|
-
...
|
|
383
|
+
})(D.content)
|
|
384
|
+
}) : /* @__PURE__ */ p("textarea", {
|
|
385
|
+
value: D.content,
|
|
386
|
+
onChange: (e) => O({
|
|
387
|
+
...D,
|
|
387
388
|
content: e.target.value
|
|
388
389
|
}),
|
|
389
|
-
placeholder:
|
|
390
|
+
placeholder: C("vibecontrols.notes.contentPlaceholder", "Write your note in Markdown..."),
|
|
390
391
|
className: "w-full h-full min-h-[300px] px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg resize-none font-mono"
|
|
391
392
|
})
|
|
392
393
|
}),
|
|
393
|
-
/* @__PURE__ */
|
|
394
|
+
/* @__PURE__ */ m("div", {
|
|
394
395
|
className: "flex items-center gap-2",
|
|
395
|
-
children: [/* @__PURE__ */
|
|
396
|
+
children: [/* @__PURE__ */ p("input", {
|
|
396
397
|
type: "checkbox",
|
|
397
398
|
id: "isPinned",
|
|
398
|
-
checked:
|
|
399
|
-
onChange: (e) =>
|
|
400
|
-
...
|
|
399
|
+
checked: D.isPinned,
|
|
400
|
+
onChange: (e) => O({
|
|
401
|
+
...D,
|
|
401
402
|
isPinned: e.target.checked
|
|
402
403
|
}),
|
|
403
404
|
className: "w-4 h-4 rounded border-border-default text-action-primary-bg focus:ring-action-primary-bg"
|
|
404
|
-
}), /* @__PURE__ */
|
|
405
|
+
}), /* @__PURE__ */ p("label", {
|
|
405
406
|
htmlFor: "isPinned",
|
|
406
407
|
className: "text-sm text-text-primary",
|
|
407
|
-
children:
|
|
408
|
+
children: C("vibecontrols.notes.pinThisNote", "Pin this note")
|
|
408
409
|
})]
|
|
409
410
|
})
|
|
410
411
|
]
|
|
411
|
-
}), /* @__PURE__ */
|
|
412
|
+
}), /* @__PURE__ */ m("div", {
|
|
412
413
|
className: "flex items-center justify-end gap-3 px-6 py-4 border-t border-border-default bg-bg-sunken",
|
|
413
|
-
children: [/* @__PURE__ */
|
|
414
|
+
children: [/* @__PURE__ */ p("button", {
|
|
414
415
|
type: "button",
|
|
415
|
-
onClick:
|
|
416
|
-
disabled:
|
|
416
|
+
onClick: x,
|
|
417
|
+
disabled: S,
|
|
417
418
|
className: "px-4 py-2 text-sm font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-elevated transition-colors disabled:opacity-50",
|
|
418
|
-
children:
|
|
419
|
-
}), /* @__PURE__ */
|
|
419
|
+
children: C("vibecontrols.notes.cancel", "Cancel")
|
|
420
|
+
}), /* @__PURE__ */ m("button", {
|
|
420
421
|
type: "submit",
|
|
421
|
-
disabled:
|
|
422
|
+
disabled: S,
|
|
422
423
|
className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50",
|
|
423
|
-
children: [/* @__PURE__ */
|
|
424
|
+
children: [/* @__PURE__ */ p(u, { className: "w-4 h-4" }), S ? C("vibecontrols.notes.saving", "Saving...") : C("vibecontrols.notes.saveNote", "Save Note")]
|
|
424
425
|
})]
|
|
425
426
|
})]
|
|
426
427
|
})]
|
|
@@ -428,6 +429,6 @@ function h({ note: h, vibeOptions: g = [], defaultVibeId: _, hideVibeSelect: v,
|
|
|
428
429
|
});
|
|
429
430
|
}
|
|
430
431
|
//#endregion
|
|
431
|
-
export {
|
|
432
|
+
export { g as NoteEditor };
|
|
432
433
|
|
|
433
434
|
//# sourceMappingURL=NoteEditor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NoteEditor.js","names":[],"sources":["../../../src/components/notes/NoteEditor.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { X, Save, Eye, Edit, Tag, Check, Plus } from 'lucide-react';\nimport type { CreateNoteInput, Note, NoteTagAssignment } from './types';\nimport { useAvailableNoteTagsQuery, useCreateNoteTagMutation } from '@/generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\n\n/** Tag selected in the editor */\nexport interface SelectedTag {\n id: string;\n key: string;\n label: string;\n color: string | null;\n}\n\n/** Note data submitted from the editor — includes `id` and selected tag IDs */\nexport type NoteEditorSubmitData = CreateNoteInput & { id?: string; selectedTagIds?: string[] };\n\nconst TAG_COLORS = [\n 'var(--color-status-error-text)',\n 'var(--color-status-warning-text)',\n 'var(--color-status-success-text)',\n 'var(--color-accent-teal)',\n 'var(--color-text-link)',\n 'var(--color-accent-purple)',\n 'var(--color-text-tertiary)',\n];\n\ninterface NoteEditorProps {\n note?: Note;\n vibeOptions?: { id: string; name: string }[];\n /** Pre-set vibeId when creating a new note (used in vibe-scoped context) */\n defaultVibeId?: string;\n /** Hide the vibe dropdown (used when notes are scoped to a single vibe) */\n hideVibeSelect?: boolean;\n onSubmit: (data: NoteEditorSubmitData) => void;\n onCancel: () => void;\n isLoading?: boolean;\n}\n\nexport function NoteEditor({\n note,\n vibeOptions = [],\n defaultVibeId,\n hideVibeSelect,\n onSubmit,\n onCancel,\n isLoading,\n}: NoteEditorProps) {\n const tr = useTr();\n const [isPreview, setIsPreview] = useState(false);\n const [formData, setFormData] = useState({\n title: note?.title || '',\n content: note?.content || '',\n vibeId: note?.vibeId || defaultVibeId || '',\n isPinned: note?.isPinned ?? false,\n });\n\n // Tag picker state\n const [selectedTagIds, setSelectedTagIds] = useState<Set<string>>(new Set());\n const [tagsInitialized, setTagsInitialized] = useState(false);\n const [showTagPicker, setShowTagPicker] = useState(false);\n\n // Fetch available tags from wspace-tags-svc\n const { data: tagsData, refetch: refetchTags } = useAvailableNoteTagsQuery();\n const availableTags: SelectedTag[] = (tagsData?.availableNoteTags ?? []).map(\n (t: { id: string; key: string; label: string; color?: string | null }) => ({\n id: t.id,\n key: t.key,\n label: t.label,\n color: t.color ?? null,\n })\n );\n\n // Initialize selected tags once available tags load\n useEffect(() => {\n if (tagsInitialized || availableTags.length === 0) return;\n const ids = new Set<string>();\n\n // First: pre-select from tagAssignments (tags service data)\n if (note?.tagAssignments && note.tagAssignments.length > 0) {\n note.tagAssignments.forEach((a: NoteTagAssignment) => ids.add(a.tagId));\n }\n // Fallback: match legacy string tags by label/key to available tags\n else if (note?.tags && note.tags.length > 0) {\n for (const legacyTag of note.tags) {\n const match = availableTags.find(\n (t) =>\n t.label.toLowerCase() === legacyTag.toLowerCase() || t.key === legacyTag.toLowerCase()\n );\n if (match) ids.add(match.id);\n }\n }\n\n if (ids.size > 0) setSelectedTagIds(ids);\n setTagsInitialized(true);\n }, [availableTags, note, tagsInitialized]);\n\n // Inline tag creation\n const [createNoteTag] = useCreateNoteTagMutation();\n const [newTagLabel, setNewTagLabel] = useState('');\n const [newTagColor, setNewTagColor] = useState('var(--color-accent-purple)');\n const [isCreatingTag, setIsCreatingTag] = useState(false);\n\n const handleCreateTag = async () => {\n const label = newTagLabel.trim();\n if (!label || isCreatingTag) return;\n setIsCreatingTag(true);\n try {\n const result = await createNoteTag({\n variables: { input: { label, color: newTagColor } },\n });\n const newTag = result.data?.createNoteTag;\n if (newTag) {\n setSelectedTagIds((prev) => new Set(prev).add(newTag.id));\n await refetchTags();\n }\n setNewTagLabel('');\n } catch (err) {\n console.error('Failed to create tag:', err);\n } finally {\n setIsCreatingTag(false);\n }\n };\n\n const toggleTag = (tagId: string) => {\n setSelectedTagIds((prev) => {\n const next = new Set(prev);\n if (next.has(tagId)) {\n next.delete(tagId);\n } else {\n next.add(tagId);\n }\n return next;\n });\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const data: NoteEditorSubmitData = {\n ...(note?.id ? { id: note.id } : {}),\n workspaceId: 'ws-demo-1',\n title: formData.title,\n content: formData.content,\n vibeId: formData.vibeId || undefined,\n isPinned: formData.isPinned,\n selectedTagIds: Array.from(selectedTagIds),\n };\n onSubmit(data);\n };\n\n // Enhanced markdown preview with inline formatting support\n const parseInlineFormatting = (text: string): React.ReactNode[] => {\n const parts: React.ReactNode[] = [];\n let remaining = text;\n let key = 0;\n\n while (remaining.length > 0) {\n // Bold: **text** or __text__\n const boldMatch = remaining.match(/^(\\*\\*|__)(.+?)\\1/);\n if (boldMatch) {\n parts.push(<strong key={key++}>{parseInlineFormatting(boldMatch[2])}</strong>);\n remaining = remaining.slice(boldMatch[0].length);\n continue;\n }\n\n // Italic: *text* or _text_\n const italicMatch = remaining.match(/^(\\*|_)([^*_]+?)\\1/);\n if (italicMatch) {\n parts.push(<em key={key++}>{parseInlineFormatting(italicMatch[2])}</em>);\n remaining = remaining.slice(italicMatch[0].length);\n continue;\n }\n\n // Strikethrough: ~~text~~\n const strikeMatch = remaining.match(/^~~(.+?)~~/);\n if (strikeMatch) {\n parts.push(<del key={key++}>{parseInlineFormatting(strikeMatch[1])}</del>);\n remaining = remaining.slice(strikeMatch[0].length);\n continue;\n }\n\n // Inline code: `code`\n const codeMatch = remaining.match(/^`([^`]+)`/);\n if (codeMatch) {\n parts.push(\n <code\n key={key++}\n className=\"bg-bg-elevated px-1.5 py-0.5 rounded text-sm font-mono text-text-primary\"\n >\n {codeMatch[1]}\n </code>\n );\n remaining = remaining.slice(codeMatch[0].length);\n continue;\n }\n\n // Links: [text](url) - only allow safe URL schemes to prevent XSS\n const linkMatch = remaining.match(/^\\[([^\\]]+)\\]\\(([^)]+)\\)/);\n if (linkMatch) {\n const url = linkMatch[2];\n // Only allow http, https, mailto, relative paths, and hash links to prevent XSS via javascript: URLs\n const isSafeUrl = /^(https?:\\/\\/|mailto:|\\/|#)/i.test(url);\n if (isSafeUrl) {\n parts.push(\n <a\n key={key++}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-action-primary-bg hover:underline\"\n >\n {linkMatch[1]}\n </a>\n );\n } else {\n // Render unsafe links as plain text with muted styling\n parts.push(\n <span key={key++} className=\"text-text-secondary\">\n {linkMatch[1]}\n </span>\n );\n }\n remaining = remaining.slice(linkMatch[0].length);\n continue;\n }\n\n // No match - take the next character\n parts.push(remaining[0]);\n remaining = remaining.slice(1);\n }\n\n return parts;\n };\n\n // Parse markdown content into React elements\n const renderMarkdown = (text: string) => {\n const lines = text.split('\\n');\n const elements: React.ReactNode[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n // Code blocks: ```\n if (line.startsWith('```')) {\n const language = line.slice(3).trim();\n const codeLines: string[] = [];\n i++;\n while (i < lines.length && !lines[i].startsWith('```')) {\n codeLines.push(lines[i]);\n i++;\n }\n elements.push(\n <pre key={elements.length} className=\"bg-bg-elevated rounded-md p-3 my-2 overflow-x-auto\">\n <code className=\"text-sm font-mono text-text-primary\">\n {language && (\n <span className=\"text-xs text-text-secondary block mb-2\">{language}</span>\n )}\n {codeLines.join('\\n')}\n </code>\n </pre>\n );\n i++;\n continue;\n }\n\n // Headings\n if (line.startsWith('# ')) {\n elements.push(\n <h1 key={elements.length} className=\"text-2xl font-bold mb-2 mt-4\">\n {parseInlineFormatting(line.slice(2))}\n </h1>\n );\n i++;\n continue;\n }\n if (line.startsWith('## ')) {\n elements.push(\n <h2 key={elements.length} className=\"text-xl font-bold mb-2 mt-3\">\n {parseInlineFormatting(line.slice(3))}\n </h2>\n );\n i++;\n continue;\n }\n if (line.startsWith('### ')) {\n elements.push(\n <h3 key={elements.length} className=\"text-lg font-bold mb-2 mt-2\">\n {parseInlineFormatting(line.slice(4))}\n </h3>\n );\n i++;\n continue;\n }\n\n // Blockquotes: > text\n if (line.startsWith('> ')) {\n elements.push(\n <blockquote\n key={elements.length}\n className=\"border-l-4 border-border-default pl-4 my-2 italic text-text-secondary\"\n >\n {parseInlineFormatting(line.slice(2))}\n </blockquote>\n );\n i++;\n continue;\n }\n\n // Unordered list: - or * (collect consecutive items and wrap in <ul>)\n if (line.match(/^[-*] /)) {\n const listItems: React.ReactNode[] = [];\n while (i < lines.length && lines[i].match(/^[-*] /)) {\n listItems.push(\n <li key={listItems.length} className=\"list-disc\">\n {parseInlineFormatting(lines[i].slice(2))}\n </li>\n );\n i++;\n }\n elements.push(\n <ul key={elements.length} className=\"ml-6 my-2\">\n {listItems}\n </ul>\n );\n continue;\n }\n\n // Ordered list: 1. 2. etc. (collect consecutive items and wrap in <ol>)\n const orderedMatch = line.match(/^(\\d+)\\. /);\n if (orderedMatch) {\n const listItems: React.ReactNode[] = [];\n while (i < lines.length) {\n const olMatch = lines[i].match(/^(\\d+)\\. /);\n if (!olMatch) break;\n listItems.push(\n <li key={listItems.length} className=\"list-decimal\">\n {parseInlineFormatting(lines[i].slice(olMatch[0].length))}\n </li>\n );\n i++;\n }\n elements.push(\n <ol key={elements.length} className=\"ml-6 my-2\">\n {listItems}\n </ol>\n );\n continue;\n }\n\n // Horizontal rule: --- or ***\n if (line.match(/^(-{3,}|\\*{3,})$/)) {\n elements.push(<hr key={elements.length} className=\"my-4 border-border-default\" />);\n i++;\n continue;\n }\n\n // Table detection (simple): | col1 | col2 |\n if (line.startsWith('|') && line.endsWith('|')) {\n const tableLines: string[] = [line];\n i++;\n while (i < lines.length && lines[i].startsWith('|') && lines[i].endsWith('|')) {\n tableLines.push(lines[i]);\n i++;\n }\n\n // Parse table\n const rows = tableLines\n .filter((l) => !l.match(/^\\|[\\s\\-:|]+\\|$/)) // Skip separator rows (including alignment markers like :---:)\n .map((l) =>\n l\n .slice(1, -1)\n .split('|')\n .map((cell) => cell.trim())\n );\n\n if (rows.length > 0) {\n const [header, ...body] = rows;\n elements.push(\n <table key={elements.length} className=\"w-full my-2 border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-default\">\n {header.map((cell, ci) => (\n <th key={ci} className=\"text-left py-2 px-3 font-semibold text-text-primary\">\n {parseInlineFormatting(cell)}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {body.map((row, ri) => (\n <tr key={ri} className=\"border-b border-border-subtle\">\n {row.map((cell, ci) => (\n <td key={ci} className=\"py-2 px-3 text-text-primary\">\n {parseInlineFormatting(cell)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n );\n }\n continue;\n }\n\n // Empty line\n if (line === '') {\n elements.push(<br key={elements.length} />);\n i++;\n continue;\n }\n\n // Regular paragraph with inline formatting\n elements.push(\n <p key={elements.length} className=\"mb-1\">\n {parseInlineFormatting(line)}\n </p>\n );\n i++;\n }\n\n return elements;\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay\">\n <div className=\"w-full max-w-3xl bg-bg-surface rounded-lg shadow-xl max-h-[90vh] flex flex-col\">\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {note\n ? tr('vibecontrols.notes.editNote', 'Edit Note')\n : tr('vibecontrols.notes.newNote', 'New Note')}\n </h2>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setIsPreview(!isPreview)}\n className={`inline-flex items-center gap-2 px-3 py-1.5 text-sm rounded-md transition-colors ${\n isPreview\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-elevated'\n }`}\n >\n {isPreview ? <Edit className=\"w-4 h-4\" /> : <Eye className=\"w-4 h-4\" />}\n {isPreview\n ? tr('vibecontrols.notes.edit', 'Edit')\n : tr('vibecontrols.notes.preview', 'Preview')}\n </button>\n <button onClick={onCancel} className=\"p-1 rounded hover:bg-bg-sunken transition-colors\">\n <X className=\"w-5 h-5 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n <form onSubmit={handleSubmit} className=\"flex-1 flex flex-col overflow-hidden\">\n <div className=\"p-6 space-y-4 flex-1 overflow-y-auto\">\n <div>\n <input\n type=\"text\"\n value={formData.title}\n onChange={(e) => setFormData({ ...formData, title: e.target.value })}\n placeholder={tr('vibecontrols.notes.titlePlaceholder', 'Note title...')}\n required\n className=\"w-full px-0 py-2 text-lg sm:text-2xl font-bold bg-transparent border-0 border-b border-border-default text-text-primary placeholder:text-text-placeholder focus:outline-none focus:border-action-primary-bg\"\n />\n </div>\n\n <div className={`grid ${hideVibeSelect ? 'grid-cols-1' : 'grid-cols-2'} gap-4`}>\n {!hideVibeSelect && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.notes.relatedVibe', 'Related Vibe (optional)')}\n </label>\n <select\n value={formData.vibeId}\n onChange={(e) => setFormData({ ...formData, vibeId: e.target.value })}\n className=\"w-full px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">No vibe</option>\n {vibeOptions.map((vibe) => (\n <option key={vibe.id} value={vibe.id}>\n {vibe.name}\n </option>\n ))}\n </select>\n </div>\n )}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.notes.tags', 'Tags')}\n </label>\n {/* Selected tags */}\n <div className=\"flex flex-wrap gap-1.5 min-h-[38px] px-3 py-2 bg-bg-sunken border border-border-default rounded-md\">\n {availableTags\n .filter((t) => selectedTagIds.has(t.id))\n .map((tag) => (\n <span\n key={tag.id}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-action-primary-bg/10 text-action-primary-bg rounded\"\n >\n <Tag className=\"w-3 h-3\" />\n {tag.label}\n <button\n type=\"button\"\n onClick={() => toggleTag(tag.id)}\n className=\"ml-0.5 hover:text-status-error-text\"\n >\n <X className=\"w-3 h-3\" />\n </button>\n </span>\n ))}\n <button\n type=\"button\"\n onClick={() => setShowTagPicker(!showTagPicker)}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs text-text-secondary hover:text-text-primary hover:bg-bg-elevated rounded transition-colors\"\n >\n <Tag className=\"w-3 h-3\" />\n {selectedTagIds.size === 0 ? 'Add tags...' : '+'}\n </button>\n </div>\n {/* Tag picker dropdown */}\n {showTagPicker && (\n <div className=\"mt-1 bg-bg-surface border border-border-default rounded-md shadow-lg\">\n {/* Inline create: text input + color picker */}\n <div className=\"flex items-center gap-2 p-2 border-b border-border-subtle\">\n <span\n aria-hidden=\"true\"\n className=\"w-6 h-6 rounded-full border-2 border-border-default\"\n style={{ backgroundColor: newTagColor }}\n />\n <input\n type=\"text\"\n value={newTagLabel}\n onChange={(e) => setNewTagLabel(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleCreateTag();\n }\n }}\n placeholder=\"New tag name... (Enter to create)\"\n disabled={isCreatingTag}\n className=\"flex-1 px-2 py-1 text-sm bg-bg-sunken border border-border-default rounded text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n />\n <button\n type=\"button\"\n onClick={handleCreateTag}\n disabled={!newTagLabel.trim() || isCreatingTag}\n className=\"p-1 text-action-primary-bg hover:bg-action-primary-bg/10 rounded transition-colors disabled:opacity-30\"\n >\n <Plus className=\"w-4 h-4\" />\n </button>\n </div>\n {/* Quick color palette */}\n <div className=\"flex items-center gap-1 px-2 py-1.5 border-b border-border-subtle\">\n {TAG_COLORS.map((c) => (\n <button\n key={c}\n type=\"button\"\n onClick={() => setNewTagColor(c)}\n className={`w-4 h-4 rounded-full transition-transform ${newTagColor === c ? 'ring-2 ring-action-primary-bg scale-125' : 'hover:scale-110'}`}\n style={{ backgroundColor: c }}\n />\n ))}\n </div>\n {/* Existing tags list */}\n <div className=\"max-h-32 overflow-y-auto p-1\">\n {availableTags.length === 0 ? (\n <p className=\"text-xs text-text-muted px-2 py-1\">\n No tags yet — create one above\n </p>\n ) : (\n availableTags.map((tag) => (\n <button\n key={tag.id}\n type=\"button\"\n onClick={() => toggleTag(tag.id)}\n className=\"flex items-center justify-between w-full px-2 py-1.5 text-sm text-text-primary hover:bg-bg-sunken rounded transition-colors\"\n >\n <span className=\"flex items-center gap-2\">\n {tag.color && (\n <span\n className=\"w-2.5 h-2.5 rounded-full\"\n style={{ backgroundColor: tag.color }}\n />\n )}\n {tag.label}\n </span>\n {selectedTagIds.has(tag.id) && (\n <Check className=\"w-4 h-4 text-action-primary-bg\" />\n )}\n </button>\n ))\n )}\n </div>\n </div>\n )}\n </div>\n </div>\n\n <div className=\"flex-1 min-h-[300px]\">\n {isPreview ? (\n <div className=\"h-full p-4 bg-bg-sunken rounded-md prose prose-sm dark:prose-invert max-w-none overflow-y-auto\">\n {renderMarkdown(formData.content)}\n </div>\n ) : (\n <textarea\n value={formData.content}\n onChange={(e) => setFormData({ ...formData, content: e.target.value })}\n placeholder={tr(\n 'vibecontrols.notes.contentPlaceholder',\n 'Write your note in Markdown...'\n )}\n className=\"w-full h-full min-h-[300px] px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg resize-none font-mono\"\n />\n )}\n </div>\n\n <div className=\"flex items-center gap-2\">\n <input\n type=\"checkbox\"\n id=\"isPinned\"\n checked={formData.isPinned}\n onChange={(e) => setFormData({ ...formData, isPinned: e.target.checked })}\n className=\"w-4 h-4 rounded border-border-default text-action-primary-bg focus:ring-action-primary-bg\"\n />\n <label htmlFor=\"isPinned\" className=\"text-sm text-text-primary\">\n {tr('vibecontrols.notes.pinThisNote', 'Pin this note')}\n </label>\n </div>\n </div>\n\n <div className=\"flex items-center justify-end gap-3 px-6 py-4 border-t border-border-default bg-bg-sunken\">\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={isLoading}\n className=\"px-4 py-2 text-sm font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-elevated transition-colors disabled:opacity-50\"\n >\n {tr('vibecontrols.notes.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isLoading}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n <Save className=\"w-4 h-4\" />\n {isLoading\n ? tr('vibecontrols.notes.saving', 'Saving...')\n : tr('vibecontrols.notes.saveNote', 'Save Note')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NoteEditor.js","names":[],"sources":["../../../src/components/notes/NoteEditor.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { X, Save, Eye, Edit, Tag, Check, Plus } from 'lucide-react';\nimport type { CreateNoteInput, Note, NoteTagAssignment } from './types';\nimport { useAvailableNoteTagsQuery, useCreateNoteTagMutation } from '@/generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useVibeControls } from '../../providers/VibeControlsProvider';\n\n/** Tag selected in the editor */\nexport interface SelectedTag {\n id: string;\n key: string;\n label: string;\n color: string | null;\n}\n\n/** Note data submitted from the editor — includes `id` and selected tag IDs */\nexport type NoteEditorSubmitData = CreateNoteInput & { id?: string; selectedTagIds?: string[] };\n\nconst TAG_COLORS = [\n 'var(--color-status-error-text)',\n 'var(--color-status-warning-text)',\n 'var(--color-status-success-text)',\n 'var(--color-accent-teal)',\n 'var(--color-text-link)',\n 'var(--color-accent-purple)',\n 'var(--color-text-tertiary)',\n];\n\ninterface NoteEditorProps {\n note?: Note;\n vibeOptions?: { id: string; name: string }[];\n /** Pre-set vibeId when creating a new note (used in vibe-scoped context) */\n defaultVibeId?: string;\n /** Hide the vibe dropdown (used when notes are scoped to a single vibe) */\n hideVibeSelect?: boolean;\n onSubmit: (data: NoteEditorSubmitData) => void;\n onCancel: () => void;\n isLoading?: boolean;\n}\n\nexport function NoteEditor({\n note,\n vibeOptions = [],\n defaultVibeId,\n hideVibeSelect,\n onSubmit,\n onCancel,\n isLoading,\n}: NoteEditorProps) {\n const tr = useTr();\n const { workspaceId } = useVibeControls();\n const [isPreview, setIsPreview] = useState(false);\n const [formData, setFormData] = useState({\n title: note?.title || '',\n content: note?.content || '',\n vibeId: note?.vibeId || defaultVibeId || '',\n isPinned: note?.isPinned ?? false,\n });\n\n // Tag picker state\n const [selectedTagIds, setSelectedTagIds] = useState<Set<string>>(new Set());\n const [tagsInitialized, setTagsInitialized] = useState(false);\n const [showTagPicker, setShowTagPicker] = useState(false);\n\n // Fetch available tags from wspace-tags-svc\n const { data: tagsData, refetch: refetchTags } = useAvailableNoteTagsQuery();\n const availableTags: SelectedTag[] = (tagsData?.availableNoteTags ?? []).map(\n (t: { id: string; key: string; label: string; color?: string | null }) => ({\n id: t.id,\n key: t.key,\n label: t.label,\n color: t.color ?? null,\n })\n );\n\n // Initialize selected tags once available tags load\n useEffect(() => {\n if (tagsInitialized || availableTags.length === 0) return;\n const ids = new Set<string>();\n\n // First: pre-select from tagAssignments (tags service data)\n if (note?.tagAssignments && note.tagAssignments.length > 0) {\n note.tagAssignments.forEach((a: NoteTagAssignment) => ids.add(a.tagId));\n }\n // Fallback: match legacy string tags by label/key to available tags\n else if (note?.tags && note.tags.length > 0) {\n for (const legacyTag of note.tags) {\n const match = availableTags.find(\n (t) =>\n t.label.toLowerCase() === legacyTag.toLowerCase() || t.key === legacyTag.toLowerCase()\n );\n if (match) ids.add(match.id);\n }\n }\n\n if (ids.size > 0) setSelectedTagIds(ids);\n setTagsInitialized(true);\n }, [availableTags, note, tagsInitialized]);\n\n // Inline tag creation\n const [createNoteTag] = useCreateNoteTagMutation();\n const [newTagLabel, setNewTagLabel] = useState('');\n const [newTagColor, setNewTagColor] = useState('var(--color-accent-purple)');\n const [isCreatingTag, setIsCreatingTag] = useState(false);\n\n const handleCreateTag = async () => {\n const label = newTagLabel.trim();\n if (!label || isCreatingTag) return;\n setIsCreatingTag(true);\n try {\n const result = await createNoteTag({\n variables: { input: { label, color: newTagColor } },\n });\n const newTag = result.data?.createNoteTag;\n if (newTag) {\n setSelectedTagIds((prev) => new Set(prev).add(newTag.id));\n await refetchTags();\n }\n setNewTagLabel('');\n } catch (err) {\n console.error('Failed to create tag:', err);\n } finally {\n setIsCreatingTag(false);\n }\n };\n\n const toggleTag = (tagId: string) => {\n setSelectedTagIds((prev) => {\n const next = new Set(prev);\n if (next.has(tagId)) {\n next.delete(tagId);\n } else {\n next.add(tagId);\n }\n return next;\n });\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const data: NoteEditorSubmitData = {\n ...(note?.id ? { id: note.id } : {}),\n workspaceId: workspaceId ?? '',\n title: formData.title,\n content: formData.content,\n vibeId: formData.vibeId || undefined,\n isPinned: formData.isPinned,\n selectedTagIds: Array.from(selectedTagIds),\n };\n onSubmit(data);\n };\n\n // Enhanced markdown preview with inline formatting support\n const parseInlineFormatting = (text: string): React.ReactNode[] => {\n const parts: React.ReactNode[] = [];\n let remaining = text;\n let key = 0;\n\n while (remaining.length > 0) {\n // Bold: **text** or __text__\n const boldMatch = remaining.match(/^(\\*\\*|__)(.+?)\\1/);\n if (boldMatch) {\n parts.push(<strong key={key++}>{parseInlineFormatting(boldMatch[2])}</strong>);\n remaining = remaining.slice(boldMatch[0].length);\n continue;\n }\n\n // Italic: *text* or _text_\n const italicMatch = remaining.match(/^(\\*|_)([^*_]+?)\\1/);\n if (italicMatch) {\n parts.push(<em key={key++}>{parseInlineFormatting(italicMatch[2])}</em>);\n remaining = remaining.slice(italicMatch[0].length);\n continue;\n }\n\n // Strikethrough: ~~text~~\n const strikeMatch = remaining.match(/^~~(.+?)~~/);\n if (strikeMatch) {\n parts.push(<del key={key++}>{parseInlineFormatting(strikeMatch[1])}</del>);\n remaining = remaining.slice(strikeMatch[0].length);\n continue;\n }\n\n // Inline code: `code`\n const codeMatch = remaining.match(/^`([^`]+)`/);\n if (codeMatch) {\n parts.push(\n <code\n key={key++}\n className=\"bg-bg-elevated px-1.5 py-0.5 rounded text-sm font-mono text-text-primary\"\n >\n {codeMatch[1]}\n </code>\n );\n remaining = remaining.slice(codeMatch[0].length);\n continue;\n }\n\n // Links: [text](url) - only allow safe URL schemes to prevent XSS\n const linkMatch = remaining.match(/^\\[([^\\]]+)\\]\\(([^)]+)\\)/);\n if (linkMatch) {\n const url = linkMatch[2];\n // Only allow http, https, mailto, relative paths, and hash links to prevent XSS via javascript: URLs\n const isSafeUrl = /^(https?:\\/\\/|mailto:|\\/|#)/i.test(url);\n if (isSafeUrl) {\n parts.push(\n <a\n key={key++}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-action-primary-bg hover:underline\"\n >\n {linkMatch[1]}\n </a>\n );\n } else {\n // Render unsafe links as plain text with muted styling\n parts.push(\n <span key={key++} className=\"text-text-secondary\">\n {linkMatch[1]}\n </span>\n );\n }\n remaining = remaining.slice(linkMatch[0].length);\n continue;\n }\n\n // No match - take the next character\n parts.push(remaining[0]);\n remaining = remaining.slice(1);\n }\n\n return parts;\n };\n\n // Parse markdown content into React elements\n const renderMarkdown = (text: string) => {\n const lines = text.split('\\n');\n const elements: React.ReactNode[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n // Code blocks: ```\n if (line.startsWith('```')) {\n const language = line.slice(3).trim();\n const codeLines: string[] = [];\n i++;\n while (i < lines.length && !lines[i].startsWith('```')) {\n codeLines.push(lines[i]);\n i++;\n }\n elements.push(\n <pre key={elements.length} className=\"bg-bg-elevated rounded-md p-3 my-2 overflow-x-auto\">\n <code className=\"text-sm font-mono text-text-primary\">\n {language && (\n <span className=\"text-xs text-text-secondary block mb-2\">{language}</span>\n )}\n {codeLines.join('\\n')}\n </code>\n </pre>\n );\n i++;\n continue;\n }\n\n // Headings\n if (line.startsWith('# ')) {\n elements.push(\n <h1 key={elements.length} className=\"text-2xl font-bold mb-2 mt-4\">\n {parseInlineFormatting(line.slice(2))}\n </h1>\n );\n i++;\n continue;\n }\n if (line.startsWith('## ')) {\n elements.push(\n <h2 key={elements.length} className=\"text-xl font-bold mb-2 mt-3\">\n {parseInlineFormatting(line.slice(3))}\n </h2>\n );\n i++;\n continue;\n }\n if (line.startsWith('### ')) {\n elements.push(\n <h3 key={elements.length} className=\"text-lg font-bold mb-2 mt-2\">\n {parseInlineFormatting(line.slice(4))}\n </h3>\n );\n i++;\n continue;\n }\n\n // Blockquotes: > text\n if (line.startsWith('> ')) {\n elements.push(\n <blockquote\n key={elements.length}\n className=\"border-l-4 border-border-default pl-4 my-2 italic text-text-secondary\"\n >\n {parseInlineFormatting(line.slice(2))}\n </blockquote>\n );\n i++;\n continue;\n }\n\n // Unordered list: - or * (collect consecutive items and wrap in <ul>)\n if (line.match(/^[-*] /)) {\n const listItems: React.ReactNode[] = [];\n while (i < lines.length && lines[i].match(/^[-*] /)) {\n listItems.push(\n <li key={listItems.length} className=\"list-disc\">\n {parseInlineFormatting(lines[i].slice(2))}\n </li>\n );\n i++;\n }\n elements.push(\n <ul key={elements.length} className=\"ml-6 my-2\">\n {listItems}\n </ul>\n );\n continue;\n }\n\n // Ordered list: 1. 2. etc. (collect consecutive items and wrap in <ol>)\n const orderedMatch = line.match(/^(\\d+)\\. /);\n if (orderedMatch) {\n const listItems: React.ReactNode[] = [];\n while (i < lines.length) {\n const olMatch = lines[i].match(/^(\\d+)\\. /);\n if (!olMatch) break;\n listItems.push(\n <li key={listItems.length} className=\"list-decimal\">\n {parseInlineFormatting(lines[i].slice(olMatch[0].length))}\n </li>\n );\n i++;\n }\n elements.push(\n <ol key={elements.length} className=\"ml-6 my-2\">\n {listItems}\n </ol>\n );\n continue;\n }\n\n // Horizontal rule: --- or ***\n if (line.match(/^(-{3,}|\\*{3,})$/)) {\n elements.push(<hr key={elements.length} className=\"my-4 border-border-default\" />);\n i++;\n continue;\n }\n\n // Table detection (simple): | col1 | col2 |\n if (line.startsWith('|') && line.endsWith('|')) {\n const tableLines: string[] = [line];\n i++;\n while (i < lines.length && lines[i].startsWith('|') && lines[i].endsWith('|')) {\n tableLines.push(lines[i]);\n i++;\n }\n\n // Parse table\n const rows = tableLines\n .filter((l) => !l.match(/^\\|[\\s\\-:|]+\\|$/)) // Skip separator rows (including alignment markers like :---:)\n .map((l) =>\n l\n .slice(1, -1)\n .split('|')\n .map((cell) => cell.trim())\n );\n\n if (rows.length > 0) {\n const [header, ...body] = rows;\n elements.push(\n <table key={elements.length} className=\"w-full my-2 border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-default\">\n {header.map((cell, ci) => (\n <th key={ci} className=\"text-left py-2 px-3 font-semibold text-text-primary\">\n {parseInlineFormatting(cell)}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {body.map((row, ri) => (\n <tr key={ri} className=\"border-b border-border-subtle\">\n {row.map((cell, ci) => (\n <td key={ci} className=\"py-2 px-3 text-text-primary\">\n {parseInlineFormatting(cell)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n );\n }\n continue;\n }\n\n // Empty line\n if (line === '') {\n elements.push(<br key={elements.length} />);\n i++;\n continue;\n }\n\n // Regular paragraph with inline formatting\n elements.push(\n <p key={elements.length} className=\"mb-1\">\n {parseInlineFormatting(line)}\n </p>\n );\n i++;\n }\n\n return elements;\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay\">\n <div className=\"w-full max-w-3xl bg-bg-surface rounded-lg shadow-xl max-h-[90vh] flex flex-col\">\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {note\n ? tr('vibecontrols.notes.editNote', 'Edit Note')\n : tr('vibecontrols.notes.newNote', 'New Note')}\n </h2>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setIsPreview(!isPreview)}\n className={`inline-flex items-center gap-2 px-3 py-1.5 text-sm rounded-md transition-colors ${\n isPreview\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-elevated'\n }`}\n >\n {isPreview ? <Edit className=\"w-4 h-4\" /> : <Eye className=\"w-4 h-4\" />}\n {isPreview\n ? tr('vibecontrols.notes.edit', 'Edit')\n : tr('vibecontrols.notes.preview', 'Preview')}\n </button>\n <button onClick={onCancel} className=\"p-1 rounded hover:bg-bg-sunken transition-colors\">\n <X className=\"w-5 h-5 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n <form onSubmit={handleSubmit} className=\"flex-1 flex flex-col overflow-hidden\">\n <div className=\"p-6 space-y-4 flex-1 overflow-y-auto\">\n <div>\n <input\n type=\"text\"\n value={formData.title}\n onChange={(e) => setFormData({ ...formData, title: e.target.value })}\n placeholder={tr('vibecontrols.notes.titlePlaceholder', 'Note title...')}\n required\n className=\"w-full px-0 py-2 text-lg sm:text-2xl font-bold bg-transparent border-0 border-b border-border-default text-text-primary placeholder:text-text-placeholder focus:outline-none focus:border-action-primary-bg\"\n />\n </div>\n\n <div className={`grid ${hideVibeSelect ? 'grid-cols-1' : 'grid-cols-2'} gap-4`}>\n {!hideVibeSelect && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.notes.relatedVibe', 'Related Vibe (optional)')}\n </label>\n <select\n value={formData.vibeId}\n onChange={(e) => setFormData({ ...formData, vibeId: e.target.value })}\n className=\"w-full px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">No vibe</option>\n {vibeOptions.map((vibe) => (\n <option key={vibe.id} value={vibe.id}>\n {vibe.name}\n </option>\n ))}\n </select>\n </div>\n )}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.notes.tags', 'Tags')}\n </label>\n {/* Selected tags */}\n <div className=\"flex flex-wrap gap-1.5 min-h-[38px] px-3 py-2 bg-bg-sunken border border-border-default rounded-md\">\n {availableTags\n .filter((t) => selectedTagIds.has(t.id))\n .map((tag) => (\n <span\n key={tag.id}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-action-primary-bg/10 text-action-primary-bg rounded\"\n >\n <Tag className=\"w-3 h-3\" />\n {tag.label}\n <button\n type=\"button\"\n onClick={() => toggleTag(tag.id)}\n className=\"ml-0.5 hover:text-status-error-text\"\n >\n <X className=\"w-3 h-3\" />\n </button>\n </span>\n ))}\n <button\n type=\"button\"\n onClick={() => setShowTagPicker(!showTagPicker)}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs text-text-secondary hover:text-text-primary hover:bg-bg-elevated rounded transition-colors\"\n >\n <Tag className=\"w-3 h-3\" />\n {selectedTagIds.size === 0 ? 'Add tags...' : '+'}\n </button>\n </div>\n {/* Tag picker dropdown */}\n {showTagPicker && (\n <div className=\"mt-1 bg-bg-surface border border-border-default rounded-md shadow-lg\">\n {/* Inline create: text input + color picker */}\n <div className=\"flex items-center gap-2 p-2 border-b border-border-subtle\">\n <span\n aria-hidden=\"true\"\n className=\"w-6 h-6 rounded-full border-2 border-border-default\"\n style={{ backgroundColor: newTagColor }}\n />\n <input\n type=\"text\"\n value={newTagLabel}\n onChange={(e) => setNewTagLabel(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleCreateTag();\n }\n }}\n placeholder=\"New tag name... (Enter to create)\"\n disabled={isCreatingTag}\n className=\"flex-1 px-2 py-1 text-sm bg-bg-sunken border border-border-default rounded text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n />\n <button\n type=\"button\"\n onClick={handleCreateTag}\n disabled={!newTagLabel.trim() || isCreatingTag}\n className=\"p-1 text-action-primary-bg hover:bg-action-primary-bg/10 rounded transition-colors disabled:opacity-30\"\n >\n <Plus className=\"w-4 h-4\" />\n </button>\n </div>\n {/* Quick color palette */}\n <div className=\"flex items-center gap-1 px-2 py-1.5 border-b border-border-subtle\">\n {TAG_COLORS.map((c) => (\n <button\n key={c}\n type=\"button\"\n onClick={() => setNewTagColor(c)}\n className={`w-4 h-4 rounded-full transition-transform ${newTagColor === c ? 'ring-2 ring-action-primary-bg scale-125' : 'hover:scale-110'}`}\n style={{ backgroundColor: c }}\n />\n ))}\n </div>\n {/* Existing tags list */}\n <div className=\"max-h-32 overflow-y-auto p-1\">\n {availableTags.length === 0 ? (\n <p className=\"text-xs text-text-muted px-2 py-1\">\n No tags yet — create one above\n </p>\n ) : (\n availableTags.map((tag) => (\n <button\n key={tag.id}\n type=\"button\"\n onClick={() => toggleTag(tag.id)}\n className=\"flex items-center justify-between w-full px-2 py-1.5 text-sm text-text-primary hover:bg-bg-sunken rounded transition-colors\"\n >\n <span className=\"flex items-center gap-2\">\n {tag.color && (\n <span\n className=\"w-2.5 h-2.5 rounded-full\"\n style={{ backgroundColor: tag.color }}\n />\n )}\n {tag.label}\n </span>\n {selectedTagIds.has(tag.id) && (\n <Check className=\"w-4 h-4 text-action-primary-bg\" />\n )}\n </button>\n ))\n )}\n </div>\n </div>\n )}\n </div>\n </div>\n\n <div className=\"flex-1 min-h-[300px]\">\n {isPreview ? (\n <div className=\"h-full p-4 bg-bg-sunken rounded-md prose prose-sm dark:prose-invert max-w-none overflow-y-auto\">\n {renderMarkdown(formData.content)}\n </div>\n ) : (\n <textarea\n value={formData.content}\n onChange={(e) => setFormData({ ...formData, content: e.target.value })}\n placeholder={tr(\n 'vibecontrols.notes.contentPlaceholder',\n 'Write your note in Markdown...'\n )}\n className=\"w-full h-full min-h-[300px] px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg resize-none font-mono\"\n />\n )}\n </div>\n\n <div className=\"flex items-center gap-2\">\n <input\n type=\"checkbox\"\n id=\"isPinned\"\n checked={formData.isPinned}\n onChange={(e) => setFormData({ ...formData, isPinned: e.target.checked })}\n className=\"w-4 h-4 rounded border-border-default text-action-primary-bg focus:ring-action-primary-bg\"\n />\n <label htmlFor=\"isPinned\" className=\"text-sm text-text-primary\">\n {tr('vibecontrols.notes.pinThisNote', 'Pin this note')}\n </label>\n </div>\n </div>\n\n <div className=\"flex items-center justify-end gap-3 px-6 py-4 border-t border-border-default bg-bg-sunken\">\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={isLoading}\n className=\"px-4 py-2 text-sm font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-elevated transition-colors disabled:opacity-50\"\n >\n {tr('vibecontrols.notes.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isLoading}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n <Save className=\"w-4 h-4\" />\n {isLoading\n ? tr('vibecontrols.notes.saving', 'Saving...')\n : tr('vibecontrols.notes.saveNote', 'Save Note')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|