Files
cozsweet-frontend-nextjs/src/stores/chat/ui-message.ts
T

90 lines
2.5 KiB
TypeScript

import { z } from "zod";
import { 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(),
});
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);
}