Cybersecurity-Panel / frontend /src /utils /starterPrefetchCache.js
NeonClary's picture
Workspace widget preview, multi-source reference search, and arXiv CORS proxy.
d0878a6 verified
Raw
History Blame Contribute Delete
2.79 kB
/** 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;
}