55import com .google .genai .types .Part ;
66import java .awt .BorderLayout ;
77import java .awt .FlowLayout ;
8+ import java .awt .Window ;
9+ import java .awt .event .ActionEvent ;
10+ import java .awt .event .KeyEvent ;
811import java .awt .event .WindowAdapter ;
912import java .awt .event .WindowEvent ;
1013import java .lang .reflect .InvocationTargetException ;
1316import java .util .LinkedHashMap ;
1417import java .util .List ;
1518import java .util .Map ;
19+ import javax .swing .AbstractAction ;
1620import javax .swing .JButton ;
1721import javax .swing .JComponent ;
1822import javax .swing .JDialog ;
19- import javax .swing .JFrame ;
2023import javax .swing .JPanel ;
2124import javax .swing .JScrollPane ;
2225import javax .swing .JTextArea ;
26+ import javax .swing .KeyStroke ;
2327import javax .swing .SwingUtilities ;
2428import javax .swing .border .TitledBorder ;
29+ import javax .swing .undo .UndoManager ;
2530import lombok .extern .slf4j .Slf4j ;
2631import uno .anahata .ai .Chat ;
2732import uno .anahata .ai .ChatMessage ;
2833import uno .anahata .ai .config .ChatConfig ;
2934import uno .anahata .ai .tools .FunctionConfirmation ;
3035import uno .anahata .ai .tools .FunctionPrompter ;
31- import uno .anahata .ai .swing .SwingChatConfig .UITheme ;
3236import uno .anahata .ai .swing .render .ContentRenderer ;
3337import uno .anahata .ai .swing .render .InteractiveFunctionCallRenderer ;
3438
@@ -52,16 +56,17 @@ public class SwingFunctionPrompter extends JDialog implements FunctionPrompter {
5256 * @param chatPanel The ChatPanel that initiated the prompt.
5357 */
5458 public SwingFunctionPrompter (ChatPanel chatPanel ) {
55- // We use null as the owner initially because the ChatPanel might not be added to a window yet.
56- // The title is updated in the prompt() method once the Chat instance is fully initialized.
57- super ((JFrame ) null , "Confirm Proposed Actions" , true );
59+ // We use the window ancestor of the chatPanel as the owner to prevent the "modal hang"
60+ super (SwingUtilities .getWindowAncestor (chatPanel ), "Confirm Proposed Actions" , ModalityType .APPLICATION_MODAL );
5861 this .chatPanel = chatPanel ;
5962 }
6063
6164 @ Override
6265 public PromptResult prompt (ChatMessage modelMessage , Chat chat ) {
6366 try {
6467 SwingUtilities .invokeAndWait (() -> {
68+ Window owner = SwingUtilities .getWindowAncestor (chatPanel );
69+
6570 setTitle ("Confirm Proposed Actions - " + chat .getDisplayName ());
6671 interactiveRenderers .clear ();
6772 functionConfirmations .clear ();
@@ -71,7 +76,10 @@ public PromptResult prompt(ChatMessage modelMessage, Chat chat) {
7176 initComponents (modelMessage );
7277 pack ();
7378 setSize (1024 , 768 );
74- setLocationRelativeTo (SwingUtilities .getWindowAncestor (chatPanel ));
79+ setLocationRelativeTo (owner );
80+
81+ // Ensure it's visible and focused
82+ toFront ();
7583 setVisible (true ); // This blocks until the dialog is closed
7684 });
7785 } catch (InterruptedException | InvocationTargetException e ) {
@@ -116,6 +124,25 @@ private void initComponents(ChatMessage modelMessage) {
116124
117125 JPanel bottomPanel = new JPanel (new BorderLayout (0 , 5 ));
118126 JTextArea commentTextArea = new JTextArea (3 , 60 );
127+
128+ // Undo/Redo Support for the comment area
129+ UndoManager undoManager = new UndoManager ();
130+ commentTextArea .getDocument ().addUndoableEditListener (e -> undoManager .addEdit (e .getEdit ()));
131+ commentTextArea .getInputMap ().put (KeyStroke .getKeyStroke (KeyEvent .VK_Z , KeyEvent .CTRL_DOWN_MASK ), "Undo" );
132+ commentTextArea .getActionMap ().put ("Undo" , new AbstractAction () {
133+ @ Override
134+ public void actionPerformed (ActionEvent e ) {
135+ if (undoManager .canUndo ()) undoManager .undo ();
136+ }
137+ });
138+ commentTextArea .getInputMap ().put (KeyStroke .getKeyStroke (KeyEvent .VK_Y , KeyEvent .CTRL_DOWN_MASK ), "Redo" );
139+ commentTextArea .getActionMap ().put ("Redo" , new AbstractAction () {
140+ @ Override
141+ public void actionPerformed (ActionEvent e ) {
142+ if (undoManager .canRedo ()) undoManager .redo ();
143+ }
144+ });
145+
119146 JPanel commentPanel = new JPanel (new BorderLayout ());
120147 commentPanel .setBorder (new TitledBorder ("Add Comment (Optional)" ));
121148 commentPanel .add (new JScrollPane (commentTextArea ), BorderLayout .CENTER );
@@ -136,7 +163,6 @@ private void initComponents(ChatMessage modelMessage) {
136163 cancelButton .addActionListener (e -> {
137164 this .userComment = commentTextArea .getText ();
138165 this .cancelled = true ;
139- // Do NOT collect results, the cancellation overrides individual choices.
140166 setVisible (false );
141167 dispose ();
142168 });
@@ -145,7 +171,6 @@ private void initComponents(ChatMessage modelMessage) {
145171 addWindowListener (new WindowAdapter () {
146172 @ Override
147173 public void windowClosing (WindowEvent e ) {
148- // Treat closing the dialog as a cancellation
149174 userComment = commentTextArea .getText ();
150175 cancelled = true ;
151176 setVisible (false );
@@ -165,7 +190,6 @@ private Map<FunctionCall, FunctionConfirmation> collectResultsFromInteractiveRen
165190 FunctionCall functionCall = renderer .getFunctionCall ();
166191 FunctionConfirmation state = renderer .getSelectedState ();
167192
168- // Persist the user's choice for the next time this function is called.
169193 config .setFunctionConfirmation (functionCall , state );
170194 results .put (functionCall , state );
171195 }
0 commit comments