Parcourir la source

[feat]亲密度分析,填充分析结果数据到图表中

hezihao il y a 7 mois
Parent
commit
848f8c3e7a

+ 10 - 0
assets/string/base/string.xml

@@ -324,9 +324,19 @@
 
     <string name="no_choose_mode_tip">请选择模式</string>
     <string name="no_upload_screenshot_tip">请上传截图</string>
+    <string name="no_choose_intimacy_ta_tip">请选择亲密对象</string>
+    <string name="no_choose_prediction_direction_tip">请选择预测方向</string>
+    <string name="no_choose_ai_model_tip">请选择AI模型</string>
 
     <string name="no_upload_conversation_image_tip">请上传聊天记录图片</string>
 
+    <string name="intimacy_analyse_ing">分析中</string>
+
+    <string name="intimacy_interaction">互动好感度</string>
+    <string name="intimacy_topic">话题好感度</string>
+    <string name="intimacy_respond">情绪回应</string>
+    <string name="intimacy_intimacy_ratio">亲密词占比</string>
+
     <string name="preview">预览</string>
 
     <string name="retry">再试试</string>

+ 5 - 1
lib/data/api/response/intimacy_analyze_response.dart

@@ -49,9 +49,13 @@ class IntimacyAnalyzeResponse {
   @JsonKey(name: "summary")
   String? summary;
 
+  /// 预测方向的名称
+  @JsonKey(name: "directionName")
+  String? directionName;
+
   IntimacyAnalyzeResponse(this.emotion, this.intimacyPhase, this.interaction,
       this.topic, this.respond, this.intimacyRatio, this.direction, this.need,
-      this.shortChat, this.longChat, this.summary);
+      this.shortChat, this.longChat, this.summary, this.directionName);
 
   factory IntimacyAnalyzeResponse.fromJson(Map<String, dynamic> json) =>
       _$IntimacyAnalyzeResponseFromJson(json);

+ 2 - 0
lib/data/api/response/intimacy_analyze_response.g.dart

