agentgui 1.0.819 → 1.0.821
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/.prd +42 -0
- package/CHANGELOG.md +1 -0
- package/CLAUDE.md +2 -1
- package/package.json +1 -1
- package/static/index.html +5 -0
- package/static/js/agent-auth-oauth.js +159 -0
- package/static/js/agent-auth.js +15 -162
- package/static/js/conv-sidebar-actions.js +184 -0
- package/static/js/conversations.js +115 -736
- package/static/js/event-filter-config.js +36 -0
- package/static/js/event-filter.js +118 -252
- package/static/js/ui-components-rendering.js +183 -0
- package/static/js/ui-components.js +3 -185
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
window.EVENT_FILTER_CSV_HEADERS = ['timestamp', 'type', 'id', 'sessionId', 'message'];
|
|
3
|
+
|
|
4
|
+
window.exportEventsAsJSON = function(events) {
|
|
5
|
+
return JSON.stringify(events, null, 2);
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
window.exportEventsAsCSV = function(events) {
|
|
9
|
+
const headers = window.EVENT_FILTER_CSV_HEADERS;
|
|
10
|
+
const rows = [headers.join(',')];
|
|
11
|
+
for (const event of events) {
|
|
12
|
+
const row = [
|
|
13
|
+
new Date(event.timestamp || event.trackedAt).toISOString(),
|
|
14
|
+
event.type,
|
|
15
|
+
event.id || '',
|
|
16
|
+
event.sessionId || '',
|
|
17
|
+
JSON.stringify(event.message || event.content || event.text || '')
|
|
18
|
+
];
|
|
19
|
+
rows.push(row.map(v => `"${v}"`).join(','));
|
|
20
|
+
}
|
|
21
|
+
return rows.join('\n');
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
window.exportEventsAsMarkdown = function(events) {
|
|
25
|
+
const lines = ['# Event Export\n'];
|
|
26
|
+
let currentType = null;
|
|
27
|
+
for (const event of events) {
|
|
28
|
+
if (event.type !== currentType) {
|
|
29
|
+
currentType = event.type;
|
|
30
|
+
lines.push(`\n## ${currentType}\n`);
|
|
31
|
+
}
|
|
32
|
+
const time = new Date(event.timestamp || event.trackedAt).toLocaleTimeString();
|
|
33
|
+
lines.push(`- **${time}**: ${JSON.stringify(event)}`);
|
|
34
|
+
}
|
|
35
|
+
return lines.join('\n');
|
|
36
|
+
};
|
|
@@ -1,252 +1,118 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
this.
|
|
5
|
-
this.
|
|
6
|
-
this.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
this.filterState.
|
|
47
|
-
|
|
48
|
-
this.filterState.
|
|
49
|
-
}
|
|
50
|
-
this.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
this.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return results.sort((a, b) => b.matchCount - a.matchCount);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
getStats() {
|
|
123
|
-
const stats = {
|
|
124
|
-
total: this.allEvents.length,
|
|
125
|
-
byType: {},
|
|
126
|
-
byTime: {
|
|
127
|
-
oldest: null,
|
|
128
|
-
newest: null,
|
|
129
|
-
span: 0
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
for (const event of this.allEvents) {
|
|
134
|
-
stats.byType[event.type] = (stats.byType[event.type] || 0) + 1;
|
|
135
|
-
|
|
136
|
-
const time = event.timestamp || event.trackedAt;
|
|
137
|
-
if (!stats.byTime.oldest || time < stats.byTime.oldest) {
|
|
138
|
-
stats.byTime.oldest = time;
|
|
139
|
-
}
|
|
140
|
-
if (!stats.byTime.newest || time > stats.byTime.newest) {
|
|
141
|
-
stats.byTime.newest = time;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (stats.byTime.oldest && stats.byTime.newest) {
|
|
146
|
-
stats.byTime.span = stats.byTime.newest - stats.byTime.oldest;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return stats;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async startReplay(events = null, speed = 1) {
|
|
153
|
-
const replayEvents = events || this.filteredEvents;
|
|
154
|
-
if (replayEvents.length === 0) return;
|
|
155
|
-
|
|
156
|
-
this.replayState.isReplaying = true;
|
|
157
|
-
this.replayState.currentIndex = 0;
|
|
158
|
-
this.replayState.speed = speed;
|
|
159
|
-
|
|
160
|
-
this.renderer.clear();
|
|
161
|
-
|
|
162
|
-
for (const event of replayEvents) {
|
|
163
|
-
if (!this.replayState.isReplaying) break;
|
|
164
|
-
|
|
165
|
-
const delay = 100 / this.replayState.speed;
|
|
166
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
167
|
-
|
|
168
|
-
this.renderer.queueEvent(event);
|
|
169
|
-
this.replayState.currentIndex++;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
this.replayState.isReplaying = false;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
stopReplay() {
|
|
176
|
-
this.replayState.isReplaying = false;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
getReplayProgress() {
|
|
180
|
-
const total = this.filteredEvents.length;
|
|
181
|
-
const current = this.replayState.currentIndex;
|
|
182
|
-
return {
|
|
183
|
-
current,
|
|
184
|
-
total,
|
|
185
|
-
percentage: total > 0 ? (current / total) * 100 : 0,
|
|
186
|
-
isReplaying: this.replayState.isReplaying
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export(format = 'json') {
|
|
191
|
-
const events = this.filterState.isActive ? this.filteredEvents : this.allEvents;
|
|
192
|
-
|
|
193
|
-
switch (format) {
|
|
194
|
-
case 'json':
|
|
195
|
-
return JSON.stringify(events, null, 2);
|
|
196
|
-
|
|
197
|
-
case 'csv':
|
|
198
|
-
return this.exportAsCSV(events);
|
|
199
|
-
|
|
200
|
-
case 'markdown':
|
|
201
|
-
return this.exportAsMarkdown(events);
|
|
202
|
-
|
|
203
|
-
default:
|
|
204
|
-
return JSON.stringify(events);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
exportAsCSV(events) {
|
|
209
|
-
const headers = ['timestamp', 'type', 'id', 'sessionId', 'message'];
|
|
210
|
-
const rows = [headers.join(',')];
|
|
211
|
-
|
|
212
|
-
for (const event of events) {
|
|
213
|
-
const row = [
|
|
214
|
-
new Date(event.timestamp || event.trackedAt).toISOString(),
|
|
215
|
-
event.type,
|
|
216
|
-
event.id || '',
|
|
217
|
-
event.sessionId || '',
|
|
218
|
-
JSON.stringify(event.message || event.content || event.text || '')
|
|
219
|
-
];
|
|
220
|
-
rows.push(row.map(v => `"${v}"`).join(','));
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return rows.join('\n');
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
exportAsMarkdown(events) {
|
|
227
|
-
const lines = ['# Event Export\n'];
|
|
228
|
-
let currentType = null;
|
|
229
|
-
|
|
230
|
-
for (const event of events) {
|
|
231
|
-
if (event.type !== currentType) {
|
|
232
|
-
currentType = event.type;
|
|
233
|
-
lines.push(`\n## ${currentType}\n`);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const time = new Date(event.timestamp || event.trackedAt).toLocaleTimeString();
|
|
237
|
-
lines.push(`- **${time}**: ${JSON.stringify(event)}`);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return lines.join('\n');
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
clear() {
|
|
244
|
-
this.allEvents = [];
|
|
245
|
-
this.filteredEvents = [];
|
|
246
|
-
this.stopReplay();
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (typeof module !== 'undefined' && module.exports) {
|
|
251
|
-
module.exports = EventFilter;
|
|
252
|
-
}
|
|
1
|
+
class EventFilter {
|
|
2
|
+
constructor(renderer) {
|
|
3
|
+
this.renderer = renderer;
|
|
4
|
+
this.allEvents = [];
|
|
5
|
+
this.filteredEvents = [];
|
|
6
|
+
this.filterState = {
|
|
7
|
+
types: new Set(),
|
|
8
|
+
searchText: '',
|
|
9
|
+
startTime: null,
|
|
10
|
+
endTime: null,
|
|
11
|
+
isActive: false
|
|
12
|
+
};
|
|
13
|
+
this.replayState = {
|
|
14
|
+
isReplaying: false,
|
|
15
|
+
currentIndex: 0,
|
|
16
|
+
speed: 1
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
trackEvent(event) {
|
|
21
|
+
this.allEvents.push({
|
|
22
|
+
...event,
|
|
23
|
+
trackingId: this.allEvents.length,
|
|
24
|
+
trackedAt: Date.now()
|
|
25
|
+
});
|
|
26
|
+
if (this.allEvents.length > 5000) this.allEvents.shift();
|
|
27
|
+
if (this.filterState.isActive) this.applyFilters();
|
|
28
|
+
return event;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setTypeFilter(types) { this.filterState.types = new Set(types); this.applyFilters(); }
|
|
32
|
+
|
|
33
|
+
toggleType(type) {
|
|
34
|
+
if (this.filterState.types.has(type)) this.filterState.types.delete(type);
|
|
35
|
+
else this.filterState.types.add(type);
|
|
36
|
+
this.applyFilters();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setSearchText(text) { this.filterState.searchText = text.toLowerCase(); this.applyFilters(); }
|
|
40
|
+
|
|
41
|
+
setTimeRange(startTime, endTime) { this.filterState.startTime = startTime; this.filterState.endTime = endTime; this.applyFilters(); }
|
|
42
|
+
|
|
43
|
+
applyFilters() {
|
|
44
|
+
this.filterState.isActive =
|
|
45
|
+
this.filterState.types.size > 0 ||
|
|
46
|
+
this.filterState.searchText.length > 0 ||
|
|
47
|
+
this.filterState.startTime !== null ||
|
|
48
|
+
this.filterState.endTime !== null;
|
|
49
|
+
if (!this.filterState.isActive) { this.filteredEvents = [...this.allEvents]; return this.filteredEvents; }
|
|
50
|
+
this.filteredEvents = this.allEvents.filter(event => {
|
|
51
|
+
if (this.filterState.types.size > 0 && !this.filterState.types.has(event.type)) return false;
|
|
52
|
+
if (this.filterState.searchText.length > 0) {
|
|
53
|
+
if (!JSON.stringify(event).toLowerCase().includes(this.filterState.searchText)) return false;
|
|
54
|
+
}
|
|
55
|
+
const eventTime = event.timestamp || event.trackedAt;
|
|
56
|
+
if (this.filterState.startTime && eventTime < this.filterState.startTime) return false;
|
|
57
|
+
if (this.filterState.endTime && eventTime > this.filterState.endTime) return false;
|
|
58
|
+
return true;
|
|
59
|
+
});
|
|
60
|
+
return this.filteredEvents;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
search(query) {
|
|
64
|
+
const lowerQuery = query.toLowerCase();
|
|
65
|
+
return this.allEvents.map((event, i) => {
|
|
66
|
+
const searchable = JSON.stringify(event).toLowerCase();
|
|
67
|
+
if (!searchable.includes(lowerQuery)) return null;
|
|
68
|
+
return { event, index: i, matchCount: (searchable.match(new RegExp(lowerQuery, 'g')) || []).length };
|
|
69
|
+
}).filter(Boolean).sort((a, b) => b.matchCount - a.matchCount);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getStats() {
|
|
73
|
+
const stats = { total: this.allEvents.length, byType: {}, byTime: { oldest: null, newest: null, span: 0 } };
|
|
74
|
+
for (const event of this.allEvents) {
|
|
75
|
+
stats.byType[event.type] = (stats.byType[event.type] || 0) + 1;
|
|
76
|
+
const time = event.timestamp || event.trackedAt;
|
|
77
|
+
if (!stats.byTime.oldest || time < stats.byTime.oldest) stats.byTime.oldest = time;
|
|
78
|
+
if (!stats.byTime.newest || time > stats.byTime.newest) stats.byTime.newest = time;
|
|
79
|
+
}
|
|
80
|
+
if (stats.byTime.oldest && stats.byTime.newest) stats.byTime.span = stats.byTime.newest - stats.byTime.oldest;
|
|
81
|
+
return stats;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async startReplay(events = null, speed = 1) {
|
|
85
|
+
const replayEvents = events || this.filteredEvents;
|
|
86
|
+
if (replayEvents.length === 0) return;
|
|
87
|
+
this.replayState = { isReplaying: true, currentIndex: 0, speed };
|
|
88
|
+
this.renderer.clear();
|
|
89
|
+
for (const event of replayEvents) {
|
|
90
|
+
if (!this.replayState.isReplaying) break;
|
|
91
|
+
await new Promise(resolve => setTimeout(resolve, 100 / this.replayState.speed));
|
|
92
|
+
this.renderer.queueEvent(event);
|
|
93
|
+
this.replayState.currentIndex++;
|
|
94
|
+
}
|
|
95
|
+
this.replayState.isReplaying = false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
stopReplay() { this.replayState.isReplaying = false; }
|
|
99
|
+
|
|
100
|
+
getReplayProgress() {
|
|
101
|
+
const total = this.filteredEvents.length;
|
|
102
|
+
const current = this.replayState.currentIndex;
|
|
103
|
+
return { current, total, percentage: total > 0 ? (current / total) * 100 : 0, isReplaying: this.replayState.isReplaying };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export(format = 'json') {
|
|
107
|
+
const events = this.filterState.isActive ? this.filteredEvents : this.allEvents;
|
|
108
|
+
if (format === 'csv') return window.exportEventsAsCSV(events);
|
|
109
|
+
if (format === 'markdown') return window.exportEventsAsMarkdown(events);
|
|
110
|
+
return window.exportEventsAsJSON(events);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
clear() { this.allEvents = []; this.filteredEvents = []; this.stopReplay(); }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
window.EventFilter = EventFilter;
|
|
117
|
+
|
|
118
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = EventFilter;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
|
|
2
|
+
UIComponents.copyToClipboard = function(text) {
|
|
3
|
+
return navigator.clipboard.writeText(text).catch(err => {
|
|
4
|
+
console.error('Failed to copy:', err);
|
|
5
|
+
return false;
|
|
6
|
+
});
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
UIComponents.downloadFile = function(data, filename, mimeType = 'text/plain') {
|
|
10
|
+
const blob = new Blob([data], { type: mimeType });
|
|
11
|
+
const url = URL.createObjectURL(blob);
|
|
12
|
+
const link = document.createElement('a');
|
|
13
|
+
link.href = url;
|
|
14
|
+
link.download = filename;
|
|
15
|
+
document.body.appendChild(link);
|
|
16
|
+
link.click();
|
|
17
|
+
document.body.removeChild(link);
|
|
18
|
+
URL.revokeObjectURL(url);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
UIComponents.createProgressBar = function(config = {}) {
|
|
22
|
+
const {
|
|
23
|
+
percentage = 0,
|
|
24
|
+
label = '',
|
|
25
|
+
showLabel = true
|
|
26
|
+
} = config;
|
|
27
|
+
|
|
28
|
+
const container = document.createElement('div');
|
|
29
|
+
container.className = 'progress-container';
|
|
30
|
+
|
|
31
|
+
let html = '';
|
|
32
|
+
if (label && showLabel) {
|
|
33
|
+
html += `<div class="flex justify-between mb-2 text-sm"><span>${UIComponents.escapeHtml(label)}</span><span>${Math.round(percentage)}%</span></div>`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
html += `
|
|
37
|
+
<progress class="progress progress-primary progress-xs w-full" value="${Math.min(100, Math.max(0, percentage))}" max="100"></progress>
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
container.innerHTML = html;
|
|
41
|
+
return container;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
UIComponents.createCollapsible = function(config = {}) {
|
|
45
|
+
const {
|
|
46
|
+
title = 'Details',
|
|
47
|
+
content = '',
|
|
48
|
+
isOpen = false
|
|
49
|
+
} = config;
|
|
50
|
+
|
|
51
|
+
const container = document.createElement('div');
|
|
52
|
+
container.className = 'collapsible';
|
|
53
|
+
|
|
54
|
+
container.innerHTML = `
|
|
55
|
+
<details ${isOpen ? 'open' : ''}>
|
|
56
|
+
<summary class="cursor-pointer font-semibold hover:bg-base-200 px-2 py-1 rounded transition-colors">
|
|
57
|
+
${UIComponents.escapeHtml(title)}
|
|
58
|
+
</summary>
|
|
59
|
+
<div class="content mt-2 ml-4">
|
|
60
|
+
${typeof content === 'string' ? content : ''}
|
|
61
|
+
</div>
|
|
62
|
+
</details>
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
return container;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
UIComponents.createInput = function(config = {}) {
|
|
69
|
+
const {
|
|
70
|
+
type = 'text',
|
|
71
|
+
name = '',
|
|
72
|
+
label = '',
|
|
73
|
+
placeholder = '',
|
|
74
|
+
value = '',
|
|
75
|
+
required = false
|
|
76
|
+
} = config;
|
|
77
|
+
|
|
78
|
+
const container = document.createElement('div');
|
|
79
|
+
container.className = 'form-group mb-4';
|
|
80
|
+
|
|
81
|
+
let html = '';
|
|
82
|
+
if (label) {
|
|
83
|
+
html += `<label class="block text-sm font-medium mb-2">${UIComponents.escapeHtml(label)}</label>`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
html += `
|
|
87
|
+
<input
|
|
88
|
+
type="${type}"
|
|
89
|
+
name="${name}"
|
|
90
|
+
placeholder="${UIComponents.escapeHtml(placeholder)}"
|
|
91
|
+
value="${UIComponents.escapeHtml(value)}"
|
|
92
|
+
${required ? 'required' : ''}
|
|
93
|
+
class="input input-block input-solid"
|
|
94
|
+
/>
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
container.innerHTML = html;
|
|
98
|
+
return container;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
UIComponents.createSelect = function(config = {}) {
|
|
102
|
+
const {
|
|
103
|
+
name = '',
|
|
104
|
+
label = '',
|
|
105
|
+
options = [],
|
|
106
|
+
value = '',
|
|
107
|
+
required = false
|
|
108
|
+
} = config;
|
|
109
|
+
|
|
110
|
+
const container = document.createElement('div');
|
|
111
|
+
container.className = 'form-group mb-4';
|
|
112
|
+
|
|
113
|
+
let html = '';
|
|
114
|
+
if (label) {
|
|
115
|
+
html += `<label class="block text-sm font-medium mb-2">${UIComponents.escapeHtml(label)}</label>`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
html += `
|
|
119
|
+
<select
|
|
120
|
+
name="${name}"
|
|
121
|
+
${required ? 'required' : ''}
|
|
122
|
+
class="select select-block select-solid"
|
|
123
|
+
>
|
|
124
|
+
${options.map(opt => `
|
|
125
|
+
<option value="${opt.value}" ${opt.value === value ? 'selected' : ''}>
|
|
126
|
+
${UIComponents.escapeHtml(opt.label)}
|
|
127
|
+
</option>
|
|
128
|
+
`).join('')}
|
|
129
|
+
</select>
|
|
130
|
+
`;
|
|
131
|
+
|
|
132
|
+
container.innerHTML = html;
|
|
133
|
+
return container;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
UIComponents.createButtonGroup = function(config = {}) {
|
|
137
|
+
const {
|
|
138
|
+
buttons = [],
|
|
139
|
+
vertical = false
|
|
140
|
+
} = config;
|
|
141
|
+
|
|
142
|
+
const container = document.createElement('div');
|
|
143
|
+
container.className = `button-group flex gap-2 ${vertical ? 'flex-col' : 'flex-row'}`;
|
|
144
|
+
|
|
145
|
+
buttons.forEach(btn => {
|
|
146
|
+
const button = document.createElement('button');
|
|
147
|
+
button.className = `btn btn-${btn.variant || 'secondary'} flex-${vertical ? '1' : 'none'}`;
|
|
148
|
+
button.textContent = btn.label;
|
|
149
|
+
if (btn.onClick) {
|
|
150
|
+
button.addEventListener('click', btn.onClick);
|
|
151
|
+
}
|
|
152
|
+
container.appendChild(button);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return container;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
UIComponents.createBadge = function(config = {}) {
|
|
159
|
+
const {
|
|
160
|
+
label = '',
|
|
161
|
+
variant = 'default',
|
|
162
|
+
size = 'medium'
|
|
163
|
+
} = config;
|
|
164
|
+
|
|
165
|
+
const sizeClasses = {
|
|
166
|
+
'small': 'badge-sm',
|
|
167
|
+
'medium': 'badge-md',
|
|
168
|
+
'large': 'badge-lg'
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const variantClasses = {
|
|
172
|
+
'default': 'badge-flat',
|
|
173
|
+
'primary': 'badge-flat-primary',
|
|
174
|
+
'success': 'badge-flat-success',
|
|
175
|
+
'warning': 'badge-flat-warning',
|
|
176
|
+
'error': 'badge-flat-error'
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const badge = document.createElement('span');
|
|
180
|
+
badge.className = `badge ${sizeClasses[size] || sizeClasses['medium']} ${variantClasses[variant] || variantClasses['default']}`;
|
|
181
|
+
badge.textContent = label;
|
|
182
|
+
return badge;
|
|
183
|
+
};
|