mirror of
https://github.com/open-webui/open-webui
synced 2025-06-26 18:26:48 +00:00
Merge pull request #4917 from Yanyutin753/upload_files_limit
🤖 Limit the size and number of uploaded files
This commit is contained in:
@@ -359,8 +359,11 @@
|
||||
<Models />
|
||||
{:else if selectedTab === 'documents'}
|
||||
<Documents
|
||||
saveHandler={() => {
|
||||
on:save={async () => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
|
||||
await tick();
|
||||
await config.set(await getBackendConfig());
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'web'}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { onMount, getContext, createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
import { getDocs } from '$lib/apis/documents';
|
||||
import { deleteAllFiles, deleteFileById } from '$lib/apis/files';
|
||||
import {
|
||||
@@ -18,14 +22,12 @@
|
||||
import ResetVectorDBConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
|
||||
import { documents, models } from '$lib/stores';
|
||||
import { onMount, getContext } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let saveHandler: Function;
|
||||
|
||||
let scanDirLoading = false;
|
||||
let updateEmbeddingModelLoading = false;
|
||||
let updateRerankingModelLoading = false;
|
||||
@@ -37,6 +39,9 @@
|
||||
let embeddingModel = '';
|
||||
let rerankingModel = '';
|
||||
|
||||
let fileMaxSize = null;
|
||||
let fileMaxCount = null;
|
||||
|
||||
let contentExtractionEngine = 'default';
|
||||
let tikaServerUrl = '';
|
||||
let showTikaServerUrl = false;
|
||||
@@ -161,19 +166,22 @@
|
||||
};
|
||||
|
||||
const submitHandler = async () => {
|
||||
embeddingModelUpdateHandler();
|
||||
await embeddingModelUpdateHandler();
|
||||
|
||||
if (querySettings.hybrid) {
|
||||
rerankingModelUpdateHandler();
|
||||
await rerankingModelUpdateHandler();
|
||||
}
|
||||
|
||||
if (contentExtractionEngine === 'tika' && tikaServerUrl === '') {
|
||||
toast.error($i18n.t('Tika Server URL required.'));
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await updateRAGConfig(localStorage.token, {
|
||||
pdf_extract_images: pdfExtractImages,
|
||||
file: {
|
||||
max_size: fileMaxSize === '' ? null : fileMaxSize,
|
||||
max_count: fileMaxCount === '' ? null : fileMaxCount
|
||||
},
|
||||
chunk: {
|
||||
chunk_overlap: chunkOverlap,
|
||||
chunk_size: chunkSize
|
||||
@@ -185,6 +193,8 @@
|
||||
});
|
||||
|
||||
await updateQuerySettings(localStorage.token, querySettings);
|
||||
|
||||
dispatch('save');
|
||||
};
|
||||
|
||||
const setEmbeddingConfig = async () => {
|
||||
@@ -218,7 +228,6 @@
|
||||
await setRerankingConfig();
|
||||
|
||||
querySettings = await getQuerySettings(localStorage.token);
|
||||
|
||||
const res = await getRAGConfig(localStorage.token);
|
||||
|
||||
if (res) {
|
||||
@@ -230,6 +239,9 @@
|
||||
contentExtractionEngine = res.content_extraction.engine;
|
||||
tikaServerUrl = res.content_extraction.tika_server_url;
|
||||
showTikaServerUrl = contentExtractionEngine === 'tika';
|
||||
|
||||
fileMaxSize = res?.file.max_size ?? '';
|
||||
fileMaxCount = res?.file.max_count ?? '';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -266,7 +278,6 @@
|
||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||
on:submit|preventDefault={() => {
|
||||
submitHandler();
|
||||
saveHandler();
|
||||
}}
|
||||
>
|
||||
<div class=" space-y-2.5 overflow-y-scroll scrollbar-hidden h-full pr-1.5">
|
||||
@@ -610,6 +621,62 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-850" />
|
||||
|
||||
<div class="">
|
||||
<div class="text-sm font-medium">{$i18n.t('Files')}</div>
|
||||
|
||||
<div class=" my-2 flex gap-1.5">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Max Upload Size')}
|
||||
</div>
|
||||
|
||||
<div class="self-center">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.'
|
||||
)}
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Leave empty for unlimited')}
|
||||
bind:value={fileMaxSize}
|
||||
autocomplete="off"
|
||||
min="0"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" w-full">
|
||||
<div class="self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Max Upload Count')}
|
||||
</div>
|
||||
<div class="self-center">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.'
|
||||
)}
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Leave empty for unlimited')}
|
||||
bind:value={fileMaxCount}
|
||||
autocomplete="off"
|
||||
min="0"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-850" />
|
||||
|
||||
<div class=" ">
|
||||
|
||||
@@ -542,6 +542,16 @@
|
||||
`Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.`
|
||||
)
|
||||
);
|
||||
} else if (
|
||||
($config?.file?.max_count ?? null) !== null &&
|
||||
files.length + chatFiles.length > $config?.file?.max_count
|
||||
) {
|
||||
console.log(chatFiles.length, files.length);
|
||||
toast.error(
|
||||
$i18n.t(`You can only chat with a maximum of {{maxCount}} file(s) at a time.`, {
|
||||
maxCount: $config?.file?.max_count
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// Reset chat input textarea
|
||||
const chatTextAreaElement = document.getElementById('chat-textarea');
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
user as _user
|
||||
} from '$lib/stores';
|
||||
import { blobToFile, findWordIndices } from '$lib/utils';
|
||||
import { processDocToVectorDB } from '$lib/apis/rag';
|
||||
|
||||
import { transcribeAudio } from '$lib/apis/audio';
|
||||
import { processDocToVectorDB } from '$lib/apis/rag';
|
||||
import { uploadFile } from '$lib/apis/files';
|
||||
|
||||
import {
|
||||
SUPPORTED_FILE_TYPE,
|
||||
SUPPORTED_FILE_EXTENSIONS,
|
||||
@@ -169,6 +171,44 @@
|
||||
}
|
||||
};
|
||||
|
||||
const inputFilesHandler = async (inputFiles) => {
|
||||
inputFiles.forEach((file) => {
|
||||
console.log(file, file.name.split('.').at(-1));
|
||||
|
||||
if (
|
||||
($config?.file?.max_size ?? null) !== null &&
|
||||
file.size > ($config?.file?.max_size ?? 0) * 1024 * 1024
|
||||
) {
|
||||
toast.error(
|
||||
$i18n.t(`File size should not exceed {{maxSize}} MB.`, {
|
||||
maxSize: $config?.file?.max_size
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(file['type'])) {
|
||||
if (visionCapableModels.length === 0) {
|
||||
toast.error($i18n.t('Selected model(s) do not support image inputs'));
|
||||
return;
|
||||
}
|
||||
let reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
files = [
|
||||
...files,
|
||||
{
|
||||
type: 'image',
|
||||
url: `${event.target.result}`
|
||||
}
|
||||
];
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
uploadFileHandler(file);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
window.setTimeout(() => chatTextAreaElement?.focus(), 0);
|
||||
|
||||
@@ -196,30 +236,9 @@
|
||||
|
||||
if (e.dataTransfer?.files) {
|
||||
const inputFiles = Array.from(e.dataTransfer?.files);
|
||||
|
||||
if (inputFiles && inputFiles.length > 0) {
|
||||
inputFiles.forEach((file) => {
|
||||
console.log(file, file.name.split('.').at(-1));
|
||||
if (['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(file['type'])) {
|
||||
if (visionCapableModels.length === 0) {
|
||||
toast.error($i18n.t('Selected model(s) do not support image inputs'));
|
||||
return;
|
||||
}
|
||||
let reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
files = [
|
||||
...files,
|
||||
{
|
||||
type: 'image',
|
||||
url: `${event.target.result}`
|
||||
}
|
||||
];
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
uploadFileHandler(file);
|
||||
}
|
||||
});
|
||||
console.log(inputFiles);
|
||||
inputFilesHandler(inputFiles);
|
||||
} else {
|
||||
toast.error($i18n.t(`File not found.`));
|
||||
}
|
||||
@@ -341,27 +360,7 @@
|
||||
on:change={async () => {
|
||||
if (inputFiles && inputFiles.length > 0) {
|
||||
const _inputFiles = Array.from(inputFiles);
|
||||
_inputFiles.forEach((file) => {
|
||||
if (['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(file['type'])) {
|
||||
if (visionCapableModels.length === 0) {
|
||||
toast.error($i18n.t('Selected model(s) do not support image inputs'));
|
||||
return;
|
||||
}
|
||||
let reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
files = [
|
||||
...files,
|
||||
{
|
||||
type: 'image',
|
||||
url: `${event.target.result}`
|
||||
}
|
||||
];
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
uploadFileHandler(file);
|
||||
}
|
||||
});
|
||||
inputFilesHandler(_inputFiles);
|
||||
} else {
|
||||
toast.error($i18n.t(`File not found.`));
|
||||
}
|
||||
@@ -653,16 +652,16 @@
|
||||
}
|
||||
}}
|
||||
rows="1"
|
||||
on:input={(e) => {
|
||||
on:input={async (e) => {
|
||||
e.target.style.height = '';
|
||||
e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
|
||||
user = null;
|
||||
}}
|
||||
on:focus={(e) => {
|
||||
on:focus={async (e) => {
|
||||
e.target.style.height = '';
|
||||
e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
|
||||
}}
|
||||
on:paste={(e) => {
|
||||
on:paste={async (e) => {
|
||||
const clipboardData = e.clipboardData || window.clipboardData;
|
||||
|
||||
if (clipboardData && clipboardData.items) {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
let importFiles = '';
|
||||
|
||||
let inputFiles = '';
|
||||
|
||||
let query = '';
|
||||
let documentsImportInputElement: HTMLInputElement;
|
||||
let tags = [];
|
||||
|
||||
Reference in New Issue
Block a user