Huyen My commited on
Commit ·
4e6c4ab
1
Parent(s): b977c43
update frontend
Browse files- ChatBot/__pycache__/app.cpython-311.pyc +0 -0
- ChatBot/__pycache__/chatbot.cpython-311.pyc +0 -0
- ChatBot/__pycache__/query_transformation.cpython-311.pyc +0 -0
- ChatBot/__pycache__/retrieval.cpython-311.pyc +0 -0
- ChatBot/app.py +14 -8
- ChatBot/chatbot.py +9 -4
- ChatBot/indexing.py +2 -2
- ChatBot/query_transformation.py +3 -3
- ChatBot/retrieval.py +1 -2
- FrontEnd/src/ChatBot.css +1 -1
- FrontEnd/src/ChatBot.jsx +68 -51
ChatBot/__pycache__/app.cpython-311.pyc
CHANGED
|
Binary files a/ChatBot/__pycache__/app.cpython-311.pyc and b/ChatBot/__pycache__/app.cpython-311.pyc differ
|
|
|
ChatBot/__pycache__/chatbot.cpython-311.pyc
CHANGED
|
Binary files a/ChatBot/__pycache__/chatbot.cpython-311.pyc and b/ChatBot/__pycache__/chatbot.cpython-311.pyc differ
|
|
|
ChatBot/__pycache__/query_transformation.cpython-311.pyc
CHANGED
|
Binary files a/ChatBot/__pycache__/query_transformation.cpython-311.pyc and b/ChatBot/__pycache__/query_transformation.cpython-311.pyc differ
|
|
|
ChatBot/__pycache__/retrieval.cpython-311.pyc
CHANGED
|
Binary files a/ChatBot/__pycache__/retrieval.cpython-311.pyc and b/ChatBot/__pycache__/retrieval.cpython-311.pyc differ
|
|
|
ChatBot/app.py
CHANGED
|
@@ -3,6 +3,9 @@ from pydantic import BaseModel
|
|
| 3 |
from chatbot import ChatBot
|
| 4 |
import warnings
|
| 5 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
|
|
|
|
|
| 6 |
# Tắt các cảnh báo
|
| 7 |
warnings.filterwarnings("ignore")
|
| 8 |
|
|
@@ -32,12 +35,15 @@ async def new_chat():
|
|
| 32 |
return {"message": "ChatBot đã được khởi tạo lại."}
|
| 33 |
|
| 34 |
# Endpoint để xử lý câu hỏi
|
| 35 |
-
@app.post("/
|
| 36 |
-
async def
|
| 37 |
global chatbot
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
from chatbot import ChatBot
|
| 4 |
import warnings
|
| 5 |
from fastapi.middleware.cors import CORSMiddleware
|
| 6 |
+
from fastapi import FastAPI, HTTPException
|
| 7 |
+
from fastapi.responses import StreamingResponse
|
| 8 |
+
import time
|
| 9 |
# Tắt các cảnh báo
|
| 10 |
warnings.filterwarnings("ignore")
|
| 11 |
|
|
|
|
| 35 |
return {"message": "ChatBot đã được khởi tạo lại."}
|
| 36 |
|
| 37 |
# Endpoint để xử lý câu hỏi
|
| 38 |
+
@app.post("/process_query_stream/")
|
| 39 |
+
async def process_query_stream(request: QueryRequest):
|
| 40 |
global chatbot
|
| 41 |
+
try:
|
| 42 |
+
raw_query = request.query
|
| 43 |
+
# Sử dụng generator để trả về từng phần của câu trả lời
|
| 44 |
+
def generate():
|
| 45 |
+
for chunk in chatbot.process_query_stream(raw_query):
|
| 46 |
+
yield chunk
|
| 47 |
+
return StreamingResponse(generate(), media_type="text/plain")
|
| 48 |
+
except Exception as e:
|
| 49 |
+
raise HTTPException(status_code=500, detail=str(e))
|
ChatBot/chatbot.py
CHANGED
|
@@ -11,6 +11,7 @@ from dotenv import load_dotenv
|
|
| 11 |
from langchain.chains import LLMChain
|
| 12 |
from langchain.prompts import PromptTemplate
|
| 13 |
from langchain.memory import ConversationBufferWindowMemory
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
class ChatBot:
|
|
@@ -54,9 +55,9 @@ class ChatBot:
|
|
| 54 |
|
| 55 |
- Với `question_type = 0|3|5|7`: *Trả về ngữ cảnh y như nó được cung cấp, không thêm bớt bất kỳ thông tin nào khác.*
|
| 56 |
|
| 57 |
-
Mỗi câu hỏi đã được làm rõ (converted_query[i]) tương ứng với `context[i]` và `question_type[i]`. Trả lời lần lượt từng câu hỏi theo các quy tắc trên. **KHÔNG ĐƯỢC sử dụng các cụm từ tương tự như "Dựa vào thông tin được cung cấp", "Dựa vào ngữ cảnh", "không được nêu rõ trong ngữ cảnh".**
|
| 58 |
|
| 59 |
-
**Cách trình bày câu trả lời:** Tách các thông tin trả lời một cách rõ ràn, không nhắc lại câu hỏi
|
| 60 |
|
| 61 |
Dữ liệu cung cấp:
|
| 62 |
- Loại câu hỏi: {question_type}
|
|
@@ -75,7 +76,7 @@ class ChatBot:
|
|
| 75 |
# LLMChain kết hợp LLM và Prompt
|
| 76 |
self.chain = LLMChain(llm=self.llm, prompt=self.prompt, memory=self.memory)
|
| 77 |
|
| 78 |
-
def
|
| 79 |
print ('QUERY GỐC: ', raw_query, '\n---------------------------------------------------------')
|
| 80 |
# Bước 1: Biến đổi câu truy vấn dựa trên lịch sử
|
| 81 |
converted_queries = self.query_transform.transform(raw_query, self.memory.load_memory_variables({})["history"])
|
|
@@ -91,4 +92,8 @@ class ChatBot:
|
|
| 91 |
# Bước 4: Tạo câu trả lời bằng LangChain
|
| 92 |
response = self.chain.run(question_type=query_types, context=context, query=raw_query, converted_query=converted_queries)
|
| 93 |
print ('CÂU TRẢ LỜI: ', response, '\n___________________________________________________________')
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
from langchain.chains import LLMChain
|
| 12 |
from langchain.prompts import PromptTemplate
|
| 13 |
from langchain.memory import ConversationBufferWindowMemory
|
| 14 |
+
import time
|
| 15 |
|
| 16 |
|
| 17 |
class ChatBot:
|
|
|
|
| 55 |
|
| 56 |
- Với `question_type = 0|3|5|7`: *Trả về ngữ cảnh y như nó được cung cấp, không thêm bớt bất kỳ thông tin nào khác.*
|
| 57 |
|
| 58 |
+
Mỗi câu hỏi đã được làm rõ (converted_query[i]) tương ứng với `context[i]` và `question_type[i]`. Trả lời lần lượt từng câu hỏi theo các quy tắc trên. **KHÔNG ĐƯỢC sử dụng các cụm từ tương tự như "Dựa vào thông tin được cung cấp", "Dựa vào ngữ cảnh", "không được nêu rõ trong ngữ cảnh", "trong văn bản được cung cấp".**
|
| 59 |
|
| 60 |
+
**Cách trình bày câu trả lời:** Tách các thông tin trả lời một cách rõ ràn, không nhắc lại câu hỏi.
|
| 61 |
|
| 62 |
Dữ liệu cung cấp:
|
| 63 |
- Loại câu hỏi: {question_type}
|
|
|
|
| 76 |
# LLMChain kết hợp LLM và Prompt
|
| 77 |
self.chain = LLMChain(llm=self.llm, prompt=self.prompt, memory=self.memory)
|
| 78 |
|
| 79 |
+
def process_query_stream(self, raw_query):
|
| 80 |
print ('QUERY GỐC: ', raw_query, '\n---------------------------------------------------------')
|
| 81 |
# Bước 1: Biến đổi câu truy vấn dựa trên lịch sử
|
| 82 |
converted_queries = self.query_transform.transform(raw_query, self.memory.load_memory_variables({})["history"])
|
|
|
|
| 92 |
# Bước 4: Tạo câu trả lời bằng LangChain
|
| 93 |
response = self.chain.run(question_type=query_types, context=context, query=raw_query, converted_query=converted_queries)
|
| 94 |
print ('CÂU TRẢ LỜI: ', response, '\n___________________________________________________________')
|
| 95 |
+
|
| 96 |
+
# Trả về từng phần của câu trả lời
|
| 97 |
+
for chunk in response.split(): # Tách câu trả lời thành các từ hoặc cụm từ
|
| 98 |
+
yield chunk + " " # Trả về từng phần với khoảng trắng
|
| 99 |
+
time.sleep(0.05) # Thêm độ trễ để mô phỏng quá trình streaming
|
ChatBot/indexing.py
CHANGED
|
@@ -49,8 +49,8 @@ vectorstore = QdrantVectorStore.from_documents(
|
|
| 49 |
embedding_model,
|
| 50 |
sparse_embedding=sparse_embeddings,
|
| 51 |
retrieval_mode=RetrievalMode.HYBRID,
|
| 52 |
-
url=
|
| 53 |
-
api_key=
|
| 54 |
collection_name="LAWDATA",
|
| 55 |
distance=models.Distance.COSINE
|
| 56 |
)
|
|
|
|
| 49 |
embedding_model,
|
| 50 |
sparse_embedding=sparse_embeddings,
|
| 51 |
retrieval_mode=RetrievalMode.HYBRID,
|
| 52 |
+
url="https://ee4decd4-2b54-4960-92f5-615ba47f3e04.us-east4-0.gcp.cloud.qdrant.io",
|
| 53 |
+
api_key="JJ9wyq9OVLta3RODIM5_WX6v5CqpwMHhk-ednmmdrj44-8f_7m40ow",
|
| 54 |
collection_name="LAWDATA",
|
| 55 |
distance=models.Distance.COSINE
|
| 56 |
)
|
ChatBot/query_transformation.py
CHANGED
|
@@ -13,8 +13,8 @@ class QueryTransform:
|
|
| 13 |
4. NẾU CÂU HỎI chứa **SỐ ĐIỀU/SỐ CHƯƠNG cụ thể** và NẾU trong lịch sử trò chuyện gần nhất có chứa:
|
| 14 |
+ "Vui lòng chọn Điều cụ thể.", thì ý định của câu hỏi là: **Khoản <số Khoản trong lịch sử> Điều <SỐ/SỐ ĐIỀU trong câu hỏi>. Ngược lại ý định là: Điều <SỐ/SỐ ĐIỀU trong câu hỏi> **
|
| 15 |
+ "Vui lòng chọn Chương cụ thể", thì ý định của câu hỏi là: **Mục <số Mục trong lịch sử> Chương <SỐ/SỐ CHƯƠNG trong câu hỏi>. Ngược lại ý định là: Chương <SỐ/SỐ CHƯƠNG trong câu hỏi> **
|
| 16 |
-
5. **NẾU CÂU HỎI KHÔNG RÕ RÀNG HOẶC CHƯA ĐẦY ĐỦ**, hãy sử dụng lịch sử trò chuyện được cung cấp **CHỈ KHI LỊCH SỬ LIÊN QUAN TRỰC TIẾP ĐẾN CÂU HỎI** để làm rõ ý nghĩa và ngữ cảnh truy vấn của người dùng. **KHÔNG ĐƯỢC
|
| 17 |
-
6. Suy ra nội dung chính của câu hỏi (hoặc câu khẳng định) thật đơn giản, rõ ràng và dễ hiểu (dùng các từ ngữ trong luật Hôn nhân và Gia đình nếu có thể), **BỎ QUA ĐẠI TỪ DANH XƯNG NẾU CÓ THỂ**. Ví dụ: Tôi là nữ 16 tuổi thì có lấy chồng được không? -> Nữ 16 tuổi có đủ điều kiện kết hôn không?
|
| 18 |
|
| 19 |
Chỉ cung cấp kết quả **theo định dạng sau** và không thêm bất kỳ văn bản, giải thích hoặc bình luận nào khác:
|
| 20 |
Kết quả: <nội dung chính của câu hỏi 1>|<nội dung chính của câu hỏi 2>...
|
|
@@ -23,7 +23,7 @@ class QueryTransform:
|
|
| 23 |
Câu hỏi gốc: {query}
|
| 24 |
""")
|
| 25 |
|
| 26 |
-
self.model = ChatGoogleGenerativeAI(model=model, temperature=temperature, api_key= "
|
| 27 |
self.parser = StrOutputParser()
|
| 28 |
|
| 29 |
def transform(self, raw_query, history):
|
|
|
|
| 13 |
4. NẾU CÂU HỎI chứa **SỐ ĐIỀU/SỐ CHƯƠNG cụ thể** và NẾU trong lịch sử trò chuyện gần nhất có chứa:
|
| 14 |
+ "Vui lòng chọn Điều cụ thể.", thì ý định của câu hỏi là: **Khoản <số Khoản trong lịch sử> Điều <SỐ/SỐ ĐIỀU trong câu hỏi>. Ngược lại ý định là: Điều <SỐ/SỐ ĐIỀU trong câu hỏi> **
|
| 15 |
+ "Vui lòng chọn Chương cụ thể", thì ý định của câu hỏi là: **Mục <số Mục trong lịch sử> Chương <SỐ/SỐ CHƯƠNG trong câu hỏi>. Ngược lại ý định là: Chương <SỐ/SỐ CHƯƠNG trong câu hỏi> **
|
| 16 |
+
5. **NẾU CÂU HỎI KHÔNG RÕ RÀNG HOẶC CHƯA ĐẦY ĐỦ**, hãy sử dụng lịch sử trò chuyện được cung cấp **CHỈ KHI LỊCH SỬ LIÊN QUAN TRỰC TIẾP ĐẾN CÂU HỎI** để làm rõ ý nghĩa và ngữ cảnh truy vấn của người dùng. **KHÔNG ĐƯỢC THAY ĐỔI NỘI DUNG CHÍNH CỦA CÂU HỎI**.
|
| 17 |
+
6. Suy ra nội dung chính của câu hỏi (hoặc câu khẳng định) thật đơn giản, rõ ràng và dễ hiểu (dùng các từ ngữ trong luật Hôn nhân và Gia đình nếu có thể), **BỎ QUA ĐẠI TỪ DANH XƯNG NẾU CÓ THỂ**. Ví dụ: Tôi là nữ 16 tuổi thì có lấy chồng được không? -> Nữ 16 tuổi có đủ điều kiện kết hôn không?
|
| 18 |
|
| 19 |
Chỉ cung cấp kết quả **theo định dạng sau** và không thêm bất kỳ văn bản, giải thích hoặc bình luận nào khác:
|
| 20 |
Kết quả: <nội dung chính của câu hỏi 1>|<nội dung chính của câu hỏi 2>...
|
|
|
|
| 23 |
Câu hỏi gốc: {query}
|
| 24 |
""")
|
| 25 |
|
| 26 |
+
self.model = ChatGoogleGenerativeAI(model=model, temperature=temperature, api_key= "AIzaSyB7CqaOvl9gRIhD7ZD61MRKsS_vS5v5VUk")
|
| 27 |
self.parser = StrOutputParser()
|
| 28 |
|
| 29 |
def transform(self, raw_query, history):
|
ChatBot/retrieval.py
CHANGED
|
@@ -81,8 +81,7 @@ class Retriever:
|
|
| 81 |
docs = [doc.page_content for doc in results]
|
| 82 |
docs = self.rerank(query, docs)
|
| 83 |
results = [results[i] for i in docs]
|
| 84 |
-
|
| 85 |
-
print (r)
|
| 86 |
res = {}
|
| 87 |
refers = set()
|
| 88 |
for doc in results[:top_n]:
|
|
|
|
| 81 |
docs = [doc.page_content for doc in results]
|
| 82 |
docs = self.rerank(query, docs)
|
| 83 |
results = [results[i] for i in docs]
|
| 84 |
+
|
|
|
|
| 85 |
res = {}
|
| 86 |
refers = set()
|
| 87 |
for doc in results[:top_n]:
|
FrontEnd/src/ChatBot.css
CHANGED
|
@@ -222,4 +222,4 @@
|
|
| 222 |
display: inline-block;
|
| 223 |
max-width: 80%;
|
| 224 |
/* box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); */
|
| 225 |
-
}
|
|
|
|
| 222 |
display: inline-block;
|
| 223 |
max-width: 80%;
|
| 224 |
/* box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); */
|
| 225 |
+
}
|
FrontEnd/src/ChatBot.jsx
CHANGED
|
@@ -8,59 +8,86 @@ const ChatBot = () => {
|
|
| 8 |
const [isLoading, setIsLoading] = useState(false);
|
| 9 |
const chatWindowRef = useRef(null);
|
| 10 |
|
|
|
|
| 11 |
useEffect(() => {
|
| 12 |
if (chatWindowRef.current) {
|
| 13 |
chatWindowRef.current.scrollTop = chatWindowRef.current.scrollHeight;
|
| 14 |
}
|
| 15 |
}, [messages]);
|
| 16 |
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
try {
|
| 19 |
-
const response = await fetch("http://127.0.0.1:8000/
|
| 20 |
method: "POST",
|
| 21 |
headers: {
|
| 22 |
"Content-Type": "application/json",
|
| 23 |
},
|
| 24 |
-
body: JSON.stringify({ query }),
|
| 25 |
});
|
| 26 |
|
| 27 |
if (!response.ok) {
|
| 28 |
throw new Error("Lỗi khi gửi yêu cầu");
|
| 29 |
}
|
| 30 |
|
| 31 |
-
const
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
} catch (error) {
|
| 34 |
console.error("Lỗi:", error);
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
}
|
| 37 |
};
|
| 38 |
|
| 39 |
-
|
| 40 |
-
if (userInput.trim() === "") return;
|
| 41 |
-
|
| 42 |
-
setMessages((prevMessages) => [
|
| 43 |
-
...prevMessages,
|
| 44 |
-
{ sender: "user", text: userInput },
|
| 45 |
-
]);
|
| 46 |
-
|
| 47 |
-
setUserInput("");
|
| 48 |
-
setIsLoading(true);
|
| 49 |
-
|
| 50 |
-
const botResponse = await sendQueryToBackend(userInput);
|
| 51 |
-
|
| 52 |
-
setMessages((prevMessages) => [
|
| 53 |
-
...prevMessages.filter((msg) => msg.text !== "..."),
|
| 54 |
-
{ sender: "bot", text: botResponse },
|
| 55 |
-
]);
|
| 56 |
-
|
| 57 |
-
setIsLoading(false);
|
| 58 |
-
};
|
| 59 |
-
|
| 60 |
const handleNewChat = async () => {
|
| 61 |
-
|
| 62 |
-
// Reset messages về mảng rỗng
|
| 63 |
-
setMessages([]);
|
| 64 |
try {
|
| 65 |
const response = await fetch("http://127.0.0.1:8000/newchat/", {
|
| 66 |
method: "GET",
|
|
@@ -72,25 +99,12 @@ const ChatBot = () => {
|
|
| 72 |
|
| 73 |
const data = await response.json();
|
| 74 |
console.log(data.message); // Chỉ log ra console, không thêm vào tin nhắn
|
| 75 |
-
|
| 76 |
} catch (error) {
|
| 77 |
console.error("Lỗi:", error);
|
| 78 |
-
// Nếu có lỗi, hiển thị thông báo lỗi trong console, không thêm vào tin nhắn
|
| 79 |
}
|
| 80 |
-
// finally {
|
| 81 |
-
// setIsLoading(false);
|
| 82 |
-
// }
|
| 83 |
};
|
| 84 |
|
| 85 |
-
|
| 86 |
-
if (isLoading) {
|
| 87 |
-
setMessages((prevMessages) => [
|
| 88 |
-
...prevMessages,
|
| 89 |
-
{ sender: "bot", text: "..." },
|
| 90 |
-
]);
|
| 91 |
-
}
|
| 92 |
-
}, [isLoading]);
|
| 93 |
-
|
| 94 |
const formatBotResponse = (text) => {
|
| 95 |
return text.split("\n").map((line, index) => (
|
| 96 |
<p key={index}>
|
|
@@ -111,6 +125,7 @@ const ChatBot = () => {
|
|
| 111 |
return (
|
| 112 |
<div className="chatbot-container">
|
| 113 |
<div className="chat-header">
|
|
|
|
| 114 |
<h1>CHATBOT HỎI ĐÁP VỀ LUẬT HÔN NHÂN VÀ GIA ĐÌNH VIỆT NAM</h1>
|
| 115 |
<button className="new-chat-btn" onClick={handleNewChat} title="Bắt đầu cuộc trò chuyện mới">
|
| 116 |
<FaCommentDots size={24} color="white" />
|
|
@@ -132,8 +147,7 @@ const ChatBot = () => {
|
|
| 132 |
{messages.map((message, index) => (
|
| 133 |
<div
|
| 134 |
key={index}
|
| 135 |
-
className={`message-container ${message.sender === "user" ? "user" : "bot"
|
| 136 |
-
}`}
|
| 137 |
>
|
| 138 |
{message.sender === "user" && (
|
| 139 |
<div className="icon-container user-icon">
|
|
@@ -141,8 +155,7 @@ const ChatBot = () => {
|
|
| 141 |
</div>
|
| 142 |
)}
|
| 143 |
<div
|
| 144 |
-
className={`message ${message.sender} ${message.text === "..." ? "typing" : ""
|
| 145 |
-
}`}
|
| 146 |
>
|
| 147 |
{message.sender === "bot" && message.text === "..." ? (
|
| 148 |
<div className="typing-indicator">
|
|
@@ -172,12 +185,16 @@ const ChatBot = () => {
|
|
| 172 |
value={userInput}
|
| 173 |
onChange={(e) => setUserInput(e.target.value)}
|
| 174 |
placeholder="Nhập câu hỏi..."
|
| 175 |
-
onKeyPress={(e) =>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
/>
|
| 177 |
<button
|
| 178 |
onClick={handleSend}
|
| 179 |
-
disabled={userInput.trim() === ""}
|
| 180 |
-
className={`send-button ${userInput.trim() === "" ? "disabled" : ""}`}
|
| 181 |
title="Gửi"
|
| 182 |
>
|
| 183 |
<FaPaperPlane size={20} color="white" />
|
|
|
|
| 8 |
const [isLoading, setIsLoading] = useState(false);
|
| 9 |
const chatWindowRef = useRef(null);
|
| 10 |
|
| 11 |
+
// Tự động cuộn xuống dưới cùng khi có tin nhắn mới
|
| 12 |
useEffect(() => {
|
| 13 |
if (chatWindowRef.current) {
|
| 14 |
chatWindowRef.current.scrollTop = chatWindowRef.current.scrollHeight;
|
| 15 |
}
|
| 16 |
}, [messages]);
|
| 17 |
|
| 18 |
+
// Xử lý gửi tin nhắn
|
| 19 |
+
const handleSend = async () => {
|
| 20 |
+
if (userInput.trim() === "" || isLoading) return; // Ngăn chặn gửi tin nhắn nếu đang tải
|
| 21 |
+
|
| 22 |
+
// Thêm tin nhắn của người dùng và một dấu ba chấm của bot
|
| 23 |
+
setMessages((prevMessages) => [
|
| 24 |
+
...prevMessages,
|
| 25 |
+
{ sender: "user", text: userInput },
|
| 26 |
+
{ sender: "bot", text: "..." }, // Chỉ thêm một dấu ba chấm
|
| 27 |
+
]);
|
| 28 |
+
|
| 29 |
+
setUserInput("");
|
| 30 |
+
setIsLoading(true);
|
| 31 |
+
|
| 32 |
try {
|
| 33 |
+
const response = await fetch("http://127.0.0.1:8000/process_query_stream/", {
|
| 34 |
method: "POST",
|
| 35 |
headers: {
|
| 36 |
"Content-Type": "application/json",
|
| 37 |
},
|
| 38 |
+
body: JSON.stringify({ query: userInput }),
|
| 39 |
});
|
| 40 |
|
| 41 |
if (!response.ok) {
|
| 42 |
throw new Error("Lỗi khi gửi yêu cầu");
|
| 43 |
}
|
| 44 |
|
| 45 |
+
const reader = response.body.getReader();
|
| 46 |
+
const decoder = new TextDecoder();
|
| 47 |
+
let botResponse = "";
|
| 48 |
+
let isFirstChunk = true; // Biến để kiểm tra chunk đầu tiên
|
| 49 |
+
|
| 50 |
+
while (true) {
|
| 51 |
+
const { done, value } = await reader.read();
|
| 52 |
+
if (done) break;
|
| 53 |
+
|
| 54 |
+
// Giải mã và cập nhật câu trả lời từng phần
|
| 55 |
+
const chunk = decoder.decode(value);
|
| 56 |
+
botResponse += chunk;
|
| 57 |
+
|
| 58 |
+
// Nếu là chunk đầu tiên, thay thế dấu ba chấm bằng chunk đầu tiên
|
| 59 |
+
if (isFirstChunk) {
|
| 60 |
+
setMessages((prevMessages) => [
|
| 61 |
+
...prevMessages.filter((msg) => msg.text !== "..."), // Xóa dấu ba chấm
|
| 62 |
+
{ sender: "bot", text: botResponse }, // Thêm chunk đầu tiên
|
| 63 |
+
]);
|
| 64 |
+
isFirstChunk = false; // Đánh dấu đã xử lý chunk đầu tiên
|
| 65 |
+
} else {
|
| 66 |
+
// Cập nhật tin nhắn cuối cùng của bot với chunk tiếp theo
|
| 67 |
+
setMessages((prevMessages) => {
|
| 68 |
+
const newMessages = [...prevMessages];
|
| 69 |
+
const lastMessageIndex = newMessages.length - 1;
|
| 70 |
+
if (newMessages[lastMessageIndex].sender === "bot") {
|
| 71 |
+
newMessages[lastMessageIndex].text = botResponse;
|
| 72 |
+
}
|
| 73 |
+
return newMessages;
|
| 74 |
+
});
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
} catch (error) {
|
| 78 |
console.error("Lỗi:", error);
|
| 79 |
+
setMessages((prevMessages) => [
|
| 80 |
+
...prevMessages.filter((msg) => msg.text !== "..."), // Xóa dấu ba chấm
|
| 81 |
+
{ sender: "bot", text: "Đã xảy ra lỗi khi kết nối với chatbot." }, // Thêm thông báo lỗi
|
| 82 |
+
]);
|
| 83 |
+
} finally {
|
| 84 |
+
setIsLoading(false);
|
| 85 |
}
|
| 86 |
};
|
| 87 |
|
| 88 |
+
// Xử lý bắt đầu cuộc trò chuyện mới
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
const handleNewChat = async () => {
|
| 90 |
+
setMessages([]); // Reset messages về mảng rỗng
|
|
|
|
|
|
|
| 91 |
try {
|
| 92 |
const response = await fetch("http://127.0.0.1:8000/newchat/", {
|
| 93 |
method: "GET",
|
|
|
|
| 99 |
|
| 100 |
const data = await response.json();
|
| 101 |
console.log(data.message); // Chỉ log ra console, không thêm vào tin nhắn
|
|
|
|
| 102 |
} catch (error) {
|
| 103 |
console.error("Lỗi:", error);
|
|
|
|
| 104 |
}
|
|
|
|
|
|
|
|
|
|
| 105 |
};
|
| 106 |
|
| 107 |
+
// Định dạng câu trả lời của bot (in đậm các phần quan trọng)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
const formatBotResponse = (text) => {
|
| 109 |
return text.split("\n").map((line, index) => (
|
| 110 |
<p key={index}>
|
|
|
|
| 125 |
return (
|
| 126 |
<div className="chatbot-container">
|
| 127 |
<div className="chat-header">
|
| 128 |
+
<FaRobot size={30} style={{ marginRight: "10px" }} /> {/* Thêm icon chatbot */}
|
| 129 |
<h1>CHATBOT HỎI ĐÁP VỀ LUẬT HÔN NHÂN VÀ GIA ĐÌNH VIỆT NAM</h1>
|
| 130 |
<button className="new-chat-btn" onClick={handleNewChat} title="Bắt đầu cuộc trò chuyện mới">
|
| 131 |
<FaCommentDots size={24} color="white" />
|
|
|
|
| 147 |
{messages.map((message, index) => (
|
| 148 |
<div
|
| 149 |
key={index}
|
| 150 |
+
className={`message-container ${message.sender === "user" ? "user" : "bot"}`}
|
|
|
|
| 151 |
>
|
| 152 |
{message.sender === "user" && (
|
| 153 |
<div className="icon-container user-icon">
|
|
|
|
| 155 |
</div>
|
| 156 |
)}
|
| 157 |
<div
|
| 158 |
+
className={`message ${message.sender} ${message.text === "..." ? "typing" : ""}`}
|
|
|
|
| 159 |
>
|
| 160 |
{message.sender === "bot" && message.text === "..." ? (
|
| 161 |
<div className="typing-indicator">
|
|
|
|
| 185 |
value={userInput}
|
| 186 |
onChange={(e) => setUserInput(e.target.value)}
|
| 187 |
placeholder="Nhập câu hỏi..."
|
| 188 |
+
onKeyPress={(e) => {
|
| 189 |
+
if (e.key === "Enter" && !isLoading) {
|
| 190 |
+
handleSend();
|
| 191 |
+
}
|
| 192 |
+
}}
|
| 193 |
/>
|
| 194 |
<button
|
| 195 |
onClick={handleSend}
|
| 196 |
+
disabled={userInput.trim() === "" || isLoading} // Vô hiệu hóa nút gửi khi đang tải
|
| 197 |
+
className={`send-button ${userInput.trim() === "" || isLoading ? "disabled" : ""}`}
|
| 198 |
title="Gửi"
|
| 199 |
>
|
| 200 |
<FaPaperPlane size={20} color="white" />
|