2023-08-01 17:50:00 +02:00
|
|
|
export function json(content: unknown) {
|
|
|
|
const headers = new Headers();
|
|
|
|
headers.append("Content-Type", "application/json");
|
|
|
|
return new Response(JSON.stringify(content), {
|
|
|
|
headers,
|
|
|
|
});
|
|
|
|
}
|
2023-08-01 21:35:21 +02:00
|
|
|
|
|
|
|
export const isValidUrl = (urlString: string) => {
|
|
|
|
try {
|
|
|
|
return Boolean(new URL(urlString));
|
2025-01-19 16:43:00 +01:00
|
|
|
} catch (_e) {
|
2023-08-01 21:35:21 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export const fixRenderedMarkdown = (content: string) => {
|
|
|
|
return content.replace("***\n", "---")
|
|
|
|
.replace("----------------", "---")
|
|
|
|
.replace("\n---", "---")
|
|
|
|
.replace(/^(date:[^'\n]*)'|'/gm, (match, p1, p2) => {
|
|
|
|
if (p1) {
|
|
|
|
// This is a line starting with date: followed by single quotes
|
|
|
|
return p1.replace(/'/gm, "");
|
|
|
|
} else if (p2) {
|
|
|
|
return "";
|
|
|
|
} else {
|
|
|
|
// This is a line with single quotes, but not starting with date:
|
|
|
|
return match;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
2023-08-02 15:05:35 +02:00
|
|
|
|
|
|
|
export async function fetchStream(url: string, cb: (chunk: string) => void) {
|
|
|
|
const response = await fetch(url);
|
|
|
|
const reader = response?.body?.getReader();
|
|
|
|
if (reader) {
|
|
|
|
while (true) {
|
|
|
|
const { done, value } = await reader.read();
|
|
|
|
if (done) return;
|
|
|
|
const data = new TextDecoder().decode(value);
|
|
|
|
data
|
|
|
|
.split("$")
|
|
|
|
.filter((d) => d && d.length)
|
|
|
|
.map((d) => cb(Array.isArray(d) ? d[0] : d));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-08 16:52:26 +02:00
|
|
|
export function hashString(message: string) {
|
|
|
|
let hash = 0;
|
|
|
|
for (let i = 0; i < message.length; i++) {
|
|
|
|
const char = message.charCodeAt(i);
|
|
|
|
hash = ((hash << 5) - hash) + char;
|
|
|
|
hash = hash & hash; // Convert to 32bit integer
|
|
|
|
}
|
|
|
|
return hash;
|
|
|
|
}
|
|
|
|
|
2023-08-02 15:05:35 +02:00
|
|
|
export const createStreamResponse = () => {
|
|
|
|
let controller: ReadableStreamController<ArrayBufferView>;
|
|
|
|
const body = new ReadableStream({
|
|
|
|
start(cont) {
|
|
|
|
controller = cont;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const response = new Response(body, {
|
|
|
|
headers: {
|
|
|
|
"content-type": "text/plain",
|
|
|
|
"x-content-type-options": "nosniff",
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
function cancel() {
|
|
|
|
controller.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
function enqueue(chunk: string) {
|
|
|
|
controller?.enqueue(new TextEncoder().encode("$" + chunk));
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
response,
|
|
|
|
cancel,
|
|
|
|
enqueue,
|
|
|
|
};
|
|
|
|
};
|
2023-08-04 13:48:12 +02:00
|
|
|
|
2025-01-19 16:43:00 +01:00
|
|
|
export type StreamResponse = ReturnType<typeof createStreamResponse>;
|
|
|
|
|
2023-08-04 13:48:12 +02:00
|
|
|
export function debounce<T extends (...args: Parameters<T>) => void>(
|
|
|
|
this: ThisParameterType<T>,
|
|
|
|
fn: T,
|
|
|
|
delay = 300,
|
|
|
|
) {
|
|
|
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
|
|
return (...args: Parameters<T>) => {
|
|
|
|
clearTimeout(timer);
|
|
|
|
timer = setTimeout(() => fn.apply(this, args), delay);
|
|
|
|
};
|
|
|
|
}
|
2023-08-29 13:48:52 +02:00
|
|
|
|
|
|
|
export function parseRating(rating: string | number) {
|
|
|
|
if (typeof rating === "string") {
|
|
|
|
return [...rating.matchAll(/⭐/)].length;
|
|
|
|
}
|
|
|
|
return rating;
|
|
|
|
}
|