Rouyan:使用WPF/C#构建的基于LLM的快捷翻译小工具
引言在全球化日益加深的今天,翻译工具已成为开发者、写作者和普通用户的刚需。虽然市面上有谷歌翻译、DeepL等成熟产品,但有时我们需要一个轻量、可定制、甚至能结合大语言模型(LLM)的本地翻译工具。为此,我使用WPF和C#构建了一个名为"Rouyan"的快捷翻译小工具。本文将带你从零开始,深入Rouyan的架构、实现细节,并展示完整的代码示例。## 项目背景与目标Rouyan的设计初衷是: - 提供一个系统托盘常驻的翻译工具,支持快捷键唤醒。 - 通过LLM(如OpenAI API或本地模型)提供高精度翻译,尤其是针对专业术语和上下文敏感内容。 - 保持轻量级,所有配置存储在本地JSON文件。 - 支持中英互译,并预留扩展接口。## 技术栈概览- UI框架 :WPF(Windows Presentation Foundation),因为其原生支持XAML和系统托盘集成。 - 语言 :C#,用于业务逻辑和API调用。 - LLM接口 :基于HTTP的REST API(如OpenAI的Chat Completion),使用HttpClient。 - 配置管理 :Microsoft.Extensions.Configuration结合JSON文件。 - 快捷键 :使用GlobalHotKey库(或原生Windows API)。## 核心架构:模块化设计Rouyan分为三个核心模块: 1. UI层 :系统托盘图标、浮动翻译窗口、设置界面。 2. 翻译引擎 :封装LLM API调用,支持异步和缓存。 3. 配置管理 :读取/写入用户偏好(如API密钥、目标语言、快捷键)。## 实战代码示例1:系统托盘与快捷键绑定以下代码展示了如何创建系统托盘图标并绑定全局快捷键(Ctrl+Shift+T)来弹出翻译窗口。这是Rouyan的"门面"部分。csharpusing System;using System.Windows;using System.Windows.Forms;using System.Runtime.InteropServices;namespace Rouyan{ public partial class App : Application { private NotifyIcon _trayIcon; private const int MOD_CONTROL = 0x0002; private const int MOD_SHIFT = 0x0004; private const int WM_HOTKEY = 0x0312; private const int HOTKEY_ID = 9000; // 注册全局热键的Win32 API [DllImport("user32.dll")] private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); [DllImport("user32.dll")] private static extern bool UnregisterHotKey(IntPtr hWnd, int id); protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); // 创建系统托盘图标 _trayIcon = new NotifyIcon { Icon = System.Drawing.Icon.ExtractAssociatedIcon("icon.ico"), // 替换为你的图标文件 Text = "Rouyan 翻译工具", Visible = true }; _trayIcon.Click += (s, args) => ShowTranslationWindow(); // 注册全局热键:Ctrl+Shift+T IntPtr handle = new System.Windows.Interop.WindowInteropHelper(this.MainWindow).Handle; RegisterHotKey(handle, HOTKEY_ID, MOD_CONTROL | MOD_SHIFT, (uint)Keys.T); // 监听热键消息 ComponentDispatcher.ThreadPreprocessMessage += (ref MSG msg, ref bool handled) => { if (msg.message == WM_HOTKEY && msg.wParam.ToInt32() == HOTKEY_ID) { ShowTranslationWindow(); handled = true; } }; } private void ShowTranslationWindow() { // 创建或激活翻译窗口(这里简化,实际应检查是否已存在) var window = new TranslationWindow(); window.Show(); window.Activate(); } protected override void OnExit(ExitEventArgs e) { // 注销热键并清理托盘图标 IntPtr handle = new System.Windows.Interop.WindowInteropHelper(this.MainWindow).Handle; UnregisterHotKey(handle, HOTKEY_ID); _trayIcon?.Dispose(); base.OnExit(e); } }}注释说明 : - 使用NotifyIcon实现系统托盘,注册Ctrl+Shift+T作为全局热键。 - 通过Win32 API RegisterHotKey确保热键在任意窗口下生效。 - 当热键触发时,调用ShowTranslationWindow弹出翻译界面。## 实战代码示例2:LLM翻译引擎封装翻译引擎是Rouyan的灵魂。以下代码封装了对OpenAI API的调用,支持流式响应(Stream)以提高用户体验。csharpusing System;using System.Net.Http;using System.Text;using System.Text.Json;using System.Threading.Tasks;using System.Collections.Generic;namespace Rouyan.Services{ public class LlmTranslator { private readonly HttpClient _httpClient; private readonly string _apiKey; private readonly string _baseUrl = "https://api.openai.com/v1/chat/completions"; // 可配置 public LlmTranslator(string apiKey) { _httpClient = new HttpClient(); _apiKey = apiKey; _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}"); } /// <summary> /// 使用LLM翻译文本,支持流式输出 /// </summary> /// <param name="text">待翻译文本</param> /// <param name="sourceLang">源语言(如"中文")</param> /// <param name="targetLang">目标语言(如"英语")</param> /// <returns>翻译后的字符串</returns> public async Task<string> TranslateAsync(string text, string sourceLang, string targetLang) { // 构造系统提示词,引导LLM进行精准翻译 var systemPrompt = $"你是一个专业翻译,请将以下{sourceLang}文本翻译为{targetLang},只返回翻译结果:"; var requestBody = new { model = "gpt-3.5-turbo", // 可替换为其他模型 messages = new[] { new { role = "system", content = systemPrompt }, new { role = "user", content = text } }, temperature = 0.3, // 低温度减少创造性,提高准确性 stream = false // 为简化,使用非流式;实际可设为true }; var json = JsonSerializer.Serialize(requestBody); var content = new StringContent(json, Encoding.UTF8, "application/json"); try { var response = await _httpClient.PostAsync(_baseUrl, content); response.EnsureSuccessStatusCode(); var responseJson = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(responseJson); var result = doc.RootElement .GetProperty("choices")[0] .GetProperty("message") .GetProperty("content") .GetString(); return result?.Trim() ?? string.Empty; } catch (Exception ex) { // 实际项目中应记录日志并通知用户 return $"翻译失败:{ex.Message}"; } } /// <summary> /// 释放资源 /// </summary> public void Dispose() { _httpClient?.Dispose(); } }}注释说明 : - 构造函数接收API密钥,并设置HTTP头。 - TranslateAsync方法构造包含系统提示的请求,调用OpenAI的Chat Completion接口。 - 使用temperature=0.3减少随机性,确保翻译一致性。 - 异常处理返回错误信息,便于调试。## 配置管理与用户设置Rouyan使用JSON文件存储配置,例如config.json:json{ "apiKey": "sk-xxxx", "sourceLanguage": "中文", "targetLanguage": "英语", "hotkey": "Ctrl+Shift+T"}C#端通过ConfigurationBuilder读取:csharpvar config = new ConfigurationBuilder() .SetBasePath(AppDomain.CurrentDomain.BaseDirectory) .AddJsonFile("config.json", optional: false, reloadOnChange: true) .Build();var apiKey = config["apiKey"];用户可以通过设置窗口修改配置,并保存回JSON文件。## 界面设计:简洁与高效翻译窗口使用WPF XAML设计,包含输入文本框、翻译按钮和结果显示区域。核心布局如下:xml<Window x:Class="Rouyan.TranslationWindow" Title="Rouyan 翻译" Height="300" Width="400" WindowStyle="None" ResizeMode="NoResize" Topmost="True"> <Grid Margin="10"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBox x:Name="InputTextBox" Grid.Row="0" Height="60" TextWrapping="Wrap" AcceptsReturn="True"/> <Button Grid.Row="1" Content="翻译" Click="TranslateButton_Click" Height="30" VerticalAlignment="Top"/> <TextBox x:Name="OutputTextBox" Grid.Row="2" Height="100" IsReadOnly="True" TextWrapping="Wrap"/> </Grid></Window>后台代码绑定TranslateButton_Click事件,调用LlmTranslator.TranslateAsync,并将结果显示。## 部署与使用1. 编译项目,生成可执行文件。 2. 将config.json和图标文件放在同一目录。 3. 运行程序,系统托盘出现图标。 4. 按Ctrl+Shift+T弹出翻译窗口,输入文本并点击"翻译"即可。## 总结Rouyan是一个由WPF/C#驱动的轻量级翻译工具,它利用LLM的强大能力,在本地提供高质量翻译。通过本文的代码示例,你可以看到如何: - 使用系统托盘和全局热键实现快捷唤醒。 - 封装LLM API调用,实现灵活的翻译引擎。 - 通过配置文件实现用户自定义。未来可以扩展的功能包括:支持更多语言、集成本地模型(如Llama.cpp)、添加历史记录、以及实现拖拽翻译。Rouyan不仅是一个工具,更是探索LLM在桌面应用落地的实践案例。希望本文能激发你构建自己的LLM驱动小工具的灵感。