Files

81 lines
3.2 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<List<AiPersonaItem>>((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<int?> {
@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, int?>(PreferredPersonaNotifier.new);
/// 按 id 找人设。聊天气泡用它取「当前会话绑的那个人」的头像。
///
/// 直接读会话的 personaId 去找,而不是用全局偏好 —— 会话锁定了谁,
/// 气泡上就该显示谁,和那段回答实际用的 prompt 是同一套。
///
/// id 为 null(内置默认人设)或找不到(人设被删)时返回 null,
/// 调用方据此显示默认图标。
final personaByIdProvider =
Provider.autoDispose.family<AiPersonaItem?, int?>((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<String?>((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;
});