瀏覽代碼

[feat]亲密度分析,截图回复-对话分析,改为SSE流式接口

hezihao 7 月之前
父節點
當前提交
8671b7dfa7

+ 22 - 0
lib/data/api/atmob_stream_api.c.dart

@@ -35,7 +35,29 @@ class _AtmobStreamApi implements AtmobStreamApi {
           .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
     );
     final _result = await _dio.fetch<ResponseBody>(_options);
+    return _result.data!;
+  }
 
+  @override
+  Future<ResponseBody> intimacyChatAnalyze(
+    IntimacyChatAnalyzeRequest request,
+  ) async {
+    final _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{};
+    final _headers = <String, dynamic>{};
+    final _data = <String, dynamic>{};
+    _data.addAll(request.toJson());
+    final _options = _setStreamType<ResponseBody>(
+      Options(method: 'POST', headers: _headers, extra: _extra)
+          .compose(
+            _dio.options,
+            '/project/keyboard/v1/intimacy/chat/analyze',
+            queryParameters: queryParameters,
+            data: _data,
+          )
+          .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
+    );
+    final _result = await _dio.fetch<ResponseBody>(_options);
     return _result.data!;
   }
 

+ 13 - 4
lib/data/api/atmob_stream_api.dart

@@ -1,22 +1,31 @@
-
 import 'package:dio/dio.dart';
 import 'package:keyboard/data/api/request/deep_seek_chat_request.dart';
+import 'package:keyboard/data/api/request/intimacy_reply_chat_request.dart';
 import 'package:retrofit/error_logger.dart';
 import 'package:retrofit/http.dart';
 import 'package:retrofit/retrofit.dart';
 import '../consts/constants.dart';
 
+/// 注:每次生成前,要将.c改成.g,生成后,再重命名为.c
 
 part 'atmob_stream_api.c.dart';
+// part 'atmob_stream_api.g.dart';
+
+/// 要把生成的.g文件中,每个方法的,final _result = await _dio.fetch<Map<String, dynamic>>(_options);,去掉
+/// 改成下面这2句:
+/// final _result = await _dio.fetch<ResponseBody>(_options);
+/// return _result.data!;
 
 @RestApi()
 abstract class AtmobStreamApi {
-
   factory AtmobStreamApi(Dio dio, {String baseUrl}) = _AtmobStreamApi;
 
   @POST("/project/gpt/v1/chat/deepseek/stream")
   Future<ResponseBody> deepSeekChat(@Body() DeepSeekChatRequest request);
 
-
+  /// 对话分析
+  @POST("/project/keyboard/v1/intimacy/chat/analyze")
+  Future<ResponseBody> intimacyChatAnalyze(
+    @Body() IntimacyChatAnalyzeRequest request,
+  );
 }
-

+ 1 - 1
lib/data/api/response/intimacy_chat_analyze_response.dart

