diff --git a/api/handlers/chat.handler.ts b/api/handlers/chat.handler.ts index 5c3b243..f6a47e0 100644 --- a/api/handlers/chat.handler.ts +++ b/api/handlers/chat.handler.ts @@ -7,13 +7,14 @@ import { getValue } from "../utils"; import { CHAT_PARAMS } from "./../../presentation/src/constants"; export class ChatHandler - implements IRequestHandler> + implements + IRequestHandler>> { private readonly apiKey: string = getValue("API_KEY"); async handle({ question, chatHistory, - }: IChatRequestDTO): Promise> { + }: IChatRequestDTO): Promise>> { try { const embeddingService: EmbeddingService = new EmbeddingService( this.apiKey diff --git a/api/services/chat.service.ts b/api/services/chat.service.ts index 0d3ff3d..bf77406 100644 --- a/api/services/chat.service.ts +++ b/api/services/chat.service.ts @@ -39,7 +39,9 @@ export class ChatService extends GenerativeAIService { If it's possible to deduce how MyBid intends to solve the issues, provide that information. If not, respond with "I don't know". Avoid External Sources: Do not search for information outside of the given context to formulate your response. If you cannot find any relevent information in relating to the Question, just answer I am sorry I dont know. - Here is the context: ${conversation.context} + Here is the context: ${conversation.context} + Reminder: Please answer the following questions based solely on the context provided. Do not search for information outside of the given context. + Reminder: If you cannot find any relevent information in relating to the Question, just answer I am sorry I dont know. `}`, }, ], @@ -63,18 +65,18 @@ export class ChatService extends GenerativeAIService { return model.startChat(this.initialConvo); }; - async run(): Promise { + async run(): Promise> { const question = `${this.conversation.questions[0]}`; this.displayChatTokenCount(question); const chat: ChatSession = await this.initChat(); - const result: GenerateContentResult = await chat.sendMessage(question); - const response: EnhancedGenerateContentResponse = result.response; - const answer = response.text(); - const chatHistory = JSON.stringify(await chat.getHistory(), null, 2); + const result = await chat.sendMessageStream(question); + let text = ""; + for await (const chunk of result.stream) { + const chunkText = chunk.text(); + text += chunkText; + } return { - question, - answer, - chatHistory, + answer: text, }; } diff --git a/presentation/src/components/ChatForm.tsx b/presentation/src/components/ChatForm.tsx index 7013884..000e882 100644 --- a/presentation/src/components/ChatForm.tsx +++ b/presentation/src/components/ChatForm.tsx @@ -1,3 +1,4 @@ +import DOMPurify from "dompurify"; import { useState } from "react"; import { Button, @@ -9,22 +10,8 @@ import { Stack, } from "react-bootstrap"; import useAxiosPrivate from "../hooks/useAxiosPrivate"; -import DOMPurify from "dompurify"; -import { formatText } from "../utils"; - -// interface Message { -// text: string; -// metaData?: { -// documentId: number; -// pageNumber: number; -// }; -// } - -// interface Iresponse { -// history: { role: string; parts: { text: string }[] }[]; -// question: string; -// answer: string; -// } +import { formatCodeBlocks, formatText } from "../utils"; +import NavBar from "./NavBar"; interface IHistory { role: string; @@ -36,6 +23,7 @@ export function Thread() { const [error, setError] = useState(""); const [question, setQuestion] = useState(""); const [chatHistory, setChatHistory] = useState([]); + const [loading, setLoading] = useState(false); const formAction = async () => { if (!question) { @@ -43,12 +31,13 @@ export function Thread() { return; } try { + setLoading(true); + setQuestion(""); const response = await axiosPrivate.post("/chat", { question, chatHistory: JSON.stringify(chatHistory), }); const data = response.data; - console.log(JSON.stringify(data.data.chatHistory)); setChatHistory((oldChat) => [ { role: "user", @@ -60,12 +49,13 @@ export function Thread() { }, ...oldChat, ]); - setQuestion(""); + setLoading(false); return data; // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { console.error(error); setError(error.message); + setLoading(false); } }; @@ -82,9 +72,12 @@ export function Thread() { return ( + + + - +
@@ -96,37 +89,73 @@ export function Thread() { value={question} onChange={(e) => setQuestion(e.target.value)} /> -
-
-
+

{error}

- {chatHistory.map((chatItem, index) => ( - - {chatItem.role} - {chatItem.parts.map((part, i) => ( - - - - ))} - - ))} +
+ {loading ? ( + <> +
+
+ + ) : ( + "" + )} +
+
+ {chatHistory.map((chatItem, index) => ( + + + {chatItem.role && chatItem.role === "user" + ? "Question" + : "Answer"} + + {chatItem.parts.map((part, i) => ( + + + + ))} + + ))} +
diff --git a/presentation/src/components/NavBar.tsx b/presentation/src/components/NavBar.tsx new file mode 100644 index 0000000..9f41b4c --- /dev/null +++ b/presentation/src/components/NavBar.tsx @@ -0,0 +1,20 @@ +import Container from 'react-bootstrap/Container'; +import Nav from 'react-bootstrap/Nav'; +import Navbar from 'react-bootstrap/Navbar'; + +function NavBar() { + return ( + + + + + + ); +} + +export default NavBar; \ No newline at end of file diff --git a/presentation/src/index.css b/presentation/src/index.css index e69de29..467e746 100644 --- a/presentation/src/index.css +++ b/presentation/src/index.css @@ -0,0 +1,25 @@ +.loading-skeleton { + background-color: #f0f0f0; + border-radius: 4px; + animation: loading 1s infinite ease-in-out; + } + + @keyframes loading { + 0% { opacity: 0.5; } + 50% { opacity: 1; } + 100% { opacity: 0.5; } + } + + .straight-line { + position: fixed; + top: 0; + left: 50%; + width: 1px; + height: 100vh; + background-color: black; + z-index: 999; + } + + body{ + background-color: #f8f9fa; + } \ No newline at end of file diff --git a/presentation/src/interfaces/generic.interface.ts b/presentation/src/interfaces/generic.interface.ts new file mode 100644 index 0000000..2264a4c --- /dev/null +++ b/presentation/src/interfaces/generic.interface.ts @@ -0,0 +1,13 @@ +export interface Message { + text: string; + metaData?: { + documentId: number; + pageNumber: number; + }; +} + +export interface Iresponse { + history: { role: string; parts: { text: string }[] }[]; + question: string; + answer: string; +} diff --git a/presentation/src/utils.ts b/presentation/src/utils.ts index abf9097..f692001 100644 --- a/presentation/src/utils.ts +++ b/presentation/src/utils.ts @@ -7,11 +7,16 @@ export const formatText = (text: string) => { if (i % 2 === 0) { formattedText += `

${paragraph}

`; } else { - const startIndex = paragraph.indexOf(" "); - const boldText = paragraph.substring(0, startIndex); - const restOfParagraph = paragraph.substring(startIndex).trim(); - formattedText += `

${boldText} ${restOfParagraph}

`; + const boldText = paragraph; + formattedText += `

${boldText} `; } } return formattedText.replace(/\*/g, ""); }; + +export const formatCodeBlocks = (text: string) => { + const regex = /```([^`]+)```/g; + return text.replace(regex, (_, context) => { + return `

${context}
`; + }); +};