docs: add frontend console debugging guide and mermaid syntax standards

This commit is contained in:
fujie
2026-01-10 15:41:17 +08:00
parent b71df8ef43
commit 5fa56ba88d
2 changed files with 214 additions and 0 deletions

View File

@@ -0,0 +1,150 @@
# 🛠️ Debugging Python Plugins with Frontend Console
When developing plugins for Open WebUI, debugging can be challenging. Standard `print()` statements or server-side logging might not always be accessible, especially in hosted environments or when you want to see the data flow in real-time alongside the UI interactions.
This guide introduces a powerful technique: **Frontend Console Debugging**. By injecting JavaScript from your Python plugin, you can print structured logs directly to the browser's Developer Tools console (F12).
## Why Frontend Debugging?
* **Real-time Feedback**: See logs immediately as actions happen in the browser.
* **Rich Objects**: Inspect complex JSON objects (like `body` or `messages`) interactively, rather than reading massive text dumps.
* **No Server Access Needed**: Debug issues even if you don't have SSH/Console access to the backend server.
* **Clean Output**: Group logs using `console.group()` to keep your console organized.
## The Core Mechanism
Open WebUI plugins (both Actions and Filters) support an event system. We can leverage the `__event_call__` (or sometimes `__event_emitter__`) to send a special event of type `execute`. This tells the frontend to run the provided JavaScript code.
### The Helper Method
To make this easy to use, we recommend adding a helper method `_emit_debug_log` to your plugin class.
```python
import json
from typing import List
async def _emit_debug_log(
self,
__event_call__,
title: str,
data: dict
):
"""
Emit debug log to browser console via JS execution.
Args:
__event_call__: The event callable passed to action/outlet.
title: A title for the log group.
data: A dictionary of data to log.
"""
# 1. Check if debugging is enabled (recommended)
if not getattr(self.valves, "show_debug_log", True) or not __event_call__:
return
try:
# 2. Construct the JavaScript code
# We use an async IIFE (Immediately Invoked Function Expression)
# to ensure a clean scope and support await if needed.
js_code = f"""
(async function() {{
console.group("🛠️ Plugin Debug: {title}");
console.log({json.dumps(data, ensure_ascii=False)});
console.groupEnd();
}})();
"""
# 3. Send the execute event
await __event_call__(
{
"type": "execute",
"data": {"code": js_code},
}
)
except Exception as e:
print(f"Error emitting debug log: {e}")
```
## Implementation Steps
### 1. Add a Valve for Control
It's best practice to make debugging optional so it doesn't clutter the console for normal users.
```python
from pydantic import BaseModel, Field
class Filter:
class Valves(BaseModel):
show_debug_log: bool = Field(
default=False,
description="Print debug logs to browser console (F12)"
)
def __init__(self):
self.valves = self.Valves()
```
### 2. Inject `__event_call__`
Ensure your `action` (for Actions) or `outlet` (for Filters) method accepts `__event_call__`.
**For Filters (`outlet`):**
```python
async def outlet(
self,
body: dict,
__user__: Optional[dict] = None,
__event_call__=None, # <--- Add this
__metadata__: Optional[dict] = None,
) -> dict:
```
**For Actions (`action`):**
```python
async def action(
self,
body: dict,
__user__=None,
__event_call__=None, # <--- Add this
__request__=None,
):
```
### 3. Call the Helper
Now you can log anything, anywhere in your logic!
```python
# Inside your logic...
new_content = self.process_content(content)
# Log the before and after
await self._emit_debug_log(
__event_call__,
"Content Normalization",
{
"original": content,
"processed": new_content,
"changes": diff_list
}
)
```
## Best Practices
1. **Use `json.dumps`**: Always serialize your Python dictionaries to JSON strings before embedding them in the f-string. This handles escaping quotes and special characters correctly.
2. **Async IIFE**: Wrapping your JS in `(async function() { ... })();` is safer than raw code. It prevents variable collisions with other scripts and allows using `await` inside your debug script if you ever need to check DOM elements.
3. **Check for None**: Always check if `__event_call__` is not None before using it, as it might not be available in all contexts (e.g., when running tests or in older Open WebUI versions).
## Example Output
When enabled, your browser console will show:
```text
> 🛠️ Plugin Debug: Content Normalization
> {original: "...", processed: "...", changes: [...]}
```
You can expand the object to inspect every detail of your data. Happy debugging!

