diff --git a/README.md b/README.md index ec3d707..8abd389 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Nous utilisons [ViteJS](https://vitejs.dev/) pour construire le projet donc chaq **VITE_ENVIRONMENT_NAME** -- une variable optionnelle utilisée pour afficher des fonctionnalités spécifiques pour les usagers de France Services. **VITE_MODEL_NAME** -- le modèle à utiliser pour les requêtes, vous pouvez trouver une liste ici [ici](https://huggingface.co/AgentPublic) -**VITE_MODEL_MODE** -- chaque modèle possède plusieurs modes lui permettant de répondre différemment: rag, rag-gt +**VITE_MODEL_MODE** -- chaque modèle possède plusieurs modes lui permettant de répondre différemment: rag, rag-gt-operators-llama **VITE_MODEL_TEMPERATURE** -- la température utilisée par le modèle pour générer la réponse. Entre 1 et 100 ## API diff --git a/src/components/Meeting/MeetingStream.tsx b/src/components/Meeting/MeetingStream.tsx index f52bdc1..9fc80e8 100644 --- a/src/components/Meeting/MeetingStream.tsx +++ b/src/components/Meeting/MeetingStream.tsx @@ -5,53 +5,18 @@ import { TextWithSources } from 'components/Sources/TextWithSources' import { GlobalParagraph } from 'components/Global/GlobalParagraph' import Separator from 'components/Global/Separator' import { Feedback } from '../Feedbacks/Feedback' +import { useStreamText } from 'hooks/useStreamText' export function MeetingStream() { const stream = useSelector((state: RootState) => state.stream) const agentResponse = stream.historyStream[0] - - // For letter-by-letter text - const [displayedText, setDisplayedText] = useState('') - const lastIndexRef = useRef(0) - - // We'll detect a new question/stream by checking user.lastStreamId const user = useSelector((state: RootState) => state.user) - const [prevStreamId, setPrevStreamId] = useState(null) - - // 1) Reset on new question - useEffect(() => { - // If lastStreamId changed => a brand-new question is being asked - if (user.lastStreamId && user.lastStreamId !== prevStreamId) { - setDisplayedText('') - lastIndexRef.current = 0 - setPrevStreamId(user.lastStreamId) - } - }, [user.lastStreamId, prevStreamId]) - - // 2) Append text while streaming - useEffect(() => { - if (!stream.isStreaming) return - - const intervalId = setInterval(() => { - if (lastIndexRef.current < stream.response.length) { - setDisplayedText((prev) => { - const nextChar = stream.response[lastIndexRef.current] - lastIndexRef.current += 1 - return prev + nextChar - }) - } - }, 0) // speed as desired - - return () => clearInterval(intervalId) - }, [stream.isStreaming, stream.response]) - // 3) Once streaming is done, ensure full text is displayed - useEffect(() => { - if (!stream.isStreaming && stream.response) { - setDisplayedText(stream.response) - lastIndexRef.current = stream.response.length - } - }, [stream.isStreaming, stream.response]) + const displayedText = useStreamText({ + text: stream.response, + isStreaming: stream.isStreaming, + streamId: user.lastStreamId, + }) return (
diff --git a/src/hooks/useStreamText.ts b/src/hooks/useStreamText.ts new file mode 100644 index 0000000..a4afe2f --- /dev/null +++ b/src/hooks/useStreamText.ts @@ -0,0 +1,55 @@ +import { useState, useRef, useEffect } from 'react' + +interface StreamTextOptions { + text: string + isStreaming: boolean + streamId?: number | null + speed?: number +} + +export function useStreamText({ + text, + isStreaming, + streamId = null, + speed = 0, +}: StreamTextOptions) { + const [displayedText, setDisplayedText] = useState('') + const lastIndexRef = useRef(0) + const [prevStreamId, setPrevStreamId] = useState(null) + + // Reset on new stream + useEffect(() => { + if (streamId && streamId !== prevStreamId) { + setDisplayedText('') + lastIndexRef.current = 0 + setPrevStreamId(streamId) + } + }, [streamId, prevStreamId]) + + // Append text while streaming + useEffect(() => { + if (!isStreaming) return + + const intervalId = setInterval(() => { + if (lastIndexRef.current < text.length) { + setDisplayedText((prev) => { + const nextChar = text[lastIndexRef.current] + lastIndexRef.current += 1 + return prev + nextChar + }) + } + }, speed) + + return () => clearInterval(intervalId) + }, [isStreaming, text, speed]) + + // Ensure full text is displayed when streaming ends + useEffect(() => { + if (!isStreaming && text) { + setDisplayedText(text) + lastIndexRef.current = text.length + } + }, [isStreaming, text]) + + return displayedText +} diff --git a/src/pages/Evaluations.tsx b/src/pages/Evaluations.tsx index b0b064e..48fde51 100644 --- a/src/pages/Evaluations.tsx +++ b/src/pages/Evaluations.tsx @@ -26,6 +26,7 @@ import { useLocation, useNavigate } from 'react-router-dom' import { onCloseStream } from '../utils/eventsEmitter' import { fr } from '@codegouvfr/react-dsfr' //import ShowError from 'components/Error/ShowError' +import { useStreamText } from 'hooks/useStreamText' const questions = [ { @@ -591,8 +592,15 @@ function AnswerPannel({ }) { const scrollRef = useRef(null) const [response, setResponse] = useState('') + const [isStreaming, setIsStreaming] = useState(false) const [isUserScrolledUp, setIsUserScrolledUp] = useState(false) + const displayedText = useStreamText({ + text: response, + isStreaming, + speed: 0, + }) + const prompt = { chat_type: 'evaluations', themes: [theme], @@ -654,11 +662,13 @@ function AnswerPannel({ stream_chat.onmessage = (e) => { const jsonData = JSON.parse(e.data) setResponse((prev) => prev + jsonData.choices[0].delta.content) + setIsStreaming(true) if (jsonData.choices[0].finish_reason === 'stop') { stream_chat.close() setIsUserScrolledUp(false) setIsStreamFinished(true) + setIsStreaming(false) } } @@ -713,7 +723,7 @@ function AnswerPannel({ className="shadow-inner fr-mt-1v rounded-lg fr-px-2w h-[50vh] overflow-y-scroll" >

- +