import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../api/ai_api.dart'; import '../core/storage/local_storage.dart'; import '../models/ai_persona.dart'; /// 可选的人设列表(含虚拟的「默认」项) /// /// 与 [aiConfigsProvider] 完全平行:一个拉模型配置,一个拉人设。 final aiPersonasProvider = FutureProvider.autoDispose>((ref) { return ref.watch(aiApiProvider).personas(); }); /// 当前选中的人设。 /// /// **不设 autoDispose**:理由和 [SelectedAiConfigNotifier] 一样 —— /// 选完回对话页要立刻生效,切页面不该丢。 class SelectedAiPersonaNotifier extends Notifier { @override int? build() { // 存的是「真实 id」,null = 用内置默认。 // 默认项在列表里的 id 也是 null,两边语义天然一致。 return LocalStorage.instance.getAiPersonaId(); } void select(int? id) { state = id; LocalStorage.instance.saveAiPersonaId(id); } } final selectedAiPersonaProvider = NotifierProvider(SelectedAiPersonaNotifier.new); /// 从人设列表里挑出当前选中的那一条。 /// /// 三种情况都要兜住: /// · 还没选过 → 用列表第一项(也就是「默认」) /// · 选过但那条被删了 → 退回第一项,并顺手把本地选择改掉 /// · 列表还没加载出来 → 返回 null,界面显示「--」 /// /// 注意这里和服务端的降级是**两套独立的安全网**:服务端拿到过期 id 也会 /// 退回内置默认,所以就算这里没来得及纠正,对话也不会挂。 final activeAiPersonaProvider = Provider.autoDispose((ref) { final personas = ref.watch(aiPersonasProvider).value; if (personas == null || personas.isEmpty) return null; final selectedId = ref.watch(selectedAiPersonaProvider); if (selectedId == null) return personas.first; for (final item in personas) { if (item.selectionKey == selectedId) return item; } // 选中的那条已经不存在了(被删了)。退回默认。 // 放在微任务里改 state —— build 过程中不能同步改另一个 provider。 Future.microtask(() { ref.read(selectedAiPersonaProvider.notifier).select(null); }); return personas.first; }); /// 内置默认人设文本,供编辑页「恢复默认」按钮使用。 /// /// 从列表的默认项里取(服务端下发),拿不到时返回 null —— /// 这时按钮会提示「还没加载出来」,而不是塞一份 App 里硬编码的文本进去 /// (那样两边迟早不一致)。 final defaultPersonaPromptProvider = Provider.autoDispose((ref) { final personas = ref.watch(aiPersonasProvider).value; if (personas == null) return null; for (final p in personas) { if (p.isDefault && (p.defaultPrompt?.isNotEmpty ?? false)) { return p.defaultPrompt; } } return null; });