Skip to content

Commit

Permalink
Merge branch 'main' into add_edit_message
Browse files Browse the repository at this point in the history
  • Loading branch information
ahuang11 authored Jan 8, 2025
2 parents eabaf0e + 5426424 commit a01ca52
Show file tree
Hide file tree
Showing 51 changed files with 545 additions and 442 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ repos:
exclude: \.min\.js$
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.7.0
rev: v0.8.6
hooks:
- id: ruff
files: panel/
Expand All @@ -36,7 +36,7 @@ repos:
additional_dependencies:
- tomli
- repo: https://github.com/hoxbro/prettier-pre-commit
rev: v3.3.3
rev: v3.4.2
hooks:
- id: prettier
entry: prettier --write --ignore-unknown --no-error-on-unmatched-pattern
Expand All @@ -47,7 +47,7 @@ repos:
- id: oxipng
stages: [manual]
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v9.13.0
rev: v9.17.0
hooks:
- id: eslint
args: ['-c', 'panel/.eslintrc.js', 'panel/*.ts', 'panel/models/**/*.ts', '--fix']
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ This release fixes a regression causing .node_modules to be bundled into our rel

### Bug fixes

- Ensure Notifications are cleaned up correctly ([#4964](https://github.com/holoviz/panel/pull/4964))
- Ensure `FileDownload` label text updates correctly ([#7489](https://github.com/holoviz/panel/pull/7489))
- Fix `Tabulator` aggregation behavior ([#7450](https://github.com/holoviz/panel/pull/7450))
- Fix typing for `.servable` method ([#7530](https://github.com/holoviz/panel/pull/7530))
- Ensure `NestedSelect` respects `disabled` parameter ([#7533](https://github.com/holoviz/panel/pull/7533))
- Ensure errors in hooks aren't masked by fallback to different signature ([#7502](https://github.com/holoviz/panel/pull/7502))
- Ensure Notifications are only shown once if scheduled onload ([#7504](https://github.com/holoviz/panel/pull/7504))

### Documentation

Expand Down
2 changes: 2 additions & 0 deletions doc/about/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ This release fixes a regression causing .node_modules to be bundled into our rel

### Bug fixes

- Ensure Notifications are cleaned up correctly ([#4964](https://github.com/holoviz/panel/pull/4964))
- Ensure `FileDownload` label text updates correctly ([#7489](https://github.com/holoviz/panel/pull/7489))
- Fix `Tabulator` aggregation behavior ([#7450](https://github.com/holoviz/panel/pull/7450))
- Fix typing for `.servable` method ([#7530](https://github.com/holoviz/panel/pull/7530))
- Ensure `NestedSelect` respects `disabled` parameter ([#7533](https://github.com/holoviz/panel/pull/7533))
- Ensure errors in hooks aren't masked by fallback to different signature ([#7502](https://github.com/holoviz/panel/pull/7502))
- Ensure Notifications are only shown once if scheduled onload ([#7504](https://github.com/holoviz/panel/pull/7504))

### Documentation

Expand Down
2 changes: 1 addition & 1 deletion examples/apps/django/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ django==4.2.17
channels==2.2.0
panel==0.9.3
bokeh==2.0.2
jinja2==3.1.4
jinja2==3.1.5
2 changes: 1 addition & 1 deletion examples/reference/chat/ChatAreaInput.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"* **`auto_grow`** (boolean, default=True): Whether the TextArea should automatically grow in height to fit the content.\n",
"* **`cols`** (int, default=2): The number of columns in the text input field.\n",
"* **`disabled`** (boolean, default=False): Whether the widget is editable\n",
"* **`max_length`** (int, default=5000): Max character length of the input field. Defaults to 5000\n",
"* **`max_length`** (int, default=50000): Max character length of the input field. Defaults to 50000\n",
"* **`max_rows`** (int, default=10): The maximum number of rows in the text input field when `auto_grow=True`.\n",
"* **`name`** (str): The title of the widget\n",
"* **`placeholder`** (str): A placeholder string displayed when no value is entered\n",
Expand Down
3 changes: 3 additions & 0 deletions panel/chat/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ class ChatAreaInput(_PnTextAreaInput):
enter_pressed = param.Event(doc="""
Event when the Enter/Ctrl+Enter key has been pressed.""")

max_length = param.Integer(default=50000, doc="""
Max count of characters in the input field.""")

_widget_type: ClassVar[type[Model]] = _bkChatAreaInput

_rename: ClassVar[Mapping[str, str | None]] = {
Expand Down
11 changes: 5 additions & 6 deletions panel/chat/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,11 +362,11 @@ def _init_widgets(self):
self._input_layout = input_layout

def _wrap_callbacks(
self,
callback: Callable | None = None,
post_callback: Callable | None = None,
name: str = ""
):
self,
callback: Callable | None = None,
post_callback: Callable | None = None,
name: str = ""
):
"""
Wrap the callback and post callback around the default callback.
"""
Expand Down Expand Up @@ -654,7 +654,6 @@ async def _cleanup_response(self):
await super()._cleanup_response()
await self._update_input_disabled()


def send(
self,
value: ChatMessage | dict | Any,
Expand Down
2 changes: 1 addition & 1 deletion panel/command/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def invoke(self, args: argparse.Namespace) -> None:
for f in args.files:
p = pathlib.Path(f).absolute()
if not p.is_file():
raise ValueError('File {f!r} not found.')
raise FileNotFoundError(f'File {f!r} not found.')
elif p not in excluded:
included.append(p)

Expand Down
8 changes: 8 additions & 0 deletions panel/dist/css/chat_feed.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
.chat-feed-header {
margin-left: 15px;
}

::-webkit-scrollbar {
width: 8px;
}

::-webkit-scrollbar-thumb {
background-color: var(--panel-secondary-color, #f1f1f1);
}
17 changes: 16 additions & 1 deletion panel/dist/css/chat_message.css
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
width: fit-content;
display: flex;
align-items: center;
justify-content: center;
justify-content: left;
overflow-wrap: anywhere;
word-break: break-word;
}
Expand Down Expand Up @@ -161,4 +161,19 @@
.edit-area {
/* for that smooth transition on a one line message */
height: 51.5px;

pre {
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word;
}

p {
overflow-wrap: break-word;
}

code {
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word;
}
4 changes: 4 additions & 0 deletions panel/dist/css/listpanel.css
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,7 @@
margin-top: 10px;
filter: invert();
}

.card-button > svg {
stroke: var(--background-text-color);
}
18 changes: 6 additions & 12 deletions panel/io/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,20 +169,16 @@ def start(self):
finally:
self._updating = False
self._start_time = time.time()
if state._is_pyodide:
self._cb = asyncio.create_task(
self._async_repeat(self._periodic_callback)
)
elif state.curdoc and state.curdoc.session_context:
if state.curdoc and state.curdoc.session_context and not state._is_pyodide:
self._doc = state.curdoc
if state._unblocked(state.curdoc):
self._cb = self._doc.add_periodic_callback(self._periodic_callback, self.period)
else:
self._doc.add_next_tick_callback(self.start)
else:
from tornado.ioloop import PeriodicCallback
self._cb = PeriodicCallback(lambda: asyncio.create_task(self._periodic_callback()), self.period)
self._cb.start()
self._cb = asyncio.create_task(
self._async_repeat(self._periodic_callback)
)

def stop(self):
"""
Expand All @@ -197,15 +193,13 @@ def stop(self):
with param.discard_events(self):
self.counter = 0
self._timeout = None
if state._is_pyodide and self._cb:
self._cb.cancel()
elif self._doc and self._cb:
if self._doc and self._cb and not state._is_pyodide:
if self._doc._session_context:
self._doc.callbacks.remove_session_callback(self._cb)
elif self._cb in self._doc.callbacks.session_callbacks:
self._doc.callbacks._session_callbacks.remove(self._cb)
elif self._cb:
self._cb.stop()
self._cb.cancel()
self._cb = None
doc = self._doc or curdoc_locked()
if doc:
Expand Down
4 changes: 2 additions & 2 deletions panel/io/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
PANEL_ROOT = pathlib.Path(__file__).parent.parent
BOKEH_VERSION = base_version(bokeh.__version__)
PY_VERSION = base_version(__version__)
PYODIDE_VERSION = 'v0.26.2'
PYSCRIPT_VERSION = '2024.8.1'
PYODIDE_VERSION = 'v0.27.0'
PYSCRIPT_VERSION = '2024.10.2'
WHL_PATH = DIST_DIR / 'wheels'
PANEL_LOCAL_WHL = WHL_PATH / f'panel-{__version__.replace("-dirty", "")}-py3-none-any.whl'
BOKEH_LOCAL_WHL = WHL_PATH / f'bokeh-{BOKEH_VERSION}-py3-none-any.whl'
Expand Down
5 changes: 4 additions & 1 deletion panel/io/datamodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,10 @@ def create_linked_datamodel(obj, root=None):
else:
_DATA_MODELS[cls] = model = construct_data_model(obj)
properties = model.properties()
model = model(**{k: v for k, v in obj.param.values().items() if k in properties})
props = {k: v for k, v in obj.param.values().items() if k in properties}
if root:
props['name'] = f"{root.ref['id']}-{id(obj)}"
model = model(**props)
_changing = []

def cb_bokeh(attr, old, new):
Expand Down
12 changes: 6 additions & 6 deletions panel/io/embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,12 @@ def is_embeddable(object):
if len(cross_product) > max_states:
if config._doc_build:
return
param.main.param.warning('The cross product of different application '
'states is very large to explore (N=%d), consider '
'reducing the number of options on the widgets or '
'increase the max_states specified in the function '
'to remove this warning' %
len(cross_product))
param.main.param.warning(
'The cross product of different application states is very large '
f'to explore (N={len(cross_product)}), consider reducing the number '
'of options on the widgets or increase the max_states specified '
'in the function to remove this warning.'
)

nested_dict = lambda: defaultdict(nested_dict)
state_dict = nested_dict()
Expand Down
61 changes: 31 additions & 30 deletions panel/io/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class Notification(param.Parameterized):

notification_type = param.String(default=None, constant=True, label='type')

_rendered = param.Boolean(default=False)

_destroyed = param.Boolean(default=False)

def destroy(self) -> None:
Expand Down Expand Up @@ -194,46 +196,45 @@ def __css__(cls):
})
""",
"notifications": """
var notification = state.current || data.notifications[data.notifications.length-1]
if (notification._destroyed) {
return
}
var config = {
duration: notification.duration,
type: notification.notification_type,
message: notification.message
}
if (notification.background != null) {
config.background = notification.background;
}
if (notification.icon != null) {
config.icon = notification.icon;
}
var toast = state.toaster.open(config);
function destroy() {
if (state.current !== notification) {
for (notification of data.notifications) {
if (notification._destroyed || notification._rendered) {
return
}
var config = {
duration: notification.duration,
type: notification.notification_type,
message: notification.message
}
if (notification.background != null) {
config.background = notification.background;
}
if (notification.icon != null) {
config.icon = notification.icon;
}
let toast = state.toaster.open(config);
function destroy() {
notification._destroyed = true;
}
notification._rendered = true
toast.on('dismiss', destroy)
if (notification.duration) {
setTimeout(destroy, notification.duration)
}
if (notification.properties === undefined)
return
view.connect(notification.properties._destroyed.change, function () {
state.toaster.dismiss(toast)
})
}
toast.on('dismiss', destroy)
if (notification.duration) {
setTimeout(destroy, notification.duration)
}
if (notification.properties === undefined)
return
view.connect(notification.properties._destroyed.change, function () {
state.toaster.dismiss(toast)
})
""",
"_clear": "state.toaster.dismissAll()",
"position": """
script('_clear');
script('render');
for (notification of data.notifications) {
state.current = notification;
script('notifications');
notification._rendered = false;
}
state.current = undefined
script('notifications');
"""
}

Expand Down
33 changes: 26 additions & 7 deletions panel/io/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import pathlib
import signal
import sys
import threading
import uuid

from collections.abc import Callable, Mapping
Expand Down Expand Up @@ -107,23 +108,41 @@ def _origin_url(url: str) -> str:

def _server_url(url: str, port: int) -> str:
if url.startswith("http"):
return '%s:%d%s' % (url.rsplit(':', 1)[0], port, "/")
return f"{url.rsplit(':', 1)[0]}:{port}/"
else:
return 'http://%s:%d%s' % (url.split(':')[0], port, "/")
return f"http://{url.split(':')[0]}:{port}/"

_tasks = set()

def async_execute(func: Callable[..., None]) -> None:
"""
Wrap async event loop scheduling to ensure that with_lock flag
is propagated from function to partial wrapping it.
"""
if not state.curdoc or not state.curdoc.session_context:
ioloop = IOLoop.current()
event_loop = ioloop.asyncio_loop # type: ignore
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# Avoid creating IOLoop if one is not already associated
# with the asyncio loop or we're on a child thread
if hasattr(IOLoop, '_ioloop_for_asyncio') and loop in IOLoop._ioloop_for_asyncio:
ioloop = IOLoop._ioloop_for_asyncio[loop]
elif threading.current_thread() is not threading.main_thread():
ioloop = IOLoop.current()
else:
ioloop = None
wrapper = state._handle_exception_wrapper(func)
if event_loop.is_running():
ioloop.add_callback(wrapper)
if loop.is_running():
if ioloop is None:
task = asyncio.ensure_future(wrapper())
_tasks.add(task)
task.add_done_callback(_tasks.discard)
else:
ioloop.add_callback(wrapper)
else:
event_loop.run_until_complete(wrapper())
loop.run_until_complete(wrapper())
return

if isinstance(func, partial) and hasattr(func.func, 'lock'):
Expand Down
2 changes: 1 addition & 1 deletion panel/layout/accordion.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class Accordion(NamedListPanel):
>>> pn.Accordion(some_pane_with_a_name, ("Plot", some_plot))
"""

active_header_background = param.String(default='#ddd', doc="""
active_header_background = param.String(default=None, doc="""
Color for currently active headers.""")

active = param.List(default=[], doc="""
Expand Down
Loading

0 comments on commit a01ca52

Please sign in to comment.