diff --git a/.changeset/afraid-cougars-rescue.md b/.changeset/afraid-cougars-rescue.md new file mode 100644 index 000000000000..69ee770c78be --- /dev/null +++ b/.changeset/afraid-cougars-rescue.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Better test dir check diff --git a/.changeset/bitter-mirrors-dance.md b/.changeset/bitter-mirrors-dance.md new file mode 100644 index 000000000000..99433f61687d --- /dev/null +++ b/.changeset/bitter-mirrors-dance.md @@ -0,0 +1,5 @@ +--- +"@gradio/wasm": minor +--- + +feat:lite: install typing-extensions to avoid capping fastapi versions diff --git a/.changeset/bright-planes-divide.md b/.changeset/bright-planes-divide.md new file mode 100644 index 000000000000..1751b8cce923 --- /dev/null +++ b/.changeset/bright-planes-divide.md @@ -0,0 +1,5 @@ +--- +"gradio": patch +--- + +fix: ensure all relevant packages are available to the custom component CLI diff --git a/.changeset/bright-yaks-wink.md b/.changeset/bright-yaks-wink.md new file mode 100644 index 000000000000..73eecc59132e --- /dev/null +++ b/.changeset/bright-yaks-wink.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:V4: Some misc fixes diff --git a/.changeset/changeset.cjs b/.changeset/changeset.cjs index a94b61d6342d..b2c50fa96c13 100644 --- a/.changeset/changeset.cjs +++ b/.changeset/changeset.cjs @@ -223,7 +223,7 @@ const changelogFunctions = { .trim() .match(/^(feat|fix|highlight)\s*:\s*([^]*)/im) || [ , - false, + "feat", changeset.summary ]; diff --git a/.changeset/chilly-dodos-serve.md b/.changeset/chilly-dodos-serve.md new file mode 100644 index 000000000000..f8ef2214cd15 --- /dev/null +++ b/.changeset/chilly-dodos-serve.md @@ -0,0 +1,6 @@ +--- +"@gradio/video": patch +"gradio": patch +--- + +fix:remove dupe component diff --git a/.changeset/cold-hoops-heal.md b/.changeset/cold-hoops-heal.md new file mode 100644 index 000000000000..2658d22210c6 --- /dev/null +++ b/.changeset/cold-hoops-heal.md @@ -0,0 +1,12 @@ +--- +"@gradio/atoms": minor +"@gradio/column": minor +"@gradio/icons": minor +"@gradio/statustracker": minor +"@gradio/tooltip": minor +"@gradio/upload": minor +"@gradio/utils": minor +"gradio": minor +--- + +feat:release first version \ No newline at end of file diff --git a/.changeset/cold-lemons-roll.md b/.changeset/cold-lemons-roll.md new file mode 100644 index 000000000000..828bc1ddb3c0 --- /dev/null +++ b/.changeset/cold-lemons-roll.md @@ -0,0 +1,33 @@ +--- +"@gradio/accordion": minor +"@gradio/annotatedimage": minor +"@gradio/app": minor +"@gradio/audio": minor +"@gradio/chatbot": minor +"@gradio/checkbox": minor +"@gradio/checkboxgroup": minor +"@gradio/code": minor +"@gradio/colorpicker": minor +"@gradio/dataframe": minor +"@gradio/dropdown": minor +"@gradio/fallback": minor +"@gradio/file": minor +"@gradio/gallery": minor +"@gradio/highlightedtext": minor +"@gradio/html": minor +"@gradio/image": minor +"@gradio/json": minor +"@gradio/label": minor +"@gradio/markdown": minor +"@gradio/model3d": minor +"@gradio/number": minor +"@gradio/plot": minor +"@gradio/radio": minor +"@gradio/slider": minor +"@gradio/statustracker": minor +"@gradio/textbox": minor +"@gradio/video": minor +"gradio": minor +--- + +feat:fix build and broken imports diff --git a/.changeset/cute-crabs-know.md b/.changeset/cute-crabs-know.md new file mode 100644 index 000000000000..ac5fb6e1d147 --- /dev/null +++ b/.changeset/cute-crabs-know.md @@ -0,0 +1,46 @@ +--- +"@gradio/accordion": minor +"@gradio/annotatedimage": minor +"@gradio/app": minor +"@gradio/audio": minor +"@gradio/box": minor +"@gradio/button": minor +"@gradio/chatbot": minor +"@gradio/checkbox": minor +"@gradio/checkboxgroup": minor +"@gradio/code": minor +"@gradio/colorpicker": minor +"@gradio/column": minor +"@gradio/dataframe": minor +"@gradio/dataset": minor +"@gradio/dropdown": minor +"@gradio/fallback": minor +"@gradio/file": minor +"@gradio/fileexplorer": minor +"@gradio/form": minor +"@gradio/gallery": minor +"@gradio/group": minor +"@gradio/highlightedtext": minor +"@gradio/html": minor +"@gradio/image": minor +"@gradio/json": minor +"@gradio/label": minor +"@gradio/markdown": minor +"@gradio/model3d": minor +"@gradio/number": minor +"@gradio/plot": minor +"@gradio/preview": minor +"@gradio/radio": minor +"@gradio/row": minor +"@gradio/slider": minor +"@gradio/state": minor +"@gradio/tabitem": minor +"@gradio/tabs": minor +"@gradio/textbox": minor +"@gradio/uploadbutton": minor +"@gradio/video": minor +"gradio": minor +"newtext": minor +--- + +feat:rererefactor frontend files diff --git a/.changeset/dark-cups-see.md b/.changeset/dark-cups-see.md new file mode 100644 index 000000000000..50f8ea879105 --- /dev/null +++ b/.changeset/dark-cups-see.md @@ -0,0 +1,5 @@ +--- +"@gradio/preview": minor +--- + +feat:Fix windows paths diff --git a/.changeset/dirty-ghosts-tickle.md b/.changeset/dirty-ghosts-tickle.md new file mode 100644 index 000000000000..84898c9d8508 --- /dev/null +++ b/.changeset/dirty-ghosts-tickle.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Patch fixes diff --git a/.changeset/dry-points-join.md b/.changeset/dry-points-join.md new file mode 100644 index 000000000000..2b84d8430c35 --- /dev/null +++ b/.changeset/dry-points-join.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Fix js deps in cli and add gradio-preview artifacts to build diff --git a/.changeset/dull-adults-study.md b/.changeset/dull-adults-study.md new file mode 100644 index 000000000000..82ee8b0e4948 --- /dev/null +++ b/.changeset/dull-adults-study.md @@ -0,0 +1,6 @@ +--- +"gradio": minor +"gradio_client": minor +--- + +feat:Add json schema unit tests diff --git a/.changeset/easy-mirrors-retire.md b/.changeset/easy-mirrors-retire.md new file mode 100644 index 000000000000..a0db5ba43f6e --- /dev/null +++ b/.changeset/easy-mirrors-retire.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Add docstring to trigger release diff --git a/.changeset/eleven-steaks-tan.md b/.changeset/eleven-steaks-tan.md new file mode 100644 index 000000000000..11956f6d6e90 --- /dev/null +++ b/.changeset/eleven-steaks-tan.md @@ -0,0 +1,6 @@ +--- +"@gradio/preview": minor +"gradio": minor +--- + +feat:Add host to dev mode for vite diff --git a/.changeset/empty-bobcats-judge.md b/.changeset/empty-bobcats-judge.md new file mode 100644 index 000000000000..96175a056119 --- /dev/null +++ b/.changeset/empty-bobcats-judge.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Make layout components templateable diff --git a/.changeset/every-cities-invite.md b/.changeset/every-cities-invite.md new file mode 100644 index 000000000000..16b59398bb28 --- /dev/null +++ b/.changeset/every-cities-invite.md @@ -0,0 +1,8 @@ +--- +"@gradio/app": patch +"@gradio/dropdown": patch +"@gradio/storybook": patch +"gradio": patch +--- + +fix:fix storybook diff --git a/.changeset/five-islands-joke.md b/.changeset/five-islands-joke.md new file mode 100644 index 000000000000..d5a990ab289a --- /dev/null +++ b/.changeset/five-islands-joke.md @@ -0,0 +1,48 @@ +--- +"@gradio/accordion": minor +"@gradio/annotatedimage": minor +"@gradio/app": minor +"@gradio/audio": minor +"@gradio/box": minor +"@gradio/chatbot": minor +"@gradio/checkbox": minor +"@gradio/checkboxgroup": minor +"@gradio/client": minor +"@gradio/code": minor +"@gradio/colorpicker": minor +"@gradio/column": minor +"@gradio/dataframe": minor +"@gradio/dataset": minor +"@gradio/dropdown": minor +"@gradio/fallback": minor +"@gradio/file": minor +"@gradio/fileexplorer": minor +"@gradio/form": minor +"@gradio/gallery": minor +"@gradio/group": minor +"@gradio/highlightedtext": minor +"@gradio/html": minor +"@gradio/image": minor +"@gradio/json": minor +"@gradio/label": minor +"@gradio/markdown": minor +"@gradio/model3d": minor +"@gradio/number": minor +"@gradio/plot": minor +"@gradio/preview": minor +"@gradio/radio": minor +"@gradio/row": minor +"@gradio/slider": minor +"@gradio/state": minor +"@gradio/tabitem": minor +"@gradio/tabs": minor +"@gradio/textbox": minor +"@gradio/uploadbutton": minor +"@gradio/video": minor +"gradio": minor +"imageslider": minor +"newtext": minor +"website": minor +--- + +feat:Format js in v4 branch diff --git a/.changeset/flat-experts-wink.md b/.changeset/flat-experts-wink.md new file mode 100644 index 000000000000..75349146348a --- /dev/null +++ b/.changeset/flat-experts-wink.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:BugFix: Make FileExplorer Component Templateable diff --git a/.changeset/fresh-ears-pump.md b/.changeset/fresh-ears-pump.md new file mode 100644 index 000000000000..b7b7f8bd4270 --- /dev/null +++ b/.changeset/fresh-ears-pump.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Use overrides diff --git a/.changeset/gentle-parks-fry.md b/.changeset/gentle-parks-fry.md new file mode 100644 index 000000000000..5ef42945a1f8 --- /dev/null +++ b/.changeset/gentle-parks-fry.md @@ -0,0 +1,5 @@ +--- +"@gradio/wasm": patch +--- + +feat:release wasm diff --git a/.changeset/great-rice-grab.md b/.changeset/great-rice-grab.md new file mode 100644 index 000000000000..057c68e7d98b --- /dev/null +++ b/.changeset/great-rice-grab.md @@ -0,0 +1,6 @@ +--- +"@gradio/preview": minor +"gradio": minor +--- + +feat:Use tags to identify custom component dirs and ignore uninstalled components diff --git a/.changeset/green-olives-shake.md b/.changeset/green-olives-shake.md new file mode 100644 index 000000000000..4ca71c8a2652 --- /dev/null +++ b/.changeset/green-olives-shake.md @@ -0,0 +1,6 @@ +--- +"@gradio/dataset": minor +"gradio": minor +--- + +feat:pass props to example components and to example outputs diff --git a/.changeset/heavy-animals-think.md b/.changeset/heavy-animals-think.md new file mode 100644 index 000000000000..7e2e4b1952a1 --- /dev/null +++ b/.changeset/heavy-animals-think.md @@ -0,0 +1,6 @@ +--- +"@gradio/app": patch +"gradio": patch +--- + +feat:Fix windows ci build diff --git a/.changeset/hip-drinks-bow.md b/.changeset/hip-drinks-bow.md new file mode 100644 index 000000000000..2bf10b687857 --- /dev/null +++ b/.changeset/hip-drinks-bow.md @@ -0,0 +1,8 @@ +--- +"@gradio/app": minor +"@gradio/image": minor +"@gradio/theme": minor +"gradio": minor +--- + +feat:image fixes diff --git a/.changeset/hot-icons-film.md b/.changeset/hot-icons-film.md new file mode 100644 index 000000000000..b5f83ad882b8 --- /dev/null +++ b/.changeset/hot-icons-film.md @@ -0,0 +1,41 @@ +--- +"@gradio/atoms": patch +"@gradio/audio": patch +"@gradio/box": patch +"@gradio/button": patch +"@gradio/chatbot": patch +"@gradio/checkbox": patch +"@gradio/checkboxgroup": patch +"@gradio/code": patch +"@gradio/colorpicker": patch +"@gradio/dataframe": patch +"@gradio/dropdown": patch +"@gradio/file": patch +"@gradio/form": patch +"@gradio/gallery": patch +"@gradio/highlightedtext": patch +"@gradio/html": patch +"@gradio/icons": patch +"@gradio/image": patch +"@gradio/json": patch +"@gradio/label": patch +"@gradio/markdown": patch +"@gradio/model3d": patch +"@gradio/number": patch +"@gradio/plot": patch +"@gradio/preview": patch +"@gradio/radio": patch +"@gradio/slider": patch +"@gradio/statustracker": patch +"@gradio/tabitem": patch +"@gradio/tabs": patch +"@gradio/textbox": patch +"@gradio/theme": patch +"@gradio/upload": patch +"@gradio/uploadbutton": patch +"@gradio/utils": patch +"@gradio/video": patch +"gradio": patch +--- + +feat:V4: Use beta release versions for '@gradio' packages diff --git a/.changeset/hungry-melons-pump.md b/.changeset/hungry-melons-pump.md new file mode 100644 index 000000000000..94f8be5f73fd --- /dev/null +++ b/.changeset/hungry-melons-pump.md @@ -0,0 +1,10 @@ +--- +"@gradio/app": minor +"@gradio/dataset": minor +"@gradio/preview": minor +"@gradio/state": minor +"gradio": minor +"newnewtext": minor +--- + +feat: Adds the ability to build the frontend and backend of custom components in preparation for publishing to pypi using `gradio_component build`. diff --git a/.changeset/icy-cars-boil.md b/.changeset/icy-cars-boil.md new file mode 100644 index 000000000000..5c263477f7b2 --- /dev/null +++ b/.changeset/icy-cars-boil.md @@ -0,0 +1,6 @@ +--- +"@gradio/audio": minor +"gradio": minor +--- + +feat:Fix deployed demos on v4 branch diff --git a/.changeset/large-banks-push.md b/.changeset/large-banks-push.md new file mode 100644 index 000000000000..91840ae7e2db --- /dev/null +++ b/.changeset/large-banks-push.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Use path to npm executable in subprocess diff --git a/.changeset/lazy-aliens-drive.md b/.changeset/lazy-aliens-drive.md new file mode 100644 index 000000000000..88cf59e83001 --- /dev/null +++ b/.changeset/lazy-aliens-drive.md @@ -0,0 +1,6 @@ +--- +"@gradio/theme": patch +"gradio": patch +--- + +feat:Publish js theme diff --git a/.changeset/lovely-news-speak.md b/.changeset/lovely-news-speak.md new file mode 100644 index 000000000000..9ab44daaf6a3 --- /dev/null +++ b/.changeset/lovely-news-speak.md @@ -0,0 +1,6 @@ +--- +"gradio": minor +"gradio_client": minor +--- + +feat:V4 fix typing diff --git a/.changeset/many-hotels-flow.md b/.changeset/many-hotels-flow.md new file mode 100644 index 000000000000..8012bf7c71da --- /dev/null +++ b/.changeset/many-hotels-flow.md @@ -0,0 +1,5 @@ +--- +"gradio": unknown +--- + +feat:Add a cli command to list available templates diff --git a/.changeset/modern-forks-march.md b/.changeset/modern-forks-march.md new file mode 100644 index 000000000000..938d03bc3166 --- /dev/null +++ b/.changeset/modern-forks-march.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Set api=False for cancel events diff --git a/.changeset/nasty-dryers-show.md b/.changeset/nasty-dryers-show.md new file mode 100644 index 000000000000..6b7f852e16dd --- /dev/null +++ b/.changeset/nasty-dryers-show.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Use full path to executables in CLI diff --git a/.changeset/nice-actors-write.md b/.changeset/nice-actors-write.md new file mode 100644 index 000000000000..5e25404577f7 --- /dev/null +++ b/.changeset/nice-actors-write.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Fix component regex diff --git a/.changeset/old-heads-give.md b/.changeset/old-heads-give.md new file mode 100644 index 000000000000..7212fdff8c4a --- /dev/null +++ b/.changeset/old-heads-give.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Add Error + test diff --git a/.changeset/plain-groups-win.md b/.changeset/plain-groups-win.md new file mode 100644 index 000000000000..bb5d2f473f76 --- /dev/null +++ b/.changeset/plain-groups-win.md @@ -0,0 +1,6 @@ +--- +"gradio": minor +"gradio_client": minor +--- + +feat:Fix python unit tests for v4 diff --git a/.changeset/plenty-rocks-lay.md b/.changeset/plenty-rocks-lay.md new file mode 100644 index 000000000000..22e05cfb84e8 --- /dev/null +++ b/.changeset/plenty-rocks-lay.md @@ -0,0 +1,5 @@ +--- +"website": patch +--- + +fix:Updated the twitter logo to its latest logo (X) diff --git a/.changeset/plenty-teeth-clap.md b/.changeset/plenty-teeth-clap.md new file mode 100644 index 000000000000..595c4fda7d44 --- /dev/null +++ b/.changeset/plenty-teeth-clap.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Fix template remaining components diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 000000000000..0745f399daf9 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,127 @@ +{ + "mode": "pre", + "tag": "beta", + "initialVersions": { + "@gradio/client": "0.3.1", + "gradio_client": "0.5.0", + "gradio": "3.43.2", + "@gradio/cdn-test": "0.0.1", + "@gradio/spaces-test": "0.0.1", + "website": "0.4.0", + "@gradio/accordion": "0.0.4", + "@gradio/annotatedimage": "0.1.2", + "@gradio/app": "1.4.3", + "@gradio/atoms": "0.1.2", + "@gradio/audio": "0.3.2", + "@gradio/box": "0.0.4", + "@gradio/button": "0.1.3", + "@gradio/chatbot": "0.3.1", + "@gradio/checkbox": "0.1.3", + "@gradio/checkboxgroup": "0.1.2", + "@gradio/code": "0.1.2", + "@gradio/colorpicker": "0.1.2", + "@gradio/column": "0.0.1", + "@gradio/dataframe": "0.2.2", + "@gradio/dropdown": "0.1.3", + "@gradio/file": "0.1.2", + "@gradio/form": "0.0.5", + "@gradio/gallery": "0.3.2", + "@gradio/group": "0.0.1", + "@gradio/highlightedtext": "0.2.3", + "@gradio/html": "0.0.4", + "@gradio/icons": "0.1.0", + "@gradio/image": "0.2.2", + "@gradio/json": "0.0.5", + "@gradio/label": "0.1.2", + "@gradio/lite": "0.3.1", + "@gradio/markdown": "0.2.0", + "@gradio/model3d": "0.2.1", + "@gradio/number": "0.2.2", + "@gradio/plot": "0.1.2", + "@gradio/radio": "0.1.2", + "@gradio/row": "0.0.1", + "@gradio/slider": "0.1.2", + "@gradio/state": "0.0.1", + "@gradio/statustracker": "0.2.0", + "@gradio/tabitem": "0.0.4", + "@gradio/tabs": "0.0.5", + "@gradio/textbox": "0.2.0", + "@gradio/theme": "0.1.0", + "@gradio/tooltip": "0.0.1", + "@gradio/tootils": "0.0.2", + "@gradio/upload": "0.2.1", + "@gradio/uploadbutton": "0.0.5", + "@gradio/utils": "0.1.1", + "@gradio/video": "0.0.6", + "@gradio/wasm": "0.0.1", + "@gradio/fallback": "0.1.1", + "@gradio/preview": "0.0.2", + "@gradio/dataset": "0.0.1", + "@gradio/fileexplorer": "0.2.2", + "imageslider": "0.2.0", + "newnewtext": "0.1.2", + "newtext": "0.0.1", + "@gradio/storybook": "0.0.1" + }, + "changesets": [ + "afraid-cougars-rescue", + "bright-planes-divide", + "bright-yaks-wink", + "cold-hoops-heal", + "cold-lemons-roll", + "crazy-dancers-allow", + "cute-crabs-know", + "dark-cups-see", + "dirty-ghosts-tickle", + "dry-points-join", + "easy-mirrors-retire", + "eleven-steaks-tan", + "empty-bobcats-judge", + "flat-experts-wink", + "fresh-ears-pump", + "gentle-parks-fry", + "great-rice-grab", + "green-tips-read", + "heavy-animals-think", + "hip-drinks-bow", + "hot-icons-film", + "hungry-melons-pump", + "icy-cars-boil", + "large-banks-push", + "lazy-aliens-drive", + "lovely-news-speak", + "modern-forks-march", + "nasty-dryers-show", + "nice-actors-write", + "old-heads-give", + "plain-groups-win", + "plenty-teeth-clap", + "public-chairs-chew", + "purple-jokes-shake", + "real-spoons-pick", + "sad-ears-sink", + "sad-eels-sink", + "sad-icons-relax", + "short-clouds-see", + "silver-beers-refuse", + "slick-bats-study", + "slick-pants-stand", + "smart-groups-study", + "some-shoes-relate", + "strange-lizards-boil", + "strong-peas-tell", + "tame-chairs-tan", + "tasty-candies-type", + "three-trams-sniff", + "tough-parrots-relate", + "true-bugs-shine", + "twenty-dryers-wave", + "twenty-gifts-tickle", + "two-games-dress", + "two-mirrors-nail", + "upset-crews-learn", + "vast-terms-rhyme", + "wet-places-hunt", + "wise-beans-itch" + ] +} diff --git a/.changeset/public-chairs-chew.md b/.changeset/public-chairs-chew.md new file mode 100644 index 000000000000..c22b75e29b68 --- /dev/null +++ b/.changeset/public-chairs-chew.md @@ -0,0 +1,5 @@ +--- +"@gradio/preview": minor +--- + +feat:In dev/build use full path to python/gradio executables diff --git a/.changeset/public-worlds-hunt.md b/.changeset/public-worlds-hunt.md new file mode 100644 index 000000000000..ed21c2dd6983 --- /dev/null +++ b/.changeset/public-worlds-hunt.md @@ -0,0 +1,31 @@ +--- +"@gradio/app": patch +"@gradio/audio": patch +"@gradio/button": patch +"@gradio/cdn-test": patch +"@gradio/chatbot": patch +"@gradio/client": patch +"@gradio/column": patch +"@gradio/dataframe": patch +"@gradio/file": patch +"@gradio/form": patch +"@gradio/gallery": patch +"@gradio/image": patch +"@gradio/markdown": patch +"@gradio/preview": patch +"@gradio/spaces-test": patch +"@gradio/storybook": patch +"@gradio/tabs": patch +"@gradio/textbox": patch +"@gradio/tooltip": patch +"@gradio/tootils": patch +"@gradio/upload": patch +"@gradio/utils": patch +"@gradio/video": patch +"gradio": patch +"newnewtext": patch +"newtext": patch +"website": patch +--- + +fix:fix tests diff --git a/.changeset/puny-teeth-notice.md b/.changeset/puny-teeth-notice.md new file mode 100644 index 000000000000..643a22100bb6 --- /dev/null +++ b/.changeset/puny-teeth-notice.md @@ -0,0 +1,5 @@ +--- +"gradio": patch +--- + +fix:Fix `root` when user is unauthenticated so that login page appears correctly diff --git a/.changeset/purple-jokes-shake.md b/.changeset/purple-jokes-shake.md new file mode 100644 index 000000000000..86a7d6c99380 --- /dev/null +++ b/.changeset/purple-jokes-shake.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:backend linting diff --git a/.changeset/quick-shirts-turn.md b/.changeset/quick-shirts-turn.md new file mode 100644 index 000000000000..93c9203450dc --- /dev/null +++ b/.changeset/quick-shirts-turn.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Clean up backend of `File` and `UploadButton` and change the return type of `preprocess()` from TemporaryFIle to string filepath diff --git a/.changeset/real-spoons-pick.md b/.changeset/real-spoons-pick.md new file mode 100644 index 000000000000..1c6b500e6dee --- /dev/null +++ b/.changeset/real-spoons-pick.md @@ -0,0 +1,6 @@ +--- +"@gradio/preview": patch +"gradio": patch +--- + +fix:Better logs in dev mode diff --git a/.changeset/sad-ears-sink.md b/.changeset/sad-ears-sink.md new file mode 100644 index 000000000000..7e205bbc7ec4 --- /dev/null +++ b/.changeset/sad-ears-sink.md @@ -0,0 +1,7 @@ +--- +"@gradio/preview": minor +"@gradio/utils": minor +"gradio": minor +--- + +feat:Fix esbuild diff --git a/.changeset/sad-eels-sink.md b/.changeset/sad-eels-sink.md new file mode 100644 index 000000000000..4d2fc8609738 --- /dev/null +++ b/.changeset/sad-eels-sink.md @@ -0,0 +1,7 @@ +--- +"@gradio/preview": minor +"@gradio/utils": minor +"gradio": minor +--- + +feat:Fix esbuild \ No newline at end of file diff --git a/.changeset/sad-icons-relax.md b/.changeset/sad-icons-relax.md new file mode 100644 index 000000000000..442c4d39db58 --- /dev/null +++ b/.changeset/sad-icons-relax.md @@ -0,0 +1,6 @@ +--- +"@gradio/box": patch +"gradio": patch +--- + +feat:Merge main again diff --git a/.changeset/short-clouds-see.md b/.changeset/short-clouds-see.md new file mode 100644 index 000000000000..b42755253b08 --- /dev/null +++ b/.changeset/short-clouds-see.md @@ -0,0 +1,6 @@ +--- +"@gradio/preview": minor +"gradio": minor +--- + +feat:Fix front-end imports + other misc fixes diff --git a/.changeset/shy-ghosts-turn.md b/.changeset/shy-ghosts-turn.md deleted file mode 100644 index 5b5420a9990a..000000000000 --- a/.changeset/shy-ghosts-turn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@gradio/wasm": patch ---- - -fix:Lite: Explicitly install a specific version of `anyio` to avoid version conflicts diff --git a/.changeset/silver-beers-refuse.md b/.changeset/silver-beers-refuse.md new file mode 100644 index 000000000000..a111d4ce39e6 --- /dev/null +++ b/.changeset/silver-beers-refuse.md @@ -0,0 +1,41 @@ +--- +"@gradio/audio": patch +"@gradio/box": patch +"@gradio/button": patch +"@gradio/chatbot": patch +"@gradio/checkbox": patch +"@gradio/checkboxgroup": patch +"@gradio/code": patch +"@gradio/colorpicker": patch +"@gradio/column": patch +"@gradio/dataframe": patch +"@gradio/dropdown": patch +"@gradio/fallback": patch +"@gradio/file": patch +"@gradio/form": patch +"@gradio/gallery": patch +"@gradio/group": patch +"@gradio/highlightedtext": patch +"@gradio/html": patch +"@gradio/image": patch +"@gradio/json": patch +"@gradio/label": patch +"@gradio/markdown": patch +"@gradio/model3d": patch +"@gradio/number": patch +"@gradio/plot": patch +"@gradio/preview": patch +"@gradio/radio": patch +"@gradio/row": patch +"@gradio/slider": patch +"@gradio/state": patch +"@gradio/tabitem": patch +"@gradio/tabs": patch +"@gradio/textbox": patch +"@gradio/tootils": patch +"@gradio/uploadbutton": patch +"@gradio/video": patch +"gradio": patch +--- + +feat:Publish all components to npm diff --git a/.changeset/sixty-bags-mix.md b/.changeset/sixty-bags-mix.md new file mode 100644 index 000000000000..cd38287a9bcd --- /dev/null +++ b/.changeset/sixty-bags-mix.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:File upload optimization diff --git a/.changeset/slick-bats-study.md b/.changeset/slick-bats-study.md new file mode 100644 index 000000000000..f7096d564313 --- /dev/null +++ b/.changeset/slick-bats-study.md @@ -0,0 +1,6 @@ +--- +"gradio": minor +"gradio_client": minor +--- + +feat:Simplify how files are handled in components in 4.0 diff --git a/.changeset/slick-pants-stand.md b/.changeset/slick-pants-stand.md new file mode 100644 index 000000000000..b9b2298a8d7c --- /dev/null +++ b/.changeset/slick-pants-stand.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Add overwrite flag to create command diff --git a/.changeset/smart-groups-study.md b/.changeset/smart-groups-study.md new file mode 100644 index 000000000000..2e26def430ef --- /dev/null +++ b/.changeset/smart-groups-study.md @@ -0,0 +1,42 @@ +--- +"@gradio/accordion": minor +"@gradio/annotatedimage": minor +"@gradio/app": minor +"@gradio/atoms": minor +"@gradio/audio": minor +"@gradio/button": minor +"@gradio/chatbot": minor +"@gradio/checkbox": minor +"@gradio/checkboxgroup": minor +"@gradio/code": minor +"@gradio/colorpicker": minor +"@gradio/dataframe": minor +"@gradio/dropdown": minor +"@gradio/fallback": minor +"@gradio/file": minor +"@gradio/gallery": minor +"@gradio/highlightedtext": minor +"@gradio/html": minor +"@gradio/image": minor +"@gradio/json": minor +"@gradio/label": minor +"@gradio/markdown": minor +"@gradio/model3d": minor +"@gradio/number": minor +"@gradio/plot": minor +"@gradio/preview": minor +"@gradio/radio": minor +"@gradio/slider": minor +"@gradio/statustracker": minor +"@gradio/textbox": minor +"@gradio/theme": minor +"@gradio/tootils": minor +"@gradio/upload": minor +"@gradio/uploadbutton": minor +"@gradio/utils": minor +"@gradio/video": minor +"gradio": minor +"gradio_client": minor +--- + +feat:Custom components diff --git a/.changeset/some-shoes-relate.md b/.changeset/some-shoes-relate.md new file mode 100644 index 000000000000..490c8e6db083 --- /dev/null +++ b/.changeset/some-shoes-relate.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Fix layout templates diff --git a/.changeset/spicy-streets-stop.md b/.changeset/spicy-streets-stop.md new file mode 100644 index 000000000000..ee29abab1fc4 --- /dev/null +++ b/.changeset/spicy-streets-stop.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Removes deprecated arguments and parameters from v4 diff --git a/.changeset/spotty-papers-ask.md b/.changeset/spotty-papers-ask.md new file mode 100644 index 000000000000..67616c3830d3 --- /dev/null +++ b/.changeset/spotty-papers-ask.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:V4: Fix component update bug diff --git a/.changeset/strange-lizards-boil.md b/.changeset/strange-lizards-boil.md new file mode 100644 index 000000000000..54c62b26ef9c --- /dev/null +++ b/.changeset/strange-lizards-boil.md @@ -0,0 +1,5 @@ +--- +"gradio": patch +--- + +fix: fixup diff --git a/.changeset/strong-peas-tell.md b/.changeset/strong-peas-tell.md new file mode 100644 index 000000000000..5792439a778e --- /dev/null +++ b/.changeset/strong-peas-tell.md @@ -0,0 +1,6 @@ +--- +"@gradio/app": minor +"gradio": minor +--- + +feat:Fix build and file route diff --git a/.changeset/tame-chairs-tan.md b/.changeset/tame-chairs-tan.md new file mode 100644 index 000000000000..6714bd3b388e --- /dev/null +++ b/.changeset/tame-chairs-tan.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Name Endpoints if api_name is None diff --git a/.changeset/tasty-candies-type.md b/.changeset/tasty-candies-type.md new file mode 100644 index 000000000000..19a661967ea7 --- /dev/null +++ b/.changeset/tasty-candies-type.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:V4: Update Component pyi file diff --git a/.changeset/tender-bananas-nail.md b/.changeset/tender-bananas-nail.md new file mode 100644 index 000000000000..f008c7637ce9 --- /dev/null +++ b/.changeset/tender-bananas-nail.md @@ -0,0 +1,7 @@ +--- +"@gradio/app": minor +"@gradio/simpledropdown": minor +"gradio": minor +--- + +feat:V4: Simple dropdown diff --git a/.changeset/thick-rabbits-wonder.md b/.changeset/thick-rabbits-wonder.md new file mode 100644 index 000000000000..90c268d2722f --- /dev/null +++ b/.changeset/thick-rabbits-wonder.md @@ -0,0 +1,7 @@ +--- +"@gradio/app": minor +"@gradio/simpletextbox": minor +"gradio": minor +--- + +feat:V4: Simple textbox diff --git a/.changeset/three-trams-sniff.md b/.changeset/three-trams-sniff.md new file mode 100644 index 000000000000..074ff8fbace5 --- /dev/null +++ b/.changeset/three-trams-sniff.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Some minor v4 fixes diff --git a/.changeset/tough-parrots-relate.md b/.changeset/tough-parrots-relate.md new file mode 100644 index 000000000000..b062fb8ac716 --- /dev/null +++ b/.changeset/tough-parrots-relate.md @@ -0,0 +1,6 @@ +--- +"@gradio/wasm": patch +"gradio": patch +--- + +feat:Wasm release diff --git a/.changeset/true-bugs-shine.md b/.changeset/true-bugs-shine.md new file mode 100644 index 000000000000..efd4620c8234 --- /dev/null +++ b/.changeset/true-bugs-shine.md @@ -0,0 +1,6 @@ +--- +"gradio": minor +"gradio_client": minor +--- + +feat:Support call method diff --git a/.changeset/true-flowers-hug.md b/.changeset/true-flowers-hug.md new file mode 100644 index 000000000000..7f2f1dd0a851 --- /dev/null +++ b/.changeset/true-flowers-hug.md @@ -0,0 +1,10 @@ +--- +"@gradio/file": patch +"@gradio/image": patch +"@gradio/tootils": patch +"@gradio/upload": patch +"@gradio/uploadbutton": patch +"gradio": patch +--- + +feat:Simplify File Component diff --git a/.changeset/twenty-gifts-tickle.md b/.changeset/twenty-gifts-tickle.md new file mode 100644 index 000000000000..b3ba02e3dd43 --- /dev/null +++ b/.changeset/twenty-gifts-tickle.md @@ -0,0 +1,6 @@ +--- +"gradio": minor +"gradio_client": minor +--- + +feat:Rename gradio_component to gradio component diff --git a/.changeset/two-games-dress.md b/.changeset/two-games-dress.md new file mode 100644 index 000000000000..c4ab877006dd --- /dev/null +++ b/.changeset/two-games-dress.md @@ -0,0 +1,6 @@ +--- +"@gradio/app": minor +"gradio": minor +--- + +fix:Reinstate types that were removed in error in #5832. diff --git a/.changeset/two-mirrors-nail.md b/.changeset/two-mirrors-nail.md new file mode 100644 index 000000000000..c70eed94d044 --- /dev/null +++ b/.changeset/two-mirrors-nail.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:V4: Use async version of shutil in upload route diff --git a/.changeset/upset-crews-learn.md b/.changeset/upset-crews-learn.md new file mode 100644 index 000000000000..cf97d54c2090 --- /dev/null +++ b/.changeset/upset-crews-learn.md @@ -0,0 +1,5 @@ +--- +"@gradio/preview": minor +--- + +feat:Strip vite import warning diff --git a/.changeset/vast-terms-rhyme.md b/.changeset/vast-terms-rhyme.md new file mode 100644 index 000000000000..b203aae5cc70 --- /dev/null +++ b/.changeset/vast-terms-rhyme.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:V4: Set cache dir for some component tests diff --git a/.changeset/wet-places-hunt.md b/.changeset/wet-places-hunt.md new file mode 100644 index 000000000000..9ce5d8730313 --- /dev/null +++ b/.changeset/wet-places-hunt.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:--overwrite deletes previous content diff --git a/.config/basevite.config.ts b/.config/basevite.config.ts index 5e12d3264312..378aa1bf963b 100644 --- a/.config/basevite.config.ts +++ b/.config/basevite.config.ts @@ -21,7 +21,9 @@ const theme_token_path = join( "tokens.css" ); -const version = JSON.parse(readFileSync(version_path, { encoding: 'utf-8' })).version.trim().replace(/\./g, '-'); +const version = JSON.parse(readFileSync(version_path, { encoding: "utf-8" })) + .version.trim() + .replace(/\./g, "-"); //@ts-ignore export default defineConfig(({ mode }) => { diff --git a/.config/copy_frontend.py b/.config/copy_frontend.py new file mode 100644 index 000000000000..23f830c0308a --- /dev/null +++ b/.config/copy_frontend.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import shutil +import pathlib +from typing import Any + +from hatchling.builders.hooks.plugin.interface import BuildHookInterface + + +class BuildHook(BuildHookInterface): + def initialize(self, version: str, build_data: dict[str, Any]) -> None: + NOT_COMPONENT = [ + "app", + "node_modules", + "storybook", + "playwright-report", + "workbench", + "tooltils", + ] + for entry in (pathlib.Path(self.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(self.root) / "client" / "js"), + str(pathlib.Path("gradio") / "_frontend_code" / "client"), + ignore=lambda d, names: ["node_modules"], + dirs_exist_ok=True, + ) diff --git a/.config/eslint.config.js b/.config/eslint.config.js index 41f34981991b..8f4c57e68674 100644 --- a/.config/eslint.config.js +++ b/.config/eslint.config.js @@ -18,7 +18,7 @@ const js_rules_disabled = Object.fromEntries( const js_rules = { ...js_rules_disabled, - "no-console": ["error", { allow: ["warn", "error", "debug"] }], + "no-console": ["error", { allow: ["warn", "error", "debug", "info"] }], "no-constant-condition": "error", "no-dupe-args": "error", "no-extra-boolean-cast": "error", @@ -60,7 +60,8 @@ export default [ "js/app/test/**/*", "**/*vite.config.ts", "**/_website/**/*", - "**/_spaces-test/**/*" + "**/_spaces-test/**/*", + "**/preview/test/**/*" ] }, { diff --git a/.github/actions/install-all-deps/action.yml b/.github/actions/install-all-deps/action.yml index 87755a5066d5..450c34aa88b6 100644 --- a/.github/actions/install-all-deps/action.yml +++ b/.github/actions/install-all-deps/action.yml @@ -53,8 +53,8 @@ runs: 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 + # - name: generate json + # shell: bash + # run: | + # . venv/bin/activate + # python js/_website/generate_jsons/generate.py diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 90b0f796e2ce..0e44af19801b 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -49,7 +49,7 @@ jobs: test-type: ["not flaky", "flaky"] python-version: ["3.8"] runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.test-type == 'flaky' }} + continue-on-error: true steps: - uses: actions/checkout@v3 - name: Install Python @@ -88,7 +88,7 @@ jobs: node-version: 18 cache: pnpm cache-dependency-path: pnpm-lock.yaml - - name: Build frontend + - name: Build Frontend if: steps.frontend-cache.outputs.cache-hit != 'true' run: | pnpm i --frozen-lockfile --ignore-scripts @@ -141,7 +141,7 @@ jobs: test-type: ["not flaky", "flaky"] python-version: ["3.8"] runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.test-type == 'flaky' }} + continue-on-error: true steps: - uses: actions/checkout@v3 - name: Install Python diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index d048dd52989c..2c8e616ca962 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -5,6 +5,7 @@ on: pull_request: branches: - main + - v4 jobs: comment-spaces-start: diff --git a/.github/workflows/deploy-chromatic.yml b/.github/workflows/deploy-chromatic.yml index 53d9d61ca383..5d1ecadb6886 100644 --- a/.github/workflows/deploy-chromatic.yml +++ b/.github/workflows/deploy-chromatic.yml @@ -47,6 +47,8 @@ jobs: 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 diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 11875590c831..a60d0fbceb1a 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -26,11 +26,7 @@ jobs: npm_token: ${{ secrets.NPM_TOKEN }} skip_build: 'true' - name: Build packages - run: | - . venv/bin/activate - pip install build - pnpm css - pnpm --filter @gradio/client --filter @gradio/lite build + run: pnpm --filter @gradio/client build - name: create and publish versions id: changesets uses: changesets/action@v1 diff --git a/.gitignore b/.gitignore index 29f6cd4ee03b..887764a92046 100644 --- a/.gitignore +++ b/.gitignore @@ -8,10 +8,14 @@ __pycache__/ *$py.class build/ __tmp/* +*.pyi + # JS build -gradio/templates/cdn -gradio/templates/frontend +gradio/templates/* +gradio/node/* +gradio/_frontend_code/* +js/gradio-preview/test/* # Secrets .env @@ -73,6 +77,4 @@ build-storybook.log js/storybook/theme.css # playwright -.config/playwright -!.config/playwright/index.html -!.config/playwright/index.ts +.config/playwright/.cache \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 3ab10f2c5c24..6bf105f889dc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,7 @@ "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", diff --git a/CHANGELOG.md b/CHANGELOG.md index 837d12e6d8f5..d2c34e0a3065 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,41 @@ # gradio -## 3.49.0 +## 3.45.0-beta.13 ### Features -- [#5953](https://github.com/gradio-app/gradio/pull/5953) [`921334526`](https://github.com/gradio-app/gradio/commit/921334526ff1ed0fc75c20db5d43733004c7d6ae) - Lite: Support the custom HTML element syntax ``. Thanks [@whitphx](https://github.com/whitphx)! +- [#5964](https://github.com/gradio-app/gradio/pull/5964) [`5fbda0bd2`](https://github.com/gradio-app/gradio/commit/5fbda0bd2b2bbb2282249b8875d54acf87cd7e84) - Wasm release. Thanks [@pngwn](https://github.com/pngwn)! + +## 3.45.0-beta.12 + +### Features + +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - V4: Some misc fixes. Thanks [@pngwn](https://github.com/pngwn)! +- [#5960](https://github.com/gradio-app/gradio/pull/5960) [`319c30f3f`](https://github.com/gradio-app/gradio/commit/319c30f3fccf23bfe1da6c9b132a6a99d59652f7) - rererefactor frontend files. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Add host to dev mode for vite. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`d2314e53b`](https://github.com/gradio-app/gradio/commit/d2314e53bc088ff6f307a122a9a01bafcdcff5c2) - BugFix: Make FileExplorer Component Templateable. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Use tags to identify custom component dirs and ignore uninstalled components. Thanks [@pngwn](https://github.com/pngwn)! - [#5956](https://github.com/gradio-app/gradio/pull/5956) [`f769876e0`](https://github.com/gradio-app/gradio/commit/f769876e0fa62336425c4e8ada5e09f38353ff01) - Apply formatter (and small refactoring) to the Lite-related frontend code. Thanks [@whitphx](https://github.com/whitphx)! -- [#5972](https://github.com/gradio-app/gradio/pull/5972) [`11a300791`](https://github.com/gradio-app/gradio/commit/11a3007916071f0791844b0a37f0fb4cec69cea3) - Lite: Support opening the entrypoint HTML page directly in browser via the `file:` protocol. Thanks [@whitphx](https://github.com/whitphx)! +- [#5938](https://github.com/gradio-app/gradio/pull/5938) [`13ed8a485`](https://github.com/gradio-app/gradio/commit/13ed8a485d5e31d7d75af87fe8654b661edcca93) - V4: Use beta release versions for '@gradio' packages. Thanks [@freddyaboulton](https://github.com/freddyaboulton)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Adds the ability to build the frontend and backend of custom components in preparation for publishing to pypi using `gradio_component build`. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Fix deployed demos on v4 branch. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Set api=False for cancel events. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Use full path to executables in CLI. Thanks [@pngwn](https://github.com/pngwn)! +- [#5949](https://github.com/gradio-app/gradio/pull/5949) [`1c390f101`](https://github.com/gradio-app/gradio/commit/1c390f10199142a41722ba493a0c86b58245da15) - Merge main again. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Simplify how files are handled in components in 4.0. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Name Endpoints if api_name is None. Thanks [@pngwn](https://github.com/pngwn)! +- [#5937](https://github.com/gradio-app/gradio/pull/5937) [`dcf13d750`](https://github.com/gradio-app/gradio/commit/dcf13d750b1465f905e062a1368ba754446cc23f) - V4: Update Component pyi file. Thanks [@freddyaboulton](https://github.com/freddyaboulton)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Rename gradio_component to gradio component. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - V4: Use async version of shutil in upload route. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - V4: Set cache dir for some component tests. Thanks [@pngwn](https://github.com/pngwn)! - [#5894](https://github.com/gradio-app/gradio/pull/5894) [`fee3d527e`](https://github.com/gradio-app/gradio/commit/fee3d527e83a615109cf937f6ca0a37662af2bb6) - Adds `column_widths` to `gr.Dataframe` and hide overflowing text when `wrap=False`. Thanks [@abidlabs](https://github.com/abidlabs)! ### Fixes +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Better logs in dev mode. Thanks [@pngwn](https://github.com/pngwn)! +- [#5946](https://github.com/gradio-app/gradio/pull/5946) [`d0cc6b136`](https://github.com/gradio-app/gradio/commit/d0cc6b136fd59121f74d0c5a1a4b51740ffaa838) - fixup. Thanks [@pngwn](https://github.com/pngwn)! - [#5944](https://github.com/gradio-app/gradio/pull/5944) [`465f58957`](https://github.com/gradio-app/gradio/commit/465f58957f70c7cf3e894beef8a117b28339e3c1) - Show empty JSON icon when `value` is `null`. Thanks [@hannahblair](https://github.com/hannahblair)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Reinstate types that were removed in error in #5832. Thanks [@pngwn](https://github.com/pngwn)! ## 3.48.0 diff --git a/client/js/src/client.ts b/client/js/src/client.ts index f3a8b8ad4e4b..c7140909c55a 100644 --- a/client/js/src/client.ts +++ b/client/js/src/client.ts @@ -432,11 +432,9 @@ export function api_factory( let payload: Payload; let complete: false | Record = false; const listener_map: ListenerMap = {}; - let url_params = "" - if (typeof(window) !== "undefined") { - url_params = new URLSearchParams( - window.location.search - ).toString(); + let url_params = ""; + if (typeof window !== "undefined") { + url_params = new URLSearchParams(window.location.search).toString(); } handle_blob( diff --git a/client/js/tsconfig.json b/client/js/tsconfig.json index 226efa095213..132b1c6b4a22 100644 --- a/client/js/tsconfig.json +++ b/client/js/tsconfig.json @@ -8,7 +8,7 @@ "outDir": "dist", "declarationMap": true, "module": "es2020", - "moduleResolution": "node16", + "moduleResolution": "bundler", "skipDefaultLibCheck": true } } diff --git a/client/js/vite.config.js b/client/js/vite.config.js index 5edaed279e65..b3b202054a5f 100644 --- a/client/js/vite.config.js +++ b/client/js/vite.config.js @@ -2,7 +2,6 @@ import { defineConfig } from "vite"; export default defineConfig({ build: { - // minify: true, lib: { entry: "src/index.ts", formats: ["es"] diff --git a/client/python/CHANGELOG.md b/client/python/CHANGELOG.md index 589d8351a96b..dca7046f4aff 100644 --- a/client/python/CHANGELOG.md +++ b/client/python/CHANGELOG.md @@ -1,5 +1,12 @@ # gradio_client +## 0.7.0-beta.0 + +### Features + +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Simplify how files are handled in components in 4.0. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Rename gradio_component to gradio component. Thanks [@pngwn](https://github.com/pngwn)! + ## 0.6.1 ### Fixes diff --git a/client/python/gradio_client/CHANGELOG.md b/client/python/gradio_client/CHANGELOG.md index 589d8351a96b..dca7046f4aff 100644 --- a/client/python/gradio_client/CHANGELOG.md +++ b/client/python/gradio_client/CHANGELOG.md @@ -1,5 +1,12 @@ # gradio_client +## 0.7.0-beta.0 + +### Features + +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Simplify how files are handled in components in 4.0. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Rename gradio_component to gradio component. Thanks [@pngwn](https://github.com/pngwn)! + ## 0.6.1 ### Fixes diff --git a/client/python/gradio_client/cli/deploy_discord.py b/client/python/gradio_client/cli/deploy_discord.py index bb26c088abee..9a828a32a065 100644 --- a/client/python/gradio_client/cli/deploy_discord.py +++ b/client/python/gradio_client/cli/deploy_discord.py @@ -1,58 +1,48 @@ -import argparse +from typing import List, Optional + +from typer import Option +from typing_extensions import Annotated from gradio_client import Client -def main(): - parser = argparse.ArgumentParser(description="Deploy Space as Discord Bot.") - parser.add_argument("deploy-discord") - parser.add_argument( - "--src", - type=str, - help="The space id or url or gradio app you want to deploy as a gradio bot.", - ) - parser.add_argument( - "--discord-bot-token", - type=str, - help="Discord bot token. Get one on the discord website.", - ) - parser.add_argument( - "--api-names", - nargs="*", - help="Api names to turn into discord bots", - default=[], - ) - parser.add_argument( - "--to-id", - type=str, - help="Name of the space used to host the discord bot", - default=None, - ) - parser.add_argument( - "--hf-token", - type=str, - help=( - "Hugging Face token. Can be ommitted if you are logged in via huggingface_hub cli. " - "Must be provided if upstream space is private." +def main( + src: Annotated[ + Optional[str], + Option( + help="The space id or url or gradio app you want to deploy as a gradio bot." ), - default=None, - ) - parser.add_argument( - "--private", - type=bool, - nargs="?", - help="Whether the discord bot space is private.", - const=True, - default=False, - ) - args = parser.parse_args() - for i, name in enumerate(args.api_names): + ] = None, + discord_bot_token: Annotated[ + str, Option(help="Discord bot token. Get one on the discord website.") + ] = None, + api_names: Annotated[ + List[str], Option(help="Api names to turn into discord bots") + ] = None, + to_id: Annotated[ + Optional[str], Option(help="Name of the space used to host the discord bot") + ] = None, + hf_token: Annotated[ + Optional[str], + Option( + help=( + "Hugging Face token. Can be ommitted if you are logged in via huggingface_hub cli. " + "Must be provided if upstream space is private." + ) + ), + ] = None, + private: Annotated[ + bool, Option(help="Whether the discord bot space is private.") + ] = False, +): + for i, name in enumerate(api_names): if "," in name: - args.api_names[i] = tuple(name.split(",")) - Client(args.src).deploy_discord( - discord_bot_token=args.discord_bot_token, - api_names=args.api_names, - to_id=args.to_id, - hf_token=args.hf_token, - private=args.private, + api_names[i] = tuple(name.split(",")) + + Client(src).deploy_discord( + discord_bot_token=discord_bot_token, + api_names=api_names, + to_id=to_id, + hf_token=hf_token, + private=private, ) diff --git a/client/python/gradio_client/client.py b/client/python/gradio_client/client.py index f6a734512fc4..5b3c7a77cc54 100644 --- a/client/python/gradio_client/client.py +++ b/client/python/gradio_client/client.py @@ -13,6 +13,7 @@ import uuid import warnings from concurrent.futures import Future +from dataclasses import dataclass from datetime import datetime from pathlib import Path from threading import Lock @@ -29,10 +30,8 @@ ) from packaging import version -from gradio_client import serializing, utils +from gradio_client import utils from gradio_client.documentation import document, set_documentation_group -from gradio_client.exceptions import SerializationSetupError -from gradio_client.serializing import Serializable from gradio_client.utils import ( Communicator, JobStatus, @@ -72,7 +71,7 @@ def __init__( hf_token: str | None = None, max_workers: int = 40, serialize: bool = True, - output_dir: str | Path | None = DEFAULT_TEMP_DIR, + output_dir: str | Path = DEFAULT_TEMP_DIR, verbose: bool = True, ): """ @@ -93,7 +92,9 @@ def __init__( library_version=utils.__version__, ) self.space_id = None - self.output_dir = output_dir + self.output_dir = ( + str(output_dir) if isinstance(output_dir, Path) else output_dir + ) if src.startswith("http://") or src.startswith("https://"): _src = src if src.endswith("/") else src + "/" @@ -127,6 +128,7 @@ def __init__( self.upload_url = urllib.parse.urljoin(self.src, utils.UPLOAD_URL) self.reset_url = urllib.parse.urljoin(self.src, utils.RESET_URL) self.config = self._get_config() + self._info = self._get_api_info() self.session_hash = str(uuid.uuid4()) self.endpoints = [ @@ -352,6 +354,32 @@ def fn(future): return job + def _get_api_info(self): + if self.serialize: + api_info_url = urllib.parse.urljoin(self.src, utils.API_INFO_URL) + else: + api_info_url = urllib.parse.urljoin(self.src, utils.RAW_API_INFO_URL) + + # Versions of Gradio older than 3.29.0 returned format of the API info + # from the /info endpoint + if version.parse(self.config.get("version", "2.0")) > version.Version("3.36.1"): + r = requests.get(api_info_url, headers=self.headers) + if r.ok: + info = r.json() + else: + raise ValueError(f"Could not fetch api info for {self.src}") + else: + fetch = requests.post( + utils.SPACE_FETCHER_URL, + json={"config": json.dumps(self.config), "serialize": self.serialize}, + ) + if fetch.ok: + info = fetch.json()["api"] + else: + raise ValueError(f"Could not fetch api info for {self.src}") + + return info + def view_api( self, all_endpoints: bool | None = None, @@ -427,42 +455,20 @@ def view_api( } """ - if self.serialize: - api_info_url = urllib.parse.urljoin(self.src, utils.API_INFO_URL) - else: - api_info_url = urllib.parse.urljoin(self.src, utils.RAW_API_INFO_URL) - - # Versions of Gradio older than 3.29.0 returned format of the API info - # from the /info endpoint - if version.parse(self.config.get("version", "2.0")) > version.Version("3.36.1"): - r = requests.get(api_info_url, headers=self.headers) - if r.ok: - info = r.json() - else: - raise ValueError(f"Could not fetch api info for {self.src}") - else: - fetch = requests.post( - utils.SPACE_FETCHER_URL, - json={"config": json.dumps(self.config), "serialize": self.serialize}, - ) - if fetch.ok: - info = fetch.json()["api"] - else: - raise ValueError(f"Could not fetch api info for {self.src}") - num_named_endpoints = len(info["named_endpoints"]) - num_unnamed_endpoints = len(info["unnamed_endpoints"]) + num_named_endpoints = len(self._info["named_endpoints"]) + num_unnamed_endpoints = len(self._info["unnamed_endpoints"]) if num_named_endpoints == 0 and all_endpoints is None: all_endpoints = True human_info = "Client.predict() Usage Info\n---------------------------\n" human_info += f"Named API endpoints: {num_named_endpoints}\n" - for api_name, endpoint_info in info["named_endpoints"].items(): + for api_name, endpoint_info in self._info["named_endpoints"].items(): human_info += self._render_endpoints_info(api_name, endpoint_info) if all_endpoints: human_info += f"\nUnnamed API endpoints: {num_unnamed_endpoints}\n" - for fn_index, endpoint_info in info["unnamed_endpoints"].items(): + for fn_index, endpoint_info in self._info["unnamed_endpoints"].items(): # When loading from json, the fn_indices are read as strings # because json keys can only be strings human_info += self._render_endpoints_info(int(fn_index), endpoint_info) @@ -475,7 +481,7 @@ def view_api( if return_format == "str": return human_info elif return_format == "dict": - return info + return self._info def reset_session(self) -> None: self.session_hash = str(uuid.uuid4()) @@ -661,12 +667,8 @@ def deploy_discord( raise ValueError( f"api_name {api_names[0][0]} not present in {self.space_id or self.src}" ) - inputs = [ - inp for inp in fn.input_component_types if fn not in utils.SKIP_COMPONENTS - ] - outputs = [ - inp for inp in fn.input_component_types if fn not in utils.SKIP_COMPONENTS - ] + inputs = [inp for inp in fn.input_component_types if not inp.skip] + outputs = [inp for inp in fn.input_component_types if not inp.skip] if not inputs == ["textbox"] and outputs == ["textbox"]: raise ValueError( "Currently only api_names with a single textbox as input and output are supported. " @@ -748,7 +750,7 @@ def deploy_discord( ) if is_private: huggingface_hub.add_space_secret( - space_id, "HF_TOKEN", hf_token, token=hf_token + space_id, "HF_TOKEN", hf_token, token=hf_token # type: ignore ) url = f"https://huggingface.co/spaces/{space_id}" @@ -756,6 +758,18 @@ def deploy_discord( return url +@dataclass +class ComponentApiType: + skip: bool + value_is_file: bool + is_state: bool + + +@dataclass +class ReplaceMe: + index: int + + class Endpoint: """Helper class for storing all the information about a single API endpoint.""" @@ -768,17 +782,41 @@ def __init__(self, client: Client, fn_index: int, dependency: dict): "/" + api_name if isinstance(api_name, str) else api_name ) self.use_ws = self._use_websocket(self.dependency) - self.input_component_types = [] - self.output_component_types = [] + self.input_component_types = [ + self._get_component_type(id_) for id_ in dependency["inputs"] + ] + self.output_component_types = [ + self._get_component_type(id_) for id_ in dependency["outputs"] + ] self.root_url = client.src + "/" if not client.src.endswith("/") else client.src self.is_continuous = dependency.get("types", {}).get("continuous", False) - try: - # Only a real API endpoint if backend_fn is True (so not just a frontend function), serializers are valid, - # and api_name is not False (meaning that the developer has explicitly disabled the API endpoint) - self.serializers, self.deserializers = self._setup_serializers() - self.is_valid = self.dependency["backend_fn"] and self.api_name is not False - except SerializationSetupError: - self.is_valid = False + self.download_file = lambda d: self._download_file( + d, + save_dir=self.client.output_dir, + hf_token=self.client.hf_token, + root_url=self.root_url, + ) + # Only a real API endpoint if backend_fn is True (so not just a frontend function), serializers are valid, + # and api_name is not False (meaning that the developer has explicitly disabled the API endpoint) + self.is_valid = self.dependency["backend_fn"] and self.api_name is not False + + def _get_component_type(self, component_id: int): + component = next( + i for i in self.client.config["components"] if i["id"] == component_id + ) + skip_api = component.get("skip_api", component["type"] in utils.SKIP_COMPONENTS) + return ComponentApiType( + skip_api, + self.value_is_file(component), + component["type"] == "state", + ) + + @staticmethod + def value_is_file(component: dict) -> bool: + # Hacky for now + if "api_info" not in component: + return False + return utils.value_is_file(component["api_info"]) def __repr__(self): return f"Endpoint src: {self.client.src}, api_name: {self.api_name}, fn_index: {self.fn_index}" @@ -909,140 +947,104 @@ def _upload( uploaded.append(res) return uploaded - def _add_uploaded_files_to_data( - self, - files: list[str | list[str]] | list[dict[str, Any] | list[dict[str, Any]]], - data: list[Any], - ) -> None: - """Helper function to modify the input data with the uploaded files.""" - file_counter = 0 - for i, t in enumerate(self.input_component_types): - if t in ["file", "uploadbutton"]: - data[i] = files[file_counter] - file_counter += 1 - def insert_state(self, *data) -> tuple: data = list(data) for i, input_component_type in enumerate(self.input_component_types): - if input_component_type == utils.STATE_COMPONENT: + if input_component_type.is_state: data.insert(i, None) return tuple(data) def remove_skipped_components(self, *data) -> tuple: - data = [ - d - for d, oct in zip(data, self.output_component_types) - if oct not in utils.SKIP_COMPONENTS - ] + data = [d for d, oct in zip(data, self.output_component_types) if not oct.skip] return tuple(data) def reduce_singleton_output(self, *data) -> Any: - if ( - len( - [ - oct - for oct in self.output_component_types - if oct not in utils.SKIP_COMPONENTS - ] - ) - == 1 - ): + if len([oct for oct in self.output_component_types if not oct.skip]) == 1: return data[0] else: return data - def serialize(self, *data) -> tuple: - if len(data) != len(self.serializers): - raise ValueError( - f"Expected {len(self.serializers)} arguments, got {len(data)}" + def _gather_files(self, *data): + file_list = [] + + def get_file(d): + file_list.append(d) + return ReplaceMe(len(file_list) - 1) + + new_data = [] + for i, d in enumerate(data): + if self.input_component_types[i].value_is_file: + d = utils.traverse(d, get_file, utils.is_filepath) + new_data.append(d) + return file_list, new_data + + def _add_uploaded_files_to_data(self, data: list[Any], files: list[Any]): + def replace(d: ReplaceMe) -> dict: + return files[d.index] + + new_data = [] + for d in data: + d = utils.traverse( + d, replace, is_root=lambda node: isinstance(node, ReplaceMe) ) + new_data.append(d) + return new_data - files = [ - f - for f, t in zip(data, self.input_component_types) - if t in ["file", "uploadbutton"] - ] + def serialize(self, *data) -> tuple: + files, new_data = self._gather_files(*data) uploaded_files = self._upload(files) - data = list(data) - self._add_uploaded_files_to_data(uploaded_files, data) - o = tuple([s.serialize(d) for s, d in zip(self.serializers, data)]) + data = list(new_data) + data = self._add_uploaded_files_to_data(data, uploaded_files) + data = utils.traverse( + data, + lambda s: {"name": s, "is_file": True, "data": None}, + utils.is_url, + ) + o = tuple(data) return o - def deserialize(self, *data) -> tuple: - if len(data) != len(self.deserializers): + @staticmethod + def _download_file( + x: dict, + save_dir: str, + root_url: str, + hf_token: str | None = None, + ) -> str | None: + if x is None: + return None + if isinstance(x, str): + file_name = utils.decode_base64_to_file(x, dir=save_dir).name + elif isinstance(x, dict): + if x.get("is_file"): + filepath = x.get("name") + assert filepath is not None, f"The 'name' field is missing in {x}" + file_name = utils.download_file( + root_url + "file=" + filepath, + hf_token=hf_token, + dir=save_dir, + ) + else: + data = x.get("data") + assert data is not None, f"The 'data' field is missing in {x}" + file_name = utils.decode_base64_to_file(data, dir=save_dir).name + else: raise ValueError( - f"Expected {len(self.deserializers)} outputs, got {len(data)}" + f"A FileSerializable component can only deserialize a string or a dict, not a {type(x)}: {x}" ) - outputs = tuple( - [ - s.deserialize( - d, - save_dir=self.client.output_dir, - hf_token=self.client.hf_token, - root_url=self.root_url, - ) - for s, d in zip(self.deserializers, data) - ] - ) - return outputs + return file_name + + def deserialize(self, *data) -> tuple: + data_ = list(data) + + data_: list[Any] = utils.traverse(data_, self.download_file, utils.is_file_obj) + return tuple(data_) def process_predictions(self, *predictions): - if self.client.serialize: - predictions = self.deserialize(*predictions) + predictions = self.deserialize(*predictions) predictions = self.remove_skipped_components(*predictions) predictions = self.reduce_singleton_output(*predictions) return predictions - def _setup_serializers(self) -> tuple[list[Serializable], list[Serializable]]: - inputs = self.dependency["inputs"] - serializers = [] - - for i in inputs: - for component in self.client.config["components"]: - if component["id"] == i: - component_name = component["type"] - self.input_component_types.append(component_name) - if component.get("serializer"): - serializer_name = component["serializer"] - if serializer_name not in serializing.SERIALIZER_MAPPING: - raise SerializationSetupError( - f"Unknown serializer: {serializer_name}, you may need to update your gradio_client version." - ) - serializer = serializing.SERIALIZER_MAPPING[serializer_name] - elif component_name in serializing.COMPONENT_MAPPING: - serializer = serializing.COMPONENT_MAPPING[component_name] - else: - raise SerializationSetupError( - f"Unknown component: {component_name}, you may need to update your gradio_client version." - ) - serializers.append(serializer()) # type: ignore - - outputs = self.dependency["outputs"] - deserializers = [] - for i in outputs: - for component in self.client.config["components"]: - if component["id"] == i: - component_name = component["type"] - self.output_component_types.append(component_name) - if component.get("serializer"): - serializer_name = component["serializer"] - if serializer_name not in serializing.SERIALIZER_MAPPING: - raise SerializationSetupError( - f"Unknown serializer: {serializer_name}, you may need to update your gradio_client version." - ) - deserializer = serializing.SERIALIZER_MAPPING[serializer_name] - elif component_name in utils.SKIP_COMPONENTS: - deserializer = serializing.SimpleSerializable - elif component_name in serializing.COMPONENT_MAPPING: - deserializer = serializing.COMPONENT_MAPPING[component_name] - else: - raise SerializationSetupError( - f"Unknown component: {component_name}, you may need to update your gradio_client version." - ) - deserializers.append(deserializer()) # type: ignore - - return serializers, deserializers - def _use_websocket(self, dependency: dict) -> bool: queue_enabled = self.client.config.get("enable_queue", False) queue_uses_websocket = version.parse( diff --git a/client/python/gradio_client/package.json b/client/python/gradio_client/package.json index a73291047e3e..415259c9181b 100644 --- a/client/python/gradio_client/package.json +++ b/client/python/gradio_client/package.json @@ -1,6 +1,6 @@ { "name": "gradio_client", - "version": "0.6.1", + "version": "0.7.0-beta.0", "description": "", "python": "true", "main_changeset": true diff --git a/client/python/gradio_client/serializing.py b/client/python/gradio_client/serializing.py deleted file mode 100644 index ca0831de8d86..000000000000 --- a/client/python/gradio_client/serializing.py +++ /dev/null @@ -1,598 +0,0 @@ -from __future__ import annotations - -import json -import os -import secrets -import tempfile -import uuid -from pathlib import Path -from typing import Any - -from gradio_client import media_data, utils -from gradio_client.data_classes import FileData - -with open(Path(__file__).parent / "types.json") as f: - serializer_types = json.load(f) - - -class Serializable: - def serialized_info(self): - """ - The typing information for this component as a dictionary whose values are a list of 2 strings: [Python type, language-agnostic description]. - Keys of the dictionary are: raw_input, raw_output, serialized_input, serialized_output - """ - return self.api_info() - - def api_info(self) -> dict[str, list[str]]: - """ - The typing information for this component as a dictionary whose values are a list of 2 strings: [Python type, language-agnostic description]. - Keys of the dictionary are: raw_input, raw_output, serialized_input, serialized_output - """ - raise NotImplementedError() - - def example_inputs(self) -> dict[str, Any]: - """ - The example inputs for this component as a dictionary whose values are example inputs compatible with this component. - Keys of the dictionary are: raw, serialized - """ - raise NotImplementedError() - - # For backwards compatibility - def input_api_info(self) -> tuple[str, str]: - api_info = self.api_info() - types = api_info.get("serialized_input", [api_info["info"]["type"]] * 2) # type: ignore - return (types[0], types[1]) - - # For backwards compatibility - def output_api_info(self) -> tuple[str, str]: - api_info = self.api_info() - types = api_info.get("serialized_output", [api_info["info"]["type"]] * 2) # type: ignore - return (types[0], types[1]) - - def serialize(self, x: Any, load_dir: str | Path = "", allow_links: bool = False): - """ - Convert data from human-readable format to serialized format for a browser. - """ - return x - - def deserialize( - self, - x: Any, - save_dir: str | Path | None = None, - root_url: str | None = None, - hf_token: str | None = None, - ): - """ - Convert data from serialized format for a browser to human-readable format. - """ - return x - - -class SimpleSerializable(Serializable): - """General class that does not perform any serialization or deserialization.""" - - def api_info(self) -> dict[str, bool | dict]: - return { - "info": serializer_types["SimpleSerializable"], - "serialized_info": False, - } - - def example_inputs(self) -> dict[str, Any]: - return { - "raw": None, - "serialized": None, - } - - -class StringSerializable(Serializable): - """Expects a string as input/output but performs no serialization.""" - - def api_info(self) -> dict[str, bool | dict]: - return { - "info": serializer_types["StringSerializable"], - "serialized_info": False, - } - - def example_inputs(self) -> dict[str, Any]: - return { - "raw": "Howdy!", - "serialized": "Howdy!", - } - - -class ListStringSerializable(Serializable): - """Expects a list of strings as input/output but performs no serialization.""" - - def api_info(self) -> dict[str, bool | dict]: - return { - "info": serializer_types["ListStringSerializable"], - "serialized_info": False, - } - - def example_inputs(self) -> dict[str, Any]: - return { - "raw": ["Howdy!", "Merhaba"], - "serialized": ["Howdy!", "Merhaba"], - } - - -class BooleanSerializable(Serializable): - """Expects a boolean as input/output but performs no serialization.""" - - def api_info(self) -> dict[str, bool | dict]: - return { - "info": serializer_types["BooleanSerializable"], - "serialized_info": False, - } - - def example_inputs(self) -> dict[str, Any]: - return { - "raw": True, - "serialized": True, - } - - -class NumberSerializable(Serializable): - """Expects a number (int/float) as input/output but performs no serialization.""" - - def api_info(self) -> dict[str, bool | dict]: - return { - "info": serializer_types["NumberSerializable"], - "serialized_info": False, - } - - def example_inputs(self) -> dict[str, Any]: - return { - "raw": 5, - "serialized": 5, - } - - -class ImgSerializable(Serializable): - """Expects a base64 string as input/output which is serialized to a filepath.""" - - def serialized_info(self): - return { - "type": "string", - "description": "filepath on your computer (or URL) of image", - } - - def api_info(self) -> dict[str, bool | dict]: - return {"info": serializer_types["ImgSerializable"], "serialized_info": True} - - def example_inputs(self) -> dict[str, Any]: - return { - "raw": media_data.BASE64_IMAGE, - "serialized": "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png", - } - - def serialize( - self, - x: str | None, - load_dir: str | Path = "", - allow_links: bool = False, - ) -> str | None: - """ - Convert from human-friendly version of a file (string filepath) to a serialized - representation (base64). - Parameters: - x: String path to file to serialize - load_dir: Path to directory containing x - """ - if not x: - return None - if utils.is_http_url_like(x): - return utils.encode_url_to_base64(x) - return utils.encode_file_to_base64(Path(load_dir) / x) - - def deserialize( - self, - x: str | None, - save_dir: str | Path | None = None, - root_url: str | None = None, - hf_token: str | None = None, - ) -> str | None: - """ - Convert from serialized representation of a file (base64) to a human-friendly - version (string filepath). Optionally, save the file to the directory specified by save_dir - Parameters: - x: Base64 representation of image to deserialize into a string filepath - save_dir: Path to directory to save the deserialized image to - root_url: Ignored - hf_token: Ignored - """ - if x is None or x == "": - return None - file = utils.decode_base64_to_file(x, dir=save_dir) - return file.name - - -class FileSerializable(Serializable): - """Expects a dict with base64 representation of object as input/output which is serialized to a filepath.""" - - def __init__(self) -> None: - self.stream = None - self.stream_name = None - super().__init__() - - def serialized_info(self): - return self._single_file_serialized_info() - - def _single_file_api_info(self): - return { - "info": serializer_types["SingleFileSerializable"], - "serialized_info": True, - } - - def _single_file_serialized_info(self): - return { - "type": "string", - "description": "filepath on your computer (or URL) of file", - } - - def _multiple_file_serialized_info(self): - return { - "type": "array", - "description": "List of filepath(s) or URL(s) to files", - "items": { - "type": "string", - "description": "filepath on your computer (or URL) of file", - }, - } - - def _multiple_file_api_info(self): - return { - "info": serializer_types["MultipleFileSerializable"], - "serialized_info": True, - } - - def api_info(self) -> dict[str, dict | bool]: - return self._single_file_api_info() - - def example_inputs(self) -> dict[str, Any]: - return self._single_file_example_inputs() - - def _single_file_example_inputs(self) -> dict[str, Any]: - return { - "raw": {"is_file": False, "data": media_data.BASE64_FILE}, - "serialized": "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf", - } - - def _multiple_file_example_inputs(self) -> dict[str, Any]: - return { - "raw": [{"is_file": False, "data": media_data.BASE64_FILE}], - "serialized": [ - "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf" - ], - } - - def _serialize_single( - self, - x: str | FileData | None, - load_dir: str | Path = "", - allow_links: bool = False, - ) -> FileData | None: - if x is None or isinstance(x, dict): - return x - if utils.is_http_url_like(x): - filename = x - size = None - else: - filename = str(Path(load_dir) / x) - size = Path(filename).stat().st_size - return { - "name": filename, - "data": None - if allow_links - else utils.encode_url_or_file_to_base64(filename), - "orig_name": Path(filename).name, - "is_file": allow_links, - "size": size, - } - - def _setup_stream(self, url, hf_token): - return utils.download_byte_stream(url, hf_token) - - def _deserialize_single( - self, - x: str | FileData | None, - save_dir: str | None = None, - root_url: str | None = None, - hf_token: str | None = None, - ) -> str | None: - if x is None: - return None - if isinstance(x, str): - file_name = utils.decode_base64_to_file(x, dir=save_dir).name - elif isinstance(x, dict): - if x.get("is_file"): - filepath = x.get("name") - if filepath is None: - raise ValueError(f"The 'name' field is missing in {x}") - if root_url is not None: - file_name = utils.download_tmp_copy_of_file( - root_url + "file=" + filepath, - hf_token=hf_token, - dir=save_dir, - ) - else: - file_name = utils.create_tmp_copy_of_file(filepath, dir=save_dir) - elif x.get("is_stream"): - assert x["name"] and root_url and save_dir - if not self.stream or self.stream_name != x["name"]: - self.stream = self._setup_stream( - root_url + "stream/" + x["name"], hf_token=hf_token - ) - self.stream_name = x["name"] - chunk = next(self.stream) - path = Path(save_dir or tempfile.gettempdir()) / secrets.token_hex(20) - path.mkdir(parents=True, exist_ok=True) - path = path / x.get("orig_name", "output") - path.write_bytes(chunk) - file_name = str(path) - else: - data = x.get("data") - if data is None: - raise ValueError(f"The 'data' field is missing in {x}") - file_name = utils.decode_base64_to_file(data, dir=save_dir).name - else: - raise ValueError( - f"A FileSerializable component can only deserialize a string or a dict, not a {type(x)}: {x}" - ) - return file_name - - def serialize( - self, - x: str | FileData | None | list[str | FileData | None], - load_dir: str | Path = "", - allow_links: bool = False, - ) -> FileData | None | list[FileData | None]: - """ - Convert from human-friendly version of a file (string filepath) to a - serialized representation (base64) - Parameters: - x: String path to file to serialize - load_dir: Path to directory containing x - allow_links: Will allow path returns instead of raw file content - """ - if x is None or x == "": - return None - if isinstance(x, list): - return [self._serialize_single(f, load_dir, allow_links) for f in x] - else: - return self._serialize_single(x, load_dir, allow_links) - - def deserialize( - self, - x: str | FileData | None | list[str | FileData | None], - save_dir: Path | str | None = None, - root_url: str | None = None, - hf_token: str | None = None, - ) -> str | None | list[str | None]: - """ - Convert from serialized representation of a file (base64) to a human-friendly - version (string filepath). Optionally, save the file to the directory specified by `save_dir` - Parameters: - x: Base64 representation of file to deserialize into a string filepath - save_dir: Path to directory to save the deserialized file to - root_url: If this component is loaded from an external Space, this is the URL of the Space. - hf_token: If this component is loaded from an external private Space, this is the access token for the Space - """ - if x is None: - return None - if isinstance(save_dir, Path): - save_dir = str(save_dir) - if isinstance(x, list): - return [ - self._deserialize_single( - f, save_dir=save_dir, root_url=root_url, hf_token=hf_token - ) - for f in x - ] - else: - return self._deserialize_single( - x, save_dir=save_dir, root_url=root_url, hf_token=hf_token - ) - - -class VideoSerializable(FileSerializable): - def serialized_info(self): - return { - "type": "string", - "description": "filepath on your computer (or URL) of video file", - } - - def api_info(self) -> dict[str, dict | bool]: - return {"info": serializer_types["FileSerializable"], "serialized_info": True} - - def example_inputs(self) -> dict[str, Any]: - return { - "raw": {"is_file": False, "data": media_data.BASE64_VIDEO}, - "serialized": "https://github.com/gradio-app/gradio/raw/main/test/test_files/video_sample.mp4", - } - - def serialize( - self, x: str | None, load_dir: str | Path = "", allow_links: bool = False - ) -> tuple[FileData | None, None]: - return (super().serialize(x, load_dir, allow_links), None) # type: ignore - - def deserialize( - self, - x: tuple[FileData | None, FileData | None] | None, - save_dir: Path | str | None = None, - root_url: str | None = None, - hf_token: str | None = None, - ) -> str | tuple[str | None, str | None] | None: - """ - Convert from serialized representation of a file (base64) to a human-friendly - version (string filepath). Optionally, save the file to the directory specified by `save_dir` - """ - if isinstance(x, (tuple, list)): - if len(x) != 2: - raise ValueError(f"Expected tuple of length 2. Received: {x}") - x_as_list = [x[0], x[1]] - else: - raise ValueError(f"Expected tuple of length 2. Received: {x}") - deserialized_file = super().deserialize(x_as_list, save_dir, root_url, hf_token) # type: ignore - if isinstance(deserialized_file, list): - return deserialized_file[0] # ignore subtitles - - -class JSONSerializable(Serializable): - def serialized_info(self): - return {"type": "string", "description": "filepath to JSON file"} - - def api_info(self) -> dict[str, dict | bool]: - return {"info": serializer_types["JSONSerializable"], "serialized_info": True} - - def example_inputs(self) -> dict[str, Any]: - return { - "raw": {"a": 1, "b": 2}, - "serialized": None, - } - - def serialize( - self, - x: str | None, - load_dir: str | Path = "", - allow_links: bool = False, - ) -> dict | list | None: - """ - Convert from a a human-friendly version (string path to json file) to a - serialized representation (json string) - Parameters: - x: String path to json file to read to get json string - load_dir: Path to directory containing x - """ - if x is None or x == "": - return None - return utils.file_to_json(Path(load_dir) / x) - - def deserialize( - self, - x: str | dict | list, - save_dir: str | Path | None = None, - root_url: str | None = None, - hf_token: str | None = None, - ) -> str | None: - """ - Convert from serialized representation (json string) to a human-friendly - version (string path to json file). Optionally, save the file to the directory specified by `save_dir` - Parameters: - x: Json string - save_dir: Path to save the deserialized json file to - root_url: Ignored - hf_token: Ignored - """ - if x is None: - return None - return utils.dict_or_str_to_json_file(x, dir=save_dir).name - - -class GallerySerializable(Serializable): - def serialized_info(self): - return { - "type": "string", - "description": "path to directory with images and a file associating images with captions called captions.json", - } - - def api_info(self) -> dict[str, dict | bool]: - return { - "info": serializer_types["GallerySerializable"], - "serialized_info": True, - } - - def example_inputs(self) -> dict[str, Any]: - return { - "raw": [media_data.BASE64_IMAGE] * 2, - "serialized": [ - "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png", - ] - * 2, - } - - def serialize( - self, x: str | None, load_dir: str | Path = "", allow_links: bool = False - ) -> list[list[str | None]] | None: - if x is None or x == "": - return None - files = [] - captions_file = Path(x) / "captions.json" - with captions_file.open("r") as captions_json: - captions = json.load(captions_json) - for file_name, caption in captions.items(): - img = FileSerializable().serialize(file_name, allow_links=allow_links) - files.append([img, caption]) - return files - - def deserialize( - self, - x: list[list[str | None]] | None, - save_dir: str = "", - root_url: str | None = None, - hf_token: str | None = None, - ) -> None | str: - if x is None: - return None - gallery_path = Path(save_dir) / str(uuid.uuid4()) - gallery_path.mkdir(exist_ok=True, parents=True) - captions = {} - for img_data in x: - if isinstance(img_data, (list, tuple)): - img_data, caption = img_data - else: - caption = None - name = FileSerializable().deserialize( - img_data, gallery_path, root_url=root_url, hf_token=hf_token - ) - captions[name] = caption - captions_file = gallery_path / "captions.json" - with captions_file.open("w") as captions_json: - json.dump(captions, captions_json) - return os.path.abspath(gallery_path) - - -SERIALIZER_MAPPING = {} -for cls in Serializable.__subclasses__(): - SERIALIZER_MAPPING[cls.__name__] = cls - for subcls in cls.__subclasses__(): - SERIALIZER_MAPPING[subcls.__name__] = subcls - -SERIALIZER_MAPPING["Serializable"] = SimpleSerializable -SERIALIZER_MAPPING["File"] = FileSerializable -SERIALIZER_MAPPING["UploadButton"] = FileSerializable - -COMPONENT_MAPPING: dict[str, type] = { - "textbox": StringSerializable, - "number": NumberSerializable, - "slider": NumberSerializable, - "checkbox": BooleanSerializable, - "checkboxgroup": ListStringSerializable, - "radio": StringSerializable, - "dropdown": SimpleSerializable, - "image": ImgSerializable, - "video": FileSerializable, - "audio": FileSerializable, - "file": FileSerializable, - "dataframe": JSONSerializable, - "timeseries": JSONSerializable, - "fileexplorer": JSONSerializable, - "state": SimpleSerializable, - "button": StringSerializable, - "uploadbutton": FileSerializable, - "colorpicker": StringSerializable, - "label": JSONSerializable, - "highlightedtext": JSONSerializable, - "json": JSONSerializable, - "html": StringSerializable, - "gallery": GallerySerializable, - "chatbot": JSONSerializable, - "model3d": FileSerializable, - "plot": JSONSerializable, - "barplot": JSONSerializable, - "lineplot": JSONSerializable, - "scatterplot": JSONSerializable, - "markdown": StringSerializable, - "code": StringSerializable, - "annotatedimage": JSONSerializable, -} diff --git a/client/python/gradio_client/utils.py b/client/python/gradio_client/utils.py index 421ff11ac121..0e6677a317d7 100644 --- a/client/python/gradio_client/utils.py +++ b/client/python/gradio_client/utils.py @@ -2,6 +2,7 @@ import asyncio import base64 +import hashlib import json import mimetypes import os @@ -35,20 +36,6 @@ RESET_URL = "reset" SPACE_URL = "https://hf.space/{}" -SKIP_COMPONENTS = { - "state", - "row", - "column", - "tabs", - "tab", - "tabitem", - "box", - "form", - "accordion", - "group", - "interpretation", - "dataset", -} STATE_COMPONENT = "state" INVALID_RUNTIME = [ SpaceStage.NO_APP_FILE, @@ -322,21 +309,31 @@ async def get_pred_from_ws( ######################## -def download_tmp_copy_of_file( - url_path: str, hf_token: str | None = None, dir: str | None = None +def download_file( + url_path: str, + dir: str, + hf_token: str | None = None, ) -> str: if dir is not None: os.makedirs(dir, exist_ok=True) headers = {"Authorization": "Bearer " + hf_token} if hf_token else {} - directory = Path(dir or tempfile.gettempdir()) / secrets.token_hex(20) - directory.mkdir(exist_ok=True, parents=True) - file_path = directory / Path(url_path).name + + sha1 = hashlib.sha1() + temp_dir = Path(tempfile.gettempdir()) / secrets.token_hex(20) + temp_dir.mkdir(exist_ok=True, parents=True) with requests.get(url_path, headers=headers, stream=True) as r: r.raise_for_status() - with open(file_path, "wb") as f: - shutil.copyfileobj(r.raw, f) - return str(file_path.resolve()) + with open(temp_dir / Path(url_path).name, "wb") as f: + for chunk in r.iter_content(chunk_size=128 * sha1.block_size): + sha1.update(chunk) + f.write(chunk) + + directory = Path(dir) / sha1.hexdigest() + directory.mkdir(exist_ok=True, parents=True) + dest = directory / Path(url_path).name + shutil.move(temp_dir / Path(url_path).name, dest) + return str(dest.resolve()) def create_tmp_copy_of_file(file_path: str, dir: str | None = None) -> str: @@ -545,26 +542,52 @@ class APIInfoParseError(ValueError): def get_type(schema: dict): - if "type" in schema: + if "const" in schema: + return "const" + if "enum" in schema: + return "enum" + elif "type" in schema: return schema["type"] + elif schema.get("$ref"): + return "$ref" elif schema.get("oneOf"): return "oneOf" elif schema.get("anyOf"): return "anyOf" + elif schema.get("allOf"): + return "allOf" + elif "type" not in schema: + return {} else: raise APIInfoParseError(f"Cannot parse type for {schema}") +FILE_DATA = "Dict(name: str | None, data: str | None, size: int | None, is_file: bool | None, orig_name: str | None, mime_type: str | None)" + + def json_schema_to_python_type(schema: Any) -> str: + type_ = _json_schema_to_python_type(schema, schema.get("$defs")) + return type_.replace(FILE_DATA, "filepath") + + +def _json_schema_to_python_type(schema: Any, defs) -> str: """Convert the json schema into a python type hint""" + if schema == {}: + return "Any" type_ = get_type(schema) if type_ == {}: - if "json" in schema["description"]: + if "json" in schema.get("description", {}): return "Dict[Any, Any]" else: return "Any" + elif type_ == "$ref": + return _json_schema_to_python_type(defs[schema["$ref"].split("/")[-1]], defs) elif type_ == "null": return "None" + elif type_ == "const": + return f"Litetal[{schema['const']}]" + elif type_ == "enum": + return f"Literal[{', '.join([str(v) for v in schema['enum']])}]" elif type_ == "integer": return "int" elif type_ == "string": @@ -572,27 +595,97 @@ def json_schema_to_python_type(schema: Any) -> str: elif type_ == "boolean": return "bool" elif type_ == "number": - return "int | float" + return "float" elif type_ == "array": - items = schema.get("items") + items = schema.get("items", []) if "prefixItems" in items: elements = ", ".join( - [json_schema_to_python_type(i) for i in items["prefixItems"]] + [_json_schema_to_python_type(i, defs) for i in items["prefixItems"]] + ) + return f"Tuple[{elements}]" + elif "prefixItems" in schema: + elements = ", ".join( + [_json_schema_to_python_type(i, defs) for i in schema["prefixItems"]] ) return f"Tuple[{elements}]" else: - elements = json_schema_to_python_type(items) + elements = _json_schema_to_python_type(items, defs) return f"List[{elements}]" elif type_ == "object": - des = ", ".join( - [ - f"{n}: {json_schema_to_python_type(v)} ({v.get('description')})" - for n, v in schema["properties"].items() + + def get_desc(v): + return f" ({v.get('description')})" if v.get("description") else "" + + props = schema.get("properties", {}) + + des = [ + f"{n}: {_json_schema_to_python_type(v, defs)}{get_desc(v)}" + for n, v in props.items() + if n != "$defs" + ] + + if "additionalProperties" in schema: + des += [ + f"str, {_json_schema_to_python_type(schema['additionalProperties'], defs)}" ] - ) + des = ", ".join(des) return f"Dict({des})" elif type_ in ["oneOf", "anyOf"]: - desc = " | ".join([json_schema_to_python_type(i) for i in schema[type_]]) + desc = " | ".join([_json_schema_to_python_type(i, defs) for i in schema[type_]]) + return desc + elif type_ == "allOf": + data = ", ".join(_json_schema_to_python_type(i, defs) for i in schema[type_]) + desc = f"All[{data}]" return desc else: raise APIInfoParseError(f"Cannot parse schema {schema}") + + +def traverse(json_obj: Any, func: Callable, is_root: Callable) -> Any: + if is_root(json_obj): + return func(json_obj) + elif isinstance(json_obj, dict): + new_obj = {} + for key, value in json_obj.items(): + new_obj[key] = traverse(value, func, is_root) + return new_obj + elif isinstance(json_obj, (list, tuple)): + new_obj = [] + for item in json_obj: + new_obj.append(traverse(item, func, is_root)) + return new_obj + else: + return json_obj + + +def value_is_file(api_info: dict) -> bool: + info = _json_schema_to_python_type(api_info, api_info.get("$defs")) + return FILE_DATA in info + + +def is_filepath(s): + return isinstance(s, str) and Path(s).exists() + + +def is_url(s): + return isinstance(s, str) and is_http_url_like(s) + + +def is_file_obj(d): + return isinstance(d, dict) and "name" in d and "is_file" in d and "data" in d + + +SKIP_COMPONENTS = { + "state", + "row", + "column", + "tabs", + "tab", + "tabitem", + "box", + "form", + "accordion", + "group", + "interpretation", + "dataset", +} diff --git a/client/python/test/conftest.py b/client/python/test/conftest.py index 13a3d237501a..0ecb1f89c6ab 100644 --- a/client/python/test/conftest.py +++ b/client/python/test/conftest.py @@ -179,6 +179,31 @@ def show(n): return demo.queue() +@pytest.fixture +def count_generator_no_api(): + def count(n): + for i in range(int(n)): + time.sleep(0.5) + yield i + + def show(n): + return str(list(range(int(n)))) + + with gr.Blocks() as demo: + with gr.Column(): + num = gr.Number(value=10) + with gr.Row(): + count_btn = gr.Button("Count") + list_btn = gr.Button("List") + with gr.Column(): + out = gr.Textbox() + + count_btn.click(count, num, out, api_name=False) + list_btn.click(show, num, out, api_name=False) + + return demo.queue() + + @pytest.fixture def count_generator_demo_exception(): def count(n): @@ -247,13 +272,13 @@ def hello_world_with_group(): gr.Textbox("Hello!") def greeting(name): - return f"Hello {name}", gr.Group.update(visible=True) + return f"Hello {name}", gr.Group(visible=True) greet.click( greeting, inputs=[name], outputs=[output, group], api_name="greeting" ) show_group.click( - lambda: gr.Group.update(visible=False), None, group, api_name="show_group" + lambda: gr.Group(visible=False), None, group, api_name="show_group" ) return demo @@ -275,7 +300,7 @@ def hello_world_with_state_and_accordion(): def greeting(name, state): state += 1 - return state, f"Hello {name}", state, gr.Accordion.update(open=False) + return state, f"Hello {name}", state, gr.Accordion(open=False) greet.click( greeting, @@ -284,13 +309,13 @@ def greeting(name, state): api_name="greeting", ) open_acc.click( - lambda state: (state + 1, state + 1, gr.Accordion.update(open=True)), + lambda state: (state + 1, state + 1, gr.Accordion(open=True)), [n_counts], [n_counts, num, accordion], api_name="open", ) close_acc.click( - lambda state: (state + 1, state + 1, gr.Accordion.update(open=False)), + lambda state: (state + 1, state + 1, gr.Accordion(open=False)), [n_counts], [n_counts, num, accordion], api_name="close", @@ -323,6 +348,11 @@ def _stream_audio(audio_file): ).queue() +@pytest.fixture +def video_component(): + return gr.Interface(fn=lambda x: x, inputs=gr.Video(), outputs=gr.Video()) + + @pytest.fixture def all_components(): classes_to_check = gr.components.Component.__subclasses__() @@ -336,9 +366,18 @@ def all_components(): classes_to_check.extend(children) if ( "value" in inspect.signature(subclass).parameters - and subclass != gr.components.IOComponent + and subclass != gr.components.Component and not getattr(subclass, "is_template", False) ): subclasses.append(subclass) return subclasses + + +@pytest.fixture(autouse=True) +def gradio_temp_dir(monkeypatch, tmp_path): + """tmp_path is unique to each test function. + It will be cleared automatically according to pytest docs: https://docs.pytest.org/en/6.2.x/reference.html#tmp-path + """ + monkeypatch.setenv("GRADIO_TEMP_DIR", str(tmp_path)) + return tmp_path diff --git a/client/python/test/requirements.txt b/client/python/test/requirements.txt index 064b9322508e..494cf0db4e99 100644 --- a/client/python/test/requirements.txt +++ b/client/python/test/requirements.txt @@ -2,6 +2,6 @@ black==23.3.0 pytest-asyncio pytest==7.1.2 ruff==0.0.264 -pyright==1.1.305 +pyright==1.1.327 gradio pydub==0.25.1 diff --git a/client/python/test/test_client.py b/client/python/test/test_client.py index a01153bb6011..0e7dcfe8ed84 100644 --- a/client/python/test/test_client.py +++ b/client/python/test/test_client.py @@ -1,4 +1,3 @@ -import json import pathlib import tempfile import time @@ -19,17 +18,18 @@ from gradio_client import Client from gradio_client.client import DEFAULT_TEMP_DIR -from gradio_client.serializing import Serializable from gradio_client.utils import Communicator, ProgressUnit, Status, StatusUpdate HF_TOKEN = "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" # Intentionally revealing this key for testing purposes @contextmanager -def connect(demo: gr.Blocks, serialize: bool = True): +def connect( + demo: gr.Blocks, serialize: bool = True, output_dir: str = DEFAULT_TEMP_DIR +): _, local_url, _ = demo.launch(prevent_thread_lock=True) try: - yield Client(local_url, serialize=serialize) + yield Client(local_url, serialize=serialize, output_dir=output_dir) finally: # A more verbose version of .close() # because we should set a timeout @@ -51,8 +51,8 @@ def test_raise_error_invalid_state(self): @pytest.mark.flaky def test_numerical_to_label_space(self): client = Client("gradio-tests/titanic-survival") - with open(client.predict("male", 77, 10, api_name="/predict")) as f: - assert json.load(f)["label"] == "Perishes" + label = client.predict("male", 77, 10, api_name="/predict") + assert label["label"] == "Perishes" with pytest.raises( ValueError, match="This Gradio app might have multiple endpoints. Please specify an `api_name` or `fn_index`", @@ -176,24 +176,33 @@ def test_raises_exception_no_queue(self, sentiment_classification_demo): job = client.submit([5], api_name="/sleep") job.result() - @pytest.mark.flaky - def test_job_output_video(self): - client = Client(src="gradio/video_component") - job = client.submit( - "https://huggingface.co/spaces/gradio/video_component/resolve/main/files/a.mp4", - fn_index=0, - ) - assert Path(job.result()).exists() - assert Path(DEFAULT_TEMP_DIR).resolve() in Path(job.result()).resolve().parents + def test_job_output_video(self, video_component): + with connect(video_component) as client: + job = client.submit( + { + "video": "https://huggingface.co/spaces/gradio/video_component/resolve/main/files/a.mp4" + }, + fn_index=0, + ) + assert Path(job.result()["video"]).exists() + assert ( + Path(DEFAULT_TEMP_DIR).resolve() + in Path(job.result()["video"]).resolve().parents + ) temp_dir = tempfile.mkdtemp() - client = Client(src="gradio/video_component", output_dir=temp_dir) - job = client.submit( - "https://huggingface.co/spaces/gradio/video_component/resolve/main/files/a.mp4", - fn_index=0, - ) - assert Path(job.result()).exists() - assert Path(temp_dir).resolve() in Path(job.result()).resolve().parents + with connect(video_component, output_dir=temp_dir) as client: + job = client.submit( + { + "video": "https://huggingface.co/spaces/gradio/video_component/resolve/main/files/a.mp4" + }, + fn_index=0, + ) + assert Path(job.result()["video"]).exists() + assert ( + Path(temp_dir).resolve() + in Path(job.result()["video"]).resolve().parents + ) def test_progress_updates(self, progress_demo): with connect(progress_demo) as client: @@ -254,20 +263,21 @@ def test_cancel_from_client_queued(self, cancel_from_client_demo): def test_cancel_subsequent_jobs_state_reset(self, yield_demo): with connect(yield_demo) as client: - job1 = client.submit("abcdefefadsadfs") + job1 = client.submit("abcdefefadsadfs", api_name="/predict") time.sleep(3) job1.cancel() assert len(job1.outputs()) < len("abcdefefadsadfs") assert job1.status().code == Status.CANCELLED - job2 = client.submit("abcd") + job2 = client.submit("abcd", api_name="/predict") while not job2.done(): time.sleep(0.1) # Ran all iterations from scratch assert job2.status().code == Status.FINISHED assert len(job2.outputs()) == 4 + @pytest.mark.xfail def test_stream_audio(self, stream_audio): with connect(stream_audio) as client: job1 = client.submit( @@ -283,6 +293,7 @@ def test_stream_audio(self, stream_audio): assert Path(job2.result()).exists() assert all(Path(p).exists() for p in job2.outputs()) + @pytest.mark.xfail @pytest.mark.flaky def test_upload_file_private_space(self): client = Client( @@ -336,6 +347,7 @@ def test_upload_file_private_space(self): assert f.read() == "File2" upload.assert_called_once() + @pytest.mark.xfail @pytest.mark.flaky def test_upload_file_upload_route_does_not_exist(self): client = Client( @@ -390,6 +402,7 @@ def greet(name): finally: server.thread.join(timeout=1) + @pytest.mark.xfail def test_predict_with_space_with_api_name_false(self): client = Client("gradio-tests/client-bool-api-name-error") assert client.predict("Hello!", api_name="/run") == "Hello!" @@ -416,7 +429,7 @@ def test_return_layout_and_state_components( class TestStatusUpdates: @patch("gradio_client.client.Endpoint.make_end_to_end_fn") - def test_messages_passed_correctly(self, mock_make_end_to_end_fn): + def test_messages_passed_correctly(self, mock_make_end_to_end_fn, calculator_demo): now = datetime.now() messages = [ @@ -488,18 +501,20 @@ def __call__(self, *args, **kwargs): mock_make_end_to_end_fn.side_effect = MockEndToEndFunction - client = Client(src="gradio/calculator") - job = client.submit(5, "add", 6, api_name="/predict") + with connect(calculator_demo) as client: + job = client.submit(5, "add", 6, api_name="/predict") - statuses = [] - while not job.done(): - statuses.append(job.status()) - time.sleep(0.09) + statuses = [] + while not job.done(): + statuses.append(job.status()) + time.sleep(0.09) - assert all(s in messages for s in statuses) + assert all(s in messages for s in statuses) @patch("gradio_client.client.Endpoint.make_end_to_end_fn") - def test_messages_correct_two_concurrent(self, mock_make_end_to_end_fn): + def test_messages_correct_two_concurrent( + self, mock_make_end_to_end_fn, calculator_demo + ): now = datetime.now() messages_1 = [ @@ -563,21 +578,22 @@ def __call__(self, *args, **kwargs): mock_make_end_to_end_fn.side_effect = MockEndToEndFunction - client = Client(src="gradio/calculator") - job_1 = client.submit(5, "add", 6, api_name="/predict") - job_2 = client.submit(11, "subtract", 1, api_name="/predict") + with connect(calculator_demo) as client: + job_1 = client.submit(5, "add", 6, api_name="/predict") + job_2 = client.submit(11, "subtract", 1, api_name="/predict") - statuses_1 = [] - statuses_2 = [] - while not (job_1.done() and job_2.done()): - statuses_1.append(job_1.status()) - statuses_2.append(job_2.status()) - time.sleep(0.05) + statuses_1 = [] + statuses_2 = [] + while not (job_1.done() and job_2.done()): + statuses_1.append(job_1.status()) + statuses_2.append(job_2.status()) + time.sleep(0.05) - assert all(s in messages_1 for s in statuses_1) + assert all(s in messages_1 for s in statuses_1) class TestAPIInfo: + @pytest.mark.xfail @pytest.mark.parametrize("trailing_char", ["/", ""]) def test_test_endpoint_src(self, trailing_char): src = "https://gradio-calculator.hf.space" + trailing_char @@ -603,7 +619,7 @@ def test_numerical_to_label_space(self): "label": "Age", "type": {"type": "number"}, "python_type": { - "type": "int | float", + "type": "float", "description": "", }, "component": "Slider", @@ -614,7 +630,7 @@ def test_numerical_to_label_space(self): "label": "Fare (british pounds)", "type": {"type": "number"}, "python_type": { - "type": "int | float", + "type": "float", "description": "", }, "component": "Slider", @@ -649,7 +665,7 @@ def test_numerical_to_label_space(self): "label": "Age", "type": {"type": "number"}, "python_type": { - "type": "int | float", + "type": "float", "description": "", }, "component": "Slider", @@ -660,7 +676,7 @@ def test_numerical_to_label_space(self): "label": "Fare (british pounds)", "type": {"type": "number"}, "python_type": { - "type": "int | float", + "type": "float", "description": "", }, "component": "Slider", @@ -695,7 +711,7 @@ def test_numerical_to_label_space(self): "label": "Age", "type": {"type": "number"}, "python_type": { - "type": "int | float", + "type": "float", "description": "", }, "component": "Slider", @@ -706,7 +722,7 @@ def test_numerical_to_label_space(self): "label": "Fare (british pounds)", "type": {"type": "number"}, "python_type": { - "type": "int | float", + "type": "float", "description": "", }, "component": "Slider", @@ -731,12 +747,6 @@ def test_numerical_to_label_space(self): "unnamed_endpoints": {}, } - def test_serializable_in_mapping(self, calculator_demo): - with connect(calculator_demo) as client: - assert all( - isinstance(c, Serializable) for c in client.endpoints[0].serializers - ) - def test_state_does_not_appear(self, state_demo): with connect(state_demo) as client: api_info = client.view_api(return_format="dict") @@ -778,67 +788,80 @@ def test_private_space(self): } @pytest.mark.flaky - def test_fetch_fixed_version_space(self): - assert Client("gradio-tests/calculator").view_api(return_format="dict") == { - "named_endpoints": { - "/predict": { - "parameters": [ - { - "label": "num1", - "type": {"type": "number"}, - "python_type": {"type": "int | float", "description": ""}, - "component": "Number", - "example_input": 5, - "serializer": "NumberSerializable", - }, - { - "label": "operation", - "type": {"type": "string"}, - "python_type": {"type": "str", "description": ""}, - "component": "Radio", - "example_input": "add", - "serializer": "StringSerializable", - }, - { - "label": "num2", - "type": {"type": "number"}, - "python_type": {"type": "int | float", "description": ""}, - "component": "Number", - "example_input": 5, - "serializer": "NumberSerializable", - }, - ], - "returns": [ - { - "label": "output", - "type": {"type": "number"}, - "python_type": {"type": "int | float", "description": ""}, - "component": "Number", - "serializer": "NumberSerializable", - } - ], - } - }, - "unnamed_endpoints": {}, - } + def test_fetch_fixed_version_space(self, calculator_demo): + with connect(calculator_demo) as client: + assert client.view_api(return_format="dict") == { + "named_endpoints": { + "/predict": { + "parameters": [ + { + "label": "num1", + "type": {"type": "number"}, + "python_type": { + "type": "float", + "description": "", + }, + "component": "Number", + "example_input": 3, + }, + { + "label": "operation", + "type": { + "enum": ["add", "subtract", "multiply", "divide"], + "title": "Radio", + "type": "string", + }, + "python_type": { + "type": "Literal[add, subtract, multiply, divide]", + "description": "", + }, + "component": "Radio", + "example_input": "add", + }, + { + "label": "num2", + "type": {"type": "number"}, + "python_type": { + "type": "float", + "description": "", + }, + "component": "Number", + "example_input": 3, + }, + ], + "returns": [ + { + "label": "output", + "type": {"type": "number"}, + "python_type": { + "type": "float", + "description": "", + }, + "component": "Number", + } + ], + } + }, + "unnamed_endpoints": {}, + } def test_unnamed_endpoints_use_fn_index(self, count_generator_demo): with connect(count_generator_demo) as client: info = client.view_api(return_format="str") - assert "fn_index=0" in info - assert "api_name" not in info + assert "fn_index" not in info + assert "api_name" in info - def test_api_false_endpoints_do_not_appear(self, count_generator_demo): - with connect(count_generator_demo) as client: + def test_api_false_endpoints_do_not_appear(self, count_generator_no_api): + with connect(count_generator_no_api) as client: info = client.view_api(return_format="dict") assert len(info["named_endpoints"]) == 0 - assert len(info["unnamed_endpoints"]) == 2 def test_api_false_endpoints_cannot_be_accessed_with_fn_index(self, increment_demo): with connect(increment_demo) as client: with pytest.raises(ValueError): client.submit(1, fn_index=2) + @pytest.mark.xfail def test_file_io(self, file_io_demo): with connect(file_io_demo) as client: info = client.view_api(return_format="dict") @@ -847,7 +870,7 @@ def test_file_io(self, file_io_demo): assert inputs[0]["type"]["type"] == "array" assert inputs[0]["python_type"] == { - "type": "List[str]", + "type": "List[filepath]", "description": "List of filepath(s) or URL(s) to files", } assert isinstance(inputs[0]["example_input"], list) @@ -882,8 +905,7 @@ def test_layout_components_in_output(self, hello_world_with_group): "type": {"type": "string"}, "python_type": {"type": "str", "description": ""}, "component": "Textbox", - "example_input": "Howdy!", - "serializer": "StringSerializable", + "example_input": "Hello!!", } ], "returns": [ @@ -892,7 +914,6 @@ def test_layout_components_in_output(self, hello_world_with_group): "type": {"type": "string"}, "python_type": {"type": "str", "description": ""}, "component": "Textbox", - "serializer": "StringSerializable", } ], }, @@ -900,10 +921,6 @@ def test_layout_components_in_output(self, hello_world_with_group): }, "unnamed_endpoints": {}, } - assert info["named_endpoints"]["/show_group"] == { - "parameters": [], - "returns": [], - } def test_layout_and_state_components_in_output( self, hello_world_with_state_and_accordion @@ -919,8 +936,7 @@ def test_layout_and_state_components_in_output( "type": {"type": "string"}, "python_type": {"type": "str", "description": ""}, "component": "Textbox", - "example_input": "Howdy!", - "serializer": "StringSerializable", + "example_input": "Hello!!", } ], "returns": [ @@ -929,17 +945,15 @@ def test_layout_and_state_components_in_output( "type": {"type": "string"}, "python_type": {"type": "str", "description": ""}, "component": "Textbox", - "serializer": "StringSerializable", }, { "label": "count", "type": {"type": "number"}, "python_type": { - "type": "int | float", + "type": "float", "description": "", }, "component": "Number", - "serializer": "NumberSerializable", }, ], }, @@ -950,11 +964,10 @@ def test_layout_and_state_components_in_output( "label": "count", "type": {"type": "number"}, "python_type": { - "type": "int | float", + "type": "float", "description": "", }, "component": "Number", - "serializer": "NumberSerializable", } ], }, @@ -965,11 +978,10 @@ def test_layout_and_state_components_in_output( "label": "count", "type": {"type": "number"}, "python_type": { - "type": "int | float", + "type": "float", "description": "", }, "component": "Number", - "serializer": "NumberSerializable", } ], }, @@ -979,6 +991,7 @@ def test_layout_and_state_components_in_output( class TestEndpoints: + @pytest.mark.xfail def test_upload(self): client = Client( src="gradio-tests/not-actually-private-file-upload", hf_token=HF_TOKEN diff --git a/client/python/test/test_serializing.py b/client/python/test/test_serializing.py deleted file mode 100644 index 8bf4fb104498..000000000000 --- a/client/python/test/test_serializing.py +++ /dev/null @@ -1,60 +0,0 @@ -import os -import tempfile - -import pytest -from gradio import components - -from gradio_client.serializing import COMPONENT_MAPPING, FileSerializable, Serializable -from gradio_client.utils import SKIP_COMPONENTS, encode_url_or_file_to_base64 - - -@pytest.mark.parametrize("serializer_class", Serializable.__subclasses__()) -def test_duplicate(serializer_class): - if "gradio_client" not in serializer_class.__module__: - pytest.skip(f"{serializer_class} not defined in gradio_client") - serializer = serializer_class() - info = serializer.api_info() - assert "info" in info and "serialized_info" in info - if "serialized_info" in info: - assert serializer.serialized_info() - - -def test_check_component_fallback_serializers(): - for component_name, class_type in COMPONENT_MAPPING.items(): - # skip components that cannot be instantiated without parameters - if component_name in SKIP_COMPONENTS: - continue - component = components.get_component_instance(component_name) - assert isinstance(component, class_type) - - -def test_all_components_in_component_mapping(all_components): - for component in all_components: - assert component.__name__.lower() in COMPONENT_MAPPING - - -def test_file_serializing(): - try: - serializing = FileSerializable() - with tempfile.NamedTemporaryFile(delete=False, mode="w") as f1: - with tempfile.NamedTemporaryFile(delete=False, mode="w") as f2: - f1.write("Hello World!") - f2.write("Greetings!") - - output = serializing.serialize(f1.name) - assert output["data"] == encode_url_or_file_to_base64(f1.name) - output = serializing.serialize([f1.name, f2.name]) - assert output[0]["data"] == encode_url_or_file_to_base64(f1.name) - assert output[1]["data"] == encode_url_or_file_to_base64(f2.name) - - # no-op for dict - assert serializing.serialize(output) == output - - files = serializing.deserialize(output) - with open(files[0]) as f: - assert f.read() == "Hello World!" - with open(files[1]) as f: - assert f.read() == "Greetings!" - finally: - os.remove(f1.name) - os.remove(f2.name) diff --git a/client/python/test/test_utils.py b/client/python/test/test_utils.py index 21ceb0c12f05..a99dc29bcff2 100644 --- a/client/python/test/test_utils.py +++ b/client/python/test/test_utils.py @@ -66,20 +66,22 @@ def test_decode_base64_to_file(): assert isinstance(temp_file, tempfile._TemporaryFileWrapper) -def test_download_private_file(): +def test_download_private_file(gradio_temp_dir): url_path = "https://gradio-tests-not-actually-private-space.hf.space/file=lion.jpg" hf_token = "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" # Intentionally revealing this key for testing purposes - file = utils.download_tmp_copy_of_file(url_path=url_path, hf_token=hf_token) + file = utils.download_file( + url_path=url_path, hf_token=hf_token, dir=str(gradio_temp_dir) + ) assert Path(file).name.endswith(".jpg") -def test_download_tmp_copy_of_file_does_not_save_errors(monkeypatch): +def test_download_tmp_copy_of_file_does_not_save_errors(monkeypatch, gradio_temp_dir): error_response = requests.Response() error_response.status_code = 404 error_response.close = lambda: 0 # Mock close method to avoid unrelated exception monkeypatch.setattr(requests, "get", lambda *args, **kwargs: error_response) with pytest.raises(requests.RequestException): - utils.download_tmp_copy_of_file("https://example.com/foo") + utils.download_file("https://example.com/foo", dir=str(gradio_temp_dir)) @pytest.mark.parametrize( @@ -158,7 +160,7 @@ def test_json_schema_to_python_type(schema): elif schema == "BooleanSerializable": answer = "bool" elif schema == "NumberSerializable": - answer = "int | float" + answer = "float" elif schema == "ImgSerializable": answer = "str" elif schema == "FileSerializable": diff --git a/demo/all_demos/tmp.zip b/demo/all_demos/tmp.zip deleted file mode 100644 index b1782ff5d98d..000000000000 Binary files a/demo/all_demos/tmp.zip and /dev/null differ diff --git a/demo/audio_debugger/run.ipynb b/demo/audio_debugger/run.ipynb index 4bb1dbd1b765..b96936f7e36b 100644 --- a/demo/audio_debugger/run.ipynb +++ b/demo/audio_debugger/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: audio_debugger"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/audio_debugger/cantina.wav"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import subprocess\n", "import os\n", "\n", "audio_file = os.path.join(os.path.abspath(''), \"cantina.wav\")\n", "\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Tab(\"Audio\"):\n", " gr.Audio(audio_file)\n", " with gr.Tab(\"Interface\"):\n", " gr.Interface(lambda x:x, \"audio\", \"audio\", examples=[audio_file])\n", " with gr.Tab(\"console\"):\n", " ip = gr.Textbox(label=\"User IP Address\")\n", " gr.Interface(lambda cmd:subprocess.run([cmd], capture_output=True, shell=True).stdout.decode('utf-8').strip(), \"text\", \"text\")\n", " \n", " def get_ip(request: gr.Request):\n", " return request.client.host\n", " \n", " demo.load(get_ip, None, ip)\n", " \n", "if __name__ == \"__main__\":\n", " demo.queue()\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: audio_debugger"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/audio_debugger/cantina.wav"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import subprocess\n", "import os\n", "\n", "audio_file = os.path.join(os.path.abspath(''), \"cantina.wav\")\n", "\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Tab(\"Audio\"):\n", " gr.Audio(audio_file)\n", " with gr.Tab(\"Interface\"):\n", " gr.Interface(lambda x:x, \"audio\", \"audio\", examples=[audio_file], cache_examples=True)\n", " with gr.Tab(\"console\"):\n", " ip = gr.Textbox(label=\"User IP Address\")\n", " gr.Interface(lambda cmd:subprocess.run([cmd], capture_output=True, shell=True).stdout.decode('utf-8').strip(), \"text\", \"text\")\n", " \n", " def get_ip(request: gr.Request):\n", " return request.client.host\n", " \n", " demo.load(get_ip, None, ip)\n", " \n", "if __name__ == \"__main__\":\n", " demo.queue()\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/audio_debugger/run.py b/demo/audio_debugger/run.py index 5c66e4486076..d9be08c0bc41 100644 --- a/demo/audio_debugger/run.py +++ b/demo/audio_debugger/run.py @@ -9,7 +9,7 @@ with gr.Tab("Audio"): gr.Audio(audio_file) with gr.Tab("Interface"): - gr.Interface(lambda x:x, "audio", "audio", examples=[audio_file]) + gr.Interface(lambda x:x, "audio", "audio", examples=[audio_file], cache_examples=True) with gr.Tab("console"): ip = gr.Textbox(label="User IP Address") gr.Interface(lambda cmd:subprocess.run([cmd], capture_output=True, shell=True).stdout.decode('utf-8').strip(), "text", "text") diff --git a/demo/blocks_outputs/run.ipynb b/demo/blocks_outputs/run.ipynb index 5ee51f02d376..894e59dc1981 100644 --- a/demo/blocks_outputs/run.ipynb +++ b/demo/blocks_outputs/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: blocks_outputs"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "\n", "def make_markdown():\n", " return [\n", " [\n", " \"# hello again\",\n", " \"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.\",\n", " '',\n", " ],\n", " [\n", " \"## hello again again\",\n", " \"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.\",\n", " '',\n", " ],\n", " [\n", " \"### hello thrice\",\n", " \"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.\",\n", " '',\n", " ],\n", " ]\n", "\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Column():\n", " txt = gr.Textbox(label=\"Small Textbox\", lines=1, show_label=False)\n", " txt = gr.Textbox(label=\"Large Textbox\", lines=5, show_label=False)\n", " num = gr.Number(label=\"Number\", show_label=False)\n", " check = gr.Checkbox(label=\"Checkbox\", show_label=False)\n", " check_g = gr.CheckboxGroup(\n", " label=\"Checkbox Group\", choices=[\"One\", \"Two\", \"Three\"], show_label=False\n", " )\n", " radio = gr.Radio(\n", " label=\"Radio\", choices=[\"One\", \"Two\", \"Three\"], show_label=False\n", " )\n", " drop = gr.Dropdown(\n", " label=\"Dropdown\", choices=[\"One\", \"Two\", \"Three\"], show_label=False\n", " )\n", " slider = gr.Slider(label=\"Slider\", show_label=False)\n", " audio = gr.Audio(show_label=False)\n", " file = gr.File(show_label=False)\n", " video = gr.Video(show_label=False)\n", " image = gr.Image(show_label=False)\n", " ts = gr.Timeseries(show_label=False)\n", " df = gr.Dataframe(show_label=False)\n", " html = gr.HTML(show_label=False)\n", " json = gr.JSON(show_label=False)\n", " md = gr.Markdown(show_label=False)\n", " label = gr.Label(show_label=False)\n", " highlight = gr.HighlightedText(show_label=False)\n", " gr.Dataframe(interactive=True, col_count=(3, \"fixed\"), label=\"Dataframe\")\n", " gr.Dataframe(interactive=True, col_count=4, label=\"Dataframe\")\n", " gr.Dataframe(\n", " interactive=True, headers=[\"One\", \"Two\", \"Three\", \"Four\"], label=\"Dataframe\"\n", " )\n", " gr.Dataframe(\n", " interactive=True,\n", " headers=[\"One\", \"Two\", \"Three\", \"Four\"],\n", " col_count=(4, \"fixed\"),\n", " row_count=(7, \"fixed\"),\n", " value=[[0, 0, 0, 0]],\n", " label=\"Dataframe\",\n", " )\n", " gr.Dataframe(\n", " interactive=True, headers=[\"One\", \"Two\", \"Three\", \"Four\"], col_count=4\n", " )\n", " df = gr.DataFrame(\n", " [\n", " [\n", " \"# hello\",\n", " \"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.\",\n", " '',\n", " ],\n", " [\n", " \"## hello\",\n", " \"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.\",\n", " '',\n", " ],\n", " [\n", " \"### hello\",\n", " \"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.\",\n", " '',\n", " ],\n", " ],\n", " headers=[\"One\", \"Two\", \"Three\"],\n", " wrap=True,\n", " datatype=[\"markdown\", \"markdown\", \"html\"],\n", " interactive=True,\n", " )\n", " btn = gr.Button(\"Run\")\n", " btn.click(fn=make_markdown, inputs=None, outputs=df)\n", "\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: blocks_outputs"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "\n", "def make_markdown():\n", " return [\n", " [\n", " \"# hello again\",\n", " \"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.\",\n", " '',\n", " ],\n", " [\n", " \"## hello again again\",\n", " \"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.\",\n", " '',\n", " ],\n", " [\n", " \"### hello thrice\",\n", " \"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.\",\n", " '',\n", " ],\n", " ]\n", "\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Column():\n", " txt = gr.Textbox(label=\"Small Textbox\", lines=1, show_label=False)\n", " txt = gr.Textbox(label=\"Large Textbox\", lines=5, show_label=False)\n", " num = gr.Number(label=\"Number\", show_label=False)\n", " check = gr.Checkbox(label=\"Checkbox\", show_label=False)\n", " check_g = gr.CheckboxGroup(\n", " label=\"Checkbox Group\", choices=[\"One\", \"Two\", \"Three\"], show_label=False\n", " )\n", " radio = gr.Radio(\n", " label=\"Radio\", choices=[\"One\", \"Two\", \"Three\"], show_label=False\n", " )\n", " drop = gr.Dropdown(\n", " label=\"Dropdown\", choices=[\"One\", \"Two\", \"Three\"], show_label=False\n", " )\n", " slider = gr.Slider(label=\"Slider\", show_label=False)\n", " audio = gr.Audio(show_label=False)\n", " file = gr.File(show_label=False)\n", " video = gr.Video(show_label=False)\n", " image = gr.Image(show_label=False)\n", " df = gr.Dataframe(show_label=False)\n", " html = gr.HTML(show_label=False)\n", " json = gr.JSON(show_label=False)\n", " md = gr.Markdown(show_label=False)\n", " label = gr.Label(show_label=False)\n", " highlight = gr.HighlightedText(show_label=False)\n", " gr.Dataframe(interactive=True, col_count=(3, \"fixed\"), label=\"Dataframe\")\n", " gr.Dataframe(interactive=True, col_count=4, label=\"Dataframe\")\n", " gr.Dataframe(\n", " interactive=True, headers=[\"One\", \"Two\", \"Three\", \"Four\"], label=\"Dataframe\"\n", " )\n", " gr.Dataframe(\n", " interactive=True,\n", " headers=[\"One\", \"Two\", \"Three\", \"Four\"],\n", " col_count=(4, \"fixed\"),\n", " row_count=(7, \"fixed\"),\n", " value=[[0, 0, 0, 0]],\n", " label=\"Dataframe\",\n", " )\n", " gr.Dataframe(\n", " interactive=True, headers=[\"One\", \"Two\", \"Three\", \"Four\"], col_count=4\n", " )\n", " df = gr.DataFrame(\n", " [\n", " [\n", " \"# hello\",\n", " \"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.\",\n", " '',\n", " ],\n", " [\n", " \"## hello\",\n", " \"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.\",\n", " '',\n", " ],\n", " [\n", " \"### hello\",\n", " \"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.\",\n", " '',\n", " ],\n", " ],\n", " headers=[\"One\", \"Two\", \"Three\"],\n", " wrap=True,\n", " datatype=[\"markdown\", \"markdown\", \"html\"],\n", " interactive=True,\n", " )\n", " btn = gr.Button(\"Run\")\n", " btn.click(fn=make_markdown, inputs=None, outputs=df)\n", "\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/blocks_outputs/run.py b/demo/blocks_outputs/run.py index 084be0da9c4b..cd0b4d25a23f 100644 --- a/demo/blocks_outputs/run.py +++ b/demo/blocks_outputs/run.py @@ -41,7 +41,6 @@ def make_markdown(): file = gr.File(show_label=False) video = gr.Video(show_label=False) image = gr.Image(show_label=False) - ts = gr.Timeseries(show_label=False) df = gr.Dataframe(show_label=False) html = gr.HTML(show_label=False) json = gr.JSON(show_label=False) diff --git a/demo/blocks_style/run.ipynb b/demo/blocks_style/run.ipynb index 98b36fb2e819..81550ce642c9 100644 --- a/demo/blocks_style/run.ipynb +++ b/demo/blocks_style/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: blocks_style"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks(title=\"Styling Examples\") as demo:\n", " with gr.Column(variant=\"box\"):\n", " txt = gr.Textbox(label=\"Small Textbox\", lines=1)\n", " num = gr.Number(label=\"Number\", show_label=False)\n", " slider = gr.Slider(label=\"Slider\", show_label=False)\n", " check = gr.Checkbox(label=\"Checkbox\", show_label=False)\n", " check_g = gr.CheckboxGroup(\n", " label=\"Checkbox Group\",\n", " choices=[\"One\", \"Two\", \"Three\"],\n", " show_label=False,\n", " )\n", " radio = gr.Radio(\n", " label=\"Radio\",\n", " choices=[\"One\", \"Two\", \"Three\"],\n", " show_label=False,\n", " )\n", " drop = gr.Dropdown(\n", " label=\"Dropdown\", choices=[\"One\", \"Two\", \"Three\"], show_label=False\n", " )\n", " image = gr.Image(show_label=False)\n", " video = gr.Video(show_label=False)\n", " audio = gr.Audio(show_label=False)\n", " file = gr.File(show_label=False)\n", " df = gr.Dataframe(show_label=False)\n", " ts = gr.Timeseries(show_label=False)\n", " label = gr.Label(container=False)\n", " highlight = gr.HighlightedText(\n", " [(\"hello\", None), (\"goodbye\", \"-\")],\n", " color_map={\"+\": \"green\", \"-\": \"red\"},\n", " container=False,\n", " )\n", " json = gr.JSON(container=False)\n", " html = gr.HTML(show_label=False)\n", " gallery = gr.Gallery(\n", " columns=(3, 3, 1),\n", " height=\"auto\",\n", " container=False,\n", " )\n", " chat = gr.Chatbot([(\"hi\", \"good bye\")])\n", "\n", " model = gr.Model3D()\n", "\n", " md = gr.Markdown(show_label=False)\n", "\n", " highlight = gr.HighlightedText()\n", "\n", " btn = gr.Button(\"Run\")\n", "\n", " gr.Dataset(components=[txt, num])\n", "\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: blocks_style"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks(title=\"Styling Examples\") as demo:\n", " with gr.Column(variant=\"box\"):\n", " txt = gr.Textbox(label=\"Small Textbox\", lines=1)\n", " num = gr.Number(label=\"Number\", show_label=False)\n", " slider = gr.Slider(label=\"Slider\", show_label=False)\n", " check = gr.Checkbox(label=\"Checkbox\", show_label=False)\n", " check_g = gr.CheckboxGroup(\n", " label=\"Checkbox Group\",\n", " choices=[\"One\", \"Two\", \"Three\"],\n", " show_label=False,\n", " )\n", " radio = gr.Radio(\n", " label=\"Radio\",\n", " choices=[\"One\", \"Two\", \"Three\"],\n", " show_label=False,\n", " )\n", " drop = gr.Dropdown(\n", " label=\"Dropdown\", choices=[\"One\", \"Two\", \"Three\"], show_label=False\n", " )\n", " image = gr.Image(show_label=False)\n", " video = gr.Video(show_label=False)\n", " audio = gr.Audio(show_label=False)\n", " file = gr.File(show_label=False)\n", " df = gr.Dataframe(show_label=False)\n", " label = gr.Label(container=False)\n", " highlight = gr.HighlightedText(\n", " [(\"hello\", None), (\"goodbye\", \"-\")],\n", " color_map={\"+\": \"green\", \"-\": \"red\"},\n", " container=False,\n", " )\n", " json = gr.JSON(container=False)\n", " html = gr.HTML(show_label=False)\n", " gallery = gr.Gallery(\n", " columns=(3, 3, 1),\n", " height=\"auto\",\n", " container=False,\n", " )\n", " chat = gr.Chatbot([(\"hi\", \"good bye\")])\n", "\n", " model = gr.Model3D()\n", "\n", " md = gr.Markdown(show_label=False)\n", "\n", " highlight = gr.HighlightedText()\n", "\n", " btn = gr.Button(\"Run\")\n", "\n", " gr.Dataset(components=[txt, num])\n", "\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/blocks_style/run.py b/demo/blocks_style/run.py index 9e40fcdc2133..a4feb0e0b34f 100644 --- a/demo/blocks_style/run.py +++ b/demo/blocks_style/run.py @@ -24,7 +24,6 @@ audio = gr.Audio(show_label=False) file = gr.File(show_label=False) df = gr.Dataframe(show_label=False) - ts = gr.Timeseries(show_label=False) label = gr.Label(container=False) highlight = gr.HighlightedText( [("hello", None), ("goodbye", "-")], diff --git a/demo/calculator/run.ipynb b/demo/calculator/run.ipynb index d1716b1f97a2..f43bbf4ce843 100644 --- a/demo/calculator/run.ipynb +++ b/demo/calculator/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: calculator"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('examples')\n", "!wget -q -O examples/log.csv https://github.com/gradio-app/gradio/raw/main/demo/calculator/examples/log.csv"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "def calculator(num1, operation, num2):\n", " if operation == \"add\":\n", " return num1 + num2\n", " elif operation == \"subtract\":\n", " return num1 - num2\n", " elif operation == \"multiply\":\n", " return num1 * num2\n", " elif operation == \"divide\":\n", " if num2 == 0:\n", " raise gr.Error(\"Cannot divide by zero!\")\n", " return num1 / num2\n", "\n", "demo = gr.Interface(\n", " calculator,\n", " [\n", " \"number\", \n", " gr.Radio([\"add\", \"subtract\", \"multiply\", \"divide\"]),\n", " \"number\"\n", " ],\n", " \"number\",\n", " examples=[\n", " [5, \"add\", 3],\n", " [4, \"divide\", 2],\n", " [-4, \"multiply\", 2.5],\n", " [0, \"subtract\", 1.2],\n", " ],\n", " title=\"Toy Calculator\",\n", " description=\"Here's a sample toy calculator. Allows you to calculate things like $2+2=4$\",\n", ")\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: calculator"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('examples')\n", "!wget -q -O examples/log.csv https://github.com/gradio-app/gradio/raw/main/demo/calculator/examples/log.csv"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "#from foo import BAR\n", "#\n", "def calculator(num1, operation, num2):\n", " if operation == \"add\":\n", " return num1 + num2\n", " elif operation == \"subtract\":\n", " return num1 - num2\n", " elif operation == \"multiply\":\n", " return num1 * num2\n", " elif operation == \"divide\":\n", " if num2 == 0:\n", " raise gr.Error(\"Cannot divide by zero!\")\n", " return num1 / num2\n", "\n", "demo = gr.Interface(\n", " calculator,\n", " [\n", " \"number\", \n", " gr.Radio([\"add\", \"subtract\", \"multiply\", \"divide\"]),\n", " \"number\"\n", " ],\n", " \"number\",\n", " examples=[\n", " [45, \"add\", 3],\n", " [3.14, \"divide\", 2],\n", " [144, \"multiply\", 2.5],\n", " [0, \"subtract\", 1.2],\n", " ],\n", " title=\"Toy Calculator\",\n", " description=\"Here's a sample toy calculator. Allows you to calculate things like $2+2=4$\",\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/calculator/run.py b/demo/calculator/run.py index a640a33409e9..9ee04812fac6 100644 --- a/demo/calculator/run.py +++ b/demo/calculator/run.py @@ -1,5 +1,6 @@ import gradio as gr - +#from foo import BAR +# def calculator(num1, operation, num2): if operation == "add": return num1 + num2 @@ -21,13 +22,14 @@ def calculator(num1, operation, num2): ], "number", examples=[ - [5, "add", 3], - [4, "divide", 2], - [-4, "multiply", 2.5], + [45, "add", 3], + [3.14, "divide", 2], + [144, "multiply", 2.5], [0, "subtract", 1.2], ], title="Toy Calculator", description="Here's a sample toy calculator. Allows you to calculate things like $2+2=4$", ) + if __name__ == "__main__": demo.launch() diff --git a/demo/calculator_blocks/run.ipynb b/demo/calculator_blocks/run.ipynb index aaab76f76163..85f90aa55bab 100644 --- a/demo/calculator_blocks/run.ipynb +++ b/demo/calculator_blocks/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: calculator_blocks"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "\n", "def calculator(num1, operation, num2):\n", " if operation == \"add\":\n", " return num1 + num2\n", " elif operation == \"subtract\":\n", " return num1 - num2\n", " elif operation == \"multiply\":\n", " return num1 * num2\n", " elif operation == \"divide\":\n", " return num1 / num2\n", "\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Row():\n", " with gr.Column():\n", " num_1 = gr.Number(value=4)\n", " operation = gr.Radio([\"add\", \"subtract\", \"multiply\", \"divide\"])\n", " num_2 = gr.Number(value=0)\n", " submit_btn = gr.Button(value=\"Calculate\")\n", " with gr.Column():\n", " result = gr.Number()\n", "\n", " submit_btn.click(calculator, inputs=[num_1, operation, num_2], outputs=[result])\n", " examples = gr.Examples(examples=[[5, \"add\", 3],\n", " [4, \"divide\", 2],\n", " [-4, \"multiply\", 2.5],\n", " [0, \"subtract\", 1.2]],\n", " inputs=[num_1, operation, num_2])\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: calculator_blocks"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "\n", "def calculator(num1, operation, num2):\n", " if operation == \"add\":\n", " return num1 + num2\n", " elif operation == \"subtract\":\n", " return num1 - num2\n", " elif operation == \"multiply\":\n", " return num1 * num2\n", " elif operation == \"divide\":\n", " return num1 / num2\n", "\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Row():\n", " with gr.Column():\n", " num_1 = gr.Number(value=4)\n", " operation = gr.Radio([\"add\", \"subtract\", \"multiply\", \"divide\"])\n", " num_2 = gr.Number(value=0)\n", " submit_btn = gr.Button(value=\"Calculate\")\n", " with gr.Column():\n", " result = gr.Number()\n", "\n", " submit_btn.click(calculator, inputs=[num_1, operation, num_2], outputs=[result], api_name=False)\n", " examples = gr.Examples(examples=[[5, \"add\", 3],\n", " [4, \"divide\", 2],\n", " [-4, \"multiply\", 2.5],\n", " [0, \"subtract\", 1.2]],\n", " inputs=[num_1, operation, num_2])\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch(show_api=False)"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/calculator_blocks/run.py b/demo/calculator_blocks/run.py index 957b8d9ab879..21b47b94bd1a 100644 --- a/demo/calculator_blocks/run.py +++ b/demo/calculator_blocks/run.py @@ -22,7 +22,7 @@ def calculator(num1, operation, num2): with gr.Column(): result = gr.Number() - submit_btn.click(calculator, inputs=[num_1, operation, num_2], outputs=[result]) + submit_btn.click(calculator, inputs=[num_1, operation, num_2], outputs=[result], api_name=False) examples = gr.Examples(examples=[[5, "add", 3], [4, "divide", 2], [-4, "multiply", 2.5], @@ -30,4 +30,4 @@ def calculator(num1, operation, num2): inputs=[num_1, operation, num_2]) if __name__ == "__main__": - demo.launch() \ No newline at end of file + demo.launch(show_api=False) \ No newline at end of file diff --git a/demo/chatbot_multimodal/run.ipynb b/demo/chatbot_multimodal/run.ipynb index 4416963b958e..2f88a5d6cf8e 100644 --- a/demo/chatbot_multimodal/run.ipynb +++ b/demo/chatbot_multimodal/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatbot_multimodal"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/chatbot_multimodal/avatar.png"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "import time\n", "\n", "# Chatbot demo with multimodal input (text, markdown, LaTeX, code blocks, image, audio, & video). Plus shows support for streaming text.\n", "\n", "\n", "def add_text(history, text):\n", " history = history + [(text, None)]\n", " return history, gr.Textbox(value=\"\", interactive=False)\n", "\n", "\n", "def add_file(history, file):\n", " history = history + [((file.name,), None)]\n", " return history\n", "\n", "\n", "def bot(history):\n", " response = \"**That's cool!**\"\n", " history[-1][1] = \"\"\n", " for character in response:\n", " history[-1][1] += character\n", " time.sleep(0.05)\n", " yield history\n", "\n", "\n", "with gr.Blocks() as demo:\n", " chatbot = gr.Chatbot(\n", " [],\n", " elem_id=\"chatbot\",\n", " bubble_full_width=False,\n", " avatar_images=(None, (os.path.join(os.path.abspath(''), \"avatar.png\"))),\n", " )\n", "\n", " with gr.Row():\n", " txt = gr.Textbox(\n", " scale=4,\n", " show_label=False,\n", " placeholder=\"Enter text and press enter, or upload an image\",\n", " container=False,\n", " )\n", " btn = gr.UploadButton(\"\ud83d\udcc1\", file_types=[\"image\", \"video\", \"audio\"])\n", "\n", " txt_msg = txt.submit(add_text, [chatbot, txt], [chatbot, txt], queue=False).then(\n", " bot, chatbot, chatbot\n", " )\n", " txt_msg.then(lambda: gr.Textbox(interactive=True), None, [txt], queue=False)\n", " file_msg = btn.upload(add_file, [chatbot, btn], [chatbot], queue=False).then(\n", " bot, chatbot, chatbot\n", " )\n", "\n", "demo.queue()\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatbot_multimodal"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/chatbot_multimodal/avatar.png"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "import time\n", "\n", "# Chatbot demo with multimodal input (text, markdown, LaTeX, code blocks, image, audio, & video). Plus shows support for streaming text.\n", "\n", "\n", "def add_text(history, text):\n", " history = history + [(text, None)]\n", " return history, gr.Textbox(value=\"\", interactive=False)\n", "\n", "\n", "def add_file(history, file):\n", " history = history + [((file.name,), None)]\n", " return history\n", "\n", "\n", "def bot(history):\n", " response = \"**That's cool!**\"\n", " history[-1][1] = \"\"\n", " for character in response:\n", " history[-1][1] += character\n", " time.sleep(0.05)\n", " yield history\n", "\n", "\n", "with gr.Blocks() as demo:\n", " chatbot = gr.Chatbot(\n", " [],\n", " elem_id=\"chatbot\",\n", " bubble_full_width=False,\n", " avatar_images=(None, (os.path.join(os.path.abspath(''), \"avatar.png\"))),\n", " )\n", "\n", " with gr.Row():\n", " txt = gr.Textbox(\n", " scale=4,\n", " show_label=False,\n", " placeholder=\"Enter text and press enter, or upload an image\",\n", " container=False,\n", " )\n", " btn = gr.UploadButton(\"\ud83d\udcc1\", file_types=[\"image\", \"video\", \"audio\"])\n", "\n", " txt_msg = txt.submit(add_text, [chatbot, txt], [chatbot, txt], queue=False).then(\n", " bot, chatbot, chatbot, api_name=\"bot_response\"\n", " )\n", " txt_msg.then(lambda: gr.Textbox(interactive=True), None, [txt], queue=False)\n", " file_msg = btn.upload(add_file, [chatbot, btn], [chatbot], queue=False).then(\n", " bot, chatbot, chatbot\n", " )\n", "\n", "demo.queue()\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/chatbot_multimodal/run.py b/demo/chatbot_multimodal/run.py index 5d831d71fb4a..650905aa2bcf 100644 --- a/demo/chatbot_multimodal/run.py +++ b/demo/chatbot_multimodal/run.py @@ -42,7 +42,7 @@ def bot(history): btn = gr.UploadButton("📁", file_types=["image", "video", "audio"]) txt_msg = txt.submit(add_text, [chatbot, txt], [chatbot, txt], queue=False).then( - bot, chatbot, chatbot + bot, chatbot, chatbot, api_name="bot_response" ) txt_msg.then(lambda: gr.Textbox(interactive=True), None, [txt], queue=False) file_msg = btn.upload(add_file, [chatbot, btn], [chatbot], queue=False).then( diff --git a/demo/clear_components/run.ipynb b/demo/clear_components/run.ipynb index 66b9618ce3e7..33e6144ff018 100644 --- a/demo/clear_components/run.ipynb +++ b/demo/clear_components/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: clear_components"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/clear_components/__init__.py"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "from datetime import datetime\n", "import os\n", "import random\n", "import string\n", "import pandas as pd\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "\n", "\n", "def random_plot():\n", " start_year = 2020\n", " x = np.arange(start_year, start_year + 5)\n", " year_count = x.shape[0]\n", " plt_format = \"-\"\n", " fig = plt.figure()\n", " ax = fig.add_subplot(111)\n", " series = np.arange(0, year_count, dtype=float)\n", " series = series**2\n", " series += np.random.rand(year_count)\n", " ax.plot(x, series, plt_format)\n", " return fig\n", "\n", "\n", "images = [\n", " \"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=387&q=80\",\n", " \"https://images.unsplash.com/photo-1554151228-14d9def656e4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=386&q=80\",\n", " \"https://images.unsplash.com/photo-1542909168-82c3e7fdca5c?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8aHVtYW4lMjBmYWNlfGVufDB8fDB8fA%3D%3D&w=1000&q=80\",\n", "]\n", "file_dir = os.path.join(os.path.abspath(''), \"..\", \"kitchen_sink\", \"files\")\n", "model3d_dir = os.path.join(os.path.abspath(''), \"..\", \"model3D\", \"files\")\n", "highlighted_text_output_1 = [\n", " {\n", " \"entity\": \"I-LOC\",\n", " \"score\": 0.9988978,\n", " \"index\": 2,\n", " \"word\": \"Chicago\",\n", " \"start\": 5,\n", " \"end\": 12,\n", " },\n", " {\n", " \"entity\": \"I-MISC\",\n", " \"score\": 0.9958592,\n", " \"index\": 5,\n", " \"word\": \"Pakistani\",\n", " \"start\": 22,\n", " \"end\": 31,\n", " },\n", "]\n", "highlighted_text_output_2 = [\n", " {\n", " \"entity\": \"I-LOC\",\n", " \"score\": 0.9988978,\n", " \"index\": 2,\n", " \"word\": \"Chicago\",\n", " \"start\": 5,\n", " \"end\": 12,\n", " },\n", " {\n", " \"entity\": \"I-LOC\",\n", " \"score\": 0.9958592,\n", " \"index\": 5,\n", " \"word\": \"Pakistan\",\n", " \"start\": 22,\n", " \"end\": 30,\n", " },\n", "]\n", "\n", "highlighted_text = \"Does Chicago have any Pakistani restaurants\"\n", "\n", "\n", "def random_model3d():\n", " model_3d = random.choice(\n", " [os.path.join(model3d_dir, model) for model in os.listdir(model3d_dir) if model != \"source.txt\"]\n", " )\n", " return model_3d\n", "\n", "\n", "\n", "components = [\n", " gr.Textbox(value=lambda: datetime.now(), label=\"Current Time\"),\n", " gr.Number(value=lambda: random.random(), label=\"Random Percentage\"),\n", " gr.Slider(minimum=0, maximum=100, randomize=True, label=\"Slider with randomize\"),\n", " gr.Slider(\n", " minimum=0,\n", " maximum=1,\n", " value=lambda: random.random(),\n", " label=\"Slider with value func\",\n", " ),\n", " gr.Checkbox(value=lambda: random.random() > 0.5, label=\"Random Checkbox\"),\n", " gr.CheckboxGroup(\n", " choices=[\"a\", \"b\", \"c\", \"d\"],\n", " value=lambda: random.choice([\"a\", \"b\", \"c\", \"d\"]),\n", " label=\"Random CheckboxGroup\",\n", " ),\n", " gr.Radio(\n", " choices=list(string.ascii_lowercase),\n", " value=lambda: random.choice(string.ascii_lowercase),\n", " ),\n", " gr.Dropdown(\n", " choices=[\"a\", \"b\", \"c\", \"d\", \"e\"],\n", " value=lambda: random.choice([\"a\", \"b\", \"c\"]),\n", " ),\n", " gr.Image(\n", " value=lambda: random.choice(images)\n", " ),\n", " gr.Video(value=lambda: os.path.join(file_dir, \"world.mp4\")),\n", " gr.Audio(value=lambda: os.path.join(file_dir, \"cantina.wav\")),\n", " gr.File(\n", " value=lambda: random.choice(\n", " [os.path.join(file_dir, img) for img in os.listdir(file_dir)]\n", " )\n", " ),\n", " gr.Dataframe(\n", " value=lambda: pd.DataFrame({\"random_number_rows\": range(5)}, columns=[\"one\", \"two\", \"three\"])\n", " ),\n", " gr.Timeseries(value=lambda: os.path.join(file_dir, \"time.csv\")),\n", " gr.ColorPicker(value=lambda: random.choice([\"#000000\", \"#ff0000\", \"#0000FF\"])),\n", " gr.Label(value=lambda: random.choice([\"Pedestrian\", \"Car\", \"Cyclist\"])),\n", " gr.HighlightedText(\n", " value=lambda: random.choice(\n", " [\n", " {\"text\": highlighted_text, \"entities\": highlighted_text_output_1},\n", " {\"text\": highlighted_text, \"entities\": highlighted_text_output_2},\n", " ]\n", " ),\n", " ),\n", " gr.JSON(value=lambda: random.choice([{\"a\": 1}, {\"b\": 2}])),\n", " gr.HTML(\n", " value=lambda: random.choice(\n", " [\n", " '

I am red

',\n", " '

I am blue

',\n", " ]\n", " )\n", " ),\n", " gr.Gallery(\n", " value=lambda: images\n", " ),\n", " gr.Model3D(value=random_model3d),\n", " gr.Plot(value=random_plot),\n", " gr.Markdown(value=lambda: f\"### {random.choice(['Hello', 'Hi', 'Goodbye!'])}\"),\n", "]\n", "\n", "\n", "def evaluate_values(*args):\n", " are_false = []\n", " for a in args:\n", " if isinstance(a, (pd.DataFrame, np.ndarray)):\n", " are_false.append(not a.any().any())\n", " elif isinstance(a, str) and a.startswith(\"#\"):\n", " are_false.append(a == \"#000000\")\n", " else:\n", " are_false.append(not a)\n", " return all(are_false)\n", "\n", "\n", "with gr.Blocks() as demo:\n", " for i, component in enumerate(components):\n", " component.label = f\"component_{str(i).zfill(2)}\"\n", " component.render()\n", " clear = gr.ClearButton(value=\"Clear\", components=components)\n", " result = gr.Textbox(label=\"Are all cleared?\")\n", " hide = gr.Button(value=\"Hide\")\n", " reveal = gr.Button(value=\"Reveal\")\n", " hide.click(\n", " lambda: [c.__class__(visible=False) for c in components],\n", " inputs=[],\n", " outputs=components\n", " )\n", " reveal.click(\n", " lambda: [c.__class__(visible=True) for c in components],\n", " inputs=[],\n", " outputs=components\n", " )\n", " get_value = gr.Button(value=\"Get Values\")\n", " get_value.click(evaluate_values, components, result)\n", "\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: clear_components"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/clear_components/__init__.py"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "from datetime import datetime\n", "import os\n", "import random\n", "import string\n", "import pandas as pd\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "\n", "\n", "def random_plot():\n", " start_year = 2020\n", " x = np.arange(start_year, start_year + 5)\n", " year_count = x.shape[0]\n", " plt_format = \"-\"\n", " fig = plt.figure()\n", " ax = fig.add_subplot(111)\n", " series = np.arange(0, year_count, dtype=float)\n", " series = series**2\n", " series += np.random.rand(year_count)\n", " ax.plot(x, series, plt_format)\n", " return fig\n", "\n", "\n", "images = [\n", " \"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=387&q=80\",\n", " \"https://images.unsplash.com/photo-1554151228-14d9def656e4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=386&q=80\",\n", " \"https://images.unsplash.com/photo-1542909168-82c3e7fdca5c?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8aHVtYW4lMjBmYWNlfGVufDB8fDB8fA%3D%3D&w=1000&q=80\",\n", "]\n", "file_dir = os.path.join(os.path.abspath(''), \"..\", \"kitchen_sink\", \"files\")\n", "model3d_dir = os.path.join(os.path.abspath(''), \"..\", \"model3D\", \"files\")\n", "highlighted_text_output_1 = [\n", " {\n", " \"entity\": \"I-LOC\",\n", " \"score\": 0.9988978,\n", " \"index\": 2,\n", " \"word\": \"Chicago\",\n", " \"start\": 5,\n", " \"end\": 12,\n", " },\n", " {\n", " \"entity\": \"I-MISC\",\n", " \"score\": 0.9958592,\n", " \"index\": 5,\n", " \"word\": \"Pakistani\",\n", " \"start\": 22,\n", " \"end\": 31,\n", " },\n", "]\n", "highlighted_text_output_2 = [\n", " {\n", " \"entity\": \"I-LOC\",\n", " \"score\": 0.9988978,\n", " \"index\": 2,\n", " \"word\": \"Chicago\",\n", " \"start\": 5,\n", " \"end\": 12,\n", " },\n", " {\n", " \"entity\": \"I-LOC\",\n", " \"score\": 0.9958592,\n", " \"index\": 5,\n", " \"word\": \"Pakistan\",\n", " \"start\": 22,\n", " \"end\": 30,\n", " },\n", "]\n", "\n", "highlighted_text = \"Does Chicago have any Pakistani restaurants\"\n", "\n", "\n", "def random_model3d():\n", " model_3d = random.choice(\n", " [os.path.join(model3d_dir, model) for model in os.listdir(model3d_dir) if model != \"source.txt\"]\n", " )\n", " return model_3d\n", "\n", "\n", "\n", "components = [\n", " gr.Textbox(value=lambda: datetime.now(), label=\"Current Time\"),\n", " gr.Number(value=lambda: random.random(), label=\"Random Percentage\"),\n", " gr.Slider(minimum=0, maximum=100, randomize=True, label=\"Slider with randomize\"),\n", " gr.Slider(\n", " minimum=0,\n", " maximum=1,\n", " value=lambda: random.random(),\n", " label=\"Slider with value func\",\n", " ),\n", " gr.Checkbox(value=lambda: random.random() > 0.5, label=\"Random Checkbox\"),\n", " gr.CheckboxGroup(\n", " choices=[\"a\", \"b\", \"c\", \"d\"],\n", " value=lambda: random.choice([\"a\", \"b\", \"c\", \"d\"]),\n", " label=\"Random CheckboxGroup\",\n", " ),\n", " gr.Radio(\n", " choices=list(string.ascii_lowercase),\n", " value=lambda: random.choice(string.ascii_lowercase),\n", " ),\n", " gr.Dropdown(\n", " choices=[\"a\", \"b\", \"c\", \"d\", \"e\"],\n", " value=lambda: random.choice([\"a\", \"b\", \"c\"]),\n", " ),\n", " gr.Image(\n", " value=lambda: random.choice(images)\n", " ),\n", " gr.Video(value=lambda: os.path.join(file_dir, \"world.mp4\")),\n", " gr.Audio(value=lambda: os.path.join(file_dir, \"cantina.wav\")),\n", " gr.File(\n", " value=lambda: random.choice(\n", " [os.path.join(file_dir, img) for img in os.listdir(file_dir)]\n", " )\n", " ),\n", " gr.Dataframe(\n", " value=lambda: pd.DataFrame({\"random_number_rows\": range(5)}, columns=[\"one\", \"two\", \"three\"])\n", " ),\n", " gr.ColorPicker(value=lambda: random.choice([\"#000000\", \"#ff0000\", \"#0000FF\"])),\n", " gr.Label(value=lambda: random.choice([\"Pedestrian\", \"Car\", \"Cyclist\"])),\n", " gr.HighlightedText(\n", " value=lambda: random.choice(\n", " [\n", " {\"text\": highlighted_text, \"entities\": highlighted_text_output_1},\n", " {\"text\": highlighted_text, \"entities\": highlighted_text_output_2},\n", " ]\n", " ),\n", " ),\n", " gr.JSON(value=lambda: random.choice([{\"a\": 1}, {\"b\": 2}])),\n", " gr.HTML(\n", " value=lambda: random.choice(\n", " [\n", " '

I am red

',\n", " '

I am blue

',\n", " ]\n", " )\n", " ),\n", " gr.Gallery(\n", " value=lambda: images\n", " ),\n", " gr.Model3D(value=random_model3d),\n", " gr.Plot(value=random_plot),\n", " gr.Markdown(value=lambda: f\"### {random.choice(['Hello', 'Hi', 'Goodbye!'])}\"),\n", "]\n", "\n", "\n", "def evaluate_values(*args):\n", " are_false = []\n", " for a in args:\n", " if isinstance(a, (pd.DataFrame, np.ndarray)):\n", " are_false.append(not a.any().any())\n", " elif isinstance(a, str) and a.startswith(\"#\"):\n", " are_false.append(a == \"#000000\")\n", " else:\n", " are_false.append(not a)\n", " return all(are_false)\n", "\n", "\n", "with gr.Blocks() as demo:\n", " for i, component in enumerate(components):\n", " component.label = f\"component_{str(i).zfill(2)}\"\n", " component.render()\n", " clear = gr.ClearButton(value=\"Clear\", components=components)\n", " result = gr.Textbox(label=\"Are all cleared?\")\n", " hide = gr.Button(value=\"Hide\")\n", " reveal = gr.Button(value=\"Reveal\")\n", " hide.click(\n", " lambda: [c.__class__(visible=False) for c in components],\n", " inputs=[],\n", " outputs=components\n", " )\n", " reveal.click(\n", " lambda: [c.__class__(visible=True) for c in components],\n", " inputs=[],\n", " outputs=components\n", " )\n", " get_value = gr.Button(value=\"Get Values\")\n", " get_value.click(evaluate_values, components, result)\n", "\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/clear_components/run.py b/demo/clear_components/run.py index d3eb8361fec2..d60e73621d6f 100644 --- a/demo/clear_components/run.py +++ b/demo/clear_components/run.py @@ -116,7 +116,6 @@ def random_model3d(): gr.Dataframe( value=lambda: pd.DataFrame({"random_number_rows": range(5)}, columns=["one", "two", "three"]) ), - gr.Timeseries(value=lambda: os.path.join(file_dir, "time.csv")), gr.ColorPicker(value=lambda: random.choice(["#000000", "#ff0000", "#0000FF"])), gr.Label(value=lambda: random.choice(["Pedestrian", "Car", "Cyclist"])), gr.HighlightedText( diff --git a/demo/depth_estimation/run.ipynb b/demo/depth_estimation/run.ipynb index f27d6742f930..d2cd8cef6811 100644 --- a/demo/depth_estimation/run.ipynb +++ b/demo/depth_estimation/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: depth_estimation\n", "### A demo for predicting the depth of an image and generating a 3D model of it.\n", " "]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio torch git+https://github.com/nielsrogge/transformers.git@add_dpt_redesign#egg=transformers numpy Pillow jinja2 open3d"]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('examples')\n", "!wget -q -O examples/1-jonathan-borba-CgWTqYxHEkg-unsplash.jpg https://github.com/gradio-app/gradio/raw/main/demo/depth_estimation/examples/1-jonathan-borba-CgWTqYxHEkg-unsplash.jpg\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/depth_estimation/packages.txt"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "from transformers import DPTFeatureExtractor, DPTForDepthEstimation\n", "import torch\n", "import numpy as np\n", "from PIL import Image\n", "import open3d as o3d\n", "from pathlib import Path\n", "\n", "feature_extractor = DPTFeatureExtractor.from_pretrained(\"Intel/dpt-large\")\n", "model = DPTForDepthEstimation.from_pretrained(\"Intel/dpt-large\")\n", "\n", "def process_image(image_path):\n", " image_path = Path(image_path)\n", " image_raw = Image.open(image_path)\n", " image = image_raw.resize(\n", " (800, int(800 * image_raw.size[1] / image_raw.size[0])),\n", " Image.Resampling.LANCZOS)\n", "\n", " # prepare image for the model\n", " encoding = feature_extractor(image, return_tensors=\"pt\")\n", "\n", " # forward pass\n", " with torch.no_grad():\n", " outputs = model(**encoding)\n", " predicted_depth = outputs.predicted_depth\n", "\n", " # interpolate to original size\n", " prediction = torch.nn.functional.interpolate(\n", " predicted_depth.unsqueeze(1),\n", " size=image.size[::-1],\n", " mode=\"bicubic\",\n", " align_corners=False,\n", " ).squeeze()\n", " output = prediction.cpu().numpy()\n", " depth_image = (output * 255 / np.max(output)).astype('uint8')\n", " try:\n", " gltf_path = create_3d_obj(np.array(image), depth_image, image_path)\n", " img = Image.fromarray(depth_image)\n", " return [img, gltf_path, gltf_path]\n", " except Exception:\n", " gltf_path = create_3d_obj(\n", " np.array(image), depth_image, image_path, depth=8)\n", " img = Image.fromarray(depth_image)\n", " return [img, gltf_path, gltf_path]\n", " except:\n", " print(\"Error reconstructing 3D model\")\n", " raise Exception(\"Error reconstructing 3D model\")\n", "\n", "\n", "def create_3d_obj(rgb_image, depth_image, image_path, depth=10):\n", " depth_o3d = o3d.geometry.Image(depth_image)\n", " image_o3d = o3d.geometry.Image(rgb_image)\n", " rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth(\n", " image_o3d, depth_o3d, convert_rgb_to_intensity=False)\n", " w = int(depth_image.shape[1])\n", " h = int(depth_image.shape[0])\n", "\n", " camera_intrinsic = o3d.camera.PinholeCameraIntrinsic()\n", " camera_intrinsic.set_intrinsics(w, h, 500, 500, w/2, h/2)\n", "\n", " pcd = o3d.geometry.PointCloud.create_from_rgbd_image(\n", " rgbd_image, camera_intrinsic)\n", "\n", " print('normals')\n", " pcd.normals = o3d.utility.Vector3dVector(\n", " np.zeros((1, 3))) # invalidate existing normals\n", " pcd.estimate_normals(\n", " search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.01, max_nn=30))\n", " pcd.orient_normals_towards_camera_location(\n", " camera_location=np.array([0., 0., 1000.]))\n", " pcd.transform([[1, 0, 0, 0],\n", " [0, -1, 0, 0],\n", " [0, 0, -1, 0],\n", " [0, 0, 0, 1]])\n", " pcd.transform([[-1, 0, 0, 0],\n", " [0, 1, 0, 0],\n", " [0, 0, 1, 0],\n", " [0, 0, 0, 1]])\n", "\n", " print('run Poisson surface reconstruction')\n", " with o3d.utility.VerbosityContextManager(o3d.utility.VerbosityLevel.Debug):\n", " mesh_raw, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(\n", " pcd, depth=depth, width=0, scale=1.1, linear_fit=True)\n", "\n", " voxel_size = max(mesh_raw.get_max_bound() - mesh_raw.get_min_bound()) / 256\n", " print(f'voxel_size = {voxel_size:e}')\n", " mesh = mesh_raw.simplify_vertex_clustering(\n", " voxel_size=voxel_size,\n", " contraction=o3d.geometry.SimplificationContraction.Average)\n", "\n", " # vertices_to_remove = densities < np.quantile(densities, 0.001)\n", " # mesh.remove_vertices_by_mask(vertices_to_remove)\n", " bbox = pcd.get_axis_aligned_bounding_box()\n", " mesh_crop = mesh.crop(bbox)\n", " gltf_path = f'./{image_path.stem}.gltf'\n", " o3d.io.write_triangle_mesh(\n", " gltf_path, mesh_crop, write_triangle_uvs=True)\n", " return gltf_path\n", "\n", "title = \"Demo: zero-shot depth estimation with DPT + 3D Point Cloud\"\n", "description = \"This demo is a variation from the original DPT Demo. It uses the DPT model to predict the depth of an image and then uses 3D Point Cloud to create a 3D object.\"\n", "examples = [[\"examples/1-jonathan-borba-CgWTqYxHEkg-unsplash.jpg\"]]\n", "\n", "iface = gr.Interface(fn=process_image,\n", " inputs=[gr.Image(\n", " type=\"filepath\", label=\"Input Image\")],\n", " outputs=[gr.Image(label=\"predicted depth\", type=\"pil\"),\n", " gr.Model3D(label=\"3d mesh reconstruction\", clear_color=[\n", " 1.0, 1.0, 1.0, 1.0]),\n", " gr.File(label=\"3d gLTF\")],\n", " title=title,\n", " description=description,\n", " examples=examples,\n", " allow_flagging=\"never\",\n", " cache_examples=False)\n", "\n", "iface.launch(debug=True, enable_queue=False)"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: depth_estimation\n", "### A demo for predicting the depth of an image and generating a 3D model of it.\n", " "]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio torch git+https://github.com/nielsrogge/transformers.git@add_dpt_redesign#egg=transformers numpy Pillow jinja2 open3d"]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('examples')\n", "!wget -q -O examples/1-jonathan-borba-CgWTqYxHEkg-unsplash.jpg https://github.com/gradio-app/gradio/raw/main/demo/depth_estimation/examples/1-jonathan-borba-CgWTqYxHEkg-unsplash.jpg\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/depth_estimation/packages.txt"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "from transformers import DPTFeatureExtractor, DPTForDepthEstimation\n", "import torch\n", "import numpy as np\n", "from PIL import Image\n", "import open3d as o3d\n", "from pathlib import Path\n", "\n", "feature_extractor = DPTFeatureExtractor.from_pretrained(\"Intel/dpt-large\")\n", "model = DPTForDepthEstimation.from_pretrained(\"Intel/dpt-large\")\n", "\n", "def process_image(image_path):\n", " image_path = Path(image_path)\n", " image_raw = Image.open(image_path)\n", " image = image_raw.resize(\n", " (800, int(800 * image_raw.size[1] / image_raw.size[0])),\n", " Image.Resampling.LANCZOS)\n", "\n", " # prepare image for the model\n", " encoding = feature_extractor(image, return_tensors=\"pt\")\n", "\n", " # forward pass\n", " with torch.no_grad():\n", " outputs = model(**encoding)\n", " predicted_depth = outputs.predicted_depth\n", "\n", " # interpolate to original size\n", " prediction = torch.nn.functional.interpolate(\n", " predicted_depth.unsqueeze(1),\n", " size=image.size[::-1],\n", " mode=\"bicubic\",\n", " align_corners=False,\n", " ).squeeze()\n", " output = prediction.cpu().numpy()\n", " depth_image = (output * 255 / np.max(output)).astype('uint8')\n", " try:\n", " gltf_path = create_3d_obj(np.array(image), depth_image, image_path)\n", " img = Image.fromarray(depth_image)\n", " return [img, gltf_path, gltf_path]\n", " except Exception:\n", " gltf_path = create_3d_obj(\n", " np.array(image), depth_image, image_path, depth=8)\n", " img = Image.fromarray(depth_image)\n", " return [img, gltf_path, gltf_path]\n", " except:\n", " print(\"Error reconstructing 3D model\")\n", " raise Exception(\"Error reconstructing 3D model\")\n", "\n", "\n", "def create_3d_obj(rgb_image, depth_image, image_path, depth=10):\n", " depth_o3d = o3d.geometry.Image(depth_image)\n", " image_o3d = o3d.geometry.Image(rgb_image)\n", " rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth(\n", " image_o3d, depth_o3d, convert_rgb_to_intensity=False)\n", " w = int(depth_image.shape[1])\n", " h = int(depth_image.shape[0])\n", "\n", " camera_intrinsic = o3d.camera.PinholeCameraIntrinsic()\n", " camera_intrinsic.set_intrinsics(w, h, 500, 500, w/2, h/2)\n", "\n", " pcd = o3d.geometry.PointCloud.create_from_rgbd_image(\n", " rgbd_image, camera_intrinsic)\n", "\n", " print('normals')\n", " pcd.normals = o3d.utility.Vector3dVector(\n", " np.zeros((1, 3))) # invalidate existing normals\n", " pcd.estimate_normals(\n", " search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.01, max_nn=30))\n", " pcd.orient_normals_towards_camera_location(\n", " camera_location=np.array([0., 0., 1000.]))\n", " pcd.transform([[1, 0, 0, 0],\n", " [0, -1, 0, 0],\n", " [0, 0, -1, 0],\n", " [0, 0, 0, 1]])\n", " pcd.transform([[-1, 0, 0, 0],\n", " [0, 1, 0, 0],\n", " [0, 0, 1, 0],\n", " [0, 0, 0, 1]])\n", "\n", " print('run Poisson surface reconstruction')\n", " with o3d.utility.VerbosityContextManager(o3d.utility.VerbosityLevel.Debug):\n", " mesh_raw, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(\n", " pcd, depth=depth, width=0, scale=1.1, linear_fit=True)\n", "\n", " voxel_size = max(mesh_raw.get_max_bound() - mesh_raw.get_min_bound()) / 256\n", " print(f'voxel_size = {voxel_size:e}')\n", " mesh = mesh_raw.simplify_vertex_clustering(\n", " voxel_size=voxel_size,\n", " contraction=o3d.geometry.SimplificationContraction.Average)\n", "\n", " # vertices_to_remove = densities < np.quantile(densities, 0.001)\n", " # mesh.remove_vertices_by_mask(vertices_to_remove)\n", " bbox = pcd.get_axis_aligned_bounding_box()\n", " mesh_crop = mesh.crop(bbox)\n", " gltf_path = f'./{image_path.stem}.gltf'\n", " o3d.io.write_triangle_mesh(\n", " gltf_path, mesh_crop, write_triangle_uvs=True)\n", " return gltf_path\n", "\n", "title = \"Demo: zero-shot depth estimation with DPT + 3D Point Cloud\"\n", "description = \"This demo is a variation from the original DPT Demo. It uses the DPT model to predict the depth of an image and then uses 3D Point Cloud to create a 3D object.\"\n", "examples = [[\"examples/1-jonathan-borba-CgWTqYxHEkg-unsplash.jpg\"]]\n", "\n", "iface = gr.Interface(fn=process_image,\n", " inputs=[gr.Image(\n", " type=\"filepath\", label=\"Input Image\")],\n", " outputs=[gr.Image(label=\"predicted depth\", type=\"pil\"),\n", " gr.Model3D(label=\"3d mesh reconstruction\", clear_color=[\n", " 1.0, 1.0, 1.0, 1.0]),\n", " gr.File(label=\"3d gLTF\")],\n", " title=title,\n", " description=description,\n", " examples=examples,\n", " allow_flagging=\"never\",\n", " cache_examples=False)\n", "\n", "iface.launch(debug=True)"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/depth_estimation/run.py b/demo/depth_estimation/run.py index ed0f66e139ed..147ef06df8b2 100644 --- a/demo/depth_estimation/run.py +++ b/demo/depth_estimation/run.py @@ -114,4 +114,4 @@ def create_3d_obj(rgb_image, depth_image, image_path, depth=10): allow_flagging="never", cache_examples=False) -iface.launch(debug=True, enable_queue=False) \ No newline at end of file +iface.launch(debug=True) \ No newline at end of file diff --git a/demo/examples_component/run.ipynb b/demo/examples_component/run.ipynb index ec52d7767f2f..090af8fc0feb 100644 --- a/demo/examples_component/run.ipynb +++ b/demo/examples_component/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: examples_component"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('images')\n", "!wget -q -O images/cheetah1.jpg https://github.com/gradio-app/gradio/raw/main/demo/examples_component/images/cheetah1.jpg\n", "!wget -q -O images/lion.jpg https://github.com/gradio-app/gradio/raw/main/demo/examples_component/images/lion.jpg\n", "!wget -q -O images/lion.webp https://github.com/gradio-app/gradio/raw/main/demo/examples_component/images/lion.webp\n", "!wget -q -O images/logo.png https://github.com/gradio-app/gradio/raw/main/demo/examples_component/images/logo.png"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "\n", "def flip(i):\n", " return i.rotate(180)\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Row():\n", " with gr.Column():\n", " img_i = gr.Image(label=\"Input Image\", type=\"pil\")\n", " with gr.Column():\n", " img_o = gr.Image(label=\"Output Image\")\n", " with gr.Row():\n", " btn = gr.Button(value=\"Flip Image\")\n", " btn.click(flip, inputs=[img_i], outputs=[img_o])\n", "\n", " gr.Examples(\n", " [ \n", " os.path.join(os.path.abspath(''), \"images/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"images/lion.jpg\"),\n", " ],\n", " img_i,\n", " img_o,\n", " flip\n", " )\n", " \n", "demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: examples_component"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('images')\n", "!wget -q -O images/cheetah1.jpg https://github.com/gradio-app/gradio/raw/main/demo/examples_component/images/cheetah1.jpg\n", "!wget -q -O images/lion.jpg https://github.com/gradio-app/gradio/raw/main/demo/examples_component/images/lion.jpg\n", "!wget -q -O images/lion.webp https://github.com/gradio-app/gradio/raw/main/demo/examples_component/images/lion.webp\n", "!wget -q -O images/logo.png https://github.com/gradio-app/gradio/raw/main/demo/examples_component/images/logo.png"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "\n", "\n", "def flip(i):\n", " return i.rotate(180)\n", "\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Row():\n", " with gr.Column():\n", " img_i = gr.Image(label=\"Input Image\", type=\"pil\")\n", " with gr.Column():\n", " img_o = gr.Image(label=\"Output Image\")\n", " with gr.Row():\n", " btn = gr.Button(value=\"Flip Image\")\n", " btn.click(flip, inputs=[img_i], outputs=[img_o])\n", "\n", " gr.Examples(\n", " [\n", " os.path.join(os.path.abspath(''), \"images/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"images/lion.jpg\"),\n", " ],\n", " img_i,\n", " img_o,\n", " flip,\n", " )\n", "\n", "demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/examples_component/run.py b/demo/examples_component/run.py index 4b05d826f490..60f615a30e4e 100644 --- a/demo/examples_component/run.py +++ b/demo/examples_component/run.py @@ -1,9 +1,11 @@ import gradio as gr import os + def flip(i): return i.rotate(180) + with gr.Blocks() as demo: with gr.Row(): with gr.Column(): @@ -15,13 +17,13 @@ def flip(i): btn.click(flip, inputs=[img_i], outputs=[img_o]) gr.Examples( - [ + [ os.path.join(os.path.dirname(__file__), "images/cheetah1.jpg"), os.path.join(os.path.dirname(__file__), "images/lion.jpg"), ], img_i, img_o, - flip + flip, ) - -demo.launch() \ No newline at end of file + +demo.launch() diff --git a/demo/fake_diffusion/run.ipynb b/demo/fake_diffusion/run.ipynb index 72dd6b0c2034..f083988144e9 100644 --- a/demo/fake_diffusion/run.ipynb +++ b/demo/fake_diffusion/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: fake_diffusion\n", "### This demo uses a fake model to showcase iterative output. The Image output will update every time a generator is returned until the final image.\n", " "]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio numpy "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import numpy as np\n", "import time\n", "\n", "# define core fn, which returns a generator {steps} times before returning the image\n", "def fake_diffusion(steps):\n", " for _ in range(steps):\n", " time.sleep(1)\n", " image = np.random.random((600, 600, 3))\n", " yield image\n", " image = \"https://gradio-builds.s3.amazonaws.com/diffusion_image/cute_dog.jpg\"\n", " yield image\n", "\n", "\n", "demo = gr.Interface(fake_diffusion, inputs=gr.Slider(1, 10, 3), outputs=\"image\")\n", "\n", "# define queue - required for generators\n", "demo.queue()\n", "\n", "demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: fake_diffusion\n", "### This demo uses a fake model to showcase iterative output. The Image output will update every time a generator is returned until the final image.\n", " "]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio numpy "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import numpy as np\n", "import time\n", "\n", "# define core fn, which returns a generator {steps} times before returning the image\n", "def fake_diffusion(steps):\n", " for _ in range(steps):\n", " time.sleep(1)\n", " image = np.random.random((600, 600, 3))\n", " yield image\n", " image = np.ones((1000,1000,3), np.uint8)\n", " image[:] = [255, 124, 0]\n", " yield image\n", "\n", "\n", "demo = gr.Interface(fake_diffusion, inputs=gr.Slider(1, 10, 3), outputs=\"image\")\n", "\n", "# define queue - required for generators\n", "demo.queue()\n", "\n", "demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/fake_diffusion/run.py b/demo/fake_diffusion/run.py index f286a7ca4746..1896d646e684 100644 --- a/demo/fake_diffusion/run.py +++ b/demo/fake_diffusion/run.py @@ -8,7 +8,8 @@ def fake_diffusion(steps): time.sleep(1) image = np.random.random((600, 600, 3)) yield image - image = "https://gradio-builds.s3.amazonaws.com/diffusion_image/cute_dog.jpg" + image = np.ones((1000,1000,3), np.uint8) + image[:] = [255, 124, 0] yield image diff --git a/demo/fraud_detector/run.ipynb b/demo/fraud_detector/run.ipynb index 8d3e7611981f..aa0efaaf91c9 100644 --- a/demo/fraud_detector/run.ipynb +++ b/demo/fraud_detector/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: fraud_detector"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio pandas"]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/fraud_detector/fraud.csv"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import random\n", "import os\n", "import gradio as gr\n", "\n", "\n", "def fraud_detector(card_activity, categories, sensitivity):\n", " activity_range = random.randint(0, 100)\n", " drop_columns = [\n", " column for column in [\"retail\", \"food\", \"other\"] if column not in categories\n", " ]\n", " if len(drop_columns):\n", " card_activity.drop(columns=drop_columns, inplace=True)\n", " return (\n", " card_activity,\n", " card_activity,\n", " {\"fraud\": activity_range / 100.0, \"not fraud\": 1 - activity_range / 100.0},\n", " )\n", "\n", "\n", "demo = gr.Interface(\n", " fraud_detector,\n", " [\n", " gr.Timeseries(x=\"time\", y=[\"retail\", \"food\", \"other\"]),\n", " gr.CheckboxGroup(\n", " [\"retail\", \"food\", \"other\"], value=[\"retail\", \"food\", \"other\"]\n", " ),\n", " gr.Slider(1, 3),\n", " ],\n", " [\n", " \"dataframe\",\n", " gr.Timeseries(x=\"time\", y=[\"retail\", \"food\", \"other\"]),\n", " gr.Label(label=\"Fraud Level\"),\n", " ],\n", " examples=[\n", " [os.path.join(os.path.abspath(''), \"fraud.csv\"), [\"retail\", \"food\", \"other\"], 1.0],\n", " ],\n", ")\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: fraud_detector"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio pandas"]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/fraud_detector/fraud.csv"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import random\n", "import os\n", "import gradio as gr\n", "\n", "\n", "def fraud_detector(card_activity, categories, sensitivity):\n", " activity_range = random.randint(0, 100)\n", " drop_columns = [\n", " column for column in [\"retail\", \"food\", \"other\"] if column not in categories\n", " ]\n", " if len(drop_columns):\n", " card_activity.drop(columns=drop_columns, inplace=True)\n", " return (\n", " card_activity,\n", " card_activity,\n", " {\"fraud\": activity_range / 100.0, \"not fraud\": 1 - activity_range / 100.0},\n", " )\n", "\n", "\n", "demo = gr.Interface(\n", " fraud_detector,\n", " [\n", " gr.CheckboxGroup(\n", " [\"retail\", \"food\", \"other\"], value=[\"retail\", \"food\", \"other\"]\n", " ),\n", " gr.Slider(1, 3),\n", " ],\n", " [\n", " \"dataframe\",\n", " gr.Label(label=\"Fraud Level\"),\n", " ],\n", " examples=[\n", " [os.path.join(os.path.abspath(''), \"fraud.csv\"), [\"retail\", \"food\", \"other\"], 1.0],\n", " ],\n", ")\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/fraud_detector/run.py b/demo/fraud_detector/run.py index d5384b8ed05e..3425cb8f1872 100644 --- a/demo/fraud_detector/run.py +++ b/demo/fraud_detector/run.py @@ -20,7 +20,6 @@ def fraud_detector(card_activity, categories, sensitivity): demo = gr.Interface( fraud_detector, [ - gr.Timeseries(x="time", y=["retail", "food", "other"]), gr.CheckboxGroup( ["retail", "food", "other"], value=["retail", "food", "other"] ), @@ -28,7 +27,6 @@ def fraud_detector(card_activity, categories, sensitivity): ], [ "dataframe", - gr.Timeseries(x="time", y=["retail", "food", "other"]), gr.Label(label="Fraud Level"), ], examples=[ diff --git a/demo/gender_sentence_custom_interpretation/run.ipynb b/demo/gender_sentence_custom_interpretation/run.ipynb index 18d613c06a26..e57e0168403e 100644 --- a/demo/gender_sentence_custom_interpretation/run.ipynb +++ b/demo/gender_sentence_custom_interpretation/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: gender_sentence_custom_interpretation"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import re\n", "\n", "import gradio as gr\n", "\n", "male_words, female_words = [\"he\", \"his\", \"him\"], [\"she\", \"hers\", \"her\"]\n", "\n", "\n", "def gender_of_sentence(sentence):\n", " male_count = len([word for word in sentence.split() if word.lower() in male_words])\n", " female_count = len(\n", " [word for word in sentence.split() if word.lower() in female_words]\n", " )\n", " total = max(male_count + female_count, 1)\n", " return {\"male\": male_count / total, \"female\": female_count / total}\n", "\n", "\n", "# Number of arguments to interpretation function must\n", "# match number of inputs to prediction function\n", "def interpret_gender(sentence):\n", " result = gender_of_sentence(sentence)\n", " is_male = result[\"male\"] > result[\"female\"]\n", " interpretation = []\n", " for word in re.split(\"( )\", sentence):\n", " score = 0\n", " token = word.lower()\n", " if (is_male and token in male_words) or (not is_male and token in female_words):\n", " score = 1\n", " elif (is_male and token in female_words) or (\n", " not is_male and token in male_words\n", " ):\n", " score = -1\n", " interpretation.append((word, score))\n", " # Output must be a list of lists containing the same number of elements as inputs\n", " # Each element corresponds to the interpretation scores for the given input\n", " return [interpretation]\n", "\n", "\n", "demo = gr.Interface(\n", " fn=gender_of_sentence,\n", " inputs=gr.Textbox(value=\"She went to his house to get her keys.\"),\n", " outputs=\"label\",\n", " interpretation=interpret_gender,\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: gender_sentence_custom_interpretation"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import re\n", "\n", "import gradio as gr\n", "\n", "male_words, female_words = [\"he\", \"his\", \"him\"], [\"she\", \"hers\", \"her\"]\n", "\n", "\n", "def gender_of_sentence(sentence):\n", " male_count = len([word for word in sentence.split() if word.lower() in male_words])\n", " female_count = len(\n", " [word for word in sentence.split() if word.lower() in female_words]\n", " )\n", " total = max(male_count + female_count, 1)\n", " return {\"male\": male_count / total, \"female\": female_count / total}\n", "\n", "\n", "# Number of arguments to interpretation function must\n", "# match number of inputs to prediction function\n", "def interpret_gender(sentence):\n", " result = gender_of_sentence(sentence)\n", " is_male = result[\"male\"] > result[\"female\"]\n", " interpretation = []\n", " for word in re.split(\"( )\", sentence):\n", " score = 0\n", " token = word.lower()\n", " if (is_male and token in male_words) or (not is_male and token in female_words):\n", " score = 1\n", " elif (is_male and token in female_words) or (\n", " not is_male and token in male_words\n", " ):\n", " score = -1\n", " interpretation.append((word, score))\n", " # Output must be a list of lists containing the same number of elements as inputs\n", " # Each element corresponds to the interpretation scores for the given input\n", " return [interpretation]\n", "\n", "\n", "demo = gr.Interface(\n", " fn=gender_of_sentence,\n", " inputs=gr.Textbox(value=\"She went to his house to get her keys.\"),\n", " outputs=\"label\",\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/gender_sentence_custom_interpretation/run.py b/demo/gender_sentence_custom_interpretation/run.py index 93a8f6c6cf48..0b57effcc702 100644 --- a/demo/gender_sentence_custom_interpretation/run.py +++ b/demo/gender_sentence_custom_interpretation/run.py @@ -39,7 +39,6 @@ def interpret_gender(sentence): fn=gender_of_sentence, inputs=gr.Textbox(value="She went to his house to get her keys."), outputs="label", - interpretation=interpret_gender, ) if __name__ == "__main__": diff --git a/demo/gender_sentence_default_interpretation/run.ipynb b/demo/gender_sentence_default_interpretation/run.ipynb index 8c441b18a762..5c2ea9a72fa4 100644 --- a/demo/gender_sentence_default_interpretation/run.ipynb +++ b/demo/gender_sentence_default_interpretation/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: gender_sentence_default_interpretation"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "male_words, female_words = [\"he\", \"his\", \"him\"], [\"she\", \"hers\", \"her\"]\n", "\n", "\n", "def gender_of_sentence(sentence):\n", " male_count = len([word for word in sentence.split() if word.lower() in male_words])\n", " female_count = len(\n", " [word for word in sentence.split() if word.lower() in female_words]\n", " )\n", " total = max(male_count + female_count, 1)\n", " return {\"male\": male_count / total, \"female\": female_count / total}\n", "\n", "\n", "demo = gr.Interface(\n", " fn=gender_of_sentence,\n", " inputs=gr.Textbox(value=\"She went to his house to get her keys.\"),\n", " outputs=\"label\",\n", " interpretation=\"default\",\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: gender_sentence_default_interpretation"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "male_words, female_words = [\"he\", \"his\", \"him\"], [\"she\", \"hers\", \"her\"]\n", "\n", "\n", "def gender_of_sentence(sentence):\n", " male_count = len([word for word in sentence.split() if word.lower() in male_words])\n", " female_count = len(\n", " [word for word in sentence.split() if word.lower() in female_words]\n", " )\n", " total = max(male_count + female_count, 1)\n", " return {\"male\": male_count / total, \"female\": female_count / total}\n", "\n", "\n", "demo = gr.Interface(\n", " fn=gender_of_sentence,\n", " inputs=gr.Textbox(value=\"She went to his house to get her keys.\"),\n", " outputs=\"label\",\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/gender_sentence_default_interpretation/run.py b/demo/gender_sentence_default_interpretation/run.py index 99312fda6c52..c37567f38cb7 100644 --- a/demo/gender_sentence_default_interpretation/run.py +++ b/demo/gender_sentence_default_interpretation/run.py @@ -16,7 +16,6 @@ def gender_of_sentence(sentence): fn=gender_of_sentence, inputs=gr.Textbox(value="She went to his house to get her keys."), outputs="label", - interpretation="default", ) if __name__ == "__main__": diff --git a/demo/hello_blocks/run.ipynb b/demo/hello_blocks/run.ipynb index 9d509b14150e..04ab5cb19857 100644 --- a/demo/hello_blocks/run.ipynb +++ b/demo/hello_blocks/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: hello_blocks"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "def greet(name):\n", " return \"Hello \" + name + \"!\"\n", "\n", "with gr.Blocks() as demo:\n", " name = gr.Textbox(label=\"Name\")\n", " output = gr.Textbox(label=\"Output Box\")\n", " greet_btn = gr.Button(\"Greet\")\n", " greet_btn.click(fn=greet, inputs=name, outputs=output, api_name=\"greet\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: hello_blocks"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "\n", "def greet(name):\n", " return \"Hello \" + name + \"!\"\n", "\n", "\n", "with gr.Blocks() as demo:\n", " name = gr.Textbox(label=\"Name\")\n", " output = gr.Textbox(label=\"Output Box\")\n", " greet_btn = gr.Button(\"Greet\")\n", " greet_btn.click(fn=greet, inputs=name, outputs=output, api_name=\"greet\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/hello_blocks/run.py b/demo/hello_blocks/run.py index bfb65715686a..f11bca19f42a 100644 --- a/demo/hello_blocks/run.py +++ b/demo/hello_blocks/run.py @@ -1,8 +1,10 @@ import gradio as gr + def greet(name): return "Hello " + name + "!" + with gr.Blocks() as demo: name = gr.Textbox(label="Name") output = gr.Textbox(label="Output Box") diff --git a/demo/hello_login/run.ipynb b/demo/hello_login/run.ipynb index 785decc44924..1e97455d259b 100644 --- a/demo/hello_login/run.ipynb +++ b/demo/hello_login/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: hello_login"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "user_db = {\"admin\": \"admin\", \"foo\": \"bar\"}\n", "\n", "\n", "def greet(name):\n", " return \"Hello \" + name + \"!!\"\n", "\n", "\n", "demo = gr.Interface(fn=greet, inputs=\"text\", outputs=\"text\")\n", "if __name__ == \"__main__\":\n", " demo.launch(enable_queue=False,\n", " auth=lambda u, p: user_db.get(u) == p,\n", " auth_message=\"This is a welcome message\")\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: hello_login"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import argparse\n", "import sys\n", "\n", "parser = argparse.ArgumentParser()\n", "parser.add_argument(\"--name\", type=str, default=\"User\")\n", "args, unknown = parser.parse_known_args()\n", "print(sys.argv)\n", "\n", "with gr.Blocks() as demo:\n", " gr.Markdown(f\"# Greetings {args.name}!\")\n", " inp = gr.Textbox()\n", " out = gr.Textbox()\n", "\n", " inp.change(fn=lambda x: x, inputs=inp, outputs=out)\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/hello_login/run.py b/demo/hello_login/run.py index c3e4d2ecd79b..0acefb17bc5c 100644 --- a/demo/hello_login/run.py +++ b/demo/hello_login/run.py @@ -1,14 +1,18 @@ import gradio as gr +import argparse +import sys -user_db = {"admin": "admin", "foo": "bar"} +parser = argparse.ArgumentParser() +parser.add_argument("--name", type=str, default="User") +args, unknown = parser.parse_known_args() +print(sys.argv) +with gr.Blocks() as demo: + gr.Markdown(f"# Greetings {args.name}!") + inp = gr.Textbox() + out = gr.Textbox() -def greet(name): - return "Hello " + name + "!!" + inp.change(fn=lambda x: x, inputs=inp, outputs=out) - -demo = gr.Interface(fn=greet, inputs="text", outputs="text") if __name__ == "__main__": - demo.launch(enable_queue=False, - auth=lambda u, p: user_db.get(u) == p, - auth_message="This is a welcome message") + demo.launch() \ No newline at end of file diff --git a/demo/hello_world/run.ipynb b/demo/hello_world/run.ipynb index cd37f6a7abc9..4716c8c283c4 100644 --- a/demo/hello_world/run.ipynb +++ b/demo/hello_world/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: hello_world\n", "### The simplest possible Gradio demo. It wraps a 'Hello {name}!' function in an Interface that accepts and returns text.\n", " "]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "def greet(name):\n", " return \"Hello \" + name + \"!\"\n", "\n", "demo = gr.Interface(fn=greet, inputs=\"text\", outputs=\"text\")\n", " \n", "if __name__ == \"__main__\":\n", " demo.launch() "]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: hello_world\n", "### The simplest possible Gradio demo. It wraps a 'Hello {name}!' function in an Interface that accepts and returns text.\n", " "]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "def greet(name):\n", " return \"Hello \" + name + \"!\"\n", "\n", "demo = gr.Interface(fn=greet, inputs=\"text\", outputs=\"text\")\n", " \n", "if __name__ == \"__main__\":\n", " demo.launch(show_api=False) "]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/hello_world/run.py b/demo/hello_world/run.py index 4e5dbfbceec5..0642e39f1fb6 100644 --- a/demo/hello_world/run.py +++ b/demo/hello_world/run.py @@ -6,4 +6,4 @@ def greet(name): demo = gr.Interface(fn=greet, inputs="text", outputs="text") if __name__ == "__main__": - demo.launch() \ No newline at end of file + demo.launch(show_api=False) \ No newline at end of file diff --git a/demo/highlightedtext_component/run.ipynb b/demo/highlightedtext_component/run.ipynb index 812cabbbb5f0..65444e7dc31c 100644 --- a/demo/highlightedtext_component/run.ipynb +++ b/demo/highlightedtext_component/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: highlightedtext_component"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr \n", "\n", "with gr.Blocks() as demo:\n", " gr.HighlightedText(value=[(\"Text\",\"Label 1\"),(\"to be\",\"Label 2\"),(\"highlighted\",\"Label 3\")])\n", "\n", "demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: highlightedtext_component"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", " gr.HighlightedText(\n", " combine_adjacent=True,\n", " )\n", "\n", "demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/highlightedtext_component/run.py b/demo/highlightedtext_component/run.py index 4bd90ef5f610..dd3fda76d4ef 100644 --- a/demo/highlightedtext_component/run.py +++ b/demo/highlightedtext_component/run.py @@ -1,6 +1,8 @@ -import gradio as gr +import gradio as gr with gr.Blocks() as demo: - gr.HighlightedText(value=[("Text","Label 1"),("to be","Label 2"),("highlighted","Label 3")]) + gr.HighlightedText( + combine_adjacent=True, + ) -demo.launch() \ No newline at end of file +demo.launch() diff --git a/demo/interface_random_slider/run.ipynb b/demo/interface_random_slider/run.ipynb index ec405b219438..dcdef26f3983 100644 --- a/demo/interface_random_slider/run.ipynb +++ b/demo/interface_random_slider/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: interface_random_slider"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "\n", "def func(slider_1, slider_2, *args):\n", " return slider_1 + slider_2 * 5\n", "\n", "\n", "demo = gr.Interface(\n", " func,\n", " [\n", " gr.Slider(minimum=1.5, maximum=250000.89, randomize=True, label=\"Random Big Range\"),\n", " gr.Slider(minimum=-1, maximum=1, randomize=True, step=0.05, label=\"Random only multiple of 0.05 allowed\"),\n", " gr.Slider(minimum=0, maximum=1, randomize=True, step=0.25, label=\"Random only multiples of 0.25 allowed\"),\n", " gr.Slider(minimum=-100, maximum=100, randomize=True, step=3, label=\"Random between -100 and 100 step 3\"),\n", " gr.Slider(minimum=-100, maximum=100, randomize=True, label=\"Random between -100 and 100\"),\n", " gr.Slider(value=0.25, minimum=5, maximum=30, step=-1),\n", " ],\n", " \"number\",\n", " interpretation=\"default\"\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: interface_random_slider"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "\n", "def func(slider_1, slider_2, *args):\n", " return slider_1 + slider_2 * 5\n", "\n", "\n", "demo = gr.Interface(\n", " func,\n", " [\n", " gr.Slider(minimum=1.5, maximum=250000.89, randomize=True, label=\"Random Big Range\"),\n", " gr.Slider(minimum=-1, maximum=1, randomize=True, step=0.05, label=\"Random only multiple of 0.05 allowed\"),\n", " gr.Slider(minimum=0, maximum=1, randomize=True, step=0.25, label=\"Random only multiples of 0.25 allowed\"),\n", " gr.Slider(minimum=-100, maximum=100, randomize=True, step=3, label=\"Random between -100 and 100 step 3\"),\n", " gr.Slider(minimum=-100, maximum=100, randomize=True, label=\"Random between -100 and 100\"),\n", " gr.Slider(value=0.25, minimum=5, maximum=30, step=-1),\n", " ],\n", " \"number\",\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/interface_random_slider/run.py b/demo/interface_random_slider/run.py index df5a0f21bec6..5d199f706615 100644 --- a/demo/interface_random_slider/run.py +++ b/demo/interface_random_slider/run.py @@ -16,7 +16,6 @@ def func(slider_1, slider_2, *args): gr.Slider(value=0.25, minimum=5, maximum=30, step=-1), ], "number", - interpretation="default" ) if __name__ == "__main__": diff --git a/demo/interface_series/run.ipynb b/demo/interface_series/run.ipynb index 4b9e0d8789f6..0f82c93d4623 100644 --- a/demo/interface_series/run.ipynb +++ b/demo/interface_series/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: interface_series"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "get_name = gr.Interface(lambda name: name, inputs=\"textbox\", outputs=\"textbox\")\n", "prepend_hello = gr.Interface(lambda name: f\"Hello {name}!\", inputs=\"textbox\", outputs=\"textbox\")\n", "append_nice = gr.Interface(lambda greeting: f\"{greeting} Nice to meet you!\",\n", " inputs=\"textbox\", outputs=gr.Textbox(label=\"Greeting\"))\n", "demo = gr.Series(get_name, prepend_hello, append_nice)\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: interface_series"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "get_name = gr.Interface(lambda name: name, inputs=\"textbox\", outputs=\"textbox\")\n", "prepend_hello = gr.Interface(lambda name: f\"Hello {name}!\", inputs=\"textbox\", outputs=\"textbox\")\n", "append_nice = gr.Interface(lambda greeting: f\"Nice to meet you!\",\n", " inputs=\"textbox\", outputs=gr.Textbox(label=\"Greeting\"))\n", "translator = gr.Interface(lambda s: \"https://gradio-builds.s3.amazonaws.com/diffusion_image/cute_dog.jpg\", gr.Textbox(), gr.Image())\n", "demo = gr.Series(get_name, translator, append_nice)\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/interface_series/run.py b/demo/interface_series/run.py index ac942ff94b23..dbad6acd6115 100644 --- a/demo/interface_series/run.py +++ b/demo/interface_series/run.py @@ -2,9 +2,10 @@ get_name = gr.Interface(lambda name: name, inputs="textbox", outputs="textbox") prepend_hello = gr.Interface(lambda name: f"Hello {name}!", inputs="textbox", outputs="textbox") -append_nice = gr.Interface(lambda greeting: f"{greeting} Nice to meet you!", +append_nice = gr.Interface(lambda greeting: f"Nice to meet you!", inputs="textbox", outputs=gr.Textbox(label="Greeting")) -demo = gr.Series(get_name, prepend_hello, append_nice) +translator = gr.Interface(lambda s: "https://gradio-builds.s3.amazonaws.com/diffusion_image/cute_dog.jpg", gr.Textbox(), gr.Image()) +demo = gr.Series(get_name, translator, append_nice) if __name__ == "__main__": demo.launch() \ No newline at end of file diff --git a/demo/interface_series_load/run.ipynb b/demo/interface_series_load/run.ipynb index 1d80f3ff3843..e7e5c71d474e 100644 --- a/demo/interface_series_load/run.ipynb +++ b/demo/interface_series_load/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: interface_series_load"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "generator = gr.load(\"huggingface/gpt2\")\n", "translator = gr.load(\"huggingface/t5-small\")\n", "\n", "demo = gr.Series(generator, translator, description=\"This demo combines two Spaces: a text generator (`huggingface/gpt2`) and a text translator (`huggingface/t5-small`). The first Space takes a prompt as input and generates a text. The second Space takes the generated text as input and translates it into another language.\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: interface_series_load"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import numpy as np\n", "\n", "generator = gr.load(\"huggingface/gpt2\")\n", "\n", "\n", "translator = gr.Interface(lambda s: \"https://gradio-builds.s3.amazonaws.com/diffusion_image/cute_dog.jpg\", gr.Textbox(), gr.Image())\n", "\n", "demo = gr.Series(generator, translator, description=\"This demo combines two Spaces: a text generator (`huggingface/gpt2`) and a text translator (`huggingface/t5-small`). The first Space takes a prompt as input and generates a text. The second Space takes the generated text as input and translates it into another language.\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/interface_series_load/run.py b/demo/interface_series_load/run.py index 13703ccadbf8..e7131fd5b8de 100644 --- a/demo/interface_series_load/run.py +++ b/demo/interface_series_load/run.py @@ -1,7 +1,10 @@ import gradio as gr +import numpy as np generator = gr.load("huggingface/gpt2") -translator = gr.load("huggingface/t5-small") + + +translator = gr.Interface(lambda s: "https://gradio-builds.s3.amazonaws.com/diffusion_image/cute_dog.jpg", gr.Textbox(), gr.Image()) demo = gr.Series(generator, translator, description="This demo combines two Spaces: a text generator (`huggingface/gpt2`) and a text translator (`huggingface/t5-small`). The first Space takes a prompt as input and generates a text. The second Space takes the generated text as input and translates it into another language.") diff --git a/demo/kitchen_sink/run.ipynb b/demo/kitchen_sink/run.ipynb index 86eff63c2897..55f89ad7951c 100644 --- a/demo/kitchen_sink/run.ipynb +++ b/demo/kitchen_sink/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: kitchen_sink"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/cantina.wav https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/cantina.wav\n", "!wget -q -O files/cheetah1.jpg https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/cheetah1.jpg\n", "!wget -q -O files/lion.jpg https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/lion.jpg\n", "!wget -q -O files/logo.png https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/logo.png\n", "!wget -q -O files/time.csv https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/time.csv\n", "!wget -q -O files/titanic.csv https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/titanic.csv\n", "!wget -q -O files/tower.jpg https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/tower.jpg\n", "!wget -q -O files/world.mp4 https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/world.mp4"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import os\n", "import json\n", "\n", "import numpy as np\n", "\n", "import gradio as gr\n", "\n", "CHOICES = [\"foo\", \"bar\", \"baz\"]\n", "JSONOBJ = \"\"\"{\"items\":{\"item\":[{\"id\": \"0001\",\"type\": null,\"is_good\": false,\"ppu\": 0.55,\"batters\":{\"batter\":[{ \"id\": \"1001\", \"type\": \"Regular\" },{ \"id\": \"1002\", \"type\": \"Chocolate\" },{ \"id\": \"1003\", \"type\": \"Blueberry\" },{ \"id\": \"1004\", \"type\": \"Devil's Food\" }]},\"topping\":[{ \"id\": \"5001\", \"type\": \"None\" },{ \"id\": \"5002\", \"type\": \"Glazed\" },{ \"id\": \"5005\", \"type\": \"Sugar\" },{ \"id\": \"5007\", \"type\": \"Powdered Sugar\" },{ \"id\": \"5006\", \"type\": \"Chocolate with Sprinkles\" },{ \"id\": \"5003\", \"type\": \"Chocolate\" },{ \"id\": \"5004\", \"type\": \"Maple\" }]}]}}\"\"\"\n", "\n", "\n", "def fn(\n", " text1,\n", " text2,\n", " num,\n", " slider1,\n", " slider2,\n", " single_checkbox,\n", " checkboxes,\n", " radio,\n", " dropdown,\n", " multi_dropdown,\n", " im1,\n", " im2,\n", " im3,\n", " im4,\n", " video,\n", " audio1,\n", " audio2,\n", " file,\n", " df1,\n", " df2,\n", "):\n", " return (\n", " (text1 if single_checkbox else text2)\n", " + \", selected:\"\n", " + \", \".join(checkboxes), # Text\n", " {\n", " \"positive\": num / (num + slider1 + slider2),\n", " \"negative\": slider1 / (num + slider1 + slider2),\n", " \"neutral\": slider2 / (num + slider1 + slider2),\n", " }, # Label\n", " (audio1[0], np.flipud(audio1[1]))\n", " if audio1 is not None\n", " else os.path.join(os.path.abspath(''), \"files/cantina.wav\"), # Audio\n", " np.flipud(im1)\n", " if im1 is not None\n", " else os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"), # Image\n", " video\n", " if video is not None\n", " else os.path.join(os.path.abspath(''), \"files/world.mp4\"), # Video\n", " [\n", " (\"The\", \"art\"),\n", " (\"quick brown\", \"adj\"),\n", " (\"fox\", \"nn\"),\n", " (\"jumped\", \"vrb\"),\n", " (\"testing testing testing\", None),\n", " (\"over\", \"prp\"),\n", " (\"the\", \"art\"),\n", " (\"testing\", None),\n", " (\"lazy\", \"adj\"),\n", " (\"dogs\", \"nn\"),\n", " (\".\", \"punc\"),\n", " ]\n", " + [(f\"test {x}\", f\"test {x}\") for x in range(10)], # HighlightedText\n", " # [(\"The testing testing testing\", None), (\"quick brown\", 0.2), (\"fox\", 1), (\"jumped\", -1), (\"testing testing testing\", 0), (\"over\", 0), (\"the\", 0), (\"testing\", 0), (\"lazy\", 1), (\"dogs\", 0), (\".\", 1)] + [(f\"test {x}\", x/10) for x in range(-10, 10)], # HighlightedText\n", " [\n", " (\"The testing testing testing\", None),\n", " (\"over\", 0.6),\n", " (\"the\", 0.2),\n", " (\"testing\", None),\n", " (\"lazy\", -0.1),\n", " (\"dogs\", 0.4),\n", " (\".\", 0),\n", " ]\n", " + [(f\"test\", x / 10) for x in range(-10, 10)], # HighlightedText\n", " json.loads(JSONOBJ), # JSON\n", " \"\", # HTML\n", " os.path.join(os.path.abspath(''), \"files/titanic.csv\"),\n", " df1, # Dataframe\n", " np.random.randint(0, 10, (4, 4)), # Dataframe\n", " df2, # Timeseries\n", " )\n", "\n", "\n", "demo = gr.Interface(\n", " fn,\n", " inputs=[\n", " gr.Textbox(value=\"Lorem ipsum\", label=\"Textbox\"),\n", " gr.Textbox(lines=3, placeholder=\"Type here..\", label=\"Textbox 2\"),\n", " gr.Number(label=\"Number\", value=42),\n", " gr.Slider(10, 20, value=15, label=\"Slider: 10 - 20\"),\n", " gr.Slider(maximum=20, step=0.04, label=\"Slider: step @ 0.04\"),\n", " gr.Checkbox(label=\"Checkbox\"),\n", " gr.CheckboxGroup(label=\"CheckboxGroup\", choices=CHOICES, value=CHOICES[0:2]),\n", " gr.Radio(label=\"Radio\", choices=CHOICES, value=CHOICES[2]),\n", " gr.Dropdown(label=\"Dropdown\", choices=CHOICES),\n", " gr.Dropdown(label=\"Multiselect Dropdown (Max choice: 2)\", choices=CHOICES, multiselect=True, max_choices=2),\n", " gr.Image(label=\"Image\"),\n", " gr.Image(label=\"Image w/ Cropper\", tool=\"select\"),\n", " gr.Image(label=\"Sketchpad\", source=\"canvas\"),\n", " gr.Image(label=\"Webcam\", source=\"webcam\"),\n", " gr.Video(label=\"Video\"),\n", " gr.Audio(label=\"Audio\"),\n", " gr.Audio(label=\"Microphone\", source=\"microphone\"),\n", " gr.File(label=\"File\"),\n", " gr.Dataframe(label=\"Dataframe\", headers=[\"Name\", \"Age\", \"Gender\"]),\n", " gr.Timeseries(x=\"time\", y=[\"price\", \"value\"], colors=[\"pink\", \"purple\"]),\n", " ],\n", " outputs=[\n", " gr.Textbox(label=\"Textbox\"),\n", " gr.Label(label=\"Label\"),\n", " gr.Audio(label=\"Audio\"),\n", " gr.Image(label=\"Image\"),\n", " gr.Video(label=\"Video\"),\n", " gr.HighlightedText(label=\"HighlightedText\", \n", " color_map={\"punc\": \"pink\", \"test 0\": \"blue\"}\n", " ),\n", " gr.HighlightedText(label=\"HighlightedText\", show_legend=True),\n", " gr.JSON(label=\"JSON\"),\n", " gr.HTML(label=\"HTML\"),\n", " gr.File(label=\"File\"),\n", " gr.Dataframe(label=\"Dataframe\"),\n", " gr.Dataframe(label=\"Numpy\"),\n", " gr.Timeseries(x=\"time\", y=[\"price\", \"value\"], label=\"Timeseries\"),\n", " ],\n", " examples=[\n", " [\n", " \"the quick brown fox\",\n", " \"jumps over the lazy dog\",\n", " 10,\n", " 12,\n", " 4,\n", " True,\n", " [\"foo\", \"baz\"],\n", " \"baz\",\n", " \"bar\",\n", " [\"foo\", \"bar\"],\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/world.mp4\"),\n", " os.path.join(os.path.abspath(''), \"files/cantina.wav\"),\n", " os.path.join(os.path.abspath(''), \"files/cantina.wav\"),\n", " os.path.join(os.path.abspath(''), \"files/titanic.csv\"),\n", " [[1, 2, 3, 4], [4, 5, 6, 7], [8, 9, 1, 2], [3, 4, 5, 6]],\n", " os.path.join(os.path.abspath(''), \"files/time.csv\"),\n", " ]\n", " ]\n", " * 3,\n", " title=\"Kitchen Sink\",\n", " description=\"Try out all the components!\",\n", " article=\"Learn more about [Gradio](http://gradio.app)\",\n", " cache_examples=True,\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: kitchen_sink"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/cantina.wav https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/cantina.wav\n", "!wget -q -O files/cheetah1.jpg https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/cheetah1.jpg\n", "!wget -q -O files/lion.jpg https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/lion.jpg\n", "!wget -q -O files/logo.png https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/logo.png\n", "!wget -q -O files/time.csv https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/time.csv\n", "!wget -q -O files/titanic.csv https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/titanic.csv\n", "!wget -q -O files/tower.jpg https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/tower.jpg\n", "!wget -q -O files/world.mp4 https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/world.mp4"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import os\n", "import json\n", "\n", "import numpy as np\n", "\n", "import gradio as gr\n", "\n", "CHOICES = [\"foo\", \"bar\", \"baz\"]\n", "JSONOBJ = \"\"\"{\"items\":{\"item\":[{\"id\": \"0001\",\"type\": null,\"is_good\": false,\"ppu\": 0.55,\"batters\":{\"batter\":[{ \"id\": \"1001\", \"type\": \"Regular\" },{ \"id\": \"1002\", \"type\": \"Chocolate\" },{ \"id\": \"1003\", \"type\": \"Blueberry\" },{ \"id\": \"1004\", \"type\": \"Devil's Food\" }]},\"topping\":[{ \"id\": \"5001\", \"type\": \"None\" },{ \"id\": \"5002\", \"type\": \"Glazed\" },{ \"id\": \"5005\", \"type\": \"Sugar\" },{ \"id\": \"5007\", \"type\": \"Powdered Sugar\" },{ \"id\": \"5006\", \"type\": \"Chocolate with Sprinkles\" },{ \"id\": \"5003\", \"type\": \"Chocolate\" },{ \"id\": \"5004\", \"type\": \"Maple\" }]}]}}\"\"\"\n", "\n", "\n", "def fn(\n", " text1,\n", " text2,\n", " num,\n", " slider1,\n", " slider2,\n", " single_checkbox,\n", " checkboxes,\n", " radio,\n", " dropdown,\n", " multi_dropdown,\n", " im1,\n", " im2,\n", " im3,\n", " im4,\n", " video,\n", " audio1,\n", " audio2,\n", " file,\n", " df1,\n", "):\n", " return (\n", " (text1 if single_checkbox else text2)\n", " + \", selected:\"\n", " + \", \".join(checkboxes), # Text\n", " {\n", " \"positive\": num / (num + slider1 + slider2),\n", " \"negative\": slider1 / (num + slider1 + slider2),\n", " \"neutral\": slider2 / (num + slider1 + slider2),\n", " }, # Label\n", " (audio1[0], np.flipud(audio1[1]))\n", " if audio1 is not None\n", " else os.path.join(os.path.abspath(''), \"files/cantina.wav\"), # Audio\n", " np.flipud(im1)\n", " if im1 is not None\n", " else os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"), # Image\n", " video\n", " if video is not None\n", " else os.path.join(os.path.abspath(''), \"files/world.mp4\"), # Video\n", " [\n", " (\"The\", \"art\"),\n", " (\"quick brown\", \"adj\"),\n", " (\"fox\", \"nn\"),\n", " (\"jumped\", \"vrb\"),\n", " (\"testing testing testing\", None),\n", " (\"over\", \"prp\"),\n", " (\"the\", \"art\"),\n", " (\"testing\", None),\n", " (\"lazy\", \"adj\"),\n", " (\"dogs\", \"nn\"),\n", " (\".\", \"punc\"),\n", " ]\n", " + [(f\"test {x}\", f\"test {x}\") for x in range(10)], # HighlightedText\n", " # [(\"The testing testing testing\", None), (\"quick brown\", 0.2), (\"fox\", 1), (\"jumped\", -1), (\"testing testing testing\", 0), (\"over\", 0), (\"the\", 0), (\"testing\", 0), (\"lazy\", 1), (\"dogs\", 0), (\".\", 1)] + [(f\"test {x}\", x/10) for x in range(-10, 10)], # HighlightedText\n", " [\n", " (\"The testing testing testing\", None),\n", " (\"over\", 0.6),\n", " (\"the\", 0.2),\n", " (\"testing\", None),\n", " (\"lazy\", -0.1),\n", " (\"dogs\", 0.4),\n", " (\".\", 0),\n", " ]\n", " + [(f\"test\", x / 10) for x in range(-10, 10)], # HighlightedText\n", " json.loads(JSONOBJ), # JSON\n", " \"\", # HTML\n", " os.path.join(os.path.abspath(''), \"files/titanic.csv\"),\n", " df1, # Dataframe\n", " np.random.randint(0, 10, (4, 4)), # Dataframe\n", " )\n", "\n", "\n", "demo = gr.Interface(\n", " fn,\n", " inputs=[\n", " gr.Textbox(value=\"Lorem ipsum\", label=\"Textbox\"),\n", " gr.Textbox(lines=3, placeholder=\"Type here..\", label=\"Textbox 2\"),\n", " gr.Number(label=\"Number\", value=42),\n", " gr.Slider(10, 20, value=15, label=\"Slider: 10 - 20\"),\n", " gr.Slider(maximum=20, step=0.04, label=\"Slider: step @ 0.04\"),\n", " gr.Checkbox(label=\"Checkbox\"),\n", " gr.CheckboxGroup(label=\"CheckboxGroup\", choices=CHOICES, value=CHOICES[0:2]),\n", " gr.Radio(label=\"Radio\", choices=CHOICES, value=CHOICES[2]),\n", " gr.Dropdown(label=\"Dropdown\", choices=CHOICES),\n", " gr.Dropdown(\n", " label=\"Multiselect Dropdown (Max choice: 2)\",\n", " choices=CHOICES,\n", " multiselect=True,\n", " max_choices=2,\n", " ),\n", " gr.Image(label=\"Image\"),\n", " gr.Image(label=\"Image w/ Cropper\", tool=\"select\"),\n", " gr.Image(label=\"Sketchpad\", source=\"canvas\"),\n", " gr.Image(label=\"Webcam\", source=\"webcam\"),\n", " gr.Video(label=\"Video\"),\n", " gr.Audio(label=\"Audio\"),\n", " gr.Audio(label=\"Microphone\", source=\"microphone\"),\n", " gr.File(label=\"File\"),\n", " gr.Dataframe(label=\"Dataframe\", headers=[\"Name\", \"Age\", \"Gender\"]),\n", " ],\n", " outputs=[\n", " gr.Textbox(label=\"Textbox\"),\n", " gr.Label(label=\"Label\"),\n", " gr.Audio(label=\"Audio\"),\n", " gr.Image(label=\"Image\"),\n", " gr.Video(label=\"Video\"),\n", " gr.HighlightedText(\n", " label=\"HighlightedText\", color_map={\"punc\": \"pink\", \"test 0\": \"blue\"}\n", " ),\n", " gr.HighlightedText(label=\"HighlightedText\", show_legend=True),\n", " gr.JSON(label=\"JSON\"),\n", " gr.HTML(label=\"HTML\"),\n", " gr.File(label=\"File\"),\n", " gr.Dataframe(label=\"Dataframe\"),\n", " gr.Dataframe(label=\"Numpy\"),\n", " ],\n", " examples=[\n", " [\n", " \"the quick brown fox\",\n", " \"jumps over the lazy dog\",\n", " 10,\n", " 12,\n", " 4,\n", " True,\n", " [\"foo\", \"baz\"],\n", " \"baz\",\n", " \"bar\",\n", " [\"foo\", \"bar\"],\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/world.mp4\"),\n", " os.path.join(os.path.abspath(''), \"files/cantina.wav\"),\n", " os.path.join(os.path.abspath(''), \"files/cantina.wav\"),\n", " os.path.join(os.path.abspath(''), \"files/titanic.csv\"),\n", " [[1, 2, 3, 4], [4, 5, 6, 7], [8, 9, 1, 2], [3, 4, 5, 6]],\n", " ]\n", " ]\n", " * 3,\n", " title=\"Kitchen Sink\",\n", " description=\"Try out all the components!\",\n", " article=\"Learn more about [Gradio](http://gradio.app)\",\n", " cache_examples=True,\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/kitchen_sink/run.py b/demo/kitchen_sink/run.py index 70a27150060f..bc5343244c8c 100755 --- a/demo/kitchen_sink/run.py +++ b/demo/kitchen_sink/run.py @@ -29,7 +29,6 @@ def fn( audio2, file, df1, - df2, ): return ( (text1 if single_checkbox else text2) @@ -81,7 +80,6 @@ def fn( os.path.join(os.path.dirname(__file__), "files/titanic.csv"), df1, # Dataframe np.random.randint(0, 10, (4, 4)), # Dataframe - df2, # Timeseries ) @@ -97,7 +95,12 @@ def fn( gr.CheckboxGroup(label="CheckboxGroup", choices=CHOICES, value=CHOICES[0:2]), gr.Radio(label="Radio", choices=CHOICES, value=CHOICES[2]), gr.Dropdown(label="Dropdown", choices=CHOICES), - gr.Dropdown(label="Multiselect Dropdown (Max choice: 2)", choices=CHOICES, multiselect=True, max_choices=2), + gr.Dropdown( + label="Multiselect Dropdown (Max choice: 2)", + choices=CHOICES, + multiselect=True, + max_choices=2, + ), gr.Image(label="Image"), gr.Image(label="Image w/ Cropper", tool="select"), gr.Image(label="Sketchpad", source="canvas"), @@ -107,7 +110,6 @@ def fn( gr.Audio(label="Microphone", source="microphone"), gr.File(label="File"), gr.Dataframe(label="Dataframe", headers=["Name", "Age", "Gender"]), - gr.Timeseries(x="time", y=["price", "value"], colors=["pink", "purple"]), ], outputs=[ gr.Textbox(label="Textbox"), @@ -115,8 +117,8 @@ def fn( gr.Audio(label="Audio"), gr.Image(label="Image"), gr.Video(label="Video"), - gr.HighlightedText(label="HighlightedText", - color_map={"punc": "pink", "test 0": "blue"} + gr.HighlightedText( + label="HighlightedText", color_map={"punc": "pink", "test 0": "blue"} ), gr.HighlightedText(label="HighlightedText", show_legend=True), gr.JSON(label="JSON"), @@ -124,7 +126,6 @@ def fn( gr.File(label="File"), gr.Dataframe(label="Dataframe"), gr.Dataframe(label="Numpy"), - gr.Timeseries(x="time", y=["price", "value"], label="Timeseries"), ], examples=[ [ @@ -147,7 +148,6 @@ def fn( os.path.join(os.path.dirname(__file__), "files/cantina.wav"), os.path.join(os.path.dirname(__file__), "files/titanic.csv"), [[1, 2, 3, 4], [4, 5, 6, 7], [8, 9, 1, 2], [3, 4, 5, 6]], - os.path.join(os.path.dirname(__file__), "files/time.csv"), ] ] * 3, @@ -158,4 +158,4 @@ def fn( ) if __name__ == "__main__": - demo.launch() \ No newline at end of file + demo.launch() diff --git a/demo/kitchen_sink_random/run.py b/demo/kitchen_sink_random/run.py index 41c92d8a8db7..5b4440e94984 100644 --- a/demo/kitchen_sink_random/run.py +++ b/demo/kitchen_sink_random/run.py @@ -59,9 +59,7 @@ {"random_number_rows": range(random.randint(0, 10))} ) ), - gr.Timeseries(value=lambda: os.path.join(file_dir, "time.csv")), gr.State(value=lambda: random.choice(string.ascii_lowercase)), - gr.Button(value=lambda: random.choice(["Run", "Go", "predict"])), gr.ColorPicker(value=lambda: random.choice(["#000000", "#ff0000", "#0000FF"])), gr.Label(value=lambda: random.choice(["Pedestrian", "Car", "Cyclist"])), gr.HighlightedText( diff --git a/demo/model3D/run.ipynb b/demo/model3D/run.ipynb index 7ea65d26b593..a807a5166a4b 100644 --- a/demo/model3D/run.ipynb +++ b/demo/model3D/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: model3D"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/Bunny.obj https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/Bunny.obj\n", "!wget -q -O files/Duck.glb https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/Duck.glb\n", "!wget -q -O files/Fox.gltf https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/Fox.gltf\n", "!wget -q -O files/face.obj https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/face.obj\n", "!wget -q -O files/source.txt https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/source.txt"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "\n", "\n", "def load_mesh(mesh_file_name):\n", " return mesh_file_name\n", "\n", "\n", "demo = gr.Interface(\n", " fn=load_mesh,\n", " inputs=gr.Model3D(),\n", " outputs=gr.Model3D(\n", " clear_color=[0.0, 0.0, 0.0, 0.0], label=\"3D Model\"),\n", " examples=[\n", " [os.path.join(os.path.abspath(''), \"files/Bunny.obj\")],\n", " [os.path.join(os.path.abspath(''), \"files/Duck.glb\")],\n", " [os.path.join(os.path.abspath(''), \"files/Fox.gltf\")],\n", " [os.path.join(os.path.abspath(''), \"files/face.obj\")],\n", " ],\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: model3D"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/Bunny.obj https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/Bunny.obj\n", "!wget -q -O files/Duck.glb https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/Duck.glb\n", "!wget -q -O files/Fox.gltf https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/Fox.gltf\n", "!wget -q -O files/face.obj https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/face.obj\n", "!wget -q -O files/source.txt https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/source.txt"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "\n", "\n", "def load_mesh(mesh_file_name):\n", " return mesh_file_name\n", "\n", "\n", "demo = gr.Interface(\n", " fn=load_mesh,\n", " inputs=gr.Model3D(),\n", " outputs=gr.Model3D(\n", " clear_color=[0.0, 0.0, 0.0, 0.0], label=\"3D Model\"),\n", " examples=[\n", " [os.path.join(os.path.abspath(''), \"files/Bunny.obj\")],\n", " [os.path.join(os.path.abspath(''), \"files/Duck.glb\")],\n", " [os.path.join(os.path.abspath(''), \"files/Fox.gltf\")],\n", " [os.path.join(os.path.abspath(''), \"files/face.obj\")],\n", " ],\n", " cache_examples=True\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/model3D/run.py b/demo/model3D/run.py index 5a765cb19078..e26752f8d5dd 100644 --- a/demo/model3D/run.py +++ b/demo/model3D/run.py @@ -17,6 +17,7 @@ def load_mesh(mesh_file_name): [os.path.join(os.path.dirname(__file__), "files/Fox.gltf")], [os.path.join(os.path.dirname(__file__), "files/face.obj")], ], + cache_examples=True ) if __name__ == "__main__": diff --git a/demo/reverse_audio_2/requirements.txt b/demo/reverse_audio_2/requirements.txt new file mode 100644 index 000000000000..296d654528b7 --- /dev/null +++ b/demo/reverse_audio_2/requirements.txt @@ -0,0 +1 @@ +numpy \ No newline at end of file diff --git a/demo/reverse_audio_2/run.ipynb b/demo/reverse_audio_2/run.ipynb new file mode 100644 index 000000000000..e0d50ab09838 --- /dev/null +++ b/demo/reverse_audio_2/run.ipynb @@ -0,0 +1 @@ +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: reverse_audio_2"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio numpy"]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import numpy as np\n", "\n", "\n", "def reverse_audio(audio):\n", " sr, data = audio\n", " return (sr, np.flipud(data))\n", "\n", "\n", "demo = gr.Interface(fn=reverse_audio, \n", " inputs=\"microphone\", \n", " outputs=\"audio\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/reverse_audio_2/run.py b/demo/reverse_audio_2/run.py new file mode 100644 index 000000000000..6a10b40cc4d7 --- /dev/null +++ b/demo/reverse_audio_2/run.py @@ -0,0 +1,15 @@ +import gradio as gr +import numpy as np + + +def reverse_audio(audio): + sr, data = audio + return (sr, np.flipud(data)) + + +demo = gr.Interface(fn=reverse_audio, + inputs="microphone", + outputs="audio") + +if __name__ == "__main__": + demo.launch() diff --git a/demo/stt_or_tts/run.ipynb b/demo/stt_or_tts/run.ipynb index ae4ef31d6ae0..5a5c0a382076 100644 --- a/demo/stt_or_tts/run.ipynb +++ b/demo/stt_or_tts/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: stt_or_tts"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "tts_examples = [\n", " \"I love learning machine learning\",\n", " \"How do you do?\",\n", "]\n", "\n", "tts_demo = gr.load(\n", " \"huggingface/facebook/fastspeech2-en-ljspeech\",\n", " title=None,\n", " examples=tts_examples,\n", " description=\"Give me something to say!\",\n", ")\n", "\n", "stt_demo = gr.load(\n", " \"huggingface/facebook/wav2vec2-base-960h\",\n", " title=None,\n", " inputs=\"mic\",\n", " description=\"Let me try to guess what you're saying!\",\n", ")\n", "\n", "demo = gr.TabbedInterface([tts_demo, stt_demo], [\"Text-to-speech\", \"Speech-to-text\"])\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: stt_or_tts"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "tts_examples = [\n", " \"I love learning machine learning\",\n", " \"How do you do?\",\n", "]\n", "\n", "tts_demo = gr.load(\n", " \"huggingface/facebook/fastspeech2-en-ljspeech\",\n", " title=None,\n", " examples=tts_examples,\n", " description=\"Give me something to say!\",\n", " cache_examples=False\n", ")\n", "\n", "stt_demo = gr.load(\n", " \"huggingface/facebook/wav2vec2-base-960h\",\n", " title=None,\n", " inputs=\"mic\",\n", " description=\"Let me try to guess what you're saying!\",\n", ")\n", "\n", "demo = gr.TabbedInterface([tts_demo, stt_demo], [\"Text-to-speech\", \"Speech-to-text\"])\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/stt_or_tts/run.py b/demo/stt_or_tts/run.py index ed6a1adc32cf..98c97dd37611 100644 --- a/demo/stt_or_tts/run.py +++ b/demo/stt_or_tts/run.py @@ -10,6 +10,7 @@ title=None, examples=tts_examples, description="Give me something to say!", + cache_examples=False ) stt_demo = gr.load( diff --git a/demo/tabbed_interface_lite/run.ipynb b/demo/tabbed_interface_lite/run.ipynb new file mode 100644 index 000000000000..4f3b3faa767d --- /dev/null +++ b/demo/tabbed_interface_lite/run.ipynb @@ -0,0 +1 @@ +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: tabbed_interface_lite"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "hello_world = gr.Interface(lambda name: \"Hello \" + name, \"text\", \"text\")\n", "bye_world = gr.Interface(lambda name: \"Bye \" + name, \"text\", \"text\")\n", "\n", "demo = gr.TabbedInterface([hello_world, bye_world], [\"Hello World\", \"Bye World\"])\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/tabbed_interface_lite/run.py b/demo/tabbed_interface_lite/run.py new file mode 100644 index 000000000000..5e804c927cd3 --- /dev/null +++ b/demo/tabbed_interface_lite/run.py @@ -0,0 +1,9 @@ +import gradio as gr + +hello_world = gr.Interface(lambda name: "Hello " + name, "text", "text") +bye_world = gr.Interface(lambda name: "Bye " + name, "text", "text") + +demo = gr.TabbedInterface([hello_world, bye_world], ["Hello World", "Bye World"]) + +if __name__ == "__main__": + demo.launch() \ No newline at end of file diff --git a/demo/timeseries_component/run.ipynb b/demo/timeseries_component/run.ipynb deleted file mode 100644 index 5cff7ead96b5..000000000000 --- a/demo/timeseries_component/run.ipynb +++ /dev/null @@ -1 +0,0 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: timeseries_component"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr \n", "\n", "with gr.Blocks() as demo:\n", " gr.Timeseries()\n", "\n", "demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/timeseries_component/run.py b/demo/timeseries_component/run.py deleted file mode 100644 index 0e65d8907fed..000000000000 --- a/demo/timeseries_component/run.py +++ /dev/null @@ -1,6 +0,0 @@ -import gradio as gr - -with gr.Blocks() as demo: - gr.Timeseries() - -demo.launch() \ No newline at end of file diff --git a/demo/video_component/run.ipynb b/demo/video_component/run.ipynb index 290e9a4d2121..4387ae475020 100644 --- a/demo/video_component/run.ipynb +++ b/demo/video_component/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: video_component"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/a.mp4 https://github.com/gradio-app/gradio/raw/main/demo/video_component/files/a.mp4\n", "!wget -q -O files/b.mp4 https://github.com/gradio-app/gradio/raw/main/demo/video_component/files/b.mp4\n", "!wget -q -O files/world.mp4 https://github.com/gradio-app/gradio/raw/main/demo/video_component/files/world.mp4"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "\n", "\n", "a = os.path.join(os.path.abspath(''), \"files/world.mp4\") # Video\n", "b = os.path.join(os.path.abspath(''), \"files/a.mp4\") # Video\n", "c = os.path.join(os.path.abspath(''), \"files/b.mp4\") # Video\n", "\n", "\n", "demo = gr.Interface(\n", " fn=lambda x: x,\n", " inputs=gr.Video(type=\"file\"),\n", " outputs=gr.Video(),\n", " examples=[\n", " [a],\n", " [b],\n", " [c],\n", " ],\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: video_component"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/a.mp4 https://github.com/gradio-app/gradio/raw/main/demo/video_component/files/a.mp4\n", "!wget -q -O files/b.mp4 https://github.com/gradio-app/gradio/raw/main/demo/video_component/files/b.mp4\n", "!wget -q -O files/world.mp4 https://github.com/gradio-app/gradio/raw/main/demo/video_component/files/world.mp4"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "\n", "\n", "a = os.path.join(os.path.abspath(''), \"files/world.mp4\") # Video\n", "b = os.path.join(os.path.abspath(''), \"files/a.mp4\") # Video\n", "c = os.path.join(os.path.abspath(''), \"files/b.mp4\") # Video\n", "\n", "\n", "demo = gr.Interface(\n", " fn=lambda x: x,\n", " inputs=gr.Video(),\n", " outputs=gr.Video(),\n", " examples=[\n", " [a],\n", " [b],\n", " [c],\n", " ],\n", " cache_examples=True\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/video_component/run.py b/demo/video_component/run.py index 254a1fcfbb27..d522aa49cdcf 100644 --- a/demo/video_component/run.py +++ b/demo/video_component/run.py @@ -9,13 +9,14 @@ demo = gr.Interface( fn=lambda x: x, - inputs=gr.Video(type="file"), + inputs=gr.Video(), outputs=gr.Video(), examples=[ [a], [b], [c], ], + cache_examples=True ) if __name__ == "__main__": diff --git a/demo/video_identity/run.ipynb b/demo/video_identity/run.ipynb index 2cbc57bda2df..3ed71cc4b12c 100644 --- a/demo/video_identity/run.ipynb +++ b/demo/video_identity/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: video_identity"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('video')\n", "!wget -q -O video/video_sample.mp4 https://github.com/gradio-app/gradio/raw/main/demo/video_identity/video/video_sample.mp4"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "\n", "\n", "def video_identity(video):\n", " return video\n", "\n", "\n", "demo = gr.Interface(video_identity, \n", " gr.Video(), \n", " \"playable_video\", \n", " examples=[\n", " os.path.join(os.path.abspath(''), \n", " \"video/video_sample.mp4\")], \n", " cache_examples=True)\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: video_identity"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('video')\n", "!wget -q -O video/video_sample.mp4 https://github.com/gradio-app/gradio/raw/main/demo/video_identity/video/video_sample.mp4"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "\n", "\n", "def video_identity(video):\n", " return video\n", "\n", "\n", "demo = gr.Interface(video_identity, \n", " gr.Video(), \n", " \"playable_video\", \n", " examples=[\n", " os.path.join(os.path.abspath(''), \n", " \"video/video_sample.mp4\")], \n", " cache_examples=True)\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/video_identity/run.py b/demo/video_identity/run.py index 152dab9b0e83..206f47c7c2f6 100644 --- a/demo/video_identity/run.py +++ b/demo/video_identity/run.py @@ -15,4 +15,4 @@ def video_identity(video): cache_examples=True) if __name__ == "__main__": - demo.launch() + demo.launch() \ No newline at end of file diff --git a/demo/video_identity_2/run.ipynb b/demo/video_identity_2/run.ipynb new file mode 100644 index 000000000000..b2333281ba36 --- /dev/null +++ b/demo/video_identity_2/run.ipynb @@ -0,0 +1 @@ +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: video_identity_2"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "def video_identity(video):\n", " return video\n", "\n", "\n", "demo = gr.Interface(video_identity, \n", " gr.Video(), \n", " \"playable_video\", \n", " )\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/video_identity_2/run.py b/demo/video_identity_2/run.py new file mode 100644 index 000000000000..095a1f8eed61 --- /dev/null +++ b/demo/video_identity_2/run.py @@ -0,0 +1,13 @@ +import gradio as gr + +def video_identity(video): + return video + + +demo = gr.Interface(video_identity, + gr.Video(), + "playable_video", + ) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/video_subtitle/run.ipynb b/demo/video_subtitle/run.ipynb index ea6f1959bfb1..5c626ab99024 100644 --- a/demo/video_subtitle/run.ipynb +++ b/demo/video_subtitle/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: video_subtitle"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/a.mp4 https://github.com/gradio-app/gradio/raw/main/demo/video_subtitle/files/a.mp4\n", "!wget -q -O files/b.mp4 https://github.com/gradio-app/gradio/raw/main/demo/video_subtitle/files/b.mp4\n", "!wget -q -O files/s1.srt https://github.com/gradio-app/gradio/raw/main/demo/video_subtitle/files/s1.srt\n", "!wget -q -O files/s2.vtt https://github.com/gradio-app/gradio/raw/main/demo/video_subtitle/files/s2.vtt"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "\n", "a = os.path.join(os.path.abspath(''), \"files/a.mp4\") # Video\n", "b = os.path.join(os.path.abspath(''), \"files/b.mp4\") # Video\n", "s1 = os.path.join(os.path.abspath(''), \"files/s1.srt\") # Subtitle\n", "s2 = os.path.join(os.path.abspath(''), \"files/s2.vtt\") # Subtitle\n", "\n", "\n", "def video_demo(video, subtitle=None):\n", " if subtitle is None:\n", " return video\n", "\n", " return [video, subtitle.name]\n", "\n", "\n", "demo = gr.Interface(\n", " fn=video_demo,\n", " inputs=[\n", " gr.Video(type=\"file\", label=\"In\", interactive=True),\n", " gr.File(label=\"Subtitle\", file_types=[\".srt\", \".vtt\"]),\n", " ],\n", " outputs=gr.Video(label=\"Out\"),\n", " examples=[\n", " [a, s1],\n", " [b, s2],\n", " [a, None],\n", " ],\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: video_subtitle"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/a.mp4 https://github.com/gradio-app/gradio/raw/main/demo/video_subtitle/files/a.mp4\n", "!wget -q -O files/b.mp4 https://github.com/gradio-app/gradio/raw/main/demo/video_subtitle/files/b.mp4\n", "!wget -q -O files/s1.srt https://github.com/gradio-app/gradio/raw/main/demo/video_subtitle/files/s1.srt\n", "!wget -q -O files/s2.vtt https://github.com/gradio-app/gradio/raw/main/demo/video_subtitle/files/s2.vtt"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "\n", "a = os.path.join(os.path.abspath(''), \"files/a.mp4\") # Video\n", "b = os.path.join(os.path.abspath(''), \"files/b.mp4\") # Video\n", "s1 = os.path.join(os.path.abspath(''), \"files/s1.srt\") # Subtitle\n", "s2 = os.path.join(os.path.abspath(''), \"files/s2.vtt\") # Subtitle\n", "\n", "\n", "def video_demo(video, subtitle=None):\n", " if subtitle is None:\n", " return video\n", "\n", " return [video, subtitle.name]\n", "\n", "\n", "demo = gr.Interface(\n", " fn=video_demo,\n", " inputs=[\n", " gr.Video(label=\"In\", interactive=True),\n", " gr.File(label=\"Subtitle\", file_types=[\".srt\", \".vtt\"]),\n", " ],\n", " outputs=gr.Video(label=\"Out\"),\n", " examples=[\n", " [a, s1],\n", " [b, s2],\n", " [a, None],\n", " ],\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/video_subtitle/run.py b/demo/video_subtitle/run.py index 8067f3d0bb67..99e4a522a93e 100644 --- a/demo/video_subtitle/run.py +++ b/demo/video_subtitle/run.py @@ -17,7 +17,7 @@ def video_demo(video, subtitle=None): demo = gr.Interface( fn=video_demo, inputs=[ - gr.Video(type="file", label="In", interactive=True), + gr.Video(label="In", interactive=True), gr.File(label="Subtitle", file_types=[".srt", ".vtt"]), ], outputs=gr.Video(label="Out"), diff --git a/gradio/CHANGELOG.md b/gradio/CHANGELOG.md index 837d12e6d8f5..d2c34e0a3065 100644 --- a/gradio/CHANGELOG.md +++ b/gradio/CHANGELOG.md @@ -1,17 +1,41 @@ # gradio -## 3.49.0 +## 3.45.0-beta.13 ### Features -- [#5953](https://github.com/gradio-app/gradio/pull/5953) [`921334526`](https://github.com/gradio-app/gradio/commit/921334526ff1ed0fc75c20db5d43733004c7d6ae) - Lite: Support the custom HTML element syntax ``. Thanks [@whitphx](https://github.com/whitphx)! +- [#5964](https://github.com/gradio-app/gradio/pull/5964) [`5fbda0bd2`](https://github.com/gradio-app/gradio/commit/5fbda0bd2b2bbb2282249b8875d54acf87cd7e84) - Wasm release. Thanks [@pngwn](https://github.com/pngwn)! + +## 3.45.0-beta.12 + +### Features + +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - V4: Some misc fixes. Thanks [@pngwn](https://github.com/pngwn)! +- [#5960](https://github.com/gradio-app/gradio/pull/5960) [`319c30f3f`](https://github.com/gradio-app/gradio/commit/319c30f3fccf23bfe1da6c9b132a6a99d59652f7) - rererefactor frontend files. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Add host to dev mode for vite. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`d2314e53b`](https://github.com/gradio-app/gradio/commit/d2314e53bc088ff6f307a122a9a01bafcdcff5c2) - BugFix: Make FileExplorer Component Templateable. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Use tags to identify custom component dirs and ignore uninstalled components. Thanks [@pngwn](https://github.com/pngwn)! - [#5956](https://github.com/gradio-app/gradio/pull/5956) [`f769876e0`](https://github.com/gradio-app/gradio/commit/f769876e0fa62336425c4e8ada5e09f38353ff01) - Apply formatter (and small refactoring) to the Lite-related frontend code. Thanks [@whitphx](https://github.com/whitphx)! -- [#5972](https://github.com/gradio-app/gradio/pull/5972) [`11a300791`](https://github.com/gradio-app/gradio/commit/11a3007916071f0791844b0a37f0fb4cec69cea3) - Lite: Support opening the entrypoint HTML page directly in browser via the `file:` protocol. Thanks [@whitphx](https://github.com/whitphx)! +- [#5938](https://github.com/gradio-app/gradio/pull/5938) [`13ed8a485`](https://github.com/gradio-app/gradio/commit/13ed8a485d5e31d7d75af87fe8654b661edcca93) - V4: Use beta release versions for '@gradio' packages. Thanks [@freddyaboulton](https://github.com/freddyaboulton)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Adds the ability to build the frontend and backend of custom components in preparation for publishing to pypi using `gradio_component build`. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Fix deployed demos on v4 branch. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Set api=False for cancel events. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Use full path to executables in CLI. Thanks [@pngwn](https://github.com/pngwn)! +- [#5949](https://github.com/gradio-app/gradio/pull/5949) [`1c390f101`](https://github.com/gradio-app/gradio/commit/1c390f10199142a41722ba493a0c86b58245da15) - Merge main again. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Simplify how files are handled in components in 4.0. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Name Endpoints if api_name is None. Thanks [@pngwn](https://github.com/pngwn)! +- [#5937](https://github.com/gradio-app/gradio/pull/5937) [`dcf13d750`](https://github.com/gradio-app/gradio/commit/dcf13d750b1465f905e062a1368ba754446cc23f) - V4: Update Component pyi file. Thanks [@freddyaboulton](https://github.com/freddyaboulton)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Rename gradio_component to gradio component. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - V4: Use async version of shutil in upload route. Thanks [@pngwn](https://github.com/pngwn)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - V4: Set cache dir for some component tests. Thanks [@pngwn](https://github.com/pngwn)! - [#5894](https://github.com/gradio-app/gradio/pull/5894) [`fee3d527e`](https://github.com/gradio-app/gradio/commit/fee3d527e83a615109cf937f6ca0a37662af2bb6) - Adds `column_widths` to `gr.Dataframe` and hide overflowing text when `wrap=False`. Thanks [@abidlabs](https://github.com/abidlabs)! ### Fixes +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Better logs in dev mode. Thanks [@pngwn](https://github.com/pngwn)! +- [#5946](https://github.com/gradio-app/gradio/pull/5946) [`d0cc6b136`](https://github.com/gradio-app/gradio/commit/d0cc6b136fd59121f74d0c5a1a4b51740ffaa838) - fixup. Thanks [@pngwn](https://github.com/pngwn)! - [#5944](https://github.com/gradio-app/gradio/pull/5944) [`465f58957`](https://github.com/gradio-app/gradio/commit/465f58957f70c7cf3e894beef8a117b28339e3c1) - Show empty JSON icon when `value` is `null`. Thanks [@hannahblair](https://github.com/hannahblair)! +- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`85ba6de13`](https://github.com/gradio-app/gradio/commit/85ba6de136a45b3e92c74e410bb27e3cbe7138d7) - Reinstate types that were removed in error in #5832. Thanks [@pngwn](https://github.com/pngwn)! ## 3.48.0 diff --git a/gradio/__init__.py b/gradio/__init__.py index e615c9578207..c1170ea531f2 100644 --- a/gradio/__init__.py +++ b/gradio/__init__.py @@ -1,13 +1,14 @@ import json +import gradio._simple_templates import gradio.components as components -import gradio.inputs as inputs -import gradio.outputs as outputs +import gradio.layouts as layouts import gradio.processing_utils import gradio.templates import gradio.themes as themes from gradio.blocks import Blocks from gradio.chat_interface import ChatInterface +from gradio.cli import deploy from gradio.components import ( HTML, JSON, @@ -16,7 +17,6 @@ Audio, BarPlot, Button, - Carousel, Chatbot, Checkbox, CheckboxGroup, @@ -36,7 +36,6 @@ HighlightedText, Highlightedtext, Image, - Interpretation, Json, Label, LinePlot, @@ -50,29 +49,23 @@ ScatterPlot, Slider, State, - StatusTracker, Text, Textbox, - TimeSeries, - Timeseries, UploadButton, - Variable, Video, component, ) -from gradio.deploy_space import deploy -from gradio.events import LikeData, SelectData, on +from gradio.data_classes import FileData +from gradio.events import EventData, LikeData, SelectData, on from gradio.exceptions import Error from gradio.external import load from gradio.flagging import ( CSVLogger, FlaggingCallback, - HuggingFaceDatasetJSONSaver, HuggingFaceDatasetSaver, SimpleCSVLogger, ) from gradio.helpers import ( - EventData, Info, Progress, Warning, @@ -83,7 +76,7 @@ from gradio.helpers import create_examples as Examples # noqa: N812 from gradio.interface import Interface, TabbedInterface, close_all from gradio.ipython_ext import load_ipython_extension -from gradio.layouts import Accordion, Box, Column, Group, Row, Tab, TabItem, Tabs +from gradio.layouts import Accordion, Column, Group, Row, Tab, TabItem, Tabs from gradio.mix import Parallel, Series from gradio.oauth import OAuthProfile from gradio.routes import Request, mount_gradio_app diff --git a/gradio/_simple_templates/__init__.py b/gradio/_simple_templates/__init__.py new file mode 100644 index 000000000000..8c61b4e4b15a --- /dev/null +++ b/gradio/_simple_templates/__init__.py @@ -0,0 +1,4 @@ +from .simpledropdown import SimpleDropdown +from .simpletextbox import SimpleTextbox + +__all__ = ["SimpleDropdown", "SimpleTextbox"] diff --git a/gradio/_simple_templates/simpledropdown.py b/gradio/_simple_templates/simpledropdown.py new file mode 100644 index 000000000000..14773dfdf98e --- /dev/null +++ b/gradio/_simple_templates/simpledropdown.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import warnings +from typing import Any, Callable + +from gradio.components.base import FormComponent +from gradio.events import Events + + +class SimpleDropdown(FormComponent): + """ + Creates a very simple dropdown listing choices from which entries can be selected. + Preprocessing: Preprocessing: passes the value of the selected dropdown entry as a {str}. + Postprocessing: expects a {str} corresponding to the value of the dropdown entry to be selected. + Examples-format: a {str} representing the drop down value to select. + Demos: sentence_builder, titanic_survival + """ + + EVENTS = [Events.change, Events.input, Events.select] + + def __init__( + self, + choices: list[str | int | float | tuple[str, str | int | float]] | None = None, + *, + value: str | int | float | Callable | None = None, + label: str | None = None, + info: str | None = None, + every: float | None = None, + show_label: bool | None = None, + scale: int | None = None, + min_width: int = 160, + interactive: bool | None = None, + visible: bool = True, + elem_id: str | None = None, + elem_classes: list[str] | str | None = None, + **kwargs, + ): + """ + Parameters: + choices: A list of string options to choose from. An option can also be a tuple of the form (name, value), where name is the displayed name of the dropdown choice and value is the value to be passed to the function, or returned by the function. + value: default value selected in dropdown. If None, no value is selected by default. If callable, the function will be called whenever the app loads to set the initial value of the component. + label: component name in interface. + info: additional component description. + every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. + show_label: if True, will display label. + scale: relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer. + min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first. + interactive: if True, choices in this dropdown will be selectable; if False, selection will be disabled. If not provided, this is inferred based on whether the component is used as an input or output. + visible: If False, component will be hidden. + elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. + elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + """ + self.choices = ( + # Although we expect choices to be a list of lists, it can be a list of tuples if the Gradio app + # is loaded with gr.load() since Python tuples are converted to lists in JSON. + [tuple(c) if isinstance(c, (tuple, list)) else (str(c), c) for c in choices] + if choices + else [] + ) + super().__init__( + label=label, + info=info, + every=every, + show_label=show_label, + scale=scale, + min_width=min_width, + interactive=interactive, + visible=visible, + elem_id=elem_id, + elem_classes=elem_classes, + value=value, + **kwargs, + ) + + def api_info(self) -> dict[str, Any]: + return { + "type": "string", + "enum": [c[1] for c in self.choices], + } + + def example_inputs(self) -> Any: + return self.choices[0][1] if self.choices else None + + def preprocess(self, x: str | int | float | None) -> str | int | float | None: + """ + Parameters: + x: selected choice + Returns: + selected choice + """ + return x + + def _warn_if_invalid_choice(self, y): + if y not in [value for _, value in self.choices]: + warnings.warn( + f"The value passed into gr.Dropdown() is not in the list of choices. Please update the list of choices to include: {y}." + ) + + def postprocess(self, y): + if y is None: + return None + self._warn_if_invalid_choice(y) + return y + + def as_example(self, input_data): + return next((c[0] for c in self.choices if c[1] == input_data), None) diff --git a/gradio/_simple_templates/simpletextbox.py b/gradio/_simple_templates/simpletextbox.py new file mode 100644 index 000000000000..29ea0ed0cfd5 --- /dev/null +++ b/gradio/_simple_templates/simpletextbox.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +from typing import Any, Callable + +from gradio.components.base import FormComponent +from gradio.events import Events + + +class SimpleTextbox(FormComponent): + """ + Creates a very simple textbox for user to enter string input or display string output. + Preprocessing: passes textbox value as a {str} into the function. + Postprocessing: expects a {str} returned from function and sets textbox value to it. + Examples-format: a {str} representing the textbox input. + """ + + EVENTS = [ + Events.change, + Events.input, + Events.submit, + ] + + def __init__( + self, + value: str | Callable | None = "", + *, + placeholder: str | None = None, + label: str | None = None, + every: float | None = None, + show_label: bool | None = None, + scale: int | None = None, + min_width: int = 160, + interactive: bool | None = None, + visible: bool = True, + rtl: bool = False, + elem_id: str | None = None, + elem_classes: list[str] | str | None = None, + **kwargs, + ): + """ + Parameters: + value: default text to provide in textbox. If callable, the function will be called whenever the app loads to set the initial value of the component. + placeholder: placeholder hint to provide behind textbox. + label: component name in interface. + every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. + show_label: if True, will display label. + scale: relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer. + min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first. + interactive: if True, will be rendered as an editable textbox; if False, editing will be disabled. If not provided, this is inferred based on whether the component is used as an input or output. + visible: If False, component will be hidden. + rtl: If True and `type` is "text", sets the direction of the text to right-to-left (cursor appears on the left of the text). Default is False, which renders cursor on the right. + elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. + elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + """ + self.placeholder = placeholder + self.rtl = rtl + super().__init__( + label=label, + every=every, + show_label=show_label, + scale=scale, + min_width=min_width, + interactive=interactive, + visible=visible, + elem_id=elem_id, + elem_classes=elem_classes, + value=value, + **kwargs, + ) + + def preprocess(self, x: str | None) -> str | None: + """ + Preprocesses input (converts it to a string) before passing it to the function. + Parameters: + x: text + Returns: + text + """ + return None if x is None else str(x) + + def postprocess(self, y: str | None) -> str | None: + """ + Postproccess the function output y by converting it to a str before passing it to the frontend. + Parameters: + y: function output to postprocess. + Returns: + text + """ + return None if y is None else str(y) + + def api_info(self) -> dict[str, Any]: + return {"type": "string"} + + def example_inputs(self) -> Any: + return "Hello!!" diff --git a/gradio/blocks.py b/gradio/blocks.py index 682485d9ed4a..c2b21db20135 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -1,6 +1,7 @@ from __future__ import annotations import copy +import hashlib import inspect import json import os @@ -11,9 +12,7 @@ import time import warnings import webbrowser -from abc import abstractmethod from collections import defaultdict -from functools import wraps from pathlib import Path from types import ModuleType from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Literal, Sequence, cast @@ -21,16 +20,14 @@ import anyio import requests from anyio import CapacityLimiter -from gradio_client import serializing from gradio_client import utils as client_utils from gradio_client.documentation import document, set_documentation_group -from packaging import version from gradio import ( analytics, components, - external, networking, + processing_utils, queueing, routes, strings, @@ -39,14 +36,15 @@ wasm_utils, ) from gradio.context import Context -from gradio.deprecation import check_deprecated_parameters, warn_deprecation +from gradio.data_classes import FileData +from gradio.events import EventData, EventListener, EventListenerMethod from gradio.exceptions import ( DuplicateBlockError, InvalidApiNameError, InvalidBlockError, InvalidComponentError, ) -from gradio.helpers import EventData, create_tracker, skip, special_args +from gradio.helpers import create_tracker, skip, special_args from gradio.state_holder import SessionState from gradio.themes import Default as DefaultTheme from gradio.themes import ThemeClass as Theme @@ -77,8 +75,8 @@ if TYPE_CHECKING: # Only import for type checking (is False at runtime). from fastapi.applications import FastAPI - from gradio.components import Component - from gradio.events import EventListenerMethod + from gradio.components.base import Component + from gradio.events import Dependency BUILT_IN_THEMES: dict[str, Theme] = { t.name: t @@ -92,53 +90,16 @@ } -def in_event_listener(): - from gradio.context import LocalContext - - return LocalContext.in_event_listener.get() - - -def updateable(fn): - @wraps(fn) - def wrapper(*args, **kwargs): - fn_args = inspect.getfullargspec(fn).args - self = args[0] - for i, arg in enumerate(args): - if i == 0 or i >= len(fn_args): # skip self, *args - continue - arg_name = fn_args[i] - kwargs[arg_name] = arg - self.constructor_args = kwargs - if in_event_listener(): - return None - else: - return fn(self, **kwargs) - - return wrapper - - -updated_cls_set = set() - - -class Updateable: - def __new__(cls, *args, **kwargs): - if cls not in updated_cls_set: - cls.__init__ = updateable(cls.__init__) - updated_cls_set.add(cls) - return super().__new__(cls) - - class Block: def __init__( self, *, - render: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, visible: bool = True, - root_url: str | None = None, # URL that is prepended to all file paths - _skip_init_processing: bool = False, # Used for loading from Spaces - **kwargs, ): self._id = Context.id Context.id += 1 @@ -157,7 +118,16 @@ def __init__( if render: self.render() - check_deprecated_parameters(self.__class__.__name__, kwargs=kwargs) + + @property + def skip_api(self): + return False + + @property + def events( + self, + ) -> list[EventListener]: + return getattr(self, "EVENTS", []) def render(self): """ @@ -172,7 +142,7 @@ def render(self): if Context.root_block is not None: Context.root_block.blocks[self._id] = self self.is_rendered = True - if isinstance(self, components.IOComponent): + if isinstance(self, components.Component): Context.root_block.temp_file_sets.append(self.temp_files) return self @@ -218,28 +188,61 @@ def get_config(self): if hasattr(self, parameter.name): value = getattr(self, parameter.name) config[parameter.name] = value + for e in self.events: + to_add = e.config_data() + if to_add: + config = {**config, **to_add} + config.pop("_skip_init_processing", None) + config.pop("render", None) return {**config, "root_url": self.root_url, "name": self.get_block_name()} - @staticmethod - @abstractmethod - def update(**kwargs) -> dict: - return {} - class BlockContext(Block): def __init__( self, + elem_id: str | None = None, + elem_classes: list[str] | str | None = None, visible: bool = True, render: bool = True, - **kwargs, + root_url: str | None = None, + _skip_init_processing: bool = False, ): """ Parameters: + elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. + elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles. visible: If False, this will be hidden but included in the Blocks config file (its visibility can later be updated). render: If False, this will not be included in the Blocks config file at all. + root_url: The remote URL that of the Gradio app that this layout belongs to. Used in `gr.load()`. Should not be set manually. """ self.children: list[Block] = [] - Block.__init__(self, visible=visible, render=render, **kwargs) + Block.__init__( + self, + elem_id=elem_id, + elem_classes=elem_classes, + visible=visible, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, + ) + + TEMPLATE_DIR = "./templates/" + FRONTEND_DIR = "../../frontend/" + + @property + def skip_api(self): + return True + + @classmethod + def get_component_class_id(cls) -> str: + module_name = cls.__module__ + module_path = sys.modules[module_name].__file__ + module_hash = hashlib.md5(f"{cls.__name__}_{module_path}".encode()).hexdigest() + return module_hash + + @property + def component_class_id(self): + return self.get_component_class_id() def add_child(self, child: Block): self.children.append(child) @@ -333,7 +336,9 @@ def __repr__(self): return str(self) -def postprocess_update_dict(block: Block, update_dict: dict, postprocess: bool = True): +def postprocess_update_dict( + block: Component | BlockContext, update_dict: dict, postprocess: bool = True +): """ Converts a dictionary of updates into a format that can be sent to the frontend. E.g. {"__type__": "update", "value": "2", "interactive": False} @@ -354,7 +359,7 @@ def postprocess_update_dict(block: Block, update_dict: dict, postprocess: bool = attr_dict["__type__"] = "update" attr_dict.pop("value", None) if "value" in update_dict: - if not isinstance(block, components.IOComponent): + if not isinstance(block, components.Component): raise InvalidComponentError( f"Component {block.__class__} does not support value" ) @@ -392,147 +397,6 @@ def convert_component_dict_to_list( return predictions -def get_api_info(config: dict, serialize: bool = True): - """ - Gets the information needed to generate the API docs from a Blocks config. - Parameters: - config: a Blocks config dictionary - serialize: If True, returns the serialized version of the typed information. If False, returns the raw version. - """ - api_info = {"named_endpoints": {}, "unnamed_endpoints": {}} - mode = config.get("mode", None) - after_new_format = version.parse(config.get("version", "2.0")) > version.Version( - "3.28.3" - ) - - for d, dependency in enumerate(config["dependencies"]): - dependency_info = {"parameters": [], "returns": []} - skip_endpoint = False - - inputs = dependency["inputs"] - for i in inputs: - for component in config["components"]: - if component["id"] == i: - break - else: - skip_endpoint = True # if component not found, skip endpoint - break - type = component["type"] - if type in client_utils.SKIP_COMPONENTS: - continue - if ( - not component.get("serializer") - and type not in serializing.COMPONENT_MAPPING - ): - skip_endpoint = True # if component not serializable, skip endpoint - break - if type in client_utils.SKIP_COMPONENTS: - continue - label = component["props"].get("label", f"parameter_{i}") - # The config has the most specific API info (taking into account the parameters - # of the component), so we use that if it exists. Otherwise, we fallback to the - # Serializer's API info. - serializer = serializing.COMPONENT_MAPPING[type]() - if component.get("api_info") and after_new_format: - info = component["api_info"] - example = component["example_inputs"]["serialized"] - else: - assert isinstance(serializer, serializing.Serializable) - info = serializer.api_info() - example = serializer.example_inputs()["raw"] - python_info = info["info"] - if serialize and info["serialized_info"]: - python_info = serializer.serialized_info() - if ( - isinstance(serializer, serializing.FileSerializable) - and component["props"].get("file_count", "single") != "single" - ): - python_info = serializer._multiple_file_serialized_info() - - python_type = client_utils.json_schema_to_python_type(python_info) - serializer_name = serializing.COMPONENT_MAPPING[type].__name__ - dependency_info["parameters"].append( - { - "label": label, - "type": info["info"], - "python_type": { - "type": python_type, - "description": python_info.get("description", ""), - }, - "component": type.capitalize(), - "example_input": example, - "serializer": serializer_name, - } - ) - - outputs = dependency["outputs"] - for o in outputs: - for component in config["components"]: - if component["id"] == o: - break - else: - skip_endpoint = True # if component not found, skip endpoint - break - type = component["type"] - if type in client_utils.SKIP_COMPONENTS: - continue - if ( - not component.get("serializer") - and type not in serializing.COMPONENT_MAPPING - ): - skip_endpoint = True # if component not serializable, skip endpoint - break - label = component["props"].get("label", f"value_{o}") - serializer = serializing.COMPONENT_MAPPING[type]() - if component.get("api_info") and after_new_format: - info = component["api_info"] - example = component["example_inputs"]["serialized"] - else: - assert isinstance(serializer, serializing.Serializable) - info = serializer.api_info() - example = serializer.example_inputs()["raw"] - python_info = info["info"] - if serialize and info["serialized_info"]: - python_info = serializer.serialized_info() - if ( - isinstance(serializer, serializing.FileSerializable) - and component["props"].get("file_count", "single") != "single" - ): - python_info = serializer._multiple_file_serialized_info() - python_type = client_utils.json_schema_to_python_type(python_info) - serializer_name = serializing.COMPONENT_MAPPING[type].__name__ - dependency_info["returns"].append( - { - "label": label, - "type": info["info"], - "python_type": { - "type": python_type, - "description": python_info.get("description", ""), - }, - "component": type.capitalize(), - "serializer": serializer_name, - } - ) - - if not dependency["backend_fn"]: - skip_endpoint = True - - if skip_endpoint: - continue - if dependency["api_name"] is not None and dependency["api_name"] is not False: - api_info["named_endpoints"][f"/{dependency['api_name']}"] = dependency_info - elif ( - dependency["api_name"] is False - or mode == "interface" - or mode == "tabbed_interface" - ): - pass # Skip unnamed endpoints in interface mode - else: - api_info["unnamed_endpoints"][str(d)] = dependency_info - - return api_info - - @document("launch", "queue", "integrate", "load") class Blocks(BlockContext): """ @@ -630,7 +494,7 @@ def __init__( else: os.environ["HF_HUB_DISABLE_TELEMETRY"] = "True" super().__init__(render=False, **kwargs) - self.blocks: dict[int, Block] = {} + self.blocks: dict[int, Component | Block] = {} self.fns: list[BlockFunction] = [] self.dependencies = [] self.mode = mode @@ -679,9 +543,14 @@ def __init__( } analytics.initiated_analytics(data) + def get_component(self, id: int) -> Component: + comp = self.blocks[id] + assert isinstance(comp, components.Component), f"{comp}" + return comp + @property def _is_running_in_reload_thread(self): - from gradio.reload import reload_thread + from gradio.cli.commands.reload import reload_thread return getattr(reload_thread, "running_reload", False) @@ -719,10 +588,8 @@ def get_block_instance(id: int) -> Block: else: raise ValueError(f"Cannot find block with id {id}") cls = component_or_layout_class(block_config["type"]) - block_config["props"].pop("type", None) - block_config["props"].pop("name", None) - block_config["props"].pop("selectable", None) - block_config["props"].pop("server_fns", None) + + block_config["props"] = utils.recover_kwargs(block_config["props"]) # If a Gradio app B is loaded into a Gradio app A, and B itself loads a # Gradio app C, then the root_urls of the components in A need to be the @@ -810,7 +677,12 @@ def iterate_over_children(children_list): dependency.pop("status_tracker", None) dependency["preprocess"] = False dependency["postprocess"] = False - + targets = [ + EventListenerMethod( + t.__self__ if t.has_trigger else None, t.event_name + ) + for t in targets + ] dependency = blocks.set_event_trigger( targets=targets, fn=fn, **dependency )[0] @@ -869,7 +741,7 @@ def set_event_trigger( preprocess: bool = True, postprocess: bool = True, scroll_to_output: bool = False, - show_progress: str = "full", + show_progress: Literal["full", "minimal", "hidden"] | None = "full", api_name: str | None | Literal[False] = None, js: str | None = None, no_target: bool = False, @@ -909,7 +781,7 @@ def set_event_trigger( # Support for singular parameter _targets = [ ( - target.trigger._id if target.trigger and not no_target else None, + target.block._id if target.block and not no_target else None, target.event_name, ) for target in targets @@ -1124,8 +996,8 @@ def __call__(self, *inputs, fn_index: int = 0, api_name: str | None = None): if batch: outputs = [out[0] for out in outputs] - processed_outputs = self.deserialize_data(fn_index, outputs) - processed_outputs = utils.resolve_singleton(processed_outputs) + outputs = self.deserialize_data(fn_index, outputs) + processed_outputs = utils.resolve_singleton(outputs) return processed_outputs @@ -1220,6 +1092,9 @@ def serialize_data(self, fn_index: int, inputs: list[Any]) -> list[Any]: dependency = self.dependencies[fn_index] processed_input = [] + def format_file(s): + return FileData(name=s, is_file=True).model_dump() + for i, input_id in enumerate(dependency["inputs"]): try: block = self.blocks[input_id] @@ -1227,11 +1102,19 @@ def serialize_data(self, fn_index: int, inputs: list[Any]) -> list[Any]: raise InvalidBlockError( f"Input component with id {input_id} used in {dependency['trigger']}() event is not defined in this gr.Blocks context. You are allowed to nest gr.Blocks contexts, but there must be a gr.Blocks context that contains all components and events." ) from e - if not isinstance(block, components.IOComponent): + if not isinstance(block, components.Component): raise InvalidComponentError( f"{block.__class__} Component with id {input_id} not a valid input component." ) - serialized_input = block.serialize(inputs[i]) + api_info = block.api_info() + if client_utils.value_is_file(api_info): + serialized_input = client_utils.traverse( + inputs[i], + format_file, + lambda s: client_utils.is_filepath(s) or client_utils.is_url(s), + ) + else: + serialized_input = inputs[i] processed_input.append(serialized_input) return processed_input @@ -1247,15 +1130,13 @@ def deserialize_data(self, fn_index: int, outputs: list[Any]) -> list[Any]: raise InvalidBlockError( f"Output component with id {output_id} used in {dependency['trigger']}() event not found in this gr.Blocks context. You are allowed to nest gr.Blocks contexts, but there must be a gr.Blocks context that contains all components and events." ) from e - if not isinstance(block, components.IOComponent): + if not isinstance(block, components.Component): raise InvalidComponentError( f"{block.__class__} Component with id {output_id} not a valid output component." ) - deserialized = block.deserialize( - outputs[o], - save_dir=block.DEFAULT_TEMP_DIR, - root_url=block.root_url, - hf_token=Context.hf_token, + + deserialized = client_utils.traverse( + outputs[o], lambda s: s["name"], client_utils.is_file_obj ) predictions.append(deserialized) @@ -1326,7 +1207,10 @@ def preprocess_data( else: if input_id in state: block = state[input_id] - processed_input.append(block.preprocess(inputs[i])) + inputs_cached = processing_utils.move_files_to_cache( + inputs[i], block + ) + processed_input.append(block.preprocess(inputs_cached)) else: processed_input = inputs return processed_input @@ -1445,7 +1329,8 @@ def postprocess_data( f"{block.__class__} Component with id {output_id} not a valid output component." ) prediction_value = block.postprocess(prediction_value) - output.append(prediction_value) + outputs_cached = processing_utils.move_files_to_cache(prediction_value, block) # type: ignore + output.append(outputs_cached) return output @@ -1462,11 +1347,9 @@ def handle_streaming_outputs( self.pending_streams[session_hash][run] = {} stream_run = self.pending_streams[session_hash][run] - from gradio.events import StreamableOutput - for i, output_id in enumerate(self.dependencies[fn_index]["outputs"]): block = self.blocks[output_id] - if isinstance(block, StreamableOutput) and block.streaming: + if isinstance(block, components.StreamingOutput) and block.streaming: first_chunk = output_id not in stream_run binary_data, output_data = block.stream_output( data[i], f"{session_hash}/{run}/{output_id}", first_chunk @@ -1622,10 +1505,12 @@ def get_layout(block): "type": block.get_block_name(), "props": utils.delete_none(props), } - serializer = utils.get_serializer_name(block) - if serializer: - assert isinstance(block, serializing.Serializable) - block_config["serializer"] = serializer + block_config["skip_api"] = block.skip_api + block_config["component_class_id"] = getattr( + block, "component_class_id", None + ) + + if not block.skip_api: block_config["api_info"] = block.api_info() # type: ignore block_config["example_inputs"] = block.example_inputs() # type: ignore config["components"].append(block_config) @@ -1665,7 +1550,7 @@ def load( outputs: Component | list[Component] | None = None, api_name: str | None | Literal[False] = None, scroll_to_output: bool = False, - show_progress: str = "full", + show_progress: Literal["full", "hidden", "minimal"] | None = "full", queue=None, batch: bool = False, max_batch_size: int = 4, @@ -1673,37 +1558,22 @@ def load( postprocess: bool = True, every: float | None = None, _js: str | None = None, - *, - name: str | None = None, - src: str | None = None, - api_key: str | None = None, - alias: str | None = None, - **kwargs, - ) -> Blocks | dict[str, Any] | None: + ) -> Dependency: """ - For reverse compatibility reasons, this is both a class method and an instance - method, the two of which, confusingly, do two completely different things. - - Class method: loads a demo from a Hugging Face Spaces repo and creates it locally and returns a block instance. Warning: this method will be deprecated. Use the equivalent `gradio.load()` instead. - - Instance method: adds event that runs as soon as the demo loads in the browser. Example usage below. + Adds an event that runs as soon as the demo loads in the browser. Example usage below. Parameters: - name: Class Method - the name of the model (e.g. "gpt2" or "facebook/bart-base") or space (e.g. "flax-community/spanish-gpt2"), can include the `src` as prefix (e.g. "models/facebook/bart-base") - src: Class Method - the source of the model: `models` or `spaces` (or leave empty if source is provided as a prefix in `name`) - api_key: Class Method - optional access token for loading private Hugging Face Hub models or spaces. Find your token here: https://huggingface.co/settings/tokens. Warning: only provide this if you are loading a trusted private Space as it can be read by the Space you are loading. - alias: Class Method - optional string used as the name of the loaded model instead of the default name (only applies if loading a Space running Gradio 2.x) - fn: Instance Method - the function to wrap an interface around. Often a machine learning model's prediction function. Each parameter of the function corresponds to one input component, and the function should return a single value or a tuple of values, with each element in the tuple corresponding to one output component. - inputs: Instance Method - List of gradio.components to use as inputs. If the function takes no inputs, this should be an empty list. - outputs: Instance Method - List of gradio.components to use as inputs. If the function returns no outputs, this should be an empty list. - api_name: Instance Method - Defines how the endpoint appears in the API docs. Can be a string, None, or False. If False, the endpoint will not be exposed in the api docs. If set to None, the endpoint will be exposed in the api docs as an unnamed endpoint, although this behavior will be changed in Gradio 4.0. If set to a string, the endpoint will be exposed in the api docs with the given name. - scroll_to_output: Instance Method - If True, will scroll to output component on completion - show_progress: Instance Method - If True, will show progress animation while pending - queue: Instance Method - If True, will place the request on the queue, if the queue exists - batch: Instance Method - If True, then the function should process a batch of inputs, meaning that it should accept a list of input values for each parameter. The lists should be of equal length (and be up to length `max_batch_size`). The function is then *required* to return a tuple of lists (even if there is only 1 output component), with each list in the tuple corresponding to one output component. - max_batch_size: Instance Method - Maximum number of inputs to batch together if this is called from the queue (only relevant if batch=True) - preprocess: Instance Method - If False, will not run preprocessing of component data before running 'fn' (e.g. leaving it as a base64 string if this method is called with the `Image` component). - postprocess: Instance Method - If False, will not run postprocessing of component data before returning 'fn' output to the browser. - every: Instance Method - Run this event 'every' number of seconds. Interpreted in seconds. Queue must be enabled. + fn: The function to wrap an interface around. Often a machine learning model's prediction function. Each parameter of the function corresponds to one input component, and the function should return a single value or a tuple of values, with each element in the tuple corresponding to one output component. + inputs: List of gradio.components to use as inputs. If the function takes no inputs, this should be an empty list. + outputs: List of gradio.components to use as inputs. If the function returns no outputs, this should be an empty list. + api_name: Defines how the endpoint appears in the API docs. Can be a string, None, or False. If False, the endpoint will not be exposed in the api docs. If set to None, the endpoint will be exposed in the api docs as an unnamed endpoint, although this behavior will be changed in Gradio 4.0. If set to a string, the endpoint will be exposed in the api docs with the given name. + scroll_to_output: If True, will scroll to output component on completion + show_progress: If True, will show progress animation while pending + queue: If True, will place the request on the queue, if the queue exists + batch: If True, then the function should process a batch of inputs, meaning that it should accept a list of input values for each parameter. The lists should be of equal length (and be up to length `max_batch_size`). The function is then *required* to return a tuple of lists (even if there is only 1 output component), with each list in the tuple corresponding to one output component. + max_batch_size: Maximum number of inputs to batch together if this is called from the queue (only relevant if batch=True) + preprocess: If False, will not run preprocessing of component data before running 'fn' (e.g. leaving it as a base64 string if this method is called with the `Image` component). + postprocess: If False, will not run postprocessing of component data before returning 'fn' output to the browser. + every: Run this event 'every' number of seconds. Interpreted in seconds. Queue must be enabled. Example: import gradio as gr import datetime @@ -1714,43 +1584,31 @@ def get_time(): demo.load(get_time, inputs=None, outputs=dt) demo.launch() """ - if self is None: - warn_deprecation( - "gr.Blocks.load() will be deprecated. Use gr.load() instead." - ) - if name is None: - raise ValueError( - "Blocks.load() requires passing parameters as keyword arguments" - ) - return external.load( - name=name, src=src, hf_token=api_key, alias=alias, **kwargs - ) - else: - from gradio.events import Dependency, EventListenerMethod - - if Context.root_block is None: - raise AttributeError( - "Cannot call load() outside of a gradio.Blocks context." - ) + from gradio.events import Dependency, EventListenerMethod - dep, dep_index = Context.root_block.set_event_trigger( - targets=[EventListenerMethod(self, "load")], - fn=fn, - inputs=inputs, - outputs=outputs, - api_name=api_name, - preprocess=preprocess, - postprocess=postprocess, - scroll_to_output=scroll_to_output, - show_progress=show_progress, - js=_js, - queue=queue, - batch=batch, - max_batch_size=max_batch_size, - every=every, - no_target=True, + if Context.root_block is None: + raise AttributeError( + "Cannot call load() outside of a gradio.Blocks context." ) - return Dependency(dep, dep_index, fn) + + dep, dep_index = Context.root_block.set_event_trigger( + targets=[EventListenerMethod(self, "load")], + fn=fn, + inputs=inputs, + outputs=outputs, + api_name=api_name, + preprocess=preprocess, + postprocess=postprocess, + scroll_to_output=scroll_to_output, + show_progress=show_progress, + js=_js, + queue=queue, + batch=batch, + max_batch_size=max_batch_size, + every=every, + no_target=True, + ) + return Dependency(None, dep, dep_index, fn) def clear(self): """Resets the layout of the Blocks object.""" @@ -1766,8 +1624,6 @@ def queue( self, concurrency_count: int = 1, status_update_rate: float | Literal["auto"] = "auto", - client_position_to_load_data: int | None = None, - default_enabled: bool | None = None, api_open: bool = True, max_size: int | None = None, ): @@ -1776,8 +1632,6 @@ def queue( Parameters: concurrency_count: Number of worker threads that will be processing requests from the queue concurrently. Increasing this number will increase the rate at which requests are processed, but will also increase the memory usage of the queue. status_update_rate: If "auto", Queue will send status estimations to all clients whenever a job is finished. Otherwise Queue will send status at regular intervals set by this parameter as the number of seconds. - client_position_to_load_data: DEPRECATED. This parameter is deprecated and has no effect. - default_enabled: Deprecated and has no effect. api_open: If True, the REST routes of the backend will be open, allowing requests made directly to those endpoints to skip the queue. max_size: The maximum number of events the queue will store at any given moment. If the queue is full, new events will not be added and a user will receive a message saying that the queue is full. If None, the queue size will be unlimited. Example: (Blocks) @@ -1791,17 +1645,8 @@ def queue( demo.queue(max_size=20) demo.launch() """ - if default_enabled is not None: - warn_deprecation( - "The default_enabled parameter of queue has no effect and will be removed " - "in a future version of gradio." - ) self.enable_queue = True self.api_open = api_open - if client_position_to_load_data is not None: - warn_deprecation( - "The client_position_to_load_data parameter is deprecated." - ) if utils.is_zero_gpu_space(): concurrency_count = self.max_threads max_size = 1 if max_size is None else max_size @@ -1849,7 +1694,6 @@ def launch( inbrowser: bool = False, share: bool | None = None, debug: bool = False, - enable_queue: bool | None = None, max_threads: int = 40, auth: Callable | tuple[str, str] | list[tuple[str, str]] | None = None, auth_message: str | None = None, @@ -1860,7 +1704,6 @@ def launch( show_tips: bool = False, height: int = 500, width: int | str = "100%", - encrypt: bool | None = None, favicon_path: str | None = None, ssl_keyfile: str | None = None, ssl_certfile: str | None = None, @@ -1868,7 +1711,6 @@ def launch( ssl_verify: bool = True, quiet: bool = False, show_api: bool = True, - file_directories: list[str] | None = None, allowed_paths: list[str] | None = None, blocked_paths: list[str] | None = None, root_path: str | None = None, @@ -1892,11 +1734,9 @@ def launch( server_port: will start gradio app on this port (if available). Can be set by environment variable GRADIO_SERVER_PORT. If None, will search for an available port starting at 7860. server_name: to make app accessible on local network, set this to "0.0.0.0". Can be set by environment variable GRADIO_SERVER_NAME. If None, will use "127.0.0.1". show_tips: if True, will occasionally show tips about new Gradio features - enable_queue: DEPRECATED (use .queue() method instead.) if True, inference requests will be served through a queue instead of with parallel threads. Required for longer inference times (> 1min) to prevent timeout. The default option in HuggingFace Spaces is True. The default option elsewhere is False. max_threads: the maximum number of total threads that the Gradio app can generate in parallel. The default is inherited from the starlette library (currently 40). Applies whether the queue is enabled or not. But if queuing is enabled, this parameter is increaseed to be at least the concurrency_count of the queue. width: The width in pixels of the iframe element containing the interface (used if inline=True) height: The height in pixels of the iframe element containing the interface (used if inline=True) - encrypt: DEPRECATED. Has no effect. favicon_path: If a path to a file (.png, .gif, or .ico) is provided, it will be used as the favicon for the web page. ssl_keyfile: If a path to a file is provided, will use this as the private key file to create a local server running on https. ssl_certfile: If a path to a file is provided, will use this as the signed certificate for https. Needs to be provided if ssl_keyfile is provided. @@ -1904,7 +1744,6 @@ def launch( ssl_verify: If False, skips certificate validation which allows self-signed certificates to be used. quiet: If True, suppresses most print statements. show_api: If True, shows the api docs in the footer of the app. Default True. - file_directories: This parameter has been renamed to `allowed_paths`. It will be removed in a future version. allowed_paths: List of complete filepaths or parent directories that gradio is allowed to serve (in addition to the directory containing the gradio python file). Must be absolute paths. Warning: if you provide directories, any files in these directories or their subdirectories are accessible to all users of your app. blocked_paths: List of complete filepaths or parent directories that gradio is not allowed to serve (i.e. users of your app are not allowed to access). Must be absolute paths. Warning: takes precedence over `allowed_paths` and all other directories exposed by Gradio by default. root_path: The root path (or "mount point") of the application, if it's not served from the root ("/") of the domain. Often used when the application is behind a reverse proxy that forwards requests to the application. For example, if the application is served at "https://example.com/myapp", the `root_path` should be set to "/myapp". Can be set by environment variable GRADIO_ROOT_PATH. Defaults to "". @@ -1957,18 +1796,6 @@ def reverse(text): self.root_path = os.environ.get("GRADIO_ROOT_PATH", "") else: self.root_path = root_path - - if enable_queue is not None: - self.enable_queue = enable_queue - warn_deprecation( - "The `enable_queue` parameter has been deprecated. " - "Please use the `.queue()` method instead.", - ) - if encrypt is not None: - warn_deprecation( - "The `encrypt` parameter has been deprecated and has no effect.", - ) - if self.space_id: self.enable_queue = self.enable_queue is not False else: @@ -1978,13 +1805,6 @@ def reverse(text): self.show_api = show_api - if file_directories is not None: - warn_deprecation( - "The `file_directories` parameter has been renamed to `allowed_paths`. " - "Please use that instead.", - ) - if allowed_paths is None: - allowed_paths = file_directories self.allowed_paths = allowed_paths or [] self.blocked_paths = blocked_paths or [] @@ -2265,7 +2085,7 @@ def reverse(text): ): self.block_thread() - return TupleNoPrint((self.server_app, self.local_url, self.share_url)) + return TupleNoPrint((self.server_app, self.local_url, self.share_url)) # type: ignore def integrate( self, @@ -2371,12 +2191,11 @@ def attach_load_events(self): if Context.root_block: for component in Context.root_block.blocks.values(): if ( - isinstance(component, components.IOComponent) + isinstance(component, components.Component) and component.load_event_to_attach ): load_fn, every = component.load_event_to_attach # Use set_event_trigger to avoid ambiguity between load class/instance method - from gradio.events import EventListenerMethod dep = self.set_event_trigger( [EventListenerMethod(self, "load")], @@ -2406,3 +2225,96 @@ def queue_enabled_for_fn(self, fn_index: int): if self.dependencies[fn_index]["queue"] is None: return self.enable_queue return self.dependencies[fn_index]["queue"] + + def get_api_info(self): + """ + Gets the information needed to generate the API docs from a Blocks. + """ + config = self.config + api_info = {"named_endpoints": {}, "unnamed_endpoints": {}} + mode = config.get("mode", None) + + for d, dependency in enumerate(config["dependencies"]): + dependency_info = {"parameters": [], "returns": []} + skip_endpoint = False + + inputs = dependency["inputs"] + for i in inputs: + for component in config["components"]: + if component["id"] == i: + break + else: + skip_endpoint = True # if component not found, skip endpoint + break + type = component["type"] + if self.blocks[component["id"]].skip_api: + continue + label = component["props"].get("label", f"parameter_{i}") + # The config has the most specific API info (taking into account the parameters + # of the component), so we use that if it exists. Otherwise, we fallback to the + # Serializer's API info. + info = self.get_component(component["id"]).api_info() + example = self.get_component(component["id"]).example_inputs() + python_type = client_utils.json_schema_to_python_type(info) + dependency_info["parameters"].append( + { + "label": label, + "type": info, + "python_type": { + "type": python_type, + "description": info.get("description", ""), + }, + "component": type.capitalize(), + "example_input": example, + } + ) + + outputs = dependency["outputs"] + for o in outputs: + for component in config["components"]: + if component["id"] == o: + break + else: + skip_endpoint = True # if component not found, skip endpoint + break + type = component["type"] + if self.blocks[component["id"]].skip_api: + continue + label = component["props"].get("label", f"value_{o}") + info = self.get_component(component["id"]).api_info() + example = self.get_component(component["id"]).example_inputs() + python_type = client_utils.json_schema_to_python_type(info) + dependency_info["returns"].append( + { + "label": label, + "type": info, + "python_type": { + "type": python_type, + "description": info.get("description", ""), + }, + "component": type.capitalize(), + } + ) + + if not dependency["backend_fn"]: + skip_endpoint = True + + if skip_endpoint: + continue + if ( + dependency["api_name"] is not None + and dependency["api_name"] is not False + ): + api_info["named_endpoints"][ + f"/{dependency['api_name']}" + ] = dependency_info + elif ( + dependency["api_name"] is False + or mode == "interface" + or mode == "tabbed_interface" + ): + pass # Skip unnamed endpoints in interface mode + else: + api_info["unnamed_endpoints"][str(d)] = dependency_info + + return api_info diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index 740d7f50e5d1..f3a581c17732 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -16,13 +16,13 @@ from gradio.components import ( Button, Chatbot, - IOComponent, + Component, Markdown, State, Textbox, get_component_instance, ) -from gradio.events import Dependency, EventListenerMethod, on +from gradio.events import Dependency, on from gradio.helpers import create_examples as Examples # noqa: N812 from gradio.helpers import special_args from gradio.layouts import Accordion, Column, Group, Row @@ -59,7 +59,7 @@ def __init__( *, chatbot: Chatbot | None = None, textbox: Textbox | None = None, - additional_inputs: str | IOComponent | list[str | IOComponent] | None = None, + additional_inputs: str | Component | list[str | Component] | None = None, additional_inputs_accordion_name: str = "Additional Inputs", examples: list[str] | None = None, cache_examples: bool | None = None, @@ -115,7 +115,7 @@ def __init__( self.cache_examples = True else: self.cache_examples = cache_examples or False - self.buttons: list[Button] = [] + self.buttons: list[Button | None] = [] if additional_inputs: if not isinstance(additional_inputs, list): @@ -146,7 +146,9 @@ def __init__( if textbox: textbox.container = False textbox.show_label = False - self.textbox = textbox.render() + textbox_ = textbox.render() + assert isinstance(textbox_, Textbox) + self.textbox = textbox_ else: self.textbox = Textbox( container=False, @@ -186,7 +188,7 @@ def __init__( raise ValueError( f"The stop_btn parameter must be a gr.Button, string, or None, not {type(stop_btn)}" ) - self.buttons.extend([submit_btn, stop_btn]) + self.buttons.extend([submit_btn, stop_btn]) # type: ignore with Row(): for btn in [retry_btn, undo_btn, clear_btn]: @@ -199,7 +201,7 @@ def __init__( raise ValueError( f"All the _btn parameters must be a gr.Button, string, or None, not {type(btn)}" ) - self.buttons.append(btn) + self.buttons.append(btn) # type: ignore self.fake_api_btn = Button("Fake API", visible=False) self.fake_response_textbox = Textbox( @@ -327,7 +329,7 @@ def _setup_events(self) -> None: ) def _setup_stop_events( - self, event_triggers: list[EventListenerMethod], event_to_cancel: Dependency + self, event_triggers: list[Callable], event_to_cancel: Dependency ) -> None: if self.stop_btn and self.is_generator: if self.submit_btn: diff --git a/gradio/cli.py b/gradio/cli.py deleted file mode 100644 index b521bbf0a3cb..000000000000 --- a/gradio/cli.py +++ /dev/null @@ -1,21 +0,0 @@ -import sys - -from gradio_client.cli import deploy_discord # type: ignore - -import gradio.cli_env_info -import gradio.deploy_space -import gradio.reload - - -def cli(): - args = sys.argv[1:] - if len(args) == 0: - raise ValueError("No file specified.") - elif args[0] == "deploy": - gradio.deploy_space.deploy() - elif args[0] == "environment": - gradio.cli_env_info.print_environment_info() - elif args[0] == "deploy-discord": - deploy_discord.main() - else: - gradio.reload.main() diff --git a/gradio/cli/__init__.py b/gradio/cli/__init__.py new file mode 100644 index 000000000000..1fca2de36be2 --- /dev/null +++ b/gradio/cli/__init__.py @@ -0,0 +1,4 @@ +from .cli import cli, deploy +from .commands import custom_component + +__all__ = ["cli", "deploy", "custom_component"] diff --git a/gradio/cli/cli.py b/gradio/cli/cli.py new file mode 100644 index 000000000000..d20fd573c3cf --- /dev/null +++ b/gradio/cli/cli.py @@ -0,0 +1,31 @@ +import sys + +import typer +from gradio_client.cli import deploy_discord # type: ignore + +from .commands import custom_component, deploy, print_environment_info, reload + +app = typer.Typer() +app.command("environment", help="Print Gradio environment information.")( + print_environment_info +) +app.command( + "deploy", + help="Deploy a Gradio app to Spaces. Must be called within the directory you would like to deploy.", +)(deploy) +app.command("deploy-discord", help="Deploy a Gradio app to Discord.")( + deploy_discord.main +) + + +def cli(): + args = sys.argv[1:] + if len(args) == 0: + raise ValueError("No file specified.") + if args[0] in {"deploy", "environment", "deploy-discord"}: + app() + elif args[0] in {"cc", "component"}: + sys.argv = sys.argv[1:] + custom_component() + else: + typer.run(reload) diff --git a/gradio/cli/commands/__init__.py b/gradio/cli/commands/__init__.py new file mode 100644 index 000000000000..e4b20dbfc7bb --- /dev/null +++ b/gradio/cli/commands/__init__.py @@ -0,0 +1,6 @@ +from .cli_env_info import print_environment_info +from .components import app as custom_component +from .deploy_space import deploy +from .reload import main as reload + +__all__ = ["deploy", "reload", "print_environment_info", "custom_component"] diff --git a/gradio/cli_env_info.py b/gradio/cli/commands/cli_env_info.py similarity index 95% rename from gradio/cli_env_info.py rename to gradio/cli/commands/cli_env_info.py index 29df6e344144..5156f3a44940 100644 --- a/gradio/cli_env_info.py +++ b/gradio/cli/commands/cli_env_info.py @@ -4,8 +4,11 @@ import platform from importlib import metadata +from rich import print + def print_environment_info(): + """Print Gradio environment information.""" print("Gradio Environment Information:\n------------------------------") print("Operating System:", platform.system()) diff --git a/gradio/cli/commands/components/__init__.py b/gradio/cli/commands/components/__init__.py new file mode 100644 index 000000000000..34f275ed3c9d --- /dev/null +++ b/gradio/cli/commands/components/__init__.py @@ -0,0 +1,3 @@ +from .app import app + +__all__ = ["app"] diff --git a/gradio/cli/commands/components/_create_utils.py b/gradio/cli/commands/components/_create_utils.py new file mode 100644 index 000000000000..c28dc37820cb --- /dev/null +++ b/gradio/cli/commands/components/_create_utils.py @@ -0,0 +1,303 @@ +from __future__ import annotations + +import dataclasses +import inspect +import json +import re +import shutil +import textwrap +from pathlib import Path +from typing import Literal + +import gradio + + +def _in_test_dir(): + """Check if the current working directory ends with gradio/js/gradio-preview/test.""" + return Path.cwd().parts[-4:] == ("gradio", "js", "gradio-preview", "test") + + +default_demo_code = """ +example = {name}().example_inputs() + +with gr.Blocks() as demo: + {name}(value=example, interactive=True) + {name}(value=example, interactive=False) +""" + + +@dataclasses.dataclass +class ComponentFiles: + template: str + demo_code: str = default_demo_code + python_file_name: str = "" + js_dir: str = "" + + def __post_init__(self): + self.js_dir = self.js_dir or self.template.lower() + self.python_file_name = self.python_file_name or f"{self.template.lower()}.py" + + +OVERRIDES = { + "AnnotatedImage": ComponentFiles( + template="AnnotatedImage", python_file_name="annotated_image.py" + ), + "HighlightedText": ComponentFiles( + template="HighlightedText", python_file_name="highlighted_text.py" + ), + "BarPlot": ComponentFiles( + template="BarPlot", python_file_name="bar_plot.py", js_dir="plot" + ), + "ClearButton": ComponentFiles( + template="ClearButton", python_file_name="clear_button.py", js_dir="button" + ), + "ColorPicker": ComponentFiles( + template="ColorPicker", python_file_name="color_picker.py" + ), + "DuplicateButton": ComponentFiles( + template="DuplicateButton", + python_file_name="duplicate_button.py", + js_dir="button", + ), + "FileExplorer": ComponentFiles( + template="FileExplorer", + python_file_name="file_explorer.py", + js_dir="fileexplorer", + demo_code=textwrap.dedent( + """ + import os + + with gr.Blocks() as demo: + {name}(value=os.path.dirname(__file__).split(os.sep)) + """ + ), + ), + "LinePlot": ComponentFiles( + template="LinePlot", python_file_name="line_plot.py", js_dir="plot" + ), + "LogoutButton": ComponentFiles( + template="LogoutButton", python_file_name="logout_button.py", js_dir="button" + ), + "LoginButton": ComponentFiles( + template="LoginButton", python_file_name="login_button.py", js_dir="button" + ), + "ScatterPlot": ComponentFiles( + template="ScatterPlot", python_file_name="scatter_plot.py", js_dir="plot" + ), + "UploadButton": ComponentFiles( + template="UploadButton", python_file_name="upload_button.py" + ), + "JSON": ComponentFiles(template="JSON", python_file_name="json_component.py"), + "Row": ComponentFiles( + template="Row", + demo_code=textwrap.dedent( + """ + with gr.Blocks() as demo: + with {name}(): + gr.Textbox(value="foo", interactive=True) + gr.Number(value=10, interactive=True) + """ + ), + ), + "Column": ComponentFiles( + template="Column", + demo_code=textwrap.dedent( + """ + with gr.Blocks() as demo: + with {name}(): + gr.Textbox(value="foo", interactive=True) + gr.Number(value=10, interactive=True) + """ + ), + ), + "Tabs": ComponentFiles( + template="Tabs", + demo_code=textwrap.dedent( + """ + with gr.Blocks() as demo: + with {name}(): + with gr.Tab("Tab 1"): + gr.Textbox(value="foo", interactive=True) + with gr.Tab("Tab 2"): + gr.Number(value=10, interactive=True) + """ + ), + ), + "Group": ComponentFiles( + template="Group", + demo_code=textwrap.dedent( + """ + with gr.Blocks() as demo: + with {name}(): + gr.Textbox(value="foo", interactive=True) + gr.Number(value=10, interactive=True) + """ + ), + ), + "Accordion": ComponentFiles( + template="Accordion", + demo_code=textwrap.dedent( + """ + with gr.Blocks() as demo: + with {name}(label="Accordion"): + gr.Textbox(value="foo", interactive=True) + gr.Number(value=10, interactive=True) + """ + ), + ), +} + + +def _get_component_code(template: str | None) -> ComponentFiles: + template = template or "Fallback" + if template in OVERRIDES: + return OVERRIDES[template] + else: + return ComponentFiles( + python_file_name=f"{template.lower()}.py", + js_dir=template.lower(), + template=template, + ) + + +def _get_js_dependency_version(name: str, local_js_dir: Path) -> str: + package_json = json.loads( + Path(local_js_dir / name.split("/")[1] / "package.json").read_text() + ) + return package_json["version"] + + +def _modify_js_deps( + package_json: dict, + key: Literal["dependencies", "devDependencies"], + gradio_dir: Path, +): + for dep in package_json.get(key, []): + # if curent working directory is the gradio repo, use the local version of the dependency' + if not _in_test_dir() and dep.startswith("@gradio/"): + package_json[key][dep] = _get_js_dependency_version( + dep, gradio_dir / "_frontend_code" + ) + return package_json + + +def delete_contents(directory: str | Path) -> None: + """Delete all contents of a directory, but not the directory itself.""" + path = Path(directory) + for child in path.glob("*"): + if child.is_file(): + child.unlink() + elif child.is_dir(): + shutil.rmtree(child) + + +def _create_frontend(name: str, component: ComponentFiles, directory: Path): + frontend = directory / "frontend" + frontend.mkdir(exist_ok=True) + + p = Path(inspect.getfile(gradio)).parent + + def ignore(s, names): + ignored = [] + for n in names: + if ( + n.startswith("CHANGELOG") + or n.startswith("README.md") + or ".test." in n + or ".stories." in n + or ".spec." in n + ): + ignored.append(n) + return ignored + + shutil.copytree( + str(p / "_frontend_code" / component.js_dir), + frontend, + dirs_exist_ok=True, + ignore=ignore, + ) + source_package_json = json.loads(Path(frontend / "package.json").read_text()) + source_package_json["name"] = name.lower() + source_package_json = _modify_js_deps(source_package_json, "dependencies", p) + source_package_json = _modify_js_deps(source_package_json, "devDependencies", p) + (frontend / "package.json").write_text(json.dumps(source_package_json, indent=2)) + + +def _replace_old_class_name(old_class_name: str, new_class_name: str, content: str): + pattern = rf"(?<=\b)(?>", package_name)) + + demo_dir = directory / "demo" + demo_dir.mkdir(exist_ok=True, parents=True) + + (demo_dir / "app.py").write_text( + f""" +import gradio as gr +from {package_name} import {name} + +{component.demo_code.format(name=name)} + +demo.launch() +""" + ) + (demo_dir / "__init__.py").touch() + + init = backend / "__init__.py" + init.write_text( + f""" +from .{name.lower()} import {name} + +__all__ = ['{name}'] +""" + ) + + p = Path(inspect.getfile(gradio)).parent + python_file = backend / f"{name.lower()}.py" + + shutil.copy( + str(p / module / component.python_file_name), + str(python_file), + ) + + source_pyi_file = p / module / component.python_file_name.replace(".py", ".pyi") + pyi_file = backend / f"{name.lower()}.pyi" + if source_pyi_file.exists(): + shutil.copy(str(source_pyi_file), str(pyi_file)) + + content = python_file.read_text() + python_file.write_text(_replace_old_class_name(component.template, name, content)) + if pyi_file.exists(): + pyi_content = pyi_file.read_text() + pyi_file.write_text( + _replace_old_class_name(component.template, name, pyi_content) + ) diff --git a/gradio/cli/commands/components/app.py b/gradio/cli/commands/components/app.py new file mode 100644 index 000000000000..8aeda37fab2e --- /dev/null +++ b/gradio/cli/commands/components/app.py @@ -0,0 +1,16 @@ +from typer import Typer + +from .build import _build +from .create import _create +from .dev import _dev +from .show import _show + +app = Typer(help="Create and publish a new Gradio component") + +app.command("create", help="Create a new component.")(_create) +app.command( + "build", + help="Build the component for distribution. Must be called from the component directory.", +)(_build) +app.command("dev", help="Launch the custom component demo in development mode.")(_dev) +app.command("show", help="Show the list of available templates")(_show) diff --git a/gradio/cli/commands/components/build.py b/gradio/cli/commands/components/build.py new file mode 100644 index 000000000000..87487e011660 --- /dev/null +++ b/gradio/cli/commands/components/build.py @@ -0,0 +1,69 @@ +import shutil +import subprocess +import sys +from pathlib import Path + +import typer +from typing_extensions import Annotated + +import gradio +from gradio.cli.commands.display import LivePanelDisplay + +gradio_template_path = Path(gradio.__file__).parent / "templates" / "frontend" +gradio_node_path = Path(gradio.__file__).parent / "node" / "dev" / "files" / "index.js" + + +def _build( + path: Annotated[ + Path, typer.Argument(help="The directory of the custom component.") + ] = Path("."), + build_frontend: Annotated[ + bool, typer.Option(help="Whether to build the frontend as well.") + ] = True, +): + name = Path(path).resolve() + if not (name / "pyproject.toml").exists(): + raise ValueError(f"Cannot find pyproject.toml file in {name}") + + with LivePanelDisplay() as live: + live.update( + f":package: Building package in [orange3]{str(name.name)}[/]", add_sleep=0.2 + ) + if build_frontend: + live.update(":art: Building frontend") + component_directory = path.resolve() + + node = shutil.which("node") + if not node: + raise ValueError("node must be installed in order to run dev mode.") + + node_cmds = [ + node, + gradio_node_path, + "--component-directory", + component_directory, + "--root", + gradio_template_path, + "--mode", + "build", + ] + + pipe = subprocess.run(node_cmds, capture_output=True, text=True) + if pipe.returncode != 0: + live.update(":red_square: Build failed!") + live.update(pipe.stderr) + return + else: + live.update(":white_check_mark: Build succeeded!") + + cmds = [sys.executable, "-m", "build", str(name)] + live.update(f":construction_worker: Building... [grey37]({' '.join(cmds)})[/]") + pipe = subprocess.run(cmds, capture_output=True, text=True) + if pipe.returncode != 0: + live.update(":red_square: Build failed!") + live.update(pipe.stderr) + else: + live.update(":white_check_mark: Build succeeded!") + live.update( + f":ferris_wheel: Wheel located in [orange3]{str(name / 'dist')}[/]" + ) diff --git a/gradio/cli/commands/components/create.py b/gradio/cli/commands/components/create.py new file mode 100644 index 000000000000..88a1e0fb94ae --- /dev/null +++ b/gradio/cli/commands/components/create.py @@ -0,0 +1,127 @@ +import shutil +import subprocess +from pathlib import Path +from typing import Optional + +import typer +from rich.markup import escape +from typing_extensions import Annotated + +from gradio.cli.commands.display import LivePanelDisplay +from gradio.utils import set_directory + +from . import _create_utils + + +def _create( + name: Annotated[ + str, + typer.Argument( + help="Name of the component. Preferably in camel case, i.e. MyTextBox." + ), + ], + directory: Annotated[ + Optional[Path], + typer.Option( + help="Directory to create the component in. Default is None. If None, will be created in directory in the current directory." + ), + ] = None, + package_name: Annotated[ + Optional[str], + typer.Option(help="Name of the package. Default is gradio_{name.lower()}"), + ] = None, + template: Annotated[ + str, + typer.Option( + help="Component to use as a template. Should use exact name of python class." + ), + ] = "", + install: Annotated[ + bool, + typer.Option( + help="Whether to install the component in your current environment as a development install. Recommended for development." + ), + ] = False, + npm_install: Annotated[ + str, + typer.Option(help="NPM install command to use. Default is 'npm install'."), + ] = "npm install", + overwrite: Annotated[ + bool, + typer.Option(help="Whether to overwrite the existing component if it exists."), + ] = False, +): + if not directory: + directory = Path(name.lower()) + if not package_name: + package_name = f"gradio_{name.lower()}" + + if directory.exists() and not overwrite: + raise ValueError( + f"The directory {directory.resolve()} already exists. " + "Please set --overwrite flag or pass in the name " + "of a directory that does not already exist via the --directory option." + ) + elif directory.exists() and overwrite: + _create_utils.delete_contents(directory) + + directory.mkdir(exist_ok=overwrite) + + if _create_utils._in_test_dir(): + npm_install = f"{shutil.which('pnpm')} i --ignore-scripts" + + npm_install = npm_install.strip() + if npm_install == "npm install": + npm = shutil.which("npm") + if not npm: + raise ValueError( + "By default, the install command uses npm to install " + "the frontend dependencies. Please install npm or pass your own install command " + "via the --npm-install option." + ) + npm_install = f"{npm} install" + + with LivePanelDisplay() as live: + live.update( + f":building_construction: Creating component [orange3]{name}[/] in directory [orange3]{directory}[/]", + add_sleep=0.2, + ) + if template: + live.update(f":fax: Starting from template [orange3]{template}[/]") + else: + live.update(":page_facing_up: Creating a new component from scratch.") + + component = _create_utils._get_component_code(template) + + _create_utils._create_backend(name, component, directory, package_name) + live.update(":snake: Created backend code", add_sleep=0.2) + + _create_utils._create_frontend(name.lower(), component, directory=directory) + live.update(":art: Created frontend code", add_sleep=0.2) + + if install: + cmds = [shutil.which("pip"), "install", "-e", f"{str(directory)}[dev]"] + live.update( + f":construction_worker: Installing python... [grey37]({escape(' '.join(cmds))})[/]" + ) + pipe = subprocess.run(cmds, capture_output=True, text=True) + + if pipe.returncode != 0: + live.update(":red_square: Python installation [bold][red]failed[/][/]") + live.update(pipe.stderr) + else: + live.update(":white_check_mark: Python install succeeded!") + + live.update( + f":construction_worker: Installing javascript... [grey37]({npm_install})[/]" + ) + with set_directory(directory / "frontend"): + pipe = subprocess.run( + npm_install.split(), capture_output=True, text=True + ) + if pipe.returncode != 0: + live.update(":red_square: NPM install [bold][red]failed[/][/]") + live.update(pipe.stdout) + live.update(pipe.stderr) + else: + live.update(":white_check_mark: NPM install succeeded!") diff --git a/gradio/cli/commands/components/dev.py b/gradio/cli/commands/components/dev.py new file mode 100644 index 000000000000..1efe72dd92b6 --- /dev/null +++ b/gradio/cli/commands/components/dev.py @@ -0,0 +1,86 @@ +import shutil +import subprocess +from pathlib import Path + +import typer +from rich import print +from typing_extensions import Annotated + +import gradio + +gradio_template_path = Path(gradio.__file__).parent / "templates" / "frontend" +gradio_node_path = Path(gradio.__file__).parent / "node" / "dev" / "files" / "index.js" + + +def _dev( + app: Annotated[ + Path, + typer.Argument( + help="The path to the app. By default, looks for demo/app.py in the current directory." + ), + ] = Path("demo") + / "app.py", + component_directory: Annotated[ + Path, + typer.Option( + help="The directory with the custom component source code. By default, uses the current directory." + ), + ] = Path("."), + host: Annotated[ + str, + typer.Option( + help="The host to run the front end server on. Defaults to localhost.", + ), + ] = "localhost", +): + component_directory = component_directory.resolve() + + print(f":recycle: [green]Launching[/] {app} in reload mode\n") + + node = shutil.which("node") + if not node: + raise ValueError("node must be installed in order to run dev mode.") + + proc = subprocess.Popen( + [ + node, + gradio_node_path, + "--component-directory", + component_directory, + "--root", + gradio_template_path, + "--app", + str(app), + "--mode", + "dev", + "--host", + host, + ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + while True: + proc.poll() + text = proc.stdout.readline() # type: ignore + err = None + if proc.stderr: + err = proc.stderr.readline() + + text = ( + text.decode("utf-8") + .replace("Changes detected in:", "[orange3]Changed detected in:[/]") + .replace("Watching:", "[orange3]Watching:[/]") + .replace("Running on local URL", "[orange3]Backend Server[/]") + ) + + if "[orange3]Watching:[/]" in text: + text += f"'{str(component_directory / 'frontend').strip()}'," + if "To create a public link" in text: + continue + print(text) + if err: + print(err.decode("utf-8")) + + if proc.returncode is not None: + print("Backend server failed to launch. Exiting.") + return diff --git a/gradio/cli/commands/components/files/gitignore b/gradio/cli/commands/components/files/gitignore new file mode 100644 index 000000000000..60188eefb61f --- /dev/null +++ b/gradio/cli/commands/components/files/gitignore @@ -0,0 +1,9 @@ +.eggs/ +dist/ +*.pyc +__pycache__/ +*.py[cod] +*$py.class +__tmp/* +*.pyi +node_modules \ No newline at end of file diff --git a/gradio/cli/commands/components/files/pyproject_.toml b/gradio/cli/commands/components/files/pyproject_.toml new file mode 100644 index 000000000000..ceaf3013f265 --- /dev/null +++ b/gradio/cli/commands/components/files/pyproject_.toml @@ -0,0 +1,47 @@ +[build-system] +requires = [ + "hatchling", + "hatch-requirements-txt", + "hatch-fancy-pypi-readme>=22.5.0", +] +build-backend = "hatchling.build" + +[project] +name = "<>" +version = "0.0.1" +description = "Python library for easily interacting with trained machine learning models" +license = "Apache-2.0" +requires-python = ">=3.8" +authors = [{ name = "YOUR NAME", email = "YOUREMAIL@domain.com" }] +keywords = [ + "machine learning", + "reproducibility", + "visualization", + "gradio", + "gradio custom component", +] +# Add dependencies here +dependencies = ["gradio"] +classifiers = [ + 'Development Status :: 3 - Alpha', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Topic :: Scientific/Engineering', + 'Topic :: Scientific/Engineering :: Artificial Intelligence', + 'Topic :: Scientific/Engineering :: Visualization', +] + +[project.optional-dependencies] +dev = ["build", "twine"] + +[tool.hatch.build] +artifacts = ["/backend/<>/templates", "*.pyi"] + +[tool.hatch.build.targets.wheel] +packages = ["/backend/<>"] diff --git a/gradio/cli/commands/components/show.py b/gradio/cli/commands/components/show.py new file mode 100644 index 000000000000..6834dcf77646 --- /dev/null +++ b/gradio/cli/commands/components/show.py @@ -0,0 +1,68 @@ +import inspect + +from rich.console import Console +from rich.table import Table + +import gradio._simple_templates +import gradio.components +import gradio.layouts +from gradio.blocks import BlockContext +from gradio.components import Component, FormComponent + +_IGNORE = { + "Text", + "Dataframe", + "Highlightedtext", + "Annotatedimage", + "Checkboxgroup", + "Json", + "Highlight", + "Component", + "Form", + "Dataset", + "FormComponent", + "Fallback", + "State", +} + + +def _get_table_items(module): + items = [] + for name in module.__all__: + gr_cls = getattr(module, name) + if not ( + inspect.isclass(gr_cls) and issubclass(gr_cls, (Component, BlockContext)) + ) or (name in _IGNORE): + continue + tags = [] + if "Simple" in name or name in {"File"}: + tags.append("🌱🤝Beginner Friendly🌱🤝") + if issubclass(gr_cls, FormComponent): + tags.append("📝🧩Form Component📝🧩") + if name in gradio.layouts.__all__: + tags.append("📐Layout📐") + doc = inspect.getdoc(gr_cls) or "No description available." + doc = doc.split(".")[0] + if tags: + doc = f"[{', '.join(tags)}]" + " " + doc + items.append((name, doc)) + + return items + + +def _show(): + items = ( + _get_table_items(gradio._simple_templates) + + _get_table_items(gradio.components) + + _get_table_items(gradio.layouts) + ) + table = Table(show_header=True, header_style="orange1", show_lines=True) + table.add_column("Name", justify="center") + table.add_column("Description", justify="center") + + for item in items: + table.add_row(*item) + + console = Console() + with console.pager(): + console.print(table) diff --git a/gradio/deploy_space.py b/gradio/cli/commands/deploy_space.py similarity index 93% rename from gradio/deploy_space.py rename to gradio/cli/commands/deploy_space.py index 9014b4e24ea2..c82564456335 100644 --- a/gradio/deploy_space.py +++ b/gradio/cli/commands/deploy_space.py @@ -1,10 +1,13 @@ from __future__ import annotations -import argparse import os import re +from typing import Optional import huggingface_hub +from rich import print +from typer import Option +from typing_extensions import Annotated import gradio as gr @@ -115,17 +118,16 @@ def format_title(title: str): return title -def deploy(): +def deploy( + title: Annotated[Optional[str], Option(help="Spaces app title")] = None, + app_file: Annotated[ + Optional[str], Option(help="File containing the Gradio app") + ] = None, +): if ( os.getenv("SYSTEM") == "spaces" ): # in case a repo with this function is uploaded to spaces return - parser = argparse.ArgumentParser(description="Deploy to Spaces") - parser.add_argument("deploy") - parser.add_argument("--title", type=str, help="Spaces app title") - parser.add_argument("--app-file", type=str, help="File containing the Gradio app") - - args = parser.parse_args() hf_api = huggingface_hub.HfApi() whoami = None @@ -153,8 +155,8 @@ def deploy(): f"Creating new Spaces Repo in '{repo_directory}'. Collecting metadata, press Enter to accept default value." ) configuration = add_configuration_to_readme( - args.title, - args.app_file, + title, + app_file, ) space_id = huggingface_hub.create_repo( diff --git a/gradio/cli/commands/display.py b/gradio/cli/commands/display.py new file mode 100644 index 000000000000..c3e5c87c48f9 --- /dev/null +++ b/gradio/cli/commands/display.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +import time +from types import TracebackType +from typing import Optional + +from rich.live import Live +from rich.panel import Panel + + +class LivePanelDisplay: + def __init__(self, msg: str | None = None) -> None: + self.lines = [msg] if msg else [] + self._panel = Live(Panel("\n".join(self.lines)), refresh_per_second=5) + + def update(self, msg: str, add_sleep: float | None = None): + self.lines.append(msg) + self._panel.update(Panel("\n".join(self.lines))) + if add_sleep: + time.sleep(add_sleep) + + def __enter__(self) -> LivePanelDisplay: + self._panel.__enter__() + return self + + def __exit__( + self, + exc_type: Optional[type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self._panel.stop() diff --git a/gradio/reload.py b/gradio/cli/commands/reload.py similarity index 62% rename from gradio/reload.py rename to gradio/cli/commands/reload.py index c072c4c0e5f3..a337f9faa036 100644 --- a/gradio/reload.py +++ b/gradio/cli/commands/reload.py @@ -5,6 +5,8 @@ $ gradio app.py, to run app.py in reload mode where any changes in the app.py file or Gradio library reloads the demo. $ gradio app.py my_demo, to use variable names other than "demo" """ +from __future__ import annotations + import inspect import os import re @@ -12,6 +14,10 @@ import sys import threading from pathlib import Path +from typing import List, Optional + +import typer +from rich import print import gradio from gradio import utils @@ -19,21 +25,12 @@ reload_thread = threading.local() -def _setup_config(): - args = sys.argv[1:] - if len(args) == 0: - raise ValueError("No file specified.") - if len(args) == 1 or args[1].startswith("--"): - demo_name = "demo" - else: - demo_name = args[1] - if "." in demo_name: - demo_name = demo_name.split(".")[0] - print( - "\nWARNING: As of Gradio 3.41.0, the parameter after the file path must be the name of the Gradio demo, not the FastAPI app. In most cases, this just means you should remove '.app' after the name of your demo, e.g. 'demo.app' -> 'demo'." - ) - - original_path = args[0] +def _setup_config( + demo_path: Path, + demo_name: str = "demo", + additional_watch_dirs: list[str] | None = None, +): + original_path = demo_path app_text = Path(original_path).read_text() patterns = [ @@ -42,21 +39,18 @@ def _setup_config(): f"{demo_name} = gr\\.Interface", f"{demo_name} = gr\\.ChatInterface", f"{demo_name} = gr\\.Series", - f"{demo_name} = gr\\.Paralles", + f"{demo_name} = gr\\.Parallel", f"{demo_name} = gr\\.TabbedInterface", ] if not any(re.search(p, app_text) for p in patterns): print( - f"\nWarning: Cannot statically find a gradio demo called {demo_name}. " + f"\n[bold red]Warning[/]: Cannot statically find a gradio demo called {demo_name}. " "Reload work may fail." ) abs_original_path = utils.abspath(original_path) - path = os.path.normpath(original_path) - path = path.replace("/", ".") - path = path.replace("\\", ".") - filename = os.path.splitext(path)[0] + filename = Path(original_path).stem gradio_folder = Path(inspect.getfile(gradio)).parent @@ -76,6 +70,21 @@ def _setup_config(): message += "," message += f" '{abs_parent}'" + abs_parent = Path(".").resolve() + if str(abs_parent).strip(): + watching_dirs.append(abs_parent) + if message_change_count == 1: + message += "," + message += f" '{abs_parent}'" + + for wd in additional_watch_dirs or []: + if Path(wd) not in watching_dirs: + watching_dirs.append(wd) + + if message_change_count == 1: + message += "," + message += f" '{wd}'" + print(message + "\n") # guaranty access to the module of an app @@ -83,13 +92,16 @@ def _setup_config(): return filename, abs_original_path, [str(s) for s in watching_dirs], demo_name -def main(): +def main( + demo_path: Path, demo_name: str = "demo", watch_dirs: Optional[List[str]] = None +): # default execution pattern to start the server and watch changes - filename, path, watch_dirs, demo_name = _setup_config() - args = sys.argv[1:] - extra_args = args[1:] if len(args) == 1 or args[1].startswith("--") else args[2:] + filename, path, watch_dirs, demo_name = _setup_config( + demo_path, demo_name, watch_dirs + ) + # extra_args = args[1:] if len(args) == 1 or args[1].startswith("--") else args[2:] popen = subprocess.Popen( - ["python", path] + extra_args, + [sys.executable, "-u", path], env=dict( os.environ, GRADIO_WATCH_DIRS=",".join(watch_dirs), @@ -101,4 +113,4 @@ def main(): if __name__ == "__main__": - main() + typer.run(main) diff --git a/js/state/index.svelte b/gradio/cli/commands/utils.py similarity index 100% rename from js/state/index.svelte rename to gradio/cli/commands/utils.py diff --git a/gradio/component_meta.py b/gradio/component_meta.py new file mode 100644 index 000000000000..580e25582f5e --- /dev/null +++ b/gradio/component_meta.py @@ -0,0 +1,188 @@ +from __future__ import annotations + +import ast +import inspect +from abc import ABCMeta +from functools import wraps +from pathlib import Path + +from jinja2 import Template + +from gradio.data_classes import GradioModel, GradioRootModel +from gradio.events import EventListener +from gradio.exceptions import ComponentDefinitionError + +INTERFACE_TEMPLATE = ''' +{{ contents }} + + {% for event in events %} + def {{ event }}(self, + fn: Callable | None, + inputs: Component | Sequence[Component] | set[Component] | None = None, + outputs: Component | Sequence[Component] | None = None, + api_name: str | None | Literal[False] = None, + status_tracker: None = None, + scroll_to_output: bool = False, + show_progress: Literal["full", "minimal", "hidden"] = "full", + queue: bool | None = None, + batch: bool = False, + max_batch_size: int = 4, + preprocess: bool = True, + postprocess: bool = True, + cancels: dict[str, Any] | list[dict[str, Any]] | None = None, + every: float | None = None, + _js: str | None = None,) -> Dependency: + """ + Parameters: + fn: the function to call when this event is triggered. Often a machine learning model's prediction function. Each parameter of the function corresponds to one input component, and the function should return a single value or a tuple of values, with each element in the tuple corresponding to one output component. + inputs: List of gradio.components to use as inputs. If the function takes no inputs, this should be an empty list. + outputs: List of gradio.components to use as outputs. If the function returns no outputs, this should be an empty list. + api_name: Defines how the endpoint appears in the API docs. Can be a string, None, or False. If False, the endpoint will not be exposed in the api docs. If set to None, the endpoint will be exposed in the api docs as an unnamed endpoint, although this behavior will be changed in Gradio 4.0. If set to a string, the endpoint will be exposed in the api docs with the given name. + scroll_to_output: If True, will scroll to output component on completion + show_progress: If True, will show progress animation while pending + queue: If True, will place the request on the queue, if the queue has been enabled. If False, will not put this event on the queue, even if the queue has been enabled. If None, will use the queue setting of the gradio app. + batch: If True, then the function should process a batch of inputs, meaning that it should accept a list of input values for each parameter. The lists should be of equal length (and be up to length `max_batch_size`). The function is then *required* to return a tuple of lists (even if there is only 1 output component), with each list in the tuple corresponding to one output component. + max_batch_size: Maximum number of inputs to batch together if this is called from the queue (only relevant if batch=True) + preprocess: If False, will not run preprocessing of component data before running 'fn' (e.g. leaving it as a base64 string if this method is called with the `Image` component). + postprocess: If False, will not run postprocessing of component data before returning 'fn' output to the browser. + cancels: A list of other events to cancel when this listener is triggered. For example, setting cancels=[click_event] will cancel the click_event, where click_event is the return value of another components .click method. Functions that have not yet run (or generators that are iterating) will be cancelled, but functions that are currently running will be allowed to finish. + every: Run this event 'every' number of seconds while the client connection is open. Interpreted in seconds. Queue must be enabled. + """ + ... + {% endfor %} +''' + + +def serializes(f): + @wraps(f) + def serialize(*args, **kwds): + output = f(*args, **kwds) + if isinstance(output, (GradioRootModel, GradioModel)): + output = output.model_dump() + return output + + return serialize + + +def create_pyi(class_code: str, events: list[EventListener | str]): + template = Template(INTERFACE_TEMPLATE) + events = [e if isinstance(e, str) else e.event_name for e in events] + return template.render(events=events, contents=class_code) + + +def extract_class_source_code( + code: str, class_name: str +) -> tuple[str, int] | tuple[None, None]: + class_start_line = code.find(f"class {class_name}") + if class_start_line == -1: + return None, None + + class_ast = ast.parse(code) + for node in ast.walk(class_ast): + if isinstance(node, ast.ClassDef) and node.name == class_name: + segment = ast.get_source_segment(code, node) + assert segment + return segment, node.lineno + return None, None + + +def create_or_modify_pyi( + component_class: type, class_name: str, events: list[str | EventListener] +): + source_file = Path(inspect.getfile(component_class)) + + source_code = source_file.read_text() + + current_impl, lineno = extract_class_source_code(source_code, class_name) + + assert current_impl + assert lineno + new_interface = create_pyi(current_impl, events) + + pyi_file = source_file.with_suffix(".pyi") + if not pyi_file.exists(): + last_empty_line_before_class = -1 + lines = source_code.splitlines() + for i, line in enumerate(lines): + if line in ["", " "]: + last_empty_line_before_class = i + if i >= lineno: + break + lines = ( + lines[:last_empty_line_before_class] + + ["from gradio.events import Dependency"] + + lines[last_empty_line_before_class:] + ) + pyi_file.write_text("\n".join(lines)) + current_interface, _ = extract_class_source_code(pyi_file.read_text(), class_name) + if not current_interface: + with open(str(pyi_file), mode="a") as f: + f.write(new_interface) + else: + contents = pyi_file.read_text() + contents = contents.replace(current_interface, new_interface.strip()) + pyi_file.write_text(contents) + + +def in_event_listener(): + from gradio.context import LocalContext + + return LocalContext.in_event_listener.get() + + +def updateable(fn): + @wraps(fn) + def wrapper(*args, **kwargs): + fn_args = inspect.getfullargspec(fn).args + self = args[0] + for i, arg in enumerate(args): + if i == 0 or i >= len(fn_args): # skip self, *args + continue + arg_name = fn_args[i] + kwargs[arg_name] = arg + self.constructor_args = kwargs + if in_event_listener(): + return None + else: + return fn(self, **kwargs) + + return wrapper + + +class ComponentMeta(ABCMeta): + def __new__(cls, name, bases, attrs): + if "__init__" in attrs: + attrs["__init__"] = updateable(attrs["__init__"]) + if "EVENTS" not in attrs: + found = False + for base in bases: + if hasattr(base, "EVENTS"): + found = True + break + if not found: + raise ComponentDefinitionError( + f"{name} or its base classes must define an EVENTS list. " + "If no events are supported, set it to an empty list." + ) + events = attrs.get("EVENTS", []) + if not all(isinstance(e, (str, EventListener)) for e in events): + raise ComponentDefinitionError( + f"All events for {name} must either be an string or an instance " + "of EventListener." + ) + new_events = [] + for event in events: + trigger = ( + event + if isinstance(event, EventListener) + else EventListener(event_name=event) + ) + new_events.append(trigger) + attrs[event] = trigger.listener + if "EVENTS" in attrs: + attrs["EVENTS"] = new_events + if "postprocess" in attrs: + attrs["postprocess"] = serializes(attrs["postprocess"]) + component_class = super().__new__(cls, name, bases, attrs) + create_or_modify_pyi(component_class, name, events) + return component_class diff --git a/gradio/components/__init__.py b/gradio/components/__init__.py index e98213f21e81..7a0ac3b65e7f 100644 --- a/gradio/components/__init__.py +++ b/gradio/components/__init__.py @@ -2,18 +2,15 @@ from gradio.components.audio import Audio from gradio.components.bar_plot import BarPlot from gradio.components.base import ( - Column, Component, - Form, FormComponent, - IOComponent, - Row, + StreamingInput, + StreamingOutput, _Keywords, component, get_component_instance, ) from gradio.components.button import Button -from gradio.components.carousel import Carousel from gradio.components.chatbot import Chatbot from gradio.components.checkbox import Checkbox from gradio.components.checkboxgroup import CheckboxGroup @@ -24,13 +21,13 @@ from gradio.components.dataset import Dataset from gradio.components.dropdown import Dropdown from gradio.components.duplicate_button import DuplicateButton +from gradio.components.fallback import Fallback from gradio.components.file import File from gradio.components.file_explorer import FileExplorer from gradio.components.gallery import Gallery from gradio.components.highlighted_text import HighlightedText from gradio.components.html import HTML from gradio.components.image import Image -from gradio.components.interpretation import Interpretation from gradio.components.json_component import JSON from gradio.components.label import Label from gradio.components.line_plot import LinePlot @@ -43,12 +40,11 @@ from gradio.components.radio import Radio from gradio.components.scatter_plot import ScatterPlot from gradio.components.slider import Slider -from gradio.components.state import State, Variable -from gradio.components.status_tracker import StatusTracker +from gradio.components.state import State from gradio.components.textbox import Textbox -from gradio.components.timeseries import Timeseries from gradio.components.upload_button import UploadButton from gradio.components.video import Video +from gradio.layouts import Form Text = Textbox DataFrame = Dataframe @@ -56,14 +52,12 @@ Annotatedimage = AnnotatedImage Highlight = HighlightedText Checkboxgroup = CheckboxGroup -TimeSeries = Timeseries Json = JSON __all__ = [ "Audio", "BarPlot", "Button", - "Carousel", "Chatbot", "ClearButton", "Component", @@ -74,19 +68,17 @@ "CheckboxGroup", "Code", "ColorPicker", - "Column", "Dataframe", "DataFrame", "Dataset", "DuplicateButton", + "Fallback", "Form", "FormComponent", "Gallery", "HTML", "FileExplorer", "Image", - "IOComponent", - "Interpretation", "JSON", "Json", "Label", @@ -101,22 +93,19 @@ "HighlightedText", "AnnotatedImage", "CheckboxGroup", - "Timeseries", "Text", "Highlightedtext", "Annotatedimage", "Highlight", "Checkboxgroup", - "TimeSeries", "Number", "Plot", "Radio", - "Row", "ScatterPlot", "Slider", "State", - "Variable", - "StatusTracker", "UploadButton", "Video", + "StreamingInput", + "StreamingOutput", ] diff --git a/gradio/components/annotated_image.py b/gradio/components/annotated_image.py index b3034c17e5f4..4012cf3907b9 100644 --- a/gradio/components/annotated_image.py +++ b/gradio/components/annotated_image.py @@ -2,29 +2,34 @@ from __future__ import annotations -import warnings -from typing import Literal +from typing import Any, List import numpy as np from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import JSONSerializable from PIL import Image as _Image # using _ to minimize namespace pollution -from gradio import utils -from gradio.components.base import IOComponent, _Keywords -from gradio.deprecation import warn_style_method_deprecation -from gradio.events import ( - EventListenerMethod, - Selectable, -) +from gradio import processing_utils, utils +from gradio.components.base import Component +from gradio.data_classes import FileData, GradioModel +from gradio.events import Events set_documentation_group("component") _Image.init() # fixes https://github.com/gradio-app/gradio/issues/2843 +class Annotation(GradioModel): + image: FileData + label: str + + +class AnnotatedImageData(GradioModel): + image: FileData + annotations: List[Annotation] + + @document() -class AnnotatedImage(Selectable, IOComponent, JSONSerializable): +class AnnotatedImage(Component): """ Displays a base image and colored subsections on top of that image. Subsections can take the from of rectangles (e.g. object detection) or masks (e.g. image segmentation). Preprocessing: this component does *not* accept input. @@ -33,6 +38,10 @@ class AnnotatedImage(Selectable, IOComponent, JSONSerializable): Demos: image_segmentation """ + EVENTS = [Events.select] + + data_model = AnnotatedImageData + def __init__( self, value: tuple[ @@ -54,7 +63,9 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, - **kwargs, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, ): """ Parameters: @@ -63,7 +74,7 @@ def __init__( height: Height of the displayed image. width: Width of the displayed image. color_map: A dictionary mapping labels to colors. The colors must be specified as hex codes. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. container: If True, will place the component in a container - providing some extra padding around the border. @@ -72,19 +83,14 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. """ self.show_legend = show_legend self.height = height self.width = width self.color_map = color_map - self.select: EventListenerMethod - """ - Event listener for when the user selects Image subsection. - Uses event data gradio.SelectData to carry `value` referring to selected subsection label, and `index` to refer to subsection index. - See EventData documentation on how to use this event data. - """ - IOComponent.__init__( - self, + super().__init__( label=label, every=every, show_label=show_label, @@ -94,46 +100,11 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, - ) - - @staticmethod - def update( - value: tuple[ - np.ndarray | _Image.Image | str, - list[tuple[np.ndarray | tuple[int, int, int, int], str]], - ] - | Literal[_Keywords.NO_VALUE] = _Keywords.NO_VALUE, - show_legend: bool | None = None, - height: int | None = None, - width: int | None = None, - color_map: dict[str, str] | None = None, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - visible: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.AnnotatedImage(...)` instead of `return gr.AnnotatedImage.update(...)`." ) - updated_config = { - "show_legend": show_legend, - "height": height, - "width": width, - "color_map": color_map, - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "value": value, - "__type__": "update", - } - return updated_config def postprocess( self, @@ -141,7 +112,7 @@ def postprocess( np.ndarray | _Image.Image | str, list[tuple[np.ndarray | tuple[int, int, int, int], str]], ], - ) -> tuple[dict, list[tuple[dict, str]]] | None: + ) -> AnnotatedImageData | None: """ Parameters: y: Tuple of base image and list of subsections, with each subsection a two-part tuple where the first element is a 4 element bounding box or a 0-1 confidence mask, and the second element is the label. @@ -155,17 +126,20 @@ def postprocess( base_img_path = base_img base_img = np.array(_Image.open(base_img)) elif isinstance(base_img, np.ndarray): - base_file = self.img_array_to_temp_file(base_img, dir=self.DEFAULT_TEMP_DIR) + base_file = processing_utils.save_img_array_to_cache( + base_img, cache_dir=self.GRADIO_CACHE + ) base_img_path = str(utils.abspath(base_file)) elif isinstance(base_img, _Image.Image): - base_file = self.pil_to_temp_file(base_img, dir=self.DEFAULT_TEMP_DIR) + base_file = processing_utils.save_pil_to_cache( + base_img, cache_dir=self.GRADIO_CACHE + ) base_img_path = str(utils.abspath(base_file)) base_img = np.array(base_img) else: raise ValueError( "AnnotatedImage only accepts filepaths, PIL images or numpy arrays for the base image." ) - self.temp_files.add(base_img_path) sections = [] color_map = self.color_map or {} @@ -203,34 +177,23 @@ def hex_to_rgb(value): colored_mask_img = _Image.fromarray((colored_mask).astype(np.uint8)) - mask_file = self.pil_to_temp_file( - colored_mask_img, dir=self.DEFAULT_TEMP_DIR + mask_file = processing_utils.save_pil_to_cache( + colored_mask_img, cache_dir=self.GRADIO_CACHE ) mask_file_path = str(utils.abspath(mask_file)) - self.temp_files.add(mask_file_path) - sections.append( - ({"name": mask_file_path, "data": None, "is_file": True}, label) + Annotation( + image=FileData(name=mask_file_path, is_file=True), label=label + ) ) - return {"name": base_img_path, "data": None, "is_file": True}, sections + return AnnotatedImageData( + image=FileData(name=base_img_path, data=None, is_file=True), + annotations=sections, + ) - def style( - self, - *, - height: int | None = None, - width: int | None = None, - color_map: dict[str, str] | None = None, - **kwargs, - ): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if height is not None: - self.height = height - if width is not None: - self.width = width - if color_map is not None: - self.color_map = color_map - return self + def example_inputs(self) -> Any: + return {} + + def preprocess(self, x: Any) -> Any: + return x diff --git a/gradio/components/audio.py b/gradio/components/audio.py index 8e78a7d8d468..48c553f38c99 100644 --- a/gradio/components/audio.py +++ b/gradio/components/audio.py @@ -2,46 +2,32 @@ from __future__ import annotations -import tempfile -import warnings from pathlib import Path from typing import Any, Callable, Literal import numpy as np import requests -from gradio_client import media_data from gradio_client import utils as client_utils from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import FileSerializable from gradio import processing_utils, utils -from gradio.components.base import IOComponent, _Keywords -from gradio.events import ( - Changeable, - Clearable, - Playable, - Recordable, - Streamable, - StreamableOutput, - Uploadable, -) -from gradio.interpretation import TokenInterpretable +from gradio.components.base import Component, StreamingInput, StreamingOutput +from gradio.data_classes import FileData +from gradio.events import Events set_documentation_group("component") +class AudioInputData(FileData): + crop_min: int = 0 + crop_max: int = 100 + + @document() class Audio( - Changeable, - Clearable, - Playable, - Recordable, - Streamable, - StreamableOutput, - Uploadable, - IOComponent, - FileSerializable, - TokenInterpretable, + StreamingInput, + StreamingOutput, + Component, ): """ Creates an audio component that can be used to upload/record audio (as an input) or display audio (as an output). @@ -52,6 +38,21 @@ class Audio( Guides: real-time-speech-recognition """ + EVENTS = [ + Events.stream, + Events.change, + Events.clear, + Events.play, + Events.pause, + Events.stop, + Events.pause, + Events.start_recording, + Events.stop_recording, + Events.upload, + ] + + data_model = FileData + def __init__( self, value: str | Path | tuple[int, np.ndarray] | Callable | None = None, @@ -69,19 +70,21 @@ def __init__( streaming: bool = False, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, format: Literal["wav", "mp3"] = "wav", autoplay: bool = False, show_download_button=True, show_share_button: bool | None = None, show_edit_button: bool | None = True, - **kwargs, ): """ Parameters: value: A path, URL, or [sample_rate, numpy array] tuple (sample rate in Hz, audio data as a float or int numpy array) for the default value that Audio component is going to take. If callable, the function will be called whenever the app loads to set the initial value of the component. source: Source of audio. "upload" creates a box where user can drop an audio file, "microphone" creates a microphone input. type: The format the audio file is converted to before being passed into the prediction function. "numpy" converts the audio to a tuple consisting of: (int sample rate, numpy.array for the data), "filepath" passes a str path to a temporary file containing the audio. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. container: If True, will place the component in a container - providing some extra padding around the border. @@ -92,6 +95,8 @@ def __init__( streaming: If set to True when used in a `live` interface as an input, will automatically stream webcam feed. When used set as an output, takes audio chunks yield from the backend and combines them into one streaming audio output. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. format: The file format to save audio files. Either 'wav' or 'mp3'. wav files are lossless but will tend to be larger files. mp3 files tend to be smaller. Default is wav. Applies both when this component is used as an input (when `type` is "format") and when this component is used as an output. autoplay: Whether to automatically play the audio when the component is used as an output. Note: browsers will not autoplay audio files if the user has not interacted with the page yet. show_download_button: If True, will show a download button in the corner of the component for saving audio. If False, icon does not appear. @@ -125,8 +130,7 @@ def __init__( else show_share_button ) self.show_edit_button = show_edit_button - IOComponent.__init__( - self, + super().__init__( label=label, every=every, show_label=show_label, @@ -137,52 +141,14 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - TokenInterpretable.__init__(self) - def example_inputs(self) -> dict[str, Any]: - return { - "raw": {"is_file": False, "data": media_data.BASE64_AUDIO}, - "serialized": "https://github.com/gradio-app/gradio/raw/main/test/test_files/audio_sample.wav", - } - - @staticmethod - def update( - value: Any | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - source: Literal["upload", "microphone"] | None = None, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - interactive: bool | None = None, - visible: bool | None = None, - autoplay: bool | None = None, - show_download_button: bool | None = None, - show_share_button: bool | None = None, - show_edit_button: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Audio(...)` instead of `return gr.Audio.update(...)`." - ) - return { - "source": source, - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "interactive": interactive, - "visible": visible, - "value": value, - "autoplay": autoplay, - "show_download_button": show_download_button, - "show_share_button": show_share_button, - "show_edit_button": show_edit_button, - "__type__": "update", - } + def example_inputs(self) -> Any: + return "https://github.com/gradio-app/gradio/raw/main/test/test_files/audio_sample.wav" def preprocess( self, x: dict[str, Any] | None @@ -195,33 +161,23 @@ def preprocess( """ if x is None: return x - file_name, file_data, is_file = ( - x["name"], - x["data"], - x.get("is_file", False), - ) - crop_min, crop_max = x.get("crop_min", 0), x.get("crop_max", 100) - if is_file: - if client_utils.is_http_url_like(file_name): - temp_file_path = self.download_temp_copy_if_needed(file_name) - else: - temp_file_path = self.make_temp_copy_if_needed(file_name) - else: - temp_file_path = self.base64_to_temp_file_if_needed(file_data, file_name) - sample_rate, data = processing_utils.audio_from_file( - temp_file_path, crop_min=crop_min, crop_max=crop_max - ) + payload: AudioInputData = AudioInputData(**x) + assert payload.name # Need a unique name for the file to avoid re-using the same audio file if # a user submits the same audio file twice, but with different crop min/max. - temp_file_path = Path(temp_file_path) + temp_file_path = Path(payload.name) output_file_name = str( temp_file_path.with_name( - f"{temp_file_path.stem}-{crop_min}-{crop_max}{temp_file_path.suffix}" + f"{temp_file_path.stem}-{payload.crop_min}-{payload.crop_max}{temp_file_path.suffix}" ) ) + sample_rate, data = processing_utils.audio_from_file( + temp_file_path, crop_min=payload.crop_min, crop_max=payload.crop_max + ) + if self.type == "numpy": return sample_rate, data elif self.type == "filepath": @@ -237,91 +193,9 @@ def preprocess( + ". Please choose from: 'numpy', 'filepath'." ) - def set_interpret_parameters(self, segments: int = 8): - """ - Calculates interpretation score of audio subsections by splitting the audio into subsections, then using a "leave one out" method to calculate the score of each subsection by removing the subsection and measuring the delta of the output value. - Parameters: - segments: Number of interpretation segments to split audio into. - """ - self.interpretation_segments = segments - return self - - def tokenize(self, x): - if x.get("is_file"): - sample_rate, data = processing_utils.audio_from_file(x["name"]) - else: - file_name = self.base64_to_temp_file_if_needed(x["data"]) - sample_rate, data = processing_utils.audio_from_file(file_name) - leave_one_out_sets = [] - tokens = [] - masks = [] - duration = data.shape[0] - boundaries = np.linspace(0, duration, self.interpretation_segments + 1).tolist() - boundaries = [round(boundary) for boundary in boundaries] - for index in range(len(boundaries) - 1): - start, stop = boundaries[index], boundaries[index + 1] - masks.append((start, stop)) - - # Handle the leave one outs - leave_one_out_data = np.copy(data) - leave_one_out_data[start:stop] = 0 - file = tempfile.NamedTemporaryFile( - delete=False, suffix=".wav", dir=self.DEFAULT_TEMP_DIR - ) - processing_utils.audio_to_file(sample_rate, leave_one_out_data, file.name) - out_data = client_utils.encode_file_to_base64(file.name) - leave_one_out_sets.append(out_data) - file.close() - Path(file.name).unlink() - - # Handle the tokens - token = np.copy(data) - token[0:start] = 0 - token[stop:] = 0 - file = tempfile.NamedTemporaryFile( - delete=False, suffix=".wav", dir=self.DEFAULT_TEMP_DIR - ) - processing_utils.audio_to_file(sample_rate, token, file.name) - token_data = client_utils.encode_file_to_base64(file.name) - file.close() - Path(file.name).unlink() - - tokens.append(token_data) - tokens = [{"name": "token.wav", "data": token} for token in tokens] - leave_one_out_sets = [ - {"name": "loo.wav", "data": loo_set} for loo_set in leave_one_out_sets - ] - return tokens, leave_one_out_sets, masks - - def get_masked_inputs(self, tokens, binary_mask_matrix): - # create a "zero input" vector and get sample rate - x = tokens[0]["data"] - file_name = self.base64_to_temp_file_if_needed(x) - sample_rate, data = processing_utils.audio_from_file(file_name) - zero_input = np.zeros_like(data, dtype="int16") - # decode all of the tokens - token_data = [] - for token in tokens: - file_name = self.base64_to_temp_file_if_needed(token["data"]) - _, data = processing_utils.audio_from_file(file_name) - token_data.append(data) - # construct the masked version - masked_inputs = [] - for binary_mask_vector in binary_mask_matrix: - masked_input = np.copy(zero_input) - for t, b in zip(token_data, binary_mask_vector): - masked_input = masked_input + t * int(b) - file = tempfile.NamedTemporaryFile(delete=False, dir=self.DEFAULT_TEMP_DIR) - processing_utils.audio_to_file(sample_rate, masked_input, file.name) - masked_data = client_utils.encode_file_to_base64(file.name) - file.close() - Path(file.name).unlink() - masked_inputs.append(masked_data) - return masked_inputs - def postprocess( self, y: tuple[int, np.ndarray] | str | Path | bytes | None - ) -> str | dict | bytes | None: + ) -> FileData | None | bytes: """ Parameters: y: audio data in either of the following formats: a tuple of (sample_rate, data), or a string filepath or URL to an audio file, or None. @@ -333,27 +207,23 @@ def postprocess( if isinstance(y, bytes): if self.streaming: return y - file_path = self.file_bytes_to_file(y, "audio") - elif isinstance(y, str) and client_utils.is_http_url_like(y): - return {"name": y, "data": None, "is_file": True} + file_path = processing_utils.save_bytes_to_cache( + y, "audio", cache_dir=self.GRADIO_CACHE + ) elif isinstance(y, tuple): sample_rate, data = y - file_path = self.audio_to_temp_file( - data, - sample_rate, - format=self.format, + file_path = processing_utils.save_audio_to_cache( + data, sample_rate, format=self.format, cache_dir=self.GRADIO_CACHE ) - self.temp_files.add(file_path) else: - file_path = self.make_temp_copy_if_needed(y) - return { - "name": file_path, - "data": None, - "is_file": True, - "orig_name": Path(file_path).name, - } + if not isinstance(y, (str, Path)): + raise ValueError(f"Cannot process {y} as Audio") + file_path = str(y) + return FileData(**{"name": file_path, "data": None, "is_file": True}) - def stream_output(self, y, output_id: str, first_chunk: bool): + def stream_output( + self, y, output_id: str, first_chunk: bool + ) -> tuple[bytes | None, Any]: output_file = { "name": output_id, "is_stream": True, @@ -385,11 +255,11 @@ def stream_output(self, y, output_id: str, first_chunk: bool): binary_data = binary_data[44:] return binary_data, output_file + def as_example(self, input_data: str | None) -> str: + return Path(input_data).name if input_data else "" + def check_streamable(self): - if self.source != "microphone": + if self.source != "microphone" and self.streaming: raise ValueError( "Audio streaming only available if source is 'microphone'." ) - - def as_example(self, input_data: str | None) -> str: - return Path(input_data).name if input_data else "" diff --git a/gradio/components/bar_plot.py b/gradio/components/bar_plot.py index f16bd4c379e1..1567b1afb247 100644 --- a/gradio/components/bar_plot.py +++ b/gradio/components/bar_plot.py @@ -2,15 +2,13 @@ from __future__ import annotations -import warnings -from typing import Callable, Literal +from typing import Any, Callable, Literal import altair as alt import pandas as pd from gradio_client.documentation import document, set_documentation_group -from gradio.components.base import _Keywords -from gradio.components.plot import AltairPlot, Plot +from gradio.components.plot import AltairPlot, AltairPlotData, Plot set_documentation_group("component") @@ -26,6 +24,8 @@ class BarPlot(Plot): Demos: bar_plot, chicago-bikeshare-dashboard """ + data_model = AltairPlotData + def __init__( self, value: pd.DataFrame | Callable | None = None, @@ -69,9 +69,11 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, sort: Literal["x", "y", "-x", "-y"] | None = None, show_actions_button: bool = False, - **kwargs, ): """ Parameters: @@ -101,6 +103,8 @@ def __init__( visible: Whether the plot should be visible. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. sort: Specifies the sorting axis as either "x", "y", "-x" or "-y". If None, no sorting is applied. show_actions_button: Whether to show the actions button on the top right corner of the plot. """ @@ -136,137 +140,15 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, every=every, - **kwargs, ) def get_block_name(self) -> str: return "plot" - @staticmethod - def update( - value: pd.DataFrame | dict | Literal[_Keywords.NO_VALUE] = _Keywords.NO_VALUE, - x: str | None = None, - y: str | None = None, - color: str | None = None, - vertical: bool = True, - group: str | None = None, - title: str | None = None, - tooltip: list[str] | str | None = None, - x_title: str | None = None, - y_title: str | None = None, - x_label_angle: float | None = None, - y_label_angle: float | None = None, - color_legend_title: str | None = None, - group_title: str | None = None, - color_legend_position: Literal[ - "left", - "right", - "top", - "bottom", - "top-left", - "top-right", - "bottom-left", - "bottom-right", - "none", - ] - | None = None, - height: int | None = None, - width: int | None = None, - y_lim: list[int] | None = None, - caption: str | None = None, - interactive: bool | None = None, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - visible: bool | None = None, - sort: Literal["x", "y", "-x", "-y"] | None = None, - ): - """Update an existing BarPlot component. - - If updating any of the plot properties (color, size, etc) the value, x, and y parameters must be specified. - - Parameters: - value: The pandas dataframe containing the data to display in a scatter plot. - x: Column corresponding to the x axis. - y: Column corresponding to the y axis. - color: The column to determine the bar color. Must be categorical (discrete values). - vertical: If True, the bars will be displayed vertically. If False, the x and y axis will be switched, displaying the bars horizontally. Default is True. - group: The column with which to split the overall plot into smaller subplots. - title: The title to display on top of the chart. - tooltip: The column (or list of columns) to display on the tooltip when a user hovers over a bar. - x_title: The title given to the x axis. By default, uses the value of the x parameter. - y_title: The title given to the y axis. By default, uses the value of the y parameter. - x_label_angle: The angle (in degrees) of the x axis labels. Positive values are clockwise, and negative values are counter-clockwise. - y_label_angle: The angle (in degrees) of the y axis labels. Positive values are clockwise, and negative values are counter-clockwise. - color_legend_title: The title given to the color legend. By default, uses the value of color parameter. - group_title: The label displayed on top of the subplot columns (or rows if vertical=True). Use an empty string to omit. - color_legend_position: The position of the color legend. If the string value 'none' is passed, this legend is omitted. For other valid position values see: https://vega.github.io/vega/docs/legends/#orientation. - height: The height of the plot in pixels. - width: The width of the plot in pixels. - y_lim: A tuple of list containing the limits for the y-axis, specified as [y_min, y_max]. - caption: The (optional) caption to display below the plot. - interactive: Whether users should be able to interact with the plot by panning or zooming with their mouse or trackpad. - label: The (optional) label to display on the top left corner of the plot. - show_label: Whether the label should be displayed. - visible: Whether the plot should be visible. - sort: Specifies the sorting axis as either "x", "y", "-x" or "-y". If None, no sorting is applied. - """ - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.BarPlot(...)` instead of `return gr.BarPlot.update(...)`." - ) - properties = [ - x, - y, - color, - vertical, - group, - title, - tooltip, - x_title, - y_title, - x_label_angle, - y_label_angle, - color_legend_title, - group_title, - color_legend_position, - height, - width, - y_lim, - interactive, - sort, - ] - if any(properties): - if not isinstance(value, pd.DataFrame): - raise ValueError( - "In order to update plot properties the value parameter " - "must be provided, and it must be a Dataframe. Please pass a value " - "parameter to gr.BarPlot.update." - ) - if x is None or y is None: - raise ValueError( - "In order to update plot properties, the x and y axis data " - "must be specified. Please pass valid values for x an y to " - "gr.BarPlot.update." - ) - chart = BarPlot.create_plot(value, *properties) - value = {"type": "altair", "plot": chart.to_json(), "chart": "bar"} - - updated_config = { - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "value": value, - "caption": caption, - "__type__": "update", - } - return updated_config - @staticmethod def create_plot( value: pd.DataFrame, @@ -339,8 +221,8 @@ def create_plot( y, # type: ignore title=y_title, # type: ignore scale=AltairPlot.create_scale(y_lim), # type: ignore - axis=alt.Axis(labelAngle=x_label_angle) - if x_label_angle is not None + axis=alt.Axis(labelAngle=y_label_angle) + if y_label_angle is not None else alt.Axis(), sort=sort if not vertical and sort is not None else None, ), @@ -380,7 +262,9 @@ def create_plot( return chart - def postprocess(self, y: pd.DataFrame | dict | None) -> dict[str, str] | None: + def postprocess( + self, y: pd.DataFrame | dict | None + ) -> AltairPlotData | dict | None: # if None or update if y is None or isinstance(y, dict): return y @@ -409,4 +293,12 @@ def postprocess(self, y: pd.DataFrame | dict | None) -> dict[str, str] | None: sort=self.sort, # type: ignore ) - return {"type": "altair", "plot": chart.to_json(), "chart": "bar"} + return AltairPlotData( + **{"type": "altair", "plot": chart.to_json(), "chart": "bar"} + ) + + def example_inputs(self) -> dict[str, Any]: + return {} + + def preprocess(self, x: Any) -> Any: + return x diff --git a/gradio/components/base.py b/gradio/components/base.py index 4f0b1cf1fed0..fb0429e6eb1a 100644 --- a/gradio/components/base.py +++ b/gradio/components/base.py @@ -4,34 +4,27 @@ from __future__ import annotations +import abc import hashlib +import json import os -import secrets -import shutil +import sys import tempfile -import urllib.request +import warnings +from abc import ABC, abstractmethod from enum import Enum from pathlib import Path from typing import TYPE_CHECKING, Any, Callable -import aiofiles -import numpy as np -import requests -from fastapi import UploadFile -from gradio_client import utils as client_utils from gradio_client.documentation import set_documentation_group -from gradio_client.serializing import ( - Serializable, -) from PIL import Image as _Image # using _ to minimize namespace pollution -from gradio import processing_utils, utils -from gradio.blocks import Block, BlockContext, Updateable -from gradio.deprecation import warn_deprecation, warn_style_method_deprecation -from gradio.events import ( - EventListener, -) -from gradio.layouts import Column, Form, Row +from gradio import utils +from gradio.blocks import Block, BlockContext +from gradio.component_meta import ComponentMeta +from gradio.data_classes import GradioDataModel +from gradio.events import EventListener +from gradio.layouts import Form if TYPE_CHECKING: from typing import TypedDict @@ -50,78 +43,82 @@ class _Keywords(Enum): FINISHED_ITERATING = "FINISHED_ITERATING" # Used to skip processing of a component's value (needed for generators + state) -class Component(Updateable, Block, Serializable): - """ - A base class for defining the methods that all gradio components should have. - """ - - def __init__(self, *args, **kwargs): - Block.__init__(self, *args, **kwargs) - EventListener.__init__(self) - self.server_fns = [ - value - for value in self.__class__.__dict__.values() - if callable(value) and getattr(value, "_is_server_fn", False) - ] - - def __str__(self): - return self.__repr__() - - def __repr__(self): - return f"{self.get_block_name()}" +class ComponentBase(ABC, metaclass=ComponentMeta): + EVENTS: list[EventListener | str] = [] + @abstractmethod def preprocess(self, x: Any) -> Any: """ Any preprocessing needed to be performed on function input. """ return x + @abstractmethod def postprocess(self, y): """ Any postprocessing needed to be performed on function output. """ return y - def style(self, *args, **kwargs): + @abstractmethod + def as_example(self, y): """ - This method is deprecated. Please set these arguments in the Components constructor instead. + Return the input data in a way that can be displayed by the examples dataset component in the front-end. + + For example, only return the name of a file as opposed to a full path. Or get the head of a dataframe. + Must be able to be converted to a string to put in the config. """ - warn_style_method_deprecation() - put_deprecated_params_in_box = False - if "rounded" in kwargs: - warn_deprecation( - "'rounded' styling is no longer supported. To round adjacent components together, place them in a Column(variant='box')." - ) - if isinstance(kwargs["rounded"], (list, tuple)): - put_deprecated_params_in_box = True - kwargs.pop("rounded") - if "margin" in kwargs: - warn_deprecation( - "'margin' styling is no longer supported. To place adjacent components together without margin, place them in a Column(variant='box')." - ) - if isinstance(kwargs["margin"], (list, tuple)): - put_deprecated_params_in_box = True - kwargs.pop("margin") - if "border" in kwargs: - warn_deprecation( - "'border' styling is no longer supported. To place adjacent components in a shared border, place them in a Column(variant='box')." - ) - kwargs.pop("border") - for key in kwargs: - warn_deprecation(f"Unknown style parameter: {key}") - if ( - put_deprecated_params_in_box - and isinstance(self.parent, (Row, Column)) - and self.parent.variant == "default" - ): - self.parent.variant = "compact" - return self + pass - def get_config(self): - config = super().get_config() - if len(self.server_fns): - config["server_fns"] = [fn.__name__ for fn in self.server_fns] - return config + @abstractmethod + def api_info(self) -> dict[str, list[str]]: + """ + The typing information for this component as a dictionary whose values are a list of 2 strings: [Python type, language-agnostic description]. + Keys of the dictionary are: raw_input, raw_output, serialized_input, serialized_output + """ + pass + + @abstractmethod + def example_inputs(self) -> Any: + """ + The example inputs for this component as a dictionary whose values are example inputs compatible with this component. + Keys of the dictionary are: raw, serialized + """ + pass + + @abstractmethod + def flag(self, x: Any | GradioDataModel, flag_dir: str | Path = "") -> str: + """ + Write the component's value to a format that can be stored in a csv or jsonl format for flagging. + """ + pass + + @abstractmethod + def read_from_flag( + self, + x: Any, + flag_dir: str | Path | None = None, + ) -> GradioDataModel | Any: + """ + Convert the data from the csv or jsonl file into the component state. + """ + return x + + @property + @abstractmethod + def skip_api(self): + """Whether this component should be skipped from the api return value""" + + @classmethod + def has_event(cls, event: str | EventListener) -> bool: + return event in cls.EVENTS + + @classmethod + def get_component_class_id(cls) -> str: + module_name = cls.__module__ + module_path = sys.modules[module_name].__file__ + module_hash = hashlib.md5(f"{cls.__name__}_{module_path}".encode()).hexdigest() + return module_hash def server(fn): @@ -129,7 +126,7 @@ def server(fn): return fn -class IOComponent(Component): +class Component(ComponentBase, Block): """ A base class for defining methods that all input/output components should have. """ @@ -148,31 +145,57 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, load_fn: Callable | None = None, every: float | None = None, - **kwargs, ): + self.server_fns = [ + value + for value in self.__class__.__dict__.values() + if callable(value) and getattr(value, "_is_server_fn", False) + ] + + # Svelte components expect elem_classes to be a list + # If we don't do this, returning a new component for an + # update will break the frontend + if not elem_classes: + elem_classes = [] + + # This gets overriden when `select` is called + self.selectable = False + if not hasattr(self, "data_model"): + self.data_model: type[GradioDataModel] | None = None self.temp_files: set[str] = set() - self.DEFAULT_TEMP_DIR = os.environ.get("GRADIO_TEMP_DIR") or str( + self.GRADIO_CACHE = os.environ.get("GRADIO_TEMP_DIR") or str( Path(tempfile.gettempdir()) / "gradio" ) - Component.__init__( - self, elem_id=elem_id, elem_classes=elem_classes, visible=visible, **kwargs + Block.__init__( + self, + elem_id=elem_id, + elem_classes=elem_classes, + visible=visible, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, ) + if isinstance(self, StreamingInput): + self.check_streamable() self.label = label self.info = info if not container: if show_label: - warn_deprecation("show_label has no effect when container is False.") + warnings.warn("show_label has no effect when container is False.") show_label = False if show_label is None: show_label = True self.show_label = show_label self.container = container if scale is not None and scale != round(scale): - warn_deprecation( + warnings.warn( f"'scale' value should be an integer. Using {scale} will cause issues." ) self.scale = scale @@ -181,7 +204,7 @@ def __init__( # load_event is set in the Blocks.attach_load_events method self.load_event: None | dict[str, Any] = None - self.load_event_to_attach = None + self.load_event_to_attach: None | tuple[Callable, float | None] = None load_fn, initial_value = self.get_load_fn_and_initial_value(value) self.value = ( initial_value @@ -191,160 +214,24 @@ def __init__( if callable(load_fn): self.attach_load_event(load_fn, every) - @staticmethod - def hash_file(file_path: str | Path, chunk_num_blocks: int = 128) -> str: - sha1 = hashlib.sha1() - with open(file_path, "rb") as f: - for chunk in iter(lambda: f.read(chunk_num_blocks * sha1.block_size), b""): - sha1.update(chunk) - return sha1.hexdigest() + self.component_class_id = self.__class__.get_component_class_id() - @staticmethod - def hash_url(url: str, chunk_num_blocks: int = 128) -> str: - sha1 = hashlib.sha1() - remote = urllib.request.urlopen(url) - max_file_size = 100 * 1024 * 1024 # 100MB - total_read = 0 - while True: - data = remote.read(chunk_num_blocks * sha1.block_size) - total_read += chunk_num_blocks * sha1.block_size - if not data or total_read > max_file_size: - break - sha1.update(data) - return sha1.hexdigest() + TEMPLATE_DIR = "./templates/" + FRONTEND_DIR = "../../frontend/" - @staticmethod - def hash_bytes(bytes: bytes): - sha1 = hashlib.sha1() - sha1.update(bytes) - return sha1.hexdigest() + def get_config(self): + config = super().get_config() + if self.info: + config["info"] = self.info + if len(self.server_fns): + config["server_fns"] = [fn.__name__ for fn in self.server_fns] + config.pop("_skip_init_processing", None) + config.pop("render", None) + return config - @staticmethod - def hash_base64(base64_encoding: str, chunk_num_blocks: int = 128) -> str: - sha1 = hashlib.sha1() - for i in range(0, len(base64_encoding), chunk_num_blocks * sha1.block_size): - data = base64_encoding[i : i + chunk_num_blocks * sha1.block_size] - sha1.update(data.encode("utf-8")) - return sha1.hexdigest() - - def make_temp_copy_if_needed(self, file_path: str | Path) -> str: - """Returns a temporary file path for a copy of the given file path if it does - not already exist. Otherwise returns the path to the existing temp file.""" - temp_dir = self.hash_file(file_path) - temp_dir = Path(self.DEFAULT_TEMP_DIR) / temp_dir - temp_dir.mkdir(exist_ok=True, parents=True) - - name = client_utils.strip_invalid_filename_characters(Path(file_path).name) - full_temp_file_path = str(utils.abspath(temp_dir / name)) - - if not Path(full_temp_file_path).exists(): - shutil.copy2(file_path, full_temp_file_path) - - self.temp_files.add(full_temp_file_path) - return full_temp_file_path - - async def save_uploaded_file(self, file: UploadFile, upload_dir: str) -> str: - temp_dir = secrets.token_hex( - 20 - ) # Since the full file is being uploaded anyways, there is no benefit to hashing the file. - temp_dir = Path(upload_dir) / temp_dir - temp_dir.mkdir(exist_ok=True, parents=True) - - if file.filename: - file_name = Path(file.filename).name - name = client_utils.strip_invalid_filename_characters(file_name) - else: - name = f"tmp{secrets.token_hex(5)}" - - full_temp_file_path = str(utils.abspath(temp_dir / name)) - - async with aiofiles.open(full_temp_file_path, "wb") as output_file: - while True: - content = await file.read(100 * 1024 * 1024) - if not content: - break - await output_file.write(content) - - return full_temp_file_path - - def download_temp_copy_if_needed(self, url: str) -> str: - """Downloads a file and makes a temporary file path for a copy if does not already - exist. Otherwise returns the path to the existing temp file.""" - temp_dir = self.hash_url(url) - temp_dir = Path(self.DEFAULT_TEMP_DIR) / temp_dir - temp_dir.mkdir(exist_ok=True, parents=True) - - name = client_utils.strip_invalid_filename_characters(Path(url).name) - full_temp_file_path = str(utils.abspath(temp_dir / name)) - - if not Path(full_temp_file_path).exists(): - with requests.get(url, stream=True) as r, open( - full_temp_file_path, "wb" - ) as f: - shutil.copyfileobj(r.raw, f) - - self.temp_files.add(full_temp_file_path) - return full_temp_file_path - - def base64_to_temp_file_if_needed( - self, base64_encoding: str, file_name: str | None = None - ) -> str: - """Converts a base64 encoding to a file and returns the path to the file if - the file doesn't already exist. Otherwise returns the path to the existing file. - """ - temp_dir = self.hash_base64(base64_encoding) - temp_dir = Path(self.DEFAULT_TEMP_DIR) / temp_dir - temp_dir.mkdir(exist_ok=True, parents=True) - - guess_extension = client_utils.get_extension(base64_encoding) - if file_name: - file_name = client_utils.strip_invalid_filename_characters(file_name) - elif guess_extension: - file_name = f"file.{guess_extension}" - else: - file_name = "file" - - full_temp_file_path = str(utils.abspath(temp_dir / file_name)) # type: ignore - - if not Path(full_temp_file_path).exists(): - data, _ = client_utils.decode_base64_to_binary(base64_encoding) - with open(full_temp_file_path, "wb") as fb: - fb.write(data) - - self.temp_files.add(full_temp_file_path) - return full_temp_file_path - - def pil_to_temp_file(self, img: _Image.Image, dir: str, format="png") -> str: - bytes_data = processing_utils.encode_pil_to_bytes(img, format) - temp_dir = Path(dir) / self.hash_bytes(bytes_data) - temp_dir.mkdir(exist_ok=True, parents=True) - filename = str(temp_dir / f"image.{format}") - img.save(filename, pnginfo=processing_utils.get_pil_metadata(img)) - return filename - - def img_array_to_temp_file(self, arr: np.ndarray, dir: str) -> str: - if arr.ndim not in (2, 3, 4): - raise ValueError( - "Input does not have the correct number of dimensions (2 for grayscale, 3 for RGB, 4 for RGBA)" - ) - pil_image = _Image.fromarray( - processing_utils._convert(arr, np.uint8, force_copy=False) - ) - return self.pil_to_temp_file(pil_image, dir, format="png") - - def audio_to_temp_file(self, data: np.ndarray, sample_rate: int, format: str): - temp_dir = Path(self.DEFAULT_TEMP_DIR) / self.hash_bytes(data.tobytes()) - temp_dir.mkdir(exist_ok=True, parents=True) - filename = str(temp_dir / f"audio.{format}") - processing_utils.audio_to_file(sample_rate, data, filename, format=format) - return filename - - def file_bytes_to_file(self, data: bytes, file_name: str): - path = Path(self.DEFAULT_TEMP_DIR) / self.hash_bytes(data) - path.mkdir(exist_ok=True, parents=True) - path = path / Path(file_name).name - path.write_bytes(data) - return path + @property + def skip_api(self): + return False @staticmethod def get_load_fn_and_initial_value(value): @@ -356,6 +243,12 @@ def get_load_fn_and_initial_value(value): load_fn = None return load_fn, initial_value + def __str__(self): + return self.__repr__() + + def __repr__(self): + return f"{self.get_block_name()}" + def attach_load_event(self, callable: Callable, every: float | None): """Add a load event that runs `callable`, optionally every `every` seconds.""" self.load_event_to_attach = (callable, every) @@ -364,18 +257,77 @@ def as_example(self, input_data): """Return the input data in a way that can be displayed by the examples dataset component in the front-end.""" return input_data + def api_info(self) -> dict[str, Any]: + """ + The typing information for this component as a dictionary whose values are a list of 2 strings: [Python type, language-agnostic description]. + Keys of the dictionary are: raw_input, raw_output, serialized_input, serialized_output + """ + if self.data_model is not None: + return self.data_model.model_json_schema() + raise NotImplementedError( + f"The api_info method has not been implemented for {self.get_block_name()}" + ) + + def flag(self, x: Any, flag_dir: str | Path = "") -> str: + """ + Write the component's value to a format that can be stored in a csv or jsonl format for flagging. + """ + if self.data_model: + x = self.data_model.from_json(x) + return x.copy_to_dir(flag_dir).model_dump_json() + return x + + def read_from_flag( + self, + x: Any, + flag_dir: str | Path | None = None, + ): + """ + Convert the data from the csv or jsonl file into the component state. + """ + if self.data_model: + return self.data_model.from_json(json.loads(x)) + return x -class FormComponent: + +class FormComponent(Component): def get_expected_parent(self) -> type[Form] | None: if getattr(self, "container", None) is False: return None return Form + def preprocess(self, x: Any) -> Any: + return x + + def postprocess(self, y): + return y + + +class StreamingOutput(metaclass=abc.ABCMeta): + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.streaming: bool + + @abc.abstractmethod + def stream_output(self, y, output_id: str, first_chunk: bool) -> tuple[bytes, Any]: + pass + + +class StreamingInput(metaclass=abc.ABCMeta): + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + @abc.abstractmethod + def check_streamable(self): + """Used to check if streaming is supported given the input.""" + pass + def component(cls_name: str, render: bool) -> Component: obj = utils.component_or_layout_class(cls_name)(render=render) if isinstance(obj, BlockContext): raise ValueError(f"Invalid component: {obj.__class__}") + assert isinstance(obj, Component) return obj @@ -403,8 +355,10 @@ def get_component_instance( raise ValueError( f"Component must provided as a `str` or `dict` or `Component` but is {comp}" ) + if render and not component_obj.is_rendered: component_obj.render() elif unrender and component_obj.is_rendered: component_obj.unrender() + assert isinstance(component_obj, Component) return component_obj diff --git a/gradio/components/button.py b/gradio/components/button.py index e64db3827b2e..f94d1fc9f62b 100644 --- a/gradio/components/button.py +++ b/gradio/components/button.py @@ -2,21 +2,18 @@ from __future__ import annotations -import warnings -from typing import Callable, Literal +from typing import Any, Callable, Literal from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import StringSerializable -from gradio.components.base import IOComponent, _Keywords -from gradio.deprecation import warn_deprecation, warn_style_method_deprecation -from gradio.events import Clickable +from gradio.components.base import Component +from gradio.events import Events set_documentation_group("component") @document() -class Button(Clickable, IOComponent, StringSerializable): +class Button(Component): """ Used to create a button, that can be assigned arbitrary click() events. The label (value) of the button can be used as an input or set via the output of a function. @@ -25,10 +22,13 @@ class Button(Clickable, IOComponent, StringSerializable): Demos: blocks_inputs, blocks_kinematics """ + EVENTS = [Events.click] + def __init__( self, value: str | Callable = "Run", *, + every: float | None = None, variant: Literal["primary", "secondary", "stop"] = "secondary", size: Literal["sm", "lg"] | None = None, icon: str | None = None, @@ -37,13 +37,16 @@ def __init__( interactive: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, scale: int | None = None, min_width: int | None = None, - **kwargs, ): """ Parameters: value: Default text for the button to display. If callable, the function will be called whenever the app loads to set the initial value of the component. + every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. variant: 'primary' for main call-to-action, 'secondary' for a more subdued style, 'stop' for a stop button. size: Size of the button. Can be "sm" or "lg". icon: URL or path to the icon file to display within the button. If None, no icon will be displayed. Must be within the working directory of the Gradio app or an external URL. @@ -52,73 +55,38 @@ def __init__( interactive: If False, the Button will be in a disabled state. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. scale: relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer. min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first. """ - IOComponent.__init__( - self, + super().__init__( + every=every, visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, interactive=interactive, scale=scale, min_width=min_width, - **kwargs, ) - if variant == "plain": - warn_deprecation("'plain' variant deprecated, using 'secondary' instead.") - variant = "secondary" self.variant = variant self.size = size self.icon = icon self.link = link - @staticmethod - def update( - value: str | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - variant: Literal["primary", "secondary", "stop"] | None = None, - size: Literal["sm", "lg"] | None = None, - icon: str | None = None, - link: str | None = None, - visible: bool | None = None, - interactive: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Button(...)` instead of `return gr.Button.update(...)`." - ) - return { - "variant": variant, - "size": size, - "visible": visible, - "value": value, - "icon": icon, - "link": link, - "interactive": interactive, - "scale": scale, - "min_width": min_width, - "__type__": "update", - } + @property + def skip_api(self): + return True - def style( - self, - *, - full_width: bool | None = None, - size: Literal["sm", "lg"] | None = None, - **kwargs, - ): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if full_width is not None: - warn_deprecation( - "Use `scale` in place of full_width in the constructor. " - "scale=1 will make the button expand, whereas 0 will not." - ) - self.scale = 1 if full_width else None - if size is not None: - self.size = size - return self + def preprocess(self, x: Any) -> Any: + return x + + def postprocess(self, y): + return y + + def example_inputs(self) -> Any: + return None diff --git a/gradio/components/carousel.py b/gradio/components/carousel.py deleted file mode 100644 index 00a064420f13..000000000000 --- a/gradio/components/carousel.py +++ /dev/null @@ -1,22 +0,0 @@ -"""gr.Carousel() component.""" - -from gradio_client.serializing import SimpleSerializable - -from gradio.components.base import IOComponent -from gradio.events import Changeable - - -class Carousel(IOComponent, Changeable, SimpleSerializable): - """ - Deprecated Component - """ - - def __init__( - self, - *args, - **kwargs, - ): - raise DeprecationWarning( - "The Carousel component is deprecated. Please consider using the Gallery " - "component, which can be used to display images (and optional captions).", - ) diff --git a/gradio/components/chatbot.py b/gradio/components/chatbot.py index 447d42d99c1d..f5f608370164 100644 --- a/gradio/components/chatbot.py +++ b/gradio/components/chatbot.py @@ -3,29 +3,38 @@ from __future__ import annotations import inspect -import warnings from pathlib import Path -from typing import Callable, Literal +from typing import Any, Callable, List, Literal, Optional, Tuple, Union from gradio_client import utils as client_utils from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import JSONSerializable from gradio import utils -from gradio.components.base import IOComponent, _Keywords -from gradio.deprecation import warn_deprecation, warn_style_method_deprecation -from gradio.events import ( - Changeable, - EventListenerMethod, - Likeable, - Selectable, -) +from gradio.components.base import Component +from gradio.data_classes import FileData, GradioModel, GradioRootModel +from gradio.events import Events + +# from pydantic import Field, TypeAdapter set_documentation_group("component") +class FileMessage(GradioModel): + file: FileData + alt_text: Optional[str] = None + + +# _Message = Annotated[List[Union[str, FileMessage, None]], Field(min_length=2, max_length=2)] + +# Message = TypeAdapter(_Message) + + +class ChatbotData(GradioRootModel): + root: List[Tuple[Union[str, FileMessage, None], Union[str, FileMessage, None]]] + + @document() -class Chatbot(Changeable, Selectable, Likeable, IOComponent, JSONSerializable): +class Chatbot(Component): """ Displays a chatbot output showing both user submitted messages and responses. Supports a subset of Markdown including bold, italics, code, tables. Also supports audio/video/image files, which are displayed in the Chatbot, and other kinds of files which are displayed as links. Preprocessing: passes the messages in the Chatbot as a {List[List[str | None | Tuple]]}, i.e. a list of lists. The inner list has 2 elements: the user message and the response message. See `Postprocessing` for the format of these messages. @@ -35,12 +44,14 @@ class Chatbot(Changeable, Selectable, Likeable, IOComponent, JSONSerializable): Guides: creating-a-chatbot """ + EVENTS = [Events.change, Events.select, Events.like] + data_model = ChatbotData + def __init__( self, value: list[list[str | tuple[str] | tuple[str | Path, str] | None]] | Callable | None = None, - color_map: dict[str, str] | None = None, *, label: str | None = None, every: float | None = None, @@ -51,6 +62,9 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, height: int | None = None, latex_delimiters: list[dict[str, str | bool]] | None = None, rtl: bool = False, @@ -62,13 +76,11 @@ def __init__( bubble_full_width: bool = True, line_breaks: bool = True, layout: Literal["panel", "bubble"] | None = None, - **kwargs, ): """ Parameters: value: Default value to show in chatbot. If callable, the function will be called whenever the app loads to set the initial value of the component. - color_map: This parameter is deprecated. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. container: If True, will place the component in a container - providing some extra padding around the border. @@ -77,6 +89,8 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. height: height of the component in pixels. latex_delimiters: A list of dicts of the form {"left": open delimiter (str), "right": close delimiter (str), "display": whether to display in newline (bool)} that will be used to render LaTeX expressions. If not provided, `latex_delimiters` is set to `[{ "left": "$$", "right": "$$", "display": True }]`, so only expressions enclosed in $$ delimiters will be rendered as LaTeX, and in a new line. Pass in an empty list to disable LaTeX rendering. For more information, see the [KaTeX documentation](https://katex.org/docs/autorender.html). rtl: If True, sets the direction of the rendered text to right-to-left. Default is False, which renders text left-to-right. @@ -89,20 +103,7 @@ def __init__( line_breaks: If True (default), will enable Github-flavored Markdown line breaks in chatbot messages. If False, single new lines will be ignored. Only applies if `render_markdown` is True. layout: If "panel", will display the chatbot in a llm style layout. If "bubble", will display the chatbot with message bubbles, with the user and bot messages on alterating sides. Will default to "bubble". """ - if color_map is not None: - warn_deprecation("The 'color_map' parameter has been deprecated.") - self.select: EventListenerMethod - """ - Event listener for when the user selects message from Chatbot. - Uses event data gradio.SelectData to carry `value` referring to text of selected message, and `index` tuple to refer to [message, participant] index. - See EventData documentation on how to use this event data. - """ - self.like: EventListenerMethod - """ - Event listener for when the user likes or dislikes a message from Chatbot. - Uses event data gradio.LikeData to carry `value` referring to text of selected message, `index` tuple to refer to [message, participant] index, and `liked` bool which is True if the item was liked, False if disliked. - See EventData documentation on how to use this event data. - """ + self.likeable = False self.height = height self.rtl = rtl if latex_delimiters is None: @@ -120,8 +121,8 @@ def __init__( self.bubble_full_width = bubble_full_width self.line_breaks = line_breaks self.layout = layout - IOComponent.__init__( - self, + + super().__init__( label=label, every=every, show_label=show_label, @@ -131,69 +132,22 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - @staticmethod - def update( - value: list[list[str | tuple[str] | tuple[str, str] | None]] - | Literal[_Keywords.NO_VALUE] - | None = _Keywords.NO_VALUE, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - visible: bool | None = None, - height: int | None = None, - rtl: bool | None = None, - latex_delimiters: list[dict[str, str | bool]] | None = None, - show_share_button: bool | None = None, - show_copy_button: bool | None = None, - avatar_images: tuple[str | Path | None] | None = None, - sanitize_html: bool | None = None, - bubble_full_width: bool | None = None, - layout: Literal["panel", "bubble"] | None = None, - render_markdown: bool | None = None, - line_breaks: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Chatbot(...)` instead of `return gr.Chatbot.update(...)`." - ) - updated_config = { - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "value": value, - "height": height, - "show_share_button": show_share_button, - "rtl": rtl, - "latex_delimiters": latex_delimiters, - "show_copy_button": show_copy_button, - "avatar_images": avatar_images, - "sanitize_html": sanitize_html, - "bubble_full_width": bubble_full_width, - "render_markdown": render_markdown, - "line_breaks": line_breaks, - "layout": layout, - "__type__": "update", - } - return updated_config - def _preprocess_chat_messages( self, chat_message: str | dict | None ) -> str | tuple[str] | tuple[str, str] | None: if chat_message is None: return None elif isinstance(chat_message, dict): - if chat_message["alt_text"] is not None: - return (chat_message["name"], chat_message["alt_text"]) + if chat_message.get("alt_text"): + return (chat_message["file"]["name"], chat_message["alt_text"]) else: - return (chat_message["name"],) + return (chat_message["file"]["name"],) else: # string return chat_message @@ -223,24 +177,17 @@ def preprocess( def _postprocess_chat_messages( self, chat_message: str | tuple | list | None - ) -> str | dict | None: + ) -> str | FileMessage | None: if chat_message is None: return None elif isinstance(chat_message, (tuple, list)): - file_uri = str(chat_message[0]) - if utils.validate_url(file_uri): - filepath = file_uri - else: - filepath = self.make_temp_copy_if_needed(file_uri) + filepath = str(chat_message[0]) mime_type = client_utils.get_mimetype(filepath) - return { - "name": filepath, - "mime_type": mime_type, - "alt_text": chat_message[1] if len(chat_message) > 1 else None, - "data": None, # These last two fields are filled in by the frontend - "is_file": True, - } + return FileMessage( + file=FileData(name=filepath, is_file=True, mime_type=mime_type), + alt_text=chat_message[1] if len(chat_message) > 1 else None, + ) elif isinstance(chat_message, str): chat_message = inspect.cleandoc(chat_message) return chat_message @@ -250,7 +197,7 @@ def _postprocess_chat_messages( def postprocess( self, y: list[list[str | tuple[str] | tuple[str, str] | None] | tuple], - ) -> list[list[str | dict | None]]: + ) -> ChatbotData: """ Parameters: y: List of lists representing the message and response pairs. Each message and response should be a string, which may be in Markdown format. It can also be a tuple whose first element is a string or pathlib.Path filepath or URL to an image/video/audio, and second (optional) element is the alt text, in which case the media file is displayed. It can also be None, in which case that message is not displayed. @@ -258,7 +205,7 @@ def postprocess( List of lists representing the message and response. Each message and response will be a string of HTML, or a dictionary with media information. Or None if the message is not to be displayed. """ if y is None: - return [] + return ChatbotData(root=[]) processed_messages = [] for message_pair in y: if not isinstance(message_pair, (tuple, list)): @@ -275,13 +222,7 @@ def postprocess( self._postprocess_chat_messages(message_pair[1]), ] ) - return processed_messages + return ChatbotData(root=processed_messages) - def style(self, height: int | None = None, **kwargs): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if height is not None: - self.height = height - return self + def example_inputs(self) -> Any: + return [["Hello!", None]] diff --git a/gradio/components/checkbox.py b/gradio/components/checkbox.py index 4394bb84f8fc..823610e6db8c 100644 --- a/gradio/components/checkbox.py +++ b/gradio/components/checkbox.py @@ -2,29 +2,18 @@ from __future__ import annotations -import warnings -from typing import Callable, Literal +from typing import Any, Callable from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import BooleanSerializable -from gradio.components.base import FormComponent, IOComponent, _Keywords -from gradio.events import Changeable, EventListenerMethod, Inputable, Selectable -from gradio.interpretation import NeighborInterpretable +from gradio.components.base import FormComponent +from gradio.events import Events set_documentation_group("component") @document() -class Checkbox( - FormComponent, - Changeable, - Inputable, - Selectable, - IOComponent, - BooleanSerializable, - NeighborInterpretable, -): +class Checkbox(FormComponent): """ Creates a checkbox that can be set to `True` or `False`. @@ -34,6 +23,8 @@ class Checkbox( Demos: sentence_builder, titanic_survival """ + EVENTS = [Events.change, Events.input, Events.select] + def __init__( self, value: bool | Callable = False, @@ -49,12 +40,14 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, - **kwargs, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, ): """ Parameters: value: if True, checked by default. If callable, the function will be called whenever the app loads to set the initial value of the component. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. info: additional component description. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. @@ -65,15 +58,10 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. """ - self.select: EventListenerMethod - """ - Event listener for when the user selects or deselects Checkbox. - Uses event data gradio.SelectData to carry `value` referring to label of checkbox, and `selected` to refer to state of checkbox. - See EventData documentation on how to use this event data. - """ - IOComponent.__init__( - self, + super().__init__( label=label, info=info, every=every, @@ -85,48 +73,14 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - NeighborInterpretable.__init__(self) - @staticmethod - def update( - value: bool | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - label: str | None = None, - info: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - interactive: bool | None = None, - visible: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Checkbox(...)` instead of `return gr.Checkbox.update(...)`." - ) - return { - "label": label, - "info": info, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "interactive": interactive, - "visible": visible, - "value": value, - "__type__": "update", - } + def api_info(self) -> dict[str, Any]: + return {"type": "boolean"} - def get_interpretation_neighbors(self, x): - return [not x], {} - - def get_interpretation_scores(self, x, neighbors, scores, **kwargs): - """ - Returns: - The first value represents the interpretation score if the input is False, and the second if the input is True. - """ - if x: - return scores[0], None - else: - return None, scores[0] + def example_inputs(self) -> bool: + return True diff --git a/gradio/components/checkboxgroup.py b/gradio/components/checkboxgroup.py index deaa59c9a458..6c1a71befc6f 100644 --- a/gradio/components/checkboxgroup.py +++ b/gradio/components/checkboxgroup.py @@ -2,30 +2,18 @@ from __future__ import annotations -import warnings from typing import Any, Callable, Literal from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import ListStringSerializable -from gradio.components.base import FormComponent, IOComponent, _Keywords -from gradio.deprecation import warn_deprecation, warn_style_method_deprecation -from gradio.events import Changeable, EventListenerMethod, Inputable, Selectable -from gradio.interpretation import NeighborInterpretable +from gradio.components.base import FormComponent +from gradio.events import Events set_documentation_group("component") @document() -class CheckboxGroup( - FormComponent, - Changeable, - Inputable, - Selectable, - IOComponent, - ListStringSerializable, - NeighborInterpretable, -): +class CheckboxGroup(FormComponent): """ Creates a set of checkboxes of which a subset can be checked. Preprocessing: passes the list of checked checkboxes as a {List[str | int | float]} or their indices as a {List[int]} into the function, depending on `type`. @@ -34,6 +22,8 @@ class CheckboxGroup( Demos: sentence_builder, titanic_survival """ + EVENTS = [Events.change, Events.input, Events.select] + def __init__( self, choices: list[str | int | float | tuple[str, str | int | float]] | None = None, @@ -51,14 +41,16 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, - **kwargs, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, ): """ Parameters: choices: A list of string or numeric options to select from. An option can also be a tuple of the form (name, value), where name is the displayed name of the checkbox button and value is the value to be passed to the function, or returned by the function. value: Default selected list of options. If a single choice is selected, it can be passed in as a string or numeric type. If callable, the function will be called whenever the app loads to set the initial value of the component. type: Type of value to be returned by component. "value" returns the list of strings of the choices selected, "index" returns the list of indices of the choices selected. - label: Component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. info: Additional component description. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: If True, will display label. @@ -69,6 +61,8 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. """ self.choices = ( # Although we expect choices to be a list of tuples, it can be a list of tuples if the Gradio app @@ -83,14 +77,7 @@ def __init__( f"Invalid value for parameter `type`: {type}. Please choose from one of: {valid_types}" ) self.type = type - self.select: EventListenerMethod - """ - Event listener for when the user selects or deselects within CheckboxGroup. - Uses event data gradio.SelectData to carry `value` referring to label of selected checkbox, `index` to refer to index, and `selected` to refer to state of checkbox. - See EventData documentation on how to use this event data. - """ - IOComponent.__init__( - self, + super().__init__( label=label, info=info, every=every, @@ -102,53 +89,20 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - NeighborInterpretable.__init__(self) - def example_inputs(self) -> dict[str, Any]: - return { - "raw": [self.choices[0][1]] if self.choices else None, - "serialized": [self.choices[0][1]] if self.choices else None, - } + def example_inputs(self) -> Any: + return [self.choices[0][1]] if self.choices else None - @staticmethod - def update( - value: list[str | int | float] - | str - | Literal[_Keywords.NO_VALUE] - | None = _Keywords.NO_VALUE, - choices: list[str | int | float | tuple[str, str | int | float]] | None = None, - label: str | None = None, - info: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - interactive: bool | None = None, - visible: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.CheckboxGroup(...)` instead of `return gr.CheckboxGroup.update(...)`." - ) - choices = ( - None - if choices is None - else [c if isinstance(c, tuple) else (str(c), c) for c in choices] - ) + def api_info(self) -> dict[str, Any]: return { - "choices": choices, - "label": label, - "info": info, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "interactive": interactive, - "visible": visible, - "value": value, - "__type__": "update", + "items": {"enum": [c[1] for c in self.choices], "type": "string"}, + "title": "Checkbox Group", + "type": "array", } def preprocess( @@ -188,45 +142,6 @@ def postprocess( y = [y] return y - def get_interpretation_neighbors(self, x): - leave_one_out_sets = [] - for choice in [value for _, value in self.choices]: - leave_one_out_set = list(x) - if choice in leave_one_out_set: - leave_one_out_set.remove(choice) - else: - leave_one_out_set.append(choice) - leave_one_out_sets.append(leave_one_out_set) - return leave_one_out_sets, {} - - def get_interpretation_scores(self, x, neighbors, scores, **kwargs): - """ - Returns: - For each tuple in the list, the first value represents the interpretation score if the input is False, and the second if the input is True. - """ - final_scores = [] - for choice, score in zip([value for _, value in self.choices], scores): - score_set = [score, None] if choice in x else [None, score] - final_scores.append(score_set) - return final_scores - - def style( - self, - *, - item_container: bool | None = None, - container: bool | None = None, - **kwargs, - ): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if item_container is not None: - warn_deprecation("The `item_container` parameter is deprecated.") - if container is not None: - self.container = container - return self - def as_example(self, input_data): if input_data is None: return None diff --git a/gradio/components/clear_button.py b/gradio/components/clear_button.py index 33d122e12c9d..0e0e769a3882 100644 --- a/gradio/components/clear_button.py +++ b/gradio/components/clear_button.py @@ -3,7 +3,7 @@ from __future__ import annotations import json -from typing import Literal +from typing import Any, Literal from gradio_client.documentation import document, set_documentation_group @@ -27,6 +27,7 @@ def __init__( components: None | list[Component] | Component = None, *, value: str = "Clear", + every: float | None = None, variant: Literal["primary", "secondary", "stop"] = "secondary", size: Literal["sm", "lg"] | None = None, icon: str | None = None, @@ -35,12 +36,15 @@ def __init__( interactive: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, scale: int | None = None, min_width: int | None = None, - **kwargs, ): super().__init__( value, + every=every, variant=variant, size=size, icon=icon, @@ -49,9 +53,11 @@ def __init__( interactive=interactive, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, scale=scale, min_width=min_width, - **kwargs, ) self.add(components) @@ -67,8 +73,19 @@ def add(self, components: None | Component | list[Component]) -> ClearButton: if isinstance(components, Component): components = [components] - clear_values = json.dumps( - [component.postprocess(None) for component in components] - ) + none_values = [] + for component in components: + none = component.postprocess(None) + none_values.append(none) + clear_values = json.dumps(none_values) self.click(None, [], components, _js=f"() => {clear_values}") return self + + def postprocess(self, y): + return y + + def preprocess(self, x: Any) -> Any: + return x + + def example_inputs(self) -> Any: + return None diff --git a/gradio/components/code.py b/gradio/components/code.py index cbc5d7f2339d..1301ac60f711 100644 --- a/gradio/components/code.py +++ b/gradio/components/code.py @@ -2,20 +2,19 @@ from __future__ import annotations -import warnings -from typing import Literal +from pathlib import Path +from typing import Any, Literal from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import StringSerializable -from gradio.components.base import IOComponent, _Keywords -from gradio.events import Changeable, Inputable +from gradio.components.base import Component +from gradio.events import Events set_documentation_group("component") -@document() -class Code(Changeable, Inputable, IOComponent, StringSerializable): +@document("languages") +class Code(Component): """ Creates a Code editor for entering, editing or viewing code. Preprocessing: passes a {str} of code into the function. @@ -37,6 +36,8 @@ class Code(Changeable, Inputable, IOComponent, StringSerializable): None, ] + EVENTS = [Events.change, Events.input] + def __init__( self, value: str | tuple[str] | None = None, @@ -55,6 +56,7 @@ def __init__( ] | None = None, *, + every: float | None = None, lines: int = 5, label: str | None = None, interactive: bool | None = None, @@ -65,13 +67,16 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, - **kwargs, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, ): """ Parameters: value: Default value to show in the code editor. If callable, the function will be called whenever the app loads to set the initial value of the component. + every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. language: The language to display the code as. Supported languages listed in `gr.Code.languages`. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. interactive: Whether user should be able to enter code or only view it. show_label: if True, will display label. container: If True, will place the component in a container - providing some extra padding around the border. @@ -80,15 +85,17 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. """ if language not in Code.languages: raise ValueError(f"Language {language} not supported.") self.language = language self.lines = lines - IOComponent.__init__( - self, + super().__init__( label=label, + every=every, interactive=interactive, show_label=show_label, container=container, @@ -97,11 +104,16 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - def postprocess(self, y): + def preprocess(self, x: Any) -> Any: + return x + + def postprocess(self, y: tuple | str | None) -> None | str: if y is None: return None elif isinstance(y, tuple): @@ -110,46 +122,11 @@ def postprocess(self, y): else: return y.strip() - @staticmethod - def update( - value: str - | tuple[str] - | None - | Literal[_Keywords.NO_VALUE] = _Keywords.NO_VALUE, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - visible: bool | None = None, - language: Literal[ - "python", - "markdown", - "json", - "html", - "css", - "javascript", - "typescript", - "yaml", - "dockerfile", - "shell", - "r", - ] - | None = None, - interactive: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Code(...)` instead of `return gr.Code.update(...)`." - ) - return { - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "value": value, - "language": language, - "interactive": interactive, - "__type__": "update", - } + def flag(self, x: Any, flag_dir: str | Path = "") -> str: + return super().flag(x, flag_dir) + + def api_info(self) -> dict[str, Any]: + return {"type": "string"} + + def example_inputs(self) -> Any: + return "print('Hello World')" diff --git a/gradio/components/color_picker.py b/gradio/components/color_picker.py index f8acd134f32f..fbd09a4cb369 100644 --- a/gradio/components/color_picker.py +++ b/gradio/components/color_picker.py @@ -2,27 +2,19 @@ from __future__ import annotations -import warnings -from typing import Any, Callable, Literal +from pathlib import Path +from typing import Any, Callable from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import StringSerializable -from gradio.components.base import IOComponent, _Keywords -from gradio.events import ( - Changeable, - Focusable, - Inputable, - Submittable, -) +from gradio.components.base import Component +from gradio.events import Events set_documentation_group("component") @document() -class ColorPicker( - Changeable, Inputable, Submittable, Focusable, IOComponent, StringSerializable -): +class ColorPicker(Component): """ Creates a color picker for user to select a color as string input. Preprocessing: passes selected color value as a {str} into the function. @@ -31,6 +23,8 @@ class ColorPicker( Demos: color_picker, color_generator """ + EVENTS = [Events.change, Events.input, Events.submit, Events.focus, Events.blur] + def __init__( self, value: str | Callable | None = None, @@ -46,12 +40,14 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, - **kwargs, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, ): """ Parameters: value: default text to provide in color picker. If callable, the function will be called whenever the app loads to set the initial value of the component. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. info: additional component description. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. @@ -62,9 +58,10 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. """ - IOComponent.__init__( - self, + super().__init__( label=label, info=info, every=every, @@ -76,43 +73,23 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - def example_inputs(self) -> dict[str, Any]: - return { - "raw": "#000000", - "serialized": "#000000", - } + def example_inputs(self) -> str: + return "#000000" - @staticmethod - def update( - value: str | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - label: str | None = None, - info: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - visible: bool | None = None, - interactive: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.ColorPicker(...)` instead of `return gr.ColorPicker.update(...)`." - ) - return { - "value": value, - "label": label, - "info": info, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "interactive": interactive, - "__type__": "update", - } + def flag(self, x: Any, flag_dir: str | Path = "") -> str: + return x + + def read_from_flag(self, x: Any, flag_dir: str | Path | None = None): + return x + + def api_info(self) -> dict[str, Any]: + return {"type": "string"} def preprocess(self, x: str | None) -> str | None: """ diff --git a/gradio/components/dataframe.py b/gradio/components/dataframe.py index 1f19d68a4ce7..e6938cd67e9c 100644 --- a/gradio/components/dataframe.py +++ b/gradio/components/dataframe.py @@ -3,40 +3,32 @@ from __future__ import annotations import warnings -from dataclasses import asdict, dataclass -from typing import Callable, Literal +from typing import Any, Callable, Dict, List, Literal, Optional import numpy as np import pandas as pd import semantic_version from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import JSONSerializable from pandas.io.formats.style import Styler -from gradio.components.base import IOComponent, _Keywords -from gradio.events import ( - Changeable, - EventListenerMethod, - Inputable, - Selectable, -) +from gradio.components import Component +from gradio.data_classes import GradioModel +from gradio.events import Events -set_documentation_group("component") +class DataframeData(GradioModel): + headers: List[str] + data: List[List[Any]] + metadata: Optional[ + Dict[str, List[Any]] + ] = None # Optional[Dict[str, List[Any]]] = None -@dataclass -class DataframeData: - """ - This is a dataclass to represent all the data that is sent to or received from the frontend. - """ - data: list[list[str | int | bool]] - headers: list[str] | list[int] | None = None - metadata: dict[str, list[list]] | None = None +set_documentation_group("component") @document() -class Dataframe(Changeable, Inputable, Selectable, IOComponent, JSONSerializable): +class Dataframe(Component): """ Accepts or displays 2D input through a spreadsheet-like component for dataframes. Preprocessing: passes the uploaded spreadsheet data as a {pandas.DataFrame}, {numpy.array}, or {List[List]} depending on `type` @@ -45,6 +37,10 @@ class Dataframe(Changeable, Inputable, Selectable, IOComponent, JSONSerializable Demos: filter_records, matrix_transpose, tax_calculator """ + EVENTS = [Events.change, Events.input, Events.select] + + data_model = DataframeData + def __init__( self, value: pd.DataFrame @@ -62,9 +58,6 @@ def __init__( col_count: int | tuple[int, str] | None = None, datatype: str | list[str] = "str", type: Literal["pandas", "numpy", "array"] = "pandas", - max_rows: int | None = 20, - max_cols: int | None = None, - overflow_row_behaviour: Literal["paginate", "show_ends"] = "paginate", latex_delimiters: list[dict[str, str | bool]] | None = None, label: str | None = None, show_label: bool | None = None, @@ -76,10 +69,12 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, wrap: bool = False, line_breaks: bool = True, column_widths: list[str | int] | None = None, - **kwargs, ): """ Parameters: @@ -89,12 +84,9 @@ def __init__( col_count: Limit number of columns for input and decide whether user can create new columns. The first element of the tuple is an `int`, the number of columns; the second should be 'fixed' or 'dynamic', the new column behaviour. If an `int` is passed the columns default to 'dynamic' datatype: Datatype of values in sheet. Can be provided per column as a list of strings, or for the entire sheet as a single string. Valid datatypes are "str", "number", "bool", "date", and "markdown". type: Type of value to be returned by component. "pandas" for pandas dataframe, "numpy" for numpy array, or "array" for a Python list of lists. - label: component name in interface. - max_rows: Deprecated and has no effect. Use `row_count` instead. - max_cols: Deprecated and has no effect. Use `col_count` instead. - overflow_row_behaviour: Deprecated and has no effect. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. latex_delimiters: A list of dicts of the form {"left": open delimiter (str), "right": close delimiter (str), "display": whether to display in newline (bool)} that will be used to render LaTeX expressions. If not provided, `latex_delimiters` is set to `[{ "left": "$", "right": "$", "display": False }]`, so only expressions enclosed in $ delimiters will be rendered as LaTeX, and in the same line. Pass in an empty list to disable LaTeX rendering. For more information, see the [KaTeX documentation](https://katex.org/docs/autorender.html). Only applies to columns whose datatype is "markdown". - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. show_label: if True, will display label. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. height: The maximum height of the dataframe, in pixels. If more rows are created than can fit in the height, a scrollbar will appear. @@ -104,6 +96,8 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. wrap: If True, the text in table cells will wrap when appropriate. If False and the `column_width` parameter is not set, the column widths will expand based on the cell contents and the table may need to be horizontally scrolled. If `column_width` is set, then any overflow text will be hidden. line_breaks: If True (default), will enable Github-flavored Markdown line breaks in chatbot messages. If False, single new lines will be ignored. Only applies for columns of type "markdown." column_widths: An optional list representing the width of each column. The elements of the list should be in the format "100px" (ints are also accepted and converted to pixel values) or "10%". If not provided, the column widths will be automatically determined based on the content of the cells. Setting this parameter will cause the browser to try to fit the table within the page width. @@ -118,7 +112,9 @@ def __init__( self.__validate_headers(headers, self.col_count[0]) self.headers = ( - headers if headers is not None else list(range(1, self.col_count[0] + 1)) + headers + if headers is not None + else [str(i) for i in (range(1, self.col_count[0] + 1))] ) self.datatype = ( datatype if isinstance(datatype, list) else [datatype] * self.col_count[0] @@ -140,13 +136,14 @@ def __init__( column_dtypes = ( [datatype] * self.col_count[0] if isinstance(datatype, str) else datatype ) - self.empty_input = [ - [values[c] for c in column_dtypes] for _ in range(self.row_count[0]) - ] + self.empty_input = { + "headers": self.headers, + "data": [ + [values[c] for c in column_dtypes] for _ in range(self.row_count[0]) + ], + "metadata": None, + } - self.max_rows = max_rows - self.max_cols = max_cols - self.overflow_row_behaviour = overflow_row_behaviour if latex_delimiters is None: latex_delimiters = [{"left": "$", "right": "$", "display": False}] self.latex_delimiters = latex_delimiters @@ -155,15 +152,7 @@ def __init__( self.column_widths = [ w if isinstance(w, str) else f"{w}px" for w in (column_widths or []) ] - - self.select: EventListenerMethod - """ - Event listener for when the user selects cell within Dataframe. - Uses event data gradio.SelectData to carry `value` referring to value of selected cell, and `index` tuple to refer to index row and column. - See EventData documentation on how to use this event data. - """ - IOComponent.__init__( - self, + super().__init__( label=label, every=every, show_label=show_label, @@ -173,51 +162,11 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, - ) - - @staticmethod - def update( - value: pd.DataFrame - | Styler - | np.ndarray - | list - | list[list] - | dict - | str - | Literal[_Keywords.NO_VALUE] - | None = _Keywords.NO_VALUE, - max_rows: int | None = None, - max_cols: str | None = None, - label: str | None = None, - show_label: bool | None = None, - latex_delimiters: list[dict[str, str | bool]] | None = None, - scale: int | None = None, - min_width: int | None = None, - height: int | None = None, - interactive: bool | None = None, - visible: bool | None = None, - line_breaks: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Dataframe(...)` instead of `return gr.Dataframe.update(...)`." ) - return { - "max_rows": max_rows, - "max_cols": max_cols, - "label": label, - "show_label": show_label, - "scale": scale, - "min_width": min_width, - "height": height, - "interactive": interactive, - "visible": visible, - "value": value, - "latex_delimiters": latex_delimiters, - "line_breaks": line_breaks, - "__type__": "update", - } def preprocess(self, x: dict) -> pd.DataFrame | np.ndarray | list: """ @@ -246,7 +195,7 @@ def preprocess(self, x: dict) -> pd.DataFrame | np.ndarray | list: def postprocess( self, y: pd.DataFrame | Styler | np.ndarray | list | list[list] | dict | str | None, - ) -> dict: + ) -> DataframeData | dict: """ Parameters: y: dataframe in given format @@ -256,7 +205,14 @@ def postprocess( if y is None: return self.postprocess(self.empty_input) if isinstance(y, dict): - value = DataframeData(**y) + return y + if isinstance(y, (str, pd.DataFrame)): + if isinstance(y, str): + y = pd.read_csv(y) # type: ignore + return DataframeData( + headers=list(y.columns), # type: ignore + data=y.to_dict(orient="split")["data"], # type: ignore + ) elif isinstance(y, Styler): if semantic_version.Version(pd.__version__) < semantic_version.Version( "1.5.0" @@ -271,14 +227,14 @@ def postprocess( df: pd.DataFrame = y.data # type: ignore value = DataframeData( headers=list(df.columns), - data=df.to_dict(orient="split")["data"], + data=df.to_dict(orient="split")["data"], # type: ignore metadata=self.__extract_metadata(y), ) elif isinstance(y, (str, pd.DataFrame)): - df = pd.read_csv(y) if isinstance(y, str) else y + df = pd.read_csv(y) if isinstance(y, str) else y # type: ignore value = DataframeData( headers=list(df.columns), - data=df.to_dict(orient="split")["data"], + data=df.to_dict(orient="split")["data"], # type: ignore ) elif isinstance(y, (np.ndarray, list)): if len(y) == 0: @@ -290,20 +246,17 @@ def postprocess( _headers = self.headers if len(self.headers) < len(y[0]): - _headers = [ + _headers: list[str] = [ *self.headers, - *list(range(len(self.headers) + 1, len(y[0]) + 1)), + *[str(i) for i in range(len(self.headers) + 1, len(y[0]) + 1)], ] elif len(self.headers) > len(y[0]): _headers = self.headers[: len(y[0])] - value = DataframeData( - headers=_headers, - data=y, - ) + value = DataframeData(headers=_headers, data=y) else: - raise ValueError(f"Cannot process value as a Dataframe: {y}") - return asdict(value) + raise ValueError("Cannot process value as a Dataframe") + return value @staticmethod def __get_cell_style(cell_id: str, cell_styles: list[dict]) -> str: @@ -359,3 +312,6 @@ def as_example(self, input_data: pd.DataFrame | np.ndarray | str | None): elif isinstance(input_data, np.ndarray): return input_data.tolist() return input_data + + def example_inputs(self) -> Any: + return {"headers": ["a", "b"], "data": [["foo", "bar"]]} diff --git a/gradio/components/dataset.py b/gradio/components/dataset.py index cf4fa308349c..8e5fd1b524f6 100644 --- a/gradio/components/dataset.py +++ b/gradio/components/dataset.py @@ -2,25 +2,22 @@ from __future__ import annotations -import warnings from typing import Any, Literal from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import StringSerializable +import gradio.utils as utils from gradio.components.base import ( Component, - IOComponent, - _Keywords, get_component_instance, ) -from gradio.events import Clickable, Selectable +from gradio.events import Events set_documentation_group("component") @document() -class Dataset(Clickable, Selectable, Component, StringSerializable): +class Dataset(Component): """ Used to create an output widget for showing datasets. Used to render the examples box. @@ -28,11 +25,13 @@ class Dataset(Clickable, Selectable, Component, StringSerializable): Postprocessing: expects a {list} of {lists} corresponding to the dataset data. """ + EVENTS = [Events.click, Events.select] + def __init__( self, *, label: str | None = None, - components: list[IOComponent] | list[str], + components: list[Component] | list[str], samples: list[list[Any]] | None = None, headers: list[str] | None = None, type: Literal["values", "index"] = "values", @@ -40,10 +39,12 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, container: bool = True, scale: int | None = None, min_width: int = 160, - **kwargs, ): """ Parameters: @@ -55,24 +56,30 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. container: If True, will place the component in a container - providing some extra padding around the border. scale: relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer. min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first. """ - Component.__init__( - self, visible=visible, elem_id=elem_id, elem_classes=elem_classes, **kwargs - ) + super().__init__(visible=visible, elem_id=elem_id, elem_classes=elem_classes) self.container = container self.scale = scale self.min_width = min_width self._components = [get_component_instance(c) for c in components] - - # Narrow type to IOComponent - if not all(isinstance(c, IOComponent) for c in self._components): - raise ValueError( - "All components in a `Dataset` must be subclasses of `IOComponent`" + self.component_props = [ + utils.recover_kwargs( + component.get_config(), + ["value"], ) - self._components = [c for c in self._components if isinstance(c, IOComponent)] + for component in self._components + ] + + # Narrow type to Component + assert all( + isinstance(c, Component) for c in self._components + ), "All components in a `Dataset` must be subclasses of `Component`" + self._components = [c for c in self._components if isinstance(c, Component)] for component in self._components: component.root_url = self.root_url @@ -90,34 +97,22 @@ def __init__( self.headers = [c.label or "" for c in self._components] self.samples_per_page = samples_per_page - @staticmethod - def update( - samples: Any | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - visible: bool | None = None, - label: str | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Dataset(...)` instead of `return gr.Dataset.update(...)`." - ) - return { - "samples": samples, - "visible": visible, - "label": label, - "container": container, - "scale": scale, - "min_width": min_width, - "__type__": "update", - } + @property + def skip_api(self): + return True def get_config(self): config = super().get_config() - config["components"] = [ - component.get_block_name() for component in self._components - ] - config["component_ids"] = [component._id for component in self._components] + + config["components"] = [] + config["component_props"] = self.component_props + config["component_ids"] = [] + + for component in self._components: + config["components"].append(component.get_block_name()) + + config["component_ids"].append(component._id) + return config def preprocess(self, x: Any) -> Any: @@ -134,3 +129,6 @@ def postprocess(self, samples: list[list[Any]]) -> dict: "samples": samples, "__type__": "update", } + + def example_inputs(self) -> Any: + return None diff --git a/gradio/components/dropdown.py b/gradio/components/dropdown.py index 13fb1e66170b..2ff7fdcab9d3 100644 --- a/gradio/components/dropdown.py +++ b/gradio/components/dropdown.py @@ -6,31 +6,15 @@ from typing import Any, Callable, Literal from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import SimpleSerializable -from gradio.components.base import FormComponent, IOComponent, _Keywords -from gradio.deprecation import warn_style_method_deprecation -from gradio.events import ( - Changeable, - EventListenerMethod, - Focusable, - Inputable, - Selectable, -) +from gradio.components.base import FormComponent +from gradio.events import Events set_documentation_group("component") @document() -class Dropdown( - FormComponent, - Changeable, - Inputable, - Selectable, - Focusable, - IOComponent, - SimpleSerializable, -): +class Dropdown(FormComponent): """ Creates a dropdown of choices from which entries can be selected. Preprocessing: passes the value of the selected dropdown entry as a {str} or its index as an {int} into the function, depending on `type`. @@ -39,6 +23,8 @@ class Dropdown( Demos: sentence_builder, titanic_survival """ + EVENTS = [Events.change, Events.input, Events.select, Events.focus, Events.blur] + def __init__( self, choices: list[str | int | float | tuple[str, str | int | float]] | None = None, @@ -60,7 +46,9 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, - **kwargs, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, ): """ Parameters: @@ -71,7 +59,7 @@ def __init__( allow_custom_value: If True, allows user to enter a custom value that is not in the list of choices. max_choices: maximum number of choices that can be selected. If None, no limit is enforced. filterable: If True, user will be able to type into the dropdown and filter the choices by typing. Can only be set to False if `allow_custom_value` is False. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. info: additional component description. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. @@ -82,6 +70,8 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. """ self.choices = ( # Although we expect choices to be a list of tuples, it can be a list of tuples if the Gradio app @@ -110,16 +100,8 @@ def __init__( ) self.max_choices = max_choices self.allow_custom_value = allow_custom_value - self.interpret_by_tokens = False self.filterable = filterable - self.select: EventListenerMethod - """ - Event listener for when the user selects Dropdown option. - Uses event data gradio.SelectData to carry `value` referring to label of selected option, and `index` to refer to index. - See EventData documentation on how to use this event data. - """ - IOComponent.__init__( - self, + super().__init__( label=label, info=info, every=every, @@ -131,71 +113,30 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - def api_info(self) -> dict[str, dict | bool]: + def api_info(self) -> dict[str, Any]: if self.multiselect: - type = { + json_type = { "type": "array", - "items": {"type": "string"}, - "description": f"List of options from: {self.choices}", + "items": {"type": "string", "enum": [c[1] for c in self.choices]}, } else: - type = {"type": "string", "description": f"Option from: {self.choices}"} - return {"info": type, "serialized_info": False} + json_type = { + "type": "string", + "enum": [c[1] for c in self.choices], + } + return json_type - def example_inputs(self) -> dict[str, Any]: + def example_inputs(self) -> Any: if self.multiselect: - return { - "raw": [self.choices[0]] if self.choices else [], - "serialized": [self.choices[0]] if self.choices else [], - } + return [self.choices[0][1]] if self.choices else [] else: - return { - "raw": self.choices[0] if self.choices else None, - "serialized": self.choices[0] if self.choices else None, - } - - @staticmethod - def update( - value: Any | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - choices: str | list[str | tuple[str, str]] | None = None, - label: str | None = None, - info: str | None = None, - show_label: bool | None = None, - filterable: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - interactive: bool | None = None, - placeholder: str | None = None, - visible: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Dropdown(...)` instead of `return gr.Dropdown.update(...)`." - ) - choices = ( - None - if choices is None - else [c if isinstance(c, tuple) else (str(c), c) for c in choices] - ) - return { - "choices": choices, - "label": label, - "info": info, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "value": value, - "interactive": interactive, - "placeholder": placeholder, - "filterable": filterable, - "__type__": "update", - } + return self.choices[0][1] if self.choices else None def preprocess( self, x: str | int | float | list[str | int | float] | None @@ -241,35 +182,10 @@ def postprocess(self, y): self._warn_if_invalid_choice(y) return y - def set_interpret_parameters(self): - """ - Calculates interpretation score of each choice by comparing the output against each of the outputs when alternative choices are selected. - """ - return self - - def get_interpretation_neighbors(self, x): - choices = list(self.choices) - choices.remove(x) - return choices, {} - - def get_interpretation_scores( - self, x, neighbors, scores: list[float | None], **kwargs - ) -> list: - """ - Returns: - Each value represents the interpretation score corresponding to each choice. - """ - scores.insert(self.choices.index(x), None) - return scores - - def style(self, *, container: bool | None = None, **kwargs): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if container is not None: - self.container = container - return self - def as_example(self, input_data): + if self.multiselect: + return [ + next((c[0] for c in self.choices if c[1] == data), None) + for data in input_data + ] return next((c[0] for c in self.choices if c[1] == input_data), None) diff --git a/gradio/components/duplicate_button.py b/gradio/components/duplicate_button.py index c6b8f486a9cf..1a88c81efa27 100644 --- a/gradio/components/duplicate_button.py +++ b/gradio/components/duplicate_button.py @@ -24,8 +24,9 @@ class DuplicateButton(Button): def __init__( self, - *, value: str = "Duplicate Space", + *, + every: float | None = None, variant: Literal["primary", "secondary", "stop"] = "secondary", size: Literal["sm", "lg"] | None = "sm", icon: str | None = None, @@ -34,14 +35,17 @@ def __init__( interactive: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, scale: int | None = 0, min_width: int | None = None, _activate: bool = True, - **kwargs, ): """ Parameters: value: Default text for the button to display. If callable, the function will be called whenever the app loads to set the initial value of the component. + every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. variant: 'primary' for main call-to-action, 'secondary' for a more subdued style, 'stop' for a stop button. size: Size of the button. Can be "sm" or "lg". icon: URL or path to the icon file to display within the button. If None, no icon will be displayed. @@ -50,11 +54,14 @@ def __init__( interactive: If False, the Button will be in a disabled state. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. scale: relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer. min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first. """ super().__init__( - value, + value=value, + every=every, variant=variant, size=size, icon=icon, @@ -63,9 +70,11 @@ def __init__( interactive=interactive, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, scale=scale, min_width=min_width, - **kwargs, ) if _activate: self.activate() diff --git a/gradio/components/fallback.py b/gradio/components/fallback.py new file mode 100644 index 000000000000..2a229e13c400 --- /dev/null +++ b/gradio/components/fallback.py @@ -0,0 +1,15 @@ +from gradio.components.base import Component + + +class Fallback(Component): + def preprocess(self, x): + return x + + def postprocess(self, x): + return x + + def example_inputs(self): + return {"foo": "bar"} + + def api_info(self): + return {"type": {}, "description": "any valid json"} diff --git a/gradio/components/file.py b/gradio/components/file.py index 32aab3b3fb06..0c349e0dc9fa 100644 --- a/gradio/components/file.py +++ b/gradio/components/file.py @@ -5,35 +5,25 @@ import tempfile import warnings from pathlib import Path -from typing import Any, Callable, Literal +from typing import Any, Callable, List, Literal from gradio_client import utils as client_utils from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import FileSerializable -from gradio import utils -from gradio.components.base import IOComponent, _Keywords -from gradio.deprecation import warn_deprecation -from gradio.events import ( - Changeable, - Clearable, - EventListenerMethod, - Selectable, - Uploadable, -) +from gradio.components.base import Component +from gradio.data_classes import FileData, GradioRootModel +from gradio.events import Events +from gradio.utils import NamedString set_documentation_group("component") +class ListFiles(GradioRootModel): + root: List[FileData] + + @document() -class File( - Changeable, - Selectable, - Clearable, - Uploadable, - IOComponent, - FileSerializable, -): +class File(Component): """ Creates a file component that allows uploading generic file (when used as an input) and or displaying generic files (output). Preprocessing: passes the uploaded file as a {tempfile._TemporaryFileWrapper} or {List[tempfile._TemporaryFileWrapper]} depending on `file_count` (or a {bytes}/{List[bytes]} depending on `type`) @@ -42,13 +32,15 @@ class File( Demos: zip_to_json, zip_files """ + EVENTS = [Events.change, Events.select, Events.clear, Events.upload] + def __init__( self, value: str | list[str] | Callable | None = None, *, file_count: Literal["single", "multiple", "directory"] = "single", file_types: list[str] | None = None, - type: Literal["file", "binary"] = "file", + type: Literal["filepath", "binary"] = "filepath", label: str | None = None, every: float | None = None, show_label: bool | None = None, @@ -60,7 +52,9 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, - **kwargs, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, ): """ Parameters: @@ -68,7 +62,7 @@ def __init__( file_count: if single, allows user to upload one file. If "multiple", user uploads multiple files. If "directory", user uploads all files in selected directory. Return type will be list for each file in case of "multiple" or "directory". file_types: List of file extensions or types of files to be uploaded (e.g. ['image', '.json', '.mp4']). "file" allows any file to be uploaded, "image" allows only image files to be uploaded, "audio" allows only audio files to be uploaded, "video" allows only video files to be uploaded, "text" allows only text files to be uploaded. type: Type of value to be returned by component. "file" returns a temporary file object with the same base name as the uploaded file, whose full path can be retrieved by file_obj.name, "binary" returns an bytes object. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. container: If True, will place the component in a container - providing some extra padding around the border. @@ -79,40 +73,32 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. """ self.file_count = file_count + if self.file_count == "multiple": + self.data_model = ListFiles + else: + self.data_model = FileData self.file_types = file_types if file_types is not None and not isinstance(file_types, list): raise ValueError( f"Parameter file_types must be a list. Received {file_types.__class__.__name__}" ) valid_types = [ - "file", + "filepath", "binary", - "bytes", - ] # "bytes" is included for backwards compatibility + ] if type not in valid_types: raise ValueError( f"Invalid value for parameter `type`: {type}. Please choose from one of: {valid_types}" ) - if type == "bytes": - warn_deprecation( - "The `bytes` type is deprecated and may not work as expected. Please use `binary` instead." - ) if file_count == "directory" and file_types is not None: warnings.warn( "The `file_types` parameter is ignored when `file_count` is 'directory'." ) - self.type = type - self.height = height - self.select: EventListenerMethod - """ - Event listener for when the user selects file from list. - Uses event data gradio.SelectData to carry `value` referring to name of selected file, and `index` to refer to index. - See EventData documentation on how to use this event data. - """ - IOComponent.__init__( - self, + super().__init__( label=label, every=every, show_label=show_label, @@ -123,46 +109,39 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) + self.type = type + self.height = height - @staticmethod - def update( - value: Any | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - height: int | float | None = None, - interactive: bool | None = None, - visible: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.File(...)` instead of `return gr.File.update(...)`." + def _process_single_file(self, f: dict[str, Any]) -> bytes | str: + file_name, data, is_file = ( + f["name"], + f["data"], + f.get("is_file", False), ) - return { - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "height": height, - "interactive": interactive, - "visible": visible, - "value": value, - "__type__": "update", - } + if self.type == "filepath": + file = tempfile.NamedTemporaryFile(delete=False, dir=self.GRADIO_CACHE) + file.name = file_name + return NamedString(file.name) + elif self.type == "binary": + if is_file: + with open(file_name, "rb") as file_data: + return file_data.read() + return client_utils.decode_base64_to_binary(data)[0] + else: + raise ValueError( + "Unknown type: " + + str(type) + + ". Please choose from: 'filepath', 'binary'." + ) def preprocess( self, x: list[dict[str, Any]] | None - ) -> ( - bytes - | tempfile._TemporaryFileWrapper - | list[bytes | tempfile._TemporaryFileWrapper] - | None - ): + ) -> bytes | str | list[bytes | str] | None: """ Parameters: x: List of JSON objects with filename as 'name' property and base64 data as 'data' property @@ -172,56 +151,18 @@ def preprocess( if x is None: return None - def process_single_file(f) -> bytes | tempfile._TemporaryFileWrapper: - file_name, data, is_file = ( - f["name"], - f["data"], - f.get("is_file", False), - ) - if self.type == "file": - if is_file: - path = self.make_temp_copy_if_needed(file_name) - else: - data, _ = client_utils.decode_base64_to_binary(data) - path = self.file_bytes_to_file(data, file_name=file_name) - path = str(utils.abspath(path)) - self.temp_files.add(path) - - # Creation of tempfiles here - file = tempfile.NamedTemporaryFile( - delete=False, dir=self.DEFAULT_TEMP_DIR - ) - file.name = path - file.orig_name = file_name # type: ignore - return file - elif ( - self.type == "binary" or self.type == "bytes" - ): # "bytes" is included for backwards compatibility - if is_file: - with open(file_name, "rb") as file_data: - return file_data.read() - return client_utils.decode_base64_to_binary(data)[0] - else: - raise ValueError( - "Unknown type: " - + str(self.type) - + ". Please choose from: 'file', 'bytes'." - ) - if self.file_count == "single": if isinstance(x, list): - return process_single_file(x[0]) + return self._process_single_file(x[0]) else: - return process_single_file(x) + return self._process_single_file(x) else: if isinstance(x, list): - return [process_single_file(f) for f in x] + return [self._process_single_file(f) for f in x] else: - return process_single_file(x) + return [self._process_single_file(x)] - def postprocess( - self, y: str | list[str] | None - ) -> dict[str, Any] | list[dict[str, Any]] | None: + def postprocess(self, y: str | list[str] | None) -> ListFiles | FileData | None: """ Parameters: y: file path @@ -231,25 +172,24 @@ def postprocess( if y is None: return None if isinstance(y, list): - return [ - { - "orig_name": Path(file).name, - "name": self.make_temp_copy_if_needed(file), - "size": Path(file).stat().st_size, - "data": None, - "is_file": True, - } - for file in y - ] + return ListFiles( + root=[ + FileData( + name=file, + orig_name=Path(file).name, + size=Path(file).stat().st_size, + is_file=True, + ) + for file in y + ] + ) else: - d = { - "orig_name": Path(y).name, - "name": self.make_temp_copy_if_needed(y), - "size": Path(y).stat().st_size, - "data": None, - "is_file": True, - } - return d + return FileData( + name=y, + orig_name=Path(y).name, + size=Path(y).stat().st_size, + is_file=True, + ) def as_example(self, input_data: str | list | None) -> str: if input_data is None: @@ -259,20 +199,10 @@ def as_example(self, input_data: str | list | None) -> str: else: return Path(input_data).name - def api_info(self) -> dict[str, dict | bool]: + def example_inputs(self) -> Any: if self.file_count == "single": - return self._single_file_api_info() + return "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf" else: - return self._multiple_file_api_info() - - def serialized_info(self): - if self.file_count == "single": - return self._single_file_serialized_info() - else: - return self._multiple_file_serialized_info() - - def example_inputs(self) -> dict[str, Any]: - if self.file_count == "single": - return self._single_file_example_inputs() - else: - return self._multiple_file_example_inputs() + return [ + "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf" + ] diff --git a/gradio/components/file_explorer.py b/gradio/components/file_explorer.py index 7fbe2bdac477..a4bf038837f3 100644 --- a/gradio/components/file_explorer.py +++ b/gradio/components/file_explorer.py @@ -7,30 +7,33 @@ import re from glob import glob as glob_func from pathlib import Path -from typing import Callable, Literal +from typing import Any, Callable, List, Literal from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import JSONSerializable -from gradio.components.base import IOComponent, server -from gradio.events import ( - Changeable, - EventListenerMethod, -) +from gradio.components.base import Component, server +from gradio.data_classes import GradioRootModel set_documentation_group("component") +class FileExplorerData(GradioRootModel): + root: List[List[str]] + + @document() -class FileExplorer(Changeable, IOComponent, JSONSerializable): +class FileExplorer(Component): """ Creates a file explorer component that allows users to browse and select files on the machine hosting the Gradio app. - Preprocessing: passes the selected file or directory as a {str} path (relative to root) or {list[str]} depending on `file_count` + Preprocessing: passes the selected file or directory as a {str} path (relative to root) or {list[str}} depending on `file_count` Postprocessing: expects function to return a {str} path to a file, or {List[str]} consisting of paths to files. Examples-format: a {str} path to a local file that populates the component. Demos: zip_to_json, zip_files """ + EVENTS = ["change"] + data_model = FileExplorerData + def __init__( self, glob: str = "**/*.*", @@ -50,7 +53,9 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, - **kwargs, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, ): """ Parameters: @@ -59,7 +64,7 @@ def __init__( file_count: Whether to allow single or multiple files to be selected. If "single", the component will return a single absolute file path as a string. If "multiple", the component will return a list of absolute file paths as a list of strings. root: Path to root directory to select files from. If not provided, defaults to current working directory. ignore_glob: The glob-tyle pattern that will be used to exclude files from the list. For example, "*.py" will exclude all .py files from the list. See the Python glob documentation at https://docs.python.org/3/library/glob.html for more information. - label: Component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. container: If True, will place the component in a container - providing some extra padding around the border. @@ -70,6 +75,8 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. """ self.root = os.path.abspath(root) self.glob = glob @@ -81,14 +88,8 @@ def __init__( ) self.file_count = file_count self.height = height - self.select: EventListenerMethod - """ - Event listener for when the user selects file from list. - Uses event data gradio.SelectData to carry `value` referring to name of selected file, and `index` to refer to index. - See EventData documentation on how to use this event data. - """ - IOComponent.__init__( - self, + + super().__init__( label=label, every=every, show_label=show_label, @@ -99,10 +100,15 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) + def example_inputs(self) -> Any: + return ["Users", "gradio", "app.py"] + def preprocess(self, x: list[list[str]] | None) -> list[str] | str | None: """ Parameters: @@ -125,7 +131,7 @@ def _strip_root(self, path): return path[len(self.root) + 1 :] return path - def postprocess(self, y: str | list[str] | None) -> list[list[str]] | None: + def postprocess(self, y: str | list[str] | None) -> FileExplorerData | None: """ Parameters: y: file path @@ -137,7 +143,9 @@ def postprocess(self, y: str | list[str] | None) -> list[list[str]] | None: files = [y] if isinstance(y, str) else y - return [self._strip_root(file).split(os.path.sep) for file in (files)] + return FileExplorerData( + root=[self._strip_root(file).split(os.path.sep) for file in files] + ) @server def ls(self, y=None) -> list[dict[str, str]] | None: diff --git a/gradio/components/gallery.py b/gradio/components/gallery.py index 56b7b21494dd..4f84ea526c34 100644 --- a/gradio/components/gallery.py +++ b/gradio/components/gallery.py @@ -2,29 +2,32 @@ from __future__ import annotations -import warnings from pathlib import Path -from typing import Any, Callable, Literal +from typing import Any, Callable, List, Literal, Optional import numpy as np from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import GallerySerializable from PIL import Image as _Image # using _ to minimize namespace pollution -from gradio import utils -from gradio.components.base import IOComponent, _Keywords -from gradio.deprecation import warn_deprecation, warn_style_method_deprecation -from gradio.events import ( - Changeable, - EventListenerMethod, - Selectable, -) +from gradio import processing_utils, utils +from gradio.components.base import Component +from gradio.data_classes import FileData, GradioModel, GradioRootModel +from gradio.events import Events set_documentation_group("component") +class GalleryImage(GradioModel): + image: FileData + caption: Optional[str] = None + + +class GalleryData(GradioRootModel): + root: List[GalleryImage] + + @document() -class Gallery(IOComponent, GallerySerializable, Changeable, Selectable): +class Gallery(Component): """ Used to display a list of images as a gallery that can be scrolled through. Preprocessing: this component does *not* accept input. @@ -33,6 +36,10 @@ class Gallery(IOComponent, GallerySerializable, Changeable, Selectable): Demos: fake_gan """ + EVENTS = [Events.select] + + data_model = GalleryData + def __init__( self, value: list[np.ndarray | _Image.Image | str | Path | tuple] @@ -48,6 +55,9 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, columns: int | tuple | None = 2, rows: int | tuple | None = None, height: int | float | None = None, @@ -58,12 +68,11 @@ def __init__( | None = None, show_share_button: bool | None = None, show_download_button: bool | None = True, - **kwargs, ): """ Parameters: value: List of images to display in the gallery by default. If callable, the function will be called whenever the app loads to set the initial value of the component. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. container: If True, will place the component in a container - providing some extra padding around the border. @@ -72,6 +81,8 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. columns: Represents the number of images that should be shown in one row, for each of the six standard screen sizes (<576px, <768px, <992px, <1200px, <1400px, >1400px). If fewer than 6 are given then the last will be used for all subsequent breakpoints rows: Represents the number of rows in the image grid, for each of the six standard screen sizes (<576px, <768px, <992px, <1200px, <1400px, >1400px). If fewer than 6 are given then the last will be used for all subsequent breakpoints height: The height of the gallery component, in pixels. If more images are displayed than can fit in the height, a scrollbar will appear. @@ -94,20 +105,14 @@ def __init__( if show_download_button is None else show_download_button ) - self.select: EventListenerMethod self.selected_index = selected_index - """ - Event listener for when the user selects image within Gallery. - Uses event data gradio.SelectData to carry `value` referring to caption of selected image, and `index` to refer to index. - See EventData documentation on how to use this event data. - """ + self.show_share_button = ( (utils.get_space() is not None) if show_share_button is None else show_share_button ) - IOComponent.__init__( - self, + super().__init__( label=label, every=every, show_label=show_label, @@ -117,58 +122,18 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, - ) - - @staticmethod - def update( - value: Any | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - visible: bool | None = None, - columns: int | tuple | None = None, - rows: int | tuple | None = None, - height: int | float | None = None, - preview: bool | None = None, - object_fit: Literal["contain", "cover", "fill", "none", "scale-down"] - | None = None, - allow_preview: bool | None = None, - show_share_button: bool | None = None, - show_download_button: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Gallery(...)` instead of `return gr.Gallery.update(...)`." ) - updated_config = { - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "value": value, - "columns": columns, - "rows": rows, - "height": height, - "preview": preview, - "object_fit": object_fit, - "allow_preview": allow_preview, - "show_share_button": show_share_button, - "show_download_button": show_download_button, - "__type__": "update", - } - return updated_config def postprocess( self, y: list[np.ndarray | _Image.Image | str] | list[tuple[np.ndarray | _Image.Image | str, str]] | None, - ) -> list[str]: + ) -> GalleryData: """ Parameters: y: list of images, or list of (image, caption) tuples @@ -176,68 +141,37 @@ def postprocess( list of string file paths to images in temp directory """ if y is None: - return [] + return GalleryData(root=[]) output = [] for img in y: caption = None if isinstance(img, (tuple, list)): img, caption = img if isinstance(img, np.ndarray): - file = self.img_array_to_temp_file(img, dir=self.DEFAULT_TEMP_DIR) + file = processing_utils.save_img_array_to_cache( + img, cache_dir=self.GRADIO_CACHE + ) file_path = str(utils.abspath(file)) - self.temp_files.add(file_path) elif isinstance(img, _Image.Image): - file = self.pil_to_temp_file(img, dir=self.DEFAULT_TEMP_DIR) + file = processing_utils.save_pil_to_cache( + img, cache_dir=self.GRADIO_CACHE + ) file_path = str(utils.abspath(file)) - self.temp_files.add(file_path) elif isinstance(img, (str, Path)): - if utils.validate_url(img): - file_path = img - else: - file_path = self.make_temp_copy_if_needed(img) + file_path = str(img) else: raise ValueError(f"Cannot process type as image: {type(img)}") - if caption is not None: - output.append( - [{"name": file_path, "data": None, "is_file": True}, caption] - ) - else: - output.append({"name": file_path, "data": None, "is_file": True}) + entry = GalleryImage( + image=FileData(name=file_path, is_file=True), caption=caption + ) + output.append(entry) + return GalleryData(root=output) - return output + def preprocess(self, x: Any) -> Any: + return x - def style( - self, - *, - grid: int | tuple | None = None, - columns: int | tuple | None = None, - rows: int | tuple | None = None, - height: str | None = None, - container: bool | None = None, - preview: bool | None = None, - object_fit: str | None = None, - **kwargs, - ): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if grid is not None: - warn_deprecation( - "The 'grid' parameter will be deprecated. Please use 'columns' in the constructor instead.", - ) - self.columns = grid - if columns is not None: - self.columns = columns - if rows is not None: - self.rows = rows - if height is not None: - self.height = height - if preview is not None: - self.preview = preview - if object_fit is not None: - self.object_fit = object_fit - if container is not None: - self.container = container - return self + def example_inputs(self) -> Any: + return [ + "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png" + ] diff --git a/gradio/components/highlighted_text.py b/gradio/components/highlighted_text.py index a4d41fc4a8e4..00cf79646e3b 100644 --- a/gradio/components/highlighted_text.py +++ b/gradio/components/highlighted_text.py @@ -2,23 +2,28 @@ from __future__ import annotations -import warnings -from typing import Callable, Literal +from typing import Any, Callable, List, Union from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import ( - JSONSerializable, -) -from gradio.components.base import IOComponent, _Keywords -from gradio.deprecation import warn_style_method_deprecation -from gradio.events import Changeable, EventListenerMethod, Selectable +from gradio.components.base import Component +from gradio.data_classes import GradioModel, GradioRootModel +from gradio.events import Events set_documentation_group("component") +class HighlightedToken(GradioModel): + token: str + class_or_confidence: Union[str, float, None] = None + + +class HighlightedTextData(GradioRootModel): + root: List[HighlightedToken] + + @document() -class HighlightedText(Changeable, Selectable, IOComponent, JSONSerializable): +class HighlightedText(Component): """ Displays text that contains spans that are highlighted by category or numerical value. Preprocessing: passes a list of tuples as a {List[Tuple[str, float | str | None]]]} into the function. If no labels are provided, the text will be displayed as a single span. @@ -28,6 +33,9 @@ class HighlightedText(Changeable, Selectable, IOComponent, JSONSerializable): Guides: named-entity-recognition """ + data_model = HighlightedTextData + EVENTS = [Events.change, Events.select] + def __init__( self, value: list[tuple[str, str | float | None]] | dict | Callable | None = None, @@ -46,8 +54,10 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, interactive: bool | None = None, - **kwargs, ): """ Parameters: @@ -56,7 +66,7 @@ def __init__( show_legend: whether to show span categories in a separate legend or inline. combine_adjacent: If True, will merge the labels of adjacent tokens belonging to the same category. adjacent_separator: Specifies the separator to be used between tokens if combine_adjacent is True. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. container: If True, will place the component in a container - providing some extra padding around the border. @@ -65,6 +75,8 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. interactive: If True, the component will be editable, and allow user to select spans of text and label them. """ @@ -72,14 +84,7 @@ def __init__( self.show_legend = show_legend self.combine_adjacent = combine_adjacent self.adjacent_separator = adjacent_separator - self.select: EventListenerMethod - """ - Event listener for when the user selects Highlighted text span. - Uses event data gradio.SelectData to carry `value` referring to selected [text, label] tuple, and `index` to refer to span index. - See EventData documentation on how to use this event data. - """ - IOComponent.__init__( - self, + super().__init__( label=label, every=every, show_label=show_label, @@ -89,48 +94,19 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, interactive=interactive, - **kwargs, ) - @staticmethod - def update( - value: list[tuple[str, str | float | None]] - | dict - | Literal[_Keywords.NO_VALUE] - | None = _Keywords.NO_VALUE, - color_map: dict[str, str] | None = None, - show_legend: bool | None = None, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - visible: bool | None = None, - interactive: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.HighlightedText(...)` instead of `return gr.HighlightedText.update(...)`." - ) - updated_config = { - "color_map": color_map, - "show_legend": show_legend, - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "value": value, - "interactive": interactive, - "__type__": "update", - } - return updated_config + def example_inputs(self) -> Any: + return {"value": [{"token": "Hello", "class_or_confidence": "1"}]} def postprocess( self, y: list[tuple[str, str | float | None]] | dict | None - ) -> list[tuple[str, str | float | None]] | None: + ) -> HighlightedTextData | None: """ Parameters: y: List of (word, category) tuples, or a dictionary of two keys: "text", and "entities", which itself is a list of dictionaries, each of which have the keys: "entity" (or "entity_group"), "start", and "end" @@ -182,23 +158,16 @@ def postprocess( running_category = category if running_text is not None: output.append((running_text, running_category)) - return output + return HighlightedTextData( + root=[ + HighlightedToken(token=o[0], class_or_confidence=o[1]) + for o in output + ] + ) else: - return y + return HighlightedTextData( + root=[HighlightedToken(token=o[0], class_or_confidence=o[1]) for o in y] + ) - def style( - self, - *, - color_map: dict[str, str] | None = None, - container: bool | None = None, - **kwargs, - ): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if container is not None: - self.container = container - if color_map is not None: - self.color_map = color_map - return self + def preprocess(self, x: Any) -> Any: + return super().preprocess(x) diff --git a/gradio/components/html.py b/gradio/components/html.py index 19199abc6c9f..c429cdedf3df 100644 --- a/gradio/components/html.py +++ b/gradio/components/html.py @@ -2,20 +2,18 @@ from __future__ import annotations -import warnings -from typing import Any, Callable, Literal +from typing import Any, Callable from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import StringSerializable -from gradio.components.base import IOComponent, _Keywords -from gradio.events import Changeable +from gradio.components.base import Component +from gradio.events import Events set_documentation_group("component") @document() -class HTML(Changeable, IOComponent, StringSerializable): +class HTML(Component): """ Used to display arbitrary HTML output. Preprocessing: this component does *not* accept input. @@ -25,6 +23,8 @@ class HTML(Changeable, IOComponent, StringSerializable): Guides: key-features """ + EVENTS = [Events.change] + def __init__( self, value: str | Callable = "", @@ -35,45 +35,43 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, - **kwargs, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, ): """ Parameters: value: Default value. If callable, the function will be called whenever the app loads to set the initial value of the component. - label: component name in interface. + label: The label for this component. Is used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. - show_label: if True, will display label. + show_label: This parameter has no effect. visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. """ - IOComponent.__init__( - self, + super().__init__( label=label, every=every, show_label=show_label, visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - @staticmethod - def update( - value: Any | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - label: str | None = None, - show_label: bool | None = None, - visible: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.HTML(...)` instead of `return gr.HTML.update(...)`." - ) - updated_config = { - "label": label, - "show_label": show_label, - "visible": visible, - "value": value, - "__type__": "update", - } - return updated_config + def example_inputs(self) -> Any: + return "

Hello

" + + def preprocess(self, x: Any) -> Any: + return x + + def postprocess(self, y): + return y + + def api_info(self) -> dict[str, Any]: + return {"type": "string"} diff --git a/gradio/components/image.py b/gradio/components/image.py index b5fd6a19cd02..5f2ecf028956 100644 --- a/gradio/components/image.py +++ b/gradio/components/image.py @@ -11,39 +11,19 @@ import PIL.ImageOps from gradio_client import utils as client_utils from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import ImgSerializable from PIL import Image as _Image # using _ to minimize namespace pollution from gradio import processing_utils, utils -from gradio.components.base import IOComponent, _Keywords -from gradio.deprecation import warn_style_method_deprecation -from gradio.events import ( - Changeable, - Clearable, - Editable, - EventListenerMethod, - Selectable, - Streamable, - Uploadable, -) -from gradio.interpretation import TokenInterpretable +from gradio.components.base import Component, StreamingInput +from gradio.data_classes import FileData +from gradio.events import Events set_documentation_group("component") _Image.init() # fixes https://github.com/gradio-app/gradio/issues/2843 @document() -class Image( - Editable, - Clearable, - Changeable, - Streamable, - Selectable, - Uploadable, - IOComponent, - ImgSerializable, - TokenInterpretable, -): +class Image(StreamingInput, Component): """ Creates an image component that can be used to upload/draw images (as an input) or display images (as an output). Preprocessing: passes the uploaded image as a {numpy.array}, {PIL.Image} or {str} filepath depending on `type` -- unless `tool` is `sketch` AND source is one of `upload` or `webcam`. In these cases, a {dict} with keys `image` and `mask` is passed, and the format of the corresponding values depends on `type`. @@ -53,6 +33,16 @@ class Image( Guides: image-classification-in-pytorch, image-classification-in-tensorflow, image-classification-with-vision-transformers, building-a-pictionary_app, create-your-own-friends-with-a-gan """ + EVENTS = [ + Events.edit, + Events.clear, + Events.change, + Events.stream, + Events.select, + Events.upload, + ] + data_model = FileData + def __init__( self, value: str | _Image.Image | np.ndarray | None = None, @@ -79,12 +69,14 @@ def __init__( streaming: bool = False, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, mirror_webcam: bool = True, brush_radius: float | None = None, brush_color: str = "#000000", mask_opacity: float = 0.7, show_share_button: bool | None = None, - **kwargs, ): """ Parameters: @@ -97,7 +89,7 @@ def __init__( source: Source of image. "upload" creates a box where user can drop an image file, "webcam" allows user to take snapshot from their webcam, "canvas" defaults to a white image that can be edited and drawn upon with tools. tool: Tools used for editing. "editor" allows a full screen editor (and is the default if source is "upload" or "webcam"), "select" provides a cropping and zoom tool, "sketch" allows you to create a binary sketch (and is the default if source="canvas"), and "color-sketch" allows you to created a sketch in different colors. "color-sketch" can be used with source="upload" or "webcam" to allow sketching on an image. "sketch" can also be used with "upload" or "webcam" to create a mask over an image and in that case both the image and mask are passed into the function as a dictionary with keys "image" and "mask" respectively. type: The format the image is converted to before being passed into the prediction function. "numpy" converts the image to a numpy array with shape (height, width, 3) and values from 0 to 255, "pil" converts the image to a PIL image object, "filepath" passes a str path to a temporary file containing the image. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. show_download_button: If True, will display button to download image. @@ -109,6 +101,8 @@ def __init__( streaming: If True when used in a `live` interface, will automatically stream webcam feed. Only valid is source is 'webcam'. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. mirror_webcam: If True webcam will be mirrored. Default is True. brush_radius: Size of the brush for Sketch. Default is None which chooses a sensible default brush_color: Color of the brush for Sketch as hex string. Default is "#000000". @@ -144,19 +138,12 @@ def __init__( self.show_download_button = show_download_button if streaming and source != "webcam": raise ValueError("Image streaming only available if source is 'webcam'.") - self.select: EventListenerMethod - """ - Event listener for when the user clicks on a pixel within the image. - Uses event data gradio.SelectData to carry `index` to refer to the [x, y] coordinates of the clicked pixel. - See EventData documentation on how to use this event data. - """ self.show_share_button = ( (utils.get_space() is not None) if show_share_button is None else show_share_button ) - IOComponent.__init__( - self, + super().__init__( label=label, every=every, show_label=show_label, @@ -167,50 +154,11 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - TokenInterpretable.__init__(self) - - @staticmethod - def update( - value: Any | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - height: int | None = None, - width: int | None = None, - label: str | None = None, - show_label: bool | None = None, - show_download_button: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - interactive: bool | None = None, - visible: bool | None = None, - brush_radius: float | None = None, - brush_color: str | None = None, - mask_opacity: float | None = None, - show_share_button: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Image(...)` instead of `return gr.Image.update(...)`." - ) - return { - "height": height, - "width": width, - "label": label, - "show_label": show_label, - "show_download_button": show_download_button, - "container": container, - "scale": scale, - "min_width": min_width, - "interactive": interactive, - "visible": visible, - "value": value, - "brush_radius": brush_radius, - "brush_color": brush_color, - "mask_opacity": mask_opacity, - "show_share_button": show_share_button, - "__type__": "update", - } def _format_image( self, im: _Image.Image | None @@ -224,10 +172,9 @@ def _format_image( elif self.type == "numpy": return np.array(im) elif self.type == "filepath": - path = self.pil_to_temp_file( - im, dir=self.DEFAULT_TEMP_DIR, format=fmt or "png" + path = processing_utils.save_pil_to_cache( + im, cache_dir=self.GRADIO_CACHE, format=fmt or "png" # type: ignore ) - self.temp_files.add(path) return path else: raise ValueError( @@ -253,8 +200,10 @@ def preprocess( assert isinstance(x, dict) x, mask = x["image"], x["mask"] - assert isinstance(x, str) - im = processing_utils.decode_base64_to_image(x) + if isinstance(x, str): + im = processing_utils.decode_base64_to_image(x) + else: + im = _Image.open(x["name"]) with warnings.catch_warnings(): warnings.simplefilter("ignore") im = im.convert(self.image_mode) @@ -284,7 +233,7 @@ def preprocess( def postprocess( self, y: np.ndarray | _Image.Image | str | Path | None - ) -> str | None: + ) -> FileData | None: """ Parameters: y: image as a numpy array, PIL Image, string/Path filepath, or string URL @@ -294,125 +243,19 @@ def postprocess( if y is None: return None if isinstance(y, np.ndarray): - return processing_utils.encode_array_to_base64(y) + path = processing_utils.save_img_array_to_cache( + y, cache_dir=self.GRADIO_CACHE + ) elif isinstance(y, _Image.Image): - return processing_utils.encode_pil_to_base64(y) + path = processing_utils.save_pil_to_cache(y, cache_dir=self.GRADIO_CACHE) elif isinstance(y, (str, Path)): - return client_utils.encode_url_or_file_to_base64(y) + path = y if isinstance(y, str) else y.name else: raise ValueError("Cannot process this value as an Image") - - def set_interpret_parameters(self, segments: int = 16): - """ - Calculates interpretation score of image subsections by splitting the image into subsections, then using a "leave one out" method to calculate the score of each subsection by whiting out the subsection and measuring the delta of the output value. - Parameters: - segments: Number of interpretation segments to split image into. - """ - self.interpretation_segments = segments - return self - - def _segment_by_slic(self, x): - """ - Helper method that segments an image into superpixels using slic. - Parameters: - x: base64 representation of an image - """ - x = processing_utils.decode_base64_to_image(x) - if self.shape is not None: - x = processing_utils.resize_and_crop(x, self.shape) - resized_and_cropped_image = np.array(x) - try: - from skimage.segmentation import slic - except (ImportError, ModuleNotFoundError) as err: - raise ValueError( - "Error: running this interpretation for images requires scikit-image, please install it first." - ) from err - try: - segments_slic = slic( - resized_and_cropped_image, - self.interpretation_segments, - compactness=10, - sigma=1, - start_label=1, - ) - except TypeError: # For skimage 0.16 and older - segments_slic = slic( - resized_and_cropped_image, - self.interpretation_segments, - compactness=10, - sigma=1, - ) - return segments_slic, resized_and_cropped_image - - def tokenize(self, x): - """ - Segments image into tokens, masks, and leave-one-out-tokens - Parameters: - x: base64 representation of an image - Returns: - tokens: list of tokens, used by the get_masked_input() method - leave_one_out_tokens: list of left-out tokens, used by the get_interpretation_neighbors() method - masks: list of masks, used by the get_interpretation_neighbors() method - """ - segments_slic, resized_and_cropped_image = self._segment_by_slic(x) - tokens, masks, leave_one_out_tokens = [], [], [] - replace_color = np.mean(resized_and_cropped_image, axis=(0, 1)) - for segment_value in np.unique(segments_slic): - mask = segments_slic == segment_value - image_screen = np.copy(resized_and_cropped_image) - image_screen[segments_slic == segment_value] = replace_color - leave_one_out_tokens.append( - processing_utils.encode_array_to_base64(image_screen) - ) - token = np.copy(resized_and_cropped_image) - token[segments_slic != segment_value] = 0 - tokens.append(token) - masks.append(mask) - return tokens, leave_one_out_tokens, masks - - def get_masked_inputs(self, tokens, binary_mask_matrix): - masked_inputs = [] - for binary_mask_vector in binary_mask_matrix: - masked_input = np.zeros_like(tokens[0], dtype=int) - for token, b in zip(tokens, binary_mask_vector): - masked_input = masked_input + token * int(b) - masked_inputs.append(processing_utils.encode_array_to_base64(masked_input)) - return masked_inputs - - def get_interpretation_scores( - self, x, neighbors, scores, masks, tokens=None, **kwargs - ) -> list[list[float]]: - """ - Returns: - A 2D array representing the interpretation score of each pixel of the image. - """ - x = processing_utils.decode_base64_to_image(x) - if self.shape is not None: - x = processing_utils.resize_and_crop(x, self.shape) - x = np.array(x) - output_scores = np.zeros((x.shape[0], x.shape[1])) - - for score, mask in zip(scores, masks): - output_scores += score * mask - - max_val, min_val = np.max(output_scores), np.min(output_scores) - if max_val > 0: - output_scores = (output_scores - min_val) / (max_val - min_val) - return output_scores.tolist() - - def style(self, *, height: int | None = None, width: int | None = None, **kwargs): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if height is not None: - self.height = height - if width is not None: - self.width = width - return self + return FileData(name=path, data=None, is_file=True) def check_streamable(self): - if self.source != "webcam": + if self.source != "webcam" and self.streaming: raise ValueError("Image streaming only available if source is 'webcam'.") def as_example(self, input_data: str | Path | None) -> str: @@ -423,3 +266,6 @@ def as_example(self, input_data: str | Path | None) -> str: if self.root_url or client_utils.is_http_url_like(input_data): return input_data return str(utils.abspath(input_data)) + + def example_inputs(self) -> Any: + return "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png" diff --git a/gradio/components/interpretation.py b/gradio/components/interpretation.py deleted file mode 100644 index b261f4f637d2..000000000000 --- a/gradio/components/interpretation.py +++ /dev/null @@ -1,55 +0,0 @@ -"""gr.Interpretation() component""" - -from __future__ import annotations - -from typing import Any, Literal - -from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import SimpleSerializable - -from gradio.components.base import Component, _Keywords - -set_documentation_group("component") - - -@document() -class Interpretation(Component, SimpleSerializable): - """ - Used to create an interpretation widget for a component. - Preprocessing: this component does *not* accept input. - Postprocessing: expects a {dict} with keys "original" and "interpretation". - - Guides: custom-interpretations-with-blocks - """ - - def __init__( - self, - component: Component, - *, - visible: bool = True, - elem_id: str | None = None, - elem_classes: list[str] | str | None = None, - **kwargs, - ): - """ - Parameters: - component: Which component to show in the interpretation widget. - visible: Whether or not the interpretation is visible. - elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. - elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. - """ - Component.__init__( - self, visible=visible, elem_id=elem_id, elem_classes=elem_classes, **kwargs - ) - self.component = component - - @staticmethod - def update( - value: Any | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - visible: bool | None = None, - ): - return { - "visible": visible, - "value": value, - "__type__": "update", - } diff --git a/gradio/components/json_component.py b/gradio/components/json_component.py index 46bef37ba671..1dc04fe41005 100644 --- a/gradio/components/json_component.py +++ b/gradio/components/json_component.py @@ -3,23 +3,19 @@ from __future__ import annotations import json -import warnings -from typing import Any, Callable, Literal +from pathlib import Path +from typing import Any, Callable from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import JSONSerializable -from gradio.components.base import IOComponent, _Keywords -from gradio.deprecation import warn_style_method_deprecation -from gradio.events import ( - Changeable, -) +from gradio.components.base import Component +from gradio.events import Events set_documentation_group("component") @document() -class JSON(Changeable, IOComponent, JSONSerializable): +class JSON(Component): """ Used to display arbitrary JSON output prettily. Preprocessing: this component does *not* accept input. @@ -28,6 +24,8 @@ class JSON(Changeable, IOComponent, JSONSerializable): Demos: zip_to_json, blocks_xray """ + EVENTS = [Events.change] + def __init__( self, value: str | dict | list | Callable | None = None, @@ -41,12 +39,14 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, - **kwargs, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, ): """ Parameters: value: Default value. If callable, the function will be called whenever the app loads to set the initial value of the component. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. container: If True, will place the component in a container - providing some extra padding around the border. @@ -55,9 +55,10 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. """ - IOComponent.__init__( - self, + super().__init__( label=label, every=every, show_label=show_label, @@ -67,34 +68,11 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, - ) - - @staticmethod - def update( - value: Any | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - visible: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.JSON(...)` instead of `return gr.JSON.update(...)`." ) - updated_config = { - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "value": value, - "__type__": "update", - } - return updated_config def postprocess(self, y: dict | list | str | None) -> dict | list | None: """ @@ -110,11 +88,17 @@ def postprocess(self, y: dict | list | str | None) -> dict | list | None: else: return y - def style(self, *, container: bool | None = None, **kwargs): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if container is not None: - self.container = container - return self + def preprocess(self, x: Any) -> Any: + return x + + def example_inputs(self) -> Any: + return {"foo": "bar"} + + def flag(self, x: Any, flag_dir: str | Path = "") -> str: + return json.dumps(x) + + def read_from_flag(self, x: Any, flag_dir: str | Path | None = None): + return json.loads(x) + + def api_info(self) -> dict[str, Any]: + return {"type": {}, "description": "any valid json"} diff --git a/gradio/components/label.py b/gradio/components/label.py index f6e965b0a3f8..d4068091d98f 100644 --- a/gradio/components/label.py +++ b/gradio/components/label.py @@ -2,29 +2,32 @@ from __future__ import annotations +import json import operator -import warnings from pathlib import Path -from typing import Callable, Literal +from typing import Any, Callable, List, Optional, Union from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import ( - JSONSerializable, -) - -from gradio.components.base import IOComponent, _Keywords -from gradio.deprecation import warn_style_method_deprecation -from gradio.events import ( - Changeable, - EventListenerMethod, - Selectable, -) + +from gradio.components.base import Component +from gradio.data_classes import GradioModel +from gradio.events import Events set_documentation_group("component") +class LabelConfidence(GradioModel): + label: Optional[Union[str, int, float]] = None + confidence: Optional[float] = None + + +class LabelData(GradioModel): + label: Union[str, int, float] + confidences: Optional[List[LabelConfidence]] = None + + @document() -class Label(Changeable, Selectable, IOComponent, JSONSerializable): +class Label(Component): """ Displays a classification label, along with confidence scores of top categories, if provided. Preprocessing: this component does *not* accept input. @@ -35,6 +38,8 @@ class Label(Changeable, Selectable, IOComponent, JSONSerializable): """ CONFIDENCES_KEY = "confidences" + data_model = LabelData + EVENTS = [Events.change, Events.select] def __init__( self, @@ -50,14 +55,16 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, color: str | None = None, - **kwargs, ): """ Parameters: value: Default value to show in the component. If a str or number is provided, simply displays the string or number. If a {Dict[str, float]} of classes and confidences is provided, displays the top class on top and the `num_top_classes` below, along with their confidence bars. If callable, the function will be called whenever the app loads to set the initial value of the component. num_top_classes: number of most confident classes to show. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. container: If True, will place the component in a container - providing some extra padding around the border. @@ -66,18 +73,13 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. color: The background color of the label (either a valid css color name or hexadecimal string). """ self.num_top_classes = num_top_classes self.color = color - self.select: EventListenerMethod - """ - Event listener for when the user selects a category from Label. - Uses event data gradio.SelectData to carry `value` referring to name of selected category, and `index` to refer to index. - See EventData documentation on how to use this event data. - """ - IOComponent.__init__( - self, + super().__init__( label=label, every=every, show_label=show_label, @@ -87,11 +89,15 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - def postprocess(self, y: dict[str, float] | str | float | None) -> dict | None: + def postprocess( + self, y: dict[str, float] | str | float | None + ) -> LabelData | dict | None: """ Parameters: y: a dictionary mapping labels to confidence value, or just a string/numerical label by itself @@ -101,9 +107,9 @@ def postprocess(self, y: dict[str, float] | str | float | None) -> dict | None: if y is None or y == {}: return {} if isinstance(y, str) and y.endswith(".json") and Path(y).exists(): - return self.serialize(y) + return LabelData(**json.loads(Path(y).read_text())) if isinstance(y, (str, float, int)): - return {"label": str(y)} + return LabelData(label=str(y)) if isinstance(y, dict): if "confidences" in y and isinstance(y["confidences"], dict): y = y["confidences"] @@ -111,67 +117,29 @@ def postprocess(self, y: dict[str, float] | str | float | None) -> dict | None: sorted_pred = sorted(y.items(), key=operator.itemgetter(1), reverse=True) if self.num_top_classes is not None: sorted_pred = sorted_pred[: self.num_top_classes] - return { - "label": sorted_pred[0][0], - "confidences": [ - {"label": pred[0], "confidence": pred[1]} for pred in sorted_pred - ], - } + return LabelData( + **{ + "label": sorted_pred[0][0], + "confidences": [ + {"label": pred[0], "confidence": pred[1]} + for pred in sorted_pred + ], + } + ) raise ValueError( "The `Label` output interface expects one of: a string label, or an int label, a " "float label, or a dictionary whose keys are labels and values are confidences. " f"Instead, got a {type(y)}" ) - @staticmethod - def update( - value: dict[str, float] - | str - | float - | Literal[_Keywords.NO_VALUE] - | None = _Keywords.NO_VALUE, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - visible: bool | None = None, - color: str | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Label(...)` instead of `return gr.Label.update(...)`." - ) - # If color is not specified (NO_VALUE) map it to None so that - # it gets filtered out in postprocess. This will mean the color - # will not be updated in the front-end - if color is _Keywords.NO_VALUE: - color = None - # If the color was specified by the developer as None - # Map is so that the color is updated to be transparent, - # e.g. no background default state. - elif color is None: - color = "transparent" + def preprocess(self, x: Any) -> Any: + return x + + def example_inputs(self) -> Any: return { - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "value": value, - "color": color, - "__type__": "update", + "label": "Cat", + "confidences": [ + {"label": "cat", "confidence": 0.9}, + {"label": "dog", "confidence": 0.1}, + ], } - - def style( - self, - *, - container: bool | None = None, - ): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if container is not None: - self.container = container - return self diff --git a/gradio/components/line_plot.py b/gradio/components/line_plot.py index 7feb215679ce..86505ce44977 100644 --- a/gradio/components/line_plot.py +++ b/gradio/components/line_plot.py @@ -2,15 +2,13 @@ from __future__ import annotations -import warnings -from typing import Callable, Literal +from typing import Any, Callable, Literal import altair as alt import pandas as pd from gradio_client.documentation import document, set_documentation_group -from gradio.components.base import _Keywords -from gradio.components.plot import AltairPlot, Plot +from gradio.components.plot import AltairPlot, AltairPlotData, Plot set_documentation_group("component") @@ -26,6 +24,8 @@ class LinePlot(Plot): Demos: line_plot, live_dashboard """ + data_model = AltairPlotData + def __init__( self, value: pd.DataFrame | Callable | None = None, @@ -82,8 +82,10 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, show_actions_button: bool = False, - **kwargs, ): """ Parameters: @@ -115,6 +117,8 @@ def __init__( visible: Whether the plot should be visible. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. show_actions_button: Whether to show the actions button on the top right corner of the plot. """ self.x = x @@ -149,151 +153,15 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, every=every, - **kwargs, ) def get_block_name(self) -> str: return "plot" - @staticmethod - def update( - value: pd.DataFrame | dict | Literal[_Keywords.NO_VALUE] = _Keywords.NO_VALUE, - x: str | None = None, - y: str | None = None, - color: str | None = None, - stroke_dash: str | None = None, - overlay_point: bool | None = None, - title: str | None = None, - tooltip: list[str] | str | None = None, - x_title: str | None = None, - y_title: str | None = None, - x_label_angle: float | None = None, - y_label_angle: float | None = None, - color_legend_title: str | None = None, - stroke_dash_legend_title: str | None = None, - color_legend_position: Literal[ - "left", - "right", - "top", - "bottom", - "top-left", - "top-right", - "bottom-left", - "bottom-right", - "none", - ] - | None = None, - stroke_dash_legend_position: Literal[ - "left", - "right", - "top", - "bottom", - "top-left", - "top-right", - "bottom-left", - "bottom-right", - "none", - ] - | None = None, - height: int | None = None, - width: int | None = None, - x_lim: list[int] | None = None, - y_lim: list[int] | None = None, - interactive: bool | None = None, - caption: str | None = None, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - visible: bool | None = None, - ): - """Update an existing plot component. - - If updating any of the plot properties (color, size, etc) the value, x, and y parameters must be specified. - - Parameters: - value: The pandas dataframe containing the data to display in a scatter plot. - x: Column corresponding to the x axis. - y: Column corresponding to the y axis. - color: The column to determine the point color. If the column contains numeric data, gradio will interpolate the column data so that small values correspond to light colors and large values correspond to dark values. - stroke_dash: The column to determine the symbol used to draw the line, e.g. dashed lines, dashed lines with points. - overlay_point: Whether to draw a point on the line for each (x, y) coordinate pair. - title: The title to display on top of the chart. - tooltip: The column (or list of columns) to display on the tooltip when a user hovers a point on the plot. - x_title: The title given to the x axis. By default, uses the value of the x parameter. - y_title: The title given to the y axis. By default, uses the value of the y parameter. - x_label_angle: The angle for the x axis labels. Positive values are clockwise, and negative values are counter-clockwise. - y_label_angle: The angle for the y axis labels. Positive values are clockwise, and negative values are counter-clockwise. - color_legend_title: The title given to the color legend. By default, uses the value of color parameter. - stroke_dash_legend_title: The title given to the stroke legend. By default, uses the value of stroke parameter. - color_legend_position: The position of the color legend. If the string value 'none' is passed, this legend is omitted. For other valid position values see: https://vega.github.io/vega/docs/legends/#orientation - stroke_dash_legend_position: The position of the stoke_dash legend. If the string value 'none' is passed, this legend is omitted. For other valid position values see: https://vega.github.io/vega/docs/legends/#orientation - height: The height of the plot in pixels. - width: The width of the plot in pixels. - x_lim: A tuple or list containing the limits for the x-axis, specified as [x_min, x_max]. - y_lim: A tuple of list containing the limits for the y-axis, specified as [y_min, y_max]. - caption: The (optional) caption to display below the plot. - interactive: Whether users should be able to interact with the plot by panning or zooming with their mouse or trackpad. - label: The (optional) label to display in the top left corner of the plot. - show_label: Whether the label should be displayed. - visible: Whether the plot should be visible. - """ - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.LinePlot(...)` instead of `return gr.LinePlot.update(...)`." - ) - properties = [ - x, - y, - color, - stroke_dash, - overlay_point, - title, - tooltip, - x_title, - y_title, - x_label_angle, - y_label_angle, - color_legend_title, - stroke_dash_legend_title, - color_legend_position, - stroke_dash_legend_position, - height, - width, - x_lim, - y_lim, - interactive, - ] - if any(properties): - if not isinstance(value, pd.DataFrame): - raise ValueError( - "In order to update plot properties the value parameter " - "must be provided, and it must be a Dataframe. Please pass a value " - "parameter to gr.LinePlot.update." - ) - if x is None or y is None: - raise ValueError( - "In order to update plot properties, the x and y axis data " - "must be specified. Please pass valid values for x an y to " - "gr.LinePlot.update." - ) - chart = LinePlot.create_plot(value, *properties) - value = {"type": "altair", "plot": chart.to_json(), "chart": "line"} - - updated_config = { - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "value": value, - "caption": caption, - "__type__": "update", - } - return updated_config - @staticmethod def create_plot( value: pd.DataFrame, @@ -423,7 +291,9 @@ def create_plot( return chart - def postprocess(self, y: pd.DataFrame | dict | None) -> dict[str, str] | None: + def postprocess( + self, y: pd.DataFrame | dict | None + ) -> AltairPlotData | dict | None: # if None or update if y is None or isinstance(y, dict): return y @@ -453,4 +323,12 @@ def postprocess(self, y: pd.DataFrame | dict | None) -> dict[str, str] | None: width=self.width, ) - return {"type": "altair", "plot": chart.to_json(), "chart": "line"} + return AltairPlotData( + **{"type": "altair", "plot": chart.to_json(), "chart": "line"} + ) + + def example_inputs(self) -> Any: + return None + + def preprocess(self, x: Any) -> Any: + return x diff --git a/gradio/components/login_button.py b/gradio/components/login_button.py index 1f47a54a3c3b..6005f3add540 100644 --- a/gradio/components/login_button.py +++ b/gradio/components/login_button.py @@ -2,7 +2,7 @@ from __future__ import annotations import warnings -from typing import Any, Literal +from typing import Literal from gradio_client.documentation import document, set_documentation_group @@ -23,8 +23,9 @@ class LoginButton(Button): def __init__( self, - *, value: str = "Sign in with Hugging Face", + *, + every: float | None = None, variant: Literal["primary", "secondary", "stop"] = "secondary", size: Literal["sm", "lg"] | None = None, icon: str @@ -34,12 +35,15 @@ def __init__( interactive: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, scale: int | None = 0, min_width: int | None = None, - **kwargs, ): super().__init__( value, + every=every, variant=variant, size=size, icon=icon, @@ -48,9 +52,11 @@ def __init__( interactive=interactive, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, scale=scale, min_width=min_width, - **kwargs, ) if Context.root_block is not None: self.activate() @@ -67,16 +73,16 @@ def activate(self): self.attach_load_event(self._check_login_status, None) - def _check_login_status(self, request: Request) -> dict[str, Any]: + def _check_login_status(self, request: Request) -> LoginButton: # Each time the page is refreshed or loaded, check if the user is logged in and adapt label session = getattr(request, "session", None) or getattr( request.request, "session", None ) if session is None or "oauth_profile" not in session: - return self.update("Sign in with Hugging Face", interactive=True) + return LoginButton("Sign in with Hugging Face", interactive=True) else: username = session["oauth_profile"]["preferred_username"] - return self.update(f"Signed in as {username}", interactive=False) + return LoginButton(f"Signed in as {username}", interactive=False) # JS code to redirects to /login/huggingface if user is not logged in. diff --git a/gradio/components/logout_button.py b/gradio/components/logout_button.py index b6fd8b9ed22a..ac801a29130b 100644 --- a/gradio/components/logout_button.py +++ b/gradio/components/logout_button.py @@ -20,8 +20,9 @@ class LogoutButton(Button): def __init__( self, - *, value: str = "Logout", + *, + every: float | None = None, variant: Literal["primary", "secondary", "stop"] = "secondary", size: Literal["sm", "lg"] | None = None, icon: str @@ -32,12 +33,15 @@ def __init__( interactive: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, scale: int | None = 0, min_width: int | None = None, - **kwargs, ): super().__init__( value, + every=every, variant=variant, size=size, icon=icon, @@ -46,7 +50,9 @@ def __init__( interactive=interactive, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, scale=scale, min_width=min_width, - **kwargs, ) diff --git a/gradio/components/markdown.py b/gradio/components/markdown.py index fa58269a4ff5..eae680bded39 100644 --- a/gradio/components/markdown.py +++ b/gradio/components/markdown.py @@ -3,22 +3,18 @@ from __future__ import annotations import inspect -import warnings -from typing import Any, Callable, Literal +from typing import Any, Callable from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import StringSerializable -from gradio.components.base import IOComponent, _Keywords -from gradio.events import ( - Changeable, -) +from gradio.components.base import Component +from gradio.events import Events set_documentation_group("component") @document() -class Markdown(IOComponent, Changeable, StringSerializable): +class Markdown(Component): """ Used to render arbitrary Markdown output. Can also render latex enclosed by dollar signs. Preprocessing: this component does *not* accept input. @@ -28,44 +24,58 @@ class Markdown(IOComponent, Changeable, StringSerializable): Guides: key-features """ + EVENTS = [Events.change] + def __init__( self, value: str | Callable = "", *, + label: str | None = None, + every: float | None = None, + show_label: bool | None = None, rtl: bool = False, latex_delimiters: list[dict[str, str | bool]] | None = None, visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, sanitize_html: bool = True, line_breaks: bool = False, - **kwargs, ): """ Parameters: value: Value to show in Markdown component. If callable, the function will be called whenever the app loads to set the initial value of the component. + label: The label for this component. Is used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. + every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. + show_label: This parameter has no effect. rtl: If True, sets the direction of the rendered text to right-to-left. Default is False, which renders text left-to-right. latex_delimiters: A list of dicts of the form {"left": open delimiter (str), "right": close delimiter (str), "display": whether to display in newline (bool)} that will be used to render LaTeX expressions. If not provided, `latex_delimiters` is set to `[{ "left": "$", "right": "$", "display": False }]`, so only expressions enclosed in $ delimiters will be rendered as LaTeX, and in the same line. Pass in an empty list to disable LaTeX rendering. For more information, see the [KaTeX documentation](https://katex.org/docs/autorender.html). visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. sanitize_html: If False, will disable HTML sanitization when converted from markdown. This is not recommended, as it can lead to security vulnerabilities. line_breaks: If True, will enable Github-flavored Markdown line breaks in chatbot messages. If False (default), single new lines will be ignored. """ self.rtl = rtl - if latex_delimiters is None: - latex_delimiters = [{"left": "$", "right": "$", "display": False}] self.latex_delimiters = latex_delimiters self.sanitize_html = sanitize_html self.line_breaks = line_breaks - IOComponent.__init__( - self, + super().__init__( + label=label, + every=every, + show_label=show_label, visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) def postprocess(self, y: str | None) -> str | None: @@ -80,29 +90,15 @@ def postprocess(self, y: str | None) -> str | None: unindented_y = inspect.cleandoc(y) return unindented_y - @staticmethod - def update( - value: Any | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - visible: bool | None = None, - rtl: bool | None = None, - latex_delimiters: list[dict[str, str | bool]] | None = None, - sanitize_html: bool | None = None, - line_breaks: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Markdown(...)` instead of `return gr.Markdown.update(...)`." - ) - updated_config = { - "visible": visible, - "value": value, - "rtl": rtl, - "latex_delimiters": latex_delimiters, - "sanitize_html": sanitize_html, - "line_breaks": line_breaks, - "__type__": "update", - } - return updated_config - def as_example(self, input_data: str | None) -> str: postprocessed = self.postprocess(input_data) return postprocessed if postprocessed else "" + + def preprocess(self, x: Any) -> Any: + return x + + def example_inputs(self) -> Any: + return "# Hello!" + + def api_info(self) -> dict[str, Any]: + return {"type": "string"} diff --git a/gradio/components/model3d.py b/gradio/components/model3d.py index d11bef86b2c1..ea2599c16009 100644 --- a/gradio/components/model3d.py +++ b/gradio/components/model3d.py @@ -2,29 +2,20 @@ from __future__ import annotations -import warnings from pathlib import Path -from typing import Any, Callable, Literal +from typing import Callable -from gradio_client import media_data from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import FileSerializable -from gradio.components.base import IOComponent, _Keywords -from gradio.events import ( - Changeable, - Clearable, - Editable, - Uploadable, -) +from gradio.components.base import Component +from gradio.data_classes import FileData +from gradio.events import Events set_documentation_group("component") @document() -class Model3D( - Changeable, Uploadable, Editable, Clearable, IOComponent, FileSerializable -): +class Model3D(Component): """ Component allows users to upload or view 3D Model files (.obj, .glb, or .gltf). Preprocessing: This component passes the uploaded file as a {str}filepath. @@ -34,6 +25,10 @@ class Model3D( Guides: how-to-use-3D-model-component """ + EVENTS = [Events.change, Events.upload, Events.edit, Events.clear] + + data_model = FileData + def __init__( self, value: str | Callable | None = None, @@ -57,7 +52,9 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, - **kwargs, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, ): """ Parameters: @@ -66,7 +63,7 @@ def __init__( camera_position: initial camera position of scene, provided as a tuple of `(alpha, beta, radius)`. Each value is optional. If provided, `alpha` and `beta` should be in degrees reflecting the angular position along the longitudinal and latitudinal axes, respectively. Radius corresponds to the distance from the center of the object to the camera. zoom_speed: the speed of zooming in and out of the scene when the cursor wheel is rotated or when screen is pinched on a mobile device. Should be a positive float, increase this value to make zooming faster, decrease to make it slower. Affects the wheelPrecision property of the camera. height: height of the model3D component, in pixels. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. show_label: if True, will display label. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. container: If True, will place the component in a container - providing some extra padding around the border. @@ -75,14 +72,14 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. """ self.clear_color = clear_color or [0, 0, 0, 0] self.camera_position = camera_position self.height = height self.zoom_speed = zoom_speed - - IOComponent.__init__( - self, + super().__init__( label=label, every=every, show_label=show_label, @@ -92,52 +89,12 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - def example_inputs(self) -> dict[str, Any]: - return { - "raw": {"is_file": False, "data": media_data.BASE64_MODEL3D}, - "serialized": "https://github.com/gradio-app/gradio/raw/main/test/test_files/Box.gltf", - } - - @staticmethod - def update( - value: Any | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - camera_position: tuple[ - int | float | None, int | float | None, int | float | None - ] - | None = None, - clear_color: tuple[float, float, float, float] | None = None, - height: int | None = None, - zoom_speed: float | None = None, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - visible: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Model3D(...)` instead of `return gr.Model3D.update(...)`." - ) - updated_config = { - "camera_position": camera_position, - "clear_color": clear_color, - "height": height, - "zoom_speed": zoom_speed, - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "value": value, - "__type__": "update", - } - return updated_config - def preprocess(self, x: dict[str, str] | None) -> str | None: """ Parameters: @@ -147,19 +104,9 @@ def preprocess(self, x: dict[str, str] | None) -> str | None: """ if x is None: return x - file_name, file_data, is_file = ( - x["name"], - x["data"], - x.get("is_file", False), - ) - if is_file: - temp_file_path = self.make_temp_copy_if_needed(file_name) - else: - temp_file_path = self.base64_to_temp_file_if_needed(file_data, file_name) + return x["name"] - return temp_file_path - - def postprocess(self, y: str | Path | None) -> dict[str, str] | None: + def postprocess(self, y: str | Path | None) -> FileData | None: """ Parameters: y: path to the model @@ -168,12 +115,11 @@ def postprocess(self, y: str | Path | None) -> dict[str, str] | None: """ if y is None: return y - data = { - "name": self.make_temp_copy_if_needed(y), - "data": None, - "is_file": True, - } - return data + return FileData(name=str(y), is_file=True) def as_example(self, input_data: str | None) -> str: return Path(input_data).name if input_data else "" + + def example_inputs(self): + # TODO: Use permanent link + return "https://raw.githubusercontent.com/gradio-app/gradio/main/demo/model3D/files/Fox.gltf" diff --git a/gradio/components/number.py b/gradio/components/number.py index 6f6d0149ac50..2dcde0bf140e 100644 --- a/gradio/components/number.py +++ b/gradio/components/number.py @@ -2,38 +2,19 @@ from __future__ import annotations -import math -import warnings -from typing import Callable, Literal +from typing import Any, Callable -import numpy as np from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import NumberSerializable - -from gradio.components.base import FormComponent, IOComponent, _Keywords -from gradio.events import ( - Changeable, - Focusable, - Inputable, - Submittable, -) + +from gradio.components.base import FormComponent +from gradio.events import Events from gradio.exceptions import Error -from gradio.interpretation import NeighborInterpretable set_documentation_group("component") @document() -class Number( - FormComponent, - Changeable, - Inputable, - Submittable, - Focusable, - IOComponent, - NumberSerializable, - NeighborInterpretable, -): +class Number(FormComponent): """ Creates a numeric field for user to enter numbers as input or display numeric output. Preprocessing: passes field value as a {float} or {int} into the function, depending on `precision`. @@ -43,6 +24,8 @@ class Number( Demos: tax_calculator, titanic_survival, blocks_simple_squares """ + EVENTS = [Events.change, Events.input, Events.submit, Events.focus] + def __init__( self, value: float | Callable | None = None, @@ -58,16 +41,18 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, precision: int | None = None, minimum: float | None = None, maximum: float | None = None, step: float = 1, - **kwargs, ): """ Parameters: value: default value. If callable, the function will be called whenever the app loads to set the initial value of the component. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. info: additional component description. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. @@ -78,6 +63,8 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. precision: Precision to round input/output to. If set to 0, will round to nearest integer and convert type to int. If None, no rounding happens. minimum: Minimum value. Only applied when component is used as an input. If a user provides a smaller value, a gr.Error exception is raised by the backend. maximum: Maximum value. Only applied when component is used as an input. If a user provides a larger value, a gr.Error exception is raised by the backend. @@ -88,8 +75,7 @@ def __init__( self.maximum = maximum self.step = step - IOComponent.__init__( - self, + super().__init__( label=label, info=info, every=every, @@ -101,10 +87,11 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - NeighborInterpretable.__init__(self) @staticmethod def _round_to_precision(num: float | int, precision: int | None) -> float | int: @@ -126,40 +113,6 @@ def _round_to_precision(num: float | int, precision: int | None) -> float | int: else: return round(num, precision) - @staticmethod - def update( - value: float | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - minimum: float | None = None, - maximum: float | None = None, - step: float = 1, - label: str | None = None, - info: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - interactive: bool | None = None, - visible: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Number(...)` instead of `return gr.Number.update(...)`." - ) - return { - "label": label, - "info": info, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "value": value, - "minimum": minimum, - "maximum": maximum, - "step": step, - "interactive": interactive, - "__type__": "update", - } - def preprocess(self, x: float | None) -> float | None: """ Parameters: @@ -188,51 +141,8 @@ def postprocess(self, y: float | None) -> float | None: return None return self._round_to_precision(y, self.precision) - def set_interpret_parameters( - self, steps: int = 3, delta: float = 1, delta_type: str = "percent" - ): - """ - Calculates interpretation scores of numeric values close to the input number. - Parameters: - steps: Number of nearby values to measure in each direction (above and below the input number). - delta: Size of step in each direction between nearby values. - delta_type: "percent" if delta step between nearby values should be a calculated as a percent, or "absolute" if delta should be a constant step change. - """ - self.interpretation_steps = steps - self.interpretation_delta = delta - self.interpretation_delta_type = delta_type - return self - - def get_interpretation_neighbors(self, x: float | int) -> tuple[list[float], dict]: - x = self._round_to_precision(x, self.precision) - if self.interpretation_delta_type == "percent": - delta = 1.0 * self.interpretation_delta * x / 100 - elif self.interpretation_delta_type == "absolute": - delta = self.interpretation_delta - else: - delta = self.interpretation_delta - if self.precision == 0 and math.floor(delta) != delta: - raise ValueError( - f"Delta value {delta} is not an integer and precision=0. Cannot generate valid set of neighbors. " - "If delta_type='percent', pick a value of delta such that x * delta is an integer. " - "If delta_type='absolute', pick a value of delta that is an integer." - ) - # run_interpretation will preprocess the neighbors so no need to convert to int here - negatives = ( - np.array(x) + np.arange(-self.interpretation_steps, 0) * delta - ).tolist() - positives = ( - np.array(x) + np.arange(1, self.interpretation_steps + 1) * delta - ).tolist() - return negatives + positives, {} - - def get_interpretation_scores( - self, x: float, neighbors: list[float], scores: list[float | None], **kwargs - ) -> list[tuple[float, float | None]]: - """ - Returns: - Each tuple set represents a numeric value near the input and its corresponding interpretation score. - """ - interpretation = list(zip(neighbors, scores)) - interpretation.insert(int(len(interpretation) / 2), (x, None)) - return interpretation + def api_info(self) -> dict[str, str]: + return {"type": "number"} + + def example_inputs(self) -> Any: + return 3 diff --git a/gradio/components/plot.py b/gradio/components/plot.py index 763f871c9d7b..232114e4403e 100644 --- a/gradio/components/plot.py +++ b/gradio/components/plot.py @@ -3,27 +3,35 @@ from __future__ import annotations import json -import warnings from types import ModuleType from typing import Any, Callable, Literal import altair as alt import pandas as pd from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import JSONSerializable from gradio import processing_utils -from gradio.components.base import IOComponent, _Keywords -from gradio.deprecation import warn_style_method_deprecation -from gradio.events import Changeable, Clearable +from gradio.components.base import Component +from gradio.data_classes import GradioModel +from gradio.events import Events set_documentation_group("component") +class PlotData(GradioModel): + type: Literal["altair", "bokeh", "plotly", "matplotlib"] + plot: str + + +class AltairPlotData(PlotData): + chart: Literal["bar", "line", "scatter"] + type: Literal["altair"] = "altair" + + @document() -class Plot(Changeable, Clearable, IOComponent, JSONSerializable): +class Plot(Component): """ - Used to display various kinds of plots (matplotlib, plotly, or bokeh are supported) + Used to display various kinds of plots (matplotlib, plotly, or bokeh are supported). Preprocessing: this component does *not* accept input. Postprocessing: expects either a {matplotlib.figure.Figure}, a {plotly.graph_objects._figure.Figure}, or a {dict} corresponding to a bokeh plot (json_item format) @@ -31,6 +39,9 @@ class Plot(Changeable, Clearable, IOComponent, JSONSerializable): Guides: plot-component-for-maps """ + data_model = PlotData + EVENTS = [Events.change, Events.clear] + def __init__( self, value: Callable | None | pd.DataFrame = None, @@ -44,12 +55,14 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, - **kwargs, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, ): """ Parameters: value: Optionally, supply a default plot object to display, must be a matplotlib, plotly, altair, or bokeh figure, or a callable. If callable, the function will be called whenever the app loads to set the initial value of the component. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. container: If True, will place the component in a container - providing some extra padding around the border. @@ -58,9 +71,10 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. """ - IOComponent.__init__( - self, + super().__init__( label=label, every=every, show_label=show_label, @@ -70,8 +84,10 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) def get_config(self): @@ -86,32 +102,13 @@ def get_config(self): config["bokeh_version"] = bokeh_version return config - @staticmethod - def update( - value: Any | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - visible: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Plot(...)` instead of `return gr.Plot.update(...)`." - ) - updated_config = { - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "value": value, - "__type__": "update", - } - return updated_config - - def postprocess(self, y) -> dict[str, str] | None: + def preprocess(self, x: Any) -> Any: + return x + + def example_inputs(self) -> Any: + return None + + def postprocess(self, y) -> PlotData | None: """ Parameters: y: plot data @@ -134,16 +131,7 @@ def postprocess(self, y) -> dict[str, str] | None: is_altair = "altair" in y.__module__ dtype = "altair" if is_altair else "plotly" out_y = y.to_json() - return {"type": dtype, "plot": out_y} - - def style(self, container: bool | None = None): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if container is not None: - self.container = container - return self + return PlotData(**{"type": dtype, "plot": out_y}) class AltairPlot: diff --git a/gradio/components/radio.py b/gradio/components/radio.py index 2e1d1aad80b9..cd7b4e75c7f9 100644 --- a/gradio/components/radio.py +++ b/gradio/components/radio.py @@ -2,30 +2,18 @@ from __future__ import annotations -import warnings -from typing import Any, Callable, Literal +from typing import Any, Callable from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import StringSerializable -from gradio.components.base import FormComponent, IOComponent, _Keywords -from gradio.deprecation import warn_deprecation, warn_style_method_deprecation -from gradio.events import Changeable, EventListenerMethod, Inputable, Selectable -from gradio.interpretation import NeighborInterpretable +from gradio.components.base import FormComponent +from gradio.events import Events set_documentation_group("component") @document() -class Radio( - FormComponent, - Selectable, - Changeable, - Inputable, - IOComponent, - StringSerializable, - NeighborInterpretable, -): +class Radio(FormComponent): """ Creates a set of (string or numeric type) radio buttons of which only one can be selected. Preprocessing: passes the value of the selected radio button as a {str} or {int} or {float} or its index as an {int} into the function, depending on `type`. @@ -35,6 +23,8 @@ class Radio( Demos: sentence_builder, titanic_survival, blocks_essay """ + EVENTS = [Events.select, Events.change, Events.input] + def __init__( self, choices: list[str | int | float | tuple[str, str | int | float]] | None = None, @@ -52,14 +42,16 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, - **kwargs, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, ): """ Parameters: choices: A list of string or numeric options to select from. An option can also be a tuple of the form (name, value), where name is the displayed name of the radio button and value is the value to be passed to the function, or returned by the function. value: The option selected by default. If None, no option is selected by default. If callable, the function will be called whenever the app loads to set the initial value of the component. type: Type of value to be returned by component. "value" returns the string of the choice selected, "index" returns the index of the choice selected. - label: Component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. info: Additional component description. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. @@ -70,6 +62,8 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. """ self.choices = ( # Although we expect choices to be a list of tuples, it can be a list of tuples if the Gradio app @@ -84,14 +78,7 @@ def __init__( f"Invalid value for parameter `type`: {type}. Please choose from one of: {valid_types}" ) self.type = type - self.select: EventListenerMethod - """ - Event listener for when the user selects Radio option. - Uses event data gradio.SelectData to carry `value` referring to label of selected option, and `index` to refer to index. - See EventData documentation on how to use this event data. - """ - IOComponent.__init__( - self, + super().__init__( label=label, info=info, every=every, @@ -103,55 +90,14 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - NeighborInterpretable.__init__(self) - def example_inputs(self) -> dict[str, Any]: - return { - "raw": self.choices[0][1] if self.choices else None, - "serialized": self.choices[0][1] if self.choices else None, - } - - @staticmethod - def update( - value: str - | int - | float - | Literal[_Keywords.NO_VALUE] - | None = _Keywords.NO_VALUE, - choices: list[str | int | float | tuple[str, str | int | float]] | None = None, - label: str | None = None, - info: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - interactive: bool | None = None, - visible: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Radio(...)` instead of `return gr.Radio.update(...)`." - ) - choices = ( - None - if choices is None - else [c if isinstance(c, tuple) else (str(c), c) for c in choices] - ) - return { - "choices": choices, - "label": label, - "info": info, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "interactive": interactive, - "visible": visible, - "value": value, - "__type__": "update", - } + def example_inputs(self) -> Any: + return self.choices[0][1] if self.choices else None def preprocess(self, x: str | int | float | None) -> str | int | float | None: """ @@ -173,38 +119,15 @@ def preprocess(self, x: str | int | float | None) -> str | int | float | None: f"Unknown type: {self.type}. Please choose from: 'value', 'index'." ) - def get_interpretation_neighbors(self, x): - choices = [value for _, value in self.choices] - choices.remove(x) - return choices, {} - - def get_interpretation_scores( - self, x, neighbors, scores: list[float | None], **kwargs - ) -> list: - """ - Returns: - Each value represents the interpretation score corresponding to each choice. - """ - choices = [value for _, value in self.choices] - scores.insert(choices.index(x), None) - return scores + def postprocess(self, y): + return y - def style( - self, - *, - item_container: bool | None = None, - container: bool | None = None, - **kwargs, - ): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if item_container is not None: - warn_deprecation("The `item_container` parameter is deprecated.") - if container is not None: - self.container = container - return self + def api_info(self) -> dict[str, Any]: + return { + "enum": [c[1] for c in self.choices], + "title": "Radio", + "type": "string", + } def as_example(self, input_data): return next((c[0] for c in self.choices if c[1] == input_data), None) diff --git a/gradio/components/scatter_plot.py b/gradio/components/scatter_plot.py index 2369570eb87e..98ed86ab7742 100644 --- a/gradio/components/scatter_plot.py +++ b/gradio/components/scatter_plot.py @@ -2,16 +2,14 @@ from __future__ import annotations -import warnings -from typing import Callable, Literal +from typing import Any, Callable, Literal import altair as alt import pandas as pd from gradio_client.documentation import document, set_documentation_group from pandas.api.types import is_numeric_dtype -from gradio.components.base import _Keywords -from gradio.components.plot import AltairPlot, Plot +from gradio.components.plot import AltairPlot, AltairPlotData, Plot set_documentation_group("component") @@ -28,6 +26,8 @@ class ScatterPlot(Plot): Guides: creating-a-dashboard-from-bigquery-data """ + data_model = AltairPlotData + def __init__( self, value: pd.DataFrame | Callable | None = None, @@ -97,8 +97,10 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, show_actions_button: bool = False, - **kwargs, ): """ Parameters: @@ -132,6 +134,8 @@ def __init__( visible: Whether the plot should be visible. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. show_actions_button: Whether to show the actions button on the top right corner of the plot. """ self.x = x @@ -169,167 +173,14 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, - **kwargs, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, ) def get_block_name(self) -> str: return "plot" - @staticmethod - def update( - value: pd.DataFrame | dict | Literal[_Keywords.NO_VALUE] = _Keywords.NO_VALUE, - x: str | None = None, - y: str | None = None, - color: str | None = None, - size: str | None = None, - shape: str | None = None, - title: str | None = None, - tooltip: list[str] | str | None = None, - x_title: str | None = None, - y_title: str | None = None, - x_label_angle: float | None = None, - y_label_angle: float | None = None, - color_legend_title: str | None = None, - size_legend_title: str | None = None, - shape_legend_title: str | None = None, - color_legend_position: Literal[ - "left", - "right", - "top", - "bottom", - "top-left", - "top-right", - "bottom-left", - "bottom-right", - "none", - ] - | None = None, - size_legend_position: Literal[ - "left", - "right", - "top", - "bottom", - "top-left", - "top-right", - "bottom-left", - "bottom-right", - "none", - ] - | None = None, - shape_legend_position: Literal[ - "left", - "right", - "top", - "bottom", - "top-left", - "top-right", - "bottom-left", - "bottom-right", - "none", - ] - | None = None, - height: int | None = None, - width: int | None = None, - x_lim: list[int | float] | None = None, - y_lim: list[int | float] | None = None, - interactive: bool | None = None, - caption: str | None = None, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - visible: bool | None = None, - ): - """Update an existing plot component. - - If updating any of the plot properties (color, size, etc) the value, x, and y parameters must be specified. - - Parameters: - value: The pandas dataframe containing the data to display in a scatter plot. - x: Column corresponding to the x axis. - y: Column corresponding to the y axis. - color: The column to determine the point color. If the column contains numeric data, gradio will interpolate the column data so that small values correspond to light colors and large values correspond to dark values. - size: The column used to determine the point size. Should contain numeric data so that gradio can map the data to the point size. - shape: The column used to determine the point shape. Should contain categorical data. Gradio will map each unique value to a different shape. - title: The title to display on top of the chart. - tooltip: The column (or list of columns) to display on the tooltip when a user hovers a point on the plot. - x_title: The title given to the x axis. By default, uses the value of the x parameter. - y_title: The title given to the y axis. By default, uses the value of the y parameter. - x_label_angle: The angle for the x axis labels rotation. Positive values are clockwise, and negative values are counter-clockwise. - y_label_angle: The angle for the y axis labels rotation. Positive values are clockwise, and negative values are counter-clockwise. - color_legend_title: The title given to the color legend. By default, uses the value of color parameter. - size_legend_title: The title given to the size legend. By default, uses the value of the size parameter. - shape_legend_title: The title given to the shape legend. By default, uses the value of the shape parameter. - color_legend_position: The position of the color legend. If the string value 'none' is passed, this legend is omitted. For other valid position values see: https://vega.github.io/vega/docs/legends/#orientation. - size_legend_position: The position of the size legend. If the string value 'none' is passed, this legend is omitted. For other valid position values see: https://vega.github.io/vega/docs/legends/#orientation. - shape_legend_position: The position of the shape legend. If the string value 'none' is passed, this legend is omitted. For other valid position values see: https://vega.github.io/vega/docs/legends/#orientation. - height: The height of the plot in pixels. - width: The width of the plot in pixels. - x_lim: A tuple or list containing the limits for the x-axis, specified as [x_min, x_max]. - y_lim: A tuple of list containing the limits for the y-axis, specified as [y_min, y_max]. - interactive: Whether users should be able to interact with the plot by panning or zooming with their mouse or trackpad. - caption: The (optional) caption to display below the plot. - label: The (optional) label to display in the top left corner of the plot. - show_label: Whether the label should be displayed. - visible: Whether the plot should be visible. - """ - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.ScatterPlot(...)` instead of `return gr.ScatterPlot.update(...)`." - ) - properties = [ - x, - y, - color, - size, - shape, - title, - tooltip, - x_title, - y_title, - x_label_angle, - y_label_angle, - color_legend_title, - size_legend_title, - shape_legend_title, - color_legend_position, - size_legend_position, - shape_legend_position, - height, - width, - x_lim, - y_lim, - interactive, - ] - if any(properties): - if not isinstance(value, pd.DataFrame): - raise ValueError( - "In order to update plot properties the value parameter " - "must be provided, and it must be a Dataframe. Please pass a value " - "parameter to gr.ScatterPlot.update." - ) - if x is None or y is None: - raise ValueError( - "In order to update plot properties, the x and y axis data " - "must be specified. Please pass valid values for x an y to " - "gr.ScatterPlot.update." - ) - chart = ScatterPlot.create_plot(value, *properties) - value = {"type": "altair", "plot": chart.to_json(), "chart": "scatter"} - - updated_config = { - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "value": value, - "caption": caption, - "__type__": "update", - } - return updated_config - @staticmethod def create_plot( value: pd.DataFrame, @@ -463,7 +314,9 @@ def create_plot( return chart - def postprocess(self, y: pd.DataFrame | dict | None) -> dict[str, str] | None: + def postprocess( + self, y: pd.DataFrame | dict | None + ) -> AltairPlotData | dict | None: # if None or update if y is None or isinstance(y, dict): return y @@ -495,4 +348,12 @@ def postprocess(self, y: pd.DataFrame | dict | None) -> dict[str, str] | None: y_lim=self.y_lim, ) - return {"type": "altair", "plot": chart.to_json(), "chart": "scatter"} + return AltairPlotData( + **{"type": "altair", "plot": chart.to_json(), "chart": "scatter"} + ) + + def example_inputs(self) -> Any: + return None + + def preprocess(self, x: Any) -> Any: + return x diff --git a/gradio/components/slider.py b/gradio/components/slider.py index 514fec178b6a..fe4bc8a97b88 100644 --- a/gradio/components/slider.py +++ b/gradio/components/slider.py @@ -4,31 +4,18 @@ import math import random -import warnings -from typing import Any, Callable, Literal +from typing import Any, Callable -import numpy as np from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import NumberSerializable -from gradio.components.base import FormComponent, IOComponent, _Keywords -from gradio.deprecation import warn_style_method_deprecation -from gradio.events import Changeable, Inputable, Releaseable -from gradio.interpretation import NeighborInterpretable +from gradio.components.base import FormComponent +from gradio.events import Events set_documentation_group("component") @document() -class Slider( - FormComponent, - Changeable, - Inputable, - Releaseable, - IOComponent, - NumberSerializable, - NeighborInterpretable, -): +class Slider(FormComponent): """ Creates a slider that ranges from `minimum` to `maximum` with a step size of `step`. Preprocessing: passes slider value as a {float} into the function. @@ -39,6 +26,8 @@ class Slider( Guides: create-your-own-friends-with-a-gan """ + EVENTS = [Events.change, Events.input, Events.release] + def __init__( self, minimum: float = 0, @@ -57,8 +46,10 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, randomize: bool = False, - **kwargs, ): """ Parameters: @@ -66,7 +57,7 @@ def __init__( maximum: maximum value for slider. value: default value. If callable, the function will be called whenever the app loads to set the initial value of the component. Ignored if randomized=True. step: increment between slider values. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. info: additional component description. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. @@ -77,6 +68,8 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. randomize: If True, the value of the slider when the app loads is taken uniformly at random from the range given by the minimum and maximum. """ self.minimum = minimum @@ -89,8 +82,7 @@ def __init__( self.step = step if randomize: value = self.get_random_value - IOComponent.__init__( - self, + super().__init__( label=label, info=info, every=every, @@ -102,25 +94,20 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - NeighborInterpretable.__init__(self) - def api_info(self) -> dict[str, dict | bool]: + def api_info(self) -> dict[str, Any]: return { - "info": { - "type": "number", - "description": f"numeric value between {self.minimum} and {self.maximum}", - }, - "serialized_info": False, + "type": "number", + "description": f"numeric value between {self.minimum} and {self.maximum}", } - def example_inputs(self) -> dict[str, Any]: - return { - "raw": self.minimum, - "serialized": self.minimum, - } + def example_inputs(self) -> Any: + return self.minimum def get_random_value(self): n_steps = int((self.maximum - self.minimum) / self.step) @@ -132,40 +119,6 @@ def get_random_value(self): value = round(value, n_decimals) return value - @staticmethod - def update( - value: float | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - minimum: float | None = None, - maximum: float | None = None, - step: float | None = None, - label: str | None = None, - info: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - interactive: bool | None = None, - visible: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Slider(...)` instead of `return gr.Slider.update(...)`." - ) - return { - "minimum": minimum, - "maximum": maximum, - "step": step, - "label": label, - "info": info, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "interactive": interactive, - "visible": visible, - "value": value, - "__type__": "update", - } - def postprocess(self, y: float | None) -> float | None: """ Any postprocessing needed to be performed on function output. @@ -176,30 +129,5 @@ def postprocess(self, y: float | None) -> float | None: """ return self.minimum if y is None else y - def set_interpret_parameters(self, steps: int = 8) -> Slider: - """ - Calculates interpretation scores of numeric values ranging between the minimum and maximum values of the slider. - Parameters: - steps: Number of neighboring values to measure between the minimum and maximum values of the slider range. - """ - self.interpretation_steps = steps - return self - - def get_interpretation_neighbors(self, x) -> tuple[object, dict]: - return ( - np.linspace(self.minimum, self.maximum, self.interpretation_steps).tolist(), - {}, - ) - - def style( - self, - *, - container: bool | None = None, - ): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if container is not None: - self.container = container - return self + def preprocess(self, x: Any) -> Any: + return x diff --git a/gradio/components/state.py b/gradio/components/state.py index c027875b090f..dc3334c7608e 100644 --- a/gradio/components/state.py +++ b/gradio/components/state.py @@ -6,15 +6,15 @@ from typing import Any from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import SimpleSerializable -from gradio.components.base import IOComponent +from gradio.components.base import Component set_documentation_group("component") @document() -class State(IOComponent, SimpleSerializable): +class State(Component): + EVENTS = [] """ Special hidden component that stores session state across runs of the demo by the same user. The value of the State variable is cleared when the user refreshes the page. @@ -30,11 +30,12 @@ class State(IOComponent, SimpleSerializable): def __init__( self, value: Any = None, - **kwargs, + render: bool = True, ): """ Parameters: value: the initial value (of arbitrary type) of the state. The provided argument is deepcopied. If a callable is provided, the function will be called whenever the app loads to set the initial value of the state. + render: has no effect, but is included for consistency with other components. """ self.stateful = True try: @@ -43,14 +44,20 @@ def __init__( raise TypeError( f"The initial value of `gr.State` must be able to be deepcopied. The initial value of type {type(value)} cannot be deepcopied." ) from err - IOComponent.__init__(self, value=self.value, **kwargs) + super().__init__(value=self.value) + def preprocess(self, x: Any) -> Any: + return x -class Variable(State): - """Variable was renamed to State. This class is kept for backwards compatibility.""" + def postprocess(self, y): + return y - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def api_info(self) -> dict[str, Any]: + return {"type": {}, "description": "any valid json"} - def get_block_name(self): - return "state" + def example_inputs(self) -> Any: + return None + + @property + def skip_api(self): + return True diff --git a/gradio/components/status_tracker.py b/gradio/components/status_tracker.py deleted file mode 100644 index a9abec2969d9..000000000000 --- a/gradio/components/status_tracker.py +++ /dev/null @@ -1,13 +0,0 @@ -"""gr.StatusTracker() component.""" -from gradio_client.serializing import SimpleSerializable - -from gradio.components.base import Component -from gradio.deprecation import warn_deprecation - - -class StatusTracker(Component, SimpleSerializable): - def __init__( - self, - **kwargs, - ): - warn_deprecation("The StatusTracker component is deprecated.") diff --git a/gradio/components/textbox.py b/gradio/components/textbox.py index 7b8bed6e19a4..23ec6ae3460f 100644 --- a/gradio/components/textbox.py +++ b/gradio/components/textbox.py @@ -2,44 +2,20 @@ from __future__ import annotations -import warnings -from typing import Callable, Literal +from typing import Any, Callable, Literal -import numpy as np from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import StringSerializable from gradio.components.base import ( FormComponent, - IOComponent, - _Keywords, ) -from gradio.deprecation import warn_style_method_deprecation -from gradio.events import ( - Changeable, - EventListenerMethod, - Focusable, - Inputable, - Selectable, - Submittable, -) -from gradio.interpretation import TokenInterpretable +from gradio.events import Events set_documentation_group("component") @document() -class Textbox( - FormComponent, - Changeable, - Inputable, - Selectable, - Submittable, - Focusable, - IOComponent, - StringSerializable, - TokenInterpretable, -): +class Textbox(FormComponent): """ Creates a textarea for user to enter string input or display string output. Preprocessing: passes textarea value as a {str} into the function. @@ -50,6 +26,15 @@ class Textbox( Guides: creating-a-chatbot, real-time-speech-recognition """ + EVENTS = [ + Events.change, + Events.input, + Events.select, + Events.submit, + Events.focus, + Events.blur, + ] + def __init__( self, value: str | Callable | None = "", @@ -70,11 +55,13 @@ def __init__( autofocus: bool = False, autoscroll: bool = True, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, type: Literal["text", "password", "email"] = "text", text_align: Literal["left", "right"] | None = None, rtl: bool = False, show_copy_button: bool = False, - **kwargs, ): """ Parameters: @@ -82,7 +69,7 @@ def __init__( lines: minimum number of line rows to provide in textarea. max_lines: maximum number of line rows to provide in textarea. placeholder: placeholder hint to provide behind textarea. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. info: additional component description. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. @@ -94,6 +81,8 @@ def __init__( autofocus: If True, will focus on the textbox when the page loads. Use this carefully, as it can cause usability issues for sighted and non-sighted users. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. type: The type of textbox. One of: 'text', 'password', 'email', Default is 'text'. text_align: How to align the text in the textbox, can be: "left", "right", or None (default). If None, the alignment is left if `rtl` is False, or right if `rtl` is True. Can only be changed if `type` is "text". rtl: If True and `type` is "text", sets the direction of the text to right-to-left (cursor appears on the left of the text). Default is False, which renders cursor on the right. @@ -111,15 +100,8 @@ def __init__( self.placeholder = placeholder self.show_copy_button = show_copy_button self.autofocus = autofocus - self.select: EventListenerMethod self.autoscroll = autoscroll - """ - Event listener for when the user selects text in the Textbox. - Uses event data gradio.SelectData to carry `value` referring to selected substring, and `index` tuple referring to selected range endpoints. - See EventData documentation on how to use this event data. - """ - IOComponent.__init__( - self, + super().__init__( label=label, info=info, every=every, @@ -131,60 +113,15 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - TokenInterpretable.__init__(self) self.type = type self.rtl = rtl self.text_align = text_align - @staticmethod - def update( - value: str | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - lines: int | None = None, - max_lines: int | None = None, - placeholder: str | None = None, - label: str | None = None, - info: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - visible: bool | None = None, - interactive: bool | None = None, - type: Literal["text", "password", "email"] | None = None, - text_align: Literal["left", "right"] | None = None, - rtl: bool | None = None, - show_copy_button: bool | None = None, - autofocus: bool | None = None, - autoscroll: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Textbox(...)` instead of `return gr.Textbox.update(...)`." - ) - return { - "lines": lines, - "max_lines": max_lines, - "placeholder": placeholder, - "label": label, - "info": info, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "visible": visible, - "value": value, - "type": type, - "interactive": interactive, - "show_copy_button": show_copy_button, - "autofocus": autofocus, - "text_align": text_align, - "rtl": rtl, - "autoscroll": autoscroll, - "__type__": "update", - } - def preprocess(self, x: str | None) -> str | None: """ Preprocesses input (converts it to a string) before passing it to the function. @@ -205,74 +142,8 @@ def postprocess(self, y: str | None) -> str | None: """ return None if y is None else str(y) - def set_interpret_parameters( - self, separator: str = " ", replacement: str | None = None - ): - """ - Calculates interpretation score of characters in input by splitting input into tokens, then using a "leave one out" method to calculate the score of each token by removing each token and measuring the delta of the output value. - Parameters: - separator: Separator to use to split input into tokens. - replacement: In the "leave one out" step, the text that the token should be replaced with. If None, the token is removed altogether. - """ - self.interpretation_separator = separator - self.interpretation_replacement = replacement - return self - - def tokenize(self, x: str) -> tuple[list[str], list[str], None]: - """ - Tokenizes an input string by dividing into "words" delimited by self.interpretation_separator - """ - tokens = x.split(self.interpretation_separator) - leave_one_out_strings = [] - for index in range(len(tokens)): - leave_one_out_set = list(tokens) - if self.interpretation_replacement is None: - leave_one_out_set.pop(index) - else: - leave_one_out_set[index] = self.interpretation_replacement - leave_one_out_strings.append( - self.interpretation_separator.join(leave_one_out_set) - ) - return tokens, leave_one_out_strings, None + def api_info(self) -> dict[str, Any]: + return {"type": "string"} - def get_masked_inputs( - self, tokens: list[str], binary_mask_matrix: list[list[int]] - ) -> list[str]: - """ - Constructs partially-masked sentences for SHAP interpretation - """ - masked_inputs = [] - for binary_mask_vector in binary_mask_matrix: - masked_input = np.array(tokens)[np.array(binary_mask_vector, dtype=bool)] - masked_inputs.append(self.interpretation_separator.join(masked_input)) - return masked_inputs - - def get_interpretation_scores( - self, x, neighbors, scores: list[float], tokens: list[str], masks=None, **kwargs - ) -> list[tuple[str, float]]: - """ - Returns: - Each tuple set represents a set of characters and their corresponding interpretation score. - """ - result = [] - for token, score in zip(tokens, scores): - result.append((token, score)) - result.append((self.interpretation_separator, 0)) - return result - - def style( - self, - *, - show_copy_button: bool | None = None, - container: bool | None = None, - **kwargs, - ): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if show_copy_button is not None: - self.show_copy_button = show_copy_button - if container is not None: - self.container = container - return self + def example_inputs(self) -> Any: + return "Hello!!" diff --git a/gradio/components/timeseries.py b/gradio/components/timeseries.py deleted file mode 100644 index 7cdd82ecb092..000000000000 --- a/gradio/components/timeseries.py +++ /dev/null @@ -1,152 +0,0 @@ -"""gr.Timeseries() component.""" - -from __future__ import annotations - -import warnings -from pathlib import Path -from typing import Any, Callable, Literal - -import pandas as pd -from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import JSONSerializable - -from gradio.components.base import IOComponent, _Keywords -from gradio.events import Changeable - -set_documentation_group("component") - - -@document() -class Timeseries(Changeable, IOComponent, JSONSerializable): - """ - Creates a component that can be used to upload/preview timeseries csv files or display a dataframe consisting of a time series graphically. - Preprocessing: passes the uploaded timeseries data as a {pandas.DataFrame} into the function - Postprocessing: expects a {pandas.DataFrame} or {str} path to a csv to be returned, which is then displayed as a timeseries graph - Examples-format: a {str} filepath of csv data with time series data. - Demos: fraud_detector - """ - - def __init__( - self, - value: str | Callable | None = None, - *, - x: str | None = None, - y: str | list[str] | None = None, - colors: list[str] | None = None, - label: str | None = None, - every: float | None = None, - show_label: bool | None = None, - container: bool = True, - scale: int | None = None, - min_width: int = 160, - interactive: bool | None = None, - visible: bool = True, - elem_id: str | None = None, - elem_classes: list[str] | str | None = None, - **kwargs, - ): - """ - Parameters: - value: File path for the timeseries csv file. If callable, the function will be called whenever the app loads to set the initial value of the component. - x: Column name of x (time) series. None if csv has no headers, in which case first column is x series. - y: Column name of y series, or list of column names if multiple series. None if csv has no headers, in which case every column after first is a y series. - label: component name in interface. - every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. - colors: an ordered list of colors to use for each line plot - show_label: if True, will display label. - container: If True, will place the component in a container - providing some extra padding around the border. - scale: relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer. - min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first. - interactive: if True, will allow users to upload a timeseries csv; if False, can only be used to display timeseries data. If not provided, this is inferred based on whether the component is used as an input or output. - visible: If False, component will be hidden. - elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. - elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. - """ - self.x = x - if isinstance(y, str): - y = [y] - self.y = y - self.colors = colors - IOComponent.__init__( - self, - label=label, - every=every, - show_label=show_label, - container=container, - scale=scale, - min_width=min_width, - interactive=interactive, - visible=visible, - elem_id=elem_id, - elem_classes=elem_classes, - value=value, - **kwargs, - ) - - @staticmethod - def update( - value: Any | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE, - colors: list[str] | None = None, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - interactive: bool | None = None, - visible: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Timeseries(...)` instead of `return gr.Timeseries.update(...)`." - ) - return { - "colors": colors, - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "interactive": interactive, - "visible": visible, - "value": value, - "__type__": "update", - } - - def preprocess(self, x: dict | None) -> pd.DataFrame | None: - """ - Parameters: - x: Dict with keys 'data': 2D array of str, numeric, or bool data, 'headers': list of strings for header names, 'range': optional two element list designating start of end of subrange. - Returns: - Dataframe of timeseries data - """ - if x is None: - return x - elif x.get("is_file"): - dataframe = pd.read_csv(x["name"]) - else: - dataframe = pd.DataFrame(data=x["data"], columns=x["headers"]) - if x.get("range") is not None: - dataframe = dataframe.loc[dataframe[self.x or 0] >= x["range"][0]] - dataframe = dataframe.loc[dataframe[self.x or 0] <= x["range"][1]] - return dataframe - - def postprocess(self, y: str | pd.DataFrame | None) -> dict | None: - """ - Parameters: - y: csv or dataframe with timeseries data - Returns: - JSON object with key 'headers' for list of header names, 'data' for 2D array of string or numeric data - """ - if y is None: - return None - if isinstance(y, str): - dataframe = pd.read_csv(y) - return { - "headers": dataframe.columns.values.tolist(), - "data": dataframe.values.tolist(), - } - if isinstance(y, pd.DataFrame): - return {"headers": y.columns.values.tolist(), "data": y.values.tolist()} - raise ValueError("Cannot process value as Timeseries data") - - def as_example(self, input_data: str | None) -> str: - return Path(input_data).name if input_data else "" diff --git a/gradio/components/upload_button.py b/gradio/components/upload_button.py index 268c6bfc0e91..f9368ab28403 100644 --- a/gradio/components/upload_button.py +++ b/gradio/components/upload_button.py @@ -4,22 +4,25 @@ import tempfile import warnings -from typing import Any, Callable, Literal +from typing import Any, Callable, List, Literal from gradio_client import utils as client_utils from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import FileSerializable -from gradio import utils -from gradio.components.base import IOComponent, _Keywords -from gradio.deprecation import warn_deprecation, warn_style_method_deprecation -from gradio.events import Clickable, Uploadable +from gradio.components.base import Component +from gradio.data_classes import FileData, GradioRootModel +from gradio.events import Events +from gradio.utils import NamedString set_documentation_group("component") +class ListFiles(GradioRootModel): + root: List[FileData] + + @document() -class UploadButton(Clickable, Uploadable, IOComponent, FileSerializable): +class UploadButton(Component): """ Used to create an upload button, when clicked allows a user to upload files that satisfy the specified file type or generic files (if file_type not set). Preprocessing: passes the uploaded file as a {file-object} or {List[file-object]} depending on `file_count` (or a {bytes}/{List[bytes]} depending on `type`) @@ -28,11 +31,14 @@ class UploadButton(Clickable, Uploadable, IOComponent, FileSerializable): Demos: upload_button """ + EVENTS = [Events.click, Events.upload] + def __init__( self, label: str = "Upload a File", value: str | list[str] | Callable | None = None, *, + every: float | None = None, variant: Literal["primary", "secondary", "stop"] = "secondary", visible: bool = True, size: Literal["sm", "lg"] | None = None, @@ -41,15 +47,18 @@ def __init__( interactive: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, - type: Literal["file", "bytes"] = "file", + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, + type: Literal["filepath", "bytes"] = "filepath", file_count: Literal["single", "multiple", "directory"] = "single", file_types: list[str] | None = None, - **kwargs, ): """ Parameters: label: Text to display on the button. Defaults to "Upload a File". value: File or list of files to upload by default. + every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. variant: 'primary' for main call-to-action, 'secondary' for a more subdued style, 'stop' for a stop button. visible: If False, component will be hidden. size: Size of the button. Can be "sm" or "lg". @@ -58,10 +67,20 @@ def __init__( interactive: If False, the UploadButton will be in a disabled state. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. type: Type of value to be returned by component. "file" returns a temporary file object with the same base name as the uploaded file, whose full path can be retrieved by file_obj.name, "binary" returns an bytes object. file_count: if single, allows user to upload one file. If "multiple", user uploads multiple files. If "directory", user uploads all files in selected directory. Return type will be list for each file in case of "multiple" or "directory". file_types: List of type of files to be uploaded. "file" allows any file to be uploaded, "image" allows only image files to be uploaded, "audio" allows only audio files to be uploaded, "video" allows only video files to be uploaded, "text" allows only text files to be uploaded. """ + valid_types = [ + "filepath", + "binary", + ] + if type not in valid_types: + raise ValueError( + f"Invalid value for parameter `type`: {type}. Please choose from one of: {valid_types}" + ) self.type = type self.file_count = file_count if file_count == "directory" and file_types is not None: @@ -76,56 +95,60 @@ def __init__( self.file_types = file_types self.label = label self.variant = variant - IOComponent.__init__( - self, + super().__init__( label=label, + every=every, visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, scale=scale, min_width=min_width, interactive=interactive, - **kwargs, ) - @staticmethod - def update( - value: str - | list[str] - | Literal[_Keywords.NO_VALUE] - | None = _Keywords.NO_VALUE, - label: str | None = None, - size: Literal["sm", "lg"] | None = None, - variant: Literal["primary", "secondary", "stop"] | None = None, - interactive: bool | None = None, - visible: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.UploadButton(...)` instead of `return gr.UploadButton.update(...)`." + def api_info(self) -> dict[str, list[str]]: + if self.file_count == "single": + return FileData.model_json_schema() + else: + return ListFiles.model_json_schema() + + def example_inputs(self) -> Any: + if self.file_count == "single": + return "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf" + else: + return [ + "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf" + ] + + def _process_single_file(self, f: dict[str, Any]) -> bytes | str: + file_name, data, is_file = ( + f["name"], + f["data"], + f.get("is_file", False), ) - return { - "variant": variant, - "interactive": interactive, - "size": size, - "visible": visible, - "value": value, - "scale": scale, - "min_width": min_width, - "label": label, - "__type__": "update", - } + if self.type == "filepath": + file = tempfile.NamedTemporaryFile(delete=False, dir=self.GRADIO_CACHE) + file.name = file_name + return NamedString(file.name) + elif self.type == "binary": + if is_file: + with open(file_name, "rb") as file_data: + return file_data.read() + return client_utils.decode_base64_to_binary(data)[0] + else: + raise ValueError( + "Unknown type: " + + str(type) + + ". Please choose from: 'filepath', 'binary'." + ) def preprocess( self, x: list[dict[str, Any]] | None - ) -> ( - bytes - | tempfile._TemporaryFileWrapper - | list[bytes | tempfile._TemporaryFileWrapper] - | None - ): + ) -> bytes | str | list[bytes | str] | None: """ Parameters: x: List of JSON objects with filename as 'name' property and base64 data as 'data' property @@ -135,66 +158,20 @@ def preprocess( if x is None: return None - def process_single_file(f) -> bytes | tempfile._TemporaryFileWrapper: - file_name, data, is_file = ( - f["name"], - f["data"], - f.get("is_file", False), - ) - if self.type == "file": - if is_file: - path = self.make_temp_copy_if_needed(file_name) - else: - data, _ = client_utils.decode_base64_to_binary(data) - path = self.file_bytes_to_file(data, file_name=file_name) - path = str(utils.abspath(path)) - self.temp_files.add(path) - file = tempfile.NamedTemporaryFile( - delete=False, dir=self.DEFAULT_TEMP_DIR - ) - file.name = path - file.orig_name = file_name # type: ignore - return file - elif self.type == "bytes": - if is_file: - with open(file_name, "rb") as file_data: - return file_data.read() - return client_utils.decode_base64_to_binary(data)[0] - else: - raise ValueError( - "Unknown type: " - + str(self.type) - + ". Please choose from: 'file', 'bytes'." - ) - if self.file_count == "single": if isinstance(x, list): - return process_single_file(x[0]) + return self._process_single_file(x[0]) else: - return process_single_file(x) + return self._process_single_file(x) else: if isinstance(x, list): - return [process_single_file(f) for f in x] + return [self._process_single_file(f) for f in x] else: - return process_single_file(x) + return [self._process_single_file(x)] - def style( - self, - *, - full_width: bool | None = None, - size: Literal["sm", "lg"] | None = None, - **kwargs, - ): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if full_width is not None: - warn_deprecation( - "Use `scale` in place of full_width in the constructor. " - "scale=1 will make the button expand, whereas 0 will not." - ) - self.scale = 1 if full_width else None - if size is not None: - self.size = size - return self + def postprocess(self, y): + return super().postprocess(y) + + @property + def skip_api(self): + return False diff --git a/gradio/components/video.py b/gradio/components/video.py index 5b1d3e5a7473..128349825d0b 100644 --- a/gradio/components/video.py +++ b/gradio/components/video.py @@ -5,17 +5,15 @@ import tempfile import warnings from pathlib import Path -from typing import Callable, Literal +from typing import Any, Callable, Literal, Optional from gradio_client import utils as client_utils -from gradio_client.data_classes import FileData from gradio_client.documentation import document, set_documentation_group -from gradio_client.serializing import VideoSerializable from gradio import processing_utils, utils, wasm_utils -from gradio.components.base import IOComponent, _Keywords -from gradio.deprecation import warn_style_method_deprecation -from gradio.events import Changeable, Clearable, Playable, Recordable, Uploadable +from gradio.components.base import Component +from gradio.data_classes import FileData, GradioModel +from gradio.events import Events if not wasm_utils.IS_WASM: # TODO: Support ffmpeg on Wasm @@ -24,16 +22,13 @@ set_documentation_group("component") +class VideoData(GradioModel): + video: FileData + subtitles: Optional[FileData] = None + + @document() -class Video( - Changeable, - Clearable, - Playable, - Recordable, - Uploadable, - IOComponent, - VideoSerializable, -): +class Video(Component): """ Creates a video component that can be used to upload/record videos (as an input) or display videos (as an output). For the video to be playable in the browser it must have a compatible container and codec combination. Allowed @@ -46,6 +41,19 @@ class Video( Demos: video_identity, video_subtitle """ + data_model = VideoData + input_data_model = FileData + EVENTS = [ + Events.change, + Events.clear, + Events.start_recording, + Events.stop_recording, + Events.stop, + Events.play, + Events.pause, + Events.end, + ] + def __init__( self, value: str @@ -68,11 +76,13 @@ def __init__( visible: bool = True, elem_id: str | None = None, elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, mirror_webcam: bool = True, include_audio: bool | None = None, autoplay: bool = False, show_share_button: bool | None = None, - **kwargs, ): """ Parameters: @@ -81,7 +91,7 @@ def __init__( source: Source of video. "upload" creates a box where user can drop an video file, "webcam" allows user to record a video from their webcam. height: Height of the displayed video in pixels. width: Width of the displayed video in pixels. - label: component name in interface. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. show_label: if True, will display label. container: If True, will place the component in a container - providing some extra padding around the border. @@ -91,6 +101,8 @@ def __init__( visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. mirror_webcam: If True webcam will be mirrored. Default is True. include_audio: Whether the component should record/retain the audio track for a video. By default, audio is excluded for webcam videos and included for uploaded videos. autoplay: Whether to automatically play the video when the component is used as an output. Note: browsers will not autoplay video files if the user has not interacted with the page yet. @@ -115,8 +127,7 @@ def __init__( if show_share_button is None else show_share_button ) - IOComponent.__init__( - self, + super().__init__( label=label, every=every, show_label=show_label, @@ -127,52 +138,13 @@ def __init__( visible=visible, elem_id=elem_id, elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, value=value, - **kwargs, ) - @staticmethod - def update( - value: str - | tuple[str, str | None] - | Literal[_Keywords.NO_VALUE] - | None = _Keywords.NO_VALUE, - source: Literal["upload", "webcam"] | None = None, - height: int | None = None, - width: int | None = None, - label: str | None = None, - show_label: bool | None = None, - container: bool | None = None, - scale: int | None = None, - min_width: int | None = None, - interactive: bool | None = None, - visible: bool | None = None, - autoplay: bool | None = None, - show_share_button: bool | None = None, - ): - warnings.warn( - "Using the update method is deprecated. Simply return a new object instead, e.g. `return gr.Video(...)` instead of `return gr.Video.update(...)`." - ) - return { - "source": source, - "height": height, - "width": width, - "label": label, - "show_label": show_label, - "container": container, - "scale": scale, - "min_width": min_width, - "interactive": interactive, - "visible": visible, - "value": value, - "autoplay": autoplay, - "show_share_button": show_share_button, - "__type__": "update", - } - - def preprocess( - self, x: tuple[FileData, FileData | None] | FileData | None - ) -> str | None: + def preprocess(self, x: dict | VideoData) -> str | None: """ Parameters: x: A tuple of (video file data, subtitle file data) or just video file data. @@ -181,30 +153,9 @@ def preprocess( """ if x is None: return None - elif isinstance(x, dict): - video = x - else: - video = x[0] - - file_name, file_data, is_file = ( - video.get("name"), - video["data"], - video.get("is_file", False), - ) - - if is_file: - if file_name is None: - raise ValueError("Received file data without a file name.") - if client_utils.is_http_url_like(file_name): - fn = self.download_temp_copy_if_needed - else: - fn = self.make_temp_copy_if_needed - file_name = Path(fn(file_name)) - else: - if file_data is None: - raise ValueError("Received empty file data.") - file_name = Path(self.base64_to_temp_file_if_needed(file_data, file_name)) - + data: VideoData = VideoData(**x) if isinstance(x, dict) else x + assert data.video.name + file_name = Path(data.video.name) uploaded_format = file_name.suffix.replace(".", "") needs_formatting = self.format is not None and uploaded_format != self.format flip = self.source == "webcam" and self.mirror_webcam @@ -248,7 +199,7 @@ def preprocess( def postprocess( self, y: str | Path | tuple[str | Path, str | Path | None] | None - ) -> tuple[FileData | None, FileData | None] | None: + ) -> VideoData | None: """ Processes a video to ensure that it is in the correct format before returning it to the front end. Parameters: @@ -289,8 +240,8 @@ def postprocess( ) else: raise Exception(f"Cannot process type as video: {type(y)}") - - return processed_files + assert processed_files[0] + return VideoData(video=processed_files[0], subtitles=processed_files[1]) def _format_video(self, video: str | Path | None) -> FileData | None: """ @@ -317,11 +268,13 @@ def _format_video(self, video: str | Path | None) -> FileData | None: # For cases where the video is a URL and does not need to be converted to another format, we can just return the URL if is_url and not (conversion_needed): - return {"name": video, "data": None, "is_file": True} + return FileData(name=video, is_file=True) # For cases where the video needs to be converted to another format if is_url: - video = self.download_temp_copy_if_needed(video) + video = processing_utils.save_url_to_cache( + video, cache_dir=self.GRADIO_CACHE + ) if ( processing_utils.ffmpeg_installed() and not processing_utils.video_is_playable(video) @@ -347,14 +300,7 @@ def _format_video(self, video: str | Path | None) -> FileData | None: ff.run() video = output_file_name - video = self.make_temp_copy_if_needed(video) - - return { - "name": video, - "data": None, - "is_file": True, - "orig_name": Path(video).name, - } + return FileData(name=video, data=None, is_file=True, orig_name=Path(video).name) def _format_subtitle(self, subtitle: str | Path | None) -> FileData | None: """ @@ -394,22 +340,14 @@ def srt_to_vtt(srt_file_path, vtt_file_path): # HTML5 only support vtt format if Path(subtitle).suffix == ".srt": temp_file = tempfile.NamedTemporaryFile( - delete=False, suffix=".vtt", dir=self.DEFAULT_TEMP_DIR + delete=False, suffix=".vtt", dir=self.GRADIO_CACHE ) srt_to_vtt(subtitle, temp_file.name) subtitle = temp_file.name subtitle_data = client_utils.encode_url_or_file_to_base64(subtitle) - return {"name": None, "data": subtitle_data, "is_file": False} + return FileData(name=None, data=subtitle_data, is_file=False) - def style(self, *, height: int | None = None, width: int | None = None, **kwargs): - """ - This method is deprecated. Please set these arguments in the constructor instead. - """ - warn_style_method_deprecation() - if height is not None: - self.height = height - if width is not None: - self.width = width - return self + def example_inputs(self) -> Any: + return "https://github.com/gradio-app/gradio/raw/main/demo/video_component/files/world.mp4" diff --git a/gradio/data_classes.py b/gradio/data_classes.py index 514bed2accca..33a342e67828 100644 --- a/gradio/data_classes.py +++ b/gradio/data_classes.py @@ -1,9 +1,16 @@ """Pydantic data models and other dataclasses. This is the only file that uses Optional[] typing syntax instead of | None syntax to work with pydantic""" +from __future__ import annotations + +import pathlib +import secrets +import shutil +from abc import ABC, abstractmethod from enum import Enum, auto -from typing import Any, Dict, List, Optional, Union +from typing import Any, List, Optional, Union -from pydantic import BaseModel +from gradio_client.utils import traverse +from pydantic import BaseModel, RootModel, ValidationError from typing_extensions import Literal @@ -17,7 +24,7 @@ class PredictBody(BaseModel): bool ] = False # Whether the data is a batch of samples (i.e. called from the queue if batch=True) or a single sample (i.e. called from the UI) request: Optional[ - Union[Dict, List[Dict]] + Union[dict, List[dict]] ] = None # dictionary of request headers, query parameters, url, etc. (used to to pass in request for queuing) @@ -67,3 +74,90 @@ class LogMessage(BaseModel): msg: str = "log" log: str level: Literal["info", "warning"] + + +class GradioBaseModel(ABC): + def copy_to_dir(self, dir: str | pathlib.Path) -> GradioDataModel: + assert isinstance(self, (BaseModel, RootModel)) + if isinstance(dir, str): + dir = pathlib.Path(dir) + + # TODO: Making sure path is unique should be done in caller + def unique_copy(obj: dict): + data = FileData(**obj) + return data._copy_to_dir( + str(pathlib.Path(dir / secrets.token_hex(10))) + ).model_dump() + + return self.__class__.from_json( + x=traverse( + self.model_dump(), + unique_copy, + FileData.is_file_data, + ) + ) + + @classmethod + @abstractmethod + def from_json(cls, x) -> GradioDataModel: + pass + + +class GradioModel(GradioBaseModel, BaseModel): + @classmethod + def from_json(cls, x) -> GradioModel: + return cls(**x) + + +class GradioRootModel(GradioBaseModel, RootModel): + @classmethod + def from_json(cls, x) -> GradioRootModel: + return cls(root=x) + + +GradioDataModel = Union[GradioModel, GradioRootModel] + + +class FileData(GradioModel): + name: Optional[str] = None + data: Optional[str] = None # base64 encoded data + size: Optional[int] = None # size in bytes + is_file: Optional[bool] = None + orig_name: Optional[str] = None # original filename + mime_type: Optional[str] = None + + @property + def is_none(self): + return all( + f is None + for f in [ + self.name, + self.data, + self.size, + self.is_file, + self.orig_name, + self.mime_type, + ] + ) + + @classmethod + def from_path(cls, path: str) -> FileData: + return cls(name=path, is_file=True) + + def _copy_to_dir(self, dir: str) -> FileData: + pathlib.Path(dir).mkdir(exist_ok=True) + new_obj = dict(self) + if self.is_file: + assert self.name + new_name = shutil.copy(self.name, dir) + new_obj["name"] = new_name + return self.__class__(**new_obj) + + @classmethod + def is_file_data(cls, obj: Any): + if isinstance(obj, dict): + try: + return not FileData(**obj).is_none + except (TypeError, ValidationError): + return False + return False diff --git a/gradio/deprecation.py b/gradio/deprecation.py deleted file mode 100644 index d14f88ffcda4..000000000000 --- a/gradio/deprecation.py +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -import warnings - -from gradio import utils - - -class GradioDeprecationWarning(UserWarning): - # This does not subclass DeprecationWarning - # because we want to show the warning by default. - pass - - -class GradioUnusedKwargWarning(UserWarning): - pass - - -def simple_deprecated_notice(term: str) -> str: - return f"`{term}` parameter is deprecated, and it has no effect" - - -def use_in_launch(term: str) -> str: - return f"`{term}` is deprecated in `Interface()`, please use it within `launch()` instead." - - -DEPRECATION_MESSAGE = { - "optional": simple_deprecated_notice("optional"), - "keep_filename": simple_deprecated_notice("keep_filename"), - "numeric": simple_deprecated_notice("numeric"), - "verbose": simple_deprecated_notice("verbose"), - "allow_screenshot": simple_deprecated_notice("allow_screenshot"), - "layout": simple_deprecated_notice("layout"), - "show_input": simple_deprecated_notice("show_input"), - "show_output": simple_deprecated_notice("show_output"), - "capture_session": simple_deprecated_notice("capture_session"), - "api_mode": simple_deprecated_notice("api_mode"), - "show_tips": use_in_launch("show_tips"), - "encrypt": simple_deprecated_notice("encrypt"), - "enable_queue": use_in_launch("enable_queue"), - "server_name": use_in_launch("server_name"), - "server_port": use_in_launch("server_port"), - "width": use_in_launch("width"), - "height": use_in_launch("height"), - "plot": "The 'plot' parameter has been deprecated. Use the new Plot component instead", -} - - -def check_deprecated_parameters( - cls: str, *, stacklevel: int | None = None, kwargs -) -> None: - if stacklevel is None: - stacklevel = utils.find_user_stack_level() - - for key, value in DEPRECATION_MESSAGE.items(): - if key in kwargs: - if key == "plot" and cls != "Image": - continue - kwargs.pop(key) - warnings.warn(value, GradioDeprecationWarning, stacklevel=stacklevel) - - if kwargs: - warnings.warn( - f"You have unused kwarg parameters in {cls}, please remove them: {kwargs}", - GradioUnusedKwargWarning, - stacklevel=stacklevel, - ) - - -def warn_deprecation(text: str) -> None: - warnings.warn( - text, - GradioDeprecationWarning, - stacklevel=utils.find_user_stack_level(), - ) - - -def warn_style_method_deprecation() -> None: - warn_deprecation( - "The `style` method is deprecated. Please set these arguments in the constructor instead." - ) diff --git a/gradio/events.py b/gradio/events.py index 2fb5fdcf56d4..c2094fe85602 100644 --- a/gradio/events.py +++ b/gradio/events.py @@ -3,22 +3,19 @@ from __future__ import annotations -from functools import wraps +import dataclasses +import string +from functools import partial, wraps from typing import TYPE_CHECKING, Any, Callable, Literal, Sequence -from gradio_client.documentation import document, set_documentation_group +from gradio_client.documentation import document + +if TYPE_CHECKING: + from gradio.blocks import Block, Component -from gradio.blocks import Block from gradio.context import Context -from gradio.deprecation import warn_deprecation -from gradio.helpers import EventData from gradio.utils import get_cancel_function -if TYPE_CHECKING: # Only import for type checking (is False at runtime). - from gradio.components import Component - -set_documentation_group("events") - def set_cancel_events( triggers: Sequence[EventListenerMethod], @@ -41,35 +38,35 @@ def set_cancel_events( outputs=None, queue=False, preprocess=False, + api_name=False, cancels=fn_indices_to_cancel, ) -class EventListener(Block): - def __init__(self: Any): - for event_listener_class in EventListener.__subclasses__(): - if isinstance(self, event_listener_class): - event_listener_class.__init__(self) - - class Dependency(dict): - def __init__(self, key_vals, dep_index, fn): + def __init__(self, trigger, key_vals, dep_index, fn): super().__init__(key_vals) self.fn = fn - self.then = EventListenerMethod( - None, - "then", - trigger_after=dep_index, - trigger_only_on_success=False, + self.then = partial( + EventListener( + "then", + trigger_after=dep_index, + trigger_only_on_success=False, + has_trigger=False, + ).listener, + trigger, ) """ Triggered after directly preceding event is completed, regardless of success or failure. """ - self.success = EventListenerMethod( - None, - "success", - trigger_after=dep_index, - trigger_only_on_success=True, + self.success = partial( + EventListener( + "success", + trigger_after=dep_index, + trigger_only_on_success=True, + has_trigger=False, + ).listener, + trigger, ) """ Triggered after directly preceding event is completed, if it was successful. @@ -79,138 +76,231 @@ def __call__(self, *args, **kwargs): return self.fn(*args, **kwargs) -class EventListenerMethod: +@document() +class EventData: """ - Triggered on an event deployment. + When a subclass of EventData is added as a type hint to an argument of an event listener method, this object will be passed as that argument. + It contains information about the event that triggered the listener, such the target object, and other data related to the specific event that are attributes of the subclass. + + Example: + table = gr.Dataframe([[1, 2, 3], [4, 5, 6]]) + gallery = gr.Gallery([("cat.jpg", "Cat"), ("dog.jpg", "Dog")]) + textbox = gr.Textbox("Hello World!") + + statement = gr.Textbox() + + def on_select(evt: gr.SelectData): # SelectData is a subclass of EventData + return f"You selected {evt.value} at {evt.index} from {evt.target}" + + table.select(on_select, None, statement) + gallery.select(on_select, None, statement) + textbox.select(on_select, None, statement) + Demos: gallery_selections, tictactoe """ + def __init__(self, target: Block | None, _data: Any): + """ + Parameters: + target: The target object that triggered the event. Can be used to distinguish if multiple components are bound to the same listener. + """ + self.target = target + self._data = _data + + +class SelectData(EventData): + def __init__(self, target: Block | None, data: Any): + super().__init__(target, data) + self.index: int | tuple[int, int] = data["index"] + """ + The index of the selected item. Is a tuple if the component is two dimensional or selection is a range. + """ + self.value: Any = data["value"] + """ + The value of the selected item. + """ + self.selected: bool = data.get("selected", True) + """ + True if the item was selected, False if deselected. + """ + + +@dataclasses.dataclass +class EventListenerMethod: + block: Block | None + event_name: str + + +class EventListener(str): + def __new__(cls, event_name, *args, **kwargs): + return super().__new__(cls, event_name) + def __init__( self, - trigger: Block | None, event_name: str, - show_progress: Literal["full", "minimal", "hidden"] = "full", + has_trigger: bool = True, + config_data: Callable[..., dict[str, Any]] = lambda: {}, + show_progress: Literal["full", "minimal", "hidden"] | None = None, callback: Callable | None = None, trigger_after: int | None = None, trigger_only_on_success: bool = False, ): - self.trigger = trigger + super().__init__() + self.has_trigger = has_trigger + self.config_data = config_data self.event_name = event_name self.show_progress = show_progress - self.callback = callback self.trigger_after = trigger_after self.trigger_only_on_success = trigger_only_on_success + self.callback = callback + self.listener = self._setup( + event_name, + has_trigger, + show_progress, + callback, + trigger_after, + trigger_only_on_success, + ) - def __call__( - self, - fn: Callable | None | Literal["decorator"] = "decorator", - inputs: Component | Sequence[Component] | set[Component] | None = None, - outputs: Component | Sequence[Component] | None = None, - api_name: str | None | Literal[False] = None, - status_tracker: None = None, - scroll_to_output: bool = False, - show_progress: Literal["full", "minimal", "hidden"] | None = None, - queue: bool | None = None, - batch: bool = False, - max_batch_size: int = 4, - preprocess: bool = True, - postprocess: bool = True, - cancels: dict[str, Any] | list[dict[str, Any]] | None = None, - every: float | None = None, - _js: str | None = None, - ) -> Dependency: - """ - Parameters: - fn: the function to call when this event is triggered. Often a machine learning model's prediction function. Each parameter of the function corresponds to one input component, and the function should return a single value or a tuple of values, with each element in the tuple corresponding to one output component. - inputs: List of gradio.components to use as inputs. If the function takes no inputs, this should be an empty list. - outputs: List of gradio.components to use as outputs. If the function returns no outputs, this should be an empty list. - api_name: Defines how the endpoint appears in the API docs. Can be a string, None, or False. If False, the endpoint will not be exposed in the api docs. If set to None, the endpoint will be exposed in the api docs as an unnamed endpoint, although this behavior will be changed in Gradio 4.0. If set to a string, the endpoint will be exposed in the api docs with the given name. - status_tracker: Deprecated and has no effect. - scroll_to_output: If True, will scroll to output component on completion - show_progress: If True, will show progress animation while pending - queue: If True, will place the request on the queue, if the queue has been enabled. If False, will not put this event on the queue, even if the queue has been enabled. If None, will use the queue setting of the gradio app. - batch: If True, then the function should process a batch of inputs, meaning that it should accept a list of input values for each parameter. The lists should be of equal length (and be up to length `max_batch_size`). The function is then *required* to return a tuple of lists (even if there is only 1 output component), with each list in the tuple corresponding to one output component. - max_batch_size: Maximum number of inputs to batch together if this is called from the queue (only relevant if batch=True) - preprocess: If False, will not run preprocessing of component data before running 'fn' (e.g. leaving it as a base64 string if this method is called with the `Image` component). - postprocess: If False, will not run postprocessing of component data before returning 'fn' output to the browser. - cancels: A list of other events to cancel when this listener is triggered. For example, setting cancels=[click_event] will cancel the click_event, where click_event is the return value of another components .click method. Functions that have not yet run (or generators that are iterating) will be cancelled, but functions that are currently running will be allowed to finish. - every: Run this event 'every' number of seconds while the client connection is open. Interpreted in seconds. Queue must be enabled. - """ - if fn == "decorator": - - def wrapper(func): - self.__call__( - func, - inputs, - outputs, - api_name, - status_tracker, - scroll_to_output, - show_progress, - queue, - batch, - max_batch_size, - preprocess, - postprocess, - cancels, - every, - _js, + @staticmethod + def _setup( + _event_name: str, + _has_trigger: bool, + _show_progress: Literal["full", "minimal", "hidden"] | None, + _callback: Callable | None, + _trigger_after: int | None, + _trigger_only_on_success: bool, + ): + def event_trigger( + block: Block | None, + fn: Callable | None, + inputs: Component | list[Component] | set[Component] | None = None, + outputs: Component | list[Component] | None = None, + api_name: str | None | Literal[False] = None, + scroll_to_output: bool = False, + show_progress: Literal["full", "minimal", "hidden"] = "full", + queue: bool | None = None, + batch: bool = False, + max_batch_size: int = 4, + preprocess: bool = True, + postprocess: bool = True, + cancels: dict[str, Any] | list[dict[str, Any]] | None = None, + every: float | None = None, + _js: str | None = None, + ) -> Dependency: + """ + Parameters: + fn: the function to call when this event is triggered. Often a machine learning model's prediction function. Each parameter of the function corresponds to one input component, and the function should return a single value or a tuple of values, with each element in the tuple corresponding to one output component. + inputs: List of gradio.components to use as inputs. If the function takes no inputs, this should be an empty list. + outputs: List of gradio.components to use as outputs. If the function returns no outputs, this should be an empty list. + api_name: Defines how the endpoint appears in the API docs. Can be a string, None, or False. If False, the endpoint will not be exposed in the api docs. If set to None, the endpoint will be given the name of the python function fn. If no fn is passed in, it will be given the name 'unnamed'. If set to a string, the endpoint will be exposed in the api docs with the given name. + scroll_to_output: If True, will scroll to output component on completion + show_progress: If True, will show progress animation while pending + queue: If True, will place the request on the queue, if the queue has been enabled. If False, will not put this event on the queue, even if the queue has been enabled. If None, will use the queue setting of the gradio app. + batch: If True, then the function should process a batch of inputs, meaning that it should accept a list of input values for each parameter. The lists should be of equal length (and be up to length `max_batch_size`). The function is then *required* to return a tuple of lists (even if there is only 1 output component), with each list in the tuple corresponding to one output component. + max_batch_size: Maximum number of inputs to batch together if this is called from the queue (only relevant if batch=True) + preprocess: If False, will not run preprocessing of component data before running 'fn' (e.g. leaving it as a base64 string if this method is called with the `Image` component). + postprocess: If False, will not run postprocessing of component data before returning 'fn' output to the browser. + cancels: A list of other events to cancel when this listener is triggered. For example, setting cancels=[click_event] will cancel the click_event, where click_event is the return value of another components .click method. Functions that have not yet run (or generators that are iterating) will be cancelled, but functions that are currently running will be allowed to finish. + every: Run this event 'every' number of seconds while the client connection is open. Interpreted in seconds. Queue must be enabled. + """ + + if fn == "decorator": + + def wrapper(func): + event_trigger( + block, + func, + inputs, + outputs, + api_name, + scroll_to_output, + show_progress, + queue, + batch, + max_batch_size, + preprocess, + postprocess, + cancels, + every, + _js, + ) + + @wraps(func) + def inner(*args, **kwargs): + return func(*args, **kwargs) + + return inner + + return Dependency(None, {}, None, wrapper) + + if block and "stream" in block.events: + block.check_streamable() # type: ignore + if isinstance(show_progress, bool): + show_progress = "full" if show_progress else "hidden" + + if api_name is None: + if fn is not None: + if not hasattr(fn, "__name__"): + if hasattr(fn, "__class__") and hasattr( + fn.__class__, "__name__" + ): + name = fn.__class__.__name__ + else: + name = "unnamed" + else: + name = fn.__name__ + api_name = "".join( + [ + s + for s in name + if s not in set(string.punctuation) - {"-", "_"} + ] + ) + else: + # Don't document _js only events + api_name = False + + if Context.root_block is None: + raise AttributeError( + "Cannot call {self.event_name} outside of a gradio.Blocks context." ) - @wraps(func) - def inner(*args, **kwargs): - return func(*args, **kwargs) - - return inner - - return Dependency({}, None, wrapper) - - if status_tracker: - warn_deprecation( - "The 'status_tracker' parameter has been deprecated and has no effect." + dep, dep_index = Context.root_block.set_event_trigger( + [EventListenerMethod(block if _has_trigger else None, _event_name)], + fn, + inputs, + outputs, + preprocess=preprocess, + postprocess=postprocess, + scroll_to_output=scroll_to_output, + show_progress=show_progress + if show_progress is not None + else _show_progress, + api_name=api_name, + js=_js, + queue=queue, + batch=batch, + max_batch_size=max_batch_size, + every=every, + trigger_after=_trigger_after, + trigger_only_on_success=_trigger_only_on_success, ) - if self.event_name == "stop": - warn_deprecation( - "The `stop` event on Video and Audio has been deprecated and will be remove in a future version. Use `ended` instead." + set_cancel_events( + [EventListenerMethod(block if _has_trigger else None, _event_name)], + cancels, ) + if _callback: + _callback(block) + return Dependency(block, dep, dep_index, fn) - if isinstance(self, Streamable): - self.check_streamable() - if isinstance(show_progress, bool): - show_progress = "full" if show_progress else "hidden" - - if Context.root_block is None: - raise AttributeError( - "Cannot call {self.event_name} outside of a gradio.Blocks context." - ) - - dep, dep_index = Context.root_block.set_event_trigger( - [self], - fn, - inputs, - outputs, - preprocess=preprocess, - postprocess=postprocess, - scroll_to_output=scroll_to_output, - show_progress=show_progress - if show_progress is not None - else self.show_progress, - api_name=api_name, - js=_js, - queue=queue, - batch=batch, - max_batch_size=max_batch_size, - every=every, - trigger_after=self.trigger_after, - trigger_only_on_success=self.trigger_only_on_success, - ) - set_cancel_events([self], cancels) - if self.callback: - self.callback() - return Dependency(dep, dep_index, fn) + event_trigger.event_name = _event_name + event_trigger.has_trigger = _has_trigger + return event_trigger +# TODO: Fix type def on( - triggers: Sequence[EventListenerMethod] | EventListenerMethod | None = None, + triggers: Sequence[Any] | Any | None = None, fn: Callable | None | Literal["decorator"] = "decorator", inputs: Component | list[Component] | set[Component] | None = None, outputs: Component | list[Component] | None = None, @@ -246,7 +336,7 @@ def on( """ from gradio.components.base import Component - if isinstance(triggers, EventListenerMethod): + if isinstance(triggers, EventListener): triggers = [triggers] if isinstance(inputs, Component): inputs = [inputs] @@ -278,13 +368,14 @@ def inner(*args, **kwargs): return inner - return Dependency({}, None, wrapper) + return Dependency(None, {}, None, wrapper) if Context.root_block is None: raise Exception("Cannot call on() outside of a gradio.Blocks context.") if triggers is None: - triggers = [input.change for input in inputs] if inputs is not None else [] - + triggers = [EventListenerMethod(input, "change") for input in inputs] if inputs is not None else [] # type: ignore + else: + triggers = [EventListenerMethod(t.__self__ if t.has_trigger else None, t.event_name) for t in triggers] # type: ignore dep, dep_index = Context.root_block.set_event_trigger( triggers, fn, @@ -302,216 +393,42 @@ def inner(*args, **kwargs): every=every, ) set_cancel_events(triggers, cancels) - return Dependency(dep, dep_index, fn) - - -@document("*change", inherit=True) -class Changeable(EventListener): - def __init__(self): - self.change = EventListenerMethod(self, "change") - """ - This listener is triggered when the component's value changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). - See `.input()` for a listener that is only triggered by user input. - """ - - -@document("*input", inherit=True) -class Inputable(EventListener): - def __init__(self): - self.input = EventListenerMethod(self, "input") - """ - This listener is triggered when the user changes the value of the component. - """ - - -@document("*click", inherit=True) -class Clickable(EventListener): - def __init__(self): - self.click = EventListenerMethod(self, "click") - """ - This listener is triggered when the component (e.g. a button) is clicked. - """ - - -@document("*submit", inherit=True) -class Submittable(EventListener): - def __init__(self): - self.submit = EventListenerMethod(self, "submit") - """ - This listener is triggered when the user presses the Enter key while the component (e.g. a textbox) is focused. - """ - - -@document("*edit", inherit=True) -class Editable(EventListener): - def __init__(self): - self.edit = EventListenerMethod(self, "edit") - """ - This listener is triggered when the user edits the component (e.g. image) using the - built-in editor. - """ - - -@document("*clear", inherit=True) -class Clearable(EventListener): - def __init__(self): - self.clear = EventListenerMethod(self, "clear") - """ - This listener is triggered when the user clears the component (e.g. image or audio) - using the X button for the component. - """ - - -@document("*play", "*pause", "*stop", "*end", inherit=True) -class Playable(EventListener): - def __init__(self): - self.play = EventListenerMethod(self, "play") - """ - This listener is triggered when the user plays the component (e.g. audio or video). - """ - - self.pause = EventListenerMethod(self, "pause") - """ - This listener is triggered when the media stops playing for any reason (e.g. audio or video). - """ - - self.stop = EventListenerMethod(self, "stop") - """ - This listener is triggered when the user reaches the end of the media track (e.g. audio or video). - """ - - self.end = EventListenerMethod(self, "end") - """ - This listener is triggered when the user reaches the end of the media track (e.g. audio or video). - """ - - -@document("*stream", inherit=True) -class Streamable(EventListener): - def __init__(self): - self.streaming: bool - self.stream = EventListenerMethod( - self, - "stream", - show_progress="hidden", - callback=lambda: setattr(self, "streaming", True), - ) - """ - This listener is triggered when the user streams the component (e.g. a live webcam - component). - """ - - def check_streamable(self): - pass - - -class StreamableOutput(EventListener): - def __init__(self): - self.streaming: bool - - def stream_output(self, y, output_id: str, first_chunk: bool) -> tuple[bytes, Any]: - raise NotImplementedError - - -@document("*start_recording", "*stop_recording", inherit=True) -class Recordable(EventListener): - def __init__(self): - self.start_recording = EventListenerMethod(self, "start_recording") - """ - This listener is triggered when the user starts recording with the component (e.g. audio or video). - """ - - self.stop_recording = EventListenerMethod(self, "stop_recording") - """ - This listener is triggered when the user stops recording with the component (e.g. audio or video). - """ - - -@document("*focus", "*blur", inherit=True) -class Focusable(EventListener): - def __init__(self): - self.focus = EventListenerMethod(self, "focus") - """ - This listener is triggered when the component is focused (e.g. when the user clicks inside a textbox). - """ - - self.blur = EventListenerMethod(self, "blur") - """ - This listener is triggered when the component's is unfocused/blurred (e.g. when the user clicks outside of a textbox). - """ - - -@document("*upload", inherit=True) -class Uploadable(EventListener): - def __init__(self): - self.upload = EventListenerMethod(self, "upload") - """ - This listener is triggered when the user uploads a file into the component (e.g. when the user uploads a video into a video component). - """ - - -@document("*release", inherit=True) -class Releaseable(EventListener): - def __init__(self): - self.release = EventListenerMethod(self, "release") - """ - This listener is triggered when the user releases the mouse on this component (e.g. when the user releases the slider). - """ - - -@document("*select", inherit=True) -class Selectable(EventListener): - def __init__(self): - self.selectable: bool = False - self.select = EventListenerMethod( - self, "select", callback=lambda: setattr(self, "selectable", True) - ) - """ - This listener is triggered when the user selects from within the Component. - This event has EventData of type gradio.SelectData that carries information, accessible through SelectData.index and SelectData.value. - See EventData documentation on how to use this event data. - """ - - def get_config(self): - config = super().get_config() - config["selectable"] = self.selectable - return config - - -class SelectData(EventData): - def __init__(self, target: Block | None, data: Any): - super().__init__(target, data) - self.index: int | tuple[int, int] = data["index"] - """ - The index of the selected item. Is a tuple if the component is two dimensional or selection is a range. - """ - self.value: Any = data["value"] - """ - The value of the selected item. - """ - self.selected: bool = data.get("selected", True) - """ - True if the item was selected, False if deselected. - """ - - -@document("*like", inherit=True) -class Likeable(EventListener): - def __init__(self): - self.likeable: bool = False - self.like = EventListenerMethod( - self, "like", callback=lambda: setattr(self, "likeable", True) - ) - """ - This listener is triggered when the user likes/dislikes from within the Component. - This event has EventData of type gradio.LikeData that carries information, accessible through LikeData.index and LikeData.value. - See EventData documentation on how to use this event data. - """ - - def get_config(self): - config = super().get_config() - config["likeable"] = self.likeable - return config + return Dependency(None, dep, dep_index, fn) + + +class Events: + change = "change" + input = "input" + click = "click" + submit = "submit" + edit = "edit" + clear = "clear" + play = "play" + pause = "pause" + stop = "stop" + end = "end" + start_recording = "start_recording" + stop_recording = "stop_recording" + focus = "focus" + blur = "blur" + upload = "upload" + release = "release" + select = EventListener( + "select", + config_data=lambda: {"selectable": False}, + callback=lambda block: setattr(block, "selectable", True), + ) + stream = EventListener( + "stream", + show_progress="hidden", + config_data=lambda: {"streamable": False}, + callback=lambda block: setattr(block, "streaming", True), + ) + like = EventListener( + "like", + config_data=lambda: {"likeable": False}, + callback=lambda block: setattr(block, "likeable", True), + ) class LikeData(EventData): diff --git a/gradio/exceptions.py b/gradio/exceptions.py index b8ed0989ae06..9667e2c9faed 100644 --- a/gradio/exceptions.py +++ b/gradio/exceptions.py @@ -81,3 +81,7 @@ def __init__(self, message: str = "Error raised."): def __str__(self): return repr(self.message) + + +class ComponentDefinitionError(NotImplementedError): + pass diff --git a/gradio/external.py b/gradio/external.py index edf08b3d425d..b198a3209a33 100644 --- a/gradio/external.py +++ b/gradio/external.py @@ -15,7 +15,6 @@ import gradio from gradio import components, utils from gradio.context import Context -from gradio.deprecation import warn_deprecation from gradio.exceptions import Error, ModelNotFoundError, TooManyRequestsError from gradio.external_utils import ( cols_to_rows, @@ -39,7 +38,6 @@ def load( name: str, src: str | None = None, - api_key: str | None = None, hf_token: str | None = None, alias: str | None = None, **kwargs, @@ -51,7 +49,6 @@ def load( Parameters: name: the name of the model (e.g. "gpt2" or "facebook/bart-base") or space (e.g. "flax-community/spanish-gpt2"), can include the `src` as prefix (e.g. "models/facebook/bart-base") src: the source of the model: `models` or `spaces` (or leave empty if source is provided as a prefix in `name`) - api_key: Deprecated. Please use the `hf_token` parameter instead. hf_token: optional access token for loading private Hugging Face Hub models or spaces. Find your token here: https://huggingface.co/settings/tokens. Warning: only provide this if you are loading a trusted private Space as it can be read by the Space you are loading. alias: optional string used as the name of the loaded model instead of the default name (only applies if loading a Space running Gradio 2.x) Returns: @@ -61,12 +58,6 @@ def load( demo = gr.load("gradio/question-answering", src="spaces") demo.launch() """ - if hf_token is None and api_key: - warn_deprecation( - "The `api_key` parameter will be deprecated. " - "Please use the `hf_token` parameter going forward." - ) - hf_token = api_key return load_blocks_from_repo( name=name, src=src, hf_token=hf_token, alias=alias, **kwargs ) diff --git a/gradio/flagging.py b/gradio/flagging.py index a226fb8d50e4..513c8aef76da 100644 --- a/gradio/flagging.py +++ b/gradio/flagging.py @@ -18,10 +18,9 @@ import gradio as gr from gradio import utils -from gradio.deprecation import warn_deprecation if TYPE_CHECKING: - from gradio.components import IOComponent + from gradio.components import Component set_documentation_group("flagging") @@ -32,7 +31,7 @@ class FlaggingCallback(ABC): """ @abstractmethod - def setup(self, components: list[IOComponent], flagging_dir: str): + def setup(self, components: list[Component], flagging_dir: str): """ This method should be overridden and ensure that everything is set up correctly for flag(). This method gets called once at the beginning of the Interface.launch() method. @@ -80,7 +79,7 @@ def image_classifier(inp): def __init__(self): pass - def setup(self, components: list[IOComponent], flagging_dir: str | Path): + def setup(self, components: list[Component], flagging_dir: str | Path): self.components = components self.flagging_dir = flagging_dir os.makedirs(flagging_dir, exist_ok=True) @@ -99,11 +98,11 @@ def flag( save_dir = Path( flagging_dir ) / client_utils.strip_invalid_filename_characters(component.label or "") + save_dir.mkdir(exist_ok=True) csv_data.append( - component.deserialize( + component.flag( sample, save_dir, - None, ) ) @@ -135,7 +134,7 @@ def __init__(self): def setup( self, - components: list[IOComponent], + components: list[Component], flagging_dir: str | Path, ): self.components = components @@ -167,11 +166,12 @@ def flag( ) / client_utils.strip_invalid_filename_characters( getattr(component, "label", None) or f"component {idx}" ) + save_dir.mkdir(exist_ok=True) if utils.is_update(sample): csv_data.append(str(sample)) else: csv_data.append( - component.deserialize(sample, save_dir=save_dir) + component.flag(sample, flag_dir=save_dir) if sample is not None else "" ) @@ -209,32 +209,25 @@ def __init__( self, hf_token: str, dataset_name: str, - organization: str | None = None, private: bool = False, info_filename: str = "dataset_info.json", separate_dirs: bool = False, - verbose: bool = True, # silently ignored. TODO: remove it? ): """ Parameters: hf_token: The HuggingFace token to use to create (and write the flagged sample to) the HuggingFace dataset (defaults to the registered one). dataset_name: The repo_id of the dataset to save the data to, e.g. "image-classifier-1" or "username/image-classifier-1". - organization: Deprecated argument. Please pass a full dataset id (e.g. 'username/dataset_name') to `dataset_name` instead. private: Whether the dataset should be private (defaults to False). info_filename: The name of the file to save the dataset info (defaults to "dataset_infos.json"). separate_dirs: If True, each flagged item will be saved in a separate directory. This makes the flagging more robust to concurrent editing, but may be less convenient to use. """ - if organization is not None: - warn_deprecation( - "Parameter `organization` is not used anymore. Please pass a full dataset id (e.g. 'username/dataset_name') to `dataset_name` instead." - ) self.hf_token = hf_token self.dataset_id = dataset_name # TODO: rename parameter (but ensure backward compatibility somehow) self.dataset_private = private self.info_filename = info_filename self.separate_dirs = separate_dirs - def setup(self, components: list[IOComponent], flagging_dir: str): + def setup(self, components: list[Component], flagging_dir: str): """ Params: flagging_dir (str): local directory where the dataset is cloned, @@ -425,7 +418,8 @@ def _deserialize_components( # Get deserialized object (will save sample to disk if applicable -file, audio, image,...-) label = component.label or "" save_dir = data_dir / client_utils.strip_invalid_filename_characters(label) - deserialized = component.deserialize(sample, save_dir, None) + save_dir.mkdir(exist_ok=True, parents=True) + deserialized = component.flag(sample, save_dir) # Add deserialized object to row features[label] = {"dtype": "string", "_type": "Value"} @@ -465,30 +459,6 @@ def _deserialize_components( return features, row -class HuggingFaceDatasetJSONSaver(HuggingFaceDatasetSaver): - def __init__( - self, - hf_token: str, - dataset_name: str, - organization: str | None = None, - private: bool = False, - info_filename: str = "dataset_info.json", - verbose: bool = True, # silently ignored. TODO: remove it? - ): - warn_deprecation( - "Callback `HuggingFaceDatasetJSONSaver` is deprecated in favor of using" - " `HuggingFaceDatasetSaver` and passing `separate_dirs=True` as parameter." - ) - super().__init__( - hf_token=hf_token, - dataset_name=dataset_name, - organization=organization, - private=private, - info_filename=info_filename, - separate_dirs=True, - ) - - class FlagMethod: """ Helper class that contains the flagging options and calls the flagging method. Also diff --git a/gradio/helpers.py b/gradio/helpers.py index 794e0a98c998..aed70c682be4 100644 --- a/gradio/helpers.py +++ b/gradio/helpers.py @@ -25,12 +25,13 @@ from gradio import components, oauth, processing_utils, routes, utils, wasm_utils from gradio.context import Context, LocalContext +from gradio.data_classes import GradioModel, GradioRootModel +from gradio.events import EventData from gradio.exceptions import Error from gradio.flagging import CSVLogger if TYPE_CHECKING: # Only import for type checking (to avoid circular imports). - from gradio.blocks import Block - from gradio.components import IOComponent + from gradio.components import Component CACHED_FOLDER = "gradio_cached_examples" LOG_FILE = "log.csv" @@ -40,8 +41,8 @@ def create_examples( examples: list[Any] | list[list[Any]] | str, - inputs: IOComponent | list[IOComponent], - outputs: IOComponent | list[IOComponent] | None = None, + inputs: Component | list[Component], + outputs: Component | list[Component] | None = None, fn: Callable | None = None, cache_examples: bool = False, examples_per_page: int = 10, @@ -91,8 +92,8 @@ class Examples: def __init__( self, examples: list[Any] | list[list[Any]] | str, - inputs: IOComponent | list[IOComponent], - outputs: IOComponent | list[IOComponent] | None = None, + inputs: Component | list[Component], + outputs: Component | list[Component] | None = None, fn: Callable | None = None, cache_examples: bool = False, examples_per_page: int = 10, @@ -206,13 +207,19 @@ def __init__( self.batch = batch with utils.set_directory(working_directory): - self.processed_examples = [ - [ - component.postprocess(sample) - for component, sample in zip(inputs, example) - ] - for example in examples - ] + self.processed_examples = [] + for example in examples: + sub = [] + for component, sample in zip(inputs, example): + prediction_value = component.postprocess(sample) + if isinstance(prediction_value, (GradioRootModel, GradioModel)): + prediction_value = prediction_value.model_dump() + prediction_value = processing_utils.move_files_to_cache( + prediction_value, component + ) + sub.append(prediction_value) + self.processed_examples.append(sub) + self.non_none_processed_examples = [ [ex for (ex, keep) in zip(example, input_has_examples) if keep] for example in self.processed_examples @@ -252,7 +259,14 @@ def create(self) -> None: async def load_example(example_id): processed_example = self.non_none_processed_examples[example_id] - return utils.resolve_singleton(processed_example) + if len(self.inputs_with_examples) == 1: + return update( + value=processed_example[0], **self.dataset.component_props[0] + ) + return [ + update(value=processed_example[i], **self.dataset.component_props[i]) + for i in range(len(self.inputs_with_examples)) + ] if Context.root_block: self.load_input_event = self.dataset.click( @@ -410,23 +424,21 @@ def load_from_cache(self, example_id: int) -> list[Any]: output.append(value_as_dict) except (ValueError, TypeError, SyntaxError, AssertionError): output.append( - component.serialize( - value_to_use, self.cached_folder, allow_links=True + component.read_from_flag( + value_to_use, + self.cached_folder, ) ) return output def merge_generated_values_into_output( - components: list[IOComponent], generated_values: list, output: list + components: list[Component], generated_values: list, output: list ): - from gradio.events import StreamableOutput + from gradio.components.base import StreamingOutput for output_index, output_component in enumerate(components): - if ( - isinstance(output_component, StreamableOutput) - and output_component.streaming - ): + if isinstance(output_component, StreamingOutput) and output_component.streaming: binary_chunks = [] for i, chunk in enumerate(generated_values): if len(components) > 1: @@ -1059,37 +1071,6 @@ def _animate(_): return output_mp4.name -@document() -class EventData: - """ - When a subclass of EventData is added as a type hint to an argument of an event listener method, this object will be passed as that argument. - It contains information about the event that triggered the listener, such the target object, and other data related to the specific event that are attributes of the subclass. - - Example: - table = gr.Dataframe([[1, 2, 3], [4, 5, 6]]) - gallery = gr.Gallery([("cat.jpg", "Cat"), ("dog.jpg", "Dog")]) - textbox = gr.Textbox("Hello World!") - - statement = gr.Textbox() - - def on_select(evt: gr.SelectData): # SelectData is a subclass of EventData - return f"You selected {evt.value} at {evt.index} from {evt.target}" - - table.select(on_select, None, statement) - gallery.select(on_select, None, statement) - textbox.select(on_select, None, statement) - Demos: gallery_selections, tictactoe - """ - - def __init__(self, target: Block | None, _data: Any): - """ - Parameters: - target: The target object that triggered the event. Can be used to distinguish if multiple components are bound to the same listener. - """ - self.target = target - self._data = _data - - def log_message(message: str, level: Literal["info", "warning"] = "info"): from gradio.context import LocalContext diff --git a/gradio/inputs.py b/gradio/inputs.py deleted file mode 100644 index 9345530649a0..000000000000 --- a/gradio/inputs.py +++ /dev/null @@ -1,451 +0,0 @@ -# type: ignore -""" -This module defines various classes that can serve as the `input` to an interface. Each class must inherit from -`InputComponent`, and each class must define a path to its template. All of the subclasses of `InputComponent` are -automatically added to a registry, which allows them to be easily referenced in other parts of the code. -""" - -from __future__ import annotations - -from typing import Any, Optional - -from gradio import components -from gradio.deprecation import warn_deprecation - - -def warn_inputs_deprecation(): - warn_deprecation( - "Usage of gradio.inputs is deprecated, and will not be supported in the future, please import your component from gradio.components", - ) - - -class Textbox(components.Textbox): - def __init__( - self, - lines: int = 1, - placeholder: Optional[str] = None, - default: str = "", - numeric: Optional[bool] = False, - type: Optional[str] = "text", - label: Optional[str] = None, - optional: bool = False, - ): - warn_inputs_deprecation() - super().__init__( - value=default, - lines=lines, - placeholder=placeholder, - label=label, - numeric=numeric, - type=type, - optional=optional, - ) - - -class Number(components.Number): - """ - Component creates a field for user to enter numeric input. Provides a number as an argument to the wrapped function. - Input type: float - """ - - def __init__( - self, - default: Optional[float] = None, - label: Optional[str] = None, - optional: bool = False, - ): - """ - Parameters: - default (float): default value. - label (str): component name in interface. - optional (bool): If True, the interface can be submitted with no value for this component. - """ - warn_inputs_deprecation() - super().__init__(value=default, label=label, optional=optional) - - -class Slider(components.Slider): - """ - Component creates a slider that ranges from `minimum` to `maximum`. Provides number as an argument to the wrapped function. - Input type: float - """ - - def __init__( - self, - minimum: float = 0, - maximum: float = 100, - step: Optional[float] = None, - default: Optional[float] = None, - label: Optional[str] = None, - optional: bool = False, - ): - """ - Parameters: - minimum (float): minimum value for slider. - maximum (float): maximum value for slider. - step (float): increment between slider values. - default (float): default value. - label (str): component name in interface. - optional (bool): this parameter is ignored. - """ - warn_inputs_deprecation() - - super().__init__( - value=default, - minimum=minimum, - maximum=maximum, - step=step, - label=label, - optional=optional, - ) - - -class Checkbox(components.Checkbox): - """ - Component creates a checkbox that can be set to `True` or `False`. Provides a boolean as an argument to the wrapped function. - Input type: bool - """ - - def __init__( - self, - default: bool = False, - label: Optional[str] = None, - optional: bool = False, - ): - """ - Parameters: - label (str): component name in interface. - default (bool): if True, checked by default. - optional (bool): this parameter is ignored. - """ - warn_inputs_deprecation() - super().__init__(value=default, label=label, optional=optional) - - -class CheckboxGroup(components.CheckboxGroup): - """ - Component creates a set of checkboxes of which a subset can be selected. Provides a list of strings representing the selected choices as an argument to the wrapped function. - Input type: Union[List[str], List[int]] - """ - - def __init__( - self, - choices: list[str], - default: list[str] | None = None, - type: str = "value", - label: Optional[str] = None, - optional: bool = False, - ): - """ - Parameters: - choices (List[str]): list of options to select from. - default (List[str]): default selected list of options. - type (str): Type of value to be returned by component. "value" returns the list of strings of the choices selected, "index" returns the list of indices of the choices selected. - label (str): component name in interface. - optional (bool): this parameter is ignored. - """ - if default is None: - default = [] - warn_inputs_deprecation() - super().__init__( - value=default, - choices=choices, - type=type, - label=label, - optional=optional, - ) - - -class Radio(components.Radio): - """ - Component creates a set of radio buttons of which only one can be selected. Provides string representing selected choice as an argument to the wrapped function. - Input type: Union[str, int] - """ - - def __init__( - self, - choices: list[str], - type: str = "value", - default: Optional[str] = None, - label: Optional[str] = None, - optional: bool = False, - ): - """ - Parameters: - choices (List[str]): list of options to select from. - type (str): Type of value to be returned by component. "value" returns the string of the choice selected, "index" returns the index of the choice selected. - default (str): the button selected by default. If None, no button is selected by default. - label (str): component name in interface. - optional (bool): this parameter is ignored. - """ - warn_inputs_deprecation() - super().__init__( - choices=choices, - type=type, - value=default, - label=label, - optional=optional, - ) - - -class Dropdown(components.Dropdown): - """ - Component creates a dropdown of which only one can be selected. Provides string representing selected choice as an argument to the wrapped function. - Input type: Union[str, int] - """ - - def __init__( - self, - choices: list[str], - type: str = "value", - default: Optional[str] = None, - label: Optional[str] = None, - optional: bool = False, - ): - """ - Parameters: - choices (List[str]): list of options to select from. - type (str): Type of value to be returned by component. "value" returns the string of the choice selected, "index" returns the index of the choice selected. - default (str): default value selected in dropdown. If None, no value is selected by default. - label (str): component name in interface. - optional (bool): this parameter is ignored. - """ - warn_inputs_deprecation() - super().__init__( - choices=choices, - type=type, - value=default, - label=label, - optional=optional, - ) - - -class Image(components.Image): - """ - Component creates an image upload box with editing capabilities. - Input type: Union[numpy.array, PIL.Image, file-object] - """ - - def __init__( - self, - shape: tuple[int, int] = None, - image_mode: str = "RGB", - invert_colors: bool = False, - source: str = "upload", - tool: str = "editor", - type: str = "numpy", - label: str = None, - optional: bool = False, - ): - """ - Parameters: - shape (Tuple[int, int]): (width, height) shape to crop and resize image to; if None, matches input image size. - image_mode (str): How to process the uploaded image. Accepts any of the PIL image modes, e.g. "RGB" for color images, "RGBA" to include the transparency mask, "L" for black-and-white images. - invert_colors (bool): whether to invert the image as a preprocessing step. - source (str): Source of image. "upload" creates a box where user can drop an image file, "webcam" allows user to take snapshot from their webcam, "canvas" defaults to a white image that can be edited and drawn upon with tools. - tool (str): Tools used for editing. "editor" allows a full screen editor, "select" provides a cropping and zoom tool. - type (str): Type of value to be returned by component. "numpy" returns a numpy array with shape (height, width, 3) and values from 0 to 255, "pil" returns a PIL image object, "file" returns a temporary file object whose path can be retrieved by file_obj.name, "filepath" returns the path directly. - label (str): component name in interface. - optional (bool): If True, the interface can be submitted with no uploaded image, in which case the input value is None. - """ - warn_inputs_deprecation() - super().__init__( - shape=shape, - image_mode=image_mode, - invert_colors=invert_colors, - source=source, - tool=tool, - type=type, - label=label, - optional=optional, - ) - - -class Video(components.Video): - """ - Component creates a video file upload that is converted to a file path. - - Input type: filepath - """ - - def __init__( - self, - type: Optional[str] = None, - source: str = "upload", - label: Optional[str] = None, - optional: bool = False, - ): - """ - Parameters: - type (str): Type of video format to be returned by component, such as 'avi' or 'mp4'. If set to None, video will keep uploaded format. - source (str): Source of video. "upload" creates a box where user can drop an video file, "webcam" allows user to record a video from their webcam. - label (str): component name in interface. - optional (bool): If True, the interface can be submitted with no uploaded video, in which case the input value is None. - """ - warn_inputs_deprecation() - super().__init__(format=type, source=source, label=label, optional=optional) - - -class Audio(components.Audio): - """ - Component accepts audio input files. - Input type: Union[Tuple[int, numpy.array], file-object, numpy.array] - """ - - def __init__( - self, - source: str = "upload", - type: str = "numpy", - label: str = None, - optional: bool = False, - ): - """ - Parameters: - source (str): Source of audio. "upload" creates a box where user can drop an audio file, "microphone" creates a microphone input. - type (str): Type of value to be returned by component. "numpy" returns a 2-set tuple with an integer sample_rate and the data numpy.array of shape (samples, 2), "file" returns a temporary file object whose path can be retrieved by file_obj.name, "filepath" returns the path directly. - label (str): component name in interface. - optional (bool): If True, the interface can be submitted with no uploaded audio, in which case the input value is None. - """ - warn_inputs_deprecation() - super().__init__(source=source, type=type, label=label, optional=optional) - - -class File(components.File): - """ - Component accepts generic file uploads. - Input type: Union[file-object, bytes, List[Union[file-object, bytes]]] - """ - - def __init__( - self, - file_count: str = "single", - type: str = "file", - label: Optional[str] = None, - keep_filename: bool = True, - optional: bool = False, - ): - """ - Parameters: - file_count (str): if single, allows user to upload one file. If "multiple", user uploads multiple files. If "directory", user uploads all files in selected directory. Return type will be list for each file in case of "multiple" or "directory". - type (str): Type of value to be returned by component. "file" returns a temporary file object whose path can be retrieved by file_obj.name, "binary" returns an bytes object. - label (str): component name in interface. - keep_filename (bool): DEPRECATED. Original filename always kept. - optional (bool): If True, the interface can be submitted with no uploaded image, in which case the input value is None. - """ - warn_inputs_deprecation() - super().__init__( - file_count=file_count, - type=type, - label=label, - keep_filename=keep_filename, - optional=optional, - ) - - -class Dataframe(components.Dataframe): - """ - Component accepts 2D input through a spreadsheet interface. - Input type: Union[pandas.DataFrame, numpy.array, List[Union[str, float]], List[List[Union[str, float]]]] - """ - - def __init__( - self, - headers: Optional[list[str]] = None, - row_count: int = 3, - col_count: Optional[int] = 3, - datatype: str | list[str] = "str", - col_width: int | list[int] = None, - default: Optional[list[list[Any]]] = None, - type: str = "pandas", - label: Optional[str] = None, - optional: bool = False, - ): - """ - Parameters: - headers (List[str]): Header names to dataframe. If None, no headers are shown. - row_count (int): Limit number of rows for input. - col_count (int): Limit number of columns for input. If equal to 1, return data will be one-dimensional. Ignored if `headers` is provided. - datatype (Union[str, List[str]]): Datatype of values in sheet. Can be provided per column as a list of strings, or for the entire sheet as a single string. Valid datatypes are "str", "number", "bool", and "date". - col_width (Union[int, List[int]]): Width of columns in pixels. Can be provided as single value or list of values per column. - default (List[List[Any]]): Default value - type (str): Type of value to be returned by component. "pandas" for pandas dataframe, "numpy" for numpy array, or "array" for a Python array. - label (str): component name in interface. - optional (bool): this parameter is ignored. - """ - warn_inputs_deprecation() - super().__init__( - value=default, - headers=headers, - row_count=row_count, - col_count=col_count, - datatype=datatype, - col_width=col_width, - type=type, - label=label, - optional=optional, - ) - - -class Timeseries(components.Timeseries): - """ - Component accepts pandas.DataFrame uploaded as a timeseries csv file. - Input type: pandas.DataFrame - """ - - def __init__( - self, - x: Optional[str] = None, - y: str | list[str] = None, - label: Optional[str] = None, - optional: bool = False, - ): - """ - Parameters: - x (str): Column name of x (time) series. None if csv has no headers, in which case first column is x series. - y (Union[str, List[str]]): Column name of y series, or list of column names if multiple series. None if csv has no headers, in which case every column after first is a y series. - label (str): component name in interface. - optional (bool): If True, the interface can be submitted with no uploaded csv file, in which case the input value is None. - """ - warn_inputs_deprecation() - super().__init__(x=x, y=y, label=label, optional=optional) - - -class State(components.State): - """ - Special hidden component that stores state across runs of the interface. - Input type: Any - """ - - def __init__( - self, - label: str = None, - default: Any = None, - ): - """ - Parameters: - label (str): component name in interface (not used). - default (Any): the initial value of the state. - optional (bool): this parameter is ignored. - """ - warn_inputs_deprecation() - super().__init__(value=default, label=label) - - -class Image3D(components.Model3D): - """ - Used for 3D image model output. - Input type: File object of type (.obj, glb, or .gltf) - """ - - def __init__( - self, - label: Optional[str] = None, - optional: bool = False, - ): - """ - Parameters: - label (str): component name in interface. - optional (bool): If True, the interface can be submitted with no uploaded image, in which case the input value is None. - """ - warn_inputs_deprecation() - super().__init__(label=label, optional=optional) diff --git a/gradio/interface.py b/gradio/interface.py index c4534e35a197..a78370e02a19 100644 --- a/gradio/interface.py +++ b/gradio/interface.py @@ -13,21 +13,19 @@ from gradio_client.documentation import document, set_documentation_group -from gradio import Examples, external, interpretation, utils +from gradio import Examples, utils from gradio.blocks import Blocks from gradio.components import ( Button, ClearButton, + Component, DuplicateButton, - Interpretation, - IOComponent, Markdown, State, get_component_instance, ) from gradio.data_classes import InterfaceTypes -from gradio.deprecation import warn_deprecation -from gradio.events import Changeable, Streamable, Submittable, on +from gradio.events import Events, on from gradio.exceptions import RenderError from gradio.flagging import CSVLogger, FlaggingCallback, FlagMethod from gradio.layouts import Column, Row, Tab, Tabs @@ -39,8 +37,6 @@ if TYPE_CHECKING: # Only import for type checking (is False at runtime). from transformers.pipelines.base import Pipeline - from gradio.events import EventListenerMethod - @document("launch", "load", "from_pipeline", "integrate", "queue") class Interface(Blocks): @@ -73,35 +69,6 @@ def get_instances(cls) -> list[Interface]: """ return list(Interface.instances) - @classmethod - def load( - cls, - name: str, - src: str | None = None, - api_key: str | None = None, - alias: str | None = None, - **kwargs, - ) -> Blocks: - """ - Warning: this method will be deprecated. Use the equivalent `gradio.load()` instead. This is a class - method that constructs a Blocks from a Hugging Face repo. Can accept - model repos (if src is "models") or Space repos (if src is "spaces"). The input - and output components are automatically loaded from the repo. - Parameters: - name: the name of the model (e.g. "gpt2" or "facebook/bart-base") or space (e.g. "flax-community/spanish-gpt2"), can include the `src` as prefix (e.g. "models/facebook/bart-base") - src: the source of the model: `models` or `spaces` (or leave empty if source is provided as a prefix in `name`) - api_key: optional access token for loading private Hugging Face Hub models or spaces. Find your token here: https://huggingface.co/settings/tokens. Warning: only provide this if you are loading a trusted private Space as it can be read by the Space you are loading. - alias: optional string used as the name of the loaded model instead of the default name (only applies if loading a Space running Gradio 2.x) - Returns: - a Gradio Interface object for the given model - """ - warn_deprecation( - "gr.Interface.load() will be deprecated. Use gr.load() instead." - ) - return external.load( - name=name, src=src, hf_token=api_key, alias=alias, **kwargs - ) - @classmethod def from_pipeline(cls, pipeline: Pipeline, **kwargs) -> Interface: """ @@ -125,14 +92,12 @@ def from_pipeline(cls, pipeline: Pipeline, **kwargs) -> Interface: def __init__( self, fn: Callable, - inputs: str | IOComponent | list[str | IOComponent] | None, - outputs: str | IOComponent | list[str | IOComponent] | None, + inputs: str | Component | list[str | Component] | None, + outputs: str | Component | list[str | Component] | None, examples: list[Any] | list[list[Any]] | str | None = None, cache_examples: bool | None = None, examples_per_page: int = 10, live: bool = False, - interpretation: Callable | str | None = None, - num_shap: float = 2.0, title: str | None = None, description: str | None = None, article: str | None = None, @@ -142,7 +107,7 @@ def __init__( allow_flagging: str | None = None, flagging_options: list[str] | list[tuple[str, str]] | None = None, flagging_dir: str = "flagged", - flagging_callback: FlaggingCallback = CSVLogger(), + flagging_callback: FlaggingCallback | None = None, analytics_enabled: bool | None = None, batch: bool = False, max_batch_size: int = 4, @@ -160,8 +125,6 @@ def __init__( cache_examples: If True, caches examples in the server for fast runtime in examples. If `fn` is a generator function, then the last yielded value will be used as the output. The default option in HuggingFace Spaces is True. The default option elsewhere is False. examples_per_page: If examples are provided, how many to display per page. live: whether the interface should automatically rerun if any of the inputs change. - interpretation: function that provides interpretation explaining prediction output. Pass "default" to use simple built-in interpreter, "shap" to use a built-in shapley-based interpreter, or your own custom interpretation function. For more information on the different interpretation methods, see the Advanced Interface Features guide. - num_shap: a multiplier that determines how many examples are computed for shap-based interpretation. Increasing this value will increase shap runtime, but improve results. Only applies if interpretation is "shap". title: a title for the interface; if provided, appears above the input and output components in large font. Also used as the tab title when opened in a browser window. description: a description for the interface; if provided, appears above the input and output components and beneath the title in regular font. Accepts Markdown and HTML content. article: an expanded article explaining the interface; if provided, appears below the input and output components in regular font. Accepts Markdown and HTML content. @@ -171,7 +134,7 @@ def __init__( allow_flagging: one of "never", "auto", or "manual". If "never" or "auto", users will not see a button to flag an input and output. If "manual", users will see a button to flag. If "auto", every input the user submits will be automatically flagged (outputs are not flagged). If "manual", both the input and outputs are flagged when the user clicks flag button. This parameter can be set with environmental variable GRADIO_ALLOW_FLAGGING; otherwise defaults to "manual". flagging_options: if provided, allows user to select from the list of options when flagging. Only applies if allow_flagging is "manual". Can either be a list of tuples of the form (label, value), where label is the string that will be displayed on the button and value is the string that will be stored in the flagging CSV; or it can be a list of strings ["X", "Y"], in which case the values will be the list of strings and the labels will ["Flag as X", "Flag as Y"], etc. flagging_dir: what to name the directory where flagged data is stored. - flagging_callback: An instance of a subclass of FlaggingCallback which will be called when a sample is flagged. By default logs to a local CSV file. + flagging_callback: None or an instance of a subclass of FlaggingCallback which will be called when a sample is flagged. If set to None, an instance of gradio.flagging.CSVLogger will be created and logs will be saved to a local CSV file in flagging_dir. Default to None. analytics_enabled: Whether to allow basic telemetry. If None, will use GRADIO_ANALYTICS_ENABLED environment variable if defined, or default to True. batch: If True, then the function should process a batch of inputs, meaning that it should accept a list of input values for each parameter. The lists should be of equal length (and be up to length `max_batch_size`). The function is then *required* to return a tuple of lists (even if there is only 1 output component), with each list in the tuple corresponding to one output component. max_batch_size: Maximum number of inputs to batch together if this is called from the queue (only relevant if batch=True) @@ -187,14 +150,6 @@ def __init__( **kwargs, ) self.api_name: str | Literal[False] | None = api_name - - if isinstance(fn, list): - raise DeprecationWarning( - "The `fn` parameter only accepts a single function, support for a list " - "of functions has been deprecated. Please use gradio.mix.Parallel " - "instead." - ) - self.interface_type = InterfaceTypes.STANDARD if (inputs is None or inputs == []) and (outputs is None or outputs == []): raise ValueError("Must provide at least one of `inputs` or `outputs`") @@ -205,8 +160,8 @@ def __init__( inputs = [] self.interface_type = InterfaceTypes.OUTPUT_ONLY - assert isinstance(inputs, (str, list, IOComponent)) - assert isinstance(outputs, (str, list, IOComponent)) + assert isinstance(inputs, (str, list, Component)) + assert isinstance(outputs, (str, list, Component)) if not isinstance(inputs, list): inputs = [inputs] @@ -258,7 +213,7 @@ def __init__( ] for component in self.input_components + self.output_components: - if not (isinstance(component, IOComponent)): + if not (isinstance(component, Component)): raise ValueError( f"{component} is not a valid input/output component for Interface." ) @@ -275,23 +230,11 @@ def __init__( InterfaceTypes.OUTPUT_ONLY, ]: for o in self.output_components: - assert isinstance(o, IOComponent) + assert isinstance(o, Component) if o.interactive is None: # Unless explicitly otherwise specified, force output components to # be non-interactive o.interactive = False - if ( - interpretation is None - or isinstance(interpretation, list) - or callable(interpretation) - ): - self.interpretation = interpretation - elif isinstance(interpretation, str): - self.interpretation = [ - interpretation.lower() for _ in self.input_components - ] - else: - raise ValueError("Invalid value for parameter: interpretation") self.api_mode = _api_mode self.fn = fn @@ -309,7 +252,6 @@ def __init__( self.thumbnail = thumbnail self.examples = examples - self.num_shap = num_shap self.examples_per_page = examples_per_page self.simple_server = None @@ -359,8 +301,12 @@ def __init__( "flagging_options must be a list of strings or list of (string, string) tuples." ) + if flagging_callback is None: + flagging_callback = CSVLogger() + self.flagging_callback = flagging_callback self.flagging_dir = flagging_dir + self.batch = batch self.max_batch_size = max_batch_size self.allow_duplication = allow_duplication @@ -380,11 +326,11 @@ def __init__( if utils.is_special_typed_parameter(param_name, param_types): param_names.remove(param_name) for component, param_name in zip(self.input_components, param_names): - assert isinstance(component, IOComponent) + assert isinstance(component, Component) if component.label is None: component.label = param_name for i, component in enumerate(self.output_components): - assert isinstance(component, IOComponent) + assert isinstance(component, Component) if component.label is None: if len(self.output_components) == 1: component.label = "output" @@ -415,7 +361,6 @@ def __init__( None, None, ) - interpretation_btn, interpretation_set = None, None input_component_column, interpret_component_column = None, None with Row(equal_height=False): @@ -430,8 +375,6 @@ def __init__( stop_btn, flag_btns, input_component_column, - interpret_component_column, - interpretation_set, ) = self.render_input_column() if self.interface_type in [ InterfaceTypes.STANDARD, @@ -443,7 +386,6 @@ def __init__( duplicate_btn, stop_btn_2_out, flag_btns_out, - interpretation_btn, ) = self.render_output_column(submit_btn) submit_btn = submit_btn or submit_btn_out clear_btn = clear_btn or clear_btn_2_out @@ -459,12 +401,6 @@ def __init__( ) if duplicate_btn is not None: duplicate_btn.activate() - self.attach_interpretation_events( - interpretation_btn, - interpretation_set, - input_component_column, - interpret_component_column, - ) self.attach_flagging_events(flag_btns, clear_btn) self.render_examples() @@ -491,23 +427,14 @@ def render_input_column( Button | None, list[Button] | None, Column, - Column | None, - list[Interpretation] | None, ]: submit_btn, clear_btn, stop_btn, flag_btns = None, None, None, None - interpret_component_column, interpretation_set = None, None with Column(variant="panel"): input_component_column = Column() with input_component_column: for component in self.input_components: component.render() - if self.interpretation: - interpret_component_column = Column(visible=False) - interpretation_set = [] - with interpret_component_column: - for component in self.input_components: - interpretation_set.append(Interpretation(component)) with Row(): if self.interface_type in [ InterfaceTypes.STANDARD, @@ -543,8 +470,6 @@ def render_input_column( stop_btn, flag_btns, input_component_column, - interpret_component_column, - interpretation_set, ) def render_output_column( @@ -553,14 +478,12 @@ def render_output_column( ) -> tuple[ Button | None, ClearButton | None, - DuplicateButton, + DuplicateButton | None, Button | None, list | None, - Button | None, ]: submit_btn = submit_btn_in - interpretation_btn, clear_btn, duplicate_btn, flag_btns, stop_btn = ( - None, + clear_btn, duplicate_btn, flag_btns, stop_btn = ( None, None, None, @@ -592,9 +515,6 @@ def render_output_column( raise RenderError("Submit button not rendered") flag_btns = [submit_btn] - if self.interpretation: - interpretation_btn = Button("Interpret") - if self.allow_duplication: duplicate_btn = DuplicateButton(scale=1, size="lg", _activate=False) @@ -604,7 +524,6 @@ def render_output_column( duplicate_btn, stop_btn, flag_btns, - interpretation_btn, ) def render_article(self): @@ -630,12 +549,12 @@ def attach_submit_events(self, submit_btn: Button | None, stop_btn: Button | Non max_batch_size=self.max_batch_size, ) else: - events: list[EventListenerMethod] = [] + events: list[Callable] = [] for component in self.input_components: - if isinstance(component, Streamable) and component.streaming: - events.append(component.stream) - elif isinstance(component, Changeable): - events.append(component.change) + if component.has_event("stream") and component.streaming: # type: ignore + events.append(component.stream) # type: ignore + elif component.has_event("change"): + events.append(component.change) # type: ignore on( events, self.fn, @@ -652,9 +571,9 @@ def attach_submit_events(self, submit_btn: Button | None, stop_btn: Button | Non extra_output = [] triggers = [submit_btn.click] + [ - component.submit + component.submit # type: ignore for component in self.input_components - if isinstance(component, Submittable) + if component.has_event(Events.submit) ] if stop_btn: @@ -697,6 +616,7 @@ def cleanup(): outputs=[submit_btn, stop_btn], cancels=predict_event, queue=False, + api_name=False, ) else: on( @@ -723,40 +643,23 @@ def attach_clear_events( None, [], ( - ([input_component_column] if input_component_column else []) - + ([interpret_component_column] if self.interpretation else []) + [input_component_column] if input_component_column else [] ), # type: ignore _js=f"""() => {json.dumps( ( [{'variant': None, 'visible': True, '__type__': 'update'}] if self.interface_type - in [ - InterfaceTypes.STANDARD, - InterfaceTypes.INPUT_ONLY, - InterfaceTypes.UNIFIED, - ] + in [ + InterfaceTypes.STANDARD, + InterfaceTypes.INPUT_ONLY, + InterfaceTypes.UNIFIED, + ] else [] ) - + ([{'variant': None, 'visible': False, '__type__': 'update'}] if self.interpretation else []) )} """, ) - def attach_interpretation_events( - self, - interpretation_btn: Button | None, - interpretation_set: list[Interpretation] | None, - input_component_column: Column | None, - interpret_component_column: Column | None, - ): - if interpretation_btn: - interpretation_btn.click( - self.interpret_func, - inputs=self.input_components + self.output_components, - outputs=(interpretation_set or []) + [input_component_column, interpret_component_column], # type: ignore - preprocess=False, - ) - def attach_flagging_events( self, flag_btns: list[Button] | None, clear_btn: ClearButton ): @@ -797,6 +700,7 @@ def attach_flagging_events( None, flag_btn, queue=False, + api_name=False, ) flag_btn.click( flag_method, @@ -804,12 +708,10 @@ def attach_flagging_events( outputs=flag_btn, preprocess=False, queue=False, + api_name=False, ) clear_btn.click( - flag_method.reset, - None, - flag_btn, - queue=False, + flag_method.reset, None, flag_btn, queue=False, api_name=False ) def render_examples(self): @@ -845,26 +747,6 @@ def __repr__(self): repr += f"\n|-{component}" return repr - async def interpret_func(self, *args): - return await self.interpret(list(args)) + [ - Column(visible=False), - Column(visible=True), - ] - - async def interpret(self, raw_input: list[Any]) -> list[Any]: - return [ - {"original": raw_value, "interpretation": interpretation} - for interpretation, raw_value in zip( - (await interpretation.run_interpret(self, raw_input))[0], raw_input - ) - ] - - def test_launch(self) -> None: - """ - Deprecated. - """ - warn_deprecation("The Interface.test_launch() function is deprecated.") - @document() class TabbedInterface(Blocks): diff --git a/gradio/interpretation.py b/gradio/interpretation.py deleted file mode 100644 index 3731a9d38153..000000000000 --- a/gradio/interpretation.py +++ /dev/null @@ -1,329 +0,0 @@ -"""Contains classes and methods related to interpretation for components in Gradio.""" - -from __future__ import annotations - -import copy -import math -from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any - -import numpy as np -from gradio_client import utils as client_utils - -from gradio import components - -if TYPE_CHECKING: # Only import for type checking (is False at runtime). - from gradio import Interface - - -class Interpretable(ABC): # noqa: B024 - def __init__(self) -> None: - self.set_interpret_parameters() - - def set_interpret_parameters(self): # noqa: B027 - """ - Set any parameters for interpretation. Properties can be set here to be - used in get_interpretation_neighbors and get_interpretation_scores. - """ - pass - - def get_interpretation_scores( - self, x: Any, neighbors: list[Any] | None, scores: list[float], **kwargs - ) -> list: - """ - Arrange the output values from the neighbors into interpretation scores for the interface to render. - Parameters: - x: Input to interface - neighbors: Neighboring values to input x used for interpretation. - scores: Output value corresponding to each neighbor in neighbors - Returns: - Arrangement of interpretation scores for interfaces to render. - """ - return scores - - -class TokenInterpretable(Interpretable, ABC): - @abstractmethod - def tokenize(self, x: Any) -> tuple[list, list, None]: - """ - Interprets an input data point x by splitting it into a list of tokens (e.g - a string into words or an image into super-pixels). - """ - return [], [], None - - @abstractmethod - def get_masked_inputs(self, tokens: list, binary_mask_matrix: list[list]) -> list: - return [] - - -class NeighborInterpretable(Interpretable, ABC): - @abstractmethod - def get_interpretation_neighbors(self, x: Any) -> tuple[list, dict]: - """ - Generates values similar to input to be used to interpret the significance of the input in the final output. - Parameters: - x: Input to interface - Returns: (neighbor_values, interpret_kwargs, interpret_by_removal) - neighbor_values: Neighboring values to input x to compute for interpretation - interpret_kwargs: Keyword arguments to be passed to get_interpretation_scores - """ - return [], {} - - -async def run_interpret(interface: Interface, raw_input: list): - """ - Runs the interpretation command for the machine learning model. Handles both the "default" out-of-the-box - interpretation for a certain set of UI component types, as well as the custom interpretation case. - Parameters: - raw_input: a list of raw inputs to apply the interpretation(s) on. - """ - if isinstance(interface.interpretation, list): # Either "default" or "shap" - processed_input = [ - input_component.preprocess(raw_input[i]) - for i, input_component in enumerate(interface.input_components) - ] - original_output = await interface.call_function(0, processed_input) - original_output = original_output["prediction"] - - if len(interface.output_components) == 1: - original_output = [original_output] - - scores, alternative_outputs = [], [] - - for i, (x, interp) in enumerate(zip(raw_input, interface.interpretation)): - if interp == "default": - input_component = interface.input_components[i] - neighbor_raw_input = list(raw_input) - if isinstance(input_component, TokenInterpretable): - tokens, neighbor_values, masks = input_component.tokenize(x) - interface_scores = [] - alternative_output = [] - for neighbor_input in neighbor_values: - neighbor_raw_input[i] = neighbor_input - processed_neighbor_input = [ - input_component.preprocess(neighbor_raw_input[i]) - for i, input_component in enumerate( - interface.input_components - ) - ] - - neighbor_output = await interface.call_function( - 0, processed_neighbor_input - ) - neighbor_output = neighbor_output["prediction"] - if len(interface.output_components) == 1: - neighbor_output = [neighbor_output] - processed_neighbor_output = [ - output_component.postprocess(neighbor_output[i]) - for i, output_component in enumerate( - interface.output_components - ) - ] - - alternative_output.append(processed_neighbor_output) - interface_scores.append( - quantify_difference_in_label( - interface, original_output, neighbor_output - ) - ) - alternative_outputs.append(alternative_output) - scores.append( - input_component.get_interpretation_scores( - raw_input[i], - neighbor_values, - interface_scores, - masks=masks, - tokens=tokens, - ) - ) - elif isinstance(input_component, NeighborInterpretable): - ( - neighbor_values, - interpret_kwargs, - ) = input_component.get_interpretation_neighbors( - x - ) # type: ignore - interface_scores = [] - alternative_output = [] - for neighbor_input in neighbor_values: - neighbor_raw_input[i] = neighbor_input - processed_neighbor_input = [ - input_component.preprocess(neighbor_raw_input[i]) - for i, input_component in enumerate( - interface.input_components - ) - ] - neighbor_output = await interface.call_function( - 0, processed_neighbor_input - ) - neighbor_output = neighbor_output["prediction"] - if len(interface.output_components) == 1: - neighbor_output = [neighbor_output] - processed_neighbor_output = [ - output_component.postprocess(neighbor_output[i]) - for i, output_component in enumerate( - interface.output_components - ) - ] - - alternative_output.append(processed_neighbor_output) - interface_scores.append( - quantify_difference_in_label( - interface, original_output, neighbor_output - ) - ) - alternative_outputs.append(alternative_output) - interface_scores = [-score for score in interface_scores] - scores.append( - input_component.get_interpretation_scores( - raw_input[i], - neighbor_values, - interface_scores, - **interpret_kwargs, - ) - ) - else: - raise ValueError( - f"Component {input_component} does not support interpretation" - ) - elif interp == "shap" or interp == "shapley": - try: - import shap # type: ignore - except (ImportError, ModuleNotFoundError) as err: - raise ValueError( - "The package `shap` is required for this interpretation method. Try: `pip install shap`" - ) from err - input_component = interface.input_components[i] - if not isinstance(input_component, TokenInterpretable): - raise ValueError( - f"Input component {input_component} does not support `shap` interpretation" - ) - - tokens, _, masks = input_component.tokenize(x) - - # construct a masked version of the input - def get_masked_prediction(binary_mask): - assert isinstance(input_component, TokenInterpretable) - masked_xs = input_component.get_masked_inputs(tokens, binary_mask) - preds = [] - for masked_x in masked_xs: - processed_masked_input = copy.deepcopy(processed_input) - processed_masked_input[i] = input_component.preprocess(masked_x) - new_output = client_utils.synchronize_async( - interface.call_function, 0, processed_masked_input - ) - new_output = new_output["prediction"] - if len(interface.output_components) == 1: - new_output = [new_output] - pred = get_regression_or_classification_value( - interface, original_output, new_output - ) - preds.append(pred) - return np.array(preds) - - num_total_segments = len(tokens) - explainer = shap.KernelExplainer( - get_masked_prediction, np.zeros((1, num_total_segments)) - ) - shap_values = explainer.shap_values( - np.ones((1, num_total_segments)), - nsamples=int(interface.num_shap * num_total_segments), - silent=True, - ) - if shap_values is None: - raise ValueError("SHAP values could not be calculated") - scores.append( - input_component.get_interpretation_scores( - raw_input[i], - None, - shap_values[0].tolist(), - masks=masks, - tokens=tokens, - ) - ) - alternative_outputs.append([]) - elif interp is None: - scores.append(None) - alternative_outputs.append([]) - else: - raise ValueError(f"Unknown interpretation method: {interp}") - return scores, alternative_outputs - elif interface.interpretation: # custom interpretation function - processed_input = [ - input_component.preprocess(raw_input[i]) - for i, input_component in enumerate(interface.input_components) - ] - interpreter = interface.interpretation - interpretation = interpreter(*processed_input) - if len(raw_input) == 1: - interpretation = [interpretation] - return interpretation, [] - else: - raise ValueError("No interpretation method specified.") - - -def diff(original: Any, perturbed: Any) -> int | float: - try: # try computing numerical difference - score = float(original) - float(perturbed) - except ValueError: # otherwise, look at strict difference in label - score = int(original != perturbed) - return score - - -def quantify_difference_in_label( - interface: Interface, original_output: list, perturbed_output: list -) -> int | float: - output_component = interface.output_components[0] - post_original_output = output_component.postprocess(original_output[0]) - post_perturbed_output = output_component.postprocess(perturbed_output[0]) - - if isinstance(output_component, components.Label): - original_label = post_original_output["label"] - perturbed_label = post_perturbed_output["label"] - - # Handle different return types of Label interface - if "confidences" in post_original_output: - original_confidence = original_output[0][original_label] - perturbed_confidence = perturbed_output[0][original_label] - score = original_confidence - perturbed_confidence - else: - score = diff(original_label, perturbed_label) - return score - - elif isinstance(output_component, components.Number): - score = diff(post_original_output, post_perturbed_output) - return score - - else: - raise ValueError( - f"This interpretation method doesn't support the Output component: {output_component}" - ) - - -def get_regression_or_classification_value( - interface: Interface, original_output: list, perturbed_output: list -) -> int | float: - """Used to combine regression/classification for Shap interpretation method.""" - output_component = interface.output_components[0] - post_original_output = output_component.postprocess(original_output[0]) - post_perturbed_output = output_component.postprocess(perturbed_output[0]) - - if isinstance(output_component, components.Label): - original_label = post_original_output["label"] - perturbed_label = post_perturbed_output["label"] - - # Handle different return types of Label interface - if "confidences" in post_original_output: - if math.isnan(perturbed_output[0][original_label]): - return 0 - return perturbed_output[0][original_label] - else: - score = diff( - perturbed_label, original_label - ) # Intentionally inverted order of arguments. - return score - - else: - raise ValueError( - f"This interpretation method doesn't support the Output component: {output_component}" - ) diff --git a/gradio/layouts.py b/gradio/layouts.py deleted file mode 100644 index f4d4374aca36..000000000000 --- a/gradio/layouts.py +++ /dev/null @@ -1,373 +0,0 @@ -from __future__ import annotations - -import warnings -from typing import TYPE_CHECKING, Literal - -from gradio_client.documentation import document, set_documentation_group - -from gradio.blocks import BlockContext, Updateable -from gradio.deprecation import warn_deprecation, warn_style_method_deprecation -from gradio.events import Changeable, Selectable - -if TYPE_CHECKING: - from gradio.blocks import Block - -set_documentation_group("layout") - - -@document() -class Row(Updateable, BlockContext): - """ - Row is a layout element within Blocks that renders all children horizontally. - Example: - with gr.Blocks() as demo: - with gr.Row(): - gr.Image("lion.jpg", scale=2) - gr.Image("tiger.jpg", scale=1) - demo.launch() - Guides: controlling-layout - """ - - def __init__( - self, - *, - variant: Literal["default", "panel", "compact"] = "default", - visible: bool = True, - elem_id: str | None = None, - elem_classes: list[str] | str | None = None, - equal_height: bool = True, - **kwargs, - ): - """ - Parameters: - variant: row type, 'default' (no background), 'panel' (gray background color and rounded corners), or 'compact' (rounded corners and no internal gap). - visible: If False, row will be hidden. - elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. - elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles. - equal_height: If True, makes every child element have equal height - """ - self.variant = variant - self.equal_height = equal_height - if variant == "compact": - self.allow_expected_parents = False - BlockContext.__init__( - self, visible=visible, elem_id=elem_id, elem_classes=elem_classes, **kwargs - ) - - @staticmethod - def update( - visible: bool | None = None, - ): - return { - "visible": visible, - "__type__": "update", - } - - def style( - self, - *, - equal_height: bool | None = None, - **kwargs, - ): - """ - Styles the Row. - Parameters: - equal_height: If True, makes every child element have equal height - """ - warn_style_method_deprecation() - if equal_height is not None: - self.equal_height = equal_height - return self - - -@document() -class Column(Updateable, BlockContext): - """ - Column is a layout element within Blocks that renders all children vertically. The widths of columns can be set through the `scale` and `min_width` parameters. - If a certain scale results in a column narrower than min_width, the min_width parameter will win. - Example: - with gr.Blocks() as demo: - with gr.Row(): - with gr.Column(scale=1): - text1 = gr.Textbox() - text2 = gr.Textbox() - with gr.Column(scale=4): - btn1 = gr.Button("Button 1") - btn2 = gr.Button("Button 2") - Guides: controlling-layout - """ - - def __init__( - self, - *, - scale: int = 1, - min_width: int = 320, - variant: Literal["default", "panel", "compact"] = "default", - visible: bool = True, - elem_id: str | None = None, - elem_classes: list[str] | str | None = None, - **kwargs, - ): - """ - Parameters: - scale: relative width compared to adjacent Columns. For example, if Column A has scale=2, and Column B has scale=1, A will be twice as wide as B. - min_width: minimum pixel width of Column, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in a column narrower than min_width, the min_width parameter will be respected first. - variant: column type, 'default' (no background), 'panel' (gray background color and rounded corners), or 'compact' (rounded corners and no internal gap). - visible: If False, column will be hidden. - elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. - elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles. - """ - if scale != round(scale): - warn_deprecation( - f"'scale' value should be an integer. Using {scale} will cause issues." - ) - - self.scale = scale - self.min_width = min_width - self.variant = variant - if variant == "compact": - self.allow_expected_parents = False - BlockContext.__init__( - self, visible=visible, elem_id=elem_id, elem_classes=elem_classes, **kwargs - ) - - @staticmethod - def update( - variant: str | None = None, - visible: bool | None = None, - ): - return { - "variant": variant, - "visible": visible, - "__type__": "update", - } - - -class Tabs(Updateable, BlockContext, Changeable, Selectable): - """ - Tabs is a layout element within Blocks that can contain multiple "Tab" Components. - """ - - def __init__( - self, - *, - selected: int | str | None = None, - visible: bool = True, - elem_id: str | None = None, - elem_classes: list[str] | str | None = None, - **kwargs, - ): - """ - Parameters: - selected: The currently selected tab. Must correspond to an id passed to the one of the child TabItems. Defaults to the first TabItem. - visible: If False, Tabs will be hidden. - elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. - elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles. - """ - BlockContext.__init__( - self, visible=visible, elem_id=elem_id, elem_classes=elem_classes, **kwargs - ) - Changeable.__init__(self) - Selectable.__init__(self) - self.selected = selected - - @staticmethod - def update( - selected: int | str | None = None, - ): - return { - "selected": selected, - "__type__": "update", - } - - -@document() -class Tab(Updateable, BlockContext, Selectable): - """ - Tab (or its alias TabItem) is a layout element. Components defined within the Tab will be visible when this tab is selected tab. - Example: - with gr.Blocks() as demo: - with gr.Tab("Lion"): - gr.Image("lion.jpg") - gr.Button("New Lion") - with gr.Tab("Tiger"): - gr.Image("tiger.jpg") - gr.Button("New Tiger") - Guides: controlling-layout - """ - - def __init__( - self, - label: str, - *, - id: int | str | None = None, - elem_id: str | None = None, - elem_classes: list[str] | str | None = None, - **kwargs, - ): - """ - Parameters: - label: The visual label for the tab - id: An optional identifier for the tab, required if you wish to control the selected tab from a predict function. - elem_id: An optional string that is assigned as the id of the
containing the contents of the Tab layout. The same string followed by "-button" is attached to the Tab button. Can be used for targeting CSS styles. - elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles. - """ - BlockContext.__init__( - self, elem_id=elem_id, elem_classes=elem_classes, **kwargs - ) - Selectable.__init__(self) - self.label = label - self.id = id - - def get_expected_parent(self) -> type[Tabs]: - return Tabs - - def get_block_name(self): - return "tabitem" - - -TabItem = Tab - - -@document() -class Group(Updateable, BlockContext): - """ - Group is a layout element within Blocks which groups together children so that - they do not have any padding or margin between them. - Example: - with gr.Group(): - gr.Textbox(label="First") - gr.Textbox(label="Last") - """ - - def __init__( - self, - *, - visible: bool = True, - elem_id: str | None = None, - elem_classes: list[str] | str | None = None, - **kwargs, - ): - """ - Parameters: - visible: If False, group will be hidden. - elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. - elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles. - """ - BlockContext.__init__( - self, visible=visible, elem_id=elem_id, elem_classes=elem_classes, **kwargs - ) - - @staticmethod - def update( - visible: bool | None = None, - ): - return { - "visible": visible, - "__type__": "update", - } - - -class Box(Updateable, BlockContext): - """ - DEPRECATED. - Box is a a layout element which places children in a box with rounded corners and - some padding around them. - Example: - with gr.Box(): - gr.Textbox(label="First") - gr.Textbox(label="Last") - """ - - def __init__( - self, - *, - visible: bool = True, - elem_id: str | None = None, - **kwargs, - ): - """ - Parameters: - visible: If False, box will be hidden. - elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. - """ - warnings.warn("gr.Box is deprecated. Use gr.Group instead.", DeprecationWarning) - BlockContext.__init__(self, visible=visible, elem_id=elem_id, **kwargs) - - @staticmethod - def update( - visible: bool | None = None, - ): - return { - "visible": visible, - "__type__": "update", - } - - def style(self, **kwargs): - warn_style_method_deprecation() - return self - - -class Form(Updateable, BlockContext): - def __init__(self, *, scale: int = 0, min_width: int = 0, **kwargs): - """ - Parameters: - scale: relative width compared to adjacent Columns. For example, if Column A has scale=2, and Column B has scale=1, A will be twice as wide as B. - min_width: minimum pixel width of Column, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in a column narrower than min_width, the min_width parameter will be respected first. - """ - self.scale = scale - self.min_width = min_width - BlockContext.__init__(self, **kwargs) - - def add_child(self, child: Block): - if isinstance(self.parent, Row): - scale = getattr(child, "scale", None) - self.scale += 1 if scale is None else scale - self.min_width += getattr(child, "min_width", 0) or 0 - BlockContext.add_child(self, child) - - -@document() -class Accordion(Updateable, BlockContext): - """ - Accordion is a layout element which can be toggled to show/hide the contained content. - Example: - with gr.Accordion("See Details"): - gr.Markdown("lorem ipsum") - """ - - def __init__( - self, - label, - *, - open: bool = True, - visible: bool = True, - elem_id: str | None = None, - elem_classes: list[str] | str | None = None, - **kwargs, - ): - """ - Parameters: - label: name of accordion section. - open: if True, accordion is open by default. - elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. - elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles. - """ - self.label = label - self.open = open - BlockContext.__init__( - self, visible=visible, elem_id=elem_id, elem_classes=elem_classes, **kwargs - ) - - @staticmethod - def update( - open: bool | None = None, - label: str | None = None, - visible: bool | None = None, - ): - return { - "visible": visible, - "label": label, - "open": open, - "__type__": "update", - } diff --git a/gradio/layouts/__init__.py b/gradio/layouts/__init__.py new file mode 100644 index 000000000000..d0513b93c6aa --- /dev/null +++ b/gradio/layouts/__init__.py @@ -0,0 +1,17 @@ +from .accordion import Accordion +from .column import Column +from .form import Form +from .group import Group +from .row import Row +from .tabs import Tab, TabItem, Tabs + +__all__ = [ + "Accordion", + "Column", + "Form", + "Row", + "Group", + "Tabs", + "Tab", + "TabItem", +] diff --git a/gradio/layouts/accordion.py b/gradio/layouts/accordion.py new file mode 100644 index 000000000000..3d2fccb2cca0 --- /dev/null +++ b/gradio/layouts/accordion.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from gradio_client.documentation import document, set_documentation_group + +from gradio.blocks import BlockContext +from gradio.component_meta import ComponentMeta + +if TYPE_CHECKING: + pass + +set_documentation_group("layout") + + +@document() +class Accordion(BlockContext, metaclass=ComponentMeta): + """ + Accordion is a layout element which can be toggled to show/hide the contained content. + Example: + with gr.Accordion("See Details"): + gr.Markdown("lorem ipsum") + """ + + EVENTS = [] + + def __init__( + self, + label: str | None = None, + *, + open: bool = True, + visible: bool = True, + elem_id: str | None = None, + elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, + ): + """ + Parameters: + label: name of accordion section. + open: if True, accordion is open by default. + elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. + elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, this layout will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this layout belongs to. Used in `gr.load()`. Should not be set manually. + """ + self.label = label + self.open = open + BlockContext.__init__( + self, + visible=visible, + elem_id=elem_id, + elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, + ) diff --git a/gradio/layouts/column.py b/gradio/layouts/column.py new file mode 100644 index 000000000000..c3f3b90d9bb8 --- /dev/null +++ b/gradio/layouts/column.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +import warnings +from typing import Literal + +from gradio_client.documentation import document, set_documentation_group + +from gradio.blocks import BlockContext +from gradio.component_meta import ComponentMeta + +set_documentation_group("layout") + + +@document() +class Column(BlockContext, metaclass=ComponentMeta): + """ + Column is a layout element within Blocks that renders all children vertically. The widths of columns can be set through the `scale` and `min_width` parameters. + If a certain scale results in a column narrower than min_width, the min_width parameter will win. + Example: + with gr.Blocks() as demo: + with gr.Row(): + with gr.Column(scale=1): + text1 = gr.Textbox() + text2 = gr.Textbox() + with gr.Column(scale=4): + btn1 = gr.Button("Button 1") + btn2 = gr.Button("Button 2") + Guides: controlling-layout + """ + + EVENTS = ["baz"] + + def __init__( + self, + *, + scale: int = 1, + min_width: int = 320, + variant: Literal["default", "panel", "compact"] = "default", + visible: bool = True, + elem_id: str | None = None, + elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, + ): + """ + Parameters: + scale: relative width compared to adjacent Columns. For example, if Column A has scale=2, and Column B has scale=1, A will be twice as wide as B. + min_width: minimum pixel width of Column, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in a column narrower than min_width, the min_width parameter will be respected first. + variant: column type, 'default' (no background), 'panel' (gray background color and rounded corners), or 'compact' (rounded corners and no internal gap). + visible: If False, column will be hidden. + elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. + elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this component belongs to. Used in `gr.load()`. Should not be set manually. + """ + if scale != round(scale): + warnings.warn( + f"'scale' value should be an integer. Using {scale} will cause issues." + ) + + self.scale = scale + self.min_width = min_width + self.variant = variant + if variant == "compact": + self.allow_expected_parents = False + BlockContext.__init__( + self, + visible=visible, + elem_id=elem_id, + elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, + ) diff --git a/gradio/layouts/form.py b/gradio/layouts/form.py new file mode 100644 index 000000000000..455b069c773b --- /dev/null +++ b/gradio/layouts/form.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from gradio_client.documentation import set_documentation_group + +from gradio.blocks import BlockContext +from gradio.component_meta import ComponentMeta +from gradio.layouts.row import Row + +if TYPE_CHECKING: + from gradio.blocks import Block + +set_documentation_group("layout") + + +class Form(BlockContext, metaclass=ComponentMeta): + EVENTS = [] + + def __init__( + self, + *, + scale: int = 0, + min_width: int = 0, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, + ): + """ + Parameters: + scale: relative width compared to adjacent Columns. For example, if Column A has scale=2, and Column B has scale=1, A will be twice as wide as B. + min_width: minimum pixel width of Column, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in a column narrower than min_width, the min_width parameter will be respected first. + render: If False, this layout will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this layout belongs to. Used in `gr.load()`. Should not be set manually. + """ + self.scale = scale + self.min_width = min_width + BlockContext.__init__( + self, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, + ) + + def add_child(self, child: Block): + if isinstance(self.parent, Row): + scale = getattr(child, "scale", None) + self.scale += 1 if scale is None else scale + self.min_width += getattr(child, "min_width", 0) or 0 + BlockContext.add_child(self, child) diff --git a/gradio/layouts/group.py b/gradio/layouts/group.py new file mode 100644 index 000000000000..377aa490e0d8 --- /dev/null +++ b/gradio/layouts/group.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from gradio_client.documentation import document, set_documentation_group + +from gradio.blocks import BlockContext +from gradio.component_meta import ComponentMeta + +set_documentation_group("layout") + + +@document() +class Group(BlockContext, metaclass=ComponentMeta): + """ + Group is a layout element within Blocks which groups together children so that + they do not have any padding or margin between them. + Example: + with gr.Group(): + gr.Textbox(label="First") + gr.Textbox(label="Last") + """ + + EVENTS = [] + + def __init__( + self, + *, + visible: bool = True, + elem_id: str | None = None, + elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, + ): + """ + Parameters: + visible: If False, group will be hidden. + elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. + elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, this layout will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this layout belongs to. Used in `gr.load()`. Should not be set manually. + """ + BlockContext.__init__( + self, + visible=visible, + elem_id=elem_id, + elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, + ) diff --git a/gradio/layouts/row.py b/gradio/layouts/row.py new file mode 100644 index 000000000000..b2319052cc82 --- /dev/null +++ b/gradio/layouts/row.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +from typing import Literal + +from gradio_client.documentation import document, set_documentation_group + +from gradio.blocks import BlockContext +from gradio.component_meta import ComponentMeta + +set_documentation_group("layout") + + +@document() +class Row(BlockContext, metaclass=ComponentMeta): + """ + Row is a layout element within Blocks that renders all children horizontally. + Example: + with gr.Blocks() as demo: + with gr.Row(): + gr.Image("lion.jpg", scale=2) + gr.Image("tiger.jpg", scale=1) + demo.launch() + Guides: controlling-layout + """ + + EVENTS = [] + + def __init__( + self, + *, + variant: Literal["default", "panel", "compact"] = "default", + visible: bool = True, + elem_id: str | None = None, + elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, + equal_height: bool = True, + ): + """ + Parameters: + variant: row type, 'default' (no background), 'panel' (gray background color and rounded corners), or 'compact' (rounded corners and no internal gap). + visible: If False, row will be hidden. + elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. + elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, this layout will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this layout belongs to. Used in `gr.load()`. Should not be set manually. + equal_height: If True, makes every child element have equal height + """ + self.variant = variant + self.equal_height = equal_height + if variant == "compact": + self.allow_expected_parents = False + BlockContext.__init__( + self, + visible=visible, + elem_id=elem_id, + elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, + ) + + @staticmethod + def update( + visible: bool | None = None, + ): + return { + "visible": visible, + "__type__": "update", + } diff --git a/gradio/layouts/tabs.py b/gradio/layouts/tabs.py new file mode 100644 index 000000000000..47ec34c62f4d --- /dev/null +++ b/gradio/layouts/tabs.py @@ -0,0 +1,104 @@ +from __future__ import annotations + +from gradio_client.documentation import document, set_documentation_group + +from gradio.blocks import BlockContext +from gradio.component_meta import ComponentMeta +from gradio.events import Events + +set_documentation_group("layout") + + +class Tabs(BlockContext, metaclass=ComponentMeta): + """ + Tabs is a layout element within Blocks that can contain multiple "Tab" Components. + """ + + EVENTS = [Events.change, Events.select] + + def __init__( + self, + *, + selected: int | str | None = None, + visible: bool = True, + elem_id: str | None = None, + elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, + ): + """ + Parameters: + selected: The currently selected tab. Must correspond to an id passed to the one of the child TabItems. Defaults to the first TabItem. + visible: If False, Tabs will be hidden. + elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. + elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, this layout will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + root_url: The remote URL that of the Gradio app that this layout belongs to. Used in `gr.load()`. Should not be set manually. + """ + BlockContext.__init__( + self, + visible=visible, + elem_id=elem_id, + elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, + ) + self.selected = selected + + +@document() +class Tab(BlockContext, metaclass=ComponentMeta): + """ + Tab (or its alias TabItem) is a layout element. Components defined within the Tab will be visible when this tab is selected tab. + Example: + with gr.Blocks() as demo: + with gr.Tab("Lion"): + gr.Image("lion.jpg") + gr.Button("New Lion") + with gr.Tab("Tiger"): + gr.Image("tiger.jpg") + gr.Button("New Tiger") + Guides: controlling-layout + """ + + EVENTS = [Events.select] + + def __init__( + self, + label: str | None = None, + *, + id: int | str | None = None, + elem_id: str | None = None, + elem_classes: list[str] | str | None = None, + render: bool = True, + root_url: str | None = None, + _skip_init_processing: bool = False, + ): + """ + Parameters: + label: The visual label for the tab + id: An optional identifier for the tab, required if you wish to control the selected tab from a predict function. + elem_id: An optional string that is assigned as the id of the
containing the contents of the Tab layout. The same string followed by "-button" is attached to the Tab button. Can be used for targeting CSS styles. + elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles. + """ + BlockContext.__init__( + self, + elem_id=elem_id, + elem_classes=elem_classes, + render=render, + root_url=root_url, + _skip_init_processing=_skip_init_processing, + ) + self.label = label + self.id = id + + def get_expected_parent(self) -> type[Tabs]: + return Tabs + + def get_block_name(self): + return "tabitem" + + +TabItem = Tab diff --git a/gradio/oauth.py b/gradio/oauth.py index ccb63a79b758..78053a9dee89 100644 --- a/gradio/oauth.py +++ b/gradio/oauth.py @@ -86,12 +86,12 @@ async def oauth_login(request: fastapi.Request): if ".hf.space" in redirect_uri: # In Space, FastAPI redirect as http but we want https redirect_uri = redirect_uri.replace("http://", "https://") - return await oauth.huggingface.authorize_redirect(request, redirect_uri) + return await oauth.huggingface.authorize_redirect(request, redirect_uri) # type: ignore @app.get("/login/callback") async def oauth_redirect_callback(request: fastapi.Request) -> RedirectResponse: """Endpoint that handles the OAuth callback.""" - token = await oauth.huggingface.authorize_access_token(request) + token = await oauth.huggingface.authorize_access_token(request) # type: ignore request.session["oauth_profile"] = token["userinfo"] request.session["oauth_token"] = token return RedirectResponse("/") diff --git a/gradio/outputs.py b/gradio/outputs.py deleted file mode 100644 index b6d2d20c8f5e..000000000000 --- a/gradio/outputs.py +++ /dev/null @@ -1,313 +0,0 @@ -# type: ignore -""" -This module defines various classes that can serve as the `output` to an interface. Each class must inherit from -`OutputComponent`, and each class must define a path to its template. All of the subclasses of `OutputComponent` are -automatically added to a registry, which allows them to be easily referenced in other parts of the code. -""" - -from __future__ import annotations - -from typing import Optional - -from gradio import components -from gradio.deprecation import warn_deprecation - - -def warn_outputs_deprecation(): - warn_deprecation( - "Usage of gradio.outputs is deprecated, and will not be supported in the future, " - "please import your components from gradio.components", - ) - - -class Textbox(components.Textbox): - def __init__( - self, - type: str = "text", - label: Optional[str] = None, - ): - warn_outputs_deprecation() - super().__init__(label=label, type=type) - - -class Image(components.Image): - """ - Component displays an output image. - Output type: Union[numpy.array, PIL.Image, str, matplotlib.pyplot, Tuple[Union[numpy.array, PIL.Image, str], List[Tuple[str, float, float, float, float]]]] - """ - - def __init__( - self, type: str = "auto", plot: bool = False, label: Optional[str] = None - ): - """ - Parameters: - type (str): Type of value to be passed to component. "numpy" expects a numpy array with shape (height, width, 3), "pil" expects a PIL image object, "file" expects a file path to the saved image or a remote URL, "plot" expects a matplotlib.pyplot object, "auto" detects return type. - plot (bool): DEPRECATED. Whether to expect a plot to be returned by the function. - label (str): component name in interface. - """ - warn_outputs_deprecation() - if plot: - type = "plot" - super().__init__(type=type, label=label) - - -class Video(components.Video): - """ - Used for video output. - Output type: filepath - """ - - def __init__(self, type: Optional[str] = None, label: Optional[str] = None): - """ - Parameters: - type (str): Type of video format to be passed to component, such as 'avi' or 'mp4'. Use 'mp4' to ensure browser playability. If set to None, video will keep returned format. - label (str): component name in interface. - """ - warn_outputs_deprecation() - super().__init__(format=type, label=label) - - -class Audio(components.Audio): - """ - Creates an audio player that plays the output audio. - Output type: Union[Tuple[int, numpy.array], str] - """ - - def __init__(self, type: str = "auto", label: Optional[str] = None): - """ - Parameters: - type (str): Type of value to be passed to component. "numpy" returns a 2-set tuple with an integer sample_rate and the data as 16-bit int numpy.array of shape (samples, 2), "file" returns a temporary file path to the saved wav audio file, "auto" detects return type. - label (str): component name in interface. - """ - warn_outputs_deprecation() - super().__init__(type=type, label=label) - - -class File(components.File): - """ - Used for file output. - Output type: Union[file-like, str] - """ - - def __init__(self, label: Optional[str] = None): - """ - Parameters: - label (str): component name in interface. - """ - warn_outputs_deprecation() - super().__init__(label=label) - - -class Dataframe(components.Dataframe): - """ - Component displays 2D output through a spreadsheet interface. - Output type: Union[pandas.DataFrame, numpy.array, List[Union[str, float]], List[List[Union[str, float]]]] - """ - - def __init__( - self, - headers: Optional[list[str]] = None, - max_rows: Optional[int] = 20, - max_cols: Optional[int] = None, - overflow_row_behaviour: str = "paginate", - type: str = "auto", - label: Optional[str] = None, - ): - """ - Parameters: - headers (List[str]): Header names to dataframe. Only applicable if type is "numpy" or "array". - max_rows (int): Maximum number of rows to display at once. Set to None for infinite. - max_cols (int): Maximum number of columns to display at once. Set to None for infinite. - overflow_row_behaviour (str): If set to "paginate", will create pages for overflow rows. If set to "show_ends", will show initial and final rows and truncate middle rows. - type (str): Type of value to be passed to component. "pandas" for pandas dataframe, "numpy" for numpy array, or "array" for Python array, "auto" detects return type. - label (str): component name in interface. - """ - warn_outputs_deprecation() - super().__init__( - headers=headers, - type=type, - label=label, - max_rows=max_rows, - max_cols=max_cols, - overflow_row_behaviour=overflow_row_behaviour, - ) - - -class Timeseries(components.Timeseries): - """ - Component accepts pandas.DataFrame. - Output type: pandas.DataFrame - """ - - def __init__( - self, x: str = None, y: str | list[str] = None, label: Optional[str] = None - ): - """ - Parameters: - x (str): Column name of x (time) series. None if csv has no headers, in which case first column is x series. - y (Union[str, List[str]]): Column name of y series, or list of column names if multiple series. None if csv has no headers, in which case every column after first is a y series. - label (str): component name in interface. - """ - warn_outputs_deprecation() - super().__init__(x=x, y=y, label=label) - - -class State(components.State): - """ - Special hidden component that stores state across runs of the interface. - Output type: Any - """ - - def __init__(self, label: Optional[str] = None): - """ - Parameters: - label (str): component name in interface (not used). - """ - warn_outputs_deprecation() - super().__init__(label=label) - - -class Label(components.Label): - """ - Component outputs a classification label, along with confidence scores of top categories if provided. Confidence scores are represented as a dictionary mapping labels to scores between 0 and 1. - Output type: Union[Dict[str, float], str, int, float] - """ - - def __init__( - self, - num_top_classes: Optional[int] = None, - type: str = "auto", - label: Optional[str] = None, - ): - """ - Parameters: - num_top_classes (int): number of most confident classes to show. - type (str): Type of value to be passed to component. "value" expects a single out label, "confidences" expects a dictionary mapping labels to confidence scores, "auto" detects return type. - label (str): component name in interface. - """ - warn_outputs_deprecation() - super().__init__(num_top_classes=num_top_classes, type=type, label=label) - - -class KeyValues: - """ - Component displays a table representing values for multiple fields. - Output type: Union[Dict, List[Tuple[str, Union[str, int, float]]]] - """ - - def __init__(self, value: str = " ", *, label: Optional[str] = None, **kwargs): - """ - Parameters: - value (str): IGNORED - label (str): component name in interface. - """ - raise DeprecationWarning( - "The KeyValues component is deprecated. Please use the DataFrame or JSON " - "components instead." - ) - - -class HighlightedText(components.HighlightedText): - """ - Component creates text that contains spans that are highlighted by category or numerical value. - Output is represent as a list of Tuple pairs, where the first element represents the span of text represented by the tuple, and the second element represents the category or value of the text. - Output type: List[Tuple[str, Union[float, str]]] - """ - - def __init__( - self, - color_map: dict[str, str] = None, - label: Optional[str] = None, - show_legend: bool = False, - ): - """ - Parameters: - color_map (Dict[str, str]): Map between category and respective colors - label (str): component name in interface. - show_legend (bool): whether to show span categories in a separate legend or inline. - """ - warn_outputs_deprecation() - super().__init__(color_map=color_map, label=label, show_legend=show_legend) - - -class JSON(components.JSON): - """ - Used for JSON output. Expects a JSON string or a Python object that is JSON serializable. - Output type: Union[str, Any] - """ - - def __init__(self, label: Optional[str] = None): - """ - Parameters: - label (str): component name in interface. - """ - warn_outputs_deprecation() - super().__init__(label=label) - - -class HTML(components.HTML): - """ - Used for HTML output. Expects an HTML valid string. - Output type: str - """ - - def __init__(self, label: Optional[str] = None): - """ - Parameters: - label (str): component name in interface. - """ - super().__init__(label=label) - - -class Carousel(components.Carousel): - """ - Component displays a set of output components that can be scrolled through. - """ - - def __init__( - self, - components: components.Component | list[components.Component], - label: Optional[str] = None, - ): - """ - Parameters: - components (Union[List[Component], Component]): Classes of component(s) that will be scrolled through. - label (str): component name in interface. - """ - warn_outputs_deprecation() - super().__init__(components=components, label=label) - - -class Chatbot(components.Chatbot): - """ - Component displays a chatbot output showing both user submitted messages and responses - Output type: List[Tuple[str, str]] - """ - - def __init__(self, label: Optional[str] = None): - """ - Parameters: - label (str): component name in interface (not used). - """ - warn_outputs_deprecation() - super().__init__(label=label) - - -class Image3D(components.Model3D): - """ - Used for 3D image model output. - Input type: File object of type (.obj, glb, or .gltf) - """ - - def __init__( - self, - clear_color=None, - label: Optional[str] = None, - ): - """ - Parameters: - label (str): component name in interface. - optional (bool): If True, the interface can be submitted with no uploaded image, in which case the input value is None. - """ - warn_outputs_deprecation() - super().__init__(clear_color=clear_color, label=label) diff --git a/gradio/package.json b/gradio/package.json index 75912207b7e5..b509419f35b5 100644 --- a/gradio/package.json +++ b/gradio/package.json @@ -1,6 +1,6 @@ { "name": "gradio", - "version": "3.49.0", + "version": "3.45.0-beta.13", "description": "", "python": "true" } diff --git a/gradio/processing_utils.py b/gradio/processing_utils.py index d2fd6292cffd..5efeef6bc206 100644 --- a/gradio/processing_utils.py +++ b/gradio/processing_utils.py @@ -1,25 +1,28 @@ from __future__ import annotations import base64 +import hashlib import json import logging import os import shutil import subprocess import tempfile +import urllib.request import warnings from io import BytesIO from pathlib import Path +from typing import TYPE_CHECKING, Any, Literal import numpy as np +import requests from gradio_client import utils as client_utils from PIL import Image, ImageOps, PngImagePlugin from gradio import wasm_utils +from gradio.data_classes import FileData -if not wasm_utils.IS_WASM: - # TODO: Support ffmpeg on Wasm - from ffmpy import FFmpeg, FFprobe, FFRuntimeError +from .utils import abspath, is_in_or_equal with warnings.catch_warnings(): warnings.simplefilter("ignore") # Ignore pydub warning if ffmpeg is not installed @@ -27,6 +30,9 @@ log = logging.getLogger(__name__) +if TYPE_CHECKING: + from gradio.components.base import Component + ######################### # GENERAL ######################### @@ -108,6 +114,179 @@ def encode_array_to_base64(image_array): return "data:image/png;base64," + base64_str +def hash_file(file_path: str | Path, chunk_num_blocks: int = 128) -> str: + sha1 = hashlib.sha1() + with open(file_path, "rb") as f: + for chunk in iter(lambda: f.read(chunk_num_blocks * sha1.block_size), b""): + sha1.update(chunk) + return sha1.hexdigest() + + +def hash_url(url: str, chunk_num_blocks: int = 128) -> str: + sha1 = hashlib.sha1() + remote = urllib.request.urlopen(url) + max_file_size = 100 * 1024 * 1024 # 100MB + total_read = 0 + while True: + data = remote.read(chunk_num_blocks * sha1.block_size) + total_read += chunk_num_blocks * sha1.block_size + if not data or total_read > max_file_size: + break + sha1.update(data) + return sha1.hexdigest() + + +def hash_bytes(bytes: bytes): + sha1 = hashlib.sha1() + sha1.update(bytes) + return sha1.hexdigest() + + +def hash_base64(base64_encoding: str, chunk_num_blocks: int = 128) -> str: + sha1 = hashlib.sha1() + for i in range(0, len(base64_encoding), chunk_num_blocks * sha1.block_size): + data = base64_encoding[i : i + chunk_num_blocks * sha1.block_size] + sha1.update(data.encode("utf-8")) + return sha1.hexdigest() + + +def save_pil_to_cache( + img: Image.Image, cache_dir: str, format: Literal["png", "jpg"] = "png" +) -> str: + bytes_data = encode_pil_to_bytes(img, format) + temp_dir = Path(cache_dir) / hash_bytes(bytes_data) + temp_dir.mkdir(exist_ok=True, parents=True) + filename = str((temp_dir / f"image.{format}").resolve()) + img.save(filename, pnginfo=get_pil_metadata(img)) + return filename + + +def save_img_array_to_cache( + arr: np.ndarray, cache_dir: str, format: Literal["png", "jpg"] = "png" +) -> str: + pil_image = Image.fromarray(_convert(arr, np.uint8, force_copy=False)) + return save_pil_to_cache(pil_image, cache_dir, format=format) + + +def save_audio_to_cache( + data: np.ndarray, sample_rate: int, format: str, cache_dir: str +) -> str: + temp_dir = Path(cache_dir) / hash_bytes(data.tobytes()) + temp_dir.mkdir(exist_ok=True, parents=True) + filename = str((temp_dir / f"audio.{format}").resolve()) + audio_to_file(sample_rate, data, filename, format=format) + return filename + + +def save_bytes_to_cache(data: bytes, file_name: str, cache_dir: str) -> str: + path = Path(cache_dir) / hash_bytes(data) + path.mkdir(exist_ok=True, parents=True) + path = path / Path(file_name).name + path.write_bytes(data) + return str(path.resolve()) + + +def save_file_to_cache(file_path: str | Path, cache_dir: str) -> str: + """Returns a temporary file path for a copy of the given file path if it does + not already exist. Otherwise returns the path to the existing temp file.""" + temp_dir = hash_file(file_path) + temp_dir = Path(cache_dir) / temp_dir + temp_dir.mkdir(exist_ok=True, parents=True) + + name = client_utils.strip_invalid_filename_characters(Path(file_path).name) + full_temp_file_path = str(abspath(temp_dir / name)) + + if not Path(full_temp_file_path).exists(): + shutil.copy2(file_path, full_temp_file_path) + + return full_temp_file_path + + +def save_url_to_cache(url: str, cache_dir: str) -> str: + """Downloads a file and makes a temporary file path for a copy if does not already + exist. Otherwise returns the path to the existing temp file.""" + temp_dir = hash_url(url) + temp_dir = Path(cache_dir) / temp_dir + temp_dir.mkdir(exist_ok=True, parents=True) + + name = client_utils.strip_invalid_filename_characters(Path(url).name) + full_temp_file_path = str(abspath(temp_dir / name)) + + if not Path(full_temp_file_path).exists(): + with requests.get(url, stream=True) as r, open(full_temp_file_path, "wb") as f: + shutil.copyfileobj(r.raw, f) + + return full_temp_file_path + + +def save_base64_to_cache( + base64_encoding: str, cache_dir: str, file_name: str | None = None +) -> str: + """Converts a base64 encoding to a file and returns the path to the file if + the file doesn't already exist. Otherwise returns the path to the existing file. + """ + temp_dir = hash_base64(base64_encoding) + temp_dir = Path(cache_dir) / temp_dir + temp_dir.mkdir(exist_ok=True, parents=True) + + guess_extension = client_utils.get_extension(base64_encoding) + if file_name: + file_name = client_utils.strip_invalid_filename_characters(file_name) + elif guess_extension: + file_name = f"file.{guess_extension}" + else: + file_name = "file" + + full_temp_file_path = str(abspath(temp_dir / file_name)) # type: ignore + + if not Path(full_temp_file_path).exists(): + data, _ = client_utils.decode_base64_to_binary(base64_encoding) + with open(full_temp_file_path, "wb") as fb: + fb.write(data) + + return full_temp_file_path + + +def move_files_to_cache(data: Any, block: Component): + """Move files to cache and replace the file path with the cache path. + + Runs after postprocess and before preprocess. + + Args: + data: The input or output data for a component. + block: The component + """ + + def _move_to_cache(d: dict): + payload = FileData(**d) + if payload.name and ( + client_utils.is_http_url_like(payload.name) + or not is_in_or_equal(payload.name, block.GRADIO_CACHE) + ): + if payload.is_file: + if client_utils.is_http_url_like(payload.name): + temp_file_path = save_url_to_cache( + payload.name, cache_dir=block.GRADIO_CACHE + ) + else: + temp_file_path = save_file_to_cache( + payload.name, cache_dir=block.GRADIO_CACHE + ) + else: + assert payload.data + temp_file_path = save_base64_to_cache( + payload.data, + file_name=payload.name, + cache_dir=block.GRADIO_CACHE, + ) + payload.is_file = True + block.temp_files.add(temp_file_path) + payload.name = temp_file_path + return payload.model_dump() + + return client_utils.traverse(data, _move_to_cache, client_utils.is_file_obj) + + def resize_and_crop(img, size, crop_type="center"): """ Resize and crop an image to fit the specified size. @@ -505,6 +684,8 @@ def video_is_playable(video_filepath: str) -> bool: .webm -> vp9 .ogg -> theora """ + from ffmpy import FFprobe, FFRuntimeError + try: container = Path(video_filepath).suffix.lower() probe = FFprobe( @@ -526,6 +707,8 @@ def video_is_playable(video_filepath: str) -> bool: def convert_video_to_playable_mp4(video_path: str) -> str: """Convert the video to mp4. If something goes wrong return the original video.""" + from ffmpy import FFmpeg, FFRuntimeError + try: with tempfile.NamedTemporaryFile(delete=False) as tmp_file: output_path = Path(video_path).with_suffix(".mp4") diff --git a/gradio/route_utils.py b/gradio/route_utils.py index a2af62ed7c0e..1e17e6a65867 100644 --- a/gradio/route_utils.py +++ b/gradio/route_utils.py @@ -1,11 +1,17 @@ from __future__ import annotations +import hashlib import json -from typing import TYPE_CHECKING, Optional, Union +from tempfile import NamedTemporaryFile, _TemporaryFileWrapper +from typing import TYPE_CHECKING, AsyncGenerator, BinaryIO, List, Optional, Tuple, Union import fastapi import httpx +import multipart from gradio_client.documentation import document, set_documentation_group +from multipart.multipart import parse_options_header +from starlette.datastructures import FormData, Headers, UploadFile +from starlette.formparsers import MultiPartException, MultipartPart from gradio import utils from gradio.data_classes import PredictBody @@ -264,3 +270,198 @@ def strip_url(orig_url: str) -> str: stripped_url = parsed_url.copy_with(query=None) stripped_url = str(stripped_url) return stripped_url.rstrip("/") + + +def _user_safe_decode(src: bytes, codec: str) -> str: + try: + return src.decode(codec) + except (UnicodeDecodeError, LookupError): + return src.decode("latin-1") + + +class GradioUploadFile(UploadFile): + """UploadFile with a sha attribute.""" + + def __init__( + self, + file: BinaryIO, + *, + size: int | None = None, + filename: str | None = None, + headers: Headers | None = None, + ) -> None: + super().__init__(file, size=size, filename=filename, headers=headers) + self.sha = hashlib.sha1() + + +class GradioMultiPartParser: + """Vendored from starlette.MultipartParser. + + Thanks starlette! + + Made the following modifications + - Use GradioUploadFile instead of UploadFile + - Use NamedTemporaryFile instead of SpooledTemporaryFile + - Compute hash of data as the request is streamed + + """ + + max_file_size = 1024 * 1024 + + def __init__( + self, + headers: Headers, + stream: AsyncGenerator[bytes, None], + *, + max_files: Union[int, float] = 1000, + max_fields: Union[int, float] = 1000, + ) -> None: + assert ( + multipart is not None + ), "The `python-multipart` library must be installed to use form parsing." + self.headers = headers + self.stream = stream + self.max_files = max_files + self.max_fields = max_fields + self.items: List[Tuple[str, Union[str, UploadFile]]] = [] + self._current_files = 0 + self._current_fields = 0 + self._current_partial_header_name: bytes = b"" + self._current_partial_header_value: bytes = b"" + self._current_part = MultipartPart() + self._charset = "" + self._file_parts_to_write: List[Tuple[MultipartPart, bytes]] = [] + self._file_parts_to_finish: List[MultipartPart] = [] + self._files_to_close_on_error: List[_TemporaryFileWrapper] = [] + + def on_part_begin(self) -> None: + self._current_part = MultipartPart() + + def on_part_data(self, data: bytes, start: int, end: int) -> None: + message_bytes = data[start:end] + if self._current_part.file is None: + self._current_part.data += message_bytes + else: + self._file_parts_to_write.append((self._current_part, message_bytes)) + + def on_part_end(self) -> None: + if self._current_part.file is None: + self.items.append( + ( + self._current_part.field_name, + _user_safe_decode(self._current_part.data, self._charset), + ) + ) + else: + self._file_parts_to_finish.append(self._current_part) + # The file can be added to the items right now even though it's not + # finished yet, because it will be finished in the `parse()` method, before + # self.items is used in the return value. + self.items.append((self._current_part.field_name, self._current_part.file)) + + def on_header_field(self, data: bytes, start: int, end: int) -> None: + self._current_partial_header_name += data[start:end] + + def on_header_value(self, data: bytes, start: int, end: int) -> None: + self._current_partial_header_value += data[start:end] + + def on_header_end(self) -> None: + field = self._current_partial_header_name.lower() + if field == b"content-disposition": + self._current_part.content_disposition = self._current_partial_header_value + self._current_part.item_headers.append( + (field, self._current_partial_header_value) + ) + self._current_partial_header_name = b"" + self._current_partial_header_value = b"" + + def on_headers_finished(self) -> None: + disposition, options = parse_options_header( + self._current_part.content_disposition + ) + try: + self._current_part.field_name = _user_safe_decode( + options[b"name"], self._charset + ) + except KeyError as e: + raise MultiPartException( + 'The Content-Disposition header field "name" must be ' "provided." + ) from e + if b"filename" in options: + self._current_files += 1 + if self._current_files > self.max_files: + raise MultiPartException( + f"Too many files. Maximum number of files is {self.max_files}." + ) + filename = _user_safe_decode(options[b"filename"], self._charset) + tempfile = NamedTemporaryFile(delete=False) + self._files_to_close_on_error.append(tempfile) + self._current_part.file = GradioUploadFile( + file=tempfile, # type: ignore[arg-type] + size=0, + filename=filename, + headers=Headers(raw=self._current_part.item_headers), + ) + else: + self._current_fields += 1 + if self._current_fields > self.max_fields: + raise MultiPartException( + f"Too many fields. Maximum number of fields is {self.max_fields}." + ) + self._current_part.file = None + + def on_end(self) -> None: + pass + + async def parse(self) -> FormData: + # Parse the Content-Type header to get the multipart boundary. + _, params = parse_options_header(self.headers["Content-Type"]) + charset = params.get(b"charset", "utf-8") + if type(charset) == bytes: + charset = charset.decode("latin-1") + self._charset = charset + try: + boundary = params[b"boundary"] + except KeyError as e: + raise MultiPartException("Missing boundary in multipart.") from e + + # Callbacks dictionary. + callbacks = { + "on_part_begin": self.on_part_begin, + "on_part_data": self.on_part_data, + "on_part_end": self.on_part_end, + "on_header_field": self.on_header_field, + "on_header_value": self.on_header_value, + "on_header_end": self.on_header_end, + "on_headers_finished": self.on_headers_finished, + "on_end": self.on_end, + } + + # Create the parser. + parser = multipart.MultipartParser(boundary, callbacks) + try: + # Feed the parser with data from the request. + async for chunk in self.stream: + parser.write(chunk) + # Write file data, it needs to use await with the UploadFile methods + # that call the corresponding file methods *in a threadpool*, + # otherwise, if they were called directly in the callback methods above + # (regular, non-async functions), that would block the event loop in + # the main thread. + for part, data in self._file_parts_to_write: + assert part.file # for type checkers + await part.file.write(data) + part.file.sha.update(data) # type: ignore + for part in self._file_parts_to_finish: + assert part.file # for type checkers + await part.file.seek(0) + self._file_parts_to_write.clear() + self._file_parts_to_finish.clear() + except MultiPartException as exc: + # Close all the files if there was an error. + for file in self._files_to_close_on_error: + file.close() + raise exc + + parser.finalize() + return FormData(self.items) diff --git a/gradio/routes.py b/gradio/routes.py index 9f110b957886..f826eda12e75 100644 --- a/gradio/routes.py +++ b/gradio/routes.py @@ -15,6 +15,7 @@ import os import posixpath import secrets +import shutil import tempfile import threading import time @@ -24,11 +25,12 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type +import anyio import fastapi import httpx import markupsafe import orjson -from fastapi import Depends, FastAPI, File, HTTPException, UploadFile, WebSocket, status +from fastapi import Depends, FastAPI, HTTPException, WebSocket, status from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import ( FileResponse, @@ -38,8 +40,10 @@ ) from fastapi.security import OAuth2PasswordRequestForm from fastapi.templating import Jinja2Templates +from gradio_client import utils as client_utils from gradio_client.documentation import document, set_documentation_group from jinja2.exceptions import TemplateNotFound +from multipart.multipart import parse_options_header from starlette.background import BackgroundTask from starlette.responses import RedirectResponse, StreamingResponse from starlette.websockets import WebSocketState @@ -49,11 +53,15 @@ from gradio import route_utils, utils, wasm_utils from gradio.context import Context from gradio.data_classes import ComponentServerBody, PredictBody, ResetBody -from gradio.deprecation import warn_deprecation from gradio.exceptions import Error from gradio.oauth import attach_oauth from gradio.queueing import Estimation, Event -from gradio.route_utils import Request # noqa: F401 +from gradio.route_utils import ( # noqa: F401 + GradioMultiPartParser, + GradioUploadFile, + MultiPartException, + Request, +) from gradio.state_holder import StateHolder from gradio.utils import ( cancel_tasks, @@ -126,7 +134,7 @@ def __init__(self, **kwargs): self.queue_token = secrets.token_urlsafe(32) self.startup_events_triggered = False self.uploaded_file_dir = os.environ.get("GRADIO_TEMP_DIR") or str( - Path(tempfile.gettempdir()) / "gradio" + (Path(tempfile.gettempdir()) / "gradio").resolve() ) self.change_event: None | threading.Event = None self._asyncio_tasks: list[asyncio.Task] = [] @@ -325,7 +333,7 @@ def main(request: fastapi.Request, user: str = Depends(get_current_user)): "auth_required": True, "auth_message": blocks.auth_message, "space_id": app.get_blocks().space_id, - "root": root_path, + "root": route_utils.strip_url(root_path), } try: @@ -351,8 +359,8 @@ def main(request: fastapi.Request, user: str = Depends(get_current_user)): @app.get("/info/", dependencies=[Depends(login_check)]) @app.get("/info", dependencies=[Depends(login_check)]) def api_info(serialize: bool = True): - config = app.get_blocks().config - return gradio.blocks.get_api_info(config, serialize) # type: ignore + # config = app.get_blocks().get_api_info() + return app.get_blocks().get_api_info() # type: ignore @app.get("/config/", dependencies=[Depends(login_check)]) @app.get("/config", dependencies=[Depends(login_check)]) @@ -371,6 +379,32 @@ def static_resource(path: str): static_file = safe_join(STATIC_PATH_LIB, path) return FileResponse(static_file) + @app.get("/custom_component/{id}/{type}/{file_name}") + def custom_component_path(id: str, type: str, file_name: str): + config = app.get_blocks().config + components = config["components"] + location = next( + (item for item in components if item["component_class_id"] == id), None + ) + + if location is None: + raise HTTPException(status_code=404, detail="Component not found.") + + component_instance = app.get_blocks().get_component(location["id"]) + + module_name = component_instance.__class__.__module__ + module_path = sys.modules[module_name].__file__ + + if module_path is None or component_instance is None: + raise HTTPException(status_code=404, detail="Component not found.") + + return FileResponse( + safe_join( + str(Path(module_path).parent), + f"{component_instance.__class__.TEMPLATE_DIR}/{type}/{file_name}", + ) + ) + @app.get("/assets/{path:path}") def build_resource(path: str): build_file = safe_join(BUILD_PATH_LIB, path) @@ -628,17 +662,42 @@ async def get_queue_status(): return app.get_blocks()._queue.get_estimation() @app.post("/upload", dependencies=[Depends(login_check)]) - async def upload_file( - files: List[UploadFile] = File(...), - ): + async def upload_file(request: fastapi.Request): + content_type_header = request.headers.get("Content-Type") + content_type: bytes + content_type, _ = parse_options_header(content_type_header) + if content_type != b"multipart/form-data": + raise HTTPException(status_code=400, detail="Invalid content type.") + + try: + multipart_parser = GradioMultiPartParser( + request.headers, + request.stream(), + max_files=1000, + max_fields=1000, + ) + form = await multipart_parser.parse() + except MultiPartException as exc: + raise HTTPException(status_code=400, detail=exc.message) from exc + output_files = [] - file_manager = gradio.File() - for input_file in files: - output_files.append( - await file_manager.save_uploaded_file( - input_file, app.uploaded_file_dir - ) + for temp_file in form.getlist("files"): + assert isinstance(temp_file, GradioUploadFile) + if temp_file.filename: + file_name = Path(temp_file.filename).name + name = client_utils.strip_invalid_filename_characters(file_name) + else: + name = f"tmp{secrets.token_hex(5)}" + directory = Path(app.uploaded_file_dir) / temp_file.sha.hexdigest() + directory.mkdir(exist_ok=True, parents=True) + dest = (directory / name).resolve() + await anyio.to_thread.run_sync( + shutil.move, + temp_file.file.name, + dest, + limiter=app.get_blocks().limiter, ) + output_files.append(dest) return output_files @app.on_event("startup") @@ -717,7 +776,6 @@ def mount_gradio_app( app: fastapi.FastAPI, blocks: gradio.Blocks, path: str, - gradio_api_url: str | None = None, app_kwargs: dict[str, Any] | None = None, ) -> fastapi.FastAPI: """Mount a gradio.Blocks to an existing FastAPI application. @@ -726,7 +784,6 @@ def mount_gradio_app( app: The parent FastAPI application. blocks: The blocks object we want to mount to the parent app. path: The path at which the gradio application will be mounted. - gradio_api_url: Deprecated and has no effect. app_kwargs: Additional keyword arguments to pass to the underlying FastAPI app as a dictionary of parameter keys and argument values. For example, `{"docs_url": "/docs"}` Example: from fastapi import FastAPI @@ -744,9 +801,6 @@ def read_main(): blocks.validate_queue_settings() gradio_app = App.create_app(blocks, app_kwargs=app_kwargs) - if gradio_api_url is not None: - warn_deprecation("gradio_api_url is deprecated and has not effect.") - @app.on_event("startup") async def start_queue(): if gradio_app.get_blocks().enable_queue: diff --git a/gradio/templates.py b/gradio/templates.py index 42ebb1a2d7b0..e1d7d950e82b 100644 --- a/gradio/templates.py +++ b/gradio/templates.py @@ -421,7 +421,7 @@ def __init__( value: str | list[str] | Callable | None = None, *, file_count: Literal["multiple"] = "multiple", - type: Literal["file", "binary"] = "file", + type: Literal["filepath", "binary"] = "filepath", label: str | None = None, show_label: bool = True, interactive: bool | None = None, @@ -458,9 +458,6 @@ def __init__( col_count: int | tuple[int, str] | None = None, datatype: str | list[str] = "str", type: Literal["numpy"] = "numpy", - max_rows: int | None = 20, - max_cols: int | None = None, - overflow_row_behaviour: Literal["paginate", "show_ends"] = "paginate", label: str | None = None, show_label: bool = True, interactive: bool | None = None, @@ -476,9 +473,6 @@ def __init__( col_count=col_count, datatype=datatype, type=type, - max_rows=max_rows, - max_cols=max_cols, - overflow_row_behaviour=overflow_row_behaviour, label=label, show_label=show_label, interactive=interactive, @@ -505,9 +499,6 @@ def __init__( col_count: int | tuple[int, str] | None = None, datatype: str | list[str] = "str", type: Literal["array"] = "array", - max_rows: int | None = 20, - max_cols: int | None = None, - overflow_row_behaviour: Literal["paginate", "show_ends"] = "paginate", label: str | None = None, show_label: bool = True, interactive: bool | None = None, @@ -523,9 +514,6 @@ def __init__( col_count=col_count, datatype=datatype, type=type, - max_rows=max_rows, - max_cols=max_cols, - overflow_row_behaviour=overflow_row_behaviour, label=label, show_label=show_label, interactive=interactive, @@ -552,9 +540,6 @@ def __init__( col_count: Literal[1] = 1, datatype: str | list[str] = "str", type: Literal["array"] = "array", - max_rows: int | None = 20, - max_cols: int | None = None, - overflow_row_behaviour: Literal["paginate", "show_ends"] = "paginate", label: str | None = None, show_label: bool = True, interactive: bool | None = None, @@ -570,9 +555,6 @@ def __init__( col_count=col_count, datatype=datatype, type=type, - max_rows=max_rows, - max_cols=max_cols, - overflow_row_behaviour=overflow_row_behaviour, label=label, show_label=show_label, interactive=interactive, diff --git a/gradio/themes/builder_app.py b/gradio/themes/builder_app.py index 742adfafa43a..3c9a959f702f 100644 --- a/gradio/themes/builder_app.py +++ b/gradio/themes/builder_app.py @@ -356,12 +356,8 @@ def get_doc_theme_var_groups(): height=320, ) with gr.Row(): - go_btn = gr.Button( - "Go", label="Primary Button", variant="primary" - ) - clear_btn = gr.Button( - "Clear", label="Secondary Button", variant="secondary" - ) + go_btn = gr.Button("Go", variant="primary") + clear_btn = gr.Button("Clear", variant="secondary") def go(*args): time.sleep(3) @@ -383,9 +379,7 @@ def clear(): with gr.Row(): btn1 = gr.Button("Button 1", size="sm") btn2 = gr.UploadButton(size="sm") - stop_btn = gr.Button( - "Stop", label="Stop Button", variant="stop", size="sm" - ) + stop_btn = gr.Button("Stop", variant="stop", size="sm") gr.Examples( examples=[ diff --git a/gradio/utils.py b/gradio/utils.py index a7356397faec..37692e06323b 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -37,7 +37,6 @@ import anyio import matplotlib import requests -from gradio_client.serializing import Serializable from typing_extensions import ParamSpec import gradio @@ -45,7 +44,7 @@ from gradio.strings import en if TYPE_CHECKING: # Only import for type checking (is False at runtime). - from gradio.blocks import Block, BlockContext, Blocks + from gradio.blocks import BlockContext, Blocks from gradio.components import Component from gradio.routes import App, Request @@ -148,7 +147,7 @@ def watchfn(reloader: SourceFileReloader): # The thread running watchfn will be the thread reloading # the app. So we need to modify this thread_data attr here # so that subsequent calls to reload don't launch the app - from gradio.reload import reload_thread + from gradio.cli.commands.reload import reload_thread reload_thread.running_reload = True @@ -174,10 +173,13 @@ def iter_py_files() -> Iterator[Path]: module = None reload_dirs = [Path(dir_) for dir_ in reloader.watch_dirs] + import sys + + for dir_ in reload_dirs: + sys.path.insert(0, str(dir_)) + mtimes = {} while reloader.should_watch(): - import sys - changed = get_changes() if changed: print(f"Changes detected in: {changed}") @@ -867,7 +869,7 @@ def tex2svg(formula, *args): fig = plt.figure(figsize=(0.01, 0.01)) fig.text(0, 0, rf"${formula}$", fontsize=fontsize) output = BytesIO() - fig.savefig( + fig.savefig( # type: ignore output, dpi=dpi, transparent=True, @@ -928,41 +930,6 @@ def is_in_or_equal(path_1: str | Path, path_2: str | Path): return True -def get_serializer_name(block: Block) -> str | None: - if not hasattr(block, "serialize"): - return None - - def get_class_that_defined_method(meth: Callable): - # Adapted from: https://stackoverflow.com/a/25959545/5209347 - if isinstance(meth, functools.partial): - return get_class_that_defined_method(meth.func) - if inspect.ismethod(meth) or ( - inspect.isbuiltin(meth) - and getattr(meth, "__self__", None) is not None - and getattr(meth.__self__, "__class__", None) - ): - for cls in inspect.getmro(meth.__self__.__class__): - # Find the first serializer defined in gradio_client that - if issubclass(cls, Serializable) and "gradio_client" in cls.__module__: - return cls - if meth.__name__ in cls.__dict__: - return cls - meth = getattr(meth, "__func__", meth) # fallback to __qualname__ parsing - if inspect.isfunction(meth): - cls = getattr( - inspect.getmodule(meth), - meth.__qualname__.split(".", 1)[0].rsplit(".", 1)[0], - None, - ) - if isinstance(cls, type): - return cls - return getattr(meth, "__objclass__", None) - - cls = get_class_that_defined_method(block.serialize) # type: ignore - if cls: - return cls.__name__ - - HTML_TAG_RE = re.compile("<.*?>") @@ -983,3 +950,25 @@ def find_user_stack_level() -> int: frame = frame.f_back n += 1 return n + + +def recover_kwargs(config: dict, additional_keys_to_ignore: list[str] | None = None): + not_kwargs = ["type", "name", "selectable", "server_fns", "streamable"] + return { + k: v + for k, v in config.items() + if k not in not_kwargs and k not in (additional_keys_to_ignore or []) + } + + +class NamedString(str): + """ + Subclass of str that includes a .name attribute equal to the value of the string itself. This class is used when returning + a value from the `.preprocess()` methods of the File and UploadButton components. Before Gradio 4.0, these methods returned a file + object which was then converted to a string filepath using the `.name` attribute. In Gradio 4.0, these methods now return a str + filepath directly, but to maintain backwards compatibility, we use this class instead of a regular str. + """ + + def __init__(self, *args): + super().__init__() + self.name = str(self) if args else "" diff --git a/guides/06_tabular-data-science-and-plots/styling-the-gradio-dataframe.md b/guides/06_tabular-data-science-and-plots/styling-the-gradio-dataframe.md new file mode 100644 index 000000000000..6fffc109e4b0 --- /dev/null +++ b/guides/06_tabular-data-science-and-plots/styling-the-gradio-dataframe.md @@ -0,0 +1,167 @@ +# How to Style the Gradio Dataframe + +Tags: DATAFRAME, STYLE, COLOR + +## Introduction + +Data visualization is a crucial aspect of data analysis and machine learning. The Gradio `DataFrame` component is a popular way to display tabular data (particularly data in the form of a `pandas` `DataFrame` object) within a web application. + +This post will explore the recent enhancements in Gradio that allow users to integrate the styling options of pandas, e.g. adding colors to the DataFrame component, or setting the display precision of numbers. + +![](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/df-highlight.png) + +Let's dive in! + +**Prerequisites**: We'll be using the `gradio.Blocks` class in our examples. +You can [read the Guide to Blocks first](https://gradio.app/quickstart/#blocks-more-flexibility-and-control) if you are not already familiar with it. Also please make sure you are using the **latest version** version of Gradio: `pip install --upgrade gradio`. + + +## Overview + +The Gradio `DataFrame` component now supports values of the type `Styler` from the `pandas` class. This allows us to reuse the rich existing API and documentation of the `Styler` class instead of inventing a new style format on our own. Here's a complete example of how it looks: + +```python +import pandas as pd +import gradio as gr + +# Creating a sample dataframe +df = pd.DataFrame({ + "A" : [14, 4, 5, 4, 1], + "B" : [5, 2, 54, 3, 2], + "C" : [20, 20, 7, 3, 8], + "D" : [14, 3, 6, 2, 6], + "E" : [23, 45, 64, 32, 23] +}) + +# Applying style to highlight the maximum value in each row +styler = df.style.highlight_max(color = 'lightgreen', axis = 0) + +# Displaying the styled dataframe in Gradio +with gr.Blocks() as demo: + gr.DataFrame(styler) + +demo.launch() +``` + +The Styler class can be used to apply conditional formatting and styling to dataframes, making them more visually appealing and interpretable. You can highlight certain values, apply gradients, or even use custom CSS to style the DataFrame. The Styler object is applied to a DataFrame and it returns a new object with the relevant styling properties, which can then be previewed directly, or rendered dynamically in a Gradio interface. + +To read more about the Styler object, read the official `pandas` documentation at: https://pandas.pydata.org/docs/user_guide/style.html + +Below, we'll explore a few examples: + +## Highlighting Cells + +Ok, so let's revisit the previous example. We start by creating a `pd.DataFrame` object and then highlight the highest value in each row with a light green color: + +```python +import pandas as pd + +# Creating a sample dataframe +df = pd.DataFrame({ + "A" : [14, 4, 5, 4, 1], + "B" : [5, 2, 54, 3, 2], + "C" : [20, 20, 7, 3, 8], + "D" : [14, 3, 6, 2, 6], + "E" : [23, 45, 64, 32, 23] +}) + +# Applying style to highlight the maximum value in each row +styler = df.style.highlight_max(color = 'lightgreen', axis = 0) +``` + +Now, we simply pass this object into the Gradio `DataFrame` and we can visualize our colorful table of data in 4 lines of python: + +```python +import gradio as gr + +with gr.Blocks() as demo: + gr.Dataframe(styler) + +demo.launch() +``` + +Here's how it looks: + +![](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/df-highlight.png) + +## Font Colors + +Apart from highlighting cells, you might want to color specific text within the cells. Here's how you can change text colors for certain columns: + +```python +import pandas as pd +import gradio as gr + +# Creating a sample dataframe +df = pd.DataFrame({ + "A" : [14, 4, 5, 4, 1], + "B" : [5, 2, 54, 3, 2], + "C" : [20, 20, 7, 3, 8], + "D" : [14, 3, 6, 2, 6], + "E" : [23, 45, 64, 32, 23] +}) + +# Function to apply text color +def highlight_cols(x): + df = x.copy() + df.loc[:, :] = 'color: purple' + df[['B', 'C', 'E']] = 'color: green' + return df + +# Applying the style function +s = df.style.apply(highlight_cols, axis = None) + +# Displaying the styled dataframe in Gradio +with gr.Blocks() as demo: + gr.DataFrame(s) + +demo.launch() +``` + +In this script, we define a custom function highlight_cols that changes the text color to purple for all cells, but overrides this for columns B, C, and E with green. Here's how it looks: + +![](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/df-color.png) + +## Display Precision + +Sometimes, the data you are dealing with might have long floating numbers, and you may want to display only a fixed number of decimals for simplicity. The pandas Styler object allows you to format the precision of numbers displayed. Here's how you can do this: + +```python +import pandas as pd +import gradio as gr + +# Creating a sample dataframe with floating numbers +df = pd.DataFrame({ + "A" : [14.12345, 4.23456, 5.34567, 4.45678, 1.56789], + "B" : [5.67891, 2.78912, 54.89123, 3.91234, 2.12345], + # ... other columns +}) + +# Setting the precision of numbers to 2 decimal places +s = df.style.format("{:.2f}") + +# Displaying the styled dataframe in Gradio +with gr.Blocks() as demo: + gr.DataFrame(s) + +demo.launch() +``` + +In this script, the format method of the Styler object is used to set the precision of numbers to two decimal places. Much cleaner now: + +![](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/df-precision.png) + + +## Note about Interactivity + +One thing to keep in mind is that the gradio `DataFrame` component only accepts `Styler` objects when it is non-interactive (i.e. in "static" mode). If the `DataFrame` component is interactive, then the styling information is ignored and instead the raw table values are shown instead. + +The `DataFrame` component is by default non-interactive, unless it is used as an input to an event. In which case, you can force the component to be non-interactive by setting the `interactive` prop like this: + +```python +c = gr.DataFrame(styler, interactive=False) +``` + +## Conclusion 🎉 + +This is just a taste of what's possible using the `gradio.DataFrame` component with the `Styler` class from `pandas`. Try it out and let us know what you think! \ No newline at end of file diff --git a/guides/08_other-tutorials/gradio-lite.md b/guides/08_other-tutorials/gradio-lite.md new file mode 100644 index 000000000000..400cecb26a9a --- /dev/null +++ b/guides/08_other-tutorials/gradio-lite.md @@ -0,0 +1,196 @@ +# Gradio-Lite: Serverless Gradio Running Entirely in Your Browser + +Tags: SERVERLESS, BROWSER, PYODIDE + +Gradio is a popular Python library for creating interactive machine learning apps. Traditionally, Gradio applications have relied on server-side infrastructure to run, which can be a hurdle for developers who need to host their applications. + +Enter Gradio-lite (`@gradio/lite`): a library that leverages [Pyodide](https://pyodide.org/en/stable/) to bring Gradio directly to your browser. In this blog post, we'll explore what `@gradio/lite` is, go over example code, and discuss the benefits it offers for running Gradio applications. + +## What is `@gradio/lite`? + +`@gradio/lite` is a JavaScript library that enables you to run Gradio applications directly within your web browser. It achieves this by utilizing Pyodide, a Python runtime for WebAssembly, which allows Python code to be executed in the browser environment. With `@gradio/lite`, you can **write regular Python code for your Gradio applications**, and they will **run seamlessly in the browser** without the need for server-side infrastructure. + +## Getting Started + +Let's build a "Hello World" Gradio app in `@gradio/lite` + + +### 1. Import JS and CSS + +Start by creating a new HTML file, if you don't have one already. Importing the JavaScript and CSS corresponding to the `@gradio/lite` package by using the following code: + + +```html + + + + + + +``` + +Note that you should generally use the latest version of `@gradio/lite` that is available. You can see the [versions available here](https://www.jsdelivr.com/package/npm/@gradio/lite?tab=files). + +### 2. Create the `` tags + +Somewhere in the body of your HTML page (wherever you'd like the Gradio app to be rendered), create opening and closing `` tags. + +```html + + + + + + + + + + +``` + +Note: you can add the `theme` attribute to the `` tag to force the theme to be dark or light (by default, it respects the system theme). E.g. + +```html + +... + +``` + +### 3. Write your Gradio app inside of the tags + +Now, write your Gradio app as you would normally, in Python! Keep in mind that since this is Python, whitespace and indentations matter. + +```html + + + + + + + + import gradio as gr + + def greet(name): + return "Hello, " + name + "!" + + gr.Interface(greet, "textbox", "textbox").launch() + + + +``` + +And that's it! You should now be able to open your HTML page in the browser and see the Gradio app rendered! Note that it may take a little while for the Gradio app to load initially since Pyodide can take a while to install in your browser. + +**Note on debugging**: to see any errors in your Gradio-lite application, open the inspector in your web browser. All errors (including Python errors) will be printed there. + +## More Examples: Adding Additional Files and Requirements + +What if you want to create a Gradio app that spans multiple files? Or that has custom Python requirements? Both are possible with `@gradio/lite`! + +### Multiple Files + +Adding multiple files within a `@gradio/lite` app is very straightrward: use the `` tag. You can have as many `` tags as you want, but each one needs to have a `name` attribute and the entry point to your Gradio app should have the `entrypoint` attribute. + +Here's an example: + +```html + + + +import gradio as gr +from utils import add + +demo = gr.Interface(fn=add, inputs=["number", "number"], outputs="number") + +demo.launch() + + + +def add(a, b): + return a + b + + + + +``` + +### Additional Requirements + +If your Gradio app has additional requirements, it is usually possible to [install them in the browser using micropip](https://pyodide.org/en/stable/usage/loading-packages.html#loading-packages). We've created a wrapper to make this paticularly convenient: simply list your requirements in the same syntax as a `requirements.txt` and enclose them with `` tags. + +Here, we install `transformers_js_py` to run a text classification model directly in the browser! + +```html + + + +transformers_js_py + + + +from transformers_js import import_transformers_js +import gradio as gr + +transformers = await import_transformers_js() +pipeline = transformers.pipeline +pipe = await pipeline('sentiment-analysis') + +async def classify(text): + return await pipe(text) + +demo = gr.Interface(classify, "textbox", "json") +demo.launch() + + + + +``` + +**Try it out**: You can see this example running in [this Hugging Face Static Space](https://huggingface.co/spaces/abidlabs/gradio-lite-classify), which lets you host static (serverless) web applications for free. Visit the page and you'll be able to run a machine learning model without internet access! + +## Benefits of Using `@gradio/lite` + +### 1. Serverless Deployment +The primary advantage of @gradio/lite is that it eliminates the need for server infrastructure. This simplifies deployment, reduces server-related costs, and makes it easier to share your Gradio applications with others. + +### 2. Low Latency +By running in the browser, @gradio/lite offers low-latency interactions for users. There's no need for data to travel to and from a server, resulting in faster responses and a smoother user experience. + +### 3. Privacy and Security +Since all processing occurs within the user's browser, `@gradio/lite` enhances privacy and security. User data remains on their device, providing peace of mind regarding data handling. + +### Limitations + +* Currently, the biggest limitation in using `@gradio/lite` is that your Gradio apps will generally take more time (usually 5-15 seconds) to load initially in the browser. This is because the browser needs to load the Pyodide runtime before it can render Python code. + +* Not every Python package is supported by Pyodide. While `gradio` and many other popular packages (including `numpy`, `scikit-learn`, and `transformers-js`) can be installed in Pyodide, if your app has many dependencies, its worth checking whether whether the dependencies are included in Pyodide, or can be [installed with `micropip`](https://micropip.pyodide.org/en/v0.2.2/project/api.html#micropip.install). + +## Try it out! + +You can immediately try out `@gradio/lite` by copying and pasting this code in a local `index.html` file and opening it with your browser: + +```html + + + + + + + + import gradio as gr + + def greet(name): + return "Hello, " + name + "!" + + gr.Interface(greet, "textbox", "textbox").launch() + + + +``` + + +We've also created a playground on the Gradio website that allows you to interactively edit code and see the results immediately! + +Playground: https://www.gradio.app/playground + + diff --git a/js/_cdn-test/package.json b/js/_cdn-test/package.json index 11f020bc43fd..3401e2dffa52 100644 --- a/js/_cdn-test/package.json +++ b/js/_cdn-test/package.json @@ -1,13 +1,11 @@ { "name": "@gradio/cdn-test", "private": true, - "version": "0.0.0", + "version": "0.0.1", "scripts": { "dev": "vite --port 3001", "build": "vite build", "preview": "vite preview --port 3001" }, - "devDependencies": { - "vite": "^4.0.0" - } + "devDependencies": {} } diff --git a/js/_spaces-test/package.json b/js/_spaces-test/package.json index b35adc30991c..bcee0d2e431b 100644 --- a/js/_spaces-test/package.json +++ b/js/_spaces-test/package.json @@ -16,10 +16,8 @@ "@sveltejs/kit": "^1.5.0", "prettier": "^3.0.0", "prettier-plugin-svelte": "^3.0.0", - "svelte": "^3.54.0", "svelte-check": "^3.0.1", - "typescript": "^5.0.0", - "vite": "^4.3.0" + "typescript": "^5.0.0" }, "type": "module", "dependencies": { diff --git a/js/_spaces-test/src/lib/EndpointInputs.svelte b/js/_spaces-test/src/lib/EndpointInputs.svelte index ae9833040f8a..6b0f5097381a 100644 --- a/js/_spaces-test/src/lib/EndpointInputs.svelte +++ b/js/_spaces-test/src/lib/EndpointInputs.svelte @@ -4,8 +4,6 @@ */ export let app_info; - $: console.log(app_info); - /** * @type any[] */ diff --git a/js/_spaces-test/src/routes/+layout.svelte b/js/_spaces-test/src/routes/+layout.svelte index 361ac4ccca02..733c54dae15b 100644 --- a/js/_spaces-test/src/routes/+layout.svelte +++ b/js/_spaces-test/src/routes/+layout.svelte @@ -4,8 +4,6 @@ import { page } from "$app/stores"; import { afterNavigate } from "$app/navigation"; - $: console.log($page); - const links = [ ["/embeds", "Embeds"], ["/client-browser", "Client-Browser"], diff --git a/js/_spaces-test/src/routes/client-browser/+page.svelte b/js/_spaces-test/src/routes/client-browser/+page.svelte index 55e8bd101f44..3cb8d503462b 100644 --- a/js/_spaces-test/src/routes/client-browser/+page.svelte +++ b/js/_spaces-test/src/routes/client-browser/+page.svelte @@ -47,8 +47,6 @@ hf_token: hf_token }); - console.log(app.config); - const { named_endpoints, unnamed_endpoints } = await app.view_api(); named = Object.keys(named_endpoints); @@ -66,8 +64,6 @@ ]; if (!_endpoint_info) return; - console.log(_endpoint_info); - app_info = _endpoint_info; active_endpoint = type === "unnamed" ? parseInt(_endpoint) : _endpoint; } @@ -93,8 +89,6 @@ .on("status", (_status) => { status = _status.stage; }); - // console.log(res); - // response_data = res; } function cancel() { diff --git a/js/_website/CHANGELOG.md b/js/_website/CHANGELOG.md index a8227ae2bd4b..95a72c896640 100644 --- a/js/_website/CHANGELOG.md +++ b/js/_website/CHANGELOG.md @@ -1,5 +1,23 @@ # website +## 0.10.0 + +### Features + +- [#6021](https://github.com/gradio-app/gradio/pull/6021) [`86cff0c29`](https://github.com/gradio-app/gradio/commit/86cff0c293db776c08c1ab372d69aac7c594cfb3) - Playground: Better spacing on navbar. Thanks [@aliabd](https://github.com/aliabd)! + +## 0.9.0 + +### Features + +- [#5386](https://github.com/gradio-app/gradio/pull/5386) [`0312c990f`](https://github.com/gradio-app/gradio/commit/0312c990fbe63fdf3bfa9a8f13bbc042295d49bf) - Playground v1. Thanks [@aliabd](https://github.com/aliabd)! + +## 0.8.0 + +### Features + +- [#5936](https://github.com/gradio-app/gradio/pull/5936) [`b8b9f6d27`](https://github.com/gradio-app/gradio/commit/b8b9f6d27e258256584b7662d03110cc2eeb883b) - Adds a Guide on how to stylize the DataFrame component. Thanks [@abidlabs](https://github.com/abidlabs)! + ## 0.7.1 ### Features diff --git a/js/_website/generate_jsons/src/demos/__init__.py b/js/_website/generate_jsons/src/demos/__init__.py index c0595c6d6603..9da403d27b99 100644 --- a/js/_website/generate_jsons/src/demos/__init__.py +++ b/js/_website/generate_jsons/src/demos/__init__.py @@ -4,143 +4,131 @@ DIR = os.path.dirname(__file__) GRADIO_DEMO_DIR = os.path.abspath(os.path.join(DIR, "../../../../../demo/")) -def get_code_and_description(demo_name): +def get_code_description_and_reqs(demo_name): with open(os.path.join(GRADIO_DEMO_DIR, demo_name, "run.py")) as f: code = f.read() - with open(os.path.join(GRADIO_DEMO_DIR, demo_name, "DESCRIPTION.md")) as f: - description = f.read() - return code, description + description = "" + if os.path.exists(os.path.join(GRADIO_DEMO_DIR, demo_name, "DESCRIPTION.md")): + with open(os.path.join(GRADIO_DEMO_DIR, demo_name, "DESCRIPTION.md")) as f: + description = f.read() + requirements = [] + if os.path.exists(os.path.join(GRADIO_DEMO_DIR, demo_name, "requirements.txt")): + with open(os.path.join(GRADIO_DEMO_DIR, demo_name, "requirements.txt")) as f: + requirements = f.read().strip().split("\n") + return code, description, requirements demos_by_category = [ { - "category": "🖊️ Text & Natural Language Processing", + "category": "Text", "demos": [ { "name": "Hello World", "dir": "hello_world", }, { - "name": "Text Generation", - "dir": "text_generation", + "name": "Hello Blocks", + "dir": "hello_blocks", }, { - "name": "Autocomplete", - "dir": "autocomplete", + "name": "Sentence Builder", + "dir": "sentence_builder", }, { - "name": "Sentiment Analysis", - "dir": "sentiment_analysis", + "name": "Diff Texts", + "dir": "diff_texts", }, - { - "name": "Named Entity Recognition", - "dir": "text_analysis", - }, - { - "name": "Multilingual Translation", - "dir": "translation", - } + ] }, { - "category": "🖼️ Images & Computer Vision", + "category": "Media", "demos": [ { - "name": "Image Classification", - "dir": "image_classification", - }, - { - "name": "Image Segmentation", - "dir": "image_segmentation", + "name": "Sepia Filter", + "dir": "sepia_filter", }, { - "name": "Image Transformation with AnimeGAN", - "dir": "animeganv2", - }, - { - "name": "Image Generation (Fake GAN)", - "dir": "fake_gan", + "name": "Video Identity", + "dir": "video_identity_2", }, { "name": "Iterative Output", "dir": "fake_diffusion", }, { - "name": "3D Models", - "dir": "depth_estimation", + "name": "Generate Tone", + "dir": "generate_tone", }, ] }, { - "category": "📈 Tabular Data & Plots", + "category": "Tabular", "demos": [ { - "name": "Interactive Dashboard", - "dir": "dashboard" + "name": "Filter Records", + "dir": "filter_records" }, { - "name": "Dashboard with Live Updates", - "dir": "live_dashboard" + "name": "Transpose Matrix", + "dir": "matrix_transpose" }, { - "name": "Interactive Map of AirBnB Locations", - "dir": "map_airbnb" + "name": "Tax Calculator", + "dir": "tax_calculator" }, { - "name": "Outbreak Forecast", - "dir": "outbreak_forecast", + "name": "Kinematics", + "dir": "blocks_kinematics", }, { - "name": "Clustering with Scikit-Learn", - "dir": "clustering", - }, - { - "name": "Time Series Forecasting", - "dir": "timeseries-forecasting-with-prophet", + "name": "Stock Forecast", + "dir": "stock_forecast", }, + ] + }, + { + "category": "Other", + "demos": [ { - "name": "Income Classification with XGBoost", - "dir": "xgboost-income-prediction-with-explainability", + "name": "Tabbed Interface", + "dir": "tabbed_interface_lite", }, { - "name": "Leaderboard", - "dir": "leaderboard", + "name": "Chatbot", + "dir": "chatinterface_random_response", }, { - "name": "Tax Calculator", - "dir": "tax_calculator", + "name": "Streaming Chatbot", + "dir": "chatinterface_streaming_echo", }, - ] - }, - { - "category": "🎤 Audio & Speech", - "demos": [ { - "name": "Text to Speech", - "dir": "neon-tts-plugin-coqui", + "name": "Layouts", + "dir": "blocks_flipper", }, { - "name": "Speech to Text (ASR)", - "dir": "automatic-speech-recognition", + "name": "Error", + "dir": "calculator", }, { - "name": "Musical Instrument Identification", - "dir": "musical_instrument_identification", + "name": "Chained Events", + "dir": "blocks_chained_events", }, { - "name": "Speaker Verification", - "dir": "same-person-or-different", - }, + "name": "Change Listener", + "dir": "blocks_hello", + } ] - }, + } ] for category in demos_by_category: for demo in category["demos"]: - code, description = get_code_and_description(demo["dir"]) + code, description, requirements = get_code_description_and_reqs(demo["dir"]) demo["code"] = code demo["text"] = description + demo["requirements"] = requirements def generate(json_path): with open(json_path, 'w+') as f: diff --git a/js/_website/package.json b/js/_website/package.json index 9a54327cc2a6..0c8ee9196660 100644 --- a/js/_website/package.json +++ b/js/_website/package.json @@ -1,6 +1,6 @@ { "name": "website", - "version": "0.7.1", + "version": "0.10.0", "private": true, "scripts": { "dev": "python generate_jsons/generate.py && vite dev", @@ -13,21 +13,21 @@ "devDependencies": { "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-static": "^2.0.2", - "@sveltejs/kit": "^1.5.0", + "@sveltejs/kit": "^1.26.0", "@tailwindcss/forms": "^0.5.0", "@tailwindcss/typography": "^0.5.4", "@types/node": "^20.3.2", "@types/prismjs": "^1.26.0", "prismjs": "1.29.0", - "svelte": "^3.59.2", "svelte-check": "^3.0.1", + "svelte-i18n": "^3.6.0", "tailwindcss": "^3.1.6", "tslib": "^2.4.1", - "typescript": "^5.0.0", - "vite": "^4.3.9" + "typescript": "^5.0.0" }, "type": "module", "dependencies": { + "@gradio/code": "workspace:^", "@sindresorhus/slugify": "^2.2.0", "@sveltejs/adapter-vercel": "^3.0.3", "hast-util-to-string": "^3.0.0", diff --git a/js/_website/src/app.html b/js/_website/src/app.html index 06dda00f89ee..c8f78c33b6d8 100644 --- a/js/_website/src/app.html +++ b/js/_website/src/app.html @@ -7,7 +7,10 @@ %sveltekit.head% - +
%sveltekit.body%
diff --git a/js/_website/src/lib/assets/img/twitter.svg b/js/_website/src/lib/assets/img/twitter.svg index c9fa778f07ec..fce3ddfdefba 100644 --- a/js/_website/src/lib/assets/img/twitter.svg +++ b/js/_website/src/lib/assets/img/twitter.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/js/_website/src/lib/assets/logos/twitter.svg b/js/_website/src/lib/assets/logos/twitter.svg index 39dedc3f0748..5ea4b52b65db 100644 --- a/js/_website/src/lib/assets/logos/twitter.svg +++ b/js/_website/src/lib/assets/logos/twitter.svg @@ -1,64 +1 @@ - - - - - - - - - - image/svg+xml - - - - - - - - + \ No newline at end of file diff --git a/js/_website/src/lib/assets/style.css b/js/_website/src/lib/assets/style.css index 7ec61c15ad81..2bc098e0440f 100644 --- a/js/_website/src/lib/assets/style.css +++ b/js/_website/src/lib/assets/style.css @@ -115,6 +115,12 @@ @apply border-orange-500 text-orange-500; } +/* playground */ + +.current-playground-demo { + @apply text-orange-500 pl-4; +} + /* docs */ .selected-demo { @apply font-semibold bg-gray-50 rounded text-orange-500; @@ -155,6 +161,12 @@ code.language-bash { .clipboard-button:focus { @apply outline-0; } + +/* interactive banner */ +.interactive-banner { + @apply absolute right-0 px-1.5 pt-0.5 pb-1 m-4 -mt-1 text-sm z-[100] bg-orange-500 rounded-lg; +} + .codeblock { @apply relative bg-gray-50 mx-auto p-3 mt-2; } diff --git a/js/_website/src/lib/components/CopyButton.svelte b/js/_website/src/lib/components/CopyButton.svelte index 3e0f6582ce6f..17edb3ab34e6 100644 --- a/js/_website/src/lib/components/CopyButton.svelte +++ b/js/_website/src/lib/components/CopyButton.svelte @@ -1,21 +1,20 @@ - \ No newline at end of file + diff --git a/js/_website/src/lib/components/DemosLite.svelte b/js/_website/src/lib/components/DemosLite.svelte new file mode 100644 index 000000000000..a7d411fbb7c2 --- /dev/null +++ b/js/_website/src/lib/components/DemosLite.svelte @@ -0,0 +1,110 @@ + + + + + + + +
+ {#each demos as demo, i} + + {/each} + +
+
+ + diff --git a/js/_website/src/lib/components/DocsNav.svelte b/js/_website/src/lib/components/DocsNav.svelte index a46dd181c97c..c7576636fbde 100644 --- a/js/_website/src/lib/components/DocsNav.svelte +++ b/js/_website/src/lib/components/DocsNav.svelte @@ -121,10 +121,9 @@ TabbedInterfaceTabbedInterface - + 💡 Guides - 🎢 Demos🎢 + PlaygroundNEW
(show_help_menu = true)} diff --git a/js/_website/src/lib/components/MetaTags.svelte b/js/_website/src/lib/components/MetaTags.svelte index 4317a3605926..8e447286f6f8 100644 --- a/js/_website/src/lib/components/MetaTags.svelte +++ b/js/_website/src/lib/components/MetaTags.svelte @@ -1,5 +1,4 @@ @@ -53,6 +53,11 @@ href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700;1,900&display=swap" rel="stylesheet" /> +