Merge "Improve chat error UI and add retry button"
diff --git a/polygerrit-ui/app/elements/chat-panel/chat-panel_screenshot_test.ts b/polygerrit-ui/app/elements/chat-panel/chat-panel_screenshot_test.ts
index 90d7e6a..9c16d2f 100644
--- a/polygerrit-ui/app/elements/chat-panel/chat-panel_screenshot_test.ts
+++ b/polygerrit-ui/app/elements/chat-panel/chat-panel_screenshot_test.ts
@@ -178,6 +178,32 @@
     await visualDiffDarkTheme(element, 'chat-panel-chat-mode-with-comment');
   });
 
+  test('chat mode with error', async () => {
+    chatModel.updateState({
+      ...chatModel.getState(),
+      turns: [
+        {
+          userMessage: {
+            content: 'Do something',
+            userType: UserType.USER,
+            contextItems: [],
+          },
+          geminiMessage: {
+            responseParts: [],
+            regenerationIndex: 0,
+            references: [],
+            citations: [],
+            userType: UserType.GEMINI,
+            errorMessage: 'Something went wrong',
+          },
+        },
+      ] as Turn[],
+    });
+    await element.updateComplete;
+    await visualDiff(element, 'chat-panel-chat-mode-with-error');
+    await visualDiffDarkTheme(element, 'chat-panel-chat-mode-with-error');
+  });
+
   test('chat mode with references', async () => {
     chatModel.updateState({
       ...chatModel.getState(),
diff --git a/polygerrit-ui/app/elements/chat-panel/gemini-message.ts b/polygerrit-ui/app/elements/chat-panel/gemini-message.ts
index 0b2e4fb..eb399b8 100644
--- a/polygerrit-ui/app/elements/chat-panel/gemini-message.ts
+++ b/polygerrit-ui/app/elements/chat-panel/gemini-message.ts
@@ -97,7 +97,34 @@
         margin-left: 10px;
       }
       .server-error {
-        font-weight: bold;
+        display: flex;
+        align-items: center;
+        gap: var(--spacing-s);
+        font-weight: 500;
+        color: var(--error-foreground);
+        margin-bottom: var(--spacing-s);
+      }
+      .error-icon {
+        color: var(--error-foreground);
+      }
+      .error-message {
+        margin-bottom: var(--spacing-m);
+        color: var(--deemphasized-text-color);
+      }
+      .error-details {
+        margin-top: var(--spacing-s);
+        margin-bottom: var(--spacing-s);
+        font-family: var(--monospace-font-family);
+        font-size: var(--font-size-small);
+        white-space: pre-wrap;
+        background-color: var(--background-color-tertiary);
+        padding: var(--spacing-s);
+        border-radius: var(--border-radius);
+      }
+      .error-actions {
+        display: flex;
+        gap: var(--spacing-m);
+        margin-top: var(--spacing-s);
       }
       .user-info {
         margin-bottom: var(--spacing-m);
@@ -179,6 +206,10 @@
     this.getCommentsModel().reloadAllComments();
   }
 
+  private onRetry() {
+    this.getChatModel().regenerateMessage(this.turnId());
+  }
+
   private toggleShowErrorDetails() {
     this.showErrorDetails = !this.showErrorDetails;
   }
@@ -210,24 +241,23 @@
       ${when(
         message.errorMessage,
         () => html`
-          <p class="server-error text-content">Server issue.</p>
-          <p class="error-message">
-            We were unable to fulfill your request for this due to a server
-            issue. Please reload the webpage to try again.
-          </p>
-          <md-text-button
-            @click=${() => this.toggleShowErrorDetails()}
-            class="error-details-button"
-          >
-            <gr-icon
-              icon=${this.showErrorDetails ? 'expand_less' : 'expand_more'}
-            ></gr-icon>
-            Details
-          </md-text-button>
-          ${when(
-            this.showErrorDetails,
-            () => html`<p class="error-details">${message.errorMessage}</p>`
-          )}
+          <div class="server-error text-content">
+            <gr-icon icon="error" class="error-icon"></gr-icon>
+            Server error
+          </div>
+          <div class="error-message">
+            We were unable to fulfill your request.
+            ${when(
+              this.showErrorDetails,
+              () => html`<p class="error-details">${message.errorMessage}</p>`
+            )}
+            <div class="error-actions">
+              <gr-button @click=${() => this.onRetry()} link>Retry</gr-button>
+              <gr-button @click=${() => this.toggleShowErrorDetails()} link>
+                ${this.showErrorDetails ? 'Hide details' : 'Show details'}
+              </gr-button>
+            </div>
+          </div>
         `
       )}
       ${when(!message.errorMessage && responseParts.length === 0, () =>
diff --git a/polygerrit-ui/app/elements/chat-panel/gemini-message_test.ts b/polygerrit-ui/app/elements/chat-panel/gemini-message_test.ts
index 1edeb95..998e8db 100644
--- a/polygerrit-ui/app/elements/chat-panel/gemini-message_test.ts
+++ b/polygerrit-ui/app/elements/chat-panel/gemini-message_test.ts
@@ -133,7 +133,21 @@
 
     const error = element.shadowRoot?.querySelector('.server-error');
     assert.isOk(error);
-    assert.equal(error?.textContent, 'Server issue.');
+    assert.include(error?.textContent, 'Server error');
+
+    const retryButton = element.shadowRoot?.querySelector(
+      'gr-button'
+    ) as HTMLElement;
+    assert.isOk(retryButton);
+    assert.equal(retryButton.textContent, 'Retry');
+
+    const spy = sinon.spy(chatModel, 'regenerateMessage');
+    retryButton.click();
+    assert.isTrue(spy.calledOnce);
+    assert.deepEqual(spy.firstCall.args[0], {
+      turnIndex: 0,
+      regenerationIndex: 0,
+    });
   });
 
   test('renders suggested comment', async () => {
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-error-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-error-dark.png
new file mode 100644
index 0000000..c2d1f08
--- /dev/null
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-error-dark.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-error.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-error.png
new file mode 100644
index 0000000..9677c0f
--- /dev/null
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-error.png
Binary files differ