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") {