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.09k stars 2.49k forks source link

How to dynamic create the output component #2066

Closed qustions closed 2 years ago

qustions commented 2 years ago

Hello @abidlabs @aliabid94, can we create dynamic output on gradio for example

all_output = []
def generate_output(number_of_outputs):
     for x in range(number_of_outputs):
          bnt = gr.Button(x)
          all.append(bnt)

gen_output.click(
        generate_output,
        [number_of_outputs],
        all_output,
    )

demo.launch()

the code is running but not generating the outputs can you share the example to generate dynamic output using for loop

abidlabs commented 2 years ago

Unfortunately, it is not possible to create components dynamically like this. However, you can create a bunch of components initially and then simply hide them or display them based on the function. Here's an example of this in action:

import gradio as gr

def words():
    sentence = "A test of Gradio"
    words = sentence.split()
    update_show = [gr.Button.update(visible=True, value=w) for w in words]
    update_hide = [gr.Button.update(visible=False, value="") for _ in range(10-len(words))]
    return update_show + update_hide 

btn_list = []

with gr.Blocks() as demo:
    with gr.Row():
        for i in range(10):
            btn = gr.Button(visible=False)
            btn_list.append(btn)
    b = gr.Button("Run")
    b.click(words, None, btn_list)

demo.launch()
abidlabs commented 2 years ago

Closing for now, but feel free to reopen if this doesn't solve your issue.

vzakharov commented 2 years ago

@abidlabs , just curious, is dynamic component creation anywhere in the roadmap? Or would it be too much hassle for too little gain?

abidlabs commented 2 years ago

Given that it is possible to mimic this effect by toggling visibility, we haven't really discussed prioritizing this feature.

vzakharov commented 1 year ago

Given that it is possible to mimic this effect by toggling visibility, we haven't really discussed prioritizing this feature.

This doesn’t quite work if there is an arbitrary number of components. For example, I’m building an “app” based on OpenAI Jukebox (music generation), and the model can return several audios. I currently “pre-create” (as you suggested) ten of dummy audio components, and show/hide them later on depending on how many outputs the model gives, but that doesn’t look quite optimal. (Not to mention that it won’t work if there is more than 10 outputs, in which case I’ll need to implement some pagination.)

But anyway, this is more of an inconvenience rather than stopper at this point.

abidlabs commented 1 year ago

Hi @vzakharov just to understand, why doesn't it look optimal? What would need to be changed for it to look better?

vzakharov commented 1 year ago

Hi @vzakharov just to understand, why doesn't it look optimal? What would need to be changed for it to look better?

Well, first of all you never know how many outputs there will be. It can be 10 or it can be 100.

Second, the very code for hiding/unhiding certain components based on the number of outputs (and assigning each component to a specific output) is bulky and unsexy 🙂

Finally, having any number of hidden HTML elements just sitting there waiting to be unhidden sounds like an anti-pattern.

abidlabs commented 1 year ago

Hmm interesting. We'll think about this!

davidbernat commented 1 year ago

Contributing to this thread, re: @abidlabs and @vzakharov. The visibility solution does not work for a dynamic and arbitrary number of components. This also exposes unnecessary bloat and insecurity to the front-end user. See also: Firebase authentication #2790.

@abidlabs: do statistics exist for how number of components, c.f. visibility solution, affect compile, render, and UI speeds and performance? re: best practices, etc.

phitoduck commented 1 year ago

Hi there. +1 to supporting this feature.

We've been able to finagle Streamlit to do this. We use Streamlit internally for all manner of internal apps, many DS-related, many not.

We often develop a Streamlit and FastAPI (or BentoML) REST API in tandem, calling the REST API from Streamlit to demonstrate to other teams how our (non)ML API could be used.

Naturally, REST API's often return array's of responses of arbitrary length. It would be great to be able to visualize the results. Or (for example), what if you want to allow the user to upload a potentially arbitrary number of images to give context to something like StableDiffusion or DreamBooth?

