Defer Chat Attachments

This commit is contained in:
2026-04-09 22:47:57 -06:00
parent 801b7dc872
commit 738ad0003b
11 changed files with 22 additions and 426 deletions

View File

@@ -1,43 +0,0 @@
package org.example.petshopdesktop.api.dto.chat;
public class ChatAttachmentResponse {
private String url;
private String fileName;
private String mimeType;
private long size;
public ChatAttachmentResponse() {
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
}

View File

@@ -2,10 +2,6 @@ package org.example.petshopdesktop.api.dto.chat;
public class MessageRequest {
private String content;
private String attachmentUrl;
private String attachmentName;
private String attachmentMimeType;
private Long attachmentSizeBytes;
public MessageRequest() {
}
@@ -21,36 +17,4 @@ public class MessageRequest {
public void setContent(String content) {
this.content = content;
}
public String getAttachmentUrl() {
return attachmentUrl;
}
public void setAttachmentUrl(String attachmentUrl) {
this.attachmentUrl = attachmentUrl;
}
public String getAttachmentName() {
return attachmentName;
}
public void setAttachmentName(String attachmentName) {
this.attachmentName = attachmentName;
}
public String getAttachmentMimeType() {
return attachmentMimeType;
}
public void setAttachmentMimeType(String attachmentMimeType) {
this.attachmentMimeType = attachmentMimeType;
}
public Long getAttachmentSizeBytes() {
return attachmentSizeBytes;
}
public void setAttachmentSizeBytes(Long attachmentSizeBytes) {
this.attachmentSizeBytes = attachmentSizeBytes;
}
}

View File

@@ -10,10 +10,6 @@ public class MessageResponse {
private String content;
private LocalDateTime timestamp;
private Boolean isRead;
private String attachmentUrl;
private String attachmentName;
private String attachmentMimeType;
private Long attachmentSizeBytes;
public MessageResponse() {
}
@@ -73,36 +69,4 @@ public class MessageResponse {
public void setIsRead(Boolean isRead) {
this.isRead = isRead;
}
public String getAttachmentUrl() {
return attachmentUrl;
}
public void setAttachmentUrl(String attachmentUrl) {
this.attachmentUrl = attachmentUrl;
}
public String getAttachmentName() {
return attachmentName;
}
public void setAttachmentName(String attachmentName) {
this.attachmentName = attachmentName;
}
public String getAttachmentMimeType() {
return attachmentMimeType;
}
public void setAttachmentMimeType(String attachmentMimeType) {
this.attachmentMimeType = attachmentMimeType;
}
public Long getAttachmentSizeBytes() {
return attachmentSizeBytes;
}
public void setAttachmentSizeBytes(Long attachmentSizeBytes) {
this.attachmentSizeBytes = attachmentSizeBytes;
}
}

View File

@@ -2,13 +2,10 @@ package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.chat.ChatAttachmentResponse;
import org.example.petshopdesktop.api.dto.chat.ConversationRequest;
import org.example.petshopdesktop.api.dto.chat.ConversationResponse;
import org.example.petshopdesktop.api.dto.chat.MessageRequest;
import org.example.petshopdesktop.api.dto.chat.MessageResponse;
import java.nio.file.Path;
import java.util.List;
public class ChatApi {
@@ -44,8 +41,4 @@ public class ChatApi {
public MessageResponse sendMessage(Long conversationId, MessageRequest request) throws Exception {
return apiClient.post("/api/v1/chat/conversations/" + conversationId + "/messages", request, MessageResponse.class);
}
public ChatAttachmentResponse uploadAttachment(Path filePath) throws Exception {
return apiClient.postMultipart("/api/v1/chat/attachments", "file", filePath, ChatAttachmentResponse.class);
}
}

View File

@@ -20,7 +20,6 @@ import javafx.scene.paint.ImagePattern;
import javafx.scene.shape.Circle;
import javafx.scene.image.Image;
import org.example.petshopdesktop.api.ChatRealtimeClient;
import org.example.petshopdesktop.api.dto.chat.ChatAttachmentResponse;
import org.example.petshopdesktop.api.dto.chat.ConversationResponse;
import org.example.petshopdesktop.api.dto.chat.MessageRequest;
import org.example.petshopdesktop.api.dto.chat.MessageResponse;
@@ -30,6 +29,7 @@ import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.File;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.HashMap;
@@ -71,7 +71,7 @@ public class ChatController {
private final Map<Long, String> customerLabels = new HashMap<>();
private final ChatRealtimeClient realtimeClient = ChatRealtimeClient.getInstance();
private ConversationResponse selectedConversation;
private ChatAttachmentResponse selectedAttachment;
private File selectedAttachmentFile;
@FXML
public void initialize() {
@@ -155,42 +155,38 @@ public class ChatController {
}
String content = txtMessage.getText() == null ? "" : txtMessage.getText().trim();
if (content.isEmpty() && selectedAttachment == null) {
if (content.isEmpty()) {
if (selectedAttachmentFile != null) {
lblChatStatus.setText("Attachments are not available yet");
}
return;
}
Long convId = selectedConversation.getId();
var attachment = selectedAttachment;
txtMessage.clear();
btnSend.setDisable(true);
selectedAttachment = null;
btnAttachment.setText("📎");
btnAttachment.setStyle("-fx-background-color: #e2e8f0; -fx-background-radius: 12; -fx-text-fill: #475569; -fx-cursor: hand;");
lblChatStatus.setText("Sending message...");
new Thread(() -> {
try {
MessageRequest request = new MessageRequest(content);
if (attachment != null) {
request.setAttachmentUrl(attachment.getUrl());
request.setAttachmentName(attachment.getFileName());
request.setAttachmentMimeType(attachment.getMimeType());
request.setAttachmentSizeBytes(attachment.getSize());
}
MessageResponse response = ChatApi.getInstance().sendMessage(convId, request);
Platform.runLater(() -> {
btnSend.setDisable(false);
appendMessageIfSelected(response);
lblChatStatus.setText("Message sent");
if (selectedAttachmentFile != null) {
clearLocalAttachment();
lblChatStatus.setText("Message sent without attachment");
} else {
lblChatStatus.setText("Message sent");
}
});
} catch (Exception e) {
Platform.runLater(() -> {
txtMessage.setText(content);
btnSend.setDisable(false);
selectedAttachment = attachment;
btnAttachment.setText("📎 " + attachment.getFileName());
lblChatStatus.setText("Chat send failed");
ActivityLogger.getInstance().logException(
"ChatController.sendMessage",
@@ -203,30 +199,19 @@ public class ChatController {
@FXML
void btnAttachmentClicked(ActionEvent event) {
java.io.File file = org.example.petshopdesktop.util.FilePickerSupport.pickAnyFile(btnAttachment.getScene().getWindow());
File file = org.example.petshopdesktop.util.FilePickerSupport.pickAnyFile(btnAttachment.getScene().getWindow());
if (file == null) return;
btnAttachment.setDisable(true);
lblChatStatus.setText("Uploading attachment...");
selectedAttachmentFile = file;
btnAttachment.setText("📎 " + file.getName());
btnAttachment.setStyle("-fx-background-color: #dcfce7; -fx-background-radius: 12; -fx-text-fill: #166534; -fx-cursor: hand;");
lblChatStatus.setText("Attachment selected");
}
new Thread(() -> {
try {
var response = ChatApi.getInstance().uploadAttachment(file.toPath());
Platform.runLater(() -> {
selectedAttachment = response;
btnAttachment.setText("📎 " + response.getFileName());
btnAttachment.setStyle("-fx-background-color: #dcfce7; -fx-background-radius: 12; -fx-text-fill: #166534; -fx-cursor: hand;");
lblChatStatus.setText("File ready to send");
btnAttachment.setDisable(false);
});
} catch (Exception e) {
ActivityLogger.getInstance().logException("ChatController.btnAttachmentClicked", e, "Uploading chat attachment");
Platform.runLater(() -> {
lblChatStatus.setText("Upload failed");
btnAttachment.setDisable(false);
});
}
}).start();
private void clearLocalAttachment() {
selectedAttachmentFile = null;
btnAttachment.setText("📎");
btnAttachment.setStyle("-fx-background-color: #e2e8f0; -fx-background-radius: 12; -fx-text-fill: #475569; -fx-cursor: hand;");
}
private void loadCustomers() {
@@ -397,26 +382,6 @@ public class ChatController {
bubble.getChildren().add(content);
}
if (message.getAttachmentUrl() != null && !message.getAttachmentUrl().isBlank()) {
String attachmentLabel = "📎 " + (message.getAttachmentName() == null || message.getAttachmentName().isBlank() ? "Attachment" : message.getAttachmentName());
if (message.getAttachmentSizeBytes() != null && message.getAttachmentSizeBytes() > 0) {
attachmentLabel = attachmentLabel + " (" + formatSize(message.getAttachmentSizeBytes()) + ")";
}
Button attachmentBtn = new Button(attachmentLabel);
attachmentBtn.setStyle("-fx-background-color: " + (mine ? "rgba(255,255,255,0.15)" : "rgba(0,0,0,0.05)") + "; -fx-text-fill: " + (mine ? "#ffffff" : "#0f766e") + "; -fx-cursor: hand; -fx-background-radius: 8; -fx-padding: 6 10;");
attachmentBtn.setOnAction(e -> {
try {
String fullUrl = org.example.petshopdesktop.api.ApiConfig.getInstance().getBaseUrl() + message.getAttachmentUrl();
if (java.awt.Desktop.isDesktopSupported()) {
java.awt.Desktop.getDesktop().browse(new java.net.URI(fullUrl));
}
} catch (Exception ex) {
ActivityLogger.getInstance().logException("ChatController.attachmentOpen", ex, "Opening attachment URL");
}
});
bubble.getChildren().add(attachmentBtn);
}
bubble.getChildren().add(timestamp);
bubble.setMaxWidth(420);
bubble.setStyle(mine
@@ -475,17 +440,4 @@ public class ChatController {
private void scrollMessagesToBottom() {
Platform.runLater(() -> spMessages.setVvalue(1.0));
}
private String formatSize(Long bytes) {
if (bytes == null || bytes <= 0) {
return "";
}
double size = bytes;
String[] units = {"B", "KB", "MB", "GB"};
int unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size = size / 1024;
unitIndex++;
}
return unitIndex == 0 ? String.format("%.0f %s", size, units[unitIndex]) : String.format("%.1f %s", size, units[unitIndex]);
}
}