Something went wrong. Try again.
small gleam coding and persistent agent daemon with a detachable cli and a canvas ui forked from okami.mom/albedo
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889import { SvelteMap } from 'svelte/reactivity';import { withoutMarkers, type Attached } from './attachments';
// What was typed in a chat's composer and not yet sent. The composer is// destroyed with its window when another board is shown or the page goes, so// a draft lives here, by session, and in localStorage. Only the text is kept// there: images are too big for it, so a reload drops them and their markers.
const KEY = 'albedo.draft.v1:';/** A draft untouched this long is not worth keeping; the session may be gone. */const STALE_MS = 30 * 24 * 60 * 60 * 1000;const SAVE_AFTER_MS = 300;
function read(session: string): string { try { const saved = JSON.parse(localStorage.getItem(KEY + session) ?? ''); return typeof saved?.text === 'string' ? withoutMarkers(saved.text) : ''; } catch { return ''; }}
/** Drops the saved drafts nobody came back to. */function prune() { try { for (const key of Object.keys(localStorage).filter((name) => name.startsWith(KEY))) { const saved = JSON.parse(localStorage.getItem(key) ?? 'null'); if (!(Date.now() - saved?.at < STALE_MS)) localStorage.removeItem(key); } } catch {}}
const unsaved = new Set<Draft>();let timer: ReturnType<typeof setTimeout> | undefined;
function flush() { clearTimeout(timer); timer = undefined; for (const draft of unsaved) draft.save(); unsaved.clear();}
export class Draft { readonly images = new SvelteMap<number, Attached>(); /** The last image marker handed out. */ next = 0; #text: string; #session: string;
constructor(session: string) { this.#session = session; this.#text = read(session); }
get text(): string { return this.#text; }
/** Setting the text saves it a moment later, once typing pauses. */ set text(value: string) { if (value === this.#text) return; this.#text = value; unsaved.add(this); timer ??= setTimeout(flush, SAVE_AFTER_MS); }
save() { try { const text = withoutMarkers(this.#text); if (text.trim()) localStorage.setItem(KEY + this.#session, JSON.stringify({ text, at: Date.now() })); else localStorage.removeItem(KEY + this.#session); } catch {} }}
const drafts = new Map<string, Draft>();
export function draftOf(session: string): Draft { let draft = drafts.get(session); if (!draft) drafts.set(session, (draft = new Draft(session))); return draft;}
if (typeof window !== 'undefined') { prune(); // The last words typed before a reload or a closed tab are still waiting on the timer. window.addEventListener('pagehide', flush);}