Merge "Add "Resolve comments" prompt to AI dialog"
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);
+  });
 });