Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

newdev_shunjiawei
lxm 12 months ago
parent
commit
26fa18f24a
  1. 202
      lib/data/services/db/database_helper.dart
  2. 41
      lib/data/services/db/sqflite_api.dart
  3. 43
      lib/data/services/meeting/meeting_task_service.dart
  4. 12
      lib/data/services/meeting/meeting_upload_service.dart
  5. 9
      lib/data/services/network/api.dart
  6. 26
      lib/modules/agent/controllers/agent_controller.dart
  7. 9
      lib/modules/login/controllers/login_controller.dart
  8. 9
      lib/modules/meeting/controllers/meeting_details_controller.dart
  9. 11
      lib/modules/meeting/views/bottomSheet/generate_bottom_sheet.dart
  10. 54
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/CustomSseClientTransport.kt
  11. 65
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt
  12. 39
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift

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

@ -104,6 +104,7 @@ class DatabaseHelper {
icon TEXT, icon TEXT,
tag TEXT, tag TEXT,
desc TEXT, desc TEXT,
outline TEXT,
prompt TEXT prompt TEXT
) )
'''); ''');
@ -112,207 +113,6 @@ class DatabaseHelper {
'synctime': 0, 'synctime': 0,
'privatecloud': 0, 'privatecloud': 0,
}); });
// 商务会议助手插入默认模版
batch.insert('meetingcategorytemplate', {
'id': 1,
'name': '通用',
});
batch.insert('meetingcategorytemplate', {
'id': 2,
'name': '会议',
});
batch.insert('meetingcategorytemplate', {
'id': 3,
'name': '演讲',
});
batch.insert('meetingcategorytemplate', {
'id': 4,
'name': '自定义',
});
batch.insert('meetingtemplate', {
'categoryid': 1,
'name': '会议纪要',
'icon': 'auto_stories',
'tag': '主题, 代办事项',
'desc': '旨在概述和回顾项目内容,重点捕捉所提话题的精髓和关键点',
'prompt': '提示词',
});
batch.insert('meetingtemplate', {
'categoryid': 1,
'name': 'Autopilot',
'icon': 'auto_graph',
'tag': '自适应结构, 全场景适配',
'desc': '旨在概述和回顾项目内容,重点捕捉所提话题的精髓和关键点',
'prompt': '提示词',
});
batch.insert('meetingtemplate', {
'categoryid': 1,
'name': '推理Autopilot',
'icon': 'add_to_drive_outlined',
'tag': '推理模型, 自动结构化',
'desc': '旨在概述和回顾项目内容,重点捕捉所提话题的精髓和关键点',
'prompt': '提示词',
});
batch.insert('meetingtemplate', {
'categoryid': 1,
'name': '通话',
'icon': 'perm_phone_msg',
'tag': '主题, 代办事项',
'desc': '旨在概述和回顾项目内容,重点捕捉所提话题的精髓和关键点',
'prompt': '提示词',
});
batch.insert('meetingtemplate', {
'categoryid': 1,
'name': '项目计划',
'icon': 'assignment',
'tag': '目标, 里程碑',
'desc': '用于规划项目阶段和关键节点,跟踪项目执行进度',
'prompt': '请按时间顺序整理项目阶段'
});
batch.insert('meetingtemplate', {
'categoryid': 1,
'name': '头脑风暴',
'icon': 'lightbulb',
'tag': '创意, 发散思维',
'desc': '记录创意发散过程,结构化整理核心创意点',
'prompt': '请将讨论内容按创新性排序'
});
batch.insert('meetingtemplate', {
'categoryid': 1,
'name': '日常记录',
'icon': 'note',
'tag': '日志, 每日总结',
'desc': '自动化生成结构化日报,自动提取工作亮点与待办事项',
'prompt': '请按优先级排列今日工作事项'
});
batch.insert('meetingtemplate', {
'categoryid': 2,
'name': '咨询',
'icon': 'auto_stories',
'tag': '主题, 代办事项',
'desc': '旨在概述和回顾项目内容,重点捕捉所提话题的精髓和关键点',
'prompt': '提示词',
});
batch.insert('meetingtemplate', {
'categoryid': 2,
'name': '讨论',
'icon': 'auto_graph',
'tag': '自适应结构, 全场景适配',
'desc': '旨在概述和回顾项目内容,重点捕捉所提话题的精髓和关键点',
'prompt': '提示词',
});
batch.insert('meetingtemplate', {
'categoryid': 2,
'name': '汇报',
'icon': 'add_to_drive_outlined',
'tag': '推理模型, 自动结构化',
'desc': '旨在概述和回顾项目内容,重点捕捉所提话题的精髓和关键点',
'prompt': '提示词',
});
batch.insert('meetingtemplate', {
'categoryid': 2,
'name': '任务分配',
'icon': 'perm_phone_msg',
'tag': '主题, 代办事项',
'desc': '旨在概述和回顾项目内容,重点捕捉所提话题的精髓和关键点',
'prompt': '提示词',
});
batch.insert('meetingtemplate', {
'categoryid': 2,
'name': '评审会议',
'icon': 'rate_review',
'tag': '质量评估, 改进建议',
'desc': '用于评估项目阶段成果,生成改进建议和风险评估',
'prompt': '请按优先级列出需要改进的环节'
});
batch.insert('meetingtemplate', {
'categoryid': 2,
'name': '周例会',
'icon': 'event_repeat',
'tag': '进度汇报, 计划制定',
'desc': '自动生成每周工作进展摘要,识别延期风险',
'prompt': '请按部门分类整理工作进度'
});
batch.insert('meetingtemplate', {
'categoryid': 2,
'name': '客户洽谈',
'icon': 'handshake',
'tag': '需求沟通, 合同条款',
'desc': '记录客户核心诉求和合作关键条款',
'prompt': '请按优先级排列客户需求'
});
batch.insert('meetingtemplate', {
'categoryid': 2,
'name': '项目复盘',
'icon': 'restart_alt',
'tag': '经验总结, 问题分析',
'desc': '自动生成项目得失分析报告',
'prompt': '请按影响程度排序待改进项'
});
batch.insert('meetingtemplate', {
'categoryid': 3,
'name': '课堂笔记',
'icon': 'auto_stories',
'tag': '主题, 代办事项',
'desc': '旨在概述和回顾项目内容,重点捕捉所提话题的精髓和关键点',
'prompt': '提示词',
});
batch.insert('meetingtemplate', {
'categoryid': 3,
'name': '讲座',
'icon': 'auto_graph',
'tag': '自适应结构, 全场景适配',
'desc': '旨在概述和回顾项目内容,重点捕捉所提话题的精髓和关键点',
'prompt': '提示词',
});
batch.insert('meetingtemplate', {
'categoryid': 3,
'name': '培训',
'icon': 'add_to_drive_outlined',
'tag': '推理模型, 自动结构化',
'desc': '旨在概述和回顾项目内容,重点捕捉所提话题的精髓和关键点',
'prompt': '提示词',
});
batch.insert('meetingtemplate', {
'categoryid': 3,
'name': 'Sermon',
'icon': 'perm_phone_msg',
'tag': '主题, 代办事项',
'desc': '旨在概述和回顾项目内容,重点捕捉所提话题的精髓和关键点',
'prompt': '提示词',
});
batch.insert('meetingtemplate', {
'categoryid': 3,
'name': '产品发布会',
'icon': 'rocket_launch',
'tag': '功能展示, 市场定位',
'desc': '结构化整理产品核心卖点和市场差异化特征',
'prompt': '请按用户场景分类产品功能'
});
batch.insert('meetingtemplate', {
'categoryid': 3,
'name': '技术分享',
'icon': 'code',
'tag': '知识传递, 最佳实践',
'desc': '自动提取技术要点和可复用代码片段',
'prompt': '请按技术难度分级整理内容'
});
batch.insert('meetingtemplate', {
'categoryid': 3,
'name': '投资者路演',
'icon': 'bar_chart',
'tag': '财务数据, 商业模式',
'desc': '自动提取核心财务指标和增长预期',
'prompt': '请按投资关注度整理内容'
});
batch.insert('meetingtemplate', {
'categoryid': 3,
'name': '学术报告',
'icon': 'school',
'tag': '研究成果, 数据分析',
'desc': '结构化整理实验数据和学术论点',
'prompt': '请按研究重要性排序结论'
});
await batch.commit(); await batch.commit();
} }
} }

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

