agentgui 1.0.145 → 1.0.147
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/lib/speech.js +5 -1
- package/package.json +1 -1
- package/server.js +3 -3
- package/static/index.html +118 -8
- package/static/js/client.js +26 -14
- package/static/js/streaming-renderer.js +103 -8
- package/static/js/voice.js +97 -23
- package/static/styles.css +24 -20
package/lib/speech.js
CHANGED
|
@@ -167,7 +167,11 @@ async function transcribe(audioBuffer) {
|
|
|
167
167
|
const decoded = decodeWavToFloat32(buf);
|
|
168
168
|
audio = resampleTo16k(decoded.audio, decoded.sampleRate);
|
|
169
169
|
} else {
|
|
170
|
-
|
|
170
|
+
const sampleCount = Math.floor(buf.byteLength / 4);
|
|
171
|
+
if (sampleCount === 0) throw new Error('Audio buffer too small');
|
|
172
|
+
const aligned = new ArrayBuffer(sampleCount * 4);
|
|
173
|
+
new Uint8Array(aligned).set(buf.subarray(0, sampleCount * 4));
|
|
174
|
+
audio = new Float32Array(aligned);
|
|
171
175
|
}
|
|
172
176
|
const result = await stt(audio);
|
|
173
177
|
return result.text || '';
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -15,7 +15,7 @@ const express = require('express');
|
|
|
15
15
|
const Busboy = require('busboy');
|
|
16
16
|
const fsbrowse = require('fsbrowse');
|
|
17
17
|
|
|
18
|
-
const SYSTEM_PROMPT = `
|
|
18
|
+
const SYSTEM_PROMPT = `Write all responses as clean semantic HTML. Use tags like <h3>, <p>, <ul>, <li>, <ol>, <table>, <code>, <pre>, <strong>, <em>, <a>, <blockquote>, <details>, <summary>. Your HTML will be rendered directly in a styled container that already provides fonts, colors, spacing, and dark mode support. Do not include <html>, <head>, <body>, <style>, or <script> tags. Do not use inline styles unless necessary for layout like tables. Do not use CSS class names. Just write semantic HTML content.`;
|
|
19
19
|
|
|
20
20
|
const activeExecutions = new Map();
|
|
21
21
|
const messageQueues = new Map();
|
|
@@ -522,7 +522,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
522
522
|
const mimeTypes = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml' };
|
|
523
523
|
const contentType = mimeTypes[ext] || 'application/octet-stream';
|
|
524
524
|
const fileContent = fs.readFileSync(normalizedPath);
|
|
525
|
-
res.writeHead(200, { 'Content-Type': contentType, 'Cache-Control': '
|
|
525
|
+
res.writeHead(200, { 'Content-Type': contentType, 'Cache-Control': 'no-cache' });
|
|
526
526
|
res.end(fileContent);
|
|
527
527
|
} catch (err) {
|
|
528
528
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
@@ -574,7 +574,7 @@ function serveFile(filePath, res) {
|
|
|
574
574
|
res.writeHead(200, {
|
|
575
575
|
'Content-Type': contentType,
|
|
576
576
|
'Content-Length': stats.size,
|
|
577
|
-
'Cache-Control': '
|
|
577
|
+
'Cache-Control': 'no-cache, must-revalidate'
|
|
578
578
|
});
|
|
579
579
|
fs.createReadStream(filePath).pipe(res);
|
|
580
580
|
});
|
package/static/index.html
CHANGED
|
@@ -524,12 +524,17 @@
|
|
|
524
524
|
}
|
|
525
525
|
|
|
526
526
|
.streaming-block-tool-use {
|
|
527
|
-
margin: 0.
|
|
527
|
+
margin: 0.25rem 0;
|
|
528
528
|
border-left: 3px solid #06b6d4;
|
|
529
529
|
background: rgba(6,182,212,0.06);
|
|
530
530
|
border-radius: 0 0.375rem 0.375rem 0;
|
|
531
531
|
overflow: hidden;
|
|
532
532
|
}
|
|
533
|
+
.streaming-block-tool-use.folded-tool {
|
|
534
|
+
border-left: none;
|
|
535
|
+
border-radius: 0.375rem;
|
|
536
|
+
margin: 0.125rem 0;
|
|
537
|
+
}
|
|
533
538
|
|
|
534
539
|
.tool-use-header {
|
|
535
540
|
padding: 0.5rem 0.75rem;
|
|
@@ -582,7 +587,7 @@
|
|
|
582
587
|
html.dark .tool-input-pre { background: rgba(255,255,255,0.03); }
|
|
583
588
|
|
|
584
589
|
.streaming-block-tool-result {
|
|
585
|
-
margin: 0.
|
|
590
|
+
margin: 0.125rem 0 0.25rem 0;
|
|
586
591
|
border-radius: 0.375rem;
|
|
587
592
|
background: var(--color-bg-code);
|
|
588
593
|
overflow: hidden;
|
|
@@ -1101,6 +1106,26 @@
|
|
|
1101
1106
|
border-top: 1px solid var(--color-border);
|
|
1102
1107
|
}
|
|
1103
1108
|
|
|
1109
|
+
.voice-reread-btn {
|
|
1110
|
+
position: absolute;
|
|
1111
|
+
top: 0.5rem;
|
|
1112
|
+
right: 0.5rem;
|
|
1113
|
+
background: none;
|
|
1114
|
+
border: 1px solid var(--color-border);
|
|
1115
|
+
border-radius: 0.25rem;
|
|
1116
|
+
cursor: pointer;
|
|
1117
|
+
padding: 0.25rem;
|
|
1118
|
+
color: var(--color-text-secondary);
|
|
1119
|
+
opacity: 0;
|
|
1120
|
+
transition: opacity 0.15s, background-color 0.15s;
|
|
1121
|
+
display: flex;
|
|
1122
|
+
align-items: center;
|
|
1123
|
+
justify-content: center;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
.voice-block:hover .voice-reread-btn { opacity: 1; }
|
|
1127
|
+
.voice-reread-btn:hover { background: var(--color-bg-primary); color: var(--color-primary); }
|
|
1128
|
+
|
|
1104
1129
|
/* ===== RESPONSIVE: TABLET ===== */
|
|
1105
1130
|
@media (min-width: 769px) and (max-width: 1024px) {
|
|
1106
1131
|
:root { --sidebar-width: 260px; }
|
|
@@ -1218,7 +1243,7 @@
|
|
|
1218
1243
|
|
|
1219
1244
|
/* --- Tool Use Block --- */
|
|
1220
1245
|
.block-tool-use {
|
|
1221
|
-
margin-bottom: 0.
|
|
1246
|
+
margin-bottom: 0.25rem;
|
|
1222
1247
|
border-radius: 0.5rem;
|
|
1223
1248
|
background: #ecfeff;
|
|
1224
1249
|
overflow: hidden;
|
|
@@ -1227,10 +1252,10 @@
|
|
|
1227
1252
|
html.dark .block-tool-use { background: #0c1a24; }
|
|
1228
1253
|
|
|
1229
1254
|
.block-tool-use .tool-header {
|
|
1230
|
-
padding: 0.
|
|
1255
|
+
padding: 0.375rem 0.75rem;
|
|
1231
1256
|
display: flex;
|
|
1232
1257
|
align-items: center;
|
|
1233
|
-
gap: 0.
|
|
1258
|
+
gap: 0.375rem;
|
|
1234
1259
|
background: #cffafe;
|
|
1235
1260
|
}
|
|
1236
1261
|
|
|
@@ -1263,7 +1288,7 @@
|
|
|
1263
1288
|
html.dark .block-tool-use .tool-header .tool-name code { background: #164e63; }
|
|
1264
1289
|
|
|
1265
1290
|
.block-tool-use .tool-params {
|
|
1266
|
-
padding: 0.75rem
|
|
1291
|
+
padding: 0.5rem 0.75rem;
|
|
1267
1292
|
}
|
|
1268
1293
|
|
|
1269
1294
|
.block-tool-use .tool-params .param-label {
|
|
@@ -1458,9 +1483,94 @@
|
|
|
1458
1483
|
text-align: center;
|
|
1459
1484
|
}
|
|
1460
1485
|
|
|
1486
|
+
/* --- Folded Tool Use (compact success-style bar) --- */
|
|
1487
|
+
.folded-tool {
|
|
1488
|
+
margin: 0.25rem 0;
|
|
1489
|
+
border-radius: 0.375rem;
|
|
1490
|
+
overflow: hidden;
|
|
1491
|
+
background: #f0fdf4;
|
|
1492
|
+
border: 1px solid #bbf7d0;
|
|
1493
|
+
}
|
|
1494
|
+
html.dark .folded-tool {
|
|
1495
|
+
background: #0a1f0f;
|
|
1496
|
+
border-color: #166534;
|
|
1497
|
+
}
|
|
1498
|
+
.folded-tool-bar {
|
|
1499
|
+
display: flex;
|
|
1500
|
+
align-items: center;
|
|
1501
|
+
gap: 0.375rem;
|
|
1502
|
+
padding: 0.3rem 0.625rem;
|
|
1503
|
+
cursor: pointer;
|
|
1504
|
+
user-select: none;
|
|
1505
|
+
list-style: none;
|
|
1506
|
+
font-size: 0.75rem;
|
|
1507
|
+
line-height: 1.3;
|
|
1508
|
+
background: #dcfce7;
|
|
1509
|
+
transition: background 0.15s;
|
|
1510
|
+
}
|
|
1511
|
+
html.dark .folded-tool-bar {
|
|
1512
|
+
background: #0f2b1a;
|
|
1513
|
+
}
|
|
1514
|
+
.folded-tool-bar::-webkit-details-marker { display: none; }
|
|
1515
|
+
.folded-tool-bar::marker { display: none; content: ''; }
|
|
1516
|
+
.folded-tool-bar::before {
|
|
1517
|
+
content: '\25b6';
|
|
1518
|
+
font-size: 0.5rem;
|
|
1519
|
+
margin-right: 0.125rem;
|
|
1520
|
+
display: inline-block;
|
|
1521
|
+
transition: transform 0.15s;
|
|
1522
|
+
color: #16a34a;
|
|
1523
|
+
flex-shrink: 0;
|
|
1524
|
+
}
|
|
1525
|
+
html.dark .folded-tool-bar::before { color: #4ade80; }
|
|
1526
|
+
.folded-tool[open] > .folded-tool-bar::before { transform: rotate(90deg); }
|
|
1527
|
+
.folded-tool-bar:hover { background: #bbf7d0; }
|
|
1528
|
+
html.dark .folded-tool-bar:hover { background: #14532d; }
|
|
1529
|
+
.folded-tool-icon {
|
|
1530
|
+
display: flex;
|
|
1531
|
+
align-items: center;
|
|
1532
|
+
color: #16a34a;
|
|
1533
|
+
width: 0.875rem;
|
|
1534
|
+
height: 0.875rem;
|
|
1535
|
+
flex-shrink: 0;
|
|
1536
|
+
}
|
|
1537
|
+
html.dark .folded-tool-icon { color: #4ade80; }
|
|
1538
|
+
.folded-tool-icon svg { width: 0.875rem; height: 0.875rem; }
|
|
1539
|
+
.folded-tool-name {
|
|
1540
|
+
font-weight: 600;
|
|
1541
|
+
color: #166534;
|
|
1542
|
+
font-family: 'Monaco','Menlo','Ubuntu Mono', monospace;
|
|
1543
|
+
font-size: 0.7rem;
|
|
1544
|
+
flex-shrink: 0;
|
|
1545
|
+
}
|
|
1546
|
+
html.dark .folded-tool-name { color: #86efac; }
|
|
1547
|
+
.folded-tool-desc {
|
|
1548
|
+
color: #15803d;
|
|
1549
|
+
font-family: 'Monaco','Menlo','Ubuntu Mono', monospace;
|
|
1550
|
+
font-size: 0.7rem;
|
|
1551
|
+
overflow: hidden;
|
|
1552
|
+
text-overflow: ellipsis;
|
|
1553
|
+
white-space: nowrap;
|
|
1554
|
+
flex: 1;
|
|
1555
|
+
min-width: 0;
|
|
1556
|
+
opacity: 0.8;
|
|
1557
|
+
}
|
|
1558
|
+
html.dark .folded-tool-desc { color: #4ade80; }
|
|
1559
|
+
.folded-tool-body {
|
|
1560
|
+
padding: 0.5rem 0.75rem;
|
|
1561
|
+
font-size: 0.75rem;
|
|
1562
|
+
border-top: 1px solid #bbf7d0;
|
|
1563
|
+
}
|
|
1564
|
+
html.dark .folded-tool-body { border-top-color: #166534; }
|
|
1565
|
+
.folded-tool-body .tool-input-pre {
|
|
1566
|
+
margin: 0;
|
|
1567
|
+
padding: 0.375rem 0.5rem;
|
|
1568
|
+
font-size: 0.7rem;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1461
1571
|
/* --- Tool Result Block --- */
|
|
1462
1572
|
.block-tool-result {
|
|
1463
|
-
margin-bottom: 0.
|
|
1573
|
+
margin-bottom: 0.25rem;
|
|
1464
1574
|
border-radius: 0.5rem;
|
|
1465
1575
|
overflow: hidden;
|
|
1466
1576
|
}
|
|
@@ -1471,7 +1581,7 @@
|
|
|
1471
1581
|
html.dark .block-tool-result.result-error { background: #1c0f0f; }
|
|
1472
1582
|
|
|
1473
1583
|
.block-tool-result .result-header {
|
|
1474
|
-
padding: 0.
|
|
1584
|
+
padding: 0.3rem 0.75rem;
|
|
1475
1585
|
display: flex;
|
|
1476
1586
|
align-items: center;
|
|
1477
1587
|
justify-content: space-between;
|
package/static/js/client.js
CHANGED
|
@@ -516,7 +516,7 @@ class AgentGUIClient {
|
|
|
516
516
|
if (block.type === 'text' && block.text) {
|
|
517
517
|
const text = block.text;
|
|
518
518
|
if (this.isHtmlContent(text)) {
|
|
519
|
-
return `<div class="html-content bg-white dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">${text}</div>`;
|
|
519
|
+
return `<div class="html-content bg-white dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">${this.sanitizeHtml(text)}</div>`;
|
|
520
520
|
}
|
|
521
521
|
const parts = this.parseMarkdownCodeBlocks(text);
|
|
522
522
|
if (parts.length === 1 && parts[0].type === 'text') {
|
|
@@ -524,7 +524,7 @@ class AgentGUIClient {
|
|
|
524
524
|
}
|
|
525
525
|
return parts.map(part => {
|
|
526
526
|
if (part.type === 'html') {
|
|
527
|
-
return `<div class="html-content bg-white dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">${part.content}</div>`;
|
|
527
|
+
return `<div class="html-content bg-white dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">${this.sanitizeHtml(part.content)}</div>`;
|
|
528
528
|
} else if (part.type === 'code') {
|
|
529
529
|
return this.renderCodeBlock(part.language, part.code);
|
|
530
530
|
}
|
|
@@ -682,9 +682,17 @@ class AgentGUIClient {
|
|
|
682
682
|
}
|
|
683
683
|
|
|
684
684
|
isHtmlContent(text) {
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
685
|
+
const htmlPattern = /<(?:div|table|section|article|ul|ol|dl|nav|header|footer|main|aside|figure|details|summary|h[1-6]|p|blockquote|pre|code|span|strong|em|a|img|br|hr|li|td|tr|th|thead|tbody|tfoot)\b[^>]*>/i;
|
|
686
|
+
return htmlPattern.test(text);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
sanitizeHtml(html) {
|
|
690
|
+
const dangerous = /<\s*\/?\s*(script|iframe|object|embed|applet|form|input|button|select|textarea)\b[^>]*>/gi;
|
|
691
|
+
let cleaned = html.replace(dangerous, '');
|
|
692
|
+
cleaned = cleaned.replace(/\s+on\w+\s*=\s*["'][^"']*["']/gi, '');
|
|
693
|
+
cleaned = cleaned.replace(/\s+on\w+\s*=\s*[^\s>]+/gi, '');
|
|
694
|
+
cleaned = cleaned.replace(/javascript\s*:/gi, '');
|
|
695
|
+
return cleaned;
|
|
688
696
|
}
|
|
689
697
|
|
|
690
698
|
parseMarkdownCodeBlocks(text) {
|
|
@@ -735,7 +743,7 @@ class AgentGUIClient {
|
|
|
735
743
|
Rendered HTML
|
|
736
744
|
</div>
|
|
737
745
|
<div class="html-content bg-white dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">
|
|
738
|
-
${code}
|
|
746
|
+
${this.sanitizeHtml(code)}
|
|
739
747
|
</div>
|
|
740
748
|
</div>
|
|
741
749
|
`;
|
|
@@ -751,7 +759,7 @@ class AgentGUIClient {
|
|
|
751
759
|
renderMessageContent(content) {
|
|
752
760
|
if (typeof content === 'string') {
|
|
753
761
|
if (this.isHtmlContent(content)) {
|
|
754
|
-
return `<div class="message-text"><div class="html-content bg-white dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">${content}</div></div>`;
|
|
762
|
+
return `<div class="message-text"><div class="html-content bg-white dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">${this.sanitizeHtml(content)}</div></div>`;
|
|
755
763
|
}
|
|
756
764
|
return `<div class="message-text">${this.escapeHtml(content)}</div>`;
|
|
757
765
|
} else if (content && typeof content === 'object' && content.type === 'claude_execution') {
|
|
@@ -762,7 +770,7 @@ class AgentGUIClient {
|
|
|
762
770
|
const parts = this.parseMarkdownCodeBlocks(block.text);
|
|
763
771
|
parts.forEach(part => {
|
|
764
772
|
if (part.type === 'html') {
|
|
765
|
-
html += `<div class="message-text"><div class="html-content bg-white dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">${part.content}</div></div>`;
|
|
773
|
+
html += `<div class="message-text"><div class="html-content bg-white dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">${this.sanitizeHtml(part.content)}</div></div>`;
|
|
766
774
|
} else if (part.type === 'text') {
|
|
767
775
|
html += `<div class="message-text">${this.escapeHtml(part.content)}</div>`;
|
|
768
776
|
} else if (part.type === 'code') {
|
|
@@ -778,7 +786,7 @@ class AgentGUIClient {
|
|
|
778
786
|
Rendered HTML
|
|
779
787
|
</div>
|
|
780
788
|
<div class="html-content bg-white dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">
|
|
781
|
-
${block.code}
|
|
789
|
+
${this.sanitizeHtml(block.code)}
|
|
782
790
|
</div>
|
|
783
791
|
</div>
|
|
784
792
|
`;
|
|
@@ -790,12 +798,14 @@ class AgentGUIClient {
|
|
|
790
798
|
let inputHtml = '';
|
|
791
799
|
if (block.input && Object.keys(block.input).length > 0) {
|
|
792
800
|
const inputStr = JSON.stringify(block.input, null, 2);
|
|
793
|
-
inputHtml = `<
|
|
801
|
+
inputHtml = `<div class="folded-tool-body"><pre class="tool-input-pre">${this.escapeHtml(inputStr)}</pre></div>`;
|
|
794
802
|
}
|
|
795
803
|
const tn = block.name || 'unknown';
|
|
796
804
|
const foldable = tn.startsWith('mcp__') || tn === 'Edit';
|
|
797
805
|
if (foldable) {
|
|
798
|
-
|
|
806
|
+
const dName = typeof StreamingRenderer !== 'undefined' ? StreamingRenderer.getToolDisplayName(tn) : tn;
|
|
807
|
+
const tTitle = typeof StreamingRenderer !== 'undefined' && block.input ? StreamingRenderer.getToolTitle(tn, block.input) : '';
|
|
808
|
+
html += `<details class="streaming-block-tool-use folded-tool"><summary class="folded-tool-bar"><span class="folded-tool-name">${this.escapeHtml(dName)}</span>${tTitle ? `<span class="folded-tool-desc">${this.escapeHtml(tTitle)}</span>` : ''}</summary>${inputHtml}</details>`;
|
|
799
809
|
} else {
|
|
800
810
|
html += `<div class="streaming-block-tool-use"><div class="tool-use-header"><span class="tool-use-icon">⚙</span> <span class="tool-use-name">${this.escapeHtml(tn)}</span></div>${inputHtml}</div>`;
|
|
801
811
|
}
|
|
@@ -1397,7 +1407,7 @@ class AgentGUIClient {
|
|
|
1397
1407
|
|
|
1398
1408
|
if (typeof msg.content === 'string') {
|
|
1399
1409
|
if (this.isHtmlContent(msg.content)) {
|
|
1400
|
-
contentHtml = `<div class="message-text"><div class="html-content bg-white dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">${msg.content}</div></div>`;
|
|
1410
|
+
contentHtml = `<div class="message-text"><div class="html-content bg-white dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">${this.sanitizeHtml(msg.content)}</div></div>`;
|
|
1401
1411
|
} else {
|
|
1402
1412
|
contentHtml = `<div class="message-text">${this.escapeHtml(msg.content)}</div>`;
|
|
1403
1413
|
}
|
|
@@ -1437,12 +1447,14 @@ class AgentGUIClient {
|
|
|
1437
1447
|
let inputHtml = '';
|
|
1438
1448
|
if (block.input && Object.keys(block.input).length > 0) {
|
|
1439
1449
|
const inputStr = JSON.stringify(block.input, null, 2);
|
|
1440
|
-
inputHtml = `<
|
|
1450
|
+
inputHtml = `<div class="folded-tool-body"><pre class="tool-input-pre">${this.escapeHtml(inputStr)}</pre></div>`;
|
|
1441
1451
|
}
|
|
1442
1452
|
const tn2 = block.name || 'unknown';
|
|
1443
1453
|
const foldable2 = tn2.startsWith('mcp__') || tn2 === 'Edit';
|
|
1444
1454
|
if (foldable2) {
|
|
1445
|
-
|
|
1455
|
+
const dName2 = typeof StreamingRenderer !== 'undefined' ? StreamingRenderer.getToolDisplayName(tn2) : tn2;
|
|
1456
|
+
const tTitle2 = typeof StreamingRenderer !== 'undefined' && block.input ? StreamingRenderer.getToolTitle(tn2, block.input) : '';
|
|
1457
|
+
contentHtml += `<details class="streaming-block-tool-use folded-tool"><summary class="folded-tool-bar"><span class="folded-tool-name">${this.escapeHtml(dName2)}</span>${tTitle2 ? `<span class="folded-tool-desc">${this.escapeHtml(tTitle2)}</span>` : ''}</summary>${inputHtml}</details>`;
|
|
1446
1458
|
} else {
|
|
1447
1459
|
contentHtml += `<div class="streaming-block-tool-use"><div class="tool-use-header"><span class="tool-use-icon">⚙</span> <span class="tool-use-name">${this.escapeHtml(tn2)}</span></div>${inputHtml}</div>`;
|
|
1448
1460
|
}
|
|
@@ -362,12 +362,30 @@ class StreamingRenderer {
|
|
|
362
362
|
div.className = 'block-text';
|
|
363
363
|
|
|
364
364
|
const text = block.text || '';
|
|
365
|
-
|
|
366
|
-
|
|
365
|
+
if (this.containsHtmlTags(text)) {
|
|
366
|
+
div.innerHTML = this.sanitizeHtml(text);
|
|
367
|
+
div.classList.add('html-content');
|
|
368
|
+
} else {
|
|
369
|
+
div.innerHTML = this.parseAndRenderMarkdown(text);
|
|
370
|
+
}
|
|
367
371
|
|
|
368
372
|
return div;
|
|
369
373
|
}
|
|
370
374
|
|
|
375
|
+
containsHtmlTags(text) {
|
|
376
|
+
const htmlPattern = /<(?:div|table|section|article|ul|ol|dl|nav|header|footer|main|aside|figure|details|summary|h[1-6]|p|blockquote|pre|code|span|strong|em|a|img|br|hr|li|td|tr|th|thead|tbody|tfoot)\b[^>]*>/i;
|
|
377
|
+
return htmlPattern.test(text);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
sanitizeHtml(html) {
|
|
381
|
+
const dangerous = /<\s*\/?\s*(script|iframe|object|embed|applet|form|input|button|select|textarea)\b[^>]*>/gi;
|
|
382
|
+
let cleaned = html.replace(dangerous, '');
|
|
383
|
+
cleaned = cleaned.replace(/\s+on\w+\s*=\s*["'][^"']*["']/gi, '');
|
|
384
|
+
cleaned = cleaned.replace(/\s+on\w+\s*=\s*[^\s>]+/gi, '');
|
|
385
|
+
cleaned = cleaned.replace(/javascript\s*:/gi, '');
|
|
386
|
+
return cleaned;
|
|
387
|
+
}
|
|
388
|
+
|
|
371
389
|
/**
|
|
372
390
|
* Parse markdown and render links, code, bold, italic
|
|
373
391
|
*/
|
|
@@ -623,6 +641,53 @@ class StreamingRenderer {
|
|
|
623
641
|
/**
|
|
624
642
|
* Render tool use block with smart parameter display
|
|
625
643
|
*/
|
|
644
|
+
getToolUseTitle(toolName, input) {
|
|
645
|
+
const normalizedName = toolName.replace(/^mcp__[^_]+__/, '');
|
|
646
|
+
if (normalizedName === 'Edit' && input.file_path) {
|
|
647
|
+
const parts = input.file_path.split('/');
|
|
648
|
+
const fileName = parts.pop();
|
|
649
|
+
const dir = parts.slice(-2).join('/');
|
|
650
|
+
return dir ? `${dir}/${fileName}` : fileName;
|
|
651
|
+
}
|
|
652
|
+
if (normalizedName === 'Read' && input.file_path) {
|
|
653
|
+
const parts = input.file_path.split('/');
|
|
654
|
+
return parts.pop();
|
|
655
|
+
}
|
|
656
|
+
if (normalizedName === 'Write' && input.file_path) {
|
|
657
|
+
const parts = input.file_path.split('/');
|
|
658
|
+
return parts.pop();
|
|
659
|
+
}
|
|
660
|
+
if (normalizedName === 'Bash' || normalizedName === 'bash') {
|
|
661
|
+
const cmd = input.command || input.commands || '';
|
|
662
|
+
const cmdText = typeof cmd === 'string' ? cmd : JSON.stringify(cmd);
|
|
663
|
+
return cmdText.length > 60 ? cmdText.substring(0, 57) + '...' : cmdText;
|
|
664
|
+
}
|
|
665
|
+
if (normalizedName === 'Glob' && input.pattern) return input.pattern;
|
|
666
|
+
if (normalizedName === 'Grep' && input.pattern) return input.pattern;
|
|
667
|
+
if (normalizedName === 'WebFetch' && input.url) {
|
|
668
|
+
try { return new URL(input.url).hostname; } catch (e) { return input.url.substring(0, 40); }
|
|
669
|
+
}
|
|
670
|
+
if (normalizedName === 'WebSearch' && input.query) return input.query.substring(0, 50);
|
|
671
|
+
if (input.file_path) return input.file_path.split('/').pop();
|
|
672
|
+
if (input.command) {
|
|
673
|
+
const c = typeof input.command === 'string' ? input.command : JSON.stringify(input.command);
|
|
674
|
+
return c.length > 50 ? c.substring(0, 47) + '...' : c;
|
|
675
|
+
}
|
|
676
|
+
if (input.query) return input.query.substring(0, 50);
|
|
677
|
+
return '';
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
getToolUseDisplayName(toolName) {
|
|
681
|
+
const normalized = toolName.replace(/^mcp__[^_]+__/, '');
|
|
682
|
+
const knownTools = ['Read','Write','Edit','Bash','Glob','Grep','WebFetch','WebSearch','TodoWrite','Task','NotebookEdit'];
|
|
683
|
+
if (knownTools.includes(normalized)) return normalized;
|
|
684
|
+
if (toolName.startsWith('mcp__')) {
|
|
685
|
+
const parts = toolName.split('__');
|
|
686
|
+
return parts.length >= 3 ? parts[2] : parts[parts.length - 1];
|
|
687
|
+
}
|
|
688
|
+
return normalized || toolName;
|
|
689
|
+
}
|
|
690
|
+
|
|
626
691
|
renderBlockToolUse(block, context) {
|
|
627
692
|
const toolName = block.name || 'unknown';
|
|
628
693
|
const input = block.input || {};
|
|
@@ -630,17 +695,20 @@ class StreamingRenderer {
|
|
|
630
695
|
|
|
631
696
|
if (shouldFold) {
|
|
632
697
|
const details = document.createElement('details');
|
|
633
|
-
details.className = 'block-tool-use';
|
|
698
|
+
details.className = 'block-tool-use folded-tool';
|
|
634
699
|
const summary = document.createElement('summary');
|
|
635
|
-
summary.className = 'tool-
|
|
636
|
-
|
|
700
|
+
summary.className = 'folded-tool-bar';
|
|
701
|
+
const displayName = this.getToolUseDisplayName(toolName);
|
|
702
|
+
const titleInfo = this.getToolUseTitle(toolName, input);
|
|
637
703
|
summary.innerHTML = `
|
|
638
|
-
<span class="tool-icon">${this.getToolIcon(toolName)}</span>
|
|
639
|
-
<span class="tool-name"
|
|
704
|
+
<span class="folded-tool-icon">${this.getToolIcon(toolName)}</span>
|
|
705
|
+
<span class="folded-tool-name">${this.escapeHtml(displayName)}</span>
|
|
706
|
+
${titleInfo ? `<span class="folded-tool-desc">${this.escapeHtml(titleInfo)}</span>` : ''}
|
|
640
707
|
`;
|
|
641
708
|
details.appendChild(summary);
|
|
642
709
|
if (Object.keys(input).length > 0) {
|
|
643
710
|
const paramsDiv = document.createElement('div');
|
|
711
|
+
paramsDiv.className = 'folded-tool-body';
|
|
644
712
|
paramsDiv.innerHTML = this.renderSmartParams(toolName, input);
|
|
645
713
|
details.appendChild(paramsDiv);
|
|
646
714
|
}
|
|
@@ -1004,6 +1072,33 @@ class StreamingRenderer {
|
|
|
1004
1072
|
return `<details class="collapsible-code"><summary class="collapsible-code-summary">${summaryLabel}</summary>${codeHtml}</details>`;
|
|
1005
1073
|
}
|
|
1006
1074
|
|
|
1075
|
+
static getToolDisplayName(toolName) {
|
|
1076
|
+
const normalized = toolName.replace(/^mcp__[^_]+__/, '');
|
|
1077
|
+
const knownTools = ['Read','Write','Edit','Bash','Glob','Grep','WebFetch','WebSearch','TodoWrite','Task','NotebookEdit'];
|
|
1078
|
+
if (knownTools.includes(normalized)) return normalized;
|
|
1079
|
+
if (toolName.startsWith('mcp__')) {
|
|
1080
|
+
const parts = toolName.split('__');
|
|
1081
|
+
return parts.length >= 3 ? parts[2] : parts[parts.length - 1];
|
|
1082
|
+
}
|
|
1083
|
+
return normalized || toolName;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
static getToolTitle(toolName, input) {
|
|
1087
|
+
const n = toolName.replace(/^mcp__[^_]+__/, '');
|
|
1088
|
+
if (n === 'Edit' && input.file_path) { const p = input.file_path.split('/'); const f = p.pop(); const d = p.slice(-2).join('/'); return d ? d+'/'+f : f; }
|
|
1089
|
+
if (n === 'Read' && input.file_path) return input.file_path.split('/').pop();
|
|
1090
|
+
if (n === 'Write' && input.file_path) return input.file_path.split('/').pop();
|
|
1091
|
+
if ((n === 'Bash' || n === 'bash') && (input.command || input.commands)) { const c = typeof (input.command||input.commands) === 'string' ? (input.command||input.commands) : JSON.stringify(input.command||input.commands); return c.length > 60 ? c.substring(0,57)+'...' : c; }
|
|
1092
|
+
if (n === 'Glob' && input.pattern) return input.pattern;
|
|
1093
|
+
if (n === 'Grep' && input.pattern) return input.pattern;
|
|
1094
|
+
if (n === 'WebFetch' && input.url) { try { return new URL(input.url).hostname; } catch(e) { return input.url.substring(0,40); } }
|
|
1095
|
+
if (n === 'WebSearch' && input.query) return input.query.substring(0,50);
|
|
1096
|
+
if (input.file_path) return input.file_path.split('/').pop();
|
|
1097
|
+
if (input.command) { const c = typeof input.command === 'string' ? input.command : JSON.stringify(input.command); return c.length > 50 ? c.substring(0,47)+'...' : c; }
|
|
1098
|
+
if (input.query) return input.query.substring(0,50);
|
|
1099
|
+
return '';
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1007
1102
|
/**
|
|
1008
1103
|
* Static HTML version of parameter rendering
|
|
1009
1104
|
*/
|
|
@@ -1182,7 +1277,7 @@ class StreamingRenderer {
|
|
|
1182
1277
|
${cost ? `<div class="result-stat"><span class="stat-icon">💰</span><span class="stat-value">${this.escapeHtml(cost)}</span><span class="stat-label">cost</span></div>` : ''}
|
|
1183
1278
|
${turns ? `<div class="result-stat"><span class="stat-icon">🔄</span><span class="stat-value">${this.escapeHtml(String(turns))}</span><span class="stat-label">turns</span></div>` : ''}
|
|
1184
1279
|
</div>
|
|
1185
|
-
${block.result ? `<div class="result-content">${
|
|
1280
|
+
${block.result ? `<div class="result-content">${(() => { const r = typeof block.result === 'string' ? block.result : JSON.stringify(block.result, null, 2); return this.containsHtmlTags(r) ? '<div class="html-content">' + this.sanitizeHtml(r) + '</div>' : this.escapeHtml(r); })()}</div>` : ''}
|
|
1186
1281
|
`;
|
|
1187
1282
|
|
|
1188
1283
|
return div;
|
package/static/js/voice.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
var isRecording = false;
|
|
4
4
|
var ttsEnabled = true;
|
|
5
5
|
var voiceActive = false;
|
|
6
|
-
var lastSpokenBlockIndex = -1;
|
|
7
6
|
var currentConversationId = null;
|
|
8
7
|
var speechQueue = [];
|
|
9
8
|
var isSpeaking = false;
|
|
@@ -13,6 +12,8 @@
|
|
|
13
12
|
var scriptNode = null;
|
|
14
13
|
var recordedChunks = [];
|
|
15
14
|
var TARGET_SAMPLE_RATE = 16000;
|
|
15
|
+
var spokenChunks = new Set();
|
|
16
|
+
var isLoadingHistory = false;
|
|
16
17
|
|
|
17
18
|
function init() {
|
|
18
19
|
setupTTSToggle();
|
|
@@ -61,14 +62,28 @@
|
|
|
61
62
|
var micBtn = document.getElementById('voiceMicBtn');
|
|
62
63
|
if (micBtn) {
|
|
63
64
|
micBtn.removeAttribute('disabled');
|
|
64
|
-
micBtn.title = '
|
|
65
|
-
micBtn.addEventListener('
|
|
65
|
+
micBtn.title = 'Hold to record';
|
|
66
|
+
micBtn.addEventListener('mousedown', function(e) {
|
|
66
67
|
e.preventDefault();
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
startRecording();
|
|
69
|
+
});
|
|
70
|
+
micBtn.addEventListener('mouseup', function(e) {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
stopRecording();
|
|
73
|
+
});
|
|
74
|
+
micBtn.addEventListener('mouseleave', function(e) {
|
|
75
|
+
if (isRecording) stopRecording();
|
|
76
|
+
});
|
|
77
|
+
micBtn.addEventListener('touchstart', function(e) {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
startRecording();
|
|
80
|
+
});
|
|
81
|
+
micBtn.addEventListener('touchend', function(e) {
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
stopRecording();
|
|
84
|
+
});
|
|
85
|
+
micBtn.addEventListener('touchcancel', function(e) {
|
|
86
|
+
if (isRecording) stopRecording();
|
|
72
87
|
});
|
|
73
88
|
}
|
|
74
89
|
var sendBtn = document.getElementById('voiceSendBtn');
|
|
@@ -92,6 +107,35 @@
|
|
|
92
107
|
return result;
|
|
93
108
|
}
|
|
94
109
|
|
|
110
|
+
function encodeWav(float32Audio, sampleRate) {
|
|
111
|
+
var numSamples = float32Audio.length;
|
|
112
|
+
var bytesPerSample = 2;
|
|
113
|
+
var dataSize = numSamples * bytesPerSample;
|
|
114
|
+
var buffer = new ArrayBuffer(44 + dataSize);
|
|
115
|
+
var view = new DataView(buffer);
|
|
116
|
+
function writeStr(off, str) {
|
|
117
|
+
for (var i = 0; i < str.length; i++) view.setUint8(off + i, str.charCodeAt(i));
|
|
118
|
+
}
|
|
119
|
+
writeStr(0, 'RIFF');
|
|
120
|
+
view.setUint32(4, 36 + dataSize, true);
|
|
121
|
+
writeStr(8, 'WAVE');
|
|
122
|
+
writeStr(12, 'fmt ');
|
|
123
|
+
view.setUint32(16, 16, true);
|
|
124
|
+
view.setUint16(20, 1, true);
|
|
125
|
+
view.setUint16(22, 1, true);
|
|
126
|
+
view.setUint32(24, sampleRate, true);
|
|
127
|
+
view.setUint32(28, sampleRate * bytesPerSample, true);
|
|
128
|
+
view.setUint16(32, bytesPerSample, true);
|
|
129
|
+
view.setUint16(34, 16, true);
|
|
130
|
+
writeStr(36, 'data');
|
|
131
|
+
view.setUint32(40, dataSize, true);
|
|
132
|
+
for (var i = 0; i < numSamples; i++) {
|
|
133
|
+
var s = Math.max(-1, Math.min(1, float32Audio[i]));
|
|
134
|
+
view.setInt16(44 + i * 2, s < 0 ? s * 32768 : s * 32767, true);
|
|
135
|
+
}
|
|
136
|
+
return buffer;
|
|
137
|
+
}
|
|
138
|
+
|
|
95
139
|
async function startRecording() {
|
|
96
140
|
if (isRecording) return;
|
|
97
141
|
var el = document.getElementById('voiceTranscript');
|
|
@@ -146,11 +190,11 @@
|
|
|
146
190
|
var resampled = resampleBuffer(merged, sourceSampleRate, TARGET_SAMPLE_RATE);
|
|
147
191
|
if (el) el.textContent = 'Transcribing...';
|
|
148
192
|
try {
|
|
149
|
-
var
|
|
193
|
+
var wavBuffer = encodeWav(resampled, TARGET_SAMPLE_RATE);
|
|
150
194
|
var resp = await fetch(BASE + '/api/stt', {
|
|
151
195
|
method: 'POST',
|
|
152
|
-
headers: { 'Content-Type': '
|
|
153
|
-
body:
|
|
196
|
+
headers: { 'Content-Type': 'audio/wav' },
|
|
197
|
+
body: wavBuffer
|
|
154
198
|
});
|
|
155
199
|
var data = await resp.json();
|
|
156
200
|
if (data.text) {
|
|
@@ -240,6 +284,10 @@
|
|
|
240
284
|
}
|
|
241
285
|
}
|
|
242
286
|
|
|
287
|
+
function stripHtml(text) {
|
|
288
|
+
return text.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();
|
|
289
|
+
}
|
|
290
|
+
|
|
243
291
|
function addVoiceBlock(text, isUser) {
|
|
244
292
|
var container = document.getElementById('voiceMessages');
|
|
245
293
|
if (!container) return;
|
|
@@ -247,13 +295,23 @@
|
|
|
247
295
|
if (emptyMsg) emptyMsg.remove();
|
|
248
296
|
var div = document.createElement('div');
|
|
249
297
|
div.className = 'voice-block' + (isUser ? ' voice-block-user' : '');
|
|
250
|
-
div.textContent = text;
|
|
298
|
+
div.textContent = isUser ? text : stripHtml(text);
|
|
299
|
+
if (!isUser) {
|
|
300
|
+
var rereadBtn = document.createElement('button');
|
|
301
|
+
rereadBtn.className = 'voice-reread-btn';
|
|
302
|
+
rereadBtn.title = 'Re-read aloud';
|
|
303
|
+
rereadBtn.innerHTML = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>';
|
|
304
|
+
rereadBtn.addEventListener('click', function() {
|
|
305
|
+
speak(text);
|
|
306
|
+
});
|
|
307
|
+
div.appendChild(rereadBtn);
|
|
308
|
+
}
|
|
251
309
|
container.appendChild(div);
|
|
252
310
|
scrollVoiceToBottom();
|
|
253
311
|
return div;
|
|
254
312
|
}
|
|
255
313
|
|
|
256
|
-
function addVoiceResultBlock(block) {
|
|
314
|
+
function addVoiceResultBlock(block, autoSpeak) {
|
|
257
315
|
var container = document.getElementById('voiceMessages');
|
|
258
316
|
if (!container) return;
|
|
259
317
|
var emptyMsg = container.querySelector('.voice-empty');
|
|
@@ -267,9 +325,10 @@
|
|
|
267
325
|
if (block.result) {
|
|
268
326
|
resultText = typeof block.result === 'string' ? block.result : JSON.stringify(block.result);
|
|
269
327
|
}
|
|
328
|
+
var displayText = stripHtml(resultText);
|
|
270
329
|
var html = '';
|
|
271
|
-
if (
|
|
272
|
-
html += '<div>' + escapeHtml(
|
|
330
|
+
if (displayText) {
|
|
331
|
+
html += '<div>' + escapeHtml(displayText) + '</div>';
|
|
273
332
|
}
|
|
274
333
|
if (duration || cost) {
|
|
275
334
|
html += '<div class="voice-result-stats">';
|
|
@@ -282,9 +341,19 @@
|
|
|
282
341
|
html = isError ? 'Execution failed' : 'Execution complete';
|
|
283
342
|
}
|
|
284
343
|
div.innerHTML = html;
|
|
344
|
+
if (resultText) {
|
|
345
|
+
var rereadBtn = document.createElement('button');
|
|
346
|
+
rereadBtn.className = 'voice-reread-btn';
|
|
347
|
+
rereadBtn.title = 'Re-read aloud';
|
|
348
|
+
rereadBtn.innerHTML = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>';
|
|
349
|
+
rereadBtn.addEventListener('click', function() {
|
|
350
|
+
speak(resultText);
|
|
351
|
+
});
|
|
352
|
+
div.appendChild(rereadBtn);
|
|
353
|
+
}
|
|
285
354
|
container.appendChild(div);
|
|
286
355
|
scrollVoiceToBottom();
|
|
287
|
-
if (ttsEnabled && resultText) {
|
|
356
|
+
if (autoSpeak && ttsEnabled && resultText) {
|
|
288
357
|
speak(resultText);
|
|
289
358
|
}
|
|
290
359
|
return div;
|
|
@@ -305,31 +374,33 @@
|
|
|
305
374
|
var data = e.detail;
|
|
306
375
|
if (!data) return;
|
|
307
376
|
if (data.type === 'streaming_progress' && data.block) {
|
|
308
|
-
handleVoiceBlock(data.block);
|
|
377
|
+
handleVoiceBlock(data.block, true);
|
|
309
378
|
}
|
|
310
379
|
if (data.type === 'streaming_start') {
|
|
311
|
-
|
|
380
|
+
spokenChunks = new Set();
|
|
312
381
|
}
|
|
313
382
|
});
|
|
314
383
|
window.addEventListener('conversation-selected', function(e) {
|
|
315
384
|
currentConversationId = e.detail.conversationId;
|
|
385
|
+
stopSpeaking();
|
|
386
|
+
spokenChunks = new Set();
|
|
316
387
|
if (voiceActive) {
|
|
317
388
|
loadVoiceBlocks(currentConversationId);
|
|
318
389
|
}
|
|
319
390
|
});
|
|
320
391
|
}
|
|
321
392
|
|
|
322
|
-
function handleVoiceBlock(block) {
|
|
393
|
+
function handleVoiceBlock(block, isNew) {
|
|
323
394
|
if (!block || !block.type) return;
|
|
324
395
|
if (block.type === 'text' && block.text) {
|
|
325
396
|
var div = addVoiceBlock(block.text, false);
|
|
326
|
-
if (div && ttsEnabled) {
|
|
397
|
+
if (div && isNew && ttsEnabled) {
|
|
327
398
|
div.classList.add('speaking');
|
|
328
399
|
speak(block.text);
|
|
329
400
|
setTimeout(function() { div.classList.remove('speaking'); }, 2000);
|
|
330
401
|
}
|
|
331
402
|
} else if (block.type === 'result') {
|
|
332
|
-
addVoiceResultBlock(block);
|
|
403
|
+
addVoiceResultBlock(block, isNew);
|
|
333
404
|
}
|
|
334
405
|
}
|
|
335
406
|
|
|
@@ -341,9 +412,11 @@
|
|
|
341
412
|
showVoiceEmpty(container);
|
|
342
413
|
return;
|
|
343
414
|
}
|
|
415
|
+
isLoadingHistory = true;
|
|
344
416
|
fetch(BASE + '/api/conversations/' + conversationId + '/chunks')
|
|
345
417
|
.then(function(res) { return res.json(); })
|
|
346
418
|
.then(function(data) {
|
|
419
|
+
isLoadingHistory = false;
|
|
347
420
|
if (!data.ok || !Array.isArray(data.chunks) || data.chunks.length === 0) {
|
|
348
421
|
showVoiceEmpty(container);
|
|
349
422
|
return;
|
|
@@ -356,19 +429,20 @@
|
|
|
356
429
|
addVoiceBlock(block.text, false);
|
|
357
430
|
hasContent = true;
|
|
358
431
|
} else if (block.type === 'result') {
|
|
359
|
-
addVoiceResultBlock(block);
|
|
432
|
+
addVoiceResultBlock(block, false);
|
|
360
433
|
hasContent = true;
|
|
361
434
|
}
|
|
362
435
|
});
|
|
363
436
|
if (!hasContent) showVoiceEmpty(container);
|
|
364
437
|
})
|
|
365
438
|
.catch(function() {
|
|
439
|
+
isLoadingHistory = false;
|
|
366
440
|
showVoiceEmpty(container);
|
|
367
441
|
});
|
|
368
442
|
}
|
|
369
443
|
|
|
370
444
|
function showVoiceEmpty(container) {
|
|
371
|
-
container.innerHTML = '<div class="voice-empty"><div class="voice-empty-icon"><svg viewBox="0 0 24 24" width="64" height="64" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg></div><div>
|
|
445
|
+
container.innerHTML = '<div class="voice-empty"><div class="voice-empty-icon"><svg viewBox="0 0 24 24" width="64" height="64" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg></div><div>Hold the microphone button to record.<br>Release to transcribe. Tap Send to submit.<br>New responses will be read aloud.</div></div>';
|
|
372
446
|
}
|
|
373
447
|
|
|
374
448
|
function activate() {
|
package/static/styles.css
CHANGED
|
@@ -281,10 +281,10 @@ html, body {
|
|
|
281
281
|
.chat-messages {
|
|
282
282
|
flex: 1;
|
|
283
283
|
overflow-y: auto;
|
|
284
|
-
padding: 2rem;
|
|
284
|
+
padding: 1rem 2rem;
|
|
285
285
|
display: flex;
|
|
286
286
|
flex-direction: column;
|
|
287
|
-
gap:
|
|
287
|
+
gap: 0.5rem;
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
.chat-context-header {
|
|
@@ -593,7 +593,7 @@ html, body {
|
|
|
593
593
|
.message {
|
|
594
594
|
display: flex;
|
|
595
595
|
flex-wrap: wrap;
|
|
596
|
-
gap:
|
|
596
|
+
gap: 0.375rem;
|
|
597
597
|
animation: slideUp 0.2s ease;
|
|
598
598
|
width: 100%;
|
|
599
599
|
}
|
|
@@ -631,7 +631,7 @@ html, body {
|
|
|
631
631
|
max-width: 70%;
|
|
632
632
|
display: flex;
|
|
633
633
|
flex-direction: column;
|
|
634
|
-
gap: 0.
|
|
634
|
+
gap: 0.25rem;
|
|
635
635
|
}
|
|
636
636
|
|
|
637
637
|
.stream-text-block {
|
|
@@ -788,7 +788,7 @@ html, body {
|
|
|
788
788
|
border-radius: 0.5rem;
|
|
789
789
|
overflow: visible;
|
|
790
790
|
background: var(--bg-secondary);
|
|
791
|
-
margin: 0.
|
|
791
|
+
margin: 0.25rem 0;
|
|
792
792
|
}
|
|
793
793
|
|
|
794
794
|
.html-header {
|
|
@@ -810,7 +810,7 @@ html, body {
|
|
|
810
810
|
border-radius: 0.5rem;
|
|
811
811
|
overflow-x: auto;
|
|
812
812
|
background: var(--bg-tertiary);
|
|
813
|
-
margin: 0.
|
|
813
|
+
margin: 0.25rem 0;
|
|
814
814
|
font-family: 'Monaco', 'Courier New', monospace;
|
|
815
815
|
font-size: 0.8rem;
|
|
816
816
|
}
|
|
@@ -828,7 +828,7 @@ html, body {
|
|
|
828
828
|
.collapsible-code {
|
|
829
829
|
border-radius: 0.375rem;
|
|
830
830
|
overflow: hidden;
|
|
831
|
-
margin: 0.
|
|
831
|
+
margin: 0.25rem 0;
|
|
832
832
|
border: 1px solid #334155;
|
|
833
833
|
background: #1e293b;
|
|
834
834
|
}
|
|
@@ -1271,7 +1271,7 @@ html, body {
|
|
|
1271
1271
|
}
|
|
1272
1272
|
|
|
1273
1273
|
.chat-messages {
|
|
1274
|
-
padding:
|
|
1274
|
+
padding: 0.75rem 1rem;
|
|
1275
1275
|
}
|
|
1276
1276
|
|
|
1277
1277
|
.chat-input-section {
|
|
@@ -1363,8 +1363,8 @@ html, body {
|
|
|
1363
1363
|
/* Small height viewports */
|
|
1364
1364
|
@media (max-height: 600px) {
|
|
1365
1365
|
.chat-messages {
|
|
1366
|
-
padding:
|
|
1367
|
-
gap: 0.
|
|
1366
|
+
padding: 0.75rem;
|
|
1367
|
+
gap: 0.375rem;
|
|
1368
1368
|
}
|
|
1369
1369
|
|
|
1370
1370
|
.chat-input-section {
|
|
@@ -1421,8 +1421,8 @@ html, body {
|
|
|
1421
1421
|
|
|
1422
1422
|
@media (max-height: 500px) {
|
|
1423
1423
|
.chat-messages {
|
|
1424
|
-
padding: 0.
|
|
1425
|
-
gap: 0.
|
|
1424
|
+
padding: 0.5rem;
|
|
1425
|
+
gap: 0.25rem;
|
|
1426
1426
|
}
|
|
1427
1427
|
|
|
1428
1428
|
.chat-input-section {
|
|
@@ -1663,8 +1663,8 @@ p code {
|
|
|
1663
1663
|
.segment-tool-use {
|
|
1664
1664
|
background: #f0f8ff;
|
|
1665
1665
|
border-left: 4px solid #007acc;
|
|
1666
|
-
padding:
|
|
1667
|
-
margin:
|
|
1666
|
+
padding: 0.5rem;
|
|
1667
|
+
margin: 0.25rem 0;
|
|
1668
1668
|
border-radius: 4px;
|
|
1669
1669
|
}
|
|
1670
1670
|
|
|
@@ -1690,8 +1690,8 @@ p code {
|
|
|
1690
1690
|
.segment-tool-result {
|
|
1691
1691
|
background: #fff9e6;
|
|
1692
1692
|
border-left: 4px solid #ffb300;
|
|
1693
|
-
padding:
|
|
1694
|
-
margin:
|
|
1693
|
+
padding: 0.5rem;
|
|
1694
|
+
margin: 0.25rem 0;
|
|
1695
1695
|
border-radius: 4px;
|
|
1696
1696
|
}
|
|
1697
1697
|
|
|
@@ -1744,12 +1744,12 @@ p code {
|
|
|
1744
1744
|
.execution-blocks {
|
|
1745
1745
|
display: flex;
|
|
1746
1746
|
flex-direction: column;
|
|
1747
|
-
gap: 0.
|
|
1747
|
+
gap: 0.25rem;
|
|
1748
1748
|
width: 100%;
|
|
1749
1749
|
}
|
|
1750
1750
|
|
|
1751
1751
|
.message-block {
|
|
1752
|
-
padding: 0.75rem
|
|
1752
|
+
padding: 0.5rem 0.75rem;
|
|
1753
1753
|
border-radius: 0.5rem;
|
|
1754
1754
|
background: var(--bg-secondary);
|
|
1755
1755
|
word-wrap: break-word;
|
|
@@ -1819,7 +1819,11 @@ p code {
|
|
|
1819
1819
|
.block-tool-use {
|
|
1820
1820
|
background: rgba(59, 130, 246, 0.05);
|
|
1821
1821
|
border-radius: 0.5rem;
|
|
1822
|
-
padding: 0.
|
|
1822
|
+
padding: 0.5rem;
|
|
1823
|
+
}
|
|
1824
|
+
.block-tool-use.folded-tool {
|
|
1825
|
+
padding: 0;
|
|
1826
|
+
background: none;
|
|
1823
1827
|
}
|
|
1824
1828
|
|
|
1825
1829
|
.tool-name {
|
|
@@ -1858,7 +1862,7 @@ p code {
|
|
|
1858
1862
|
.block-tool-result {
|
|
1859
1863
|
background: rgba(16, 185, 129, 0.05);
|
|
1860
1864
|
border-radius: 0.5rem;
|
|
1861
|
-
padding: 0.
|
|
1865
|
+
padding: 0.5rem;
|
|
1862
1866
|
}
|
|
1863
1867
|
|
|
1864
1868
|
.block-tool-result strong {
|