Wire all up to BlobUtils

This commit is contained in:
Enrico Ros
2025-05-30 11:25:30 -07:00
parent 95d9976a2c
commit c9ebb44442
7 changed files with 46 additions and 65 deletions
@@ -314,7 +314,7 @@ export function LLMAttachmentMenu(props: {
<Link onClick={(event) => {
event.preventDefault();
event.stopPropagation();
showImageDataURLInNewTab(draftInput?.urlImage?.imgDataUrl || '');
void showImageDataURLInNewTab(draftInput?.urlImage?.imgDataUrl || '');
}}>
open <LaunchIcon sx={{ mx: 0.5, fontSize: 16 }} />
</Link>
@@ -6,7 +6,7 @@ import { youTubeGetVideoData } from '~/modules/youtube/useYouTubeTranscript';
import { Is } from '~/common/util/pwaUtils';
import { agiCustomId, agiUuid } from '~/common/util/idUtils';
import { base64ToArrayBuffer } from '~/common/util/urlUtils';
import { convert_Base64_To_UInt8Array } from '~/common/util/blobUtils';
import { htmlTableToMarkdown } from '~/common/util/htmlTableToMarkdown';
import { humanReadableHyphenated } from '~/common/util/textUtils';
import { pdfToImageDataURLs, pdfToText } from '~/common/util/pdfUtils';
@@ -125,16 +125,20 @@ export async function attachmentLoadInputAsync(source: Readonly<AttachmentDraftS
else
edit({ inputError: 'No content found at this link' });
} else if (file) {
const data = base64ToArrayBuffer(file.data);
edit({
label: file.fileName || source.refUrl,
// ref: source.refUrl,
input: {
mimeType: file.mimeType,
data: data,
dataSize: data.byteLength,
},
});
try {
const dataArray = convert_Base64_To_UInt8Array(file.data, 'attachment-draft-load-input')
edit({
label: file.fileName || source.refUrl,
// ref: source.refUrl,
input: {
mimeType: file.mimeType,
data: dataArray.buffer,
dataSize: dataArray.byteLength,
},
});
} catch (error: any) {
edit({ inputError: `Issue downloading web file: ${error?.message || (typeof error === 'string' ? error : JSON.stringify(error))}` });
}
} else
edit({ inputError: 'No content or file found at this link' });
} catch (error: any) {
+11 -4
View File
@@ -6,15 +6,22 @@
*/
import { canvasToDataURLAndMimeType } from './canvasUtils';
import { createBlobURLFromDataURL } from './urlUtils';
import { convert_Base64DataURL_To_Base64WithMimeType, convert_Base64WithMimeType_To_Blob, } from '~/common/util/blobUtils';
/**
* Opens an image Data URL in a new tab
*/
export function showImageDataURLInNewTab(imageDataURL: string) {
const blobURL = createBlobURLFromDataURL(imageDataURL);
return blobURL ? showBlobURLInNewTab(blobURL) : false;
export async function showImageDataURLInNewTab(imageDataURL: string) {
try {
const { base64Data, mimeType } = convert_Base64DataURL_To_Base64WithMimeType(imageDataURL, 'showImageDataURLInNewTab');
const imageBlob = await convert_Base64WithMimeType_To_Blob(base64Data, mimeType, 'showImageDataURLInNewTab')
// NOTE: we don't really know when to release this, as the user may still be viewing the image in the new tab
return URL.createObjectURL(imageBlob);
} catch (error) {
console.warn('showImageDataURLInNewTab: Failed to convert image Data URL to Blob URL.', error);
return false;
}
}
export function showBlobURLInNewTab(blobURL: string) {
-37
View File
@@ -161,40 +161,3 @@ export function extractUrlsFromText(text: string): string[] {
// }
// }
// }
/**
* Creates a Blob Object URL (that can be opened in a new tab with window.open, for instance)
*/
export function createBlobURLFromData(base64Data: string, mimeType: string) {
const byteArray = base64ToUint8Array(base64Data);
const blob = new Blob([byteArray], { type: mimeType });
return URL.createObjectURL(blob);
}
export function base64ToUint8Array(base64Data: string) {
const binaryString = atob(base64Data);
return Uint8Array.from(binaryString, char => char.charCodeAt(0));
}
export function base64ToArrayBuffer(base64Data: string) {
return base64ToUint8Array(base64Data).buffer;
}
/**
* Creates a Blob Object URL (that can be opened in a new tab with window.open, for instance) from a Data URL
*/
export function createBlobURLFromDataURL(dataURL: string) {
if (!dataURL.startsWith('data:')) {
console.error('createBlobURLFromDataURL: Invalid data URL', dataURL);
return null;
}
const mimeType = dataURL.slice(5, dataURL.indexOf(';'));
const base64Data = dataURL.slice(dataURL.indexOf(',') + 1);
if (!mimeType || !base64Data) {
console.error('createBlobURLFromDataURL: Invalid data URL', dataURL);
return null;
}
return createBlobURLFromData(base64Data, mimeType);
}
+3 -3
View File
@@ -3,6 +3,7 @@ import { addDBImageAsset } from '~/modules/dblobs/dblobs.images';
import type { MaybePromise } from '~/common/types/useful.types';
import { DEFAULT_ADRAFT_IMAGE_MIMETYPE } from '~/common/attachment-drafts/attachment.pipeline';
import { convertBase64Image, getImageDimensions } from '~/common/util/imageUtils';
import { convert_Base64_To_UInt8Array } from '~/common/util/blobUtils';
import { create_CodeExecutionInvocation_ContentFragment, create_CodeExecutionResponse_ContentFragment, create_FunctionCallInvocation_ContentFragment, createAnnotationsVoidFragment, createDMessageDataRefDBlob, createDVoidWebCitation, createErrorContentFragment, createImageContentFragment, createModelAuxVoidFragment, createTextContentFragment, DVoidModelAuxPart, isContentFragment, isModelAuxPart, isTextContentFragment, isVoidAnnotationsFragment, isVoidFragment } from '~/common/stores/chat/chat.fragments';
import { ellipsizeMiddle } from '~/common/util/textUtils';
import { metricsFinishChatGenerateLg, metricsPendChatGenerateLg } from '~/common/stores/metrics/metrics.chatgenerate';
@@ -14,7 +15,6 @@ import type { AixClientDebugger, AixFrameId } from './debugger/memstore-aix-clie
import { aixClientDebugger_completeFrame, aixClientDebugger_init, aixClientDebugger_recordParticleReceived, aixClientDebugger_setProfilerMeasurements, aixClientDebugger_setRequest } from './debugger/reassembler-debug';
import { AixChatGenerateContent_LL, DEBUG_PARTICLES } from './aix.client';
import { base64ToUint8Array } from '~/common/util/urlUtils';
// configuration
@@ -400,8 +400,8 @@ export class ContentReassembler {
try {
// create blob and play audio
const bytes = base64ToUint8Array(base64Data); // convert base64 to blob
// create blob and play audio - this will throw on malformed data
const bytes = convert_Base64_To_UInt8Array(base64Data, 'ContentReassembler.onAppendInlineAudio');
const audioBlob = new Blob([bytes], { type: mimeType });
const audioUrl = URL.createObjectURL(audioBlob);
+9 -4
View File
@@ -1,5 +1,5 @@
import { Is } from '~/common/util/pwaUtils';
import { createBlobURLFromData } from '~/common/util/urlUtils';
import { convert_Base64WithMimeType_To_Blob } from '~/common/util/blobUtils';
import { resizeBase64ImageIfNeeded } from '~/common/util/imageUtils';
import { _addDBAsset, gcDBAssetsByScope, getDBAsset } from './dblobs.db';
@@ -67,9 +67,14 @@ export async function getImageAsset(id: DBlobAssetId) {
export async function getImageAssetAsBlobURL(id: DBlobAssetId) {
const imageAsset = await getImageAsset(id);
if (imageAsset)
return createBlobURLFromData(imageAsset.data.base64, imageAsset.data.mimeType);
return null;
if (!imageAsset) return null;
try {
const imageBlob = await convert_Base64WithMimeType_To_Blob(imageAsset.data.base64, imageAsset.data.mimeType, 'getImageAssetAsBlobURL');
return URL.createObjectURL(imageBlob);
} catch (error) {
console.warn('[DEV] getImageAssetAsBlobURL: Failed to convert image data to Blob.', error);
return null;
}
}
// export async function getImageAssetAsDataURL(id: DBlobAssetId) {
+7 -5
View File
@@ -4,7 +4,7 @@ import { AudioLivePlayer } from '~/common/util/audio/AudioLivePlayer';
import { AudioPlayer } from '~/common/util/audio/AudioPlayer';
import { CapabilityElevenLabsSpeechSynthesis } from '~/common/components/useCapabilities';
import { apiStream } from '~/common/util/trpc.client';
import { base64ToArrayBuffer } from '~/common/util/urlUtils';
import { convert_Base64_To_UInt8Array } from '~/common/util/blobUtils';
import { useUIPreferencesStore } from '~/common/stores/store-ui';
import { getElevenLabsData, useElevenLabsData } from './store-module-elevenlabs';
@@ -57,13 +57,15 @@ export async function elevenLabsSpeakText(text: string, voiceId: string | undefi
if (!liveAudioPlayer)
liveAudioPlayer = new AudioLivePlayer();
const chunkBuffer = base64ToArrayBuffer(piece.audioChunk.base64);
liveAudioPlayer.enqueueChunk(chunkBuffer);
// enqueue a decoded audio chunk - this will throw on malformed base64 data
const chunkArray = convert_Base64_To_UInt8Array(piece.audioChunk.base64, 'elevenLabsSpeakText (chunk)')
liveAudioPlayer.enqueueChunk(chunkArray.buffer);
} else if (piece.audio) {
// also consieder mergin LiveAudioPlayer into AudioPlayer
void AudioPlayer.playBuffer(base64ToArrayBuffer(piece.audio.base64)); // fire/forget - it's a single piece of audio (could be long tho)
// also consider merging LiveAudioPlayer into AudioPlayer - note this will throw on malformed base64 data
const audioArray = convert_Base64_To_UInt8Array(piece.audio.base64, 'elevenLabsSpeakText');
void AudioPlayer.playBuffer(audioArray.buffer); // fire/forget - it's a single piece of audio (could be long tho)
} else if (piece.errorMessage)
console.log('ElevenLabs issue:', piece.errorMessage);