ai-progress-controls 0.1.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 +21 -0
- package/README.md +823 -0
- package/dist/ai-progress-controls.es.js +7191 -0
- package/dist/ai-progress-controls.es.js.map +1 -0
- package/dist/ai-progress-controls.umd.js +2 -0
- package/dist/ai-progress-controls.umd.js.map +1 -0
- package/dist/index.d.ts +2212 -0
- package/package.json +105 -0
- package/src/__tests__/setup.ts +93 -0
- package/src/core/base/AIControl.ts +230 -0
- package/src/core/base/index.ts +3 -0
- package/src/core/base/types.ts +77 -0
- package/src/core/base/utils.ts +168 -0
- package/src/core/batch-progress/BatchProgress.test.ts +458 -0
- package/src/core/batch-progress/BatchProgress.ts +760 -0
- package/src/core/batch-progress/index.ts +14 -0
- package/src/core/batch-progress/styles.ts +480 -0
- package/src/core/batch-progress/types.ts +169 -0
- package/src/core/model-loader/ModelLoader.test.ts +311 -0
- package/src/core/model-loader/ModelLoader.ts +673 -0
- package/src/core/model-loader/index.ts +2 -0
- package/src/core/model-loader/styles.ts +496 -0
- package/src/core/model-loader/types.ts +127 -0
- package/src/core/parameter-panel/ParameterPanel.test.ts +856 -0
- package/src/core/parameter-panel/ParameterPanel.ts +877 -0
- package/src/core/parameter-panel/index.ts +14 -0
- package/src/core/parameter-panel/styles.ts +323 -0
- package/src/core/parameter-panel/types.ts +278 -0
- package/src/core/parameter-slider/ParameterSlider.test.ts +299 -0
- package/src/core/parameter-slider/ParameterSlider.ts +653 -0
- package/src/core/parameter-slider/index.ts +8 -0
- package/src/core/parameter-slider/styles.ts +493 -0
- package/src/core/parameter-slider/types.ts +107 -0
- package/src/core/queue-progress/QueueProgress.test.ts +344 -0
- package/src/core/queue-progress/QueueProgress.ts +563 -0
- package/src/core/queue-progress/index.ts +5 -0
- package/src/core/queue-progress/styles.ts +469 -0
- package/src/core/queue-progress/types.ts +130 -0
- package/src/core/retry-progress/RetryProgress.test.ts +397 -0
- package/src/core/retry-progress/RetryProgress.ts +957 -0
- package/src/core/retry-progress/index.ts +6 -0
- package/src/core/retry-progress/styles.ts +530 -0
- package/src/core/retry-progress/types.ts +176 -0
- package/src/core/stream-progress/StreamProgress.test.ts +531 -0
- package/src/core/stream-progress/StreamProgress.ts +517 -0
- package/src/core/stream-progress/index.ts +2 -0
- package/src/core/stream-progress/styles.ts +349 -0
- package/src/core/stream-progress/types.ts +82 -0
- package/src/index.ts +19 -0
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
import { AIControl } from '../base/AIControl';
|
|
2
|
+
import type {
|
|
3
|
+
BatchProgressConfig,
|
|
4
|
+
BatchProgressState,
|
|
5
|
+
BatchProgressUpdate,
|
|
6
|
+
BatchItem,
|
|
7
|
+
BatchItemStatus,
|
|
8
|
+
BatchStartEvent,
|
|
9
|
+
BatchItemUpdateEvent,
|
|
10
|
+
BatchCompleteEvent,
|
|
11
|
+
BatchCancelEvent,
|
|
12
|
+
BatchItemCompleteEvent,
|
|
13
|
+
BatchItemFailedEvent,
|
|
14
|
+
} from './types';
|
|
15
|
+
import { styles } from './styles';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* BatchProgress Component
|
|
19
|
+
*
|
|
20
|
+
* Displays progress for batch operations processing multiple items.
|
|
21
|
+
* Perfect for processing multiple AI requests, documents, or images in parallel.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* // Create batch progress
|
|
26
|
+
* const batch = new BatchProgress({
|
|
27
|
+
* totalItems: 50,
|
|
28
|
+
* concurrency: 5,
|
|
29
|
+
* showItems: true,
|
|
30
|
+
* showStats: true
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* document.body.appendChild(batch);
|
|
34
|
+
*
|
|
35
|
+
* // Start batch
|
|
36
|
+
* batch.start();
|
|
37
|
+
*
|
|
38
|
+
* // Add items
|
|
39
|
+
* for (let i = 0; i < 50; i++) {
|
|
40
|
+
* batch.addItem(`item-${i}`, `Process item ${i}`);
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* // Update item progress
|
|
44
|
+
* batch.updateItem({
|
|
45
|
+
* itemId: 'item-0',
|
|
46
|
+
* status: 'processing',
|
|
47
|
+
* progress: 50
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* // Complete item
|
|
51
|
+
* batch.completeItem('item-0', { result: 'success' });
|
|
52
|
+
*
|
|
53
|
+
* // Listen to events
|
|
54
|
+
* batch.addEventListener('batchcomplete', (e) => {
|
|
55
|
+
* console.log(`Completed ${e.detail.successCount}/${e.detail.totalItems}`);
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @fires batchstart - Fired when batch processing starts
|
|
60
|
+
* @fires itemupdate - Fired when batch item is updated
|
|
61
|
+
* @fires itemcomplete - Fired when batch item completes
|
|
62
|
+
* @fires itemfailed - Fired when batch item fails
|
|
63
|
+
* @fires batchcomplete - Fired when all items are processed
|
|
64
|
+
* @fires batchcancel - Fired when batch is cancelled
|
|
65
|
+
*/
|
|
66
|
+
export class BatchProgress extends AIControl {
|
|
67
|
+
protected override config: Required<BatchProgressConfig>;
|
|
68
|
+
private readonly state: BatchProgressState;
|
|
69
|
+
private updateThrottleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
70
|
+
|
|
71
|
+
static get observedAttributes() {
|
|
72
|
+
return ['total-items', 'disabled', 'size', 'variant', 'animation'];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
constructor(config: BatchProgressConfig = {}) {
|
|
76
|
+
super({
|
|
77
|
+
debug: config.debug ?? false,
|
|
78
|
+
className: config.className,
|
|
79
|
+
ariaLabel: config.ariaLabel ?? 'Batch Progress',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
this.config = {
|
|
83
|
+
totalItems: config.totalItems ?? 0,
|
|
84
|
+
concurrency: config.concurrency ?? 5,
|
|
85
|
+
showItems: config.showItems ?? true,
|
|
86
|
+
maxDisplayItems: config.maxDisplayItems ?? 100,
|
|
87
|
+
showProgressBar: config.showProgressBar ?? true,
|
|
88
|
+
showStats: config.showStats ?? true,
|
|
89
|
+
showTime: config.showTime ?? true,
|
|
90
|
+
showRate: config.showRate ?? true,
|
|
91
|
+
allowCancel: config.allowCancel ?? true,
|
|
92
|
+
cancelLabel: config.cancelLabel ?? 'Cancel Batch',
|
|
93
|
+
collapseCompleted: config.collapseCompleted ?? false,
|
|
94
|
+
message: config.message ?? 'Processing batch...',
|
|
95
|
+
disabled: config.disabled ?? false,
|
|
96
|
+
debug: config.debug ?? false,
|
|
97
|
+
className: config.className ?? '',
|
|
98
|
+
ariaLabel: config.ariaLabel ?? 'Batch Progress',
|
|
99
|
+
cursorFeedback: config.cursorFeedback ?? true,
|
|
100
|
+
size: config.size ?? 'default',
|
|
101
|
+
variant: config.variant ?? 'default',
|
|
102
|
+
animation: config.animation ?? 'none',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
this.state = {
|
|
106
|
+
status: 'idle',
|
|
107
|
+
items: new Map(),
|
|
108
|
+
totalItems: this.config.totalItems,
|
|
109
|
+
completedCount: 0,
|
|
110
|
+
failedCount: 0,
|
|
111
|
+
successCount: 0,
|
|
112
|
+
currentConcurrency: 0,
|
|
113
|
+
startTime: null,
|
|
114
|
+
endTime: null,
|
|
115
|
+
message: this.config.message,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
this.attachShadow({ mode: 'open' });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
override connectedCallback(): void {
|
|
122
|
+
super.connectedCallback();
|
|
123
|
+
this.render();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
override attributeChangedCallback(
|
|
127
|
+
name: string,
|
|
128
|
+
oldValue: string | null,
|
|
129
|
+
newValue: string | null
|
|
130
|
+
): void {
|
|
131
|
+
if (oldValue === newValue) return;
|
|
132
|
+
|
|
133
|
+
switch (name) {
|
|
134
|
+
case 'total-items':
|
|
135
|
+
this.state.totalItems = Number.parseInt(newValue || '0', 10);
|
|
136
|
+
this.render();
|
|
137
|
+
break;
|
|
138
|
+
case 'disabled':
|
|
139
|
+
this._disabled = newValue !== null;
|
|
140
|
+
this.render();
|
|
141
|
+
break;
|
|
142
|
+
case 'size':
|
|
143
|
+
this.config.size = newValue as any;
|
|
144
|
+
this.render();
|
|
145
|
+
break;
|
|
146
|
+
case 'variant':
|
|
147
|
+
this.config.variant = newValue as any;
|
|
148
|
+
this.render();
|
|
149
|
+
break;
|
|
150
|
+
case 'animation':
|
|
151
|
+
this.config.animation = newValue as any;
|
|
152
|
+
this.render();
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Update cursor based on component state
|
|
159
|
+
*/
|
|
160
|
+
private updateCursor(): void {
|
|
161
|
+
if (!this.config.cursorFeedback) return;
|
|
162
|
+
|
|
163
|
+
if (this.state.status === 'processing') {
|
|
164
|
+
this.style.cursor = 'progress';
|
|
165
|
+
} else if (this.state.status === 'cancelled') {
|
|
166
|
+
this.style.cursor = 'not-allowed';
|
|
167
|
+
} else {
|
|
168
|
+
this.style.cursor = 'default';
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Start batch processing
|
|
174
|
+
*/
|
|
175
|
+
public start(message?: string): void {
|
|
176
|
+
if (this.config.disabled) return;
|
|
177
|
+
|
|
178
|
+
this.state.status = 'processing';
|
|
179
|
+
this.state.startTime = Date.now();
|
|
180
|
+
this.state.message = message || this.config.message;
|
|
181
|
+
this.state.completedCount = 0;
|
|
182
|
+
this.state.failedCount = 0;
|
|
183
|
+
this.state.successCount = 0;
|
|
184
|
+
|
|
185
|
+
this.render();
|
|
186
|
+
this.updateCursor();
|
|
187
|
+
|
|
188
|
+
const event: BatchStartEvent = {
|
|
189
|
+
totalItems: this.state.totalItems,
|
|
190
|
+
startTime: this.state.startTime,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
this.dispatchEvent(
|
|
194
|
+
new CustomEvent('batchstart', {
|
|
195
|
+
detail: event,
|
|
196
|
+
bubbles: true,
|
|
197
|
+
composed: true,
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
this.log('Batch started', event);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Add item to batch
|
|
206
|
+
*/
|
|
207
|
+
public addItem(itemId: string, label?: string): void {
|
|
208
|
+
const item: BatchItem = {
|
|
209
|
+
id: itemId,
|
|
210
|
+
label: label || itemId,
|
|
211
|
+
status: 'pending',
|
|
212
|
+
progress: 0,
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
this.state.items.set(itemId, item);
|
|
216
|
+
this.state.totalItems = this.state.items.size;
|
|
217
|
+
this.render();
|
|
218
|
+
|
|
219
|
+
this.log('Item added', item);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Update batch item
|
|
224
|
+
*/
|
|
225
|
+
public updateItem(update: BatchProgressUpdate): void {
|
|
226
|
+
const item = this.state.items.get(update.itemId);
|
|
227
|
+
if (!item) {
|
|
228
|
+
this.log(`Item not found: ${update.itemId}`);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (update.status) item.status = update.status;
|
|
233
|
+
if (update.progress !== undefined) item.progress = update.progress;
|
|
234
|
+
if (update.error) item.error = update.error;
|
|
235
|
+
if (update.result !== undefined) item.result = update.result;
|
|
236
|
+
if (update.label) item.label = update.label;
|
|
237
|
+
|
|
238
|
+
// Track status changes
|
|
239
|
+
if (update.status === 'completed') {
|
|
240
|
+
item.endTime = Date.now();
|
|
241
|
+
this.state.completedCount++;
|
|
242
|
+
if (!item.error) this.state.successCount++;
|
|
243
|
+
} else if (update.status === 'failed') {
|
|
244
|
+
item.endTime = Date.now();
|
|
245
|
+
this.state.completedCount++;
|
|
246
|
+
this.state.failedCount++;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this.throttledRender();
|
|
250
|
+
|
|
251
|
+
const overallProgress = this.getOverallProgress();
|
|
252
|
+
|
|
253
|
+
const event: BatchItemUpdateEvent = {
|
|
254
|
+
...item,
|
|
255
|
+
totalCompleted: this.state.completedCount,
|
|
256
|
+
totalFailed: this.state.failedCount,
|
|
257
|
+
overallProgress,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
this.dispatchEvent(
|
|
261
|
+
new CustomEvent('itemupdate', {
|
|
262
|
+
detail: event,
|
|
263
|
+
bubbles: true,
|
|
264
|
+
composed: true,
|
|
265
|
+
})
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// Check if batch is complete
|
|
269
|
+
if (this.state.completedCount === this.state.totalItems && this.state.status === 'processing') {
|
|
270
|
+
this.complete();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Complete a batch item
|
|
276
|
+
*/
|
|
277
|
+
public completeItem(itemId: string, result?: any): void {
|
|
278
|
+
this.updateItem({
|
|
279
|
+
itemId,
|
|
280
|
+
status: 'completed',
|
|
281
|
+
progress: 100,
|
|
282
|
+
result,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const item = this.state.items.get(itemId);
|
|
286
|
+
if (!item) return;
|
|
287
|
+
|
|
288
|
+
const event: BatchItemCompleteEvent = {
|
|
289
|
+
item,
|
|
290
|
+
totalCompleted: this.state.completedCount,
|
|
291
|
+
remainingItems: this.state.totalItems - this.state.completedCount,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
this.dispatchEvent(
|
|
295
|
+
new CustomEvent('itemcomplete', {
|
|
296
|
+
detail: event,
|
|
297
|
+
bubbles: true,
|
|
298
|
+
composed: true,
|
|
299
|
+
})
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Fail a batch item
|
|
305
|
+
*/
|
|
306
|
+
public failItem(itemId: string, error: string): void {
|
|
307
|
+
this.updateItem({
|
|
308
|
+
itemId,
|
|
309
|
+
status: 'failed',
|
|
310
|
+
error,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const item = this.state.items.get(itemId);
|
|
314
|
+
if (!item) return;
|
|
315
|
+
|
|
316
|
+
const event: BatchItemFailedEvent = {
|
|
317
|
+
item,
|
|
318
|
+
error,
|
|
319
|
+
totalFailed: this.state.failedCount,
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
this.dispatchEvent(
|
|
323
|
+
new CustomEvent('itemfailed', {
|
|
324
|
+
detail: event,
|
|
325
|
+
bubbles: true,
|
|
326
|
+
composed: true,
|
|
327
|
+
})
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Complete batch processing
|
|
333
|
+
*/
|
|
334
|
+
public complete(): void {
|
|
335
|
+
this.state.status = 'completed';
|
|
336
|
+
this.state.endTime = Date.now();
|
|
337
|
+
|
|
338
|
+
this.render();
|
|
339
|
+
this.updateCursor();
|
|
340
|
+
|
|
341
|
+
const duration = this.state.endTime - (this.state.startTime || 0);
|
|
342
|
+
const averageRate = this.state.totalItems / (duration / 1000);
|
|
343
|
+
|
|
344
|
+
const event: BatchCompleteEvent = {
|
|
345
|
+
totalItems: this.state.totalItems,
|
|
346
|
+
successCount: this.state.successCount,
|
|
347
|
+
failedCount: this.state.failedCount,
|
|
348
|
+
duration,
|
|
349
|
+
averageRate,
|
|
350
|
+
startTime: this.state.startTime!,
|
|
351
|
+
endTime: this.state.endTime,
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
this.dispatchEvent(
|
|
355
|
+
new CustomEvent('batchcomplete', {
|
|
356
|
+
detail: event,
|
|
357
|
+
bubbles: true,
|
|
358
|
+
composed: true,
|
|
359
|
+
})
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
this.log('Batch completed', event);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Cancel batch processing
|
|
367
|
+
*/
|
|
368
|
+
public cancel(reason?: string): void {
|
|
369
|
+
this.state.status = 'cancelled';
|
|
370
|
+
this.state.endTime = Date.now();
|
|
371
|
+
|
|
372
|
+
// Cancel all pending items
|
|
373
|
+
let cancelledCount = 0;
|
|
374
|
+
this.state.items.forEach((item) => {
|
|
375
|
+
if (item.status === 'pending' || item.status === 'processing') {
|
|
376
|
+
item.status = 'cancelled';
|
|
377
|
+
cancelledCount++;
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
this.render();
|
|
382
|
+
this.updateCursor();
|
|
383
|
+
|
|
384
|
+
const event: BatchCancelEvent = {
|
|
385
|
+
completedCount: this.state.completedCount,
|
|
386
|
+
failedCount: this.state.failedCount,
|
|
387
|
+
cancelledCount,
|
|
388
|
+
reason,
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
this.dispatchEvent(
|
|
392
|
+
new CustomEvent('batchcancel', {
|
|
393
|
+
detail: event,
|
|
394
|
+
bubbles: true,
|
|
395
|
+
composed: true,
|
|
396
|
+
})
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
this.log('Batch cancelled', event);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Reset batch to initial state
|
|
404
|
+
*/
|
|
405
|
+
public reset(): void {
|
|
406
|
+
this.state.status = 'idle';
|
|
407
|
+
this.state.items.clear();
|
|
408
|
+
this.state.totalItems = this.config.totalItems;
|
|
409
|
+
this.state.completedCount = 0;
|
|
410
|
+
this.state.failedCount = 0;
|
|
411
|
+
this.state.successCount = 0;
|
|
412
|
+
this.state.currentConcurrency = 0;
|
|
413
|
+
this.state.startTime = null;
|
|
414
|
+
this.state.endTime = null;
|
|
415
|
+
this.state.message = this.config.message;
|
|
416
|
+
|
|
417
|
+
this.render();
|
|
418
|
+
this.log('Batch reset');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Get overall progress percentage
|
|
423
|
+
*/
|
|
424
|
+
public getOverallProgress(): number {
|
|
425
|
+
if (this.state.totalItems === 0) return 0;
|
|
426
|
+
return (this.state.completedCount / this.state.totalItems) * 100;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Get processing rate (items/second)
|
|
431
|
+
*/
|
|
432
|
+
public getRate(): number {
|
|
433
|
+
if (!this.state.startTime || this.state.completedCount === 0) return 0;
|
|
434
|
+
const elapsed = (Date.now() - this.state.startTime) / 1000;
|
|
435
|
+
return this.state.completedCount / elapsed;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Get batch statistics
|
|
440
|
+
*/
|
|
441
|
+
public getStats() {
|
|
442
|
+
return {
|
|
443
|
+
total: this.state.totalItems,
|
|
444
|
+
completed: this.state.completedCount,
|
|
445
|
+
success: this.state.successCount,
|
|
446
|
+
failed: this.state.failedCount,
|
|
447
|
+
pending: this.state.totalItems - this.state.completedCount,
|
|
448
|
+
progress: this.getOverallProgress(),
|
|
449
|
+
rate: this.getRate(),
|
|
450
|
+
duration: this._calculateDuration(),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Throttled render to avoid excessive updates
|
|
456
|
+
*/
|
|
457
|
+
private throttledRender(): void {
|
|
458
|
+
if (this.updateThrottleTimer) return;
|
|
459
|
+
|
|
460
|
+
this.updateThrottleTimer = globalThis.setTimeout(() => {
|
|
461
|
+
this.render();
|
|
462
|
+
this.updateThrottleTimer = null;
|
|
463
|
+
}, 100);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Calculate duration based on state
|
|
468
|
+
*/
|
|
469
|
+
private _calculateDuration(): number {
|
|
470
|
+
if (this.state.endTime) {
|
|
471
|
+
return this.state.endTime - (this.state.startTime || 0);
|
|
472
|
+
}
|
|
473
|
+
if (this.state.startTime) {
|
|
474
|
+
return Date.now() - this.state.startTime;
|
|
475
|
+
}
|
|
476
|
+
return 0;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Get rate display HTML
|
|
481
|
+
*/
|
|
482
|
+
private _getRateDisplay(rate: number): string {
|
|
483
|
+
if (!this.config.showRate) {
|
|
484
|
+
return '';
|
|
485
|
+
}
|
|
486
|
+
return `
|
|
487
|
+
<div class="stat-item">
|
|
488
|
+
<span class="stat-label">Rate</span>
|
|
489
|
+
<span class="stat-value rate">${rate.toFixed(1)}/s</span>
|
|
490
|
+
</div>
|
|
491
|
+
`;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Sync config attributes to host element
|
|
496
|
+
*/
|
|
497
|
+
private _syncAttributes(): void {
|
|
498
|
+
if (this.config.size && this.getAttribute('size') !== this.config.size) {
|
|
499
|
+
this.setAttribute('size', this.config.size);
|
|
500
|
+
}
|
|
501
|
+
if (this.config.variant && this.getAttribute('variant') !== this.config.variant) {
|
|
502
|
+
this.setAttribute('variant', this.config.variant);
|
|
503
|
+
}
|
|
504
|
+
if (this.config.animation && this.getAttribute('animation') !== this.config.animation) {
|
|
505
|
+
this.setAttribute('animation', this.config.animation);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Get status badge CSS class
|
|
511
|
+
*/
|
|
512
|
+
private _getStatusBadgeClass(status: string): string {
|
|
513
|
+
switch (status) {
|
|
514
|
+
case 'processing':
|
|
515
|
+
return 'processing';
|
|
516
|
+
case 'completed':
|
|
517
|
+
return 'completed';
|
|
518
|
+
case 'cancelled':
|
|
519
|
+
return 'cancelled';
|
|
520
|
+
default:
|
|
521
|
+
return '';
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Get status display text
|
|
527
|
+
*/
|
|
528
|
+
private _getStatusText(status: string): string {
|
|
529
|
+
switch (status) {
|
|
530
|
+
case 'idle':
|
|
531
|
+
return 'Ready';
|
|
532
|
+
case 'processing':
|
|
533
|
+
return 'Processing';
|
|
534
|
+
case 'completed':
|
|
535
|
+
return 'Completed';
|
|
536
|
+
case 'cancelled':
|
|
537
|
+
return 'Cancelled';
|
|
538
|
+
default:
|
|
539
|
+
return '';
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Get stats section HTML
|
|
545
|
+
*/
|
|
546
|
+
private _getStatsHtml(stats: ReturnType<typeof this.getStats>, rate: number): string {
|
|
547
|
+
if (!this.config.showStats) {
|
|
548
|
+
return '';
|
|
549
|
+
}
|
|
550
|
+
return `
|
|
551
|
+
<div class="stats">
|
|
552
|
+
<div class="stat-item">
|
|
553
|
+
<span class="stat-label">Total</span>
|
|
554
|
+
<span class="stat-value">${stats.total}</span>
|
|
555
|
+
</div>
|
|
556
|
+
<div class="stat-item">
|
|
557
|
+
<span class="stat-label">Success</span>
|
|
558
|
+
<span class="stat-value success">${stats.success}</span>
|
|
559
|
+
</div>
|
|
560
|
+
<div class="stat-item">
|
|
561
|
+
<span class="stat-label">Failed</span>
|
|
562
|
+
<span class="stat-value error">${stats.failed}</span>
|
|
563
|
+
</div>
|
|
564
|
+
${this._getRateDisplay(rate)}
|
|
565
|
+
</div>
|
|
566
|
+
`;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Get progress bar HTML
|
|
571
|
+
*/
|
|
572
|
+
private _getProgressBarHtml(
|
|
573
|
+
overallProgress: number,
|
|
574
|
+
stats: ReturnType<typeof this.getStats>
|
|
575
|
+
): string {
|
|
576
|
+
if (!this.config.showProgressBar) {
|
|
577
|
+
return '';
|
|
578
|
+
}
|
|
579
|
+
return `
|
|
580
|
+
<div class="overall-progress">
|
|
581
|
+
<div class="progress-header">
|
|
582
|
+
<span>Overall Progress</span>
|
|
583
|
+
<span>${stats.completed} / ${stats.total} (${overallProgress.toFixed(0)}%)</span>
|
|
584
|
+
</div>
|
|
585
|
+
<div class="progress-bar">
|
|
586
|
+
<div class="progress-fill" style="width: ${overallProgress}%"></div>
|
|
587
|
+
</div>
|
|
588
|
+
</div>
|
|
589
|
+
`;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Get controls HTML
|
|
594
|
+
*/
|
|
595
|
+
private _getControlsHtml(): string {
|
|
596
|
+
if (!this.config.allowCancel || this.state.status !== 'processing') {
|
|
597
|
+
return '';
|
|
598
|
+
}
|
|
599
|
+
const disabledAttr = this.config.disabled ? 'disabled' : '';
|
|
600
|
+
return `
|
|
601
|
+
<div class="controls">
|
|
602
|
+
<button class="cancel-btn" id="cancel-btn" ${disabledAttr}>
|
|
603
|
+
${this.config.cancelLabel}
|
|
604
|
+
</button>
|
|
605
|
+
</div>
|
|
606
|
+
`;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Attach event listeners to rendered elements
|
|
611
|
+
*/
|
|
612
|
+
private _attachEventListeners(): void {
|
|
613
|
+
if (!this.config.allowCancel) return;
|
|
614
|
+
|
|
615
|
+
const cancelBtn = this.shadowRoot?.getElementById('cancel-btn');
|
|
616
|
+
if (cancelBtn) {
|
|
617
|
+
cancelBtn.addEventListener('click', () => this.cancel('User cancelled'));
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Render the component
|
|
623
|
+
*/
|
|
624
|
+
protected render(): void {
|
|
625
|
+
if (!this.shadowRoot) return;
|
|
626
|
+
|
|
627
|
+
this._syncAttributes();
|
|
628
|
+
|
|
629
|
+
const overallProgress = this.getOverallProgress();
|
|
630
|
+
const rate = this.getRate();
|
|
631
|
+
const stats = this.getStats();
|
|
632
|
+
const statusBadgeClass = this._getStatusBadgeClass(this.state.status);
|
|
633
|
+
const statusText = this._getStatusText(this.state.status);
|
|
634
|
+
|
|
635
|
+
const statsHtml = this._getStatsHtml(stats, rate);
|
|
636
|
+
const progressHtml = this._getProgressBarHtml(overallProgress, stats);
|
|
637
|
+
const itemsHtml = this.config.showItems ? this.renderItems() : '';
|
|
638
|
+
const controlsHtml = this._getControlsHtml();
|
|
639
|
+
|
|
640
|
+
this.shadowRoot.innerHTML = `
|
|
641
|
+
<style>${styles}</style>
|
|
642
|
+
<div class="container">
|
|
643
|
+
<div class="header">
|
|
644
|
+
<div class="status-message">${this.state.message}</div>
|
|
645
|
+
${statusBadgeClass ? `<span class="status-badge ${statusBadgeClass}">${statusText}</span>` : ''}
|
|
646
|
+
</div>
|
|
647
|
+
${statsHtml}
|
|
648
|
+
${progressHtml}
|
|
649
|
+
${itemsHtml}
|
|
650
|
+
${controlsHtml}
|
|
651
|
+
</div>
|
|
652
|
+
`;
|
|
653
|
+
|
|
654
|
+
this._attachEventListeners();
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Render batch items
|
|
659
|
+
*/
|
|
660
|
+
private renderItems(): string {
|
|
661
|
+
if (this.state.items.size === 0) {
|
|
662
|
+
return `
|
|
663
|
+
<div class="empty-state">
|
|
664
|
+
<div class="empty-state-icon">📦</div>
|
|
665
|
+
<div>No items in batch</div>
|
|
666
|
+
</div>
|
|
667
|
+
`;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const items = Array.from(this.state.items.values());
|
|
671
|
+
const displayItems = items.slice(0, this.config.maxDisplayItems);
|
|
672
|
+
|
|
673
|
+
const itemsHtml = displayItems
|
|
674
|
+
.map((item) => {
|
|
675
|
+
const statusIcon = this.getStatusIcon(item.status);
|
|
676
|
+
const collapsed =
|
|
677
|
+
this.config.collapseCompleted && item.status === 'completed' ? 'collapsed' : '';
|
|
678
|
+
|
|
679
|
+
const progressBarHtml =
|
|
680
|
+
item.status === 'processing' && item.progress !== undefined
|
|
681
|
+
? `
|
|
682
|
+
<div class="item-progress-bar">
|
|
683
|
+
<div class="item-progress-fill" style="width: ${item.progress}%"></div>
|
|
684
|
+
</div>
|
|
685
|
+
`
|
|
686
|
+
: '';
|
|
687
|
+
|
|
688
|
+
const errorHtml = item.error
|
|
689
|
+
? `
|
|
690
|
+
<div class="item-error">
|
|
691
|
+
${item.error}
|
|
692
|
+
</div>
|
|
693
|
+
`
|
|
694
|
+
: '';
|
|
695
|
+
|
|
696
|
+
return `
|
|
697
|
+
<div class="batch-item ${item.status} ${collapsed}">
|
|
698
|
+
<div class="item-header">
|
|
699
|
+
<span class="item-label">${item.label || item.id}</span>
|
|
700
|
+
<span class="item-status ${item.status}">
|
|
701
|
+
<span class="item-status-icon">${statusIcon}</span>
|
|
702
|
+
${item.status}
|
|
703
|
+
</span>
|
|
704
|
+
</div>
|
|
705
|
+
${progressBarHtml}
|
|
706
|
+
${errorHtml}
|
|
707
|
+
</div>
|
|
708
|
+
`;
|
|
709
|
+
})
|
|
710
|
+
.join('');
|
|
711
|
+
|
|
712
|
+
return `
|
|
713
|
+
<div class="items-container">
|
|
714
|
+
${itemsHtml}
|
|
715
|
+
</div>
|
|
716
|
+
`;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Get status icon
|
|
721
|
+
*/
|
|
722
|
+
private getStatusIcon(status: BatchItemStatus): string {
|
|
723
|
+
switch (status) {
|
|
724
|
+
case 'pending':
|
|
725
|
+
return '⏳';
|
|
726
|
+
case 'processing':
|
|
727
|
+
return '<span class="spinner"></span>';
|
|
728
|
+
case 'completed':
|
|
729
|
+
return '✅';
|
|
730
|
+
case 'failed':
|
|
731
|
+
return '❌';
|
|
732
|
+
case 'cancelled':
|
|
733
|
+
return '⛔';
|
|
734
|
+
default:
|
|
735
|
+
return '';
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Get/set disabled state
|
|
741
|
+
*/
|
|
742
|
+
override get disabled(): boolean {
|
|
743
|
+
return this.config.disabled;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
override set disabled(value: boolean) {
|
|
747
|
+
this.config.disabled = value;
|
|
748
|
+
if (value) {
|
|
749
|
+
this.setAttribute('disabled', '');
|
|
750
|
+
} else {
|
|
751
|
+
this.removeAttribute('disabled');
|
|
752
|
+
}
|
|
753
|
+
this.render();
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Define custom element
|
|
758
|
+
if (!customElements.get('batch-progress')) {
|
|
759
|
+
customElements.define('batch-progress', BatchProgress);
|
|
760
|
+
}
|