import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable, throwError } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; import { Router } from '@angular/router'; import { AuthService } from './auth.service'; // Interfaces export interface API { name: string; url: string; method: string; headers?: any; body_template?: any; timeout_seconds: number; retry?: { retry_count: number; backoff_seconds: number; strategy: string; }; auth?: { enabled: boolean; token_endpoint?: string; response_token_path?: string; token_request_body?: any; token_refresh_endpoint?: string; token_refresh_body?: any; }; response_prompt?: string; response_mappings?: ResponseMapping[]; // Yeni alan deleted?: boolean; last_update_date?: string; last_update_user?: string; } export interface LocalizedCaption { locale_code: string; caption: string; } export interface LocalizedExample { locale_code: string; example: string; } export interface ResponseMapping { variable_name: string; type: 'str' | 'int' | 'float' | 'bool' | 'date'; json_path: string; caption: LocalizedCaption[]; } export interface IntentParameter { name: string; caption: LocalizedCaption[]; type: 'str' | 'int' | 'float' | 'bool' | 'date'; required: boolean; variable_name: string; extraction_prompt?: string; validation_regex?: string; invalid_prompt?: string; type_error_prompt?: string; } export interface Intent { name: string; caption: string; detection_prompt: string; examples: LocalizedExample[]; parameters: IntentParameter[]; action: string; fallback_timeout_prompt?: string; fallback_error_prompt?: string; } export interface GenerationConfig { max_new_tokens: number; temperature: number; top_p: number; top_k?: number; repetition_penalty?: number; do_sample?: boolean; num_beams?: number; length_penalty?: number; early_stopping?: boolean; } export interface LLMConfig { repo_id: string; generation_config: GenerationConfig; // any yerine GenerationConfig use_fine_tune: boolean; fine_tune_zip: string; } export interface Version { no: number; caption?: string; description?: string; published: boolean; general_prompt?: string; llm: LLMConfig; // inline yerine LLMConfig intents: Intent[]; parameters: any[]; last_update_date?: string; } export interface Project { id: number; name: string; caption: string; enabled: boolean; icon?: string; description?: string; default_locale?: string; supported_locales?: string[]; timezone?: string; region?: string; versions: Version[]; last_update_date?: string; deleted?: boolean; created_date?: string; created_by?: string; last_update_user?: string; } export interface ParameterCollectionConfig { max_params_per_question: number; smart_grouping: boolean; retry_unanswered: boolean; collection_prompt: string; } export interface ProviderConfig { type: 'llm' | 'tts' | 'stt'; name: string; display_name: string; requires_endpoint: boolean; requires_api_key: boolean; requires_repo_info?: boolean; description: string; } export interface ProviderSettings { name: string; api_key?: string; endpoint?: string | null; settings?: any; } export interface Environment { llm_provider: ProviderSettings; tts_provider: ProviderSettings; stt_provider: ProviderSettings; providers: ProviderConfig[]; parameter_collection_config?: ParameterCollectionConfig; } export interface STTSettings { speech_timeout_ms: number; noise_reduction_level: number; vad_sensitivity: number; language: string; model: string; use_enhanced: boolean; enable_punctuation: boolean; interim_results: boolean; } export interface TTSSettings { use_ssml: boolean; } @Injectable({ providedIn: 'root' }) export class ApiService { private apiUrl = '/api'; private adminUrl = `${this.apiUrl}/admin`; constructor( private http: HttpClient, private router: Router, private authService: AuthService ) {} // ===================== Utils ===================== private normalizeTimestamp(timestamp: string | null | undefined): string { if (!timestamp) return ''; // Remove milliseconds for comparison if (timestamp.includes('.') && timestamp.endsWith('Z')) { return timestamp.split('.')[0] + 'Z'; } return timestamp; } // ===================== Auth ===================== login(username: string, password: string): Observable { return this.http.post(`${this.adminUrl}/login`, { username, password }).pipe( tap((response: any) => { this.authService.setToken(response.token); this.authService.setUsername(response.username); }) ); } logout(): void { this.authService.logout(); } private getAuthHeaders(): HttpHeaders { try { const token = this.authService.getToken(); if (!token) { console.warn('No auth token available'); // Token yoksa boş header dön veya login'e yönlendir return new HttpHeaders({ 'Content-Type': 'application/json' }); } return new HttpHeaders({ 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }); } catch (error) { console.error('Error getting auth headers:', error); return new HttpHeaders({ 'Content-Type': 'application/json' }); } } // ===================== User ===================== changePassword(currentPassword: string, newPassword: string): Observable { return this.http.post( `${this.adminUrl}/change-password`, { current_password: currentPassword, new_password: newPassword }, { headers: this.getAuthHeaders() } ).pipe( catchError(this.handleError) ); } // ===================== Environment ===================== getEnvironment(): Observable { return this.http.get(`${this.adminUrl}/environment`, { headers: this.getAuthHeaders() }); } updateEnvironment(data: Environment): Observable { return this.http.put(`${this.adminUrl}/environment`, data, { headers: this.getAuthHeaders() }).pipe( catchError(this.handleError) ); } // ===================== Projects ===================== getProjects(includeDeleted = false): Observable { return this.http.get(`${this.adminUrl}/projects`, { headers: this.getAuthHeaders(), params: { include_deleted: includeDeleted.toString() } }).pipe( catchError(error => { // Race condition check if (error.status === 409) { console.warn('Race condition detected in getProjects:', error.error); // Component'ler bu hatayı handle edecek } return throwError(() => error); }) ); } getProject(id: number): Observable { return this.http.get(`${this.adminUrl}/projects/${id}`); } createProject(data: any): Observable { console.log('createProject called with data:', data); let headers; try { headers = this.getAuthHeaders(); console.log('Headers obtained successfully'); } catch (error) { console.error('Error getting headers:', error); return throwError(() => ({ message: 'Authentication error' })); } console.log('Making POST request to:', `${this.adminUrl}/projects`); return this.http.post(`${this.adminUrl}/projects`, data, { headers }).pipe( tap(response => { console.log('Project creation successful:', response); }), catchError(error => { console.error('Project creation failed:', error); return this.handleError(error); }) ); } updateProject(id: number, data: any): Observable { // Normalize the timestamp before sending if (data.last_update_date) { data.last_update_date = this.normalizeTimestamp(data.last_update_date); } return this.http.put(`${this.adminUrl}/projects/${id}`, data, { headers: this.getAuthHeaders() }).pipe( catchError(error => { // Race condition özel handling if (error.status === 409) { const details = error.error?.details || {}; return throwError(() => ({ ...error, raceCondition: true, lastUpdateUser: details.last_update_user, lastUpdateDate: details.last_update_date })); } return throwError(() => error); }) ); } deleteProject(id: number): Observable { return this.http.delete(`${this.adminUrl}/projects/${id}`, { headers: this.getAuthHeaders() }).pipe( catchError(this.handleError) ); } toggleProject(id: number): Observable { return this.http.patch(`${this.adminUrl}/projects/${id}/toggle`, {}, { headers: this.getAuthHeaders() }).pipe( catchError(this.handleError) ); } exportProject(id: number): Observable { return this.http.get(`${this.adminUrl}/projects/${id}/export`, { headers: this.getAuthHeaders() }).pipe( catchError(this.handleError) ); } importProject(data: any): Observable { return this.http.post(`${this.adminUrl}/projects/import`, data, { headers: this.getAuthHeaders() }).pipe( catchError(this.handleError) ); } // ===================== Versions ===================== createVersion(projectId: number, data: any): Observable { return this.http.post(`${this.adminUrl}/projects/${projectId}/versions`, data, { headers: this.getAuthHeaders() }).pipe( catchError(error => { if (error.status === 409) { console.warn('Race condition in createVersion:', error.error); } return throwError(() => error); }) ); } updateVersion(projectId: number, versionNo: number, data: any, force: boolean = false): Observable { // Normalize the timestamp before sending if (data.last_update_date) { data.last_update_date = this.normalizeTimestamp(data.last_update_date); } return this.http.put( `${this.adminUrl}/projects/${projectId}/versions/${versionNo}${force ? '?force=true' : ''}`, data, { headers: this.getAuthHeaders() } ).pipe( catchError(this.handleError) ); } deleteVersion(projectId: number, versionNo: number): Observable { return this.http.delete(`${this.adminUrl}/projects/${projectId}/versions/${versionNo}`, { headers: this.getAuthHeaders() }).pipe( catchError(this.handleError) ); } publishVersion(projectId: number, versionNo: number): Observable { return this.http.post(`${this.adminUrl}/projects/${projectId}/versions/${versionNo}/publish`, {}, { headers: this.getAuthHeaders() }).pipe( catchError(this.handleError) ); } // ===================== APIs ===================== getAPIs(includeDeleted = false): Observable { return this.http.get(`${this.adminUrl}/apis`, { headers: this.getAuthHeaders(), params: { include_deleted: includeDeleted.toString() } }).pipe( catchError(this.handleError) ); } createAPI(data: any): Observable { return this.http.post(`${this.adminUrl}/apis`, data, { headers: this.getAuthHeaders() }).pipe( catchError(this.handleError) ); } updateAPI(name: string, data: any): Observable { // Normalize the timestamp before sending if (data.last_update_date) { data.last_update_date = this.normalizeTimestamp(data.last_update_date); } return this.http.put(`${this.adminUrl}/apis/${name}`, data, { headers: this.getAuthHeaders() }).pipe( catchError(error => { if (error.status === 409) { const details = error.error?.details || {}; return throwError(() => ({ ...error, raceCondition: true, lastUpdateUser: details.last_update_user, lastUpdateDate: details.last_update_date })); } return throwError(() => error); }) ); } deleteAPI(name: string): Observable { return this.http.delete(`${this.adminUrl}/apis/${name}`, { headers: this.getAuthHeaders() }).pipe( catchError(this.handleError) ); } testAPI(data: any): Observable { return this.http.post(`${this.adminUrl}/apis/test`, data, { headers: this.getAuthHeaders() }).pipe( catchError(this.handleError) ); } // ===================== Spark Integration ===================== sparkStartup(projectName: string): Observable { return this.http.post(`${this.adminUrl}/spark/startup`, { project_name: projectName }, { headers: this.getAuthHeaders() } ).pipe( catchError(this.handleError) ); } sparkGetProjects(): Observable { return this.http.get(`${this.adminUrl}/spark/projects`, { headers: this.getAuthHeaders() }).pipe( catchError(this.handleError) ); } sparkEnableProject(projectName: string): Observable { return this.http.post(`${this.adminUrl}/spark/project/enable`, { project_name: projectName }, { headers: this.getAuthHeaders() } ).pipe( catchError(this.handleError) ); } sparkDisableProject(projectName: string): Observable { return this.http.post(`${this.adminUrl}/spark/project/disable`, { project_name: projectName }, { headers: this.getAuthHeaders() } ).pipe( catchError(this.handleError) ); } sparkDeleteProject(projectName: string): Observable { return this.http.delete(`${this.adminUrl}/spark/project/${projectName}`, { headers: this.getAuthHeaders() }).pipe( catchError(this.handleError) ); } // ===================== Tests ===================== runTests(testType: string): Observable { return this.http.post(`${this.adminUrl}/test/run-all`, { test_type: testType }, { headers: this.getAuthHeaders() }).pipe( catchError(this.handleError) ); } // ===================== Activity Log ===================== getActivityLog(limit = 50): Observable { return this.http.get(`${this.adminUrl}/activity-log`, { headers: this.getAuthHeaders(), params: { limit: limit.toString() } }).pipe( catchError(this.handleError) ); } // ===================== Validation ===================== validateRegex(pattern: string, testValue: string): Observable { return this.http.post(`${this.adminUrl}/validate/regex`, { pattern, test_value: testValue }, { headers: this.getAuthHeaders() } ).pipe( catchError(this.handleError) ); } // ===================== Chat ===================== /* 1️⃣ Proje isimleri (combo’yu doldurmak için) */ getChatProjects() { return this.http.get(`${this.adminUrl}/projects/names`); } /* 2️⃣ Oturum başlat */ startChat(projectName: string, isRealtime: boolean, locale?: string) { return this.http.post<{ session_id: string; answer: string; }>(`${this.apiUrl}/start_session`, { project_name: projectName, is_realtime: isRealtime, locale: locale || 'tr' }); } /* 3️⃣ Mesaj gönder/al */ chat(sessionId: string, text: string) { const headers = new HttpHeaders().set('X-Session-ID', sessionId); return this.http.post<{ response: string; intent?: string; state: string; }>( `${this.apiUrl}/chat`, { message: text }, { headers } ); } endSession(sessionId: string): Observable { return this.http.post(`${this.apiUrl}/end-session`, { session_id: sessionId }, { headers: this.getAuthHeaders() } ); } // ===================== TTS ===================== generateTTS(text: string, voiceId?: string, modelId?: string, outputFormat: string = 'mp3_44100_128'): Observable { const body = { text, voice_id: voiceId, model_id: modelId, output_format: outputFormat }; return this.http.post(`${this.apiUrl}/tts/generate`, body, { headers: this.getAuthHeaders(), responseType: 'blob' }).pipe( catchError(this.handleError) ); } // ===================== Locale ===================== getAvailableLocales(): Observable { return this.http.get(`${this.adminUrl}/locales`, { headers: this.getAuthHeaders() }); } getLocaleDetails(code: string): Observable { return this.http.get(`${this.adminUrl}/locales/${code}`, { headers: this.getAuthHeaders() }); } // ===================== Error Handler ===================== private handleError(error: any) { console.error('API Error:', error); if (error.status === 401) { // Token expired or invalid this.authService.logout(); } else if (error.status === 409) { // Race condition error const message = error.error?.detail || 'Resource was modified by another user'; return throwError(() => ({ ...error, userMessage: message, requiresReload: true })); } // Ensure error object has proper structure const errorResponse = { status: error.status, error: error.error || { detail: error.message || 'Unknown error' }, message: error.error?.detail || error.error?.message || error.message || 'Unknown error' }; return throwError(() => errorResponse); } }