|
|
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 |
|
|
); |
|
|
} |
|
|
} |