@databridgeai/toolkit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -0
- package/dist/agent.d.ts +70 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +194 -0
- package/dist/agent.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/introspect-mongo.d.ts +8 -0
- package/dist/introspect-mongo.d.ts.map +1 -0
- package/dist/introspect-mongo.js +75 -0
- package/dist/introspect-mongo.js.map +1 -0
- package/dist/introspect-postgres.d.ts +8 -0
- package/dist/introspect-postgres.d.ts.map +1 -0
- package/dist/introspect-postgres.js +55 -0
- package/dist/introspect-postgres.js.map +1 -0
- package/dist/playground.d.ts +16 -0
- package/dist/playground.d.ts.map +1 -0
- package/dist/playground.js +846 -0
- package/dist/playground.js.map +1 -0
- package/package.json +32 -0
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Embedded Chat Playground for SDK users
|
|
4
|
+
*
|
|
5
|
+
* Developers can mount this in their Express apps:
|
|
6
|
+
* app.get('/playground', agent.playgroundUI())
|
|
7
|
+
*
|
|
8
|
+
* This provides a ready-to-use chat interface for testing the AI agent.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.getPlaygroundHTML = getPlaygroundHTML;
|
|
12
|
+
function getPlaygroundHTML(config) {
|
|
13
|
+
const { chatEndpoint, defaultUserId = 'user_1', defaultOrgId = 'org_acme', defaultRole = 'admin', projectName = 'AI Agent', } = config;
|
|
14
|
+
return `<!DOCTYPE html>
|
|
15
|
+
<html lang="en" data-theme="dark">
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="UTF-8" />
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
19
|
+
<title>${projectName} — Chat Playground</title>
|
|
20
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
|
|
21
|
+
<style>
|
|
22
|
+
/* ── Reset & Variables ────────────────────────────── */
|
|
23
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
24
|
+
|
|
25
|
+
:root {
|
|
26
|
+
--bg: #0f1117;
|
|
27
|
+
--bg-surface: #1a1d27;
|
|
28
|
+
--bg-input: #252830;
|
|
29
|
+
--bg-hover: #2a2d38;
|
|
30
|
+
--border: #2e313c;
|
|
31
|
+
--text: #e4e6eb;
|
|
32
|
+
--text-muted: #8b8fa3;
|
|
33
|
+
--primary: #6c63ff;
|
|
34
|
+
--primary-hover: #5a52e0;
|
|
35
|
+
--accent: #4fc3f7;
|
|
36
|
+
--success: #66bb6a;
|
|
37
|
+
--error: #ef5350;
|
|
38
|
+
--bot-bg: #1e2235;
|
|
39
|
+
--user-bg: #2d2673;
|
|
40
|
+
--radius: 12px;
|
|
41
|
+
--radius-sm: 8px;
|
|
42
|
+
--shadow: 0 2px 12px rgba(0,0,0,0.3);
|
|
43
|
+
--font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
44
|
+
--mono: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
body {
|
|
48
|
+
font-family: var(--font);
|
|
49
|
+
background: var(--bg);
|
|
50
|
+
color: var(--text);
|
|
51
|
+
height: 100vh;
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
overflow: hidden;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* ── Header ──────────────────────────────────────── */
|
|
58
|
+
.header {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
justify-content: space-between;
|
|
62
|
+
padding: 12px 20px;
|
|
63
|
+
background: var(--bg-surface);
|
|
64
|
+
border-bottom: 1px solid var(--border);
|
|
65
|
+
flex-shrink: 0;
|
|
66
|
+
}
|
|
67
|
+
.header-left {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
gap: 12px;
|
|
71
|
+
}
|
|
72
|
+
.header-logo {
|
|
73
|
+
font-size: 18px;
|
|
74
|
+
font-weight: 700;
|
|
75
|
+
background: linear-gradient(135deg, var(--primary), var(--accent));
|
|
76
|
+
-webkit-background-clip: text;
|
|
77
|
+
-webkit-text-fill-color: transparent;
|
|
78
|
+
}
|
|
79
|
+
.header-badge {
|
|
80
|
+
font-size: 10px;
|
|
81
|
+
padding: 3px 8px;
|
|
82
|
+
border-radius: 20px;
|
|
83
|
+
background: rgba(108,99,255,0.15);
|
|
84
|
+
color: var(--primary);
|
|
85
|
+
font-weight: 600;
|
|
86
|
+
}
|
|
87
|
+
.context-pills {
|
|
88
|
+
display: flex;
|
|
89
|
+
gap: 8px;
|
|
90
|
+
font-size: 11px;
|
|
91
|
+
}
|
|
92
|
+
.pill {
|
|
93
|
+
padding: 4px 10px;
|
|
94
|
+
background: var(--bg-input);
|
|
95
|
+
border: 1px solid var(--border);
|
|
96
|
+
border-radius: 20px;
|
|
97
|
+
color: var(--text-muted);
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
transition: all 0.2s;
|
|
100
|
+
}
|
|
101
|
+
.pill:hover {
|
|
102
|
+
border-color: var(--primary);
|
|
103
|
+
color: var(--text);
|
|
104
|
+
}
|
|
105
|
+
.pill.editable::after {
|
|
106
|
+
content: ' ✎';
|
|
107
|
+
opacity: 0.5;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* ── Main layout ─────────────────────────────────── */
|
|
111
|
+
.main {
|
|
112
|
+
flex: 1;
|
|
113
|
+
display: flex;
|
|
114
|
+
flex-direction: column;
|
|
115
|
+
overflow: hidden;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.chat-messages {
|
|
119
|
+
flex: 1;
|
|
120
|
+
overflow-y: auto;
|
|
121
|
+
padding: 24px;
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-direction: column;
|
|
124
|
+
gap: 16px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.chat-messages::-webkit-scrollbar { width: 6px; }
|
|
128
|
+
.chat-messages::-webkit-scrollbar-track { background: transparent; }
|
|
129
|
+
.chat-messages::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
|
130
|
+
|
|
131
|
+
.message {
|
|
132
|
+
max-width: 75%;
|
|
133
|
+
padding: 12px 16px;
|
|
134
|
+
border-radius: var(--radius);
|
|
135
|
+
line-height: 1.6;
|
|
136
|
+
font-size: 14px;
|
|
137
|
+
word-wrap: break-word;
|
|
138
|
+
animation: fadeIn 0.25s ease;
|
|
139
|
+
}
|
|
140
|
+
@keyframes fadeIn {
|
|
141
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
142
|
+
to { opacity: 1; transform: translateY(0); }
|
|
143
|
+
}
|
|
144
|
+
.message.user {
|
|
145
|
+
align-self: flex-end;
|
|
146
|
+
background: var(--user-bg);
|
|
147
|
+
border-bottom-right-radius: 4px;
|
|
148
|
+
}
|
|
149
|
+
.message.bot {
|
|
150
|
+
align-self: flex-start;
|
|
151
|
+
background: var(--bot-bg);
|
|
152
|
+
border-bottom-left-radius: 4px;
|
|
153
|
+
}
|
|
154
|
+
.message.system {
|
|
155
|
+
align-self: center;
|
|
156
|
+
background: transparent;
|
|
157
|
+
color: var(--text-muted);
|
|
158
|
+
font-size: 12px;
|
|
159
|
+
padding: 8px;
|
|
160
|
+
max-width: 100%;
|
|
161
|
+
text-align: center;
|
|
162
|
+
}
|
|
163
|
+
.message.error {
|
|
164
|
+
align-self: center;
|
|
165
|
+
background: rgba(239,83,80,0.1);
|
|
166
|
+
color: var(--error);
|
|
167
|
+
font-size: 13px;
|
|
168
|
+
max-width: 90%;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.message-sender {
|
|
172
|
+
font-size: 10px;
|
|
173
|
+
font-weight: 600;
|
|
174
|
+
color: var(--text-muted);
|
|
175
|
+
margin-bottom: 4px;
|
|
176
|
+
text-transform: uppercase;
|
|
177
|
+
letter-spacing: 0.5px;
|
|
178
|
+
}
|
|
179
|
+
.message.user .message-sender { color: rgba(255,255,255,0.6); }
|
|
180
|
+
.message.bot .message-sender { color: var(--accent); }
|
|
181
|
+
|
|
182
|
+
.message-data {
|
|
183
|
+
margin-top: 10px;
|
|
184
|
+
background: var(--bg);
|
|
185
|
+
border-radius: var(--radius-sm);
|
|
186
|
+
padding: 12px;
|
|
187
|
+
overflow-x: auto;
|
|
188
|
+
font-size: 12px;
|
|
189
|
+
}
|
|
190
|
+
.message-data table {
|
|
191
|
+
width: 100%;
|
|
192
|
+
border-collapse: collapse;
|
|
193
|
+
font-family: var(--mono);
|
|
194
|
+
font-size: 11px;
|
|
195
|
+
}
|
|
196
|
+
.message-data th {
|
|
197
|
+
text-align: left;
|
|
198
|
+
padding: 6px 10px;
|
|
199
|
+
color: var(--accent);
|
|
200
|
+
border-bottom: 1px solid var(--border);
|
|
201
|
+
font-weight: 600;
|
|
202
|
+
white-space: nowrap;
|
|
203
|
+
}
|
|
204
|
+
.message-data td {
|
|
205
|
+
padding: 5px 10px;
|
|
206
|
+
border-bottom: 1px solid rgba(46,49,60,0.5);
|
|
207
|
+
color: var(--text);
|
|
208
|
+
white-space: nowrap;
|
|
209
|
+
}
|
|
210
|
+
.message-data tr:last-child td { border-bottom: none; }
|
|
211
|
+
.message-data tr:hover td { background: rgba(108,99,255,0.05); }
|
|
212
|
+
|
|
213
|
+
/* ── Image cells ─────────────────────────────────── */
|
|
214
|
+
.cell-image {
|
|
215
|
+
width: 40px;
|
|
216
|
+
height: 40px;
|
|
217
|
+
border-radius: 6px;
|
|
218
|
+
object-fit: cover;
|
|
219
|
+
vertical-align: middle;
|
|
220
|
+
cursor: pointer;
|
|
221
|
+
transition: transform 0.2s;
|
|
222
|
+
}
|
|
223
|
+
.cell-image:hover { transform: scale(2.5); z-index: 10; position: relative; }
|
|
224
|
+
.cell-image-round {
|
|
225
|
+
border-radius: 50%;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* ── Chart container ─────────────────────────────── */
|
|
229
|
+
.chart-container {
|
|
230
|
+
margin-top: 12px;
|
|
231
|
+
background: var(--bg);
|
|
232
|
+
border-radius: var(--radius-sm);
|
|
233
|
+
padding: 16px;
|
|
234
|
+
max-width: 480px;
|
|
235
|
+
}
|
|
236
|
+
.chart-container canvas {
|
|
237
|
+
max-height: 300px;
|
|
238
|
+
}
|
|
239
|
+
.chart-title {
|
|
240
|
+
font-size: 12px;
|
|
241
|
+
font-weight: 600;
|
|
242
|
+
color: var(--text-muted);
|
|
243
|
+
margin-bottom: 10px;
|
|
244
|
+
text-transform: uppercase;
|
|
245
|
+
letter-spacing: 0.5px;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.data-json {
|
|
249
|
+
font-family: var(--mono);
|
|
250
|
+
font-size: 11px;
|
|
251
|
+
white-space: pre-wrap;
|
|
252
|
+
color: var(--text-muted);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* ── Typing indicator ────────────────────────────── */
|
|
256
|
+
.typing {
|
|
257
|
+
display: flex;
|
|
258
|
+
gap: 4px;
|
|
259
|
+
padding: 8px 16px;
|
|
260
|
+
align-self: flex-start;
|
|
261
|
+
}
|
|
262
|
+
.typing span {
|
|
263
|
+
width: 6px; height: 6px;
|
|
264
|
+
border-radius: 50%;
|
|
265
|
+
background: var(--text-muted);
|
|
266
|
+
animation: bounce 1.4s infinite both;
|
|
267
|
+
}
|
|
268
|
+
.typing span:nth-child(2) { animation-delay: 0.16s; }
|
|
269
|
+
.typing span:nth-child(3) { animation-delay: 0.32s; }
|
|
270
|
+
@keyframes bounce {
|
|
271
|
+
0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
|
|
272
|
+
40% { transform: scale(1); opacity: 1; }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/* ── Empty state ─────────────────────────────────── */
|
|
276
|
+
.empty-state {
|
|
277
|
+
flex: 1;
|
|
278
|
+
display: flex;
|
|
279
|
+
flex-direction: column;
|
|
280
|
+
align-items: center;
|
|
281
|
+
justify-content: center;
|
|
282
|
+
color: var(--text-muted);
|
|
283
|
+
gap: 16px;
|
|
284
|
+
padding: 40px;
|
|
285
|
+
}
|
|
286
|
+
.empty-state .icon { font-size: 48px; opacity: 0.3; }
|
|
287
|
+
.empty-state h2 { font-size: 18px; color: var(--text); }
|
|
288
|
+
.empty-state p {
|
|
289
|
+
font-size: 13px;
|
|
290
|
+
text-align: center;
|
|
291
|
+
max-width: 400px;
|
|
292
|
+
line-height: 1.6;
|
|
293
|
+
}
|
|
294
|
+
.suggestion-chips {
|
|
295
|
+
display: flex;
|
|
296
|
+
flex-wrap: wrap;
|
|
297
|
+
gap: 8px;
|
|
298
|
+
margin-top: 8px;
|
|
299
|
+
justify-content: center;
|
|
300
|
+
}
|
|
301
|
+
.suggestion-chip {
|
|
302
|
+
padding: 8px 14px;
|
|
303
|
+
background: var(--bg-input);
|
|
304
|
+
border: 1px solid var(--border);
|
|
305
|
+
border-radius: 20px;
|
|
306
|
+
font-size: 12px;
|
|
307
|
+
color: var(--text);
|
|
308
|
+
cursor: pointer;
|
|
309
|
+
transition: all 0.2s;
|
|
310
|
+
}
|
|
311
|
+
.suggestion-chip:hover {
|
|
312
|
+
border-color: var(--primary);
|
|
313
|
+
background: rgba(108,99,255,0.1);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/* ── Input area ──────────────────────────────────── */
|
|
317
|
+
.input-area {
|
|
318
|
+
padding: 16px 24px;
|
|
319
|
+
background: var(--bg-surface);
|
|
320
|
+
border-top: 1px solid var(--border);
|
|
321
|
+
flex-shrink: 0;
|
|
322
|
+
}
|
|
323
|
+
.input-row {
|
|
324
|
+
display: flex;
|
|
325
|
+
gap: 10px;
|
|
326
|
+
align-items: flex-end;
|
|
327
|
+
}
|
|
328
|
+
.input-wrapper {
|
|
329
|
+
flex: 1;
|
|
330
|
+
position: relative;
|
|
331
|
+
}
|
|
332
|
+
.input-wrapper textarea {
|
|
333
|
+
width: 100%;
|
|
334
|
+
padding: 12px 16px;
|
|
335
|
+
background: var(--bg-input);
|
|
336
|
+
border: 1px solid var(--border);
|
|
337
|
+
border-radius: var(--radius);
|
|
338
|
+
color: var(--text);
|
|
339
|
+
font-size: 14px;
|
|
340
|
+
font-family: var(--font);
|
|
341
|
+
resize: none;
|
|
342
|
+
outline: none;
|
|
343
|
+
transition: border-color 0.2s;
|
|
344
|
+
min-height: 44px;
|
|
345
|
+
max-height: 120px;
|
|
346
|
+
line-height: 1.5;
|
|
347
|
+
}
|
|
348
|
+
.input-wrapper textarea:focus { border-color: var(--primary); }
|
|
349
|
+
.input-wrapper textarea::placeholder { color: var(--text-muted); }
|
|
350
|
+
.send-btn {
|
|
351
|
+
width: 44px; height: 44px;
|
|
352
|
+
background: var(--primary);
|
|
353
|
+
color: #fff;
|
|
354
|
+
border: none;
|
|
355
|
+
border-radius: var(--radius);
|
|
356
|
+
cursor: pointer;
|
|
357
|
+
display: flex;
|
|
358
|
+
align-items: center;
|
|
359
|
+
justify-content: center;
|
|
360
|
+
transition: background 0.2s;
|
|
361
|
+
flex-shrink: 0;
|
|
362
|
+
}
|
|
363
|
+
.send-btn:hover { background: var(--primary-hover); }
|
|
364
|
+
.send-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
365
|
+
.send-btn svg { width: 18px; height: 18px; }
|
|
366
|
+
|
|
367
|
+
/* ── Context modal ───────────────────────────────── */
|
|
368
|
+
.modal {
|
|
369
|
+
position: fixed;
|
|
370
|
+
inset: 0;
|
|
371
|
+
background: rgba(0,0,0,0.7);
|
|
372
|
+
display: none;
|
|
373
|
+
align-items: center;
|
|
374
|
+
justify-content: center;
|
|
375
|
+
z-index: 1000;
|
|
376
|
+
}
|
|
377
|
+
.modal.show { display: flex; }
|
|
378
|
+
.modal-content {
|
|
379
|
+
background: var(--bg-surface);
|
|
380
|
+
border: 1px solid var(--border);
|
|
381
|
+
border-radius: var(--radius);
|
|
382
|
+
padding: 24px;
|
|
383
|
+
width: 90%;
|
|
384
|
+
max-width: 400px;
|
|
385
|
+
box-shadow: var(--shadow);
|
|
386
|
+
}
|
|
387
|
+
.modal-header {
|
|
388
|
+
font-size: 16px;
|
|
389
|
+
font-weight: 600;
|
|
390
|
+
margin-bottom: 16px;
|
|
391
|
+
}
|
|
392
|
+
.modal-field {
|
|
393
|
+
margin-bottom: 12px;
|
|
394
|
+
}
|
|
395
|
+
.modal-field label {
|
|
396
|
+
display: block;
|
|
397
|
+
font-size: 12px;
|
|
398
|
+
color: var(--text-muted);
|
|
399
|
+
margin-bottom: 4px;
|
|
400
|
+
}
|
|
401
|
+
.modal-field input {
|
|
402
|
+
width: 100%;
|
|
403
|
+
padding: 8px 10px;
|
|
404
|
+
background: var(--bg-input);
|
|
405
|
+
border: 1px solid var(--border);
|
|
406
|
+
border-radius: var(--radius-sm);
|
|
407
|
+
color: var(--text);
|
|
408
|
+
font-size: 13px;
|
|
409
|
+
font-family: var(--font);
|
|
410
|
+
outline: none;
|
|
411
|
+
}
|
|
412
|
+
.modal-field input:focus { border-color: var(--primary); }
|
|
413
|
+
.modal-actions {
|
|
414
|
+
display: flex;
|
|
415
|
+
gap: 8px;
|
|
416
|
+
justify-content: flex-end;
|
|
417
|
+
margin-top: 16px;
|
|
418
|
+
}
|
|
419
|
+
.modal-btn {
|
|
420
|
+
padding: 8px 16px;
|
|
421
|
+
border-radius: var(--radius-sm);
|
|
422
|
+
font-size: 13px;
|
|
423
|
+
font-weight: 600;
|
|
424
|
+
cursor: pointer;
|
|
425
|
+
transition: all 0.2s;
|
|
426
|
+
border: none;
|
|
427
|
+
}
|
|
428
|
+
.modal-btn.primary {
|
|
429
|
+
background: var(--primary);
|
|
430
|
+
color: #fff;
|
|
431
|
+
}
|
|
432
|
+
.modal-btn.primary:hover { background: var(--primary-hover); }
|
|
433
|
+
.modal-btn.secondary {
|
|
434
|
+
background: transparent;
|
|
435
|
+
color: var(--text-muted);
|
|
436
|
+
border: 1px solid var(--border);
|
|
437
|
+
}
|
|
438
|
+
.modal-btn.secondary:hover {
|
|
439
|
+
border-color: var(--primary);
|
|
440
|
+
color: var(--text);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/* ── Responsive ──────────────────────────────────── */
|
|
444
|
+
@media (max-width: 768px) {
|
|
445
|
+
.message { max-width: 90%; }
|
|
446
|
+
.context-pills { display: none; }
|
|
447
|
+
}
|
|
448
|
+
</style>
|
|
449
|
+
</head>
|
|
450
|
+
<body>
|
|
451
|
+
|
|
452
|
+
<!-- Header -->
|
|
453
|
+
<header class="header">
|
|
454
|
+
<div class="header-left">
|
|
455
|
+
<span class="header-logo">${projectName}</span>
|
|
456
|
+
<span class="header-badge">Playground</span>
|
|
457
|
+
</div>
|
|
458
|
+
<div class="context-pills">
|
|
459
|
+
<div class="pill editable" onclick="editContext()">
|
|
460
|
+
org: <strong id="orgDisplay">${defaultOrgId}</strong>
|
|
461
|
+
</div>
|
|
462
|
+
<div class="pill editable" onclick="editContext()">
|
|
463
|
+
user: <strong id="userDisplay">${defaultUserId}</strong>
|
|
464
|
+
</div>
|
|
465
|
+
<div class="pill editable" onclick="editContext()">
|
|
466
|
+
role: <strong id="roleDisplay">${defaultRole}</strong>
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
</header>
|
|
470
|
+
|
|
471
|
+
<!-- Main area -->
|
|
472
|
+
<div class="main">
|
|
473
|
+
<div class="chat-messages" id="chatMessages">
|
|
474
|
+
<div class="empty-state" id="emptyState">
|
|
475
|
+
<div class="icon">💬</div>
|
|
476
|
+
<h2>Chat Playground</h2>
|
|
477
|
+
<p>Ask questions about your data in natural language. The AI will query your database and return results.</p>
|
|
478
|
+
<div class="suggestion-chips">
|
|
479
|
+
<button class="suggestion-chip" onclick="askSuggestion(this)">How many users do we have?</button>
|
|
480
|
+
<button class="suggestion-chip" onclick="askSuggestion(this)">Show me all products with images</button>
|
|
481
|
+
<button class="suggestion-chip" onclick="askSuggestion(this)">Show orders by status as a pie chart</button>
|
|
482
|
+
<button class="suggestion-chip" onclick="askSuggestion(this)">Bar chart of product prices by category</button>
|
|
483
|
+
<button class="suggestion-chip" onclick="askSuggestion(this)">Show me recent orders</button>
|
|
484
|
+
</div>
|
|
485
|
+
</div>
|
|
486
|
+
</div>
|
|
487
|
+
|
|
488
|
+
<div class="input-area">
|
|
489
|
+
<div class="input-row">
|
|
490
|
+
<div class="input-wrapper">
|
|
491
|
+
<textarea id="messageInput" placeholder="Ask a question about your data…" rows="1"
|
|
492
|
+
onkeydown="handleKeyDown(event)" oninput="autoResize(this)"></textarea>
|
|
493
|
+
</div>
|
|
494
|
+
<button class="send-btn" id="sendBtn" onclick="sendMessage()">
|
|
495
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
496
|
+
<line x1="22" y1="2" x2="11" y2="13"></line>
|
|
497
|
+
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
|
498
|
+
</svg>
|
|
499
|
+
</button>
|
|
500
|
+
</div>
|
|
501
|
+
</div>
|
|
502
|
+
</div>
|
|
503
|
+
|
|
504
|
+
<!-- Context edit modal -->
|
|
505
|
+
<div class="modal" id="contextModal" onclick="if(event.target===this) closeModal()">
|
|
506
|
+
<div class="modal-content">
|
|
507
|
+
<div class="modal-header">Edit User Context</div>
|
|
508
|
+
<div class="modal-field">
|
|
509
|
+
<label for="orgInput">Organization ID</label>
|
|
510
|
+
<input type="text" id="orgInput" value="${defaultOrgId}" />
|
|
511
|
+
</div>
|
|
512
|
+
<div class="modal-field">
|
|
513
|
+
<label for="userInput">User ID</label>
|
|
514
|
+
<input type="text" id="userInput" value="${defaultUserId}" />
|
|
515
|
+
</div>
|
|
516
|
+
<div class="modal-field">
|
|
517
|
+
<label for="roleInput">Role</label>
|
|
518
|
+
<input type="text" id="roleInput" value="${defaultRole}" />
|
|
519
|
+
</div>
|
|
520
|
+
<div class="modal-actions">
|
|
521
|
+
<button class="modal-btn secondary" onclick="closeModal()">Cancel</button>
|
|
522
|
+
<button class="modal-btn primary" onclick="saveContext()">Save</button>
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
</div>
|
|
526
|
+
|
|
527
|
+
<script>
|
|
528
|
+
// ── State ──────────────────────────────────────────
|
|
529
|
+
let sending = false;
|
|
530
|
+
let chartCounter = 0;
|
|
531
|
+
let userContext = {
|
|
532
|
+
userId: '${defaultUserId}',
|
|
533
|
+
orgId: '${defaultOrgId}',
|
|
534
|
+
role: '${defaultRole}',
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const chatEndpoint = '${chatEndpoint}';
|
|
538
|
+
|
|
539
|
+
// Chart.js color palette
|
|
540
|
+
const CHART_COLORS = [
|
|
541
|
+
'#6c63ff', '#4fc3f7', '#66bb6a', '#ff9800', '#ef5350',
|
|
542
|
+
'#ab47bc', '#26c6da', '#ffee58', '#8d6e63', '#78909c',
|
|
543
|
+
'#ec407a', '#7e57c2', '#29b6f6', '#9ccc65', '#ffa726',
|
|
544
|
+
];
|
|
545
|
+
|
|
546
|
+
// ── DOM refs ──────────────────────────────────────
|
|
547
|
+
const chatMessages = document.getElementById('chatMessages');
|
|
548
|
+
const emptyState = document.getElementById('emptyState');
|
|
549
|
+
const messageInput = document.getElementById('messageInput');
|
|
550
|
+
const sendBtn = document.getElementById('sendBtn');
|
|
551
|
+
const contextModal = document.getElementById('contextModal');
|
|
552
|
+
const orgDisplay = document.getElementById('orgDisplay');
|
|
553
|
+
const userDisplay = document.getElementById('userDisplay');
|
|
554
|
+
const roleDisplay = document.getElementById('roleDisplay');
|
|
555
|
+
const orgInput = document.getElementById('orgInput');
|
|
556
|
+
const userInput = document.getElementById('userInput');
|
|
557
|
+
const roleInput = document.getElementById('roleInput');
|
|
558
|
+
|
|
559
|
+
// ── Send message ──────────────────────────────────
|
|
560
|
+
async function sendMessage() {
|
|
561
|
+
const msg = messageInput.value.trim();
|
|
562
|
+
if (!msg || sending) return;
|
|
563
|
+
|
|
564
|
+
sending = true;
|
|
565
|
+
sendBtn.disabled = true;
|
|
566
|
+
messageInput.value = '';
|
|
567
|
+
autoResize(messageInput);
|
|
568
|
+
|
|
569
|
+
hideEmptyState();
|
|
570
|
+
addMessage('user', msg);
|
|
571
|
+
const typingEl = addTyping();
|
|
572
|
+
|
|
573
|
+
try {
|
|
574
|
+
const res = await fetch(chatEndpoint, {
|
|
575
|
+
method: 'POST',
|
|
576
|
+
headers: { 'Content-Type': 'application/json' },
|
|
577
|
+
body: JSON.stringify({ message: msg, userContext }),
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
removeTyping(typingEl);
|
|
581
|
+
|
|
582
|
+
if (!res.ok) {
|
|
583
|
+
const err = await res.json().catch(() => ({ error: 'Request failed' }));
|
|
584
|
+
addMessage('error', err.error || 'Request failed with status ' + res.status);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const result = await res.json();
|
|
589
|
+
addMessage('bot', result.message, result.data, result.visualization);
|
|
590
|
+
} catch (err) {
|
|
591
|
+
removeTyping(typingEl);
|
|
592
|
+
addMessage('error', 'Network error: ' + err.message);
|
|
593
|
+
} finally {
|
|
594
|
+
sending = false;
|
|
595
|
+
sendBtn.disabled = false;
|
|
596
|
+
messageInput.focus();
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ── Image URL detection ──────────────────────────
|
|
601
|
+
const IMAGE_EXTENSIONS = /\\.(jpg|jpeg|png|gif|webp|svg|bmp|ico)(\\?.*)?$/i;
|
|
602
|
+
const IMAGE_SERVICES = /ui-avatars\\.com|picsum\\.photos|placeholder\\.com|placekitten\\.com|placehold\\.co|imgur\\.com|cloudinary\\.com|unsplash\\.com|gravatar\\.com/i;
|
|
603
|
+
|
|
604
|
+
function isImageUrl(value) {
|
|
605
|
+
if (typeof value !== 'string') return false;
|
|
606
|
+
if (!value.startsWith('http://') && !value.startsWith('https://')) return false;
|
|
607
|
+
return IMAGE_EXTENSIONS.test(value) || IMAGE_SERVICES.test(value);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function isAvatarField(fieldName) {
|
|
611
|
+
const lower = (fieldName || '').toLowerCase();
|
|
612
|
+
return lower.includes('avatar') || lower.includes('profile_pic') || lower.includes('profile_image');
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// ── UI helpers ────────────────────────────────────
|
|
616
|
+
function addMessage(role, text, data, visualization) {
|
|
617
|
+
const div = document.createElement('div');
|
|
618
|
+
div.className = 'message ' + role;
|
|
619
|
+
|
|
620
|
+
let senderLabel = '';
|
|
621
|
+
if (role === 'user') senderLabel = 'You';
|
|
622
|
+
else if (role === 'bot') senderLabel = 'AI';
|
|
623
|
+
|
|
624
|
+
let html = '';
|
|
625
|
+
if (senderLabel) html += '<div class="message-sender">' + senderLabel + '</div>';
|
|
626
|
+
html += '<div>' + formatMessageText(text) + '</div>';
|
|
627
|
+
|
|
628
|
+
if (data) {
|
|
629
|
+
html += renderData(data);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (visualization && visualization.type && visualization.labels && visualization.datasets) {
|
|
633
|
+
html += renderChartContainer(visualization);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
div.innerHTML = html;
|
|
637
|
+
chatMessages.appendChild(div);
|
|
638
|
+
scrollToBottom();
|
|
639
|
+
|
|
640
|
+
// Render chart after DOM insertion
|
|
641
|
+
if (visualization && visualization.type && visualization.labels && visualization.datasets) {
|
|
642
|
+
renderChart(visualization);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function addTyping() {
|
|
647
|
+
const div = document.createElement('div');
|
|
648
|
+
div.className = 'typing';
|
|
649
|
+
div.innerHTML = '<span></span><span></span><span></span>';
|
|
650
|
+
chatMessages.appendChild(div);
|
|
651
|
+
scrollToBottom();
|
|
652
|
+
return div;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function removeTyping(el) {
|
|
656
|
+
if (el && el.parentNode) el.parentNode.removeChild(el);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function hideEmptyState() {
|
|
660
|
+
if (emptyState) emptyState.style.display = 'none';
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function scrollToBottom() {
|
|
664
|
+
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function formatMessageText(text) {
|
|
668
|
+
if (!text) return '';
|
|
669
|
+
return escapeHtml(text)
|
|
670
|
+
.replace(/\\n/g, '<br>')
|
|
671
|
+
.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')
|
|
672
|
+
.replace(/\`(.+?)\`/g, '<code style="background:var(--bg-input);padding:2px 5px;border-radius:4px;font-family:var(--mono);font-size:12px;">$1</code>');
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function renderData(data) {
|
|
676
|
+
if (!data) return '';
|
|
677
|
+
|
|
678
|
+
// Handle { rows: [...], rowCount, executionTimeMs } format
|
|
679
|
+
let rows = data;
|
|
680
|
+
let meta = null;
|
|
681
|
+
if (data && typeof data === 'object' && !Array.isArray(data) && Array.isArray(data.rows)) {
|
|
682
|
+
rows = data.rows;
|
|
683
|
+
meta = { rowCount: data.rowCount, executionTimeMs: data.executionTimeMs };
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (Array.isArray(rows) && rows.length > 0 && typeof rows[0] === 'object') {
|
|
687
|
+
const keys = Object.keys(rows[0]);
|
|
688
|
+
let html = '<div class="message-data"><table>';
|
|
689
|
+
html += '<tr>' + keys.map(k => '<th>' + escapeHtml(String(k)) + '</th>').join('') + '</tr>';
|
|
690
|
+
rows.slice(0, 50).forEach(row => {
|
|
691
|
+
html += '<tr>' + keys.map(k => {
|
|
692
|
+
const val = row[k];
|
|
693
|
+
if (isImageUrl(val)) {
|
|
694
|
+
const roundClass = isAvatarField(k) ? ' cell-image-round' : '';
|
|
695
|
+
return '<td><img class="cell-image' + roundClass + '" src="' + escapeHtml(String(val)) + '" alt="' + escapeHtml(String(k)) + '" loading="lazy" onerror="this.style.display=\\'none\\'" /></td>';
|
|
696
|
+
}
|
|
697
|
+
return '<td>' + escapeHtml(String(val ?? '')) + '</td>';
|
|
698
|
+
}).join('') + '</tr>';
|
|
699
|
+
});
|
|
700
|
+
if (rows.length > 50) {
|
|
701
|
+
html += '<tr><td colspan="' + keys.length + '" style="color:var(--text-muted);text-align:center;">… and ' + (rows.length - 50) + ' more rows</td></tr>';
|
|
702
|
+
}
|
|
703
|
+
html += '</table>';
|
|
704
|
+
if (meta) {
|
|
705
|
+
html += '<div style="margin-top:6px;font-size:10px;color:var(--text-muted);">' + meta.rowCount + ' row(s) in ' + (meta.executionTimeMs || 0) + 'ms</div>';
|
|
706
|
+
}
|
|
707
|
+
html += '</div>';
|
|
708
|
+
return html;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
return '<div class="message-data"><div class="data-json">' + escapeHtml(JSON.stringify(rows, null, 2)) + '</div></div>';
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// ── Chart rendering ──────────────────────────────
|
|
715
|
+
function renderChartContainer(viz) {
|
|
716
|
+
chartCounter++;
|
|
717
|
+
const id = 'chart-' + chartCounter;
|
|
718
|
+
let html = '<div class="chart-container">';
|
|
719
|
+
if (viz.title) {
|
|
720
|
+
html += '<div class="chart-title">' + escapeHtml(viz.title) + '</div>';
|
|
721
|
+
}
|
|
722
|
+
html += '<canvas id="' + id + '"></canvas>';
|
|
723
|
+
html += '</div>';
|
|
724
|
+
return html;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function renderChart(viz) {
|
|
728
|
+
const id = 'chart-' + chartCounter;
|
|
729
|
+
const canvas = document.getElementById(id);
|
|
730
|
+
if (!canvas) return;
|
|
731
|
+
|
|
732
|
+
const isPie = viz.type === 'pie' || viz.type === 'doughnut';
|
|
733
|
+
|
|
734
|
+
const datasets = viz.datasets.map(function(ds, i) {
|
|
735
|
+
const colors = viz.labels.map(function(_, j) {
|
|
736
|
+
return CHART_COLORS[j % CHART_COLORS.length];
|
|
737
|
+
});
|
|
738
|
+
const cfg = {
|
|
739
|
+
label: ds.label || '',
|
|
740
|
+
data: ds.data,
|
|
741
|
+
};
|
|
742
|
+
if (isPie) {
|
|
743
|
+
cfg.backgroundColor = colors;
|
|
744
|
+
cfg.borderColor = 'rgba(0,0,0,0.2)';
|
|
745
|
+
cfg.borderWidth = 1;
|
|
746
|
+
} else {
|
|
747
|
+
cfg.backgroundColor = CHART_COLORS[i % CHART_COLORS.length] + 'cc';
|
|
748
|
+
cfg.borderColor = CHART_COLORS[i % CHART_COLORS.length];
|
|
749
|
+
cfg.borderWidth = 2;
|
|
750
|
+
cfg.borderRadius = 4;
|
|
751
|
+
}
|
|
752
|
+
return cfg;
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
new Chart(canvas, {
|
|
756
|
+
type: viz.type,
|
|
757
|
+
data: {
|
|
758
|
+
labels: viz.labels,
|
|
759
|
+
datasets: datasets,
|
|
760
|
+
},
|
|
761
|
+
options: {
|
|
762
|
+
responsive: true,
|
|
763
|
+
maintainAspectRatio: true,
|
|
764
|
+
plugins: {
|
|
765
|
+
legend: {
|
|
766
|
+
display: isPie || viz.datasets.length > 1,
|
|
767
|
+
position: isPie ? 'right' : 'top',
|
|
768
|
+
labels: {
|
|
769
|
+
color: '#8b8fa3',
|
|
770
|
+
font: { size: 11 },
|
|
771
|
+
padding: 12,
|
|
772
|
+
},
|
|
773
|
+
},
|
|
774
|
+
},
|
|
775
|
+
scales: isPie ? {} : {
|
|
776
|
+
x: {
|
|
777
|
+
ticks: { color: '#8b8fa3', font: { size: 11 } },
|
|
778
|
+
grid: { color: 'rgba(46,49,60,0.5)' },
|
|
779
|
+
},
|
|
780
|
+
y: {
|
|
781
|
+
ticks: { color: '#8b8fa3', font: { size: 11 } },
|
|
782
|
+
grid: { color: 'rgba(46,49,60,0.5)' },
|
|
783
|
+
beginAtZero: true,
|
|
784
|
+
},
|
|
785
|
+
},
|
|
786
|
+
},
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
setTimeout(function() { scrollToBottom(); }, 100);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function escapeHtml(str) {
|
|
793
|
+
const div = document.createElement('div');
|
|
794
|
+
div.textContent = str;
|
|
795
|
+
return div.innerHTML;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function askSuggestion(el) {
|
|
799
|
+
messageInput.value = el.textContent;
|
|
800
|
+
autoResize(messageInput);
|
|
801
|
+
sendMessage();
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function handleKeyDown(e) {
|
|
805
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
806
|
+
e.preventDefault();
|
|
807
|
+
sendMessage();
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function autoResize(el) {
|
|
812
|
+
el.style.height = 'auto';
|
|
813
|
+
el.style.height = Math.min(el.scrollHeight, 120) + 'px';
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// ── Context editing ───────────────────────────────
|
|
817
|
+
function editContext() {
|
|
818
|
+
orgInput.value = userContext.orgId;
|
|
819
|
+
userInput.value = userContext.userId;
|
|
820
|
+
roleInput.value = userContext.role;
|
|
821
|
+
contextModal.classList.add('show');
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function closeModal() {
|
|
825
|
+
contextModal.classList.remove('show');
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function saveContext() {
|
|
829
|
+
userContext.orgId = orgInput.value.trim();
|
|
830
|
+
userContext.userId = userInput.value.trim();
|
|
831
|
+
userContext.role = roleInput.value.trim();
|
|
832
|
+
|
|
833
|
+
orgDisplay.textContent = userContext.orgId;
|
|
834
|
+
userDisplay.textContent = userContext.userId;
|
|
835
|
+
roleDisplay.textContent = userContext.role;
|
|
836
|
+
|
|
837
|
+
closeModal();
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Focus input on load
|
|
841
|
+
messageInput.focus();
|
|
842
|
+
</script>
|
|
843
|
+
</body>
|
|
844
|
+
</html>`;
|
|
845
|
+
}
|
|
846
|
+
//# sourceMappingURL=playground.js.map
|