Skip to content

Reimplement the UI on top of panel-material-ui #1146

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

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

Conversation

philippjfr
Copy link
Member

This PR has multiple goals:

  • Redesign the UI and UX to be cleaner and clearer
  • Use panel-material-ui components for consistent look and feel
  • Refactor the unmanageable ExplorerUI class with state on a bunch of different variables and instead move state onto Exploration objects.
  • Factor out a TableExplorer class

Copy link

codecov bot commented Apr 3, 2025

Codecov Report

Attention: Patch coverage is 1.34831% with 439 lines in your changes missing coverage. Please review.

Project coverage is 41.16%. Comparing base (79f9a5c) to head (579644b).

Files with missing lines Patch % Lines
lumen/ai/ui.py 0.00% 179 Missing ⚠️
lumen/ai/report.py 0.00% 124 Missing ⚠️
lumen/ai/coordinator.py 0.00% 90 Missing ⚠️
lumen/ai/views.py 0.00% 20 Missing ⚠️
lumen/ai/agents.py 0.00% 10 Missing ⚠️
lumen/ai/actor.py 14.28% 6 Missing ⚠️
lumen/ai/tools.py 0.00% 5 Missing ⚠️
lumen/ai/controls.py 0.00% 2 Missing ⚠️
lumen/ai/config.py 50.00% 1 Missing ⚠️
lumen/ai/llm.py 0.00% 1 Missing ⚠️
... and 1 more
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #1146       +/-   ##
===========================================
- Coverage   59.05%   41.16%   -17.89%     
===========================================
  Files         114      115        +1     
  Lines       18114    18197       +83     
===========================================
- Hits        10697     7491     -3206     
- Misses       7417    10706     +3289     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ahuang11
Copy link
Contributor

ahuang11 commented May 21, 2025

Based on my initial testing, I think the material UI version shows too little information to the user; there's a lot of waiting (and wondering if it's working, like here), which I don't think is ideal, especially since Lumen is still not very stable yet.

FWIW, when Gemini hid the CoT, people complained about it:

Can't properly debug the thought process. For example, I can't see wrong assumptions by the LLM anymore
I now have to wait for the entire thinking process to complete to obtain an answer (minutes). Even though the thought process often contains key insights within the first few seconds.

I think if we hide most of the steps, we should have at least a progress bar to indicate where the model is at (Plan -> Table Lookup -> SQL -> VegaLite)

@ahuang11 ahuang11 mentioned this pull request May 21, 2025
@philippjfr
Copy link
Member Author

Based on my initial testing, I think the material UI version shows too little information to the user; there's a lot of waiting (and wondering if it's working, like here), which I don't think is ideal, especially since Lumen is still not very stable yet.

I'm quite confused by this, I cannot reproduce what you're seeing at all. All the context is shown when I test it.

@ahuang11
Copy link
Contributor

ahuang11 commented Jun 4, 2025

Okay, I can reproduce. Not sure why it was previously just showing the planning step and nothing else.

@ahuang11
Copy link
Contributor

ahuang11 commented Jun 4, 2025

When I chat consecutively creating plots, I get a circular reference error. bokeh/bokeh#14383

Here's the traceback for circular reference:

Traceback (most recent call last):
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/panel/reactive.py", line 470, in _process_events
    self.param.update(**self_params)
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/param/parameterized.py", line 2406, in update
    restore = dict(self_._update(arg, **kwargs))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/param/parameterized.py", line 2439, in _update
    self_._batch_call_watchers()
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/param/parameterized.py", line 2624, in _batch_call_watchers
    self_._execute_watcher(watcher, events)
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/param/parameterized.py", line 2586, in _execute_watcher
    watcher.fn(*args, **kwargs)
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/param/parameterized.py", line 767, in _sync_caller
    return function()
           ^^^^^^^^^^
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/param/depends.py", line 85, in _depends
    return func(*args, **kw)
           ^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/panel/layout/feed.py", line 99, in _trigger_get_objects
    self.param.trigger("objects")
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/param/parameterized.py", line 2556, in trigger
    self_.update(dict(params, **triggers))
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/param/parameterized.py", line 2406, in update
    restore = dict(self_._update(arg, **kwargs))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/param/parameterized.py", line 2439, in _update
    self_._batch_call_watchers()
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/param/parameterized.py", line 2624, in _batch_call_watchers
    self_._execute_watcher(watcher, events)
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/param/parameterized.py", line 2586, in _execute_watcher
    watcher.fn(*args, **kwargs)
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/panel/reactive.py", line 456, in _param_change
    self._apply_update(named_events, properties, model, ref)
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/panel/reactive.py", line 344, in _apply_update
    with unlocked():
         ^^^^^^^^^^
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/contextlib.py", line 144, in __exit__
    next(self.gen)
  File "/Users/ahuang/miniconda3/envs/lumen/lib/python3.12/site-packages/panel/io/document.py", line 528, in unlocked
    curdoc.unhold()
  File "/Users/ahuang/repos/bokeh/src/bokeh/document/document.py", line 776, in unhold
    self.callbacks.unhold()
  File "/Users/ahuang/repos/bokeh/src/bokeh/document/callbacks.py", line 441, in unhold
    self.trigger_on_change(event)
  File "/Users/ahuang/repos/bokeh/src/bokeh/document/callbacks.py", line 423, in trigger_on_change
    invoke_with_curdoc(doc, invoke_callbacks)
  File "/Users/ahuang/repos/bokeh/src/bokeh/document/callbacks.py", line 453, in invoke_with_curdoc
    return f()
           ^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/document/callbacks.py", line 422, in invoke_callbacks
    cb(event)
  File "/Users/ahuang/repos/bokeh/src/bokeh/document/callbacks.py", line 278, in <lambda>
    self._change_callbacks[receiver] = lambda event: event.dispatch(receiver)
                                                     ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/document/events.py", line 352, in dispatch
    super().dispatch(receiver)
  File "/Users/ahuang/repos/bokeh/src/bokeh/document/events.py", line 218, in dispatch
    cast(DocumentPatchedMixin, receiver)._document_patched(self)
  File "/Users/ahuang/repos/bokeh/src/bokeh/server/session.py", line 252, in _document_patched
    self._pending_writes.append(connection.send_patch_document(event))
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/server/connection.py", line 86, in send_patch_document
    msg = self.protocol.create('PATCH-DOC', [event])
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/protocol/__init__.py", line 131, in create
    return self._messages[msgtype].create(*args, **kwargs)  # type: ignore [attr-defined]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/protocol/messages/patch_doc.py", line 90, in create
    patch_json = PatchJson(events=serializer.encode(events))
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 253, in encode
    return self._encode(obj)
           ^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 278, in _encode
    return self._encode_list(obj)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 326, in _encode_list
    return [self.encode(item) for item in obj]
            ^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 253, in encode
    return self._encode(obj)
           ^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 262, in _encode
    return obj.to_serializable(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/document/events.py", line 368, in to_serializable
    new   = serializer.encode(self.new),
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 253, in encode
    return self._encode(obj)
           ^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 282, in _encode
    return self._encode_dict(obj)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 343, in _encode_dict
    entries=[(self.encode(key), self.encode(val)) for key, val in obj.items()],
                                ^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 253, in encode
    return self._encode(obj)
           ^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 278, in _encode
    return self._encode_list(obj)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 326, in _encode_list
    return [self.encode(item) for item in obj]
            ^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 253, in encode
    return self._encode(obj)
           ^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 262, in _encode
    return obj.to_serializable(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/model/model.py", line 546, in to_serializable
    super_rep = super().to_serializable(serializer)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/has_props.py", line 415, in to_serializable
    attributes = {key: serializer.encode(val) for key, val in properties.items()}
                       ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 253, in encode
    return self._encode(obj)
           ^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 282, in _encode
    return self._encode_dict(obj)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 343, in _encode_dict
    entries=[(self.encode(key), self.encode(val)) for key, val in obj.items()],
                                ^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 253, in encode
    return self._encode(obj)
           ^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 262, in _encode
    return obj.to_serializable(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/model/model.py", line 546, in to_serializable
    super_rep = super().to_serializable(serializer)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/has_props.py", line 415, in to_serializable
    attributes = {key: serializer.encode(val) for key, val in properties.items()}
                       ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 249, in encode
    self.error("circular reference")
  File "/Users/ahuang/repos/bokeh/src/bokeh/core/serialization.py", line 475, in error
    raise SerializationError(message)
bokeh.core.serialization.SerializationError: circular reference

@philippjfr
Copy link
Member Author

You were right initially, steps weren't reported after the first plan. Got a fix.

@ahuang11
Copy link
Contributor

ahuang11 commented Jun 5, 2025

Thanks for fixing it! Using latest panel, panel-material-ui, and this branch, here's my feedback. Let me know if you'd like me to address any of them.

  1. I think the copy icon should only show if the content is copyable
image 2. The Upload Data button in the header may flow better if it's on the left of the report(?) icon, or be converted into an icon. image 3. Upon hover, the material chat input options expands, but I intuitively clicked it so it closed again, before re-opening image 4. The header colors doesn't translate very well to dark mode image 5. I personally think we can get rid of the Upload tab in the right sidebar as I migrated & centralized it to the header for visibility (it'll send a SourceAgent to chat). Alternatively, you could have a complete separate page for Upload, but right now, I think there's too much going on in the same tabs image 6. The SourceControls seem to have weird margins/padding image 7. The intro message should share additional instructions about the left sidebar image 8. The progress bar right under the header keeps on being active after loading is done image 9. Exploring a large dataset freezes the main event loop; is the `remote` option on? image 10. Planner steps should some how indicate it "Failed" if it did not succeed, or else it just collapses with no output, confusing the user. image

@ahuang11
Copy link
Contributor

ahuang11 commented Jun 5, 2025

  1. Sometimes the input gets sent, but persists in the chat area
image
  1. On first press, the retry icon doesn't trigger. Only on second re-opening and press does the retry get invoked
image
  1. The alert shows a checkmark and is green instead of an X when it errors
image

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

Successfully merging this pull request may close these issues.

2 participants