1- using System . Net ;
1+ using System . Net ;
22using System . Net . Http ;
33using System . Text ;
44using System . Text . Json ;
@@ -23,6 +23,7 @@ public static readonly Dictionary<string, Func<string, CancellationToken, Task<s
2323 { "Google2" , Google2 } ,
2424 { "Ollama" , Ollama } ,
2525 { "OpenAI" , OpenAI } ,
26+ { "LMStudio" , LMStudio } ,
2627 { "DeepL" , DeepL } ,
2728 { "OpenRouter" , OpenRouter } ,
2829 { "Youdao" , Youdao } ,
@@ -32,7 +33,7 @@ public static readonly Dictionary<string, Func<string, CancellationToken, Task<s
3233 } ;
3334 public static readonly List < string > LLM_BASED_APIS = new ( )
3435 {
35- "Ollama" , "OpenAI" , "OpenRouter"
36+ "Ollama" , "OpenAI" , "OpenRouter" , "LMStudio"
3637 } ;
3738 public static readonly List < string > NO_CONFIG_APIS = new ( )
3839 {
@@ -191,6 +192,92 @@ public static async Task<string> Ollama(string text, CancellationToken token = d
191192 return $ "[ERROR] Translation Failed: HTTP Error - { response . StatusCode } ";
192193 }
193194
195+ public static async Task < string > LMStudio ( string text , CancellationToken token = default )
196+ {
197+ var config = Translator . Setting [ "LMStudio" ] as LMStudioConfig ;
198+ string language = LMStudioConfig . SupportedLanguages . TryGetValue (
199+ Translator . Setting . TargetLanguage , out var langValue ) ? langValue : Translator . Setting . TargetLanguage ;
200+ string apiUrl = TextUtil . NormalizeUrl ( config . ApiUrl ) + "/chat" ;
201+
202+ string systemPrompt = string . Format ( Prompt , language ) ;
203+
204+ // Build input with optional context
205+ string input = $ "🔤 { text } 🔤";
206+ if ( Translator . Setting . ContextAware )
207+ {
208+ var contextLines = new List < string > ( ) ;
209+ foreach ( var entry in Translator . Caption . AwareContexts )
210+ {
211+ string translatedText = entry . TranslatedText ;
212+ if ( translatedText . Contains ( "[ERROR]" ) || translatedText . Contains ( "[WARNING]" ) )
213+ continue ;
214+ translatedText = RegexPatterns . NoticePrefix ( ) . Replace ( translatedText , "" ) ;
215+ contextLines . Add ( $ "🔤 { entry . SourceText } 🔤 → { translatedText } ") ;
216+ }
217+ if ( contextLines . Count > 0 )
218+ input = string . Join ( "\n " , contextLines ) + "\n " + input ;
219+ }
220+
221+ var requestData = new
222+ {
223+ model = config . ModelName ,
224+ system_prompt = systemPrompt ,
225+ input = input ,
226+ temperature = config . Temperature
227+ } ;
228+
229+ string jsonContent = JsonSerializer . Serialize ( requestData ) ;
230+ var content = new StringContent ( jsonContent , Encoding . UTF8 , "application/json" ) ;
231+ client . DefaultRequestHeaders . Clear ( ) ;
232+
233+ HttpResponseMessage response ;
234+ try
235+ {
236+ response = await client . PostAsync ( apiUrl , content , token ) ;
237+ }
238+ catch ( OperationCanceledException ex )
239+ {
240+ if ( ex . Message . StartsWith ( "The request" ) )
241+ return $ "[ERROR] Translation Failed: The request was canceled due to timeout (> 8 seconds), " +
242+ $ "please use a faster API or check network connection.";
243+ throw ;
244+ }
245+ catch ( Exception ex )
246+ {
247+ return $ "[ERROR] Translation Failed: { ex . Message } ";
248+ }
249+
250+ if ( response . IsSuccessStatusCode )
251+ {
252+ string responseString = await response . Content . ReadAsStringAsync ( ) ;
253+ using var doc = JsonDocument . Parse ( responseString ) ;
254+ var root = doc . RootElement ;
255+
256+ // LMStudio native /api/v1/chat response:
257+ // { "output": [ { "type": "message", "content": "..." }, ... ] }
258+ if ( root . TryGetProperty ( "output" , out var outputArray ) &&
259+ outputArray . ValueKind == JsonValueKind . Array )
260+ {
261+ foreach ( var item in outputArray . EnumerateArray ( ) )
262+ {
263+ if ( item . TryGetProperty ( "type" , out var typeProp ) &&
264+ typeProp . GetString ( ) == "message" &&
265+ item . TryGetProperty ( "content" , out var contentProp ) )
266+ {
267+ return RegexPatterns . ModelThinking ( ) . Replace ( contentProp . GetString ( ) ?? "" , "" ) ;
268+ }
269+ }
270+ }
271+
272+ return "[ERROR] Translation Failed: Unexpected response format" ;
273+ }
274+ else
275+ {
276+ string body = await response . Content . ReadAsStringAsync ( ) ;
277+ return $ "[ERROR] Translation Failed: HTTP Error - { response . StatusCode } : { body } ";
278+ }
279+ }
280+
194281 public static async Task < string > OpenRouter ( string text , CancellationToken token = default )
195282 {
196283 var config = Translator . Setting [ "OpenRouter" ] as OpenRouterConfig ;
0 commit comments