gradio
gradio copied to clipboard
A change handler for `gr.State`
- [x] I have searched to see if a similar issue already exists.
Is your feature request related to a problem? Please describe.
I have 3 different input components that affect a variable -- essentially in my app, you can upload a document one of 3 ways (file upload, url, or an example). Each upload updates a document
variable, which has the bytes of the upload + some pre-computed metadata. I'd like any of these three methods of upload to update some state that depends on the value of the document.
Describe the solution you'd like
It'd be very convenient to write something like
document.change(process_document, document, output_component)
so that each of the 3 upload components simply needs to update the variable, and then the rest of the pipeline runs.
Additional context
N/A but happy to provide more details if helpful.
Thanks for creating the issue @ankrgyl. Would you be able to provide a more complete example here of what you'd like to do so we can use it to reproduce and test the fix?
Absolutely! Here is the program I'd like to be able to write:
import gradio as gr
import requests
import os
def process_url(url):
return requests.get(url, stream=True).content.read()
def process_file(file):
return open(file.name, "rb").read()
with gr.Blocks() as demo:
file_bytes = gr.Variable()
with gr.Row():
url = gr.Textbox(label="URL", interactive=True)
file = gr.File(label="Upload", interactive=True)
byte_len = gr.JSON(label="File length")
file.change(process_file, inputs=[file], outputs=[file_bytes])
url.change(process_url, inputs=[file], outputs=[file_bytes])
file_bytes.change(lambda b: len(b), inputs=[file_bytes], outputs=[byte_len])
if __name__ == "__main__":
demo.launch()
and the one I have to write:
import gradio as gr
import requests
import os
def process_url(url):
b = requests.get(url, stream=True).content.read()
return b, len(b)
def process_file(file):
b = open(file.name, "rb").read()
return b, len(b)
with gr.Blocks() as demo:
file_bytes = gr.Variable()
with gr.Row():
url = gr.Textbox(label="URL", interactive=True)
file = gr.File(label="Upload", interactive=True)
byte_len = gr.JSON(label="File length")
file.change(process_file, inputs=[file], outputs=[file_bytes, byte_len])
url.change(process_url, inputs=[file], outputs=[file_bytes, byte_len])
if __name__ == "__main__":
demo.launch()
(I omitted some details, like other uses of the variable, but imagine there's another control in the app, like a radio button which selects which "metric" you want to display about the file).
Hope this helps, and apologize if I missed something in the docs or elsewhere.
A big +1 to this request. In my case I keep a per-session list of items in a gr.State
and in my UI (across several tabs) populate several gr.Dropdown
instances with the contents of that list. When the list changes (e.g. I edit an item or create a new one) I need all those dropdowns to refresh & reflect the updated state.
I've used the workaround @ankrgyl mentions above, where (in my example case) anything that modifies the gr.State
would also be responsible for with generating updates to the gr.Dropdown
instances. It works, and wasn't a big deal when my app was toy-sized, but UI code can quickly become quite hard to read, and arranging the flow of change events sensibly is important in keeping the code logically legible.
I ended up with an even more arcane workaround for now, but I'd dearly love to toss it.
It might make sense for us to think of this as part of #2570 @aliabid94 instead of creating a specific gr.State.change()
event
This issue proposes a State.change -> IOComponent.update
flow, which is the general solution to state reactivity and effectively fixes the IOComponent.change -> IOComponent.update
flow expectation that is being violated.
I believe #2570 is syntactic sugar for the special State.change -> State.update
flow and can be deferred until the underlying functionality proposed in this issue is implemented.
I would love this feature too. Being able to decouple components via a state variable is really powerful and allows you to simplify the code. It's currently really hard to have some kind of status / state trigger other components without explicitly adding them. It becomes even more complicated when you want something to update itself based on some state because then change handlers on that component will end up firing multiple times or end up in an infinite loop.