web-mojo 2.4.6 → 2.4.7
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/admin.cjs.js +1 -1
- package/dist/admin.es.js +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.es.js +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +1 -1
- package/dist/chunks/{AssistantPanelView-D1wEbgtM.js → AssistantPanelView-DmlaVMvK.js} +2 -2
- package/dist/chunks/{AssistantPanelView-D1wEbgtM.js.map → AssistantPanelView-DmlaVMvK.js.map} +1 -1
- package/dist/chunks/{AssistantPanelView-CkQEcaFk.js → AssistantPanelView-rkVX6wky.js} +2 -2
- package/dist/chunks/{AssistantPanelView-CkQEcaFk.js.map → AssistantPanelView-rkVX6wky.js.map} +1 -1
- package/dist/chunks/{TicketPanelView-TYh5iZiR.js → TicketPanelView-BGQZ6q2B.js} +2 -2
- package/dist/chunks/{TicketPanelView-TYh5iZiR.js.map → TicketPanelView-BGQZ6q2B.js.map} +1 -1
- package/dist/chunks/{TicketPanelView-DVePzWyJ.js → TicketPanelView-BZ8e66o1.js} +2 -2
- package/dist/chunks/{TicketPanelView-DVePzWyJ.js.map → TicketPanelView-BZ8e66o1.js.map} +1 -1
- package/dist/chunks/{admin-CHPo4iDn.js → admin-CmyeTstQ.js} +2 -2
- package/dist/chunks/{admin-CHPo4iDn.js.map → admin-CmyeTstQ.js.map} +1 -1
- package/dist/chunks/{admin-CucHFXfD.js → admin-D6gHOojO.js} +2 -2
- package/dist/chunks/{admin-CucHFXfD.js.map → admin-D6gHOojO.js.map} +1 -1
- package/dist/chunks/{version-BxLEknar.js → version-DdqQDDOW.js} +2 -2
- package/dist/chunks/{version-BxLEknar.js.map → version-DdqQDDOW.js.map} +1 -1
- package/dist/chunks/{version-BYy2UAT0.js → version-NEBZhkhG.js} +2 -2
- package/dist/chunks/{version-BYy2UAT0.js.map → version-NEBZhkhG.js.map} +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.es.js +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +1 -1
- package/package.json +1 -1
package/dist/chunks/{AssistantPanelView-CkQEcaFk.js.map → AssistantPanelView-rkVX6wky.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantPanelView-CkQEcaFk.js","sources":["../../src/extensions/admin/assistant/AssistantPanelView.js"],"sourcesContent":["import View from '@core/View.js';\nimport ChatView from '@core/views/chat/ChatView.js';\nimport { AssistantConversation, AssistantConversationList } from '@ext/admin/models/Assistant.js';\nimport AssistantMessageView from './AssistantMessageView.js';\nimport AssistantConversationListView from './AssistantConversationListView.js';\nimport AssistantView from './AssistantView.js';\n\n/**\n * AssistantPanelView - Chat-only sidebar panel for the admin assistant\n *\n * Compact layout for the right sidebar panel. No conversation list by default —\n * a hamburger toggle switches between chat and conversation history.\n *\n * Emits:\n * panel:close — when the close button is clicked\n */\nclass AssistantPanelView extends View {\n constructor(options = {}) {\n super({\n className: 'assistant-panel-view',\n ...options\n });\n\n this.app = options.app;\n this.ws = this.app?.ws;\n this.conversationId = options.conversationId || this.app?._assistantConversationId || null;\n this._wsHandlers = {};\n this._messageIdCounter = 0;\n this._hasMessages = false;\n this._activePlans = {};\n this._requestStartTime = null;\n this._showingHistory = false;\n }\n\n getTemplate() {\n const userName = this._escapeHtml(\n this.app?.activeUser?.get('first_name') || 'there'\n );\n\n return `\n <div class=\"assistant-panel-resize-handle\" data-ref=\"resize-handle\"></div>\n <div class=\"assistant-panel-layout\">\n <div class=\"assistant-panel-header\">\n <button class=\"assistant-panel-header-btn\" data-action=\"toggle-history\" type=\"button\" title=\"Conversation history\">\n <i class=\"bi bi-list\"></i>\n </button>\n <span class=\"assistant-panel-title text-truncate\" data-ref=\"panel-title\">New conversation</span>\n <div class=\"d-flex gap-1 ms-auto\">\n <button class=\"assistant-panel-header-btn\" data-action=\"new-conversation\" type=\"button\" title=\"New conversation\">\n <i class=\"bi bi-plus-lg\"></i>\n </button>\n <button class=\"assistant-panel-header-btn\" data-action=\"fullscreen\" type=\"button\" title=\"Open fullscreen\">\n <i class=\"bi bi-arrows-fullscreen\"></i>\n </button>\n <button class=\"assistant-panel-header-btn\" data-action=\"pop-out\" type=\"button\" title=\"Open in popup window\">\n <i class=\"bi bi-box-arrow-up-right\"></i>\n </button>\n <button class=\"assistant-panel-header-btn\" data-action=\"close-panel\" type=\"button\" title=\"Close\">\n <i class=\"bi bi-x-lg\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"assistant-panel-history d-none\" data-ref=\"history\" data-container=\"conversation-list\"></div>\n\n <div class=\"assistant-panel-chat\" data-ref=\"chat-wrapper\">\n <div class=\"assistant-welcome\" data-ref=\"welcome\">\n <div class=\"assistant-welcome-content\">\n <div class=\"assistant-welcome-icon\">\n <img src=\"https://mojo-verify.s3.amazonaws.com/signatures/14e7aab75c2749cb846f7d57298691ac/mojo_ai_7c0322e9.png\" alt=\"Mojo\">\n </div>\n <h3 class=\"assistant-welcome-title\">Hi ${userName}</h3>\n <p class=\"assistant-welcome-subtitle\">How can I help you today?</p>\n <div class=\"assistant-suggestions\">\n <button class=\"assistant-suggestion\" data-action=\"use-suggestion\" data-text=\"Show me a summary of recent activity\">\n <i class=\"bi bi-activity\"></i>\n <span>Recent activity summary</span>\n </button>\n <button class=\"assistant-suggestion\" data-action=\"use-suggestion\" data-text=\"How many active users are there?\">\n <i class=\"bi bi-people\"></i>\n <span>Active user count</span>\n </button>\n </div>\n </div>\n </div>\n <div class=\"assistant-chat-area\" data-container=\"chat-area\"></div>\n <div class=\"assistant-input-wrapper\">\n <div class=\"assistant-input-status d-none\" data-ref=\"input-status\"></div>\n <div class=\"assistant-input-box\">\n <textarea class=\"assistant-input\" placeholder=\"Message Mojo...\" rows=\"1\" data-ref=\"input\"></textarea>\n <button class=\"assistant-send-btn\" data-action=\"send\" type=\"button\" title=\"Send message\" data-ref=\"send-btn\">\n <i class=\"bi bi-arrow-up\"></i>\n </button>\n <button class=\"assistant-stop-btn d-none\" data-action=\"stop\" type=\"button\" title=\"Stop generating\" data-ref=\"stop-btn\">\n <i class=\"bi bi-stop-fill\"></i>\n </button>\n </div>\n <div class=\"assistant-input-footer\">\n <span class=\"assistant-connection-indicator\" data-ref=\"status\">\n <span class=\"status-dot connected\"></span>\n </span>\n <span class=\"text-muted\">Enter to send</span>\n </div>\n </div>\n </div>\n </div>\n `;\n }\n\n async onInit() {\n // Conversation list for history panel\n this.conversations = new AssistantConversationList();\n this.conversations.params.user = this.app?.activeUser?.id;\n this.conversationListView = new AssistantConversationListView({\n containerId: 'conversation-list',\n collection: this.conversations\n });\n this.addChild(this.conversationListView);\n\n // Chat view\n this.chatView = new ChatView({\n containerId: 'chat-area',\n theme: 'compact',\n messageViewClass: AssistantMessageView,\n currentUserId: this.app?.activeUser?.id,\n showFileInput: false,\n showInput: false,\n adapter: this._createAdapter()\n });\n this.addChild(this.chatView);\n\n // Safety net: re-enable input on assistant message\n const origAddMessage = this.chatView.addMessage.bind(this.chatView);\n this.chatView.addMessage = (msg, scroll) => {\n origAddMessage(msg, scroll);\n if (msg.role === 'assistant' && (msg.content || msg.blocks?.length)) {\n this.chatView.hideThinking();\n this._setInputEnabled(true);\n }\n };\n\n // Wire conversation list events\n this.conversationListView.on('conversation:select', (data) => {\n this._onConversationSelect(data);\n this._toggleHistory(false);\n });\n this.conversationListView.on('conversation:new', () => {\n this._onNewConversation();\n this._toggleHistory(false);\n });\n this.conversationListView.on('conversation:deleted', (data) => this._onConversationDeleted(data));\n\n this._subscribeWS();\n }\n\n async onAfterRender() {\n await super.onAfterRender();\n\n const textarea = this.element.querySelector('[data-ref=\"input\"]');\n if (textarea) {\n textarea.addEventListener('input', () => this._autoResize(textarea));\n textarea.addEventListener('keydown', (e) => this._handleKeydown(e));\n setTimeout(() => textarea.focus(), 100);\n }\n\n // Load existing conversation after DOM is ready\n if (this.conversationId) {\n this._showChatArea();\n await this.chatView.refresh();\n }\n\n this._updateConnectionStatus();\n this._updateTitle();\n this._setupResizeHandle();\n }\n\n // ── Resize Handle ────────────────────────────────────────\n\n _setupResizeHandle() {\n const handle = this.element?.querySelector('[data-ref=\"resize-handle\"]');\n if (!handle) return;\n\n const MIN_WIDTH = 300;\n const MAX_WIDTH = 700;\n const STORAGE_KEY = 'mojo:assistant_panel_width';\n\n // Restore saved width\n const saved = localStorage.getItem(STORAGE_KEY);\n if (saved) {\n const w = parseInt(saved, 10);\n if (w >= MIN_WIDTH && w <= MAX_WIDTH) {\n const panelEl = document.getElementById('assistant-panel');\n if (panelEl) panelEl.style.width = w + 'px';\n }\n }\n\n let startX, startWidth;\n\n const onMouseMove = (e) => {\n const delta = startX - e.clientX;\n const newWidth = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, startWidth + delta));\n const panelEl = document.getElementById('assistant-panel');\n if (panelEl) panelEl.style.width = newWidth + 'px';\n };\n\n const onMouseUp = () => {\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n\n // Save width\n const panelEl = document.getElementById('assistant-panel');\n if (panelEl) {\n localStorage.setItem(STORAGE_KEY, parseInt(panelEl.style.width, 10));\n }\n };\n\n handle.addEventListener('mousedown', (e) => {\n e.preventDefault();\n startX = e.clientX;\n const panelEl = document.getElementById('assistant-panel');\n startWidth = panelEl ? panelEl.offsetWidth : 500;\n document.body.style.cursor = 'col-resize';\n document.body.style.userSelect = 'none';\n document.addEventListener('mousemove', onMouseMove);\n document.addEventListener('mouseup', onMouseUp);\n });\n }\n\n // ── Actions ──────────────────────────────────────────────\n\n onActionToggleHistory() {\n this._toggleHistory(!this._showingHistory);\n }\n\n onActionNewConversation() {\n this._onNewConversation();\n if (this._showingHistory) this._toggleHistory(false);\n }\n\n onActionClosePanel() {\n this.emit('panel:close');\n }\n\n onActionFullscreen() {\n this.emit('panel:fullscreen', { conversationId: this.conversationId });\n }\n\n onActionPopOut() {\n this.emit('panel:popout', { conversationId: this.conversationId });\n }\n\n onActionUseSuggestion(_event, element) {\n const text = element.dataset.text || element.closest('[data-text]')?.dataset.text;\n if (!text) return;\n\n const textarea = this.element.querySelector('[data-ref=\"input\"]');\n if (textarea) {\n textarea.value = text;\n this._autoResize(textarea);\n }\n this._sendMessage();\n }\n\n onActionSend() {\n this._sendMessage();\n }\n\n onActionStop() {\n this.chatView.hideThinking();\n this._setInputEnabled(true);\n this._showSystemMessage('Response cancelled.');\n const textarea = this.element?.querySelector('[data-ref=\"input\"]');\n if (textarea) textarea.focus();\n }\n\n // ── History Toggle ───────────────────────────────────────\n\n _toggleHistory(show) {\n this._showingHistory = show;\n const history = this.element?.querySelector('[data-ref=\"history\"]');\n const chat = this.element?.querySelector('[data-ref=\"chat-wrapper\"]');\n const toggleBtn = this.element?.querySelector('[data-action=\"toggle-history\"] i');\n\n if (history) history.classList.toggle('d-none', !show);\n if (chat) chat.classList.toggle('d-none', show);\n if (toggleBtn) toggleBtn.className = show ? 'bi bi-chat-dots' : 'bi bi-list';\n\n if (show) {\n this.conversationListView.refresh();\n }\n }\n\n // ── Input Handling ───────────────────────────────────────\n\n _autoResize(textarea) {\n textarea.style.height = 'auto';\n textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px';\n }\n\n _handleKeydown(e) {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n this._sendMessage();\n }\n }\n\n async _sendMessage() {\n const textarea = this.element.querySelector('[data-ref=\"input\"]');\n if (!textarea) return;\n\n const text = textarea.value.trim();\n if (!text) return;\n\n textarea.value = '';\n textarea.style.height = 'auto';\n this._showChatArea();\n await this.chatView.adapter.addNote({ text, files: [] });\n }\n\n _showChatArea() {\n if (this._hasMessages) return;\n this._hasMessages = true;\n\n const welcome = this.element.querySelector('[data-ref=\"welcome\"]');\n const chatArea = this.element.querySelector('[data-container=\"chat-area\"]');\n if (welcome) welcome.classList.add('d-none');\n if (chatArea) chatArea.classList.remove('d-none');\n }\n\n _showWelcome() {\n this._hasMessages = false;\n\n const welcome = this.element.querySelector('[data-ref=\"welcome\"]');\n const chatArea = this.element.querySelector('[data-container=\"chat-area\"]');\n if (welcome) welcome.classList.remove('d-none');\n if (chatArea) chatArea.classList.add('d-none');\n }\n\n _setInputEnabled(enabled, reason) {\n const textarea = this.element?.querySelector('[data-ref=\"input\"]');\n const sendBtn = this.element?.querySelector('[data-ref=\"send-btn\"]');\n const stopBtn = this.element?.querySelector('[data-ref=\"stop-btn\"]');\n\n if (textarea) textarea.disabled = !enabled;\n if (sendBtn) sendBtn.classList.toggle('d-none', !enabled);\n if (stopBtn) stopBtn.classList.toggle('d-none', enabled);\n\n this._setInputStatus(enabled ? null : reason);\n\n if (this._responseTimeout) clearTimeout(this._responseTimeout);\n if (!enabled) {\n this._responseTimeout = setTimeout(() => this._onResponseTimeout(), 60000);\n } else {\n this._requestStartTime = null;\n }\n }\n\n _setInputStatus(message) {\n const el = this.element?.querySelector('[data-ref=\"input-status\"]');\n if (!el) return;\n if (message) {\n el.innerHTML = `${this._escapeHtml(message)} <span class=\"assistant-input-status-dismiss\">Click to dismiss</span>`;\n el.classList.remove('d-none');\n if (!el._hasDismiss) {\n el._hasDismiss = true;\n el.addEventListener('click', () => {\n this.chatView.hideThinking();\n this._setInputEnabled(true);\n const textarea = this.element?.querySelector('[data-ref=\"input\"]');\n if (textarea) textarea.focus();\n });\n }\n } else {\n el.classList.add('d-none');\n el.innerHTML = '';\n }\n }\n\n _onResponseTimeout() {\n this._responseTimeout = null;\n this.chatView.hideThinking();\n this._setInputEnabled(true);\n this._showSystemMessage('Request timed out. Please try again.');\n }\n\n // ── Title ────────────────────────────────────────────────\n\n _updateTitle(title) {\n const el = this.element?.querySelector('[data-ref=\"panel-title\"]');\n if (!el) return;\n el.textContent = title || (this.conversationId ? 'Mojo' : 'New conversation');\n }\n\n // ── Chat Adapter ─────────────────────────────────────────\n\n _createAdapter() {\n return {\n fetch: async () => {\n if (!this.conversationId) return [];\n try {\n const conversation = new AssistantConversation({ id: this.conversationId });\n await conversation.fetch({ graph: 'detail' });\n const title = conversation.get('title') || conversation.get('summary');\n if (title) this._updateTitle(title);\n const messages = conversation.get('messages') || [];\n const transformed = messages.map(msg => this._transformMessage(msg)).filter(Boolean);\n return AssistantView._collapseMessages(transformed);\n } catch (_err) {\n if (_err.status === 404) {\n this._onNewConversation();\n this._showSystemMessage('Conversation not found.');\n }\n return [];\n }\n },\n addNote: async (data) => {\n if (!data.text || !data.text.trim()) return { success: false };\n\n const userMsg = {\n id: `local-${++this._messageIdCounter}`,\n role: 'user',\n author: {\n id: this.app?.activeUser?.id,\n name: this.app?.activeUser?.get('display_name') || 'You'\n },\n content: data.text,\n timestamp: new Date().toISOString()\n };\n this.chatView.addMessage(userMsg);\n\n this.chatView.showThinking('Thinking...');\n this._requestStartTime = Date.now();\n this._setInputEnabled(false, 'Waiting for response…');\n\n if (this.ws && this.ws.isConnected) {\n this.ws.send({\n type: 'assistant_message',\n message: data.text,\n conversation_id: this.conversationId\n });\n } else {\n try {\n const resp = await this.app.rest.post('/api/assistant', {\n message: data.text,\n conversation_id: this.conversationId\n });\n const respData = resp?.data?.data || resp?.data || resp;\n if (respData.conversation_id) {\n this.conversationId = respData.conversation_id;\n this.app._assistantConversationId = this.conversationId;\n }\n if (respData.response) {\n this.chatView.addMessage(this._transformMessage(respData.response));\n }\n this._setInputEnabled(true);\n } catch (_err) {\n this._handleAPIError(_err);\n }\n }\n\n return { success: true };\n }\n };\n }\n\n // ── WebSocket Subscriptions ───────────────────────────────\n\n _subscribeWS() {\n if (!this.ws) return;\n\n this._wsHandlers = {\n thinking: (data) => this._onThinking(data),\n text: (data) => this._onText(data),\n tool_call: (data) => this._onToolCall(data),\n response: (data) => this._onResponse(data),\n error: (data) => this._onError(data),\n plan: (data) => this._onPlan(data),\n plan_update: (data) => this._onPlanUpdate(data),\n message: (envelope) => this._dispatchWSMessage(envelope),\n connected: () => this._updateConnectionStatus(),\n disconnected: () => this._updateConnectionStatus(),\n reconnecting: () => this._updateConnectionStatus()\n };\n\n this.ws.on('message:assistant_thinking', this._wsHandlers.thinking);\n this.ws.on('message:assistant_text', this._wsHandlers.text);\n this.ws.on('message:assistant_tool_call', this._wsHandlers.tool_call);\n this.ws.on('message:assistant_response', this._wsHandlers.response);\n this.ws.on('message:assistant_error', this._wsHandlers.error);\n this.ws.on('message:assistant_plan', this._wsHandlers.plan);\n this.ws.on('message:assistant_plan_update', this._wsHandlers.plan_update);\n this.ws.on('message:message', this._wsHandlers.message);\n this.ws.on('connected', this._wsHandlers.connected);\n this.ws.on('disconnected', this._wsHandlers.disconnected);\n this.ws.on('reconnecting', this._wsHandlers.reconnecting);\n }\n\n _unsubscribeWS() {\n if (!this.ws || !this._wsHandlers) return;\n\n this.ws.off('message:assistant_thinking', this._wsHandlers.thinking);\n this.ws.off('message:assistant_text', this._wsHandlers.text);\n this.ws.off('message:assistant_tool_call', this._wsHandlers.tool_call);\n this.ws.off('message:assistant_response', this._wsHandlers.response);\n this.ws.off('message:assistant_error', this._wsHandlers.error);\n this.ws.off('message:assistant_plan', this._wsHandlers.plan);\n this.ws.off('message:assistant_plan_update', this._wsHandlers.plan_update);\n this.ws.off('message:message', this._wsHandlers.message);\n this.ws.off('connected', this._wsHandlers.connected);\n this.ws.off('disconnected', this._wsHandlers.disconnected);\n this.ws.off('reconnecting', this._wsHandlers.reconnecting);\n\n this._wsHandlers = {};\n }\n\n // ── WS Event Handlers ────────────────────────────────────\n\n _dispatchWSMessage(envelope) {\n const inner = envelope?.data;\n if (!inner?.type) return;\n\n switch (inner.type) {\n case 'assistant_thinking': this._onThinking(inner); break;\n case 'assistant_text': this._onText(inner); break;\n case 'assistant_tool_call': this._onToolCall(inner); break;\n case 'assistant_response': this._onResponse(inner); break;\n case 'assistant_error': this._onError(inner); break;\n case 'assistant_plan': this._onPlan(inner); break;\n case 'assistant_plan_update': this._onPlanUpdate(inner); break;\n }\n }\n\n _isMyConversation(data) {\n if (!data.conversation_id) return true;\n if (!this.conversationId) return true;\n return String(data.conversation_id) === String(this.conversationId);\n }\n\n _adoptConversationId(data) {\n if (data.conversation_id && !this.conversationId) {\n this.conversationId = data.conversation_id;\n this.app._assistantConversationId = this.conversationId;\n }\n }\n\n _onThinking(data) {\n if (!this._isMyConversation(data)) return;\n this._adoptConversationId(data);\n this._showChatArea();\n this.chatView.showThinking('Thinking...');\n this._setInputEnabled(false, 'Assistant is thinking…');\n }\n\n /**\n * Intermediate prose alongside tool calls in the same turn. Renders as\n * an assistant bubble, but does NOT clear the thinking indicator or\n * re-enable input — `assistant_response` remains the terminal signal.\n * @private\n */\n _onText(data) {\n if (!this._isMyConversation(data)) return;\n this._adoptConversationId(data);\n // Server is still working — refresh the safety timeout.\n this._resetResponseTimeout();\n\n const msg = this._transformMessage({\n id: data.message_id || `text-${++this._messageIdCounter}`,\n role: 'assistant',\n content: data.text || '',\n blocks: data.blocks || [],\n tool_calls: [],\n created: data.created || data.timestamp || new Date().toISOString()\n });\n\n if (msg && (msg.content || msg.blocks?.length)) {\n this.chatView.addMessage(msg);\n }\n }\n\n _onToolCall(data) {\n if (!this._isMyConversation(data)) return;\n this.chatView.showThinking(`Using ${data.tool || data.name || 'tool'}...`);\n this._resetResponseTimeout();\n }\n\n _resetResponseTimeout() {\n if (this._responseTimeout) {\n if (this._requestStartTime && (Date.now() - this._requestStartTime) >= 300000) {\n this._onResponseTimeout();\n return;\n }\n clearTimeout(this._responseTimeout);\n this._responseTimeout = setTimeout(() => this._onResponseTimeout(), 60000);\n }\n }\n\n _onResponse(data) {\n if (!this._isMyConversation(data)) return;\n\n this.chatView.hideThinking();\n this._setInputEnabled(true);\n this._adoptConversationId(data);\n\n const textarea = this.element?.querySelector('[data-ref=\"input\"]');\n if (textarea) textarea.focus();\n\n const msg = this._transformMessage({\n id: data.message_id || `resp-${++this._messageIdCounter}`,\n role: 'assistant',\n content: data.response || data.content || data.message || '',\n blocks: data.blocks || [],\n tool_calls: data.tool_calls_made || data.tool_calls || [],\n created: data.created || data.timestamp || new Date().toISOString()\n });\n\n if (msg && (msg.content || msg.blocks?.length || msg.tool_calls?.length)) {\n this.chatView.addMessage(msg);\n }\n }\n\n _onError(data) {\n if (!this._isMyConversation(data)) return;\n\n this.chatView.hideThinking();\n this._setInputEnabled(true);\n this._adoptConversationId(data);\n\n const errorText = data.error || data.message || 'An error occurred';\n this._showSystemMessage(errorText);\n }\n\n _onPlan(data) {\n if (!this._isMyConversation(data)) return;\n this._adoptConversationId(data);\n this._showChatArea();\n\n const plan = data.plan;\n if (!plan) return;\n\n this._activePlans[plan.plan_id] = plan;\n\n this.chatView.addMessage({\n id: `plan-${plan.plan_id}`,\n role: 'assistant',\n author: { name: 'Mojo' },\n content: '',\n timestamp: new Date().toISOString(),\n blocks: [{ type: 'progress', ...plan }],\n tool_calls: []\n });\n }\n\n _onPlanUpdate(data) {\n if (!this._isMyConversation(data)) return;\n\n const plan = this._activePlans[data.plan_id];\n if (plan) {\n const step = plan.steps.find(s => s.id === data.step_id);\n if (step) {\n step.status = data.status;\n step.summary = data.summary;\n }\n }\n\n const msgView = this.chatView.messageViews.get(`plan-${data.plan_id}`);\n if (msgView?.updateProgressStep) {\n msgView.updateProgressStep(data.plan_id, data.step_id, data.status, data.summary);\n }\n this._resetResponseTimeout();\n }\n\n // ── Conversation Management ──────────────────────────────\n\n async _onConversationSelect(data) {\n this.conversationId = data.id;\n this.app._assistantConversationId = this.conversationId;\n this.conversationListView.setActive(data.id);\n this._showChatArea();\n this._updateTitle(data.model?.get('title') || data.model?.get('summary'));\n await this.chatView.refresh();\n }\n\n _onNewConversation() {\n this.conversationId = null;\n this.app._assistantConversationId = null;\n this.conversationListView.setActive(null);\n this.chatView.clearMessages();\n this._setInputEnabled(true);\n this._showWelcome();\n this._updateTitle();\n\n const textarea = this.element?.querySelector('[data-ref=\"input\"]');\n if (textarea) textarea.focus();\n }\n\n _onConversationDeleted(data) {\n if (String(data.id) === String(this.conversationId)) {\n this._onNewConversation();\n }\n }\n\n // ── Helpers ──────────────────────────────────────────────\n\n _transformMessage(msg) {\n if (msg.role === 'tool_result') return null;\n\n let content = msg.content || msg.text || '';\n let blocks = msg.blocks || [];\n let toolCalls = msg.tool_calls || [];\n\n if (toolCalls.length > 0) {\n // Normalize WS `tool_calls_made` shape `{ tool, input }` → Anthropic `{ type, name, input }`\n toolCalls = toolCalls.map(tc =>\n (!tc.type && tc.tool)\n ? { type: 'tool_use', name: tc.tool, input: tc.input }\n : tc\n );\n const textParts = toolCalls\n .filter(tc => tc.type === 'text' && tc.text)\n .map(tc => tc.text);\n if (!content && textParts.length > 0) {\n content = textParts.join('\\n\\n');\n }\n toolCalls = toolCalls\n .filter(tc => tc.type === 'tool_use')\n .filter(tc => !AssistantView.INTERNAL_TOOLS.has(tc.name));\n }\n\n if (blocks.length === 0 && content.includes('assistant_block')) {\n const parsed = AssistantView._parseBlocks(content);\n content = parsed.content;\n blocks = parsed.blocks;\n }\n\n const currentUserId = this.app?.activeUser?.id;\n\n return {\n id: msg.id,\n role: msg.role || 'user',\n author: msg.role === 'assistant'\n ? { name: 'Mojo' }\n : msg.author || {\n name: msg.user?.display_name || this.app?.activeUser?.get('display_name') || 'You',\n id: msg.user?.id || currentUserId\n },\n content,\n timestamp: msg.created || msg.timestamp,\n blocks,\n tool_calls: toolCalls,\n _conversationId: this.conversationId\n };\n }\n\n _showSystemMessage(text) {\n this._showChatArea();\n this.chatView.addMessage({\n id: `sys-${++this._messageIdCounter}`,\n type: 'system_event',\n content: text,\n timestamp: new Date().toISOString()\n });\n }\n\n _handleAPIError(_err) {\n if (_err.status === 404) {\n this._showSystemMessage('Assistant is not enabled on this server.');\n } else if (_err.status === 503) {\n this._showSystemMessage('LLM API key not configured. Contact your administrator.');\n } else {\n this._showSystemMessage('Failed to send message. Please try again.');\n }\n this._setInputEnabled(true);\n }\n\n _updateConnectionStatus() {\n const dot = this.element?.querySelector('.status-dot');\n if (!dot) return;\n\n if (this.ws?.isConnected) {\n dot.className = 'status-dot connected';\n dot.title = 'Connected';\n if (!this._responseTimeout) {\n this._setInputEnabled(true);\n } else {\n this._setInputEnabled(false, 'Waiting for response…');\n }\n } else if (this.ws?.isReconnecting) {\n dot.className = 'status-dot reconnecting';\n dot.title = 'Reconnecting...';\n this._setInputEnabled(false, 'Reconnecting…');\n if (this._responseTimeout) {\n clearTimeout(this._responseTimeout);\n this._responseTimeout = null;\n }\n } else {\n dot.className = 'status-dot disconnected';\n dot.title = 'Disconnected';\n this._setInputEnabled(false, 'Disconnected — reconnecting…');\n if (this._responseTimeout) {\n clearTimeout(this._responseTimeout);\n this._responseTimeout = null;\n }\n }\n }\n\n _escapeHtml(text) {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n /**\n * Focus the input textarea — called externally when reopening the panel.\n */\n focusInput() {\n const textarea = this.element?.querySelector('[data-ref=\"input\"]');\n if (textarea) textarea.focus();\n }\n\n async onBeforeDestroy() {\n this._unsubscribeWS();\n if (this._responseTimeout) {\n clearTimeout(this._responseTimeout);\n this._responseTimeout = null;\n }\n }\n}\n\nexport default AssistantPanelView;\n"],"names":["AssistantPanelView","View","constructor","options","super","className","this","app","ws","conversationId","_assistantConversationId","_wsHandlers","_messageIdCounter","_hasMessages","_activePlans","_requestStartTime","_showingHistory","getTemplate","_escapeHtml","activeUser","get","onInit","conversations","AssistantConversationList","params","user","id","conversationListView","AssistantConversationListView","containerId","collection","addChild","chatView","ChatView","theme","messageViewClass","AssistantMessageView","currentUserId","showFileInput","showInput","adapter","_createAdapter","origAddMessage","addMessage","bind","msg","scroll","role","content","blocks","length","hideThinking","_setInputEnabled","on","data","_onConversationSelect","_toggleHistory","_onNewConversation","_onConversationDeleted","_subscribeWS","onAfterRender","textarea","element","querySelector","addEventListener","_autoResize","e","_handleKeydown","setTimeout","focus","_showChatArea","refresh","_updateConnectionStatus","_updateTitle","_setupResizeHandle","handle","STORAGE_KEY","saved","localStorage","getItem","w","parseInt","panelEl","document","getElementById","style","width","startX","startWidth","onMouseMove","delta","clientX","newWidth","Math","min","max","onMouseUp","removeEventListener","body","cursor","userSelect","setItem","preventDefault","offsetWidth","onActionToggleHistory","onActionNewConversation","onActionClosePanel","emit","onActionFullscreen","onActionPopOut","onActionUseSuggestion","_event","text","dataset","closest","value","_sendMessage","onActionSend","onActionStop","_showSystemMessage","show","history","chat","toggleBtn","classList","toggle","height","scrollHeight","key","shiftKey","trim","addNote","files","welcome","chatArea","add","remove","_showWelcome","enabled","reason","sendBtn","stopBtn","disabled","_setInputStatus","_responseTimeout","clearTimeout","_onResponseTimeout","message","el","innerHTML","_hasDismiss","title","textContent","fetch","async","conversation","AssistantConversation","graph","transformed","map","_transformMessage","filter","Boolean","AssistantView","_collapseMessages","_err","status","success","userMsg","author","name","timestamp","Date","toISOString","showThinking","now","isConnected","send","type","conversation_id","resp","rest","post","respData","response","_handleAPIError","thinking","_onThinking","_onText","tool_call","_onToolCall","_onResponse","error","_onError","plan","_onPlan","plan_update","_onPlanUpdate","envelope","_dispatchWSMessage","connected","disconnected","reconnecting","_unsubscribeWS","off","inner","_isMyConversation","String","_adoptConversationId","_resetResponseTimeout","message_id","tool_calls","created","tool","tool_calls_made","errorText","plan_id","step","steps","find","s","step_id","summary","msgView","messageViews","updateProgressStep","setActive","model","clearMessages","toolCalls","tc","input","textParts","join","INTERNAL_TOOLS","has","includes","parsed","_parseBlocks","display_name","_conversationId","dot","isReconnecting","div","createElement","focusInput","onBeforeDestroy"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AssistantPanelView-rkVX6wky.js","sources":["../../src/extensions/admin/assistant/AssistantPanelView.js"],"sourcesContent":["import View from '@core/View.js';\nimport ChatView from '@core/views/chat/ChatView.js';\nimport { AssistantConversation, AssistantConversationList } from '@ext/admin/models/Assistant.js';\nimport AssistantMessageView from './AssistantMessageView.js';\nimport AssistantConversationListView from './AssistantConversationListView.js';\nimport AssistantView from './AssistantView.js';\n\n/**\n * AssistantPanelView - Chat-only sidebar panel for the admin assistant\n *\n * Compact layout for the right sidebar panel. No conversation list by default —\n * a hamburger toggle switches between chat and conversation history.\n *\n * Emits:\n * panel:close — when the close button is clicked\n */\nclass AssistantPanelView extends View {\n constructor(options = {}) {\n super({\n className: 'assistant-panel-view',\n ...options\n });\n\n this.app = options.app;\n this.ws = this.app?.ws;\n this.conversationId = options.conversationId || this.app?._assistantConversationId || null;\n this._wsHandlers = {};\n this._messageIdCounter = 0;\n this._hasMessages = false;\n this._activePlans = {};\n this._requestStartTime = null;\n this._showingHistory = false;\n }\n\n getTemplate() {\n const userName = this._escapeHtml(\n this.app?.activeUser?.get('first_name') || 'there'\n );\n\n return `\n <div class=\"assistant-panel-resize-handle\" data-ref=\"resize-handle\"></div>\n <div class=\"assistant-panel-layout\">\n <div class=\"assistant-panel-header\">\n <button class=\"assistant-panel-header-btn\" data-action=\"toggle-history\" type=\"button\" title=\"Conversation history\">\n <i class=\"bi bi-list\"></i>\n </button>\n <span class=\"assistant-panel-title text-truncate\" data-ref=\"panel-title\">New conversation</span>\n <div class=\"d-flex gap-1 ms-auto\">\n <button class=\"assistant-panel-header-btn\" data-action=\"new-conversation\" type=\"button\" title=\"New conversation\">\n <i class=\"bi bi-plus-lg\"></i>\n </button>\n <button class=\"assistant-panel-header-btn\" data-action=\"fullscreen\" type=\"button\" title=\"Open fullscreen\">\n <i class=\"bi bi-arrows-fullscreen\"></i>\n </button>\n <button class=\"assistant-panel-header-btn\" data-action=\"pop-out\" type=\"button\" title=\"Open in popup window\">\n <i class=\"bi bi-box-arrow-up-right\"></i>\n </button>\n <button class=\"assistant-panel-header-btn\" data-action=\"close-panel\" type=\"button\" title=\"Close\">\n <i class=\"bi bi-x-lg\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"assistant-panel-history d-none\" data-ref=\"history\" data-container=\"conversation-list\"></div>\n\n <div class=\"assistant-panel-chat\" data-ref=\"chat-wrapper\">\n <div class=\"assistant-welcome\" data-ref=\"welcome\">\n <div class=\"assistant-welcome-content\">\n <div class=\"assistant-welcome-icon\">\n <img src=\"https://mojo-verify.s3.amazonaws.com/signatures/14e7aab75c2749cb846f7d57298691ac/mojo_ai_7c0322e9.png\" alt=\"Mojo\">\n </div>\n <h3 class=\"assistant-welcome-title\">Hi ${userName}</h3>\n <p class=\"assistant-welcome-subtitle\">How can I help you today?</p>\n <div class=\"assistant-suggestions\">\n <button class=\"assistant-suggestion\" data-action=\"use-suggestion\" data-text=\"Show me a summary of recent activity\">\n <i class=\"bi bi-activity\"></i>\n <span>Recent activity summary</span>\n </button>\n <button class=\"assistant-suggestion\" data-action=\"use-suggestion\" data-text=\"How many active users are there?\">\n <i class=\"bi bi-people\"></i>\n <span>Active user count</span>\n </button>\n </div>\n </div>\n </div>\n <div class=\"assistant-chat-area\" data-container=\"chat-area\"></div>\n <div class=\"assistant-input-wrapper\">\n <div class=\"assistant-input-status d-none\" data-ref=\"input-status\"></div>\n <div class=\"assistant-input-box\">\n <textarea class=\"assistant-input\" placeholder=\"Message Mojo...\" rows=\"1\" data-ref=\"input\"></textarea>\n <button class=\"assistant-send-btn\" data-action=\"send\" type=\"button\" title=\"Send message\" data-ref=\"send-btn\">\n <i class=\"bi bi-arrow-up\"></i>\n </button>\n <button class=\"assistant-stop-btn d-none\" data-action=\"stop\" type=\"button\" title=\"Stop generating\" data-ref=\"stop-btn\">\n <i class=\"bi bi-stop-fill\"></i>\n </button>\n </div>\n <div class=\"assistant-input-footer\">\n <span class=\"assistant-connection-indicator\" data-ref=\"status\">\n <span class=\"status-dot connected\"></span>\n </span>\n <span class=\"text-muted\">Enter to send</span>\n </div>\n </div>\n </div>\n </div>\n `;\n }\n\n async onInit() {\n // Conversation list for history panel\n this.conversations = new AssistantConversationList();\n this.conversations.params.user = this.app?.activeUser?.id;\n this.conversationListView = new AssistantConversationListView({\n containerId: 'conversation-list',\n collection: this.conversations\n });\n this.addChild(this.conversationListView);\n\n // Chat view\n this.chatView = new ChatView({\n containerId: 'chat-area',\n theme: 'compact',\n messageViewClass: AssistantMessageView,\n currentUserId: this.app?.activeUser?.id,\n showFileInput: false,\n showInput: false,\n adapter: this._createAdapter()\n });\n this.addChild(this.chatView);\n\n // Safety net: re-enable input on assistant message\n const origAddMessage = this.chatView.addMessage.bind(this.chatView);\n this.chatView.addMessage = (msg, scroll) => {\n origAddMessage(msg, scroll);\n if (msg.role === 'assistant' && (msg.content || msg.blocks?.length)) {\n this.chatView.hideThinking();\n this._setInputEnabled(true);\n }\n };\n\n // Wire conversation list events\n this.conversationListView.on('conversation:select', (data) => {\n this._onConversationSelect(data);\n this._toggleHistory(false);\n });\n this.conversationListView.on('conversation:new', () => {\n this._onNewConversation();\n this._toggleHistory(false);\n });\n this.conversationListView.on('conversation:deleted', (data) => this._onConversationDeleted(data));\n\n this._subscribeWS();\n }\n\n async onAfterRender() {\n await super.onAfterRender();\n\n const textarea = this.element.querySelector('[data-ref=\"input\"]');\n if (textarea) {\n textarea.addEventListener('input', () => this._autoResize(textarea));\n textarea.addEventListener('keydown', (e) => this._handleKeydown(e));\n setTimeout(() => textarea.focus(), 100);\n }\n\n // Load existing conversation after DOM is ready\n if (this.conversationId) {\n this._showChatArea();\n await this.chatView.refresh();\n }\n\n this._updateConnectionStatus();\n this._updateTitle();\n this._setupResizeHandle();\n }\n\n // ── Resize Handle ────────────────────────────────────────\n\n _setupResizeHandle() {\n const handle = this.element?.querySelector('[data-ref=\"resize-handle\"]');\n if (!handle) return;\n\n const MIN_WIDTH = 300;\n const MAX_WIDTH = 700;\n const STORAGE_KEY = 'mojo:assistant_panel_width';\n\n // Restore saved width\n const saved = localStorage.getItem(STORAGE_KEY);\n if (saved) {\n const w = parseInt(saved, 10);\n if (w >= MIN_WIDTH && w <= MAX_WIDTH) {\n const panelEl = document.getElementById('assistant-panel');\n if (panelEl) panelEl.style.width = w + 'px';\n }\n }\n\n let startX, startWidth;\n\n const onMouseMove = (e) => {\n const delta = startX - e.clientX;\n const newWidth = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, startWidth + delta));\n const panelEl = document.getElementById('assistant-panel');\n if (panelEl) panelEl.style.width = newWidth + 'px';\n };\n\n const onMouseUp = () => {\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n\n // Save width\n const panelEl = document.getElementById('assistant-panel');\n if (panelEl) {\n localStorage.setItem(STORAGE_KEY, parseInt(panelEl.style.width, 10));\n }\n };\n\n handle.addEventListener('mousedown', (e) => {\n e.preventDefault();\n startX = e.clientX;\n const panelEl = document.getElementById('assistant-panel');\n startWidth = panelEl ? panelEl.offsetWidth : 500;\n document.body.style.cursor = 'col-resize';\n document.body.style.userSelect = 'none';\n document.addEventListener('mousemove', onMouseMove);\n document.addEventListener('mouseup', onMouseUp);\n });\n }\n\n // ── Actions ──────────────────────────────────────────────\n\n onActionToggleHistory() {\n this._toggleHistory(!this._showingHistory);\n }\n\n onActionNewConversation() {\n this._onNewConversation();\n if (this._showingHistory) this._toggleHistory(false);\n }\n\n onActionClosePanel() {\n this.emit('panel:close');\n }\n\n onActionFullscreen() {\n this.emit('panel:fullscreen', { conversationId: this.conversationId });\n }\n\n onActionPopOut() {\n this.emit('panel:popout', { conversationId: this.conversationId });\n }\n\n onActionUseSuggestion(_event, element) {\n const text = element.dataset.text || element.closest('[data-text]')?.dataset.text;\n if (!text) return;\n\n const textarea = this.element.querySelector('[data-ref=\"input\"]');\n if (textarea) {\n textarea.value = text;\n this._autoResize(textarea);\n }\n this._sendMessage();\n }\n\n onActionSend() {\n this._sendMessage();\n }\n\n onActionStop() {\n this.chatView.hideThinking();\n this._setInputEnabled(true);\n this._showSystemMessage('Response cancelled.');\n const textarea = this.element?.querySelector('[data-ref=\"input\"]');\n if (textarea) textarea.focus();\n }\n\n // ── History Toggle ───────────────────────────────────────\n\n _toggleHistory(show) {\n this._showingHistory = show;\n const history = this.element?.querySelector('[data-ref=\"history\"]');\n const chat = this.element?.querySelector('[data-ref=\"chat-wrapper\"]');\n const toggleBtn = this.element?.querySelector('[data-action=\"toggle-history\"] i');\n\n if (history) history.classList.toggle('d-none', !show);\n if (chat) chat.classList.toggle('d-none', show);\n if (toggleBtn) toggleBtn.className = show ? 'bi bi-chat-dots' : 'bi bi-list';\n\n if (show) {\n this.conversationListView.refresh();\n }\n }\n\n // ── Input Handling ───────────────────────────────────────\n\n _autoResize(textarea) {\n textarea.style.height = 'auto';\n textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px';\n }\n\n _handleKeydown(e) {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n this._sendMessage();\n }\n }\n\n async _sendMessage() {\n const textarea = this.element.querySelector('[data-ref=\"input\"]');\n if (!textarea) return;\n\n const text = textarea.value.trim();\n if (!text) return;\n\n textarea.value = '';\n textarea.style.height = 'auto';\n this._showChatArea();\n await this.chatView.adapter.addNote({ text, files: [] });\n }\n\n _showChatArea() {\n if (this._hasMessages) return;\n this._hasMessages = true;\n\n const welcome = this.element.querySelector('[data-ref=\"welcome\"]');\n const chatArea = this.element.querySelector('[data-container=\"chat-area\"]');\n if (welcome) welcome.classList.add('d-none');\n if (chatArea) chatArea.classList.remove('d-none');\n }\n\n _showWelcome() {\n this._hasMessages = false;\n\n const welcome = this.element.querySelector('[data-ref=\"welcome\"]');\n const chatArea = this.element.querySelector('[data-container=\"chat-area\"]');\n if (welcome) welcome.classList.remove('d-none');\n if (chatArea) chatArea.classList.add('d-none');\n }\n\n _setInputEnabled(enabled, reason) {\n const textarea = this.element?.querySelector('[data-ref=\"input\"]');\n const sendBtn = this.element?.querySelector('[data-ref=\"send-btn\"]');\n const stopBtn = this.element?.querySelector('[data-ref=\"stop-btn\"]');\n\n if (textarea) textarea.disabled = !enabled;\n if (sendBtn) sendBtn.classList.toggle('d-none', !enabled);\n if (stopBtn) stopBtn.classList.toggle('d-none', enabled);\n\n this._setInputStatus(enabled ? null : reason);\n\n if (this._responseTimeout) clearTimeout(this._responseTimeout);\n if (!enabled) {\n this._responseTimeout = setTimeout(() => this._onResponseTimeout(), 60000);\n } else {\n this._requestStartTime = null;\n }\n }\n\n _setInputStatus(message) {\n const el = this.element?.querySelector('[data-ref=\"input-status\"]');\n if (!el) return;\n if (message) {\n el.innerHTML = `${this._escapeHtml(message)} <span class=\"assistant-input-status-dismiss\">Click to dismiss</span>`;\n el.classList.remove('d-none');\n if (!el._hasDismiss) {\n el._hasDismiss = true;\n el.addEventListener('click', () => {\n this.chatView.hideThinking();\n this._setInputEnabled(true);\n const textarea = this.element?.querySelector('[data-ref=\"input\"]');\n if (textarea) textarea.focus();\n });\n }\n } else {\n el.classList.add('d-none');\n el.innerHTML = '';\n }\n }\n\n _onResponseTimeout() {\n this._responseTimeout = null;\n this.chatView.hideThinking();\n this._setInputEnabled(true);\n this._showSystemMessage('Request timed out. Please try again.');\n }\n\n // ── Title ────────────────────────────────────────────────\n\n _updateTitle(title) {\n const el = this.element?.querySelector('[data-ref=\"panel-title\"]');\n if (!el) return;\n el.textContent = title || (this.conversationId ? 'Mojo' : 'New conversation');\n }\n\n // ── Chat Adapter ─────────────────────────────────────────\n\n _createAdapter() {\n return {\n fetch: async () => {\n if (!this.conversationId) return [];\n try {\n const conversation = new AssistantConversation({ id: this.conversationId });\n await conversation.fetch({ graph: 'detail' });\n const title = conversation.get('title') || conversation.get('summary');\n if (title) this._updateTitle(title);\n const messages = conversation.get('messages') || [];\n const transformed = messages.map(msg => this._transformMessage(msg)).filter(Boolean);\n return AssistantView._collapseMessages(transformed);\n } catch (_err) {\n if (_err.status === 404) {\n this._onNewConversation();\n this._showSystemMessage('Conversation not found.');\n }\n return [];\n }\n },\n addNote: async (data) => {\n if (!data.text || !data.text.trim()) return { success: false };\n\n const userMsg = {\n id: `local-${++this._messageIdCounter}`,\n role: 'user',\n author: {\n id: this.app?.activeUser?.id,\n name: this.app?.activeUser?.get('display_name') || 'You'\n },\n content: data.text,\n timestamp: new Date().toISOString()\n };\n this.chatView.addMessage(userMsg);\n\n this.chatView.showThinking('Thinking...');\n this._requestStartTime = Date.now();\n this._setInputEnabled(false, 'Waiting for response…');\n\n if (this.ws && this.ws.isConnected) {\n this.ws.send({\n type: 'assistant_message',\n message: data.text,\n conversation_id: this.conversationId\n });\n } else {\n try {\n const resp = await this.app.rest.post('/api/assistant', {\n message: data.text,\n conversation_id: this.conversationId\n });\n const respData = resp?.data?.data || resp?.data || resp;\n if (respData.conversation_id) {\n this.conversationId = respData.conversation_id;\n this.app._assistantConversationId = this.conversationId;\n }\n if (respData.response) {\n this.chatView.addMessage(this._transformMessage(respData.response));\n }\n this._setInputEnabled(true);\n } catch (_err) {\n this._handleAPIError(_err);\n }\n }\n\n return { success: true };\n }\n };\n }\n\n // ── WebSocket Subscriptions ───────────────────────────────\n\n _subscribeWS() {\n if (!this.ws) return;\n\n this._wsHandlers = {\n thinking: (data) => this._onThinking(data),\n text: (data) => this._onText(data),\n tool_call: (data) => this._onToolCall(data),\n response: (data) => this._onResponse(data),\n error: (data) => this._onError(data),\n plan: (data) => this._onPlan(data),\n plan_update: (data) => this._onPlanUpdate(data),\n message: (envelope) => this._dispatchWSMessage(envelope),\n connected: () => this._updateConnectionStatus(),\n disconnected: () => this._updateConnectionStatus(),\n reconnecting: () => this._updateConnectionStatus()\n };\n\n this.ws.on('message:assistant_thinking', this._wsHandlers.thinking);\n this.ws.on('message:assistant_text', this._wsHandlers.text);\n this.ws.on('message:assistant_tool_call', this._wsHandlers.tool_call);\n this.ws.on('message:assistant_response', this._wsHandlers.response);\n this.ws.on('message:assistant_error', this._wsHandlers.error);\n this.ws.on('message:assistant_plan', this._wsHandlers.plan);\n this.ws.on('message:assistant_plan_update', this._wsHandlers.plan_update);\n this.ws.on('message:message', this._wsHandlers.message);\n this.ws.on('connected', this._wsHandlers.connected);\n this.ws.on('disconnected', this._wsHandlers.disconnected);\n this.ws.on('reconnecting', this._wsHandlers.reconnecting);\n }\n\n _unsubscribeWS() {\n if (!this.ws || !this._wsHandlers) return;\n\n this.ws.off('message:assistant_thinking', this._wsHandlers.thinking);\n this.ws.off('message:assistant_text', this._wsHandlers.text);\n this.ws.off('message:assistant_tool_call', this._wsHandlers.tool_call);\n this.ws.off('message:assistant_response', this._wsHandlers.response);\n this.ws.off('message:assistant_error', this._wsHandlers.error);\n this.ws.off('message:assistant_plan', this._wsHandlers.plan);\n this.ws.off('message:assistant_plan_update', this._wsHandlers.plan_update);\n this.ws.off('message:message', this._wsHandlers.message);\n this.ws.off('connected', this._wsHandlers.connected);\n this.ws.off('disconnected', this._wsHandlers.disconnected);\n this.ws.off('reconnecting', this._wsHandlers.reconnecting);\n\n this._wsHandlers = {};\n }\n\n // ── WS Event Handlers ────────────────────────────────────\n\n _dispatchWSMessage(envelope) {\n const inner = envelope?.data;\n if (!inner?.type) return;\n\n switch (inner.type) {\n case 'assistant_thinking': this._onThinking(inner); break;\n case 'assistant_text': this._onText(inner); break;\n case 'assistant_tool_call': this._onToolCall(inner); break;\n case 'assistant_response': this._onResponse(inner); break;\n case 'assistant_error': this._onError(inner); break;\n case 'assistant_plan': this._onPlan(inner); break;\n case 'assistant_plan_update': this._onPlanUpdate(inner); break;\n }\n }\n\n _isMyConversation(data) {\n if (!data.conversation_id) return true;\n if (!this.conversationId) return true;\n return String(data.conversation_id) === String(this.conversationId);\n }\n\n _adoptConversationId(data) {\n if (data.conversation_id && !this.conversationId) {\n this.conversationId = data.conversation_id;\n this.app._assistantConversationId = this.conversationId;\n }\n }\n\n _onThinking(data) {\n if (!this._isMyConversation(data)) return;\n this._adoptConversationId(data);\n this._showChatArea();\n this.chatView.showThinking('Thinking...');\n this._setInputEnabled(false, 'Assistant is thinking…');\n }\n\n /**\n * Intermediate prose alongside tool calls in the same turn. Renders as\n * an assistant bubble, but does NOT clear the thinking indicator or\n * re-enable input — `assistant_response` remains the terminal signal.\n * @private\n */\n _onText(data) {\n if (!this._isMyConversation(data)) return;\n this._adoptConversationId(data);\n // Server is still working — refresh the safety timeout.\n this._resetResponseTimeout();\n\n const msg = this._transformMessage({\n id: data.message_id || `text-${++this._messageIdCounter}`,\n role: 'assistant',\n content: data.text || '',\n blocks: data.blocks || [],\n tool_calls: [],\n created: data.created || data.timestamp || new Date().toISOString()\n });\n\n if (msg && (msg.content || msg.blocks?.length)) {\n this.chatView.addMessage(msg);\n }\n }\n\n _onToolCall(data) {\n if (!this._isMyConversation(data)) return;\n this.chatView.showThinking(`Using ${data.tool || data.name || 'tool'}...`);\n this._resetResponseTimeout();\n }\n\n _resetResponseTimeout() {\n if (this._responseTimeout) {\n if (this._requestStartTime && (Date.now() - this._requestStartTime) >= 300000) {\n this._onResponseTimeout();\n return;\n }\n clearTimeout(this._responseTimeout);\n this._responseTimeout = setTimeout(() => this._onResponseTimeout(), 60000);\n }\n }\n\n _onResponse(data) {\n if (!this._isMyConversation(data)) return;\n\n this.chatView.hideThinking();\n this._setInputEnabled(true);\n this._adoptConversationId(data);\n\n const textarea = this.element?.querySelector('[data-ref=\"input\"]');\n if (textarea) textarea.focus();\n\n const msg = this._transformMessage({\n id: data.message_id || `resp-${++this._messageIdCounter}`,\n role: 'assistant',\n content: data.response || data.content || data.message || '',\n blocks: data.blocks || [],\n tool_calls: data.tool_calls_made || data.tool_calls || [],\n created: data.created || data.timestamp || new Date().toISOString()\n });\n\n if (msg && (msg.content || msg.blocks?.length || msg.tool_calls?.length)) {\n this.chatView.addMessage(msg);\n }\n }\n\n _onError(data) {\n if (!this._isMyConversation(data)) return;\n\n this.chatView.hideThinking();\n this._setInputEnabled(true);\n this._adoptConversationId(data);\n\n const errorText = data.error || data.message || 'An error occurred';\n this._showSystemMessage(errorText);\n }\n\n _onPlan(data) {\n if (!this._isMyConversation(data)) return;\n this._adoptConversationId(data);\n this._showChatArea();\n\n const plan = data.plan;\n if (!plan) return;\n\n this._activePlans[plan.plan_id] = plan;\n\n this.chatView.addMessage({\n id: `plan-${plan.plan_id}`,\n role: 'assistant',\n author: { name: 'Mojo' },\n content: '',\n timestamp: new Date().toISOString(),\n blocks: [{ type: 'progress', ...plan }],\n tool_calls: []\n });\n }\n\n _onPlanUpdate(data) {\n if (!this._isMyConversation(data)) return;\n\n const plan = this._activePlans[data.plan_id];\n if (plan) {\n const step = plan.steps.find(s => s.id === data.step_id);\n if (step) {\n step.status = data.status;\n step.summary = data.summary;\n }\n }\n\n const msgView = this.chatView.messageViews.get(`plan-${data.plan_id}`);\n if (msgView?.updateProgressStep) {\n msgView.updateProgressStep(data.plan_id, data.step_id, data.status, data.summary);\n }\n this._resetResponseTimeout();\n }\n\n // ── Conversation Management ──────────────────────────────\n\n async _onConversationSelect(data) {\n this.conversationId = data.id;\n this.app._assistantConversationId = this.conversationId;\n this.conversationListView.setActive(data.id);\n this._showChatArea();\n this._updateTitle(data.model?.get('title') || data.model?.get('summary'));\n await this.chatView.refresh();\n }\n\n _onNewConversation() {\n this.conversationId = null;\n this.app._assistantConversationId = null;\n this.conversationListView.setActive(null);\n this.chatView.clearMessages();\n this._setInputEnabled(true);\n this._showWelcome();\n this._updateTitle();\n\n const textarea = this.element?.querySelector('[data-ref=\"input\"]');\n if (textarea) textarea.focus();\n }\n\n _onConversationDeleted(data) {\n if (String(data.id) === String(this.conversationId)) {\n this._onNewConversation();\n }\n }\n\n // ── Helpers ──────────────────────────────────────────────\n\n _transformMessage(msg) {\n if (msg.role === 'tool_result') return null;\n\n let content = msg.content || msg.text || '';\n let blocks = msg.blocks || [];\n let toolCalls = msg.tool_calls || [];\n\n if (toolCalls.length > 0) {\n // Normalize WS `tool_calls_made` shape `{ tool, input }` → Anthropic `{ type, name, input }`\n toolCalls = toolCalls.map(tc =>\n (!tc.type && tc.tool)\n ? { type: 'tool_use', name: tc.tool, input: tc.input }\n : tc\n );\n const textParts = toolCalls\n .filter(tc => tc.type === 'text' && tc.text)\n .map(tc => tc.text);\n if (!content && textParts.length > 0) {\n content = textParts.join('\\n\\n');\n }\n toolCalls = toolCalls\n .filter(tc => tc.type === 'tool_use')\n .filter(tc => !AssistantView.INTERNAL_TOOLS.has(tc.name));\n }\n\n if (blocks.length === 0 && content.includes('assistant_block')) {\n const parsed = AssistantView._parseBlocks(content);\n content = parsed.content;\n blocks = parsed.blocks;\n }\n\n const currentUserId = this.app?.activeUser?.id;\n\n return {\n id: msg.id,\n role: msg.role || 'user',\n author: msg.role === 'assistant'\n ? { name: 'Mojo' }\n : msg.author || {\n name: msg.user?.display_name || this.app?.activeUser?.get('display_name') || 'You',\n id: msg.user?.id || currentUserId\n },\n content,\n timestamp: msg.created || msg.timestamp,\n blocks,\n tool_calls: toolCalls,\n _conversationId: this.conversationId\n };\n }\n\n _showSystemMessage(text) {\n this._showChatArea();\n this.chatView.addMessage({\n id: `sys-${++this._messageIdCounter}`,\n type: 'system_event',\n content: text,\n timestamp: new Date().toISOString()\n });\n }\n\n _handleAPIError(_err) {\n if (_err.status === 404) {\n this._showSystemMessage('Assistant is not enabled on this server.');\n } else if (_err.status === 503) {\n this._showSystemMessage('LLM API key not configured. Contact your administrator.');\n } else {\n this._showSystemMessage('Failed to send message. Please try again.');\n }\n this._setInputEnabled(true);\n }\n\n _updateConnectionStatus() {\n const dot = this.element?.querySelector('.status-dot');\n if (!dot) return;\n\n if (this.ws?.isConnected) {\n dot.className = 'status-dot connected';\n dot.title = 'Connected';\n if (!this._responseTimeout) {\n this._setInputEnabled(true);\n } else {\n this._setInputEnabled(false, 'Waiting for response…');\n }\n } else if (this.ws?.isReconnecting) {\n dot.className = 'status-dot reconnecting';\n dot.title = 'Reconnecting...';\n this._setInputEnabled(false, 'Reconnecting…');\n if (this._responseTimeout) {\n clearTimeout(this._responseTimeout);\n this._responseTimeout = null;\n }\n } else {\n dot.className = 'status-dot disconnected';\n dot.title = 'Disconnected';\n this._setInputEnabled(false, 'Disconnected — reconnecting…');\n if (this._responseTimeout) {\n clearTimeout(this._responseTimeout);\n this._responseTimeout = null;\n }\n }\n }\n\n _escapeHtml(text) {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n /**\n * Focus the input textarea — called externally when reopening the panel.\n */\n focusInput() {\n const textarea = this.element?.querySelector('[data-ref=\"input\"]');\n if (textarea) textarea.focus();\n }\n\n async onBeforeDestroy() {\n this._unsubscribeWS();\n if (this._responseTimeout) {\n clearTimeout(this._responseTimeout);\n this._responseTimeout = null;\n }\n }\n}\n\nexport default AssistantPanelView;\n"],"names":["AssistantPanelView","View","constructor","options","super","className","this","app","ws","conversationId","_assistantConversationId","_wsHandlers","_messageIdCounter","_hasMessages","_activePlans","_requestStartTime","_showingHistory","getTemplate","_escapeHtml","activeUser","get","onInit","conversations","AssistantConversationList","params","user","id","conversationListView","AssistantConversationListView","containerId","collection","addChild","chatView","ChatView","theme","messageViewClass","AssistantMessageView","currentUserId","showFileInput","showInput","adapter","_createAdapter","origAddMessage","addMessage","bind","msg","scroll","role","content","blocks","length","hideThinking","_setInputEnabled","on","data","_onConversationSelect","_toggleHistory","_onNewConversation","_onConversationDeleted","_subscribeWS","onAfterRender","textarea","element","querySelector","addEventListener","_autoResize","e","_handleKeydown","setTimeout","focus","_showChatArea","refresh","_updateConnectionStatus","_updateTitle","_setupResizeHandle","handle","STORAGE_KEY","saved","localStorage","getItem","w","parseInt","panelEl","document","getElementById","style","width","startX","startWidth","onMouseMove","delta","clientX","newWidth","Math","min","max","onMouseUp","removeEventListener","body","cursor","userSelect","setItem","preventDefault","offsetWidth","onActionToggleHistory","onActionNewConversation","onActionClosePanel","emit","onActionFullscreen","onActionPopOut","onActionUseSuggestion","_event","text","dataset","closest","value","_sendMessage","onActionSend","onActionStop","_showSystemMessage","show","history","chat","toggleBtn","classList","toggle","height","scrollHeight","key","shiftKey","trim","addNote","files","welcome","chatArea","add","remove","_showWelcome","enabled","reason","sendBtn","stopBtn","disabled","_setInputStatus","_responseTimeout","clearTimeout","_onResponseTimeout","message","el","innerHTML","_hasDismiss","title","textContent","fetch","async","conversation","AssistantConversation","graph","transformed","map","_transformMessage","filter","Boolean","AssistantView","_collapseMessages","_err","status","success","userMsg","author","name","timestamp","Date","toISOString","showThinking","now","isConnected","send","type","conversation_id","resp","rest","post","respData","response","_handleAPIError","thinking","_onThinking","_onText","tool_call","_onToolCall","_onResponse","error","_onError","plan","_onPlan","plan_update","_onPlanUpdate","envelope","_dispatchWSMessage","connected","disconnected","reconnecting","_unsubscribeWS","off","inner","_isMyConversation","String","_adoptConversationId","_resetResponseTimeout","message_id","tool_calls","created","tool","tool_calls_made","errorText","plan_id","step","steps","find","s","step_id","summary","msgView","messageViews","updateProgressStep","setActive","model","clearMessages","toolCalls","tc","input","textParts","join","INTERNAL_TOOLS","has","includes","parsed","_parseBlocks","display_name","_conversationId","dot","isReconnecting","div","createElement","focusInput","onBeforeDestroy"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./View-CPWwS19u.js"),t=require("./ChatView-kWguc444.js"),n=require("./ContextMenu-BeveGkJr.js"),i=require("./Modal-Bm1OQ8Ou.js"),a=require("./admin-models-CkHjtMHf.js"),s=require("./User-9qvKV7G6.js"),o=require("./admin-CHPo4iDn.js"),r=require("./Collection-C0pHSKDH.js");function l(e){return e?String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""):""}const c=/* @__PURE__ */new Set(["incident.RuleSet","incident.Incident","incident.Event","incident.Ticket","account.GeoLocatedIP"]),d={"incident.rule_approval":{dot:"accent",label:"Rule"},"incident.block_confirm":{dot:"red",label:"Block"},"incident.rule_update":{dot:"green",label:"Update"},"incident.escalate":{dot:"amber",label:"Escalate"}};class ActionCardView extends e.View{constructor(e={}){super({className:"action-card-view",...e}),this.action=e.action,this.noteId=e.noteId,this.ticketStatus=e.ticketStatus}get isResolved(){return!!this.action?.resolved}get isContext(){return"context"===this.action?.type}get isClosed(){return"closed"===this.ticketStatus||"resolved"===this.ticketStatus}get handlerConfig(){return d[this.action?.handler]||{dot:"accent",label:"Action"}}onBeforeRender(){const e=this.handlerConfig;this.dotClass=e.dot,this.isContext?this._buildContextTemplate():this.isResolved?this._buildResolvedTemplate():this._buildPendingTemplate()}onActionToggleCompact(){const e=this.element?.querySelector(".ac.resolved");e&&e.classList.toggle("compact")}_buildContextTemplate(){const e=(this.action.references||[]).filter(e=>c.has(e.model)).map(e=>{const t=l(e.label)||`${l(e.model.split(".").pop())} #${l(e.pk)}`;return`<span class="ac-ref" data-action="open-ref" data-model="${l(e.model)}" data-pk="${l(e.pk)}"><i class="bi bi-box-arrow-up-right"></i>${t}</span>`}).join("");this.template=`\n <div class="ac ac-context">\n <div class="ac-top">\n <span class="ac-dot context"></span>\n <span class="ac-label">Referenced models</span>\n </div>\n <div class="ac-detail">${e}</div>\n </div>\n `}_buildResolvedTemplate(){const e=this.action.resolution||"approved",t="approved"===e?"approved":"denied",n="approved"===e?"Approved":"Denied",i=this.action.context?.target;let a="";if(i&&c.has(i.model)){const e=l(this.action.context.label)||`${l(i.model.split(".").pop())} #${l(i.pk)}`;a=`<div class="ac-detail"><span class="ac-ref" data-action="open-ref" data-model="${l(i.model)}" data-pk="${l(i.pk)}"><i class="bi bi-box-arrow-up-right"></i>${e}</span></div>`}this.template=`\n <div class="ac resolved compact">\n <div class="ac-top" data-action="toggle-compact" title="Click to toggle">\n <span class="ac-dot ${this.dotClass}"></span>\n <span class="ac-label">${l(this.action.label)||"Action"}</span>\n <span class="ac-badge ${t}">${n}</span>\n <i class="bi bi-chevron-down ac-chevron"></i>\n </div>\n ${a}\n </div>\n `}_buildPendingTemplate(){const e=this.action.context?.target;let t="";if(e&&c.has(e.model)){const n=l(this.action.context.label)||`${l(e.model.split(".").pop())} #${l(e.pk)}`;t=`<br><span class="ac-ref" data-action="open-ref" data-model="${l(e.model)}" data-pk="${l(e.pk)}"><i class="bi bi-box-arrow-up-right"></i>${n}</span>`}const n=l(this.action.context?.detail),i=this.isClosed?" disabled":"";this.template=`\n <div class="ac">\n <div class="ac-top">\n <span class="ac-dot ${this.dotClass}"></span>\n <span class="ac-label">${l(this.action.label)||"Action"}</span>\n </div>\n <div class="ac-detail">${n}${t}</div>\n <div class="ac-foot">\n <button class="btn-approve" data-action="approve"${i}>Approve</button>\n <button class="btn-deny" data-action="deny"${i}>Deny</button>\n </div>\n </div>\n `}async onActionOpenRef(e,t){const n=t.dataset.model,a=t.dataset.pk;if(!c.has(n)||!/^\d+$/.test(a))return;const s=this.getApp(),o=s?.getModelByRef(n);o?.VIEW_CLASS&&i.Modal.showModel(new o({id:a}))}onActionApprove(){this.emit("action:respond",{noteId:this.noteId,action:"approve",handler:this.action.handler,context:this.action.context})}onActionDeny(){this.emit("action:respond",{noteId:this.noteId,action:"deny",handler:this.action.handler,context:this.action.context})}}const p=["new","open","in_progress","pending","resolved","qa","closed","ignored"],h={new:"pill-new",open:"pill-open",in_progress:"pill-prog",pending:"pill-prog",resolved:"pill-resolved",qa:"pill-open",closed:"pill-closed",ignored:"pill-closed"},g=[{value:10,label:"P10 — Critical"},{value:9,label:"P9 — Severe"},{value:8,label:"P8 — High"},{value:7,label:"P7 — Elevated"},{value:5,label:"P5 — Normal"},{value:3,label:"P3 — Low"},{value:1,label:"P1 — Info"}];class TicketPanelView extends e.View{constructor(e={}){super({className:"ticket-panel-view",...e}),this.model=e.model||new a.Ticket(e.data||{})}onBeforeRender(){const e=this.model.get("status")||"new";this.statusPill=h[e]||"pill-closed",this.statusLabel=e.replace(/_/g," "),this.priorityLabel=`P${this.model.get("priority")||5}`,this.assigneeName=this.model.get("assignee.display_name")||this.model.get("assignee")||"Unassigned",this.categoryLabel=this.model.get("category")||"ticket",this.groupName=this.model.get("group.name")||this.model.get("group")||"None",this.hasDescription=!!this.model.get("description"),this.hasIncident=!(!this.model.get("incident")||"object"!=typeof this.model.get("incident")||!this.model.get("incident").id),this.priorityColor=(this.model.get("priority")||5)>=7?"var(--bs-danger)":"var(--bs-secondary-color)",this.template=`\n <style>\n .ticket-panel-view { height: 100%; display: flex; flex-direction: column; }\n .tp-header { padding: 10px 16px 6px; border-bottom: 1px solid var(--bs-border-color-translucent); flex-shrink: 0; }\n .tp-title-row { display: flex; align-items: flex-start; gap: 6px; }\n .tp-title { font-size: 0.88rem; font-weight: 600; color: var(--bs-emphasis-color); line-height: 1.3; flex: 1; min-width: 0; cursor: ${this.hasDescription?"pointer":"default"}; transition: color 0.12s; }\n ${this.hasDescription?".tp-title:hover { color: var(--bs-primary); }":""}\n .tp-title i { font-size: 0.6rem; vertical-align: middle; margin-left: 3px; opacity: 0; transition: opacity 0.12s; }\n .tp-title:hover i { opacity: 0.6; }\n .tp-btns { display: flex; gap: 2px; align-items: center; flex-shrink: 0; }\n .tp-btn { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border: none; background: none; color: var(--bs-secondary-color); border-radius: 6px; cursor: pointer; font-size: 0.85rem; transition: all 0.12s; }\n .tp-btn:hover { background: var(--bs-tertiary-bg); color: var(--bs-body-color); }\n .tp-sub { display: flex; align-items: center; gap: 6px; margin-top: 3px; }\n .tp-id { font-family: var(--bs-font-monospace); font-size: 0.7rem; color: var(--bs-secondary-color); }\n .tp-pill { display: inline-block; padding: 1px 7px; border-radius: 10px; font-size: 0.66rem; font-weight: 500; cursor: pointer; transition: filter 0.12s; }\n .tp-pill:hover { filter: brightness(0.9); }\n .tp-pill-new { background: rgba(var(--bs-info-rgb), 0.1); color: var(--bs-info); }\n .tp-pill-open { background: rgba(var(--bs-success-rgb), 0.1); color: var(--bs-success); }\n .tp-pill-prog { background: rgba(var(--bs-warning-rgb), 0.1); color: var(--bs-warning); }\n .tp-pill-closed { background: var(--bs-secondary-bg); color: var(--bs-secondary-color); }\n .tp-pill-resolved { background: rgba(var(--bs-success-rgb), 0.1); color: var(--bs-success); }\n .tp-time { font-size: 0.66rem; color: var(--bs-secondary-color); }\n .tp-desc-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 0.7rem; color: var(--bs-primary); cursor: pointer; padding: 2px 8px; border-radius: 5px; background: rgba(var(--bs-primary-rgb), 0.08); transition: all 0.12s; }\n .tp-desc-chip:hover { background: rgba(var(--bs-primary-rgb), 0.16); }\n .tp-desc-chip i { font-size: 0.7rem; }\n .tp-meta { display: flex; align-items: center; gap: 3px; margin-top: 5px; }\n .tp-fields { display: inline-flex; align-items: center; gap: 2px; flex-wrap: wrap; }\n .tp-field { display: inline-flex; align-items: center; gap: 4px; font-size: 0.72rem; color: var(--bs-secondary-color); padding: 2px 7px; border-radius: 5px; cursor: pointer; transition: all 0.12s; border: 1px solid transparent; }\n .tp-field:hover { background: var(--bs-tertiary-bg); border-color: var(--bs-border-color); color: var(--bs-body-color); }\n .tp-field i { font-size: 0.68rem; }\n .tp-field .caret { font-size: 0.55rem; opacity: 0; transition: opacity 0.12s; margin-left: -1px; }\n .tp-field:hover .caret { opacity: 0.6; }\n .tp-sep { color: var(--bs-secondary-color); font-size: 0.6rem; margin: 0 1px; user-select: none; }\n\n .tp-linked { display: flex; align-items: center; gap: 6px; padding: 7px 16px; border-bottom: 1px solid var(--bs-border-color-translucent); font-size: 0.75rem; color: var(--bs-secondary-color); flex-shrink: 0; }\n .tp-linked i { color: var(--bs-warning); font-size: 0.72rem; }\n .tp-linked a { color: var(--bs-primary); text-decoration: none; font-weight: 500; }\n .tp-linked a:hover { text-decoration: underline; }\n .tp-linked .lpill { font-size: 0.62rem; padding: 0 5px; border-radius: 3px; background: rgba(var(--bs-warning-rgb), 0.1); color: var(--bs-warning); font-weight: 500; }\n\n .tp-conv { flex: 1; overflow-y: auto; min-height: 0; }\n .tp-conv .chat-container { border: none; border-radius: 0; }\n .tp-conv .chat-messages { padding: 6px 0; }\n .tp-conv .chat-input-wrapper { display: none; }\n .tp-conv .message-item { position: relative; }\n .tp-conv .tp-edit-btn { position: absolute; top: 8px; right: 8px; width: 24px; height: 24px; display: none; align-items: center; justify-content: center; border: none; background: var(--bs-tertiary-bg); color: var(--bs-secondary-color); border-radius: 5px; cursor: pointer; font-size: 0.72rem; transition: all 0.12s; }\n .tp-conv .tp-edit-btn:hover { background: var(--bs-secondary-bg); color: var(--bs-body-color); }\n .tp-conv .message-item:hover .tp-edit-btn { display: flex; }\n .tp-conv .message-text { white-space: normal; }\n .tp-conv .message-text p { margin-bottom: 6px; }\n .tp-conv .message-text p:last-child { margin-bottom: 0; }\n .tp-conv .message-text h1,\n .tp-conv .message-text h2,\n .tp-conv .message-text h3,\n .tp-conv .message-text h4,\n .tp-conv .message-text h5,\n .tp-conv .message-text h6 { font-weight: 600; margin-top: 10px; margin-bottom: 4px; line-height: 1.3; }\n .tp-conv .message-text h1 { font-size: 1.05rem; }\n .tp-conv .message-text h2 { font-size: 1rem; }\n .tp-conv .message-text h3 { font-size: 0.95rem; }\n .tp-conv .message-text h4,\n .tp-conv .message-text h5,\n .tp-conv .message-text h6 { font-size: 0.88rem; }\n .tp-conv .message-text h1:first-child,\n .tp-conv .message-text h2:first-child,\n .tp-conv .message-text h3:first-child { margin-top: 0; }\n .tp-conv .message-text hr { margin: 4px 0; opacity: 0.15; }\n .tp-conv .message-text ul,\n .tp-conv .message-text ol { padding-left: 20px; margin-top: 2px; margin-bottom: 6px; }\n .tp-conv .message-text li { margin-bottom: 2px; }\n .tp-conv .message-text pre { background: var(--bs-tertiary-bg); border-radius: 6px; padding: 10px 14px; margin: 8px 0; font-size: 0.8rem; overflow-x: auto; }\n .tp-conv .message-text code { font-size: 0.85em; padding: 1px 5px; background: var(--bs-tertiary-bg); border-radius: 4px; }\n .tp-conv .message-text pre code { padding: 0; background: none; }\n .tp-conv .message-text table { width: 100%; margin: 8px 0; border-collapse: collapse; font-size: 0.82rem; }\n .tp-conv .message-text th,\n .tp-conv .message-text td { padding: 5px 8px; border: 1px solid var(--bs-border-color); text-align: left; }\n .tp-conv .message-text th { background: var(--bs-tertiary-bg); font-weight: 600; }\n .tp-conv .message-text blockquote { margin: 6px 0; padding: 4px 12px; border-left: 3px solid var(--bs-border-color); color: var(--bs-secondary-color); }\n\n .tp-action-area { padding: 0; }\n\n .tp-input { border-top: 1px solid var(--bs-border-color-translucent); padding: 10px 16px; flex-shrink: 0; }\n .tp-input-wrap { display: flex; align-items: flex-end; gap: 8px; }\n .tp-input textarea { flex: 1; font-size: 0.8rem; border: 1px solid var(--bs-border-color); border-radius: 8px; padding: 7px 10px; resize: none; background: var(--bs-body-bg); color: var(--bs-body-color); outline: none; transition: border-color 0.15s; font-family: inherit; max-height: 160px; overflow-y: auto; }\n .tp-input textarea:focus { border-color: var(--bs-primary); }\n .tp-input textarea::placeholder { color: var(--bs-secondary-color); }\n .tp-send-btn { width: 32px; height: 32px; border-radius: 8px; border: none; background: var(--bs-primary); color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; font-size: 0.85rem; transition: filter 0.12s; }\n .tp-send-btn:hover { filter: brightness(1.1); }\n .tp-input-hint { display: flex; align-items: center; gap: 4px; margin-top: 4px; font-size: 0.7rem; color: var(--bs-secondary-color); }\n .tp-input-hint i { font-size: 0.75rem; }\n .tp-input.tp-dragover { background: rgba(var(--bs-primary-rgb), 0.04); }\n .tp-input.tp-dragover textarea { border-color: var(--bs-primary); border-style: dashed; }\n .tp-attachments { display: flex; flex-wrap: wrap; gap: 4px; padding: 4px 0; }\n .tp-attach-chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; background: var(--bs-tertiary-bg); border: 1px solid var(--bs-border-color); border-radius: 6px; font-size: 0.72rem; color: var(--bs-body-color); }\n .tp-attach-chip i { font-size: 0.68rem; }\n .tp-attach-chip .remove { cursor: pointer; color: var(--bs-secondary-color); margin-left: 2px; }\n .tp-attach-chip .remove:hover { color: var(--bs-danger); }\n .tp-attach-chip.uploading { opacity: 0.6; }\n .tp-conv .message-content.tp-collapsed { max-height: var(--tp-collapse-h, 52px); overflow: hidden; position: relative; -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%); mask-image: linear-gradient(to bottom, black 60%, transparent 100%); }\n .tp-conv .message-item:has(.tp-show-more) { flex-wrap: wrap; }\n .tp-show-more { background: none; border: none; padding: 2px 0; margin: -2px 0 0 48px; font-size: 0.7rem; color: var(--bs-secondary-color); cursor: pointer; text-align: left; width: calc(100% - 48px); }\n .tp-show-more:hover { color: var(--bs-body-color); }\n </style>\n\n <div class="tp-header">\n <div class="tp-title-row">\n <div class="tp-title" ${this.hasDescription?'data-action="show-description"':""} ${this.hasDescription?'title="View full description"':""}>\n {{model.title}}${this.hasDescription?' <i class="bi bi-arrow-up-right-square"></i>':""}\n </div>\n <div class="tp-btns">\n <div data-container="panel-menu"></div>\n </div>\n </div>\n <div class="tp-sub">\n <span class="tp-id">#{{model.id}}</span>\n <span class="tp-pill tp-${this.statusPill}" data-action="change-status" title="Change status">{{statusLabel}} <i class="bi bi-chevron-down" style="font-size:0.5rem;"></i></span>\n <span class="tp-time"><i class="bi bi-clock"></i> {{model.created|relative}}</span>\n <span class="tp-desc-chip" data-action="show-description" title="${this.hasDescription?"View / edit description":"Add description"}"><i class="bi bi-file-text"></i> ${this.hasDescription?"Description":"Add description"}</span>\n </div>\n <div class="tp-meta">\n <div class="tp-fields">\n <span class="tp-field" data-action="change-priority" title="Change priority">\n <i class="bi bi-flag-fill" style="color:${this.priorityColor};"></i>{{priorityLabel}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n <span class="tp-sep">·</span>\n <span class="tp-field" data-action="change-assignee" title="Assign">\n <i class="bi bi-person"></i>{{assigneeName}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n <span class="tp-sep">·</span>\n <span class="tp-field" data-action="change-category" title="Change category">\n <i class="bi bi-tag"></i>{{categoryLabel}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n <span class="tp-sep">·</span>\n <span class="tp-field" data-action="change-group" title="Change group">\n <i class="bi bi-people"></i>{{groupName}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n </div>\n </div>\n </div>\n\n {{#hasIncident|bool}}\n <div class="tp-linked">\n <i class="bi bi-exclamation-triangle-fill"></i>\n <a href="#" data-action="view-incident">Incident #{{model.incident.id}}</a>\n {{#model.incident.status}}<span class="lpill">{{model.incident.status}}</span>{{/model.incident.status}}\n {{#model.incident.event_count}}<span>· {{model.incident.event_count}} events</span>{{/model.incident.event_count}}\n </div>\n {{/hasIncident|bool}}\n\n <div class="tp-conv">\n <div data-container="chat-area"></div>\n </div>\n\n <div class="tp-input" data-ref="drop-zone">\n <div class="tp-attachments" data-ref="attachments"></div>\n <div class="tp-input-wrap">\n <textarea rows="2" placeholder="Add a note..." data-ref="note-textarea"></textarea>\n <button class="tp-send-btn" data-action="send-note" title="Send"><i class="bi bi-arrow-up"></i></button>\n </div>\n <div class="tp-input-hint">\n <i class="bi bi-paperclip"></i> Drag & drop files to attach\n </div>\n </div>\n `}async onInit(){this.adapter=new o.TicketNoteAdapter(this.model.get("id")),this.chatView=new t.ChatView({containerId:"chat-area",adapter:this.adapter,theme:"compact",currentUserId:this._getCurrentUserId(),showInput:!1}),this.addChild(this.chatView);const e=new n.ContextMenu({containerId:"panel-menu",className:"context-menu-view header-menu-absolute",context:this.model,config:{icon:"bi-three-dots",btnClass:"tp-btn",items:[{label:"Ask AI",action:"ask-ai",icon:"bi-robot"},{type:"divider"},{label:"Edit Ticket",action:"edit-ticket",icon:"bi-pencil"},{label:"Refresh Notes",action:"refresh-notes",icon:"bi-arrow-clockwise"},{type:"divider"},{label:"Close Window",action:"close",icon:"bi-x-lg"}]}});this.addChild(e)}async onAfterRender(){this._setupTextarea(),this._setupDragDrop(),await new Promise(e=>setTimeout(e,0)),await this._loadActionCards(),this._addEditButtons(),this._setupCollapsible()}async _loadActionCards(){this._cleanupActionCards();const e=this.chatView.messages||[];if(e.length)for(const t of e){if(!t.action||"object"!=typeof t.action)continue;const e=this.chatView.messageViews.get(t.id);if(!e?.element)continue;const n=new ActionCardView({action:t.action,noteId:t.id,ticketStatus:this.model.get("status")});"context"===t.action.type||t.action.resolved||n.on("action:respond",e=>this._handleActionResponse(e)),this.addChild(n),await n.render(),e.element.after(n.element)}}_cleanupActionCards(){for(const e in this.children){const t=this.children[e];t instanceof ActionCardView&&this.removeChild(t)}}async _handleActionResponse(e){const t={action:{handler:e.handler,context:e.context}},n=this.getApp();n?.showLoading();try{await this.adapter.addActionResponse(t,e.action),await this.model.fetch(),await this.chatView.refresh(),this.render()}finally{n?.hideLoading()}}_getCurrentUserId(){const e=this.getApp();return e?.activeUser?.id||e?.getActiveUser?.()?.id||null}onActionClose(){this.emit("panel:close")}async onActionSendNote(){const e=this.element?.querySelector('[data-ref="note-textarea"]'),t=e?.value?.trim(),n=this._stagedFiles||[];(t||n.length)&&(e.value="",e.style.height="",this._stagedFiles=[],this._renderAttachments(),await this.adapter.addNote({text:t||"",files:n}),await this.chatView.refresh(),await this._afterChatRefresh())}_setupTextarea(){const e=this.element?.querySelector('[data-ref="note-textarea"]');if(!e)return;const t=(t,n=t.length)=>{const i=e.selectionStart,a=e.selectionEnd;e.setRangeText(t,i,a,"end"),e.selectionStart=e.selectionEnd=i+n,e.dispatchEvent(new Event("input")),e.scrollTop=e.scrollHeight},n=t=>{const n=e.selectionStart,i=e.selectionEnd,a=e.value.substring(n,i);a.startsWith(t)&&a.endsWith(t)?(e.setRangeText(a.slice(t.length,-t.length),n,i,"end"),e.selectionStart=n,e.selectionEnd=i-2*t.length):(e.setRangeText(t+a+t,n,i,"end"),e.selectionStart=n+t.length,e.selectionEnd=i+t.length)},i=t=>{const n=e.value.substring(0,t).lastIndexOf("\n")+1;return{start:n,text:e.value.substring(n,t)}},a=t=>(e.value.substring(0,t).match(/^```/gm)||[]).length%2==1;e.addEventListener("keydown",s=>{const o=s.ctrlKey||s.metaKey;if("Enter"===s.key&&!s.shiftKey&&!o)return s.preventDefault(),void this.onActionSendNote();if("Enter"===s.key&&s.shiftKey){const{start:n,text:a}=i(e.selectionStart),o=a.match(/^(\s*)([-*]|\d+\.)\s/);if(o){s.preventDefault();const i=o[1],r=o[2];if(a.trim()===r)e.setRangeText("",n,e.selectionStart,"end");else{const e=/^\d+\./.test(r)?`${parseInt(r)+1}.`:r;t(`\n${i}${e} `)}return}}if("`"===s.key&&!o){const n=e.selectionStart;if(e.value.substring(0,n).endsWith("``")&&!a(n-2))return s.preventDefault(),void t("`\n\n```",2)}if("Tab"!==s.key||!a(e.selectionStart))return o&&"b"===s.key?(s.preventDefault(),void n("**")):o&&"i"===s.key?(s.preventDefault(),void n("*")):void 0;if(s.preventDefault(),s.shiftKey){const{start:t,text:n}=i(e.selectionStart),a=n.replace(/^ {1,2}/,"");e.setRangeText(a,t,t+n.length,"end"),e.selectionStart=e.selectionEnd=t+a.length}else t(" ")});const s={"(":")","[":"]",'"':'"'};e.addEventListener("keydown",n=>{if(n.ctrlKey||n.metaKey||n.altKey)return;const i=s[n.key];if(!i)return;const a=e.selectionStart,o=e.selectionEnd;if(a!==o){n.preventDefault();const t=e.value.substring(a,o);e.setRangeText(n.key+t+i,a,o,"end"),e.selectionStart=a+1,e.selectionEnd=o+1}else n.preventDefault(),t(n.key+i,1)}),e.addEventListener("input",()=>{e.style.height="",e.style.height=Math.min(e.scrollHeight,160)+"px"})}_setupDragDrop(){const e=this.element?.querySelector('[data-ref="drop-zone"]');if(!e)return;this._stagedFiles=this._stagedFiles||[];let t=0;e.addEventListener("dragenter",n=>{n.preventDefault(),t++,e.classList.add("tp-dragover")}),e.addEventListener("dragleave",()=>{t--,t<=0&&(t=0,e.classList.remove("tp-dragover"))}),e.addEventListener("dragover",e=>e.preventDefault()),e.addEventListener("drop",async n=>{n.preventDefault(),t=0,e.classList.remove("tp-dragover");const i=Array.from(n.dataTransfer?.files||[]);if(!i.length)return;const{File:a}=await Promise.resolve().then(()=>require("./Modal-Bm1OQ8Ou.js")).then(e=>e.Files);for(const e of i){const t=Date.now()+Math.random();this._addAttachChip(t,e.name,!0);try{const n=new a;await n.upload({file:e,showToast:!1}),this._stagedFiles.push(n),this._updateAttachChip(t,n.get("name")||e.name,n)}catch(s){console.error("File upload failed:",s),this._removeAttachChip(t),this.getApp()?.toast?.error?.("Upload failed: "+e.name)}}})}_addAttachChip(e,t,n){const i=this.element?.querySelector('[data-ref="attachments"]');if(!i)return;const a=document.createElement("span");a.className="tp-attach-chip"+(n?" uploading":""),a.dataset.chipId=e,a.innerHTML=`<i class="bi bi-paperclip"></i>${this._escapeHtml(t)}`+(n?"":'<span class="remove" data-remove="1"><i class="bi bi-x"></i></span>'),n||a.querySelector(".remove").addEventListener("click",()=>{this._removeAttachChip(e)}),i.appendChild(a)}_updateAttachChip(e,t,n){const i=this.element?.querySelector(`[data-chip-id="${e}"]`);i&&(i.classList.remove("uploading"),i.innerHTML=`<i class="bi bi-paperclip"></i>${this._escapeHtml(t)}<span class="remove" data-remove="1"><i class="bi bi-x"></i></span>`,i.querySelector(".remove").addEventListener("click",()=>{this._stagedFiles=(this._stagedFiles||[]).filter(e=>e!==n),i.remove()}))}_removeAttachChip(e){const t=this.element?.querySelector(`[data-chip-id="${e}"]`);t&&t.remove()}_renderAttachments(){const e=this.element?.querySelector('[data-ref="attachments"]');e&&(e.innerHTML="")}_escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}_addEditButtons(){const e=this._getCurrentUserId();if(!e||!this.chatView?.messageViews)return;const t=new Map((this.chatView.messages||[]).map(e=>[e.id,e]));this.chatView.messageViews.forEach((n,i)=>{const a=t.get(i),s=n?.element?.querySelector(".message-item");if(!s)return;if(s.querySelectorAll(".tp-edit-btn").forEach(e=>e.remove()),!a||a.author?.id!==e)return;const o=document.createElement("button");o.className="tp-edit-btn",o.title="Edit note",o.innerHTML='<i class="bi bi-pencil"></i>',o.addEventListener("click",e=>{e.stopPropagation(),this._editNote(a)}),s.appendChild(o)})}_setupCollapsible(){const e=this.element?.querySelector('[data-container="chat-area"]');e&&(e.querySelectorAll(".tp-show-more").forEach(e=>e.remove()),e.querySelectorAll(".tp-collapsed").forEach(e=>e.classList.remove("tp-collapsed")),setTimeout(()=>{e.querySelectorAll(".message-content").forEach(e=>{if(e.scrollHeight<=52)return;e.classList.add("tp-collapsed"),e.style.setProperty("--tp-collapse-h","52px");const t=document.createElement("button");t.className="tp-show-more",t.textContent="Show more",t.addEventListener("click",()=>{const n=e.classList.toggle("tp-collapsed");t.textContent=n?"Show more":"Show less"}),e.after(t)})},150))}async _editNote(e){const t=Object.keys(e._metadata||{}).length?JSON.stringify(e._metadata,null,2):"",n=await i.Modal.form({title:"Edit Note",icon:"bi-pencil",size:"lg",fields:[{type:"tabset",tabs:[{label:"Note",fields:[{name:"note",type:"textarea",label:"Note",required:!0,cols:12,rows:8,value:e._rawContent||e.content}]},{label:"Metadata",fields:[{name:"metadata_json",type:"json",label:"Metadata (JSON)",cols:12,rows:10,value:t,help:'Action metadata — e.g. { "action": { "handler": "incident.rule_approval", "label": "...", "context": { ... } } }'}]}]}]});if(!n)return;const{TicketNote:a}=await Promise.resolve().then(()=>require("./admin-models-CkHjtMHf.js")).then(e=>e.Tickets),s=new a({id:e.id}),o={note:n.note};n.metadata_json&&(o.metadata="string"==typeof n.metadata_json?JSON.parse(n.metadata_json):n.metadata_json),await s.save(o),await this.chatView.refresh(),await this._afterChatRefresh()}async _saveAndSync(e){await this.model.save(e),await this.model.fetch(),this.chatView&&(await this.chatView.refresh(),await this._afterChatRefresh())}async onActionChangeStatus(e){const t=p.map(e=>({label:e.replace(/_/g," "),value:e,active:e===this.model.get("status")})),n=await this._showInlineSelect(t,e);n&&(await this._saveAndSync({status:n}),this.render())}async onActionChangePriority(e){const t=g.map(e=>({label:e.label,value:e.value,active:e.value===this.model.get("priority")})),n=await this._showInlineSelect(t,e);n&&(await this._saveAndSync({priority:parseInt(n)}),this.render())}async onActionChangeAssignee(){const e=await i.Modal.form({title:"Assign User",icon:"bi-person-plus",size:"sm",fields:[{name:"assignee",type:"collection",label:"User",Collection:s.UserList,labelField:"display_name",valueField:"id",required:!0,cols:12,value:this.model.get("assignee")}]});e&&(await this._saveAndSync({assignee:e.assignee}),this.render())}async onActionChangeCategory(e){const t=Object.entries(a.TicketCategories).map(([e,t])=>({label:t,value:e,active:e===this.model.get("category")})),n=await this._showInlineSelect(t,e);n&&(await this._saveAndSync({category:n}),this.render())}async onActionChangeGroup(){const e=await i.Modal.form({title:"Change Group",icon:"bi-people",size:"sm",fields:[{name:"group",type:"collection",label:"Group",Collection:s.GroupList,labelField:"name",valueField:"id",required:!1,cols:12,value:this.model.get("group")}]});e&&(await this._saveAndSync({group:e.group}),this.render())}async onActionShowDescription(){const e=this.model.get("description")||"";if(!e)return this._editDescription();let t=!1,n="";try{const i=await r.rest.post("/api/docit/render",{markdown:e});n=i?.data?.data?.html||i?.data?.html||"",t=!!n}catch(a){}if(!t){const t=document.createElement("div");t.textContent=e,n=`<pre style="white-space:pre-wrap;">${t.innerHTML}</pre>`}"edit"===await i.Modal.dialog({title:`Ticket #${this.model.get("id")} — Description`,body:`<div style="font-size:0.85rem; line-height:1.65;">${n}</div>`,size:"lg",buttons:[{text:"Edit",class:"btn-primary",value:"edit"},{text:"Close",class:"btn-secondary",value:"close"}]})&&await this._editDescription()}async _editDescription(){const e=this.model.get("id"),t=`\n <div class="tp-desc-edit">\n <textarea data-ref="desc-textarea" rows="16" placeholder="Description (markdown supported)..."\n style="width:100%; font-family: var(--bs-font-monospace); font-size: 0.85rem; padding: 10px 12px; border: 1px solid var(--bs-border-color); border-radius: 8px; background: var(--bs-body-bg); color: var(--bs-body-color); resize: vertical; outline: none;">${(this.model.get("description")||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}</textarea>\n <div class="text-muted small mt-1">\n Markdown supported. Cmd/Ctrl+B = bold · Cmd/Ctrl+I = italic · Shift+Enter continues lists · \`\`\` opens a code block\n </div>\n </div>\n `,n=(async()=>{for(let e=0;e<20;e++){await new Promise(e=>setTimeout(e,50));const e=document.querySelector('.modal.show [data-ref="desc-textarea"]');if(e)return this._wireMarkdownTextarea(e),e}return null})(),a=await i.Modal.dialog({title:`Ticket #${e} — Edit Description`,body:t,size:"lg",buttons:[{text:"Cancel",class:"btn-secondary",value:null},{text:"Save",class:"btn-primary",handler:()=>{const e=document.querySelector('.modal.show [data-ref="desc-textarea"]');return e?e.value:null}}]});await n,null!=a&&(await this._saveAndSync({description:a}),this.render())}_wireMarkdownTextarea(e){const t=(t,n=t.length)=>{const i=e.selectionStart,a=e.selectionEnd;e.setRangeText(t,i,a,"end"),e.selectionStart=e.selectionEnd=i+n,e.dispatchEvent(new Event("input"))},n=t=>{const n=e.selectionStart,i=e.selectionEnd,a=e.value.substring(n,i);a.startsWith(t)&&a.endsWith(t)?(e.setRangeText(a.slice(t.length,-t.length),n,i,"end"),e.selectionStart=n,e.selectionEnd=i-2*t.length):(e.setRangeText(t+a+t,n,i,"end"),e.selectionStart=n+t.length,e.selectionEnd=i+t.length)},i=t=>{const n=e.value.substring(0,t).lastIndexOf("\n")+1;return{start:n,text:e.value.substring(n,t)}},a=t=>(e.value.substring(0,t).match(/^```/gm)||[]).length%2==1;e.addEventListener("keydown",s=>{const o=s.ctrlKey||s.metaKey;if("Enter"===s.key&&s.shiftKey){const{start:n,text:a}=i(e.selectionStart),o=a.match(/^(\s*)([-*]|\d+\.)\s/);if(o){s.preventDefault();const i=o[1],r=o[2];if(a.trim()===r)e.setRangeText("",n,e.selectionStart,"end");else{const e=/^\d+\./.test(r)?`${parseInt(r)+1}.`:r;t(`\n${i}${e} `)}return}}if("`"===s.key&&!o){const n=e.selectionStart;if(e.value.substring(0,n).endsWith("``")&&!a(n-2))return s.preventDefault(),void t("`\n\n```",2)}if("Tab"!==s.key||!a(e.selectionStart))return o&&"b"===s.key?(s.preventDefault(),void n("**")):o&&"i"===s.key?(s.preventDefault(),void n("*")):void 0;if(s.preventDefault(),s.shiftKey){const{start:t,text:n}=i(e.selectionStart),a=n.replace(/^ {1,2}/,"");e.setRangeText(a,t,t+n.length,"end"),e.selectionStart=e.selectionEnd=t+a.length}else t(" ")});const s={"(":")","[":"]",'"':'"'};e.addEventListener("keydown",n=>{if(n.ctrlKey||n.metaKey||n.altKey)return;const i=s[n.key];if(!i)return;const a=e.selectionStart,o=e.selectionEnd;if(a!==o){n.preventDefault();const t=e.value.substring(a,o);e.setRangeText(n.key+t+i,a,o,"end"),e.selectionStart=a+1,e.selectionEnd=o+1}else n.preventDefault(),t(n.key+i,1)})}async onActionViewIncident(){const e=this.model.get("incident");e?.id&&i.Modal.showModel(new a.Incident({id:e.id}))}async onActionEditTicket(){await i.Modal.modelForm({title:`Edit Ticket #${this.model.get("id")}`,model:this.model,size:"lg",fields:a.TicketForms.edit.fields})&&this.render()}async onActionRefreshNotes(){await this.chatView.refresh(),await this._afterChatRefresh(),this.getApp()?.toast?.success("Notes refreshed")}async onActionAskAi(){await o.openAssistantChat(this,"incident.Ticket")}async _showInlineSelect(e,t){return new Promise(i=>{let a=!1;const s=new n.ContextMenu({config:{items:e.map((e,t)=>({label:e.label,action:`pick-${t}`,class:e.active?"fw-bold":"",handler:()=>{a=!0,this.removeChild(s),i(e.value)}}))}}),o=s.closeDropdown.bind(s);s.closeDropdown=()=>{o(),a||(this.removeChild(s),i(null))},this.addChild(s),s.openAt(t.clientX,t.clientY)})}async _afterChatRefresh(){await new Promise(e=>setTimeout(e,0)),await this._loadActionCards(),this._addEditButtons(),this._setupCollapsible()}async setTicket(e){this.model=e,this.adapter=new o.TicketNoteAdapter(e.get("id")),this.chatView.adapter=this.adapter,this.chatView.clearMessages(),await this.render(),await this.chatView.refresh(),await this._afterChatRefresh()}}exports.default=TicketPanelView;
|
|
2
|
-
//# sourceMappingURL=TicketPanelView-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./View-CPWwS19u.js"),t=require("./ChatView-kWguc444.js"),n=require("./ContextMenu-BeveGkJr.js"),i=require("./Modal-Bm1OQ8Ou.js"),a=require("./admin-models-CkHjtMHf.js"),s=require("./User-9qvKV7G6.js"),o=require("./admin-CmyeTstQ.js"),r=require("./Collection-C0pHSKDH.js");function l(e){return e?String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""):""}const c=/* @__PURE__ */new Set(["incident.RuleSet","incident.Incident","incident.Event","incident.Ticket","account.GeoLocatedIP"]),d={"incident.rule_approval":{dot:"accent",label:"Rule"},"incident.block_confirm":{dot:"red",label:"Block"},"incident.rule_update":{dot:"green",label:"Update"},"incident.escalate":{dot:"amber",label:"Escalate"}};class ActionCardView extends e.View{constructor(e={}){super({className:"action-card-view",...e}),this.action=e.action,this.noteId=e.noteId,this.ticketStatus=e.ticketStatus}get isResolved(){return!!this.action?.resolved}get isContext(){return"context"===this.action?.type}get isClosed(){return"closed"===this.ticketStatus||"resolved"===this.ticketStatus}get handlerConfig(){return d[this.action?.handler]||{dot:"accent",label:"Action"}}onBeforeRender(){const e=this.handlerConfig;this.dotClass=e.dot,this.isContext?this._buildContextTemplate():this.isResolved?this._buildResolvedTemplate():this._buildPendingTemplate()}onActionToggleCompact(){const e=this.element?.querySelector(".ac.resolved");e&&e.classList.toggle("compact")}_buildContextTemplate(){const e=(this.action.references||[]).filter(e=>c.has(e.model)).map(e=>{const t=l(e.label)||`${l(e.model.split(".").pop())} #${l(e.pk)}`;return`<span class="ac-ref" data-action="open-ref" data-model="${l(e.model)}" data-pk="${l(e.pk)}"><i class="bi bi-box-arrow-up-right"></i>${t}</span>`}).join("");this.template=`\n <div class="ac ac-context">\n <div class="ac-top">\n <span class="ac-dot context"></span>\n <span class="ac-label">Referenced models</span>\n </div>\n <div class="ac-detail">${e}</div>\n </div>\n `}_buildResolvedTemplate(){const e=this.action.resolution||"approved",t="approved"===e?"approved":"denied",n="approved"===e?"Approved":"Denied",i=this.action.context?.target;let a="";if(i&&c.has(i.model)){const e=l(this.action.context.label)||`${l(i.model.split(".").pop())} #${l(i.pk)}`;a=`<div class="ac-detail"><span class="ac-ref" data-action="open-ref" data-model="${l(i.model)}" data-pk="${l(i.pk)}"><i class="bi bi-box-arrow-up-right"></i>${e}</span></div>`}this.template=`\n <div class="ac resolved compact">\n <div class="ac-top" data-action="toggle-compact" title="Click to toggle">\n <span class="ac-dot ${this.dotClass}"></span>\n <span class="ac-label">${l(this.action.label)||"Action"}</span>\n <span class="ac-badge ${t}">${n}</span>\n <i class="bi bi-chevron-down ac-chevron"></i>\n </div>\n ${a}\n </div>\n `}_buildPendingTemplate(){const e=this.action.context?.target;let t="";if(e&&c.has(e.model)){const n=l(this.action.context.label)||`${l(e.model.split(".").pop())} #${l(e.pk)}`;t=`<br><span class="ac-ref" data-action="open-ref" data-model="${l(e.model)}" data-pk="${l(e.pk)}"><i class="bi bi-box-arrow-up-right"></i>${n}</span>`}const n=l(this.action.context?.detail),i=this.isClosed?" disabled":"";this.template=`\n <div class="ac">\n <div class="ac-top">\n <span class="ac-dot ${this.dotClass}"></span>\n <span class="ac-label">${l(this.action.label)||"Action"}</span>\n </div>\n <div class="ac-detail">${n}${t}</div>\n <div class="ac-foot">\n <button class="btn-approve" data-action="approve"${i}>Approve</button>\n <button class="btn-deny" data-action="deny"${i}>Deny</button>\n </div>\n </div>\n `}async onActionOpenRef(e,t){const n=t.dataset.model,a=t.dataset.pk;if(!c.has(n)||!/^\d+$/.test(a))return;const s=this.getApp(),o=s?.getModelByRef(n);o?.VIEW_CLASS&&i.Modal.showModel(new o({id:a}))}onActionApprove(){this.emit("action:respond",{noteId:this.noteId,action:"approve",handler:this.action.handler,context:this.action.context})}onActionDeny(){this.emit("action:respond",{noteId:this.noteId,action:"deny",handler:this.action.handler,context:this.action.context})}}const p=["new","open","in_progress","pending","resolved","qa","closed","ignored"],h={new:"pill-new",open:"pill-open",in_progress:"pill-prog",pending:"pill-prog",resolved:"pill-resolved",qa:"pill-open",closed:"pill-closed",ignored:"pill-closed"},g=[{value:10,label:"P10 — Critical"},{value:9,label:"P9 — Severe"},{value:8,label:"P8 — High"},{value:7,label:"P7 — Elevated"},{value:5,label:"P5 — Normal"},{value:3,label:"P3 — Low"},{value:1,label:"P1 — Info"}];class TicketPanelView extends e.View{constructor(e={}){super({className:"ticket-panel-view",...e}),this.model=e.model||new a.Ticket(e.data||{})}onBeforeRender(){const e=this.model.get("status")||"new";this.statusPill=h[e]||"pill-closed",this.statusLabel=e.replace(/_/g," "),this.priorityLabel=`P${this.model.get("priority")||5}`,this.assigneeName=this.model.get("assignee.display_name")||this.model.get("assignee")||"Unassigned",this.categoryLabel=this.model.get("category")||"ticket",this.groupName=this.model.get("group.name")||this.model.get("group")||"None",this.hasDescription=!!this.model.get("description"),this.hasIncident=!(!this.model.get("incident")||"object"!=typeof this.model.get("incident")||!this.model.get("incident").id),this.priorityColor=(this.model.get("priority")||5)>=7?"var(--bs-danger)":"var(--bs-secondary-color)",this.template=`\n <style>\n .ticket-panel-view { height: 100%; display: flex; flex-direction: column; }\n .tp-header { padding: 10px 16px 6px; border-bottom: 1px solid var(--bs-border-color-translucent); flex-shrink: 0; }\n .tp-title-row { display: flex; align-items: flex-start; gap: 6px; }\n .tp-title { font-size: 0.88rem; font-weight: 600; color: var(--bs-emphasis-color); line-height: 1.3; flex: 1; min-width: 0; cursor: ${this.hasDescription?"pointer":"default"}; transition: color 0.12s; }\n ${this.hasDescription?".tp-title:hover { color: var(--bs-primary); }":""}\n .tp-title i { font-size: 0.6rem; vertical-align: middle; margin-left: 3px; opacity: 0; transition: opacity 0.12s; }\n .tp-title:hover i { opacity: 0.6; }\n .tp-btns { display: flex; gap: 2px; align-items: center; flex-shrink: 0; }\n .tp-btn { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border: none; background: none; color: var(--bs-secondary-color); border-radius: 6px; cursor: pointer; font-size: 0.85rem; transition: all 0.12s; }\n .tp-btn:hover { background: var(--bs-tertiary-bg); color: var(--bs-body-color); }\n .tp-sub { display: flex; align-items: center; gap: 6px; margin-top: 3px; }\n .tp-id { font-family: var(--bs-font-monospace); font-size: 0.7rem; color: var(--bs-secondary-color); }\n .tp-pill { display: inline-block; padding: 1px 7px; border-radius: 10px; font-size: 0.66rem; font-weight: 500; cursor: pointer; transition: filter 0.12s; }\n .tp-pill:hover { filter: brightness(0.9); }\n .tp-pill-new { background: rgba(var(--bs-info-rgb), 0.1); color: var(--bs-info); }\n .tp-pill-open { background: rgba(var(--bs-success-rgb), 0.1); color: var(--bs-success); }\n .tp-pill-prog { background: rgba(var(--bs-warning-rgb), 0.1); color: var(--bs-warning); }\n .tp-pill-closed { background: var(--bs-secondary-bg); color: var(--bs-secondary-color); }\n .tp-pill-resolved { background: rgba(var(--bs-success-rgb), 0.1); color: var(--bs-success); }\n .tp-time { font-size: 0.66rem; color: var(--bs-secondary-color); }\n .tp-desc-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 0.7rem; color: var(--bs-primary); cursor: pointer; padding: 2px 8px; border-radius: 5px; background: rgba(var(--bs-primary-rgb), 0.08); transition: all 0.12s; }\n .tp-desc-chip:hover { background: rgba(var(--bs-primary-rgb), 0.16); }\n .tp-desc-chip i { font-size: 0.7rem; }\n .tp-meta { display: flex; align-items: center; gap: 3px; margin-top: 5px; }\n .tp-fields { display: inline-flex; align-items: center; gap: 2px; flex-wrap: wrap; }\n .tp-field { display: inline-flex; align-items: center; gap: 4px; font-size: 0.72rem; color: var(--bs-secondary-color); padding: 2px 7px; border-radius: 5px; cursor: pointer; transition: all 0.12s; border: 1px solid transparent; }\n .tp-field:hover { background: var(--bs-tertiary-bg); border-color: var(--bs-border-color); color: var(--bs-body-color); }\n .tp-field i { font-size: 0.68rem; }\n .tp-field .caret { font-size: 0.55rem; opacity: 0; transition: opacity 0.12s; margin-left: -1px; }\n .tp-field:hover .caret { opacity: 0.6; }\n .tp-sep { color: var(--bs-secondary-color); font-size: 0.6rem; margin: 0 1px; user-select: none; }\n\n .tp-linked { display: flex; align-items: center; gap: 6px; padding: 7px 16px; border-bottom: 1px solid var(--bs-border-color-translucent); font-size: 0.75rem; color: var(--bs-secondary-color); flex-shrink: 0; }\n .tp-linked i { color: var(--bs-warning); font-size: 0.72rem; }\n .tp-linked a { color: var(--bs-primary); text-decoration: none; font-weight: 500; }\n .tp-linked a:hover { text-decoration: underline; }\n .tp-linked .lpill { font-size: 0.62rem; padding: 0 5px; border-radius: 3px; background: rgba(var(--bs-warning-rgb), 0.1); color: var(--bs-warning); font-weight: 500; }\n\n .tp-conv { flex: 1; overflow-y: auto; min-height: 0; }\n .tp-conv .chat-container { border: none; border-radius: 0; }\n .tp-conv .chat-messages { padding: 6px 0; }\n .tp-conv .chat-input-wrapper { display: none; }\n .tp-conv .message-item { position: relative; }\n .tp-conv .tp-edit-btn { position: absolute; top: 8px; right: 8px; width: 24px; height: 24px; display: none; align-items: center; justify-content: center; border: none; background: var(--bs-tertiary-bg); color: var(--bs-secondary-color); border-radius: 5px; cursor: pointer; font-size: 0.72rem; transition: all 0.12s; }\n .tp-conv .tp-edit-btn:hover { background: var(--bs-secondary-bg); color: var(--bs-body-color); }\n .tp-conv .message-item:hover .tp-edit-btn { display: flex; }\n .tp-conv .message-text { white-space: normal; }\n .tp-conv .message-text p { margin-bottom: 6px; }\n .tp-conv .message-text p:last-child { margin-bottom: 0; }\n .tp-conv .message-text h1,\n .tp-conv .message-text h2,\n .tp-conv .message-text h3,\n .tp-conv .message-text h4,\n .tp-conv .message-text h5,\n .tp-conv .message-text h6 { font-weight: 600; margin-top: 10px; margin-bottom: 4px; line-height: 1.3; }\n .tp-conv .message-text h1 { font-size: 1.05rem; }\n .tp-conv .message-text h2 { font-size: 1rem; }\n .tp-conv .message-text h3 { font-size: 0.95rem; }\n .tp-conv .message-text h4,\n .tp-conv .message-text h5,\n .tp-conv .message-text h6 { font-size: 0.88rem; }\n .tp-conv .message-text h1:first-child,\n .tp-conv .message-text h2:first-child,\n .tp-conv .message-text h3:first-child { margin-top: 0; }\n .tp-conv .message-text hr { margin: 4px 0; opacity: 0.15; }\n .tp-conv .message-text ul,\n .tp-conv .message-text ol { padding-left: 20px; margin-top: 2px; margin-bottom: 6px; }\n .tp-conv .message-text li { margin-bottom: 2px; }\n .tp-conv .message-text pre { background: var(--bs-tertiary-bg); border-radius: 6px; padding: 10px 14px; margin: 8px 0; font-size: 0.8rem; overflow-x: auto; }\n .tp-conv .message-text code { font-size: 0.85em; padding: 1px 5px; background: var(--bs-tertiary-bg); border-radius: 4px; }\n .tp-conv .message-text pre code { padding: 0; background: none; }\n .tp-conv .message-text table { width: 100%; margin: 8px 0; border-collapse: collapse; font-size: 0.82rem; }\n .tp-conv .message-text th,\n .tp-conv .message-text td { padding: 5px 8px; border: 1px solid var(--bs-border-color); text-align: left; }\n .tp-conv .message-text th { background: var(--bs-tertiary-bg); font-weight: 600; }\n .tp-conv .message-text blockquote { margin: 6px 0; padding: 4px 12px; border-left: 3px solid var(--bs-border-color); color: var(--bs-secondary-color); }\n\n .tp-action-area { padding: 0; }\n\n .tp-input { border-top: 1px solid var(--bs-border-color-translucent); padding: 10px 16px; flex-shrink: 0; }\n .tp-input-wrap { display: flex; align-items: flex-end; gap: 8px; }\n .tp-input textarea { flex: 1; font-size: 0.8rem; border: 1px solid var(--bs-border-color); border-radius: 8px; padding: 7px 10px; resize: none; background: var(--bs-body-bg); color: var(--bs-body-color); outline: none; transition: border-color 0.15s; font-family: inherit; max-height: 160px; overflow-y: auto; }\n .tp-input textarea:focus { border-color: var(--bs-primary); }\n .tp-input textarea::placeholder { color: var(--bs-secondary-color); }\n .tp-send-btn { width: 32px; height: 32px; border-radius: 8px; border: none; background: var(--bs-primary); color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; font-size: 0.85rem; transition: filter 0.12s; }\n .tp-send-btn:hover { filter: brightness(1.1); }\n .tp-input-hint { display: flex; align-items: center; gap: 4px; margin-top: 4px; font-size: 0.7rem; color: var(--bs-secondary-color); }\n .tp-input-hint i { font-size: 0.75rem; }\n .tp-input.tp-dragover { background: rgba(var(--bs-primary-rgb), 0.04); }\n .tp-input.tp-dragover textarea { border-color: var(--bs-primary); border-style: dashed; }\n .tp-attachments { display: flex; flex-wrap: wrap; gap: 4px; padding: 4px 0; }\n .tp-attach-chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; background: var(--bs-tertiary-bg); border: 1px solid var(--bs-border-color); border-radius: 6px; font-size: 0.72rem; color: var(--bs-body-color); }\n .tp-attach-chip i { font-size: 0.68rem; }\n .tp-attach-chip .remove { cursor: pointer; color: var(--bs-secondary-color); margin-left: 2px; }\n .tp-attach-chip .remove:hover { color: var(--bs-danger); }\n .tp-attach-chip.uploading { opacity: 0.6; }\n .tp-conv .message-content.tp-collapsed { max-height: var(--tp-collapse-h, 52px); overflow: hidden; position: relative; -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%); mask-image: linear-gradient(to bottom, black 60%, transparent 100%); }\n .tp-conv .message-item:has(.tp-show-more) { flex-wrap: wrap; }\n .tp-show-more { background: none; border: none; padding: 2px 0; margin: -2px 0 0 48px; font-size: 0.7rem; color: var(--bs-secondary-color); cursor: pointer; text-align: left; width: calc(100% - 48px); }\n .tp-show-more:hover { color: var(--bs-body-color); }\n </style>\n\n <div class="tp-header">\n <div class="tp-title-row">\n <div class="tp-title" ${this.hasDescription?'data-action="show-description"':""} ${this.hasDescription?'title="View full description"':""}>\n {{model.title}}${this.hasDescription?' <i class="bi bi-arrow-up-right-square"></i>':""}\n </div>\n <div class="tp-btns">\n <div data-container="panel-menu"></div>\n </div>\n </div>\n <div class="tp-sub">\n <span class="tp-id">#{{model.id}}</span>\n <span class="tp-pill tp-${this.statusPill}" data-action="change-status" title="Change status">{{statusLabel}} <i class="bi bi-chevron-down" style="font-size:0.5rem;"></i></span>\n <span class="tp-time"><i class="bi bi-clock"></i> {{model.created|relative}}</span>\n <span class="tp-desc-chip" data-action="show-description" title="${this.hasDescription?"View / edit description":"Add description"}"><i class="bi bi-file-text"></i> ${this.hasDescription?"Description":"Add description"}</span>\n </div>\n <div class="tp-meta">\n <div class="tp-fields">\n <span class="tp-field" data-action="change-priority" title="Change priority">\n <i class="bi bi-flag-fill" style="color:${this.priorityColor};"></i>{{priorityLabel}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n <span class="tp-sep">·</span>\n <span class="tp-field" data-action="change-assignee" title="Assign">\n <i class="bi bi-person"></i>{{assigneeName}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n <span class="tp-sep">·</span>\n <span class="tp-field" data-action="change-category" title="Change category">\n <i class="bi bi-tag"></i>{{categoryLabel}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n <span class="tp-sep">·</span>\n <span class="tp-field" data-action="change-group" title="Change group">\n <i class="bi bi-people"></i>{{groupName}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n </div>\n </div>\n </div>\n\n {{#hasIncident|bool}}\n <div class="tp-linked">\n <i class="bi bi-exclamation-triangle-fill"></i>\n <a href="#" data-action="view-incident">Incident #{{model.incident.id}}</a>\n {{#model.incident.status}}<span class="lpill">{{model.incident.status}}</span>{{/model.incident.status}}\n {{#model.incident.event_count}}<span>· {{model.incident.event_count}} events</span>{{/model.incident.event_count}}\n </div>\n {{/hasIncident|bool}}\n\n <div class="tp-conv">\n <div data-container="chat-area"></div>\n </div>\n\n <div class="tp-input" data-ref="drop-zone">\n <div class="tp-attachments" data-ref="attachments"></div>\n <div class="tp-input-wrap">\n <textarea rows="2" placeholder="Add a note..." data-ref="note-textarea"></textarea>\n <button class="tp-send-btn" data-action="send-note" title="Send"><i class="bi bi-arrow-up"></i></button>\n </div>\n <div class="tp-input-hint">\n <i class="bi bi-paperclip"></i> Drag & drop files to attach\n </div>\n </div>\n `}async onInit(){this.adapter=new o.TicketNoteAdapter(this.model.get("id")),this.chatView=new t.ChatView({containerId:"chat-area",adapter:this.adapter,theme:"compact",currentUserId:this._getCurrentUserId(),showInput:!1}),this.addChild(this.chatView);const e=new n.ContextMenu({containerId:"panel-menu",className:"context-menu-view header-menu-absolute",context:this.model,config:{icon:"bi-three-dots",btnClass:"tp-btn",items:[{label:"Ask AI",action:"ask-ai",icon:"bi-robot"},{type:"divider"},{label:"Edit Ticket",action:"edit-ticket",icon:"bi-pencil"},{label:"Refresh Notes",action:"refresh-notes",icon:"bi-arrow-clockwise"},{type:"divider"},{label:"Close Window",action:"close",icon:"bi-x-lg"}]}});this.addChild(e)}async onAfterRender(){this._setupTextarea(),this._setupDragDrop(),await new Promise(e=>setTimeout(e,0)),await this._loadActionCards(),this._addEditButtons(),this._setupCollapsible()}async _loadActionCards(){this._cleanupActionCards();const e=this.chatView.messages||[];if(e.length)for(const t of e){if(!t.action||"object"!=typeof t.action)continue;const e=this.chatView.messageViews.get(t.id);if(!e?.element)continue;const n=new ActionCardView({action:t.action,noteId:t.id,ticketStatus:this.model.get("status")});"context"===t.action.type||t.action.resolved||n.on("action:respond",e=>this._handleActionResponse(e)),this.addChild(n),await n.render(),e.element.after(n.element)}}_cleanupActionCards(){for(const e in this.children){const t=this.children[e];t instanceof ActionCardView&&this.removeChild(t)}}async _handleActionResponse(e){const t={action:{handler:e.handler,context:e.context}},n=this.getApp();n?.showLoading();try{await this.adapter.addActionResponse(t,e.action),await this.model.fetch(),await this.chatView.refresh(),this.render()}finally{n?.hideLoading()}}_getCurrentUserId(){const e=this.getApp();return e?.activeUser?.id||e?.getActiveUser?.()?.id||null}onActionClose(){this.emit("panel:close")}async onActionSendNote(){const e=this.element?.querySelector('[data-ref="note-textarea"]'),t=e?.value?.trim(),n=this._stagedFiles||[];(t||n.length)&&(e.value="",e.style.height="",this._stagedFiles=[],this._renderAttachments(),await this.adapter.addNote({text:t||"",files:n}),await this.chatView.refresh(),await this._afterChatRefresh())}_setupTextarea(){const e=this.element?.querySelector('[data-ref="note-textarea"]');if(!e)return;const t=(t,n=t.length)=>{const i=e.selectionStart,a=e.selectionEnd;e.setRangeText(t,i,a,"end"),e.selectionStart=e.selectionEnd=i+n,e.dispatchEvent(new Event("input")),e.scrollTop=e.scrollHeight},n=t=>{const n=e.selectionStart,i=e.selectionEnd,a=e.value.substring(n,i);a.startsWith(t)&&a.endsWith(t)?(e.setRangeText(a.slice(t.length,-t.length),n,i,"end"),e.selectionStart=n,e.selectionEnd=i-2*t.length):(e.setRangeText(t+a+t,n,i,"end"),e.selectionStart=n+t.length,e.selectionEnd=i+t.length)},i=t=>{const n=e.value.substring(0,t).lastIndexOf("\n")+1;return{start:n,text:e.value.substring(n,t)}},a=t=>(e.value.substring(0,t).match(/^```/gm)||[]).length%2==1;e.addEventListener("keydown",s=>{const o=s.ctrlKey||s.metaKey;if("Enter"===s.key&&!s.shiftKey&&!o)return s.preventDefault(),void this.onActionSendNote();if("Enter"===s.key&&s.shiftKey){const{start:n,text:a}=i(e.selectionStart),o=a.match(/^(\s*)([-*]|\d+\.)\s/);if(o){s.preventDefault();const i=o[1],r=o[2];if(a.trim()===r)e.setRangeText("",n,e.selectionStart,"end");else{const e=/^\d+\./.test(r)?`${parseInt(r)+1}.`:r;t(`\n${i}${e} `)}return}}if("`"===s.key&&!o){const n=e.selectionStart;if(e.value.substring(0,n).endsWith("``")&&!a(n-2))return s.preventDefault(),void t("`\n\n```",2)}if("Tab"!==s.key||!a(e.selectionStart))return o&&"b"===s.key?(s.preventDefault(),void n("**")):o&&"i"===s.key?(s.preventDefault(),void n("*")):void 0;if(s.preventDefault(),s.shiftKey){const{start:t,text:n}=i(e.selectionStart),a=n.replace(/^ {1,2}/,"");e.setRangeText(a,t,t+n.length,"end"),e.selectionStart=e.selectionEnd=t+a.length}else t(" ")});const s={"(":")","[":"]",'"':'"'};e.addEventListener("keydown",n=>{if(n.ctrlKey||n.metaKey||n.altKey)return;const i=s[n.key];if(!i)return;const a=e.selectionStart,o=e.selectionEnd;if(a!==o){n.preventDefault();const t=e.value.substring(a,o);e.setRangeText(n.key+t+i,a,o,"end"),e.selectionStart=a+1,e.selectionEnd=o+1}else n.preventDefault(),t(n.key+i,1)}),e.addEventListener("input",()=>{e.style.height="",e.style.height=Math.min(e.scrollHeight,160)+"px"})}_setupDragDrop(){const e=this.element?.querySelector('[data-ref="drop-zone"]');if(!e)return;this._stagedFiles=this._stagedFiles||[];let t=0;e.addEventListener("dragenter",n=>{n.preventDefault(),t++,e.classList.add("tp-dragover")}),e.addEventListener("dragleave",()=>{t--,t<=0&&(t=0,e.classList.remove("tp-dragover"))}),e.addEventListener("dragover",e=>e.preventDefault()),e.addEventListener("drop",async n=>{n.preventDefault(),t=0,e.classList.remove("tp-dragover");const i=Array.from(n.dataTransfer?.files||[]);if(!i.length)return;const{File:a}=await Promise.resolve().then(()=>require("./Modal-Bm1OQ8Ou.js")).then(e=>e.Files);for(const e of i){const t=Date.now()+Math.random();this._addAttachChip(t,e.name,!0);try{const n=new a;await n.upload({file:e,showToast:!1}),this._stagedFiles.push(n),this._updateAttachChip(t,n.get("name")||e.name,n)}catch(s){console.error("File upload failed:",s),this._removeAttachChip(t),this.getApp()?.toast?.error?.("Upload failed: "+e.name)}}})}_addAttachChip(e,t,n){const i=this.element?.querySelector('[data-ref="attachments"]');if(!i)return;const a=document.createElement("span");a.className="tp-attach-chip"+(n?" uploading":""),a.dataset.chipId=e,a.innerHTML=`<i class="bi bi-paperclip"></i>${this._escapeHtml(t)}`+(n?"":'<span class="remove" data-remove="1"><i class="bi bi-x"></i></span>'),n||a.querySelector(".remove").addEventListener("click",()=>{this._removeAttachChip(e)}),i.appendChild(a)}_updateAttachChip(e,t,n){const i=this.element?.querySelector(`[data-chip-id="${e}"]`);i&&(i.classList.remove("uploading"),i.innerHTML=`<i class="bi bi-paperclip"></i>${this._escapeHtml(t)}<span class="remove" data-remove="1"><i class="bi bi-x"></i></span>`,i.querySelector(".remove").addEventListener("click",()=>{this._stagedFiles=(this._stagedFiles||[]).filter(e=>e!==n),i.remove()}))}_removeAttachChip(e){const t=this.element?.querySelector(`[data-chip-id="${e}"]`);t&&t.remove()}_renderAttachments(){const e=this.element?.querySelector('[data-ref="attachments"]');e&&(e.innerHTML="")}_escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}_addEditButtons(){const e=this._getCurrentUserId();if(!e||!this.chatView?.messageViews)return;const t=new Map((this.chatView.messages||[]).map(e=>[e.id,e]));this.chatView.messageViews.forEach((n,i)=>{const a=t.get(i),s=n?.element?.querySelector(".message-item");if(!s)return;if(s.querySelectorAll(".tp-edit-btn").forEach(e=>e.remove()),!a||a.author?.id!==e)return;const o=document.createElement("button");o.className="tp-edit-btn",o.title="Edit note",o.innerHTML='<i class="bi bi-pencil"></i>',o.addEventListener("click",e=>{e.stopPropagation(),this._editNote(a)}),s.appendChild(o)})}_setupCollapsible(){const e=this.element?.querySelector('[data-container="chat-area"]');e&&(e.querySelectorAll(".tp-show-more").forEach(e=>e.remove()),e.querySelectorAll(".tp-collapsed").forEach(e=>e.classList.remove("tp-collapsed")),setTimeout(()=>{e.querySelectorAll(".message-content").forEach(e=>{if(e.scrollHeight<=52)return;e.classList.add("tp-collapsed"),e.style.setProperty("--tp-collapse-h","52px");const t=document.createElement("button");t.className="tp-show-more",t.textContent="Show more",t.addEventListener("click",()=>{const n=e.classList.toggle("tp-collapsed");t.textContent=n?"Show more":"Show less"}),e.after(t)})},150))}async _editNote(e){const t=Object.keys(e._metadata||{}).length?JSON.stringify(e._metadata,null,2):"",n=await i.Modal.form({title:"Edit Note",icon:"bi-pencil",size:"lg",fields:[{type:"tabset",tabs:[{label:"Note",fields:[{name:"note",type:"textarea",label:"Note",required:!0,cols:12,rows:8,value:e._rawContent||e.content}]},{label:"Metadata",fields:[{name:"metadata_json",type:"json",label:"Metadata (JSON)",cols:12,rows:10,value:t,help:'Action metadata — e.g. { "action": { "handler": "incident.rule_approval", "label": "...", "context": { ... } } }'}]}]}]});if(!n)return;const{TicketNote:a}=await Promise.resolve().then(()=>require("./admin-models-CkHjtMHf.js")).then(e=>e.Tickets),s=new a({id:e.id}),o={note:n.note};n.metadata_json&&(o.metadata="string"==typeof n.metadata_json?JSON.parse(n.metadata_json):n.metadata_json),await s.save(o),await this.chatView.refresh(),await this._afterChatRefresh()}async _saveAndSync(e){await this.model.save(e),await this.model.fetch(),this.chatView&&(await this.chatView.refresh(),await this._afterChatRefresh())}async onActionChangeStatus(e){const t=p.map(e=>({label:e.replace(/_/g," "),value:e,active:e===this.model.get("status")})),n=await this._showInlineSelect(t,e);n&&(await this._saveAndSync({status:n}),this.render())}async onActionChangePriority(e){const t=g.map(e=>({label:e.label,value:e.value,active:e.value===this.model.get("priority")})),n=await this._showInlineSelect(t,e);n&&(await this._saveAndSync({priority:parseInt(n)}),this.render())}async onActionChangeAssignee(){const e=await i.Modal.form({title:"Assign User",icon:"bi-person-plus",size:"sm",fields:[{name:"assignee",type:"collection",label:"User",Collection:s.UserList,labelField:"display_name",valueField:"id",required:!0,cols:12,value:this.model.get("assignee")}]});e&&(await this._saveAndSync({assignee:e.assignee}),this.render())}async onActionChangeCategory(e){const t=Object.entries(a.TicketCategories).map(([e,t])=>({label:t,value:e,active:e===this.model.get("category")})),n=await this._showInlineSelect(t,e);n&&(await this._saveAndSync({category:n}),this.render())}async onActionChangeGroup(){const e=await i.Modal.form({title:"Change Group",icon:"bi-people",size:"sm",fields:[{name:"group",type:"collection",label:"Group",Collection:s.GroupList,labelField:"name",valueField:"id",required:!1,cols:12,value:this.model.get("group")}]});e&&(await this._saveAndSync({group:e.group}),this.render())}async onActionShowDescription(){const e=this.model.get("description")||"";if(!e)return this._editDescription();let t=!1,n="";try{const i=await r.rest.post("/api/docit/render",{markdown:e});n=i?.data?.data?.html||i?.data?.html||"",t=!!n}catch(a){}if(!t){const t=document.createElement("div");t.textContent=e,n=`<pre style="white-space:pre-wrap;">${t.innerHTML}</pre>`}"edit"===await i.Modal.dialog({title:`Ticket #${this.model.get("id")} — Description`,body:`<div style="font-size:0.85rem; line-height:1.65;">${n}</div>`,size:"lg",buttons:[{text:"Edit",class:"btn-primary",value:"edit"},{text:"Close",class:"btn-secondary",value:"close"}]})&&await this._editDescription()}async _editDescription(){const e=this.model.get("id"),t=`\n <div class="tp-desc-edit">\n <textarea data-ref="desc-textarea" rows="16" placeholder="Description (markdown supported)..."\n style="width:100%; font-family: var(--bs-font-monospace); font-size: 0.85rem; padding: 10px 12px; border: 1px solid var(--bs-border-color); border-radius: 8px; background: var(--bs-body-bg); color: var(--bs-body-color); resize: vertical; outline: none;">${(this.model.get("description")||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}</textarea>\n <div class="text-muted small mt-1">\n Markdown supported. Cmd/Ctrl+B = bold · Cmd/Ctrl+I = italic · Shift+Enter continues lists · \`\`\` opens a code block\n </div>\n </div>\n `,n=(async()=>{for(let e=0;e<20;e++){await new Promise(e=>setTimeout(e,50));const e=document.querySelector('.modal.show [data-ref="desc-textarea"]');if(e)return this._wireMarkdownTextarea(e),e}return null})(),a=await i.Modal.dialog({title:`Ticket #${e} — Edit Description`,body:t,size:"lg",buttons:[{text:"Cancel",class:"btn-secondary",value:null},{text:"Save",class:"btn-primary",handler:()=>{const e=document.querySelector('.modal.show [data-ref="desc-textarea"]');return e?e.value:null}}]});await n,null!=a&&(await this._saveAndSync({description:a}),this.render())}_wireMarkdownTextarea(e){const t=(t,n=t.length)=>{const i=e.selectionStart,a=e.selectionEnd;e.setRangeText(t,i,a,"end"),e.selectionStart=e.selectionEnd=i+n,e.dispatchEvent(new Event("input"))},n=t=>{const n=e.selectionStart,i=e.selectionEnd,a=e.value.substring(n,i);a.startsWith(t)&&a.endsWith(t)?(e.setRangeText(a.slice(t.length,-t.length),n,i,"end"),e.selectionStart=n,e.selectionEnd=i-2*t.length):(e.setRangeText(t+a+t,n,i,"end"),e.selectionStart=n+t.length,e.selectionEnd=i+t.length)},i=t=>{const n=e.value.substring(0,t).lastIndexOf("\n")+1;return{start:n,text:e.value.substring(n,t)}},a=t=>(e.value.substring(0,t).match(/^```/gm)||[]).length%2==1;e.addEventListener("keydown",s=>{const o=s.ctrlKey||s.metaKey;if("Enter"===s.key&&s.shiftKey){const{start:n,text:a}=i(e.selectionStart),o=a.match(/^(\s*)([-*]|\d+\.)\s/);if(o){s.preventDefault();const i=o[1],r=o[2];if(a.trim()===r)e.setRangeText("",n,e.selectionStart,"end");else{const e=/^\d+\./.test(r)?`${parseInt(r)+1}.`:r;t(`\n${i}${e} `)}return}}if("`"===s.key&&!o){const n=e.selectionStart;if(e.value.substring(0,n).endsWith("``")&&!a(n-2))return s.preventDefault(),void t("`\n\n```",2)}if("Tab"!==s.key||!a(e.selectionStart))return o&&"b"===s.key?(s.preventDefault(),void n("**")):o&&"i"===s.key?(s.preventDefault(),void n("*")):void 0;if(s.preventDefault(),s.shiftKey){const{start:t,text:n}=i(e.selectionStart),a=n.replace(/^ {1,2}/,"");e.setRangeText(a,t,t+n.length,"end"),e.selectionStart=e.selectionEnd=t+a.length}else t(" ")});const s={"(":")","[":"]",'"':'"'};e.addEventListener("keydown",n=>{if(n.ctrlKey||n.metaKey||n.altKey)return;const i=s[n.key];if(!i)return;const a=e.selectionStart,o=e.selectionEnd;if(a!==o){n.preventDefault();const t=e.value.substring(a,o);e.setRangeText(n.key+t+i,a,o,"end"),e.selectionStart=a+1,e.selectionEnd=o+1}else n.preventDefault(),t(n.key+i,1)})}async onActionViewIncident(){const e=this.model.get("incident");e?.id&&i.Modal.showModel(new a.Incident({id:e.id}))}async onActionEditTicket(){await i.Modal.modelForm({title:`Edit Ticket #${this.model.get("id")}`,model:this.model,size:"lg",fields:a.TicketForms.edit.fields})&&this.render()}async onActionRefreshNotes(){await this.chatView.refresh(),await this._afterChatRefresh(),this.getApp()?.toast?.success("Notes refreshed")}async onActionAskAi(){await o.openAssistantChat(this,"incident.Ticket")}async _showInlineSelect(e,t){return new Promise(i=>{let a=!1;const s=new n.ContextMenu({config:{items:e.map((e,t)=>({label:e.label,action:`pick-${t}`,class:e.active?"fw-bold":"",handler:()=>{a=!0,this.removeChild(s),i(e.value)}}))}}),o=s.closeDropdown.bind(s);s.closeDropdown=()=>{o(),a||(this.removeChild(s),i(null))},this.addChild(s),s.openAt(t.clientX,t.clientY)})}async _afterChatRefresh(){await new Promise(e=>setTimeout(e,0)),await this._loadActionCards(),this._addEditButtons(),this._setupCollapsible()}async setTicket(e){this.model=e,this.adapter=new o.TicketNoteAdapter(e.get("id")),this.chatView.adapter=this.adapter,this.chatView.clearMessages(),await this.render(),await this.chatView.refresh(),await this._afterChatRefresh()}}exports.default=TicketPanelView;
|
|
2
|
+
//# sourceMappingURL=TicketPanelView-BGQZ6q2B.js.map
|