From 874e4605ef05db6fd4f49600192d697a05099f95 Mon Sep 17 00:00:00 2001 From: Lee Jackson <130007945+Imagineer99@users.noreply.github.com> Date: Sat, 2 May 2026 17:55:24 +0100 Subject: [PATCH] Studio: Add dataset upload dropzone and update preserve think copy (#5253) * fix: improve training dataset upload affordance * chore: update preserve think * fix: guard dataset dropzone drag target --- .../src/components/assistant-ui/thread.tsx | 4 +- .../src/features/chat/shared-composer.tsx | 4 +- .../studio/sections/dataset-section.tsx | 112 +++++++++++++++--- 3 files changed, 102 insertions(+), 18 deletions(-) diff --git a/studio/frontend/src/components/assistant-ui/thread.tsx b/studio/frontend/src/components/assistant-ui/thread.tsx index 3f67b11b51..d32f698384 100644 --- a/studio/frontend/src/components/assistant-ui/thread.tsx +++ b/studio/frontend/src/components/assistant-ui/thread.tsx @@ -485,7 +485,7 @@ const PreserveThinkingToggle: FC = () => { : "bg-muted text-muted-foreground hover:bg-muted-foreground/15", )} aria-label={ - preserveThinking ? "Disable preserve thinking" : "Enable preserve thinking" + preserveThinking ? "Disable preserve think" : "Enable preserve think" } > {preserveThinking && !disabled ? ( @@ -493,7 +493,7 @@ const PreserveThinkingToggle: FC = () => { ) : ( )} - Preserve Thinking + Preserve Think ); }; diff --git a/studio/frontend/src/features/chat/shared-composer.tsx b/studio/frontend/src/features/chat/shared-composer.tsx index 3ba2f8b6f5..1a3ba7e166 100644 --- a/studio/frontend/src/features/chat/shared-composer.tsx +++ b/studio/frontend/src/features/chat/shared-composer.tsx @@ -662,7 +662,7 @@ export function SharedComposer({ : "bg-muted text-muted-foreground hover:bg-muted-foreground/15", )} aria-label={ - preserveThinking ? "Disable preserve thinking" : "Enable preserve thinking" + preserveThinking ? "Disable preserve think" : "Enable preserve think" } > {preserveThinking && modelLoaded ? ( @@ -670,7 +670,7 @@ export function SharedComposer({ ) : ( )} - Preserve Thinking + Preserve Think )} (TRAINING_UPLOAD_EXTENSIONS); +const TRAINING_UPLOAD_ACCEPT = TRAINING_UPLOAD_EXTENSIONS.join(","); +const TRAINING_UPLOAD_LABEL = "CSV, JSONL, JSON, Parquet, PDF, DOCX, TXT"; const DOCUMENT_REDIRECT_EXTENSIONS = new Set([".pdf", ".docx", ".txt"]); const SEARCH_INPUT_REASONS = new Set(["input-change", "input-paste", "input-clear"]); const OPEN_LEARNING_RECIPES_ON_ARRIVAL_KEY = "data-recipes:open-learning-recipes"; +function getFileExtension(fileName: string) { + const extensionStart = fileName.lastIndexOf("."); + return extensionStart >= 0 ? fileName.slice(extensionStart).toLowerCase() : ""; +} + function isLikelyLocalDatasetRef(value: string) { return ( value.startsWith("/") || @@ -354,6 +379,7 @@ export function DatasetSection() { ); const [isUploading, setIsUploading] = useState(false); + const [isDatasetDragOver, setIsDatasetDragOver] = useState(false); const [documentRedirectOpen, setDocumentRedirectOpen] = useState(false); const [redirectFileName, setRedirectFileName] = useState(null); @@ -380,12 +406,15 @@ export function DatasetSection() { } }; - const handleDatasetFileChange = async (event: ChangeEvent) => { - const file = event.target.files?.[0]; - event.target.value = ""; - if (!file) return; + const handleDatasetFile = async (file: File) => { + const extension = getFileExtension(file.name); + if (!TRAINING_UPLOAD_EXTENSION_SET.has(extension)) { + toast.error("Unsupported file type", { + description: `Upload one ${TRAINING_UPLOAD_LABEL} file.`, + }); + return; + } - const extension = file.name.slice(file.name.lastIndexOf(".")).toLowerCase(); if (DOCUMENT_REDIRECT_EXTENSIONS.has(extension)) { setRedirectFileName(file.name); setDocumentRedirectOpen(true); @@ -395,6 +424,44 @@ export function DatasetSection() { await handleFileUpload(file, selectLocalDataset, "Dataset uploaded"); }; + const handleDatasetFileChange = async (event: ChangeEvent) => { + const file = event.target.files?.[0]; + event.target.value = ""; + if (!file) return; + + await handleDatasetFile(file); + }; + + const handleDatasetDrop = (event: DragEvent) => { + event.preventDefault(); + setIsDatasetDragOver(false); + if (isUploading) return; + + const files = Array.from(event.dataTransfer.files); + if (files.length === 0) return; + + if (files.length > 1) { + toast.error("Upload one file at a time", { + description: "Training dataset upload accepts a single file.", + }); + return; + } + + void handleDatasetFile(files[0]); + }; + + const handleDatasetDragOver = (event: DragEvent) => { + event.preventDefault(); + if (isUploading) return; + + event.dataTransfer.dropEffect = "copy"; + setIsDatasetDragOver(true); + }; + + const handleDatasetDragLeave = () => { + setIsDatasetDragOver(false); + }; + const handleEvalFileChange = async (event: ChangeEvent) => { const file = event.target.files?.[0]; event.target.value = ""; @@ -924,7 +991,7 @@ export function DatasetSection() { - + {selectedDatasetName ? ( @@ -971,15 +1038,32 @@ export function DatasetSection() { ) : ( - + - - No dataset selected + + + Drop 1 file here or click to upload + + + {TRAINING_UPLOAD_LABEL} + - + )} @@ -1012,7 +1096,7 @@ export function DatasetSection() { { void handleDatasetFileChange(event);