1use crate::services::ai::openai::OpenAIClient;
8use crate::services::vad::{LocalVadDetector, VadAudioProcessor, VadSyncDetector};
9use crate::{
10 Result,
11 config::{Config, ConfigService},
12 core::{file_manager::FileManager, matcher::engine::MatchEngine},
13 error::SubXError,
14 services::ai::AIProvider,
15};
16
17pub struct ComponentFactory {
42 config: Config,
43}
44
45impl ComponentFactory {
46 pub fn new(config_service: &dyn ConfigService) -> Result<Self> {
56 let config = config_service.get_config()?;
57 Ok(Self { config })
58 }
59
60 pub fn create_match_engine(&self) -> Result<MatchEngine> {
69 let ai_provider = self.create_ai_provider()?;
70 let match_config = crate::core::matcher::MatchConfig {
71 confidence_threshold: 0.8, max_sample_length: self.config.ai.max_sample_length,
73 enable_content_analysis: true,
74 backup_enabled: self.config.general.backup_enabled,
75 relocation_mode: crate::core::matcher::engine::FileRelocationMode::None,
76 conflict_resolution: crate::core::matcher::engine::ConflictResolution::AutoRename,
77 ai_model: self.config.ai.model.clone(),
78 };
79 Ok(MatchEngine::new(ai_provider, match_config))
80 }
81
82 pub fn create_file_manager(&self) -> FileManager {
87 FileManager::new()
90 }
91
92 pub fn create_ai_provider(&self) -> Result<Box<dyn AIProvider>> {
102 create_ai_provider(&self.config.ai)
103 }
104
105 pub fn config(&self) -> &Config {
109 &self.config
110 }
111
112 pub fn create_vad_sync_detector(&self) -> Result<VadSyncDetector> {
120 VadSyncDetector::new(self.config.sync.vad.clone())
121 }
122
123 pub fn create_vad_detector(&self) -> Result<LocalVadDetector> {
131 LocalVadDetector::new(self.config.sync.vad.clone())
132 }
133
134 pub fn create_audio_processor(&self) -> Result<VadAudioProcessor> {
142 VadAudioProcessor::new()
143 }
144}
145
146fn validate_ai_config(ai_config: &crate::config::AIConfig) -> Result<()> {
160 if ai_config.api_key.as_deref().unwrap_or("").trim().is_empty() {
161 return Err(SubXError::config(
162 "AI API key is required. Set ai.api_key in configuration or use environment variable."
163 .to_string(),
164 ));
165 }
166 if ai_config.model.trim().is_empty() {
167 return Err(SubXError::config(
168 "AI model is required. Set ai.model in configuration.".to_string(),
169 ));
170 }
171 if ai_config.temperature < 0.0 || ai_config.temperature > 2.0 {
172 return Err(SubXError::config(
173 "AI temperature must be between 0.0 and 2.0.".to_string(),
174 ));
175 }
176 if ai_config.max_tokens == 0 {
177 return Err(SubXError::config(
178 "AI max_tokens must be greater than 0.".to_string(),
179 ));
180 }
181 Ok(())
182}
183
184pub fn create_ai_provider(ai_config: &crate::config::AIConfig) -> Result<Box<dyn AIProvider>> {
189 match ai_config.provider.as_str() {
190 "openai" => {
191 validate_ai_config(ai_config)?;
192 let client = OpenAIClient::from_config(ai_config)?;
193 Ok(Box::new(client))
194 }
195 other => Err(SubXError::config(format!(
196 "Unsupported AI provider: {}. Supported providers: openai",
197 other
198 ))),
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use crate::config::test_service::TestConfigService;
206
207 #[test]
208 fn test_component_factory_creation() {
209 let config_service = TestConfigService::default();
210 let factory = ComponentFactory::new(&config_service);
211 assert!(factory.is_ok());
212 }
213
214 #[test]
215 fn test_factory_creation() {
216 let config_service = TestConfigService::default();
217 let factory = ComponentFactory::new(&config_service);
218 assert!(factory.is_ok());
219 }
220
221 #[test]
222 fn test_create_file_manager() {
223 let config_service = TestConfigService::default();
224 let factory = ComponentFactory::new(&config_service).unwrap();
225
226 let _file_manager = factory.create_file_manager();
227 }
230
231 #[test]
232 fn test_unsupported_ai_provider() {
233 let mut config = crate::config::Config::default();
234 config.ai.provider = "unsupported".to_string();
235
236 let result: Result<Box<dyn AIProvider>> = create_ai_provider(&config.ai);
237 assert!(result.is_err());
238
239 match result {
240 Err(e) => {
241 let error_msg = e.to_string();
242 assert!(error_msg.contains("Unsupported AI provider"));
243 }
244 Ok(_) => panic!("Expected error for unsupported provider"),
245 }
246 }
247
248 #[test]
249 fn test_create_vad_sync_detector() {
250 let config_service = TestConfigService::default();
251 let factory = ComponentFactory::new(&config_service).unwrap();
252 let result = factory.create_vad_sync_detector();
253 assert!(result.is_ok());
254 }
255
256 #[test]
257 fn test_create_vad_detector() {
258 let config_service = TestConfigService::default();
259 let factory = ComponentFactory::new(&config_service).unwrap();
260 let result = factory.create_vad_detector();
261 assert!(result.is_ok());
262 }
263
264 #[test]
265 fn test_create_audio_processor() {
266 let config_service = TestConfigService::default();
267 let factory = ComponentFactory::new(&config_service).unwrap();
268 let result = factory.create_audio_processor();
269 assert!(result.is_ok());
270 }
271
272 #[test]
273 fn test_create_ai_provider_openai_success() {
274 let config_service = TestConfigService::default();
275 config_service.set_ai_settings_and_key("openai", "gpt-4.1-mini", "test-api-key");
276 let factory = ComponentFactory::new(&config_service).unwrap();
277 let result = factory.create_ai_provider();
278 assert!(result.is_ok());
279 }
280
281 #[test]
282 fn test_create_ai_provider_missing_api_key() {
283 let config_service = TestConfigService::default();
284 config_service.set_ai_settings_and_key("openai", "gpt-4.1-mini", "");
285 let factory = ComponentFactory::new(&config_service).unwrap();
286 let result = factory.create_ai_provider();
287 assert!(result.is_err());
288 let error_msg = result.err().unwrap().to_string();
289 assert!(error_msg.contains("API key is required"));
290 }
291
292 #[test]
293 fn test_create_ai_provider_unsupported_provider() {
294 let config_service = TestConfigService::default();
295 config_service.set_ai_settings_and_key("unsupported-provider", "model", "key");
296 let factory = ComponentFactory::new(&config_service).unwrap();
297 let result = factory.create_ai_provider();
298 assert!(result.is_err());
299 let error_msg = result.err().unwrap().to_string();
300 assert!(error_msg.contains("Unsupported AI provider"));
301 }
302
303 #[test]
304 fn test_create_ai_provider_with_custom_base_url() {
305 let config_service = TestConfigService::default();
306 config_service.set_ai_settings_and_key("openai", "gpt-4.1-mini", "test-api-key");
307 config_service.config_mut().ai.base_url = "https://custom-api.com/v1".to_string();
308 let factory = ComponentFactory::new(&config_service).unwrap();
309 let result = factory.create_ai_provider();
310 assert!(result.is_ok());
311 }
312}