ui: Add max image size option (#22849)

* webui: Add max image size option

* remove magic numbers

* support all image formats

* use const

* Move regex to match b64 images to constants

* use SETTINGS_KEYS to get max image resolution setting

* Do not touch the image if already under the size threshold
This commit is contained in:
stduhpf
2026-05-21 00:00:09 +02:00
committed by GitHub
parent ad27757261
commit 3a479c9132
7 changed files with 153 additions and 52 deletions
+50 -37
View File
@@ -5,7 +5,8 @@ import {
ATTACHMENT_LABEL_PDF_FILE,
ATTACHMENT_LABEL_MCP_PROMPT,
ATTACHMENT_LABEL_MCP_RESOURCE,
LEGACY_AGENTIC_REGEX
LEGACY_AGENTIC_REGEX,
SETTINGS_KEYS
} from '$lib/constants';
import {
AttachmentType,
@@ -27,6 +28,9 @@ import type {
DatabaseMessageExtraMcpResource
} from '$lib/types';
import { modelsStore } from '$lib/stores/models.svelte';
import { settingsStore } from '../stores/settings.svelte';
import { capImageDataURLSize } from '../utils/cap-img-size';
import { MEGAPIXELS_TO_PIXELS } from '$lib/constants/image-size';
function getAudioInputFormat(mimeType: string): AudioInputFormat {
const normalizedMimeType = mimeType.trim().toLowerCase();
@@ -156,26 +160,28 @@ export class ChatService {
continueFinalMessage
} = options;
const normalizedMessages: ApiChatMessageData[] = messages
.map((msg) => {
if ('id' in msg && 'convId' in msg && 'timestamp' in msg) {
const dbMsg = msg as DatabaseMessage & { extra?: DatabaseMessageExtra[] };
const normalizedMessages: ApiChatMessageData[] = (
await Promise.all(
messages.map((msg) => {
if ('id' in msg && 'convId' in msg && 'timestamp' in msg) {
const dbMsg = msg as DatabaseMessage & { extra?: DatabaseMessageExtra[] };
return ChatService.convertDbMessageToApiChatMessageData(dbMsg);
} else {
return msg as ApiChatMessageData;
}
})
.filter((msg) => {
// Filter out empty system messages
if (msg.role === MessageRole.SYSTEM) {
const content = typeof msg.content === 'string' ? msg.content : '';
return ChatService.convertDbMessageToApiChatMessageData(dbMsg);
} else {
return msg as ApiChatMessageData;
}
})
)
).filter((msg: { role: ChatRole; content: string | ApiChatMessageContentPart[] }) => {
// Filter out empty system messages
if (msg.role === MessageRole.SYSTEM) {
const content = typeof msg.content === 'string' ? msg.content : '';
return content.trim().length > 0;
}
return content.trim().length > 0;
}
return true;
});
return true;
});
// Filter out image attachments if the model doesn't support vision
if (options.model && !modelsStore.modelSupportsVision(options.model)) {
@@ -404,25 +410,27 @@ export class ChatService {
excludeReasoning?: boolean,
signal?: AbortSignal
): Promise<void> {
const normalizedMessages: ApiChatMessageData[] = messages
.map((msg) => {
if ('id' in msg && 'convId' in msg && 'timestamp' in msg) {
return ChatService.convertDbMessageToApiChatMessageData(
msg as DatabaseMessage & { extra?: DatabaseMessageExtra[] }
);
}
const normalizedMessages: ApiChatMessageData[] = (
await Promise.all(
messages.map((msg) => {
if ('id' in msg && 'convId' in msg && 'timestamp' in msg) {
return ChatService.convertDbMessageToApiChatMessageData(
msg as DatabaseMessage & { extra?: DatabaseMessageExtra[] }
);
}
return msg as ApiChatMessageData;
})
.filter((msg) => {
if (msg.role === MessageRole.SYSTEM) {
const content = typeof msg.content === 'string' ? msg.content : '';
return msg as ApiChatMessageData;
})
)
).filter((msg: { role: ChatRole; content: string | ApiChatMessageContentPart[] }) => {
if (msg.role === MessageRole.SYSTEM) {
const content = typeof msg.content === 'string' ? msg.content : '';
return content.trim().length > 0;
}
return content.trim().length > 0;
}
return true;
});
return true;
});
const requestBody: Record<string, unknown> = {
messages: normalizedMessages.map((msg: ApiChatMessageData) => {
@@ -805,9 +813,9 @@ export class ChatService {
* @returns {ApiChatMessageData} object formatted for the chat completion API
* @static
*/
static convertDbMessageToApiChatMessageData(
static async convertDbMessageToApiChatMessageData(
message: DatabaseMessage & { extra?: DatabaseMessageExtra[] }
): ApiChatMessageData {
): Promise<ApiChatMessageData> {
// Handle tool result messages (role: 'tool')
if (message.role === MessageRole.TOOL && message.toolCallId) {
return {
@@ -885,9 +893,14 @@ export class ChatService {
);
for (const image of imageFiles) {
const maxImageResolution = settingsStore.getConfig(SETTINGS_KEYS.MAX_IMAGE_RESOLUTION);
let base64Url = image.base64Url;
if (maxImageResolution > 1 / MEGAPIXELS_TO_PIXELS) {
base64Url = await capImageDataURLSize(image.base64Url, maxImageResolution);
}
contentParts.push({
type: ContentPartType.IMAGE_URL,
image_url: { url: image.base64Url }
image_url: { url: base64Url }
});
}