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:
@@ -9,8 +9,8 @@ import {
|
||||
CreateEventDTO,
|
||||
UpdateEventDTO,
|
||||
EventAction,
|
||||
CreateMessageDTO,
|
||||
RecurringDeleteMode,
|
||||
ConflictingEvent,
|
||||
} from "@calchat/shared";
|
||||
import { ChatRepository, EventRepository, AIProvider } from "./interfaces";
|
||||
import { EventService } from "./EventService";
|
||||
@@ -570,7 +570,6 @@ export class ChatService {
|
||||
responseIndex++;
|
||||
} else {
|
||||
// Production mode: use real AI
|
||||
const events = await this.eventRepo.findByUserId(userId);
|
||||
const history = await this.chatRepo.getMessages(conversationId, {
|
||||
limit: 20,
|
||||
});
|
||||
@@ -578,8 +577,16 @@ export class ChatService {
|
||||
response = await this.aiProvider.processMessage(data.content, {
|
||||
userId,
|
||||
conversationHistory: history,
|
||||
existingEvents: events,
|
||||
currentDate: new Date(),
|
||||
fetchEventsInRange: async (start, end) => {
|
||||
return this.eventService.getByDateRange(userId, start, end);
|
||||
},
|
||||
searchEvents: async (query) => {
|
||||
return this.eventRepo.searchByTitle(userId, query);
|
||||
},
|
||||
fetchEventById: async (eventId) => {
|
||||
return this.eventService.getById(eventId, userId);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -713,6 +720,56 @@ export class ChatService {
|
||||
proposalId: string,
|
||||
event: CreateEventDTO,
|
||||
): Promise<ChatMessage | null> {
|
||||
return this.chatRepo.updateProposalEvent(messageId, proposalId, event);
|
||||
// Get the message to find the conversation
|
||||
const message = await this.chatRepo.getMessageById(messageId);
|
||||
if (!message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the conversation to find the userId
|
||||
const conversation = await this.chatRepo.getConversationById(
|
||||
message.conversationId,
|
||||
);
|
||||
if (!conversation) {
|
||||
return null;
|
||||
}
|
||||
const userId = conversation.userId;
|
||||
|
||||
// Get event times
|
||||
const eventStart = new Date(event.startTime);
|
||||
const eventEnd = new Date(event.endTime);
|
||||
|
||||
// Get day range for conflict checking
|
||||
const dayStart = new Date(eventStart);
|
||||
dayStart.setHours(0, 0, 0, 0);
|
||||
const dayEnd = new Date(dayStart);
|
||||
dayEnd.setDate(dayStart.getDate() + 1);
|
||||
|
||||
// Fetch events for the day
|
||||
const dayEvents = await this.eventService.getByDateRange(
|
||||
userId,
|
||||
dayStart,
|
||||
dayEnd,
|
||||
);
|
||||
|
||||
// Check for time overlaps (use occurrenceStart/End for expanded recurring events)
|
||||
const conflicts: ConflictingEvent[] = dayEvents
|
||||
.filter(
|
||||
(e) =>
|
||||
new Date(e.occurrenceStart) < eventEnd &&
|
||||
new Date(e.occurrenceEnd) > eventStart,
|
||||
)
|
||||
.map((e) => ({
|
||||
title: e.title,
|
||||
startTime: new Date(e.occurrenceStart),
|
||||
endTime: new Date(e.occurrenceEnd),
|
||||
}));
|
||||
|
||||
return this.chatRepo.updateProposalEvent(
|
||||
messageId,
|
||||
proposalId,
|
||||
event,
|
||||
conflicts.length > 0 ? conflicts : undefined,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import {
|
||||
CalendarEvent,
|
||||
ChatMessage,
|
||||
ProposedEventChange,
|
||||
ExpandedEvent,
|
||||
CalendarEvent,
|
||||
} from "@calchat/shared";
|
||||
|
||||
export interface AIContext {
|
||||
userId: string;
|
||||
conversationHistory: ChatMessage[];
|
||||
existingEvents: CalendarEvent[];
|
||||
currentDate: Date;
|
||||
// Callback to load events from a specific date range
|
||||
// Returns ExpandedEvent[] with occurrenceStart/occurrenceEnd for recurring events
|
||||
fetchEventsInRange: (startDate: Date, endDate: Date) => Promise<ExpandedEvent[]>;
|
||||
// Callback to search events by title
|
||||
searchEvents: (query: string) => Promise<CalendarEvent[]>;
|
||||
// Callback to fetch a single event by ID
|
||||
fetchEventById: (eventId: string) => Promise<CalendarEvent | null>;
|
||||
}
|
||||
|
||||
export interface AIResponse {
|
||||
|
||||
@@ -5,11 +5,13 @@ import {
|
||||
CreateEventDTO,
|
||||
GetMessagesOptions,
|
||||
UpdateMessageDTO,
|
||||
ConflictingEvent,
|
||||
} from "@calchat/shared";
|
||||
|
||||
export interface ChatRepository {
|
||||
// Conversations
|
||||
getConversationsByUser(userId: string): Promise<Conversation[]>;
|
||||
getConversationById(conversationId: string): Promise<Conversation | null>;
|
||||
createConversation(userId: string): Promise<Conversation>;
|
||||
|
||||
// Messages (cursor-based pagination)
|
||||
@@ -38,5 +40,8 @@ export interface ChatRepository {
|
||||
messageId: string,
|
||||
proposalId: string,
|
||||
event: CreateEventDTO,
|
||||
conflictingEvents?: ConflictingEvent[],
|
||||
): Promise<ChatMessage | null>;
|
||||
|
||||
getMessageById(messageId: string): Promise<ChatMessage | null>;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface EventRepository {
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
): Promise<CalendarEvent[]>;
|
||||
searchByTitle(userId: string, query: string): Promise<CalendarEvent[]>;
|
||||
create(userId: string, data: CreateEventDTO): Promise<CalendarEvent>;
|
||||
update(id: string, data: UpdateEventDTO): Promise<CalendarEvent | null>;
|
||||
delete(id: string): Promise<boolean>;
|
||||
|
||||
Reference in New Issue
Block a user