import ipywidgets
import time
from threading import Thread
Callbacks vs async/await
Let’s start with a very simple interface. An input text, a submit button, and an output text (disabled=True
so that it’s not editable):
= ipywidgets.Text("Hey ChatGPT, please summarise this text.")
input_ = ipywidgets.Button(description="Submit")
button = ipywidgets.Text(disabled=True)
output display(input_, button, output)
This won’t do anything yet. We need to setup an on_click
callback first. We’ll fake a request to OpenAI that simply sleeps for half a second then returns a fixed string. Then we’ll update the output text’s value with the result:
def request_open_ai(prompt):
0.5)
time.sleep(return "Here's a summary of your text."
def update_output(text):
= text output.value
def on_click(button):
= request_open_ai(input_.value)
text update_output(text)
button.on_click(on_click)
If you click “Submit” now, it should populate the output after half a second.
If we do this synchronously and in the main thread, the entire UI will hang during the requestOpenAi
call. So instead, we can separate that call into another thread. I think ipywidgets already does a version of this for us. But if it didn’t, here’s a very rough version of how we’d do it:
=request_open_ai, args=(input,)).run() Thread(target
But then how do we get the result and update the output with it? It’s often trickier to pass data across threads. Instead, we define our request function so that it accepts a callback that gets called with the result:
def request_open_ai(prompt, on_completion):
0.5)
time.sleep("Here's a summary of your text.") on_completion(
def on_click(button):
=request_open_ai, args=(input, update_output)).run() Thread(target
= ipywidgets.Text("Hey ChatGPT, please summarise this text.")
input_ = ipywidgets.Button(description="Submit")
button = ipywidgets.Text(disabled=True)
output
button.on_click(on_click) display(input_, button, output)
This works okay, but starts to get very confusing as you add more and more nested callbacks. In fact, it can get so bad that it’s been nicknamed callback hell. Someone was so frustrated with it that they even created a website! This is where async/await becomes useful, since it looks a lot more like ordinary programming:
import asyncio
async def request_open_ai(prompt):
0.5)
time.sleep(return "Here's a summary of your text."
async def on_click(button):
= await request_open_ai(input_.value)
text update_output(text)
Note how these functions look a lot similar to the original synchronous ones instead of having the weird callbacks.
In most UI frameworks, we’d be able to pass in an async function like the new on_click
. I’m not sure how to do that with ipywidgets, so we need to define a little wrapper that synchronously calls the async function, so we can set it as the button’s on_click
handler (confusing, I know):
def on_click_sync(button):
= on_click(button)
coroutine asyncio.ensure_future(coroutine)
= ipywidgets.Text("Hey ChatGPT, please summarise this text.")
input_ = ipywidgets.Button(description="Submit")
button = ipywidgets.Text(disabled=True)
output
button.on_click(on_click_sync) display(input_, button, output)