Skip to content

Commit

Permalink
feat: scaffolding and wiring for sendMessageToConversation feature
Browse files Browse the repository at this point in the history
- Client-side usecase wired (with the correct presenter, logger, and client side gateways)
- Signal wired
- Client-side gateways do TRPC calls to the server-side gateways; TRPC routers wired
- Cleanup of function signatures and types
- Implemented page and wired it to the UI kit's chat component
- TODO: OpenAI server-side gateways missing implementation (prepare message context, and send message); new KP client missing, which will imply some small refactoring of the server-side kernel gateway function send message; adapt page and client component to changes
  • Loading branch information
alebg committed Oct 5, 2024
1 parent 794cba3 commit f2580e2
Show file tree
Hide file tree
Showing 21 changed files with 574 additions and 238 deletions.
18 changes: 7 additions & 11 deletions src/app/[rc_id]/conversations/[conv_id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import { notFound, redirect } from "next/navigation";
import { Suspense } from "react";
import { ChatClientPage } from "~/app/_components/chat-page";
import { ChatClientPage, ChatClientPageSkeleton } from "../../../_components/chat-page";
import type { Signal } from "~/lib/core/entity/signals";
import type AuthGatewayOutputPort from "~/lib/core/ports/secondary/auth-gateway-output-port";
import type ConversationGatewayOutputPort from "~/lib/core/ports/secondary/conversation-gateway-output-port";
import type ResearchContextGatewayOutputPort from "~/lib/core/ports/secondary/research-context-gateway-output-port";
import { type TListConversationsViewModel } from "~/lib/core/view-models/list-conversations-view-model";
import { type TListMessagesForConversationViewModel } from "~/lib/core/view-models/list-messages-for-conversation-view-model";
import signalsContainer from "~/lib/infrastructure/common/signals-container";
import { SIGNAL_FACTORY } from "~/lib/infrastructure/common/signals-ioc-container";
import serverContainer from "~/lib/infrastructure/server/config/ioc/server-container";
import { CONTROLLERS, GATEWAYS } from "~/lib/infrastructure/server/config/ioc/server-ioc-symbols";
import ListConversationsController from "~/lib/infrastructure/server/controller/list-conversations-controller";
import {type TListMessagesForConversationControllerParameters} from "~/lib/infrastructure/server/controller/list-messages-for-conversation-controller";
import type ListMessagesForConversationController from "~/lib/infrastructure/server/controller/list-messages-for-conversation-controller";

export default async function ChatServerPage({ params }: { params: { rc_id: string; conv_id: string } }) {

export default async function ChatServerPage({ params }: { params: { rc_id: string; conv_id: string } }) {

// Auth check
const authGateway = serverContainer.get<AuthGatewayOutputPort>(GATEWAYS.AUTH_GATEWAY);
Expand All @@ -34,8 +32,7 @@ export default async function ChatServerPage({ params }: { params: { rc_id: stri

const listResearchContextsDTO = await researchContextGateway.list()
if (!listResearchContextsDTO.success) {
// TODO: discuss what to do here
redirect("/");
throw new Error(`Server error: Could not list research contexts. Please try again later.`);
}

const researchContextsDTOs = listResearchContextsDTO.data;
Expand All @@ -48,8 +45,7 @@ export default async function ChatServerPage({ params }: { params: { rc_id: stri

const listConversationsDTO = await conversationGateway.listConversations(researchContextID);
if (!listConversationsDTO.success) {
// TODO: discuss what to do here
redirect(`/${researchContextID}/conversations`);
throw new Error(`Server error: Could not list conversations. Please try again later.`);
}

const conversations = listConversationsDTO.data;
Expand All @@ -59,8 +55,7 @@ export default async function ChatServerPage({ params }: { params: { rc_id: stri
}




// Initialize the messages to show on page load
const listMessagesController = serverContainer.get<ListMessagesForConversationController>(CONTROLLERS.LIST_MESSAGES_CONTROLLER);

const signalFactory = signalsContainer.get<(initialValue: TListMessagesForConversationViewModel, update?: (value: TListMessagesForConversationViewModel) => void) => Signal<TListMessagesForConversationViewModel>>(
Expand All @@ -79,8 +74,9 @@ export default async function ChatServerPage({ params }: { params: { rc_id: stri

await listMessagesController.execute(controllerParameters);


return (
<Suspense fallback={<div>SKELETON...</div>}>
<Suspense fallback={<ChatClientPageSkeleton/>}>
<ChatClientPage listMessagesViewModel={response.value} researchContextID={researchContextID} conversationID={conversationID} />
</Suspense>
);
Expand Down
131 changes: 103 additions & 28 deletions src/app/_components/chat-page.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
"use client";
import { list } from "postcss";
//import { ChatPage } from "@maany_shr/rage-ui-kit";
import { ChatPage, type MessageViewModel, type ChatPageViewModel } from "@maany_shr/rage-ui-kit";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import { type Signal } from "~/lib/core/entity/signals";
import { type TListMessagesForConversationViewModel } from "~/lib/core/view-models/list-messages-for-conversation-view-model";
import { type TSendMessageToConversationViewModel } from "~/lib/core/view-models/send-message-to-conversation-view-model";
import clientContainer from "~/lib/infrastructure/client/config/ioc/client-container";
import { CONTROLLERS } from "~/lib/infrastructure/client/config/ioc/client-ioc-symbols";
import { type TBrowserListMessagesForConversationControllerParameters } from "~/lib/infrastructure/client/controller/browser-list-messages-for-conversation-controller";
import type BrowserListMessagesForConversationController from "~/lib/infrastructure/client/controller/browser-list-messages-for-conversation-controller";
import { type TBrowserSendMessageToConversationControllerParameters } from "~/lib/infrastructure/client/controller/browser-send-message-to-conversation-controller";
import type BrowserSendMessageToConversationController from "~/lib/infrastructure/client/controller/browser-send-message-to-conversation-controller";
import signalsContainer from "~/lib/infrastructure/common/signals-container";
import { SIGNAL_FACTORY } from "~/lib/infrastructure/common/signals-ioc-container";

export type MessageViewModel = {
role: "user" | "agent";
content: string;
type: "text" | "image";
timestamp: number;
isLoading?: boolean;
};

export type ChatPageViewModel = {
messages: MessageViewModel[];
onSendMessage: (message: string) => void;
};
export function ChatClientPageSkeleton() {
return <ChatPage messages={[]} onSendMessage={(messsage: string) => {console.log("Loading, please wait...")}} />;
}

export function ChatClientPage(props: { listMessagesViewModel: TListMessagesForConversationViewModel; researchContextID: number; conversationID: number }) {
const [listMessagesViewModel, setListMessagesViewModel] = useState<TListMessagesForConversationViewModel>(props.listMessagesViewModel);
Expand All @@ -36,26 +36,101 @@ export function ChatClientPage(props: { listMessagesViewModel: TListMessagesForC
messageContent: messageToSend.content,
});

const queryClient = useQueryClient();

const { isFetching, isLoading, isError } = useQuery<Signal<TListMessagesForConversationViewModel>>({
queryKey: [`list-messages-for-conversation#${props.conversationID}`],
queryFn: async () => {
const signalFactory = signalsContainer.get<(initialValue: TListMessagesForConversationViewModel, update?: (value: TListMessagesForConversationViewModel) => void) => Signal<TListMessagesForConversationViewModel>>(
SIGNAL_FACTORY.KERNEL_LIST_MESSAGES_FOR_CONVERSATION,
);
const response: Signal<TListMessagesForConversationViewModel> = signalFactory(
{
status: "request",
conversationID: props.conversationID,
},
setListMessagesViewModel,
);
const controllerParameters: TBrowserListMessagesForConversationControllerParameters = {
response: response,
conversationID: props.conversationID,
};
const controller = clientContainer.get<BrowserListMessagesForConversationController>(CONTROLLERS.LIST_MESSAGES_FOR_CONVERSATION_CONTROLLER);
await controller.execute(controllerParameters);
return response;
},
});

const mutation = useMutation({
mutationKey: ["send-message-to-conversation"],
retry: 3,
retryDelay: 3000,

onSuccess: async (data) => {
await queryClient.invalidateQueries({ queryKey: [`list-messages-for-conversation#${props.conversationID}`] });
},

mutationFn: async (message: string) => {
const signalFactory = signalsContainer.get<(initialValue: TSendMessageToConversationViewModel, update?: (value: TSendMessageToConversationViewModel) => void) => Signal<TSendMessageToConversationViewModel>>(
SIGNAL_FACTORY.SEND_MESSAGE_TO_CONVERSATION,
);

const response: Signal<TSendMessageToConversationViewModel> = signalFactory(
{
status: "request",
researchContextID: props.researchContextID,
conversationID: props.conversationID,
messageContent: message,
} as TSendMessageToConversationViewModel,
setSendMessaageViewModel,
);

const controller = clientContainer.get<BrowserSendMessageToConversationController>(CONTROLLERS.SEND_MESSAGE_TO_CONVERSATION_CONTROLLER);

const controllerParameters: TBrowserSendMessageToConversationControllerParameters = {
response: response,
researchContextID: props.researchContextID,
conversationID: props.conversationID,
messageToSendContent: message,
messageToSendTimestamp: Date.now(),
};

await controller.execute(controllerParameters);
},
});

const handleSendMessage = (message: string) => {
console.log("Sending message: ", message);
mutation.mutate(message);
};

if (listMessagesViewModel.status === "request") {
return (
<div>hi</div>
//<ChatPage
//messages={[]}
//onSendMessage={(messsage: string) => {
//console.log("hi");
//}}
///>
<ChatPage
messages={[]}
onSendMessage={(messsage: string) => {
console.log("Loading, please wait...");
}}
/>
);
} else if (listMessagesViewModel.status === "error") {
throw new Error(listMessagesViewModel.message);
} else if (listMessagesViewModel.status === "success") {
return (
<div>
<p>Error: {listMessagesViewModel.message}</p>
<p>Context: {JSON.stringify(listMessagesViewModel.context)}</p>
</div>
<ChatPage
messages={listMessagesViewModel.messages.map((message) => {
return {
role: message.id % 2 === 0 ? "agent" : "user", // TODO: fix this after KP has been refactored
content: message.content,
type: "text", // TODO: fix this after KP has been refactored
timestamp: Number(message.timestamp),
isLoading: false,
};
})}
onSendMessage={handleSendMessage}
/>
);
} else if (listMessagesViewModel.status === "success") {
console.log("hi");
}

return <div>Dummy</div>;
throw new Error("Invalid state");
}
2 changes: 1 addition & 1 deletion src/app/_components/list-conversations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export function ListConversationsClientPage(props: { viewModel: TListConversatio

const handleGoToConversation = (conversationID: number) => {
console.log("Going to conversation with ID: ", conversationID);
router.push(`${props.researchContextID}/conversations/${conversationID}`);
router.push(`conversations/${conversationID}`);
}

return (
Expand Down
4 changes: 2 additions & 2 deletions src/lib/core/dto/conversation-gateway-dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ export const SendMessageToConversationResponseDTODataSchema = z.discriminatedUni
"type", [
z.object({
type: z.enum(["success"]),
conversationID: z.string(),
conversationID: z.number(),
message: MessageSchema,
response: MessageSchema,
}),
z.object({
type: z.enum(["progress"]),
conversationID: z.string(),
conversationID: z.number(),
message: MessageSchema,
progressMessage: z.string(),
}),
Expand Down
4 changes: 2 additions & 2 deletions src/lib/core/entity/kernel-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ export const ConversationSchema = z.object({
});
export type TConversation = z.infer<typeof ConversationSchema>;


export const MessageSchema = z.object({
id: z.number(),
content: z.string(),
timestamp: z.string(),
timestamp: z.number(),
sender: z.string(),
senderType: z.union([z.literal("user"), z.literal("agent")]),
});

export type TMessage = z.infer<typeof MessageSchema>;
2 changes: 1 addition & 1 deletion src/lib/core/ports/secondary/agent-gateway-output-port.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import { type TBaseDTO } from "~/sdk/core/dto";

export default interface AgentGatewayOutputPort<TPrepareContext extends TBaseDTO<any,any>> {
createAgent(researchContextID: number, researchContextName: string, researchContextDesciption: string, vectorStoreID: string ): Promise<TCreateAgentDTO>;
prepareMessageContext(researchContextID: string, conversationID: string, message: TMessage): Promise<TPrepareContext>;
prepareMessageContext(researchContextID: number, conversationID: number, message: TMessage): Promise<TPrepareContext>;
sendMessage(context: TPrepareContext["data"], message: TMessage): Promise<TSendMessageDTO>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import type { TMessage } from "../../entity/kernel-models";
export default interface ConversationGatewayOutputPort {
createConversation(researchContextID: number, conversationTitle: string): Promise<CreateConversationDTO>;
listConversations(researchContextID: number): Promise<ListConversationsDTO>;
sendMessageToConversation(conversationID: string, message: TMessage): Promise<SendMessageToConversationResponseDTO>;
sendMessageToConversation(conversationID: number, message: TMessage): Promise<SendMessageToConversationResponseDTO>;
listMessagesForConversation(conversationID: number): Promise<ListMessagesForConversationDTO>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const SendMessageToConversationRequestSchema = z.object({
researchContextID: z.number(),
conversationID: z.number(),
messageToSendContent: z.string(),
messageToSendTimestamp: z.string(),
messageToSendTimestamp: z.number(),
});

export type TSendMessageToConversationRequest = z.infer<typeof SendMessageToConversationRequestSchema>;
Expand Down
Loading

0 comments on commit f2580e2

Please sign in to comment.