Browse Source

上传图片理解修复

newdev_shunjiawei
liwei1dao 1 year ago
parent
commit
625dcc928a
  1. 43
      lib/data/services/chat_history_service.dart
  2. 7
      lib/modules/agent/controllers/agent_controller.dart
  3. 94
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  4. 7
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt
  5. 3
      local_plugins/agent_service/lib/agent_service.dart
  6. 64
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiPlugin.kt
  7. 162
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt
  8. 6
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift
  9. 2
      local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt

43
lib/data/services/chat_history_service.dart

@ -8,50 +8,50 @@ import 'package:chat_storage/chat_storage.dart';
class ChatHistoryService extends GetxService { class ChatHistoryService extends GetxService {
// 最大历史记录数量 // 最大历史记录数量
static const int _maxHistoryCount = 200; // 每个对话最多保存的消息数 static const int _maxHistoryCount = 200; // 每个对话最多保存的消息数
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
Logger.info('ChatHistoryService 已初始化'); Logger.info('ChatHistoryService 已初始化');
} }
/// 保存聊天记录 /// 保存聊天记录
/// ///
/// [agentId] 代理ID /// [agentId] 代理ID
/// [messages] 消息列表 /// [messages] 消息列表
Future<void> saveHistory(String agentId, List<Message> messages) async { Future<void> saveHistory(String agentId, List<Message> messages) async {
try { try {
if (messages.isEmpty) return; if (messages.isEmpty) return;
// 获取最新的消息 // 获取最新的消息
final latestMessage = messages.last; final latestMessage = messages.last;
// 如果是加载中的消息则跳过 // 如果是加载中的消息则跳过
if (latestMessage.isIntermediate) return; if (latestMessage.isIntermediate) return;
// 直接插入最新的消息到数据库 // 直接插入最新的消息到数据库
await ChatStorage.saveMessage( await ChatStorage.saveMessage(
sessionId: agentId, sessionId: agentId,
message: latestMessage.content, message: latestMessage.content,
sender: latestMessage.role, sender: latestMessage.role,
); );
// 检查并清理过多的历史记录 // 检查并清理过多的历史记录
await _cleanupOldMessages(agentId); await _cleanupOldMessages(agentId);
} catch (e) { } catch (e) {
Logger.error('保存聊天记录失败', e.toString()); Logger.error('保存聊天记录失败', e.toString());
} }
} }
/// 清理旧消息,保持每个会话的消息数量不超过最大限制 /// 清理旧消息,保持每个会话的消息数量不超过最大限制
Future<void> _cleanupOldMessages(String agentId) async { Future<void> _cleanupOldMessages(String agentId) async {
try { try {
final pagedResult = await ChatStorage.getMessages( final pagedResult = await ChatStorage.getMessages(
sessionId: agentId, sessionId: agentId,
page: 1, page: 1,
pageSize: 1, // 只获取总数信息 pageSize: 1, // 只获取总数信息
); );
// 如果消息总数超过最大限制,删除最早的消息 // 如果消息总数超过最大限制,删除最早的消息
if (pagedResult.totalCount > _maxHistoryCount) { if (pagedResult.totalCount > _maxHistoryCount) {
final messagesToDelete = pagedResult.totalCount - _maxHistoryCount; final messagesToDelete = pagedResult.totalCount - _maxHistoryCount;
@ -61,13 +61,13 @@ class ChatHistoryService extends GetxService {
page: 1, page: 1,
pageSize: messagesToDelete, pageSize: messagesToDelete,
); );
// 构建要删除的消息ID列表 // 构建要删除的消息ID列表
final List<int> messageIds = oldestMessages.messages final List<int> messageIds = oldestMessages.messages
.where((m) => m.id != null) .where((m) => m.id != null)
.map((m) => m.id!) .map((m) => m.id!)
.toList(); .toList();
// 删除这些消息 // 删除这些消息
if (messageIds.isNotEmpty) { if (messageIds.isNotEmpty) {
await ChatStorage.deleteMessages(messageIds: messageIds); await ChatStorage.deleteMessages(messageIds: messageIds);
@ -77,9 +77,9 @@ class ChatHistoryService extends GetxService {
Logger.error('清理旧消息失败', e); Logger.error('清理旧消息失败', e);
} }
} }
/// 加载聊天记录 /// 加载聊天记录
/// ///
/// [agentId] 代理ID /// [agentId] 代理ID
/// 返回消息列表,如果没有找到或解析错误则返回空列表 /// 返回消息列表,如果没有找到或解析错误则返回空列表
Future<List<Message>> loadHistory(String agentId) async { Future<List<Message>> loadHistory(String agentId) async {
@ -89,29 +89,30 @@ class ChatHistoryService extends GetxService {
page: 1, page: 1,
pageSize: _maxHistoryCount, pageSize: _maxHistoryCount,
); );
if (pagedResult.messages.isEmpty) { if (pagedResult.messages.isEmpty) {
return []; return [];
} }
// 将ChatStorage.ChatMessage转换为Message // 将ChatStorage.ChatMessage转换为Message
final messages = pagedResult.messages.map((chatMessage) { final messages = pagedResult.messages.map((chatMessage) {
return Message( return Message(
role: chatMessage.sender, role: chatMessage.sender,
content: chatMessage.message, content: chatMessage.message,
timestamp: DateTime.parse(chatMessage.timestamp), // 直接使用chatMessage的timestamp timestamp: DateTime.parse(
chatMessage.timestamp), // 直接使用chatMessage的timestamp
// isLoading 默认false // isLoading 默认false
// isLoading: metadata['isLoading'] ?? false, // isLoading: metadata['isLoading'] ?? false,
); );
}).toList(); }).toList();
return messages; return messages;
} catch (e) { } catch (e) {
Logger.error('加载聊天记录失败', e); Logger.error('加载聊天记录失败', e);
return []; return [];
} }
} }
/// 清除特定代理的聊天记录 /// 清除特定代理的聊天记录
Future<void> clearHistory(String agentId) async { Future<void> clearHistory(String agentId) async {
try { try {
@ -121,7 +122,7 @@ class ChatHistoryService extends GetxService {
Logger.error('清除聊天记录失败', e); Logger.error('清除聊天记录失败', e);
} }
} }
/// 清除所有聊天记录 /// 清除所有聊天记录
Future<void> clearAllHistory() async { Future<void> clearAllHistory() async {
try { try {
@ -131,4 +132,4 @@ class ChatHistoryService extends GetxService {
Logger.error('清除所有聊天记录失败', e); Logger.error('清除所有聊天记录失败', e);
} }
} }
} }

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

