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:
ARCHITECT
2026-01-16 18:34:02 +00:00
commit 3663e4c622
31 changed files with 2343 additions and 0 deletions

View 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();
}
}