Files
model-platform/frontend/app/features/platform/CreateScriptModal.tsx
T
2026-08-25 16:52:10 +08:00

198 lines
6.3 KiB
TypeScript

import { type FormEvent } from "react";
import Icon from "../../components/common/Icon";
import type { ScriptType, Visibility } from "../../services/api";
import {
destinationChipClass,
formFieldClass,
formHintClass,
formInputClass,
iconButtonClass,
modalBackdropClass,
modalEyebrowClass,
modalFooterClass,
modalFormClass,
modalHeaderClass,
modalPanelClass,
modalTitleClass,
primaryButtonClass,
secondaryButtonClass,
} from "./modalUi";
type NewScriptForm = {
name: string;
scriptType: ScriptType;
visibility: Visibility;
parentPath: string;
};
type CreateScriptModalProps = {
open: boolean;
creating: boolean;
form: NewScriptForm;
scripts: Array<{
script_type: ScriptType;
script_name: string;
relative_path?: string;
}>;
onFormChange: (form: NewScriptForm) => void;
onSubmit: (event: FormEvent) => void;
onClose: () => void;
};
export function CreateScriptModal({
open,
creating,
form,
scripts: _scripts,
onFormChange,
onSubmit,
onClose,
}: CreateScriptModalProps) {
if (!open) return null;
return (
<div className={modalBackdropClass} role="presentation">
<section className={modalPanelClass} role="dialog" aria-modal="true">
<div className={modalHeaderClass}>
<div>
<span className={modalEyebrowClass}>工作副本</span>
<h2 className={modalTitleClass}>新建构建脚本</h2>
</div>
<button
className={iconButtonClass}
type="button"
aria-label="关闭"
onClick={onClose}
>
<Icon name="close" />
</button>
</div>
<form className={modalFormClass} onSubmit={onSubmit}>
<div className={destinationChipClass}>
<Icon name="folder" size={16} />
保存到:{form.parentPath || "个人根目录"}
</div>
<label className={formFieldClass}>
<span>脚本名称</span>
<input
className={formInputClass}
autoFocus
maxLength={255}
placeholder={form.scriptType === "notebook"
? "例如:数据探索"
: "例如:data_process"}
value={form.name}
onChange={(event) =>
onFormChange({
...form,
name: event.target.value,
})}
/>
<small className={formHintClass}>
系统会自动补充
{form.scriptType === "notebook" ? " .ipynb" : " .py"} 后缀
</small>
</label>
<fieldset className="mb-[17px] grid grid-cols-2 gap-[9px] border-0 p-0">
<legend className="mb-[7px] text-xs font-[650] text-[#3c4e62]">
脚本类型
</legend>
{(
[
{
type: "notebook" as const,
title: "Jupyter Notebook",
desc: "交互式数据探索与模型实验",
icon: "notebook" as const,
iconTone: "bg-[#fff0ed] text-[#dd5a4c]",
size: 22,
},
{
type: "python" as const,
title: "Python 脚本",
desc: "批处理、训练和模型调用任务",
icon: "python" as const,
iconTone: "bg-[#e9f3ff] text-[#2a75c5]",
size: 23,
},
] as const
).map((item) => {
const selected = form.scriptType === item.type;
return (
<button
key={item.type}
className={`relative flex min-h-[72px] cursor-pointer items-center gap-[9px] rounded-[7px] border p-2.5 text-left ${
selected
? "border-[#4d98df] bg-[#f4f9ff] shadow-[0_0_0_2px_rgb(49_133_216/7%)]"
: "border-[#dae2ea] bg-white hover:border-[#a9c6e1]"
}`}
type="button"
onClick={() => onFormChange({
...form,
scriptType: item.type,
})}
>
<span
className={`grid size-[37px] shrink-0 place-items-center rounded-lg ${item.iconTone}`}
>
<Icon name={item.icon} size={item.size} />
</span>
<span className="flex min-w-0 flex-col">
<strong className="text-[11px] text-[#34475b]">
{item.title}
</strong>
<small className="mt-[3px] text-[8px] leading-[1.35] text-[#919eac]">
{item.desc}
</small>
</span>
{selected && (
<span className="absolute top-1.5 right-1.5 grid size-[17px] place-items-center rounded-full bg-[#2e88dd] text-white">
<Icon name="check" size={14} />
</span>
)}
</button>
);
})}
</fieldset>
<label className={formFieldClass}>
<span>可见范围</span>
<select
className={formInputClass}
value={form.visibility}
onChange={(event) =>
onFormChange({
...form,
visibility: event.target.value as Visibility,
})}
>
<option value="private">仅自己可见</option>
<option value="workspace">Workspace 成员可见</option>
<option value="public">公开</option>
</select>
</label>
<div className={modalFooterClass}>
<button
className={secondaryButtonClass}
type="button"
onClick={onClose}
>
取消
</button>
<button
className={primaryButtonClass}
type="submit"
disabled={creating || !form.name.trim()}
>
{creating ? <span className="button-spinner" /> : <Icon name="plus" size={16} />}
{creating ? "正在创建…" : "创建脚本"}
</button>
</div>
</form>
</section>
</div>
);
}