/** 三页 Cached history 共用的 `?content=` 参数名 */ export const DEFAULT_CONTENT_URL_PARAM = 'content'; /** LLM Causal Flow(gen_attribute)打包 demo:`?demo=` 为文件名(不含 .json) */ export const DEFAULT_DEMO_URL_PARAM = 'demo'; export function readContentUrlParam(paramName: string = DEFAULT_CONTENT_URL_PARAM): string | null { try { const u = new URL(window.location.href); const v = u.searchParams.get(paramName); return v != null && v.length > 0 ? v : null; } catch { return null; } } export function replaceContentUrlParam( value: string | null, paramName: string = DEFAULT_CONTENT_URL_PARAM, logLabel?: string ): void { replaceUrlParam(value, paramName, logLabel ?? 'contentUrl'); } export function readDemoUrlParam(paramName: string = DEFAULT_DEMO_URL_PARAM): string | null { try { const u = new URL(window.location.href); const v = u.searchParams.get(paramName); return v != null && v.length > 0 ? v : null; } catch { return null; } } function replaceUrlParam(value: string | null, paramName: string, logLabel: string): void { try { const u = new URL(window.location.href); if (value) { u.searchParams.set(paramName, value); } else { u.searchParams.delete(paramName); } window.history.replaceState(null, '', u.toString()); } catch (e: unknown) { console.warn(`[${logLabel}] URL sync failed:`, e); } } export function replaceDemoUrlParam( value: string | null, paramName: string = DEFAULT_DEMO_URL_PARAM, logLabel?: string ): void { replaceUrlParam(value, paramName, logLabel ?? 'contentUrl:demo'); } export type RunContentUrlHydrateOptions = { readRaw: () => string | null; fetchEntry: (raw: string) => Promise; /** 缺省:仅判断 entry 非 undefined/null */ isValid?: (entry: T) => boolean; /** 第二参为 URL 中的原始 `content` 值(IndexedDB 条目的短哈希键) */ apply: (entry: T, rawContentKey: string) => void | Promise; onMissing: () => void | Promise; onApplyError?: (error: unknown) => void | Promise; }; export async function runContentUrlHydrate(options: RunContentUrlHydrateOptions): Promise { const raw = options.readRaw(); if (!raw) return; const entry = await options.fetchEntry(raw); const ok = entry != null && (options.isValid ? options.isValid(entry) : true); if (!ok) { await Promise.resolve(options.onMissing()); return; } try { await options.apply(entry, raw); } catch (e: unknown) { if (options.onApplyError) { await Promise.resolve(options.onApplyError(e)); } else { throw e; } } }