| |
| import TextLineStream from 'textlinestream'; |
| import { APIMessage, Message } from './types'; |
|
|
| |
| import { asyncIterator } from '@sec-ant/readable-stream/ponyfill/asyncIterator'; |
|
|
| |
| export const isString = (x: any) => !!x.toLowerCase; |
| |
| export const isBoolean = (x: any) => x === true || x === false; |
| |
| export const isNumeric = (n: any) => !isString(n) && !isNaN(n) && !isBoolean(n); |
| export const escapeAttr = (str: string) => |
| str.replace(/>/g, '>').replace(/"/g, '"'); |
|
|
| |
| export async function* getSSEStreamAsync(fetchResponse: Response) { |
| if (!fetchResponse.body) throw new Error('Response body is empty'); |
| const lines: ReadableStream<string> = fetchResponse.body |
| .pipeThrough(new TextDecoderStream()) |
| .pipeThrough(new TextLineStream()); |
| |
| for await (const line of asyncIterator(lines)) { |
| |
| 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'); |
| } |
| } |
| } |
|
|
| |
| export const copyStr = (textToCopy: string) => { |
| |
| if (navigator.clipboard && window.isSecureContext) { |
| navigator.clipboard.writeText(textToCopy); |
| } else { |
| |
| const textArea = document.createElement('textarea'); |
| textArea.value = textToCopy; |
| |
| textArea.style.position = 'absolute'; |
| textArea.style.left = '-999999px'; |
| document.body.prepend(textArea); |
| textArea.select(); |
| document.execCommand('copy'); |
| } |
| }; |
|
|
| |
| |
| |
| |
| export function normalizeMsgsForAPI(messages: Readonly<Message[]>) { |
| return messages.map((msg) => { |
| let newContent = ''; |
|
|
| for (const extra of msg.extra ?? []) { |
| if (extra.type === 'context') { |
| newContent += `${extra.content}\n\n`; |
| } |
| } |
|
|
| newContent += msg.content; |
|
|
| return { |
| role: msg.role, |
| content: newContent, |
| }; |
| }) as APIMessage[]; |
| } |
|
|
| |
| |
| |
| export function filterThoughtFromMsgs(messages: APIMessage[]) { |
| return messages.map((msg) => { |
| return { |
| role: msg.role, |
| content: |
| msg.role === 'assistant' |
| ? msg.content.split('</think>').at(-1)!.trim() |
| : msg.content, |
| } as APIMessage; |
| }); |
| } |
|
|
| export function classNames(classes: Record<string, boolean>): string { |
| return Object.entries(classes) |
| .filter(([_, value]) => value) |
| .map(([key, _]) => key) |
| .join(' '); |
| } |
|
|
| export const delay = (ms: number) => |
| new Promise((resolve) => setTimeout(resolve, ms)); |
|
|
| export const throttle = <T extends unknown[]>( |
| callback: (...args: T) => void, |
| delay: number |
| ) => { |
| let isWaiting = false; |
|
|
| return (...args: T) => { |
| if (isWaiting) { |
| return; |
| } |
|
|
| callback(...args); |
| isWaiting = true; |
|
|
| setTimeout(() => { |
| isWaiting = false; |
| }, delay); |
| }; |
| }; |
|
|
| export const cleanCurrentUrl = (removeQueryParams: string[]) => { |
| const url = new URL(window.location.href); |
| removeQueryParams.forEach((param) => { |
| url.searchParams.delete(param); |
| }); |
| window.history.replaceState({}, '', url.toString()); |
| }; |
|
|