Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allowing stateful flows in Gradio #2570

Closed
aliabid94 opened this issue Oct 30, 2022 · 2 comments · Fixed by #8243
Closed

Allowing stateful flows in Gradio #2570

aliabid94 opened this issue Oct 30, 2022 · 2 comments · Fixed by #8243
Labels
enhancement New feature or request needs designing The proposed feature needs to be discussed and designed before being implemented
Milestone

Comments

@aliabid94
Copy link
Collaborator

aliabid94 commented Oct 30, 2022

An example of stateful flow API for gradio that is very compatible with our current API.

import gradio as gr
import random

answer_list = ["good", "happy", "twelve"]
MAX_GUESSES = 10

with gr.Blocks() as demo:
    # state variables
    secret_word = gr.Variable()
    letters_guessed = gr.Variable([])

    def reset():
        return {secret_word: random.choice(answer_list), letters_guessed: []}
    demo.load(reset, None, {secret_word, letters_guessed}) # we could remove the need to declare Variables in inputs= and outputs= since they are stored in the backend and don't need to be collected

    # layout
    with gr.If() as win_condition:
        gr.Markdown("# You Won")
        reset_win = gr.Button("Reset")
    with gr.Elif() as lose_condition:
        gr.Markdown("# You Lose")
        reset_lose = gr.Button("Reset")
    with gr.Else():
        guesses_left = gr.Number("Guesses Left")
        with gr.Row():
            current_letter = gr.Textbox("Letter")
            guess_btn = gr.Button("Guess")
    with gr.Row():
        visible_word = gr.Textbox(label="Visible Word")
        guesses = gr.Textbox(label="Guessed So Far")

    # event listener affects state variables directly
    def add_letter(data):
        return {data[letters_guessed]: data[letters_guessed] + data[current_letter]}
    guess_btn.click(add_letter, {current_letter, letters_guessed}, letters_guessed)
    reset_win.click(reset, None, {secret_word, letters_guessed})
    reset_lose.click(reset, None, {secret_word, letters_guessed})

    # state variables update components directly
    def recalculate_state(data):
        output = {}
        for letter in data[secret_word]:
            if letter not in data[secret_word]:
                output[win_condition] = False
        else:
            output[win_condition] = True
        output[guesses_left] = MAX_GUESSES - len(data[letters_guessed])
        output[lose_condition] = output[guesses_left] == 0
        output[visible_word] = "".join(
            letter if letter in data[letters_guessed] else "_"
            for letter in data[secret_word]
        )
        output[guesses] = ", ".join(data[letters_guessed])
        return output

    demo.bind(
        recalculate_state,
        {secret_word, letters_guessed},
        {
            win_condition,
            guesses_left,
            lose_condition,
            visible_word,
            letters_guessed,
            guesses,
        },
    )
@abidlabs abidlabs added enhancement New feature or request needs designing The proposed feature needs to be discussed and designed before being implemented labels Oct 31, 2022
@aliabid94
Copy link
Collaborator Author

aliabid94 commented Nov 6, 2022

Another try @pngwn

import gradio as gr
import random

answer_list = ["good", "happy", "twelve"]
MAX_GUESSES = 10

class Demo(gr.Blocks):
    def init(self):
        self.win = False
        self.secret_word = random.choice(answer_list)
        self.letters_guessed = []
        self.guesses_left = MAX_GUESSES

    def state(self):
        self.visible_word = ""
        self.guesses_left = MAX_GUESSES - len(self.letters_guessed)
        self.win = True
        for letter in self.secret_word:
            if letter in self.letters_guessed:
                self.visible_word += letter
            else:
                self.win = False
                self.visible_word += "_"

    def ui(self):
        if self.win:
            gr.Markdown("# You Won")
            gr.Button("Reset").click(self.init)
        elif self.guesses_left == 0:
            gr.Markdown("# You Lose")
            gr.Button("Reset").click(self.init)
        else:
            gr.Number(self.guesses_left, label="Guesses Left")
            with gr.Row():
                gr.Textbox(self.current_letter, label="Letter")
                gr.Button("Guess").click(self.add_letter)
        with gr.Row():
            gr.Textbox(self.visible_word, label="Visible Word")
            gr.Textbox(self.letters_guessed, label="Guessed So Far")

    def add_letter(self):
        self.letters_guessed.append(self.current_letter)

@aliabid94
Copy link
Collaborator Author

aliabid94 commented Nov 10, 2022

Another attempt! This is backwards compatible, where the bind method re-renders the function if the bound state variables change. Also using a decorator syntax.

import gradio as gr
import random

word_list = ["good", "happy", "twelve"]
MAX_GUESSES = 10

with gr.Blocks() as demo:
    secret_word = gr.Variable()
    letters_guessed = gr.Variable([])

    @demo.load([secret_word, letters_guessed])
    def reset():
        return {
            secret_word: random.choice(word_list),
            letters_guessed: []
        }

    gr.Markdown("# Hangman")
    gr.Markdown("Guess the secret word!")

    @demo.bind({secret_word, letters_guessed})
    def hangman(data):
        for letter in data[secret_word]:
            if letter not in letters_guessed:
                win_condition = True
                break
        else:
            win_condition = False
        lose_condition = len(letters_guessed) == MAX_GUESSES
        if win_condition:
            gr.Markdown("# You Won")
            gr.Button("Reset").click(reset)
        elif lose_condition:
            gr.Markdown("# You Won")
            gr.Button("Reset").click(reset)
        else:
            with gr.Row():
                letter = gr.Textbox(label="Enter Letter")
                submit = gr.Button("Go")
        
        @submit.click(letters_guessed, {letter, letters_guessed})
        def add_letter(data):
            return {
                letters_guessed: data[letters_guessed] + [data[letter]]
            }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request needs designing The proposed feature needs to be discussed and designed before being implemented
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants