Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## v3.8.1 可配置长图渲染与浏览器运行时回退

本版本为 AI 渲染工具新增适合长内容发送的长图布局,可精确控制成图宽度与内边距;同时完善 Playwright 浏览器选择,在缺少内置浏览器时可使用已配置或系统安装的 Chrome / Chromium 完成渲染。

- 扩展 `render.render_html` 与 `render.render_markdown`。新增 `layout=long`、`width` 和 `padding` 参数;长图默认宽度为 900 像素、内边距为 28 像素,并可通过 `[network]` 配置统一调整。默认布局保持原有行为,避免影响现有调用。
- 优化长图版式与输出尺寸。长图模式移除页面外部留白和 Markdown 内容最大宽度限制,使正文填满指定画布;截图使用 CSS 像素缩放,最终图片宽度与请求的 `width` 一致,便于聊天平台直接预览和发送。
- 完善渲染浏览器选择。新增 `render_browser_executable_path` 配置;Playwright 内置浏览器缺失时自动探测系统 Chrome / Chromium,显式配置路径无效或其他启动错误仍会直接报告,避免掩盖真实故障。
- 收紧 HTML 渲染网络边界。BrowserContext 强制离线、禁用 Service Worker,并在上下文级终止全部网络请求,不再依赖可被 DNS 重绑定绕过的主机名预检;内联 CSS、脚本及 `data:` / `blob:` 资源仍可使用。LaTeX 同步收敛为本地 mathtext 渲染,复杂且不受支持的 TeX 不再等待外部 MathJax CDN。
- 加固渲染缓存与配置集成。缓存键纳入截图缩放和样式参数,避免不同布局错误复用缓存;同步环境变量、热更新边界、配置模板、部署与使用文档,并补充长图参数、缓存隔离、浏览器回退和实际渲染回归测试。

---

## v3.8.0 多协议 LLM SDK、推理回放与 WebUI 可用性

