Files

190 lines
5.8 KiB
JavaScript

import { PromptStatus, StoreEvents } from "./constants.js";
import { createPromptState, revokePromptBlobs } from "./promptState.js";
import { PersistenceAdapter } from "./persistence.js";
class Store {
constructor() {
this.prompts = new Map();
this.ui = {
searchQuery: "",
currentlyActivePromptId: null,
activeSubmenuPromptId: null,
activeSubmenuBatchImages: null,
mainQueueScrollTop: null,
sequenceNumber: 0,
sidebarContainer: null,
cardStack: null
};
this.listeners = new Map();
this._saveTimer = null;
this.init();
}
init() {
const saved = PersistenceAdapter.load();
for (const item of saved) {
const state = createPromptState(item.pid, item);
this.prompts.set(String(state.pid), state);
if (state.timestamp > this.ui.sequenceNumber) {
this.ui.sequenceNumber = state.timestamp;
}
}
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event).add(callback);
return () => this.off(event, callback);
}
off(event, callback) {
if (this.listeners.has(event)) {
this.listeners.get(event).delete(callback);
}
}
emit(event, payload) {
if (this.listeners.has(event)) {
for (const callback of this.listeners.get(event)) {
try {
callback(payload);
} catch (err) {
console.error(`Store error during event '${event}':`, err);
}
}
}
}
nextSequence() {
return ++this.ui.sequenceNumber;
}
scheduleSave(delay = 250) {
if (this._saveTimer) clearTimeout(this._saveTimer);
this._saveTimer = setTimeout(() => {
this._saveTimer = null;
PersistenceAdapter.save(this.prompts);
}, delay);
}
getPrompt(pid) {
return this.prompts.get(String(pid)) || null;
}
hasPrompt(pid) {
return this.prompts.has(String(pid));
}
getAllPrompts() {
return Array.from(this.prompts.values());
}
setPrompt(pid, state) {
const key = String(pid);
const isNew = !this.prompts.has(key);
this.prompts.set(key, state);
this.scheduleSave();
this.emit(isNew ? StoreEvents.PROMPT_ADDED : StoreEvents.PROMPT_UPDATED, { pid: key, prompt: state });
return state;
}
updatePrompt(pid, patch) {
const key = String(pid);
const state = this.prompts.get(key);
if (!state) return null;
Object.assign(state, patch);
this.scheduleSave();
this.emit(StoreEvents.PROMPT_UPDATED, { pid: key, prompt: state, patch });
return state;
}
deletePrompt(pid) {
const key = String(pid);
const state = this.prompts.get(key);
if (state) {
revokePromptBlobs(state);
this.prompts.delete(key);
if (this.ui.currentlyActivePromptId === key) {
this.ui.currentlyActivePromptId = null;
}
if (this.ui.activeSubmenuPromptId === key) {
this.ui.activeSubmenuPromptId = null;
}
this.scheduleSave();
this.emit(StoreEvents.PROMPT_DELETED, { pid: key });
return true;
}
return false;
}
pruneHistory(maxItems = 64) {
const candidates = PersistenceAdapter.getPruneCandidates(this.prompts, maxItems);
for (const pid of candidates) {
this.deletePrompt(pid);
}
}
clearFinished() {
const toDelete = [];
for (const [pid, state] of this.prompts.entries()) {
if (state.status !== PromptStatus.PENDING && state.status !== PromptStatus.ACTIVE) {
toDelete.push(pid);
}
}
for (const pid of toDelete) {
this.deletePrompt(pid);
}
this.emit(StoreEvents.PROMPTS_CLEARED, { count: toDelete.length });
return toDelete;
}
clearCancelledOrFailed() {
const toDelete = [];
for (const [pid, state] of this.prompts.entries()) {
if (state.status === PromptStatus.CANCELLED || state.status === PromptStatus.ERROR) {
toDelete.push(pid);
}
}
for (const pid of toDelete) {
this.deletePrompt(pid);
}
this.emit(StoreEvents.PROMPTS_CLEARED, { count: toDelete.length });
return toDelete;
}
setSearchQuery(query) {
this.ui.searchQuery = (query || "").trim();
this.emit(StoreEvents.SEARCH_CHANGED, { query: this.ui.searchQuery });
}
setActivePromptId(pid) {
this.ui.currentlyActivePromptId = pid ? String(pid) : null;
this.emit(StoreEvents.ACTIVE_PROMPT_CHANGED, { pid: this.ui.currentlyActivePromptId });
}
openBatchSubmenu(batchInfo) {
this.ui.activeSubmenuBatchImages = batchInfo;
this.ui.activeSubmenuPromptId = null;
this.emit(StoreEvents.SUBMENU_CHANGED, { type: "batch", data: batchInfo });
}
openOutputsSubmenu(pid) {
this.ui.activeSubmenuPromptId = String(pid);
this.ui.activeSubmenuBatchImages = null;
this.emit(StoreEvents.SUBMENU_CHANGED, { type: "outputs", pid: this.ui.activeSubmenuPromptId });
}
closeSubmenu() {
const hadSubmenu = this.ui.activeSubmenuPromptId !== null || this.ui.activeSubmenuBatchImages !== null;
this.ui.activeSubmenuPromptId = null;
this.ui.activeSubmenuBatchImages = null;
if (hadSubmenu) {
this.emit(StoreEvents.SUBMENU_CHANGED, { type: null });
}
}
}
export const store = new Store();