Merge "Update README to use navigationToken for stubbing"
diff --git a/polygerrit-ui/app/api/ai-code-review.ts b/polygerrit-ui/app/api/ai-code-review.ts
index 640dc1b..6c0c759 100644
--- a/polygerrit-ui/app/api/ai-code-review.ts
+++ b/polygerrit-ui/app/api/ai-code-review.ts
@@ -259,6 +259,10 @@
 }
 
 export declare interface ContextItem {
+  /**
+   * The type of the context item, e.g. 'gerrit' or 'buganizer'.
+   * Corresponds to the 'type' of a GerritReference provided by the plugin.
+   */
   type_id: string;
   link: string;
   title: string;
diff --git a/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog.ts b/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog.ts
index a990898..1b73243 100644
--- a/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog.ts
@@ -16,6 +16,9 @@
 import {subscribe} from '../../lit/subscription-controller';
 import {resolve} from '../../../models/dependency';
 import {changeModelToken} from '../../../models/change/change-model';
+import {commentsModelToken} from '../../../models/comments/comments-model';
+import {CommentThread} from '../../../types/common';
+import {isUnresolved} from '../../../utils/comment-util';
 import {ParsedChangeInfo} from '../../../types/types';
 import {PatchSetNum} from '../../../types/common';
 import {HELP_ME_REVIEW_PROMPT, IMPROVE_COMMIT_MESSAGE} from './prompts';
@@ -42,6 +45,11 @@
     label: 'Just patch content',
     prompt: '{{patch}}',
   },
