pvanand commited on
Commit
e727ed4
·
verified ·
1 Parent(s): d8a939d

Update static/multi-agent.html

Browse files
Files changed (1) hide show
  1. static/multi-agent.html +614 -510
static/multi-agent.html CHANGED
@@ -1,511 +1,615 @@
1
-
2
- <!DOCTYPE html>
3
- <html lang="en">
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>DigiYatra Assistant</title>
8
- <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
9
- <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
10
- <script src="https://cdnjs.cloudflare.com/ajax/libs/uuid/8.3.2/uuid.min.js"></script>
11
- <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
12
- <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
13
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/atom-one-dark.min.css">
14
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script>
15
- <style>
16
- :root {
17
- --primary-color: #304b76;;
18
- --secondary-color: #f0f8ff;
19
- --text-color: #333;
20
- --background-color: #f5f7fa;
21
- }
22
- body {
23
- font-family: 'Poppins', sans-serif;
24
- /* font-family: 'Roboto', sans-serif; */
25
- line-height: 1.6;
26
- margin: 0;
27
- padding: 0;
28
- background-color: var(--background-color);
29
- color: var(--text-color);
30
- }
31
- #app {
32
- max-width: 900px;
33
- margin: 0 auto;
34
- padding: 10px;
35
- }
36
- .chat-container {
37
- background-color: #ffffff;
38
- border-radius: 15px;
39
- box-shadow: 0 4px 20px rgba(0,0,0,0.1);
40
- overflow: hidden;
41
- display: flex;
42
- flex-direction: column;
43
- height: 98vh;
44
- transition: all 0.3s ease;
45
- }
46
- .messages {
47
- flex-grow: 1;
48
- overflow-y: auto;
49
- padding: 20px;
50
- display: flex;
51
- flex-direction: column;
52
- }
53
- .message {
54
- max-width: 80%;
55
- margin-bottom: 20px;
56
- padding: 10px 20px;
57
- border-radius: 20px;
58
- font-size: 16px;
59
- line-height: 1.5;
60
- word-wrap: break-word;
61
- animation: fadeIn 0.5s ease;
62
- }
63
- table {
64
- width: 100%;
65
- border-collapse: collapse;
66
- margin-bottom: 20px;
67
- }
68
- table, th, td {
69
- border: 1px solid #ddd;
70
- }
71
- th, td {
72
- padding: 10px;
73
- text-align: left;
74
- }
75
- @keyframes fadeIn {
76
- from { opacity: 0; transform: translateY(10px); }
77
- to { opacity: 1; transform: translateY(0); }
78
- }
79
- .user-message {
80
- background-color: var(--primary-color);
81
- color: #ffffff;
82
- align-self: flex-end;
83
- border-bottom-right-radius: 5px;
84
- }
85
- .bot-message {
86
- background-color: var(--secondary-color);
87
- color: var(--text-color);
88
- align-self: flex-start;
89
- border-bottom-left-radius: 5px;
90
- }
91
- .input-area {
92
- display: flex;
93
- padding: 15px;
94
- background-color: #ffffff;
95
- border-top: 1px solid #e0e0e0;
96
- }
97
- #user-input {
98
- flex-grow: 1;
99
- padding: 12px 15px;
100
- border: 2px solid var(--primary-color);
101
- border-radius: 15px;
102
- font-size: 16px;
103
- outline: none;
104
- transition: all 0.3s ease;
105
- }
106
- #user-input:focus {
107
- box-shadow: 0 0 0 3px rgba(0,119,190,0.3);
108
- }
109
- .send-button, .reset-button {
110
- background-color: var(--primary-color);
111
- color: #ffffff;
112
- border: none;
113
- border-radius: 50%;
114
- width: 50px;
115
- height: 50px;
116
- margin-left: 10px;
117
- cursor: pointer;
118
- display: flex;
119
- align-items: center;
120
- justify-content: center;
121
- transition: all 0.3s ease;
122
- }
123
- .send-button:hover, .reset-button:hover {
124
- background-color: #005fa3;
125
- transform: scale(1.05);
126
- }
127
- .send-button svg, .reset-button svg {
128
- width: 24px;
129
- height: 24px;
130
- }
131
- .option-buttons {
132
- display: flex;
133
- flex-wrap: wrap;
134
- gap: 10px;
135
- margin-top: 15px;
136
- }
137
- .option-button {
138
- background-color: #ffffff;
139
- border: 2px solid var(--primary-color);
140
- border-radius: 20px;
141
- padding: 8px 16px;
142
- font-size: 14px;
143
- cursor: pointer;
144
- transition: all 0.3s ease;
145
- }
146
- .option-button:hover {
147
- background-color: var(--secondary-color);
148
- transform: translateY(-2px);
149
- }
150
- .option-button.selected {
151
- background-color: var(--primary-color);
152
- color: #ffffff;
153
- }
154
- .audio-button {
155
- background: none;
156
- border: none;
157
- cursor: pointer;
158
- padding: 0;
159
- margin-top: 10px;
160
- transition: all 0.3s ease;
161
- }
162
- .audio-button:hover {
163
- transform: scale(1.1);
164
- }
165
- .audio-button svg {
166
- width: 30px;
167
- height: 30px;
168
- fill: var(--primary-color);
169
- }
170
- @media (max-width: 600px) {
171
- #app {
172
- padding: 10px;
173
- }
174
- .chat-container {
175
- height: 95vh;
176
- }
177
- .message {
178
- max-width: 90%;
179
- }
180
- }
181
- .dot {
182
- display: inline-block;
183
- width: 8px;
184
- height: 8px;
185
- border-radius: 50%;
186
- background-color: #5853c0;
187
- margin: 0 3px;
188
- animation: bounce 1.4s infinite ease-in-out;
189
- }
190
- .dot:nth-child(1) { animation-delay: -0.32s; }
191
- .dot:nth-child(2) { animation-delay: -0.16s; }
192
- @keyframes bounce {
193
- 0%, 80%, 100% {
194
- transform: scale(0);
195
- } 40% {
196
- transform: scale(1.0);
197
- }
198
- }
199
- .chat-container {
200
- position: relative;
201
- }
202
-
203
- .input-area {
204
- position: absolute;
205
- bottom: 0;
206
- left: 0;
207
- right: 0;
208
- z-index: 10;
209
- transition: transform 0.3s ease;
210
- }
211
-
212
- .messages {
213
- padding-bottom: 80px;
214
- }
215
-
216
- .input-hidden {
217
- transform: translateY(100%);
218
- }
219
- </style>
220
- </head>
221
- <body>
222
- <div id="app">
223
- <div class="chat-container">
224
- <div class="messages" ref="messageContainer" @scroll="handleScroll">
225
- <div v-for="(message, index) in messages" :key="index"
226
- :class="['message', message.type === 'user' ? 'user-message' : 'bot-message']">
227
- <div v-if="message.type === 'bot' && message.content === ''" id="typing-animation"></div>
228
- <div v-else v-html="message.content"></div>
229
- <button v-if="message.type === 'bot' && message.audio" @click="toggleAudio(index)" class="audio-button">
230
- <svg v-if="!message.isPlaying" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
231
- <path d="M8 5v14l11-7z"/>
232
- </svg>
233
- <svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
234
- <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
235
- </svg>
236
- </button>
237
- </div>
238
- </div>
239
- <div class="input-area" :class="{ 'input-hidden': isScrollingUp }">
240
- <input type="text" id="user-input" v-model="userInput" @keyup.enter="sendMessage" placeholder="Type your message...">
241
- <button class="send-button" @click="sendMessage">
242
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
243
- <line x1="22" y1="2" x2="11" y2="13"></line>
244
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
245
- </svg>
246
- </button>
247
- <button class="reset-button" @click="resetConversation">
248
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
249
- <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
250
- <path d="M3 3v5h5"></path>
251
- </svg>
252
- </button>
253
- </div>
254
- </div>
255
- </div>
256
-
257
-
258
- <script>
259
- marked.setOptions({
260
- highlight: function (code, lang) {
261
- if (lang && hljs.getLanguage(lang)) {
262
- return hljs.highlight(code, { language: lang }).value;
263
- } else {
264
- return hljs.highlightAuto(code).value;
265
- }
266
- },
267
- sanitize: false
268
- });
269
-
270
- function createTypingAnimation() {
271
- const container = document.getElementById('typing-animation');
272
- for (let i = 0; i < 3; i++) {
273
- const dot = document.createElement('div');
274
- dot.className = 'dot';
275
- container.appendChild(dot);
276
- }
277
- }
278
-
279
- const app = new Vue({
280
- el: '#app',
281
- data: {
282
- messages: [],
283
- userInput: '',
284
- selectedOptions: {},
285
- conversationId: '',
286
- currentAudio: null,
287
- isScrollingUp: false,
288
- lastScrollTop: 0,
289
- },
290
- methods: {
291
- async sendMessage() {
292
- if (!this.userInput.trim()) return;
293
-
294
- this.messages.push({ type: 'user', content: marked.parse(this.userInput) });
295
- const message = this.userInput;
296
- this.userInput = '';
297
- this.selectedOptions = {};
298
- let streamingIndex = this.messages.push({ type: 'bot', content: '' }) - 1;
299
-
300
- this.$nextTick(() => {
301
- createTypingAnimation();
302
- });
303
-
304
- try {
305
- const response = await fetch('https://pvanand-general-chat.hf.space/v2/followup-tools-agent', {
306
- method: 'POST',
307
- headers: {
308
- 'Content-Type': 'application/json',
309
- 'X-API-Key': '44d5c2ac18ced6fc25c1e57dcd06fc0b31fb4ad97bf56e67540671a647465df4'
310
- },
311
- body: JSON.stringify({
312
- query: message,
313
- model_id: 'openai/gpt-4o-mini',
314
- conversation_id: this.conversationId,
315
- user_id: 'string',
316
- })
317
- });
318
- const reader = response.body.getReader();
319
- let rawResponse = '';
320
- let jsonData = null;
321
- while (true) {
322
- const { done, value } = await reader.read();
323
- if (done) break;
324
- const chunk = new TextDecoder().decode(value);
325
- if (chunk.includes('<json>')) {
326
- const [textPart, jsonPart] = chunk.split('<json>');
327
- rawResponse += textPart;
328
- const jsonString = jsonPart.split('</json>')[0]; // Extract JSON string
329
- try {
330
- jsonData = JSON.parse(jsonString);
331
- } catch (error) {
332
- console.error("JSON parsing error:", error);
333
- console.log("Raw JSON string:", jsonString);
334
- }
335
- } else {
336
- rawResponse += chunk;
337
- }
338
-
339
- // Update the streaming message content
340
- //replace <tools> and </tools> <interact> and </interact> with empty string
341
- // it should also replace <tool> and </tool>
342
- rawResponse = rawResponse.replace(/<tools>[\s\S]*?<\/tools>/g, '');
343
- rawResponse = rawResponse.replace(/<interact>[\s\S]*?<\/interact>/g, '');
344
- rawResponse = rawResponse.replace(/<tool>[\s\S]*?<\/tool>/g, '');
345
- this.$set(this.messages[streamingIndex], 'content', marked.parse(rawResponse));
346
- }
347
-
348
- // Process jsonData after the loop if it exists
349
- if (jsonData) {
350
- if (jsonData.tools && jsonData.tools.length > 0) {
351
- let toolsMessage = "\n\n**Tools Used:**\n";
352
- jsonData.tools.forEach(tool => {
353
- toolsMessage += `- ${tool.name.charAt(0).toUpperCase() + tool.name.slice(1)}: ${tool.input}\n`;
354
- });
355
-
356
- this.$set(this.messages[streamingIndex], 'content', this.messages[streamingIndex].content + marked.parse(toolsMessage));
357
- }
358
-
359
- if (jsonData.clarification && jsonData.clarification.length > 0) {
360
- // Increment streamingIndex for the new clarification message
361
- streamingIndex = this.messages.push({ type: 'bot', content: '' }) - 1;
362
- this.renderClarificationQuestions(jsonData.clarification, streamingIndex);
363
- }
364
- }
365
- const audioUrl = await this.convertToSpeech(rawResponse);
366
- if (audioUrl) {
367
- this.$set(this.messages[streamingIndex], 'audio', audioUrl);
368
- }
369
- } catch (error) {
370
- console.error('Error:', error);
371
- this.messages.push({ type: 'bot', content: 'An error occurred while processing your request.' });
372
- }
373
- this.$nextTick(() => this.scrollToBottom());
374
- },
375
- handleScroll(event) {
376
- const st = event.target.scrollTop;
377
- this.isScrollingUp = st < this.lastScrollTop;
378
- this.lastScrollTop = st <= 0 ? 0 : st;
379
- },
380
- renderClarificationQuestions(clarification, messageIndex) {
381
- if (!clarification || clarification.length === 0) return;
382
-
383
- let clarificationHtml = '';
384
- clarification.forEach((item, questionIndex) => {
385
- clarificationHtml += `<strong>${item.question}</strong><br>`;
386
- clarificationHtml += '<div class="option-buttons">';
387
- item.options.forEach((option, optionIndex) => {
388
- const escapedOption = option.replace(/'/g, "\\'");
389
- clarificationHtml += `<button class="option-button" onclick="app.toggleOption('${escapedOption}', ${questionIndex}, ${optionIndex})">${option}</button>`;
390
- });
391
- clarificationHtml += '</div><br>';
392
- });
393
-
394
- this.$set(this.messages[messageIndex], 'content', this.messages[messageIndex].content + marked.parse(clarificationHtml));
395
- this.$nextTick(() => {
396
- this.scrollToBottom();
397
- this.updateButtonStates();
398
- });
399
- },
400
- toggleOption(option, questionIndex, optionIndex) {
401
- if (!this.selectedOptions[questionIndex]) {
402
- this.$set(this.selectedOptions, questionIndex, []);
403
- }
404
-
405
- const index = this.selectedOptions[questionIndex].indexOf(option);
406
- if (index > -1) {
407
- this.selectedOptions[questionIndex].splice(index, 1);
408
- } else {
409
- this.selectedOptions[questionIndex].push(option);
410
- }
411
-
412
- this.updateInputFromSelectedOptions();
413
- this.updateButtonStates();
414
- },
415
- updateInputFromSelectedOptions() {
416
- this.userInput = Object.entries(this.selectedOptions)
417
- .map(([questionIndex, options]) =>
418
- `Q${parseInt(questionIndex) + 1}: ${options.join(', ')}`)
419
- .join(' | ');
420
- this.$nextTick(() => document.getElementById('user-input').focus());
421
- },
422
- updateButtonStates() {
423
- Object.entries(this.selectedOptions).forEach(([questionIndex, options]) => {
424
- const buttons = document.querySelectorAll(`.option-buttons:nth-of-type(${parseInt(questionIndex) + 1}) .option-button`);
425
- buttons.forEach((button) => {
426
- if (options.includes(button.textContent)) {
427
- button.classList.add('selected');
428
- } else {
429
- button.classList.remove('selected');
430
- }
431
- });
432
- });
433
- },
434
- resetConversation() {
435
- this.conversationId = uuid.v4();
436
- this.messages = [];
437
- this.selectedOptions = {};
438
- this.userInput = '';
439
- this.isScrollingUp = false;
440
- this.lastScrollTop = 0;
441
-
442
- // Reset scroll position
443
- this.$nextTick(() => {
444
- if (this.$refs.messageContainer) {
445
- this.$refs.messageContainer.scrollTop = 0;
446
- }
447
- });
448
-
449
- // Ensure input area is visible
450
- const inputArea = document.querySelector('.input-area');
451
- if (inputArea) inputArea.classList.remove('input-hidden');
452
- },
453
-
454
- scrollToBottom() {
455
- const container = this.$refs.messageContainer;
456
- container.scrollTop = container.scrollHeight;
457
- },
458
- async convertToSpeech(text) {
459
- const voice = 'en-US-JennyNeural';
460
- const encodedText = encodeURIComponent(text);
461
- try {
462
- const response = await fetch(`https://pvanand-audio-chat.hf.space/tts?text=${encodedText}&voice=${voice}`, {
463
- method: 'GET',
464
- headers: {
465
- 'accept': 'application/json'
466
- }
467
- });
468
- if (!response.ok) {
469
- throw new Error(`HTTP error! status: ${response.status}`);
470
- }
471
- const blob = await response.blob();
472
- return URL.createObjectURL(blob);
473
- } catch (error) {
474
- console.error('Error:', error);
475
- return null;
476
- }
477
- },
478
- toggleAudio(index) {
479
- const message = this.messages[index];
480
- if (this.currentAudio && this.currentAudio !== message.audioElement) {
481
- this.currentAudio.pause();
482
- this.messages.forEach(m => {
483
- if (m.audioElement === this.currentAudio) {
484
- m.isPlaying = false;
485
- }
486
- });
487
- }
488
- if (!message.audioElement) {
489
- message.audioElement = new Audio(message.audio);
490
- message.audioElement.addEventListener('ended', () => {
491
- message.isPlaying = false;
492
- this.$forceUpdate();
493
- });
494
- }
495
- if (message.isPlaying) {
496
- message.audioElement.pause();
497
- } else {
498
- message.audioElement.play();
499
- this.currentAudio = message.audioElement;
500
- }
501
- message.isPlaying = !message.isPlaying;
502
- this.$forceUpdate();
503
- }
504
- },
505
- mounted() {
506
- this.resetConversation();
507
- }
508
- });
509
- </script>
510
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  </html>
 
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>DigiYatra Assistant</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/uuid/8.3.2/uuid.min.js"></script>
10
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
11
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
12
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/atom-one-dark.min.css">
13
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script>
14
+ <style>
15
+ :root {
16
+ --primary-color: #304b76;;
17
+ --secondary-color: #f0f8ff;
18
+ --text-color: #333;
19
+ --background-color: #f5f7fa;
20
+ }
21
+ body {
22
+ font-family: 'Poppins', sans-serif;
23
+ /* font-family: 'Roboto', sans-serif; */
24
+ line-height: 1.6;
25
+ margin: 0;
26
+ padding: 0;
27
+ background-color: var(--background-color);
28
+ color: var(--text-color);
29
+ }
30
+ #app {
31
+ max-width: 900px;
32
+ margin: 0 auto;
33
+ padding: 10px;
34
+ }
35
+ .chat-container {
36
+ background-color: #ffffff;
37
+ border-radius: 15px;
38
+ box-shadow: 0 4px 20px rgba(0,0,0,0.1);
39
+ overflow: hidden;
40
+ display: flex;
41
+ flex-direction: column;
42
+ height: 98vh;
43
+ transition: all 0.3s ease;
44
+ }
45
+ .messages {
46
+ flex-grow: 1;
47
+ overflow-y: auto;
48
+ padding: 20px;
49
+ display: flex;
50
+ flex-direction: column;
51
+ }
52
+ .message {
53
+ max-width: 80%;
54
+ margin-bottom: 20px;
55
+ padding: 10px 20px;
56
+ border-radius: 20px;
57
+ font-size: 16px;
58
+ line-height: 1.5;
59
+ word-wrap: break-word;
60
+ animation: fadeIn 0.5s ease;
61
+ }
62
+ table {
63
+ width: 100%;
64
+ border-collapse: collapse;
65
+ margin-bottom: 20px;
66
+ }
67
+ table, th, td {
68
+ border: 1px solid #ddd;
69
+ }
70
+ th, td {
71
+ padding: 10px;
72
+ text-align: left;
73
+ }
74
+ @keyframes fadeIn {
75
+ from { opacity: 0; transform: translateY(10px); }
76
+ to { opacity: 1; transform: translateY(0); }
77
+ }
78
+ .user-message {
79
+ background-color: var(--primary-color);
80
+ color: #ffffff;
81
+ align-self: flex-end;
82
+ border-bottom-right-radius: 5px;
83
+ }
84
+ .bot-message {
85
+ background-color: var(--secondary-color);
86
+ color: var(--text-color);
87
+ align-self: flex-start;
88
+ border-bottom-left-radius: 5px;
89
+ position: relative; /* Add this line */
90
+ }
91
+ .copy-button {
92
+ position: absolute;
93
+ top: 5px;
94
+ right: 5px;
95
+ background-color: transparent;
96
+ border: none;
97
+ cursor: pointer;
98
+ padding: 5px;
99
+ transition: all 0.3s ease;
100
+ }
101
+ .copy-button svg {
102
+ width: 16px;
103
+ height: 16px;
104
+ stroke: var(--primary-color);
105
+ }
106
+ .copy-button:hover {
107
+ transform: scale(1.1);
108
+ }
109
+ .input-area {
110
+ display: flex;
111
+ padding: 15px;
112
+ background-color: #ffffff;
113
+ border-top: 1px solid #e0e0e0;
114
+ }
115
+ #user-input {
116
+ flex-grow: 1;
117
+ padding: 12px 15px;
118
+ border: 2px solid var(--primary-color);
119
+ border-radius: 15px;
120
+ font-size: 16px;
121
+ outline: none;
122
+ transition: all 0.3s ease;
123
+ }
124
+ #user-input:focus {
125
+ box-shadow: 0 0 0 3px rgba(0,119,190,0.3);
126
+ }
127
+ .send-button, .reset-button {
128
+ background-color: var(--primary-color);
129
+ color: #ffffff;
130
+ border: none;
131
+ border-radius: 50%;
132
+ width: 50px;
133
+ height: 50px;
134
+ margin-left: 10px;
135
+ cursor: pointer;
136
+ display: flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ transition: all 0.3s ease;
140
+ }
141
+ .send-button:hover, .reset-button:hover {
142
+ background-color: #005fa3;
143
+ transform: scale(1.05);
144
+ }
145
+ .send-button svg, .reset-button svg {
146
+ width: 24px;
147
+ height: 24px;
148
+ }
149
+ .option-buttons {
150
+ display: flex;
151
+ flex-wrap: wrap;
152
+ gap: 10px;
153
+ margin-top: 15px;
154
+ }
155
+ .option-button {
156
+ background-color: #ffffff;
157
+ border: 2px solid var(--primary-color);
158
+ border-radius: 20px;
159
+ padding: 8px 16px;
160
+ font-size: 14px;
161
+ cursor: pointer;
162
+ transition: all 0.3s ease;
163
+ }
164
+ .option-button:hover {
165
+ background-color: var(--secondary-color);
166
+ transform: translateY(-2px);
167
+ }
168
+ .option-button.selected {
169
+ background-color: var(--primary-color);
170
+ color: #ffffff;
171
+ }
172
+ .audio-button {
173
+ background: none;
174
+ border: none;
175
+ cursor: pointer;
176
+ padding: 0;
177
+ margin-top: 10px;
178
+ transition: all 0.3s ease;
179
+ }
180
+ .audio-button:hover {
181
+ transform: scale(1.1);
182
+ }
183
+ .audio-button svg {
184
+ width: 30px;
185
+ height: 30px;
186
+ fill: var(--primary-color);
187
+ }
188
+ @media (max-width: 600px) {
189
+ #app {
190
+ padding: 10px;
191
+ }
192
+ .chat-container {
193
+ height: 95vh;
194
+ }
195
+ .message {
196
+ max-width: 90%;
197
+ }
198
+ }
199
+ .dot {
200
+ display: inline-block;
201
+ width: 8px;
202
+ height: 8px;
203
+ border-radius: 50%;
204
+ background-color: #5853c0;
205
+ margin: 0 3px;
206
+ animation: bounce 1.4s infinite ease-in-out;
207
+ }
208
+ .dot:nth-child(1) { animation-delay: -0.32s; }
209
+ .dot:nth-child(2) { animation-delay: -0.16s; }
210
+ @keyframes bounce {
211
+ 0%, 80%, 100% {
212
+ transform: scale(0);
213
+ } 40% {
214
+ transform: scale(1.0);
215
+ }
216
+ }
217
+ .chat-container {
218
+ position: relative;
219
+ }
220
+
221
+ .input-area {
222
+ position: absolute;
223
+ bottom: 0;
224
+ left: 0;
225
+ right: 0;
226
+ z-index: 10;
227
+ transition: transform 0.3s ease;
228
+ }
229
+
230
+ .messages {
231
+ padding-bottom: 80px;
232
+ }
233
+
234
+ .input-hidden {
235
+ transform: translateY(100%);
236
+ }
237
+
238
+ .tool-options {
239
+ position: absolute;
240
+ bottom: 100%;
241
+ left: 0;
242
+ background-color: #ffffff;
243
+ border: 1px solid #ddd;
244
+ border-radius: 8px;
245
+ box-shadow: 0 4px 20px rgba(0,0,0,0.2);
246
+ display: flex;
247
+ flex-direction: column;
248
+ width: auto;
249
+ min-width: 120px;
250
+ margin-bottom: 10px; /* Added padding for better spacing */
251
+ }
252
+
253
+ .tool-options button {
254
+ padding: 10px 15px;
255
+ border: none;
256
+ background: none;
257
+ text-align: left;
258
+ cursor: pointer;
259
+ white-space: nowrap;
260
+ transition: background-color 0.3s ease;
261
+ font-size: 16px;
262
+ font-weight: 500;
263
+ color: #333; /* Added margin to separate buttons */
264
+ border-radius: 4px; /* Added border radius for a floating effect */
265
+ }
266
+
267
+ .tool-options button:hover {
268
+ background-color: #e0e0e0;
269
+ color: #000;
270
+ }
271
+
272
+ </style>
273
+ </head>
274
+ <body>
275
+ <div id="app">
276
+ <div class="chat-container">
277
+ <div class="messages" ref="messageContainer" @scroll="handleScroll">
278
+ <div v-for="(message, index) in messages" :key="index"
279
+ :class="['message', message.type === 'user' ? 'user-message' : 'bot-message']">
280
+ <div v-if="message.type === 'bot' && message.content === ''" id="typing-animation"></div>
281
+ <div v-else v-html="message.content"></div>
282
+ <button v-if="message.type === 'bot' && message.audio" @click="toggleAudio(index)" class="audio-button">
283
+ <svg v-if="!message.isPlaying" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
284
+ <path d="M8 5v14l11-7z"/>
285
+ </svg>
286
+ <svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
287
+ <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
288
+ </svg>
289
+ </button>
290
+ <button v-if="message.type === 'bot' && message.rawResponse" @click="copyMarkdownToClipboard(message.rawResponse)" class="copy-button">
291
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
292
+ <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path>
293
+ <rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect>
294
+ </svg>
295
+ </button>
296
+ </div>
297
+ </div>
298
+ <div class="input-area" :class="{ 'input-hidden': isScrollingUp }">
299
+ <div v-if="showToolOptions" class="tool-options">
300
+ <button @click="selectTool('web')">/web</button>
301
+ <button @click="selectTool('news')">/news</button>
302
+ </div>
303
+ <input type="text" id="user-input" v-model="userInput" @keyup.enter="sendMessage" @input="handleInput($event)" placeholder="Type your message... (type / to see available tools)">
304
+
305
+ <button class="send-button" @click="sendMessage">
306
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
307
+ <line x1="22" y1="2" x2="11" y2="13"></line>
308
+ <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
309
+ </svg>
310
+ </button>
311
+ <button class="reset-button" @click="resetConversation">
312
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
313
+ <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
314
+ <path d="M3 3v5h5"></path>
315
+ </svg>
316
+ </button>
317
+ </div>
318
+ </div>
319
+ </div>
320
+
321
+ <script>
322
+ marked.setOptions({
323
+ highlight: function (code, lang) {
324
+ if (lang && hljs.getLanguage(lang)) {
325
+ return hljs.highlight(code, { language: lang }).value;
326
+ } else {
327
+ return hljs.highlightAuto(code).value;
328
+ }
329
+ },
330
+ sanitize: false
331
+ });
332
+
333
+ function createTypingAnimation() {
334
+ const container = document.getElementById('typing-animation');
335
+ for (let i = 0; i < 3; i++) {
336
+ const dot = document.createElement('div');
337
+ dot.className = 'dot';
338
+ container.appendChild(dot);
339
+ }
340
+ }
341
+
342
+ const app = new Vue({
343
+ el: '#app',
344
+ data: {
345
+ messages: [],
346
+ userInput: '',
347
+ selectedOptions: {},
348
+ conversationId: '',
349
+ currentAudio: null,
350
+ isScrollingUp: false,
351
+ lastScrollTop: 0,
352
+ tool_call: 'auto',
353
+ showToolOptions: false,
354
+ },
355
+ methods: {
356
+ async sendMessage() {
357
+ if (!this.userInput.trim()) return;
358
+
359
+ this.messages.push({ type: 'user', content: marked.parse(this.userInput) });
360
+ const message = this.userInput;
361
+ this.userInput = '';
362
+ this.selectedOptions = {};
363
+ let streamingIndex = this.messages.push({ type: 'bot', content: '' }) - 1;
364
+
365
+ this.$nextTick(() => {
366
+ createTypingAnimation();
367
+ });
368
+
369
+ try {
370
+ const response = await fetch('https://pvanand-general-chat.hf.space/v2/followup-tools-agent', {
371
+ method: 'POST',
372
+ headers: {
373
+ 'Content-Type': 'application/json',
374
+ 'X-API-Key': '44d5c2ac18ced6fc25c1e57dcd06fc0b31fb4ad97bf56e67540671a647465df4'
375
+ },
376
+ body: JSON.stringify({
377
+ query: message,
378
+ model_id: 'openai/gpt-4o-mini',
379
+ conversation_id: this.conversationId,
380
+ user_id: 'string',
381
+ tool_call: this.tool_call,
382
+ })
383
+ });
384
+ const reader = response.body.getReader();
385
+ let rawResponse = '';
386
+ let jsonData = null;
387
+ while (true) {
388
+ const { done, value } = await reader.read();
389
+ if (done) break;
390
+ const chunk = new TextDecoder().decode(value);
391
+ if (chunk.includes('<json>')) {
392
+ const [textPart, jsonPart] = chunk.split('<json>');
393
+ rawResponse += textPart;
394
+ const jsonString = jsonPart.split('</json>')[0];
395
+ try {
396
+ jsonData = JSON.parse(jsonString);
397
+ } catch (error) {
398
+ console.error("JSON parsing error:", error);
399
+ console.log("Raw JSON string:", jsonString);
400
+ }
401
+ } else {
402
+ rawResponse += chunk;
403
+ }
404
+
405
+ rawResponse = rawResponse.replace(/<\/?response>/g, '');
406
+ rawResponse = rawResponse.replace(/<tools>[\s\S]*?<\/tools>/g, '');
407
+ rawResponse = rawResponse.replace(/<interact>[\s\S]*?<\/interact>/g, '');
408
+ rawResponse = rawResponse.replace(/<tool>[\s\S]*?<\/tool>/g, '');
409
+ this.$set(this.messages[streamingIndex], 'content', marked.parse(rawResponse));
410
+ this.$set(this.messages[streamingIndex], 'rawResponse', rawResponse);
411
+ }
412
+
413
+ if (jsonData) {
414
+ if (jsonData.tools && jsonData.tools.length > 0) {
415
+ let toolsMessage = "\n\n**Tools Used:**\n";
416
+ jsonData.tools.forEach(tool => {
417
+ toolsMessage += `- ${tool.name.charAt(0).toUpperCase() + tool.name.slice(1)}: ${tool.input}\n`;
418
+ });
419
+
420
+ this.$set(this.messages[streamingIndex], 'content', this.messages[streamingIndex].content + marked.parse(toolsMessage));
421
+ }
422
+
423
+ if (jsonData.clarification && jsonData.clarification.length > 0) {
424
+ streamingIndex = this.messages.push({ type: 'bot', content: '' }) - 1;
425
+ this.renderClarificationQuestions(jsonData.clarification, streamingIndex);
426
+ }
427
+ }
428
+ const audioUrl = await this.convertToSpeech(rawResponse);
429
+ if (audioUrl) {
430
+ this.$set(this.messages[streamingIndex], 'audio', audioUrl);
431
+ }
432
+ } catch (error) {
433
+ console.error('Error:', error);
434
+ this.messages.push({ type: 'bot', content: 'An error occurred while processing your request.' });
435
+ }
436
+ this.$nextTick(() => this.scrollToBottom());
437
+ },
438
+ handleScroll(event) {
439
+ const st = event.target.scrollTop;
440
+ this.isScrollingUp = st < this.lastScrollTop;
441
+ this.lastScrollTop = st <= 0 ? 0 : st;
442
+ },
443
+ renderClarificationQuestions(clarification, messageIndex) {
444
+ if (!clarification || clarification.length === 0) return;
445
+
446
+ let clarificationHtml = '';
447
+ clarification.forEach((item, questionIndex) => {
448
+ clarificationHtml += `<strong>${item.question}</strong><br>`;
449
+ clarificationHtml += '<div class="option-buttons">';
450
+ item.options.forEach((option, optionIndex) => {
451
+ const escapedOption = option.replace(/'/g, "\\'");
452
+ clarificationHtml += `<button class="option-button" onclick="app.toggleOption('${escapedOption}', ${questionIndex}, ${optionIndex})">${option}</button>`;
453
+ });
454
+ clarificationHtml += '</div><br>';
455
+ });
456
+
457
+ this.$set(this.messages[messageIndex], 'content', this.messages[messageIndex].content + marked.parse(clarificationHtml));
458
+ this.$nextTick(() => {
459
+ this.scrollToBottom();
460
+ this.updateButtonStates();
461
+ });
462
+ },
463
+ toggleOption(option, questionIndex, optionIndex) {
464
+ if (!this.selectedOptions[questionIndex]) {
465
+ this.$set(this.selectedOptions, questionIndex, []);
466
+ }
467
+
468
+ const index = this.selectedOptions[questionIndex].indexOf(option);
469
+ if (index > -1) {
470
+ this.selectedOptions[questionIndex].splice(index, 1);
471
+ } else {
472
+ this.selectedOptions[questionIndex].push(option);
473
+ }
474
+
475
+ this.updateInputFromSelectedOptions();
476
+ this.updateButtonStates();
477
+ },
478
+ updateInputFromSelectedOptions() {
479
+ this.userInput = Object.entries(this.selectedOptions)
480
+ .map(([questionIndex, options]) =>
481
+ `Q${parseInt(questionIndex) + 1}: ${options.join(', ')}`)
482
+ .join(' | ');
483
+ this.$nextTick(() => document.getElementById('user-input').focus());
484
+ },
485
+ updateButtonStates() {
486
+ Object.entries(this.selectedOptions).forEach(([questionIndex, options]) => {
487
+ const buttons = document.querySelectorAll(`.option-buttons:nth-of-type(${parseInt(questionIndex) + 1}) .option-button`);
488
+ buttons.forEach((button) => {
489
+ if (options.includes(button.textContent)) {
490
+ button.classList.add('selected');
491
+ } else {
492
+ button.classList.remove('selected');
493
+ }
494
+ });
495
+ });
496
+ },
497
+ resetConversation() {
498
+ this.conversationId = uuid.v4();
499
+ this.messages = [];
500
+ this.selectedOptions = {};
501
+ this.userInput = '';
502
+ this.isScrollingUp = false;
503
+ this.lastScrollTop = 0;
504
+ this.tool_call = 'auto';
505
+ this.showToolOptions = false;
506
+
507
+ this.$nextTick(() => {
508
+ if (this.$refs.messageContainer) {
509
+ this.$refs.messageContainer.scrollTop = 0;
510
+ }
511
+ });
512
+
513
+ const inputArea = document.querySelector('.input-area');
514
+ if (inputArea) inputArea.classList.remove('input-hidden');
515
+ },
516
+ scrollToBottom() {
517
+ const container = this.$refs.messageContainer;
518
+ container.scrollTop = container.scrollHeight;
519
+ },
520
+ async convertToSpeech(text) {
521
+ const voice = 'en-US-JennyNeural';
522
+ const encodedText = encodeURIComponent(text);
523
+ try {
524
+ const response = await fetch(`https://pvanand-audio-chat.hf.space/tts?text=${encodedText}&voice=${voice}`, {
525
+ method: 'GET',
526
+ headers: {
527
+ 'accept': 'application/json'
528
+ }
529
+ });
530
+ if (!response.ok) {
531
+ throw new Error(`HTTP error! status: ${response.status}`);
532
+ }
533
+ const blob = await response.blob();
534
+ return URL.createObjectURL(blob);
535
+ } catch (error) {
536
+ console.error('Error:', error);
537
+ return null;
538
+ }
539
+ },
540
+ toggleAudio(index) {
541
+ const message = this.messages[index];
542
+ if (this.currentAudio && this.currentAudio !== message.audioElement) {
543
+ this.currentAudio.pause();
544
+ this.messages.forEach(m => {
545
+ if (m.audioElement === this.currentAudio) {
546
+ m.isPlaying = false;
547
+ }
548
+ });
549
+ }
550
+ if (!message.audioElement) {
551
+ message.audioElement = new Audio(message.audio);
552
+ message.audioElement.addEventListener('ended', () => {
553
+ message.isPlaying = false;
554
+ this.$forceUpdate();
555
+ });
556
+ }
557
+ if (message.isPlaying) {
558
+ message.audioElement.pause();
559
+ } else {
560
+ message.audioElement.play();
561
+ this.currentAudio = message.audioElement;
562
+ }
563
+ message.isPlaying = !message.isPlaying;
564
+ this.$forceUpdate();
565
+ },
566
+ // remove alert
567
+ copyMarkdownToClipboard(rawResponse) {
568
+ navigator.clipboard.writeText(rawResponse).then(() => {
569
+ // remove alert
570
+ }).catch(err => {
571
+ console.error('Failed to copy: ', err);
572
+ });
573
+ },
574
+ handleInput(event) {
575
+ console.log("Input event triggered", event.target.value); // For debugging
576
+ if (event.target.value === '/') {
577
+ console.log("Slash detected, showing tool options"); // For debugging
578
+ this.showToolOptions = true;
579
+ } else {
580
+ this.showToolOptions = false;
581
+ }
582
+ },
583
+ selectTool(tool) {
584
+ if (tool === 'web') {
585
+ this.tool_call = 'web';
586
+ this.userInput = '/web ';
587
+ } else if (tool === 'news') {
588
+ this.tool_call = 'news';
589
+ this.userInput = '/news ';
590
+ }
591
+ this.showToolOptions = false;
592
+ this.$nextTick(() => {
593
+ const inputElement = document.getElementById('user-input');
594
+ inputElement.focus();
595
+ inputElement.setSelectionRange(inputElement.value.length, inputElement.value.length);
596
+ });
597
+ },
598
+ watch: {
599
+ userInput(newVal, oldVal) {
600
+ console.log("userInput changed", newVal); // For debugging
601
+ if (newVal === '/') {
602
+ this.showToolOptions = true;
603
+ } else {
604
+ this.showToolOptions = false;
605
+ }
606
+ }
607
+ },
608
+ },
609
+ mounted() {
610
+ this.resetConversation();
611
+ }
612
+ });
613
+ </script>
614
+ </body>
615
  </html>