gradio-app / gradio

Build and share delightful machine learning apps, all in Python. 🌟 Star to support our work!
http://www.gradio.app
Apache License 2.0
33.41k stars 2.52k forks source link

Alternative Component Update API in 4.0 #4926

Closed freddyaboulton closed 1 year ago

freddyaboulton commented 1 year ago

Is your feature request related to a problem? Please describe.
In https://github.com/gradio-app/gradio/issues/4774, there is a new api for component updates being proposed. In short, the proposal is to return a new component instance rather than using the update method. For example, gr.Dropdown(choices=['a', 'b', 'c']) as opposed to gr.Dropdown.update(choices=['a', 'b', 'c']).

I like this, but it requires every component's default value be None and then do some stuff under the hood to determine when components are created with event handlers and then back out what the values changed were.

I don't like having to enforce a particular signature on __init__ for components (an extra barrier for custom components) and it treats updating the value vs updating the other props of a component as different concepts.

Given we'll be storing the component state internally for each session, we can pass the current instance of the component to the event handler. Updating the component's props, will just be updating the properties of the component.

For an example:

def update_dropdown(dd):
    dd.value = 'b'
    dd.choices = ['a', 'b', 'c']
    return dd

Another example, inspired by modelscope demo that I've been looking at this week, where the event generates a video and opens an accordion:

def generate_video(prompt):
    vid = pipeline.generate(prompt.value)
    return gr.Video(value=vid), gr.Accordion(open=True)

Here's what I see as the pros and cons:

Pros

Cons

abidlabs commented 1 year ago

The downside I see with:

def update_dropdown(dd):
    dd.value = 'b'
    dd.choices = ['a', 'b', 'c']
    return dd

is that now we are passing components as parameters instead of regular values, which means that you have to think about your Gradio app when you are writing your functions, and you can't test your functions in isolation from your Gradio app. Also, the API seems to have limited utility since you can only update a component's attributes if they are also inputs to your function, otherwise you have to return a gr.Component(...) anyways, like we do in this example:

def generate_video(prompt):
    vid = pipeline.generate(prompt.value)
    return gr.Video(value=vid), gr.Accordion(open=True)

I would be in favor of eliminating the .update() and instead just providing an instance of the component itself, which I think @aliabid94 also suggested in #4774

aliabid94 commented 1 year ago

I have thought about a similar approach, where we embrace this "attribute getter/setter" API style entirely:

with gr.Blocks() as demo:
  num1 = gr.Number()
  num2 = gr.Number()
  operation = gr.Radio("add", "subtract", ...)
  result = gr.Number(visible=False)

  def calculate():
    result.visible = True
    if operation.value == "add"
      result.value = num1.value + num2.value
    elif operation.value == "subtract"
      ...

In this API, we override the getters and setters of all components, and use thread locals and websockets to get / send component updates immediately, in the same manner as we send gr.Warnings. Clearly a huge departure from our current style of event listeners.

Pros Cons
No need to specify inputs and outputs Need to specify interactive as it cannot be determined beforehand
Intuitive API Very different from our current style
abidlabs commented 1 year ago

Two more cons:

abidlabs commented 1 year ago

This has been implemented now!