Files
labelmain/app/management/project/audit/components/NewProjectModal.tsx

460 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import {
checkLabelDataSource,
getProjectAdminList,
getProjectTypeList,
projectAdd,
} from "@/components/label/api/project"
import {
getAllVersionByName,
getLabelSchemeList,
} from "@/components/label/api/scheme"
import { usePermissionStore } from "@/components/label/store/auth"
import {
Alert,
Button,
Group,
Modal,
MultiSelect,
Radio,
Select,
Stack,
Switch,
Text,
TextInput,
Textarea,
} from "@mantine/core"
import { useForm } from "@mantine/form"
import { notifications } from "@mantine/notifications"
import { IconAlertTriangle } from "@tabler/icons-react"
import { useEffect, useMemo, useRef, useState } from "react"
export default function NewProjectModal(props: {
opened: boolean
onCloseAction: (refresh?: boolean) => void
}) {
const { opened, onCloseAction } = props
const userName = usePermissionStore((s) => s.user_name)
const [typeOpts, setTypeOpts] = useState<
Array<{ label: string; value: string }>
>([])
const [labelSchemeOpts, setLabelSchemeOpts] = useState<
Array<{ label: string; value: string }>
>([])
const [versionOpts, setVersionOpts] = useState<
Array<{ label: string; value: string }>
>([])
const [adminUserOpts, setAdminUserOpts] = useState<
Array<{ label: string; value: string }>
>([])
const [dataSize, setDataSize] = useState<number>(0)
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const form = useForm({
initialValues: {
project_name: "",
project_type: "",
is_dynamic: false,
data_source_radio: "" as "" | "0" | "1",
data_source: "",
label_schema_name: "",
label_schema_version: "",
is_embedding: false,
admin_user: [] as string[],
remark: "",
},
validate: {
project_name: (v) => {
if (!v) return "必填"
if (/^\s*$/.test(v)) return "项目名称不能全为空格"
return null
},
project_type: (v) => (!v ? "必填" : null),
label_schema_name: (v) => (!v ? "必填" : null),
label_schema_version: (v) => (!v ? "必填" : null),
admin_user: (v) => (v && v.length ? null : "必填"),
},
})
const isDynamic = form.values.is_dynamic
const projectType = form.values.project_type
const labelSchema = form.values.label_schema_name
useEffect(() => {
if (!opened) return
const initFormOpts = async () => {
try {
const [schemeRes, adminRes, typeRes] = await Promise.all([
getLabelSchemeList({ page_number: 1, page_size: 5000 } as any),
getProjectAdminList(),
getProjectTypeList(),
])
const schemes = (schemeRes as any)?.label_schema_list ?? []
setLabelSchemeOpts(
schemes.map((item: any) => ({ label: item.name, value: item.name }))
)
setAdminUserOpts(
(adminRes ?? []).map((item: any) => ({
label: item.label,
value: String(item.value),
}))
)
setTypeOpts(
(typeRes ?? []).map((name) => ({ label: name, value: name }))
)
} catch {
setLabelSchemeOpts([])
setAdminUserOpts([])
setTypeOpts([])
}
}
initFormOpts()
}, [opened])
useEffect(() => {
if (!opened) return
const run = async () => {
form.setFieldValue("label_schema_version", "")
setVersionOpts([])
if (!labelSchema) return
try {
const res = await getAllVersionByName(labelSchema)
const list = (res as any)?.label_schema_version_list ?? []
setVersionOpts(
list.map((item: any) => ({
label: item.version,
value: item.version,
}))
)
} catch {
setVersionOpts([])
}
}
run()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [labelSchema, opened])
useEffect(() => {
if (!opened) return
if (isDynamic) {
form.setFieldValue("data_source_radio", "")
form.setFieldValue("data_source", "")
queueMicrotask(() => setDataSize(0))
form.clearFieldError("data_source")
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDynamic, opened])
const triggerCheckDataSource = (value: string) => {
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current)
if (!value) {
setDataSize(0)
form.clearFieldError("data_source")
return
}
const radio = form.values.data_source_radio
if (!projectType) {
setDataSize(0)
form.setFieldError("data_source", "请先选择所属业务")
return
}
if (radio !== "0" && radio !== "1") {
setDataSize(0)
form.setFieldError("data_source", "请先选择标注数据源对应来源类型")
return
}
debounceTimerRef.current = setTimeout(async () => {
try {
const res = await checkLabelDataSource({
project_type: projectType,
nfs_path: value,
flag: Number(radio),
})
const errorCode = (res as any)?.error_code
const size = (res as any)?.data_size ?? 0
const errorMap = new Map<number, string>([
[1, "nfs路径不存在"],
[2, "nfs路径下不存在data目录"],
[3, "nfs路径下不存在tag目录"],
[4, "tag文件数量小于data文件数量"],
[5, "pattern.json文件不存在"],
[6, "clip.json文件不合法"],
[7, "tag.json文件不合法"],
[8, "pattern.json文件不合法"],
])
if (errorCode === 0) {
setDataSize(size)
form.clearFieldError("data_source")
} else if (errorMap.has(errorCode)) {
setDataSize(0)
form.setFieldError(
"data_source",
errorMap.get(errorCode) || "标注数据源填写有误"
)
} else {
setDataSize(0)
form.setFieldError("data_source", "标注数据源填写有误")
}
} catch (e) {
setDataSize(0)
form.setFieldError(
"data_source",
e instanceof Error ? e.message : "标注数据源校验失败"
)
}
}, 1000)
}
const canSubmit = useMemo(() => {
if (!opened) return false
if (!form.values.project_name || /^\s*$/.test(form.values.project_name))
return false
if (!form.values.project_type) return false
if (!form.values.label_schema_name || !form.values.label_schema_version)
return false
if (!form.values.admin_user.length) return false
if (!form.values.is_dynamic) {
if (!form.values.data_source_radio) return false
if (!form.values.data_source) return false
if (form.errors.data_source) return false
}
return true
}, [form.errors.data_source, form.values, opened])
return (
<Modal
opened={opened}
onClose={() => onCloseAction()}
title="新增标注任务"
centered
size="50%"
closeOnClickOutside={false}>
<Stack gap="sm">
<Alert
color="yellow"
radius="xs"
p="xs"
icon={<IconAlertTriangle size={16} />}
title="提示">
MDS模块进行数据处理
</Alert>
<form
onSubmit={form.onSubmit(async (values) => {
if (!canSubmit) return
const data: any = { ...values }
data.is_dynamic = values.is_dynamic ? 1 : 0
data.is_embedding = values.is_embedding ? 1 : 0
data.data_source = values.data_source ?? ""
data.create_user = userName ?? ""
data.data_size = dataSize
data.admin_user = values.admin_user
.map((v) => Number(v))
.filter((n) => Number.isFinite(n))
data.remarks = values.remark || undefined
delete data.remark
delete data.data_source_radio
try {
await projectAdd(data)
notifications.show({
color: "green",
title: "项目创建成功",
message: "已创建项目",
})
form.reset()
setDataSize(0)
onCloseAction(true)
} catch (e) {
notifications.show({
color: "red",
title: "创建失败",
message: e instanceof Error ? e.message : "请求失败",
})
}
})}>
<div
style={{ maxHeight: "58vh", overflowY: "auto", paddingRight: 6 }}>
<Stack gap="sm">
<TextInput
label="项目名称"
radius="xs"
size="xs"
placeholder="请输入项目名称"
withAsterisk
{...form.getInputProps("project_name")}
/>
<Select
label="所属业务"
radius="xs"
size="xs"
placeholder="请选择所属业务"
data={typeOpts}
searchable
clearable
withAsterisk
value={form.values.project_type || null}
onChange={(v) => form.setFieldValue("project_type", v ?? "")}
error={form.errors.project_type}
/>
<Switch
label="是否为动态任务"
size="sm"
checked={form.values.is_dynamic}
onChange={(e) =>
form.setFieldValue("is_dynamic", e.currentTarget.checked)
}
/>
{!isDynamic ? (
<>
<Radio.Group
label="标注数据源"
size="xs"
withAsterisk
value={form.values.data_source_radio}
onChange={(v) => {
const next = v as "0" | "1" | ""
form.setFieldValue("data_source_radio", next)
if (form.values.data_source)
triggerCheckDataSource(form.values.data_source)
}}>
<Group gap="md" mt={6}>
<Radio
value="0"
label="新建数据源"
size="xs"
disabled={!projectType}
/>
<Radio
value="1"
label="现存数据源"
size="xs"
disabled={!projectType}
/>
</Group>
</Radio.Group>
<TextInput
label="标注数据源"
size="xs"
radius="xs"
placeholder="请输入标注数据源"
withAsterisk
disabled={!projectType}
value={form.values.data_source}
onChange={(e) => {
const v = e.currentTarget.value
form.setFieldValue("data_source", v)
triggerCheckDataSource(v)
}}
error={form.errors.data_source}
rightSection={
dataSize > 0 && form.values.data_source ? (
<Text size="xs" c="blue" style={{ paddingRight: 6 }}>
{dataSize}
</Text>
) : undefined
}
/>
</>
) : null}
<Select
label="标注方案"
size="xs"
radius="xs"
placeholder="请选择标注方案"
data={labelSchemeOpts}
searchable
clearable
withAsterisk
value={form.values.label_schema_name || null}
onChange={(v) =>
form.setFieldValue("label_schema_name", v ?? "")
}
error={form.errors.label_schema_name}
/>
<Select
label="方案版本"
size="xs"
radius="xs"
placeholder="请选择标注方案版本号"
data={versionOpts}
searchable
clearable
withAsterisk
value={form.values.label_schema_version || null}
onChange={(v) =>
form.setFieldValue("label_schema_version", v ?? "")
}
error={form.errors.label_schema_version}
/>
<Switch
label="是否生成embedding"
size="sm"
checked={form.values.is_embedding}
onChange={(e) =>
form.setFieldValue("is_embedding", e.currentTarget.checked)
}
/>
<MultiSelect
label="标注管理员"
size="xs"
radius="xs"
placeholder="请选择标注管理员"
data={adminUserOpts}
searchable
clearable
withAsterisk
value={form.values.admin_user}
onChange={(v) => form.setFieldValue("admin_user", v)}
error={form.errors.admin_user}
/>
<Textarea
label="备注"
size="xs"
radius="xs"
placeholder="请输入"
maxLength={50}
autosize
minRows={3}
value={form.values.remark}
onChange={(e) =>
form.setFieldValue("remark", e.currentTarget.value)
}
/>
</Stack>
</div>
<Group justify="flex-end" mt="md" gap="sm">
<Button
variant="default"
size="xs"
radius="xs"
onClick={() => onCloseAction()}>
</Button>
<Button type="submit" size="xs" radius="xs" disabled={!canSubmit}>
</Button>
</Group>
</form>
</Stack>
</Modal>
)
}