1import { Controller } from "@hotwired/stimulus" 2 3export default class extends Controller { 4 static targets = ["content", "submit", "attachments", "attachmentButton", "attachmentCount"] 5 6 appendUserMessage() { 7 const content = this.contentTarget.value.trim() 8 const attachmentFiles = Array.from(this.attachmentsTarget.files || []) 9 if (content === "" && attachmentFiles.length === 0) return 10 11 document.getElementById("ai-chat-messages-empty")?.remove() 12 document.getElementById("ai-chat-messages")?.appendChild(this.buildUserMessage(content, attachmentFiles)) 13 14 // ã¹ã¯ãã¼ã«ãä¸çªä¸ã«ç§»å 15 const scrollContainer = document.getElementById("ai-chat-messages") 16 if (scrollContainer) { 17 scrollContainer.scrollTop = scrollContainer.scrollHeight 18 } 19 20 const statusElement = document.getElementById("ai-chat-thread-status") 21 if (statusElement) { 22 statusElement.textContent = "AIãå¿çä¸ã§ã..." 23 } 24 25 // ãã©ã¦ã¶ã®ãã©ã¼ã éä¿¡å¦çï¼submitï¼ã妨ããªããããdisabledåãæ¬¡ã®ã¤ãã³ãã«ã¼ãã«åã 26 setTimeout(() => { 27 this.contentTarget.disabled = true 28 this.attachmentsTarget.disabled = true 29 this.attachmentButtonTarget.disabled = true 30 this.submitTarget.disabled = true 31 }, 0) 32 } 33 34 openFileDialog(event) { 35 event.preventDefault() 36 this.attachmentsTarget.focus() 37 this.attachmentsTarget.click() 38 } 39 40 previewAttachments() { 41 const attachmentCount = this.attachmentsTarget.files.length 42 this.attachmentButtonTarget.classList.toggle("has-attachments", attachmentCount > 0) 43 this.attachmentCountTarget.hidden = attachmentCount === 0 44 this.attachmentCountTarget.textContent = attachmentCount.toString() 45 } 46 47 buildUserMessage(content, attachmentFiles) { 48 const wrapper = document.createElement("div") 49 wrapper.className = "ai-chat-message-wrapper user-message" 50 wrapper.dataset.testid = "ai-chat-message-user" 51 52 const bubble = document.createElement("div") 53 bubble.className = "ai-chat-message-bubble" 54 55 if (content !== "") { 56 const text = document.createElement("p") 57 text.textContent = content 58 bubble.appendChild(text) 59 } 60 61 if (attachmentFiles.length > 0) { 62 bubble.appendChild(this.buildAttachmentPreview(attachmentFiles)) 63 } 64 65 wrapper.appendChild(bubble) 66 67 return wrapper 68 } 69 70 buildAttachmentPreview(attachmentFiles) { 71 const attachments = document.createElement("div") 72 attachments.className = "ai-chat-message-attachments" 73 74 attachmentFiles.forEach((attachmentFile) => { 75 const image = document.createElement("img") 76 image.className = "ai-chat-message-image" 77 image.alt = attachmentFile.name 78 image.dataset.testid = "ai-chat-message-image" 79 80 const objectUrl = URL.createObjectURL(attachmentFile) 81 image.src = objectUrl 82 image.onload = () => URL.revokeObjectURL(objectUrl) 83 84 attachments.appendChild(image) 85 }) 86 87 return attachments 88 } 89};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.