fix: recurring event display and AI query improvements

- Use occurrenceStart instead of startTime in getEventsInRange so
  recurring events show their actual occurrence date to the AI
- Add lazy CalDAV sync in ChatService (syncOnce before first DB access)
- Add CaldavService.sync() with internal config check (silent no-op)
- Show German recurrence description (e.g. "Jede Woche") instead of
  generic "Wiederkehrend" in EventCardBase via formatRecurrenceRule()
- Move RepeatType and REPEAT_TYPE_LABELS from editEvent to shared
- Separate calendar overlay useFocusEffect from event loading
This commit is contained in:
2026-02-09 18:17:39 +01:00
parent 325246826a
commit 0a2aef2098
11 changed files with 105 additions and 29 deletions

View File

@@ -234,7 +234,7 @@ export async function executeToolCall(
const eventsText = events
.map((e) => {
const start = new Date(e.startTime);
const start = new Date(e.occurrenceStart);
const recurrenceInfo = e.recurrenceRule ? " (wiederkehrend)" : "";
return `- ${e.title} (ID: ${e.id}) am ${formatDate(start)} um ${formatTime(start)} Uhr${recurrenceInfo}`;
})

View File

@@ -62,12 +62,13 @@ const aiProvider = new GPTAdapter();
// Initialize services
const authService = new AuthService(userRepo);
const eventService = new EventService(eventRepo);
const caldavService = new CaldavService(caldavRepo, eventService);
const chatService = new ChatService(
chatRepo,
eventService,
aiProvider,
caldavService,
);
const caldavService = new CaldavService(caldavRepo, eventService);
// Initialize controllers
const authController = new AuthController(authService);

View File

@@ -257,4 +257,14 @@ export class CaldavService {
async deleteConfig(userId: string) {
return await this.caldavRepo.deleteByUserId(userId);
}
/**
* Sync with CalDAV server if config exists. Silent no-op if no config.
*/
async sync(userId: string): Promise<void> {
const config = await this.getConfig(userId);
if (!config) return;
await this.pushAll(userId);
await this.pullEvents(userId);
}
}

View File

@@ -14,6 +14,7 @@ import {
} from "@calchat/shared";
import { ChatRepository, AIProvider } from "./interfaces";
import { EventService } from "./EventService";
import { CaldavService } from "./CaldavService";
import { getWeeksOverview, getMonthOverview } from "../utils/eventFormatters";
type TestResponse = {
@@ -543,6 +544,7 @@ export class ChatService {
private chatRepo: ChatRepository,
private eventService: EventService,
private aiProvider: AIProvider,
private caldavService: CaldavService,
) {}
async processMessage(
@@ -573,17 +575,32 @@ export class ChatService {
limit: 20,
});
// Lazy CalDAV sync: only sync once when the AI first accesses event data
let hasSynced = false;
const syncOnce = async () => {
if (hasSynced) return;
hasSynced = true;
try {
await this.caldavService.sync(userId);
} catch {
// CalDAV sync is not critical for AI responses
}
};
response = await this.aiProvider.processMessage(data.content, {
userId,
conversationHistory: history,
currentDate: new Date(),
fetchEventsInRange: async (start, end) => {
await syncOnce();
return this.eventService.getByDateRange(userId, start, end);
},
searchEvents: async (query) => {
await syncOnce();
return this.eventService.searchByTitle(userId, query);
},
fetchEventById: async (eventId) => {
await syncOnce();
return this.eventService.getById(eventId, userId);
},
});