Commit
·
b51ab8b
1
Parent(s):
4fa0b30
Fix footer position, prompts size, flexible textarea, send button, prompts refresh, and add voice recording
Browse files- static/css/button.css +21 -23
- static/css/input.css +11 -6
- static/images/mic.svg +5 -0
- static/js/chat.js +65 -19
- templates/chat.html +2 -9
static/css/button.css
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
* SPDX-License-Identifier: Apache-2.0
|
| 4 |
*/
|
| 5 |
|
| 6 |
-
#sendBtn, #stopBtn, #
|
| 7 |
width: 2.75rem;
|
| 8 |
height: 2.75rem;
|
| 9 |
border: none;
|
|
@@ -22,12 +22,17 @@
|
|
| 22 |
color: white;
|
| 23 |
}
|
| 24 |
|
| 25 |
-
#sendBtn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
transform: translateY(-2px) scale(1.05);
|
| 27 |
box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
|
| 28 |
}
|
| 29 |
|
| 30 |
-
#sendBtn:active:not(:disabled) {
|
| 31 |
transform: translateY(0) scale(0.98);
|
| 32 |
}
|
| 33 |
|
|
@@ -53,25 +58,6 @@
|
|
| 53 |
transform: translateY(0) scale(0.98);
|
| 54 |
}
|
| 55 |
|
| 56 |
-
#voiceBtn {
|
| 57 |
-
background: linear-gradient(135deg, #10b981, #059669);
|
| 58 |
-
color: white;
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
#voiceBtn.recording {
|
| 62 |
-
background: linear-gradient(135deg, #f87171, #ef4444);
|
| 63 |
-
transform: scale(1.1);
|
| 64 |
-
}
|
| 65 |
-
|
| 66 |
-
#voiceBtn:hover:not(.recording) {
|
| 67 |
-
transform: translateY(-2px) scale(1.05);
|
| 68 |
-
box-shadow: 0 4px 8px rgba(16, 185, 129, 0.3);
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
#voiceBtn:active:not(.recording) {
|
| 72 |
-
transform: translateY(0) scale(0.98);
|
| 73 |
-
}
|
| 74 |
-
|
| 75 |
#fileBtn, #audioBtn {
|
| 76 |
background: linear-gradient(135deg, #6b7280, #4b5563);
|
| 77 |
color: white;
|
|
@@ -93,6 +79,18 @@
|
|
| 93 |
transition: transform 0.2s ease;
|
| 94 |
}
|
| 95 |
|
| 96 |
-
#sendBtn:hover:not(:disabled) #sendIcon {
|
| 97 |
transform: translateX(2px);
|
| 98 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
* SPDX-License-Identifier: Apache-2.0
|
| 4 |
*/
|
| 5 |
|
| 6 |
+
#sendBtn, #stopBtn, #fileBtn, #audioBtn {
|
| 7 |
width: 2.75rem;
|
| 8 |
height: 2.75rem;
|
| 9 |
border: none;
|
|
|
|
| 22 |
color: white;
|
| 23 |
}
|
| 24 |
|
| 25 |
+
#sendBtn.recording {
|
| 26 |
+
background: linear-gradient(135deg, #f87171, #ef4444);
|
| 27 |
+
transform: scale(1.1);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
#sendBtn:hover:not(:disabled):not(.recording) {
|
| 31 |
transform: translateY(-2px) scale(1.05);
|
| 32 |
box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
|
| 33 |
}
|
| 34 |
|
| 35 |
+
#sendBtn:active:not(:disabled):not(.recording) {
|
| 36 |
transform: translateY(0) scale(0.98);
|
| 37 |
}
|
| 38 |
|
|
|
|
| 58 |
transform: translateY(0) scale(0.98);
|
| 59 |
}
|
| 60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
#fileBtn, #audioBtn {
|
| 62 |
background: linear-gradient(135deg, #6b7280, #4b5563);
|
| 63 |
color: white;
|
|
|
|
| 79 |
transition: transform 0.2s ease;
|
| 80 |
}
|
| 81 |
|
| 82 |
+
#sendBtn:hover:not(:disabled):not(.recording) #sendIcon {
|
| 83 |
transform: translateX(2px);
|
| 84 |
}
|
| 85 |
+
|
| 86 |
+
#sendBtn.recording #sendIcon {
|
| 87 |
+
display: none;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
#sendBtn.recording::after {
|
| 91 |
+
content: '';
|
| 92 |
+
width: 1rem;
|
| 93 |
+
height: 1rem;
|
| 94 |
+
background: url('/static/images/mic.svg') no-repeat center;
|
| 95 |
+
background-size: contain;
|
| 96 |
+
}
|
static/css/input.css
CHANGED
|
@@ -5,15 +5,15 @@
|
|
| 5 |
|
| 6 |
#inputContainer {
|
| 7 |
display: flex;
|
| 8 |
-
align-items:
|
| 9 |
flex: 1;
|
| 10 |
background: linear-gradient(145deg, #1a1a1a, #141414);
|
| 11 |
border-radius: 1rem;
|
| 12 |
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 13 |
-
padding: 0.
|
| 14 |
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 15 |
max-width: 800px;
|
| 16 |
-
margin: 0 auto;
|
| 17 |
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
| 18 |
position: relative;
|
| 19 |
}
|
|
@@ -38,24 +38,29 @@
|
|
| 38 |
word-break: break-word;
|
| 39 |
overflow-wrap: break-word;
|
| 40 |
min-height: 40px;
|
| 41 |
-
max-height:
|
| 42 |
-
resize:
|
| 43 |
font-family: 'Inter', sans-serif;
|
|
|
|
|
|
|
| 44 |
}
|
| 45 |
|
| 46 |
#rightIconGroup {
|
| 47 |
position: absolute;
|
| 48 |
right: 0.5rem;
|
|
|
|
| 49 |
display: flex;
|
| 50 |
gap: 0.5rem;
|
|
|
|
| 51 |
}
|
| 52 |
|
| 53 |
@media (max-width: 768px) {
|
| 54 |
#inputContainer {
|
| 55 |
max-width: 100%;
|
| 56 |
-
padding: 0.
|
| 57 |
}
|
| 58 |
#userInput {
|
| 59 |
padding: 0.5rem 2.5rem 0.5rem 0.5rem;
|
|
|
|
| 60 |
}
|
| 61 |
}
|
|
|
|
| 5 |
|
| 6 |
#inputContainer {
|
| 7 |
display: flex;
|
| 8 |
+
align-items: flex-end; /* Align items to the bottom for better textarea expansion */
|
| 9 |
flex: 1;
|
| 10 |
background: linear-gradient(145deg, #1a1a1a, #141414);
|
| 11 |
border-radius: 1rem;
|
| 12 |
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 13 |
+
padding: 0.5rem;
|
| 14 |
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 15 |
max-width: 800px;
|
| 16 |
+
margin: 0 auto 1rem auto; /* Reduced bottom margin */
|
| 17 |
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
| 18 |
position: relative;
|
| 19 |
}
|
|
|
|
| 38 |
word-break: break-word;
|
| 39 |
overflow-wrap: break-word;
|
| 40 |
min-height: 40px;
|
| 41 |
+
max-height: 200px; /* Increased max-height for more expansion */
|
| 42 |
+
resize: none; /* Disable manual resize */
|
| 43 |
font-family: 'Inter', sans-serif;
|
| 44 |
+
line-height: 1.5;
|
| 45 |
+
overflow-y: auto;
|
| 46 |
}
|
| 47 |
|
| 48 |
#rightIconGroup {
|
| 49 |
position: absolute;
|
| 50 |
right: 0.5rem;
|
| 51 |
+
bottom: 0.5rem;
|
| 52 |
display: flex;
|
| 53 |
gap: 0.5rem;
|
| 54 |
+
align-items: center;
|
| 55 |
}
|
| 56 |
|
| 57 |
@media (max-width: 768px) {
|
| 58 |
#inputContainer {
|
| 59 |
max-width: 100%;
|
| 60 |
+
padding: 0.5rem;
|
| 61 |
}
|
| 62 |
#userInput {
|
| 63 |
padding: 0.5rem 2.5rem 0.5rem 0.5rem;
|
| 64 |
+
font-size: 0.9rem;
|
| 65 |
}
|
| 66 |
}
|
static/images/mic.svg
ADDED
|
|
static/js/chat.js
CHANGED
|
@@ -15,7 +15,6 @@ const btn = document.getElementById('sendBtn');
|
|
| 15 |
const stopBtn = document.getElementById('stopBtn');
|
| 16 |
const fileBtn = document.getElementById('fileBtn');
|
| 17 |
const audioBtn = document.getElementById('audioBtn');
|
| 18 |
-
const voiceBtn = document.getElementById('voiceBtn');
|
| 19 |
const fileInput = document.getElementById('fileInput');
|
| 20 |
const audioInput = document.getElementById('audioInput');
|
| 21 |
const filePreview = document.getElementById('filePreview');
|
|
@@ -36,6 +35,14 @@ let isRequestActive = false;
|
|
| 36 |
let abortController = null;
|
| 37 |
let mediaRecorder = null;
|
| 38 |
let audioChunks = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
// تحميل المحادثة عند تحميل الصفحة
|
| 41 |
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -51,6 +58,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 51 |
addMsg(msg.role, msg.content);
|
| 52 |
});
|
| 53 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
});
|
| 55 |
|
| 56 |
// تحقق من الـ token
|
|
@@ -143,6 +154,7 @@ function clearAllMessages() {
|
|
| 143 |
audioPreview.style.display = 'none';
|
| 144 |
messageLimitWarning.classList.add('hidden');
|
| 145 |
enterChatView();
|
|
|
|
| 146 |
}
|
| 147 |
|
| 148 |
// File preview.
|
|
@@ -155,6 +167,7 @@ function previewFile() {
|
|
| 155 |
filePreview.innerHTML = `<img src="${e.target.result}" class="upload-preview">`;
|
| 156 |
filePreview.style.display = 'block';
|
| 157 |
audioPreview.style.display = 'none';
|
|
|
|
| 158 |
};
|
| 159 |
reader.readAsDataURL(file);
|
| 160 |
}
|
|
@@ -167,6 +180,7 @@ function previewFile() {
|
|
| 167 |
audioPreview.innerHTML = `<audio controls src="${e.target.result}"></audio>`;
|
| 168 |
audioPreview.style.display = 'block';
|
| 169 |
filePreview.style.display = 'none';
|
|
|
|
| 170 |
};
|
| 171 |
reader.readAsDataURL(file);
|
| 172 |
}
|
|
@@ -175,24 +189,29 @@ function previewFile() {
|
|
| 175 |
|
| 176 |
// Voice recording.
|
| 177 |
function startVoiceRecording() {
|
|
|
|
|
|
|
|
|
|
| 178 |
navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
|
| 179 |
mediaRecorder = new MediaRecorder(stream);
|
| 180 |
audioChunks = [];
|
| 181 |
mediaRecorder.start();
|
| 182 |
-
voiceBtn.classList.add('recording');
|
| 183 |
mediaRecorder.addEventListener('dataavailable', event => {
|
| 184 |
audioChunks.push(event.data);
|
| 185 |
});
|
| 186 |
}).catch(err => {
|
| 187 |
console.error('Error accessing microphone:', err);
|
| 188 |
alert('Failed to access microphone. Please check permissions.');
|
|
|
|
|
|
|
| 189 |
});
|
| 190 |
}
|
| 191 |
|
| 192 |
function stopVoiceRecording() {
|
| 193 |
if (mediaRecorder && mediaRecorder.state === 'recording') {
|
| 194 |
mediaRecorder.stop();
|
| 195 |
-
|
|
|
|
| 196 |
mediaRecorder.addEventListener('stop', async () => {
|
| 197 |
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
|
| 198 |
const formData = new FormData();
|
|
@@ -285,7 +304,7 @@ async function submitAudioMessage(formData) {
|
|
| 285 |
|
| 286 |
// Send user message.
|
| 287 |
async function submitMessage() {
|
| 288 |
-
if (isRequestActive) return;
|
| 289 |
let message = input.value.trim();
|
| 290 |
let formData = new FormData();
|
| 291 |
let endpoint = '/api/chat';
|
|
@@ -335,6 +354,7 @@ async function submitMessage() {
|
|
| 335 |
btn.disabled = true;
|
| 336 |
filePreview.style.display = 'none';
|
| 337 |
audioPreview.style.display = 'none';
|
|
|
|
| 338 |
|
| 339 |
isRequestActive = true;
|
| 340 |
abortController = new AbortController();
|
|
@@ -431,7 +451,10 @@ async function submitMessage() {
|
|
| 431 |
|
| 432 |
// Stop streaming and cancel the ongoing request.
|
| 433 |
function stopStream(forceCancel = false) {
|
| 434 |
-
if (!isRequestActive) return;
|
|
|
|
|
|
|
|
|
|
| 435 |
isRequestActive = false;
|
| 436 |
if (abortController) {
|
| 437 |
abortController.abort();
|
|
@@ -455,6 +478,8 @@ promptItems.forEach(p => {
|
|
| 455 |
p.addEventListener('click', e => {
|
| 456 |
e.preventDefault();
|
| 457 |
input.value = p.dataset.prompt;
|
|
|
|
|
|
|
| 458 |
submitMessage();
|
| 459 |
});
|
| 460 |
});
|
|
@@ -469,32 +494,58 @@ audioBtn.addEventListener('click', () => {
|
|
| 469 |
fileInput.addEventListener('change', previewFile);
|
| 470 |
audioInput.addEventListener('change', previewFile);
|
| 471 |
|
| 472 |
-
//
|
| 473 |
-
|
| 474 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
e.preventDefault();
|
| 476 |
-
|
|
|
|
|
|
|
|
|
|
| 477 |
});
|
| 478 |
-
|
| 479 |
-
voiceBtn.addEventListener('touchend', e => {
|
| 480 |
e.preventDefault();
|
| 481 |
-
|
|
|
|
|
|
|
|
|
|
| 482 |
});
|
| 483 |
|
| 484 |
// Submit.
|
| 485 |
form.addEventListener('submit', e => {
|
| 486 |
e.preventDefault();
|
| 487 |
-
|
|
|
|
|
|
|
| 488 |
});
|
| 489 |
|
| 490 |
// Handle Enter key to submit without adding new line.
|
| 491 |
input.addEventListener('keydown', e => {
|
| 492 |
if (e.key === 'Enter' && !e.shiftKey) {
|
| 493 |
e.preventDefault();
|
| 494 |
-
|
|
|
|
|
|
|
| 495 |
}
|
| 496 |
});
|
| 497 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 498 |
// Stop.
|
| 499 |
stopBtn.addEventListener('click', () => {
|
| 500 |
stopBtn.style.pointerEvents = 'none';
|
|
@@ -517,8 +568,3 @@ if (loginBtn) {
|
|
| 517 |
window.location.href = '/login';
|
| 518 |
});
|
| 519 |
}
|
| 520 |
-
|
| 521 |
-
// Enable send button only if input has text or files.
|
| 522 |
-
input.addEventListener('input', () => {
|
| 523 |
-
btn.disabled = input.value.trim() === '' && fileInput.files.length === 0 && audioInput.files.length === 0;
|
| 524 |
-
});
|
|
|
|
| 15 |
const stopBtn = document.getElementById('stopBtn');
|
| 16 |
const fileBtn = document.getElementById('fileBtn');
|
| 17 |
const audioBtn = document.getElementById('audioBtn');
|
|
|
|
| 18 |
const fileInput = document.getElementById('fileInput');
|
| 19 |
const audioInput = document.getElementById('audioInput');
|
| 20 |
const filePreview = document.getElementById('filePreview');
|
|
|
|
| 35 |
let abortController = null;
|
| 36 |
let mediaRecorder = null;
|
| 37 |
let audioChunks = [];
|
| 38 |
+
let isRecording = false;
|
| 39 |
+
let pressTimer = null;
|
| 40 |
+
|
| 41 |
+
// Auto-resize textarea
|
| 42 |
+
function autoResizeTextarea() {
|
| 43 |
+
input.style.height = 'auto';
|
| 44 |
+
input.style.height = `${Math.min(input.scrollHeight, 200)}px`; // Max height is 200px as per CSS
|
| 45 |
+
}
|
| 46 |
|
| 47 |
// تحميل المحادثة عند تحميل الصفحة
|
| 48 |
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
| 58 |
addMsg(msg.role, msg.content);
|
| 59 |
});
|
| 60 |
}
|
| 61 |
+
// Initialize textarea height
|
| 62 |
+
autoResizeTextarea();
|
| 63 |
+
// Enable send button based on input
|
| 64 |
+
btn.disabled = input.value.trim() === '' && fileInput.files.length === 0 && audioInput.files.length === 0;
|
| 65 |
});
|
| 66 |
|
| 67 |
// تحقق من الـ token
|
|
|
|
| 154 |
audioPreview.style.display = 'none';
|
| 155 |
messageLimitWarning.classList.add('hidden');
|
| 156 |
enterChatView();
|
| 157 |
+
autoResizeTextarea();
|
| 158 |
}
|
| 159 |
|
| 160 |
// File preview.
|
|
|
|
| 167 |
filePreview.innerHTML = `<img src="${e.target.result}" class="upload-preview">`;
|
| 168 |
filePreview.style.display = 'block';
|
| 169 |
audioPreview.style.display = 'none';
|
| 170 |
+
btn.disabled = false; // Enable send button when file is selected
|
| 171 |
};
|
| 172 |
reader.readAsDataURL(file);
|
| 173 |
}
|
|
|
|
| 180 |
audioPreview.innerHTML = `<audio controls src="${e.target.result}"></audio>`;
|
| 181 |
audioPreview.style.display = 'block';
|
| 182 |
filePreview.style.display = 'none';
|
| 183 |
+
btn.disabled = false; // Enable send button when audio is selected
|
| 184 |
};
|
| 185 |
reader.readAsDataURL(file);
|
| 186 |
}
|
|
|
|
| 189 |
|
| 190 |
// Voice recording.
|
| 191 |
function startVoiceRecording() {
|
| 192 |
+
if (isRequestActive || isRecording) return;
|
| 193 |
+
isRecording = true;
|
| 194 |
+
btn.classList.add('recording');
|
| 195 |
navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
|
| 196 |
mediaRecorder = new MediaRecorder(stream);
|
| 197 |
audioChunks = [];
|
| 198 |
mediaRecorder.start();
|
|
|
|
| 199 |
mediaRecorder.addEventListener('dataavailable', event => {
|
| 200 |
audioChunks.push(event.data);
|
| 201 |
});
|
| 202 |
}).catch(err => {
|
| 203 |
console.error('Error accessing microphone:', err);
|
| 204 |
alert('Failed to access microphone. Please check permissions.');
|
| 205 |
+
isRecording = false;
|
| 206 |
+
btn.classList.remove('recording');
|
| 207 |
});
|
| 208 |
}
|
| 209 |
|
| 210 |
function stopVoiceRecording() {
|
| 211 |
if (mediaRecorder && mediaRecorder.state === 'recording') {
|
| 212 |
mediaRecorder.stop();
|
| 213 |
+
btn.classList.remove('recording');
|
| 214 |
+
isRecording = false;
|
| 215 |
mediaRecorder.addEventListener('stop', async () => {
|
| 216 |
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
|
| 217 |
const formData = new FormData();
|
|
|
|
| 304 |
|
| 305 |
// Send user message.
|
| 306 |
async function submitMessage() {
|
| 307 |
+
if (isRequestActive || isRecording) return;
|
| 308 |
let message = input.value.trim();
|
| 309 |
let formData = new FormData();
|
| 310 |
let endpoint = '/api/chat';
|
|
|
|
| 354 |
btn.disabled = true;
|
| 355 |
filePreview.style.display = 'none';
|
| 356 |
audioPreview.style.display = 'none';
|
| 357 |
+
autoResizeTextarea();
|
| 358 |
|
| 359 |
isRequestActive = true;
|
| 360 |
abortController = new AbortController();
|
|
|
|
| 451 |
|
| 452 |
// Stop streaming and cancel the ongoing request.
|
| 453 |
function stopStream(forceCancel = false) {
|
| 454 |
+
if (!isRequestActive && !isRecording) return;
|
| 455 |
+
if (isRecording) {
|
| 456 |
+
stopVoiceRecording();
|
| 457 |
+
}
|
| 458 |
isRequestActive = false;
|
| 459 |
if (abortController) {
|
| 460 |
abortController.abort();
|
|
|
|
| 478 |
p.addEventListener('click', e => {
|
| 479 |
e.preventDefault();
|
| 480 |
input.value = p.dataset.prompt;
|
| 481 |
+
autoResizeTextarea();
|
| 482 |
+
btn.disabled = false;
|
| 483 |
submitMessage();
|
| 484 |
});
|
| 485 |
});
|
|
|
|
| 494 |
fileInput.addEventListener('change', previewFile);
|
| 495 |
audioInput.addEventListener('change', previewFile);
|
| 496 |
|
| 497 |
+
// Send button events for send and voice recording.
|
| 498 |
+
btn.addEventListener('mousedown', e => {
|
| 499 |
+
if (btn.disabled || isRequestActive) return;
|
| 500 |
+
pressTimer = setTimeout(() => {
|
| 501 |
+
startVoiceRecording();
|
| 502 |
+
}, 500); // 500ms for long press
|
| 503 |
+
});
|
| 504 |
+
btn.addEventListener('mouseup', e => {
|
| 505 |
+
clearTimeout(pressTimer);
|
| 506 |
+
if (isRecording) {
|
| 507 |
+
stopVoiceRecording();
|
| 508 |
+
}
|
| 509 |
+
});
|
| 510 |
+
btn.addEventListener('touchstart', e => {
|
| 511 |
e.preventDefault();
|
| 512 |
+
if (btn.disabled || isRequestActive) return;
|
| 513 |
+
pressTimer = setTimeout(() => {
|
| 514 |
+
startVoiceRecording();
|
| 515 |
+
}, 500);
|
| 516 |
});
|
| 517 |
+
btn.addEventListener('touchend', e => {
|
|
|
|
| 518 |
e.preventDefault();
|
| 519 |
+
clearTimeout(pressTimer);
|
| 520 |
+
if (isRecording) {
|
| 521 |
+
stopVoiceRecording();
|
| 522 |
+
}
|
| 523 |
});
|
| 524 |
|
| 525 |
// Submit.
|
| 526 |
form.addEventListener('submit', e => {
|
| 527 |
e.preventDefault();
|
| 528 |
+
if (!isRecording) {
|
| 529 |
+
submitMessage();
|
| 530 |
+
}
|
| 531 |
});
|
| 532 |
|
| 533 |
// Handle Enter key to submit without adding new line.
|
| 534 |
input.addEventListener('keydown', e => {
|
| 535 |
if (e.key === 'Enter' && !e.shiftKey) {
|
| 536 |
e.preventDefault();
|
| 537 |
+
if (!isRecording && !btn.disabled) {
|
| 538 |
+
submitMessage();
|
| 539 |
+
}
|
| 540 |
}
|
| 541 |
});
|
| 542 |
|
| 543 |
+
// Auto-resize textarea on input.
|
| 544 |
+
input.addEventListener('input', () => {
|
| 545 |
+
autoResizeTextarea();
|
| 546 |
+
btn.disabled = input.value.trim() === '' && fileInput.files.length === 0 && audioInput.files.length === 0;
|
| 547 |
+
});
|
| 548 |
+
|
| 549 |
// Stop.
|
| 550 |
stopBtn.addEventListener('click', () => {
|
| 551 |
stopBtn.style.pointerEvents = 'none';
|
|
|
|
| 568 |
window.location.href = '/login';
|
| 569 |
});
|
| 570 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
templates/chat.html
CHANGED
|
@@ -119,7 +119,7 @@
|
|
| 119 |
</div>
|
| 120 |
<p class="system text-gray-300 mb-4">
|
| 121 |
A versatile chatbot powered by DeepSeek, GPT-OSS, CLIP, Whisper, and TTS.<br>
|
| 122 |
-
Type your query, upload images/files, or record audio!
|
| 123 |
</p>
|
| 124 |
<!-- Prompts -->
|
| 125 |
<div class="prompts w-full max-w-md mx-auto grid gap-2">
|
|
@@ -176,14 +176,7 @@
|
|
| 176 |
</svg>
|
| 177 |
</button>
|
| 178 |
<input type="file" id="audioInput" accept="audio/*" style="display: none;" />
|
| 179 |
-
<button type="
|
| 180 |
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
|
| 181 |
-
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
|
| 182 |
-
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
|
| 183 |
-
<path d="M12 19v4" />
|
| 184 |
-
</svg>
|
| 185 |
-
</button>
|
| 186 |
-
<button type="submit" id="sendBtn" class="icon-btn" disabled aria-label="Send" title="Send">
|
| 187 |
<svg id="sendIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 188 |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7-7 7M3 12h11" />
|
| 189 |
</svg>
|
|
|
|
| 119 |
</div>
|
| 120 |
<p class="system text-gray-300 mb-4">
|
| 121 |
A versatile chatbot powered by DeepSeek, GPT-OSS, CLIP, Whisper, and TTS.<br>
|
| 122 |
+
Type your query, upload images/files, or hold the send button to record audio!
|
| 123 |
</p>
|
| 124 |
<!-- Prompts -->
|
| 125 |
<div class="prompts w-full max-w-md mx-auto grid gap-2">
|
|
|
|
| 176 |
</svg>
|
| 177 |
</button>
|
| 178 |
<input type="file" id="audioInput" accept="audio/*" style="display: none;" />
|
| 179 |
+
<button type="submit" id="sendBtn" class="icon-btn" disabled aria-label="Send or Hold to Record" title="Click to Send or Hold to Record Voice">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
<svg id="sendIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 181 |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7-7 7M3 12h11" />
|
| 182 |
</svg>
|