190 lines
6.1 KiB
TypeScript
190 lines
6.1 KiB
TypeScript
import { type FormEvent } from "react";
|
|
import { BookOpen, Check, FileCode, Folder, Loader2Icon, Plus } from "lucide-react";
|
|
import type { ScriptType, Visibility } from "../../services/api";
|
|
import {
|
|
AppFormDialog,
|
|
dialogPrimaryButtonClass,
|
|
dialogSecondaryButtonClass,
|
|
} from "~/components/common/AppFormDialog";
|
|
import { Button } from "~/components/ui/button";
|
|
import {
|
|
destinationChipClass,
|
|
formFieldClass,
|
|
formHintClass,
|
|
formInputClass,
|
|
modalFormClass,
|
|
} 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) {
|
|
return (
|
|
<AppFormDialog
|
|
open={open}
|
|
onOpenChange={(nextOpen) => {
|
|
if (!nextOpen) onClose();
|
|
}}
|
|
eyebrow="工作副本"
|
|
title="新建构建脚本"
|
|
size="panel"
|
|
>
|
|
<form className={modalFormClass} onSubmit={onSubmit}>
|
|
<div className={destinationChipClass}>
|
|
<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: BookOpen,
|
|
iconTone: "bg-[#fff0ed] text-[#dd5a4c]",
|
|
size: 22,
|
|
},
|
|
{
|
|
type: "python" as const,
|
|
title: "Python 脚本",
|
|
desc: "批处理、训练和模型调用任务",
|
|
Icon: FileCode,
|
|
iconTone: "bg-[#e9f3ff] text-[#2a75c5]",
|
|
size: 23,
|
|
},
|
|
] as const
|
|
).map((item) => {
|
|
const selected = form.scriptType === item.type;
|
|
const TypeIcon = item.Icon;
|
|
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] ring-2 ring-brand/10"
|
|
: "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}`}
|
|
>
|
|
<TypeIcon 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">
|
|
<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="mt-[3px] flex shrink-0 justify-end gap-2 border-t border-line bg-white -mx-[22px] px-[22px] py-3.5">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
type="button"
|
|
onClick={onClose}
|
|
className={dialogSecondaryButtonClass}
|
|
>
|
|
取消
|
|
</Button>
|
|
<Button
|
|
variant="default"
|
|
size="sm"
|
|
type="submit"
|
|
disabled={creating || !form.name.trim()}
|
|
className={dialogPrimaryButtonClass}
|
|
>
|
|
{creating ? <Loader2Icon className="size-4 animate-spin" /> : <Plus size={16} />}
|
|
{creating ? "正在创建…" : "创建脚本"}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</AppFormDialog>
|
|
);
|
|
}
|