diff --git a/api.ts b/api.ts
index ee28e72..1b076e3 100644
--- a/api.ts
+++ b/api.ts
@@ -64,4 +64,7 @@ export const API = {
UPDATE_STATUS: (id: number | string) => `${API_URL_ROOT}/submissions/${id}/status`,
DELETE: (id: number | string) => `${API_URL_ROOT}/submissions/${id}`,
},
+ Chatbot:{
+ CHAT: `${API_URL_ROOT}/chatbot/chat`,
+ }
}
diff --git a/src/app/user/layout.tsx b/src/app/user/layout.tsx
index 239c85c..cbca4bb 100644
--- a/src/app/user/layout.tsx
+++ b/src/app/user/layout.tsx
@@ -8,6 +8,7 @@ import { apiGetCurrentUser } from "@/service/auth";
import { setUserData } from "@/store/features/userSlice";
import React, { useEffect } from "react";
import { useDispatch } from "react-redux";
+import ChatbotWidget from "@/components/ui/chat/ChatbotWidget";
export default function AdminLayout({
children,
@@ -51,6 +52,7 @@ export default function AdminLayout({
{/* Page Content */}
{children}
+
);
}
diff --git a/src/app/user/projects/page.tsx b/src/app/user/projects/page.tsx
index ad0704e..619dd50 100644
--- a/src/app/user/projects/page.tsx
+++ b/src/app/user/projects/page.tsx
@@ -342,7 +342,7 @@ export default function ProjectsPage() {
variant="outline"
disabled={isExportingProjectId === String(project.id)}
onClick={() => handleExportHeadSnapshot(project)}
- title="Export head commit snapshot_json"
+ // title="Export head commit snapshot_json"
>
ExportJSON
@@ -484,7 +484,7 @@ export default function ProjectsPage() {
disabled={isSubmitting}
className="bg-gray-900 hover:bg-gray-800 text-white"
onClick={handleCreateProjectWithJson}
- title="Tạo dự án và tạo commit đầu tiên từ JSON snapshot"
+ // title="Tạo dự án và tạo commit đầu tiên từ JSON snapshot"
>
Tạo với JSON
@@ -494,4 +494,4 @@ export default function ProjectsPage() {
);
-}
+}
\ No newline at end of file
diff --git a/src/components/ui/chat/ChatbotWidget.tsx b/src/components/ui/chat/ChatbotWidget.tsx
new file mode 100644
index 0000000..33d860c
--- /dev/null
+++ b/src/components/ui/chat/ChatbotWidget.tsx
@@ -0,0 +1,223 @@
+"use client";
+
+import React, { useState, useRef, useEffect } from "react";
+import { ChatbotPayload } from "@/interface/chatbot";
+import { apiChatbot } from "@/service/chatbotService";
+
+type Message = {
+ id: string;
+ sender: "user" | "bot";
+ text: string;
+};
+
+export default function ChatbotWidget({
+ projectId = "",
+}: {
+ projectId?: string;
+}) {
+ const [isOpen, setIsOpen] = useState(false);
+ const [messages, setMessages] = useState([
+ {
+ id: "init",
+ sender: "bot",
+ text: "Xin chào! Tôi là trợ lý lịch sử thân thiện. Tôi có thể giúp gì cho bạn?",
+ },
+ ]);
+ const [input, setInput] = useState("");
+ const [isLoading, setIsLoading] = useState(false);
+ const messagesEndRef = useRef(null);
+
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
+ };
+
+ useEffect(() => {
+ if (isOpen) {
+ scrollToBottom();
+ }
+ }, [messages, isOpen]);
+
+ const handleSend = async () => {
+ if (!input.trim()) return;
+
+ const userMessage: Message = {
+ id: Date.now().toString(),
+ sender: "user",
+ text: input.trim(),
+ };
+
+ setMessages((prev) => [...prev, userMessage]);
+ setInput("");
+ setIsLoading(true);
+
+ try {
+ const payload: ChatbotPayload = {
+ project_id: projectId,
+ question: userMessage.text,
+ };
+
+ const res = await apiChatbot(payload);
+
+ const botMessage: Message = {
+ id: (Date.now() + 1).toString(),
+ sender: "bot",
+ text: res?.status
+ ? res?.data
+ : "Xin lỗi, tôi không thể trả lời lúc này.",
+ };
+
+ setMessages((prev) => [...prev, botMessage]);
+ } catch (error: any) {
+ const errorMessage: Message = {
+ id: (Date.now() + 1).toString(),
+ sender: "bot",
+ text:
+ error?.response?.data?.message ||
+ "Có lỗi xảy ra khi kết nối. Vui lòng thử lại sau.",
+ };
+ setMessages((prev) => [...prev, errorMessage]);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ handleSend();
+ }
+ };
+
+ return (
+
+ {!isOpen && (
+
+ )}
+
+ {/* Khung Chat */}
+ {isOpen && (
+
+ {/* Header */}
+
+
+
+ Trợ lý lịch sử.
+
+
+
+
+ {/* Nội dung Chat */}
+
+ {messages.map((msg) => (
+
+ {msg.text}
+
+ ))}
+ {isLoading && (
+
+ )}
+
+
+
+ {/* Khu vực Nhập Input */}
+
+
+
setInput(e.target.value)}
+ onKeyDown={handleKeyDown}
+ disabled={isLoading}
+ className="flex-1 bg-gray-100 dark:bg-gray-800 border-transparent focus:border-brand-500 focus:bg-white dark:focus:bg-gray-900 focus:ring-1 focus:ring-brand-500/20 rounded-full px-4 py-2.5 text-sm outline-none transition-all"
+ />
+
+
+
+
+ )}
+
+ );
+}
diff --git a/src/interface/chatbot.ts b/src/interface/chatbot.ts
new file mode 100644
index 0000000..9622e70
--- /dev/null
+++ b/src/interface/chatbot.ts
@@ -0,0 +1,9 @@
+export interface ChatbotPayload {
+ project_id?: string;
+ question: string;
+}
+
+export interface ChatbotResponse {
+ status: boolean;
+ data: string;
+}
\ No newline at end of file
diff --git a/src/service/chatbotService.ts b/src/service/chatbotService.ts
new file mode 100644
index 0000000..885d0f6
--- /dev/null
+++ b/src/service/chatbotService.ts
@@ -0,0 +1,8 @@
+import api from "@/config/config";
+import { API } from "../../api";
+import { ChatbotPayload, ChatbotResponse } from "@/interface/chatbot";
+
+export const apiChatbot = async (payload: ChatbotPayload): Promise => {
+ const response = await api.post(API.Chatbot.CHAT,payload);
+ return await response?.data;
+};
\ No newline at end of file