refactor: improve AI event handling and conflict display in chat
- AI fetches events on-demand via callbacks for better efficiency - Add conflict detection with warning display when proposing overlapping events - Improve event search and display in chat interface - Load full chat history for display while limiting AI context
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
CreateEventDTO,
|
||||
GetMessagesOptions,
|
||||
UpdateMessageDTO,
|
||||
ConflictingEvent,
|
||||
} from "@calchat/shared";
|
||||
import { ChatRepository } from "../../services/interfaces";
|
||||
import { Logged } from "../../logging";
|
||||
@@ -25,12 +26,20 @@ export class MongoChatRepository implements ChatRepository {
|
||||
return conversation.toJSON() as unknown as Conversation;
|
||||
}
|
||||
|
||||
async getConversationById(
|
||||
conversationId: string,
|
||||
): Promise<Conversation | null> {
|
||||
const conversation = await ConversationModel.findById(conversationId);
|
||||
return conversation
|
||||
? (conversation.toJSON() as unknown as Conversation)
|
||||
: null;
|
||||
}
|
||||
|
||||
// Messages (cursor-based pagination)
|
||||
async getMessages(
|
||||
conversationId: string,
|
||||
options?: GetMessagesOptions,
|
||||
): Promise<ChatMessage[]> {
|
||||
const limit = options?.limit ?? 20;
|
||||
const query: Record<string, unknown> = { conversationId };
|
||||
|
||||
// Cursor: load messages before this ID (for "load more" scrolling up)
|
||||
@@ -39,9 +48,12 @@ export class MongoChatRepository implements ChatRepository {
|
||||
}
|
||||
|
||||
// Fetch newest first, then reverse for chronological order
|
||||
const docs = await ChatMessageModel.find(query)
|
||||
.sort({ _id: -1 })
|
||||
.limit(limit);
|
||||
// Only apply limit if explicitly specified (no default - load all messages)
|
||||
let queryBuilder = ChatMessageModel.find(query).sort({ _id: -1 });
|
||||
if (options?.limit) {
|
||||
queryBuilder = queryBuilder.limit(options.limit);
|
||||
}
|
||||
const docs = await queryBuilder;
|
||||
|
||||
return docs.reverse().map((doc) => doc.toJSON() as unknown as ChatMessage);
|
||||
}
|
||||
@@ -88,12 +100,28 @@ export class MongoChatRepository implements ChatRepository {
|
||||
messageId: string,
|
||||
proposalId: string,
|
||||
event: CreateEventDTO,
|
||||
conflictingEvents?: ConflictingEvent[],
|
||||
): Promise<ChatMessage | null> {
|
||||
// Always set both fields - use empty array when no conflicts
|
||||
// (MongoDB has issues combining $set and $unset on positional operator)
|
||||
const setFields: Record<string, unknown> = {
|
||||
"proposedChanges.$.event": event,
|
||||
"proposedChanges.$.conflictingEvents":
|
||||
conflictingEvents && conflictingEvents.length > 0
|
||||
? conflictingEvents
|
||||
: [],
|
||||
};
|
||||
|
||||
const doc = await ChatMessageModel.findOneAndUpdate(
|
||||
{ _id: messageId, "proposedChanges.id": proposalId },
|
||||
{ $set: { "proposedChanges.$.event": event } },
|
||||
{ $set: setFields },
|
||||
{ new: true },
|
||||
);
|
||||
return doc ? (doc.toJSON() as unknown as ChatMessage) : null;
|
||||
}
|
||||
|
||||
async getMessageById(messageId: string): Promise<ChatMessage | null> {
|
||||
const doc = await ChatMessageModel.findById(messageId);
|
||||
return doc ? (doc.toJSON() as unknown as ChatMessage) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,14 @@ export class MongoEventRepository implements EventRepository {
|
||||
return events.map((e) => e.toJSON() as unknown as CalendarEvent);
|
||||
}
|
||||
|
||||
async searchByTitle(userId: string, query: string): Promise<CalendarEvent[]> {
|
||||
const events = await EventModel.find({
|
||||
userId,
|
||||
title: { $regex: query, $options: "i" },
|
||||
}).sort({ startTime: 1 });
|
||||
return events.map((e) => e.toJSON() as unknown as CalendarEvent);
|
||||
}
|
||||
|
||||
async create(userId: string, data: CreateEventDTO): Promise<CalendarEvent> {
|
||||
const event = await EventModel.create({ userId, ...data });
|
||||
// NOTE: Casting required because Mongoose's toJSON() type doesn't reflect our virtual 'id' field
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
CreateEventDTO,
|
||||
UpdateEventDTO,
|
||||
ProposedEventChange,
|
||||
ConflictingEvent,
|
||||
} from "@calchat/shared";
|
||||
import { IdVirtual } from "./types";
|
||||
|
||||
@@ -41,6 +42,15 @@ const UpdatesSchema = new Schema<UpdateEventDTO>(
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
const ConflictingEventSchema = new Schema<ConflictingEvent>(
|
||||
{
|
||||
title: { type: String, required: true },
|
||||
startTime: { type: Date, required: true },
|
||||
endTime: { type: Date, required: true },
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
const ProposedChangeSchema = new Schema<ProposedEventChange>(
|
||||
{
|
||||
id: { type: String, required: true },
|
||||
@@ -61,6 +71,7 @@ const ProposedChangeSchema = new Schema<ProposedEventChange>(
|
||||
enum: ["single", "future", "all"],
|
||||
},
|
||||
occurrenceDate: { type: String },
|
||||
conflictingEvents: { type: [ConflictingEventSchema] },
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user