Spaces:
Running
Running
| /** Bound in-memory + sessionStorage cache of prefetch NDJSON chat streams. */ | |
| const STORE_KEY = 'csa.starterPrefetch.v1'; | |
| export const MAX_PREFETCH_PROMPTS = 8; | |
| const memory = new Map(); | |
| function normalizePrompt(prompt) { | |
| return String(prompt || '').replace(/\s+/g, ' ').trim(); | |
| } | |
| function readStore(userKey) { | |
| try { | |
| const raw = sessionStorage.getItem(`${STORE_KEY}:${userKey}`); | |
| if (!raw) return {}; | |
| const parsed = JSON.parse(raw); | |
| return parsed && typeof parsed === 'object' ? parsed : {}; | |
| } catch { | |
| return {}; | |
| } | |
| } | |
| function writeStore(userKey, data) { | |
| try { | |
| sessionStorage.setItem(`${STORE_KEY}:${userKey}`, JSON.stringify(data)); | |
| } catch { | |
| /* ignore */ | |
| } | |
| } | |
| function touchMemory(userKey, prompt, entry) { | |
| const key = `${userKey}::${prompt}`; | |
| memory.set(key, entry); | |
| while (memory.size > MAX_PREFETCH_PROMPTS * 4) { | |
| const first = memory.keys().next().value; | |
| memory.delete(first); | |
| } | |
| } | |
| export function getPrefetchedStream(userKey, prompt) { | |
| const p = normalizePrompt(prompt); | |
| if (!p) return null; | |
| const mem = memory.get(`${userKey}::${p}`); | |
| if (mem?.lines?.length) return mem; | |
| const stored = readStore(userKey)[p]; | |
| if (stored?.lines?.length) { | |
| touchMemory(userKey, p, stored); | |
| return stored; | |
| } | |
| return null; | |
| } | |
| export function putPrefetchedStream(userKey, prompt, lines) { | |
| const p = normalizePrompt(prompt); | |
| if (!p || !Array.isArray(lines) || lines.length === 0) return; | |
| const entry = { lines, ts: Date.now() }; | |
| touchMemory(userKey, p, entry); | |
| const stored = readStore(userKey); | |
| stored[p] = entry; | |
| const keys = Object.keys(stored); | |
| if (keys.length > MAX_PREFETCH_PROMPTS) { | |
| keys | |
| .sort((a, b) => (stored[a].ts || 0) - (stored[b].ts || 0)) | |
| .slice(0, keys.length - MAX_PREFETCH_PROMPTS) | |
| .forEach((k) => { delete stored[k]; }); | |
| } | |
| writeStore(userKey, stored); | |
| } | |
| export function dropPrefetchedStream(userKey, prompt) { | |
| const p = normalizePrompt(prompt); | |
| memory.delete(`${userKey}::${p}`); | |
| const stored = readStore(userKey); | |
| if (stored[p]) { | |
| delete stored[p]; | |
| writeStore(userKey, stored); | |
| } | |
| } | |
| export async function readNdjsonLines(response) { | |
| if (!response?.body) return []; | |
| const reader = response.body.getReader(); | |
| const decoder = new TextDecoder(); | |
| const lines = []; | |
| let buffer = ''; | |
| while (true) { | |
| const { done, value } = await reader.read(); | |
| if (done) break; | |
| buffer += decoder.decode(value, { stream: true }); | |
| const parts = buffer.split('\n'); | |
| buffer = parts.pop() ?? ''; | |
| parts.forEach((line) => { | |
| if (line.trim()) lines.push(line); | |
| }); | |
| } | |
| if (buffer.trim()) lines.push(buffer); | |
| return lines; | |
| } | |