Initial commit: Captain Claude Mobile App
- Flutter app with chat and terminal screens - WebSocket integration for real-time chat - xterm integration for screen sessions - Markdown rendering with code blocks - JWT authentication Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
105
lib/services/chat_service.dart
Normal file
105
lib/services/chat_service.dart
Normal file
@@ -0,0 +1,105 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
import '../config/api_config.dart';
|
||||
|
||||
enum ChatConnectionState {
|
||||
disconnected,
|
||||
connecting,
|
||||
connected,
|
||||
error,
|
||||
}
|
||||
|
||||
class ChatService {
|
||||
WebSocketChannel? _channel;
|
||||
String? _token;
|
||||
|
||||
final _messageController = StreamController<Map<String, dynamic>>.broadcast();
|
||||
final _stateController = StreamController<ChatConnectionState>.broadcast();
|
||||
|
||||
Stream<Map<String, dynamic>> get messages => _messageController.stream;
|
||||
Stream<ChatConnectionState> get connectionState => _stateController.stream;
|
||||
|
||||
ChatConnectionState _currentState = ChatConnectionState.disconnected;
|
||||
ChatConnectionState get currentState => _currentState;
|
||||
|
||||
void setToken(String? token) {
|
||||
_token = token;
|
||||
}
|
||||
|
||||
Future<void> connect() async {
|
||||
if (_token == null) {
|
||||
_updateState(ChatConnectionState.error);
|
||||
return;
|
||||
}
|
||||
|
||||
_updateState(ChatConnectionState.connecting);
|
||||
|
||||
try {
|
||||
final uri = Uri.parse('${ApiConfig.wsUrl}${ApiConfig.wsChat}');
|
||||
_channel = WebSocketChannel.connect(uri);
|
||||
|
||||
await _channel!.ready;
|
||||
|
||||
// Send auth token
|
||||
_channel!.sink.add(jsonEncode({'token': _token}));
|
||||
|
||||
_channel!.stream.listen(
|
||||
(data) {
|
||||
try {
|
||||
final message = jsonDecode(data);
|
||||
if (message['type'] == 'connected') {
|
||||
_updateState(ChatConnectionState.connected);
|
||||
}
|
||||
_messageController.add(message);
|
||||
} catch (e) {
|
||||
// Handle parse error
|
||||
}
|
||||
},
|
||||
onError: (error) {
|
||||
_updateState(ChatConnectionState.error);
|
||||
},
|
||||
onDone: () {
|
||||
_updateState(ChatConnectionState.disconnected);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
_updateState(ChatConnectionState.error);
|
||||
}
|
||||
}
|
||||
|
||||
void sendMessage(String content, {List<String>? files}) {
|
||||
if (_channel == null || _currentState != ChatConnectionState.connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
_channel!.sink.add(jsonEncode({
|
||||
'type': 'message',
|
||||
'content': content,
|
||||
'files': files ?? [],
|
||||
}));
|
||||
}
|
||||
|
||||
void ping() {
|
||||
if (_channel != null && _currentState == ChatConnectionState.connected) {
|
||||
_channel!.sink.add(jsonEncode({'type': 'ping'}));
|
||||
}
|
||||
}
|
||||
|
||||
void _updateState(ChatConnectionState state) {
|
||||
_currentState = state;
|
||||
_stateController.add(state);
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
_channel?.sink.close();
|
||||
_channel = null;
|
||||
_updateState(ChatConnectionState.disconnected);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
disconnect();
|
||||
_messageController.close();
|
||||
_stateController.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user