@ -210,6 +210,47 @@ class SqfliteApi {
await db.insert('meetingai', meeting); await db.insert('meetingai', meeting);
} }
// 初始化模版
static Future initMeetingTemplate(List data) async {
final db = await DatabaseHelper().database;
await db.transaction((txn) async {
// 清空旧数据
await txn.delete('meetingtemplate');
await txn.delete('meetingcategorytemplate');
List categoryList = [];
for (var i = 0; i < data.length; i++) {
Map item = data[i];
int index = categoryList.indexOf(item['ttype']);
int categoryId = categoryList.length;
if (index == -1) {
categoryList.add(item['ttype']);
categoryId++;
await txn.insert('meetingcategorytemplate', {
'id': categoryId,
'name': item['ttype'],
});
}
await txn.insert('meetingtemplate', {
'categoryid': categoryId,
'name': item['title'],
'icon': item['icon'],
'tag': item['tags'],
'desc': item['description'],
'outline': item['outline'],
'prompt': item['template'],
});
}
if (!categoryList.contains('自定义')) {
await txn.insert('meetingcategorytemplate', {
'id': categoryList.length + 1,
'name': '自定义',
});
}
});
}
// 获取商务会议助手所有分类模版列表 // 获取商务会议助手所有分类模版列表
static Future getMeetingCategoryTemplateList() async { static Future getMeetingCategoryTemplateList() async {
final db = await DatabaseHelper().database; final db = await DatabaseHelper().database;

43
lib/data/services/meeting/meeting_task_service.dart

@ -6,7 +6,6 @@ import 'package:http/http.dart' as http;
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import '../../../core/utils/synchrodata.dart'; import '../../../core/utils/synchrodata.dart';
import '../../models/appconfig.dart';
import '../db/sqflite_api.dart'; import '../db/sqflite_api.dart';
import '../deapsound_ai_service.dart'; import '../deapsound_ai_service.dart';
import 'meeting_upload_service.dart'; import 'meeting_upload_service.dart';
@ -21,7 +20,6 @@ class MeetingTaskService extends GetxService {
bool _isTask = false; bool _isTask = false;
String _systemPrompt = '';
String _userId = ''; String _userId = '';
final String _appid = '2316081933'; final String _appid = '2316081933';
@ -50,7 +48,6 @@ class MeetingTaskService extends GetxService {
void _getAgent() async { void _getAgent() async {
Map userInfo = _storage.read("user_info"); Map userInfo = _storage.read("user_info");
_userId = userInfo['user']['uid']; _userId = userInfo['user']['uid'];
_systemPrompt = AppConfig.agentsystemPrompt("meet_assistant") ?? '';
} }
void _getTaskList() { void _getTaskList() {
@ -59,7 +56,8 @@ class MeetingTaskService extends GetxService {
_executeTask(); _executeTask();
} }
Future<void> addTask(int id, String audiourl, String taskid) async { Future<void> addTask(int id, String audiourl, String taskid, String language,
String prompt, String outline) async {
if (audiourl.isEmpty) { if (audiourl.isEmpty) {
Map uploadData = _uploadService.uploadList.firstWhere( Map uploadData = _uploadService.uploadList.firstWhere(
(element) => element['id'] == id, (element) => element['id'] == id,
@ -68,24 +66,31 @@ class MeetingTaskService extends GetxService {
if (uploadData.isNotEmpty) { if (uploadData.isNotEmpty) {
if (uploadData['audiourl'].isEmpty) { if (uploadData['audiourl'].isEmpty) {
uploadData['isTask'] = true; uploadData['isTask'] = true;
uploadData['language'] = language;
uploadData['prompt'] = prompt;
uploadData['outline'] = outline;
} else { } else {
await addTaskList(id, uploadData['audiourl'], taskid); await addTaskList(
id, uploadData['audiourl'], taskid, language, prompt, outline);
} }
} }
} else { } else {
await addTaskList(id, audiourl, taskid); await addTaskList(id, audiourl, taskid, language, prompt, outline);
} }
} }
Future<void> addTaskList(int id, String audiourl, String taskId) async { Future<void> addTaskList(int id, String audiourl, String taskId,
String language, String prompt, String outline) async {
if (taskId.isEmpty) { if (taskId.isEmpty) {
taskId = await _submitTask(audiourl); taskId = await _submitTask(audiourl, language);
} }
await SqfliteApi.editMeeting(id, {'tasktype': 1, 'taskid': taskId}); await SqfliteApi.editMeeting(id, {'tasktype': 1, 'taskid': taskId});
_taskList.add({ _taskList.add({
'id': id, 'id': id,
'taskid': taskId, 'taskid': taskId,
'tasktype': 1, 'tasktype': 1,
'prompt': prompt,
'outline': outline,
}); });
taskFinishList.add({ taskFinishList.add({
'id': id, 'id': id,
@ -126,7 +131,7 @@ class MeetingTaskService extends GetxService {
} }
} }
Future<String> _submitTask(String audiourl) async { Future<String> _submitTask(String audiourl, String language) async {
String format = _getFileType(audiourl); String format = _getFileType(audiourl);
final requestBody = jsonEncode({ final requestBody = jsonEncode({
@ -143,6 +148,7 @@ class MeetingTaskService extends GetxService {
"url": audiourl, "url": audiourl,
}, },
"additions": { "additions": {
"language": language,
"with_speaker_info": "True", "with_speaker_info": "True",
} }
}); });
@ -206,7 +212,12 @@ class MeetingTaskService extends GetxService {
var code = respDic['resp']['code'] as int; var code = respDic['resp']['code'] as int;
if (code == 1000) { if (code == 1000) {
await _addTextList(item['id'], respDic['resp']['utterances']); await _addTextList(
item['id'],
respDic['resp']['utterances'],
item['prompt'],
item['outline'],
);
return; return;
} else if (code < 2000) { } else if (code < 2000) {
Logger.debug("FileRecognize failed"); Logger.debug("FileRecognize failed");
@ -217,7 +228,8 @@ class MeetingTaskService extends GetxService {
} }
} }
Future<void> _addTextList(int id, List utterances) async { Future<void> _addTextList(
int id, List utterances, String prompt, String outline) async {
String text = ''; String text = '';
String personnel = ''; String personnel = '';
List<Map<String, Object?>> list = []; List<Map<String, Object?>> list = [];
@ -267,10 +279,10 @@ class MeetingTaskService extends GetxService {
_storage.write("meeting_task_list", _taskList); _storage.write("meeting_task_list", _taskList);
_storage.write("meeting_task_finish_list", taskFinishList); _storage.write("meeting_task_finish_list", taskFinishList);
} }
aiServiceMessage(id, text); aiServiceMessage(id, text, prompt, outline);
} }
void aiServiceMessage(int id, String text) { void aiServiceMessage(int id, String text, String prompt, String outline) {
int awaitQuantity = 0; int awaitQuantity = 0;
String summaryText = ''; String summaryText = '';
String overviewText = ''; String overviewText = '';
@ -278,8 +290,7 @@ class MeetingTaskService extends GetxService {
messages: [ messages: [
{ {
'role': 'system', 'role': 'system',
'content': 'content': prompt,
'您将扮演一名会议总结专家,拥有超强的信息归纳与细分能力。不仅能高效处理各类通用场景会议,精准提炼会议核心内容,为后续思维导图生成奠定坚实基础。若涉及人员,人员名称保留[]。并根据内容只输出对应语言。',
}, },
{'role': 'user', 'content': text}, {'role': 'user', 'content': text},
], ],
@ -294,7 +305,7 @@ class MeetingTaskService extends GetxService {
messages: [ messages: [
{ {
'role': 'system', 'role': 'system',
'content': '您将扮演一名会议事件归纳专家,能准确抓住会议中事件的关键点,并每个事件只需一句话精简总结。', 'content': outline,
}, },
{'role': 'user', 'content': text}, {'role': 'user', 'content': text},
], ],

12
lib/data/services/meeting/meeting_upload_service.dart

@ -41,6 +41,9 @@ class MeetingUploadService extends GetxService {
'rootdir': rootDir, 'rootdir': rootDir,
'audiourl': '', 'audiourl': '',
'isTask': false, 'isTask': false,
'language': '',
'prompt': '',
'outline': '',
}); });
_storage.write("meeting_upload_list", uploadList); _storage.write("meeting_upload_list", uploadList);
if (isUpload) { if (isUpload) {
@ -70,7 +73,14 @@ class MeetingUploadService extends GetxService {
{'audiourl': audiourl}, {'audiourl': audiourl},
); );
if (element['isTask']) { if (element['isTask']) {
await _taskService.addTaskList(element['id'], audiourl, ''); await _taskService.addTaskList(
element['id'],
audiourl,
'',
element['language'],
element['prompt'],
element['outline'],
);
} }
element['audiourl'] = audiourl; element['audiourl'] = audiourl;
uploadProgress.value = 0.0; uploadProgress.value = 0.0;

9
lib/data/services/network/api.dart

@ -180,4 +180,13 @@ class Api {
params: params, params: params,
); );
} }
//获取模版数据
static getEchomeetTemplates([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/echomeet_gettemplates',
params: params,
);
}
} }

26
lib/modules/agent/controllers/agent_controller.dart

@ -613,10 +613,20 @@ class AgentController extends GetxController with WidgetsBindingObserver {
final sessionid = event.data['sessionid'] ?? ''; final sessionid = event.data['sessionid'] ?? '';
final text = event.data['text'] ?? ''; final text = event.data['text'] ?? '';
Logger.i(TAG, '[$sessionid]识别最终结果: $text'); Logger.i(TAG, '[$sessionid]识别最终结果: $text');
if (currsessionid.value != "" && currsessionid.value != sessionid) {
final index = messages.lastIndexWhere(
(msg) => msg.sessionid == currsessionid.value && msg.isUser);
//出现了为识别结束的消息
if (index >= 0) {
//异除去当前会话的识别中消息
messages.removeAt(index);
}
}
currsessionid.value = sessionid;
// 查找是否有正在识别中的消息 // 查找是否有正在识别中的消息
final index = messages.lastIndexWhere( final index = messages.lastIndexWhere(
(msg) => msg.sessionid == sessionid && msg.isUser); (msg) => msg.sessionid == sessionid && msg.isUser);
if (text.isNotEmpty) { if (text.isNotEmpty) {
if (index >= 0) { if (index >= 0) {
// 将识别中的消息转换为最终消息 // 将识别中的消息转换为最终消息
@ -655,7 +665,7 @@ class AgentController extends GetxController with WidgetsBindingObserver {
// 查找是否有正在识别中的消息 // 查找是否有正在识别中的消息
final index = messages.lastIndexWhere( final index = messages.lastIndexWhere(
(msg) => msg.sessionid == sessionid && msg.isUser); (msg) => msg.sessionid == sessionid && msg.isUser);
currsessionid.value = '';
if (currentText.value.isNotEmpty) { if (currentText.value.isNotEmpty) {
if (index >= 0) { if (index >= 0) {
// 将识别中的消息转换为最终消息 // 将识别中的消息转换为最终消息
@ -668,14 +678,14 @@ class AgentController extends GetxController with WidgetsBindingObserver {
functionCalls: messages[index].functionCalls, functionCalls: messages[index].functionCalls,
isToolCallsExpanded: messages[index].isToolCallsExpanded, isToolCallsExpanded: messages[index].isToolCallsExpanded,
); );
// messages.refresh();
} else { } else {
// 如果没有找到识别中的消息,添加新消息 // 如果没有找到识别中的消息,添加新消息
messages.add(Message( messages.add(Message(
sessionid: sessionid, sessionid: sessionid,
isUser: true, isUser: true,
text: currentText.value, text: currentText.value,
isRecognizing: false)); isRecognizing: false,
));
} }
isProcessing.value = true; isProcessing.value = true;
@ -685,8 +695,8 @@ class AgentController extends GetxController with WidgetsBindingObserver {
} else if (index >= 0) { } else if (index >= 0) {
// 如果识别结果为空,移除临时的识别消息 // 如果识别结果为空,移除临时的识别消息
messages.removeAt(index); messages.removeAt(index);
// messages.refresh();
} }
messages.refresh();
break; break;
case AgentServiceEventType.ttsStarted: case AgentServiceEventType.ttsStarted:
isSpeaking.value = true; isSpeaking.value = true;

9
lib/modules/login/controllers/login_controller.dart

@ -8,6 +8,7 @@ import 'package:logger/web.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'dart:convert'; import 'dart:convert';
import '../../../data/services/db/sqflite_api.dart';
import 'facebookLogin.dart'; import 'facebookLogin.dart';
import 'googleLogin.dart'; import 'googleLogin.dart';
import 'weixinLogin.dart'; import 'weixinLogin.dart';
@ -880,6 +881,7 @@ class LoginController extends GetxController {
// 获取app的配置信息 // 获取app的配置信息
await getAppConfig(); await getAppConfig();
await _initMeetingTemplate();
Get.offAllNamed(Routes.pairing); Get.offAllNamed(Routes.pairing);
} else { } else {
throw Exception('接口返回错误: ${response['msg']}'); throw Exception('接口返回错误: ${response['msg']}');
@ -1002,4 +1004,11 @@ class LoginController extends GetxController {
return false; return false;
} }
} }
Future _initMeetingTemplate() async {
var response = await Api.getEchomeetTemplates();
if (response != null) {
await SqfliteApi.initMeetingTemplate(response['templates']);
}
}
} }

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

@ -245,18 +245,19 @@ class MeetingDetailsController extends GetxController
} }
} }
void summaryCreate() async { void summaryCreate(String language, String prompt, String outline) async {
EasyLoading.show(); EasyLoading.show();
_everType = 2; _everType = 2;
String audiourl = meetingData.value.audiourl; String audiourl = meetingData.value.audiourl;
String taskId = meetingDetails.value.taskid; String taskId = meetingDetails.value.taskid;
await _meetingTaskService.addTask(id, audiourl, taskId); await _meetingTaskService.addTask(
id, audiourl, taskId, language, prompt, outline);
meetingDetails.value.tasktype = 1; meetingDetails.value.tasktype = 1;
meetingDetails.refresh(); meetingDetails.refresh();
EasyLoading.dismiss(); EasyLoading.dismiss();
} }
void refreshSummary() async { void refreshSummary(String prompt, String outline) async {
String text = ''; String text = '';
for (var element in textList) { for (var element in textList) {
if (element['speaker'].isNotEmpty) { if (element['speaker'].isNotEmpty) {
@ -267,6 +268,6 @@ class MeetingDetailsController extends GetxController
_everType = 3; _everType = 3;
await SqfliteApi.editMeeting(id, {'tasktype': 2}); await SqfliteApi.editMeeting(id, {'tasktype': 2});
_meetingTaskService.tasktype.value = 2; _meetingTaskService.tasktype.value = 2;
_meetingTaskService.aiServiceMessage(id, text); _meetingTaskService.aiServiceMessage(id, text, prompt, outline);
} }
} }

11
lib/modules/meeting/views/bottomSheet/generate_bottom_sheet.dart

@ -283,9 +283,16 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
_storage.write("meeting_template_list", _templateList); _storage.write("meeting_template_list", _templateList);
Get.back(); Get.back();
if (widget.isSummarizeAgain) { if (widget.isSummarizeAgain) {
_controller.refreshSummary(); _controller.refreshSummary(
_templateData['prompt'],
_templateData['outline'],
);
} else { } else {
_controller.summaryCreate(); _controller.summaryCreate(
_language,
_templateData['prompt'],
_templateData['outline'],
);
} }
}, },
child: Container( child: Container(

54
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/CustomSseClientTransport.kt

@ -177,14 +177,39 @@ class CustomSseClientTransport(
} }
} }
// 添加查询参数 // 添加查询参数,避免重复参数
val endpointWithParams = if (queryParams.isNotEmpty()) { val endpointWithParams = if (queryParams.isNotEmpty()) {
// 解析现有的查询参数
val existingParams = mutableMapOf<String, String>()
if (validatedEndpoint.contains("?")) { if (validatedEndpoint.contains("?")) {
val queryString = queryParams.entries.joinToString("&") { "${it.key}=${it.value}" } val urlParts = validatedEndpoint.split("?", limit = 2)
"$validatedEndpoint&$queryString" if (urlParts.size == 2) {
urlParts[1].split("&").forEach { param ->
val parts = param.split("=", limit = 2)
if (parts.size == 2) {
existingParams[parts[0]] = parts[1]
}
}
}
}
// 合并参数,避免重复
val allParams = mutableMapOf<String, String>()
allParams.putAll(existingParams)
allParams.putAll(queryParams)
// 构建最终URL
val baseUrl = if (validatedEndpoint.contains("?")) {
validatedEndpoint.split("?")[0]
} else { } else {
val queryString = queryParams.entries.joinToString("&") { "${it.key}=${it.value}" } validatedEndpoint
"$validatedEndpoint?$queryString" }
if (allParams.isNotEmpty()) {
val queryString = allParams.entries.joinToString("&") { "${it.key}=${it.value}" }
"$baseUrl?$queryString"
} else {
baseUrl
} }
} else { } else {
validatedEndpoint validatedEndpoint
@ -280,7 +305,7 @@ class CustomSseClientTransport(
} }
sessionScope = CoroutineScope(transportScope.coroutineContext + SupervisorJob()) sessionScope = CoroutineScope(transportScope.coroutineContext + SupervisorJob())
Log.e(TAG, "$serviceidString 解析成功: start 会话")
// 创建SSE会话 - 直接使用原始URL // 创建SSE会话 - 直接使用原始URL
session = urlString?.let { session = urlString?.let {
val sseConnectUrl = if (queryParams.isNotEmpty()) { val sseConnectUrl = if (queryParams.isNotEmpty()) {
@ -303,14 +328,14 @@ class CustomSseClientTransport(
reconnectionTime = reconnectionTime, reconnectionTime = reconnectionTime,
block = requestBuilder, block = requestBuilder,
) )
Log.e(TAG, "$serviceidString: start 会话 collectEvents")
// 收集SSE事件 // 收集SSE事件
collectEvents() collectEvents()
Log.e(TAG, "$serviceidString: start 会话 endpoint")
// 等待endpoint就绪 // 等待endpoint就绪
endpoint.await() endpoint.await()
Log.d(TAG, "CustomSseClientTransport启动完成") Log.d(TAG, "$serviceidString: CustomSseClientTransport启动完成")
} }
/** /**
@ -322,15 +347,18 @@ class CustomSseClientTransport(
Log.e(TAG, "发送失败: 未连接") Log.e(TAG, "发送失败: 未连接")
error("Not connected") error("Not connected")
} }
try { try {
val messageEndpoint = endpoint.getCompleted() val messageEndpoint = endpoint.getCompleted()
Log.d(TAG, "$serviceidString 准备发送消息到: $messageEndpoint")
val jsonString = json.encodeToString(message) val jsonString = json.encodeToString(message)
val response = client.post(messageEndpoint) { val response = withTimeout(15000) { // 减少超时时间到15秒
headers.append(HttpHeaders.ContentType, ContentType.Application.Json.toString()) client.post(messageEndpoint) {
setBody(jsonString) headers.append(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setBody(jsonString)
}
} }
// 优化处理百度API的202状态码 // 优化处理百度API的202状态码

65
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt

@ -60,6 +60,18 @@ class SystemFunctionHandler(private val context: Context? = null) {
// handler = TranslationModeHandler(context) // handler = TranslationModeHandler(context)
// ) // )
// 注册获取联系人列表函数
client.registerLocalFunction(
name = "get_contacts_list",
description = "获取用户通信录联系人列表",
parameters = mapOf(
"type" to "object",
"properties" to emptyMap<String, Any>(),
"required" to emptyList<String>()
),
handler = GetContactsListHandler(context)
)
// 注册发送短信函数 // 注册发送短信函数
client.registerLocalFunction( client.registerLocalFunction(
name = "send_text_message", name = "send_text_message",
@ -67,7 +79,7 @@ class SystemFunctionHandler(private val context: Context? = null) {
parameters = mapOf( parameters = mapOf(
"type" to "object", "type" to "object",
"properties" to mapOf( "properties" to mapOf(
"contact" to mapOf( "phone_number" to mapOf(
"type" to "string", "type" to "string",
"description" to "电话号码" "description" to "电话号码"
), ),
@ -76,23 +88,11 @@ class SystemFunctionHandler(private val context: Context? = null) {
"description" to "短信内容" "description" to "短信内容"
) )
), ),
"required" to listOf("contact", "message") "required" to listOf("phone_number", "message")
), ),
handler = SendTextMessageHandler(context) handler = SendTextMessageHandler(context)
) )
// 注册获取联系人列表函数
client.registerLocalFunction(
name = "get_contacts_list",
description = "获取用户通信录联系人列表",
parameters = mapOf(
"type" to "object",
"properties" to emptyMap<String, Any>(),
"required" to emptyList<String>()
),
handler = GetContactsListHandler(context)
)
// 注册拨打电话函数 // 注册拨打电话函数
client.registerLocalFunction( client.registerLocalFunction(
name = "make_phone_call", name = "make_phone_call",
@ -100,12 +100,12 @@ class SystemFunctionHandler(private val context: Context? = null) {
parameters = mapOf( parameters = mapOf(
"type" to "object", "type" to "object",
"properties" to mapOf( "properties" to mapOf(
"contact" to mapOf( "phone_number" to mapOf(
"type" to "string", "type" to "string",
"description" to "联系人姓名或电话号码" "description" to "联系人电话号码"
) )
), ),
"required" to listOf("contact") "required" to listOf("phone_number")
), ),
handler = MakePhoneCallHandler(context) handler = MakePhoneCallHandler(context)
) )
@ -495,10 +495,10 @@ private class TranslationModeHandler(private val context: Context?) : FunctionHa
*/ */
private class SendTextMessageHandler(private val context: Context?) : FunctionHandler { private class SendTextMessageHandler(private val context: Context?) : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String { override suspend fun handle(arguments: Map<String, Any>): String {
val contact = arguments["contact"] as? String val phone_number = arguments["phone_number"] as? String
val message = arguments["message"] as? String val message = arguments["message"] as? String
if (contact == null || message == null) { if (phone_number == null || message == null) {
return JSONObject().apply { return JSONObject().apply {
put("error", JSONObject().apply { put("error", JSONObject().apply {
put("code", "invalid_arguments") put("code", "invalid_arguments")
@ -529,12 +529,12 @@ private class SendTextMessageHandler(private val context: Context?) : FunctionHa
}.toString() }.toString()
} }
val phoneNumber = handler.getPhoneNumberForContact(contact) val phoneNumber = handler.getPhoneNumberForContact(phone_number)
if (phoneNumber.isEmpty()) { if (phoneNumber.isEmpty()) {
return JSONObject().apply { return JSONObject().apply {
put("error", JSONObject().apply { put("error", JSONObject().apply {
put("code", "contact_not_found") put("code", "contact_not_found")
put("message", "未找到联系人信息: $contact") put("message", "未找到联系人信息: $phone_number")
}) })
}.toString() }.toString()
} }
@ -556,7 +556,7 @@ private class SendTextMessageHandler(private val context: Context?) : FunctionHa
} }
JSONObject().apply { JSONObject().apply {
put("result", "已成功向${contact}发送短信") put("result", "已成功向${phone_number}发送短信")
put("phone", phoneNumber) put("phone", phoneNumber)
put("message", message) put("message", message)
}.toString() }.toString()
@ -637,13 +637,13 @@ private class MakePhoneCallHandler(private val context: Context?) : FunctionHand
} }
override suspend fun handle(arguments: Map<String, Any>): String { override suspend fun handle(arguments: Map<String, Any>): String {
val contact = arguments["contact"] as? String val phone_number = arguments["phone_number"] as? String
if (contact == null) { if (phone_number == null) {
return JSONObject().apply { return JSONObject().apply {
put("error", JSONObject().apply { put("error", JSONObject().apply {
put("code", "invalid_arguments") put("code", "invalid_arguments")
put("message", "缺少必要参数:contact") put("message", "缺少必要参数:phone_number")
}) })
}.toString() }.toString()
} }
@ -670,12 +670,12 @@ private class MakePhoneCallHandler(private val context: Context?) : FunctionHand
}.toString() }.toString()
} }
val phoneNumber = handler.getPhoneNumberForContact(contact) val phoneNumber = handler.getPhoneNumberForContact(phone_number)
if (phoneNumber.isEmpty()) { if (phoneNumber.isEmpty()) {
return JSONObject().apply { return JSONObject().apply {
put("error", JSONObject().apply { put("error", JSONObject().apply {
put("code", "contact_not_found") put("code", "contact_not_found")
put("message", "未找到联系人信息: $contact") put("message", "未找到联系人信息: $phone_number")
}) })
}.toString() }.toString()
} }
@ -688,7 +688,7 @@ private class MakePhoneCallHandler(private val context: Context?) : FunctionHand
context.startActivity(intent) context.startActivity(intent)
JSONObject().apply { JSONObject().apply {
put("result", "正在拨打${contact}的电话") put("result", "正在拨打${phone_number}的电话")
put("phone", phoneNumber) put("phone", phoneNumber)
}.toString() }.toString()
} catch (e: SecurityException) { } catch (e: SecurityException) {
@ -1106,10 +1106,11 @@ private class GetCurrentLocationHandler(private val context: Context?) : Functio
val result = JSONObject().apply { val result = JSONObject().apply {
put("result", JSONObject().apply { put("result", JSONObject().apply {
put("longitude", longitude) put("经度", longitude) // 经度
put("latitude", latitude) put("纬度", latitude) // 纬度
put("timestamp", System.currentTimeMillis()) put("经纬度", "$latitude,$longitude")
put("纬经度", "$longitude,$latitude")
put("时间戳", System.currentTimeMillis())
}) })
}.toString() }.toString()

39
local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift

@ -71,12 +71,13 @@ public class SystemFunctionHandler: NSObject {
// 注册翻译模式函数 // 注册翻译模式函数
// registerTranslationModeFunction(client: client) // registerTranslationModeFunction(client: client)
// 注册发送短信函数
registerSendTextMessageFunction(client: client)
// 注册查询通讯录函数 // 注册查询通讯录函数
registerGetContactsFunction(client: client) registerGetContactsFunction(client: client)
// 注册发送短信函数
registerSendTextMessageFunction(client: client)
// 注册拨打电话函数 // 注册拨打电话函数
registerMakePhoneCallFunction(client: client) registerMakePhoneCallFunction(client: client)
@ -163,16 +164,16 @@ public class SystemFunctionHandler: NSObject {
let parameters: [String: Any] = [ let parameters: [String: Any] = [
"type": "object", "type": "object",
"properties": [ "properties": [
"contact": [ "phone_number": [
"type": "string", "type": "string",
"description": "联系人姓名或电话号码" "description": "联系人电话号码"
], ],
"message": [ "message": [
"type": "string", "type": "string",
"description": "要发送的短信内容" "description": "要发送的短信内容"
] ]
], ],
"required": ["contact", "message"] "required": ["phone_number", "message"]
] ]
let handler = SendTextMessageHandler() let handler = SendTextMessageHandler()
@ -194,8 +195,8 @@ public class SystemFunctionHandler: NSObject {
let handler = GetContactsHandler() let handler = GetContactsHandler()
_ = client.registerLocalFunction( _ = client.registerLocalFunction(
name: "get_contacts", name: "get_contacts_list",
description: "获取联系人列表,读取通讯录信息,联系人名称和电话", description: "获取用户通信录联系人列表",
parameters: parameters, parameters: parameters,
handler: handler handler: handler
) )
@ -206,12 +207,12 @@ public class SystemFunctionHandler: NSObject {
let parameters: [String: Any] = [ let parameters: [String: Any] = [
"type": "object", "type": "object",
"properties": [ "properties": [
"contact": [ "phone_number": [
"type": "string", "type": "string",
"description": "联系人姓名或电话号码" "description": "联系人电话号码"
] ]
], ],
"required": ["contact"] "required": ["phone_number"]
] ]
let handler = MakePhoneCallHandler() let handler = MakePhoneCallHandler()
@ -628,22 +629,22 @@ private class SendTextMessageHandler: NSObject, FunctionHandler, MFMessageCompos
private var continuation: CheckedContinuation<Result, Never>? private var continuation: CheckedContinuation<Result, Never>?
func handle(arguments: [String: Any]) async throws -> String { func handle(arguments: [String: Any]) async throws -> String {
guard let contact = arguments["contact"] as? String, guard let phoneNumber = arguments["phone_number"] as? String,
let message = arguments["message"] as? String else { let message = arguments["message"] as? String else {
throw ChatApiException("缺少必要参数") throw ChatApiException("缺少必要参数")
} }
let result = await sendTextMessage(contact: contact, message: message) let result = await sendTextMessage(phoneNumber: phoneNumber, message: message)
return "{\"result\": \"\(result.message)\"}" return "{\"result\": \"\(result.message)\"}"
} }
private func sendTextMessage(contact: String, message: String) async -> Result { private func sendTextMessage(phoneNumber: String, message: String) async -> Result {
return await withCheckedContinuation { continuation in return await withCheckedContinuation { continuation in
self.continuation = continuation self.continuation = continuation
DispatchQueue.main.async { DispatchQueue.main.async {
if MFMessageComposeViewController.canSendText() { if MFMessageComposeViewController.canSendText() {
let messageVC = MFMessageComposeViewController() let messageVC = MFMessageComposeViewController()
messageVC.recipients = [contact] messageVC.recipients = [phoneNumber]
messageVC.body = message messageVC.body = message
messageVC.messageComposeDelegate = self // 设置 delegate messageVC.messageComposeDelegate = self // 设置 delegate
@ -693,16 +694,16 @@ private class SendTextMessageHandler: NSObject, FunctionHandler, MFMessageCompos
/// 拨打电话处理器 /// 拨打电话处理器
private class MakePhoneCallHandler: FunctionHandler { private class MakePhoneCallHandler: FunctionHandler {
func handle(arguments: [String: Any]) async throws -> String { func handle(arguments: [String: Any]) async throws -> String {
guard let contact = arguments["contact"] as? String else { guard let phoneNumber = arguments["phone_number"] as? String else {
throw ChatApiException("缺少联系人参数") throw ChatApiException("缺少联系人参数")
} }
let result = await makePhoneCall(contact: contact) let result = await makePhoneCall(phoneNumber: phoneNumber)
return "{\"result\": \"\(result.message)\"}" return "{\"result\": \"\(result.message)\"}"
} }
private func makePhoneCall(contact: String) async -> Result { private func makePhoneCall(phoneNumber: String) async -> Result {
guard let url = URL(string: "tel:\(contact)") else { guard let url = URL(string: "tel:\(phoneNumber)") else {
return Result(success: false, message: "无法创建电话URL") return Result(success: false, message: "无法创建电话URL")
} }
return await withCheckedContinuation { continuation in return await withCheckedContinuation { continuation in

Loading…
Cancel
Save