91 lines
2.6 KiB
TypeScript
91 lines
2.6 KiB
TypeScript
import { z } from "zod";
|
|
import { ChatActionSchema, CommercialActionSchema } from "@/data/schemas/chat";
|
|
|
|
export const UiMessageSchema = z.object({
|
|
displayId: z.string().min(1),
|
|
remoteId: z.string().min(1).optional(),
|
|
clientId: z.string().min(1).optional(),
|
|
content: z.string(),
|
|
isFromAI: z.boolean(),
|
|
date: z.string(),
|
|
isSynthetic: z.boolean().optional(),
|
|
imageUrl: z.string().optional(),
|
|
imagePaywalled: z.boolean().optional(),
|
|
audioUrl: z.string().optional(),
|
|
locked: z.boolean().nullable().optional(),
|
|
lockReason: z.string().nullable().optional(),
|
|
isPrivate: z.boolean().nullable().optional(),
|
|
lockedPrivate: z.boolean().nullable().optional(),
|
|
privateMessageHint: z.string().nullable().optional(),
|
|
commercialAction: CommercialActionSchema.nullable().optional(),
|
|
chatAction: ChatActionSchema.nullable().optional(),
|
|
});
|
|
|
|
export type UiMessage = z.infer<typeof UiMessageSchema>;
|
|
|
|
let fallbackClientId = 0;
|
|
|
|
export function createRemoteUiMessageIdentity(
|
|
remoteId: string,
|
|
isFromAI: boolean,
|
|
occurrence = 0,
|
|
): Pick<UiMessage, "displayId" | "remoteId"> {
|
|
const role = isFromAI ? "assistant" : "user";
|
|
const base = `server:${encodeURIComponent(remoteId)}:${role}`;
|
|
return {
|
|
displayId: occurrence > 0 ? `${base}:${occurrence}` : base,
|
|
remoteId,
|
|
};
|
|
}
|
|
|
|
export function createLegacyUiMessageIdentity(
|
|
fingerprint: string,
|
|
occurrence = 0,
|
|
): Pick<UiMessage, "displayId"> {
|
|
const base = `legacy:${hashIdentity(fingerprint)}`;
|
|
return {
|
|
displayId: occurrence > 0 ? `${base}:${occurrence}` : base,
|
|
};
|
|
}
|
|
|
|
export function createClientUiMessageIdentity(
|
|
kind: "message" | "image" | "reply" | "error",
|
|
): Pick<UiMessage, "displayId" | "clientId"> {
|
|
const clientId = createClientId();
|
|
return {
|
|
clientId,
|
|
displayId: `client:${kind}:${clientId}`,
|
|
};
|
|
}
|
|
|
|
export const UiMessage = {
|
|
create(input: Omit<UiMessage, "date"> & { date?: string }): UiMessage {
|
|
return UiMessageSchema.parse({
|
|
...input,
|
|
date:
|
|
input.date ??
|
|
new Date().toLocaleTimeString([], {
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
}),
|
|
});
|
|
},
|
|
};
|
|
|
|
function createClientId(): string {
|
|
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
return globalThis.crypto.randomUUID();
|
|
}
|
|
fallbackClientId += 1;
|
|
return `${Date.now().toString(36)}-${fallbackClientId.toString(36)}`;
|
|
}
|
|
|
|
function hashIdentity(value: string): string {
|
|
let hash = 0x811c9dc5;
|
|
for (let index = 0; index < value.length; index += 1) {
|
|
hash ^= value.charCodeAt(index);
|
|
hash = Math.imul(hash, 0x01000193);
|
|
}
|
|
return (hash >>> 0).toString(36);
|
|
}
|