AI助手优化-人设与会话绑定

This commit is contained in:
2026-09-29 10:44:39 +08:00
parent f4ad2f8612
commit 900d48697e
5 changed files with 464 additions and 150 deletions
+34 -31
View File
@@ -11,53 +11,56 @@ final aiPersonasProvider = FutureProvider.autoDispose<List<AiPersonaItem>>((ref)
return ref.watch(aiApiProvider).personas();
});
/// 当前选中的人设。
/// **新对话时预选哪个人设**的偏好。null = 内置默认人设。
///
/// **不设 autoDispose**:理由和 [SelectedAiConfigNotifier] 一样 ——
/// 选完回对话页要立刻生效,切页面不该丢。
class SelectedAiPersonaNotifier extends Notifier<int?> {
/// ⚠️ 注意它**不是**「当前正在用的人设」—— 那是 `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 = 内置默认。
// 默认项在列表里的 id 也是 null,两边语义天然一致。
return LocalStorage.instance.getAiPersonaId();
}
void select(int? id) {
void remember(int? id) {
state = id;
LocalStorage.instance.saveAiPersonaId(id);
}
/// 被记住的那条人设删了 —— 忘掉它,别留着指向不存在的 id
void forget() => remember(null);
}
final selectedAiPersonaProvider =
NotifierProvider<SelectedAiPersonaNotifier, int?>(SelectedAiPersonaNotifier.new);
final preferredPersonaProvider =
NotifierProvider<PreferredPersonaNotifier, int?>(PreferredPersonaNotifier.new);
/// 从人设列表里挑出当前选中的那一条。
/// 按 id 找人设。聊天气泡用它取「当前会话绑的那个人」的头像。
///
/// 三种情况都要兜住:
/// · 还没选过 → 用列表第一项(也就是「默认」)
/// · 选过但那条被删了 → 退回第一项,并顺手把本地选择改掉
/// · 列表还没加载出来 → 返回 null,界面显示「--」
/// 直接读会话的 personaId 去找,而不是用全局偏好 —— 会话锁定了谁,
/// 气泡上就该显示谁,和那段回答实际用的 prompt 是同一套。
///
/// 注意这里和服务端的降级是**两套独立的安全网**:服务端拿到过期 id 也会
/// 退回内置默认,所以就算这里没来得及纠正,对话也不会挂。
final activeAiPersonaProvider = Provider.autoDispose<AiPersonaItem?>((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;
/// 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;
}
// 选中的那条已经不存在了(被删了)。退回默认。
// 放在微任务里改 state —— build 过程中不能同步改另一个 provider。
Future.microtask(() {
ref.read(selectedAiPersonaProvider.notifier).select(null);
});
return personas.first;
return null;
});
/// 内置默认人设文本,供编辑页「恢复默认」按钮使用。