Spaces:
Sleeping
Sleeping
Update frontend/script.js
Browse files- frontend/script.js +85 -64
frontend/script.js
CHANGED
|
@@ -271,12 +271,12 @@ class TTSPlayer {
|
|
| 271 |
* Creates a new TTSPlayer instance.
|
| 272 |
*
|
| 273 |
* Properties:
|
| 274 |
-
*
|
| 275 |
-
*
|
| 276 |
-
*
|
| 277 |
-
*
|
| 278 |
-
*
|
| 279 |
-
*
|
| 280 |
*/
|
| 281 |
constructor() {
|
| 282 |
this.queue = [];
|
|
@@ -294,11 +294,11 @@ class TTSPlayer {
|
|
| 294 |
* This should be called during a user gesture (e.g., clicking "Send").
|
| 295 |
*
|
| 296 |
* It does two things:
|
| 297 |
-
*
|
| 298 |
-
*
|
| 299 |
-
*
|
| 300 |
-
*
|
| 301 |
-
*
|
| 302 |
*
|
| 303 |
* After this, the browser treats subsequent .play() calls on the same
|
| 304 |
* <audio> element as user-initiated, even if they happen in an async
|
|
@@ -344,9 +344,9 @@ class TTSPlayer {
|
|
| 344 |
* stop() β Immediately halts all audio playback and clears the queue.
|
| 345 |
*
|
| 346 |
* Called when:
|
| 347 |
-
*
|
| 348 |
-
*
|
| 349 |
-
*
|
| 350 |
*
|
| 351 |
* It also removes visual indicators (CSS classes on the TTS button,
|
| 352 |
* the orb container, and deactivates the orb animation).
|
|
@@ -436,7 +436,7 @@ class TTSPlayer {
|
|
| 436 |
*
|
| 437 |
* @param {string} b64 - Base64-encoded MP3 audio data.
|
| 438 |
* @returns {Promise<void>} Resolves when the audio finishes playing
|
| 439 |
-
*
|
| 440 |
*
|
| 441 |
* Sets the <audio> element's src to a data URL and calls .play().
|
| 442 |
* Returns a Promise that resolves on 'ended' or 'error', so the
|
|
@@ -540,10 +540,10 @@ function saveSettings() {
|
|
| 540 |
* time of day.
|
| 541 |
*
|
| 542 |
* Time ranges:
|
| 543 |
-
*
|
| 544 |
-
*
|
| 545 |
-
*
|
| 546 |
-
*
|
| 547 |
*
|
| 548 |
* This is called on page load and when starting a new chat.
|
| 549 |
*/
|
|
@@ -568,9 +568,9 @@ function setGreeting() {
|
|
| 568 |
* and we skip initialization gracefully.
|
| 569 |
*
|
| 570 |
* Configuration:
|
| 571 |
-
*
|
| 572 |
-
*
|
| 573 |
-
*
|
| 574 |
*
|
| 575 |
* The orb's "active" state (pulsing animation) is toggled via
|
| 576 |
* orb.setActive(true/false), which we call when TTS starts/stops.
|
|
@@ -701,9 +701,9 @@ function initSpeech() {
|
|
| 701 |
* startListening() β Activates the microphone and begins speech recognition.
|
| 702 |
*
|
| 703 |
* Guards:
|
| 704 |
-
*
|
| 705 |
-
*
|
| 706 |
-
*
|
| 707 |
*/
|
| 708 |
function startListening() {
|
| 709 |
if (!recognition || isStreaming || isListening) return;
|
|
@@ -732,10 +732,10 @@ function startListening() {
|
|
| 732 |
* stopListening() β Deactivates the microphone and stops recognition.
|
| 733 |
*
|
| 734 |
* Called when:
|
| 735 |
-
*
|
| 736 |
-
*
|
| 737 |
-
*
|
| 738 |
-
*
|
| 739 |
*/
|
| 740 |
function stopListening() {
|
| 741 |
clearTimeout(speechSendTimeout);
|
|
@@ -773,8 +773,8 @@ function maybeRestartListening() {
|
|
| 773 |
* if the server is running and healthy.
|
| 774 |
*
|
| 775 |
* Updates the status indicator in the UI:
|
| 776 |
-
*
|
| 777 |
-
*
|
| 778 |
*
|
| 779 |
* Uses AbortSignal.timeout(5000) to avoid waiting forever if the
|
| 780 |
* server is down β the request will automatically abort after 5 seconds.
|
|
@@ -937,10 +937,10 @@ function bindEvents() {
|
|
| 937 |
* its content, up to a maximum of 120px.
|
| 938 |
*
|
| 939 |
* How it works:
|
| 940 |
-
*
|
| 941 |
-
*
|
| 942 |
-
*
|
| 943 |
-
*
|
| 944 |
*/
|
| 945 |
function autoResizeInput() {
|
| 946 |
messageInput.style.height = 'auto';
|
|
@@ -976,9 +976,9 @@ function updatePanelOverlay() {
|
|
| 976 |
* @param {string} mode - Either 'general' or 'realtime'.
|
| 977 |
*
|
| 978 |
* Updates:
|
| 979 |
-
*
|
| 980 |
-
*
|
| 981 |
-
*
|
| 982 |
*/
|
| 983 |
function setMode(mode) {
|
| 984 |
currentMode = mode;
|
|
@@ -1003,12 +1003,12 @@ function setMode(mode) {
|
|
| 1003 |
* newChat() β Resets the entire conversation to a fresh state.
|
| 1004 |
*
|
| 1005 |
* Steps:
|
| 1006 |
-
*
|
| 1007 |
-
*
|
| 1008 |
-
*
|
| 1009 |
-
*
|
| 1010 |
-
*
|
| 1011 |
-
*
|
| 1012 |
*/
|
| 1013 |
function newChat() {
|
| 1014 |
if (ttsPlayer) ttsPlayer.stop();
|
|
@@ -1033,13 +1033,13 @@ function newChat() {
|
|
| 1033 |
* createWelcome() β Builds and returns the welcome screen DOM element.
|
| 1034 |
*
|
| 1035 |
* @returns {HTMLDivElement} The welcome screen element, ready to be
|
| 1036 |
-
*
|
| 1037 |
*
|
| 1038 |
* The welcome screen includes:
|
| 1039 |
-
*
|
| 1040 |
-
*
|
| 1041 |
-
*
|
| 1042 |
-
*
|
| 1043 |
*
|
| 1044 |
* The chip buttons get their own click listeners here because they
|
| 1045 |
* are dynamically created (not present in the original HTML).
|
|
@@ -1276,20 +1276,20 @@ function hideWelcome() {
|
|
| 1276 |
* addMessage(role, text) β Creates and appends a chat message bubble.
|
| 1277 |
*
|
| 1278 |
* @param {string} role - Either 'user' or 'assistant'. Determines
|
| 1279 |
-
*
|
| 1280 |
* @param {string} text - The message content to display.
|
| 1281 |
* @returns {HTMLDivElement} The inner content element β returned so
|
| 1282 |
-
*
|
| 1283 |
-
*
|
| 1284 |
*
|
| 1285 |
* DOM structure created:
|
| 1286 |
-
*
|
| 1287 |
-
*
|
| 1288 |
-
*
|
| 1289 |
-
*
|
| 1290 |
-
*
|
| 1291 |
-
*
|
| 1292 |
-
*
|
| 1293 |
*/
|
| 1294 |
/* Inline SVG icons for chat avatars (user = person, assistant = bot). */
|
| 1295 |
const AVATAR_ICON_USER = '<svg class="msg-avatar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>';
|
|
@@ -1371,8 +1371,8 @@ function addTypingIndicator() {
|
|
| 1371 |
* removeTypingIndicator() β Removes the typing indicator from the DOM.
|
| 1372 |
*
|
| 1373 |
* Called when:
|
| 1374 |
-
*
|
| 1375 |
-
*
|
| 1376 |
*/
|
| 1377 |
function removeTypingIndicator() {
|
| 1378 |
const t = document.getElementById('typing-msg');
|
|
@@ -1445,8 +1445,8 @@ function scrollToBottom() {
|
|
| 1445 |
* sendMessage(textOverride) β Sends a user message and streams the AI response.
|
| 1446 |
*
|
| 1447 |
* AUDIO WORKFLOW (minimizes waiting):
|
| 1448 |
-
*
|
| 1449 |
-
*
|
| 1450 |
*/
|
| 1451 |
async function sendMessage(textOverride) {
|
| 1452 |
// Step 1: Get the message text, trimming whitespace
|
|
@@ -1580,8 +1580,29 @@ async function sendMessage(textOverride) {
|
|
| 1580 |
fullResponse += chunkText;
|
| 1581 |
const textSpan = contentEl.querySelector('.msg-stream-text');
|
| 1582 |
if (textSpan) {
|
| 1583 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1584 |
textSpan.classList.remove('stream-placeholder');
|
|
|
|
| 1585 |
}
|
| 1586 |
|
| 1587 |
// Add a blinking cursor at the end (created once, on the first chunk)
|
|
|
|
| 271 |
* Creates a new TTSPlayer instance.
|
| 272 |
*
|
| 273 |
* Properties:
|
| 274 |
+
* queue β Array of base64 audio strings waiting to be played.
|
| 275 |
+
* playing β True if the play loop is currently running.
|
| 276 |
+
* enabled β True if the user has toggled TTS on (via the speaker button).
|
| 277 |
+
* stopped β True if playback was forcibly stopped (e.g., new chat).
|
| 278 |
+
* This prevents queued audio from playing after a stop.
|
| 279 |
+
* audio β A single persistent <audio> element reused for all playback.
|
| 280 |
*/
|
| 281 |
constructor() {
|
| 282 |
this.queue = [];
|
|
|
|
| 294 |
* This should be called during a user gesture (e.g., clicking "Send").
|
| 295 |
*
|
| 296 |
* It does two things:
|
| 297 |
+
* 1. Plays a tiny silent WAV file on the <audio> element, which
|
| 298 |
+
* tells the browser "the user initiated audio playback."
|
| 299 |
+
* 2. Creates a brief AudioContext oscillator at zero volume β this
|
| 300 |
+
* unlocks the Web Audio API context on iOS (a separate lock from
|
| 301 |
+
* the <audio> element).
|
| 302 |
*
|
| 303 |
* After this, the browser treats subsequent .play() calls on the same
|
| 304 |
* <audio> element as user-initiated, even if they happen in an async
|
|
|
|
| 344 |
* stop() β Immediately halts all audio playback and clears the queue.
|
| 345 |
*
|
| 346 |
* Called when:
|
| 347 |
+
* - The user starts a "New Chat"
|
| 348 |
+
* - The user toggles TTS off while audio is playing
|
| 349 |
+
* - We need to reset before a new streaming response
|
| 350 |
*
|
| 351 |
* It also removes visual indicators (CSS classes on the TTS button,
|
| 352 |
* the orb container, and deactivates the orb animation).
|
|
|
|
| 436 |
*
|
| 437 |
* @param {string} b64 - Base64-encoded MP3 audio data.
|
| 438 |
* @returns {Promise<void>} Resolves when the audio finishes playing
|
| 439 |
+
* (or errors out).
|
| 440 |
*
|
| 441 |
* Sets the <audio> element's src to a data URL and calls .play().
|
| 442 |
* Returns a Promise that resolves on 'ended' or 'error', so the
|
|
|
|
| 540 |
* time of day.
|
| 541 |
*
|
| 542 |
* Time ranges:
|
| 543 |
+
* 00:00β11:59 β "Good morning."
|
| 544 |
+
* 12:00β16:59 β "Good afternoon."
|
| 545 |
+
* 17:00β21:59 β "Good evening."
|
| 546 |
+
* 22:00β23:59 β "Burning the midnight oil?" (a fun late-night touch)
|
| 547 |
*
|
| 548 |
* This is called on page load and when starting a new chat.
|
| 549 |
*/
|
|
|
|
| 568 |
* and we skip initialization gracefully.
|
| 569 |
*
|
| 570 |
* Configuration:
|
| 571 |
+
* hue: 0 β The base hue of the orb color
|
| 572 |
+
* hoverIntensity: 0.3 β How much the orb reacts to mouse hover
|
| 573 |
+
* backgroundColor: [0.02,0.02,0.06] β Near-black dark blue background (RGB, 0β1 range)
|
| 574 |
*
|
| 575 |
* The orb's "active" state (pulsing animation) is toggled via
|
| 576 |
* orb.setActive(true/false), which we call when TTS starts/stops.
|
|
|
|
| 701 |
* startListening() β Activates the microphone and begins speech recognition.
|
| 702 |
*
|
| 703 |
* Guards:
|
| 704 |
+
* - Does nothing if recognition isn't available (unsupported browser).
|
| 705 |
+
* - Does nothing if we're currently streaming a response (to avoid
|
| 706 |
+
* accidentally sending a voice message mid-stream).
|
| 707 |
*/
|
| 708 |
function startListening() {
|
| 709 |
if (!recognition || isStreaming || isListening) return;
|
|
|
|
| 732 |
* stopListening() β Deactivates the microphone and stops recognition.
|
| 733 |
*
|
| 734 |
* Called when:
|
| 735 |
+
* - A final transcript is received (auto-send).
|
| 736 |
+
* - The user clicks the mic button again (manual toggle off).
|
| 737 |
+
* - An error occurs.
|
| 738 |
+
* - The recognition engine stops unexpectedly.
|
| 739 |
*/
|
| 740 |
function stopListening() {
|
| 741 |
clearTimeout(speechSendTimeout);
|
|
|
|
| 773 |
* if the server is running and healthy.
|
| 774 |
*
|
| 775 |
* Updates the status indicator in the UI:
|
| 776 |
+
* - Green dot + "Online" if the server responds with { status: "healthy" }
|
| 777 |
+
* - Red dot + "Offline" if the request fails or returns unhealthy
|
| 778 |
*
|
| 779 |
* Uses AbortSignal.timeout(5000) to avoid waiting forever if the
|
| 780 |
* server is down β the request will automatically abort after 5 seconds.
|
|
|
|
| 937 |
* its content, up to a maximum of 120px.
|
| 938 |
*
|
| 939 |
* How it works:
|
| 940 |
+
* 1. Reset height to 'auto' so scrollHeight reflects actual content height.
|
| 941 |
+
* 2. Set height to the smaller of scrollHeight or 120px.
|
| 942 |
+
* This creates a textarea that grows as the user types but doesn't
|
| 943 |
+
* take over the whole screen for very long messages.
|
| 944 |
*/
|
| 945 |
function autoResizeInput() {
|
| 946 |
messageInput.style.height = 'auto';
|
|
|
|
| 976 |
* @param {string} mode - Either 'general' or 'realtime'.
|
| 977 |
*
|
| 978 |
* Updates:
|
| 979 |
+
* - currentMode variable (used when sending messages)
|
| 980 |
+
* - Button active states (highlights the selected button)
|
| 981 |
+
* - Slider position (slides the pill indicator left or right)
|
| 982 |
*/
|
| 983 |
function setMode(mode) {
|
| 984 |
currentMode = mode;
|
|
|
|
| 1003 |
* newChat() β Resets the entire conversation to a fresh state.
|
| 1004 |
*
|
| 1005 |
* Steps:
|
| 1006 |
+
* 1. Stop any playing TTS audio.
|
| 1007 |
+
* 2. Clear the session ID (server will create a new one on next message).
|
| 1008 |
+
* 3. Clear all messages from the chat container.
|
| 1009 |
+
* 4. Re-create and display the welcome screen.
|
| 1010 |
+
* 5. Clear the input field and reset its size.
|
| 1011 |
+
* 6. Update the greeting text (in case time-of-day changed).
|
| 1012 |
*/
|
| 1013 |
function newChat() {
|
| 1014 |
if (ttsPlayer) ttsPlayer.stop();
|
|
|
|
| 1033 |
* createWelcome() β Builds and returns the welcome screen DOM element.
|
| 1034 |
*
|
| 1035 |
* @returns {HTMLDivElement} The welcome screen element, ready to be
|
| 1036 |
+
* appended to the chat container.
|
| 1037 |
*
|
| 1038 |
* The welcome screen includes:
|
| 1039 |
+
* - A decorative SVG icon
|
| 1040 |
+
* - A time-based greeting (same logic as setGreeting)
|
| 1041 |
+
* - A subtitle prompt ("How may I assist you today?")
|
| 1042 |
+
* - Quick-action chip buttons with predefined messages
|
| 1043 |
*
|
| 1044 |
* The chip buttons get their own click listeners here because they
|
| 1045 |
* are dynamically created (not present in the original HTML).
|
|
|
|
| 1276 |
* addMessage(role, text) β Creates and appends a chat message bubble.
|
| 1277 |
*
|
| 1278 |
* @param {string} role - Either 'user' or 'assistant'. Determines
|
| 1279 |
+
* styling, avatar letter, and label text.
|
| 1280 |
* @param {string} text - The message content to display.
|
| 1281 |
* @returns {HTMLDivElement} The inner content element β returned so
|
| 1282 |
+
* the caller (sendMessage) can update it
|
| 1283 |
+
* later during streaming.
|
| 1284 |
*
|
| 1285 |
* DOM structure created:
|
| 1286 |
+
* <div class="message user|assistant">
|
| 1287 |
+
* <div class="msg-avatar"><svg>...</svg></div>
|
| 1288 |
+
* <div class="msg-body">
|
| 1289 |
+
* <div class="msg-label">Jarvis (General) | You</div>
|
| 1290 |
+
* <div class="msg-content">...text...</div>
|
| 1291 |
+
* </div>
|
| 1292 |
+
* </div>
|
| 1293 |
*/
|
| 1294 |
/* Inline SVG icons for chat avatars (user = person, assistant = bot). */
|
| 1295 |
const AVATAR_ICON_USER = '<svg class="msg-avatar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>';
|
|
|
|
| 1371 |
* removeTypingIndicator() β Removes the typing indicator from the DOM.
|
| 1372 |
*
|
| 1373 |
* Called when:
|
| 1374 |
+
* - The first token of the response arrives (replaced by real content).
|
| 1375 |
+
* - An error occurs (replaced by an error message).
|
| 1376 |
*/
|
| 1377 |
function removeTypingIndicator() {
|
| 1378 |
const t = document.getElementById('typing-msg');
|
|
|
|
| 1445 |
* sendMessage(textOverride) β Sends a user message and streams the AI response.
|
| 1446 |
*
|
| 1447 |
* AUDIO WORKFLOW (minimizes waiting):
|
| 1448 |
+
* 1. Pre-starter: Play random cached audio on dedicated PreStarterPlayer (immune to reset).
|
| 1449 |
+
* 2. Main: Stream from chatbot; when first real chunk arrives, reset() and main TTS plays.
|
| 1450 |
*/
|
| 1451 |
async function sendMessage(textOverride) {
|
| 1452 |
// Step 1: Get the message text, trimming whitespace
|
|
|
|
| 1580 |
fullResponse += chunkText;
|
| 1581 |
const textSpan = contentEl.querySelector('.msg-stream-text');
|
| 1582 |
if (textSpan) {
|
| 1583 |
+
// --- SAFE MAGIC INTERCEPTOR ---
|
| 1584 |
+
let displayResponse = fullResponse;
|
| 1585 |
+
|
| 1586 |
+
// 1. Screen par code dikhne se roko aur aawaz mein aane se roko
|
| 1587 |
+
if (displayResponse.includes('[EXECUTE]')) {
|
| 1588 |
+
displayResponse = displayResponse.split('[EXECUTE]')[0].trim();
|
| 1589 |
+
}
|
| 1590 |
+
|
| 1591 |
+
// 2. Chupke se code ko nikal kar PC ko bhejo
|
| 1592 |
+
const match = fullResponse.match(/\[EXECUTE\]([\s\S]*?)\[\/EXECUTE\]/i);
|
| 1593 |
+
if (match) {
|
| 1594 |
+
let pcCommand = match[1].replace(/```python/gi, '').replace(/```/g, '').trim();
|
| 1595 |
+
fullResponse = fullResponse.replace(/\[EXECUTE\][\s\S]*?\[\/EXECUTE\]/i, '').trim();
|
| 1596 |
+
fetch('/api/pc/send-command', {
|
| 1597 |
+
method: 'POST',
|
| 1598 |
+
headers: { 'Content-Type': 'application/json' },
|
| 1599 |
+
body: JSON.stringify({ password: "aadit1812", command: pcCommand })
|
| 1600 |
+
}).catch(err => console.error("Command send error:", err));
|
| 1601 |
+
}
|
| 1602 |
+
|
| 1603 |
+
textSpan.textContent = displayResponse;
|
| 1604 |
textSpan.classList.remove('stream-placeholder');
|
| 1605 |
+
// --- END SAFE MAGIC ---
|
| 1606 |
}
|
| 1607 |
|
| 1608 |
// Add a blinking cursor at the end (created once, on the first chunk)
|