Skip to content

Commit 93abd26

Browse files
committed
fix(minimax): add region selector to support international and China API endpoints (#125)
MiniMax provider previously hardcoded minimaxi.com URLs, which only works for China region users. This adds a MiniMaxRegion enum with international (minimax.io) and china (minimaxi.com) options, a region picker in Settings, and dynamic URL resolution throughout the probe and provider layers. - Add MiniMaxRegion enum with region-specific URLs - Add region selector (segmented picker) in MiniMax settings UI - Rename minimaxiXxx methods to minimaxXxx for consistent naming - Update MiniMaxUsageProbe to resolve API URL from configured region - Update MiniMaxProvider.dashboardURL to use region-aware URL - Add region-specific tests for API URL resolution Closes #125
1 parent cd46f53 commit 93abd26

File tree

7 files changed

+194
-32
lines changed

7 files changed

+194
-32
lines changed

Sources/App/Views/SettingsView.swift

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ struct SettingsContentView: View {
6565
@State private var miniMaxConfigExpanded: Bool = false
6666
@State private var miniMaxApiKeyInput: String = ""
6767
@State private var miniMaxAuthEnvVarInput: String = ""
68+
@State private var miniMaxRegion: MiniMaxRegion = .international
6869
@State private var showMiniMaxApiKey: Bool = false
6970
@State private var isTestingMiniMax = false
7071
@State private var miniMaxTestResult: String?
@@ -243,7 +244,8 @@ struct SettingsContentView: View {
243244
kimiProbeMode = UserDefaultsProviderSettingsRepository.shared.kimiProbeMode()
244245

245246
// Initialize MiniMax settings
246-
miniMaxAuthEnvVarInput = UserDefaultsProviderSettingsRepository.shared.minimaxiAuthEnvVar()
247+
miniMaxRegion = UserDefaultsProviderSettingsRepository.shared.minimaxRegion()
248+
miniMaxAuthEnvVarInput = UserDefaultsProviderSettingsRepository.shared.minimaxAuthEnvVar()
247249

248250
// Initialize Hook settings
249251
hooksEnabled = UserDefaultsProviderSettingsRepository.shared.isHookEnabled()
@@ -1093,6 +1095,24 @@ struct SettingsContentView: View {
10931095

10941096
private var miniMaxConfigForm: some View {
10951097
VStack(alignment: .leading, spacing: 14) {
1098+
// Region selector (区域选择)
1099+
VStack(alignment: .leading, spacing: 6) {
1100+
Text("REGION")
1101+
.font(.system(size: 9, weight: .semibold, design: theme.fontDesign))
1102+
.foregroundStyle(theme.textSecondary)
1103+
.tracking(0.5)
1104+
1105+
Picker("", selection: $miniMaxRegion) {
1106+
ForEach(MiniMaxRegion.allCases, id: \.self) { region in
1107+
Text(region.displayName).tag(region)
1108+
}
1109+
}
1110+
.pickerStyle(.segmented)
1111+
.onChange(of: miniMaxRegion) { _, newValue in
1112+
UserDefaultsProviderSettingsRepository.shared.setMinimaxRegion(newValue)
1113+
}
1114+
}
1115+
10961116
// API Key input
10971117
VStack(alignment: .leading, spacing: 6) {
10981118
HStack {
@@ -1103,7 +1123,7 @@ struct SettingsContentView: View {
11031123

11041124
Spacer()
11051125

1106-
if UserDefaultsProviderSettingsRepository.shared.hasMinimaxiApiKey() {
1126+
if UserDefaultsProviderSettingsRepository.shared.hasMinimaxApiKey() {
11071127
HStack(spacing: 3) {
11081128
Image(systemName: "checkmark.circle.fill")
11091129
.font(.system(size: 9))
@@ -1173,7 +1193,7 @@ struct SettingsContentView: View {
11731193
)
11741194
)
11751195
.onChange(of: miniMaxAuthEnvVarInput) { _, newValue in
1176-
UserDefaultsProviderSettingsRepository.shared.setMinimaxiAuthEnvVar(newValue)
1196+
UserDefaultsProviderSettingsRepository.shared.setMinimaxAuthEnvVar(newValue)
11771197
}
11781198
}
11791199

@@ -1232,7 +1252,7 @@ struct SettingsContentView: View {
12321252
.font(.system(size: 9, weight: .semibold, design: theme.fontDesign))
12331253
.foregroundStyle(theme.textTertiary)
12341254

1235-
Link(destination: URL(string: "https://platform.minimaxi.com/user-center/basic-information/interface-key")!) {
1255+
Link(destination: miniMaxRegion.apiKeysURL) {
12361256
HStack(spacing: 3) {
12371257
Text("Open MiniMax API Keys")
12381258
.font(.system(size: 9, weight: .semibold, design: theme.fontDesign))
@@ -1244,9 +1264,9 @@ struct SettingsContentView: View {
12441264
}
12451265

12461266
// Delete API key
1247-
if UserDefaultsProviderSettingsRepository.shared.hasMinimaxiApiKey() {
1267+
if UserDefaultsProviderSettingsRepository.shared.hasMinimaxApiKey() {
12481268
Button {
1249-
UserDefaultsProviderSettingsRepository.shared.deleteMinimaxiApiKey()
1269+
UserDefaultsProviderSettingsRepository.shared.deleteMinimaxApiKey()
12501270
miniMaxApiKeyInput = ""
12511271
miniMaxTestResult = nil
12521272
} label: {
@@ -2933,10 +2953,10 @@ struct SettingsContentView: View {
29332953
miniMaxTestResult = nil
29342954

29352955
// Save current inputs
2936-
UserDefaultsProviderSettingsRepository.shared.setMinimaxiAuthEnvVar(miniMaxAuthEnvVarInput)
2956+
UserDefaultsProviderSettingsRepository.shared.setMinimaxAuthEnvVar(miniMaxAuthEnvVarInput)
29372957
if !miniMaxApiKeyInput.isEmpty {
29382958
AppLog.credentials.info("Saving MiniMax API key for connection test")
2939-
UserDefaultsProviderSettingsRepository.shared.saveMinimaxiApiKey(miniMaxApiKeyInput)
2959+
UserDefaultsProviderSettingsRepository.shared.saveMinimaxApiKey(miniMaxApiKeyInput)
29402960
miniMaxApiKeyInput = ""
29412961
}
29422962

Sources/Domain/Provider/MiniMax/MiniMaxProvider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public final class MiniMaxProvider: AIProvider, @unchecked Sendable {
1313
public let cliCommand: String = "" // API-only provider, no CLI (纯 API 提供者,无 CLI)
1414

1515
public var dashboardURL: URL? {
16-
URL(string: "https://platform.minimaxi.com/user-center/payment/coding-plan")
16+
settingsRepository.minimaxRegion().dashboardURL
1717
}
1818

1919
public var statusPageURL: URL? {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import Foundation
2+
3+
/// Represents the MiniMax API region. (MiniMax API 区域设置)
4+
/// - international: api.minimax.io / platform.minimax.io (For International Users)
5+
/// - china: api.minimaxi.com / platform.minimaxi.com (For Users in China)
6+
public enum MiniMaxRegion: String, Sendable, Equatable, CaseIterable {
7+
case international
8+
case china
9+
10+
/// Display name for the region picker (区域显示名称)
11+
public var displayName: String {
12+
switch self {
13+
case .international: return "International (minimax.io)"
14+
case .china: return "China (minimaxi.com)"
15+
}
16+
}
17+
18+
/// API base URL for coding plan remains endpoint (Coding Plan API 基础 URL)
19+
public var apiBaseURL: String {
20+
switch self {
21+
case .international: return "https://api.minimax.io"
22+
case .china: return "https://api.minimaxi.com"
23+
}
24+
}
25+
26+
/// Platform URL for dashboard (平台仪表盘 URL)
27+
public var platformURL: String {
28+
switch self {
29+
case .international: return "https://platform.minimax.io"
30+
case .china: return "https://platform.minimaxi.com"
31+
}
32+
}
33+
34+
/// URL to get API keys from the platform (获取 API Key 的页面 URL)
35+
public var apiKeysURL: URL {
36+
switch self {
37+
case .international:
38+
return URL(string: "https://platform.minimax.io/user-center/basic-information/interface-key")!
39+
case .china:
40+
return URL(string: "https://platform.minimaxi.com/user-center/basic-information/interface-key")!
41+
}
42+
}
43+
44+
/// Dashboard URL for coding plan payment page (Coding Plan 付费页面 URL)
45+
public var dashboardURL: URL {
46+
switch self {
47+
case .international:
48+
return URL(string: "https://platform.minimax.io/user-center/payment/coding-plan")!
49+
case .china:
50+
return URL(string: "https://platform.minimaxi.com/user-center/payment/coding-plan")!
51+
}
52+
}
53+
54+
/// Full API URL for the coding plan remains endpoint (Coding Plan 剩余额度 API URL)
55+
public var codingPlanRemainsURL: String {
56+
"\(apiBaseURL)/v1/api/openplatform/coding_plan/remains"
57+
}
58+
}

Sources/Domain/Provider/ProviderSettingsRepository.swift

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -191,27 +191,34 @@ public protocol KimiSettingsRepository: ProviderSettingsRepository {
191191
}
192192

193193
/// MiniMax-specific settings repository, extending base ProviderSettingsRepository.
194-
/// Stores API key configuration for MiniMax Coding Plan quota monitoring.
194+
/// Stores API key and region configuration for MiniMax Coding Plan quota monitoring.
195195
/// Tests can use UserDefaultsProviderSettingsRepository with test UserDefaults.
196196
/// App uses UserDefaultsProviderSettingsRepository.
197197
public protocol MiniMaxSettingsRepository: ProviderSettingsRepository {
198+
/// Gets the API region (international or china, default: international)
199+
/// (获取 API 区域设置,默认国际版)
200+
func minimaxRegion() -> MiniMaxRegion
201+
202+
/// Sets the API region (设置 API 区域)
203+
func setMinimaxRegion(_ region: MiniMaxRegion)
204+
198205
/// Gets the environment variable name for MiniMax API key (empty = use default MINIMAX_API_KEY)
199-
func minimaxiAuthEnvVar() -> String
206+
func minimaxAuthEnvVar() -> String
200207

201208
/// Sets the environment variable name for MiniMax API key
202-
func setMinimaxiAuthEnvVar(_ envVar: String)
209+
func setMinimaxAuthEnvVar(_ envVar: String)
203210

204211
/// Saves the MiniMax API key (for Settings UI input)
205-
func saveMinimaxiApiKey(_ key: String)
212+
func saveMinimaxApiKey(_ key: String)
206213

207214
/// Retrieves the MiniMax API key
208-
func getMinimaxiApiKey() -> String?
215+
func getMinimaxApiKey() -> String?
209216

210217
/// Deletes the MiniMax API key
211-
func deleteMinimaxiApiKey()
218+
func deleteMinimaxApiKey()
212219

213220
/// Checks if a MiniMax API key is saved
214-
func hasMinimaxiApiKey() -> Bool
221+
func hasMinimaxApiKey() -> Bool
215222
}
216223

217224
// MARK: - Default Implementation

Sources/Infrastructure/MiniMax/MiniMaxUsageProbe.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@ import Foundation
22
import Domain
33

44
/// Probes MiniMax Coding Plan API for usage quota information.
5-
/// Uses REST API: GET https://www.minimaxi.com/v1/api/openplatform/coding_plan/remains
5+
/// Supports both international (minimax.io) and China (minimaxi.com) regions.
6+
/// (支持国际版和中国版两个区域)
67
/// Authentication: Bearer token from env var or stored API key.
78
public struct MiniMaxUsageProbe: UsageProbe {
89
private let networkClient: any NetworkClient
910
private let settingsRepository: any MiniMaxSettingsRepository
1011
private let timeout: TimeInterval
1112

12-
private static let apiURL = "https://www.minimaxi.com/v1/api/openplatform/coding_plan/remains"
13+
/// Resolves the API URL based on the configured region (根据区域配置动态选择 API URL)
14+
var apiURL: String {
15+
settingsRepository.minimaxRegion().codingPlanRemainsURL
16+
}
1317

1418
public init(
1519
networkClient: any NetworkClient = URLSession.shared,
@@ -25,15 +29,15 @@ public struct MiniMaxUsageProbe: UsageProbe {
2529

2630
func getApiKey() -> String? {
2731
// First, check environment variable if configured
28-
let envVarName = settingsRepository.minimaxiAuthEnvVar()
32+
let envVarName = settingsRepository.minimaxAuthEnvVar()
2933
let effectiveEnvVar = envVarName.isEmpty ? "MINIMAX_API_KEY" : envVarName
3034
if let envValue = ProcessInfo.processInfo.environment[effectiveEnvVar], !envValue.isEmpty {
3135
AppLog.probes.debug("MiniMax: Using API key from env var '\(effectiveEnvVar)'")
3236
return envValue
3337
}
3438

3539
// Fall back to stored API key
36-
if let storedKey = settingsRepository.getMinimaxiApiKey(), !storedKey.isEmpty {
40+
if let storedKey = settingsRepository.getMinimaxApiKey(), !storedKey.isEmpty {
3741
AppLog.probes.debug("MiniMax: Using stored API key")
3842
return storedKey
3943
}
@@ -59,7 +63,7 @@ public struct MiniMaxUsageProbe: UsageProbe {
5963

6064
AppLog.probes.info("Starting MiniMax probe...")
6165

62-
guard let url = URL(string: Self.apiURL) else {
66+
guard let url = URL(string: apiURL) else {
6367
throw ProbeError.executionFailed("Invalid MiniMax API URL")
6468
}
6569

Sources/Infrastructure/Storage/UserDefaultsProviderSettingsRepository.swift

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -251,27 +251,40 @@ public final class UserDefaultsProviderSettingsRepository: ZaiSettingsRepository
251251

252252
// MARK: - MiniMaxSettingsRepository
253253

254-
public func minimaxiAuthEnvVar() -> String {
254+
public func minimaxRegion() -> MiniMaxRegion {
255+
// Legacy compatibility: key absent means user upgraded from pre-region version,
256+
// which only supported china (minimaxi.com). (兼容旧版:无 key 则默认中国区)
257+
guard let rawValue = userDefaults.string(forKey: Keys.minimaxRegion) else {
258+
return .china
259+
}
260+
return MiniMaxRegion(rawValue: rawValue) ?? .china
261+
}
262+
263+
public func setMinimaxRegion(_ region: MiniMaxRegion) {
264+
userDefaults.set(region.rawValue, forKey: Keys.minimaxRegion)
265+
}
266+
267+
public func minimaxAuthEnvVar() -> String {
255268
userDefaults.string(forKey: Keys.minimaxiAuthEnvVar) ?? ""
256269
}
257270

258-
public func setMinimaxiAuthEnvVar(_ envVar: String) {
271+
public func setMinimaxAuthEnvVar(_ envVar: String) {
259272
userDefaults.set(envVar, forKey: Keys.minimaxiAuthEnvVar)
260273
}
261274

262-
public func saveMinimaxiApiKey(_ key: String) {
275+
public func saveMinimaxApiKey(_ key: String) {
263276
userDefaults.set(key, forKey: Keys.minimaxiApiKey)
264277
}
265278

266-
public func getMinimaxiApiKey() -> String? {
279+
public func getMinimaxApiKey() -> String? {
267280
userDefaults.string(forKey: Keys.minimaxiApiKey)
268281
}
269282

270-
public func deleteMinimaxiApiKey() {
283+
public func deleteMinimaxApiKey() {
271284
userDefaults.removeObject(forKey: Keys.minimaxiApiKey)
272285
}
273286

274-
public func hasMinimaxiApiKey() -> Bool {
287+
public func hasMinimaxApiKey() -> Bool {
275288
userDefaults.object(forKey: Keys.minimaxiApiKey) != nil
276289
}
277290

@@ -325,6 +338,7 @@ public final class UserDefaultsProviderSettingsRepository: ZaiSettingsRepository
325338
static let bedrockRegions = "providerConfig.bedrockRegions"
326339
static let bedrockDailyBudget = "providerConfig.bedrockDailyBudget"
327340
// MiniMax settings (key strings kept for backward compatibility 保持向后兼容)
341+
static let minimaxRegion = "providerConfig.minimaxRegion"
328342
static let minimaxiAuthEnvVar = "providerConfig.minimaxiAuthEnvVar"
329343
static let minimaxiApiKey = "com.claudebar.credentials.minimaxi-api-key"
330344
// Credentials (kept compatible with old UserDefaultsCredentialRepository keys)

0 commit comments

Comments
 (0)