| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
- using System.Windows;
- using LocalizationTool.Services;
- namespace LocalizationTool.ViewModels
- {
- /// <summary>
- /// 翻译设置窗口 ViewModel。
- /// 语义:BaseUrl / Model / Keys / AppId / Secret 留空 = 内置默认;填了 = 自定义优先;
- /// "恢复默认"清空自定义字段。
- /// </summary>
- 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<string> _allModels = new List<string>();
- 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));
- }
- }
- /// <summary>模型最大上下文(token)输入;空/非法 = 用内置默认(agnes-2.5-flash 512K)。</summary>
- 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;
- }
- }
- /// <summary>当前生效的最大上下文(含内置默认值说明)。</summary>
- public string MaxContextHint =>
- $"留空 = 内置默认 {Config.BuiltInMaxContextTokens:N0} token(agnes-2.5-flash 规格)";
- /// <summary>已存 Key 的掩码(输入框内容 == 掩码 = 未修改)。</summary>
- public string OpenAiKeyMask
- {
- get => _openAiKeyMask;
- set => Set(ref _openAiKeyMask, value);
- }
- private string _openAiKeyMask = "";
- /// <summary>输入框当前内容(由 View 在 PasswordBox 变化时更新)。</summary>
- 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));
- }
- }
- /// <summary>NMT 模式(与 IsLlm 互斥)。</summary>
- 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);
- }
- // ---- 测试 ----
- /// <summary>测试目标语言候选(全量语言目录)。</summary>
- public List<LanguageEntry> 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);
- }
- /// <summary>请求关闭窗口(true = 保存成功)。</summary>
- public event Action<bool> 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<string>();
- else if (OpenAiKeyInput != OpenAiKeyMask)
- _cfg.Translation.Keys = new List<string> { 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<string> { Config.EncryptKey(OpenAiKeyInput) }
- : (_cfg.Translation.Keys ?? new List<string>()),
- },
- 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<string>();
- 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() ?? "";
- }
- /// <summary>模型下拉候选(按 Model 输入过滤)。</summary>
- public List<string> 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));
- }
- }
|