AI人设可以配置

This commit is contained in:
2026-09-28 17:16:34 +08:00
parent 2dd89bfde0
commit 80222099ff
10 changed files with 1269 additions and 23 deletions
+74 -2
View File
@@ -7,6 +7,7 @@ import '../core/network/api_exception.dart';
import '../core/network/dio_client.dart';
import '../models/ai.dart';
import '../models/ai_config.dart';
import '../models/ai_persona.dart';
import '../models/json_util.dart';
final aiApiProvider = Provider<AiApi>((ref) => AiApi(ref.watch(dioProvider)));
@@ -68,13 +69,24 @@ class AiApi {
/// (`event: xxx` / `data: {...}` / 空行分隔),手动解析比多引一个依赖可控。
///
/// 注意:[sessionId] 传 null 表示开新会话,会话 ID 会由后端在 done 事件里返回。
Stream<AiEvent> chat({int? sessionId, int? configId, required String content}) async* {
Stream<AiEvent> chat({
int? sessionId,
int? configId,
int? personaId,
required String content,
}) async* {
final Response<ResponseBody> res;
try {
res = await _dio.post<ResponseBody>(
'/ai/chat',
// configId 传 null = 用服务端 .env 里那套(列表里虚拟的「默认」项)
data: {'sessionId': sessionId, 'configId': configId, 'content': content},
// personaId 传 null = 用服务端内置的默认人设。两者互相独立
data: {
'sessionId': sessionId,
'configId': configId,
'personaId': personaId,
'content': content,
},
options: Options(
responseType: ResponseType.stream,
// LLM 生成慢 + 工具循环可能跑好几轮,给足;后端 SseEmitter 是 180s
@@ -200,6 +212,66 @@ class AiApi {
}
}
// ---------------------------------------------------------------- AI 人设
/// 人设列表。后端把虚拟的「默认」项放在第一位(id 为 null,带 defaultPrompt)。
Future<List<AiPersonaItem>> personas() async {
try {
final res = await _dio.get('/ai/personas');
final data = res.data;
if (data is! List) return const [];
return asMapList(data).map(AiPersonaItem.fromJson).toList();
} on DioException catch (e) {
final err = e.error;
throw err is ApiException ? err : ApiException(-1, '人设列表加载失败');
}
}
/// 新增一套人设。
Future<AiPersonaItem> createPersona({
required String name,
required String systemPrompt,
}) async {
try {
final res = await _dio.post('/ai/personas', data: {
'name': name,
'systemPrompt': systemPrompt,
});
return AiPersonaItem.fromJson(Map<String, dynamic>.from(res.data as Map));
} on DioException catch (e) {
final err = e.error;
throw err is ApiException ? err : ApiException(-1, '保存失败');
}
}
/// 修改人设。提示词是**整体替换**的 —— 每次都把完整文本传上来,
/// 没有模型配置那种「key 留空 = 不改动」的语义。
Future<AiPersonaItem> updatePersona({
required int id,
required String name,
required String systemPrompt,
}) async {
try {
final res = await _dio.put('/ai/personas/$id', data: {
'name': name,
'systemPrompt': systemPrompt,
});
return AiPersonaItem.fromJson(Map<String, dynamic>.from(res.data as Map));
} on DioException catch (e) {
final err = e.error;
throw err is ApiException ? err : ApiException(-1, '保存失败');
}
}
Future<void> deletePersona(int id) async {
try {
await _dio.delete('/ai/personas/$id');
} on DioException catch (e) {
final err = e.error;
throw err is ApiException ? err : ApiException(-1, '删除失败');
}
}
AiEvent? _parseEvent(String event, String payload) {
final decoded = jsonDecode(payload);
if (decoded is! Map<String, dynamic>) return null;