From 7d109850d14b6be9dea09a7dd41e85c00c2ba207 Mon Sep 17 00:00:00 2001 From: Maham Akif <113524403+mahamakifdar19@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:02:09 +0500 Subject: [PATCH] [ENT-8322] feat: askXpert component to show user's query results and adding the ability to tweak them (#378) * feat: create react component to all users to input querries and show loading bar * feat: askXpert component to show users query results and the ability to tweak results --------- Co-authored-by: jajjibhai008 Co-authored-by: Maham Akif --- src/components/aiCuration/data/hooks.jsx | 35 ++- .../xpertResultCard/XpertResultCard.jsx | 199 ++++++++++++++++++ .../xpertResultCard/XpertResultCard.scss | 24 +++ src/index.scss | 1 + 4 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 src/components/aiCuration/xpertResultCard/XpertResultCard.jsx create mode 100644 src/components/aiCuration/xpertResultCard/XpertResultCard.scss diff --git a/src/components/aiCuration/data/hooks.jsx b/src/components/aiCuration/data/hooks.jsx index f1544340..d803c29c 100644 --- a/src/components/aiCuration/data/hooks.jsx +++ b/src/components/aiCuration/data/hooks.jsx @@ -1,4 +1,6 @@ -import { useEffect, useRef } from 'react'; +import { useState, useEffect, useRef } from 'react'; +import { logError } from '@edx/frontend-platform/logging'; +import EnterpriseCatalogAiCurationApiService from './service'; export default function useInterval(callback, delay) { const savedCallback = useRef(); @@ -19,3 +21,34 @@ export default function useInterval(callback, delay) { } }, [delay]); } + +export const useXpertResultsWithThreshold = () => { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [xpertResultsData, setXpertResultsData] = useState([]); + + const getXpertResultsWithThreshold = async (taskId, threshold) => { + try { + setLoading(true); + const results = await EnterpriseCatalogAiCurationApiService.getXpertResults(taskId, threshold); + const { status, data: responseData, error: responseError } = results; + + if (status >= 400 && status < 600) { + setError(responseError); + setXpertResultsData([]); + } else { + setXpertResultsData(responseData || []); + } + } catch (err) { + setError(err); + logError(err); + setXpertResultsData([]); + } finally { + setLoading(false); + } + }; + + return { + loading, error, xpertResultsData, getXpertResultsWithThreshold, + }; +}; diff --git a/src/components/aiCuration/xpertResultCard/XpertResultCard.jsx b/src/components/aiCuration/xpertResultCard/XpertResultCard.jsx new file mode 100644 index 00000000..facd7766 --- /dev/null +++ b/src/components/aiCuration/xpertResultCard/XpertResultCard.jsx @@ -0,0 +1,199 @@ +import React, { useState, useMemo } from 'react'; +import PropTypes from 'prop-types'; +import { + Icon, Card, Stack, Form, Button, Spinner, Image, +} from '@edx/paragon'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; +import { Close } from '@edx/paragon/icons'; +import askXpretImage from '../../../assets/edx-xpert-card-side-image.png'; +import { useXpertResultsWithThreshold } from '../data/hooks'; +import { + CONTENT_TYPE_COURSE, + CONTENT_TYPE_PROGRAM, + EXEC_ED_TITLE, +} from '../../../constants'; + +const debounce = require('lodash.debounce'); + +const XpertResultCard = ({ taskId, queryTitle, xpertData }) => { + const [thresholdValue, setThresholdValue] = useState(0); + const [xpertResults, setXpertResults] = useState(xpertData); + const { + loading, error, xpertResultsData, getXpertResultsWithThreshold, + } = useXpertResultsWithThreshold(); + + const debouncedHandleChange = debounce(async (threshold) => { + getXpertResultsWithThreshold(taskId, threshold); + + if (xpertResultsData) { + setXpertResults(xpertResultsData); + } + }, 1000); + + const handleChange = async (e) => { + const threshold = Number(e.target.value); + setThresholdValue(threshold); + + debouncedHandleChange(threshold); + }; + + const xpertResultStats = useMemo(() => { + const selfPacedCoursesCount = xpertResults.filter(item => item.learning_type === CONTENT_TYPE_COURSE).length; + const selfPacedProgramsCount = xpertResults.filter(item => item.learning_type === CONTENT_TYPE_PROGRAM).length; + const execEdCoursesCount = xpertResults.filter(item => item.learning_type === EXEC_ED_TITLE).length; + + return { + selfPacedCoursesCount, + selfPacedProgramsCount, + execEdCoursesCount, + }; + }, [xpertResults]); + + const { selfPacedCoursesCount, selfPacedProgramsCount, execEdCoursesCount } = xpertResultStats; + + return ( +
+
+ + + edx Xpert logo + + + +

+ +

+
+ {queryTitle} }} + /> +
+ {xpertResults && xpertResults.length > 0 + && ( +
+
+ {loading + ? + :

{selfPacedCoursesCount}

} + + + +
+
+ {loading + ? + :

{selfPacedProgramsCount}

} + + + +
+
+ {loading + ? + :

{execEdCoursesCount}

} + + + +
+
+ )} + {error + && ( +

+ +

+ )} + {!error && !xpertResults.length + && ( +

+ +

+ )} +
+ + + Update your results + + + + + +
+
+
+ +
+ + +
+
+ +

Powered by OpenAI

+
+
+
+
+ ); +}; + +XpertResultCard.propTypes = { + taskId: PropTypes.number.isRequired, + queryTitle: PropTypes.string.isRequired, + xpertData: PropTypes.arrayOf( + PropTypes.shape({ + learning_type: PropTypes.string.isRequired, + queryTitle: PropTypes.string, + card_image_url: PropTypes.string, + course_keys: PropTypes.arrayOf(PropTypes.string), + enterprise_catalog_query_queryTitles: PropTypes.arrayOf(PropTypes.string), + original_image_url: PropTypes.string, + }), + ).isRequired, +}; + +export default XpertResultCard; diff --git a/src/components/aiCuration/xpertResultCard/XpertResultCard.scss b/src/components/aiCuration/xpertResultCard/XpertResultCard.scss new file mode 100644 index 00000000..db709c12 --- /dev/null +++ b/src/components/aiCuration/xpertResultCard/XpertResultCard.scss @@ -0,0 +1,24 @@ +input[type='range'] { + accent-color: yellow; +} + +input[type='range']::-webkit-slider-runnable-track { + width: 300px; + height: 6px; + background: #fff; + border: none; + border-radius: 3px; +} + +input[type='range']::-webkit-slider-thumb { + -webkit-appearance: none; + border: none; + height: 16px; + width: 16px; + border-radius: 50%; + margin-top: -4px; +} + +input[type='range']:focus::-webkit-slider-runnable-track { + background: #ffff; +} diff --git a/src/index.scss b/src/index.scss index f9886ea7..b4546dd3 100644 --- a/src/index.scss +++ b/src/index.scss @@ -17,6 +17,7 @@ @import './components/hero/Hero'; @import 'components/catalogSearchResults/associatedComponents/downloadCsvButton/DownloadCsvButton'; @import 'components/catalogSelectionDeck/CatalogSelectionDeck'; +@import 'components/aiCuration/xpertResultCard/XpertResultCard'; .page-width { max-width: 1350px;