main
ts 206 lines 6.26 KB
Raw
1 import type {
2 AiAnalystIoc,
3 AiAnalystJob,
4 AiAnalystPalaceLesson,
5 AiAnalystReport,
6 AiAnalystReview,
7 AiAnalystReviewStats,
8 AlertWithReport,
9 PalaceConsolidation,
10 PalaceSearchHit,
11 QueuePalaceLessonPayload,
12 ReplayPayload,
13 SubmitReviewPayload
14 } from "@/types/aiAnalyst.d"
15 import type { FlaskBaseResponse } from "@/types/flask.d"
16 import { HttpClient } from "../httpClient"
17
18 export default {
19 // Jobs
20 createJob(payload: {
21 id: string
22 alert_id: number
23 customer_code: string
24 triggered_by: "scheduled" | "manual" | "webhook"
25 alert_type?: string
26 template_used?: string
27 }) {
28 return HttpClient.post<FlaskBaseResponse & { job: AiAnalystJob }>(`/ai_analyst/jobs`, payload)
29 },
30 updateJob(
31 jobId: string,
32 payload: {
33 status: "pending" | "running" | "completed" | "failed"
34 alert_type?: string
35 template_used?: string
36 error_message?: string
37 }
38 ) {
39 return HttpClient.patch<FlaskBaseResponse & { job: AiAnalystJob }>(`/ai_analyst/jobs/${jobId}`, payload)
40 },
41 getJob(jobId: string) {
42 return HttpClient.get<FlaskBaseResponse & { job: AiAnalystJob }>(`/ai_analyst/jobs/${jobId}`)
43 },
44 getJobsByAlert(alertId: number) {
45 return HttpClient.get<FlaskBaseResponse & { jobs: AiAnalystJob[] }>(`/ai_analyst/jobs/alert/${alertId}`)
46 },
47 getJobsByCustomer(customerCode: string) {
48 return HttpClient.get<FlaskBaseResponse & { jobs: AiAnalystJob[] }>(`/ai_analyst/jobs/customer/${customerCode}`)
49 },
50
51 // Reports
52 submitReport(payload: {
53 job_id: string
54 alert_id: number
55 customer_code: string
56 severity_assessment?: "Critical" | "High" | "Medium" | "Low" | "Informational"
57 summary?: string
58 report_markdown?: string
59 recommended_actions?: string
60 }) {
61 return HttpClient.post<FlaskBaseResponse & { report: AiAnalystReport }>(`/ai_analyst/reports`, payload)
62 },
63 getReportsByAlert(alertId: number) {
64 return HttpClient.get<FlaskBaseResponse & { reports: AiAnalystReport[] }>(
65 `/ai_analyst/reports/alert/${alertId}`
66 )
67 },
68
69 // IOCs
70 submitIocs(payload: {
71 report_id: number
72 alert_id: number
73 customer_code: string
74 iocs: {
75 ioc_value: string
76 ioc_type: "ip" | "domain" | "hash" | "process" | "url" | "user" | "command"
77 vt_verdict?: "malicious" | "suspicious" | "clean" | "unknown"
78 vt_score?: string
79 details?: string
80 }[]
81 }) {
82 return HttpClient.post<FlaskBaseResponse & { iocs_created: number; iocs: AiAnalystIoc[] }>(
83 `/ai_analyst/iocs`,
84 payload
85 )
86 },
87 getIocsByReport(reportId: number) {
88 return HttpClient.get<FlaskBaseResponse & { iocs: AiAnalystIoc[] }>(`/ai_analyst/iocs/report/${reportId}`)
89 },
90 getIocsByAlert(alertId: number) {
91 return HttpClient.get<FlaskBaseResponse & { iocs: AiAnalystIoc[] }>(`/ai_analyst/iocs/alert/${alertId}`)
92 },
93 getIocsByCustomer(customerCode: string, vtVerdict?: string) {
94 return HttpClient.get<FlaskBaseResponse & { iocs: AiAnalystIoc[] }>(
95 `/ai_analyst/iocs/customer/${customerCode}`,
96 {
97 params: vtVerdict ? { vt_verdict: vtVerdict } : {}
98 }
99 )
100 },
101
102 // Alerts with reports
103 getAlertsWithReports(customerCode?: string) {
104 return HttpClient.get<FlaskBaseResponse & { alerts: AlertWithReport[] }>(`/ai_analyst/alerts_with_reports`, {
105 params: customerCode ? { customer_code: customerCode } : {}
106 })
107 },
108
109 // Combined alert analysis
110 getAlertAnalysis(alertId: number) {
111 return HttpClient.get<
112 FlaskBaseResponse & {
113 job: AiAnalystJob | null
114 report: AiAnalystReport | null
115 iocs: AiAnalystIoc[] | null
116 }
117 >(`/ai_analyst/alert/${alertId}`)
118 },
119
120 // --- Reviews ---
121 /**
122 * Fetch the current user's existing review for a report (if any).
123 * Returns review=null in create-mode so the UI shows a fresh rubric.
124 */
125 getMyReview(reportId: number) {
126 return HttpClient.get<FlaskBaseResponse & { review: AiAnalystReview | null }>(
127 `/ai_analyst/reports/${reportId}/review/mine`
128 )
129 },
130 /**
131 * Upsert the current user's review for a report. Backend enforces
132 * one review per (report, user) via unique constraint — a second call
133 * updates the existing row and sets updated_at.
134 */
135 submitReview(reportId: number, payload: SubmitReviewPayload) {
136 return HttpClient.post<FlaskBaseResponse & { review: AiAnalystReview }>(
137 `/ai_analyst/reports/${reportId}/review`,
138 payload
139 )
140 },
141 getReviewsByCustomer(customerCode: string) {
142 return HttpClient.get<FlaskBaseResponse & { reviews: AiAnalystReview[] }>(
143 `/ai_analyst/reviews/customer/${customerCode}`
144 )
145 },
146 /**
147 * SQL-side feedback dashboard rollup — counts, averages, template
148 * breakdown, IOC accuracy, and embedded recent reviews for drill-in.
149 */
150 getReviewStats(customerCode: string, recentLimit = 10) {
151 return HttpClient.get<FlaskBaseResponse & AiAnalystReviewStats>(
152 `/ai_analyst/reviews/customer/${customerCode}/stats`,
153 {
154 params: { recent_limit: recentLimit }
155 }
156 )
157 },
158
159 // --- Replay ---
160 /**
161 * Re-run an investigation for the report's alert with a forced template.
162 * The replay creates its own new job/report via Talon's normal callbacks —
163 * this call does not mutate local DB itself.
164 */
165 replayReport(reportId: number, payload: ReplayPayload) {
166 return HttpClient.post<FlaskBaseResponse & { data?: Record<string, unknown> }>(
167 `/ai_analyst/reports/${reportId}/replay`,
168 payload
169 )
170 },
171
172 // --- Palace lessons ---
173 queuePalaceLesson(payload: QueuePalaceLessonPayload) {
174 return HttpClient.post<FlaskBaseResponse & { lesson: AiAnalystPalaceLesson }>(
175 `/ai_analyst/palace_lessons`,
176 payload
177 )
178 },
179 /**
180 * Preview similar lessons already stored in MemPalace — debounced against
181 * the lesson-text textarea so the reviewer can see overlap before queueing.
182 */
183 searchPalaceLessons(customerCode: string, query: string, room?: string, limit = 5) {
184 return HttpClient.get<FlaskBaseResponse & { lessons: PalaceSearchHit[] }>(
185 `/ai_analyst/palace_lessons/customer/${customerCode}`,
186 {
187 params: {
188 query,
189 limit,
190 ...(room ? { room } : {})
191 }
192 }
193 )
194 },
195 /**
196 * Manual consolidation digest — builds a point-in-time view of a
197 * customer's active MemPalace lessons (pending + ingested), grouped
198 * by room, with near-duplicate pairs and upcoming expirations
199 * surfaced for reviewer action. Pure read-only, no Talon round-trip.
200 */
201 getPalaceConsolidation(customerCode: string) {
202 return HttpClient.get<FlaskBaseResponse & PalaceConsolidation>(
203 `/ai_analyst/palace_lessons/customer/${customerCode}/consolidation`
204 )
205 }
206 }