import { SendButton } from '@/components/SendButton' import { BotContext, InputSubmitContent } from '@/types' import { FileInputBlock } from '@indite.io/schemas' import { createSignal, Match, Show, Switch, For } from 'solid-js' import { Button } from '@/components/Button' import { Spinner } from '@/components/Spinner' import { uploadFiles } from '../helpers/uploadFiles' import { guessApiHost } from '@/utils/guessApiHost' import { defaultFileInputOptions } from '@indite.io/schemas/features/blocks/inputs/file/constants' import { isDefined } from '@indite.io/lib' import { SelectedFile } from './SelectedFile' import { sanitizeNewFile } from '../helpers/sanitizeSelectedFiles' import { toaster } from '@/utils/toaster' type Props = { context: BotContext block: FileInputBlock onSubmit: (url: InputSubmitContent) => void onSkip: (label: string) => void } export const FileUploadForm = (props: Props) => { const [selectedFiles, setSelectedFiles] = createSignal([]) const [isUploading, setIsUploading] = createSignal(false) const [uploadProgressPercent, setUploadProgressPercent] = createSignal(0) const [isDraggingOver, setIsDraggingOver] = createSignal(false) const onNewFiles = (files: FileList) => { const newFiles = Array.from(files) .map((file) => sanitizeNewFile({ existingFiles: selectedFiles(), newFile: file, params: { sizeLimit: props.block.options && 'sizeLimit' in props.block.options ? props.block.options.sizeLimit : undefined, }, onError: ({ description, title }) => toaster.create({ title, description, }), }) ) .filter(isDefined) if (newFiles.length === 0) return if (!props.block.options?.isMultipleAllowed) return startSingleFileUpload(newFiles[0]) setSelectedFiles([...selectedFiles(), ...newFiles]) } const handleSubmit = async (e: SubmitEvent) => { e.preventDefault() if (selectedFiles().length === 0) return startFilesUpload(selectedFiles()) } const startSingleFileUpload = async (file: File) => { setIsUploading(true) const urls = await uploadFiles({ apiHost: props.context.apiHost ?? guessApiHost({ ignoreChatApiUrl: true }), files: [ { file, input: { sessionId: props.context.sessionId, fileName: file.name, }, }, ], }) setIsUploading(false) if (urls.length && urls[0]) return props.onSubmit({ type: 'text', label: props.block.options?.labels?.success?.single ?? defaultFileInputOptions.labels.success.single, value: urls[0] ? encodeUrl(urls[0].url) : '', attachments: [{ type: file.type, url: urls[0]!.url }], }) toaster.create({ description: 'An error occured while uploading the file' }) } const startFilesUpload = async (files: File[]) => { setIsUploading(true) const urls = await uploadFiles({ apiHost: props.context.apiHost ?? guessApiHost({ ignoreChatApiUrl: true }), files: files.map((file) => ({ file: file, input: { sessionId: props.context.sessionId, fileName: file.name, }, })), onUploadProgress: setUploadProgressPercent, }) setIsUploading(false) setUploadProgressPercent(0) if (urls.length !== files.length) return toaster.create({ description: 'An error occured while uploading the files', }) props.onSubmit({ type: 'text', label: urls.length > 1 ? ( props.block.options?.labels?.success?.multiple ?? defaultFileInputOptions.labels.success.multiple ).replaceAll('{total}', urls.length.toString()) : props.block.options?.labels?.success?.single ?? defaultFileInputOptions.labels.success.single, value: urls .filter(isDefined) .map(({ url }) => encodeUrl(url)) .join(', '), attachments: urls.filter(isDefined), }) } const handleDragOver = (e: DragEvent) => { e.preventDefault() setIsDraggingOver(true) } const handleDragLeave = () => setIsDraggingOver(false) const handleDropFile = (e: DragEvent) => { e.preventDefault() e.stopPropagation() if (!e.dataTransfer?.files) return onNewFiles(e.dataTransfer.files) } const clearFiles = () => setSelectedFiles([]) const skip = () => props.onSkip( props.block.options?.labels?.skip ?? defaultFileInputOptions.labels.skip ) const removeSelectedFile = (index: number) => { setSelectedFiles((selectedFiles) => selectedFiles.filter((_, i) => i !== index) ) } return (