From ba805f4c5188ea113eb1bab5513fc6ce8d5f8a7a Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Wed, 14 May 2025 18:57:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=BC=96=E8=BE=91=E8=AF=B4?= =?UTF-8?q?=E8=AF=9D=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/services/db/sqflite_api.dart | 5 +- lib/data/services/deapsound_ai_service.dart | 38 ++- lib/data/services/network/api.dart | 1 - lib/data/services/network/dio_manager.dart | 2 +- .../meeting_details_controller.dart | 47 ++-- .../meeting/views/speaker_bottom_sheet.dart | 261 +++++++++++++++--- lib/modules/meeting/views/speech_tab.dart | 5 +- lib/modules/meeting/views/summary_tab.dart | 3 +- 8 files changed, 278 insertions(+), 84 deletions(-) diff --git a/lib/data/services/db/sqflite_api.dart b/lib/data/services/db/sqflite_api.dart index cdb2e8d57..bf2d5a8cf 100644 --- a/lib/data/services/db/sqflite_api.dart +++ b/lib/data/services/db/sqflite_api.dart @@ -123,9 +123,12 @@ class SqfliteApi { try { final batch = db.batch(); for (var item in data) { + Map values = item['content'] != null + ? {'content': item['content']} + : {'speaker': item['speaker']}; batch.update( 'meetingspeaker', - {'content': item['content']}, + values, where: 'id = ?', whereArgs: [item['id']], ); diff --git a/lib/data/services/deapsound_ai_service.dart b/lib/data/services/deapsound_ai_service.dart index 186116ab4..022f2aa2e 100644 --- a/lib/data/services/deapsound_ai_service.dart +++ b/lib/data/services/deapsound_ai_service.dart @@ -1,17 +1,20 @@ import 'dart:convert'; -import 'package:get_storage/get_storage.dart'; import 'package:http/http.dart' as http; import '../../core/utils/logger.dart'; +import '../models/appconfig.dart'; import 'ai_service.dart'; class DeapsoundAIService implements AiService { + late final String baseUrl; late final String apiKey; + late final String model; DeapsoundAIService() { - final GetStorage storage = GetStorage(); - apiKey = storage.read('logintoken'); + apiKey = AppConfig.env('OPENAI_API_KEY') ?? ''; + baseUrl = AppConfig.env('OPENAI_BASE_URL') ?? ''; + model = AppConfig.env('OPENAI_MODEL') ?? ''; } @override @@ -20,7 +23,34 @@ class DeapsoundAIService implements AiService { String? systemPrompt, String? userProperties, }) async { - return ''; + final url = Uri.parse(baseUrl); + + final headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $apiKey', + }; + + final requestBody = { + "model": model, + "messages": messages, + }; + + try { + final response = await http.post( + url, + headers: headers, + body: jsonEncode(requestBody), + ); + if (response.statusCode != 200) { + throw Exception('Unknown error'); + } + + final result = jsonDecode(response.body); + + return result['choices'].first['message']['content']; + } catch (e) { + throw Exception('Unknown error'); + } } @override diff --git a/lib/data/services/network/api.dart b/lib/data/services/network/api.dart index 98daae281..77e1caee9 100644 --- a/lib/data/services/network/api.dart +++ b/lib/data/services/network/api.dart @@ -4,7 +4,6 @@ import 'nw_method.dart'; class Api { // 登录 static login([params]) { - print("请求后台数据login params=${params}"); return DioManager().request( NWMethod.post, '/api/home/user_sgin', diff --git a/lib/data/services/network/dio_manager.dart b/lib/data/services/network/dio_manager.dart index 0872436ec..5fe3ed8eb 100644 --- a/lib/data/services/network/dio_manager.dart +++ b/lib/data/services/network/dio_manager.dart @@ -20,7 +20,7 @@ class DioManager { ); dio = Dio(options); -//fdp + dio.interceptors.add(AuthInterceptor()); } diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index b906902ed..943d6f2c9 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -7,14 +7,12 @@ import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:http/http.dart' as http; -import 'package:uuid/uuid.dart'; import '../../../core/utils/logger.dart'; import '../../../core/utils/upload_oss.dart'; -import '../../../data/models/appconfig_model.dart'; -import '../../../data/services/ai_service.dart'; +import '../../../data/models/appconfig.dart'; import '../../../data/services/db/sqflite_api.dart'; -import '../../../data/services/open_ai_service_adapter.dart'; +import '../../../data/services/deapsound_ai_service.dart'; import '../model/meeting_details_model.dart'; import '../model/meeting_model.dart'; @@ -40,11 +38,8 @@ class MeetingDetailsController extends GetxController RxInt editSpeakerTextId = 0.obs; String editSpeakerTextChange = ''; - final AiService _aiService = Get.find(); - // Agent 信息 - late final DBAgent _agent; + String _systemPrompt = ''; String _userId = ''; - StreamSubscription? _aiResponseSubscription; bool _isDisposed = false; @@ -78,7 +73,6 @@ class MeetingDetailsController extends GetxController scrollController.dispose(); tabController.dispose(); playerController.dispose(); - _aiResponseSubscription?.cancel(); _isDisposed = true; super.onClose(); } @@ -110,10 +104,9 @@ class MeetingDetailsController extends GetxController void _getAgent() async { final GetStorage storage = GetStorage(); - // final List agents = storage.read("AGENTS"); Map userInfo = storage.read("user_info"); _userId = userInfo['user']['uid']; - // _agent = agents.firstWhere((agent) => agent.id == 'meet_assistant'); + _systemPrompt = AppConfig.agentsystemPrompt("personal_assistant") ?? ''; } void _getDataDetails() async { @@ -196,28 +189,20 @@ class MeetingDetailsController extends GetxController } allText = text; } - _aiServiceListen(); - // const uuid = Uuid(); - // _aiService.send( - // sid: uuid.v4(), - // messages: [ - // {'role': 'user', 'content': allText}, - // ], - // systemPrompt: _agent.systemPrompt, - // ); + await _aiServiceSummary(); + EasyLoading.dismiss(); } - void _aiServiceListen() { - String summaryText = ''; - // _aiResponseSubscription = _aiService.start().listen((AIStreamEvent event) { - // summaryText += event.content ?? ''; - // if (event.type == StreamEventType.complete) { - // SqfliteApi.editMeeting(id, {'summary': summaryText}); - // meetingDetails.value.summary = summaryText; - // meetingDetails.refresh(); - // EasyLoading.dismiss(); - // } - // }); + Future _aiServiceSummary() async { + String summaryText = await DeapsoundAIService().sendMessage( + messages: [ + {'role': 'system', 'content': _systemPrompt}, + {'role': 'user', 'content': allText}, + ], + ); + SqfliteApi.editMeeting(id, {'summary': summaryText}); + meetingDetails.value.summary = summaryText; + meetingDetails.refresh(); } Future _audioSpeaker() async { diff --git a/lib/modules/meeting/views/speaker_bottom_sheet.dart b/lib/modules/meeting/views/speaker_bottom_sheet.dart index 2d0bcf665..124804933 100644 --- a/lib/modules/meeting/views/speaker_bottom_sheet.dart +++ b/lib/modules/meeting/views/speaker_bottom_sheet.dart @@ -1,14 +1,68 @@ import 'package:flutter/material.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; -class SpeakerBottomSheet extends StatelessWidget { - const SpeakerBottomSheet({super.key}); +import '../../../data/services/db/sqflite_api.dart'; +import '../controllers/meeting_details_controller.dart'; + +class SpeakerBottomSheet extends StatefulWidget { + final int id; + final String speakerName; + + const SpeakerBottomSheet(this.id, this.speakerName, {super.key}); + + @override + State createState() => _SpeakerBottomSheetState(); +} + +class _SpeakerBottomSheetState extends State { + final _controller = Get.find(); + final GetStorage _storage = GetStorage(); + + final TextEditingController _speakerController = TextEditingController(); + int _type = 1; + List _historySpeakerList = []; + List _speakerList = []; + + @override + void initState() { + super.initState(); + _getSpeakerList(); + } + + void _getSpeakerList() { + _historySpeakerList = _storage.read("speaker_list") ?? []; + List speakerList = List.from(_historySpeakerList); + List textList = _controller.textList; + for (var element in textList) { + String speaker = element['speaker']; + if (speaker.isNotEmpty && + !RegExp(r'^Speaker \d+$').hasMatch(speaker) && + !speakerList.contains(speaker)) { + speakerList.add(speaker); + } + } + setState(() { + _speakerList = speakerList; + }); + } + + @override + void dispose() { + _speakerController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { return Padding( - padding: EdgeInsets.symmetric(horizontal: 12.w), + padding: EdgeInsets.only( + left: 12.w, + right: 12.w, + bottom: MediaQuery.of(context).viewInsets.bottom, + ), child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -49,17 +103,45 @@ class SpeakerBottomSheet extends StatelessWidget { color: Colors.grey[300], ), Container( - height: 46.w, margin: EdgeInsets.symmetric(vertical: 15.w), + alignment: Alignment.center, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12.r), ), + child: TextField( + controller: _speakerController, + style: TextStyle( + fontSize: 16.sp, + color: Colors.black87, + letterSpacing: 0, + height: 1.4, + ), + decoration: InputDecoration( + hintText: widget.speakerName, + hintStyle: TextStyle( + fontSize: 16.sp, + color: Colors.grey[400], + letterSpacing: 0, + height: 1.4, + ), + isDense: true, + fillColor: Colors.transparent, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 12.w, + ), + ), + ), ), Container( - constraints: BoxConstraints(maxHeight: 1.sh - 390.w), + constraints: BoxConstraints( + maxHeight: + 1.sh - 390.w - MediaQuery.of(context).viewInsets.bottom, + ), child: ListView.builder( - itemCount: 30, + itemCount: _speakerList.length, shrinkWrap: true, itemBuilder: (BuildContext context, int index) { return Container( @@ -83,15 +165,22 @@ class SpeakerBottomSheet extends StatelessWidget { color: Colors.grey[600]!, ), ), - SizedBox( - height: 42.w, - child: Align( - alignment: Alignment.centerLeft, - child: Text( - '龙哥', - style: TextStyle( - fontSize: 14.sp, - color: Colors.black87, + GestureDetector( + onTap: () { + _speakerController.text = _speakerList[index]; + }, + behavior: HitTestBehavior.translucent, + child: SizedBox( + width: 200.w, + height: 42.w, + child: Align( + alignment: Alignment.centerLeft, + child: Text( + _speakerList[index], + style: TextStyle( + fontSize: 14.sp, + color: Colors.black87, + ), ), ), ), @@ -102,12 +191,25 @@ class SpeakerBottomSheet extends StatelessWidget { }, ), ), - _row('应用到当前段落', false), - _row('应用到这个发言者的所有段落', true, description: '总结将根据模版自动更新'), + _row('应用到当前段落', _type == 0, onTap: () { + setState(() { + _type = 0; + }); + }), + _row( + '应用到这个发言者的所有段落', + _type == 1, + description: '总结将根据模版自动更新', + onTap: () { + setState(() { + _type = 1; + }); + }, + ), Padding( padding: EdgeInsets.only(top: 10.w, bottom: 20.w), child: GestureDetector( - onTap: () {}, + onTap: _save, child: Container( height: 48.w, alignment: Alignment.center, @@ -130,35 +232,39 @@ class SpeakerBottomSheet extends StatelessWidget { ); } - Widget _row(String name, bool isActive, {String? description}) { - return Padding( - padding: EdgeInsets.symmetric(vertical: 10.w), - child: Row( - children: [ - isActive ? _round() : _circle(), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - name, - style: TextStyle( - fontSize: 14.sp, - color: Colors.black87, - ), - ), - if (description != null) + Widget _row(String name, bool isActive, + {String? description, Function()? onTap}) { + return GestureDetector( + onTap: onTap, + child: Padding( + padding: EdgeInsets.symmetric(vertical: 10.w), + child: Row( + children: [ + isActive ? _round() : _circle(), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Text( - description, + name, style: TextStyle( - fontSize: 10.sp, - color: Colors.grey[500]!, + fontSize: 14.sp, + color: Colors.black87, ), ), - ], + if (description != null) + Text( + description, + style: TextStyle( + fontSize: 10.sp, + color: Colors.grey[500]!, + ), + ), + ], + ), ), - ), - ], + ], + ), ), ); } @@ -199,4 +305,75 @@ class SpeakerBottomSheet extends StatelessWidget { ), ); } + + void _save() { + if (_speakerController.text.isNotEmpty && + _speakerController.text != widget.speakerName) { + if (_type == 0) { + _single(); + } else { + _all(); + } + } else { + Get.back(); + } + } + + void _single() async { + EasyLoading.show(); + List list = [ + { + 'id': widget.id, + 'speaker': _speakerController.text, + } + ]; + var result = await SqfliteApi.editMeetingSpeaker(list); + if (result != null) { + int index = + _controller.textList.indexWhere((item) => item['id'] == widget.id); + if (index != -1) { + final dataMap = Map.from(_controller.textList[index]); + dataMap['speaker'] = _speakerController.text; + _controller.textList[index] = dataMap; + } + _addHistorySpeaker(); + Get.back(); + } + EasyLoading.dismiss(); + } + + void _all() async { + EasyLoading.show(); + List textList = _controller.textList; + List newTextList = []; + List editSpeakerList = []; + for (var element in textList) { + final dataMap = Map.from(element); + if (dataMap['speaker'] == widget.speakerName) { + dataMap['speaker'] = _speakerController.text; + editSpeakerList.add({ + 'id': dataMap['id'], + 'speaker': dataMap['speaker'], + }); + } + newTextList.add(dataMap); + } + var result = await SqfliteApi.editMeetingSpeaker(editSpeakerList); + if (result != null) { + _controller.textList.value = newTextList; + _addHistorySpeaker(); + Get.back(); + } + EasyLoading.dismiss(); + } + + void _addHistorySpeaker() { + String speaker = _speakerController.text; + if (speaker.isNotEmpty && + !RegExp(r'^Speaker \d+$').hasMatch(speaker) && + !_historySpeakerList.contains(speaker)) { + _historySpeakerList.add(speaker); + _storage.write("speaker_list", _historySpeakerList); + } + } } diff --git a/lib/modules/meeting/views/speech_tab.dart b/lib/modules/meeting/views/speech_tab.dart index d0c58e85c..8a586f27d 100644 --- a/lib/modules/meeting/views/speech_tab.dart +++ b/lib/modules/meeting/views/speech_tab.dart @@ -59,7 +59,8 @@ class SpeechTab extends GetView { ), ), builder: (BuildContext context) { - return const SpeakerBottomSheet(); + return SpeakerBottomSheet( + item['id'], item['speaker']); }, ); }, @@ -199,7 +200,7 @@ class SpeechTab extends GetView { ), ), builder: (BuildContext context) { - return const SpeakerBottomSheet(); + return Container(); }, ); }, diff --git a/lib/modules/meeting/views/summary_tab.dart b/lib/modules/meeting/views/summary_tab.dart index 073df9df2..47ffc6957 100644 --- a/lib/modules/meeting/views/summary_tab.dart +++ b/lib/modules/meeting/views/summary_tab.dart @@ -5,7 +5,6 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import '../controllers/meeting_details_controller.dart'; -import 'speaker_bottom_sheet.dart'; class SummaryTab extends GetView { const SummaryTab({super.key}); @@ -169,7 +168,7 @@ class SummaryTab extends GetView { ), ), builder: (BuildContext context) { - return const SpeakerBottomSheet(); + return Container(); }, ); },