markdown_translator/
engine.rs

1//! 统一的翻译引擎模块
2//!
3//! 这个模块合并了原来的翻译引擎和服务,提供了一个统一的、
4//! 函数式风格的翻译处理系统。
5//!
6//! ## 设计原则
7//!
8//! - **函数式组合**: 使用函数组合和管道操作
9//! - **不可变性**: 优先使用不可变数据结构
10//! - **错误处理**: 统一的Result类型和错误传播
11//! - **异步友好**: 原生支持async/await模式
12
13use std::sync::Arc;
14use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
15use std::time::{Instant, Duration};
16
17use crate::{
18    TranslationService as BaseTranslationService,
19    functional::{TextItem, TextFilter, BatchManager, Batch},
20    collector::{DomNode, TextCollector},
21    simple_config::SimpleTranslationConfig,
22    error::{TranslationError, Result as TranslationResult},
23};
24
25// ============================================================================
26// 统计和监控
27// ============================================================================
28
29/// 翻译引擎统计信息
30#[derive(Debug, Default)]
31pub struct EngineStats {
32    /// 处理的文本总数
33    pub texts_processed: AtomicUsize,
34    /// 翻译的批次总数
35    pub batches_processed: AtomicUsize,
36    /// 缓存命中次数
37    pub cache_hits: AtomicUsize,
38    /// 缓存未命中次数
39    pub cache_misses: AtomicUsize,
40    /// 翻译API调用次数
41    pub api_calls: AtomicUsize,
42    /// 总翻译时间(毫秒)
43    pub total_translation_time_ms: AtomicU64,
44    /// 错误次数
45    pub error_count: AtomicUsize,
46}
47
48impl EngineStats {
49    pub fn new() -> Self {
50        Self::default()
51    }
52
53    pub fn texts_processed(&self) -> usize {
54        self.texts_processed.load(Ordering::Relaxed)
55    }
56
57    pub fn batches_processed(&self) -> usize {
58        self.batches_processed.load(Ordering::Relaxed)
59    }
60
61    pub fn cache_hit_rate(&self) -> f64 {
62        let hits = self.cache_hits.load(Ordering::Relaxed);
63        let misses = self.cache_misses.load(Ordering::Relaxed);
64        let total = hits + misses;
65        if total == 0 {
66            0.0
67        } else {
68            hits as f64 / total as f64
69        }
70    }
71
72    pub fn average_translation_time_ms(&self) -> f64 {
73        let total_time = self.total_translation_time_ms.load(Ordering::Relaxed);
74        let api_calls = self.api_calls.load(Ordering::Relaxed);
75        if api_calls == 0 {
76            0.0
77        } else {
78            total_time as f64 / api_calls as f64
79        }
80    }
81
82    pub fn error_rate(&self) -> f64 {
83        let errors = self.error_count.load(Ordering::Relaxed);
84        let total = self.api_calls.load(Ordering::Relaxed);
85        if total == 0 {
86            0.0
87        } else {
88            errors as f64 / total as f64
89        }
90    }
91
92    fn increment_texts_processed(&self, count: usize) {
93        self.texts_processed.fetch_add(count, Ordering::Relaxed);
94    }
95
96    fn increment_batches_processed(&self) {
97        self.batches_processed.fetch_add(1, Ordering::Relaxed);
98    }
99
100    fn increment_cache_hits(&self) {
101        self.cache_hits.fetch_add(1, Ordering::Relaxed);
102    }
103
104    fn increment_cache_misses(&self) {
105        self.cache_misses.fetch_add(1, Ordering::Relaxed);
106    }
107
108    fn increment_api_calls(&self) {
109        self.api_calls.fetch_add(1, Ordering::Relaxed);
110    }
111
112    fn add_translation_time(&self, duration: Duration) {
113        self.total_translation_time_ms.fetch_add(
114            duration.as_millis() as u64, 
115            Ordering::Relaxed
116        );
117    }
118
119    fn increment_errors(&self) {
120        self.error_count.fetch_add(1, Ordering::Relaxed);
121    }
122}
123
124// ============================================================================
125// 简单缓存实现
126// ============================================================================
127
128use std::collections::HashMap;
129use std::sync::Mutex;
130
131/// 简单的内存缓存
132struct SimpleCache {
133    cache: Mutex<HashMap<String, (String, Instant)>>,
134    ttl: Duration,
135}
136
137impl SimpleCache {
138    fn new(ttl: Duration) -> Self {
139        Self {
140            cache: Mutex::new(HashMap::new()),
141            ttl,
142        }
143    }
144
145    fn get(&self, key: &str) -> Option<String> {
146        let mut cache = self.cache.lock().ok()?;
147        
148        if let Some((value, timestamp)) = cache.get(key) {
149            if timestamp.elapsed() < self.ttl {
150                return Some(value.clone());
151            } else {
152                cache.remove(key);
153            }
154        }
155        
156        None
157    }
158
159    fn set(&self, key: String, value: String) {
160        if let Ok(mut cache) = self.cache.lock() {
161            cache.insert(key, (value, Instant::now()));
162        }
163    }
164
165    fn clear_expired(&self) {
166        if let Ok(mut cache) = self.cache.lock() {
167            let now = Instant::now();
168            cache.retain(|_, (_, timestamp)| now.duration_since(*timestamp) < self.ttl);
169        }
170    }
171}
172
173// ============================================================================
174// 统一翻译引擎
175// ============================================================================
176
177/// 统一的翻译引擎
178/// 
179/// 合并了原来的翻译引擎和服务功能,提供简化的API和更好的性能。
180pub struct UnifiedTranslationEngine {
181    /// 基础翻译服务
182    base_service: BaseTranslationService,
183    /// 配置
184    config: SimpleTranslationConfig,
185    /// 文本过滤器
186    filter: TextFilter,
187    /// 文本收集器
188    collector: TextCollector,
189    /// 批次管理器
190    batch_manager: BatchManager,
191    /// 简单缓存
192    cache: Option<SimpleCache>,
193    /// 统计信息
194    stats: Arc<EngineStats>,
195}
196
197impl UnifiedTranslationEngine {
198    /// 创建新的翻译引擎
199    pub fn new(config: SimpleTranslationConfig) -> TranslationResult<Self> {
200        let base_service = BaseTranslationService::new(crate::types::TranslationConfig {
201            enabled: config.enabled,
202            source_lang: config.source_lang.clone(),
203            target_lang: config.target_lang.clone(),
204            deeplx_api_url: config.api_url.clone(),
205            max_requests_per_second: config.requests_per_second,
206            max_text_length: config.max_text_length,
207            max_paragraphs_per_request: 10, // 固定值
208        });
209
210        let cache = if config.cache_enabled {
211            Some(SimpleCache::new(config.cache_ttl()))
212        } else {
213            None
214        };
215
216        Ok(Self {
217            base_service,
218            config,
219            filter: TextFilter::new(),
220            collector: TextCollector::new(),
221            batch_manager: BatchManager::new(),
222            cache,
223            stats: Arc::new(EngineStats::new()),
224        })
225    }
226
227    /// 快速创建引擎
228    pub fn quick(target_lang: &str, api_url: Option<&str>) -> TranslationResult<Self> {
229        let config = crate::simple_config::quick_config(target_lang, api_url);
230        Self::new(config)
231    }
232
233    /// 翻译单个文本
234    pub async fn translate_text(&self, text: &str) -> TranslationResult<String> {
235        if !self.config.enabled {
236            return Ok(text.to_string());
237        }
238
239        if !self.filter.should_translate(text) {
240            return Ok(text.to_string());
241        }
242
243        // 检查缓存
244        let cache_key = format!("{}:{}", text, self.config.target_lang);
245        if let Some(ref cache) = self.cache {
246            if let Some(cached) = cache.get(&cache_key) {
247                self.stats.increment_cache_hits();
248                return Ok(cached);
249            }
250            self.stats.increment_cache_misses();
251        }
252
253        // 执行翻译
254        let start = Instant::now();
255        let result = self.base_service.translate(text).await
256            .map_err(|e| TranslationError::ApiError { code: 500, message: e.to_string() })?;
257        
258        let duration = start.elapsed();
259        self.stats.add_translation_time(duration);
260        self.stats.increment_api_calls();
261        self.stats.increment_texts_processed(1);
262
263        // 存储到缓存
264        if let Some(ref cache) = self.cache {
265            cache.set(cache_key, result.clone());
266        }
267
268        Ok(result)
269    }
270
271    /// 翻译文本列表
272    pub async fn translate_texts(&self, texts: &[String]) -> TranslationResult<Vec<String>> {
273        if !self.config.enabled {
274            return Ok(texts.to_vec());
275        }
276
277        // 过滤可翻译文本
278        let translatable_items: Vec<TextItem> = texts
279            .iter()
280            .enumerate()
281            .filter_map(|(i, text)| {
282                if self.filter.should_translate(text) {
283                    Some(crate::functional::create_text_item(
284                        text.clone(), 
285                        format!("text[{}]", i)
286                    ))
287                } else {
288                    None
289                }
290            })
291            .collect();
292
293        // 创建批次
294        let batches = self.batch_manager.create_batches(translatable_items);
295        
296        // 翻译每个批次
297        let mut results = HashMap::new();
298        for batch in batches {
299            let batch_result = self.translate_batch(&batch).await?;
300            for (item, translated) in batch.items.iter().zip(batch_result.iter()) {
301                results.insert(item.location.clone(), translated.clone());
302            }
303        }
304
305        // 构建结果向量
306        let translated: Vec<String> = texts
307            .iter()
308            .enumerate()
309            .map(|(i, original)| {
310                let location = format!("text[{}]", i);
311                results.get(&location).cloned().unwrap_or_else(|| original.clone())
312            })
313            .collect();
314
315        Ok(translated)
316    }
317
318    /// 翻译批次
319    async fn translate_batch(&self, batch: &Batch) -> TranslationResult<Vec<String>> {
320        if batch.items.is_empty() {
321            return Ok(Vec::new());
322        }
323
324        let texts: Vec<&str> = batch.items.iter().map(|item| item.text.as_str()).collect();
325        let combined_text = texts.join("\n\n");
326
327        let start = Instant::now();
328        let translated = self.base_service.translate(&combined_text).await
329            .map_err(|e| {
330                self.stats.increment_errors();
331                TranslationError::ApiError { code: 500, message: e.to_string() }
332            })?;
333
334        let duration = start.elapsed();
335        self.stats.add_translation_time(duration);
336        self.stats.increment_api_calls();
337        self.stats.increment_batches_processed();
338        self.stats.increment_texts_processed(batch.items.len());
339
340        // 简单的分割逻辑(实际应用中需要更复杂的处理)
341        let translated_parts: Vec<String> = translated
342            .split("\n\n")
343            .map(|s| s.trim().to_string())
344            .collect();
345
346        // 确保结果数量匹配
347        if translated_parts.len() == texts.len() {
348            Ok(translated_parts)
349        } else {
350            // 如果分割失败,返回原始文本
351            Ok(texts.iter().map(|s| s.to_string()).collect())
352        }
353    }
354
355    /// 从DOM节点收集并翻译文本
356    pub async fn translate_dom_texts(&self, root: &dyn DomNode) -> TranslationResult<Vec<(String, String)>> {
357        if !self.config.enabled {
358            return Ok(Vec::new());
359        }
360
361        // 收集文本
362        let text_items = self.collector.collect_texts(root);
363        
364        // 创建批次
365        let batches = self.batch_manager.create_batches(text_items);
366        
367        // 翻译所有批次
368        let mut results = Vec::new();
369        for batch in batches {
370            let translations = self.translate_batch(&batch).await?;
371            for (item, translation) in batch.items.iter().zip(translations.iter()) {
372                results.push((item.location.clone(), translation.clone()));
373            }
374        }
375
376        Ok(results)
377    }
378
379    /// 获取统计信息
380    pub fn stats(&self) -> Arc<EngineStats> {
381        Arc::clone(&self.stats)
382    }
383
384    /// 获取配置
385    pub fn config(&self) -> &SimpleTranslationConfig {
386        &self.config
387    }
388
389    /// 清理过期缓存
390    pub fn cleanup_cache(&self) {
391        if let Some(ref cache) = self.cache {
392            cache.clear_expired();
393        }
394    }
395
396    /// 检查引擎健康状态
397    pub fn health_check(&self) -> EngineHealth {
398        let stats = &self.stats;
399        
400        let error_rate = stats.error_rate();
401        let avg_time = stats.average_translation_time_ms();
402        
403        let status = if error_rate > 0.5 {
404            HealthStatus::Critical
405        } else if error_rate > 0.2 || avg_time > 5000.0 {
406            HealthStatus::Warning
407        } else {
408            HealthStatus::Healthy
409        };
410
411        EngineHealth {
412            status,
413            error_rate,
414            average_response_time_ms: avg_time,
415            cache_hit_rate: stats.cache_hit_rate(),
416            total_requests: stats.api_calls.load(Ordering::Relaxed),
417        }
418    }
419}
420
421// ============================================================================
422// 健康检查
423// ============================================================================
424
425/// 引擎健康状态
426#[derive(Debug, Clone)]
427pub struct EngineHealth {
428    pub status: HealthStatus,
429    pub error_rate: f64,
430    pub average_response_time_ms: f64,
431    pub cache_hit_rate: f64,
432    pub total_requests: usize,
433}
434
435/// 健康状态枚举
436#[derive(Debug, Clone, PartialEq)]
437pub enum HealthStatus {
438    Healthy,
439    Warning,
440    Critical,
441}
442
443// ============================================================================
444// 便利函数
445// ============================================================================
446
447/// 创建快速翻译引擎
448pub fn create_engine(target_lang: &str, api_url: Option<&str>) -> TranslationResult<UnifiedTranslationEngine> {
449    UnifiedTranslationEngine::quick(target_lang, api_url)
450}
451
452/// 创建开发环境引擎
453pub fn create_dev_engine() -> TranslationResult<UnifiedTranslationEngine> {
454    let config = crate::simple_config::presets::development();
455    UnifiedTranslationEngine::new(config)
456}
457
458/// 创建生产环境引擎
459pub fn create_prod_engine() -> TranslationResult<UnifiedTranslationEngine> {
460    let config = crate::simple_config::presets::production();
461    UnifiedTranslationEngine::new(config)
462}
463
464// ============================================================================
465// 测试
466// ============================================================================
467
468#[cfg(test)]
469mod tests {
470    use super::*;
471    use crate::collector::TestDomNode;
472
473    #[tokio::test]
474    async fn test_engine_creation() {
475        let engine = UnifiedTranslationEngine::quick("zh", Some("http://localhost:1188/translate"));
476        assert!(engine.is_ok());
477    }
478
479    #[tokio::test]
480    async fn test_engine_stats() {
481        let engine = UnifiedTranslationEngine::quick("zh", Some("http://localhost:1188/translate"))
482            .expect("Failed to create engine");
483        
484        let stats = engine.stats();
485        assert_eq!(stats.texts_processed(), 0);
486        assert_eq!(stats.batches_processed(), 0);
487    }
488
489    #[tokio::test]
490    async fn test_health_check() {
491        let engine = UnifiedTranslationEngine::quick("zh", Some("http://localhost:1188/translate"))
492            .expect("Failed to create engine");
493        
494        let health = engine.health_check();
495        assert_eq!(health.status, HealthStatus::Healthy);
496    }
497
498    #[tokio::test]
499    async fn test_disabled_engine() {
500        let mut config = crate::simple_config::quick_config("zh", Some("http://localhost:1188/translate"));
501        config.enabled = false;
502        
503        let engine = UnifiedTranslationEngine::new(config)
504            .expect("Failed to create engine");
505        
506        let result = engine.translate_text("Hello World").await.unwrap();
507        assert_eq!(result, "Hello World");
508    }
509
510    #[tokio::test]
511    async fn test_dom_text_collection() {
512        // 创建禁用翻译的引擎来避免网络请求
513        let mut config = crate::simple_config::quick_config("zh", Some("http://localhost:1188/translate"));
514        config.enabled = false;
515        
516        let engine = UnifiedTranslationEngine::new(config)
517            .expect("Failed to create engine");
518        
519        let root = TestDomNode::new_element("div")
520            .with_child(TestDomNode::new_text("Hello World"));
521        
522        let results = engine.translate_dom_texts(&root).await;
523        assert!(results.is_ok());
524    }
525
526    #[test]
527    fn test_convenience_functions() {
528        let engine = create_engine("zh", Some("http://localhost:1188/translate"));
529        assert!(engine.is_ok());
530
531        let dev_engine = create_dev_engine();
532        assert!(dev_engine.is_ok());
533
534        let prod_engine = create_prod_engine();
535        assert!(prod_engine.is_ok());
536    }
537}