diff --git a/frontend/src/app/routes/models/index.tsx b/frontend/src/app/routes/models/index.tsx index 6bc0d30e..6a7958b3 100644 --- a/frontend/src/app/routes/models/index.tsx +++ b/frontend/src/app/routes/models/index.tsx @@ -111,12 +111,12 @@ const LayoutToggle = ({ query, updateQuery, isMobile, - disabled=false + disabled = false, }: { updateQuery: (params: TQueryParams) => void; query: TQueryParams; isMobile?: boolean; - disabled?:boolean + disabled?: boolean; }) => { const activeLayout = query[SEARCH_PARAMS.layout]; return ( @@ -134,7 +134,7 @@ const LayoutToggle = ({ disabled={disabled} > {activeLayout !== LayoutView.LIST ? ( - + ) : ( )} @@ -295,7 +295,10 @@ export const ModelsPage = () => {
- +
@@ -360,7 +363,11 @@ export const ModelsPage = () => {
{/* Desktop */} - +
{/* Mobile */} diff --git a/frontend/src/components/landing/cta/cta.tsx b/frontend/src/components/landing/cta/cta.tsx index a416912b..50117ca0 100644 --- a/frontend/src/components/landing/cta/cta.tsx +++ b/frontend/src/components/landing/cta/cta.tsx @@ -3,12 +3,9 @@ import HOTTeam from "@/assets/images/hot_team_2.jpg"; import styles from "./cta.module.css"; import { APP_CONTENT } from "@/utils"; import { Image } from "@/components/ui/image"; +import { Link } from "@/components/ui/link"; const CallToAction = () => { - const joinTheCommunityClick = () => - { - window.open("https://slack.hotosm.org/") - } return (
@@ -17,10 +14,16 @@ const CallToAction = () => {

{APP_CONTENT.homepage.callToAction.paragraph}

- + + +
diff --git a/frontend/src/components/landing/kpi/kpi.tsx b/frontend/src/components/landing/kpi/kpi.tsx index db383493..a837e0ca 100644 --- a/frontend/src/components/landing/kpi/kpi.tsx +++ b/frontend/src/components/landing/kpi/kpi.tsx @@ -1,41 +1,68 @@ import { APP_CONTENT } from "@/utils"; import styles from "./kpi.module.css"; +import { API_ENDPOINTS, apiClient } from "@/services"; +import { useQuery } from "@tanstack/react-query"; type TKPIS = { - figure: string; + figure?: number; label: string; }[]; -const KPIs: TKPIS = [ - { - figure: "15", - label: APP_CONTENT.homepage.kpi.publishedAIModels, - }, - { - figure: "120", - label: APP_CONTENT.homepage.kpi.totalUsers, - }, - { - figure: "100", - label: APP_CONTENT.homepage.kpi.humanFeedback, - }, - { - figure: "15", - label: APP_CONTENT.homepage.kpi.acceptedPrediction, - } - // , - // { - // figure: "86", - // label: "Mappers", - // }, -]; +type TKPIResponse = { + total_accepted_predictions: number; + total_feedback_labels: number; + total_models_published: number; + total_registered_users: number; +}; + +const fetchKPIStats = async (): Promise => { + const { data } = await apiClient.get(API_ENDPOINTS.GET_KPI_STATS); + return data; +}; const Kpi = () => { + const { data, isLoading,isError,error} = useQuery({ + queryKey: ["kpis"], + queryFn: fetchKPIStats, + }); + + if (isError) { + return ( +
+
+

Error fetching KPI Stats

+

{(error as Error)?.message || 'An unknown error occurred.'}

+
+
+ ); + } + + const KPIs: TKPIS = [ + { + figure: data?.total_models_published ?? 0, + label: APP_CONTENT.homepage.kpi.publishedAIModels, + }, + { + figure: data?.total_registered_users ?? 0, + label: APP_CONTENT.homepage.kpi.totalUsers, + }, + { + figure: data?.total_feedback_labels ?? 0, + label: APP_CONTENT.homepage.kpi.humanFeedback, + }, + { + figure: data?.total_accepted_predictions ?? 0, + label: APP_CONTENT.homepage.kpi.acceptedPrediction, + }, + ]; + return (
{KPIs.map((kpi, id) => (
-

{kpi.figure}

+

+ {kpi.figure} +

{kpi.label}

))} diff --git a/frontend/src/features/models/components/dialogs/training-details-dialog.tsx b/frontend/src/features/models/components/dialogs/training-details-dialog.tsx index 9107ca92..23f3b3c9 100644 --- a/frontend/src/features/models/components/dialogs/training-details-dialog.tsx +++ b/frontend/src/features/models/components/dialogs/training-details-dialog.tsx @@ -6,13 +6,14 @@ type TrainingDetailsDialogProps = { isOpened: boolean; closeDialog: () => void; trainingId: number; - datasetId:number + datasetId: number; }; const TrainingDetailsDialog: React.FC = ({ isOpened, closeDialog, - trainingId,datasetId + trainingId, + datasetId, }) => { const isMobile = useDevice(); @@ -23,7 +24,11 @@ const TrainingDetailsDialog: React.FC = ({ label={`Training ${trainingId}`} size={isMobile ? "extra-large" : "medium"} > - + ); }; diff --git a/frontend/src/features/models/components/model-card.tsx b/frontend/src/features/models/components/model-card.tsx index 9bf6775c..7fe95ccc 100644 --- a/frontend/src/features/models/components/model-card.tsx +++ b/frontend/src/features/models/components/model-card.tsx @@ -6,14 +6,11 @@ import { Link } from "@/components/ui/link"; import { truncateString } from "@/utils"; import { roundNumber } from "@/utils/number-utils"; - type ModelCardProps = { model: TModel; }; - const ModelCard: React.FC = ({ model }) => { - return (
= ({ model }) => { >
= ({ source_imagery, } = data || {}; - - const trainingResultsGraph = `${ENVS.BASE_API_URL}workspace/download/dataset_${datasetId}/output/training_${data?.id}/graphs/training_validation_sparse_categorical_accuracy.png` - + const trainingResultsGraph = `${ENVS.BASE_API_URL}workspace/download/dataset_${datasetId}/output/training_${data?.id}/graphs/training_validation_sparse_categorical_accuracy.png`; + const content = useMemo(() => { if (isPending) { return ; @@ -201,9 +200,11 @@ const ModelProperties: React.FC = ({ isTMS />
- -
- {""} + +
+ {""}
{/* Show logs only in modal and when status failed */} {isTrainingDetailsDialog && data?.status === TrainingStatus.FAILED && ( @@ -221,7 +222,7 @@ const ModelProperties: React.FC = ({ input_contact_spacing, input_boundary_width, source_imagery, - trainingResultsGraph + trainingResultsGraph, ]); return isError ? ( diff --git a/frontend/src/features/models/components/training-history-table.tsx b/frontend/src/features/models/components/training-history-table.tsx index 694eff77..572ba2aa 100644 --- a/frontend/src/features/models/components/training-history-table.tsx +++ b/frontend/src/features/models/components/training-history-table.tsx @@ -30,7 +30,7 @@ type TrainingHistoryTableProps = { modelId: string; trainingId: number; modelOwner: string; - datasetId:number + datasetId: number; }; const columnDefinitions = ( @@ -226,7 +226,7 @@ const TrainingHistoryTable: React.FC = ({ trainingId, modelId, modelOwner, - datasetId + datasetId, }) => { const [offset, setOffset] = useState(0); const { data, isPending, isPlaceholderData } = useTrainingHistory( diff --git a/frontend/src/services/api-routes.ts b/frontend/src/services/api-routes.ts index 95c7d597..d0c3ed26 100644 --- a/frontend/src/services/api-routes.ts +++ b/frontend/src/services/api-routes.ts @@ -7,6 +7,10 @@ export const API_ENDPOINTS = { AUTH_CALLBACK: "auth/callback/", USER: "auth/me/", + //KPIs + + GET_KPI_STATS: "kpi/stats/ ", + //Models GET_MODELS: "model/", GET_MODEL_DETAILS: (id: string) => `model/${id}`, diff --git a/frontend/src/utils/content.ts b/frontend/src/utils/content.ts index 34a8673f..32a8ef60 100644 --- a/frontend/src/utils/content.ts +++ b/frontend/src/utils/content.ts @@ -73,10 +73,10 @@ export const APP_CONTENT = { ctaSecondaryButton: "Start Mapping", jumbotronImageAlt: "A user engaging in a mapping activity", kpi: { - publishedAIModels:"Published AI Models", - totalUsers:"Total Registered Users", + publishedAIModels: "Published AI Models", + totalUsers: "Total Registered Users", humanFeedback: "Human Feedbacks", - acceptedPrediction: "Accepted Prediction" + acceptedPrediction: "Accepted Prediction", }, aboutTitle: "WHAT IS fAIr?", aboutContent: `fAIr is an open AI-assisted mapping service developed by the Humanitarian OpenStreetMap Team (HOT) that aims to improve the efficiency and accuracy of mapping efforts for humanitarian purposes. The service uses AI models, specifically computer vision techniques, to detect objects in satellite and UAV imagery.`, @@ -144,7 +144,7 @@ export const APP_CONTENT = { { question: "Can I use fAIr without having a sound knowledge of AI?", answer: - "fAIr is design for users without the need for python or any programming skills. However, basic knowledge in humanitarian mapping and Geographical Information Systems (GIS) would be sufficient for self exploration.", + "fAIr is designed for users without the need for Python or any programming skills. However, basic knowledge in humanitarian mapping and Geographical Information Systems (GIS) would be sufficient for self exploration.", }, ], }, @@ -158,7 +158,9 @@ export const APP_CONTENT = { callToAction: { title: `We can't do it without you`, ctaButton: "Join The Community", - paragraph: "fAIr is a collaborative project. We welcome all types of experience to join our community on HOTOSM Slack. There is always a room for AI/ML for earth observation expertise, community engagement enthusiastic, academic researcher or student looking for an academic challenge around social impact." + ctaLink: "https://slack.hotosm.org", + paragraph: + "fAIr is a collaborative project. We welcome all types of experience to join our community on HOTOSM Slack. There is always a room for AI/ML for earth observation expertise, community engagement enthusiastic, academic researcher or student looking for an academic challenge around social impact.", }, }, pageNotFound: {