@anionex/dsh-vision-toolkit 0.1.36 → 0.1.38

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
@@ -64,8 +64,8 @@ dsh plugin --profile web add @anionex/dsh-vision-toolkit
64
64
 
65
65
  <table>
66
66
  <tr>
67
- <td width="220"><a href="https://aihubmix.com/?aff=5wj6sgx8"><img src="assets/logo_aihubmix.png" alt="AIHubMix" height="48"></a></td>
68
- <td>Thanks to <a href="https://aihubmix.com/?aff=5wj6sgx8">AIHubMix</a> for sponsoring this project! AIHubMix is a stable, high-concurrency AI model API gateway that connects Claude, GPT, Gemini, DeepSeek, and other mainstream models through a single API key, compatible with multiple protocols, with <b>free model options</b> available. Users in China can use it via the <a href="https://inferera.com/?aff=5wj6sgx8">China entry</a>, and users abroad via the <a href="https://aihubmix.com/?aff=5wj6sgx8">Global entry</a>.</td>
67
+ <td width="220"><a href="https://inferera.com/?aff=5wj6sgx8"><img src="assets/logo_aihubmix.png" alt="AIHubMix" height="48"></a></td>
68
+ <td>Thanks to <a href="https://inferera.com/?aff=5wj6sgx8">AIHubMix</a> for sponsoring this project! AIHubMix is a stable, high-concurrency AI model API gateway that connects Claude, GPT, Gemini, DeepSeek, and other mainstream models through a single API key, compatible with multiple protocols, with <b>free model options</b> available. Use the <a href="https://inferera.com/?aff=5wj6sgx8">Inferera entry</a> to sign up.</td>
69
69
  </tr>
70
70
  </table>
71
71
 
@@ -86,10 +86,10 @@ dsh plugin --profile web add @anionex/dsh-vision-toolkit
86
86
 
87
87
  ## Recent updates
88
88
 
89
+ - **2026-08-20 · AIHubMix setup guide:** Added a screenshot-based guide for getting an API key through the Inferera entry and using the free Gemini 3.7 Flash vision model; Settings now links directly to this guide.
89
90
  - **2026-08-19 · Transparent routing by default:** The model selector keeps one entry per model with the original name, and image input (paste, history, `read_image`) works without manually switching to a `(Vision Toolkit)` variant. Disable “Transparent variant routing” in advanced settings → image input to restore the explicit entries.
90
91
  - **2026-08-16 · Windows Python:** Added Microsoft Store Python support, fixing first-time isolated-runtime setup failures for affected Windows users.
91
92
  - **2026-08-17 · Free vision upgrade:** Switched the built-in no-key service to Gemini 3.7 Flash and fixed Qwen/Gemini bounding-box coordinate order.
92
- - **2026-08-16 · Better free vision:** Switched the built-in no-key service to Groq Qwen3.6, improving image understanding without adding setup steps.
93
93
  - **2026-08-16 · Image paste:** Text-only routes now switch to a `(Vision Toolkit)` variant and keep a workspace path, fixing blocked pastes and images that could not be reused later.
94
94
  - **2026-08-16 · More shared capacity:** Expanded the free service capacity to reduce peak-time `429` responses.
95
95
  - **2026-08-16 · Real model test:** Added a full image-request test in Settings, fixing the false confidence caused by a successful `/models` request to a model that still cannot process images.
@@ -285,7 +285,7 @@ Existing clients that still send `api_key="free"` remain compatible.
285
285
 
286
286
  For higher quotas, private endpoints, or another model, change the provider in **Settings → Vision Toolkit** and store the API key as a DSH Credential. Settings stores the Credential reference and never reads the saved secret back into the browser.
287
287
 
288
- **Step-by-step Groq tutorial:** [Get a free Groq API key and use Qwen3.6-27B for image understanding](docs/groq-qwen3.6-vision.md). It includes screenshots for account/API-key setup, the exact Vision Toolkit settings, and working cURL and Python examples.
288
+ **Step-by-step AIHubMix tutorial:** [Get an AIHubMix API key and use free Gemini 3.7 Flash for vision](docs/aihubmix-gemini-vision.md). It includes screenshots for account/API-key setup, the exact Vision Toolkit settings, free-model selection, and troubleshooting.
289
289
 
290
290
  You can also configure a Profile patch:
291
291
 
package/README.zh.md CHANGED
@@ -63,8 +63,8 @@ dsh plugin --profile web add @anionex/dsh-vision-toolkit
63
63
 
64
64
  <table>
65
65
  <tr>
66
- <td width="220"><a href="https://aihubmix.com/?aff=5wj6sgx8"><img src="assets/logo_aihubmix.png" alt="AIHubMix" height="48"></a></td>
67
- <td>感谢 <a href="https://aihubmix.com/?aff=5wj6sgx8">AIHubMix</a> 赞助本项目!AIHubMix 是稳定、高并发的 AI 大模型 API 聚合平台,一个 API Key 即可接入 Claude、GPT、Gemini、DeepSeek 等主流模型,兼容多种协议,并提供<b>免费模型选择</b>。国内用户请通过<a href="https://inferera.com/?aff=5wj6sgx8">国内入口</a>使用,海外用户请通过<a href="https://aihubmix.com/?aff=5wj6sgx8">海外入口</a>使用。</td>
66
+ <td width="220"><a href="https://inferera.com/?aff=5wj6sgx8"><img src="assets/logo_aihubmix.png" alt="AIHubMix" height="48"></a></td>
67
+ <td>感谢 <a href="https://inferera.com/?aff=5wj6sgx8">AIHubMix</a> 赞助本项目!AIHubMix 是稳定、高并发的 AI 大模型 API 聚合平台,一个 API Key 即可接入 Claude、GPT、Gemini、DeepSeek 等主流模型,兼容多种协议,并提供<b>免费模型选择</b>。注册时请使用 <a href="https://inferera.com/?aff=5wj6sgx8">Inferera 入口</a>。</td>
68
68
  </tr>
