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

This commit is contained in:
2026-05-05 14:58:07 +08:00
238 changed files with 29234 additions and 144 deletions

View File

@@ -105,8 +105,11 @@ test('creation hub reflects updated draft title summary and counts after rerende
expect(screen.queryByText('角色 3')).toBeNull();
expect(screen.queryByText('地点 4')).toBeNull();
const puzzleButton = screen.getByRole('button', { name: /.*/u });
const squareHoleButton = screen.getByRole('button', { name: //u });
expect((squareHoleButton as HTMLButtonElement).disabled).toBe(false);
expect(puzzleButton).toBeTruthy();
expect((puzzleButton as HTMLButtonElement).disabled).toBe(false);
expect(screen.getByText('反直觉形状分拣')).toBeTruthy();
expect(screen.queryByRole('button', { name: //u })).toBeNull();
expect(screen.queryByRole('button', { name: //u })).toBeNull();
expect(screen.queryByRole('button', { name: //u })).toBeNull();

View File

@@ -4,6 +4,7 @@ import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/
import type { CustomWorldWorkSummary } from '../../../packages/shared/src/contracts/customWorldAgent';
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
import type { CustomWorldLibraryEntry } from '../../../packages/shared/src/contracts/runtime';
import type { CustomWorldProfile } from '../../types';
import type { PlatformCreationTypeId } from '../platform-entry/platformEntryCreationTypes';
@@ -48,6 +49,9 @@ type CustomWorldCreationHubProps = {
match3dItems?: Match3DWorkSummary[];
onOpenMatch3DDetail?: (item: Match3DWorkSummary) => void;
onDeleteMatch3D?: ((item: Match3DWorkSummary) => void) | null;
squareHoleItems?: SquareHoleWorkSummary[];
onOpenSquareHoleDetail?: (item: SquareHoleWorkSummary) => void;
onDeleteSquareHole?: ((item: SquareHoleWorkSummary) => void) | null;
puzzleItems?: PuzzleWorkSummary[];
onOpenPuzzleDetail?: (item: PuzzleWorkSummary) => void;
onDeletePuzzle?: ((item: PuzzleWorkSummary) => void) | null;
@@ -137,6 +141,9 @@ export function CustomWorldCreationHub({
match3dItems = [],
onOpenMatch3DDetail,
onDeleteMatch3D = null,
squareHoleItems = [],
onOpenSquareHoleDetail,
onDeleteSquareHole = null,
puzzleItems = [],
onOpenPuzzleDetail,
onDeletePuzzle = null,
@@ -152,10 +159,12 @@ export function CustomWorldCreationHub({
rpgLibraryEntries,
bigFishItems,
match3dItems,
squareHoleItems,
puzzleItems,
canDeleteRpg: Boolean(onDeletePublished),
canDeleteBigFish: Boolean(onDeleteBigFish),
canDeleteMatch3D: Boolean(onDeleteMatch3D),
canDeleteSquareHole: Boolean(onDeleteSquareHole),
canDeletePuzzle: Boolean(onDeletePuzzle),
}),
[
@@ -164,10 +173,12 @@ export function CustomWorldCreationHub({
match3dItems,
onDeleteBigFish,
onDeleteMatch3D,
onDeleteSquareHole,
onDeletePublished,
onDeletePuzzle,
puzzleItems,
rpgLibraryEntries,
squareHoleItems,
],
);
const [metricSnapshot] = useState<WorkMetricSnapshot>(() =>
@@ -201,6 +212,9 @@ export function CustomWorldCreationHub({
case 'match3d':
onOpenMatch3DDetail?.(item.source.item);
return;
case 'square-hole':
onOpenSquareHoleDetail?.(item.source.item);
return;
case 'rpg':
if (item.status === 'draft') {
onOpenDraft(item.source.item);
@@ -237,6 +251,12 @@ export function CustomWorldCreationHub({
onDeleteMatch3D?.(sourceItem);
};
}
case 'square-hole': {
const sourceItem = item.source.item;
return () => {
onDeleteSquareHole?.(sourceItem);
};
}
case 'rpg': {
const sourceItem = item.source.item;
return () => {

View File

@@ -40,7 +40,7 @@ export function CustomWorldCreationStartCard({
</span>
</div>
<div className="-mx-1 flex snap-x gap-2 overflow-x-auto px-1 pb-1 scrollbar-hide sm:mx-0 sm:grid sm:gap-3 sm:overflow-visible sm:px-0 sm:pb-0 sm:grid-cols-2 xl:grid-cols-5 xl:gap-2.5">
<div className="-mx-1 flex snap-x gap-2 overflow-x-auto px-1 pb-1 scrollbar-hide sm:mx-0 sm:grid sm:gap-3 sm:overflow-visible sm:px-0 sm:pb-0 sm:grid-cols-2 xl:grid-cols-6 xl:gap-2.5">
{visibleCreationTypes.map((item) => {
const disabled = item.locked || busy;

View File

@@ -2,16 +2,23 @@ import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/
import type { CustomWorldWorkSummary } from '../../../packages/shared/src/contracts/customWorldAgent';
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
import type { CustomWorldLibraryEntry } from '../../../packages/shared/src/contracts/runtime';
import { buildPublicWorkStagePath } from '../../routing/appPageRoutes';
import {
buildBigFishPublicWorkCode,
buildMatch3DPublicWorkCode,
buildPuzzlePublicWorkCode,
buildSquareHolePublicWorkCode,
} from '../../services/publicWorkCode';
import type { CustomWorldProfile } from '../../types';
export type CreationWorkShelfKind = 'rpg' | 'big-fish' | 'match3d' | 'puzzle';
export type CreationWorkShelfKind =
| 'rpg'
| 'big-fish'
| 'match3d'
| 'square-hole'
| 'puzzle';
export type CreationWorkShelfStatus = 'draft' | 'published';
export type CreationWorkShelfBadgeTone = 'warm' | 'success' | 'neutral';
@@ -56,6 +63,10 @@ export type CreationWorkShelfSource =
kind: 'match3d';
item: Match3DWorkSummary;
}
| {
kind: 'square-hole';
item: SquareHoleWorkSummary;
}
| {
kind: 'puzzle';
item: PuzzleWorkSummary;
@@ -87,10 +98,12 @@ export function buildCreationWorkShelfItems(params: {
rpgLibraryEntries?: CustomWorldLibraryEntry<CustomWorldProfile>[];
bigFishItems: BigFishWorkSummary[];
match3dItems?: Match3DWorkSummary[];
squareHoleItems?: SquareHoleWorkSummary[];
puzzleItems: PuzzleWorkSummary[];
canDeleteRpg?: boolean;
canDeleteBigFish?: boolean;
canDeleteMatch3D?: boolean;
canDeleteSquareHole?: boolean;
canDeletePuzzle?: boolean;
}) {
const {
@@ -98,10 +111,12 @@ export function buildCreationWorkShelfItems(params: {
rpgLibraryEntries = [],
bigFishItems,
match3dItems = [],
squareHoleItems = [],
puzzleItems,
canDeleteRpg = false,
canDeleteBigFish = false,
canDeleteMatch3D = false,
canDeleteSquareHole = false,
canDeletePuzzle = false,
} = params;
@@ -115,6 +130,9 @@ export function buildCreationWorkShelfItems(params: {
...match3dItems.map((item) =>
mapMatch3DWorkToShelfItem(item, canDeleteMatch3D),
),
...squareHoleItems.map((item) =>
mapSquareHoleWorkToShelfItem(item, canDeleteSquareHole),
),
...puzzleItems.map((item) =>
mapPuzzleWorkToShelfItem(item, canDeletePuzzle),
),
@@ -319,6 +337,50 @@ function mapPuzzleWorkToShelfItem(
};
}
function mapSquareHoleWorkToShelfItem(
item: SquareHoleWorkSummary,
canDelete: boolean,
): CreationWorkShelfItem {
const status = item.publicationStatus === 'published' ? 'published' : 'draft';
const publicWorkCode =
status === 'published'
? buildSquareHolePublicWorkCode(item.profileId)
: null;
return {
id: item.workId,
kind: 'square-hole',
status,
title: item.gameName,
summary: item.summary,
updatedAt: item.updatedAt,
coverImageSrc: item.coverImageSrc ?? null,
coverRenderMode: 'image',
coverCharacterImageSrcs: [],
publicWorkCode,
sharePath:
publicWorkCode && status === 'published'
? buildPublicWorkStagePath('work-detail', publicWorkCode)
: null,
openActionLabel: status === 'published' ? '查看详情' : '继续创作',
canDelete,
canShare: status === 'published' && Boolean(publicWorkCode),
badges: [
buildStatusBadge(status),
{ id: 'type', label: '方洞', tone: 'neutral' },
],
metrics:
status === 'published'
? buildPublishedMetrics({
playCount: item.playCount,
remixCount: 0,
likeCount: 0,
})
: [],
source: { kind: 'square-hole', item },
};
}
function buildPublishedMetrics(params: {
playCount?: number | null;
remixCount?: number | null;