@@ -20,6 +20,7 @@ IntimacyAnalyzeResponse _$IntimacyAnalyzeResponseFromJson(
   json['shortChat'] as String?,
   json['longChat'] as String?,
   json['summary'] as String?,
+  json['directionName'] as String?,
 );
 
 Map<String, dynamic> _$IntimacyAnalyzeResponseToJson(
@@ -36,4 +37,5 @@ Map<String, dynamic> _$IntimacyAnalyzeResponseToJson(
   'shortChat': instance.shortChat,
   'longChat': instance.longChat,
   'summary': instance.summary,
+  'directionName': instance.directionName,
 };

+ 53 - 7
lib/module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_upload_controller.dart

@@ -19,8 +19,10 @@ import '../../../data/bean/keyboard_info.dart';
 import '../../../data/bean/option_select_config.dart';
 import '../../../data/bean/option_select_item.dart';
 import '../../../data/repository/intimacy_analyze_repository.dart';
+import '../../../dialog/loading_dialog.dart';
 import '../../../router/app_page_arguments.dart';
 import '../../../utils/atmob_log.dart';
+import '../../../utils/http_handler.dart';
 import '../../../utils/image_picker_util.dart';
 import '../../../utils/intimacy_analyze_config_helper.dart';
 import '../../../utils/upload/upload_file_manager.dart';
@@ -104,6 +106,9 @@ class IntimacyAnalyseUploadController extends BaseController {
   /// 当前选择的预测方向
   Rx<String> currentDirectionOption = "".obs;
 
+  /// 亲密分析结果
+  Rxn<IntimacyAnalyzeResponse> intimacyAnalyzeResult = Rxn();
+
   IntimacyAnalyseUploadController(
     this.intimacyAnalyzeRepository,
     this.intimacyAnalyzeConfigHelper,
@@ -326,13 +331,54 @@ class IntimacyAnalyseUploadController extends BaseController {
     // 当前选择的Ai模型
     String aiModel = currentAiModel.value?.value ?? "";
 
-    // 分析亲密度
-    IntimacyAnalyzeResponse response = await intimacyAnalyzeRepository
-        .getIntimacyAnalyze(
-          IntimacyAnalyzeRequest(imageList, keyboardId, direction, aiModel),
-        );
-    AtmobLog.d(tag, "分析亲密度 => ${response.toJson()}");
-    isUploadPage.value = false;
+    if (imageList.isEmpty) {
+      ToastUtil.show(StringName.noUploadScreenshotTip);
+      return;
+    }
+    if (keyboardId.isEmpty) {
+      ToastUtil.show(StringName.noChooseIntimacyTaTip);
+      return;
+    }
+    if (direction.isEmpty) {
+      ToastUtil.show(StringName.noChoosePredictionDirectionTip);
+      return;
+    }
+    if (aiModel.isEmpty) {
+      ToastUtil.show(StringName.noChooseAiModelTip);
+      return;
+    }
+
+    LoadingDialog.show(StringName.intimacyAnalyseIng);
+    try {
+      // 分析亲密度
+      IntimacyAnalyzeResponse response = await intimacyAnalyzeRepository
+          .getIntimacyAnalyze(
+            IntimacyAnalyzeRequest(imageList, keyboardId, direction, aiModel),
+          );
+      // 设置预测方向的名称
+      response.directionName = currentDirectionOption.value;
+
+      AtmobLog.d(tag, "分析亲密度 => ${response.toJson()}");
+
+      LoadingDialog.hide();
+
+      intimacyAnalyzeResult.value = response;
+      isUploadPage.value = false;
+    } catch (error) {
+      LoadingDialog.hide();
+      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 {
+        ErrorHandler.toastError(error);
+      }
+    }
   }
 
   /// 换Ta测测

+ 42 - 39
lib/module/intimacy_analyse/intimacy_analyse_upload/intimacy_analyse_upload_page.dart

@@ -279,6 +279,7 @@ class IntimacyAnalyseUploadPage
           SizedBox(height: 12.h),
           IntimacyAnalyseReportWidget(
             reportContent: controller.reportMarkdownData.value,
+            intimacyAnalyzeResult: controller.intimacyAnalyzeResult.value,
             unlock: controller.isReportUnlock.value,
           ),
         ],
@@ -321,7 +322,7 @@ class IntimacyAnalyseUploadPage
                     SizedBox(width: 2.w),
                     // 结果文字
                     Text(
-                      "未来",
+                      controller.currentDirectionOption.value,
                       style: TextStyle(
                         color: ColorName.black80,
                         fontSize: 13.sp,
@@ -351,46 +352,48 @@ class IntimacyAnalyseUploadPage
 
   /// Ai模型结果
   Widget _buildAiModelResult() {
-    return Expanded(
-      child: Container(
-        padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 10.w),
-        decoration: BoxDecoration(
-          color: ColorName.white,
-          borderRadius: BorderRadius.circular(14.r),
-        ),
-        child: Row(
-          children: [
-            Column(
-              crossAxisAlignment: CrossAxisAlignment.start,
-              children: [
-                // 标题
-                Text(
-                  StringName.intimacyAnalyseModel,
-                  style: TextStyle(
-                    color: ColorName.black47,
-                    fontSize: 12.sp,
-                    fontWeight: FontWeight.w400,
+    return Obx(() {
+      return Expanded(
+        child: Container(
+          padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 10.w),
+          decoration: BoxDecoration(
+            color: ColorName.white,
+            borderRadius: BorderRadius.circular(14.r),
+          ),
+          child: Row(
+            children: [
+              Column(
+                crossAxisAlignment: CrossAxisAlignment.start,
+                children: [
+                  // 标题
+                  Text(
+                    StringName.intimacyAnalyseModel,
+                    style: TextStyle(
+                      color: ColorName.black47,
+                      fontSize: 12.sp,
+                      fontWeight: FontWeight.w400,
+                    ),
                   ),
-                ),
-                SizedBox(height: 4.h),
-                // 结果文字
-                Text(
-                  "DeepSeek R1",
-                  style: TextStyle(
-                    color: ColorName.black80,
-                    fontSize: 13.sp,
-                    fontWeight: FontWeight.w500,
+                  SizedBox(height: 4.h),
+                  // 结果文字
+                  Text(
+                    controller.currentAiModel.value?.name ?? "",
+                    style: TextStyle(
+                      color: ColorName.black80,
+                      fontSize: 13.sp,
+                      fontWeight: FontWeight.w500,
+                    ),
                   ),
-                ),
-              ],
-            ),
-            Expanded(child: SizedBox()),
-            // Ai模型图片
-            Assets.images.iconAiModel.image(width: 38, height: 30),
-          ],
+                ],
+              ),
+              Expanded(child: SizedBox()),
+              // Ai模型图片
+              Assets.images.iconAiModel.image(width: 38, height: 30),
+            ],
+          ),
         ),
-      ),
-    );
+      );
+    });
   }
 
   /// 添加预测方向按钮
@@ -750,7 +753,7 @@ class IntimacyAnalyseUploadPage
   Widget _buildCardList() {
     return Obx(() {
       // 上传页
-      if (controller.isUploadPage.value) {
+      if (controller.intimacyAnalyzeResult.value == null) {
         return Column(
           children: [
             // 上传卡片

+ 73 - 21
lib/module/intimacy_analyse/widget/intimacy_analyse_report_widget.dart

@@ -3,16 +3,21 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:keyboard/resource/string.gen.dart';
 import 'package:lottie/lottie.dart';
 
+import '../../../data/api/response/intimacy_analyze_response.dart';
 import '../../../resource/assets.gen.dart';
 import '../../../resource/colors.gen.dart';
+import '../../../utils/string_format_util.dart';
 import '../../../widget/animated_progress_bar.dart';
 import '../../../widget/markdown/markdown_viewer.dart';
 
 /// 亲密度报告组件
 class IntimacyAnalyseReportWidget extends StatelessWidget {
-  /// 报告内容
+  /// 报告内容,Markdown格式
   final String reportContent;
 
+  /// 分析结果
+  final IntimacyAnalyzeResponse? intimacyAnalyzeResult;
+
   /// 是否已解锁
   final bool unlock;
 
@@ -22,6 +27,7 @@ class IntimacyAnalyseReportWidget extends StatelessWidget {
   const IntimacyAnalyseReportWidget({
     super.key,
     required this.reportContent,
+    this.intimacyAnalyzeResult,
     this.unlock = false,
     this.isPreview = false,
   });
@@ -35,11 +41,15 @@ class IntimacyAnalyseReportWidget extends StatelessWidget {
     if (!unlock) {
       return UnlockReportCardWidget();
     }
-    return reportContent.isEmpty
-        // 报告生成中
-        ? CreatingReportCardWidget()
-        // 已出报告
-        : ExistReportCardWidget(reportContent: reportContent);
+    // 报告生成中
+    if (intimacyAnalyzeResult == null || reportContent.isEmpty) {
+      return CreatingReportCardWidget();
+    }
+    // 已出报告
+    return ExistReportCardWidget(
+      reportContent: reportContent,
+      intimacyAnalyzeResult: intimacyAnalyzeResult,
+    );
   }
 }
 
@@ -86,7 +96,14 @@ class ExistReportCardWidget extends StatelessWidget {
   /// 报告内容,Markdown格式
   final String reportContent;
 
-  const ExistReportCardWidget({super.key, required this.reportContent});
+  /// 分析结果
+  final IntimacyAnalyzeResponse? intimacyAnalyzeResult;
+
+  const ExistReportCardWidget({
+    super.key,
+    required this.reportContent,
+    this.intimacyAnalyzeResult,
+  });
 
   @override
   Widget build(BuildContext context) {
@@ -104,6 +121,12 @@ class ExistReportCardWidget extends StatelessWidget {
 
   /// 报告概览,包含概览数值和图表
   Widget _buildReportOverview() {
+    if (intimacyAnalyzeResult == null) {
+      return SizedBox();
+    }
+
+    var analyzeResult = intimacyAnalyzeResult!;
+
     // 圆角背景
     return Container(
       padding: EdgeInsets.only(left: 12.w, right: 12.w, top: 12.h),
@@ -123,15 +146,21 @@ class ExistReportCardWidget extends StatelessWidget {
             ),
             child: Row(
               children: [
-                _buildIntimacyOverviewItem("30"),
+                _buildIntimacyOverviewItem(
+                  StringFormatUtil.removePercentSymbol(
+                    analyzeResult.emotion ?? "",
+                  ),
+                ),
                 _buildOverviewDivider(),
-                _buildCurrentStageOverviewItem("相互了解"),
+                _buildCurrentStageOverviewItem(
+                  analyzeResult.intimacyPhase ?? "",
+                ),
               ],
             ),
           ),
           SizedBox(height: 36.h),
           // 图表
-          _buildChart(),
+          _buildChart(analyzeResult),
         ],
       ),
     );
@@ -244,29 +273,48 @@ class ExistReportCardWidget extends StatelessWidget {
     );
   }
 
+  /// 将百分比转换为进度条需要的数值
+  double _convertPercentValue2ProgressBar(String percentValue) {
+    if (percentValue.isEmpty) {
+      return 0;
+    }
+    // 去掉%号后的数值
+    String valueStr = StringFormatUtil.removePercentSymbol(percentValue);
+    double noPercentValue = double.tryParse(valueStr) ?? 0;
+    if (noPercentValue == 0) {
+      return 0;
+    } else {
+      // 除以100,转换为0-1之间的数值,才是进度条需要的数值
+      double progressValue = noPercentValue / 100;
+      return progressValue;
+    }
+  }
+
   /// 报告图表
-  Widget _buildChart() {
+  Widget _buildChart(IntimacyAnalyzeResponse analyzeResult) {
     return Column(
       mainAxisSize: MainAxisSize.min,
       children: [
         _buildValueItem(
           iconProvider: Assets.images.iconEmojiLike.provider(),
-          title: "互动好感度",
-          value: 0.5,
+          title: StringName.intimacyInteraction,
+          value: _convertPercentValue2ProgressBar(
+            analyzeResult.interaction ?? "",
+          ),
           progressColors: [ColorName.blueGradient1, ColorName.blueGradient2],
         ),
         SizedBox(height: 18.h),
         _buildValueItem(
           iconProvider: Assets.images.iconEmojiChat.provider(),
-          title: "话题好感度",
-          value: 0.35,
+          title: StringName.intimacyTopic,
+          value: _convertPercentValue2ProgressBar(analyzeResult.topic ?? ""),
           progressColors: [ColorName.greenGradient1, ColorName.greenGradient2],
         ),
         SizedBox(height: 18.h),
         _buildValueItem(
           iconProvider: Assets.images.iconEmojiLike.provider(),
-          title: "情绪回应",
-          value: 1.0,
+          title: StringName.intimacyRespond,
+          value: _convertPercentValue2ProgressBar(analyzeResult.respond ?? ""),
           progressColors: [
             ColorName.yellowGradient1,
             ColorName.yellowGradient2,
@@ -275,15 +323,19 @@ class ExistReportCardWidget extends StatelessWidget {
         SizedBox(height: 18.h),
         _buildValueItem(
           iconProvider: Assets.images.iconEmojiPercent.provider(),
-          title: "亲密词占比",
-          value: 0.4,
+          title: StringName.intimacyintimacyRatio,
+          value: _convertPercentValue2ProgressBar(
+            analyzeResult.intimacyRatio ?? "",
+          ),
           progressColors: [ColorName.pinkGradient1, ColorName.pinkGradient2],
         ),
         SizedBox(height: 18.h),
         _buildValueItem(
           iconProvider: Assets.images.iconEmojiLove.provider(),
-          title: "缘分指数",
-          value: 0.15,
+          title: analyzeResult.directionName ?? "",
+          value: _convertPercentValue2ProgressBar(
+            analyzeResult.direction ?? "",
+          ),
           progressColors: [
             ColorName.purpleGradient1,
             ColorName.purpleGradient2,

+ 16 - 0
lib/resource/string.gen.dart

@@ -232,7 +232,15 @@ class StringName {
   static final String noChooseOptionTip = 'no_choose_option_tip'.tr; // 请选择一项需求
   static final String noChooseModeTip = 'no_choose_mode_tip'.tr; // 请选择模式
   static final String noUploadScreenshotTip = 'no_upload_screenshot_tip'.tr; // 请上传截图
+  static final String noChooseIntimacyTaTip = 'no_choose_intimacy_ta_tip'.tr; // 请选择亲密对象
+  static final String noChoosePredictionDirectionTip = 'no_choose_prediction_direction_tip'.tr; // 请选择预测方向
+  static final String noChooseAiModelTip = 'no_choose_ai_model_tip'.tr; // 请选择AI模型
   static final String noUploadConversationImageTip = 'no_upload_conversation_image_tip'.tr; // 请上传聊天记录图片
+  static final String intimacyAnalyseIng = 'intimacy_analyse_ing'.tr; // 分析中
+  static final String intimacyInteraction = 'intimacy_interaction'.tr; // 互动好感度
+  static final String intimacyTopic = 'intimacy_topic'.tr; // 话题好感度
+  static final String intimacyRespond = 'intimacy_respond'.tr; // 情绪回应
+  static final String intimacyintimacyRatio = 'intimacy_intimacy_ratio'.tr; // 亲密词占比
   static final String preview = 'preview'.tr; // 预览
   static final String retry = 'retry'.tr; // 再试试
   static final String nextStep = 'next_step'.tr; // 下一步
@@ -507,7 +515,15 @@ class StringMultiSource {
       'no_choose_option_tip': '请选择一项需求',
       'no_choose_mode_tip': '请选择模式',
       'no_upload_screenshot_tip': '请上传截图',
+      'no_choose_intimacy_ta_tip': '请选择亲密对象',
+      'no_choose_prediction_direction_tip': '请选择预测方向',
+      'no_choose_ai_model_tip': '请选择AI模型',
       'no_upload_conversation_image_tip': '请上传聊天记录图片',
+      'intimacy_analyse_ing': '分析中',
+      'intimacy_interaction': '互动好感度',
+      'intimacy_topic': '话题好感度',
+      'intimacy_respond': '情绪回应',
+      'intimacy_intimacy_ratio': '亲密词占比',
       'preview': '预览',
       'retry': '再试试',
       'next_step': '下一步',

+ 5 - 0
lib/utils/string_format_util.dart

@@ -6,4 +6,9 @@ class StringFormatUtil {
   static String formatStr(String str, String format) {
     return sprintf(str, [format]);
   }
+
+  /// 清除百分比符号
+  static String removePercentSymbol(String str) {
+    return str.replaceAll("%", "");
+  }
 }

+ 21 - 5
lib/widget/animated_progress_bar.dart

@@ -100,6 +100,15 @@ class AnimatedGradientProgressBarState
       builder: (context, constraints) {
         // 进度条的实际宽度
         final progressBarWidth = constraints.maxWidth;
+        // 计算气泡距离左边的偏移量
+        double leftOffset = 0;
+        if (_animation.value == 0) {
+          leftOffset = 0;
+        } else {
+          leftOffset =
+              _animation.value * progressBarWidth -
+              (widget.targetValue > 0.5 ? 32 : 24);
+        }
         return Stack(
           // 允许子组件溢出自己本身的大小,默认是裁切的
           clipBehavior: Clip.none,
@@ -107,7 +116,7 @@ class AnimatedGradientProgressBarState
             // 百分比文本
             Positioned(
               // 左侧偏移量,气泡跟随在当前进度的末尾,公式:当前比例值 * 进度条的宽度 - 气泡宽度
-              left:  _animation.value * progressBarWidth - (widget.targetValue > 0.5 ? 32 : 24),
+              left: leftOffset,
               // 进度气泡,位于进度条上方
               bottom: widget.height + 0.85,
               child: AnimatedBuilder(
@@ -116,18 +125,25 @@ class AnimatedGradientProgressBarState
                   return BubbleWidget(
                     // 箭头方向
                     arrowDirection: AxisDirection.down,
-                    arrowOffset: 22,
+                    // 箭头距离左边的偏移量
+                    arrowOffset: leftOffset == 0 ? 10 : 22,
                     arrowLength: 8,
                     arrowRadius: 2,
                     arrowWidth: 5,
-                    padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 7),
+                    padding: const EdgeInsets.symmetric(
+                      vertical: 2,
+                      horizontal: 7,
+                    ),
                     borderRadius: BorderRadius.circular(9),
                     // 气泡的背景颜色,取渐变色的第2个颜色
                     backgroundColor: widget.gradient.colors.last,
                     contentBuilder: (context) {
                       return Text(
                         '${(_animation.value * 100).toStringAsFixed(0)}%',
-                        style: const TextStyle(fontSize: 10, color: Colors.white),
+                        style: const TextStyle(
+                          fontSize: 10,
+                          color: Colors.white,
+                        ),
                       );
                     },
                   );
@@ -169,7 +185,7 @@ class AnimatedGradientProgressBarState
             ),
           ],
         );
-      }
+      },
     );
   }
 }