69
69
  </table>
70
70
 
@@ -85,10 +85,10 @@ dsh plugin --profile web add @anionex/dsh-vision-toolkit
85
85
 
86
86
  ## 最近更新
87
87
 
88
+ - **2026-08-20 · AIHubMix 申请教程:** 新增通过 Inferera 入口申请 API Key、使用免费 Gemini 3.7 Flash 视觉模型的图文教程,并在视觉工具设置中直接提供入口。
88
89
  - **2026-08-19 · 透明变体路由默认开启:** 模型选择器默认只显示每个模型一项并保留原模型名,粘贴图片、历史图片和内置 `read_image` 工具都能直接使用,不再需要手动切换到 `(Vision Toolkit)` 变体;如需恢复显式条目,可在 设置 → 高级设置 → 图片输入 关闭“透明变体路由”。
89
90
  - **2026-08-16 · Windows Python:** 支持 Microsoft Store Python,解决 Windows 用户首次创建隔离环境失败的问题。
90
91
  - **2026-08-17 · 免费视觉升级:** 默认模型切换到 Gemini 3.7 Flash,并修复 Qwen/Gemini 检测框坐标顺序错位的问题。
91
- - **2026-08-16 · 免费视觉升级:** 默认模型切换到 Groq Qwen3.6,解决免 Key 方案看图效果不足的问题。
92
92
  - **2026-08-16 · 图片粘贴:** 文本模型自动切换到 `(Vision Toolkit)` 变体并保留工作区路径,解决粘贴图片被拦截或后续无法复用的问题。
93
93
  - **2026-08-16 · 共享容量:** 扩大免费服务容量,减少高峰期出现 `429` 的情况。
94
94
  - **2026-08-16 · 真实模型测试:** Settings 新增完整图片请求测试,解决 `/models` 可访问却不能证明模型真的会看图的问题。
@@ -283,7 +283,7 @@ API Key: https://agent-vision.anionex.me(自动填写)
283
283
 
284
284
  如果你需要更高额度、私有端点或其他模型,可以在 **设置 → 视觉工具** 中修改提供方,并把 API Key 保存为 DSH Credential。Settings 只保存 Credential 引用,不会回显密钥。
285
285
 
286
- **Groq 图文教程:** [免费获取 Groq API Key,并调用 Qwen3.6-27B 识图](docs/groq-qwen3.6-vision.zh.md)。教程包含账号与 API Key 获取截图、Vision Toolkit 的准确配置,以及可直接使用的 cURL 和 Python 示例。
286
+ **AIHubMix 图文教程:** [申请 AIHubMix API Key,并用免费 Gemini 3.7 Flash 识图](docs/aihubmix-gemini-vision.zh.md)。教程包含账号与 API Key 获取截图、Vision Toolkit 的准确配置、免费模型选择和常见问题排查。
287
287
 
288
288
  也可以在 Profile patch 中配置:
