diff --git a/src/client/ChatPage.jsx b/src/client/ChatPage.jsx deleted file mode 100644 index d556921..0000000 --- a/src/client/ChatPage.jsx +++ /dev/null @@ -1,213 +0,0 @@ -// import { User } from '@wasp/entities'; -import { useQuery } from '@wasp/queries' -import getChats from '@wasp/queries/getChats' -import getConversations from '@wasp/queries/getConversations' -import logout from '@wasp/auth/logout'; -import { useState, useEffect, useRef } from 'react'; -// import { Chat, Conversation } from '@wasp/entities' -import { Link } from '@wasp/router' -import Markdown from "markdown-to-jsx"; - -import logo from './static/captn-logo.png' -import createChat from '@wasp/actions/createChat' -import updateConversation from '@wasp/actions/updateConversation' -import getAgentResponse from '@wasp/actions/getAgentResponse' -import { useHistory } from 'react-router-dom'; - -const ChatsList = ({ chats }) => { - if (!chats?.length) return

- return ( -
- {chats.map((chat, idx) => ( - -
  • -
    - - - {chat.id} - -
    -
  • - - ))} -
    - ) - } - -const Loader = () => { - return ( -
    -
    -
    - ) -} - -const ConversationsList = ({ conversations }) => { - if (!conversations?.length) return
    No conversations
    - - return ( -
    - {conversations.map((conversation, idx) => { - const conversationBgColor = conversation.role === "user" ? "captn-light-blue" : "captn-dark-blue"; - const conversationTextColor = conversation.role === "user" ? "captn-dark-blue" : "captn-light-cream"; - const conversationLogo = conversation.role === "user" ?
    You
    : captn logo - return ( -
    -
    - -
    - - {conversationLogo} - -
    - {conversation.content} -
    -
    -
    -
    - ); - })} -
    - ) - -} - -// export default function ChatPage({ user }: { user: User }, props: RouteComponentProps<{ id: string }>) { -export default function ChatPage(props) { - const [isLoading, setIsLoading] = useState(false); - const [chatConversations, setChatConversations] = useState([{}]); - const [conversationId, setConversationId] = useState(null); - const chatContainerRef = useRef(null); - // const [chatId, setChatId] = useState(null); - - const { data: chats, isLoading: isLoadingChats } = useQuery(getChats) - const { data: conversations, isLoading: isLoadingConversations } = useQuery(getConversations, - { - chatId: Number(props.match.params.id), - } - ) - // if(conversations) { - // setChatConversations(conversations.conversation) - // } - // setChatId(props.match.params.id) - // console.log(`chatId: ${chatId}`) - - useEffect(() => { - if (chatContainerRef.current) { - chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; - } - }, [conversations]); - - - const history = useHistory(); - - const chatContainerClass = `flex h-full flex-col items-center justify-between pb-24 overflow-y-auto bg-captn-light-blue ${isLoading ? 'opacity-40' : 'opacity-100'}` - - const handleClick = async (event) => { - event.preventDefault() - try { - const newChatConversations = await createChat(); - setChatConversations(newChatConversations.conversation); - setConversationId(newChatConversations.id); - history.push(`/chat/${newChatConversations.chatId}`); - } catch (err) { - window.alert('Error: ' + err.message) - } - // console.log(event.target); - // console.log(event.currentTarget); - }; - - const handleFormSubmit = async (event) => { - event.preventDefault() - try { - const target = event.target - const userQuery = target.userQuery.value - target.reset() - - // 1. add new conversation to table - const payload = { - conversation_id: conversations.id, - conversations: [...conversations.conversation, ...[{ "role": "user", "content": userQuery }]] - } - await updateConversation(payload) - // 2. call backend python server to get agent response - setIsLoading(true) - const response = await getAgentResponse({ - message: userQuery, - conv_id: payload.conversation_id, - }) - setIsLoading(false) - // 3. add agent response as new conversation in the table - const openAIPayload = { - conversation_id: conversations.id, - conversations: [...payload.conversations, ...[{ "role": "assistant", "content": response.content }]] - } - await updateConversation(openAIPayload) - } catch (err) { - window.alert('Error: ' + err.message) - } - - } - - return ( -
    -
    -
    -
    - -
    - -
    -
      - {chats && } -
    -
    -
    -
    -
    -
    -
    -
    -
    - {conversations && } -
    - {isLoading && } - {props.match.params.id ? (
    -
    - -
    -
    - {/* */} -
    - - -
    -
    -
    ) :

    Please initiate a new chat or select existing chats to resume your conversation.

    } -
    -
    -
    -
    - {/*
    - -
    */} -
    - ) -} \ No newline at end of file diff --git a/src/client/ChatPage.tsx b/src/client/ChatPage.tsx new file mode 100644 index 0000000..ce6ed2e --- /dev/null +++ b/src/client/ChatPage.tsx @@ -0,0 +1,39 @@ +import { useQuery } from "@wasp/queries"; +import getChats from "@wasp/queries/getChats"; + +import CreateNewChatBtn from "./components/CreateNewChat"; +import ChatsList from "./components/ChatList"; +import ConversationWrapper from "./components/ConversationWrapper"; + +export default function ChatPage() { + let { data: chats, isLoading: isLoadingChats } = useQuery(getChats); + + return ( +
    +
    +
    + +
    +
      + { + // Todo: remove the below ignore comment + // @ts-ignore + chats && + } +
    +
    +
    +
    +
    + +
    +
    + ); +} diff --git a/src/client/components/ChatList.tsx b/src/client/components/ChatList.tsx new file mode 100644 index 0000000..e1fc974 --- /dev/null +++ b/src/client/components/ChatList.tsx @@ -0,0 +1,36 @@ +import { Link } from "@wasp/router"; +import type { Chat } from "@wasp/entities"; + +export default function ChatsList(chats: Chat[]) { + return ( +
    + { + // Todo: remove the below ignore comment + // @ts-ignore + chats.chats.map((chat, idx) => ( + +
  • +
    + + + + {chat.id} +
    +
  • + + )) + } +
    + ); +} diff --git a/src/client/components/ConversationList.tsx b/src/client/components/ConversationList.tsx new file mode 100644 index 0000000..508b930 --- /dev/null +++ b/src/client/components/ConversationList.tsx @@ -0,0 +1,85 @@ +import Markdown from "markdown-to-jsx"; + +import type { Conversation } from "@wasp/entities"; + +import logo from "../static/captn-logo.png"; + +export default function ConversationsList(conversations: Conversation[]) { + return ( +
    + { + // Todo: remove the below ignore comment + // @ts-ignore + conversations.conversations.map((conversation, idx) => { + const conversationBgColor = + conversation.role === "user" + ? "captn-light-blue" + : "captn-dark-blue"; + const conversationTextColor = + conversation.role === "user" + ? "captn-dark-blue" + : "captn-light-cream"; + const conversationLogo = + conversation.role === "user" ? ( +
    +
    You
    +
    + ) : ( + captn logo + ); + return ( +
    +
    +
    + + {conversationLogo} + +
    + {conversation.content} +
    +
    +
    +
    + ); + }) + } +
    + ); +} diff --git a/src/client/components/ConversationWrapper.tsx b/src/client/components/ConversationWrapper.tsx new file mode 100644 index 0000000..65226d5 --- /dev/null +++ b/src/client/components/ConversationWrapper.tsx @@ -0,0 +1,206 @@ +import React from "react"; +import { useState, useRef, useEffect } from "react"; +import { useParams } from "react-router"; +import { Redirect, useLocation } from "react-router-dom"; + +import { useQuery } from "@wasp/queries"; +import updateConversation from "@wasp/actions/updateConversation"; +import getAgentResponse from "@wasp/actions/getAgentResponse"; +import getConversations from "@wasp/queries/getConversations"; + +// import type { Conversation } from "@wasp/entities"; + +import ConversationsList from "./ConversationList"; +import Loader from "./Loader"; + +// A custom hook that builds on useLocation to parse +// the query string for you. +function getQueryParam(paramName: string) { + const { search } = useLocation(); + return React.useMemo(() => new URLSearchParams(search), [search]).get( + paramName + ); +} + +export async function triggerSubmit( + loginMsgQuery: string, + // Todo: remove the below ignore comment + // @ts-ignore + submitBtnRef, + // Todo: remove the below ignore comment + // @ts-ignore + formInputRef +) { + if (loginMsgQuery) { + setTimeout(() => { + formInputRef.current.value = decodeURIComponent(loginMsgQuery); + submitBtnRef.current.click(); + }, 500); + + // set it in + } +} + +export default function ConversationWrapper() { + // Todo: remove the below ignore comment + // @ts-ignore + const { id } = useParams(); + const [isLoading, setIsLoading] = useState(false); + const loginMsgQuery = getQueryParam("msg"); + const chatContainerRef = useRef(null); + const submitBtnRef = useRef(null); + const formInputRef = useRef(null); + const { + data: conversations, + isLoading: isConversationLoading, + error: isConversationError, + } = useQuery( + getConversations, + { + chatId: Number(id), + }, + { enabled: !!id } + ); + + // componentDidMount + useEffect(() => { + // Todo: remove the below ignore comment + // @ts-ignore + triggerSubmit(loginMsgQuery, submitBtnRef, formInputRef); + }, [loginMsgQuery]); + + useEffect(() => { + if (chatContainerRef.current) { + // Todo: remove the below ignore comment + // @ts-ignore + chatContainerRef.current.scrollTop = + // Todo: remove the below ignore comment + // @ts-ignore + chatContainerRef.current.scrollHeight; + } + }, [conversations]); + + async function callAgent(userQuery: string) { + try { + // 1. add new conversation to table + const payload = { + conversation_id: conversations.id, + conversations: [ + // Todo: remove the below ignore comment + // @ts-ignore + ...conversations.conversation, + ...[{ role: "user", content: userQuery }], + ], + }; + + await updateConversation(payload); + // 2. call backend python server to get agent response + setIsLoading(true); + const response = await getAgentResponse({ + message: userQuery, + conv_id: payload.conversation_id, + }); + // 3. add agent response as new conversation in the table + const openAIPayload = { + conversation_id: conversations.id, + conversations: [ + ...payload.conversations, + // Todo: remove the below ignore comment + // @ts-ignore + ...[{ role: "assistant", content: response.content }], + ], + }; + await updateConversation(openAIPayload); + setIsLoading(false); + } catch (err: any) { + setIsLoading(false); + window.alert("Error: " + err.message); + } + } + + const handleFormSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + const target = event.target; + // Todo: remove the below ignore comment + // @ts-ignore + const userQuery = target.userQuery.value; + // Todo: remove the below ignore comment + // @ts-ignore + target.reset(); + callAgent(userQuery); + }; + + if (isConversationLoading && !!id) return ; + if (isConversationError) { + return ( + <> + + + ); + } + + const chatContainerClass = `flex h-full flex-col items-center justify-between pb-24 overflow-y-auto bg-captn-light-blue ${ + isLoading ? "opacity-40" : "opacity-100" + }`; + + return ( +
    +
    +
    +
    +
    + {conversations && ( + // Todo: remove the below ignore comment + // @ts-ignore + + )} +
    + {isLoading && } + {id ? ( +
    +
    + +
    + + +
    +
    +
    + ) : ( +

    + Please initiate a new chat or select existing chats to resume + your conversation. +

    + )} +
    +
    +
    +
    + ); +} diff --git a/src/client/components/CreateNewChat.tsx b/src/client/components/CreateNewChat.tsx new file mode 100644 index 0000000..eb4eb06 --- /dev/null +++ b/src/client/components/CreateNewChat.tsx @@ -0,0 +1,45 @@ +import { useHistory } from "react-router-dom"; + +import createChat from "@wasp/actions/createChat"; + +export default function CreateNewChatBtn() { + const history = useHistory(); + + const handleClick = async ( + event: React.MouseEvent + ): Promise => { + event.preventDefault(); + try { + const newChatConversations = await createChat(); + history.push(`/chat/${newChatConversations.chatId}`); + } catch (err: any) { + window.alert("Error: " + err.message); + } + }; + return ( +
    + +
    + ); +} diff --git a/src/client/components/Loader.tsx b/src/client/components/Loader.tsx new file mode 100644 index 0000000..f10ad50 --- /dev/null +++ b/src/client/components/Loader.tsx @@ -0,0 +1,7 @@ +export default function Loader() { + return ( +
    +
    +
    + ); +}