AI人设可以配置
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
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();
|
||||
});
|
||||
|
||||
/// 当前选中的人设。
|
||||
///
|
||||
/// **不设 autoDispose**:理由和 [SelectedAiConfigNotifier] 一样 ——
|
||||
/// 选完回对话页要立刻生效,切页面不该丢。
|
||||
class SelectedAiPersonaNotifier extends Notifier<int?> {
|
||||
@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, int?>(SelectedAiPersonaNotifier.new);
|
||||
|
||||
/// 从人设列表里挑出当前选中的那一条。
|
||||
///
|
||||
/// 三种情况都要兜住:
|
||||
/// · 还没选过 → 用列表第一项(也就是「默认」)
|
||||
/// · 选过但那条被删了 → 退回第一项,并顺手把本地选择改掉
|
||||
/// · 列表还没加载出来 → 返回 null,界面显示「--」
|
||||
///
|
||||
/// 注意这里和服务端的降级是**两套独立的安全网**:服务端拿到过期 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;
|
||||
}
|
||||
|
||||
// 选中的那条已经不存在了(被删了)。退回默认。
|
||||
// 放在微任务里改 state —— build 过程中不能同步改另一个 provider。
|
||||
Future.microtask(() {
|
||||
ref.read(selectedAiPersonaProvider.notifier).select(null);
|
||||
});
|
||||
return personas.first;
|
||||
});
|
||||
|
||||
/// 内置默认人设文本,供编辑页「恢复默认」按钮使用。
|
||||
///
|
||||
/// 从列表的默认项里取(服务端下发),拿不到时返回 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;
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import '../api/ai_api.dart';
|
||||
import '../core/network/api_exception.dart';
|
||||
import '../models/ai.dart';
|
||||
import 'ai_config_provider.dart';
|
||||
import 'ai_persona_provider.dart';
|
||||
import 'bill_refresh.dart';
|
||||
import 'note_provider.dart';
|
||||
|
||||
@@ -160,9 +161,14 @@ class AssistantNotifier extends Notifier<AssistantState> {
|
||||
try {
|
||||
// 带上用户选中的配置。null = 用服务端 .env 那套(虚拟的「默认」项)
|
||||
final configId = ref.read(selectedAiConfigProvider);
|
||||
await for (final event in ref
|
||||
.read(aiApiProvider)
|
||||
.chat(sessionId: state.sessionId, configId: configId, content: trimmed)) {
|
||||
// 人设是独立的另一套。null = 用服务端内置的默认人设
|
||||
final personaId = ref.read(selectedAiPersonaProvider);
|
||||
await for (final event in ref.read(aiApiProvider).chat(
|
||||
sessionId: state.sessionId,
|
||||
configId: configId,
|
||||
personaId: personaId,
|
||||
content: trimmed,
|
||||
)) {
|
||||
if (!ref.mounted) return;
|
||||
|
||||
switch (event.type) {
|
||||
|
||||
Reference in New Issue
Block a user