bingocode 1.0.18 → 1.0.20

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.
@@ -30,8 +30,9 @@ const FIXED_APIFMT = 'openai_chat';
30
30
 
31
31
  const ProvidersMenu: React.FC = () => {
32
32
  const { exit } = useApp();
33
+ const [modelOptions, setModelOptions] = useState<string[]>([]);
33
34
  const [modelSelectIdx, setModelSelectIdx] = useState(0);
34
- const [addStep, setAddStep] = useState(0); // 0:选模型, 1:填key
35
+ const [addStep, setAddStep] = useState(0); // 0:输入key并加载模型, 1:选模型
35
36
  const [inputKey, setInputKey] = useState('');
36
37
  const [addError, setAddError] = useState('');
37
38
  const [mode, setMode] = useState<UiMode>('list');
@@ -44,6 +45,9 @@ const ProvidersMenu: React.FC = () => {
44
45
  const [msg, setMsg] = useState<string>('');
45
46
  const [detail, setDetail] = useState<SavedProvider | null>(null);
46
47
  const [presets, setPresets] = useState<any[]>([]);
48
+ // 临时存储当前正在配置的 Provider 草案
49
+ const [pendingProvider, setPendingProvider] = useState<CreateProviderInput | null>(null);
50
+
47
51
  useEffect(() => { ProviderManager.listPresets().then(setPresets).catch(()=>setPresets([])); }, []);
48
52
 
49
53
  const refresh = async () => {
@@ -54,7 +58,14 @@ const ProvidersMenu: React.FC = () => {
54
58
  };
55
59
  useEffect(() => { refresh(); }, []);
56
60
  useEffect(() => {
57
- if (mode !== 'add') { setAddStep(0); setInputKey(''); setAddError(''); setModelSelectIdx(0); }
61
+ if (mode !== 'add') {
62
+ setAddStep(0);
63
+ setInputKey('');
64
+ setAddError('');
65
+ setModelSelectIdx(0);
66
+ setModelOptions([]);
67
+ setPendingProvider(null);
68
+ }
58
69
  }, [mode]);
59
70
 
60
71
  // 主界面 LIST 模式
@@ -96,33 +107,55 @@ const ProvidersMenu: React.FC = () => {
96
107
  setMode('list'); setAddError(''); setInputKey(''); setAddStep(0);
97
108
  return;
98
109
  }
99
- // 步骤0: 主模型选择
100
- if (addStep === 0) {
101
- if (key.downArrow) setModelSelectIdx(idx => Math.min(MODEL_OPTIONS.length - 1, idx + 1));
110
+ // 步骤1: 选模型
111
+ if (addStep === 1 && modelOptions.length > 0) {
112
+ if (key.downArrow) setModelSelectIdx(idx => Math.min(modelOptions.length - 1, idx + 1));
102
113
  else if (key.upArrow) setModelSelectIdx(idx => Math.max(0, idx - 1));
103
- else if (key.return) { setAddStep(1); }
114
+ else if (key.return) {
115
+ completeAdd(modelOptions[modelSelectIdx]);
116
+ }
104
117
  }
105
118
  });
106
119
 
107
- // 新增表单
108
- const addSubmit = async (keyInput: string) => {
109
- const selectedModel = MODEL_OPTIONS[modelSelectIdx];
110
- const presetId = selectedModel.replace(/[^a-zA-Z0-9]/g, '') + '-preset';
111
- const name = `${selectedModel} Provider`;
120
+ // 获取模型并进入下一步
121
+ const fetchModelsStep = async (keyInput: string) => {
112
122
  if (!keyInput.trim()) { setAddError('API Key 不能为空'); return; }
113
- const exists = (await ProviderManager.listProviders()).some(p => p.id === presetId);
114
- if (exists) { setAddError('该模型已添加过,如要更换Key请在列表编辑。'); return; }
115
- setAddError('');
116
- // 支持预设覆盖
117
- const overrideBase = (global as any).__PM_BASEURL_OVERRIDE__ || FIXED_BASEURL;
118
- const overrideFmt = (global as any).__PM_APIFMT_OVERRIDE__ || FIXED_APIFMT;
123
+ setAddError('正在获取模型列表...');
124
+ try {
125
+ // 如果没有 pendingProvider,手动创建一个(比如 Custom 模式)
126
+ const draft = pendingProvider || {
127
+ presetId: 'custom-' + Date.now(),
128
+ name: 'Custom Provider',
129
+ baseUrl: FIXED_BASEURL,
130
+ apiFormat: FIXED_APIFMT,
131
+ apiKey: keyInput.trim(),
132
+ models: { main: '', haiku: '', sonnet: '', opus: '' }
133
+ };
134
+
135
+ const models = await ProviderManager.fetchModels(draft, keyInput.trim());
136
+ if (models.length === 0) {
137
+ setAddError('未获取到可用模型,请检查 API Key 或网络连通性。');
138
+ return;
139
+ }
140
+ setModelOptions(models);
141
+ setPendingProvider({ ...draft, apiKey: keyInput.trim() });
142
+ setAddStep(1);
143
+ setAddError('');
144
+ } catch (e: any) {
145
+ setAddError('获取模型失败: ' + (e.message || '未知错误'));
146
+ }
147
+ };
148
+
149
+ const completeAdd = async (selectedModel: string) => {
150
+ if (!pendingProvider) return;
151
+ const name = pendingProvider.name || `${selectedModel} Provider`;
152
+
119
153
  await ProviderManager.addProvider({
120
- presetId, name, apiKey: keyInput.trim(),
121
- baseUrl: overrideBase, apiFormat: overrideFmt,
154
+ ...pendingProvider,
155
+ name,
122
156
  models: { main: selectedModel, haiku: '', sonnet: '', opus: '' }
123
157
  });
124
- delete (global as any).__PM_BASEURL_OVERRIDE__;
125
- delete (global as any).__PM_APIFMT_OVERRIDE__;
158
+
126
159
  setMode('list');
127
160
  setAddStep(0);
128
161
  setInputKey('');
@@ -153,25 +186,25 @@ const ProvidersMenu: React.FC = () => {
153
186
  {/* 新增 */}
154
187
  {mode === 'add' && (
155
188
  <Box flexDirection="column">
156
- <Text>选择模型与输入API Key:</Text>
157
189
  {addStep === 0
158
190
  ? <>
159
- {MODEL_OPTIONS.map((m, idx) => (
160
- <Text key={m} color={idx === modelSelectIdx ? 'yellow' : undefined}>
161
- {idx === modelSelectIdx ? '> ' : ' '}{m}
162
- </Text>
163
- ))}
164
- <Text color="gray">↑↓选择,回车下一步,q返回</Text>
165
- </>
166
- : <>
167
- <Text>已选模型:{MODEL_OPTIONS[modelSelectIdx]}</Text>
191
+ <Text>请输入 API Key (将自动拉取可用模型):</Text>
168
192
  <TextInput
169
193
  value={inputKey}
170
194
  onChange={setInputKey}
171
- onSubmit={addSubmit}
195
+ onSubmit={fetchModelsStep}
172
196
  placeholder="请输入API Key"
173
197
  />
174
- <Text color="gray">输入后回车提交,q返回</Text>
198
+ <Text color="gray">输入后回车继续,q返回</Text>
199
+ </>
200
+ : <>
201
+ <Text>获取成功!请选择主模型:</Text>
202
+ {modelOptions.map((m, idx) => (
203
+ <Text key={m} color={idx === modelSelectIdx ? 'yellow' : undefined}>
204
+ {idx === modelSelectIdx ? '> ' : ' '}{m}
205
+ </Text>
206
+ ))}
207
+ <Text color="gray">↑↓选择,回车保存,q返回</Text>
175
208
  </>
176
209
  }
177
210
  {addError && <Text color="red">{addError}</Text>}
@@ -259,17 +292,14 @@ const ProvidersMenu: React.FC = () => {
259
292
  <SelectInput
260
293
  items={presets.map((p: any) => ({ label: `${p.name || p.id} ${p.baseUrl || ''}`, value: p.id }))}
261
294
  onSelect={async it => {
262
- const p = presets.find((x: any) => x.id === it.value);
263
- if (!p) return;
264
- const model = (p.defaultModel || MODEL_OPTIONS[0]);
265
- const presetBase = p.baseUrl || FIXED_BASEURL;
266
- const presetFmt = p.apiFormat || FIXED_APIFMT;
267
- const idx = Math.max(0, MODEL_OPTIONS.findIndex(m => m === model));
268
- setModelSelectIdx(idx);
269
- (global as any).__PM_BASEURL_OVERRIDE__ = presetBase;
270
- (global as any).__PM_APIFMT_OVERRIDE__ = presetFmt;
271
- setMode('add');
272
- setAddStep(1); // 直接跳到 Key 输入
295
+ try {
296
+ const draft = await ProviderManager.applyPreset(it.value);
297
+ setPendingProvider(draft);
298
+ setMode('add');
299
+ setAddStep(0); // 去输入 Key
300
+ } catch (e: any) {
301
+ setMsg('预设加载失败: ' + e.message);
302
+ }
273
303
  }}
274
304
  />
275
305
  <Text color="gray">回车选择预设,q 返回</Text>
@@ -1,207 +1,207 @@
1
- version: 2
2
-
3
- # Provider 预设配置
4
- # fields 数组声明新增时需填写的字段
5
- # key: 'name' | 'apiKey' | 'baseUrl' 直接映射到顶层字段,其余存入 extra.<key>
6
- # secret: true 时前端使用密码掩码显示
7
- #
8
- # modelsUrl: 相对于 baseUrl 的模型列表路径,空字符串表示不支持动态拉取
9
- # modelsAuthStyle: bearer → Authorization: Bearer <apiKey>
10
- # x-api-key → x-api-key: <apiKey> + anthropic-version header
11
- # modelsDataPath: 响应 JSON 中模型数组的字段名(几乎总是 'data')
12
-
13
- presets:
14
- - id: official
15
- name: Claude Official
16
- baseUrl: ''
17
- apiFormat: anthropic
18
- needsApiKey: false
19
- websiteUrl: https://www.anthropic.com/claude-code
20
- modelsUrl: /v1/models
21
- modelsAuthStyle: x-api-key
22
- modelsDataPath: data
23
- fields:
24
- - key: name
25
- label: Provider 昵称
26
- required: true
27
- secret: false
28
- placeholder: 'e.g. Claude Official'
29
-
30
- - id: openai
31
- name: OpenAI
32
- baseUrl: https://api.openai.com/v1
33
- apiFormat: openai_chat
34
- needsApiKey: true
35
- websiteUrl: https://platform.openai.com
36
- modelsUrl: /v1/models
37
- modelsAuthStyle: bearer
38
- modelsDataPath: data
39
- fields:
40
- - key: name
41
- label: Provider 昵称
42
- required: true
43
- secret: false
44
- placeholder: 'e.g. My OpenAI'
45
- - key: apiKey
46
- label: API Key
47
- required: true
48
- secret: true
49
- placeholder: 'sk-...'
50
- - key: baseUrl
51
- label: Base URL (Optional)
52
- required: false
53
- secret: false
54
- default: https://api.openai.com/v1
55
- placeholder: 'https://api.openai.com/v1'
56
-
57
- - id: gemini
58
- name: Google Gemini
59
- baseUrl: https://generativelanguage.googleapis.com/v1beta/openai
60
- apiFormat: openai_chat
61
- needsApiKey: true
62
- websiteUrl: https://aistudio.google.com
63
- modelsUrl: /v1/models
64
- modelsAuthStyle: bearer
65
- modelsDataPath: data
66
- fields:
67
- - key: name
68
- label: Provider 昵称
69
- required: true
70
- secret: false
71
- placeholder: 'e.g. My Gemini'
72
- - key: apiKey
73
- label: API Key
74
- required: true
75
- secret: true
76
- placeholder: 'Gemini API Key'
77
-
78
- - id: mistral
79
- name: Mistral AI
80
- baseUrl: https://api.mistral.ai/v1
81
- apiFormat: openai_chat
82
- needsApiKey: true
83
- websiteUrl: https://console.mistral.ai
84
- modelsUrl: /v1/models
85
- modelsAuthStyle: bearer
86
- modelsDataPath: data
87
- fields:
88
- - key: name
89
- label: Provider 昵称
90
- required: true
91
- secret: false
92
- placeholder: 'e.g. My Mistral'
93
- - key: apiKey
94
- label: API Key
95
- required: true
96
- secret: true
97
- placeholder: 'Mistral API Key'
98
-
99
- - id: deepseek
100
- name: DeepSeek
101
- baseUrl: https://api.deepseek.com/anthropic
102
- apiFormat: anthropic
103
- needsApiKey: true
104
- websiteUrl: https://platform.deepseek.com
105
- modelsUrl: /v1/models
106
- modelsAuthStyle: bearer
107
- modelsDataPath: data
108
- fields:
109
- - key: name
110
- label: Provider 昵称
111
- required: true
112
- secret: false
113
- placeholder: 'e.g. My DeepSeek'
114
- - key: apiKey
115
- label: API Key
116
- required: true
117
- secret: true
118
- placeholder: 'sk-...'
119
-
120
- - id: zhipuglm
121
- name: Zhipu GLM
122
- baseUrl: https://open.bigmodel.cn/api/anthropic
123
- apiFormat: anthropic
124
- needsApiKey: true
125
- websiteUrl: https://open.bigmodel.cn
126
- modelsUrl: /v1/models
127
- modelsAuthStyle: bearer
128
- modelsDataPath: data
129
- fields:
130
- - key: name
131
- label: Provider 昵称
132
- required: true
133
- secret: false
134
- placeholder: 'e.g. My GLM'
135
- - key: apiKey
136
- label: API Key
137
- required: true
138
- secret: true
139
- placeholder: '智谱 API Key'
140
-
141
- - id: kimi
142
- name: Kimi
143
- baseUrl: https://api.moonshot.cn/anthropic
144
- apiFormat: anthropic
145
- needsApiKey: true
146
- websiteUrl: https://platform.moonshot.cn
147
- modelsUrl: /v1/models
148
- modelsAuthStyle: bearer
149
- modelsDataPath: data
150
- fields:
151
- - key: name
152
- label: Provider 昵称
153
- required: true
154
- secret: false
155
- placeholder: 'e.g. My Kimi'
156
- - key: apiKey
157
- label: API Key
158
- required: true
159
- secret: true
160
- placeholder: 'Moonshot API Key'
161
-
162
- - id: minimax
163
- name: MiniMax
164
- baseUrl: https://api.minimaxi.com/anthropic
165
- apiFormat: anthropic
166
- needsApiKey: true
167
- websiteUrl: https://platform.minimaxi.com
168
- modelsUrl: /v1/models
169
- modelsAuthStyle: bearer
170
- modelsDataPath: data
171
- fields:
172
- - key: name
173
- label: Provider 昵称
174
- required: true
175
- secret: false
176
- placeholder: 'e.g. My MiniMax'
177
- - key: apiKey
178
- label: API Key
179
- required: true
180
- secret: true
181
- placeholder: 'MiniMax API Key'
182
-
183
- - id: custom
184
- name: Custom
185
- baseUrl: ''
186
- apiFormat: openai_chat
187
- needsApiKey: true
188
- websiteUrl: ''
189
- modelsUrl: /v1/models
190
- modelsAuthStyle: bearer
191
- modelsDataPath: data
192
- fields:
193
- - key: name
194
- label: Provider 昵称
195
- required: true
196
- secret: false
197
- placeholder: 'e.g. My Custom Provider'
198
- - key: baseUrl
199
- label: Base URL
200
- required: true
201
- secret: false
202
- placeholder: 'https://your-api-endpoint.com/v1'
203
- - key: apiKey
204
- label: API Key
205
- required: false
206
- secret: true
207
- placeholder: '(可选)API Key'
1
+ version: 2
2
+
3
+ # Provider 预设配置
4
+ # fields 数组声明新增时需填写的字段
5
+ # key: 'name' | 'apiKey' | 'baseUrl' 直接映射到顶层字段,其余存入 extra.<key>
6
+ # secret: true 时前端使用密码掩码显示
7
+ #
8
+ # modelsUrl: 相对于 baseUrl 的模型列表路径,空字符串表示不支持动态拉取
9
+ # modelsAuthStyle: bearer → Authorization: Bearer <apiKey>
10
+ # x-api-key → x-api-key: <apiKey> + anthropic-version header
11
+ # modelsDataPath: 响应 JSON 中模型数组的字段名(几乎总是 'data')
12
+
13
+ presets:
14
+ - id: official
15
+ name: Claude Official
16
+ baseUrl: ''
17
+ apiFormat: anthropic
18
+ needsApiKey: false
19
+ websiteUrl: https://www.anthropic.com/claude-code
20
+ modelsUrl: /v1/models
21
+ modelsAuthStyle: x-api-key
22
+ modelsDataPath: data
23
+ fields:
24
+ - key: name
25
+ label: Provider 昵称
26
+ required: true
27
+ secret: false
28
+ placeholder: 'e.g. Claude Official'
29
+
30
+ - id: openai
31
+ name: OpenAI
32
+ baseUrl: https://api.openai.com/v1
33
+ apiFormat: openai_chat
34
+ needsApiKey: true
35
+ websiteUrl: https://platform.openai.com
36
+ modelsUrl: /v1/models
37
+ modelsAuthStyle: bearer
38
+ modelsDataPath: data
39
+ fields:
40
+ - key: name
41
+ label: Provider 昵称
42
+ required: true
43
+ secret: false
44
+ placeholder: 'e.g. My OpenAI'
45
+ - key: apiKey
46
+ label: API Key
47
+ required: true
48
+ secret: true
49
+ placeholder: 'sk-...'
50
+ - key: baseUrl
51
+ label: Base URL (Optional)
52
+ required: false
53
+ secret: false
54
+ default: https://api.openai.com/v1
55
+ placeholder: 'https://api.openai.com/v1'
56
+
57
+ - id: gemini
58
+ name: Google Gemini
59
+ baseUrl: https://generativelanguage.googleapis.com/v1beta/openai
60
+ apiFormat: openai_chat
61
+ needsApiKey: true
62
+ websiteUrl: https://aistudio.google.com
63
+ modelsUrl: /v1/models
64
+ modelsAuthStyle: bearer
65
+ modelsDataPath: data
66
+ fields:
67
+ - key: name
68
+ label: Provider 昵称
69
+ required: true
70
+ secret: false
71
+ placeholder: 'e.g. My Gemini'
72
+ - key: apiKey
73
+ label: API Key
74
+ required: true
75
+ secret: true
76
+ placeholder: 'Gemini API Key'
77
+
78
+ - id: mistral
79
+ name: Mistral AI
80
+ baseUrl: https://api.mistral.ai/v1
81
+ apiFormat: openai_chat
82
+ needsApiKey: true
83
+ websiteUrl: https://console.mistral.ai
84
+ modelsUrl: /v1/models
85
+ modelsAuthStyle: bearer
86
+ modelsDataPath: data
87
+ fields:
88
+ - key: name
89
+ label: Provider 昵称
90
+ required: true
91
+ secret: false
92
+ placeholder: 'e.g. My Mistral'
93
+ - key: apiKey
94
+ label: API Key
95
+ required: true
96
+ secret: true
97
+ placeholder: 'Mistral API Key'
98
+
99
+ - id: deepseek
100
+ name: DeepSeek
101
+ baseUrl: https://api.deepseek.com
102
+ apiFormat: openai_chat
103
+ needsApiKey: true
104
+ websiteUrl: https://platform.deepseek.com
105
+ modelsUrl: /v1/models
106
+ modelsAuthStyle: bearer
107
+ modelsDataPath: data
108
+ fields:
109
+ - key: name
110
+ label: Provider 昵称
111
+ required: true
112
+ secret: false
113
+ placeholder: 'e.g. My DeepSeek'
114
+ - key: apiKey
115
+ label: API Key
116
+ required: true
117
+ secret: true
118
+ placeholder: 'sk-...'
119
+
120
+ - id: zhipuglm
121
+ name: Zhipu GLM
122
+ baseUrl: https://open.bigmodel.cn/api/paas/v4
123
+ apiFormat: openai_chat
124
+ needsApiKey: true
125
+ websiteUrl: https://open.bigmodel.cn
126
+ modelsUrl: /models
127
+ modelsAuthStyle: bearer
128
+ modelsDataPath: data
129
+ fields:
130
+ - key: name
131
+ label: Provider 昵称
132
+ required: true
133
+ secret: false
134
+ placeholder: 'e.g. My GLM'
135
+ - key: apiKey
136
+ label: API Key
137
+ required: true
138
+ secret: true
139
+ placeholder: '智谱 API Key'
140
+
141
+ - id: kimi
142
+ name: Kimi
143
+ baseUrl: https://api.moonshot.cn/v1
144
+ apiFormat: openai_chat
145
+ needsApiKey: true
146
+ websiteUrl: https://platform.moonshot.cn
147
+ modelsUrl: /models
148
+ modelsAuthStyle: bearer
149
+ modelsDataPath: data
150
+ fields:
151
+ - key: name
152
+ label: Provider 昵称
153
+ required: true
154
+ secret: false
155
+ placeholder: 'e.g. My Kimi'
156
+ - key: apiKey
157
+ label: API Key
158
+ required: true
159
+ secret: true
160
+ placeholder: 'Moonshot API Key'
161
+
162
+ - id: minimax
163
+ name: MiniMax
164
+ baseUrl: https://api.minimaxi.com/v1
165
+ apiFormat: openai_chat
166
+ needsApiKey: true
167
+ websiteUrl: https://platform.minimaxi.com
168
+ modelsUrl: /models
169
+ modelsAuthStyle: bearer
170
+ modelsDataPath: data
171
+ fields:
172
+ - key: name
173
+ label: Provider 昵称
174
+ required: true
175
+ secret: false
176
+ placeholder: 'e.g. My MiniMax'
177
+ - key: apiKey
178
+ label: API Key
179
+ required: true
180
+ secret: true
181
+ placeholder: 'MiniMax API Key'
182
+
183
+ - id: custom
184
+ name: Custom
185
+ baseUrl: ''
186
+ apiFormat: openai_chat
187
+ needsApiKey: true
188
+ websiteUrl: ''
189
+ modelsUrl: /v1/models
190
+ modelsAuthStyle: bearer
191
+ modelsDataPath: data
192
+ fields:
193
+ - key: name
194
+ label: Provider 昵称
195
+ required: true
196
+ secret: false
197
+ placeholder: 'e.g. My Custom Provider'
198
+ - key: baseUrl
199
+ label: Base URL
200
+ required: true
201
+ secret: false
202
+ placeholder: 'https://your-api-endpoint.com/v1'
203
+ - key: apiKey
204
+ label: API Key
205
+ required: false
206
+ secret: true
207
+ placeholder: '(可选)API Key'