diff --git a/src/apps/chat/components/composer/llmattachments/LLMAttachmentMenu.tsx b/src/apps/chat/components/composer/llmattachments/LLMAttachmentMenu.tsx
index 0259accf2..bcf7b1c82 100644
--- a/src/apps/chat/components/composer/llmattachments/LLMAttachmentMenu.tsx
+++ b/src/apps/chat/components/composer/llmattachments/LLMAttachmentMenu.tsx
@@ -1,12 +1,16 @@
import * as React from 'react';
-import { Box, ListDivider, ListItemDecorator, MenuItem, Radio, Typography } from '@mui/joy';
+import { Box, IconButton, Link, ListDivider, ListItem, ListItemDecorator, MenuItem, Radio, Tooltip, Typography } from '@mui/joy';
import ClearIcon from '@mui/icons-material/Clear';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import KeyboardArrowLeftIcon from '@mui/icons-material/KeyboardArrowLeft';
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
+import LaunchIcon from '@mui/icons-material/Launch';
import VerticalAlignBottomIcon from '@mui/icons-material/VerticalAlignBottom';
+import { getImageBlobURLById } from '~/modules/dblobs/dblobs.db';
+
+import type { DContentRef } from '~/common/stores/chat/chat.message';
import { CloseableMenu } from '~/common/components/CloseableMenu';
import { copyToClipboard } from '~/common/util/clipboardUtils';
@@ -19,6 +23,22 @@ import type { LLMAttachment } from './useLLMAttachments';
export const DEBUG_LLMATTACHMENTS = true;
+/**
+ * Note: this utility function could be extracted more broadly to chat.message.ts, but
+ * I don't want to introduce a (circular) dependency from chat.message.ts to dblobs.db.ts.
+ */
+async function handleShowContentInNewTab(source: DContentRef) {
+ console.log('handleShowContentInNewTab', source);
+ let imageUrl: string | null = null;
+ if (source.reftype === 'url')
+ imageUrl = source.url;
+ else if (source.reftype === 'dblob')
+ imageUrl = await getImageBlobURLById(source.dblobId);
+ if (imageUrl && typeof window !== 'undefined')
+ window.open(imageUrl, '_blank', 'noopener,noreferrer');
+}
+
+
export function LLMAttachmentMenu(props: {
attachmentDraftsStoreApi: AttachmentDraftsStoreApi,
llmAttachment: LLMAttachment,
@@ -145,8 +165,16 @@ export function LLMAttachmentMenu(props: {
{!isUnconvertible && }
{DEBUG_LLMATTACHMENTS && !!aInput && (
-
+
)}
{DEBUG_LLMATTACHMENTS && !!aInput && }
diff --git a/src/common/attachment-drafts/attachment.pipeline.ts b/src/common/attachment-drafts/attachment.pipeline.ts
index 248d65f6e..af58ea918 100644
--- a/src/common/attachment-drafts/attachment.pipeline.ts
+++ b/src/common/attachment-drafts/attachment.pipeline.ts
@@ -10,6 +10,13 @@ import type { AttachmentDraft, AttachmentDraftConverter, AttachmentDraftInput, A
import { attachmentImageToPartViaDBlob } from './attachment.dblobs';
+// configuration
+export const DEFAULT_ADRAFT_IMAGE_MIMETYPE = 'image/webp';
+export const DEFAULT_ADRAFT_IMAGE_QUALITY = 0.98;
+const PDF_IMAGE_PAGE_SCALE = 1.5;
+const PDF_IMAGE_QUALITY = 0.5;
+
+
// extensions to treat as plain text
const PLAIN_TEXT_EXTENSIONS: string[] = ['.ts', '.tsx'];
@@ -70,9 +77,6 @@ const IMAGE_MIMETYPES: string[] = [
'image/gif',
];
-export const DEFAULT_ADRAFT_IMAGE_MIMETYPE = 'image/webp';
-export const DEFAULT_ADRAFT_IMAGE_QUALITY = 0.98;
-
/**
* Creates a new AttachmentDraft object.
@@ -430,7 +434,7 @@ export async function attachmentPerformConversion(attachment: Readonly {
+export async function pdfToImageDataURLs(pdfBuffer: ArrayBuffer, imageMimeType: string, imageQuality: number /* = 0.95 */, scale: number /*= 1.5*/): Promise {
const { getDocument } = await dynamicImportPdfJs();
const pdf = await getDocument({ data: pdfBuffer }).promise;
const images: PdfPageImage[] = [];
diff --git a/src/modules/dblobs/dblobs.db.ts b/src/modules/dblobs/dblobs.db.ts
index da739dafd..6c44fcc5f 100644
--- a/src/modules/dblobs/dblobs.db.ts
+++ b/src/modules/dblobs/dblobs.db.ts
@@ -48,6 +48,31 @@ export async function deleteDBlobItem(id: string) {
}
+// Specific item types
+async function getImageItemById(id: string) {
+ return await getItemById(id);
+}
+
+export async function getImageDataURLById(id: string) {
+ const item = await getImageItemById(id);
+ return item ? `data:${item.data.mimeType};base64,${item.data.base64}` : null;
+}
+
+export async function getImageBlobURLById(id: string) {
+ const item = await getImageItemById(id);
+ if (item) {
+ const byteCharacters = atob(item.data.base64);
+ const byteNumbers = new Array(byteCharacters.length);
+ for (let i = 0; i < byteCharacters.length; i++) {
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
+ }
+ const byteArray = new Uint8Array(byteNumbers);
+ const blob = new Blob([byteArray], { type: item.data.mimeType });
+ return URL.createObjectURL(blob);
+ }
+ return null;
+}
+
// Example usage:
async function getAllImages(): Promise {
return await getDBlobItemsByType(DBlobMetaDataType.IMAGE);