From dc211822ca6f0c6ca2b4b648be3e40e4102c288c Mon Sep 17 00:00:00 2001 From: Ali Abid Date: Wed, 30 Jun 2021 13:12:27 -0700 Subject: [PATCH] fix screenshot, image editor; --- frontend/package-lock.json | 113 ++++++++++++++++ frontend/package.json | 2 + frontend/src/gradio.jsx | 11 +- frontend/src/interfaces/input/audio.jsx | 2 +- frontend/src/interfaces/input/checkbox.jsx | 2 +- .../src/interfaces/input/checkbox_group.jsx | 2 +- frontend/src/interfaces/input/dropdown.jsx | 2 +- frontend/src/interfaces/input/file.jsx | 2 +- frontend/src/interfaces/input/image.jsx | 57 ++++++++- frontend/src/interfaces/input/number.jsx | 2 +- frontend/src/interfaces/input/radio.jsx | 2 +- frontend/src/interfaces/input/slider.jsx | 2 +- frontend/src/interfaces/input/textbox.jsx | 2 +- frontend/src/interfaces/output/file.jsx | 2 +- frontend/src/themes/defaults.scss | 20 +++ frontend/src/themes/huggingface.scss | 121 ++++++++++++++++-- frontend/src/{interfaces => }/utils.jsx | 32 +++-- gradio.egg-info/SOURCES.txt | 24 ++++ gradio/component.py | 3 +- gradio/frontend/asset-manifest.json | 6 +- gradio/frontend/index.html | 2 +- gradio/inputs.py | 10 +- gradio/interface.py | 16 +-- gradio/strings.py | 10 +- 24 files changed, 388 insertions(+), 59 deletions(-) rename frontend/src/{interfaces => }/utils.jsx (70%) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e9debc1d2f..37c5c23e5e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,9 +13,11 @@ "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^11.2.6", "@testing-library/user-event": "^12.8.3", + "@toast-ui/react-image-editor": "^3.14.2", "audio-react-recorder": "^1.0.4", "classnames": "^2.3.1", "fabric": "^4.5.0", + "html2canvas-objectfit-fix": "^1.2.0", "jspreadsheet-ce": "^4.7.3", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -2578,6 +2580,18 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@toast-ui/react-image-editor": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/@toast-ui/react-image-editor/-/react-image-editor-3.14.2.tgz", + "integrity": "sha512-FWnEBTESI6yqM9eDqkGTNbnN6lM9/XxxrxMlg06sW9Dh5LvQsIYdz0G8J77brR0Qm+FXdwAK/n2jltitcTnB0Q==", + "dependencies": { + "fabric": "^4.2.0", + "tui-image-editor": "^3.14.2" + }, + "peerDependencies": { + "react": "^16.0.0" + } + }, "node_modules/@types/anymatch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", @@ -4321,6 +4335,14 @@ "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" }, + "node_modules/base64-arraybuffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", + "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -5694,6 +5716,14 @@ "node": ">=4" } }, + "node_modules/css-line-break": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-1.1.1.tgz", + "integrity": "sha512-1feNVaM4Fyzdj4mKPIQNL2n70MmuYzAXZ1aytlROFX1JsOo070OsugwGjj7nl6jnDJWHDM8zRZswkmeYVWZJQA==", + "dependencies": { + "base64-arraybuffer": "^0.2.0" + } + }, "node_modules/css-loader": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-4.3.0.tgz", @@ -9610,6 +9640,17 @@ "node": ">=4.0.0" } }, + "node_modules/html2canvas-objectfit-fix": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/html2canvas-objectfit-fix/-/html2canvas-objectfit-fix-1.2.0.tgz", + "integrity": "sha512-eBQPJyJJPgAlgtMioB42Kv+wHXGgfjUyPdYWZKbF8jUlss4J6xdj5tJM8n2uZ/94NU2UEooN97ypGdzbFE562A==", + "dependencies": { + "css-line-break": "1.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/htmlparser2": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", @@ -18884,6 +18925,27 @@ "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" }, + "node_modules/tui-code-snippet": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/tui-code-snippet/-/tui-code-snippet-1.5.2.tgz", + "integrity": "sha512-6UqTlQaaC1KLcmC0HAoq5dtl1G4Fib+R+NC7pmaV7kiIlZ7JqKhUmnOoGRcreAyzd81UTK/vCvhrw9QJskpCFQ==" + }, + "node_modules/tui-color-picker": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/tui-color-picker/-/tui-color-picker-2.2.7.tgz", + "integrity": "sha512-y7gUe6PdVBq8ruZbiOkeaN7N/DX+ogVyLkK53OM4qW5Vrc4pDWYEKlnOBCsPODb4/RSzS9iTd6fXYirXxeakJA==" + }, + "node_modules/tui-image-editor": { + "version": "3.14.3", + "resolved": "https://registry.npmjs.org/tui-image-editor/-/tui-image-editor-3.14.3.tgz", + "integrity": "sha512-zNbC+nKVYdLSvUuHK+ZWTJw6T5Jr02oPBKrdEjfGAP7gx2yfWzHbpmrsivLyiCJzKV580N4mt0mAXttB2EClYg==", + "dependencies": { + "core-js-pure": "^3.6.4", + "fabric": "^4.2.0", + "tui-code-snippet": "^1.5.0", + "tui-color-picker": "^2.2.6" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -23264,6 +23326,15 @@ "@babel/runtime": "^7.12.5" } }, + "@toast-ui/react-image-editor": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/@toast-ui/react-image-editor/-/react-image-editor-3.14.2.tgz", + "integrity": "sha512-FWnEBTESI6yqM9eDqkGTNbnN6lM9/XxxrxMlg06sW9Dh5LvQsIYdz0G8J77brR0Qm+FXdwAK/n2jltitcTnB0Q==", + "requires": { + "fabric": "^4.2.0", + "tui-image-editor": "^3.14.2" + } + }, "@types/anymatch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", @@ -24703,6 +24774,11 @@ "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" }, + "base64-arraybuffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", + "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==" + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -25818,6 +25894,14 @@ } } }, + "css-line-break": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-1.1.1.tgz", + "integrity": "sha512-1feNVaM4Fyzdj4mKPIQNL2n70MmuYzAXZ1aytlROFX1JsOo070OsugwGjj7nl6jnDJWHDM8zRZswkmeYVWZJQA==", + "requires": { + "base64-arraybuffer": "^0.2.0" + } + }, "css-loader": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-4.3.0.tgz", @@ -28904,6 +28988,14 @@ } } }, + "html2canvas-objectfit-fix": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/html2canvas-objectfit-fix/-/html2canvas-objectfit-fix-1.2.0.tgz", + "integrity": "sha512-eBQPJyJJPgAlgtMioB42Kv+wHXGgfjUyPdYWZKbF8jUlss4J6xdj5tJM8n2uZ/94NU2UEooN97ypGdzbFE562A==", + "requires": { + "css-line-break": "1.1.1" + } + }, "htmlparser2": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", @@ -36222,6 +36314,27 @@ "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" }, + "tui-code-snippet": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/tui-code-snippet/-/tui-code-snippet-1.5.2.tgz", + "integrity": "sha512-6UqTlQaaC1KLcmC0HAoq5dtl1G4Fib+R+NC7pmaV7kiIlZ7JqKhUmnOoGRcreAyzd81UTK/vCvhrw9QJskpCFQ==" + }, + "tui-color-picker": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/tui-color-picker/-/tui-color-picker-2.2.7.tgz", + "integrity": "sha512-y7gUe6PdVBq8ruZbiOkeaN7N/DX+ogVyLkK53OM4qW5Vrc4pDWYEKlnOBCsPODb4/RSzS9iTd6fXYirXxeakJA==" + }, + "tui-image-editor": { + "version": "3.14.3", + "resolved": "https://registry.npmjs.org/tui-image-editor/-/tui-image-editor-3.14.3.tgz", + "integrity": "sha512-zNbC+nKVYdLSvUuHK+ZWTJw6T5Jr02oPBKrdEjfGAP7gx2yfWzHbpmrsivLyiCJzKV580N4mt0mAXttB2EClYg==", + "requires": { + "core-js-pure": "^3.6.4", + "fabric": "^4.2.0", + "tui-code-snippet": "^1.5.0", + "tui-color-picker": "^2.2.6" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7b0f211a70..ea59bf50e5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,9 +8,11 @@ "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^11.2.6", "@testing-library/user-event": "^12.8.3", + "@toast-ui/react-image-editor": "^3.14.2", "audio-react-recorder": "^1.0.4", "classnames": "^2.3.1", "fabric": "^4.5.0", + "html2canvas-objectfit-fix": "^1.2.0", "jspreadsheet-ce": "^4.7.3", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/frontend/src/gradio.jsx b/frontend/src/gradio.jsx index fd5612a62b..2e4498f420 100644 --- a/frontend/src/gradio.jsx +++ b/frontend/src/gradio.jsx @@ -1,4 +1,7 @@ import React from 'react'; +import html2canvas from 'html2canvas-objectfit-fix'; +import { saveAs } from './utils'; +import ReactDOM from "react-dom"; import classNames from "classnames"; import { AudioInput, AudioInputExample } from './interfaces/input/audio'; @@ -67,6 +70,7 @@ export class GradioInterface extends React.Component { this.flag = this.flag.bind(this); this.interpret = this.interpret.bind(this); this.removeInterpret = this.removeInterpret.bind(this); + this.takeScreenshot = this.takeScreenshot.bind(this); this.handleExampleChange = this.handleExampleChange.bind(this); this.state = this.get_default_state(); this.state["examples_page"] = 0; @@ -162,6 +166,11 @@ export class GradioInterface extends React.Component { removeInterpret() { this.setState({ "interpretation": null }); } + takeScreenshot() { + html2canvas(ReactDOM.findDOMNode(this)).then(canvas => { + saveAs(canvas.toDataURL(), 'screenshot.png'); + }); + } handleChange(_id, value) { let state_change = { [_id]: value, "has_changed": true }; if (this.props.live && !(this.state.submitting)) { @@ -238,7 +247,7 @@ export class GradioInterface extends React.Component { : false } {this.props.allow_screenshot ? - + : false} {this.props.allow_flagging ? (this.props.flagging_options === null ? diff --git a/frontend/src/interfaces/input/audio.jsx b/frontend/src/interfaces/input/audio.jsx index 23aef34d9b..ed66d94594 100644 --- a/frontend/src/interfaces/input/audio.jsx +++ b/frontend/src/interfaces/input/audio.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { DataURLComponentExample } from '../component_example'; import AudioReactRecorder, { RecordState } from 'audio-react-recorder' -import { getSaliencyColor } from '../utils'; +import { getSaliencyColor } from '../../utils'; class AudioInput extends React.Component { constructor(props) { diff --git a/frontend/src/interfaces/input/checkbox.jsx b/frontend/src/interfaces/input/checkbox.jsx index a084bfb867..d9a0ae6118 100644 --- a/frontend/src/interfaces/input/checkbox.jsx +++ b/frontend/src/interfaces/input/checkbox.jsx @@ -1,7 +1,7 @@ import React from 'react'; import ComponentExample from '../component_example'; import classNames from "classnames"; -import { getSaliencyColor } from '../utils'; +import { getSaliencyColor } from '../../utils'; class CheckboxInput extends React.Component { constructor(props) { diff --git a/frontend/src/interfaces/input/checkbox_group.jsx b/frontend/src/interfaces/input/checkbox_group.jsx index e16d6f118c..e409292e4c 100644 --- a/frontend/src/interfaces/input/checkbox_group.jsx +++ b/frontend/src/interfaces/input/checkbox_group.jsx @@ -1,7 +1,7 @@ import React from 'react'; import ComponentExample from '../component_example'; import classNames from "classnames"; -import { getSaliencyColor } from '../utils'; +import { getSaliencyColor } from '../../utils'; class CheckboxGroupInput extends React.Component { constructor(props) { diff --git a/frontend/src/interfaces/input/dropdown.jsx b/frontend/src/interfaces/input/dropdown.jsx index a8c67311dd..f55a4b2258 100644 --- a/frontend/src/interfaces/input/dropdown.jsx +++ b/frontend/src/interfaces/input/dropdown.jsx @@ -1,7 +1,7 @@ import React from 'react'; import ComponentExample from '../component_example'; import classNames from "classnames"; -import { getSaliencyColor } from '../utils'; +import { getSaliencyColor } from '../../utils'; class DropdownInput extends React.Component { constructor(props) { diff --git a/frontend/src/interfaces/input/file.jsx b/frontend/src/interfaces/input/file.jsx index dfece4ce2e..3f17788204 100644 --- a/frontend/src/interfaces/input/file.jsx +++ b/frontend/src/interfaces/input/file.jsx @@ -1,6 +1,6 @@ import React from 'react'; import ComponentExample from '../component_example'; -import {prettyBytes} from '../utils'; +import {prettyBytes} from '../../utils'; class FileInput extends React.Component { constructor(props) { diff --git a/frontend/src/interfaces/input/image.jsx b/frontend/src/interfaces/input/image.jsx index c7a374ad5a..d0b294b22e 100644 --- a/frontend/src/interfaces/input/image.jsx +++ b/frontend/src/interfaces/input/image.jsx @@ -2,7 +2,9 @@ import React from 'react'; import { DataURLComponentExample } from '../component_example'; import Webcam from "react-webcam"; import { SketchField, Tools } from '../../vendor/ReactSketch'; -import { getObjectFitSize, paintSaliency } from '../utils'; +import { getObjectFitSize, paintSaliency } from '../../utils'; +import 'tui-image-editor/dist/tui-image-editor.css'; +import ImageEditor from '@toast-ui/react-image-editor'; class ImageInput extends React.Component { constructor(props) { @@ -14,12 +16,17 @@ class ImageInput extends React.Component { this.load_preview_from_files = this.load_preview_from_files.bind(this); this.load_preview_from_upload = this.load_preview_from_upload.bind(this); this.load_preview_from_drop = this.load_preview_from_drop.bind(this); + this.saveEditor = this.saveEditor.bind(this); + this.cancelEditor = this.cancelEditor.bind(this); this.snapshot = this.snapshot.bind(this); this.getSketch = this.getSketch.bind(this); + this.openEditor = this.openEditor.bind(this); this.imgRef = React.createRef(); this.webcamRef = React.createRef(); this.sketchRef = React.createRef(); + this.editorRef = React.createRef(); this.sketchKey = 0; + this.state = { editorMode: false }; } handleChange(data) { this.props.handleChange(data); @@ -35,10 +42,25 @@ class ImageInput extends React.Component { let imageSrc = this.sketchRef.current.toDataURL(); this.handleChange(imageSrc); } - onImgLoad({target:img}) { - this.setState({dimensions:{height:img.offsetHeight, - width:img.offsetWidth}}); - } + cancelEditor() { + this.setState({"editorMode": false}); + } + saveEditor() { + const editorInstance = this.editorRef.current.getInstance(); + this.handleChange(editorInstance.toDataURL()); + this.setState({"editorMode": false}); + } + onImgLoad({ target: img }) { + this.setState({ + dimensions: { + height: img.offsetHeight, + width: img.offsetWidth + } + }); + } + openEditor() { + this.setState({ editorMode: true }) + } render() { let no_action = (evt) => { evt.preventDefault(); @@ -65,6 +87,29 @@ class ImageInput extends React.Component { return (
+ {this.state.editorMode ? +
+
+ + +
+ +
+ : + }
{interpretation} @@ -101,7 +146,7 @@ class ImageInput extends React.Component { lineWidth={20} backgroundColor="white" onChange={this.getSketch} - /> + />
); } diff --git a/frontend/src/interfaces/input/number.jsx b/frontend/src/interfaces/input/number.jsx index a77e8a363c..cd2146cc8a 100644 --- a/frontend/src/interfaces/input/number.jsx +++ b/frontend/src/interfaces/input/number.jsx @@ -1,6 +1,6 @@ import React from 'react'; import ComponentExample from '../component_example'; -import { getSaliencyColor } from '../utils'; +import { getSaliencyColor } from '../../utils'; class NumberInput extends React.Component { constructor(props) { diff --git a/frontend/src/interfaces/input/radio.jsx b/frontend/src/interfaces/input/radio.jsx index 5eac15568b..c34fa3ef8c 100644 --- a/frontend/src/interfaces/input/radio.jsx +++ b/frontend/src/interfaces/input/radio.jsx @@ -1,7 +1,7 @@ import React from 'react'; import ComponentExample from '../component_example'; import classNames from "classnames"; -import { getSaliencyColor } from '../utils'; +import { getSaliencyColor } from '../../utils'; class RadioInput extends React.Component { constructor(props) { diff --git a/frontend/src/interfaces/input/slider.jsx b/frontend/src/interfaces/input/slider.jsx index 87357d9ece..b5c99c7d2c 100644 --- a/frontend/src/interfaces/input/slider.jsx +++ b/frontend/src/interfaces/input/slider.jsx @@ -1,6 +1,6 @@ import React from 'react'; import ComponentExample from '../component_example'; -import { getSaliencyColor } from '../utils'; +import { getSaliencyColor } from '../../utils'; class SliderInput extends React.Component { constructor(props) { diff --git a/frontend/src/interfaces/input/textbox.jsx b/frontend/src/interfaces/input/textbox.jsx index 2d2c34ff63..5713cf295b 100644 --- a/frontend/src/interfaces/input/textbox.jsx +++ b/frontend/src/interfaces/input/textbox.jsx @@ -1,6 +1,6 @@ import React from 'react'; import ComponentExample from '../component_example'; -import { getSaliencyColor } from '../utils'; +import { getSaliencyColor } from '../../utils'; class TextboxInput extends React.Component { constructor(props) { diff --git a/frontend/src/interfaces/output/file.jsx b/frontend/src/interfaces/output/file.jsx index b13cc6229c..768e6fc33c 100644 --- a/frontend/src/interfaces/output/file.jsx +++ b/frontend/src/interfaces/output/file.jsx @@ -1,6 +1,6 @@ import React from 'react'; import ComponentExample from '../component_example'; -import {prettyBytes} from '../utils'; +import {prettyBytes} from '../../utils'; class FileOutput extends React.Component { render() { diff --git a/frontend/src/themes/defaults.scss b/frontend/src/themes/defaults.scss index 8c4debfb2a..d416517039 100644 --- a/frontend/src/themes/defaults.scss +++ b/frontend/src/themes/defaults.scss @@ -160,6 +160,26 @@ .image_preview_holder { @apply w-full h-full flex justify-center items-center bg-gray-200 relative inline-block; } + .edit_button { + @apply absolute top-1 right-1 bg-yellow-500 text-white px-2 py-1; + } + .image_editor { + @apply fixed w-screen h-screen top-0 left-0 bg-black bg-opacity-50 z-40 flex flex-col justify-center items-center; + .image_editor_buttons { + width: 800px; + @apply flex justify-end gap-1; + button { + @apply px-2 py-1 text-xl bg-black text-white font-semibold rounded-t; + } + } + .tui-image-editor-header-buttons { + @apply hidden; + } + .tui-colorpicker-palette-button { + width: 12px; + height: 12px; + } + } .image_preview, video { @apply w-full h-full object-contain; diff --git a/frontend/src/themes/huggingface.scss b/frontend/src/themes/huggingface.scss index 57d91bbc22..4c25540297 100644 --- a/frontend/src/themes/huggingface.scss +++ b/frontend/src/themes/huggingface.scss @@ -11,7 +11,7 @@ @apply pb-4; } .article { - @apply pt-8 pb-4 max-w-none; + @apply pt-8 pb-4 max-w-none; } } @@ -40,6 +40,23 @@ .panel_buttons { @apply flex gap-4 my-4; } + .flag { + @apply relative; + .dropcontent { + @apply hidden absolute top-8 left-0 bg-white border-gray-200 border-2 w-full; + div { + @apply p-2; + } + div:hover { + @apply bg-gray-100; + } + } + } + .flag:hover:not(.disabled) { + .dropcontent { + @apply block; + } + } .screenshot_set { @apply hidden flex hidden flex-grow; } @@ -86,33 +103,71 @@ } } /* Input Components */ - .input_text textarea { - @apply w-full p-3 border rounded-lg shadow-inner outline-none focus:ring-1 focus:ring-inset focus:ring-indigo-200 focus:shadow-inner placeholder-gray-400; + .input_text { + textarea { + @apply w-full p-3 border rounded-lg shadow-inner outline-none focus:ring-1 focus:ring-inset focus:ring-indigo-200 focus:shadow-inner placeholder-gray-400; + } + input { + @apply w-full p-3 border rounded-lg shadow-inner outline-none focus:ring-1 focus:ring-inset focus:ring-indigo-200 focus:shadow-inner placeholder-gray-400; + } + .interpretation { + .interpretation_box { + @apply inline-block whitespace-pre-wrap; + } + } } - .input_text input { - @apply w-full p-3 border rounded-lg shadow-inner outline-none focus:ring-1 focus:ring-inset focus:ring-indigo-200 focus:shadow-inner placeholder-gray-400; - } - .input_number input { - @apply w-full p-3 border rounded-lg shadow-inner outline-none focus:ring-1 focus:ring-inset focus:ring-indigo-200 focus:shadow-inner placeholder-gray-400; + .input_number { + input { + @apply w-full p-3 border rounded-lg shadow-inner outline-none focus:ring-1 focus:ring-inset focus:ring-indigo-200 focus:shadow-inner placeholder-gray-400; + } + .interpretation { + @apply flex h-6; + .interpretation_box { + @apply flex-grow; + } + } } .input_image { - @apply w-full h-80; + @apply w-full h-80 relative; .upload_zone { @apply border-8 border-gray-300 border-dashed w-full h-full flex justify-center items-center text-3xl text-gray-400 text-center cursor-pointer leading-10; } .image_preview_holder { @apply w-full h-full flex justify-center items-center bg-gray-300 relative inline-block; } - .sketch > div { - @apply bg-white; + .edit_button { + @apply absolute top-1 right-1 bg-indigo-500 text-white px-2 py-1; + } + .image_editor { + @apply fixed w-screen h-screen top-0 left-0 bg-black bg-opacity-50 z-40 flex flex-col justify-center items-center; + .image_editor_buttons { + width: 800px; + @apply flex justify-end gap-1; + button { + @apply px-2 py-1 text-xl bg-black text-white font-semibold rounded-t; + } + } + .tui-image-editor-header-buttons { + @apply hidden; + } + .tui-colorpicker-palette-button { + width: 12px; + height: 12px; + } } .image_preview, video { @apply w-full h-full object-contain; } + .sketch > div { + @apply bg-white; + } .snapshot { @apply absolute bottom-0 w-full bg-white bg-opacity-90 py-3 font-bold text-lg text-center; } + .interpretation { + @apply w-full h-full absolute top-0 left-0 flex justify-center items-center opacity-90 hover:opacity-20 transition; + } } .input_image_example { @apply h-24; @@ -157,6 +212,20 @@ stroke-width: 4; stroke-linecap: round; } + .interpretation { + @apply flex; + .interpretation_box { + @apply flex-grow h-4 w-4; + } + .interpret_check { + @apply w-full h-full; + } + .interpret_check line { + stroke: gray; + stroke-width: 4; + stroke-linecap: round; + } + } } .input_checkbox { @apply flex flex-wrap gap-2; @@ -183,6 +252,20 @@ stroke-width: 4; stroke-linecap: round; } + .interpretation { + @apply flex; + .interpretation_box { + @apply flex-grow h-4 w-4; + } + .interpret_check { + @apply w-full h-full; + } + .interpret_check line { + stroke: gray; + stroke-width: 4; + stroke-linecap: round; + } + } } .input_dropdown { @apply inline-block relative; @@ -210,6 +293,9 @@ :hover .dropdown_menu { @apply block; } + .interpretation_box { + @apply p-1; + } } .input_slider { @apply text-center; @@ -226,6 +312,12 @@ .value { @apply inline-block mx-auto mt-1 px-2 py-0.5 bg-gray-100 text-gray-700 rounded shadow-inner; } + .interpret_range { + @apply flex h-6; + div { + @apply flex-grow; + } + } } .input_audio { .audio-react-recorder { @@ -239,6 +331,12 @@ } audio { @apply w-full; + .interpret_range { + @apply flex h-4; + div { + @apply flex-grow h-full; + } + } } } .input_video { @@ -268,7 +366,6 @@ @apply text-2xl p-2; } } - /* Output Components */ .output_text { word-break: break-word; diff --git a/frontend/src/interfaces/utils.jsx b/frontend/src/utils.jsx similarity index 70% rename from frontend/src/interfaces/utils.jsx rename to frontend/src/utils.jsx index b751e741b1..f60933d218 100644 --- a/frontend/src/interfaces/utils.jsx +++ b/frontend/src/utils.jsx @@ -63,13 +63,29 @@ export function paintSaliency(data, ctx, width, height) { var cell_width = width / data[0].length var cell_height = height / data.length var r = 0 - data.forEach(function(row) { - var c = 0 - row.forEach(function(cell) { - ctx.fillStyle = getSaliencyColor(cell); - ctx.fillRect(c * cell_width, r * cell_height, cell_width, cell_height); - c++; - }) - r++; + data.forEach(function (row) { + var c = 0 + row.forEach(function (cell) { + ctx.fillStyle = getSaliencyColor(cell); + ctx.fillRect(c * cell_width, r * cell_height, cell_width, cell_height); + c++; + }) + r++; }) +} + +export function saveAs(uri, filename) { + var link = document.createElement('a'); + if (typeof link.download === 'string') { + link.href = uri; + link.download = filename; + //Firefox requires the link to be in the body + document.body.appendChild(link); + //simulate click + link.click(); + //remove the link when done + document.body.removeChild(link); + } else { + window.open(uri); + } } \ No newline at end of file diff --git a/gradio.egg-info/SOURCES.txt b/gradio.egg-info/SOURCES.txt index b6f1c95f59..25745b07a5 100644 --- a/gradio.egg-info/SOURCES.txt +++ b/gradio.egg-info/SOURCES.txt @@ -26,6 +26,30 @@ gradio.egg-info/requires.txt gradio.egg-info/top_level.txt gradio/frontend/asset-manifest.json gradio/frontend/index.html +gradio/frontend/static/bundle.css +gradio/frontend/static/bundle.css.map +gradio/frontend/static/bundle.js +gradio/frontend/static/bundle.js.LICENSE.txt +gradio/frontend/static/bundle.js.map +gradio/frontend/static/css/main.20be28ac.css +gradio/frontend/static/css/main.20be28ac.css.map +gradio/frontend/static/css/main.2b64a968.css +gradio/frontend/static/css/main.2b64a968.css.map +gradio/frontend/static/css/main.380e3222.css +gradio/frontend/static/css/main.380e3222.css.map +gradio/frontend/static/css/main.4aea80f8.css +gradio/frontend/static/css/main.4aea80f8.css.map +gradio/frontend/static/css/main.4f157d97.css +gradio/frontend/static/css/main.4f157d97.css.map +gradio/frontend/static/css/main.5c663906.css +gradio/frontend/static/css/main.5c663906.css.map +gradio/frontend/static/css/main.99922310.css +gradio/frontend/static/css/main.99922310.css.map +gradio/frontend/static/css/main.acb02c85.css +gradio/frontend/static/css/main.acb02c85.css.map +gradio/frontend/static/css/main.cbbf8898.css +gradio/frontend/static/css/main.cbbf8898.css.map +gradio/frontend/static/media/logo_loading.e93acd82.jpg test/test_demos.py test/test_inputs.py test/test_interfaces.py diff --git a/gradio/component.py b/gradio/component.py index e5f0adeabb..c3cb1a35f4 100644 --- a/gradio/component.py +++ b/gradio/component.py @@ -7,8 +7,9 @@ class Component(): A class for defining the methods that all gradio input and output components should have. """ - def __init__(self, label): + def __init__(self, label, requires_permissions=False): self.label = label + self.requires_permissions = requires_permissions def __str__(self): return self.__repr__() diff --git a/gradio/frontend/asset-manifest.json b/gradio/frontend/asset-manifest.json index 68cd6e3726..96912c37a9 100644 --- a/gradio/frontend/asset-manifest.json +++ b/gradio/frontend/asset-manifest.json @@ -1,17 +1,17 @@ { "files": { - "main.css": "/static/css/main.4f157d97.css", + "main.css": "/static/css/main.5c663906.css", "main.js": "/static/bundle.js", "main.js.map": "/static/bundle.js.map", "index.html": "/index.html", "static/bundle.css.map": "/static/bundle.css.map", "static/bundle.js.LICENSE.txt": "/static/bundle.js.LICENSE.txt", - "static/css/main.4f157d97.css.map": "/static/css/main.4f157d97.css.map", + "static/css/main.5c663906.css.map": "/static/css/main.5c663906.css.map", "static/media/logo_loading.e93acd82.jpg": "/static/media/logo_loading.e93acd82.jpg" }, "entrypoints": [ "static/bundle.css", - "static/css/main.4f157d97.css", + "static/css/main.5c663906.css", "static/bundle.js" ] } \ No newline at end of file diff --git a/gradio/frontend/index.html b/gradio/frontend/index.html index 44e43b0965..b03d2a39b0 100644 --- a/gradio/frontend/index.html +++ b/gradio/frontend/index.html @@ -8,4 +8,4 @@ window.config = {{ config|tojson }}; } catch (e) { window.config = {}; - }Gradio
\ No newline at end of file + }Gradio
\ No newline at end of file diff --git a/gradio/inputs.py b/gradio/inputs.py index 009c0d767d..a0d3e5f983 100644 --- a/gradio/inputs.py +++ b/gradio/inputs.py @@ -28,9 +28,9 @@ class InputComponent(Component): """ Input Component. All input components subclass this. """ - def __init__(self, label): + def __init__(self, label, requires_permissions=False): self.interpret() - super().__init__(label) + super().__init__(label, requires_permissions) def preprocess(self, x): """ @@ -642,11 +642,12 @@ class Image(InputComponent): self.shape = shape self.image_mode = image_mode self.source = source + requires_permissions = source == "webcam" self.tool = tool self.type = type self.invert_colors = invert_colors self.test_input = test_data.BASE64_IMAGE - super().__init__(label) + super().__init__(label, requires_permissions) @classmethod def get_shortcut_implementations(cls): @@ -821,9 +822,10 @@ class Audio(InputComponent): label (str): component name in interface. """ self.source = source + requires_permissions = source == "microphone" self.type = type self.test_input = test_data.BASE64_AUDIO - super().__init__(label) + super().__init__(label, requires_permissions) def get_template_context(self): return { diff --git a/gradio/interface.py b/gradio/interface.py index 1e3ec20129..3815ea9bec 100644 --- a/gradio/interface.py +++ b/gradio/interface.py @@ -183,6 +183,7 @@ class Interface: self.local_url = None self.embedding = embedding self.show_tips = show_tips + self.requires_permissions = any([component.requires_permissions for component in self.input_components]) data = {'fn': fn, 'inputs': inputs, @@ -486,6 +487,8 @@ class Interface: print(strings.en["COLAB_DEBUG_FALSE"]) else: print(strings.en["RUNNING_LOCALLY"].format(path_to_local_server)) + if is_colab and self.requires_permissions: + print(strings.en["MEDIA_PERMISSIONS_IN_COLAB"]) if private_endpoint is not None: share = True @@ -567,18 +570,9 @@ class Interface: def show_tip(io): - if not(io.show_tips): + if not(io.show_tips) or random.random() < 0.5: # Only show tip every other use. return - if random.random() < 0.8: # Only show tips once every 5 uses - return - relevant_tips = [] - if io.interpretation is None: - relevant_tips.append(strings.en["TIP_INTERPRETATION"]) - if io.embedding is None and not(io.examples is None) and len(io.examples)>4: - relevant_tips.append(strings.en["TIP_EMBEDDING"]) - if len(relevant_tips)==0: - return - print(random.choice(relevant_tips)) + print(random.choice(strings.en.TIPS)) def launch_counter(): try: diff --git a/gradio/strings.py b/gradio/strings.py index c0e4263ac2..bf802ad678 100644 --- a/gradio/strings.py +++ b/gradio/strings.py @@ -23,8 +23,14 @@ en = { "SHARE_LINK_MESSAGE": "This share link will expire in 24 hours. If you need a permanent link, visit: https://gradio.app/introducing-hosted (NEW!)", "SHARE_LINK_DISPLAY": "Running on External URL: {}", "INLINE_DISPLAY_BELOW": "Interface loading below...", - "TIP_INTERPRETATION": "Tip: Add interpretation to your model by simply adding `interpretation=\"default\"` to `Interface()`", - "TIP_EMBEDDING": "Tip: View embeddings of your dataset by simply adding `embedding=\"default\"` to `Interface()`", + "MEDIA_PERMISSIONS_IN_COLAB": "Your interface requires microphone or webcam permissions - this may cause issues in Colab. Use the External URL in case of issues.", + "TIPS": [ + "You can add authentication to your app with the auth= kwarg in the launch command; for example: gr.Interface(...).launch(auth=('username', 'password'))", + "Let users specify why they flagged input with the flagging_options= kwarg; for example: gr.Interface(..., flagging_options=['too slow', 'incorrect output', 'other'])", + "You can show or hide the buttons for flagging, screenshots, and interpretation with the allow_*= kwargs; for example: gr.Interface(..., allow_screenshot=True, allow_flagging=False)", + "The inputs and outputs flagged by the users are stored in the flagging directory, specified by the flagging_dir= kwarg. You can view this data through the interface by setting the examples= kwarg to the flagging directory; for example gr.Interface(..., examples='flagged')", + "You can add a title and description to your interface using the title= and description= kwargs. The article= kwarg can be used to add markdown or HTML under the interface; for example gr.Interface(..., title='My app', description='Lorem ipsum')" + ] } try: