From 7bd42ab45c35da6738bfb66c8014c37024fd3ec3 Mon Sep 17 00:00:00 2001 From: deeleeramone <> Date: Sat, 16 May 2026 20:44:14 -0700 Subject: [PATCH 1/5] fix chart handler getting swallowed --- pywry/examples/pywry_demo_deepagent_nvidia.py | 55 +++++++++---------- pywry/pywry/frontend/src/chat-handlers.js | 12 +++- pywry/pywry/frontend/src/main.js | 12 ++++ pywry/pywry/frontend/src/system-events.js | 9 +++ pywry/pywry/templates.py | 6 ++ pywry/pywry/widget.py | 6 ++ 6 files changed, 69 insertions(+), 31 deletions(-) diff --git a/pywry/examples/pywry_demo_deepagent_nvidia.py b/pywry/examples/pywry_demo_deepagent_nvidia.py index 6da1394..b655f7e 100644 --- a/pywry/examples/pywry_demo_deepagent_nvidia.py +++ b/pywry/examples/pywry_demo_deepagent_nvidia.py @@ -84,22 +84,19 @@ def new_event_loop(self) -> asyncio.AbstractEventLoop: from pywry.tvchart import build_tvchart_toolbars # noqa: E402 -DEFAULT_MODEL = "qwen/qwen3-coder-480b-a35b-instruct" - -# Preferred tool-capable chat models exposed through NVIDIA NIM, in -# priority order. The first model that the live ``ChatNVIDIA. -# get_available_models()`` lookup actually returns is picked as the -# default. Llama variants are intentionally excluded — they under- -# perform on strict-format tool-calling compared to the models below. +DEFAULT_MODEL = "deepseek-ai/deepseek-v4-pro" + +# Small curated set of currently-hosted tool-capable chat models on +# NVIDIA NIM (May 2026). Kept intentionally short — every entry must +# (a) support tool/function calling, (b) be currently served (no 4xx +# at inference), and (c) be strong enough for the chart-agent loop. +# The first entry that ``ChatNVIDIA.get_available_models()`` confirms +# is live becomes the default; the rest populate the model dropdown. PREFERRED_TOOL_MODELS = [ + "deepseek-ai/deepseek-v4-pro", + "nvidia/nemotron-3-super-120b-a12b", "qwen/qwen3-coder-480b-a35b-instruct", - "deepseek-ai/deepseek-v3.1", - "deepseek-ai/deepseek-r1", - "moonshotai/kimi-k2-instruct-0905", "openai/gpt-oss-120b", - "qwen/qwen3-235b-a22b-instruct-2507", - "zai-org/glm-4.5-air", - "mistralai/mistral-nemotron", ] CHART_SYSTEM_PROMPT = """\ @@ -304,26 +301,28 @@ def stop() -> None: def _fetch_nvidia_models() -> list[str]: - """Fetch available tool-capable chat models from NVIDIA NIM.""" + """Return the curated tool-capable models actually live on NIM. + + Filters ``PREFERRED_TOOL_MODELS`` against the live + ``ChatNVIDIA.get_available_models()`` catalog so the dropdown only + ever shows models we've vetted AND that NVIDIA is currently serving. + Falls back to the full curated list on lookup failure so the UI is + never empty. + """ try: from langchain_nvidia_ai_endpoints import ChatNVIDIA - available = ChatNVIDIA.get_available_models() - model_ids = sorted( + live_ids = { m.id - for m in available + for m in ChatNVIDIA.get_available_models() if getattr(m, "model_type", None) == "chat" and getattr(m, "supports_tools", False) - ) - if not model_ids: - return [DEFAULT_MODEL] - for preferred in PREFERRED_TOOL_MODELS: - if preferred in model_ids: - model_ids = [preferred, *[m for m in model_ids if m != preferred]] - break - if model_ids: - return model_ids - except Exception: - return [DEFAULT_MODEL] + } + except Exception as exc: + print(f"[deepagent] NVIDIA model catalog lookup failed ({exc}); using curated list.") + return list(PREFERRED_TOOL_MODELS) + + curated_live = [m for m in PREFERRED_TOOL_MODELS if m in live_ids] + return curated_live or list(PREFERRED_TOOL_MODELS) def main() -> None: diff --git a/pywry/pywry/frontend/src/chat-handlers.js b/pywry/pywry/frontend/src/chat-handlers.js index ee87042..be55d1a 100644 --- a/pywry/pywry/frontend/src/chat-handlers.js +++ b/pywry/pywry/frontend/src/chat-handlers.js @@ -1878,12 +1878,18 @@ function initChatHandlers(container, pywry) { // they try to reconnect, which is the intended behaviour. var html = data.html || data.content || ''; if (data.widgetId && data.revision) { + // NOTE: tag literals are split so this script can be safely inlined + // into a host document's without a regex-based head/script + // extractor (see pywry/__main__.py) terminating early on a stray + // closing tag inside this string. + var headOpen = '<' + 'head>'; + var headClose = ''; var meta = ''; // Insert meta tag into if present, else as first element. - if (html.indexOf('') !== -1) { - html = html.replace('', '' + meta); + if (html.indexOf(headOpen) !== -1) { + html = html.replace(headOpen, headOpen + meta); } else if (html.indexOf('') !== -1) { - html = html.replace('', '' + meta + ''); + html = html.replace('', '' + headOpen + meta + headClose); } else { html = meta + html; } diff --git a/pywry/pywry/frontend/src/main.js b/pywry/pywry/frontend/src/main.js index a7b843c..fba51d7 100644 --- a/pywry/pywry/frontend/src/main.js +++ b/pywry/pywry/frontend/src/main.js @@ -23,6 +23,12 @@ window.pywry = { htmlEl.classList.add('dark', 'pywry-theme-dark'); } document.getElementById('app').innerHTML = html; + if (typeof initToolbarHandlers === 'function') { + initToolbarHandlers(document, window.pywry); + } + if (typeof initChatHandlers === 'function') { + initChatHandlers(document, window.pywry); + } window.pywry.sendEvent('content:ready', { timestamp: Date.now() }); }, @@ -231,6 +237,12 @@ function registerBuiltinHandlers() { } else { document.body.innerHTML = data.html; } + if (typeof initToolbarHandlers === 'function') { + initToolbarHandlers(document, window.pywry); + } + if (typeof initChatHandlers === 'function') { + initChatHandlers(document, window.pywry); + } } }); diff --git a/pywry/pywry/frontend/src/system-events.js b/pywry/pywry/frontend/src/system-events.js index 88a1a84..789f64d 100644 --- a/pywry/pywry/frontend/src/system-events.js +++ b/pywry/pywry/frontend/src/system-events.js @@ -119,6 +119,9 @@ window.pywry.on('pywry:set-content', function(data) { window.pywry.setContent(data); + if (typeof initChatHandlers === 'function') { + initChatHandlers(document, window.pywry); + } }); window.pywry.on('pywry:refresh', function() { @@ -224,6 +227,12 @@ } else { document.body.innerHTML = data.html; } + if (typeof initToolbarHandlers === 'function') { + initToolbarHandlers(document, window.pywry); + } + if (typeof initChatHandlers === 'function') { + initChatHandlers(document, window.pywry); + } } }); diff --git a/pywry/pywry/templates.py b/pywry/pywry/templates.py index 7abfc8c..8c72c34 100644 --- a/pywry/pywry/templates.py +++ b/pywry/pywry/templates.py @@ -834,5 +834,11 @@ def build_content_update_script(html_content: str) -> str: }} else {{ document.body.innerHTML = '
' + {escaped_html} + '
'; }} + if (typeof initToolbarHandlers === 'function') {{ + initToolbarHandlers(document, window.pywry); + }} + if (typeof initChatHandlers === 'function') {{ + initChatHandlers(document, window.pywry); + }} }})(); """ diff --git a/pywry/pywry/widget.py b/pywry/pywry/widget.py index 021a571..a5a621c 100644 --- a/pywry/pywry/widget.py +++ b/pywry/pywry/widget.py @@ -658,6 +658,9 @@ def _get_aggrid_widget_esm() -> str: if (event.type === 'pywry:update-html' && event.data && event.data.html) { container.innerHTML = event.data.html; initToolbarHandlers(container, pywry); + if (typeof initChatHandlers === 'function') { + initChatHandlers(container, pywry); + } } const inlineHandledEvents = ['pywry:update-theme', 'pywry:inject-css', 'pywry:remove-css', @@ -1256,6 +1259,9 @@ def _get_widget_esm() -> str: if (event.type === 'pywry:update-html' && event.data && event.data.html) { container.innerHTML = event.data.html; initToolbarHandlers(container, pywry); + if (typeof initChatHandlers === 'function') { + initChatHandlers(container, pywry); + } } const inlineHandledEvents = ['pywry:update-theme', 'pywry:inject-css', 'pywry:remove-css', From 9fcbad5829b25c1bdbfafb702cf8d16bcadcfab0 Mon Sep 17 00:00:00 2001 From: deeleeramone <> Date: Sat, 16 May 2026 20:46:41 -0700 Subject: [PATCH 2/5] version bump --- claude/plugins/pywry/.claude-plugin/marketplace.json | 2 +- claude/plugins/pywry/.claude-plugin/plugin.json | 2 +- pywry/pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/claude/plugins/pywry/.claude-plugin/marketplace.json b/claude/plugins/pywry/.claude-plugin/marketplace.json index b87a203..92f6821 100644 --- a/claude/plugins/pywry/.claude-plugin/marketplace.json +++ b/claude/plugins/pywry/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "pywry", "source": "./", "description": "Native Claude Code integration for PyWry — MCP tools for generating and rendering HTML components, chat artifacts, and building native, web, or Jupyter applications with live preview. Built-in support for AgGrid, Plotly, TradingView, and more.", - "version": "0.1.0", + "version": "0.1.1", "author": { "name": "PyWry", "email": "pywry2@gmail.com", diff --git a/claude/plugins/pywry/.claude-plugin/plugin.json b/claude/plugins/pywry/.claude-plugin/plugin.json index 79812f9..10bc786 100644 --- a/claude/plugins/pywry/.claude-plugin/plugin.json +++ b/claude/plugins/pywry/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "pywry", - "version": "0.1.0", + "version": "0.1.1", "description": "Native Claude Code integration for PyWry — MCP tools for generating and rendering HTML components, chat artifacts, and building native, web, or Jupyter applications with live preview. Built-in support for AgGrid, Plotly, TradingView, and more.", "author": { "name": "PyWry", diff --git a/pywry/pyproject.toml b/pywry/pyproject.toml index d091de4..5860c74 100644 --- a/pywry/pyproject.toml +++ b/pywry/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pywry" -version = "2.0.1" +version = "2.0.2" description = "A lightweight and blazingly fast, cross-platform, WebView rendering engine and desktop UI toolkit for Python. Batteries included." authors = [{ name = "PyWry", email = "pywry2@gmail.com" }] license = { text = "Apache 2.0" } From bb35e20690a23d2dbc77e7bf4db908f428bad9f4 Mon Sep 17 00:00:00 2001 From: deeleeramone <> Date: Sat, 16 May 2026 20:50:03 -0700 Subject: [PATCH 3/5] claude marketplace version --- claude/.claude-plugin/marketplace.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/claude/.claude-plugin/marketplace.json b/claude/.claude-plugin/marketplace.json index 4550ea9..5f832dd 100644 --- a/claude/.claude-plugin/marketplace.json +++ b/claude/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "pywry", "source": "./plugins/pywry/", "description": "Native Claude Code integration for PyWry — MCP tools for generating and rendering HTML components, chat artifacts, and building native, web, or Jupyter applications with live preview. Built-in support for AgGrid, Plotly, TradingView, and more.", - "version": "0.1.0", + "version": "0.1.1", "author": { "name": "PyWry", "email": "pywry2@gmail.com", From 52b8f6fcf8c72682cc3bfbc2102ca93597e2ef95 Mon Sep 17 00:00:00 2001 From: deeleeramone <> Date: Sat, 16 May 2026 20:54:42 -0700 Subject: [PATCH 4/5] claude marketplace version --- claude/desktop-extension/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/claude/desktop-extension/manifest.json b/claude/desktop-extension/manifest.json index 7da9568..5ab7a54 100644 --- a/claude/desktop-extension/manifest.json +++ b/claude/desktop-extension/manifest.json @@ -2,7 +2,7 @@ "manifest_version": "0.4", "name": "pywry", "display_name": "PyWry", - "version": "0.1.0", + "version": "0.1.1", "description": "Native Claude Desktop integration for PyWry — MCP tools for generating and rendering HTML components, chat artifacts, and building native, web, or Jupyter applications with live preview. Built-in support for AgGrid, Plotly, TradingView, and more.", "long_description": "PyWry is a cross-platform rendering engine and desktop UI toolkit for Python. This Desktop Extension bundles the PyWry MCP server (66+ tools) so Claude Desktop can scaffold widgets, render Plotly charts, drive AG Grid tables, build TradingView lightweight charts, and produce streaming chat artifacts — all delivered as embedded HTML resources viewable directly in the artifact pane. The `uv` runtime resolves `pywry[mcp]` from PyPI on first launch; no manual `pip install` is required.", "author": { From edc010e6f4ed80769f77cdecadc751e6ffc7f1fb Mon Sep 17 00:00:00 2001 From: deeleeramone <> Date: Sat, 16 May 2026 20:56:56 -0700 Subject: [PATCH 5/5] claude changelog --- claude/plugins/pywry/CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/claude/plugins/pywry/CHANGELOG.md b/claude/plugins/pywry/CHANGELOG.md index 63a4cd7..89a8a08 100644 --- a/claude/plugins/pywry/CHANGELOG.md +++ b/claude/plugins/pywry/CHANGELOG.md @@ -3,6 +3,25 @@ Versioning follows [semver](https://semver.org/). Format is a feature list per release — not a delta. +## 0.1.1 — chat-handler regression fix + +**Chat widget reliability.** Fixed a regression where the desktop chat +panel rendered empty (no welcome message, dead buttons) after a +`set_content` IPC swap. Root cause: `chat-handlers.js` contained a +literal `''` string inside its iframe meta-tag injection +helper. When inlined into the host document's ``, the desktop +subprocess's regex-based head-script extractor terminated early on +the stray tag, dropping the chat-handlers `