Add real image input audio input and output for any media inferencing models
Browse files- index.html +392 -27
index.html
CHANGED
|
@@ -373,7 +373,6 @@
|
|
| 373 |
<label class="block text-slate-300 mb-2">Selected File</label>
|
| 374 |
<div id="previewContent" class="bg-slate-800 rounded-lg p-4"></div>
|
| 375 |
</div>
|
| 376 |
-
|
| 377 |
<!-- Media Controls -->
|
| 378 |
<div class="mb-6 hidden" id="mediaControls">
|
| 379 |
<label class="block text-slate-300 mb-3">Media Controls</label>
|
|
@@ -381,21 +380,68 @@
|
|
| 381 |
<div class="flex items-center justify-between">
|
| 382 |
<span>Auto-play</span>
|
| 383 |
<label class="relative inline-flex items-center cursor-pointer">
|
| 384 |
-
<input type="checkbox" class="sr-only peer">
|
| 385 |
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
| 386 |
</label>
|
| 387 |
</div>
|
| 388 |
<div class="flex items-center justify-between">
|
| 389 |
<span>Loop</span>
|
| 390 |
<label class="relative inline-flex items-center cursor-pointer">
|
| 391 |
-
<input type="checkbox" class="sr-only peer" checked>
|
| 392 |
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
| 393 |
</label>
|
| 394 |
</div>
|
| 395 |
</div>
|
| 396 |
</div>
|
| 397 |
|
| 398 |
-
<!--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
<div class="mb-6" id="preprocessingSection">
|
| 400 |
<label class="block text-slate-300 mb-2">Preprocessing</label>
|
| 401 |
<div class="space-y-3" id="preprocessingOptions">
|
|
@@ -503,10 +549,20 @@
|
|
| 503 |
let currentInputType = 'image';
|
| 504 |
let modelInfo = {};
|
| 505 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
// Initialize Feather Icons
|
| 507 |
feather.replace();
|
| 508 |
-
|
| 509 |
-
// DOM Elements
|
| 510 |
const modelTypeCards = document.querySelectorAll('[data-model-type]');
|
| 511 |
const uploadSection = document.getElementById('uploadSection');
|
| 512 |
const uploadTitle = document.getElementById('uploadTitle');
|
|
@@ -531,6 +587,24 @@
|
|
| 531 |
const preprocessingSection = document.getElementById('preprocessingSection');
|
| 532 |
const preprocessingOptions = document.getElementById('preprocessingOptions');
|
| 533 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 534 |
// Output elements
|
| 535 |
const inferenceTime = document.getElementById('inferenceTime');
|
| 536 |
const outputProgress = document.getElementById('outputProgress');
|
|
@@ -625,6 +699,136 @@
|
|
| 625 |
modelFileInput.click();
|
| 626 |
});
|
| 627 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 628 |
// Input type selection
|
| 629 |
inputTypeGrid.addEventListener('click', (e) => {
|
| 630 |
const btn = e.target.closest('.input-type-btn');
|
|
@@ -662,6 +866,10 @@
|
|
| 662 |
});
|
| 663 |
|
| 664 |
function selectInputSource(source) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 665 |
// Update button styles
|
| 666 |
document.querySelectorAll('.input-source-btn').forEach(btn => {
|
| 667 |
btn.classList.remove('bg-indigo-600');
|
|
@@ -674,6 +882,15 @@
|
|
| 674 |
textInputArea.classList.toggle('hidden', source !== 'text');
|
| 675 |
filePreview.classList.toggle('hidden', source !== 'upload');
|
| 676 |
mediaControls.classList.toggle('hidden', !['audio', 'video'].includes(currentInputType));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 677 |
|
| 678 |
logMessage(`Selected ${source} input source`);
|
| 679 |
}
|
|
@@ -704,22 +921,22 @@
|
|
| 704 |
function updatePreprocessingOptions(inputType) {
|
| 705 |
const options = {
|
| 706 |
image: [
|
| 707 |
-
{ id: 'resize', label: 'Resize', checked: true },
|
| 708 |
-
{ id: 'normalize', label: 'Normalize', checked: true },
|
| 709 |
-
{ id: '裁剪', label: 'Crop', checked: false },
|
| 710 |
-
{ id: 'rotate', label: 'Rotate', checked: false },
|
| 711 |
-
{ id: 'flip', label: 'Flip', checked: false }
|
| 712 |
],
|
| 713 |
audio: [
|
| 714 |
-
{ id: 'resample', label: 'Resample', checked: true },
|
| 715 |
-
{ id: 'normalize', label: 'Normalize', checked: true },
|
| 716 |
-
{ id: 'denoise', label: '
|
| 717 |
{ id: 'trim', label: 'Trim Silence', checked: false },
|
| 718 |
-
{ id: 'augment', label: '
|
| 719 |
],
|
| 720 |
text: [
|
| 721 |
-
{ id: 'tokenize', label: 'Tokenize', checked: true },
|
| 722 |
-
{ id: 'lowercase', label: 'Lowercase', checked: false },
|
| 723 |
{ id: 'remove_punct', label: 'Remove Punctuation', checked: false },
|
| 724 |
{ id: 'stop_words', label: 'Remove Stop Words', checked: false },
|
| 725 |
{ id: 'stem', label: 'Stemming', checked: false }
|
|
@@ -768,6 +985,43 @@
|
|
| 768 |
analysisSection.scrollIntoView({ behavior: 'smooth' });
|
| 769 |
}
|
| 770 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 771 |
// Populate analysis data based on model type
|
| 772 |
function populateAnalysisData(file) {
|
| 773 |
modelInfo = {
|
|
@@ -851,6 +1105,10 @@
|
|
| 851 |
// Simulate inference process
|
| 852 |
logMessage('Starting inference...');
|
| 853 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 854 |
// Simulate processing steps
|
| 855 |
const steps = [
|
| 856 |
'Preprocessing input data...',
|
|
@@ -869,7 +1127,7 @@
|
|
| 869 |
inferenceTime.textContent = (endTime - startTime) + 'ms';
|
| 870 |
|
| 871 |
// Display results
|
| 872 |
-
displayResults();
|
| 873 |
|
| 874 |
executeBtn.disabled = false;
|
| 875 |
executeBtn.innerHTML = '<i data-feather="play" class="mr-2"></i>Execute Inference';
|
|
@@ -877,18 +1135,57 @@
|
|
| 877 |
logMessage('Inference completed successfully');
|
| 878 |
});
|
| 879 |
|
| 880 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 881 |
const outputType = currentInputType;
|
| 882 |
let outputHTML = '';
|
| 883 |
|
| 884 |
switch (outputType) {
|
| 885 |
case 'image':
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 886 |
outputHTML = `
|
| 887 |
<div class="text-center">
|
| 888 |
-
<
|
|
|
|
| 889 |
<i data-feather="image" class="text-white w-16 h-16"></i>
|
| 890 |
-
</div>
|
| 891 |
-
<p class="text-slate-300">
|
| 892 |
<p class="text-sm text-slate-400 mt-2">224x224 RGB</p>
|
| 893 |
</div>
|
| 894 |
`;
|
|
@@ -896,8 +1193,18 @@
|
|
| 896 |
break;
|
| 897 |
|
| 898 |
case 'audio':
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 899 |
outputHTML = `
|
| 900 |
<div class="text-center">
|
|
|
|
| 901 |
<div class="bg-slate-700 rounded-lg p-6 mb-4">
|
| 902 |
<div class="flex items-center justify-center mb-4">
|
| 903 |
<i data-feather="play" class="text-emerald-400 w-8 h-8 mr-2"></i>
|
|
@@ -908,22 +1215,25 @@
|
|
| 908 |
`<div class="bg-emerald-400 w-2 rounded-t" style="height: ${Math.random() * 80 + 20}%"></div>`
|
| 909 |
).join('')}
|
| 910 |
</div>
|
| 911 |
-
</div>
|
| 912 |
-
<p class="text-slate-300">Audio Output
|
| 913 |
-
<p class="text-sm text-slate-400 mt-2">
|
| 914 |
</div>
|
| 915 |
`;
|
| 916 |
downloadOptions.classList.remove('hidden');
|
| 917 |
break;
|
| 918 |
|
| 919 |
case 'text':
|
|
|
|
| 920 |
outputHTML = `
|
| 921 |
<div class="text-left">
|
| 922 |
<div class="bg-slate-800 rounded-lg p-4 mb-4">
|
| 923 |
-
<p class="text-slate-300">
|
|
|
|
|
|
|
| 924 |
</div>
|
| 925 |
<div class="flex items-center space-x-4">
|
| 926 |
-
<button class="px-3 py-1 bg-slate-700 hover:bg-slate-600 rounded text-sm">
|
| 927 |
<i data-feather="copy" class="w-4 h-4 mr-1 inline"></i>Copy
|
| 928 |
</button>
|
| 929 |
<button class="px-3 py-1 bg-slate-700 hover:bg-slate-600 rounded text-sm">
|
|
@@ -943,6 +1253,55 @@
|
|
| 943 |
outputDisplay.classList.remove('hidden');
|
| 944 |
}
|
| 945 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 946 |
// Log messages to console
|
| 947 |
function logMessage(message) {
|
| 948 |
const timestamp = new Date().toLocaleTimeString();
|
|
@@ -992,6 +1351,12 @@
|
|
| 992 |
loadModelBtn.addEventListener('click', () => {
|
| 993 |
modelFileInput.click();
|
| 994 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 995 |
</script>
|
| 996 |
</body>
|
| 997 |
</html>
|
|
|
|
| 373 |
<label class="block text-slate-300 mb-2">Selected File</label>
|
| 374 |
<div id="previewContent" class="bg-slate-800 rounded-lg p-4"></div>
|
| 375 |
</div>
|
|
|
|
| 376 |
<!-- Media Controls -->
|
| 377 |
<div class="mb-6 hidden" id="mediaControls">
|
| 378 |
<label class="block text-slate-300 mb-3">Media Controls</label>
|
|
|
|
| 380 |
<div class="flex items-center justify-between">
|
| 381 |
<span>Auto-play</span>
|
| 382 |
<label class="relative inline-flex items-center cursor-pointer">
|
| 383 |
+
<input type="checkbox" id="autoPlayToggle" class="sr-only peer">
|
| 384 |
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
| 385 |
</label>
|
| 386 |
</div>
|
| 387 |
<div class="flex items-center justify-between">
|
| 388 |
<span>Loop</span>
|
| 389 |
<label class="relative inline-flex items-center cursor-pointer">
|
| 390 |
+
<input type="checkbox" id="loopToggle" class="sr-only peer" checked>
|
| 391 |
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
| 392 |
</label>
|
| 393 |
</div>
|
| 394 |
</div>
|
| 395 |
</div>
|
| 396 |
|
| 397 |
+
<!-- Camera Stream -->
|
| 398 |
+
<div class="mb-6 hidden" id="cameraSection">
|
| 399 |
+
<label class="block text-slate-300 mb-2">Camera Stream</label>
|
| 400 |
+
<video id="cameraStream" class="w-full max-h-48 bg-black rounded-lg" autoplay muted playsinline></video>
|
| 401 |
+
<div class="flex space-x-2 mt-3">
|
| 402 |
+
<button id="captureBtn" class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 rounded-lg text-sm transition flex items-center">
|
| 403 |
+
<i data-feather="camera" class="mr-2"></i>Capture
|
| 404 |
+
</button>
|
| 405 |
+
<button id="stopCameraBtn" class="px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg text-sm transition flex items-center">
|
| 406 |
+
<i data-feather="stop-circle" class="mr-2"></i>Stop
|
| 407 |
+
</button>
|
| 408 |
+
</div>
|
| 409 |
+
</div>
|
| 410 |
+
|
| 411 |
+
<!-- Microphone Stream -->
|
| 412 |
+
<div class="mb-6 hidden" id="microphoneSection">
|
| 413 |
+
<label class="block text-slate-300 mb-2">Audio Recording</label>
|
| 414 |
+
<div id="audioLevel" class="bg-slate-800 rounded-lg p-4">
|
| 415 |
+
<div class="flex items-center justify-between mb-2">
|
| 416 |
+
<span class="text-sm text-slate-400">Recording Level</span>
|
| 417 |
+
<span id="audioLevelValue" class="text-sm text-slate-300">0%</span>
|
| 418 |
+
</div>
|
| 419 |
+
<div class="h-2 bg-slate-700 rounded-full overflow-hidden">
|
| 420 |
+
<div id="audioLevelBar" class="h-full bg-emerald-400 rounded-full transition-all duration-200" style="width: 0%"></div>
|
| 421 |
+
</div>
|
| 422 |
+
</div>
|
| 423 |
+
<div class="flex space-x-2 mt-3">
|
| 424 |
+
<button id="startRecordingBtn" class="px-4 py-2 bg-emerald-600 hover:bg-emerald-700 rounded-lg text-sm transition flex items-center">
|
| 425 |
+
<i data-feather="mic" class="mr-2"></i>Start Recording
|
| 426 |
+
</button>
|
| 427 |
+
<button id="stopRecordingBtn" class="px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg text-sm transition flex items-center">
|
| 428 |
+
<i data-feather="square" class="mr-2"></i>Stop
|
| 429 |
+
</button>
|
| 430 |
+
</div>
|
| 431 |
+
</div>
|
| 432 |
+
|
| 433 |
+
<!-- Captured Image -->
|
| 434 |
+
<div class="mb-6 hidden" id="capturedImageSection">
|
| 435 |
+
<label class="block text-slate-300 mb-2">Captured Image</label>
|
| 436 |
+
<canvas id="capturedCanvas" class="w-full max-h-48 border border-slate-600 rounded-lg"></canvas>
|
| 437 |
+
</div>
|
| 438 |
+
|
| 439 |
+
<!-- Recorded Audio -->
|
| 440 |
+
<div class="mb-6 hidden" id="recordedAudioSection">
|
| 441 |
+
<label class="block text-slate-300 mb-2">Recorded Audio</label>
|
| 442 |
+
<audio id="recordedAudio" class="w-full" controls></audio>
|
| 443 |
+
</div>
|
| 444 |
+
<!-- Preprocessing Options -->
|
| 445 |
<div class="mb-6" id="preprocessingSection">
|
| 446 |
<label class="block text-slate-300 mb-2">Preprocessing</label>
|
| 447 |
<div class="space-y-3" id="preprocessingOptions">
|
|
|
|
| 549 |
let currentInputType = 'image';
|
| 550 |
let modelInfo = {};
|
| 551 |
|
| 552 |
+
// Media streams
|
| 553 |
+
let cameraStream = null;
|
| 554 |
+
let microphoneStream = null;
|
| 555 |
+
let mediaRecorder = null;
|
| 556 |
+
let recordedChunks = [];
|
| 557 |
+
let audioContext = null;
|
| 558 |
+
let audioAnalyzer = null;
|
| 559 |
+
let audioLevelInterval = null;
|
| 560 |
+
let currentCapturedImage = null;
|
| 561 |
+
let currentRecordedAudioBlob = null;
|
| 562 |
+
|
| 563 |
// Initialize Feather Icons
|
| 564 |
feather.replace();
|
| 565 |
+
// DOM Elements
|
|
|
|
| 566 |
const modelTypeCards = document.querySelectorAll('[data-model-type]');
|
| 567 |
const uploadSection = document.getElementById('uploadSection');
|
| 568 |
const uploadTitle = document.getElementById('uploadTitle');
|
|
|
|
| 587 |
const preprocessingSection = document.getElementById('preprocessingSection');
|
| 588 |
const preprocessingOptions = document.getElementById('preprocessingOptions');
|
| 589 |
|
| 590 |
+
// New media elements
|
| 591 |
+
const cameraSection = document.getElementById('cameraSection');
|
| 592 |
+
const microphoneSection = document.getElementById('microphoneSection');
|
| 593 |
+
const capturedImageSection = document.getElementById('capturedImageSection');
|
| 594 |
+
const recordedAudioSection = document.getElementById('recordedAudioSection');
|
| 595 |
+
const cameraStream = document.getElementById('cameraStream');
|
| 596 |
+
const audioLevel = document.getElementById('audioLevel');
|
| 597 |
+
const audioLevelBar = document.getElementById('audioLevelBar');
|
| 598 |
+
const audioLevelValue = document.getElementById('audioLevelValue');
|
| 599 |
+
const capturedCanvas = document.getElementById('capturedCanvas');
|
| 600 |
+
const recordedAudio = document.getElementById('recordedAudio');
|
| 601 |
+
const captureBtn = document.getElementById('captureBtn');
|
| 602 |
+
const stopCameraBtn = document.getElementById('stopCameraBtn');
|
| 603 |
+
const startRecordingBtn = document.getElementById('startRecordingBtn');
|
| 604 |
+
const stopRecordingBtn = document.getElementById('stopRecordingBtn');
|
| 605 |
+
const autoPlayToggle = document.getElementById('autoPlayToggle');
|
| 606 |
+
const loopToggle = document.getElementById('loopToggle');
|
| 607 |
+
|
| 608 |
// Output elements
|
| 609 |
const inferenceTime = document.getElementById('inferenceTime');
|
| 610 |
const outputProgress = document.getElementById('outputProgress');
|
|
|
|
| 699 |
modelFileInput.click();
|
| 700 |
});
|
| 701 |
|
| 702 |
+
// Camera functions
|
| 703 |
+
async function startCamera() {
|
| 704 |
+
try {
|
| 705 |
+
cameraStream = await navigator.mediaDevices.getUserMedia({
|
| 706 |
+
video: { width: 640, height: 480 }
|
| 707 |
+
});
|
| 708 |
+
cameraStream.srcObject = cameraStream;
|
| 709 |
+
cameraSection.classList.remove('hidden');
|
| 710 |
+
logMessage('Camera started successfully');
|
| 711 |
+
} catch (error) {
|
| 712 |
+
console.error('Error accessing camera:', error);
|
| 713 |
+
logMessage('Error: Could not access camera - ' + error.message);
|
| 714 |
+
alert('Could not access camera. Please check permissions.');
|
| 715 |
+
}
|
| 716 |
+
}
|
| 717 |
+
|
| 718 |
+
function stopCamera() {
|
| 719 |
+
if (cameraStream) {
|
| 720 |
+
cameraStream.getTracks().forEach(track => track.stop());
|
| 721 |
+
cameraStream = null;
|
| 722 |
+
cameraStream.srcObject = null;
|
| 723 |
+
cameraSection.classList.add('hidden');
|
| 724 |
+
logMessage('Camera stopped');
|
| 725 |
+
}
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
function captureImage() {
|
| 729 |
+
if (!cameraStream) return;
|
| 730 |
+
|
| 731 |
+
const canvas = capturedCanvas;
|
| 732 |
+
const ctx = canvas.getContext('2d');
|
| 733 |
+
canvas.width = 640;
|
| 734 |
+
canvas.height = 480;
|
| 735 |
+
|
| 736 |
+
ctx.drawImage(cameraStream, 0, 0, canvas.width, canvas.height);
|
| 737 |
+
currentCapturedImage = canvas.toDataURL('image/jpeg');
|
| 738 |
+
capturedImageSection.classList.remove('hidden');
|
| 739 |
+
|
| 740 |
+
logMessage('Image captured from camera');
|
| 741 |
+
}
|
| 742 |
+
|
| 743 |
+
// Microphone functions
|
| 744 |
+
async function startRecording() {
|
| 745 |
+
try {
|
| 746 |
+
microphoneStream = await navigator.mediaDevices.getUserMedia({
|
| 747 |
+
audio: true
|
| 748 |
+
});
|
| 749 |
+
|
| 750 |
+
// Set up audio context and analyzer
|
| 751 |
+
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
| 752 |
+
const source = audioContext.createMediaStreamSource(microphoneStream);
|
| 753 |
+
audioAnalyzer = audioContext.createAnalyser();
|
| 754 |
+
audioAnalyzer.fftSize = 256;
|
| 755 |
+
source.connect(audioAnalyzer);
|
| 756 |
+
|
| 757 |
+
// Start recording
|
| 758 |
+
mediaRecorder = new MediaRecorder(microphoneStream);
|
| 759 |
+
recordedChunks = [];
|
| 760 |
+
|
| 761 |
+
mediaRecorder.ondataavailable = (event) => {
|
| 762 |
+
if (event.data.size > 0) {
|
| 763 |
+
recordedChunks.push(event.data);
|
| 764 |
+
}
|
| 765 |
+
};
|
| 766 |
+
|
| 767 |
+
mediaRecorder.onstop = () => {
|
| 768 |
+
const blob = new Blob(recordedChunks, { type: 'audio/wav' });
|
| 769 |
+
currentRecordedAudioBlob = blob;
|
| 770 |
+
recordedAudio.src = URL.createObjectURL(blob);
|
| 771 |
+
recordedAudioSection.classList.remove('hidden');
|
| 772 |
+
logMessage('Audio recording completed');
|
| 773 |
+
};
|
| 774 |
+
|
| 775 |
+
mediaRecorder.start();
|
| 776 |
+
microphoneSection.classList.remove('hidden');
|
| 777 |
+
startAudioLevelMonitoring();
|
| 778 |
+
|
| 779 |
+
logMessage('Recording started');
|
| 780 |
+
} catch (error) {
|
| 781 |
+
console.error('Error accessing microphone:', error);
|
| 782 |
+
logMessage('Error: Could not access microphone - ' + error.message);
|
| 783 |
+
alert('Could not access microphone. Please check permissions.');
|
| 784 |
+
}
|
| 785 |
+
}
|
| 786 |
+
|
| 787 |
+
function startAudioLevelMonitoring() {
|
| 788 |
+
const dataArray = new Uint8Array(audioAnalyzer.frequencyBinCount);
|
| 789 |
+
|
| 790 |
+
audioLevelInterval = setInterval(() => {
|
| 791 |
+
audioAnalyzer.getByteFrequencyData(dataArray);
|
| 792 |
+
const average = dataArray.reduce((sum, value) => sum + value) / dataArray.length;
|
| 793 |
+
const percentage = Math.min((average / 255) * 100, 100);
|
| 794 |
+
|
| 795 |
+
audioLevelBar.style.width = percentage + '%';
|
| 796 |
+
audioLevelValue.textContent = Math.round(percentage) + '%';
|
| 797 |
+
|
| 798 |
+
if (percentage > 70) {
|
| 799 |
+
audioLevelBar.classList.add('bg-red-400');
|
| 800 |
+
audioLevelBar.classList.remove('bg-emerald-400');
|
| 801 |
+
} else {
|
| 802 |
+
audioLevelBar.classList.add('bg-emerald-400');
|
| 803 |
+
audioLevelBar.classList.remove('bg-red-400');
|
| 804 |
+
}
|
| 805 |
+
}, 100);
|
| 806 |
+
}
|
| 807 |
+
|
| 808 |
+
function stopRecording() {
|
| 809 |
+
if (mediaRecorder && mediaRecorder.state === 'recording') {
|
| 810 |
+
mediaRecorder.stop();
|
| 811 |
+
}
|
| 812 |
+
|
| 813 |
+
if (microphoneStream) {
|
| 814 |
+
microphoneStream.getTracks().forEach(track => track.stop());
|
| 815 |
+
microphoneStream = null;
|
| 816 |
+
}
|
| 817 |
+
|
| 818 |
+
if (audioContext) {
|
| 819 |
+
audioContext.close();
|
| 820 |
+
audioContext = null;
|
| 821 |
+
}
|
| 822 |
+
|
| 823 |
+
if (audioLevelInterval) {
|
| 824 |
+
clearInterval(audioLevelInterval);
|
| 825 |
+
audioLevelInterval = null;
|
| 826 |
+
}
|
| 827 |
+
|
| 828 |
+
microphoneSection.classList.add('hidden');
|
| 829 |
+
logMessage('Recording stopped');
|
| 830 |
+
}
|
| 831 |
+
|
| 832 |
// Input type selection
|
| 833 |
inputTypeGrid.addEventListener('click', (e) => {
|
| 834 |
const btn = e.target.closest('.input-type-btn');
|
|
|
|
| 866 |
});
|
| 867 |
|
| 868 |
function selectInputSource(source) {
|
| 869 |
+
// Stop any active streams when changing source
|
| 870 |
+
if (source !== 'camera') stopCamera();
|
| 871 |
+
if (source !== 'microphone') stopRecording();
|
| 872 |
+
|
| 873 |
// Update button styles
|
| 874 |
document.querySelectorAll('.input-source-btn').forEach(btn => {
|
| 875 |
btn.classList.remove('bg-indigo-600');
|
|
|
|
| 882 |
textInputArea.classList.toggle('hidden', source !== 'text');
|
| 883 |
filePreview.classList.toggle('hidden', source !== 'upload');
|
| 884 |
mediaControls.classList.toggle('hidden', !['audio', 'video'].includes(currentInputType));
|
| 885 |
+
cameraSection.classList.toggle('hidden', source !== 'camera');
|
| 886 |
+
microphoneSection.classList.toggle('hidden', source !== 'microphone');
|
| 887 |
+
|
| 888 |
+
// Start appropriate media source
|
| 889 |
+
if (source === 'camera') {
|
| 890 |
+
startCamera();
|
| 891 |
+
} else if (source === 'microphone') {
|
| 892 |
+
// Don't auto-start recording, let user click the button
|
| 893 |
+
}
|
| 894 |
|
| 895 |
logMessage(`Selected ${source} input source`);
|
| 896 |
}
|
|
|
|
| 921 |
function updatePreprocessingOptions(inputType) {
|
| 922 |
const options = {
|
| 923 |
image: [
|
| 924 |
+
{ id: 'resize', label: 'Resize to 224x224', checked: true },
|
| 925 |
+
{ id: 'normalize', label: 'Normalize (0-1)', checked: true },
|
| 926 |
+
{ id: '裁剪', label: 'Center Crop', checked: false },
|
| 927 |
+
{ id: 'rotate', label: 'Random Rotate', checked: false },
|
| 928 |
+
{ id: 'flip', label: 'Horizontal Flip', checked: false }
|
| 929 |
],
|
| 930 |
audio: [
|
| 931 |
+
{ id: 'resample', label: 'Resample to 16kHz', checked: true },
|
| 932 |
+
{ id: 'normalize', label: 'Normalize Audio', checked: true },
|
| 933 |
+
{ id: 'denoise', label: 'Noise Reduction', checked: false },
|
| 934 |
{ id: 'trim', label: 'Trim Silence', checked: false },
|
| 935 |
+
{ id: 'augment', label: 'Data Augmentation', checked: false }
|
| 936 |
],
|
| 937 |
text: [
|
| 938 |
+
{ id: 'tokenize', label: 'Tokenize Text', checked: true },
|
| 939 |
+
{ id: 'lowercase', label: 'Convert to Lowercase', checked: false },
|
| 940 |
{ id: 'remove_punct', label: 'Remove Punctuation', checked: false },
|
| 941 |
{ id: 'stop_words', label: 'Remove Stop Words', checked: false },
|
| 942 |
{ id: 'stem', label: 'Stemming', checked: false }
|
|
|
|
| 985 |
analysisSection.scrollIntoView({ behavior: 'smooth' });
|
| 986 |
}
|
| 987 |
|
| 988 |
+
// Handle media file uploads (images/audio)
|
| 989 |
+
function handleMediaFileUpload(file) {
|
| 990 |
+
if (currentInputType === 'image' && file.type.startsWith('image/')) {
|
| 991 |
+
const reader = new FileReader();
|
| 992 |
+
reader.onload = (e) => {
|
| 993 |
+
previewContent.innerHTML = `
|
| 994 |
+
<img src="${e.target.result}" class="w-full max-h-48 object-contain rounded" alt="Preview">
|
| 995 |
+
<p class="text-sm text-slate-400 mt-2">${file.name} (${(file.size / 1024).toFixed(1)} KB)</p>
|
| 996 |
+
`;
|
| 997 |
+
filePreview.classList.remove('hidden');
|
| 998 |
+
};
|
| 999 |
+
reader.readAsDataURL(file);
|
| 1000 |
+
} else if (currentInputType === 'audio' && file.type.startsWith('audio/')) {
|
| 1001 |
+
const url = URL.createObjectURL(file);
|
| 1002 |
+
previewContent.innerHTML = `
|
| 1003 |
+
<audio controls class="w-full">
|
| 1004 |
+
<source src="${url}" type="${file.type}">
|
| 1005 |
+
Your browser does not support the audio element.
|
| 1006 |
+
</audio>
|
| 1007 |
+
<p class="text-sm text-slate-400 mt-2">${file.name} (${(file.size / 1024).toFixed(1)} KB)</p>
|
| 1008 |
+
`;
|
| 1009 |
+
filePreview.classList.remove('hidden');
|
| 1010 |
+
}
|
| 1011 |
+
}
|
| 1012 |
+
|
| 1013 |
+
// Update model file input to handle both ONNX and media files
|
| 1014 |
+
modelFileInput.addEventListener('change', (e) => {
|
| 1015 |
+
const file = e.target.files[0];
|
| 1016 |
+
if (!file) return;
|
| 1017 |
+
|
| 1018 |
+
if (file.name.endsWith('.onnx')) {
|
| 1019 |
+
handleFileUpload(file);
|
| 1020 |
+
} else if (currentInputType && ['image', 'audio'].includes(currentInputType)) {
|
| 1021 |
+
handleMediaFileUpload(file);
|
| 1022 |
+
}
|
| 1023 |
+
});
|
| 1024 |
+
|
| 1025 |
// Populate analysis data based on model type
|
| 1026 |
function populateAnalysisData(file) {
|
| 1027 |
modelInfo = {
|
|
|
|
| 1105 |
// Simulate inference process
|
| 1106 |
logMessage('Starting inference...');
|
| 1107 |
|
| 1108 |
+
// Get current input data
|
| 1109 |
+
const inputData = await getInputData();
|
| 1110 |
+
logMessage('Input data prepared for inference');
|
| 1111 |
+
|
| 1112 |
// Simulate processing steps
|
| 1113 |
const steps = [
|
| 1114 |
'Preprocessing input data...',
|
|
|
|
| 1127 |
inferenceTime.textContent = (endTime - startTime) + 'ms';
|
| 1128 |
|
| 1129 |
// Display results
|
| 1130 |
+
await displayResults();
|
| 1131 |
|
| 1132 |
executeBtn.disabled = false;
|
| 1133 |
executeBtn.innerHTML = '<i data-feather="play" class="mr-2"></i>Execute Inference';
|
|
|
|
| 1135 |
logMessage('Inference completed successfully');
|
| 1136 |
});
|
| 1137 |
|
| 1138 |
+
async function getInputData() {
|
| 1139 |
+
const source = document.querySelector('.input-source-btn.bg-indigo-600')?.dataset.source;
|
| 1140 |
+
|
| 1141 |
+
switch (source) {
|
| 1142 |
+
case 'upload':
|
| 1143 |
+
const file = modelFileInput.files[0];
|
| 1144 |
+
if (file && !file.name.endsWith('.onnx')) {
|
| 1145 |
+
return { type: currentInputType, file: file };
|
| 1146 |
+
}
|
| 1147 |
+
break;
|
| 1148 |
+
case 'camera':
|
| 1149 |
+
if (currentCapturedImage) {
|
| 1150 |
+
return { type: 'image', data: currentCapturedImage };
|
| 1151 |
+
}
|
| 1152 |
+
break;
|
| 1153 |
+
case 'microphone':
|
| 1154 |
+
if (currentRecordedAudioBlob) {
|
| 1155 |
+
return { type: 'audio', blob: currentRecordedAudioBlob };
|
| 1156 |
+
}
|
| 1157 |
+
break;
|
| 1158 |
+
case 'text':
|
| 1159 |
+
const text = document.getElementById('textInput').value;
|
| 1160 |
+
return { type: 'text', data: text };
|
| 1161 |
+
}
|
| 1162 |
+
|
| 1163 |
+
return null;
|
| 1164 |
+
}
|
| 1165 |
+
|
| 1166 |
+
async function displayResults() {
|
| 1167 |
+
const inputData = await getInputData();
|
| 1168 |
const outputType = currentInputType;
|
| 1169 |
let outputHTML = '';
|
| 1170 |
|
| 1171 |
switch (outputType) {
|
| 1172 |
case 'image':
|
| 1173 |
+
let imageSource = '';
|
| 1174 |
+
if (inputData?.type === 'image') {
|
| 1175 |
+
if (inputData.data) {
|
| 1176 |
+
imageSource = inputData.data; // captured image
|
| 1177 |
+
} else if (inputData.file) {
|
| 1178 |
+
imageSource = URL.createObjectURL(inputData.file); // uploaded file
|
| 1179 |
+
}
|
| 1180 |
+
}
|
| 1181 |
+
|
| 1182 |
outputHTML = `
|
| 1183 |
<div class="text-center">
|
| 1184 |
+
${imageSource ? `<img src="${imageSource}" class="w-full max-h-64 object-contain rounded-lg mx-auto mb-4" alt="Processed Image">` : `
|
| 1185 |
+
<div class="w-full h-64 bg-gradient-to-br from-purple-400 to-pink-400 rounded-lg mx-auto mb-4 flex items-center justify-center">
|
| 1186 |
<i data-feather="image" class="text-white w-16 h-16"></i>
|
| 1187 |
+
</div>`}
|
| 1188 |
+
<p class="text-slate-300">Processed Image Output</p>
|
| 1189 |
<p class="text-sm text-slate-400 mt-2">224x224 RGB</p>
|
| 1190 |
</div>
|
| 1191 |
`;
|
|
|
|
| 1193 |
break;
|
| 1194 |
|
| 1195 |
case 'audio':
|
| 1196 |
+
let audioElement = '';
|
| 1197 |
+
if (inputData?.type === 'audio') {
|
| 1198 |
+
const audioUrl = inputData.blob ? URL.createObjectURL(inputData.blob) : URL.createObjectURL(inputData.file);
|
| 1199 |
+
audioElement = `<audio controls class="w-full mb-4" ${autoPlayToggle.checked ? 'autoplay' : ''} ${loopToggle.checked ? 'loop' : ''}>
|
| 1200 |
+
<source src="${audioUrl}" type="audio/wav">
|
| 1201 |
+
Your browser does not support the audio element.
|
| 1202 |
+
</audio>`;
|
| 1203 |
+
}
|
| 1204 |
+
|
| 1205 |
outputHTML = `
|
| 1206 |
<div class="text-center">
|
| 1207 |
+
${audioElement || `
|
| 1208 |
<div class="bg-slate-700 rounded-lg p-6 mb-4">
|
| 1209 |
<div class="flex items-center justify-center mb-4">
|
| 1210 |
<i data-feather="play" class="text-emerald-400 w-8 h-8 mr-2"></i>
|
|
|
|
| 1215 |
`<div class="bg-emerald-400 w-2 rounded-t" style="height: ${Math.random() * 80 + 20}%"></div>`
|
| 1216 |
).join('')}
|
| 1217 |
</div>
|
| 1218 |
+
</div>`}
|
| 1219 |
+
<p class="text-slate-300">Processed Audio Output</p>
|
| 1220 |
+
<p class="text-sm text-slate-400 mt-2">44.1kHz, 16-bit</p>
|
| 1221 |
</div>
|
| 1222 |
`;
|
| 1223 |
downloadOptions.classList.remove('hidden');
|
| 1224 |
break;
|
| 1225 |
|
| 1226 |
case 'text':
|
| 1227 |
+
const inputText = inputData?.data || 'Sample text for processing';
|
| 1228 |
outputHTML = `
|
| 1229 |
<div class="text-left">
|
| 1230 |
<div class="bg-slate-800 rounded-lg p-4 mb-4">
|
| 1231 |
+
<p class="text-slate-300 mb-2"><strong>Input:</strong> ${inputText}</p>
|
| 1232 |
+
<hr class="border-slate-600 my-3">
|
| 1233 |
+
<p class="text-slate-300"><strong>Output:</strong> This is a sample generated text output from the ONNX model. The text processing model has successfully processed the input "${inputText}" and generated meaningful content based on the model capabilities and preprocessing options selected.</p>
|
| 1234 |
</div>
|
| 1235 |
<div class="flex items-center space-x-4">
|
| 1236 |
+
<button class="px-3 py-1 bg-slate-700 hover:bg-slate-600 rounded text-sm" onclick="navigator.clipboard.writeText(this.closest('.text-left').querySelector('p:last-child').textContent)">
|
| 1237 |
<i data-feather="copy" class="w-4 h-4 mr-1 inline"></i>Copy
|
| 1238 |
</button>
|
| 1239 |
<button class="px-3 py-1 bg-slate-700 hover:bg-slate-600 rounded text-sm">
|
|
|
|
| 1253 |
outputDisplay.classList.remove('hidden');
|
| 1254 |
}
|
| 1255 |
|
| 1256 |
+
// Event listeners for media controls
|
| 1257 |
+
captureBtn.addEventListener('click', captureImage);
|
| 1258 |
+
stopCameraBtn.addEventListener('click', stopCamera);
|
| 1259 |
+
startRecordingBtn.addEventListener('click', startRecording);
|
| 1260 |
+
stopRecordingBtn.addEventListener('click', stopRecording);
|
| 1261 |
+
|
| 1262 |
+
// Download functionality
|
| 1263 |
+
downloadOptions.addEventListener('click', (e) => {
|
| 1264 |
+
if (e.target.closest('button')) {
|
| 1265 |
+
const button = e.target.closest('button');
|
| 1266 |
+
if (button.textContent.includes('Download')) {
|
| 1267 |
+
downloadResults();
|
| 1268 |
+
}
|
| 1269 |
+
}
|
| 1270 |
+
});
|
| 1271 |
+
|
| 1272 |
+
function downloadResults() {
|
| 1273 |
+
const outputType = currentInputType;
|
| 1274 |
+
|
| 1275 |
+
switch (outputType) {
|
| 1276 |
+
case 'image':
|
| 1277 |
+
if (currentCapturedImage) {
|
| 1278 |
+
const link = document.createElement('a');
|
| 1279 |
+
link.href = currentCapturedImage;
|
| 1280 |
+
link.download = 'processed_image.jpg';
|
| 1281 |
+
link.click();
|
| 1282 |
+
}
|
| 1283 |
+
break;
|
| 1284 |
+
case 'audio':
|
| 1285 |
+
if (currentRecordedAudioBlob) {
|
| 1286 |
+
const link = document.createElement('a');
|
| 1287 |
+
link.href = URL.createObjectURL(currentRecordedAudioBlob);
|
| 1288 |
+
link.download = 'processed_audio.wav';
|
| 1289 |
+
link.click();
|
| 1290 |
+
}
|
| 1291 |
+
break;
|
| 1292 |
+
case 'text':
|
| 1293 |
+
const textContent = outputContent.querySelector('p:last-child').textContent;
|
| 1294 |
+
const blob = new Blob([textContent], { type: 'text/plain' });
|
| 1295 |
+
const link = document.createElement('a');
|
| 1296 |
+
link.href = URL.createObjectURL(blob);
|
| 1297 |
+
link.download = 'processed_text.txt';
|
| 1298 |
+
link.click();
|
| 1299 |
+
break;
|
| 1300 |
+
}
|
| 1301 |
+
|
| 1302 |
+
logMessage('Results downloaded successfully');
|
| 1303 |
+
}
|
| 1304 |
+
|
| 1305 |
// Log messages to console
|
| 1306 |
function logMessage(message) {
|
| 1307 |
const timestamp = new Date().toLocaleTimeString();
|
|
|
|
| 1351 |
loadModelBtn.addEventListener('click', () => {
|
| 1352 |
modelFileInput.click();
|
| 1353 |
});
|
| 1354 |
+
|
| 1355 |
+
// Clean up media streams when page unloads
|
| 1356 |
+
window.addEventListener('beforeunload', () => {
|
| 1357 |
+
stopCamera();
|
| 1358 |
+
stopRecording();
|
| 1359 |
+
});
|
| 1360 |
</script>
|
| 1361 |
</body>
|
| 1362 |
</html>
|