|
|
|
|
|
|
|
|
import * as state from './state.js'; |
|
|
import * as api from './api.js'; |
|
|
import * as db from './db.js'; |
|
|
|
|
|
|
|
|
import { dom } from './ui/dom.js'; |
|
|
import * as chatUI from './ui/chat.js'; |
|
|
import * as modalUI from './ui/modals.js'; |
|
|
import * as toolUI from './ui/tools.js'; |
|
|
import * as ttsUI from './ui/tts.js'; |
|
|
|
|
|
|
|
|
let currentUserStatus = { |
|
|
isPremium: false, |
|
|
hasBeenChecked: false, |
|
|
fingerprint: null |
|
|
}; |
|
|
|
|
|
const MAX_CHAT_SESSIONS = 150; |
|
|
|
|
|
function checkUserPremiumStatus() { |
|
|
return currentUserStatus.isPremium; |
|
|
} |
|
|
|
|
|
async function handleFileSelection(event) { |
|
|
const file = event.target.files[0]; |
|
|
if (!file) return; |
|
|
|
|
|
chatUI.showFileUploading(file.name); |
|
|
dom.submitButton.disabled = true; |
|
|
|
|
|
try { |
|
|
const uploadedFileData = await api.processAndUploadFile(file); |
|
|
state.setAttachedFile(uploadedFileData); |
|
|
chatUI.showFileReady(file.name, file.type, uploadedFileData.blobUrl); |
|
|
} catch (error) { |
|
|
console.error("خطا در پردازش فایل:", error); |
|
|
chatUI.showFileError(error.message); |
|
|
} finally { |
|
|
event.target.value = ''; |
|
|
toolUI.toggleFilePopupMenu(false); |
|
|
dom.submitButton.disabled = false; |
|
|
dom.messageInput.dispatchEvent(new Event('input')); |
|
|
} |
|
|
} |
|
|
|
|
|
function handleNewChat() { |
|
|
if (state.chatSessions.length >= MAX_CHAT_SESSIONS) { |
|
|
state.chatSessions.pop(); |
|
|
} |
|
|
|
|
|
const newSession = { id: Date.now().toString(), title: 'چت جدید', messages: [], showThoughts: false }; |
|
|
state.chatSessions.unshift(newSession); |
|
|
state.setActiveChatId(newSession.id); |
|
|
chatUI.renderActiveChat(); |
|
|
chatUI.renderHistoryList(); |
|
|
toolUI.updateToolsButton(null); |
|
|
state.setActiveToolPrefix(null); |
|
|
state.setActiveTool(null); |
|
|
state.saveSessions(); |
|
|
ttsUI.clearAllCache(); |
|
|
ttsUI.stopAudio(); |
|
|
} |
|
|
|
|
|
function getFullChatText(session) { |
|
|
if (!session || !session.messages) return ""; |
|
|
return session.messages |
|
|
.map(msg => { |
|
|
const prefix = msg.role === 'user' ? 'کاربر' : 'مدل'; |
|
|
const textContent = msg.parts?.find(p => p.text)?.text || '[محتوای غیر متنی]'; |
|
|
return `${prefix}:\n${textContent}`; |
|
|
}) |
|
|
.join('\n\n---\n\n'); |
|
|
} |
|
|
|
|
|
function handlePremiumFeatureClick(element) { |
|
|
if (checkUserPremiumStatus()) { |
|
|
return true; |
|
|
} |
|
|
|
|
|
const parentContainer = element.closest('.premium-locked-item'); |
|
|
if (parentContainer) { |
|
|
parentContainer.classList.add('animate-premium-lock'); |
|
|
setTimeout(() => { |
|
|
parentContainer.classList.remove('animate-premium-lock'); |
|
|
}, 800); |
|
|
} |
|
|
|
|
|
modalUI.togglePremiumFeatureModal(true); |
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => { |
|
|
await db.initDB(); |
|
|
await db.cleanupOldFiles(); |
|
|
|
|
|
chatUI.initTheme(); |
|
|
ttsUI.initTtsPlayer(); |
|
|
state.loadSessions(); |
|
|
|
|
|
currentUserStatus.fingerprint = await api.getBrowserFingerprint(); |
|
|
|
|
|
if (state.chatSessions.length > MAX_CHAT_SESSIONS) { |
|
|
state.chatSessions.length = MAX_CHAT_SESSIONS; |
|
|
state.saveSessions(); |
|
|
} |
|
|
|
|
|
if (state.chatSessions.length === 0 || !state.getActiveChat()) { |
|
|
handleNewChat(); |
|
|
} else { |
|
|
state.setActiveChatId(state.activeChatId || state.chatSessions[0].id); |
|
|
chatUI.renderActiveChat(); |
|
|
chatUI.renderHistoryList(); |
|
|
} |
|
|
|
|
|
chatUI.setupMobileKeyboardFix(); |
|
|
|
|
|
dom.newChatButton.addEventListener('click', handleNewChat); |
|
|
dom.menuButton.addEventListener('click', () => modalUI.toggleSidebar(true)); |
|
|
dom.sidebarOverlay.addEventListener('click', () => modalUI.toggleSidebar(false)); |
|
|
|
|
|
dom.deleteAllChatsButton.addEventListener('click', () => { |
|
|
modalUI.showConfirmModal('آیا از حذف تمام چتها مطمئن هستید؟', () => { |
|
|
state.setChatSessions([]); |
|
|
state.setActiveChatId(null); |
|
|
state.saveSessions(); |
|
|
handleNewChat(); |
|
|
modalUI.toggleSidebar(false); |
|
|
}); |
|
|
}); |
|
|
|
|
|
dom.settingsButton.addEventListener('click', () => { |
|
|
modalUI.updateSettingsUI(checkUserPremiumStatus()); |
|
|
modalUI.toggleSettingsModal(true); |
|
|
}); |
|
|
dom.settingsModal.addEventListener('click', (e) => { |
|
|
if (e.target === dom.settingsModal) modalUI.toggleSettingsModal(false); |
|
|
}); |
|
|
dom.themeToggle.addEventListener('change', (e) => { |
|
|
const newTheme = e.target.checked ? 'dark' : 'light'; |
|
|
localStorage.setItem('theme', newTheme); |
|
|
chatUI.applyTheme(newTheme); |
|
|
}); |
|
|
|
|
|
dom.toolsButton.addEventListener('click', (e) => { |
|
|
if (dom.toolsButton.classList.contains('tool-selected')) return; |
|
|
e.stopPropagation(); |
|
|
const activeChat = state.getActiveChat(); |
|
|
const toggleSwitch = dom.toolsMenu.querySelector('.toggle-switch'); |
|
|
if (activeChat && toggleSwitch) { |
|
|
toggleSwitch.classList.toggle('active', activeChat.showThoughts); |
|
|
} |
|
|
toolUI.toggleToolsMenu(!dom.toolsMenu.classList.contains('active')); |
|
|
}); |
|
|
dom.attachFileButton.addEventListener('click', (e) => { |
|
|
e.stopPropagation(); |
|
|
toolUI.toggleFilePopupMenu(!dom.filePopupMenu.classList.contains('active')); |
|
|
}); |
|
|
|
|
|
dom.toolsMenu.addEventListener('click', (e) => { |
|
|
const toggleSwitch = e.target.closest('.toggle-switch'); |
|
|
if (toggleSwitch) { |
|
|
e.stopPropagation(); |
|
|
if (!handlePremiumFeatureClick(toggleSwitch)) return; |
|
|
toggleSwitch.classList.toggle('active'); |
|
|
const activeChat = state.getActiveChat(); |
|
|
if (activeChat) { |
|
|
activeChat.showThoughts = toggleSwitch.classList.contains('active'); |
|
|
state.saveSessions(); |
|
|
} |
|
|
return; |
|
|
} |
|
|
|
|
|
const toolItem = e.target.closest('.tool-item'); |
|
|
if (!toolItem) return; |
|
|
if (toolItem.parentElement.classList.contains('premium-locked-item') && !handlePremiumFeatureClick(toolItem)) { |
|
|
toolUI.toggleToolsMenu(false); |
|
|
return; |
|
|
} |
|
|
|
|
|
const tool = toolItem.dataset.tool; |
|
|
const toolName = toolItem.dataset.toolName; |
|
|
|
|
|
toolUI.toggleToolsMenu(false); |
|
|
toolUI.updateToolsButton(toolName); |
|
|
state.setActiveTool(tool); |
|
|
|
|
|
if (tool === 'deep-think') { |
|
|
state.setActiveToolPrefix("شما یک محقق حرفهای هستید. با بررسی عمیق و جامع، به سوال زیر یک پاسخ کامل، ساختاریافته و دقیق بدهید: "); |
|
|
dom.messageInput.placeholder = "موضوع برای تفکر عمیق..."; |
|
|
} else if (tool === 'reasoning') { |
|
|
state.setActiveToolPrefix("شما یک استدلالگر منطقی هستید. با تحلیل گام به گام و ارائه دلایل روشن، به سوال زیر پاسخ دهید: "); |
|
|
dom.messageInput.placeholder = "موضوع برای استدلال..."; |
|
|
} else { |
|
|
state.setActiveToolPrefix(null); |
|
|
state.setActiveTool(null); |
|
|
} |
|
|
dom.messageInput.focus(); |
|
|
}); |
|
|
|
|
|
dom.clearToolSelection.addEventListener('click', (e) => { |
|
|
e.stopPropagation(); |
|
|
state.setActiveToolPrefix(null); |
|
|
state.setActiveTool(null); |
|
|
toolUI.updateToolsButton(null); |
|
|
dom.messageInput.focus(); |
|
|
}); |
|
|
|
|
|
window.addEventListener('click', (e) => { |
|
|
if (dom.toolsMenu.classList.contains('active') && !dom.toolsMenu.contains(e.target) && !dom.toolsButton.contains(e.target)) { |
|
|
toolUI.toggleToolsMenu(false); |
|
|
} |
|
|
if (dom.filePopupMenu.classList.contains('active') && !dom.filePopupMenu.contains(e.target) && !dom.attachFileButton.contains(e.target)) { |
|
|
toolUI.toggleFilePopupMenu(false); |
|
|
} |
|
|
}); |
|
|
|
|
|
dom.selectImageOption.addEventListener('click', (e) => { |
|
|
toolUI.toggleFilePopupMenu(false); |
|
|
if (handlePremiumFeatureClick(e.currentTarget)) dom.imageFileInput.click(); |
|
|
}); |
|
|
|
|
|
dom.selectFileOption.addEventListener('click', (e) => { |
|
|
toolUI.toggleFilePopupMenu(false); |
|
|
if (handlePremiumFeatureClick(e.currentTarget)) dom.generalFileInput.click(); |
|
|
}); |
|
|
|
|
|
dom.premiumModalCloseBtn.addEventListener('click', () => modalUI.togglePremiumFeatureModal(false)); |
|
|
dom.premiumFeatureModal.addEventListener('click', (e) => { |
|
|
if(e.target === dom.premiumFeatureModal) modalUI.togglePremiumFeatureModal(false); |
|
|
}); |
|
|
dom.premiumModalUpgradeBtn.addEventListener('click', () => { |
|
|
parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM', payload: { url: chatUI.PREMIUM_URL } }, '*'); |
|
|
modalUI.togglePremiumFeatureModal(false); |
|
|
}); |
|
|
|
|
|
dom.plusModalCloseBtn.addEventListener('click', () => modalUI.togglePlusRequiredModal(false)); |
|
|
dom.plusRequiredModal.addEventListener('click', (e) => { |
|
|
if(e.target === dom.plusRequiredModal) modalUI.togglePlusRequiredModal(false); |
|
|
}); |
|
|
|
|
|
dom.imageFileInput.addEventListener('change', handleFileSelection); |
|
|
dom.generalFileInput.addEventListener('change', handleFileSelection); |
|
|
|
|
|
dom.removeImageButton.addEventListener('click', () => { |
|
|
state.setAttachedFile(null); |
|
|
chatUI.hideFilePreview(); |
|
|
dom.messageInput.dispatchEvent(new Event('input')); |
|
|
}); |
|
|
|
|
|
dom.htmlPreviewCloseBtn.addEventListener('click', () => modalUI.toggleHtmlPreviewModal(false)); |
|
|
dom.htmlPreviewOverlay.addEventListener('click', () => modalUI.toggleHtmlPreviewModal(false)); |
|
|
|
|
|
dom.messageForm.addEventListener('submit', async (e) => { |
|
|
e.preventDefault(); |
|
|
|
|
|
if (state.isGenerating) { |
|
|
if (state.globalAbortController) state.globalAbortController.abort(); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (state.isMessageLimitReached(checkUserPremiumStatus())) { |
|
|
chatUI.showLimitReachedUpgrade(); |
|
|
return; |
|
|
} |
|
|
|
|
|
const activeChat = state.getActiveChat(); |
|
|
if (!activeChat) return; |
|
|
|
|
|
const userMessageText = dom.messageInput.value.trim(); |
|
|
if (!userMessageText && !state.attachedFile) return; |
|
|
|
|
|
chatUI.setGeneratingState(true); |
|
|
let modelBubbleOuterDiv; |
|
|
|
|
|
try { |
|
|
const isFirstMessageOfChat = activeChat.messages.length === 0; |
|
|
if (isFirstMessageOfChat && dom.chatWindow.querySelector('.welcome-screen')) { |
|
|
dom.chatWindow.querySelector('.welcome-screen').remove(); |
|
|
} |
|
|
|
|
|
const userParts = []; |
|
|
let fileAttachedInThisTurn = null; |
|
|
if (state.attachedFile) { |
|
|
fileAttachedInThisTurn = { ...state.attachedFile }; |
|
|
userParts.push({ |
|
|
id: fileAttachedInThisTurn.id, blobUrl: fileAttachedInThisTurn.blobUrl, |
|
|
mimeType: fileAttachedInThisTurn.mimeType, name: fileAttachedInThisTurn.name, |
|
|
base64Data: fileAttachedInThisTurn.base64Data |
|
|
}); |
|
|
chatUI.hideFilePreview(); |
|
|
state.setAttachedFile(null); |
|
|
} |
|
|
if (userMessageText) userParts.push({ text: userMessageText }); |
|
|
|
|
|
const newUserMessage = { role: 'user', parts: userParts, tool: state.getActiveTool() }; |
|
|
activeChat.messages.push(newUserMessage); |
|
|
|
|
|
state.incrementMessageCount(checkUserPremiumStatus()); |
|
|
|
|
|
await chatUI.addMessageToUI(newUserMessage, activeChat.messages.length - 1, {isLastUser: true, animate: true}); |
|
|
|
|
|
const modelPlaceholderMessage = { role: 'assistant', isTemporary: true, parts: [] }; |
|
|
activeChat.messages.push(modelPlaceholderMessage); |
|
|
modelBubbleOuterDiv = await chatUI.addMessageToUI(modelPlaceholderMessage, activeChat.messages.length - 1, {animate: true}); |
|
|
|
|
|
const historyForApi = activeChat.messages.map((msg, index) => { |
|
|
const isLastMessage = index === activeChat.messages.length - 1; |
|
|
const isUserMessageJustSent = index === activeChat.messages.length - 2; |
|
|
|
|
|
if (isUserMessageJustSent) { |
|
|
return JSON.parse(JSON.stringify(msg)); |
|
|
} |
|
|
if (isLastMessage) { |
|
|
return msg; |
|
|
} |
|
|
|
|
|
const cleanMsg = { role: msg.role, parts: [] }; |
|
|
if (msg.parts) { |
|
|
msg.parts.forEach(part => { |
|
|
if (part.text) { |
|
|
cleanMsg.parts.push({ text: part.text }); |
|
|
} |
|
|
}); |
|
|
} |
|
|
return cleanMsg; |
|
|
}); |
|
|
|
|
|
const toolPrefix = state.getActiveToolPrefix(); |
|
|
const lastUserMsgInHistory = historyForApi.findLast(m => m.role === 'user'); |
|
|
const textPartInHistory = lastUserMsgInHistory.parts.find(p => p.text); |
|
|
if (textPartInHistory) { |
|
|
textPartInHistory.text = (toolPrefix ? toolPrefix : '') + (textPartInHistory.text || ''); |
|
|
} else if (toolPrefix) { |
|
|
lastUserMsgInHistory.parts.push({ text: toolPrefix }); |
|
|
} |
|
|
|
|
|
if (isFirstMessageOfChat && userMessageText) { |
|
|
activeChat.title = userMessageText.substring(0, 30); |
|
|
chatUI.renderHistoryList(); |
|
|
} |
|
|
|
|
|
dom.messageInput.value = ''; |
|
|
dom.messageInput.dispatchEvent(new Event('input')); |
|
|
|
|
|
const activeTool = state.getActiveTool(); |
|
|
if (activeTool === 'deep-think' || activeTool === 'reasoning') { |
|
|
if (activeTool === 'deep-think') toolUI.updateDeepThinkPanel({ topic: userMessageText || 'فایل ضمیمه شده' }, modelBubbleOuterDiv); |
|
|
else if (activeTool === 'reasoning') toolUI.updateReasoningPanel({ topic: userMessageText || 'فایل ضمیمه شده' }, modelBubbleOuterDiv); |
|
|
|
|
|
const progressBar = modelBubbleOuterDiv.querySelector('.bar'); |
|
|
|
|
|
if (progressBar) { |
|
|
requestAnimationFrame(() => { |
|
|
progressBar.style.transition = 'width 30s ease-out'; |
|
|
progressBar.style.width = '100%'; |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const response = await api.getChatStream(historyForApi, state.globalAbortController.signal); |
|
|
await api.readStreamAndDisplay(response, modelBubbleOuterDiv); |
|
|
|
|
|
} catch (error) { |
|
|
if (error.name !== 'AbortError') { |
|
|
console.error("خطا در هنگام تولید پیام:", error); |
|
|
|
|
|
|
|
|
if (modelBubbleOuterDiv) { |
|
|
|
|
|
|
|
|
} |
|
|
} else { |
|
|
if (modelBubbleOuterDiv && !modelBubbleOuterDiv.querySelector('.message-content')?.innerText.includes('متوقف شد')) { |
|
|
const contentArea = modelBubbleOuterDiv.querySelector('.message-content') || modelBubbleOuterDiv; |
|
|
contentArea.innerHTML += '<p class="text-xs text-slate-500 mt-2 text-center p-4">-- عملیات متوقف شد --</p>'; |
|
|
} |
|
|
} |
|
|
} finally { |
|
|
chatUI.resetState(); |
|
|
state.saveSessions(); |
|
|
state.setActiveToolPrefix(null); |
|
|
state.setActiveTool(null); |
|
|
toolUI.updateToolsButton(null); |
|
|
} |
|
|
}); |
|
|
|
|
|
dom.chatWindow.addEventListener('click', async (e) => { |
|
|
const button = e.target.closest('.action-button'); |
|
|
if (!button) return; |
|
|
const action = button.dataset.action; |
|
|
const messageEntry = button.closest('.message-entry'); |
|
|
if (!messageEntry) return; |
|
|
const messageIndex = parseInt(messageEntry.dataset.index, 10); |
|
|
const activeChat = state.getActiveChat(); |
|
|
if (!activeChat || isNaN(messageIndex)) return; |
|
|
const message = activeChat.messages[messageIndex]; |
|
|
if (action === 'copy') { |
|
|
const textToCopy = message.parts?.find(p => p.text)?.text || ''; |
|
|
if (textToCopy) navigator.clipboard.writeText(textToCopy).then(() => chatUI.showCopyFeedback(button)); |
|
|
} else if (action === 'like' || action === 'dislike') { |
|
|
chatUI.handleLikeDislike(button, messageEntry); |
|
|
} else if (action === 'speak') { |
|
|
const audioState = ttsUI.getAudioState(); |
|
|
if (audioState.messageIndex === messageIndex && (audioState.status === 'running' || audioState.status === 'suspended')) { |
|
|
ttsUI.stopAudio(); |
|
|
return; |
|
|
} |
|
|
if (ttsUI.hasCacheForMessage(messageIndex)) { |
|
|
ttsUI.playFromCache(messageIndex, button); |
|
|
return; |
|
|
} |
|
|
const fullText = message.parts?.find(p => p.text)?.text; |
|
|
if (!fullText) return; |
|
|
const codeBlockRegex = /```[\s\S]*?```/g; |
|
|
const textToSpeak = fullText.replace(codeBlockRegex, '').trim(); |
|
|
if (!textToSpeak) { |
|
|
alert("محتوای متنی برای خواندن وجود ندارد (فقط کد شناسایی شد)."); |
|
|
return; |
|
|
} |
|
|
ttsUI.stream(messageIndex, textToSpeak, button); |
|
|
} else if (action === 'regenerate') { |
|
|
if (state.isGenerating) return; |
|
|
chatUI.setGeneratingState(true); |
|
|
const lastModelMessageIndex = state.findLastIndex(activeChat.messages, msg => msg.role === 'assistant'); |
|
|
if (messageIndex === lastModelMessageIndex) { |
|
|
ttsUI.clearCacheForMessage(messageIndex); |
|
|
activeChat.messages.length = messageIndex; |
|
|
messageEntry.remove(); |
|
|
const lastUserMessageIndex = state.findLastIndex(activeChat.messages, msg => msg.role === 'user'); |
|
|
if (lastUserMessageIndex !== -1) { |
|
|
const lastUserMessageElement = dom.chatWindow.querySelector(`.message-entry[data-index="${lastUserMessageIndex}"]`); |
|
|
if (lastUserMessageElement) chatUI.updateMessageActions(lastUserMessageElement, activeChat.messages[lastUserMessageIndex], true, false); |
|
|
} |
|
|
const modelPlaceholderMessage = { role: 'assistant', isTemporary: true, parts: [] }; |
|
|
activeChat.messages.push(modelPlaceholderMessage); |
|
|
const newModelBubble = await chatUI.addMessageToUI(modelPlaceholderMessage, activeChat.messages.length - 1, { animate: true }); |
|
|
try { |
|
|
const lastUserMessage = activeChat.messages.findLast(m => m.role === 'user'); |
|
|
const lastTool = lastUserMessage?.tool; |
|
|
|
|
|
const historyForApi = activeChat.messages.map((msg, index) => { |
|
|
if (index === activeChat.messages.length - 1) return msg; |
|
|
if (index === activeChat.messages.length - 2) return JSON.parse(JSON.stringify(msg)); |
|
|
const cleanMsg = { role: msg.role, parts: [] }; |
|
|
if (msg.parts) msg.parts.forEach(part => { if (part.text) cleanMsg.parts.push({ text: part.text }); }); |
|
|
return cleanMsg; |
|
|
}); |
|
|
|
|
|
if (lastTool === 'deep-think' || lastTool === 'reasoning') { |
|
|
if (lastTool === 'deep-think') toolUI.updateDeepThinkPanel({ topic: lastUserMessage.parts.find(p=>p.text)?.text || 'فایل ضمیمه شده' }, newModelBubble); |
|
|
if (lastTool === 'reasoning') toolUI.updateReasoningPanel({ topic: lastUserMessage.parts.find(p=>p.text)?.text || 'فایل ضمیمه شده' }, newModelBubble); |
|
|
const progressBar = newModelBubble.querySelector('.bar'); |
|
|
if (progressBar) { |
|
|
requestAnimationFrame(() => { |
|
|
progressBar.style.transition = 'width 30s ease-out'; |
|
|
progressBar.style.width = '100%'; |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
const response = await api.getChatStream(historyForApi, state.globalAbortController.signal); |
|
|
await api.readStreamAndDisplay(response, newModelBubble); |
|
|
|
|
|
} catch(error) { |
|
|
if (error.name !== 'AbortError') console.error("Regeneration failed:", error); |
|
|
} finally { |
|
|
chatUI.resetState(); |
|
|
state.saveSessions(); |
|
|
} |
|
|
} else { |
|
|
chatUI.resetState(); |
|
|
} |
|
|
} else if (action === 'edit') { |
|
|
if (state.isGenerating) return; |
|
|
const lastUserMessageIndex = state.findLastIndex(activeChat.messages, msg => msg.role === 'user'); |
|
|
if (messageIndex === lastUserMessageIndex) { |
|
|
const textPart = message.parts.find(p => p.text); |
|
|
const filePart = message.parts.find(p => p.id); |
|
|
if (textPart || filePart) { |
|
|
modalUI.showEditModal(textPart ? textPart.text : '', async (newText) => { |
|
|
chatUI.setGeneratingState(true); |
|
|
try { |
|
|
const allMessagesInDOM = dom.chatWindow.querySelectorAll('.message-entry'); |
|
|
allMessagesInDOM.forEach(msgEl => { |
|
|
const idx = parseInt(msgEl.dataset.index, 10); |
|
|
if (idx >= messageIndex) { |
|
|
ttsUI.clearCacheForMessage(idx); |
|
|
msgEl.remove(); |
|
|
} |
|
|
}); |
|
|
activeChat.messages.length = messageIndex; |
|
|
const newParts = []; |
|
|
if (filePart) { |
|
|
const file = await db.getFile(filePart.id); |
|
|
const blobUrl = URL.createObjectURL(file); |
|
|
const base64 = await api.processAndUploadFile(file).then(d => d.base64Data); |
|
|
newParts.push({ ...filePart, blobUrl, base64Data: base64 }); |
|
|
} |
|
|
if (newText.trim()) newParts.push({ text: newText }); |
|
|
if (newParts.length > 0) { |
|
|
const editedUserMessage = { role: 'user', parts: newParts }; |
|
|
activeChat.messages.push(editedUserMessage); |
|
|
await chatUI.addMessageToUI(editedUserMessage, activeChat.messages.length - 1, { isLastUser: true, animate: true }); |
|
|
} |
|
|
const modelPlaceholderMessage = { role: 'assistant', isTemporary: true, parts: [] }; |
|
|
activeChat.messages.push(modelPlaceholderMessage); |
|
|
const newModelBubble = await chatUI.addMessageToUI(modelPlaceholderMessage, activeChat.messages.length - 1, { animate: true }); |
|
|
|
|
|
const historyForApi = activeChat.messages.map((msg, index) => { |
|
|
if (index >= activeChat.messages.length - 2) return JSON.parse(JSON.stringify(msg)); |
|
|
const cleanMsg = { role: msg.role, parts: [] }; |
|
|
if (msg.parts) msg.parts.forEach(part => { if (part.text) cleanMsg.parts.push({ text: part.text }); }); |
|
|
return cleanMsg; |
|
|
}); |
|
|
|
|
|
const response = await api.getChatStream(historyForApi, state.globalAbortController.signal); |
|
|
await api.readStreamAndDisplay(response, newModelBubble); |
|
|
} catch (error) { |
|
|
if (error.name !== 'AbortError') console.error("Edit failed:", error); |
|
|
} finally { |
|
|
chatUI.resetState(); |
|
|
state.saveSessions(); |
|
|
} |
|
|
}); |
|
|
} |
|
|
} |
|
|
} |
|
|
else if (action === 'show-message-menu') { |
|
|
modalUI.showMessageMenu(e, messageIndex, activeChat, chatUI.escapeHTML); |
|
|
} |
|
|
}); |
|
|
|
|
|
dom.historyItemMenu.addEventListener('click', (e) => { |
|
|
const button = e.target.closest('.menu-item'); |
|
|
if (!button) return; |
|
|
const action = button.dataset.action; |
|
|
const format = button.dataset.format; |
|
|
const sessionId = dom.historyItemMenu.dataset.sessionId; |
|
|
const session = state.chatSessions.find(s => s.id === sessionId); |
|
|
if (!session) return; |
|
|
if (action === 'rename') { |
|
|
modalUI.showRenameModal(session.title, (newTitle) => { |
|
|
session.title = newTitle; |
|
|
state.saveSessions(); |
|
|
chatUI.renderHistoryList(); |
|
|
}); |
|
|
} else if (action === 'delete') { |
|
|
modalUI.showConfirmModal(`آیا از حذف گفتگوی "${session.title}" مطمئن هستید؟`, () => { |
|
|
state.setChatSessions(state.chatSessions.filter(s => s.id !== sessionId)); |
|
|
state.saveSessions(); |
|
|
if (state.activeChatId === sessionId) { |
|
|
if (state.chatSessions.length > 0) { |
|
|
state.setActiveChatId(state.chatSessions[0].id); |
|
|
chatUI.renderActiveChat(); |
|
|
} else { |
|
|
handleNewChat(); |
|
|
} |
|
|
} |
|
|
chatUI.renderHistoryList(); |
|
|
}); |
|
|
} else if (action === 'convert-chat') { |
|
|
const fullText = getFullChatText(session); |
|
|
api.convertTextToFile(fullText, format, button); |
|
|
} |
|
|
dom.historyItemMenu.classList.remove('visible'); |
|
|
}); |
|
|
|
|
|
dom.messageItemMenu.addEventListener('click', (e) => { |
|
|
const menu = dom.messageItemMenu; |
|
|
const closeMenu = () => { |
|
|
menu.classList.remove('visible'); |
|
|
setTimeout(() => { menu.classList.add('hidden'); }, 300); |
|
|
}; |
|
|
if (e.target === dom.messageItemMenuOverlay) { |
|
|
closeMenu(); |
|
|
return; |
|
|
} |
|
|
const button = e.target.closest('.menu-item'); |
|
|
if (!button) return; |
|
|
const action = button.dataset.action; |
|
|
const format = button.dataset.format; |
|
|
const messageIndex = parseInt(menu.dataset.messageIndex, 10); |
|
|
const activeChat = state.getActiveChat(); |
|
|
if (!activeChat || isNaN(messageIndex)) { |
|
|
closeMenu(); |
|
|
return; |
|
|
} |
|
|
const message = activeChat.messages[messageIndex]; |
|
|
if (action === 'delete-message') { |
|
|
modalUI.showConfirmModal('آیا از حذف این پیام مطمئن هستید؟', () => { |
|
|
state.deleteMessage(activeChat.id, messageIndex); |
|
|
ttsUI.clearCacheForMessage(messageIndex); |
|
|
chatUI.renderActiveChat(); |
|
|
}); |
|
|
} else if (action === 'convert-message') { |
|
|
const textContent = message.parts?.find(p => p.text)?.text || ''; |
|
|
if (textContent) api.convertTextToFile(textContent, format, button); |
|
|
else alert('محتوای متنی برای تبدیل وجود ندارد.'); |
|
|
} |
|
|
closeMenu(); |
|
|
}); |
|
|
|
|
|
dom.messageInput.addEventListener('input', () => { |
|
|
chatUI.adjustTextareaHeight(dom.messageInput); |
|
|
if (dom.messageInput.value.trim().length > 0 || state.attachedFile) { |
|
|
dom.submitButton.classList.add('active'); |
|
|
} else { |
|
|
dom.submitButton.classList.remove('active'); |
|
|
} |
|
|
}); |
|
|
|
|
|
dom.editInput.addEventListener('input', () => { |
|
|
chatUI.adjustTextareaHeight(dom.editInput); |
|
|
}); |
|
|
|
|
|
window.addEventListener('message', (event) => { |
|
|
if (event.data && event.data.type === 'USER_DATA_RESPONSE_SIMPLE_CHECK') { |
|
|
const PREMIUM_PAGE_ID = '1149636'; |
|
|
let isUserPremium = false; |
|
|
if (event.data.payload) { |
|
|
try { |
|
|
const userObject = JSON.parse(event.data.payload); |
|
|
if (userObject && userObject.isLogin && userObject.accessible_pages) { |
|
|
if (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) || userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID))) { |
|
|
isUserPremium = true; |
|
|
} |
|
|
} |
|
|
} catch (e) { console.error("Error parsing user data from parent:", e); } |
|
|
} |
|
|
currentUserStatus.isPremium = isUserPremium; |
|
|
currentUserStatus.hasBeenChecked = true; |
|
|
if (!dom.settingsModal.classList.contains('hidden')) { |
|
|
modalUI.updateSettingsUI(isUserPremium); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
parent.postMessage({ type: 'REQUEST_USER_DATA_SIMPLE_CHECK' }, '*'); |
|
|
}); |
|
|
|
|
|
window.handleSuggestionClick = chatUI.handleSuggestionClick; |
|
|
|