Browse Source

会议助手添加会议总结

weicu
tanlongsheng 2 years ago
parent
commit
a70817745c
  1. 6
      lib/data/services/ai_service.dart
  2. 6
      lib/data/services/db/database_helper.dart
  3. 12
      lib/data/services/db/sqflite_api.dart
  4. 8
      lib/data/services/deapsound_ai_service.dart
  5. 8
      lib/data/services/kouzi_ai_service.dart
  6. 1
      lib/data/services/volcano_ai_service.dart
  7. 12
      lib/main.dart
  8. 31
      lib/modules/meeting/controllers/meeting_edit_controller.dart
  9. 69
      lib/modules/meeting/controllers/meeting_record_controller.dart
  10. 5
      lib/modules/meeting/model/meeting_details_model.dart
  11. 9
      lib/modules/meeting/model/meeting_model.dart
  12. 4
      lib/modules/meeting/views/meeting_details_view.dart
  13. 12
      lib/modules/meeting/views/meeting_edit_view.dart
  14. 2
      lib/modules/meeting/views/meeting_record_view.dart
  15. 110
      lib/modules/meeting/views/meeting_view.dart
  16. 2
      pubspec.yaml

6
lib/data/services/ai_service.dart

@ -1,5 +1,11 @@
/// AI回复服务接口
abstract class AiService {
/// 非流式输出方法
Future<String> sendMessage({
required List<Map<String, String>> messages,
required String systemPrompt,
});
/// 流式输出方法
Stream<String> sendMessageStream({
required List<Map<String, String>> messages,

6
lib/data/services/db/database_helper.dart

@ -32,6 +32,9 @@ class DatabaseHelper {
title TEXT,
content TEXT,
seconds INTEGER,
address TEXT,
longitude TEXT,
latitude TEXT,
creationtime INTEGER
)
''');
@ -41,7 +44,8 @@ class DatabaseHelper {
id INTEGER PRIMARY KEY,
meetingid INTEGER,
audio TEXT,
content TEXT
content TEXT,
aisummary TEXT
)
''');
await batch.commit();

12
lib/data/services/db/sqflite_api.dart

@ -41,12 +41,16 @@ class SqfliteApi {
'title': meeting['title'],
'content': meeting['firstContent'],
'seconds': meeting['seconds'],
'address': meeting['address'],
'longitude': meeting['longitude'],
'latitude': meeting['latitude'],
'creationtime': meeting['creationTime'],
});
await txn.insert('meetingdetails', {
'meetingid': id,
'audio': meeting['audio'],
'content': meeting['content'],
'aisummary': meeting['aisummary'],
});
});
return id;
@ -73,13 +77,19 @@ class SqfliteApi {
'title': meeting['title'],
'content': meeting['firstContent'],
'seconds': meeting['seconds'],
'address': meeting['address'],
'longitude': meeting['longitude'],
'latitude': meeting['latitude'],
},
where: 'id = ?',
whereArgs: [id],
);
await txn.update(
'meetingdetails',
{'content': meeting['content']},
{
'content': meeting['content'],
'aisummary': meeting['aisummary'],
},
where: 'meetingid = ?',
whereArgs: [id],
);

8
lib/data/services/deapsound_ai_service.dart

