Cursor rule
.cursor/rules/service-integration-patterns.mdcPatterns for integrating external services (APIs, OCR, TTS)
Cursor rules
Quality
69/100
Scores the file, not the repository.Length
1,236 words
41 headings · 25 code blocksRepository
103
— · pushed 17 days agoLast changed
3 days ago
First indexed 3 days ago.1234567# Service Integration Patterns89## Translation Service Interface1011### ITranslationService Pattern12All translation services implement a common interface:1314```csharp15public interface ITranslationService16{17 Task<TranslationResult?> TranslateAsync(18 string sourceText,19 string targetLanguage,20 string context);21}22```2324### TranslationResult Structure25```csharp26public class TranslationResult27{28 public string TranslatedText { get; set; } = "";29 public string SourceLanguage { get; set; } = "";30 public double Confidence { get; set; } = 1.0;31}32```3334## HTTP Client Usage3536### Service HTTP Client Pattern37Each service creates its own HttpClient:3839```csharp40public class GeminiTranslationService : ITranslationService41{42 private static readonly HttpClient _httpClient = new HttpClient();43 private readonly string _apiKey;4445 public GeminiTranslationService()46 {47 _apiKey = ConfigManager.Instance.GetGeminiApiKey();48 }4950 public async Task<TranslationResult?> TranslateAsync(string text, string lang, string context)51 {52 // Implementation53 }54}55```5657### Request Building58```csharp59var requestBody = new60{61 contents = new[]62 {63 new64 {65 parts = new[]66 {67 new { text = prompt }68 }69 }70 }71};7273var json = JsonSerializer.Serialize(requestBody);74var content = new StringContent(json, Encoding.UTF8, "application/json");75var response = await _httpClient.PostAsync(url, content);76```7778## Error Handling in Services7980### HTTP Error Handling Pattern81```csharp82if (response.IsSuccessStatusCode)83{84 var result = await response.Content.ReadAsStringAsync();85 return ParseResult(result);86}87else88{89 string errorMessage = await response.Content.ReadAsStringAsync();90 Console.WriteLine($"API error: {response.StatusCode}, {errorMessage}");9192 // Try to parse JSON error93 try94 {95 using JsonDocument errorDoc = JsonDocument.Parse(errorMessage);96 if (errorDoc.RootElement.TryGetProperty("error", out JsonElement errorElement))97 {98 // Extract detailed error99 return null;100 }101 }102 catch (JsonException)103 {104 // Fallback to raw message105 }106107 // Show user-friendly error108 ErrorPopupManager.ShowError("Translation Error", errorMessage);109110 return null;111}112```113114### Exception Handling115```csharp116try117{118 var response = await _httpClient.PostAsync(url, content);119 // Process response120}121catch (Exception ex)122{123 Console.WriteLine($"Service error: {ex.Message}");124 LogManager.Instance.LogError("Translation failed", ex);125126 ErrorPopupManager.ShowError("Translation Error", ex.Message);127128 return null;129}130```131132## Service Factory Pattern133134### TranslationServiceFactory135```csharp136public static class TranslationServiceFactory137{138 public static ITranslationService CreateService(string serviceName)139 {140 return serviceName switch141 {142 "Gemini" => new GeminiTranslationService(),143 "ChatGPT" => new ChatGptTranslationService(),144 "Ollama" => new OllamaTranslationService(),145 "Google Translate" => new GoogleTranslateService(),146 "llama.cpp" => new LlamaCppTranslationService(),147 _ => throw new ArgumentException($"Unknown service: {serviceName}")148 };149 }150}151```152153## OCR Service Integration154155### Windows OCR Pattern156```csharp157public class WindowsOCRManager158{159 public async Task<List<TextObject>> ProcessImageAsync(Bitmap bitmap)160 {161 return await Task.Run(() =>162 {163 // Convert bitmap to Windows bitmap164 // Process with Windows OCR API165 // Return TextObject list166 });167 }168}169```170171### Google Vision OCR Pattern172```csharp173public class GoogleVisionOCRService174{175 public async Task<List<TextObject>> ProcessImageAsync(Bitmap bitmap)176 {177 // Convert bitmap to base64178 // Send to Google Vision API179 // Parse response and return TextObject list180 }181}182```183184### Python OCR Service Pattern (HTTP-based)185```csharp186// Services are managed via PythonServicesManager187// Discover services on startup188PythonServicesManager.Instance.DiscoverServices();189190// Get service by name191var service = PythonServicesManager.Instance.GetServiceByName("EasyOCR");192193// Check if service is running194if (!service.IsRunning)195{196 bool isRunning = await service.CheckIsRunningAsync();197 if (!isRunning)198 {199 // Show error dialog or start service200 await service.StartAsync(showWindow: false);201 }202}203204// Process image with HTTP service205private async Task<string?> ProcessImageWithHttpServiceAsync(byte[] imageBytes, string serviceName, string language)206{207 var service = PythonServicesManager.Instance.GetServiceByName(serviceName);208 if (service == null) return null;209210 // Build URL with query parameters211 string langParam = MapLanguageForService(language);212 string url = $"{service.ServerUrl}:{service.Port}/process?lang={langParam}&char_level=true";213214 // Add service-specific parameters (e.g., MangaOCR)215 if (serviceName == "MangaOCR")216 {217 url += $"&min_region_width={minWidth}&min_region_height={minHeight}&overlap_allowed_percent={overlapPercent}";218 }219220 // Add PaddleOCR-specific parameters221 if (serviceName == "PaddleOCR")222 {223 bool useAngleCls = ConfigManager.Instance.GetBoolValue(ConfigManager.PADDLE_OCR_USE_ANGLE_CLS, false);224 url += $"&use_angle_cls={useAngleCls.ToString().ToLower()}";225 }226227 // Send binary image data228 var content = new ByteArrayContent(imageBytes);229 content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");230231 using var request = new HttpRequestMessage(HttpMethod.Post, url);232 request.Content = content;233 request.Headers.ConnectionClose = false; // Enable HTTP keep-alive234 var response = await _httpClient.SendAsync(request);235236 if (!response.IsSuccessStatusCode)237 {238 service.MarkAsNotRunning();239 return null;240 }241242 return await response.Content.ReadAsStringAsync();243}244```245246## Text-to-Speech Integration247248### TTS Service Pattern249```csharp250public interface ITTSService251{252 Task<byte[]?> SynthesizeAsync(string text, string language);253}254255public class GoogleTTSService : ITTSService256{257 public async Task<byte[]?> SynthesizeAsync(string text, string language)258 {259 var url = $"https://texttospeech.googleapis.com/v1/text:synthesize";260 // Build request and get audio bytes261 return audioBytes;262 }263}264```265266## Audio Processing267268### NAudio Integration269```csharp270using NAudio.Wave;271272public class AudioPlaybackManager273{274 private WaveOutEvent? _waveOut;275276 public void PlayAudio(byte[] audioData)277 {278 _waveOut?.Stop();279 _waveOut?.Dispose();280281 using var ms = new MemoryStream(audioData);282 using var reader = new Mp3FileReader(ms);283284 _waveOut = new WaveOutEvent();285 _waveOut.Init(reader);286 _waveOut.Play();287 }288}289```290291### Audio Preloading292```csharp293public class AudioPreloadService294{295 // Preload audio for source and target languages296 public async Task PreloadAudioAsync(string text, string language, string service)297 {298 // Generate audio and cache it299 // Used for faster playback later300 }301}302```303304## Real-time Audio Streaming305306### WebSocket Pattern307```csharp308public class OpenAIRealtimeAudioService309{310 private ClientWebSocket? _webSocket;311312 public async Task ConnectAsync()313 {314 _webSocket = new ClientWebSocket();315 await _webSocket.ConnectAsync(new Uri("wss://api.openai.com/v1/realtime"), CancellationToken.None);316317 // Start receiving loop318 _ = Task.Run(ReceiveLoop);319 }320321 private async Task ReceiveLoop()322 {323 var buffer = new byte[4096];324 while (_webSocket?.State == WebSocketState.Open)325 {326 var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);327 ProcessMessage(buffer, result.Count);328 }329 }330}331```332333## Service Configuration334335### Python OCR Service Configuration336Python OCR services are configured via `service_config.txt` files in each service directory:337338```csharp339// Service discovery reads service_config.txt340// Format: key|value|341// Fields: service_name, venv_name, port, description, version, author, github_url, local_only, server_url342343// C# PythonService.ParseFromConfig() reads the config file344var service = PythonService.ParseFromConfig(serviceDirectory);345346// Service properties available:347// - service.ServiceName (from service_name)348// - service.VenvName (from venv_name)349// - service.Port (from port)350// - service.Description, Version, Author, GithubUrl, etc.351```352353### Translation Service Configuration354Translation services read their configuration from ConfigManager:355356```csharp357public class GeminiTranslationService358{359 private readonly string _apiKey;360 private readonly string _model;361 private readonly string _prompt;362363 public GeminiTranslationService()364 {365 _apiKey = ConfigManager.Instance.GetGeminiApiKey();366 _model = ConfigManager.Instance.GetGeminiModel();367 _prompt = ConfigManager.Instance.GetGeminiPrompt();368 }369}370```371372## Retry Logic373374### Exponential Backoff Pattern375```csharp376public async Task<TranslationResult?> TranslateWithRetryAsync(string text, int maxRetries = 3)377{378 for (int attempt = 0; attempt < maxRetries; attempt++)379 {380 try381 {382 return await TranslateAsync(text);383 }384 catch (Exception ex)385 {386 if (attempt == maxRetries - 1)387 {388 throw;389 }390391 int delay = (int)Math.Pow(2, attempt) * 1000; // Exponential backoff392 await Task.Delay(delay);393 }394 }395396 return null;397}398```399400## Timeout Handling401402### Request Timeout403```csharp404private static readonly HttpClient _httpClient = new HttpClient405{406 Timeout = TimeSpan.FromSeconds(30)407};408```409410### Cancellation Token411```csharp412using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));413var response = await _httpClient.PostAsync(url, content, cts.Token);414```415416## JSON Serialization417418### System.Text.Json Pattern419```csharp420using System.Text.Json;421422var options = new JsonSerializerOptions423{424 PropertyNamingPolicy = JsonNamingPolicy.CamelCase,425 WriteIndented = false426};427428var json = JsonSerializer.Serialize(requestObject, options);429var responseObject = JsonSerializer.Deserialize<ResponseType>(jsonString, options);430```431432## Service Health Checks433434### Connection Testing435```csharp436public async Task<bool> TestConnectionAsync()437{438 try439 {440 var testRequest = new { text = "test" };441 var result = await TranslateAsync("test", "en", "");442 return result != null;443 }444 catch445 {446 return false;447 }448}449```450451### Python Service Health Check452```csharp453public async Task<bool> CheckServiceHealthAsync(PythonService service)454{455 try456 {457 var url = $"{service.ServerUrl}:{service.Port}/health";458 var response = await _httpClient.GetAsync(url);459 return response.IsSuccessStatusCode;460 }461 catch462 {463 return false;464 }465}466```467468## Service Discovery469470### Python Service Discovery471```csharp472// Automatically discovers services in app/services/473PythonServicesManager.Instance.DiscoverServices();474475// Get all discovered services476var services = PythonServicesManager.Instance.GetAllServices();477478// Get service by name479var easyOCR = PythonServicesManager.Instance.GetServiceByName("EasyOCR");480```481482## Service Lifecycle Management483484### Starting Services485```csharp486public async Task<bool> StartServiceAsync(PythonService service, bool showWindow = false)487{488 if (service.IsRunning)489 {490 return true;491 }492493 // Start Python server process494 // Wait for health check495 // Mark as running496}497```498499### Stopping Services500```csharp501public async Task StopServiceAsync(PythonService service)502{503 if (!service.IsRunning)504 {505 return;506 }507508 // Send shutdown request509 // Wait for process to exit510 // Mark as not running511}512```513
Also in SethRobinson/UGTLive
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| SethRobinson/UGTLive.cursor/rules/ui-ux-patterns.mdc · 103 | Cursor rules | stylearchtypesui+1 | 62/100 | 3 days ago | |
| SethRobinson/UGTLive.cursor/rules/architecture-patterns.mdc · 103 | Cursor rules | buildtestlint-formatstyle+6 | 77/100 | 3 days ago | |
| SethRobinson/UGTLive.cursor/rules/async-threading-patterns.mdc · 103 | Cursor rules | styleuido-not | 65/100 | 3 days ago | |
| SethRobinson/UGTLive.cursor/rules/code-style-conventions.mdc · 103 | Cursor rules | stylearchtypesdocs | 62/100 | 3 days ago | |
| SethRobinson/UGTLive.cursor/rules/configuration-management.mdc · 103 | Cursor rules | archsecuritydatabaseapi+1 | 65/100 | 3 days ago | |
| SethRobinson/UGTLive.cursor/rules/debugging-testing.mdc · 103 | Cursor rules | buildteststylearch+3 | 69/100 | 3 days ago | |
| SethRobinson/UGTLive.cursor/rules/locale-invariant-formatting.mdc · 103 | Cursor rules | lint-formatuido-not | 61/100 | 3 days ago | |
| SethRobinson/UGTLive.cursor/rules/translation-workflow.mdc · 103 | Cursor rules | testlint-formatarchapi+3 | 66/100 | 3 days ago | |
| SethRobinson/UGTLive.cursor/rules/ugtlive-project-guide.mdc · 103 | Cursor rules | stylearchuideployment+1 | 69/100 | 3 days ago | |
| SethRobinson/UGTLive.cursor/rules/versioning-workflow.mdc · 103 | Cursor rules | stylegitdeploymentagent-behaviour+1 | 62/100 | 3 days ago | |
| SethRobinson/UGTLive.cursor/rules/wpf-ui-patterns.mdc · 103 | Cursor rules | setuplint-formatstylearch+1 | 66/100 | 3 days ago | |
| SethRobinson/UGTLiveAGENTS.md · 103 | AGENTS.md | testgitsecurityperformance+1 | 74/100 | 3 days ago |
Diff against .cursor/rules/ui-ux-patterns.mdc Diff against .cursor/rules/architecture-patterns.mdc Diff against .cursor/rules/async-threading-patterns.mdc Diff against .cursor/rules/code-style-conventions.mdc Diff against .cursor/rules/configuration-management.mdc Diff against .cursor/rules/debugging-testing.mdc Diff against .cursor/rules/locale-invariant-formatting.mdc Diff against .cursor/rules/translation-workflow.mdc Diff against .cursor/rules/ugtlive-project-guide.mdc Diff against .cursor/rules/versioning-workflow.mdc Diff against .cursor/rules/wpf-ui-patterns.mdc Diff against AGENTS.md
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| ThanhTrunggDEV/DontBeLazy.cursor/rules/rust-testing.mdc · 6 | Cursor rules | teststyletesting-strategy | 90/100 | 3 days ago | |
| ThanhTrunggDEV/DontBeLazy.cursor/rules/dart-testing.mdc · 6 | Cursor rules | teststyletypestesting-strategy | 85/100 | 3 days ago | |
| ThanhTrunggDEV/DontBeLazy.cursor/rules/dart-coding-style.mdc · 6 | Cursor rules | lint-formatstyletypesdo-not | 84/100 | 3 days ago | |
| ThanhTrunggDEV/DontBeLazy.cursor/rules/rust-security.mdc · 6 | Cursor rules | stylesecuritydatabasedo-not | 80/100 | 3 days ago | |
| imazen/imageflow.cursor/rules/ci.mdc · 4.4k | Cursor rules | setupbuildtestarch+3 | 79/100 | 3 days ago | |
| ThanhTrunggDEV/DontBeLazy.cursor/rules/rust-coding-style.mdc · 6 | Cursor rules | lint-formatstyle | 78/100 | 3 days ago | |
| ThanhTrunggDEV/DontBeLazy.cursor/rules/dart-hooks.mdc · 6 | Cursor rules | lint-formatarchtesting-strategygit+1 | 78/100 | 3 days ago | |
| SethRobinson/UGTLive.cursor/rules/architecture-patterns.mdc · 103 | Cursor rules | buildtestlint-formatstyle+6 | 77/100 | 3 days ago |