@@ -19,7 +19,7 @@ class IntimacyChatAnalyzeResponse {
 
   /// Ai模型
   @JsonKey(name: "model")
-  int? model;
+  String? model;
 
   /// 选择项列表
   @JsonKey(name: "choices")

+ 1 - 1
lib/data/api/response/intimacy_chat_analyze_response.g.dart

@@ -12,7 +12,7 @@ IntimacyChatAnalyzeResponse _$IntimacyChatAnalyzeResponseFromJson(
   json['id'] as String?,
   json['object'] as String?,
   (json['created'] as num?)?.toInt(),
-  (json['model'] as num?)?.toInt(),
+  json['model'] as String?,
   (json['choices'] as List<dynamic>?)
       ?.map((e) => AnalyzeChoiceItem.fromJson(e as Map<String, dynamic>))
       .toList(),

+ 36 - 6
lib/data/repository/intimacy_analyze_repository.dart

@@ -1,14 +1,18 @@
+import 'dart:convert';
 import 'dart:io';
 
 import 'package:get/get_rx/src/rx_types/rx_types.dart';
 import 'package:injectable/injectable.dart';
 import 'package:keyboard/base/app_base_request.dart';
 
+import '../../base/base_response.dart';
 import '../../di/get_it.dart';
 import '../../utils/async_util.dart';
 import '../../utils/atmob_log.dart';
 import '../../utils/http_handler.dart';
+import '../../utils/sse_parse_util.dart';
 import '../api/atmob_api.dart';
+import '../api/atmob_stream_api.dart';
 import '../api/request/intimacy_analyze_request.dart';
 import '../api/request/intimacy_reply_analyze_request.dart';
 import '../api/request/intimacy_reply_chat_request.dart';
@@ -25,6 +29,7 @@ class IntimacyAnalyzeRepository {
   final String tag = "IntimacyAnalyzeRepository";
 
   final AtmobApi atmobApi;
+  final AtmobStreamApi atmobStreamApi;
 
   /// 亲密度配置
   Rxn<IntimacyAnalyzeConfigResponse> intimacyAnalyzeConfig =
@@ -38,7 +43,7 @@ class IntimacyAnalyzeRepository {
   Rxn<IntimacyAnalyzeReplyConfigResponse> intimacyAnalyzeReplyConfig =
       Rxn<IntimacyAnalyzeReplyConfigResponse>();
 
-  IntimacyAnalyzeRepository(this.atmobApi) {
+  IntimacyAnalyzeRepository(this.atmobApi, this.atmobStreamApi) {
     AtmobLog.d(tag, '$tag...init');
     // 初始化时,刷新配置
     _refreshIntimacyAnalyzeConfig();
@@ -101,11 +106,36 @@ class IntimacyAnalyzeRepository {
         .then(HttpHandler.handle(true));
   }
 
-  /// 对话分析
-  Future<IntimacyChatAnalyzeResponse> intimacyChatAnalyze(
-    IntimacyChatAnalyzeRequest request,
-  ) {
-    return atmobApi.intimacyChatAnalyze(request).then(HttpHandler.handle(true));
+  /// 获取对话分析配置(SSE流式)
+  Future<Stream<Message>> intimacyChatAnalyze(IntimacyChatAnalyzeRequest request) {
+    return atmobStreamApi
+        .intimacyChatAnalyze(request)
+        .then((response) async {
+      List<String>? contentType = response.headers['Content-Type'];
+      if (contentType != null) {
+        for (var value in contentType) {
+          if (value.contains('text/event-stream')) {
+            return response.stream;
+          } else if (value.contains('application/json')) {
+            BaseResponse<String> baseResponse = BaseResponse.fromJson(
+              jsonDecode(
+                await response.stream
+                    .map((bytes) => utf8.decoder.convert(bytes))
+                    .toList()
+                    .then((value) => value.join()),
+              ),
+                  (json) => json as String,
+            );
+            throw ServerErrorException(
+              baseResponse.code,
+              baseResponse.message,
+            );
+          }
+        }
+      }
+      throw Exception('Invalid content type');
+    })
+        .then((stream) => SSEParseUtil.parse(stream));
   }
 
   /// 获取识图回复配置

+ 43 - 40
lib/di/get_it.config.dart

@@ -155,6 +155,17 @@ extension GetItInjectableX on _i174.GetIt {
         gh<_i361.Dio>(instanceName: 'defaultDio'),
       ),
     );
+    gh.lazySingleton<_i283.IntimacyAnalyzeRepository>(
+      () => _i283.IntimacyAnalyzeRepository(
+        gh<_i243.AtmobApi>(),
+        gh<_i329.AtmobStreamApi>(),
+      ),
+    );
+    gh.factory<_i738.IntimacyAnalyzeConfigHelper>(
+      () => _i738.IntimacyAnalyzeConfigHelper(
+        gh<_i283.IntimacyAnalyzeRepository>(),
+      ),
+    );
     gh.lazySingleton<_i428.UploadFileManager>(
       () => _i428.UploadFileManager(gh<_i815.FileUploadRepository>()),
     );
@@ -170,9 +181,6 @@ extension GetItInjectableX on _i174.GetIt {
     gh.lazySingleton<_i274.KeyboardRepository>(
       () => _i274.KeyboardRepository(gh<_i243.AtmobApi>()),
     );
-    gh.lazySingleton<_i283.IntimacyAnalyzeRepository>(
-      () => _i283.IntimacyAnalyzeRepository(gh<_i243.AtmobApi>()),
-    );
     gh.factoryParam<
       _i293.CharacterTabGroupContentController,
       _i96.CharacterGroupInfo,
@@ -221,6 +229,11 @@ extension GetItInjectableX on _i174.GetIt {
         gh<_i274.KeyboardRepository>(),
       ),
     );
+    gh.factory<_i278.CustomDirectionEditController>(
+      () => _i278.CustomDirectionEditController(
+        gh<_i738.IntimacyAnalyzeConfigHelper>(),
+      ),
+    );
     gh.factory<_i161.KeyBoardController>(
       () => _i161.KeyBoardController(gh<_i274.KeyboardRepository>()),
     );
@@ -230,6 +243,14 @@ extension GetItInjectableX on _i174.GetIt {
         gh<_i274.KeyboardRepository>(),
       ),
     );
+    gh.factory<_i666.IntimacyAnalyseUploadController>(
+      () => _i666.IntimacyAnalyseUploadController(
+        gh<_i283.IntimacyAnalyzeRepository>(),
+        gh<_i738.IntimacyAnalyzeConfigHelper>(),
+        gh<_i428.UploadFileManager>(),
+        gh<_i83.AccountRepository>(),
+      ),
+    );
     gh.factory<_i970.CharacterGroupContentController>(
       () => _i970.CharacterGroupContentController(
         gh<_i421.CharactersRepository>(),
@@ -289,9 +310,26 @@ extension GetItInjectableX on _i174.GetIt {
         gh<_i50.ConfigRepository>(),
       ),
     );
-    gh.factory<_i738.IntimacyAnalyzeConfigHelper>(
-      () => _i738.IntimacyAnalyzeConfigHelper(
+    gh.factory<_i510.ConversationAnalysisController>(
+      () => _i510.ConversationAnalysisController(
+        gh<_i738.IntimacyAnalyzeConfigHelper>(),
+        gh<_i428.UploadFileManager>(),
         gh<_i283.IntimacyAnalyzeRepository>(),
+        gh<_i83.AccountRepository>(),
+      ),
+    );
+    gh.factory<_i464.ScanImageReplyController>(
+      () => _i464.ScanImageReplyController(
+        gh<_i738.IntimacyAnalyzeConfigHelper>(),
+        gh<_i428.UploadFileManager>(),
+        gh<_i283.IntimacyAnalyzeRepository>(),
+        gh<_i83.AccountRepository>(),
+      ),
+    );
+    gh.factory<_i987.IntimacyAnalyseReportController>(
+      () => _i987.IntimacyAnalyseReportController(
+        gh<_i283.IntimacyAnalyzeRepository>(),
+        gh<_i738.IntimacyAnalyzeConfigHelper>(),
       ),
     );
     gh.factory<_i1008.LoginController>(
@@ -320,41 +358,6 @@ extension GetItInjectableX on _i174.GetIt {
         gh<_i779.PaymentStatusManager>(),
       ),
     );
-    gh.factory<_i278.CustomDirectionEditController>(
-      () => _i278.CustomDirectionEditController(
-        gh<_i738.IntimacyAnalyzeConfigHelper>(),
-      ),
-    );
-    gh.factory<_i666.IntimacyAnalyseUploadController>(
-      () => _i666.IntimacyAnalyseUploadController(
-        gh<_i283.IntimacyAnalyzeRepository>(),
-        gh<_i738.IntimacyAnalyzeConfigHelper>(),
-        gh<_i428.UploadFileManager>(),
-        gh<_i83.AccountRepository>(),
-      ),
-    );
-    gh.factory<_i510.ConversationAnalysisController>(
-      () => _i510.ConversationAnalysisController(
-        gh<_i738.IntimacyAnalyzeConfigHelper>(),
-        gh<_i428.UploadFileManager>(),
-        gh<_i283.IntimacyAnalyzeRepository>(),
-        gh<_i83.AccountRepository>(),
-      ),
-    );
-    gh.factory<_i464.ScanImageReplyController>(
-      () => _i464.ScanImageReplyController(
-        gh<_i738.IntimacyAnalyzeConfigHelper>(),
-        gh<_i428.UploadFileManager>(),
-        gh<_i283.IntimacyAnalyzeRepository>(),
-        gh<_i83.AccountRepository>(),
-      ),
-    );
-    gh.factory<_i987.IntimacyAnalyseReportController>(
-      () => _i987.IntimacyAnalyseReportController(
-        gh<_i283.IntimacyAnalyzeRepository>(),
-        gh<_i738.IntimacyAnalyzeConfigHelper>(),
-      ),
-    );
     return this;
   }
 }

+ 42 - 21
lib/module/intimacy_analyse/screenshot_reply/conversation_analysis/conversation_analysis_controller.dart

@@ -1,3 +1,5 @@
+import 'dart:async';
+import 'dart:convert';
 import 'dart:io';
 
 import 'package:flutter/cupertino.dart';
@@ -19,6 +21,7 @@ import '../../../../utils/error_handler.dart';
 import '../../../../utils/http_handler.dart';
 import '../../../../utils/image_picker_util.dart';
 import '../../../../utils/intimacy_analyze_config_helper.dart';
+import '../../../../utils/sse_parse_util.dart';
 import '../../../../utils/toast_util.dart';
 import '../../../../utils/upload/upload_file_manager.dart';
 import '../../../../utils/upload/upload_scene_type.dart';
@@ -47,9 +50,6 @@ class ConversationAnalysisController extends BaseController {
   /// 是否是上传页
   Rx<bool> isUploadPage = false.obs;
 
-  /// 报告是否已经生成
-  Rx<bool> hasReport = false.obs;
-
   /// 是否已解锁
   Rx<bool> isUnlock = false.obs;
 
@@ -66,6 +66,9 @@ class ConversationAnalysisController extends BaseController {
   /// 上传图片列表
   RxList<UploadInfo> uploadInfoList = <UploadInfo>[].obs;
 
+  /// 对话分析的订阅
+  StreamSubscription<Message>? _intimacyChatAnalyzeSubscription;
+
   ConversationAnalysisController(
     this.intimacyAnalyzeConfigHelper,
     this.uploadFileManager,
@@ -75,7 +78,10 @@ class ConversationAnalysisController extends BaseController {
 
   @override
   void onClose() {
+    // 删除文件上传记录
     uploadFileManager.deleteUploadInfoBySceneType(uploadSceneType);
+    // 取消SSE流数据订阅
+    _intimacyChatAnalyzeSubscription?.cancel();
     super.onClose();
   }
 
@@ -166,26 +172,41 @@ class ConversationAnalysisController extends BaseController {
       return;
     }
 
-    try {
-      IntimacyChatAnalyzeResponse response = await intimacyAnalyzeRepository
-          .intimacyChatAnalyze(IntimacyChatAnalyzeRequest(imageList, title));
-      String reportContent = response.choices?[0].delta?.content ?? "";
-      reportData.value = reportContent;
-      hasReport.value = true;
-    } catch (error) {
-      AtmobLog.e(_tag, error.toString());
-      if (error is ServerErrorException) {
-        // 需要Vip权限
-        if (error.code == 1005) {
-          ToastUtil.show(error.message);
-          StorePage.start();
+    // 切换为生成报告
+    isUploadPage.value = false;
+    reportData.value = "";
+
+    Stream<Message> stream = await intimacyAnalyzeRepository
+        .intimacyChatAnalyze(IntimacyChatAnalyzeRequest(imageList, title));
+    _intimacyChatAnalyzeSubscription = stream.listen(
+      (message) {
+        // 流数据更新
+        String json = message.data;
+        // 解析json为实体类
+        IntimacyChatAnalyzeResponse response =
+            IntimacyChatAnalyzeResponse.fromJson(jsonDecode(json));
+        String reportContent = response.choices?[0].delta?.content ?? "";
+        reportData.value = (reportData.value + reportContent);
+      },
+      onError: (error) {
+        // 发生错误
+        AtmobLog.e(_tag, error.toString());
+        if (error is ServerErrorException) {
+          // 需要Vip权限
+          if (error.code == 1005) {
+            ToastUtil.show(error.message);
+            StorePage.start();
+          } else {
+            ToastUtil.show(error.message);
+          }
         } else {
-          ToastUtil.show(error.message);
+          ErrorHandler.toastError(error);
         }
-      } else {
-        ErrorHandler.toastError(error);
-      }
-    }
+      },
+      onDone: () {
+        // 流关闭
+      },
+    );
   }
 
   /// 点击上传按钮

+ 41 - 40
lib/module/intimacy_analyse/screenshot_reply/conversation_analysis/conversation_analysis_view.dart

@@ -29,9 +29,8 @@ class ConversationAnalysisView
   Widget buildBody(BuildContext context) {
     return Stack(
       children: [
-        Obx(() {
-          return _buildContentList();
-        }),
+        // 内容列表
+        _buildContentList(),
         // 上传截图按钮
         Positioned.fill(
           child: Align(
@@ -47,43 +46,45 @@ class ConversationAnalysisView
 
   /// 内容列表
   Widget _buildContentList() {
-    Widget contentWidget;
-    // 上传页
-    if (controller.isUploadPage.value) {
-      contentWidget = Column(
-        children: [
-          // 上传聊天记录卡片
-          _buildUploadCard(),
-          SizedBox(height: 14.h),
-          // 选项卡片
-          _buildOptionCard(),
-          // 距离底部有一定间距
-          SizedBox(height: 90.h),
-        ],
-      );
-    } else if (controller.hasReport.value) {
-      // 已出结果
-      contentWidget = Column(
-        children: [
-          // 分析结果卡片
-          _buildAnalysisResultCard(),
-          SizedBox(height: 12.h),
-          // 分析结果Markdown卡片
-          _buildMarkdownAnalysisResultCard(),
-          // 距离底部有一定间距
-          SizedBox(height: 90.h),
-        ],
-      );
-    } else {
-      // 例子列表
-      contentWidget = Column(
-        children: [
-          // 上传聊天记录例子卡片
-          _buildChatRecordSampleCard(),
-        ],
-      );
-    }
-    return SingleChildScrollView(child: contentWidget);
+    return Obx(() {
+      Widget contentWidget;
+      // 上传页
+      if (controller.isUploadPage.value) {
+        contentWidget = Column(
+          children: [
+            // 上传聊天记录卡片
+            _buildUploadCard(),
+            SizedBox(height: 14.h),
+            // 选项卡片
+            _buildOptionCard(),
+            // 距离底部有一定间距
+            SizedBox(height: 90.h),
+          ],
+        );
+      } else if (controller.reportData.value.isNotEmpty) {
+        // 已出结果
+        contentWidget = Column(
+          children: [
+            // 分析结果卡片
+            _buildAnalysisResultCard(),
+            SizedBox(height: 12.h),
+            // 分析结果Markdown卡片
+            _buildMarkdownAnalysisResultCard(),
+            // 距离底部有一定间距
+            SizedBox(height: 90.h),
+          ],
+        );
+      } else {
+        // 例子列表
+        contentWidget = Column(
+          children: [
+            // 上传聊天记录例子卡片
+            _buildChatRecordSampleCard(),
+          ],
+        );
+      }
+      return SingleChildScrollView(child: contentWidget);
+    });
   }
 
   /// 上传步骤卡片