Stabilize frontend login. Make chat messages multiline.

This commit is contained in:
Shivering Ocelot committed 2025-05-31 13:33:42 -05:00
1 parent 5bbdfe8747
commit 3f37f727af
6 files changed
+71 -31

No files matched your search

-2
View File
@@ -34,8 +34,6 @@ class Agent:
] ]
tools = [] tools = []
# while this constructor accepts two parameters,
# all subclasses must also accept a fourth 'data' parameter
def __init__(self, app, agent_id, data): def __init__(self, app, agent_id, data):
self.app = app self.app = app
self.agent_id = agent_id self.agent_id = agent_id
+2 -4
View File
@@ -31,7 +31,7 @@ class App:
if replicate_api_token: if replicate_api_token:
self.replicate_client = replicate.Client(api_token=replicate_api_token) self.replicate_client = replicate.Client(api_token=replicate_api_token)
else: else:
raise Exception("Horde only works with a replicate client. Please provide an API Token.") raise Exception("Horde only works with a replicate connection. Please provide an API Token.")
# `Agent` is the kind 'basic', will be class `BasicAgent` in the future # `Agent` is the kind 'basic', will be class `BasicAgent` in the future
self.agent_classes = [Agent] self.agent_classes = [Agent]
@@ -119,7 +119,7 @@ class App:
if data: if data:
# Assign a copy to trigger a value change # Assign a copy to trigger a value change
agent.data = agent.data.copy() agent.data = agent.data.copy() if agent.data else {}
agent.data.update(data) agent.data.update(data)
summary.extend([f"data.{name}" for name in data.keys()]) summary.extend([f"data.{name}" for name in data.keys()])
@@ -335,8 +335,6 @@ if __name__ == "__main__":
print("No directory specified. Using current directory as default.") print("No directory specified. Using current directory as default.")
data_directory = "." data_directory = "."
print("api token", os.getenv('REPLICATE_API_TOKEN'))
app = App( app = App(
data_directory=data_directory, data_directory=data_directory,
replicate_api_token=os.getenv('REPLICATE_API_TOKEN'), replicate_api_token=os.getenv('REPLICATE_API_TOKEN'),
+5
View File
@@ -86,6 +86,10 @@ def start_server(app):
await websocket.send(json.dumps({'error': 'invalid credentials'})) await websocket.send(json.dumps({'error': 'invalid credentials'}))
return return
# TODO: print username instead, same in the finally block
print("User logged in:", user_id)
await websocket.send(json.dumps({'message': 'authentication successful'}))
send_queue = asyncio.Queue() send_queue = asyncio.Queue()
async def sender_task(): async def sender_task():
nonlocal send_queue nonlocal send_queue
@@ -190,6 +194,7 @@ def start_server(app):
else: else:
await send({'error': 'unknown action: ' + action, 'nonce': nonce}) await send({'error': 'unknown action: ' + action, 'nonce': nonce})
finally: finally:
print("User logged out:", user_id)
app.remove_user_connection(user_id, send_queue) app.remove_user_connection(user_id, send_queue)
server.run(debug=True) server.run(debug=True)
+25 -7
View File
@@ -24,7 +24,7 @@ class Backend {
// Authentication // Authentication
static ({String username, String password})? _credentials; static ({String username, String password})? _credentials;
static Completer? _loginCompleter; static Completer<bool>? _loginCompleter;
static Completer? _credentialsCompleter; static Completer? _credentialsCompleter;
static void setup(String url) { static void setup(String url) {
@@ -49,8 +49,11 @@ class Backend {
} }
} }
static Future<void> auth(String username, String password) { static Future<bool> auth(String username, String password) {
_socket!.send(json.encode({'username': username, 'password': password})); if (_credentials != null) {
print("Cannot authenticate twice");
return Future.value(false);
}
_credentials = (username: username, password: password); _credentials = (username: username, password: password);
if (_credentialsCompleter != null) { if (_credentialsCompleter != null) {
_credentialsCompleter!.complete(_credentials); _credentialsCompleter!.complete(_credentials);
@@ -62,11 +65,13 @@ class Backend {
static void connect() { static void connect() {
_socket = WebSocket(_url! + "/ws"); _socket = WebSocket(_url! + "/ws");
_socket!.onOpen.listen((event) { _socket!.onOpen.listen((event) async {
print('Connected to the WebSocket server'); print('Connected to the WebSocket server');
Backend.invalidateCache(chat: true); Backend.invalidateCache(chat: true);
print('Logging in...'); print('Logging in...');
// secure the listener for the response before sending credentials
var responseFuture = nextIncomingMessage();
if (_credentials != null) { if (_credentials != null) {
_socket!.send(json.encode({'username': _credentials!.username, 'password': _credentials!.password})); _socket!.send(json.encode({'username': _credentials!.username, 'password': _credentials!.password}));
} else { } else {
@@ -75,7 +80,14 @@ class Backend {
_socket!.send(json.encode({'username': credentials.username, 'password': credentials.password})); _socket!.send(json.encode({'username': credentials.username, 'password': credentials.password}));
}); });
} }
_loginCompleter?.complete(); var response = await responseFuture;
if (response['error'] == null) {
_loginCompleter?.complete(true);
} else {
_loginCompleter?.complete(false);
_socket!.close(); // Retry
}
_loginCompleter = null;
}); });
_socket!.onError.listen((event) { _socket!.onError.listen((event) {
print('Error: $event'); print('Error: $event');
@@ -92,11 +104,10 @@ class Backend {
static void disconnect() { static void disconnect() {
_socket?.close(); _socket?.close();
_loginCompleter = null;
} }
static void _reconnect() { static void _reconnect() {
Future.delayed(Duration(seconds: 5), () { Future.delayed(Duration(seconds: 1), () {
connect(); connect();
}); });
} }
@@ -140,10 +151,17 @@ class Backend {
print("No active listeners for chatId $chatId"); print("No active listeners for chatId $chatId");
} }
} else { } else {
if (jsonData['error'] != null) {
print("Error: ${jsonData['error']}");
}
_packetStreamController.add(jsonData); _packetStreamController.add(jsonData);
} }
} }
static Future<Map<String, dynamic>> nextIncomingMessage() {
return _packetStreamController.stream.first;
}
static Stream<Map<String, dynamic>> chatMessages(int chatId) { static Stream<Map<String, dynamic>> chatMessages(int chatId) {
if (!_chatControllers.containsKey(chatId) || _chatControllers[chatId]!.isClosed) { if (!_chatControllers.containsKey(chatId) || _chatControllers[chatId]!.isClosed) {
_chatControllers[chatId] = StreamController<Map<String, dynamic>>.broadcast(); _chatControllers[chatId] = StreamController<Map<String, dynamic>>.broadcast();
+31 -16
View File
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'app_menu.dart'; import 'app_menu.dart';
import 'styles.dart'; import 'styles.dart';
import 'backend.dart'; import 'backend.dart';
@@ -36,6 +37,7 @@ class _ChatScreenState extends State<ChatScreen> {
String? _title; String? _title;
List<Message> _messages = []; List<Message> _messages = [];
final TextEditingController _textController = TextEditingController(); final TextEditingController _textController = TextEditingController();
final FocusNode _inputFocusNode = FocusNode();
StreamSubscription? _chatSubscription; StreamSubscription? _chatSubscription;
@override @override
@@ -137,23 +139,36 @@ class _ChatScreenState extends State<ChatScreen> {
), ),
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Row( child: KeyboardListener(
children: [ focusNode: _inputFocusNode,
Expanded( onKeyEvent: (event) {
child: TextField( // TODO: Probably doing this in the input focus node would be cleaner
controller: _textController, if (HardwareKeyboard.instance.isControlPressed && HardwareKeyboard.instance.isLogicalKeyPressed(LogicalKeyboardKey.enter)) {
decoration: inputDecoration(), sendMessage(_textController.text);
onSubmitted: sendMessage, // Send message on Enter key _textController.clear();
//return KeyEventResult.handled;
}
//return KeyEventResult.ignored;
},
child: Column(
children: [
Expanded(
child: TextField(
controller: _textController,
maxLines: null,
decoration: inputDecoration(),
),
), ),
), SizedBox(width: 8),
SizedBox(width: 8), ElevatedButton(
ElevatedButton( onPressed: () {
onPressed: () { sendMessage(_textController.text);
sendMessage(_textController.text); _textController.clear();
}, },
child: Text('Send'), child: Text('Send'),
), ),
], ],
),
), ),
), ),
], ],
+8 -2
View File
@@ -44,12 +44,18 @@ class _UserCheckerState extends State<UserChecker> {
} }
void _handleLogin(String user, String password) async { void _handleLogin(String user, String password) async {
bool success = await Backend.auth(user, password);
if (!success) {
setState(() {
isLoading = false;
});
return;
}
setState(() { setState(() {
username = user; username = user;
isLoading = true; isLoading = true;
}); });
await Backend.auth(user, password);
await _storage.write(key: 'username', value: user); await _storage.write(key: 'username', value: user);
await _storage.write(key: 'password', value: password); await _storage.write(key: 'password', value: password);