@ -2,6 +2,8 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:amap_service/amap_service.dart'; import 'package:amap_service/amap_service.dart';
import 'package:deep_voice/core/utils/upload_oss.dart';
import 'package:deep_voice/data/models/user_Info.dart';
import 'package:deep_voice/data/services/location_manager.dart'; import 'package:deep_voice/data/services/location_manager.dart';
import 'package:deep_voice/data/services/music_manager.dart'; import 'package:deep_voice/data/services/music_manager.dart';
import 'package:deep_voice/data/services/navigation_manager.dart'; import 'package:deep_voice/data/services/navigation_manager.dart';
@ -548,7 +550,9 @@ class AgentController extends GetxController {
// 复制图片到应用永久存储目录 // 复制图片到应用永久存储目录
final permanentImagePath = await _copyImageToPermanentStorage(imagePath); final permanentImagePath = await _copyImageToPermanentStorage(imagePath);
var ImageUrl = await UploadOss.upload(
filepath: imagePath, rootDir: 'User/${User.instance.uid}/ChatImages');
Logger.i(TAG, '图片上传: $imagePath URL: $ImageUrl');
// 显示的文本,如果没有提供则使用默认值 // 显示的文本,如果没有提供则使用默认值
final displayText = text?.isNotEmpty == true ? text! : '[图片]'; final displayText = text?.isNotEmpty == true ? text! : '[图片]';
@ -578,6 +582,7 @@ class AgentController extends GetxController {
// 调用Agent Service处理图片,考虑TTS状态 // 调用Agent Service处理图片,考虑TTS状态
await AgentService.processImageInput( await AgentService.processImageInput(
permanentImagePath, permanentImagePath,
ImageUrl,
text: text ?? '', text: text ?? '',
speakResponse: isTtsEnabled.value, speakResponse: isTtsEnabled.value,
); );

94
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -576,6 +576,7 @@ object AgentService : CoroutineScope {
) { ) {
// 创建带图片的用户消息并处理 // 创建带图片的用户消息并处理
val userMessage = createUserMessageWithImage(text, imageBase64) val userMessage = createUserMessageWithImage(text, imageBase64)
Log.d(TAG, "liwei--------- 图像Mesage: ${userMessage}")
// 图片描述用于存储 // 图片描述用于存储
val displayText = text.ifEmpty { "[图片]" } val displayText = text.ifEmpty { "[图片]" }
processWithChatApiInternal(userMessage, displayText, speakResponse, true) processWithChatApiInternal(userMessage, displayText, speakResponse, true)
@ -599,7 +600,7 @@ object AgentService : CoroutineScope {
) { ) {
// 如果有正在进行的AI流式输出,先停止它 // 如果有正在进行的AI流式输出,先停止它
stopAiStream() stopAiStream()
stopTts()
// 记录新的AI任务 // 记录新的AI任务
currentAiJob = launch { currentAiJob = launch {
try { try {
@ -621,16 +622,16 @@ object AgentService : CoroutineScope {
val messagesWithSystemPrompt = JSONArray() val messagesWithSystemPrompt = JSONArray()
// 先添加系统提示词 // 先添加系统提示词
if (systemPrompt.isNotEmpty()) { if (systemPrompt.isNotEmpty()) {
val location = LocationService.getCachedLocation()?: mapOf( val location = LocationService.getCachedLocation()?: mapOf(
"user_location" to "定位未开启", "user_location" to "定位未开启",
"user_lat" to "定位未开启", "user_lat" to "定位未开启",
"user_lng" to "定位未开启", "user_lng" to "定位未开启",
) )
val _systemPrompt = Utils.fillTemplate(systemPrompt,location) val _systemPrompt = Utils.fillTemplate(systemPrompt,location)
messagesWithSystemPrompt.put(createSystemMessage(_systemPrompt)) messagesWithSystemPrompt.put(createSystemMessage(_systemPrompt))
Log.d(TAG, "liwei--------- 系统提示词: ${_systemPrompt}") Log.d(TAG, "liwei--------- 系统提示词: ${_systemPrompt}")
} }
// 再添加历史消息 // 再添加历史消息
for (i in 0 until historyMessages.length()) { for (i in 0 until historyMessages.length()) {
@ -665,7 +666,7 @@ object AgentService : CoroutineScope {
// 兜底处理,直接添加原消息 // 兜底处理,直接添加原消息
addToHistoryMessages(userMessage) addToHistoryMessages(userMessage)
} }
// Log.e("ChatApiService", "liwei---------sendMessageStream messages:${messagesWithSystemPrompt}")
chatApiService.sendMessageStream( chatApiService.sendMessageStream(
messagesWithSystemPrompt, messagesWithSystemPrompt,
object : StreamCallback { object : StreamCallback {
@ -684,25 +685,21 @@ object AgentService : CoroutineScope {
ttsService?.flushStream() ttsService?.flushStream()
} }
val response = responseBuilder.toString() val response = responseBuilder.toString()
// 发送完整回复,包含是否有图片的标记
val responseData = mutableMapOf<String, Any>(
"text" to response,
"userInput" to displayText
)
if (hasImage) {
responseData["hasImage"] = true
}
sendEvent("assistant_response", responseData)
if (response.isNotEmpty()) { if (response.isNotEmpty()) {
// 发送完整回复,包含是否有图片的标记
val responseData = mutableMapOf<String, Any>(
"text" to response,
"userInput" to displayText
)
if (hasImage) {
responseData["hasImage"] = true
}
sendEvent("assistant_response", responseData)
// 添加AI回复到历史记录 // 添加AI回复到历史记录
addToHistoryMessages(createAssistantMessage(response)) addToHistoryMessages(createAssistantMessage(response))
// 保存聊天记录
saveChatMessage(displayText, response,aiMetadata,userMetadata.toString())
} }
// 保存聊天记录
saveChatMessage(displayText, response,aiMetadata,userMetadata.toString())
// 标记AI流式输出已完成 // 标记AI流式输出已完成
isAiStreaming = false isAiStreaming = false
currentAiJob = null currentAiJob = null
@ -861,14 +858,15 @@ object AgentService : CoroutineScope {
) )
if (userMessageId != -1L) { if (userMessageId != -1L) {
// 保存AI回复 if (assistantMessage.isNotEmpty() ) {
val assistantMessageId = chatStorageHelper.saveMessage( // 保存AI回复
sessionId = sessionId, val assistantMessageId = chatStorageHelper.saveMessage(
message = assistantMessage, sessionId = sessionId,
sender = "assistant", message = assistantMessage,
metadata = aiMetadata sender = "assistant",
) metadata = aiMetadata
)
}
// if (assistantMessageId != -1L) { // if (assistantMessageId != -1L) {
// Log.d(TAG, "聊天记录已保存:用户消息ID=$userMessageId, 助手消息ID=$assistantMessageId") // Log.d(TAG, "聊天记录已保存:用户消息ID=$userMessageId, 助手消息ID=$assistantMessageId")
// } else { // } else {
@ -970,7 +968,7 @@ object AgentService : CoroutineScope {
/** /**
* 创建带图片的用户消息 * 创建带图片的用户消息
*/ */
private fun createUserMessageWithImage(text: String, imageBase64: String): JSONObject { private fun createUserMessageWithImage(text: String, imageUrl: String): JSONObject {
val content = JSONArray().apply { val content = JSONArray().apply {
if (text.isNotEmpty()) { if (text.isNotEmpty()) {
put(JSONObject().apply { put(JSONObject().apply {
@ -981,8 +979,8 @@ object AgentService : CoroutineScope {
put(JSONObject().apply { put(JSONObject().apply {
put("type", "image_url") put("type", "image_url")
put("image_url", JSONObject().apply { put("image_url", JSONObject().apply {
put("url", "data:image/jpeg;base64,$imageBase64") // put("url", "data:image/jpeg;base64,$imageBase64")
put("detail", "auto") put("url", imageUrl)
}) })
}) })
} }
@ -996,7 +994,7 @@ object AgentService : CoroutineScope {
/** /**
* 处理图片输入 * 处理图片输入
*/ */
fun processImageInput(imagePath: String, text: String = "", speakResponse: Boolean = false): Boolean { fun processImageInput(imagePath: String, imageUrl: String,text: String = "", speakResponse: Boolean = false): Boolean {
if (!isInitialized) { if (!isInitialized) {
Log.e(TAG, "服务未初始化") Log.e(TAG, "服务未初始化")
sendEvent("error", mapOf("code" to "NOT_INITIALIZED", "message" to "服务未初始化")) sendEvent("error", mapOf("code" to "NOT_INITIALIZED", "message" to "服务未初始化"))
@ -1019,16 +1017,16 @@ object AgentService : CoroutineScope {
launch { launch {
try { try {
// 将图片转换为Base64格式 // 将图片转换为Base64格式
val imageBase64 = chatApiService.fileToBase64(imagePath) ?: run { // val imageBase64 = chatApiService.fileToBase64(imagePath) ?: run {
sendEvent("error", mapOf( // sendEvent("error", mapOf(
"code" to "IMAGE_CONVERSION_FAILED", // "code" to "IMAGE_CONVERSION_FAILED",
"message" to "图片转换失败" // "message" to "图片转换失败"
)) // ))
return@launch // return@launch
} // }
// 创建带图片的用户消息并处理 // 创建带图片的用户消息并处理
val userMessage = createUserMessageWithImage(text, imageBase64) val userMessage = createUserMessageWithImage(text, imageUrl)
// 图片描述用于存储 // 图片描述用于存储
val displayText = text.ifEmpty { "[图片]" } val displayText = text.ifEmpty { "[图片]" }
@ -1037,7 +1035,7 @@ object AgentService : CoroutineScope {
"status" to "ready", "status" to "ready",
"imagePath" to imagePath "imagePath" to imagePath
)) ))
Log.d(TAG, "liwei--------- 图像Mesage: ${userMessage}")
// 处理消息并传入存储路径 // 处理消息并传入存储路径
processWithChatApiInternal(userMessage, displayText, speakResponse, true, imagePath) processWithChatApiInternal(userMessage, displayText, speakResponse, true, imagePath)
} catch (e: Exception) { } catch (e: Exception) {

7
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt

@ -130,13 +130,18 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("INVALID_ARGUMENTS", "图片路径不能为空", null) result.error("INVALID_ARGUMENTS", "图片路径不能为空", null)
return return
} }
val imageUrl = call.argument<String>("imageUrl")
if (imageUrl.isNullOrEmpty()) {
result.error("INVALID_ARGUMENTS", "图片Url不能为空", null)
return
}
val text = call.argument<String>("text") ?: "" val text = call.argument<String>("text") ?: ""
val speakResponse = call.argument<Boolean>("speakResponse") ?: false val speakResponse = call.argument<Boolean>("speakResponse") ?: false
try { try {
val isProcessing = AgentService.processImageInput( val isProcessing = AgentService.processImageInput(
imagePath = imagePath, imagePath = imagePath,
imageUrl = imageUrl,
text = text, text = text,
speakResponse = speakResponse speakResponse = speakResponse
) )

3
local_plugins/agent_service/lib/agent_service.dart

@ -326,11 +326,12 @@ class AgentService {
/// [text] 可选的文本描述或问题 /// [text] 可选的文本描述或问题
/// [speakResponse] 是否朗读响应 /// [speakResponse] 是否朗读响应
/// 返回是否成功处理图片 /// 返回是否成功处理图片
static Future<bool> processImageInput(String imagePath, static Future<bool> processImageInput(String imagePath, String imageUrl,
{String text = "", bool speakResponse = false}) async { {String text = "", bool speakResponse = false}) async {
try { try {
final bool result = await _channel.invokeMethod('processImageInput', { final bool result = await _channel.invokeMethod('processImageInput', {
'imagePath': imagePath, 'imagePath': imagePath,
'imageUrl': imageUrl,
'text': text, 'text': text,
'speakResponse': speakResponse, 'speakResponse': speakResponse,
}); });

64
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiPlugin.kt

@ -37,30 +37,30 @@ class ChatApiPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandl
chatApiService = ChatApiService(flutterPluginBinding.applicationContext) chatApiService = ChatApiService(flutterPluginBinding.applicationContext)
// 设置流式回调 // 设置流式回调
chatApiService?.setStreamCallback(object : StreamCallback { // chatApiService?.setStreamCallback(object : StreamCallback {
override fun onToken(token: String) { // override fun onToken(token: String) {
channel.invokeMethod("onToken", token) // channel.invokeMethod("onToken", token)
} // }
//
override fun onComplete() { // override fun onComplete() {
channel.invokeMethod("onComplete", null) // channel.invokeMethod("onComplete", null)
} // }
//
override fun onError(error: Exception) { // override fun onError(error: Exception) {
channel.invokeMethod("onError", error.message) // channel.invokeMethod("onError", error.message)
} // }
//
override fun onFunctionCall(functionCall: org.json.JSONObject) { // override fun onFunctionCall(functionCall: org.json.JSONObject) {
channel.invokeMethod("onFunctionCall", functionCall.toString()) // channel.invokeMethod("onFunctionCall", functionCall.toString())
} // }
//
override fun onFunctionCallResult(functionCall: org.json.JSONObject, functionCallResult: org.json.JSONObject) { // override fun onFunctionCallResult(functionCall: org.json.JSONObject, functionCallResult: org.json.JSONObject) {
channel.invokeMethod("onFunctionCallResult", mapOf( // channel.invokeMethod("onFunctionCallResult", mapOf(
"functionCall" to functionCall.toString(), // "functionCall" to functionCall.toString(),
"functionCallResult" to functionCallResult.toString() // "functionCallResult" to functionCallResult.toString()
)) // ))
} // }
}) // })
} }
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
@ -84,14 +84,14 @@ class ChatApiPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandl
val success = chatApiService?.initialize(apiKey, baseUrl, model, mcpServer) ?: false val success = chatApiService?.initialize(apiKey, baseUrl, model, mcpServer) ?: false
result.success(success) result.success(success)
} }
"chatCompletionStream" -> { // "chatCompletionStream" -> {
val messages = call.argument<List<Map<String, Any>>>("messages") ?: emptyList() // val messages = call.argument<List<Map<String, Any>>>("messages") ?: emptyList()
val tool = call.argument<Boolean>("tool") ?: false // val tool = call.argument<Boolean>("tool") ?: false
pluginScope.launch { // pluginScope.launch {
chatApiService?.chatCompletionStream(messages, tool) // chatApiService?.chatCompletionStream(messages, tool)
} // }
result.success(true) // result.success(true)
} // }
"cancelChatStream" -> { "cancelChatStream" -> {
chatApiService?.cancelChatStream() chatApiService?.cancelChatStream()
result.success(true) result.success(true)

162
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt

@ -23,6 +23,7 @@ import android.util.Log
import org.json.JSONObject import org.json.JSONObject
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import java.util.UUID
/** /**
* ChatAPI服务异常 * ChatAPI服务异常
@ -109,10 +110,12 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
// 流式请求相关 // 流式请求相关
private var currentStreamJob: Job? = null private var currentStreamJob: Job? = null
private var streamCallback: StreamCallback? = null // private var streamCallback: StreamCallback? = null
private var currentMessages: List<Map<String, Any>> = emptyList() private var currentMessages: List<Map<String, Any>> = emptyList()
private var toolCalls: MutableMap<Int, ToolCallInfo> = mutableMapOf() private var toolCalls: MutableMap<Int, ToolCallInfo> = mutableMapOf()
private var isCanceled = false private var sessions: MutableMap<String, StreamCallback?> = mutableMapOf()
private var currSessionId = "" //
// private var isCanceled = false
@ -124,9 +127,9 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
/** /**
* 设置流式回调 * 设置流式回调
*/ */
fun setStreamCallback(callback: StreamCallback) { // fun setStreamCallback(callback: StreamCallback) {
streamCallback = callback // streamCallback = callback
} // }
/** /**
* 初始化ChatAPI服务 * 初始化ChatAPI服务
@ -258,32 +261,33 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
*/ */
fun sendMessageStream(messages: org.json.JSONArray, callback: StreamCallback) { fun sendMessageStream(messages: org.json.JSONArray, callback: StreamCallback) {
// 设置回调 // 设置回调
setStreamCallback(callback) // setStreamCallback(callback)
// 转换JSONArray为List<Map<String, Any>> // 转换JSONArray为List<Map<String, Any>>
val messageList = convertJsonArrayToList(messages) val messageList = convertJsonArrayToList(messages)
currSessionId = UUID.randomUUID().toString()
sessions[currSessionId] = callback
// 调用原始方法 // 调用原始方法
sendMessageStream(messageList) sendMessageStream(currSessionId,messageList)
} }
/** /**
* 发送消息(流式输出) * 发送消息(流式输出)
*/ */
fun sendMessageStream(messages: List<Map<String, Any>>) { fun sendMessageStream(sessionid:String,messages: List<Map<String, Any>>) {
if (!isInitialized || apiKey.isEmpty() || openAI == null) { if (!isInitialized || apiKey.isEmpty() || openAI == null) {
Log.e("ChatApiService", "ChatAPI服务未初始化,无法发送消息") Log.e("ChatApiService", "ChatAPI服务未初始化,无法发送消息")
streamCallback?.onError(ChatApiException("ChatAPI服务未初始化")) sessions[sessionid]?.onError(ChatApiException("ChatAPI服务未初始化"))
return return
} }
// 重置状态 // 重置状态
isCanceled = false // isCanceled = false
currentMessages = messages currentMessages = messages
toolCalls.clear() toolCalls.clear()
val currentModel = model
// 检查是否包含图片,决定使用哪个模型 // 检查是否包含图片,决定使用哪个模型
val currentModel = if (containsImage(messages)) visionModel else model // val currentModel = if (containsImage(messages)) visionModel else model
currentStreamJob = launch { currentStreamJob = launch {
try { try {
@ -323,7 +327,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
Log.e("ChatApiService", "openAI对象为null") Log.e("ChatApiService", "openAI对象为null")
throw IllegalStateException("OpenAI客户端未初始化") throw IllegalStateException("OpenAI客户端未初始化")
} }
// Log.e("ChatApiService", "liwei---------,发送AI请求 model:${currentModel} messages:${chatMessages}")
val flow = openAI!!.chatCompletions(chatCompletionRequest) val flow = openAI!!.chatCompletions(chatCompletionRequest)
flow flow
} catch (e: Exception) { } catch (e: Exception) {
@ -332,7 +336,9 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
} }
chatsFlow.collect { result -> chatsFlow.collect { result ->
if (isCanceled) return@collect if (currSessionId != sessionid) {
return@collect
}
val choice = result.choices.firstOrNull() val choice = result.choices.firstOrNull()
if (choice == null) { if (choice == null) {
@ -348,7 +354,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
// 处理普通文本内容 // 处理普通文本内容
delta.content?.let { content -> delta.content?.let { content ->
streamCallback?.onToken(content) sessions[sessionid]?.onToken(content)
} }
// 收集工具调用信息 // 收集工具调用信息
@ -389,18 +395,20 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
} }
} }
if (!isCanceled) { if (currSessionId == sessionid) {
// 检查是否有工具调用需要处理 // 检查是否有工具调用需要处理
val hasToolCalls = processToolCalls() val hasToolCalls = processToolCalls(sessionid)
if (!hasToolCalls) { if (!hasToolCalls) {
streamCallback?.onComplete() sessions[sessionid]?.onComplete()
} }
}else{
sessions[sessionid]?.onComplete()
} }
} catch (e: Exception) { } catch (e: Exception) {
if (!isCanceled) { // if (currSessionId == sessionid) {
streamCallback?.onError(ChatApiException("流式请求失败: ${e.message}")) sessions[sessionid]?.onError(ChatApiException("流式请求失败: ${e.message}"))
} // }
} }
} }
} }
@ -408,7 +416,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
/** /**
* 处理工具调用 * 处理工具调用
*/ */
private suspend fun processToolCalls(): Boolean { private suspend fun processToolCalls(sessionid: String): Boolean {
val firstToolCall = try { val firstToolCall = try {
toolCalls.values.firstOrNull { it.isValid() } toolCalls.values.firstOrNull { it.isValid() }
} catch (e: Exception) { } catch (e: Exception) {
@ -430,11 +438,11 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
return false return false
} }
// 通知上层工具调用事件 // 通知上层工具调用事件
streamCallback?.onFunctionCall(convertMapToJsonObject(functionCall)) sessions[sessionid]?.onFunctionCall(convertMapToJsonObject(functionCall))
// 在后台队列处理工具调用 // 在后台队列处理工具调用
launch { launch {
try { try {
if (!isCanceled) { if (currSessionId == sessionid) {
// 通过MCP客户端处理工具调用 // 通过MCP客户端处理工具调用
val functionName = firstToolCall.name val functionName = firstToolCall.name
val argumentsJson = firstToolCall.arguments val argumentsJson = firstToolCall.arguments
@ -471,29 +479,35 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
// 工具不存在 // 工具不存在
mapOf("context" to "Tool not found: $functionName") mapOf("context" to "Tool not found: $functionName")
} }
if (!isCanceled) { if (currSessionId == sessionid) {
// 处理结果 // 处理结果
streamCallback?.onFunctionCallResult( sessions[sessionid]?.onFunctionCallResult(
convertMapToJsonObject(functionCall), convertMapToJsonObject(functionCall),
convertMapToJsonObject(result) convertMapToJsonObject(result)
) )
// 将结果发送回OpenAI继续对话 // 将结果发送回OpenAI继续对话
sendFunctionCallResultInternal( sendFunctionCallResultInternal(
sessionid= sessionid,
messages = currentMessages, messages = currentMessages,
functionCall = functionCall, functionCall = functionCall,
functionResult = gson.toJson(result) functionResult = gson.toJson(result)
) )
}else{
sessions[sessionid]?.onComplete()
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("ChatApiService", "工具调用处理过程中出错: ${e.message}", e) Log.e("ChatApiService", "工具调用处理过程中出错: ${e.message}", e)
if (!isCanceled) { if (currSessionId == sessionid) {
val errorMessage = "工具调用处理失败: ${e.message}" val errorMessage = "工具调用处理失败: ${e.message}"
sendFunctionCallResultInternal( sendFunctionCallResultInternal(
sessionid= sessionid,
messages = currentMessages, messages = currentMessages,
functionCall = functionCall, functionCall = functionCall,
functionResult = errorMessage functionResult = errorMessage
) )
}else{
sessions[sessionid]?.onComplete()
} }
} }
} }
@ -505,11 +519,12 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
* 发送函数调用结果(内部方法) * 发送函数调用结果(内部方法)
*/ */
private suspend fun sendFunctionCallResultInternal( private suspend fun sendFunctionCallResultInternal(
sessionid: String,
messages: List<Map<String, Any>>, messages: List<Map<String, Any>>,
functionCall: Map<String, Any>, functionCall: Map<String, Any>,
functionResult: String functionResult: String
) { ) {
if (isCanceled) return if (currSessionId != sessionid) return
val fullMessages = messages.toMutableList() val fullMessages = messages.toMutableList()
@ -542,14 +557,16 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
) )
// 重新发起对话,让AI基于工具调用结果生成回复 // 重新发起对话,让AI基于工具调用结果生成回复
sendMessageStream(fullMessages) sendMessageStream(sessionid,fullMessages)
} }
/** /**
* 取消当前流式请求 * 取消当前流式请求
*/ */
fun cancelCurrentStream(): Boolean { fun cancelCurrentStream(): Boolean {
isCanceled = true sessions[currSessionId]?.onComplete()
sessions.remove(currSessionId)
currSessionId = ""
currentStreamJob?.cancel() currentStreamJob?.cancel()
currentStreamJob = null currentStreamJob = null
return true return true
@ -778,9 +795,9 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
* 聊天完成流式接口 * 聊天完成流式接口
* 与iOS版本保持一致的接口 * 与iOS版本保持一致的接口
*/ */
fun chatCompletionStream(messages: List<Map<String, Any>>, tool: Boolean = false) { fun chatCompletionStream(sessionid: String,messages: List<Map<String, Any>>, tool: Boolean = false) {
// 直接调用sendMessageStream,因为该方法已经处理了工具调用 // 直接调用sendMessageStream,因为该方法已经处理了工具调用
sendMessageStream(messages) sendMessageStream(sessionid,messages)
} }
// MARK: - 工具方法 // MARK: - 工具方法
@ -893,27 +910,70 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
} }
is List<*> -> { is List<*> -> {
// 对于多模态内容,提取文本部分(简化处理) // 对于多模态内容,提取文本部分(简化处理)
var textContent = "" val contentParts = mutableListOf<ContentPart>()
content.forEach { item -> content.forEach { item ->
if (item is Map<*, *>) { if (item is Map<*, *>) {
val type = item["type"] as? String val type = item["type"] as? String
if (type == "text") { when (type) {
val text = item["text"] as? String ?: "" "text" -> {
textContent += text val text = item["text"] as? String ?: ""
contentParts.add(TextPart(text))
}
"image_url" -> {
val imageUrlMap = item["image_url"] as? Map<*, *>
val url = imageUrlMap?.get("url") as? String ?: ""
val detail = imageUrlMap?.get("detail") as? String ?: "auto"
if (url.isNotEmpty()) {
contentParts.add(ImagePart(url, detail))
}
} // 处理其他可能的内容类型
else -> {
// 记录未知类型,便于调试
Log.d("MessageParser", "Unknown content type: $type")
}
} }
} else {
// 非Map类型的内容,记录日志
Log.d("MessageParser", "Item is not a Map: $item")
} }
} }
val finalContent = if (textContent.isEmpty()) "多媒体内容" else textContent // 创建消息对象
if (contentParts.isNotEmpty()) {
val chatMessage = when (chatRole) { val chatMessage = when (chatRole) {
Role.System -> ChatMessage.System(finalContent) Role.System -> {
Role.User -> ChatMessage.User(finalContent) // 系统消息简化显示,将图片替换为[图片]
Role.Assistant -> ChatMessage.Assistant(finalContent) val displayText = contentParts.joinToString { part ->
else -> ChatMessage.User(finalContent) when (part) {
is TextPart -> part.text // 使用TextPart
is ImagePart -> "[图片]" // 使用ImagePart
}
}
ChatMessage.System(displayText)
}
Role.User -> ChatMessage.User(contentParts)
Role.Assistant -> {
// 将多模态内容转换为ListContent
val listContent = ListContent(contentParts)
// 提取文本内容作为Assistant消息的content
val textContent = contentParts
.filterIsInstance<TextPart>()
.joinToString(separator = "") { it.text }
// 调用Assistant函数,传递文本内容
ChatMessage.Assistant(
content = textContent.ifEmpty { null },
toolCalls = null // 如果有工具调用,需要在这里添加
)
}
else -> ChatMessage.User(contentParts)
}
chatMessages.add(chatMessage)
} else {
// 内容为空时的处理
Log.d("MessageParser", "No valid content parts found")
} }
chatMessages.add(chatMessage)
} }
} }
} }
@ -1035,13 +1095,13 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
*/ */
private fun convertJsonArrayToList(jsonArray: org.json.JSONArray): List<Map<String, Any>> { private fun convertJsonArrayToList(jsonArray: org.json.JSONArray): List<Map<String, Any>> {
val result = mutableListOf<Map<String, Any>>() val result = mutableListOf<Map<String, Any>>()
// Log.e("ChatApiService", "convertJsonArrayToList 1:${jsonArray}")
for (i in 0 until jsonArray.length()) { for (i in 0 until jsonArray.length()) {
val jsonObject = jsonArray.optJSONObject(i) ?: continue val jsonObject = jsonArray.optJSONObject(i) ?: continue
val map = convertJsonObjectToMap(jsonObject) val map = convertJsonObjectToMap(jsonObject)
result.add(map) result.add(map)
} }
// Log.e("ChatApiService", "convertJsonArrayToList 2:${result}")
return result return result
} }
@ -1123,9 +1183,9 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
} }
// 通知Flutter端处理函数调用 // 通知Flutter端处理函数调用
mainHandler.post { // mainHandler.post {
streamCallback?.onFunctionCall(functionCall) // streamCallback?.onFunctionCall(functionCall)
} // }
// 返回一个标记,表示函数已被调用 // 返回一个标记,表示函数已被调用
return "Function '$functionName' called with arguments: $arguments" return "Function '$functionName' called with arguments: $arguments"

6
local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift

@ -184,9 +184,9 @@ public class ChatApiService: NSObject {
// 检查是否包含图片,决定使用哪个模型 // 检查是否包含图片,决定使用哪个模型
var currentModel = model var currentModel = model
if containsImage(in: messages) { // if containsImage(in: messages) {
currentModel = visionModel // currentModel = visionModel
} // }
// 转换消息格式 // 转换消息格式
let chatMessages = try convertToChatMessages(messages) let chatMessages = try convertToChatMessages(messages)

2
local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt

@ -33,7 +33,7 @@ object LocationService {
locationClient?.setLocationListener { location -> locationClient?.setLocationListener { location ->
if (location != null && location.errorCode == 0) { if (location != null && location.errorCode == 0) {
currentLocation = location currentLocation = location
Log.d(TAG, "liwei---------定位更新: ${location.latitude}, ${location.longitude}") // Log.d(TAG, "liwei---------定位更新: ${location.latitude}, ${location.longitude}")
} else { } else {
Log.w(TAG, "liwei---------定位失败: ${location?.errorCode} - ${location?.errorInfo}") Log.w(TAG, "liwei---------定位失败: ${location?.errorCode} - ${location?.errorInfo}")
} }

Loading…
Cancel
Save