{"id":22974,"date":"2025-11-14T05:31:46","date_gmt":"2025-11-14T04:31:46","guid":{"rendered":"http:\/\/blog.wenzlaff.de\/?p=22974"},"modified":"2025-11-10T16:42:43","modified_gmt":"2025-11-10T15:42:43","slug":"ki-bot-programm-fuer-mindmap-erzeugung-mit-vorschau","status":"publish","type":"post","link":"http:\/\/blog.wenzlaff.de\/?p=22974","title":{"rendered":"KI-Bot Programm f\u00fcr Mindmap Erzeugung mit Vorschau"},"content":{"rendered":"<p>Auf <a href=\"http:\/\/blog.wenzlaff.de\/?p=22965\" target=\"_blank\">dieser Basis <\/a>noch ein GUI erstellt, so das man einfach Mindmaps mit KI erzeugen kann, und in der Vorschau anzeigen lassen kann, wie hier gezeigt:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2025\/11\/ki-bot2-scaled.jpg\" alt=\"\" width=\"2560\" height=\"1780\" \/><\/p>\n<p>Der von der KI erzeugt Code, kann auch auf diesem Reiter bearbeitet werden, und wird dann wie oben in der Vorschau angezeigt. <!--more--><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2025\/11\/ki-bot3-scaled.jpg\" alt=\"\" width=\"2560\" height=\"1780\" \/><\/p>\n<p>Hier die Antwort der KI.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2025\/11\/ki-bot4-scaled.jpg\" alt=\"\" width=\"2560\" height=\"1780\"  \/><\/p>\n<p>Der Ablauf des Programms geht so:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2025\/11\/ki-bot1-scaled.jpg\" alt=\"\" width=\"2560\" height=\"1722\"  \/><\/p>\n<p>Und hier der Java-Code:<\/p>\n<pre class=\"nums:false nums-toggle:false lang:java decode:true \" >package de.wenzlaff.ki.model;\r\n\r\nimport java.awt.BorderLayout;\r\nimport java.awt.Color;\r\nimport java.awt.FlowLayout;\r\nimport java.awt.Font;\r\nimport java.awt.Toolkit;\r\nimport java.awt.datatransfer.StringSelection;\r\nimport java.awt.event.ActionEvent;\r\nimport java.awt.image.BufferedImage;\r\nimport java.io.ByteArrayInputStream;\r\nimport java.io.ByteArrayOutputStream;\r\nimport java.io.IOException;\r\nimport java.util.concurrent.TimeUnit;\r\n\r\nimport javax.imageio.ImageIO;\r\nimport javax.swing.ImageIcon;\r\nimport javax.swing.JButton;\r\nimport javax.swing.JFrame;\r\nimport javax.swing.JLabel;\r\nimport javax.swing.JOptionPane;\r\nimport javax.swing.JPanel;\r\nimport javax.swing.JScrollPane;\r\nimport javax.swing.JTabbedPane;\r\nimport javax.swing.JTextArea;\r\nimport javax.swing.SwingUtilities;\r\nimport javax.swing.Timer; \r\nimport javax.swing.event.DocumentEvent; \r\nimport javax.swing.event.DocumentListener;\r\n\r\nimport com.fasterxml.jackson.databind.JsonNode;\r\nimport com.fasterxml.jackson.databind.ObjectMapper;\r\n\r\nimport net.sourceforge.plantuml.FileFormat;\r\nimport net.sourceforge.plantuml.FileFormatOption;\r\nimport net.sourceforge.plantuml.SourceStringReader;\r\nimport okhttp3.MediaType;\r\nimport okhttp3.OkHttpClient;\r\nimport okhttp3.Request;\r\nimport okhttp3.RequestBody;\r\nimport okhttp3.Response;\r\n\r\n\/**\r\n * GUI f\u00fcr den Mindmap Perplexity KI Bot mit interaktiver grafischer Vorschau.\r\n * \r\n * Die Vorschau wird bei manuellen \u00c4nderungen im PlantUML-Code aktualisiert.\r\n *\r\n * \u00a9 2025 Thomas Wenzlaff\r\n *\/\r\npublic class MindmapPerplexityKiBotGui extends JFrame {\r\n\r\n    private static final long serialVersionUID = 1L;\r\n\r\n    private JTextArea inputArea;\r\n    private JTextArea outputArea;\r\n    private JTextArea plantUmlArea;\r\n    private JLabel plantUmlPreviewLabel;\r\n    private JButton sendButton;\r\n    private JButton copyPlantUmlButton;\r\n    private JLabel statusLabel;\r\n    private Timer previewUpdateTimer; \/\/ Timer f\u00fcr verz\u00f6gerte Aktualisierung\r\n\r\n    public MindmapPerplexityKiBotGui() {\r\n\tsuper(\"Mindmap Perplexity KI Bot\");\r\n\r\n\tsetDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);\r\n\tsetSize(1100, 800);\r\n\tsetLocationRelativeTo(null);\r\n\tsetLayout(new BorderLayout(10, 10));\r\n\r\n\t\/\/ ---------- Eingabe ----------\r\n\tinputArea = new JTextArea(5, 80);\r\n\tinputArea.setLineWrap(true);\r\n\tinputArea.setWrapStyleWord(true);\r\n\tJScrollPane inputScroll = new JScrollPane(inputArea);\r\n\r\n\t\/\/ ---------- Ausgabe ----------\r\n\toutputArea = new JTextArea(25, 80);\r\n\toutputArea.setEditable(false); \/\/ KI-Antwort sollte nicht editierbar sein\r\n\toutputArea.setLineWrap(true);\r\n\toutputArea.setWrapStyleWord(true);\r\n\tJScrollPane outputScroll = new JScrollPane(outputArea);\r\n\r\n\t\/\/ ---------- PlantUML-Code ----------\r\n\tplantUmlArea = new JTextArea(25, 80);\r\n\tplantUmlArea.setEditable(true);\r\n\tplantUmlArea.setFont(new Font(\"Monospaced\", Font.PLAIN, 13));\r\n\tJScrollPane plantUmlScroll = new JScrollPane(plantUmlArea);\r\n\r\n\t\/\/ ---------- Listener und Timer f\u00fcr Live-Vorschau ----------\r\n\tpreviewUpdateTimer = new Timer(300, e -&gt; updatePlantUmlPreview(plantUmlArea.getText()));\r\n\tpreviewUpdateTimer.setRepeats(false); \/\/ Timer nur einmal ausf\u00fchren nach Verz\u00f6gerung\r\n\r\n\tplantUmlArea.getDocument().addDocumentListener(new DocumentListener() {\r\n\t    @Override\r\n\t    public void insertUpdate(DocumentEvent e) {\r\n\t\tpreviewUpdateTimer.restart(); \/\/ Timer bei jeder \u00c4nderung neu starten\r\n\t    }\r\n\r\n\t    @Override\r\n\t    public void removeUpdate(DocumentEvent e) {\r\n\t\tpreviewUpdateTimer.restart();\r\n\t    }\r\n\r\n\t    @Override\r\n\t    public void changedUpdate(DocumentEvent e) {\r\n\t\tpreviewUpdateTimer.restart();\r\n\t    }\r\n\t});\r\n\r\n\t\/\/ ---------- PlantUML Vorschau ----------\r\n\tplantUmlPreviewLabel = new JLabel();\r\n\tplantUmlPreviewLabel.setHorizontalAlignment(JLabel.CENTER);\r\n\tJScrollPane previewScroll = new JScrollPane(plantUmlPreviewLabel);\r\n\r\n\t\/\/ ---------- Tabs ----------\r\n\tJTabbedPane tabPane = new JTabbedPane();\r\n\ttabPane.addTab(\"KI-Antwort\", outputScroll);\r\n\ttabPane.addTab(\"PlantUML-Code\", plantUmlScroll);\r\n\ttabPane.addTab(\"Vorschau\", previewScroll);\r\n\r\n\t\/\/ ---------- Buttons ----------\r\n\tsendButton = new JButton(\"Anfrage senden\");\r\n\tsendButton.addActionListener(this::onSendClicked);\r\n\r\n\tcopyPlantUmlButton = new JButton(\"PlantUML kopieren\");\r\n\tcopyPlantUmlButton.setEnabled(false);\r\n\tcopyPlantUmlButton.addActionListener(this::onCopyPlantUmlClicked);\r\n\r\n\tstatusLabel = new JLabel(\"Bereit.\");\r\n\tstatusLabel.setForeground(Color.DARK_GRAY);\r\n\tJPanel topPanel = new JPanel(new BorderLayout());\r\n\ttopPanel.add(new JLabel(\"Prompt eingeben:\"), BorderLayout.NORTH);\r\n\ttopPanel.add(inputScroll, BorderLayout.CENTER);\r\n\tJPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));\r\n\tbuttonPanel.add(sendButton);\r\n\tbuttonPanel.add(copyPlantUmlButton);\r\n\ttopPanel.add(buttonPanel, BorderLayout.SOUTH);\r\n\tJPanel bottomPanel = new JPanel(new BorderLayout());\r\n\tbottomPanel.add(statusLabel, BorderLayout.CENTER);\r\n\tadd(topPanel, BorderLayout.NORTH);\r\n\tadd(tabPane, BorderLayout.CENTER);\r\n\tadd(bottomPanel, BorderLayout.SOUTH);\r\n\tsetVisible(true);\r\n    }\r\n\r\n    private void onSendClicked(ActionEvent e) {\r\n\tString prompt = inputArea.getText().trim();\r\n\tif (prompt.isEmpty()) {\r\n\t    JOptionPane.showMessageDialog(this, \"Bitte einen Prompt eingeben!\", \"Fehler\", JOptionPane.WARNING_MESSAGE);\r\n\t    return;\r\n\t}\r\n\r\n\tsendButton.setEnabled(false);\r\n\tcopyPlantUmlButton.setEnabled(false);\r\n\tstatusLabel.setText(\"Sende Anfrage ...\");\r\n\toutputArea.setText(\"\");\r\n\tplantUmlArea.setText(\"\");\r\n\tplantUmlPreviewLabel.setIcon(null);\r\n\tplantUmlPreviewLabel.setText(\"Generiere Vorschau ...\");\r\n\r\n\tnew Thread(() -&gt; {\r\n\t    try {\r\n\t\tString result = callPerplexityAPI(prompt);\r\n\t\tObjectMapper mapper = new ObjectMapper();\r\n\t\tJsonNode root = mapper.readTree(result);\r\n\r\n\t\tJsonNode choices = root.path(\"choices\");\r\n\t\tString content = choices.isArray() &amp;&amp; choices.size() &gt; 0\r\n\t\t\t? choices.get(0).path(\"message\").path(\"content\").asText()\r\n\t\t\t: \"Keine Antwort gefunden.\";\r\n\r\n\t\tString readableContent = content.replace(\"\\\\n\", \"\\n\").replace(\"\\\\t\", \"    \").replace(\"\\\\r\", \"\").replaceAll(\"\\\\\\\\\\\"\", \"\\\"\");\r\n\r\n\t\tString extractedPlantUml = getPlantUml(content);\r\n\r\n\t\tSwingUtilities.invokeLater(() -&gt; {\r\n\t\t    outputArea.setText(readableContent.trim());\r\n\t\t    plantUmlArea.setText(extractedPlantUml != null ? formatPlantUml(extractedPlantUml)\r\n\t\t\t    : \"Kein PlantUML-Block gefunden.\");\r\n\t\t    copyPlantUmlButton.setEnabled(extractedPlantUml != null);\r\n\t\t    statusLabel.setText(\"Fertig.\");\r\n\t\t    sendButton.setEnabled(true);\r\n\t\t});\r\n\r\n\t    } catch (Exception ex) {\r\n\t\tSwingUtilities.invokeLater(() -&gt; {\r\n\t\t    outputArea.setText(\"Fehler: \" + ex.getMessage());\r\n\t\t    statusLabel.setText(\"Fehler.\");\r\n\t\t    sendButton.setEnabled(true);\r\n\t\t});\r\n\t    }\r\n\t}).start();\r\n    }\r\n\r\n    private String getPlantUml(String text) {\r\n\t\/\/ Zuerst nach Markdown-Block suchen\r\n\tfinal String startTagMd = \"```plantuml\";\r\n\tint startIndex = text.indexOf(startTagMd);\r\n\tif (startIndex != -1) {\r\n\t    int endIndex = text.indexOf(\"```\", startIndex + startTagMd.length());\r\n\t    if (endIndex != -1) {\r\n\t\treturn text.substring(startIndex + startTagMd.length(), endIndex).trim();\r\n\t    }\r\n\t}\r\n\t\/\/ Wenn nicht gefunden, nach reinem @startmindmap-Block suchen\r\n\tfinal String startTagPlain = \"@startmindmap\";\r\n\tstartIndex = text.indexOf(startTagPlain);\r\n\tif (startIndex != -1) {\r\n\t    int endIndex = text.indexOf(\"@endmindmap\", startIndex);\r\n\t    if (endIndex != -1) {\r\n\t\treturn text.substring(startIndex, endIndex + \"@endmindmap\".length()).trim();\r\n\t    }\r\n\t}\r\n\treturn null;\r\n    }\r\n\r\n    private void updatePlantUmlPreview(String plantUml) {\r\n\tif (plantUml == null || plantUml.trim().isEmpty()) {\r\n\t    plantUmlPreviewLabel.setIcon(null);\r\n\t    plantUmlPreviewLabel.setText(\"Kein PlantUML-Code f\u00fcr die Vorschau vorhanden.\");\r\n\t    return;\r\n\t}\r\n\r\n\tnew Thread(() -&gt; {\r\n\t    try {\r\n\t\tString source = plantUml.trim();\r\n\t\t\/\/ Sicherstellen, dass der Code von Start- und End-Tags umschlossen ist\r\n\t\tif (!source.startsWith(\"@start\")) {\r\n\t\t    source = \"@startmindmap\\n\" + source + \"\\n@endmindmap\";\r\n\t\t}\r\n\r\n\t\tSourceStringReader reader = new SourceStringReader(source);\r\n\t\tfinal ByteArrayOutputStream os = new ByteArrayOutputStream();\r\n\t\treader.outputImage(os, new FileFormatOption(FileFormat.PNG));\r\n\t\tos.close();\r\n\r\n\t\tfinal byte[] imageData = os.toByteArray();\r\n\t\tBufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(imageData));\r\n\r\n\t\tSwingUtilities.invokeLater(() -&gt; {\r\n\t\t    if (bufferedImage != null) {\r\n\t\t\tplantUmlPreviewLabel.setIcon(new ImageIcon(bufferedImage));\r\n\t\t\tplantUmlPreviewLabel.setText(null);\r\n\t\t    } else {\r\n\t\t\tplantUmlPreviewLabel.setIcon(null);\r\n\t\t\tplantUmlPreviewLabel.setText(\"Fehler beim Erstellen der Vorschau.\");\r\n\t\t    }\r\n\t\t});\r\n\r\n\t    } catch (IOException ex) {\r\n\t\tSwingUtilities.invokeLater(() -&gt; {\r\n\t\t    plantUmlPreviewLabel.setIcon(null);\r\n\t\t    plantUmlPreviewLabel.setText(\"Fehler bei der Bildgenerierung: \" + ex.getMessage());\r\n\t\t});\r\n\t    }\r\n\t}).start();\r\n    }\r\n\r\n    private void onCopyPlantUmlClicked(ActionEvent e) {\r\n\t\/\/ Text direkt aus der JTextArea holen, da er manuell ge\u00e4ndert sein kann\r\n\tString currentPlantUml = plantUmlArea.getText();\r\n\tif (currentPlantUml == null || currentPlantUml.trim().isEmpty()) {\r\n\t    JOptionPane.showMessageDialog(this, \"Kein PlantUML-Text zum Kopieren vorhanden!\", \"Hinweis\",\r\n\t\t    JOptionPane.INFORMATION_MESSAGE);\r\n\t    return;\r\n\t}\r\n\tStringSelection selection = new StringSelection(currentPlantUml);\r\n\tToolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null);\r\n\tstatusLabel.setText(\"PlantUML-Code in Zwischenablage kopiert.\");\r\n    }\r\n\r\n    private String formatPlantUml(String uml) {\r\n\treturn uml.replace(\"\\\\n\", \"\\n\").replace(\"\\\\t\", \"    \").replace(\"\\\\r\", \"\").replaceAll(\"(?&lt;=\\\\})\", \"\\n\")\r\n\t\t.replaceAll(\"\\\\\\\\\\\"\", \"\\\"\").trim();\r\n    }\r\n\r\n    private String callPerplexityAPI(String prompt) throws IOException {\r\n\tString apiKey = System.getenv(\"PPLX_API_KEY\");\r\n\tif (apiKey == null) {\r\n\t    throw new IllegalStateException(\"PPLX_API_KEY ist nicht gesetzt\");\r\n\t}\r\n\r\n\tString jsonInputPrompt = \"{\" + \"\\\"model\\\": \\\"sonar-pro\\\", \" + \"\\\"messages\\\": [\"\r\n\t\t+ \"  {\\\"role\\\": \\\"system\\\", \\\"content\\\": \\\"\" + \"1. Du bist ein hilfreicher Assistent. \"\r\n\t\t+ \"2. Antworte immer ohne Quellenangaben im Ergebnis. \"\r\n\t\t+ \"3. Erzeuge dazu eine Mindmap im plantUml Format in einem Markdown `plantuml` Code-Block. \"\r\n\t\t+ \"4. Verwende als Hintergrundfarbe ein helles blau. \"\r\n\t\t+ \"5. F\u00fcge: skinparam defaultTextAlignment center nach @startmindmap ein. \"\r\n\t\t+ \"6. Formatiere den Haupt Node in der Gr\u00f6\u00dfe von 36pt z.B &lt;size:36&gt;THEMA&lt;\/size&gt;. \"\r\n\t\t+ \"7. Alle Nodes der 1. Ebene mit Gr\u00f6\u00dfe 24pt z.B. &lt;size:24&gt;NODE&lt;\/size&gt;. \"\r\n\t\t+ \"8. Verteile die Nodes gleichm\u00e4\u00dfig (links\/rechts), indem `left side` einmalig verwendet wird. \"\r\n\t\t+ \"9. F\u00fcge unten einen Footer ein: center footer \u00a9 2025 by [[http:\\\\\/\\\\\/www.Kleinhirn.eu Kleinhirn.eu]]\"\r\n\t\t+ \"\\\"},\" + \"  {\\\"role\\\": \\\"user\\\", \\\"content\\\": \\\"\" + prompt.replace(\"\\\"\", \"\\\\\\\"\") + \"\\\"}\" + \"], \"\r\n\t\t+ \"\\\"temperature\\\": 0.8, \" + \"\\\"max_tokens\\\": 5000 \" + \"}\";\r\n\r\n\tRequest request = new Request.Builder().url(\"https:\/\/api.perplexity.ai\/chat\/completions\")\r\n\t\t.post(RequestBody.create(jsonInputPrompt, MediaType.get(\"application\/json\")))\r\n\t\t.addHeader(\"Authorization\", \"Bearer \" + apiKey).build();\r\n\r\n\tOkHttpClient client = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.MINUTES)\r\n\t\t.readTimeout(5, TimeUnit.MINUTES).writeTimeout(5, TimeUnit.MINUTES).build();\r\n\r\n\ttry (Response response = client.newCall(request).execute()) {\r\n\t    if (!response.isSuccessful()) {\r\n\t\tthrow new RuntimeException(\"HTTP Fehler: \" + response.code());\r\n\t    }\r\n\t    return response.body().string();\r\n\t}\r\n    }\r\n\r\n    public static void main(String[] args) {\r\n\tSwingUtilities.invokeLater(MindmapPerplexityKiBotGui::new);\r\n    }\r\n}\r\n<\/pre>\n<p>Das ganze Projekt, <a href=\"https:\/\/gitlab.com\/IT-Berater\/twmathe\/-\/tree\/main\/src\/main\/java\/de\/wenzlaff\/ki\/model?ref_type=heads\" target=\"_blank\">hier auf GitLab<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[220,5373,5,217],"tags":[6262,3205,6259,2178,6076,6260,218,1363,6261],"class_list":["post-22974","post","type-post","status-publish","format-standard","hentry","category-anleitung","category-chatgpt","category-java","category-mind-map","tag-anwendung-mindmap","tag-app","tag-bot","tag-java","tag-ki","tag-ki-bot","tag-mindmap","tag-swing","tag-vorschau"],"_links":{"self":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/posts\/22974","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=22974"}],"version-history":[{"count":0,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/posts\/22974\/revisions"}],"wp:attachment":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=22974"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=22974"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=22974"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}