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
32.35k stars 2.42k forks source link

Error when using State component in a TabbedInterface #4020

Closed beskrovnykh closed 1 year ago

beskrovnykh commented 1 year ago

Describe the bug

I was attempting to use a State component in a TabbedInterface to share state between different tabs. However, when running the application, it crashed with the error "DuplicateBlockError: At least one block in this Blocks has already been rendered." Upon further investigation, I discovered that the State component was being treated as a rendered block, even though it was not supposed to be. I believe that this is a bug in the Gradio library. A workaround is to use low-level blocks instead of interfaces, but this requires a significant rewrite of the code.

Is there an existing issue for this?

Reproduction

  1. Create a new gradio interface with two tabs using TabbedInterface.
  2. Inside each tab, add an interface component that uses a shared state variable as shown below
  3. Try to run the app.
  4. The error DuplicateBlockError: At least one block in this Blocks has already been rendered. should appear in the console.
import gradio as gr

state = gr.State("")
gr.TabbedInterface([
    gr.Interface(lambda _, x: (x, f"I don't know"), inputs=[state, gr.Textbox()], outputs=[state, gr.Textbox()]),
    gr.Interface(lambda s: (s, f"User question: {s}"), inputs=[state], outputs=[state, gr.Textbox(interactive=False)])
], ["Ask question", "Show question"], title="Example of usage state variable in tabbed environment.").launch()

Screenshot

No response

Logs

Traceback (most recent call last):
  File "/Users/beskrovnykh/Work/ws/projects/danielsearch-embeddings/tabbed_app_with_state.py", line 4, in <module>
    gr.TabbedInterface([
  File "/Users/beskrovnykh/Work/ws/projects/danielsearch-embeddings/venv/lib/python3.8/site-packages/gradio/interface.py", line 864, in __init__
    interface.render()
  File "/Users/beskrovnykh/Work/ws/projects/danielsearch-embeddings/venv/lib/python3.8/site-packages/gradio/blocks.py", line 848, in render
    raise DuplicateBlockError(
gradio.exceptions.DuplicateBlockError: At least one block in this Blocks has already been rendered.

System Info

3.27.0

Severity

blocking all usage of gradio

beskrovnykh commented 1 year ago

I looked in the debugger for what is the reason, the check below does not pass

 if not set(Context.root_block.blocks).isdisjoint(self.blocks):
      raise DuplicateBlockError(
          "At least one block in this Blocks has already been rendered."
      )

The problem is that

set(Context.root_block.blocks).intersection(self.blocks)

returns a single element, 1, which is the id of the state block, but as far as I know the State component is not assumed to be rendered. Can we weaken this check to make the application run?

Workaround

Skip interfaces and use low-level blocks. On blocks, this code does what I need, but then I have to rewrite 3 tabs to blocks, which takes a long time

demo = gr.Blocks()

with demo:
    gr.Markdown("Example of usage state variable in tabbed environment.")
    with gr.Tabs() as tabs:
        state = gr.State("")
        asc_tab = gr.TabItem("Ask question")
        with asc_tab:
            with gr.Row():
                ask_input = gr.Textbox()
                answer_output = gr.Textbox()
            clear_button = gr.Button("Clear")
            submit_button = gr.Button("Submit")

        with gr.TabItem("Show question") as tab2:
            with gr.Row():
                question = gr.Textbox(interactive=False)
            tab2.select(lambda s: (s, f"User question: {s}"), [state], [state, question])

    submit_button.click(lambda s, x: (x, f"I don't know"), inputs=[state, ask_input],
                        outputs=[state, answer_output])

demo.launch()
abidlabs commented 1 year ago

Very interesting issue, let me take a look @beskrovnykh!

beskrovnykh commented 1 year ago

How fast, I did not expect! Thank you