Merge branch 'codex/backend-rewrite-spacetimedb' of http://82.157.175.59:3000/GenarrativeAI/Genarrative into codex/backend-rewrite-spacetimedb
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import type { BigFishSessionSnapshotResponse } from '../../../packages/shared/src/contracts/bigFish';
|
||||
@@ -146,4 +146,28 @@ describe('BigFishResultView', () => {
|
||||
expect(screen.getByAltText('荧潮幼体')).toBeTruthy();
|
||||
expect(screen.getByAltText('深海谜境 场地背景')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('shows publish failures in a dismissible modal', () => {
|
||||
const onDismissError = vi.fn();
|
||||
|
||||
render(
|
||||
<BigFishResultView
|
||||
session={createSession()}
|
||||
error="big_fish 发布校验未通过:还缺少 16 个基础动作"
|
||||
onBack={() => {}}
|
||||
onDismissError={onDismissError}
|
||||
onExecuteAction={() => {}}
|
||||
onStartTestRun={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole('dialog')).toBeTruthy();
|
||||
expect(screen.getByText('发布失败')).toBeTruthy();
|
||||
expect(
|
||||
screen.getByText('big_fish 发布校验未通过:还缺少 16 个基础动作'),
|
||||
).toBeTruthy();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '知道了' }));
|
||||
expect(onDismissError).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,6 +37,7 @@ type BigFishResultViewProps = {
|
||||
isBusy?: boolean;
|
||||
error?: string | null;
|
||||
onBack: () => void;
|
||||
onDismissError?: () => void;
|
||||
onExecuteAction: (payload: ExecuteBigFishActionRequest) => void;
|
||||
onStartTestRun: () => void;
|
||||
};
|
||||
@@ -330,6 +331,7 @@ export function BigFishResultView({
|
||||
isBusy = false,
|
||||
error = null,
|
||||
onBack,
|
||||
onDismissError,
|
||||
onExecuteAction,
|
||||
onStartTestRun,
|
||||
}: BigFishResultViewProps) {
|
||||
@@ -417,12 +419,6 @@ export function BigFishResultView({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<div className="rounded-[1.15rem] border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600">
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid min-h-0 flex-1 gap-3 overflow-hidden lg:grid-cols-[minmax(0,1fr)_18rem]">
|
||||
<div className="min-h-0 overflow-y-auto pr-1">
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
@@ -520,6 +516,58 @@ export function BigFishResultView({
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{error ? (
|
||||
<BigFishResultErrorModal
|
||||
message={error}
|
||||
onClose={() => {
|
||||
onDismissError?.();
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BigFishResultErrorModal({
|
||||
message,
|
||||
onClose,
|
||||
}: {
|
||||
message: string;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-[160] flex items-center justify-center bg-slate-950/58 px-4 py-6 backdrop-blur-sm">
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="big-fish-result-error-title"
|
||||
className="w-full max-w-sm rounded-[1.6rem] border border-red-100/80 bg-white p-5 text-slate-950 shadow-2xl"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="mt-0.5 inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-red-50 text-red-600">
|
||||
<Waves className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div
|
||||
id="big-fish-result-error-title"
|
||||
className="text-base font-black text-slate-950"
|
||||
>
|
||||
发布失败
|
||||
</div>
|
||||
<div className="mt-2 text-sm leading-6 text-slate-600">
|
||||
{message}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="mt-5 inline-flex w-full items-center justify-center rounded-full bg-slate-950 px-4 py-2.5 text-sm font-bold text-white"
|
||||
>
|
||||
知道了
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -108,6 +108,24 @@ function resolveRuntimeEntityAsset(
|
||||
);
|
||||
}
|
||||
|
||||
function resolveSettlementCopy(run: BigFishRuntimeSnapshotResponse) {
|
||||
if (run.status === 'won') {
|
||||
return {
|
||||
title: '通关完成',
|
||||
message: `已成长到 Lv.${run.playerLevel},本轮生态征服完成。`,
|
||||
tone: 'from-emerald-300/28 via-cyan-300/18 to-white/10',
|
||||
};
|
||||
}
|
||||
if (run.status === 'failed') {
|
||||
return {
|
||||
title: '本轮失败',
|
||||
message: '己方鱼群已经耗尽,重新调整路线再来一次。',
|
||||
tone: 'from-rose-300/30 via-orange-300/16 to-white/10',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function BigFishEntityDot({
|
||||
entity,
|
||||
run,
|
||||
@@ -241,6 +259,7 @@ export function BigFishRuntimeShell({
|
||||
|
||||
const statusLabel =
|
||||
run.status === 'won' ? '通关' : run.status === 'failed' ? '失败' : '进行中';
|
||||
const settlementCopy = resolveSettlementCopy(run);
|
||||
const backgroundAsset =
|
||||
findBigFishAssetSlot(assetSlots, 'stage_background')?.assetUrl?.trim() || null;
|
||||
|
||||
@@ -298,6 +317,21 @@ export function BigFishRuntimeShell({
|
||||
))}
|
||||
</div>
|
||||
|
||||
{settlementCopy ? (
|
||||
<div className="pointer-events-none absolute inset-0 z-40 flex items-center justify-center px-5">
|
||||
<div
|
||||
className={`w-full max-w-[20rem] rounded-[2rem] border border-white/24 bg-gradient-to-br ${settlementCopy.tone} p-6 text-center shadow-2xl shadow-slate-950/45 backdrop-blur-xl`}
|
||||
>
|
||||
<div className="text-3xl font-black tracking-[0.22em] text-white [text-shadow:0_2px_12px_rgba(2,6,23,0.6)]">
|
||||
{settlementCopy.title}
|
||||
</div>
|
||||
<div className="mt-3 text-sm font-semibold leading-6 text-white/82">
|
||||
{settlementCopy.message}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="pointer-events-none absolute bottom-6 right-4 z-30 max-w-[13rem] space-y-2 text-right text-xs text-white/72">
|
||||
{isBusy ? <div>同步中...</div> : null}
|
||||
{error ? <div className="text-rose-200">{error}</div> : null}
|
||||
|
||||
@@ -32,6 +32,37 @@ function ensureScrollApis() {
|
||||
}
|
||||
}
|
||||
|
||||
test('creation agent workspace keeps initial chat progress at zero percent', () => {
|
||||
ensureScrollApis();
|
||||
|
||||
render(
|
||||
<CreationAgentWorkspace
|
||||
session={{
|
||||
sessionId: 'creation-agent-session-1',
|
||||
title: null,
|
||||
currentTurn: 0,
|
||||
progressPercent: 0,
|
||||
anchors: [],
|
||||
messages: [],
|
||||
}}
|
||||
theme={testTheme}
|
||||
loadingText="正在准备"
|
||||
composerPlaceholder="输入消息"
|
||||
primaryActionLabel="生成结果页"
|
||||
onBack={() => {}}
|
||||
onSubmitText={() => {}}
|
||||
onPrimaryAction={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const progressbar = screen.getByRole('progressbar');
|
||||
|
||||
expect(progressbar.getAttribute('aria-valuenow')).toBe('0');
|
||||
expect((progressbar.firstElementChild as HTMLElement | null)?.style.width).toBe(
|
||||
'0%',
|
||||
);
|
||||
});
|
||||
|
||||
test('creation agent workspace filters duplicate recommended replies', () => {
|
||||
const consoleErrorSpy = vi
|
||||
.spyOn(console, 'error')
|
||||
|
||||
@@ -311,6 +311,7 @@ export function CreationAgentWorkspace({
|
||||
}
|
||||
|
||||
const progress = normalizeCreationAgentProgress(session.progressPercent);
|
||||
const progressFillWidth = progress <= 0 ? '0%' : `${Math.max(6, progress)}%`;
|
||||
const hasHeroCopy = Boolean(session.title || session.assistantSummary);
|
||||
const canShowPrimaryAction = progress >= 100;
|
||||
const visibleQuickActions = quickActions.filter((action) =>
|
||||
@@ -414,10 +415,16 @@ export function CreationAgentWorkspace({
|
||||
{progress}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 overflow-hidden rounded-full bg-white/12">
|
||||
<div
|
||||
className="h-2 overflow-hidden rounded-full bg-white/12"
|
||||
role="progressbar"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
aria-valuenow={progress}
|
||||
>
|
||||
<div
|
||||
className={`h-full rounded-full transition-all ${theme.accentBgClass}`}
|
||||
style={{ width: `${Math.max(6, progress)}%` }}
|
||||
style={{ width: progressFillWidth }}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 text-xs leading-5 text-white/64">
|
||||
|
||||
@@ -1820,6 +1820,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
onBack={() => {
|
||||
setSelectionStage('big-fish-agent-workspace');
|
||||
}}
|
||||
onDismissError={() => {
|
||||
setBigFishError(null);
|
||||
}}
|
||||
onExecuteAction={(payload) => {
|
||||
void executeBigFishAction(payload);
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user