diff --git a/frontend/app/components/platform/CreateScriptModal.tsx b/frontend/app/components/platform/CreateScriptModal.tsx index 9c8a919..b02ae45 100644 --- a/frontend/app/components/platform/CreateScriptModal.tsx +++ b/frontend/app/components/platform/CreateScriptModal.tsx @@ -13,7 +13,11 @@ type CreateScriptModalProps = { open: boolean; creating: boolean; form: NewScriptForm; - scripts: Array<{ script_type: ScriptType; script_name: string }>; + scripts: Array<{ + script_type: ScriptType; + script_name: string; + relative_path?: string; + }>; onFormChange: (form: NewScriptForm) => void; onSubmit: (event: FormEvent) => void; onClose: () => void; @@ -35,10 +39,21 @@ export function CreateScriptModal({ const normalizedName = requestedName.toLocaleLowerCase().endsWith(suffix) ? requestedName : `${requestedName}${suffix}`; - const duplicate = scripts.some((script) => - script.script_type === form.scriptType - && script.script_name.toLocaleLowerCase() === normalizedName.toLocaleLowerCase() - ); + const duplicate = scripts.some((script) => { + if (script.script_type !== form.scriptType) return false; + if (script.script_name.toLocaleLowerCase() + !== normalizedName.toLocaleLowerCase()) return false; + // Same name in a different subdirectory is allowed; mirror the + // backend name_clash scope (StorageObjects.relative_path JOIN). + if (!script.relative_path) return true; + const segments = script.relative_path.replaceAll("\\", "/") + .split("/").slice(2); + const existingUserPath = segments.join("/"); + const existingParent = existingUserPath.includes("/") + ? existingUserPath.slice(0, existingUserPath.lastIndexOf("/")) + : ""; + return existingParent === form.parentPath; + }); return (
diff --git a/frontend/app/features/platform/ScriptsPage.tsx b/frontend/app/features/platform/ScriptsPage.tsx index 0ae8f90..86038bc 100644 --- a/frontend/app/features/platform/ScriptsPage.tsx +++ b/frontend/app/features/platform/ScriptsPage.tsx @@ -392,6 +392,7 @@ export default function ScriptsPage() { scripts={scripts.map((s) => ({ script_type: s.script_type, script_name: s.script_name, + relative_path: s.relative_path, }))} onFormChange={setCreateForm} onSubmit={handleCreateSubmit} diff --git a/frontend/app/features/platform/state/scriptWorkspaceStore.ts b/frontend/app/features/platform/state/scriptWorkspaceStore.ts index 24786f4..c95fef3 100644 --- a/frontend/app/features/platform/state/scriptWorkspaceStore.ts +++ b/frontend/app/features/platform/state/scriptWorkspaceStore.ts @@ -758,12 +758,19 @@ export const useScriptWorkspaceStore = create((set, get) => { const normalizedName = requestedName.toLocaleLowerCase().endsWith(suffix) ? requestedName : `${requestedName}${suffix}`; - const duplicate = get().scripts.some( - (script) => - script.script_type === form.scriptType - && script.script_name.toLocaleLowerCase() - === normalizedName.toLocaleLowerCase(), - ); + const duplicate = get().scripts.some((script) => { + if (script.script_type !== form.scriptType) return false; + if (script.script_name.toLocaleLowerCase() + !== normalizedName.toLocaleLowerCase()) return false; + // Same name in a different subdirectory is allowed: mirror the + // backend name_clash check, which JOINs StorageObjects and scopes + // by relative_path. + const existingUserPath = ownedScriptPath(script); + const existingParent = existingUserPath.includes("/") + ? existingUserPath.slice(0, existingUserPath.lastIndexOf("/")) + : ""; + return existingParent === form.parentPath; + }); if (duplicate) { pushToast("error", `${normalizedName} 已存在,请更换名称`); return null;