diff --git a/tools/server/webui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentThumbnailFile.svelte b/tools/server/webui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentThumbnailFile.svelte
index 4f5d802e43..cecdc784c9 100644
--- a/tools/server/webui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentThumbnailFile.svelte
+++ b/tools/server/webui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentThumbnailFile.svelte
@@ -3,7 +3,7 @@
import { getFileTypeLabel, getPreviewText } from '$lib/utils/file-preview';
import { formatFileSize } from '$lib/utils/formatters';
import { isTextFile } from '$lib/utils/attachment-type';
- import type { DatabaseMessageExtra } from '$lib/types/database';
+ import type { DatabaseMessageExtra, DatabaseMessageExtraPdfFile } from '$lib/types/database';
import { AttachmentType } from '$lib/enums';
interface Props {
@@ -35,14 +35,31 @@
let isText = $derived(isTextFile(attachment, uploadedFile));
- // Get file type for display - check uploadedFile first, then attachment mimeType
- let fileType = $derived.by(() => {
- if (uploadedFile?.type) return uploadedFile.type;
- // For audio attachments stored in DB, get mimeType from the attachment
- if (attachment?.type === AttachmentType.AUDIO && 'mimeType' in attachment) {
- return attachment.mimeType;
+ let fileTypeLabel = $derived.by(() => {
+ if (uploadedFile?.type) {
+ return getFileTypeLabel(uploadedFile.type);
}
- return 'unknown';
+
+ if (attachment) {
+ if ('mimeType' in attachment && attachment.mimeType) {
+ return getFileTypeLabel(attachment.mimeType);
+ }
+
+ if (attachment.type) {
+ return getFileTypeLabel(attachment.type);
+ }
+ }
+
+ return getFileTypeLabel(name);
+ });
+
+ let pdfProcessingMode = $derived.by(() => {
+ if (attachment?.type === AttachmentType.PDF) {
+ const pdfAttachment = attachment as DatabaseMessageExtraPdfFile;
+
+ return pdfAttachment.processedAsImages ? 'Sent as Image' : 'Sent as Text';
+ }
+ return null;
});
@@ -123,17 +140,19 @@
- {getFileTypeLabel(fileType)}
+ {fileTypeLabel}
-
+
{name}
- {#if size}
+ {#if pdfProcessingMode}
+ {pdfProcessingMode}
+ {:else if size}
{formatFileSize(size)}
{/if}
diff --git a/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte b/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte
index d94bad60b6..26afe56f20 100644
--- a/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte
+++ b/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte
@@ -229,7 +229,9 @@
}
async function handleSendMessage(message: string, files?: ChatUploadedFile[]): Promise
{
- const result = files ? await parseFilesToMessageExtras(files) : undefined;
+ const result = files
+ ? await parseFilesToMessageExtras(files, activeModelId ?? undefined)
+ : undefined;
if (result?.emptyFiles && result.emptyFiles.length > 0) {
emptyFileNames = result.emptyFiles;
@@ -292,7 +294,10 @@
}
if (supportedFiles.length > 0) {
- const processed = await processFilesToChatUploaded(supportedFiles);
+ const processed = await processFilesToChatUploaded(
+ supportedFiles,
+ activeModelId ?? undefined
+ );
uploadedFiles = [...uploadedFiles, ...processed];
}
}
diff --git a/tools/server/webui/src/lib/components/app/dialogs/DialogModelInformation.svelte b/tools/server/webui/src/lib/components/app/dialogs/DialogModelInformation.svelte
index 94903d3235..fd0823c33e 100644
--- a/tools/server/webui/src/lib/components/app/dialogs/DialogModelInformation.svelte
+++ b/tools/server/webui/src/lib/components/app/dialogs/DialogModelInformation.svelte
@@ -3,6 +3,7 @@
import * as Table from '$lib/components/ui/table';
import { BadgeModality, CopyToClipboardIcon } from '$lib/components/app';
import { serverStore } from '$lib/stores/server.svelte';
+ import { modelsStore } from '$lib/stores/models.svelte';
import { ChatService } from '$lib/services/chat';
import type { ApiModelListResponse } from '$lib/types/api';
import { formatFileSize, formatParameters, formatNumber } from '$lib/utils/formatters';
@@ -15,7 +16,14 @@
let { open = $bindable(), onOpenChange }: Props = $props();
let serverProps = $derived(serverStore.props);
- let modalities = $derived(serverStore.supportedModalities);
+ let modelName = $derived(modelsStore.singleModelName);
+
+ // Get modalities from modelStore using the model ID from the first model
+ let modalities = $derived.by(() => {
+ if (!modelsData?.data?.[0]?.id) return [];
+
+ return modelsStore.getModelModalitiesArray(modelsData.data[0].id);
+ });
let modelsData = $state(null);
let isLoadingModels = $state(false);
@@ -77,12 +85,12 @@
class="resizable-text-container min-w-0 flex-1 truncate"
style:--threshold="12rem"
>
- {serverStore.modelName}
+ {modelName}
diff --git a/tools/server/webui/src/lib/components/app/misc/BadgeModelName.svelte b/tools/server/webui/src/lib/components/app/misc/BadgeModelName.svelte
index a8e3822237..275699be7a 100644
--- a/tools/server/webui/src/lib/components/app/misc/BadgeModelName.svelte
+++ b/tools/server/webui/src/lib/components/app/misc/BadgeModelName.svelte
@@ -1,6 +1,7 @@
diff --git a/tools/server/webui/src/lib/components/app/misc/SelectorModel.svelte b/tools/server/webui/src/lib/components/app/misc/SelectorModel.svelte
index 1b1326fefb..44a1468ca6 100644
--- a/tools/server/webui/src/lib/components/app/misc/SelectorModel.svelte
+++ b/tools/server/webui/src/lib/components/app/misc/SelectorModel.svelte
@@ -10,11 +10,13 @@
modelsLoading,
modelsUpdating,
selectedModelId,
- routerModels
+ routerModels,
+ propsCacheVersion,
+ singleModelName
} from '$lib/stores/models.svelte';
import { usedModalities, conversationsStore } from '$lib/stores/conversations.svelte';
import { ServerModelStatus } from '$lib/enums';
- import { isRouterMode, serverStore } from '$lib/stores/server.svelte';
+ import { isRouterMode } from '$lib/stores/server.svelte';
import { DialogModelInformation } from '$lib/components/app';
import type { ModelOption } from '$lib/types/models';
@@ -50,7 +52,7 @@
let updating = $derived(modelsUpdating());
let activeId = $derived(selectedModelId());
let isRouter = $derived(isRouterMode());
- let serverModel = $derived(serverStore.modelName);
+ let serverModel = $derived(singleModelName());
// Reactive router models state - needed for proper reactivity of status checks
let currentRouterModels = $derived(routerModels());
@@ -69,9 +71,19 @@
* Returns true if the model can be selected, false if it should be disabled.
*/
function isModelCompatible(option: ModelOption): boolean {
- const modelModalities = option.modalities;
+ void propsCacheVersion();
- if (!modelModalities) return true;
+ const modelModalities = modelsStore.getModelModalities(option.model);
+
+ if (!modelModalities) {
+ const status = getModelStatus(option.model);
+
+ if (status === ServerModelStatus.LOADED) {
+ if (requiredModalities.vision || requiredModalities.audio) return false;
+ }
+
+ return true;
+ }
if (requiredModalities.vision && !modelModalities.vision) return false;
if (requiredModalities.audio && !modelModalities.audio) return false;
@@ -84,8 +96,24 @@
* Returns object with vision/audio booleans indicating what's missing.
*/
function getMissingModalities(option: ModelOption): { vision: boolean; audio: boolean } | null {
- const modelModalities = option.modalities;
- if (!modelModalities) return null;
+ void propsCacheVersion();
+
+ const modelModalities = modelsStore.getModelModalities(option.model);
+
+ if (!modelModalities) {
+ const status = getModelStatus(option.model);
+
+ if (status === ServerModelStatus.LOADED) {
+ const missing = {
+ vision: requiredModalities.vision,
+ audio: requiredModalities.audio
+ };
+
+ if (missing.vision || missing.audio) return missing;
+ }
+
+ return null;
+ }
const missing = {
vision: requiredModalities.vision && !modelModalities.vision,
@@ -93,6 +121,7 @@
};
if (!missing.vision && !missing.audio) return null;
+
return missing;
}
@@ -160,9 +189,10 @@
await tick();
updateMenuPosition();
requestAnimationFrame(() => updateMenuPosition());
+
+ modelsStore.fetchModalitiesForLoadedModels();
}
- // Export open function for programmatic access
export function open() {
if (isRouter) {
openMenu();
diff --git a/tools/server/webui/src/lib/components/app/server/ServerStatus.svelte b/tools/server/webui/src/lib/components/app/server/ServerStatus.svelte
index f04c954d70..d9f6d4a32a 100644
--- a/tools/server/webui/src/lib/components/app/server/ServerStatus.svelte
+++ b/tools/server/webui/src/lib/components/app/server/ServerStatus.svelte
@@ -2,7 +2,8 @@
import { AlertTriangle, Server } from '@lucide/svelte';
import { Badge } from '$lib/components/ui/badge';
import { Button } from '$lib/components/ui/button';
- import { serverProps, serverLoading, serverError, modelName } from '$lib/stores/server.svelte';
+ import { serverProps, serverLoading, serverError } from '$lib/stores/server.svelte';
+ import { singleModelName } from '$lib/stores/models.svelte';
interface Props {
class?: string;
@@ -13,7 +14,7 @@
let error = $derived(serverError());
let loading = $derived(serverLoading());
- let model = $derived(modelName());
+ let model = $derived(singleModelName());
let serverData = $derived(serverProps());
function getStatusColor() {
diff --git a/tools/server/webui/src/lib/stores/models.svelte.ts b/tools/server/webui/src/lib/stores/models.svelte.ts
index a30ad6962c..8c53ae268d 100644
--- a/tools/server/webui/src/lib/stores/models.svelte.ts
+++ b/tools/server/webui/src/lib/stores/models.svelte.ts
@@ -1,7 +1,7 @@
import { SvelteSet } from 'svelte/reactivity';
import { ModelsService } from '$lib/services/models';
import { PropsService } from '$lib/services/props';
-import { ServerModelStatus } from '$lib/enums';
+import { ServerModelStatus, ModelModality } from '$lib/enums';
import { serverStore } from '$lib/stores/server.svelte';
import type { ModelOption, ModelModalities } from '$lib/types/models';
import type { ApiModelDataEntry } from '$lib/types/api';
@@ -56,6 +56,11 @@ class ModelsStore {
private modelPropsCache = $state