Skip to content

Commit 05e29eb

Browse files
authored
Merge pull request #1599 from basedosdados/feat/structured-chatbot
Feat/structured chatbot
2 parents 35e6f67 + ecdbcfd commit 05e29eb

7 files changed

Lines changed: 678 additions & 145 deletions

File tree

next/components/organisms/chatbot/ChatWindow.js

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1-
import React, { useRef, useCallback, useEffect } from 'react';
1+
import React, { useRef, useCallback, useEffect, useMemo } from 'react';
22
import { VStack, Box } from '@chakra-ui/react';
33
import { useVirtualizer } from '@tanstack/react-virtual';
44
import Message from './Message';
55

6-
function ChatWindow({ messages, onFeedback, scrollTrigger }) {
6+
function ChatWindow({ messages, onFeedback, onFollowUpClick, scrollTrigger }) {
77
const scrollContainerRef = useRef(null);
88
const shouldAutoScrollRef = useRef(true);
9+
const isProgrammaticScrollRef = useRef(false);
910
const bottomThreshold = 80;
1011

12+
const lastAssistantMessageId = useMemo(() => {
13+
for (let i = messages.length - 1; i >= 0; i -= 1) {
14+
const msg = messages[i];
15+
if (msg?.role === 'assistant') {
16+
return msg.id;
17+
}
18+
}
19+
return null;
20+
}, [messages]);
21+
1122
const rowVirtualizer = useVirtualizer({
1223
count: messages.length,
1324
getScrollElement: () => scrollContainerRef.current,
@@ -25,11 +36,18 @@ function ChatWindow({ messages, onFeedback, scrollTrigger }) {
2536
if (!el) return;
2637

2738
requestAnimationFrame(() => {
28-
el.scrollTop = el.scrollHeight;
39+
if (!scrollContainerRef.current) return;
40+
isProgrammaticScrollRef.current = true;
41+
scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight;
42+
requestAnimationFrame(() => {
43+
isProgrammaticScrollRef.current = false;
44+
});
2945
});
3046
}, []);
3147

3248
const handleScroll = useCallback(() => {
49+
if (isProgrammaticScrollRef.current) return;
50+
3351
const el = scrollContainerRef.current;
3452
if (!el) return;
3553

@@ -64,6 +82,7 @@ function ChatWindow({ messages, onFeedback, scrollTrigger }) {
6482
align="stretch"
6583
spacing={0}
6684
css={{
85+
overflowAnchor: "none",
6786
"&::-webkit-scrollbar": {
6887
width: "6px",
6988
},
@@ -95,9 +114,14 @@ function ChatWindow({ messages, onFeedback, scrollTrigger }) {
95114
transform={`translateY(${virtualRow.start}px)`}
96115
paddingBottom="16px"
97116
>
98-
<Message
99-
message={messages[virtualRow.index]}
100-
onFeedback={onFeedback}
117+
<Message
118+
message={messages[virtualRow.index]}
119+
onFeedback={onFeedback}
120+
showFollowUpQuestions={
121+
messages[virtualRow.index]?.id === lastAssistantMessageId &&
122+
!messages[virtualRow.index]?.isLoading
123+
}
124+
onFollowUpClick={onFollowUpClick}
101125
/>
102126
</Box>
103127
))}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { useEffect, useState } from "react";
2+
import {
3+
ModalCloseButton,
4+
Stack,
5+
Textarea,
6+
} from "@chakra-ui/react";
7+
import TitleText from "../../atoms/Text/TitleText";
8+
import {
9+
ModalGeneral,
10+
Button,
11+
ExtraInfoTextForm,
12+
} from "../../molecules/uiUserPage";
13+
14+
export default function FeedbackModal({
15+
isOpen,
16+
onClose,
17+
rating,
18+
onSubmit,
19+
isSubmitting,
20+
}) {
21+
const [feedbackText, setFeedbackText] = useState("");
22+
const isPositive = rating === 1;
23+
24+
useEffect(() => {
25+
if (isOpen) {
26+
setFeedbackText("");
27+
}
28+
}, [isOpen, rating]);
29+
30+
const handleSubmit = () => {
31+
onSubmit(feedbackText.trim());
32+
};
33+
34+
return (
35+
<ModalGeneral
36+
isOpen={isOpen}
37+
onClose={onClose}
38+
propsModalContent={{
39+
minWidth: { base: "auto", lg: "520px !important" },
40+
maxWidth: "520px",
41+
}}
42+
>
43+
<Stack spacing={0} marginBottom="16px">
44+
<TitleText marginRight="24px">Feedback</TitleText>
45+
<ModalCloseButton
46+
fontSize="14px"
47+
top="34px"
48+
right="26px"
49+
_hover={{ backgroundColor: "transparent", opacity: 0.7 }}
50+
onClick={onClose}
51+
/>
52+
</Stack>
53+
54+
<Stack spacing="16px" marginBottom="24px">
55+
<ExtraInfoTextForm marginBottom="0">
56+
Dê os detalhes: (opcional)
57+
</ExtraInfoTextForm>
58+
59+
<Textarea
60+
value={feedbackText}
61+
onChange={(e) => setFeedbackText(e.target.value)}
62+
placeholder={
63+
isPositive
64+
? "O que foi satisfatório na resposta?"
65+
: "O que foi insatisfatório na resposta?"
66+
}
67+
minHeight="120px"
68+
resize="vertical"
69+
border="2px solid transparent"
70+
borderRadius="8px"
71+
backgroundColor="#EEEEEE"
72+
color="#464A51"
73+
fontFamily="Roboto"
74+
fontSize="14px"
75+
lineHeight="20px"
76+
padding="12px 16px"
77+
_placeholder={{ color: "#71757A", opacity: 1 }}
78+
_hover={{
79+
border: "2px solid transparent",
80+
backgroundColor: "#DEDFE0",
81+
}}
82+
_focus={{
83+
border: "2px solid #0068C5",
84+
backgroundColor: "#FFF",
85+
boxShadow: "none",
86+
}}
87+
/>
88+
</Stack>
89+
90+
<Stack
91+
flexDirection={{ base: "column-reverse", lg: "row" }}
92+
spacing={0}
93+
gap="16px"
94+
width={{base: "100%", lg: "fit-content"}}
95+
marginLeft={{ base: "0", lg: "auto" }}
96+
>
97+
<Button
98+
isVariant
99+
width={{base: "100%", lg: "fit-content"}}
100+
minWidth="120px"
101+
onClick={onClose}
102+
>
103+
Cancelar
104+
</Button>
105+
106+
<Button
107+
width={{base: "100%", lg: "fit-content"}}
108+
minWidth="120px"
109+
onClick={handleSubmit}
110+
isLoading={isSubmitting}
111+
isDisabled={!isPositive && feedbackText.trim() === ""}
112+
>
113+
Enviar
114+
</Button>
115+
</Stack>
116+
</ModalGeneral>
117+
);
118+
}

0 commit comments

Comments
 (0)