调整策划项目命名
Project CI / Repository checks (pull_request) Failing after 2m57s
Project CI / Frontend tests (pull_request) Failing after 7m44s
Project CI / Backend tests (pull_request) Successful in 8m6s
Project CI / Native shell tests (pull_request) Failing after 4m31s

策划入口使用带随机短码的策划项目名称

保留游戏与素材入口的现有命名链路

补充自动建项测试与技术方案说明
This commit is contained in:
2026-09-11 07:35:41 +00:00
parent 123c699db9
commit e6e2a5fda6
4 changed files with 31 additions and 10 deletions
@@ -332,6 +332,7 @@ fn automatic_local_game_projects_root(app: &tauri::AppHandle) -> Result<PathBuf,
pub(crate) fn create_automatic_local_game_project_at(
projects_root: &Path,
requested_name: Option<&str>,
planning: bool,
) -> Result<InitLocalProjectResult, String> {
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<String>,
planning: Option<bool>,
) -> Result<InitLocalProjectResult, String> {
create_automatic_local_game_project_at(
&automatic_local_game_projects_root(&app)?,
name.as_deref(),
planning.unwrap_or(false),
)
}
@@ -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("不能包含符号链接"));
@@ -598,6 +598,7 @@ export function useHomeProjectCreation({
'create_automatic_local_game_project',
{
name: suggestedName,
planning: startMode === 'planning',
},
);
try {
@@ -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 客户端会话恢复可观测性与超时兜底