Skip to content

fix(smart-mind-map): prevent OWUI v0.10+ frontend crash on action early-return#102

Merged
Fu-Jie merged 7 commits into
mainfrom
fix/issue-101-mindmap-v10
Jul 2, 2026
Merged

fix(smart-mind-map): prevent OWUI v0.10+ frontend crash on action early-return#102
Fu-Jie merged 7 commits into
mainfrom
fix/issue-101-mindmap-v10

Conversation

@Fu-Jie

@Fu-Jie Fu-Jie commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Closes #101

Problem

On OpenWebUI v0.10+, clicking the Smart Mind Map action icon crashes the frontend:

Chat.svelte:1796 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'content')
    at Se (Chat.svelte:1796:37)
    at async V (Messages.svelte:427:3)

The Docker log shows the action itself returns HTTP 200 in ~38 ms (no LLM call), yet the browser crashes.

Root cause (confirmed against OWUI main source)

I fetched the current OWUI main source to trace the exact failure path instead of guessing.

Frontend (Chat.sveltechatActionHandler, Chat.svelte:1791-1802):

if (res !== null && res.messages) {
    for (const message of res.messages) {
        history.messages[message.id] = {
            ...history.messages[message.id],
            ...(history.messages[message.id].content !== message.content   // ← line 1796
                ? { originalContent: history.messages[message.id].content }
                : {}),
            ...message
        };
    }
}

The frontend looks up every returned message by id in history.messages. If history.messages[message.id] is undefined, accessing .content throws.

Plugin (before this fix) — all three early-return paths appended a new message dict without an id:

body["messages"].append({"role": "assistant", "content": "❌ ..."})
return body

When the frontend iterates that appended message, message.id is undefined, history.messages[undefined] is undefined, and the crash follows. This is exactly the 38 ms / HTTP 200 / no-LLM-call scenario in the issue: an early-return path was hit (empty/short content), the plugin appended an id-less message, and the frontend crashed while processing the otherwise-successful response.

Fix

Update the existing last message's content (it already carries a valid id) instead of appending a new id-less message. The OWUI frontend preserves the original content as originalContent before applying the update, so nothing is lost. The error is still surfaced to the user via the notification emitter (_emit_notification).

  • New _set_last_message_content(body, content) helper with safe fallbacks (no-op when body has no messages, handles non-dict last entry, preserves id and other fields).
  • Rewired all three early-return paths (no messages / no extractable content / text too short) to use it.
  • Path 希望可以统一一下图标风格 #1 (messages is not a list) now simply returns body unchanged (cannot safely touch body["messages"]).
  • Path Build MkDocs documentation portal with Material theme and GitHub Pages deployment #3 (text too short) preserves the original short text alongside the warning, mirroring the existing exception handler.
  • Bumped version 1.0.11.0.2.

Tests

New regression test suite (tests/plugins/actions/smart-mind-map/test_issue_101_regression.py, 11 cases). Verified the tests fail on the old code (11 failures, including assert 3 == 2 showing the extra appended message, and Message without id would crash OWUI v0.10 frontend) and pass on the fix.

Key invariant enforced: every message in the returned body["messages"] must carry an id.

tests/plugins/actions/smart-mind-map/test_issue_101_regression.py ...... [ 54%]
.....                                                                    [100%]
11 passed

Issue #97 regression tests still pass (no break to markdown_normalizer):

tests/plugins/filters/markdown_normalizer/test_issue_97_regression.py ..   [ 33%]
....                                                                       [100%]
6 passed

Notes for the issue reporter

  • The "No content found to export!" message you saw is the plugin's error_no_content notification — it means the last assistant message had no extractable text content at the moment the action ran. After this fix the crash is gone; if you still see the notification (without a crash), that's a separate content-availability question (e.g. the model response being too short, or stored in a structured-output format). Please retry on v1.0.2 and report back.

