refactor: Architecture improvements
This commit is contained in:
parent
55d33a8b8c
commit
8a88576849
|
|
@ -23,8 +23,8 @@ import type {
|
||||||
ApiRouterModelsUnloadResponse
|
ApiRouterModelsUnloadResponse
|
||||||
} from '$lib/types/api';
|
} from '$lib/types/api';
|
||||||
|
|
||||||
import { ServerMode } from '$lib/enums/server';
|
import { ServerMode, ServerModelStatus } from '$lib/enums/server';
|
||||||
import { ServerModelStatus } from '$lib/enums/model';
|
import { ModelModality } from '$lib/enums/model';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ChatMessageType,
|
ChatMessageType,
|
||||||
|
|
@ -79,8 +79,6 @@ declare global {
|
||||||
ApiRouterModelsListResponse,
|
ApiRouterModelsListResponse,
|
||||||
ApiRouterModelsUnloadRequest,
|
ApiRouterModelsUnloadRequest,
|
||||||
ApiRouterModelsUnloadResponse,
|
ApiRouterModelsUnloadResponse,
|
||||||
ServerMode,
|
|
||||||
ServerModelStatus,
|
|
||||||
ChatMessageData,
|
ChatMessageData,
|
||||||
ChatMessagePromptProgress,
|
ChatMessagePromptProgress,
|
||||||
ChatMessageSiblingInfo,
|
ChatMessageSiblingInfo,
|
||||||
|
|
@ -96,6 +94,9 @@ declare global {
|
||||||
DatabaseMessageExtraTextFile,
|
DatabaseMessageExtraTextFile,
|
||||||
DatabaseMessageExtraPdfFile,
|
DatabaseMessageExtraPdfFile,
|
||||||
DatabaseMessageExtraLegacyContext,
|
DatabaseMessageExtraLegacyContext,
|
||||||
|
ModelModality,
|
||||||
|
ServerMode,
|
||||||
|
ServerModelStatus,
|
||||||
SettingsConfigValue,
|
SettingsConfigValue,
|
||||||
SettingsFieldConfig,
|
SettingsFieldConfig,
|
||||||
SettingsConfigType,
|
SettingsConfigType,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { FileText, Image, Music, FileIcon, Eye } from '@lucide/svelte';
|
import { FileText, Image, Music, FileIcon, Eye } from '@lucide/svelte';
|
||||||
import { FileTypeCategory, MimeTypeApplication } from '$lib/enums/files';
|
import { FileTypeCategory, MimeTypeApplication } from '$lib/enums/files';
|
||||||
|
import { ModelModality } from '$lib/enums/model';
|
||||||
|
import { AttachmentType } from '$lib/enums/attachment';
|
||||||
|
import type { DatabaseMessageExtra } from '$lib/types/database';
|
||||||
import { convertPDFToImage } from '$lib/utils/pdf-processing';
|
import { convertPDFToImage } from '$lib/utils/pdf-processing';
|
||||||
import { Button } from '$lib/components/ui/button';
|
|
||||||
import { getFileTypeCategory } from '$lib/utils/file-type';
|
import { getFileTypeCategory } from '$lib/utils/file-type';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -21,33 +24,36 @@
|
||||||
let displayName = $derived(uploadedFile?.name || attachment?.name || name || 'Unknown File');
|
let displayName = $derived(uploadedFile?.name || attachment?.name || name || 'Unknown File');
|
||||||
|
|
||||||
let displayPreview = $derived(
|
let displayPreview = $derived(
|
||||||
uploadedFile?.preview || (attachment?.type === 'imageFile' ? attachment.base64Url : preview)
|
uploadedFile?.preview ||
|
||||||
|
(attachment?.type === AttachmentType.IMAGE ? attachment.base64Url : preview)
|
||||||
);
|
);
|
||||||
|
|
||||||
let displayType = $derived(
|
let displayType = $derived(
|
||||||
uploadedFile?.type ||
|
uploadedFile
|
||||||
(attachment?.type === 'imageFile'
|
? uploadedFile.type
|
||||||
|
: attachment?.type === AttachmentType.IMAGE
|
||||||
? 'image'
|
? 'image'
|
||||||
: attachment?.type === 'textFile'
|
: attachment?.type === AttachmentType.TEXT
|
||||||
? 'text'
|
? 'text'
|
||||||
: attachment?.type === 'audioFile'
|
: attachment?.type === AttachmentType.AUDIO
|
||||||
? attachment.mimeType || 'audio'
|
? attachment.mimeType || ModelModality.AUDIO
|
||||||
: attachment?.type === 'pdfFile'
|
: attachment?.type === AttachmentType.PDF
|
||||||
? MimeTypeApplication.PDF
|
? MimeTypeApplication.PDF
|
||||||
: type || 'unknown')
|
: type || 'unknown'
|
||||||
);
|
);
|
||||||
|
|
||||||
let displayTextContent = $derived(
|
let displayTextContent = $derived(
|
||||||
uploadedFile?.textContent ||
|
uploadedFile?.textContent ||
|
||||||
(attachment?.type === 'textFile'
|
(attachment?.type === AttachmentType.TEXT
|
||||||
? attachment.content
|
? attachment.content
|
||||||
: attachment?.type === 'pdfFile'
|
: attachment?.type === AttachmentType.PDF
|
||||||
? attachment.content
|
? attachment.content
|
||||||
: textContent)
|
: textContent)
|
||||||
);
|
);
|
||||||
|
|
||||||
let isAudio = $derived(
|
let isAudio = $derived(
|
||||||
getFileTypeCategory(displayType) === FileTypeCategory.AUDIO || displayType === 'audio'
|
getFileTypeCategory(displayType) === FileTypeCategory.AUDIO ||
|
||||||
|
displayType === ModelModality.AUDIO
|
||||||
);
|
);
|
||||||
|
|
||||||
let isImage = $derived(
|
let isImage = $derived(
|
||||||
|
|
@ -87,9 +93,9 @@
|
||||||
|
|
||||||
if (uploadedFile?.file) {
|
if (uploadedFile?.file) {
|
||||||
file = uploadedFile.file;
|
file = uploadedFile.file;
|
||||||
} else if (attachment?.type === 'pdfFile') {
|
} else if (attachment?.type === AttachmentType.PDF) {
|
||||||
// Check if we have pre-processed images
|
// Check if we have pre-processed images
|
||||||
if (attachment.images && Array.isArray(attachment.images)) {
|
if (attachment.images && Array.isArray(attachment.images) && attachment.images.length > 0) {
|
||||||
pdfImages = attachment.images;
|
pdfImages = attachment.images;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -237,7 +243,7 @@
|
||||||
<div class="w-full max-w-md text-center">
|
<div class="w-full max-w-md text-center">
|
||||||
<Music class="mx-auto mb-4 h-16 w-16 text-muted-foreground" />
|
<Music class="mx-auto mb-4 h-16 w-16 text-muted-foreground" />
|
||||||
|
|
||||||
{#if attachment?.type === 'audioFile'}
|
{#if attachment?.type === AttachmentType.AUDIO}
|
||||||
<audio
|
<audio
|
||||||
controls
|
controls
|
||||||
class="mb-4 w-full"
|
class="mb-4 w-full"
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,13 @@
|
||||||
import { ChatAttachmentThumbnailImage, ChatAttachmentThumbnailFile } from '$lib/components/app';
|
import { ChatAttachmentThumbnailImage, ChatAttachmentThumbnailFile } from '$lib/components/app';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { ChevronLeft, ChevronRight } from '@lucide/svelte';
|
import { ChevronLeft, ChevronRight } from '@lucide/svelte';
|
||||||
import { FileTypeCategory } from '$lib/enums/files';
|
|
||||||
import { getFileTypeCategory } from '$lib/utils/file-type';
|
import { getFileTypeCategory } from '$lib/utils/file-type';
|
||||||
|
import { FileTypeCategory } from '$lib/enums/files';
|
||||||
|
import { ModelModality } from '$lib/enums/model';
|
||||||
|
import { AttachmentType } from '$lib/enums/attachment';
|
||||||
import { DialogChatAttachmentPreview, DialogChatAttachmentsViewAll } from '$lib/components/app';
|
import { DialogChatAttachmentPreview, DialogChatAttachmentsViewAll } from '$lib/components/app';
|
||||||
import type { ChatAttachmentDisplayItem, ChatAttachmentPreviewItem } from '$lib/types/chat';
|
import type { ChatAttachmentDisplayItem, ChatAttachmentPreviewItem } from '$lib/types/chat';
|
||||||
|
import type { DatabaseMessageExtra } from '$lib/types/database';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
class?: string;
|
class?: string;
|
||||||
|
|
@ -68,7 +71,7 @@
|
||||||
|
|
||||||
// Add stored attachments (ChatMessage)
|
// Add stored attachments (ChatMessage)
|
||||||
for (const [index, attachment] of attachments.entries()) {
|
for (const [index, attachment] of attachments.entries()) {
|
||||||
if (attachment.type === 'imageFile') {
|
if (attachment.type === AttachmentType.IMAGE) {
|
||||||
items.push({
|
items.push({
|
||||||
id: `attachment-${index}`,
|
id: `attachment-${index}`,
|
||||||
name: attachment.name,
|
name: attachment.name,
|
||||||
|
|
@ -78,7 +81,7 @@
|
||||||
attachment,
|
attachment,
|
||||||
attachmentIndex: index
|
attachmentIndex: index
|
||||||
});
|
});
|
||||||
} else if (attachment.type === 'textFile') {
|
} else if (attachment.type === AttachmentType.TEXT) {
|
||||||
items.push({
|
items.push({
|
||||||
id: `attachment-${index}`,
|
id: `attachment-${index}`,
|
||||||
name: attachment.name,
|
name: attachment.name,
|
||||||
|
|
@ -88,7 +91,25 @@
|
||||||
attachmentIndex: index,
|
attachmentIndex: index,
|
||||||
textContent: attachment.content
|
textContent: attachment.content
|
||||||
});
|
});
|
||||||
} else if (attachment.type === 'context') {
|
} else if (attachment.type === AttachmentType.AUDIO) {
|
||||||
|
items.push({
|
||||||
|
id: `attachment-${index}`,
|
||||||
|
name: attachment.name,
|
||||||
|
type: attachment.mimeType || ModelModality.AUDIO,
|
||||||
|
isImage: false,
|
||||||
|
attachment,
|
||||||
|
attachmentIndex: index
|
||||||
|
});
|
||||||
|
} else if (attachment.type === AttachmentType.PDF) {
|
||||||
|
items.push({
|
||||||
|
id: `attachment-${index}`,
|
||||||
|
name: attachment.name,
|
||||||
|
type: 'application/pdf',
|
||||||
|
isImage: false,
|
||||||
|
attachment,
|
||||||
|
attachmentIndex: index
|
||||||
|
});
|
||||||
|
} else if (attachment.type === AttachmentType.LEGACY_CONTEXT) {
|
||||||
// Legacy format from old webui - treat as text file
|
// Legacy format from old webui - treat as text file
|
||||||
items.push({
|
items.push({
|
||||||
id: `attachment-${index}`,
|
id: `attachment-${index}`,
|
||||||
|
|
@ -99,25 +120,6 @@
|
||||||
attachmentIndex: index,
|
attachmentIndex: index,
|
||||||
textContent: attachment.content
|
textContent: attachment.content
|
||||||
});
|
});
|
||||||
} else if (attachment.type === 'audioFile') {
|
|
||||||
items.push({
|
|
||||||
id: `attachment-${index}`,
|
|
||||||
name: attachment.name,
|
|
||||||
type: attachment.mimeType || 'audio',
|
|
||||||
isImage: false,
|
|
||||||
attachment,
|
|
||||||
attachmentIndex: index
|
|
||||||
});
|
|
||||||
} else if (attachment.type === 'pdfFile') {
|
|
||||||
items.push({
|
|
||||||
id: `attachment-${index}`,
|
|
||||||
name: attachment.name,
|
|
||||||
type: 'application/pdf',
|
|
||||||
isImage: false,
|
|
||||||
attachment,
|
|
||||||
attachmentIndex: index,
|
|
||||||
textContent: attachment.content
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,11 @@
|
||||||
DialogChatAttachmentPreview
|
DialogChatAttachmentPreview
|
||||||
} from '$lib/components/app';
|
} from '$lib/components/app';
|
||||||
import { FileTypeCategory } from '$lib/enums/files';
|
import { FileTypeCategory } from '$lib/enums/files';
|
||||||
|
import { ModelModality } from '$lib/enums/model';
|
||||||
|
import { AttachmentType } from '$lib/enums/attachment';
|
||||||
import { getFileTypeCategory } from '$lib/utils/file-type';
|
import { getFileTypeCategory } from '$lib/utils/file-type';
|
||||||
import type { ChatAttachmentDisplayItem, ChatAttachmentPreviewItem } from '$lib/types/chat';
|
import type { ChatAttachmentDisplayItem, ChatAttachmentPreviewItem } from '$lib/types/chat';
|
||||||
|
import type { DatabaseMessageExtra } from '$lib/types/database';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
uploadedFiles?: ChatUploadedFile[];
|
uploadedFiles?: ChatUploadedFile[];
|
||||||
|
|
@ -52,7 +55,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [index, attachment] of attachments.entries()) {
|
for (const [index, attachment] of attachments.entries()) {
|
||||||
if (attachment.type === 'imageFile') {
|
if (attachment.type === AttachmentType.IMAGE) {
|
||||||
items.push({
|
items.push({
|
||||||
id: `attachment-${index}`,
|
id: `attachment-${index}`,
|
||||||
name: attachment.name,
|
name: attachment.name,
|
||||||
|
|
@ -62,7 +65,7 @@
|
||||||
attachment,
|
attachment,
|
||||||
attachmentIndex: index
|
attachmentIndex: index
|
||||||
});
|
});
|
||||||
} else if (attachment.type === 'textFile') {
|
} else if (attachment.type === AttachmentType.TEXT) {
|
||||||
items.push({
|
items.push({
|
||||||
id: `attachment-${index}`,
|
id: `attachment-${index}`,
|
||||||
name: attachment.name,
|
name: attachment.name,
|
||||||
|
|
@ -72,7 +75,25 @@
|
||||||
attachmentIndex: index,
|
attachmentIndex: index,
|
||||||
textContent: attachment.content
|
textContent: attachment.content
|
||||||
});
|
});
|
||||||
} else if (attachment.type === 'context') {
|
} else if (attachment.type === AttachmentType.AUDIO) {
|
||||||
|
items.push({
|
||||||
|
id: `attachment-${index}`,
|
||||||
|
name: attachment.name,
|
||||||
|
type: attachment.mimeType || ModelModality.AUDIO,
|
||||||
|
isImage: false,
|
||||||
|
attachment,
|
||||||
|
attachmentIndex: index
|
||||||
|
});
|
||||||
|
} else if (attachment.type === AttachmentType.PDF) {
|
||||||
|
items.push({
|
||||||
|
id: `attachment-${index}`,
|
||||||
|
name: attachment.name,
|
||||||
|
type: 'application/pdf',
|
||||||
|
isImage: false,
|
||||||
|
attachment,
|
||||||
|
attachmentIndex: index
|
||||||
|
});
|
||||||
|
} else if (attachment.type === AttachmentType.LEGACY_CONTEXT) {
|
||||||
// Legacy format from old webui - treat as text file
|
// Legacy format from old webui - treat as text file
|
||||||
items.push({
|
items.push({
|
||||||
id: `attachment-${index}`,
|
id: `attachment-${index}`,
|
||||||
|
|
@ -83,25 +104,6 @@
|
||||||
attachmentIndex: index,
|
attachmentIndex: index,
|
||||||
textContent: attachment.content
|
textContent: attachment.content
|
||||||
});
|
});
|
||||||
} else if (attachment.type === 'audioFile') {
|
|
||||||
items.push({
|
|
||||||
id: `attachment-${index}`,
|
|
||||||
name: attachment.name,
|
|
||||||
type: attachment.mimeType || 'audio',
|
|
||||||
isImage: false,
|
|
||||||
attachment,
|
|
||||||
attachmentIndex: index
|
|
||||||
});
|
|
||||||
} else if (attachment.type === 'pdfFile') {
|
|
||||||
items.push({
|
|
||||||
id: `attachment-${index}`,
|
|
||||||
name: attachment.name,
|
|
||||||
type: 'application/pdf',
|
|
||||||
isImage: false,
|
|
||||||
attachment,
|
|
||||||
attachmentIndex: index,
|
|
||||||
textContent: attachment.content
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Dialog from '$lib/components/ui/dialog';
|
import * as Dialog from '$lib/components/ui/dialog';
|
||||||
|
import { ModelModality } from '$lib/enums/model';
|
||||||
|
import { AttachmentType } from '$lib/enums/attachment';
|
||||||
|
import type { DatabaseMessageExtra } from '$lib/types/database';
|
||||||
import { ChatAttachmentPreview } from '$lib/components/app';
|
import { ChatAttachmentPreview } from '$lib/components/app';
|
||||||
import { formatFileSize } from '$lib/utils/file-preview';
|
import { formatFileSize } from '$lib/utils/file-preview';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
onOpenChange?: (open: boolean) => void;
|
||||||
// Either an uploaded file or a stored attachment
|
// Either an uploaded file or a stored attachment
|
||||||
uploadedFile?: ChatUploadedFile;
|
uploadedFile?: ChatUploadedFile;
|
||||||
attachment?: DatabaseMessageExtra;
|
attachment?: DatabaseMessageExtra;
|
||||||
|
|
@ -18,6 +22,7 @@
|
||||||
|
|
||||||
let {
|
let {
|
||||||
open = $bindable(),
|
open = $bindable(),
|
||||||
|
onOpenChange,
|
||||||
uploadedFile,
|
uploadedFile,
|
||||||
attachment,
|
attachment,
|
||||||
preview,
|
preview,
|
||||||
|
|
@ -32,16 +37,17 @@
|
||||||
let displayName = $derived(uploadedFile?.name || attachment?.name || name || 'Unknown File');
|
let displayName = $derived(uploadedFile?.name || attachment?.name || name || 'Unknown File');
|
||||||
|
|
||||||
let displayType = $derived(
|
let displayType = $derived(
|
||||||
uploadedFile?.type ||
|
uploadedFile
|
||||||
(attachment?.type === 'imageFile'
|
? uploadedFile.type
|
||||||
|
: attachment?.type === AttachmentType.IMAGE
|
||||||
? 'image'
|
? 'image'
|
||||||
: attachment?.type === 'textFile'
|
: attachment?.type === AttachmentType.TEXT
|
||||||
? 'text'
|
? 'text'
|
||||||
: attachment?.type === 'audioFile'
|
: attachment?.type === AttachmentType.AUDIO
|
||||||
? attachment.mimeType || 'audio'
|
? attachment.mimeType || ModelModality.AUDIO
|
||||||
: attachment?.type === 'pdfFile'
|
: attachment?.type === AttachmentType.PDF
|
||||||
? 'application/pdf'
|
? 'application/pdf'
|
||||||
: type || 'unknown')
|
: type || 'unknown'
|
||||||
);
|
);
|
||||||
|
|
||||||
let displaySize = $derived(uploadedFile?.size || size);
|
let displaySize = $derived(uploadedFile?.size || size);
|
||||||
|
|
@ -53,7 +59,7 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Dialog.Root bind:open>
|
<Dialog.Root bind:open {onOpenChange}>
|
||||||
<Dialog.Content class="grid max-h-[90vh] max-w-5xl overflow-hidden sm:w-auto sm:max-w-6xl">
|
<Dialog.Content class="grid max-h-[90vh] max-w-5xl overflow-hidden sm:w-auto sm:max-w-6xl">
|
||||||
<Dialog.Header>
|
<Dialog.Header>
|
||||||
<Dialog.Title>{displayName}</Dialog.Title>
|
<Dialog.Title>{displayName}</Dialog.Title>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
* Attachment type enum for database message extras
|
||||||
|
*/
|
||||||
|
export enum AttachmentType {
|
||||||
|
AUDIO = 'AUDIO',
|
||||||
|
IMAGE = 'IMAGE',
|
||||||
|
PDF = 'PDF',
|
||||||
|
TEXT = 'TEXT',
|
||||||
|
LEGACY_CONTEXT = 'context' // Legacy attachment type for backward compatibility
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
/**
|
export enum ModelModality {
|
||||||
* Model status enum - matches tools/server/server-models.h from C++ server
|
TEXT = 'TEXT',
|
||||||
*/
|
AUDIO = 'AUDIO',
|
||||||
export enum ServerModelStatus {
|
VISION = 'VISION'
|
||||||
UNLOADED = 'UNLOADED',
|
|
||||||
LOADING = 'LOADING',
|
|
||||||
LOADED = 'LOADED',
|
|
||||||
FAILED = 'FAILED'
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,13 @@ export enum ServerMode {
|
||||||
/** Router mode - server managing multiple model instances */
|
/** Router mode - server managing multiple model instances */
|
||||||
ROUTER = 'ROUTER'
|
ROUTER = 'ROUTER'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model status enum - matches tools/server/server-models.h from C++ server
|
||||||
|
*/
|
||||||
|
export enum ServerModelStatus {
|
||||||
|
UNLOADED = 'UNLOADED',
|
||||||
|
LOADING = 'LOADING',
|
||||||
|
LOADED = 'LOADED',
|
||||||
|
FAILED = 'FAILED'
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@ import type {
|
||||||
ApiChatCompletionStreamChunk,
|
ApiChatCompletionStreamChunk,
|
||||||
ApiChatCompletionToolCall,
|
ApiChatCompletionToolCall,
|
||||||
ApiChatCompletionToolCallDelta,
|
ApiChatCompletionToolCallDelta,
|
||||||
ApiChatMessageData
|
ApiChatMessageData,
|
||||||
|
ApiModelListResponse
|
||||||
} from '$lib/types/api';
|
} from '$lib/types/api';
|
||||||
|
import { AttachmentType } from '$lib/enums/attachment';
|
||||||
import type {
|
import type {
|
||||||
DatabaseMessage,
|
DatabaseMessage,
|
||||||
DatabaseMessageExtra,
|
DatabaseMessageExtra,
|
||||||
|
|
@ -618,7 +620,7 @@ export class ChatService {
|
||||||
|
|
||||||
const imageFiles = message.extra.filter(
|
const imageFiles = message.extra.filter(
|
||||||
(extra: DatabaseMessageExtra): extra is DatabaseMessageExtraImageFile =>
|
(extra: DatabaseMessageExtra): extra is DatabaseMessageExtraImageFile =>
|
||||||
extra.type === 'imageFile'
|
extra.type === AttachmentType.IMAGE
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const image of imageFiles) {
|
for (const image of imageFiles) {
|
||||||
|
|
@ -630,7 +632,7 @@ export class ChatService {
|
||||||
|
|
||||||
const textFiles = message.extra.filter(
|
const textFiles = message.extra.filter(
|
||||||
(extra: DatabaseMessageExtra): extra is DatabaseMessageExtraTextFile =>
|
(extra: DatabaseMessageExtra): extra is DatabaseMessageExtraTextFile =>
|
||||||
extra.type === 'textFile'
|
extra.type === AttachmentType.TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const textFile of textFiles) {
|
for (const textFile of textFiles) {
|
||||||
|
|
@ -643,7 +645,7 @@ export class ChatService {
|
||||||
// Handle legacy 'context' type from old webui (pasted content)
|
// Handle legacy 'context' type from old webui (pasted content)
|
||||||
const legacyContextFiles = message.extra.filter(
|
const legacyContextFiles = message.extra.filter(
|
||||||
(extra: DatabaseMessageExtra): extra is DatabaseMessageExtraLegacyContext =>
|
(extra: DatabaseMessageExtra): extra is DatabaseMessageExtraLegacyContext =>
|
||||||
extra.type === 'context'
|
extra.type === AttachmentType.LEGACY_CONTEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const legacyContextFile of legacyContextFiles) {
|
for (const legacyContextFile of legacyContextFiles) {
|
||||||
|
|
@ -655,7 +657,7 @@ export class ChatService {
|
||||||
|
|
||||||
const audioFiles = message.extra.filter(
|
const audioFiles = message.extra.filter(
|
||||||
(extra: DatabaseMessageExtra): extra is DatabaseMessageExtraAudioFile =>
|
(extra: DatabaseMessageExtra): extra is DatabaseMessageExtraAudioFile =>
|
||||||
extra.type === 'audioFile'
|
extra.type === AttachmentType.AUDIO
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const audio of audioFiles) {
|
for (const audio of audioFiles) {
|
||||||
|
|
@ -670,7 +672,7 @@ export class ChatService {
|
||||||
|
|
||||||
const pdfFiles = message.extra.filter(
|
const pdfFiles = message.extra.filter(
|
||||||
(extra: DatabaseMessageExtra): extra is DatabaseMessageExtraPdfFile =>
|
(extra: DatabaseMessageExtra): extra is DatabaseMessageExtraPdfFile =>
|
||||||
extra.type === 'pdfFile'
|
extra.type === AttachmentType.PDF
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const pdfFile of pdfFiles) {
|
for (const pdfFile of pdfFiles) {
|
||||||
|
|
@ -722,6 +724,33 @@ export class ChatService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get model information from /models endpoint
|
||||||
|
*/
|
||||||
|
static async getModels(): Promise<ApiModelListResponse> {
|
||||||
|
try {
|
||||||
|
const currentConfig = config();
|
||||||
|
const apiKey = currentConfig.apiKey?.toString().trim();
|
||||||
|
|
||||||
|
const response = await fetch(`./models`, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch models: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching models:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aborts any ongoing chat completion request.
|
* Aborts any ongoing chat completion request.
|
||||||
* Cancels the current request and cleans up the abort controller.
|
* Cancels the current request and cleans up the abort controller.
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { SERVER_PROPS_LOCALSTORAGE_KEY } from '$lib/constants/localstorage-keys'
|
||||||
import { ChatService } from '$lib/services/chat';
|
import { ChatService } from '$lib/services/chat';
|
||||||
import { config } from '$lib/stores/settings.svelte';
|
import { config } from '$lib/stores/settings.svelte';
|
||||||
import { ServerMode } from '$lib/enums/server';
|
import { ServerMode } from '$lib/enums/server';
|
||||||
|
import { ModelModality } from '$lib/enums/model';
|
||||||
import { updateConfig } from '$lib/stores/settings.svelte';
|
import { updateConfig } from '$lib/stores/settings.svelte';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -115,10 +116,10 @@ class ServerStore {
|
||||||
get supportedModalities(): string[] {
|
get supportedModalities(): string[] {
|
||||||
const modalities: string[] = [];
|
const modalities: string[] = [];
|
||||||
if (this._serverProps?.modalities?.audio) {
|
if (this._serverProps?.modalities?.audio) {
|
||||||
modalities.push('audio');
|
modalities.push(ModelModality.AUDIO);
|
||||||
}
|
}
|
||||||
if (this._serverProps?.modalities?.vision) {
|
if (this._serverProps?.modalities?.vision) {
|
||||||
modalities.push('vision');
|
modalities.push(ModelModality.VISION);
|
||||||
}
|
}
|
||||||
return modalities;
|
return modalities;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import type { ChatMessageTimings } from './chat';
|
import type { ChatMessageTimings, ChatRole, ChatMessageType } from '$lib/types/chat';
|
||||||
|
import { AttachmentType } from '$lib/enums/attachment';
|
||||||
|
|
||||||
export interface DatabaseConversation {
|
export interface DatabaseConversation {
|
||||||
currNode: string | null;
|
currNode: string | null;
|
||||||
|
|
@ -8,38 +9,39 @@ export interface DatabaseConversation {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseMessageExtraAudioFile {
|
export interface DatabaseMessageExtraAudioFile {
|
||||||
type: 'audioFile';
|
type: AttachmentType.AUDIO;
|
||||||
name: string;
|
name: string;
|
||||||
base64Data: string;
|
base64Data: string;
|
||||||
mimeType: string;
|
mimeType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseMessageExtraImageFile {
|
export interface DatabaseMessageExtraImageFile {
|
||||||
type: 'imageFile';
|
type: AttachmentType.IMAGE;
|
||||||
name: string;
|
name: string;
|
||||||
base64Url: string;
|
base64Url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseMessageExtraTextFile {
|
|
||||||
type: 'textFile';
|
|
||||||
name: string;
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DatabaseMessageExtraPdfFile {
|
|
||||||
type: 'pdfFile';
|
|
||||||
name: string;
|
|
||||||
content: string; // Text content extracted from PDF
|
|
||||||
images?: string[]; // Optional: PDF pages as base64 images
|
|
||||||
processedAsImages: boolean; // Whether PDF was processed as images
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Legacy format from old webui - pasted content was stored as "context" type
|
* Legacy format from old webui - pasted content was stored as "context" type
|
||||||
* @deprecated Use DatabaseMessageExtraTextFile instead
|
* @deprecated Use DatabaseMessageExtraTextFile instead
|
||||||
*/
|
*/
|
||||||
export interface DatabaseMessageExtraLegacyContext {
|
export interface DatabaseMessageExtraLegacyContext {
|
||||||
type: 'context';
|
type: AttachmentType.LEGACY_CONTEXT;
|
||||||
|
name: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatabaseMessageExtraPdfFile {
|
||||||
|
type: AttachmentType.PDF;
|
||||||
|
base64Data: string;
|
||||||
|
name: string;
|
||||||
|
content: string; // Text content extracted from PDF
|
||||||
|
images?: string[]; // Optional: PDF pages as base64 images
|
||||||
|
processedAsImages: boolean; // Whether PDF was processed as images
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatabaseMessageExtraTextFile {
|
||||||
|
type: AttachmentType.TEXT;
|
||||||
name: string;
|
name: string;
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { convertPDFToImage, convertPDFToText } from './pdf-processing';
|
||||||
import { isSvgMimeType, svgBase64UrlToPngDataURL } from './svg-to-png';
|
import { isSvgMimeType, svgBase64UrlToPngDataURL } from './svg-to-png';
|
||||||
import { isWebpMimeType, webpBase64UrlToPngDataURL } from './webp-to-png';
|
import { isWebpMimeType, webpBase64UrlToPngDataURL } from './webp-to-png';
|
||||||
import { FileTypeCategory } from '$lib/enums/files';
|
import { FileTypeCategory } from '$lib/enums/files';
|
||||||
|
import { AttachmentType } from '$lib/enums/attachment';
|
||||||
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
||||||
import { supportsVision } from '$lib/stores/server.svelte';
|
import { supportsVision } from '$lib/stores/server.svelte';
|
||||||
import { getFileTypeCategory } from '$lib/utils/file-type';
|
import { getFileTypeCategory } from '$lib/utils/file-type';
|
||||||
|
|
@ -56,7 +57,7 @@ export async function parseFilesToMessageExtras(
|
||||||
}
|
}
|
||||||
|
|
||||||
extras.push({
|
extras.push({
|
||||||
type: 'imageFile',
|
type: AttachmentType.IMAGE,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
base64Url
|
base64Url
|
||||||
});
|
});
|
||||||
|
|
@ -67,7 +68,7 @@ export async function parseFilesToMessageExtras(
|
||||||
const base64Data = await readFileAsBase64(file.file);
|
const base64Data = await readFileAsBase64(file.file);
|
||||||
|
|
||||||
extras.push({
|
extras.push({
|
||||||
type: 'audioFile',
|
type: AttachmentType.AUDIO,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
base64Data: base64Data,
|
base64Data: base64Data,
|
||||||
mimeType: file.type
|
mimeType: file.type
|
||||||
|
|
@ -117,7 +118,7 @@ export async function parseFilesToMessageExtras(
|
||||||
);
|
);
|
||||||
|
|
||||||
extras.push({
|
extras.push({
|
||||||
type: 'pdfFile',
|
type: AttachmentType.PDF,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
content: `PDF file with ${images.length} pages`,
|
content: `PDF file with ${images.length} pages`,
|
||||||
images: images,
|
images: images,
|
||||||
|
|
@ -134,7 +135,7 @@ export async function parseFilesToMessageExtras(
|
||||||
const content = await convertPDFToText(file.file);
|
const content = await convertPDFToText(file.file);
|
||||||
|
|
||||||
extras.push({
|
extras.push({
|
||||||
type: 'pdfFile',
|
type: AttachmentType.PDF,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
content: content,
|
content: content,
|
||||||
processedAsImages: false,
|
processedAsImages: false,
|
||||||
|
|
@ -151,7 +152,7 @@ export async function parseFilesToMessageExtras(
|
||||||
});
|
});
|
||||||
|
|
||||||
extras.push({
|
extras.push({
|
||||||
type: 'pdfFile',
|
type: AttachmentType.PDF,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
content: content,
|
content: content,
|
||||||
processedAsImages: false,
|
processedAsImages: false,
|
||||||
|
|
@ -171,7 +172,7 @@ export async function parseFilesToMessageExtras(
|
||||||
emptyFiles.push(file.name);
|
emptyFiles.push(file.name);
|
||||||
} else if (isLikelyTextFile(content)) {
|
} else if (isLikelyTextFile(content)) {
|
||||||
extras.push({
|
extras.push({
|
||||||
type: 'textFile',
|
type: AttachmentType.TEXT,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
content: content
|
content: content
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,8 @@ export default defineConfig({
|
||||||
proxy: {
|
proxy: {
|
||||||
'/v1': 'http://localhost:8080',
|
'/v1': 'http://localhost:8080',
|
||||||
'/props': 'http://localhost:8080',
|
'/props': 'http://localhost:8080',
|
||||||
'/slots': 'http://localhost:8080'
|
'/slots': 'http://localhost:8080',
|
||||||
|
'/models': 'http://localhost:8080'
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue