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

Add panel-lite-editor #5812

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

Conversation

MarcSkovMadsen
Copy link
Collaborator

@MarcSkovMadsen MarcSkovMadsen commented Nov 5, 2023

Implements #5768. In #5769 I made a lot of experiments. I learned that to create a general panel-lite playground we need to

  • Use an iframe with pyodide loaded once
  • Create the html template from the code on the fly to support js extensions and templates

Video

panel-lite-editor.mp4

Resources

@MarcSkovMadsen MarcSkovMadsen mentioned this pull request Nov 5, 2023
35 tasks
@MarcSkovMadsen MarcSkovMadsen changed the title add poc for panel-lite-editor Add panel-lite-editor Nov 5, 2023
@@ -0,0 +1,24 @@
<!DOCTYPE html>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI. This is the document iframe startswith. Currently its set via the src attribute. Instead of serving this externally, we should see if we could insert it via the srcdoc attribute. I have not tried with this specific document. But I've tried the srcdoc attribute with another doc in #4279 and could not get it working.

@@ -0,0 +1,26 @@
<!DOCTYPE html>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo

  • Make it easy for Panel, awesome-panel and any user to host a Panel panel-lite-editor containing some examples.
    • Style the editor
    • Put the panel-lite-editor in an easy to use js library that can be loaded from a CDN.
    • Make it possible to provide a dictionary of examples to the panel-lite-lite editor
    • Make it possible to run the panel-lite-editor as a Panel PanelLiteEditor widget.
    • Make it possible to toggle the editor part such that you can display the result but users can click some button and editor the code to update the result.
    • Add a console to make it easier to understand what goes on.

const appContainer = "app-container"
const codeEditor = "code-editor"

const welcomeExample=`\
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo

  • Refactor the examples to an external panel-lite-examples.json or panel-lite-examples.js file.

pn.pane.Perspective(df, width=1000).servable()
`

const fastTemplateExample=`\
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo:

Get the fastTemplateExample working

ModuleNotFoundError: no module named 'tornado'
Uncaught PythonError: Traceback (most recent call last):
  File "/lib/python3.11/site-packages/panel/io/pyodide.py", line 255, in jssync
    pydoc.apply_json_patch(json_patch.to_py(), setter='js')
  File "/lib/python3.11/site-packages/bokeh/document/document.py", line 391, in apply_json_patch
    DocumentPatchedEvent.handle_event(self, event, setter)
  File "/lib/python3.11/site-packages/bokeh/document/events.py", line 245, in handle_event
    event_cls._handle_event(doc, event)
  File "/lib/python3.11/site-packages/bokeh/document/events.py", line 280, in _handle_event
    cb(event.msg_data)
  File "/lib/python3.11/site-packages/bokeh/document/callbacks.py", line 390, in trigger_event
    model._trigger_event(event)
  File "/lib/python3.11/site-packages/bokeh/util/callback_manager.py", line 113, in _trigger_event
    self.document.callbacks.notify_event(cast(Model, self), event, invoke)
  File "/lib/python3.11/site-packages/bokeh/document/callbacks.py", line 260, in notify_event
    invoke_with_curdoc(doc, callback_invoker)
  File "/lib/python3.11/site-packages/bokeh/document/callbacks.py", line 443, in invoke_with_curdoc
    return f()
           ^^^
  File "/lib/python3.11/site-packages/bokeh/util/callback_manager.py", line 109, in invoke
    cast(EventCallbackWithEvent, callback)(event)
  File "/lib/python3.11/site-packages/panel/reactive.py", line 494, in _server_event
    self._comm_event(doc, event)
  File "/lib/python3.11/site-packages/panel/reactive.py", line 481, in _comm_event
    state._handle_exception(e)
  File "/lib/python3.11/site-packages/panel/io/state.py", line 441, in _handle_exception
    raise exception
  File "/lib/python3.11/site-packages/panel/reactive.py", line 479, in _comm_event
    self._process_bokeh_event(doc, event)
  File "/lib/python3.11/site-packages/panel/reactive.py", line 413, in _process_bokeh_event
    state._busy_counter += 1
    ^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.11/site-packages/param/parameterized.py", line 525, in _f
    instance_param.__set__(obj, val)
  File "/lib/python3.11/site-packages/param/parameterized.py", line 527, in _f
    return f(self, obj, val)
           ^^^^^^^^^^^^^^^^^
  File "/lib/python3.11/site-packages/param/parameters.py", line 542, in __set__
    super().__set__(obj,val)
  File "/lib/python3.11/site-packages/param/parameterized.py", line 527, in _f
    return f(self, obj, val)
           ^^^^^^^^^^^^^^^^^
  File "/lib/python3.11/site-packages/param/parameterized.py", line 1545, in __set__
    obj.param._call_watcher(watcher, event)
  File "/lib/python3.11/site-packages/param/parameterized.py", line 2486, in _call_watcher
    self_._execute_watcher(watcher, (event,))
  File "/lib/python3.11/site-packages/param/parameterized.py", line 2468, in _execute_watcher
    watcher.fn(*args, **kwargs)
  File "/lib/python3.11/site-packages/param/parameterized.py", line 781, in _sync_caller
    return function()
           ^^^^^^^^^^
  File "/lib/python3.11/site-packages/param/depends.py", line 41, in _depends
    return func(*args, **kw)
           ^^^^^^^^^^^^^^^^^
  File "/lib/python3.11/site-packages/panel/io/state.py", line 273, in _update_busy_counter
    self.busy = self._busy_counter >= 1
    ^^^^^^^^^
  File "/lib/python3.11/site-packages/param/parameterized.py", line 525, in _f
    instance_param.__set__(obj, val)
  File "/lib/python3.11/site-packages/param/parameterized.py", line 527, in _f
    return f(self, obj, val)
           ^^^^^^^^^^^^^^^^^
  File "/lib/python3.11/site-packages/param/parameterized.py", line 1545, in __set__
    obj.param._call_watcher(watcher, event)
  File "/lib/python3.11/site-packages/param/parameterized.py", line 2486, in _call_watcher
    self_._execute_watcher(watcher, (event,))
  File "/lib/python3.11/site-packages/param/parameterized.py", line 2468, in _execute_watcher
    watcher.fn(*args, **kwargs)
  File "/lib/python3.11/site-packages/param/parameterized.py", line 781, in _sync_caller
    return function()
           ^^^^^^^^^^
  File "/lib/python3.11/site-packages/param/depends.py", line 41, in _depends
    return func(*args, **kw)
           ^^^^^^^^^^^^^^^^^
  File "/lib/python3.11/site-packages/panel/io/state.py", line 278, in _update_busy
    indicator.value = self.busy
    ^^^^^^^^^^^^^^^
  File "/lib/python3.11/site-packages/param/parameterized.py", line 525, in _f
    instance_param.__set__(obj, val)
  File "/lib/python3.11/site-packages/param/parameterized.py", line 527, in _f
    return f(self, obj, val)
           ^^^^^^^^^^^^^^^^^
  File "/lib/python3.11/site-packages/param/parameterized.py", line 1545, in __set__
    obj.param._call_watcher(watcher, event)
  File "/lib/python3.11/site-packages/param/parameterized.py", line 2486, in _call_watcher
    self_._execute_watcher(watcher, (event,))
  File "/lib/python3.11/site-packages/param/parameterized.py", line 2468, in _execute_watcher
    watcher.fn(*args, **kwargs)
  File "/lib/python3.11/site-packages/panel/reactive.py", line 374, in _param_change
    self._apply_update(named_events, properties, model, ref)
  File "/lib/python3.11/site-packages/panel/reactive.py", line 307, in _apply_update
    doc.add_next_tick_callback(cb)
  File "/lib/python3.11/site-packages/bokeh/document/document.py", line 268, in add_next_tick_callback
    from ..server.callbacks import NextTickCallback
  File "/lib/python3.11/site-packages/bokeh/server/callbacks.py", line 29, in <module>
    from ..util.tornado import _CallbackGroup
  File "/lib/python3.11/site-packages/bokeh/util/tornado.py", line 37, in <module>
    import tornado
ModuleNotFoundError: No module named 'tornado'

    at new_error (pyodide.asm.js:9:12519)
    at VM17 pyodide.asm.wasm:1:1411112
    at VM17 pyodide.asm.wasm:1:1411373
    at Module._pythonexc2js (pyodide.asm.js:9:640895)
    at Module.callPyObjectKwargs (pyodide.asm.js:9:81856)
    at Module.callPyObject (pyodide.asm.js:9:82066)
    at Function.apply (pyodide.asm.js:9:97147)
    at Object.apply (pyodide.asm.js:9:95381)
    at S._trigger_on_change (VM621 bokeh-3.3.0.min.js:165:4949)
    at z.send_event (VM621 bokeh-3.3.0.min.js:165:408)
    at a._process_event (VM621 bokeh-3.3.0.min.js:215:892)
    at z.trigger (VM621 bokeh-3.3.0.min.js:165:545)
    at a.trigger_event (VM621 bokeh-3.3.0.min.js:215:987)
    at r.click (VM623 bokeh-widgets-3.3.0.min.js:56:117)
    at HTMLButtonElement.<anonymous> (VM623 bokeh-widgets-3.3.0.min.js:46:898)
new_error @ pyodide.asm.js:9
$wrap_exception @ VM17 pyodide.asm.wasm:1
$pythonexc2js @ VM17 pyodide.asm.wasm:1
Module._pythonexc2js @ pyodide.asm.js:9
Module.callPyObjectKwargs @ pyodide.asm.js:9
Module.callPyObject @ pyodide.asm.js:9
apply @ pyodide.asm.js:9
apply @ pyodide.asm.js:9
_trigger_on_change @ VM621 bokeh-3.3.0.min.js:165
send_event @ VM621 bokeh-3.3.0.min.js:165
_process_event @ VM621 bokeh-3.3.0.min.js:215
trigger @ VM621 bokeh-3.3.0.min.js:165
trigger_event @ VM621 bokeh-3.3.0.min.js:215
click @ VM623 bokeh-widgets-3.3.0.min.js:56
(anonymous) @ VM623 bokeh-widgets-3.3.0.min.js:46

`

// See https://github.com/holoviz/panel/blob/8579e5cf322604e61b95bb1e10dcc57466298df1/panel/io/convert.py#L87
const pyoidideScript = `
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI. We need to override the PYODIDE_SCRIPT to

  • not reload pyodide, micropip and python packages
  • listen to a message for the parent window to run some new code.

const pyoidideScript = `
<script type="text/javascript">
async function main() {
let envSpec = [{{ env_spec }}]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI. I check if python packages have already been loaded before loading them again.

]
await window.micropip.install(window.envSpec)
// Todo: https://github.com/holoviz/panel/issues/5811
window.envSpec = window.envSpec.concat(["panel", "bokeh", "datetime", "random"])
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo

from panel.io.convert import script_to_html

code = \"\"\"
${fixPanel}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo:

try:
html, _ = script_to_html(file)
except Exception as ex:
html = f"Exception: {ex}"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo

  • Add much better error handling.

print(ex)


return html.replace("v0.23.4", "v0.24.1")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo

  • Get Panel updated to use latest version of Pyodide
  • Remove .replace when released.


async function runCode(code){
// Todo: panel.io.convert.find_requirements does not find these?
await installIfMissing(code, 'plotly')
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo:

  • Get plotly and matplotlib provided by {{ env_spec }}. It does not when running the examples.
    • I've not been able to reproduce this outside this application.

await runCode(event.data)
}

window.handleMessage = handleMessage
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo:

  • Remove window.handleMessage if possible.

