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
34.13k stars 2.6k forks source link

Code component event listeners not working properly when being dynamically rendered with gr.render #8670

Open z3lx opened 4 months ago

z3lx commented 4 months ago

Describe the bug

The event listeners for the gr.Code component does not seem to work properly when used in a function with the gr.render decorator.

These issues are not observed when using a gr.Textbox or a gr.TextArea.

Have you searched existing issues? 🔎

Reproduction

import gradio as gr

def identity(v):
    print(v)
    return v

with gr.Blocks() as demo:
    show_checkbox = gr.Checkbox(label="Show", value=True)
    text_state = gr.State("Some text")

    @gr.render(inputs=[show_checkbox, text_state])
    def render(show: bool, text: str) -> None:
        if not show:
            return
        text_input = gr.Code(
            value=text,
        )
        text_input.input(
            fn=identity,
            inputs=[text_input],
            outputs=[text_state],
        )

demo.launch()

Screenshot

No response

Logs

No response

System Info

Gradio Environment Information:
------------------------------
Operating System: Windows
gradio version: 4.37.2
gradio_client version: 1.0.2

------------------------------------------------
gradio dependencies in your environment:

aiofiles: 23.2.1
altair: 5.3.0
fastapi: 0.111.0
ffmpy: 0.3.2
gradio-client==1.0.2 is not installed.
httpx: 0.27.0
huggingface-hub: 0.23.3
importlib-resources: 6.4.0
jinja2: 3.1.3
markupsafe: 2.1.5
matplotlib: 3.9.0
numpy: 1.26.3
orjson: 3.10.5
packaging: 24.1
pandas: 2.2.2
pillow: 10.2.0
pydantic: 2.7.3
pydub: 0.25.1
python-multipart: 0.0.9
pyyaml: 6.0.1
ruff: 0.4.9
semantic-version: 2.10.0
tomlkit==0.12.0 is not installed.
typer: 0.12.3
typing-extensions: 4.9.0
urllib3: 2.2.1
uvicorn: 0.30.1
authlib; extra == 'oauth' is not installed.
itsdangerous; extra == 'oauth' is not installed.

gradio_client dependencies in your environment:

fsspec: 2024.2.0
httpx: 0.27.0
huggingface-hub: 0.23.3
packaging: 24.1
typing-extensions: 4.9.0
websockets: 11.0.3

Severity

I can work around it

pecanjk commented 3 months ago

same bug. waiting to be solved

freddyaboulton commented 3 months ago

Also see #8973

gsarti commented 3 months ago

Hi @freddyaboulton, any update on this? I'm currently blocked in the development of a demo because of this issue, so I'd like to know if it will be fixed in the near future. Thanks!

elismasilva commented 2 months ago

Hi @freddyaboulton, any update on this? I'm currently blocked in the development of a demo because of this issue, so I'd like to know if it will be fixed in the near future. Thanks!

hi @gsarti i got this problem too and i am using events without listening decorator, here is my fixed code snipet:

with gr.Row():                                              
      with gr.Column():                                                
        selected_loras=[]                                                
        selected_loras_state= gr.State(value=selected_loras)                                                
        use_this_loras=[]
        loras = []
        loras_scales = []                                                
        def click_add_lora(lora):
            global selected_loras
            if lora not in selected_loras:
                selected_loras.append(lora)                                                        
                return selected_loras, None
            else:                                                                                                                
                return selected_loras, "This model has already been added!"

        def click_remove_lora(value):
            global selected_lora
            selected_loras.pop(selected_loras.index(value))
            return selected_loras

        add_lora.click(fn=click_add_lora, inputs=lora_files, outputs=[selected_loras_state, status])

        @gr.render(inputs=selected_loras_state)
        def render_loras(selected):                                                       
            with gr.Row(elem_id="lora_row"):                                                        
                for i in range(len(selected)):                                                                                                                        
                    with gr.Column(variant="panel", min_width=300):
                        with gr.Row():
                            with gr.Column(min_width=300):
                                #use_this_lora = gr.Checkbox(label="Apply Lora", value=False)
                                remove_lora = gr.Button(value=remove_symbol, elem_id="remove_lora_button", key=f"remove-lora-{selected[i]}", elem_classes="remove-button vertical-center")
                                lora = gr.Textbox(label="File name", value=f"{selected[i]}", key=f"label-{selected[i]}", show_label=True)                                                                
                                lora_scale = gr.Slider(
                                    interactive=True,
                                    minimum=0.1,
                                    maximum=20.0,
                                    step=0.1,
                                    value=0.1,
                                    label="Lora scale", 
                                    key=f"scale-{selected[i]}")                                                                

                        remove_lora.click(fn=click_remove_lora, inputs=lora, outputs=selected_loras_state)

image

this code is plotting cards when i click on "+" button from item selected in dropdown, and remove item when i click on "X" icon on the card. I was getting same error from _id attribute for the event remove_lora. I notice that object "selected_loras_state" lost _id property for some reason i dont know, other problem that i got was if i remove card the card remains on page but object is updated, so i found is necessary you have a exclusive key on compomentes that will be updated, in my case the value i got in "selected" variable is my filename that is unique then it is replaced on re-render, and when i was using only index variable not work because new itens got same index and generate this problem. Well, my code is working know, i dont now if it will help you..

freddyaboulton commented 2 months ago

Sorry for the delay here @gsarti @z3lx ! I actually think this is the correct intended behavior (at least for @z3lx 's repro). There is a cycle in the @render decorator. Changing the code component, changes the value of the state variable, so the whole block gets re-rendered. That's why it looks like it's flickering and laggy. When I break the cycle, the input and blur events of the code component are triggered correctly.

render_retry

Code

import gradio as gr

def identity(v):
    print(v)
    return v

with gr.Blocks() as demo:
    show_checkbox = gr.Checkbox(label="Show", value=True)
    text_state = gr.Textbox("The Code")

    @gr.render(inputs=[show_checkbox])
    def render(show: bool) -> None:
        if not show:
            return
        text_input = gr.Code(
            value="",
        )
        text_input.input(
            fn=identity,
            inputs=[text_input],
            outputs=[text_state],
        )
        text_input.blur(
            lambda: "blurred",
            inputs=None,
            outputs=[text_state],
        )

demo.launch()
aliabid94 commented 3 weeks ago

The issue here, like @freddyaboulton described, is that when text_input changes, it changes text_state, which causes text_input to re-render. This causes a loop that, when typing fast, can causes an input event to trigger on a test_input that no longer exists because the the backend believes it has re-rendered. There should be a better error message to explain the issue, but you should be using key= to preserve the value of the textbox between re-renders instead. You can still store the value of the textbox in a state if you'd like. See below:

import gradio as gr

def identity(v):
    print(v)
    return v

with gr.Blocks() as demo:
    show_checkbox = gr.Checkbox(label="Show", value=True)
    text_state = gr.State("Some text")

    @gr.render(inputs=[show_checkbox])
    def render(show: bool) -> None:
        if not show:
            return
        text_input = gr.Textbox(key="text_input", label="Enter text")
        text_input.input(
            fn=identity,
            inputs=[text_input],
            outputs=[text_state],
        )

demo.launch()