From 452fb485f60a9fbb0dc4a589fac1a0d7e5074c5f Mon Sep 17 00:00:00 2001 From: Harish Mohan Raj Date: Thu, 30 Nov 2023 16:20:22 +0530 Subject: [PATCH] WIP: Support multiple teams in conversation --- main.wasp | 5 ++ src/client/chatConversationHelper.tsx | 50 ++++++++------ src/client/components/ConversationList.tsx | 69 +++++++++++++++++-- src/client/components/ConversationWrapper.tsx | 44 ++++++++---- src/client/helpers.tsx | 38 ++-------- src/client/tests/helpers.test.tsx | 57 ++------------- src/server/actions.ts | 43 +++++++----- src/server/queries.ts | 1 + src/server/webSocket.js | 3 +- 9 files changed, 170 insertions(+), 140 deletions(-) diff --git a/main.wasp b/main.wasp index b082436..94c8494 100644 --- a/main.wasp +++ b/main.wasp @@ -233,6 +233,11 @@ action addNewConversationToChat { entities: [Chat, Conversation] } +action updateExistingConversation { + fn: import { updateExistingConversation } from "@server/actions.js", + entities: [Chat, Conversation] +} + action getAgentResponse { fn: import { getAgentResponse } from "@server/actions.js", entities: [Chat, Conversation] diff --git a/src/client/chatConversationHelper.tsx b/src/client/chatConversationHelper.tsx index 21388de..5f74f34 100644 --- a/src/client/chatConversationHelper.tsx +++ b/src/client/chatConversationHelper.tsx @@ -1,37 +1,48 @@ import getAgentResponse from "@wasp/actions/getAgentResponse"; import addNewConversationToChat from "@wasp/actions/addNewConversationToChat"; +import updateExistingConversation from "@wasp/actions/updateExistingConversation"; import { prepareOpenAIRequest } from "./helpers"; export async function addUserMessageToConversation( chat_id: number, - userQuery: string + userQuery: string, + conv_id?: number, + team_name?: string, + team_id?: number ) { + let userMessage = userQuery; + let isAnswerToAgentQuestion = false; + let user_answer_to_team_id = null; + if (team_id) { + const payload = { + chat_id: chat_id, + conv_id: conv_id, + type: null, + team_status: null, + }; + await updateExistingConversation(payload); + userMessage = `

Replying to ${team_name}:



` + userQuery; + isAnswerToAgentQuestion = true; + user_answer_to_team_id = team_id; + } + const payload = { chat_id: chat_id, - message: userQuery, + message: userMessage, role: "user", }; const updatedConversation: any = await addNewConversationToChat(payload); - const [ - message, - conv_id, - previousConversationIdToClearStatus, - isAnswerToAgentQuestion, - userResponseToTeamId, - ]: [ - message: any, - conv_id: number, - previousConversationIdToClearStatus: number, - isAnswerToAgentQuestion: boolean, - userResponseToTeamId: number | null | undefined + + const [messages, latestConversationID]: [ + messages: any, + latestConversationID: number ] = prepareOpenAIRequest(updatedConversation); return [ - message, - conv_id, - previousConversationIdToClearStatus, + messages, + latestConversationID, isAnswerToAgentQuestion, - userResponseToTeamId, + user_answer_to_team_id, ]; } @@ -39,17 +50,16 @@ export async function addAgentMessageToConversation( chat_id: number, message: any, conv_id: number, - previousConversationIdToClearStatus: number, isAnswerToAgentQuestion: boolean, userResponseToTeamId: number | null | undefined ) { const response: any = await getAgentResponse({ message: message, conv_id: conv_id, - previousConversationIdToClearStatus: previousConversationIdToClearStatus, isAnswerToAgentQuestion: isAnswerToAgentQuestion, userResponseToTeamId: userResponseToTeamId, }); + const openAIResponse = { chat_id: Number(chat_id), message: response.content, diff --git a/src/client/components/ConversationList.tsx b/src/client/components/ConversationList.tsx index 06e92ae..57b07d7 100644 --- a/src/client/components/ConversationList.tsx +++ b/src/client/components/ConversationList.tsx @@ -1,19 +1,24 @@ +import React from "react"; +import { useState } from "react"; + import Markdown from "markdown-to-jsx"; import type { Conversation } from "@wasp/entities"; import logo from "../static/captn-logo.png"; -type ConversationsListType = { +type ConversationsListProps = { conversations: Conversation[]; + onInlineFormSubmit: (event: React.FormEvent) => void; }; -export default function ConversationsList( - conversations: ConversationsListType -) { +export default function ConversationsList({ + conversations, + onInlineFormSubmit, +}: ConversationsListProps) { return (
- {conversations.conversations.map((conversation, idx) => { + {conversations.map((conversation, idx) => { const conversationBgColor = conversation.role === "user" ? "captn-light-blue" : "captn-dark-blue"; const conversationTextColor = @@ -51,11 +56,25 @@ export default function ConversationsList( style={{ borderRadius: "50%" }} /> ); + + const handleFormSubmit = ( + event: React.FormEvent, + conv_id: number, + team_name: string, + team_id: number + ) => { + event.preventDefault(); + const target = event.target as HTMLFormElement; + const userQuery = target.userQuery.value; + target.reset(); + onInlineFormSubmit(userQuery, conv_id, team_name, team_id); + }; + const displayInlineForm = conversation.type === "agent_question"; return (
{conversation.message}
+ {displayInlineForm && ( +
+ handleFormSubmit( + event, + conversation.id, + conversation.team_name, + conversation.team_id + ) + } + className="relative block w-full mt-[15px]" + > + +
+ + +
+
+ )}
diff --git a/src/client/components/ConversationWrapper.tsx b/src/client/components/ConversationWrapper.tsx index 617da51..b2237fc 100644 --- a/src/client/components/ConversationWrapper.tsx +++ b/src/client/components/ConversationWrapper.tsx @@ -24,7 +24,6 @@ export default function ConversationWrapper() { { chatId: Number(id), }, - // { enabled: !!id } { enabled: !!id, refetchInterval: 1000 } ); @@ -47,23 +46,32 @@ export default function ConversationWrapper() { [loginMsgQuery, formInputRef] ); - async function addMessagesToConversation(userQuery: string) { + async function addMessagesToConversation( + userQuery: string, + conv_id?: number, + team_name?: string, + team_id?: number + ) { try { const [ - message, - conv_id, - previousConversationIdToClearStatus, + messages, + conversation_id, isAnswerToAgentQuestion, - userResponseToTeamId, - ] = await addUserMessageToConversation(Number(id), userQuery); + user_answer_to_team_id, + ] = await addUserMessageToConversation( + Number(id), + userQuery, + conv_id, + team_name, + team_id + ); setIsLoading(true); await addAgentMessageToConversation( Number(id), - message, - conv_id, - previousConversationIdToClearStatus, + messages, + conversation_id, isAnswerToAgentQuestion, - userResponseToTeamId + user_answer_to_team_id ); setIsLoading(false); } catch (err: any) { @@ -81,6 +89,15 @@ export default function ConversationWrapper() { await addMessagesToConversation(userQuery); }; + const handleInlineFormSubmit = async ( + userQuery: string, + conv_id: number, + team_name: string, + team_id: number + ) => { + await addMessagesToConversation(userQuery, conv_id, team_name, team_id); + }; + 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" }`; @@ -101,7 +118,10 @@ export default function ConversationWrapper() {
{conversations && ( - + )}
{isLoading && } diff --git a/src/client/helpers.tsx b/src/client/helpers.tsx index 31dbf2d..5c4ae36 100644 --- a/src/client/helpers.tsx +++ b/src/client/helpers.tsx @@ -26,50 +26,24 @@ type OutputMessage = { content: string; }; -function getConvIDAndTeamDetails( - input: InputMessage[] -): [number, number, boolean, number | null | undefined] { +function getLatestConversationID(input: InputMessage[]): number { const allMessageIDS: number[] = input.map((message) => message.id); const sortedAllMessageIDS = allMessageIDS.sort((a, b) => b - a); const latestConversationID = sortedAllMessageIDS[0]; - const previousConversationID = sortedAllMessageIDS[1]; - const previousConversation = input.find( - (message) => message.id === previousConversationID - ); - const previousConversationTeamStatus = previousConversation?.team_status; - const isAnswerToAgentQuestion = previousConversationTeamStatus == "pause"; - const userResponseToTeamId = previousConversation?.team_id; - const previousConversationIdToClearStatus = previousConversationID; - return [ - latestConversationID, - previousConversationIdToClearStatus, - isAnswerToAgentQuestion, - userResponseToTeamId, - ]; + return latestConversationID; } export function prepareOpenAIRequest( input: InputMessage[] -): [OutputMessage[], number, number, boolean, number | null | undefined] { - const message: OutputMessage[] = input.map((message) => { +): [OutputMessage[], number] { + const messages: OutputMessage[] = input.map((message) => { return { role: message.role, content: message.message, }; }); - const [ - latestConversationID, - previousConversationIdToClearStatus, - isAnswerToAgentQuestion, - userResponseToTeamId, - ] = getConvIDAndTeamDetails(input); - return [ - message, - latestConversationID, - previousConversationIdToClearStatus, - isAnswerToAgentQuestion, - userResponseToTeamId, - ]; + const latestConversationID = getLatestConversationID(input); + return [messages, latestConversationID]; } // A custom hook that builds on useLocation to parse diff --git a/src/client/tests/helpers.test.tsx b/src/client/tests/helpers.test.tsx index c1b6288..ab7c236 100644 --- a/src/client/tests/helpers.test.tsx +++ b/src/client/tests/helpers.test.tsx @@ -50,26 +50,10 @@ test("prepareOpenAIRequest_1", () => { }, ]; const expected_conv_id = 9; - const expected_is_answer_to_agent_question = false; - const expected_team_id = null; - const expected_previous_conversation_id_to_clear_status = 8; - const [ - actual_message, - actual_conv_id, - actual_previous_conversation_id_to_clear_status, - actual_is_answer_to_agent_question, - actual_team_id, - ] = prepareOpenAIRequest(input); + const [actual_message, actual_conv_id] = prepareOpenAIRequest(input); expect(actual_message).toStrictEqual(expected_message); expect(actual_conv_id).toStrictEqual(expected_conv_id); - expect(actual_is_answer_to_agent_question).toStrictEqual( - expected_is_answer_to_agent_question - ); - expect(actual_team_id).toStrictEqual(expected_team_id); - expect(actual_previous_conversation_id_to_clear_status).toStrictEqual( - expected_previous_conversation_id_to_clear_status - ); }); test("prepareOpenAIRequest_2", () => { @@ -97,26 +81,10 @@ test("prepareOpenAIRequest_2", () => { }, ]; const expected_conv_id = 8; - const expected_is_answer_to_agent_question = false; - const expected_team_id = undefined; - const expected_previous_conversation_id_to_clear_status = undefined; - const [ - actual_message, - actual_conv_id, - actual_previous_conversation_id_to_clear_status, - actual_is_answer_to_agent_question, - actual_team_id, - ] = prepareOpenAIRequest(input); + const [actual_message, actual_conv_id] = prepareOpenAIRequest(input); expect(actual_message).toStrictEqual(expected_message); expect(actual_conv_id).toStrictEqual(expected_conv_id); - expect(actual_is_answer_to_agent_question).toStrictEqual( - expected_is_answer_to_agent_question - ); - expect(actual_team_id).toStrictEqual(expected_team_id); - expect(actual_previous_conversation_id_to_clear_status).toStrictEqual( - expected_previous_conversation_id_to_clear_status - ); }); test("prepareOpenAIRequest_3", () => { @@ -201,24 +169,7 @@ test("prepareOpenAIRequest_3", () => { }, ]; const expected_conv_id = 22; - const expected_is_answer_to_agent_question = true; - const expected_team_id = 123; - const expected_previous_conversation_id_to_clear_status = 10; - - const [ - actual_message, - actual_conv_id, - actual_previous_conversation_id_to_clear_status, - actual_is_answer_to_agent_question, - actual_team_id, - ] = prepareOpenAIRequest(input); + const [actual_message, actual_last_conv_id] = prepareOpenAIRequest(input); expect(actual_message).toStrictEqual(expected_message); - expect(actual_conv_id).toStrictEqual(expected_conv_id); - expect(actual_is_answer_to_agent_question).toStrictEqual( - expected_is_answer_to_agent_question - ); - expect(actual_team_id).toStrictEqual(expected_team_id); - expect(actual_previous_conversation_id_to_clear_status).toStrictEqual( - expected_previous_conversation_id_to_clear_status - ); + expect(actual_last_conv_id).toStrictEqual(expected_conv_id); }); diff --git a/src/server/actions.ts b/src/server/actions.ts index a0ecc70..555fc93 100644 --- a/src/server/actions.ts +++ b/src/server/actions.ts @@ -7,6 +7,7 @@ import type { StripePayment, CreateChat, AddNewConversationToChat, + UpdateExistingConversation, GetAgentResponse, } from "@wasp/actions/types"; import type { StripePaymentResult, OpenAIResponse } from "./types"; @@ -137,25 +138,46 @@ export const addNewConversationToChat: AddNewConversationToChat< }); }; +type UpdateExistingConversationPayload = { + chat_id: number; + conv_id: number; + type: null; + team_status: null; +}; + +export const updateExistingConversation: UpdateExistingConversation< + UpdateExistingConversationPayload, + void +> = async (args, context) => { + if (!context.user) { + throw new HttpError(401); + } + await context.entities.Conversation.update({ + where: { + id: args.conv_id, + }, + data: { + team_status: args.team_status, + type: args.type, + }, + }); +}; + type AgentPayload = { message: any; conv_id: number; - previousConversationIdToClearStatus: number; - isAnswerToAgentQuestion?: boolean; - userResponseToTeamId?: number; + isAnswerToAgentQuestion: boolean; }; export const getAgentResponse: GetAgentResponse = async ( { message, conv_id, - previousConversationIdToClearStatus, isAnswerToAgentQuestion, userResponseToTeamId, }: { message: any; conv_id: number; - previousConversationIdToClearStatus: number; isAnswerToAgentQuestion: boolean; userResponseToTeamId: number | null | undefined; }, @@ -192,17 +214,6 @@ export const getAgentResponse: GetAgentResponse = async ( throw new Error(errorMsg); } - if (payload.is_answer_to_agent_question) { - await context.entities.Conversation.update({ - where: { - id: previousConversationIdToClearStatus, - }, - data: { - team_status: null, - }, - }); - } - return { content: json["content"], team_status: json["team_status"], diff --git a/src/server/queries.ts b/src/server/queries.ts index 9836de0..2db9f0d 100644 --- a/src/server/queries.ts +++ b/src/server/queries.ts @@ -30,5 +30,6 @@ export const getConversations: GetConversations< } return context.entities.Conversation.findMany({ where: { chatId: args.chatId, userId: context.user.id }, + orderBy: { id: "asc" }, }); }; diff --git a/src/server/webSocket.js b/src/server/webSocket.js index fe7a719..2cc692a 100644 --- a/src/server/webSocket.js +++ b/src/server/webSocket.js @@ -55,7 +55,7 @@ export const checkTeamStatusAndUpdateInDB = (io, context) => { team_status: null, }, }); - // create a new entry in conversation table with assistant role and json["msg"], status + await context.entities.Conversation.create({ data: { message: json["msg"], @@ -63,6 +63,7 @@ export const checkTeamStatusAndUpdateInDB = (io, context) => { team_name: json["team_name"], team_id: Number(json["team_id"]), team_status: team_status, + type: team_status === "pause" ? "agent_question" : null, chat: { connect: { id: conversation.chatId } }, user: { connect: { id: socket.data.user.id } }, },