Skip to content

Commit

Permalink
feat: merge chat history with chat notifications (#1127)
Browse files Browse the repository at this point in the history
* feat: add chat_id to upload and crawl payload

* feat(chat): return chat_history_with_notifications

* feat: explicit notification status on create

* feat: handle notifications in frontend

* feat: delete chat notifications on chat delete request
  • Loading branch information
mamadoudicko authored Sep 7, 2023
1 parent 575d988 commit 9464707
Show file tree
Hide file tree
Showing 17 changed files with 213 additions and 35 deletions.
4 changes: 4 additions & 0 deletions backend/models/databases/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ def update_notification_by_id(self, id: UUID):
def remove_notification_by_id(self, id: UUID):
pass

@abstractmethod
def remove_notifications_by_chat_id(self, chat_id: UUID):
pass

@abstractmethod
def get_notifications_by_chat_id(self, chat_id: UUID):
pass
17 changes: 16 additions & 1 deletion backend/models/databases/supabase/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,19 @@ def remove_notification_by_id(
status="deleted", notification_id=notification_id
)

def remove_notifications_by_chat_id(self, chat_id: UUID) -> None:
"""
Remove all notifications for a chat
Args:
chat_id (UUID): The id of the chat
"""
(
self.db.from_("notifications")
.delete()
.filter("chat_id", "eq", chat_id)
.execute()
).data

def get_notifications_by_chat_id(self, chat_id: UUID) -> list[Notification]:
"""
Get all notifications for a chat
Expand All @@ -102,9 +115,11 @@ def get_notifications_by_chat_id(self, chat_id: UUID) -> list[Notification]:
Returns:
list[Notification]: The notifications
"""
return (
notifications = (
self.db.from_("notifications")
.select("*")
.filter("chat_id", "eq", chat_id)
.execute()
).data

return [Notification(**notification) for notification in notifications]
57 changes: 57 additions & 0 deletions backend/repository/chat/get_chat_history_with_notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from enum import Enum
from typing import List, Union
from uuid import UUID

from models.notifications import Notification
from pydantic import BaseModel
from utils.parse_message_time import (
parse_message_time,
)

from repository.chat.get_chat_history import GetChatHistoryOutput, get_chat_history
from repository.notification.get_chat_notifications import (
get_chat_notifications,
)


class ChatItemType(Enum):
MESSAGE = "MESSAGE"
NOTIFICATION = "NOTIFICATION"


class ChatItem(BaseModel):
item_type: ChatItemType
body: Union[GetChatHistoryOutput, Notification]


def merge_chat_history_and_notifications(
chat_history: List[GetChatHistoryOutput], notifications: List[Notification]
) -> List[ChatItem]:
chat_history_and_notifications = chat_history + notifications

chat_history_and_notifications.sort(
key=lambda x: parse_message_time(x.message_time)
if isinstance(x, GetChatHistoryOutput)
else parse_message_time(x.datetime)
)

transformed_data = []
for item in chat_history_and_notifications:
if isinstance(item, GetChatHistoryOutput):
item_type = ChatItemType.MESSAGE
body = item
else:
item_type = ChatItemType.NOTIFICATION
body = item
transformed_item = ChatItem(item_type=item_type, body=body)
transformed_data.append(transformed_item)

return transformed_data


def get_chat_history_with_notifications(
chat_id: UUID,
) -> List[ChatItem]:
chat_history = get_chat_history(str(chat_id))
chat_notifications = get_chat_notifications(chat_id)
return merge_chat_history_and_notifications(chat_history, chat_notifications)
14 changes: 14 additions & 0 deletions backend/repository/notification/get_chat_notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from typing import List
from uuid import UUID

from models.notifications import Notification
from models.settings import get_supabase_db


def get_chat_notifications(chat_id: UUID) -> List[Notification]:
"""
Get notifications by chat_id
"""
supabase_db = get_supabase_db()

return supabase_db.get_notifications_by_chat_id(chat_id)
11 changes: 11 additions & 0 deletions backend/repository/notification/remove_chat_notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from uuid import UUID
from models.settings import get_supabase_db


def remove_chat_notifications(chat_id: UUID) -> None:
"""
Remove all notifications for a chat
"""
supabase_db = get_supabase_db()

supabase_db.remove_notifications_by_chat_id(chat_id)
14 changes: 11 additions & 3 deletions backend/routes/chat_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
from auth import AuthBearer, get_current_user
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.responses import StreamingResponse
from repository.notification.remove_chat_notifications import (
remove_chat_notifications,
)
from llm.openai import OpenAIBrainPicking
from llm.qa_headless import HeadlessQA
from models import (
Expand All @@ -26,10 +29,13 @@
GetChatHistoryOutput,
create_chat,
get_chat_by_id,
get_chat_history,
get_user_chats,
update_chat,
)
from repository.chat.get_chat_history_with_notifications import (
ChatItem,
get_chat_history_with_notifications,
)
from repository.user_identity import get_user_identity

chat_router = APIRouter()
Expand Down Expand Up @@ -114,6 +120,8 @@ async def delete_chat(chat_id: UUID):
Delete a specific chat by chat ID.
"""
supabase_db = get_supabase_db()
remove_chat_notifications(chat_id)

delete_chat_from_db(supabase_db=supabase_db, chat_id=chat_id)
return {"message": f"{chat_id} has been deleted."}

Expand Down Expand Up @@ -333,6 +341,6 @@ async def create_stream_question_handler(
)
async def get_chat_history_handler(
chat_id: UUID,
) -> List[GetChatHistoryOutput]:
) -> List[ChatItem]:
# TODO: RBAC with current_user
return get_chat_history(str(chat_id))
return get_chat_history_with_notifications(chat_id)
26 changes: 16 additions & 10 deletions backend/routes/crawl_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ async def crawl_endpoint(
request: Request,
crawl_website: CrawlWebsite,
brain_id: UUID = Query(..., description="The ID of the brain"),
chat_id: UUID = Query(..., description="The ID of the chat"),
enable_summarization: bool = False,
current_user: UserIdentity = Depends(get_current_user),
):
Expand All @@ -56,11 +57,15 @@ async def crawl_endpoint(
"type": "error",
}
else:
crawl_notification = add_notification(
CreateNotificationProperties(
action="CRAWL",
crawl_notification = None
if chat_id:
crawl_notification = add_notification(
CreateNotificationProperties(
action="CRAWL",
chat_id=chat_id,
status=NotificationsStatusEnum.Pending,
)
)
)
if not crawl_website.checkGithub():
(
file_path,
Expand Down Expand Up @@ -92,10 +97,11 @@ async def crawl_endpoint(
brain_id=brain_id,
user_openai_api_key=request.headers.get("Openai-Api-Key", None),
)
update_notification_by_id(
crawl_notification.id,
NotificationUpdatableProperties(
status=NotificationsStatusEnum.Done, message=str(message)
),
)
if crawl_notification:
update_notification_by_id(
crawl_notification.id,
NotificationUpdatableProperties(
status=NotificationsStatusEnum.Done, message=str(message)
),
)
return message
27 changes: 17 additions & 10 deletions backend/routes/upload_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ async def upload_file(
request: Request,
uploadFile: UploadFile,
brain_id: UUID = Query(..., description="The ID of the brain"),
chat_id: UUID = Query(..., description="The ID of the chat"),
enable_summarization: bool = False,
current_user: UserIdentity = Depends(get_current_user),
):
Expand Down Expand Up @@ -71,11 +72,15 @@ async def upload_file(
"type": "error",
}
else:
upload_notification = add_notification(
CreateNotificationProperties(
action="UPLOAD",
upload_notification = None
if chat_id:
upload_notification = add_notification(
CreateNotificationProperties(
action="UPLOAD",
chat_id=chat_id,
status=NotificationsStatusEnum.Pending,
)
)
)
openai_api_key = request.headers.get("Openai-Api-Key", None)
if openai_api_key is None:
brain_details = get_brain_details(brain_id)
Expand All @@ -91,11 +96,13 @@ async def upload_file(
brain_id=brain_id,
openai_api_key=openai_api_key,
)
update_notification_by_id(
upload_notification.id,
NotificationUpdatableProperties(
status=NotificationsStatusEnum.Done, message=str(message)
),
)

if upload_notification:
update_notification_by_id(
upload_notification.id,
NotificationUpdatableProperties(
status=NotificationsStatusEnum.Done, message=str(message)
),
)

return message
5 changes: 5 additions & 0 deletions backend/utils/parse_message_time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from datetime import datetime


def parse_message_time(message_time_str):
return datetime.strptime(message_time_str, "%Y-%m-%dT%H:%M:%S.%f")
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/* eslint-disable max-lines */
import axios from "axios";
import { UUID } from "crypto";
import { useParams } from "next/navigation";
import { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";

import { useChatApi } from "@/lib/api/chat/useChatApi";
import { useCrawlApi } from "@/lib/api/crawl/useCrawlApi";
import { useUploadApi } from "@/lib/api/upload/useUploadApi";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
Expand All @@ -18,6 +20,10 @@ export const useKnowledgeUploader = () => {
const { uploadFile } = useUploadApi();
const { t } = useTranslation(["upload"]);
const { crawlWebsiteUrl } = useCrawlApi();
const { createChat } = useChatApi();

const params = useParams();
const chatId = params?.chatId as UUID | undefined;

const { currentBrainId } = useBrainContext();
const addContent = (content: FeedItemType) => {
Expand All @@ -28,7 +34,7 @@ export const useKnowledgeUploader = () => {
};

const crawlWebsiteHandler = useCallback(
async (url: string, brainId: UUID) => {
async (url: string, brainId: UUID, chat_id: UUID) => {
// Configure parameters
const config = {
url: url,
Expand All @@ -42,6 +48,7 @@ export const useKnowledgeUploader = () => {
await crawlWebsiteUrl({
brainId,
config,
chat_id,
});
} catch (error: unknown) {
publish({
Expand All @@ -56,13 +63,14 @@ export const useKnowledgeUploader = () => {
);

const uploadFileHandler = useCallback(
async (file: File, brainId: UUID) => {
async (file: File, brainId: UUID, chat_id: UUID) => {
const formData = new FormData();
formData.append("uploadFile", file);
try {
await uploadFile({
brainId: brainId,
brainId,
formData,
chat_id,
});
} catch (e: unknown) {
if (axios.isAxiosError(e) && e.response?.status === 403) {
Expand Down Expand Up @@ -104,12 +112,14 @@ export const useKnowledgeUploader = () => {

return;
}

try {
const currentChatId = chatId ?? (await createChat("New Chat")).chat_id;
const uploadPromises = files.map((file) =>
uploadFileHandler(file, currentBrainId)
uploadFileHandler(file, currentBrainId, currentChatId)
);
const crawlPromises = urls.map((url) =>
crawlWebsiteHandler(url, currentBrainId)
crawlWebsiteHandler(url, currentBrainId, currentChatId)
);

await Promise.all([...uploadPromises, ...crawlPromises]);
Expand Down
4 changes: 3 additions & 1 deletion frontend/app/chat/[chatId]/hooks/useSelectedChatPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { useEffect } from "react";
import { useChatApi } from "@/lib/api/chat/useChatApi";
import { useChatContext } from "@/lib/context";

import { getMessagesFromChatHistory } from "../utils/getMessagesFromChatHistory";

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useSelectedChatPage = () => {
const { setHistory } = useChatContext();
Expand All @@ -23,7 +25,7 @@ export const useSelectedChatPage = () => {
const chatHistory = await getHistory(chatId);

if (chatHistory.length > 0) {
setHistory(chatHistory);
setHistory(getMessagesFromChatHistory(chatHistory));
}
};
void fetchHistory();
Expand Down
16 changes: 16 additions & 0 deletions frontend/app/chat/[chatId]/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ export type ChatHistory = {
brain_name?: string;
};

type HistoryItemType = "MESSAGE" | "NOTIFICATION";

type Notification = {
id: string;
datetime: string;
chat_id?: string | null;
message?: string | null;
action: string;
status: string;
};

export type ChatItem = {
item_type: HistoryItemType;
body: ChatHistory | Notification;
};

export type ChatEntity = {
chat_id: UUID;
user_id: string;
Expand Down
11 changes: 11 additions & 0 deletions frontend/app/chat/[chatId]/utils/getMessagesFromChatHistory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ChatHistory, ChatItem } from "../types";

export const getMessagesFromChatHistory = (
chatHistory: ChatItem[]
): ChatHistory[] => {
const messages = chatHistory
.filter((item) => item.item_type === "MESSAGE")
.map((item) => item.body as ChatHistory);

return messages;
};
Loading

0 comments on commit 9464707

Please sign in to comment.