…ly-return (issue #101)

On OpenWebUI v0.10+, clicking the Smart Mind Map action crashed the
frontend with `Cannot read properties of undefined (reading 'content')`
at Chat.svelte (chatActionHandler).

Root cause: the action's three early-return paths (no messages / no
extractable content / text too short) appended a *new* message dict
without an `id` field to body["messages"]. The OWUI v0.10+ frontend
iterates the returned messages and does `history.messages[message.id].content`
for each entry; when `message.id` is undefined, the lookup returns undefined
and accessing `.content` throws.

Fix: update the *existing* last message's content (it already carries a
valid `id`) instead of appending a new id-less message. The frontend
preserves the original content as `originalContent` before applying the
update, so nothing is lost. The error is still surfaced via the
notification emitter.

- Add `_set_last_message_content` helper with safe fallbacks
- Rewire all three early-return paths to use it
- Add regression tests verifying every returned message carries an `id`
- Bump version to 1.0.2
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

✅ Plugin Version Check / 插件版本检查

版本更新检测通过!PR 包含版本变化和更新说明。

Version check passed! PR contains version changes and update description.


版本变化 / Version Changes

Plugin Updates


This comment was generated automatically. / 此评论由自动生成。

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request addresses a frontend crash (issue #101) on OpenWebUI v0.10+ by updating the last message's content in-place instead of appending a new id-less message, and adds a regression test suite. The feedback recommends making the message update helper more robust by ensuring an id field is always present (with a fallback), updating the regression tests to verify this fallback, and syncing the version bump to 1.0.2 across all required documentation files per the repository style guide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +1719 to +1722
last = messages[-1]
if not isinstance(last, dict):
messages[-1] = {"role": "assistant"}
messages[-1]["content"] = content

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

If last is not a dictionary, it is replaced with {"role": "assistant"}. However, this new dictionary does not contain an id field. Since the core issue (#101) is that the frontend crashes when a message lacks an id field, returning a message without an id will still trigger the same crash.

Furthermore, if last is a dictionary but somehow lacks an id field, it will also cause a crash.

To make this robust and defensive, we should ensure that the updated message always has an id field, falling back to body.get("id") (which represents the current assistant message ID) if it is missing.

Suggested change
last = messages[-1]
if not isinstance(last, dict):
messages[-1] = {"role": "assistant"}
messages[-1]["content"] = content
last = messages[-1]
if not isinstance(last, dict):
last = {"role": "assistant"}
messages[-1] = last
if "id" not in last and isinstance(body, dict) and "id" in body:
last["id"] = body["id"]
last["content"] = content

author_url: https://github.com/Fu-Jie/openwebui-extensions
funding_url: https://github.com/open-webui
version: 1.0.1
version: 1.0.2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

According to the repository style guide's Documentation Sync rule, when changing a plugin and bumping its version, you must update the version and release notes in all associated documentation files.

Currently, smart_mind_map.py has been bumped to 1.0.2, but README.md and README_CN.md still reference 1.0.1. Please ensure the following files are updated to maintain consistency:

  1. plugins/actions/smart-mind-map/README.md
  2. plugins/actions/smart-mind-map/README_CN.md
  3. docs/plugins/actions/smart-mind-map.md
  4. docs/plugins/actions/smart-mind-map.zh.md
  5. docs/plugins/actions/index.md
  6. docs/plugins/actions/index.zh.md
References
  1. Must update all documentation files (README, README_CN, docs, index) with the new version when changing a plugin. (link)

Comment on lines +149 to +153
def test_handles_non_dict_last_message(self):
action = _make_action()
body = {"messages": ["not-a-dict"]}
action._set_last_message_content(body, "recovered")
assert body["messages"][-1]["content"] == "recovered"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To align with the defensive fallback id logic added to _set_last_message_content, we should update this test to verify that the fallback id is correctly assigned when the last message is not a dictionary.

Suggested change
def test_handles_non_dict_last_message(self):
action = _make_action()
body = {"messages": ["not-a-dict"]}
action._set_last_message_content(body, "recovered")
assert body["messages"][-1]["content"] == "recovered"
def test_handles_non_dict_last_message(self):
action = _make_action()
body = {"id": "fallback-id", "messages": ["not-a-dict"]}
action._set_last_message_content(body, "recovered")
assert body["messages"][-1]["content"] == "recovered"
assert body["messages"][-1]["id"] == "fallback-id"

Fu-Jie and others added 6 commits July 2, 2026 10:53
…I v0.10+ (issue #101)

OWUI v0.10+ stores assistant replies in a structured `output` field and
leaves the flat `content` empty. The frontend-crash fix in v1.0.2 stopped
the `Cannot read properties of undefined (reading 'content')` error, but
the plugin still hit the "No content found to export!" early-return path
because `_extract_text_content` saw an empty `content` — so the action
wasn't actually usable on v0.10+.

This is the second layer of issue #101, mirroring the approach in PR #103
(rbb-dev's fix for export_to_excel / export_to_docx):

- New `_extract_text_from_output` rebuilds assistant text from the
  structured `output` using OWUI's own `convert_output_to_messages`
  (reasoning excluded), so we follow upstream schema changes instead of
  hand-rolling a parser.
- New `_recover_message_content` resolves text in three steps:
  1. existing flat `content` when non-empty (pre-v0.10, or OWUI backfilled)
  2. inline `msg.output` when present on the payload
  3. DB lookup via `ChatMessages.get_message_by_id(chat_id-message_id)`
     for when the payload doesn't carry `output` at all
  Recovered text is backfilled onto `msg["content"]` so downstream code
  reads it uniformly.
- Guarded imports: on older OWUI versions without `ChatMessages` /
  `convert_output_to_messages`, the recovery silently no-ops and the
  plugin falls back to the original `content`-only path — no breakage.
- Action's aggregation loop now calls `_recover_message_content` instead
  of `_extract_text_content`.
- Bump version 1.0.2 → 1.0.3.

Tests: 22 cases (was 11). New `TestRecoverContentFromOutput` (8 unit
tests) and `TestActionUsesOutputRecovery` (3 end-to-end tests) cover
inline output, DB lookup, missing record, reasoning exclusion, empty
fallback, and the id-invariant under the full action flow. Verified
the new tests fail on v1.0.2 code (8 failures, methods absent) and pass
on v1.0.3.
Co-authored-by: traeagent <traeagent@users.noreply.github.com>
…w-version users

- Add `required_open_webui_version: 0.10.2` metadata (aligns with PR #103's
  approach for export_to_excel/docx). OWUI enforces this at install time.
- Add 4 backward-compat regression tests (TestPreV010BackwardCompat) that
  simulate pre-v0.10 environment (ChatMessages=None, convert_output_to_messages=None)
  and verify: module loads, content read directly, empty content returns empty
  without DB attempt, full action flow preserves id invariant.

Low-version users should install v1.0.1 (last pre-fix release):
https://github.com/Fu-Jie/openwebui-extensions/releases/tag/smart-mind-map-v1.0.1

Note: the guarded imports still let v1.0.3 *run* on older OWUI, but
`required_open_webui_version` makes the supported boundary explicit and
prevents silent partial functionality (recovery falls back to empty on
pre-v0.10 when content is empty).
Co-authored-by: traeagent <traeagent@users.noreply.github.com>
Update the Smart Mind Map version badge in docs/plugins/actions/index.md
and index.zh.md from 1.0.1 to 1.0.3 so all 7 version locations stay
consistent (release-prep skill check now passes).
…ersion locations

v1.0.3 was never tagged/released, so collapse both layers of the OWUI
v0.10+ fix (frontend crash + empty content recovery) into a single
v1.0.2 release. Syncs .py docstring/log strings, README/README_CN,
docs, index, and renames v1.0.3(.md|_CN.md) -> v1.0.2(.md|_CN.md).

Version consistency check passes; 26 regression tests still green.
@Fu-Jie
Fu-Jie merged commit 8b2ed0f into main Jul 2, 2026
1 check passed
@Fu-Jie
Fu-Jie deleted the fix/issue-101-mindmap-v10 branch July 2, 2026 11:37
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.

[BUG] Smart Mind Map no longer works on OWUI v0.10+

1 participant