合并 origin/master 到资源工作台 V3 分支:解 4 处冲突并保留两侧行为
- WorkspaceLauncher.tsx:保留本分支清单快照 CAS 拒收提示(manifestMergeNotice 提示条、data-manifest-merge-* 观察点、recoverRejectedManifestSnapshot「重新读取清单」)与 activeVersionId/onActiveVersionChange 版本口径,并入 master 的策划/游戏运行态切换(onMakeGame + switchToGameRuntime、suppressInitialGameTurn 抑制首轮、supervisor key 带 agentRuntimeMode、onSwitchToGameRuntime),planningStartMode 统一取 master 的派生值(上下文 startMode 为 planning 且未切到 game 运行时) - ProjectSupervisorView.tsx:props 同时保留本分支 versions/activeVersionId 与 master 的 designView/onDesignApprove/onDesignClarify/onDesignRetry,DesignAgentSurface 审批链路与本分支资源引用输入区并存;directCodex 输入区保留本分支 @ 引用按钮 + 模型选择 + 发送按钮控制条,发送按钮禁用条件并入 master 的 designView 审批待定口径,模型选择沿用 master 的「对话中可切换」口径 - styles.css:本分支追加的资源画布/输入区样式块与 master 追加的 .design-agent-reasoning 样式块都保留,并补上本分支最后一条规则的收尾大括号 - view/project-development/index.tsx:保留本分支的 image-editor 引用(ImageCanvasProjectAssetPickerDialog、ImageCanvasQuickEditPanelView、ImageCanvasSelectedLayerToolbarView、useImageCanvasFloatingOptionDismiss),去掉 master 对已退役 features/asset-canvas 的 import(本分支已由 resource-canvas 取代),保留 master 的 DesignWorkspacePanel 与 planningStartMode 策划工作台分支 - tests/workspaceLauncherManifestMerge.test.tsx:补 master 新引入的 get_design_agent_runtime_mode mock(返回 null,与 master 各套件同口径),拒收提示断言不变 - tests/appSurface/design-agent.suite.ts:审批待定禁用输入的断言改用本分支 Lexical 输入区的禁用口径(容器 data-disabled + editor.setEditable(false)),断言意图不变 - 验证:npm run typecheck 通过;apps/ai-game-creator-shell typecheck 通过;apps/ai-game-creator-shell/tests 96 个测试文件 1380 通过 4 跳过 0 失败;全仓 vitest 297 通过 2 失败(scripts 下两例为 Windows 权限语义导致的既有失败,相关文件与实现均未参与本次合并);npm run check:encoding 与 git diff --check 通过
This commit is contained in:
@@ -51,6 +51,10 @@ import type {
|
||||
AgentRuntimeUserInputRequest,
|
||||
AgentStatusCard,
|
||||
ChatMessage,
|
||||
DesignAgentInput,
|
||||
DesignClarificationRequest,
|
||||
DesignEvent,
|
||||
DesignView,
|
||||
GameCreatorAgentRuntimeUpdateEvent,
|
||||
GameCreatorChatAgentReply,
|
||||
GameCreatorDirectTurnUpdateEvent,
|
||||
@@ -566,6 +570,14 @@ export function App({
|
||||
const [planningV2Active, setPlanningV2Active] = useState(planningStartMode);
|
||||
const planningV2ActiveRef = useRef(planningStartMode);
|
||||
planningV2ActiveRef.current = planningV2Active;
|
||||
const [designAgentView, setDesignAgentView] = useState<DesignView | null>(
|
||||
null,
|
||||
);
|
||||
const designAgentLaneRef = useRef(planningStartMode);
|
||||
const designAgentTurnRef = useRef<{
|
||||
projectPath: string;
|
||||
clientTurnId: string;
|
||||
} | null>(null);
|
||||
// 做方案入口独立成链:立项策划需要委派、澄清 pending 与 GDD 审批,这些只存在于
|
||||
// Supervisor Runtime;direct-codex 是单回合「生成→试玩→修」循环,没有对应机制。
|
||||
// 因此策划入口不走产品默认的 direct-codex,做游戏与做素材保持 master 的新默认。
|
||||
@@ -734,6 +746,7 @@ export function App({
|
||||
);
|
||||
planningV2SessionRef.current = planningV2Session;
|
||||
const [planningV2TransientReply, setPlanningV2TransientReply] = useState('');
|
||||
const [planningV2Reasoning, setPlanningV2Reasoning] = useState('');
|
||||
const planningV2TurnRef = useRef<{
|
||||
projectPath: string;
|
||||
clientTurnId: string;
|
||||
@@ -850,13 +863,105 @@ export function App({
|
||||
if (planningStartMode) {
|
||||
planningV2ActiveRef.current = true;
|
||||
setPlanningV2Active(true);
|
||||
designAgentLaneRef.current = true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
designAgentLaneRef.current = false;
|
||||
setDesignAgentView(null);
|
||||
applyPlanningV2CommandResult(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
function designMessagesToChat(view: DesignView): ChatMessage[] {
|
||||
return view.messages
|
||||
.filter((message) => message.text.trim())
|
||||
.map((message) => ({
|
||||
role: message.role === 'user' ? 'user' : 'assistant',
|
||||
text: message.text,
|
||||
runtimeOwned: true,
|
||||
messageId: message.id,
|
||||
updatedAt: Date.now(),
|
||||
}));
|
||||
}
|
||||
|
||||
function applyDesignView(view: DesignView, projectPath: string) {
|
||||
designAgentLaneRef.current = true;
|
||||
setDesignAgentView(view);
|
||||
planningV2ActiveRef.current = true;
|
||||
setPlanningV2Active(true);
|
||||
setPlanGddError(view.session.lastError);
|
||||
setChatAgentBusy(view.running);
|
||||
const conversation = designMessagesToChat(view);
|
||||
setMessages(conversation);
|
||||
savedConversationProjectPathRef.current = projectPath;
|
||||
savedConversationCountRef.current = conversation.length;
|
||||
latestMessagesRef.current = conversation;
|
||||
}
|
||||
|
||||
async function hydrateDesignAgentSession(nextProjectPath: string) {
|
||||
const invoke = resolveTauriInvoke();
|
||||
if (!invoke || !nextProjectPath.trim()) {
|
||||
return null;
|
||||
}
|
||||
const view = await invoke<DesignView | null>(
|
||||
'hydrate_design_agent_session',
|
||||
{ projectPath: nextProjectPath },
|
||||
);
|
||||
if (localProjectPathRef.current !== nextProjectPath) {
|
||||
return null;
|
||||
}
|
||||
if (!view) {
|
||||
return null;
|
||||
}
|
||||
applyDesignView(view, nextProjectPath);
|
||||
return view;
|
||||
}
|
||||
|
||||
async function executeDesignAgentTurn(
|
||||
nextProjectPath: string,
|
||||
input: DesignAgentInput,
|
||||
clientTurnId = createAgentChatRunId('design-agent-turn'),
|
||||
) {
|
||||
const invoke = resolveTauriInvoke();
|
||||
if (!invoke) {
|
||||
setProjectSupervisorRuntimeError('需要在 Tauri App 内运行。');
|
||||
return;
|
||||
}
|
||||
designAgentTurnRef.current = {
|
||||
projectPath: nextProjectPath,
|
||||
clientTurnId,
|
||||
};
|
||||
setChatAgentBusy(true);
|
||||
setProjectSupervisorRuntimeError('');
|
||||
setPlanningV2TransientReply('');
|
||||
try {
|
||||
const view = await invoke<DesignView>('continue_design_agent_session', {
|
||||
projectPath: nextProjectPath,
|
||||
clientTurnId,
|
||||
input,
|
||||
});
|
||||
if (localProjectPathRef.current !== nextProjectPath) {
|
||||
return;
|
||||
}
|
||||
applyDesignView(view, nextProjectPath);
|
||||
} catch (error) {
|
||||
if (localProjectPathRef.current !== nextProjectPath) {
|
||||
return;
|
||||
}
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
if (isRuntimeConfigMissingError(message)) {
|
||||
requestRuntimeConfigOpen();
|
||||
}
|
||||
setProjectSupervisorRuntimeError(message);
|
||||
setPlanGddError(message);
|
||||
} finally {
|
||||
designAgentTurnRef.current = null;
|
||||
setPlanningV2TransientReply('');
|
||||
setChatAgentBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
const hydratePlanGddState = useCallback(
|
||||
async (nextProjectPath?: string) => {
|
||||
const targetProjectPath =
|
||||
@@ -1346,6 +1451,9 @@ export function App({
|
||||
setPlanningV2TransientReply('');
|
||||
setPlanningV2Active(planningStartMode);
|
||||
planningV2ActiveRef.current = planningStartMode;
|
||||
designAgentLaneRef.current = planningStartMode;
|
||||
designAgentTurnRef.current = null;
|
||||
setDesignAgentView(null);
|
||||
}
|
||||
|
||||
function syncTerminalProjectSupervisorConversation(
|
||||
@@ -1768,6 +1876,52 @@ export function App({
|
||||
};
|
||||
}, [planningV2Active]);
|
||||
|
||||
useEffect(() => {
|
||||
const listen = window.__TAURI__?.event?.listen;
|
||||
if (!listen || !planningV2Active) {
|
||||
return;
|
||||
}
|
||||
let cleanup: (() => void) | null = null;
|
||||
let disposed = false;
|
||||
void listen<DesignEvent>('design-agent-update', (event) => {
|
||||
const payload = event.payload;
|
||||
const tracked = designAgentTurnRef.current;
|
||||
if (
|
||||
payload.projectPath !== localProjectPathRef.current ||
|
||||
(tracked && payload.clientTurnId !== tracked.clientTurnId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (payload.kind === 'text' && payload.text != null) {
|
||||
setPlanningV2TransientReply(payload.text);
|
||||
}
|
||||
if (payload.reasoningText != null) {
|
||||
setPlanningV2Reasoning(payload.reasoningText);
|
||||
}
|
||||
if (payload.kind === 'tool' && payload.text) {
|
||||
setPlanningV2TransientReply(payload.text);
|
||||
}
|
||||
if (payload.view) {
|
||||
applyDesignView(payload.view, payload.projectPath);
|
||||
}
|
||||
})
|
||||
.then((unlisten) => {
|
||||
if (disposed) {
|
||||
unlisten();
|
||||
return;
|
||||
}
|
||||
cleanup = unlisten;
|
||||
})
|
||||
.catch(() => undefined);
|
||||
return () => {
|
||||
disposed = true;
|
||||
cleanup?.();
|
||||
};
|
||||
// applyDesignView 读的是 refs 和当前项目路径,
|
||||
// 把它写进依赖会在每轮回复时重订事件。
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [planningV2Active]);
|
||||
|
||||
useEffect(() => {
|
||||
const listen = window.__TAURI__?.event?.listen;
|
||||
if (!listen) {
|
||||
@@ -2959,6 +3113,29 @@ export function App({
|
||||
// existing open-project behavior for older projects.
|
||||
let planningV2: PlanningSessionCommandResultV2 | null = null;
|
||||
if (projectSupervisorOnly || planningStartMode) {
|
||||
try {
|
||||
const design = await hydrateDesignAgentSession(nextProjectPath);
|
||||
if (design) {
|
||||
if (
|
||||
projectSupervisorHistoryLoadVersionRef.current !== loadVersion ||
|
||||
localProjectPathRef.current !== nextProjectPath
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setWorkspaceStatus((workspaceStatus) =>
|
||||
workspaceStatus === '等待确认'
|
||||
? `已打开:${nextProjectPath}`
|
||||
: workspaceStatus,
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
if (planningStartMode) {
|
||||
setPlanGddError(
|
||||
`策划会话无法恢复:${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
try {
|
||||
planningV2 = await invoke<PlanningSessionCommandResultV2 | null>(
|
||||
'hydrate_planning_session_v2',
|
||||
@@ -2982,8 +3159,11 @@ export function App({
|
||||
planningV2ActiveRef.current = true;
|
||||
setPlanningV2Active(true);
|
||||
if (planningV2) {
|
||||
designAgentLaneRef.current = false;
|
||||
setDesignAgentView(null);
|
||||
applyPlanningV2CommandResult(planningV2);
|
||||
} else {
|
||||
designAgentLaneRef.current = true;
|
||||
planningV2SessionRef.current = null;
|
||||
setPlanningV2Session(null);
|
||||
setPlanGddState(null);
|
||||
@@ -5850,6 +6030,14 @@ export function App({
|
||||
setProjectSupervisorRuntimeError('请先初始化本地项目');
|
||||
return;
|
||||
}
|
||||
if (designAgentLaneRef.current) {
|
||||
await executeDesignAgentTurn(
|
||||
nextProjectPath,
|
||||
{ type: 'message', text: prompt },
|
||||
directConversationTurnId ?? createAgentChatRunId('design-agent-turn'),
|
||||
);
|
||||
return;
|
||||
}
|
||||
await executePlanningV2Turn(
|
||||
nextProjectPath,
|
||||
prompt,
|
||||
@@ -11401,6 +11589,8 @@ export function App({
|
||||
agent.id !== PROJECT_SUPERVISOR_AGENT_ID &&
|
||||
(agent.runtimeStatus !== null || agent.hasRecentEvidence),
|
||||
);
|
||||
const useDesignAgentSurface =
|
||||
Boolean(designAgentView) || (planningStartMode && !planningV2Session);
|
||||
|
||||
if (projectSupervisorOnly && supervisorChatOnly) {
|
||||
const supervisorProjectPath =
|
||||
@@ -11489,6 +11679,7 @@ export function App({
|
||||
? directCodexTransientReply
|
||||
: projectSupervisorTransientReply
|
||||
}
|
||||
designReasoning={planningV2Reasoning}
|
||||
visibleMessages={visibleMessages}
|
||||
visibleProfessionalAgentCards={visibleProfessionalAgentCards}
|
||||
showProfessionalCollaboration={
|
||||
@@ -11502,6 +11693,95 @@ export function App({
|
||||
onPlanGddRefresh={() => void hydratePlanGddState()}
|
||||
onPlanGddDecision={decidePlanGdd}
|
||||
planningLane={planningV2Active}
|
||||
designView={useDesignAgentSurface ? designAgentView : null}
|
||||
onDesignApprove={
|
||||
useDesignAgentSurface
|
||||
? (requestId, approved) => {
|
||||
const nextProjectPath = resolveChatProjectPath(localProject);
|
||||
const invoke = resolveTauriInvoke();
|
||||
if (!nextProjectPath || !invoke) {
|
||||
return;
|
||||
}
|
||||
const clientTurnId = createAgentChatRunId('design-agent-turn');
|
||||
designAgentTurnRef.current = {
|
||||
projectPath: nextProjectPath,
|
||||
clientTurnId,
|
||||
};
|
||||
setPlanningV2TransientReply('');
|
||||
setChatAgentBusy(true);
|
||||
setPlanGddDecisionBusy(true);
|
||||
void invoke<DesignView>('decide_design_phase', {
|
||||
projectPath: nextProjectPath,
|
||||
clientTurnId,
|
||||
requestId,
|
||||
approved,
|
||||
})
|
||||
.then((view) => {
|
||||
if (
|
||||
localProjectPathRef.current !== nextProjectPath ||
|
||||
designAgentTurnRef.current?.projectPath !==
|
||||
nextProjectPath ||
|
||||
designAgentTurnRef.current?.clientTurnId !== clientTurnId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
applyDesignView(view, nextProjectPath);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (
|
||||
localProjectPathRef.current !== nextProjectPath ||
|
||||
designAgentTurnRef.current?.projectPath !==
|
||||
nextProjectPath ||
|
||||
designAgentTurnRef.current?.clientTurnId !== clientTurnId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setPlanGddError(String(error));
|
||||
})
|
||||
.finally(() => {
|
||||
if (
|
||||
localProjectPathRef.current !== nextProjectPath ||
|
||||
designAgentTurnRef.current?.projectPath !==
|
||||
nextProjectPath ||
|
||||
designAgentTurnRef.current?.clientTurnId !== clientTurnId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
designAgentTurnRef.current = null;
|
||||
setPlanningV2TransientReply('');
|
||||
setChatAgentBusy(false);
|
||||
setPlanGddDecisionBusy(false);
|
||||
});
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onDesignClarify={
|
||||
useDesignAgentSurface
|
||||
? (question: DesignClarificationRequest, optionIndex, text) => {
|
||||
const nextProjectPath = resolveChatProjectPath(localProject);
|
||||
if (!nextProjectPath) {
|
||||
return;
|
||||
}
|
||||
void executeDesignAgentTurn(nextProjectPath, {
|
||||
type: 'clarification',
|
||||
requestId: question.requestId,
|
||||
optionIndex,
|
||||
text: text.trim() ? text : null,
|
||||
});
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onDesignRetry={
|
||||
useDesignAgentSurface
|
||||
? () => {
|
||||
const nextProjectPath = resolveChatProjectPath(localProject);
|
||||
if (!nextProjectPath) {
|
||||
return;
|
||||
}
|
||||
void executeDesignAgentTurn(nextProjectPath, { type: 'retry' });
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onMakeGameFromApprovedGdd={
|
||||
onMakeGameFromApprovedGdd
|
||||
? () =>
|
||||
|
||||
@@ -940,6 +940,68 @@ export interface ChatMessage {
|
||||
runtimeOwned?: boolean;
|
||||
}
|
||||
|
||||
export type DesignAgentInput =
|
||||
| { type: 'message'; text: string }
|
||||
| {
|
||||
type: 'clarification';
|
||||
requestId: string;
|
||||
optionIndex?: number | null;
|
||||
text?: string | null;
|
||||
}
|
||||
| { type: 'retry' };
|
||||
|
||||
export interface DesignApprovalRequest {
|
||||
requestId: string;
|
||||
phase: string;
|
||||
submittedAt: number;
|
||||
}
|
||||
|
||||
export interface DesignClarificationRequest {
|
||||
requestId: string;
|
||||
question: string;
|
||||
options: string[];
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
export interface DesignSessionSummary {
|
||||
sessionId: string;
|
||||
projectId: string;
|
||||
currentPhase: string;
|
||||
approvedPhases: string[];
|
||||
pendingApproval: DesignApprovalRequest | null;
|
||||
pendingClarification: DesignClarificationRequest | null;
|
||||
turnIndex: number;
|
||||
lastError: string | null;
|
||||
}
|
||||
|
||||
export interface DesignAgentMessage {
|
||||
id: string;
|
||||
role: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface DesignView {
|
||||
session: DesignSessionSummary;
|
||||
messages: DesignAgentMessage[];
|
||||
running: boolean;
|
||||
canRetry: boolean;
|
||||
}
|
||||
|
||||
export interface DesignEvent {
|
||||
projectPath: string;
|
||||
clientTurnId: string;
|
||||
kind: string;
|
||||
messageId?: string | null;
|
||||
text?: string | null;
|
||||
reasoningText?: string | null;
|
||||
view?: DesignView | null;
|
||||
}
|
||||
|
||||
export interface DesignWorkspaceEntry {
|
||||
path: string;
|
||||
kind: string;
|
||||
}
|
||||
|
||||
export interface AgentProgressEvent {
|
||||
projectPath: string;
|
||||
stage: string;
|
||||
|
||||
@@ -64,6 +64,13 @@ export function WorkspaceLauncherShell({
|
||||
title: string;
|
||||
message: string;
|
||||
} | null>(null);
|
||||
// 「做成游戏」切换记录按项目上下文(路径 + createdAt)定位。运行模式必须随
|
||||
// currentProjectContext 同步派生,不能靠 effect 后置修正:首帧挂错 lane 会先
|
||||
// 以游戏运行时挂载并消耗首轮 claim,重挂后的策划实例再也发不出首轮。
|
||||
const [gameRuntimeSwitch, setGameRuntimeSwitch] = useState<{
|
||||
projectPath: string;
|
||||
createdAt: number;
|
||||
} | null>(null);
|
||||
const developerAgent = useDeveloperAgentPanel(launcherView);
|
||||
const homeProject = useHomeProjectCreation({
|
||||
setStatus,
|
||||
@@ -87,6 +94,17 @@ export function WorkspaceLauncherShell({
|
||||
createHomeDraftAutomatically,
|
||||
openProject,
|
||||
} = homeProject;
|
||||
const switchedToGameRuntime =
|
||||
gameRuntimeSwitch !== null &&
|
||||
currentProjectContext !== null &&
|
||||
gameRuntimeSwitch.projectPath === currentProjectContext.projectPath &&
|
||||
gameRuntimeSwitch.createdAt === currentProjectContext.createdAt;
|
||||
const planningStartMode =
|
||||
currentProjectContext?.startMode === 'planning' && !switchedToGameRuntime;
|
||||
const agentRuntimeMode: 'design' | 'game' = planningStartMode
|
||||
? 'design'
|
||||
: 'game';
|
||||
const suppressInitialGameTurn = switchedToGameRuntime;
|
||||
const activeProjectContextRef = useRef(currentProjectContext);
|
||||
const manifestMergeRef = useRef<ProjectManifestMergeState | null>(null);
|
||||
activeProjectContextRef.current = currentProjectContext;
|
||||
@@ -418,6 +436,19 @@ export function WorkspaceLauncherShell({
|
||||
});
|
||||
}
|
||||
|
||||
async function switchToGameRuntime(nextProjectPath: string) {
|
||||
const invoke = resolveTauriInvoke();
|
||||
if (!invoke) throw new Error('需要在陶泥儿客户端内运行');
|
||||
await invoke('set_design_agent_runtime_mode', {
|
||||
projectPath: nextProjectPath,
|
||||
activeRuntime: 'game',
|
||||
});
|
||||
setGameRuntimeSwitch({
|
||||
projectPath: nextProjectPath,
|
||||
createdAt: currentProjectContext?.createdAt ?? 0,
|
||||
});
|
||||
}
|
||||
|
||||
const currentHelpTitle =
|
||||
launcherView === 'guide'
|
||||
? '使用指南'
|
||||
@@ -557,30 +588,43 @@ export function WorkspaceLauncherShell({
|
||||
preview={activeProjectPreview}
|
||||
agentRuntimeSummaries={activeProjectAgentRuntimeSummaries}
|
||||
agentResults={activeProjectAgentResults}
|
||||
planningStartMode={currentProjectContext.startMode === 'planning'}
|
||||
planningStartMode={planningStartMode}
|
||||
activeVersionId={activeVersionId}
|
||||
onActiveVersionChange={setActiveVersionId}
|
||||
onPlay={() =>
|
||||
requestCurrentProjectPlay(currentProjectContext.projectPath)
|
||||
}
|
||||
onMakeGame={() =>
|
||||
void switchToGameRuntime(currentProjectContext.projectPath)
|
||||
}
|
||||
onManifestChange={syncActiveProjectManifest}
|
||||
onHomeOpen={() => setLauncherView('home')}
|
||||
onProjectsOpen={() => setLauncherView('projects')}
|
||||
supervisor={
|
||||
<ProjectSupervisor
|
||||
key={currentProjectContext.projectPath}
|
||||
key={`${currentProjectContext.projectPath}:${agentRuntimeMode}`}
|
||||
initialProjectPath={currentProjectContext.projectPath}
|
||||
initialProjectManifest={currentProjectContext.manifest}
|
||||
initialProjectKind={currentProjectContext.projectKind}
|
||||
initialSupervisorMessage={currentProjectContext.initialPrompt}
|
||||
initialCreationType={currentProjectContext.creationType}
|
||||
initialAttachments={currentProjectContext.attachments}
|
||||
initialSupervisorMessage={
|
||||
!suppressInitialGameTurn
|
||||
? currentProjectContext.initialPrompt
|
||||
: ''
|
||||
}
|
||||
initialCreationType={
|
||||
!suppressInitialGameTurn
|
||||
? currentProjectContext.creationType
|
||||
: null
|
||||
}
|
||||
initialAttachments={
|
||||
!suppressInitialGameTurn
|
||||
? currentProjectContext.attachments
|
||||
: []
|
||||
}
|
||||
activeVersionId={activeVersionId}
|
||||
orchestrationMode="single-supervisor"
|
||||
projectSupervisorOnly
|
||||
planningStartMode={
|
||||
currentProjectContext.startMode === 'planning'
|
||||
}
|
||||
planningStartMode={planningStartMode}
|
||||
playRequest={playRequest}
|
||||
onPlayRequestHandled={handlePlayRequestHandled}
|
||||
onManifestChange={syncActiveProjectManifest}
|
||||
@@ -590,6 +634,7 @@ export function WorkspaceLauncherShell({
|
||||
}
|
||||
onAgentResultsChange={setActiveProjectAgentResults}
|
||||
onMakeGameFromApprovedGdd={startGameFromApprovedGdd}
|
||||
onSwitchToGameRuntime={switchToGameRuntime}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -61,6 +61,7 @@ export type ProjectSupervisorComponentProps = {
|
||||
) => void;
|
||||
onAgentResultsChange?: (results: ProjectAgentResultSummary[]) => void;
|
||||
onMakeGameFromApprovedGdd?: (projectPath: string) => Promise<void>;
|
||||
onSwitchToGameRuntime?: (projectPath: string) => Promise<void>;
|
||||
};
|
||||
|
||||
export type WorkspaceLauncherShellProps = WorkspaceLauncherProps & {
|
||||
|
||||
@@ -274,6 +274,12 @@ export function useHomeProjectCreation({
|
||||
attachments: HomeAttachmentDraft[],
|
||||
startMode: ProjectStartMode,
|
||||
) {
|
||||
if (startMode === 'planning') {
|
||||
await invoke('set_design_agent_runtime_mode', {
|
||||
projectPath: result.projectPath,
|
||||
activeRuntime: 'design',
|
||||
});
|
||||
}
|
||||
const importedAttachments = await importHomeAttachments(
|
||||
invoke,
|
||||
result.projectPath,
|
||||
@@ -499,6 +505,11 @@ export function useHomeProjectCreation({
|
||||
setStatus('这不是已初始化的 AI 游戏项目,请使用新建项目。');
|
||||
return;
|
||||
}
|
||||
const runtimeMode = await invoke<{
|
||||
activeRuntime: 'design' | 'game';
|
||||
} | null>('get_design_agent_runtime_mode', {
|
||||
projectPath: trimmedProjectPath,
|
||||
});
|
||||
setStatus('已打开项目');
|
||||
await enterProjectDevelopment({
|
||||
projectPath: trimmedProjectPath,
|
||||
@@ -516,7 +527,7 @@ export function useHomeProjectCreation({
|
||||
trimmedProjectPath,
|
||||
),
|
||||
creationType: null,
|
||||
startMode: null,
|
||||
startMode: runtimeMode?.activeRuntime === 'design' ? 'planning' : null,
|
||||
initialPrompt: '',
|
||||
attachments: [],
|
||||
recentRunStatus: directoryStatus.recentRunStatus,
|
||||
@@ -620,6 +631,7 @@ export function useHomeProjectCreation({
|
||||
'create_automatic_local_game_project',
|
||||
{
|
||||
name: suggestedName,
|
||||
planning: startMode === 'planning',
|
||||
},
|
||||
);
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Check, RotateCcw, X } from 'lucide-react';
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
|
||||
import type { DesignClarificationRequest, DesignView } from '../../app/types';
|
||||
|
||||
const PHASE_LABELS: Record<string, string> = {
|
||||
concept: '概念设计',
|
||||
top_design: '顶层设计',
|
||||
architecture: '系统架构',
|
||||
systems: '系统文档',
|
||||
tdd: '技术文档',
|
||||
consultant: '顾问',
|
||||
};
|
||||
|
||||
type DesignAgentSurfaceProps = {
|
||||
view: DesignView | null;
|
||||
busy: boolean;
|
||||
error: string | null;
|
||||
onApprove: (requestId: string, approved: boolean) => void;
|
||||
onClarify: (
|
||||
question: DesignClarificationRequest,
|
||||
optionIndex: number | null,
|
||||
text: string,
|
||||
) => void;
|
||||
onRetry: () => void;
|
||||
};
|
||||
|
||||
export function DesignAgentSurface({
|
||||
view,
|
||||
busy,
|
||||
error,
|
||||
onApprove,
|
||||
onClarify,
|
||||
onRetry,
|
||||
}: DesignAgentSurfaceProps) {
|
||||
const [clarifyText, setClarifyText] = useState('');
|
||||
const phase = view?.session.currentPhase ?? 'concept';
|
||||
const pending = view?.session.pendingApproval;
|
||||
const clarification = view?.session.pendingClarification;
|
||||
useLayoutEffect(() => {
|
||||
setClarifyText('');
|
||||
}, [clarification?.requestId]);
|
||||
return (
|
||||
<section className="design-agent-controls" aria-label="策划阶段控制">
|
||||
<div className="design-agent-controls__header">
|
||||
<div>
|
||||
<span>当前阶段</span>
|
||||
<div className="design-agent-controls__phase-title">
|
||||
<strong>{PHASE_LABELS[phase] ?? phase}</strong>
|
||||
</div>
|
||||
</div>
|
||||
{view?.running ? (
|
||||
<span className="design-agent-controls__status">工作中</span>
|
||||
) : null}
|
||||
</div>
|
||||
{error ? <p className="design-agent-controls__error">{error}</p> : null}
|
||||
{pending && phase !== 'consultant' ? (
|
||||
<div className="design-agent-approval">
|
||||
<p>当前阶段产物已提交,是否批准进入下一阶段?</p>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="批准"
|
||||
disabled={busy}
|
||||
onClick={() => onApprove(pending.requestId, true)}
|
||||
>
|
||||
<Check size={15} aria-hidden="true" />
|
||||
批准
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="继续修改"
|
||||
disabled={busy}
|
||||
onClick={() => onApprove(pending.requestId, false)}
|
||||
>
|
||||
<X size={15} aria-hidden="true" />
|
||||
继续修改
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{clarification ? (
|
||||
<div className="design-agent-clarify">
|
||||
<p>{clarification.question}</p>
|
||||
<div className="design-agent-clarify__options">
|
||||
{clarification.options.map((option, index) => (
|
||||
<button
|
||||
key={`${clarification.requestId}-${index}`}
|
||||
type="button"
|
||||
disabled={busy}
|
||||
onClick={() => onClarify(clarification, index, '')}
|
||||
>
|
||||
{option}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<textarea
|
||||
rows={2}
|
||||
value={clarifyText}
|
||||
disabled={busy}
|
||||
placeholder="或者直接输入你的回答"
|
||||
onChange={(event) => setClarifyText(event.currentTarget.value)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
disabled={busy || !clarifyText.trim()}
|
||||
onClick={() => onClarify(clarification, null, clarifyText)}
|
||||
>
|
||||
提交回答
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
{view?.canRetry ? (
|
||||
<button
|
||||
className="design-agent-retry"
|
||||
type="button"
|
||||
disabled={busy}
|
||||
onClick={onRetry}
|
||||
>
|
||||
<RotateCcw size={15} aria-hidden="true" />
|
||||
重试本轮
|
||||
</button>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+54
-13
@@ -16,6 +16,7 @@ import type {
|
||||
PlanGddDecisionAction,
|
||||
PlanGddStateViewV1,
|
||||
} from '../../app/types';
|
||||
import type { DesignClarificationRequest, DesignView } from '../../app/types';
|
||||
import { ChatMarkdownMessage } from '../../components/ChatMarkdownMessage';
|
||||
import {
|
||||
projectProfessionalAgentLabel,
|
||||
@@ -30,6 +31,7 @@ import {
|
||||
ConversationModelSelect,
|
||||
type ConversationModelSelectHandle,
|
||||
} from './ConversationModelSelect';
|
||||
import { DesignAgentSurface } from './DesignAgentSurface';
|
||||
import { PlanGddSurface } from './GddApprovalCard';
|
||||
import {
|
||||
pendingCommandDetail,
|
||||
@@ -91,6 +93,7 @@ type ProjectSupervisorViewProps = RuntimePanelProps & {
|
||||
projectPath: string;
|
||||
showProfessionalCollaboration?: boolean;
|
||||
transientReply: string;
|
||||
designReasoning?: string;
|
||||
visibleMessages: ChatMessage[];
|
||||
visibleProfessionalAgentCards: AgentStatusCard[];
|
||||
workspaceStatus: string;
|
||||
@@ -106,6 +109,14 @@ type ProjectSupervisorViewProps = RuntimePanelProps & {
|
||||
) => Promise<void>;
|
||||
onMakeGameFromApprovedGdd?: () => Promise<void>;
|
||||
versions?: import('../../../../../packages/shared/src/contracts/gameCreationApp').GameIterationVersion[];
|
||||
designView?: DesignView | null;
|
||||
onDesignApprove?: (requestId: string, approved: boolean) => void;
|
||||
onDesignClarify?: (
|
||||
question: DesignClarificationRequest,
|
||||
optionIndex: number | null,
|
||||
text: string,
|
||||
) => void;
|
||||
onDesignRetry?: () => void;
|
||||
};
|
||||
|
||||
export function ProjectSupervisorView({
|
||||
@@ -134,6 +145,7 @@ export function ProjectSupervisorView({
|
||||
projectPath,
|
||||
showProfessionalCollaboration = true,
|
||||
transientReply,
|
||||
designReasoning = '',
|
||||
visibleMessages,
|
||||
visibleProfessionalAgentCards,
|
||||
workspaceStatus,
|
||||
@@ -146,6 +158,10 @@ export function ProjectSupervisorView({
|
||||
onPlanGddDecision,
|
||||
onMakeGameFromApprovedGdd,
|
||||
versions,
|
||||
designView = null,
|
||||
onDesignApprove,
|
||||
onDesignClarify,
|
||||
onDesignRetry,
|
||||
...runtimePanelProps
|
||||
}: ProjectSupervisorViewProps) {
|
||||
const planningSurfaceActive =
|
||||
@@ -177,6 +193,8 @@ export function ProjectSupervisorView({
|
||||
disabled={
|
||||
runtimePanelProps.controlBusy ||
|
||||
needsUserInput ||
|
||||
Boolean(designView?.session.pendingApproval) ||
|
||||
Boolean(designView?.session.pendingClarification) ||
|
||||
(directCodex && (!modelReady || modelValidating))
|
||||
}
|
||||
>
|
||||
@@ -193,17 +211,28 @@ export function ProjectSupervisorView({
|
||||
aria-label={directCodex ? '陶泥儿项目对话' : '项目总控对话'}
|
||||
>
|
||||
<div className="project-supervisor-conversation">
|
||||
<PlanGddSurface
|
||||
state={planGddState}
|
||||
active={planningSurfaceActive}
|
||||
projectPath={projectPath}
|
||||
hydrateBusy={planGddHydrateBusy}
|
||||
decisionBusy={planGddDecisionBusy}
|
||||
error={planGddError}
|
||||
onRefresh={onPlanGddRefresh}
|
||||
onDecision={onPlanGddDecision}
|
||||
onMakeGame={onMakeGameFromApprovedGdd}
|
||||
/>
|
||||
{designView || onDesignApprove ? (
|
||||
<DesignAgentSurface
|
||||
view={designView}
|
||||
busy={runtimePanelProps.controlBusy || planGddDecisionBusy}
|
||||
error={planGddError}
|
||||
onApprove={onDesignApprove ?? (() => undefined)}
|
||||
onClarify={onDesignClarify ?? (() => undefined)}
|
||||
onRetry={onDesignRetry ?? (() => undefined)}
|
||||
/>
|
||||
) : (
|
||||
<PlanGddSurface
|
||||
state={planGddState}
|
||||
active={planningSurfaceActive}
|
||||
projectPath={projectPath}
|
||||
hydrateBusy={planGddHydrateBusy}
|
||||
decisionBusy={planGddDecisionBusy}
|
||||
error={planGddError}
|
||||
onRefresh={onPlanGddRefresh}
|
||||
onDecision={onPlanGddDecision}
|
||||
onMakeGame={onMakeGameFromApprovedGdd}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
ref={messagesRef}
|
||||
className="message-list project-supervisor-message-list"
|
||||
@@ -230,6 +259,12 @@ export function ProjectSupervisorView({
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{designReasoning ? (
|
||||
<details className="design-agent-reasoning">
|
||||
<summary>显示思考过程</summary>
|
||||
<pre>{designReasoning}</pre>
|
||||
</details>
|
||||
) : null}
|
||||
{directCodex &&
|
||||
(runtimePanelProps.controlBusy || Boolean(directProcessDetail)) ? (
|
||||
<section
|
||||
@@ -393,7 +428,11 @@ export function ProjectSupervisorView({
|
||||
assets={chatProjectAssets}
|
||||
projectPath={projectPath}
|
||||
disabled={
|
||||
runtimePanelProps.controlBusy || needsUserInput || modelValidating
|
||||
runtimePanelProps.controlBusy ||
|
||||
needsUserInput ||
|
||||
modelValidating ||
|
||||
Boolean(designView?.session.pendingApproval) ||
|
||||
Boolean(designView?.session.pendingClarification)
|
||||
}
|
||||
rows={3}
|
||||
value={chatInput}
|
||||
@@ -423,7 +462,9 @@ export function ProjectSupervisorView({
|
||||
<div className="project-supervisor-composer-controls-right">
|
||||
<ConversationModelSelect
|
||||
ref={modelSelectRef}
|
||||
disabled={runtimePanelProps.controlBusy || needsUserInput}
|
||||
// 允许在对话进行中切换模型:写回的是客户端配置,只影响后续轮次,
|
||||
// 当前回合不受影响;发送按钮仍由 controlBusy / modelReady 把关。
|
||||
disabled={needsUserInput}
|
||||
onReady={setModelReady}
|
||||
projectPath={projectPath}
|
||||
/>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -89,6 +89,7 @@ import { ImageCanvasProjectAssetPickerDialog } from '../../../../../src/componen
|
||||
import { ImageCanvasQuickEditPanelView } from '../../../../../src/components/image-editor/ImageCanvasQuickEditPanelView';
|
||||
import { ImageCanvasSelectedLayerToolbarView } from '../../../../../src/components/image-editor/ImageCanvasSelectedLayerToolbarView';
|
||||
import { useImageCanvasFloatingOptionDismiss } from '../../../../../src/components/image-editor/useImageCanvasFloatingOptionDismiss';
|
||||
import { DesignWorkspacePanel } from '../../features/project-workspace/DesignWorkspacePanel';
|
||||
import {
|
||||
LocalGamePreviewFrame,
|
||||
type LocalGamePreviewInspectSelection,
|
||||
@@ -540,6 +541,7 @@ export type ProjectDevelopmentViewProps = {
|
||||
onHomeOpen: () => void;
|
||||
onProjectsOpen: () => void;
|
||||
onPlay?: () => void;
|
||||
onMakeGame?: () => void;
|
||||
onManifestChange?: (
|
||||
projectPath: string,
|
||||
manifest: GameCreationAppManifest,
|
||||
@@ -1314,6 +1316,7 @@ export default function ProjectDevelopmentView({
|
||||
onActiveVersionChange,
|
||||
onManifestChange,
|
||||
onPlay,
|
||||
onMakeGame,
|
||||
}: ProjectDevelopmentViewProps) {
|
||||
const professionalDagVisible = orchestrationMode === 'professional-dag';
|
||||
const [mode, setMode] = useState<WorkbenchMode>('resources');
|
||||
@@ -6319,6 +6322,36 @@ export default function ProjectDevelopmentView({
|
||||
projectId: manifest.projectId,
|
||||
});
|
||||
|
||||
if (planningStartMode) {
|
||||
return (
|
||||
<section
|
||||
className="launcher-page launcher-project-development game-project-workbench game-project-workbench--design"
|
||||
aria-label="策划工作台"
|
||||
>
|
||||
<div className="game-workbench-layout game-workbench-layout--design">
|
||||
<section className="game-workbench-stage" aria-label="策划工作区">
|
||||
<DesignWorkspacePanel
|
||||
projectPath={projectPath}
|
||||
onMakeGame={onMakeGame}
|
||||
/>
|
||||
</section>
|
||||
<aside className="game-workbench-chat" aria-label="策划 Agent 对话">
|
||||
<header>
|
||||
<div className="game-workbench-chat-title">
|
||||
<strong>与策划 Agent 对话</strong>
|
||||
<small>持续协作推进设计</small>
|
||||
</div>
|
||||
{walletEntry ? (
|
||||
<div className="game-workbench-chat-wallet">{walletEntry}</div>
|
||||
) : null}
|
||||
</header>
|
||||
{supervisor}
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
className="launcher-page launcher-project-development game-project-workbench"
|
||||
|
||||
Reference in New Issue
Block a user