From 6ced30cdaa69b09466074e1700eb18d2e94501c3 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Fri, 24 Apr 2026 16:01:28 +0000 Subject: [PATCH] Studio: auto-detect server GH_TOKEN and widen Easy-mode detection The GitHub seed form now fetches /api/data-recipe/seed/github/env-token on mount and, when the server exposes a GH_TOKEN / GITHUB_TOKEN env var and the token field is blank, shows a small 'Using server env var' badge and swaps the placeholder text. The token value itself is never returned to the UI. Widens Easy-mode detection in recipe-studio-page.tsx so that recipes saved before ui.seed_source_type was persisted also get the Easy tab: falls back to recipe.seed_config.source.seed_type, which is always present for github_repo seeds. --- studio/backend/routes/data_recipe/seed.py | 14 +++++++ .../src/features/recipe-studio/api/index.ts | 7 ++++ .../dialogs/seed/seed-dialog.tsx | 41 ++++++++++++++++--- .../recipe-studio/recipe-studio-page.tsx | 5 ++- 4 files changed, 60 insertions(+), 7 deletions(-) diff --git a/studio/backend/routes/data_recipe/seed.py b/studio/backend/routes/data_recipe/seed.py index e9cf828610..a296e38515 100644 --- a/studio/backend/routes/data_recipe/seed.py +++ b/studio/backend/routes/data_recipe/seed.py @@ -8,6 +8,7 @@ from __future__ import annotations import base64 import binascii import json +import os import re from itertools import islice from pathlib import Path @@ -627,3 +628,16 @@ def inspect_seed_upload(payload: SeedInspectUploadRequest) -> SeedInspectRespons split = None, subset = None, ) + + +@router.get("/seed/github/env-token") +def get_github_env_token_status() -> dict: + """Report whether the server has a GH_TOKEN / GITHUB_TOKEN env var. + + The value is never returned; the UI uses this to tell the user they + can leave the token field blank. + """ + has_token = bool( + os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN") + ) + return {"has_token": has_token} diff --git a/studio/frontend/src/features/recipe-studio/api/index.ts b/studio/frontend/src/features/recipe-studio/api/index.ts index 9212e4db9b..bb2969c8bc 100644 --- a/studio/frontend/src/features/recipe-studio/api/index.ts +++ b/studio/frontend/src/features/recipe-studio/api/index.ts @@ -315,6 +315,13 @@ export async function inspectSeedUpload( return postJson("/seed/inspect-upload", payload); } +// biome-ignore lint/style/useNamingConvention: api schema +export type GithubEnvTokenStatus = { has_token: boolean }; + +export async function getGithubEnvTokenStatus(): Promise { + return getJson("/seed/github/env-token"); +} + export async function listMcpTools( payload: McpToolsListRequest, ): Promise { diff --git a/studio/frontend/src/features/recipe-studio/dialogs/seed/seed-dialog.tsx b/studio/frontend/src/features/recipe-studio/dialogs/seed/seed-dialog.tsx index 7a1fa9aef5..2f89ef3312 100644 --- a/studio/frontend/src/features/recipe-studio/dialogs/seed/seed-dialog.tsx +++ b/studio/frontend/src/features/recipe-studio/dialogs/seed/seed-dialog.tsx @@ -40,7 +40,7 @@ import { import { type ReactElement, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { cn } from "@/lib/utils"; import { UnstructuredDropZone, type FileEntry } from "./unstructured-drop-zone"; -import { inspectSeedDataset, inspectSeedUpload } from "../../api"; +import { getGithubEnvTokenStatus, inspectSeedDataset, inspectSeedUpload } from "../../api"; import { resolveImagePreview } from "../../utils/image-preview"; import type { SeedConfig, @@ -83,8 +83,26 @@ export function GithubRepoSeedForm({ onUpdate: (patch: Partial) => void; }): ReactElement { const [advancedOpen, setAdvancedOpen] = useState(false); + const [serverHasEnvToken, setServerHasEnvToken] = useState( + null, + ); const limitStr = (config.github_limit ?? "100").trim(); const allMode = limitStr === "" || limitStr === "0"; + useEffect(() => { + let cancelled = false; + void getGithubEnvTokenStatus() + .then((status) => { + if (!cancelled) setServerHasEnvToken(status.has_token); + }) + .catch(() => { + if (!cancelled) setServerHasEnvToken(false); + }); + return () => { + cancelled = true; + }; + }, []); + const userTokenEmpty = !(config.github_token ?? "").trim(); + const usingEnvToken = userTokenEmpty && serverHasEnvToken === true; return (
@@ -100,16 +118,27 @@ export function GithubRepoSeedForm({ />
- +
+ + {usingEnvToken && ( + + Using server env var + + )} +
onUpdate({ github_token: e.target.value })} - placeholder="ghp_... (optional if server has GH_TOKEN)" + placeholder={ + usingEnvToken + ? "using server's GH_TOKEN env var" + : "ghp_... (optional if server has GH_TOKEN)" + } />
diff --git a/studio/frontend/src/features/recipe-studio/recipe-studio-page.tsx b/studio/frontend/src/features/recipe-studio/recipe-studio-page.tsx index 0a86b5d165..5b7beeaa9f 100644 --- a/studio/frontend/src/features/recipe-studio/recipe-studio-page.tsx +++ b/studio/frontend/src/features/recipe-studio/recipe-studio-page.tsx @@ -202,7 +202,10 @@ export function RecipeStudioPage({ null, ); const flowContainerRef = useRef(null); - const supportsEasyMode = initialPayload?.ui?.seed_source_type === "github_repo"; + const supportsEasyMode = + initialPayload?.ui?.seed_source_type === "github_repo" || + (initialPayload?.recipe?.seed_config as { source?: { seed_type?: string } } | undefined) + ?.source?.seed_type === "github_repo"; const viewModeStorageKey = `recipe-studio:view-mode:${recipeId}`; const [activeView, setActiveViewState] = useState(() => { if (typeof window !== "undefined") {