diff --git a/examples/server/webui/package-lock.json b/examples/server/webui/package-lock.json index bbebccbf2..e4545006f 100644 --- a/examples/server/webui/package-lock.json +++ b/examples/server/webui/package-lock.json @@ -15,6 +15,7 @@ "highlight.js": "^11.10.0", "katex": "^0.16.15", "markdown-it": "^14.1.0", + "pako": "^2.1.0", "postcss": "^8.4.49", "tailwindcss": "^3.4.15", "textlinestream": "^1.1.1", @@ -1732,6 +1733,12 @@ "node": ">= 6" } }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", diff --git a/examples/server/webui/package.json b/examples/server/webui/package.json index 2836cce00..8810d013e 100644 --- a/examples/server/webui/package.json +++ b/examples/server/webui/package.json @@ -21,6 +21,7 @@ "highlight.js": "^11.10.0", "katex": "^0.16.15", "markdown-it": "^14.1.0", + "pako": "^2.1.0", "postcss": "^8.4.49", "tailwindcss": "^3.4.15", "textlinestream": "^1.1.1", diff --git a/examples/server/webui/src/main.js b/examples/server/webui/src/main.js index 358a40628..e7e6b8bb4 100644 --- a/examples/server/webui/src/main.js +++ b/examples/server/webui/src/main.js @@ -15,7 +15,11 @@ import daisyuiThemes from 'daisyui/src/theming/themes'; // ponyfill for missing ReadableStream asyncIterator on Safari import { asyncIterator } from '@sec-ant/readable-stream/ponyfill/asyncIterator'; -const isDev = import.meta.env.MODE === 'development'; +// compression library +import pako from "pako"; + +const isDev = import.meta.env.MODE === "development"; +const useCompression = false; // set to true if you want to use gzip compression for local storage // utility functions const isString = (x) => !!x.toLowerCase; @@ -38,6 +42,45 @@ const copyStr = (textToCopy) => { document.execCommand('copy'); } }; +const compressedStorage = { + getItem(key) { + const compressed = window.localStorage.getItem(key); + try { + const bytes = Uint8Array.from(atob(compressed), (c) => c.charCodeAt(0)); + return new TextDecoder().decode(this.decompress(bytes)); + } catch (error) { + return compressed; + } + }, + setItem(key, value) { + const compressed = this.compress(new TextEncoder().encode(value)); + const compressedHex = btoa(this.uintToString(compressed)); + window.localStorage.setItem(key, compressedHex); + }, + removeItem: (key) => window.localStorage.removeItem(key), + clear: () => window.localStorage.clear(), + key: (index) => window.localStorage.key(index), + length: window.localStorage.length, + [Symbol.iterator]: function* () { + for (let i = 0; i < window.localStorage.length; i++) { + yield window.localStorage.key(i); + } + }, + uintToString(bytes) { + const chunkSize = 0x8000; + let result = ""; + for (let i = 0; i < bytes.length; i += chunkSize) { + result += String.fromCharCode.apply( + null, + bytes.subarray(i, i + chunkSize) + ); + } + return result; + }, + compress: (encoded) => pako.gzip(encoded, { level: 9 }), + decompress: (bytes) => pako.ungzip(bytes), +}; +const localStorage = useCompression ? compressedStorage : window.localStorage; // constants const BASE_URL = isDev @@ -211,7 +254,8 @@ const StorageUtils = { // manage conversations getAllConversations() { const res = []; - for (const key in localStorage) { + const storage = useCompression ? localStorage : Object.keys(localStorage); + for (const key of storage) { if (key.startsWith('conv-')) { res.push(JSON.parse(localStorage.getItem(key))); }