AI人设可以配置
This commit is contained in:
+74
-2
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user