ai-chatbot/lib/utils.ts
Your Name 1fe2c3ba8a
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: block image send when points are insufficient
2026-02-21 01:54:13 +08:00

181 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type {
CoreAssistantMessage,
CoreToolMessage,
UIMessage,
UIMessagePart,
} from 'ai';
import { type ClassValue, clsx } from 'clsx';
import { formatISO } from 'date-fns';
import { twMerge } from 'tailwind-merge';
import type { DBMessage, Document } from '@/lib/db/schema';
import { ChatSDKError, type ErrorCode } from './errors';
import type { ChatMessage, ChatTools, CustomUIDataTypes } from './types';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export const fetcher = async (url: string) => {
const response = await fetch(url);
if (!response.ok) {
const { code, cause } = await response.json();
throw new ChatSDKError(code as ErrorCode, cause);
}
return response.json();
};
export async function fetchWithErrorHandlers(
input: RequestInfo | URL,
init?: RequestInit,
) {
try {
const response = await fetch(input, init);
if (!response.ok) {
let payload: any = null;
try {
payload = await response.json();
} catch {
payload = null;
}
const code =
typeof payload?.code === "string" && payload.code.includes(":")
? (payload.code as ErrorCode)
: null;
const cause = typeof payload?.cause === "string" ? payload.cause : undefined;
if (code) {
throw new ChatSDKError(code, cause);
}
const fallbackMessage =
(typeof payload?.message === "string" && payload.message) ||
(typeof payload?.error === "string" && payload.error) ||
`Request failed with status ${response.status}`;
const requestError = new Error(fallbackMessage) as Error & {
status?: number;
data?: any;
};
requestError.name = "RequestError";
requestError.status = response.status;
requestError.data = payload;
throw requestError;
}
return response;
} catch (error: unknown) {
if (typeof navigator !== 'undefined' && !navigator.onLine) {
throw new ChatSDKError('offline:chat');
}
throw error;
}
}
export function getLocalStorage(key: string) {
if (typeof window !== 'undefined') {
return JSON.parse(localStorage.getItem(key) || '[]');
}
return [];
}
export function generateUUID(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
type ResponseMessageWithoutId = CoreToolMessage | CoreAssistantMessage;
type ResponseMessage = ResponseMessageWithoutId & { id: string };
export function getMostRecentUserMessage(messages: UIMessage[]) {
const userMessages = messages.filter((message) => message.role === 'user');
return userMessages.at(-1);
}
export function getDocumentTimestampByIndex(
documents: Document[],
index: number,
) {
if (!documents) { return new Date(); }
if (index > documents.length) { return new Date(); }
return documents[index].createdAt;
}
export function getTrailingMessageId({
messages,
}: {
messages: ResponseMessage[];
}): string | null {
const trailingMessage = messages.at(-1);
if (!trailingMessage) { return null; }
return trailingMessage.id;
}
export function sanitizeText(text: string) {
return text.replace('<has_function_call>', '');
}
export function convertToUIMessages(messages: DBMessage[]): ChatMessage[] {
return messages.map((message) => ({
id: message.id,
role: message.role as 'user' | 'assistant' | 'system',
parts: message.parts as UIMessagePart<CustomUIDataTypes, ChatTools>[],
metadata: {
createdAt: formatISO(message.createdAt),
},
}));
}
export function getTextFromMessage(message: ChatMessage | UIMessage): string {
return message.parts
.filter((part) => part.type === 'text')
.map((part) => (part as { type: 'text'; text: string}).text)
.join('');
}
/**
* 将UTC时间转换为北京时间(UTC+8)
* @param date - Date对象或时间字符串
* @returns 北京时间对应的Date对象
*/
export function toBeijingTime(date: Date | string): Date {
const d = typeof date === "string" ? new Date(date) : date;
// 获取UTC时间戳
const utcTime = d.getTime() + (d.getTimezoneOffset() * 60 * 1000);
// 转换为北京时间(UTC+8)
const beijingTime = new Date(utcTime + (8 * 60 * 60 * 1000));
return beijingTime;
}
/**
* 格式化时间为北京时间字符串
* @param date - Date对象或时间字符串
* @param options - Intl.DateTimeFormatOptions 格式化选项
* @returns 格式化后的北京时间字符串
*/
export function formatBeijingTime(
date: Date | string,
options?: Intl.DateTimeFormatOptions
): string {
const beijingTime = toBeijingTime(date);
const defaultOptions: Intl.DateTimeFormatOptions = {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
timeZone: "Asia/Shanghai",
...options,
};
return beijingTime.toLocaleString("zh-CN", defaultOptions);
}