289
289
 
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write dsh-vision-toolkit/docs/aihubmix-gemini-vision.md
5
+ aihubmix-gemini-vision.md: c2958163461843045fc4547e56fbaae78d1b9f72
6
+ aihubmix-gemini-vision.zh.md: dd2ba9246bb921f4986cacfd28e31aaeec4903e3
@@ -0,0 +1,138 @@
1
+ # Get an AIHubMix API key and use free Gemini 3.7 Flash for vision
2
+
3
+ **English** | [中文](aihubmix-gemini-vision.zh.md)
4
+
5
+ This tutorial completes three tasks:
6
+
7
+ 1. Create an AIHubMix account.
8
+ 2. Create and safely store an AIHubMix API key.
9
+ 3. Use the free `gemini-3.7-flash-free` model for image analysis in DSH Vision Toolkit.
10
+
11
+ > As of August 20, 2026, the AIHubMix model page lists `gemini-3.7-flash-free` as a free trial model with image input. Free capacity is limited, may return `429`, and is not guaranteed for production workloads; use the paid `gemini-3.7-flash` route when reliability is required. Models, pricing, and availability can change, so check the [AIHubMix model page](https://aihubmix.com/model/gemini-3.7-flash-free) for current details.
12
+
13
+ ## 1. Open the signup entry and register
14
+
15
+ Open the [Inferera signup entry](https://inferera.com/?aff=5wj6sgx8), which redirects to AIHubMix, then select **Sign up** in the upper-right corner or **Get API Key** on the page.
16
+
17
+ This URL includes the project's referral parameter. You can instead open [Inferera](https://inferera.com/) directly if you prefer not to use a referral link.
18
+
19
+ <p align="center">
20
+ <img src="assets/aihubmix-home.png" width="92%" alt="AIHubMix homepage and signup entry" />
21
+ </p>
22
+
23
+ The signup page supports GitHub, Google, and email. For email signup, enter an email address and password, accept the terms, and complete any verification requested by the page.
24
+
25
+ <p align="center">
26
+ <img src="assets/aihubmix-sign-up.png" width="92%" alt="AIHubMix account signup page" />
27
+ </p>
28
+
29
+ ## 2. Create an API key
30
+
31
+ After signing in, open **Developer → API Keys** in the console sidebar, or go directly to [AIHubMix API Keys](https://console.aihubmix.com/token). The page shows two endpoint hosts:
32
+
33
+ - Default: `https://aihubmix.com`
34
+ - Preferred: `https://api.inferera.com`
35
+
36
+ This guide uses the preferred `https://api.inferera.com/v1` endpoint in Vision Toolkit. If that endpoint does not work from your network, use `https://aihubmix.com/v1` instead.
37
+
38
+ <p align="center">
39
+ <img src="assets/aihubmix-api-keys.png" width="88%" alt="AIHubMix API Keys page with default and preferred base URLs and the Create API key button" />
40
+ </p>
41
+
42
+ Select **Create API key**, then:
43
+
44
+ 1. Enter a recognizable name such as `dsh-vision-toolkit`.
45
+ 2. For an initial trial, turn off unlimited quota and set a small quota, expiration date, model range, or IP restriction so a later paid-model mistake cannot spend without a limit.
46
+ 3. Select **Submit** to create the key.
47
+ 4. Copy the complete `sk-...` value immediately and store it in a password manager or DSH Credential.
48
+
49
+ <p align="center">
50
+ <img src="assets/aihubmix-create-key.png" width="56%" alt="AIHubMix Create API key form" />
51
+ </p>
52
+
53
+ Do not place the complete key in a README, chat transcript, screenshot, Git commit, browser frontend, or public log. Delete and replace a key immediately if it becomes public.
54
+
55
+ ## 3. Select the free vision model
56
+
57
+ Use this exact model ID:
58
+
59
+ ```text
60
+ gemini-3.7-flash-free
61
+ ```
62
+
63
+ The model page lists it as free and shows text, image, audio, video, and PDF input. Vision Toolkit uses its image input and text output.
64
+
65
+ <p align="center">
66
+ <img src="assets/aihubmix-free-vision-model.png" width="92%" alt="AIHubMix Gemini 3.7 Flash free model page showing image input" />
67
+ </p>
68
+
69
+ The free route is for trials and can return `429 Too Many Requests` when capacity is exhausted. For reliable usage, switch the model to `gemini-3.7-flash` and ensure the account has sufficient credit. Optional capabilities such as web search or cache storage may have separate charges; ordinary Vision Toolkit image analysis does not enable them.
70
+
71
+ ## 4. Configure DSH Vision Toolkit
72
+
73
+ 1. Open **Settings → Vision** in DSH Web.
74
+ 2. Enter these values under the vision service:
75
+
76
+ | Field | Value |
77
+ |---|---|
78
+ | API protocol | `OpenAI Chat Completions` |
79
+ | Base URL | `https://api.inferera.com/v1` |
80
+ | Model | `gemini-3.7-flash-free` |
81
+ | API key | Paste the newly created `sk-...` key |
82
+
83
+ 3. Expand **Advanced settings** and change **Credential name** to `AIHUBMIX_API_KEY` instead of reusing the built-in free provider's credential name.
84
+ 4. Select **Save and apply**. The key is stored in DSH Credentials and is not displayed again in full.
85
+ 5. Select **Test vision model**. This action sends the bundled diagnostic image and verifies a real multimodal request; **Test API connection**, which only queries `/models`, is not a substitute.
86
+ 6. After the test succeeds, paste an image into a session and ask a concrete question, for example:
87
+
88
+ ```text
89
+ Transcribe the error in this screenshot exactly, then explain the most likely cause and the repair steps.
90
+ ```
91
+
92
+ The same provider can be stored in a Profile patch. Keep the key value in DSH Credentials rather than YAML:
93
+
94
+ ```yaml
95
+ - id: vision-toolkit
96
+ config:
97
+ provider:
98
+ protocol: openai
99
+ baseUrl: https://api.inferera.com/v1
100
+ model: gemini-3.7-flash-free
101
+ credential: AIHUBMIX_API_KEY
102
+ ```
103
+
104
+ ## 5. Troubleshooting
105
+
106
+ ### `401` or `Unauthorized`
107
+
108
+ - Confirm that you pasted the complete `sk-...` value, not the key name or a masked fragment.
109
+ - Remove extra spaces, line breaks, or an `AIHUBMIX_API_KEY=` prefix from the value.
110
+ - Delete and replace a key that has appeared in a public location.
111
+
112
+ ### `402`, insufficient credit, or an unavailable model
113
+
114
+ - The free trial route must use the full `gemini-3.7-flash-free` model ID.
115
+ - If you switch to `gemini-3.7-flash` or another paid model, add credit from **Credits** in the console.
116
+ - Model availability changes; check the current AIHubMix model page and console.
117
+
118
+ ### `404` or `model not found`
119
+
120
+ - Use exactly `gemini-3.7-flash-free`.
121
+ - Do not send the display name `Gemini 3.7 Flash (free)` as the API model ID.
122
+
123
+ ### `429 Too Many Requests`
124
+
125
+ - Free-model capacity is limited. Wait and retry, or lower concurrency and request frequency.
126
+ - Switch to `gemini-3.7-flash` when stable production service is required.
127
+
128
+ ### Connection timeout or unreachable endpoint
129
+
130
+ - Start with this guide's `https://api.inferera.com/v1` endpoint.
131
+ - If it is unavailable, switch to `https://aihubmix.com/v1` and rerun **Test vision model**.
132
+
133
+ ## Official resources
134
+
135
+ - [Inferera signup entry](https://inferera.com/?aff=5wj6sgx8)
136
+ - [AIHubMix API Keys](https://console.aihubmix.com/token)
137
+ - [AIHubMix documentation](https://docs.aihubmix.com/en)
138
+ - [Gemini 3.7 Flash (free) model page](https://aihubmix.com/model/gemini-3.7-flash-free)
@@ -0,0 +1,138 @@
1
+ # 申请 AIHubMix API Key,并用免费 Gemini 3.7 Flash 识图
2
+
3
+ [English](aihubmix-gemini-vision.md) | **中文**
4
+
5
+ 这篇教程完成三件事:
6
+
7
+ 1. 注册 AIHubMix 账号。
8
+ 2. 创建并安全保存一个 AIHubMix API Key。
9
+ 3. 在 DSH Vision Toolkit 中使用免费的 `gemini-3.7-flash-free` 分析图片。
10
+
11
+ > 截至 2026-08-20,AIHubMix 模型页把 `gemini-3.7-flash-free` 标为支持图像输入的免费试用模型。免费资源有限,可能返回 `429`,不保证生产环境稳定性;生产用途应切换到正式模型 `gemini-3.7-flash`。模型、价格和可用性可能调整,请以 [AIHubMix 模型页](https://aihubmix.com/model/gemini-3.7-flash-free) 为准。
12
+
13
+ ## 1. 打开申请入口并注册
14
+
15
+ 打开 [Inferera 国内申请入口](https://inferera.com/?aff=5wj6sgx8),页面会跳转到 AIHubMix;点击右上角 **注册**,也可以点击页面中的 **立即体验**。
16
+
17
+ 这个入口带有本项目的推荐参数;如果不想使用推荐链接,也可以直接访问 [Inferera](https://inferera.com/)。
18
+
19
+ <p align="center">
20
+ <img src="assets/aihubmix-home.png" width="92%" alt="AIHubMix 中文首页与注册入口" />
21
+ </p>
22
+
23
+ 注册页支持 GitHub、Google 或邮箱。使用邮箱时,填写邮箱和密码、同意服务条款,然后按页面提示完成验证。
24
+
25
+ <p align="center">
26
+ <img src="assets/aihubmix-sign-up.png" width="92%" alt="AIHubMix 中文注册页面" />
27
+ </p>
28
+
29
+ ## 2. 创建 API Key
30
+
31
+ 登录后进入控制台左侧的 **开发 → API Keys**,或直接打开 [AIHubMix API Keys](https://console.aihubmix.com/token)。页面会同时显示两个接口地址:
32
+
33
+ - 默认地址:`https://aihubmix.com`
34
+ - 优选地址:`https://api.inferera.com`
35
+
36
+ 本教程在 Vision Toolkit 中使用优选地址 `https://api.inferera.com/v1`。如果该地址在你的网络中不可用,可以改用 `https://aihubmix.com/v1`。
37
+
38
+ <p align="center">
39
+ <img src="assets/aihubmix-api-keys.png" width="88%" alt="AIHubMix API Keys 页面中的默认地址、优选地址和 Create API key 按钮" />
40
+ </p>
41
+
42
+ 点击 **创建 Key**(英文界面显示 **Create API key**),然后:
43
+
44
+ 1. 在 **名称** 中填写容易辨认的名称,例如 `dsh-vision-toolkit`。
45
+ 2. 首次试用建议关闭无限额度,并设置小额上限、过期时间、模型范围或 IP 限制,避免以后误用付费模型时产生无上限费用。
46
+ 3. 点击 **提交** 创建密钥。
47
+ 4. 立即复制完整的 `sk-...` 密钥,并保存到密码管理器或 DSH Credential 中。
48
+
49
+ <p align="center">
50
+ <img src="assets/aihubmix-create-key.png" width="56%" alt="AIHubMix Create API key 表单" />
51
+ </p>
52
+
53
+ 不要把完整 API Key 放进 README、聊天记录、截图、Git 提交、浏览器前端代码或公开日志。密钥泄露后应立即删除旧 Key 并重新创建。
54
+
55
+ ## 3. 选择免费的视觉模型
56
+
57
+ 本教程使用准确模型 ID:
58
+
59
+ ```text
60
+ gemini-3.7-flash-free
61
+ ```
62
+
63
+ 模型页将它标为免费,并列出文本、图像、音频、视频和 PDF 输入能力。Vision Toolkit 使用其中的图像输入和文本输出。
64
+
65
+ <p align="center">
66
+ <img src="assets/aihubmix-free-vision-model.png" width="92%" alt="AIHubMix Gemini 3.7 Flash free 模型页显示图像输入能力" />
67
+ </p>
68
+
69
+ 免费版本只适合试用,资源紧张时可能返回 `429 Too Many Requests`。需要稳定调用时,把模型改为 `gemini-3.7-flash`,并确认账户余额足够。模型页列出的联网搜索、缓存等可选能力可能单独计费;Vision Toolkit 的普通图片分析不会主动启用这些能力。
70
+
71
+ ## 4. 在 DSH Vision Toolkit 中使用
72
+
73
+ 1. 打开 DSH Web 的 **设置 → 视觉工具**。
74
+ 2. 在“在线视觉服务”中填写:
75
+
76
+ | 字段 | 值 |
77
+ |---|---|
78
+ | API 协议 | `OpenAI Chat Completions` |
79
+ | API 地址 | `https://api.inferera.com/v1` |
80
+ | 模型名称 | `gemini-3.7-flash-free` |
81
+ | API 密钥 | 粘贴刚创建的 `sk-...` 密钥 |
82
+
83
+ 3. 展开 **高级设置**,把 **凭据名称** 改为 `AIHUBMIX_API_KEY`,避免继续使用内置免费服务的凭据名称。
84
+ 4. 点击 **保存设置**。密钥会写入 DSH Credentials,页面以后不会回显完整内容。
85
+ 5. 点击 **测试视觉模型**。这个测试会发送插件自带的诊断图片,验证真实多模态请求;只测试 `/models` 的 **测试 API 连接** 不能代替它。
86
+ 6. 测试成功后,在会话中粘贴图片并直接提问,例如:
87
+
88
+ ```text
89
+ 请先逐字抄出截图中的报错,再判断最可能的原因和修复步骤。
90
+ ```
91
+
92
+ 也可以在 Profile patch 中保存相同配置。密钥值仍应保存在 DSH Credential 中,不要写入 YAML:
93
+
94
+ ```yaml
95
+ - id: vision-toolkit
96
+ config:
97
+ provider:
98
+ protocol: openai
99
+ baseUrl: https://api.inferera.com/v1
100
+ model: gemini-3.7-flash-free
101
+ credential: AIHUBMIX_API_KEY
102
+ ```
103
+
104
+ ## 5. 常见问题
105
+
106
+ ### `401` 或 `Unauthorized`
107
+
108
+ - 确认粘贴的是完整的 `sk-...` 密钥,而不是 Key 名称或被遮盖后的片段。
109
+ - 粘贴的密钥值中不要包含多余空格、换行或 `AIHUBMIX_API_KEY=` 前缀。
110
+ - 如果密钥曾经公开,删除旧 Key 并重新创建。
111
+
112
+ ### `402`、余额不足或模型不可用
113
+
114
+ - 免费试用模型应使用完整 ID `gemini-3.7-flash-free`。
115
+ - 如果改用 `gemini-3.7-flash` 或其他付费模型,请在控制台的 **余额充值** 中补充余额。
116
+ - 模型可用性会变化,请以 AIHubMix 当前模型页和控制台为准。
117
+
118
+ ### `404` 或 `model not found`
119
+
120
+ - 模型名必须准确写成 `gemini-3.7-flash-free`。
121
+ - 不要把显示名称 `Gemini 3.7 Flash (free)` 当作 API 模型 ID。
122
+
123
+ ### `429 Too Many Requests`
124
+
125
+ - 免费模型资源有限,等待后重试,或降低并发和调用频率。
126
+ - 需要稳定生产调用时切换到 `gemini-3.7-flash`。
127
+
128
+ ### 连接超时或无法访问
129
+
130
+ - 先使用本教程推荐的 `https://api.inferera.com/v1`。
131
+ - 如果该地址不可用,改为 `https://aihubmix.com/v1` 后重新执行 **测试视觉模型**。
132
+
133
+ ## 官方资料
134
+
135
+ - [Inferera 国内申请入口](https://inferera.com/?aff=5wj6sgx8)
136
+ - [AIHubMix API Keys](https://console.aihubmix.com/token)
137
+ - [AIHubMix 中文文档](https://docs.aihubmix.com/cn)
138
+ - [Gemini 3.7 Flash (free) 模型页](https://aihubmix.com/model/gemini-3.7-flash-free)
Binary file
Binary file
package/lib/client.js CHANGED
@@ -87,8 +87,8 @@ const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKi
87
87
  const BUILT_IN_FREE_VISION_BASE_URL = 'https://vision.anionex.me/v1';
88
88
  const BUILT_IN_FREE_VISION_CREDENTIAL = 'ANIONEX_FREE_VISION';
89
89
  const BUILT_IN_FREE_VISION_MODEL = 'gemini-3.7-flash';
90
- const GROQ_TUTORIAL_URL_EN = 'https://github.com/Anionex/dsh-vision-toolkit/blob/main/docs/groq-qwen3.6-vision.md';
91
- const GROQ_TUTORIAL_URL_ZH = 'https://github.com/Anionex/dsh-vision-toolkit/blob/main/docs/groq-qwen3.6-vision.zh.md';
90
+ const AIHUBMIX_TUTORIAL_URL_EN = 'https://github.com/Anionex/dsh-vision-toolkit/blob/main/docs/aihubmix-gemini-vision.md';
91
+ const AIHUBMIX_TUTORIAL_URL_ZH = 'https://github.com/Anionex/dsh-vision-toolkit/blob/main/docs/aihubmix-gemini-vision.zh.md';
92
92
  const en = {
93
93
  nav: 'Vision',
94
94
  settingsTitle: 'Vision Toolkit',
@@ -96,7 +96,7 @@ const en = {
96
96
  externalNotice: 'Remote tools send the selected image bytes to the configured external vision API. Local crop, trace, pixel diff, palette, foreground extraction, and HTML rendering do not upload images.',
97
97
  provider: 'Vision service',
98
98
  providerHint: 'Choose the API protocol, then provide the service address, model, and API key used by online vision features.',
99
- groqTutorial: 'Want a free Groq key for Qwen3.6-27B vision? Follow the step-by-step tutorial →',
99
+ aihubmixTutorial: 'Need an AIHubMix key for free Gemini 3.7 Flash vision? Follow the signup guide →',
100
100
  baseUrl: 'Base URL',
101
101
  apiKey: 'API key',
102
102
  apiKeyPlaceholderMissing: 'Paste the API key',
@@ -281,7 +281,7 @@ const zh = {
281
281
  externalNotice: '使用图像理解、目标定位、界面检测或文字识别等在线功能时,所选图片会发送到下方配置的视觉服务。图片裁剪、轮廓描摹、像素对比、主色提取、前景提取和网页截图均在本机完成,不会上传图片。',
282
282
  provider: '在线视觉服务',
283
283
  providerHint: '选择接口协议后,填写在线视觉功能使用的 API 地址、模型名称和 API 密钥。',
284
- groqTutorial: '想免费申请 Groq Key 并用 Qwen3.6-27B 识图?看这篇图文教程 →',
284
+ aihubmixTutorial: '想申请 AIHubMix Key,并用免费 Gemini 3.7 Flash 识图?看这篇图文教程 →',
285
285
  baseUrl: 'API 地址',
286
286
  apiKey: 'API 密钥',
287
287
  apiKeyPlaceholderMissing: '粘贴 API 密钥',
@@ -1133,7 +1133,7 @@ function LoadedSettings({ controller, t }) {
1133
1133
  const updateHasUnsavedChanges = apiKey.length > 0 || settingsDraftChanged(draft, snapshot.settings.value, t);
1134
1134
  const manualUpdateProfile = updateCapability.profile ?? 'web';
1135
1135
  const manualUpdateCommand = `dsh plugin --profile ${manualUpdateProfile} add @anionex/dsh-vision-toolkit@latest --registry=https://registry.npmjs.org/`;
1136
- const tutorialUrl = draft?.language === 'en' ? GROQ_TUTORIAL_URL_EN : GROQ_TUTORIAL_URL_ZH;
1136
+ const aihubmixTutorialUrl = draft?.language === 'en' ? AIHUBMIX_TUTORIAL_URL_EN : AIHUBMIX_TUTORIAL_URL_ZH;
1137
1137
  const copyManualUpdate = () => {
1138
1138
  void navigator.clipboard?.writeText(manualUpdateCommand)
1139
1139
  .then(() => {
@@ -1149,7 +1149,7 @@ function LoadedSettings({ controller, t }) {
1149
1149
  return;
1150
1150
  void controller.applyUpdate(latestVersion);
1151
1151
  };
1152
- return ((0, jsx_runtime_1.jsxs)("div", { className: "dvt-settings", children: [(0, jsx_runtime_1.jsx)("div", { className: "dvt-alert notice", children: t('externalNotice') }), !snapshot.writable ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert warning", children: t('readOnly') }) : null, draftError === undefined ? null : (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert error", children: draftError }), state.error === undefined ? null : (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert error", children: state.error }), state.message === 'saved' ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert success", children: t('saved') }) : null, state.message === 'restarting' && state.restart !== undefined ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert success", children: t('restarting', { version: state.restart.toVersion }) }) : null, state.message === 'manual-restart-required' && state.restart !== undefined ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert success", children: t('manualRestartRequired', { version: state.restart.toVersion }) }) : null, snapshot.runtime.lastError === undefined ? null : (0, jsx_runtime_1.jsxs)("div", { className: "dvt-alert error", children: [(0, jsx_runtime_1.jsx)("strong", { children: runtimeErrorTitle }), (0, jsx_runtime_1.jsx)("span", { children: snapshot.runtime.lastError })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel dvt-essential", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-panel-title", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { children: t('provider') }), (0, jsx_runtime_1.jsx)("p", { children: t('providerHint') })] }), (0, jsx_runtime_1.jsx)("span", { className: `dvt-badge ${snapshot.credential.configured ? 'ok' : 'error'}`, children: snapshot.credential.configured ? t('configured') : t('missing') })] }), (0, jsx_runtime_1.jsx)("p", { className: "dvt-tutorial-link", children: (0, jsx_runtime_1.jsx)("a", { href: tutorialUrl, target: "_blank", rel: "noreferrer", children: t('groqTutorial') }) }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('protocol'), children: (0, jsx_runtime_1.jsxs)("select", { disabled: !snapshot.writable || busy, value: draft.protocol, onChange: (event) => { update('protocol', event.target.value); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "openai", children: "OpenAI Chat Completions" }), (0, jsx_runtime_1.jsx)("option", { value: "anthropic", children: "Anthropic Messages" })] }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('baseUrl'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { disabled: !snapshot.writable || busy, value: draft.baseUrl, onChange: (event) => { update('baseUrl', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('model'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { disabled: !snapshot.writable || busy, value: draft.model, onChange: (event) => { update('model', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('apiKey'), hint: keyLocked ? t('apiKeyLocked') : snapshot.credential.source === undefined ? t('apiKeyHint') : `${t('apiKeyHint')} ${t('sourceHint', { source: t('source'), value: credentialSource(snapshot.credential.source, t) })}`, children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('apiKey'), type: "password", autoComplete: "new-password", disabled: busy || keyLocked, placeholder: snapshot.credential.configured ? t('apiKeyPlaceholderConfigured') : t('apiKeyPlaceholderMissing'), value: apiKey, onChange: (event) => { setApiKey(event.target.value); setDraftError(undefined); } }) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-save-row", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "primary", disabled: !canSave || busy, onClick: save, children: state.action === 'save' ? t('saving') : t('save') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", disabled: busy, onClick: () => { void controller.load(); }, children: t('reload') })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-panel-title", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { children: t('health') }), (0, jsx_runtime_1.jsx)("p", { children: t('connectionHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-actions", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "outline", disabled: busy || !snapshot.runtime.ready, onClick: () => { void controller.runHealth('health'); }, children: state.action === 'health' ? t('testing') : t('runHealth') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "outline", disabled: busy || !snapshot.runtime.ready, onClick: () => { void controller.runHealth('connection'); }, children: state.action === 'connection' ? t('testing') : t('testConnection') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "primary", disabled: busy || !snapshot.runtime.ready, onClick: () => { void controller.runHealth('model'); }, children: state.action === 'model' ? t('testingModel') : t('testModel') })] })] }), (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('saveBeforeTesting') }), state.health === undefined ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('notTested') }) : (0, jsx_runtime_1.jsx)("div", { className: "dvt-health-grid", children: Object.entries(state.health.checks).map(([name, check]) => {
1152
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "dvt-settings", children: [(0, jsx_runtime_1.jsx)("div", { className: "dvt-alert notice", children: t('externalNotice') }), !snapshot.writable ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert warning", children: t('readOnly') }) : null, draftError === undefined ? null : (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert error", children: draftError }), state.error === undefined ? null : (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert error", children: state.error }), state.message === 'saved' ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert success", children: t('saved') }) : null, state.message === 'restarting' && state.restart !== undefined ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert success", children: t('restarting', { version: state.restart.toVersion }) }) : null, state.message === 'manual-restart-required' && state.restart !== undefined ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert success", children: t('manualRestartRequired', { version: state.restart.toVersion }) }) : null, snapshot.runtime.lastError === undefined ? null : (0, jsx_runtime_1.jsxs)("div", { className: "dvt-alert error", children: [(0, jsx_runtime_1.jsx)("strong", { children: runtimeErrorTitle }), (0, jsx_runtime_1.jsx)("span", { children: snapshot.runtime.lastError })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel dvt-essential", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-panel-title", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { children: t('provider') }), (0, jsx_runtime_1.jsx)("p", { children: t('providerHint') })] }), (0, jsx_runtime_1.jsx)("span", { className: `dvt-badge ${snapshot.credential.configured ? 'ok' : 'error'}`, children: snapshot.credential.configured ? t('configured') : t('missing') })] }), (0, jsx_runtime_1.jsx)("p", { className: "dvt-tutorial-link", children: (0, jsx_runtime_1.jsx)("a", { href: aihubmixTutorialUrl, target: "_blank", rel: "noreferrer", children: t('aihubmixTutorial') }) }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('protocol'), children: (0, jsx_runtime_1.jsxs)("select", { disabled: !snapshot.writable || busy, value: draft.protocol, onChange: (event) => { update('protocol', event.target.value); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "openai", children: "OpenAI Chat Completions" }), (0, jsx_runtime_1.jsx)("option", { value: "anthropic", children: "Anthropic Messages" })] }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('baseUrl'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { disabled: !snapshot.writable || busy, value: draft.baseUrl, onChange: (event) => { update('baseUrl', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('model'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { disabled: !snapshot.writable || busy, value: draft.model, onChange: (event) => { update('model', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('apiKey'), hint: keyLocked ? t('apiKeyLocked') : snapshot.credential.source === undefined ? t('apiKeyHint') : `${t('apiKeyHint')} ${t('sourceHint', { source: t('source'), value: credentialSource(snapshot.credential.source, t) })}`, children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('apiKey'), type: "password", autoComplete: "new-password", disabled: busy || keyLocked, placeholder: snapshot.credential.configured ? t('apiKeyPlaceholderConfigured') : t('apiKeyPlaceholderMissing'), value: apiKey, onChange: (event) => { setApiKey(event.target.value); setDraftError(undefined); } }) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-save-row", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "primary", disabled: !canSave || busy, onClick: save, children: state.action === 'save' ? t('saving') : t('save') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", disabled: busy, onClick: () => { void controller.load(); }, children: t('reload') })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-panel-title", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { children: t('health') }), (0, jsx_runtime_1.jsx)("p", { children: t('connectionHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-actions", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "outline", disabled: busy || !snapshot.runtime.ready, onClick: () => { void controller.runHealth('health'); }, children: state.action === 'health' ? t('testing') : t('runHealth') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "outline", disabled: busy || !snapshot.runtime.ready, onClick: () => { void controller.runHealth('connection'); }, children: state.action === 'connection' ? t('testing') : t('testConnection') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "primary", disabled: busy || !snapshot.runtime.ready, onClick: () => { void controller.runHealth('model'); }, children: state.action === 'model' ? t('testingModel') : t('testModel') })] })] }), (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('saveBeforeTesting') }), state.health === undefined ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('notTested') }) : (0, jsx_runtime_1.jsx)("div", { className: "dvt-health-grid", children: Object.entries(state.health.checks).map(([name, check]) => {
1153
1153
  const testTag = name === 'model' ? modelTestTag(state.health, check) : undefined;
1154
1154
  return (0, jsx_runtime_1.jsxs)("div", { "data-status": check.status, children: [(0, jsx_runtime_1.jsx)("span", { children: t(HEALTH_NAME_KEYS[name] ?? 'health') }), testTag === undefined ? null : (0, jsx_runtime_1.jsx)("em", { className: "dvt-health-test-tag", "data-status": testTag.status, children: t(testTag.label) }), (0, jsx_runtime_1.jsx)("strong", { children: t(HEALTH_STATUS_KEYS[check.status]) }), (0, jsx_runtime_1.jsx)("p", { children: healthDetail(name, check.detail, t) })] }, name);
1155
1155
  }) })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel dvt-update-panel", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-panel-title", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { children: t('updates') }), (0, jsx_runtime_1.jsx)("p", { children: t('updatesHint') })] }), (0, jsx_runtime_1.jsx)("span", { className: `dvt-badge ${pluginUpdate?.updateAvailable ? 'warning' : pluginUpdate !== undefined && pluginUpdate.supported ? 'ok' : ''}`, children: pluginUpdate?.updateAvailable ? t('updateAvailable') : pluginUpdate !== undefined && pluginUpdate.supported ? t('upToDate') : t('pluginVersion') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-update-grid", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { children: t('updateInstalled') }), (0, jsx_runtime_1.jsx)("strong", { children: snapshot.release.pluginVersion })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { children: t('updateLatest') }), (0, jsx_runtime_1.jsx)("strong", { children: latestVersion ?? '—' })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { children: t('updateProfile') }), (0, jsx_runtime_1.jsx)("strong", { children: updateCapability.profile ?? '—' })] })] }), !updateCapability.supported ? (0, jsx_runtime_1.jsxs)("div", { className: "dvt-alert warning", children: [(0, jsx_runtime_1.jsx)("strong", { children: t('updateUnsupported') }), (0, jsx_runtime_1.jsx)("span", { children: updateReason })] }) : null, updateCapability.supported && updateHasUnsavedChanges ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert warning", children: t('updateSaveFirst') }) : null, pluginUpdate?.supported && pluginUpdate.updateAvailable && latestVersion !== undefined ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('updateAvailableDetail', { version: latestVersion }) }) : null, pluginUpdate?.supported && !pluginUpdate.updateAvailable && latestVersion !== undefined ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('upToDateDetail', { version: latestVersion }) }) : null, (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('manualUpdateHint') }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-manual-update", children: [(0, jsx_runtime_1.jsx)("code", { children: manualUpdateCommand }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "outline", onClick: copyManualUpdate, children: copiedCommand ? t('copied') : t('copy') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-actions", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", disabled: busy || !updateCheckSupported || state.restart !== undefined, onClick: () => { void controller.checkUpdate(); }, children: state.action === 'check-update' ? t('checkingUpdate') : t('checkUpdate') }), pluginUpdate?.supported && pluginUpdate.updateAvailable && latestVersion !== undefined ? (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "primary", disabled: busy || state.restart !== undefined || updateHasUnsavedChanges, onClick: applyUpdate, children: state.action === 'apply-update' ? t('updatingPlugin') : t('updateNow') }) : null] })] }), (0, jsx_runtime_1.jsxs)("details", { className: "dvt-advanced", children: [(0, jsx_runtime_1.jsxs)("summary", { children: [(0, jsx_runtime_1.jsxs)("span", { children: [(0, jsx_runtime_1.jsx)("strong", { children: t('advanced') }), (0, jsx_runtime_1.jsx)("small", { children: t('advancedHint') })] }), (0, jsx_runtime_1.jsx)("span", { className: "dvt-details-chevron", "aria-hidden": "true", children: "\u2304" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-advanced-body", children: [(0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "dvt-panel-title", children: (0, jsx_runtime_1.jsx)("h3", { children: t('provider') }) }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('credential'), hint: t('credentialHint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('credential'), disabled: !snapshot.writable || busy, value: draft.credential, onChange: (event) => { update('credential', event.target.value); } }) }), draft.protocol === 'anthropic' ? (0, jsx_runtime_1.jsx)(Field, { label: t('anthropicThinking'), hint: t('anthropicThinkingHint'), children: (0, jsx_runtime_1.jsxs)("select", { "aria-label": t('anthropicThinking'), value: draft.anthropicThinking, onChange: (event) => { update('anthropicThinking', event.target.value); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "omit", children: "omit (widest compatibility)" }), (0, jsx_runtime_1.jsx)("option", { value: "disabled", children: "disabled (model support required)" }), (0, jsx_runtime_1.jsx)("option", { value: "adaptive", children: "adaptive (model support required)" })] }) }) : null, (0, jsx_runtime_1.jsx)(Field, { label: t('userAgent'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { value: draft.userAgent, onChange: (event) => { update('userAgent', event.target.value); } }) })] })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "dvt-panel-title", children: (0, jsx_runtime_1.jsx)("h3", { children: t('limits') }) }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('language'), children: (0, jsx_runtime_1.jsxs)("select", { value: draft.language, onChange: (event) => { update('language', event.target.value); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "zh", children: "\u4E2D\u6587" }), (0, jsx_runtime_1.jsx)("option", { value: "en", children: "English" })] }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('timeout'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { inputMode: "numeric", value: draft.timeoutMs, onChange: (event) => { update('timeoutMs', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('maxBytes'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { inputMode: "numeric", value: draft.maxImageBytes, onChange: (event) => { update('maxImageBytes', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('maxPixels'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { inputMode: "numeric", value: draft.maxImagePixels, onChange: (event) => { update('maxImagePixels', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('concurrency'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { inputMode: "numeric", value: draft.concurrency, onChange: (event) => { update('concurrency', event.target.value); } }) })] })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "dvt-panel-title", children: (0, jsx_runtime_1.jsx)("h3", { children: t('imageInput') }) }), (0, jsx_runtime_1.jsxs)("label", { className: "dvt-check", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: draft.hiddenVariants, disabled: !snapshot.writable || busy, onChange: (event) => { update('hiddenVariants', event.target.checked); } }), (0, jsx_runtime_1.jsx)("span", { children: t('hiddenVariantsLabel') }), (0, jsx_runtime_1.jsx)("small", { children: t('hiddenVariantsHint') })] })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-panel-title", children: [(0, jsx_runtime_1.jsx)("h3", { children: t('runtime') }), (0, jsx_runtime_1.jsx)("span", { className: `dvt-badge ${snapshot.runtime.ready ? 'ok' : 'error'}`, children: snapshot.runtime.ready ? snapshot.runtime.upstream?.source === 'managed' ? t('runtimeManaged') : snapshot.runtime.upstream?.source === 'external' ? t('runtimeExternal') : t('runtimeReady') : t('runtimeUnavailable') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('runtimeMode'), children: (0, jsx_runtime_1.jsxs)("select", { value: draft.runtimeMode, onChange: (event) => { update('runtimeMode', event.target.value); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "managed", children: t('runtimeManaged') }), (0, jsx_runtime_1.jsx)("option", { value: "external", children: t('runtimeExternal') })] }) }), draft.runtimeMode === 'external' ? (0, jsx_runtime_1.jsx)(Field, { label: t('toolkitPath'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { value: draft.toolkitPath, onChange: (event) => { update('toolkitPath', event.target.value); } }) }) : null, (0, jsx_runtime_1.jsx)(Field, { label: t('python'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { placeholder: "python3", value: draft.python, onChange: (event) => { update('python', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('allowedDirs'), hint: t('allowedDirsHint'), children: (0, jsx_runtime_1.jsx)("textarea", { rows: 3, value: draft.allowedDirs, onChange: (event) => { update('allowedDirs', event.target.value); } }) })] }), snapshot.runtime.upstream === undefined ? null : (0, jsx_runtime_1.jsxs)("div", { className: "dvt-runtime-facts", children: [(0, jsx_runtime_1.jsx)("code", { children: snapshot.runtime.upstream.path }), (0, jsx_runtime_1.jsxs)("code", { children: [snapshot.runtime.upstream.python, " \u00B7 ", snapshot.runtime.upstream.pythonVersion] }), (0, jsx_runtime_1.jsx)("code", { children: snapshot.runtime.upstream.runtimeHome })] })] })] })] }), (0, jsx_runtime_1.jsxs)("footer", { className: "dvt-settings-footer", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { className: "dvt-kicker", children: t('pluginKind') }), (0, jsx_runtime_1.jsx)("h2", { children: t('settingsTitle') }), (0, jsx_runtime_1.jsx)("p", { children: t('settingsIntro') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-release", children: [(0, jsx_runtime_1.jsxs)("span", { children: [t('pluginVersion'), " ", (0, jsx_runtime_1.jsx)("strong", { children: snapshot.release.pluginVersion })] }), (0, jsx_runtime_1.jsxs)("span", { children: [t('upstreamVersion'), " ", (0, jsx_runtime_1.jsx)("strong", { children: snapshot.release.upstreamVersion })] }), (0, jsx_runtime_1.jsxs)("span", { children: [t('activeGeneration'), " ", (0, jsx_runtime_1.jsx)("strong", { children: t('activeGenerationValue', { generation: snapshot.runtime.generation }) })] })] })] })] }));