Skip to content

Commit ecf1f04

Browse files
committed
feat: Adds PDF file support to chat
Enables displaying PDF files in the chat interface. This commit introduces the `pdfjs-dist` library to process PDFs, extracting text content for display. It also includes functionality to potentially convert PDF pages into images, allowing preview of PDF documents. The changes enhance the chat functionality by allowing add PDF attachments to the chat. Currently only supports converting PDFs to text.
1 parent 6830c25 commit ecf1f04

File tree

9 files changed

+411
-7
lines changed

9 files changed

+411
-7
lines changed

tools/server/webui/package-lock.json

Lines changed: 198 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/server/webui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"dependencies": {
6969
"highlight.js": "^11.11.1",
7070
"mode-watcher": "^1.1.0",
71+
"pdfjs-dist": "^5.4.54",
7172
"rehype-highlight": "^7.0.2",
7273
"rehype-stringify": "^10.0.1",
7374
"remark": "^15.0.1",

tools/server/webui/src/lib/components/chat/ChatAttachmentFilePreview.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@
121121
{getFileTypeLabel(type)}
122122
</div>
123123
<div class="flex flex-col">
124-
<span class="text-foreground max-w-48 truncate text-sm font-medium">{name}</span>
124+
<span class="text-foreground max-w-72 truncate text-sm font-medium">{name}</span>
125125
{#if size}
126126
<span class="text-muted-foreground text-xs">{formatFileSize(size)}</span>
127127
{/if}

tools/server/webui/src/lib/components/chat/ChatAttachmentsList.svelte

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,16 @@
8888
type: attachment.mimeType || 'audio',
8989
isImage: false
9090
});
91+
} else if (attachment.type === 'pdfFile') {
92+
items.push({
93+
id: `attachment-${index}`,
94+
name: attachment.name,
95+
type: 'application/pdf',
96+
isImage: false,
97+
attachment,
98+
attachmentIndex: index,
99+
textContent: attachment.content
100+
});
91101
}
92102
}
93103

tools/server/webui/src/lib/components/chat/ChatScreen.svelte

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,15 @@
1313
import { Upload } from '@lucide/svelte';
1414
import type { ChatUploadedFile } from '$lib/types/chat.d.ts';
1515
import type { DatabaseMessageExtra } from '$lib/types/database.d.ts';
16-
import { isLikelyTextFile, isTextFileByName, readFileAsText } from '$lib/utils/text-files';
17-
import { svgBase64UrlToPngDataURL, isSvgMimeType } from '$lib/utils/svg-to-png';
16+
import {
17+
convertPDFToText,
18+
isLikelyTextFile,
19+
isPdfMimeType,
20+
isSvgMimeType,
21+
isTextFileByName,
22+
readFileAsText,
23+
svgBase64UrlToPngDataURL
24+
} from '$lib/utils';
1825
1926
let { showCenteredEmpty = false } = $props();
2027
let chatScrollContainer: HTMLDivElement | undefined = $state();
@@ -83,6 +90,21 @@
8390
base64Url
8491
});
8592
}
93+
} else if (isPdfMimeType(file.type)) {
94+
try {
95+
// For now, always process PDF as text
96+
// todo: Add settings to allow PDF as images for vision models
97+
const content = await convertPDFToText(file.file);
98+
99+
extras.push({
100+
type: 'pdfFile',
101+
name: file.name,
102+
content: content,
103+
processedAsImages: false
104+
});
105+
} catch (error) {
106+
console.error(`Failed to process PDF file ${file.name}:`, error);
107+
}
86108
} else {
87109
try {
88110
const content = await readFileAsText(file.file);
@@ -186,6 +208,10 @@
186208
uploadedFiles = [...uploadedFiles, uploadedFile];
187209
};
188210
reader.readAsText(file);
211+
} else if (isPdfMimeType(file.type)) {
212+
// For PDF files, we'll process them during conversion to extras
213+
// Just add them to the list for now
214+
uploadedFiles = [...uploadedFiles, uploadedFile];
189215
} else {
190216
uploadedFiles = [...uploadedFiles, uploadedFile];
191217
}

tools/server/webui/src/lib/services/chat.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import type {
1111
DatabaseMessage,
1212
DatabaseMessageExtra,
1313
DatabaseMessageExtraImageFile,
14-
DatabaseMessageExtraTextFile
14+
DatabaseMessageExtraTextFile,
15+
DatabaseMessageExtraPdfFile
1516
} from '$lib/types/database'
1617

1718
export class ChatService {
@@ -221,6 +222,26 @@ export class ChatService {
221222
});
222223
}
223224

225+
// Add PDF files as text content
226+
const pdfFiles = message.extra.filter((extra): extra is DatabaseMessageExtraPdfFile => extra.type === 'pdfFile');
227+
for (const pdfFile of pdfFiles) {
228+
if (pdfFile.processedAsImages && pdfFile.images) {
229+
// If PDF was processed as images, add each page as an image
230+
for (let i = 0; i < pdfFile.images.length; i++) {
231+
contentParts.push({
232+
type: 'image_url',
233+
image_url: { url: pdfFile.images[i] }
234+
});
235+
}
236+
} else {
237+
// If PDF was processed as text, add as text content
238+
contentParts.push({
239+
type: 'text',
240+
text: `\n\n--- PDF File: ${pdfFile.name} ---\n${pdfFile.content}`
241+
});
242+
}
243+
}
244+
224245
return {
225246
role: message.role as 'user' | 'assistant' | 'system',
226247
content: contentParts

tools/server/webui/src/lib/types/database.d.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,15 @@ export interface DatabaseMessageExtraTextFile {
3232
content: string;
3333
}
3434

35-
export type DatabaseMessageExtra = DatabaseMessageExtraImageFile | DatabaseMessageExtraTextFile | DatabaseMessageExtraAudioFile;
35+
export interface DatabaseMessageExtraPdfFile {
36+
type: 'pdfFile';
37+
name: string;
38+
content: string; // Text content extracted from PDF
39+
images?: string[]; // Optional: PDF pages as base64 images
40+
processedAsImages: boolean; // Whether PDF was processed as images
41+
}
42+
43+
export type DatabaseMessageExtra = DatabaseMessageExtraImageFile | DatabaseMessageExtraTextFile | DatabaseMessageExtraAudioFile | DatabaseMessageExtraPdfFile;
3644

3745
export interface DatabaseMessage {
3846
id: string;
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
import autoResizeTextarea from './autoresize-textarea';
22
import { copyCodeToClipboard, copyToClipboard } from './copy';
3+
import { convertPDFToText, convertPDFToImage, isPdfMimeType } from './pdf-processing';
34
import { isLikelyTextFile, isTextFileByName, readFileAsText } from './text-files';
45
import { extractPartialThinking, hasThinkingEnd, hasThinkingStart, parseThinkingContent } from './thinking';
6+
import { isSvgMimeType, svgBase64UrlToPngDataURL } from './svg-to-png';
57

68
export {
79
autoResizeTextarea,
810
copyCodeToClipboard,
911
copyToClipboard,
12+
convertPDFToText,
13+
convertPDFToImage,
1014
extractPartialThinking,
1115
hasThinkingEnd,
12-
hasThinkingStart,
1316
isLikelyTextFile,
17+
isPdfMimeType,
18+
isSvgMimeType,
1419
isTextFileByName,
20+
hasThinkingStart,
1521
parseThinkingContent,
16-
readFileAsText
22+
readFileAsText,
23+
svgBase64UrlToPngDataURL,
1724
};

0 commit comments

Comments
 (0)