dsh-vision-router 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,8 +10,8 @@
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://awesome-dsh-plugin.com"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="awesome · DSH plugin" /></a>
13
- <a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v1.0.0"><img src="https://img.shields.io/badge/release-v1.0.0-5B4CF0?style=flat-square" alt="Release v1.0.0" /></a>
14
- <a href="tests"><img src="https://img.shields.io/badge/verified-86%20tests-2EA44F?style=flat-square" alt="Verified: 86 tests" /></a>
13
+ <a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v1.1.0"><img src="https://img.shields.io/badge/release-v1.1.0-5B4CF0?style=flat-square" alt="Release v1.1.0" /></a>
14
+ <a href="tests"><img src="https://img.shields.io/badge/verified-94%20tests-2EA44F?style=flat-square" alt="Verified: 94 tests" /></a>
15
15
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2EA44F?style=flat-square" alt="License: MIT" /></a>
16
16
  <a href="package.json"><img src="https://img.shields.io/badge/Node.js-%3E%3D22-339933?style=flat-square&amp;logo=nodedotjs&amp;logoColor=white" alt="Node.js >=22" /></a>
17
17
  <img src="https://img.shields.io/badge/runtime-no%20Python-8A2BE2?style=flat-square" alt="No Python" />
@@ -20,6 +20,11 @@
20
20
 
21
21
  <p align="center">English · <a href="README.zh.md">中文</a></p>
22
22
 
23
+ > [!WARNING]
24
+ > 📌 **Announcement (v1.1.0)**
25
+ >
26
+ > Now supports **Extra vision wrappers**: any custom text route (e.g. opencode) sends images out of the box — plus long-screenshot OCR, a "Test connection" button and artifact preview cards in the settings panel. Stealth mode is off by default and the official DeepSeek row is no longer taken over implicitly.
27
+
23
28
  <p align="center">
24
29
  <img src="assets/vision-demo.gif" width="640" alt="Demo: paste an image, the agent locates the send button with vision_ground / vision_crop / vision_pixel_diff and answers with coordinates" />
25
30
  </p>
@@ -28,7 +33,7 @@
28
33
 
29
34
  Most DSH vision plugins bridge images to DeepSeek as *text descriptions* — lossy, one-shot, and blind to pixels. This plugin keeps the **original pixels on the vision model's side** and DeepSeek on the reasoning side, and makes looking at an image an **ordinary tool call**:
30
35
 
31
- - **One command install.** The package ships its own composition patch (`dsh.bundle.patch`): `dsh plugin add` wires the row, the admission wrapper, the stealth takeover and the attachment limits automatically — zero manual file edits.
36
+ - **One command install.** The package ships its own composition patch (`dsh.bundle.patch`): `dsh plugin add` wires the row, the admission wrapper and the attachment limits automatically — zero manual file edits. Taking over the official DeepSeek route is an optional setting (stealth mode, off by default).
32
37
  - **Free by default.** The vision chain starts with a built-in OVHcloud anonymous endpoint (`Qwen2.5-VL-72B-Instruct`, no account, no key, 2 req/min per IP). Paid chains (OpenRouter, Pi-AI providers, direct OpenAI-compatible endpoints) are optional upgrades.
33
38
  - **No Python.** The whole pipeline — downscale, grounding, crop, pixel diff, palette, OCR, SVG trace, cutout, HTML screenshot — runs on sharp / potrace / tesseract / system Chrome.
34
39
  - **Continuous multi-step image work.** An image turn is a text turn that calls tools: `vision_ground` → `vision_crop` → `vision_describe` → `vision_pixel_diff` → fix → screenshot again. The agent keeps iterating until the work is done.
