Auf dieser Basis noch ein GUI erstellt, so das man einfach Mindmaps mit KI erzeugen kann, und in der Vorschau anzeigen lassen kann, wie hier gezeigt:

Der von der KI erzeugt Code, kann auch auf diesem Reiter bearbeitet werden, und wird dann wie oben in der Vorschau angezeigt.

Hier die Antwort der KI.

Der Ablauf des Programms geht so:

Und hier der Java-Code:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
package de.wenzlaff.ki.model; import java.awt.BorderLayout; import java.awt.Color; import java.awt.FlowLayout; import java.awt.Font; import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.concurrent.TimeUnit; import javax.imageio.ImageIO; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import net.sourceforge.plantuml.FileFormat; import net.sourceforge.plantuml.FileFormatOption; import net.sourceforge.plantuml.SourceStringReader; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; /** * GUI für den Mindmap Perplexity KI Bot mit interaktiver grafischer Vorschau. * * Die Vorschau wird bei manuellen Änderungen im PlantUML-Code aktualisiert. * * © 2025 Thomas Wenzlaff */ public class MindmapPerplexityKiBotGui extends JFrame { private static final long serialVersionUID = 1L; private JTextArea inputArea; private JTextArea outputArea; private JTextArea plantUmlArea; private JLabel plantUmlPreviewLabel; private JButton sendButton; private JButton copyPlantUmlButton; private JLabel statusLabel; private Timer previewUpdateTimer; // Timer für verzögerte Aktualisierung public MindmapPerplexityKiBotGui() { super("Mindmap Perplexity KI Bot"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(1100, 800); setLocationRelativeTo(null); setLayout(new BorderLayout(10, 10)); // ---------- Eingabe ---------- inputArea = new JTextArea(5, 80); inputArea.setLineWrap(true); inputArea.setWrapStyleWord(true); JScrollPane inputScroll = new JScrollPane(inputArea); // ---------- Ausgabe ---------- outputArea = new JTextArea(25, 80); outputArea.setEditable(false); // KI-Antwort sollte nicht editierbar sein outputArea.setLineWrap(true); outputArea.setWrapStyleWord(true); JScrollPane outputScroll = new JScrollPane(outputArea); // ---------- PlantUML-Code ---------- plantUmlArea = new JTextArea(25, 80); plantUmlArea.setEditable(true); plantUmlArea.setFont(new Font("Monospaced", Font.PLAIN, 13)); JScrollPane plantUmlScroll = new JScrollPane(plantUmlArea); // ---------- Listener und Timer für Live-Vorschau ---------- previewUpdateTimer = new Timer(300, e -> updatePlantUmlPreview(plantUmlArea.getText())); previewUpdateTimer.setRepeats(false); // Timer nur einmal ausführen nach Verzögerung plantUmlArea.getDocument().addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { previewUpdateTimer.restart(); // Timer bei jeder Änderung neu starten } @Override public void removeUpdate(DocumentEvent e) { previewUpdateTimer.restart(); } @Override public void changedUpdate(DocumentEvent e) { previewUpdateTimer.restart(); } }); // ---------- PlantUML Vorschau ---------- plantUmlPreviewLabel = new JLabel(); plantUmlPreviewLabel.setHorizontalAlignment(JLabel.CENTER); JScrollPane previewScroll = new JScrollPane(plantUmlPreviewLabel); // ---------- Tabs ---------- JTabbedPane tabPane = new JTabbedPane(); tabPane.addTab("KI-Antwort", outputScroll); tabPane.addTab("PlantUML-Code", plantUmlScroll); tabPane.addTab("Vorschau", previewScroll); // ---------- Buttons ---------- sendButton = new JButton("Anfrage senden"); sendButton.addActionListener(this::onSendClicked); copyPlantUmlButton = new JButton("PlantUML kopieren"); copyPlantUmlButton.setEnabled(false); copyPlantUmlButton.addActionListener(this::onCopyPlantUmlClicked); statusLabel = new JLabel("Bereit."); statusLabel.setForeground(Color.DARK_GRAY); JPanel topPanel = new JPanel(new BorderLayout()); topPanel.add(new JLabel("Prompt eingeben:"), BorderLayout.NORTH); topPanel.add(inputScroll, BorderLayout.CENTER); JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); buttonPanel.add(sendButton); buttonPanel.add(copyPlantUmlButton); topPanel.add(buttonPanel, BorderLayout.SOUTH); JPanel bottomPanel = new JPanel(new BorderLayout()); bottomPanel.add(statusLabel, BorderLayout.CENTER); add(topPanel, BorderLayout.NORTH); add(tabPane, BorderLayout.CENTER); add(bottomPanel, BorderLayout.SOUTH); setVisible(true); } private void onSendClicked(ActionEvent e) { String prompt = inputArea.getText().trim(); if (prompt.isEmpty()) { JOptionPane.showMessageDialog(this, "Bitte einen Prompt eingeben!", "Fehler", JOptionPane.WARNING_MESSAGE); return; } sendButton.setEnabled(false); copyPlantUmlButton.setEnabled(false); statusLabel.setText("Sende Anfrage ..."); outputArea.setText(""); plantUmlArea.setText(""); plantUmlPreviewLabel.setIcon(null); plantUmlPreviewLabel.setText("Generiere Vorschau ..."); new Thread(() -> { try { String result = callPerplexityAPI(prompt); ObjectMapper mapper = new ObjectMapper(); JsonNode root = mapper.readTree(result); JsonNode choices = root.path("choices"); String content = choices.isArray() && choices.size() > 0 ? choices.get(0).path("message").path("content").asText() : "Keine Antwort gefunden."; String readableContent = content.replace("\\n", "\n").replace("\\t", " ").replace("\\r", "").replaceAll("\\\\\"", "\""); String extractedPlantUml = getPlantUml(content); SwingUtilities.invokeLater(() -> { outputArea.setText(readableContent.trim()); plantUmlArea.setText(extractedPlantUml != null ? formatPlantUml(extractedPlantUml) : "Kein PlantUML-Block gefunden."); copyPlantUmlButton.setEnabled(extractedPlantUml != null); statusLabel.setText("Fertig."); sendButton.setEnabled(true); }); } catch (Exception ex) { SwingUtilities.invokeLater(() -> { outputArea.setText("Fehler: " + ex.getMessage()); statusLabel.setText("Fehler."); sendButton.setEnabled(true); }); } }).start(); } private String getPlantUml(String text) { // Zuerst nach Markdown-Block suchen final String startTagMd = "```plantuml"; int startIndex = text.indexOf(startTagMd); if (startIndex != -1) { int endIndex = text.indexOf("```", startIndex + startTagMd.length()); if (endIndex != -1) { return text.substring(startIndex + startTagMd.length(), endIndex).trim(); } } // Wenn nicht gefunden, nach reinem @startmindmap-Block suchen final String startTagPlain = "@startmindmap"; startIndex = text.indexOf(startTagPlain); if (startIndex != -1) { int endIndex = text.indexOf("@endmindmap", startIndex); if (endIndex != -1) { return text.substring(startIndex, endIndex + "@endmindmap".length()).trim(); } } return null; } private void updatePlantUmlPreview(String plantUml) { if (plantUml == null || plantUml.trim().isEmpty()) { plantUmlPreviewLabel.setIcon(null); plantUmlPreviewLabel.setText("Kein PlantUML-Code für die Vorschau vorhanden."); return; } new Thread(() -> { try { String source = plantUml.trim(); // Sicherstellen, dass der Code von Start- und End-Tags umschlossen ist if (!source.startsWith("@start")) { source = "@startmindmap\n" + source + "\n@endmindmap"; } SourceStringReader reader = new SourceStringReader(source); final ByteArrayOutputStream os = new ByteArrayOutputStream(); reader.outputImage(os, new FileFormatOption(FileFormat.PNG)); os.close(); final byte[] imageData = os.toByteArray(); BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(imageData)); SwingUtilities.invokeLater(() -> { if (bufferedImage != null) { plantUmlPreviewLabel.setIcon(new ImageIcon(bufferedImage)); plantUmlPreviewLabel.setText(null); } else { plantUmlPreviewLabel.setIcon(null); plantUmlPreviewLabel.setText("Fehler beim Erstellen der Vorschau."); } }); } catch (IOException ex) { SwingUtilities.invokeLater(() -> { plantUmlPreviewLabel.setIcon(null); plantUmlPreviewLabel.setText("Fehler bei der Bildgenerierung: " + ex.getMessage()); }); } }).start(); } private void onCopyPlantUmlClicked(ActionEvent e) { // Text direkt aus der JTextArea holen, da er manuell geändert sein kann String currentPlantUml = plantUmlArea.getText(); if (currentPlantUml == null || currentPlantUml.trim().isEmpty()) { JOptionPane.showMessageDialog(this, "Kein PlantUML-Text zum Kopieren vorhanden!", "Hinweis", JOptionPane.INFORMATION_MESSAGE); return; } StringSelection selection = new StringSelection(currentPlantUml); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); statusLabel.setText("PlantUML-Code in Zwischenablage kopiert."); } private String formatPlantUml(String uml) { return uml.replace("\\n", "\n").replace("\\t", " ").replace("\\r", "").replaceAll("(?<=\\})", "\n") .replaceAll("\\\\\"", "\"").trim(); } private String callPerplexityAPI(String prompt) throws IOException { String apiKey = System.getenv("PPLX_API_KEY"); if (apiKey == null) { throw new IllegalStateException("PPLX_API_KEY ist nicht gesetzt"); } String jsonInputPrompt = "{" + "\"model\": \"sonar-pro\", " + "\"messages\": [" + " {\"role\": \"system\", \"content\": \"" + "1. Du bist ein hilfreicher Assistent. " + "2. Antworte immer ohne Quellenangaben im Ergebnis. " + "3. Erzeuge dazu eine Mindmap im plantUml Format in einem Markdown `plantuml` Code-Block. " + "4. Verwende als Hintergrundfarbe ein helles blau. " + "5. Füge: skinparam defaultTextAlignment center nach @startmindmap ein. " + "6. Formatiere den Haupt Node in der Größe von 36pt z.B <size:36>THEMA</size>. " + "7. Alle Nodes der 1. Ebene mit Größe 24pt z.B. <size:24>NODE</size>. " + "8. Verteile die Nodes gleichmäßig (links/rechts), indem `left side` einmalig verwendet wird. " + "9. Füge unten einen Footer ein: center footer © 2025 by [[http:\\/\\/www.Kleinhirn.eu Kleinhirn.eu]]" + "\"}," + " {\"role\": \"user\", \"content\": \"" + prompt.replace("\"", "\\\"") + "\"}" + "], " + "\"temperature\": 0.8, " + "\"max_tokens\": 5000 " + "}"; Request request = new Request.Builder().url("https://api.perplexity.ai/chat/completions") .post(RequestBody.create(jsonInputPrompt, MediaType.get("application/json"))) .addHeader("Authorization", "Bearer " + apiKey).build(); OkHttpClient client = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.MINUTES) .readTimeout(5, TimeUnit.MINUTES).writeTimeout(5, TimeUnit.MINUTES).build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { throw new RuntimeException("HTTP Fehler: " + response.code()); } return response.body().string(); } } public static void main(String[] args) { SwingUtilities.invokeLater(MindmapPerplexityKiBotGui::new); } } |
Das ganze Projekt, hier auf GitLab.
