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