import { isModelId, type ChatMessage, type Conversation, type MessageRole, type MessageStatus, } from "@/lib/chat/types"; const STORAGE_PREFIX = "ai-control-center:chats:"; const MAX_CONVERSATIONS = 50; const ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; const messageRoles = new Set(["user", "assistant"]); const messageStatuses = new Set(["streaming", "complete", "error", "stopped"]); export function storageKey(userId: string): string { return `${STORAGE_PREFIX}${encodeURIComponent(userId)}`; } export function loadConversations(userId: string): Conversation[] { try { if (typeof window === "undefined") return []; const serialized = window.localStorage.getItem(storageKey(userId)); if (serialized === null) return []; const value: unknown = JSON.parse(serialized); if (!Array.isArray(value) || !value.every(isConversation)) return []; return value.toSorted((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt)); } catch { return []; } } export function saveConversations(userId: string, conversations: Conversation[]): void { try { if (typeof window === "undefined") return; const latestConversations = conversations .filter(isConversation) .toSorted((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt)) .slice(0, MAX_CONVERSATIONS); window.localStorage.setItem(storageKey(userId), JSON.stringify(latestConversations)); } catch { // Browser storage can be disabled or unavailable; persistence is best effort. } } function isConversation(value: unknown): value is Conversation { if (!isRecord(value)) return false; return ( isNonEmptyString(value.id) && typeof value.title === "string" && isModelId(value.modelId) && isIsoDate(value.updatedAt) && Array.isArray(value.messages) && value.messages.every(isChatMessage) ); } function isChatMessage(value: unknown): value is ChatMessage { if (!isRecord(value)) return false; return ( isNonEmptyString(value.id) && isMessageRole(value.role) && typeof value.content === "string" && isIsoDate(value.createdAt) && isMessageStatus(value.status) ); } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function isNonEmptyString(value: unknown): value is string { return typeof value === "string" && value.length > 0; } function isIsoDate(value: unknown): value is string { if (typeof value !== "string" || !ISO_TIMESTAMP.test(value)) return false; const date = new Date(value); return !Number.isNaN(date.getTime()) && date.toISOString() === value; } function isMessageRole(value: unknown): value is MessageRole { return typeof value === "string" && messageRoles.has(value as MessageRole); } function isMessageStatus(value: unknown): value is MessageStatus { return typeof value === "string" && messageStatuses.has(value as MessageStatus); }