diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e5b6d8d6a67ad0dca8f20117fbfc72e076882d00 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/changeset.cjs b/.changeset/changeset.cjs new file mode 100644 index 0000000000000000000000000000000000000000..b2c50fa96c1378a0dd83b75ba8648e7c0fccdd05 --- /dev/null +++ b/.changeset/changeset.cjs @@ -0,0 +1,280 @@ +const { getPackagesSync } = require("@manypkg/get-packages"); +const gh = require("@changesets/get-github-info"); +const { existsSync, readFileSync, writeFileSync } = require("fs"); +const { join } = require("path"); + +const { getInfo, getInfoFromPullRequest } = gh; +const { packages, rootDir } = getPackagesSync(process.cwd()); + +/** + * @typedef {{packageJson: {name: string, python?: boolean}, dir: string}} Package + */ + +/** + * @typedef {{summary: string, id: string, commit: string, releases: {name: string}}} Changeset + */ + +/** + * + * @param {string} package_name The name of the package to find the directories for + * @returns {string[]} The directories for the package + */ +function find_packages_dirs(package_name) { + /** @type {string[]} */ + let package_dirs = []; + + /** @type {Package | undefined} */ + const _package = packages.find((p) => p.packageJson.name === package_name); + if (!_package) throw new Error(`Package ${package_name} not found`); + + package_dirs.push(_package.dir); + if (_package.packageJson.python) { + package_dirs.push(join(_package.dir, "..")); + } + return package_dirs; +} + +const changelogFunctions = { + /** + * + * @param {Changeset[]} changesets The changesets that have been created + * @param {any} dependenciesUpdated The dependencies that have been updated + * @param {any} options The options passed to the changelog generator + * @returns {Promise} The release line for the dependencies + */ + getDependencyReleaseLine: async ( + changesets, + dependenciesUpdated, + options + ) => { + if (!options.repo) { + throw new Error( + 'Please provide a repo to this changelog generator like this:\n"changelog": ["@changesets/changelog-github", { "repo": "org/repo" }]' + ); + } + if (dependenciesUpdated.length === 0) return ""; + + const changesetLink = `- Updated dependencies [${( + await Promise.all( + changesets.map(async (cs) => { + if (cs.commit) { + let { links } = await getInfo({ + repo: options.repo, + commit: cs.commit + }); + return links.commit; + } + }) + ) + ) + .filter((_) => _) + .join(", ")}]:`; + + const updatedDepenenciesList = dependenciesUpdated.map( + /** + * + * @param {any} dependency The dependency that has been updated + * @returns {string} The formatted dependency + */ + (dependency) => ` - ${dependency.name}@${dependency.newVersion}` + ); + + return [changesetLink, ...updatedDepenenciesList].join("\n"); + }, + /** + * + * @param {{summary: string, id: string, commit: string, releases: {name: string}[]}} changeset The changeset that has been created + * @param {any} type The type of changeset + * @param {any} options The options passed to the changelog generator + * @returns {Promise} The release line for the changeset + */ + getReleaseLine: async (changeset, type, options) => { + if (!options || !options.repo) { + throw new Error( + 'Please provide a repo to this changelog generator like this:\n"changelog": ["@changesets/changelog-github", { "repo": "org/repo" }]' + ); + } + + let prFromSummary; + let commitFromSummary; + /** + * @type {string[]} + */ + let usersFromSummary = []; + + const replacedChangelog = changeset.summary + .replace(/^\s*(?:pr|pull|pull\s+request):\s*#?(\d+)/im, (_, pr) => { + let num = Number(pr); + if (!isNaN(num)) prFromSummary = num; + return ""; + }) + .replace(/^\s*commit:\s*([^\s]+)/im, (_, commit) => { + commitFromSummary = commit; + return ""; + }) + .replace(/^\s*(?:author|user):\s*@?([^\s]+)/gim, (_, user) => { + usersFromSummary.push(user); + return ""; + }) + .trim(); + + const [firstLine, ...futureLines] = replacedChangelog + .split("\n") + .map((l) => l.trimRight()); + + const links = await (async () => { + if (prFromSummary !== undefined) { + let { links } = await getInfoFromPullRequest({ + repo: options.repo, + pull: prFromSummary + }); + if (commitFromSummary) { + links = { + ...links, + commit: `[\`${commitFromSummary}\`](https://github.com/${options.repo}/commit/${commitFromSummary})` + }; + } + return links; + } + const commitToFetchFrom = commitFromSummary || changeset.commit; + if (commitToFetchFrom) { + let { links } = await getInfo({ + repo: options.repo, + commit: commitToFetchFrom + }); + return links; + } + return { + commit: null, + pull: null, + user: null + }; + })(); + + const users = + usersFromSummary && usersFromSummary.length + ? usersFromSummary + .map( + (userFromSummary) => + `[@${userFromSummary}](https://github.com/${userFromSummary})` + ) + .join(", ") + : links.user; + + const prefix = [ + links.pull === null ? "" : `${links.pull}`, + links.commit === null ? "" : `${links.commit}` + ] + .join(" ") + .trim(); + + const suffix = users === null ? "" : ` Thanks ${users}!`; + + /** + * @typedef {{[key: string]: string[] | {dirs: string[], current_changelog: string, feat: {summary: string}[], fix: {summary: string}[], highlight: {summary: string}[]}}} ChangesetMeta + */ + + /** + * @type { ChangesetMeta & { _handled: string[] } }} + */ + let lines; + if (existsSync(join(rootDir, ".changeset", "_changelog.json"))) { + lines = JSON.parse( + readFileSync(join(rootDir, ".changeset", "_changelog.json"), "utf-8") + ); + } else { + lines = { + _handled: [] + }; + } + + if (lines._handled.includes(changeset.id)) { + return "done"; + } + lines._handled.push(changeset.id); + + changeset.releases.forEach((release) => { + if (!lines[release.name]) + lines[release.name] = { + dirs: find_packages_dirs(release.name), + current_changelog: "", + feat: [], + fix: [], + highlight: [] + }; + + const changelog_path = join( + //@ts-ignore + lines[release.name].dirs[1] || lines[release.name].dirs[0], + "CHANGELOG.md" + ); + + if (existsSync(changelog_path)) { + //@ts-ignore + lines[release.name].current_changelog = readFileSync( + changelog_path, + "utf-8" + ) + .replace(`# ${release.name}`, "") + .trim(); + } + + const [, _type, summary] = changeset.summary + .trim() + .match(/^(feat|fix|highlight)\s*:\s*([^]*)/im) || [ + , + "feat", + changeset.summary + ]; + + let formatted_summary = ""; + + if (_type === "highlight") { + const [heading, ...rest] = summary.trim().split("\n"); + const _heading = `${heading} ${prefix ? `(${prefix})` : ""}`; + const _rest = rest.concat(["", suffix]); + + formatted_summary = `${_heading}\n${_rest.join("\n")}`; + } else { + formatted_summary = handle_line(summary, prefix, suffix); + } + + //@ts-ignore + lines[release.name][_type].push({ + summary: formatted_summary + }); + }); + + writeFileSync( + join(rootDir, ".changeset", "_changelog.json"), + JSON.stringify(lines, null, 2) + ); + + return `\n\n-${prefix ? `${prefix} -` : ""} ${firstLine}\n${futureLines + .map((l) => ` ${l}`) + .join("\n")}`; + } +}; + +/** + * @param {string} str The changelog entry + * @param {string} prefix The prefix to add to the first line + * @param {string} suffix The suffix to add to the last line + * @returns {string} The formatted changelog entry + */ +function handle_line(str, prefix, suffix) { + const [_s, ...lines] = str.split("\n").filter(Boolean); + + const desc = `${prefix ? `${prefix} -` : ""} ${_s.replace( + /[\s\.]$/, + "" + )}. ${suffix}`; + + if (_s.length === 1) { + return desc; + } + + return [desc, ...lines.map((l) => ` ${l}`)].join("/n"); +} + +module.exports = changelogFunctions; diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000000000000000000000000000000000000..d2ba85f6bb23a7c7a64595f488aa35eed7441f7f --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", + "changelog": ["./changeset.cjs", { "repo": "gradio-app/gradio" }], + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": ["@gradio/spaces-test", "@gradio/cdn-test"] +} diff --git a/.changeset/every-bears-peel.md b/.changeset/every-bears-peel.md new file mode 100644 index 0000000000000000000000000000000000000000..d2f60b1409fa570ca9db316b58c4f078a70d5c8a --- /dev/null +++ b/.changeset/every-bears-peel.md @@ -0,0 +1,6 @@ +--- +"@gradio/audio": patch +"gradio": patch +--- + +fix:Fix audio recording events not dispatching diff --git a/.changeset/fix_changelogs.cjs b/.changeset/fix_changelogs.cjs new file mode 100644 index 0000000000000000000000000000000000000000..202ce30462a0e2fc9e868f56b1e5df73a465a803 --- /dev/null +++ b/.changeset/fix_changelogs.cjs @@ -0,0 +1,122 @@ +const { join } = require("path"); +const { readFileSync, existsSync, writeFileSync, unlinkSync } = require("fs"); +const { getPackagesSync } = require("@manypkg/get-packages"); + +const RE_PKG_NAME = /^[\w-]+\b/; +const pkg_meta = getPackagesSync(process.cwd()); + +/** + * @typedef {{dirs: string[], highlight: {summary: string}[], feat: {summary: string}[], fix: {summary: string}[], current_changelog: string}} ChangesetMeta + */ + +/** + * @typedef {{[key: string]: ChangesetMeta}} ChangesetMetaCollection + */ + +function run() { + if (!existsSync(join(pkg_meta.rootDir, ".changeset", "_changelog.json"))) { + console.warn("No changesets to process"); + return; + } + + /** + * @type { ChangesetMetaCollection & { _handled: string[] } }} + */ + const { _handled, ...packages } = JSON.parse( + readFileSync( + join(pkg_meta.rootDir, ".changeset", "_changelog.json"), + "utf-8" + ) + ); + + /** + * @typedef { {packageJson: {name: string, version: string, python: boolean}, dir: string} } PackageMeta + */ + + /** + * @type { {[key:string]: PackageMeta} } + */ + const all_packages = pkg_meta.packages.reduce((acc, pkg) => { + acc[pkg.packageJson.name] = /**@type {PackageMeta} */ ( + /** @type {unknown} */ (pkg) + ); + return acc; + }, /** @type {{[key:string] : PackageMeta}} */ ({})); + + for (const pkg_name in packages) { + const { dirs, highlight, feat, fix, current_changelog } = + /**@type {ChangesetMeta} */ (packages[pkg_name]); + + const { version, python } = all_packages[pkg_name].packageJson; + + const highlights = highlight.map((h) => `${h.summary}`); + const features = feat.map((f) => `- ${f.summary}`); + const fixes = fix.map((f) => `- ${f.summary}`); + + const release_notes = /** @type {[string[], string][]} */ ([ + [highlights, "### Highlights"], + [features, "### Features"], + [fixes, "### Fixes"] + ]) + .filter(([s], i) => s.length > 0) + .map(([lines, title]) => { + if (title === "### Highlights") { + return `${title}\n\n${lines.join("\n\n")}`; + } + + return `${title}\n\n${lines.join("\n")}`; + }) + .join("\n\n"); + + const new_changelog = `# ${pkg_name} + +## ${version} + +${release_notes} + +${current_changelog.replace(`# ${pkg_name}`, "").trim()} +`.trim(); + + dirs.forEach((dir) => { + writeFileSync(join(dir, "CHANGELOG.md"), new_changelog); + }); + + if (python) { + bump_local_dependents(pkg_name, version); + } + } + + unlinkSync(join(pkg_meta.rootDir, ".changeset", "_changelog.json")); + + /** + * @param {string} pkg_to_bump The name of the package to bump + * @param {string} version The version to bump to + * @returns {void} + * */ + function bump_local_dependents(pkg_to_bump, version) { + for (const pkg_name in all_packages) { + const { + dir, + packageJson: { python } + } = all_packages[pkg_name]; + + if (!python) continue; + + const requirements_path = join(dir, "..", "requirements.txt"); + const requirements = readFileSync(requirements_path, "utf-8").split("\n"); + + const pkg_index = requirements.findIndex((line) => { + const m = line.trim().match(RE_PKG_NAME); + if (!m) return false; + return m[0] === pkg_to_bump; + }); + + if (pkg_index !== -1) { + requirements[pkg_index] = `${pkg_to_bump}==${version}`; + writeFileSync(requirements_path, requirements.join("\n")); + } + } + } +} + +run(); diff --git a/.config/.prettierignore b/.config/.prettierignore new file mode 100644 index 0000000000000000000000000000000000000000..70ec7a1841d9e410073f242ce0ed0f4c69f1ff8e --- /dev/null +++ b/.config/.prettierignore @@ -0,0 +1,30 @@ +**/js/app/public/** +**/pnpm-workspace.yaml +**/js/app/dist/** +**/js/wasm/dist/** +**/client/js/dist/** +**/js/lite/dist/** +**/pnpm-lock.yaml +**/js/plot/src/Plot.svelte +**/.svelte-kit/** +**/demo/** +**/gradio/** +**/.pnpm-store/** +**/.venv/** +**/.github/** +/guides/** +**/.mypy_cache/** +!test-strategy.md +**/js/_space-test/** +../js/app/src/lite/theme.css +../js/storybook/theme.css +**/gradio_cached_examples/** +**/storybook-static/** +**/.vscode/** +sweep.yaml +**/.vercel/** +**/build/** +**/*.md +**/src/lib/json/**/* +**/playwright/.cache/**/* +**/theme/src/pollen.css \ No newline at end of file diff --git a/.config/.prettierrc.json b/.config/.prettierrc.json new file mode 100644 index 0000000000000000000000000000000000000000..0e8b0ad886052fcbf88e07df76b2f2ab0969e418 --- /dev/null +++ b/.config/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "useTabs": true, + "singleQuote": false, + "trailingComma": "none", + "printWidth": 80, + "plugins": ["prettier-plugin-svelte"] +} diff --git a/.config/basevite.config.ts b/.config/basevite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..3bc5f18da0faa0b85c9ee6530322b01c79993249 --- /dev/null +++ b/.config/basevite.config.ts @@ -0,0 +1,90 @@ +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import sveltePreprocess from "svelte-preprocess"; +// @ts-ignore +import custom_media from "postcss-custom-media"; +import global_data from "@csstools/postcss-global-data"; +// @ts-ignore +import prefixer from "postcss-prefix-selector"; +import { readFileSync } from "fs"; +import { join } from "path"; +import { fileURLToPath } from "url"; + +const __dirname = fileURLToPath(new URL(".", import.meta.url)); +const version_path = join(__dirname, "..", "gradio", "package.json"); +const theme_token_path = join( + __dirname, + "..", + "js", + "theme", + "src", + "tokens.css" +); + +const version = JSON.parse(readFileSync(version_path, { encoding: "utf-8" })) + .version.trim() + .replace(/\./g, "-"); + +//@ts-ignore +export default defineConfig(({ mode }) => { + const production = mode === "production"; + + return { + server: { + port: 9876 + }, + + build: { + sourcemap: false, + target: "esnext", + minify: production + }, + define: { + BUILD_MODE: production ? JSON.stringify("prod") : JSON.stringify("dev"), + BACKEND_URL: production + ? JSON.stringify("") + : JSON.stringify("http://localhost:7860/"), + GRADIO_VERSION: JSON.stringify(version) + }, + css: { + postcss: { + plugins: [ + prefixer({ + prefix: `.gradio-container-${version}`, + // @ts-ignore + transform(prefix, selector, prefixedSelector, fileName) { + if (selector.indexOf("gradio-container") > -1) { + return prefix; + } else if ( + selector.indexOf(":root") > -1 || + selector.indexOf("dark") > -1 || + fileName.indexOf(".svelte") > -1 + ) { + return selector; + } + return prefixedSelector; + } + }), + custom_media() + ] + } + }, + plugins: [ + svelte({ + inspector: true, + compilerOptions: { + dev: !production + }, + hot: !process.env.VITEST && !production, + preprocess: sveltePreprocess({ + postcss: { + plugins: [ + global_data({ files: [theme_token_path] }), + custom_media() + ] + } + }) + }) + ] + }; +}); diff --git a/.config/copy_frontend.py b/.config/copy_frontend.py new file mode 100644 index 0000000000000000000000000000000000000000..4aeff7f87fb6658b5f52acb245d7a0e53558a051 --- /dev/null +++ b/.config/copy_frontend.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +import shutil +import pathlib +from typing import Any + +from hatchling.builders.hooks.plugin.interface import BuildHookInterface + + +def copy_js_code(root: str | pathlib.Path): + NOT_COMPONENT = [ + "app", + "node_modules", + "storybook", + "playwright-report", + "workbench", + "tooltils", + ] + for entry in (pathlib.Path(root) / "js").iterdir(): + if ( + entry.is_dir() + and not str(entry.name).startswith("_") + and not str(entry.name) in NOT_COMPONENT + ): + + def ignore(s, names): + ignored = [] + for n in names: + if ( + n.startswith("CHANGELOG") + or n.startswith("README.md") + or n.startswith("node_modules") + or ".test." in n + or ".stories." in n + or ".spec." in n + ): + ignored.append(n) + return ignored + shutil.copytree( + str(entry), + str(pathlib.Path("gradio") / "_frontend_code" / entry.name), + ignore=ignore, + dirs_exist_ok=True, + ) + shutil.copytree( + str(pathlib.Path(root) / "client" / "js"), + str(pathlib.Path("gradio") / "_frontend_code" / "client"), + ignore=lambda d, names: ["node_modules"], + dirs_exist_ok=True, + ) + + +class BuildHook(BuildHookInterface): + def initialize(self, version: str, build_data: dict[str, Any]) -> None: + copy_js_code(self.root) + + +if __name__ == "__main__": + copy_js_code(pathlib.Path("..").resolve()) diff --git a/.config/eslint.config.js b/.config/eslint.config.js new file mode 100644 index 0000000000000000000000000000000000000000..467f270e3c9fa4f78b0d3d7ed909dad48f61cf1b --- /dev/null +++ b/.config/eslint.config.js @@ -0,0 +1,153 @@ +import globals from "globals"; +import ts_plugin from "@typescript-eslint/eslint-plugin"; +import js_plugin from "@eslint/js"; + +import typescriptParser from "@typescript-eslint/parser"; +import sveltePlugin from "eslint-plugin-svelte"; +import svelteParser from "svelte-eslint-parser"; + +const ts_rules_disabled = Object.fromEntries( + Object.keys(ts_plugin.rules).map((rule) => [ + `@typescript-eslint/${rule}`, + "off" + ]) +); +const js_rules_disabled = Object.fromEntries( + Object.keys(js_plugin.configs.all.rules).map((rule) => [rule, "off"]) +); + +const js_rules = { + ...js_rules_disabled, + "no-console": ["error", { allow: ["warn", "error", "debug", "info"] }], + "no-constant-condition": "error", + "no-dupe-args": "error", + "no-extra-boolean-cast": "error", + "no-unexpected-multiline": "error", + "no-unreachable": "error", + "valid-jsdoc": "error", + "array-callback-return": "error", + complexity: "error", + "no-else-return": "error", + "no-useless-return": "error", + "no-undef": "error", + "valid-jsdoc": [ + "error", + { + requireReturn: false, + requireParamDescription: true, + requireReturnDescription: true, + requireReturnType: false, + requireParamType: false + } + ] +}; + +const ts_rules = { + ...ts_rules_disabled, + "@typescript-eslint/adjacent-overload-signatures": "error", + "@typescript-eslint/explicit-function-return-type": [ + "error", + { allowExpressions: true } + ], + "@typescript-eslint/consistent-type-exports": "error", + "@typescript-eslint/ban-types": "error", + "@typescript-eslint/array-type": "error", + "@typescript-eslint/no-inferrable-types": "error" +}; + +const { browser, es2021, node } = globals; + +export default [ + { + ignores: [ + ".svelte-kit/**/*", + "**/node_modules/**", + "**/dist/**", + "**/.config/*", + "**/*.spec.ts", + "**/*.test.ts", + "**/*.node-test.ts", + "js/app/test/**/*", + "**/*vite.config.ts", + "**/_website/**/*", + "**/_spaces-test/**/*", + "**/preview/test/**/*" + ] + }, + { + files: ["**/*.js", "**/*.cjs"], + languageOptions: { + globals: { + ...browser, + ...es2021, + ...node + } + }, + + plugins: { + "eslint:recommended": js_plugin + }, + rules: js_rules + }, + + { + files: ["**/*.ts"], + languageOptions: { + parser: typescriptParser, + parserOptions: { + project: "./tsconfig.json", + extraFileExtensions: [".svelte"] + }, + globals: { + ...browser, + ...es2021, + ...node + } + }, + + plugins: { + "@typescript-eslint": ts_plugin, + "eslint:recommended": js_plugin + }, + rules: { + ...ts_rules, + ...js_rules, + "no-undef": "off" + } + }, + { + files: ["**/client/js/**"], + languageOptions: { + parserOptions: { + project: "./client/js/tsconfig.json" + } + } + }, + { + files: ["**/*.svelte"], + languageOptions: { + parser: svelteParser, + parserOptions: { + parser: typescriptParser, + project: "./tsconfig.json", + extraFileExtensions: [".svelte"] + }, + globals: { + ...browser, + ...es2021 + } + }, + plugins: { + svelte: sveltePlugin, + "@typescript-eslint": ts_plugin, + "eslint:recommended": js_plugin + }, + rules: { + ...ts_rules, + ...js_rules, + ...sveltePlugin.configs.recommended.rules, + "svelte/no-at-html-tags": "off", + "no-undef": "off" + } + } +]; diff --git a/.config/playwright-ct.config.ts b/.config/playwright-ct.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..3f89855797a04fe2a9cab4e781728fd02279e046 --- /dev/null +++ b/.config/playwright-ct.config.ts @@ -0,0 +1,41 @@ +import { defineConfig, devices } from "@playwright/experimental-ct-svelte"; +import config from "./basevite.config"; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "../", + /* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */ + snapshotDir: "./__snapshots__", + /* Maximum time one test can run for. */ + timeout: 10 * 1000, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + + /* Port to use for Playwright component endpoint. */ + ctPort: 3100, + ctViteConfig: config({ mode: "development" }) + }, + testMatch: "*.component.spec.ts", + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] } + } + ] +}); diff --git a/.config/playwright-setup.js b/.config/playwright-setup.js new file mode 100644 index 0000000000000000000000000000000000000000..3fc61854f8a23e125a608c7aa7a190287d79c374 --- /dev/null +++ b/.config/playwright-setup.js @@ -0,0 +1,152 @@ +import { spawn } from "node:child_process"; +import { join, basename } from "path"; +import { fileURLToPath } from "url"; +import { readdirSync, writeFileSync } from "fs"; +import net from "net"; + +import kl from "kleur"; + +const __dirname = fileURLToPath(new URL(".", import.meta.url)); +const TEST_APP_PATH = join(__dirname, "./test.py"); +const TEST_FILES_PATH = join(__dirname, "..", "js", "app", "test"); +const ROOT = join(__dirname, ".."); + +const test_files = readdirSync(TEST_FILES_PATH) + .filter( + (f) => + f.endsWith("spec.ts") && + !f.endsWith(".skip.spec.ts") && + !f.endsWith(".component.spec.ts") + ) + .map((f) => basename(f, ".spec.ts")); + +export default async function global_setup() { + const verbose = process.env.GRADIO_TEST_VERBOSE; + + const port = await find_free_port(7860, 8860); + process.env.GRADIO_E2E_TEST_PORT = port; + + process.stdout.write(kl.yellow("\nCreating test gradio app.\n\n")); + + const test_app = make_app(test_files, port); + process.stdout.write(kl.yellow("App created. Starting test server.\n\n")); + + process.stdout.write(kl.bgBlue(" =========================== \n")); + process.stdout.write(kl.bgBlue(" === PYTHON STARTUP LOGS === \n")); + process.stdout.write(kl.bgBlue(" =========================== \n\n")); + + writeFileSync(TEST_APP_PATH, test_app); + + const app = await spawn_gradio_app(TEST_APP_PATH, port, verbose); + + process.stdout.write( + kl.green(`\n\nServer started. Running tests on port ${port}.\n`) + ); + + return () => { + process.stdout.write(kl.green(`\nTests complete, cleaning up!\n`)); + + kill_process(app); + }; +} +const INFO_RE = /^INFO:/; + +function spawn_gradio_app(app, port, verbose) { + const PORT_RE = new RegExp(`:${port}`); + + return new Promise((res, rej) => { + const _process = spawn(`python`, [app], { + shell: true, + stdio: "pipe", + cwd: ROOT, + env: { + ...process.env, + GRADIO_SERVER_PORT: `7879`, + PYTHONUNBUFFERED: "true", + GRADIO_ANALYTICS_ENABLED: "False" + } + }); + _process.stdout.setEncoding("utf8"); + + function std_out(data) { + const _data = data.toString(); + const is_info = INFO_RE.test(_data); + + if (is_info) { + process.stdout.write(kl.yellow(_data)); + } + + if (!is_info) { + process.stdout.write(`${_data}\n`); + } + + if (PORT_RE.test(_data)) { + process.stdout.write(kl.bgBlue("\n =========== END =========== ")); + + res(_process); + + if (!verbose) { + _process.stdout.off("data", std_out); + _process.stderr.off("data", std_out); + } + } + } + + _process.stdout.on("data", std_out); + _process.stderr.on("data", std_out); + _process.on("exit", () => kill_process(_process)); + _process.on("close", () => kill_process(_process)); + _process.on("disconnect", () => kill_process(_process)); + }); +} + +function kill_process(process) { + process.kill("SIGKILL"); +} + +function make_app(demos, port) { + return `import gradio as gr +import uvicorn +from fastapi import FastAPI +import gradio as gr +${demos.map((d) => `from demo.${d}.run import demo as ${d}`).join("\n")} + +app = FastAPI() +${demos + .map((d) => `app = gr.mount_gradio_app(app, ${d}, path="/${d}")`) + .join("\n")} + +config = uvicorn.Config(app, port=${port}, log_level="info") +server = uvicorn.Server(config=config) +server.run()`; +} + +export async function find_free_port(start_port, end_port) { + for (let port = start_port; port < end_port; port++) { + if (await is_free_port(port)) { + return port; + } + } + + throw new Error( + `Could not find free ports: there were not enough ports available.` + ); +} + +export function is_free_port(port) { + return new Promise((accept, reject) => { + const sock = net.createConnection(port, "127.0.0.1"); + sock.once("connect", () => { + sock.end(); + accept(false); + }); + sock.once("error", (e) => { + sock.destroy(); + if (e.code === "ECONNREFUSED") { + accept(true); + } else { + reject(e); + } + }); + }); +} diff --git a/.config/playwright.config.js b/.config/playwright.config.js new file mode 100644 index 0000000000000000000000000000000000000000..04b78a4a8e7db8c0a66645f9401c5f3c403a687e --- /dev/null +++ b/.config/playwright.config.js @@ -0,0 +1,24 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + use: { + screenshot: "only-on-failure", + trace: "retain-on-failure", + permissions: ["clipboard-read", "clipboard-write", "microphone"], + bypassCSP: true, + launchOptions: { + args: [ + "--disable-web-security", + "--use-fake-device-for-media-stream", + "--use-fake-ui-for-media-stream", + "--use-file-for-fake-audio-capture=../gradio/test_data/test_audio.wav" + ] + } + }, + expect: { timeout: 60000 }, + timeout: 90000, + testMatch: /.*.spec.ts/, + testDir: "..", + globalSetup: "./playwright-setup.js", + workers: process.env.CI ? 1 : undefined +}); diff --git a/.config/playwright/index.html b/.config/playwright/index.html new file mode 100644 index 0000000000000000000000000000000000000000..229f296a9e5a3df9f93a2fe378fc31e3b4fff879 --- /dev/null +++ b/.config/playwright/index.html @@ -0,0 +1,12 @@ + + + + + + Testing Page + + +
+ + + diff --git a/.config/playwright/index.ts b/.config/playwright/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..ac6de14bf2ed6d010d48977f7e17bb756307d5ce --- /dev/null +++ b/.config/playwright/index.ts @@ -0,0 +1,2 @@ +// Import styles, initialize component theme here. +// import '../src/common.css'; diff --git a/.config/postcss.config.cjs b/.config/postcss.config.cjs new file mode 100644 index 0000000000000000000000000000000000000000..81b1976568fb4a99716139d8c63d14a8f5d99390 --- /dev/null +++ b/.config/postcss.config.cjs @@ -0,0 +1,8 @@ +const tailwindcss = require("tailwindcss"); +const autoprefixer = require("autoprefixer"); +const nested = require("tailwindcss/nesting"); +const tw_config = require("./tailwind.config.cjs"); + +module.exports = { + plugins: [nested, tailwindcss(tw_config), autoprefixer] +}; diff --git a/.config/setup_vite_tests.ts b/.config/setup_vite_tests.ts new file mode 100644 index 0000000000000000000000000000000000000000..0917122266f78918d2d43103a2d060191f104c2a --- /dev/null +++ b/.config/setup_vite_tests.ts @@ -0,0 +1,11 @@ +import type { TestingLibraryMatchers } from "@testing-library/jest-dom/matchers"; +import matchers from "@testing-library/jest-dom/matchers"; +import { expect } from "vitest"; + +declare module "vitest" { + interface Assertion + extends jest.Matchers, + TestingLibraryMatchers {} +} + +expect.extend(matchers); diff --git a/.config/tailwind.config.cjs b/.config/tailwind.config.cjs new file mode 100644 index 0000000000000000000000000000000000000000..fe92c25a4ef936efc0d55db7bc77b6ecb8c3a1d0 --- /dev/null +++ b/.config/tailwind.config.cjs @@ -0,0 +1,12 @@ +module.exports = { + content: [ + "./src/**/*.{html,js,svelte,ts}", + "**/@gradio/**/*.{html,js,svelte,ts}" + ], + + theme: { + extend: {} + }, + + plugins: [require("@tailwindcss/forms")] +}; diff --git a/.config/vitest.config.ts b/.config/vitest.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..9de93ad0ec2eb93ff2a5b755d9c99ad9b837b4a9 --- /dev/null +++ b/.config/vitest.config.ts @@ -0,0 +1,3 @@ +import config from "../js/app/vite.config"; + +export default config; diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000000000000000000000000000000000..6dafa31d21d36771530cc98a1a759ad2800a46c5 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,41 @@ +// See https://containers.dev +{ + "name": "Python 3", + "image": "mcr.microsoft.com/devcontainers/python:0-3.9", + + // See https://containers.dev/features + "features": { + "ghcr.io/devcontainers/features/git:1": {}, + "ghcr.io/devcontainers/features/node:1": {}, + "ghcr.io/devcontainers-contrib/features/ffmpeg-apt-get:1": {} + }, + + "hostRequirements": { + "cpus": 4, + "memory": "8gb", + "storage": "32gb" + }, + + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-python.vscode-pylance", + "ms-python.black-formatter", + "ms-toolsai.jupyter", + "esbenp.prettier-vscode", + "svelte.svelte-vscode", + "phoenisx.cssvar" + ], + "remote.autoForwardPorts": false + } + }, + + "forwardPorts": [7860, 9876], + "portsAttributes": { + "7860": { "label": "gradio port" }, + "9876": { "label": "gradio dev port" } + }, + + "postCreateCommand": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && chmod +x scripts/install_gradio.sh scripts/install_test_requirements.sh scripts/build_frontend.sh && ./scripts/install_gradio.sh && ./scripts/install_test_requirements.sh && ./scripts/build_frontend.sh" +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..916fc50c4d5d5b47fc23bd6b3137c74564926a15 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,40 @@ +# Python build +.eggs/ +gradio.egg-info/* +!gradio.egg-info/requires.txt +!gradio.egg-info/PKG-INFO +dist/ +*.pyc +__pycache__/ +*.py[cod] +*$py.class +build/ + +# JS build +gradio/templates/frontend/static +gradio/templates/frontend/cdn + +# Secrets +.env + +# Gradio run artifacts +*.db +*.sqlite3 +gradio/launches.json + +# Tests +.coverage +coverage.xml +test.txt + +# Demos +demo/tmp.zip +demo/flagged +demo/files/*.avi +demo/files/*.mp4 + +# Etc +.idea/* +.DS_Store +*.bak +workspace.code-workspace \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..f4e045fdb9a018c79ddf8e8c52de8b01eecd38ce --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ + +root = true + +[{js/**,client/js/**}] +end_of_line = lf +insert_final_newline = true +indent_style = tab +tab_width = 2 diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000000000000000000000000000000000..5acfdd111f027eb9964e437655316a6cb290d957 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,14 @@ +# https://github.com/gradio-app/gradio/pull/4487 - refactor components.py to separate files +69f36f98535c904e7cac2b4942cecc747ed7443c +# Format the codebase +cc0cff893f9d7d472788adc2510c123967b384fe +# Switch from black to ruff +8a70e83db9c7751b46058cdd2514e6bddeef6210 +# format (#4810) +7fa5e766ce0f89f1fb84c329e62c9df9c332120a +# lint website +4bf301324b3b180fa32166ff1774312b01334c88 +# format frontend with prettier +980b9f60eb49ed81e4957debe7b23a559a4d4b51 +# Refactor component directories (#5074) +1419538ea795caa391e3de809379f10639e9e764 diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..9d0d253bc669a853ad06d6c9653444b03f89fe52 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,20 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +demo/blocks_flipper/screenshot.gif filter=lfs diff=lfs merge=lfs -text +demo/blocks_neural_instrument_coding/sax.wav filter=lfs diff=lfs merge=lfs -text +demo/calculator/screenshot.gif filter=lfs diff=lfs merge=lfs -text +demo/dataset/files/world.mp4 filter=lfs diff=lfs merge=lfs -text +demo/fake_diffusion_with_gif/image.gif filter=lfs diff=lfs merge=lfs -text +demo/hello_world_2/screenshot.gif filter=lfs diff=lfs merge=lfs -text +demo/hello_world_4/screenshot.gif filter=lfs diff=lfs merge=lfs -text +demo/image_mod/screenshot.png filter=lfs diff=lfs merge=lfs -text +demo/kitchen_sink/files/world.mp4 filter=lfs diff=lfs merge=lfs -text +demo/sales_projections/screenshot.gif filter=lfs diff=lfs merge=lfs -text +demo/sepia_filter/screenshot.gif filter=lfs diff=lfs merge=lfs -text +demo/unispeech-speaker-verification/samples/kirsten_dunst.wav filter=lfs diff=lfs merge=lfs -text +demo/video_component/files/world.mp4 filter=lfs diff=lfs merge=lfs -text +guides/assets/hf_demo.mp4 filter=lfs diff=lfs merge=lfs -text +guides/cn/assets/hf_demo.mp4 filter=lfs diff=lfs merge=lfs -text +js/app/test/files/file_test.ogg filter=lfs diff=lfs merge=lfs -text +test/test_files/rotated_image.jpeg filter=lfs diff=lfs merge=lfs -text diff --git a/.github/ISSUE_TEMPLATE/bug_report_template.yml b/.github/ISSUE_TEMPLATE/bug_report_template.yml new file mode 100644 index 0000000000000000000000000000000000000000..825410fa979a63389dfc7980cb7dc69cb1658074 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report_template.yml @@ -0,0 +1,69 @@ +name: "\U0001F41E Bug report" +description: Report a bug on Gradio +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! Before you get started, please [search to see](https://github.com/gradio-app/gradio/issues) if an issue already exists for the bug you encountered + - type: textarea + id: bug-description + attributes: + label: Describe the bug + description: Please provide a concise description of what the bug is, in clear English. If you intend to submit a PR for this issue, tell us in the description. + placeholder: Bug description + validations: + required: true + - type: checkboxes + attributes: + label: Have you searched existing issues? 🔎 + description: Please search to see if an issue already exists for the issue you encountered. + options: + - label: I have searched and found no existing issues + required: true + - type: textarea + id: reproduction + attributes: + label: Reproduction + description: Please provide a minimal example, with code, that can be run to reproduce the issue. Do NOT provide screenshots of code, or link to external repos or applications. Use ``` to format code blocks. + placeholder: Reproduction + value: | + ```python + import gradio as gr + + ``` + validations: + required: true + - type: textarea + id: screenshot + attributes: + label: Screenshot + description: If relevant, please include screenshot(s) of your Gradio app so that we can understand what the issue is. + - type: textarea + id: logs + attributes: + label: Logs + description: "Please include the full stacktrace of the errors you get from Python or Javascript. If you are running in a colab notebooks, you can get the logs with by setting `debug=True`, i.e: `gradio.Interface.launch(debug=True)`" + render: shell + - type: textarea + id: system-info + attributes: + label: System Info + description: Please ensure you are running the latest version of Gradio. You can get the Gradio version and all its dependencies by running `gradio environment` + render: shell + validations: + required: true + - type: dropdown + id: severity + attributes: + label: Severity + description: Select the severity of this issue + options: + - I can work around it + - Blocking usage of gradio + validations: + required: true + - type: markdown + attributes: + value: | + 📌 Please ensure that you have filled all of the required sections above, and that the reproduction you have provided is [minimal, complete, and reproducible](https://stackoverflow.com/help/minimal-reproducible-example). Incomplete issues will be closed. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..f7f293887d8b1e8e499fbd7da0943f0bcd389750 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: 💡 General questions + url: https://discord.com/invite/feTf9x3ZSB + about: Have general questions about how to use Gradio? Please ask in our community Discord for quicker responses diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000000000000000000000000000000..c51010af86e60376e2eddbfd091a92785aa1683f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: ⚡ Feature request +about: Suggest an improvement or new feature or a new Guide for Gradio +title: '' +labels: '' +assignees: '' + +--- +- [ ] I have searched to see if a similar issue already exists. + + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000000000000000000000000000000000..593027bdc8b84046bad647f14b18e3c7c385f9d8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ +## Description + +Please include a concise summary, in clear English, of the changes in this pull request. If it closes an issue, please mention it here. + +Closes: #(issue) + +## 🎯 PRs Should Target Issues + +Before your create a PR, please check to see if there is [an existing issue](https://github.com/gradio-app/gradio/issues) for this change. If not, please create an issue before you create this PR, unless the fix is very small. + +Not adhering to this guideline will result in the PR being closed. + +## Tests + +1. PRs will only be merged if tests pass on CI. To run the tests locally, please set up [your Gradio environment locally](https://github.com/gradio-app/gradio/blob/main/CONTRIBUTING.md) and run the tests: `bash scripts/run_all_tests.sh` + +2. You may need to run the linters: `bash scripts/format_backend.sh` and `bash scripts/format_frontend.sh` + diff --git a/.github/actions/install-all-deps/action.yml b/.github/actions/install-all-deps/action.yml new file mode 100644 index 0000000000000000000000000000000000000000..da8f17fe3c3e7de1a1db6a8f04dab560ce92fc72 --- /dev/null +++ b/.github/actions/install-all-deps/action.yml @@ -0,0 +1,60 @@ +name: 'install all deps' +description: 'Install all deps' + +inputs: + always-install-pnpm: + description: 'Dictates whether or not we should install pnpm & dependencies, regardless of the cache' + default: 'false' + node_auth_token: + description: 'Node auth token' + default: "" + npm_token: + description: 'npm token' + default: "" + skip_build: + description: 'Skip build' + default: 'false' + +runs: + using: "composite" + steps: + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: 3.8 + cache: pip + cache-dependency-path: | + client/python/requirements.txt + requirements.txt + test/requirements.txt + - name: Create env + shell: bash + run: | + python -m pip install --upgrade virtualenv + python -m virtualenv venv + - uses: actions/cache@v4 + id: cache + with: + path: | + venv/* + key: gradio-lib-ubuntu-latest-pip-${{ hashFiles('client/python/requirements.txt') }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('test/requirements.txt') }} + - name: Install Gradio and Client Libraries Locally (Linux) + shell: bash + run: | + . venv/bin/activate + python -m pip install -e client/python + python -m pip install -e . + - name: Install ffmpeg + uses: FedericoCarboni/setup-ffmpeg@v2 + - name: install-frontend + uses: "./.github/actions/install-frontend-deps" + with: + always-install-pnpm: ${{ inputs.always-install-pnpm }} + node_auth_token: ${{ inputs.node_auth_token }} + npm_token: ${{ inputs.npm_token }} + skip_build: ${{ inputs.skip_build }} + - name: generate json + shell: bash + run: | + . venv/bin/activate + python js/_website/generate_jsons/generate.py diff --git a/.github/actions/install-frontend-deps/action.yml b/.github/actions/install-frontend-deps/action.yml new file mode 100644 index 0000000000000000000000000000000000000000..4a591530bc7d98439ea77bad2166c9799d00587c --- /dev/null +++ b/.github/actions/install-frontend-deps/action.yml @@ -0,0 +1,51 @@ +name: 'install frontend' +description: 'Install frontend deps' + +inputs: + always-install-pnpm: + description: 'Dictates whether or not we should install pnpm & dependencies, regardless of the cache' + default: 'false' + node_auth_token: + description: 'Node auth token' + default: "" + npm_token: + description: 'npm token' + default: "" + skip_build: + description: 'Skip build' + default: 'false' + +runs: + using: "composite" + steps: + - uses: actions/cache@v4 + id: frontend-cache + with: + path: | + gradio/templates/* + key: gradio-lib-front-end-${{ hashFiles('js/**', 'client/js/**')}} + - name: Install pnpm + if: steps.frontend-cache.outputs.cache-hit != 'true' || inputs.always-install-pnpm == 'true' + uses: pnpm/action-setup@v2 + with: + version: 8.9 + - uses: actions/setup-node@v4 + with: + node-version: 18 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml + env: + NODE_AUTH_TOKEN: ${{ inputs.always-install-pnpm }} + NPM_TOKEN: ${{ inputs.always-install-pnpm }} + - name: Install deps + if: steps.frontend-cache.outputs.cache-hit != 'true' || inputs.always-install-pnpm == 'true' + shell: bash + run: pnpm i --frozen-lockfile --ignore-scripts + - name: Build Css + if: inputs.always-install-pnpm == 'true' + shell: bash + run: pnpm css + - name: Build frontend + if: inputs.skip_build == 'false' && steps.frontend-cache.outputs.cache-hit != 'true' + shell: bash + run: pnpm build \ No newline at end of file diff --git a/.github/stale b/.github/stale new file mode 100644 index 0000000000000000000000000000000000000000..9d23fb5a9d8ba617e7d77a55f8ae5bfc1e46f8fc --- /dev/null +++ b/.github/stale @@ -0,0 +1,17 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 30 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security +# Label to use when marking an issue as stale +staleLabel: wontfix +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml new file mode 100644 index 0000000000000000000000000000000000000000..cd8a273fb5835d1b272b2a63790eda63757d2cdc --- /dev/null +++ b/.github/workflows/backend.yml @@ -0,0 +1,244 @@ +name: gradio-backend + +on: + push: + branches: + - "main" + pull_request: + types: [opened, synchronize, reopened, labeled, unlabeled] + +concurrency: + group: backend-${{ github.ref }}-${{ github.event_name == 'push' || github.event.inputs.fire != null }} + cancel-in-progress: true + +env: + NODE_OPTIONS: "--max-old-space-size=4096" + HF_TOKEN: ${{ secrets.HF_TOKEN }} + +jobs: + changes: + runs-on: ubuntu-latest + outputs: + python-client: ${{ steps.changes.outputs.python-client }} + gradio: ${{ steps.changes.outputs.gradio }} + test: ${{ steps.changes.outputs.test }} + workflows: ${{ steps.changes.outputs.workflows }} + scripts: ${{ steps.changes.outputs.scripts }} + client-scripts: ${{ steps.changes.outputs.client-scripts }} + steps: + - uses: actions/checkout@v3 + - uses: dorny/paths-filter@v2 + id: changes + with: + filters: | + python-client: + - 'client/python/**' + - 'gradio/**' + - 'requirements.txt' + gradio: + - 'client/python/**' + - 'gradio/**' + - 'requirements.txt' + test: + - 'test/**' + workflows: + - '.github/**' + scripts: + - 'scripts/**' + client-test: + needs: [changes] + if: needs.changes.outputs.python-client == 'true' || needs.changes.outputs.workflows == 'true' + strategy: + matrix: + os: ["ubuntu-latest", "windows-latest"] + test-type: ["not flaky", "flaky"] + python-version: ["3.8"] + exclude: + - os: ${{ github.event_name == 'pull_request' && contains( github.event.pull_request.labels.*.name, 'windows-tests') && 'dummy' || 'windows-latest' }} + - test-type: ${{ github.event_name == 'pull_request' && contains( github.event.pull_request.labels.*.name, 'flaky-tests') && 'dummy' || 'flaky' }} + runs-on: ${{ matrix.os }} + continue-on-error: true + steps: + - uses: actions/checkout@v3 + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: | + client/python/requirements.txt + requirements.txt + test/requirements.txt + - name: Create env + run: | + python -m pip install --upgrade virtualenv + python -m virtualenv venv + - uses: actions/cache@master + id: cache + with: + path: | + client/python/venv/* + key: python-client-${{ runner.os }}-pip-${{ hashFiles('client/python/requirements.txt') }}-${{ hashFiles('client/python/test/requirements.txt') }} + - uses: actions/cache@v4 + id: frontend-cache + with: + path: | + gradio/templates/* + key: gradio-lib-front-end-${{ hashFiles('js/**', 'client/js/**')}} + - name: Install pnpm + if: steps.frontend-cache.outputs.cache-hit != 'true' + uses: pnpm/action-setup@v2 + with: + version: 8 + - uses: actions/setup-node@v4 + if: steps.frontend-cache.outputs.cache-hit != 'true' + with: + node-version: 18 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml + - name: Build Frontend + if: steps.frontend-cache.outputs.cache-hit != 'true' + run: | + pnpm i --frozen-lockfile --ignore-scripts + pnpm build + - name: Install Test Requirements (Linux) + if: runner.os == 'Linux' + run: | + . venv/bin/activate + python -m pip install -r client/python/test/requirements.txt + - name: Install ffmpeg + uses: FedericoCarboni/setup-ffmpeg@v2 + - name: Install Gradio and Client Libraries Locally (Linux) + if: runner.os == 'Linux' + run: | + . venv/bin/activate + python -m pip install client/python + python -m pip install ".[oauth]" + - name: Lint (Linux) + if: runner.os == 'Linux' + run: | + . venv/bin/activate + bash client/python/scripts/lint.sh + - name: Tests (Linux) + if: runner.os == 'Linux' + run: | + . venv/bin/activate + python -m pytest -m "${{ matrix.test-type }}" client/python/ + - name: Install Test Requirements (Windows) + if: runner.os == 'Windows' + run: | + venv\Scripts\activate + pip install -r client/python/test/requirements.txt + - name: Install Gradio and Client Libraries Locally (Windows) + if: runner.os == 'Windows' + run: | + venv\Scripts\activate + python -m pip install client/python + python -m pip install ".[oauth]" + - name: Tests (Windows) + if: runner.os == 'Windows' + run: | + venv\Scripts\activate + python -m pytest -m "${{ matrix.test-type }}" client/python/ + test: + needs: [changes] + if: needs.changes.outputs.gradio == 'true' || needs.changes.outputs.workflows == 'true' || needs.changes.outputs.scripts == 'true' || needs.changes.outputs.test == 'true' + strategy: + matrix: + os: ["ubuntu-latest", "windows-latest"] + test-type: ["not flaky", "flaky"] + python-version: ["3.8"] + exclude: + - os: ${{ github.event_name == 'pull_request' && contains( github.event.pull_request.labels.*.name, 'windows-tests') && 'dummy' || 'windows-latest' }} + - test-type: ${{ github.event_name == 'pull_request' && contains( github.event.pull_request.labels.*.name, 'flaky-tests') && 'dummy' || 'flaky' }} + runs-on: ${{ matrix.os }} + continue-on-error: true + steps: + - uses: actions/checkout@v3 + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: | + client/python/requirements.txt + requirements.txt + test/requirements.txt + - name: Create env + run: | + python -m pip install --upgrade virtualenv + python -m virtualenv venv + - uses: actions/cache@v4 + id: cache + with: + path: | + venv/* + key: gradio-lib-${{ runner.os }}-pip-${{ hashFiles('client/python/requirements.txt') }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('test/requirements.txt') }} + - uses: actions/cache@v4 + id: frontend-cache + with: + path: | + gradio/templates/* + gradio/node/* + key: gradio-lib-front-end-${{ hashFiles('js/**', 'client/js/**')}} + - name: Install pnpm + if: steps.frontend-cache.outputs.cache-hit != 'true' + uses: pnpm/action-setup@v2 + with: + version: 8 + - uses: actions/setup-node@v4 + if: steps.frontend-cache.outputs.cache-hit != 'true' + with: + node-version: 18 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml + - name: Build frontend + if: steps.frontend-cache.outputs.cache-hit != 'true' + run: | + pnpm i --frozen-lockfile --ignore-scripts + pnpm build + - name: Install Gradio and Client Libraries Locally (Linux) + if: runner.os == 'Linux' + run: | + . venv/bin/activate + python -m pip install client/python + python -m pip install ".[oauth]" + - name: Install Test Dependencies (Linux) + if: steps.cache.outputs.cache-hit != 'true' && runner.os == 'Linux' + run: | + . venv/bin/activate + bash scripts/install_test_requirements.sh + - name: Install ffmpeg + uses: FedericoCarboni/setup-ffmpeg@v2 + - name: Lint (Linux) + if: runner.os == 'Linux' + run: | + . venv/bin/activate + bash scripts/lint_backend.sh + - name: Typecheck (Linux) + if: runner.os == 'Linux' + run: | + . venv/bin/activate + bash scripts/type_check_backend.sh + - name: Run tests (Linux) + if: runner.os == 'Linux' + run: | + . venv/bin/activate + python -m pytest -m "${{ matrix.test-type }}" --ignore=client + - name: Install Gradio and Client Libraries Locally (Windows) + if: runner.os == 'Windows' + run: | + venv\Scripts\activate + python -m pip install client/python + python -m pip install ".[oauth]" + - name: Install Test Dependencies (Windows) + if: steps.cache.outputs.cache-hit != 'true' && runner.os == 'Windows' + run: | + venv\Scripts\activate + python -m pip install . -r test/requirements.txt + - name: Run tests (Windows) + if: runner.os == 'Windows' + run: | + venv\Scripts\activate + python -m pytest -m "${{ matrix.test-type }}" --ignore=client + echo "The exit code for pytest was $LASTEXITCODE" diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml new file mode 100644 index 0000000000000000000000000000000000000000..ee853e1d677700f33bfa62b1d1b35833f6ab3e89 --- /dev/null +++ b/.github/workflows/build-pr.yml @@ -0,0 +1,76 @@ +name: Build PR Artifacts + +on: + workflow_dispatch: + pull_request: + branches: + - main + +jobs: + comment-spaces-start: + uses: "./.github/workflows/comment-queue.yml" + secrets: + gh_token: ${{ secrets.COMMENT_TOKEN }} + with: + pr_number: ${{ github.event.pull_request.number }} + message: spaces~pending~null + build_pr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: '3.9' + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + - uses: actions/setup-node@v4 + with: + node-version: 18 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml + - name: Install pip + run: python -m pip install build requests + - name: Get PR Number + id: get_pr_number + run: | + if ${{ github.event_name == 'pull_request' }}; then + echo "GRADIO_VERSION=$(python -c 'import requests;print(requests.get("https://pypi.org/pypi/gradio/json").json()["info"]["version"])')" >> $GITHUB_OUTPUT + python -c "import os;print(os.environ['GITHUB_REF'].split('/')[2])" > pr_number.txt + echo "PR_NUMBER=$(cat pr_number.txt)" >> $GITHUB_OUTPUT + else + echo "GRADIO_VERSION=$(python -c 'import json; print(json.load(open("gradio/package.json"))["version"])')" >> $GITHUB_OUTPUT + echo "PR_NUMBER='main'" >> $GITHUB_OUTPUT + fi + - name: Build pr package + run: | + python -c 'import json; j = json.load(open("gradio/package.json")); j["version"] = "${{ steps.get_pr_number.outputs.GRADIO_VERSION }}"; json.dump(j, open("gradio/package.json", "w"))' + pnpm i --frozen-lockfile --ignore-scripts + pnpm build + python3 -m build -w + env: + NODE_OPTIONS: --max_old_space_size=8192 + - name: Upload wheel + uses: actions/upload-artifact@v4 + with: + name: gradio-${{ steps.get_pr_number.outputs.GRADIO_VERSION }}-py3-none-any.whl + path: dist/gradio-${{ steps.get_pr_number.outputs.GRADIO_VERSION }}-py3-none-any.whl + - name: Set up Demos + run: | + python scripts/copy_demos.py https://gradio-builds.s3.amazonaws.com/${{ github.sha }}/gradio-${{ steps.get_pr_number.outputs.GRADIO_VERSION }}-py3-none-any.whl \ + "gradio-client @ git+https://github.com/gradio-app/gradio@${{ github.sha }}#subdirectory=client/python" + - name: Upload all_demos + uses: actions/upload-artifact@v4 + with: + name: all_demos + path: demo/all_demos + - name: Create metadata artifact + run: | + python -c "import json; json.dump({'gh_sha': '${{ github.sha }}', 'pr_number': ${{ steps.get_pr_number.outputs.pr_number }}, 'version': '${{ steps.get_pr_number.outputs.GRADIO_VERSION }}', 'wheel': 'gradio-${{ steps.get_pr_number.outputs.GRADIO_VERSION }}-py3-none-any.whl'}, open('metadata.json', 'w'))" + - name: Upload metadata + uses: actions/upload-artifact@v4 + with: + name: metadata.json + path: metadata.json \ No newline at end of file diff --git a/.github/workflows/check-demo-notebooks.yml b/.github/workflows/check-demo-notebooks.yml new file mode 100644 index 0000000000000000000000000000000000000000..6d5c6e868664ff8492686dbbe60b741562733a28 --- /dev/null +++ b/.github/workflows/check-demo-notebooks.yml @@ -0,0 +1,50 @@ +# This workflow will check if the run.py files in every demo match the run.ipynb notebooks. + +name: Check Demos Match Notebooks + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - 'demo/**' + +jobs: + comment-notebook-start: + uses: "./.github/workflows/comment-queue.yml" + secrets: + gh_token: ${{ secrets.COMMENT_TOKEN }} + with: + pr_number: ${{ github.event.pull_request.number }} + message: notebooks~pending~null + check-notebooks: + name: Generate Notebooks and Check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + - name: Generate Notebooks + run: | + pip install nbformat && cd demo && python generate_notebooks.py + - name: Print Git Status + run: echo $(git status) && echo $(git diff) + - name: Assert Notebooks Match + id: assertNotebooksMatch + run: git status | grep "nothing to commit, working tree clean" + - name: Get PR Number + if: always() + run: | + python -c "import os;print(os.environ['GITHUB_REF'].split('/')[2])" > pr_number.txt + echo "PR_NUMBER=$(cat pr_number.txt)" >> $GITHUB_ENV + - name: Upload PR Number + if: always() + run: | + python -c "import json; json.dump({'pr_number': ${{ env.PR_NUMBER }}}, open('metadata.json', 'w'))" + - name: Upload metadata + if: always() + uses: actions/upload-artifact@v4 + with: + name: metadata.json + path: metadata.json \ No newline at end of file diff --git a/.github/workflows/comment-queue.yml b/.github/workflows/comment-queue.yml new file mode 100644 index 0000000000000000000000000000000000000000..54d8f788224a0bf9419981bf70f4cf73695aee4c --- /dev/null +++ b/.github/workflows/comment-queue.yml @@ -0,0 +1,36 @@ +name: Comment on pull request without race conditions + +on: + workflow_call: + inputs: + pr_number: + type: string + message: + required: true + type: string + tag: + required: false + type: string + default: "previews" + additional_text: + required: false + type: string + default: "" + secrets: + gh_token: + required: true + +jobs: + comment: + concurrency: + group: ${{inputs.pr_number || inputs.tag}} + runs-on: ubuntu-latest + steps: + - name: comment on pr + uses: "gradio-app/github/actions/comment-pr@main" + with: + gh_token: ${{ secrets.gh_token }} + tag: ${{ inputs.tag }} + pr_number: ${{ inputs.pr_number}} + message: ${{ inputs.message }} + additional_text: ${{ inputs.additional_text }} diff --git a/.github/workflows/delete-stale-spaces.yml b/.github/workflows/delete-stale-spaces.yml new file mode 100644 index 0000000000000000000000000000000000000000..3b93398a69a93aadc315192a6d17c2c182df4311 --- /dev/null +++ b/.github/workflows/delete-stale-spaces.yml @@ -0,0 +1,35 @@ +name: Delete Stale Spaces + +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + inputs: + daysStale: + description: 'How stale a space needs to be to be deleted (days)' + required: true + default: '7' + + +jobs: + delete-old-spaces: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: '3.9' + - name: Install pip + run: python -m pip install pip wheel requests + - name: Install Hub Client Library + run: pip install huggingface-hub==0.9.1 + - name: Set daysStale + env: + DEFAULT_DAYS_STALE: '7' + run: echo "DAYS_STALE=${{ github.event.inputs.daysStale || env.DEFAULT_DAYS_STALE }}" >> $GITHUB_ENV + - name: Find and delete stale spaces + run: | + python scripts/delete_old_spaces.py $DAYS_STALE \ + gradio-pr-deploys \ + ${{ secrets.SPACES_DEPLOY_TOKEN }} diff --git a/.github/workflows/deploy-chromatic.yml b/.github/workflows/deploy-chromatic.yml new file mode 100644 index 0000000000000000000000000000000000000000..1842b64b92965fa19158957db405abff9255fbd0 --- /dev/null +++ b/.github/workflows/deploy-chromatic.yml @@ -0,0 +1,88 @@ +name: 'deploy to chromatic' + +on: + push: + paths: + - 'js/**' + - 'gradio/themes/**' + - '.github/workflows/deploy-chromatic.yml' + - '!js/_website/**' + + +jobs: + get-current-pr: + runs-on: ubuntu-latest + outputs: + pr_found: ${{ steps.get-pr.outputs.pr_found }} + pr_number: ${{ steps.get-pr.outputs.number }} + pr_labels: ${{ steps.get-pr.outputs.pr_labels }} + steps: + - uses: 8BitJonny/gh-get-current-pr@2.2.0 + id: get-pr + with: + filterOutDraft: true + comment-chromatic-start: + uses: "./.github/workflows/comment-queue.yml" + needs: get-current-pr + secrets: + gh_token: ${{ secrets.COMMENT_TOKEN }} + with: + pr_number: ${{ needs.get-current-pr.outputs.pr_number }} + message: | + storybook~pending~null + visual~pending~0~0~null + chromatic-deployment: + needs: get-current-pr + runs-on: ubuntu-latest + outputs: + changes: ${{ steps.publish-chromatic.outputs.changeCount }} + errors: ${{ steps.publish-chromatic.outputs.errorCount }} + storybook_url: ${{ steps.publish-chromatic.outputs.storybookUrl }} + build_url: ${{ steps.publish-chromatic.outputs.buildUrl }} + if: ${{ github.repository == 'gradio-app/gradio' && !contains(needs.get-current-pr.outputs.pr_labels, 'no-visual-update') }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: install dependencies + uses: "./.github/actions/install-all-deps" + with: + always-install-pnpm: true + skip_build: 'true' + - name: build client + run: pnpm --filter @gradio/client build + - name: generate theme.css + run: | + . venv/bin/activate + python scripts/generate_theme.py --outfile js/storybook/theme.css + - name: build storybook + run: pnpm build-storybook --quiet + - name: publish to chromatic + id: publish-chromatic + uses: chromaui/action@v10 + with: + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} + onlyChanged: true + exitOnceUploaded: true + comment-chromatic-end: + uses: "./.github/workflows/comment-queue.yml" + needs: [chromatic-deployment, get-current-pr] + secrets: + gh_token: ${{ secrets.COMMENT_TOKEN }} + with: + pr_number: ${{ needs.get-current-pr.outputs.pr_number }} + message: | + storybook~success~${{ needs.chromatic-deployment.outputs.storybook_url }} + visual~success~${{ needs.chromatic-deployment.outputs.changes }}~${{ needs.chromatic-deployment.outputs.errors }}~${{ needs.chromatic-deployment.outputs.build_url }} + comment-chromatic-fail: + uses: "./.github/workflows/comment-queue.yml" + needs: [chromatic-deployment, get-current-pr] + if: always() && needs.chromatic-deployment.result == 'failure' + secrets: + gh_token: ${{ secrets.COMMENT_TOKEN }} + with: + pr_number: ${{ needs.get-current-pr.outputs.pr_number }} + message: | + storybook~failure~https://github.com/gradio-app/gradio/actions/runs/${{github.run_id}}/ + visual~failure~0~0~https://github.com/gradio-app/gradio/actions/runs/${{github.run_id}}/ \ No newline at end of file diff --git a/.github/workflows/deploy-pr-to-spaces.yml b/.github/workflows/deploy-pr-to-spaces.yml new file mode 100644 index 0000000000000000000000000000000000000000..1e336512e6dbb1d6a115ab9f431268f628f7af5d --- /dev/null +++ b/.github/workflows/deploy-pr-to-spaces.yml @@ -0,0 +1,99 @@ +name: Deploy PR to Spaces + +on: + workflow_run: + workflows: [Build PR Artifacts] + types: + - completed + +jobs: + deploy-current-pr: + outputs: + pr_number: ${{ steps.set-outputs.outputs.pr_number }} + space_url: ${{ steps.upload-demo.outputs.SPACE_URL }} + sha: ${{ steps.set-outputs.outputs.gh_sha }} + gradio_version: ${{ steps.set-outputs.outputs.gradio_version }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: '3.9' + - name: Install pip + run: python -m pip install build requests + - name: Download metadata + run: python scripts/download_artifacts.py ${{github.event.workflow_run.id }} metadata.json ${{ secrets.COMMENT_TOKEN }} --owner ${{ github.repository_owner }} + - run: unzip metadata.json.zip + - name: set outputs + id: set-outputs + run: | + echo "wheel_name=$(python -c 'import json; print(json.load(open("metadata.json"))["wheel"])')" >> $GITHUB_OUTPUT + echo "gh_sha=$(python -c 'import json; print(json.load(open("metadata.json"))["gh_sha"])')" >> $GITHUB_OUTPUT + echo "gradio_version=$(python -c 'import json; print(json.load(open("metadata.json"))["version"])')" >> $GITHUB_OUTPUT + echo "pr_number=$(python -c 'import json; print(json.load(open("metadata.json"))["pr_number"])')" >> $GITHUB_OUTPUT + - name: 'Download wheel' + run: python scripts/download_artifacts.py ${{ github.event.workflow_run.id }} ${{ steps.set-outputs.outputs.wheel_name }} ${{ secrets.COMMENT_TOKEN }} --owner ${{ github.repository_owner }} + - run: unzip ${{ steps.set-outputs.outputs.wheel_name }}.zip + - name: Upload wheel + run: | + export AWS_ACCESS_KEY_ID=${{ secrets.PR_DEPLOY_KEY }} + export AWS_SECRET_ACCESS_KEY=${{ secrets.PR_DEPLOY_SECRET }} + export AWS_DEFAULT_REGION=us-east-1 + aws s3 cp ${{ steps.set-outputs.outputs.wheel_name }} s3://gradio-builds/${{ steps.set-outputs.outputs.gh_sha }}/ + - name: Install Hub Client Library + run: pip install huggingface-hub + - name: 'Download all_demos' + run: python scripts/download_artifacts.py ${{ github.event.workflow_run.id }} all_demos ${{ secrets.COMMENT_TOKEN }} --owner ${{ github.repository_owner }} + - run: unzip all_demos.zip -d all_demos + - run: cp -R all_demos/* demo/all_demos + - name: Upload demo to spaces + if: github.event.workflow_run.event == 'pull_request' + id: upload-demo + run: | + python scripts/upload_demo_to_space.py all_demos \ + gradio-pr-deploys/pr-${{ steps.set-outputs.outputs.pr_number }}-all-demos \ + ${{ secrets.SPACES_DEPLOY_TOKEN }} \ + --gradio-version ${{ steps.set-outputs.outputs.gradio_version }} > url.txt + echo "SPACE_URL=$(cat url.txt)" >> $GITHUB_OUTPUT + - name: Upload Website Demos + if: > + github.event.workflow_run.event == 'workflow_dispatch' && + github.event.workflow_run.conclusion == 'success' + id: upload-website-demos + run: | + python scripts/upload_website_demos.py --AUTH_TOKEN ${{ secrets.WEBSITE_SPACES_DEPLOY_TOKEN }} \ + --WHEEL_URL https://gradio-builds.s3.amazonaws.com/${{ steps.set-outputs.outputs.gh_sha }}/ \ + --GRADIO_VERSION ${{ steps.set-outputs.outputs.gradio_version }} + + comment-spaces-success: + uses: "./.github/workflows/comment-queue.yml" + needs: [deploy-current-pr] + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' && + needs.deploy-current-pr.result == 'success' + secrets: + gh_token: ${{ secrets.COMMENT_TOKEN }} + with: + pr_number: ${{ needs.deploy-current-pr.outputs.pr_number }} + message: spaces~success~${{ needs.deploy-current-pr.outputs.space_url }} + additional_text: | + **Install Gradio from this PR** + ```bash + pip install https://gradio-builds.s3.amazonaws.com/${{ needs.deploy-current-pr.outputs.sha }}/gradio-${{ needs.deploy-current-pr.outputs.gradio_version }}-py3-none-any.whl + ``` + + **Install Gradio Python Client from this PR** + ```bash + pip install "gradio-client @ git+https://github.com/gradio-app/gradio@${{ needs.deploy-current-pr.outputs.sha }}#subdirectory=client/python" + ``` + comment-spaces-failure: + uses: "./.github/workflows/comment-queue.yml" + needs: [deploy-current-pr] + if: always() && needs.deploy-current-pr.result == 'failure' + secrets: + gh_token: ${{ secrets.COMMENT_TOKEN }} + with: + pr_number: ${{ needs.deploy-current-pr.outputs.pr_number }} + message: spaces~failure~https://github.com/gradio-app/gradio/actions/runs/${{github.run_id}}/ diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml new file mode 100644 index 0000000000000000000000000000000000000000..7485a3c3afc30d74f4806fc1537eadb84b5f4554 --- /dev/null +++ b/.github/workflows/deploy-website.yml @@ -0,0 +1,108 @@ +name: "deploy website" + +on: + workflow_call: + inputs: + branch_name: + description: "The branch name" + type: string + pr_number: + description: "The PR number" + type: string + secrets: + vercel_token: + description: "Vercel API token" + gh_token: + description: "Github token" + required: true + vercel_org_id: + description: "Vercel organization ID" + required: true + vercel_project_id: + description: "Vercel project ID" + required: true + +env: + VERCEL_ORG_ID: ${{ secrets.vercel_org_id }} + VERCEL_PROJECT_ID: ${{ secrets.vercel_project_id }} + +jobs: + comment-deploy-start: + uses: "./.github/workflows/comment-queue.yml" + secrets: + gh_token: ${{ secrets.gh_token }} + with: + pr_number: ${{ inputs.pr_number }} + message: website~pending~null + deploy: + name: "Deploy website" + runs-on: ubuntu-latest + outputs: + vercel_url: ${{ steps.output_url.outputs.vercel_url }} + steps: + - uses: actions/checkout@v3 + - name: install dependencies + uses: "./.github/actions/install-frontend-deps" + with: + always-install-pnpm: true + skip_build: true + - name: download artifacts + uses: actions/download-artifact@v4 + with: + name: website-json-${{ inputs.pr_number }} + path: | + ./js/_website/src/lib/json + - name: echo artifact path + shell: bash + run: ls ./js/_website/src/lib/json + - name: Install Vercel CLI + shell: bash + run: pnpm install --global vercel@latest + # preview + - name: Pull Vercel Environment Information + shell: bash + if: github.event_name == 'pull_request' + run: vercel pull --yes --environment=preview --token=${{ secrets.vercel_token }} + - name: Build Project Artifacts + if: github.event_name == 'pull_request' + shell: bash + run: vercel build --token=${{ secrets.vercel_token }} + - name: Deploy Project Artifacts to Vercel + if: github.event_name == 'pull_request' + id: output_url + shell: bash + run: echo "vercel_url=$(vercel deploy --prebuilt --token=${{ secrets.vercel_token }})" >> $GITHUB_OUTPUT + # production + - name: Pull Vercel Environment Information + if: github.event_name == 'push' && inputs.branch_name == 'main' + shell: bash + run: vercel pull --yes --environment=production --token=${{ secrets.vercel_token }} + - name: Build Project Artifacts + if: github.event_name == 'push' && inputs.branch_name == 'main' + shell: bash + run: vercel build --prod --token=${{ secrets.vercel_token }} + - name: Deploy Project Artifacts to Vercel + if: github.event_name == 'push' && inputs.branch_name == 'main' + shell: bash + run: echo "VERCEL_URL=$(vercel deploy --prebuilt --prod --token=${{ secrets.vercel_token }})" >> $GITHUB_ENV + - name: echo vercel url + shell: bash + run: echo $VERCEL_URL #add to comment + comment-deploy-success: + uses: "./.github/workflows/comment-queue.yml" + needs: deploy + if: needs.deploy.result == 'success' + secrets: + gh_token: ${{ secrets.gh_token }} + with: + pr_number: ${{ inputs.pr_number }} + message: website~success~${{needs.deploy.outputs.vercel_url}} + comment-deploy-failure: + uses: "./.github/workflows/comment-queue.yml" + needs: deploy + if: always() && needs.deploy.result == 'failure' + secrets: + gh_token: ${{ secrets.gh_token }} + with: + pr_number: ${{ inputs.pr_number }} + message: website~failure~https://github.com/gradio-app/gradio/actions/runs/${{github.run_id}}/ \ No newline at end of file diff --git a/.github/workflows/generate-changeset.yml b/.github/workflows/generate-changeset.yml new file mode 100644 index 0000000000000000000000000000000000000000..50c4af6c644bcafc4cf46db2a5036f24c976d335 --- /dev/null +++ b/.github/workflows/generate-changeset.yml @@ -0,0 +1,88 @@ +name: Generate changeset +on: + workflow_run: + workflows: ["trigger changeset generation"] + types: + - completed + +env: + CI: true + NODE_OPTIONS: "--max-old-space-size=4096" + +concurrency: + group: ${{ github.event.workflow_run.head_repository.full_name }}::${{ github.event.workflow_run.head_branch }} + +jobs: + get-pr: + runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion == 'success' + outputs: + found_pr: ${{ steps.pr_details.outputs.found_pr }} + pr_number: ${{ steps.pr_details.outputs.pr_number }} + source_repo: ${{ steps.pr_details.outputs.source_repo }} + source_branch: ${{ steps.pr_details.outputs.source_branch }} + steps: + - name: get pr details + id: pr_details + uses: gradio-app/github/actions/find-pr@main + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + comment-changes-start: + uses: "./.github/workflows/comment-queue.yml" + needs: get-pr + secrets: + gh_token: ${{ secrets.COMMENT_TOKEN }} + with: + pr_number: ${{ needs.get-pr.outputs.pr_number }} + message: changes~pending~null + version: + permissions: write-all + name: static checks + needs: get-pr + runs-on: ubuntu-22.04 + if: needs.get-pr.outputs.found_pr == 'true' + outputs: + skipped: ${{ steps.version.outputs.skipped }} + comment_url: ${{ steps.version.outputs.comment_url }} + steps: + - uses: actions/checkout@v3 + with: + repository: ${{ needs.get-pr.outputs.source_repo }} + ref: ${{ needs.get-pr.outputs.source_branch }} + fetch-depth: 0 + token: ${{ secrets.COMMENT_TOKEN }} + - name: generate changeset + id: version + uses: "gradio-app/github/actions/generate-changeset@main" + with: + github_token: ${{ secrets.COMMENT_TOKEN }} + main_pkg: gradio + pr_number: ${{ needs.get-pr.outputs.pr_number }} + branch_name: ${{ needs.get-pr.outputs.source_branch }} + comment-changes-skipped: + uses: "./.github/workflows/comment-queue.yml" + needs: [get-pr, version] + if: needs.version.result == 'success' && needs.version.outputs.skipped == 'true' + secrets: + gh_token: ${{ secrets.COMMENT_TOKEN }} + with: + pr_number: ${{ needs.get-pr.outputs.pr_number }} + message: changes~warning~https://github.com/gradio-app/gradio/actions/runs/${{github.run_id}}/ + comment-changes-success: + uses: "./.github/workflows/comment-queue.yml" + needs: [get-pr, version] + if: needs.version.result == 'success' && needs.version.outputs.skipped == 'false' + secrets: + gh_token: ${{ secrets.COMMENT_TOKEN }} + with: + pr_number: ${{ needs.get-pr.outputs.pr_number }} + message: changes~success~${{ needs.version.outputs.comment_url }} + comment-changes-failure: + uses: "./.github/workflows/comment-queue.yml" + needs: [get-pr, version] + if: always() && needs.version.result == 'failure' + secrets: + gh_token: ${{ secrets.COMMENT_TOKEN }} + with: + pr_number: ${{ needs.get-pr.outputs.pr_number }} + message: changes~failure~https://github.com/gradio-app/gradio/actions/runs/${{github.run_id}}/ \ No newline at end of file diff --git a/.github/workflows/large-files.yml b/.github/workflows/large-files.yml new file mode 100644 index 0000000000000000000000000000000000000000..50c6fb74b6d7d7638c41ba8da6c38070f2d68594 --- /dev/null +++ b/.github/workflows/large-files.yml @@ -0,0 +1,21 @@ +name: Check for large files + +on: + pull_request: + +jobs: + check-files: + runs-on: ubuntu-latest + if: github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + - name: Check for large files + uses: actionsdesk/lfs-warning@v3.2 + with: + filesizelimit: 5MB + diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml new file mode 100644 index 0000000000000000000000000000000000000000..d29e9fd6b8d0848b941ce7dffdef50666f154bff --- /dev/null +++ b/.github/workflows/publish-npm.yml @@ -0,0 +1,77 @@ +name: Changesets +on: + push: + branches: + - main + +env: + CI: true + PNPM_CACHE_FOLDER: .pnpm-store + NODE_OPTIONS: "--max-old-space-size=4096" +jobs: + version_or_publish: + runs-on: ubuntu-22.04 + steps: + - name: checkout repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + persist-credentials: false + - name: install dependencies + uses: "./.github/actions/install-all-deps" + with: + always-install-pnpm: true + node_auth_token: ${{ secrets.NPM_TOKEN }} + npm_token: ${{ secrets.NPM_TOKEN }} + skip_build: 'true' + - name: Build packages + run: | + . venv/bin/activate + pip install build + pnpm --filter @gradio/client --filter @gradio/lite build + - name: create and publish versions + id: changesets + uses: changesets/action@v1 + with: + version: pnpm ci:version + commit: "chore: update versions" + title: "chore: update versions" + publish: pnpm ci:publish + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GRADIO_PAT }} + - name: add label to skip chromatic build + if: ${{ steps.changesets.outputs.pullRequestNumber != '' && steps.changesets.outputs.pullRequestNumber != 'undefined' }} + run: gh pr edit "$PR_NUMBER" --add-label "no-visual-update" + env: + PR_NUMBER: ${{ steps.changesets.outputs.pullRequestNumber }} + GITHUB_TOKEN: ${{ secrets.GRADIO_PAT }} + - name: add label to run flaky tests + if: ${{ steps.changesets.outputs.pullRequestNumber != '' && steps.changesets.outputs.pullRequestNumber != 'undefined' }} + run: gh pr edit "$PR_NUMBER" --add-label "flaky-tests" + env: + PR_NUMBER: ${{ steps.changesets.outputs.pullRequestNumber }} + GITHUB_TOKEN: ${{ secrets.GRADIO_PAT }} + - name: add label to run backend tests on Windows + if: ${{ steps.changesets.outputs.pullRequestNumber != '' && steps.changesets.outputs.pullRequestNumber != 'undefined' }} + run: gh pr edit "$PR_NUMBER" --add-label "windows-tests" + env: + PR_NUMBER: ${{ steps.changesets.outputs.pullRequestNumber }} + GITHUB_TOKEN: ${{ secrets.GRADIO_PAT }} + - name: publish to pypi + if: steps.changesets.outputs.hasChangesets != 'true' + uses: "gradio-app/github/actions/publish-pypi@main" + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWSACCESSKEYID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWSSECRETKEY }} + AWS_DEFAULT_REGION: us-west-2 + with: + user: __token__ + passwords: | + gradio:${{ secrets.PYPI_API_TOKEN }} + gradio_client:${{ secrets.PYPI_GRADIO_CLIENT_TOKEN }} + - name: trigger spaces deploy workflow + env: + GITHUB_TOKEN: ${{ secrets.COMMENT_TOKEN }} + run: gh workflow run build-pr.yml \ No newline at end of file diff --git a/.github/workflows/report-notebook-status-pr.yml b/.github/workflows/report-notebook-status-pr.yml new file mode 100644 index 0000000000000000000000000000000000000000..d943420e442aaa47833ee5530377bac02b7a489f --- /dev/null +++ b/.github/workflows/report-notebook-status-pr.yml @@ -0,0 +1,47 @@ +on: + workflow_run: + workflows: [Check Demos Match Notebooks] + types: [completed] + +jobs: + get-pr-number: + runs-on: ubuntu-latest + outputs: + pr_number: ${{ steps.pr_number.outputs.pr_number }} + steps: + - uses: actions/checkout@v3 + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: '3.9' + - name: Install pip + run: python -m pip install requests + - name: Download metadata + run: python scripts/download_artifacts.py ${{github.event.workflow_run.id }} metadata.json ${{ secrets.COMMENT_TOKEN }} --owner ${{ github.repository_owner }} + - run: unzip metadata.json.zip + - name: Pipe metadata to env + id: pr_number + run: echo "pr_number=$(python -c 'import json; print(json.load(open("metadata.json"))["pr_number"])')" >> $GITHUB_OUTPUT + comment-success: + uses: "./.github/workflows/comment-queue.yml" + if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.name == 'Check Demos Match Notebooks'}} + needs: get-pr-number + secrets: + gh_token: ${{ secrets.COMMENT_TOKEN }} + with: + pr_number: ${{ needs.get-pr-number.outputs.pr_number }} + message: notebooks~success~null + comment-failure: + uses: "./.github/workflows/comment-queue.yml" + if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.name == 'Check Demos Match Notebooks'}} + needs: get-pr-number + secrets: + gh_token: ${{ secrets.COMMENT_TOKEN }} + with: + pr_number: ${{ needs.get-pr-number.outputs.pr_number }} + message: notebooks~failure~https://github.com/gradio-app/gradio/actions/runs/${{github.run_id}}/ + additional_text: | + The demo notebooks don't match the run.py files. Please run this command from the root of the repo and then commit the changes: + ```bash + pip install nbformat && cd demo && python generate_notebooks.py + ``` \ No newline at end of file diff --git a/.github/workflows/trigger-changeset.yml b/.github/workflows/trigger-changeset.yml new file mode 100644 index 0000000000000000000000000000000000000000..cc6e20d9962b43ada7113702be3e37e17cbab60a --- /dev/null +++ b/.github/workflows/trigger-changeset.yml @@ -0,0 +1,19 @@ +name: trigger changeset generation +on: + pull_request: + types: [opened, synchronize, reopened, edited, labeled, unlabeled] + branches: + - main + issue_comment: + types: [edited] + +jobs: + version: + permissions: write-all + name: static checks + runs-on: ubuntu-22.04 + if: github.event.sender.login != 'gradio-pr-bot' + steps: + - run: echo ${{ github.event_name }} + - run: echo ${{ github.event.sender.login }} + - run: echo "Triggering changeset generation" diff --git a/.github/workflows/ui.yml b/.github/workflows/ui.yml new file mode 100644 index 0000000000000000000000000000000000000000..3cf5ce416a4e369890f81a9015421f6ad2012d68 --- /dev/null +++ b/.github/workflows/ui.yml @@ -0,0 +1,103 @@ +name: gradio-ui + +on: + push: + branches: + - "main" + pull_request: + +env: + CI: true + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1" + NODE_OPTIONS: "--max-old-space-size=4096" + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} +concurrency: + group: deploy-${{ github.ref }}-${{ github.event_name == 'push' || github.event.inputs.fire != null }} + cancel-in-progress: true + +jobs: + quick-checks: + name: static checks + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - name: install dependencies + uses: "./.github/actions/install-frontend-deps" + with: + always-install-pnpm: true + - name: build client + run: pnpm --filter @gradio/client build + - name: build the wasm module + run: pnpm --filter @gradio/wasm build + - name: format check + run: pnpm format:check + - name: lint + run: pnpm lint + - name: typecheck + run: pnpm ts:check + - name: unit tests + run: pnpm test:run + functional-test: + runs-on: ubuntu-latest + outputs: + source_branch: ${{ steps.pr_details.outputs.source_branch }} + pr_number: ${{ steps.pr_details.outputs.pr_number }} + steps: + - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 + - name: install dependencies + id: install_deps + uses: "./.github/actions/install-all-deps" + with: + always-install-pnpm: true + - name: get pr details + id: pr_details + uses: gradio-app/github/actions/find-pr@main + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: deploy json to aws + if: steps.pr_details.outputs.source_branch == 'changeset-release/main' + run: | + export AWS_ACCESS_KEY_ID=${{ secrets.AWSACCESSKEYID }} + export AWS_SECRET_ACCESS_KEY=${{ secrets.AWSSECRETKEY }} + export AWS_DEFAULT_REGION=us-west-2 + version=$(jq -r .version js/_website/src/lib/json/version.json) + aws s3 cp ./js/_website/src/lib/json/ s3://gradio-docs-json/$version/ --recursive + - name: install outbreak_forecast dependencies + run: | + . venv/bin/activate + python -m pip install -r demo/outbreak_forecast/requirements.txt + - run: pnpm exec playwright install chromium + - name: run browser tests + run: | + . venv/bin/activate + CI=1 pnpm test:browser + - name: upload screenshots + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-screenshots + path: | + ./test-results + - name: run browser component tests + run: | + . venv/bin/activate + pnpm run test:ct + - name: save artifacts + uses: actions/upload-artifact@v4 + with: + name: website-json-${{ steps.pr_details.outputs.pr_number }} + path: | + ./js/_website/src/lib/json + deploy_to_vercel: + uses: "./.github/workflows/deploy-website.yml" + needs: functional-test + if: always() + secrets: + gh_token: ${{ secrets.COMMENT_TOKEN }} + vercel_token: ${{ secrets.VERCEL_TOKEN }} + vercel_org_id: ${{ secrets.VERCEL_ORG_ID }} + vercel_project_id: ${{ secrets.VERCEL_PROJECT_ID }} + with: + branch_name: ${{ needs.functional-test.outputs.source_branch }} + pr_number: ${{ needs.functional-test.outputs.pr_number }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c79420e85a6811dfe07e8a5a7b6c3f6230d3b832 --- /dev/null +++ b/.gitignore @@ -0,0 +1,80 @@ +# Python build +.eggs/ +gradio.egg-info +dist/ +*.pyc +__pycache__/ +*.py[cod] +*$py.class +build/ +__tmp/* +*.pyi +py.typed + +# JS build +gradio/templates/* +gradio/node/* +gradio/_frontend_code/* +js/gradio-preview/test/* + +# Secrets +.env + +# Gradio run artifacts +*.db +*.sqlite3 +gradio/launches.json +flagged/ +gradio_cached_examples/ +tmp.zip + +# Tests +.coverage +coverage.xml +test.txt +**/snapshots/**/*.png +playwright-report/ + +# Demos +demo/tmp.zip +demo/files/*.avi +demo/files/*.mp4 +demo/all_demos/demos/* +demo/all_demos/requirements.txt +demo/*/config.json +demo/annotatedimage_component/*.png + +# Etc +.idea/* +.DS_Store +*.bak +workspace.code-workspace +*.h5 + +# dev containers +.pnpm-store/ + +# log files +.pnpm-debug.log + +# Local virtualenv for devs +.venv* + +# FRP +gradio/frpc_* +.vercel + +# js +node_modules +public/build/ +test-results +client/js/test.js +.config/test.py + +# storybook +storybook-static +build-storybook.log +js/storybook/theme.css + +# playwright +.config/playwright/.cache \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000000000000000000000000000000000..f94e184c9c9c254edd18b91f54903b6c97e544d4 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "phoenisx.cssvar", + "esbenp.prettier-vscode", + "svelte.svelte-vscode", + "charliermarsh.ruff" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..73595981aea90ef5f62289b210c9a564b49deb08 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,23 @@ +{ + "python.formatting.provider": "none", + "cssvar.files": ["./js/node_modules/pollen-css/pollen.css"], + "cssvar.ignore": [], + "cssvar.disableSort": true, + "cssvar.extensions": ["js", "css", "html", "jsx", "tsx", "svelte"], + "python.analysis.extraPaths": ["./gradio/themes/utils"], + "svelte.plugin.svelte.format.enable": true, + "svelte.plugin.svelte.diagnostics.enable": false, + "svelte.enable-ts-plugin": true, + "prettier.configPath": ".config/.prettierrc.json", + "prettier.ignorePath": ".config/.prettierignore", + "python.analysis.typeCheckingMode": "basic", + "python.testing.pytestArgs": ["."], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "eslint.validate": ["javascript", "typescript", "html", "markdown", "svelte"], + "eslint.experimental.useFlatConfig": true, + "eslint.options": { + "overrideConfigFile": "./.config/eslint.config.js" + }, + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..4b993d7053b6a09c0974557e458ced890771d62a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4904 @@ +# gradio + +## 4.16.0 + +### Features + +- [#7124](https://github.com/gradio-app/gradio/pull/7124) [`21a16c6`](https://github.com/gradio-app/gradio/commit/21a16c60e8f34b870bd2aae9af07713eb1307252) - add params to `gr.Interface` and `gr.ChatInterface`. Thanks [@abidlabs](https://github.com/abidlabs)! +- [#7139](https://github.com/gradio-app/gradio/pull/7139) [`6abad53`](https://github.com/gradio-app/gradio/commit/6abad536778517a2ab9f5fc75d52afc576f01218) - Added polars dataframe support with demo. Thanks [@cswamy](https://github.com/cswamy)! +- [#7084](https://github.com/gradio-app/gradio/pull/7084) [`94aa271`](https://github.com/gradio-app/gradio/commit/94aa271ab11fc3426a7e143ebaa757eb30c9911d) - Improve rapid generation performance via UI throttling. Thanks [@aliabid94](https://github.com/aliabid94)! +- [#7104](https://github.com/gradio-app/gradio/pull/7104) [`bc2cdc1`](https://github.com/gradio-app/gradio/commit/bc2cdc1df95b38025486cf76df4a494b66d98585) - Allow download button for interactive Audio and Video components. Thanks [@hannahblair](https://github.com/hannahblair)! +- [#7109](https://github.com/gradio-app/gradio/pull/7109) [`125a832`](https://github.com/gradio-app/gradio/commit/125a832ab7ee2b5affa574e8b32c88f430cc6663) - generate docs when running `gradio cc build`. Thanks [@pngwn](https://github.com/pngwn)! +- [#7148](https://github.com/gradio-app/gradio/pull/7148) [`c60ad4d`](https://github.com/gradio-app/gradio/commit/c60ad4d34ab5b56a89bf6796822977e51e7a4a32) - Use Gallery as input component. Thanks [@freddyaboulton](https://github.com/freddyaboulton)! +- [#7049](https://github.com/gradio-app/gradio/pull/7049) [`1718c4a`](https://github.com/gradio-app/gradio/commit/1718c4aeb23a88ef02b17b30a1d1cb72e413e04a) - add STL 3D model support. Thanks [@Mon-ius](https://github.com/Mon-ius)! +- [#7159](https://github.com/gradio-app/gradio/pull/7159) [`6ee22dc`](https://github.com/gradio-app/gradio/commit/6ee22dc6a8f6419e127a0f650e58c87a31bc59c9) - Ensure `gradio cc publish` uploads the documentation space, if it exists. Thanks [@pngwn](https://github.com/pngwn)! +- [#7034](https://github.com/gradio-app/gradio/pull/7034) [`82fe73d`](https://github.com/gradio-app/gradio/commit/82fe73d04297ac4e2c3ef42edc62bab4300bf915) - Redirect with query params after oauth. Thanks [@Wauplin](https://github.com/Wauplin)! +- [#7063](https://github.com/gradio-app/gradio/pull/7063) [`2cdcf4a`](https://github.com/gradio-app/gradio/commit/2cdcf4a890202a55673588c16f27b327d27915b6) - Single oauth button. Thanks [@Wauplin](https://github.com/Wauplin)! + +### Fixes + +- [#7126](https://github.com/gradio-app/gradio/pull/7126) [`5727b92`](https://github.com/gradio-app/gradio/commit/5727b92abc8a00a675bfc0a921b38de771af947b) - Allow buttons to take null value. Thanks [@abidlabs](https://github.com/abidlabs)! +- [#7112](https://github.com/gradio-app/gradio/pull/7112) [`217bfe3`](https://github.com/gradio-app/gradio/commit/217bfe39ca6a0de885824f16be4a707e7f032d57) - Support audio data in `np.int8` format in the `gr.Audio` component. Thanks [@Ram-Pasupula](https://github.com/Ram-Pasupula)! +- [#7029](https://github.com/gradio-app/gradio/pull/7029) [`ac73555`](https://github.com/gradio-app/gradio/commit/ac735551bb2ccc288b2bbf10b008b6c3d9e65132) - Run before_fn and after_fn for each generator iteration. Thanks [@freddyaboulton](https://github.com/freddyaboulton)! +- [#7131](https://github.com/gradio-app/gradio/pull/7131) [`7d53aa1`](https://github.com/gradio-app/gradio/commit/7d53aa13a304d056d1973b8e86c6f89ff84cbd28) - Miscellaneous doc fixes. Thanks [@abidlabs](https://github.com/abidlabs)! +- [#7138](https://github.com/gradio-app/gradio/pull/7138) [`ca8753b`](https://github.com/gradio-app/gradio/commit/ca8753bb3d829d0077f758ba8d0ddc866ff74d3d) - Fixes: Chatbot crashes when given empty url following http:// or https://. Thanks [@dawoodkhan82](https://github.com/dawoodkhan82)! +- [#7115](https://github.com/gradio-app/gradio/pull/7115) [`cb90b3d`](https://github.com/gradio-app/gradio/commit/cb90b3d5d6a291270e047e10f9173cbc03678e1c) - Programmatically determine max wheel version to push to spaces. Thanks [@freddyaboulton](https://github.com/freddyaboulton)! +- [#7107](https://github.com/gradio-app/gradio/pull/7107) [`80f8fbf`](https://github.com/gradio-app/gradio/commit/80f8fbf0e8900627b9c2575bbd7c68fad8108544) - Add logic to handle non-interactive or hidden tabs. Thanks [@hannahblair](https://github.com/hannahblair)! +- [#7142](https://github.com/gradio-app/gradio/pull/7142) [`b961652`](https://github.com/gradio-app/gradio/commit/b9616528ab099aab0adc7027bce4655111f7366c) - Remove kwargs from template components. Thanks [@freddyaboulton](https://github.com/freddyaboulton)! +- [#7125](https://github.com/gradio-app/gradio/pull/7125) [`45f725f`](https://github.com/gradio-app/gradio/commit/45f725f8d0dc7813b3d2e768ca9582d6ad878d6f) - un-disable output components after exception is raised. Thanks [@abidlabs](https://github.com/abidlabs)! +- [#7081](https://github.com/gradio-app/gradio/pull/7081) [`44c53d9`](https://github.com/gradio-app/gradio/commit/44c53d9bde7cab605b7dbd16331683d13cae029e) - Fix dropdown refocusing due to `