@in-the-loop-labs/pair-review 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +371 -0
- package/bin/git-diff-lines +146 -0
- package/bin/pair-review.js +49 -0
- package/package.json +71 -0
- package/public/css/ai-summary-modal.css +183 -0
- package/public/css/pr.css +8698 -0
- package/public/css/repo-settings.css +891 -0
- package/public/css/styles.css +479 -0
- package/public/favicon.png +0 -0
- package/public/index.html +1104 -0
- package/public/js/components/AIPanel.js +1639 -0
- package/public/js/components/AISummaryModal.js +278 -0
- package/public/js/components/AnalysisConfigModal.js +684 -0
- package/public/js/components/ConfirmDialog.js +227 -0
- package/public/js/components/PreviewModal.js +344 -0
- package/public/js/components/ProgressModal.js +678 -0
- package/public/js/components/ReviewModal.js +531 -0
- package/public/js/components/SplitButton.js +382 -0
- package/public/js/components/StatusIndicator.js +265 -0
- package/public/js/components/SuggestionNavigator.js +489 -0
- package/public/js/components/Toast.js +166 -0
- package/public/js/local.js +1580 -0
- package/public/js/modules/analysis-history.js +940 -0
- package/public/js/modules/comment-manager.js +643 -0
- package/public/js/modules/diff-renderer.js +585 -0
- package/public/js/modules/file-comment-manager.js +1242 -0
- package/public/js/modules/gap-coordinates.js +190 -0
- package/public/js/modules/hunk-parser.js +358 -0
- package/public/js/modules/line-tracker.js +386 -0
- package/public/js/modules/panel-resizer.js +228 -0
- package/public/js/modules/storage-cleanup.js +36 -0
- package/public/js/modules/suggestion-manager.js +692 -0
- package/public/js/pr.js +3503 -0
- package/public/js/repo-settings.js +691 -0
- package/public/js/utils/file-order.js +87 -0
- package/public/js/utils/markdown.js +97 -0
- package/public/js/utils/suggestion-ui.js +55 -0
- package/public/js/utils/tier-icons.js +25 -0
- package/public/local.html +460 -0
- package/public/pr.html +329 -0
- package/public/repo-settings.html +243 -0
- package/src/ai/analyzer.js +2592 -0
- package/src/ai/claude-cli.js +153 -0
- package/src/ai/claude-provider.js +261 -0
- package/src/ai/codex-provider.js +361 -0
- package/src/ai/copilot-provider.js +345 -0
- package/src/ai/gemini-provider.js +375 -0
- package/src/ai/index.js +47 -0
- package/src/ai/prompts/baseline/_meta.json +14 -0
- package/src/ai/prompts/baseline/level1/balanced.js +239 -0
- package/src/ai/prompts/baseline/level1/fast.js +194 -0
- package/src/ai/prompts/baseline/level1/thorough.js +319 -0
- package/src/ai/prompts/baseline/level2/balanced.js +248 -0
- package/src/ai/prompts/baseline/level2/fast.js +201 -0
- package/src/ai/prompts/baseline/level2/thorough.js +367 -0
- package/src/ai/prompts/baseline/level3/balanced.js +280 -0
- package/src/ai/prompts/baseline/level3/fast.js +220 -0
- package/src/ai/prompts/baseline/level3/thorough.js +459 -0
- package/src/ai/prompts/baseline/orchestration/balanced.js +259 -0
- package/src/ai/prompts/baseline/orchestration/fast.js +213 -0
- package/src/ai/prompts/baseline/orchestration/thorough.js +446 -0
- package/src/ai/prompts/config.js +52 -0
- package/src/ai/prompts/index.js +267 -0
- package/src/ai/prompts/shared/diff-instructions.js +50 -0
- package/src/ai/prompts/shared/output-schema.js +179 -0
- package/src/ai/prompts/shared/valid-files.js +37 -0
- package/src/ai/provider.js +260 -0
- package/src/config.js +139 -0
- package/src/database.js +2284 -0
- package/src/git/gitattributes.js +207 -0
- package/src/git/worktree.js +688 -0
- package/src/github/client.js +893 -0
- package/src/github/parser.js +247 -0
- package/src/local-review.js +691 -0
- package/src/main.js +987 -0
- package/src/routes/analysis.js +897 -0
- package/src/routes/comments.js +534 -0
- package/src/routes/config.js +250 -0
- package/src/routes/local.js +1728 -0
- package/src/routes/pr.js +1164 -0
- package/src/routes/shared.js +218 -0
- package/src/routes/worktrees.js +500 -0
- package/src/server.js +295 -0
- package/src/utils/diff-annotator.js +414 -0
- package/src/utils/instructions.js +33 -0
- package/src/utils/json-extractor.js +107 -0
- package/src/utils/line-validation.js +183 -0
- package/src/utils/logger.js +142 -0
- package/src/utils/paths.js +161 -0
- package/src/utils/stats-calculator.js +86 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
/**
|
|
3
|
+
* AI Analysis Progress Modal Component
|
|
4
|
+
* Displays three-level progress structure and handles background execution
|
|
5
|
+
*/
|
|
6
|
+
class ProgressModal {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.modal = null;
|
|
9
|
+
this.isVisible = false;
|
|
10
|
+
this.currentAnalysisId = null;
|
|
11
|
+
this.eventSource = null;
|
|
12
|
+
this.statusCheckInterval = null;
|
|
13
|
+
this.isRunningInBackground = false;
|
|
14
|
+
|
|
15
|
+
this.createModal();
|
|
16
|
+
this.setupEventListeners();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create the modal DOM structure
|
|
21
|
+
*/
|
|
22
|
+
createModal() {
|
|
23
|
+
// Remove existing modal if it exists
|
|
24
|
+
const existing = document.getElementById('progress-modal');
|
|
25
|
+
if (existing) {
|
|
26
|
+
existing.remove();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Create modal container
|
|
30
|
+
const modalContainer = document.createElement('div');
|
|
31
|
+
modalContainer.id = 'progress-modal';
|
|
32
|
+
modalContainer.className = 'modal-overlay';
|
|
33
|
+
modalContainer.style.display = 'none';
|
|
34
|
+
|
|
35
|
+
modalContainer.innerHTML = `
|
|
36
|
+
<div class="modal-backdrop" onclick="progressModal.hide()"></div>
|
|
37
|
+
<div class="modal-container">
|
|
38
|
+
<div class="modal-header">
|
|
39
|
+
<h3>AI Review Analysis</h3>
|
|
40
|
+
<button class="modal-close-btn" onclick="progressModal.hide()" title="Close">
|
|
41
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
42
|
+
<path d="M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z"/>
|
|
43
|
+
</svg>
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="modal-body">
|
|
48
|
+
<div class="progress-levels">
|
|
49
|
+
<div class="progress-level" id="level-1">
|
|
50
|
+
<div class="level-icon">
|
|
51
|
+
<span class="icon pending">○</span>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="level-content">
|
|
54
|
+
<div class="level-title">Level 1: Analyzing diff</div>
|
|
55
|
+
<div class="level-status">Preparing to start...</div>
|
|
56
|
+
<div class="progress-bar-container" style="display: none;">
|
|
57
|
+
<div class="barbershop-progress-bar">
|
|
58
|
+
<div class="barbershop-stripes"></div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div class="progress-level" id="level-2">
|
|
65
|
+
<div class="level-icon">
|
|
66
|
+
<span class="icon pending">○</span>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="level-content">
|
|
69
|
+
<div class="level-title">Level 2: File context</div>
|
|
70
|
+
<div class="level-status">Pending</div>
|
|
71
|
+
<div class="progress-bar-container" style="display: none;">
|
|
72
|
+
<div class="barbershop-progress-bar">
|
|
73
|
+
<div class="barbershop-stripes"></div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="progress-level" id="level-3">
|
|
80
|
+
<div class="level-icon">
|
|
81
|
+
<span class="icon pending">○</span>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="level-content">
|
|
84
|
+
<div class="level-title">Level 3: Codebase context</div>
|
|
85
|
+
<div class="level-status">Pending</div>
|
|
86
|
+
<div class="progress-bar-container" style="display: none;">
|
|
87
|
+
<div class="barbershop-progress-bar">
|
|
88
|
+
<div class="barbershop-stripes"></div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div class="progress-level" id="level-4">
|
|
95
|
+
<div class="level-icon">
|
|
96
|
+
<span class="icon pending">○</span>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="level-content">
|
|
99
|
+
<div class="level-title">Finalizing Results</div>
|
|
100
|
+
<div class="level-status">Pending</div>
|
|
101
|
+
<div class="progress-bar-container" style="display: none;">
|
|
102
|
+
<div class="barbershop-progress-bar">
|
|
103
|
+
<div class="barbershop-stripes"></div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div class="modal-footer">
|
|
112
|
+
<button class="btn btn-secondary" id="run-background-btn" onclick="progressModal.runInBackground()">
|
|
113
|
+
Run in Background
|
|
114
|
+
</button>
|
|
115
|
+
<button class="btn btn-danger" id="cancel-btn" onclick="progressModal.cancel()">
|
|
116
|
+
Cancel
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
document.body.appendChild(modalContainer);
|
|
123
|
+
this.modal = modalContainer;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Setup event listeners
|
|
128
|
+
*/
|
|
129
|
+
setupEventListeners() {
|
|
130
|
+
// Close modal on Escape key
|
|
131
|
+
document.addEventListener('keydown', (e) => {
|
|
132
|
+
if (e.key === 'Escape' && this.isVisible) {
|
|
133
|
+
this.hide();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Show the modal
|
|
140
|
+
* @param {string} analysisId - Analysis ID to track
|
|
141
|
+
*/
|
|
142
|
+
show(analysisId) {
|
|
143
|
+
this.currentAnalysisId = analysisId;
|
|
144
|
+
this.isVisible = true;
|
|
145
|
+
this.modal.style.display = 'flex';
|
|
146
|
+
|
|
147
|
+
// Reset progress state
|
|
148
|
+
this.resetProgress();
|
|
149
|
+
|
|
150
|
+
// Start monitoring progress
|
|
151
|
+
this.startProgressMonitoring();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Hide the modal
|
|
156
|
+
*/
|
|
157
|
+
hide() {
|
|
158
|
+
this.isVisible = false;
|
|
159
|
+
this.modal.style.display = 'none';
|
|
160
|
+
|
|
161
|
+
// Don't stop monitoring if running in background
|
|
162
|
+
if (!this.isRunningInBackground) {
|
|
163
|
+
this.stopProgressMonitoring();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Run analysis in background
|
|
169
|
+
*/
|
|
170
|
+
runInBackground() {
|
|
171
|
+
this.isRunningInBackground = true;
|
|
172
|
+
this.hide();
|
|
173
|
+
|
|
174
|
+
// Button already shows analyzing state, no need for separate status indicator
|
|
175
|
+
// The button was set to analyzing state when analysis started
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Cancel the analysis
|
|
180
|
+
*/
|
|
181
|
+
async cancel() {
|
|
182
|
+
if (!this.currentAnalysisId) {
|
|
183
|
+
this.hide();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
// Make cancel request to backend
|
|
189
|
+
const response = await fetch(`/api/analyze/cancel/${this.currentAnalysisId}`, {
|
|
190
|
+
method: 'POST'
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (response.ok) {
|
|
194
|
+
this.updateStatus('Analysis cancelled');
|
|
195
|
+
}
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.warn('Cancel not available on server:', error.message);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
this.stopProgressMonitoring();
|
|
201
|
+
this.hide();
|
|
202
|
+
|
|
203
|
+
// Reset button
|
|
204
|
+
if (window.prManager) {
|
|
205
|
+
window.prManager.resetButton();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Reset AI panel to non-loading state
|
|
209
|
+
if (window.aiPanel?.setAnalysisState) {
|
|
210
|
+
window.aiPanel.setAnalysisState('unknown');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Reset progress to initial state
|
|
216
|
+
*/
|
|
217
|
+
resetProgress() {
|
|
218
|
+
// Reset levels 1-3 to running state
|
|
219
|
+
for (let i = 1; i <= 3; i++) {
|
|
220
|
+
const level = document.getElementById(`level-${i}`);
|
|
221
|
+
if (level) {
|
|
222
|
+
const icon = level.querySelector('.icon');
|
|
223
|
+
const status = level.querySelector('.level-status');
|
|
224
|
+
const progressContainer = level.querySelector('.progress-bar-container');
|
|
225
|
+
|
|
226
|
+
icon.className = 'icon active';
|
|
227
|
+
icon.textContent = '▶';
|
|
228
|
+
status.textContent = 'Starting...';
|
|
229
|
+
status.style.display = 'none';
|
|
230
|
+
|
|
231
|
+
// Show progress bar immediately for levels 1-3
|
|
232
|
+
if (progressContainer) {
|
|
233
|
+
progressContainer.style.display = 'block';
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Level 4 (orchestration) starts as pending
|
|
239
|
+
const level4 = document.getElementById('level-4');
|
|
240
|
+
if (level4) {
|
|
241
|
+
const icon = level4.querySelector('.icon');
|
|
242
|
+
const status = level4.querySelector('.level-status');
|
|
243
|
+
const progressContainer = level4.querySelector('.progress-bar-container');
|
|
244
|
+
|
|
245
|
+
icon.className = 'icon pending';
|
|
246
|
+
icon.textContent = '○';
|
|
247
|
+
status.textContent = 'Pending';
|
|
248
|
+
status.style.display = 'block';
|
|
249
|
+
|
|
250
|
+
if (progressContainer) {
|
|
251
|
+
progressContainer.style.display = 'none';
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Reset footer buttons to initial state
|
|
256
|
+
const runBackgroundBtn = document.getElementById('run-background-btn');
|
|
257
|
+
const cancelBtn = document.getElementById('cancel-btn');
|
|
258
|
+
|
|
259
|
+
if (runBackgroundBtn) {
|
|
260
|
+
runBackgroundBtn.textContent = 'Run in Background';
|
|
261
|
+
runBackgroundBtn.disabled = false;
|
|
262
|
+
}
|
|
263
|
+
if (cancelBtn) {
|
|
264
|
+
cancelBtn.textContent = 'Cancel';
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Reset background running state
|
|
268
|
+
this.isRunningInBackground = false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Start monitoring progress via Server-Sent Events (SSE)
|
|
273
|
+
*/
|
|
274
|
+
startProgressMonitoring() {
|
|
275
|
+
if (this.eventSource) {
|
|
276
|
+
this.eventSource.close();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!this.currentAnalysisId) return;
|
|
280
|
+
|
|
281
|
+
// Connect to SSE endpoint
|
|
282
|
+
this.eventSource = new EventSource(`/api/pr/${this.currentAnalysisId}/ai-suggestions/status`);
|
|
283
|
+
|
|
284
|
+
this.eventSource.onopen = () => {
|
|
285
|
+
console.log('Connected to progress stream');
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
this.eventSource.onmessage = (event) => {
|
|
289
|
+
try {
|
|
290
|
+
const data = JSON.parse(event.data);
|
|
291
|
+
|
|
292
|
+
if (data.type === 'connected') {
|
|
293
|
+
console.log('SSE connection established');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (data.type === 'progress') {
|
|
298
|
+
this.updateProgress(data);
|
|
299
|
+
|
|
300
|
+
// Stop monitoring if analysis is complete, failed, or cancelled
|
|
301
|
+
if (data.status === 'completed' || data.status === 'failed' || data.status === 'cancelled') {
|
|
302
|
+
this.stopProgressMonitoring();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.error('Error parsing SSE data:', error);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
this.eventSource.onerror = (error) => {
|
|
311
|
+
console.error('SSE connection error:', error);
|
|
312
|
+
// Fallback to polling if SSE fails
|
|
313
|
+
this.fallbackToPolling();
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Fallback to polling if SSE fails
|
|
319
|
+
*/
|
|
320
|
+
fallbackToPolling() {
|
|
321
|
+
if (this.eventSource) {
|
|
322
|
+
this.eventSource.close();
|
|
323
|
+
this.eventSource = null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (this.statusCheckInterval) {
|
|
327
|
+
clearInterval(this.statusCheckInterval);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
this.statusCheckInterval = setInterval(async () => {
|
|
331
|
+
if (!this.currentAnalysisId) return;
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
const response = await fetch(`/api/analyze/status/${this.currentAnalysisId}`);
|
|
335
|
+
if (!response.ok) {
|
|
336
|
+
throw new Error('Failed to fetch status');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const status = await response.json();
|
|
340
|
+
this.updateProgress(status);
|
|
341
|
+
|
|
342
|
+
// Stop monitoring if analysis is complete, failed, or cancelled
|
|
343
|
+
if (status.status === 'completed' || status.status === 'failed' || status.status === 'cancelled') {
|
|
344
|
+
this.stopProgressMonitoring();
|
|
345
|
+
}
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.error('Error checking analysis status:', error);
|
|
348
|
+
}
|
|
349
|
+
}, 1000); // Check every second
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Stop progress monitoring
|
|
354
|
+
*/
|
|
355
|
+
stopProgressMonitoring() {
|
|
356
|
+
if (this.statusCheckInterval) {
|
|
357
|
+
clearInterval(this.statusCheckInterval);
|
|
358
|
+
this.statusCheckInterval = null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (this.eventSource) {
|
|
362
|
+
this.eventSource.close();
|
|
363
|
+
this.eventSource = null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Update progress based on status
|
|
369
|
+
* @param {Object} status - Status object from server
|
|
370
|
+
*/
|
|
371
|
+
updateProgress(status) {
|
|
372
|
+
// Validate status structure before accessing properties
|
|
373
|
+
if (!status.levels || typeof status.levels !== 'object') {
|
|
374
|
+
console.warn('Invalid status structure - missing or malformed levels object:', status);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Update each level's progress independently from the levels object
|
|
379
|
+
for (let level = 1; level <= 4; level++) {
|
|
380
|
+
const levelStatus = status.levels[level];
|
|
381
|
+
if (levelStatus) {
|
|
382
|
+
this.updateLevelProgress(level, levelStatus);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Update overall progress message
|
|
387
|
+
this.updateStatus(status.progress || 'Running...');
|
|
388
|
+
|
|
389
|
+
// Handle completion, failure, or cancellation
|
|
390
|
+
if (status.status === 'completed') {
|
|
391
|
+
this.handleCompletion(status);
|
|
392
|
+
} else if (status.status === 'failed') {
|
|
393
|
+
this.handleFailure(status);
|
|
394
|
+
} else if (status.status === 'cancelled') {
|
|
395
|
+
this.handleCancellation(status);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Mark a level as completed
|
|
401
|
+
* @param {number} level - Level number to mark as completed
|
|
402
|
+
*/
|
|
403
|
+
markLevelAsCompleted(level) {
|
|
404
|
+
const levelElement = document.getElementById(`level-${level}`);
|
|
405
|
+
if (!levelElement) return;
|
|
406
|
+
|
|
407
|
+
const icon = levelElement.querySelector('.icon');
|
|
408
|
+
const statusText = levelElement.querySelector('.level-status');
|
|
409
|
+
const progressContainer = levelElement.querySelector('.progress-bar-container');
|
|
410
|
+
|
|
411
|
+
icon.className = 'icon completed';
|
|
412
|
+
icon.textContent = '✓';
|
|
413
|
+
statusText.textContent = 'Completed';
|
|
414
|
+
statusText.style.display = 'block';
|
|
415
|
+
|
|
416
|
+
// Hide progress bar for completed levels
|
|
417
|
+
if (progressContainer) {
|
|
418
|
+
progressContainer.style.display = 'none';
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Update a specific level's progress
|
|
424
|
+
* @param {number} level - Level number (1, 2, or 3)
|
|
425
|
+
* @param {Object} levelStatus - Level status object with { status, progress }
|
|
426
|
+
*/
|
|
427
|
+
updateLevelProgress(level, levelStatus) {
|
|
428
|
+
const levelElement = document.getElementById(`level-${level}`);
|
|
429
|
+
if (!levelElement) return;
|
|
430
|
+
|
|
431
|
+
const icon = levelElement.querySelector('.icon');
|
|
432
|
+
const statusText = levelElement.querySelector('.level-status');
|
|
433
|
+
const progressContainer = levelElement.querySelector('.progress-bar-container');
|
|
434
|
+
|
|
435
|
+
// Update icon and status based on current state
|
|
436
|
+
if (levelStatus.status === 'running') {
|
|
437
|
+
icon.className = 'icon active';
|
|
438
|
+
icon.textContent = '▶';
|
|
439
|
+
|
|
440
|
+
// Show progress bar and hide status text for running levels
|
|
441
|
+
statusText.style.display = 'none';
|
|
442
|
+
if (progressContainer) {
|
|
443
|
+
progressContainer.style.display = 'block';
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
} else if (levelStatus.status === 'completed') {
|
|
447
|
+
icon.className = 'icon completed';
|
|
448
|
+
icon.textContent = '✓';
|
|
449
|
+
statusText.textContent = 'Completed';
|
|
450
|
+
statusText.style.display = 'block';
|
|
451
|
+
|
|
452
|
+
// Hide progress bar for completed levels
|
|
453
|
+
if (progressContainer) {
|
|
454
|
+
progressContainer.style.display = 'none';
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
} else if (levelStatus.status === 'failed') {
|
|
458
|
+
icon.className = 'icon error';
|
|
459
|
+
icon.textContent = '❌';
|
|
460
|
+
statusText.textContent = 'Failed';
|
|
461
|
+
statusText.style.display = 'block';
|
|
462
|
+
|
|
463
|
+
// Hide progress bar for failed levels
|
|
464
|
+
if (progressContainer) {
|
|
465
|
+
progressContainer.style.display = 'none';
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
} else if (levelStatus.status === 'cancelled') {
|
|
469
|
+
icon.className = 'icon cancelled';
|
|
470
|
+
icon.textContent = '⊘';
|
|
471
|
+
statusText.textContent = 'Cancelled';
|
|
472
|
+
statusText.style.display = 'block';
|
|
473
|
+
|
|
474
|
+
// Hide progress bar for cancelled levels
|
|
475
|
+
if (progressContainer) {
|
|
476
|
+
progressContainer.style.display = 'none';
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
} else {
|
|
480
|
+
// For pending or other states
|
|
481
|
+
console.warn('Unexpected level status:', levelStatus.status, 'for level', level);
|
|
482
|
+
icon.className = 'icon pending';
|
|
483
|
+
icon.textContent = '○';
|
|
484
|
+
statusText.textContent = levelStatus.progress || 'Pending';
|
|
485
|
+
statusText.style.display = 'block';
|
|
486
|
+
|
|
487
|
+
if (progressContainer) {
|
|
488
|
+
progressContainer.style.display = 'none';
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Update toolbar progress dots (check both PR and local managers)
|
|
493
|
+
const manager = window.prManager || window.localManager;
|
|
494
|
+
if (manager?.updateProgressDot) {
|
|
495
|
+
manager.updateProgressDot(level, levelStatus.status);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Update general status message
|
|
501
|
+
* @param {string} message - Status message
|
|
502
|
+
*/
|
|
503
|
+
updateStatus(message) {
|
|
504
|
+
// Could add a general status area if needed
|
|
505
|
+
console.log('Progress:', message);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Handle analysis completion
|
|
510
|
+
* @param {Object} status - Final status object
|
|
511
|
+
*/
|
|
512
|
+
handleCompletion(status) {
|
|
513
|
+
// Levels are already marked as completed by updateProgress
|
|
514
|
+
// Just update the UI buttons
|
|
515
|
+
|
|
516
|
+
const completedLevel = status.completedLevel || status.level || 3;
|
|
517
|
+
|
|
518
|
+
// Update button to show completion
|
|
519
|
+
const runBackgroundBtn = document.getElementById('run-background-btn');
|
|
520
|
+
const cancelBtn = document.getElementById('cancel-btn');
|
|
521
|
+
|
|
522
|
+
if (runBackgroundBtn) {
|
|
523
|
+
runBackgroundBtn.textContent = `Analysis Complete`;
|
|
524
|
+
runBackgroundBtn.disabled = true;
|
|
525
|
+
}
|
|
526
|
+
if (cancelBtn) {
|
|
527
|
+
cancelBtn.textContent = 'Close';
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Update button to show completion
|
|
531
|
+
if (window.prManager) {
|
|
532
|
+
window.prManager.setButtonComplete();
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// CRITICAL FIX: Automatically reload AI suggestions when analysis completes
|
|
536
|
+
console.log('Analysis completed, reloading AI suggestions...');
|
|
537
|
+
|
|
538
|
+
// Support both PR mode (prManager) and Local mode (localManager)
|
|
539
|
+
const manager = window.prManager || window.localManager;
|
|
540
|
+
|
|
541
|
+
if (manager && typeof manager.loadAISuggestions === 'function') {
|
|
542
|
+
// Determine whether to switch to the new run:
|
|
543
|
+
// - If modal is visible, user was waiting for results -> switch immediately
|
|
544
|
+
// - If modal is hidden (running in background), user was viewing older results -> don't switch
|
|
545
|
+
const shouldSwitchToNew = this.isVisible;
|
|
546
|
+
|
|
547
|
+
// First, refresh the analysis history manager to include the new run
|
|
548
|
+
const refreshHistory = async () => {
|
|
549
|
+
if (manager.analysisHistoryManager) {
|
|
550
|
+
console.log('Refreshing analysis history, switchToNew:', shouldSwitchToNew);
|
|
551
|
+
const result = await manager.analysisHistoryManager.refresh({ switchToNew: shouldSwitchToNew });
|
|
552
|
+
// Return whether the manager actually switched to the new run
|
|
553
|
+
// This can differ from shouldSwitchToNew when it's the first-ever run
|
|
554
|
+
// (first run always switches regardless of shouldSwitchToNew)
|
|
555
|
+
return result.didSwitch;
|
|
556
|
+
}
|
|
557
|
+
// No history manager, so we'll load suggestions directly
|
|
558
|
+
return true;
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
refreshHistory()
|
|
562
|
+
.then((didSwitch) => {
|
|
563
|
+
// Load suggestions if we switched to the new run
|
|
564
|
+
// Note: didSwitch may be true even if shouldSwitchToNew was false
|
|
565
|
+
// (e.g., first-ever analysis run always switches because there's no previous selection)
|
|
566
|
+
if (didSwitch) {
|
|
567
|
+
return manager.loadAISuggestions();
|
|
568
|
+
}
|
|
569
|
+
// Otherwise, just return - the user will load when they select the new run
|
|
570
|
+
console.log('New analysis available - user will see indicator on dropdown');
|
|
571
|
+
return Promise.resolve();
|
|
572
|
+
})
|
|
573
|
+
.then(() => {
|
|
574
|
+
console.log('AI suggestions reloaded successfully');
|
|
575
|
+
// Only auto-close after suggestions have loaded successfully
|
|
576
|
+
if (this.isVisible) {
|
|
577
|
+
setTimeout(() => {
|
|
578
|
+
this.hide();
|
|
579
|
+
}, 2000); // Reduced to 2 seconds since loading is complete
|
|
580
|
+
}
|
|
581
|
+
})
|
|
582
|
+
.catch(error => {
|
|
583
|
+
console.error('Error reloading AI suggestions:', error);
|
|
584
|
+
// Still auto-close even if loading failed, but give more time for user to see error
|
|
585
|
+
if (this.isVisible) {
|
|
586
|
+
setTimeout(() => {
|
|
587
|
+
this.hide();
|
|
588
|
+
}, 5000);
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
} else {
|
|
592
|
+
console.warn('Manager not available for automatic suggestion reload');
|
|
593
|
+
// Auto-close after 3 seconds if no manager available
|
|
594
|
+
if (this.isVisible) {
|
|
595
|
+
setTimeout(() => {
|
|
596
|
+
this.hide();
|
|
597
|
+
}, 3000);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Handle analysis failure
|
|
604
|
+
* @param {Object} status - Error status object
|
|
605
|
+
*/
|
|
606
|
+
handleFailure(status) {
|
|
607
|
+
// Levels are already marked as failed by updateProgress
|
|
608
|
+
// Just update the UI buttons
|
|
609
|
+
|
|
610
|
+
// Update buttons
|
|
611
|
+
const runBackgroundBtn = document.getElementById('run-background-btn');
|
|
612
|
+
const cancelBtn = document.getElementById('cancel-btn');
|
|
613
|
+
|
|
614
|
+
if (runBackgroundBtn) {
|
|
615
|
+
runBackgroundBtn.textContent = 'Analysis Failed';
|
|
616
|
+
runBackgroundBtn.disabled = true;
|
|
617
|
+
}
|
|
618
|
+
if (cancelBtn) {
|
|
619
|
+
cancelBtn.textContent = 'Close';
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Reset button on failure
|
|
623
|
+
if (window.prManager) {
|
|
624
|
+
window.prManager.resetButton();
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Handle analysis cancellation (via SSE status)
|
|
630
|
+
* @param {Object} status - Cancellation status object
|
|
631
|
+
*/
|
|
632
|
+
handleCancellation(status) {
|
|
633
|
+
// Update buttons to show cancelled state
|
|
634
|
+
const runBackgroundBtn = document.getElementById('run-background-btn');
|
|
635
|
+
const cancelBtn = document.getElementById('cancel-btn');
|
|
636
|
+
|
|
637
|
+
if (runBackgroundBtn) {
|
|
638
|
+
runBackgroundBtn.textContent = 'Analysis Cancelled';
|
|
639
|
+
runBackgroundBtn.disabled = true;
|
|
640
|
+
}
|
|
641
|
+
if (cancelBtn) {
|
|
642
|
+
cancelBtn.textContent = 'Close';
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Reset the analyze button and AI panel state
|
|
646
|
+
if (window.prManager) {
|
|
647
|
+
window.prManager.resetButton();
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Reset AI panel to non-loading state
|
|
651
|
+
if (window.aiPanel?.setAnalysisState) {
|
|
652
|
+
window.aiPanel.setAnalysisState('unknown');
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Hide modal after a brief delay
|
|
656
|
+
if (this.isVisible) {
|
|
657
|
+
setTimeout(() => {
|
|
658
|
+
this.hide();
|
|
659
|
+
}, 1500);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Reopen modal from background
|
|
665
|
+
*/
|
|
666
|
+
reopenFromBackground() {
|
|
667
|
+
this.isRunningInBackground = false;
|
|
668
|
+
this.show(this.currentAnalysisId);
|
|
669
|
+
|
|
670
|
+
// Hide status indicator
|
|
671
|
+
if (window.statusIndicator) {
|
|
672
|
+
window.statusIndicator.hide();
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Initialize global instance
|
|
678
|
+
window.progressModal = new ProgressModal();
|