mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-06-11 08:37:50 +02:00
871 lines
32 KiB
Java
871 lines
32 KiB
Java
package burp;
|
|
|
|
import javax.swing.*;
|
|
import java.awt.*;
|
|
import java.awt.datatransfer.StringSelection;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
final class CyberStrikeAITab implements ITab {
|
|
private final JPanel root = new JPanel(new BorderLayout());
|
|
|
|
private final JTextField hostField = new JTextField("127.0.0.1");
|
|
private final JTextField portField = new JTextField("8080");
|
|
private final JCheckBox useHttpsBox = new JCheckBox("HTTPS", true);
|
|
private final JPasswordField passwordField = new JPasswordField();
|
|
private final JComboBox<String> agentModeBox = new JComboBox<>(agentModeLabels());
|
|
private static String[] agentModeLabels() {
|
|
CyberStrikeAIClient.AgentMode[] modes = CyberStrikeAIClient.AgentMode.values();
|
|
String[] labels = new String[modes.length];
|
|
for (int i = 0; i < modes.length; i++) {
|
|
labels[i] = modes[i].displayName;
|
|
}
|
|
return labels;
|
|
}
|
|
|
|
private final JButton validateButton = new JButton("Validate");
|
|
private final JButton clearButton = new JButton("Clear Output");
|
|
private final JButton stopButton = new JButton("Stop");
|
|
private final JButton copyButton = new JButton("Copy");
|
|
private final JButton clearAllButton = new JButton("Clear All");
|
|
private final JLabel statusLabel = new JLabel("Not validated");
|
|
private final JCheckBox showDebugEventsBox = new JCheckBox("Show debug events", false);
|
|
private final JCheckBox renderMarkdownBox = new JCheckBox("Render Markdown", true);
|
|
|
|
private final JTextArea progressArea = new JTextArea();
|
|
private final JTextArea finalRawArea = new JTextArea(); // raw final stream / final response
|
|
private JScrollPane progressScrollPane;
|
|
private JScrollPane finalRawScrollPane;
|
|
/** 距底部在此像素内视为「跟随滚动」,否则用户上拉阅读时不抢滚动条 */
|
|
private static final int SCROLL_FOLLOW_THRESHOLD_PX = 48;
|
|
private final JEditorPane markdownPane = new JEditorPane("text/html", "");
|
|
private final CardLayout outputCardsLayout = new CardLayout();
|
|
private final JPanel outputCards = new JPanel(outputCardsLayout);
|
|
private final JPanel outputRoot = new JPanel(new BorderLayout());
|
|
private final JPanel progressContainer = new JPanel(new CardLayout());
|
|
private final JToggleButton progressToggle = new JToggleButton("Progress ▾", true);
|
|
private final JTextArea requestArea = new JTextArea();
|
|
private final JTextArea responseArea = new JTextArea();
|
|
private final JTabbedPane rightTabs = new JTabbedPane();
|
|
|
|
private final CyberStrikeAIClient client = new CyberStrikeAIClient();
|
|
private final AtomicReference<String> tokenRef = new AtomicReference<>("");
|
|
private final AtomicReference<Thread> validateThreadRef = new AtomicReference<>();
|
|
|
|
private final DefaultListModel<TestRun> testListModel = new DefaultListModel<>();
|
|
private final JList<TestRun> testList = new JList<>(testListModel);
|
|
private final DefaultListModel<TestRun> filteredListModel = new DefaultListModel<>();
|
|
private final JList<TestRun> filteredList = new JList<>(filteredListModel);
|
|
private final JTextField searchField = new JTextField();
|
|
private final Map<String, TestRun> runs = new HashMap<>();
|
|
private final Map<String, Integer> runIdToIndex = new HashMap<>();
|
|
private final AtomicInteger runSeq = new AtomicInteger(1);
|
|
private String selectedRunId = null;
|
|
|
|
private static final class TestRun {
|
|
final String id;
|
|
final String title;
|
|
final String agentMode;
|
|
final StringBuilder buffer = new StringBuilder();
|
|
final StringBuilder progressBuffer = new StringBuilder();
|
|
final StringBuilder finalBuffer = new StringBuilder();
|
|
final StringBuilder thinkingPending = new StringBuilder();
|
|
String status;
|
|
String conversationId;
|
|
String requestRaw;
|
|
String responseRaw;
|
|
String finalResponse;
|
|
|
|
TestRun(String id, String title, String agentMode) {
|
|
this.id = id;
|
|
this.title = title;
|
|
this.agentMode = agentMode;
|
|
this.status = "running";
|
|
this.conversationId = "";
|
|
this.requestRaw = "";
|
|
this.responseRaw = "";
|
|
this.finalResponse = "";
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return id;
|
|
}
|
|
}
|
|
|
|
CyberStrikeAITab() {
|
|
root.add(buildConfigPanel(), BorderLayout.NORTH);
|
|
root.add(buildMainPane(), BorderLayout.CENTER);
|
|
wireActions();
|
|
}
|
|
|
|
private JComponent buildConfigPanel() {
|
|
// Best-practice toolbar layout:
|
|
// Row 1 = connection settings
|
|
// Row 2 = run controls + view options
|
|
JPanel rootPanel = new JPanel();
|
|
rootPanel.setLayout(new BoxLayout(rootPanel, BoxLayout.Y_AXIS));
|
|
rootPanel.setBorder(BorderFactory.createEmptyBorder(4, 6, 4, 6));
|
|
|
|
hostField.setColumns(14);
|
|
portField.setColumns(6);
|
|
passwordField.setColumns(12);
|
|
agentModeBox.setPreferredSize(new Dimension(200, agentModeBox.getPreferredSize().height));
|
|
|
|
JPanel row1 = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 2));
|
|
row1.add(new JLabel("Host"));
|
|
row1.add(hostField);
|
|
row1.add(new JLabel("Port"));
|
|
row1.add(portField);
|
|
useHttpsBox.setToolTipText("Use https:// for CyberStrikeAI (self-signed certs are trusted automatically)");
|
|
row1.add(useHttpsBox);
|
|
row1.add(new JLabel("Password"));
|
|
row1.add(passwordField);
|
|
row1.add(validateButton);
|
|
row1.add(statusLabel);
|
|
|
|
JPanel row2 = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 2));
|
|
row2.add(new JLabel("Agent"));
|
|
row2.add(agentModeBox);
|
|
row2.add(stopButton);
|
|
row2.add(copyButton);
|
|
row2.add(clearButton);
|
|
row2.add(showDebugEventsBox);
|
|
row2.add(renderMarkdownBox);
|
|
|
|
rootPanel.add(row1);
|
|
rootPanel.add(row2);
|
|
return rootPanel;
|
|
}
|
|
|
|
private JComponent buildMainPane() {
|
|
JPanel sidebarPanel = buildSidebarPanel();
|
|
JComponent right = buildRightPanel();
|
|
|
|
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sidebarPanel, right);
|
|
split.setResizeWeight(0.25);
|
|
split.setBorder(null);
|
|
return split;
|
|
}
|
|
|
|
private JPanel buildSidebarPanel() {
|
|
JPanel p = new JPanel(new BorderLayout());
|
|
filteredList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
|
|
|
filteredList.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
|
|
filteredList.setCellRenderer(new TestRunCellRenderer());
|
|
filteredList.addListSelectionListener(e -> {
|
|
if (!e.getValueIsAdjusting()) {
|
|
String id = getSelectedRunIdFromList();
|
|
if (id != null) {
|
|
setLogAreaToRun(id);
|
|
}
|
|
}
|
|
});
|
|
|
|
JLabel title = new JLabel("Test History");
|
|
title.setBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8));
|
|
|
|
JPanel top = new JPanel(new BorderLayout(8, 6));
|
|
top.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 8));
|
|
top.add(title, BorderLayout.NORTH);
|
|
searchField.setToolTipText("Search runs (title)");
|
|
top.add(searchField, BorderLayout.SOUTH);
|
|
|
|
JScrollPane sp = new JScrollPane(filteredList);
|
|
sp.setBorder(BorderFactory.createTitledBorder("Runs"));
|
|
|
|
clearAllButton.addActionListener(e -> clearAllRuns());
|
|
JPanel bottom = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 6));
|
|
bottom.add(clearAllButton);
|
|
|
|
p.add(top, BorderLayout.NORTH);
|
|
p.add(sp, BorderLayout.CENTER);
|
|
p.add(bottom, BorderLayout.SOUTH);
|
|
p.setPreferredSize(new Dimension(320, 200));
|
|
return p;
|
|
}
|
|
|
|
private JComponent buildRightPanel() {
|
|
configureTextArea(progressArea, true);
|
|
configureTextArea(finalRawArea, true);
|
|
markdownPane.setEditable(false);
|
|
markdownPane.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
|
|
markdownPane.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
|
|
markdownPane.setOpaque(true);
|
|
markdownPane.setBackground(Color.WHITE);
|
|
|
|
configureTextArea(requestArea, false);
|
|
configureTextArea(responseArea, false);
|
|
|
|
finalRawScrollPane = new JScrollPane(finalRawArea);
|
|
finalRawScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
|
finalRawScrollPane.getVerticalScrollBar().setUnitIncrement(16);
|
|
outputCards.add(finalRawScrollPane, "raw");
|
|
outputCards.add(new JScrollPane(markdownPane), "md");
|
|
|
|
outputRoot.add(buildOutputHeader(), BorderLayout.NORTH);
|
|
outputRoot.add(buildOutputBody(), BorderLayout.CENTER);
|
|
|
|
rightTabs.addTab("Output", outputRoot);
|
|
JScrollPane requestScroll = new JScrollPane(requestArea);
|
|
requestScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
|
rightTabs.addTab("Request", requestScroll);
|
|
JScrollPane responseScroll = new JScrollPane(responseArea);
|
|
responseScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
|
rightTabs.addTab("Response", responseScroll);
|
|
return rightTabs;
|
|
}
|
|
|
|
private JComponent buildOutputHeader() {
|
|
JPanel header = new JPanel(new BorderLayout(8, 0));
|
|
header.setBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8));
|
|
|
|
JPanel left = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
|
|
left.add(progressToggle);
|
|
header.add(left, BorderLayout.WEST);
|
|
|
|
return header;
|
|
}
|
|
|
|
private JComponent buildOutputBody() {
|
|
progressScrollPane = new JScrollPane(progressArea);
|
|
progressScrollPane.setBorder(BorderFactory.createTitledBorder("Progress"));
|
|
progressScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
|
progressScrollPane.getVerticalScrollBar().setUnitIncrement(16);
|
|
|
|
JPanel empty = new JPanel();
|
|
progressContainer.add(progressScrollPane, "show");
|
|
progressContainer.add(empty, "hide");
|
|
((CardLayout) progressContainer.getLayout()).show(progressContainer, "show");
|
|
|
|
JPanel finalPanel = new JPanel(new BorderLayout());
|
|
finalPanel.add(outputCards, BorderLayout.CENTER);
|
|
finalPanel.setBorder(BorderFactory.createTitledBorder("Final Response"));
|
|
|
|
JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT, progressContainer, finalPanel);
|
|
split.setResizeWeight(0.15);
|
|
split.setBorder(null);
|
|
split.setDividerSize(6);
|
|
|
|
final int[] lastDividerLocation = new int[]{140}; // sensible default
|
|
|
|
progressToggle.addActionListener(e -> {
|
|
boolean show = progressToggle.isSelected();
|
|
progressToggle.setText(show ? "Progress ▾" : "Progress ▸");
|
|
CardLayout cl = (CardLayout) progressContainer.getLayout();
|
|
cl.show(progressContainer, show ? "show" : "hide");
|
|
if (!show) {
|
|
int current = split.getDividerLocation();
|
|
if (current > 0) {
|
|
lastDividerLocation[0] = current;
|
|
}
|
|
split.setDividerLocation(0);
|
|
split.setDividerSize(0);
|
|
} else {
|
|
split.setDividerSize(6);
|
|
// Restore previous divider location (or fallback to 20% of height)
|
|
int restore = lastDividerLocation[0];
|
|
if (restore <= 0) {
|
|
int h = split.getHeight();
|
|
restore = (h > 0) ? Math.max(80, (int) (h * 0.2)) : 140;
|
|
}
|
|
split.setDividerLocation(restore);
|
|
}
|
|
split.revalidate();
|
|
split.repaint();
|
|
});
|
|
|
|
return split;
|
|
}
|
|
|
|
private static boolean isScrollNearBottom(JScrollPane scrollPane) {
|
|
if (scrollPane == null) {
|
|
return true;
|
|
}
|
|
JScrollBar bar = scrollPane.getVerticalScrollBar();
|
|
int max = Math.max(0, bar.getMaximum() - bar.getVisibleAmount());
|
|
return bar.getValue() >= max - SCROLL_FOLLOW_THRESHOLD_PX;
|
|
}
|
|
|
|
private static void scrollPaneToBottom(JScrollPane scrollPane) {
|
|
if (scrollPane == null) {
|
|
return;
|
|
}
|
|
JScrollBar bar = scrollPane.getVerticalScrollBar();
|
|
bar.setValue(bar.getMaximum());
|
|
}
|
|
|
|
private static void configureTextArea(JTextArea area, boolean monospaced) {
|
|
area.setEditable(false);
|
|
area.setLineWrap(true);
|
|
area.setWrapStyleWord(true);
|
|
if (monospaced) {
|
|
area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
|
} else {
|
|
area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
|
}
|
|
}
|
|
|
|
private static Color colorForStatus(String status) {
|
|
if (status == null) return new Color(120, 120, 120);
|
|
switch (status) {
|
|
case "running":
|
|
return new Color(33, 150, 243);
|
|
case "done":
|
|
return new Color(76, 175, 80);
|
|
case "error":
|
|
return new Color(244, 67, 54);
|
|
case "cancelled":
|
|
case "cancelling":
|
|
return new Color(255, 152, 0);
|
|
default:
|
|
return new Color(120, 120, 120);
|
|
}
|
|
}
|
|
|
|
private static final class DotIcon implements Icon {
|
|
private final int size;
|
|
private Color color;
|
|
|
|
DotIcon(int size, Color color) {
|
|
this.size = size;
|
|
this.color = color;
|
|
}
|
|
|
|
void setColor(Color color) {
|
|
this.color = color;
|
|
}
|
|
|
|
@Override
|
|
public int getIconWidth() {
|
|
return size;
|
|
}
|
|
|
|
@Override
|
|
public int getIconHeight() {
|
|
return size;
|
|
}
|
|
|
|
@Override
|
|
public void paintIcon(Component c, Graphics g, int x, int y) {
|
|
Graphics2D g2 = (Graphics2D) g.create();
|
|
try {
|
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
|
g2.setColor(color != null ? color : Color.GRAY);
|
|
g2.fillOval(x, y, size, size);
|
|
} finally {
|
|
g2.dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static final class TestRunCellRenderer implements ListCellRenderer<TestRun> {
|
|
private final JPanel panel = new JPanel(new BorderLayout(8, 0));
|
|
private final JLabel dotLabel = new JLabel();
|
|
private final JLabel titleLabel = new JLabel();
|
|
private final JLabel metaLabel = new JLabel();
|
|
private final JPanel textPanel = new JPanel();
|
|
private final DotIcon dotIcon = new DotIcon(10, new Color(120, 120, 120));
|
|
|
|
TestRunCellRenderer() {
|
|
panel.setBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8));
|
|
dotLabel.setIcon(dotIcon);
|
|
|
|
textPanel.setLayout(new BoxLayout(textPanel, BoxLayout.Y_AXIS));
|
|
titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD));
|
|
metaLabel.setFont(metaLabel.getFont().deriveFont(Font.PLAIN, 11f));
|
|
metaLabel.setForeground(new Color(102, 102, 102));
|
|
textPanel.add(titleLabel);
|
|
textPanel.add(metaLabel);
|
|
|
|
panel.add(dotLabel, BorderLayout.WEST);
|
|
panel.add(textPanel, BorderLayout.CENTER);
|
|
panel.setOpaque(true);
|
|
textPanel.setOpaque(false);
|
|
}
|
|
|
|
@Override
|
|
public Component getListCellRendererComponent(JList<? extends TestRun> list, TestRun value, int index, boolean isSelected, boolean cellHasFocus) {
|
|
String titleText = value != null ? value.title : "";
|
|
String modeText = value != null ? value.agentMode : "";
|
|
String statusText = value != null ? value.status : "";
|
|
|
|
String shownTitle = titleText;
|
|
if (shownTitle.length() > 80) {
|
|
shownTitle = shownTitle.substring(0, 77) + "...";
|
|
}
|
|
titleLabel.setText(shownTitle);
|
|
metaLabel.setText(modeText + " · " + statusText);
|
|
|
|
dotIcon.setColor(colorForStatus(statusText));
|
|
|
|
if (isSelected) {
|
|
panel.setBackground(list.getSelectionBackground());
|
|
titleLabel.setForeground(list.getSelectionForeground());
|
|
metaLabel.setForeground(list.getSelectionForeground());
|
|
} else {
|
|
panel.setBackground(list.getBackground());
|
|
titleLabel.setForeground(list.getForeground());
|
|
metaLabel.setForeground(new Color(102, 102, 102));
|
|
}
|
|
|
|
return panel;
|
|
}
|
|
}
|
|
|
|
// right panel builds scroll panes for each tab
|
|
|
|
private void wireActions() {
|
|
validateButton.addActionListener(e -> {
|
|
if ("Cancel".equals(validateButton.getText())) {
|
|
cancelValidateInProgress();
|
|
return;
|
|
}
|
|
validateButton.setText("Cancel");
|
|
validateButton.setEnabled(true);
|
|
stopButton.setEnabled(true);
|
|
statusLabel.setText("Validating...");
|
|
log("Validating connection... (max ~10s; click Cancel or Stop to abort)");
|
|
Thread worker = new Thread(() -> {
|
|
try {
|
|
CyberStrikeAIClient.Config cfg = currentConfig();
|
|
String token = client.loginAndValidate(cfg);
|
|
if (Thread.currentThread().isInterrupted()) {
|
|
return;
|
|
}
|
|
tokenRef.set(token);
|
|
SwingUtilities.invokeLater(() -> statusLabel.setText("OK (token saved)"));
|
|
log("Validation OK.");
|
|
} catch (Exception ex) {
|
|
tokenRef.set("");
|
|
if (Thread.currentThread().isInterrupted()) {
|
|
SwingUtilities.invokeLater(() -> statusLabel.setText("Cancelled"));
|
|
log("Validation cancelled.");
|
|
} else {
|
|
SwingUtilities.invokeLater(() -> statusLabel.setText("Failed: " + ex.getMessage()));
|
|
log("Validation failed: " + ex.getMessage());
|
|
}
|
|
} finally {
|
|
validateThreadRef.set(null);
|
|
SwingUtilities.invokeLater(() -> {
|
|
validateButton.setText("Validate");
|
|
validateButton.setEnabled(true);
|
|
});
|
|
}
|
|
}, "CyberStrikeAI-Validate");
|
|
validateThreadRef.set(worker);
|
|
worker.start();
|
|
});
|
|
|
|
clearButton.addActionListener(e -> {
|
|
if (selectedRunId == null) {
|
|
progressArea.setText("");
|
|
finalRawArea.setText("");
|
|
markdownPane.setText("");
|
|
return;
|
|
}
|
|
TestRun run = runs.get(selectedRunId);
|
|
if (run == null) return;
|
|
synchronized (run) {
|
|
run.buffer.setLength(0);
|
|
run.progressBuffer.setLength(0);
|
|
run.finalBuffer.setLength(0);
|
|
}
|
|
progressArea.setText("");
|
|
finalRawArea.setText("");
|
|
markdownPane.setText("");
|
|
});
|
|
|
|
copyButton.addActionListener(e -> {
|
|
String text;
|
|
int idx = rightTabs.getSelectedIndex();
|
|
String tabName = idx >= 0 ? rightTabs.getTitleAt(idx) : "";
|
|
if ("Request".equals(tabName)) {
|
|
text = requestArea.getText();
|
|
} else if ("Response".equals(tabName)) {
|
|
text = responseArea.getText();
|
|
} else {
|
|
text = finalRawArea.getText();
|
|
}
|
|
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(text == null ? "" : text), null);
|
|
});
|
|
|
|
stopButton.addActionListener(e -> {
|
|
if ("Cancel".equals(validateButton.getText())) {
|
|
cancelValidateInProgress();
|
|
return;
|
|
}
|
|
|
|
String runId = selectedRunId;
|
|
if (runId != null && client.hasActiveRequest()) {
|
|
client.cancelActiveRequest();
|
|
appendProgressToRun(runId, "\n[info] Stream stopped.\n");
|
|
setRunStatus(runId, "cancelled");
|
|
return;
|
|
}
|
|
|
|
if (runId == null) return;
|
|
TestRun run = runs.get(runId);
|
|
if (run == null) return;
|
|
|
|
String token = getToken();
|
|
if (token == null || token.trim().isEmpty()) {
|
|
appendProgressToRun(runId, "\n[error] Not validated.\n");
|
|
return;
|
|
}
|
|
String convId;
|
|
synchronized (run) {
|
|
convId = run.conversationId;
|
|
}
|
|
if (convId == null || convId.trim().isEmpty()) {
|
|
appendProgressToRun(runId, "\n[info] conversationId not available yet (wait for server to create session).\n");
|
|
return;
|
|
}
|
|
|
|
stopButton.setEnabled(false);
|
|
new Thread(() -> {
|
|
try {
|
|
CyberStrikeAIClient.Config cfg = currentConfig();
|
|
client.cancelByConversationId(cfg.baseUrl, token, convId);
|
|
appendProgressToRun(runId, "\n[info] Cancel requested.\n");
|
|
setRunStatus(runId, "cancelling");
|
|
} catch (Exception ex) {
|
|
appendProgressToRun(runId, "\n[error] Cancel failed: " + ex.getMessage() + "\n");
|
|
} finally {
|
|
SwingUtilities.invokeLater(() -> stopButton.setEnabled(true));
|
|
}
|
|
}, "CyberStrikeAI-Cancel").start();
|
|
});
|
|
|
|
searchField.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() {
|
|
@Override public void insertUpdate(javax.swing.event.DocumentEvent e) { applyFilter(); }
|
|
@Override public void removeUpdate(javax.swing.event.DocumentEvent e) { applyFilter(); }
|
|
@Override public void changedUpdate(javax.swing.event.DocumentEvent e) { applyFilter(); }
|
|
});
|
|
|
|
renderMarkdownBox.addActionListener(e -> refreshOutputView());
|
|
}
|
|
|
|
private static final CyberStrikeAIClient.AgentMode[] AGENT_MODES = CyberStrikeAIClient.AgentMode.values();
|
|
|
|
CyberStrikeAIClient.Config currentConfig() {
|
|
String host = hostField.getText().trim();
|
|
String port = portField.getText().trim();
|
|
String password = new String(passwordField.getPassword());
|
|
String scheme = useHttpsBox.isSelected() ? "https" : "http";
|
|
String baseUrl = scheme + "://" + host + ":" + port;
|
|
int idx = agentModeBox.getSelectedIndex();
|
|
CyberStrikeAIClient.AgentMode mode = (idx >= 0 && idx < AGENT_MODES.length)
|
|
? AGENT_MODES[idx]
|
|
: CyberStrikeAIClient.AgentMode.EINO_SINGLE;
|
|
return new CyberStrikeAIClient.Config(baseUrl, password, mode);
|
|
}
|
|
|
|
String getToken() {
|
|
return tokenRef.get();
|
|
}
|
|
|
|
boolean isShowDebugEvents() {
|
|
return showDebugEventsBox.isSelected();
|
|
}
|
|
|
|
private String nextRunId() {
|
|
return "run_" + runSeq.getAndIncrement();
|
|
}
|
|
|
|
private String formatRunDisplay(String title, String agentMode, String status) {
|
|
return title + " [" + agentMode + "] - " + status;
|
|
}
|
|
|
|
String startNewRun(String title, String agentMode, IHttpRequestResponse msg) {
|
|
String id = nextRunId();
|
|
TestRun run = new TestRun(id, title, agentMode);
|
|
if (msg != null) {
|
|
run.requestRaw = bytesToString(msg.getRequest());
|
|
run.responseRaw = bytesToString(msg.getResponse());
|
|
}
|
|
runs.put(id, run);
|
|
|
|
int index = testListModel.getSize();
|
|
runIdToIndex.put(id, index);
|
|
testListModel.addElement(run);
|
|
filteredListModel.addElement(run);
|
|
|
|
selectedRunId = id;
|
|
filteredList.setSelectedIndex(filteredListModel.getSize() - 1);
|
|
progressArea.setText("");
|
|
finalRawArea.setText("");
|
|
markdownPane.setText("");
|
|
requestArea.setText(run.requestRaw);
|
|
responseArea.setText(run.responseRaw);
|
|
refreshOutputView();
|
|
return id;
|
|
}
|
|
|
|
void setRunStatus(String runId, String status) {
|
|
TestRun run = runs.get(runId);
|
|
if (run == null) return;
|
|
synchronized (run) {
|
|
run.status = status;
|
|
}
|
|
Integer index = runIdToIndex.get(runId);
|
|
if (index != null) {
|
|
SwingUtilities.invokeLater(() -> filteredList.repaint());
|
|
}
|
|
}
|
|
|
|
void setRunConversationId(String runId, String conversationId) {
|
|
if (runId == null) return;
|
|
TestRun run = runs.get(runId);
|
|
if (run == null) return;
|
|
synchronized (run) {
|
|
run.conversationId = conversationId == null ? "" : conversationId;
|
|
}
|
|
}
|
|
|
|
void appendToRun(String runId, String s) {
|
|
// Backward compatibility: default to progress bucket
|
|
appendProgressToRun(runId, s);
|
|
}
|
|
|
|
void appendProgressToRun(String runId, String s) {
|
|
if (runId == null || s == null) return;
|
|
TestRun run = runs.get(runId);
|
|
if (run == null) return;
|
|
synchronized (run) {
|
|
run.buffer.append(s);
|
|
run.progressBuffer.append(s);
|
|
}
|
|
if (runId.equals(selectedRunId)) {
|
|
SwingUtilities.invokeLater(() -> appendProgressUi(s, false));
|
|
}
|
|
}
|
|
|
|
private void appendProgressUi(String s, boolean forceFollow) {
|
|
JScrollBar bar = progressScrollPane != null ? progressScrollPane.getVerticalScrollBar() : null;
|
|
int scrollBefore = bar != null ? bar.getValue() : 0;
|
|
boolean follow = forceFollow || isScrollNearBottom(progressScrollPane);
|
|
progressArea.append(s);
|
|
if (follow) {
|
|
scrollPaneToBottom(progressScrollPane);
|
|
} else if (bar != null) {
|
|
bar.setValue(scrollBefore);
|
|
}
|
|
}
|
|
|
|
private void appendFinalUi(String s, boolean forceFollow) {
|
|
JScrollBar bar = finalRawScrollPane != null ? finalRawScrollPane.getVerticalScrollBar() : null;
|
|
int scrollBefore = bar != null ? bar.getValue() : 0;
|
|
boolean follow = forceFollow || isScrollNearBottom(finalRawScrollPane);
|
|
finalRawArea.append(s);
|
|
if (follow) {
|
|
scrollPaneToBottom(finalRawScrollPane);
|
|
} else if (bar != null) {
|
|
bar.setValue(scrollBefore);
|
|
}
|
|
}
|
|
|
|
void resetThinkingStream(String runId) {
|
|
if (runId == null) return;
|
|
TestRun run = runs.get(runId);
|
|
if (run == null) return;
|
|
synchronized (run) {
|
|
run.thinkingPending.setLength(0);
|
|
}
|
|
appendProgressToRun(runId, "\n[thinking]\n");
|
|
}
|
|
|
|
void appendThinkingDelta(String runId, String delta) {
|
|
if (runId == null || delta == null) return;
|
|
TestRun run = runs.get(runId);
|
|
if (run == null) return;
|
|
|
|
StringBuilder toAppend = new StringBuilder();
|
|
synchronized (run) {
|
|
for (int i = 0; i < delta.length(); i++) {
|
|
char c = delta.charAt(i);
|
|
if (c == '\n') {
|
|
if (run.thinkingPending.length() > 0) {
|
|
toAppend.append(" ").append(run.thinkingPending).append("\n");
|
|
run.thinkingPending.setLength(0);
|
|
} else {
|
|
toAppend.append("\n");
|
|
}
|
|
} else if (c != '\r') {
|
|
run.thinkingPending.append(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (toAppend.length() > 0) {
|
|
appendProgressToRun(runId, toAppend.toString());
|
|
}
|
|
}
|
|
|
|
void appendFinalToRun(String runId, String s) {
|
|
if (runId == null || s == null) return;
|
|
TestRun run = runs.get(runId);
|
|
if (run == null) return;
|
|
synchronized (run) {
|
|
run.buffer.append(s);
|
|
run.finalBuffer.append(s);
|
|
}
|
|
if (runId.equals(selectedRunId)) {
|
|
SwingUtilities.invokeLater(() -> appendFinalUi(s, false));
|
|
}
|
|
}
|
|
|
|
void setFinalResponse(String runId, String finalResponse) {
|
|
if (runId == null) return;
|
|
TestRun run = runs.get(runId);
|
|
if (run == null) return;
|
|
synchronized (run) {
|
|
run.finalResponse = finalResponse == null ? "" : finalResponse;
|
|
}
|
|
if (runId.equals(selectedRunId)) {
|
|
SwingUtilities.invokeLater(this::refreshOutputView);
|
|
}
|
|
}
|
|
|
|
private String getSelectedRunIdFromList() {
|
|
TestRun run = filteredList.getSelectedValue();
|
|
return run == null ? null : run.id;
|
|
}
|
|
|
|
private void setLogAreaToRun(String runId) {
|
|
TestRun run = runs.get(runId);
|
|
if (run == null) return;
|
|
selectedRunId = runId;
|
|
String progress;
|
|
String fin;
|
|
synchronized (run) {
|
|
progress = run.progressBuffer.toString();
|
|
fin = run.finalBuffer.toString();
|
|
}
|
|
SwingUtilities.invokeLater(() -> {
|
|
progressArea.setText(progress);
|
|
scrollPaneToBottom(progressScrollPane);
|
|
finalRawArea.setText(fin);
|
|
scrollPaneToBottom(finalRawScrollPane);
|
|
requestArea.setText(run.requestRaw == null ? "" : run.requestRaw);
|
|
responseArea.setText(run.responseRaw == null ? "" : run.responseRaw);
|
|
refreshOutputView();
|
|
});
|
|
}
|
|
|
|
private void clearAllRuns() {
|
|
runs.clear();
|
|
runIdToIndex.clear();
|
|
testListModel.clear();
|
|
filteredListModel.clear();
|
|
selectedRunId = null;
|
|
SwingUtilities.invokeLater(() -> {
|
|
progressArea.setText("");
|
|
finalRawArea.setText("");
|
|
markdownPane.setText("");
|
|
requestArea.setText("");
|
|
responseArea.setText("");
|
|
});
|
|
}
|
|
|
|
void clearAndShowStreamHeader(String title) {
|
|
SwingUtilities.invokeLater(() -> {
|
|
progressArea.setText("[*] " + title + "\n\n");
|
|
finalRawArea.setText("");
|
|
markdownPane.setText("");
|
|
});
|
|
}
|
|
|
|
// Legacy helpers kept for Validate logging
|
|
void appendStreamLine(String s) {
|
|
if (s == null) return;
|
|
SwingUtilities.invokeLater(() -> appendProgressUi(s + "\n", false));
|
|
}
|
|
|
|
private void log(String s) {
|
|
appendStreamLine("[*] " + s);
|
|
}
|
|
|
|
private void cancelValidateInProgress() {
|
|
client.cancelActiveRequest();
|
|
Thread t = validateThreadRef.getAndSet(null);
|
|
if (t != null) {
|
|
t.interrupt();
|
|
}
|
|
SwingUtilities.invokeLater(() -> {
|
|
statusLabel.setText("Cancelled");
|
|
validateButton.setText("Validate");
|
|
validateButton.setEnabled(true);
|
|
});
|
|
log("Validation cancelled.");
|
|
}
|
|
|
|
private void applyFilter() {
|
|
String q = searchField.getText();
|
|
if (q == null) q = "";
|
|
String query = q.trim().toLowerCase();
|
|
filteredListModel.clear();
|
|
for (int i = 0; i < testListModel.size(); i++) {
|
|
TestRun r = testListModel.getElementAt(i);
|
|
if (query.isEmpty() || (r.title != null && r.title.toLowerCase().contains(query))) {
|
|
filteredListModel.addElement(r);
|
|
}
|
|
}
|
|
if (filteredListModel.size() > 0 && filteredList.getSelectedIndex() < 0) {
|
|
filteredList.setSelectedIndex(0);
|
|
}
|
|
}
|
|
|
|
private void refreshOutputView() {
|
|
if (!renderMarkdownBox.isSelected()) {
|
|
outputCardsLayout.show(outputCards, "raw");
|
|
return;
|
|
}
|
|
|
|
if (selectedRunId == null) {
|
|
outputCardsLayout.show(outputCards, "raw");
|
|
return;
|
|
}
|
|
|
|
TestRun run = runs.get(selectedRunId);
|
|
if (run == null) {
|
|
outputCardsLayout.show(outputCards, "raw");
|
|
return;
|
|
}
|
|
|
|
String finalResp;
|
|
synchronized (run) {
|
|
finalResp = run.finalResponse;
|
|
}
|
|
if (finalResp == null || finalResp.trim().isEmpty()) {
|
|
// while streaming, stick to raw for performance
|
|
outputCardsLayout.show(outputCards, "raw");
|
|
return;
|
|
}
|
|
|
|
String html = MarkdownRenderer.toHtml(finalResp);
|
|
markdownPane.setText(html);
|
|
markdownPane.setCaretPosition(0);
|
|
outputCardsLayout.show(outputCards, "md");
|
|
}
|
|
private static String bytesToString(byte[] bytes) {
|
|
if (bytes == null || bytes.length == 0) return "";
|
|
return new String(bytes, StandardCharsets.ISO_8859_1);
|
|
}
|
|
|
|
@Override
|
|
public String getTabCaption() {
|
|
return "CyberStrikeAI";
|
|
}
|
|
|
|
@Override
|
|
public Component getUiComponent() {
|
|
return root;
|
|
}
|
|
}
|
|
|