Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Clients/src/application/config/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import Tasks from "../../presentation/pages/Tasks";
import IntegratedDashboard from "../../presentation/pages/DashboardOverview/IntegratedDashboard";
import StartHere from "../../presentation/pages/StartHere";
import RiskManagement from "../../presentation/pages/RiskManagement";
import RiskIntelligenceLibrary from "../../presentation/pages/RiskIntelligenceLibrary";
import AutomationsPage from "../../presentation/pages/Automations";
import StyleGuide from "../../presentation/pages/StyleGuide";

Expand Down Expand Up @@ -126,6 +127,7 @@ export const createRoutes = (
{/* Dynamic route for plugin tabs (e.g., mlflow, other future plugins) */}
<Route path="/model-inventory/:pluginTab" element={<ModelInventory />} />
<Route path="/risk-management" element={<RiskManagement />} />
<Route path="/risk-intelligence-library" element={<RiskIntelligenceLibrary />} />
<Route path="/tasks" element={<Tasks />} />
<Route path="/automations" element={<AutomationsPage />} />
<Route path="/ai-incident-managements" element={<IncidentManagement />} />
Expand Down
169 changes: 169 additions & 0 deletions Clients/src/application/hooks/useRiskLibrary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import {
searchRiskLibrary,
getRiskLibraryEntry,
getRiskLibraryFilters,
getRiskLibraryStats,
submitRiskLibraryFeedback,
removeRiskLibraryFeedback,
upsertRiskLibraryCustomization,
generateRiskTaxonomy,
generateRiskMitigations,
generateRiskAssessment,
submitGenerationFeedback,
} from "../repository/riskLibrary.repository";
import {
RiskLibrarySearchParams,
RiskLibrarySearchResult,
RiskLibraryEntryDetail,
RiskLibraryFilters,
RiskLibraryStats,
} from "../../domain/types/RiskLibrary";

// ============================================================================
// QUERY KEYS
// ============================================================================

export const riskLibraryKeys = {
all: ["riskLibrary"] as const,
lists: () => [...riskLibraryKeys.all, "list"] as const,
list: (params: RiskLibrarySearchParams) =>
[...riskLibraryKeys.lists(), params] as const,
details: () => [...riskLibraryKeys.all, "detail"] as const,
detail: (id: number) => [...riskLibraryKeys.details(), id] as const,
filters: () => [...riskLibraryKeys.all, "filters"] as const,
stats: () => [...riskLibraryKeys.all, "stats"] as const,
feedback: (id: number) => [...riskLibraryKeys.all, "feedback", id] as const,
};

// ============================================================================
// SEARCH & READ HOOKS
// ============================================================================

export function useRiskLibrarySearch(params: RiskLibrarySearchParams) {
return useQuery({
queryKey: riskLibraryKeys.list(params),
queryFn: async ({ signal }) => {
const response = await searchRiskLibrary({ params, signal });
return response.data as RiskLibrarySearchResult;
},
staleTime: 5 * 60 * 1000,
gcTime: 10 * 60 * 1000,
});
}

export function useRiskLibraryEntry(id: number | null) {
return useQuery({
queryKey: riskLibraryKeys.detail(id!),
queryFn: async ({ signal }) => {
const response = await getRiskLibraryEntry({ id: id!, signal });
return response.data as RiskLibraryEntryDetail;
},
enabled: id !== null,
staleTime: 5 * 60 * 1000,
});
}

export function useRiskLibraryFilters() {
return useQuery({
queryKey: riskLibraryKeys.filters(),
queryFn: async ({ signal }) => {
const response = await getRiskLibraryFilters({ signal });
return response.data as RiskLibraryFilters;
},
staleTime: 30 * 60 * 1000,
gcTime: 60 * 60 * 1000,
});
}

export function useRiskLibraryStats() {
return useQuery({
queryKey: riskLibraryKeys.stats(),
queryFn: async ({ signal }) => {
const response = await getRiskLibraryStats({ signal });
return response.data as RiskLibraryStats;
},
staleTime: 5 * 60 * 1000,
});
}

// ============================================================================
// FEEDBACK MUTATIONS
// ============================================================================

export function useSubmitFeedback() {
const queryClient = useQueryClient();

return useMutation({
mutationFn: submitRiskLibraryFeedback,
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({
queryKey: riskLibraryKeys.detail(variables.id),
});
queryClient.invalidateQueries({
queryKey: riskLibraryKeys.feedback(variables.id),
});
},
});
}

export function useRemoveFeedback() {
const queryClient = useQueryClient();

return useMutation({
mutationFn: removeRiskLibraryFeedback,
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({
queryKey: riskLibraryKeys.detail(variables.id),
});
queryClient.invalidateQueries({
queryKey: riskLibraryKeys.feedback(variables.id),
});
},
});
}

