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(); }); /// **新对话时预选哪个人设**的偏好。null = 内置默认人设。 /// /// ⚠️ 注意它**不是**「当前正在用的人设」—— 那是 `AssistantState.personaId`, /// 在新建会话时定下来、之后不再变(见 `ai_provider.dart` 里的说明)。 /// /// 两者的分工: /// · 这个 = 「上次选了谁」,只用来在新对话弹层里打个勾,省得每次重选 /// · 会话的 personaId = 「这次对话实际在用谁」,气泡、prompt 都看它 /// /// 会不一样:用户拿人设 A 聊过一轮,之后新对话选了 B —— 那条旧会话的 /// 历史消息仍然该显示 A 的头像和 A 的记录。 /// /// 名字里刻意没有「selected」这个词:AI 设置页**不提供**「选说话的人」这个动作, /// 选人只发生在新建会话那一步。用「selected」会让人以为设置页能改。 class PreferredPersonaNotifier extends Notifier { @override int? build() { // 存的是「真实 id」,null = 内置默认。 // 默认项在列表里的 id 也是 null,两边语义天然一致。 return LocalStorage.instance.getAiPersonaId(); } void remember(int? id) { state = id; LocalStorage.instance.saveAiPersonaId(id); } /// 被记住的那条人设删了 —— 忘掉它,别留着指向不存在的 id void forget() => remember(null); } final preferredPersonaProvider = NotifierProvider(PreferredPersonaNotifier.new); /// 按 id 找人设。聊天气泡用它取「当前会话绑的那个人」的头像。 /// /// 直接读会话的 personaId 去找,而不是用全局偏好 —— 会话锁定了谁, /// 气泡上就该显示谁,和那段回答实际用的 prompt 是同一套。 /// /// id 为 null(内置默认人设)或找不到(人设被删)时返回 null, /// 调用方据此显示默认图标。 final personaByIdProvider = Provider.autoDispose.family((ref, id) { if (id == null) return null; final list = ref.watch(aiPersonasProvider).value; if (list == null) return null; for (final p in list) { if (p.id == id) return p; } return null; }); /// 内置默认人设文本,供编辑页「恢复默认」按钮使用。 /// /// 从列表的默认项里取(服务端下发),拿不到时返回 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; });