mirror of
https://github.com/open-webui/open-webui
synced 2024-12-29 15:25:29 +00:00
enh: image compression
This commit is contained in:
parent
591aac5e16
commit
326514be4e
@ -7,7 +7,7 @@
|
|||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
import { config, mobile, settings } from '$lib/stores';
|
import { config, mobile, settings } from '$lib/stores';
|
||||||
import { blobToFile } from '$lib/utils';
|
import { blobToFile, compressImage } from '$lib/utils';
|
||||||
|
|
||||||
import Tooltip from '../common/Tooltip.svelte';
|
import Tooltip from '../common/Tooltip.svelte';
|
||||||
import RichTextInput from '../common/RichTextInput.svelte';
|
import RichTextInput from '../common/RichTextInput.svelte';
|
||||||
@ -100,15 +100,28 @@
|
|||||||
|
|
||||||
if (['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(file['type'])) {
|
if (['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(file['type'])) {
|
||||||
let reader = new FileReader();
|
let reader = new FileReader();
|
||||||
reader.onload = (event) => {
|
|
||||||
|
reader.onload = async (event) => {
|
||||||
|
let imageUrl = event.target.result;
|
||||||
|
|
||||||
|
if ($settings?.imageCompression ?? false) {
|
||||||
|
const width = $settings?.imageCompressionSize?.width ?? null;
|
||||||
|
const height = $settings?.imageCompressionSize?.height ?? null;
|
||||||
|
|
||||||
|
if (width || height) {
|
||||||
|
imageUrl = await compressImage(imageUrl, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
files = [
|
files = [
|
||||||
...files,
|
...files,
|
||||||
{
|
{
|
||||||
type: 'image',
|
type: 'image',
|
||||||
url: `${event.target.result}`
|
url: `${imageUrl}`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
} else {
|
} else {
|
||||||
uploadFileHandler(file);
|
uploadFileHandler(file);
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
showControls
|
showControls
|
||||||
} from '$lib/stores';
|
} from '$lib/stores';
|
||||||
|
|
||||||
import { blobToFile, createMessagesList, findWordIndices } from '$lib/utils';
|
import { blobToFile, compressImage, createMessagesList, findWordIndices } from '$lib/utils';
|
||||||
import { transcribeAudio } from '$lib/apis/audio';
|
import { transcribeAudio } from '$lib/apis/audio';
|
||||||
import { uploadFile } from '$lib/apis/files';
|
import { uploadFile } from '$lib/apis/files';
|
||||||
import { getTools } from '$lib/apis/tools';
|
import { getTools } from '$lib/apis/tools';
|
||||||
@ -244,12 +244,23 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let reader = new FileReader();
|
let reader = new FileReader();
|
||||||
reader.onload = (event) => {
|
reader.onload = async (event) => {
|
||||||
|
let imageUrl = event.target.result;
|
||||||
|
|
||||||
|
if ($settings?.imageCompression ?? false) {
|
||||||
|
const width = $settings?.imageCompressionSize?.width ?? null;
|
||||||
|
const height = $settings?.imageCompressionSize?.height ?? null;
|
||||||
|
|
||||||
|
if (width || height) {
|
||||||
|
imageUrl = await compressImage(imageUrl, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
files = [
|
files = [
|
||||||
...files,
|
...files,
|
||||||
{
|
{
|
||||||
type: 'image',
|
type: 'image',
|
||||||
url: `${event.target.result}`
|
url: `${imageUrl}`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
@ -37,6 +37,12 @@
|
|||||||
let chatBubble = true;
|
let chatBubble = true;
|
||||||
let chatDirection: 'LTR' | 'RTL' = 'LTR';
|
let chatDirection: 'LTR' | 'RTL' = 'LTR';
|
||||||
|
|
||||||
|
let imageCompression = false;
|
||||||
|
let imageCompressionSize = {
|
||||||
|
width: '',
|
||||||
|
height: ''
|
||||||
|
};
|
||||||
|
|
||||||
// Admin - Show Update Available Toast
|
// Admin - Show Update Available Toast
|
||||||
let showUpdateToast = true;
|
let showUpdateToast = true;
|
||||||
let showChangelog = true;
|
let showChangelog = true;
|
||||||
@ -95,6 +101,11 @@
|
|||||||
saveSettings({ voiceInterruption: voiceInterruption });
|
saveSettings({ voiceInterruption: voiceInterruption });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleImageCompression = async () => {
|
||||||
|
imageCompression = !imageCompression;
|
||||||
|
saveSettings({ imageCompression });
|
||||||
|
};
|
||||||
|
|
||||||
const toggleHapticFeedback = async () => {
|
const toggleHapticFeedback = async () => {
|
||||||
hapticFeedback = !hapticFeedback;
|
hapticFeedback = !hapticFeedback;
|
||||||
saveSettings({ hapticFeedback: hapticFeedback });
|
saveSettings({ hapticFeedback: hapticFeedback });
|
||||||
@ -176,7 +187,8 @@
|
|||||||
|
|
||||||
const updateInterfaceHandler = async () => {
|
const updateInterfaceHandler = async () => {
|
||||||
saveSettings({
|
saveSettings({
|
||||||
models: [defaultModelId]
|
models: [defaultModelId],
|
||||||
|
imageCompressionSize: imageCompressionSize
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -206,6 +218,9 @@
|
|||||||
|
|
||||||
hapticFeedback = $settings.hapticFeedback ?? false;
|
hapticFeedback = $settings.hapticFeedback ?? false;
|
||||||
|
|
||||||
|
imageCompression = $settings.imageCompression ?? false;
|
||||||
|
imageCompressionSize = $settings.imageCompressionSize ?? { width: '', height: '' };
|
||||||
|
|
||||||
defaultModelId = $settings?.models?.at(0) ?? '';
|
defaultModelId = $settings?.models?.at(0) ?? '';
|
||||||
if ($config?.default_models) {
|
if ($config?.default_models) {
|
||||||
defaultModelId = $config.default_models.split(',')[0];
|
defaultModelId = $config.default_models.split(',')[0];
|
||||||
@ -662,6 +677,53 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class=" my-1.5 text-sm font-medium">{$i18n.t('File')}</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class=" py-0.5 flex w-full justify-between">
|
||||||
|
<div class=" self-center text-xs">{$i18n.t('Image Compression')}</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="p-1 px-3 text-xs flex rounded transition"
|
||||||
|
on:click={() => {
|
||||||
|
toggleImageCompression();
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{#if imageCompression === true}
|
||||||
|
<span class="ml-2 self-center">{$i18n.t('On')}</span>
|
||||||
|
{:else}
|
||||||
|
<span class="ml-2 self-center">{$i18n.t('Off')}</span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if imageCompression}
|
||||||
|
<div>
|
||||||
|
<div class=" py-0.5 flex w-full justify-between text-xs">
|
||||||
|
<div class=" self-center text-xs">{$i18n.t('Image Max Compression Size')}</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
bind:value={imageCompressionSize.width}
|
||||||
|
type="number"
|
||||||
|
class="w-20 bg-transparent outline-none text-center"
|
||||||
|
min="0"
|
||||||
|
placeholder="Width"
|
||||||
|
/>x
|
||||||
|
<input
|
||||||
|
bind:value={imageCompressionSize.height}
|
||||||
|
type="number"
|
||||||
|
class="w-20 bg-transparent outline-none text-center"
|
||||||
|
min="0"
|
||||||
|
placeholder="Height"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ function escapeRegExp(string: string): string {
|
|||||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const replaceTokens = (content, sourceIds, char, user) => {
|
export const replaceTokens = (content, sourceIds, char, user) => {
|
||||||
const charToken = /{{char}}/gi;
|
const charToken = /{{char}}/gi;
|
||||||
const userToken = /{{user}}/gi;
|
const userToken = /{{user}}/gi;
|
||||||
@ -189,6 +190,72 @@ export const canvasPixelTest = () => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const compressImage = async (imageUrl, maxWidth, maxHeight) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
let width = img.width;
|
||||||
|
let height = img.height;
|
||||||
|
|
||||||
|
// Maintain aspect ratio while resizing
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (maxWidth && maxHeight) {
|
||||||
|
// Resize with both dimensions defined (preserves aspect ratio)
|
||||||
|
|
||||||
|
if (width <= maxWidth && height <= maxHeight) {
|
||||||
|
resolve(imageUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (width / height > maxWidth / maxHeight) {
|
||||||
|
height = Math.round((maxWidth * height) / width);
|
||||||
|
width = maxWidth;
|
||||||
|
} else {
|
||||||
|
width = Math.round((maxHeight * width) / height);
|
||||||
|
height = maxHeight;
|
||||||
|
}
|
||||||
|
} else if (maxWidth) {
|
||||||
|
// Only maxWidth defined
|
||||||
|
|
||||||
|
if (width <= maxWidth) {
|
||||||
|
resolve(imageUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
height = Math.round((maxWidth * height) / width);
|
||||||
|
width = maxWidth;
|
||||||
|
} else if (maxHeight) {
|
||||||
|
// Only maxHeight defined
|
||||||
|
|
||||||
|
if (height <= maxHeight) {
|
||||||
|
resolve(imageUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
width = Math.round((maxHeight * width) / height);
|
||||||
|
height = maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
context.drawImage(img, 0, 0, width, height);
|
||||||
|
|
||||||
|
// Get compressed image URL
|
||||||
|
const compressedUrl = canvas.toDataURL();
|
||||||
|
resolve(compressedUrl);
|
||||||
|
};
|
||||||
|
img.onerror = (error) => reject(error);
|
||||||
|
img.src = imageUrl;
|
||||||
|
});
|
||||||
|
}
|
||||||
export const generateInitialsImage = (name) => {
|
export const generateInitialsImage = (name) => {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
|
Loading…
Reference in New Issue
Block a user