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.
358 lines
8.8 KiB
358 lines
8.8 KiB
import 'dart:async';
|
|
import 'dart:convert';
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
/// 聊天API异常
|
|
class ChatApiException implements Exception {
|
|
final String message;
|
|
|
|
ChatApiException(this.message);
|
|
|
|
@override
|
|
String toString() => 'ChatApiException: $message';
|
|
}
|
|
|
|
/// 聊天API事件类型
|
|
enum ChatApiEventType {
|
|
/// 收到新的token
|
|
token,
|
|
/// 对话完成
|
|
complete,
|
|
/// 发生错误
|
|
error,
|
|
/// 函数调用
|
|
functionCall,
|
|
}
|
|
|
|
/// 聊天API事件
|
|
class ChatApiEvent {
|
|
final ChatApiEventType type;
|
|
final dynamic content;
|
|
final Map<String, dynamic>? meta;
|
|
|
|
ChatApiEvent({
|
|
required this.type,
|
|
this.content,
|
|
this.meta,
|
|
});
|
|
|
|
factory ChatApiEvent.fromMap(Map<String, dynamic> map) {
|
|
final typeStr = map['type'] as String;
|
|
final content = map['content'];
|
|
final meta = map['meta'] as Map<String, dynamic>?;
|
|
|
|
return ChatApiEvent(
|
|
type: _typeFromString(typeStr),
|
|
content: content,
|
|
meta: meta,
|
|
);
|
|
}
|
|
|
|
static ChatApiEventType _typeFromString(String typeStr) {
|
|
switch (typeStr) {
|
|
case 'token':
|
|
return ChatApiEventType.token;
|
|
case 'complete':
|
|
return ChatApiEventType.complete;
|
|
case 'error':
|
|
return ChatApiEventType.error;
|
|
case 'functionCall':
|
|
return ChatApiEventType.functionCall;
|
|
default:
|
|
throw ArgumentError('未知的事件类型: $typeStr');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 消息角色
|
|
enum MessageRole {
|
|
system,
|
|
user,
|
|
assistant,
|
|
function,
|
|
}
|
|
|
|
extension MessageRoleExtension on MessageRole {
|
|
String get value {
|
|
switch (this) {
|
|
case MessageRole.system:
|
|
return 'system';
|
|
case MessageRole.user:
|
|
return 'user';
|
|
case MessageRole.assistant:
|
|
return 'assistant';
|
|
case MessageRole.function:
|
|
return 'function';
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 聊天消息
|
|
class ChatMessage {
|
|
final MessageRole role;
|
|
final dynamic content;
|
|
final String? name;
|
|
final Map<String, dynamic>? functionCall;
|
|
|
|
ChatMessage({
|
|
required this.role,
|
|
required this.content,
|
|
this.name,
|
|
this.functionCall,
|
|
});
|
|
|
|
Map<String, dynamic> toMap() {
|
|
final map = {
|
|
'role': role.value,
|
|
'content': content,
|
|
};
|
|
if (name != null) {
|
|
map['name'] = name!;
|
|
}
|
|
if (functionCall != null) {
|
|
map['function_call'] = functionCall!;
|
|
}
|
|
return map;
|
|
}
|
|
|
|
factory ChatMessage.fromMap(Map<String, dynamic> map) {
|
|
return ChatMessage(
|
|
role: _roleFromString(map['role'] as String),
|
|
content: map['content'],
|
|
name: map['name'] as String?,
|
|
functionCall: map['function_call'] as Map<String, dynamic>?,
|
|
);
|
|
}
|
|
|
|
static MessageRole _roleFromString(String roleStr) {
|
|
switch (roleStr) {
|
|
case 'system':
|
|
return MessageRole.system;
|
|
case 'user':
|
|
return MessageRole.user;
|
|
case 'assistant':
|
|
return MessageRole.assistant;
|
|
case 'function':
|
|
return MessageRole.function;
|
|
default:
|
|
throw ArgumentError('未知的角色类型: $roleStr');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 聊天API插件
|
|
class ChatApi {
|
|
static const MethodChannel _channel =
|
|
MethodChannel('com.yunqiinnovation.chat_api/methods');
|
|
static const EventChannel _eventChannel =
|
|
EventChannel('com.yunqiinnovation.chat_api/events');
|
|
|
|
/// 事件流控制器
|
|
StreamController<ChatApiEvent>? _eventStreamController;
|
|
|
|
/// 事件流
|
|
Stream<ChatApiEvent>? _eventStream;
|
|
|
|
/// 获取事件流
|
|
Stream<ChatApiEvent> get eventStream {
|
|
if (_eventStream == null) {
|
|
_eventStreamController = StreamController<ChatApiEvent>.broadcast();
|
|
_eventStream = _eventStreamController!.stream;
|
|
|
|
// 监听原生事件
|
|
_eventChannel.receiveBroadcastStream().listen(
|
|
(dynamic event) {
|
|
if (event is Map<dynamic, dynamic>) {
|
|
final eventMap = Map<String, dynamic>.from(event);
|
|
final chatApiEvent = ChatApiEvent.fromMap(eventMap);
|
|
_eventStreamController!.add(chatApiEvent);
|
|
}
|
|
},
|
|
onError: (error) {
|
|
_eventStreamController!.addError(ChatApiException('事件流错误: $error'));
|
|
},
|
|
);
|
|
}
|
|
|
|
return _eventStream!;
|
|
}
|
|
|
|
/// 初始化聊天API服务
|
|
///
|
|
/// [apiKey] OpenAI API密钥
|
|
/// [organization] 可选,组织ID
|
|
/// [model] 可选,使用的模型,默认为gpt-3.5-turbo
|
|
/// [baseUrl] 可选,自定义API基础URL
|
|
Future<bool> initialize({
|
|
required String apiKey,
|
|
String? organization,
|
|
String model = 'gpt-3.5-turbo',
|
|
String? baseUrl,
|
|
}) async {
|
|
try {
|
|
final result = await _channel.invokeMethod<bool>(
|
|
'initialize',
|
|
{
|
|
'apiKey': apiKey,
|
|
'organization': organization,
|
|
'model': model,
|
|
'baseUrl': baseUrl,
|
|
},
|
|
);
|
|
|
|
return result ?? false;
|
|
} catch (e) {
|
|
throw ChatApiException('初始化失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 发送聊天消息
|
|
///
|
|
/// [messages] 消息列表
|
|
/// [temperature] 温度参数,控制输出的随机性
|
|
/// [maxTokens] 最大token数
|
|
/// [topP] 核心采样参数
|
|
/// [n] 生成的回复数量
|
|
/// [stop] 停止序列
|
|
/// [presencePenalty] 存在惩罚
|
|
/// [frequencyPenalty] 频率惩罚
|
|
/// [functions] 可用的函数列表
|
|
Future<String> sendMessage({
|
|
required List<ChatMessage> messages,
|
|
double temperature = 0.7,
|
|
int? maxTokens,
|
|
double topP = 1.0,
|
|
int n = 1,
|
|
List<String>? stop,
|
|
double presencePenalty = 0.0,
|
|
double frequencyPenalty = 0.0,
|
|
List<Map<String, dynamic>>? functions,
|
|
}) async {
|
|
try {
|
|
final messagesMap = messages.map((m) => m.toMap()).toList();
|
|
|
|
final result = await _channel.invokeMethod<String>(
|
|
'sendMessage',
|
|
{
|
|
'messages': messagesMap,
|
|
'temperature': temperature,
|
|
'maxTokens': maxTokens,
|
|
'topP': topP,
|
|
'n': n,
|
|
'stop': stop,
|
|
'presencePenalty': presencePenalty,
|
|
'frequencyPenalty': frequencyPenalty,
|
|
'functions': functions,
|
|
},
|
|
);
|
|
|
|
if (result == null) {
|
|
throw ChatApiException('发送消息失败: 结果为空');
|
|
}
|
|
|
|
return result;
|
|
} catch (e) {
|
|
throw ChatApiException('发送消息失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 发送聊天消息(流式输出)
|
|
///
|
|
/// [messages] 消息列表
|
|
/// [temperature] 温度参数,控制输出的随机性
|
|
/// [maxTokens] 最大token数
|
|
/// [topP] 核心采样参数
|
|
/// [n] 生成的回复数量
|
|
/// [stop] 停止序列
|
|
/// [presencePenalty] 存在惩罚
|
|
/// [frequencyPenalty] 频率惩罚
|
|
/// [functions] 可用的函数列表
|
|
Future<bool> sendMessageStream({
|
|
required List<ChatMessage> messages,
|
|
double temperature = 0.7,
|
|
int? maxTokens,
|
|
double topP = 1.0,
|
|
int n = 1,
|
|
List<String>? stop,
|
|
double presencePenalty = 0.0,
|
|
double frequencyPenalty = 0.0,
|
|
List<Map<String, dynamic>>? functions,
|
|
}) async {
|
|
try {
|
|
final messagesMap = messages.map((m) => m.toMap()).toList();
|
|
|
|
final result = await _channel.invokeMethod<bool>(
|
|
'sendMessageStream',
|
|
{
|
|
'messages': messagesMap,
|
|
'temperature': temperature,
|
|
'maxTokens': maxTokens,
|
|
'topP': topP,
|
|
'n': n,
|
|
'stop': stop,
|
|
'presencePenalty': presencePenalty,
|
|
'frequencyPenalty': frequencyPenalty,
|
|
'functions': functions,
|
|
},
|
|
);
|
|
|
|
return result ?? false;
|
|
} catch (e) {
|
|
throw ChatApiException('发送流式消息失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 取消当前的流式请求
|
|
Future<bool> cancelStream() async {
|
|
try {
|
|
final result = await _channel.invokeMethod<bool>('cancelStream');
|
|
return result ?? false;
|
|
} catch (e) {
|
|
throw ChatApiException('取消流式请求失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 获取可用的模型列表
|
|
Future<List<String>> getAvailableModels() async {
|
|
try {
|
|
final result = await _channel.invokeMethod<List<dynamic>>('getAvailableModels');
|
|
return result?.cast<String>() ?? [];
|
|
} catch (e) {
|
|
throw ChatApiException('获取模型列表失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 设置默认模型
|
|
Future<bool> setModel(String model) async {
|
|
try {
|
|
final result = await _channel.invokeMethod<bool>(
|
|
'setModel',
|
|
{'model': model},
|
|
);
|
|
return result ?? false;
|
|
} catch (e) {
|
|
throw ChatApiException('设置模型失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 估算token数量
|
|
Future<int> countTokens(String text) async {
|
|
try {
|
|
final result = await _channel.invokeMethod<int>(
|
|
'countTokens',
|
|
{'text': text},
|
|
);
|
|
return result ?? 0;
|
|
} catch (e) {
|
|
throw ChatApiException('计算token失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 释放资源
|
|
Future<void> dispose() async {
|
|
await _eventStreamController?.close();
|
|
_eventStreamController = null;
|
|
_eventStream = null;
|
|
}
|
|
}
|