From 5fcb78176e514b5d265dde0fde47070066b01e17 Mon Sep 17 00:00:00 2001 From: qmstyle Date: Sun, 6 Apr 2025 14:39:53 +0800 Subject: [PATCH] =?UTF-8?q?2025=E5=B9=B44=E6=9C=886=E6=97=A514:39:48?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aiclient/controller/MainController.java | 201 +++++++++--------- .../com/aiclient/service/ChatService.java | 4 +- src/main/resources/css/style.css | 12 -- src/main/resources/fxml/main.fxml | 2 +- 4 files changed, 109 insertions(+), 110 deletions(-) diff --git a/src/main/java/com/aiclient/controller/MainController.java b/src/main/java/com/aiclient/controller/MainController.java index b767607..72ddda2 100644 --- a/src/main/java/com/aiclient/controller/MainController.java +++ b/src/main/java/com/aiclient/controller/MainController.java @@ -5,6 +5,7 @@ import javafx.scene.control.*; import javafx.scene.layout.VBox; import javafx.scene.layout.HBox; import javafx.scene.text.Text; +import javafx.scene.text.TextAlignment; import javafx.scene.text.TextFlow; import com.aiclient.service.ChatService; import com.aiclient.service.MarkdownService; @@ -24,86 +25,95 @@ import javafx.scene.text.Font; import javafx.scene.control.ScrollPane; public class MainController { - + + @FXML + public ScrollPane chatScrollPane; + @FXML private ListView chatListView; - + @FXML private ComboBox modelSelector; - + @FXML private VBox chatContainer; - + @FXML private TextArea inputArea; - + private ChatService chatService; private MarkdownService markdownService; private ThemeService themeService; private Chat currentChat; private ShortcutService shortcutService; private Scene scene; - + @FXML public void initialize() { chatService = new ChatService(); markdownService = new MarkdownService(); themeService = new ThemeService(); shortcutService = new ShortcutService(); - + // 初始化模型选择器 modelSelector.getItems().addAll("deepseek-coder:latest"); modelSelector.setValue("deepseek-coder:latest"); - + // 初始化聊天列表 chatListView.setCellFactory(param -> new ChatListCell()); chatListView.getSelectionModel().selectedItemProperty().addListener( - (observable, oldValue, newValue) -> loadChat(newValue) + (observable, oldValue, newValue) -> loadChat(newValue) ); - + // 创建初始会话 createNewChat(); - + // 配置快捷键 setupShortcuts(); + + chatContainer.heightProperty().addListener((observable, oldValue, newValue) -> { + if (chatScrollPane.getVmax() > 0) { + chatScrollPane.setVvalue(1.0); + } + }); } - + private void setupShortcuts() { // 发送消息 (Ctrl/Cmd + Enter) - KeyCombination sendCombination = new KeyCodeCombination(KeyCode.ENTER, - KeyCombination.SHORTCUT_DOWN); + KeyCombination sendCombination = new KeyCodeCombination(KeyCode.ENTER, + KeyCombination.SHORTCUT_DOWN); inputArea.setOnKeyPressed(event -> { if (sendCombination.match(event)) { event.consume(); sendMessage(); } }); - + // 新建会话 (Ctrl/Cmd + N) shortcutService.addShortcut( - new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN), - this::createNewChat + new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN), + this::createNewChat ); - + // 切换主题 (Ctrl/Cmd + T) shortcutService.addShortcut( - new KeyCodeCombination(KeyCode.T, KeyCombination.SHORTCUT_DOWN), - this::toggleTheme + new KeyCodeCombination(KeyCode.T, KeyCombination.SHORTCUT_DOWN), + this::toggleTheme ); - + // 清空当前会话 (Ctrl/Cmd + L) shortcutService.addShortcut( - new KeyCodeCombination(KeyCode.L, KeyCombination.SHORTCUT_DOWN), - this::clearCurrentChat + new KeyCodeCombination(KeyCode.L, KeyCombination.SHORTCUT_DOWN), + this::clearCurrentChat ); - + // 复制选中文本 (Ctrl/Cmd + C) shortcutService.addShortcut( - new KeyCodeCombination(KeyCode.C, KeyCombination.SHORTCUT_DOWN), - this::copySelectedText + new KeyCodeCombination(KeyCode.C, KeyCombination.SHORTCUT_DOWN), + this::copySelectedText ); } - + @FXML private void createNewChat() { Chat newChat = chatService.createNewChat(); @@ -111,108 +121,109 @@ public class MainController { chatListView.getSelectionModel().select(newChat); currentChat = newChat; } - + @FXML private void sendMessage() { String messageText = inputArea.getText(); if (messageText.trim().isEmpty()) return; - + // 禁用输入框和发送按钮 inputArea.setDisable(true); - + Message userMessage = new Message("user", messageText); currentChat.addMessage(userMessage); addMessageToChat(userMessage); - + // 清空输入框 inputArea.clear(); - + // 创建助手消息 Message assistantMessage = new Message("assistant", ""); currentChat.addMessage(assistantMessage); HBox messageBox = addMessageToChat(assistantMessage); - TextArea textArea = ((TextArea) ((VBox) messageBox.getChildren().get(0)).getChildren().get(0)); - + TextFlow textFlow = (TextFlow) messageBox.getChildren().get(0); + StringBuilder contentBuilder = new StringBuilder(); - + // 发送到服务器并获取流式响应 chatService.sendMessageStream( - messageText, - modelSelector.getValue(), - chunk -> { - // 在JavaFX线程中更新UI - contentBuilder.append(chunk); - javafx.application.Platform.runLater(() -> { - assistantMessage.setContent(contentBuilder.toString()); - updateMessage(textArea, contentBuilder.toString() ); - }); - }, - () -> { - // 完成后在JavaFX线程中更新UI - javafx.application.Platform.runLater(() -> { - inputArea.setDisable(false); - inputArea.requestFocus(); - }); - } + messageText, + modelSelector.getValue(), + chunk -> { + // 在JavaFX线程中更新UI + contentBuilder.append(chunk); + javafx.application.Platform.runLater(() -> { + assistantMessage.setContent(contentBuilder.toString()); + updateMessage(textFlow, chunk); + }); + }, + () -> { + // 完成后在JavaFX线程中更新UI + javafx.application.Platform.runLater(() -> { + inputArea.setDisable(false); + inputArea.requestFocus(); + }); + }, + currentChat ); } - + private HBox addMessageToChat(Message message) { + TextFlow textFlow = new TextFlow(); - return addMessageToChat(message, textFlow); - } - - private HBox addMessageToChat(Message message, TextFlow textFlow) { - // 创建一个VBox来包含消息内容 - VBox messageContent = new VBox(5); // 5是内部间距 - messageContent.setMaxWidth(800); // 限制最大宽度 - - // 创建文本显示区域 - TextArea textArea = new TextArea(message.getContent()); - textArea.setWrapText(true); - textArea.setEditable(false); - textArea.setPrefRowCount(1); // 初始显示1行 - textArea.setStyle( - "-fx-background-color: transparent;" + - "-fx-text-fill: " + (themeService.isDarkTheme() ? "white" : "black") + ";" + - "-fx-focus-color: transparent;" + - "-fx-faint-focus-color: transparent;" - ); - - // 自动调整高度 - textArea.textProperty().addListener((obs, old, newText) -> { - int lineCount = (int) newText.lines().count(); - textArea.setPrefRowCount(Math.max(1, lineCount)); - }); - - messageContent.getChildren().add(textArea); - + textFlow.prefWidthProperty().bind(chatContainer.widthProperty()); + + textFlow.setLineSpacing(15); + + Text text = new Text(message.getContent()); + textFlow.getChildren().add(text); + // 设置消息样式 - HBox messageBox = new HBox(messageContent); + HBox messageBox = new HBox(textFlow); + messageBox.prefWidthProperty().bind(chatContainer.widthProperty()); messageBox.getStyleClass().add(message.getRole() + "-message"); messageBox.setPadding(new javafx.geometry.Insets(10)); - + if ("user".equals(message.getRole())) { + textFlow.setTextAlignment(TextAlignment.RIGHT); messageBox.setAlignment(Pos.CENTER_RIGHT); } else { messageBox.setAlignment(Pos.CENTER_LEFT); + textFlow.setTextAlignment(TextAlignment.LEFT); } - + chatContainer.getChildren().add(messageBox); return messageBox; } - - private void updateMessage(TextArea textArea, String content) { - textArea.setText(content); + + private void updateMessage(TextFlow textFlow, String content) { + + if (content.contains("")) { + content = content.split("")[1]; + if (content.equals("")){ + return; + } + } + + if (content.contains("")) { + content = content.split("")[1]; + } + + + Text text = new Text(content); + Text spacer = new Text("\r\n"); + textFlow.getChildren().add(text); + textFlow.getChildren().add(spacer); + } - + private void loadChat(Chat chat) { if (chat == null) return; currentChat = chat; chatContainer.getChildren().clear(); chat.getMessages().forEach(this::addMessageToChat); } - + @FXML private void toggleTheme() { if (themeService == null) return; @@ -220,7 +231,7 @@ public class MainController { markdownService.setDarkTheme(themeService.isDarkTheme()); reloadMessages(); } - + private void reloadMessages() { VBox oldContainer = chatContainer; chatContainer = new VBox(10); @@ -235,14 +246,14 @@ public class MainController { chatContainer.getChildren().clear(); } } - + private void copySelectedText() { if (scene == null) return; - + // 获取当前焦点节点 Node focusedNode = scene.getFocusOwner(); String selectedText = null; - + // 根据不同的节点类型获取选中的文本 if (focusedNode instanceof TextArea) { TextArea textArea = (TextArea) focusedNode; @@ -263,7 +274,7 @@ public class MainController { } } } - + // 如果有文本,复制到剪贴板 if (selectedText != null && !selectedText.isEmpty()) { final Clipboard clipboard = Clipboard.getSystemClipboard(); @@ -272,7 +283,7 @@ public class MainController { clipboard.setContent(content); } } - + public void setScene(Scene scene) { if (scene == null) return; this.scene = scene; diff --git a/src/main/java/com/aiclient/service/ChatService.java b/src/main/java/com/aiclient/service/ChatService.java index 4bbd647..02407fa 100644 --- a/src/main/java/com/aiclient/service/ChatService.java +++ b/src/main/java/com/aiclient/service/ChatService.java @@ -29,11 +29,11 @@ public class ChatService { return new Chat(); } - public void sendMessageStream(String message, String model, Consumer onChunk, Runnable onComplete) { + public void sendMessageStream(String message, String model, Consumer onChunk, Runnable onComplete,Chat currentChat) { CompletableFuture.runAsync(() -> { try { String encodedMessage = URLEncoder.encode(message, StandardCharsets.UTF_8); - String url = String.format("%s?userId=user&input=%s&stream=true", BASE_URL, encodedMessage); + String url = String.format("%s?input=%s&userId=%s", BASE_URL, encodedMessage,currentChat.getId()); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) diff --git a/src/main/resources/css/style.css b/src/main/resources/css/style.css index 4e65733..3e6164d 100644 --- a/src/main/resources/css/style.css +++ b/src/main/resources/css/style.css @@ -37,11 +37,6 @@ -fx-background-color: transparent; } -.user-message, .assistant-message { - -fx-background-radius: 5; - -fx-max-width: 800; -} - .user-message { -fx-background-color: #007AFF22; } @@ -72,14 +67,7 @@ -fx-line-spacing: 5; } -/* 滚动条样式 */ -.scroll-bar:vertical { - -fx-pref-width: 12; -} -.scroll-bar:horizontal { - -fx-pref-height: 12; -} .scroll-bar > .thumb { -fx-background-radius: 6; diff --git a/src/main/resources/fxml/main.fxml b/src/main/resources/fxml/main.fxml index 15dc9e9..fe4752e 100644 --- a/src/main/resources/fxml/main.fxml +++ b/src/main/resources/fxml/main.fxml @@ -33,7 +33,7 @@ - +