|
|
|
|
|
|
|
|
|
|
|
|
|
|
import * as state from './state.js'; |
|
|
import * as db from './db.js'; |
|
|
import * as chatUI from './ui/chat.js'; |
|
|
import * as toolUI from './ui/tools.js'; |
|
|
|
|
|
|
|
|
|
|
|
export function interruptibleTimeout(duration, signal) { |
|
|
return new Promise((resolve, reject) => { |
|
|
if (signal.aborted) { |
|
|
return reject(new DOMException('Aborted', 'AbortError')); |
|
|
} |
|
|
const timeoutId = setTimeout(resolve, duration); |
|
|
signal.addEventListener('abort', () => { |
|
|
clearTimeout(timeoutId); |
|
|
reject(new DOMException('Aborted', 'AbortError')); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export async function getBrowserFingerprint() { |
|
|
const components = [navigator.userAgent, navigator.language, screen.width + 'x' + screen.height, new Date().getTimezoneOffset()]; |
|
|
try { |
|
|
const canvas = document.createElement('canvas'); |
|
|
const ctx = canvas.getContext('2d'); |
|
|
ctx.textBaseline = "top"; |
|
|
ctx.font = "14px 'Arial'"; |
|
|
ctx.textBaseline = "alphabetic"; |
|
|
ctx.fillStyle = "#f60"; |
|
|
ctx.fillRect(125, 1, 62, 20); |
|
|
ctx.fillStyle = "#069"; |
|
|
ctx.fillText("a1b2c3d4e5f6g7h8i9j0_!@#$%^&*()", 2, 15); |
|
|
components.push(canvas.toDataURL()); |
|
|
} catch (e) { |
|
|
components.push("canvas-error"); |
|
|
} |
|
|
const fingerprintString = components.join('~~~'); |
|
|
let hash = 0; |
|
|
for (let i = 0; i < fingerprintString.length; i++) { |
|
|
const char = fingerprintString.charCodeAt(i); |
|
|
hash = ((hash << 5) - hash) + char; |
|
|
hash |= 0; |
|
|
} |
|
|
return 'fp_' + Math.abs(hash).toString(16); |
|
|
} |
|
|
|
|
|
export async function convertTextToFile(content, format, buttonElement) { |
|
|
chatUI.showLoadingOnButton(buttonElement, true); |
|
|
try { |
|
|
const convertFormData = new FormData(); |
|
|
convertFormData.append('content', content); |
|
|
convertFormData.append('format', format); |
|
|
const convertResponse = await fetch('https://texttopdf-5irq.onrender.com/', { method: 'POST', body: convertFormData }); |
|
|
if (!convertResponse.ok) throw new Error(`خطا در ارتباط با سرور تبدیل: ${convertResponse.statusText}`); |
|
|
const fileBlob = await convertResponse.blob(); |
|
|
const fileName = `alpha-export-${Date.now()}.${format}`; |
|
|
const uploadFormData = new FormData(); |
|
|
uploadFormData.append('image', fileBlob, fileName); |
|
|
const uploadResponse = await fetch('https://www.aisada.ir/hamed/upload.php', { method: 'POST', body: uploadFormData }); |
|
|
if (!uploadResponse.ok) { |
|
|
const errorText = await uploadResponse.text().catch(() => `HTTP ${uploadResponse.status}`); |
|
|
throw new Error(`آپلود فایل ساخته شده به سرور شما ناموفق بود: ${errorText}`); |
|
|
} |
|
|
const uploadData = await uploadResponse.json(); |
|
|
if (uploadData.success && uploadData.url) { |
|
|
window.parent.postMessage({ type: 'OPEN_EXTERNAL_URL', url: uploadData.url }, '*'); |
|
|
} else { |
|
|
throw new Error(uploadData.message || 'پاسخ سرور آپلود شما پس از ساخت فایل، نامعتبر بود.'); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('خطا در فرآیند تبدیل و آپلود فایل:', error); |
|
|
alert(`متاسفانه در آمادهسازی فایل برای باز کردن خطایی رخ داد: ${error.message}`); |
|
|
} finally { |
|
|
chatUI.showLoadingOnButton(buttonElement, false); |
|
|
} |
|
|
} |
|
|
|
|
|
export async function processAndUploadFile(file) { |
|
|
const readFileAsBase64 = (file) => new Promise((resolve, reject) => { |
|
|
const reader = new FileReader(); |
|
|
reader.onload = () => resolve(reader.result.split(',')[1]); |
|
|
reader.onerror = (error) => reject(error); |
|
|
reader.readAsDataURL(file); |
|
|
}); |
|
|
try { |
|
|
const [fileId, base64Data] = await Promise.all([db.storeFile(file), readFileAsBase64(file)]); |
|
|
const blobUrl = URL.createObjectURL(file); |
|
|
return { id: fileId, blobUrl, base64Data, name: file.name, mimeType: file.type }; |
|
|
} catch (error) { |
|
|
console.error("خطا در پردازش و ذخیره فایل:", error); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
export async function getChatStream(conversationHistory, signal) { |
|
|
const activeChat = state.getActiveChat(); |
|
|
const messagesForApi = conversationHistory |
|
|
.filter(msg => !msg.isTemporary) |
|
|
.map(msg => { |
|
|
const apiMsg = { role: msg.role, parts: [] }; |
|
|
msg.parts.forEach(part => { |
|
|
if (part.text) apiMsg.parts.push({ type: 'text', text: part.text }); |
|
|
if (part.base64Data && part.mimeType) apiMsg.parts.push({ base64Data: part.base64Data, mimeType: part.mimeType }); |
|
|
}); |
|
|
return apiMsg; |
|
|
}).filter(msg => msg.parts.length > 0); |
|
|
|
|
|
const bodyPayload = { |
|
|
messages: messagesForApi, |
|
|
id: 'chat_' + Date.now(), |
|
|
trigger: 'submit-message', |
|
|
show_thoughts: activeChat ? activeChat.showThoughts : false |
|
|
}; |
|
|
|
|
|
const response = await fetch('/chat', { |
|
|
method: 'POST', |
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
signal: signal, |
|
|
body: JSON.stringify(bodyPayload), |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
const errorText = await response.text(); |
|
|
throw new Error(`خطای شبکه: ${response.status} - ${errorText}`); |
|
|
} |
|
|
|
|
|
return response; |
|
|
} |
|
|
|
|
|
export async function readStreamAndDisplay(response, modelBubbleOuterDivElement) { |
|
|
let fullBotResponse = ""; |
|
|
const activeChat = state.getActiveChat(); |
|
|
const activeTool = state.getActiveTool(); |
|
|
|
|
|
try { |
|
|
const reader = response.body.getReader(); |
|
|
const decoder = new TextDecoder(); |
|
|
let buffer = ''; |
|
|
|
|
|
while (true) { |
|
|
const { value, done } = await reader.read(); |
|
|
if (done) break; |
|
|
|
|
|
buffer += decoder.decode(value, { stream: true }); |
|
|
const events = buffer.split('\n\n'); |
|
|
buffer = events.pop(); |
|
|
|
|
|
for (const event of events) { |
|
|
if (event.startsWith('data: ')) { |
|
|
const dataString = event.substring(6); |
|
|
if (dataString.trim() === '[DONE]') break; |
|
|
try { |
|
|
const jsonData = JSON.parse(dataString); |
|
|
if (jsonData.type === 'thought' && jsonData.content) { |
|
|
chatUI.streamThought(jsonData.content, modelBubbleOuterDivElement); |
|
|
} else if (jsonData.choices?.[0]?.delta?.content) { |
|
|
const textChunk = jsonData.choices[0].delta.content; |
|
|
fullBotResponse += textChunk; |
|
|
if (activeTool === 'deep-think' || activeTool === 'reasoning') { |
|
|
chatUI.streamFinalText(fullBotResponse, modelBubbleOuterDivElement); |
|
|
} else { |
|
|
const hasThoughtsUI = modelBubbleOuterDivElement.querySelector('.thinking-panel-wrapper'); |
|
|
if (hasThoughtsUI) chatUI.streamFinalText(fullBotResponse, modelBubbleOuterDivElement); |
|
|
else chatUI.streamFreeWsChunk(modelBubbleOuterDivElement, fullBotResponse); |
|
|
} |
|
|
} else if (jsonData.type === 'error') { |
|
|
throw new Error(jsonData.message || 'خطای ناشناخته از سرور'); |
|
|
} |
|
|
} catch (e) { |
|
|
console.warn('خطا در پردازش قطعه JSON:', dataString, e); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
throw error; |
|
|
} finally { |
|
|
if (fullBotResponse) { |
|
|
const finalMessageObject = { |
|
|
role: 'assistant', |
|
|
parts: [{ text: fullBotResponse }], |
|
|
toolUsed: activeTool, |
|
|
wasGeneratedWithThoughts: activeChat ? activeChat.showThoughts : false |
|
|
}; |
|
|
const tempMsgIndex = activeChat.messages.findIndex(m => m.isTemporary); |
|
|
if (tempMsgIndex > -1) activeChat.messages[tempMsgIndex] = finalMessageObject; |
|
|
else activeChat.messages.push(finalMessageObject); |
|
|
|
|
|
if (activeTool === 'deep-think') { |
|
|
chatUI.finalizeFinalText(modelBubbleOuterDivElement, fullBotResponse); |
|
|
toolUI.hideDeepThinkPanel(modelBubbleOuterDivElement); |
|
|
} else if (activeTool === 'reasoning') { |
|
|
chatUI.finalizeFinalText(modelBubbleOuterDivElement, fullBotResponse); |
|
|
toolUI.hideReasoningPanel(modelBubbleOuterDivElement); |
|
|
} else { |
|
|
const hasThoughtsUI = modelBubbleOuterDivElement.querySelector('.thinking-panel-wrapper'); |
|
|
if (hasThoughtsUI) chatUI.finalizeFinalText(modelBubbleOuterDivElement, fullBotResponse); |
|
|
else chatUI.finalizeFreeWsMessage(modelBubbleOuterDivElement, fullBotResponse); |
|
|
} |
|
|
chatUI.updateMessageActions(modelBubbleOuterDivElement, finalMessageObject, false, true); |
|
|
} else { |
|
|
const tempMsgIndex = activeChat.messages.findIndex(m => m.isTemporary); |
|
|
if (tempMsgIndex > -1) { |
|
|
if(!modelBubbleOuterDivElement.querySelector('.message-content')?.innerText.includes('متوقف شد')) { |
|
|
activeChat.messages.splice(tempMsgIndex, 1); |
|
|
const tempElement = document.getElementById('chat-window').querySelector(`.message-entry[data-index="${tempMsgIndex}"]`); |
|
|
if (tempElement) tempElement.remove(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|