feat: support multiple event proposals in single AI response

- Change proposedChange to proposedChanges array in ChatMessage type
- Add unique id and individual respondedAction to each ProposedEventChange
- Implement arrow navigation UI for multiple proposals with "Event X von Y" counter
- Add updateProposalResponse() method for per-proposal confirm/reject tracking
- GPTAdapter now collects multiple tool call results into proposals array
- Add RRULE documentation to system prompt (separate events for different times)
- Fix RRULE parsing to strip RRULE: prefix if present
- Add log summarization for large args (conversationHistory, existingEvents)
- Keep proposedChanges logged in full for debugging AI issues
This commit is contained in:
2026-01-10 23:30:32 +01:00
parent 8efe6c304e
commit e6b9dd9d34
18 changed files with 533 additions and 158 deletions

View File

@@ -205,15 +205,17 @@ src/
- `User`: id, email, userName, passwordHash?, createdAt?, updatedAt?
- `CalendarEvent`: id, userId, title, description?, startTime, endTime, note?, isRecurring?, recurrenceRule?
- `ExpandedEvent`: Extends CalendarEvent with occurrenceStart, occurrenceEnd (for recurring event instances)
- `ChatMessage`: id, conversationId, sender ('user' | 'assistant'), content, proposedChange?, respondedAction?
- `ProposedEventChange`: action ('create' | 'update' | 'delete'), eventId?, event?, updates?
- `ChatMessage`: id, conversationId, sender ('user' | 'assistant'), content, proposedChanges?
- `ProposedEventChange`: id, action ('create' | 'update' | 'delete'), eventId?, event?, updates?, respondedAction?
- Each proposal has unique `id` (e.g., "proposal-0") for individual confirm/reject
- `respondedAction` tracks user response per proposal (not per message)
- `Conversation`: id, userId, createdAt?, updatedAt? (messages loaded separately via lazy loading)
- `CreateUserDTO`: email, userName, password (for registration)
- `LoginDTO`: identifier (email OR userName), password
- `CreateEventDTO`: Used for creating events AND for AI-proposed events
- `GetMessagesOptions`: Cursor-based pagination with `before?: string` and `limit?: number`
- `ConversationSummary`: id, lastMessage?, createdAt? (for conversation list)
- `UpdateMessageDTO`: respondedAction? (for marking messages as confirmed/rejected)
- `UpdateMessageDTO`: proposalId?, respondedAction? (for marking individual proposals as confirmed/rejected)
- `RespondedAction`: 'confirm' | 'reject' (tracks user response to proposed events)
- `Day`: "Monday" | "Tuesday" | ... | "Sunday"
- `Month`: "January" | "February" | ... | "December"
@@ -275,6 +277,14 @@ export class GPTAdapter implements AIProvider { ... }
The decorator uses a Proxy to intercept method calls lazily, preserves sync/async nature, and logs start/completion/failure with duration.
**Log Summarization:**
The `@Logged` decorator automatically summarizes large arguments to keep logs readable:
- `conversationHistory``"[5 messages]"`
- `existingEvents``"[3 events]"`
- `proposedChanges` → logged in full (for debugging AI issues)
- Long strings (>100 chars) → truncated
- Arrays → `"[Array(n)]"`
**Client Logging:**
- `react-native-logs` with namespaced loggers (apiLogger, storeLogger)
- ApiClient logs all requests with method, endpoint, status, duration
@@ -339,10 +349,12 @@ NODE_ENV=development # development = pretty logs, production = JSON
- `utils/recurrenceExpander`: expandRecurringEvents() using rrule library for RRULE parsing
- `ChatController`: getConversations(), getConversation() with cursor-based pagination support
- `ChatService`: getConversations(), getConversation(), processMessage() uses real AI or test responses (via USE_TEST_RESPONSES), confirmEvent()/rejectEvent() update respondedAction and persist response messages
- `MongoChatRepository`: Full CRUD implemented (getConversationsByUser, createConversation, getMessages with cursor pagination, createMessage, updateMessage)
- `ChatRepository` interface: updateMessage() added for respondedAction tracking
- `GPTAdapter`: Full implementation with OpenAI GPT (gpt-5-mini model), function calling for calendar operations
- `MongoChatRepository`: Full CRUD implemented (getConversationsByUser, createConversation, getMessages with cursor pagination, createMessage, updateMessage, updateProposalResponse)
- `ChatRepository` interface: updateMessage() and updateProposalResponse() for per-proposal respondedAction tracking
- `GPTAdapter`: Full implementation with OpenAI GPT (gpt-4o-mini model), function calling for calendar operations, collects multiple proposals per response
- `ai/utils/`: Provider-agnostic shared utilities (systemPrompt, toolDefinitions, toolExecutor, eventFormatter)
- `ai/utils/systemPrompt`: Includes RRULE documentation - AI knows to create separate events when times differ by day
- `utils/recurrenceExpander`: Handles RRULE parsing, strips `RRULE:` prefix if present (AI may include it)
- `logging/`: Structured logging with pino, pino-http middleware, @Logged decorator
- All repositories and GPTAdapter decorated with @Logged for automatic method logging
- CORS configured to allow X-User-Id header
@@ -373,6 +385,9 @@ NODE_ENV=development # development = pretty logs, production = JSON
- Supports events from adjacent months visible in grid
- Uses `useFocusEffect` for automatic reload on tab focus
- Chat screen fully functional with FlashList, message sending, and event confirm/reject
- **Multiple event proposals**: AI can propose multiple events in one response
- Arrow navigation between proposals with "Event X von Y" counter
- Each proposal individually confirmable/rejectable
- Messages persisted to database via ChatService and loaded on mount
- Tracks conversationId for message continuity across sessions
- ChatStore with addMessages() for bulk loading, chatMessageToMessageData() helper