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