AI配置化
This commit is contained in:
+71
-2
@@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../core/network/api_exception.dart';
|
||||
import '../core/network/dio_client.dart';
|
||||
import '../models/ai.dart';
|
||||
import '../models/ai_config.dart';
|
||||
import '../models/json_util.dart';
|
||||
|
||||
final aiApiProvider = Provider<AiApi>((ref) => AiApi(ref.watch(dioProvider)));
|
||||
@@ -67,12 +68,13 @@ class AiApi {
|
||||
/// (`event: xxx` / `data: {...}` / 空行分隔),手动解析比多引一个依赖可控。
|
||||
///
|
||||
/// 注意:[sessionId] 传 null 表示开新会话,会话 ID 会由后端在 done 事件里返回。
|
||||
Stream<AiEvent> chat({int? sessionId, required String content}) async* {
|
||||
Stream<AiEvent> chat({int? sessionId, int? configId, required String content}) async* {
|
||||
final Response<ResponseBody> res;
|
||||
try {
|
||||
res = await _dio.post<ResponseBody>(
|
||||
'/ai/chat',
|
||||
data: {'sessionId': sessionId, 'content': content},
|
||||
// configId 传 null = 用服务端 .env 里那套(列表里虚拟的「默认」项)
|
||||
data: {'sessionId': sessionId, 'configId': configId, 'content': content},
|
||||
options: Options(
|
||||
responseType: ResponseType.stream,
|
||||
// LLM 生成慢 + 工具循环可能跑好几轮,给足;后端 SseEmitter 是 180s
|
||||
@@ -131,6 +133,73 @@ class AiApi {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- AI 配置
|
||||
|
||||
/// 配置列表。后端把虚拟的「默认」项放在第一位(id 为 null)。
|
||||
Future<List<AiConfigItem>> configs() async {
|
||||
try {
|
||||
final res = await _dio.get('/ai/configs');
|
||||
final data = res.data;
|
||||
if (data is! List) return const [];
|
||||
return asMapList(data).map(AiConfigItem.fromJson).toList();
|
||||
} on DioException catch (e) {
|
||||
final err = e.error;
|
||||
throw err is ApiException ? err : ApiException(-1, '配置列表加载失败');
|
||||
}
|
||||
}
|
||||
|
||||
/// 新增。创建时 apiKey 必填。
|
||||
Future<AiConfigItem> createConfig({
|
||||
required String name,
|
||||
required String baseUrl,
|
||||
required String apiKey,
|
||||
required String model,
|
||||
}) async {
|
||||
try {
|
||||
final res = await _dio.post('/ai/configs', data: {
|
||||
'name': name,
|
||||
'baseUrl': baseUrl,
|
||||
'apiKey': apiKey,
|
||||
'model': model,
|
||||
});
|
||||
return AiConfigItem.fromJson(Map<String, dynamic>.from(res.data as Map));
|
||||
} on DioException catch (e) {
|
||||
final err = e.error;
|
||||
throw err is ApiException ? err : ApiException(-1, '保存失败');
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新。[apiKey] 传空表示不改动 —— App 只有掩码,不该被迫重新输入 key。
|
||||
Future<AiConfigItem> updateConfig({
|
||||
required int id,
|
||||
required String name,
|
||||
required String baseUrl,
|
||||
required String model,
|
||||
String? apiKey,
|
||||
}) async {
|
||||
try {
|
||||
final res = await _dio.put('/ai/configs/$id', data: {
|
||||
'name': name,
|
||||
'baseUrl': baseUrl,
|
||||
'model': model,
|
||||
if (apiKey != null && apiKey.isNotEmpty) 'apiKey': apiKey,
|
||||
});
|
||||
return AiConfigItem.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> deleteConfig(int id) async {
|
||||
try {
|
||||
await _dio.delete('/ai/configs/$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