From 12c5c1730114957d1790738c927939eee35c9679 Mon Sep 17 00:00:00 2001
From: Raylan LIN
Date: Fri, 2 Oct 2026 02:02:30 +0000
Subject: [PATCH 1/3] =?UTF-8?q?feat:=20P132=20providers=20=C3=97=20protoco?=
=?UTF-8?q?ls=20in=20Settings,=20deepseek-flash,=20installer=20on=20the=20?=
=?UTF-8?q?Actions=20run=20page?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Settings: presets are organised by provider (PROVIDERS), each with an OpenAI-compatible
and/or Anthropic-compatible URL. The quick-fill row shows under both protocols and fills
the provider's URL for the selected protocol; the model dropdown lists only the active
provider's models + Custom; switching protocol keeps the provider when it serves both
(e.g. https://api.deepseek.com <-> https://api.deepseek.com/anthropic) and keeps the
model when that provider lists it.
Model ids: DeepSeek V4.1 Flash is deepseek-flash (deepseek-v4-flash retired);
kimi-k3 and qwen3.8-max added and suggested. Anthropic-compatible URLs added for
DeepSeek, Kimi, MiniMax, GLM, Qwen and Ollama.
Build: the Actions artifacts are now the Setup installer and the zip, uploaded unzipped
(archive: false) instead of the win-unpacked folder that GitHub served as a zip; a
manual "Run workflow" also packages, verifies and attaches the installer (no Release).
Docs: README / README.zh-CN provider tables list both URLs per provider; CHANGELOG.
Tests: provider resolution, per-provider model list, protocol switch, quick-fill.
197 JS + 58 Python tests pass; typecheck, lint, ruff, compileall, renderer build OK.
---
.github/workflows/build.yml | 44 ++--
CHANGELOG.md | 54 +++++
README.md | 34 +--
README.zh-CN.md | 36 +--
package.json | 2 +-
src/renderer/components/SettingsModal.tsx | 75 +++----
src/renderer/i18n/strings.ts | 6 +-
src/shared/presets.ts | 259 ++++++++++++++++++----
tests/presets.test.mjs | 61 +++++
9 files changed, 430 insertions(+), 141 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index fe92115..a4aaa4b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -3,7 +3,10 @@ name: Build Windows Installer
on:
push:
tags: ['v*']
- workflow_dispatch: # 允许手动触发
+ # P132: a manual "Run workflow" now builds the same Setup installer + zip and attaches
+ # them to the run (no Release). Before, the packaging steps were tag-only, so a manual
+ # run produced nothing to download.
+ workflow_dispatch:
permissions:
contents: write
@@ -70,19 +73,18 @@ jobs:
# vendor/python/ before electron-builder runs. Idempotent — safe to re-run.
# This is a build-time network op; end users have no network requirement.
- name: Prepare bundled Python runtime
- if: startsWith(github.ref, 'refs/tags/')
+ if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
shell: pwsh
run: ./scripts/prepare-python.ps1
- name: Build
run: npm run build
- # P110: NSIS setup retired (2026-08-04) — users found the installer repeatedly
- # unusable, so tag builds now ship ONLY the portable single-file exe.
- # npm run dist = prepare-python + build + electron-builder (target: portable
- # from electron-builder.yml) → release/Millwright-Portable--x64.exe
- - name: Package (portable / 免安装版)
- if: startsWith(github.ref, 'refs/tags/')
+ # npm run dist = prepare-python + build + electron-builder (targets from
+ # electron-builder.yml, P129): release/Millwright-Setup--x64.exe (NSIS
+ # installer, auto-update) + release/Millwright--x64.zip (extract-and-run).
+ - name: Package (Setup installer + zip)
+ if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
run: npm run dist
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -93,13 +95,15 @@ jobs:
# that class of accident impossible. It also confirms the installer filename carries
# the version from package.json (the old electron-builder cache bug).
- name: Verify packaged payload
- if: startsWith(github.ref, 'refs/tags/')
+ id: verify
+ if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$res = 'release/win-unpacked/resources'
$version = (Get-Content package.json -Raw | ConvertFrom-Json).version
Write-Host "package.json version: ${version}"
+ "version=${version}" >> $env:GITHUB_OUTPUT
$required = @(
"$res/sidecar/sw_agent/server.py",
@@ -151,16 +155,24 @@ jobs:
if ($y -notmatch [regex]::Escape($version)) { Write-Host "::error::latest.yml version mismatch"; exit 1 }
Write-Host "installer + latest.yml: OK"
- # P110 v3: extract-and-run zip (nsis + portable exe both retired).
- # npm run dist with the zip target produces release/*.zip containing the
- # full win-unpacked tree — download, extract, run Millwright.exe.
+ # P132: the run page offers the INSTALLER. The old artifact was release/win-unpacked/**,
+ # which GitHub serves as one zip that extracts to a bare app folder — easy to mistake
+ # for "the build has no installer". archive:false uploads each file as-is, so the
+ # artifact downloads as the .exe / .zip itself (the artifact is named after the file).
+ - name: Upload artifact (Setup installer)
+ if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
+ uses: actions/upload-artifact@v7
+ with:
+ path: release/Millwright-Setup-${{ steps.verify.outputs.version }}-x64.exe
+ archive: false
+ retention-days: 30
- - name: Upload artifact (portable directory)
- if: startsWith(github.ref, 'refs/tags/')
+ - name: Upload artifact (zip, extract-and-run)
+ if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v7
with:
- name: Millwright
- path: release/win-unpacked/**
+ path: release/Millwright-${{ steps.verify.outputs.version }}-x64.zip
+ archive: false
retention-days: 30
- name: Create Release
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 639368d..3c6f548 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,60 @@
## [Unreleased]
+## [0.2.131] - 2026-10-02
+
+### Changed (P132 — providers × protocols, installer on the run page)
+
+**Settings: providers now serve both protocols.** Most providers expose an
+OpenAI-compatible URL and an Anthropic-compatible one. DeepSeek, for example, serves
+`https://api.deepseek.com` and `https://api.deepseek.com/anthropic`.
+- **Quick-fill buttons, both protocols:** before, they only appeared under the OpenAI
+ protocol and always filled the OpenAI URL. They now appear under both protocols and fill
+ the URL **for the selected protocol**.
+- **Model list per provider:** the model list shows only the active provider's models plus
+ *Custom model*. Before, it listed every vendor's models under a protocol.
+- **Protocol switch:** switching protocol keeps the provider when it serves both, and
+ keeps the model if that provider lists it. Otherwise it falls back to the protocol's
+ official endpoint.
+- **Data:** presets are reorganised as `PROVIDERS` (per-protocol URLs, models, defaults).
+ New pure helpers `providerForURL` / `modelOptions` / `switchProtocol` /
+ `applyProviderPreset` are covered by tests. `MODEL_PRESETS` and
+ `OPENAI_COMPATIBLE_PROVIDERS` remain as derived lists.
+- **Anthropic-compatible URLs added:**
+ - DeepSeek `…/anthropic`
+ - Kimi `api.moonshot.cn/anthropic`
+ - MiniMax `api.minimax.io/anthropic`
+ - GLM `open.bigmodel.cn/api/anthropic`
+ - Qwen `dashscope.aliyuncs.com/apps/anthropic`
+ - Ollama `localhost:11434`
+
+**Model IDs**
+- **DeepSeek:** V4.1 Flash (2026-09-10) is `deepseek-flash`. DeepSeek retired
+ `deepseek-v4-flash`, which is now only routed over, so it is removed from the presets.
+ `deepseek-v4-pro` stays.
+- **Kimi:** `kimi-k3` is added and suggested.
+- **Qwen:** `qwen3.8-max` is added and suggested.
+
+**Build: the installer is on the run page.**
+- **What looked like "only a zip":** the Actions artifact was `release/win-unpacked/**`.
+ GitHub serves that as one zip which extracts to a bare app folder, so it was easy to read
+ as "the build has no installer". The Release itself did carry the Setup exe.
+- **Artifacts now:** they are the Setup installer and the zip, uploaded with
+ `archive: false`, so they download as the `.exe` / `.zip` themselves.
+- **Manual runs:** a manual **Run workflow** now packages and verifies too, and attaches
+ the installer to the run without creating a Release. Before, it only ran the checks.
+
+### Tests
+- **`tests/presets.test.mjs`:**
+ - every provider URL is valid
+ - suggested models are listed
+ - no retired DeepSeek id remains
+ - URL → provider resolution
+ - the per-provider model list
+ - protocol switching
+ - quick-fill by protocol
+- **Totals:** 197 JS + 58 Python tests.
+
## [0.2.130] - 2026-10-01
### Fixed (P131 — current models, and what P129/P130 broke)
diff --git a/README.md b/README.md
index d3b0b65..8b780e7 100644
--- a/README.md
+++ b/README.md
@@ -28,12 +28,12 @@
-
+
-
+
@@ -86,7 +86,7 @@ Millwright:
- **Agentic tool loop.** Observe → reason → act. The model chains multiple tool calls, reads structured JSON back from each one, and recovers from errors instead of failing silently.
- **Visual understanding.** Reorient, rotate, screenshot, and analyze the model — via a multimodal main model or a dedicated vision model.
- **Resident execution engine.** A persistent Python sidecar holds one COM connection open across an entire multi-step task.
-- **Developer-friendly.** 191 TypeScript/Node tests plus a Python suite (`pytest sidecar/tests`) for the sidecar, a typed IPC boundary, and a `SKIP_SW_CONNECT` mode for UI-only development without SolidWorks installed.
+- **Developer-friendly.** 197 TypeScript/Node tests plus a Python suite (`pytest sidecar/tests`) for the sidecar, a typed IPC boundary, and a `SKIP_SW_CONNECT` mode for UI-only development without SolidWorks installed.
## Cross-version compatibility
@@ -158,19 +158,21 @@ A `Millwright-*-x64.zip` is also published alongside the Setup installer for use
## Supported AI providers
-| Provider | Protocol | Base URL | Suggested model |
+| Provider | OpenAI-compatible URL | Anthropic-compatible URL | Suggested model |
|---|---|---|---|
-| OpenAI | OpenAI | `https://api.openai.com/v1` | `gpt-6-astra` (GPT-6 Astra) |
-| Anthropic | Anthropic | `https://api.anthropic.com` | `claude-opus-5-5` (Opus 5.5, default) / `claude-fable-5-1` (Fable 5.1, most capable) |
-| DeepSeek | OpenAI-compatible | `https://api.deepseek.com` | `deepseek-v4-pro` |
-| Kimi / Moonshot | OpenAI-compatible | `https://api.moonshot.cn/v1` | `kimi-k3` |
-| MiniMax | OpenAI-compatible | `https://api.minimaxi.com/v1` | `minimax-m3` |
-| Alibaba Bailian (Qwen) | OpenAI-compatible | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `qwen-3.8max` |
-| Zhipu (GLM) | OpenAI-compatible | `https://open.bigmodel.cn/api/paas/v4` | `glm-4.6` |
-| SiliconFlow | OpenAI-compatible | `https://api.siliconflow.cn/v1` | — |
-| Ollama (local) | OpenAI-compatible | `http://localhost:11434/v1` | — |
-
-> Model IDs move fast — check your provider's docs for the current lineup. Agentic tool calling requires a model that supports function calling; GPT-6 Astra, Claude Opus 5.5 / Fable 5.1, DeepSeek V4, Kimi K3, MiniMax M3, and GLM-4.6 are first-class targets.
+| OpenAI | `https://api.openai.com/v1` | — | `gpt-6-astra` (GPT-6 Astra) |
+| Anthropic | — | `https://api.anthropic.com` | `claude-opus-5-5` (Opus 5.5, default) / `claude-fable-5-1` (Fable 5.1, most capable) |
+| DeepSeek | `https://api.deepseek.com` | `https://api.deepseek.com/anthropic` | `deepseek-v4-pro` (strong) / `deepseek-flash` (V4.1 Flash, fast) |
+| Kimi / Moonshot | `https://api.moonshot.cn/v1` | `https://api.moonshot.cn/anthropic` | `kimi-k3` |
+| MiniMax | `https://api.minimax.io/v1` | `https://api.minimax.io/anthropic` | `minimax-m3` |
+| Alibaba Bailian (Qwen) | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `https://dashscope.aliyuncs.com/apps/anthropic` | `qwen3.8-max` |
+| Zhipu (GLM) | `https://open.bigmodel.cn/api/paas/v4` | `https://open.bigmodel.cn/api/anthropic` | `glm-4.6` |
+| SiliconFlow | `https://api.siliconflow.cn/v1` | — | — |
+| Ollama (local) | `http://localhost:11434/v1` | `http://localhost:11434` | — |
+
+> In ⚙️ Settings, pick the protocol first: the provider quick-fill buttons then fill that provider's URL **for the selected protocol**, and the model list shows only that provider's models (plus *Custom model*). Switching protocol keeps the provider when it serves both.
+
+> Model IDs move fast — check your provider's docs for the current lineup. Agentic tool calling requires a model that supports function calling; GPT-6 Astra, Claude Opus 5.5 / Fable 5.1, DeepSeek V4 Pro / V4.1 Flash, Kimi K3, MiniMax M3, and GLM-4.6 are first-class targets.
>
> The newest models reject request fields that older ones accepted: GPT-6 Astra needs `max_completion_tokens`, takes no `temperature`, and refuses `reasoning_effort` alongside tools on `/chat/completions`; Claude Opus 5.5 and Fable 5.1 reject `temperature` and `budget_tokens` and always think (depth is set with `effort`). Millwright detects these models by ID and sends the right fields — the **Reasoning depth** setting maps onto each model's own controls.
@@ -246,7 +248,7 @@ Contributions welcome — see [CONTRIBUTING.md](docs/CONTRIBUTING.md). We especi
- [x] **v0.1** — MVP: Electron shell, LLM adapters, COM bridge, first tool set
- [x] **v0.2** — Python sidecar, agentic tool loop, dual-engine fallback, vision feedback, confirmation cards, Apache-2.0 open source
-- [x] **v0.2.4 → v0.2.130** — Extensive hardening against real SolidWorks installs ← *current*: the sketch → feature → cut → visual-verification loop now runs end to end on real hardware
+- [x] **v0.2.4 → v0.2.131** — Extensive hardening against real SolidWorks installs ← *current*: the sketch → feature → cut → visual-verification loop now runs end to end on real hardware
- [ ] **v0.3** — Streaming tool calls, sketching on model faces (not just reference planes), hole wizard, sheet metal, drawing annotations, remaining `#VERIFY` parameters confirmed
- [ ] **v1.0** — MCP server, multi-CAD support
diff --git a/README.zh-CN.md b/README.zh-CN.md
index ddb3835..b182a89 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -27,12 +27,12 @@
-
+
-
+
@@ -85,7 +85,7 @@ Millwright:
- **Agent 工具循环。** 观察 → 推理 → 执行。模型串联多次工具调用,读取每次返回的结构化 JSON,出错能自愈而不是静默失败。
- **视觉理解。** 可翻转、旋转、截屏,再做分析——既支持多模态主模型,也支持独立视觉模型。
- **常驻执行引擎。** 常驻 Python 边车在一整个多步任务中复用同一条 COM 连接。
-- **开发者友好。** 191 个 TS/Node 单元测试,另有独立的 Python 测试套件(`pytest sidecar/tests`),类型化 IPC 边界,`SKIP_SW_CONNECT` 纯 UI 开发模式(无需 SolidWorks)。
+- **开发者友好。** 197 个 TS/Node 单元测试,另有独立的 Python 测试套件(`pytest sidecar/tests`),类型化 IPC 边界,`SKIP_SW_CONNECT` 纯 UI 开发模式(无需 SolidWorks)。
## 跨版本兼容
@@ -157,19 +157,21 @@ npm run dev
## 支持的 AI 服务商
-| 服务商 | 协议 | Base URL | 推荐模型 |
-| ----------- | --------- | --------------------------------------------------- | ----------------------------------- |
-| OpenAI | OpenAI | `https://api.openai.com/v1` | `gpt-6-astra`(GPT-6 Astra) |
-| Anthropic | Anthropic | `https://api.anthropic.com` | `claude-opus-5-5`(Opus 5.5,默认)/ `claude-fable-5-1`(Fable 5.1,最强) |
-| DeepSeek | OpenAI 兼容 | `https://api.deepseek.com` | `deepseek-v4-pro`(强) / `deepseek-v4-flash`(快) |
-| Kimi / 月之暗面 | OpenAI 兼容 | `https://api.moonshot.cn/v1` | `kimi-k2.5` |
-| MiniMax | OpenAI 兼容 | `https://api.minimaxi.com/v1` | `minimax-m3`(512K 上下文) |
-| 智谱 (GLM) | OpenAI 兼容 | `https://open.bigmodel.cn/api/paas/v4` | `glm-4.6` |
-| 阿里百炼 (Qwen) | OpenAI 兼容 | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `qwen3.7-max` |
-| 硅基流动 | OpenAI 兼容 | `https://api.siliconflow.cn/v1` | —(用户自填) |
-| Ollama(本地) | OpenAI 兼容 | `http://localhost:11434/v1` | —(用户自填) |
-
-> 各家型号更新很快,请以服务商官方文档为准。Agent 工具调用需要模型支持 function calling;GPT-6 Astra、Claude Opus 5.5 / Fable 5.1、DeepSeek V4、Kimi K2、MiniMax M3、GLM-4.6 是一等公民。
+| 服务商 | OpenAI 兼容地址 | Anthropic 兼容地址 | 推荐模型 |
+| --- | --- | --- | --- |
+| OpenAI | `https://api.openai.com/v1` | — | `gpt-6-astra`(GPT-6 Astra) |
+| Anthropic | — | `https://api.anthropic.com` | `claude-opus-5-5`(Opus 5.5,默认)/ `claude-fable-5-1`(Fable 5.1,最强) |
+| DeepSeek | `https://api.deepseek.com` | `https://api.deepseek.com/anthropic` | `deepseek-v4-pro`(强)/ `deepseek-flash`(V4.1 Flash,快) |
+| Kimi / 月之暗面 | `https://api.moonshot.cn/v1` | `https://api.moonshot.cn/anthropic` | `kimi-k3` |
+| MiniMax | `https://api.minimax.io/v1` | `https://api.minimax.io/anthropic` | `minimax-m3`(512K 上下文) |
+| 智谱 (GLM) | `https://open.bigmodel.cn/api/paas/v4` | `https://open.bigmodel.cn/api/anthropic` | `glm-4.6` |
+| 阿里百炼 (Qwen) | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `https://dashscope.aliyuncs.com/apps/anthropic` | `qwen3.8-max` |
+| 硅基流动 | `https://api.siliconflow.cn/v1` | — | —(用户自填) |
+| Ollama(本地) | `http://localhost:11434/v1` | `http://localhost:11434` | —(用户自填) |
+
+> 在 ⚙️ 设置里先选协议:服务商快捷按钮会填入该服务商**对应协议**的地址,模型下拉框只列出该服务商的模型(外加「自定义模型」)。切换协议时,如果该服务商两种协议都支持,会自动换成另一种协议的地址。
+
+> 各家型号更新很快,请以服务商官方文档为准。Agent 工具调用需要模型支持 function calling;GPT-6 Astra、Claude Opus 5.5 / Fable 5.1、DeepSeek V4 Pro / V4.1 Flash、Kimi K3、MiniMax M3、GLM-4.6 是一等公民。
>
> 新一代模型会拒绝老模型能接受的请求字段:GPT-6 Astra 要求 `max_completion_tokens`、不接受 `temperature`,并且在 `/chat/completions` 上不允许 `reasoning_effort` 与工具同时出现;Claude Opus 5.5 和 Fable 5.1 拒绝 `temperature` 和 `budget_tokens`,并且始终开启思考(深度用 `effort` 控制)。Millwright 按模型 ID 自动识别并发送正确的字段——设置里的「推理深度」会映射到各模型自己的控制参数上。OpenAI `gpt-5.x` / o 系列同样已自动识别。
@@ -245,7 +247,7 @@ SolidWorks
- [x] **v0.1** — MVP:Electron 骨架、LLM 适配器、COM 桥接、首批工具
- [x] **v0.2** — Python 边车、agent 工具循环、双引擎降级、视觉反馈、确认卡片,Apache-2.0 开源
-- [x] **v0.2.4 → v0.2.130** — 大量真机加固 ← *当前*:草图 → 特征 → 切除 → 视觉核验的完整闭环已在真机上端到端跑通
+- [x] **v0.2.4 → v0.2.131** — 大量真机加固 ← *当前*:草图 → 特征 → 切除 → 视觉核验的完整闭环已在真机上端到端跑通
- [ ] **v0.3** — 流式工具调用、在模型面上画草图(而非仅基准面)、孔向导、钣金、工程图标注、剩余 `# VERIFY` 参数完成核验
- [ ] **v1.0** — MCP server、多 CAD 支持
diff --git a/package.json b/package.json
index eab98c6..9d061e3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "millwright",
- "version": "0.2.130",
+ "version": "0.2.131",
"description": "Open-source AI automation for SolidWorks — talk to your CAD.",
"keywords": [
"solidworks",
diff --git a/src/renderer/components/SettingsModal.tsx b/src/renderer/components/SettingsModal.tsx
index 21f24a0..dea328a 100644
--- a/src/renderer/components/SettingsModal.tsx
+++ b/src/renderer/components/SettingsModal.tsx
@@ -8,7 +8,10 @@
import { useEffect, useMemo, useState } from 'react';
import type { LLMConfig, LLMErrorInfo, ThemeName, SWStatus } from '../../shared/types';
-import { DEFAULT_URLS, MODEL_PRESETS, OPENAI_COMPATIBLE_PROVIDERS } from '../../shared/presets';
+import {
+ DEFAULT_URLS, applyProviderPreset, modelOptions, providerForURL, providersFor, switchProtocol,
+ type ProviderPreset,
+} from '../../shared/presets';
import type { ThemeTokens } from '../themes';
import { useLocale, useT } from '../i18n/LocaleContext';
import { LOCALE_LABELS } from '../i18n/strings';
@@ -52,9 +55,11 @@ export function SettingsModal({
setDraft(config);
}, [config]);
- // Dropdown options: if the current model is not in the preset list, show it as "custom"
- const presets = MODEL_PRESETS[draft.protocol];
- const modelIsPreset = presets.some((p) => p.value === draft.model);
+ // Dropdown options: the base URL's provider's models + "custom" (P132); a model not in
+ // that list shows as "custom"
+ const presets = modelOptions(draft.protocol, draft.baseURL);
+ const activeProvider = providerForURL(draft.baseURL);
+ const modelIsPreset = presets.some((p) => p.value !== 'custom' && p.value === draft.model);
const selectValue = modelIsPreset ? draft.model : 'custom';
const customModel = modelIsPreset ? '' : draft.model;
@@ -74,29 +79,18 @@ export function SettingsModal({
});
};
+ // P132: switching protocol keeps the provider when it serves both (DeepSeek's OpenAI URL
+ // becomes its /anthropic URL) and keeps the model if that provider lists it; otherwise
+ // the protocol's official endpoint and its recommended model.
const handleProtocol = (p: 'anthropic' | 'openai') => {
- // P131: pair the default URL with a model that endpoint actually serves. The first
- // OpenAI-protocol preset is DeepSeek, so switching gave api.openai.com + deepseek-v4-pro.
- const official = OPENAI_COMPATIBLE_PROVIDERS.find((x) => x.url === DEFAULT_URLS[p]);
- setDraft((d) => ({
- ...d,
- protocol: p,
- baseURL: DEFAULT_URLS[p],
- model: official?.suggestedModel ?? MODEL_PRESETS[p][0].value,
- }));
+ setDraft((d) => ({ ...d, protocol: p, ...switchProtocol(d, p) }));
setTestStatus({ kind: 'idle' });
};
- // P131: a quick-fill button sets the whole provider, not just its URL — the suggested
- // model and the per-provider context / output defaults were declared but never applied.
- const applyProvider = (p: (typeof OPENAI_COMPATIBLE_PROVIDERS)[number]) => {
- setDraft((d) => ({
- ...d,
- baseURL: p.url,
- ...(p.suggestedModel ? { model: p.suggestedModel } : {}),
- ...(p.contextWindow ? { contextWindow: p.contextWindow } : {}),
- ...(p.maxTokens ? { maxTokens: p.maxTokens } : {}),
- }));
+ // P131/P132: a quick-fill button sets the whole provider — the URL for the CURRENT
+ // protocol, one of its models, and its context / output defaults.
+ const applyProvider = (p: ProviderPreset) => {
+ setDraft((d) => ({ ...d, ...applyProviderPreset(p, d.protocol, d.model) }));
setTestStatus({ kind: 'idle' });
};
@@ -302,34 +296,33 @@ export function SettingsModal({
placeholder={DEFAULT_URLS[draft.protocol]}
style={{ ...fieldStyle, marginBottom: 4 }}
/>
- {draft.protocol === 'openai' && (
-
-
- {tr('settings.quickFill')}
-
-
- {OPENAI_COMPATIBLE_PROVIDERS.map((p) => (
+ {/* P132: providers serving the selected protocol; each fills ITS URL for it */}
+