Separate dataset upload queues
This commit is contained in:
@@ -637,7 +637,7 @@ function App() {
|
|||||||
const [selectedGpuIds, setSelectedGpuIds] = useState<number[]>([]);
|
const [selectedGpuIds, setSelectedGpuIds] = useState<number[]>([]);
|
||||||
const [selectedCondaEnv, setSelectedCondaEnv] = useState("auto");
|
const [selectedCondaEnv, setSelectedCondaEnv] = useState("auto");
|
||||||
const [uploadKind, setUploadKind] = useState<"images" | "masks">("images");
|
const [uploadKind, setUploadKind] = useState<"images" | "masks">("images");
|
||||||
const [uploadFiles, setUploadFiles] = useState<FileList | null>(null);
|
const [uploadFilesByKind, setUploadFilesByKind] = useState<Record<"images" | "masks", FileList | null>>({ images: null, masks: null });
|
||||||
const [agentValidation, setAgentValidation] = useState<ValidationAgentPayload | null>(null);
|
const [agentValidation, setAgentValidation] = useState<ValidationAgentPayload | null>(null);
|
||||||
const [weightVerification, setWeightVerification] = useState<WeightVerifyPayload | null>(null);
|
const [weightVerification, setWeightVerification] = useState<WeightVerifyPayload | null>(null);
|
||||||
const [agentBusy, setAgentBusy] = useState(false);
|
const [agentBusy, setAgentBusy] = useState(false);
|
||||||
@@ -761,6 +761,7 @@ function App() {
|
|||||||
const uploadAccept = uploadKind === "images"
|
const uploadAccept = uploadKind === "images"
|
||||||
? "image/*,video/*,.zip,.tar,.tar.gz,.tgz"
|
? "image/*,video/*,.zip,.tar,.tar.gz,.tgz"
|
||||||
: "image/*,.txt,.json,.yaml,.yml,.zip,.tar,.tar.gz,.tgz";
|
: "image/*,.txt,.json,.yaml,.yml,.zip,.tar,.tar.gz,.tgz";
|
||||||
|
const activeUploadFiles = uploadFilesByKind[uploadKind];
|
||||||
const inferenceOutputs = useMemo(() => {
|
const inferenceOutputs = useMemo(() => {
|
||||||
const predictions = results.filter((item) => item.role === "segmentation" && item.previewable).slice(0, 8);
|
const predictions = results.filter((item) => item.role === "segmentation" && item.previewable).slice(0, 8);
|
||||||
const heatmaps = results.filter((item) => item.role === "heatmap" && item.previewable).slice(0, 8);
|
const heatmaps = results.filter((item) => item.role === "heatmap" && item.previewable).slice(0, 8);
|
||||||
@@ -979,12 +980,13 @@ function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function uploadDatasetFiles() {
|
async function uploadDatasetFiles() {
|
||||||
if (!uploadFiles || uploadFiles.length === 0) return;
|
const files = uploadFilesByKind[uploadKind];
|
||||||
|
if (!files || files.length === 0) return;
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
try {
|
try {
|
||||||
setActionError("");
|
setActionError("");
|
||||||
const body = new FormData();
|
const body = new FormData();
|
||||||
Array.from(uploadFiles).forEach((file) => body.append("files", file));
|
Array.from(files).forEach((file) => body.append("files", file));
|
||||||
let res: Response;
|
let res: Response;
|
||||||
try {
|
try {
|
||||||
res = await fetch(`${API_BASE}/api/datasets/${encodeURIComponent(datasetName)}/upload/${uploadKind}`, {
|
res = await fetch(`${API_BASE}/api/datasets/${encodeURIComponent(datasetName)}/upload/${uploadKind}`, {
|
||||||
@@ -997,7 +999,7 @@ function App() {
|
|||||||
if (!res.ok) throw new Error(await res.text());
|
if (!res.ok) throw new Error(await res.text());
|
||||||
setSelectedDatasetName(datasetName);
|
setSelectedDatasetName(datasetName);
|
||||||
setUploadModalOpen(false);
|
setUploadModalOpen(false);
|
||||||
setUploadFiles(null);
|
setUploadFilesByKind((current) => ({ ...current, [uploadKind]: null }));
|
||||||
await refresh();
|
await refresh();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(String(err));
|
setActionError(String(err));
|
||||||
@@ -1528,7 +1530,7 @@ function App() {
|
|||||||
<span>说明</span>
|
<span>说明</span>
|
||||||
<input value={datasetDescription} onChange={(event) => setDatasetDescription(event.target.value)} />
|
<input value={datasetDescription} onChange={(event) => setDatasetDescription(event.target.value)} />
|
||||||
</label>
|
</label>
|
||||||
{datasetNameExists && <small className="formWarn">已存在同名数据集,不能重复创建,但可以继续上传追加文件。</small>}
|
{datasetNameExists && <small className="formWarn">将追加到现有数据集的 {uploadKind},不会新建同名数据集。</small>}
|
||||||
<div className="segmented">
|
<div className="segmented">
|
||||||
{(["images", "masks"] as const).map((kind) => (
|
{(["images", "masks"] as const).map((kind) => (
|
||||||
<button key={kind} type="button" className={uploadKind === kind ? "active" : ""} onClick={() => setUploadKind(kind)}>
|
<button key={kind} type="button" className={uploadKind === kind ? "active" : ""} onClick={() => setUploadKind(kind)}>
|
||||||
@@ -1536,15 +1538,25 @@ function App() {
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="uploadKindCounts">
|
||||||
|
<span className={uploadKind === "images" ? "active" : ""}>images 已选择 {uploadFilesByKind.images?.length ?? 0}</span>
|
||||||
|
<span className={uploadKind === "masks" ? "active" : ""}>masks 已选择 {uploadFilesByKind.masks?.length ?? 0}</span>
|
||||||
|
</div>
|
||||||
<label className="drop">
|
<label className="drop">
|
||||||
{uploadFiles?.length ? <PackageOpen size={24} /> : <UploadCloud size={24} />}
|
{activeUploadFiles?.length ? <PackageOpen size={24} /> : <UploadCloud size={24} />}
|
||||||
<span>{uploadFiles?.length ? `${uploadFiles.length} 个文件待上传` : `选择 ${uploadKind} 图片、视频或压缩包`}</span>
|
<span>{activeUploadFiles?.length ? `${activeUploadFiles.length} 个 ${uploadKind} 文件待上传` : `选择 ${uploadKind} 图片、视频或压缩包`}</span>
|
||||||
<small>.png/.jpg/.tif/.mp4/.zip/.tar.gz 均可上传</small>
|
<small>.png/.jpg/.tif/.mp4/.zip/.tar.gz 均可上传</small>
|
||||||
<input multiple type="file" accept={uploadAccept} onChange={(event) => setUploadFiles(event.target.files)} />
|
<input
|
||||||
|
key={uploadKind}
|
||||||
|
multiple
|
||||||
|
type="file"
|
||||||
|
accept={uploadAccept}
|
||||||
|
onChange={(event) => setUploadFilesByKind((current) => ({ ...current, [uploadKind]: event.target.files }))}
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div className="modalActions">
|
<div className="modalActions">
|
||||||
<button className="primary" disabled={busy || datasetNameExists} onClick={createDataset}><Boxes size={17} />创建数据集</button>
|
<button className="primary" disabled={busy || datasetNameExists} onClick={createDataset}><Boxes size={17} />创建数据集</button>
|
||||||
<button className="primary secondary" disabled={busy || !uploadFiles?.length} onClick={uploadDatasetFiles}><UploadCloud size={17} />上传文件</button>
|
<button className="primary secondary" disabled={busy || !activeUploadFiles?.length} onClick={uploadDatasetFiles}><UploadCloud size={17} />{datasetNameExists ? "追加文件" : "上传文件"}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -839,6 +839,30 @@ textarea {
|
|||||||
font-weight: 760;
|
font-weight: 760;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.uploadKindCounts {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadKindCounts span {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 7px 9px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(8, 10, 8, 0.72);
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadKindCounts span.active {
|
||||||
|
border-color: rgba(157, 226, 111, 0.58);
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
.drop {
|
.drop {
|
||||||
min-height: 128px;
|
min-height: 128px;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
Reference in New Issue
Block a user