The ability to add a "+" button that adds another "Image Upload" element to the page and has it's own state would be quite helpful here.

Anyway, I get the sense Gradio is better loved/supported than Streamlit, so naturally I want it to be able to do everything Streamlit can do and then some, but I understand this likely goes against fundamental design assumptions of Gradio.

abidlabs commented 1 year ago

Hi @phitoduck thanks for the comment. You could achieve something like this by creating a large number of image components, whose visibility you toggle (the same workaround described earlier: https://github.com/gradio-app/gradio/issues/2066#issuecomment-1224515753)

We're also seeking ideas for how to expand the Gradio API to support dynamic use cases in this issue: https://github.com/gradio-app/gradio/issues/4689 -- it would be super helpful if you can chime in with your feature request there!

rburke45 commented 11 months ago

Is there a method for handling arbitrary layouts? For example if I wanted to return a list of experimental results, but each result is a layout with nested layouts and components. The fact that gradio uses the context manager for layouts means you can't store the layout in a list as shown above and it complicates updating the components in a specific layout.

Fusseldieb commented 10 months ago

+1. It's quite a hassle to hide and unhide stuff.

saiakarsh193 commented 10 months ago

Unfortunately, it is not possible to create components dynamically like this. However, you can create a bunch of components initially and then simply hide them or display them based on the function. Here's an example of this in action:

import gradio as gr

def words():
    sentence = "A test of Gradio"
    words = sentence.split()
    update_show = [gr.Button.update(visible=True, value=w) for w in words]
    update_hide = [gr.Button.update(visible=False, value="") for _ in range(10-len(words))]
    return update_show + update_hide 

btn_list = []

with gr.Blocks() as demo:
    with gr.Row():
        for i in range(10):
            btn = gr.Button(visible=False)
            btn_list.append(btn)
    b = gr.Button("Run")
    b.click(words, None, btn_list)

demo.launch()

@abidlabs Component.update() method is not callable anymore (v4.7.1). is there any alternative to achieve the same?

abidlabs commented 10 months ago

Hi @saiakarsh193 you can now just return the component itself. E.g. your function would look like this:

def words():
    sentence = "A test of Gradio"
    words = sentence.split()
    update_show = [gr.Button(visible=True, value=w) for w in words]
    update_hide = [gr.Button(visible=False, value="") for _ in range(10-len(words))]
    return update_show + update_hide 

everything else would be unchanged

saiakarsh193 commented 10 months ago

@abidlabs Thanks for the response. I just figured out that we can also do this to achieve the same:

def words():
    sentence = "A test of Gradio"
    words = sentence.split()
    update_show = [gr.update(visible=True, value=w) for w in words]
    update_hide = [gr.update(visible=False, value="") for _ in range(10-len(words))]
    return update_show + update_hide
abidlabs commented 10 months ago

Yes, that also works, although I believe you don't get the type-hinting in your IDE

abhishek0093 commented 8 months ago

Hi @saiakarsh193 you can now just return the component itself. E.g. your function would look like this:

def words():
    sentence = "A test of Gradio"
    words = sentence.split()
    update_show = [gr.Button(visible=True, value=w) for w in words]
    update_hide = [gr.Button(visible=False, value="") for _ in range(10-len(words))]
    return update_show + update_hide 

everything else would be unchanged

Hi @abidlabs, I'm facing some difficulty with the suggested method when I'm extending it to return 2 items like this :

def words() : 
    sentence = "A test of Gradio"
    words = sentence.split()
    update_show = [gr.Button(visible=True, value=w) for w in words]
    update_hide = [gr.Button(visible=False, value="") for _ in range(MAX_ITEMS-len(words))]

    sentence2 = "A Double test of Gradio"
    words2 = sentence2.split()
    update_show2 = [gr.Button(visible=True, value=w) for w in words2]
    update_hide2 = [gr.Button(visible=False, value="") for _ in range(MAX_ITEMS-len(words2))]

    return update_show + update_hide, update_show2 + update_hide2

output_list = []
output_list2 = []
MAX_ITEMS = 10

with gr.Blocks() as demo:
    with gr.Row() : 
        with gr.Column() :
            with gr.Row() : 
                button = gr.Button("Go")

            with gr.Row() :
                for i in range(MAX_ITEMS) : 
                    output_res = gr.Button(visible=False)
                    output_list.append(output_res) 

            with gr.Row() : 
                for i in range(MAX_ITEMS) : 
                    output_res2 = gr.Button(visible=False)
                    output_list2.append(output_res2) 

    button.click(fn=words, inputs=None, outputs=[output_list, output_list2])

demo.launch()

It's working as expected if i return and update only 1 of the list. But for both it gives this error : AttributeError: 'list' object has no attribute '_id'. Basically it's not allowing me to pass list of outputs to outputs parameter in .click() function. I'm not sure what's going wrong here or if I'm missing something.

jeffliu-LL commented 5 months ago

Hi @abidlabs, I'm facing some difficulty with the suggested method when I'm extending it to return 2 items like this :

def words() : 
    sentence = "A test of Gradio"
    words = sentence.split()
    update_show = [gr.Button(visible=True, value=w) for w in words]
    update_hide = [gr.Button(visible=False, value="") for _ in range(MAX_ITEMS-len(words))]

    sentence2 = "A Double test of Gradio"
    words2 = sentence2.split()
    update_show2 = [gr.Button(visible=True, value=w) for w in words2]
    update_hide2 = [gr.Button(visible=False, value="") for _ in range(MAX_ITEMS-len(words2))]

    return update_show + update_hide, update_show2 + update_hide2

output_list = []
output_list2 = []
MAX_ITEMS = 10

with gr.Blocks() as demo:
    with gr.Row() : 
        with gr.Column() :
            with gr.Row() : 
                button = gr.Button("Go")

            with gr.Row() :
                for i in range(MAX_ITEMS) : 
                    output_res = gr.Button(visible=False)
                    output_list.append(output_res) 

            with gr.Row() : 
                for i in range(MAX_ITEMS) : 
                    output_res2 = gr.Button(visible=False)
                    output_list2.append(output_res2) 

    button.click(fn=words, inputs=None, outputs=[output_list, output_list2])

demo.launch()

It's working as expected if i return and update only 1 of the list. But for both it gives this error : AttributeError: 'list' object has no attribute '_id'. Basically it's not allowing me to pass list of outputs to outputs parameter in .click() function. I'm not sure what's going wrong here or if I'm missing something.

The function needs to return a single (unnested) list, so have words return return update_show + update_hide + update_show2 + update_hide2, and have the button outputs take button.click(fn=words, inputs=None, outputs=output_list + output_list2)

secsilm commented 4 months ago

Unfortunately, it is not possible to create components dynamically like this. However, you can create a bunch of components initially and then simply hide them or display them based on the function. Here's an example of this in action:

import gradio as gr

def words():
    sentence = "A test of Gradio"
    words = sentence.split()
    update_show = [gr.Button.update(visible=True, value=w) for w in words]
    update_hide = [gr.Button.update(visible=False, value="") for _ in range(10-len(words))]
    return update_show + update_hide 

btn_list = []

with gr.Blocks() as demo:
    with gr.Row():
        for i in range(10):
            btn = gr.Button(visible=False)
            btn_list.append(btn)
    b = gr.Button("Run")
    b.click(words, None, btn_list)

demo.launch()

Note that you cannot use the form [gr.Button()] * n when initializing btn_list, as all the gr.Button() obtained this way will be the same, and they will still not be displayed in the end:

image

abidlabs commented 4 months ago

Correct, and btw we are planning on introducing a much nicer way to render components dynamically, see https://github.com/gradio-app/gradio/pull/8243