本版本重构生成模型请求层,统一 OpenAI Chat Completions、OpenAI Responses 与 Anthropic Messages 的 SDK 调用和配置语义,补全多轮工具调用中的原生推理载体回放,并修复 WebUI 配置编辑器与多行日志查询的可用性问题。
Expand Down
4 changes: 2 additions & 2 deletions apps/undefined-chat/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/undefined-chat/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "undefined-chat",
"private": true,
"version": "3.8.0",
"version": "3.8.1",
"type": "module",
"scripts": {
"tauri": "tauri",
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-chat/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/undefined-chat/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "undefined_chat"
version = "3.8.0"
version = "3.8.1"
description = "Undefined native chat client"
authors = ["Undefined contributors"]
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-chat/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Undefined Chat",
"version": "3.8.0",
"version": "3.8.1",
"identifier": "com.undefined.chat",
"build": {
"beforeDevCommand": "npm run dev",
Expand Down
4 changes: 2 additions & 2 deletions apps/undefined-console/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/undefined-console/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "undefined-console",
"private": true,
"version": "3.8.0",
"version": "3.8.1",
"type": "module",
"scripts": {
"tauri": "tauri",
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-console/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/undefined-console/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "undefined_console"
version = "3.8.0"
version = "3.8.1"
description = "Undefined cross-platform management console"
authors = ["Undefined contributors"]
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-console/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Undefined Console",
"version": "3.8.0",
"version": "3.8.1",
"identifier": "com.undefined.console",
"build": {
"beforeDevCommand": "npm run dev",
Expand Down
13 changes: 11 additions & 2 deletions config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -1095,12 +1095,21 @@ request_retries = 0
# zh: HTML/Markdown 图片渲染配置。
# en: HTML/Markdown image rendering settings.
[render]
# zh: 是否让 crawl4ai、HTML/Markdown/LaTeX 渲染中的远程资源使用 [proxy] 代理地址。默认关闭。
# en: Whether crawl4ai and remote resources in HTML/Markdown/LaTeX rendering use proxy addresses from [proxy]. Disabled by default.
# zh: 是否让 crawl4ai 等网页抓取链路使用 [proxy] 代理地址。HTML/Markdown 浏览器渲染始终离线。默认关闭。
# en: Whether crawl4ai and other web crawling paths use proxy addresses from [proxy]. HTML/Markdown browser rendering is always offline. Disabled by default.
use_proxy = false
# zh: 渲染浏览器最大同时开启数量。0 表示自动:Linux 默认 1,其它平台默认 2。
# en: Max concurrent render browser pages. 0 = auto: Linux defaults to 1, other platforms default to 2.
browser_max_concurrency = 0
# zh: 可选的 Chrome/Chromium 可执行文件路径。留空时优先使用 Playwright 自带浏览器,缺失时再自动查找系统浏览器。
# en: Optional Chrome/Chromium executable path. Empty prefers Playwright's bundled browser, then discovers an installed system browser if missing.
browser_executable_path = ""
# zh: render_html/render_markdown 的 long 布局未显式传 width 时使用的最终图片宽度(像素,320-2048)。
# en: Final image width used by the long layout when width is omitted (pixels, 320-2048).
long_image_default_width = 900
# zh: long 布局未显式传 padding 时使用的内边距(像素,0-160)。
# en: Content padding used by the long layout when padding is omitted (pixels, 0-160).
long_image_default_padding = 28

# zh: HTML 渲染结果缓存:基于 HTML 内容 hash 复用同一张图片,避免重复渲染。
# en: HTML render result cache: reuse rendered images by content hash to skip re-rendering.
Expand Down
8 changes: 5 additions & 3 deletions docs/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ uv sync --group dev -p 3.12
uv run playwright install
```

### LaTeX 渲染环境
### 渲染环境

`render.render_latex` 会优先使用 Python 依赖中的 `matplotlib` mathtext 在本地渲染常见数学公式,不需要额外安装系统 TeX。mathtext 无法处理的复杂内容会回退到 MathJax + Playwright,因此请确保已经执行:
`render.render_latex` 使用 Python 依赖中的 `matplotlib.mathtext` 本地渲染常见数学公式,不需要系统 TeX、Playwright 或外部网络。复杂 TeX 环境和自定义宏可能不受支持。

HTML 和 Markdown 图片渲染需要 Playwright:

```bash
uv run playwright install
```

如果运行环境无法访问 MathJax CDN,请在配置中启用 HTTP/HTTPS 代理,或尽量使用 mathtext 支持的常见数学公式语法
渲染 BrowserContext 强制离线;外部图片、字体、样式和脚本不会加载,应改为内联资源

### Node.js / Rust / Tauri

Expand Down
13 changes: 11 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -757,12 +757,18 @@ Prompt caching 补充:
| 字段 | 默认值 | 说明 | 约束/回退 |
|---|---:|---|---|
| `browser_max_concurrency` | `0` | 渲染浏览器最大同时开启数量 | `<=0` 时启用自动值:Linux=`1`,其它平台=`2` |
| `use_proxy` | `false` | HTML/Markdown 渲染及网页抓取渲染链路是否使用 `[proxy]` 中的代理地址 | |
| `browser_executable_path` | `""` | 可选 Chrome/Chromium 可执行文件路径 | 留空时优先使用 Playwright 自带浏览器;其缺失时自动查找系统 Chrome/Chromium |
| `use_proxy` | `false` | 网页抓取链路是否使用 `[proxy]` 中的代理地址 | HTML/Markdown 浏览器渲染始终离线,不使用代理 |
| `long_image_default_width` | `900` | `layout=long` 未传 `width` 时的最终图片宽度(像素) | 自动钳制到 `320..2048` |
| `long_image_default_padding` | `28` | `layout=long` 未传 `padding` 时的内边距(像素) | 自动钳制到 `0..160`,且保证小于宽度的一半 |

说明:
- 该配置只影响 `render.py` 的 HTML/Markdown 图片渲染链路,不影响 `crawl_webpage` 等独立浏览器实现
- 浏览器路径、并发和长图配置只影响 `render.py` 的 HTML/Markdown 图片渲染链路;`use_proxy` 仍供 `crawl_webpage` 等独立网页抓取实现使用
- 渲染浏览器当前采用单例复用,因此这里限制的是并发页面/上下文数量,而不是浏览器进程数量。
- 显式修改 `browser_executable_path` 后需重启 Bot;仅当 Playwright 报告自带浏览器缺失时才会自动回退到系统浏览器,其他启动错误仍会原样报出。
- 配置变更会对后续新的渲染请求生效;已在执行中的渲染任务不受影响。
- `render.render_html` 和 `render.render_markdown` 默认使用 `layout=default`,视觉效果与旧版一致。显式传 `layout=long` 时,高度按内容自动延伸,使用 CSS 像素截图保证 `width` 对应最终图片宽度,并去掉两侧外部留白。
- `width` 可选范围为 `320..2048`,`padding` 可选范围为 `0..160`;两者只能与 `layout=long` 一起使用。HTML 长图支持内联 CSS、脚本和 `data:` / `blob:` 资源;BrowserContext 强制离线并终止全部网络请求,外部图片、字体、样式和脚本不会加载。`padding=0` 可用于全幅设计。

#### `[render.cache]` HTML 渲染结果缓存

Expand Down Expand Up @@ -1567,6 +1573,9 @@ Prompt caching 补充:

| TOML 路径 | 环境变量 |
|-----------|----------|
| `render.browser_executable_path` | `RENDER_BROWSER_EXECUTABLE_PATH` |
| `render.long_image_default_padding` | `RENDER_LONG_IMAGE_DEFAULT_PADDING` |
| `render.long_image_default_width` | `RENDER_LONG_IMAGE_DEFAULT_WIDTH` |
| `render.use_proxy` | `RENDER_USE_PROXY` |

#### `search`
Expand Down
10 changes: 7 additions & 3 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,17 @@ uv run playwright install

### 3. 安装渲染运行时

网页截图、Markdown 渲染和复杂 LaTeX 公式回退渲染依赖 Playwright 浏览器内核。源码部署时请执行:
网页截图和 Markdown 渲染依赖 Playwright 浏览器内核。源码部署时请执行:

```bash
uv run playwright install
```

`render.render_latex` 会优先使用 Python 依赖中的 `matplotlib` mathtext 在本地渲染常见数学公式,不需要额外安装系统 TeX。mathtext 无法处理的复杂内容会回退到 MathJax + Playwright;如果运行环境无法访问 MathJax CDN,请在配置中启用 HTTP/HTTPS 代理。
`render.render_latex` 使用 Python 依赖中的 `matplotlib.mathtext` 在本地渲染常见数学公式,不需要额外安装系统 TeX,也不访问外部网络。复杂 TeX 环境和自定义宏可能不受支持,此时工具会立即返回明确错误。

`render.render_html` / `render.render_markdown` 的 `layout=long` 与普通渲染复用同一套 Playwright 运行时,无需新增系统依赖。渲染 BrowserContext 强制离线并终止全部网络请求;请将所需样式、脚本和图片内联,图片可使用 `data:` / `blob:` 资源。

如果 Playwright 自带 Chromium 未安装,渲染器会尝试复用系统已安装的 Chrome/Chromium。需要指定其他路径时,设置 `[render].browser_executable_path`;与 Playwright 自带版本相比,系统浏览器的版本兼容性不受 Playwright 保证,因此生产环境仍优先执行 `uv run playwright install`。

### 4. 配置环境

Expand Down Expand Up @@ -144,7 +148,7 @@ uv tool install Undefined-bot
uv tool run --from Undefined-bot playwright install
```

> **渲染依赖提醒**:同源码部署要求一致,你需要在宿主机上预先安装 Playwright 浏览器内核。请参考上文 [3. 安装渲染运行时](#3-安装渲染运行时)。未配置前,网页截图、Markdown 渲染和复杂 LaTeX 公式回退渲染可能会失败
> **渲染依赖提醒**:同源码部署要求一致,你需要在宿主机上预先安装 Playwright 浏览器内核。请参考上文 [3. 安装渲染运行时](#3-安装渲染运行时)。未配置前,HTML 与 Markdown 图片渲染可能会失败;LaTeX 常见公式使用本地 mathtext,不依赖浏览器

安装完成后,在任意目录准备 `config.toml` 并启动(库嵌入场景也可用 `Config.from_mapping()` 代替配置文件,见 [python-api.md](python-api.md)):

Expand Down
14 changes: 11 additions & 3 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,23 @@ Undefined 搭载了基于 ChromaDB 向量数据库的后台认知系统,无需

| 工具 | 说明 |
|---|---|
| `render.render_markdown` | 将 Markdown 文本(含表格、代码块、标题等)渲染为图片发送 |
| `render.render_latex` | LaTeX 数学公式渲染为图片;常见公式本地渲染,复杂内容回退 MathJax + Playwright(详见[部署文档](deployment.md#3-安装渲染运行时)) |
| `render.render_html` | HTML 内容渲染为图片 |
| `render.render_markdown` | 将 Markdown 文本(含表格、代码块、标题等)渲染为普通图片或单张长图 |
| `render.render_latex` | 通过本地 `matplotlib.mathtext` 将常见 LaTeX 数学公式渲染为图片或 PDF;不访问外部网络,复杂 TeX 环境可能不受支持 |
| `render.render_html` | 将完整 HTML、内联 CSS/脚本渲染为普通图片或单张长图;浏览器上下文完全离线,不加载外部资源 |

支持 `embed`(嵌入回复)和 `send`(直接发送)两种图片交付方式。

HTML 和 Markdown 工具都支持显式长图版式:

- `layout=default`:保持原有页面与居中宽版布局,不接受 `width` / `padding`。
- `layout=long`:输出一张高度随内容延伸的 PNG,去除两侧外部留白。`width` 表示最终图片像素宽度,`padding` 表示内边距。
- 未指定宽度和内边距时,默认为 `900px` 和 `28px`,可在 `[render]` 中调整。HTML 全幅设计可显式传 `padding=0`。

**示例:**
> *"请把这段数学公式渲染成图片发给我:$E=mc^2$"*
> *"请把下面这份 Markdown 表格渲染成图片。"*
> *"请把这份 Markdown 渲染成 900px 宽的单张长图,不要两侧留白。"*
> *"把这份完整 HTML 按长图渲染,宽 1080px、内边距 0。"*

---

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "Undefined-bot"
version = "3.8.0"
version = "3.8.1"
description = "QQ bot platform with cognitive memory architecture and multi-agent Skills, via OneBot V11."
readme = "README.md"
authors = [
Expand Down
2 changes: 1 addition & 1 deletion src/Undefined/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from .skills.registry import BaseRegistry as BaseRegistry
from .skills.tools import ToolRegistry as ToolRegistry

__version__: str = "3.8.0"
__version__: str = "3.8.1"

# symbol -> (module_path, attribute_name);首次访问时才 importlib 加载
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
Expand Down
3 changes: 3 additions & 0 deletions src/Undefined/config/config_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,10 @@ class Config:
network_request_timeout: float
network_request_retries: int
render_browser_max_concurrency: int
render_browser_executable_path: str
render_use_proxy: bool
render_long_image_default_width: int
render_long_image_default_padding: int
api_xxapi_base_url: str
api_xingzhige_base_url: str
api_jkyai_base_url: str
Expand Down
3 changes: 3 additions & 0 deletions src/Undefined/config/env_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@
("naga", "use_proxy"): "NAGA_USE_PROXY",
("onebot", "token"): "ONEBOT_TOKEN",
("onebot", "ws_url"): "ONEBOT_WS_URL",
("render", "browser_executable_path"): "RENDER_BROWSER_EXECUTABLE_PATH",
("render", "long_image_default_padding"): "RENDER_LONG_IMAGE_DEFAULT_PADDING",
("render", "long_image_default_width"): "RENDER_LONG_IMAGE_DEFAULT_WIDTH",
("render", "use_proxy"): "RENDER_USE_PROXY",
("search", "use_proxy"): "SEARCH_USE_PROXY",
("search", "firecrawl_search_enabled"): "FIRECRAWL_SEARCH_ENABLED",
Expand Down
3 changes: 3 additions & 0 deletions src/Undefined/config/hot_reload.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"webui_port",
"webui_password",
"webui_autostart_bot",
"render_browser_executable_path",
"api",
"api.enabled",
"api.host",
Expand Down Expand Up @@ -79,6 +80,8 @@
"tool_search_enabled",
"tool_search_always_loaded",
"tool_search_max_results",
"render_long_image_default_width",
"render_long_image_default_padding",
)

_AGENT_INTRO_KEYS: set[str] = {
Expand Down
44 changes: 44 additions & 0 deletions src/Undefined/config/load_sections/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@

logger = logging.getLogger(__name__)

_LONG_IMAGE_MIN_WIDTH: int = 320
_LONG_IMAGE_MAX_WIDTH: int = 2048
_LONG_IMAGE_MAX_PADDING: int = 160


def load_network(
data: dict[str, Any], *, config_path: Optional[Path] = None
Expand Down Expand Up @@ -127,9 +131,46 @@ def load_network(
0,
),
)
render_browser_executable_path = _coerce_str(
_get_value(
data,
("render", "browser_executable_path"),
"RENDER_BROWSER_EXECUTABLE_PATH",
),
"",
)
render_use_proxy = _coerce_bool(
_get_value(data, ("render", "use_proxy"), "RENDER_USE_PROXY"), False
)
render_long_image_default_width = min(
_LONG_IMAGE_MAX_WIDTH,
max(
_LONG_IMAGE_MIN_WIDTH,
_coerce_int(
_get_value(
data,
("render", "long_image_default_width"),
"RENDER_LONG_IMAGE_DEFAULT_WIDTH",
),
900,
),
),
)
render_long_image_default_padding = min(
_LONG_IMAGE_MAX_PADDING,
max(
0,
_coerce_int(
_get_value(
data,
("render", "long_image_default_padding"),
"RENDER_LONG_IMAGE_DEFAULT_PADDING",
),
28,
),
),
(render_long_image_default_width - 1) // 2,
)

api_xxapi_base_url = _normalize_base_url(
_coerce_str(
Expand Down Expand Up @@ -194,7 +235,10 @@ def load_network(
"network_request_timeout": network_request_timeout,
"network_request_retries": network_request_retries,
"render_browser_max_concurrency": render_browser_max_concurrency,
"render_browser_executable_path": render_browser_executable_path,
"render_use_proxy": render_use_proxy,
"render_long_image_default_width": render_long_image_default_width,
"render_long_image_default_padding": render_long_image_default_padding,
"api_xxapi_base_url": api_xxapi_base_url,
"api_xingzhige_base_url": api_xingzhige_base_url,
"api_jkyai_base_url": api_jkyai_base_url,
Expand Down
Loading
Loading