@composy/chat-core 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +187 -123
- package/dist/index.js +2018 -1035
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1370 -1644
- package/dist/index.min.js.map +1 -0
- package/es/.dts-output-manifest.json +1 -0
- package/es/.dts.tsbuildinfo +1 -0
- package/es/constants/dom.d.ts +3 -19
- package/es/constants/dom.js +4 -4
- package/es/constants/dom.js.map +1 -1
- package/es/core/chat-manager.d.ts +33 -22
- package/es/core/chat-manager.js +174 -50
- package/es/core/chat-manager.js.map +1 -1
- package/es/core/event-emitter.d.ts +0 -1
- package/es/core/event-emitter.js +14 -7
- package/es/core/event-emitter.js.map +1 -1
- package/es/core/mention-engine.d.ts +1 -2
- package/es/core/mention-engine.js +9 -9
- package/es/core/mention-engine.js.map +1 -1
- package/es/core/message-schema.d.ts +19 -0
- package/es/core/message-schema.js +197 -0
- package/es/core/message-schema.js.map +1 -0
- package/es/core/outbox.d.ts +63 -0
- package/es/core/outbox.js +160 -0
- package/es/core/outbox.js.map +1 -0
- package/es/core/transport.d.ts +79 -0
- package/es/core/transport.js +102 -0
- package/es/core/transport.js.map +1 -0
- package/es/index.d.ts +12 -7
- package/es/index.js +167 -36
- package/es/index.js.map +1 -1
- package/es/renderer/dom-renderer.d.ts +16 -4
- package/es/renderer/dom-renderer.js +316 -61
- package/es/renderer/dom-renderer.js.map +1 -1
- package/es/renderer/style-manager.d.ts +14 -38
- package/es/renderer/style-manager.js +92 -63
- package/es/renderer/style-manager.js.map +1 -1
- package/es/theme/theme-manager.d.ts +4 -3
- package/es/theme/theme-manager.js +19 -9
- package/es/theme/theme-manager.js.map +1 -1
- package/es/theme/tokens.d.ts +0 -1
- package/es/theme/tokens.js +92 -92
- package/es/theme/tokens.js.map +1 -1
- package/es/types/index.d.ts +104 -24
- package/es/types/index.js +8 -3
- package/es/types/index.js.map +1 -0
- package/esm/constants/dom.d.ts +3 -19
- package/esm/constants/dom.js +4 -4
- package/esm/constants/dom.js.map +1 -1
- package/esm/core/chat-manager.d.ts +33 -22
- package/esm/core/chat-manager.js +174 -50
- package/esm/core/chat-manager.js.map +1 -1
- package/esm/core/event-emitter.d.ts +0 -1
- package/esm/core/event-emitter.js +14 -7
- package/esm/core/event-emitter.js.map +1 -1
- package/esm/core/mention-engine.d.ts +1 -2
- package/esm/core/mention-engine.js +9 -9
- package/esm/core/mention-engine.js.map +1 -1
- package/esm/core/message-schema.d.ts +19 -0
- package/esm/core/message-schema.js +197 -0
- package/esm/core/message-schema.js.map +1 -0
- package/esm/core/outbox.d.ts +63 -0
- package/esm/core/outbox.js +160 -0
- package/esm/core/outbox.js.map +1 -0
- package/esm/core/transport.d.ts +79 -0
- package/esm/core/transport.js +102 -0
- package/esm/core/transport.js.map +1 -0
- package/esm/index.d.ts +12 -7
- package/esm/index.js +169 -38
- package/esm/index.js.map +1 -1
- package/esm/renderer/dom-renderer.d.ts +16 -4
- package/esm/renderer/dom-renderer.js +316 -61
- package/esm/renderer/dom-renderer.js.map +1 -1
- package/esm/renderer/style-manager.d.ts +14 -38
- package/esm/renderer/style-manager.js +92 -63
- package/esm/renderer/style-manager.js.map +1 -1
- package/esm/theme/theme-manager.d.ts +4 -3
- package/esm/theme/theme-manager.js +19 -9
- package/esm/theme/theme-manager.js.map +1 -1
- package/esm/theme/tokens.d.ts +0 -1
- package/esm/theme/tokens.js +92 -92
- package/esm/theme/tokens.js.map +1 -1
- package/esm/types/index.d.ts +104 -24
- package/esm/types/index.js +8 -3
- package/esm/types/index.js.map +1 -0
- package/lib/constants/dom.cjs +4 -4
- package/lib/constants/dom.cjs.map +1 -1
- package/lib/constants/dom.d.cts +25 -0
- package/lib/constants/dom.d.ts +3 -19
- package/lib/core/chat-manager.cjs +174 -50
- package/lib/core/chat-manager.cjs.map +1 -1
- package/lib/core/chat-manager.d.cts +45 -0
- package/lib/core/chat-manager.d.ts +33 -22
- package/lib/core/event-emitter.cjs +14 -7
- package/lib/core/event-emitter.cjs.map +1 -1
- package/lib/core/event-emitter.d.cts +47 -0
- package/lib/core/event-emitter.d.ts +0 -1
- package/lib/core/mention-engine.cjs +9 -9
- package/lib/core/mention-engine.cjs.map +1 -1
- package/lib/core/mention-engine.d.cts +40 -0
- package/lib/core/mention-engine.d.ts +1 -2
- package/lib/core/message-schema.cjs +202 -0
- package/lib/core/message-schema.cjs.map +1 -0
- package/lib/core/message-schema.d.cts +19 -0
- package/lib/core/message-schema.d.ts +19 -0
- package/lib/core/outbox.cjs +162 -0
- package/lib/core/outbox.cjs.map +1 -0
- package/lib/core/outbox.d.cts +63 -0
- package/lib/core/outbox.d.ts +63 -0
- package/lib/core/transport.cjs +103 -0
- package/lib/core/transport.cjs.map +1 -0
- package/lib/core/transport.d.cts +79 -0
- package/lib/core/transport.d.ts +79 -0
- package/lib/index.cjs +175 -35
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +33 -0
- package/lib/index.d.ts +12 -7
- package/lib/renderer/dom-renderer.cjs +316 -61
- package/lib/renderer/dom-renderer.cjs.map +1 -1
- package/lib/renderer/dom-renderer.d.cts +91 -0
- package/lib/renderer/dom-renderer.d.ts +16 -4
- package/lib/renderer/style-manager.cjs +92 -62
- package/lib/renderer/style-manager.cjs.map +1 -1
- package/lib/renderer/style-manager.d.cts +34 -0
- package/lib/renderer/style-manager.d.ts +14 -38
- package/lib/theme/theme-manager.cjs +19 -9
- package/lib/theme/theme-manager.cjs.map +1 -1
- package/lib/theme/theme-manager.d.cts +34 -0
- package/lib/theme/theme-manager.d.ts +4 -3
- package/lib/theme/tokens.cjs +92 -92
- package/lib/theme/tokens.cjs.map +1 -1
- package/lib/theme/tokens.d.cts +56 -0
- package/lib/theme/tokens.d.ts +0 -1
- package/lib/types/index.cjs +8 -3
- package/lib/types/index.cjs.map +1 -0
- package/lib/types/index.d.cts +284 -0
- package/lib/types/index.d.ts +104 -24
- package/package.json +16 -13
- package/es/constants/dom.d.ts.map +0 -1
- package/es/core/chat-manager.d.ts.map +0 -1
- package/es/core/event-emitter.d.ts.map +0 -1
- package/es/core/mention-engine.d.ts.map +0 -1
- package/es/index.d.ts.map +0 -1
- package/es/renderer/dom-renderer.d.ts.map +0 -1
- package/es/renderer/style-manager.d.ts.map +0 -1
- package/es/theme/theme-manager.d.ts.map +0 -1
- package/es/theme/tokens.d.ts.map +0 -1
- package/es/types/index.d.ts.map +0 -1
- package/esm/constants/dom.d.ts.map +0 -1
- package/esm/core/chat-manager.d.ts.map +0 -1
- package/esm/core/event-emitter.d.ts.map +0 -1
- package/esm/core/mention-engine.d.ts.map +0 -1
- package/esm/index.d.ts.map +0 -1
- package/esm/renderer/dom-renderer.d.ts.map +0 -1
- package/esm/renderer/style-manager.d.ts.map +0 -1
- package/esm/theme/theme-manager.d.ts.map +0 -1
- package/esm/theme/tokens.d.ts.map +0 -1
- package/esm/types/index.d.ts.map +0 -1
- package/lib/constants/dom.d.ts.map +0 -1
- package/lib/core/chat-manager.d.ts.map +0 -1
- package/lib/core/event-emitter.d.ts.map +0 -1
- package/lib/core/mention-engine.d.ts.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/renderer/dom-renderer.d.ts.map +0 -1
- package/lib/renderer/style-manager.d.ts.map +0 -1
- package/lib/theme/theme-manager.d.ts.map +0 -1
- package/lib/theme/tokens.d.ts.map +0 -1
- package/lib/types/index.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom-renderer.cjs","names":["row"],"sources":["../../src/renderer/dom-renderer.ts"],"sourcesContent":["import type { ChatConfig, ChatMessage, MentionItem, MessageStatus } from '../types';\nimport { EventEmitter } from '../core/event-emitter';\nimport { MentionEngine } from '../core/mention-engine';\nimport { DEFAULT_CLASS_PREFIX, cx, normalizeClassPrefix } from '../constants/dom';\n\nconst SEND_ICON_SVG = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"></line><polygon points=\"22 2 15 22 11 13 2 9 22 2\"></polygon></svg>';\nconst ICON_IMAGE = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/></svg>';\nconst ICON_PAPERCLIP = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48\"/></svg>';\nconst ICON_MIC = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\"/><path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/><line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/></svg>';\nconst ICON_MIC_OFF = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"2\" x2=\"22\" y1=\"2\" y2=\"22\"/><path d=\"M18.89 13.23A7.12 7.12 0 0 0 19 12v-2\"/><path d=\"M5 10v2a7 7 0 0 0 12 5.29\"/><path d=\"M15 9.34V5a3 3 0 0 0-5.68-1.33\"/><path d=\"M9 9v3a3 3 0 0 0 5.12 2.12\"/><line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/></svg>';\nconst FILE_ICON = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\"/><path d=\"M14 2v4a2 2 0 0 0 2 2h4\"/></svg>';\n\nconst STATUS_MAP: Record<MessageStatus, string> = {\n sending: '...',\n sent: '✓',\n delivered: '✓✓',\n read: '✓✓',\n failed: '!',\n};\n\nexport class DOMRenderer {\n private container: HTMLElement;\n private config: ChatConfig;\n private emitter: EventEmitter;\n private mentionEngine: MentionEngine;\n private prefix: string;\n\n private root!: HTMLElement;\n private messageListEl!: HTMLElement;\n private inputArea: HTMLElement | null = null;\n private textareaEl: HTMLTextAreaElement | null = null;\n private sendBtn: HTMLButtonElement | null = null;\n private mentionPanel: HTMLElement | null = null;\n private loadMoreEl: HTMLElement | null = null;\n private toolbarEl: HTMLElement | null = null;\n private dropOverlay: HTMLElement | null = null;\n private voiceBtn: HTMLElement | null = null;\n private loadingMore = false;\n private isRecording = false;\n private recognition: any = null;\n private lastVoiceTranscript = '';\n\n constructor(\n container: HTMLElement,\n config: ChatConfig,\n emitter: EventEmitter,\n mentionEngine: MentionEngine,\n ) {\n this.container = container;\n this.config = config;\n this.emitter = emitter;\n this.mentionEngine = mentionEngine;\n this.prefix = normalizeClassPrefix(config.classPrefix ?? DEFAULT_CLASS_PREFIX);\n }\n\n init(): void {\n this.container.innerHTML = '';\n this.root = this.el('div', `${this.prefix}-container`);\n this.messageListEl = this.el('div', `${this.prefix}-message-list`);\n this.root.appendChild(this.messageListEl);\n\n if (this.config.showInput !== false) {\n this.renderInputArea();\n }\n\n this.container.appendChild(this.root);\n this.setupEventListeners();\n }\n\n updateMessageContent(id: string, content: string): void {\n const rowEl = this.messageListEl.querySelector(`[data-msg-id=\"${this.escapeAttr(id)}\"]`) as HTMLElement | null;\n const bubble = rowEl?.querySelector(`.${this.prefix}-bubble`) as HTMLElement | null;\n if (!bubble) return;\n bubble.innerHTML = this.renderTextContent(content);\n }\n\n renderMessages(messages: ChatMessage[]): void {\n this.messageListEl.innerHTML = '';\n\n if (this.config.enableLoadMore) {\n this.renderLoadMoreButton();\n }\n\n if (messages.length === 0) {\n this.renderEmpty();\n return;\n }\n\n let lastTimestamp = 0;\n const interval = this.config.timeGroupInterval ?? 300000;\n for (const message of messages) {\n if (this.config.showTimestamp !== false && message.timestamp - lastTimestamp > interval) {\n this.renderTimeDivider(message.timestamp);\n lastTimestamp = message.timestamp;\n }\n this.renderMessageRow(message);\n }\n }\n\n appendMessage(message: ChatMessage): void {\n this.removeEmpty();\n const messageEls = this.getMessageElements();\n const lastTime = messageEls.length > 0\n ? Number.parseInt(messageEls[messageEls.length - 1].dataset.timestamp ?? '0', 10)\n : 0;\n const interval = this.config.timeGroupInterval ?? 300000;\n\n if (this.config.showTimestamp !== false && message.timestamp - lastTime > interval) {\n this.renderTimeDivider(message.timestamp);\n }\n\n this.renderMessageRow(message);\n }\n\n prependMessages(messages: ChatMessage[]): void {\n if (!messages.length) return;\n this.removeEmpty();\n\n const scrollHeight = this.messageListEl.scrollHeight;\n const scrollTop = this.messageListEl.scrollTop;\n const fragment = document.createDocumentFragment();\n let lastTimestamp = 0;\n const interval = this.config.timeGroupInterval ?? 300000;\n\n for (const message of messages) {\n if (this.config.showTimestamp !== false && message.timestamp - lastTimestamp > interval) {\n fragment.appendChild(this.createTimeDivider(message.timestamp));\n lastTimestamp = message.timestamp;\n }\n fragment.appendChild(this.createMessageRow(message));\n }\n\n const firstMessage = this.messageListEl.querySelector(`.${this.prefix}-message-row, .${this.prefix}-time-divider`);\n if (firstMessage) {\n firstMessage.before(fragment);\n } else if (this.loadMoreEl) {\n this.loadMoreEl.after(fragment);\n } else {\n this.messageListEl.appendChild(fragment);\n }\n\n this.messageListEl.scrollTop = scrollTop + (this.messageListEl.scrollHeight - scrollHeight);\n }\n\n updateMessage(id: string, message: ChatMessage): void {\n const rowEl = this.messageListEl.querySelector(`[data-msg-id=\"${this.escapeAttr(id)}\"]`);\n if (!rowEl) return;\n rowEl.replaceWith(this.createMessageRow(message));\n }\n\n removeMessage(id: string): void {\n this.messageListEl.querySelector(`[data-msg-id=\"${this.escapeAttr(id)}\"]`)?.remove();\n if (this.getMessageElements().length === 0) {\n this.renderEmpty();\n }\n }\n\n scrollToBottom(smooth = true): void {\n if (typeof this.messageListEl.scrollTo === 'function') {\n this.messageListEl.scrollTo({\n top: this.messageListEl.scrollHeight,\n behavior: smooth ? 'smooth' : 'auto',\n });\n return;\n }\n this.messageListEl.scrollTop = this.messageListEl.scrollHeight;\n }\n\n isAtBottom(threshold = 4): boolean {\n const distance = this.messageListEl.scrollHeight - this.messageListEl.scrollTop - this.messageListEl.clientHeight;\n return distance <= threshold;\n }\n\n setLoadingMore(loading: boolean): void {\n this.loadingMore = loading;\n if (!this.loadMoreEl) return;\n this.loadMoreEl.textContent = loading ? '加载中...' : (this.config.loadMoreText ?? '加载更多');\n this.loadMoreEl.classList.toggle(`${this.prefix}-load-more--loading`, loading);\n }\n\n focusInput(): void {\n this.textareaEl?.focus();\n }\n\n setInputContent(content: string): void {\n if (!this.textareaEl) return;\n this.textareaEl.value = this.limitInput(content);\n this.autoResize();\n this.updateSendButtonState();\n this.config.onInputChange?.(this.textareaEl.value);\n }\n\n getInputContent(): string {\n return this.textareaEl?.value ?? '';\n }\n\n clearInput(): void {\n this.setInputContent('');\n }\n\n renderMentionPanel(members: MentionItem[], activeIndex: number): void {\n if (!this.mentionPanel) return;\n\n if (members.length === 0) {\n this.mentionPanel.style.display = 'none';\n return;\n }\n\n this.mentionPanel.style.display = 'block';\n this.mentionPanel.innerHTML = '';\n\n members.forEach((member, index) => {\n const item = this.el(\n 'div',\n cx(`${this.prefix}-mention-item`, index === activeIndex && `${this.prefix}-mention-item--active`),\n );\n item.setAttribute('role', 'option');\n item.setAttribute('aria-selected', String(index === activeIndex));\n\n const avatar = this.el('div', `${this.prefix}-mention-item-avatar`);\n if (member.avatar) {\n const img = document.createElement('img');\n img.src = member.avatar;\n img.alt = '';\n avatar.appendChild(img);\n } else {\n avatar.textContent = member.name[0]?.toUpperCase() ?? '';\n }\n item.appendChild(avatar);\n\n const name = this.el('span', `${this.prefix}-mention-item-name`);\n name.textContent = member.name;\n item.appendChild(name);\n\n item.addEventListener('click', () => {\n this.mentionEngine.select(index);\n });\n\n this.mentionPanel?.appendChild(item);\n });\n }\n\n hideMentionPanel(): void {\n if (this.mentionPanel) {\n this.mentionPanel.style.display = 'none';\n }\n }\n\n destroy(): void {\n this.stopVoice();\n this.container.innerHTML = '';\n }\n\n private renderInputArea(): void {\n this.inputArea = this.el('div', `${this.prefix}-input-area`);\n const wrapper = this.el('div', `${this.prefix}-input-wrapper`);\n\n this.mentionPanel = this.el('div', `${this.prefix}-mention-panel`);\n this.mentionPanel.style.display = 'none';\n this.mentionPanel.setAttribute('role', 'listbox');\n wrapper.appendChild(this.mentionPanel);\n\n if (this.config.enableToolbar !== false) {\n this.renderToolbar(wrapper);\n }\n\n this.textareaEl = document.createElement('textarea');\n this.textareaEl.className = `${this.prefix}-textarea`;\n this.textareaEl.placeholder = this.config.inputPlaceholder ?? '输入消息...';\n this.textareaEl.rows = 1;\n if (this.config.maxInputLength && this.config.maxInputLength > 0) {\n this.textareaEl.maxLength = this.config.maxInputLength;\n }\n wrapper.appendChild(this.textareaEl);\n\n this.dropOverlay = this.el('div', `${this.prefix}-drop-overlay`);\n this.dropOverlay.innerHTML = '<span>拖放文件到这里</span>';\n this.dropOverlay.style.display = 'none';\n wrapper.appendChild(this.dropOverlay);\n\n this.inputArea.appendChild(wrapper);\n\n this.sendBtn = document.createElement('button');\n this.sendBtn.className = `${this.prefix}-send-btn`;\n this.sendBtn.innerHTML = SEND_ICON_SVG;\n this.sendBtn.title = '发送';\n this.sendBtn.type = 'button';\n this.sendBtn.disabled = true;\n this.inputArea.appendChild(this.sendBtn);\n\n this.root.appendChild(this.inputArea);\n }\n\n private renderToolbar(wrapper: HTMLElement): void {\n const toolbar = this.config.toolbarConfig ?? {};\n this.toolbarEl = this.el('div', `${this.prefix}-toolbar`);\n\n if (toolbar.image !== false) {\n const imageBtn = this.createToolbarBtn(ICON_IMAGE, '插入图片');\n imageBtn.addEventListener('click', () => this.openFilePicker('image/*'));\n this.toolbarEl.appendChild(imageBtn);\n }\n\n if (toolbar.file !== false) {\n const fileBtn = this.createToolbarBtn(ICON_PAPERCLIP, '插入文件');\n fileBtn.addEventListener('click', () => this.openFilePicker('*/*'));\n this.toolbarEl.appendChild(fileBtn);\n }\n\n if (toolbar.voice !== false) {\n this.voiceBtn = this.createToolbarBtn(ICON_MIC, '语音输入');\n this.voiceBtn.addEventListener('click', () => this.toggleVoice());\n this.toolbarEl.appendChild(this.voiceBtn);\n }\n\n wrapper.insertBefore(this.toolbarEl, wrapper.firstChild);\n }\n\n private createToolbarBtn(iconSvg: string, title: string): HTMLButtonElement {\n const btn = document.createElement('button');\n btn.className = `${this.prefix}-toolbar-btn`;\n btn.innerHTML = iconSvg;\n btn.title = title;\n btn.type = 'button';\n return btn;\n }\n\n private openFilePicker(accept: string): void {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = accept;\n input.multiple = true;\n input.style.display = 'none';\n input.addEventListener('change', () => {\n const files = Array.from(input.files ?? []);\n input.remove();\n if (!files.length) return;\n const isImage = accept.startsWith('image');\n this.emitter.emit(isImage ? 'toolbar-image' : 'toolbar-file', files);\n if (isImage) {\n this.config.onImageUpload?.(files);\n } else {\n this.config.onFileUpload?.(files);\n }\n });\n document.body.appendChild(input);\n input.click();\n }\n\n private toggleVoice(): void {\n if (this.isRecording) {\n this.stopVoice();\n } else {\n this.startVoice();\n }\n }\n\n private startVoice(): void {\n if (typeof window === 'undefined') return;\n const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;\n if (!SpeechRecognition) {\n this.emitter.emit('toolbar-voice-unsupported');\n this.config.onVoiceUnsupported?.();\n return;\n }\n\n try {\n this.lastVoiceTranscript = '';\n this.recognition = new SpeechRecognition();\n this.recognition.continuous = false;\n this.recognition.interimResults = true;\n this.recognition.lang = this.config.voiceLang ?? 'zh-CN';\n\n this.recognition.onresult = (event: any) => {\n let transcript = '';\n for (let i = 0; i < event.results.length; i++) {\n transcript += event.results[i][0].transcript;\n }\n this.lastVoiceTranscript = transcript;\n const lastResult = event.results[event.results.length - 1];\n if (lastResult?.isFinal && this.textareaEl) {\n this.insertTextAtCursor(transcript);\n }\n };\n\n this.recognition.onend = () => {\n this.finishVoice();\n this.emitter.emit('toolbar-voice-end', this.lastVoiceTranscript);\n this.config.onVoiceEnd?.(this.lastVoiceTranscript);\n };\n\n this.recognition.onerror = (error: unknown) => {\n this.finishVoice();\n this.emitter.emit('toolbar-voice-error', error);\n this.config.onVoiceError?.(error);\n };\n\n this.isRecording = true;\n this.voiceBtn?.classList.add(`${this.prefix}-toolbar-btn--active`);\n if (this.voiceBtn) this.voiceBtn.innerHTML = ICON_MIC_OFF;\n this.recognition.start();\n this.emitter.emit('toolbar-voice-start');\n this.config.onVoiceStart?.();\n } catch (error) {\n this.finishVoice();\n this.emitter.emit('toolbar-voice-error', error);\n this.config.onVoiceError?.(error);\n }\n }\n\n private stopVoice(): void {\n if (!this.recognition) return;\n const recognition = this.recognition;\n this.recognition = null;\n try {\n recognition.stop();\n } catch {\n this.finishVoice();\n }\n }\n\n private finishVoice(): void {\n this.isRecording = false;\n this.recognition = null;\n this.voiceBtn?.classList.remove(`${this.prefix}-toolbar-btn--active`);\n if (this.voiceBtn) this.voiceBtn.innerHTML = ICON_MIC;\n }\n\n private setupEventListeners(): void {\n if (this.textareaEl) {\n this.textareaEl.addEventListener('input', () => {\n const limited = this.limitInput(this.textareaEl?.value ?? '');\n if (this.textareaEl && limited !== this.textareaEl.value) {\n this.textareaEl.value = limited;\n }\n this.autoResize();\n this.updateSendButtonState();\n this.config.onInputChange?.(this.textareaEl?.value ?? '');\n if (this.config.enableMention && this.textareaEl) {\n this.mentionEngine.handleInput(this.textareaEl.value, this.textareaEl.selectionStart);\n }\n });\n\n this.textareaEl.addEventListener('keydown', (event) => {\n const mentionState = this.mentionEngine.getState();\n\n if (mentionState.active) {\n if (event.key === 'ArrowUp') {\n event.preventDefault();\n this.mentionEngine.moveUp();\n } else if (event.key === 'ArrowDown') {\n event.preventDefault();\n this.mentionEngine.moveDown();\n } else if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault();\n const member = this.mentionEngine.selectCurrent();\n if (member) this.insertMention(member);\n } else if (event.key === 'Escape') {\n event.preventDefault();\n this.mentionEngine.deactivate();\n }\n return;\n }\n\n if (this.config.enterToSend !== false && event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault();\n this.handleSend();\n }\n });\n\n this.textareaEl.addEventListener('paste', (event) => {\n const items = event.clipboardData?.items;\n if (!items) return;\n const files: File[] = [];\n for (let i = 0; i < items.length; i++) {\n if (items[i].kind === 'file') {\n const file = items[i].getAsFile();\n if (file) files.push(file);\n }\n }\n if (files.length > 0) {\n event.preventDefault();\n this.emitter.emit('toolbar-paste', files);\n this.config.onPaste?.(files);\n }\n });\n }\n\n if (this.inputArea) {\n const wrapper = this.inputArea.querySelector(`.${this.prefix}-input-wrapper`) as HTMLElement | null;\n if (wrapper) this.setupDropEvents(wrapper);\n }\n\n this.sendBtn?.addEventListener('click', () => {\n this.handleSend();\n });\n\n this.emitter.on('mention-filter', (state: { filtered: MentionItem[]; activeIndex: number }) => {\n this.renderMentionPanel(state.filtered, state.activeIndex);\n });\n this.emitter.on('mention-active-change', (index: number) => {\n const items = this.mentionPanel?.querySelectorAll(`.${this.prefix}-mention-item`);\n items?.forEach((el, i) => {\n el.classList.toggle(`${this.prefix}-mention-item--active`, i === index);\n el.setAttribute('aria-selected', String(i === index));\n });\n });\n this.emitter.on('mention-deactivate', () => {\n this.hideMentionPanel();\n });\n }\n\n private setupDropEvents(wrapper: HTMLElement): void {\n let dragCounter = 0;\n wrapper.addEventListener('dragenter', (event) => {\n event.preventDefault();\n dragCounter++;\n if (this.dropOverlay) this.dropOverlay.style.display = 'flex';\n });\n wrapper.addEventListener('dragleave', (event) => {\n event.preventDefault();\n dragCounter--;\n if (dragCounter <= 0) {\n dragCounter = 0;\n if (this.dropOverlay) this.dropOverlay.style.display = 'none';\n }\n });\n wrapper.addEventListener('dragover', (event) => {\n event.preventDefault();\n });\n wrapper.addEventListener('drop', (event) => {\n event.preventDefault();\n dragCounter = 0;\n if (this.dropOverlay) this.dropOverlay.style.display = 'none';\n const files = Array.from(event.dataTransfer?.files ?? []);\n if (files.length > 0) {\n this.emitter.emit('toolbar-drop', files);\n this.config.onDrop?.(files);\n }\n });\n }\n\n private handleSend(): void {\n const rawContent = this.textareaEl?.value ?? '';\n const content = rawContent.trim();\n if (!content) return;\n\n const mentions = this.mentionEngine.parseMentions(content);\n this.emitter.emit('send', content, mentions);\n this.config.onSend?.(content, mentions);\n\n if (this.config.clearInputOnSend !== false) {\n this.clearInput();\n }\n this.mentionEngine.resetSelected();\n }\n\n private insertMention(member: MentionItem): void {\n if (!this.textareaEl) return;\n const state = this.mentionEngine.getState();\n const text = this.textareaEl.value;\n const pos = this.textareaEl.selectionStart;\n const insertText = this.mentionEngine.getInsertText(member);\n\n const before = text.slice(0, state.triggerPosition);\n const after = text.slice(pos);\n this.textareaEl.value = this.limitInput(before + insertText + after);\n const newPos = Math.min(before.length + insertText.length, this.textareaEl.value.length);\n this.textareaEl.setSelectionRange(newPos, newPos);\n this.textareaEl.focus();\n this.autoResize();\n this.updateSendButtonState();\n this.config.onInputChange?.(this.textareaEl.value);\n }\n\n private insertTextAtCursor(text: string): void {\n if (!this.textareaEl) return;\n const current = this.textareaEl.value;\n const start = this.textareaEl.selectionStart;\n const end = this.textareaEl.selectionEnd;\n this.textareaEl.value = this.limitInput(current.slice(0, start) + text + current.slice(end));\n const nextCursor = Math.min(start + text.length, this.textareaEl.value.length);\n this.textareaEl.setSelectionRange(nextCursor, nextCursor);\n this.autoResize();\n this.updateSendButtonState();\n this.config.onInputChange?.(this.textareaEl.value);\n }\n\n private autoResize(): void {\n if (!this.textareaEl) return;\n this.textareaEl.style.height = 'auto';\n const maxHeight = this.getCssSizeValue('--ldesign-chat-textarea-max-height', 120);\n this.textareaEl.style.height = `${Math.min(this.textareaEl.scrollHeight, maxHeight)}px`;\n }\n\n private updateSendButtonState(): void {\n if (!this.sendBtn) return;\n this.sendBtn.disabled = !this.getInputContent().trim();\n }\n\n private renderLoadMoreButton(): void {\n this.loadMoreEl = this.el('div', `${this.prefix}-load-more`);\n this.loadMoreEl.textContent = this.config.loadMoreText ?? '加载更多';\n this.loadMoreEl.addEventListener('click', () => {\n if (this.loadingMore) return;\n this.setLoadingMore(true);\n this.emitter.emit('load-more');\n Promise.resolve(this.config.onLoadMore?.()).finally(() => {\n this.setLoadingMore(false);\n });\n });\n this.messageListEl.appendChild(this.loadMoreEl);\n }\n\n private renderTimeDivider(timestamp: number): void {\n this.messageListEl.appendChild(this.createTimeDivider(timestamp));\n }\n\n private createTimeDivider(timestamp: number): HTMLElement {\n const divider = this.el('div', `${this.prefix}-time-divider`);\n const span = document.createElement('span');\n span.textContent = this.formatTime(timestamp);\n divider.appendChild(span);\n return divider;\n }\n\n private renderMessageRow(message: ChatMessage): void {\n this.messageListEl.appendChild(this.createMessageRow(message));\n }\n\n private createMessageRow(message: ChatMessage): HTMLElement {\n const p = this.prefix;\n\n if (message.type === 'system') {\n const row = this.el('div', cx(`${p}-message-row`, `${p}-message-row--system`));\n row.dataset.msgId = message.id;\n row.dataset.timestamp = String(message.timestamp);\n const system = this.el('div', `${p}-system-message`);\n system.textContent = message.content ?? '';\n row.appendChild(system);\n return row;\n }\n\n const isOutgoing = message.direction === 'outgoing';\n const row = this.el('div', cx(`${p}-message-row`, isOutgoing && `${p}-message-row--outgoing`));\n row.dataset.msgId = message.id;\n row.dataset.timestamp = String(message.timestamp);\n\n if (this.config.showAvatar !== false) {\n row.appendChild(this.createAvatar(message.sender.nickname, message.sender.avatar));\n }\n\n const body = this.el('div', `${p}-message-body`);\n if (this.config.showNickname !== false && !isOutgoing) {\n const nickname = this.el('div', `${p}-nickname`);\n nickname.textContent = message.sender.nickname;\n body.appendChild(nickname);\n }\n\n body.appendChild(this.createBubble(message, isOutgoing));\n body.appendChild(this.createMessageFooter(message, isOutgoing));\n row.appendChild(body);\n\n row.addEventListener('click', () => {\n this.emitter.emit('message-click', message);\n this.config.onMessageClick?.(message);\n });\n\n return row;\n }\n\n private createAvatar(name: string, avatarUrl?: string): HTMLElement {\n const avatar = this.el('div', `${this.prefix}-avatar`);\n if (avatarUrl) {\n const img = document.createElement('img');\n img.src = avatarUrl;\n img.alt = name;\n avatar.appendChild(img);\n } else {\n avatar.textContent = name[0]?.toUpperCase() ?? '';\n }\n return avatar;\n }\n\n private createMessageFooter(message: ChatMessage, isOutgoing: boolean): HTMLElement {\n const footer = this.el('div', `${this.prefix}-message-footer`);\n if (this.config.showTimestamp !== false) {\n const time = this.el('span', `${this.prefix}-timestamp`);\n time.textContent = this.formatShortTime(message.timestamp);\n footer.appendChild(time);\n }\n if (this.config.showStatus !== false && isOutgoing && message.status) {\n const status = this.el('span', cx(`${this.prefix}-status`, `${this.prefix}-status--${message.status}`));\n status.textContent = STATUS_MAP[message.status] ?? '';\n if (message.status === 'failed') {\n status.addEventListener('click', (event) => {\n event.stopPropagation();\n this.emitter.emit('message-retry', message);\n });\n }\n footer.appendChild(status);\n }\n return footer;\n }\n\n private createBubble(message: ChatMessage, isOutgoing: boolean): HTMLElement {\n const p = this.prefix;\n const dirClass = isOutgoing ? `${p}-bubble--outgoing` : `${p}-bubble--incoming`;\n\n if (message.revoked) {\n const bubble = this.el('div', cx(`${p}-bubble`, dirClass, `${p}-bubble--revoked`));\n bubble.textContent = '消息已撤回';\n return bubble;\n }\n\n switch (message.type) {\n case 'image': {\n const bubble = this.el('div', cx(`${p}-bubble`, dirClass, `${p}-bubble--image`));\n if (message.image) {\n const img = document.createElement('img');\n img.src = message.image.thumbUrl ?? message.image.url;\n img.alt = message.content ?? '';\n if (message.image.width) img.width = Math.min(message.image.width, 240);\n bubble.appendChild(img);\n }\n return bubble;\n }\n\n case 'file': {\n const bubble = this.el('div', cx(`${p}-bubble`, dirClass, `${p}-bubble--file`));\n const icon = this.el('div', `${p}-file-icon`);\n icon.innerHTML = FILE_ICON;\n bubble.appendChild(icon);\n\n const info = this.el('div', `${p}-file-info`);\n const name = this.el('div', `${p}-file-name`);\n name.textContent = message.file?.name ?? 'file';\n info.appendChild(name);\n\n if (message.file?.size) {\n const size = this.el('div', `${p}-file-size`);\n size.textContent = this.formatFileSize(message.file.size);\n info.appendChild(size);\n }\n bubble.appendChild(info);\n return bubble;\n }\n\n case 'custom': {\n const bubble = this.el('div', cx(`${p}-bubble`, dirClass));\n if (message.customHtml) {\n bubble.innerHTML = message.customHtml;\n } else {\n bubble.textContent = message.content ?? '';\n }\n return bubble;\n }\n\n default: {\n const bubble = this.el('div', cx(`${p}-bubble`, dirClass, message.streaming && `${p}-bubble--streaming`));\n bubble.innerHTML = this.renderTextContent(message.content ?? '', message.mentions);\n return bubble;\n }\n }\n }\n\n private renderTextContent(content: string, mentions?: MentionItem[]): string {\n let html = this.escapeHtml(content);\n\n if (mentions?.length) {\n for (const mention of mentions) {\n const tag = `<span class=\"${this.prefix}-mention-tag\" data-mention-id=\"${this.escapeAttr(mention.id)}\">@${this.escapeHtml(mention.name)}</span>`;\n html = html.replace(new RegExp(`@${this.escapeRegex(mention.name)}`, 'g'), tag);\n }\n }\n\n return html.replace(\n /(https?:\\/\\/[^\\s<]+)/g,\n `<a class=\"${this.prefix}-link\" href=\"$1\" target=\"_blank\" rel=\"noopener noreferrer\">$1</a>`,\n );\n }\n\n private renderEmpty(): void {\n if (this.messageListEl.querySelector(`.${this.prefix}-empty`)) return;\n const empty = this.el('div', `${this.prefix}-empty`);\n empty.textContent = '暂无消息';\n this.messageListEl.appendChild(empty);\n }\n\n private removeEmpty(): void {\n this.messageListEl.querySelector(`.${this.prefix}-empty`)?.remove();\n }\n\n private el(tag: string, className: string): HTMLElement {\n const el = document.createElement(tag);\n el.className = className;\n return el;\n }\n\n private getMessageElements(): HTMLElement[] {\n return Array.from(this.messageListEl.querySelectorAll<HTMLElement>(`.${this.prefix}-message-row`));\n }\n\n private formatTime(timestamp: number): string {\n if (this.config.formatTime) return this.config.formatTime(timestamp);\n const date = new Date(timestamp);\n const now = new Date();\n const isToday = date.toDateString() === now.toDateString();\n if (isToday) {\n return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });\n }\n return date.toLocaleDateString('zh-CN', {\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n });\n }\n\n private formatShortTime(timestamp: number): string {\n return new Date(timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });\n }\n\n private formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n }\n\n private limitInput(content: string): string {\n const max = this.config.maxInputLength;\n if (!max || max <= 0) return content;\n return content.slice(0, max);\n }\n\n private getCssSizeValue(name: string, fallback: number): number {\n if (typeof window === 'undefined' || !this.root) return fallback;\n const value = window.getComputedStyle(this.root).getPropertyValue(name).trim();\n const parsed = Number.parseFloat(value);\n return Number.isFinite(parsed) ? parsed : fallback;\n }\n\n private escapeHtml(str: string): string {\n const div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n }\n\n private escapeAttr(str: string): string {\n return str\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n }\n\n private escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n }\n}\n"],"mappings":";;;;;;;;;;;;AAKA,MAAM,gBAAgB;AACtB,MAAM,aAAa;AACnB,MAAM,iBAAiB;AACvB,MAAM,WAAW;AACjB,MAAM,eAAe;AACrB,MAAM,YAAY;AAElB,MAAM,aAA4C;CAChD,SAAS;CACT,MAAM;CACN,WAAW;CACX,MAAM;CACN,QAAQ;AACV;AAEO,IAAM,cAAN,MAAkB;CAsBvB,YACE,WACA,QACA,SACA,eACA;EAlBF,KAAQ,YAAgC;EACxC,KAAQ,aAAyC;EACjD,KAAQ,UAAoC;EAC5C,KAAQ,eAAmC;EAC3C,KAAQ,aAAiC;EACzC,KAAQ,YAAgC;EACxC,KAAQ,cAAkC;EAC1C,KAAQ,WAA+B;EACvC,KAAQ,cAAc;EACtB,KAAQ,cAAc;EACtB,KAAQ,cAAmB;EAC3B,KAAQ,sBAAsB;EAQ5B,KAAK,YAAY;EACjB,KAAK,SAAS;EACd,KAAK,UAAU;EACf,KAAK,gBAAgB;EACrB,KAAK,SAAS,YAAA,qBAAqB,OAAO,eAAA,cAAmC;CAC/E;CAEA,OAAa;EACX,KAAK,UAAU,YAAY;EAC3B,KAAK,OAAO,KAAK,GAAG,OAAO,GAAG,KAAK,OAAM,WAAY;EACrD,KAAK,gBAAgB,KAAK,GAAG,OAAO,GAAG,KAAK,OAAM,cAAe;EACjE,KAAK,KAAK,YAAY,KAAK,aAAa;EAExC,IAAI,KAAK,OAAO,cAAc,OAC5B,KAAK,gBAAgB;EAGvB,KAAK,UAAU,YAAY,KAAK,IAAI;EACpC,KAAK,oBAAoB;CAC3B;CAEA,qBAAqB,IAAY,SAAuB;EAEtD,MAAM,SADQ,KAAK,cAAc,cAAc,iBAAiB,KAAK,WAAW,EAAE,EAAC,GACpE,CAAA,EAAO,cAAc,IAAI,KAAK,OAAM,QAAS;EAC5D,IAAI,CAAC,QAAQ;EACb,OAAO,YAAY,KAAK,kBAAkB,OAAO;CACnD;CAEA,eAAe,UAA+B;EAC5C,KAAK,cAAc,YAAY;EAE/B,IAAI,KAAK,OAAO,gBACd,KAAK,qBAAqB;EAG5B,IAAI,SAAS,WAAW,GAAG;GACzB,KAAK,YAAY;GACjB;EACF;EAEA,IAAI,gBAAgB;EACpB,MAAM,WAAW,KAAK,OAAO,qBAAqB;EAClD,KAAA,MAAW,WAAW,UAAU;GAC9B,IAAI,KAAK,OAAO,kBAAkB,SAAS,QAAQ,YAAY,gBAAgB,UAAU;IACvF,KAAK,kBAAkB,QAAQ,SAAS;IACxC,gBAAgB,QAAQ;GAC1B;GACA,KAAK,iBAAiB,OAAO;EAC/B;CACF;CAEA,cAAc,SAA4B;EACxC,KAAK,YAAY;EACjB,MAAM,aAAa,KAAK,mBAAmB;EAC3C,MAAM,WAAW,WAAW,SAAS,IACjC,OAAO,SAAS,WAAW,WAAW,SAAS,EAAC,CAAE,QAAQ,aAAa,KAAK,EAAE,IAC9E;EACJ,MAAM,WAAW,KAAK,OAAO,qBAAqB;EAElD,IAAI,KAAK,OAAO,kBAAkB,SAAS,QAAQ,YAAY,WAAW,UACxE,KAAK,kBAAkB,QAAQ,SAAS;EAG1C,KAAK,iBAAiB,OAAO;CAC/B;CAEA,gBAAgB,UAA+B;EAC7C,IAAI,CAAC,SAAS,QAAQ;EACtB,KAAK,YAAY;EAEjB,MAAM,eAAe,KAAK,cAAc;EACxC,MAAM,YAAY,KAAK,cAAc;EACrC,MAAM,WAAW,SAAS,uBAAuB;EACjD,IAAI,gBAAgB;EACpB,MAAM,WAAW,KAAK,OAAO,qBAAqB;EAElD,KAAA,MAAW,WAAW,UAAU;GAC9B,IAAI,KAAK,OAAO,kBAAkB,SAAS,QAAQ,YAAY,gBAAgB,UAAU;IACvF,SAAS,YAAY,KAAK,kBAAkB,QAAQ,SAAS,CAAC;IAC9D,gBAAgB,QAAQ;GAC1B;GACA,SAAS,YAAY,KAAK,iBAAiB,OAAO,CAAC;EACrD;EAEA,MAAM,eAAe,KAAK,cAAc,cAAc,IAAI,KAAK,OAAM,iBAAkB,KAAK,OAAM,cAAe;EACjH,IAAI,cACF,aAAa,OAAO,QAAQ;OAC9B,IAAW,KAAK,YACd,KAAK,WAAW,MAAM,QAAQ;OAE9B,KAAK,cAAc,YAAY,QAAQ;EAGzC,KAAK,cAAc,YAAY,aAAa,KAAK,cAAc,eAAe;CAChF;CAEA,cAAc,IAAY,SAA4B;EACpD,MAAM,QAAQ,KAAK,cAAc,cAAc,iBAAiB,KAAK,WAAW,EAAE,EAAC,GAAI;EACvF,IAAI,CAAC,OAAO;EACZ,MAAM,YAAY,KAAK,iBAAiB,OAAO,CAAC;CAClD;CAEA,cAAc,IAAkB;EAC9B,KAAK,cAAc,cAAc,iBAAiB,KAAK,WAAW,EAAE,EAAC,GAAI,CAAA,EAAG,OAAO;EACnF,IAAI,KAAK,mBAAmB,CAAA,CAAE,WAAW,GACvC,KAAK,YAAY;CAErB;CAEA,eAAe,SAAS,MAAY;EAClC,IAAI,OAAO,KAAK,cAAc,aAAa,YAAY;GACrD,KAAK,cAAc,SAAS;IAC1B,KAAK,KAAK,cAAc;IACxB,UAAU,SAAS,WAAW;GAChC,CAAC;GACD;EACF;EACA,KAAK,cAAc,YAAY,KAAK,cAAc;CACpD;CAEA,WAAW,YAAY,GAAY;EAEjC,OADiB,KAAK,cAAc,eAAe,KAAK,cAAc,YAAY,KAAK,cAAc,gBAClF;CACrB;CAEA,eAAe,SAAwB;EACrC,KAAK,cAAc;EACnB,IAAI,CAAC,KAAK,YAAY;EACtB,KAAK,WAAW,cAAc,UAAU,WAAY,KAAK,OAAO,gBAAgB;EAChF,KAAK,WAAW,UAAU,OAAO,GAAG,KAAK,OAAM,sBAAuB,OAAO;CAC/E;CAEA,aAAmB;EACjB,KAAK,YAAY,MAAM;CACzB;CAEA,gBAAgB,SAAuB;EACrC,IAAI,CAAC,KAAK,YAAY;EACtB,KAAK,WAAW,QAAQ,KAAK,WAAW,OAAO;EAC/C,KAAK,WAAW;EAChB,KAAK,sBAAsB;EAC3B,KAAK,OAAO,gBAAgB,KAAK,WAAW,KAAK;CACnD;CAEA,kBAA0B;EACxB,OAAO,KAAK,YAAY,SAAS;CACnC;CAEA,aAAmB;EACjB,KAAK,gBAAgB,EAAE;CACzB;CAEA,mBAAmB,SAAwB,aAA2B;EACpE,IAAI,CAAC,KAAK,cAAc;EAExB,IAAI,QAAQ,WAAW,GAAG;GACxB,KAAK,aAAa,MAAM,UAAU;GAClC;EACF;EAEA,KAAK,aAAa,MAAM,UAAU;EAClC,KAAK,aAAa,YAAY;EAE9B,QAAQ,SAAS,QAAQ,UAAU;GACjC,MAAM,OAAO,KAAK,GAChB,OACA,YAAA,GAAG,GAAG,KAAK,OAAM,gBAAiB,UAAU,eAAe,GAAG,KAAK,OAAM,sBAAuB,CAClG;GACA,KAAK,aAAa,QAAQ,QAAQ;GAClC,KAAK,aAAa,iBAAiB,OAAO,UAAU,WAAW,CAAC;GAEhE,MAAM,SAAS,KAAK,GAAG,OAAO,GAAG,KAAK,OAAM,qBAAsB;GAClE,IAAI,OAAO,QAAQ;IACjB,MAAM,MAAM,SAAS,cAAc,KAAK;IACxC,IAAI,MAAM,OAAO;IACjB,IAAI,MAAM;IACV,OAAO,YAAY,GAAG;GACxB,OACE,OAAO,cAAc,OAAO,KAAK,EAAC,EAAG,YAAY,KAAK;GAExD,KAAK,YAAY,MAAM;GAEvB,MAAM,OAAO,KAAK,GAAG,QAAQ,GAAG,KAAK,OAAM,mBAAoB;GAC/D,KAAK,cAAc,OAAO;GAC1B,KAAK,YAAY,IAAI;GAErB,KAAK,iBAAiB,eAAe;IACnC,KAAK,cAAc,OAAO,KAAK;GACjC,CAAC;GAED,KAAK,cAAc,YAAY,IAAI;EACrC,CAAC;CACH;CAEA,mBAAyB;EACvB,IAAI,KAAK,cACP,KAAK,aAAa,MAAM,UAAU;CAEtC;CAEA,UAAgB;EACd,KAAK,UAAU;EACf,KAAK,UAAU,YAAY;CAC7B;CAEQ,kBAAwB;EAC9B,KAAK,YAAY,KAAK,GAAG,OAAO,GAAG,KAAK,OAAM,YAAa;EAC3D,MAAM,UAAU,KAAK,GAAG,OAAO,GAAG,KAAK,OAAM,eAAgB;EAE7D,KAAK,eAAe,KAAK,GAAG,OAAO,GAAG,KAAK,OAAM,eAAgB;EACjE,KAAK,aAAa,MAAM,UAAU;EAClC,KAAK,aAAa,aAAa,QAAQ,SAAS;EAChD,QAAQ,YAAY,KAAK,YAAY;EAErC,IAAI,KAAK,OAAO,kBAAkB,OAChC,KAAK,cAAc,OAAO;EAG5B,KAAK,aAAa,SAAS,cAAc,UAAU;EACnD,KAAK,WAAW,YAAY,GAAG,KAAK,OAAM;EAC1C,KAAK,WAAW,cAAc,KAAK,OAAO,oBAAoB;EAC9D,KAAK,WAAW,OAAO;EACvB,IAAI,KAAK,OAAO,kBAAkB,KAAK,OAAO,iBAAiB,GAC7D,KAAK,WAAW,YAAY,KAAK,OAAO;EAE1C,QAAQ,YAAY,KAAK,UAAU;EAEnC,KAAK,cAAc,KAAK,GAAG,OAAO,GAAG,KAAK,OAAM,cAAe;EAC/D,KAAK,YAAY,YAAY;EAC7B,KAAK,YAAY,MAAM,UAAU;EACjC,QAAQ,YAAY,KAAK,WAAW;EAEpC,KAAK,UAAU,YAAY,OAAO;EAElC,KAAK,UAAU,SAAS,cAAc,QAAQ;EAC9C,KAAK,QAAQ,YAAY,GAAG,KAAK,OAAM;EACvC,KAAK,QAAQ,YAAY;EACzB,KAAK,QAAQ,QAAQ;EACrB,KAAK,QAAQ,OAAO;EACpB,KAAK,QAAQ,WAAW;EACxB,KAAK,UAAU,YAAY,KAAK,OAAO;EAEvC,KAAK,KAAK,YAAY,KAAK,SAAS;CACtC;CAEQ,cAAc,SAA4B;EAChD,MAAM,UAAU,KAAK,OAAO,iBAAiB,CAAC;EAC9C,KAAK,YAAY,KAAK,GAAG,OAAO,GAAG,KAAK,OAAM,SAAU;EAExD,IAAI,QAAQ,UAAU,OAAO;GAC3B,MAAM,WAAW,KAAK,iBAAiB,YAAY,MAAM;GACzD,SAAS,iBAAiB,eAAe,KAAK,eAAe,SAAS,CAAC;GACvE,KAAK,UAAU,YAAY,QAAQ;EACrC;EAEA,IAAI,QAAQ,SAAS,OAAO;GAC1B,MAAM,UAAU,KAAK,iBAAiB,gBAAgB,MAAM;GAC5D,QAAQ,iBAAiB,eAAe,KAAK,eAAe,KAAK,CAAC;GAClE,KAAK,UAAU,YAAY,OAAO;EACpC;EAEA,IAAI,QAAQ,UAAU,OAAO;GAC3B,KAAK,WAAW,KAAK,iBAAiB,UAAU,MAAM;GACtD,KAAK,SAAS,iBAAiB,eAAe,KAAK,YAAY,CAAC;GAChE,KAAK,UAAU,YAAY,KAAK,QAAQ;EAC1C;EAEA,QAAQ,aAAa,KAAK,WAAW,QAAQ,UAAU;CACzD;CAEQ,iBAAiB,SAAiB,OAAkC;EAC1E,MAAM,MAAM,SAAS,cAAc,QAAQ;EAC3C,IAAI,YAAY,GAAG,KAAK,OAAM;EAC9B,IAAI,YAAY;EAChB,IAAI,QAAQ;EACZ,IAAI,OAAO;EACX,OAAO;CACT;CAEQ,eAAe,QAAsB;EAC3C,MAAM,QAAQ,SAAS,cAAc,OAAO;EAC5C,MAAM,OAAO;EACb,MAAM,SAAS;EACf,MAAM,WAAW;EACjB,MAAM,MAAM,UAAU;EACtB,MAAM,iBAAiB,gBAAgB;GACrC,MAAM,QAAQ,MAAM,KAAK,MAAM,SAAS,CAAC,CAAC;GAC1C,MAAM,OAAO;GACb,IAAI,CAAC,MAAM,QAAQ;GACnB,MAAM,UAAU,OAAO,WAAW,OAAO;GACzC,KAAK,QAAQ,KAAK,UAAU,kBAAkB,gBAAgB,KAAK;GACnE,IAAI,SACF,KAAK,OAAO,gBAAgB,KAAK;QAEjC,KAAK,OAAO,eAAe,KAAK;EAEpC,CAAC;EACD,SAAS,KAAK,YAAY,KAAK;EAC/B,MAAM,MAAM;CACd;CAEQ,cAAoB;EAC1B,IAAI,KAAK,aACP,KAAK,UAAU;OAEf,KAAK,WAAW;CAEpB;CAEQ,aAAmB;EACzB,IAAI,OAAO,WAAW,aAAa;EACnC,MAAM,oBAAqB,OAAe,qBAAsB,OAAe;EAC/E,IAAI,CAAC,mBAAmB;GACtB,KAAK,QAAQ,KAAK,2BAA2B;GAC7C,KAAK,OAAO,qBAAqB;GACjC;EACF;EAEA,IAAI;GACF,KAAK,sBAAsB;GAC3B,KAAK,cAAc,IAAI,kBAAkB;GACzC,KAAK,YAAY,aAAa;GAC9B,KAAK,YAAY,iBAAiB;GAClC,KAAK,YAAY,OAAO,KAAK,OAAO,aAAa;GAEjD,KAAK,YAAY,YAAY,UAAe;IAC1C,IAAI,aAAa;IACjB,KAAA,IAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,QAAQ,KACxC,cAAc,MAAM,QAAQ,EAAC,CAAE,EAAC,CAAE;IAEpC,KAAK,sBAAsB;IAE3B,IADmB,MAAM,QAAQ,MAAM,QAAQ,SAAS,EACpD,EAAY,WAAW,KAAK,YAC9B,KAAK,mBAAmB,UAAU;GAEtC;GAEA,KAAK,YAAY,cAAc;IAC7B,KAAK,YAAY;IACjB,KAAK,QAAQ,KAAK,qBAAqB,KAAK,mBAAmB;IAC/D,KAAK,OAAO,aAAa,KAAK,mBAAmB;GACnD;GAEA,KAAK,YAAY,WAAW,UAAmB;IAC7C,KAAK,YAAY;IACjB,KAAK,QAAQ,KAAK,uBAAuB,KAAK;IAC9C,KAAK,OAAO,eAAe,KAAK;GAClC;GAEA,KAAK,cAAc;GACnB,KAAK,UAAU,UAAU,IAAI,GAAG,KAAK,OAAM,qBAAsB;GACjE,IAAI,KAAK,UAAU,KAAK,SAAS,YAAY;GAC7C,KAAK,YAAY,MAAM;GACvB,KAAK,QAAQ,KAAK,qBAAqB;GACvC,KAAK,OAAO,eAAe;EAC7B,SAAS,OAAO;GACd,KAAK,YAAY;GACjB,KAAK,QAAQ,KAAK,uBAAuB,KAAK;GAC9C,KAAK,OAAO,eAAe,KAAK;EAClC;CACF;CAEQ,YAAkB;EACxB,IAAI,CAAC,KAAK,aAAa;EACvB,MAAM,cAAc,KAAK;EACzB,KAAK,cAAc;EACnB,IAAI;GACF,YAAY,KAAK;EACnB,QAAQ;GACN,KAAK,YAAY;EACnB;CACF;CAEQ,cAAoB;EAC1B,KAAK,cAAc;EACnB,KAAK,cAAc;EACnB,KAAK,UAAU,UAAU,OAAO,GAAG,KAAK,OAAM,qBAAsB;EACpE,IAAI,KAAK,UAAU,KAAK,SAAS,YAAY;CAC/C;CAEQ,sBAA4B;EAClC,IAAI,KAAK,YAAY;GACnB,KAAK,WAAW,iBAAiB,eAAe;IAC9C,MAAM,UAAU,KAAK,WAAW,KAAK,YAAY,SAAS,EAAE;IAC5D,IAAI,KAAK,cAAc,YAAY,KAAK,WAAW,OACjD,KAAK,WAAW,QAAQ;IAE1B,KAAK,WAAW;IAChB,KAAK,sBAAsB;IAC3B,KAAK,OAAO,gBAAgB,KAAK,YAAY,SAAS,EAAE;IACxD,IAAI,KAAK,OAAO,iBAAiB,KAAK,YACpC,KAAK,cAAc,YAAY,KAAK,WAAW,OAAO,KAAK,WAAW,cAAc;GAExF,CAAC;GAED,KAAK,WAAW,iBAAiB,YAAY,UAAU;IAGrD,IAFqB,KAAK,cAAc,SAEpC,CAAA,CAAa,QAAQ;KACvB,IAAI,MAAM,QAAQ,WAAW;MAC3B,MAAM,eAAe;MACrB,KAAK,cAAc,OAAO;KAC5B,OAAA,IAAW,MAAM,QAAQ,aAAa;MACpC,MAAM,eAAe;MACrB,KAAK,cAAc,SAAS;KAC9B,OAAA,IAAW,MAAM,QAAQ,WAAW,CAAC,MAAM,UAAU;MACnD,MAAM,eAAe;MACrB,MAAM,SAAS,KAAK,cAAc,cAAc;MAChD,IAAI,QAAQ,KAAK,cAAc,MAAM;KACvC,OAAA,IAAW,MAAM,QAAQ,UAAU;MACjC,MAAM,eAAe;MACrB,KAAK,cAAc,WAAW;KAChC;KACA;IACF;IAEA,IAAI,KAAK,OAAO,gBAAgB,SAAS,MAAM,QAAQ,WAAW,CAAC,MAAM,UAAU;KACjF,MAAM,eAAe;KACrB,KAAK,WAAW;IAClB;GACF,CAAC;GAED,KAAK,WAAW,iBAAiB,UAAU,UAAU;IACnD,MAAM,QAAQ,MAAM,eAAe;IACnC,IAAI,CAAC,OAAO;IACZ,MAAM,QAAgB,CAAC;IACvB,KAAA,IAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAChC,IAAI,MAAM,EAAC,CAAE,SAAS,QAAQ;KAC5B,MAAM,OAAO,MAAM,EAAC,CAAE,UAAU;KAChC,IAAI,MAAM,MAAM,KAAK,IAAI;IAC3B;IAEF,IAAI,MAAM,SAAS,GAAG;KACpB,MAAM,eAAe;KACrB,KAAK,QAAQ,KAAK,iBAAiB,KAAK;KACxC,KAAK,OAAO,UAAU,KAAK;IAC7B;GACF,CAAC;EACH;EAEA,IAAI,KAAK,WAAW;GAClB,MAAM,UAAU,KAAK,UAAU,cAAc,IAAI,KAAK,OAAM,eAAgB;GAC5E,IAAI,SAAS,KAAK,gBAAgB,OAAO;EAC3C;EAEA,KAAK,SAAS,iBAAiB,eAAe;GAC5C,KAAK,WAAW;EAClB,CAAC;EAED,KAAK,QAAQ,GAAG,mBAAmB,UAA4D;GAC7F,KAAK,mBAAmB,MAAM,UAAU,MAAM,WAAW;EAC3D,CAAC;EACD,KAAK,QAAQ,GAAG,0BAA0B,UAAkB;GAE1D,CADc,KAAK,cAAc,iBAAiB,IAAI,KAAK,OAAM,cAAe,EAAA,EACzE,SAAS,IAAI,MAAM;IACxB,GAAG,UAAU,OAAO,GAAG,KAAK,OAAM,wBAAyB,MAAM,KAAK;IACtE,GAAG,aAAa,iBAAiB,OAAO,MAAM,KAAK,CAAC;GACtD,CAAC;EACH,CAAC;EACD,KAAK,QAAQ,GAAG,4BAA4B;GAC1C,KAAK,iBAAiB;EACxB,CAAC;CACH;CAEQ,gBAAgB,SAA4B;EAClD,IAAI,cAAc;EAClB,QAAQ,iBAAiB,cAAc,UAAU;GAC/C,MAAM,eAAe;GACrB;GACA,IAAI,KAAK,aAAa,KAAK,YAAY,MAAM,UAAU;EACzD,CAAC;EACD,QAAQ,iBAAiB,cAAc,UAAU;GAC/C,MAAM,eAAe;GACrB;GACA,IAAI,eAAe,GAAG;IACpB,cAAc;IACd,IAAI,KAAK,aAAa,KAAK,YAAY,MAAM,UAAU;GACzD;EACF,CAAC;EACD,QAAQ,iBAAiB,aAAa,UAAU;GAC9C,MAAM,eAAe;EACvB,CAAC;EACD,QAAQ,iBAAiB,SAAS,UAAU;GAC1C,MAAM,eAAe;GACrB,cAAc;GACd,IAAI,KAAK,aAAa,KAAK,YAAY,MAAM,UAAU;GACvD,MAAM,QAAQ,MAAM,KAAK,MAAM,cAAc,SAAS,CAAC,CAAC;GACxD,IAAI,MAAM,SAAS,GAAG;IACpB,KAAK,QAAQ,KAAK,gBAAgB,KAAK;IACvC,KAAK,OAAO,SAAS,KAAK;GAC5B;EACF,CAAC;CACH;CAEQ,aAAmB;EAEzB,MAAM,WADa,KAAK,YAAY,SAAS,GAAA,CAClB,KAAK;EAChC,IAAI,CAAC,SAAS;EAEd,MAAM,WAAW,KAAK,cAAc,cAAc,OAAO;EACzD,KAAK,QAAQ,KAAK,QAAQ,SAAS,QAAQ;EAC3C,KAAK,OAAO,SAAS,SAAS,QAAQ;EAEtC,IAAI,KAAK,OAAO,qBAAqB,OACnC,KAAK,WAAW;EAElB,KAAK,cAAc,cAAc;CACnC;CAEQ,cAAc,QAA2B;EAC/C,IAAI,CAAC,KAAK,YAAY;EACtB,MAAM,QAAQ,KAAK,cAAc,SAAS;EAC1C,MAAM,OAAO,KAAK,WAAW;EAC7B,MAAM,MAAM,KAAK,WAAW;EAC5B,MAAM,aAAa,KAAK,cAAc,cAAc,MAAM;EAE1D,MAAM,SAAS,KAAK,MAAM,GAAG,MAAM,eAAe;EAClD,MAAM,QAAQ,KAAK,MAAM,GAAG;EAC5B,KAAK,WAAW,QAAQ,KAAK,WAAW,SAAS,aAAa,KAAK;EACnE,MAAM,SAAS,KAAK,IAAI,OAAO,SAAS,WAAW,QAAQ,KAAK,WAAW,MAAM,MAAM;EACvF,KAAK,WAAW,kBAAkB,QAAQ,MAAM;EAChD,KAAK,WAAW,MAAM;EACtB,KAAK,WAAW;EAChB,KAAK,sBAAsB;EAC3B,KAAK,OAAO,gBAAgB,KAAK,WAAW,KAAK;CACnD;CAEQ,mBAAmB,MAAoB;EAC7C,IAAI,CAAC,KAAK,YAAY;EACtB,MAAM,UAAU,KAAK,WAAW;EAChC,MAAM,QAAQ,KAAK,WAAW;EAC9B,MAAM,MAAM,KAAK,WAAW;EAC5B,KAAK,WAAW,QAAQ,KAAK,WAAW,QAAQ,MAAM,GAAG,KAAK,IAAI,OAAO,QAAQ,MAAM,GAAG,CAAC;EAC3F,MAAM,aAAa,KAAK,IAAI,QAAQ,KAAK,QAAQ,KAAK,WAAW,MAAM,MAAM;EAC7E,KAAK,WAAW,kBAAkB,YAAY,UAAU;EACxD,KAAK,WAAW;EAChB,KAAK,sBAAsB;EAC3B,KAAK,OAAO,gBAAgB,KAAK,WAAW,KAAK;CACnD;CAEQ,aAAmB;EACzB,IAAI,CAAC,KAAK,YAAY;EACtB,KAAK,WAAW,MAAM,SAAS;EAC/B,MAAM,YAAY,KAAK,gBAAgB,sCAAsC,GAAG;EAChF,KAAK,WAAW,MAAM,SAAS,GAAG,KAAK,IAAI,KAAK,WAAW,cAAc,SAAS,EAAC;CACrF;CAEQ,wBAA8B;EACpC,IAAI,CAAC,KAAK,SAAS;EACnB,KAAK,QAAQ,WAAW,CAAC,KAAK,gBAAgB,CAAA,CAAE,KAAK;CACvD;CAEQ,uBAA6B;EACnC,KAAK,aAAa,KAAK,GAAG,OAAO,GAAG,KAAK,OAAM,WAAY;EAC3D,KAAK,WAAW,cAAc,KAAK,OAAO,gBAAgB;EAC1D,KAAK,WAAW,iBAAiB,eAAe;GAC9C,IAAI,KAAK,aAAa;GACtB,KAAK,eAAe,IAAI;GACxB,KAAK,QAAQ,KAAK,WAAW;GAC7B,QAAQ,QAAQ,KAAK,OAAO,aAAa,CAAC,CAAA,CAAE,cAAc;IACxD,KAAK,eAAe,KAAK;GAC3B,CAAC;EACH,CAAC;EACD,KAAK,cAAc,YAAY,KAAK,UAAU;CAChD;CAEQ,kBAAkB,WAAyB;EACjD,KAAK,cAAc,YAAY,KAAK,kBAAkB,SAAS,CAAC;CAClE;CAEQ,kBAAkB,WAAgC;EACxD,MAAM,UAAU,KAAK,GAAG,OAAO,GAAG,KAAK,OAAM,cAAe;EAC5D,MAAM,OAAO,SAAS,cAAc,MAAM;EAC1C,KAAK,cAAc,KAAK,WAAW,SAAS;EAC5C,QAAQ,YAAY,IAAI;EACxB,OAAO;CACT;CAEQ,iBAAiB,SAA4B;EACnD,KAAK,cAAc,YAAY,KAAK,iBAAiB,OAAO,CAAC;CAC/D;CAEQ,iBAAiB,SAAmC;EAC1D,MAAM,IAAI,KAAK;EAEf,IAAI,QAAQ,SAAS,UAAU;GAC7B,MAAMA,OAAM,KAAK,GAAG,OAAO,YAAA,GAAG,GAAG,EAAC,eAAgB,GAAG,EAAC,qBAAsB,CAAC;GAC7EA,KAAI,QAAQ,QAAQ,QAAQ;GAC5BA,KAAI,QAAQ,YAAY,OAAO,QAAQ,SAAS;GAChD,MAAM,SAAS,KAAK,GAAG,OAAO,GAAG,EAAC,gBAAiB;GACnD,OAAO,cAAc,QAAQ,WAAW;GACxCA,KAAI,YAAY,MAAM;GACtB,OAAOA;EACT;EAEA,MAAM,aAAa,QAAQ,cAAc;EACzC,MAAM,MAAM,KAAK,GAAG,OAAO,YAAA,GAAG,GAAG,EAAC,eAAgB,cAAc,GAAG,EAAC,uBAAwB,CAAC;EAC7F,IAAI,QAAQ,QAAQ,QAAQ;EAC5B,IAAI,QAAQ,YAAY,OAAO,QAAQ,SAAS;EAEhD,IAAI,KAAK,OAAO,eAAe,OAC7B,IAAI,YAAY,KAAK,aAAa,QAAQ,OAAO,UAAU,QAAQ,OAAO,MAAM,CAAC;EAGnF,MAAM,OAAO,KAAK,GAAG,OAAO,GAAG,EAAC,cAAe;EAC/C,IAAI,KAAK,OAAO,iBAAiB,SAAS,CAAC,YAAY;GACrD,MAAM,WAAW,KAAK,GAAG,OAAO,GAAG,EAAC,UAAW;GAC/C,SAAS,cAAc,QAAQ,OAAO;GACtC,KAAK,YAAY,QAAQ;EAC3B;EAEA,KAAK,YAAY,KAAK,aAAa,SAAS,UAAU,CAAC;EACvD,KAAK,YAAY,KAAK,oBAAoB,SAAS,UAAU,CAAC;EAC9D,IAAI,YAAY,IAAI;EAEpB,IAAI,iBAAiB,eAAe;GAClC,KAAK,QAAQ,KAAK,iBAAiB,OAAO;GAC1C,KAAK,OAAO,iBAAiB,OAAO;EACtC,CAAC;EAED,OAAO;CACT;CAEQ,aAAa,MAAc,WAAiC;EAClE,MAAM,SAAS,KAAK,GAAG,OAAO,GAAG,KAAK,OAAM,QAAS;EACrD,IAAI,WAAW;GACb,MAAM,MAAM,SAAS,cAAc,KAAK;GACxC,IAAI,MAAM;GACV,IAAI,MAAM;GACV,OAAO,YAAY,GAAG;EACxB,OACE,OAAO,cAAc,KAAK,EAAC,EAAG,YAAY,KAAK;EAEjD,OAAO;CACT;CAEQ,oBAAoB,SAAsB,YAAkC;EAClF,MAAM,SAAS,KAAK,GAAG,OAAO,GAAG,KAAK,OAAM,gBAAiB;EAC7D,IAAI,KAAK,OAAO,kBAAkB,OAAO;GACvC,MAAM,OAAO,KAAK,GAAG,QAAQ,GAAG,KAAK,OAAM,WAAY;GACvD,KAAK,cAAc,KAAK,gBAAgB,QAAQ,SAAS;GACzD,OAAO,YAAY,IAAI;EACzB;EACA,IAAI,KAAK,OAAO,eAAe,SAAS,cAAc,QAAQ,QAAQ;GACpE,MAAM,SAAS,KAAK,GAAG,QAAQ,YAAA,GAAG,GAAG,KAAK,OAAM,UAAW,GAAG,KAAK,OAAM,WAAY,QAAQ,QAAQ,CAAC;GACtG,OAAO,cAAc,WAAW,QAAQ,WAAW;GACnD,IAAI,QAAQ,WAAW,UACrB,OAAO,iBAAiB,UAAU,UAAU;IAC1C,MAAM,gBAAgB;IACtB,KAAK,QAAQ,KAAK,iBAAiB,OAAO;GAC5C,CAAC;GAEH,OAAO,YAAY,MAAM;EAC3B;EACA,OAAO;CACT;CAEQ,aAAa,SAAsB,YAAkC;EAC3E,MAAM,IAAI,KAAK;EACf,MAAM,WAAW,aAAa,GAAG,EAAC,qBAAsB,GAAG,EAAC;EAE5D,IAAI,QAAQ,SAAS;GACnB,MAAM,SAAS,KAAK,GAAG,OAAO,YAAA,GAAG,GAAG,EAAC,UAAW,UAAU,GAAG,EAAC,iBAAkB,CAAC;GACjF,OAAO,cAAc;GACrB,OAAO;EACT;EAEA,QAAQ,QAAQ,MAAhB;GACE,KAAK,SAAS;IACZ,MAAM,SAAS,KAAK,GAAG,OAAO,YAAA,GAAG,GAAG,EAAC,UAAW,UAAU,GAAG,EAAC,eAAgB,CAAC;IAC/E,IAAI,QAAQ,OAAO;KACjB,MAAM,MAAM,SAAS,cAAc,KAAK;KACxC,IAAI,MAAM,QAAQ,MAAM,YAAY,QAAQ,MAAM;KAClD,IAAI,MAAM,QAAQ,WAAW;KAC7B,IAAI,QAAQ,MAAM,OAAO,IAAI,QAAQ,KAAK,IAAI,QAAQ,MAAM,OAAO,GAAG;KACtE,OAAO,YAAY,GAAG;IACxB;IACA,OAAO;GACT;GAEA,KAAK,QAAQ;IACX,MAAM,SAAS,KAAK,GAAG,OAAO,YAAA,GAAG,GAAG,EAAC,UAAW,UAAU,GAAG,EAAC,cAAe,CAAC;IAC9E,MAAM,OAAO,KAAK,GAAG,OAAO,GAAG,EAAC,WAAY;IAC5C,KAAK,YAAY;IACjB,OAAO,YAAY,IAAI;IAEvB,MAAM,OAAO,KAAK,GAAG,OAAO,GAAG,EAAC,WAAY;IAC5C,MAAM,OAAO,KAAK,GAAG,OAAO,GAAG,EAAC,WAAY;IAC5C,KAAK,cAAc,QAAQ,MAAM,QAAQ;IACzC,KAAK,YAAY,IAAI;IAErB,IAAI,QAAQ,MAAM,MAAM;KACtB,MAAM,OAAO,KAAK,GAAG,OAAO,GAAG,EAAC,WAAY;KAC5C,KAAK,cAAc,KAAK,eAAe,QAAQ,KAAK,IAAI;KACxD,KAAK,YAAY,IAAI;IACvB;IACA,OAAO,YAAY,IAAI;IACvB,OAAO;GACT;GAEA,KAAK,UAAU;IACb,MAAM,SAAS,KAAK,GAAG,OAAO,YAAA,GAAG,GAAG,EAAC,UAAW,QAAQ,CAAC;IACzD,IAAI,QAAQ,YACV,OAAO,YAAY,QAAQ;SAE3B,OAAO,cAAc,QAAQ,WAAW;IAE1C,OAAO;GACT;GAEA,SAAS;IACP,MAAM,SAAS,KAAK,GAAG,OAAO,YAAA,GAAG,GAAG,EAAC,UAAW,UAAU,QAAQ,aAAa,GAAG,EAAC,mBAAoB,CAAC;IACxG,OAAO,YAAY,KAAK,kBAAkB,QAAQ,WAAW,IAAI,QAAQ,QAAQ;IACjF,OAAO;GACT;EACF;CACF;CAEQ,kBAAkB,SAAiB,UAAkC;EAC3E,IAAI,OAAO,KAAK,WAAW,OAAO;EAElC,IAAI,UAAU,QACZ,KAAA,MAAW,WAAW,UAAU;GAC9B,MAAM,MAAM,gBAAgB,KAAK,OAAM,iCAAkC,KAAK,WAAW,QAAQ,EAAE,EAAC,KAAM,KAAK,WAAW,QAAQ,IAAI,EAAC;GACvI,OAAO,KAAK,QAAQ,IAAI,OAAO,IAAI,KAAK,YAAY,QAAQ,IAAI,KAAK,GAAG,GAAG,GAAG;EAChF;EAGF,OAAO,KAAK,QACV,yBACA,aAAa,KAAK,OAAM,kEAC1B;CACF;CAEQ,cAAoB;EAC1B,IAAI,KAAK,cAAc,cAAc,IAAI,KAAK,OAAM,OAAQ,GAAG;EAC/D,MAAM,QAAQ,KAAK,GAAG,OAAO,GAAG,KAAK,OAAM,OAAQ;EACnD,MAAM,cAAc;EACpB,KAAK,cAAc,YAAY,KAAK;CACtC;CAEQ,cAAoB;EAC1B,KAAK,cAAc,cAAc,IAAI,KAAK,OAAM,OAAQ,CAAA,EAAG,OAAO;CACpE;CAEQ,GAAG,KAAa,WAAgC;EACtD,MAAM,KAAK,SAAS,cAAc,GAAG;EACrC,GAAG,YAAY;EACf,OAAO;CACT;CAEQ,qBAAoC;EAC1C,OAAO,MAAM,KAAK,KAAK,cAAc,iBAA8B,IAAI,KAAK,OAAM,aAAc,CAAC;CACnG;CAEQ,WAAW,WAA2B;EAC5C,IAAI,KAAK,OAAO,YAAY,OAAO,KAAK,OAAO,WAAW,SAAS;EACnE,MAAM,OAAO,IAAI,KAAK,SAAS;EAC/B,MAAM,sBAAM,IAAI,KAAK;EAErB,IADgB,KAAK,aAAa,MAAM,IAAI,aAAa,GAEvD,OAAO,KAAK,mBAAmB,SAAS;GAAE,MAAM;GAAW,QAAQ;EAAU,CAAC;EAEhF,OAAO,KAAK,mBAAmB,SAAS;GACtC,OAAO;GACP,KAAK;GACL,MAAM;GACN,QAAQ;EACV,CAAC;CACH;CAEQ,gBAAgB,WAA2B;EACjD,OAAO,IAAI,KAAK,SAAS,CAAA,CAAE,mBAAmB,SAAS;GAAE,MAAM;GAAW,QAAQ;EAAU,CAAC;CAC/F;CAEQ,eAAe,OAAuB;EAC5C,IAAI,QAAQ,MAAM,OAAO,GAAG,MAAK;EACjC,IAAI,QAAQ,OAAO,MAAM,OAAO,IAAI,QAAQ,KAAA,CAAM,QAAQ,CAAC,EAAC;EAC5D,OAAO,IAAI,SAAS,OAAO,MAAA,CAAO,QAAQ,CAAC,EAAC;CAC9C;CAEQ,WAAW,SAAyB;EAC1C,MAAM,MAAM,KAAK,OAAO;EACxB,IAAI,CAAC,OAAO,OAAO,GAAG,OAAO;EAC7B,OAAO,QAAQ,MAAM,GAAG,GAAG;CAC7B;CAEQ,gBAAgB,MAAc,UAA0B;EAC9D,IAAI,OAAO,WAAW,eAAe,CAAC,KAAK,MAAM,OAAO;EACxD,MAAM,QAAQ,OAAO,iBAAiB,KAAK,IAAI,CAAA,CAAE,iBAAiB,IAAI,CAAA,CAAE,KAAK;EAC7E,MAAM,SAAS,OAAO,WAAW,KAAK;EACtC,OAAO,OAAO,SAAS,MAAM,IAAI,SAAS;CAC5C;CAEQ,WAAW,KAAqB;EACtC,MAAM,MAAM,SAAS,cAAc,KAAK;EACxC,IAAI,cAAc;EAClB,OAAO,IAAI;CACb;CAEQ,WAAW,KAAqB;EACtC,OAAO,IACJ,QAAQ,MAAM,QAAQ,CAAA,CACtB,QAAQ,MAAM,OAAO,CAAA,CACrB,QAAQ,MAAM,MAAM,CAAA,CACpB,QAAQ,MAAM,MAAM;CACzB;CAEQ,YAAY,KAAqB;EACvC,OAAO,IAAI,QAAQ,uBAAuB,MAAM;CAClD;AACF"}
|
|
1
|
+
{"version":3,"file":"dom-renderer.cjs","names":["row"],"sources":["../../src/renderer/dom-renderer.ts"],"sourcesContent":["import type { EventEmitter } from '../core/event-emitter'\r\nimport type { MentionEngine } from '../core/mention-engine'\r\nimport type { ThemeTokens } from '../theme/tokens'\r\nimport type {\r\n ChatConfig,\r\n ChatContentBlock,\r\n ChatMessage,\r\n MentionItem,\r\n MessageStatus,\r\n} from '../types'\r\nimport {\r\n cx,\r\n DEFAULT_CLASS_PREFIX,\r\n normalizeClassPrefix,\r\n} from '../constants/dom'\r\nimport { applyThemeTokens } from './style-manager'\r\n\r\nconst SEND_ICON_SVG =\r\n '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"></line><polygon points=\"22 2 15 22 11 13 2 9 22 2\"></polygon></svg>'\r\nconst ICON_IMAGE =\r\n '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/></svg>'\r\nconst ICON_PAPERCLIP =\r\n '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48\"/></svg>'\r\nconst ICON_MIC =\r\n '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\"/><path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/><line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/></svg>'\r\nconst ICON_MIC_OFF =\r\n '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"2\" x2=\"22\" y1=\"2\" y2=\"22\"/><path d=\"M18.89 13.23A7.12 7.12 0 0 0 19 12v-2\"/><path d=\"M5 10v2a7 7 0 0 0 12 5.29\"/><path d=\"M15 9.34V5a3 3 0 0 0-5.68-1.33\"/><path d=\"M9 9v3a3 3 0 0 0 5.12 2.12\"/><line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\"/></svg>'\r\nconst FILE_ICON =\r\n '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\"/><path d=\"M14 2v4a2 2 0 0 0 2 2h4\"/></svg>'\r\n\r\nconst ALLOWED_CUSTOM_TAGS = new Set([\r\n 'a',\r\n 'b',\r\n 'blockquote',\r\n 'br',\r\n 'code',\r\n 'div',\r\n 'em',\r\n 'h1',\r\n 'h2',\r\n 'h3',\r\n 'h4',\r\n 'h5',\r\n 'h6',\r\n 'i',\r\n 'img',\r\n 'li',\r\n 'ol',\r\n 'p',\r\n 'pre',\r\n 's',\r\n 'span',\r\n 'strong',\r\n 'u',\r\n 'ul',\r\n])\r\nconst DANGEROUS_CUSTOM_TAGS = new Set([\r\n 'iframe',\r\n 'object',\r\n 'embed',\r\n 'script',\r\n 'style',\r\n 'svg',\r\n 'math',\r\n 'template',\r\n 'form',\r\n 'input',\r\n 'button',\r\n 'link',\r\n 'meta',\r\n 'base',\r\n])\r\n\r\nconst STATUS_MAP: Record<MessageStatus, string> = {\r\n sending: '...',\r\n sent: '✓',\r\n delivered: '✓✓',\r\n read: '✓✓',\r\n failed: '!',\r\n}\r\n\r\ninterface SpeechRecognitionResultLike {\r\n isFinal: boolean\r\n 0: { transcript: string }\r\n}\r\n\r\ninterface SpeechRecognitionEventLike {\r\n resultIndex?: number\r\n results: ArrayLike<SpeechRecognitionResultLike>\r\n}\r\n\r\ninterface SpeechRecognitionLike {\r\n continuous: boolean\r\n interimResults: boolean\r\n lang: string\r\n onresult: ((event: SpeechRecognitionEventLike) => void) | null\r\n onend: (() => void) | null\r\n onerror: ((error: unknown) => void) | null\r\n start: () => void\r\n stop: () => void\r\n}\r\n\r\nexport class DOMRenderer {\r\n private container: HTMLElement\r\n private document: Document\r\n private config: ChatConfig\r\n private emitter: EventEmitter\r\n private mentionEngine: MentionEngine\r\n private prefix: string\r\n\r\n private root!: HTMLElement\r\n private messageListEl!: HTMLElement\r\n private inputArea: HTMLElement | null = null\r\n private textareaEl: HTMLTextAreaElement | null = null\r\n private sendBtn: HTMLButtonElement | null = null\r\n private mentionPanel: HTMLElement | null = null\r\n private loadMoreEl: HTMLElement | null = null\r\n private toolbarEl: HTMLElement | null = null\r\n private dropOverlay: HTMLElement | null = null\r\n private voiceBtn: HTMLElement | null = null\r\n private loadingMore = false\r\n private isRecording = false\r\n private recognition: SpeechRecognitionLike | null = null\r\n private lastVoiceTranscript = ''\r\n private destroyed = false\r\n private pendingFileInputs = new Set<HTMLInputElement>()\r\n\r\n constructor(\r\n container: HTMLElement,\r\n config: ChatConfig,\r\n emitter: EventEmitter,\r\n mentionEngine: MentionEngine\r\n ) {\r\n this.container = container\r\n this.document = container.ownerDocument\r\n this.config = config\r\n this.emitter = emitter\r\n this.mentionEngine = mentionEngine\r\n this.prefix = normalizeClassPrefix(\r\n config.classPrefix ?? DEFAULT_CLASS_PREFIX\r\n )\r\n }\r\n\r\n init(): void {\r\n if (this.destroyed) return\r\n this.container.innerHTML = ''\r\n this.root = this.el('div', `${this.prefix}-container`)\r\n this.messageListEl = this.el('div', `${this.prefix}-message-list`)\r\n this.root.appendChild(this.messageListEl)\r\n\r\n if (this.config.showInput !== false) {\r\n this.renderInputArea()\r\n }\r\n\r\n this.container.appendChild(this.root)\r\n this.setupEventListeners()\r\n }\r\n\r\n updateMessageContent(id: string, content: string): void {\r\n const rowEl = this.getMessageRow(id)\r\n const bubble = rowEl?.querySelector(\r\n `.${this.prefix}-bubble`\r\n ) as HTMLElement | null\r\n if (!bubble) return\r\n bubble.innerHTML = this.renderTextContent(content)\r\n }\r\n\r\n renderMessages(messages: ChatMessage[]): void {\r\n this.messageListEl.innerHTML = ''\r\n\r\n if (this.config.enableLoadMore) {\r\n this.renderLoadMoreButton()\r\n }\r\n\r\n if (messages.length === 0) {\r\n this.renderEmpty()\r\n return\r\n }\r\n\r\n let lastTimestamp = 0\r\n const interval = this.config.timeGroupInterval ?? 300000\r\n for (const message of messages) {\r\n if (\r\n this.config.showTimestamp !== false &&\r\n message.timestamp - lastTimestamp > interval\r\n ) {\r\n this.renderTimeDivider(message.timestamp)\r\n lastTimestamp = message.timestamp\r\n }\r\n this.renderMessageRow(message)\r\n }\r\n }\r\n\r\n appendMessage(message: ChatMessage): void {\r\n this.removeEmpty()\r\n const messageEls = this.getMessageElements()\r\n const lastTime =\r\n messageEls.length > 0\r\n ? Number.parseInt(\r\n messageEls[messageEls.length - 1].dataset.timestamp ?? '0',\r\n 10\r\n )\r\n : 0\r\n const interval = this.config.timeGroupInterval ?? 300000\r\n\r\n if (\r\n this.config.showTimestamp !== false &&\r\n message.timestamp - lastTime > interval\r\n ) {\r\n this.renderTimeDivider(message.timestamp)\r\n }\r\n\r\n this.renderMessageRow(message)\r\n }\r\n\r\n prependMessages(messages: ChatMessage[]): void {\r\n if (!messages.length) return\r\n this.removeEmpty()\r\n\r\n const scrollHeight = this.messageListEl.scrollHeight\r\n const scrollTop = this.messageListEl.scrollTop\r\n const fragment = this.document.createDocumentFragment()\r\n let lastTimestamp = 0\r\n const interval = this.config.timeGroupInterval ?? 300000\r\n\r\n for (const message of messages) {\r\n if (\r\n this.config.showTimestamp !== false &&\r\n message.timestamp - lastTimestamp > interval\r\n ) {\r\n fragment.appendChild(this.createTimeDivider(message.timestamp))\r\n lastTimestamp = message.timestamp\r\n }\r\n fragment.appendChild(this.createMessageRow(message))\r\n }\r\n\r\n const firstMessage = this.messageListEl.querySelector(\r\n `.${this.prefix}-message-row, .${this.prefix}-time-divider`\r\n )\r\n if (firstMessage) {\r\n firstMessage.before(fragment)\r\n } else if (this.loadMoreEl) {\r\n this.loadMoreEl.after(fragment)\r\n } else {\r\n this.messageListEl.appendChild(fragment)\r\n }\r\n\r\n this.messageListEl.scrollTop =\r\n scrollTop + (this.messageListEl.scrollHeight - scrollHeight)\r\n }\r\n\r\n updateMessage(id: string, message: ChatMessage): void {\r\n const rowEl = this.getMessageRow(id)\r\n if (!rowEl) return\r\n rowEl.replaceWith(this.createMessageRow({ ...message, id }))\r\n }\r\n\r\n removeMessage(id: string): void {\r\n this.getMessageRow(id)?.remove()\r\n this.refreshTimeDividers()\r\n if (this.getMessageElements().length === 0) {\r\n this.renderEmpty()\r\n } else {\r\n this.removeEmpty()\r\n }\r\n }\r\n\r\n scrollToBottom(smooth = true): void {\r\n if (typeof this.messageListEl.scrollTo === 'function') {\r\n this.messageListEl.scrollTo({\r\n top: this.messageListEl.scrollHeight,\r\n behavior: smooth ? 'smooth' : 'auto',\r\n })\r\n return\r\n }\r\n this.messageListEl.scrollTop = this.messageListEl.scrollHeight\r\n }\r\n\r\n isAtBottom(threshold = 4): boolean {\r\n const distance =\r\n this.messageListEl.scrollHeight -\r\n this.messageListEl.scrollTop -\r\n this.messageListEl.clientHeight\r\n return distance <= threshold\r\n }\r\n\r\n setLoadingMore(loading: boolean): void {\r\n this.loadingMore = loading\r\n if (!this.loadMoreEl) return\r\n this.loadMoreEl.textContent = loading\r\n ? '加载中...'\r\n : (this.config.loadMoreText ?? '加载更多')\r\n this.loadMoreEl.classList.toggle(\r\n `${this.prefix}-load-more--loading`,\r\n loading\r\n )\r\n }\r\n\r\n setThemeTokens(tokens: ThemeTokens): void {\r\n if (this.root) applyThemeTokens(this.root, tokens)\r\n }\r\n\r\n getRootElement(): HTMLElement {\r\n return this.root\r\n }\r\n\r\n focusInput(): void {\r\n this.textareaEl?.focus()\r\n }\r\n\r\n setInputContent(content: string): void {\r\n if (!this.textareaEl) return\r\n this.textareaEl.value = this.limitInput(content)\r\n this.autoResize()\r\n this.updateSendButtonState()\r\n this.config.onInputChange?.(this.textareaEl.value)\r\n }\r\n\r\n getInputContent(): string {\r\n return this.textareaEl?.value ?? ''\r\n }\r\n\r\n clearInput(): void {\r\n this.setInputContent('')\r\n }\r\n\r\n renderMentionPanel(members: MentionItem[], activeIndex: number): void {\r\n if (!this.mentionPanel) return\r\n\r\n if (members.length === 0) {\r\n this.mentionPanel.style.display = 'none'\r\n return\r\n }\r\n\r\n this.mentionPanel.style.display = 'block'\r\n this.mentionPanel.innerHTML = ''\r\n\r\n members.forEach((member, index) => {\r\n const item = this.el(\r\n 'div',\r\n cx(\r\n `${this.prefix}-mention-item`,\r\n index === activeIndex && `${this.prefix}-mention-item--active`\r\n )\r\n )\r\n item.setAttribute('role', 'option')\r\n item.setAttribute('aria-selected', String(index === activeIndex))\r\n\r\n const avatar = this.el('div', `${this.prefix}-mention-item-avatar`)\r\n if (member.avatar) {\r\n const img = this.document.createElement('img')\r\n img.src = this.safeResourceUrl(member.avatar)\r\n img.alt = ''\r\n avatar.appendChild(img)\r\n } else {\r\n avatar.textContent = member.name[0]?.toUpperCase() ?? ''\r\n }\r\n item.appendChild(avatar)\r\n\r\n const name = this.el('span', `${this.prefix}-mention-item-name`)\r\n name.textContent = member.name\r\n item.appendChild(name)\r\n\r\n item.addEventListener('mousedown', event => event.preventDefault())\r\n item.addEventListener('click', () => {\r\n const triggerPosition = this.mentionEngine.getState().triggerPosition\r\n const selectionEnd = this.textareaEl?.selectionEnd\r\n const selected = this.mentionEngine.select(index)\r\n if (selected)\r\n this.insertMention(selected, triggerPosition, selectionEnd)\r\n })\r\n\r\n this.mentionPanel?.appendChild(item)\r\n })\r\n }\r\n\r\n hideMentionPanel(): void {\r\n if (this.mentionPanel) {\r\n this.mentionPanel.style.display = 'none'\r\n }\r\n }\r\n\r\n destroy(): void {\r\n if (this.destroyed) return\r\n this.destroyed = true\r\n this.stopVoice()\r\n this.finishVoice()\r\n this.pendingFileInputs.forEach(input => input.remove())\r\n this.pendingFileInputs.clear()\r\n this.root?.remove()\r\n }\r\n\r\n private renderInputArea(): void {\r\n this.inputArea = this.el('div', `${this.prefix}-input-area`)\r\n const wrapper = this.el('div', `${this.prefix}-input-wrapper`)\r\n\r\n this.mentionPanel = this.el('div', `${this.prefix}-mention-panel`)\r\n this.mentionPanel.style.display = 'none'\r\n this.mentionPanel.setAttribute('role', 'listbox')\r\n wrapper.appendChild(this.mentionPanel)\r\n\r\n if (this.config.enableToolbar !== false) {\r\n this.renderToolbar(wrapper)\r\n }\r\n\r\n this.textareaEl = this.document.createElement('textarea')\r\n this.textareaEl.className = `${this.prefix}-textarea`\r\n this.textareaEl.placeholder = this.config.inputPlaceholder ?? '输入消息...'\r\n this.textareaEl.rows = 1\r\n if (this.config.maxInputLength && this.config.maxInputLength > 0) {\r\n this.textareaEl.maxLength = this.config.maxInputLength\r\n }\r\n wrapper.appendChild(this.textareaEl)\r\n\r\n this.dropOverlay = this.el('div', `${this.prefix}-drop-overlay`)\r\n this.dropOverlay.innerHTML = '<span>拖放文件到这里</span>'\r\n this.dropOverlay.style.display = 'none'\r\n wrapper.appendChild(this.dropOverlay)\r\n\r\n this.inputArea.appendChild(wrapper)\r\n\r\n this.sendBtn = this.document.createElement('button')\r\n this.sendBtn.className = `${this.prefix}-send-btn`\r\n this.sendBtn.innerHTML = SEND_ICON_SVG\r\n this.sendBtn.title = '发送'\r\n this.sendBtn.type = 'button'\r\n this.sendBtn.disabled = true\r\n this.inputArea.appendChild(this.sendBtn)\r\n\r\n this.root.appendChild(this.inputArea)\r\n }\r\n\r\n private renderToolbar(wrapper: HTMLElement): void {\r\n const toolbar = this.config.toolbarConfig ?? {}\r\n this.toolbarEl = this.el('div', `${this.prefix}-toolbar`)\r\n\r\n if (toolbar.image !== false) {\r\n const imageBtn = this.createToolbarBtn(ICON_IMAGE, '插入图片')\r\n imageBtn.addEventListener('click', () => this.openFilePicker('image/*'))\r\n this.toolbarEl.appendChild(imageBtn)\r\n }\r\n\r\n if (toolbar.file !== false) {\r\n const fileBtn = this.createToolbarBtn(ICON_PAPERCLIP, '插入文件')\r\n fileBtn.addEventListener('click', () => this.openFilePicker('*/*'))\r\n this.toolbarEl.appendChild(fileBtn)\r\n }\r\n\r\n if (toolbar.voice !== false) {\r\n this.voiceBtn = this.createToolbarBtn(ICON_MIC, '语音输入')\r\n this.voiceBtn.addEventListener('click', () => this.toggleVoice())\r\n this.toolbarEl.appendChild(this.voiceBtn)\r\n }\r\n\r\n wrapper.insertBefore(this.toolbarEl, wrapper.firstChild)\r\n }\r\n\r\n private createToolbarBtn(iconSvg: string, title: string): HTMLButtonElement {\r\n const btn = this.document.createElement('button')\r\n btn.className = `${this.prefix}-toolbar-btn`\r\n btn.innerHTML = iconSvg\r\n btn.title = title\r\n btn.type = 'button'\r\n return btn\r\n }\r\n\r\n private openFilePicker(accept: string): void {\r\n const input = this.document.createElement('input')\r\n input.type = 'file'\r\n input.accept = accept\r\n input.multiple = true\r\n input.style.display = 'none'\r\n this.pendingFileInputs.add(input)\r\n const cleanup = () => {\r\n input.remove()\r\n this.pendingFileInputs.delete(input)\r\n }\r\n input.addEventListener('change', () => {\r\n const files = Array.from(input.files ?? [])\r\n cleanup()\r\n if (!files.length) return\r\n const isImage = accept.startsWith('image')\r\n this.emitter.emit(isImage ? 'toolbar-image' : 'toolbar-file', files)\r\n if (isImage) {\r\n this.config.onImageUpload?.(files)\r\n } else {\r\n this.config.onFileUpload?.(files)\r\n }\r\n })\r\n input.addEventListener('cancel', cleanup)\r\n ;(this.document.body ?? this.document.documentElement).appendChild(input)\r\n input.click()\r\n }\r\n\r\n private toggleVoice(): void {\r\n if (this.isRecording) {\r\n this.stopVoice()\r\n } else {\r\n this.startVoice()\r\n }\r\n }\r\n\r\n private startVoice(): void {\r\n const view = this.document.defaultView as\r\n | (Window & {\r\n SpeechRecognition?: new () => SpeechRecognitionLike\r\n webkitSpeechRecognition?: new () => SpeechRecognitionLike\r\n })\r\n | null\r\n const SpeechRecognition =\r\n view?.SpeechRecognition || view?.webkitSpeechRecognition\r\n if (!SpeechRecognition) {\r\n this.emitter.emit('toolbar-voice-unsupported')\r\n this.config.onVoiceUnsupported?.()\r\n return\r\n }\r\n\r\n try {\r\n this.lastVoiceTranscript = ''\r\n const recognition = new SpeechRecognition()\r\n const finalSegments = new Map<number, string>()\r\n recognition.continuous = false\r\n recognition.interimResults = true\r\n recognition.lang = this.config.voiceLang ?? 'zh-CN'\r\n this.recognition = recognition\r\n\r\n recognition.onresult = event => {\r\n if (this.destroyed || this.recognition !== recognition) return\r\n const firstChangedResult = Math.max(0, event.resultIndex ?? 0)\r\n const newFinalSegments: string[] = []\r\n for (\r\n let index = firstChangedResult;\r\n index < event.results.length;\r\n index++\r\n ) {\r\n const result = event.results[index]\r\n if (!result?.isFinal || finalSegments.has(index)) continue\r\n finalSegments.set(index, result[0].transcript)\r\n newFinalSegments.push(result[0].transcript)\r\n }\r\n this.lastVoiceTranscript = [...finalSegments.entries()]\r\n .sort(([left], [right]) => left - right)\r\n .map(([, transcript]) => transcript)\r\n .join('')\r\n if (newFinalSegments.length && this.textareaEl) {\r\n this.insertTextAtCursor(newFinalSegments.join(''))\r\n }\r\n }\r\n\r\n recognition.onend = () => {\r\n if (this.destroyed || this.recognition !== recognition) return\r\n const transcript = this.lastVoiceTranscript\r\n this.finishVoice()\r\n this.emitter.emit('toolbar-voice-end', transcript)\r\n this.config.onVoiceEnd?.(transcript)\r\n }\r\n\r\n recognition.onerror = error => {\r\n if (this.destroyed || this.recognition !== recognition) return\r\n this.finishVoice()\r\n this.emitter.emit('toolbar-voice-error', error)\r\n this.config.onVoiceError?.(error)\r\n }\r\n\r\n this.isRecording = true\r\n this.voiceBtn?.classList.add(`${this.prefix}-toolbar-btn--active`)\r\n if (this.voiceBtn) this.voiceBtn.innerHTML = ICON_MIC_OFF\r\n recognition.start()\r\n this.emitter.emit('toolbar-voice-start')\r\n this.config.onVoiceStart?.()\r\n } catch (error) {\r\n this.finishVoice()\r\n this.emitter.emit('toolbar-voice-error', error)\r\n this.config.onVoiceError?.(error)\r\n }\r\n }\r\n\r\n private stopVoice(): void {\r\n if (!this.recognition) return\r\n const recognition = this.recognition\r\n try {\r\n recognition.stop()\r\n } catch {\r\n this.finishVoice()\r\n }\r\n }\r\n\r\n private finishVoice(): void {\r\n this.isRecording = false\r\n this.recognition = null\r\n this.voiceBtn?.classList.remove(`${this.prefix}-toolbar-btn--active`)\r\n if (this.voiceBtn) this.voiceBtn.innerHTML = ICON_MIC\r\n }\r\n\r\n private setupEventListeners(): void {\r\n if (this.textareaEl) {\r\n this.textareaEl.addEventListener('input', () => {\r\n const limited = this.limitInput(this.textareaEl?.value ?? '')\r\n if (this.textareaEl && limited !== this.textareaEl.value) {\r\n this.textareaEl.value = limited\r\n }\r\n this.autoResize()\r\n this.updateSendButtonState()\r\n this.config.onInputChange?.(this.textareaEl?.value ?? '')\r\n if (this.config.enableMention && this.textareaEl) {\r\n this.mentionEngine.handleInput(\r\n this.textareaEl.value,\r\n this.textareaEl.selectionStart\r\n )\r\n }\r\n })\r\n\r\n this.textareaEl.addEventListener('keydown', event => {\r\n const mentionState = this.mentionEngine.getState()\r\n\r\n if (mentionState.active) {\r\n if (event.key === 'ArrowUp') {\r\n event.preventDefault()\r\n this.mentionEngine.moveUp()\r\n } else if (event.key === 'ArrowDown') {\r\n event.preventDefault()\r\n this.mentionEngine.moveDown()\r\n } else if (event.key === 'Enter' && !event.shiftKey) {\r\n event.preventDefault()\r\n const triggerPosition = mentionState.triggerPosition\r\n const selectionEnd = this.textareaEl.selectionEnd\r\n const member = this.mentionEngine.selectCurrent()\r\n if (member)\r\n this.insertMention(member, triggerPosition, selectionEnd)\r\n } else if (event.key === 'Escape') {\r\n event.preventDefault()\r\n this.mentionEngine.deactivate()\r\n }\r\n return\r\n }\r\n\r\n if (\r\n this.config.enterToSend !== false &&\r\n event.key === 'Enter' &&\r\n !event.shiftKey\r\n ) {\r\n event.preventDefault()\r\n this.handleSend()\r\n }\r\n })\r\n\r\n this.textareaEl.addEventListener('paste', event => {\r\n const items = event.clipboardData?.items\r\n if (!items) return\r\n const files: File[] = []\r\n for (let i = 0; i < items.length; i++) {\r\n if (items[i].kind === 'file') {\r\n const file = items[i].getAsFile()\r\n if (file) files.push(file)\r\n }\r\n }\r\n if (files.length > 0) {\r\n event.preventDefault()\r\n this.emitter.emit('toolbar-paste', files)\r\n this.config.onPaste?.(files)\r\n }\r\n })\r\n }\r\n\r\n if (this.inputArea) {\r\n const wrapper = this.inputArea.querySelector(\r\n `.${this.prefix}-input-wrapper`\r\n ) as HTMLElement | null\r\n if (wrapper) this.setupDropEvents(wrapper)\r\n }\r\n\r\n this.sendBtn?.addEventListener('click', () => {\r\n this.handleSend()\r\n })\r\n\r\n this.emitter.on(\r\n 'mention-filter',\r\n (state: { filtered: MentionItem[]; activeIndex: number }) => {\r\n this.renderMentionPanel(state.filtered, state.activeIndex)\r\n }\r\n )\r\n this.emitter.on('mention-active-change', (index: number) => {\r\n const items = this.mentionPanel?.querySelectorAll(\r\n `.${this.prefix}-mention-item`\r\n )\r\n items?.forEach((el, i) => {\r\n el.classList.toggle(`${this.prefix}-mention-item--active`, i === index)\r\n el.setAttribute('aria-selected', String(i === index))\r\n })\r\n })\r\n this.emitter.on('mention-deactivate', () => {\r\n this.hideMentionPanel()\r\n })\r\n }\r\n\r\n private setupDropEvents(wrapper: HTMLElement): void {\r\n let dragCounter = 0\r\n wrapper.addEventListener('dragenter', event => {\r\n event.preventDefault()\r\n dragCounter++\r\n if (this.dropOverlay) this.dropOverlay.style.display = 'flex'\r\n })\r\n wrapper.addEventListener('dragleave', event => {\r\n event.preventDefault()\r\n dragCounter--\r\n if (dragCounter <= 0) {\r\n dragCounter = 0\r\n if (this.dropOverlay) this.dropOverlay.style.display = 'none'\r\n }\r\n })\r\n wrapper.addEventListener('dragover', event => {\r\n event.preventDefault()\r\n })\r\n wrapper.addEventListener('drop', event => {\r\n event.preventDefault()\r\n dragCounter = 0\r\n if (this.dropOverlay) this.dropOverlay.style.display = 'none'\r\n const files = Array.from(event.dataTransfer?.files ?? [])\r\n if (files.length > 0) {\r\n this.emitter.emit('toolbar-drop', files)\r\n this.config.onDrop?.(files)\r\n }\r\n })\r\n }\r\n\r\n private handleSend(): void {\r\n const rawContent = this.textareaEl?.value ?? ''\r\n const content = rawContent.trim()\r\n if (!content) return\r\n\r\n const mentions = this.mentionEngine.parseMentions(content)\r\n this.emitter.emit('send', content, mentions)\r\n this.config.onSend?.(content, mentions)\r\n\r\n if (this.config.clearInputOnSend !== false) {\r\n this.clearInput()\r\n }\r\n this.mentionEngine.resetSelected()\r\n }\r\n\r\n private insertMention(\r\n member: MentionItem,\r\n triggerPosition?: number,\r\n selectionEnd?: number\r\n ): void {\r\n if (!this.textareaEl) return\r\n const state = this.mentionEngine.getState()\r\n const text = this.textareaEl.value\r\n const end = selectionEnd ?? this.textareaEl.selectionEnd\r\n const insertText = this.mentionEngine.getInsertText(member)\r\n\r\n const before = text.slice(0, triggerPosition ?? state.triggerPosition)\r\n const after = text.slice(end)\r\n this.textareaEl.value = this.limitInput(before + insertText + after)\r\n const newPos = Math.min(\r\n before.length + insertText.length,\r\n this.textareaEl.value.length\r\n )\r\n this.textareaEl.setSelectionRange(newPos, newPos)\r\n this.textareaEl.focus()\r\n this.autoResize()\r\n this.updateSendButtonState()\r\n this.config.onInputChange?.(this.textareaEl.value)\r\n }\r\n\r\n private insertTextAtCursor(text: string): void {\r\n if (!this.textareaEl) return\r\n const current = this.textareaEl.value\r\n const start = this.textareaEl.selectionStart\r\n const end = this.textareaEl.selectionEnd\r\n this.textareaEl.value = this.limitInput(\r\n current.slice(0, start) + text + current.slice(end)\r\n )\r\n const nextCursor = Math.min(\r\n start + text.length,\r\n this.textareaEl.value.length\r\n )\r\n this.textareaEl.setSelectionRange(nextCursor, nextCursor)\r\n this.autoResize()\r\n this.updateSendButtonState()\r\n this.config.onInputChange?.(this.textareaEl.value)\r\n }\r\n\r\n private autoResize(): void {\r\n if (!this.textareaEl) return\r\n this.textareaEl.style.height = 'auto'\r\n this.textareaEl.style.height = `${this.textareaEl.scrollHeight}px`\r\n }\r\n\r\n private updateSendButtonState(): void {\r\n if (!this.sendBtn) return\r\n this.sendBtn.disabled = !this.getInputContent().trim()\r\n }\r\n\r\n private renderLoadMoreButton(): void {\r\n this.loadMoreEl = this.el('div', `${this.prefix}-load-more`)\r\n this.loadMoreEl.textContent = this.config.loadMoreText ?? '加载更多'\r\n this.loadMoreEl.addEventListener('click', () => {\r\n if (this.destroyed || this.loadingMore) return\r\n this.setLoadingMore(true)\r\n this.emitter.emit('load-more')\r\n void this.loadMore()\r\n })\r\n this.messageListEl.appendChild(this.loadMoreEl)\r\n }\r\n\r\n private async loadMore(): Promise<void> {\r\n try {\r\n await this.config.onLoadMore?.()\r\n } catch (error) {\r\n this.emitter.emit('load-more-error', error)\r\n } finally {\r\n if (!this.destroyed) this.setLoadingMore(false)\r\n }\r\n }\r\n\r\n private renderTimeDivider(timestamp: number): void {\r\n this.messageListEl.appendChild(this.createTimeDivider(timestamp))\r\n }\r\n\r\n private createTimeDivider(timestamp: number): HTMLElement {\r\n const divider = this.el('div', `${this.prefix}-time-divider`)\r\n const span = this.document.createElement('span')\r\n span.textContent = this.formatTime(timestamp)\r\n divider.appendChild(span)\r\n return divider\r\n }\r\n\r\n private renderMessageRow(message: ChatMessage): void {\r\n this.messageListEl.appendChild(this.createMessageRow(message))\r\n }\r\n\r\n private createMessageRow(message: ChatMessage): HTMLElement {\r\n const p = this.prefix\r\n\r\n if (message.type === 'system') {\r\n const row = this.el(\r\n 'div',\r\n cx(`${p}-message-row`, `${p}-message-row--system`)\r\n )\r\n row.dataset.msgId = message.id\r\n row.dataset.timestamp = String(message.timestamp)\r\n const system = this.el('div', `${p}-system-message`)\r\n system.textContent = message.content ?? ''\r\n row.appendChild(system)\r\n return row\r\n }\r\n\r\n const isOutgoing = message.direction === 'outgoing'\r\n const row = this.el(\r\n 'div',\r\n cx(`${p}-message-row`, isOutgoing && `${p}-message-row--outgoing`)\r\n )\r\n row.dataset.msgId = message.id\r\n row.dataset.timestamp = String(message.timestamp)\r\n\r\n if (this.config.showAvatar !== false) {\r\n row.appendChild(\r\n this.createAvatar(message.sender.nickname, message.sender.avatar)\r\n )\r\n }\r\n\r\n const body = this.el('div', `${p}-message-body`)\r\n if (this.config.showNickname !== false && !isOutgoing) {\r\n const nickname = this.el('div', `${p}-nickname`)\r\n nickname.textContent = message.sender.nickname\r\n body.appendChild(nickname)\r\n }\r\n\r\n body.appendChild(this.createBubble(message, isOutgoing))\r\n body.appendChild(this.createMessageFooter(message, isOutgoing))\r\n row.appendChild(body)\r\n\r\n row.addEventListener('click', () => {\r\n this.emitter.emit('message-click', message)\r\n this.config.onMessageClick?.(message)\r\n })\r\n\r\n return row\r\n }\r\n\r\n private createAvatar(name: string, avatarUrl?: string): HTMLElement {\r\n const avatar = this.el('div', `${this.prefix}-avatar`)\r\n if (avatarUrl) {\r\n const img = this.document.createElement('img')\r\n img.src = this.safeResourceUrl(avatarUrl)\r\n img.alt = name\r\n avatar.appendChild(img)\r\n } else {\r\n avatar.textContent = name[0]?.toUpperCase() ?? ''\r\n }\r\n return avatar\r\n }\r\n\r\n private createMessageFooter(\r\n message: ChatMessage,\r\n isOutgoing: boolean\r\n ): HTMLElement {\r\n const footer = this.el('div', `${this.prefix}-message-footer`)\r\n if (this.config.showTimestamp !== false) {\r\n const time = this.el('span', `${this.prefix}-timestamp`)\r\n time.textContent = this.formatShortTime(message.timestamp)\r\n footer.appendChild(time)\r\n }\r\n if (this.config.showStatus !== false && isOutgoing && message.status) {\r\n const status = this.el(\r\n 'span',\r\n cx(`${this.prefix}-status`, `${this.prefix}-status--${message.status}`)\r\n )\r\n status.textContent = STATUS_MAP[message.status] ?? ''\r\n if (message.status === 'failed') {\r\n status.addEventListener('click', event => {\r\n event.stopPropagation()\r\n this.emitter.emit('message-retry', message)\r\n })\r\n }\r\n footer.appendChild(status)\r\n }\r\n return footer\r\n }\r\n\r\n private createBubble(message: ChatMessage, isOutgoing: boolean): HTMLElement {\r\n const p = this.prefix\r\n const dirClass = isOutgoing\r\n ? `${p}-bubble--outgoing`\r\n : `${p}-bubble--incoming`\r\n\r\n if (message.revoked) {\r\n const bubble = this.el(\r\n 'div',\r\n cx(`${p}-bubble`, dirClass, `${p}-bubble--revoked`)\r\n )\r\n bubble.textContent = '消息已撤回'\r\n return bubble\r\n }\r\n\r\n switch (message.type) {\r\n case 'image': {\r\n const bubble = this.el(\r\n 'div',\r\n cx(`${p}-bubble`, dirClass, `${p}-bubble--image`)\r\n )\r\n if (message.image) {\r\n const img = this.document.createElement('img')\r\n img.src = this.safeResourceUrl(\r\n message.image.thumbUrl ?? message.image.url\r\n )\r\n img.alt = message.content ?? ''\r\n if (message.image.width)\r\n img.width = Math.min(message.image.width, 240)\r\n bubble.appendChild(img)\r\n }\r\n return bubble\r\n }\r\n\r\n case 'file': {\r\n const bubble = this.el(\r\n 'div',\r\n cx(`${p}-bubble`, dirClass, `${p}-bubble--file`)\r\n )\r\n const icon = this.el('div', `${p}-file-icon`)\r\n icon.innerHTML = FILE_ICON\r\n bubble.appendChild(icon)\r\n\r\n const info = this.el('div', `${p}-file-info`)\r\n const name = this.el('div', `${p}-file-name`)\r\n name.textContent = message.file?.name ?? 'file'\r\n info.appendChild(name)\r\n\r\n if (message.file?.size) {\r\n const size = this.el('div', `${p}-file-size`)\r\n size.textContent = this.formatFileSize(message.file.size)\r\n info.appendChild(size)\r\n }\r\n bubble.appendChild(info)\r\n return bubble\r\n }\r\n\r\n case 'custom': {\r\n const bubble = this.el('div', cx(`${p}-bubble`, dirClass))\r\n if (message.customHtml) {\r\n this.setSanitizedCustomHtml(bubble, message.customHtml)\r\n } else {\r\n bubble.textContent = message.content ?? ''\r\n }\r\n return bubble\r\n }\r\n\r\n default: {\r\n const bubble = this.el(\r\n 'div',\r\n cx(\r\n `${p}-bubble`,\r\n dirClass,\r\n message.streaming && `${p}-bubble--streaming`\r\n )\r\n )\r\n if (message.contentBlocks?.length) {\r\n this.renderContentBlocks(bubble, message.contentBlocks)\r\n } else {\r\n bubble.innerHTML = this.renderTextContent(\r\n message.content ?? '',\r\n message.mentions\r\n )\r\n }\r\n return bubble\r\n }\r\n }\r\n }\r\n\r\n private renderContentBlocks(\r\n target: HTMLElement,\r\n blocks: ChatContentBlock[]\r\n ): void {\r\n target.replaceChildren()\r\n for (const block of blocks) {\r\n switch (block.type) {\r\n case 'text':\r\n target.appendChild(this.document.createTextNode(block.text))\r\n break\r\n case 'mention': {\r\n const mention = this.el('span', `${this.prefix}-mention-tag`)\r\n mention.dataset.mentionId = block.id\r\n mention.textContent = `@${block.label}`\r\n target.appendChild(mention)\r\n break\r\n }\r\n case 'link': {\r\n const link = this.document.createElement('a')\r\n const safeUrl = this.safeUrl(block.url, [\r\n 'http:',\r\n 'https:',\r\n 'mailto:',\r\n ])\r\n link.className = `${this.prefix}-link`\r\n link.textContent = block.label ?? block.url\r\n if (safeUrl) {\r\n link.href = safeUrl\r\n link.target = '_blank'\r\n link.rel = 'noopener noreferrer'\r\n }\r\n target.appendChild(link)\r\n break\r\n }\r\n case 'code': {\r\n const code = this.document.createElement('code')\r\n code.textContent = block.code\r\n if (block.language) code.dataset.language = block.language\r\n target.appendChild(code)\r\n break\r\n }\r\n case 'image': {\r\n const image = this.document.createElement('img')\r\n const safeUrl = this.safeResourceUrl(block.src)\r\n const width = block.width\r\n const height = block.height\r\n if (safeUrl) image.src = safeUrl\r\n image.alt = block.alt ?? ''\r\n image.loading = 'lazy'\r\n if (Number.isFinite(width) && width > 0) {\r\n image.width = width\r\n }\r\n if (Number.isFinite(height) && height > 0) {\r\n image.height = height\r\n }\r\n target.appendChild(image)\r\n break\r\n }\r\n case 'file': {\r\n const file = this.document.createElement('a')\r\n const safeUrl = this.safeResourceUrl(block.url)\r\n file.className = `${this.prefix}-file-link`\r\n file.textContent = block.name\r\n if (safeUrl) {\r\n file.href = safeUrl\r\n file.target = '_blank'\r\n file.rel = 'noopener noreferrer'\r\n }\r\n target.appendChild(file)\r\n break\r\n }\r\n case 'break':\r\n target.appendChild(this.document.createElement('br'))\r\n break\r\n case 'custom':\r\n target.appendChild(\r\n this.document.createTextNode(block.fallback ?? `[${block.kind}]`)\r\n )\r\n break\r\n }\r\n }\r\n }\r\n\r\n private renderTextContent(content: string, mentions?: MentionItem[]): string {\r\n let html = this.escapeHtml(content)\r\n\r\n if (mentions?.length) {\r\n for (const mention of mentions) {\r\n const tag = `<span class=\"${this.prefix}-mention-tag\" data-mention-id=\"${this.escapeAttr(mention.id)}\">@${this.escapeHtml(mention.name)}</span>`\r\n html = html.replace(\r\n new RegExp(`@${this.escapeRegex(mention.name)}`, 'g'),\r\n tag\r\n )\r\n }\r\n }\r\n\r\n return html.replace(\r\n /(https?:\\/\\/[^\\s<]+)/g,\r\n `<a class=\"${this.prefix}-link\" href=\"$1\" target=\"_blank\" rel=\"noopener noreferrer\">$1</a>`\r\n )\r\n }\r\n\r\n private setSanitizedCustomHtml(target: HTMLElement, html: string): void {\r\n const template = this.document.createElement('template')\r\n template.innerHTML = html\r\n const fragment = this.document.createDocumentFragment()\r\n for (const node of Array.from(template.content.childNodes)) {\r\n const sanitized = this.sanitizeCustomNode(node)\r\n if (sanitized) fragment.appendChild(sanitized)\r\n }\r\n target.replaceChildren(fragment)\r\n }\r\n\r\n private sanitizeCustomNode(node: Node): Node | null {\r\n if (node.nodeType === 3) {\r\n return this.document.createTextNode(node.textContent ?? '')\r\n }\r\n if (node.nodeType !== 1) return null\r\n\r\n const source = node as Element\r\n const tagName = source.tagName.toLowerCase()\r\n if (DANGEROUS_CUSTOM_TAGS.has(tagName)) return null\r\n\r\n if (!ALLOWED_CUSTOM_TAGS.has(tagName)) {\r\n const fragment = this.document.createDocumentFragment()\r\n for (const child of Array.from(source.childNodes)) {\r\n const sanitized = this.sanitizeCustomNode(child)\r\n if (sanitized) fragment.appendChild(sanitized)\r\n }\r\n return fragment\r\n }\r\n\r\n const element = this.document.createElement(tagName)\r\n const title = source.getAttribute('title')\r\n if (title) element.setAttribute('title', title)\r\n const ariaLabel = source.getAttribute('aria-label')\r\n if (ariaLabel) element.setAttribute('aria-label', ariaLabel)\r\n\r\n if (tagName === 'a') {\r\n const href = source.getAttribute('href')\r\n const safeHref = href\r\n ? this.safeUrl(href, ['http:', 'https:', 'mailto:'])\r\n : null\r\n if (safeHref) element.setAttribute('href', safeHref)\r\n const target = source.getAttribute('target')\r\n if (target === '_blank' || target === '_self')\r\n element.setAttribute('target', target)\r\n element.setAttribute('rel', 'noopener noreferrer')\r\n }\r\n\r\n if (tagName === 'img') {\r\n const src = source.getAttribute('src')\r\n const safeSrc = src\r\n ? this.safeUrl(src, ['http:', 'https:', 'blob:'])\r\n : null\r\n if (safeSrc) element.setAttribute('src', safeSrc)\r\n element.setAttribute('alt', source.getAttribute('alt') ?? '')\r\n for (const dimension of ['width', 'height'] as const) {\r\n const value = Number(source.getAttribute(dimension))\r\n if (Number.isFinite(value) && value > 0)\r\n element.setAttribute(dimension, String(value))\r\n }\r\n element.setAttribute('loading', 'lazy')\r\n }\r\n\r\n for (const child of Array.from(source.childNodes)) {\r\n const sanitized = this.sanitizeCustomNode(child)\r\n if (sanitized) element.appendChild(sanitized)\r\n }\r\n return element\r\n }\r\n\r\n private safeResourceUrl(value?: string): string {\r\n if (!value) return ''\r\n return this.safeUrl(value, ['http:', 'https:', 'blob:']) ?? ''\r\n }\r\n\r\n private safeUrl(value: string, protocols: string[]): string | null {\r\n const trimmed = value.trim()\r\n if (\r\n !trimmed ||\r\n [...trimmed].some(character => {\r\n const code = character.charCodeAt(0)\r\n return code < 32 || code === 127\r\n })\r\n )\r\n return null\r\n try {\r\n const resolved = new URL(trimmed, this.document.baseURI)\r\n return protocols.includes(resolved.protocol) ? trimmed : null\r\n } catch {\r\n return null\r\n }\r\n }\r\n\r\n private renderEmpty(): void {\r\n if (this.messageListEl.querySelector(`.${this.prefix}-empty`)) return\r\n const empty = this.el('div', `${this.prefix}-empty`)\r\n empty.textContent = '暂无消息'\r\n this.messageListEl.appendChild(empty)\r\n }\r\n\r\n private removeEmpty(): void {\r\n this.messageListEl.querySelector(`.${this.prefix}-empty`)?.remove()\r\n }\r\n\r\n private el(tag: string, className: string): HTMLElement {\r\n const el = this.document.createElement(tag)\r\n el.className = className\r\n return el\r\n }\r\n\r\n private getMessageElements(): HTMLElement[] {\r\n return Array.from(\r\n this.messageListEl.querySelectorAll<HTMLElement>(\r\n `.${this.prefix}-message-row`\r\n )\r\n )\r\n }\r\n\r\n private getMessageRow(id: string): HTMLElement | null {\r\n return (\r\n this.getMessageElements().find(element => element.dataset.msgId === id) ??\r\n null\r\n )\r\n }\r\n\r\n private refreshTimeDividers(): void {\r\n this.messageListEl\r\n .querySelectorAll(`.${this.prefix}-time-divider`)\r\n .forEach(divider => divider.remove())\r\n const interval = this.config.timeGroupInterval ?? 300000\r\n let previousTimestamp = 0\r\n for (const row of this.getMessageElements()) {\r\n const timestamp = Number(row.dataset.timestamp ?? 0)\r\n if (\r\n this.config.showTimestamp !== false &&\r\n timestamp - previousTimestamp > interval\r\n ) {\r\n row.before(this.createTimeDivider(timestamp))\r\n }\r\n previousTimestamp = timestamp\r\n }\r\n }\r\n\r\n private formatTime(timestamp: number): string {\r\n if (this.config.formatTime) return this.config.formatTime(timestamp)\r\n const date = new Date(timestamp)\r\n const now = new Date()\r\n const isToday = date.toDateString() === now.toDateString()\r\n if (isToday) {\r\n return date.toLocaleTimeString('zh-CN', {\r\n hour: '2-digit',\r\n minute: '2-digit',\r\n })\r\n }\r\n return date.toLocaleDateString('zh-CN', {\r\n month: '2-digit',\r\n day: '2-digit',\r\n hour: '2-digit',\r\n minute: '2-digit',\r\n })\r\n }\r\n\r\n private formatShortTime(timestamp: number): string {\r\n return new Date(timestamp).toLocaleTimeString('zh-CN', {\r\n hour: '2-digit',\r\n minute: '2-digit',\r\n })\r\n }\r\n\r\n private formatFileSize(bytes: number): string {\r\n if (bytes < 1024) return `${bytes} B`\r\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\r\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\r\n }\r\n\r\n private limitInput(content: string): string {\r\n const max = this.config.maxInputLength\r\n if (!max || max <= 0) return content\r\n return content.slice(0, max)\r\n }\r\n\r\n private escapeHtml(str: string): string {\r\n const div = this.document.createElement('div')\r\n div.textContent = str\r\n return div.innerHTML\r\n }\r\n\r\n private escapeAttr(str: string): string {\r\n return str\r\n .replace(/\"/g, '"')\r\n .replace(/'/g, ''')\r\n .replace(/</g, '<')\r\n .replace(/>/g, '>')\r\n }\r\n\r\n private escapeRegex(str: string): string {\r\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { EventEmitter } from '../core/event-emitter';
|
|
2
|
+
import type { MentionEngine } from '../core/mention-engine';
|
|
3
|
+
import type { ThemeTokens } from '../theme/tokens';
|
|
4
|
+
import type { ChatConfig, ChatMessage, MentionItem } from '../types';
|
|
5
|
+
export declare class DOMRenderer {
|
|
6
|
+
private container;
|
|
7
|
+
private document;
|
|
8
|
+
private config;
|
|
9
|
+
private emitter;
|
|
10
|
+
private mentionEngine;
|
|
11
|
+
private prefix;
|
|
12
|
+
private root;
|
|
13
|
+
private messageListEl;
|
|
14
|
+
private inputArea;
|
|
15
|
+
private textareaEl;
|
|
16
|
+
private sendBtn;
|
|
17
|
+
private mentionPanel;
|
|
18
|
+
private loadMoreEl;
|
|
19
|
+
private toolbarEl;
|
|
20
|
+
private dropOverlay;
|
|
21
|
+
private voiceBtn;
|
|
22
|
+
private loadingMore;
|
|
23
|
+
private isRecording;
|
|
24
|
+
private recognition;
|
|
25
|
+
private lastVoiceTranscript;
|
|
26
|
+
private destroyed;
|
|
27
|
+
private pendingFileInputs;
|
|
28
|
+
constructor(container: HTMLElement, config: ChatConfig, emitter: EventEmitter, mentionEngine: MentionEngine);
|
|
29
|
+
init(): void;
|
|
30
|
+
updateMessageContent(id: string, content: string): void;
|
|
31
|
+
renderMessages(messages: ChatMessage[]): void;
|
|
32
|
+
appendMessage(message: ChatMessage): void;
|
|
33
|
+
prependMessages(messages: ChatMessage[]): void;
|
|
34
|
+
updateMessage(id: string, message: ChatMessage): void;
|
|
35
|
+
removeMessage(id: string): void;
|
|
36
|
+
scrollToBottom(smooth?: boolean): void;
|
|
37
|
+
isAtBottom(threshold?: number): boolean;
|
|
38
|
+
setLoadingMore(loading: boolean): void;
|
|
39
|
+
setThemeTokens(tokens: ThemeTokens): void;
|
|
40
|
+
getRootElement(): HTMLElement;
|
|
41
|
+
focusInput(): void;
|
|
42
|
+
setInputContent(content: string): void;
|
|
43
|
+
getInputContent(): string;
|
|
44
|
+
clearInput(): void;
|
|
45
|
+
renderMentionPanel(members: MentionItem[], activeIndex: number): void;
|
|
46
|
+
hideMentionPanel(): void;
|
|
47
|
+
destroy(): void;
|
|
48
|
+
private renderInputArea;
|
|
49
|
+
private renderToolbar;
|
|
50
|
+
private createToolbarBtn;
|
|
51
|
+
private openFilePicker;
|
|
52
|
+
private toggleVoice;
|
|
53
|
+
private startVoice;
|
|
54
|
+
private stopVoice;
|
|
55
|
+
private finishVoice;
|
|
56
|
+
private setupEventListeners;
|
|
57
|
+
private setupDropEvents;
|
|
58
|
+
private handleSend;
|
|
59
|
+
private insertMention;
|
|
60
|
+
private insertTextAtCursor;
|
|
61
|
+
private autoResize;
|
|
62
|
+
private updateSendButtonState;
|
|
63
|
+
private renderLoadMoreButton;
|
|
64
|
+
private loadMore;
|
|
65
|
+
private renderTimeDivider;
|
|
66
|
+
private createTimeDivider;
|
|
67
|
+
private renderMessageRow;
|
|
68
|
+
private createMessageRow;
|
|
69
|
+
private createAvatar;
|
|
70
|
+
private createMessageFooter;
|
|
71
|
+
private createBubble;
|
|
72
|
+
private renderContentBlocks;
|
|
73
|
+
private renderTextContent;
|
|
74
|
+
private setSanitizedCustomHtml;
|
|
75
|
+
private sanitizeCustomNode;
|
|
76
|
+
private safeResourceUrl;
|
|
77
|
+
private safeUrl;
|
|
78
|
+
private renderEmpty;
|
|
79
|
+
private removeEmpty;
|
|
80
|
+
private el;
|
|
81
|
+
private getMessageElements;
|
|
82
|
+
private getMessageRow;
|
|
83
|
+
private refreshTimeDividers;
|
|
84
|
+
private formatTime;
|
|
85
|
+
private formatShortTime;
|
|
86
|
+
private formatFileSize;
|
|
87
|
+
private limitInput;
|
|
88
|
+
private escapeHtml;
|
|
89
|
+
private escapeAttr;
|
|
90
|
+
private escapeRegex;
|
|
91
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import type { EventEmitter } from '../core/event-emitter';
|
|
2
|
+
import type { MentionEngine } from '../core/mention-engine';
|
|
3
|
+
import type { ThemeTokens } from '../theme/tokens';
|
|
1
4
|
import type { ChatConfig, ChatMessage, MentionItem } from '../types';
|
|
2
|
-
import { EventEmitter } from '../core/event-emitter';
|
|
3
|
-
import { MentionEngine } from '../core/mention-engine';
|
|
4
5
|
export declare class DOMRenderer {
|
|
5
6
|
private container;
|
|
7
|
+
private document;
|
|
6
8
|
private config;
|
|
7
9
|
private emitter;
|
|
8
10
|
private mentionEngine;
|
|
@@ -21,6 +23,8 @@ export declare class DOMRenderer {
|
|
|
21
23
|
private isRecording;
|
|
22
24
|
private recognition;
|
|
23
25
|
private lastVoiceTranscript;
|
|
26
|
+
private destroyed;
|
|
27
|
+
private pendingFileInputs;
|
|
24
28
|
constructor(container: HTMLElement, config: ChatConfig, emitter: EventEmitter, mentionEngine: MentionEngine);
|
|
25
29
|
init(): void;
|
|
26
30
|
updateMessageContent(id: string, content: string): void;
|
|
@@ -32,6 +36,8 @@ export declare class DOMRenderer {
|
|
|
32
36
|
scrollToBottom(smooth?: boolean): void;
|
|
33
37
|
isAtBottom(threshold?: number): boolean;
|
|
34
38
|
setLoadingMore(loading: boolean): void;
|
|
39
|
+
setThemeTokens(tokens: ThemeTokens): void;
|
|
40
|
+
getRootElement(): HTMLElement;
|
|
35
41
|
focusInput(): void;
|
|
36
42
|
setInputContent(content: string): void;
|
|
37
43
|
getInputContent(): string;
|
|
@@ -55,6 +61,7 @@ export declare class DOMRenderer {
|
|
|
55
61
|
private autoResize;
|
|
56
62
|
private updateSendButtonState;
|
|
57
63
|
private renderLoadMoreButton;
|
|
64
|
+
private loadMore;
|
|
58
65
|
private renderTimeDivider;
|
|
59
66
|
private createTimeDivider;
|
|
60
67
|
private renderMessageRow;
|
|
@@ -62,18 +69,23 @@ export declare class DOMRenderer {
|
|
|
62
69
|
private createAvatar;
|
|
63
70
|
private createMessageFooter;
|
|
64
71
|
private createBubble;
|
|
72
|
+
private renderContentBlocks;
|
|
65
73
|
private renderTextContent;
|
|
74
|
+
private setSanitizedCustomHtml;
|
|
75
|
+
private sanitizeCustomNode;
|
|
76
|
+
private safeResourceUrl;
|
|
77
|
+
private safeUrl;
|
|
66
78
|
private renderEmpty;
|
|
67
79
|
private removeEmpty;
|
|
68
80
|
private el;
|
|
69
81
|
private getMessageElements;
|
|
82
|
+
private getMessageRow;
|
|
83
|
+
private refreshTimeDividers;
|
|
70
84
|
private formatTime;
|
|
71
85
|
private formatShortTime;
|
|
72
86
|
private formatFileSize;
|
|
73
87
|
private limitInput;
|
|
74
|
-
private getCssSizeValue;
|
|
75
88
|
private escapeHtml;
|
|
76
89
|
private escapeAttr;
|
|
77
90
|
private escapeRegex;
|
|
78
91
|
}
|
|
79
|
-
//# sourceMappingURL=dom-renderer.d.ts.map
|