@@ -44,7 +49,7 @@ The closest alternative is [@anionex/dsh-vision-toolkit](https://github.com/Anio
44
49
  | Image Q&A out of the box | ✅ Built-in free chain (anonymous OVHcloud endpoint) — no account, no key | Requires your own vision API key (local pixel tools work without one) |
45
50
  | Runtime | ✅ Node only — no Python | Python 3.11+ managed runtime |
46
51
  | Getting an image in | ✅ Paste it — the turn auto-routes to the vision chain and auto-mounts the tools | Workspace path + `/vision-tools` command, then explicit tool calls |
47
- | Turn routing | ✅ Image turns switch to vision, text turns switch back to DeepSeek — stealth takeover, the model picker looks stock | Tool-driven; no whole-turn auto-routing |
52
+ | Turn routing | ✅ Image turns switch to vision, text turns switch back to DeepSeek — optional stealth takeover keeps the model picker looking stock | Tool-driven; no whole-turn auto-routing |
48
53
  | Profiles | Web | Web + Headless |
49
54
  | Playbooks | The pixel loop: ground → crop → diff → fix → screenshot again | Richer case library (long-screenshot OCR, UI restoration, GUI automation) |
50
55
  | Tests | 86 | 162 |
@@ -55,13 +60,14 @@ Both are MIT-licensed and one command away. Pick this plugin when you want image
55
60
  ## Quick start
56
61
 
57
62
  ```sh
58
- dsh plugin --profile web add github:ysr666/dsh-vision-router
63
+ dsh plugin --profile web add dsh-vision-router
59
64
  ```
60
65
 
61
66
  Restart `dsh web` — done. Zero configuration:
62
67
 
63
- - the plugin's bundle patch mounts the row, takes over the official DeepSeek route (stealth mode — the model picker looks exactly like stock), and relaxes attachment limits to 20 MB / 100 MP;
68
+ - the plugin's bundle patch mounts the row, adds the admission wrapper and relaxes attachment limits to 20 MB / 100 MP — pure-additive, it never touches the core rows; whether the official DeepSeek route is taken over is decided by the optional stealth setting (off by default);
64
69
  - the default vision chain is the built-in free endpoint;
70
+ - custom/third-party routes (e.g. opencode) gain image input through **Extra vision wrappers**;
65
71
  - every setting is editable live in **Settings → Plugins → Plugin config → 视觉路由(自动识图)**.
66
72
 
67
73
  Then just paste an image into a conversation. The agent mounts the vision tools automatically and looks at it through `vision_describe` (and friends) — multi-step if needed.
@@ -120,6 +126,7 @@ All ten deep tools mount automatically on image turns (`autoActivateOnImage`); t
120
126
  | `vision_trace` | SVG vectorization (potrace posterization; icons/logos) | SVG |
121
127
  | `vision_extract_foreground` | Cutout via border flood fill (uniform backgrounds) | transparent PNG |
122
128
  | `vision_html_screenshot` | Screenshot a local HTML file (headless system Chrome) | PNG |
129
+ | `vision_long_screenshot_ocr` | Long-screenshot transcription: overlapping chunks, tesseract first / vision model fallback, stitched Markdown | chunk PNGs + Markdown + manifest |
123
130
 
124
131
  Formats are sniffed from magic bytes, so extensionless content-addressed attachment files work everywhere (no `.png` renaming needed).
125
132
 
@@ -136,6 +143,7 @@ vision_colors image="ref.png" top=8
136
143
  vision_trace image="icon.png" steps=4
137
144
  vision_extract_foreground image="logo.png"
138
145
  vision_html_screenshot source="page.html" width=1200 height=720
146
+ vision_long_screenshot_ocr image="chat-log.png" chunkHeight=1200 overlap=120
139
147
  ```
140
148
 
141
149
  ## Provider fallback chain
@@ -152,25 +160,35 @@ Failures are classified (region / tos / quota / rate-limit / context / network)
152
160
 
153
161
  ## Stealth mode
154
162
 
155
- A default install takes over the official `deepseek-official` route: the model picker looks exactly like stock (same DeepSeek group, same model names), but each entry is the auto-vision wrapper that declares image input and delegates text turns to a rebuilt native DeepSeek adapter (same `llm-deepseek` settings section and credentials). Old sessions keep working through the hidden `deepseek-vision` alias.
163
+ Stealth mode is **off by default** (explicit opt-in since issue #34): with it off, the official `deepseek-official` route stays untouched and image turns go through the visible "DeepSeek + 自动识图" wrapper entry in the picker.
156
164
 
157
- To keep the stock row instead, override it in your profile patch layer (`~/.dsh/profiles/<profile>/cordis.patch.yml`):
165
+ With stealth on, the plugin takes over the official `deepseek-official` route: the model picker looks exactly like stock (same DeepSeek group, same model names), but each entry is the auto-vision wrapper that declares image input and delegates text turns to a rebuilt native DeepSeek adapter (same `llm-deepseek` settings section and credentials). Old sessions keep working through the hidden `deepseek-vision` alias. The takeover requires the stock row to be absent — disable it in your profile patch layer (`~/.dsh/profiles/<profile>/cordis.patch.yml`):
158
166
 
159
167
  ```yaml
160
168
  - id: llm-deepseek
161
169
  name: '@deepseek-ai/dsh-llm-deepseek'
170
+ disabled: true
162
171
  ```
163
172
 
164
- With the stock row present, the plugin falls back to the visible "DeepSeek + 自动识图" wrapper entry — pick it in the model picker for image turns. Recovery from a broken install is the same one-line override.
173
+ With the stock row present, the plugin falls back to the visible wrapper entry. Conversely, with stealth off but the stock row still disabled, the plugin performs a keep-alive takeover so the DeepSeek models don't vanish (the settings card explains this); to restore the fully official route, flip the `disabled` above back to `false` and restart.
174
+
175
+ > Stealth mode **only affects the official DeepSeek route**. Custom/third-party routes like opencode are unrelated — use **Extra vision wrappers** below to give them image input.
176
+
177
+ ## Extra vision wrappers
178
+
179
+ `wrappedProviders` registers an auto-vision twin for any third-party/custom text route: pick the twin in the model selector and send images; text turns delegate to the original route unchanged. The typical use case is a custom interface such as opencode — it only declares text input, and one wrapper row makes it image-ready. In the settings card, configure it with two dropdowns (provider + model); leaving the model empty wraps every model of that route.
165
180
 
166
181
  ## Web settings
167
182
 
168
183
  The Web profile registers a **视觉路由(自动识图)** card under **Settings → Plugins → Plugin config**, styled like the built-in cards. It live-edits:
169
184
 
170
- - switches: whole-turn legacy routing, vision tools, image-block rewriting, stealth;
185
+ - switches: whole-turn legacy routing, vision tools, image-block rewriting, stealth (official DeepSeek route only);
186
+ - **extra vision wrappers**: provider + model dropdowns that register image-capable twin entries for custom routes such as opencode;
171
187
  - vision request timeout, wrapper/chain route names;
172
188
  - the **vision chain** (one `provider/model` per line, top-down fallback) and the text model;
173
- - every field shows an "overridden" badge with a one-click reset to the composition default, plus discard/save.
189
+ - every field shows an "overridden" badge with a one-click reset to the composition default, plus discard/save;
190
+ - a **Test connection** button probes the first vision provider and reports latency inline;
191
+ - artifact-producing tools render dedicated call cards with result facts and open-file buttons.
174
192
 
175
193
  <p align="center">
176
194
  <img src="assets/vision-settings.png" width="72%" alt="The vision-router card in Settings > Plugins > Plugin config." />
@@ -186,12 +204,13 @@ Everything is optional; defaults work out of the box. Edit via the Web card or a
186
204
  |---|---|---|
187
205
  | `provider` / `model` | `vision-http` / `ovh/Qwen2.5-VL-72B-Instruct` | shorthand chain (adapter-backed provider + model) |
188
206
  | `fallbacks` | `[]` | backup models for the shorthand provider |
189
- | `providers` | `[]` | multi-provider chain `{ provider, model, fallbacks[] }`, tried in order; wins over the shorthand |
207
+ | `providers` | built-in free `vision-http` pair | multi-provider chain `{ provider, model, fallbacks[] }`, tried in order; wins over the shorthand. The first row ships as the built-in free model |
190
208
  | `httpProviders` | built-in OVH entry | direct OpenAI-compatible endpoints `{ name, baseURL, model, apiKeyEnv, maxTokens }` |
209
+ | `wrappedProviders` | `[{ provider: 'deepseek-official', models: [] }]` | extra text routes to wrap as image-capable twins: `{ provider, models[] }` — registers an auto-vision twin for any custom/third-party route (e.g. opencode); in the card, provider + model dropdowns, empty model = wrap all. The pre-filled deepseek-official row marks the built-in wrapper and is a no-op; changes apply live |
191
210
  | `routing` | `false` | legacy whole-turn chain routing (one-shot answer). `false` = tools-first flow (recommended) |
192
211
  | `reverseRouting` | `true` | with `routing: true`, route text turns back to `textProvider` |
193
212
  | `wrapperRoute` / `chainRoute` | `deepseek-vision` / `vision-chain` | admission wrapper route name / fallback chain route name (empty disables) |
194
- | `stealth` | `true` | take over the official `deepseek-official` route |
213
+ | `stealth` | `false` | take over the official `deepseek-official` route (official row only; custom routes use `wrappedProviders`) |
195
214
  | `textProvider` | `deepseek-official` / `deepseek-v4-pro` | the model that reasons (your daily model) |
196
215
  | `tool` / `progressiveTools` / `autoActivateOnImage` | `true` ×3 | vision tools on / progressive mounting / auto-mount on image turns |
197
216
  | `rewriteImages` | `true` | rewrite image blocks in the model input (cached description or tool-hint marker); the UI log keeps images |
@@ -214,7 +233,7 @@ Everything is optional; defaults work out of the box. Edit via the Web card or a
214
233
  ### Install
215
234
 
216
235
  ```sh
217
- dsh plugin --profile web add github:ysr666/dsh-vision-router
236
+ dsh plugin --profile web add dsh-vision-router
218
237
  dsh --profile web --dump-config | grep vision-router # one row, mounted by the bundle patch
219
238
  ```
220
239
 
package/README.zh.md CHANGED
@@ -10,8 +10,8 @@
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://awesome-dsh-plugin.com"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="awesome · DSH plugin" /></a>
13
- <a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v1.0.0"><img src="https://img.shields.io/badge/release-v1.0.0-5B4CF0?style=flat-square" alt="Release v1.0.0" /></a>
14
- <a href="tests"><img src="https://img.shields.io/badge/verified-86%20tests-2EA44F?style=flat-square" alt="Verified: 86 tests" /></a>
13
+ <a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v1.1.0"><img src="https://img.shields.io/badge/release-v1.1.0-5B4CF0?style=flat-square" alt="Release v1.1.0" /></a>
14
+ <a href="tests"><img src="https://img.shields.io/badge/verified-94%20tests-2EA44F?style=flat-square" alt="Verified: 94 tests" /></a>
15
15
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2EA44F?style=flat-square" alt="License: MIT" /></a>
16
16
  <a href="package.json"><img src="https://img.shields.io/badge/Node.js-%3E%3D22-339933?style=flat-square&amp;logo=nodedotjs&amp;logoColor=white" alt="Node.js >=22" /></a>
17
17
  <img src="https://img.shields.io/badge/runtime-no%20Python-8A2BE2?style=flat-square" alt="No Python" />
@@ -20,6 +20,11 @@
20
20
 
21
21
  <p align="center"><a href="README.md">English</a> · 中文</p>
22
22
 
23
+ > [!WARNING]
24
+ > 📌 **公告(v1.1.0)**
25
+ >
26
+ > 现已支持「**额外识图包装**」:opencode 等任意自定义文本路由开箱即可发图;另含长截图 OCR、设置卡片「测试连接」与视觉产物预览卡。隐身模式默认关闭,官方 DeepSeek 行不再被默认接管。
27
+
23
28
  <p align="center">
24
29
  <img src="assets/vision-demo.gif" width="640" alt="演示:粘贴图片,Agent 用 vision_ground / vision_crop / vision_pixel_diff 定位发送按钮并给出坐标" />
25
30
  </p>
@@ -28,7 +33,7 @@
28
33
 
29
34
  大多数 DSH 视觉插件把图片“翻译”成一段文字描述再喂给 DeepSeek——有损、一次性、看不见像素。本插件把**原图像素留在视觉模型侧**、把推理留在 DeepSeek 侧,并把“看图”变成一次**普通的工具调用**:
30
35
 
31
- - **一条命令安装。** 包自带组合补丁(`dsh.bundle.patch`):`dsh plugin add` 自动完成插件行挂载、准入包装、隐身接管与附件限制放宽——不用手改任何文件。
36
+ - **一条命令安装。** 包自带组合补丁(`dsh.bundle.patch`):`dsh plugin add` 自动完成插件行挂载、准入包装与附件限制放宽——不用手改任何文件。是否接管官方 DeepSeek 路由由「隐身模式」开关决定(默认关)。
32
37
  - **默认免费。** 视觉链内置 OVHcloud 匿名端点(`Qwen2.5-VL-72B-Instruct`,免注册、免 Key,每 IP 2 次/分钟)。付费链路(OpenRouter、Pi-AI 供应商、任意 OpenAI 兼容直连端点)是可选升级。
33
38
  - **无 Python。** 整条管线——缩放、定位、裁剪、像素对比、取色、OCR、SVG 矢量化、抠图、HTML 截图——全部基于 sharp / potrace / tesseract / 系统 Chrome。
34
39
  - **可连续多步看图。** 图片轮 = 调用工具的文本轮:`vision_ground` → `vision_crop` → `vision_describe` → `vision_pixel_diff` → 修复 → 再截图,Agent 可以一直迭代到任务完成。
@@ -44,7 +49,7 @@
44
49
  | 开箱图片问答 | ✅ 内置免费视觉链(OVHcloud 匿名端点),免注册免 Key | 远程工具需自备视觉 API Key(本地像素工具免 Key) |
45
50
  | 运行时 | ✅ 纯 Node,无需 Python | 需要 Python 3.11+ 受管运行时 |
46
51
  | 图片怎么进来 | ✅ 直接粘贴——轮次自动切视觉链并自动挂载工具 | 工作区路径 + `/vision-tools` 命令,再显式调用工具 |
47
- | 轮次路由 | ✅ 图片轮切视觉、文本轮切回 DeepSeek——隐身接管,模型选择器与官方一致 | 工具驱动,无整轮自动路由 |
52
+ | 轮次路由 | ✅ 图片轮切视觉、文本轮切回 DeepSeek——可选隐身接管,模型选择器与官方一致 | 工具驱动,无整轮自动路由 |
48
53
  | 支持 profile | Web | Web + Headless |
49
54
  | 玩法库 | 像素循环:定位 → 裁剪 → 对比 → 修复 → 再截图 | 更丰富的案例库(长截图 OCR、UI 还原、GUI 自动化) |
50
55
  | 测试 | 86 | 162 |
@@ -55,13 +60,14 @@
55
60
  ## 快速开始
56
61
 
57
62
  ```sh
58
- dsh plugin --profile web add github:ysr666/dsh-vision-router
63
+ dsh plugin --profile web add dsh-vision-router
59
64
  ```
60
65
 
61
66
  重启 `dsh web`——完成,零配置:
62
67
 
63
- - 插件的 bundle 补丁自动挂载插件行、接管官方 DeepSeek 路由(隐身模式——模型选择器看起来和原版一模一样)、放宽附件限制到 20MB / 1 亿像素;
68
+ - 插件的 bundle 补丁自动挂载插件行、挂上准入包装并放宽附件限制到 20MB / 1 亿像素——纯增量、不碰核心行;是否接管官方 DeepSeek 路由由「隐身模式」开关决定(默认关);
64
69
  - 默认视觉链就是内置免费端点;
70
+ - opencode 等自定义/第三方路由用「额外识图包装」获得发图能力;
65
71
  - 全部配置可在 **设置 → 插件 → 插件配置 → 视觉路由(自动识图)** 实时修改。
66
72
 
67
73
  然后直接往对话里贴一张图。Agent 自动挂载视觉工具,通过 `vision_describe`(以及其余 8 个工具)看图,需要时连续多步。
@@ -120,6 +126,7 @@ Agent 仅根据参考图复刻 UI,再用 `vision_pixel_diff` 验证最终结
120
126
  | `vision_trace` | SVG 矢量化(potrace 分色;图标/logo) | SVG |
121
127
  | `vision_extract_foreground` | 边界洪泛抠图(纯色背景) | 透明 PNG |
122
128
  | `vision_html_screenshot` | 给本地 HTML 文件截图(无头系统 Chrome) | PNG |
129
+ | `vision_long_screenshot_ocr` | 长截图转写:重叠分片,tesseract 优先 / 视觉模型回退,按序拼接 Markdown | 分片 PNG + Markdown + manifest |
123
130
 
124
131
  图片格式按**魔数识别**,无扩展名的内容寻址附件文件也能直接用(不用再复制成 `.png`)。
125
132
 
@@ -136,6 +143,7 @@ vision_colors image="ref.png" top=8
136
143
  vision_trace image="icon.png" steps=4
137
144
  vision_extract_foreground image="logo.png"
138
145
  vision_html_screenshot source="page.html" width=1200 height=720
146
+ vision_long_screenshot_ocr image="chat-log.png" chunkHeight=1200 overlap=120
139
147
  ```
140
148
 
141
149
  ## 供应商降级链
@@ -152,25 +160,35 @@ vision_html_screenshot source="page.html" width=1200 height=720
152
160
 
153
161
  ## 隐身模式
154
162
 
155
- 默认安装即接管官方 `deepseek-official` 路由:模型选择器看起来和原版完全一样(同一个 DeepSeek 组、同样的模型名),但每个条目背后都是声明了图片输入的自动识图包装;文字轮交给插件重建的原生 DeepSeek 适配器(读取同一个 `llm-deepseek` 设置段与凭据)。老会话通过隐藏的 `deepseek-vision` 别名继续工作。
163
+ 隐身模式默认**关闭**(issue #34 起显式 opt-in):关闭时官方 `deepseek-official` 路由原样保留,发图走选择器里可见的「DeepSeek + 自动识图」包装入口。
156
164
 
157
- 想保留官方行,在你的 profile 补丁层(`~/.dsh/profiles/<profile>/cordis.patch.yml`)覆写即可:
165
+ 开启隐身模式后,插件接管官方 `deepseek-official` 路由:模型选择器看起来和原版完全一样(同一个 DeepSeek 组、同样的模型名),但每个条目背后都是声明了图片输入的自动识图包装;文字轮交给插件重建的原生 DeepSeek 适配器(读取同一个 `llm-deepseek` 设置段与凭据)。老会话通过隐藏的 `deepseek-vision` 别名继续工作。接管的前提是官方行不在场——在你的 profile 补丁层(`~/.dsh/profiles/<profile>/cordis.patch.yml`)禁用即可:
158
166
 
159
167
  ```yaml
160
168
  - id: llm-deepseek
161
169
  name: '@deepseek-ai/dsh-llm-deepseek'
170
+ disabled: true
162
171
  ```
163
172
 
164
- 官方行在场时,插件回退为选择器里可见的「DeepSeek + 自动识图」包装入口——发图前选它即可。安装出问题时同样用这一行恢复。
173
+ 官方行在场时,插件自动回退为可见包装入口。反过来,隐身模式关闭但官方行仍被禁用时,插件会做 keep-alive 兜底接管,保住 DeepSeek 模型(设置卡片会给出提示);想完全恢复官方原生行,把上面的 `disabled` 改回 `false` 再重启即可。
174
+
175
+ > 隐身模式**只作用于官方 DeepSeek 路由**。opencode 等自定义/第三方文本路由与隐身模式无关——用「额外识图包装」让它们支持发图。
176
+
177
+ ## 额外识图包装
178
+
179
+ `wrappedProviders` 给任意第三方/自定义文本路由注册「自动识图」孪生条目:模型选择器里选中它就能发图,文字轮原样交给原路由处理。典型用法是 opencode 等接入的自定义接口——它们默认只声明文本输入,加一行包装即可直接发图。设置卡片里用两个下拉(provider + 模型)配置;模型留空 = 包装该路由的全部模型,同一 provider 要包装多个模型就添加多行。
165
180
 
166
181
  ## Web 设置
167
182
 
168
183
  Web 配置页在 **设置 → 插件 → 插件配置** 下注册「视觉路由(自动识图)」卡片,样式与内置卡片一致,可实时修改:
169
184
 
170
- - 开关:整轮自动路由(旧模式)、识图工具、图片块改写、隐身模式;
185
+ - 开关:整轮自动路由(旧模式)、识图工具、图片块改写、隐身模式(仅官方 DeepSeek 路由);
186
+ - **额外识图包装**:provider + 模型双下拉,给 opencode 等自定义路由注册可发图的孪生条目;
171
187
  - 视觉请求超时、包装/链路由名;
172
188
  - **视觉模型链**(每行一个 `provider/model`,自上而下降级)与文本模型;
173
- - 每个字段都有「已覆盖」徽标与一键恢复组合默认,以及放弃/保存。
189
+ - 每个字段都有「已覆盖」徽标与一键恢复组合默认,以及放弃/保存;
190
+ - 「测试连接」按钮探测第一个视觉提供方并内联显示延迟/失败原因;
191
+ - 产出制品的工具在对话里渲染专用调用卡(关键字段 + 打开文件按钮)。
174
192
 
175
193
  <p align="center">
176
194
  <img src="assets/vision-settings.png" width="72%" alt="设置 → 插件 → 插件配置 里的视觉路由卡片。" />
@@ -186,12 +204,13 @@ Web 配置页在 **设置 → 插件 → 插件配置** 下注册「视觉路由
186
204
  |---|---|---|
187
205
  | `provider` / `model` | `vision-http` / `ovh/Qwen2.5-VL-72B-Instruct` | 简写链路(有适配器的供应商 + 模型) |
188
206
  | `fallbacks` | `[]` | 简写供应商的备用模型 |
189
- | `providers` | `[]` | 多供应商链路 `{ provider, model, fallbacks[] }`,按序尝试;优先于简写形式 |
207
+ | `providers` | 内置免费 `vision-http` 条目 | 多供应商链路 `{ provider, model, fallbacks[] }`,按序尝试;优先于简写形式。第一行开箱预置内置免费模型 |
190
208
  | `httpProviders` | 内置 OVH 条目 | OpenAI 兼容直连端点 `{ name, baseURL, model, apiKeyEnv, maxTokens }` |
209
+ | `wrappedProviders` | `[{ provider: 'deepseek-official', models: [] }]` | 额外识图包装:`{ provider, models[] }`,给 opencode 等任意第三方/自定义文本路由注册可发图的孪生条目(卡片里 provider + 模型双下拉;模型留空 = 包装全部)。预置的 deepseek-official 条目标记内置包装、无副作用;改动即时生效 |
191
210
  | `routing` | `false` | 旧版整轮链路由(一次性整轮回答)。`false` = 工具优先流程(推荐) |
192
211
  | `reverseRouting` | `true` | 开启 `routing` 时,文字轮路由回 `textProvider` |
193
212
  | `wrapperRoute` / `chainRoute` | `deepseek-vision` / `vision-chain` | 准入包装路由名 / 降级链路由名(置空关闭) |
194
- | `stealth` | `true` | 接管官方 `deepseek-official` 路由 |
213
+ | `stealth` | `false` | 接管官方 `deepseek-official` 路由(仅官方行;自定义路由用 `wrappedProviders`) |
195
214
  | `textProvider` | `deepseek-official` / `deepseek-v4-pro` | 负责思考的模型(你的日常模型) |
196
215
  | `tool` / `progressiveTools` / `autoActivateOnImage` | `true` ×3 | 视觉工具开关 / 渐进式挂载 / 图片轮自动挂载 |
197
216
  | `rewriteImages` | `true` | 模型输入层改写图片块(缓存描述或工具提示标记);界面日志保留图片 |
@@ -214,7 +233,7 @@ Web 配置页在 **设置 → 插件 → 插件配置** 下注册「视觉路由
214
233
  ### 安装
215
234
 
216
235
  ```sh
217
- dsh plugin --profile web add github:ysr666/dsh-vision-router
236
+ dsh plugin --profile web add dsh-vision-router
218
237
  dsh --profile web --dump-config | grep vision-router # 一行,由 bundle 补丁挂载
219
238
  ```
220
239
 
@@ -1,41 +1,209 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" width="1600" height="640" viewBox="0 0 1600 640">
2
2
  <defs>
3
- <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#070914"/><stop offset="0.52" stop-color="#0A0D1B"/><stop offset="1" stop-color="#080A13"/></linearGradient>
4
- <radialGradient id="violetGlow" cx="73%" cy="49%" r="42%"><stop offset="0" stop-color="#715CFF" stop-opacity=".23"/><stop offset=".45" stop-color="#604CFF" stop-opacity=".08"/><stop offset="1" stop-color="#604CFF" stop-opacity="0"/></radialGradient>
5
- <radialGradient id="cyanGlow" cx="82%" cy="46%" r="28%"><stop offset="0" stop-color="#46D8FF" stop-opacity=".15"/><stop offset="1" stop-color="#46D8FF" stop-opacity="0"/></radialGradient>
6
- <linearGradient id="iris" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#8D7BFF"/><stop offset=".5" stop-color="#6256FF"/><stop offset="1" stop-color="#36C8FF"/></linearGradient>
7
- <linearGradient id="route" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#695BFF"/><stop offset=".55" stop-color="#7B6DFF"/><stop offset="1" stop-color="#42D8FF"/></linearGradient>
8
- <linearGradient id="chip" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#171B2C" stop-opacity=".95"/><stop offset="1" stop-color="#0D1020" stop-opacity=".95"/></linearGradient>
9
- <filter id="glow"><feGaussianBlur stdDeviation="5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
10
- <filter id="softGlow"><feGaussianBlur stdDeviation="12" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
11
- <pattern id="dots" width="34" height="34" patternUnits="userSpaceOnUse"><circle cx="1.4" cy="1.4" r="1.35" fill="#65708A" opacity=".12"/></pattern>
3
+ <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#070914"/>
5
+ <stop offset="0.52" stop-color="#0A0D1B"/>
6
+ <stop offset="1" stop-color="#080A13"/>
7
+ </linearGradient>
8
+
9
+ <radialGradient id="violetGlow" cx="73%" cy="49%" r="42%">
10
+ <stop offset="0" stop-color="#715CFF" stop-opacity=".23"/>
11
+ <stop offset=".45" stop-color="#604CFF" stop-opacity=".08"/>
12
+ <stop offset="1" stop-color="#604CFF" stop-opacity="0"/>
13
+ </radialGradient>
14
+
15
+ <radialGradient id="cyanGlow" cx="82%" cy="46%" r="28%">
16
+ <stop offset="0" stop-color="#46D8FF" stop-opacity=".15"/>
17
+ <stop offset="1" stop-color="#46D8FF" stop-opacity="0"/>
18
+ </radialGradient>
19
+
20
+ <linearGradient id="iris" x1="0" y1="0" x2="1" y2="1">
21
+ <stop offset="0" stop-color="#8D7BFF"/>
22
+ <stop offset=".5" stop-color="#6256FF"/>
23
+ <stop offset="1" stop-color="#36C8FF"/>
24
+ </linearGradient>
25
+
26
+ <linearGradient id="route" x1="0" y1="0" x2="1" y2="0">
27
+ <stop offset="0" stop-color="#695BFF"/>
28
+ <stop offset=".55" stop-color="#7B6DFF"/>
29
+ <stop offset="1" stop-color="#42D8FF"/>
30
+ </linearGradient>
31
+
32
+ <linearGradient id="chip" x1="0" y1="0" x2="0" y2="1">
33
+ <stop offset="0" stop-color="#171B2C" stop-opacity=".95"/>
34
+ <stop offset="1" stop-color="#0D1020" stop-opacity=".95"/>
35
+ </linearGradient>
36
+
37
+ <filter id="blur40"><feGaussianBlur stdDeviation="40"/></filter>
38
+ <filter id="glow">
39
+ <feGaussianBlur stdDeviation="5" result="b"/>
40
+ <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
41
+ </filter>
42
+ <filter id="softGlow">
43
+ <feGaussianBlur stdDeviation="12" result="b"/>
44
+ <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
45
+ </filter>
46
+
47
+ <pattern id="dots" width="34" height="34" patternUnits="userSpaceOnUse">
48
+ <circle cx="1.4" cy="1.4" r="1.35" fill="#65708A" opacity=".12"/>
49
+ </pattern>
12
50
  </defs>
13
- <rect width="1600" height="640" rx="32" fill="url(#bg)"/><rect width="1600" height="640" rx="32" fill="url(#dots)" opacity=".45"/><rect width="1600" height="640" rx="32" fill="url(#violetGlow)"/><rect width="1600" height="640" rx="32" fill="url(#cyanGlow)"/>
14
- <path d="M790 70 C980 15 1240 42 1515 180" fill="none" stroke="#8D7BFF" stroke-opacity=".08"/><path d="M780 575 C1020 620 1300 585 1510 425" fill="none" stroke="#47CFFF" stroke-opacity=".08"/><path d="M925 36 C1120 160 1380 132 1565 250" fill="none" stroke="#FFFFFF" stroke-opacity=".035"/>
51
+
52
+ <!-- Background -->
53
+ <rect width="1600" height="640" rx="32" fill="url(#bg)"/>
54
+ <rect width="1600" height="640" rx="32" fill="url(#dots)" opacity=".45"/>
55
+ <rect width="1600" height="640" rx="32" fill="url(#violetGlow)"/>
56
+ <rect width="1600" height="640" rx="32" fill="url(#cyanGlow)"/>
57
+
58
+ <!-- ambient curves -->
59
+ <path d="M790 70 C980 15 1240 42 1515 180" fill="none" stroke="#8D7BFF" stroke-opacity=".08" stroke-width="1"/>
60
+ <path d="M780 575 C1020 620 1300 585 1510 425" fill="none" stroke="#47CFFF" stroke-opacity=".08" stroke-width="1"/>
61
+ <path d="M925 36 C1120 160 1380 132 1565 250" fill="none" stroke="#FFFFFF" stroke-opacity=".035" stroke-width="1"/>
62
+
63
+ <!-- LEFT COPY -->
15
64
  <g transform="translate(92,86)">
16
- <g><rect x="0" y="0" width="224" height="34" rx="17" fill="#111528" stroke="#7568FF" stroke-opacity=".42"/><circle cx="19" cy="17" r="4" fill="#6A5BFF" filter="url(#glow)"/><text x="33" y="22" fill="#BFB8FF" font-family="Inter, Arial, sans-serif" font-size="14" font-weight="700" letter-spacing="1.7">DEEPSEEK HARNESS</text></g>
65
+ <g>
66
+ <rect x="0" y="0" width="224" height="34" rx="17" fill="#111528" stroke="#7568FF" stroke-opacity=".42"/>
67
+ <circle cx="19" cy="17" r="4" fill="#6A5BFF" filter="url(#glow)"/>
68
+ <text x="33" y="22" fill="#BFB8FF" font-family="Inter, Arial, sans-serif" font-size="14" font-weight="700" letter-spacing="1.7">DEEPSEEK HARNESS</text>
69
+ </g>
70
+
17
71
  <text x="0" y="101" fill="#F7F8FF" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="68" font-weight="800" letter-spacing="-2.2">DSH 视觉</text>
18
72
  <text x="0" y="176" fill="url(#iris)" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="70" font-weight="800" letter-spacing="-2.2">路由器</text>
19
- <text x="2" y="228" fill="#C7CCDB" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="24">给 DeepSeek 一双眼,而不是换一个大脑。</text>
20
- <text x="2" y="277" fill="#7F889F" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="16.5">内置免费视觉链 · 自动故障切换 · 像素级工具</text>
21
- <text x="2" y="306" fill="#7F889F" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="16.5">一条命令安装 · 无需注册 · 无需 API Key · 无需 Python</text>
22
- <g transform="translate(0,355)" fill="#CFD5E6" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="14" font-weight="650">
23
- <g><rect width="132" height="39" rx="12" fill="#111526" stroke="#252B42"/><circle cx="18" cy="19.5" r="4" fill="#50D890"/><text x="31" y="25">免费链路</text></g>
24
- <g transform="translate(145,0)"><rect width="132" height="39" rx="12" fill="#111526" stroke="#252B42"/><circle cx="18" cy="19.5" r="4" fill="#7B6DFF"/><text x="31" y="25">自动路由</text></g>
25
- <g transform="translate(290,0)"><rect width="132" height="39" rx="12" fill="#111526" stroke="#252B42"/><circle cx="18" cy="19.5" r="4" fill="#4CD5FF"/><text x="31" y="25">像素循环</text></g>
26
- </g>
27
- <g transform="translate(2,438)"><path d="M0 0 H422" stroke="#252A3C"/><text x="0" y="38" fill="#666F85" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="14">github.com/ysr666/dsh-vision-router</text></g>
73
+
74
+ <text x="2" y="230" fill="#F2F5FF" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="31" font-weight="700">粘贴图片,直接就能用。</text>
75
+
76
+ <text x="2" y="270" fill="#C7CCDB" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="19" font-weight="500">DeepSeek 继续思考,Vision Router 负责看。</text>
77
+
78
+ <text x="2" y="304" fill="#7F889F" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="16.8" font-weight="500">内置免费视觉 · 无需 API Key · 无需 Python · 自动路由</text>
79
+
80
+ <!-- feature chips -->
81
+ <g transform="translate(0,355)">
82
+ <g>
83
+ <rect x="0" y="0" width="116" height="39" rx="12" fill="#111526" stroke="#252B42"/>
84
+ <circle cx="18" cy="19.5" r="4" fill="#50D890"/>
85
+ <text x="31" y="25" fill="#CFD5E6" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="14" font-weight="650">无需 Key</text>
86
+ </g>
87
+ <g transform="translate(129,0)">
88
+ <rect x="0" y="0" width="132" height="39" rx="12" fill="#111526" stroke="#252B42"/>
89
+ <circle cx="18" cy="19.5" r="4" fill="#7B6DFF"/>
90
+ <text x="31" y="25" fill="#CFD5E6" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="14" font-weight="650">自动路由</text>
91
+ </g>
92
+ <g transform="translate(274,0)">
93
+ <rect x="0" y="0" width="132" height="39" rx="12" fill="#111526" stroke="#252B42"/>
94
+ <circle cx="18" cy="19.5" r="4" fill="#4CD5FF"/>
95
+ <text x="31" y="25" fill="#CFD5E6" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="14" font-weight="650">像素循环</text>
96
+ </g>
97
+ </g>
98
+
99
+ <g transform="translate(2,438)">
100
+ <path d="M0 0 H422" stroke="#252A3C"/>
101
+ <text x="0" y="38" fill="#666F85" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="14">github.com/ysr666/dsh-vision-router</text>
102
+ </g>
28
103
  </g>
104
+
105
+ <!-- RIGHT VISUAL -->
29
106
  <g>
30
- <rect x="760" y="64" width="760" height="510" rx="34" fill="#0C0F1C" fill-opacity=".45" stroke="#252B40" stroke-opacity=".72"/><rect x="780" y="84" width="720" height="470" rx="26" fill="none" stroke="#FFFFFF" stroke-opacity=".035"/>
31
- <g transform="translate(815,248)"><rect width="116" height="142" rx="20" fill="url(#chip)" stroke="#2B3148"/><rect x="16" y="17" width="84" height="82" rx="13" fill="#0A0D17" stroke="#2D3550"/><rect x="28" y="31" width="23" height="23" rx="5" fill="#5C51E8"/><rect x="55" y="31" width="33" height="23" rx="5" fill="#183C58"/><rect x="28" y="58" width="38" height="28" rx="5" fill="#154A63"/><rect x="70" y="58" width="18" height="28" rx="5" fill="#7F6FFF"/><text x="58" y="121" text-anchor="middle" fill="#AEB6CC" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="13" font-weight="700">图像</text></g>
32
- <path d="M931 319 C978 319 995 319 1032 319" fill="none" stroke="url(#route)" stroke-width="2.7" stroke-linecap="round" filter="url(#glow)"/><circle cx="974" cy="319" r="4" fill="#7668FF"/><circle cx="1004" cy="319" r="3" fill="#7668FF" opacity=".65"/>
33
- <g transform="translate(1111,319)"><circle r="102" fill="#0D1020" stroke="#2B314B" stroke-width="1.4"/><circle r="87" fill="none" stroke="#6659FF" stroke-opacity=".18"/><circle r="68" fill="none" stroke="#46D7FF" stroke-opacity=".10"/><path d="M-64 0 C-35 -38 35 -38 64 0 C35 38 -35 38 -64 0Z" fill="#11162A" stroke="url(#iris)" stroke-width="2.5" filter="url(#glow)"/><circle r="28" fill="url(#iris)" opacity=".92" filter="url(#softGlow)"/><circle r="12" fill="#080B14"/><circle cx="7" cy="-8" r="4.5" fill="#DDF8FF"/><circle cx="-91" r="4" fill="#7568FF"/><circle cx="91" cy="-49" r="4" fill="#49D5FF"/><circle cx="101" r="4" fill="#49D5FF"/><circle cx="91" cy="49" r="4" fill="#49D5FF"/><text x="0" y="132" text-anchor="middle" fill="#7A839A" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="13" font-weight="700">视觉路由核心</text></g>
34
- <path d="M1212 270 C1258 270 1260 183 1308 183" fill="none" stroke="url(#route)" stroke-width="2.2"/><path d="M1214 319 C1264 319 1266 319 1308 319" fill="none" stroke="url(#route)" stroke-width="2.2"/><path d="M1212 368 C1258 368 1260 455 1308 455" fill="none" stroke="url(#route)" stroke-width="2.2"/><circle cx="1268" cy="213" r="4" fill="#65D9FF"/><circle cx="1262" cy="319" r="4" fill="#7B6DFF"/><circle cx="1268" cy="425" r="4" fill="#65D9FF"/>
35
- <g transform="translate(1308,137)"><rect width="164" height="92" rx="17" fill="url(#chip)" stroke="#2C334B"/><circle cx="24" cy="26" r="8" fill="#2ED887" fill-opacity=".14" stroke="#4BDB96"/><path d="M20 26 l3 3 l6 -7" fill="none" stroke="#60E6A5" stroke-width="2"/><text x="43" y="31" fill="#E0E5F2" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="14" font-weight="700">免费视觉</text><text x="22" y="61" fill="#737D94" font-family="monospace" font-size="11.5">Qwen2.5-VL</text><text x="22" y="78" fill="#535D74" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="10.5">内置 · 无需 Key</text></g>
36
- <g transform="translate(1308,273)"><rect width="164" height="92" rx="17" fill="url(#chip)" stroke="#403772"/><circle cx="24" cy="26" r="8" fill="#7A6BFF" fill-opacity=".15" stroke="#7A6BFF"/><path d="M20 26 h8 M24 22 v8" stroke="#A79DFF" stroke-width="1.7"/><text x="43" y="31" fill="#E7E4FF" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="14" font-weight="700">故障切换</text><text x="22" y="61" fill="#8E86C9" font-family="monospace" font-size="11.5">provider chain</text><text x="22" y="78" fill="#615B83" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="10.5">配额 · 区域 · 429</text></g>
37
- <g transform="translate(1308,409)"><rect width="164" height="92" rx="17" fill="url(#chip)" stroke="#263D52"/><circle cx="24" cy="26" r="8" fill="#43D6FF" fill-opacity=".13" stroke="#44D6FF"/><path d="M19 26 h10 M24 21 v10" stroke="#7AE3FF" stroke-width="1.7"/><text x="43" y="31" fill="#DDF7FF" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="14" font-weight="700">像素工具</text><text x="22" y="61" fill="#7DB5C6" font-family="monospace" font-size="11.5">crop · diff · OCR</text><text x="22" y="78" fill="#52707D" font-family="monospace" font-size="10.5">ground · trace · color</text></g>
38
- <g transform="translate(982,106)"><rect width="316" height="40" rx="20" fill="#101526" stroke="#2D3450"/><circle cx="21" cy="20" r="4" fill="#A092FF"/><text x="36" y="25" fill="#BAC1D2" font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif" font-size="13" font-weight="650">DEEPSEEK 仍是大脑</text><path d="M239 20 h46" stroke="#343C59" stroke-width="1.5"/><circle cx="291" cy="20" r="4" fill="#4BD5FF"/></g>
107
+ <rect x="760" y="64" width="760" height="510" rx="34" fill="#0C0F1C" fill-opacity=".45" stroke="#252B40" stroke-opacity=".72"/>
108
+ <rect x="780" y="84" width="720" height="470" rx="26" fill="none" stroke="#FFFFFF" stroke-opacity=".035"/>
109
+
110
+ <!-- input tile -->
111
+ <g transform="translate(815,248)">
112
+ <rect x="0" y="0" width="116" height="142" rx="20" fill="url(#chip)" stroke="#2B3148"/>
113
+ <rect x="16" y="17" width="84" height="82" rx="13" fill="#0A0D17" stroke="#2D3550"/>
114
+ <rect x="28" y="31" width="23" height="23" rx="5" fill="#5C51E8"/>
115
+ <rect x="55" y="31" width="33" height="23" rx="5" fill="#183C58"/>
116
+ <rect x="28" y="58" width="38" height="28" rx="5" fill="#154A63"/>
117
+ <rect x="70" y="58" width="18" height="28" rx="5" fill="#7F6FFF"/>
118
+ <text x="58" y="121" text-anchor="middle" fill="#AEB6CC"
119
+ font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif"
120
+ font-size="13" font-weight="700">图像</text>
121
+ </g>
122
+
123
+ <!-- route in -->
124
+ <path d="M931 319 C978 319 995 319 1032 319" fill="none" stroke="url(#route)" stroke-width="2.7" stroke-linecap="round" filter="url(#glow)"/>
125
+ <circle cx="974" cy="319" r="4" fill="#7668FF"/>
126
+ <circle cx="1004" cy="319" r="3" fill="#7668FF" opacity=".65"/>
127
+
128
+ <!-- router eye -->
129
+ <g transform="translate(1111,319)">
130
+ <circle cx="0" cy="0" r="102" fill="#0D1020" stroke="#2B314B" stroke-width="1.4"/>
131
+ <circle cx="0" cy="0" r="87" fill="none" stroke="#6659FF" stroke-opacity=".18"/>
132
+ <circle cx="0" cy="0" r="68" fill="none" stroke="#46D7FF" stroke-opacity=".10"/>
133
+
134
+ <path d="M-64 0 C-35 -38 35 -38 64 0 C35 38 -35 38 -64 0Z" fill="#11162A" stroke="url(#iris)" stroke-width="2.5" filter="url(#glow)"/>
135
+ <circle cx="0" cy="0" r="28" fill="url(#iris)" opacity=".92" filter="url(#softGlow)"/>
136
+ <circle cx="0" cy="0" r="12" fill="#080B14"/>
137
+ <circle cx="7" cy="-8" r="4.5" fill="#DDF8FF" opacity=".9"/>
138
+
139
+ <circle cx="-91" cy="0" r="4" fill="#7568FF"/>
140
+ <circle cx="91" cy="-49" r="4" fill="#49D5FF"/>
141
+ <circle cx="101" cy="0" r="4" fill="#49D5FF"/>
142
+ <circle cx="91" cy="49" r="4" fill="#49D5FF"/>
143
+
144
+ <text x="0" y="132" text-anchor="middle" fill="#7A839A"
145
+ font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif"
146
+ font-size="13" font-weight="700" letter-spacing="1.0">视觉路由核心</text>
147
+ </g>
148
+
149
+ <!-- routes out -->
150
+ <path d="M1212 270 C1258 270 1260 183 1308 183" fill="none" stroke="url(#route)" stroke-width="2.2" stroke-linecap="round"/>
151
+ <path d="M1214 319 C1264 319 1266 319 1308 319" fill="none" stroke="url(#route)" stroke-width="2.2" stroke-linecap="round"/>
152
+ <path d="M1212 368 C1258 368 1260 455 1308 455" fill="none" stroke="url(#route)" stroke-width="2.2" stroke-linecap="round"/>
153
+
154
+ <circle cx="1268" cy="213" r="4" fill="#65D9FF" filter="url(#glow)"/>
155
+ <circle cx="1262" cy="319" r="4" fill="#7B6DFF" filter="url(#glow)"/>
156
+ <circle cx="1268" cy="425" r="4" fill="#65D9FF" filter="url(#glow)"/>
157
+
158
+ <!-- provider cards -->
159
+ <g transform="translate(1308,137)">
160
+ <rect x="0" y="0" width="164" height="92" rx="17" fill="url(#chip)" stroke="#2C334B"/>
161
+ <circle cx="24" cy="26" r="8" fill="#2ED887" fill-opacity=".14" stroke="#4BDB96"/>
162
+ <path d="M20 26 l3 3 l6 -7" fill="none" stroke="#60E6A5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
163
+ <text x="43" y="31" fill="#E0E5F2"
164
+ font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif"
165
+ font-size="14" font-weight="700">免费视觉</text>
166
+ <text x="22" y="61" fill="#737D94" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="11.5">Qwen2.5-VL</text>
167
+ <text x="22" y="78" fill="#535D74"
168
+ font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif"
169
+ font-size="10.5">内置 · 无需 Key</text>
170
+ </g>
171
+
172
+ <g transform="translate(1308,273)">
173
+ <rect x="0" y="0" width="164" height="92" rx="17" fill="url(#chip)" stroke="#403772"/>
174
+ <circle cx="24" cy="26" r="8" fill="#7A6BFF" fill-opacity=".15" stroke="#7A6BFF"/>
175
+ <path d="M20 26 h8 M24 22 v8" stroke="#A79DFF" stroke-width="1.7" stroke-linecap="round"/>
176
+ <text x="43" y="31" fill="#E7E4FF"
177
+ font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif"
178
+ font-size="14" font-weight="700">故障切换</text>
179
+ <text x="22" y="61" fill="#8E86C9" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="11.5">provider chain</text>
180
+ <text x="22" y="78" fill="#615B83"
181
+ font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif"
182
+ font-size="10.5">配额 · 区域 · 429</text>
183
+ </g>
184
+
185
+ <g transform="translate(1308,409)">
186
+ <rect x="0" y="0" width="164" height="92" rx="17" fill="url(#chip)" stroke="#263D52"/>
187
+ <circle cx="24" cy="26" r="8" fill="#43D6FF" fill-opacity=".13" stroke="#44D6FF"/>
188
+ <path d="M19 26 h10 M24 21 v10" stroke="#7AE3FF" stroke-width="1.7" stroke-linecap="round"/>
189
+ <text x="43" y="31" fill="#DDF7FF"
190
+ font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif"
191
+ font-size="14" font-weight="700">像素工具</text>
192
+ <text x="22" y="61" fill="#7DB5C6" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="11.5">crop · diff · OCR</text>
193
+ <text x="22" y="78" fill="#52707D" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="10.5">ground · trace · color</text>
194
+ </g>
195
+
196
+ <!-- brain stays brain -->
197
+ <g transform="translate(982,106)">
198
+ <rect x="0" y="0" width="316" height="40" rx="20" fill="#101526" stroke="#2D3450"/>
199
+ <circle cx="21" cy="20" r="4" fill="#A092FF"/>
200
+ <text x="36" y="25" fill="#BAC1D2"
201
+ font-family="PingFang SC, Noto Sans CJK SC, Microsoft YaHei, Inter, Arial, sans-serif"
202
+ font-size="13" font-weight="650">DEEPSEEK 仍是大脑</text>
203
+ <path d="M239 20 h46" stroke="#343C59" stroke-width="1.5"/>
204
+ <circle cx="291" cy="20" r="4" fill="#4BD5FF"/>
205
+ </g>
39
206
  </g>
207
+
40
208
  <rect x=".75" y=".75" width="1598.5" height="638.5" rx="31.25" fill="none" stroke="#FFFFFF" stroke-opacity=".08" stroke-width="1.5"/>
41
209
  </svg>