You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
360 lines
17 KiB
360 lines
17 KiB
import Flutter
|
|
import UIKit
|
|
|
|
// 修改OpenAIService类,添加转发方法
|
|
extension OpenAIService {
|
|
// 为插件提供的公共发送消息方法
|
|
public func publicSendMessageStream(messages: [[String: Any]], systemPrompt: String, onToken: @escaping (String) -> Void, onComplete: @escaping () -> Void, onError: @escaping (Error) -> Void, onFunctionCall: @escaping ([String: Any]) -> Void, onFunctionCallResult: @escaping ([String: Any], [String: Any]) -> Void) {
|
|
|
|
let callback = StreamCallback(
|
|
onToken: onToken,
|
|
onComplete: onComplete,
|
|
onError: onError,
|
|
onFunctionCall: onFunctionCall,
|
|
onFunctionCallResult: onFunctionCallResult
|
|
)
|
|
|
|
sendMessageStream(messages: messages, systemPrompt: systemPrompt, callback: callback)
|
|
}
|
|
|
|
// 为插件提供的公共发送函数调用结果方法
|
|
public func publicSendFunctionCallResult(messages: [[String: Any]], systemPrompt: String, functionCall: [String: Any], functionResult: String, onToken: @escaping (String) -> Void, onComplete: @escaping () -> Void, onError: @escaping (Error) -> Void, onFunctionCall: @escaping ([String: Any]) -> Void, onFunctionCallResult: @escaping ([String: Any], [String: Any]) -> Void) {
|
|
|
|
let callback = StreamCallback(
|
|
onToken: onToken,
|
|
onComplete: onComplete,
|
|
onError: onError,
|
|
onFunctionCall: onFunctionCall,
|
|
onFunctionCallResult: onFunctionCallResult
|
|
)
|
|
|
|
sendFunctionCallResult(messages: messages, systemPrompt: systemPrompt, functionCall: functionCall, functionResult: functionResult, callback: callback)
|
|
}
|
|
}
|
|
|
|
public class OpenAIServicePlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
|
|
// OpenAI服务实例
|
|
private let openAIService = OpenAIService()
|
|
|
|
// 事件接收器
|
|
private var eventSink: FlutterEventSink?
|
|
|
|
// 注册插件
|
|
public static func register(with registrar: FlutterPluginRegistrar) {
|
|
let methodChannel = FlutterMethodChannel(name: "com.yunqiinnovation.open_ai_service/methods", binaryMessenger: registrar.messenger())
|
|
let eventChannel = FlutterEventChannel(name: "com.yunqiinnovation.open_ai_service/events", binaryMessenger: registrar.messenger())
|
|
|
|
let instance = OpenAIServicePlugin()
|
|
registrar.addMethodCallDelegate(instance, channel: methodChannel)
|
|
eventChannel.setStreamHandler(instance)
|
|
}
|
|
|
|
// 处理方法调用
|
|
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
|
switch call.method {
|
|
case "initialize":
|
|
if let args = call.arguments as? [String: Any],
|
|
let apiKey = args["apiKey"] as? String {
|
|
let baseUrl = args["baseUrl"] as? String ?? ""
|
|
let model = args["model"] as? String ?? ""
|
|
let mcpServer = args["mcpServer"] as? String ?? ""
|
|
let initialized = openAIService.initialize(apiKey: apiKey, baseUrl: baseUrl, model: model, mcpServer: mcpServer)
|
|
result(initialized)
|
|
} else {
|
|
result(FlutterError(code: "INVALID_ARGUMENT", message: "初始化参数无效", details: nil))
|
|
}
|
|
|
|
case "registerFunction":
|
|
if let args = call.arguments as? [String: Any],
|
|
let name = args["name"] as? String,
|
|
let description = args["description"] as? String,
|
|
let parameters = args["parameters"] as? [String: Any] {
|
|
|
|
let registered = openAIService.registerFunction(name: name, description: description, parameters: parameters)
|
|
result(registered)
|
|
} else {
|
|
result(FlutterError(code: "INVALID_ARGUMENT", message: "函数注册参数无效", details: nil))
|
|
}
|
|
|
|
case "sendMessage":
|
|
guard let args = call.arguments as? [String: Any],
|
|
let messagesRaw = args["messages"] as? [[String: Any]] else {
|
|
result(FlutterError(code: "INVALID_ARGUMENT", message: "发送消息参数无效", details: nil))
|
|
return
|
|
}
|
|
|
|
let systemPrompt = args["systemPrompt"] as? String ?? ""
|
|
|
|
// 在后台线程执行
|
|
DispatchQueue.global(qos: .userInitiated).async {
|
|
do {
|
|
let response = try self.openAIService.sendMessage(messages: messagesRaw, systemPrompt: systemPrompt)
|
|
// 在主线程返回结果
|
|
DispatchQueue.main.async {
|
|
result(response)
|
|
}
|
|
} catch {
|
|
// 在主线程返回错误
|
|
DispatchQueue.main.async {
|
|
result(FlutterError(code: "OPENAI_ERROR", message: error.localizedDescription, details: nil))
|
|
}
|
|
}
|
|
}
|
|
|
|
case "sendMessageStream":
|
|
guard let args = call.arguments as? [String: Any],
|
|
let messagesRaw = args["messages"] as? [[String: Any]] else {
|
|
result(FlutterError(code: "INVALID_ARGUMENT", message: "发送消息参数无效", details: nil))
|
|
return
|
|
}
|
|
|
|
let systemPrompt = args["systemPrompt"] as? String ?? ""
|
|
|
|
// 检查事件接收器
|
|
guard let eventSink = self.eventSink else {
|
|
result(FlutterError(code: "NO_EVENT_SINK", message: "没有可用的事件流接收器", details: nil))
|
|
return
|
|
}
|
|
|
|
// 在后台线程执行
|
|
DispatchQueue.global(qos: .userInitiated).async {
|
|
self.openAIService.publicSendMessageStream(
|
|
messages: messagesRaw,
|
|
systemPrompt: systemPrompt,
|
|
onToken: { token in
|
|
DispatchQueue.main.async {
|
|
eventSink(["type": "token", "content": token])
|
|
}
|
|
},
|
|
onComplete: {
|
|
DispatchQueue.main.async {
|
|
eventSink(["type": "complete"])
|
|
}
|
|
},
|
|
onError: { error in
|
|
DispatchQueue.main.async {
|
|
eventSink(["type": "error", "content": error.localizedDescription])
|
|
}
|
|
},
|
|
onFunctionCall: { functionCall in
|
|
DispatchQueue.main.async {
|
|
do {
|
|
let data = try JSONSerialization.data(withJSONObject: functionCall)
|
|
if let jsonString = String(data: data, encoding: .utf8) {
|
|
eventSink(["type": "function_call", "content": jsonString])
|
|
} else {
|
|
eventSink(["type": "error", "content": "函数调用数据序列化失败"])
|
|
}
|
|
} catch {
|
|
eventSink(["type": "error", "content": "函数调用数据序列化失败: \(error.localizedDescription)"])
|
|
}
|
|
}
|
|
},
|
|
onFunctionCallResult: { functionCall, functionCallResult in
|
|
DispatchQueue.main.async {
|
|
do {
|
|
let functionCallData = try JSONSerialization.data(withJSONObject: functionCall)
|
|
let resultData = try JSONSerialization.data(withJSONObject: functionCallResult)
|
|
|
|
if let functionCallString = String(data: functionCallData, encoding: .utf8),
|
|
let resultString = String(data: resultData, encoding: .utf8) {
|
|
eventSink(["type": "function_call_result", "function_call": functionCallString, "result": resultString])
|
|
} else {
|
|
eventSink(["type": "error", "content": "函数调用结果数据序列化失败"])
|
|
}
|
|
} catch {
|
|
eventSink(["type": "error", "content": "函数调用结果数据序列化失败: \(error.localizedDescription)"])
|
|
}
|
|
}
|
|
}
|
|
)
|
|
|
|
// 请求已开始
|
|
DispatchQueue.main.async {
|
|
result(true)
|
|
}
|
|
}
|
|
|
|
case "sendFunctionCallResult":
|
|
guard let args = call.arguments as? [String: Any],
|
|
let messagesRaw = args["messages"] as? [[String: Any]],
|
|
let functionCallRaw = args["functionCall"] as? [String: Any],
|
|
let functionResult = args["functionResult"] as? String else {
|
|
result(FlutterError(code: "INVALID_ARGUMENT", message: "发送函数调用结果参数无效", details: nil))
|
|
return
|
|
}
|
|
|
|
let systemPrompt = args["systemPrompt"] as? String ?? ""
|
|
|
|
// 检查事件接收器
|
|
guard let eventSink = self.eventSink else {
|
|
result(FlutterError(code: "NO_EVENT_SINK", message: "没有可用的事件流接收器", details: nil))
|
|
return
|
|
}
|
|
|
|
// 在后台线程执行
|
|
DispatchQueue.global(qos: .userInitiated).async {
|
|
self.openAIService.publicSendFunctionCallResult(
|
|
messages: messagesRaw,
|
|
systemPrompt: systemPrompt,
|
|
functionCall: functionCallRaw,
|
|
functionResult: functionResult,
|
|
onToken: { token in
|
|
DispatchQueue.main.async {
|
|
eventSink(["type": "token", "content": token])
|
|
}
|
|
},
|
|
onComplete: {
|
|
DispatchQueue.main.async {
|
|
eventSink(["type": "complete"])
|
|
}
|
|
},
|
|
onError: { error in
|
|
DispatchQueue.main.async {
|
|
eventSink(["type": "error", "content": error.localizedDescription])
|
|
}
|
|
},
|
|
onFunctionCall: { functionCall in
|
|
DispatchQueue.main.async {
|
|
do {
|
|
let data = try JSONSerialization.data(withJSONObject: functionCall)
|
|
if let jsonString = String(data: data, encoding: .utf8) {
|
|
eventSink(["type": "function_call", "content": jsonString])
|
|
} else {
|
|
eventSink(["type": "error", "content": "函数调用数据序列化失败"])
|
|
}
|
|
} catch {
|
|
eventSink(["type": "error", "content": "函数调用数据序列化失败: \(error.localizedDescription)"])
|
|
}
|
|
}
|
|
},
|
|
onFunctionCallResult: { functionCall, functionCallResult in
|
|
DispatchQueue.main.async {
|
|
do {
|
|
let functionCallData = try JSONSerialization.data(withJSONObject: functionCall)
|
|
let resultData = try JSONSerialization.data(withJSONObject: functionCallResult)
|
|
|
|
if let functionCallString = String(data: functionCallData, encoding: .utf8),
|
|
let resultString = String(data: resultData, encoding: .utf8) {
|
|
eventSink(["type": "function_call_result", "function_call": functionCallString, "result": resultString])
|
|
} else {
|
|
eventSink(["type": "error", "content": "函数调用结果数据序列化失败"])
|
|
}
|
|
} catch {
|
|
eventSink(["type": "error", "content": "函数调用结果数据序列化失败: \(error.localizedDescription)"])
|
|
}
|
|
}
|
|
}
|
|
)
|
|
|
|
// 请求已开始
|
|
DispatchQueue.main.async {
|
|
result(true)
|
|
}
|
|
}
|
|
|
|
case "createUserMessage":
|
|
if let args = call.arguments as? [String: Any],
|
|
let content = args["content"] as? String {
|
|
let message = openAIService.createUserMessage(content: content)
|
|
result(message)
|
|
} else {
|
|
result(FlutterError(code: "INVALID_ARGUMENT", message: "创建用户消息参数无效", details: nil))
|
|
}
|
|
|
|
case "createAssistantMessage":
|
|
if let args = call.arguments as? [String: Any],
|
|
let content = args["content"] as? String {
|
|
let message = openAIService.createAssistantMessage(content: content)
|
|
result(message)
|
|
} else {
|
|
result(FlutterError(code: "INVALID_ARGUMENT", message: "创建助手消息参数无效", details: nil))
|
|
}
|
|
|
|
case "createUserMessageWithImage":
|
|
if let args = call.arguments as? [String: Any],
|
|
let text = args["text"] as? String,
|
|
let imageBase64 = args["imageBase64"] as? String {
|
|
let message = openAIService.createUserMessageWithImage(text: text, imageBase64: imageBase64)
|
|
result(message)
|
|
} else {
|
|
result(FlutterError(code: "INVALID_ARGUMENT", message: "创建图片消息参数无效", details: nil))
|
|
}
|
|
|
|
case "fileToBase64":
|
|
if let args = call.arguments as? [String: Any],
|
|
let filePath = args["filePath"] as? String {
|
|
let maxSizeKB = args["maxSizeKB"] as? Int ?? 20480
|
|
let base64 = openAIService.fileToBase64(filePath, maxSizeKB: maxSizeKB)
|
|
if let base64 = base64 {
|
|
result(base64)
|
|
} else {
|
|
result(FlutterError(code: "FILE_ERROR", message: "文件转换失败", details: nil))
|
|
}
|
|
} else {
|
|
result(FlutterError(code: "INVALID_ARGUMENT", message: "文件转换参数无效", details: nil))
|
|
}
|
|
|
|
case "initializeMcpClient":
|
|
if let args = call.arguments as? [String: Any],
|
|
let serverUrl = args["serverUrl"] as? String {
|
|
let success = openAIService.initializeMcpClient(serverUrl)
|
|
result(success)
|
|
} else {
|
|
result(FlutterError(code: "INVALID_ARGUMENT", message: "初始化MCP客户端参数无效", details: nil))
|
|
}
|
|
|
|
case "isMcpInitialized":
|
|
result(openAIService.checkMcpInitialized())
|
|
|
|
case "closeMcpClient":
|
|
result(openAIService.closeMcpClient())
|
|
|
|
case "handleMcpToolCall":
|
|
if let args = call.arguments as? [String: Any],
|
|
let functionCallJson = args["functionCall"] as? String {
|
|
|
|
// 解析函数调用JSON
|
|
guard let data = functionCallJson.data(using: .utf8),
|
|
let functionCall = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
result(FlutterError(code: "INVALID_ARGUMENT", message: "无效的函数调用JSON", details: nil))
|
|
return
|
|
}
|
|
|
|
// 在后台线程执行
|
|
Task {
|
|
do {
|
|
let toolResult = await openAIService.handleMcpToolCall(functionCall)
|
|
|
|
// 在主线程返回结果
|
|
DispatchQueue.main.async {
|
|
result(toolResult)
|
|
}
|
|
} catch {
|
|
// 在主线程返回错误
|
|
DispatchQueue.main.async {
|
|
result(FlutterError(code: "MCP_TOOL_ERROR", message: "处理MCP工具调用失败: \(error.localizedDescription)", details: nil))
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
result(FlutterError(code: "INVALID_ARGUMENT", message: "处理MCP工具调用参数无效", details: nil))
|
|
}
|
|
|
|
default:
|
|
result(FlutterMethodNotImplemented)
|
|
}
|
|
}
|
|
|
|
// MARK: - FlutterStreamHandler
|
|
|
|
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
|
|
self.eventSink = events
|
|
return nil
|
|
}
|
|
|
|
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
|
|
self.eventSink = nil
|
|
return nil
|
|
}
|
|
}
|