feature: fullscreen chat input (#269)
* feature: fullscreen chat input * css fixes
Alessandro committed
Dec 19, 2024 at 19:50 UTC
d42bc2c76134b0eb5976ef060b186f647a7b2ae2
5 files changed
+233
-48
webui/css/modals.css
+92
-3
@@ -75,7 +75,7 @@
75
76
/* Modal Description */
77
.modal-description {
78
- padding: 0.8rem 2rem 0 2rem;
78
+ padding: 0.8rem 1rem 0 1rem;
79
flex-grow: 1;
80
transition: all 0.3s ease;
81
}
@@ -114,6 +114,96 @@
114
background-color: rgba(155, 155, 155, 0.7);
115
}
116
117
+/* Full Screen Input Modal Styles */
118
+.full-screen-input-modal {
119
+ width: 90%;
120
+ max-width: 800px;
121
+ max-height: 80vh;
122
+ position: relative;
123
+ padding: 0;
124
+ background-color: rgb(20, 20, 20, 0.96);
125
+ border: 1.5px solid var(--color-border);
126
+}
127
+
128
+.full-screen-input-modal h2 {
129
+ margin: 0;
130
+ padding: 0;
131
+ font-size: 1.1rem;
132
+ color: var(--color-text);
133
+ opacity: 0.8;
134
+}
135
+
136
+.full-screen-input-modal .modal-close {
137
+ position: absolute;
138
+ top: 1rem;
139
+ right: 1rem;
140
+ font-size: 1.5rem;
141
+ padding: 0 0.5rem;
142
+ line-height: 0.8;
143
+}
144
+
145
+.full-screen-input-modal .btn-ok {
146
+ margin-right: 1rem;
147
+}
148
+
149
+.full-screen-input-modal .modal-content {
150
+ height: calc(80vh);
151
+ padding: 0;
152
+ margin: 0;
153
+ overflow: hidden;
154
+}
155
+
156
+.full-screen-input-modal .modal-footer {
157
+ background: transparent;
158
+ max-height: 50px;
159
+}
160
+
161
+#full-screen-input {
162
+ width: 100%;
163
+ height: 100%;
164
+ border: none;
165
+ background-color: transparent;
166
+ color: var(--color-text);
167
+ font-family: 'Roboto Mono', monospace;
168
+ font-optical-sizing: auto;
169
+ font-size: 0.955rem;
170
+ padding: 1.2rem 1rem;
171
+ resize: none;
172
+ outline: none;
173
+}
174
+
175
+#full-screen-input::-webkit-scrollbar {
176
+ width: 6px;
177
+ height: 6px;
178
+}
179
+
180
+#full-screen-input::-webkit-scrollbar-track {
181
+ background: transparent;
182
+ margin: 14px;
183
+ border-radius: 6px;
184
+}
185
+
186
+#full-screen-input::-webkit-scrollbar-thumb {
187
+ background-color: rgba(155, 155, 155, 0.5);
188
+ border-radius: 6px;
189
+ -webkit-transition: background-color 0.2s ease;
190
+ transition: background-color 0.2s ease;
191
+}
192
+
193
+#full-screen-input::-webkit-scrollbar-thumb:hover {
194
+ background-color: rgba(155, 155, 155, 0.7);
195
+}
196
+
197
+.light-mode .full-screen-input-modal {
198
+ background-color: rgb(220, 220, 220, 0.86);
199
+}
200
+
201
+.full-screen-input-modal .modal-footer {
202
+ padding: 1rem 0;
203
+ border-top: none;
204
+ background: transparent;
205
+}
206
+
207
/* Buttons Container */
208
#buttons-container {
209
display: flex;
@@ -273,7 +363,6 @@ h2 {
363
.btn {
364
text-wrap: wrap;
365
font-size: var(--font-size-small);
276
- padding: 0.5rem 0.85rem;
366
}
367
368
.btn-upload {
@@ -285,4 +374,4 @@ h2 {
374
.btn-upload > svg {
375
width: 20px;
376
}
288
-}
377
+}
\ No newline at end of file
webui/css/settings.css
+2
-2
@@ -214,13 +214,13 @@ nav ul li a img {
214
}
215
}
216
217
-@media (max-width: 480px) {
217
+@media (max-width: 640px) {
218
nav ul {
219
grid-template-columns: repeat(2, 1.2fr);
220
}
221
}
222
223
-@media (max-width: 380px) {
223
+@media (max-width: 480px) {
224
nav ul {
225
grid-template-columns: 1fr;
226
}
webui/index.css
+70
-40
@@ -7,30 +7,30 @@
7
8
:root {
9
/* Dark mode */
10
- --color-background-dark: #171717;
11
- --color-text-dark: #e0e0e0;
12
- --color-primary-dark: #86929d;
13
- --color-secondary-dark: #3f3f3f;
10
+ --color-background-dark: #131313;
11
+ --color-text-dark: #d4d4d4;
12
+ --color-primary-dark: #737a81;
13
+ --color-secondary-dark: #656565;
14
--color-accent-dark: #cf6679;
15
--color-message-bg-dark: #2d2d2d;
16
--color-message-text-dark: #e0e0e0;
17
- --color-panel-dark: #1e1e1e;
17
+ --color-panel-dark: #171717;
18
--color-border-dark: #444444a8;
19
- --color-input-dark: #222;
20
- --color-input-focus-dark: #1b1b1b;
19
+ --color-input-dark: #131313;
20
+ --color-input-focus-dark: #101010;
21
22
/* Light mode */
23
- --color-background-light: #e8e9e9;
23
+ --color-background-light: #dbdbdb;
24
--color-text-light: #333333;
25
- --color-primary-light: #324d66;
25
+ --color-primary-light: #384653;
26
--color-secondary-light: #e8eaf6;
27
--color-accent-light: #b00020;
28
--color-message-bg-light: #ffffff;
29
--color-message-text-light: #333333;
30
- --color-panel-light: #ffffff;
30
+ --color-panel-light: #f0f0f0;
31
--color-border-light: #e0e0e0c7;
32
- --color-input-light: #f1f1f1;
33
- --color-input-focus-light: #ebeced;
32
+ --color-input-light: #e4e4e4;
33
+ --color-input-focus-light: #dadada;
34
35
/* Default to dark mode */
36
--color-background: var(--color-background-dark);
@@ -752,6 +752,11 @@ pre {
752
}
753
754
/* Text input */
755
+#chat-input-container {
756
+ position: relative;
757
+ width: 100%;
758
+}
759
+
760
#chat-input {
761
background-color: var(--color-input);
762
border: 1px solid var(--color-border);
@@ -763,8 +768,10 @@ pre {
768
-webkit-font-optical-sizing: auto;
769
font-size: 0.875rem;
770
max-height: 7rem;
766
- min-height: 2.8rem;
767
- padding: 0.65rem var(--spacing-sm) var(--spacing-sm) var(--spacing-sm);
771
+ min-height: 3rem;
772
+ width: 100%;
773
+ padding: 0.5rem 40px var(--spacing-sm) var(--spacing-sm);
774
+ margin-right: var(--spacing-xs);
775
overflow-y: auto;
776
scroll-behavior: smooth;
777
resize: none;
@@ -775,6 +782,36 @@ pre {
782
-webkit-transition: all 0.3s ease;
783
}
784
785
+#chat-input {
786
+ outline: 1px solid var(--color-border);
787
+}
788
+
789
+#expand-button {
790
+ position: absolute;
791
+ top: 12px;
792
+ right: 10px;
793
+ background: transparent;
794
+ border: none;
795
+ cursor: pointer;
796
+ font-size: 1.2rem;
797
+ color: var(--color-text);
798
+ opacity: 0.4;
799
+ transition: opacity 0.2s;
800
+}
801
+
802
+#expand-button:hover {
803
+ opacity: 0.7;
804
+}
805
+
806
+#expand-button:active {
807
+ opacity: 1;
808
+}
809
+
810
+#expand-button svg {
811
+ width: 1.3rem;
812
+ height: 1.3rem;
813
+}
814
+
815
#chat-input::-webkit-scrollbar {
816
width: 6px;
817
height: 6px;
@@ -798,9 +835,9 @@ pre {
835
}
836
837
#chat-input:focus {
801
- outline: 0.05rem solid rgba(155, 155, 155, 0.3);
838
+ outline: 0.05rem solid rgba(155, 155, 155, 0.5);
839
font-size: 0.955rem;
803
- padding-top: 0.58rem;
840
+ padding-top: 0.5rem;
841
background-color: var(--color-input-focus);
842
}
843
@@ -1011,6 +1048,7 @@ pre {
1048
gap: 8px;
1049
margin-bottom: 10px;
1050
padding: var(--spacing-xs);
1051
+ background-color: var(--color-border);
1052
}
1053
1054
.preview-item {
@@ -1098,18 +1136,6 @@ pre {
1136
}
1137
1138
/* Text input */
1101
-#chat-input {
1102
- flex-grow: 1;
1103
- min-height: 2.7rem;
1104
- padding: var(--spacing-sm) var(--spacing-sm);
1105
- padding-top: 0.65rem;
1106
- margin-right: var(--spacing-xs);
1107
- border: 1px solid var(--color-border);
1108
- border-radius: 8px;
1109
- resize: none;
1110
- align-content: start;
1111
-}
1112
-
1139
.input-row {
1140
width: 100%;
1141
white-space: nowrap;
@@ -1164,6 +1190,7 @@ pre {
1190
1191
#chat-buttons-wrapper {
1192
gap: var(--spacing-xs);
1193
+ padding-left: var(--spacing-xs);
1194
}
1195
1196
.chat-button {
@@ -1245,7 +1272,10 @@ pre {
1272
.preview-item {
1273
position: relative;
1274
flex-shrink: 0;
1275
+ padding: 4px;
1276
animation: fadeIn 0.3s ease;
1277
+ min-width: min-content;
1278
+ background-color: var(--color-background);
1279
}
1280
1281
.preview-item img {
@@ -1778,57 +1808,57 @@ input:checked + .slider:before {
1808
}
1809
1810
.light-mode .message-default {
1781
- background-color: #ffffff;
1811
+ background-color: #f3f3f3;
1812
color: #1a242f;
1813
}
1814
1815
.light-mode .message-agent {
1786
- background-color: #ffffff;
1816
+ background-color: #f3f3f3;
1817
color: #356ca3;
1818
}
1819
1820
.light-mode .message-agent-response {
1791
- background-color: #ffffff;
1821
+ background-color: #f3f3f3;
1822
color: #188216;
1823
}
1824
1825
.light-mode .message-agent-delegation {
1796
- background-color: #ffffff;
1826
+ background-color: #f3f3f3;
1827
color: #12685e;
1828
}
1829
1830
.light-mode .message-tool {
1801
- background-color: #ffffff;
1831
+ background-color: #f3f3f3;
1832
color: #1c3c88;
1833
}
1834
1835
.light-mode .message-code-exe {
1806
- background-color: #ffffff;
1836
+ background-color: #f3f3f3;
1837
color: #6c43b0;
1838
}
1839
1840
.light-mode .message-info {
1811
- background-color: #ffffff;
1841
+ background-color: #f3f3f3;
1842
color: #3f3f3f;
1843
}
1844
1845
.light-mode .message-util {
1816
- background-color: #ffffff;
1846
+ background-color: #f3f3f3;
1847
color: #5b5540;
1848
}
1849
1850
.light-mode .message-warning {
1821
- background-color: #ffffff;
1851
+ background-color: #f3f3f3;
1852
color: #8f4800;
1853
}
1854
1855
.light-mode .message-error {
1826
- background-color: #ffffff;
1856
+ background-color: #f3f3f3;
1857
color: #8f1010;
1858
}
1859
1860
.light-mode .message-user {
1831
- background-color: #ffffff;
1861
+ background-color: #f3f3f3;
1862
color: #4e4e4e;
1863
}
1864
@@ -1950,4 +1980,4 @@ input:checked + .slider:before {
1980
100% {
1981
transform: scale(1);
1982
}
1953
-}
1983
+}
\ No newline at end of file
webui/index.html
+35
-3
@@ -304,10 +304,20 @@
304
</div>
305
</div>
306
307
- <!-- Text input -->
308
- <textarea id="chat-input" placeholder="Type your message here..." rows="1"></textarea>
309
-
307
+ <!-- Container for textarea and button -->
308
+ <div id="chat-input-container" style="position: relative;">
309
+ <textarea id="chat-input" placeholder="Type your message here..." rows="1"></textarea>
310
+
311
+ <!-- Expand button inside the textarea container -->
312
+ <button id="expand-button" @click="$store.fullScreenInputModal.openModal()" aria-label="Expand input">
313
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
314
+ <path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
315
+ </svg>
316
+ </button>
317
+ </div>
318
+
319
<div id="chat-buttons-wrapper">
320
+
321
<!-- Send button -->
322
<button class="chat-button" id="send-button" aria-label="Send message">
323
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
@@ -682,6 +692,28 @@
692
</template>
693
</div>
694
695
+ <!-- Full Screen Input Modal -->
696
+ <div id="fullScreenInputModal" x-data="fullScreenInputModalProxy">
697
+ <template x-teleport="body">
698
+ <div x-show="isOpen" class="modal-overlay" @click.self="handleClose()"
699
+ @keydown.escape.window="handleClose()" x-transition>
700
+ <div class="modal-container full-screen-input-modal">
701
+ <div class="modal-content">
702
+ <button class="modal-close" @click="handleClose()">×</button>
703
+ <textarea id="full-screen-input" x-model="inputText"
704
+ placeholder="Type your message here..."
705
+ @keydown.ctrl.enter="handleClose()"></textarea>
706
+ </div>
707
+ <div class="modal-footer">
708
+ <div id="buttons-container">
709
+ <button class="btn btn-ok" @click="handleClose()">Done (Ctrl+Enter)</button>
710
+ </div>
711
+ </div>
712
+ </div>
713
+ </div>
714
+ </template>
715
+ </div>
716
+
717
</body>
718
719
</html>
\ No newline at end of file
webui/js/modal.js
+34
@@ -1,3 +1,37 @@
1
+const fullScreenInputModalProxy = {
2
+ isOpen: false,
3
+ inputText: '',
4
+
5
+ openModal() {
6
+ const chatInput = document.getElementById('chat-input');
7
+ this.inputText = chatInput.value;
8
+ this.isOpen = true;
9
+
10
+ // Focus the full screen input after a short delay to ensure the modal is rendered
11
+ setTimeout(() => {
12
+ const fullScreenInput = document.getElementById('full-screen-input');
13
+ fullScreenInput.focus();
14
+ }, 100);
15
+ },
16
+
17
+ handleClose() {
18
+ const chatInput = document.getElementById('chat-input');
19
+ chatInput.value = this.inputText;
20
+ chatInput.dispatchEvent(new Event('input')); // Trigger input event for textarea auto-resize
21
+ this.isOpen = false;
22
+ }
23
+};
24
+
25
+// Register the full screen input modal with Alpine as a store
26
+document.addEventListener('alpine:init', () => {
27
+ Alpine.store('fullScreenInputModal', fullScreenInputModalProxy);
28
+});
29
+
30
+// Also register as a component for x-data usage
31
+document.addEventListener('alpine:init', () => {
32
+ Alpine.data('fullScreenInputModalProxy', () => fullScreenInputModalProxy);
33
+});
34
+
35
const genericModalProxy = {
36
isOpen: false,
37
isLoading: false,