CoolFace
Modelpublic

AXERA-TECH/lite_webui

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes7downloads
store.js240 linesDownload Raw Back to src
1// LocalStorage keys2const KEYS = {3  SETTINGS: 'lw_settings',4  CONVERSATIONS: 'lw_conversations',5  CURRENT_CONV: 'lw_current_conv',6  MODEL_CAPS: 'lw_model_caps',7  AVAILABLE_MODELS: 'lw_available_models',8  MODEL_SELECTIONS: 'lw_model_selections',9};10 11const DEFAULT_SETTINGS = {12  apiKey: '',13  baseUrl: 'http://127.0.0.1:8000',14  theme: 'dark',15  contextLimitTokens: 4096,16  contextResetThresholdPercent: 85,17};18 19export function normalizeBaseUrl(baseUrl) {20  const raw = String(baseUrl || '').trim();21  if (!raw) return DEFAULT_SETTINGS.baseUrl;22  return raw.replace(/\/+$/, '');23}24 25function isCapabilityRecord(value) {26  if (!value || typeof value !== 'object' || Array.isArray(value)) return false;27  return ['text', 'image', 'audio'].some((key) => key in value);28}29 30function isLegacyCapabilityMap(value) {31  if (!value || typeof value !== 'object' || Array.isArray(value)) return false;32  const entries = Object.values(value);33  return entries.length > 0 && entries.every(isCapabilityRecord);34}35 36function normalizeSettings(settings = {}) {37  const merged = { ...DEFAULT_SETTINGS, ...settings };38  const contextLimitTokens = Number(merged.contextLimitTokens);39  const contextResetThresholdPercent = Number(merged.contextResetThresholdPercent);40 41  merged.baseUrl = normalizeBaseUrl(merged.baseUrl);42  merged.contextLimitTokens = Number.isFinite(contextLimitTokens) && contextLimitTokens >= 102443    ? Math.round(contextLimitTokens)44    : DEFAULT_SETTINGS.contextLimitTokens;45 46  merged.contextResetThresholdPercent = Number.isFinite(contextResetThresholdPercent)47    ? Math.min(95, Math.max(50, Math.round(contextResetThresholdPercent)))48    : DEFAULT_SETTINGS.contextResetThresholdPercent;49 50  return merged;51}52 53function load(key, fallback) {54  try {55    const raw = localStorage.getItem(key);56    return raw ? JSON.parse(raw) : fallback;57  } catch {58    return fallback;59  }60}61 62function save(key, value) {63  try {64    localStorage.setItem(key, JSON.stringify(value));65  } catch (e) {66    if (e?.name === 'QuotaExceededError' || e?.code === 22) {67      console.warn('[store] localStorage quota exceeded — conversation not persisted');68    } else {69      throw e;70    }71  }72}73 74function uuid() {75  return crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2) + Date.now().toString(36);76}77 78export const store = {79  getSettings() {80    return normalizeSettings(load(KEYS.SETTINGS, {}));81  },82 83  saveSettings(settings) {84    save(KEYS.SETTINGS, normalizeSettings(settings));85  },86 87  getConversations() {88    return load(KEYS.CONVERSATIONS, []);89  },90 91  saveConversations(conversations) {92    save(KEYS.CONVERSATIONS, conversations);93  },94 95  getCurrentConversationId() {96    return localStorage.getItem(KEYS.CURRENT_CONV) || null;97  },98 99  setCurrentConversationId(id) {100    if (id) {101      localStorage.setItem(KEYS.CURRENT_CONV, id);102    } else {103      localStorage.removeItem(KEYS.CURRENT_CONV);104    }105  },106 107  getCurrentConversation() {108    const id = this.getCurrentConversationId();109    if (!id) return null;110    const convs = this.getConversations();111    return convs.find(c => c.id === id) || null;112  },113 114  getAvailableModels(baseUrl = this.getSettings().baseUrl) {115    const catalogs = load(KEYS.AVAILABLE_MODELS, {});116    const list = catalogs[normalizeBaseUrl(baseUrl)];117    return Array.isArray(list) ? [...new Set(list.filter(Boolean))].sort() : [];118  },119 120  saveAvailableModels(baseUrl, models) {121    const catalogs = load(KEYS.AVAILABLE_MODELS, {});122    catalogs[normalizeBaseUrl(baseUrl)] = Array.isArray(models)123      ? [...new Set(models.filter(Boolean))].sort()124      : [];125    save(KEYS.AVAILABLE_MODELS, catalogs);126  },127 128  getCurrentModel(baseUrl = this.getSettings().baseUrl) {129    const selections = load(KEYS.MODEL_SELECTIONS, {});130    const selected = selections[normalizeBaseUrl(baseUrl)];131    return typeof selected === 'string' ? selected : '';132  },133 134  setCurrentModel(baseUrl, modelId) {135    const selections = load(KEYS.MODEL_SELECTIONS, {});136    const normalizedUrl = normalizeBaseUrl(baseUrl);137    if (modelId) {138      selections[normalizedUrl] = modelId;139    } else {140      delete selections[normalizedUrl];141    }142    save(KEYS.MODEL_SELECTIONS, selections);143  },144 145  getModelCapabilities(baseUrl = this.getSettings().baseUrl) {146    const raw = load(KEYS.MODEL_CAPS, {});147    if (isLegacyCapabilityMap(raw)) return raw;148 149    const caps = raw[normalizeBaseUrl(baseUrl)];150    return caps && typeof caps === 'object' && !Array.isArray(caps) ? caps : {};151  },152 153  saveModelCapabilities(baseUrlOrCaps, maybeCaps) {154    if (maybeCaps === undefined) {155      save(KEYS.MODEL_CAPS, baseUrlOrCaps);156      return;157    }158 159    const raw = load(KEYS.MODEL_CAPS, {});160    const nested = isLegacyCapabilityMap(raw) ? {} : raw;161    nested[normalizeBaseUrl(baseUrlOrCaps)] = maybeCaps;162    save(KEYS.MODEL_CAPS, nested);163  },164 165  createConversation(model) {166    const conv = {167      id: uuid(),168      title: 'New Chat',169      model: model || '',170      messages: [],171      createdAt: new Date().toISOString(),172      updatedAt: new Date().toISOString(),173    };174    const convs = this.getConversations();175    convs.unshift(conv);176    this.saveConversations(convs);177    return conv;178  },179 180  addMessage(convId, message) {181    const convs = this.getConversations();182    const idx = convs.findIndex(c => c.id === convId);183    if (idx === -1) return;184    convs[idx].messages.push(message);185    convs[idx].updatedAt = new Date().toISOString();186    this.saveConversations(convs);187  },188 189  updateLastAssistantMessage(convId, content) {190    const convs = this.getConversations();191    const idx = convs.findIndex(c => c.id === convId);192    if (idx === -1) return;193    const msgs = convs[idx].messages;194    // Find last assistant message195    for (let i = msgs.length - 1; i >= 0; i--) {196      if (msgs[i].role === 'assistant') {197        msgs[i].content = content;198        msgs[i].timestamp = new Date().toISOString();199        break;200      }201    }202    convs[idx].updatedAt = new Date().toISOString();203    this.saveConversations(convs);204  },205 206  clearMessages(convId) {207    const convs = this.getConversations();208    const idx = convs.findIndex(c => c.id === convId);209    if (idx === -1) return;210    convs[idx].messages = [];211    convs[idx].updatedAt = new Date().toISOString();212    this.saveConversations(convs);213  },214 215  deleteConversation(convId) {216    let convs = this.getConversations();217    convs = convs.filter(c => c.id !== convId);218    this.saveConversations(convs);219    if (this.getCurrentConversationId() === convId) {220      this.setCurrentConversationId(convs[0]?.id || null);221    }222  },223 224  updateConversationTitle(convId, title) {225    const convs = this.getConversations();226    const idx = convs.findIndex(c => c.id === convId);227    if (idx === -1) return;228    convs[idx].title = title;229    this.saveConversations(convs);230  },231 232  updateConversationModel(convId, model) {233    const convs = this.getConversations();234    const idx = convs.findIndex(c => c.id === convId);235    if (idx === -1) return;236    convs[idx].model = model;237    this.saveConversations(convs);238  },239};240