Files
MaliangAINovalWriter/AINoval/lib/services/websocket_service.dart
2025-09-10 00:07:52 +08:00

205 lines
6.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:convert';
import 'package:stream_channel/stream_channel.dart';
import 'package:uuid/uuid.dart';
import 'package:web_socket_channel/status.dart' as status;
import 'package:web_socket_channel/web_socket_channel.dart';
import '../models/chat_models.dart';
class WebSocketService {
WebSocketService({
this.baseUrl = 'ws://localhost:8080/chat',
});
final String baseUrl;
final Map<String, WebSocketChannel> _connections = {};
// 创建聊天连接
Future<WebSocketChannel> createChatConnection(String sessionId) async {
// 在第二周迭代中我们不实际连接WebSocket而是模拟
// 返回一个模拟的WebSocketChannel
final channel = MockWebSocketChannel(sessionId: sessionId);
_connections[sessionId] = channel;
return channel;
}
// 关闭连接
void closeConnection(String sessionId) {
if (_connections.containsKey(sessionId)) {
_connections[sessionId]?.sink.close(status.goingAway);
_connections.remove(sessionId);
}
}
// 关闭所有连接
void closeAllConnections() {
for (final connection in _connections.values) {
connection.sink.close(status.goingAway);
}
_connections.clear();
}
}
// 简化的模拟WebSocketChannel实现
class MockWebSocketChannel extends StreamChannelMixin implements WebSocketChannel {
MockWebSocketChannel({required this.sessionId}) {
_sink = MockWebSocketSink(_sinkController);
// 监听发送的消息,模拟响应
_sinkController.stream.listen(_handleMessage);
}
final String sessionId;
final StreamController<dynamic> _controller = StreamController<dynamic>();
final StreamController<dynamic> _sinkController = StreamController<dynamic>();
late final MockWebSocketSink _sink;
@override
Stream get stream => _controller.stream;
@override
WebSocketSink get sink => _sink;
// 处理发送的消息,模拟响应
void _handleMessage(dynamic message) {
if (message is String) {
try {
final Map<String, dynamic> data = jsonDecode(message);
if (data.containsKey('action') && data['action'] == 'cancel') {
// 模拟取消请求
_controller.add(jsonEncode({
'done': true,
'message': '请求已取消',
}));
return;
}
if (data.containsKey('message')) {
// 模拟流式响应
_simulateStreamingResponse(data['message'] as String);
}
} catch (e) {
_controller.addError('解析消息失败: $e');
}
}
}
// 模拟流式响应
void _simulateStreamingResponse(String message) async {
// 根据消息内容生成不同的响应
String response;
List<MessageAction> actions = [];
if (message.contains('角色')) {
response = '角色设计是小说创作中的重要环节。好的角色应该有鲜明的性格特点、合理的动机和明确的目标。';
actions.add(MessageAction(
id: const Uuid().v4(),
label: '创建角色',
type: ActionType.createCharacter,
data: {'suggestion': '根据对话创建新角色'},
));
} else if (message.contains('情节')) {
response = '情节发展需要有起承转合,保持读者的兴趣。一个好的情节应该包含引人入胜的开端、不断升级的冲突、出人意料的转折和合理的结局。';
actions.add(MessageAction(
id: const Uuid().v4(),
label: '生成情节',
type: ActionType.generatePlot,
data: {'suggestion': '根据当前内容生成情节'},
));
} else {
response = '感谢您的提问。作为您的AI写作助手我很乐意帮助您解决创作中遇到的问题。请告诉我您需要什么样的帮助';
}
// 始终添加一个应用到编辑器的操作
actions.add(MessageAction(
id: const Uuid().v4(),
label: '应用到编辑器',
type: ActionType.applyToEditor,
data: {'suggestion': '将AI回复应用到编辑器'},
));
// 模拟流式响应,将响应分成多个块发送
final chunks = _splitIntoChunks(response, 10);
for (int i = 0; i < chunks.length; i++) {
// 添加随机延迟,模拟网络延迟
await Future.delayed(Duration(milliseconds: 100 + (50 * i)));
// 发送块
_controller.add(jsonEncode({
'chunk': chunks[i],
}));
}
// 发送完成信号和操作
await Future.delayed(const Duration(milliseconds: 500));
_controller.add(jsonEncode({
'done': true,
'actions': actions.map((a) => a.toJson()).toList(),
}));
}
// 将文本分成多个块
List<String> _splitIntoChunks(String text, int chunkSize) {
final chunks = <String>[];
for (int i = 0; i < text.length; i += chunkSize) {
final end = (i + chunkSize < text.length) ? i + chunkSize : text.length;
chunks.add(text.substring(i, end));
}
return chunks;
}
@override
Future<void> close([int? closeCode, String? closeReason]) {
_sinkController.close();
return _controller.close();
}
// WebSocketChannel 接口所需的属性
@override
int? get closeCode => null;
@override
String? get closeReason => null;
@override
String? get protocol => null;
@override
Future<void> get ready => Future.value();
}
// 模拟的WebSocketSink
class MockWebSocketSink implements WebSocketSink {
MockWebSocketSink(this._controller);
final StreamController<dynamic> _controller;
@override
void add(dynamic data) {
_controller.add(data);
}
@override
void addError(Object error, [StackTrace? stackTrace]) {
_controller.addError(error, stackTrace);
}
@override
Future<void> addStream(Stream<dynamic> stream) async {
await for (final data in stream) {
add(data);
}
}
@override
Future<void> close([int? closeCode, String? closeReason]) async {
// 不实际关闭,因为这是模拟的
return Future.value();
}
@override
Future<void> get done => Future.value();
}