Update templates/index.html
Browse files- templates/index.html +50 -56
templates/index.html
CHANGED
|
@@ -4,31 +4,42 @@
|
|
| 4 |
<meta charset="utf-8" />
|
| 5 |
<title>Ortho Buddy — Voice</title>
|
| 6 |
<style>
|
| 7 |
-
html,body { height:100%; margin:0; font-family: "Segoe UI", Roboto, Arial;
|
| 8 |
-
background: radial-gradient(circle at 50% 10%, #144a2b, #062219); color:#bfe6c9; }
|
| 9 |
.container { width:360px; margin:40px auto; text-align:center; }
|
| 10 |
-
h1 { letter-spacing:4px; font-size:36px; color:#2de08b;
|
| 11 |
-
text-shadow:0 6px 30px rgba(0,0,0,0.7); margin:20px 0; }
|
| 12 |
.subtitle { color:#d6f3de; margin-bottom:10px; font-size:24px; }
|
| 13 |
-
.robot { width:270px; height:270px; background: url('/static/robot.gif') center/contain no-repeat;
|
| 14 |
-
margin: 40px auto; border-radius:8px; }
|
| 15 |
.control { margin-top:20px; }
|
| 16 |
-
.record-btn { width:50px; height:50px; border-radius:60px; border:none;
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
.reset-btn {
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
</style>
|
|
|
|
| 28 |
</head>
|
| 29 |
<body>
|
| 30 |
-
<h1 class="heading">Summit Dental & Orthopedics</h1>
|
| 31 |
<div class="container">
|
|
|
|
| 32 |
<div class="subtitle"><b>KAMMI</b></div>
|
| 33 |
|
| 34 |
<div class="robot" id="robotImg"></div>
|
|
@@ -42,9 +53,7 @@
|
|
| 42 |
<div class="text-stream" id="textStream"></div>
|
| 43 |
|
| 44 |
<!-- Hidden audio player for streamed voice -->
|
| 45 |
-
<audio id="player" controls autoplay
|
| 46 |
-
<!-- Optional fallback button for mobile autoplay -->
|
| 47 |
-
<button id="playBtn" style="display:none;">▶️ Play Response</button>
|
| 48 |
</div>
|
| 49 |
|
| 50 |
<script>
|
|
@@ -54,12 +63,11 @@ let recBtn = document.getElementById("recBtn");
|
|
| 54 |
let textStream = document.getElementById("textStream");
|
| 55 |
let recording = false;
|
| 56 |
let player = document.getElementById("player");
|
| 57 |
-
let playBtn = document.getElementById("playBtn");
|
| 58 |
-
|
| 59 |
-
// Reset chat
|
| 60 |
document.getElementById("resetBtn").addEventListener("click", async () => {
|
| 61 |
try {
|
| 62 |
-
const response = await fetch("/reset_chat", {
|
|
|
|
|
|
|
| 63 |
if (response.ok) {
|
| 64 |
showTempMessage("Please proceed.", "lightgreen");
|
| 65 |
} else {
|
|
@@ -70,23 +78,18 @@ document.getElementById("resetBtn").addEventListener("click", async () => {
|
|
| 70 |
showTempMessage("Reset error: " + error.message, "#ffb3b3");
|
| 71 |
}
|
| 72 |
});
|
| 73 |
-
|
| 74 |
// Utility function to show a message for 2 seconds
|
| 75 |
function showTempMessage(msg, color) {
|
| 76 |
const msgDiv = document.createElement("div");
|
| 77 |
msgDiv.style.color = color;
|
| 78 |
msgDiv.textContent = msg;
|
| 79 |
textStream.appendChild(msgDiv);
|
| 80 |
-
setTimeout(() => {
|
|
|
|
|
|
|
| 81 |
}
|
| 82 |
-
|
| 83 |
-
// Record button logic
|
| 84 |
recBtn.addEventListener("click", async () => {
|
| 85 |
if (!recording) {
|
| 86 |
-
// Prime audio for mobile autoplay
|
| 87 |
-
try { await player.play(); player.pause(); }
|
| 88 |
-
catch(e) { console.warn("Audio priming failed:", e); }
|
| 89 |
-
|
| 90 |
await startRecording();
|
| 91 |
} else {
|
| 92 |
stopRecordingAndSend();
|
|
@@ -95,10 +98,8 @@ recBtn.addEventListener("click", async () => {
|
|
| 95 |
recBtn.textContent = recording ? "Stop" : "🎤";
|
| 96 |
recBtn.classList.toggle("recording", recording);
|
| 97 |
});
|
| 98 |
-
|
| 99 |
-
// Start recording
|
| 100 |
async function startRecording() {
|
| 101 |
-
textStream.innerHTML = "";
|
| 102 |
audioChunks = [];
|
| 103 |
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
| 104 |
alert("Your browser does not support microphone capture.");
|
|
@@ -106,11 +107,11 @@ async function startRecording() {
|
|
| 106 |
}
|
| 107 |
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
| 108 |
mediaRecorder = new MediaRecorder(stream);
|
| 109 |
-
mediaRecorder.ondataavailable = e => {
|
|
|
|
|
|
|
| 110 |
mediaRecorder.start();
|
| 111 |
}
|
| 112 |
-
|
| 113 |
-
// Stop recording & send to server
|
| 114 |
function stopRecordingAndSend() {
|
| 115 |
if (!mediaRecorder) return;
|
| 116 |
mediaRecorder.stop();
|
|
@@ -118,21 +119,24 @@ function stopRecordingAndSend() {
|
|
| 118 |
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
|
| 119 |
const form = new FormData();
|
| 120 |
form.append("file", audioBlob, "recording.webm");
|
| 121 |
-
|
| 122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
if (!resp.ok) {
|
| 124 |
const txt = await resp.text();
|
| 125 |
textStream.innerHTML += "<div style='color:#ffb3b3'>Server error: " + txt + "</div>";
|
| 126 |
return;
|
| 127 |
}
|
| 128 |
-
|
| 129 |
-
// Stream audio using MediaSource
|
| 130 |
const mediaSource = new MediaSource();
|
| 131 |
player.src = URL.createObjectURL(mediaSource);
|
| 132 |
mediaSource.addEventListener('sourceopen', async () => {
|
| 133 |
const sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');
|
| 134 |
const reader = resp.body.getReader();
|
| 135 |
-
while(true) {
|
| 136 |
const { done, value } = await reader.read();
|
| 137 |
if (done) break;
|
| 138 |
sourceBuffer.appendBuffer(value);
|
|
@@ -140,19 +144,9 @@ function stopRecordingAndSend() {
|
|
| 140 |
}
|
| 141 |
mediaSource.endOfStream();
|
| 142 |
});
|
| 143 |
-
|
| 144 |
-
// Show fallback play button if autoplay blocked
|
| 145 |
-
player.play().catch(() => {
|
| 146 |
-
playBtn.style.display = "inline-block";
|
| 147 |
-
});
|
| 148 |
};
|
| 149 |
}
|
| 150 |
-
|
| 151 |
-
// Fallback play button
|
| 152 |
-
playBtn.addEventListener("click", () => {
|
| 153 |
-
player.play();
|
| 154 |
-
playBtn.style.display = "none";
|
| 155 |
-
});
|
| 156 |
</script>
|
| 157 |
</body>
|
| 158 |
-
</html>
|
|
|
|
| 4 |
<meta charset="utf-8" />
|
| 5 |
<title>Ortho Buddy — Voice</title>
|
| 6 |
<style>
|
| 7 |
+
html,body { height:100%; margin:0; font-family: "Segoe UI", Roboto, Arial; background: radial-gradient(circle at 50% 10%, #144a2b, #062219); color:#bfe6c9; }
|
|
|
|
| 8 |
.container { width:360px; margin:40px auto; text-align:center; }
|
| 9 |
+
h1 { letter-spacing:4px; font-size:36px; color:#2de08b; text-shadow:0 6px 30px rgba(0,0,0,0.7); margin:20px 0; }
|
|
|
|
| 10 |
.subtitle { color:#d6f3de; margin-bottom:10px; font-size:24px; }
|
| 11 |
+
.robot { width:270px; height:270px; background: url('/static/robot.gif') center/contain no-repeat; margin: 40px auto; border-radius:8px; }
|
|
|
|
| 12 |
.control { margin-top:20px; }
|
| 13 |
+
.record-btn { width:50px; height:50px; border-radius:60px; border:none; background:linear-gradient(rgb(248, 245, 248), rgb(248, 245, 248)); box-shadow: 0 10px 30px rgba(0,0,0,0.6); color:white; font-size:18px; cursor:pointer; }
|
| 14 |
+
.record-btn.recording { background: linear-gradient(#ff6666, #cc2222); box-shadow: 0 10px 30px rgba(0,0,0,0.7); }
|
| 15 |
+
.heading {
|
| 16 |
+
text-align: center;
|
| 17 |
+
}
|
| 18 |
+
.reset-btn {
|
| 19 |
+
position: fixed;
|
| 20 |
+
top: 20px;
|
| 21 |
+
right: 20px;
|
| 22 |
+
padding: 10px 20px;
|
| 23 |
+
background: linear-gradient(#2de08b, #0a8f5c);
|
| 24 |
+
border: none;
|
| 25 |
+
border-radius: 6px;
|
| 26 |
+
font-size: 16px;
|
| 27 |
+
font-weight: bold;
|
| 28 |
+
color: #062219;
|
| 29 |
+
cursor: pointer;
|
| 30 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
| 31 |
+
z-index: 1000;
|
| 32 |
+
transition: background 0.3s ease;
|
| 33 |
+
}
|
| 34 |
+
.reset-btn:hover {
|
| 35 |
+
background: linear-gradient(#1bc47a, #07734f);
|
| 36 |
+
}
|
| 37 |
</style>
|
| 38 |
+
<h1 class = "heading">Summit Dental & Orthopedics</h1>
|
| 39 |
</head>
|
| 40 |
<body>
|
|
|
|
| 41 |
<div class="container">
|
| 42 |
+
|
| 43 |
<div class="subtitle"><b>KAMMI</b></div>
|
| 44 |
|
| 45 |
<div class="robot" id="robotImg"></div>
|
|
|
|
| 53 |
<div class="text-stream" id="textStream"></div>
|
| 54 |
|
| 55 |
<!-- Hidden audio player for streamed voice -->
|
| 56 |
+
<audio id="player" controls autoplay hidden></audio>
|
|
|
|
|
|
|
| 57 |
</div>
|
| 58 |
|
| 59 |
<script>
|
|
|
|
| 63 |
let textStream = document.getElementById("textStream");
|
| 64 |
let recording = false;
|
| 65 |
let player = document.getElementById("player");
|
|
|
|
|
|
|
|
|
|
| 66 |
document.getElementById("resetBtn").addEventListener("click", async () => {
|
| 67 |
try {
|
| 68 |
+
const response = await fetch("/reset_chat", {
|
| 69 |
+
method: "POST"
|
| 70 |
+
});
|
| 71 |
if (response.ok) {
|
| 72 |
showTempMessage("Please proceed.", "lightgreen");
|
| 73 |
} else {
|
|
|
|
| 78 |
showTempMessage("Reset error: " + error.message, "#ffb3b3");
|
| 79 |
}
|
| 80 |
});
|
|
|
|
| 81 |
// Utility function to show a message for 2 seconds
|
| 82 |
function showTempMessage(msg, color) {
|
| 83 |
const msgDiv = document.createElement("div");
|
| 84 |
msgDiv.style.color = color;
|
| 85 |
msgDiv.textContent = msg;
|
| 86 |
textStream.appendChild(msgDiv);
|
| 87 |
+
setTimeout(() => {
|
| 88 |
+
msgDiv.remove();
|
| 89 |
+
}, 2000);
|
| 90 |
}
|
|
|
|
|
|
|
| 91 |
recBtn.addEventListener("click", async () => {
|
| 92 |
if (!recording) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
await startRecording();
|
| 94 |
} else {
|
| 95 |
stopRecordingAndSend();
|
|
|
|
| 98 |
recBtn.textContent = recording ? "Stop" : "🎤";
|
| 99 |
recBtn.classList.toggle("recording", recording);
|
| 100 |
});
|
|
|
|
|
|
|
| 101 |
async function startRecording() {
|
| 102 |
+
textStream.innerHTML = ""; // clear previous
|
| 103 |
audioChunks = [];
|
| 104 |
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
| 105 |
alert("Your browser does not support microphone capture.");
|
|
|
|
| 107 |
}
|
| 108 |
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
| 109 |
mediaRecorder = new MediaRecorder(stream);
|
| 110 |
+
mediaRecorder.ondataavailable = e => {
|
| 111 |
+
if (e.data && e.data.size > 0) audioChunks.push(e.data);
|
| 112 |
+
};
|
| 113 |
mediaRecorder.start();
|
| 114 |
}
|
|
|
|
|
|
|
| 115 |
function stopRecordingAndSend() {
|
| 116 |
if (!mediaRecorder) return;
|
| 117 |
mediaRecorder.stop();
|
|
|
|
| 119 |
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
|
| 120 |
const form = new FormData();
|
| 121 |
form.append("file", audioBlob, "recording.webm");
|
| 122 |
+
// fetch streaming audio directly
|
| 123 |
+
console.log("Sending audio to server...");
|
| 124 |
+
const resp = await fetch("/chat_stream", {
|
| 125 |
+
method: "POST",
|
| 126 |
+
body: form,
|
| 127 |
+
});
|
| 128 |
if (!resp.ok) {
|
| 129 |
const txt = await resp.text();
|
| 130 |
textStream.innerHTML += "<div style='color:#ffb3b3'>Server error: " + txt + "</div>";
|
| 131 |
return;
|
| 132 |
}
|
| 133 |
+
// create an object URL from streaming response
|
|
|
|
| 134 |
const mediaSource = new MediaSource();
|
| 135 |
player.src = URL.createObjectURL(mediaSource);
|
| 136 |
mediaSource.addEventListener('sourceopen', async () => {
|
| 137 |
const sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');
|
| 138 |
const reader = resp.body.getReader();
|
| 139 |
+
while (true) {
|
| 140 |
const { done, value } = await reader.read();
|
| 141 |
if (done) break;
|
| 142 |
sourceBuffer.appendBuffer(value);
|
|
|
|
| 144 |
}
|
| 145 |
mediaSource.endOfStream();
|
| 146 |
});
|
| 147 |
+
player.play();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
};
|
| 149 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
</script>
|
| 151 |
</body>
|
| 152 |
+
</html>
|