KBaba7/llama.cpp
0
1// @ts-expect-error this package does not have typing2import TextLineStream from 'textlinestream';3import { APIMessage, Message } from './types';4 5// ponyfill for missing ReadableStream asyncIterator on Safari6import { asyncIterator } from '@sec-ant/readable-stream/ponyfill/asyncIterator';7import { isDev } from '../Config';8 9// eslint-disable-next-line @typescript-eslint/no-explicit-any10export const isString = (x: any) => !!x.toLowerCase;11// eslint-disable-next-line @typescript-eslint/no-explicit-any12export const isBoolean = (x: any) => x === true || x === false;13// eslint-disable-next-line @typescript-eslint/no-explicit-any14export const isNumeric = (n: any) => !isString(n) && !isNaN(n) && !isBoolean(n);15export const escapeAttr = (str: string) =>16 str.replace(/>/g, '>').replace(/"/g, '"');17 18// wrapper for SSE19export async function* getSSEStreamAsync(fetchResponse: Response) {20 if (!fetchResponse.body) throw new Error('Response body is empty');21 const lines: ReadableStream<string> = fetchResponse.body22 .pipeThrough(new TextDecoderStream())23 .pipeThrough(new TextLineStream());24 // @ts-expect-error asyncIterator complains about type, but it should work25 for await (const line of asyncIterator(lines)) {26 if (isDev) console.log({ line });27 if (line.startsWith('data:') && !line.endsWith('[DONE]')) {28 const data = JSON.parse(line.slice(5));29 yield data;30 } else if (line.startsWith('error:')) {31 const data = JSON.parse(line.slice(6));32 throw new Error(data.message || 'Unknown error');33 }34 }35}36 37// copy text to clipboard38export const copyStr = (textToCopy: string) => {39 // Navigator clipboard api needs a secure context (https)40 if (navigator.clipboard && window.isSecureContext) {41 navigator.clipboard.writeText(textToCopy);42 } else {43 // Use the 'out of viewport hidden text area' trick44 const textArea = document.createElement('textarea');45 textArea.value = textToCopy;46 // Move textarea out of the viewport so it's not visible47 textArea.style.position = 'absolute';48 textArea.style.left = '-999999px';49 document.body.prepend(textArea);50 textArea.select();51 document.execCommand('copy');52 }53};54 55/**56 * filter out redundant fields upon sending to API57 */58export function normalizeMsgsForAPI(messages: Message[]) {59 return messages.map((msg) => {60 return {61 role: msg.role,62 content: msg.content,63 };64 }) as APIMessage[];65}66 67/**68 * recommended for DeepsSeek-R1, filter out content between <think> and </think> tags69 */70export function filterThoughtFromMsgs(messages: APIMessage[]) {71 return messages.map((msg) => {72 return {73 role: msg.role,74 content:75 msg.role === 'assistant'76 ? msg.content.split('</think>').at(-1)!.trim()77 : msg.content,78 } as APIMessage;79 });80}81 82export function classNames(classes: Record<string, boolean>): string {83 return Object.entries(classes)84 .filter(([_, value]) => value)85 .map(([key, _]) => key)86 .join(' ');87}88 