feat: add recurring event deletion with three modes
Implement three deletion modes for recurring events: - single: exclude specific occurrence via EXDATE mechanism - future: set RRULE UNTIL to stop future occurrences - all: delete entire event series Changes include: - Add exceptionDates field to CalendarEvent model - Add RecurringDeleteMode type and DeleteRecurringEventDTO - EventService.deleteRecurring() with mode-based logic using rrule library - EventController DELETE endpoint accepts mode/occurrenceDate query params - recurrenceExpander filters out exception dates during expansion - AI tools support deleteMode and occurrenceDate for proposed deletions - ChatService.confirmEvent() handles recurring delete modes - New DeleteEventModal component for unified delete confirmation UI - Calendar screen integrates modal for both recurring and non-recurring events
This commit is contained in:
@@ -140,7 +140,7 @@ export const TOOL_DEFINITIONS: ToolDefinition[] = [
|
||||
{
|
||||
name: "proposeDeleteEvent",
|
||||
description:
|
||||
"Propose deleting an event. The user must confirm. Use this when the user wants to remove an appointment.",
|
||||
"Propose deleting an event. The user must confirm. Use this when the user wants to remove an appointment. For recurring events, specify deleteMode to control which occurrences to delete.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -148,6 +148,17 @@ export const TOOL_DEFINITIONS: ToolDefinition[] = [
|
||||
type: "string",
|
||||
description: "ID of the event to delete",
|
||||
},
|
||||
deleteMode: {
|
||||
type: "string",
|
||||
enum: ["single", "future", "all"],
|
||||
description:
|
||||
"For recurring events: 'single' = only this occurrence, 'future' = this and all future, 'all' = entire recurring event. Defaults to 'all' for non-recurring events.",
|
||||
},
|
||||
occurrenceDate: {
|
||||
type: "string",
|
||||
description:
|
||||
"ISO date string (YYYY-MM-DD) of the specific occurrence to delete. Required for 'single' and 'future' modes.",
|
||||
},
|
||||
},
|
||||
required: ["eventId"],
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
getDay,
|
||||
Day,
|
||||
DAY_TO_GERMAN,
|
||||
RecurringDeleteMode,
|
||||
} from "@calchat/shared";
|
||||
import { AIContext } from "../../services/interfaces";
|
||||
import { formatDate, formatTime, formatDateTime } from "./eventFormatter";
|
||||
@@ -111,6 +112,8 @@ export function executeToolCall(
|
||||
|
||||
case "proposeDeleteEvent": {
|
||||
const eventId = args.eventId as string;
|
||||
const deleteMode = (args.deleteMode as RecurringDeleteMode) || "all";
|
||||
const occurrenceDate = args.occurrenceDate as string | undefined;
|
||||
const existingEvent = context.existingEvents.find(
|
||||
(e) => e.id === eventId,
|
||||
);
|
||||
@@ -119,8 +122,24 @@ export function executeToolCall(
|
||||
return { content: `Event mit ID ${eventId} nicht gefunden.` };
|
||||
}
|
||||
|
||||
// Build descriptive content based on delete mode
|
||||
let modeDescription = "";
|
||||
if (existingEvent.isRecurring) {
|
||||
switch (deleteMode) {
|
||||
case "single":
|
||||
modeDescription = " (nur dieses Vorkommen)";
|
||||
break;
|
||||
case "future":
|
||||
modeDescription = " (dieses und alle zukünftigen Vorkommen)";
|
||||
break;
|
||||
case "all":
|
||||
modeDescription = " (alle Vorkommen)";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content: `Lösch-Vorschlag für "${existingEvent.title}" erstellt.`,
|
||||
content: `Lösch-Vorschlag für "${existingEvent.title}"${modeDescription} erstellt.`,
|
||||
proposedChange: {
|
||||
action: "delete",
|
||||
eventId,
|
||||
@@ -131,6 +150,10 @@ export function executeToolCall(
|
||||
description: existingEvent.description,
|
||||
isRecurring: existingEvent.isRecurring,
|
||||
},
|
||||
deleteMode: existingEvent.isRecurring ? deleteMode : undefined,
|
||||
occurrenceDate: existingEvent.isRecurring
|
||||
? occurrenceDate
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user