feat(cache): Add lightweight in-memory cache with TTL and LRU eviction
This commit is contained in:
@@ -18,7 +18,6 @@ import '../../../core/utils/user_display_name.dart';
|
||||
import '../../../core/utils/model_icon_utils.dart';
|
||||
import '../../auth/providers/unified_auth_providers.dart';
|
||||
import '../../../core/utils/android_assistant_handler.dart';
|
||||
|
||||
import '../widgets/modern_chat_input.dart';
|
||||
import '../widgets/user_message_bubble.dart';
|
||||
import '../widgets/assistant_message_widget.dart' as assistant;
|
||||
@@ -83,6 +82,42 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
return fileSize <= (maxSizeMB * 1024 * 1024);
|
||||
}
|
||||
|
||||
Future<Model?> _trySelectCachedModel() async {
|
||||
final existing = ref.read(selectedModelProvider);
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
try {
|
||||
final storage = ref.read(optimizedStorageServiceProvider);
|
||||
// Prefer the stored default model ID/name if available
|
||||
final settingsDesired = ref.read(appSettingsProvider).defaultModel;
|
||||
final storedDesired = await SettingsService.getDefaultModel().catchError(
|
||||
(_) => null,
|
||||
);
|
||||
final desiredId = settingsDesired ?? storedDesired;
|
||||
|
||||
final match = await selectCachedModel(storage, desiredId);
|
||||
if (match != null) {
|
||||
ref.read(selectedModelProvider.notifier).set(match);
|
||||
DebugLogger.log(
|
||||
'cache-select',
|
||||
scope: 'chat/model',
|
||||
data: {'name': match.name, 'source': 'cache'},
|
||||
);
|
||||
}
|
||||
return match;
|
||||
} catch (error, stackTrace) {
|
||||
DebugLogger.error(
|
||||
'cache-select-failed',
|
||||
scope: 'chat/model',
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void startNewChat() {
|
||||
// Clear current conversation
|
||||
ref.read(chatMessagesProvider.notifier).clearMessages();
|
||||
@@ -110,6 +145,17 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fast path: try cached models + stored default before waiting on providers
|
||||
final cached = await _trySelectCachedModel();
|
||||
if (cached != null) {
|
||||
// Still continue to reconcile against remote models below
|
||||
DebugLogger.log(
|
||||
'cache-hit',
|
||||
scope: 'chat/model',
|
||||
data: {'name': cached.name},
|
||||
);
|
||||
}
|
||||
|
||||
DebugLogger.log('auto-select-start', scope: 'chat/model');
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,15 +1,59 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import 'package:conduit/core/models/tool.dart';
|
||||
import 'package:conduit/core/providers/storage_providers.dart';
|
||||
import 'package:conduit/core/services/tools_service.dart';
|
||||
|
||||
part 'tools_providers.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<List<Tool>> toolsList(Ref ref) async {
|
||||
final toolsService = ref.watch(toolsServiceProvider);
|
||||
if (toolsService == null) return [];
|
||||
return await toolsService.getTools();
|
||||
class ToolsList extends _$ToolsList {
|
||||
@override
|
||||
Future<List<Tool>> build() async {
|
||||
final storage = ref.watch(optimizedStorageServiceProvider);
|
||||
final toolsService = ref.watch(toolsServiceProvider);
|
||||
final cached = await storage.getLocalTools();
|
||||
|
||||
if (cached.isNotEmpty) {
|
||||
_scheduleWarmRefresh(toolsService);
|
||||
return cached;
|
||||
}
|
||||
|
||||
if (toolsService == null) {
|
||||
return const [];
|
||||
}
|
||||
|
||||
return _fetchAndPersist(toolsService);
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
final toolsService = ref.read(toolsServiceProvider);
|
||||
if (toolsService == null) {
|
||||
return;
|
||||
}
|
||||
final result = await AsyncValue.guard(() => _fetchAndPersist(toolsService));
|
||||
if (!ref.mounted) return;
|
||||
state = result;
|
||||
}
|
||||
|
||||
void _scheduleWarmRefresh(ToolsService? service) {
|
||||
if (service == null) {
|
||||
return;
|
||||
}
|
||||
Future.microtask(() async {
|
||||
await refresh();
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<Tool>> _fetchAndPersist(ToolsService service) async {
|
||||
final tools = await service.getTools();
|
||||
final storage = ref.read(optimizedStorageServiceProvider);
|
||||
await storage.saveLocalTools(tools);
|
||||
return tools;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
|
||||
Reference in New Issue
Block a user