@ailuntz/minilink 0.9.0 → 0.9.3
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 +34 -22
- package/bin/minilink.mjs +6 -2
- package/examples/providers/.env.example +3 -0
- package/examples/providers/README.md +13 -0
- package/examples/providers/deepseek-models.json +83 -0
- package/examples/providers/deepseek.config.toml +9 -0
- package/examples/providers/doubao-models.json +68 -0
- package/examples/providers/doubao.config.toml +9 -0
- package/examples/providers/sub2api.config.toml +8 -0
- package/package.json +2 -1
- package/src/picker.mjs +11 -3
- package/src/proxy.mjs +14 -1
- package/src/terminal.mjs +10 -8
package/README.md
CHANGED
|
@@ -1,46 +1,58 @@
|
|
|
1
|
-
#
|
|
1
|
+
# MiniLink
|
|
2
2
|
|
|
3
|
-
在 ChatGPT / Codex Desktop
|
|
3
|
+
在 ChatGPT / Codex Desktop 的原生模型菜单中使用自定义模型。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 定位与支持周期
|
|
6
|
+
|
|
7
|
+
官方 [Profiles](https://learn.chatgpt.com/docs/config-file/config-advanced?site_locale=en#profiles) 已支持独立配置文件:`codex --profile deep-review` 会在 `~/.codex/config.toml` 上叠加 `~/.codex/deep-review.config.toml`。配置使用顶层字段,无需嵌套在 `[profiles.deep-review]` 中。
|
|
8
|
+
|
|
9
|
+
**截至 2026-09-24,仅添加 Profile 不会将额外供应商的模型加入 Desktop 菜单。MiniLink 读取这些配置,补充桌面端的模型选择能力。**
|
|
10
|
+
|
|
11
|
+
MiniLink 定位为过渡方案:在官方提供等效的原生菜单支持前维护这一能力;官方支持完善后,优先迁移至原生方案,结束对应兼容功能的维护。兼容性以已验证的 Desktop 版本为准。
|
|
6
12
|
|
|
7
13
|
## 安装
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
需 Node.js ≥22.16,并已安装 Desktop。
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
| 平台 | 接入状态 |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| macOS | 支持 npm 自动安装 |
|
|
20
|
+
| Windows / Linux | 已完成手动接入验证,npm 自动安装尚未支持 |
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
macOS:
|
|
16
23
|
|
|
17
24
|
```sh
|
|
25
|
+
npm install -g @ailuntz/minilink
|
|
18
26
|
minilink on
|
|
19
27
|
```
|
|
20
28
|
|
|
29
|
+
完全退出并重开 Desktop。
|
|
30
|
+
|
|
31
|
+
## 命令
|
|
32
|
+
|
|
21
33
|
| 命令 | 用途 |
|
|
22
34
|
| --- | --- |
|
|
23
|
-
| `on` |
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
35
|
+
| `minilink on` | 开启 |
|
|
36
|
+
| `minilink link` | 选择模型与推理强度 |
|
|
37
|
+
| `minilink off` | 关闭 |
|
|
38
|
+
| `minilink doctor` | 检查配置与运行状态 |
|
|
39
|
+
|
|
40
|
+
通过 `minilink link` 保存选择后,完全退出并重开 Desktop。
|
|
41
|
+
|
|
42
|
+
## 配置
|
|
26
43
|
|
|
27
|
-
|
|
44
|
+
读取 `~/.codex/*.config.toml`。通过 `model_catalog_json` 指定模型目录;未指定时继承原生目录。密钥使用提供商配置的环境变量,也可保存在 `~/.codex/.env`。
|
|
28
45
|
|
|
29
|
-
|
|
46
|
+
支持 Responses API。符合条件的连接提供 `-Chat` 选项,用于纯文字聊天。
|
|
30
47
|
|
|
31
|
-
|
|
48
|
+
供应商示例:[DeepSeek / 豆包 / Sub2API](examples/providers/)。修改配置后重启 Desktop。
|
|
32
49
|
|
|
33
50
|
## 卸载
|
|
34
51
|
|
|
52
|
+
macOS:
|
|
53
|
+
|
|
35
54
|
```sh
|
|
36
55
|
npm uninstall -g @ailuntz/minilink
|
|
37
56
|
```
|
|
38
57
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
## 注意
|
|
42
|
-
|
|
43
|
-
- 跨连接切换需要任务已保存且空闲;临时任务、尚无历史或含子代理的任务请新建。
|
|
44
|
-
- Chat 仅支持纯文字历史,不支持附件、中途追加和上下文压缩。
|
|
45
|
-
- 切换模型保留当前任务权限。
|
|
46
|
-
- 密钥使用提供商声明的环境变量,也可放在 `~/.codex/.env`。支持 `CODEX_HOME`。
|
|
58
|
+
卸载后完全退出并重开 Desktop。
|
package/bin/minilink.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { selectModel, text, language } from '../src/terminal.mjs';
|
|
|
9
9
|
import { installation, environmentStatus } from '../src/install.mjs';
|
|
10
10
|
|
|
11
11
|
const self = fileURLToPath(import.meta.url), args = process.argv.slice(2);
|
|
12
|
-
const commands = ['on', 'off', 'doctor'];
|
|
12
|
+
const commands = ['on', 'link', 'off', 'doctor'];
|
|
13
13
|
const output = value => console.log(typeof value === 'string' ? value : JSON.stringify(value, null, 2));
|
|
14
14
|
|
|
15
15
|
async function main() {
|
|
@@ -21,7 +21,11 @@ async function main() {
|
|
|
21
21
|
if (command === 'on' || command === 'off') {
|
|
22
22
|
if (command === 'on') discoverProfiles();
|
|
23
23
|
atomicJSON(configPath(), { version: 1, enabled: command === 'on' });
|
|
24
|
-
return output(
|
|
24
|
+
return output(text(command));
|
|
25
|
+
}
|
|
26
|
+
if (command === 'link') {
|
|
27
|
+
if (!readConfig().enabled) throw new Error(text('disabled'));
|
|
28
|
+
return output(await selectModel(pickerEndpoints(codexHome())));
|
|
25
29
|
}
|
|
26
30
|
if (command === 'doctor') {
|
|
27
31
|
const profiles = discoverProfiles(), env = childEnvironment(profiles);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 供应商配置
|
|
2
|
+
|
|
3
|
+
将所需配置放入 `~/.codex/`,按 `.env.example` 将密钥补充到 `~/.codex/.env`,然后重启 Desktop。DeepSeek 和豆包使用独立模型目录;Sub2API 继承 Desktop 的原生模型目录,只需一个配置文件。
|
|
4
|
+
|
|
5
|
+
| 供应商 | 模型 |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| DeepSeek | V4.1 Flash、V4 Pro |
|
|
8
|
+
| 豆包 | Seed 2.1 Pro、Seed 2.1 Lite(260915) |
|
|
9
|
+
| Sub2API | 继承原生模型目录 |
|
|
10
|
+
|
|
11
|
+
MiniLink 的 Chat 模式仅支持文字。
|
|
12
|
+
|
|
13
|
+
模型核对:2026-09-24。[DeepSeek](https://api-docs.deepseek.com/quick_start/pricing/) · [豆包](https://docs.volcengine.com/docs/ark/model-list?lang=zh)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"models": [
|
|
3
|
+
{
|
|
4
|
+
"slug": "deepseek-flash",
|
|
5
|
+
"display_name": "DeepSeek V4.1 Flash",
|
|
6
|
+
"description": "DeepSeek V4.1 Flash",
|
|
7
|
+
"default_reasoning_level": "low",
|
|
8
|
+
"supported_reasoning_levels": [
|
|
9
|
+
{
|
|
10
|
+
"effort": "low",
|
|
11
|
+
"description": "low"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"effort": "high",
|
|
15
|
+
"description": "high"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"effort": "max",
|
|
19
|
+
"description": "max"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"shell_type": "unified_exec",
|
|
23
|
+
"visibility": "list",
|
|
24
|
+
"supported_in_api": true,
|
|
25
|
+
"priority": 0,
|
|
26
|
+
"upgrade": null,
|
|
27
|
+
"base_instructions": "You are a helpful coding assistant. Follow the user instructions, inspect the workspace when needed, and explain results clearly.",
|
|
28
|
+
"support_verbosity": false,
|
|
29
|
+
"supports_reasoning_summaries": false,
|
|
30
|
+
"default_reasoning_summary": "none",
|
|
31
|
+
"supports_parallel_tool_calls": false,
|
|
32
|
+
"truncation_policy": {
|
|
33
|
+
"mode": "bytes",
|
|
34
|
+
"limit": 10000
|
|
35
|
+
},
|
|
36
|
+
"experimental_supported_tools": [],
|
|
37
|
+
"context_window": 1000000,
|
|
38
|
+
"input_modalities": [
|
|
39
|
+
"text",
|
|
40
|
+
"image"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"slug": "deepseek-v4-pro",
|
|
45
|
+
"display_name": "DeepSeek V4 Pro",
|
|
46
|
+
"description": "DeepSeek V4 Pro",
|
|
47
|
+
"default_reasoning_level": "low",
|
|
48
|
+
"supported_reasoning_levels": [
|
|
49
|
+
{
|
|
50
|
+
"effort": "low",
|
|
51
|
+
"description": "low"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"effort": "high",
|
|
55
|
+
"description": "high"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"effort": "max",
|
|
59
|
+
"description": "max"
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"shell_type": "unified_exec",
|
|
63
|
+
"visibility": "list",
|
|
64
|
+
"supported_in_api": true,
|
|
65
|
+
"priority": 0,
|
|
66
|
+
"upgrade": null,
|
|
67
|
+
"base_instructions": "You are a helpful coding assistant. Follow the user instructions, inspect the workspace when needed, and explain results clearly.",
|
|
68
|
+
"support_verbosity": false,
|
|
69
|
+
"supports_reasoning_summaries": false,
|
|
70
|
+
"default_reasoning_summary": "none",
|
|
71
|
+
"supports_parallel_tool_calls": false,
|
|
72
|
+
"truncation_policy": {
|
|
73
|
+
"mode": "bytes",
|
|
74
|
+
"limit": 10000
|
|
75
|
+
},
|
|
76
|
+
"experimental_supported_tools": [],
|
|
77
|
+
"context_window": 1000000,
|
|
78
|
+
"input_modalities": [
|
|
79
|
+
"text"
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"models": [
|
|
3
|
+
{
|
|
4
|
+
"slug": "doubao-seed-2-1-pro-260915",
|
|
5
|
+
"display_name": "Doubao Seed 2.1 Pro",
|
|
6
|
+
"description": "Doubao Seed 2.1 Pro",
|
|
7
|
+
"default_reasoning_level": "low",
|
|
8
|
+
"supported_reasoning_levels": [
|
|
9
|
+
{
|
|
10
|
+
"effort": "low",
|
|
11
|
+
"description": "low"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"shell_type": "unified_exec",
|
|
15
|
+
"visibility": "list",
|
|
16
|
+
"supported_in_api": true,
|
|
17
|
+
"priority": 0,
|
|
18
|
+
"upgrade": null,
|
|
19
|
+
"base_instructions": "You are a helpful coding assistant. Follow the user instructions, inspect the workspace when needed, and explain results clearly.",
|
|
20
|
+
"support_verbosity": false,
|
|
21
|
+
"supports_reasoning_summaries": false,
|
|
22
|
+
"default_reasoning_summary": "none",
|
|
23
|
+
"supports_parallel_tool_calls": false,
|
|
24
|
+
"truncation_policy": {
|
|
25
|
+
"mode": "bytes",
|
|
26
|
+
"limit": 10000
|
|
27
|
+
},
|
|
28
|
+
"experimental_supported_tools": [],
|
|
29
|
+
"context_window": 1048576,
|
|
30
|
+
"input_modalities": [
|
|
31
|
+
"text",
|
|
32
|
+
"image"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"slug": "doubao-seed-2-1-lite-260915",
|
|
37
|
+
"display_name": "Doubao Seed 2.1 Lite",
|
|
38
|
+
"description": "Doubao Seed 2.1 Lite",
|
|
39
|
+
"default_reasoning_level": "low",
|
|
40
|
+
"supported_reasoning_levels": [
|
|
41
|
+
{
|
|
42
|
+
"effort": "low",
|
|
43
|
+
"description": "low"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"shell_type": "unified_exec",
|
|
47
|
+
"visibility": "list",
|
|
48
|
+
"supported_in_api": true,
|
|
49
|
+
"priority": 0,
|
|
50
|
+
"upgrade": null,
|
|
51
|
+
"base_instructions": "You are a helpful coding assistant. Follow the user instructions, inspect the workspace when needed, and explain results clearly.",
|
|
52
|
+
"support_verbosity": false,
|
|
53
|
+
"supports_reasoning_summaries": false,
|
|
54
|
+
"default_reasoning_summary": "none",
|
|
55
|
+
"supports_parallel_tool_calls": false,
|
|
56
|
+
"truncation_policy": {
|
|
57
|
+
"mode": "bytes",
|
|
58
|
+
"limit": 10000
|
|
59
|
+
},
|
|
60
|
+
"experimental_supported_tools": [],
|
|
61
|
+
"context_window": 1048576,
|
|
62
|
+
"input_modalities": [
|
|
63
|
+
"text",
|
|
64
|
+
"image"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ailuntz/minilink",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "Use your Codex profiles in the Desktop model menu.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT AND Apache-2.0",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"files": [
|
|
14
14
|
"bin/",
|
|
15
15
|
"src/",
|
|
16
|
+
"examples/",
|
|
16
17
|
"README.md",
|
|
17
18
|
"LICENSE"
|
|
18
19
|
],
|
package/src/picker.mjs
CHANGED
|
@@ -10,8 +10,8 @@ import { additionalModels } from './model-menu.mjs';
|
|
|
10
10
|
// Keep the selected model when Desktop substitutes its reserve model at quota.
|
|
11
11
|
// A pending choice belongs to the next user task that starts or sends a message.
|
|
12
12
|
export class Picker {
|
|
13
|
-
constructor({ bridge, getConfig, serialize, chats = [] } = {}) {
|
|
14
|
-
Object.assign(this, { bridge, getConfig, serialize, chats });
|
|
13
|
+
constructor({ bridge, getConfig, serialize, configRpc, chats = [] } = {}) {
|
|
14
|
+
Object.assign(this, { bridge, getConfig, serialize, configRpc, chats });
|
|
15
15
|
this.pins = new Map(); this.pending = new Map(); this.next = null;
|
|
16
16
|
}
|
|
17
17
|
request(message) {
|
|
@@ -52,7 +52,8 @@ export class Picker {
|
|
|
52
52
|
async state() {
|
|
53
53
|
this.request(null);
|
|
54
54
|
const models = await this.catalog();
|
|
55
|
-
|
|
55
|
+
const { config } = await this.configRpc('config/read', { includeLayers: false });
|
|
56
|
+
return { next: this.next, selected: { model: config.model, effort: config.model_reasoning_effort }, models: models.map(m => ({ model: m.model, name: m.displayName || m.model,
|
|
56
57
|
effort: m.defaultReasoningEffort, efforts: m.supportedReasoningEfforts || [] })) };
|
|
57
58
|
}
|
|
58
59
|
async choose({ model, effort }) {
|
|
@@ -62,6 +63,13 @@ export class Picker {
|
|
|
62
63
|
const supported = entry.supportedReasoningEfforts || [];
|
|
63
64
|
effort = supported.length ? (effort ?? entry.defaultReasoningEffort) : null;
|
|
64
65
|
if (supported.length && !supported.some(e => e.reasoningEffort === effort)) throw new Error(text('invalidEffort'));
|
|
66
|
+
await this.configRpc('config/batchWrite', {
|
|
67
|
+
edits: [
|
|
68
|
+
{ keyPath: 'model', value: model, mergeStrategy: 'upsert' },
|
|
69
|
+
{ keyPath: 'model_reasoning_effort', value: effort, mergeStrategy: 'upsert' }
|
|
70
|
+
],
|
|
71
|
+
reloadUserConfig: true
|
|
72
|
+
});
|
|
65
73
|
this.next = { model, effort };
|
|
66
74
|
return { ok: true };
|
|
67
75
|
}
|
package/src/proxy.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { setting } from './legacy.mjs';
|
|
2
2
|
import { runtimeDirectory } from './runtime.mjs';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
3
4
|
import { spawn } from 'node:child_process';
|
|
4
5
|
import { join } from 'node:path';
|
|
5
6
|
import { rmSync, existsSync, readFileSync } from 'node:fs';
|
|
@@ -102,7 +103,19 @@ export async function runProxy(args, selfPath) {
|
|
|
102
103
|
catch { route.error = `Profile ${route.profile} 的模型目录无法加载,请检查 model_catalog_json。`; }
|
|
103
104
|
}
|
|
104
105
|
};
|
|
105
|
-
|
|
106
|
+
const configRpc = async (method, params) => {
|
|
107
|
+
// Use the same alias translation and saved provider identity as Desktop.
|
|
108
|
+
const id = `minilink-picker-${randomUUID()}`;
|
|
109
|
+
const request = menu.request({ id, method, params });
|
|
110
|
+
try {
|
|
111
|
+
const result = await switches.rpc(method, request.params);
|
|
112
|
+
return menu.response({ id, result }).result;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
menu.response({ id, error: { message: error.message } });
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
Object.assign(picker, { bridge: switches, getConfig, serialize, refreshCatalogs, configRpc });
|
|
106
119
|
for (const chat of chats) Object.assign(chat, { bridge: switches, menu });
|
|
107
120
|
present = async message => {
|
|
108
121
|
picker.response(message);
|
package/src/terminal.mjs
CHANGED
|
@@ -2,18 +2,19 @@ import { createInterface } from 'node:readline/promises';
|
|
|
2
2
|
|
|
3
3
|
export const language = (env = process.env) => /^zh(?:[_-]|$)/i.test(env.LC_ALL || env.LC_MESSAGES || env.LANG || '') ? 'zh' : 'en';
|
|
4
4
|
const messages = {
|
|
5
|
-
help: ['minilink\n\n on
|
|
5
|
+
help: ['minilink\n\n on 开启 MiniLink\n link 选择模型和推理强度\n off 关闭额外选项和模型接管\n doctor 检查配置和运行状态\n\n读取现有 profiles。其他 Codex 参数原样透传。', 'minilink\n\n on Enable MiniLink\n link Select a model and reasoning effort\n off Disable extra models and overrides\n doctor Check configuration and status\n\nUses existing profiles. Other arguments are forwarded to Codex.'],
|
|
6
|
+
on: ['minilink on 已开启,请完全退出并重开 Desktop。', 'MiniLink is enabled. Fully quit and reopen Desktop.'],
|
|
6
7
|
off: ['已关闭。已有任务保留当前提供商。', 'Disabled. Existing tasks keep their current provider.'],
|
|
7
|
-
unavailable: ['
|
|
8
|
-
interactive: ['
|
|
8
|
+
unavailable: ['未连接到 Desktop。请在运行 Desktop 的电脑上完全退出并重开 Desktop,然后运行 minilink link。', 'No Desktop connection. On the computer hosting Desktop, fully quit and reopen Desktop, then run minilink link.'],
|
|
9
|
+
interactive: ['请在交互终端运行 minilink link;SSH 请使用 ssh -t。', 'Run minilink link in an interactive terminal; use ssh -t over SSH.'],
|
|
9
10
|
multiple: ['检测到多个 Desktop 连接。请只保留一个连接后重试。', 'Multiple Desktop connections are running. Keep one connection open and try again.'],
|
|
10
11
|
model: ['模型', 'Model'], effort: ['推理强度', 'Reasoning effort'],
|
|
11
12
|
prompt: ['输入编号', 'Enter a number'], invalid: ['请输入列表中的编号。', 'Enter a number from the list.'],
|
|
12
13
|
cancelled: ['已取消,未更改模型。', 'Cancelled. Model unchanged.'],
|
|
13
|
-
done: ['
|
|
14
|
+
done: ['已保存到 config.toml。请完全退出并重开 Desktop,让模型菜单加载已保存的选择。', 'Saved to config.toml. Fully quit and reopen Desktop to load the saved selection in the model menu.'],
|
|
14
15
|
empty: ['暂无可用模型,请检查连接后重试。', 'No models available. Check your connection and try again.'],
|
|
15
|
-
disconnected: ['连接已结束,请重新运行 minilink
|
|
16
|
-
expired: ['连接已失效,请重新运行 minilink
|
|
16
|
+
disconnected: ['连接已结束,请重新运行 minilink link。', 'Connection closed. Run minilink link again.'],
|
|
17
|
+
expired: ['连接已失效,请重新运行 minilink link。', 'Connection expired. Run minilink link again.'],
|
|
17
18
|
catalogLarge: ['模型目录过大,请检查配置。', 'Model catalog is too large. Check your configuration.'],
|
|
18
19
|
disabled: ['请先运行 minilink on。', 'Run minilink on first.'],
|
|
19
20
|
missingModel: ['模型已不在目录中,请重新选择。', 'Model is no longer available. Select again.'],
|
|
@@ -44,6 +45,7 @@ export async function selectModel(endpoints, { input = process.stdin, output = p
|
|
|
44
45
|
};
|
|
45
46
|
const state = await api('/state');
|
|
46
47
|
if (!state.models.length) throw new Error(t('empty'));
|
|
48
|
+
const preferred = state.next ?? state.selected;
|
|
47
49
|
const rl = createInterface({ input, output });
|
|
48
50
|
const abort = new AbortController();
|
|
49
51
|
rl.on('SIGINT', () => abort.abort());
|
|
@@ -62,10 +64,10 @@ export async function selectModel(endpoints, { input = process.stdin, output = p
|
|
|
62
64
|
throw new DOMException('Cancelled', 'AbortError');
|
|
63
65
|
};
|
|
64
66
|
try {
|
|
65
|
-
const model = await select(t('model'), state.models.map(m => ({ value: m.model, label: m.name })),
|
|
67
|
+
const model = await select(t('model'), state.models.map(m => ({ value: m.model, label: m.name })), preferred?.model);
|
|
66
68
|
const entry = state.models.find(m => m.model === model);
|
|
67
69
|
const efforts = entry.efforts || [];
|
|
68
|
-
const effort = efforts.length > 1 ? await select(t('effort'), efforts.map(e => ({ value: e.reasoningEffort, label: t(e.reasoningEffort) })),
|
|
70
|
+
const effort = efforts.length > 1 ? await select(t('effort'), efforts.map(e => ({ value: e.reasoningEffort, label: t(e.reasoningEffort) })), preferred?.model === model ? preferred.effort : entry.effort) : efforts[0]?.reasoningEffort ?? null;
|
|
69
71
|
if (abort.signal.aborted) return t('cancelled');
|
|
70
72
|
await api('/choose', { model, effort });
|
|
71
73
|
return t('done');
|