59778f0196
* webui: Move static build output from `tools/server/public` to `build/ui` directory * refactor: Move to `tools/ui` * refactor: rename CMake variables and preprocessor defines - Rename LLAMA_BUILD_WEBUI -> LLAMA_BUILD_UI (old kept as deprecated) - Rename LLAMA_USE_PREBUILT_WEBUI -> LLAMA_USE_PREBUILT_UI (old kept as deprecated) - Backward compat: old vars auto-forward to new ones with DEPRECATION warning - Rename internal vars: WEBUI_SOURCE -> UI_SOURCE, WEBUI_SOURCE_DIR -> UI_SOURCE_DIR, etc. - Rename HF bucket: LLAMA_WEBUI_HF_BUCKET -> LLAMA_UI_HF_BUCKET - Emit both LLAMA_BUILD_WEBUI and LLAMA_BUILD_UI preprocessor defines - Emit both LLAMA_WEBUI_DEFAULT_ENABLED and LLAMA_UI_DEFAULT_ENABLED * refactor: rename CLI flags (--webui -> --ui) with backward compat - Add --ui/--no-ui (old --webui/--no-webui kept as deprecated aliases) - Add --ui-config (old --webui-config kept as deprecated alias) - Add --ui-config-file (old --webui-config-file kept as deprecated alias) - Add --ui-mcp-proxy/--no-ui-mcp-proxy (old --webui-mcp-proxy kept as deprecated) - Add new env vars: LLAMA_ARG_UI, LLAMA_ARG_UI_CONFIG, LLAMA_ARG_UI_CONFIG_FILE, LLAMA_ARG_UI_MCP_PROXY - C++ struct fields: params.ui, params.ui_config_json, params.ui_mcp_proxy added alongside old fields - Backward compat: old fields synced to new ones in g_params_to_internals * refactor: update C++ server internals with backward compat - Rename json_webui_settings -> json_ui_settings (both kept in server_context_meta) - Rename params.webui usage -> params.ui (both synced, old still works) - JSON API emits both "ui"/"ui_settings" and "webui"/"webui_settings" keys - Server routes use params.ui_mcp_proxy || params.webui_mcp_proxy - Preprocessor guards use #if defined(LLAMA_BUILD_UI) || defined(LLAMA_BUILD_WEBUI) * refactor: rename CI/CD workflows, artifacts, and build script - Rename webui-build.yml -> ui-build.yml; artifact webui-build -> ui-build - Rename webui-publish.yml -> ui-publish.yml; var HF_BUCKET_WEBUI_STATIC_OUTPUT -> HF_BUCKET_UI_STATIC_OUTPUT - Rename server-webui.yml -> server-ui.yml; job webui-build/checks -> ui-build/checks - Update server.yml: job/artifact refs webui-build -> ui-build - Update release.yml: all webui-build/publish refs -> ui-build/publish; HF_TOKEN_WEBUI_STATIC_OUTPUT -> HF_TOKEN_UI_STATIC_OUTPUT - Update server-self-hosted.yml: webui-build -> ui-build - Update build-self-hosted.yml: HF_WEBUI_VERSION -> HF_UI_VERSION - Rename webui-download.cmake -> ui-download.cmake (internal refs updated) - Update labeler.yml: server/webui -> server/ui path label * docs: update CODEOWNERS and server README docs - Update CODEOWNERS: team ggml-org/llama-webui -> ggml-org/llama-ui, path /tools/server/webui/ -> /tools/ui/ - Update server README.md: CLI tables show --ui flags with deprecated --webui aliases - Update server README-dev.md: "WebUI" -> "UI", paths updated to tools/ui/ * fix: Small fixes for UI build * fix: CMake.txt syntax * chore: Formatting * fix: `.editorconfig` for llama-ui * chore: Formatting * refactor: Use `APP_NAME` in Error route * refactor: Cleanup * refactor: Single migration service * make llama-ui a linkable target * fix: UI Build output * fix: Missing change * fix: separate llama-ui npm build output into build/tools/ui/dist subfolder + use cmake npm build instead of downloading ui-build.yml artifacts in CI * refactor: UI workflows cleanup --------- Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
293 lines
5.9 KiB
TypeScript
293 lines
5.9 KiB
TypeScript
import { DEFAULT_CACHE_TTL_MS, DEFAULT_CACHE_MAX_ENTRIES } from '$lib/constants';
|
|
|
|
/**
|
|
* TTL Cache - Time-To-Live cache implementation for memory optimization
|
|
*
|
|
* Provides automatic expiration of cached entries to prevent memory bloat
|
|
* in long-running sessions.
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const cache = new TTLCache<string, ApiData>({ ttlMs: 5 * 60 * 1000 }); // 5 minutes
|
|
* cache.set('key', data);
|
|
* const value = cache.get('key'); // null if expired
|
|
* ```
|
|
*/
|
|
|
|
export interface TTLCacheOptions {
|
|
/** Time-to-live in milliseconds. Default: 5 minutes */
|
|
ttlMs?: number;
|
|
/** Maximum number of entries. Oldest entries are evicted when exceeded. Default: 100 */
|
|
maxEntries?: number;
|
|
/** Callback when an entry expires or is evicted */
|
|
onEvict?: (key: string, value: unknown) => void;
|
|
}
|
|
|
|
interface CacheEntry<T> {
|
|
value: T;
|
|
expiresAt: number;
|
|
lastAccessed: number;
|
|
}
|
|
|
|
export class TTLCache<K extends string, V> {
|
|
private cache = new Map<K, CacheEntry<V>>();
|
|
private readonly ttlMs: number;
|
|
private readonly maxEntries: number;
|
|
private readonly onEvict?: (key: string, value: unknown) => void;
|
|
|
|
constructor(options: TTLCacheOptions = {}) {
|
|
this.ttlMs = options.ttlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
this.maxEntries = options.maxEntries ?? DEFAULT_CACHE_MAX_ENTRIES;
|
|
this.onEvict = options.onEvict;
|
|
}
|
|
|
|
/**
|
|
* Get a value from cache. Returns null if expired or not found.
|
|
*/
|
|
get(key: K): V | null {
|
|
const entry = this.cache.get(key);
|
|
if (!entry) return null;
|
|
|
|
if (Date.now() > entry.expiresAt) {
|
|
this.delete(key);
|
|
return null;
|
|
}
|
|
|
|
// Update last accessed time for LRU-like behavior
|
|
entry.lastAccessed = Date.now();
|
|
return entry.value;
|
|
}
|
|
|
|
/**
|
|
* Set a value in cache with TTL.
|
|
*/
|
|
set(key: K, value: V, customTtlMs?: number): void {
|
|
// Evict oldest entries if at capacity
|
|
if (this.cache.size >= this.maxEntries && !this.cache.has(key)) {
|
|
this.evictOldest();
|
|
}
|
|
|
|
const ttl = customTtlMs ?? this.ttlMs;
|
|
const now = Date.now();
|
|
|
|
this.cache.set(key, {
|
|
value,
|
|
expiresAt: now + ttl,
|
|
lastAccessed: now
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check if key exists and is not expired.
|
|
*/
|
|
has(key: K): boolean {
|
|
const entry = this.cache.get(key);
|
|
if (!entry) return false;
|
|
|
|
if (Date.now() > entry.expiresAt) {
|
|
this.delete(key);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Delete a specific key from cache.
|
|
*/
|
|
delete(key: K): boolean {
|
|
const entry = this.cache.get(key);
|
|
if (entry && this.onEvict) {
|
|
this.onEvict(key, entry.value);
|
|
}
|
|
return this.cache.delete(key);
|
|
}
|
|
|
|
/**
|
|
* Clear all entries from cache.
|
|
*/
|
|
clear(): void {
|
|
if (this.onEvict) {
|
|
for (const [key, entry] of this.cache) {
|
|
this.onEvict(key, entry.value);
|
|
}
|
|
}
|
|
this.cache.clear();
|
|
}
|
|
|
|
/**
|
|
* Get the number of entries (including potentially expired ones).
|
|
*/
|
|
get size(): number {
|
|
return this.cache.size;
|
|
}
|
|
|
|
/**
|
|
* Remove all expired entries from cache.
|
|
* Call periodically for proactive cleanup.
|
|
*/
|
|
prune(): number {
|
|
const now = Date.now();
|
|
let pruned = 0;
|
|
|
|
for (const [key, entry] of this.cache) {
|
|
if (now > entry.expiresAt) {
|
|
this.delete(key);
|
|
pruned++;
|
|
}
|
|
}
|
|
|
|
return pruned;
|
|
}
|
|
|
|
/**
|
|
* Get all valid (non-expired) keys.
|
|
*/
|
|
keys(): K[] {
|
|
const now = Date.now();
|
|
const validKeys: K[] = [];
|
|
|
|
for (const [key, entry] of this.cache) {
|
|
if (now <= entry.expiresAt) {
|
|
validKeys.push(key);
|
|
}
|
|
}
|
|
|
|
return validKeys;
|
|
}
|
|
|
|
/**
|
|
* Evict the oldest (least recently accessed) entry.
|
|
*/
|
|
private evictOldest(): void {
|
|
let oldestKey: K | null = null;
|
|
let oldestTime = Infinity;
|
|
|
|
for (const [key, entry] of this.cache) {
|
|
if (entry.lastAccessed < oldestTime) {
|
|
oldestTime = entry.lastAccessed;
|
|
oldestKey = key;
|
|
}
|
|
}
|
|
|
|
if (oldestKey !== null) {
|
|
this.delete(oldestKey);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refresh TTL for an existing entry without changing the value.
|
|
*/
|
|
touch(key: K): boolean {
|
|
const entry = this.cache.get(key);
|
|
if (!entry) return false;
|
|
|
|
const now = Date.now();
|
|
if (now > entry.expiresAt) {
|
|
this.delete(key);
|
|
return false;
|
|
}
|
|
|
|
entry.expiresAt = now + this.ttlMs;
|
|
entry.lastAccessed = now;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reactive TTL Map for Svelte stores
|
|
* Wraps SvelteMap with TTL functionality
|
|
*/
|
|
export class ReactiveTTLMap<K extends string, V> {
|
|
private entries = $state<Map<K, CacheEntry<V>>>(new Map());
|
|
private readonly ttlMs: number;
|
|
private readonly maxEntries: number;
|
|
|
|
constructor(options: TTLCacheOptions = {}) {
|
|
this.ttlMs = options.ttlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
this.maxEntries = options.maxEntries ?? DEFAULT_CACHE_MAX_ENTRIES;
|
|
}
|
|
|
|
get(key: K): V | null {
|
|
const entry = this.entries.get(key);
|
|
if (!entry) return null;
|
|
|
|
if (Date.now() > entry.expiresAt) {
|
|
this.entries.delete(key);
|
|
return null;
|
|
}
|
|
|
|
entry.lastAccessed = Date.now();
|
|
return entry.value;
|
|
}
|
|
|
|
set(key: K, value: V, customTtlMs?: number): void {
|
|
if (this.entries.size >= this.maxEntries && !this.entries.has(key)) {
|
|
this.evictOldest();
|
|
}
|
|
|
|
const ttl = customTtlMs ?? this.ttlMs;
|
|
const now = Date.now();
|
|
|
|
this.entries.set(key, {
|
|
value,
|
|
expiresAt: now + ttl,
|
|
lastAccessed: now
|
|
});
|
|
}
|
|
|
|
has(key: K): boolean {
|
|
const entry = this.entries.get(key);
|
|
if (!entry) return false;
|
|
|
|
if (Date.now() > entry.expiresAt) {
|
|
this.entries.delete(key);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
delete(key: K): boolean {
|
|
return this.entries.delete(key);
|
|
}
|
|
|
|
clear(): void {
|
|
this.entries.clear();
|
|
}
|
|
|
|
get size(): number {
|
|
return this.entries.size;
|
|
}
|
|
|
|
prune(): number {
|
|
const now = Date.now();
|
|
let pruned = 0;
|
|
|
|
for (const [key, entry] of this.entries) {
|
|
if (now > entry.expiresAt) {
|
|
this.entries.delete(key);
|
|
pruned++;
|
|
}
|
|
}
|
|
|
|
return pruned;
|
|
}
|
|
|
|
private evictOldest(): void {
|
|
let oldestKey: K | null = null;
|
|
let oldestTime = Infinity;
|
|
|
|
for (const [key, entry] of this.entries) {
|
|
if (entry.lastAccessed < oldestTime) {
|
|
oldestTime = entry.lastAccessed;
|
|
oldestKey = key;
|
|
}
|
|
}
|
|
|
|
if (oldestKey !== null) {
|
|
this.entries.delete(oldestKey);
|
|
}
|
|
}
|
|
}
|