Jan2000 commited on
Commit
2717123
·
unverified ·
1 Parent(s): ceebe88

Add files via upload

Browse files
Files changed (3) hide show
  1. static/js/db.js +153 -0
  2. static/js/main.js +703 -0
  3. static/js/state.js +204 -0
static/js/db.js ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // static/js/db.js
2
+
3
+ const DB_NAME = 'AlphaChatFileStore';
4
+ const STORE_NAME = 'files';
5
+ // *** قدم 1: نسخه دیتابیس را افزایش می‌دهیم تا فرآیند ارتقا فعال شود ***
6
+ const DB_VERSION = 2;
7
+
8
+ let db;
9
+
10
+ // 1. مقداردهی اولیه و باز کردن دیتابیس
11
+ export function initDB() {
12
+ return new Promise((resolve, reject) => {
13
+ console.log(`در حال تلاش برای باز کردن دیتابیس ${DB_NAME} با نسخه ${DB_VERSION}...`);
14
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
15
+
16
+ request.onerror = (event) => {
17
+ console.error("خطای حیاتی در باز کردن IndexedDB:", event.target.error);
18
+ reject("خطای دیتابیس. لطفا از فعال بودن کوکی‌ها و فضای ذخیره‌سازی مرورگر اطمینان حاصل کنید.");
19
+ };
20
+
21
+ request.onsuccess = (event) => {
22
+ db = event.target.result;
23
+ console.log("IndexedDB با موفقیت باز و آماده استفاده است.");
24
+ resolve(db);
25
+ };
26
+
27
+ // این تابع فقط زمانی اجرا می‌شود که دیتابیس برای اولین بار ساخته شود یا نسخه آن (DB_VERSION) افزایش یابد.
28
+ request.onupgradeneeded = (event) => {
29
+ console.log("رویداد onupgradeneeded فعال شد. در حال ارتقا ساختار دیتابیس...");
30
+ const db = event.target.result;
31
+
32
+ // *** قدم 2: منطق ارتقا را هوشمند می‌کنیم ***
33
+ // ابتدا بررسی می‌کنیم که آیا ساختار قدیمی (و اشتباه) وجود دارد یا نه
34
+ if (db.objectStoreNames.contains(STORE_NAME)) {
35
+ // اگر وجود داشت، آن را حذف می‌کنیم تا راه برای ساختار جدید باز شود.
36
+ db.deleteObjectStore(STORE_NAME);
37
+ console.log("Object store قدیمی 'files' پیدا و برای ارتقا حذف شد.");
38
+ }
39
+
40
+ // حالا ساختار جدید و صحیح را ایجاد می‌کنیم.
41
+ // با حذف keyPath، به IndexedDB اجازه می‌دهیم کلید را به صورت خودکار و خارج از آبجکت مدیریت کند.
42
+ // این کار مشکل اصلی شما را به طور قطعی حل می‌کند.
43
+ const store = db.createObjectStore(STORE_NAME, { autoIncrement: true });
44
+
45
+ // یک ایندکس برای جستجوی فایل‌ها بر اساس زمان ایجاد می‌کنیم.
46
+ store.createIndex('timestamp', 'timestamp', { unique: false });
47
+ console.log("Object store جدید و صحیح 'files' با موفقیت ساخته شد.");
48
+ };
49
+ });
50
+ }
51
+
52
+ // 2. ذخیره کردن یک فایل در دیتابیس
53
+ export function storeFile(file) {
54
+ return new Promise((resolve, reject) => {
55
+ if (!db) {
56
+ // این حالت نباید رخ دهد چون main.js منتظر اتمام initDB می‌ماند.
57
+ reject("دیتابیس هنوز مقداردهی اولیه نشده است.");
58
+ return;
59
+ }
60
+ const transaction = db.transaction([STORE_NAME], 'readwrite');
61
+ const store = transaction.objectStore(STORE_NAME);
62
+ const fileRecord = {
63
+ file: file,
64
+ name: file.name,
65
+ type: file.type,
66
+ timestamp: Date.now()
67
+ };
68
+
69
+ const request = store.add(fileRecord);
70
+
71
+ request.onsuccess = (event) => {
72
+ // event.target.result حاوی کلید (ID) اختصاص داده شده به رکورد جدید است.
73
+ resolve(event.target.result);
74
+ };
75
+
76
+ request.onerror = (event) => {
77
+ console.error("خطا در ذخیره فایل در IndexedDB:", event.target.error);
78
+ reject("ذخیره فایل در دیتابیس محلی ناموفق بود.");
79
+ };
80
+ });
81
+ }
82
+
83
+ // 3. بازیابی یک فایل از دیتابیس با استفاده از ID
84
+ export function getFile(id) {
85
+ return new Promise((resolve, reject) => {
86
+ if (!db) {
87
+ reject("دیتابیس مقداردهی اولیه نشده است.");
88
+ return;
89
+ }
90
+
91
+ // کلیدهای تولید شده توسط autoIncrement همیشه عددی هستند.
92
+ // این تبدیل، اطمینان می‌دهد که حتی اگر ID به صورت رشته ذخیره شده باشد، به درستی بازیابی شود.
93
+ const numericId = typeof id === 'string' ? parseInt(id, 10) : id;
94
+ if (isNaN(numericId)) {
95
+ reject("ID فایل برای بازیابی نامعتبر است.");
96
+ return;
97
+ }
98
+
99
+ const transaction = db.transaction([STORE_NAME], 'readonly');
100
+ const store = transaction.objectStore(STORE_NAME);
101
+ const request = store.get(numericId);
102
+
103
+ request.onsuccess = (event) => {
104
+ if (event.target.result) {
105
+ // خود فایل که یک Blob است را باز می‌گردانیم.
106
+ resolve(event.target.result.file);
107
+ } else {
108
+ reject("فایلی با این ID در دیتابیس محلی یافت نشد.");
109
+ }
110
+ };
111
+
112
+ request.onerror = (event) => {
113
+ console.error("خطا در بازیابی فایل:", event.target.error);
114
+ reject("بازیابی فایل ناموفق بود.");
115
+ };
116
+ });
117
+ }
118
+
119
+ // 4. پاک کردن فایل‌های قدیمی‌تر از یک هفته
120
+ export function cleanupOldFiles() {
121
+ return new Promise((resolve, reject) => {
122
+ if (!db) {
123
+ reject("دیتابیس مقداردهی اولیه نشده است.");
124
+ return;
125
+ }
126
+ const transaction = db.transaction([STORE_NAME], 'readwrite');
127
+ const store = transaction.objectStore(STORE_NAME);
128
+ const index = store.index('timestamp');
129
+
130
+ const oneWeekAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
131
+ const range = IDBKeyRange.upperBound(oneWeekAgo);
132
+
133
+ const request = index.openCursor(range);
134
+ let deletedCount = 0;
135
+
136
+ request.onsuccess = (event) => {
137
+ const cursor = event.target.result;
138
+ if (cursor) {
139
+ cursor.delete();
140
+ deletedCount++;
141
+ cursor.continue();
142
+ } else {
143
+ if(deletedCount > 0) console.log(`${deletedCount} فایل قدیمی و منقضی شده از IndexedDB پاک شد.`);
144
+ resolve();
145
+ }
146
+ };
147
+
148
+ request.onerror = (event) => {
149
+ console.error("خطا در پاکسازی فایل‌های قدیمی:", event.target.error);
150
+ reject("پاکسازی فایل‌های قدیمی ناموفق بود.");
151
+ };
152
+ });
153
+ }
static/js/main.js ADDED
@@ -0,0 +1,703 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // static/js/main.js
2
+
3
+ import * as state from './state.js';
4
+ import * as api from './api.js';
5
+ import * as db from './db.js';
6
+
7
+ // New UI module imports
8
+ import { dom } from './ui/dom.js';
9
+ import * as chatUI from './ui/chat.js';
10
+ import * as modalUI from './ui/modals.js';
11
+ import * as toolUI from './ui/tools.js';
12
+ import * as ttsUI from './ui/tts.js';
13
+
14
+
15
+ let currentUserStatus = {
16
+ isPremium: false,
17
+ hasBeenChecked: false,
18
+ fingerprint: null
19
+ };
20
+
21
+ const MAX_CHAT_SESSIONS = 150;
22
+
23
+ function checkUserPremiumStatus() {
24
+ return currentUserStatus.isPremium;
25
+ }
26
+
27
+ async function handleFileSelection(event) {
28
+ const file = event.target.files[0];
29
+ if (!file) return;
30
+
31
+ chatUI.showFileUploading(file.name);
32
+ dom.submitButton.disabled = true;
33
+
34
+ try {
35
+ const uploadedFileData = await api.processAndUploadFile(file);
36
+ state.setAttachedFile(uploadedFileData);
37
+ chatUI.showFileReady(file.name, file.type, uploadedFileData.blobUrl);
38
+ } catch (error) {
39
+ console.error("خطا در پردازش فایل:", error);
40
+ chatUI.showFileError(error.message);
41
+ } finally {
42
+ event.target.value = '';
43
+ toolUI.toggleFilePopupMenu(false);
44
+ dom.submitButton.disabled = false;
45
+ dom.messageInput.dispatchEvent(new Event('input'));
46
+ }
47
+ }
48
+
49
+ function handleNewChat() {
50
+ if (state.chatSessions.length >= MAX_CHAT_SESSIONS) {
51
+ state.chatSessions.pop();
52
+ }
53
+
54
+ const newSession = { id: Date.now().toString(), title: 'چت جدید', messages: [], showThoughts: false };
55
+ state.chatSessions.unshift(newSession);
56
+ state.setActiveChatId(newSession.id);
57
+ chatUI.renderActiveChat();
58
+ chatUI.renderHistoryList();
59
+ toolUI.updateToolsButton(null);
60
+ state.setActiveToolPrefix(null);
61
+ state.setActiveTool(null);
62
+ state.saveSessions();
63
+ ttsUI.clearAllCache();
64
+ ttsUI.stopAudio();
65
+ }
66
+
67
+ function getFullChatText(session) {
68
+ if (!session || !session.messages) return "";
69
+ return session.messages
70
+ .map(msg => {
71
+ const prefix = msg.role === 'user' ? 'کاربر' : 'مدل';
72
+ const textContent = msg.parts?.find(p => p.text)?.text || '[محتوای غیر متنی]';
73
+ return `${prefix}:\n${textContent}`;
74
+ })
75
+ .join('\n\n---\n\n');
76
+ }
77
+
78
+ function handlePremiumFeatureClick(element) {
79
+ if (checkUserPremiumStatus()) {
80
+ return true;
81
+ }
82
+
83
+ const parentContainer = element.closest('.premium-locked-item');
84
+ if (parentContainer) {
85
+ parentContainer.classList.add('animate-premium-lock');
86
+ setTimeout(() => {
87
+ parentContainer.classList.remove('animate-premium-lock');
88
+ }, 800);
89
+ }
90
+
91
+ modalUI.togglePremiumFeatureModal(true);
92
+
93
+ return false;
94
+ }
95
+
96
+ document.addEventListener('DOMContentLoaded', async () => {
97
+ await db.initDB();
98
+ await db.cleanupOldFiles();
99
+
100
+ chatUI.initTheme();
101
+ ttsUI.initTtsPlayer();
102
+ state.loadSessions();
103
+
104
+ currentUserStatus.fingerprint = await api.getBrowserFingerprint();
105
+
106
+ if (state.chatSessions.length > MAX_CHAT_SESSIONS) {
107
+ state.chatSessions.length = MAX_CHAT_SESSIONS;
108
+ state.saveSessions();
109
+ }
110
+
111
+ if (state.chatSessions.length === 0 || !state.getActiveChat()) {
112
+ handleNewChat();
113
+ } else {
114
+ state.setActiveChatId(state.activeChatId || state.chatSessions[0].id);
115
+ chatUI.renderActiveChat();
116
+ chatUI.renderHistoryList();
117
+ }
118
+
119
+ chatUI.setupMobileKeyboardFix();
120
+
121
+ dom.newChatButton.addEventListener('click', handleNewChat);
122
+ dom.menuButton.addEventListener('click', () => modalUI.toggleSidebar(true));
123
+ dom.sidebarOverlay.addEventListener('click', () => modalUI.toggleSidebar(false));
124
+
125
+ dom.deleteAllChatsButton.addEventListener('click', () => {
126
+ modalUI.showConfirmModal('آیا از حذف تمام چت‌ها مطمئن هستید؟', () => {
127
+ state.setChatSessions([]);
128
+ state.setActiveChatId(null);
129
+ state.saveSessions();
130
+ handleNewChat();
131
+ modalUI.toggleSidebar(false);
132
+ });
133
+ });
134
+
135
+ dom.settingsButton.addEventListener('click', () => {
136
+ modalUI.updateSettingsUI(checkUserPremiumStatus());
137
+ modalUI.toggleSettingsModal(true);
138
+ });
139
+ dom.settingsModal.addEventListener('click', (e) => {
140
+ if (e.target === dom.settingsModal) modalUI.toggleSettingsModal(false);
141
+ });
142
+ dom.themeToggle.addEventListener('change', (e) => {
143
+ const newTheme = e.target.checked ? 'dark' : 'light';
144
+ localStorage.setItem('theme', newTheme);
145
+ chatUI.applyTheme(newTheme);
146
+ });
147
+
148
+ dom.toolsButton.addEventListener('click', (e) => {
149
+ if (dom.toolsButton.classList.contains('tool-selected')) return;
150
+ e.stopPropagation();
151
+ const activeChat = state.getActiveChat();
152
+ const toggleSwitch = dom.toolsMenu.querySelector('.toggle-switch');
153
+ if (activeChat && toggleSwitch) {
154
+ toggleSwitch.classList.toggle('active', activeChat.showThoughts);
155
+ }
156
+ toolUI.toggleToolsMenu(!dom.toolsMenu.classList.contains('active'));
157
+ });
158
+ dom.attachFileButton.addEventListener('click', (e) => {
159
+ e.stopPropagation();
160
+ toolUI.toggleFilePopupMenu(!dom.filePopupMenu.classList.contains('active'));
161
+ });
162
+
163
+ dom.toolsMenu.addEventListener('click', (e) => {
164
+ const toggleSwitch = e.target.closest('.toggle-switch');
165
+ if (toggleSwitch) {
166
+ e.stopPropagation();
167
+ if (!handlePremiumFeatureClick(toggleSwitch)) return;
168
+ toggleSwitch.classList.toggle('active');
169
+ const activeChat = state.getActiveChat();
170
+ if (activeChat) {
171
+ activeChat.showThoughts = toggleSwitch.classList.contains('active');
172
+ state.saveSessions();
173
+ }
174
+ return;
175
+ }
176
+
177
+ const toolItem = e.target.closest('.tool-item');
178
+ if (!toolItem) return;
179
+ if (toolItem.parentElement.classList.contains('premium-locked-item') && !handlePremiumFeatureClick(toolItem)) {
180
+ toolUI.toggleToolsMenu(false);
181
+ return;
182
+ }
183
+
184
+ const tool = toolItem.dataset.tool;
185
+ const toolName = toolItem.dataset.toolName;
186
+
187
+ toolUI.toggleToolsMenu(false);
188
+ toolUI.updateToolsButton(toolName);
189
+ state.setActiveTool(tool);
190
+
191
+ if (tool === 'deep-think') {
192
+ state.setActiveToolPrefix("شما یک محقق حرفه‌ای هستید. با بررسی عمیق و جامع، به سوال زیر یک پاسخ کامل، ساختاریافته و دقیق بدهید: ");
193
+ dom.messageInput.placeholder = "موضوع برای تفکر عمیق...";
194
+ } else if (tool === 'reasoning') {
195
+ state.setActiveToolPrefix("شما یک استدلال‌گر منطقی هستید. با تحلیل گام به گام و ارائه دلایل روشن، به سوال زیر پاسخ دهید: ");
196
+ dom.messageInput.placeholder = "موضوع برای استدلال...";
197
+ } else {
198
+ state.setActiveToolPrefix(null);
199
+ state.setActiveTool(null);
200
+ }
201
+ dom.messageInput.focus();
202
+ });
203
+
204
+ dom.clearToolSelection.addEventListener('click', (e) => {
205
+ e.stopPropagation();
206
+ state.setActiveToolPrefix(null);
207
+ state.setActiveTool(null);
208
+ toolUI.updateToolsButton(null);
209
+ dom.messageInput.focus();
210
+ });
211
+
212
+ window.addEventListener('click', (e) => {
213
+ if (dom.toolsMenu.classList.contains('active') && !dom.toolsMenu.contains(e.target) && !dom.toolsButton.contains(e.target)) {
214
+ toolUI.toggleToolsMenu(false);
215
+ }
216
+ if (dom.filePopupMenu.classList.contains('active') && !dom.filePopupMenu.contains(e.target) && !dom.attachFileButton.contains(e.target)) {
217
+ toolUI.toggleFilePopupMenu(false);
218
+ }
219
+ });
220
+
221
+ dom.selectImageOption.addEventListener('click', (e) => {
222
+ toolUI.toggleFilePopupMenu(false);
223
+ if (handlePremiumFeatureClick(e.currentTarget)) dom.imageFileInput.click();
224
+ });
225
+
226
+ dom.selectFileOption.addEventListener('click', (e) => {
227
+ toolUI.toggleFilePopupMenu(false);
228
+ if (handlePremiumFeatureClick(e.currentTarget)) dom.generalFileInput.click();
229
+ });
230
+
231
+ dom.premiumModalCloseBtn.addEventListener('click', () => modalUI.togglePremiumFeatureModal(false));
232
+ dom.premiumFeatureModal.addEventListener('click', (e) => {
233
+ if(e.target === dom.premiumFeatureModal) modalUI.togglePremiumFeatureModal(false);
234
+ });
235
+ dom.premiumModalUpgradeBtn.addEventListener('click', () => {
236
+ parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM', payload: { url: chatUI.PREMIUM_URL } }, '*');
237
+ modalUI.togglePremiumFeatureModal(false);
238
+ });
239
+
240
+ dom.plusModalCloseBtn.addEventListener('click', () => modalUI.togglePlusRequiredModal(false));
241
+ dom.plusRequiredModal.addEventListener('click', (e) => {
242
+ if(e.target === dom.plusRequiredModal) modalUI.togglePlusRequiredModal(false);
243
+ });
244
+
245
+ dom.imageFileInput.addEventListener('change', handleFileSelection);
246
+ dom.generalFileInput.addEventListener('change', handleFileSelection);
247
+
248
+ dom.removeImageButton.addEventListener('click', () => {
249
+ state.setAttachedFile(null);
250
+ chatUI.hideFilePreview();
251
+ dom.messageInput.dispatchEvent(new Event('input'));
252
+ });
253
+
254
+ dom.htmlPreviewCloseBtn.addEventListener('click', () => modalUI.toggleHtmlPreviewModal(false));
255
+ dom.htmlPreviewOverlay.addEventListener('click', () => modalUI.toggleHtmlPreviewModal(false));
256
+
257
+ dom.messageForm.addEventListener('submit', async (e) => {
258
+ e.preventDefault();
259
+
260
+ if (state.isGenerating) {
261
+ if (state.globalAbortController) state.globalAbortController.abort();
262
+ return;
263
+ }
264
+
265
+ // *** START: ADDED MESSAGE LIMIT CHECK ***
266
+ if (state.isMessageLimitReached(checkUserPremiumStatus())) {
267
+ chatUI.showLimitReachedUpgrade();
268
+ return;
269
+ }
270
+ // *** END: ADDED MESSAGE LIMIT CHECK ***
271
+
272
+ const activeChat = state.getActiveChat();
273
+ if (!activeChat) return;
274
+
275
+ const userMessageText = dom.messageInput.value.trim();
276
+ if (!userMessageText && !state.attachedFile) return;
277
+
278
+ chatUI.setGeneratingState(true);
279
+ let modelBubbleOuterDiv;
280
+
281
+ try {
282
+ const isFirstMessageOfChat = activeChat.messages.length === 0;
283
+ if (isFirstMessageOfChat && dom.chatWindow.querySelector('.welcome-screen')) {
284
+ dom.chatWindow.querySelector('.welcome-screen').remove();
285
+ }
286
+
287
+ const userParts = [];
288
+ let fileAttachedInThisTurn = null;
289
+ if (state.attachedFile) {
290
+ fileAttachedInThisTurn = { ...state.attachedFile };
291
+ userParts.push({
292
+ id: fileAttachedInThisTurn.id, blobUrl: fileAttachedInThisTurn.blobUrl,
293
+ mimeType: fileAttachedInThisTurn.mimeType, name: fileAttachedInThisTurn.name,
294
+ base64Data: fileAttachedInThisTurn.base64Data
295
+ });
296
+ chatUI.hideFilePreview();
297
+ state.setAttachedFile(null);
298
+ }
299
+ if (userMessageText) userParts.push({ text: userMessageText });
300
+
301
+ const newUserMessage = { role: 'user', parts: userParts, tool: state.getActiveTool() };
302
+ activeChat.messages.push(newUserMessage);
303
+
304
+ // *** START: ADDED MESSAGE COUNT INCREMENT ***
305
+ // Increment count immediately after adding the message to the state,
306
+ // before making the API call.
307
+ state.incrementMessageCount(checkUserPremiumStatus());
308
+ // *** END: ADDED MESSAGE COUNT INCREMENT ***
309
+
310
+ await chatUI.addMessageToUI(newUserMessage, activeChat.messages.length - 1, {isLastUser: true, animate: true});
311
+
312
+ const modelPlaceholderMessage = { role: 'assistant', isTemporary: true, parts: [] };
313
+ activeChat.messages.push(modelPlaceholderMessage);
314
+ modelBubbleOuterDiv = await chatUI.addMessageToUI(modelPlaceholderMessage, activeChat.messages.length - 1, {animate: true});
315
+
316
+ // *** START: MODIFIED - منطق پاکسازی تاریخچه اینجا اضافه شده است ***
317
+ const historyForApi = activeChat.messages.map((msg, index) => {
318
+ const isLastMessage = index === activeChat.messages.length - 1; // این پیام placeholder مدل است
319
+ const isUserMessageJustSent = index === activeChat.messages.length - 2; // این پیام کاربر است
320
+
321
+ // برای پیام کاربر که همین الان ارسال شده، تمام اطلاعات را نگه دار
322
+ if (isUserMessageJustSent) {
323
+ return JSON.parse(JSON.stringify(msg));
324
+ }
325
+ // برای پیام placeholder مدل نیز کاری انجام نده
326
+ if (isLastMessage) {
327
+ return msg;
328
+ }
329
+
330
+ // برای تمام پیام‌های قدیمی‌تر در تاریخچه...
331
+ const cleanMsg = { role: msg.role, parts: [] };
332
+ if (msg.parts) {
333
+ msg.parts.forEach(part => {
334
+ // ... فقط و فقط بخش متنی را نگه دار.
335
+ if (part.text) {
336
+ cleanMsg.parts.push({ text: part.text });
337
+ }
338
+ });
339
+ }
340
+ return cleanMsg;
341
+ });
342
+ // *** END: MODIFIED ***
343
+
344
+ const toolPrefix = state.getActiveToolPrefix();
345
+ const lastUserMsgInHistory = historyForApi.findLast(m => m.role === 'user');
346
+ const textPartInHistory = lastUserMsgInHistory.parts.find(p => p.text);
347
+ if (textPartInHistory) {
348
+ textPartInHistory.text = (toolPrefix ? toolPrefix : '') + (textPartInHistory.text || '');
349
+ } else if (toolPrefix) {
350
+ lastUserMsgInHistory.parts.push({ text: toolPrefix });
351
+ }
352
+
353
+ if (isFirstMessageOfChat && userMessageText) {
354
+ activeChat.title = userMessageText.substring(0, 30);
355
+ chatUI.renderHistoryList();
356
+ }
357
+
358
+ dom.messageInput.value = '';
359
+ dom.messageInput.dispatchEvent(new Event('input'));
360
+
361
+ const activeTool = state.getActiveTool();
362
+ if (activeTool === 'deep-think' || activeTool === 'reasoning') {
363
+ if (activeTool === 'deep-think') toolUI.updateDeepThinkPanel({ topic: userMessageText || 'فایل ضمیمه شده' }, modelBubbleOuterDiv);
364
+ else if (activeTool === 'reasoning') toolUI.updateReasoningPanel({ topic: userMessageText || 'فایل ضمیمه شده' }, modelBubbleOuterDiv);
365
+
366
+ const progressBar = modelBubbleOuterDiv.querySelector('.bar');
367
+
368
+ if (progressBar) {
369
+ requestAnimationFrame(() => {
370
+ progressBar.style.transition = 'width 30s ease-out';
371
+ progressBar.style.width = '100%';
372
+ });
373
+ }
374
+
375
+ const response = await api.getChatStream(historyForApi, state.globalAbortController.signal);
376
+ await api.readStreamAndDisplay(response, modelBubbleOuterDiv);
377
+
378
+ } else {
379
+ const MAX_RETRIES = 2;
380
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
381
+ if (state.globalAbortController.signal.aborted) throw new DOMException('Aborted by user', 'AbortError');
382
+
383
+ const attemptController = new AbortController();
384
+ const onGlobalAbort = () => attemptController.abort();
385
+ state.globalAbortController.signal.addEventListener('abort', onGlobalAbort);
386
+
387
+ try {
388
+ const timeoutDuration = fileAttachedInThisTurn ? 8000 : 3000;
389
+ if (attempt > 0) console.warn(`تلاش ${attempt + 1}/${MAX_RETRIES + 1}: سرور پاسخ نداد. تلاش مجدد...`);
390
+
391
+ const timeoutPromise = new Promise((_, reject) => {
392
+ const id = setTimeout(() => {
393
+ clearTimeout(id);
394
+ reject(new Error('ClientTimeout'));
395
+ }, timeoutDuration);
396
+ });
397
+
398
+ const responsePromise = api.getChatStream(historyForApi, attemptController.signal);
399
+
400
+ const response = await Promise.race([responsePromise, timeoutPromise]);
401
+
402
+ await api.readStreamAndDisplay(response, modelBubbleOuterDiv);
403
+
404
+ break;
405
+ } catch (error) {
406
+ attemptController.abort();
407
+ if (error.name === 'AbortError') {
408
+ throw error;
409
+ }
410
+ if (error.message === 'ClientTimeout') {
411
+ if (attempt === MAX_RETRIES) {
412
+ throw new Error('سرور پس از چندین تلاش پاسخگو نبود.');
413
+ }
414
+ } else {
415
+ throw error;
416
+ }
417
+ } finally {
418
+ state.globalAbortController.signal.removeEventListener('abort', onGlobalAbort);
419
+ }
420
+ }
421
+ }
422
+
423
+ } catch (error) {
424
+ if (error.name !== 'AbortError') {
425
+ console.error("خطا در هنگام تولید پیام:", error);
426
+ if (modelBubbleOuterDiv) {
427
+ chatUI.displayError(modelBubbleOuterDiv, error.message || 'یک خطای ناشناخته رخ داد.');
428
+ }
429
+ } else {
430
+ if (modelBubbleOuterDiv && !modelBubbleOuterDiv.querySelector('.message-content')?.innerText.includes('متوقف شد')) {
431
+ const contentArea = modelBubbleOuterDiv.querySelector('.message-content') || modelBubbleOuterDiv;
432
+ contentArea.innerHTML += '<p class="text-xs text-slate-500 mt-2 text-center p-4">-- عملیات متوقف شد --</p>';
433
+ }
434
+ }
435
+ } finally {
436
+ chatUI.resetState();
437
+ state.saveSessions();
438
+ state.setActiveToolPrefix(null);
439
+ state.setActiveTool(null);
440
+ toolUI.updateToolsButton(null);
441
+ }
442
+ });
443
+
444
+ dom.chatWindow.addEventListener('click', async (e) => {
445
+ const button = e.target.closest('.action-button');
446
+ if (!button) return;
447
+ const action = button.dataset.action;
448
+ const messageEntry = button.closest('.message-entry');
449
+ if (!messageEntry) return;
450
+ const messageIndex = parseInt(messageEntry.dataset.index, 10);
451
+ const activeChat = state.getActiveChat();
452
+ if (!activeChat || isNaN(messageIndex)) return;
453
+ const message = activeChat.messages[messageIndex];
454
+ if (action === 'copy') {
455
+ const textToCopy = message.parts?.find(p => p.text)?.text || '';
456
+ if (textToCopy) navigator.clipboard.writeText(textToCopy).then(() => chatUI.showCopyFeedback(button));
457
+ } else if (action === 'like' || action === 'dislike') {
458
+ chatUI.handleLikeDislike(button, messageEntry);
459
+ } else if (action === 'speak') {
460
+ const audioState = ttsUI.getAudioState();
461
+ if (audioState.messageIndex === messageIndex && (audioState.status === 'running' || audioState.status === 'suspended')) {
462
+ ttsUI.stopAudio();
463
+ return;
464
+ }
465
+ if (ttsUI.hasCacheForMessage(messageIndex)) {
466
+ ttsUI.playFromCache(messageIndex, button);
467
+ return;
468
+ }
469
+ const fullText = message.parts?.find(p => p.text)?.text;
470
+ if (!fullText) return;
471
+ const codeBlockRegex = /```[\s\S]*?```/g;
472
+ const textToSpeak = fullText.replace(codeBlockRegex, '').trim();
473
+ if (!textToSpeak) {
474
+ alert("محتوای متنی برای خواندن وجود ندارد (فقط کد شناسایی شد).");
475
+ return;
476
+ }
477
+ ttsUI.stream(messageIndex, textToSpeak, button);
478
+ } else if (action === 'regenerate') {
479
+ if (state.isGenerating) return;
480
+ chatUI.setGeneratingState(true);
481
+ const lastModelMessageIndex = state.findLastIndex(activeChat.messages, msg => msg.role === 'assistant');
482
+ if (messageIndex === lastModelMessageIndex) {
483
+ ttsUI.clearCacheForMessage(messageIndex);
484
+ activeChat.messages.length = messageIndex;
485
+ messageEntry.remove();
486
+ const lastUserMessageIndex = state.findLastIndex(activeChat.messages, msg => msg.role === 'user');
487
+ if (lastUserMessageIndex !== -1) {
488
+ const lastUserMessageElement = dom.chatWindow.querySelector(`.message-entry[data-index="${lastUserMessageIndex}"]`);
489
+ if (lastUserMessageElement) chatUI.updateMessageActions(lastUserMessageElement, activeChat.messages[lastUserMessageIndex], true, false);
490
+ }
491
+ const modelPlaceholderMessage = { role: 'assistant', isTemporary: true, parts: [] };
492
+ activeChat.messages.push(modelPlaceholderMessage);
493
+ const newModelBubble = await chatUI.addMessageToUI(modelPlaceholderMessage, activeChat.messages.length - 1, { animate: true });
494
+ try {
495
+ const lastUserMessage = activeChat.messages.findLast(m => m.role === 'user');
496
+ const lastTool = lastUserMessage?.tool;
497
+
498
+ // *** START: MODIFIED - اعمال منطق پاکسازی به بازسازی ***
499
+ const historyForApi = activeChat.messages.map((msg, index) => {
500
+ if (index === activeChat.messages.length - 1) return msg; // placeholder
501
+ if (index === activeChat.messages.length - 2) return JSON.parse(JSON.stringify(msg)); // user message
502
+ const cleanMsg = { role: msg.role, parts: [] };
503
+ if (msg.parts) msg.parts.forEach(part => { if (part.text) cleanMsg.parts.push({ text: part.text }); });
504
+ return cleanMsg;
505
+ });
506
+ // *** END: MODIFIED ***
507
+
508
+ if (lastTool === 'deep-think' || lastTool === 'reasoning') {
509
+ if (lastTool === 'deep-think') toolUI.updateDeepThinkPanel({ topic: lastUserMessage.parts.find(p=>p.text)?.text || 'فایل ضمیمه شده' }, newModelBubble);
510
+ if (lastTool === 'reasoning') toolUI.updateReasoningPanel({ topic: lastUserMessage.parts.find(p=>p.text)?.text || 'فایل ضمیمه شده' }, newModelBubble);
511
+ const progressBar = newModelBubble.querySelector('.bar');
512
+ if (progressBar) {
513
+ requestAnimationFrame(() => {
514
+ progressBar.style.transition = 'width 30s ease-out';
515
+ progressBar.style.width = '100%';
516
+ });
517
+ }
518
+ const response = await api.getChatStream(historyForApi, state.globalAbortController.signal);
519
+ await api.readStreamAndDisplay(response, newModelBubble);
520
+ } else {
521
+ const response = await api.getChatStream(historyForApi, state.globalAbortController.signal);
522
+ await api.readStreamAndDisplay(response, newModelBubble);
523
+ }
524
+ } catch(error) {
525
+ if (error.name !== 'AbortError') console.error("Regeneration failed:", error);
526
+ } finally {
527
+ chatUI.resetState();
528
+ state.saveSessions();
529
+ }
530
+ } else {
531
+ chatUI.resetState();
532
+ }
533
+ } else if (action === 'edit') {
534
+ if (state.isGenerating) return;
535
+ const lastUserMessageIndex = state.findLastIndex(activeChat.messages, msg => msg.role === 'user');
536
+ if (messageIndex === lastUserMessageIndex) {
537
+ const textPart = message.parts.find(p => p.text);
538
+ const filePart = message.parts.find(p => p.id);
539
+ if (textPart || filePart) {
540
+ modalUI.showEditModal(textPart ? textPart.text : '', async (newText) => {
541
+ chatUI.setGeneratingState(true);
542
+ try {
543
+ const allMessagesInDOM = dom.chatWindow.querySelectorAll('.message-entry');
544
+ allMessagesInDOM.forEach(msgEl => {
545
+ const idx = parseInt(msgEl.dataset.index, 10);
546
+ if (idx >= messageIndex) {
547
+ ttsUI.clearCacheForMessage(idx);
548
+ msgEl.remove();
549
+ }
550
+ });
551
+ activeChat.messages.length = messageIndex;
552
+ const newParts = [];
553
+ if (filePart) {
554
+ const file = await db.getFile(filePart.id);
555
+ const blobUrl = URL.createObjectURL(file);
556
+ const base64 = await api.processAndUploadFile(file).then(d => d.base64Data);
557
+ newParts.push({ ...filePart, blobUrl, base64Data: base64 });
558
+ }
559
+ if (newText.trim()) newParts.push({ text: newText });
560
+ if (newParts.length > 0) {
561
+ const editedUserMessage = { role: 'user', parts: newParts };
562
+ activeChat.messages.push(editedUserMessage);
563
+ await chatUI.addMessageToUI(editedUserMessage, activeChat.messages.length - 1, { isLastUser: true, animate: true });
564
+ }
565
+ const modelPlaceholderMessage = { role: 'assistant', isTemporary: true, parts: [] };
566
+ activeChat.messages.push(modelPlaceholderMessage);
567
+ const newModelBubble = await chatUI.addMessageToUI(modelPlaceholderMessage, activeChat.messages.length - 1, { animate: true });
568
+
569
+ // *** START: MODIFIED - اعمال منطق پاکسازی به ویرایش ***
570
+ const historyForApi = activeChat.messages.map((msg, index) => {
571
+ if (index >= activeChat.messages.length - 2) return JSON.parse(JSON.stringify(msg));
572
+ const cleanMsg = { role: msg.role, parts: [] };
573
+ if (msg.parts) msg.parts.forEach(part => { if (part.text) cleanMsg.parts.push({ text: part.text }); });
574
+ return cleanMsg;
575
+ });
576
+ // *** END: MODIFIED ***
577
+
578
+ const response = await api.getChatStream(historyForApi, state.globalAbortController.signal);
579
+ await api.readStreamAndDisplay(response, newModelBubble);
580
+ } catch (error) {
581
+ if (error.name !== 'AbortError') console.error("Edit failed:", error);
582
+ } finally {
583
+ chatUI.resetState();
584
+ state.saveSessions();
585
+ }
586
+ });
587
+ }
588
+ }
589
+ }
590
+ else if (action === 'show-message-menu') {
591
+ modalUI.showMessageMenu(e, messageIndex, activeChat, chatUI.escapeHTML);
592
+ }
593
+ });
594
+
595
+ dom.historyItemMenu.addEventListener('click', (e) => {
596
+ const button = e.target.closest('.menu-item');
597
+ if (!button) return;
598
+ const action = button.dataset.action;
599
+ const format = button.dataset.format;
600
+ const sessionId = dom.historyItemMenu.dataset.sessionId;
601
+ const session = state.chatSessions.find(s => s.id === sessionId);
602
+ if (!session) return;
603
+ if (action === 'rename') {
604
+ modalUI.showRenameModal(session.title, (newTitle) => {
605
+ session.title = newTitle;
606
+ state.saveSessions();
607
+ chatUI.renderHistoryList();
608
+ });
609
+ } else if (action === 'delete') {
610
+ modalUI.showConfirmModal(`آیا از حذف گفتگوی "${session.title}" مطمئن هستید؟`, () => {
611
+ state.setChatSessions(state.chatSessions.filter(s => s.id !== sessionId));
612
+ state.saveSessions();
613
+ if (state.activeChatId === sessionId) {
614
+ if (state.chatSessions.length > 0) {
615
+ state.setActiveChatId(state.chatSessions[0].id);
616
+ chatUI.renderActiveChat();
617
+ } else {
618
+ handleNewChat();
619
+ }
620
+ }
621
+ chatUI.renderHistoryList();
622
+ });
623
+ } else if (action === 'convert-chat') {
624
+ const fullText = getFullChatText(session);
625
+ api.convertTextToFile(fullText, format, button);
626
+ }
627
+ dom.historyItemMenu.classList.remove('visible');
628
+ });
629
+
630
+ dom.messageItemMenu.addEventListener('click', (e) => {
631
+ const menu = dom.messageItemMenu;
632
+ const closeMenu = () => {
633
+ menu.classList.remove('visible');
634
+ setTimeout(() => { menu.classList.add('hidden'); }, 300);
635
+ };
636
+ if (e.target === dom.messageItemMenuOverlay) {
637
+ closeMenu();
638
+ return;
639
+ }
640
+ const button = e.target.closest('.menu-item');
641
+ if (!button) return;
642
+ const action = button.dataset.action;
643
+ const format = button.dataset.format;
644
+ const messageIndex = parseInt(menu.dataset.messageIndex, 10);
645
+ const activeChat = state.getActiveChat();
646
+ if (!activeChat || isNaN(messageIndex)) {
647
+ closeMenu();
648
+ return;
649
+ }
650
+ const message = activeChat.messages[messageIndex];
651
+ if (action === 'delete-message') {
652
+ modalUI.showConfirmModal('آیا از حذف این پیام مطمئن هستید؟', () => {
653
+ state.deleteMessage(activeChat.id, messageIndex);
654
+ ttsUI.clearCacheForMessage(messageIndex);
655
+ chatUI.renderActiveChat();
656
+ });
657
+ } else if (action === 'convert-message') {
658
+ const textContent = message.parts?.find(p => p.text)?.text || '';
659
+ if (textContent) api.convertTextToFile(textContent, format, button);
660
+ else alert('محتوای متنی برای تبدیل وجود ندارد.');
661
+ }
662
+ closeMenu();
663
+ });
664
+
665
+ dom.messageInput.addEventListener('input', () => {
666
+ chatUI.adjustTextareaHeight(dom.messageInput);
667
+ if (dom.messageInput.value.trim().length > 0 || state.attachedFile) {
668
+ dom.submitButton.classList.add('active');
669
+ } else {
670
+ dom.submitButton.classList.remove('active');
671
+ }
672
+ });
673
+
674
+ dom.editInput.addEventListener('input', () => {
675
+ chatUI.adjustTextareaHeight(dom.editInput);
676
+ });
677
+
678
+ window.addEventListener('message', (event) => {
679
+ if (event.data && event.data.type === 'USER_DATA_RESPONSE_SIMPLE_CHECK') {
680
+ const PREMIUM_PAGE_ID = '1149636';
681
+ let isUserPremium = false;
682
+ if (event.data.payload) {
683
+ try {
684
+ const userObject = JSON.parse(event.data.payload);
685
+ if (userObject && userObject.isLogin && userObject.accessible_pages) {
686
+ if (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) || userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID))) {
687
+ isUserPremium = true;
688
+ }
689
+ }
690
+ } catch (e) { console.error("Error parsing user data from parent:", e); }
691
+ }
692
+ currentUserStatus.isPremium = isUserPremium;
693
+ currentUserStatus.hasBeenChecked = true;
694
+ if (!dom.settingsModal.classList.contains('hidden')) {
695
+ modalUI.updateSettingsUI(isUserPremium);
696
+ }
697
+ }
698
+ });
699
+
700
+ parent.postMessage({ type: 'REQUEST_USER_DATA_SIMPLE_CHECK' }, '*');
701
+ });
702
+
703
+ window.handleSuggestionClick = chatUI.handleSuggestionClick;
static/js/state.js ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // static/js/state.js
2
+
3
+ export let chatSessions = [];
4
+ export let activeChatId = null;
5
+ export let attachedFile = null;
6
+ export let isGenerating = false;
7
+ export let globalAbortController = null;
8
+ export let currentUploadXHR = null; // این متغیر دیگر برای آپلود استفاده نمی‌شود اما برای لغو پردازش فایل نگه داشته می‌شود
9
+ export let activeToolPrefix = null;
10
+ export let activeTool = null;
11
+
12
+ // *** START: ADDED FOR MESSAGE LIMIT ***
13
+ const DAILY_MESSAGE_LIMIT = 10;
14
+ const MESSAGE_LIMIT_KEY = 'alphaChatDailyLimit';
15
+
16
+ /**
17
+ * Gets the current date as a formatted string (YYYY-MM-DD).
18
+ * @returns {string} The formatted date string.
19
+ */
20
+ function getTodayDateString() {
21
+ const today = new Date();
22
+ const year = today.getFullYear();
23
+ const month = String(today.getMonth() + 1).padStart(2, '0');
24
+ const day = String(today.getDate()).padStart(2, '0');
25
+ return `${year}-${month}-${day}`;
26
+ }
27
+
28
+ /**
29
+ * Retrieves usage data from localStorage.
30
+ * @returns {{count: number, date: string}} The usage data object.
31
+ */
32
+ function getUsageData() {
33
+ try {
34
+ const saved = localStorage.getItem(MESSAGE_LIMIT_KEY);
35
+ return saved ? JSON.parse(saved) : { count: 0, date: getTodayDateString() };
36
+ } catch (e) {
37
+ console.error("Failed to parse usage data from localStorage:", e);
38
+ return { count: 0, date: getTodayDateString() };
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Saves usage data to localStorage.
44
+ * @param {{count: number, date: string}} data The usage data object to save.
45
+ */
46
+ function saveUsageData(data) {
47
+ try {
48
+ localStorage.setItem(MESSAGE_LIMIT_KEY, JSON.stringify(data));
49
+ } catch (e) {
50
+ console.error("Failed to save usage data to localStorage:", e);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Checks if the daily message limit has been reached for a free user.
56
+ * @param {boolean} isPremium - Whether the current user is premium.
57
+ * @returns {boolean} - True if the limit is reached, otherwise false.
58
+ */
59
+ export function isMessageLimitReached(isPremium) {
60
+ if (isPremium) {
61
+ return false; // Premium users have no limit.
62
+ }
63
+
64
+ let usageData = getUsageData();
65
+ const today = getTodayDateString();
66
+
67
+ if (usageData.date !== today) {
68
+ // It's a new day, reset the counter.
69
+ usageData = { count: 0, date: today };
70
+ saveUsageData(usageData);
71
+ }
72
+
73
+ return usageData.count >= DAILY_MESSAGE_LIMIT;
74
+ }
75
+
76
+ /**
77
+ * Increments the message count for a free user for the current day.
78
+ * @param {boolean} isPremium - Whether the current user is premium.
79
+ */
80
+ export function incrementMessageCount(isPremium) {
81
+ if (isPremium) {
82
+ return; // Don't track for premium users.
83
+ }
84
+
85
+ let usageData = getUsageData();
86
+ const today = getTodayDateString();
87
+
88
+ if (usageData.date !== today) {
89
+ // If the day changed between the check and the increment, reset first.
90
+ usageData = { count: 1, date: today };
91
+ } else {
92
+ usageData.count += 1;
93
+ }
94
+
95
+ saveUsageData(usageData);
96
+ }
97
+ // *** END: ADDED FOR MESSAGE LIMIT ***
98
+
99
+
100
+ export function setActiveTool(toolName) {
101
+ activeTool = toolName;
102
+ }
103
+
104
+ export function getActiveTool() {
105
+ return activeTool;
106
+ }
107
+
108
+ export function setActiveToolPrefix(prefix) {
109
+ activeToolPrefix = prefix;
110
+ }
111
+
112
+ export function getActiveToolPrefix() {
113
+ return activeToolPrefix;
114
+ }
115
+
116
+ export function setChatSessions(newSessions) {
117
+ chatSessions = newSessions;
118
+ }
119
+
120
+ export function setActiveChatId(id) {
121
+ activeChatId = id;
122
+ }
123
+
124
+ export function setAttachedFile(file) {
125
+ attachedFile = file;
126
+ }
127
+
128
+ export function setGenerating(status) {
129
+ isGenerating = status;
130
+ }
131
+
132
+ export function setGlobalAbortController(controller) {
133
+ globalAbortController = controller;
134
+ }
135
+
136
+ export function setCurrentUploadXHR(xhr) {
137
+ currentUploadXHR = xhr;
138
+ }
139
+
140
+ export function getActiveChat() {
141
+ return chatSessions.find(s => s.id === activeChatId);
142
+ }
143
+
144
+ export function saveSessions() {
145
+ try {
146
+ const sessionsToSave = JSON.parse(JSON.stringify(chatSessions));
147
+
148
+ sessionsToSave.forEach(session => {
149
+ session.messages.forEach(message => {
150
+ if (message.parts) {
151
+ message.parts.forEach(part => {
152
+ if (part.base64Data) {
153
+ delete part.base64Data;
154
+ }
155
+ if (part.blobUrl) {
156
+ delete part.blobUrl;
157
+ }
158
+ });
159
+ }
160
+ });
161
+ });
162
+
163
+ localStorage.setItem('alphaChatSessions', JSON.stringify(sessionsToSave));
164
+ } catch (e) {
165
+ console.error("Failed to save sessions to localStorage:", e);
166
+ }
167
+ }
168
+
169
+ export function loadSessions() {
170
+ try {
171
+ const saved = localStorage.getItem('alphaChatSessions');
172
+ chatSessions = saved ? JSON.parse(saved) : [];
173
+ chatSessions.forEach(session => {
174
+ if (session.showThoughts === undefined) {
175
+ session.showThoughts = false;
176
+ }
177
+ // *** START: MODIFIED - افزودن فیلد جدید به پیام‌های قدیمی برای سازگاری ***
178
+ session.messages.forEach(message => {
179
+ if (message.role === 'assistant' && message.wasGeneratedWithThoughts === undefined) {
180
+ message.wasGeneratedWithThoughts = false;
181
+ }
182
+ });
183
+ // *** END: MODIFIED ***
184
+ });
185
+ } catch (e) {
186
+ console.error("Failed to load sessions from localStorage:", e);
187
+ chatSessions = [];
188
+ }
189
+ }
190
+
191
+ export function findLastIndex(array, predicate) {
192
+ for (let i = array.length - 1; i >= 0; i--) {
193
+ if (predicate(array[i])) { return i; }
194
+ }
195
+ return -1;
196
+ }
197
+
198
+ export function deleteMessage(chatId, messageIndex) {
199
+ const chat = chatSessions.find(s => s.id === chatId);
200
+ if (chat && chat.messages[messageIndex]) {
201
+ chat.messages.splice(messageIndex, 1);
202
+ saveSessions();
203
+ }
204
+ }