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,563 @@
|
|
|
1
|
+
import { AIControl } from '../base/AIControl';
|
|
2
|
+
import { throttle, formatTime } from '../base/utils';
|
|
3
|
+
import type {
|
|
4
|
+
QueueProgressConfig,
|
|
5
|
+
QueueProgressState,
|
|
6
|
+
QueueStatus,
|
|
7
|
+
QueueUpdate,
|
|
8
|
+
PositionChangeEvent,
|
|
9
|
+
QueueStartEvent,
|
|
10
|
+
QueueCompleteEvent,
|
|
11
|
+
QueueErrorEvent,
|
|
12
|
+
} from './types';
|
|
13
|
+
import { styles } from './styles';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* QueueProgress Component
|
|
17
|
+
*
|
|
18
|
+
* Displays queue position and estimated wait time for rate-limited AI APIs.
|
|
19
|
+
* Shows position updates, processing rate, and estimated wait time.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Create the component
|
|
24
|
+
* const queue = new QueueProgress({
|
|
25
|
+
* position: 47,
|
|
26
|
+
* queueSize: 120,
|
|
27
|
+
* estimatedWait: 180, // 3 minutes
|
|
28
|
+
* processingRate: 3, // 3 requests per second
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* document.body.appendChild(queue);
|
|
32
|
+
*
|
|
33
|
+
* // Start tracking
|
|
34
|
+
* queue.start();
|
|
35
|
+
*
|
|
36
|
+
* // Update position
|
|
37
|
+
* queue.update({
|
|
38
|
+
* position: 25,
|
|
39
|
+
* estimatedWait: 90
|
|
40
|
+
* });
|
|
41
|
+
*
|
|
42
|
+
* // Complete
|
|
43
|
+
* queue.complete();
|
|
44
|
+
*
|
|
45
|
+
* // Listen to events
|
|
46
|
+
* queue.addEventListener('positionchange', (e) => {
|
|
47
|
+
* console.log('Position changed', e.detail);
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @fires queuestart - Fired when queue tracking starts
|
|
52
|
+
* @fires positionchange - Fired when position updates
|
|
53
|
+
* @fires queuecomplete - Fired when processing begins
|
|
54
|
+
* @fires queueerror - Fired when an error occurs
|
|
55
|
+
*/
|
|
56
|
+
export class QueueProgress extends AIControl {
|
|
57
|
+
protected override config: Required<QueueProgressConfig>;
|
|
58
|
+
private state: QueueProgressState;
|
|
59
|
+
private readonly updateThrottled: (update: QueueUpdate) => void;
|
|
60
|
+
private timerInterval: ReturnType<typeof setInterval> | undefined;
|
|
61
|
+
private initialPosition: number = 0;
|
|
62
|
+
|
|
63
|
+
static get observedAttributes() {
|
|
64
|
+
return ['position', 'queue-size', 'disabled', 'size', 'variant', 'animation'];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
constructor(config: QueueProgressConfig = {}) {
|
|
68
|
+
super({
|
|
69
|
+
debug: config.debug ?? false,
|
|
70
|
+
className: config.className,
|
|
71
|
+
ariaLabel: config.ariaLabel ?? 'Queue Progress',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Set default configuration
|
|
75
|
+
this.config = {
|
|
76
|
+
position: config.position ?? 0,
|
|
77
|
+
queueSize: config.queueSize ?? 0,
|
|
78
|
+
estimatedWait: config.estimatedWait ?? 0,
|
|
79
|
+
processingRate: config.processingRate ?? 1,
|
|
80
|
+
showPosition: config.showPosition ?? true,
|
|
81
|
+
showWaitTime: config.showWaitTime ?? true,
|
|
82
|
+
showRate: config.showRate ?? true,
|
|
83
|
+
showQueueSize: config.showQueueSize ?? true,
|
|
84
|
+
showProgressBar: config.showProgressBar ?? true,
|
|
85
|
+
message: config.message ?? 'You are in the queue',
|
|
86
|
+
animate: config.animate ?? true,
|
|
87
|
+
updateThrottle: config.updateThrottle ?? 100,
|
|
88
|
+
cursorFeedback: config.cursorFeedback ?? true,
|
|
89
|
+
debug: config.debug ?? false,
|
|
90
|
+
className: config.className ?? '',
|
|
91
|
+
ariaLabel: config.ariaLabel ?? 'Queue Progress',
|
|
92
|
+
size: config.size ?? 'default',
|
|
93
|
+
variant: config.variant ?? 'default',
|
|
94
|
+
animation: config.animation ?? 'none',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Initialize state
|
|
98
|
+
this.state = {
|
|
99
|
+
status: 'waiting',
|
|
100
|
+
position: this.config.position,
|
|
101
|
+
queueSize: this.config.queueSize,
|
|
102
|
+
estimatedWait: this.config.estimatedWait,
|
|
103
|
+
processingRate: this.config.processingRate,
|
|
104
|
+
startTime: 0,
|
|
105
|
+
message: this.config.message,
|
|
106
|
+
elapsedTime: 0,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
this.initialPosition = this.config.position;
|
|
110
|
+
|
|
111
|
+
// Create throttled update function
|
|
112
|
+
this.updateThrottled = throttle(this._updateInternal.bind(this), this.config.updateThrottle);
|
|
113
|
+
|
|
114
|
+
// Attach shadow DOM
|
|
115
|
+
this.attachShadow({ mode: 'open' });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
override connectedCallback(): void {
|
|
119
|
+
super.connectedCallback();
|
|
120
|
+
this.log('QueueProgress mounted');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
override disconnectedCallback(): void {
|
|
124
|
+
this.stopTimer();
|
|
125
|
+
super.disconnectedCallback();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
protected override getDefaultRole(): string {
|
|
129
|
+
return 'status';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
protected override handleAttributeChange(
|
|
133
|
+
name: string,
|
|
134
|
+
_oldValue: string,
|
|
135
|
+
newValue: string
|
|
136
|
+
): void {
|
|
137
|
+
switch (name) {
|
|
138
|
+
case 'position':
|
|
139
|
+
this.update({ position: Number.parseInt(newValue, 10) || 0 });
|
|
140
|
+
break;
|
|
141
|
+
case 'queue-size':
|
|
142
|
+
this.update({ queueSize: Number.parseInt(newValue, 10) || 0 });
|
|
143
|
+
break;
|
|
144
|
+
case 'disabled':
|
|
145
|
+
this._disabled = newValue !== null;
|
|
146
|
+
this.render();
|
|
147
|
+
break;
|
|
148
|
+
case 'size':
|
|
149
|
+
this.config.size = newValue as any;
|
|
150
|
+
this.render();
|
|
151
|
+
break;
|
|
152
|
+
case 'variant':
|
|
153
|
+
this.config.variant = newValue as any;
|
|
154
|
+
this.render();
|
|
155
|
+
break;
|
|
156
|
+
case 'animation':
|
|
157
|
+
this.config.animation = newValue as any;
|
|
158
|
+
this.render();
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Start queue tracking
|
|
165
|
+
*/
|
|
166
|
+
public start(message?: string): void {
|
|
167
|
+
this.state = {
|
|
168
|
+
...this.state,
|
|
169
|
+
status: 'waiting',
|
|
170
|
+
startTime: Date.now(),
|
|
171
|
+
elapsedTime: 0,
|
|
172
|
+
message: message || this.config.message,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
this.initialPosition = this.state.position;
|
|
176
|
+
this.startTimer();
|
|
177
|
+
this.render();
|
|
178
|
+
this.updateCursor();
|
|
179
|
+
this.emit('queuestart', {
|
|
180
|
+
position: this.state.position,
|
|
181
|
+
queueSize: this.state.queueSize,
|
|
182
|
+
estimatedWait: this.state.estimatedWait,
|
|
183
|
+
timestamp: Date.now(),
|
|
184
|
+
} as QueueStartEvent);
|
|
185
|
+
this.log('Queue tracking started', this.state);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Update queue position
|
|
190
|
+
*/
|
|
191
|
+
public update(update: QueueUpdate): void {
|
|
192
|
+
if (this.state.status === 'completed' || this.state.status === 'error') {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
this.updateThrottled(update);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private _updateInternal(update: QueueUpdate): void {
|
|
200
|
+
const previousPosition = this.state.position;
|
|
201
|
+
|
|
202
|
+
this.state = {
|
|
203
|
+
...this.state,
|
|
204
|
+
position: update.position ?? this.state.position,
|
|
205
|
+
queueSize: update.queueSize ?? this.state.queueSize,
|
|
206
|
+
estimatedWait: update.estimatedWait ?? this.state.estimatedWait,
|
|
207
|
+
processingRate: update.processingRate ?? this.state.processingRate,
|
|
208
|
+
message: update.message ?? this.state.message,
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Animate position number if changed
|
|
212
|
+
if (update.position !== undefined && update.position !== previousPosition) {
|
|
213
|
+
const positionEl = this.shadowRoot?.querySelector('.position-number');
|
|
214
|
+
if (positionEl && this.config.animate) {
|
|
215
|
+
positionEl.classList.add('changing');
|
|
216
|
+
setTimeout(() => positionEl.classList.remove('changing'), 500);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.emit('positionchange', {
|
|
220
|
+
previousPosition,
|
|
221
|
+
currentPosition: this.state.position,
|
|
222
|
+
queueSize: this.state.queueSize,
|
|
223
|
+
estimatedWait: this.state.estimatedWait,
|
|
224
|
+
timestamp: Date.now(),
|
|
225
|
+
} as PositionChangeEvent);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.render();
|
|
229
|
+
this.log('Position updated', this.state);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Mark as processing (reached front of queue)
|
|
234
|
+
*/
|
|
235
|
+
public complete(): void {
|
|
236
|
+
this.state = {
|
|
237
|
+
...this.state,
|
|
238
|
+
status: 'completed',
|
|
239
|
+
position: 0,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
this.stopTimer();
|
|
243
|
+
this.render();
|
|
244
|
+
this.updateCursor();
|
|
245
|
+
|
|
246
|
+
const totalWaitTime = this.state.startTime > 0 ? Date.now() - this.state.startTime : 0;
|
|
247
|
+
|
|
248
|
+
this.emit('queuecomplete', {
|
|
249
|
+
totalWaitTime,
|
|
250
|
+
startPosition: this.initialPosition,
|
|
251
|
+
timestamp: Date.now(),
|
|
252
|
+
} as QueueCompleteEvent);
|
|
253
|
+
|
|
254
|
+
this.log('Queue completed', { totalWaitTime });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Cancel queue
|
|
259
|
+
*/
|
|
260
|
+
public cancel(reason: string = 'Cancelled by user'): void {
|
|
261
|
+
this.state = {
|
|
262
|
+
...this.state,
|
|
263
|
+
status: 'cancelled',
|
|
264
|
+
message: reason,
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
this.stopTimer();
|
|
268
|
+
this.render();
|
|
269
|
+
this.updateCursor();
|
|
270
|
+
this.log('Queue cancelled', reason);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Handle error
|
|
275
|
+
*/
|
|
276
|
+
public error(errorMessage: string): void {
|
|
277
|
+
this.state = {
|
|
278
|
+
...this.state,
|
|
279
|
+
status: 'error',
|
|
280
|
+
message: errorMessage,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
this.stopTimer();
|
|
284
|
+
this.render();
|
|
285
|
+
this.updateCursor();
|
|
286
|
+
|
|
287
|
+
this.emit('queueerror', {
|
|
288
|
+
message: errorMessage,
|
|
289
|
+
position: this.state.position,
|
|
290
|
+
timestamp: Date.now(),
|
|
291
|
+
} as QueueErrorEvent);
|
|
292
|
+
|
|
293
|
+
this.logError('Queue error', new Error(errorMessage));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Reset to initial state
|
|
298
|
+
*/
|
|
299
|
+
public reset(): void {
|
|
300
|
+
this.stopTimer();
|
|
301
|
+
|
|
302
|
+
this.state = {
|
|
303
|
+
status: 'waiting',
|
|
304
|
+
position: this.config.position,
|
|
305
|
+
queueSize: this.config.queueSize,
|
|
306
|
+
estimatedWait: this.config.estimatedWait,
|
|
307
|
+
processingRate: this.config.processingRate,
|
|
308
|
+
startTime: 0,
|
|
309
|
+
message: this.config.message,
|
|
310
|
+
elapsedTime: 0,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
this.initialPosition = this.config.position;
|
|
314
|
+
this.render();
|
|
315
|
+
this.log('Queue reset');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Get current position
|
|
320
|
+
*/
|
|
321
|
+
public getPosition(): number {
|
|
322
|
+
return this.state.position;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Get current status
|
|
327
|
+
*/
|
|
328
|
+
public getStatus(): QueueStatus {
|
|
329
|
+
return this.state.status;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Start elapsed time timer
|
|
334
|
+
*/
|
|
335
|
+
protected override startTimer(): void {
|
|
336
|
+
this.stopTimer();
|
|
337
|
+
this.timerInterval = globalThis.setInterval(() => {
|
|
338
|
+
if (this.state.startTime > 0) {
|
|
339
|
+
this.state.elapsedTime = Date.now() - this.state.startTime;
|
|
340
|
+
this.render();
|
|
341
|
+
}
|
|
342
|
+
}, 1000);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Stop timer
|
|
347
|
+
*/
|
|
348
|
+
private stopTimer(): void {
|
|
349
|
+
if (this.timerInterval) {
|
|
350
|
+
clearInterval(this.timerInterval);
|
|
351
|
+
this.timerInterval = undefined;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Calculate progress percentage
|
|
357
|
+
*/
|
|
358
|
+
private getProgressPercentage(): number {
|
|
359
|
+
if (this.state.queueSize === 0 || this.initialPosition === 0) return 0;
|
|
360
|
+
const processed = this.initialPosition - this.state.position;
|
|
361
|
+
return Math.min(100, Math.max(0, (processed / this.initialPosition) * 100));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Update cursor based on queue state
|
|
366
|
+
*/
|
|
367
|
+
private updateCursor(): void {
|
|
368
|
+
if (!this.config.cursorFeedback) return;
|
|
369
|
+
|
|
370
|
+
if (this.state.status === 'waiting') {
|
|
371
|
+
this.style.cursor = 'wait';
|
|
372
|
+
} else if (this.state.status === 'error' || this.state.status === 'cancelled') {
|
|
373
|
+
this.style.cursor = 'not-allowed';
|
|
374
|
+
} else {
|
|
375
|
+
this.style.cursor = 'default';
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Render metrics section
|
|
381
|
+
*/
|
|
382
|
+
private renderMetrics(): string {
|
|
383
|
+
if (this.state.status !== 'waiting') return '';
|
|
384
|
+
|
|
385
|
+
let metricsHtml = '';
|
|
386
|
+
|
|
387
|
+
if (this.config.showQueueSize) {
|
|
388
|
+
metricsHtml += `
|
|
389
|
+
<div class="metric">
|
|
390
|
+
<div class="metric-value">${this.state.queueSize}</div>
|
|
391
|
+
<div class="metric-label">Queue Size</div>
|
|
392
|
+
</div>
|
|
393
|
+
`;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (this.config.showWaitTime) {
|
|
397
|
+
metricsHtml += `
|
|
398
|
+
<div class="metric">
|
|
399
|
+
<div class="metric-value">${formatTime(Math.round(this.state.estimatedWait))}</div>
|
|
400
|
+
<div class="metric-label">Est. Wait</div>
|
|
401
|
+
</div>
|
|
402
|
+
`;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (this.config.showRate) {
|
|
406
|
+
metricsHtml += `
|
|
407
|
+
<div class="metric">
|
|
408
|
+
<div class="metric-value">${this.state.processingRate.toFixed(1)}/s</div>
|
|
409
|
+
<div class="metric-label">Processing Rate</div>
|
|
410
|
+
</div>
|
|
411
|
+
`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return metricsHtml
|
|
415
|
+
? `
|
|
416
|
+
<div class="queue-metrics">
|
|
417
|
+
${metricsHtml}
|
|
418
|
+
</div>
|
|
419
|
+
`
|
|
420
|
+
: '';
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Render component
|
|
425
|
+
*/
|
|
426
|
+
protected override render(): void {
|
|
427
|
+
if (!this.shadowRoot) return;
|
|
428
|
+
|
|
429
|
+
// Sync attributes to host element for CSS selectors
|
|
430
|
+
if (this.config.size && this.getAttribute('size') !== this.config.size) {
|
|
431
|
+
this.setAttribute('size', this.config.size);
|
|
432
|
+
}
|
|
433
|
+
if (this.config.variant && this.getAttribute('variant') !== this.config.variant) {
|
|
434
|
+
this.setAttribute('variant', this.config.variant);
|
|
435
|
+
}
|
|
436
|
+
if (this.config.animation && this.getAttribute('animation') !== this.config.animation) {
|
|
437
|
+
this.setAttribute('animation', this.config.animation);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const progress = this.getProgressPercentage();
|
|
441
|
+
const statusClass = this.state.status;
|
|
442
|
+
const statusText = this.getStatusText();
|
|
443
|
+
|
|
444
|
+
this.shadowRoot.innerHTML = `
|
|
445
|
+
<style>${styles}</style>
|
|
446
|
+
<div class="queue-container">
|
|
447
|
+
<div class="queue-header">
|
|
448
|
+
<div class="queue-icon">${this.getStatusIcon()}</div>
|
|
449
|
+
<div class="queue-title">
|
|
450
|
+
<h3 class="queue-status">${statusText}</h3>
|
|
451
|
+
<p class="queue-message">${this.state.message || ''}</p>
|
|
452
|
+
</div>
|
|
453
|
+
<span class="queue-badge ${statusClass}">${this.state.status}</span>
|
|
454
|
+
</div>
|
|
455
|
+
|
|
456
|
+
${
|
|
457
|
+
this.config.showPosition && this.state.status === 'waiting'
|
|
458
|
+
? `
|
|
459
|
+
<div class="queue-position">
|
|
460
|
+
<div class="position-number">${this.state.position}</div>
|
|
461
|
+
<div class="position-label">Position in Queue</div>
|
|
462
|
+
</div>
|
|
463
|
+
`
|
|
464
|
+
: ''
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
${this.renderMetrics()}
|
|
468
|
+
|
|
469
|
+
${
|
|
470
|
+
this.config.showProgressBar && this.state.status === 'waiting' && this.initialPosition > 0
|
|
471
|
+
? `
|
|
472
|
+
<div class="progress-container">
|
|
473
|
+
<div class="progress-bar">
|
|
474
|
+
<div class="progress-fill" style="width: ${progress}%"></div>
|
|
475
|
+
</div>
|
|
476
|
+
<div class="progress-label">
|
|
477
|
+
<span>${Math.round(progress)}% through queue</span>
|
|
478
|
+
<span>${formatTime(Math.round(this.state.elapsedTime / 1000))} elapsed</span>
|
|
479
|
+
</div>
|
|
480
|
+
</div>
|
|
481
|
+
`
|
|
482
|
+
: ''
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
${
|
|
486
|
+
this.state.status === 'waiting' && this.state.position <= 5
|
|
487
|
+
? `
|
|
488
|
+
<div class="queue-info">
|
|
489
|
+
<span class="info-icon">🎯</span>
|
|
490
|
+
<p class="info-text">You're almost there! Processing will begin shortly.</p>
|
|
491
|
+
</div>
|
|
492
|
+
`
|
|
493
|
+
: ''
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
${
|
|
497
|
+
this.state.status === 'completed'
|
|
498
|
+
? `
|
|
499
|
+
<div class="queue-info">
|
|
500
|
+
<span class="info-icon">✅</span>
|
|
501
|
+
<p class="info-text">Your request is now being processed!</p>
|
|
502
|
+
</div>
|
|
503
|
+
`
|
|
504
|
+
: ''
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
${
|
|
508
|
+
this.state.status === 'error'
|
|
509
|
+
? `
|
|
510
|
+
<div class="error-message">
|
|
511
|
+
<span class="error-icon">⚠️</span>
|
|
512
|
+
<p class="error-text">${this.state.message}</p>
|
|
513
|
+
</div>
|
|
514
|
+
`
|
|
515
|
+
: ''
|
|
516
|
+
}
|
|
517
|
+
</div>
|
|
518
|
+
`;
|
|
519
|
+
|
|
520
|
+
// Update ARIA attributes
|
|
521
|
+
this.setAttribute('aria-valuenow', this.state.position.toString());
|
|
522
|
+
this.setAttribute('aria-valuetext', `Position ${this.state.position} in queue`);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private getStatusIcon(): string {
|
|
526
|
+
switch (this.state.status) {
|
|
527
|
+
case 'waiting':
|
|
528
|
+
return '⏳';
|
|
529
|
+
case 'processing':
|
|
530
|
+
return '⚙️';
|
|
531
|
+
case 'completed':
|
|
532
|
+
return '✅';
|
|
533
|
+
case 'cancelled':
|
|
534
|
+
return '🚫';
|
|
535
|
+
case 'error':
|
|
536
|
+
return '❌';
|
|
537
|
+
default:
|
|
538
|
+
return '⏳';
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
private getStatusText(): string {
|
|
543
|
+
switch (this.state.status) {
|
|
544
|
+
case 'waiting':
|
|
545
|
+
return 'Waiting in Queue';
|
|
546
|
+
case 'processing':
|
|
547
|
+
return 'Processing Your Request';
|
|
548
|
+
case 'completed':
|
|
549
|
+
return 'Processing Started';
|
|
550
|
+
case 'cancelled':
|
|
551
|
+
return 'Queue Cancelled';
|
|
552
|
+
case 'error':
|
|
553
|
+
return 'Queue Error';
|
|
554
|
+
default:
|
|
555
|
+
return 'Queue Status';
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Register the custom element
|
|
561
|
+
if (!customElements.get('queue-progress')) {
|
|
562
|
+
customElements.define('queue-progress', QueueProgress);
|
|
563
|
+
}
|