@ -14,6 +14,14 @@ class DeapsoundAIService implements AiService {
apiKey = storage.read('logintoken');
}
@override
Future<String> sendMessage({
required List<Map<String, String>> messages,
required String systemPrompt,
}) async {
return '';
}
@override
Stream<String> sendMessageStream({
required List<Map<String, String>> messages,

8
lib/data/services/kouzi_ai_service.dart

@ -10,6 +10,14 @@ class KouziAIService implements AiService {
'pat_QGDh6UGm3PaeL9GRS95JxrJ9aoU92ehMLQQ2NCre1TeBC5YeTcjpDmlz3efsJ8lh';
final String modelId = '7481292724306657316';
@override
Future<String> sendMessage({
required List<Map<String, String>> messages,
required String systemPrompt,
}) async {
return '';
}
@override
Stream<String> sendMessageStream({
required List<Map<String, String>> messages,

1
lib/data/services/volcano_ai_service.dart

@ -32,6 +32,7 @@ class VolcanoAIService implements AiService {
}
// 非流式输出方法
@override
Future<String> sendMessage({
required List<Map<String, String>> messages,
required String systemPrompt,

12
lib/main.dart

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
@ -82,6 +83,17 @@ class MainApp extends StatelessWidget {
locale: AppTranslations.deviceLocale(),
fallbackLocale: AppTranslations.fallbackLocale,
supportedLocales: AppTranslations.supportedLocales,
builder: EasyLoading.init(
builder: (BuildContext context, Widget? widget) {
EasyLoading.instance.userInteractions = false;
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.noScaling,
),
child: widget!,
);
},
),
);
},
);

31
lib/modules/meeting/controllers/meeting_edit_controller.dart

@ -1,13 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:just_audio/just_audio.dart';
import '../../../data/services/ai_service.dart';
import '../../../data/services/db/sqflite_api.dart';
import '../../../data/services/volcano_ai_service.dart';
import 'meeting_controller.dart';
import 'meeting_details_controller.dart';
class MeetingEditController extends GetxController
with GetSingleTickerProviderStateMixin {
late AiService _aiService;
final MeetingController meetingController = Get.find<MeetingController>();
final MeetingDetailsController meetingDetailsController =
Get.find<MeetingDetailsController>();
@ -21,6 +26,10 @@ class MeetingEditController extends GetxController
int creationTime = 0;
RxString address = ''.obs;
String _longitude = '';
String _latitude = '';
int id = 0;
RxBool isPlay = false.obs;
@ -51,6 +60,9 @@ class MeetingEditController extends GetxController
text: meetingDetailsController.dataDetails.value.content,
);
creationTime = meetingDetailsController.meetingData.value.creationtime;
address.value = meetingDetailsController.meetingData.value.address;
_longitude = meetingDetailsController.meetingData.value.longitude;
_latitude = meetingDetailsController.meetingData.value.latitude;
audioPlayer.setAudioSource(
AudioSource.file(meetingDetailsController.dataDetails.value.audio));
audioPlayer.playerStateStream.listen((state) async {
@ -76,6 +88,15 @@ class MeetingEditController extends GetxController
}
void confirm() async {
EasyLoading.show();
_aiService = Get.find<VolcanoAIService>();
String aiSummary = await _aiService.sendMessage(
systemPrompt: '',
messages: [
{'role': 'user', 'content': contentController.text},
{'role': 'user', 'content': '帮我总结一下这次会议'},
],
);
String content = contentController.text;
String firstContent =
content.length >= 30 ? content.substring(0, 30) : content;
@ -87,6 +108,10 @@ class MeetingEditController extends GetxController
'firstContent': firstContent.replaceAll("\n", ""),
'seconds': meetingDetailsController.seconds,
'content': contentController.text,
'aisummary': aiSummary,
'address': address.value,
'longitude': _longitude,
'latitude': _latitude,
};
final result = await SqfliteApi.editMeeting(id, data);
if (result != null) {
@ -99,14 +124,20 @@ class MeetingEditController extends GetxController
dataMap['title'] = title;
dataMap['content'] = firstContent.replaceAll("\n", "");
dataMap['seconds'] = meetingDetailsController.seconds;
dataMap['address'] = address.value;
meetingController.dataList[index] = dataMap;
}
meetingDetailsController.meetingData.value.title = title;
meetingDetailsController.meetingData.value.address = address.value;
meetingDetailsController.meetingData.value.longitude = _longitude;
meetingDetailsController.meetingData.value.latitude = _latitude;
meetingDetailsController.dataDetails.value.content =
contentController.text;
meetingDetailsController.dataDetails.value.aisummary = aiSummary;
meetingDetailsController.meetingData.refresh();
meetingDetailsController.dataDetails.refresh();
Get.back();
}
EasyLoading.dismiss();
}
}

69
lib/modules/meeting/controllers/meeting_record_controller.dart

@ -3,18 +3,23 @@ import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../../core/utils/logger.dart';
import '../../../data/services/ai_service.dart';
import '../../../data/services/asr_service.dart';
import '../../../data/services/db/sqflite_api.dart';
import '../../../data/services/speech_impl/xunfei_asr_service.dart';
import '../../../data/services/volcano_ai_service.dart';
import 'meeting_controller.dart';
class MeetingRecordController extends GetxController
with GetSingleTickerProviderStateMixin {
late AiService _aiService;
final MeetingController meetingController = Get.find<MeetingController>();
late AnimationController lottieController;
@ -28,6 +33,10 @@ class MeetingRecordController extends GetxController
String title = '';
int creationTime = 0;
RxString address = '深圳市高新技术产业园区W2-A座'.obs;
String _longitude = '';
String _latitude = '';
RxString intermediateContent = ''.obs;
RxString finalContent = ''.obs;
@ -75,7 +84,7 @@ class MeetingRecordController extends GetxController
}
// 暂停
void _pause() async {
Future<void> _pause() async {
await _stopRecorder();
lottieController.stop();
isRecording.value = false;
@ -150,7 +159,9 @@ class MeetingRecordController extends GetxController
// 完成
void complete() async {
if (await Permission.storage.request().isGranted) {
_save();
EasyLoading.show();
await _pause();
_aiSummary();
} else {
Get.snackbar(
'error'.tr, // 错误
@ -162,7 +173,19 @@ class MeetingRecordController extends GetxController
}
}
void _save() async {
void _aiSummary() async {
_aiService = Get.find<VolcanoAIService>();
String aiSummary = await _aiService.sendMessage(
systemPrompt: '',
messages: [
{'role': 'user', 'content': finalContent.value},
{'role': 'user', 'content': '帮我总结一下这次会议'},
],
);
_save(aiSummary);
}
void _save(String aiSummary) async {
String firstContent = finalContent.value.length >= 30
? finalContent.value.substring(0, 30)
: finalContent.value;
@ -176,26 +199,32 @@ class MeetingRecordController extends GetxController
Uint8List wavData = Uint8List.fromList([...wavHeader, ...audio]);
String audiopPath =
await _saveAudioToFile(wavData, 'meeting_$creationTime.wav');
if (audiopPath.isEmpty) return;
Map<String, Object?> data = {
'title': title,
'firstContent': firstContent.replaceAll("\n", ""),
'seconds': seconds.value,
'creationTime': creationTime,
'audio': audiopPath,
'content': finalContent.value,
};
final result = await SqfliteApi.insertMeeting(data);
if (result != null) {
meetingController.dataList.insert(0, {
'id': result,
if (audiopPath.isNotEmpty) {
Map<String, Object?> data = {
'title': title,
'content': firstContent,
'firstContent': firstContent.replaceAll("\n", ""),
'seconds': seconds.value,
'creationtime': creationTime,
});
Get.back();
'creationTime': creationTime,
'audio': audiopPath,
'content': finalContent.value,
'aisummary': aiSummary,
'address': address.value,
'longitude': _longitude,
'latitude': _latitude,
};
final result = await SqfliteApi.insertMeeting(data);
if (result != null) {
meetingController.dataList.insert(0, {
'id': result,
'title': title,
'content': firstContent,
'seconds': seconds.value,
'creationtime': creationTime,
});
Get.back();
}
}
EasyLoading.dismiss();
}
Future<String> _saveAudioToFile(Uint8List audioData, String fileName) async {

5
lib/modules/meeting/model/meeting_details_model.dart

@ -5,13 +5,15 @@ class MeetingDetailsModel {
late int meetingid;
late String audio;
late String content;
late String aisummary;
factory MeetingDetailsModel.fromJson(Map<String, dynamic> map) =>
MeetingDetailsModel()
..id = map["id"]
..meetingid = map["meetingid"]
..audio = map["audio"]
..content = map["content"];
..content = map["content"]
..aisummary = map["aisummary"];
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
@ -19,6 +21,7 @@ class MeetingDetailsModel {
data['meetingid'] = meetingid;
data['audio'] = audio;
data['content'] = content;
data['aisummary'] = aisummary;
return data;
}
}

9
lib/modules/meeting/model/meeting_model.dart

@ -5,6 +5,9 @@ class MeetingModel {
late String title;
late String content;
late int seconds;
late String address;
late String longitude;
late String latitude;
late int creationtime;
factory MeetingModel.fromJson(Map<String, dynamic> map) => MeetingModel()
@ -12,6 +15,9 @@ class MeetingModel {
..title = map["title"]
..content = map["content"]
..seconds = map["seconds"]
..address = map["address"]
..longitude = map["longitude"]
..latitude = map["latitude"]
..creationtime = map["creationtime"];
Map<String, dynamic> toJson() {
@ -20,6 +26,9 @@ class MeetingModel {
data['title'] = title;
data['content'] = content;
data['seconds'] = seconds;
data['address'] = address;
data['longitude'] = longitude;
data['latitude'] = latitude;
data['creationtime'] = creationtime;
return data;
}

4
lib/modules/meeting/views/meeting_details_view.dart

@ -90,7 +90,7 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
),
Text(
'深圳市高新技术产业园区W2-A座',
controller.meetingData.value.address,
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 14.sp,
@ -268,7 +268,7 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
children: [
SingleChildScrollView(
child: Text(
controller.dataDetails.value.content,
controller.dataDetails.value.aisummary,
),
),
SingleChildScrollView(

12
lib/modules/meeting/views/meeting_edit_view.dart

@ -119,11 +119,13 @@ class MeetingEditView extends GetView<MeetingEditController> {
size: 16.w,
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
),
Text(
'深圳市高新技术产业园区W2-A座',
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 14.sp,
Obx(
() => Text(
controller.address.value,
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 14.sp,
),
),
),
],

2
lib/modules/meeting/views/meeting_record_view.dart

@ -131,7 +131,7 @@ class MeetingRecordView extends GetView<MeetingRecordController> {
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
),
Text(
'深圳市高新技术产业园区W2-A座',
controller.address.value,
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 14.sp,

110
lib/modules/meeting/views/meeting_view.dart

@ -161,13 +161,12 @@ class MeetingView extends GetView<MeetingController> {
),
],
),
child: Row(
child: Column(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
Row(
children: [
Expanded(
child: Text(
data.title,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
@ -175,44 +174,77 @@ class MeetingView extends GetView<MeetingController> {
),
overflow: TextOverflow.ellipsis,
),
Text(
data.content.isNotEmpty ? data.content : '无内容',
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54,
fontSize: 12.sp,
),
overflow: TextOverflow.ellipsis,
),
10.horizontalSpace,
Text(
DateUtil.formatDateMs(
data.creationtime,
format: DateFormats.y_mo_d_h_m,
),
Text(
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54,
fontSize: 12.sp,
),
),
],
),
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
data.content.isNotEmpty ? data.content : '无内容',
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54,
fontSize: 12.sp,
),
overflow: TextOverflow.ellipsis,
),
Row(
children: [
Icon(
Icons.location_on,
size: 14.w,
color: isDarkMode ? Colors.white70 : Colors.black54,
),
Expanded(
child: Text(
data.address,
style: TextStyle(
color: isDarkMode
? Colors.white70
: Colors.black54,
fontSize: 12.sp,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
],
),
),
Container(
width: 76.w,
height: 28.w,
margin: EdgeInsets.only(left: 10.w),
alignment: Alignment.center,
decoration: BoxDecoration(
color: isDarkMode ? AppColors.primary : Colors.blue,
borderRadius: BorderRadius.circular(28.w),
),
child: Text(
DateUtil.formatDateMs(
data.creationtime,
format: DateFormats.y_mo_d_h_m,
),
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54,
fontSize: 12.sp,
data.seconds * 1000,
format: 'HH:mm:ss',
isUtc: true,
),
style: TextStyle(color: Colors.white, fontSize: 14.sp),
),
],
),
),
Container(
width: 76.w,
height: 28.w,
margin: EdgeInsets.only(left: 10.w),
alignment: Alignment.center,
decoration: BoxDecoration(
color: isDarkMode ? AppColors.primary : Colors.blue,
borderRadius: BorderRadius.circular(28.w),
),
child: Text(
DateUtil.formatDateMs(
data.seconds * 1000,
format: 'HH:mm:ss',
isUtc: true,
),
style: TextStyle(color: Colors.white, fontSize: 14.sp),
),
],
),
],
),

2
pubspec.yaml

@ -31,7 +31,7 @@ dependencies:
flutter_blue_plus: ^1.31.16
just_audio: ^0.9.36
webview_flutter: ^4.10.0
flutter_easyloading: ^3.0.5
window_size:
git:

Loading…
Cancel
Save