diff --git a/studio/frontend/src/features/studio/sections/dataset-preview-dialog.tsx b/studio/frontend/src/features/studio/sections/dataset-preview-dialog.tsx
index e9d70846be..80fbcb65ee 100644
--- a/studio/frontend/src/features/studio/sections/dataset-preview-dialog.tsx
+++ b/studio/frontend/src/features/studio/sections/dataset-preview-dialog.tsx
@@ -27,6 +27,14 @@ type CheckFormatResponse = {
total_rows?: number | null;
};
+type PreviewImagePayload = {
+ type: "image";
+ mime?: string;
+ width?: number;
+ height?: number;
+ data?: string;
+};
+
type DatasetPreviewDialogProps = {
open: boolean;
onOpenChange: (open: boolean) => void;
@@ -123,6 +131,36 @@ export function DatasetPreviewDialog({
),
cell: ({ getValue }: { getValue: () => unknown }) => {
const value = getValue();
+ const images = collectPreviewImages(value);
+ if (images.length > 0) {
+ return (
+
+ {images.slice(0, 4).map((image, index) => {
+ const mime = image.mime || "image/jpeg";
+ const src = image.data ? `data:${mime};base64,${image.data}` : "";
+ const width = image.width ?? 128;
+ const height = image.height ?? 128;
+ return (
+

+ );
+ })}
+ {images.length > 4 && (
+
+ +{images.length - 4} more
+
+ )}
+
+ );
+ }
+
const text = formatCell(value);
if (!text) {
return (
@@ -285,3 +323,41 @@ function formatCell(value: unknown): string {
return JSON.stringify(value).slice(0, 500);
return String(value);
}
+
+function isPreviewImagePayload(value: unknown): value is PreviewImagePayload {
+ if (!value || typeof value !== "object") return false;
+ const record = value as Record;
+ return (
+ record.type === "image" &&
+ typeof record.data === "string" &&
+ record.data.length > 0
+ );
+}
+
+function collectPreviewImages(value: unknown): PreviewImagePayload[] {
+ const images: PreviewImagePayload[] = [];
+ const stack: unknown[] = [value];
+ let steps = 0;
+
+ while (stack.length > 0 && steps < 200) {
+ steps += 1;
+ const current = stack.pop();
+ if (isPreviewImagePayload(current)) {
+ images.push(current);
+ continue;
+ }
+
+ if (Array.isArray(current)) {
+ for (const item of current) stack.push(item);
+ continue;
+ }
+
+ if (current && typeof current === "object") {
+ for (const nested of Object.values(current as Record)) {
+ stack.push(nested);
+ }
+ }
+ }
+
+ return images;
+}
diff --git a/studio/frontend/src/features/training/stores/training-config-store.ts b/studio/frontend/src/features/training/stores/training-config-store.ts
index 95ded4c211..a9d6d37f42 100644
--- a/studio/frontend/src/features/training/stores/training-config-store.ts
+++ b/studio/frontend/src/features/training/stores/training-config-store.ts
@@ -96,6 +96,10 @@ export const useTrainingConfigStore = create()(
}),
{
name: "unsloth_training_config_v1",
+ partialize: (state) => {
+ const { modelType, ...rest } = state;
+ return rest;
+ },
},
),
);
diff --git a/studio/frontend/src/hooks/use-hf-model-search.ts b/studio/frontend/src/hooks/use-hf-model-search.ts
index e9d3b054d0..4745b4dc7b 100644
--- a/studio/frontend/src/hooks/use-hf-model-search.ts
+++ b/studio/frontend/src/hooks/use-hf-model-search.ts
@@ -23,6 +23,28 @@ const EXCLUDED_TAGS = new Set([
"ctranslate2",
]);
+function withPopularitySort(
+ input: Parameters[0],
+ init?: Parameters[1],
+): ReturnType {
+ const rawUrl =
+ typeof input === "string"
+ ? input
+ : input instanceof URL
+ ? input.toString()
+ : input.url;
+ const url = new URL(rawUrl);
+
+ if (!url.searchParams.has("sort")) {
+ url.searchParams.set("sort", "downloads");
+ }
+ if (!url.searchParams.has("direction")) {
+ url.searchParams.set("direction", "-1");
+ }
+
+ return fetch(url, init);
+}
+
function mapModel(raw: unknown): HfModelResult | null {
const m = raw as {
name: string;
@@ -53,10 +75,10 @@ export function useHfModelSearch(
listModels({
search: {
...(query.trim() ? { query } : { owner: "unsloth" }),
- tags: ["transformers"],
...(task ? { task } : {}),
},
additionalFields: ["safetensors", "tags"],
+ fetch: withPopularitySort,
...(accessToken ? { credentials: { accessToken } } : {}),
}) as AsyncGenerator,
[query, task, accessToken],