using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Windows; using LocalizationTool.Services; namespace LocalizationTool.ViewModels { /// /// 翻译设置窗口 ViewModel。 /// 语义:BaseUrl / Model / Keys / AppId / Secret 留空 = 内置默认;填了 = 自定义优先; /// "恢复默认"清空自定义字段。 /// public class TranslationSettingsViewModel : ViewModelBase { private readonly Config.ConfigRoot _cfg; private bool _isBaidu; private string _baseUrl; private string _model; private bool _isLlm; private string _reference; private string _testText = "保存配置"; private string _testResult = "翻译结果将显示在这里..."; private LanguageEntry _testLang; private bool _isBusy; private List _allModels = new List(); public TranslationSettingsViewModel(Config.ConfigRoot cfg) { _cfg = cfg ?? new Config.ConfigRoot(); // 掩码回显 var firstKey = Config.GetOpenAiKeys(_cfg.Translation).FirstOrDefault() ?? ""; OpenAiKeyMask = Config.MaskKey(firstKey); var (appId, secret) = Config.GetBaiduCredentials(_cfg.Baidu); BaiduAppIdMask = Config.MaskKey(appId); BaiduSecretMask = Config.MaskKey(secret); BaseUrl = Config.ResolveBaseUrl(_cfg.Translation); Model = Config.ResolveModel(_cfg.Translation); IsBaidu = string.Equals(_cfg.Translation.Provider, "baidu", StringComparison.OrdinalIgnoreCase); IsLlm = string.Equals(_cfg.Baidu.ModelType, "llm", StringComparison.OrdinalIgnoreCase); Reference = _cfg.Baidu.Reference ?? ""; // 输入框初始显示掩码(内容 == 掩码 = 未修改,保存时保持原值) OpenAiKeyInput = OpenAiKeyMask; BaiduAppIdInput = BaiduAppIdMask; BaiduSecretInput = BaiduSecretMask; TestCommand = new RelayCommand(() => { var _ = TestTranslateAsync(); }); FetchModelsCommand = new RelayCommand(() => { var _ = FetchModelsAsync(); }); ResetOpenAICommand = new RelayCommand(ResetOpenAiToDefault); ResetBaiduCommand = new RelayCommand(ResetBaiduToDefault); SaveCommand = new RelayCommand(Save); CancelCommand = new RelayCommand(Cancel); SwitchToOpenAiCommand = new RelayCommand(() => IsOpenAi = true); SwitchToBaiduCommand = new RelayCommand(() => IsBaidu = true); } // ---- 引擎选择 ---- public bool IsOpenAi { get => !_isBaidu; set { if (value) IsBaidu = false; } } public bool IsBaidu { get => _isBaidu; set { if (Set(ref _isBaidu, value)) { OnPropertyChanged(nameof(IsOpenAi)); UpdateStatus(); } } } // ---- OpenAI ---- public string BaseUrl { get => _baseUrl; set => Set(ref _baseUrl, value); } public string Model { get => _model; set { if (Set(ref _model, value)) OnPropertyChanged(nameof(FilteredModels)); } } /// 模型最大上下文(token)输入;空/非法 = 用内置默认(agnes-2.5-flash 512K)。 public string MaxContextTokensText { get => _cfg.Translation.MaxContextTokens > 0 ? _cfg.Translation.MaxContextTokens.ToString() : ""; set { _cfg.Translation.MaxContextTokens = int.TryParse(value?.Trim(), out var n) && n > 0 ? n : 0; } } /// 当前生效的最大上下文(含内置默认值说明)。 public string MaxContextHint => $"留空 = 内置默认 {Config.BuiltInMaxContextTokens:N0} token(agnes-2.5-flash 规格)"; /// 已存 Key 的掩码(输入框内容 == 掩码 = 未修改)。 public string OpenAiKeyMask { get => _openAiKeyMask; set => Set(ref _openAiKeyMask, value); } private string _openAiKeyMask = ""; /// 输入框当前内容(由 View 在 PasswordBox 变化时更新)。 public string OpenAiKeyInput { get => _openAiKeyInput; set => Set(ref _openAiKeyInput, value); } private string _openAiKeyInput = ""; // ---- 百度 ---- public string BaiduAppIdMask { get => _baiduAppIdMask; set => Set(ref _baiduAppIdMask, value); } private string _baiduAppIdMask = ""; public string BaiduSecretMask { get => _baiduSecretMask; set => Set(ref _baiduSecretMask, value); } private string _baiduSecretMask = ""; public string BaiduAppIdInput { get => _baiduAppIdInput; set => Set(ref _baiduAppIdInput, value); } private string _baiduAppIdInput = ""; public string BaiduSecretInput { get => _baiduSecretInput; set => Set(ref _baiduSecretInput, value); } private string _baiduSecretInput = ""; public bool IsLlm { get => _isLlm; set { if (Set(ref _isLlm, value)) OnPropertyChanged(nameof(IsNmt)); } } /// NMT 模式(与 IsLlm 互斥)。 public bool IsNmt { get => !_isLlm; set { var newLlm = !value; if (_isLlm == newLlm) return; _isLlm = newLlm; OnPropertyChanged(nameof(IsLlm)); OnPropertyChanged(nameof(IsNmt)); } } public string Reference { get => _reference; set => Set(ref _reference, value); } // ---- 测试 ---- /// 测试目标语言候选(全量语言目录)。 public List TestLanguages => LanguageCatalog.All; public LanguageEntry TestLang { get => _testLang; set => Set(ref _testLang, value); } public string TestText { get => _testText; set => Set(ref _testText, value); } public string TestResult { get => _testResult; set => Set(ref _testResult, value); } public bool IsBusy { get => _isBusy; set => Set(ref _isBusy, value); } // ---- 状态 ---- private string _openAiStatus = ""; private string _baiduStatus = ""; public string OpenAiStatus { get => _openAiStatus; private set => Set(ref _openAiStatus, value); } public string BaiduStatus { get => _baiduStatus; private set => Set(ref _baiduStatus, value); } /// 请求关闭窗口(true = 保存成功)。 public event Action RequestClose; // ---- 命令 ---- public RelayCommand TestCommand { get; } public RelayCommand FetchModelsCommand { get; } public RelayCommand ResetOpenAICommand { get; } public RelayCommand ResetBaiduCommand { get; } public RelayCommand SaveCommand { get; } public RelayCommand CancelCommand { get; } public RelayCommand SwitchToOpenAiCommand { get; } public RelayCommand SwitchToBaiduCommand { get; } // ---- 初始化 ---- public void Initialize(LanguageEntry defaultLang) { TestLang = defaultLang; UpdateStatus(); } public void UpdateStatus() { OpenAiStatus = Config.IsDefaultOpenAi(_cfg.Translation) ? "✅ 当前使用内置默认 AI(留空即内置默认)" : "✏️ 当前使用自定义 AI 配置"; BaiduStatus = Config.IsDefaultBaidu(_cfg.Baidu) ? "✅ 当前使用内置默认凭证(留空即内置默认)" : "✏️ 当前使用自定义凭证"; } // ---- 恢复默认 ---- private void ResetOpenAiToDefault() { Config.ResetOpenAiToDefault(_cfg.Translation); BaseUrl = Config.BuiltInBaseUrl; Model = Config.BuiltInModel; OpenAiKeyMask = Config.MaskKey(Config.GetOpenAiKeys(_cfg.Translation).FirstOrDefault() ?? ""); OpenAiKeyInput = OpenAiKeyMask; UpdateStatus(); } private void ResetBaiduToDefault() { Config.ResetBaiduToDefault(_cfg.Baidu); BaiduAppIdMask = Config.MaskKey(Config.GetBaiduCredentials(_cfg.Baidu).appId); BaiduSecretMask = Config.MaskKey(Config.GetBaiduCredentials(_cfg.Baidu).secret); BaiduAppIdInput = BaiduAppIdMask; BaiduSecretInput = BaiduSecretMask; UpdateStatus(); } // ---- 保存 ---- private void Save() { if (IsOpenAi) { _cfg.Translation.Provider = "openai"; _cfg.Translation.ApiKey = null; var url = BaseUrl?.Trim(); _cfg.Translation.BaseUrl = (string.IsNullOrEmpty(url) || url == Config.BuiltInBaseUrl) ? "" : url; var model = Model?.Trim(); _cfg.Translation.Model = (string.IsNullOrEmpty(model) || model == Config.BuiltInModel) ? "" : model; if (OpenAiKeyInput.Length == 0) _cfg.Translation.Keys = new List(); else if (OpenAiKeyInput != OpenAiKeyMask) _cfg.Translation.Keys = new List { Config.EncryptKey(OpenAiKeyInput) }; } else { _cfg.Translation.Provider = "baidu"; _cfg.Baidu.ModelType = IsLlm ? "llm" : "nmt"; _cfg.Baidu.Reference = Reference?.Trim(); if (BaiduAppIdInput.Length == 0) _cfg.Baidu.AppId = ""; else if (BaiduAppIdInput != BaiduAppIdMask) _cfg.Baidu.AppId = Config.EncryptKey(BaiduAppIdInput); if (BaiduSecretInput.Length == 0) _cfg.Baidu.Secret = ""; else if (BaiduSecretInput != BaiduSecretMask) _cfg.Baidu.Secret = Config.EncryptKey(BaiduSecretInput); } Config.Save(_cfg); Translation.Reset(); RequestClose?.Invoke(true); } private void Cancel() => RequestClose?.Invoke(false); // ---- 测试翻译 ---- private async Task TestTranslateAsync() { var text = TestText?.Trim(); if (string.IsNullOrEmpty(text)) { MessageBox.Show("请输入测试文本", "提示"); return; } var langEntry = TestLang; var targetLang = langEntry?.Code ?? "en-US"; var targetDisplay = langEntry?.Display ?? targetLang; try { IsBusy = true; TestResult = $"翻译中...(目标:{targetDisplay})"; var tempConfig = new Config.ConfigRoot { Translation = new Config.TranslationConfig { Provider = IsOpenAi ? "openai" : "baidu", BaseUrl = string.IsNullOrWhiteSpace(BaseUrl) ? Config.BuiltInBaseUrl : BaseUrl.Trim(), Model = string.IsNullOrWhiteSpace(Model) ? Config.BuiltInModel : Model.Trim(), Keys = OpenAiKeyInput.Length > 0 && OpenAiKeyInput != OpenAiKeyMask ? new List { Config.EncryptKey(OpenAiKeyInput) } : (_cfg.Translation.Keys ?? new List()), }, Baidu = new Config.BaiduConfig { AppId = BaiduAppIdInput.Length > 0 && BaiduAppIdInput != BaiduAppIdMask ? Config.EncryptKey(BaiduAppIdInput) : _cfg.Baidu.AppId, Secret = BaiduSecretInput.Length > 0 && BaiduSecretInput != BaiduSecretMask ? Config.EncryptKey(BaiduSecretInput) : _cfg.Baidu.Secret, ModelType = IsLlm ? "llm" : "nmt", Reference = Reference?.Trim(), } }; Translation.Reset(); var provider = Translation.Create(tempConfig); var result = await provider.TranslateAsync(text, "zh", targetLang); TestResult = $"✅ 翻译成功(引擎:{provider.Name},目标:{targetDisplay}):\n{result}"; } catch (Exception ex) { TestResult = $"❌ 翻译失败:\n{ex.Message}"; } finally { IsBusy = false; } } // ---- 获取模型列表 ---- private async Task FetchModelsAsync() { var baseUrl = string.IsNullOrWhiteSpace(BaseUrl) ? Config.BuiltInBaseUrl : BaseUrl.Trim().TrimEnd('/'); var key = ResolveOpenAiKey(); if (string.IsNullOrWhiteSpace(baseUrl) || string.IsNullOrWhiteSpace(key)) { MessageBox.Show("请先填写 Base URL 和 API Key", "提示"); return; } try { IsBusy = true; using (var client = new System.Net.Http.HttpClient { Timeout = TimeSpan.FromSeconds(20) }) { client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", key); var resp = await client.GetAsync(baseUrl + "/models"); var body = await resp.Content.ReadAsStringAsync(); var j = Newtonsoft.Json.Linq.JObject.Parse(body); var arr = j["data"] as Newtonsoft.Json.Linq.JArray; var models = arr?.Select(t => (string)t["id"]) .Where(s => !string.IsNullOrEmpty(s)) .Distinct() .OrderBy(s => s) .ToList() ?? new List(); if (models.Count == 0) { MessageBox.Show("接口返回为空,未获取到模型", "提示"); return; } _allModels = models; Model = models.First(); OnPropertyChanged(nameof(FilteredModels)); } } catch (Exception ex) { MessageBox.Show($"获取模型失败:{ex.Message}", "错误"); } finally { IsBusy = false; } } private string ResolveOpenAiKey() { var input = OpenAiKeyInput?.Trim() ?? ""; if (!string.IsNullOrEmpty(input) && input != OpenAiKeyMask) return input; return Config.GetOpenAiKeys(_cfg.Translation).FirstOrDefault() ?? ""; } /// 模型下拉候选(按 Model 输入过滤)。 public List FilteredModels { get { if (_allModels.Count == 0) return null; var kw = Model?.Trim() ?? ""; return string.IsNullOrEmpty(kw) ? _allModels : _allModels.Where(m => m.IndexOf(kw, StringComparison.OrdinalIgnoreCase) >= 0).ToList(); } } public void RefreshModelFilter() => OnPropertyChanged(nameof(FilteredModels)); } }