+  RESOLVE_COMMENTS: {
+    id: 'resolve_comments',
+    label: 'Unresolved Comments',
+    prompt: '{{comments}}',
+  },
 };
 
 const CONTEXT_OPTIONS = [
@@ -76,12 +84,17 @@
 
   @state() private context = 3;
 
+  // private but used in tests
+  @state() threads: CommentThread[] = [];
+
   @state() private promptContent = '';
 
   @state() private promptSize = '';
 
   private readonly getChangeModel = resolve(this, changeModelToken);
 
+  private readonly getCommentsModel = resolve(this, commentsModelToken);
+
   private readonly restApiService = getAppContext().restApiService;
 
   constructor() {
@@ -96,6 +109,11 @@
       () => this.getChangeModel().patchNum$,
       x => (this.patchNum = x)
     );
+    subscribe(
+      this,
+      () => this.getCommentsModel().threads$,
+      x => (this.threads = x)
+    );
   }
 
   static override get styles() {
@@ -297,7 +315,8 @@
   override willUpdate(changedProperties: PropertyValues) {
     if (
       changedProperties.has('patchContent') ||
-      changedProperties.has('selectedTemplate')
+      changedProperties.has('selectedTemplate') ||
+      changedProperties.has('threads')
     ) {
       this.updatePromptContent();
     }
@@ -338,6 +357,29 @@
     this.patchContent = content;
   }
 
+  private getUnresolvedCommentsFormatted(): string {
+    const unresolvedThreads = this.threads.filter(isUnresolved);
+    if (unresolvedThreads.length === 0) return 'No unresolved comments.';
+
+    return unresolvedThreads
+      .map(thread => {
+        const comments = thread.comments.map(
+          c => `${c.author?.name ?? 'Unknown'}:\n${c.message}`
+        );
+        let loc = '';
+        if (thread.line) {
+          loc = `Line ${thread.line}`;
+        } else if (thread.range) {
+          loc = `Lines ${thread.range.start_line}-${thread.range.end_line}`;
+        } else {
+          loc = 'File level';
+        }
+        return `* File: ${thread.path} (${loc})
+${comments.join('\n\n')}`;
+      })
+      .join('\n\n');
+  }
+
   private updatePromptContent() {
     if (!this.patchContent) {
       this.promptContent = '';
@@ -349,6 +391,12 @@
       '{{patch}}',
       this.patchContent
     );
+    if (this.selectedTemplate === 'RESOLVE_COMMENTS') {
+      this.promptContent = this.promptContent.replace(
+        '{{comments}}',
+        this.getUnresolvedCommentsFormatted()
+      );
+    }
     // Inserts a space before each capital letter to handle CamelCase
     const textWithSpaces = this.promptContent.replace(/([A-Z])/g, ' $1');
 
diff --git a/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog_test.ts
index 8165a0f..6ac9971 100644
--- a/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog_test.ts
@@ -10,6 +10,9 @@
 import {createParsedChange} from '../../../test/test-data-generators';
 import {CommitId, PatchSetNum} from '../../../api/rest-api';
 import {stubRestApi, waitUntil} from '../../../test/test-utils';
+import {testResolver} from '../../../test/common-test-setup';
+import {commentsModelToken} from '../../../models/comments/comments-model';
+import {of} from 'rxjs';
 
 suite('gr-ai-prompt-dialog test', () => {
   let element: GrAiPromptDialog;
@@ -17,6 +20,12 @@
   setup(async () => {
     getPatchContentStub = stubRestApi('getPatchContent');
     getPatchContentStub.resolves('test code');
+    const commentsModel = testResolver(commentsModelToken);
+    Object.defineProperty(commentsModel, 'threads$', {
+      value: of([]),
+      writable: true,
+    });
+
     element = await fixture(html`<gr-ai-prompt-dialog></gr-ai-prompt-dialog>`);
     element.change = createParsedChange();
     element.change.revisions['abc'].commit!.parents = [
@@ -69,6 +78,14 @@
                    </md-radio>
                    Just patch content
                  </label>
+                 <label class="template-option">
+                   <md-radio
+                     name="template"
+                     tabindex="-1"
+                   >
+                   </md-radio>
+                   Unresolved Comments
+                 </label>
                </div>
              </div>
              <div class="context-selector">
@@ -167,4 +184,42 @@
       )
     );
   });
+  test('renders help review prompt', async () => {
+    element.selectedTemplate = 'HELP_REVIEW';
+    await element.updateComplete;
+    assert.include(
+      (element as any).promptContent,
+      'You are a highly experienced code reviewer'
+    );
+  });
+
+  test('renders resolve comments prompt', async () => {
+    element.selectedTemplate = 'RESOLVE_COMMENTS';
+    await element.updateComplete;
+    assert.include((element as any).promptContent, 'No unresolved comments.');
+  });
+
+  test('renders resolve comments prompt with comments', async () => {
+    element.threads = [
+      {
+        comments: [
+          {
+            message: 'test comment',
+            author: {name: 'Tester'},
+            updated: '2025-01-01 10:00:00.000000000',
+            unresolved: true,
+          },
+        ],
+        path: 'test.txt',
+        line: 1,
+        rootId: '1',
+      },
+    ] as any[];
+    element.selectedTemplate = 'RESOLVE_COMMENTS';
+    await element.updateComplete;
+    const expected = `* File: test.txt (Line 1)
+Tester:
+test comment`;
+    assert.include((element as any).promptContent, expected);
+  });
 });
diff --git a/polygerrit-ui/app/elements/chat-panel/context-chip.ts b/polygerrit-ui/app/elements/chat-panel/context-chip.ts
index c451bbb..92a8ac4 100644
--- a/polygerrit-ui/app/elements/chat-panel/context-chip.ts
+++ b/polygerrit-ui/app/elements/chat-panel/context-chip.ts
@@ -53,6 +53,7 @@
     }
     md-filter-chip {
       --md-sys-color-primary: var(--primary-text-color);
+      --md-filter-chip-label-text-color: var(--primary-text-color);
       --md-filter-chip-container-height: 20px;
       --md-filter-chip-label-text-size: var(--font-size-small);
       --md-filter-chip-label-text-weight: var(--font-weight-medium);
@@ -90,9 +91,8 @@
           .isCustomAction
           ? 'custom-action-chip'
           : ''}"
-        .label=${this.text}
-        ?selected=${this.isCustomAction}
-        .title=${this.tooltip ?? ''}
+        .label=${this.contextItem?.title ?? this.text}
+        .title=${this.contextItem?.tooltip ?? this.tooltip ?? ''}
         @click=${this.navigateToUrl}
         ?removable=${this.isRemovable && !this.isSuggestion}
         @remove=${this.onRemoveContextChip}
diff --git a/polygerrit-ui/app/elements/chat-panel/prompt-box.ts b/polygerrit-ui/app/elements/chat-panel/prompt-box.ts
index 4474b4f..95afaf1 100644
--- a/polygerrit-ui/app/elements/chat-panel/prompt-box.ts
+++ b/polygerrit-ui/app/elements/chat-panel/prompt-box.ts
@@ -8,7 +8,7 @@
 import './context-chip';
 import './context-input-chip';
 
-import {css, html, LitElement} from 'lit';
+import {css, html, LitElement, nothing} from 'lit';
 import {customElement, property, query, state} from 'lit/decorators.js';
 import {when} from 'lit/directives/when.js';
 
@@ -18,13 +18,16 @@
   ModelInfo,
 } from '../../api/ai-code-review';
 import {chatModelToken, Turn} from '../../models/chat/chat-model';
+import {changeModelToken} from '../../models/change/change-model';
 import {
   contextItemEquals,
   searchForContextLinks,
 } from '../../models/chat/context-item-util';
 import {resolve} from '../../models/dependency';
