From 44fa579d4c31f16b3fcf123395ee7582d6380b1d Mon Sep 17 00:00:00 2001 From: mikejgray Date: Fri, 22 Dec 2023 00:07:36 -0600 Subject: [PATCH] feat: animations by audio recording status --- neon_iris/models/web_sat.py | 2 +- neon_iris/static/scripts/main.js | 0 neon_iris/static/scripts/sprite.js | 20 +++++++++ neon_iris/static/scripts/ui.js | 2 + neon_iris/static/scripts/websocket.js | 16 ++++--- neon_iris/static/sprite.css | 61 +++++++++++++++++++++++++++ neon_iris/static/styles.css | 20 ++++++++- neon_iris/templates/index.html | 5 ++- 8 files changed, 116 insertions(+), 10 deletions(-) delete mode 100644 neon_iris/static/scripts/main.js create mode 100644 neon_iris/static/scripts/sprite.js create mode 100644 neon_iris/static/sprite.css diff --git a/neon_iris/models/web_sat.py b/neon_iris/models/web_sat.py index 87ad757..08ab5ef 100644 --- a/neon_iris/models/web_sat.py +++ b/neon_iris/models/web_sat.py @@ -1,7 +1,7 @@ """API data models for the WebSAT API.""" # NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System # All trademark and other rights reserved by their respective owners -# Copyright 2008-2021 Neongecko.com Inc. +# Copyright 2008-2024 Neongecko.com Inc. # BSD-3 # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: diff --git a/neon_iris/static/scripts/main.js b/neon_iris/static/scripts/main.js deleted file mode 100644 index e69de29..0000000 diff --git a/neon_iris/static/scripts/sprite.js b/neon_iris/static/scripts/sprite.js new file mode 100644 index 0000000..303e72b --- /dev/null +++ b/neon_iris/static/scripts/sprite.js @@ -0,0 +1,20 @@ +const sprite = document.querySelector(".sprite"); + +function triggerWake() { + sprite.classList.remove("record", "waiting"); + sprite.classList.add("wake"); +} + +function triggerRecord() { + sprite.classList.remove("wake", "waiting"); + sprite.classList.add("record"); +} + +function triggerWaiting() { + sprite.classList.remove("wake", "record"); + sprite.classList.add("waiting"); +} + +function triggerDone() { + sprite.classList.remove("wake", "record", "waiting"); +} diff --git a/neon_iris/static/scripts/ui.js b/neon_iris/static/scripts/ui.js index 8730434..7e55d77 100644 --- a/neon_iris/static/scripts/ui.js +++ b/neon_iris/static/scripts/ui.js @@ -18,6 +18,7 @@ function submitMessage() { async function getAIResponse(text = "", recording = "") { try { + triggerWaiting(); // Trigger waiting animation const payload = text !== "" && recording === "" ? { utterance: text } @@ -43,6 +44,7 @@ async function getAIResponse(text = "", recording = "") { // Assuming 'data' contains the AI response in a property named 'reply' const aiMessage = data.transcription; + triggerDone(); // Trigger done animation // Add in the user's transcription if STT if (text === "" && recording !== "") { const userMessage = createMessageDiv("user", data.utterance); diff --git a/neon_iris/static/scripts/websocket.js b/neon_iris/static/scripts/websocket.js index 6ff963b..ccf4071 100644 --- a/neon_iris/static/scripts/websocket.js +++ b/neon_iris/static/scripts/websocket.js @@ -77,12 +77,14 @@ async function handleSpeechEnd(audio) { // Save the spoken audio as a downloadable file const downloadArea = document.getElementById("download-area"); if (downloadArea) { - downloadArea.innerHTML = ""; - const downloadLink = document.createElement("a"); - downloadLink.href = audioUrl; - downloadLink.download = "recorded_audio.wav"; - downloadLink.textContent = "Download Recorded Audio"; - downloadArea.appendChild(downloadLink); + const downloadButton = document.createElement("a"); + downloadButton.href = audioUrl; + downloadButton.download = "recorded_audio.wav"; + downloadButton.textContent = "Download Recorded Audio"; + downloadButton.className = "download-button"; // Add a class for styling + downloadButton.setAttribute("role", "button"); // Accessibility improvement + downloadArea.appendChild(downloadButton); + triggerWaiting(); // Trigger waiting animation } else { console.error("Download area not found"); } @@ -131,6 +133,7 @@ const WebSocketHandler = (() => { audio.onended = () => { console.log("Activation sound is done playing"); if (myVad && !isVadRunning) { + triggerRecord(); // Trigger recording animation myVad.start(); isVadRunning = true; } else if (!shouldListen && isVadRunning) { @@ -138,6 +141,7 @@ const WebSocketHandler = (() => { isVadRunning = false; } }; + triggerWake(); // Trigger wake animation audio.play(); lastActivationTime = currentTime; } diff --git a/neon_iris/static/sprite.css b/neon_iris/static/sprite.css new file mode 100644 index 0000000..73d5d1e --- /dev/null +++ b/neon_iris/static/sprite.css @@ -0,0 +1,61 @@ +/* Base styles for the split sprite */ +.sprite { + width: 40px; /* sprite width adjusted to 40px */ + height: 40px; /* sprite height adjusted to 40px */ + border-radius: 50%; + background-image: linear-gradient(to right, #000 50%, #fff 50%); + box-shadow: 0 0 0 2px #000; /* Adjusted border thickness for smaller size */ + transition: transform 0.3s ease, opacity 0.3s ease; + opacity: 0; /* sprite is invisible by default */ + position: relative; /* Required for absolute positioning of pseudo-elements */ + display: flex; /* Center content */ + justify-content: center; + align-items: center; + margin: 20px; +} + +/* Pulse animation while recording */ +@keyframes pulse { + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.1); + } +} + +.sprite.record { + animation: pulse 1s infinite ease-in-out; + opacity: 1; /* sprite is visible while recording */ +} + +/* Spin animation while waiting for a response */ +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.sprite.waiting { + animation: spin 2s infinite linear; + opacity: 1; /* sprite is visible while waiting */ +} + +/* Appear animation for wake word activation */ +@keyframes appear { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +.sprite.wake { + animation: appear 1s forwards; + opacity: 1; /* sprite is visible when awake */ +} diff --git a/neon_iris/static/styles.css b/neon_iris/static/styles.css index 64ea000..a048b26 100644 --- a/neon_iris/static/styles.css +++ b/neon_iris/static/styles.css @@ -7,11 +7,15 @@ html { margin: 0; padding: 0; } +img.logo { + width: 40px; + height: 40px; +} a { text-decoration: none; } a:link { - color: #000; + color: #fff; border-bottom: 1px solid #ff0000; } a:visited { @@ -19,7 +23,7 @@ a:visited { border-bottom: 1px solid #b3b3b3; } a:hover { - color: #2d8653; + color: black; border-bottom: 1px solid #000099; } .button-container { @@ -116,6 +120,18 @@ a:hover { text-align: center; font-size: 1.5em; } +.download-button { + display: inline-block; + padding: 10px 20px; + margin: 10px 0; + background-color: #03a9f4; + color: #ffffff; + text-align: center; + text-decoration: none; + border-radius: 5px; + transition: background-color 0.3s; +} + /* Style for user messages */ .user-message { background-color: #007bff; /* Blue background for user messages */ diff --git a/neon_iris/templates/index.html b/neon_iris/templates/index.html index 6969c90..ddb610a 100644 --- a/neon_iris/templates/index.html +++ b/neon_iris/templates/index.html @@ -6,11 +6,12 @@ {{ title }} +
- {{ description }} + {{ description }}
@@ -22,6 +23,7 @@
+
@@ -36,4 +38,5 @@ +