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;