View File

@@ -0,0 +1,64 @@
# Mermaid Syntax Standards & Best Practices
This document summarizes the official syntax standards for Mermaid flowcharts, focusing on node labels, quoting rules, and special character handling. It serves as a reference for the `markdown_normalizer` plugin logic.
## 1. Node Shapes & Syntax
Mermaid supports various node shapes defined by specific wrapping characters.
| Shape | Syntax | Example |
| :--- | :--- | :--- |
| **Rectangle** (Default) | `id[Label]` | `A[Start]` |
| **Rounded** | `id(Label)` | `B(Process)` |
| **Stadium** (Pill) | `id([Label])` | `C([End])` |
| **Subroutine** | `id[[Label]]` | `D[[Subroutine]]` |
| **Cylinder** (Database) | `id[(Label)]` | `E[(Database)]` |
| **Circle** | `id((Label))` | `F((Point))` |
| **Double Circle** | `id(((Label)))` | `G(((Endpoint)))` |
| **Asymmetric** | `id>Label]` | `H>Flag]` |
| **Rhombus** (Decision) | `id{Label}` | `I{Decision}` |
| **Hexagon** | `id{{Label}}` | `J{{Prepare}}` |
| **Parallelogram** | `id[/Label/]` | `K[/Input/]` |
| **Parallelogram Alt** | `id[\Label\]` | `L[\Output\]` |
| **Trapezoid** | `id[/Label\]` | `M[/Trap/]` |
| **Trapezoid Alt** | `id[\Label/]` | `N[\TrapAlt/]` |
## 2. Quoting Rules (Critical)
### Why Quote?
Quoting node labels is **highly recommended** and sometimes **mandatory** to prevent syntax errors.
### Mandatory Quoting Scenarios
You **MUST** enclose labels in double quotes `"` if they contain:
1. **Special Characters**: `()`, `[]`, `{}`, `;`, `"`, etc.
2. **Keywords**: Words like `end`, `subgraph`, etc., if used in specific contexts.
3. **Unicode/Emoji**: While often supported without quotes, quoting ensures consistent rendering across different environments.
4. **Markdown**: If you want to use Markdown formatting (bold, italic) inside a label.
### Best Practice: Always Quote
To ensure robustness, especially when processing LLM-generated content which may contain unpredictable characters, **always enclosing labels in double quotes is the safest strategy**.
**Examples:**
* ❌ Risky: `id(Start: 15:00)` (Colon might be interpreted as style separator)
* ✅ Safe: `id("Start: 15:00")`
* ❌ Broken: `id(Func(x))` (Nested parentheses break parsing)
* ✅ Safe: `id("Func(x)")`
## 3. Escape Characters
Inside a quoted string:
* Double quotes `"` must be escaped as `\"`.
* HTML entities (e.g., `#35;` for `#`) can be used.
## 4. Plugin Logic Verification
The `markdown_normalizer` plugin implements the following logic:
1. **Detection**: Identifies Mermaid node definitions using a comprehensive regex covering all shapes above.
2. **Normalization**:
* Checks if the label is already quoted.
* If **NOT quoted**, it wraps the label in double quotes `""`.
* Escapes any existing double quotes inside the label (`"` -> `\"`).
3. **Shape Preservation**: The regex captures the specific opening and closing delimiters (e.g., `((` and `))`) to ensure the node shape is strictly preserved during normalization.
**Conclusion**: The plugin's behavior of automatically adding quotes to unquoted labels is **fully aligned with Mermaid's official best practices** for robustness and error prevention.