diff --git a/apps/ai-game-creator-shell/src-tauri/src/commands.rs b/apps/ai-game-creator-shell/src-tauri/src/commands.rs index 351fcf45c..9a370cfde 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/commands.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/commands.rs @@ -332,6 +332,7 @@ fn automatic_local_game_projects_root(app: &tauri::AppHandle) -> Result, + planning: bool, ) -> Result { let requested_name = requested_name .map(normalize_game_creation_project_name) @@ -354,9 +355,14 @@ pub(crate) fn create_automatic_local_game_project_at( for _ in 0..16 { let workspace_id = uuid::Uuid::new_v4().simple().to_string(); let short_id = &workspace_id[..8]; - let project_name = requested_name - .clone() - .unwrap_or_else(|| format!("GameAgent 项目 {short_id}")); + let project_name = requested_name.clone().unwrap_or_else(|| { + let prefix = if planning { + "策划项目" + } else { + "GameAgent 项目" + }; + format!("{prefix} {short_id}") + }); let project_root = projects_root.join(format!("gameagent-{short_id}")); match fs::create_dir(&project_root) { Ok(()) => { @@ -395,10 +401,12 @@ pub(crate) fn create_automatic_local_game_project_at( pub(crate) fn create_automatic_local_game_project( app: tauri::AppHandle, name: Option, + planning: Option, ) -> Result { create_automatic_local_game_project_at( &automatic_local_game_projects_root(&app)?, name.as_deref(), + planning.unwrap_or(false), ) } diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs index 048a20a18..f8a9ad84d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs @@ -1411,15 +1411,26 @@ fn project_picker_uses_the_closest_existing_parent_for_a_suggested_new_path() { fn automatic_local_game_project_allocates_unique_initialized_workspaces() { let projects_root = unique_project_path(); - let first = create_automatic_local_game_project_at(&projects_root, None) + let first = create_automatic_local_game_project_at(&projects_root, None, false) .expect("create first automatic workspace"); - let second = create_automatic_local_game_project_at(&projects_root, Some("自定义项目名")) + let second = create_automatic_local_game_project_at(&projects_root, Some("自定义项目名"), false) .expect("create second automatic workspace"); + let planning = create_automatic_local_game_project_at(&projects_root, None, true) + .expect("create planning workspace"); assert_ne!(first.project_path, second.project_path); assert!(first.manifest.name.starts_with("GameAgent 项目 ")); assert_eq!(second.manifest.name, "自定义项目名"); - for result in [first, second] { + let planning_directory = Path::new(&planning.project_path) + .file_name() + .and_then(|name| name.to_str()) + .expect("planning directory name"); + let short_id = planning_directory.strip_prefix("gameagent-").unwrap(); + assert_eq!(short_id.len(), 8); + assert_eq!(planning.manifest.name, format!("策划项目 {short_id}")); + assert_ne!(planning.project_path, first.project_path); + assert_ne!(planning.project_path, second.project_path); + for result in [first, second, planning] { let root = PathBuf::from(&result.project_path); assert_eq!(root.parent(), Some(projects_root.as_path())); assert!(root @@ -1437,7 +1448,7 @@ fn automatic_local_game_project_allocates_unique_initialized_workspaces() { fn automatic_local_game_project_accepts_only_a_safe_custom_name() { let projects_root = unique_project_path(); - let result = create_automatic_local_game_project_at(&projects_root, Some(" 星轨夜航 ")) + let result = create_automatic_local_game_project_at(&projects_root, Some(" 星轨夜航 "), false) .expect("create named automatic workspace"); assert_eq!(result.manifest.name, "星轨夜航"); @@ -1447,7 +1458,7 @@ fn automatic_local_game_project_accepts_only_a_safe_custom_name() { &"长".repeat(GAME_CREATION_PROJECT_NAME_MAX_CHARS + 1), ] { assert!( - create_automatic_local_game_project_at(&projects_root, Some(name)).is_err(), + create_automatic_local_game_project_at(&projects_root, Some(name), false).is_err(), "unsafe name must fail: {name:?}" ); } @@ -1589,7 +1600,7 @@ fn automatic_local_game_project_rejects_symlinked_projects_root() { fs::create_dir_all(&target).expect("create automatic workspace target"); symlink(&target, &projects_root).expect("symlink automatic workspace root"); - let error = create_automatic_local_game_project_at(&projects_root, None) + let error = create_automatic_local_game_project_at(&projects_root, None, false) .expect_err("symlinked automatic workspace root must fail"); assert!(error.contains("不能包含符号链接")); diff --git a/apps/ai-game-creator-shell/src/features/app-shell/useHomeProjectCreation.ts b/apps/ai-game-creator-shell/src/features/app-shell/useHomeProjectCreation.ts index 99ec9ac5d..1ab395f17 100644 --- a/apps/ai-game-creator-shell/src/features/app-shell/useHomeProjectCreation.ts +++ b/apps/ai-game-creator-shell/src/features/app-shell/useHomeProjectCreation.ts @@ -598,6 +598,7 @@ export function useHomeProjectCreation({ 'create_automatic_local_game_project', { name: suggestedName, + planning: startMode === 'planning', }, ); try { diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index 5842afc88..833b1e28e 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -26,7 +26,8 @@ npm 游戏的可预览产物固定为对应 package 目录下的 `dist/index.htm - `.agent/manifest.json` 的 `name` 仍是本地项目显示名唯一事实源;项目组页行尾更多菜单提供行内重命名,保存必须走 Tauri 受控命令、项目写锁、manifest 写锁与既有 ACL/权限校验。重命名只更新 manifest,不改变项目目录、`projectId`、项目类型、任务、资源、版本或远端同步状态;保存成功后当前项目上下文、窗口标题和最近项目检查结果必须回读新 manifest 并保持一致。 - 项目名称统一 trim 后非空、最多 80 个字符且不得包含控制字符。空名称、超长、控制字符或未初始化项目必须失败关闭,原 manifest 保持可用;失败提示只展示安全错误,不泄露宿主路径之外的新内部信息。 -- 首页自动创建工作区前允许一次受限 LLM 名称提炼:输入只包含用户文本需求和附件元数据摘要,输出只允许一个简短中文项目名。Rust 侧统一规范化并在空值、控制字符、超长或多行格式时返回失败;前端把有效名称传给自动建项命令,失败时继续使用现有 `GameAgent 项目 {短ID}` 默认名并照常进入创作,不重试、不阻断、不额外消耗 Provider 请求。 +- 首页自动创建游戏或素材工作区前允许一次受限 LLM 名称提炼:输入只包含用户文本需求,输出只允许一个简短中文项目名。Rust 侧统一规范化并在空值、控制字符、超长或多行格式时返回失败;前端把有效名称传给自动建项命令,失败时继续使用现有 `GameAgent 项目 {短ID}` 默认名并照常进入创作,不重试、不阻断、不额外消耗 Provider 请求。 +- 首页“做方案”直接创建 `策划项目 {短ID}`,短 ID 沿用 Rust 生成的八位随机码,与项目目录的后缀一致。该入口不调用 LLM 命名;项目显示名持久化到 manifest,项目目录与身份规则沿用现有自动建项链路。 - 手动“新建项目”继续默认使用所选文件夹名,不额外调用 LLM;项目创建后用户可通过项目组页重命名修正显示名。自动建项命令的自定义名称必须走同一校验,未提供名称时保持现有默认名,保证旧调用与失败回退路径不变。 ## 2026-09-02 客户端会话恢复可观测性与超时兜底