退役旧创作模板业务
保留 SpacetimeDB 历史表、迁移白名单与旧业务源码 切换前端 active 入口并解除旧创作页面和路由编译链 移除旧后端路由、worker 与纯业务 crate 依赖 收敛 SpacetimeDB 模块为历史数据壳 同步 Nginx、Pingora、验证门禁与架构文档
This commit is contained in:
+174
-8
@@ -71,12 +71,6 @@ module.exports = {
|
|||||||
'simple-import-sort/exports': 'off',
|
'simple-import-sort/exports': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
files: ['src/components/match3d-runtime/Match3DPhysicsBoard.tsx'],
|
|
||||||
rules: {
|
|
||||||
'react-refresh/only-export-components': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
'@typescript-eslint',
|
'@typescript-eslint',
|
||||||
@@ -96,22 +90,191 @@ module.exports = {
|
|||||||
'dist_check',
|
'dist_check',
|
||||||
'dist_check_monster_position',
|
'dist_check_monster_position',
|
||||||
'coverage',
|
'coverage',
|
||||||
|
'build',
|
||||||
|
'output',
|
||||||
|
'retired/**',
|
||||||
'node_modules',
|
'node_modules',
|
||||||
'server-rs/target',
|
'server-rs/target',
|
||||||
'server-rs/target-*',
|
'server-rs/target-*',
|
||||||
'apps/desktop-shell/src-tauri/target',
|
'apps/desktop-shell/src-tauri/target',
|
||||||
'target',
|
'target',
|
||||||
|
'src/main.tsx',
|
||||||
|
'src/App.tsx',
|
||||||
|
'src/routing/appPageRoutes.ts',
|
||||||
|
'src/routing/appPageRoutes.test.ts',
|
||||||
|
'src/routing/appRoutes.tsx',
|
||||||
|
'src/routing/appRoutes.test.ts',
|
||||||
|
'src/services/appTitle.ts',
|
||||||
|
'src/services/appTitle.test.ts',
|
||||||
'src/components/CharacterAnimator.tsx',
|
'src/components/CharacterAnimator.tsx',
|
||||||
|
'src/AdventurePanel.tsx',
|
||||||
|
'src/AdventureEntityModal.tsx',
|
||||||
|
'src/App.tsx',
|
||||||
|
'src/App.test.tsx',
|
||||||
|
'src/CharacterCreation.tsx',
|
||||||
|
'src/RpgRuntimeApp.tsx',
|
||||||
|
'src/routing/appPageRoutes.ts',
|
||||||
|
'src/routing/appPageRoutes.test.ts',
|
||||||
|
'src/routing/appRoutes.tsx',
|
||||||
|
'src/routing/appRoutes.test.ts',
|
||||||
|
'src/*PlaygroundApp.tsx',
|
||||||
|
'src/ChildMotionDemoApp.tsx',
|
||||||
|
'src/Match3DPlaygroundApp.tsx',
|
||||||
|
'src/components/child-motion-demo/**',
|
||||||
|
'src/components/asset-studio/**',
|
||||||
|
'src/components/bark-battle-creation/**',
|
||||||
|
'src/components/big-fish-creation/**',
|
||||||
|
'src/components/big-fish-result/**',
|
||||||
|
'src/components/big-fish-runtime/**',
|
||||||
|
'src/components/creation-agent/**',
|
||||||
|
'src/components/creation-home/**',
|
||||||
|
'src/components/creative-agent/**',
|
||||||
|
'src/components/custom-world-agent/**',
|
||||||
|
'src/components/custom-world-home/**',
|
||||||
|
'src/components/edutainment-creation/**',
|
||||||
|
'src/components/edutainment-result/**',
|
||||||
|
'src/components/edutainment-runtime/**',
|
||||||
|
'src/components/game-canvas/**',
|
||||||
|
'src/components/jump-hop-result/**',
|
||||||
'src/components/jump-hop-runtime/**',
|
'src/components/jump-hop-runtime/**',
|
||||||
|
'src/components/match3d-result/**',
|
||||||
'src/components/match3d-runtime/**',
|
'src/components/match3d-runtime/**',
|
||||||
|
'src/components/puzzle-clear-creation/**',
|
||||||
|
'src/components/puzzle-clear-result/**',
|
||||||
|
'src/components/puzzle-clear-runtime/**',
|
||||||
|
'src/components/puzzle-gallery/**',
|
||||||
|
'src/components/puzzle-result/**',
|
||||||
|
'src/components/puzzle-runtime/**',
|
||||||
|
'src/components/rpg-creation-asset-studio/**',
|
||||||
|
'src/components/rpg-creation-result/**',
|
||||||
|
'src/components/rpg-runtime-panels/**',
|
||||||
|
'src/components/square-hole-creation/**',
|
||||||
|
'src/components/square-hole-result/**',
|
||||||
|
'src/components/square-hole-runtime/**',
|
||||||
|
'src/components/unified-creation/**',
|
||||||
|
'src/components/visual-novel-creation/**',
|
||||||
|
'src/components/visual-novel-result/**',
|
||||||
|
'src/components/visual-novel-runtime/**',
|
||||||
|
'src/components/wooden-fish-result/**',
|
||||||
|
'src/components/wooden-fish-runtime/**',
|
||||||
|
'src/components/common/PublishShare*',
|
||||||
|
'src/components/common/publishShare*',
|
||||||
|
'src/components/platform-entry/PlatformEntryCreation*',
|
||||||
|
'src/components/platform-entry/PlatformEntryFlowShellImpl.tsx',
|
||||||
|
'src/components/platform-entry/platformEntryTypes.ts',
|
||||||
|
'src/components/platform-entry/PlatformEntryHome*',
|
||||||
|
'src/components/platform-entry/PlatformEntryWorld*',
|
||||||
|
'src/components/platform-entry/PlatformMobileHome*',
|
||||||
|
'src/components/platform-entry/PlatformWork*',
|
||||||
|
'src/components/platform-entry/PlatformDraft*',
|
||||||
|
'src/components/platform-entry/PlatformTask*',
|
||||||
|
'src/components/platform-entry/PlatformError*',
|
||||||
|
'src/components/platform-entry/barkBattle*',
|
||||||
|
'src/components/platform-entry/platformCreation*',
|
||||||
|
'src/components/platform-entry/platformDialog*',
|
||||||
|
'src/components/platform-entry/platformDraft*',
|
||||||
|
'src/components/platform-entry/platformEdutainment*',
|
||||||
|
'src/components/platform-entry/platformEntryCreation*',
|
||||||
|
'src/components/platform-entry/platformExternal*',
|
||||||
|
'src/components/platform-entry/platformGeneration*',
|
||||||
|
'src/components/platform-entry/platformHost*',
|
||||||
|
'src/components/platform-entry/platformMiniGame*',
|
||||||
|
'src/components/platform-entry/platformPlayed*',
|
||||||
|
'src/components/platform-entry/platformPublic*',
|
||||||
|
'src/components/platform-entry/platformPuzzle*',
|
||||||
|
'src/components/platform-entry/platformRecommend*',
|
||||||
|
'src/components/platform-entry/platformRpg*',
|
||||||
|
'src/components/platform-entry/platformSelection*',
|
||||||
|
'src/components/platform-entry/puzzleDraft*',
|
||||||
|
'src/components/platform-entry/usePlatformCreation*',
|
||||||
|
'src/components/platform-entry/usePlatformEntry*',
|
||||||
|
'src/components/platform-entry/PlatformEntryFlowShellImpl/**',
|
||||||
|
'src/components/platform-entry/platformMatch3DRuntimeProfile*',
|
||||||
|
'src/components/unified-creation/workspaces/JumpHopCreationWorkspace*',
|
||||||
|
'src/components/unified-creation/workspaces/Match3DCreationWorkspace*',
|
||||||
'src/components/rpg-creation-editor/**',
|
'src/components/rpg-creation-editor/**',
|
||||||
'src/components/rpg-entry/**',
|
'src/components/rpg-entry/**',
|
||||||
|
'!src/components/rpg-entry/',
|
||||||
|
'!src/components/rpg-entry/rpgEntryProfileFundsViewModel.ts',
|
||||||
|
'src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx',
|
||||||
|
'src/components/custom-world-home/CustomWorldCreationHub.test.tsx',
|
||||||
|
'src/components/custom-world-home/CustomWorldCreationHub.testAdapter.tsx',
|
||||||
|
'src/components/custom-world-home/creationWorkShelf.test.ts',
|
||||||
'src/components/rpg-runtime-shell/**',
|
'src/components/rpg-runtime-shell/**',
|
||||||
'src/hooks/rpg-runtime-story/**',
|
'src/hooks/rpg-runtime-story/**',
|
||||||
|
'src/hooks/rpg-session/**',
|
||||||
|
'src/hooks/combat/**',
|
||||||
'src/prompts/customWorldPrompts.ts',
|
'src/prompts/customWorldPrompts.ts',
|
||||||
'src/services/ai.ts',
|
'src/services/ai.ts',
|
||||||
|
'src/services/appTitle.ts',
|
||||||
|
'src/services/appTitle.test.ts',
|
||||||
'src/services/miniGameDraftGenerationProgress.ts',
|
'src/services/miniGameDraftGenerationProgress.ts',
|
||||||
|
'src/services/bark-battle-creation/**',
|
||||||
|
'src/services/bark-battle-runtime/**',
|
||||||
|
'src/services/big-fish-creation/**',
|
||||||
|
'src/services/big-fish-gallery/**',
|
||||||
|
'src/services/big-fish-runtime/**',
|
||||||
|
'src/services/big-fish-works/**',
|
||||||
|
'src/services/creation-agent/**',
|
||||||
|
'src/services/creation-audio/**',
|
||||||
|
'src/services/creative-agent/**',
|
||||||
|
'src/services/edutainment-baby-drawing/**',
|
||||||
|
'src/services/edutainment-baby-object/**',
|
||||||
|
'src/services/child-motion-demo/**',
|
||||||
|
'src/services/jump-hop/**',
|
||||||
|
'src/services/match3d-creation/**',
|
||||||
|
'src/services/match3d-runtime/**',
|
||||||
|
'src/services/match3d-works/**',
|
||||||
|
'src/services/match3dGeneratedModelCache*',
|
||||||
|
'src/services/match3dSpritesheetParser*',
|
||||||
|
'src/services/puzzle-agent/**',
|
||||||
|
'src/services/puzzle-gallery/**',
|
||||||
|
'src/services/puzzle-onboarding/**',
|
||||||
|
'src/services/puzzle-runtime/**',
|
||||||
|
'src/services/puzzle-works/**',
|
||||||
|
'src/services/rpg-creation/**',
|
||||||
|
'src/services/rpg-entry/index.ts',
|
||||||
|
'src/services/rpg-entry/rpgEntryLibraryClient.ts',
|
||||||
|
'src/services/rpg-entry/rpgProfileClient.test.ts',
|
||||||
|
'src/services/rpg-runtime/**',
|
||||||
|
'!src/services/rpg-runtime/',
|
||||||
|
'!src/services/rpg-runtime/rpgRuntimeRequest.ts',
|
||||||
|
'src/services/square-hole-creation/**',
|
||||||
|
'src/services/square-hole-runtime/**',
|
||||||
|
'src/services/square-hole-works/**',
|
||||||
|
'src/services/storyEngine/**',
|
||||||
|
'src/services/visual-novel-creation/**',
|
||||||
|
'src/services/visual-novel-runtime/**',
|
||||||
|
'src/services/visual-novel-works/**',
|
||||||
|
'src/services/wooden-fish/**',
|
||||||
|
'packages/shared/src/contracts/jumpHop.ts',
|
||||||
|
'packages/shared/src/contracts/match3dAgent.ts',
|
||||||
|
'packages/shared/src/contracts/match3dRuntime.ts',
|
||||||
|
'packages/shared/src/contracts/match3dWorks.ts',
|
||||||
|
'packages/shared/src/contracts/barkBattle*',
|
||||||
|
'packages/shared/src/contracts/bigFish*',
|
||||||
|
'packages/shared/src/contracts/creationAgent*',
|
||||||
|
'packages/shared/src/contracts/creationAudio*',
|
||||||
|
'packages/shared/src/contracts/creativeAgent*',
|
||||||
|
'packages/shared/src/contracts/customWorld*',
|
||||||
|
'packages/shared/src/contracts/edutainment*',
|
||||||
|
'packages/shared/src/contracts/playTypes*',
|
||||||
|
'packages/shared/src/contracts/publicWork*',
|
||||||
|
'packages/shared/src/contracts/puzzle*',
|
||||||
|
'packages/shared/src/contracts/rpg*',
|
||||||
|
'packages/shared/src/contracts/squareHole*',
|
||||||
|
'packages/shared/src/contracts/story*',
|
||||||
|
'packages/shared/src/contracts/visualNovel*',
|
||||||
|
'packages/shared/src/contracts/woodenFish*',
|
||||||
|
'scripts/export-match3d-resource-pipeline*',
|
||||||
|
'scripts/generate-child-motion-demo-assets.mjs',
|
||||||
'src/services/puzzle-clear/**',
|
'src/services/puzzle-clear/**',
|
||||||
|
'src/games/**',
|
||||||
|
'src/data/**',
|
||||||
|
'src/prompts/**',
|
||||||
|
'apps/admin-web/src/pages/AdminCreationEntrySwitchPage*',
|
||||||
|
'apps/admin-web/src/pages/AdminGrayReleaseConfigPage*',
|
||||||
|
'apps/admin-web/src/pages/AdminWorkVisibilityPage*',
|
||||||
'src/services/recommendedRuntimeGuestLaunch.test.ts',
|
'src/services/recommendedRuntimeGuestLaunch.test.ts',
|
||||||
'src/data/sceneEncounterPreviews.ts',
|
'src/data/sceneEncounterPreviews.ts',
|
||||||
'public/Icons',
|
'public/Icons',
|
||||||
@@ -130,7 +293,10 @@ module.exports = {
|
|||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/no-var-requires': 'off',
|
'@typescript-eslint/no-var-requires': 'off',
|
||||||
'react-hooks/rules-of-hooks': 'error',
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
'react-refresh/only-export-components': ['error', {allowConstantExport: true}],
|
'react-refresh/only-export-components': [
|
||||||
|
'error',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
'simple-import-sort/imports': 'error',
|
'simple-import-sort/imports': 'error',
|
||||||
'simple-import-sort/exports': 'error',
|
'simple-import-sort/exports': 'error',
|
||||||
'@typescript-eslint/no-unused-vars': 'off',
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
@@ -143,7 +309,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
'no-constant-condition': 'error',
|
'no-constant-condition': 'error',
|
||||||
'no-console': ['error', {allow: ['warn', 'error']}],
|
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||||
'no-useless-escape': 'error',
|
'no-useless-escape': 'error',
|
||||||
'prefer-const': 'error',
|
'prefer-const': 'error',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import type {
|
|||||||
AdminCreateAccountResponse,
|
AdminCreateAccountResponse,
|
||||||
AdminCreateEditorShowcaseCampaignImageUploadTicketRequest,
|
AdminCreateEditorShowcaseCampaignImageUploadTicketRequest,
|
||||||
AdminCreateEditorShowcaseCampaignImageUploadTicketResponse,
|
AdminCreateEditorShowcaseCampaignImageUploadTicketResponse,
|
||||||
AdminCreationEntryConfigResponse,
|
|
||||||
AdminDashboardQuery,
|
AdminDashboardQuery,
|
||||||
AdminDashboardResponse,
|
AdminDashboardResponse,
|
||||||
AdminDatabaseTableListResponse,
|
AdminDatabaseTableListResponse,
|
||||||
@@ -23,7 +22,6 @@ import type {
|
|||||||
AdminEditorShowcaseListQuery,
|
AdminEditorShowcaseListQuery,
|
||||||
AdminEditorShowcaseListResponse,
|
AdminEditorShowcaseListResponse,
|
||||||
AdminEditorShowcaseReviewRequest,
|
AdminEditorShowcaseReviewRequest,
|
||||||
AdminFeatureGateConfigResponse,
|
|
||||||
AdminLoginResponse,
|
AdminLoginResponse,
|
||||||
AdminMeResponse,
|
AdminMeResponse,
|
||||||
AdminOverviewResponse,
|
AdminOverviewResponse,
|
||||||
@@ -40,24 +38,17 @@ import type {
|
|||||||
AdminTrackingEventListResponse,
|
AdminTrackingEventListResponse,
|
||||||
AdminUpdateAccountRequest,
|
AdminUpdateAccountRequest,
|
||||||
AdminUpdateAccountResponse,
|
AdminUpdateAccountResponse,
|
||||||
AdminUpdateWorkVisibilityRequest,
|
|
||||||
AdminUpdateWorkVisibilityResponse,
|
|
||||||
AdminUploadedEditorShowcaseCampaignImage,
|
AdminUploadedEditorShowcaseCampaignImage,
|
||||||
AdminUpsertCreationEntryEventBannersRequest,
|
|
||||||
AdminUpsertCreationEntryTypeConfigRequest,
|
|
||||||
AdminUpsertEditorShowcaseCampaignRequest,
|
AdminUpsertEditorShowcaseCampaignRequest,
|
||||||
AdminUpsertFeatureGateConfigRequest,
|
|
||||||
AdminUpsertProfileInviteCodeRequest,
|
AdminUpsertProfileInviteCodeRequest,
|
||||||
AdminUpsertProfileRechargeProductRequest,
|
AdminUpsertProfileRechargeProductRequest,
|
||||||
AdminUpsertProfileRedeemCodeRequest,
|
AdminUpsertProfileRedeemCodeRequest,
|
||||||
AdminUpsertProfileTaskConfigRequest,
|
AdminUpsertProfileTaskConfigRequest,
|
||||||
AdminUpsertProfileWalletConfigRequest,
|
AdminUpsertProfileWalletConfigRequest,
|
||||||
AdminUpsertPublicWorkInteractionConfigRequest,
|
|
||||||
AdminUserDetailQuery,
|
AdminUserDetailQuery,
|
||||||
AdminUserDetailResponse,
|
AdminUserDetailResponse,
|
||||||
AdminWalletRestrictionRequest,
|
AdminWalletRestrictionRequest,
|
||||||
AdminWalletRestrictionResponse,
|
AdminWalletRestrictionResponse,
|
||||||
AdminWorkVisibilityListResponse,
|
|
||||||
ApiErrorEnvelope,
|
ApiErrorEnvelope,
|
||||||
ApiMeta,
|
ApiMeta,
|
||||||
ApiSuccessEnvelope,
|
ApiSuccessEnvelope,
|
||||||
@@ -194,7 +185,7 @@ export function getAdminMe(token: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function listAdminAccounts(token: string) {
|
export function listAdminAccounts(token: string) {
|
||||||
return request<AdminAccountListResponse>('/admin/api/accounts', {token});
|
return request<AdminAccountListResponse>('/admin/api/accounts', { token });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createAdminAccount(
|
export function createAdminAccount(
|
||||||
@@ -215,7 +206,7 @@ export function updateAdminAccount(
|
|||||||
) {
|
) {
|
||||||
return request<AdminUpdateAccountResponse>(
|
return request<AdminUpdateAccountResponse>(
|
||||||
`/admin/api/accounts/${encodeURIComponent(accountId)}`,
|
`/admin/api/accounts/${encodeURIComponent(accountId)}`,
|
||||||
{method: 'PUT', token, body: payload},
|
{ method: 'PUT', token, body: payload },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,74 +266,6 @@ export function listAdminTrackingEventKeys(token: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAdminFeatureGateConfig(token: string) {
|
|
||||||
return request<AdminFeatureGateConfigResponse>('/admin/api/feature-gates', {
|
|
||||||
token,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function upsertAdminFeatureGateConfig(
|
|
||||||
token: string,
|
|
||||||
payload: AdminUpsertFeatureGateConfigRequest,
|
|
||||||
) {
|
|
||||||
return request<AdminFeatureGateConfigResponse>('/admin/api/feature-gates', {
|
|
||||||
method: 'PUT',
|
|
||||||
token,
|
|
||||||
body: payload,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAdminCreationEntryConfig(token: string) {
|
|
||||||
return request<AdminCreationEntryConfigResponse>(
|
|
||||||
'/admin/api/creation-entry/config',
|
|
||||||
{ token },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function upsertAdminCreationEntryConfig(
|
|
||||||
token: string,
|
|
||||||
payload: AdminUpsertCreationEntryTypeConfigRequest,
|
|
||||||
) {
|
|
||||||
return request<AdminCreationEntryConfigResponse>(
|
|
||||||
'/admin/api/creation-entry/config',
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
token,
|
|
||||||
body: payload,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 保存创作入口公告表单序列化后的后端传输字段。 */
|
|
||||||
export function upsertAdminCreationEntryBanners(
|
|
||||||
token: string,
|
|
||||||
payload: AdminUpsertCreationEntryEventBannersRequest,
|
|
||||||
) {
|
|
||||||
return request<AdminCreationEntryConfigResponse>(
|
|
||||||
'/admin/api/creation-entry/config/banners',
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
token,
|
|
||||||
body: payload,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 保存公开作品详情页点赞 / 改造能力配置。 */
|
|
||||||
export function upsertAdminPublicWorkInteractions(
|
|
||||||
token: string,
|
|
||||||
payload: AdminUpsertPublicWorkInteractionConfigRequest,
|
|
||||||
) {
|
|
||||||
return request<AdminCreationEntryConfigResponse>(
|
|
||||||
'/admin/api/creation-entry/config/interactions',
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
token,
|
|
||||||
body: payload,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAdminEditorGenerationPricing(token: string) {
|
export function getAdminEditorGenerationPricing(token: string) {
|
||||||
return request<EditorGenerationPricingConfigPayload>(
|
return request<EditorGenerationPricingConfigPayload>(
|
||||||
'/admin/api/editor-generation-pricing',
|
'/admin/api/editor-generation-pricing',
|
||||||
@@ -364,27 +287,6 @@ export function upsertAdminEditorGenerationPricing(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listAdminWorkVisibility(token: string) {
|
|
||||||
return request<AdminWorkVisibilityListResponse>(
|
|
||||||
'/admin/api/works/visibility',
|
|
||||||
{ token },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateAdminWorkVisibility(
|
|
||||||
token: string,
|
|
||||||
payload: AdminUpdateWorkVisibilityRequest,
|
|
||||||
) {
|
|
||||||
return request<AdminUpdateWorkVisibilityResponse>(
|
|
||||||
'/admin/api/works/visibility',
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
token,
|
|
||||||
body: payload,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAdminAssetReadUrl(
|
export function getAdminAssetReadUrl(
|
||||||
token: string,
|
token: string,
|
||||||
query: AdminAssetReadUrlQuery,
|
query: AdminAssetReadUrlQuery,
|
||||||
@@ -471,18 +373,19 @@ export async function uploadAdminEditorShowcaseCampaignImage(
|
|||||||
): Promise<AdminUploadedEditorShowcaseCampaignImage> {
|
): Promise<AdminUploadedEditorShowcaseCampaignImage> {
|
||||||
const contentType = resolveAdminImageContentType(file);
|
const contentType = resolveAdminImageContentType(file);
|
||||||
const dimensions = await readAdminImageFileDimensions(file);
|
const dimensions = await readAdminImageFileDimensions(file);
|
||||||
const response = await request<AdminCreateEditorShowcaseCampaignImageUploadTicketResponse>(
|
const response =
|
||||||
'/admin/api/editor-showcase/campaign/image-upload-ticket',
|
await request<AdminCreateEditorShowcaseCampaignImageUploadTicketResponse>(
|
||||||
{
|
'/admin/api/editor-showcase/campaign/image-upload-ticket',
|
||||||
method: 'POST',
|
{
|
||||||
token,
|
method: 'POST',
|
||||||
body: {
|
token,
|
||||||
fileName: file.name.trim() || 'showcase-campaign.png',
|
body: {
|
||||||
contentType,
|
fileName: file.name.trim() || 'showcase-campaign.png',
|
||||||
contentLength: file.size,
|
contentType,
|
||||||
} satisfies AdminCreateEditorShowcaseCampaignImageUploadTicketRequest,
|
contentLength: file.size,
|
||||||
},
|
} satisfies AdminCreateEditorShowcaseCampaignImageUploadTicketRequest,
|
||||||
);
|
},
|
||||||
|
);
|
||||||
await postAdminDirectUploadFile(response.upload, file);
|
await postAdminDirectUploadFile(response.upload, file);
|
||||||
const objectKey = response.upload.objectKey.trim().replace(/^\/+/u, '');
|
const objectKey = response.upload.objectKey.trim().replace(/^\/+/u, '');
|
||||||
return {
|
return {
|
||||||
@@ -630,17 +533,14 @@ export function listAdminRechargeOrders(
|
|||||||
) {
|
) {
|
||||||
return request<AdminRechargeOrderListResponse>(
|
return request<AdminRechargeOrderListResponse>(
|
||||||
`/admin/api/profile/recharge-orders${buildAdminRechargeOrderListQuery(query)}`,
|
`/admin/api/profile/recharge-orders${buildAdminRechargeOrderListQuery(query)}`,
|
||||||
{token},
|
{ token },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAdminUserDetail(
|
export function getAdminUserDetail(token: string, query: AdminUserDetailQuery) {
|
||||||
token: string,
|
|
||||||
query: AdminUserDetailQuery,
|
|
||||||
) {
|
|
||||||
return request<AdminUserDetailResponse>(
|
return request<AdminUserDetailResponse>(
|
||||||
`/admin/api/profile/users/detail${buildAdminUserDetailQuery(query)}`,
|
`/admin/api/profile/users/detail${buildAdminUserDetailQuery(query)}`,
|
||||||
{token},
|
{ token },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,7 +550,7 @@ export function previewAdminRechargeRefund(
|
|||||||
) {
|
) {
|
||||||
return request<AdminRechargeRefundPreviewResponse>(
|
return request<AdminRechargeRefundPreviewResponse>(
|
||||||
'/admin/api/profile/recharge-refunds/preview',
|
'/admin/api/profile/recharge-refunds/preview',
|
||||||
{method: 'POST', token, body: payload},
|
{ method: 'POST', token, body: payload },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,7 +560,7 @@ export function executeAdminRechargeRefund(
|
|||||||
) {
|
) {
|
||||||
return request<AdminRechargeRefundActionResponse>(
|
return request<AdminRechargeRefundActionResponse>(
|
||||||
'/admin/api/profile/recharge-refunds/execute',
|
'/admin/api/profile/recharge-refunds/execute',
|
||||||
{method: 'POST', token, body: payload},
|
{ method: 'POST', token, body: payload },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,7 +570,7 @@ export function registerAdminRechargeRefund(
|
|||||||
) {
|
) {
|
||||||
return request<AdminRechargeRefundActionResponse>(
|
return request<AdminRechargeRefundActionResponse>(
|
||||||
'/admin/api/profile/recharge-refunds/register',
|
'/admin/api/profile/recharge-refunds/register',
|
||||||
{method: 'POST', token, body: payload},
|
{ method: 'POST', token, body: payload },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,7 +580,7 @@ export function resolveAdminRechargeRefundManualReview(
|
|||||||
) {
|
) {
|
||||||
return request<AdminRechargeRefundActionResponse>(
|
return request<AdminRechargeRefundActionResponse>(
|
||||||
'/admin/api/profile/recharge-refunds/manual-review/resolve',
|
'/admin/api/profile/recharge-refunds/manual-review/resolve',
|
||||||
{method: 'POST', token, body: payload},
|
{ method: 'POST', token, body: payload },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -690,7 +590,7 @@ export function updateAdminWalletRestriction(
|
|||||||
) {
|
) {
|
||||||
return request<AdminWalletRestrictionResponse>(
|
return request<AdminWalletRestrictionResponse>(
|
||||||
'/admin/api/profile/wallet-restriction',
|
'/admin/api/profile/wallet-restriction',
|
||||||
{method: 'POST', token, body: payload},
|
{ method: 'POST', token, body: payload },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -710,10 +610,7 @@ function buildAssetReadUrlQuery(query: AdminAssetReadUrlQuery) {
|
|||||||
if (objectKey) {
|
if (objectKey) {
|
||||||
params.set('objectKey', objectKey);
|
params.set('objectKey', objectKey);
|
||||||
} else if (legacyPublicPath) {
|
} else if (legacyPublicPath) {
|
||||||
params.set(
|
params.set('legacyPublicPath', `/${legacyPublicPath.replace(/^\/+/u, '')}`);
|
||||||
'legacyPublicPath',
|
|
||||||
`/${legacyPublicPath.replace(/^\/+/u, '')}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
typeof query.expireSeconds === 'number' &&
|
typeof query.expireSeconds === 'number' &&
|
||||||
@@ -731,7 +628,10 @@ function resolveAdminImageContentType(file: File) {
|
|||||||
if (declaredType.startsWith('image/')) {
|
if (declaredType.startsWith('image/')) {
|
||||||
return declaredType;
|
return declaredType;
|
||||||
}
|
}
|
||||||
const extension = file.name.trim().toLowerCase().match(/\.([a-z0-9]+)$/u)?.[1];
|
const extension = file.name
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.match(/\.([a-z0-9]+)$/u)?.[1];
|
||||||
if (extension === 'jpg' || extension === 'jpeg') {
|
if (extension === 'jpg' || extension === 'jpeg') {
|
||||||
return 'image/jpeg';
|
return 'image/jpeg';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -286,98 +286,6 @@ export interface AdminTrackingEventListQuery {
|
|||||||
exportAll?: boolean;
|
exportAll?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminFeatureGateConfigPayload {
|
|
||||||
gateKey: string;
|
|
||||||
enabled: boolean;
|
|
||||||
rolloutPercent: number;
|
|
||||||
allowUserIds: string[];
|
|
||||||
allowUserTags: string[];
|
|
||||||
denyUserIds: string[];
|
|
||||||
description: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdminFeatureGateConfigResponse {
|
|
||||||
gates: AdminFeatureGateConfigPayload[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AdminUpsertFeatureGateConfigRequest = Omit<
|
|
||||||
AdminFeatureGateConfigPayload,
|
|
||||||
'updatedAt'
|
|
||||||
>;
|
|
||||||
|
|
||||||
/** 后台创作入口配置响应,同时包含模板入口和独立公告配置。 */
|
|
||||||
export interface AdminCreationEntryConfigResponse {
|
|
||||||
entries: AdminCreationEntryTypeConfigPayload[];
|
|
||||||
eventBanners: AdminCreationEntryEventBannerPayload[];
|
|
||||||
publicWorkInteractions: PublicWorkInteractionConfigPayload[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 后台创作入口公告位配置项;旧结构化 banner 字段仅保留兼容。 */
|
|
||||||
export interface AdminCreationEntryEventBannerPayload {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
coverImageSrc: string;
|
|
||||||
prizePoolMudPoints: number;
|
|
||||||
startsAtText: string;
|
|
||||||
endsAtText: string;
|
|
||||||
renderMode: 'structured' | 'html';
|
|
||||||
htmlCode?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 后台单个创作模板入口配置,公告不再绑定在某一个入口上。 */
|
|
||||||
export interface AdminCreationEntryTypeConfigPayload {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
badge: string;
|
|
||||||
imageSrc: string;
|
|
||||||
visible: boolean;
|
|
||||||
open: boolean;
|
|
||||||
sortOrder: number;
|
|
||||||
categoryId: string;
|
|
||||||
categoryLabel: string;
|
|
||||||
categorySortOrder: number;
|
|
||||||
updatedAtMicros: number;
|
|
||||||
unifiedCreationSpec?: UnifiedCreationSpecPayload | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 后台保存创作模板入口开关与统一创作契约的请求体。 */
|
|
||||||
export interface AdminUpsertCreationEntryTypeConfigRequest {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
badge: string;
|
|
||||||
imageSrc: string;
|
|
||||||
visible: boolean;
|
|
||||||
open: boolean;
|
|
||||||
sortOrder: number;
|
|
||||||
categoryId: string;
|
|
||||||
categoryLabel: string;
|
|
||||||
categorySortOrder: number;
|
|
||||||
unifiedCreationSpec?: UnifiedCreationSpecPayload | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 后台保存创作入口公告表单序列化结果的请求体。 */
|
|
||||||
export interface AdminUpsertCreationEntryEventBannersRequest {
|
|
||||||
/** 传输字段沿用后端契约,内容由后台表单生成。 */
|
|
||||||
eventBannersJson: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 后台公开作品详情页互动能力配置项。 */
|
|
||||||
export interface PublicWorkInteractionConfigPayload {
|
|
||||||
sourceType: string;
|
|
||||||
likeEnabled: boolean;
|
|
||||||
remixEnabled: boolean;
|
|
||||||
likeDisabledMessage: string;
|
|
||||||
remixDisabledMessage: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 后台保存公开作品点赞 / 改造能力配置请求体。 */
|
|
||||||
export interface AdminUpsertPublicWorkInteractionConfigRequest {
|
|
||||||
publicWorkInteractions: PublicWorkInteractionConfigPayload[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 图片画布生成模型泥点定价配置。 */
|
/** 图片画布生成模型泥点定价配置。 */
|
||||||
export type EditorGenerationPricingUnitPayload = 'perGeneration' | 'perSecond';
|
export type EditorGenerationPricingUnitPayload = 'perGeneration' | 'perSecond';
|
||||||
|
|
||||||
@@ -391,55 +299,6 @@ export interface EditorGenerationPricingConfigPayload {
|
|||||||
models: Record<string, EditorGenerationModelPricingPayload>;
|
models: Record<string, EditorGenerationModelPricingPayload>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 后台统一创作工作台契约表单的传输结构。 */
|
|
||||||
export interface UnifiedCreationSpecPayload {
|
|
||||||
playId: string;
|
|
||||||
title: string;
|
|
||||||
mudPointCost: number;
|
|
||||||
workspaceStage: string;
|
|
||||||
generationStage: string;
|
|
||||||
resultStage: string;
|
|
||||||
fields: UnifiedCreationFieldPayload[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 后台统一创作字段契约,保存前会校验字段类型和必填标记。 */
|
|
||||||
export interface UnifiedCreationFieldPayload {
|
|
||||||
id: string;
|
|
||||||
kind: 'text' | 'select' | 'image' | 'audio';
|
|
||||||
label: string;
|
|
||||||
required: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdminWorkVisibilityEntryPayload {
|
|
||||||
sourceType: string;
|
|
||||||
workId: string;
|
|
||||||
profileId: string;
|
|
||||||
sourceSessionId?: string | null;
|
|
||||||
publicWorkCode: string;
|
|
||||||
ownerUserId: string;
|
|
||||||
authorDisplayName: string;
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
coverImageSrc?: string | null;
|
|
||||||
visible: boolean;
|
|
||||||
publishedAtMicros?: number | null;
|
|
||||||
updatedAtMicros: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdminWorkVisibilityListResponse {
|
|
||||||
entries: AdminWorkVisibilityEntryPayload[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdminUpdateWorkVisibilityRequest {
|
|
||||||
sourceType: string;
|
|
||||||
profileId: string;
|
|
||||||
visible: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdminUpdateWorkVisibilityResponse {
|
|
||||||
entry: AdminWorkVisibilityEntryPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdminEditorAssetListQuery {
|
export interface AdminEditorAssetListQuery {
|
||||||
cursor?: string | null;
|
cursor?: string | null;
|
||||||
ownerUserId?: string | null;
|
ownerUserId?: string | null;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {useCallback, useEffect, useMemo, useState} from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
formatAdminApiError,
|
formatAdminApiError,
|
||||||
@@ -17,33 +17,30 @@ import {
|
|||||||
getStoredAdminToken,
|
getStoredAdminToken,
|
||||||
setStoredAdminToken,
|
setStoredAdminToken,
|
||||||
} from '../auth/adminAuthStore';
|
} from '../auth/adminAuthStore';
|
||||||
import {AdminAccountsPage} from '../pages/AdminAccountsPage';
|
import { AdminAccountsPage } from '../pages/AdminAccountsPage';
|
||||||
import {AdminCreationEntrySwitchPage} from '../pages/AdminCreationEntrySwitchPage';
|
import { AdminDashboardPage } from '../pages/AdminDashboardPage';
|
||||||
import {AdminDashboardPage} from '../pages/AdminDashboardPage';
|
import { AdminDatabaseTablesPage } from '../pages/AdminDatabaseTablesPage';
|
||||||
import {AdminDatabaseTablesPage} from '../pages/AdminDatabaseTablesPage';
|
import { AdminDebugHttpPage } from '../pages/AdminDebugHttpPage';
|
||||||
import {AdminDebugHttpPage} from '../pages/AdminDebugHttpPage';
|
import { AdminEditorAssetQueryPage } from '../pages/AdminEditorAssetQueryPage';
|
||||||
import {AdminEditorAssetQueryPage} from '../pages/AdminEditorAssetQueryPage';
|
import { AdminEditorGenerationPricingPage } from '../pages/AdminEditorGenerationPricingPage';
|
||||||
import {AdminEditorGenerationPricingPage} from '../pages/AdminEditorGenerationPricingPage';
|
import { AdminEditorShowcaseReviewPage } from '../pages/AdminEditorShowcaseReviewPage';
|
||||||
import {AdminEditorShowcaseReviewPage} from '../pages/AdminEditorShowcaseReviewPage';
|
import { AdminInviteCodePage } from '../pages/AdminInviteCodePage';
|
||||||
import {AdminGrayReleaseConfigPage} from '../pages/AdminGrayReleaseConfigPage';
|
import { AdminLoginPage } from '../pages/AdminLoginPage';
|
||||||
import {AdminInviteCodePage} from '../pages/AdminInviteCodePage';
|
import { AdminOverviewPage } from '../pages/AdminOverviewPage';
|
||||||
import {AdminLoginPage} from '../pages/AdminLoginPage';
|
import { AdminProfileWalletConfigPage } from '../pages/AdminProfileWalletConfigPage';
|
||||||
import {AdminOverviewPage} from '../pages/AdminOverviewPage';
|
import { AdminRechargeOrderPage } from '../pages/AdminRechargeOrderPage';
|
||||||
import {AdminProfileWalletConfigPage} from '../pages/AdminProfileWalletConfigPage';
|
import { AdminRechargeProductPage } from '../pages/AdminRechargeProductPage';
|
||||||
import {AdminRechargeOrderPage} from '../pages/AdminRechargeOrderPage';
|
import { AdminRedeemCodePage } from '../pages/AdminRedeemCodePage';
|
||||||
import {AdminRechargeProductPage} from '../pages/AdminRechargeProductPage';
|
import { AdminTaskConfigPage } from '../pages/AdminTaskConfigPage';
|
||||||
import {AdminRedeemCodePage} from '../pages/AdminRedeemCodePage';
|
import { AdminTrackingEventsPage } from '../pages/AdminTrackingEventsPage';
|
||||||
import {AdminTaskConfigPage} from '../pages/AdminTaskConfigPage';
|
import type { AdminRouteId } from './adminRoutes';
|
||||||
import {AdminTrackingEventsPage} from '../pages/AdminTrackingEventsPage';
|
|
||||||
import {AdminWorkVisibilityPage} from '../pages/AdminWorkVisibilityPage';
|
|
||||||
import type {AdminRouteId} from './adminRoutes';
|
|
||||||
import {
|
import {
|
||||||
getAccessibleAdminRoutes,
|
getAccessibleAdminRoutes,
|
||||||
resolveAccessibleAdminRoute,
|
resolveAccessibleAdminRoute,
|
||||||
resolveAdminRoute,
|
resolveAdminRoute,
|
||||||
routeHash,
|
routeHash,
|
||||||
} from './adminRoutes';
|
} from './adminRoutes';
|
||||||
import {AdminShell} from './AdminShell';
|
import { AdminShell } from './AdminShell';
|
||||||
|
|
||||||
type SessionStatus = 'checking' | 'guest' | 'authenticated';
|
type SessionStatus = 'checking' | 'guest' | 'authenticated';
|
||||||
|
|
||||||
@@ -157,17 +154,20 @@ export function AdminApp() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleLogin = useCallback(async (username: string, password: string) => {
|
const handleLogin = useCallback(
|
||||||
const response = await loginAdmin(username, password);
|
async (username: string, password: string) => {
|
||||||
setStoredAdminToken(response.token);
|
const response = await loginAdmin(username, password);
|
||||||
setToken(response.token);
|
setStoredAdminToken(response.token);
|
||||||
setAdmin(response.admin);
|
setToken(response.token);
|
||||||
setTaskConfigResult(null);
|
setAdmin(response.admin);
|
||||||
setProfileWalletConfigResult(null);
|
setTaskConfigResult(null);
|
||||||
setRechargeProductResult(null);
|
setProfileWalletConfigResult(null);
|
||||||
setLoginNotice('');
|
setRechargeProductResult(null);
|
||||||
setStatus('authenticated');
|
setLoginNotice('');
|
||||||
}, []);
|
setStatus('authenticated');
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const handleUnauthorized = useCallback(
|
const handleUnauthorized = useCallback(
|
||||||
(message = '登录状态已失效') => {
|
(message = '登录状态已失效') => {
|
||||||
@@ -227,12 +227,6 @@ export function AdminApp() {
|
|||||||
onUnauthorized={handleUnauthorized}
|
onUnauthorized={handleUnauthorized}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{activeRouteId === 'gray-release' ? (
|
|
||||||
<AdminGrayReleaseConfigPage
|
|
||||||
token={token}
|
|
||||||
onUnauthorized={handleUnauthorized}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{activeRouteId === 'redeem' ? (
|
{activeRouteId === 'redeem' ? (
|
||||||
<AdminRedeemCodePage
|
<AdminRedeemCodePage
|
||||||
token={token}
|
token={token}
|
||||||
@@ -245,25 +239,6 @@ export function AdminApp() {
|
|||||||
onUnauthorized={handleUnauthorized}
|
onUnauthorized={handleUnauthorized}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{activeRouteId === 'creation-announcement' ? (
|
|
||||||
<AdminCreationEntrySwitchPage
|
|
||||||
mode="announcements"
|
|
||||||
token={token}
|
|
||||||
onUnauthorized={handleUnauthorized}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{activeRouteId === 'creation-entry' ? (
|
|
||||||
<AdminCreationEntrySwitchPage
|
|
||||||
token={token}
|
|
||||||
onUnauthorized={handleUnauthorized}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{activeRouteId === 'work-visibility' ? (
|
|
||||||
<AdminWorkVisibilityPage
|
|
||||||
token={token}
|
|
||||||
onUnauthorized={handleUnauthorized}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{activeRouteId === 'tasks' ? (
|
{activeRouteId === 'tasks' ? (
|
||||||
<AdminTaskConfigPage
|
<AdminTaskConfigPage
|
||||||
result={taskConfigResult}
|
result={taskConfigResult}
|
||||||
@@ -313,10 +288,7 @@ export function AdminApp() {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{activeRouteId === 'accounts' ? (
|
{activeRouteId === 'accounts' ? (
|
||||||
<AdminAccountsPage
|
<AdminAccountsPage token={token} onUnauthorized={handleUnauthorized} />
|
||||||
token={token}
|
|
||||||
onUnauthorized={handleUnauthorized}
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
</AdminShell>
|
</AdminShell>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,16 +4,12 @@ import {
|
|||||||
Bug,
|
Bug,
|
||||||
Coins,
|
Coins,
|
||||||
Database,
|
Database,
|
||||||
Eye,
|
|
||||||
GitBranch,
|
|
||||||
Images,
|
Images,
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
ListChecks,
|
ListChecks,
|
||||||
LogOut,
|
LogOut,
|
||||||
Megaphone,
|
|
||||||
ReceiptText,
|
ReceiptText,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
SlidersHorizontal,
|
|
||||||
Star,
|
Star,
|
||||||
Table2,
|
Table2,
|
||||||
TicketCheck,
|
TicketCheck,
|
||||||
@@ -21,10 +17,10 @@ import {
|
|||||||
Users,
|
Users,
|
||||||
WalletCards,
|
WalletCards,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import type {ReactNode} from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
import type {AdminSessionPayload} from '../api/adminApiTypes';
|
import type { AdminSessionPayload } from '../api/adminApiTypes';
|
||||||
import type {AdminRouteDefinition, AdminRouteId} from './adminRoutes';
|
import type { AdminRouteDefinition, AdminRouteId } from './adminRoutes';
|
||||||
|
|
||||||
interface AdminShellProps {
|
interface AdminShellProps {
|
||||||
admin: AdminSessionPayload;
|
admin: AdminSessionPayload;
|
||||||
@@ -41,7 +37,6 @@ const routeIcons = {
|
|||||||
tables: Database,
|
tables: Database,
|
||||||
debug: Bug,
|
debug: Bug,
|
||||||
tracking: Table2,
|
tracking: Table2,
|
||||||
'gray-release': GitBranch,
|
|
||||||
redeem: TicketPercent,
|
redeem: TicketPercent,
|
||||||
invite: TicketCheck,
|
invite: TicketCheck,
|
||||||
'profile-wallet': WalletCards,
|
'profile-wallet': WalletCards,
|
||||||
@@ -51,9 +46,6 @@ const routeIcons = {
|
|||||||
'editor-generation-pricing': Coins,
|
'editor-generation-pricing': Coins,
|
||||||
'editor-showcase': Star,
|
'editor-showcase': Star,
|
||||||
'editor-assets': Images,
|
'editor-assets': Images,
|
||||||
'creation-announcement': Megaphone,
|
|
||||||
'creation-entry': SlidersHorizontal,
|
|
||||||
'work-visibility': Eye,
|
|
||||||
accounts: Users,
|
accounts: Users,
|
||||||
} satisfies Record<AdminRouteId, typeof LayoutDashboard>;
|
} satisfies Record<AdminRouteId, typeof LayoutDashboard>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {expect, test} from 'vitest';
|
import { expect, test } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
adminRoutes,
|
adminRoutes,
|
||||||
@@ -19,19 +19,6 @@ test('后台默认进入 Dashboard', () => {
|
|||||||
expect(routeHash('dashboard')).toBe('#dashboard');
|
expect(routeHash('dashboard')).toBe('#dashboard');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 中文注释:后台入口公告必须作为独立导航存在,避免公告表单被误藏在入口开关页。
|
|
||||||
test('后台入口公告路由可通过导航和 hash 访问', () => {
|
|
||||||
expect(adminRoutes).toContainEqual({
|
|
||||||
id: 'creation-announcement',
|
|
||||||
label: '入口公告',
|
|
||||||
hash: '#creation-announcement',
|
|
||||||
});
|
|
||||||
expect(resolveAdminRoute('#creation-announcement')).toBe(
|
|
||||||
'creation-announcement',
|
|
||||||
);
|
|
||||||
expect(routeHash('creation-announcement')).toBe('#creation-announcement');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('后台模型定价路由可通过导航和 hash 访问', () => {
|
test('后台模型定价路由可通过导航和 hash 访问', () => {
|
||||||
expect(adminRoutes).toContainEqual({
|
expect(adminRoutes).toContainEqual({
|
||||||
id: 'editor-generation-pricing',
|
id: 'editor-generation-pricing',
|
||||||
@@ -46,16 +33,12 @@ test('后台模型定价路由可通过导航和 hash 访问', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('后台灰度发布路由可通过导航和 hash 访问', () => {
|
test('后台不再暴露旧创作模板管理路由', () => {
|
||||||
expect(adminRoutes).toContainEqual({
|
expect(resolveAdminRoute('#creation-entry')).toBe('dashboard');
|
||||||
id: 'gray-release',
|
expect(resolveAdminRoute('#creation-announcement')).toBe('dashboard');
|
||||||
label: '灰度发布',
|
expect(resolveAdminRoute('#gray-release')).toBe('dashboard');
|
||||||
hash: '#gray-release',
|
expect(resolveAdminRoute('#work-visibility')).toBe('dashboard');
|
||||||
});
|
|
||||||
expect(resolveAdminRoute('#gray-release')).toBe('gray-release');
|
|
||||||
expect(routeHash('gray-release')).toBe('#gray-release');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('后台素材查询路由可通过导航和 hash 访问', () => {
|
test('后台素材查询路由可通过导航和 hash 访问', () => {
|
||||||
expect(adminRoutes).toContainEqual({
|
expect(adminRoutes).toContainEqual({
|
||||||
id: 'editor-assets',
|
id: 'editor-assets',
|
||||||
@@ -92,7 +75,7 @@ test('owner 可访问全部业务 Tab 和账号管理', () => {
|
|||||||
tabPermissions: [],
|
tabPermissions: [],
|
||||||
});
|
});
|
||||||
expect(routes).toEqual(adminRoutes);
|
expect(routes).toEqual(adminRoutes);
|
||||||
expect(routes.at(-1)).toMatchObject({id: 'accounts', ownerOnly: true});
|
expect(routes.at(-1)).toMatchObject({ id: 'accounts', ownerOnly: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('member 只访问已分配 Tab 且无权 hash 回落到第一项', () => {
|
test('member 只访问已分配 Tab 且无权 hash 回落到第一项', () => {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ export type AdminRouteId =
|
|||||||
| 'tables'
|
| 'tables'
|
||||||
| 'debug'
|
| 'debug'
|
||||||
| 'tracking'
|
| 'tracking'
|
||||||
| 'gray-release'
|
|
||||||
| 'redeem'
|
| 'redeem'
|
||||||
| 'invite'
|
| 'invite'
|
||||||
| 'profile-wallet'
|
| 'profile-wallet'
|
||||||
@@ -15,9 +14,6 @@ export type AdminRouteId =
|
|||||||
| 'editor-generation-pricing'
|
| 'editor-generation-pricing'
|
||||||
| 'editor-showcase'
|
| 'editor-showcase'
|
||||||
| 'editor-assets'
|
| 'editor-assets'
|
||||||
| 'creation-announcement'
|
|
||||||
| 'creation-entry'
|
|
||||||
| 'work-visibility'
|
|
||||||
| 'accounts';
|
| 'accounts';
|
||||||
|
|
||||||
export type AdminTabPermission = Exclude<AdminRouteId, 'accounts'>;
|
export type AdminTabPermission = Exclude<AdminRouteId, 'accounts'>;
|
||||||
@@ -31,25 +27,25 @@ export interface AdminRouteDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const adminRoutes: AdminRouteDefinition[] = [
|
export const adminRoutes: AdminRouteDefinition[] = [
|
||||||
{id: 'dashboard', label: 'Dashboard', hash: '#dashboard'},
|
{ id: 'dashboard', label: 'Dashboard', hash: '#dashboard' },
|
||||||
{id: 'overview', label: '服务总览', hash: '#overview'},
|
{ id: 'overview', label: '服务总览', hash: '#overview' },
|
||||||
{id: 'tables', label: '表查询', hash: '#tables'},
|
{ id: 'tables', label: '表查询', hash: '#tables' },
|
||||||
{id: 'debug', label: 'API 调试', hash: '#debug'},
|
{ id: 'debug', label: 'API 调试', hash: '#debug' },
|
||||||
{id: 'tracking', label: '埋点数据', hash: '#tracking'},
|
{ id: 'tracking', label: '埋点数据', hash: '#tracking' },
|
||||||
{id: 'gray-release', label: '灰度发布', hash: '#gray-release'},
|
{ id: 'redeem', label: '兑换码', hash: '#redeem' },
|
||||||
{id: 'redeem', label: '兑换码', hash: '#redeem'},
|
{ id: 'invite', label: '邀请码', hash: '#invite' },
|
||||||
{id: 'invite', label: '邀请码', hash: '#invite'},
|
{ id: 'profile-wallet', label: '账号配置', hash: '#profile-wallet' },
|
||||||
{id: 'profile-wallet', label: '账号配置', hash: '#profile-wallet'},
|
{ id: 'tasks', label: '任务配置', hash: '#tasks' },
|
||||||
{id: 'tasks', label: '任务配置', hash: '#tasks'},
|
{ id: 'recharge-products', label: '充值商品', hash: '#recharge-products' },
|
||||||
{id: 'recharge-products', label: '充值商品', hash: '#recharge-products'},
|
{ id: 'recharge-orders', label: '充值管理', hash: '#recharge-orders' },
|
||||||
{id: 'recharge-orders', label: '充值管理', hash: '#recharge-orders'},
|
{
|
||||||
{id: 'editor-generation-pricing', label: '模型定价', hash: '#editor-generation-pricing'},
|
id: 'editor-generation-pricing',
|
||||||
{id: 'editor-showcase', label: '精选审核', hash: '#editor-showcase'},
|
label: '模型定价',
|
||||||
{id: 'editor-assets', label: '素材查询', hash: '#editor-assets'},
|
hash: '#editor-generation-pricing',
|
||||||
{id: 'creation-announcement', label: '入口公告', hash: '#creation-announcement'},
|
},
|
||||||
{id: 'creation-entry', label: '入口开关', hash: '#creation-entry'},
|
{ id: 'editor-showcase', label: '精选审核', hash: '#editor-showcase' },
|
||||||
{id: 'work-visibility', label: '作品可见性', hash: '#work-visibility'},
|
{ id: 'editor-assets', label: '素材查询', hash: '#editor-assets' },
|
||||||
{id: 'accounts', label: '账号管理', hash: '#accounts', ownerOnly: true},
|
{ id: 'accounts', label: '账号管理', hash: '#accounts', ownerOnly: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface AdminRouteAccess {
|
export interface AdminRouteAccess {
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ describe('admin tracking event definitions', () => {
|
|||||||
expect(keys).toContain('auth_login_options_view');
|
expect(keys).toContain('auth_login_options_view');
|
||||||
expect(keys).toContain('task_center_view');
|
expect(keys).toContain('task_center_view');
|
||||||
expect(keys).toContain('asset_upload_ticket_create');
|
expect(keys).toContain('asset_upload_ticket_create');
|
||||||
expect(keys).toContain('creative_agent_route_success');
|
expect(keys).not.toContain('creative_agent_route_success');
|
||||||
expect(keys).toContain('work_play_start');
|
expect(keys).not.toContain('work_play_start');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('任务配置候选只开放适合个人任务的事件', () => {
|
test('任务配置候选只开放适合个人任务的事件', () => {
|
||||||
@@ -49,13 +49,15 @@ describe('admin tracking event definitions', () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
expect(options.find(({ key }) => key === 'work_play_start')?.title).toBe(
|
expect(options.find(({ key }) => key === 'work_play_start')?.title).toBe(
|
||||||
'作品开始游玩',
|
'work_play_start',
|
||||||
);
|
);
|
||||||
expect(options.find(({ key }) => key === 'unknown_event')?.title).toBe(
|
expect(options.find(({ key }) => key === 'unknown_event')?.title).toBe(
|
||||||
'未知事件',
|
'未知事件',
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
filterAdminTrackingEventKeyOptions(options, '作品').map(({ key }) => key),
|
filterAdminTrackingEventKeyOptions(options, 'work_play').map(
|
||||||
|
({ key }) => key,
|
||||||
|
),
|
||||||
).toEqual(['work_play_start']);
|
).toEqual(['work_play_start']);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -65,7 +67,7 @@ describe('admin tracking event definitions', () => {
|
|||||||
).toEqual(['asset_upload_ticket_create']);
|
).toEqual(['asset_upload_ticket_create']);
|
||||||
expect(
|
expect(
|
||||||
filterAdminTrackingEventDefinitions('work_play').map(({ key }) => key),
|
filterAdminTrackingEventDefinitions('work_play').map(({ key }) => key),
|
||||||
).toEqual(['work_play_start']);
|
).toEqual([]);
|
||||||
expect(findAdminTrackingEventDefinition(' daily_login ')?.title).toBe(
|
expect(findAdminTrackingEventDefinition(' daily_login ')?.title).toBe(
|
||||||
'每日登录',
|
'每日登录',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -148,42 +148,6 @@ export const adminTrackingEventDefinitions: AdminTrackingEventDefinition[] = [
|
|||||||
scopeKind: 'user',
|
scopeKind: 'user',
|
||||||
remark: '领取个人任务奖励成功后记录。',
|
remark: '领取个人任务奖励成功后记录。',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'save_archive_list_view',
|
|
||||||
title: '存档列表查看',
|
|
||||||
scopeKind: 'user',
|
|
||||||
remark: '读取个人存档列表成功后记录。',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'save_archive_detail_view',
|
|
||||||
title: '存档详情查看',
|
|
||||||
scopeKind: 'user',
|
|
||||||
remark: '读取个人存档详情成功后记录。',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'browse_history_view',
|
|
||||||
title: '浏览历史查看',
|
|
||||||
scopeKind: 'user',
|
|
||||||
remark: '读取浏览历史成功后记录。',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'browse_history_record',
|
|
||||||
title: '浏览历史写入',
|
|
||||||
scopeKind: 'user',
|
|
||||||
remark: '记录浏览历史成功后记录。',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'browse_history_clear',
|
|
||||||
title: '浏览历史清空',
|
|
||||||
scopeKind: 'user',
|
|
||||||
remark: '清空浏览历史成功后记录。',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'play_stats_view',
|
|
||||||
title: '游玩统计查看',
|
|
||||||
scopeKind: 'user',
|
|
||||||
remark: '读取个人游玩统计成功后记录。',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'profile_analytics_metric_view',
|
key: 'profile_analytics_metric_view',
|
||||||
title: '个人指标查看',
|
title: '个人指标查看',
|
||||||
@@ -352,62 +316,6 @@ export const adminTrackingEventDefinitions: AdminTrackingEventDefinition[] = [
|
|||||||
scopeKind: 'user',
|
scopeKind: 'user',
|
||||||
remark: '更新运行设置成功后记录。',
|
remark: '更新运行设置成功后记录。',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'runtime_snapshot_view',
|
|
||||||
title: '运行快照查看',
|
|
||||||
scopeKind: 'user',
|
|
||||||
remark: '读取运行快照成功后记录。',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'runtime_snapshot_save',
|
|
||||||
title: '运行快照保存',
|
|
||||||
scopeKind: 'user',
|
|
||||||
remark: '保存运行快照成功后记录。',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'runtime_snapshot_delete',
|
|
||||||
title: '运行快照删除',
|
|
||||||
scopeKind: 'user',
|
|
||||||
remark: '删除运行快照成功后记录。',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'puzzle_route_success',
|
|
||||||
title: '拼图路由成功',
|
|
||||||
scopeKind: 'user',
|
|
||||||
remark: '拼图运行或创作接口成功响应后兜底记录;GET 入口可能按 site 统计。',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'match3d_route_success',
|
|
||||||
title: '抓大鹅路由成功',
|
|
||||||
scopeKind: 'user',
|
|
||||||
remark:
|
|
||||||
'抓大鹅创作或运行接口成功响应后兜底记录;GET 入口可能按 site 统计。',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'square_hole_route_success',
|
|
||||||
title: '方洞路由成功',
|
|
||||||
scopeKind: 'user',
|
|
||||||
remark: '方洞创作或运行接口成功响应后兜底记录;GET 入口可能按 site 统计。',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'custom_world_route_success',
|
|
||||||
title: '自定义世界路由成功',
|
|
||||||
scopeKind: 'user',
|
|
||||||
remark: '自定义世界运行接口成功响应后兜底记录;GET 入口可能按 site 统计。',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'creative_agent_route_success',
|
|
||||||
title: '创意 Agent 路由成功',
|
|
||||||
scopeKind: 'user',
|
|
||||||
remark: '创意 Agent 接口成功响应后兜底记录;GET 入口可能按 site 统计。',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'work_play_start',
|
|
||||||
title: '作品开始游玩',
|
|
||||||
scopeKind: 'work',
|
|
||||||
remark:
|
|
||||||
'拼图、抓大鹅、方洞、自定义世界、大鱼吃小鱼、Visual Novel 正式开始游玩时记录。',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const adminProfileTaskTrackingEventDefinitions =
|
export const adminProfileTaskTrackingEventDefinitions =
|
||||||
|
|||||||
@@ -15,5 +15,12 @@
|
|||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"types": ["vite/client"]
|
"types": ["vite/client"]
|
||||||
},
|
},
|
||||||
"include": ["src", "vite.config.ts"]
|
"include": ["src", "vite.config.ts"],
|
||||||
|
"exclude": [
|
||||||
|
"src/pages/AdminCreationEntrySwitchPage.tsx",
|
||||||
|
"src/pages/AdminCreationEntrySwitchPage.test.tsx",
|
||||||
|
"src/pages/AdminGrayReleaseConfigPage.tsx",
|
||||||
|
"src/pages/AdminGrayReleaseConfigPage.test.tsx",
|
||||||
|
"src/pages/AdminWorkVisibilityPage.tsx"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ npm run container:up -- spacetimedb
|
|||||||
spacetime publish genarrative-loadtest --server http://127.0.0.1:13101 --module-path server-rs/crates/spacetime-module --yes --build-options="--debug"
|
spacetime publish genarrative-loadtest --server http://127.0.0.1:13101 --module-path server-rs/crates/spacetime-module --yes --build-options="--debug"
|
||||||
npm run container:up
|
npm run container:up
|
||||||
npm run container:ps
|
npm run container:ps
|
||||||
curl -sS http://127.0.0.1:18080/api/runtime/puzzle/gallery
|
curl -sS -H 'Authorization: Bearer <access-token>' \
|
||||||
|
'http://127.0.0.1:18080/api/assets/history?kind=character_visual'
|
||||||
```
|
```
|
||||||
|
|
||||||
查看日志:
|
查看日志:
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ services:
|
|||||||
soft: 4096
|
soft: 4096
|
||||||
hard: 4096
|
hard: 4096
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/api/runtime/puzzle/gallery"]
|
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 12
|
retries: 12
|
||||||
|
|||||||
@@ -88,85 +88,6 @@ http {
|
|||||||
try_files $uri =404;
|
try_files $uri =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
location = /api/runtime/puzzle/gallery {
|
|
||||||
default_type application/json;
|
|
||||||
limit_conn genarrative_api_conn 320;
|
|
||||||
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
|
|
||||||
|
|
||||||
proxy_pass http://genarrative_api;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_read_timeout 3600s;
|
|
||||||
proxy_send_timeout 3600s;
|
|
||||||
add_header X-Accel-Buffering no always;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
proxy_set_header X-Request-Id $request_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /api/runtime/custom-world-gallery {
|
|
||||||
default_type application/json;
|
|
||||||
limit_conn genarrative_api_conn 320;
|
|
||||||
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
|
|
||||||
|
|
||||||
proxy_pass http://genarrative_api;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_read_timeout 3600s;
|
|
||||||
proxy_send_timeout 3600s;
|
|
||||||
add_header X-Accel-Buffering no always;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
proxy_set_header X-Request-Id $request_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ ^/api/runtime/puzzle/gallery/[^/]+$ {
|
|
||||||
default_type application/json;
|
|
||||||
limit_conn genarrative_api_conn 32;
|
|
||||||
limit_req zone=genarrative_api_rps burst=32 nodelay;
|
|
||||||
|
|
||||||
proxy_pass http://genarrative_api;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_read_timeout 3600s;
|
|
||||||
proxy_send_timeout 3600s;
|
|
||||||
add_header X-Accel-Buffering no always;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
proxy_set_header X-Request-Id $request_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ ^/api/runtime/custom-world-gallery/[^/]+/[^/]+$ {
|
|
||||||
default_type application/json;
|
|
||||||
limit_conn genarrative_api_conn 32;
|
|
||||||
limit_req zone=genarrative_api_rps burst=32 nodelay;
|
|
||||||
|
|
||||||
proxy_pass http://genarrative_api;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_read_timeout 3600s;
|
|
||||||
proxy_send_timeout 3600s;
|
|
||||||
add_header X-Accel-Buffering no always;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
proxy_set_header X-Request-Id $request_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ ^/api(?:/|$) {
|
location ~ ^/api(?:/|$) {
|
||||||
default_type application/json;
|
default_type application/json;
|
||||||
@@ -220,7 +141,7 @@ http {
|
|||||||
try_files /index.html =404;
|
try_files /index.html =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~* "^/(?:bark-battle|big-fish|child-motion-demo|creation|creation/baby-object-match|creation/baby-object-match/generating|creation/baby-object-match/result|creation/bark-battle|creation/bark-battle/generating|creation/bark-battle/result|creation/big-fish|creation/big-fish/generating|creation/big-fish/result|creation/creative-agent|creation/jump-hop|creation/jump-hop/generating|creation/jump-hop/result|creation/match3d|creation/match3d/generating|creation/match3d/result|creation/puzzle|creation/puzzle-clear|creation/puzzle-clear/generating|creation/puzzle-clear/result|creation/puzzle/generating|creation/puzzle/result|creation/rpg|creation/rpg/agent|creation/rpg/generating|creation/rpg/result|creation/square-hole|creation/square-hole/generating|creation/square-hole/result|creation/visual-novel|creation/visual-novel/generating|creation/visual-novel/result|creation/wooden-fish|creation/wooden-fish/generating|creation/wooden-fish/result|editor/canvas|gallery/jump-hop/detail|gallery/puzzle/detail|gallery/visual-novel/detail|match3d|project|puzzle|runtime/baby-love-drawing|runtime/baby-object-match|runtime/bark-battle|runtime/big-fish|runtime/jump-hop|runtime/match3d|runtime/puzzle|runtime/puzzle-clear|runtime/rpg/adventure|runtime/rpg/characters|runtime/square-hole|runtime/visual-novel|runtime/wooden-fish|works/detail|worlds/detail)/?$" {
|
location ~* "^/(?:editor/canvas|project)/?$" {
|
||||||
try_files $uri /index.html =404;
|
try_files $uri /index.html =404;
|
||||||
}
|
}
|
||||||
# END GENARRATIVE MAIN SPA ROUTES
|
# END GENARRATIVE MAIN SPA ROUTES
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
## gzip
|
## gzip
|
||||||
|
|
||||||
- `deploy/nginx/genarrative.conf` 与 `deploy/nginx/genarrative-dev-http.conf` 默认开启 gzip。
|
- `deploy/nginx/genarrative.conf` 与 `deploy/nginx/genarrative-dev-http.conf` 默认开启 gzip。
|
||||||
- 覆盖 `application/json`,用于降低 `/api/runtime/*/gallery` 这类 JSON 列表接口的公网带宽占用。
|
- 覆盖 `application/json`,用于降低 `/api/assets/history` 这类 JSON 列表接口的公网带宽占用。
|
||||||
- 当前推荐等级为 `gzip_comp_level 5`,兼顾 2C/2G 服务器 CPU 与压缩收益。
|
- 当前推荐等级为 `gzip_comp_level 5`,兼顾 2C/2G 服务器 CPU 与压缩收益。
|
||||||
- Pingora 影子网关已通过 `GENARRATIVE_PINGORA_GATEWAY_COMPRESSION_ALGORITHMS=gzip`、`GENARRATIVE_PINGORA_GATEWAY_GZIP_ENABLED=true`、`GENARRATIVE_PINGORA_GATEWAY_GZIP_LEVEL=5` 与 `GENARRATIVE_PINGORA_GATEWAY_GZIP_MIN_LENGTH_BYTES=1024` 承接 gzip parity;网关会把压缩算法收敛到 gzip allowlist,并按 `gzip_min_length 1024` 避免压缩小响应,同时沿用 Nginx `gzip_types` 边界不压缩图片资源。改动后必须运行 `npm run check:pingora-gateway-smoke` 验证小响应不压缩、图片资源不压缩,以及 `Accept-Encoding: gzip` 和 `Accept-Encoding: br, gzip` 的大响应都返回 gzip。
|
- Pingora 影子网关已通过 `GENARRATIVE_PINGORA_GATEWAY_COMPRESSION_ALGORITHMS=gzip`、`GENARRATIVE_PINGORA_GATEWAY_GZIP_ENABLED=true`、`GENARRATIVE_PINGORA_GATEWAY_GZIP_LEVEL=5` 与 `GENARRATIVE_PINGORA_GATEWAY_GZIP_MIN_LENGTH_BYTES=1024` 承接 gzip parity;网关会把压缩算法收敛到 gzip allowlist,并按 `gzip_min_length 1024` 避免压缩小响应,同时沿用 Nginx `gzip_types` 边界不压缩图片资源。改动后必须运行 `npm run check:pingora-gateway-smoke` 验证小响应不压缩、图片资源不压缩,以及 `Accept-Encoding: gzip` 和 `Accept-Encoding: br, gzip` 的大响应都返回 gzip。
|
||||||
|
|
||||||
@@ -65,14 +65,14 @@
|
|||||||
## Pingora canary
|
## Pingora canary
|
||||||
|
|
||||||
- `deploy/nginx/snippets/genarrative-pingora-canary.conf` 是默认不启用的人工前缀 canary 入口,只在需要验证 Nginx -> Pingora handoff 时临时 include。它使用 `/__genarrative_pingora_canary/` 前缀改写后转发到 `127.0.0.1:18081`,并返回 `X-Genarrative-Nginx-Handoff: pingora-canary`。
|
- `deploy/nginx/snippets/genarrative-pingora-canary.conf` 是默认不启用的人工前缀 canary 入口,只在需要验证 Nginx -> Pingora handoff 时临时 include。它使用 `/__genarrative_pingora_canary/` 前缀改写后转发到 `127.0.0.1:18081`,并返回 `X-Genarrative-Nginx-Handoff: pingora-canary`。
|
||||||
- `deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf` 是前缀 canary 之后、direct 直连之前的真实路径 canary。它只能在 Nginx `http` 上下文人工 include,默认监听 `127.0.0.1:18083`,覆盖 `/api/creation-entry/config`、`/v1/identity`、`/v1/database/<db>/subscribe`、`/assets/app.js` 和拒绝入口,并写入独立 `/var/log/nginx/genarrative-pingora-realpath-canary.access.log`;不要把它 include 到生产 `443` server 内作为 location 覆盖。
|
- `deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf` 是前缀 canary 之后、direct 直连之前的真实路径 canary。它只能在 Nginx `http` 上下文人工 include,默认监听 `127.0.0.1:18083`,覆盖 `/api/assets/history`、`/v1/identity`、`/v1/database/<db>/subscribe`、`/assets/app.js` 和拒绝入口,并写入独立 `/var/log/nginx/genarrative-pingora-realpath-canary.access.log`;不要把它 include 到生产 `443` server 内作为 location 覆盖。
|
||||||
- 真实路径 canary 片段使用 `access_log ... genarrative_upstream`,如果以独立文件放入 `/etc/nginx/conf.d/`,文件加载顺序必须晚于定义 `log_format genarrative_upstream` 的主站配置。当前目标机可使用 `/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf`;若站点把 `log_format` 移到全局 Nginx 配置,则需确保它仍在所有 `conf.d` server include 之前加载。启用前必须先跑 `nginx -t`,不要用会早于 `genarrative.conf` 加载的文件名。
|
- 真实路径 canary 片段使用 `access_log ... genarrative_upstream`,如果以独立文件放入 `/etc/nginx/conf.d/`,文件加载顺序必须晚于定义 `log_format genarrative_upstream` 的主站配置。当前目标机可使用 `/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf`;若站点把 `log_format` 移到全局 Nginx 配置,则需确保它仍在所有 `conf.d` server include 之前加载。启用前必须先跑 `nginx -t`,不要用会早于 `genarrative.conf` 加载的文件名。
|
||||||
- 启用 Nginx canary 前,目标机必须已经有持久 `genarrative-pingora-gateway.service` 运行在 `127.0.0.1:18081`,且带真实 `api-server`、SpacetimeDB、静态目录和 `/var/log/genarrative/pingora-gateway.access.log` 完成本机 shadow 验收;不要从一次性 `/tmp` 网关进程直接切到 Nginx handoff。
|
- 启用 Nginx canary 前,目标机必须已经有持久 `genarrative-pingora-gateway.service` 运行在 `127.0.0.1:18081`,且带真实 `api-server`、SpacetimeDB、静态目录和 `/var/log/genarrative/pingora-gateway.access.log` 完成本机 shadow 验收;不要从一次性 `/tmp` 网关进程直接切到 Nginx handoff。
|
||||||
- 启用前必须运行 `npm run check:nginx-pingora-canary`;目标 agent 有 Nginx 时运行 `node scripts/check-nginx-pingora-canary.mjs --require-nginx`,同时做两个 snippet 的静态护栏和真实 `nginx -t`。本机或 CI 可运行 `npm run check:pingora-canary-docker`,用 Docker Nginx、真实 Pingora 和 mock 上游复现前缀 canary 与真实路径 canary 两条 handoff 链路。
|
- 启用前必须运行 `npm run check:nginx-pingora-canary`;目标 agent 有 Nginx 时运行 `node scripts/check-nginx-pingora-canary.mjs --require-nginx`,同时做两个 snippet 的静态护栏和真实 `nginx -t`。本机或 CI 可运行 `npm run check:pingora-canary-docker`,用 Docker Nginx、真实 Pingora 和 mock 上游复现前缀 canary 与真实路径 canary 两条 handoff 链路。
|
||||||
- 前缀 canary reload 后运行 `GENARRATIVE_PINGORA_CANARY_BASE_URL=http://127.0.0.1 GENARRATIVE_PINGORA_CANARY_HOST=<域名> npm run check:pingora-canary-live`,再用 `scripts/check-pingora-canary-access-log-parity.mjs` 对账 `/var/log/nginx/genarrative.access.log` 与 `/var/log/genarrative/pingora-gateway.access.log`。
|
- 前缀 canary reload 后运行 `GENARRATIVE_PINGORA_CANARY_BASE_URL=http://127.0.0.1 GENARRATIVE_PINGORA_CANARY_HOST=<域名> npm run check:pingora-canary-live`,再用 `scripts/check-pingora-canary-access-log-parity.mjs` 对账 `/var/log/nginx/genarrative.access.log` 与 `/var/log/genarrative/pingora-gateway.access.log`。
|
||||||
- 真实路径 canary 不再手工编辑 `/etc/nginx/conf.d/`。目标机从 current release 执行 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-enable.sh --apply --probe-token <token> --host <域名> --base-url http://127.0.0.1:18083`;脚本会渲染到 `/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf`,保持晚于主站 `log_format genarrative_upstream` 加载,执行 `nginx -t`、reload Nginx,并默认运行 realpath live smoke。`nginx -t`、reload 或 live smoke 任一失败都会恢复写入前配置。关闭时执行 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-disable.sh --apply`,脚本会删除该 `zz-...` 文件,执行 `nginx -t` 和 reload,失败时恢复删除前配置。
|
- 真实路径 canary 不再手工编辑 `/etc/nginx/conf.d/`。目标机从 current release 执行 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-enable.sh --apply --probe-token <token> --host <域名> --base-url http://127.0.0.1:18083`;脚本会渲染到 `/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf`,保持晚于主站 `log_format genarrative_upstream` 加载,执行 `nginx -t`、reload Nginx,并默认运行 realpath live smoke。`nginx -t`、reload 或 live smoke 任一失败都会恢复写入前配置。关闭时执行 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-disable.sh --apply`,脚本会删除该 `zz-...` 文件,执行 `nginx -t` 和 reload,失败时恢复删除前配置。
|
||||||
- `npm run check:pingora-release-readiness` 默认包含 `npm run check:pingora-realpath-canary-toggle`,用临时 Nginx 目录和 fake `nginx` / `systemctl` / live smoke 验证 realpath canary 启停脚本默认 dry-run、apply 写入、失败回滚和 disable 恢复逻辑;不再把这条脚本护栏留成单独可选项。
|
- `npm run check:pingora-release-readiness` 默认包含 `npm run check:pingora-realpath-canary-toggle`,用临时 Nginx 目录和 fake `nginx` / `systemctl` / live smoke 验证 realpath canary 启停脚本默认 dry-run、apply 写入、失败回滚和 disable 恢复逻辑;不再把这条脚本护栏留成单独可选项。
|
||||||
- 真实路径 canary reload 后可再次运行 `node -- /opt/genarrative/current/scripts/check-pingora-canary-live.mjs --realpath --base-url http://127.0.0.1:18083 --host <域名>`,再运行 `node -- /opt/genarrative/current/scripts/check-pingora-canary-access-log-parity.mjs --realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log --pingora-log-file /var/log/genarrative/pingora-gateway.access.log --path /__genarrative_pingora_realpath_canary/healthz --path /api/creation-entry/config --path /v1/identity --path /assets/app.js`。真实路径模式除 healthz 探针外要求 Nginx path 与 Pingora path 完全一致。
|
- 真实路径 canary reload 后可再次运行 `node -- /opt/genarrative/current/scripts/check-pingora-canary-live.mjs --realpath --base-url http://127.0.0.1:18083 --host <域名>`,再运行 `node -- /opt/genarrative/current/scripts/check-pingora-canary-access-log-parity.mjs --realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log --pingora-log-file /var/log/genarrative/pingora-gateway.access.log --path /__genarrative_pingora_realpath_canary/healthz --path /api/assets/history --path /v1/identity --path /assets/app.js`。真实路径模式除 healthz 探针外要求 Nginx path 与 Pingora path 完全一致。
|
||||||
- 正式切换前在源码 checkout / CI / 构建环境跑聚合门禁,并按目标机已启用的 canary 入口选择参数:前缀 canary 已启用时使用 `node scripts/check-pingora-release-readiness.mjs --require-docker --pull-docker --require-nginx --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;真实路径 canary 已启用时追加或单独使用 `--require-realpath-live --realpath-live-base-url http://127.0.0.1:18083 --realpath-live-host <域名> --realpath-live-nginx-access-log /var/log/nginx/genarrative-pingora-realpath-canary.access.log --realpath-live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`。如果现场只启用了真实路径 canary,不要同时传 `--require-live`。
|
- 正式切换前在源码 checkout / CI / 构建环境跑聚合门禁,并按目标机已启用的 canary 入口选择参数:前缀 canary 已启用时使用 `node scripts/check-pingora-release-readiness.mjs --require-docker --pull-docker --require-nginx --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;真实路径 canary 已启用时追加或单独使用 `--require-realpath-live --realpath-live-base-url http://127.0.0.1:18083 --realpath-live-host <域名> --realpath-live-nginx-access-log /var/log/nginx/genarrative-pingora-realpath-canary.access.log --realpath-live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`。如果现场只启用了真实路径 canary,不要同时传 `--require-live`。
|
||||||
- 目标机 `/opt/genarrative/current` 只能跑随包 runtime-only 门禁,并同样按现场已启用入口选择:前缀 canary 用 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;真实路径 canary 用 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only --require-realpath-live --realpath-live-base-url http://127.0.0.1:18083 --realpath-live-host <域名> --realpath-live-nginx-access-log /var/log/nginx/genarrative-pingora-realpath-canary.access.log --realpath-live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;验证 Pingora 直连入口时再追加 `--require-direct ...` 参数。未带 `--require-direct` 的 runtime-only 基础门禁会自动执行直连彩排状态复核,确认公网 `80/443` 仍由 Nginx 接流、Pingora shadow `127.0.0.1:18081`、realpath canary `127.0.0.1:18083` 和 current release 自审均通过;启用后 `--require-direct` 复核不再要求 Nginx 接公网彩排状态。生成正式直连切换 runbook 时,`--direct-redirect-host` 和回退 smoke Host 必须与 `--direct-host` 同 hostname,只允许端口不同。
|
- 目标机 `/opt/genarrative/current` 只能跑随包 runtime-only 门禁,并同样按现场已启用入口选择:前缀 canary 用 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;真实路径 canary 用 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only --require-realpath-live --realpath-live-base-url http://127.0.0.1:18083 --realpath-live-host <域名> --realpath-live-nginx-access-log /var/log/nginx/genarrative-pingora-realpath-canary.access.log --realpath-live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;验证 Pingora 直连入口时再追加 `--require-direct ...` 参数。未带 `--require-direct` 的 runtime-only 基础门禁会自动执行直连彩排状态复核,确认公网 `80/443` 仍由 Nginx 接流、Pingora shadow `127.0.0.1:18081`、realpath canary `127.0.0.1:18083` 和 current release 自审均通过;启用后 `--require-direct` 复核不再要求 Nginx 接公网彩排状态。生成正式直连切换 runbook 时,`--direct-redirect-host` 和回退 smoke Host 必须与 `--direct-host` 同 hostname,只允许端口不同。
|
||||||
|
|
||||||
@@ -84,11 +84,13 @@ ls -l /etc/nginx/modules-enabled/*brotli*
|
|||||||
nginx -T 2>/dev/null | grep -Ei 'brotli|load_module'
|
nginx -T 2>/dev/null | grep -Ei 'brotli|load_module'
|
||||||
|
|
||||||
curl -sSI -H 'Accept-Encoding: gzip' \
|
curl -sSI -H 'Accept-Encoding: gzip' \
|
||||||
http://<host>/api/runtime/puzzle/gallery \
|
-H 'Authorization: Bearer <access-token>' \
|
||||||
|
'http://<host>/api/assets/history?kind=character_visual' \
|
||||||
| grep -iE 'content-encoding|vary|content-type|content-length'
|
| grep -iE 'content-encoding|vary|content-type|content-length'
|
||||||
|
|
||||||
curl -sSI -H 'Accept-Encoding: br' \
|
curl -sSI -H 'Accept-Encoding: br' \
|
||||||
http://<host>/api/runtime/puzzle/gallery \
|
-H 'Authorization: Bearer <access-token>' \
|
||||||
|
'http://<host>/api/assets/history?kind=character_visual' \
|
||||||
| grep -iE 'content-encoding|vary|content-type|content-length'
|
| grep -iE 'content-encoding|vary|content-type|content-length'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -116,101 +116,6 @@ server {
|
|||||||
try_files $uri =404;
|
try_files $uri =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
location = /api/runtime/puzzle/gallery {
|
|
||||||
default_type application/json;
|
|
||||||
limit_conn genarrative_api_conn 320;
|
|
||||||
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
|
|
||||||
|
|
||||||
if ($genarrative_maintenance) {
|
|
||||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_pass http://genarrative_api;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_read_timeout 3600s;
|
|
||||||
proxy_send_timeout 3600s;
|
|
||||||
add_header X-Accel-Buffering no always;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
proxy_set_header X-Request-Id $request_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /api/runtime/custom-world-gallery {
|
|
||||||
default_type application/json;
|
|
||||||
limit_conn genarrative_api_conn 320;
|
|
||||||
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
|
|
||||||
|
|
||||||
if ($genarrative_maintenance) {
|
|
||||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_pass http://genarrative_api;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_read_timeout 3600s;
|
|
||||||
proxy_send_timeout 3600s;
|
|
||||||
add_header X-Accel-Buffering no always;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
proxy_set_header X-Request-Id $request_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ ^/api/runtime/puzzle/gallery/[^/]+$ {
|
|
||||||
default_type application/json;
|
|
||||||
limit_conn genarrative_api_conn 32;
|
|
||||||
limit_req zone=genarrative_api_rps burst=32 nodelay;
|
|
||||||
|
|
||||||
if ($genarrative_maintenance) {
|
|
||||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_pass http://genarrative_api;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_read_timeout 3600s;
|
|
||||||
proxy_send_timeout 3600s;
|
|
||||||
add_header X-Accel-Buffering no always;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
proxy_set_header X-Request-Id $request_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ ^/api/runtime/custom-world-gallery/[^/]+/[^/]+$ {
|
|
||||||
default_type application/json;
|
|
||||||
limit_conn genarrative_api_conn 32;
|
|
||||||
limit_req zone=genarrative_api_rps burst=32 nodelay;
|
|
||||||
|
|
||||||
if ($genarrative_maintenance) {
|
|
||||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_pass http://genarrative_api;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_read_timeout 3600s;
|
|
||||||
proxy_send_timeout 3600s;
|
|
||||||
add_header X-Accel-Buffering no always;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
proxy_set_header X-Request-Id $request_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 临时兼容主站仍在使用的 /api/* HTTP facade;前端完成 SpacetimeDB SDK 迁移后删除。
|
# 临时兼容主站仍在使用的 /api/* HTTP facade;前端完成 SpacetimeDB SDK 迁移后删除。
|
||||||
location ~ ^/api(?:/|$) {
|
location ~ ^/api(?:/|$) {
|
||||||
@@ -285,7 +190,7 @@ server {
|
|||||||
try_files /index.html =404;
|
try_files /index.html =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~* "^/(?:bark-battle|big-fish|child-motion-demo|creation|creation/baby-object-match|creation/baby-object-match/generating|creation/baby-object-match/result|creation/bark-battle|creation/bark-battle/generating|creation/bark-battle/result|creation/big-fish|creation/big-fish/generating|creation/big-fish/result|creation/creative-agent|creation/jump-hop|creation/jump-hop/generating|creation/jump-hop/result|creation/match3d|creation/match3d/generating|creation/match3d/result|creation/puzzle|creation/puzzle-clear|creation/puzzle-clear/generating|creation/puzzle-clear/result|creation/puzzle/generating|creation/puzzle/result|creation/rpg|creation/rpg/agent|creation/rpg/generating|creation/rpg/result|creation/square-hole|creation/square-hole/generating|creation/square-hole/result|creation/visual-novel|creation/visual-novel/generating|creation/visual-novel/result|creation/wooden-fish|creation/wooden-fish/generating|creation/wooden-fish/result|editor/canvas|gallery/jump-hop/detail|gallery/puzzle/detail|gallery/visual-novel/detail|match3d|project|puzzle|runtime/baby-love-drawing|runtime/baby-object-match|runtime/bark-battle|runtime/big-fish|runtime/jump-hop|runtime/match3d|runtime/puzzle|runtime/puzzle-clear|runtime/rpg/adventure|runtime/rpg/characters|runtime/square-hole|runtime/visual-novel|runtime/wooden-fish|works/detail|worlds/detail)/?$" {
|
location ~* "^/(?:editor/canvas|project)/?$" {
|
||||||
error_page 503 /maintenance.html;
|
error_page 503 /maintenance.html;
|
||||||
|
|
||||||
if ($genarrative_maintenance) {
|
if ($genarrative_maintenance) {
|
||||||
|
|||||||
@@ -136,101 +136,6 @@ server {
|
|||||||
try_files $uri =404;
|
try_files $uri =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
location = /api/runtime/puzzle/gallery {
|
|
||||||
default_type application/json;
|
|
||||||
limit_conn genarrative_api_conn 320;
|
|
||||||
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
|
|
||||||
|
|
||||||
if ($genarrative_maintenance) {
|
|
||||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_pass http://genarrative_api;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_read_timeout 3600s;
|
|
||||||
proxy_send_timeout 3600s;
|
|
||||||
add_header X-Accel-Buffering no always;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
proxy_set_header X-Request-Id $request_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /api/runtime/custom-world-gallery {
|
|
||||||
default_type application/json;
|
|
||||||
limit_conn genarrative_api_conn 320;
|
|
||||||
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
|
|
||||||
|
|
||||||
if ($genarrative_maintenance) {
|
|
||||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_pass http://genarrative_api;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_read_timeout 3600s;
|
|
||||||
proxy_send_timeout 3600s;
|
|
||||||
add_header X-Accel-Buffering no always;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
proxy_set_header X-Request-Id $request_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ ^/api/runtime/puzzle/gallery/[^/]+$ {
|
|
||||||
default_type application/json;
|
|
||||||
limit_conn genarrative_api_conn 32;
|
|
||||||
limit_req zone=genarrative_api_rps burst=32 nodelay;
|
|
||||||
|
|
||||||
if ($genarrative_maintenance) {
|
|
||||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_pass http://genarrative_api;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_read_timeout 3600s;
|
|
||||||
proxy_send_timeout 3600s;
|
|
||||||
add_header X-Accel-Buffering no always;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
proxy_set_header X-Request-Id $request_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ ^/api/runtime/custom-world-gallery/[^/]+/[^/]+$ {
|
|
||||||
default_type application/json;
|
|
||||||
limit_conn genarrative_api_conn 32;
|
|
||||||
limit_req zone=genarrative_api_rps burst=32 nodelay;
|
|
||||||
|
|
||||||
if ($genarrative_maintenance) {
|
|
||||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_pass http://genarrative_api;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_read_timeout 3600s;
|
|
||||||
proxy_send_timeout 3600s;
|
|
||||||
add_header X-Accel-Buffering no always;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
proxy_set_header X-Request-Id $request_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 临时兼容主站仍在使用的 /api/* HTTP facade;前端完成 SpacetimeDB SDK 迁移后删除。
|
# 临时兼容主站仍在使用的 /api/* HTTP facade;前端完成 SpacetimeDB SDK 迁移后删除。
|
||||||
location ~ ^/api(?:/|$) {
|
location ~ ^/api(?:/|$) {
|
||||||
@@ -305,7 +210,7 @@ server {
|
|||||||
try_files /index.html =404;
|
try_files /index.html =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~* "^/(?:bark-battle|big-fish|child-motion-demo|creation|creation/baby-object-match|creation/baby-object-match/generating|creation/baby-object-match/result|creation/bark-battle|creation/bark-battle/generating|creation/bark-battle/result|creation/big-fish|creation/big-fish/generating|creation/big-fish/result|creation/creative-agent|creation/jump-hop|creation/jump-hop/generating|creation/jump-hop/result|creation/match3d|creation/match3d/generating|creation/match3d/result|creation/puzzle|creation/puzzle-clear|creation/puzzle-clear/generating|creation/puzzle-clear/result|creation/puzzle/generating|creation/puzzle/result|creation/rpg|creation/rpg/agent|creation/rpg/generating|creation/rpg/result|creation/square-hole|creation/square-hole/generating|creation/square-hole/result|creation/visual-novel|creation/visual-novel/generating|creation/visual-novel/result|creation/wooden-fish|creation/wooden-fish/generating|creation/wooden-fish/result|editor/canvas|gallery/jump-hop/detail|gallery/puzzle/detail|gallery/visual-novel/detail|match3d|project|puzzle|runtime/baby-love-drawing|runtime/baby-object-match|runtime/bark-battle|runtime/big-fish|runtime/jump-hop|runtime/match3d|runtime/puzzle|runtime/puzzle-clear|runtime/rpg/adventure|runtime/rpg/characters|runtime/square-hole|runtime/visual-novel|runtime/wooden-fish|works/detail|worlds/detail)/?$" {
|
location ~* "^/(?:editor/canvas|project)/?$" {
|
||||||
error_page 503 /maintenance.html;
|
error_page 503 /maintenance.html;
|
||||||
|
|
||||||
if ($genarrative_maintenance) {
|
if ($genarrative_maintenance) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ server {
|
|||||||
proxy_pass http://127.0.0.1:18081/__genarrative_pingora/healthz;
|
proxy_pass http://127.0.0.1:18081/__genarrative_pingora/healthz;
|
||||||
}
|
}
|
||||||
|
|
||||||
location = /api/creation-entry/config {
|
location = /api/assets/history {
|
||||||
default_type application/json;
|
default_type application/json;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
|
|||||||
@@ -16,7 +16,9 @@
|
|||||||
"root /var/www/html;"
|
"root /var/www/html;"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/.well-known/acme-challenge/*`"]
|
"docs": [
|
||||||
|
"`/.well-known/acme-challenge/*`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "shadow_probe",
|
"id": "shadow_probe",
|
||||||
@@ -24,7 +26,9 @@
|
|||||||
"expect": {
|
"expect": {
|
||||||
"kind": "shadow_probe"
|
"kind": "shadow_probe"
|
||||||
},
|
},
|
||||||
"docs": ["`/__genarrative_pingora/healthz`"]
|
"docs": [
|
||||||
|
"`/__genarrative_pingora/healthz`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "admin_redirect",
|
"id": "admin_redirect",
|
||||||
@@ -34,10 +38,19 @@
|
|||||||
"location": "/admin/"
|
"location": "/admin/"
|
||||||
},
|
},
|
||||||
"nginx": {
|
"nginx": {
|
||||||
"production": ["location = /admin", "return 301 /admin/;"],
|
"production": [
|
||||||
"development": ["location = /admin", "return 301 /admin/;"]
|
"location = /admin",
|
||||||
|
"return 301 /admin/;"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"location = /admin",
|
||||||
|
"return 301 /admin/;"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/admin`", "301 到 `/admin/`"]
|
"docs": [
|
||||||
|
"`/admin`",
|
||||||
|
"301 到 `/admin/`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "admin_api_proxy",
|
"id": "admin_api_proxy",
|
||||||
@@ -62,7 +75,9 @@
|
|||||||
"limit_req zone=genarrative_admin_rps burst=16 nodelay;"
|
"limit_req zone=genarrative_admin_rps burst=16 nodelay;"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/admin/api/*`"]
|
"docs": [
|
||||||
|
"`/admin/api/*`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "admin_assets",
|
"id": "admin_assets",
|
||||||
@@ -73,10 +88,18 @@
|
|||||||
"mode": "exact"
|
"mode": "exact"
|
||||||
},
|
},
|
||||||
"nginx": {
|
"nginx": {
|
||||||
"production": ["location ^~ /admin/assets/", "try_files $uri =404;"],
|
"production": [
|
||||||
"development": ["location ^~ /admin/assets/", "try_files $uri =404;"]
|
"location ^~ /admin/assets/",
|
||||||
|
"try_files $uri =404;"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"location ^~ /admin/assets/",
|
||||||
|
"try_files $uri =404;"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/admin/assets/*`"]
|
"docs": [
|
||||||
|
"`/admin/assets/*`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "admin_spa_fallback",
|
"id": "admin_spa_fallback",
|
||||||
@@ -96,7 +119,9 @@
|
|||||||
"try_files $uri $uri/ /admin/index.html;"
|
"try_files $uri $uri/ /admin/index.html;"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/admin/*`"]
|
"docs": [
|
||||||
|
"`/admin/*`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "web_assets",
|
"id": "web_assets",
|
||||||
@@ -106,115 +131,23 @@
|
|||||||
"root": "web",
|
"root": "web",
|
||||||
"mode": "exact"
|
"mode": "exact"
|
||||||
},
|
},
|
||||||
"nginx": {
|
|
||||||
"production": ["location ^~ /assets/", "try_files $uri =404;"],
|
|
||||||
"development": ["location ^~ /assets/", "try_files $uri =404;"]
|
|
||||||
},
|
|
||||||
"docs": ["`/assets/*`"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "puzzle_gallery_list",
|
|
||||||
"samplePath": "/api/runtime/puzzle/gallery",
|
|
||||||
"expect": {
|
|
||||||
"kind": "proxy",
|
|
||||||
"target": "api",
|
|
||||||
"bodyLimit": null,
|
|
||||||
"protectionClass": "gallery_list"
|
|
||||||
},
|
|
||||||
"nginx": {
|
"nginx": {
|
||||||
"production": [
|
"production": [
|
||||||
"location = /api/runtime/puzzle/gallery",
|
"location ^~ /assets/",
|
||||||
"limit_conn genarrative_api_conn 320;",
|
"try_files $uri =404;"
|
||||||
"limit_req zone=genarrative_gallery_rps burst=4096 nodelay;",
|
|
||||||
"add_header X-Accel-Buffering no always;"
|
|
||||||
],
|
],
|
||||||
"development": [
|
"development": [
|
||||||
"location = /api/runtime/puzzle/gallery",
|
"location ^~ /assets/",
|
||||||
"limit_conn genarrative_api_conn 320;",
|
"try_files $uri =404;"
|
||||||
"limit_req zone=genarrative_gallery_rps burst=4096 nodelay;",
|
|
||||||
"add_header X-Accel-Buffering no always;"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/api/runtime/puzzle/gallery`"]
|
"docs": [
|
||||||
},
|
"`/assets/*`"
|
||||||
{
|
]
|
||||||
"id": "custom_world_gallery_list",
|
|
||||||
"samplePath": "/api/runtime/custom-world-gallery",
|
|
||||||
"expect": {
|
|
||||||
"kind": "proxy",
|
|
||||||
"target": "api",
|
|
||||||
"bodyLimit": null,
|
|
||||||
"protectionClass": "gallery_list"
|
|
||||||
},
|
|
||||||
"nginx": {
|
|
||||||
"production": [
|
|
||||||
"location = /api/runtime/custom-world-gallery",
|
|
||||||
"limit_conn genarrative_api_conn 320;",
|
|
||||||
"limit_req zone=genarrative_gallery_rps burst=4096 nodelay;",
|
|
||||||
"add_header X-Accel-Buffering no always;"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"location = /api/runtime/custom-world-gallery",
|
|
||||||
"limit_conn genarrative_api_conn 320;",
|
|
||||||
"limit_req zone=genarrative_gallery_rps burst=4096 nodelay;",
|
|
||||||
"add_header X-Accel-Buffering no always;"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"docs": ["`/api/runtime/custom-world-gallery`"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "puzzle_gallery_detail",
|
|
||||||
"samplePath": "/api/runtime/puzzle/gallery/work-a",
|
|
||||||
"expect": {
|
|
||||||
"kind": "proxy",
|
|
||||||
"target": "api",
|
|
||||||
"bodyLimit": null,
|
|
||||||
"protectionClass": "gallery_detail"
|
|
||||||
},
|
|
||||||
"nginx": {
|
|
||||||
"production": [
|
|
||||||
"location ~ ^/api/runtime/puzzle/gallery/[^/]+$",
|
|
||||||
"limit_conn genarrative_api_conn 32;",
|
|
||||||
"limit_req zone=genarrative_api_rps burst=32 nodelay;",
|
|
||||||
"add_header X-Accel-Buffering no always;"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"location ~ ^/api/runtime/puzzle/gallery/[^/]+$",
|
|
||||||
"limit_conn genarrative_api_conn 32;",
|
|
||||||
"limit_req zone=genarrative_api_rps burst=32 nodelay;",
|
|
||||||
"add_header X-Accel-Buffering no always;"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"docs": ["`/api/runtime/puzzle/gallery/{id}`"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "custom_world_gallery_detail",
|
|
||||||
"samplePath": "/api/runtime/custom-world-gallery/profile-a/owner-b",
|
|
||||||
"expect": {
|
|
||||||
"kind": "proxy",
|
|
||||||
"target": "api",
|
|
||||||
"bodyLimit": null,
|
|
||||||
"protectionClass": "gallery_detail"
|
|
||||||
},
|
|
||||||
"nginx": {
|
|
||||||
"production": [
|
|
||||||
"location ~ ^/api/runtime/custom-world-gallery/[^/]+/[^/]+$",
|
|
||||||
"limit_conn genarrative_api_conn 32;",
|
|
||||||
"limit_req zone=genarrative_api_rps burst=32 nodelay;",
|
|
||||||
"add_header X-Accel-Buffering no always;"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"location ~ ^/api/runtime/custom-world-gallery/[^/]+/[^/]+$",
|
|
||||||
"limit_conn genarrative_api_conn 32;",
|
|
||||||
"limit_req zone=genarrative_api_rps burst=32 nodelay;",
|
|
||||||
"add_header X-Accel-Buffering no always;"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"docs": ["`/api/runtime/custom-world-gallery/{profile}/{owner}`"]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "generic_api_proxy",
|
"id": "generic_api_proxy",
|
||||||
"samplePath": "/api/creation-entry/config",
|
"samplePath": "/api/assets/history",
|
||||||
"expect": {
|
"expect": {
|
||||||
"kind": "proxy",
|
"kind": "proxy",
|
||||||
"target": "api",
|
"target": "api",
|
||||||
@@ -237,7 +170,9 @@
|
|||||||
"add_header X-Accel-Buffering no always;"
|
"add_header X-Accel-Buffering no always;"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/api`、`/api/*`"]
|
"docs": [
|
||||||
|
"`/api`、`/api/*`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "spacetime_subscribe",
|
"id": "spacetime_subscribe",
|
||||||
@@ -264,7 +199,9 @@
|
|||||||
"proxy_read_timeout 3600s;"
|
"proxy_read_timeout 3600s;"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/v1/database/{db}/subscribe`"]
|
"docs": [
|
||||||
|
"`/v1/database/{db}/subscribe`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "spacetime_identity",
|
"id": "spacetime_identity",
|
||||||
@@ -289,7 +226,9 @@
|
|||||||
"proxy_set_header Connection \"Upgrade\";"
|
"proxy_set_header Connection \"Upgrade\";"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/v1/identity*`"]
|
"docs": [
|
||||||
|
"`/v1/identity*`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "v1_forbidden",
|
"id": "v1_forbidden",
|
||||||
@@ -298,10 +237,18 @@
|
|||||||
"kind": "not_found"
|
"kind": "not_found"
|
||||||
},
|
},
|
||||||
"nginx": {
|
"nginx": {
|
||||||
"production": ["location ^~ /v1/", "return 404;"],
|
"production": [
|
||||||
"development": ["location ^~ /v1/", "return 404;"]
|
"location ^~ /v1/",
|
||||||
|
"return 404;"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"location ^~ /v1/",
|
||||||
|
"return 404;"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/v1/*`"]
|
"docs": [
|
||||||
|
"`/v1/*`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "healthz_forbidden",
|
"id": "healthz_forbidden",
|
||||||
@@ -319,7 +266,9 @@
|
|||||||
"return 404;"
|
"return 404;"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/healthz*`"]
|
"docs": [
|
||||||
|
"`/healthz*`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "readyz_forbidden",
|
"id": "readyz_forbidden",
|
||||||
@@ -337,7 +286,9 @@
|
|||||||
"return 404;"
|
"return 404;"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/readyz*`"]
|
"docs": [
|
||||||
|
"`/readyz*`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "generated_assets_forbidden",
|
"id": "generated_assets_forbidden",
|
||||||
@@ -355,11 +306,13 @@
|
|||||||
"return 404;"
|
"return 404;"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/generated-*`"]
|
"docs": [
|
||||||
|
"`/generated-*`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "web_spa_fallback",
|
"id": "web_spa_fallback",
|
||||||
"samplePath": "/creation/puzzle/result",
|
"samplePath": "/project",
|
||||||
"expect": {
|
"expect": {
|
||||||
"kind": "static",
|
"kind": "static",
|
||||||
"root": "web",
|
"root": "web",
|
||||||
@@ -375,21 +328,33 @@
|
|||||||
"try_files $uri /index.html =404;"
|
"try_files $uri /index.html =404;"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"docs": ["主站 SPA allowlist", "失败回退 `/index.html`"]
|
"docs": [
|
||||||
|
"主站 SPA allowlist",
|
||||||
|
"失败回退 `/index.html`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "web_spa_case_trailing_slash",
|
"id": "web_spa_case_trailing_slash",
|
||||||
"samplePath": "/CREATION/PUZZLE/RESULT/",
|
"samplePath": "/PROJECT/",
|
||||||
"expect": {
|
"expect": {
|
||||||
"kind": "static",
|
"kind": "static",
|
||||||
"root": "web",
|
"root": "web",
|
||||||
"mode": "spa_fallback"
|
"mode": "spa_fallback"
|
||||||
},
|
},
|
||||||
"nginx": {
|
"nginx": {
|
||||||
"production": ["location ~*", "try_files $uri /index.html =404;"],
|
"production": [
|
||||||
"development": ["location ~*", "try_files $uri /index.html =404;"]
|
"location ~*",
|
||||||
|
"try_files $uri /index.html =404;"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"location ~*",
|
||||||
|
"try_files $uri /index.html =404;"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"docs": ["大小写不敏感", "一个尾部斜杠"]
|
"docs": [
|
||||||
|
"大小写不敏感",
|
||||||
|
"一个尾部斜杠"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "web_unknown_path_exact",
|
"id": "web_unknown_path_exact",
|
||||||
@@ -400,10 +365,19 @@
|
|||||||
"mode": "exact"
|
"mode": "exact"
|
||||||
},
|
},
|
||||||
"nginx": {
|
"nginx": {
|
||||||
"production": ["location /", "try_files $uri $uri/ =404;"],
|
"production": [
|
||||||
"development": ["location /", "try_files $uri $uri/ =404;"]
|
"location /",
|
||||||
|
"try_files $uri $uri/ =404;"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"location /",
|
||||||
|
"try_files $uri $uri/ =404;"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"docs": ["其它 Web 路径", "缺失时返回真实 404"]
|
"docs": [
|
||||||
|
"其它 Web 路径",
|
||||||
|
"缺失时返回真实 404"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "creation_unknown_path_exact",
|
"id": "creation_unknown_path_exact",
|
||||||
@@ -414,10 +388,16 @@
|
|||||||
"mode": "exact"
|
"mode": "exact"
|
||||||
},
|
},
|
||||||
"nginx": {
|
"nginx": {
|
||||||
"production": ["try_files $uri $uri/ =404;"],
|
"production": [
|
||||||
"development": ["try_files $uri $uri/ =404;"]
|
"try_files $uri $uri/ =404;"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"try_files $uri $uri/ =404;"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/creation/not-exist`"]
|
"docs": [
|
||||||
|
"`/creation/not-exist`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "runtime_unknown_path_exact",
|
"id": "runtime_unknown_path_exact",
|
||||||
@@ -428,10 +408,16 @@
|
|||||||
"mode": "exact"
|
"mode": "exact"
|
||||||
},
|
},
|
||||||
"nginx": {
|
"nginx": {
|
||||||
"production": ["try_files $uri $uri/ =404;"],
|
"production": [
|
||||||
"development": ["try_files $uri $uri/ =404;"]
|
"try_files $uri $uri/ =404;"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"try_files $uri $uri/ =404;"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/runtime/not-exist`"]
|
"docs": [
|
||||||
|
"`/runtime/not-exist`"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "puzzle_unknown_path_exact",
|
"id": "puzzle_unknown_path_exact",
|
||||||
@@ -442,10 +428,16 @@
|
|||||||
"mode": "exact"
|
"mode": "exact"
|
||||||
},
|
},
|
||||||
"nginx": {
|
"nginx": {
|
||||||
"production": ["try_files $uri $uri/ =404;"],
|
"production": [
|
||||||
"development": ["try_files $uri $uri/ =404;"]
|
"try_files $uri $uri/ =404;"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"try_files $uri $uri/ =404;"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"docs": ["`/puzzle/not-exist`"]
|
"docs": [
|
||||||
|
"`/puzzle/not-exist`"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4213,3 +4213,6 @@
|
|||||||
- 决策:旧创作模板、旧创作入口及其专属运行服务进入下线范围,不再为跳一跳、抓大鹅 Match3D、儿童动作 Demo 等旧链路修复兼容问题、补生成脚本或维持专属门禁。
|
- 决策:旧创作模板、旧创作入口及其专属运行服务进入下线范围,不再为跳一跳、抓大鹅 Match3D、儿童动作 Demo 等旧链路修复兼容问题、补生成脚本或维持专属门禁。
|
||||||
- 边界:共享账号、钱包、资产、图片编辑器、公开作品、通用 HostBridge、API、SpacetimeDB、发布运维和安全能力不属于旧链路,仍需维持正式门禁。历史文档只作为背景材料,不再作为旧入口继续运行的依据。
|
- 边界:共享账号、钱包、资产、图片编辑器、公开作品、通用 HostBridge、API、SpacetimeDB、发布运维和安全能力不属于旧链路,仍需维持正式门禁。历史文档只作为背景材料,不再作为旧入口继续运行的依据。
|
||||||
- 清理方式:允许直接删除已经失效的旧素材生成命令、入口路由、专属服务和对应测试;删除工程链路时仍需核对是否被当前共享能力引用,不能连带移除仍在使用的公共契约或持久化事实。
|
- 清理方式:允许直接删除已经失效的旧素材生成命令、入口路由、专属服务和对应测试;删除工程链路时仍需核对是否被当前共享能力引用,不能连带移除仍在使用的公共契约或持久化事实。
|
||||||
|
- 最终落地:本次退役范围覆盖整个旧创作模板体系,包括 RPG / 自定义世界、拼图、拼消消、大鱼吃小鱼、敲木鱼、方洞挑战、视觉小说、汪汪声浪、寓教于乐、Creative Agent、Match3D、跳一跳和儿童动作 Demo。全部相关历史表继续作为数据壳参与 `spacetime-module` 编译,`migration.rs` 白名单与历史数据不变;旧 reducer/procedure/view、API 路由/handler/worker、前端页面/工作台/运行态、共享业务 DTO 和纯业务 crate 从编译链与依赖图移除,但旧源码和素材保留在仓库中用于历史追溯。
|
||||||
|
- 兼容读取:只保留历史审计、迁移和资产归属核对所需的最小读取定义;旧 `worldType`、公开作品号、URL、详情页和专属运行态均不再形成用户可访问入口。
|
||||||
|
- 方案文档:`docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md`。
|
||||||
|
|||||||
@@ -10,12 +10,7 @@ Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台,把 A
|
|||||||
|
|
||||||
## 当前主要能力
|
## 当前主要能力
|
||||||
|
|
||||||
- RPG / 自定义世界创作与运行时。
|
- 图片画布编辑器、项目与资产管理。
|
||||||
- 拼图玩法创作、草稿、发布、运行态和排行榜。
|
|
||||||
- 拼消消玩法创作、素材图集生成、结果页、发布、统一作品详情、正式运行态和基础统计。
|
|
||||||
- 敲木鱼玩法创作、草稿、发布、运行态、公开详情和分享码。
|
|
||||||
- 抓大鹅 Match3D 创作、2D 多视角素材生成、发布和运行态。
|
|
||||||
- 大鱼吃小鱼、方洞挑战、视觉小说、汪汪声浪和儿童向寓教于乐玩法。
|
|
||||||
- 账号、短信 / 密码 / 微信登录、个人资料、任务、钱包、邀请码、充值、反馈、法律信息和后台管理。
|
- 账号、短信 / 密码 / 微信登录、个人资料、任务、钱包、邀请码、充值、反馈、法律信息和后台管理。
|
||||||
|
|
||||||
## 当前入口
|
## 当前入口
|
||||||
@@ -50,6 +45,8 @@ server-rs + Axum + SpacetimeDB
|
|||||||
|
|
||||||
明确废弃:旧 `server-node`、Express、PostgreSQL、Go 服务端、`maincloud`、人工 `spacetime --root-dir` 口径,以及前端承接正式业务真相的路线。
|
明确废弃:旧 `server-node`、Express、PostgreSQL、Go 服务端、`maincloud`、人工 `spacetime --root-dir` 口径,以及前端承接正式业务真相的路线。
|
||||||
|
|
||||||
|
全部旧创作模板及其创作、生成、发布、公开业务详情与专属运行态已于 2026-07-17 退役。相关历史持久化表只以最小 schema 数据壳继续参与 `spacetime-module` 编译,迁移白名单与历史数据保持不变;旧前端、API、worker、procedure/reducer 和纯业务 crate 源码仅供追溯,不属于当前编译或运行能力。
|
||||||
|
|
||||||
## 当前文档入口
|
## 当前文档入口
|
||||||
|
|
||||||
- `docs/README.md`
|
- `docs/README.md`
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# 旧创作模板业务退役方案
|
||||||
|
|
||||||
|
更新时间:`2026-07-17`
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
退役整个旧创作模板体系及其专属运行态,同时保留全部相关历史持久化表、迁移白名单和必要的兼容读取定义。历史数据不删除,持久化表及字段不删除、不改名、不重排、不改变类型。
|
||||||
|
|
||||||
|
本次执行口径是“数据壳保留,业务实现退役”:历史表继续以最小 schema 代码随 `spacetime-module` 编译和发布;创作入口、模板选择、生成、发布、公开业务详情、作品广场、排行榜和专属运行态不再进入正式编译链或运行路由。
|
||||||
|
|
||||||
|
## 范围
|
||||||
|
|
||||||
|
### 退役
|
||||||
|
|
||||||
|
- RPG / 自定义世界、拼图、拼消消、大鱼吃小鱼、敲木鱼、方洞挑战、视觉小说、汪汪声浪、寓教于乐、Creative Agent、Match3D、跳一跳和儿童动作 Demo 的前端页面、路由、工作台、结果页、运行态、service、测试及专属素材生成入口。
|
||||||
|
- `api-server` 中上述业务的 router、handler、service、生成与发布编排、公开详情、专属 runtime 和 worker 启动点。
|
||||||
|
- `spacetime-client` 的模板业务 facade、mapper、reducer/procedure 调用和业务 DTO 依赖。
|
||||||
|
- `spacetime-module` 的模板 reducer、procedure、业务 view、初始化逻辑和领域规则调用。
|
||||||
|
- 所有纯模板 crate 及 RPG 专属运行态 crate 的 workspace/default 目标和在运依赖边。
|
||||||
|
- 后台及主站中只服务于旧创作入口的配置、灰度、展示、搜索、启动、追踪和运维门禁。
|
||||||
|
|
||||||
|
### 保留
|
||||||
|
|
||||||
|
- 全部旧模板历史持久化表及其原有字段顺序、字段类型、默认值、索引和可见性。
|
||||||
|
- `migration.rs` 中相关表的迁移白名单、表名兼容和字段目录。
|
||||||
|
- 为历史审计、迁移、资产归属核对所必需的最小只读表定义;不得借兼容读取重新暴露旧创作、发布、公开详情或运行接口。
|
||||||
|
- 编辑器、项目、账号、钱包、资产、HostBridge、运维和安全等平台公共能力。
|
||||||
|
- 旧页面、测试、素材、handler、service、worker、生成 bindings 和纯业务 crate 的源码目录;它们仅用于历史追溯,不属于任何正式入口或编译目标。
|
||||||
|
|
||||||
|
## 编译边界
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
- 主路由、Vite 入口和运行时动态 import 不得引用旧业务目录。
|
||||||
|
- 正式应用入口使用 `active-main.tsx`、`ActiveApp.tsx`、`routing/activeAppRoutes.tsx`、`routing/activeAppPageRoutes.ts`、`services/activeAppTitle.ts`、`platform-entry/PlatformEntryActiveFlowShell.tsx` 和 `platformEntryActiveTypes.ts`;原同名非 active 文件保持历史源码原貌并退出 Vite、TypeScript、ESLint 和 Vitest。
|
||||||
|
- 旧业务源码与素材保留在仓库中;Vite 不得再引用入口或动态 import,TypeScript、ESLint、Vitest 必须明确排除旧目录和专属测试。退役源码只用于历史追溯,不允许从在运代码重新导入。
|
||||||
|
- 原 `main.tsx`、`App.tsx`、旧路由、旧标题映射、`PlatformEntryFlowShellImpl.tsx` 与旧入口类型保持原样;正式链路由 `active-main.tsx`、`ActiveApp.tsx`、`activeApp*`、`PlatformEntryActiveFlowShell.tsx` 和 `platformEntryActiveTypes.ts` 承载,只包含项目、编辑器、账号、设置与钱包公共能力。`retired/legacy-creation-templates/frontend/original/` 另保留逐文件原样快照。
|
||||||
|
- 旧 URL 统一回落到当前平台公共首页,不再提供旧业务错误页、详情页或兼容壳。
|
||||||
|
|
||||||
|
### Rust
|
||||||
|
|
||||||
|
- `api-server` 不声明模板模块,不挂模板路由,不保留模板 worker 启动点。
|
||||||
|
- `spacetime-client` 不保留模板业务 facade 和 mutation 调用;历史表生成绑定只允许服务必要兼容读取。
|
||||||
|
- `spacetime-module` 对旧模板只编译历史表结构,不导出旧 reducer、procedure、业务 view 或领域规则。
|
||||||
|
- `spacetime-module` 与 `spacetime-client` 的 Cargo `lib.path` 固定指向各自的 `src/active.rs`;原 `src/lib.rs` 及旧业务源码继续原位保留,但不再作为 crate 根参与编译。
|
||||||
|
- 历史表最小定义集中在 `spacetime-module/src/legacy_schema/` 与 `spacetime-module/src/runtime/legacy_schema/`,混合 profile 表的在运数据壳位于 `spacetime-module/src/runtime/active/profile.rs`;这些目录只允许 schema 和必要兼容读取定义。
|
||||||
|
- 纯模板 crate 和专属运行态 crate 不属于 workspace members、default members 或任何在运 crate 的依赖图;源码目录保持原样。
|
||||||
|
|
||||||
|
## 验收
|
||||||
|
|
||||||
|
- 旧 URL 不再命中旧页面或后端路由。
|
||||||
|
- Vite 构建产物和依赖图不包含旧前端业务目录。
|
||||||
|
- `cargo tree` 中不存在纯模板 crate 或专属运行态 crate。
|
||||||
|
- `spacetime-module` 编译结果仍包含全部历史表,但不包含任何旧模板 reducer、procedure 和业务 view。
|
||||||
|
- `npm run check:spacetime-schema`、定向 Rust 检查、前端类型检查、`npm run check:encoding`、`git diff --check` 通过。
|
||||||
|
|
||||||
|
## 历史记录兼容
|
||||||
|
|
||||||
|
- 历史数据库行、作品号、`worldType` 和资产对象前缀可以继续被审计或迁移工具识别,但不再形成面向用户的列表、详情、创作或运行入口。
|
||||||
|
- 旧公开作品号、创作 URL 和统一创作规格不再作为前端可启动契约;未知或旧 URL 统一回落平台公共首页。
|
||||||
|
- 历史表名、资产对象前缀和后台数据库表目录可继续出现旧域名,它们属于数据审计与资产读取边界,不代表旧业务仍在运行。
|
||||||
|
- 原本与历史表混在同一文件的 reducer/procedure 实现按模块保存在 `retired/legacy-creation-templates/rust/`,不属于任何 Cargo workspace/module;从全局样式表移出的专属 CSS 保存在同目录的 `frontend/` 下且不被 Vite 导入。
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# server-rs 与 SpacetimeDB 数据契约
|
# server-rs 与 SpacetimeDB 数据契约
|
||||||
|
|
||||||
|
> 2026-07-17 状态更新:旧创作入口、全部模板业务 API/worker/运行态及 SpacetimeDB 业务逻辑已退役。本文逐玩法路由、流程和 DTO 章节仅作为历史设计记录;相关持久化表仍按原结构作为最小 schema 数据壳编译,当前边界以 `docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md` 为准。
|
||||||
|
|
||||||
更新时间:`2026-07-17`
|
更新时间:`2026-07-17`
|
||||||
|
|
||||||
## 后端主线
|
## 后端主线
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# 平台入口与玩法链路
|
# 平台入口与玩法链路
|
||||||
|
|
||||||
|
> 2026-07-17 退役覆盖:全部旧创作模板的前端、API、worker、reducer/procedure、纯业务 crate、生成发布链路、公开业务详情和专属运行态已下线,仅保留相关历史表的数据壳与必要兼容读取。本文后续玩法章节只作为历史设计记录,不再描述当前可用能力;当前实现与验收以 `docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md` 为准。
|
||||||
|
|
||||||
更新时间:`2026-06-10`
|
更新时间:`2026-06-10`
|
||||||
|
|
||||||
## 平台创作入口
|
## 平台创作入口
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# 当前产品与工程约束
|
# 当前产品与工程约束
|
||||||
|
|
||||||
|
> 2026-07-17 状态更新:旧创作入口、全部模板创作/生成/发布/公开业务详情及专属运行态已退役。本文涉及 RPG、拼图、拼消消、大鱼、木鱼、方洞、视觉小说、汪汪声浪、寓教于乐等玩法的内容仅作为历史设计记录;当前退役边界以 `docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md` 为准。
|
||||||
|
|
||||||
更新时间:`2026-06-05`
|
更新时间:`2026-06-05`
|
||||||
|
|
||||||
## 项目定位
|
## 项目定位
|
||||||
|
|||||||
+2
-2
@@ -7,7 +7,7 @@
|
|||||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||||
/>
|
/>
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<title>陶泥儿 Genarrative|游戏美术AI创作工具与美术Agent工作台</title>
|
<title>陶泥儿 Genarrative|美术编辑器与项目工作台</title>
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="陶泥儿 Genarrative 是面向个人游戏创作者、独立开发者和UGC游戏社群作者的游戏美术AI创作工具,当前重点提供美术Agent与无限画布,支持素材库管理、AI生成、画布编排、图层编辑、角色素材、游戏图标和宣发图制作,帮助创作者更快准备可展示的游戏美术素材。"
|
content="陶泥儿 Genarrative 是面向个人游戏创作者、独立开发者和UGC游戏社群作者的游戏美术AI创作工具,当前重点提供美术Agent与无限画布,支持素材库管理、AI生成、画布编排、图层编辑、角色素材、游戏图标和宣发图制作,帮助创作者更快准备可展示的游戏美术素材。"
|
||||||
@@ -49,6 +49,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/active-main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user