Update chat.html
Browse files
chat.html
CHANGED
|
@@ -579,7 +579,7 @@
|
|
| 579 |
</div>
|
| 580 |
</div>
|
| 581 |
<button class="settings-btn" onclick="toggleSettingsMenu()">
|
| 582 |
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1
|
| 583 |
تنظیمات کاربری
|
| 584 |
</button>
|
| 585 |
</div>
|
|
@@ -834,7 +834,7 @@
|
|
| 834 |
let html = txt.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
| 835 |
|
| 836 |
// تبدیل متون ضخیم
|
| 837 |
-
html = html.replace(/\*\*(.*?)\*\
|
| 838 |
|
| 839 |
// تبدیل هدرها برای زیبایی و مرتب شدن
|
| 840 |
html = html.replace(/^### (.*?)$/gm, '<h3 style="font-size: 15px; margin: 12px 0 6px 0; color: #5a67d8; font-weight: bold;">$1</h3>');
|
|
@@ -982,8 +982,6 @@
|
|
| 982 |
document.getElementById('icon-sound-on').style.display = isMuted ? 'none' : 'block';
|
| 983 |
document.getElementById('icon-sound-off').style.display = isMuted ? 'block' : 'none';
|
| 984 |
|
| 985 |
-
globalAudioPlayer.muted = isMuted;
|
| 986 |
-
|
| 987 |
if (isMuted) {
|
| 988 |
if (!globalAudioPlayer.paused) {
|
| 989 |
globalAudioPlayer.pause();
|
|
@@ -1456,7 +1454,7 @@
|
|
| 1456 |
|
| 1457 |
function openPremiumModal() {
|
| 1458 |
document.getElementById('modalOverlay').classList.add('open');
|
| 1459 |
-
document.getElementById('premiumModal').classList.
|
| 1460 |
}
|
| 1461 |
// -----------------------
|
| 1462 |
|
|
@@ -1627,6 +1625,7 @@
|
|
| 1627 |
if (audioContext.state === 'suspended') audioContext.resume();
|
| 1628 |
}
|
| 1629 |
|
|
|
|
| 1630 |
function playStreamingAudio(base64Data) {
|
| 1631 |
if (!base64Data || isMuted) return;
|
| 1632 |
const binaryString = atob(base64Data);
|
|
@@ -1648,7 +1647,11 @@
|
|
| 1648 |
};
|
| 1649 |
activeSources.push(source);
|
| 1650 |
|
| 1651 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1652 |
source.start(nextAudioTime);
|
| 1653 |
nextAudioTime += audioBuffer.duration;
|
| 1654 |
}
|
|
@@ -1888,30 +1891,11 @@
|
|
| 1888 |
});
|
| 1889 |
const reader = response.body.getReader();
|
| 1890 |
const decoder = new TextDecoder("utf-8");
|
| 1891 |
-
let streamBuffer = ""; // بافر دریافت پیامها برای اینترنت ملی (جلوگیری از نصفه رسیدن JSON)
|
| 1892 |
-
|
| 1893 |
while (true) {
|
| 1894 |
const { done, value } = await reader.read();
|
| 1895 |
-
if (done)
|
| 1896 |
-
|
| 1897 |
-
|
| 1898 |
-
const data = JSON.parse(streamBuffer);
|
| 1899 |
-
if (data.status === "streaming") {
|
| 1900 |
-
if (data.text) {
|
| 1901 |
-
currentBotText += data.text;
|
| 1902 |
-
currentBotBubble.innerHTML = formatMessageText(currentBotText);
|
| 1903 |
-
}
|
| 1904 |
-
if (data.audio) playStreamingAudio(data.audio);
|
| 1905 |
-
}
|
| 1906 |
-
} catch(e) {}
|
| 1907 |
-
}
|
| 1908 |
-
break;
|
| 1909 |
-
}
|
| 1910 |
-
|
| 1911 |
-
streamBuffer += decoder.decode(value, { stream: true });
|
| 1912 |
-
const lines = streamBuffer.split('\n');
|
| 1913 |
-
streamBuffer = lines.pop(); // ذخیره خط آخر (که معمولا ناقص است) برای دور بعدی
|
| 1914 |
-
|
| 1915 |
for (let line of lines) {
|
| 1916 |
if (!line.trim()) continue;
|
| 1917 |
try {
|
|
@@ -1936,6 +1920,7 @@
|
|
| 1936 |
statusBar.textContent = "";
|
| 1937 |
if (data.status === "error") {
|
| 1938 |
currentBotText = data.message || "خطا در برقراری ارتباط";
|
|
|
|
| 1939 |
}
|
| 1940 |
let finalHtml = `<div class="message-bubble">${formatMessageText(currentBotText)}</div><div class="action-buttons">`;
|
| 1941 |
finalHtml += `<button class="action-btn" title="کپی" onclick="copyMessageText(this)">${COPY_ICON_SVG}</button>`;
|
|
@@ -1955,24 +1940,30 @@
|
|
| 1955 |
}
|
| 1956 |
}
|
| 1957 |
} catch (error) {
|
|
|
|
| 1958 |
if (error.name === 'AbortError') {
|
| 1959 |
statusBar.textContent = "تولید پیام متوقف شد";
|
| 1960 |
|
|
|
|
| 1961 |
if (!globalAudioPlayer.paused) globalAudioPlayer.pause();
|
| 1962 |
activeSources.forEach(source => { try { source.stop(); } catch(e) {} });
|
| 1963 |
activeSources = [];
|
| 1964 |
nextAudioTime = 0;
|
| 1965 |
} else {
|
| 1966 |
statusBar.textContent = "اتصال قطع شد یا زمان پاسخ به پایان رسید";
|
|
|
|
| 1967 |
}
|
| 1968 |
|
|
|
|
| 1969 |
if (currentBotRow) {
|
| 1970 |
let finalHtml = `<div class="message-bubble">${formatMessageText(currentBotText)}</div><div class="action-buttons">`;
|
| 1971 |
finalHtml += `<button class="action-btn" title="کپی" onclick="copyMessageText(this)">${COPY_ICON_SVG}</button>`;
|
|
|
|
| 1972 |
finalHtml += `</div>`;
|
| 1973 |
currentBotRow.innerHTML = finalHtml;
|
| 1974 |
updateDynamicSpacer();
|
| 1975 |
|
|
|
|
| 1976 |
if (currentBotText.trim() !== "") {
|
| 1977 |
saveMessage('bot', currentBotText, null, null, Date.now());
|
| 1978 |
}
|
|
@@ -1987,6 +1978,25 @@
|
|
| 1987 |
}
|
| 1988 |
}
|
| 1989 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1990 |
sendBtn.addEventListener('click', () => {
|
| 1991 |
if (isGenerating) {
|
| 1992 |
if (currentAbortController) {
|
|
|
|
| 579 |
</div>
|
| 580 |
</div>
|
| 581 |
<button class="settings-btn" onclick="toggleSettingsMenu()">
|
| 582 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
| 583 |
تنظیمات کاربری
|
| 584 |
</button>
|
| 585 |
</div>
|
|
|
|
| 834 |
let html = txt.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
| 835 |
|
| 836 |
// تبدیل متون ضخیم
|
| 837 |
+
html = html.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
|
| 838 |
|
| 839 |
// تبدیل هدرها برای زیبایی و مرتب شدن
|
| 840 |
html = html.replace(/^### (.*?)$/gm, '<h3 style="font-size: 15px; margin: 12px 0 6px 0; color: #5a67d8; font-weight: bold;">$1</h3>');
|
|
|
|
| 982 |
document.getElementById('icon-sound-on').style.display = isMuted ? 'none' : 'block';
|
| 983 |
document.getElementById('icon-sound-off').style.display = isMuted ? 'block' : 'none';
|
| 984 |
|
|
|
|
|
|
|
| 985 |
if (isMuted) {
|
| 986 |
if (!globalAudioPlayer.paused) {
|
| 987 |
globalAudioPlayer.pause();
|
|
|
|
| 1454 |
|
| 1455 |
function openPremiumModal() {
|
| 1456 |
document.getElementById('modalOverlay').classList.add('open');
|
| 1457 |
+
document.getElementById('premiumModal').classList.add('open');
|
| 1458 |
}
|
| 1459 |
// -----------------------
|
| 1460 |
|
|
|
|
| 1625 |
if (audioContext.state === 'suspended') audioContext.resume();
|
| 1626 |
}
|
| 1627 |
|
| 1628 |
+
// 🟢 بازگرداندن تابع پخش صوتی زنده به نسخه همگام، بدون تاخیر و کاملاً روان قبلی
|
| 1629 |
function playStreamingAudio(base64Data) {
|
| 1630 |
if (!base64Data || isMuted) return;
|
| 1631 |
const binaryString = atob(base64Data);
|
|
|
|
| 1647 |
};
|
| 1648 |
activeSources.push(source);
|
| 1649 |
|
| 1650 |
+
// استفاده از کمترین بافر (0.1 ثانیه) جهت همگامسازی سریع بدون ایجاد تاخیر
|
| 1651 |
+
if (nextAudioTime < audioContext.currentTime) {
|
| 1652 |
+
nextAudioTime = audioContext.currentTime + 0.1;
|
| 1653 |
+
}
|
| 1654 |
+
|
| 1655 |
source.start(nextAudioTime);
|
| 1656 |
nextAudioTime += audioBuffer.duration;
|
| 1657 |
}
|
|
|
|
| 1891 |
});
|
| 1892 |
const reader = response.body.getReader();
|
| 1893 |
const decoder = new TextDecoder("utf-8");
|
|
|
|
|
|
|
| 1894 |
while (true) {
|
| 1895 |
const { done, value } = await reader.read();
|
| 1896 |
+
if (done) break;
|
| 1897 |
+
const chunk = decoder.decode(value, { stream: true });
|
| 1898 |
+
const lines = chunk.split('\n');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1899 |
for (let line of lines) {
|
| 1900 |
if (!line.trim()) continue;
|
| 1901 |
try {
|
|
|
|
| 1920 |
statusBar.textContent = "";
|
| 1921 |
if (data.status === "error") {
|
| 1922 |
currentBotText = data.message || "خطا در برقراری ارتباط";
|
| 1923 |
+
// بخش ایجاد اجباری چت جدید برای جلوگیری از پرش به گفتگوی جدید حذف شد
|
| 1924 |
}
|
| 1925 |
let finalHtml = `<div class="message-bubble">${formatMessageText(currentBotText)}</div><div class="action-buttons">`;
|
| 1926 |
finalHtml += `<button class="action-btn" title="کپی" onclick="copyMessageText(this)">${COPY_ICON_SVG}</button>`;
|
|
|
|
| 1940 |
}
|
| 1941 |
}
|
| 1942 |
} catch (error) {
|
| 1943 |
+
// نمایش وضعیت مناسب بر اساس لغو یا خطای اینترنت
|
| 1944 |
if (error.name === 'AbortError') {
|
| 1945 |
statusBar.textContent = "تولید پیام متوقف شد";
|
| 1946 |
|
| 1947 |
+
// متوقف کردن پخشکنندههای صوتی فقط و فقط در صورت لغو دستی توسط کاربر
|
| 1948 |
if (!globalAudioPlayer.paused) globalAudioPlayer.pause();
|
| 1949 |
activeSources.forEach(source => { try { source.stop(); } catch(e) {} });
|
| 1950 |
activeSources = [];
|
| 1951 |
nextAudioTime = 0;
|
| 1952 |
} else {
|
| 1953 |
statusBar.textContent = "اتصال قطع شد یا زمان پاسخ به پایان رسید";
|
| 1954 |
+
// در خطاهای شبکه و تایماوتهای معمولی صدا قطع نمیشود تا بافرهای دریافتی تا انتها خوانده شوند
|
| 1955 |
}
|
| 1956 |
|
| 1957 |
+
// بازیابی متن داستان در دیتابیس داخلی تا با رفرش ناپدید نشود
|
| 1958 |
if (currentBotRow) {
|
| 1959 |
let finalHtml = `<div class="message-bubble">${formatMessageText(currentBotText)}</div><div class="action-buttons">`;
|
| 1960 |
finalHtml += `<button class="action-btn" title="کپی" onclick="copyMessageText(this)">${COPY_ICON_SVG}</button>`;
|
| 1961 |
+
// از آنجایی که استریم ناقص قطع شده، دکمه پخش مجدد صوتی برای این پیام غیرفعال میماند اما کپی در دسترس است
|
| 1962 |
finalHtml += `</div>`;
|
| 1963 |
currentBotRow.innerHTML = finalHtml;
|
| 1964 |
updateDynamicSpacer();
|
| 1965 |
|
| 1966 |
+
// ذخیره متن داستان در دیتابیس داخلی تا با رفرش ناپدید نشود
|
| 1967 |
if (currentBotText.trim() !== "") {
|
| 1968 |
saveMessage('bot', currentBotText, null, null, Date.now());
|
| 1969 |
}
|
|
|
|
| 1978 |
}
|
| 1979 |
}
|
| 1980 |
|
| 1981 |
+
// 🟢 بازگرداندن تابع مدیریت کنترل وضعیت صدا به نسخه همگام، ایمن و ۱۰۰٪ بدون تاخیر قبلی
|
| 1982 |
+
function toggleMute() {
|
| 1983 |
+
isMuted = !isMuted;
|
| 1984 |
+
document.getElementById('icon-sound-on').style.display = isMuted ? 'none' : 'block';
|
| 1985 |
+
document.getElementById('icon-sound-off').style.display = isMuted ? 'block' : 'none';
|
| 1986 |
+
|
| 1987 |
+
if (isMuted) {
|
| 1988 |
+
if (!globalAudioPlayer.paused) {
|
| 1989 |
+
globalAudioPlayer.pause();
|
| 1990 |
+
if (currentPlayingButton) currentPlayingButton.innerHTML = PLAY_ICON_SVG;
|
| 1991 |
+
}
|
| 1992 |
+
activeSources.forEach(source => {
|
| 1993 |
+
try { source.stop(); } catch(e) {}
|
| 1994 |
+
});
|
| 1995 |
+
activeSources = [];
|
| 1996 |
+
nextAudioTime = 0;
|
| 1997 |
+
}
|
| 1998 |
+
}
|
| 1999 |
+
|
| 2000 |
sendBtn.addEventListener('click', () => {
|
| 2001 |
if (isGenerating) {
|
| 2002 |
if (currentAbortController) {
|