Files

1 line
29 KiB
JSON

{"repo": "castlepower666/roastsub", "files": {"src/Services/DeepSeekTranslationService.cs": {"640eb99ab04ee1ce71820e858813224ee9294499": {"t": 1785746192.9831846, "n": 6537, "h": "9acbc2b0b55845b2", "c": "using System.IO;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Text;\nusing System.Text.Json;\n\nnamespace RoastSub.Services;\n\n/// <summary>\n/// DeepSeek AI 翻译服务(OpenAI 兼容格式)\n/// </summary>\npublic class DeepSeekTranslationService : IDisposable\n{\n private readonly HttpClient _httpClient;\n private string _apiKey = \"\";\n private string _baseUrl = \"https://api.deepseek.com\";\n private string _model = \"deepseek-v4-flash\"; // 便宜快速,适合翻译\n\n public DeepSeekTranslationService()\n {\n _httpClient = new HttpClient { Timeout = TimeSpan.FromMinutes(2) };\n }\n\n /// <summary>设置 API Key</summary>\n public void SetApiKey(string apiKey)\n {\n _apiKey = apiKey;\n }\n\n /// <summary>设置模型</summary>\n public void SetModel(string model)\n {\n _model = model;\n }\n\n /// <summary>\n /// 流式翻译文本(带逐 token 回调 + 上下文历史)\n /// </summary>\n /// <param name=\"text\">要翻译的文本</param>\n /// <param name=\"onChunk\">每收到一个 token 的回调</param>\n /// <param name=\"context\">上文历史(前几条识别出的原文)</param>\n /// <param name=\"sourceLang\">源语言</param>\n /// <param name=\"targetLang\">目标语言</param>\n public async Task<string> TranslateStreamAsync(string text, Action<string>? onChunk, List<string>? context = null, string sourceLang = \"en\", string targetLang = \"zh\")\n {\n if (string.IsNullOrWhiteSpace(text) || string.IsNullOrEmpty(_apiKey))\n return \"\";\n\n try\n {\n var request = new HttpRequestMessage(HttpMethod.Post, $\"{_baseUrl}/chat/completions\");\n request.Headers.Authorization = new AuthenticationHeaderValue(\"Bearer\", _apiKey);\n\n // Build system prompt\n var systemPrompt = \"\";\n\n // 1. Base Setting\n systemPrompt += \"===Base Setting===\\n\";\n systemPrompt += $\"You are a veteran {GetLanguageName(sourceLang)}-to-{GetLanguageName(targetLang)} subtitle translator with 10 years of experience, specializing in translating colloquial content for TV talk shows, podcasts, and online programs. You excel at transforming everyday spoken {GetLanguageName(sourceLang)}, including talk-show banter, into natural, smooth, and tonally appropriate spoken {GetLanguageName(targetLang)} that sounds as if the host is speaking it, not like a stilted written translation. You adapt word choice based on context (e.g., openings, transitions, interviews) while maintaining full consistency in terminology and tone throughout.\";\n\n // 2. Context\n if (context != null && context.Count > 0)\n {\n systemPrompt += \"\\n\\n===Context===\\n\";\n systemPrompt += \"The following are previous sentences for context. Use them to maintain consistent terminology and style, but DO NOT translate them:\\n\";\n systemPrompt += string.Join(\"\\n\", context);\n }\n\n // 3. Text to translate\n systemPrompt += \"\\n\\n===Text to Translate===\\n\";\n systemPrompt += \"Translate the following text:\\n\";\n systemPrompt += text;\n\n // 4. Rules\n systemPrompt += \"\\n\\n===Rules===\\n\";\n systemPrompt += $\"- Translate the text from {GetLanguageName(sourceLang)} to {GetLanguageName(targetLang)}.\";\n systemPrompt += \"\\n- Always translate regardless of the input content, even if it is a single word, fragment, or seems incomplete. Never question or refuse.\";\n systemPrompt += \"\\n- Only output the translation, no explanations.\";\n systemPrompt += \"\\n- The translation must be suitable for reading aloud, with natural rhythm, like a host's voiceover; avoid overly literal or stiff translations that would sound unnatural when spoken.\";\n systemPrompt += \"\\n- Maintain consistent tone, address style, and familiarity level with the previous lines.\";\n\n var messages = new object[]\n {\n new { role = \"system\", content = systemPrompt }\n };\n\n var body = new\n {\n model = _model,\n messages = messages,\n stream = true,\n temperature = 0.3,\n thinking = new { type = \"disabled\" }\n };\n\n request.Content = new StringContent(\n JsonSerializer.Serialize(body),\n Encoding.UTF8,\n \"application/json\");\n\n var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);\n response.EnsureSuccessStatusCode();\n\n var stream = await response.Content.ReadAsStreamAsync();\n using var reader = new StreamReader(stream);\n\n var fullText = new StringBuilder();\n while (await reader.ReadLineAsync() is { } line)\n {\n if (string.IsNullOrEmpty(line) || !line.StartsWith(\"data: \"))\n continue;\n\n var data = line[\"data: \".Length..];\n if (data == \"[DONE]\")\n break;\n\n try\n {\n using var doc = JsonDocument.Parse(data);\n var delta = doc.RootElement\n .GetProperty(\"choices\")[0]\n .GetProperty(\"delta\");\n\n if (delta.TryGetProperty(\"content\", out var contentElement))\n {\n var content = contentElement.GetString();\n if (!string.IsNullOrEmpty(content))\n {\n fullText.Append(content);\n onChunk?.Invoke(content);\n }\n }\n }\n catch { /* 忽略解析错误 */ }\n }\n\n var result = fullText.ToString().Trim();\n return result;\n }\n catch (Exception ex)\n {\n System.Diagnostics.Debug.WriteLine($\"[翻译] 流式错误: {ex.Message}\");\n return $\"[翻译失败] {ex.Message}\";\n }\n }\n\n public void Dispose()\n {\n _httpClient.Dispose();\n }\n\n private static string GetLanguageName(string code)\n {\n return code.ToLower() switch\n {\n \"en\" => \"English\",\n \"zh\" => \"Chinese\",\n \"ja\" => \"Japanese\",\n \"ko\" => \"Korean\",\n \"fr\" => \"French\",\n \"de\" => \"German\",\n \"es\" => \"Spanish\",\n \"ru\" => \"Russian\",\n \"pt\" => \"Portuguese\",\n \"ar\" => \"Arabic\",\n _ => code\n };\n }\n}\n"}, "7094fe842aad318c834fb9adff1db70647d514a6": {"t": 1785760507.5359933, "n": 6537, "h": "9acbc2b0b55845b2", "c": "using System.IO;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Text;\nusing System.Text.Json;\n\nnamespace RoastSub.Services;\n\n/// <summary>\n/// DeepSeek AI 翻译服务(OpenAI 兼容格式)\n/// </summary>\npublic class DeepSeekTranslationService : IDisposable\n{\n private readonly HttpClient _httpClient;\n private string _apiKey = \"\";\n private string _baseUrl = \"https://api.deepseek.com\";\n private string _model = \"deepseek-v4-flash\"; // 便宜快速,适合翻译\n\n public DeepSeekTranslationService()\n {\n _httpClient = new HttpClient { Timeout = TimeSpan.FromMinutes(2) };\n }\n\n /// <summary>设置 API Key</summary>\n public void SetApiKey(string apiKey)\n {\n _apiKey = apiKey;\n }\n\n /// <summary>设置模型</summary>\n public void SetModel(string model)\n {\n _model = model;\n }\n\n /// <summary>\n /// 流式翻译文本(带逐 token 回调 + 上下文历史)\n /// </summary>\n /// <param name=\"text\">要翻译的文本</param>\n /// <param name=\"onChunk\">每收到一个 token 的回调</param>\n /// <param name=\"context\">上文历史(前几条识别出的原文)</param>\n /// <param name=\"sourceLang\">源语言</param>\n /// <param name=\"targetLang\">目标语言</param>\n public async Task<string> TranslateStreamAsync(string text, Action<string>? onChunk, List<string>? context = null, string sourceLang = \"en\", string targetLang = \"zh\")\n {\n if (string.IsNullOrWhiteSpace(text) || string.IsNullOrEmpty(_apiKey))\n return \"\";\n\n try\n {\n var request = new HttpRequestMessage(HttpMethod.Post, $\"{_baseUrl}/chat/completions\");\n request.Headers.Authorization = new AuthenticationHeaderValue(\"Bearer\", _apiKey);\n\n // Build system prompt\n var systemPrompt = \"\";\n\n // 1. Base Setting\n systemPrompt += \"===Base Setting===\\n\";\n systemPrompt += $\"You are a veteran {GetLanguageName(sourceLang)}-to-{GetLanguageName(targetLang)} subtitle translator with 10 years of experience, specializing in translating colloquial content for TV talk shows, podcasts, and online programs. You excel at transforming everyday spoken {GetLanguageName(sourceLang)}, including talk-show banter, into natural, smooth, and tonally appropriate spoken {GetLanguageName(targetLang)} that sounds as if the host is speaking it, not like a stilted written translation. You adapt word choice based on context (e.g., openings, transitions, interviews) while maintaining full consistency in terminology and tone throughout.\";\n\n // 2. Context\n if (context != null && context.Count > 0)\n {\n systemPrompt += \"\\n\\n===Context===\\n\";\n systemPrompt += \"The following are previous sentences for context. Use them to maintain consistent terminology and style, but DO NOT translate them:\\n\";\n systemPrompt += string.Join(\"\\n\", context);\n }\n\n // 3. Text to translate\n systemPrompt += \"\\n\\n===Text to Translate===\\n\";\n systemPrompt += \"Translate the following text:\\n\";\n systemPrompt += text;\n\n // 4. Rules\n systemPrompt += \"\\n\\n===Rules===\\n\";\n systemPrompt += $\"- Translate the text from {GetLanguageName(sourceLang)} to {GetLanguageName(targetLang)}.\";\n systemPrompt += \"\\n- Always translate regardless of the input content, even if it is a single word, fragment, or seems incomplete. Never question or refuse.\";\n systemPrompt += \"\\n- Only output the translation, no explanations.\";\n systemPrompt += \"\\n- The translation must be suitable for reading aloud, with natural rhythm, like a host's voiceover; avoid overly literal or stiff translations that would sound unnatural when spoken.\";\n systemPrompt += \"\\n- Maintain consistent tone, address style, and familiarity level with the previous lines.\";\n\n var messages = new object[]\n {\n new { role = \"system\", content = systemPrompt }\n };\n\n var body = new\n {\n model = _model,\n messages = messages,\n stream = true,\n temperature = 0.3,\n thinking = new { type = \"disabled\" }\n };\n\n request.Content = new StringContent(\n JsonSerializer.Serialize(body),\n Encoding.UTF8,\n \"application/json\");\n\n var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);\n response.EnsureSuccessStatusCode();\n\n var stream = await response.Content.ReadAsStreamAsync();\n using var reader = new StreamReader(stream);\n\n var fullText = new StringBuilder();\n while (await reader.ReadLineAsync() is { } line)\n {\n if (string.IsNullOrEmpty(line) || !line.StartsWith(\"data: \"))\n continue;\n\n var data = line[\"data: \".Length..];\n if (data == \"[DONE]\")\n break;\n\n try\n {\n using var doc = JsonDocument.Parse(data);\n var delta = doc.RootElement\n .GetProperty(\"choices\")[0]\n .GetProperty(\"delta\");\n\n if (delta.TryGetProperty(\"content\", out var contentElement))\n {\n var content = contentElement.GetString();\n if (!string.IsNullOrEmpty(content))\n {\n fullText.Append(content);\n onChunk?.Invoke(content);\n }\n }\n }\n catch { /* 忽略解析错误 */ }\n }\n\n var result = fullText.ToString().Trim();\n return result;\n }\n catch (Exception ex)\n {\n System.Diagnostics.Debug.WriteLine($\"[翻译] 流式错误: {ex.Message}\");\n return $\"[翻译失败] {ex.Message}\";\n }\n }\n\n public void Dispose()\n {\n _httpClient.Dispose();\n }\n\n private static string GetLanguageName(string code)\n {\n return code.ToLower() switch\n {\n \"en\" => \"English\",\n \"zh\" => \"Chinese\",\n \"ja\" => \"Japanese\",\n \"ko\" => \"Korean\",\n \"fr\" => \"French\",\n \"de\" => \"German\",\n \"es\" => \"Spanish\",\n \"ru\" => \"Russian\",\n \"pt\" => \"Portuguese\",\n \"ar\" => \"Arabic\",\n _ => code\n };\n }\n}\n"}, "ec5ac876c6bc951be0ab2c7c21f54560dc69dbac": {"t": 1785760508.754238, "n": 6538, "h": "e71ef3477bef4096", "c": "using System.IO;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Text;\nusing System.Text.Json;\n\nnamespace RoastSub.Services;\n\n/// <summary>\n/// DeepSeek AI 翻译服务(OpenAI 兼容格式)\n/// </summary>\npublic class DeepSeekTranslationService : IDisposable\n{\n private readonly HttpClient _httpClient;\n private string _apiKey = \"\";\n private string _baseUrl = \"https://api.deepseek.com\";\n private string _model = \"deepseek-v4-flash\"; // 便宜快速,适合翻译\n\n public DeepSeekTranslationService()\n {\n _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };\n }\n\n /// <summary>设置 API Key</summary>\n public void SetApiKey(string apiKey)\n {\n _apiKey = apiKey;\n }\n\n /// <summary>设置模型</summary>\n public void SetModel(string model)\n {\n _model = model;\n }\n\n /// <summary>\n /// 流式翻译文本(带逐 token 回调 + 上下文历史)\n /// </summary>\n /// <param name=\"text\">要翻译的文本</param>\n /// <param name=\"onChunk\">每收到一个 token 的回调</param>\n /// <param name=\"context\">上文历史(前几条识别出的原文)</param>\n /// <param name=\"sourceLang\">源语言</param>\n /// <param name=\"targetLang\">目标语言</param>\n public async Task<string> TranslateStreamAsync(string text, Action<string>? onChunk, List<string>? context = null, string sourceLang = \"en\", string targetLang = \"zh\")\n {\n if (string.IsNullOrWhiteSpace(text) || string.IsNullOrEmpty(_apiKey))\n return \"\";\n\n try\n {\n var request = new HttpRequestMessage(HttpMethod.Post, $\"{_baseUrl}/chat/completions\");\n request.Headers.Authorization = new AuthenticationHeaderValue(\"Bearer\", _apiKey);\n\n // Build system prompt\n var systemPrompt = \"\";\n\n // 1. Base Setting\n systemPrompt += \"===Base Setting===\\n\";\n systemPrompt += $\"You are a veteran {GetLanguageName(sourceLang)}-to-{GetLanguageName(targetLang)} subtitle translator with 10 years of experience, specializing in translating colloquial content for TV talk shows, podcasts, and online programs. You excel at transforming everyday spoken {GetLanguageName(sourceLang)}, including talk-show banter, into natural, smooth, and tonally appropriate spoken {GetLanguageName(targetLang)} that sounds as if the host is speaking it, not like a stilted written translation. You adapt word choice based on context (e.g., openings, transitions, interviews) while maintaining full consistency in terminology and tone throughout.\";\n\n // 2. Context\n if (context != null && context.Count > 0)\n {\n systemPrompt += \"\\n\\n===Context===\\n\";\n systemPrompt += \"The following are previous sentences for context. Use them to maintain consistent terminology and style, but DO NOT translate them:\\n\";\n systemPrompt += string.Join(\"\\n\", context);\n }\n\n // 3. Text to translate\n systemPrompt += \"\\n\\n===Text to Translate===\\n\";\n systemPrompt += \"Translate the following text:\\n\";\n systemPrompt += text;\n\n // 4. Rules\n systemPrompt += \"\\n\\n===Rules===\\n\";\n systemPrompt += $\"- Translate the text from {GetLanguageName(sourceLang)} to {GetLanguageName(targetLang)}.\";\n systemPrompt += \"\\n- Always translate regardless of the input content, even if it is a single word, fragment, or seems incomplete. Never question or refuse.\";\n systemPrompt += \"\\n- Only output the translation, no explanations.\";\n systemPrompt += \"\\n- The translation must be suitable for reading aloud, with natural rhythm, like a host's voiceover; avoid overly literal or stiff translations that would sound unnatural when spoken.\";\n systemPrompt += \"\\n- Maintain consistent tone, address style, and familiarity level with the previous lines.\";\n\n var messages = new object[]\n {\n new { role = \"system\", content = systemPrompt }\n };\n\n var body = new\n {\n model = _model,\n messages = messages,\n stream = true,\n temperature = 0.3,\n thinking = new { type = \"disabled\" }\n };\n\n request.Content = new StringContent(\n JsonSerializer.Serialize(body),\n Encoding.UTF8,\n \"application/json\");\n\n var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);\n response.EnsureSuccessStatusCode();\n\n var stream = await response.Content.ReadAsStreamAsync();\n using var reader = new StreamReader(stream);\n\n var fullText = new StringBuilder();\n while (await reader.ReadLineAsync() is { } line)\n {\n if (string.IsNullOrEmpty(line) || !line.StartsWith(\"data: \"))\n continue;\n\n var data = line[\"data: \".Length..];\n if (data == \"[DONE]\")\n break;\n\n try\n {\n using var doc = JsonDocument.Parse(data);\n var delta = doc.RootElement\n .GetProperty(\"choices\")[0]\n .GetProperty(\"delta\");\n\n if (delta.TryGetProperty(\"content\", out var contentElement))\n {\n var content = contentElement.GetString();\n if (!string.IsNullOrEmpty(content))\n {\n fullText.Append(content);\n onChunk?.Invoke(content);\n }\n }\n }\n catch { /* 忽略解析错误 */ }\n }\n\n var result = fullText.ToString().Trim();\n return result;\n }\n catch (Exception ex)\n {\n System.Diagnostics.Debug.WriteLine($\"[翻译] 流式错误: {ex.Message}\");\n return $\"[翻译失败] {ex.Message}\";\n }\n }\n\n public void Dispose()\n {\n _httpClient.Dispose();\n }\n\n private static string GetLanguageName(string code)\n {\n return code.ToLower() switch\n {\n \"en\" => \"English\",\n \"zh\" => \"Chinese\",\n \"ja\" => \"Japanese\",\n \"ko\" => \"Korean\",\n \"fr\" => \"French\",\n \"de\" => \"German\",\n \"es\" => \"Spanish\",\n \"ru\" => \"Russian\",\n \"pt\" => \"Portuguese\",\n \"ar\" => \"Arabic\",\n _ => code\n };\n }\n}\n"}, "a2905bb3c19eaf01e676ec2727f0f98ada4029de": {"t": 1785760509.9386113, "n": 7297, "h": "6fb1fce2e1af33c5", "c": "using System.IO;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Text;\nusing System.Text.Json;\n\nnamespace RoastSub.Services;\n\n/// <summary>\n/// DeepSeek AI 翻译服务(OpenAI 兼容格式)\n/// </summary>\npublic class DeepSeekTranslationService : IDisposable\n{\n private readonly HttpClient _httpClient;\n private string _apiKey = \"\";\n private string _baseUrl = \"https://api.deepseek.com\";\n private string _model = \"deepseek-v4-flash\"; // 便宜快速,适合翻译\n\n /// <summary>翻译结果事件(流式输出)</summary>\n public event EventHandler<string>? TranslationReceived;\n\n /// <summary>完整翻译结果事件</summary>\n public event EventHandler<string>? TranslationCompleted;\n\n public DeepSeekTranslationService()\n {\n _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };\n }\n\n /// <summary>设置 API Key</summary>\n public void SetApiKey(string apiKey)\n {\n _apiKey = apiKey;\n }\n\n /// <summary>设置模型</summary>\n public void SetModel(string model)\n {\n _model = model;\n }\n\n /// <summary>\n /// 翻译文本(非流式)\n /// </summary>\n public async Task<string> TranslateAsync(string text, string sourceLang = \"en\", string targetLang = \"zh\")\n {\n if (string.IsNullOrWhiteSpace(text) || string.IsNullOrEmpty(_apiKey))\n return \"\";\n\n try\n {\n var request = new HttpRequestMessage(HttpMethod.Post, $\"{_baseUrl}/chat/completions\");\n request.Headers.Authorization = new AuthenticationHeaderValue(\"Bearer\", _apiKey);\n\n var body = new\n {\n model = _model,\n messages = new[]\n {\n new { role = \"system\", content = $\"You are a professional translator. Translate the following text from {sourceLang} to {targetLang}. Always translate regardless of the input content, even if it is a single word, fragment, or seems incomplete. Never question or refuse. Only output the translation, no explanations.\" },\n new { role = \"user\", content = text }\n },\n stream = false,\n temperature = 0.3\n };\n\n request.Content = new StringContent(\n JsonSerializer.Serialize(body),\n Encoding.UTF8,\n \"application/json\");\n\n var response = await _httpClient.SendAsync(request);\n response.EnsureSuccessStatusCode();\n\n var json = await response.Content.ReadAsStringAsync();\n using var doc = JsonDocument.Parse(json);\n\n var content = doc.RootElement\n .GetProperty(\"choices\")[0]\n .GetProperty(\"message\")\n .GetProperty(\"content\")\n .GetString();\n\n return content?.Trim() ?? \"\";\n }\n catch (Exception ex)\n {\n System.Diagnostics.Debug.WriteLine($\"[翻译] 错误: {ex.Message}\");\n return $\"[翻译失败] {ex.Message}\";\n }\n }\n\n /// <summary>\n /// 流式翻译文本\n /// </summary>\n public async Task<string> TranslateStreamAsync(string text, string sourceLang = \"en\", string targetLang = \"zh\")\n {\n return await TranslateStreamAsync(text, onChunk: null, context: null, sourceLang: sourceLang, targetLang: targetLang);\n }\n\n /// <summary>\n /// 流式翻译文本(带逐 token 回调 + 上下文历史)\n /// </summary>\n /// <param name=\"text\">要翻译的文本</param>\n /// <param name=\"onChunk\">每收到一个 token 的回调</param>\n /// <param name=\"context\">上文历史(前几条识别出的原文)</param>\n /// <param name=\"sourceLang\">源语言</param>\n /// <param name=\"targetLang\">目标语言</param>\n public async Task<string> TranslateStreamAsync(string text, Action<string>? onChunk, List<string>? context = null, string sourceLang = \"en\", string targetLang = \"zh\")\n {\n if (string.IsNullOrWhiteSpace(text) || string.IsNullOrEmpty(_apiKey))\n return \"\";\n\n try\n {\n var request = new HttpRequestMessage(HttpMethod.Post, $\"{_baseUrl}/chat/completions\");\n request.Headers.Authorization = new AuthenticationHeaderValue(\"Bearer\", _apiKey);\n\n // 构建 messages,注入上下文\n var messages = new List<object>\n {\n new { role = \"system\", content = $\"You are a professional translator. Translate the following text from {sourceLang} to {targetLang}. Always translate regardless of the input content, even if it is a single word, fragment, or seems incomplete. Never question or refuse. Only output the translation, no explanations. Maintain consistent terminology and style with previous translations.\" }\n };\n\n // 添加上文原文作为上下文,帮助 LLM 理解语境连贯性\n if (context != null && context.Count > 0)\n {\n var contextText = string.Join(\"\\n\", context);\n messages.Add(new { role = \"user\", content = $\"Previous sentences for context (do not translate these):\\n{contextText}\" });\n messages.Add(new { role = \"assistant\", content = \"Understood. I will maintain consistency with the context.\" });\n }\n\n messages.Add(new { role = \"user\", content = text });\n\n var body = new\n {\n model = _model,\n messages = messages.ToArray(),\n stream = true,\n temperature = 0.3\n };\n\n request.Content = new StringContent(\n JsonSerializer.Serialize(body),\n Encoding.UTF8,\n \"application/json\");\n\n var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);\n response.EnsureSuccessStatusCode();\n\n var stream = await response.Content.ReadAsStreamAsync();\n using var reader = new StreamReader(stream);\n\n var fullText = new StringBuilder();\n while (await reader.ReadLineAsync() is { } line)\n {\n if (string.IsNullOrEmpty(line) || !line.StartsWith(\"data: \"))\n continue;\n\n var data = line[\"data: \".Length..];\n if (data == \"[DONE]\")\n break;\n\n try\n {\n using var doc = JsonDocument.Parse(data);\n var delta = doc.RootElement\n .GetProperty(\"choices\")[0]\n .GetProperty(\"delta\");\n\n if (delta.TryGetProperty(\"content\", out var contentElement))\n {\n var content = contentElement.GetString();\n if (!string.IsNullOrEmpty(content))\n {\n fullText.Append(content);\n TranslationReceived?.Invoke(this, content);\n onChunk?.Invoke(content);\n }\n }\n }\n catch { /* 忽略解析错误 */ }\n }\n\n var result = fullText.ToString().Trim();\n TranslationCompleted?.Invoke(this, result);\n return result;\n }\n catch (Exception ex)\n {\n System.Diagnostics.Debug.WriteLine($\"[翻译] 流式错误: {ex.Message}\");\n return $\"[翻译失败] {ex.Message}\";\n }\n }\n\n public void Dispose()\n {\n _httpClient.Dispose();\n }\n}\n"}}}}