// @ts-expect-error this package does not have typing import TextLineStream from 'textlinestream'; import { APIMessage, Message } from './types'; // ponyfill for missing ReadableStream asyncIterator on Safari import { asyncIterator } from '@sec-ant/readable-stream/ponyfill/asyncIterator'; import { isDev } from '../Config'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const isString = (x: any) => !!x.toLowerCase; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const isBoolean = (x: any) => x === true || x === false; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const isNumeric = (n: any) => !isString(n) && !isNaN(n) && !isBoolean(n); export const escapeAttr = (str: string) => str.replace(/>/g, '>').replace(/"/g, '"'); // wrapper for SSE export async function* getSSEStreamAsync(fetchResponse: Response) { if (!fetchResponse.body) throw new Error('Response body is empty'); const lines: ReadableStream = fetchResponse.body .pipeThrough(new TextDecoderStream()) .pipeThrough(new TextLineStream()); // @ts-expect-error asyncIterator complains about type, but it should work for await (const line of asyncIterator(lines)) { if (isDev) console.log({ line }); if (line.startsWith('data:') && !line.endsWith('[DONE]')) { const data = JSON.parse(line.slice(5)); yield data; } else if (line.startsWith('error:')) { const data = JSON.parse(line.slice(6)); throw new Error(data.message || 'Unknown error'); } } } // copy text to clipboard export const copyStr = (textToCopy: string) => { // Navigator clipboard api needs a secure context (https) if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(textToCopy); } else { // Use the 'out of viewport hidden text area' trick const textArea = document.createElement('textarea'); textArea.value = textToCopy; // Move textarea out of the viewport so it's not visible textArea.style.position = 'absolute'; textArea.style.left = '-999999px'; document.body.prepend(textArea); textArea.select(); document.execCommand('copy'); } }; /** * filter out redundant fields upon sending to API */ export function normalizeMsgsForAPI(messages: Message[]) { return messages.map((msg) => { return { role: msg.role, content: msg.content, }; }) as APIMessage[]; } /** * recommended for DeepsSeek-R1, filter out content between and tags */ export function filterThoughtFromMsgs(messages: APIMessage[]) { return messages.map((msg) => { return { role: msg.role, content: msg.role === 'assistant' ? msg.content.split('').at(-1)!.trim() : msg.content, } as APIMessage; }); } export function classNames(classes: Record): string { return Object.entries(classes) .filter(([_, value]) => value) .map(([key, _]) => key) .join(' '); } export const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));