-
Notifications
You must be signed in to change notification settings - Fork 66
setup #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
setup #27
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,42 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| package com.example; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import javafx.fxml.FXML; | ||||||||||||||||||||||||||||||||||||||||||||
| import javafx.scene.control.ListView; | ||||||||||||||||||||||||||||||||||||||||||||
| import javafx.scene.control.TextField; | ||||||||||||||||||||||||||||||||||||||||||||
| import javafx.application.Platform; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| public class ChatController { | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @FXML | ||||||||||||||||||||||||||||||||||||||||||||
| private ListView<String> messagesList; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @FXML | ||||||||||||||||||||||||||||||||||||||||||||
| private TextField inputField; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private final ChatModel model = new ChatModel(); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @FXML | ||||||||||||||||||||||||||||||||||||||||||||
| private void onSend() { | ||||||||||||||||||||||||||||||||||||||||||||
| String message = inputField.getText().trim(); | ||||||||||||||||||||||||||||||||||||||||||||
| if (!message.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||
| messagesList.getItems().add("Me: " + message); | ||||||||||||||||||||||||||||||||||||||||||||
| model.sendMessage(message); | ||||||||||||||||||||||||||||||||||||||||||||
| inputField.clear(); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+20
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for model.sendMessage(). The Apply this diff to add error handling: @FXML
private void onSend() {
String message = inputField.getText().trim();
if (!message.isEmpty()) {
- messagesList.getItems().add("Me: " + message);
- model.sendMessage(message);
- inputField.clear();
+ try {
+ messagesList.getItems().add("Me: " + message);
+ model.sendMessage(message);
+ inputField.clear();
+ } catch (Exception e) {
+ System.err.println("Failed to send message: " + e.getMessage());
+ // TODO: Show error dialog to user
+ }
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @FXML | ||||||||||||||||||||||||||||||||||||||||||||
| private void initialize(){ | ||||||||||||||||||||||||||||||||||||||||||||
| model.subscribe(msg -> { | ||||||||||||||||||||||||||||||||||||||||||||
| Platform.runLater(() -> messagesList.getItems().add("Friend: " + msg)); | ||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,117 @@ | ||||||||||||||||||||||||||||||
| package com.example; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import javafx.application.Platform; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import java.io.BufferedReader; | ||||||||||||||||||||||||||||||
| import java.io.InputStreamReader; | ||||||||||||||||||||||||||||||
| import java.net.URI; | ||||||||||||||||||||||||||||||
| import java.net.http.HttpClient; | ||||||||||||||||||||||||||||||
| import java.net.http.HttpRequest; | ||||||||||||||||||||||||||||||
| import java.net.http.HttpResponse; | ||||||||||||||||||||||||||||||
| import java.util.Collections; | ||||||||||||||||||||||||||||||
| import java.util.Set; | ||||||||||||||||||||||||||||||
| import java.util.UUID; | ||||||||||||||||||||||||||||||
| import java.util.concurrent.ConcurrentHashMap; | ||||||||||||||||||||||||||||||
| import java.util.function.Consumer; | ||||||||||||||||||||||||||||||
| import java.util.regex.Matcher; | ||||||||||||||||||||||||||||||
| import java.util.regex.Pattern; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| public class ChatModel { | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| private final String sendUrl; | ||||||||||||||||||||||||||||||
| private final String subscribeUrl; | ||||||||||||||||||||||||||||||
| private final String clientId; | ||||||||||||||||||||||||||||||
| private final Set<String> sentMessages = Collections.newSetFromMap(new ConcurrentHashMap<>()); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| public ChatModel() { | ||||||||||||||||||||||||||||||
| String topic = System.getenv().getOrDefault("NTFY_TOPIC", "https://ntfy.sh/newchatroom3"); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| sendUrl = topic; | ||||||||||||||||||||||||||||||
| subscribeUrl = topic + "/sse"; | ||||||||||||||||||||||||||||||
| clientId = UUID.randomUUID().toString(); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| public void sendMessage(String message) { | ||||||||||||||||||||||||||||||
| sentMessages.add(message); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| new Thread(() -> { | ||||||||||||||||||||||||||||||
| try { Thread.sleep(5000); } catch (Exception ignored) {} | ||||||||||||||||||||||||||||||
| sentMessages.remove(message); | ||||||||||||||||||||||||||||||
| }).start(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| HttpClient client = HttpClient.newHttpClient(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| HttpRequest request = HttpRequest.newBuilder() | ||||||||||||||||||||||||||||||
| .uri(URI.create(sendUrl)) | ||||||||||||||||||||||||||||||
| .header("Content-Type", "application/json") | ||||||||||||||||||||||||||||||
| .header("X-Client-ID", clientId) | ||||||||||||||||||||||||||||||
| .header("Title", "Friend: ") | ||||||||||||||||||||||||||||||
| .POST(HttpRequest.BodyPublishers.ofString(message)) | ||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||
|
Comment on lines
+44
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix the Content-Type/body mismatch to unblock sending. We POST the raw message string but advertise Apply this diff to fix the header: - .header("Content-Type", "application/json")
+ .header("Content-Type", "text/plain; charset=UTF-8")📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) | ||||||||||||||||||||||||||||||
| .thenAccept(response -> System.out.println("Sent, status=" + response.statusCode())) | ||||||||||||||||||||||||||||||
| .exceptionally(e -> { e.printStackTrace(); return null; }); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| public void subscribe(Consumer<String> onMessageReceived) { | ||||||||||||||||||||||||||||||
| HttpClient client = HttpClient.newHttpClient(); | ||||||||||||||||||||||||||||||
| HttpRequest request = HttpRequest.newBuilder() | ||||||||||||||||||||||||||||||
| .uri(URI.create(subscribeUrl)) | ||||||||||||||||||||||||||||||
| .header("Accept", "text/event-stream") | ||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| client.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()) | ||||||||||||||||||||||||||||||
| .thenAccept(response -> { | ||||||||||||||||||||||||||||||
| try (BufferedReader reader = new BufferedReader(new InputStreamReader(response.body()))) { | ||||||||||||||||||||||||||||||
| String line; | ||||||||||||||||||||||||||||||
| boolean firstMessageSkipped = false; | ||||||||||||||||||||||||||||||
| while ((line = reader.readLine()) != null) { | ||||||||||||||||||||||||||||||
| if (line.startsWith("data:")) { | ||||||||||||||||||||||||||||||
| if (!firstMessageSkipped) { | ||||||||||||||||||||||||||||||
| firstMessageSkipped = true; | ||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| String raw = line.substring(5).trim(); | ||||||||||||||||||||||||||||||
| String msg = parseMessage(raw); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (msg != null && !sentMessages.contains(msg)) { | ||||||||||||||||||||||||||||||
| Platform.runLater(() -> onMessageReceived.accept(msg)); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||
| e.printStackTrace(); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| .exceptionally(e -> { e.printStackTrace(); return null; }); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| private String parseMessage(String data) { | ||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||
| Matcher eventMatcher = Pattern.compile("\"event\"\\s*:\\s*\"(.*?)\"").matcher(data); | ||||||||||||||||||||||||||||||
| if (eventMatcher.find()) { | ||||||||||||||||||||||||||||||
| String event = eventMatcher.group(1); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (!"message".equals(event)) { | ||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Matcher msgMatcher = Pattern.compile("\"message\"\\s*:\\s*\"(.*?)\"").matcher(data); | ||||||||||||||||||||||||||||||
| if (msgMatcher.find()) { | ||||||||||||||||||||||||||||||
| return msgMatcher.group(1).replace("\\\"", "\""); | ||||||||||||||||||||||||||||||
|
Comment on lines
+77
to
+105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't drop legitimate messages that match our recent text.
Apply this diff to switch to client-based filtering: - String raw = line.substring(5).trim();
- String msg = parseMessage(raw);
-
- if (msg != null && !sentMessages.contains(msg)) {
- Platform.runLater(() -> onMessageReceived.accept(msg));
- }
+ String raw = line.substring(5).trim();
+ MessagePayload payload = parseMessage(raw);
+
+ if (payload != null && !clientId.equals(payload.client())) {
+ Platform.runLater(() -> onMessageReceived.accept(payload.message()));
+ }And adjust - private String parseMessage(String data) {
+ private MessagePayload parseMessage(String data) {
try {
Matcher eventMatcher = Pattern.compile("\"event\"\\s*:\\s*\"(.*?)\"").matcher(data);
if (eventMatcher.find()) {
String event = eventMatcher.group(1);
if (!"message".equals(event)) {
return null;
}
}
Matcher msgMatcher = Pattern.compile("\"message\"\\s*:\\s*\"(.*?)\"").matcher(data);
if (msgMatcher.find()) {
- return msgMatcher.group(1).replace("\\\"", "\"");
+ String message = msgMatcher.group(1).replace("\\\"", "\"");
+ Matcher clientMatcher = Pattern.compile("\"client\"\\s*:\\s*\"(.*?)\"").matcher(data);
+ String client = clientMatcher.find() ? clientMatcher.group(1) : null;
+ return new MessagePayload(message, client);
}
} catch (Exception ignored) {}
return null;
}
+
+ private record MessagePayload(String message, String client) {}Once this is in place you can drop the |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } catch (Exception ignored) {} | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
|
|
||
| <?import javafx.scene.layout.BorderPane?> | ||
| <?import javafx.scene.control.ListView?> | ||
| <?import javafx.scene.control.TextField?> | ||
| <?import javafx.scene.control.Button?> | ||
| <?import javafx.scene.layout.HBox?> | ||
|
|
||
| <BorderPane xmlns="http://javafx.com/javafx" | ||
| xmlns:fx="http://javafx.com/fxml" | ||
| fx:controller="com.example.ChatController"> | ||
|
|
||
| <center> | ||
| <ListView fx:id="messagesList"/> | ||
| </center> | ||
|
|
||
| <bottom> | ||
| <HBox spacing="8" style="-fx-padding: 10;"> | ||
| <TextField fx:id="inputField" HBox.hgrow="ALWAYS"/> | ||
| <Button text="Send" onAction="#onSend"/> | ||
| </HBox> | ||
| </bottom> | ||
|
|
||
| </BorderPane> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Prefer dependency injection over direct instantiation for better testability.
The direct instantiation of
ChatModelcreates tight coupling and makes the controller difficult to unit test.Consider using constructor injection:
This allows you to inject a mock
ChatModelduring testing.🤖 Prompt for AI Agents