// ============================================================================
// ORG CUSTOMIZATION MUTATION
// ============================================================================

export function useUpsertCustomization() {
const queryClient = useQueryClient();

return useMutation({
mutationFn: upsertRiskLibraryCustomization,
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({
queryKey: riskLibraryKeys.detail(variables.id),
});
},
});
}

// ============================================================================
// AI GENERATION MUTATIONS
// ============================================================================

export function useGenerateTaxonomy() {
return useMutation({
mutationFn: generateRiskTaxonomy,
});
}

export function useGenerateMitigations() {
return useMutation({
mutationFn: generateRiskMitigations,
});
}

export function useGenerateAssessment() {
return useMutation({
mutationFn: generateRiskAssessment,
});
}

export function useSubmitGenerationFeedback() {
return useMutation({
mutationFn: submitGenerationFeedback,
});
}
183 changes: 183 additions & 0 deletions Clients/src/application/repository/riskLibrary.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { apiServices } from "../../infrastructure/api/networkServices";
import { RiskLibrarySearchParams } from "../../domain/types/RiskLibrary";

// ============================================================================
// SEARCH & READ
// ============================================================================

export async function searchRiskLibrary({
params,
signal,
}: {
params: RiskLibrarySearchParams;
signal?: AbortSignal;
}) {
const query = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== "") {
query.append(key, String(value));
}
});
const response = await apiServices.get(`/risk-library?${query.toString()}`, {
signal,
});
return response.data;
}

export async function getRiskLibraryEntry({
id,
signal,
}: {
id: number;
signal?: AbortSignal;
}) {
const response = await apiServices.get(`/risk-library/${id}`, { signal });
return response.data;
}

export async function getRiskLibraryFilters({
signal,
}: {
signal?: AbortSignal;
} = {}) {
const response = await apiServices.get("/risk-library/filters", { signal });
return response.data;
}

export async function getRiskLibraryStats({
signal,
}: {
signal?: AbortSignal;
} = {}) {
const response = await apiServices.get("/risk-library/stats", { signal });
return response.data;
}

// ============================================================================
// FEEDBACK
// ============================================================================

export async function submitRiskLibraryFeedback({
id,
feedback_type,
flag_reason,
context,
}: {
id: number;
feedback_type: "upvote" | "downvote" | "flag";
flag_reason?: string;
context?: Record<string, unknown>;
}) {
const response = await apiServices.post(`/risk-library/${id}/feedback`, {
feedback_type,
flag_reason,
context,
});
return response.data;
}

export async function removeRiskLibraryFeedback({ id }: { id: number }) {
const response = await apiServices.delete(`/risk-library/${id}/feedback`);
return response.data;
}

export async function getRiskLibraryFeedback({
id,
signal,
}: {
id: number;
signal?: AbortSignal;
}) {
const response = await apiServices.get(`/risk-library/${id}/feedback`, {
signal,
});
return response.data;
}

// ============================================================================
// ORG CUSTOMIZATION
// ============================================================================

export async function upsertRiskLibraryCustomization({
id,
custom_mitigations,
custom_notes,
is_hidden,
}: {
id: number;
custom_mitigations?: string;
custom_notes?: string;
is_hidden?: boolean;
}) {
const response = await apiServices.put(`/risk-library/${id}/customize`, {
custom_mitigations,
custom_notes,
is_hidden,
});
return response.data;
}

// ============================================================================
// AI GENERATION
// ============================================================================

export async function generateRiskTaxonomy(body: {
industry: string;
use_case: string;
ai_system_type?: string;
lifecycle_phase?: string;
project_description?: string;
existing_risks?: string[];
llm_key_id: number;
}) {
const response = await apiServices.post(
"/risk-library/generate/taxonomy",
body
);
return response.data;
}

export async function generateRiskMitigations(body: {
risk_summary: string;
risk_description: string;
risk_category?: string;
severity?: string;
industry?: string;
existing_mitigations?: string[];
llm_key_id: number;
}) {
const response = await apiServices.post(
"/risk-library/generate/mitigations",
body
);
return response.data;
}

export async function generateRiskAssessment(body: {
use_case: string;
industry: string;
project_description?: string;
model_type?: string;
lifecycle_phase?: string;
llm_key_id: number;
}) {
const response = await apiServices.post(
"/risk-library/generate/assessment",
body
);
return response.data;
}

export async function submitGenerationFeedback({
id,
feedback_type,
}: {
id: number;
feedback_type: "upvote" | "downvote" | "flag";
}) {
const response = await apiServices.post(
`/risk-library/generations/${id}/feedback`,
{ feedback_type }
);
return response.data;
}
Loading
Loading