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()">&times;</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,