Files
Genarrative/src/services/customWorldAgentUiState.ts

215 lines
6.2 KiB
TypeScript

import type { CustomWorldAgentUiState } from '../types';
export const CUSTOM_WORLD_AGENT_SESSION_QUERY_KEY = 'customWorldSessionId';
export const CUSTOM_WORLD_AGENT_OPERATION_QUERY_KEY = 'customWorldOperationId';
export const CUSTOM_WORLD_GENERATION_SOURCE_QUERY_KEY =
'customWorldGenerationSource';
export const CUSTOM_WORLD_AGENT_UI_STATE_STORAGE_KEY =
'genarrative.custom-world-agent-ui.v1';
type CustomWorldAgentUiEnvironment = {
location?: {
pathname: string;
search: string;
} | null;
history?: {
replaceState: (
data: unknown,
unused: string,
url?: string | URL | null,
) => void;
} | null;
sessionStorage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | null;
};
function resolveEnvironment(
env?: CustomWorldAgentUiEnvironment,
): Required<CustomWorldAgentUiEnvironment> {
if (env) {
return {
location: env.location ?? null,
history: env.history ?? null,
sessionStorage: env.sessionStorage ?? null,
};
}
if (typeof window === 'undefined') {
return {
location: null,
history: null,
sessionStorage: null,
};
}
return {
location: window.location,
history: window.history,
sessionStorage: window.sessionStorage,
};
}
function normalizeValue(value: unknown) {
return typeof value === 'string' && value.trim() ? value.trim() : null;
}
function normalizeGenerationSource(value: unknown) {
return value === 'agent-draft-foundation' ? value : null;
}
function hasExplicitAgentUiStateQuery(
params: URLSearchParams,
) {
return (
params.has(CUSTOM_WORLD_AGENT_SESSION_QUERY_KEY) ||
params.has(CUSTOM_WORLD_AGENT_OPERATION_QUERY_KEY) ||
params.has(CUSTOM_WORLD_GENERATION_SOURCE_QUERY_KEY)
);
}
function normalizePathname(value: string | undefined) {
const pathname = value?.trim().toLowerCase() ?? '';
if (!pathname || pathname === '/') {
return '/';
}
return pathname.replace(/\/+$/u, '');
}
function isRpgCreationRestorePath(pathname: string | undefined) {
const normalizedPathname = normalizePathname(pathname);
return (
normalizedPathname === '/creation/rpg' ||
normalizedPathname.startsWith('/creation/rpg/')
);
}
export function shouldRestoreCustomWorldAgentUiState(
env?: CustomWorldAgentUiEnvironment,
) {
const resolved = resolveEnvironment(env);
const params = new URLSearchParams(resolved.location?.search ?? '');
// URL 显式恢复参数优先于当前路径,用于支持外部分享或登录回跳后的深链恢复。
if (hasExplicitAgentUiStateQuery(params)) {
return true;
}
// sessionStorage 里的残留指针只能在 RPG 创作页面生效,
// 避免刷新平台首页时被旧工作区状态强制带到 Agent 页面。
return isRpgCreationRestorePath(resolved.location?.pathname);
}
export function readCustomWorldAgentUiState(
env?: CustomWorldAgentUiEnvironment,
): CustomWorldAgentUiState {
const resolved = resolveEnvironment(env);
const params = new URLSearchParams(resolved.location?.search ?? '');
const stateFromQuery: CustomWorldAgentUiState = {
activeSessionId: normalizeValue(
params.get(CUSTOM_WORLD_AGENT_SESSION_QUERY_KEY),
),
activeOperationId: normalizeValue(
params.get(CUSTOM_WORLD_AGENT_OPERATION_QUERY_KEY),
),
customWorldGenerationSource: normalizeGenerationSource(
params.get(CUSTOM_WORLD_GENERATION_SOURCE_QUERY_KEY),
),
};
if (
stateFromQuery.activeSessionId ||
stateFromQuery.activeOperationId ||
stateFromQuery.customWorldGenerationSource
) {
return stateFromQuery;
}
const storedValue = resolved.sessionStorage?.getItem(
CUSTOM_WORLD_AGENT_UI_STATE_STORAGE_KEY,
);
if (!storedValue) {
return {};
}
try {
const parsed = JSON.parse(storedValue) as CustomWorldAgentUiState;
return {
activeSessionId: normalizeValue(parsed.activeSessionId),
activeOperationId: normalizeValue(parsed.activeOperationId),
customWorldGenerationSource: normalizeGenerationSource(
parsed.customWorldGenerationSource,
),
ownerUserId: normalizeValue(parsed.ownerUserId),
};
} catch {
resolved.sessionStorage?.removeItem(CUSTOM_WORLD_AGENT_UI_STATE_STORAGE_KEY);
return {};
}
}
export function writeCustomWorldAgentUiState(
state: CustomWorldAgentUiState,
env?: CustomWorldAgentUiEnvironment,
) {
const resolved = resolveEnvironment(env);
const activeSessionId = normalizeValue(state.activeSessionId);
const activeOperationId = normalizeValue(state.activeOperationId);
const customWorldGenerationSource = normalizeGenerationSource(
state.customWorldGenerationSource,
);
const ownerUserId = normalizeValue(state.ownerUserId);
const nextState: CustomWorldAgentUiState = {
activeSessionId,
activeOperationId,
customWorldGenerationSource,
ownerUserId,
};
if (resolved.location && resolved.history?.replaceState) {
const params = new URLSearchParams(resolved.location.search);
if (activeSessionId) {
params.set(CUSTOM_WORLD_AGENT_SESSION_QUERY_KEY, activeSessionId);
} else {
params.delete(CUSTOM_WORLD_AGENT_SESSION_QUERY_KEY);
}
if (activeOperationId) {
params.set(CUSTOM_WORLD_AGENT_OPERATION_QUERY_KEY, activeOperationId);
} else {
params.delete(CUSTOM_WORLD_AGENT_OPERATION_QUERY_KEY);
}
if (customWorldGenerationSource) {
params.set(
CUSTOM_WORLD_GENERATION_SOURCE_QUERY_KEY,
customWorldGenerationSource,
);
} else {
params.delete(CUSTOM_WORLD_GENERATION_SOURCE_QUERY_KEY);
}
const search = params.toString();
const nextUrl = search
? `${resolved.location.pathname}?${search}`
: resolved.location.pathname;
resolved.history.replaceState(null, '', nextUrl);
}
if (resolved.sessionStorage) {
if (activeSessionId || activeOperationId || customWorldGenerationSource) {
resolved.sessionStorage.setItem(
CUSTOM_WORLD_AGENT_UI_STATE_STORAGE_KEY,
JSON.stringify(nextState),
);
} else {
resolved.sessionStorage.removeItem(CUSTOM_WORLD_AGENT_UI_STATE_STORAGE_KEY);
}
}
}
export function clearCustomWorldAgentUiState(
env?: CustomWorldAgentUiEnvironment,
) {
writeCustomWorldAgentUiState({}, env);
}