feat: implement chat persistence with MongoDB

- Add full chat persistence to database (conversations and messages)
- Implement MongoChatRepository with cursor-based pagination
- Add getConversations/getConversation endpoints in ChatController
- Save user and assistant messages in ChatService.processMessage()
- Track respondedAction (confirm/reject) on proposed event messages
- Load existing messages on chat screen mount
- Add addMessages() bulk action and chatMessageToMessageData() helper to ChatStore
- Add RespondedAction type and UpdateMessageDTO to shared types
This commit is contained in:
2026-01-09 16:21:01 +01:00
parent d86b18173f
commit c897b6d680
11 changed files with 245 additions and 45 deletions

View File

@@ -1,18 +1,25 @@
import { View, Text, TextInput, Pressable, KeyboardAvoidingView, Platform, Keyboard } from "react-native";
import {
View,
Text,
TextInput,
Pressable,
KeyboardAvoidingView,
Platform,
Keyboard,
} from "react-native";
import currentTheme from "../../Themes";
import { useState, useRef, useEffect } from "react";
import React, { useState, useRef, useEffect } from "react";
import Header from "../../components/Header";
import BaseBackground from "../../components/BaseBackground";
import { FlashList } from "@shopify/flash-list";
import { ChatService } from "../../services";
import { useChatStore, MessageData } from "../../stores";
import { useChatStore, chatMessageToMessageData, MessageData } from "../../stores";
import { ProposedEventChange } from "@caldav/shared";
import { ProposedEventCard } from "../../components/ProposedEventCard";
// TODO: better shadows for everything
// (maybe with extra library because of differences between android and ios)
// TODO: max width for messages
// TODO: create new messages
type BubbleSide = "left" | "right";
@@ -30,14 +37,43 @@ type ChatInputProps = {
};
const Chat = () => {
const { messages, addMessage, updateMessage } = useChatStore();
const listRef = useRef<FlashList<MessageData>>(null);
const { messages, addMessage, addMessages, updateMessage } = useChatStore();
const listRef =
useRef<React.ComponentRef<typeof FlashList<MessageData>>>(null);
const [currentConversationId, setCurrentConversationId] = useState<
string | undefined
>();
useEffect(() => {
const keyboardDidShow = Keyboard.addListener("keyboardDidShow", scrollToEnd);
const keyboardDidShow = Keyboard.addListener(
"keyboardDidShow",
scrollToEnd,
);
return () => keyboardDidShow.remove();
}, []);
// Load existing messages from database on mount
useEffect(() => {
const fetchMessages = async () => {
try {
const conversationSummaries = await ChatService.getConversations();
if (conversationSummaries.length > 0) {
const conversationId = conversationSummaries[0].id;
setCurrentConversationId(conversationId);
const serverMessages =
await ChatService.getConversation(conversationId);
const clientMessages = serverMessages.map(chatMessageToMessageData);
addMessages(clientMessages);
scrollToEnd();
}
} catch (error) {
console.error("Failed to load messages:", error);
}
};
fetchMessages();
}, []);
const scrollToEnd = () => {
setTimeout(() => {
listRef.current?.scrollToEnd({ animated: true });
@@ -87,13 +123,22 @@ const Chat = () => {
id: Date.now().toString(),
side: "right",
content: text,
conversationId: currentConversationId,
};
addMessage(userMessage);
scrollToEnd();
try {
// Fetch server response
const response = await ChatService.sendMessage({ content: text });
// Fetch server response (include conversationId for existing conversations)
const response = await ChatService.sendMessage({
content: text,
conversationId: currentConversationId,
});
// Track conversation ID for subsequent messages
if (!currentConversationId) {
setCurrentConversationId(response.conversationId);
}
// Show bot response
const botMessage: MessageData = {