import { ref } from 'vue'
import { useChatStore } from '@/stores/chat.js'
import { uploadFile } from '@/api/index.js'
export function useFileUpload() {
const chat = useChatStore()
const isDragging = ref(false)
const uploadProgress = ref(0)
const uploading = ref(false)
// ─── Image handling ──────────────────────────────────────────────────────
function addImageFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = async (e) => {
try {
const resized = await resizeImage(e.target.result, 1024, 0.9)
chat.pendingImages.push(resized)
resolve()
} catch (err) {
reject(err)
}
}
reader.onerror = reject
reader.readAsDataURL(file)
})
}
function resizeImage(dataUrl, maxDim = 1024, quality = 0.9) {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => {
let w = img.width
let h = img.height
if (w > maxDim || h > maxDim) {
const ratio = maxDim / Math.max(w, h)
w = Math.round(w * ratio)
h = Math.round(h * ratio)
}
const canvas = document.createElement('canvas')
canvas.width = w
canvas.height = h
const ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0, w, h)
canvas.toBlob((blob) => {
if (!blob) { reject(new Error('Canvas toBlob failed')); return }
const r = new FileReader()
r.onload = (ev) => resolve(ev.target.result)
r.onerror = reject
r.readAsDataURL(blob)
}, 'image/jpeg', quality)
}
img.onerror = reject
img.src = dataUrl
})
}
function removeImage(index) {
chat.pendingImages.splice(index, 1)
}
// ─── File handling ───────────────────────────────────────────────────────
async function addFile(file) {
if (!chat.currentId) return
uploading.value = true
uploadProgress.value = 0
try {
const result = await uploadFile(chat.currentId, file)
chat.pendingFiles.push(result)
chat.fetchFiles(chat.currentId)
} finally {
uploading.value = false
uploadProgress.value = 0
}
}
function removeFile(index) {
chat.pendingFiles.splice(index, 1)
}
// ─── File picker ─────────────────────────────────────────────────────────
async function handleFileInput(event) {
const files = Array.from(event.target.files ?? [])
event.target.value = ''
await processFiles(files)
}
// ─── Image paste ─────────────────────────────────────────────────────────
async function handlePaste(event) {
const items = Array.from(event.clipboardData?.items ?? [])
const imageItems = items.filter(i => i.type.startsWith('image/'))
if (!imageItems.length) return
event.preventDefault()
for (const item of imageItems) {
const file = item.getAsFile()
if (file) await addImageFile(file)
}
}
// ─── Drag and drop ───────────────────────────────────────────────────────
function handleDragEnter(event) {
event.preventDefault()
if (hasFiles(event)) isDragging.value = true
}
function handleDragOver(event) {
event.preventDefault()
}
function handleDragLeave(event) {
// Only clear if leaving the drop zone entirely
if (!event.currentTarget.contains(event.relatedTarget)) {
isDragging.value = false
}
}
async function handleDrop(event) {
event.preventDefault()
isDragging.value = false
const files = Array.from(event.dataTransfer?.files ?? [])
await processFiles(files)
}
// ─── Shared processing ───────────────────────────────────────────────────
async function processFiles(files) {
for (const file of files) {
if (file.type.startsWith('image/')) {
await addImageFile(file)
} else {
await addFile(file)
}
}
}
function clearPending() {
chat.pendingImages = []
chat.pendingFiles = []
}
function hasFiles(event) {
return Array.from(event.dataTransfer?.types ?? []).includes('Files')
}
return {
isDragging,
uploading,
uploadProgress,
handleFileInput,
handlePaste,
handleDragEnter,
handleDragOver,
handleDragLeave,
handleDrop,
removeImage,
removeFile,
clearPending
}
}