export interface SessionData { sessionId: string; caseData?: CaseData; accusedData?: AccusedData; evidence?: EvidenceItem[]; questions?: any[]; responses?: any[]; notes?: any[]; report?: any; } import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable, BehaviorSubject, of, throwError } from 'rxjs'; import { delay, tap, catchError } from 'rxjs/operators'; import { environment } from '../../environments/environment'; // Request investigation questions based on Brief Description export interface CaseData { caseId: string; caseType: string; crimeCategory: string; crimeSubtype: string; description: string; location: string; dateTime: string; urgency: string; officerName: string; badgeNumber: string; department: string; contactInfo: string; } export interface AccusedData { name: string; age: string; gender: string; address: string; occupation: string; contactNumber: string; relationship: string; background: string; previousRecords: string; } export interface EvidenceItem { type: string; description: string; location: string; collectedBy: string; dateCollected: string; chainOfCustody: string; significance: string; } @Injectable({ providedIn: 'root' }) export class PyDetectService { private baseUrl = environment.pyDetectApiUrl; private httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }; // Session management private currentSessionSubject = new BehaviorSubject(null); public currentSession$ = this.currentSessionSubject.asObservable(); // Voice settings private voiceEnabledSubject = new BehaviorSubject(true); public voiceEnabled$ = this.voiceEnabledSubject.asObservable(); constructor(private http: HttpClient) { // Initialize voice settings from localStorage const savedVoiceEnabled = localStorage.getItem('pydetect_voice_enabled'); if (savedVoiceEnabled !== null) { this.voiceEnabledSubject.next(JSON.parse(savedVoiceEnabled)); } } // Request investigation questions based on Brief Description // ============ SESSION MANAGEMENT ============ // Start a new session startSession(briefDescription?: string): Observable { const payload: any = {}; if (briefDescription) payload.brief_description = briefDescription; return this.http.post(`${this.baseUrl}/start_session`, payload, this.httpOptions) .pipe( tap(response => { const responseData = response as any; const sessionData: SessionData = { sessionId: responseData.session_id || this.generateSessionId(), caseData: undefined, accusedData: undefined, evidence: [], questions: [], responses: [], notes: [], report: undefined }; this.currentSessionSubject.next(sessionData); this.saveSessionToStorage(sessionData); }), catchError(this.handleError) ); } // Get current session getCurrentSession(): SessionData | null { return this.currentSessionSubject.value; } // Update session data updateSession(updates: Partial): void { const current = this.currentSessionSubject.value; if (current) { const updated = { ...current, ...updates }; this.currentSessionSubject.next(updated); this.saveSessionToStorage(updated); } } // ============ CASE MANAGEMENT ============ // Submit complete case details submitCaseDetails(sessionId: string, caseData: CaseData, briefDescription?: string): Observable { const payload: any = { session_id: sessionId, case_data: caseData, timestamp: new Date().toISOString() }; // ...existing code... return this.http.post(`${this.baseUrl}/submit_case`, payload, this.httpOptions) .pipe( tap(response => { this.updateSession({ caseData: caseData }); }), catchError(this.handleError) ); } // ============ BODY LANGUAGE EXPLANATION ============ /** * Fetches body language explanation for a given cue from backend. * @param cue The body language cue to explain * @returns Observable<{ explanation: string }> */ bodyLanguageExplain(cue: string): Observable<{ meaning?: string; explanation?: string }> { const payload = { cue }; return this.http.post<{ meaning?: string; explanation?: string }>(`${this.baseUrl}/body_language_explain`, payload, this.httpOptions) .pipe(catchError((error): Observable<{ meaning?: string; explanation?: string }> => { let errorMessage = 'An unknown error occurred'; if (error.error instanceof ErrorEvent) { errorMessage = `Client Error: ${error.error.message}`; } else { errorMessage = `Server Error: ${error.status} - ${error.message}`; } // Return an object with explanation only for error case return of({ explanation: errorMessage }); })); } // Submit accused details submitAccused(sessionId: string, accused: AccusedData, additionalData?: any): Observable { const currentSession = this.getCurrentSession(); const payload = { session_id: sessionId, accused: accused, crime: additionalData?.crime || currentSession?.caseData?.crimeCategory || '', profile: currentSession?.caseData || additionalData?.profile || {}, evidence: currentSession?.evidence || additionalData?.evidence || [], timestamp: new Date().toISOString(), ...additionalData }; return this.http.post(`${this.baseUrl}/submit_accused`, payload, this.httpOptions) .pipe( tap(response => { this.updateSession({ accusedData: accused }); }), catchError(this.handleError) ); } // ============ EVIDENCE MANAGEMENT ============ // Submit evidence submitEvidence(sessionId: string, evidence: EvidenceItem[]): Observable { const payload = { session_id: sessionId, evidence: evidence.map(item => ({ ...item, timestamp: new Date().toISOString(), evidence_id: this.generateEvidenceId() })) }; return this.http.post(`${this.baseUrl}/submit_evidence`, payload, this.httpOptions) .pipe( tap(response => { const currentSession = this.getCurrentSession(); const updatedEvidence = [...(currentSession?.evidence || []), ...evidence]; this.updateSession({ evidence: updatedEvidence }); }), catchError(this.handleError) ); } // Add single evidence item addEvidenceItem(sessionId: string, evidenceItem: EvidenceItem): Observable { return this.submitEvidence(sessionId, [evidenceItem]); } // ============ INVESTIGATION NOTES ============ // Add investigation note addNote(sessionId: string, note: string, category: string = 'general'): Observable { const noteData = { session_id: sessionId, note: note, timestamp: new Date().toISOString(), category: category, note_id: this.generateNoteId(), officer: this.getCurrentSession()?.caseData?.officerName || 'Unknown Officer' }; return this.http.post(`${this.baseUrl}/add_note`, noteData, this.httpOptions) .pipe( tap(response => { const currentSession = this.getCurrentSession(); const updatedNotes = [...(currentSession?.notes || []), noteData]; this.updateSession({ notes: updatedNotes }); }), catchError(this.handleError) ); } // ============ AI QUESTIONING SYSTEM ============ // Get context-aware AI questions askQuestion(sessionId: string, crimeType?: string, briefDescription?: string): Observable { // ...existing code... const queryParams = [`session_id=${encodeURIComponent(sessionId)}`]; // Always send crimeType, even if empty queryParams.push(`crime_type=${encodeURIComponent(crimeType ?? '')}`); // Always send briefDescription, even if empty queryParams.push(`brief_description=${encodeURIComponent(briefDescription ?? '')}`); const queryString = queryParams.join('&'); return this.http.get(`${this.baseUrl}/ask_question?${queryString}`, this.httpOptions) .pipe(catchError(this.handleError)); } // Submit response to AI question submitResponse(sessionId: string, text: string, questionId?: string, timing?: { answer_start_at?: number; answer_end_at?: number; duration_ms?: number; mode?: string; }): Observable { const responseData: any = { session_id: sessionId, text: text, question_id: questionId, timestamp: new Date().toISOString(), response_id: this.generateResponseId() }; if (timing) { if (timing.answer_start_at) responseData.answer_start_at = timing.answer_start_at; if (timing.answer_end_at) responseData.answer_end_at = timing.answer_end_at; if (typeof timing.duration_ms === 'number') responseData.duration_ms = timing.duration_ms; if (timing.mode) responseData.mode = timing.mode; } return this.http.post(`${this.baseUrl}/submit_response`, responseData, this.httpOptions) .pipe( tap(response => { const currentSession = this.getCurrentSession(); const updatedResponses = [...(currentSession?.responses || []), responseData]; this.updateSession({ responses: updatedResponses }); }), catchError(this.handleError) ); } // Stream a single face frame for nonverbal analysis faceFrame(sessionId: string, frameDataUrl: string): Observable { const payload = { session_id: sessionId, frame: frameDataUrl }; return this.http.post(`${this.baseUrl}/face_frame`, payload, this.httpOptions) .pipe(catchError(this.handleError)); } // ============ REPORTING SYSTEM ============ // Get comprehensive report getReport(sessionId: string, reportType: string = 'complete'): Observable { return this.http.get(`${this.baseUrl}/get_report/${sessionId}?type=${reportType}`, this.httpOptions) .pipe( tap(response => { this.updateSession({ report: response }); }), catchError(this.handleError) ); } // Generate summary report generateSummary(sessionId: string): Observable { const currentSession = this.getCurrentSession(); const summaryData = { session_id: sessionId, case_data: currentSession?.caseData, accused_data: currentSession?.accusedData, evidence_count: currentSession?.evidence?.length || 0, questions_answered: currentSession?.responses?.length || 0, notes_count: currentSession?.notes?.length || 0, timestamp: new Date().toISOString() }; return this.http.post(`${this.baseUrl}/generate_summary`, summaryData, this.httpOptions) .pipe(catchError(this.handleError)); } // ============ VOICE FUNCTIONALITY ============ // Toggle voice functionality toggleVoice(): void { const current = this.voiceEnabledSubject.value; this.voiceEnabledSubject.next(!current); localStorage.setItem('pydetect_voice_enabled', JSON.stringify(!current)); } // Check if voice is enabled isVoiceEnabled(): boolean { return this.voiceEnabledSubject.value; } // ============ AUTHENTICATION ============ // Sign in signIn(email: string, password: string): Observable { return this.http.post(`${this.baseUrl}/sign-in`, { email: email, password: password }, this.httpOptions).pipe(catchError(this.handleError)); } // Sign up signUp(name: string, email: string, password: string, role: string = 'investigator'): Observable { return this.http.post(`${this.baseUrl}/sign-up`, { name: name, email: email, password: password, role: role }, this.httpOptions).pipe(catchError(this.handleError)); } // Health check healthCheck(): Observable { return this.http.get(`${this.baseUrl}/health`, this.httpOptions) .pipe(catchError(this.handleError)); } // ============ UTILITY METHODS ============ // Generate unique session ID private generateSessionId(): string { return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } // Generate unique evidence ID private generateEvidenceId(): string { return 'evidence_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } // Generate unique note ID private generateNoteId(): string { return 'note_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } // Generate unique response ID private generateResponseId(): string { return 'response_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } // Save session to localStorage private saveSessionToStorage(session: SessionData): void { try { localStorage.setItem('pydetect_current_session', JSON.stringify(session)); } catch (error) { } } // Load session from localStorage loadSessionFromStorage(): SessionData | null { try { const stored = localStorage.getItem('pydetect_current_session'); if (stored) { const session = JSON.parse(stored); this.currentSessionSubject.next(session); return session; } } catch (error) { } return null; } // Clear current session clearSession(): void { this.currentSessionSubject.next(null); localStorage.removeItem('pydetect_current_session'); } // Error handling private handleError(error: any): Observable { let errorMessage = 'An unknown error occurred'; if (error.error instanceof ErrorEvent) { errorMessage = `Client Error: ${error.error.message}`; } else { errorMessage = `Server Error: ${error.status} - ${error.message}`; } return throwError(() => new Error(errorMessage)); } // ============ DATA VALIDATION ============ // Validate case data validateCaseData(caseData: Partial): { isValid: boolean; errors: string[] } { const errors: string[] = []; if (!caseData.caseId) errors.push('Case ID is required'); if (!caseData.crimeCategory) errors.push('Crime category is required'); if (!caseData.description) errors.push('Description is required'); if (!caseData.officerName) errors.push('Officer name is required'); if (!caseData.badgeNumber) errors.push('Badge number is required'); return { isValid: errors.length === 0, errors: errors }; } // Validate accused data validateAccusedData(accusedData: Partial): { isValid: boolean; errors: string[] } { const errors: string[] = []; if (!accusedData.name) errors.push('Accused name is required'); if (!accusedData.age) errors.push('Age is required'); if (!accusedData.gender) errors.push('Gender is required'); return { isValid: errors.length === 0, errors: errors }; } // ============ STATISTICS & ANALYTICS ============ // Get session statistics getSessionStats(): any { const session = this.getCurrentSession(); if (!session) return null; return { sessionId: session.sessionId, questionsAnswered: session.responses?.length || 0, evidenceItems: session.evidence?.length || 0, notesAdded: session.notes?.length || 0, hasAccusedData: !!session.accusedData, hasCaseData: !!session.caseData, hasReport: !!session.report }; } // Request investigation questions from backend getInvestigationQuestions(sessionId: string, crimeType: string, briefDescription: string): Observable { return this.http.get( `${this.baseUrl}/ask_question?session_id=${sessionId}&crime_type=${encodeURIComponent(crimeType)}&brief_description=${encodeURIComponent(briefDescription)}`, this.httpOptions ); } }