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