| 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'; |
| |
|
|
| 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' |
| }) |
| }; |
|
|
| |
| private currentSessionSubject = new BehaviorSubject<SessionData | null>(null); |
| public currentSession$ = this.currentSessionSubject.asObservable(); |
| |
| |
| private voiceEnabledSubject = new BehaviorSubject<boolean>(true); |
| public voiceEnabled$ = this.voiceEnabledSubject.asObservable(); |
|
|
| constructor(private http: HttpClient) { |
| |
| const savedVoiceEnabled = localStorage.getItem('pydetect_voice_enabled'); |
| if (savedVoiceEnabled !== null) { |
| this.voiceEnabledSubject.next(JSON.parse(savedVoiceEnabled)); |
| } |
| } |
|
|
| |
|
|
| |
| |
| |
| startSession(briefDescription?: string): Observable<any> { |
| 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) |
| ); |
| } |
|
|
| |
| getCurrentSession(): SessionData | null { |
| return this.currentSessionSubject.value; |
| } |
|
|
| |
| updateSession(updates: Partial<SessionData>): void { |
| const current = this.currentSessionSubject.value; |
| if (current) { |
| const updated = { ...current, ...updates }; |
| this.currentSessionSubject.next(updated); |
| this.saveSessionToStorage(updated); |
| } |
| } |
|
|
| |
|
|
| |
| submitCaseDetails(sessionId: string, caseData: CaseData, briefDescription?: string): Observable<any> { |
| const payload: any = { |
| session_id: sessionId, |
| case_data: caseData, |
| timestamp: new Date().toISOString() |
| }; |
| |
| return this.http.post(`${this.baseUrl}/submit_case`, payload, this.httpOptions) |
| .pipe( |
| tap(response => { |
| this.updateSession({ caseData: caseData }); |
| }), |
| catchError(this.handleError) |
| ); |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| |
| 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 of({ explanation: errorMessage }); |
| })); |
| } |
|
|
| |
| submitAccused(sessionId: string, accused: AccusedData, additionalData?: any): Observable<any> { |
| 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) |
| ); |
| } |
|
|
| |
|
|
| |
| submitEvidence(sessionId: string, evidence: EvidenceItem[]): Observable<any> { |
| 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) |
| ); |
| } |
|
|
| |
| addEvidenceItem(sessionId: string, evidenceItem: EvidenceItem): Observable<any> { |
| return this.submitEvidence(sessionId, [evidenceItem]); |
| } |
|
|
| |
|
|
| |
| addNote(sessionId: string, note: string, category: string = 'general'): Observable<any> { |
| 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) |
| ); |
| } |
|
|
| |
|
|
| |
| askQuestion(sessionId: string, crimeType?: string, briefDescription?: string): Observable<any> { |
| |
| const queryParams = [`session_id=${encodeURIComponent(sessionId)}`]; |
| |
| queryParams.push(`crime_type=${encodeURIComponent(crimeType ?? '')}`); |
| |
| 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)); |
| } |
|
|
| |
| submitResponse(sessionId: string, text: string, questionId?: string, timing?: { |
| answer_start_at?: number; |
| answer_end_at?: number; |
| duration_ms?: number; |
| mode?: string; |
| }): Observable<any> { |
| 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) |
| ); |
| } |
|
|
| |
| faceFrame(sessionId: string, frameDataUrl: string): Observable<any> { |
| const payload = { session_id: sessionId, frame: frameDataUrl }; |
| return this.http.post(`${this.baseUrl}/face_frame`, payload, this.httpOptions) |
| .pipe(catchError(this.handleError)); |
| } |
|
|
| |
|
|
| |
| getReport(sessionId: string, reportType: string = 'complete'): Observable<any> { |
| return this.http.get(`${this.baseUrl}/get_report/${sessionId}?type=${reportType}`, this.httpOptions) |
| .pipe( |
| tap(response => { |
| this.updateSession({ report: response }); |
| }), |
| catchError(this.handleError) |
| ); |
| } |
|
|
| |
| generateSummary(sessionId: string): Observable<any> { |
| 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)); |
| } |
|
|
| |
|
|
| |
| toggleVoice(): void { |
| const current = this.voiceEnabledSubject.value; |
| this.voiceEnabledSubject.next(!current); |
| localStorage.setItem('pydetect_voice_enabled', JSON.stringify(!current)); |
| } |
|
|
| |
| isVoiceEnabled(): boolean { |
| return this.voiceEnabledSubject.value; |
| } |
|
|
| |
|
|
| |
| signIn(email: string, password: string): Observable<any> { |
| return this.http.post(`${this.baseUrl}/sign-in`, { |
| email: email, |
| password: password |
| }, this.httpOptions).pipe(catchError(this.handleError)); |
| } |
|
|
| |
| signUp(name: string, email: string, password: string, role: string = 'investigator'): Observable<any> { |
| return this.http.post(`${this.baseUrl}/sign-up`, { |
| name: name, |
| email: email, |
| password: password, |
| role: role |
| }, this.httpOptions).pipe(catchError(this.handleError)); |
| } |
|
|
| |
| healthCheck(): Observable<any> { |
| return this.http.get(`${this.baseUrl}/health`, this.httpOptions) |
| .pipe(catchError(this.handleError)); |
| } |
|
|
| |
|
|
| |
| private generateSessionId(): string { |
| return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); |
| } |
|
|
| |
| private generateEvidenceId(): string { |
| return 'evidence_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); |
| } |
|
|
| |
| private generateNoteId(): string { |
| return 'note_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); |
| } |
|
|
| |
| private generateResponseId(): string { |
| return 'response_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); |
| } |
|
|
| |
| private saveSessionToStorage(session: SessionData): void { |
| try { |
| localStorage.setItem('pydetect_current_session', JSON.stringify(session)); |
| } catch (error) { |
| } |
| } |
|
|
| |
| 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; |
| } |
|
|
| |
| clearSession(): void { |
| this.currentSessionSubject.next(null); |
| localStorage.removeItem('pydetect_current_session'); |
| } |
|
|
| |
| private handleError(error: any): Observable<never> { |
| |
| 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)); |
| } |
|
|
| |
|
|
| |
| validateCaseData(caseData: Partial<CaseData>): { 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 |
| }; |
| } |
|
|
| |
| validateAccusedData(accusedData: Partial<AccusedData>): { 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 |
| }; |
| } |
|
|
| |
|
|
| |
| 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 |
| }; |
| } |
|
|
| |
| getInvestigationQuestions(sessionId: string, crimeType: string, briefDescription: string): Observable<any> { |
| return this.http.get( |
| `${this.baseUrl}/ask_question?session_id=${sessionId}&crime_type=${encodeURIComponent(crimeType)}&brief_description=${encodeURIComponent(briefDescription)}`, |
| this.httpOptions |
| ); |
| } |
| } |