Chat / static /js /api.js
Jan2000's picture
Create api.js
ceebe88 unverified
raw
history blame
10.1 kB
// --- 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();
}
}
}
}
}