From 490bcdaa1b4da1d5f84bb4785c160015b8d7c6ad Mon Sep 17 00:00:00 2001 From: Joe Slain Date: Thu, 19 Dec 2024 14:51:50 +0100 Subject: [PATCH 1/4] feat: add question list --- src/pages/Evaluations.tsx | 177 +++++++++++++++++++++++++++++++------- 1 file changed, 146 insertions(+), 31 deletions(-) diff --git a/src/pages/Evaluations.tsx b/src/pages/Evaluations.tsx index 9c8dd84..7e7b71b 100644 --- a/src/pages/Evaluations.tsx +++ b/src/pages/Evaluations.tsx @@ -43,39 +43,145 @@ function difficultyLevelsToColor(difficulty: string) { const questions = [ { question: - 'Un usager relevant de la msa travaille maintenant dans le privé depuis 1 an. comment doit il faire pour que son dossier soit muté à la cpam ?', + 'Comment changer le titulaire sur la carte grise suite au décès du conjoint ?', + theme: 'Immatriculation', + operators: ['ANTS / France titres'], + complexity: 'Simple', + }, + { + question: + "L'usager souhaite faire la carte grise d'un véhicule acquis en 2021 mais n'a plus le certificat de cession. Comment faire ?", + theme: 'Immatriculation', + operators: ['ANTS / France titres'], + complexity: 'Intermédiaire', + }, + { + question: + "Une usagère, dont son frère est décédé et était titulaire de la carte grise, a hérité du véhicule. Si elle ne veut pas conserver le véhicule, peut-elle le vendre directement sans mettre la carte grise à son nom, sachant que le véhicule n'a pas roulé depuis le décès mais que le décès était il y a plus de 3 mois ?", + theme: 'Immatriculation', + operators: ['ANTS / France titres'], + complexity: 'Complexe', + }, + { + question: "Quel cerfa dois-je fournir pour une demande d'aide au logement à la CAF ?", + theme: 'Logement', + operators: ['CAF'], + complexity: 'Simple', + }, + { + question: + "L'usagère, âgée de 57 ans, est en accident de travail depuis presque 6 mois suite à une mission d'intérim. Elle est par ailleurs reconnue travailleur handicapé. Elle va passer en longue maladie mais n'a pas d'employeur puisque sa mission d'intérim s'est terminée depuis. Elle vit seule et n'a pas d'enfant. Quels sont ses droits financiers ? A-t-elle droit au RSA ?", + theme: 'RSA', + operators: ['CAF'], + complexity: 'Complexe', + }, + { + question: + "Quelle est le montant maximum pour un couple qui souhaite bénéficier de l'ASPA ?", + theme: 'ASPA', + operators: ['CARSAT'], + complexity: 'Simple', + }, + { + question: 'Combien faut-il de trimestres cotisés pour une retraite à taux plein ?', + theme: 'Retraite', + operators: ['CARSAT'], + complexity: 'Simple', + }, + { + question: + "Une usagère souhaite savoir si le fait d'être à mi-temps thérapeutique pour une durée de 6 mois engendre un changement dans la date de départ à la retraite ou sur le montant de sa retraite ?", + theme: 'Retraite', + operators: ['CARSAT'], + complexity: 'Intermédiaire', + }, + { + question: + "L'usager ne comprend pas pourquoi sa pension de retraite a diminué alors qu'il y a eu une revalorisation des retraites de 5,3% au premier janvier 2024. Pourquoi est-ce que sa pension de retraite diminue ?", theme: 'Retraite', - operator: 'CPAM', + operators: ['CARSAT'], complexity: 'Complexe', }, { question: - "Quels sont les documents nécessaires pour faire une demande de prime d'activité ?", - theme: 'Prime', - operator: 'CAF', + 'Une usagère part en vacances au Maroc, elle souhaite savoir si les dépenses de santé au Maroc peuvent être prises en charge par la CPAM ?', + theme: 'Aides financières', + operators: ['CPAM'], complexity: 'Simple', }, { question: - "Existe-t-il une aide sociale pour participer au coût d'un déménagement réalisé par un professionnel?", - theme: 'Déménagement', - operator: 'CAF', + "L'usager ne perçoit plus sa pension d'invalidité. La CPAM peut-elle arrêter le versement de la prestation après 2 ans de paiement alors que l'usager n'a jamais rencontré de médecin conseil ?", + theme: "Pension d'invalidité", + operators: ['CPAM'], complexity: 'Intermédiaire', }, { question: - "Une personne en EHPAD souhaite faire une demande d'APL à la CAF. Quelles sont les pièces à joindre à sa demande ?", - theme: 'APL', - operator: 'CAF', + 'Un usager relevant de la MSA travaille maintenant dans le privé depuis 1 an. Comment doit-il faire pour que son dossier soit muté à la CPAM ?', + theme: 'Droits', + operators: ['CPAM'], complexity: 'Complexe', }, + { + question: "Quelles sont les conditions d'exonération de taxe foncière ?", + theme: 'Impôts', + operators: ['DGFIP'], + complexity: 'Simple', + }, + { + question: + "Comment est calculé le montant de la taxe d'ordure ménagère pour des locations foncières ?", + theme: 'Impôts', + operators: ['DGFIP'], + complexity: 'Simple', + }, + { + question: + "Sur GMBI, est-ce qu'un bien apparaît sur les espaces des deux conjoints lorsque le bien est en indivision ?", + theme: 'GMBI', + operators: ['DGFIP'], + complexity: 'Intermédiaire', + }, + { + question: + "L'usager a-t-il le droit à des indemnités France travail après une démission ?", + theme: 'Emploi', + operators: ['France travail'], + complexity: 'Simple', + }, + { + question: 'Quelle est la démarche pour demander son casier judiciaire ?', + theme: 'Casier judiciaire', + operators: ['Ministère de la Justice'], + complexity: 'Simple', + }, { question: - "Comment signaler un changement d'adresse auprès des administrations en une seule fois ?", - theme: 'Démarches', - operator: 'ANTS', + "L'usager est affilié à la MSA. Comment procéder au renouvellement de sa carte vitale ?", + theme: 'Carte vitale', + operators: ['MSA'], complexity: 'Simple', }, + { + question: 'Comment recourir au médiateur de la Caf ou de la MSA ?', + theme: 'Médiation', + operators: ['MSA', 'CAF'], + complexity: 'Intermédiaire', + }, + { + question: 'Comment demander un chèque énergie 2023 non reçu ?', + theme: 'Chèque énergie', + operators: ['MTE'], + complexity: 'Simple', + }, + { + question: + "Une usagère n'ayant pas fait sa déclaration de revenu fiscal depuis 2021 peut-elle avoir droit au chèque énergie ?", + theme: 'Chèque énergie', + operators: ['MTE'], + complexity: 'Intermédiaire', + }, ] export default function Evaluations() { @@ -101,7 +207,7 @@ export default function Evaluations() { ) : ( @@ -142,14 +248,14 @@ function QuestionRow({ index, question, theme, - operator, + operators, complexity, setSelectedCardIndex, }: { index: number question: string theme: string - operator: string + operators: string[] complexity: string setSelectedCardIndex: (index: number) => void }) { @@ -176,17 +282,26 @@ function QuestionRow({

{question}

{theme} + {operators.map((operator, index) => ( + + {operator} + + ))} - {operator} - - {complexity} @@ -197,7 +312,7 @@ function QuestionRow({ ) } -function QuestionDetail({ question, theme, operator, onBack, complexity }) { +function QuestionDetail({ question, theme, operators, onBack, complexity }) { const [isStreamFinished, setIsStreamFinished] = useState(false) const [streamId, setStreamId] = useState(null) @@ -213,7 +328,7 @@ function QuestionDetail({ question, theme, operator, onBack, complexity }) { setIsStreamFinished={setIsStreamFinished} setStreamId={setStreamId} theme={theme} - operator={operator} + operators={operators} complexity={complexity} /> void complexity: string setStreamId: (value: number) => void @@ -464,7 +579,7 @@ function AnswerPannel({ const prompt = { chat_type: 'evaluations', themes: [theme], - operator: [operator], + operators: [operators], } // Auto-scroll to bottom when new content is added @@ -555,7 +670,7 @@ function AnswerPannel({ {theme}

- {operator} + {operators}

{complexity} From 6c597cad50d86f2c7b0d678bf919bc6acd68c064 Mon Sep 17 00:00:00 2001 From: Joe Slain Date: Thu, 19 Dec 2024 15:16:36 +0100 Subject: [PATCH 2/4] feat: show 5 random questions, add evaluated questions to localstorage to avoid seeing it multiple times added onback naviguation between evaluation pannel and question list --- src/pages/Evaluations.tsx | 96 ++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 32 deletions(-) diff --git a/src/pages/Evaluations.tsx b/src/pages/Evaluations.tsx index 7e7b71b..1ef4ca6 100644 --- a/src/pages/Evaluations.tsx +++ b/src/pages/Evaluations.tsx @@ -13,6 +13,7 @@ import { TextWithSources } from 'components/Sources/TextWithSources' import { EventSourcePolyfill } from 'event-source-polyfill' import { useCallback, useEffect, useRef, useState } from 'react' import { onCloseStream } from '../utils/eventsEmitter' +import { useNavigate, useLocation } from 'react-router-dom' import { negativeTags, positiveTags, @@ -185,24 +186,28 @@ const questions = [ ] export default function Evaluations() { - const [selectedCardIndex, setSelectedCardIndex] = useState(null) + const navigate = useNavigate() + const location = useLocation() + + // Determine whether we're on the panel or the questions list + const selectedCardIndex = location.state?.selectedCardIndex ?? null const handleBack = () => { - setSelectedCardIndex(null) + navigate('/evaluations', { state: null }) } return ( -

+
{selectedCardIndex === null ? ( <>

Sélectionnez une question

- Dans le cadre de l’expérimentation nous entamons une phase de ré-évaluation du - modèle proposé sur des questions pré-définies. Le travail s’effectue sur un + Dans le cadre de l’expérimentation, nous entamons une phase de ré-évaluation + du modèle proposé sur des questions pré-définies. Le travail s’effectue sur un échantillon de X questions à évaluer. Nous en présentons à chaque évaluation 5 de manière aléatoire.

- + ) : ( - // } +function Questions({ navigate }) { + const [filteredQuestions, setFilteredQuestions] = useState([]) + const [evaluatedQuestions, setEvaluatedQuestions] = useState( + JSON.parse(localStorage.getItem('evaluatedQuestions') || '[]'), + ) + useEffect(() => { - console.log('render') - }, []) + const unevaluatedQuestions = questions.filter( + (q, index) => !evaluatedQuestions.includes(index), + ) + const shuffledQuestions = unevaluatedQuestions + .map((q, index) => ({ ...q, originalIndex: questions.indexOf(q) })) + .sort(() => Math.random() - 0.5) + .slice(0, 5) + + setFilteredQuestions(shuffledQuestions) + }, [evaluatedQuestions]) + + const handleEvaluation = (index) => { + const newEvaluatedQuestions = [...evaluatedQuestions, index] + localStorage.setItem('evaluatedQuestions', JSON.stringify(newEvaluatedQuestions)) + setEvaluatedQuestions(newEvaluatedQuestions) + } + return (
- {questions.map((question, index) => ( - - ))} + {filteredQuestions.length > 0 ? ( + filteredQuestions.map((question, idx) => ( + + navigate('/evaluations', { + state: { selectedCardIndex: question.originalIndex }, + }) + } + onEvaluate={() => handleEvaluation(question.originalIndex)} + /> + )) + ) : ( +

+ Il n'y a plus de questions à évaluer. Merci pour votre contribution ! +

+ )}
) } @@ -250,17 +280,20 @@ function QuestionRow({ theme, operators, complexity, - setSelectedCardIndex, + onSelect, + onEvaluate, }: { index: number question: string theme: string operators: string[] complexity: string - setSelectedCardIndex: (index: number) => void + onSelect: () => void + onEvaluate: () => void }) { const handleClick = () => { - setSelectedCardIndex(index) + onSelect() + onEvaluate() } const handleKeyDown = (e: React.KeyboardEvent) => { @@ -273,7 +306,7 @@ function QuestionRow({ return ( <>
{ setShowErrorNotice(false) - // TODO: BACK END LOGIC if (isSubmitDisabled) return if (!streamId) { setShowErrorNotice(true) @@ -579,7 +611,7 @@ function AnswerPannel({ const prompt = { chat_type: 'evaluations', themes: [theme], - operators: [operators], + operators: operators, } // Auto-scroll to bottom when new content is added From f72e67d0f3d0cb0186e86d17bc0c3a2ff211c4bd Mon Sep 17 00:00:00 2001 From: Joe Slain Date: Thu, 19 Dec 2024 15:41:24 +0100 Subject: [PATCH 3/4] feat: add question to local storage only uppon succesfull evaluation post --- src/pages/Evaluations.tsx | 62 ++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/src/pages/Evaluations.tsx b/src/pages/Evaluations.tsx index 1ef4ca6..b031c79 100644 --- a/src/pages/Evaluations.tsx +++ b/src/pages/Evaluations.tsx @@ -3,44 +3,30 @@ const modelMode: string = import.meta.env.VITE_MODEL_MODE as string const modelTemperature: number = import.meta.env.VITE_MODEL_TEMPERATURE as number import { chatUrl, streamUrl, useAddFeedback } from '@api' +import { fr } from '@codegouvfr/react-dsfr' import { Notice } from '@codegouvfr/react-dsfr/Notice' import StarIcon from '@mui/icons-material/Star' import Box from '@mui/material/Box' import Rating from '@mui/material/Rating' -import { useFetch } from '@utils/hooks' -import { setHeaders } from '@utils/setData' -import { TextWithSources } from 'components/Sources/TextWithSources' -import { EventSourcePolyfill } from 'event-source-polyfill' -import { useCallback, useEffect, useRef, useState } from 'react' -import { onCloseStream } from '../utils/eventsEmitter' -import { useNavigate, useLocation } from 'react-router-dom' import { - negativeTags, - positiveTags, type NegativeFeedbackArray, type NegativeReason, type PositiveFeedbackArray, type PositiveReason, + negativeTags, + positiveTags, } from '@types' -import { LoadingSpinner } from 'components/LoadingSpinner' -import { set } from 'valibot' +import { useFetch } from '@utils/hooks' +import { setHeaders } from '@utils/setData' import Separator from 'components/Global/Separator' -import { fr } from '@codegouvfr/react-dsfr' +import { LoadingSpinner } from 'components/LoadingSpinner' +import { TextWithSources } from 'components/Sources/TextWithSources' +import { EventSourcePolyfill } from 'event-source-polyfill' +import { useCallback, useEffect, useRef, useState } from 'react' +import { useLocation, useNavigate } from 'react-router-dom' +import { onCloseStream } from '../utils/eventsEmitter' //import ShowError from 'components/Error/ShowError' -const difficultyLevels = ['Simple', 'Intermédiaire', 'Complexe'] -function difficultyLevelsToColor(difficulty: string) { - switch (difficulty) { - case 'Simple': - return fr.colors.decisions.background.alt.greenEmeraude.active - case 'Intermédiaire': - return fr.colors.decisions.background.actionLow.yellowTournesol.default - case 'Complexe': - return fr.colors.decisions.background.open.redMarianne.active - default: - return 'fr-tag--blue' - } -} const questions = [ { question: @@ -240,12 +226,6 @@ function Questions({ navigate }) { setFilteredQuestions(shuffledQuestions) }, [evaluatedQuestions]) - const handleEvaluation = (index) => { - const newEvaluatedQuestions = [...evaluatedQuestions, index] - localStorage.setItem('evaluatedQuestions', JSON.stringify(newEvaluatedQuestions)) - setEvaluatedQuestions(newEvaluatedQuestions) - } - return (
{filteredQuestions.length > 0 ? ( @@ -262,7 +242,6 @@ function Questions({ navigate }) { state: { selectedCardIndex: question.originalIndex }, }) } - onEvaluate={() => handleEvaluation(question.originalIndex)} /> )) ) : ( @@ -281,7 +260,6 @@ function QuestionRow({ operators, complexity, onSelect, - onEvaluate, }: { index: number question: string @@ -289,11 +267,9 @@ function QuestionRow({ operators: string[] complexity: string onSelect: () => void - onEvaluate: () => void }) { const handleClick = () => { onSelect() - onEvaluate() } const handleKeyDown = (e: React.KeyboardEvent) => { @@ -368,6 +344,7 @@ function QuestionDetail({ question, theme, operators, onBack, complexity }) { isStreamFinished={isStreamFinished} onBack={onBack} streamId={streamId} + question={question} />
@@ -378,10 +355,12 @@ function EvaluationPannel({ isStreamFinished, onBack, streamId, + question, }: { isStreamFinished: boolean onBack: () => void streamId: number | null + question: string }) { const [positiveFeedback, setPositiveFeedback] = useState([]) const [negativeFeedback, setNegativeFeedback] = useState([]) @@ -408,6 +387,13 @@ function EvaluationPannel({ prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag], ) } + const addQuestionToLocalStorage = (questionIndex: number) => { + const evaluatedQuestions = JSON.parse( + localStorage.getItem('evaluatedQuestions') || '[]', + ) + const updatedQuestions = [...evaluatedQuestions, questionIndex] + localStorage.setItem('evaluatedQuestions', JSON.stringify(updatedQuestions)) + } const handleSubmit = () => { setShowErrorNotice(false) if (isSubmitDisabled) return @@ -452,6 +438,8 @@ function EvaluationPannel({ return newProgress }) }, intervalTime) + const questionIndex = questions.findIndex((q) => q.question === question) + addQuestionToLocalStorage(questionIndex) }, onError: (error) => { setShowErrorNotice(true) @@ -805,8 +793,8 @@ function AlertNotice({ From bd621bbc027ee945e0e0c9675a805013949acbe3 Mon Sep 17 00:00:00 2001 From: Joe Slain Date: Thu, 19 Dec 2024 17:12:24 +0100 Subject: [PATCH 4/4] feat: final styling of evals --- src/pages/Evaluations.tsx | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/pages/Evaluations.tsx b/src/pages/Evaluations.tsx index b031c79..b4de413 100644 --- a/src/pages/Evaluations.tsx +++ b/src/pages/Evaluations.tsx @@ -3,7 +3,6 @@ const modelMode: string = import.meta.env.VITE_MODEL_MODE as string const modelTemperature: number = import.meta.env.VITE_MODEL_TEMPERATURE as number import { chatUrl, streamUrl, useAddFeedback } from '@api' -import { fr } from '@codegouvfr/react-dsfr' import { Notice } from '@codegouvfr/react-dsfr/Notice' import StarIcon from '@mui/icons-material/Star' import Box from '@mui/material/Box' @@ -190,8 +189,8 @@ export default function Evaluations() {

Dans le cadre de l’expérimentation, nous entamons une phase de ré-évaluation du modèle proposé sur des questions pré-définies. Le travail s’effectue sur un - échantillon de X questions à évaluer. Nous en présentons à chaque évaluation 5 - de manière aléatoire. + échantillon de 21 questions à évaluer. Nous en présentons à chaque évaluation + 5 de manière aléatoire.

@@ -281,15 +280,14 @@ function QuestionRow({ return ( <> -
-

{question}

-
+

{question}

+
{theme} - {operators.map((operator, index) => ( + {operators.map((operator, idx) => (
-
+ ) @@ -707,7 +705,7 @@ function AnswerPannel({