feat: SQLite event storage with pagination and filtering
Implement SQLite-based event persistence as specified in sqlite-event-manager.md.
## Changes
### Backend
- **EventStorage** (`src/classes/event_storage.py`): New SQLite storage layer
- Cursor-based pagination with compound cursor `{month_stamp}_{rowid}`
- Avatar filtering (single and pair queries)
- Major/minor event separation
- Cleanup API with `keep_major` and `before_month_stamp` filters
- **EventManager** (`src/classes/event_manager.py`): Refactored to use SQLite
- Delegates to EventStorage for persistence
- Memory fallback mode for testing
- New `get_events_paginated()` method
- **API** (`src/server/main.py`):
- `GET /api/events` - Paginated event retrieval with filtering
- `DELETE /api/events/cleanup` - User-triggered cleanup
### Frontend
- **EventPanel.vue**: Scroll-to-load pagination, dual-person filter UI
- **world.ts**: Event state management with pagination
- **game.ts**: New API client methods
### Testing
- 81 new tests for EventStorage, EventManager, and API
- Added `pytest-asyncio` and `httpx` to requirements.txt
## Known Issues: Save/Load is Currently Broken
After loading a saved game, the following issues occur:
1. **Wrong database used**: API returns events from the startup database instead
of the loaded save's `_events.db` file
2. **Events from wrong time period**: Shows events from year 115 when loaded
save is at year 114
3. **Pagination broken after load**: `has_more` returns `False` despite hundreds
of events in the saved database
4. **Filter functionality broken**: Character selection filter stops working
after loading a game
Root cause: `load_game.py` does not properly switch the EventManager's database
connection to the loaded save's events database.
This commit is contained in:
@@ -66,6 +66,34 @@ export interface LLMConfigDTO {
|
||||
mode: string;
|
||||
}
|
||||
|
||||
// --- Events Pagination ---
|
||||
|
||||
export interface EventDTO {
|
||||
id: string;
|
||||
text: string;
|
||||
content: string;
|
||||
year: number;
|
||||
month: number;
|
||||
month_stamp: number;
|
||||
related_avatar_ids: string[];
|
||||
is_major: boolean;
|
||||
is_story: boolean;
|
||||
}
|
||||
|
||||
export interface EventsResponseDTO {
|
||||
events: EventDTO[];
|
||||
next_cursor: string | null;
|
||||
has_more: boolean;
|
||||
}
|
||||
|
||||
export interface FetchEventsParams {
|
||||
avatar_id?: string;
|
||||
avatar_id_1?: string;
|
||||
avatar_id_2?: string;
|
||||
cursor?: string;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export const gameApi = {
|
||||
// --- World State ---
|
||||
|
||||
@@ -165,5 +193,25 @@ export const gameApi = {
|
||||
|
||||
saveLLMConfig(config: LLMConfigDTO) {
|
||||
return httpClient.post<{ status: string; message: string }>('/api/config/llm/save', config);
|
||||
},
|
||||
|
||||
// --- Events Pagination ---
|
||||
|
||||
fetchEvents(params: FetchEventsParams = {}) {
|
||||
const query = new URLSearchParams();
|
||||
if (params.avatar_id) query.set('avatar_id', params.avatar_id);
|
||||
if (params.avatar_id_1) query.set('avatar_id_1', params.avatar_id_1);
|
||||
if (params.avatar_id_2) query.set('avatar_id_2', params.avatar_id_2);
|
||||
if (params.cursor) query.set('cursor', params.cursor);
|
||||
if (params.limit) query.set('limit', String(params.limit));
|
||||
const qs = query.toString();
|
||||
return httpClient.get<EventsResponseDTO>(`/api/events${qs ? '?' + qs : ''}`);
|
||||
},
|
||||
|
||||
cleanupEvents(keepMajor = true, beforeMonthStamp?: number) {
|
||||
const query = new URLSearchParams();
|
||||
query.set('keep_major', String(keepMajor));
|
||||
if (beforeMonthStamp !== undefined) query.set('before_month_stamp', String(beforeMonthStamp));
|
||||
return httpClient.delete<{ deleted: number }>(`/api/events/cleanup?${query}`);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user