From d19eb92e4a3478e4a405ee20e8714dbfa9ae4df6 Mon Sep 17 00:00:00 2001 From: tzdwindows 7 <3076584115@qq.com> Date: Sat, 15 Mar 2025 21:02:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E6=B7=BB=E5=8A=A0=20AI=20=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E7=AA=97=E5=8F=A3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现了一个可拖动、可扩展的 AI 聊天窗口 - 添加了输入框、输出框、动画边框等 UI 元素- 集成了 Markdown 渲染和数学公式支持 - 添加了全局快捷键支持,用于快速打开/关闭 AI 聊天窗口 - 优化了窗口大小调整和布局逻辑 --- build.gradle | 5 + .../box/plugins/BoxClassLoader.java | 2 +- .../axis/innovators/box/ui/AIChatDialog.java | 629 ++++++++++++++++++ .../innovators/box/util/GlobalShortcuts.java | 114 ++++ src/main/java/org/tzd/lm/LM.java | 2 +- src/main/resources/icons/search_icon.png | Bin 0 -> 724 bytes 6 files changed, 750 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/axis/innovators/box/ui/AIChatDialog.java create mode 100644 src/main/java/com/axis/innovators/box/util/GlobalShortcuts.java create mode 100644 src/main/resources/icons/search_icon.png diff --git a/build.gradle b/build.gradle index 6789fa1..a82027f 100644 --- a/build.gradle +++ b/build.gradle @@ -89,6 +89,8 @@ dependencies { implementation 'com.github.javaparser:javaparser-core:3.25.1' + implementation 'com.1stleg:jnativehook:2.1.0' + //implementation 'org.springframework.boot:spring-boot-starter-web' // Web支持 //implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // JPA数据库支持 //implementation 'org.springframework.boot:spring-boot-starter-validation' // 参数校验 @@ -109,6 +111,9 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' + implementation 'com.kitfox.svg:svg-salamander:1.0' + + implementation 'com.vladsch.flexmark:flexmark:0.64.8' } // 分离依赖项到 libs 目录 diff --git a/src/main/java/com/axis/innovators/box/plugins/BoxClassLoader.java b/src/main/java/com/axis/innovators/box/plugins/BoxClassLoader.java index a2201b6..5495fbf 100644 --- a/src/main/java/com/axis/innovators/box/plugins/BoxClassLoader.java +++ b/src/main/java/com/axis/innovators/box/plugins/BoxClassLoader.java @@ -26,7 +26,7 @@ public class BoxClassLoader extends URLClassLoader { "java.", "javax.", "sun.", "com.sun.", "jdk.", "org.xml.", "org.w3c.", "org.apache.", "javax.management.", "javax.swing." - , "javafx." + , "javafx.","org.jnativehook." ); } diff --git a/src/main/java/com/axis/innovators/box/ui/AIChatDialog.java b/src/main/java/com/axis/innovators/box/ui/AIChatDialog.java new file mode 100644 index 0000000..b308f30 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/ui/AIChatDialog.java @@ -0,0 +1,629 @@ +package com.axis.innovators.box.ui; + +import com.vladsch.flexmark.util.ast.Node; +import org.tzd.lm.LM; +import com.vladsch.flexmark.html.HtmlRenderer; +import com.vladsch.flexmark.parser.Parser; +import com.vladsch.flexmark.util.data.MutableDataSet; + +import javax.swing.*; +import javax.swing.border.AbstractBorder; +import javax.swing.text.html.HTMLDocument; +import javax.swing.text.html.HTMLEditorKit; +import javax.swing.text.html.StyleSheet; +import java.awt.*; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.awt.event.*; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * + * @author tzdwindows 7 + */ +public class AIChatDialog extends JFrame { + private static final Font UI_FONT = createSystemFont(); + private static final Color TEXT_COLOR = new Color(0xF0F0F0); + private static final int COLLAPSED_HEIGHT = 100; + private static final int EXPANDED_HEIGHT = 400; + private final JTextPane outputPane = new JTextPane(); + private final JTextField inputField = new JTextField(); + private final Timer resizeTimer; + private final StringBuilder currentResponse = new StringBuilder(); + private final HtmlRenderer htmlRenderer; + private final Parser markdownParser; + private long modelHandle; + private long ctxHandle; + private JScrollPane scrollPane; + private final AtomicBoolean isRendering = new AtomicBoolean(false); + private final Timer borderAnimationTimer; + private float hue = 0f; + private boolean isUserResized = false; + private boolean isProcessing = false; + private final AnimatedBorder inputAnimatedBorder; + private final AnimatedBorder outputAnimatedBorder; + private boolean borderActive = false; + // 用于窗口拖动的变量 + private Point initialClick; + + static { + try { + LM.loadLibrary(LM.CUDA); + } catch (Exception ex) { + JOptionPane.showMessageDialog(null, "无法加载AI推理库", + "无法加载AI推理库", JOptionPane.ERROR_MESSAGE); + } + } + + private static Font createSystemFont() { + String[] fontNames = { + "Microsoft YaHei", + "PingFang SC", + "Noto Sans CJK SC", + "SimHei", + "sans-serif" + }; + + for (String name : fontNames) { + Font font = new Font(name, Font.PLAIN, 14); + if (font.getFamily().equals(name)) { + return font.deriveFont(Font.PLAIN, 14); + } + } + return new Font("sans-serif", Font.PLAIN, 14); + } + + public AIChatDialog() { + super("AI助手"); + setAlwaysOnTop(true); + + initResources(); + + inputAnimatedBorder = new AnimatedBorder(20, true); + outputAnimatedBorder = new AnimatedBorder(20, false); + + resizeTimer = new Timer(15, e -> updateWindowSize()); + borderAnimationTimer = new Timer(30, e -> updateBorderAnimation()); + initUI(); + initListeners(); + + MutableDataSet options = new MutableDataSet(); + markdownParser = Parser.builder(options).build(); + htmlRenderer = HtmlRenderer.builder(options).build(); + } + + private void updateBorderAnimation() { + hue = (hue + 0.015f) % 1f; + inputField.repaint(); + outputPane.repaint(); + } + + private void initResources() { + modelHandle = LM.llamaLoadModelFromFile(LM.DEEP_SEEK); + ctxHandle = LM.createContext(modelHandle); + } + + private void initListeners() { + // 窗口关闭时释放资源 + addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + releaseResources(); + } + }); + + addComponentListener(new ComponentAdapter() { + @Override + public void componentMoved(ComponentEvent e) { + repaintImmediately(); + } + + @Override + public void componentResized(ComponentEvent e) { + repaintImmediately(); + } + }); + + // ESC键关闭窗口 + getRootPane().registerKeyboardAction(e -> dispose(), + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), + JComponent.WHEN_IN_FOCUSED_WINDOW + ); + + // 输入框回车事件 + inputField.addActionListener(e -> processQuery()); + + // 窗口拖动功能 + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + initialClick = e.getPoint(); // 记录鼠标点击的初始位置 + } + }); + + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + if (!resizeTimer.isRunning()) { + isUserResized = true; + } + } + }); + + addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseDragged(MouseEvent e) { + // 计算窗口新位置 + int deltaX = e.getX() - initialClick.x; + int deltaY = e.getY() - initialClick.y; + setLocation(getX() + deltaX, getY() + deltaY); + } + }); + } + + private void repaintImmediately() { + SwingUtilities.invokeLater(() -> { + getContentPane().repaint(); + outputPane.repaint(); + inputField.repaint(); + }); + } + + private void initUI() { + // 窗口基础设置 + //getRootPane().setOpaque(false); + //setUndecorated(true); + //setBackground(new Color(0, 0, 0, 0)); + //setShape(new RoundRectangle2D.Double(0, 0, getWidth(), getHeight(), 30, 30)); + + // 窗口尺寸和位置配置 + setSize(600, COLLAPSED_HEIGHT); + setMinimumSize(new Dimension(400, COLLAPSED_HEIGHT)); + setLocationRelativeTo(null); + + // 窗口阴影效果 + JPanel shadowPanel = new JPanel(new BorderLayout()) { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g.create(); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + for (int i = 0; i < 8; i++) { + g2.setColor(new Color(0, 0, 0, 30 - i * 3)); + g2.fillRoundRect(i, i, getWidth() - i * 2, getHeight() - i * 2, 30, 30); + } + g2.dispose(); + } + }; + + shadowPanel.setOpaque(false); + + // 主容器 + JPanel mainPanel = new JPanel(new BorderLayout()) { + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2 = (Graphics2D) g.create(); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + g2.setColor(new Color(70, 73, 75, 255)); + g2.fillRoundRect(0, 0, getWidth(), getHeight(), 20, 20); + g2.dispose(); + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(getWidth(), getHeight()); + } + }; + mainPanel.setOpaque(false); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 25, 20, 25)); + + // 输入面板(浏览器搜索框风格) + JPanel inputPanel = new JPanel(new BorderLayout()); + inputPanel.setOpaque(false); + inputPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 8, 0)); + + // 搜索图标 + //JLabel searchIcon = new JLabel(LoadIcon.loadIcon("search_icon.png", 32)); + //inputPanel.add(searchIcon, BorderLayout.WEST); + + // 输入框样式 + inputField.setFont(new Font("Microsoft YaHei", Font.PLAIN, 14)); + inputField.setForeground(Color.WHITE); + + inputField.setBorder(BorderFactory.createCompoundBorder( + inputAnimatedBorder, + null + )); + + // 获取字体度量 + FontMetrics metrics = inputField.getFontMetrics(inputField.getFont()); + // 计算最小显示高度 = 字体高度 + 上下边距 + int minHeight = metrics.getHeight() + (5 + 5); // 5+5是EmptyBorder的上下值 + // 设置动态高度 + inputField.setPreferredSize(new Dimension( + inputField.getWidth(), + Math.max(minHeight, 36) + )); + + inputField.putClientProperty("Text.antialias", true); + inputField.putClientProperty("Text.renderer", "subpixel"); + + inputField.putClientProperty("JTextField.placeholderText", "向我提出问题..."); + inputPanel.add(inputField, BorderLayout.CENTER); + + // 在initUI方法开头添加 + UIManager.put("TextPane.font", new Font("JetBrains Mono", Font.PLAIN, 14)); + + // 修改输出区域配置 + outputPane.putClientProperty("JTextPane.w3cLengthUnits", true); + outputPane.putClientProperty("JTextPane.honorDisplayProperties", true); + + // 代码高亮样式增强 + StyleSheet styleSheet = new StyleSheet(); + + HTMLEditorKit editorKit = new HTMLEditorKit() { + @Override + public void install(JEditorPane c) { + super.install(c); + } + }; + editorKit.setStyleSheet(styleSheet); + + outputPane.setFont(new Font("Microsoft YaHei", Font.PLAIN, 16)); + + outputPane.setBorder(BorderFactory.createCompoundBorder( + outputAnimatedBorder, + BorderFactory.createEmptyBorder(15, 15, 15, 15) + )); + + outputPane.setOpaque(false); + + JPopupMenu popupMenu = new JPopupMenu(); + JMenuItem copyItem = new JMenuItem("复制"); + copyItem.addActionListener(e -> { + String text = outputPane.getSelectedText(); + if (text != null && !text.isEmpty()) { + copyToClipboard(text); + } + }); + popupMenu.add(copyItem); + outputPane.setComponentPopupMenu(popupMenu); + + outputPane.setEditorKit(editorKit); + outputPane.setContentType("text/html; charset=UTF-8"); + + HTMLDocument doc = (HTMLDocument) editorKit.createDefaultDocument(); + doc.setPreservesUnknownTags(false); + + doc.setPreservesUnknownTags(false); + outputPane.setDocument(doc); + + scrollPane = new JScrollPane(outputPane); + scrollPane.setVisible(false); + scrollPane.setBorder(null); + scrollPane.getViewport().setOpaque(false); + scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + + outputPane.setMinimumSize(new Dimension(580, 200)); + scrollPane.setMinimumSize(new Dimension(580, 200)); + + mainPanel.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + if (getHeight() > COLLAPSED_HEIGHT + 50) { + scrollPane.setVisible(true); + } else { + scrollPane.setVisible(false); + } + } + }); + + // 布局组装 + mainPanel.add(inputPanel, BorderLayout.NORTH); + mainPanel.add(scrollPane, BorderLayout.CENTER); + shadowPanel.add(mainPanel); + setContentPane(shadowPanel); + + } + + class AnimatedBorder extends AbstractBorder { + private final int radius; + private final boolean isInput; + private boolean active = false; + + public AnimatedBorder(int radius, boolean isInput) { + this.radius = radius; + this.isInput = isInput; + } + + public void setActive(boolean active) { + this.active = active; + } + + @Override + public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { + Graphics2D g2 = (Graphics2D) g.create(); + try { + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); + + if (active) { + Color color1 = Color.getHSBColor(hue, 0.8f, 1f); + Color color2 = Color.getHSBColor(hue + 0.3f, 0.8f, 1f); + GradientPaint gp = new GradientPaint(0, 0, color1, width, height, color2); + g2.setPaint(gp); + } else { + float fixedHue = 0.6f; // 固定的色调,例如蓝色 + Color color1 = Color.getHSBColor(fixedHue, 0.8f, 1f); + Color color2 = Color.getHSBColor(fixedHue + 0.3f, 0.8f, 1f); + GradientPaint gp = new GradientPaint(0, 0, color1, width, height, color2); + g2.setPaint(gp); + } + + g2.setStroke(new BasicStroke(isInput ? 3.5f : 3f)); // 加粗线条 + g2.drawRoundRect(x, y, width - 1, height - 1, radius, radius); + } finally { + g2.dispose(); + } + } + + @Override + public Insets getBorderInsets(Component c) { + return new Insets(radius/2, radius/2, radius/2, radius/2); + } + + @Override + public Insets getBorderInsets(Component c, Insets insets) { + insets.left = insets.right = radius/2; + insets.top = insets.bottom = radius/2; + return insets; + } + } + + @Override + public void paint(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB); + g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); + + getContentPane().paint(g2d); + + Image buffer = createImage(getWidth(), getHeight()); + Graphics bufferGraphics = buffer.getGraphics(); + super.paint(bufferGraphics); + g2d.drawImage(buffer, 0, 0, null); + } + + private void copyToClipboard(String text) { + StringSelection selection = new StringSelection(text); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(selection, null); + } + + private void processQuery() { + String query = inputField.getText().trim(); + if (query.isEmpty()) return; + + // 修正边框激活状态 + isProcessing = true; + borderActive = true; + inputAnimatedBorder.setActive(true); + outputAnimatedBorder.setActive(true); + borderAnimationTimer.start(); + + // 展开对话框 + if (!resizeTimer.isRunning()) { + resizeTimer.start(); + } + + // 清空输入 + inputField.setText(""); + appendMessage(query, true); + + // 异步推理 + new SwingWorker() { + @Override + protected Void doInBackground() { + appendMessage("", false); + + String systemPrompt = """ + 你是一个基于DeepSeek-R1的智能助手 + """; + + LM.inference(modelHandle, + ctxHandle, + 0.7f, + query, + systemPrompt, + this::publishChunk); + return null; + } + + private void publishChunk(String chunk) { + publish(chunk); + } + + @Override + protected void process(java.util.List chunks) { + chunks.forEach(chunk -> { + currentResponse.append(chunk); + renderMarkdown(currentResponse.toString()); + }); + } + + @Override + protected void done() { + inputAnimatedBorder.setActive(false); + outputAnimatedBorder.setActive(false); + super.done(); + } + }.execute(); + if (!shouldExpand) { + shouldExpand = true; + resizeTimer.start(); + } + } + + private void renderMarkdown(String markdown) { + if (!isRendering.compareAndSet(false, true)) return; + + new SwingWorker() { + private String processedHtml; + + @Override + protected Void doInBackground() { + try { + Node document = markdownParser.parse(markdown); + processedHtml = htmlRenderer.render(document); + + // 强化 MathJax 配置 + processedHtml = String.format(""" + + + + + + + + %s + + """, UI_FONT.getFamily(), processedHtml); + } catch (Exception e) { + processedHtml = "
渲染错误:" + e.getMessage() + "
"; + } + return null; + } + + @Override + protected void done() { + try { + if (processedHtml == null) return; + + HTMLDocument doc = (HTMLDocument) outputPane.getDocument(); + doc.setInnerHTML(doc.getDefaultRootElement(), processedHtml); + + } catch (Exception ex) { + ex.printStackTrace(); + } finally { + isRendering.set(false); + } + } + }.execute(); + } + + private void appendMessage(String message, boolean isUser) { + String prefix; + if (isUser) { + prefix = "
You:
"; + } else { + prefix = "
AI:
"; + message = message + ""; + } + + currentResponse.setLength(0); + currentResponse.append(prefix).append(message); + renderMarkdown(currentResponse.toString()); + } + + private boolean shouldExpand = false; + private void updateWindowSize() { + if (!SwingUtilities.isEventDispatchThread()) { + SwingUtilities.invokeLater(this::updateWindowSize); + return; + } + + Dimension currentSize = getSize(); + int targetHeight = shouldExpand ? EXPANDED_HEIGHT : COLLAPSED_HEIGHT; + + // 改进的插值算法(使用弹簧物理模型) + int delta = targetHeight - currentSize.height; + int acceleration = delta / 15; // 控制加速度的系数 + int newHeight = currentSize.height + acceleration; + + // 严格边界约束 + if ((delta > 0 && newHeight > targetHeight) || + (delta < 0 && newHeight < targetHeight)) { + newHeight = targetHeight; + } + + // 当接近目标时直接锁定 + if (Math.abs(delta) <= 2) { + newHeight = targetHeight; + } + + // 更新窗口尺寸(保持水平居中) + Point currentLocation = getLocation(); + int newWidth = Math.max(getWidth(), 400); + setLocation( + currentLocation.x - (newWidth - currentSize.width)/2, // 保持水平居中 + currentLocation.y + ); + setSize(new Dimension(newWidth, newHeight)); + + // 终局状态处理 + if (newHeight == targetHeight) { + resizeTimer.stop(); + + // 强制设置精确尺寸 + setSize(new Dimension(getWidth(), targetHeight)); + + // 单次布局更新 + SwingUtilities.invokeLater(() -> { + scrollPane.setVisible(shouldExpand); + if (shouldExpand) { + scrollPane.getVerticalScrollBar().setValue(0); + } + }); + } + + // 优化重绘策略 + if (Math.abs(currentSize.height - newHeight) > 1) { + getContentPane().repaint(); + } + } + + private void releaseResources() { + if (ctxHandle != 0) { + LM.llamaFreeContext(ctxHandle); + ctxHandle = 0; + } + if (modelHandle != 0) { + LM.llamaFreeModel(modelHandle); + modelHandle = 0; + } + } + + public void toggleVisibility() { + if (!isVisible()) { + setVisible(true); + } + updateWindowSize(); + + SwingUtilities.invokeLater(() -> { + setVisible(true); + setLocationRelativeTo(null); + + // 添加自动聚焦逻辑 + if (inputField.isDisplayable() && inputField.isEnabled()) { + inputField.requestFocusInWindow(); + // 触发动画边框 + inputAnimatedBorder.setActive(true); + outputAnimatedBorder.setActive(false); + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/com/axis/innovators/box/util/GlobalShortcuts.java b/src/main/java/com/axis/innovators/box/util/GlobalShortcuts.java new file mode 100644 index 0000000..1356d57 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/util/GlobalShortcuts.java @@ -0,0 +1,114 @@ +package com.axis.innovators.box.util; + +import com.axis.innovators.box.ui.AIChatDialog; +import org.jnativehook.GlobalScreen; +import org.jnativehook.NativeHookException; +import org.jnativehook.keyboard.NativeKeyEvent; +import org.jnativehook.keyboard.NativeKeyListener; + +import javax.swing.*; +import java.awt.*; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 支持组合键的全局快捷键注册 + * + * @author tzdwindows 7 + */ +public class GlobalShortcuts { + private static AIChatDialog aiDialog; + private static final Map, TriggeringEvents> shortcuts = new HashMap<>(); + private static final Set pressedKeys = new HashSet<>(); + private static final Set> triggeredCombinations = new HashSet<>(); + + static { + Logger logger = Logger.getLogger(GlobalScreen.class.getPackage().getName()); + logger.setLevel(Level.WARNING); + logger.setUseParentHandlers(false); + + try { + GlobalScreen.registerNativeHook(); + } catch (NativeHookException ex) { + ex.printStackTrace(); + } + + GlobalScreen.addNativeKeyListener(new NativeKeyListener() { + @Override + public void nativeKeyTyped(NativeKeyEvent nativeKeyEvent) {} + + @Override + public void nativeKeyPressed(NativeKeyEvent e) { + int keyCode = e.getKeyCode(); + pressedKeys.add(keyCode); + checkCombinations(); + } + + @Override + public void nativeKeyReleased(NativeKeyEvent e) { + int keyCode = e.getKeyCode(); + pressedKeys.remove(keyCode); + // 释放任意键时重置触发状态 + triggeredCombinations.clear(); + } + }); + + // 注册关闭钩子 + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + GlobalScreen.unregisterNativeHook(); + } catch (NativeHookException ex) { + ex.printStackTrace(); + } + })); + } + + /** + * 注册组合快捷键 + * @param events 触发事件 + * @param keyCodes 组合键键码(如 NativeKeyEvent.VC_SHIFT, NativeKeyEvent.VC_F12) + */ + public static void registerShortcut(TriggeringEvents events, int... keyCodes) { + Set combination = new HashSet<>(); + for (int code : keyCodes) { + combination.add(code); + } + shortcuts.put(combination, events); + } + + // 检查当前按下的键是否匹配任何组合 + private static void checkCombinations() { + for (Map.Entry, TriggeringEvents> entry : shortcuts.entrySet()) { + Set requiredKeys = entry.getKey(); + // 检查是否满足组合键条件且未触发过 + if (pressedKeys.containsAll(requiredKeys) && !triggeredCombinations.contains(requiredKeys)) { + entry.getValue().triggering(); + triggeredCombinations.add(requiredKeys); + } + } + } + + public interface TriggeringEvents { + void triggering(); + } + + private static void toggleAIDialog() { + SwingUtilities.invokeLater(() -> { + if (aiDialog == null) { + aiDialog = new AIChatDialog(); + } + aiDialog.toggleVisibility(); + }); + } + + public static void main(String[] args) throws UnsupportedLookAndFeelException { + UIManager.setLookAndFeel(new com.formdev.flatlaf.FlatDarculaLaf()); + registerShortcut( + GlobalShortcuts::toggleAIDialog, + NativeKeyEvent.VC_CONTROL, + NativeKeyEvent.VC_SHIFT, + NativeKeyEvent.VC_A + ); + } +} \ No newline at end of file diff --git a/src/main/java/org/tzd/lm/LM.java b/src/main/java/org/tzd/lm/LM.java index bce8855..d41cfb2 100644 --- a/src/main/java/org/tzd/lm/LM.java +++ b/src/main/java/org/tzd/lm/LM.java @@ -10,7 +10,7 @@ import org.apache.logging.log4j.Logger; * @author tzdwindows 7 */ public class LM { - public static boolean CUDA = false; + public static boolean CUDA = true; public final static String DEEP_SEEK = FolderCreator.getModelFolder() + "\\DeepSeek-R1-Distill-Qwen-1.5B-Q8_0.gguf"; private static final Logger logger = LogManager.getLogger(LM.class); diff --git a/src/main/resources/icons/search_icon.png b/src/main/resources/icons/search_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a10c674050c859d05f479b1cbd754404d5e9a9ca GIT binary patch literal 724 zcmV;_0xSKAP)A18cxKu%qz>_@MVL0~JP)Ge8^Y0KLB)_St3NG$X(fV8yw)F7OJt z1{?#*z#a{|J`P+5R`uEBdoO^)!wRAWeASJ71MUI)Q+(e7yANziV*_XqnK@@R7l82r zv}uw#3GW&t7?d z&r2X1d3m%x`b>7)6vgJGysZr9qWnJcAvP^B+n&bejl9qKdQOuT#Y?M@vPJ0t`+SKm zYi6vGi^T(ZT1)SZ6B6ATLK}b7w|Lu`Az%CSewg+%c|8wdP}RcJ({?lzx8-3clLteY zG@Qc+J1FqY1?TYLEPF3n3eQPBth)z#D-YGBspV;Z2!uHlN0W+(%U2gbth*w zRhrYRGi8<7EvGT;_6epz1gcIoON>|5T1;v&9n+}jwVUdl+?!rdVmF5w3_7w|O8K{f zZ4aLIX-0n+Or407bx`mGc&`$=t;OvV@D_NeoBGqXpfL);_`=G2r%x~!$x$#ZGRRb( zf~oxv!8FNeFgrys87TmBWR&yHV|Is(Q;Yd`c0s|f8Tbh}t`i9^c>0000