// --- START OF FILE api.js --- // static/js/api.js 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'; // *** START: MODIFIED - بازگرداندن تابع به حالت استاندارد *** // این تابع اکنون در صورت اتمام زمان با موفقیت resolve می‌شود و فقط در صورت لغو شدن reject می‌کند. 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')); }); }); } // *** END: MODIFIED *** 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(); } } } } }