+import {ParsedChangeInfo} from '../../types/types';
 import {debounce, DelayedTask} from '../../utils/async-util';
 import {fire} from '../../utils/event-util';
+import {createChangeUrl} from '../../models/views/change';
 import {subscribe} from '../lit/subscription-controller';
 
 const MAX_VISIBLE_CONTEXT_ITEMS_COLLAPSED = 3;
@@ -65,6 +68,8 @@
 
   @state() contextItemTypes: readonly ContextItemType[] = [];
 
+  @state() private change?: ParsedChangeInfo;
+
   // TODO(milutin): Find out if we need this.
   // @ts-ignore
   private turnBasisForUserInput?: number;
@@ -77,10 +82,17 @@
 
   private readonly getChatModel = resolve(this, chatModelToken);
 
+  private readonly getChangeModel = resolve(this, changeModelToken);
+
   constructor() {
     super();
     subscribe(
       this,
+      () => this.getChangeModel().change$,
+      x => (this.change = x)
+    );
+    subscribe(
+      this,
       () => this.getChatModel().modelsLoadingError$,
       x => (this.hasModelLoadingError = !!x)
     );
@@ -340,6 +352,28 @@
     )}`;
   }
 
+  private renderThisChangeChip() {
+    // This Change is implicitly added to the context, so we don't need to add it.
+    // The chip makes it clear to the user that it is already in the context.
+    if (!this.change) return nothing;
+    const changeContextItem: ContextItem = {
+      type_id: 'gerrit',
+      link: createChangeUrl({
+        change: this.change,
+      }),
+      title: 'This Change',
+      identifier: this.change.id,
+      tooltip: 'File diffs (against base), commit message, and comments.',
+    };
+    return html`
+      <context-chip
+        class="this-change-context"
+        .contextItem=${changeContextItem}
+        .isRemovable=${false}
+      ></context-chip>
+    `;
+  }
+
   private renderAddContext() {
     return html`
       <md-chip-set class="context-chip-set">
@@ -347,6 +381,7 @@
           @context-item-added=${(e: CustomEvent<ContextItem>) =>
             this.onContextItemAdded(e.detail)}
         ></context-input-chip>
+        ${this.renderThisChangeChip()}
         ${(this.showAllContextItems
           ? this.contextItems
           : this.contextItems.slice(0, MAX_VISIBLE_CONTEXT_ITEMS_COLLAPSED)
diff --git a/polygerrit-ui/app/elements/chat-panel/prompt-box_test.ts b/polygerrit-ui/app/elements/chat-panel/prompt-box_test.ts
index 429bba7..2913d9d 100644
--- a/polygerrit-ui/app/elements/chat-panel/prompt-box_test.ts
+++ b/polygerrit-ui/app/elements/chat-panel/prompt-box_test.ts
@@ -63,6 +63,7 @@
         </div>
         <md-chip-set class="context-chip-set">
           <context-input-chip> </context-input-chip>
+          <context-chip class="this-change-context"> </context-chip>
         </md-chip-set>
       `
     );
@@ -134,7 +135,9 @@
       },
     });
     await element.updateComplete;
-    const contextChips = element.shadowRoot?.querySelectorAll('context-chip');
+    const contextChips = element.shadowRoot?.querySelectorAll(
+      'context-chip.external-context'
+    );
     assert.isOk(contextChips);
     assert.equal(contextChips?.length, 2);
   });
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-dark.png
index 4b6e5e7..c7ad776 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-dark.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-dark.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations-dark.png
index 9494d6e..dac5fa2 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations-dark.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations-dark.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations.png
index 277550e..105a290 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment-dark.png
index 1f1cd44..c460946 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment-dark.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment-dark.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment.png
index 9d635d5..7914809 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment.png
Binary files differ
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
index c2d1f08..14f8b9d 100644
--- 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
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
index 9677c0f..18a3a1eb 100644
--- 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
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references-dark.png
index caa010a..694df41 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references-dark.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references-dark.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references.png
index fd428cd..924e634 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode.png
index 9c55238..387d1bf 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions-dark.png
index c85405c..7493690 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions-dark.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions-dark.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions.png
index ceadc1d..a9c0589 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-dark.png
index 94e160f..a888964 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-dark.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-dark.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private-dark.png
index 204f938..8fcf45d 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private-dark.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private-dark.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private.png
index 8ccf3b6..43b6c10 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page.png
index 6344dc9..904a4d4 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open-dark.png
index b1ee87c..6ed4e5d 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open-dark.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open-dark.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open.png
index 6be0d064..0cdb6c6 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open.png
Binary files differ