"use client"; /** * Chat types shared by the chat UI, the context builder and the streaming * client. The API key rule applies here too: nothing in a chat request may * carry the key anywhere except the `x-ollama-key` header of our own route. */ export type ChatRole = "user" | "assistant"; /** A context chunk that actually travelled with a request. */ export type ChatSource = { deckId: string; deckTitle: string; section: string; }; export type ChatAttachment = { name: string; kind: "photo" | "document" | "text"; chars: number; }; export type ChatError = { message: string; kind?: string }; export type ChatMessage = { id: string; role: ChatRole; content: string; /** Reasoning streamed separately; hidden behind the Thinking row. */ thinking?: string; /** Model that produced an assistant turn. */ model?: string; /** Context chunks sent with this turn (assistant turns display them). */ sources?: ChatSource[]; attachments?: ChatAttachment[]; at: number; /** Set when an assistant turn failed; content may hold a partial answer. */ error?: ChatError; }; /** * What the chat is allowed to ground on. `all` = every deck with notes, * `none` = general chat, `decks` = a chosen set. */ export type ChatScope = | { kind: "all" } | { kind: "none" } | { kind: "decks"; deckIds: string[] }; export type ChatModelChoice = { name: string; badge: "text" | "vision" | "both"; available: boolean | null; }; /** Last model choice, remembered across visits. */ const MODEL_KEY = "freezmo.chat.model.v1"; export function readStoredChatModel(): string | null { try { const value = window.localStorage.getItem(MODEL_KEY); return value && value.trim() ? value : null; } catch { return null; } } export function storeChatModel(model: string): void { try { window.localStorage.setItem(MODEL_KEY, model); } catch { /* private mode */ } } export function chatMessageId(prefix: string): string { const rand = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID().slice(0, 8) : Math.random().toString(36).slice(2, 10); return `${prefix}_${Date.now().toString(36)}_${rand}`; }