Coverage for aipyapp/gui/providers.py: 0%
475 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-11 12:02 +0200
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-11 12:02 +0200
1#! /usr/bin/env python3
2# -*- coding: utf-8 -*-
4import requests
5from typing import List
6import time
7import webbrowser
8import threading
10import wx
11import wx.adv
12from loguru import logger
14from ..aipy.trustoken import TrustTokenAPI
15from .. import T
17class InitialProviderPage(wx.adv.WizardPage):
18 def __init__(self, parent, provider_config):
19 super().__init__(parent)
20 self.provider_config = provider_config
21 self.init_ui()
22 self.SetSize(800, 600)
24 def init_ui(self):
25 vbox = wx.BoxSizer(wx.VERTICAL)
27 # 标题
28 title = wx.StaticText(self, label=T('Select LLM Provider'))
29 title.SetFont(wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD))
30 vbox.Add(title, 0, wx.ALL | wx.ALIGN_CENTER, 10)
32 # Provider 选择
33 provider_box = wx.StaticBox(self, label=T('Provider'))
34 provider_sizer = wx.StaticBoxSizer(provider_box, wx.VERTICAL)
36 self.provider_choice = wx.Choice(
37 self,
38 choices=['Trustoken', T('Other')]
39 )
40 self.provider_choice.SetSelection(0) # 默认选中 TrustToken
41 self.provider_choice.Bind(wx.EVT_CHOICE, self.on_provider_selected)
42 provider_sizer.Add(self.provider_choice, 0, wx.EXPAND | wx.ALL, 5)
43 vbox.Add(provider_sizer, 0, wx.EXPAND | wx.ALL, 5)
45 # 提示信息
46 self.hint = wx.StaticText(self, label="")
47 # 设置文本自动换行
48 self.hint.Wrap(400)
49 vbox.Add(self.hint, 0, wx.ALL | wx.EXPAND, 10)
51 self.SetSizer(vbox)
52 # 设置初始提示信息
53 self.on_provider_selected(None)
55 def on_provider_selected(self, event):
56 provider = self.provider_choice.GetStringSelection()
57 if provider == 'Trustoken':
58 self.hint.SetLabel(f"""{T('Trustoken is an intelligent API Key management service')}:
601. {T('Auto get and manage API Key')}
612. {T('Support multiple LLM providers')}
623. {T('Auto select optimal model')}
634. {T('Recommended for beginners, easy to configure and feature-rich')}""")
64 else:
65 self.hint.SetLabel(f"""{T('Select other providers requires manual configuration')}:
671. {T('Need to apply for API Key yourself')}
682. {T('Manually input API Key')}
693. {T('Manually select model')}""")
70 # 重新计算换行
71 self.hint.Wrap(400)
73 def get_selection(self):
74 # 获取当前选择,如果没有选择则返回默认值
75 selection = self.provider_choice.GetStringSelection()
76 if not selection:
77 # 如果获取不到选择,使用 GetSelection() 获取索引
78 index = self.provider_choice.GetSelection()
79 if index >= 0:
80 selection = self.provider_choice.GetString(index)
81 else:
82 selection = 'Trustoken' # 如果索引也无效,返回默认值
83 return selection
85 def GetNext(self):
86 selection = self.get_selection()
87 if selection == "Trustoken":
88 return self.GetParent().trust_token_page
89 else:
90 return self.GetParent().provider_page
92 def GetPrev(self):
93 return None
95class TrustTokenPage(wx.adv.WizardPage):
96 def __init__(self, parent, provider_config):
97 super().__init__(parent)
98 self.provider_config = provider_config
99 self.api = TrustTokenAPI()
100 self.poll_interval = 5
101 self.request_id = None
102 self.polling_thread = None
103 self.stop_polling = False
104 self.start_time = None
105 self.polling_timeout = 310 # 5分钟10秒
106 self.init_ui()
107 self.SetSize(800, 600)
108 # 绑定页面切换事件
109 self.GetParent().Bind(wx.adv.EVT_WIZARD_PAGE_CHANGING, self.on_page_changing)
111 def init_ui(self):
112 vbox = wx.BoxSizer(wx.VERTICAL)
114 # 标题
115 title = wx.StaticText(self, label=T('Trustoken Configuration'))
116 title.SetFont(wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD))
117 vbox.Add(title, 0, wx.ALL | wx.ALIGN_CENTER, 10)
119 # API Key 显示
120 api_key_box = wx.StaticBox(self, label="API Key")
121 api_key_sizer = wx.StaticBoxSizer(api_key_box, wx.VERTICAL)
123 self.api_key_text = wx.TextCtrl(
124 self,
125 style=wx.TE_READONLY
126 )
127 api_key_sizer.Add(self.api_key_text, 1, wx.EXPAND | wx.ALL, 5)
128 vbox.Add(api_key_sizer, 0, wx.EXPAND | wx.ALL, 5)
130 # 获取按钮
131 self.fetch_button = wx.Button(self, label=T('Auto open browser to get API Key'))
132 self.fetch_button.Bind(wx.EVT_BUTTON, self.on_fetch)
133 vbox.Add(self.fetch_button, 0, wx.ALL | wx.ALIGN_CENTER, 10)
135 # 状态信息
136 self.status_text = wx.StaticText(self, label="")
137 vbox.Add(self.status_text, 0, wx.ALL | wx.EXPAND, 5)
139 self.url_ctrl = wx.adv.HyperlinkCtrl(self, label=T('You can also click this link to open browser to get API Key'), style=wx.adv.HL_ALIGN_LEFT | wx.adv.HL_CONTEXTMENU)
140 self.url_ctrl.Hide()
141 vbox.Add(self.url_ctrl, 0, wx.ALL, 5)
143 vbox.AddSpacer(20)
145 # 剩余时间文本
146 self.time_text = wx.StaticText(self, label='')
147 vbox.Add(self.time_text, 0, wx.EXPAND | wx.ALL, 5)
149 # 进度条
150 self.progress_bar = wx.Gauge(self, range=100)
151 vbox.Add(self.progress_bar, 0, wx.EXPAND | wx.ALL, 5)
153 self._toggle_progress(False)
154 self.SetSizer(vbox)
156 def _toggle_progress(self, show: bool):
157 self.progress_bar.Show(show)
158 self.time_text.Show(show)
160 def _update_progress(self):
161 if not self.start_time:
162 return
164 elapsed = time.time() - self.start_time
165 if elapsed >= self.polling_timeout:
166 progress = 100
167 time_remaining = 0
168 else:
169 progress = int((elapsed / self.polling_timeout) * 100)
170 time_remaining = int(self.polling_timeout - elapsed)
172 wx.CallAfter(self.progress_bar.SetValue, progress)
173 wx.CallAfter(self.time_text.SetLabel, f"{T('Remaining time')}: {time_remaining}{T('seconds')}")
175 def _poll_status(self, save_func):
176 self.start_time = time.time()
177 while not self.stop_polling and time.time() - self.start_time < self.polling_timeout:
178 self._update_progress()
180 data = self.api.check_status(self.request_id)
181 if not data:
182 time.sleep(self.poll_interval)
183 continue
185 status = data.get('status')
186 wx.CallAfter(self._update_status, f"{T('Current status')}: {status}")
188 if status == 'approved':
189 if save_func:
190 save_func(data['secret_token'])
191 wx.CallAfter(self._on_success)
192 return True
193 elif status == 'expired':
194 wx.CallAfter(self._update_status, T('Binding expired'))
195 wx.CallAfter(self._on_failure)
196 return False
197 elif status == 'pending':
198 pass
199 else:
200 wx.CallAfter(self._update_status, f"{T('Unknown status')}: {status}")
201 wx.CallAfter(self._on_failure)
202 return False
204 time.sleep(self.poll_interval)
206 if not self.stop_polling:
207 wx.CallAfter(self._update_status, T('Waiting timeout'))
208 wx.CallAfter(self._on_failure)
209 return False
211 def _update_status(self, status):
212 self.status_text.SetLabel(status)
214 def _on_success(self):
215 self._toggle_progress(False)
216 self.fetch_button.Enable()
217 self.status_text.SetLabel(T("API Key obtained successfully!"))
218 self.Layout() # 强制重新布局
220 def _on_failure(self):
221 self._toggle_progress(False)
222 self.fetch_button.Enable()
223 self.status_text.SetLabel(T('API Key acquisition failed, please try again'))
224 self.Layout() # 强制重新布局
226 def on_fetch(self, event):
227 self.status_text.SetLabel(T('Requesting binding'))
228 self.fetch_button.Disable()
230 data = self.api.request_binding()
231 if not data:
232 self._on_failure()
233 return
235 approval_url = data['approval_url']
236 self.request_id = data['request_id']
237 expires_in = data['expires_in']
238 self.polling_timeout = expires_in
239 self._update_status(T('Waiting for user confirmation'))
241 # 打开浏览器
242 self.url_ctrl.SetURL(approval_url)
243 self.url_ctrl.Show()
244 webbrowser.open(approval_url)
246 # 显示进度组并重新布局
247 self._toggle_progress(True)
248 self.Layout()
250 # 开始轮询
251 self.polling_thread = threading.Thread(
252 target=self._poll_status,
253 args=(self._save_token,)
254 )
255 self.polling_thread.daemon = True
256 self.polling_thread.start()
258 def _save_token(self, token):
259 self.api_key_text.SetValue(token)
261 def get_api_key(self):
262 return self.api_key_text.GetValue()
264 def on_page_changing(self, event):
265 # 如果是当前页面,且是向前切换
266 if event.GetPage() == self and event.GetDirection():
267 api_key = self.get_api_key()
268 if not api_key:
269 wx.MessageBox(T('Please get API Key first'), T('Hint'), wx.OK | wx.ICON_INFORMATION)
270 event.Veto()
271 return
272 event.Skip()
274 def GetNext(self):
275 return self.GetParent().model_page
277 def GetPrev(self):
278 return self.GetParent().initial_page
280class ProviderPage(wx.adv.WizardPage):
281 def __init__(self, parent, provider_config):
282 super().__init__(parent)
283 self.provider_config = provider_config
284 self.init_ui()
285 self.SetSize(800, 600)
286 self.GetParent().Bind(wx.adv.EVT_WIZARD_PAGE_CHANGING, self.on_page_changing)
288 def init_ui(self):
289 vbox = wx.BoxSizer(wx.VERTICAL)
291 # 标题
292 title = wx.StaticText(self, label=T('Select LLM Provider'))
293 title.SetFont(wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD))
294 vbox.Add(title, 0, wx.ALL | wx.ALIGN_CENTER, 10)
296 # Provider 选择
297 provider_box = wx.StaticBox(self, label=T('Provider'))
298 provider_sizer = wx.StaticBoxSizer(provider_box, wx.VERTICAL)
300 # 过滤掉 TrustToken
301 providers = [name for name in self.provider_config.providers.keys() if name != "Trustoken"]
302 self.provider_choice = wx.Choice(
303 self,
304 choices=providers
305 )
306 if providers: # 确保列表不为空
307 self.provider_choice.SetSelection(0) # 默认选中第一个选项
308 self.provider_choice.Bind(wx.EVT_CHOICE, self.on_provider_selected)
309 provider_sizer.Add(self.provider_choice, 0, wx.EXPAND | wx.ALL, 5)
310 vbox.Add(provider_sizer, 0, wx.EXPAND | wx.ALL, 5)
312 # API Key 输入
313 api_key_box = wx.StaticBox(self, label="API Key")
314 api_key_sizer = wx.StaticBoxSizer(api_key_box, wx.VERTICAL)
316 self.api_key_text = wx.TextCtrl(
317 self,
318 size=(400, -1)
319 )
320 api_key_sizer.Add(self.api_key_text, 0, wx.EXPAND | wx.ALL, 5)
321 vbox.Add(api_key_sizer, 0, wx.EXPAND | wx.ALL, 5)
323 # 提示信息
324 hint = wx.StaticText(self, label=T('Please select provider and input API Key, click Next to verify'))
325 hint.SetForegroundColour(wx.Colour(100, 100, 100))
326 vbox.Add(hint, 0, wx.ALL, 10)
328 self.SetSizer(vbox)
330 def on_provider_selected(self, event):
331 provider = self.provider_choice.GetStringSelection()
332 if provider in self.provider_config.config:
333 config = self.provider_config.config[provider]
334 self.api_key_text.SetValue(config["api_key"])
336 def get_provider(self):
337 return self.provider_choice.GetStringSelection()
339 def get_api_key(self):
340 return self.api_key_text.GetValue()
342 def GetNext(self):
343 return self.GetParent().model_page
345 def GetPrev(self):
346 return self.GetParent().initial_page
348 def on_page_changing(self, event):
349 # 如果是当前页面,且是向前切换
350 if event.GetPage() == self and event.GetDirection():
351 api_key = self.get_api_key()
352 if not api_key:
353 wx.MessageBox(T('Please get API Key first'), T('Hint'), wx.OK | wx.ICON_INFORMATION)
354 event.Veto()
355 return
356 event.Skip()
358class ModelPage(wx.adv.WizardPage):
359 def __init__(self, parent, provider_config):
360 super().__init__(parent)
361 self.provider_config = provider_config
362 self.prev_page = None # 添加一个变量来记录上一页
363 self.init_ui()
364 self.SetSize(800, 600)
366 def init_ui(self):
367 vbox = wx.BoxSizer(wx.VERTICAL)
369 # 标题
370 title = wx.StaticText(self, label=T('Select Model'))
371 title.SetFont(wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD))
372 vbox.Add(title, 0, wx.ALL | wx.ALIGN_CENTER, 10)
374 # Model 选择
375 model_box = wx.StaticBox(self, label=T('Available Models'))
376 model_sizer = wx.StaticBoxSizer(model_box, wx.VERTICAL)
378 self.model_choice = wx.Choice(self)
379 model_sizer.Add(self.model_choice, 0, wx.EXPAND | wx.ALL, 5)
380 vbox.Add(model_sizer, 0, wx.EXPAND | wx.ALL, 5)
382 # Max Tokens 配置
383 max_tokens_box = wx.StaticBox(self, label=T('Max Tokens'))
384 max_tokens_sizer = wx.StaticBoxSizer(max_tokens_box, wx.VERTICAL)
386 self.max_tokens_text = wx.TextCtrl(self, value="8192")
387 max_tokens_sizer.Add(self.max_tokens_text, 0, wx.EXPAND | wx.ALL, 5)
388 vbox.Add(max_tokens_sizer, 0, wx.EXPAND | wx.ALL, 5)
390 # 提示信息
391 self.hint = wx.StaticText(self, label=T('Please select model to use and configure parameters'))
392 self.hint.SetForegroundColour(wx.Colour(100, 100, 100))
393 vbox.Add(self.hint, 0, wx.ALL, 10)
395 self.SetSizer(vbox)
397 def set_models(self, models: List[str], selected_model: str = None, is_trust_token: bool = False):
398 if is_trust_token:
399 self.model_choice.SetItems(["auto"])
400 self.model_choice.SetSelection(0)
401 self.model_choice.Disable() # 禁用选择
402 self.hint.SetLabel(T('Trustoken uses automatic model selection'))
403 else:
404 self.model_choice.SetItems(models)
405 self.model_choice.Enable() # 启用选择
406 if selected_model and selected_model in models:
407 self.model_choice.SetStringSelection(selected_model)
408 elif models: # 如果有模型列表但没有指定选择的模型,默认选择第一个
409 self.model_choice.SetSelection(0)
410 self.hint.SetLabel(T('Please select model to use and configure parameters'))
412 def get_selected_model(self):
413 return self.model_choice.GetStringSelection()
415 def get_max_tokens(self):
416 try:
417 return int(self.max_tokens_text.GetValue())
418 except ValueError:
419 return 8192
421 def GetNext(self):
422 return None
424 def GetPrev(self):
425 return self.prev_page
427class OAuthPage(wx.adv.WizardPage):
428 def __init__(self, parent, provider_config):
429 super().__init__(parent)
430 self.provider_config = provider_config
431 self.prev_page = None
432 self.init_ui()
434 def init_ui(self):
435 vbox = wx.BoxSizer(wx.VERTICAL)
437 # 标题
438 title = wx.StaticText(self, label=T('OAuth Gateway Configuration'))
439 title.SetFont(wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD))
440 vbox.Add(title, 0, wx.ALL | wx.ALIGN_CENTER, 10)
442 # Client ID
443 client_id_box = wx.BoxSizer(wx.HORIZONTAL)
444 client_id_label = wx.StaticText(self, label=T('Client ID:'))
445 self.client_id_input = wx.TextCtrl(self)
446 client_id_box.Add(client_id_label, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
447 client_id_box.Add(self.client_id_input, 1, wx.ALL | wx.EXPAND, 5)
448 vbox.Add(client_id_box, 0, wx.EXPAND | wx.ALL, 5)
450 # Client Secret
451 client_secret_box = wx.BoxSizer(wx.HORIZONTAL)
452 client_secret_label = wx.StaticText(self, label=T('Client Secret:'))
453 self.client_secret_input = wx.TextCtrl(self, style=wx.TE_PASSWORD)
454 client_secret_box.Add(client_secret_label, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
455 client_secret_box.Add(self.client_secret_input, 1, wx.ALL | wx.EXPAND, 5)
456 vbox.Add(client_secret_box, 0, wx.EXPAND | wx.ALL, 5)
458 # Token URL
459 token_url_box = wx.BoxSizer(wx.HORIZONTAL)
460 token_url_label = wx.StaticText(self, label=T('Token URL:'))
461 self.token_url_input = wx.TextCtrl(self)
462 token_url_box.Add(token_url_label, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
463 token_url_box.Add(self.token_url_input, 1, wx.ALL | wx.EXPAND, 5)
464 vbox.Add(token_url_box, 0, wx.EXPAND | wx.ALL, 5)
466 # API Base URL
467 base_url_box = wx.BoxSizer(wx.HORIZONTAL)
468 base_url_label = wx.StaticText(self, label=T('API Base URL:'))
469 self.base_url_input = wx.TextCtrl(self)
470 base_url_box.Add(base_url_label, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
471 base_url_box.Add(self.base_url_input, 1, wx.ALL | wx.EXPAND, 5)
472 vbox.Add(base_url_box, 0, wx.EXPAND | wx.ALL, 5)
474 # Model Name
475 model_box = wx.BoxSizer(wx.HORIZONTAL)
476 model_label = wx.StaticText(self, label=T('Model Name:'))
477 self.model_input = wx.TextCtrl(self)
478 model_box.Add(model_label, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
479 model_box.Add(self.model_input, 1, wx.ALL | wx.EXPAND, 5)
480 vbox.Add(model_box, 0, wx.EXPAND | wx.ALL, 5)
482 # 测试按钮
483 test_box = wx.BoxSizer(wx.HORIZONTAL)
484 self.test_button = wx.Button(self, label=T('Test Connection'))
485 self.test_button.Bind(wx.EVT_BUTTON, self.on_test)
486 test_box.Add(self.test_button, 0, wx.ALL, 5)
487 self.test_result = wx.StaticText(self, label="")
488 test_box.Add(self.test_result, 1, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
489 vbox.Add(test_box, 0, wx.EXPAND | wx.ALL, 10)
491 self.SetSizer(vbox)
493 def get_client_id(self):
494 return self.client_id_input.GetValue().strip()
496 def get_client_secret(self):
497 return self.client_secret_input.GetValue().strip()
499 def get_token_url(self):
500 return self.token_url_input.GetValue().strip()
502 def get_base_url(self):
503 return self.base_url_input.GetValue().strip()
505 def get_model(self):
506 return self.model_input.GetValue().strip()
508 def on_test(self, event):
509 client_id = self.get_client_id()
510 client_secret = self.get_client_secret()
511 token_url = self.get_token_url()
512 base_url = self.get_base_url()
513 model = self.get_model()
515 if not client_id or not client_secret or not token_url or not base_url or not model:
516 self.test_result.SetLabel(T("Please fill in all fields"))
517 return
519 # Start testing in a separate thread to avoid UI freeze
520 threading.Thread(target=self._test_connection, args=(client_id, client_secret, token_url, base_url)).start()
521 self.test_result.SetLabel(T("Testing connection..."))
522 self.test_button.Enable(False)
524 def _test_connection(self, client_id, client_secret, token_url, base_url):
525 try:
526 # First try to get a token
527 response = requests.post(
528 token_url,
529 data={
530 "grant_type": "client_credentials",
531 "client_id": client_id,
532 "client_secret": client_secret
533 },
534 timeout=10
535 )
536 response.raise_for_status()
537 token_data = response.json()
538 token = token_data.get("access_token")
540 if not token:
541 wx.CallAfter(self._update_test_result, False, "No token received")
542 return
544 # Now try to access the models endpoint
545 models_url = f"{base_url}/models"
546 response = requests.get(
547 models_url,
548 headers={"Authorization": f"Bearer {token}"},
549 timeout=10
550 )
551 response.raise_for_status()
553 # If we get here, the connection is working
554 wx.CallAfter(self._update_test_result, True)
555 except Exception as e:
556 wx.CallAfter(self._update_test_result, False, str(e))
558 def _update_test_result(self, success, error=None):
559 if success:
560 self.test_result.SetLabel(T("✓ Connection successful"))
561 self.test_result.SetForegroundColour(wx.Colour(0, 128, 0))
562 else:
563 self.test_result.SetLabel(f"✗ {error}")
564 self.test_result.SetForegroundColour(wx.Colour(255, 0, 0))
565 self.test_button.Enable(True)
567 def GetNext(self):
568 # In the wizard flow, this should go directly to the model page
569 # but for OAuth we have the model defined here
570 wizard = self.GetParent()
571 return wizard.model_page
573 def GetPrev(self):
574 return self.prev_page
576class ProviderConfigWizard(wx.adv.Wizard):
577 def __init__(self, llm_config, parent):
578 super().__init__(parent, title=T('LLM Provider Configuration Wizard'))
579 self.provider_config = llm_config
580 self.init_ui()
581 self.SetPageSize((600, 400))
582 self.Centre()
583 self.log = logger.bind(src="wizard")
585 def init_ui(self):
586 # 创建向导页面
587 self.initial_page = InitialProviderPage(self, self.provider_config)
588 self.trust_token_page = TrustTokenPage(self, self.provider_config)
589 self.provider_page = ProviderPage(self, self.provider_config)
590 self.model_page = ModelPage(self, self.provider_config)
591 self.oauth_page = OAuthPage(self, self.provider_config)
593 # 绑定事件
594 self.Bind(wx.adv.EVT_WIZARD_PAGE_CHANGED, self.on_page_changed)
595 self.Bind(wx.adv.EVT_WIZARD_FINISHED, self.on_finished)
597 wx.CallAfter(self._set_button_labels)
599 def _set_button_labels(self):
600 btn_next = self.FindWindowById(wx.ID_FORWARD)
601 btn_back = self.FindWindowById(wx.ID_BACKWARD)
602 btn_cancel = self.FindWindowById(wx.ID_CANCEL)
604 if btn_next: btn_next.SetLabel(T('Next'))
605 if btn_back: btn_back.SetLabel(T('Back'))
606 if btn_cancel: btn_cancel.SetLabel(T('Cancel'))
608 def on_page_changed(self, event):
609 btn_next = self.FindWindowById(wx.ID_FORWARD)
610 if btn_next: btn_next.SetLabel(T('Next'))
612 if event.GetPage() == self.model_page:
613 if btn_next: btn_next.SetLabel(T('Finish'))
614 # 从第一步进入第二步时,验证 API Key 并获取模型列表
615 provider = self.provider_page.get_provider()
616 api_key = self.provider_page.get_api_key()
617 if not provider or not api_key:
618 self.model_page.prev_page = self.trust_token_page
619 provider = "Trustoken"
620 api_key = self.trust_token_page.get_api_key()
621 # 对于 TrustToken,直接设置模型为 auto
622 self.model_page.set_models([], None, is_trust_token=True)
623 else:
624 self.model_page.prev_page = self.provider_page
625 models = self.get_models(provider, api_key)
626 if not models:
627 event.Veto()
628 return
630 # 设置模型列表
631 selected_model = None
632 if provider in self.provider_config.config:
633 selected_model = self.provider_config.config[provider].get("selected_model")
634 self.model_page.set_models(models, selected_model)
635 elif event.GetPage() == self.trust_token_page:
636 self.trust_token_page.prev_page = self.initial_page
637 elif event.GetPage() == self.provider_page:
638 self.provider_page.prev_page = self.initial_page
639 elif event.GetPage() == self.initial_page:
640 self.initial_page.prev_page = None
641 elif event.GetPage() == self.oauth_page:
642 self.oauth_page.prev_page = self.initial_page
644 def on_finished(self, event):
645 # 根据 model_page 的 prev_page 来判断是从哪个路径来的
646 if self.model_page.prev_page == self.trust_token_page:
647 provider = "Trustoken"
648 api_key = self.trust_token_page.get_api_key()
649 else:
650 provider = self.provider_page.get_provider()
651 api_key = self.provider_page.get_api_key()
653 selected_model = self.model_page.get_selected_model()
654 max_tokens = self.model_page.get_max_tokens()
655 provider_info = self.provider_config.providers[provider]
657 config = self.provider_config.config
658 config[provider] = {
659 "api_key": api_key,
660 "models": self.model_page.model_choice.GetItems(),
661 "model": selected_model,
662 "max_tokens": max_tokens,
663 "type": provider_info["type"],
664 "enable": True
665 }
667 self.provider_config.save_config(config)
668 wx.MessageBox(T('Configuration saved'), T('Success'), wx.OK | wx.ICON_INFORMATION)
670 def get_models(self, provider: str, api_key: str) -> List[str]:
671 provider_info = self.provider_config.providers[provider]
672 headers = {
673 "Content-Type": "application/json"
674 }
676 if provider == "Claude":
677 headers["x-api-key"] = api_key
678 headers["anthropic-version"] = "2023-06-01"
679 else:
680 headers["Authorization"] = f"Bearer {api_key}"
682 try:
683 response = requests.get(
684 f"{provider_info['api_base']}{provider_info['models_endpoint']}",
685 headers=headers
686 )
687 self.log.info(f"获取模型列表: {response.text}")
688 if response.status_code == 200:
689 data = response.json()
690 self.log.info(f"获取模型列表成功: {data}")
691 if provider in ["OpenAI", "DeepSeek", "xAI", "Claude"]:
692 return [model["id"] for model in data["data"]]
693 elif provider == "Gemini":
694 return [model["name"] for model in data["models"]]
695 # 其他 provider 的模型解析逻辑
696 return []
697 except Exception as e:
698 wx.MessageBox(f"{T('Model list acquisition failed')}: {str(e)}", T('Error'), wx.OK | wx.ICON_ERROR)
699 return []
701def show_provider_config(llm_config, parent=None):
702 wizard = ProviderConfigWizard(llm_config, parent)
703 wizard.RunWizard(wizard.initial_page)
704 wizard.Destroy()
705 return wx.ID_OK