diff --git a/web/src/helpers/base64.js b/web/src/helpers/base64.js new file mode 100644 index 00000000..1e15d6b9 --- /dev/null +++ b/web/src/helpers/base64.js @@ -0,0 +1,56 @@ +/* +Copyright (C) 2025 QuantumNous + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +For commercial licensing, please contact support@quantumnous.com +*/ + +const toBinaryString = (text) => { + if (typeof TextEncoder !== 'undefined') { + const bytes = new TextEncoder().encode(text); + let binary = ''; + + bytes.forEach((byte) => { + binary += String.fromCharCode(byte); + }); + + return binary; + } + + return encodeURIComponent(text).replace(/%([0-9A-F]{2})/g, (_, hex) => + String.fromCharCode(parseInt(hex, 16)), + ); +}; + +export const encodeToBase64 = (value) => { + const input = value == null ? '' : String(value); + + if (typeof window === 'undefined') { + if (typeof Buffer !== 'undefined') { + return Buffer.from(input, 'utf-8').toString('base64'); + } + if ( + typeof globalThis !== 'undefined' && + typeof globalThis.btoa === 'function' + ) { + return globalThis.btoa(toBinaryString(input)); + } + throw new Error( + 'Base64 encoding is unavailable in the current environment', + ); + } + + return window.btoa(toBinaryString(input)); +}; diff --git a/web/src/helpers/index.js b/web/src/helpers/index.js index 26ebc649..cfb0270e 100644 --- a/web/src/helpers/index.js +++ b/web/src/helpers/index.js @@ -20,6 +20,7 @@ For commercial licensing, please contact support@quantumnous.com export * from './history'; export * from './auth'; export * from './utils'; +export * from './base64'; export * from './api'; export * from './render'; export * from './log'; diff --git a/web/src/hooks/tokens/useTokensData.jsx b/web/src/hooks/tokens/useTokensData.jsx index 9b3a071c..a34508f4 100644 --- a/web/src/hooks/tokens/useTokensData.jsx +++ b/web/src/hooks/tokens/useTokensData.jsx @@ -20,7 +20,13 @@ For commercial licensing, please contact support@quantumnous.com import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Modal } from '@douyinfe/semi-ui'; -import { API, copy, showError, showSuccess } from '../../helpers'; +import { + API, + copy, + showError, + showSuccess, + encodeToBase64, +} from '../../helpers'; import { ITEMS_PER_PAGE } from '../../constants'; import { useTableCompactMode } from '../common/useTableCompactMode'; @@ -136,7 +142,7 @@ export const useTokensData = (openFluentNotification) => { apiKey: 'sk-' + record.key, }; let encodedConfig = encodeURIComponent( - btoa(JSON.stringify(cherryConfig)), + encodeToBase64(JSON.stringify(cherryConfig)), ); url = url.replaceAll('{cherryConfig}', encodedConfig); } else { diff --git a/web/src/pages/Playground/index.jsx b/web/src/pages/Playground/index.jsx index dcf8367c..c2083fde 100644 --- a/web/src/pages/Playground/index.jsx +++ b/web/src/pages/Playground/index.jsx @@ -47,6 +47,7 @@ import { createLoadingAssistantMessage, getTextContent, buildApiPayload, + encodeToBase64, } from '../../helpers'; // Components @@ -72,7 +73,7 @@ const generateAvatarDataUrl = (username) => { ${firstLetter} `; - return `data:image/svg+xml;base64,${btoa(svg)}`; + return `data:image/svg+xml;base64,${encodeToBase64(svg)}`; }; const Playground = () => {