Files
TanTan fa9c73363d
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
SnowLuma v2.4.2 source snapshot
2026-11-13 09:02:00 +08:00

101 lines
3.4 KiB
TypeScript

/** Browser-safe contract shared by startup, the WebUI and embedding hosts.
* Targets are deployment choices; they never belong in the saved UI preferences. */
export const WEBUI_TARGETS = {
maibot: {
label: '麦麦 / 一键包',
styles: ['millennium'],
defaultStyle: 'millennium',
},
} as const;
export type WebuiTarget = keyof typeof WEBUI_TARGETS;
export type WebuiColorMode = 'light' | 'dark' | 'system';
export type WebuiPresentation = {
[T in WebuiTarget]: {
version: 1;
target: T;
style: (typeof WEBUI_TARGETS)[T]['styles'][number];
mode: WebuiColorMode;
};
}[WebuiTarget];
/** null releases the override for this document, without changing deployment opt-in. */
export type WebuiPresentationUpdate = WebuiPresentation | { version: 1; target: null };
export const WEBUI_PRESENTATION_EVENT = 'snowluma:host-appearance';
export const WEBUI_TARGET_BOOTSTRAP_ID = 'snowluma-webui-target';
export function resolveWebuiTarget(raw: string | undefined): WebuiTarget | null {
if (raw === undefined || raw.trim() === '') {
return null;
}
const value = raw.trim();
if (!Object.hasOwn(WEBUI_TARGETS, value)) {
throw new Error(
`Invalid SNOWLUMA_WEBUI_TARGET: ${JSON.stringify(value)}. Expected ${Object.keys(WEBUI_TARGETS).join(', ')} or an empty value.`,
);
}
return value as WebuiTarget;
}
export function defaultWebuiPresentation(target: WebuiTarget): WebuiPresentation {
return { version: 1, target, style: WEBUI_TARGETS[target].defaultStyle, mode: 'system' };
}
export function parseWebuiPresentation(value: unknown): WebuiPresentationUpdate {
if (
!value ||
typeof value !== 'object' ||
!('version' in value) ||
value.version !== 1 ||
!('target' in value)
) {
throw new TypeError('Invalid host appearance: expected a version 1 presentation');
}
if (value.target === null) {
return { version: 1, target: null };
}
if (typeof value.target !== 'string' || !Object.hasOwn(WEBUI_TARGETS, value.target)) {
throw new TypeError('Invalid host appearance target');
}
const target = value.target as WebuiTarget;
const styles: readonly string[] = WEBUI_TARGETS[target].styles;
if (!('style' in value) || typeof value.style !== 'string' || !styles.includes(value.style)) {
throw new TypeError(`Invalid host appearance style for ${target}`);
}
if (
!('mode' in value) ||
(value.mode !== 'light' && value.mode !== 'dark' && value.mode !== 'system')
) {
throw new TypeError('Invalid host appearance color mode');
}
// Both discriminants have been checked against the same target registry.
return { version: 1, target, style: value.style, mode: value.mode } as WebuiPresentation;
}
/** Inject before the cached pre-paint theme script. Default HTML stays byte-for-byte intact. */
export function injectWebuiTarget(html: string, target: WebuiTarget | null): string {
if (target === null) {
return html;
}
if (!html.includes('<head>')) {
throw new Error('WebUI HTML is missing <head>; cannot inject runtime target');
}
const json = JSON.stringify(defaultWebuiPresentation(target)).replaceAll('<', '\\u003c');
return html.replace(
'<head>',
`<head>\n <script id="${WEBUI_TARGET_BOOTSTRAP_ID}" type="application/json">${json}</script>`,
);
}