Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -666,12 +666,12 @@ HTML_CONTENT = """<!DOCTYPE html>
|
|
666 |
</div>
|
667 |
|
668 |
<div class="tabs-container">
|
669 |
-
<button class="tab-button active" onclick="switchTab(
|
670 |
-
<button class="tab-button" onclick="switchTab(
|
671 |
-
<button class="tab-button" onclick="switchTab(
|
672 |
-
<button class="tab-button" onclick="switchTab(
|
673 |
-
<button class="tab-button" onclick="switchTab(
|
674 |
-
<button class="tab-button" onclick="switchTab(
|
675 |
</div>
|
676 |
|
677 |
<!-- Voice Chat Tab (Original) -->
|
@@ -859,48 +859,49 @@ HTML_CONTENT = """<!DOCTYPE html>
|
|
859 |
<audio id="audio-output"></audio>
|
860 |
|
861 |
<script>
|
862 |
-
// Tab switching functionality
|
863 |
-
function switchTab(
|
864 |
-
|
865 |
-
event.preventDefault();
|
866 |
|
867 |
// Hide all tabs
|
868 |
-
|
869 |
-
tabs.forEach(tab => {
|
870 |
-
tab.classList.remove('active');
|
871 |
tab.style.display = 'none';
|
|
|
872 |
});
|
873 |
|
874 |
-
|
875 |
-
|
876 |
-
|
|
|
|
|
|
|
877 |
|
878 |
// Show selected tab
|
879 |
const selectedTab = document.getElementById(tabName);
|
880 |
if (selectedTab) {
|
881 |
-
selectedTab.classList.add('active');
|
882 |
selectedTab.style.display = 'flex';
|
|
|
883 |
}
|
884 |
|
885 |
-
//
|
886 |
-
|
887 |
-
event.target.classList.add('active');
|
888 |
-
}
|
889 |
}
|
890 |
|
891 |
-
//
|
892 |
-
let peerConnection;
|
893 |
-
let webrtc_id;
|
894 |
let webSearchEnabled = false;
|
895 |
let selectedLanguage = "";
|
896 |
let systemPrompt = "You are a helpful assistant. Respond in a friendly and professional manner.";
|
897 |
let audioLevel = 0;
|
898 |
-
let animationFrame;
|
899 |
-
let audioContext
|
|
|
|
|
900 |
let dataChannel = null;
|
901 |
let isVoiceActive = false;
|
902 |
|
903 |
-
// Whisper
|
904 |
let micRecorder = null;
|
905 |
let isRecording = false;
|
906 |
let realtimeRecorder = null;
|
@@ -1069,7 +1070,7 @@ HTML_CONTENT = """<!DOCTYPE html>
|
|
1069 |
|
1070 |
async function setupWebRTC() {
|
1071 |
const audioOutput = document.getElementById('audio-output');
|
1072 |
-
const config = __RTC_CONFIGURATION__;
|
1073 |
peerConnection = new RTCPeerConnection(config);
|
1074 |
const timeoutId = setTimeout(() => {
|
1075 |
const toast = document.getElementById('error-toast');
|
@@ -1271,6 +1272,39 @@ HTML_CONTENT = """<!DOCTYPE html>
|
|
1271 |
|
1272 |
// Whisper Tab Functions
|
1273 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1274 |
// Process audio blob (for microphone recording)
|
1275 |
async function processAudioBlob(blob, type) {
|
1276 |
const formData = new FormData();
|
@@ -1499,180 +1533,119 @@ HTML_CONTENT = """<!DOCTYPE html>
|
|
1499 |
processor.connect(audioContext.destination);
|
1500 |
}
|
1501 |
|
1502 |
-
//
|
1503 |
-
window.
|
1504 |
-
console.log('
|
1505 |
-
|
1506 |
-
// Initialize voice chat tab elements
|
1507 |
-
initializeVoiceChatTab();
|
1508 |
-
|
1509 |
-
// Initialize tab functionality
|
1510 |
-
initializeTabs();
|
1511 |
-
|
1512 |
-
// Initialize Whisper tabs
|
1513 |
-
initializeWhisperTabs();
|
1514 |
-
});
|
1515 |
-
|
1516 |
-
function initializeVoiceChatTab() {
|
1517 |
-
// Initialize send button
|
1518 |
-
const sendBtn = document.getElementById('send-button');
|
1519 |
-
if (sendBtn) {
|
1520 |
-
sendBtn.style.display = 'block';
|
1521 |
-
sendBtn.addEventListener('click', sendTextMessage);
|
1522 |
-
console.log('Send button initialized');
|
1523 |
-
}
|
1524 |
-
|
1525 |
-
// Text input handling
|
1526 |
-
const textInputEl = document.getElementById('text-input');
|
1527 |
-
if (textInputEl) {
|
1528 |
-
textInputEl.addEventListener('keypress', (e) => {
|
1529 |
-
if (e.key === 'Enter' && !e.shiftKey) {
|
1530 |
-
e.preventDefault();
|
1531 |
-
sendTextMessage();
|
1532 |
-
}
|
1533 |
-
});
|
1534 |
-
console.log('Text input initialized');
|
1535 |
-
}
|
1536 |
|
1537 |
// Web search toggle
|
1538 |
-
|
1539 |
-
|
1540 |
-
|
1541 |
-
|
1542 |
-
|
1543 |
-
console.log('Web search toggled:', webSearchEnabled);
|
1544 |
-
});
|
1545 |
-
console.log('Search toggle initialized');
|
1546 |
-
}
|
1547 |
|
1548 |
-
// Language
|
1549 |
-
|
1550 |
-
|
1551 |
-
|
1552 |
-
|
1553 |
-
console.log('Language selected:', selectedLanguage);
|
1554 |
-
});
|
1555 |
-
console.log('Language select initialized');
|
1556 |
-
}
|
1557 |
|
1558 |
// System prompt
|
1559 |
-
|
1560 |
-
|
1561 |
-
|
1562 |
-
systemPrompt = this.value || "You are a helpful assistant. Respond in a friendly and professional manner.";
|
1563 |
-
});
|
1564 |
-
console.log('System prompt initialized');
|
1565 |
-
}
|
1566 |
|
1567 |
-
//
|
1568 |
-
|
1569 |
-
|
1570 |
-
|
1571 |
-
|
1572 |
-
|
1573 |
-
|
1574 |
-
} else {
|
1575 |
-
stop();
|
1576 |
-
}
|
1577 |
-
});
|
1578 |
-
console.log('Start button initialized');
|
1579 |
-
}
|
1580 |
-
}
|
1581 |
-
|
1582 |
-
function initializeTabs() {
|
1583 |
-
// Ensure first tab is visible
|
1584 |
-
const firstTab = document.getElementById('voice-chat');
|
1585 |
-
if (firstTab) {
|
1586 |
-
firstTab.style.display = 'flex';
|
1587 |
-
firstTab.classList.add('active');
|
1588 |
-
console.log('First tab made visible');
|
1589 |
-
}
|
1590 |
|
1591 |
-
//
|
1592 |
-
|
1593 |
-
|
1594 |
-
|
1595 |
-
|
1596 |
-
|
1597 |
-
|
1598 |
-
|
1599 |
-
|
1600 |
-
|
1601 |
-
|
1602 |
-
|
1603 |
-
// Initialize file upload areas and other Whisper functionality
|
1604 |
-
console.log('Initializing Whisper tabs...');
|
1605 |
|
1606 |
-
// File upload
|
1607 |
const audioUploadArea = document.getElementById('audio-upload-area');
|
1608 |
if (audioUploadArea) {
|
1609 |
-
audioUploadArea.
|
1610 |
document.getElementById('audio-file-input').click();
|
1611 |
-
}
|
1612 |
}
|
1613 |
|
1614 |
const videoUploadArea = document.getElementById('video-upload-area');
|
1615 |
if (videoUploadArea) {
|
1616 |
-
videoUploadArea.
|
1617 |
document.getElementById('video-file-input').click();
|
1618 |
-
}
|
1619 |
}
|
1620 |
|
1621 |
const pdfUploadArea = document.getElementById('pdf-upload-area');
|
1622 |
if (pdfUploadArea) {
|
1623 |
-
pdfUploadArea.
|
1624 |
document.getElementById('pdf-file-input').click();
|
1625 |
-
}
|
1626 |
}
|
1627 |
|
1628 |
-
//
|
1629 |
-
['audio', 'video', 'pdf'].forEach(type => {
|
1630 |
-
const area = document.getElementById(`${type}-upload-area`);
|
1631 |
-
if (!area) return;
|
1632 |
-
|
1633 |
-
area.addEventListener('dragover', (e) => {
|
1634 |
-
e.preventDefault();
|
1635 |
-
area.classList.add('drag-over');
|
1636 |
-
});
|
1637 |
-
|
1638 |
-
area.addEventListener('dragleave', () => {
|
1639 |
-
area.classList.remove('drag-over');
|
1640 |
-
});
|
1641 |
-
|
1642 |
-
area.addEventListener('drop', (e) => {
|
1643 |
-
e.preventDefault();
|
1644 |
-
area.classList.remove('drag-over');
|
1645 |
-
const file = e.dataTransfer.files[0];
|
1646 |
-
if (file) {
|
1647 |
-
if (type === 'audio') processAudioFile(file);
|
1648 |
-
else if (type === 'video') processVideoFile(file);
|
1649 |
-
else if (type === 'pdf') processPDFFile(file);
|
1650 |
-
}
|
1651 |
-
});
|
1652 |
-
});
|
1653 |
-
|
1654 |
-
// File input change handlers
|
1655 |
const audioFileInput = document.getElementById('audio-file-input');
|
1656 |
if (audioFileInput) {
|
1657 |
-
audioFileInput.
|
1658 |
if (e.target.files[0]) processAudioFile(e.target.files[0]);
|
1659 |
-
}
|
1660 |
}
|
1661 |
|
1662 |
const videoFileInput = document.getElementById('video-file-input');
|
1663 |
if (videoFileInput) {
|
1664 |
-
videoFileInput.
|
1665 |
if (e.target.files[0]) processVideoFile(e.target.files[0]);
|
1666 |
-
}
|
1667 |
}
|
1668 |
|
1669 |
const pdfFileInput = document.getElementById('pdf-file-input');
|
1670 |
if (pdfFileInput) {
|
1671 |
-
pdfFileInput.
|
1672 |
if (e.target.files[0]) processPDFFile(e.target.files[0]);
|
1673 |
-
}
|
1674 |
}
|
1675 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1676 |
</script>
|
1677 |
</body>
|
1678 |
|
|
|
666 |
</div>
|
667 |
|
668 |
<div class="tabs-container">
|
669 |
+
<button class="tab-button active" onclick="switchTab('voice-chat')">음성 채팅</button>
|
670 |
+
<button class="tab-button" onclick="switchTab('mic-whisper')">마이크 전사</button>
|
671 |
+
<button class="tab-button" onclick="switchTab('audio-whisper')">오디오 파일</button>
|
672 |
+
<button class="tab-button" onclick="switchTab('video-whisper')">비디오 파일</button>
|
673 |
+
<button class="tab-button" onclick="switchTab('pdf-whisper')">PDF 번역</button>
|
674 |
+
<button class="tab-button" onclick="switchTab('realtime-whisper')">실시간 통역</button>
|
675 |
</div>
|
676 |
|
677 |
<!-- Voice Chat Tab (Original) -->
|
|
|
859 |
<audio id="audio-output"></audio>
|
860 |
|
861 |
<script>
|
862 |
+
// Tab switching functionality - 맨 앞에 배치
|
863 |
+
function switchTab(tabName) {
|
864 |
+
console.log('Switching to tab:', tabName);
|
|
|
865 |
|
866 |
// Hide all tabs
|
867 |
+
document.querySelectorAll('.tab-content').forEach(tab => {
|
|
|
|
|
868 |
tab.style.display = 'none';
|
869 |
+
tab.classList.remove('active');
|
870 |
});
|
871 |
|
872 |
+
console.log('All initialized!');
|
873 |
+
|
874 |
+
// Remove active from all buttons
|
875 |
+
document.querySelectorAll('.tab-button').forEach(btn => {
|
876 |
+
btn.classList.remove('active');
|
877 |
+
});
|
878 |
|
879 |
// Show selected tab
|
880 |
const selectedTab = document.getElementById(tabName);
|
881 |
if (selectedTab) {
|
|
|
882 |
selectedTab.style.display = 'flex';
|
883 |
+
selectedTab.classList.add('active');
|
884 |
}
|
885 |
|
886 |
+
// Mark button as active
|
887 |
+
event.target.classList.add('active');
|
|
|
|
|
888 |
}
|
889 |
|
890 |
+
// Global variables
|
891 |
+
let peerConnection = null;
|
892 |
+
let webrtc_id = null;
|
893 |
let webSearchEnabled = false;
|
894 |
let selectedLanguage = "";
|
895 |
let systemPrompt = "You are a helpful assistant. Respond in a friendly and professional manner.";
|
896 |
let audioLevel = 0;
|
897 |
+
let animationFrame = null;
|
898 |
+
let audioContext = null;
|
899 |
+
let analyser = null;
|
900 |
+
let audioSource = null;
|
901 |
let dataChannel = null;
|
902 |
let isVoiceActive = false;
|
903 |
|
904 |
+
// Whisper variables
|
905 |
let micRecorder = null;
|
906 |
let isRecording = false;
|
907 |
let realtimeRecorder = null;
|
|
|
1070 |
|
1071 |
async function setupWebRTC() {
|
1072 |
const audioOutput = document.getElementById('audio-output');
|
1073 |
+
const config = typeof __RTC_CONFIGURATION__ !== 'undefined' ? __RTC_CONFIGURATION__ : {iceServers: [{urls: 'stun:stun.l.google.com:19302'}]};
|
1074 |
peerConnection = new RTCPeerConnection(config);
|
1075 |
const timeoutId = setTimeout(() => {
|
1076 |
const toast = document.getElementById('error-toast');
|
|
|
1272 |
|
1273 |
// Whisper Tab Functions
|
1274 |
|
1275 |
+
// Microphone recording
|
1276 |
+
async function toggleMicRecording() {
|
1277 |
+
const btn = document.getElementById('mic-record-btn');
|
1278 |
+
const status = document.getElementById('mic-status');
|
1279 |
+
|
1280 |
+
if (!isRecording) {
|
1281 |
+
try {
|
1282 |
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
1283 |
+
micRecorder = new MediaRecorder(stream);
|
1284 |
+
const chunks = [];
|
1285 |
+
|
1286 |
+
micRecorder.ondataavailable = (e) => chunks.push(e.data);
|
1287 |
+
micRecorder.onstop = async () => {
|
1288 |
+
const blob = new Blob(chunks, { type: 'audio/webm' });
|
1289 |
+
await processAudioBlob(blob, 'mic');
|
1290 |
+
stream.getTracks().forEach(track => track.stop());
|
1291 |
+
};
|
1292 |
+
|
1293 |
+
micRecorder.start();
|
1294 |
+
isRecording = true;
|
1295 |
+
btn.textContent = '녹음 중지';
|
1296 |
+
status.innerHTML = '<div class="recording-indicator"><div class="recording-dot"></div>녹음 중...</div>';
|
1297 |
+
} catch (err) {
|
1298 |
+
showError('마이크 접근 권한이 필요합니다.');
|
1299 |
+
}
|
1300 |
+
} else {
|
1301 |
+
micRecorder.stop();
|
1302 |
+
isRecording = false;
|
1303 |
+
btn.textContent = '녹음 시작';
|
1304 |
+
status.textContent = '처리 중...';
|
1305 |
+
}
|
1306 |
+
}
|
1307 |
+
|
1308 |
// Process audio blob (for microphone recording)
|
1309 |
async function processAudioBlob(blob, type) {
|
1310 |
const formData = new FormData();
|
|
|
1533 |
processor.connect(audioContext.destination);
|
1534 |
}
|
1535 |
|
1536 |
+
// Simple initialization
|
1537 |
+
window.onload = function() {
|
1538 |
+
console.log('Page loaded!');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1539 |
|
1540 |
// Web search toggle
|
1541 |
+
document.getElementById('search-toggle').onclick = function() {
|
1542 |
+
webSearchEnabled = !webSearchEnabled;
|
1543 |
+
this.classList.toggle('active', webSearchEnabled);
|
1544 |
+
console.log('Web search:', webSearchEnabled);
|
1545 |
+
};
|
|
|
|
|
|
|
|
|
1546 |
|
1547 |
+
// Language select
|
1548 |
+
document.getElementById('language-select').onchange = function() {
|
1549 |
+
selectedLanguage = this.value;
|
1550 |
+
console.log('Language:', selectedLanguage);
|
1551 |
+
};
|
|
|
|
|
|
|
|
|
1552 |
|
1553 |
// System prompt
|
1554 |
+
document.getElementById('system-prompt').oninput = function() {
|
1555 |
+
systemPrompt = this.value || "You are a helpful assistant. Respond in a friendly and professional manner.";
|
1556 |
+
};
|
|
|
|
|
|
|
|
|
1557 |
|
1558 |
+
// Text input enter key
|
1559 |
+
document.getElementById('text-input').onkeypress = function(e) {
|
1560 |
+
if (e.key === 'Enter' && !e.shiftKey) {
|
1561 |
+
e.preventDefault();
|
1562 |
+
sendTextMessage();
|
1563 |
+
}
|
1564 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1565 |
|
1566 |
+
// Send button
|
1567 |
+
document.getElementById('send-button').onclick = sendTextMessage;
|
1568 |
+
document.getElementById('send-button').style.display = 'block';
|
1569 |
+
|
1570 |
+
// Start button
|
1571 |
+
document.getElementById('start-button').onclick = function() {
|
1572 |
+
if (!peerConnection || peerConnection.connectionState !== 'connected') {
|
1573 |
+
setupWebRTC();
|
1574 |
+
} else {
|
1575 |
+
stop();
|
1576 |
+
}
|
1577 |
+
};
|
|
|
|
|
1578 |
|
1579 |
+
// File upload areas
|
1580 |
const audioUploadArea = document.getElementById('audio-upload-area');
|
1581 |
if (audioUploadArea) {
|
1582 |
+
audioUploadArea.onclick = function() {
|
1583 |
document.getElementById('audio-file-input').click();
|
1584 |
+
};
|
1585 |
}
|
1586 |
|
1587 |
const videoUploadArea = document.getElementById('video-upload-area');
|
1588 |
if (videoUploadArea) {
|
1589 |
+
videoUploadArea.onclick = function() {
|
1590 |
document.getElementById('video-file-input').click();
|
1591 |
+
};
|
1592 |
}
|
1593 |
|
1594 |
const pdfUploadArea = document.getElementById('pdf-upload-area');
|
1595 |
if (pdfUploadArea) {
|
1596 |
+
pdfUploadArea.onclick = function() {
|
1597 |
document.getElementById('pdf-file-input').click();
|
1598 |
+
};
|
1599 |
}
|
1600 |
|
1601 |
+
// File input handlers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1602 |
const audioFileInput = document.getElementById('audio-file-input');
|
1603 |
if (audioFileInput) {
|
1604 |
+
audioFileInput.onchange = function(e) {
|
1605 |
if (e.target.files[0]) processAudioFile(e.target.files[0]);
|
1606 |
+
};
|
1607 |
}
|
1608 |
|
1609 |
const videoFileInput = document.getElementById('video-file-input');
|
1610 |
if (videoFileInput) {
|
1611 |
+
videoFileInput.onchange = function(e) {
|
1612 |
if (e.target.files[0]) processVideoFile(e.target.files[0]);
|
1613 |
+
};
|
1614 |
}
|
1615 |
|
1616 |
const pdfFileInput = document.getElementById('pdf-file-input');
|
1617 |
if (pdfFileInput) {
|
1618 |
+
pdfFileInput.onchange = function(e) {
|
1619 |
if (e.target.files[0]) processPDFFile(e.target.files[0]);
|
1620 |
+
};
|
1621 |
}
|
1622 |
+
|
1623 |
+
// Drag and drop handlers
|
1624 |
+
['audio', 'video', 'pdf'].forEach(type => {
|
1625 |
+
const area = document.getElementById(`${type}-upload-area`);
|
1626 |
+
if (area) {
|
1627 |
+
area.ondragover = function(e) {
|
1628 |
+
e.preventDefault();
|
1629 |
+
area.classList.add('drag-over');
|
1630 |
+
};
|
1631 |
+
|
1632 |
+
area.ondragleave = function() {
|
1633 |
+
area.classList.remove('drag-over');
|
1634 |
+
};
|
1635 |
+
|
1636 |
+
area.ondrop = function(e) {
|
1637 |
+
e.preventDefault();
|
1638 |
+
area.classList.remove('drag-over');
|
1639 |
+
const file = e.dataTransfer.files[0];
|
1640 |
+
if (file) {
|
1641 |
+
if (type === 'audio') processAudioFile(file);
|
1642 |
+
else if (type === 'video') processVideoFile(file);
|
1643 |
+
else if (type === 'pdf') processPDFFile(file);
|
1644 |
+
}
|
1645 |
+
};
|
1646 |
+
}
|
1647 |
+
});
|
1648 |
+
};
|
1649 |
</script>
|
1650 |
</body>
|
1651 |
|