-
-
Notifications
You must be signed in to change notification settings - Fork 531
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
Add append mode for reactive generator output #5129
Conversation
One issue with your example. The appended results are disabled until all results are ready. If for example my generative AI model streams multiple intermediate video files, audio files, tables or similar back, I want to be able to interact with them as soon as they are ready. Not wait several minutes until the final result is returned 😄 |
Codecov Report
@@ Coverage Diff @@
## main #5129 +/- ##
==========================================
+ Coverage 83.78% 83.80% +0.02%
==========================================
Files 274 274
Lines 39391 39449 +58
==========================================
+ Hits 33003 33061 +58
Misses 6388 6388
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 4 files with indirect coverage changes 📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
Please also consider if (you could probably say the same for |
import random
import time
import panel as pn
pn.extension(sizing_mode="stretch_width")
def model():
time.sleep(1)
return random.randint(0, 100)
def results(running):
if not running:
yield "The model has not run yet"
return
for i in range(0, 10):
result = pn.panel('Running model...', loading=True)
yield result
result.param.update(object=f"Result {i}: {model()}", loading=False)
run_input = pn.widgets.Button(name="Run model")
pn.Column(
run_input,
pn.panel(pn.bind(results, run_input), generator_mode='append'),
).servable() |
A |
I've been thinking about this a lot but haven't yet come to any good solutions. In |
I do find |
result = pn.panel('Running model...', loading=True)
yield result
result.param.update(object=f"Result {i}: {model()}", loading=False) Please consider whether this can be simplified to the first two lines. For example you can temporarily |
Maybe pn.bind(...).panel(loading_indicator=True)
pn.bind(...).configure(loading_indicator=True)
pn.bind(...).update(loading_indicator=True)
pn.bind(...).display_options(loading_indicator=True) |
That approach just falls naturally out of the functionality and is pretty powerful. Anything else like you're suggesting would require injecting arguments into the generator or set up some special control flow signals, which seems overly complex. It would be neat if there was a clean way to temporarily switch between append and replace modes, so you can yield something that is appended and then subsequently yield a replacement. |
Only thing I can imagine is some sentinel value on the replacement: def results(running):
if not running:
yield "The model has not run yet"
return
for i in range(0, 10):
yield pn.panel('Running model...', loading=True)
yield pn.panel(f"Result {i}: {model()}", tags=['replace']) |
At a first glance it does not "feel" right. Its like an "agreed", "convention" that you need to remember. Then I probably prefer your 3 lines and then clear documentation. |
Indeed, the |
Only other suggestion I have and it's definitely in weird convention territory again: def results(running):
if not running:
yield "The model has not run yet"
return
for i in range(0, 10):
yield pn.panel('Running model...', loading=True), ...
yield f"Result {i}: {model()}" i.e. the |
I don't think there is a great solutions. The convention seems non-ideal, I don't like adding the tag, and the I would not say the solution I have thought of is great either. But the thinking is making custom bind functions where you set the panel arguments, so you define them once and reuse that custom bind as needed. import random
import time
import panel as pn
pn.extension(sizing_mode="stretch_width")
def model():
time.sleep(1)
return random.randint(0, 100)
def results(running):
if not running:
yield "The model has not run yet"
return
for i in range(0, 10):
yield f"Result {i}: {model()}"
# Only reason for two buttons is so they don't block each other
run_input1 = pn.widgets.Button(name="Run model")
run_input2 = pn.widgets.Button(name="Run model")
custom_bind = pn.bind.create(loading_indicator=True, generator_mode="append")
pn.Column(
pn.Row(run_input1, run_input2),
pn.Row(
custom_bind(results, run_input1),
pn.bind(results, run_input2),
),
).servable() screenrecord-2023-06-16_20.43.04.mp4The hack to get it to work 🙃import panel as pn
from panel.pane import PaneBase
def create(**pn_kwargs):
def func(*args, **kwargs):
bound = pn.bind(*args, **kwargs)
bound.__panel__ = lambda: PaneBase.get_pane_type(bound, **pn_kwargs)(
bound, **pn_kwargs
)
return bound
return func
pn.bind.create = create |
I'm having trouble really wrapping my head around this enough to have a strong opinion, but it seems like |
Merging this, adding an API like |
Allows generator output to append to output rather than simply replacing it: