1import { Controller } from "@hotwired/stimulus" 2 3const MEDIA_EXTENSIONS = /\.(mp4|mov|webm|mkv|avi|mpeg|mp3|m4a|aac|ogg|wav|flac)$/i 4 5export default class extends Controller { 6 static targets = ["input", "filePlaceholderTemplate", "chatPlaceholderTemplate"] 7 static values = { url: String, mediaUrl: String, directUploadUrl: String } 8 9 #abortControllers = new Set() 10 11 disconnect() { 12 for (const ac of this.#abortControllers) ac.abort() 13 this.#abortControllers.clear() 14 } 15 16 upload() { 17 const file = this.inputTarget.files[0] 18 if (!file) return 19 this.inputTarget.value = "" 20 21 const isMedia = file.type.startsWith("audio/") || file.type.startsWith("video/") || MEDIA_EXTENSIONS.test(file.name) 22 const id = crypto.randomUUID() 23 const placeholder = this.#insertPlaceholder(id, file.name) 24 const ac = new AbortController() 25 this.#abortControllers.add(ac) 26 27 const cleanup = () => { 28 ac.abort() 29 this.#abortControllers.delete(ac) 30 placeholder.remove() 31 } 32 33 document.addEventListener("upload-manager:progress", (e) => { 34 if (e.detail.id === id) this.#updateProgress(placeholder, e.detail.percent) 35 }, { signal: ac.signal }) 36 37 document.addEventListener("upload-manager:complete", (e) => { 38 if (e.detail.id === id) cleanup() 39 }, { signal: ac.signal }) 40 41 document.addEventListener("upload-manager:error", (e) => { 42 if (e.detail.id === id) cleanup() 43 }, { signal: ac.signal }) 44 45 document.dispatchEvent(new CustomEvent("upload-manager:request", { 46 detail: { 47 id, 48 file, 49 directUploadUrl: this.directUploadUrlValue, 50 destinationUrl: isMedia && this.hasMediaUrlValue ? this.mediaUrlValue : this.urlValue, 51 body: (signedId) => isMedia 52 ? { signed_id: signedId } 53 : { document: { signed_id: signedId, filename: file.name } }, 54 fileName: file.name, 55 }, 56 })) 57 } 58 59 get #container() { 60 return document.getElementById("files") || document.getElementById("recordings") 61 } 62 63 get #isFilesPage() { 64 return !!document.getElementById("files") 65 } 66 67 #insertPlaceholder(id, filename) { 68 const template = this.#isFilesPage ? this.filePlaceholderTemplateTarget : this.chatPlaceholderTemplateTarget 69 const el = template.content.firstElementChild.cloneNode(true) 70 el.dataset.uploadId = id 71 el.querySelector("[data-filename]").textContent = filename 72 this.#container?.prepend(el) 73 return el 74 } 75 76 #updateProgress(el, percent) { 77 const radial = el.querySelector(".radial-progress") 78 if (!radial) return 79 radial.style.setProperty("--value", percent) 80 radial.querySelector("span").textContent = `${percent}%` 81 if (percent >= 100) { 82 const statusEl = el.querySelector("[data-status]") 83 if (statusEl) statusEl.textContent = "Processing..." 84 } 85 } 86}
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.