feature: copy text button, nudge & fix: various styles
- Copy button for all messages - Nudge button front-end - Fixed various non-styled light mode elements to do -> css cleanup and whisper loading
Alessandro committed
Dec 3, 2024 at 04:03 UTC
fedf2d4bdc6357f9e50e76b9202e06081c66db5e
3 files changed
+219
-31
webui/index.css
+127
-25
@@ -540,6 +540,7 @@ pre {
540
.message-user {
541
background-color: #4a4a4a;
542
border-bottom-right-radius: var(--spacing-xs);
543
+ min-width: 195px;
544
text-align: end;
545
}
546
@@ -590,6 +591,7 @@ font-size: var(--font-size-small)
591
color: #e0e0e0;
592
}
593
.message-agent-response {
594
+ min-width: 255px;
595
background-color: #1f3c1e;
596
color: #e0e0e0;
597
}
@@ -641,7 +643,7 @@ font-size: var(--font-size-small)
643
.msg-kvps th,
644
.msg-kvps td {
645
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
644
- align-content: baseline;
646
+ align-content: center;
647
padding: 0.25rem;
648
text-align: left;
649
}
@@ -877,7 +879,7 @@ font-size: var(--font-size-small)
879
}
880
881
.attachment-item:hover {
880
- background: var(--color-secondary-dark);
882
+ background: var(--color-secondary);
883
}
884
885
.attachment-item.file-type {
@@ -885,7 +887,7 @@ font-size: var(--font-size-small)
887
}
888
889
.attachment-item:hover {
888
- background: var(--color-secondary-dark);
890
+ background: var(--color-secondary);
891
}
892
893
.attachment-preview {
@@ -982,10 +984,6 @@ font-size: var(--font-size-small)
984
text-transform: uppercase;
985
}
986
985
-.file-preview:hover {
986
- background: var(--color-secondary-dark);
987
-}
988
-
987
.remove-attachment {
988
position: absolute;
989
top: -6px;
@@ -1013,21 +1011,6 @@ font-size: var(--font-size-small)
1011
transform: scale(0.9);
1012
}
1013
1016
-/* File type specific styles
1017
-.extension[data-type="image"] {
1018
- background: #4CAF50;
1019
-}
1020
-
1021
-.extension[data-type="code"] {
1022
- background: #2196F3;
1023
-}
1024
-
1025
-.extension[data-type="document"] {
1026
- background: #FF9800;
1027
-}
1028
-
1029
-*/
1030
-
1014
/* Error handling */
1015
.image-error {
1016
border: 1px solid var(--color-error);
@@ -1075,7 +1058,7 @@ font-size: var(--font-size-small)
1058
border-radius: 5px;
1059
color: var(--color-text);
1060
font-family: "Rubik", Arial, Helvetica, sans-serif;
1078
- font-size: 0.7rem;
1061
+ font-size: 0.6rem;
1062
padding: 6px var(--spacing-sm);
1063
cursor: pointer;
1064
opacity: 0.8;
@@ -1326,6 +1309,11 @@ input:checked + .slider:before {
1309
--color-input-focus: var(--color-input-focus-light);
1310
}
1311
1312
+.light-mode .msg-kvps th,
1313
+.light-mode .msg-kvps td {
1314
+ border-bottom: 1px solid var(--color-border);
1315
+}
1316
+
1317
.light-mode .message-default {
1318
background-color: #ffffff;
1319
color: #1a242f;
@@ -1434,6 +1422,12 @@ input:checked + .slider:before {
1422
filter: invert(100%) grayscale(100%);
1423
}
1424
1425
+.light-mode .extension {
1426
+ background: var(--color-primary);
1427
+ color: var(--color-background);
1428
+ opacity: 0.7;
1429
+}
1430
+
1431
/* Utility Classes */
1432
.kvps-key {
1433
font-weight: 500;
@@ -1563,7 +1557,7 @@ input:checked + .slider:before {
1557
}
1558
}
1559
1566
-@media (max-width: 480px) {
1560
+@media (max-width: 510px) {
1561
.text-buttons-row {
1562
display: table;
1563
gap: 0.1rem !important;
@@ -1649,18 +1643,59 @@ input:checked + .slider:before {
1643
}
1644
1645
.text-buttons-row {
1652
- width: 100%;
1646
+ width: 90%;
1647
display: flex;
1648
padding-top: var(--spacing-xs);
1649
gap: var(--spacing-xs);
1650
white-space: pre-wrap;
1651
}
1652
1653
+ .text-button {
1654
+ font-size: 0.6rem;
1655
+ }
1656
+
1657
.text-button svg {
1658
width: 18px;
1659
height: 18px;
1660
flex-shrink: 0; /* prevents SVG from shrinking */
1661
}
1662
+ .copy-button {
1663
+ display: none !important;
1664
+ }
1665
+
1666
+ .msg-content span,
1667
+ .kvps-val,
1668
+ .message-text {
1669
+ cursor: pointer;
1670
+ position: relative;
1671
+ }
1672
+
1673
+ .msg-thoughts span::after,
1674
+ .msg-content span::after,
1675
+ .kvps-val::after,
1676
+ .message-text::after {
1677
+ content: 'Copied!';
1678
+ position: absolute;
1679
+ opacity: 0;
1680
+ font-family: "Rubik", Arial, Helvetica, sans-serif;
1681
+ font-size: 0.7rem;
1682
+ text-wrap: nowrap;
1683
+ padding: 6px var(--spacing-sm);
1684
+ transition: opacity var(--transition-speed) ease-in-out;
1685
+ }
1686
+
1687
+ .msg-thoughts span.copied::after,
1688
+ .msg-content span.copied::after,
1689
+ .kvps-val.copied::after,
1690
+ .message-text.copied::after {
1691
+ opacity: 1;
1692
+ right: 0px;
1693
+ top: 0px;
1694
+ background-color: var(--color-background);
1695
+ border: none;
1696
+ border-radius: 5px;
1697
+ color: inherit;
1698
+ }
1699
}
1700
1701
@media (min-width: 768px) {
@@ -1703,4 +1738,71 @@ input:checked + .slider:before {
1738
-webkit-text-size-adjust: none;
1739
text-size-adjust: none;
1740
}
1741
+}
1742
+
1743
+/* Copy button styles */
1744
+.copy-button {
1745
+ position: absolute;
1746
+ right: 0;
1747
+ top: var(--spacing-sm);
1748
+ background: none;
1749
+ border: none;
1750
+ padding: var(--spacing-xs) var(--spacing-sm);
1751
+ padding-right: 0;
1752
+ cursor: pointer;
1753
+ text-decoration: underline;
1754
+ text-wrap: nowrap;
1755
+ opacity: 0;
1756
+ transition: opacity var(--transition-speed) ease-in-out;
1757
+ color: inherit;
1758
+ font-size: 12px;
1759
+ font-family: "Rubik", Arial, Helvetica, sans-serif;
1760
+}
1761
+
1762
+.copy-button:hover {
1763
+ opacity: 0.8 !important;
1764
+}
1765
+
1766
+.msg-content:hover .copy-button,
1767
+.kvps-row:hover .copy-button,
1768
+.message-text:hover .copy-button {
1769
+ opacity: 0.6;
1770
+}
1771
+
1772
+.copy-button.copied {
1773
+ font-family: "Rubik", Arial, Helvetica, sans-serif !important;
1774
+ opacity: 1 !important;
1775
+}
1776
+
1777
+.msg-thoughts .copy-button {
1778
+ top: -12px !important;
1779
+}
1780
+
1781
+.message-user .copy-button {
1782
+ top: -15px !important;
1783
+ left: -13px !important;
1784
+ right: 99% !important;
1785
+}
1786
+
1787
+.message-agent-response .copy-button {
1788
+ top: -22px !important;
1789
+ right: 0 !important;
1790
+ padding-right: 0px !important;
1791
+}
1792
+
1793
+.message-tool .copy-button {
1794
+ top: -12px !important;
1795
+ right: 0 !important;
1796
+}
1797
+
1798
+.msg-output .copy-button {
1799
+ top: -6px !important;
1800
+ right: 0 !important;
1801
+}
1802
+
1803
+/* Make message containers relative for absolute positioning of copy buttons */
1804
+.msg-content,
1805
+.kvps-row,
1806
+.message-text {
1807
+ position: relative;
1808
}
\ No newline at end of file
webui/index.html
+8
@@ -388,6 +388,14 @@
388
<p>Context</p>
389
</button>
390
391
+ <button class="text-button" id="nudges_window" @click="NUDGE_LOGIC()">
392
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 49 58" fill="currentColor">
393
+ <path d="m11.97,16.32c-.46,0-.91-.25-1.15-.68-.9-1.63-1.36-3.34-1.36-5.1C9.45,4.73,14.18,0,20,0s10.55,4.73,10.55,10.55c0,.87-.13,1.75-.41,2.76-.19.7-.9,1.13-1.62.93-.7-.19-1.12-.92-.93-1.62.21-.79.31-1.44.31-2.07,0-4.36-3.55-7.91-7.91-7.91s-7.91,3.55-7.91,7.91c0,1.3.35,2.59,1.03,3.82.36.64.13,1.44-.51,1.79-.21.11-.42.17-.64.17Z" stroke-width="0.5" stroke="currentColor"/>
394
+ <path d="m34.5,58h-6.18c-3.17,0-6.15-1.23-8.39-3.47L1.16,35.75c-1.54-1.54-1.54-4.05,0-5.59,2.4-2.4,6.27-2.68,8.99-.64l4.58,3.44V10.55c0-2.91,2.36-5.27,5.27-5.27s5.27,2.36,5.27,5.27v8.62c.78-.45,1.68-.71,2.64-.71,2.3,0,4.26,1.48,4.98,3.53.84-.56,1.85-.89,2.93-.89,2.3,0,4.26,1.48,4.98,3.53.84-.56,1.85-.89,2.93-.89,2.91,0,5.27,2.36,5.27,5.27v14.5c0,8-6.51,14.5-14.5,14.5ZM6.03,30.79c-1.1,0-2.19.42-3.01,1.23-.51.51-.51,1.35,0,1.86l18.77,18.78c1.74,1.74,4.06,2.7,6.53,2.7h6.18c6.54,0,11.86-5.32,11.86-11.86v-14.5c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v1.32c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32v-3.95c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v3.95c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32v-6.59c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v6.59c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32V10.55c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v25.05c0,.5-.28.95-.73,1.18s-.98.18-1.38-.12l-6.69-5.02c-.75-.56-1.65-.84-2.54-.84Z" stroke-width="0.5" stroke="currentColor"/>
395
+ </svg>
396
+ <p>Nudge</p>
397
+ </button>
398
+
399
</div>
400
</div>
401
</div>
webui/js/messages.js
+84
-6
@@ -1,3 +1,46 @@
1
+// copy button
2
+
3
+function createCopyButton() {
4
+ const button = document.createElement('button');
5
+ button.className = 'copy-button';
6
+ button.textContent = 'Copy';
7
+
8
+ button.addEventListener('click', async function(e) {
9
+ e.stopPropagation();
10
+ const container = this.closest('.msg-content, .kvps-row, .message-text');
11
+ let textToCopy;
12
+
13
+ if (container.classList.contains('kvps-row')) {
14
+ textToCopy = container.querySelector('.kvps-val').textContent;
15
+ } else if (container.classList.contains('message-text')) {
16
+ textToCopy = container.textContent.replace('copy', '');
17
+ } else {
18
+ textToCopy = container.querySelector('span').textContent;
19
+ }
20
+
21
+ try {
22
+ await navigator.clipboard.writeText(textToCopy);
23
+ const originalText = button.textContent;
24
+ button.classList.add('copied');
25
+ button.textContent = 'Copied!';
26
+ setTimeout(() => {
27
+ button.classList.remove('copied');
28
+ button.textContent = originalText;
29
+ }, 2000);
30
+ } catch (err) {
31
+ console.error('Failed to copy text:', err);
32
+ }
33
+ });
34
+
35
+ return button;
36
+}
37
+
38
+function addCopyButtonToElement(element) {
39
+ if (!element.querySelector('.copy-button')) {
40
+ element.appendChild(createCopyButton());
41
+ }
42
+}
43
+
44
export function getHandler(type) {
45
switch (type) {
46
case 'user':
@@ -49,7 +92,14 @@ export function _drawMessage(messageContainer, heading, content, temp, followUp,
92
93
const spanElement = document.createElement('span');
94
spanElement.innerHTML = content;
95
+
96
+ // Add click handler for small screens
97
+ spanElement.addEventListener('click', () => {
98
+ copyText(spanElement.textContent, spanElement);
99
+ });
100
+
101
preElement.appendChild(spanElement);
102
+ addCopyButtonToElement(preElement);
103
messageDiv.appendChild(preElement);
104
105
// Render LaTeX math within the span
@@ -114,6 +164,13 @@ export function drawMessageUser(messageContainer, id, type, heading, content, te
164
const textDiv = document.createElement('div');
165
textDiv.classList.add('message-text');
166
textDiv.textContent = content;
167
+
168
+ // Add click handler
169
+ textDiv.addEventListener('click', () => {
170
+ copyText(content, textDiv);
171
+ });
172
+
173
+ addCopyButtonToElement(textDiv);
174
messageDiv.appendChild(textDiv);
175
}
176
@@ -229,17 +286,20 @@ function drawKvps(container, kvps) {
286
const pre = document.createElement('pre');
287
pre.classList.add('kvps-val');
288
232
- // if value is array, join it with new line
289
if (Array.isArray(value)) value = value.join('\n');
290
291
if (row.classList.contains('msg-thoughts')) {
236
- // Wrap content in a <span> to allow HTML parsing
292
const span = document.createElement('span');
238
- span.innerHTML = value; // Use innerHTML to enable KaTeX parsing
293
+ span.innerHTML = value;
294
pre.appendChild(span);
295
td.appendChild(pre);
296
+ addCopyButtonToElement(row);
297
+
298
+ // Add click handler
299
+ span.addEventListener('click', () => {
300
+ copyText(span.textContent, span);
301
+ });
302
242
- // Render LaTeX math within the span
303
if (window.renderMathInElement) {
304
renderMathInElement(span, {
305
delimiters: [
@@ -248,13 +308,19 @@ function drawKvps(container, kvps) {
308
{ left: "$", right: "$", display: false },
309
{ left: "\$$", right: "\$$", display: false }
310
],
251
- throwOnError: false // Prevent KaTeX from throwing errors
311
+ throwOnError: false
312
});
313
}
314
} else {
255
- // For non-thought rows, use textContent as before
315
pre.textContent = value;
316
+
317
+ // Add click handler
318
+ pre.addEventListener('click', () => {
319
+ copyText(value, pre);
320
+ });
321
+
322
td.appendChild(pre);
323
+ addCopyButtonToElement(row);
324
}
325
}
326
container.appendChild(table);
@@ -282,3 +348,15 @@ function convertImageTags(content) {
348
349
return updatedContent;
350
}
351
+
352
+async function copyText(text, element) {
353
+ try {
354
+ await navigator.clipboard.writeText(text);
355
+ element.classList.add('copied');
356
+ setTimeout(() => {
357
+ element.classList.remove('copied');
358
+ }, 2000);
359
+ } catch (err) {
360
+ console.error('Failed to copy text:', err);
361
+ }
362
+}