Files
Genarrative/apps/ai-game-creator-shell/src/features/app-shell/sessionPreview.ts
T
suzmii d04fe83f10 修复 PR #316 review(前端 TS/TSX):@ 引用与提示词润色主链路
- App.tsx:`conversation.write` 策略确认后的重跑改为整体复用首轮入参(`directCodexPolicyRetryInput`),不再手写字段,修掉漏传 `references` 导致 @ 引用被静默丢弃的缺陷
- resourceReferences.ts:运行画面引用的判别指纹补上 `resourceIds`(排序集合口径)/`versionId`/`elementRole`/`width`/`height`,避免不同选点撞 key 被 `dedupeChatReferences` 去重丢掉
- ResourceReferenceInput.tsx:程序化重建编辑器内容改为把 chip 内联嵌在文本里的同名 token 位置(不再另起一段堆在末尾),并把 `lastEmittedDraftRef` 记为「编辑器实际读回来的草稿」而不是 props,消除每轮多一段 `@显示名` 的渲染循环
- ResourceReferenceInput.tsx:候选浮层(候选菜单 / 素材选择器)打开时 Enter 不再提交表单,把按键让给 `LexicalTypeaheadMenuPlugin`
- ResourceReferenceInput.tsx:`assets` / `currentVersionAssetReferences` 的 memo 依赖改用内容签名;预览请求在换素材 / 卸载时主动作废上一个 scope;空态文案的嵌套三元抽成 `pickerEmptyMessage`
- usePromptPolish.ts:`polish()` 补 `catch`,注入式 `requestPolish` 拒绝时给出可见失败提示而不是未处理拒绝;`reset()` 递增请求代次作废在飞请求
- ChatPromptPolishReminder.tsx:章节级 Esc 分支补 `busy` 判据,与关闭按钮 / 遮罩同一口径(在飞不许关面板)
- LocalGamePreviewFrame.tsx:资源 id 改用专用净化(允许 `:` 与可打印非 ASCII),修掉 `local-asset:<id>`、`persisted-角色草图.png` 被整条过滤导致引用丢素材关联
- sessionPreview.ts:`get_local_game_preview_status` 读失败不再当作「确认没有在跑」去发停止命令,避免停掉真在跑的活体预览
- useHomeProjectCreation.ts:进项目流程加代次令牌,慢请求后到不再覆盖新项目
- WorkspaceLauncher.tsx:拒收恢复的阶段回写按 `projectId`/`heldRevision`/`snapshotRevision` 关联,不再把恢复结果盖到后续新拒收的提示上
- 用例:`directCodexPolicyRetryInput` 透传、引用指纹区分度、重建不循环、浮层打开时 Enter 不提交、润色拒绝 / reset 作废、预览读失败不停预览
2026-09-12 20:48:13 +08:00

104 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { GameCreationAppPreviewState } from '../../../../../packages/shared/src/contracts/gameCreationApp';
/**
* 进入项目时的预览活体核验。
*
* 为什么不能直接信项目 manifest 里的 `preview`:那是"上次留下的一条记录",不是"现在
* 有没有在跑"。本地预览服务器是**进程内线程**(`src-tauri/src/preview.rs` 的
* `thread::spawn` + loopback listener),非 Linux 平台端口还是**随机临时端口**
* (`bind_loopback_listener_with_linux_fallback` 里的 `TcpListener::bind(..., 0)`),
* 客户端一退 URL 就永久失效;而落盘记录会一直写着 `running`。照它渲染,用户就会被直接
* 丢进一个打不开的运行界面——这正是"有时候进项目看到的是运行界面"的成因。
*
* 所以进项目时问一次内存 registry(`get_local_game_preview_status`):
* - 确实在跑 → 作为**本次会话的预览**交给工作台(工作台据此自动进运行视图);
* - 没在跑但记录写着 running → 记录陈旧:把落盘记录对齐成 stopped(复用现役
* `stop_local_game_preview`,它现在会把陈旧记录一并收尾),本次会话不带预览。
*/
export type SessionPreviewResolution = {
/** 本次会话可用的预览:只有内存 registry 里真的在跑才有值。 */
sessionPreview: GameCreationAppPreviewState | null;
/**
* 会话 manifest 里该投影成的预览状态;`null` 表示落盘记录与真相一致,不用改。
*
* 判定陈旧的同一刻就按真相投影(而不是等落盘写成功):否则一份写不进去的陈旧记录
* (例如项目策略把 `preview.stop` 设成需确认)会让运行入口继续拿着一个打不开的 URL
* 渲染运行画面。落盘那份由这次写入、退出收尾或下次进入时的核对负责收敛。
*/
manifestPreviewPatch: GameCreationAppPreviewState | null;
};
/** 预览命令的最小 invoke 形状(与 App 里 `window.__TAURI__.core.invoke` 一致)。 */
export type PreviewCommandInvoke = <T>(
command: string,
args?: Record<string, unknown>,
) => Promise<T>;
type LocalPreviewStatusLike = {
status?: string | null;
url?: string | null;
port?: number | null;
};
function livePreviewFromStatus(
status: LocalPreviewStatusLike | null,
): GameCreationAppPreviewState | null {
const url = typeof status?.url === 'string' ? status.url.trim() : '';
if (status?.status !== 'running' || !url) {
return null;
}
return {
status: 'running',
url,
port: typeof status.port === 'number' ? status.port : null,
};
}
export async function resolveSessionPreviewOnProjectOpen({
invoke,
projectPath,
recordedPreview,
}: {
invoke: PreviewCommandInvoke | null | undefined;
projectPath: string;
recordedPreview: GameCreationAppPreviewState | null;
}): Promise<SessionPreviewResolution> {
const trimmedProjectPath = projectPath.trim();
if (!invoke || !trimmedProjectPath) {
return { sessionPreview: null, manifestPreviewPatch: null };
}
let status: LocalPreviewStatusLike | null = null;
try {
status = await invoke<LocalPreviewStatusLike>(
'get_local_game_preview_status',
{ projectPath: trimmedProjectPath },
);
} catch {
// 读不到 registry 不等于"确认没有在跑":`stop_local_game_preview` 会停掉
// 真在跑的活体预览,读失败(权限拒绝 / 瞬时 IPC 错误)时不能顺手当清理动作。
// 只按"不带预览进入"处理,让用户自己点「运行」。
return { sessionPreview: null, manifestPreviewPatch: null };
}
const sessionPreview = livePreviewFromStatus(status);
if (sessionPreview) {
return { sessionPreview, manifestPreviewPatch: null };
}
if (recordedPreview?.status !== 'running') {
return { sessionPreview: null, manifestPreviewPatch: null };
}
try {
await invoke('stop_local_game_preview', {
projectPath: trimmedProjectPath,
});
} catch {
// 对齐落盘记录失败不改判:本次会话照样不把陈旧记录当活体用。
}
return {
sessionPreview: null,
manifestPreviewPatch: { status: 'stopped' },
};
}