haixunMaster/lib/utils.ts

52 lines
1.8 KiB
TypeScript
Raw Permalink Normal View History

2026-06-21 12:50:31 +00:00
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export const THREADS_MAX_CHARS = 500;
/** 避免素材特殊字元讓 provider API 在組 request JSON 時解析失敗 */
export function sanitizePromptText(text: string | null | undefined): string {
if (!text) return "";
return text
.replace(/\r\n/g, "\n")
.replace(/[\u2028\u2029]/g, "\n")
.replace(/[\uD800-\uDFFF]/g, "")
.replace(/\\(?!u[0-9a-fA-F]{4})/g, "")
.replace(/\\u(?![0-9a-fA-F]{4})/g, "u")
.replace(/\\/g, "")
.replace(/\u0000/g, "");
}
/** 安全解析 fetch 回應,避免空 body 造成 JSON.parse 崩潰。 */
export async function parseFetchJson<T = Record<string, unknown>>(res: Response): Promise<T> {
const text = await res.text();
if (!text.trim()) {
throw new Error(`伺服器回應為空(${res.status}`);
}
try {
return JSON.parse(text) as T;
} catch {
throw new Error(`伺服器回應格式錯誤(${res.status}`);
}
}
export function humanDelay(minMs = 800, maxMs = 2000): Promise<void> {
const ms = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;
return new Promise((resolve) => setTimeout(resolve, ms));
}
export function parseCount(text: string | null | undefined): number {
if (!text) return 0;
const cleaned = text.trim().toLowerCase().replace(/,/g, "");
const match = cleaned.match(/([\d.]+)\s*([km萬]?)/);
if (!match) return parseInt(cleaned, 10) || 0;
const value = parseFloat(match[1]);
const suffix = match[2];
if (suffix === "k") return Math.round(value * 1000);
if (suffix === "m") return Math.round(value * 1_000_000);
if (suffix === "萬") return Math.round(value * 10_000);
return Math.round(value);
}