Coverage for aipyapp/gui/providers.py: 0%

1#! /usr/bin/env python3

2# -*- coding: utf-8 -*-

3

4 import requests

5 from typing import List

6 import time

7 import webbrowser

8 import threading

9

10 import wx

11 import wx.adv

12 from loguru import logger

13

14 from ..aipy.trustoken import TrustTokenAPI

15 from .. import T

16

17 class 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)

23

24 def init_ui(self):

25 vbox = wx.BoxSizer(wx.VERTICAL)

26

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)

31

32 # Provider 选择

33 provider_box = wx.StaticBox(self, label=T('Provider'))

34 provider_sizer = wx.StaticBoxSizer(provider_box, wx.VERTICAL)

35

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)

44

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)

50

51 self.SetSizer(vbox)

52 # 设置初始提示信息

53 self.on_provider_selected(None)

54

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')}：

59

60 1. {T('Auto get and manage API Key')}

61 2. {T('Support multiple LLM providers')}

62 3. {T('Auto select optimal model')}

63 4. {T('Recommended for beginners, easy to configure and feature-rich')}""")

64 else:

65 self.hint.SetLabel(f"""{T('Select other providers requires manual configuration')}：

66

67 1. {T('Need to apply for API Key yourself')}

68 2. {T('Manually input API Key')}

69 3. {T('Manually select model')}""")

70 # 重新计算换行

71 self.hint.Wrap(400)

72

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

84

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

91

92 def GetPrev(self):

93 return None

94

95 class 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)

110

111 def init_ui(self):

112 vbox = wx.BoxSizer(wx.VERTICAL)

113

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)

118

119 # API Key 显示

120 api_key_box = wx.StaticBox(self, label="API Key")

121 api_key_sizer = wx.StaticBoxSizer(api_key_box, wx.VERTICAL)

122

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)

129

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)

134

135 # 状态信息

136 self.status_text = wx.StaticText(self, label="")

137 vbox.Add(self.status_text, 0, wx.ALL | wx.EXPAND, 5)

138

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)

142

143 vbox.AddSpacer(20)

144

145 # 剩余时间文本

146 self.time_text = wx.StaticText(self, label='')

147 vbox.Add(self.time_text, 0, wx.EXPAND | wx.ALL, 5)

148

149 # 进度条

150 self.progress_bar = wx.Gauge(self, range=100)

151 vbox.Add(self.progress_bar, 0, wx.EXPAND | wx.ALL, 5)

152

153 self._toggle_progress(False)

154 self.SetSizer(vbox)

155

156 def _toggle_progress(self, show: bool):

157 self.progress_bar.Show(show)

158 self.time_text.Show(show)

159

160 def _update_progress(self):

161 if not self.start_time:

162 return

163

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)

171

172 wx.CallAfter(self.progress_bar.SetValue, progress)

173 wx.CallAfter(self.time_text.SetLabel, f"{T('Remaining time')}: {time_remaining}{T('seconds')}")

174

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()

179

180 data = self.api.check_status(self.request_id)

181 if not data:

182 time.sleep(self.poll_interval)

183 continue

184

185 status = data.get('status')

186 wx.CallAfter(self._update_status, f"{T('Current status')}: {status}")

187

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

203

204 time.sleep(self.poll_interval)

205

206 if not self.stop_polling:

207 wx.CallAfter(self._update_status, T('Waiting timeout'))

208 wx.CallAfter(self._on_failure)

209 return False

210

211 def _update_status(self, status):

212 self.status_text.SetLabel(status)

213

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() # 强制重新布局

219

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() # 强制重新布局

225

226 def on_fetch(self, event):

227 self.status_text.SetLabel(T('Requesting binding'))

228 self.fetch_button.Disable()

229

230 data = self.api.request_binding()

231 if not data:

232 self._on_failure()

233 return

234

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'))

240

241 # 打开浏览器

242 self.url_ctrl.SetURL(approval_url)

243 self.url_ctrl.Show()

244 webbrowser.open(approval_url)

245

246 # 显示进度组并重新布局

247 self._toggle_progress(True)

248 self.Layout()

249

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()

257

258 def _save_token(self, token):

259 self.api_key_text.SetValue(token)

260

261 def get_api_key(self):

262 return self.api_key_text.GetValue()

263

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()

273

274 def GetNext(self):

275 return self.GetParent().model_page

276

277 def GetPrev(self):

278 return self.GetParent().initial_page

279

280 class 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)

287

288 def init_ui(self):

289 vbox = wx.BoxSizer(wx.VERTICAL)

290

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)

295

296 # Provider 选择

297 provider_box = wx.StaticBox(self, label=T('Provider'))

298 provider_sizer = wx.StaticBoxSizer(provider_box, wx.VERTICAL)

299

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)

311

312 # API Key 输入

313 api_key_box = wx.StaticBox(self, label="API Key")

314 api_key_sizer = wx.StaticBoxSizer(api_key_box, wx.VERTICAL)

315

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)

322

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)

327

328 self.SetSizer(vbox)

329

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"])

335

336 def get_provider(self):

337 return self.provider_choice.GetStringSelection()

338

339 def get_api_key(self):

340 return self.api_key_text.GetValue()

341

342 def GetNext(self):

343 return self.GetParent().model_page

344

345 def GetPrev(self):

346 return self.GetParent().initial_page

347

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()

357

358 class 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)

365

366 def init_ui(self):

367 vbox = wx.BoxSizer(wx.VERTICAL)

368

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)

373

374 # Model 选择

375 model_box = wx.StaticBox(self, label=T('Available Models'))

376 model_sizer = wx.StaticBoxSizer(model_box, wx.VERTICAL)

377

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)

381

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)

385

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)

389

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)

394

395 self.SetSizer(vbox)

396

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'))

411

412 def get_selected_model(self):

413 return self.model_choice.GetStringSelection()

414

415 def get_max_tokens(self):

416 try:

417 return int(self.max_tokens_text.GetValue())

418 except ValueError:

419 return 8192

420

421 def GetNext(self):

422 return None

423

424 def GetPrev(self):

425 return self.prev_page

426

427 class 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()

433

434 def init_ui(self):

435 vbox = wx.BoxSizer(wx.VERTICAL)

436

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)

441

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)

449

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)

457

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)

465

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)

473

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)

481

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)

490

491 self.SetSizer(vbox)

492

493 def get_client_id(self):

494 return self.client_id_input.GetValue().strip()

495

496 def get_client_secret(self):

497 return self.client_secret_input.GetValue().strip()

498

499 def get_token_url(self):

500 return self.token_url_input.GetValue().strip()

501

502 def get_base_url(self):

503 return self.base_url_input.GetValue().strip()

504

505 def get_model(self):

506 return self.model_input.GetValue().strip()

507

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()

514

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

518

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)

523

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")

539

540 if not token:

541 wx.CallAfter(self._update_test_result, False, "No token received")

542 return

543

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()

552

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))

557

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)

566

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

572

573 def GetPrev(self):

574 return self.prev_page

575

576 class 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")

584

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)

592

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)

596

597 wx.CallAfter(self._set_button_labels)

598

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)

603

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'))

607

608 def on_page_changed(self, event):

609 btn_next = self.FindWindowById(wx.ID_FORWARD)

610 if btn_next: btn_next.SetLabel(T('Next'))

611

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

629

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

643

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()

652

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]

656

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 }

666

667 self.provider_config.save_config(config)

668 wx.MessageBox(T('Configuration saved'), T('Success'), wx.OK | wx.ICON_INFORMATION)

669

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 }

675

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}"

681

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 []

700

701 def 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