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);