lxm 1 year ago
parent
commit
7be35a99a9
  1. 29
      lib/data/services/db/database_helper.dart
  2. 53
      lib/data/services/db/sqflite_api.dart
  3. 144
      lib/modules/meeting/controllers/meeting_controller.dart
  4. 41
      lib/modules/meeting/controllers/meeting_details_controller.dart
  5. 3
      lib/modules/meeting/model/meeting_model.dart
  6. 17
      lib/modules/meeting/views/meeting_view.dart
  7. 2
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt

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

@ -1,4 +1,5 @@
import 'dart:async';
import 'package:get_storage/get_storage.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
@ -6,6 +7,7 @@ class DatabaseHelper {
static final DatabaseHelper _instance = DatabaseHelper._internal();
factory DatabaseHelper() => _instance;
static Database? _database;
static String _userId = '';
DatabaseHelper._internal();
@ -16,15 +18,35 @@ class DatabaseHelper {
}
Future<Database> _initDatabase() async {
final GetStorage storage = GetStorage();
Map? userInfo = storage.read("user_info");
if (userInfo != null) {
_userId = userInfo['user']['uid'];
}
String path = join(
await getDatabasesPath(),
'database.db',
_userId.isNotEmpty ? '${_userId}database.db' : 'database.db',
);
return await openDatabase(path, version: 1, onCreate: _createDB);
}
Future<void> _createDB(Database db, int version) async {
final batch = db.batch();
// 用户信息
batch.execute('''
CREATE TABLE IF NOT EXISTS user (
id TEXT PRIMARY KEY,
synctime INTEGER,
)
''');
// 操作记录
batch.execute('''
CREATE TABLE IF NOT EXISTS operationrecord (
id INTEGER PRIMARY KEY,
method TEXT,
data TEXT,
)
''');
// 商务会议助手列表
batch.execute('''
CREATE TABLE IF NOT EXISTS meeting (
@ -34,6 +56,7 @@ class DatabaseHelper {
seconds INTEGER,
filepath TEXT,
audiourl TEXT,
tasktype INTEGER,
creationtime INTEGER
)
''');
@ -81,6 +104,10 @@ class DatabaseHelper {
prompt TEXT
)
''');
batch.insert('user', {
'id': _userId,
'synctime': 0,
});
// 商务会议助手插入默认模版
batch.insert('meetingcategorytemplate', {
'id': 1,

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

@ -1,17 +1,57 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'database_helper.dart';
class SqfliteApi {
static GetStorage storage = GetStorage();
static String userId = '';
// 保存操作记录
static Future saveMeetingOperationRecord(
String method,
dynamic params,
) async {
if (userId.isEmpty) {
Map userInfo = storage.read("user_info");
userId = userInfo['user']['uid'];
}
final db = await DatabaseHelper().database;
String data = '';
if (method == 'records') {
Map records = {
...params,
'id': '${userId}_${params['id']}',
'number': params['id'],
'uid': userId,
};
data = jsonEncode(records);
} else {
data = params;
}
db.insert('operationrecord', {
'method': method,
'data': data,
});
}
// 操作记录列表
static Future getMeetingOperationRecord() async {
final db = await DatabaseHelper().database;
return await db.query('operationrecord');
}
// 获取商务会议助手列表
static Future getMeetingList(int page) async {
static Future getMeetingList(int offset) async {
final db = await DatabaseHelper().database;
List<Map<String, dynamic>> dataList = [];
List meetingList = await db.query(
'meeting',
limit: 10,
offset: (page - 1) * 10,
offset: offset,
orderBy: 'creationtime DESC',
);
for (var element in meetingList) {
@ -22,7 +62,7 @@ class SqfliteApi {
);
dataList.add({
...element,
'tasktype': detailsList.first['tasktype'],
'taskid': detailsList.first['taskid'],
});
}
return dataList;
@ -40,7 +80,7 @@ class SqfliteApi {
);
return <String, dynamic>{
...meetingList.first,
'tasktype': detailsList.first['tasktype'],
'taskid': detailsList.first['taskid'],
'details': detailsList.first
};
}
@ -162,10 +202,9 @@ class SqfliteApi {
}
// 修改商务会议助手标题
static Future editMeetingTitle(id, title) async {
static Future editMeetingTitle(id, Map<String, Object?> meeting) async {
final db = await DatabaseHelper().database;
await db.update('meeting', {'title': title},
where: 'id = ?', whereArgs: [id]);
await db.update('meeting', meeting, where: 'id = ?', whereArgs: [id]);
}
// 删除商务会议助手

144
lib/modules/meeting/controllers/meeting_controller.dart

@ -1,13 +1,16 @@
import 'dart:convert';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:just_audio/just_audio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../../core/utils/logger.dart';
import '../../../core/utils/upload_oss.dart';
import '../../../data/services/db/sqflite_api.dart';
@ -16,18 +19,33 @@ class MeetingController extends GetxController {
final AudioPlayer _audioPlayer = AudioPlayer();
// 是否还有更多数据
bool isLoadingMore = true;
bool isGetMore = true;
bool isLoading = false;
int page = 1;
int addQuantity = 0;
TextEditingController titleController = TextEditingController(); // 录音标题
RxList dataList = [].obs;
List dataListTask = [];
bool isDisposed = false;
final String _appid = '2316081933';
final String _token = 'mcJTh6OelBqU_L78lgGd77Nyrc_-5-En';
final String _cluster = 'volc_auc_meeting_flash';
final String _serviceUrl = 'https://openspeech.bytedance.com/api/v1/auc';
Map<String, String> _headers = {};
@override
void onInit() {
super.onInit();
listController.addListener(_onScroll);
Permission.storage.request();
_headers = {
'Authorization': 'Bearer; $_token',
'Content-Type': 'application/json',
};
_getDataList();
}
@ -36,13 +54,16 @@ class MeetingController extends GetxController {
listController.dispose();
titleController.dispose();
_audioPlayer.dispose();
isDisposed = true;
super.onClose();
}
void _onScroll() {
if (isLoadingMore &&
if (isGetMore &&
!isLoading &&
listController.position.pixels >=
listController.position.maxScrollExtent - 100) {
isLoading = true;
page++;
_getDataList();
}
@ -50,13 +71,16 @@ class MeetingController extends GetxController {
// 请求本地数据
void _getDataList() async {
List list = await SqfliteApi.getMeetingList(page);
List list = await SqfliteApi.getMeetingList((page - 1) * 10 + addQuantity);
dataList.addAll(list);
queryDataListTask(list);
addQuantity = 0;
if (list.length < 10) {
isLoadingMore = false;
isGetMore = false;
} else {
isLoadingMore = true;
isGetMore = true;
}
isLoading = false;
}
void addAudio() async {
@ -79,15 +103,17 @@ class MeetingController extends GetxController {
'seconds': seconds,
'filepath': filePath,
'audiourl': audiourl,
'tasktype': 0,
'creationtime': creationTime,
};
final meetingId = await SqfliteApi.insertMeeting(data);
if (meetingId != null) {
dataList.insert(0, {
'id': meetingId,
'tasktype': 0,
'taskid': '',
...data,
});
addQuantity++;
}
}
EasyLoading.dismiss();
@ -104,7 +130,7 @@ class MeetingController extends GetxController {
// 修改标题
void editMeetingTitle(int id, String title) async {
if (title.isNotEmpty) {
SqfliteApi.editMeetingTitle(id, title);
SqfliteApi.editMeetingTitle(id, {'title': title});
int index = dataList.indexWhere((item) => item['id'] == id);
if (index != -1) {
final dataMap = Map<String, dynamic>.from(dataList[index]);
@ -121,6 +147,7 @@ class MeetingController extends GetxController {
// 删除数据库中的记录
SqfliteApi.deleteMeeting(id);
_deleteFile(filepath);
addQuantity--;
}
// 删除文件
@ -131,4 +158,107 @@ class MeetingController extends GetxController {
await file.delete();
}
}
// 查询转写状态
void queryDataListTask(List list, {bool isAdd = true}) {
for (var element in List.from(list)) {
if (element['tasktype'] == 1) {
if (isAdd) {
dataListTask.add(element);
}
final taskId = element['taskid'];
_fileRecognize(element['id'], taskId);
}
}
}
Future<void> _fileRecognize(int id, String taskId) async {
try {
final startTime = DateTime.now().millisecondsSinceEpoch;
if (isDisposed) return;
var respDic = await _queryTask(taskId);
var code = respDic['resp']['code'] as int;
if (code == 1000) {
await SqfliteApi.editMeetingTitle(id, {'tasktype': 2});
int index = dataList.indexWhere((item) => item['id'] == id);
if (index != -1) {
final dataMap = Map<String, dynamic>.from(dataList[index]);
dataMap['tasktype'] = 2;
dataList[index] = dataMap;
int taskIndex = dataListTask.indexWhere((item) => item['id'] == id);
if (taskIndex != -1) {
dataListTask.removeAt(taskIndex);
}
}
return;
} else if (code < 2000) {
Logger.debug("FileRecognize failed");
return;
}
while (!isDisposed) {
await Future.delayed(const Duration(seconds: 2));
if (isDisposed) return;
respDic = await _queryTask(taskId);
code = respDic['resp']['code'] as int;
if (code == 1000) {
await SqfliteApi.editMeetingTitle(id, {'tasktype': 2});
int index = dataList.indexWhere((item) => item['id'] == id);
if (index != -1) {
final dataMap = Map<String, dynamic>.from(dataList[index]);
dataMap['tasktype'] = 2;
dataList[index] = dataMap;
int taskIndex = dataListTask.indexWhere((item) => item['id'] == id);
if (taskIndex != -1) {
dataListTask.removeAt(taskIndex);
}
}
return;
} else if (code < 2000) {
Logger.debug("FileRecognize failed");
return;
}
final currentTime = DateTime.now().millisecondsSinceEpoch;
if (currentTime - startTime > 300 * 1000) {
Logger.debug('wait time exceeds 300s');
return;
}
}
} catch (e) {
Logger.error('Error occurred: ${e.toString()}');
}
}
Future<Map<String, dynamic>> _queryTask(String taskId) async {
final queryBody = jsonEncode({
"appid": _appid,
"token": _token,
"id": taskId,
"cluster": _cluster,
});
try {
final response = await http.post(
Uri.parse('$_serviceUrl/query'),
headers: _headers,
body: queryBody,
);
if (response.statusCode != 200) {
throw Exception('Query failed with status: ${response.statusCode}');
}
final respDic = jsonDecode(response.body);
return respDic;
} catch (e) {
Logger.error('Query error: ${e.toString()}');
rethrow;
}
}
}

41
lib/modules/meeting/controllers/meeting_details_controller.dart

@ -92,17 +92,40 @@ class MeetingDetailsController extends GetxController
super.onClose();
}
void popInvokedWithResult(didPop, result) {
void popInvokedWithResult(didPop, result) async {
if (meetingData.value.tasktype != meetingDetails.value.tasktype) {
await SqfliteApi.editMeetingTitle(
id, {'tasktype': meetingDetails.value.tasktype});
int index =
_meetingController.dataList.indexWhere((item) => item['id'] == id);
if (index != -1) {
final dataMap =
Map<String, dynamic>.from(_meetingController.dataList[index]);
dataMap['tasktype'] = meetingDetails.value.tasktype;
if (dataMap['taskid'].isEmpty) {
dataMap['taskid'] = meetingDetails.value.taskid;
}
_meetingController.dataList[index] = dataMap;
int taskIndex = _meetingController.dataListTask
.indexWhere((item) => item['id'] == id);
if (taskIndex != -1) {
if (meetingDetails.value.tasktype == 1) {
_meetingController.dataListTask[taskIndex] = dataMap;
} else {
_meetingController.dataListTask.removeAt(taskIndex);
}
} else {
if (meetingDetails.value.tasktype == 1) {
_meetingController.dataListTask.add(dataMap);
}
}
}
}
_meetingController.isDisposed = false;
_meetingController.queryDataListTask(
_meetingController.dataListTask,
isAdd: false,
);
}
void _getAgent() async {
@ -327,13 +350,25 @@ class MeetingDetailsController extends GetxController
try {
final startTime = DateTime.now().millisecondsSinceEpoch;
if (_isDisposed) return;
var respDic = await _queryTask(taskId);
var code = respDic['resp']['code'] as int;
if (code == 1000) {
await _addTextList(respDic['resp']['utterances']);
return;
} else if (code < 2000) {
Logger.debug("FileRecognize failed");
return;
}
while (!_isDisposed) {
await Future.delayed(const Duration(seconds: 2));
if (_isDisposed) return;
final respDic = await _queryTask(taskId);
final code = respDic['resp']['code'] as int;
respDic = await _queryTask(taskId);
code = respDic['resp']['code'] as int;
if (code == 1000) {
await _addTextList(respDic['resp']['utterances']);

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

@ -7,6 +7,7 @@ class MeetingModel {
late int seconds;
late String filepath;
late String audiourl;
late String taskid;
late int tasktype;
late int creationtime;
@ -17,6 +18,7 @@ class MeetingModel {
..seconds = map["seconds"]
..filepath = map["filepath"]
..audiourl = map["audiourl"]
..taskid = map["taskid"]
..tasktype = map["tasktype"]
..creationtime = map["creationtime"];
@ -28,6 +30,7 @@ class MeetingModel {
data['seconds'] = seconds;
data['filepath'] = filepath;
data['audiourl'] = audiourl;
data['taskid'] = taskid;
data['tasktype'] = tasktype;
data['creationtime'] = creationtime;
return data;

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

@ -56,6 +56,7 @@ class MeetingView extends GetView<MeetingController> {
Widget _buildBody(BuildContext context, bool isDarkMode) {
return Obx(
() => ListView.builder(
controller: controller.listController,
itemCount: controller.dataList.length,
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 5.w),
itemBuilder: (BuildContext context, int index) {
@ -69,6 +70,7 @@ class MeetingView extends GetView<MeetingController> {
Widget _item(BuildContext context, bool isDarkMode, MeetingModel data) {
return GestureDetector(
onTap: () {
controller.isDisposed = true;
Get.toNamed(Routes.meetingDetails, arguments: {
'id': data.id,
});
@ -158,11 +160,22 @@ class MeetingView extends GetView<MeetingController> {
),
),
),
if (data.tasktype == 1 || data.tasktype == 2)
if (data.tasktype == 1)
Padding(
padding: EdgeInsets.only(top: 5.w),
child: Text(
data.tasktype == 1 ? '正在生成...' : '已生成',
'正在生成...',
style: TextStyle(
color: Colors.blue,
fontSize: 10.sp,
),
),
),
if (data.tasktype == 2)
Padding(
padding: EdgeInsets.only(top: 5.w),
child: Text(
'已生成',
style: TextStyle(
color: Colors.blue,
fontSize: 10.sp,

2
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt

@ -380,7 +380,7 @@ class AzureTtsHelper(private val context: Context) : ITtsService {
}
lastSpeakTime = currentTime
val currentText = cleanTextForTTS(streamBuffer.toString())
val currentText = streamBuffer.toString() //cleanTextForTTS(streamBuffer.toString())
// 定义标点符号列表
val punctuationMarks = listOf('.', '。', '!', '!', '?', '?', ';', ';', ',', ',', ':', ':', '\n')

Loading…
Cancel
Save