diff --git a/src/apps/chat/components/composer/llmattachments/LLMAttachmentMenu.tsx b/src/apps/chat/components/composer/llmattachments/LLMAttachmentMenu.tsx
index 89e590804..a5f489508 100644
--- a/src/apps/chat/components/composer/llmattachments/LLMAttachmentMenu.tsx
+++ b/src/apps/chat/components/composer/llmattachments/LLMAttachmentMenu.tsx
@@ -314,7 +314,7 @@ export function LLMAttachmentMenu(props: {
{
event.preventDefault();
event.stopPropagation();
- showImageDataURLInNewTab(draftInput?.urlImage?.imgDataUrl || '');
+ void showImageDataURLInNewTab(draftInput?.urlImage?.imgDataUrl || '');
}}>
open
diff --git a/src/common/attachment-drafts/attachment.pipeline.ts b/src/common/attachment-drafts/attachment.pipeline.ts
index a41935ed7..02d99480a 100644
--- a/src/common/attachment-drafts/attachment.pipeline.ts
+++ b/src/common/attachment-drafts/attachment.pipeline.ts
@@ -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 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);
-}
diff --git a/src/modules/aix/client/ContentReassembler.ts b/src/modules/aix/client/ContentReassembler.ts
index afdf1f638..781121750 100644
--- a/src/modules/aix/client/ContentReassembler.ts
+++ b/src/modules/aix/client/ContentReassembler.ts
@@ -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);
diff --git a/src/modules/dblobs/dblobs.images.ts b/src/modules/dblobs/dblobs.images.ts
index 61cd898ff..b16294a07 100644
--- a/src/modules/dblobs/dblobs.images.ts
+++ b/src/modules/dblobs/dblobs.images.ts
@@ -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) {
diff --git a/src/modules/elevenlabs/elevenlabs.client.ts b/src/modules/elevenlabs/elevenlabs.client.ts
index 247270212..bcd10b473 100644
--- a/src/modules/elevenlabs/elevenlabs.client.ts
+++ b/src/modules/elevenlabs/elevenlabs.client.ts
@@ -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);