1
This commit is contained in:
@@ -1606,6 +1606,8 @@ type ResolvedAgentIntent =
|
|||||||
1. 不再承担整世界重生成主入口
|
1. 不再承担整世界重生成主入口
|
||||||
2. 不再承担核心 Agent 对话主流程
|
2. 不再承担核心 Agent 对话主流程
|
||||||
3. 仅保留“查看 / 导出 / 发布确认 / 进入世界”
|
3. 仅保留“查看 / 导出 / 发布确认 / 进入世界”
|
||||||
|
4. 结果页底部不常驻展示“数据源”提示
|
||||||
|
5. 发布阻断项只在创作者点击发布动作时,通过独立确认面板提示,不在结果页吸底常驻展开
|
||||||
|
|
||||||
## 9.12 角色资产工坊
|
## 9.12 角色资产工坊
|
||||||
|
|
||||||
|
|||||||
@@ -1217,8 +1217,9 @@ Phase 4 本轮已完成以下主链接线:
|
|||||||
- published works 明确输出 `canEnterWorld=true`
|
- published works 明确输出 `canEnterWorld=true`
|
||||||
4. 前端 Agent 结果页已开始消费服务端 Phase4 状态:
|
4. 前端 Agent 结果页已开始消费服务端 Phase4 状态:
|
||||||
- 结果页在 Agent 草稿未发布时把主 CTA 改成“发布并进入世界”
|
- 结果页在 Agent 草稿未发布时把主 CTA 改成“发布并进入世界”
|
||||||
- 结果页会展示服务端 preview source、publish blockers、warning 数量
|
- 结果页会消费服务端 gate 语义,但不再把 preview source 做成底部常驻提示
|
||||||
- 有 blocker 时会禁用“发布并进入世界”按钮,不再让前端继续假装可以直接进入世界
|
- publish blockers 改为点击“发布并进入世界”时,通过独立面板提示
|
||||||
|
- warning 数量仍可作为非阻断摘要展示
|
||||||
5. `useRpgCreationEnterWorld.ts` 与 `RpgEntryFlowShellImpl.tsx` 已把 Agent 结果页进入世界主链改成:
|
5. `useRpgCreationEnterWorld.ts` 与 `RpgEntryFlowShellImpl.tsx` 已把 Agent 结果页进入世界主链改成:
|
||||||
- 先 `sync_result_profile`
|
- 先 `sync_result_profile`
|
||||||
- 再执行后端 `publish_world`
|
- 再执行后端 `publish_world`
|
||||||
|
|||||||
@@ -50,9 +50,10 @@ src/services/creation-agent/
|
|||||||
聊天页展示规则:
|
聊天页展示规则:
|
||||||
|
|
||||||
1. Agent 聊天页不展示锚点内容卡片,锚点只作为进度与后端生成依据。
|
1. Agent 聊天页不展示锚点内容卡片,锚点只作为进度与后端生成依据。
|
||||||
2. 生成草稿 / 生成结果页主按钮只在 `progressPercent` 归一化后达到 `100%` 时显示。
|
2. 标题区文案支持按品类留空;当 `title` 与 `assistantSummary` 都为空时,顶部模块只保留返回、进度和操作按钮,不显示额外标题与副文案。
|
||||||
3. 进度条下方承载“总结当前设定”“补全剩余设定”等进度操作按钮。
|
3. 生成草稿 / 生成结果页主按钮只在 `progressPercent` 归一化后达到 `100%` 时显示。
|
||||||
4. “补全剩余设定”必须配置 `minTurn: 2`,对话不足两轮时不显示。
|
4. 进度条下方承载“总结当前设定”“补全剩余设定”等进度操作按钮。
|
||||||
|
5. “补全剩余设定”必须配置 `minTurn: 2`,对话不足两轮时不显示。
|
||||||
|
|
||||||
组件内部只做表现,不读取任何 RPG、Big Fish、Puzzle 专属字段。
|
组件内部只做表现,不读取任何 RPG、Big Fish、Puzzle 专属字段。
|
||||||
|
|
||||||
|
|||||||
@@ -443,7 +443,7 @@ test('readOnly result view hides edit and create actions for agent preview mode'
|
|||||||
expect(screen.queryByRole('button', { name: /批量删除/u })).toBeNull();
|
expect(screen.queryByRole('button', { name: /批量删除/u })).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('agent result view shows publish blockers and disables publish-enter action', () => {
|
test('agent result view keeps publish-enter action clickable and hides sticky publish hints', () => {
|
||||||
render(
|
render(
|
||||||
<RpgCreationResultView
|
<RpgCreationResultView
|
||||||
profile={baseProfile}
|
profile={baseProfile}
|
||||||
@@ -474,15 +474,48 @@ test('agent result view shows publish blockers and disables publish-enter action
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByText(/当前结果页数据源:服务端预览/u)).toBeTruthy();
|
|
||||||
expect(screen.getByText(/当前还有 2 个发布阻断项/u)).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
screen.getByText(/仍有角色缺少正式主图或动作资产/u),
|
|
||||||
).toBeTruthy();
|
|
||||||
const actionButton = screen.getByRole('button', {
|
const actionButton = screen.getByRole('button', {
|
||||||
name: '发布并进入世界',
|
name: '发布并进入世界',
|
||||||
});
|
});
|
||||||
expect((actionButton as HTMLButtonElement).disabled).toBe(true);
|
expect((actionButton as HTMLButtonElement).disabled).toBe(false);
|
||||||
|
expect(screen.queryByText(/当前结果页数据源:服务端预览/u)).toBeNull();
|
||||||
|
expect(screen.queryByText(/当前还有 2 个发布阻断项/u)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('agent result view opens publish blocker dialog only when user clicks publish action', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<RpgCreationResultView
|
||||||
|
profile={baseProfile}
|
||||||
|
previewCharacters={[]}
|
||||||
|
isGenerating={false}
|
||||||
|
progress={0}
|
||||||
|
progressLabel=""
|
||||||
|
error={null}
|
||||||
|
onBack={() => {}}
|
||||||
|
onProfileChange={() => {}}
|
||||||
|
compactAgentResultMode
|
||||||
|
publishReady={false}
|
||||||
|
publishBlockers={[
|
||||||
|
'仍有角色缺少正式主图或动作资产,发布前需要先补齐。',
|
||||||
|
'营地还缺少正式场景图资产,发布前需要先确认营地图。',
|
||||||
|
]}
|
||||||
|
previewSourceLabel="服务端预览"
|
||||||
|
enterWorldActionLabel="发布并进入世界"
|
||||||
|
onEnterWorld={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: '发布并进入世界' }));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('dialog', { name: '发布前检查' }),
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(screen.getByText(/当前还有 2 个阻断项/u)).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
screen.getByText(/仍有角色缺少正式主图或动作资产/u),
|
||||||
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('agent result view keeps publish-enter action enabled when publish gate is clear', () => {
|
test('agent result view keeps publish-enter action enabled when publish gate is clear', () => {
|
||||||
|
|||||||
@@ -205,3 +205,40 @@ test('creation agent workspace shows primary and progress actions at completed p
|
|||||||
expect(screen.getByRole('button', { name: '总结当前设定' })).toBeTruthy();
|
expect(screen.getByRole('button', { name: '总结当前设定' })).toBeTruthy();
|
||||||
expect(screen.getByRole('button', { name: '补全剩余设定' })).toBeTruthy();
|
expect(screen.getByRole('button', { name: '补全剩余设定' })).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('creation agent workspace hides hero copy area when title and summary are absent', () => {
|
||||||
|
if (!Element.prototype.scrollIntoView) {
|
||||||
|
Element.prototype.scrollIntoView = () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CreationAgentWorkspace
|
||||||
|
session={{
|
||||||
|
sessionId: 'creation-agent-session-1',
|
||||||
|
title: null,
|
||||||
|
assistantSummary: null,
|
||||||
|
currentTurn: 2,
|
||||||
|
progressPercent: 60,
|
||||||
|
anchors: [],
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
id: 'message-1',
|
||||||
|
role: 'assistant',
|
||||||
|
kind: 'chat',
|
||||||
|
text: '继续把设定收束到可生成状态。',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
theme={testTheme}
|
||||||
|
loadingText="正在准备"
|
||||||
|
composerPlaceholder="输入消息"
|
||||||
|
primaryActionLabel="生成结果页"
|
||||||
|
onBack={() => {}}
|
||||||
|
onSubmitText={() => {}}
|
||||||
|
onPrimaryAction={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByText('统一共创')).toBeNull();
|
||||||
|
expect(screen.getByText('创作进度')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export type CreationAgentOperationView = {
|
|||||||
|
|
||||||
export type CreationAgentSessionView = {
|
export type CreationAgentSessionView = {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
title: string;
|
title?: string | null;
|
||||||
assistantSummary?: string | null;
|
assistantSummary?: string | null;
|
||||||
currentTurn: number;
|
currentTurn: number;
|
||||||
progressPercent: number;
|
progressPercent: number;
|
||||||
@@ -267,6 +267,7 @@ export function CreationAgentWorkspace({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const progress = normalizeCreationAgentProgress(session.progressPercent);
|
const progress = normalizeCreationAgentProgress(session.progressPercent);
|
||||||
|
const hasHeroCopy = Boolean(session.title || session.assistantSummary);
|
||||||
const canShowPrimaryAction = progress >= 100;
|
const canShowPrimaryAction = progress >= 100;
|
||||||
const visibleQuickActions = quickActions.filter((action) =>
|
const visibleQuickActions = quickActions.filter((action) =>
|
||||||
shouldShowQuickAction(action, session, progress),
|
shouldShowQuickAction(action, session, progress),
|
||||||
@@ -313,18 +314,22 @@ export function CreationAgentWorkspace({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{hasHeroCopy ? (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
|
{session.title ? (
|
||||||
<div className="text-2xl font-black leading-tight sm:text-3xl">
|
<div className="text-2xl font-black leading-tight sm:text-3xl">
|
||||||
{session.title}
|
{session.title}
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
{session.assistantSummary ? (
|
{session.assistantSummary ? (
|
||||||
<div className="mt-2 max-w-2xl text-sm leading-6 text-white/76">
|
<div className="mt-2 max-w-2xl text-sm leading-6 text-white/76">
|
||||||
{session.assistantSummary}
|
{session.assistantSummary}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className={hasHeroCopy ? 'mt-4' : 'mt-6'}>
|
||||||
<div className="mb-2 flex items-center justify-between gap-3">
|
<div className="mb-2 flex items-center justify-between gap-3">
|
||||||
<span className="text-xs font-semibold tracking-[0.14em] text-white/72">
|
<span className="text-xs font-semibold tracking-[0.14em] text-white/72">
|
||||||
创作进度
|
创作进度
|
||||||
|
|||||||
@@ -77,6 +77,10 @@ test('custom world agent workspace renders minimum loop chat layout', () => {
|
|||||||
expect(html).toContain('输入消息');
|
expect(html).toContain('输入消息');
|
||||||
expect(html).toContain('总结当前设定');
|
expect(html).toContain('总结当前设定');
|
||||||
expect(html).toContain('补全剩余设定');
|
expect(html).toContain('补全剩余设定');
|
||||||
|
expect(html).not.toContain('世界共创');
|
||||||
|
expect(html).not.toContain(
|
||||||
|
'先说一个你最想让玩家记住的世界方向,我会帮你收束成可生成草稿的锚点。',
|
||||||
|
);
|
||||||
expect(html).not.toContain('Agent');
|
expect(html).not.toContain('Agent');
|
||||||
expect(html).not.toContain('刷新');
|
expect(html).not.toContain('刷新');
|
||||||
expect(html).not.toContain('当前轮次');
|
expect(html).not.toContain('当前轮次');
|
||||||
|
|||||||
@@ -83,10 +83,9 @@ function mapCustomWorldSession(
|
|||||||
): CreationAgentSessionView {
|
): CreationAgentSessionView {
|
||||||
return {
|
return {
|
||||||
sessionId: session.sessionId,
|
sessionId: session.sessionId,
|
||||||
title: '世界共创',
|
// 自定义世界 Agent 聊天页顶部保持纯操作区,不额外显示标题和引导副文案。
|
||||||
assistantSummary:
|
title: null,
|
||||||
session.lastAssistantReply ||
|
assistantSummary: null,
|
||||||
'先说一个你最想让玩家记住的世界方向,我会帮你收束成可生成草稿的锚点。',
|
|
||||||
currentTurn: session.currentTurn,
|
currentTurn: session.currentTurn,
|
||||||
progressPercent: session.progressPercent,
|
progressPercent: session.progressPercent,
|
||||||
anchors: [
|
anchors: [
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import type { ReactNode } from 'react';
|
import { X } from 'lucide-react';
|
||||||
|
import { type ReactNode, useState } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
import type { CustomWorldProfile } from '../../types';
|
import type { CustomWorldProfile } from '../../types';
|
||||||
|
|
||||||
@@ -29,6 +31,81 @@ function SmallButton({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PublishBlockersDialog({
|
||||||
|
blockers,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
blockers: string[];
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<div
|
||||||
|
className="platform-overlay fixed inset-0 z-[140] flex items-end justify-center bg-slate-950/56 p-3 backdrop-blur-sm sm:items-center sm:p-4"
|
||||||
|
onClick={(event) => {
|
||||||
|
if (event.target === event.currentTarget) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-label="发布前检查"
|
||||||
|
className="platform-modal-shell platform-remap-surface flex max-h-[min(88vh,42rem)] w-full max-w-lg flex-col overflow-hidden rounded-t-[1.75rem] shadow-[0_24px_80px_rgba(0,0,0,0.55)] sm:rounded-[1.75rem]"
|
||||||
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between gap-3 border-b border-white/10 px-5 py-4">
|
||||||
|
<div>
|
||||||
|
<div className="text-base font-semibold text-white">
|
||||||
|
发布前还需要补齐这些内容
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 text-sm leading-6 text-[var(--platform-text-soft)]">
|
||||||
|
当前还有 {blockers.length} 个阻断项,补齐后再发布并进入世界。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
aria-label="关闭"
|
||||||
|
className="platform-icon-button"
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
{blockers.map((blocker, index) => (
|
||||||
|
<div
|
||||||
|
key={`publish-blocker-${index}-${blocker}`}
|
||||||
|
className="rounded-[1.1rem] border border-amber-300/18 bg-amber-500/10 px-4 py-3 text-sm leading-6 text-[var(--platform-text-strong)]"
|
||||||
|
>
|
||||||
|
<div className="text-xs font-semibold tracking-[0.14em] text-amber-100/78">
|
||||||
|
阻断项 {index + 1}
|
||||||
|
</div>
|
||||||
|
<div className="mt-1">{blocker}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end border-t border-white/10 px-5 py-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className="platform-button platform-button--primary"
|
||||||
|
>
|
||||||
|
我知道了
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
document.body,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface RpgCreationResultActionBarProps {
|
interface RpgCreationResultActionBarProps {
|
||||||
editActionLabel: string;
|
editActionLabel: string;
|
||||||
enterWorldActionLabel: string;
|
enterWorldActionLabel: string;
|
||||||
@@ -40,6 +117,7 @@ interface RpgCreationResultActionBarProps {
|
|||||||
profile: CustomWorldProfile;
|
profile: CustomWorldProfile;
|
||||||
regenerateActionLabel: string;
|
regenerateActionLabel: string;
|
||||||
publishReady: boolean;
|
publishReady: boolean;
|
||||||
|
publishBlockers: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RpgCreationResultActionBar({
|
export function RpgCreationResultActionBar({
|
||||||
@@ -53,7 +131,21 @@ export function RpgCreationResultActionBar({
|
|||||||
profile,
|
profile,
|
||||||
regenerateActionLabel,
|
regenerateActionLabel,
|
||||||
publishReady,
|
publishReady,
|
||||||
|
publishBlockers,
|
||||||
}: RpgCreationResultActionBarProps) {
|
}: RpgCreationResultActionBarProps) {
|
||||||
|
const [showPublishBlockersDialog, setShowPublishBlockersDialog] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
|
// 结果页保持清爽,只有用户发起发布动作时才弹出阻断项提示。
|
||||||
|
const handleEnterWorld = () => {
|
||||||
|
if (!publishReady) {
|
||||||
|
setShowPublishBlockersDialog(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onEnterWorld?.();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-4 flex flex-col gap-3">
|
<div className="mt-4 flex flex-col gap-3">
|
||||||
{profile.generationStatus === 'key_only' ? (
|
{profile.generationStatus === 'key_only' ? (
|
||||||
@@ -82,14 +174,24 @@ export function RpgCreationResultActionBar({
|
|||||||
{onEnterWorld ? (
|
{onEnterWorld ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onEnterWorld}
|
onClick={handleEnterWorld}
|
||||||
disabled={isGenerating || !publishReady}
|
disabled={isGenerating}
|
||||||
className={`platform-button platform-button--primary ${isGenerating ? 'opacity-55' : ''}`}
|
className={`platform-button platform-button--primary ${isGenerating ? 'opacity-55' : ''}`}
|
||||||
>
|
>
|
||||||
{enterWorldActionLabel}
|
{enterWorldActionLabel}
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
{showPublishBlockersDialog ? (
|
||||||
|
<PublishBlockersDialog
|
||||||
|
blockers={
|
||||||
|
publishBlockers.length > 0
|
||||||
|
? publishBlockers
|
||||||
|
: ['当前草稿还没有通过发布门槛,请先补齐必要内容。']
|
||||||
|
}
|
||||||
|
onClose={() => setShowPublishBlockersDialog(false)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ export function RpgCreationResultView({
|
|||||||
publishReady = true,
|
publishReady = true,
|
||||||
publishBlockers = [],
|
publishBlockers = [],
|
||||||
qualityFindings = [],
|
qualityFindings = [],
|
||||||
previewSourceLabel = null,
|
|
||||||
}: RpgCreationResultViewProps) {
|
}: RpgCreationResultViewProps) {
|
||||||
const [activeTab, setActiveTab] = useState<ResultTab>('world');
|
const [activeTab, setActiveTab] = useState<ResultTab>('world');
|
||||||
const assetDebugEnabled = useMemo(
|
const assetDebugEnabled = useMemo(
|
||||||
@@ -171,25 +170,6 @@ export function RpgCreationResultView({
|
|||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{!error && compactAgentResultMode && previewSourceLabel ? (
|
|
||||||
<div className="platform-banner platform-banner--info mt-3 rounded-2xl text-sm leading-6">
|
|
||||||
当前结果页数据源:{previewSourceLabel}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{!error && compactAgentResultMode && publishBlockers.length > 0 ? (
|
|
||||||
<div className="platform-banner platform-banner--warning mt-3 rounded-2xl text-sm leading-6">
|
|
||||||
{publishReady
|
|
||||||
? '当前世界已满足发布门槛。'
|
|
||||||
: `当前还有 ${publishBlockers.length} 个发布阻断项,请先补齐后再进入世界。`}
|
|
||||||
<div className="mt-2 space-y-1">
|
|
||||||
{publishBlockers.slice(0, 4).map((entry, index) => (
|
|
||||||
<div key={`publish-blocker-${index}-${entry}`}>
|
|
||||||
{index + 1}. {entry}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{!error &&
|
{!error &&
|
||||||
compactAgentResultMode &&
|
compactAgentResultMode &&
|
||||||
publishBlockers.length <= 0 &&
|
publishBlockers.length <= 0 &&
|
||||||
@@ -216,6 +196,7 @@ export function RpgCreationResultView({
|
|||||||
profile={profile}
|
profile={profile}
|
||||||
regenerateActionLabel={regenerateActionLabel}
|
regenerateActionLabel={regenerateActionLabel}
|
||||||
publishReady={publishReady}
|
publishReady={publishReady}
|
||||||
|
publishBlockers={publishBlockers}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RpgCreationEntityEditorModal
|
<RpgCreationEntityEditorModal
|
||||||
|
|||||||
@@ -1097,7 +1097,7 @@ test('existing draft sessions open result page refinement instead of agent dialo
|
|||||||
expect(screen.getByRole('button', { name: /AI生成/u })).toBeTruthy();
|
expect(screen.getByRole('button', { name: /AI生成/u })).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('agent result view shows publish blockers and disables publish-enter action when preview gate is not ready', async () => {
|
test('agent result view shows publish blocker dialog before publish action when preview gate is not ready', async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
|
|
||||||
vi.mocked(getRpgCreationOperation).mockResolvedValue({
|
vi.mocked(getRpgCreationOperation).mockResolvedValue({
|
||||||
@@ -1128,11 +1128,33 @@ test('agent result view shows publish blockers and disables publish-enter action
|
|||||||
|
|
||||||
await openNewRpgCreation(user);
|
await openNewRpgCreation(user);
|
||||||
|
|
||||||
expect(await screen.findByText(/当前还有 1 个发布阻断项/u)).toBeTruthy();
|
const actionButton = await screen.findByRole('button', {
|
||||||
const actionButton = screen.getByRole('button', {
|
|
||||||
name: /发布并进入世界/u,
|
name: /发布并进入世界/u,
|
||||||
});
|
});
|
||||||
expect((actionButton as HTMLButtonElement).disabled).toBe(true);
|
expect((actionButton as HTMLButtonElement).disabled).toBe(false);
|
||||||
|
|
||||||
|
const publishWorldCallCountBeforeClick = vi
|
||||||
|
.mocked(executeRpgCreationAction)
|
||||||
|
.mock.calls.filter(
|
||||||
|
([sessionId, payload]) =>
|
||||||
|
sessionId === 'custom-world-agent-session-1' &&
|
||||||
|
payload?.action === 'publish_world',
|
||||||
|
).length;
|
||||||
|
|
||||||
|
await user.click(actionButton);
|
||||||
|
|
||||||
|
expect(await screen.findByRole('dialog', { name: '发布前检查' })).toBeTruthy();
|
||||||
|
expect(screen.getByText(/当前还有 1 个阻断项/u)).toBeTruthy();
|
||||||
|
expect(screen.getByText(/仍有角色缺少正式主图或动作资产/u)).toBeTruthy();
|
||||||
|
|
||||||
|
const publishWorldCallCountAfterClick = vi
|
||||||
|
.mocked(executeRpgCreationAction)
|
||||||
|
.mock.calls.filter(
|
||||||
|
([sessionId, payload]) =>
|
||||||
|
sessionId === 'custom-world-agent-session-1' &&
|
||||||
|
payload?.action === 'publish_world',
|
||||||
|
).length;
|
||||||
|
expect(publishWorldCallCountAfterClick).toBe(publishWorldCallCountBeforeClick);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('agent draft result publishes before entering world and uses published preview profile', async () => {
|
test('agent draft result publishes before entering world and uses published preview profile', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user