Merge branch 'master' of http://82.157.175.59:3000/GenarrativeAI/Genarrative
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-27 14:23:33 +08:00
50 changed files with 1908 additions and 270 deletions

View File

@@ -0,0 +1,29 @@
import type { BigFishWorksResponse } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
import { type ApiRetryOptions, requestJson } from '../apiClient';
const BIG_FISH_GALLERY_API_BASE = '/api/runtime/big-fish/gallery';
const BIG_FISH_GALLERY_READ_RETRY: ApiRetryOptions = {
maxRetries: 1,
baseDelayMs: 120,
maxDelayMs: 360,
};
/**
* 读取大鱼吃小鱼公开广场列表。
*/
export async function listBigFishGallery() {
return requestJson<BigFishWorksResponse>(
BIG_FISH_GALLERY_API_BASE,
{
method: 'GET',
},
'读取大鱼吃小鱼广场失败',
{
retry: BIG_FISH_GALLERY_READ_RETRY,
},
);
}
export const bigFishGalleryClient = {
list: listBigFishGallery,
};

View File

@@ -0,0 +1,4 @@
export {
bigFishGalleryClient,
listBigFishGallery,
} from './bigFishGalleryClient';

View File

@@ -3,6 +3,7 @@ import { expect, test } from 'vitest';
import {
clearCustomWorldAgentUiState,
readCustomWorldAgentUiState,
shouldRestoreCustomWorldAgentUiState,
writeCustomWorldAgentUiState,
} from './customWorldAgentUiState';
@@ -73,3 +74,49 @@ test('custom world agent ui state reads from query first and persists to session
clearCustomWorldAgentUiState(env);
expect(readCustomWorldAgentUiState(env)).toEqual({});
});
test('custom world agent ui state only auto restores stored pointers on RPG creation paths', () => {
const sessionStorage = createMemoryStorage();
sessionStorage.setItem(
'genarrative.custom-world-agent-ui.v1',
JSON.stringify({
activeSessionId: 'session-1',
ownerUserId: 'user-1',
}),
);
expect(
shouldRestoreCustomWorldAgentUiState({
location: {
pathname: '/',
search: '',
},
history: null,
sessionStorage,
}),
).toBe(false);
expect(
shouldRestoreCustomWorldAgentUiState({
location: {
pathname: '/creation/rpg/agent',
search: '',
},
history: null,
sessionStorage,
}),
).toBe(true);
});
test('custom world agent ui state restores explicit query pointers on any main path', () => {
expect(
shouldRestoreCustomWorldAgentUiState({
location: {
pathname: '/',
search: '?customWorldSessionId=session-1',
},
history: null,
sessionStorage: createMemoryStorage(),
}),
).toBe(true);
});

View File

@@ -56,6 +56,49 @@ 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 {

View File

@@ -13,6 +13,14 @@ export function buildPuzzlePublicWorkCode(profileId: string) {
return `PZ-${suffix}`;
}
export function buildBigFishPublicWorkCode(sessionId: string) {
const normalized = normalizePublicCodeText(sessionId);
const fallback = normalized || '00000000';
const suffix = fallback.slice(-8).padStart(8, '0');
return `BF-${suffix}`;
}
export function isSamePuzzlePublicWorkCode(keyword: string, profileId: string) {
const normalizedKeyword = normalizePublicCodeText(keyword);
@@ -22,3 +30,16 @@ export function isSamePuzzlePublicWorkCode(keyword: string, profileId: string) {
normalizedKeyword === normalizePublicCodeText(profileId)
);
}
export function isSameBigFishPublicWorkCode(
keyword: string,
sessionId: string,
) {
const normalizedKeyword = normalizePublicCodeText(keyword);
return (
normalizedKeyword ===
normalizePublicCodeText(buildBigFishPublicWorkCode(sessionId)) ||
normalizedKeyword === normalizePublicCodeText(sessionId)
);
}