azurefunctions-agents-runtime 0.0.0.dev1__py3-none-any.whl
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.
- azure_functions_agents/__init__.py +20 -0
- azure_functions_agents/app.py +720 -0
- azure_functions_agents/arm.py +95 -0
- azure_functions_agents/client_manager.py +84 -0
- azure_functions_agents/config.py +191 -0
- azure_functions_agents/connector_tool_cache.py +124 -0
- azure_functions_agents/connector_tools.py +267 -0
- azure_functions_agents/connectors.py +460 -0
- azure_functions_agents/mcp.py +87 -0
- azure_functions_agents/public/index.html +1504 -0
- azure_functions_agents/runner.py +406 -0
- azure_functions_agents/sandbox.py +288 -0
- azure_functions_agents/skills.py +24 -0
- azure_functions_agents/tools.py +316 -0
- azurefunctions_agents_runtime-0.0.0.dev1.dist-info/METADATA +386 -0
- azurefunctions_agents_runtime-0.0.0.dev1.dist-info/RECORD +20 -0
- azurefunctions_agents_runtime-0.0.0.dev1.dist-info/WHEEL +5 -0
- azurefunctions_agents_runtime-0.0.0.dev1.dist-info/licenses/LICENSE.md +21 -0
- azurefunctions_agents_runtime-0.0.0.dev1.dist-info/top_level.txt +2 -0
- copilot_functions/__init__.py +3 -0
|
@@ -0,0 +1,1504 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Copilot Chat</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #f5f7fb;
|
|
10
|
+
--panel: #ffffff;
|
|
11
|
+
--border: #d8dce8;
|
|
12
|
+
--text: #111827;
|
|
13
|
+
--muted: #6b7280;
|
|
14
|
+
--you: #2563eb;
|
|
15
|
+
--copilot: #e8eefc;
|
|
16
|
+
--danger: #b91c1c;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
* { box-sizing: border-box; }
|
|
20
|
+
|
|
21
|
+
body {
|
|
22
|
+
margin: 0;
|
|
23
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
24
|
+
color: var(--text);
|
|
25
|
+
background: var(--bg);
|
|
26
|
+
min-height: 100vh;
|
|
27
|
+
display: flex;
|
|
28
|
+
justify-content: center;
|
|
29
|
+
padding: 16px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.app {
|
|
33
|
+
width: min(900px, 100%);
|
|
34
|
+
height: calc(100vh - 32px);
|
|
35
|
+
background: var(--panel);
|
|
36
|
+
border: 1px solid var(--border);
|
|
37
|
+
border-radius: 14px;
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.topbar {
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: space-between;
|
|
47
|
+
padding: 10px 14px;
|
|
48
|
+
border-bottom: 1px solid var(--border);
|
|
49
|
+
background: #fff;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.topbar-title {
|
|
53
|
+
font-size: 14px;
|
|
54
|
+
font-weight: 600;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.topbar-actions {
|
|
58
|
+
display: flex;
|
|
59
|
+
gap: 8px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
button {
|
|
63
|
+
border: 1px solid var(--border);
|
|
64
|
+
background: white;
|
|
65
|
+
color: var(--text);
|
|
66
|
+
border-radius: 8px;
|
|
67
|
+
padding: 8px 10px;
|
|
68
|
+
cursor: pointer;
|
|
69
|
+
font-size: 13px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
button:hover { background: #f9fafb; }
|
|
73
|
+
button:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
74
|
+
|
|
75
|
+
.gear-btn {
|
|
76
|
+
width: 36px;
|
|
77
|
+
height: 36px;
|
|
78
|
+
display: grid;
|
|
79
|
+
place-items: center;
|
|
80
|
+
padding: 0;
|
|
81
|
+
font-size: 16px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.chat {
|
|
85
|
+
flex: 1;
|
|
86
|
+
overflow: auto;
|
|
87
|
+
padding: 14px;
|
|
88
|
+
display: flex;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
gap: 10px;
|
|
91
|
+
background: linear-gradient(180deg, #ffffff, #f8faff 70%);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.bubble-wrap {
|
|
95
|
+
display: flex;
|
|
96
|
+
width: 100%;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.bubble-wrap.you { justify-content: flex-end; }
|
|
100
|
+
.bubble-wrap.copilot { justify-content: flex-start; }
|
|
101
|
+
|
|
102
|
+
.bubble {
|
|
103
|
+
max-width: min(75ch, 85%);
|
|
104
|
+
padding: 10px 12px;
|
|
105
|
+
border-radius: 12px;
|
|
106
|
+
line-height: 1.4;
|
|
107
|
+
font-size: 14px;
|
|
108
|
+
white-space: pre-wrap;
|
|
109
|
+
word-break: break-word;
|
|
110
|
+
border: 1px solid var(--border);
|
|
111
|
+
box-shadow: 0 1px 1px rgba(15, 23, 42, 0.03);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.bubble p {
|
|
115
|
+
margin: 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.bubble p + p,
|
|
119
|
+
.bubble p + ul,
|
|
120
|
+
.bubble p + ol,
|
|
121
|
+
.bubble ul + p,
|
|
122
|
+
.bubble ol + p,
|
|
123
|
+
.bubble ul + ul,
|
|
124
|
+
.bubble ol + ol,
|
|
125
|
+
.bubble pre + p,
|
|
126
|
+
.bubble p + pre,
|
|
127
|
+
.bubble pre + ul,
|
|
128
|
+
.bubble pre + ol {
|
|
129
|
+
margin-top: 8px;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.bubble ul,
|
|
133
|
+
.bubble ol {
|
|
134
|
+
margin: 0;
|
|
135
|
+
padding-left: 22px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.bubble li + li {
|
|
139
|
+
margin-top: 4px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.bubble-intermediates {
|
|
143
|
+
display: none;
|
|
144
|
+
margin-top: 8px;
|
|
145
|
+
padding-top: 8px;
|
|
146
|
+
border-top: 1px dashed var(--border);
|
|
147
|
+
font-size: 12px;
|
|
148
|
+
color: var(--muted);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.bubble-intermediates.open {
|
|
152
|
+
display: block;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.bubble-intermediates-title {
|
|
156
|
+
font-weight: 600;
|
|
157
|
+
margin-bottom: 4px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.bubble-intermediates-text {
|
|
161
|
+
white-space: pre-wrap;
|
|
162
|
+
word-break: break-word;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.bubble a {
|
|
166
|
+
color: inherit;
|
|
167
|
+
text-decoration: underline;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.bubble .md-inline-code {
|
|
171
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
172
|
+
font-size: 0.92em;
|
|
173
|
+
padding: 1px 5px;
|
|
174
|
+
border-radius: 6px;
|
|
175
|
+
background: rgba(15, 23, 42, 0.1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.bubble .md-pre {
|
|
179
|
+
margin: 0;
|
|
180
|
+
padding: 9px 10px;
|
|
181
|
+
border-radius: 10px;
|
|
182
|
+
border: 1px solid var(--border);
|
|
183
|
+
background: #f8fafc;
|
|
184
|
+
overflow-x: auto;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.bubble .md-pre code {
|
|
188
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
189
|
+
font-size: 12px;
|
|
190
|
+
line-height: 1.45;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.bubble .md-table-wrap {
|
|
194
|
+
overflow-x: auto;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.bubble .md-table {
|
|
198
|
+
width: 100%;
|
|
199
|
+
border-collapse: collapse;
|
|
200
|
+
font-size: 13px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.bubble .md-table th,
|
|
204
|
+
.bubble .md-table td {
|
|
205
|
+
border: 1px solid var(--border);
|
|
206
|
+
padding: 6px 8px;
|
|
207
|
+
text-align: left;
|
|
208
|
+
vertical-align: top;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.bubble .md-table thead th {
|
|
212
|
+
background: #f8fafc;
|
|
213
|
+
font-weight: 600;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.bubble.you {
|
|
217
|
+
background: var(--you);
|
|
218
|
+
color: #fff;
|
|
219
|
+
border-color: #1d4ed8;
|
|
220
|
+
border-bottom-right-radius: 4px;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.bubble.copilot {
|
|
224
|
+
background: var(--copilot);
|
|
225
|
+
border-bottom-left-radius: 4px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.bubble.copilot.has-details {
|
|
229
|
+
cursor: pointer;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.bubble.copilot.has-details:hover {
|
|
233
|
+
background: #dce7ff;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.meta {
|
|
237
|
+
margin-top: 4px;
|
|
238
|
+
font-size: 11px;
|
|
239
|
+
color: var(--muted);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.bubble.you .meta {
|
|
243
|
+
color: rgba(255, 255, 255, 0.92);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.composer {
|
|
247
|
+
border-top: 1px solid var(--border);
|
|
248
|
+
background: #fff;
|
|
249
|
+
padding: 10px;
|
|
250
|
+
display: grid;
|
|
251
|
+
grid-template-columns: 1fr auto;
|
|
252
|
+
gap: 8px;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
textarea {
|
|
256
|
+
width: 100%;
|
|
257
|
+
min-height: 42px;
|
|
258
|
+
max-height: 160px;
|
|
259
|
+
resize: vertical;
|
|
260
|
+
border: 1px solid var(--border);
|
|
261
|
+
border-radius: 10px;
|
|
262
|
+
padding: 10px;
|
|
263
|
+
font-size: 14px;
|
|
264
|
+
font-family: inherit;
|
|
265
|
+
outline: none;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
textarea:focus { border-color: #93c5fd; }
|
|
269
|
+
|
|
270
|
+
.send-btn {
|
|
271
|
+
min-width: 88px;
|
|
272
|
+
background: #111827;
|
|
273
|
+
color: white;
|
|
274
|
+
border-color: #111827;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.send-btn:hover { background: #1f2937; }
|
|
278
|
+
|
|
279
|
+
.status {
|
|
280
|
+
padding: 0 12px 10px 12px;
|
|
281
|
+
font-size: 12px;
|
|
282
|
+
color: var(--muted);
|
|
283
|
+
min-height: 20px;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.status.error { color: var(--danger); }
|
|
287
|
+
|
|
288
|
+
.typing {
|
|
289
|
+
display: inline-flex;
|
|
290
|
+
gap: 4px;
|
|
291
|
+
align-items: center;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.typing-dot {
|
|
295
|
+
width: 6px;
|
|
296
|
+
height: 6px;
|
|
297
|
+
border-radius: 50%;
|
|
298
|
+
background: #64748b;
|
|
299
|
+
animation: pulse 1.2s infinite;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
|
|
303
|
+
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
|
|
304
|
+
|
|
305
|
+
@keyframes pulse {
|
|
306
|
+
0%, 80%, 100% { transform: translateY(0); opacity: 0.35; }
|
|
307
|
+
40% { transform: translateY(-2px); opacity: 1; }
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.settings-backdrop {
|
|
311
|
+
position: fixed;
|
|
312
|
+
inset: 0;
|
|
313
|
+
background: rgba(17, 24, 39, 0.45);
|
|
314
|
+
display: none;
|
|
315
|
+
align-items: center;
|
|
316
|
+
justify-content: center;
|
|
317
|
+
padding: 16px;
|
|
318
|
+
z-index: 10;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.settings-backdrop.open { display: flex; }
|
|
322
|
+
|
|
323
|
+
.settings-modal {
|
|
324
|
+
width: min(500px, 100%);
|
|
325
|
+
background: #fff;
|
|
326
|
+
border-radius: 14px;
|
|
327
|
+
border: 1px solid var(--border);
|
|
328
|
+
padding: 14px;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.settings-modal h2 {
|
|
332
|
+
margin: 0 0 8px;
|
|
333
|
+
font-size: 16px;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.settings-help {
|
|
337
|
+
margin: 0 0 12px;
|
|
338
|
+
font-size: 12px;
|
|
339
|
+
color: var(--muted);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.field {
|
|
343
|
+
display: grid;
|
|
344
|
+
gap: 6px;
|
|
345
|
+
margin-bottom: 10px;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
label { font-size: 12px; color: var(--muted); }
|
|
349
|
+
|
|
350
|
+
input {
|
|
351
|
+
width: 100%;
|
|
352
|
+
border: 1px solid var(--border);
|
|
353
|
+
border-radius: 10px;
|
|
354
|
+
padding: 9px 10px;
|
|
355
|
+
font-size: 14px;
|
|
356
|
+
outline: none;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
input:focus { border-color: #93c5fd; }
|
|
360
|
+
|
|
361
|
+
.settings-actions {
|
|
362
|
+
display: flex;
|
|
363
|
+
justify-content: flex-end;
|
|
364
|
+
gap: 8px;
|
|
365
|
+
margin-top: 4px;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.details-backdrop {
|
|
369
|
+
position: fixed;
|
|
370
|
+
inset: 0;
|
|
371
|
+
background: rgba(17, 24, 39, 0.45);
|
|
372
|
+
display: none;
|
|
373
|
+
align-items: center;
|
|
374
|
+
justify-content: center;
|
|
375
|
+
padding: 16px;
|
|
376
|
+
z-index: 11;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.details-backdrop.open { display: flex; }
|
|
380
|
+
|
|
381
|
+
.details-modal {
|
|
382
|
+
width: min(900px, 100%);
|
|
383
|
+
max-height: 85vh;
|
|
384
|
+
background: #fff;
|
|
385
|
+
border-radius: 14px;
|
|
386
|
+
border: 1px solid var(--border);
|
|
387
|
+
display: flex;
|
|
388
|
+
flex-direction: column;
|
|
389
|
+
overflow: hidden;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.details-header {
|
|
393
|
+
display: flex;
|
|
394
|
+
align-items: center;
|
|
395
|
+
justify-content: space-between;
|
|
396
|
+
padding: 12px 14px;
|
|
397
|
+
border-bottom: 1px solid var(--border);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.details-title {
|
|
401
|
+
margin: 0;
|
|
402
|
+
font-size: 15px;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.details-content {
|
|
406
|
+
overflow: auto;
|
|
407
|
+
padding: 12px 14px;
|
|
408
|
+
display: grid;
|
|
409
|
+
gap: 14px;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.details-section h3 {
|
|
413
|
+
margin: 0 0 8px;
|
|
414
|
+
font-size: 14px;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.details-item {
|
|
418
|
+
border: 1px solid var(--border);
|
|
419
|
+
border-radius: 10px;
|
|
420
|
+
padding: 9px 10px;
|
|
421
|
+
background: #f8fafc;
|
|
422
|
+
font-size: 13px;
|
|
423
|
+
line-height: 1.45;
|
|
424
|
+
white-space: pre-wrap;
|
|
425
|
+
word-break: break-word;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.details-item + .details-item { margin-top: 8px; }
|
|
429
|
+
|
|
430
|
+
.details-item-title {
|
|
431
|
+
font-size: 12px;
|
|
432
|
+
color: var(--muted);
|
|
433
|
+
margin-bottom: 6px;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.details-pre {
|
|
437
|
+
margin: 0;
|
|
438
|
+
font-size: 12px;
|
|
439
|
+
line-height: 1.35;
|
|
440
|
+
white-space: pre-wrap;
|
|
441
|
+
word-break: break-word;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.details-empty {
|
|
445
|
+
font-size: 12px;
|
|
446
|
+
color: var(--muted);
|
|
447
|
+
}
|
|
448
|
+
</style>
|
|
449
|
+
</head>
|
|
450
|
+
<body>
|
|
451
|
+
<main class="app">
|
|
452
|
+
<header class="topbar">
|
|
453
|
+
<div class="topbar-title">Copilot Chat</div>
|
|
454
|
+
<div class="topbar-actions">
|
|
455
|
+
<button id="newSessionBtn" type="button">New session</button>
|
|
456
|
+
<button id="settingsBtn" class="gear-btn" type="button" aria-label="Settings">⚙️</button>
|
|
457
|
+
</div>
|
|
458
|
+
</header>
|
|
459
|
+
|
|
460
|
+
<section id="chat" class="chat" aria-live="polite"></section>
|
|
461
|
+
|
|
462
|
+
<form id="composer" class="composer">
|
|
463
|
+
<textarea id="promptInput" placeholder="Type your message..." required></textarea>
|
|
464
|
+
<button id="sendBtn" class="send-btn" type="submit">Send</button>
|
|
465
|
+
</form>
|
|
466
|
+
|
|
467
|
+
<div id="status" class="status"></div>
|
|
468
|
+
</main>
|
|
469
|
+
|
|
470
|
+
<div id="settingsBackdrop" class="settings-backdrop" role="dialog" aria-modal="true" aria-labelledby="settingsTitle">
|
|
471
|
+
<section class="settings-modal">
|
|
472
|
+
<h2 id="settingsTitle">Connection settings</h2>
|
|
473
|
+
<p class="settings-help">These are saved locally in your browser and can be edited any time from the gear icon.</p>
|
|
474
|
+
<div class="field">
|
|
475
|
+
<label for="baseUrlInput">Base URL</label>
|
|
476
|
+
<input id="baseUrlInput" type="url" placeholder="https://your-function-app.azurewebsites.net" />
|
|
477
|
+
</div>
|
|
478
|
+
<div class="field">
|
|
479
|
+
<label for="keyInput">Function key</label>
|
|
480
|
+
<input id="keyInput" type="password" placeholder="Enter your key (optional on localhost)" />
|
|
481
|
+
</div>
|
|
482
|
+
<div class="settings-actions">
|
|
483
|
+
<button id="cancelSettingsBtn" type="button">Cancel</button>
|
|
484
|
+
<button id="saveSettingsBtn" type="button">Save</button>
|
|
485
|
+
</div>
|
|
486
|
+
</section>
|
|
487
|
+
</div>
|
|
488
|
+
|
|
489
|
+
<div id="detailsBackdrop" class="details-backdrop" role="dialog" aria-modal="true" aria-labelledby="detailsTitle">
|
|
490
|
+
<section class="details-modal">
|
|
491
|
+
<header class="details-header">
|
|
492
|
+
<h2 id="detailsTitle" class="details-title">Copilot details</h2>
|
|
493
|
+
<button id="closeDetailsBtn" type="button">Close</button>
|
|
494
|
+
</header>
|
|
495
|
+
<div id="detailsContent" class="details-content"></div>
|
|
496
|
+
</section>
|
|
497
|
+
</div>
|
|
498
|
+
|
|
499
|
+
<script>
|
|
500
|
+
const STORAGE_KEYS = {
|
|
501
|
+
baseUrl: "chat.baseUrl",
|
|
502
|
+
key: "chat.key"
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
const chatEl = document.getElementById("chat");
|
|
506
|
+
const composerEl = document.getElementById("composer");
|
|
507
|
+
const promptInputEl = document.getElementById("promptInput");
|
|
508
|
+
const sendBtnEl = document.getElementById("sendBtn");
|
|
509
|
+
const statusEl = document.getElementById("status");
|
|
510
|
+
const newSessionBtnEl = document.getElementById("newSessionBtn");
|
|
511
|
+
const settingsBtnEl = document.getElementById("settingsBtn");
|
|
512
|
+
|
|
513
|
+
const settingsBackdropEl = document.getElementById("settingsBackdrop");
|
|
514
|
+
const baseUrlInputEl = document.getElementById("baseUrlInput");
|
|
515
|
+
const keyInputEl = document.getElementById("keyInput");
|
|
516
|
+
const saveSettingsBtnEl = document.getElementById("saveSettingsBtn");
|
|
517
|
+
const cancelSettingsBtnEl = document.getElementById("cancelSettingsBtn");
|
|
518
|
+
const detailsBackdropEl = document.getElementById("detailsBackdrop");
|
|
519
|
+
const detailsContentEl = document.getElementById("detailsContent");
|
|
520
|
+
const closeDetailsBtnEl = document.getElementById("closeDetailsBtn");
|
|
521
|
+
let currentDetailsSource = null;
|
|
522
|
+
|
|
523
|
+
const defaultBaseUrl = `${location.protocol}//${location.host}`;
|
|
524
|
+
|
|
525
|
+
const state = {
|
|
526
|
+
baseUrl: "",
|
|
527
|
+
key: "",
|
|
528
|
+
sessionId: null,
|
|
529
|
+
loading: false,
|
|
530
|
+
loadingTimer: null,
|
|
531
|
+
loadingSeconds: 0
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
function escapeHtml(value) {
|
|
535
|
+
return value
|
|
536
|
+
.replaceAll("&", "&")
|
|
537
|
+
.replaceAll("<", "<")
|
|
538
|
+
.replaceAll(">", ">")
|
|
539
|
+
.replaceAll('"', """)
|
|
540
|
+
.replaceAll("'", "'");
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function renderInlineMarkdown(text) {
|
|
544
|
+
let content = text;
|
|
545
|
+
content = content.replace(/`([^`]+)`/g, '<code class="md-inline-code">$1</code>');
|
|
546
|
+
content = content.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
|
547
|
+
content = content.replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
548
|
+
content = content.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
549
|
+
return content;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function renderMarkdownBasic(markdown) {
|
|
553
|
+
const safeMarkdown = escapeHtml(String(markdown || "")).replace(/\r\n?/g, "\n");
|
|
554
|
+
const codeBlocks = [];
|
|
555
|
+
|
|
556
|
+
const withPlaceholders = safeMarkdown.replace(/```[^\n]*\n?([\s\S]*?)```/g, (_match, code) => {
|
|
557
|
+
const token = `@@CODEBLOCK_${codeBlocks.length}@@`;
|
|
558
|
+
codeBlocks.push(`<pre class="md-pre"><code>${code.trimEnd()}</code></pre>`);
|
|
559
|
+
return token;
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
const lines = withPlaceholders.split("\n");
|
|
563
|
+
const html = [];
|
|
564
|
+
let inUnorderedList = false;
|
|
565
|
+
let inOrderedList = false;
|
|
566
|
+
|
|
567
|
+
const splitTableRow = (line) => {
|
|
568
|
+
const normalized = line.trim().replace(/^\|/, "").replace(/\|$/, "");
|
|
569
|
+
if (!normalized.includes("|")) return null;
|
|
570
|
+
return normalized.split("|").map((cell) => cell.trim());
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
const isTableSeparator = (line) => {
|
|
574
|
+
const cells = splitTableRow(line);
|
|
575
|
+
if (!cells || cells.length < 2) return false;
|
|
576
|
+
return cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
const renderTable = (headerCells, bodyRows) => {
|
|
580
|
+
const thead = `<thead><tr>${headerCells.map((cell) => `<th>${renderInlineMarkdown(cell)}</th>`).join("")}</tr></thead>`;
|
|
581
|
+
const tbodyRows = bodyRows
|
|
582
|
+
.map((row) => `<tr>${row.map((cell) => `<td>${renderInlineMarkdown(cell)}</td>`).join("")}</tr>`)
|
|
583
|
+
.join("");
|
|
584
|
+
return `<div class="md-table-wrap"><table class="md-table">${thead}<tbody>${tbodyRows}</tbody></table></div>`;
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
const closeLists = () => {
|
|
588
|
+
if (inUnorderedList) {
|
|
589
|
+
html.push("</ul>");
|
|
590
|
+
inUnorderedList = false;
|
|
591
|
+
}
|
|
592
|
+
if (inOrderedList) {
|
|
593
|
+
html.push("</ol>");
|
|
594
|
+
inOrderedList = false;
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
599
|
+
const rawLine = lines[i];
|
|
600
|
+
const line = rawLine.trim();
|
|
601
|
+
|
|
602
|
+
if (!line) {
|
|
603
|
+
closeLists();
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (/^@@CODEBLOCK_\d+@@$/.test(line)) {
|
|
608
|
+
closeLists();
|
|
609
|
+
html.push(line);
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
614
|
+
if (headingMatch) {
|
|
615
|
+
closeLists();
|
|
616
|
+
const level = headingMatch[1].length;
|
|
617
|
+
html.push(`<h${level}>${renderInlineMarkdown(headingMatch[2])}</h${level}>`);
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const nextLine = (lines[i + 1] || "").trim();
|
|
622
|
+
const headerCells = splitTableRow(line);
|
|
623
|
+
if (headerCells && isTableSeparator(nextLine) && headerCells.length >= 2) {
|
|
624
|
+
closeLists();
|
|
625
|
+
const bodyRows = [];
|
|
626
|
+
i += 2;
|
|
627
|
+
for (; i < lines.length; i += 1) {
|
|
628
|
+
const rowLine = lines[i].trim();
|
|
629
|
+
if (!rowLine) {
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
const rowCells = splitTableRow(rowLine);
|
|
633
|
+
if (!rowCells) {
|
|
634
|
+
i -= 1;
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
bodyRows.push(rowCells);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
html.push(renderTable(headerCells, bodyRows));
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const unorderedMatch = line.match(/^[-*]\s+(.+)$/);
|
|
645
|
+
if (unorderedMatch) {
|
|
646
|
+
if (inOrderedList) {
|
|
647
|
+
html.push("</ol>");
|
|
648
|
+
inOrderedList = false;
|
|
649
|
+
}
|
|
650
|
+
if (!inUnorderedList) {
|
|
651
|
+
html.push("<ul>");
|
|
652
|
+
inUnorderedList = true;
|
|
653
|
+
}
|
|
654
|
+
html.push(`<li>${renderInlineMarkdown(unorderedMatch[1])}</li>`);
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const orderedMatch = line.match(/^\d+\.\s+(.+)$/);
|
|
659
|
+
if (orderedMatch) {
|
|
660
|
+
if (inUnorderedList) {
|
|
661
|
+
html.push("</ul>");
|
|
662
|
+
inUnorderedList = false;
|
|
663
|
+
}
|
|
664
|
+
if (!inOrderedList) {
|
|
665
|
+
html.push("<ol>");
|
|
666
|
+
inOrderedList = true;
|
|
667
|
+
}
|
|
668
|
+
html.push(`<li>${renderInlineMarkdown(orderedMatch[1])}</li>`);
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
closeLists();
|
|
673
|
+
html.push(`<p>${renderInlineMarkdown(line)}</p>`);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
closeLists();
|
|
677
|
+
|
|
678
|
+
const rendered = html.join("").replace(/@@CODEBLOCK_(\d+)@@/g, (_match, index) => {
|
|
679
|
+
return codeBlocks[Number(index)] || "";
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
return rendered || "<p></p>";
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function setStatus(message = "", isError = false) {
|
|
686
|
+
statusEl.textContent = message;
|
|
687
|
+
statusEl.classList.toggle("error", Boolean(isError));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function scrollChatToBottom() {
|
|
691
|
+
chatEl.scrollTop = chatEl.scrollHeight;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function scrollToBubbleTop(bubbleWrapEl) {
|
|
695
|
+
if (!bubbleWrapEl) return;
|
|
696
|
+
const topOffset = bubbleWrapEl.offsetTop - chatEl.offsetTop;
|
|
697
|
+
chatEl.scrollTop = Math.max(0, topOffset - 8);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function renderDetailsSection(title) {
|
|
701
|
+
const section = document.createElement("section");
|
|
702
|
+
section.className = "details-section";
|
|
703
|
+
const heading = document.createElement("h3");
|
|
704
|
+
heading.textContent = title;
|
|
705
|
+
section.appendChild(heading);
|
|
706
|
+
return section;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function toPrettyJson(value) {
|
|
710
|
+
try {
|
|
711
|
+
return JSON.stringify(value ?? {}, null, 2);
|
|
712
|
+
} catch {
|
|
713
|
+
return String(value);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function normalizeIntermediateResponses(values) {
|
|
718
|
+
const input = Array.isArray(values) ? values : [];
|
|
719
|
+
const seen = new Set();
|
|
720
|
+
const result = [];
|
|
721
|
+
|
|
722
|
+
for (const value of input) {
|
|
723
|
+
if (typeof value !== "string") continue;
|
|
724
|
+
const text = value.trim();
|
|
725
|
+
if (!text) continue;
|
|
726
|
+
if (seen.has(text)) continue;
|
|
727
|
+
seen.add(text);
|
|
728
|
+
result.push(text);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return result;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function getToolCallKey(toolCall) {
|
|
735
|
+
if (toolCall?.event_id) return `event:${toolCall.event_id}`;
|
|
736
|
+
const argsKey = toPrettyJson(toolCall?.arguments || {});
|
|
737
|
+
return [
|
|
738
|
+
toolCall?.tool_call_id || "",
|
|
739
|
+
toolCall?.parent_tool_call_id || "",
|
|
740
|
+
toolCall?.tool_name || "",
|
|
741
|
+
toolCall?.timestamp || "",
|
|
742
|
+
argsKey
|
|
743
|
+
].join("|");
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function normalizeToolCalls(values) {
|
|
747
|
+
const input = Array.isArray(values) ? values : [];
|
|
748
|
+
const seen = new Set();
|
|
749
|
+
const result = [];
|
|
750
|
+
|
|
751
|
+
for (const item of input) {
|
|
752
|
+
const key = getToolCallKey(item);
|
|
753
|
+
if (seen.has(key)) continue;
|
|
754
|
+
seen.add(key);
|
|
755
|
+
result.push(item);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return result;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function normalizeDetails(details) {
|
|
762
|
+
return {
|
|
763
|
+
responseIntermediate: normalizeIntermediateResponses(details?.responseIntermediate),
|
|
764
|
+
toolCalls: normalizeToolCalls(details?.toolCalls)
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function hasDetails(details) {
|
|
769
|
+
if (!details) return false;
|
|
770
|
+
const normalized = normalizeDetails(details);
|
|
771
|
+
const intermediates = normalized.responseIntermediate;
|
|
772
|
+
const toolCalls = normalized.toolCalls;
|
|
773
|
+
return intermediates.length > 0 || toolCalls.length > 0;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function showDetails(details) {
|
|
777
|
+
currentDetailsSource = details;
|
|
778
|
+
detailsContentEl.innerHTML = "";
|
|
779
|
+
const normalized = normalizeDetails(details);
|
|
780
|
+
|
|
781
|
+
const intermediates = normalized.responseIntermediate;
|
|
782
|
+
const toolCalls = normalized.toolCalls;
|
|
783
|
+
|
|
784
|
+
const intermediatesSection = renderDetailsSection("Reasoning");
|
|
785
|
+
if (intermediates.length === 0) {
|
|
786
|
+
const empty = document.createElement("div");
|
|
787
|
+
empty.className = "details-empty";
|
|
788
|
+
empty.textContent = "No reasoning trace in this message.";
|
|
789
|
+
intermediatesSection.appendChild(empty);
|
|
790
|
+
} else {
|
|
791
|
+
const item = document.createElement("div");
|
|
792
|
+
item.className = "details-item";
|
|
793
|
+
const content = document.createElement("div");
|
|
794
|
+
content.style.whiteSpace = "pre-wrap";
|
|
795
|
+
content.textContent = intermediates.join("");
|
|
796
|
+
item.appendChild(content);
|
|
797
|
+
intermediatesSection.appendChild(item);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const toolsSection = renderDetailsSection("Tool calls");
|
|
801
|
+
if (toolCalls.length === 0) {
|
|
802
|
+
const empty = document.createElement("div");
|
|
803
|
+
empty.className = "details-empty";
|
|
804
|
+
empty.textContent = "No tool calls in this message.";
|
|
805
|
+
toolsSection.appendChild(empty);
|
|
806
|
+
} else {
|
|
807
|
+
toolCalls.forEach((toolCall, index) => {
|
|
808
|
+
const item = document.createElement("div");
|
|
809
|
+
item.className = "details-item";
|
|
810
|
+
|
|
811
|
+
const ts = toolCall?.timestamp ? ` • ${toolCall.timestamp}` : "";
|
|
812
|
+
const title = document.createElement("div");
|
|
813
|
+
title.className = "details-item-title";
|
|
814
|
+
title.textContent = `${index + 1}. ${toolCall?.tool_name || "(unknown tool)"}${ts}`;
|
|
815
|
+
|
|
816
|
+
const pre = document.createElement("pre");
|
|
817
|
+
pre.className = "details-pre";
|
|
818
|
+
pre.textContent = toPrettyJson({
|
|
819
|
+
tool_call_id: toolCall?.tool_call_id || null,
|
|
820
|
+
parent_tool_call_id: toolCall?.parent_tool_call_id || null,
|
|
821
|
+
arguments: toolCall?.arguments || {}
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
item.appendChild(title);
|
|
825
|
+
item.appendChild(pre);
|
|
826
|
+
toolsSection.appendChild(item);
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
detailsContentEl.appendChild(intermediatesSection);
|
|
831
|
+
detailsContentEl.appendChild(toolsSection);
|
|
832
|
+
detailsBackdropEl.classList.add("open");
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function hideDetails() {
|
|
836
|
+
currentDetailsSource = null;
|
|
837
|
+
detailsBackdropEl.classList.remove("open");
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function refreshOpenDetails(details) {
|
|
841
|
+
if (!detailsBackdropEl.classList.contains("open")) return;
|
|
842
|
+
if (currentDetailsSource !== details) return;
|
|
843
|
+
showDetails(details);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function renderBubbleContent(role, text) {
|
|
847
|
+
return role === "copilot" ? renderMarkdownBasic(text) : escapeHtml(text);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function setBubbleText(bubble, role, text) {
|
|
851
|
+
const contentEl = bubble.querySelector(".bubble-content");
|
|
852
|
+
if (!contentEl) return;
|
|
853
|
+
contentEl.innerHTML = renderBubbleContent(role, text);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function setBubbleMeta(bubble, meta = "") {
|
|
857
|
+
let metaEl = bubble.querySelector(".meta");
|
|
858
|
+
if (!meta) {
|
|
859
|
+
if (metaEl) metaEl.remove();
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (!metaEl) {
|
|
864
|
+
metaEl = document.createElement("div");
|
|
865
|
+
metaEl.className = "meta";
|
|
866
|
+
bubble.appendChild(metaEl);
|
|
867
|
+
}
|
|
868
|
+
metaEl.textContent = meta;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function setBubbleIntermediates(bubble, responses = []) {
|
|
872
|
+
const intermediatesEl = bubble.querySelector(".bubble-intermediates");
|
|
873
|
+
if (!intermediatesEl) return;
|
|
874
|
+
|
|
875
|
+
const items = Array.isArray(responses)
|
|
876
|
+
? responses.filter((item) => typeof item === "string" && item.length > 0)
|
|
877
|
+
: [];
|
|
878
|
+
|
|
879
|
+
if (items.length === 0) {
|
|
880
|
+
intermediatesEl.classList.remove("open");
|
|
881
|
+
intermediatesEl.innerHTML = "";
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
intermediatesEl.classList.add("open");
|
|
886
|
+
intermediatesEl.innerHTML = "";
|
|
887
|
+
|
|
888
|
+
const title = document.createElement("div");
|
|
889
|
+
title.className = "bubble-intermediates-title";
|
|
890
|
+
title.textContent = "Reasoning";
|
|
891
|
+
intermediatesEl.appendChild(title);
|
|
892
|
+
|
|
893
|
+
const text = document.createElement("div");
|
|
894
|
+
text.className = "bubble-intermediates-text";
|
|
895
|
+
text.textContent = items.join("");
|
|
896
|
+
intermediatesEl.appendChild(text);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function attachBubbleDetails(bubble, details) {
|
|
900
|
+
bubble.classList.remove("has-details");
|
|
901
|
+
bubble.title = "";
|
|
902
|
+
bubble.onclick = null;
|
|
903
|
+
|
|
904
|
+
if (!hasDetails(details)) {
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
bubble.classList.add("has-details");
|
|
909
|
+
bubble.title = "Click to view tool calls and intermediate responses";
|
|
910
|
+
bubble.onclick = (event) => {
|
|
911
|
+
if (event.target instanceof Element && event.target.closest("a")) {
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
showDetails(details);
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function renderBubble(role, text, meta = "", details = null) {
|
|
919
|
+
const wrap = document.createElement("div");
|
|
920
|
+
wrap.className = `bubble-wrap ${role}`;
|
|
921
|
+
|
|
922
|
+
const bubble = document.createElement("div");
|
|
923
|
+
bubble.className = `bubble ${role}`;
|
|
924
|
+
|
|
925
|
+
const contentEl = document.createElement("div");
|
|
926
|
+
contentEl.className = "bubble-content";
|
|
927
|
+
contentEl.innerHTML = renderBubbleContent(role, text);
|
|
928
|
+
bubble.appendChild(contentEl);
|
|
929
|
+
|
|
930
|
+
const intermediatesEl = document.createElement("div");
|
|
931
|
+
intermediatesEl.className = "bubble-intermediates";
|
|
932
|
+
bubble.appendChild(intermediatesEl);
|
|
933
|
+
|
|
934
|
+
setBubbleMeta(bubble, meta);
|
|
935
|
+
if (role === "copilot") {
|
|
936
|
+
attachBubbleDetails(bubble, details);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
wrap.appendChild(bubble);
|
|
940
|
+
chatEl.appendChild(wrap);
|
|
941
|
+
scrollToBubbleTop(wrap);
|
|
942
|
+
return { wrap, bubble, contentEl, intermediatesEl };
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function renderWaitingBubble() {
|
|
946
|
+
const wrap = document.createElement("div");
|
|
947
|
+
wrap.className = "bubble-wrap copilot";
|
|
948
|
+
wrap.id = "waitingBubble";
|
|
949
|
+
|
|
950
|
+
const bubble = document.createElement("div");
|
|
951
|
+
bubble.className = "bubble copilot";
|
|
952
|
+
bubble.innerHTML = `
|
|
953
|
+
<span class="typing" aria-label="Waiting for response">
|
|
954
|
+
<span class="typing-dot"></span>
|
|
955
|
+
<span class="typing-dot"></span>
|
|
956
|
+
<span class="typing-dot"></span>
|
|
957
|
+
</span>
|
|
958
|
+
`;
|
|
959
|
+
|
|
960
|
+
wrap.appendChild(bubble);
|
|
961
|
+
chatEl.appendChild(wrap);
|
|
962
|
+
scrollChatToBottom();
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function clearWaitingBubble() {
|
|
966
|
+
document.getElementById("waitingBubble")?.remove();
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function setLoading(loading, options = {}) {
|
|
970
|
+
const showWaitingBubble = options.showWaitingBubble !== false;
|
|
971
|
+
state.loading = loading;
|
|
972
|
+
sendBtnEl.disabled = loading;
|
|
973
|
+
promptInputEl.disabled = loading;
|
|
974
|
+
|
|
975
|
+
if (loading) {
|
|
976
|
+
if (showWaitingBubble) {
|
|
977
|
+
renderWaitingBubble();
|
|
978
|
+
}
|
|
979
|
+
state.loadingSeconds = 0;
|
|
980
|
+
setStatus("Waiting for Copilot...");
|
|
981
|
+
state.loadingTimer = window.setInterval(() => {
|
|
982
|
+
state.loadingSeconds += 1;
|
|
983
|
+
setStatus(`Waiting for Copilot... ${state.loadingSeconds}s`);
|
|
984
|
+
}, 1000);
|
|
985
|
+
} else {
|
|
986
|
+
clearWaitingBubble();
|
|
987
|
+
if (state.loadingTimer) {
|
|
988
|
+
clearInterval(state.loadingTimer);
|
|
989
|
+
state.loadingTimer = null;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function showSettings(force = false) {
|
|
995
|
+
baseUrlInputEl.value = state.baseUrl || defaultBaseUrl;
|
|
996
|
+
keyInputEl.value = state.key || "";
|
|
997
|
+
cancelSettingsBtnEl.style.display = force ? "none" : "inline-block";
|
|
998
|
+
settingsBackdropEl.classList.add("open");
|
|
999
|
+
baseUrlInputEl.focus();
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function hideSettings() {
|
|
1003
|
+
settingsBackdropEl.classList.remove("open");
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
function loadSettings() {
|
|
1007
|
+
state.baseUrl = (localStorage.getItem(STORAGE_KEYS.baseUrl) || "").trim();
|
|
1008
|
+
state.key = (localStorage.getItem(STORAGE_KEYS.key) || "").trim();
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function loadSettingsFromHash() {
|
|
1012
|
+
const rawHash = window.location.hash ? window.location.hash.slice(1) : "";
|
|
1013
|
+
if (!rawHash) {
|
|
1014
|
+
return false;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const params = new URLSearchParams(rawHash);
|
|
1018
|
+
const hashBaseUrl = (params.get("baseUrl") || "").trim().replace(/\/+$/, "");
|
|
1019
|
+
const hashKey = (params.get("key") || "").trim();
|
|
1020
|
+
|
|
1021
|
+
if (!hashBaseUrl && !hashKey) {
|
|
1022
|
+
return false;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
if (hashBaseUrl) {
|
|
1026
|
+
state.baseUrl = hashBaseUrl;
|
|
1027
|
+
localStorage.setItem(STORAGE_KEYS.baseUrl, state.baseUrl);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
if (hashKey) {
|
|
1031
|
+
state.key = hashKey;
|
|
1032
|
+
localStorage.setItem(STORAGE_KEYS.key, state.key);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
history.replaceState(null, "", `${location.pathname}${location.search}`);
|
|
1036
|
+
return true;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
function isLocalBaseUrl(baseUrl) {
|
|
1040
|
+
if (!baseUrl) return false;
|
|
1041
|
+
try {
|
|
1042
|
+
const parsed = new URL(baseUrl);
|
|
1043
|
+
const hostname = (parsed.hostname || "").toLowerCase();
|
|
1044
|
+
if (!hostname) return false;
|
|
1045
|
+
return (
|
|
1046
|
+
hostname === "localhost" ||
|
|
1047
|
+
hostname === "127.0.0.1" ||
|
|
1048
|
+
hostname === "::1" ||
|
|
1049
|
+
hostname.endsWith(".localhost")
|
|
1050
|
+
);
|
|
1051
|
+
} catch {
|
|
1052
|
+
return false;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function requiresFunctionKey(baseUrl) {
|
|
1057
|
+
return !isLocalBaseUrl(baseUrl);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
function saveSettings() {
|
|
1061
|
+
state.baseUrl = baseUrlInputEl.value.trim().replace(/\/+$/, "");
|
|
1062
|
+
state.key = keyInputEl.value.trim();
|
|
1063
|
+
|
|
1064
|
+
if (!state.baseUrl) {
|
|
1065
|
+
setStatus("Base URL is required.", true);
|
|
1066
|
+
baseUrlInputEl.focus();
|
|
1067
|
+
return false;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
if (requiresFunctionKey(state.baseUrl) && !state.key) {
|
|
1071
|
+
setStatus("Key is required for non-localhost URLs.", true);
|
|
1072
|
+
keyInputEl.focus();
|
|
1073
|
+
return false;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
localStorage.setItem(STORAGE_KEYS.baseUrl, state.baseUrl);
|
|
1077
|
+
localStorage.setItem(STORAGE_KEYS.key, state.key);
|
|
1078
|
+
setStatus("Settings saved.");
|
|
1079
|
+
hideSettings();
|
|
1080
|
+
promptInputEl.focus();
|
|
1081
|
+
return true;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function getMessageFromBody(body) {
|
|
1085
|
+
if (!body) return "(No response body)";
|
|
1086
|
+
if (typeof body === "string") return body;
|
|
1087
|
+
|
|
1088
|
+
const candidates = [
|
|
1089
|
+
body.response,
|
|
1090
|
+
body.answer,
|
|
1091
|
+
body.message,
|
|
1092
|
+
body.text,
|
|
1093
|
+
body.output,
|
|
1094
|
+
body.content,
|
|
1095
|
+
body.result,
|
|
1096
|
+
body?.data?.response,
|
|
1097
|
+
body?.data?.message,
|
|
1098
|
+
body?.choices?.[0]?.message?.content
|
|
1099
|
+
];
|
|
1100
|
+
|
|
1101
|
+
for (const value of candidates) {
|
|
1102
|
+
if (typeof value === "string" && value.trim()) {
|
|
1103
|
+
return value;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
return JSON.stringify(body, null, 2);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function getSessionId(response, body) {
|
|
1111
|
+
return (
|
|
1112
|
+
response.headers.get("x-ms-session-id") ||
|
|
1113
|
+
body?.session_id ||
|
|
1114
|
+
body?.sessionId ||
|
|
1115
|
+
body?.session?.id ||
|
|
1116
|
+
null
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
async function sendMessage(prompt, callbacks = {}) {
|
|
1121
|
+
const authQuery = state.key ? `?code=${encodeURIComponent(state.key)}` : "";
|
|
1122
|
+
const endpoint = `${state.baseUrl}/agent/chatstream${authQuery}`;
|
|
1123
|
+
const headers = {
|
|
1124
|
+
"Content-Type": "application/json",
|
|
1125
|
+
"Accept": "text/event-stream"
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
if (state.sessionId) {
|
|
1129
|
+
headers["x-ms-session-id"] = state.sessionId;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
const response = await fetch(endpoint, {
|
|
1133
|
+
method: "POST",
|
|
1134
|
+
headers,
|
|
1135
|
+
body: JSON.stringify({ prompt })
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
if (!response.ok) {
|
|
1139
|
+
const raw = await response.text();
|
|
1140
|
+
let body;
|
|
1141
|
+
try {
|
|
1142
|
+
body = raw ? JSON.parse(raw) : null;
|
|
1143
|
+
} catch {
|
|
1144
|
+
body = raw;
|
|
1145
|
+
}
|
|
1146
|
+
const message = getMessageFromBody(body);
|
|
1147
|
+
throw new Error(`HTTP ${response.status}: ${message}`);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
if (!response.body) {
|
|
1151
|
+
throw new Error("Streaming response body is empty.");
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
const reader = response.body.getReader();
|
|
1155
|
+
const decoder = new TextDecoder();
|
|
1156
|
+
let buffer = "";
|
|
1157
|
+
let streamText = "";
|
|
1158
|
+
let finalMessage = "";
|
|
1159
|
+
let doneReceived = false;
|
|
1160
|
+
const intermediateResponses = [];
|
|
1161
|
+
const toolCalls = [];
|
|
1162
|
+
const seenToolStartKeys = new Set();
|
|
1163
|
+
let pendingIntermediateLineBreak = false;
|
|
1164
|
+
let lastStreamEventKey = null;
|
|
1165
|
+
|
|
1166
|
+
const getStreamEventKey = (event) => {
|
|
1167
|
+
const type = event?.type || "";
|
|
1168
|
+
if (!type) return null;
|
|
1169
|
+
|
|
1170
|
+
if (event?.event_id) {
|
|
1171
|
+
return `${type}|event:${event.event_id}`;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
if (event?.tool_call_id) {
|
|
1175
|
+
return `${type}|tool:${event.tool_call_id}`;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
if (type === "delta" || type === "message" || type === "intermediate") {
|
|
1179
|
+
return `${type}|content:${String(event?.content || "")}`;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
if (type === "session") {
|
|
1183
|
+
return `${type}|session:${String(event?.session_id || "")}`;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
if (type === "done" || type === "error") {
|
|
1187
|
+
return `${type}|${String(event?.content || "")}`;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
return `${type}|${JSON.stringify(event)}`;
|
|
1191
|
+
};
|
|
1192
|
+
|
|
1193
|
+
const handleEvent = (event) => {
|
|
1194
|
+
const type = event?.type;
|
|
1195
|
+
|
|
1196
|
+
if (type === "session" && typeof event.session_id === "string" && event.session_id.trim()) {
|
|
1197
|
+
state.sessionId = event.session_id;
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
if (type === "delta" && typeof event.content === "string") {
|
|
1202
|
+
streamText += event.content;
|
|
1203
|
+
if (typeof callbacks.onDelta === "function") {
|
|
1204
|
+
callbacks.onDelta(streamText);
|
|
1205
|
+
}
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
if (type === "message" && typeof event.content === "string") {
|
|
1210
|
+
finalMessage = event.content;
|
|
1211
|
+
if (!event.content) {
|
|
1212
|
+
pendingIntermediateLineBreak = true;
|
|
1213
|
+
}
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
if (type === "intermediate" && typeof event.content === "string") {
|
|
1218
|
+
const content = event.content;
|
|
1219
|
+
if (content.length > 0) {
|
|
1220
|
+
if (pendingIntermediateLineBreak && intermediateResponses.length > 0) {
|
|
1221
|
+
intermediateResponses.push("\n");
|
|
1222
|
+
}
|
|
1223
|
+
pendingIntermediateLineBreak = false;
|
|
1224
|
+
intermediateResponses.push(content);
|
|
1225
|
+
if (typeof callbacks.onIntermediate === "function") {
|
|
1226
|
+
callbacks.onIntermediate(intermediateResponses.length, intermediateResponses);
|
|
1227
|
+
}
|
|
1228
|
+
if (typeof callbacks.onDetailsUpdate === "function") {
|
|
1229
|
+
callbacks.onDetailsUpdate({
|
|
1230
|
+
responseIntermediate: intermediateResponses,
|
|
1231
|
+
toolCalls
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
if (type === "tool_start") {
|
|
1239
|
+
const toolStartKey = [
|
|
1240
|
+
event?.event_id || "",
|
|
1241
|
+
event?.tool_call_id || "",
|
|
1242
|
+
event?.tool_name || ""
|
|
1243
|
+
].join("|");
|
|
1244
|
+
if (seenToolStartKeys.has(toolStartKey)) {
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
seenToolStartKeys.add(toolStartKey);
|
|
1248
|
+
|
|
1249
|
+
toolCalls.push({
|
|
1250
|
+
event_id: event?.event_id || null,
|
|
1251
|
+
tool_name: event?.tool_name || "(unknown tool)",
|
|
1252
|
+
tool_call_id: event?.tool_call_id || null,
|
|
1253
|
+
parent_tool_call_id: event?.parent_tool_call_id || null,
|
|
1254
|
+
arguments: event?.arguments || {},
|
|
1255
|
+
timestamp: event?.timestamp || new Date().toISOString()
|
|
1256
|
+
});
|
|
1257
|
+
if (typeof callbacks.onToolCount === "function") {
|
|
1258
|
+
callbacks.onToolCount(toolCalls.length);
|
|
1259
|
+
}
|
|
1260
|
+
if (typeof callbacks.onDetailsUpdate === "function") {
|
|
1261
|
+
callbacks.onDetailsUpdate({
|
|
1262
|
+
responseIntermediate: intermediateResponses,
|
|
1263
|
+
toolCalls
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
if (type === "tool_end") {
|
|
1270
|
+
const toolCallId = event?.tool_call_id || null;
|
|
1271
|
+
if (toolCallId) {
|
|
1272
|
+
const existing = toolCalls.find((item) => item?.tool_call_id === toolCallId);
|
|
1273
|
+
if (existing) {
|
|
1274
|
+
existing.result = event?.result ?? null;
|
|
1275
|
+
existing.timestamp = event?.timestamp || existing.timestamp;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
if (typeof callbacks.onDetailsUpdate === "function") {
|
|
1279
|
+
callbacks.onDetailsUpdate({
|
|
1280
|
+
responseIntermediate: intermediateResponses,
|
|
1281
|
+
toolCalls
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
if (type === "error") {
|
|
1288
|
+
throw new Error(typeof event?.content === "string" && event.content.trim() ? event.content : "Streaming request failed.");
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
if (type === "done") {
|
|
1292
|
+
doneReceived = true;
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
while (!doneReceived) {
|
|
1297
|
+
const { value, done } = await reader.read();
|
|
1298
|
+
if (done) break;
|
|
1299
|
+
|
|
1300
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1301
|
+
let boundary = buffer.indexOf("\n\n");
|
|
1302
|
+
while (boundary !== -1) {
|
|
1303
|
+
const block = buffer.slice(0, boundary);
|
|
1304
|
+
buffer = buffer.slice(boundary + 2);
|
|
1305
|
+
|
|
1306
|
+
const lines = block.split("\n");
|
|
1307
|
+
for (const line of lines) {
|
|
1308
|
+
if (!line.startsWith("data:")) continue;
|
|
1309
|
+
const payload = line.slice(5).trim();
|
|
1310
|
+
if (!payload) continue;
|
|
1311
|
+
|
|
1312
|
+
let event;
|
|
1313
|
+
try {
|
|
1314
|
+
event = JSON.parse(payload);
|
|
1315
|
+
} catch {
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
const eventKey = getStreamEventKey(event);
|
|
1320
|
+
if (eventKey && eventKey === lastStreamEventKey) {
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
lastStreamEventKey = eventKey;
|
|
1324
|
+
|
|
1325
|
+
handleEvent(event);
|
|
1326
|
+
if (doneReceived) break;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
if (doneReceived) break;
|
|
1330
|
+
boundary = buffer.indexOf("\n\n");
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
const finalTrimmed = typeof finalMessage === "string" ? finalMessage.trim() : "";
|
|
1335
|
+
const filteredIntermediateResponses = intermediateResponses.filter((item) => {
|
|
1336
|
+
if (typeof item !== "string") return false;
|
|
1337
|
+
const value = item.trim();
|
|
1338
|
+
if (!value) return false;
|
|
1339
|
+
if (finalTrimmed && value === finalTrimmed) return false;
|
|
1340
|
+
return true;
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
const details = normalizeDetails({
|
|
1344
|
+
responseIntermediate: filteredIntermediateResponses,
|
|
1345
|
+
toolCalls
|
|
1346
|
+
});
|
|
1347
|
+
|
|
1348
|
+
return {
|
|
1349
|
+
text: finalMessage || streamText || "(No response body)",
|
|
1350
|
+
sessionId: state.sessionId,
|
|
1351
|
+
details
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
composerEl.addEventListener("submit", async (event) => {
|
|
1356
|
+
event.preventDefault();
|
|
1357
|
+
|
|
1358
|
+
if (!state.baseUrl || (requiresFunctionKey(state.baseUrl) && !state.key)) {
|
|
1359
|
+
showSettings(true);
|
|
1360
|
+
setStatus("Set your base URL and key before chatting.", true);
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
const prompt = promptInputEl.value.trim();
|
|
1365
|
+
if (!prompt || state.loading) return;
|
|
1366
|
+
|
|
1367
|
+
renderBubble("you", prompt, "You");
|
|
1368
|
+
promptInputEl.value = "";
|
|
1369
|
+
setLoading(true, { showWaitingBubble: false });
|
|
1370
|
+
const streamingBubble = renderBubble("copilot", "", "Copilot • streaming...");
|
|
1371
|
+
const liveDetails = {
|
|
1372
|
+
responseIntermediate: [],
|
|
1373
|
+
toolCalls: []
|
|
1374
|
+
};
|
|
1375
|
+
|
|
1376
|
+
try {
|
|
1377
|
+
let currentToolCount = 0;
|
|
1378
|
+
let currentIntermediateCount = 0;
|
|
1379
|
+
const updateStreamingMeta = () => {
|
|
1380
|
+
const segments = ["Copilot", "streaming..."];
|
|
1381
|
+
if (currentToolCount > 0) {
|
|
1382
|
+
segments.push(`${currentToolCount} tool call${currentToolCount === 1 ? "" : "s"}`);
|
|
1383
|
+
}
|
|
1384
|
+
if (currentIntermediateCount > 0) {
|
|
1385
|
+
segments.push(`${currentIntermediateCount} intermediate${currentIntermediateCount === 1 ? "" : "s"}`);
|
|
1386
|
+
}
|
|
1387
|
+
setBubbleMeta(streamingBubble.bubble, segments.join(" • "));
|
|
1388
|
+
};
|
|
1389
|
+
|
|
1390
|
+
const result = await sendMessage(prompt, {
|
|
1391
|
+
onDelta: (text) => {
|
|
1392
|
+
setBubbleText(streamingBubble.bubble, "copilot", text);
|
|
1393
|
+
scrollChatToBottom();
|
|
1394
|
+
},
|
|
1395
|
+
onToolCount: (count) => {
|
|
1396
|
+
currentToolCount = count;
|
|
1397
|
+
updateStreamingMeta();
|
|
1398
|
+
},
|
|
1399
|
+
onIntermediate: (count, responses) => {
|
|
1400
|
+
currentIntermediateCount = count;
|
|
1401
|
+
updateStreamingMeta();
|
|
1402
|
+
setBubbleIntermediates(streamingBubble.bubble, responses);
|
|
1403
|
+
scrollChatToBottom();
|
|
1404
|
+
},
|
|
1405
|
+
onDetailsUpdate: (detailsSnapshot) => {
|
|
1406
|
+
const normalizedLive = normalizeDetails(detailsSnapshot);
|
|
1407
|
+
liveDetails.responseIntermediate = normalizedLive.responseIntermediate;
|
|
1408
|
+
liveDetails.toolCalls = normalizedLive.toolCalls;
|
|
1409
|
+
attachBubbleDetails(streamingBubble.bubble, liveDetails);
|
|
1410
|
+
refreshOpenDetails(liveDetails);
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
const copilotMeta = hasDetails(result.details)
|
|
1414
|
+
? "Copilot • click bubble for details"
|
|
1415
|
+
: "Copilot";
|
|
1416
|
+
setBubbleText(streamingBubble.bubble, "copilot", result.text);
|
|
1417
|
+
setBubbleIntermediates(streamingBubble.bubble, []);
|
|
1418
|
+
setBubbleMeta(streamingBubble.bubble, copilotMeta);
|
|
1419
|
+
liveDetails.responseIntermediate = result.details.responseIntermediate;
|
|
1420
|
+
liveDetails.toolCalls = result.details.toolCalls;
|
|
1421
|
+
attachBubbleDetails(streamingBubble.bubble, liveDetails);
|
|
1422
|
+
refreshOpenDetails(liveDetails);
|
|
1423
|
+
if (result.sessionId) {
|
|
1424
|
+
setStatus(`Connected • Session ${result.sessionId}`);
|
|
1425
|
+
} else {
|
|
1426
|
+
setStatus("Connected");
|
|
1427
|
+
}
|
|
1428
|
+
} catch (error) {
|
|
1429
|
+
setBubbleText(streamingBubble.bubble, "copilot", `Request failed. ${error.message}`);
|
|
1430
|
+
setBubbleIntermediates(streamingBubble.bubble, []);
|
|
1431
|
+
setBubbleMeta(streamingBubble.bubble, "Copilot");
|
|
1432
|
+
attachBubbleDetails(streamingBubble.bubble, null);
|
|
1433
|
+
setStatus(error.message, true);
|
|
1434
|
+
} finally {
|
|
1435
|
+
setLoading(false);
|
|
1436
|
+
promptInputEl.focus();
|
|
1437
|
+
}
|
|
1438
|
+
});
|
|
1439
|
+
|
|
1440
|
+
promptInputEl.addEventListener("keydown", (event) => {
|
|
1441
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
1442
|
+
event.preventDefault();
|
|
1443
|
+
composerEl.requestSubmit();
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
newSessionBtnEl.addEventListener("click", () => {
|
|
1448
|
+
state.sessionId = null;
|
|
1449
|
+
chatEl.innerHTML = "";
|
|
1450
|
+
hideDetails();
|
|
1451
|
+
setStatus("Started a new session. Next message will not send a session id.");
|
|
1452
|
+
promptInputEl.focus();
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
settingsBtnEl.addEventListener("click", () => {
|
|
1456
|
+
showSettings(false);
|
|
1457
|
+
setStatus("Editing connection settings.");
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1460
|
+
saveSettingsBtnEl.addEventListener("click", () => {
|
|
1461
|
+
saveSettings();
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1464
|
+
cancelSettingsBtnEl.addEventListener("click", () => {
|
|
1465
|
+
hideSettings();
|
|
1466
|
+
setStatus("Settings unchanged.");
|
|
1467
|
+
});
|
|
1468
|
+
|
|
1469
|
+
settingsBackdropEl.addEventListener("click", (event) => {
|
|
1470
|
+
if (event.target === settingsBackdropEl && cancelSettingsBtnEl.style.display !== "none") {
|
|
1471
|
+
hideSettings();
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
closeDetailsBtnEl.addEventListener("click", () => {
|
|
1476
|
+
hideDetails();
|
|
1477
|
+
});
|
|
1478
|
+
|
|
1479
|
+
detailsBackdropEl.addEventListener("click", (event) => {
|
|
1480
|
+
if (event.target === detailsBackdropEl) {
|
|
1481
|
+
hideDetails();
|
|
1482
|
+
}
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
document.addEventListener("keydown", (event) => {
|
|
1486
|
+
if (event.key === "Escape") {
|
|
1487
|
+
hideDetails();
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
loadSettings();
|
|
1492
|
+
loadSettingsFromHash();
|
|
1493
|
+
if (!state.baseUrl || (requiresFunctionKey(state.baseUrl) && !state.key)) {
|
|
1494
|
+
state.baseUrl = state.baseUrl || defaultBaseUrl;
|
|
1495
|
+
showSettings(true);
|
|
1496
|
+
setStatus("Enter your base URL and key to begin.");
|
|
1497
|
+
} else {
|
|
1498
|
+
setStatus("Ready.");
|
|
1499
|
+
renderBubble("copilot", "Hi! Send a message to start chatting.", "Copilot");
|
|
1500
|
+
promptInputEl.focus();
|
|
1501
|
+
}
|
|
1502
|
+
</script>
|
|
1503
|
+
</body>
|
|
1504
|
+
</html>
|