<div id="loading-spinner" style="width:100%">Loading Pyodide...</div>
<script>
async function main(){
window.pyodide = await loadPyodideAndPackages(jsglobals=window)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo

  • Load and run pyodide as efficiently as possible. Probably using service worker.

examples/playground/panel-lite-editor.js Show resolved Hide resolved
examples/playground/panel-lite-editor.js Show resolved Hide resolved
<body>
<img src="https://panel.holoviz.org/_static/logo_horizontal_light_theme.png" style="height:50px"></img></br>
<span>Code Editor</span><br/>
<textarea id="code-editor" style="height:200px;width:800px"></textarea><br/>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo

  • Run code on CTRL+Save.

@@ -0,0 +1,131 @@
const appContainer = "app-container"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo

Add more features

  • Ability to add list of requirements
  • Ability to add list of environment variables. For example OPENAI_API_KEY. The environment variables should be added to os.environ.
  • Separate PanelLiteEditor and PanelLiteExamples. You might want to use the PanelLiteEditor seperately.
  • Add ability to hide PanelLiteEditor for example if embedded on web page.
  • Add better code editor. Like Ace or Monaco.
  • Layout Examples, Editor and App components in grid that orients it self nicely depending on screen size.
  • Add console to editor.

Maybe

pn.pane.Matplotlib(fig0, dpi=144).servable()
`

const perspectiveExample=`
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo

  • Fix sporadic pyodide.ffi.JsException: Error: Error rendering Bokeh model: could not find ... HTML tag
    • I've experienced this when click my way through the examples from left to right.
pyodide.ffi.JsException: ... could not find ... HTML tag`
Uncaught (in promise) PythonError: Traceback (most recent call last):
  File "/lib/python311.zip/_pyodide/_base.py", line 571, in eval_code_async
    await CodeRunner(
  File "/lib/python311.zip/_pyodide/_base.py", line 396, in run_async
    await coroutine
  File "<exec>", line 45, in <module>
  File "/lib/python3.11/site-packages/panel/io/pyodide.py", line 519, in write_doc
    views = await Bokeh.embed.embed_items(JSON.parse(docs_json), JSON.parse(render_items))
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pyodide.ffi.JsException: Error: Error rendering Bokeh model: could not find #e97f9bbb-8b5e-42a7-b685-b355a0ee7b92 HTML tag

    at new_error (pyodide.asm.js:9:12519)
    at pyodide.asm.wasm:0x158827
    at pyodide.asm.wasm:0x15fcd5
    at _PyCFunctionWithKeywords_TrampolineCall (pyodide.asm.js:9:123052)
    at pyodide.asm.wasm:0x1a3091
    at pyodide.asm.wasm:0x289e4d
    at pyodide.asm.wasm:0x1e3f77
    at pyodide.asm.wasm:0x1a3579
    at pyodide.asm.wasm:0x1a383a
    at pyodide.asm.wasm:0x1a38dc
    at pyodide.asm.wasm:0x2685c5
    at pyodide.asm.wasm:0x26e3d0
    at pyodide.asm.wasm:0x1a3a04
    at pyodide.asm.wasm:0x1a3694
    at pyodide.asm.wasm:0x15f45e
    at Module.callPyObjectKwargs (pyodide.asm.js:9:81732)
    at Module.callPyObject (pyodide.asm.js:9:82066)
    at wrapper (pyodide.asm.js:9:58562)

@@ -0,0 +1,159 @@
const welcome=`\
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo

  • Wrap this into an easy to use class PanelLiteIframe that takes code, requirements and either settings as arguments. It creates the iframe and provides a run function to update the code and other settings.

Maybe

  • Enable rendering non-Panel components by wrapping the last output in a pn.panel?

Copy link

codecov bot commented Nov 6, 2023

Codecov Report

Merging #5812 (ae5bbbd) into main (0169933) will increase coverage by 9.98%.
The diff coverage is n/a.

@@            Coverage Diff             @@
##             main    #5812      +/-   ##
==========================================
+ Coverage   72.37%   82.35%   +9.98%     
==========================================
  Files         290      290              
  Lines       42235    42235              
==========================================
+ Hits        30566    34783    +4217     
+ Misses      11669     7452    -4217     
Flag Coverage Δ
ui-tests 38.35% <ø> (?)
unitexamples-tests 72.37% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

see 71 files with indirect coverage changes

📣 Codecov offers a browser extension for seamless coverage viewing on GitHub. Try it in Chrome or Firefox today!

@MarcSkovMadsen
Copy link
Collaborator Author

Looking at the numpy web page. The below might be a simple solution if we enable easily copy-pasting code from a gallery?

image

@Coderambling
Copy link
Contributor

Like it! But would it not need to support some form of Intellisense to be easier to use?

As for code <-> output, @ahuang11 made a great example with code-editor + Panel / Holoviz + AI that does something similar. Would that be of use?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants