@@ -96,15 +96,18 @@ public String getLastAssistantResponse()
9696 }
9797
9898 /**
99- * @return every SQL expression suggested in the most recent assistant response, in display order.
100- * Usually a single entry, occasionally more.
99+ * @return every <em>applicable</em> SQL expression suggested in the most recent assistant response, in display
100+ * order. Only counts {@code .assistant-expression} blocks that include an "Apply Expression" button — read-only
101+ * SQL the assistant shows for illustration (e.g. an alternative custom-query example) is excluded, since the user
102+ * can't accept it as the field's calculation.
101103 */
102104 public List <String > getSuggestedExpressions ()
103105 {
104106 WebElement lastResponse = lastAssistantResponseElement ();
105107 if (lastResponse == null )
106108 return List .of ();
107109 return Locator .tagWithClass ("div" , "assistant-expression" )
110+ .withDescendant (Locator .tagWithClass ("button" , "clickable-text" ))
108111 .descendant (Locator .tag ("code" ))
109112 .findElements (lastResponse ).stream ()
110113 .map (WebElement ::getText )
@@ -125,17 +128,67 @@ public String getFirstSuggestedExpression()
125128 * Returns the underlying field row (the dialog stays open; call {@link #clickEndChat()} to close it).
126129 */
127130 public DomainFieldRow applyFirstSuggestedExpression ()
131+ {
132+ return applySuggestedExpression (0 );
133+ }
134+
135+ /**
136+ * Click "Apply Expression" on the suggestion at the given index in the most recent assistant response.
137+ */
138+ public DomainFieldRow applySuggestedExpression (int index )
128139 {
129140 WebElement lastResponse = lastAssistantResponseElement ();
130141 if (lastResponse == null )
131142 throw new IllegalStateException ("No assistant response is available to apply." );
132- Locator .tagWithClass ("div" , "assistant-expression" )
143+ List < WebElement > buttons = Locator .tagWithClass ("div" , "assistant-expression" )
133144 .descendant (Locator .tagWithClass ("button" , "clickable-text" ))
134- .findElement (lastResponse )
135- .click ();
145+ .findElements (lastResponse );
146+ if (index < 0 || index >= buttons .size ())
147+ throw new IndexOutOfBoundsException (
148+ "Requested expression index " + index + " but only " + buttons .size () + " expression(s) available." );
149+ buttons .get (index ).click ();
136150 return _row ;
137151 }
138152
153+ /**
154+ * @return text of the first assistant response in the chat history, or empty string if there are none. Useful
155+ * for asserting the intro message in NEW / CHANGE / VALIDATE entry modes.
156+ */
157+ public String getFirstAssistantResponse ()
158+ {
159+ List <String > responses = getAssistantResponses ();
160+ return responses .isEmpty () ? "" : responses .get (0 );
161+ }
162+
163+ /**
164+ * @return true while the dialog is waiting for an AI response (the "Thinking..." pending bubble is shown).
165+ */
166+ public boolean isPending ()
167+ {
168+ return Locator .tagWithClass ("div" , "chat-item" ).withClass ("pending" ).existsIn (this );
169+ }
170+
171+ /**
172+ * Click the stop button to abort an in-flight AI request. The submit button toggles to a stop button (fa-stop)
173+ * while the dialog is in the pending state; calling this method when no request is pending will fail.
174+ */
175+ public void clickStop ()
176+ {
177+ Locator .tagWithClass ("button" , "prompt-button" )
178+ .withDescendant (Locator .tagWithClass ("i" , "fa-stop" ))
179+ .findElement (this )
180+ .click ();
181+ }
182+
183+ /**
184+ * Click submit without waiting for the response. Useful for tests that need to interrupt or otherwise observe
185+ * the pending state before the response arrives. Prefer {@link #submitPrompt()} when the caller wants to wait.
186+ */
187+ public void clickSubmitWithoutWaiting ()
188+ {
189+ elementCache ().promptSubmitButton .click ();
190+ }
191+
139192 private WebElement lastAssistantResponseElement ()
140193 {
141194 List <WebElement > responses = Locator .tagWithClass ("div" , "chat-item" ).withClass ("assistant-response" )
@@ -169,12 +222,12 @@ protected class ElementCache extends ModalDialog.ElementCache
169222 {
170223 final WebElement endChatButton = Locator .tagWithClass ("button" , "btn" )
171224 .withText ("End Chat" )
172- .findWhenNeeded (this );
225+ .refindWhenNeeded (this );
173226
174227 final WebElement promptInput = Locator .tagWithClass ("textarea" , "prompt-input" )
175- .findWhenNeeded (this );
228+ .refindWhenNeeded (this );
176229
177230 final WebElement promptSubmitButton = Locator .tagWithClass ("button" , "prompt-button" )
178- .findWhenNeeded (this );
231+ .refindWhenNeeded (this );
179232 }
180233}
0 commit comments