Opera8 commited on
Commit
a59a7e9
·
verified ·
1 Parent(s): d5a94ec

Create chat.html

Browse files
Files changed (1) hide show
  1. chat.html +1599 -0
chat.html ADDED
@@ -0,0 +1,1599 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fa" dir="rtl">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
+ <title>AI Alpha Chat</title>
7
+ <style>
8
+ /* --- استایل‌های پایه و ریست --- */
9
+ * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
10
+ body, html {
11
+ margin: 0; padding: 0;
12
+ font-family: IRANSans, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
13
+ background-color: #ffffff;
14
+ height: 100dvh;
15
+ overflow: hidden;
16
+ color: #333;
17
+ transition: background-color 0.3s, color 0.3s;
18
+ }
19
+
20
+ ::-webkit-scrollbar { width: 5px; height: 5px; }
21
+ ::-webkit-scrollbar-track { background: transparent; }
22
+ ::-webkit-scrollbar-thumb { background: #dcdcdc; border-radius: 10px; }
23
+ ::-webkit-scrollbar-thumb:hover { background: #b0b0b0; }
24
+
25
+ /* --- ساختار اصلی --- */
26
+ .app-container {
27
+ display: flex; flex-direction: column; height: 100%; position: relative;
28
+ }
29
+
30
+ /* --- هدر --- */
31
+ .header {
32
+ display: flex; justify-content: space-between; align-items: center;
33
+ padding: 12px 14px; background-color: rgba(255, 255, 255, 0.95);
34
+ backdrop-filter: blur(10px); border-bottom: 1px solid #f0f0f0; z-index: 20;
35
+ transition: all 0.3s;
36
+ }
37
+ .header-left, .header-right {
38
+ display: flex; align-items: center; gap: 4px; flex-shrink: 0;
39
+ }
40
+ .header-center {
41
+ display: flex; align-items: center; justify-content: center; flex-grow: 1; padding: 0 8px;
42
+ }
43
+
44
+ /* --- سوئیچ انتخاب مدل --- */
45
+ .model-switch {
46
+ position: relative;
47
+ display: flex;
48
+ background: #f1f5f9;
49
+ border-radius: 20px;
50
+ padding: 3px;
51
+ width: 170px;
52
+ max-width: 100%;
53
+ transition: background 0.3s;
54
+ }
55
+ .switch-btn {
56
+ width: 50%;
57
+ border: none;
58
+ background: none;
59
+ padding: 6px 0;
60
+ font-size: 11px;
61
+ font-weight: 700;
62
+ color: #64748b;
63
+ cursor: pointer;
64
+ z-index: 2;
65
+ transition: color 0.3s;
66
+ font-family: inherit;
67
+ white-space: nowrap;
68
+ }
69
+ .switch-btn.active { color: white; }
70
+ .switch-glider {
71
+ position: absolute;
72
+ top: 3px;
73
+ right: 3px;
74
+ width: calc(50% - 3px);
75
+ height: calc(100% - 6px);
76
+ background: #5a67d8;
77
+ border-radius: 18px;
78
+ transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
79
+ z-index: 1;
80
+ }
81
+ .model-switch[data-active="audio"] .switch-glider {
82
+ transform: translateX(-100%);
83
+ }
84
+ .model-switch[data-active="gpt5"] .switch-glider {
85
+ transform: translateX(0);
86
+ }
87
+
88
+ .icon-btn {
89
+ background: none; border: none; padding: 8px; border-radius: 50%;
90
+ cursor: pointer; display: flex; align-items: center; justify-content: center;
91
+ color: #555; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
92
+ }
93
+ .icon-btn:hover { background-color: #f5f5f5; color: #111; }
94
+ .icon-btn:active { transform: scale(0.85); background-color: #e0e0e0; }
95
+ .icon-btn svg { width: 22px; height: 22px; stroke: currentColor; stroke-width: 2; fill: none; stroke-linecap: round; stroke-linejoin: round; }
96
+
97
+ /* --- سایدبار و لیست چت‌ها --- */
98
+ .sidebar {
99
+ position: fixed; top: 0; right: -300px; width: 280px; height: 100%;
100
+ background-color: #f8f9fa; box-shadow: -4px 0 15px rgba(0,0,0,0.08);
101
+ transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); z-index: 100;
102
+ display: flex; flex-direction: column;
103
+ }
104
+ .sidebar.open { right: 0; }
105
+ .sidebar-header {
106
+ padding: 20px 16px; font-weight: bold; border-bottom: 1px solid #eee;
107
+ display: flex; justify-content: space-between; align-items: center;
108
+ color: #5a67d8; transition: border-color 0.3s;
109
+ }
110
+ .session-list { list-style: none; padding: 12px; margin: 0; overflow-y: auto; flex-grow: 1; }
111
+
112
+ .session-item {
113
+ display: flex; align-items: center; justify-content: space-between;
114
+ padding: 12px 14px; margin-bottom: 8px; border-radius: 12px;
115
+ cursor: pointer; font-size: 14px; color: #333;
116
+ transition: all 0.2s ease; position: relative;
117
+ }
118
+ .session-item.active { background-color: #eef2ff; font-weight: bold; }
119
+ .session-item:hover:not(.active) { background-color: #f0f0f0; }
120
+
121
+ .session-item-content {
122
+ display: flex; align-items: center; gap: 10px; flex-grow: 1; overflow: hidden;
123
+ }
124
+ .session-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex-grow: 1; }
125
+
126
+ .dots-btn {
127
+ background: none; border: none; padding: 4px; border-radius: 50%;
128
+ cursor: pointer; color: #666; display: flex; align-items: center; justify-content: center;
129
+ }
130
+ .dots-btn:hover { background: #ddd; }
131
+ .dots-btn svg { width: 18px; height: 18px; fill: currentColor; stroke: none; }
132
+
133
+ .popover-menu {
134
+ position: absolute; left: 10px; top: 40px; background: white;
135
+ border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.15);
136
+ display: none; flex-direction: column; z-index: 10; min-width: 120px;
137
+ overflow: hidden; transition: background 0.3s;
138
+ }
139
+ .popover-menu.show { display: flex; }
140
+ .popover-btn {
141
+ display: flex; align-items: center; justify-content: space-between;
142
+ padding: 12px 16px; border: none; background: none; width: 100%;
143
+ cursor: pointer; font-family: inherit; font-size: 14px; color: #ef4444; font-weight: bold;
144
+ }
145
+ .popover-btn:hover { background-color: #fff1f2; }
146
+ .popover-btn svg { width: 18px; height: 18px; stroke: currentColor; stroke-width: 2; fill: none; }
147
+
148
+ .overlay {
149
+ position: fixed; top: 0; left: 0; width: 100%; height: 100%;
150
+ background: rgba(0,0,0,0.4); backdrop-filter: blur(2px); z-index: 90;
151
+ display: none; opacity: 0; transition: opacity 0.3s ease;
152
+ }
153
+ .overlay.visible { display: block; opacity: 1; }
154
+
155
+ /* --- مودال تایید حذف و ارتقا --- */
156
+ .bottom-modal {
157
+ position: fixed; bottom: -100%; left: 0; width: 100%;
158
+ background-color: #fff; border-radius: 24px 24px 0 0;
159
+ padding: 24px 24px 30px 24px; box-shadow: 0 -5px 25px rgba(0,0,0,0.1);
160
+ z-index: 200; transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
161
+ display: flex; flex-direction: column; align-items: center;
162
+ }
163
+ .bottom-modal.open { bottom: 0; }
164
+ .modal-handle {
165
+ width: 40px; height: 4px; background-color: #ddd;
166
+ border-radius: 2px; margin-bottom: 20px;
167
+ }
168
+ .modal-title { font-size: 18px; font-weight: bold; color: #333; margin-bottom: 15px; text-align: center;}
169
+ .modal-desc { font-size: 15px; color: #666; margin-bottom: 30px; text-align: center; line-height: 1.6;}
170
+ .modal-buttons { display: flex; gap: 15px; width: 100%; }
171
+ .modal-btn {
172
+ flex: 1; padding: 14px 0; border-radius: 12px; font-size: 16px; font-weight: bold;
173
+ cursor: pointer; border: none; transition: all 0.2s; font-family: inherit;
174
+ }
175
+ .btn-cancel { background-color: transparent; border: 1.5px solid #5a67d8; color: #5a67d8; }
176
+ .btn-cancel:active { background-color: #eef2ff; }
177
+ .btn-delete { background-color: #f43f5e; color: white; }
178
+ .btn-delete:active { background-color: #e11d48; }
179
+
180
+ .btn-upgrade { background: linear-gradient(135deg, #8b5cf6, #6d28d9); color: white; box-shadow: 0 4px 12px rgba(109, 40, 217, 0.2); }
181
+ .btn-upgrade:active { background: linear-gradient(135deg, #7c3aed, #5b21b6); }
182
+
183
+ .modal-overlay {
184
+ position: fixed; top: 0; left: 0; width: 100%; height: 100%;
185
+ background: rgba(0,0,0,0.5); z-index: 190; display: none; opacity: 0; transition: opacity 0.3s;
186
+ }
187
+ .modal-overlay.open { display: block; opacity: 1; }
188
+
189
+ /* --- محیط چت --- */
190
+ .chat-area {
191
+ flex-grow: 1; overflow-y: auto;
192
+ padding: 20px 16px 120px 16px;
193
+ position: relative;
194
+ display: flex; flex-direction: column; gap: 20px; scroll-behavior: smooth;
195
+ }
196
+
197
+ .empty-state {
198
+ position: absolute; top: 40%; left: 50%; transform: translate(-50%, -50%);
199
+ font-size: 24px; font-weight: 900; text-align: center;
200
+ background: linear-gradient(to right, #ff8a80, #8c9eff);
201
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
202
+ color: transparent; text-shadow: none; -webkit-font-smoothing: antialiased;
203
+ display: none; width: 100%;
204
+ }
205
+
206
+ @keyframes messageSlideUp {
207
+ 0% { opacity: 0; transform: translateY(20px); }
208
+ 100% { opacity: 1; transform: translateY(0); }
209
+ }
210
+
211
+ .message-row {
212
+ display: flex; flex-direction: column; width: 100%;
213
+ animation: messageSlideUp 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
214
+ }
215
+ .message-row.user { align-items: flex-start; }
216
+ .message-row.bot { align-items: flex-start; }
217
+
218
+ .message-bubble {
219
+ max-width: 88%; font-size: 15px; line-height: 1.7;
220
+ word-wrap: break-word; white-space: pre-wrap; letter-spacing: 0.2px;
221
+ transition: background 0.3s, color 0.3s;
222
+ }
223
+
224
+ .message-row.user .message-bubble {
225
+ padding: 14px 18px;
226
+ background-color: #e6ecff;
227
+ color: #15151c;
228
+ border-radius: 20px 20px 4px 20px;
229
+ box-shadow: 0 2px 8px rgba(0,0,0,0.04);
230
+ }
231
+
232
+ .message-row.bot .message-bubble {
233
+ padding: 5px 0;
234
+ background-color: transparent;
235
+ color: #333;
236
+ border: none;
237
+ border-radius: 0;
238
+ text-align: right;
239
+ box-shadow: none;
240
+ max-width: 100%;
241
+ }
242
+
243
+ .action-buttons { display: flex; gap: 8px; margin-top: 8px; margin-right: 4px; }
244
+ .action-btn {
245
+ background: #f8f9fa; border: 1px solid #eee; border-radius: 50%;
246
+ width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
247
+ cursor: pointer; color: #666; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
248
+ }
249
+ .action-btn:hover { background: #fff; border-color: #ddd; color: #333; box-shadow: 0 2px 5px rgba(0,0,0,0.05); }
250
+ .action-btn:active { transform: scale(0.85); }
251
+ .action-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
252
+ .action-btn.play-btn svg { fill: currentColor; stroke: none; }
253
+
254
+ @keyframes checkmarkPop {
255
+ 0% { transform: scale(0.5) rotate(-45deg); opacity: 0; }
256
+ 50% { transform: scale(1.2) rotate(0); }
257
+ 100% { transform: scale(1) rotate(0); opacity: 1; }
258
+ }
259
+ .icon-check { animation: checkmarkPop 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; stroke: currentColor; }
260
+
261
+ .status-bar { text-align: center; font-size: 11px; font-weight: bold; color: #999; margin-bottom: 8px; letter-spacing: 0.5px; transition: 0.3s; }
262
+
263
+ /* --- انیمیشن در حال تایپ --- */
264
+ .typing-indicator {
265
+ display: flex; align-items: center; gap: 5px; padding: 10px 8px;
266
+ }
267
+ .typing-indicator .dot {
268
+ width: 8px; height: 8px; border-radius: 50%; background-color: #5a67d8;
269
+ animation: bounce 1.4s infinite ease-in-out both;
270
+ }
271
+ .typing-indicator .dot:nth-child(1) { animation-delay: -0.32s; }
272
+ .typing-indicator .dot:nth-child(2) { animation-delay: -0.16s; }
273
+
274
+ @keyframes bounce {
275
+ 0%, 80%, 100% { transform: scale(0); opacity: 0.4; }
276
+ 40% { transform: scale(1); opacity: 1; }
277
+ }
278
+
279
+ /* --- باکس ورودی پیام و پیش‌نمایش تصویر --- */
280
+ .input-container {
281
+ position: absolute; bottom: 0; left: 0; width: 100%;
282
+ padding: 10px 16px 20px 16px;
283
+ background: linear-gradient(to top, rgba(255,255,255,1) 85%, rgba(255,255,255,0));
284
+ z-index: 10; transition: background 0.3s;
285
+ }
286
+ .image-preview-box {
287
+ display: none; padding: 6px; background: #fff; border-radius: 12px;
288
+ border: 1px solid #ddd; margin-bottom: 8px; position: relative; max-width: 120px;
289
+ box-shadow: 0 4px 10px rgba(0,0,0,0.05); transition: background 0.3s, border-color 0.3s;
290
+ }
291
+ .image-preview-box img { width: 100%; border-radius: 8px; display: block; }
292
+ .image-preview-box .close-btn {
293
+ position: absolute; top: -5px; right: -5px; background: #ff4d4f; color: white;
294
+ border: none; border-radius: 50%; width: 22px; height: 22px; cursor: pointer;
295
+ display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold;
296
+ }
297
+
298
+ .input-wrapper {
299
+ display: flex; align-items: center; background-color: #ffffff;
300
+ border: 1.5px solid #e8e8e8; border-radius: 30px; padding: 6px 6px 6px 16px;
301
+ box-shadow: 0 8px 25px rgba(0,0,0,0.06); transition: border-color 0.3s ease, background 0.3s;
302
+ }
303
+ .input-wrapper:focus-within { border-color: #c0caff; }
304
+
305
+ .attach-btn {
306
+ background: none; border: none; padding: 6px; color: #666; cursor: pointer;
307
+ display: flex; align-items: center; justify-content: center; transition: all 0.2s;
308
+ }
309
+ .attach-btn:hover { color: #5a67d8; }
310
+
311
+ .input-wrapper input[type="text"] {
312
+ flex-grow: 1; border: none; background: transparent; outline: none;
313
+ font-size: 15px; padding: 12px 5px; font-family: inherit; color: #333; transition: color 0.3s;
314
+ }
315
+ .input-wrapper input::placeholder { color: #bbb; }
316
+
317
+ .send-btn {
318
+ background-color: #333; color: white; border: none;
319
+ width: 40px; height: 40px; border-radius: 50%;
320
+ display: flex; align-items: center; justify-content: center;
321
+ cursor: pointer; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
322
+ }
323
+ .send-btn:hover { background-color: #111; }
324
+ .send-btn:active { transform: scale(0.85); }
325
+ .send-btn:disabled { background-color: #e0e0e0; cursor: not-allowed; transform: none; box-shadow: none; }
326
+ .send-btn svg { width: 20px; height: 20px; stroke: white; stroke-width: 2.5; fill: none; stroke-linecap: round; stroke-linejoin: round; }
327
+
328
+ /* --- استایل‌های تنظیمات و تم تاریک/روشن --- */
329
+ .sidebar-footer {
330
+ padding: 15px; border-top: 1px solid #eee; background: #f8f9fa; z-index: 10;
331
+ transition: background 0.3s, border-color 0.3s;
332
+ }
333
+ .settings-btn {
334
+ display: flex; align-items: center; gap: 10px; width: 100%; padding: 12px;
335
+ background: #fff; border: 1px solid #ddd; border-radius: 12px;
336
+ color: #333; font-weight: bold; font-family: inherit; cursor: pointer;
337
+ transition: all 0.2s; font-size: 14px; justify-content: center;
338
+ }
339
+ .settings-btn:hover { background: #f0f0f0; }
340
+ .settings-btn svg { width: 18px; height: 18px; }
341
+
342
+ .settings-menu {
343
+ display: none; background: #fff; border: 1px solid #ddd; border-radius: 12px;
344
+ padding: 12px; margin-bottom: 10px; box-shadow: 0 4px 10px rgba(0,0,0,0.05);
345
+ animation: messageSlideUp 0.3s forwards; transition: background 0.3s, border-color 0.3s;
346
+ }
347
+ .settings-menu.open { display: block; }
348
+
349
+ /* استایل وضعیت کاربر */
350
+ .user-status-row { text-align: center; margin-bottom: 15px; }
351
+ .status-badge { display: inline-block; padding: 6px 12px; border-radius: 12px; font-size: 12px; font-weight: bold; margin-bottom: 8px;}
352
+ .status-badge.free { background: #f1f5f9; color: #475569; border: 1px solid #e2e8f0; }
353
+ .status-badge.paid { background: #ede9fe; color: #5b21b6; border: 1px solid #ddd6fe; }
354
+ .upgrade-btn { display: none; width: 100%; margin-top: 10px; padding: 10px; background: linear-gradient(135deg, #8b5cf6, #6d28d9); color: #fff; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; font-family: inherit; font-size: 13px; box-shadow: 0 4px 12px rgba(109, 40, 217, 0.2);}
355
+ .upgrade-btn:active { background: linear-gradient(135deg, #7c3aed, #5b21b6); }
356
+
357
+ .theme-toggle-row {
358
+ display: flex; justify-content: space-between; align-items: center; font-size: 14px; font-weight: bold; color: #333;
359
+ transition: color 0.3s;
360
+ }
361
+ .toggle-switch { position: relative; display: inline-block; width: 44px; height: 24px; }
362
+ .toggle-switch input { opacity: 0; width: 0; height: 0; }
363
+ .toggle-slider {
364
+ position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
365
+ background-color: #ccc; transition: .4s; border-radius: 24px;
366
+ }
367
+ .toggle-slider:before {
368
+ position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px;
369
+ background-color: white; transition: .4s; border-radius: 50%; box-shadow: 0 2px 4px rgba(0,0,0,0.2);
370
+ }
371
+ input:checked + .toggle-slider { background-color: #5a67d8; }
372
+ input:checked + .toggle-slider:before { transform: translateX(20px); }
373
+
374
+ /* --- Dark Theme --- */
375
+ body.dark-theme { background-color: #121212; color: #e0e0e0; }
376
+ body.dark-theme .header { background-color: rgba(30, 30, 30, 0.95); border-bottom-color: #333; }
377
+ body.dark-theme .sidebar { background-color: #1a1a1a; border-left: 1px solid #333; box-shadow: -4px 0 15px rgba(0,0,0,0.5); }
378
+ body.dark-theme .sidebar-header { border-bottom-color: #333; color: #818cf8; }
379
+ body.dark-theme .session-item { color: #e0e0e0; }
380
+ body.dark-theme .session-item:hover:not(.active) { background-color: #2a2a2a; }
381
+ body.dark-theme .session-item.active { background-color: #2d3748; color: #fff; }
382
+ body.dark-theme .sidebar-footer { background-color: #1a1a1a; border-top-color: #333; }
383
+ body.dark-theme .settings-btn { background-color: #2a2a2a; border-color: #444; color: #e0e0e0; }
384
+ body.dark-theme .settings-btn:hover { background-color: #333; }
385
+ body.dark-theme .settings-menu { background-color: #2a2a2a; border-color: #444; }
386
+ body.dark-theme .theme-toggle-row { color: #e0e0e0; }
387
+ body.dark-theme .input-container { background: linear-gradient(to top, rgba(18,18,18,1) 85%, rgba(18,18,18,0)); }
388
+ body.dark-theme .input-wrapper { background-color: #1e1e1e; border-color: #444; }
389
+ body.dark-theme .input-wrapper input[type="text"] { color: #e0e0e0; }
390
+ body.dark-theme .message-row.user .message-bubble { background-color: #2d3748; color: #e0e0e0; }
391
+ body.dark-theme .message-row.bot .message-bubble { color: #e0e0e0; }
392
+ body.dark-theme .action-btn { background: #2a2a2a; border-color: #444; color: #aaa; }
393
+ body.dark-theme .action-btn:hover { background: #333; border-color: #555; color: #fff; }
394
+ body.dark-theme .icon-btn { color: #ccc; }
395
+ body.dark-theme .icon-btn:hover { background-color: #333; color: #fff; }
396
+ body.dark-theme .model-switch { background: #2a2a2a; }
397
+ body.dark-theme .switch-btn { color: #aaa; }
398
+ body.dark-theme .switch-btn.active { color: white; }
399
+ body.dark-theme .bottom-modal { background-color: #1e1e1e; }
400
+ body.dark-theme .modal-title { color: #fff; }
401
+ body.dark-theme .modal-desc { color: #aaa; }
402
+ body.dark-theme .modal-handle { background-color: #555; }
403
+ body.dark-theme .popover-menu { background: #2a2a2a; }
404
+ body.dark-theme .popover-btn { color: #f87171; }
405
+ body.dark-theme .popover-btn:hover { background-color: #3f1d1d; }
406
+ body.dark-theme .dots-btn { color: #aaa; }
407
+ body.dark-theme .dots-btn:hover { background: #444; }
408
+ body.dark-theme .image-preview-box { background: #1e1e1e; border-color: #444; }
409
+ body.dark-theme .send-btn:disabled { background-color: #444; }
410
+ body.dark-theme .status-badge.free { background: #334155; color: #cbd5e1; border-color: #475569; }
411
+ body.dark-theme .status-badge.paid { background: #2e1065; color: #ddd6fe; border-color: #4c1d95; }
412
+ body.dark-theme .typing-indicator .dot { background-color: #818cf8; }
413
+ </style>
414
+ </head>
415
+ <body>
416
+
417
+ <input type="file" id="imageInput" accept="image/*" style="position: absolute; left: -9999px; top: -9999px; width: 1px; height: 1px; opacity: 0;" onchange="handleImageSelection(event)">
418
+
419
+ <div class="app-container">
420
+ <div class="header">
421
+ <div class="header-right">
422
+ <button class="icon-btn" onclick="toggleSidebar()" aria-label="منو">
423
+ <svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
424
+ </button>
425
+ </div>
426
+ <div class="header-center">
427
+ <div class="model-switch" id="modelSwitch" data-active="gpt5">
428
+ <div class="switch-glider"></div>
429
+ <button class="switch-btn active" id="btn-gpt5" onclick="setModel('gpt5')">آلفا GPT5</button>
430
+ <button class="switch-btn" id="btn-audio" onclick="setModel('audio')">آلفا صوتی</button>
431
+ </div>
432
+ </div>
433
+ <div class="header-left">
434
+ <button class="icon-btn" onclick="startNewSession()" aria-label="چت جدید">
435
+ <svg viewBox="0 0 24 24"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/><path d="M9 12h6M12 9v6"/></svg>
436
+ </button>
437
+ <button class="icon-btn" id="muteToggleBtn" onclick="toggleMute()" aria-label="قطع و وصل صدا">
438
+ <svg id="icon-sound-on" viewBox="0 0 24 24"><path d="M11 5L6 9H2v6h4l5 4V5zM15.54 8.46a5 5 0 0 1 0 7.07M19.07 4.93a10 10 0 0 1 0 14.14"/></svg>
439
+ <svg id="icon-sound-off" viewBox="0 0 24 24" style="display:none;"><path d="M11 5L6 9H2v6h4l5 4V5zM22 9l-6 6M16 9l6 6"/></svg>
440
+ </button>
441
+ </div>
442
+ </div>
443
+
444
+ <div class="overlay" id="overlay" onclick="toggleSidebar()"></div>
445
+ <div class="sidebar" id="sidebar">
446
+ <div class="sidebar-header">
447
+ <span>تاریخچه گفتگوها</span>
448
+ <button class="icon-btn" onclick="toggleSidebar()">
449
+ <svg viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12"/></svg>
450
+ </button>
451
+ </div>
452
+ <ul class="session-list" id="sessionList"></ul>
453
+
454
+ <!-- بخش تنظیمات و انتخاب تم -->
455
+ <div class="sidebar-footer">
456
+ <div class="settings-menu" id="settingsMenu">
457
+
458
+ <!-- بخش وضعیت اشتراک کاربر -->
459
+ <div class="user-status-row">
460
+ <span id="userStatusBadge" class="status-badge free">در حال بررسی...</span>
461
+ <button id="upgradeBtnChat" class="upgrade-btn" onclick="goToPremium()">ارتقا به نسخه نامحدود ⭐️</button>
462
+ <hr style="border:0; border-top:1px solid #ddd; margin:15px 0;">
463
+ </div>
464
+
465
+ <div class="theme-toggle-row">
466
+ <span>حالت تاریک</span>
467
+ <label class="toggle-switch">
468
+ <input type="checkbox" id="themeCheckbox" onchange="toggleTheme()">
469
+ <span class="toggle-slider"></span>
470
+ </label>
471
+ </div>
472
+ </div>
473
+ <button class="settings-btn" onclick="toggleSettingsMenu()">
474
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
475
+ تنظیمات کاربری
476
+ </button>
477
+ </div>
478
+ </div>
479
+
480
+ <!-- پس زمینه تیره برای مودال‌ها -->
481
+ <div class="modal-overlay" id="modalOverlay" onclick="closeAllModals()"></div>
482
+
483
+ <!-- مودال حذف -->
484
+ <div class="bottom-modal" id="deleteModal">
485
+ <div class="modal-handle"></div>
486
+ <div class="modal-title">حذف گفتگو</div>
487
+ <div class="modal-desc">آیا از حذف گفت‌وگوی خود مطمئن هستی؟</div>
488
+ <div class="modal-buttons">
489
+ <button class="modal-btn btn-cancel" onclick="closeAllModals()">انصراف</button>
490
+ <button class="modal-btn btn-delete" onclick="confirmDeleteSession()">حذف</button>
491
+ </div>
492
+ </div>
493
+
494
+ <!-- مودال اتمام محدودیت (ارتقا) -->
495
+ <div class="bottom-modal" id="premiumModal">
496
+ <div class="modal-handle"></div>
497
+ <div class="modal-title" style="color: #8b5cf6;">محدودیت نسخه رایگان</div>
498
+ <div class="modal-desc" id="premiumModalDesc">محدودیت پیام‌های روزانه شما به پایان رسیده است. برای ادامه چت به‌صورت نامحدود، حسابتان را ارتقا دهید.</div>
499
+ <div class="modal-buttons">
500
+ <button class="modal-btn btn-cancel" onclick="closeAllModals()">متوجه شدم</button>
501
+ <button class="modal-btn btn-upgrade" onclick="goToPremium()">ارتقا حساب ⭐️</button>
502
+ </div>
503
+ </div>
504
+
505
+ <div class="chat-area" id="chatArea">
506
+ <div class="empty-state" id="emptyState">چطور میتونم کمکت کنم؟</div>
507
+ <div id="dynamicSpacer" style="height: 0px; flex-shrink: 0;"></div>
508
+ </div>
509
+
510
+ <div class="input-container">
511
+ <div class="status-bar" id="statusBar"></div>
512
+
513
+ <div class="image-preview-box" id="imagePreviewBox">
514
+ <button class="close-btn" onclick="clearSelectedImage()">✕</button>
515
+ <img id="previewImage" src="" alt="پیش‌نمایش">
516
+ </div>
517
+
518
+ <div class="input-wrapper">
519
+ <button type="button" class="attach-btn" id="attachBtn" onclick="openFilePicker()" aria-label="ارسال تصویر">
520
+ <svg viewBox="0 0 24 24" width="22" height="22" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
521
+ <line x1="12" y1="5" x2="12" y2="19"></line>
522
+ <line x1="5" y1="12" x2="19" y2="12"></line>
523
+ </svg>
524
+ </button>
525
+
526
+ <input type="text" id="textInput" placeholder="پیام خودتو اینجا بنویس..." autocomplete="off">
527
+
528
+ <button type="button" class="send-btn" id="sendBtn">
529
+ <svg viewBox="0 0 24 24"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
530
+ </button>
531
+ </div>
532
+ </div>
533
+ </div>
534
+
535
+ <script>
536
+ // --- متغیرهای کنترل ارتباط ---
537
+ let currentAbortController = null;
538
+ let isGenerating = false;
539
+
540
+ // --- مدیریت کاربران و محدودیت‌ها (Freemium) ---
541
+ let userSubscriptionStatus = 'free'; // پیش‌فرض رایگان
542
+ const PREMIUM_PAGE_ID = '1149636'; // آیدی صفحه ویژه خرید
543
+ const DAILY_MSG_LIMIT = 20; // محدودیت پیام
544
+ const DAILY_IMG_LIMIT = 2; // محدودیت عکس
545
+
546
+ function isUserPaid(userObject) {
547
+ return userObject?.isLogin && userObject.accessible_pages?.some(p => p == PREMIUM_PAGE_ID);
548
+ }
549
+
550
+ // دریافت اطلاعات کاربر از اپلیکیشن اندروید (پدر)
551
+ window.addEventListener('message', (event) => {
552
+ if (event.data?.type === 'USER_DATA_RESPONSE') {
553
+ if (event.data.error || !event.data.payload) {
554
+ updateSubscriptionUI('free');
555
+ return;
556
+ }
557
+ try {
558
+ const userObject = JSON.parse(event.data.payload);
559
+ updateSubscriptionUI(isUserPaid(userObject) ? 'paid' : 'free');
560
+ } catch (e) {
561
+ updateSubscriptionUI('free');
562
+ }
563
+ }
564
+ });
565
+
566
+ function goToPremium() {
567
+ parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM' }, '*');
568
+ }
569
+
570
+ function getTodayString() {
571
+ const date = new Date();
572
+ return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
573
+ }
574
+
575
+ function getUsage() {
576
+ const today = getTodayString();
577
+ let usage = JSON.parse(localStorage.getItem('alpha_chat_usage') || '{}');
578
+ if (usage.date !== today) {
579
+ // ریست کردن برای روز جدید
580
+ usage = { date: today, messages: 0, images: 0 };
581
+ localStorage.setItem('alpha_chat_usage', JSON.stringify(usage));
582
+ }
583
+ return usage;
584
+ }
585
+
586
+ function updateSubscriptionUI(status) {
587
+ userSubscriptionStatus = status;
588
+ const badge = document.getElementById('userStatusBadge');
589
+ const upgradeBtn = document.getElementById('upgradeBtnChat');
590
+
591
+ if (status === 'paid') {
592
+ badge.textContent = 'نسخه نامحدود ⭐️';
593
+ badge.className = 'status-badge paid';
594
+ upgradeBtn.style.display = 'none';
595
+ } else {
596
+ badge.textContent = 'کاربر نسخه رایگان';
597
+ badge.className = 'status-badge free';
598
+ upgradeBtn.style.display = 'block';
599
+ }
600
+ }
601
+
602
+ function checkCanSend(hasImage) {
603
+ if (userSubscriptionStatus === 'paid') return true;
604
+ const usage = getUsage();
605
+
606
+ if (usage.messages >= DAILY_MSG_LIMIT) {
607
+ document.getElementById('premiumModalDesc').innerText = "محدودیت ۲۰ پیام روزانه شما به پایان رسیده است. برای چت نامحدود حساب خود را ارتقا دهید.";
608
+ openPremiumModal();
609
+ return false;
610
+ }
611
+ if (hasImage && usage.images >= DAILY_IMG_LIMIT) {
612
+ document.getElementById('premiumModalDesc').innerText = "محدودیت تحلیل ۲ تصویر در روز به پایان رسیده است. برای استفاده نامحدود حساب خود را ارتقا دهید.";
613
+ openPremiumModal();
614
+ return false;
615
+ }
616
+ return true;
617
+ }
618
+
619
+ function incrementUsage(hasImage) {
620
+ if (userSubscriptionStatus === 'paid') return;
621
+ let usage = getUsage();
622
+ usage.messages += 1;
623
+ if (hasImage) usage.images += 1;
624
+ localStorage.setItem('alpha_chat_usage', JSON.stringify(usage));
625
+ }
626
+ // --- پایان بخش محدودیت‌ها ---
627
+
628
+ let isMuted = false;
629
+ let globalAudioPlayer = new Audio();
630
+ let currentPlayingButton = null;
631
+ let currentModel = 'gpt5';
632
+ let modelActiveSession = { gpt5: null, audio: null };
633
+ let currentAttachedImageBase64 = null;
634
+ let forceNewSession = false;
635
+ let activeUserRow = null;
636
+
637
+ const PLAY_ICON_SVG = `<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>`;
638
+ const PAUSE_ICON_SVG = `<svg viewBox="0 0 24 24"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>`;
639
+ const COPY_ICON_SVG = `<svg viewBox="0 0 24 24"><path d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/></svg>`;
640
+ const EDIT_ICON_SVG = `<svg viewBox="0 0 24 24"><path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"/></svg>`;
641
+ const CHECK_ICON_SVG = `<svg class="icon-check" viewBox="0 0 24 24" fill="none" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
642
+
643
+ /* --- توابع منوی تنظیمات و تغییر تم --- */
644
+ function toggleSettingsMenu() {
645
+ const menu = document.getElementById('settingsMenu');
646
+ menu.classList.toggle('open');
647
+ }
648
+
649
+ function toggleTheme() {
650
+ const isDark = document.getElementById('themeCheckbox').checked;
651
+ if (isDark) {
652
+ document.body.classList.add('dark-theme');
653
+ localStorage.setItem('appTheme', 'dark');
654
+ } else {
655
+ document.body.classList.remove('dark-theme');
656
+ localStorage.setItem('appTheme', 'light');
657
+ }
658
+ }
659
+ /* ---------------------------------- */
660
+
661
+ /* --- تبدیل بلوک کد و کپی آن --- */
662
+ function formatMessageText(text) {
663
+ if (!text) return "";
664
+ const escapeHTML = (str) => str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
665
+ const codeBlockRegex = /```(.*?)?\n([\s\S]*?)```/g;
666
+ let lastIndex = 0;
667
+ let result = "";
668
+ let match;
669
+
670
+ while ((match = codeBlockRegex.exec(text)) !== null) {
671
+ const beforeText = text.substring(lastIndex, match.index);
672
+ result += escapeHTML(beforeText);
673
+
674
+ const lang = escapeHTML(match[1].trim());
675
+ const code = escapeHTML(match[2]);
676
+ const uniqueId = 'code-' + Math.random().toString(36).substr(2, 9);
677
+
678
+ result += `
679
+ <div class="code-container" style="background: #1e1e1e; border-radius: 8px; margin: 10px 0; direction: ltr; text-align: left; white-space: normal; font-family: sans-serif; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
680
+ <div style="background: #2d2d2d; padding: 8px 12px; border-radius: 8px 8px 0 0; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #444;">
681
+ <span style="color: #9cdcfe; font-size: 12px; font-family: monospace;">${lang || 'Code'}</span>
682
+ <button onclick="copyCodeBlock('${uniqueId}', this)" style="background: transparent; border: none; color: #ccc; font-size: 12px; cursor: pointer; display: flex; align-items: center; gap: 4px; font-family: inherit; padding: 0;">
683
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
684
+ <span class="copy-text">کپی</span>
685
+ </button>
686
+ </div>
687
+ <div style="padding: 12px; overflow-x: auto; color: #d4d4d4; font-size: 13px; line-height: 1.5; white-space: pre;">
688
+ <pre style="margin: 0; font-family: monospace;" id="${uniqueId}">${code}</pre>
689
+ </div>
690
+ </div>`;
691
+
692
+ lastIndex = codeBlockRegex.lastIndex;
693
+ }
694
+
695
+ let remainingText = text.substring(lastIndex);
696
+
697
+ const unclosedMatch = /```(.*?)?\n([\s\S]*)$/.exec(remainingText);
698
+ if (unclosedMatch) {
699
+ const beforeUnclosed = remainingText.substring(0, unclosedMatch.index);
700
+ result += escapeHTML(beforeUnclosed);
701
+
702
+ const lang = escapeHTML(unclosedMatch[1].trim());
703
+ const code = escapeHTML(unclosedMatch[2]);
704
+ const uniqueId = 'code-' + Math.random().toString(36).substr(2, 9);
705
+
706
+ result += `
707
+ <div class="code-container" style="background: #1e1e1e; border-radius: 8px; margin: 10px 0; direction: ltr; text-align: left; white-space: normal; font-family: sans-serif; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
708
+ <div style="background: #2d2d2d; padding: 8px 12px; border-radius: 8px 8px 0 0; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #444;">
709
+ <span style="color: #9cdcfe; font-size: 12px; font-family: monospace;">${lang || 'Code'}</span>
710
+ <button style="background: transparent; border: none; color: #888; font-size: 12px; display: flex; align-items: center; gap: 4px; font-family: inherit; padding: 0;">
711
+ <span class="copy-text">در حال تایپ...</span>
712
+ </button>
713
+ </div>
714
+ <div style="padding: 12px; overflow-x: auto; color: #d4d4d4; font-size: 13px; line-height: 1.5; white-space: pre;">
715
+ <pre style="margin: 0; font-family: monospace;" id="${uniqueId}">${code}</pre>
716
+ </div>
717
+ </div>`;
718
+ } else {
719
+ result += escapeHTML(remainingText);
720
+ }
721
+
722
+ return result;
723
+ }
724
+
725
+ function copyCodeBlock(id, btn) {
726
+ const codeElement = document.getElementById(id);
727
+ if (!codeElement) return;
728
+
729
+ let textToCopy = codeElement.innerText;
730
+
731
+ if (navigator.clipboard && window.isSecureContext) {
732
+ navigator.clipboard.writeText(textToCopy).then(showSuccess).catch(fallbackCopy);
733
+ } else {
734
+ fallbackCopy();
735
+ }
736
+
737
+ function showSuccess() {
738
+ const span = btn.querySelector('.copy-text');
739
+ const originalText = span.innerText;
740
+ span.innerText = "کپی شد ✔";
741
+ btn.style.color = "#10b981";
742
+ setTimeout(() => {
743
+ span.innerText = originalText;
744
+ btn.style.color = "#ccc";
745
+ }, 2000);
746
+ }
747
+
748
+ function fallbackCopy() {
749
+ const textArea = document.createElement("textarea");
750
+ textArea.value = textToCopy;
751
+ document.body.appendChild(textArea);
752
+ textArea.select();
753
+ try { document.execCommand('copy'); showSuccess(); } catch(e) {}
754
+ textArea.remove();
755
+ }
756
+ }
757
+ /* ---------------------------------------------------- */
758
+
759
+ function updateModelUI(modelName) {
760
+ currentModel = modelName;
761
+ const switchContainer = document.getElementById('modelSwitch');
762
+ switchContainer.setAttribute('data-active', modelName);
763
+ document.getElementById('btn-audio').classList.remove('active');
764
+ document.getElementById('btn-gpt5').classList.remove('active');
765
+ document.getElementById(`btn-${modelName}`).classList.add('active');
766
+ const attachBtn = document.getElementById('attachBtn');
767
+ if (modelName === 'audio') {
768
+ attachBtn.style.display = 'none';
769
+ clearSelectedImage();
770
+ } else {
771
+ attachBtn.style.display = 'flex';
772
+ }
773
+ }
774
+
775
+ function setModel(modelName) {
776
+ if (currentModel === modelName) return;
777
+
778
+ modelActiveSession[currentModel] = currentSessionId;
779
+
780
+ updateModelUI(modelName);
781
+
782
+ const targetSessionId = modelActiveSession[currentModel];
783
+ if (targetSessionId) {
784
+ loadSession(targetSessionId, false);
785
+ } else {
786
+ startNewSession();
787
+ }
788
+ }
789
+
790
+ function toggleMute() {
791
+ isMuted = !isMuted;
792
+ document.getElementById('icon-sound-on').style.display = isMuted ? 'none' : 'block';
793
+ document.getElementById('icon-sound-off').style.display = isMuted ? 'block' : 'none';
794
+
795
+ if (isMuted) {
796
+ if (!globalAudioPlayer.paused) {
797
+ globalAudioPlayer.pause();
798
+ if (currentPlayingButton) currentPlayingButton.innerHTML = PLAY_ICON_SVG;
799
+ }
800
+ activeSources.forEach(source => {
801
+ try { source.stop(); } catch(e) {}
802
+ });
803
+ activeSources = [];
804
+ nextAudioTime = 0;
805
+ }
806
+ }
807
+
808
+ function toggleAudioReplay(audioUrl, btnElement) {
809
+ if (!audioUrl) return;
810
+ if (globalAudioPlayer.src.endsWith(audioUrl) && !globalAudioPlayer.paused) {
811
+ globalAudioPlayer.pause();
812
+ btnElement.innerHTML = PLAY_ICON_SVG;
813
+ return;
814
+ }
815
+ if (currentPlayingButton && currentPlayingButton !== btnElement) {
816
+ currentPlayingButton.innerHTML = PLAY_ICON_SVG;
817
+ }
818
+ globalAudioPlayer.src = audioUrl;
819
+ globalAudioPlayer.play();
820
+ btnElement.innerHTML = PAUSE_ICON_SVG;
821
+ currentPlayingButton = btnElement;
822
+ globalAudioPlayer.onended = () => {
823
+ btnElement.innerHTML = PLAY_ICON_SVG;
824
+ currentPlayingButton = null;
825
+ };
826
+ }
827
+
828
+ function copyMessageText(btnElement) {
829
+ const messageRow = btnElement.closest('.message-row');
830
+ let textToCopy = "";
831
+ const bubble = messageRow.querySelector('.message-bubble');
832
+ if (bubble) {
833
+ const clone = bubble.cloneNode(true);
834
+ const imgs = clone.querySelectorAll('img');
835
+ imgs.forEach(img => img.remove());
836
+ textToCopy = clone.innerText.trim();
837
+ }
838
+ function showCopiedSuccess() {
839
+ btnElement.innerHTML = CHECK_ICON_SVG;
840
+ const originalColor = btnElement.style.color;
841
+ btnElement.style.color = '#10b981';
842
+ statusBar.textContent = "متن کپی شد ✔️";
843
+ setTimeout(() => {
844
+ btnElement.innerHTML = COPY_ICON_SVG;
845
+ btnElement.style.color = originalColor;
846
+ statusBar.textContent = "";
847
+ }, 2000);
848
+ }
849
+ if (navigator.clipboard && window.isSecureContext) {
850
+ navigator.clipboard.writeText(textToCopy).then(showCopiedSuccess).catch(fallbackCopy);
851
+ } else {
852
+ fallbackCopy();
853
+ }
854
+ function fallbackCopy() {
855
+ const textArea = document.createElement("textarea");
856
+ textArea.value = textToCopy;
857
+ textArea.style.position = "fixed";
858
+ textArea.style.left = "-999999px";
859
+ textArea.style.top = "-999999px";
860
+ document.body.appendChild(textArea);
861
+ textArea.focus();
862
+ textArea.select();
863
+ try {
864
+ document.execCommand('copy');
865
+ showCopiedSuccess();
866
+ } catch (err) {
867
+ console.error('Copy failed', err);
868
+ }
869
+ textArea.remove();
870
+ }
871
+ }
872
+
873
+ /* --- منطق ویرایش پیام کاربر --- */
874
+ function editUserMessage(btn) {
875
+ if (isGenerating) return;
876
+ const row = btn.closest('.message-row');
877
+ const bubble = row.querySelector('.message-bubble');
878
+ const originalHTML = row.innerHTML;
879
+
880
+ const clone = bubble.cloneNode(true);
881
+ const imgs = clone.querySelectorAll('img');
882
+ imgs.forEach(img => img.remove());
883
+ const textToEdit = clone.innerText.trim();
884
+
885
+ const editContainer = document.createElement('div');
886
+ editContainer.style.width = "100%";
887
+ editContainer.style.maxWidth = "88%";
888
+ editContainer.style.background = "var(--bg-color, #f8f9fa)";
889
+ editContainer.style.padding = "10px";
890
+ editContainer.style.borderRadius = "12px";
891
+ editContainer.style.boxShadow = "0 2px 8px rgba(0,0,0,0.05)";
892
+ if(document.body.classList.contains('dark-theme')) {
893
+ editContainer.style.background = "#2a2a2a";
894
+ }
895
+
896
+ const textarea = document.createElement('textarea');
897
+ textarea.value = textToEdit;
898
+ textarea.style.width = "100%";
899
+ textarea.style.minHeight = "60px";
900
+ textarea.style.borderRadius = "8px";
901
+ textarea.style.border = "1px solid #ccc";
902
+ textarea.style.padding = "10px";
903
+ textarea.style.fontFamily = "inherit";
904
+ textarea.style.fontSize = "14px";
905
+ textarea.style.resize = "vertical";
906
+ textarea.style.marginBottom = "8px";
907
+ textarea.style.outline = "none";
908
+ if(document.body.classList.contains('dark-theme')) {
909
+ textarea.style.background = "#1e1e1e";
910
+ textarea.style.color = "#eee";
911
+ textarea.style.borderColor = "#444";
912
+ }
913
+
914
+ const btnContainer = document.createElement('div');
915
+ btnContainer.style.display = "flex";
916
+ btnContainer.style.gap = "8px";
917
+ btnContainer.style.justifyContent = "flex-end";
918
+
919
+ const cancelBtn = document.createElement('button');
920
+ cancelBtn.innerText = "انصراف";
921
+ cancelBtn.style.padding = "6px 14px";
922
+ cancelBtn.style.borderRadius = "20px";
923
+ cancelBtn.style.border = "none";
924
+ cancelBtn.style.background = "#e2e8f0";
925
+ cancelBtn.style.color = "#475569";
926
+ cancelBtn.style.cursor = "pointer";
927
+ cancelBtn.style.fontFamily = "inherit";
928
+ cancelBtn.style.fontSize = "12px";
929
+ cancelBtn.style.fontWeight = "bold";
930
+ cancelBtn.onclick = () => { row.innerHTML = originalHTML; };
931
+ if(document.body.classList.contains('dark-theme')) {
932
+ cancelBtn.style.background = "#444";
933
+ cancelBtn.style.color = "#ccc";
934
+ }
935
+
936
+ const submitBtn = document.createElement('button');
937
+ submitBtn.innerText = "تایید و ارسال";
938
+ submitBtn.style.padding = "6px 14px";
939
+ submitBtn.style.borderRadius = "20px";
940
+ submitBtn.style.border = "none";
941
+ submitBtn.style.background = "#5a67d8";
942
+ submitBtn.style.color = "white";
943
+ submitBtn.style.cursor = "pointer";
944
+ submitBtn.style.fontFamily = "inherit";
945
+ submitBtn.style.fontSize = "12px";
946
+ submitBtn.style.fontWeight = "bold";
947
+ submitBtn.onclick = () => {
948
+ const newText = textarea.value.trim();
949
+ if(!newText && !row.querySelector('img')) return;
950
+
951
+ // بررسی مجدد محدودیت موقع ویرایش
952
+ const hasImg = !!row.querySelector('img');
953
+ if (!checkCanSend(hasImg)) return;
954
+
955
+ incrementUsage(hasImg);
956
+ submitEditedMessage(row, newText);
957
+ };
958
+
959
+ btnContainer.appendChild(cancelBtn);
960
+ btnContainer.appendChild(submitBtn);
961
+ editContainer.appendChild(textarea);
962
+ editContainer.appendChild(btnContainer);
963
+
964
+ row.innerHTML = '';
965
+ row.appendChild(editContainer);
966
+ }
967
+
968
+ function submitEditedMessage(row, newText) {
969
+ const timestamp = parseInt(row.getAttribute('data-timestamp'), 10);
970
+
971
+ const tx = db.transaction('messages', 'readwrite');
972
+ const store = tx.objectStore('messages');
973
+ const index = store.index('sessionId');
974
+ const request = index.getAll(currentSessionId);
975
+
976
+ request.onsuccess = () => {
977
+ const allMessages = request.result.sort((a, b) => a.timestamp - b.timestamp);
978
+ const editedMsgIndex = allMessages.findIndex(m => m.timestamp === timestamp);
979
+
980
+ if (editedMsgIndex === -1) {
981
+ row.remove();
982
+ proceedToSend([], newText, null);
983
+ return;
984
+ }
985
+
986
+ const editedMsg = allMessages[editedMsgIndex];
987
+ const imageToKeep = editedMsg.imageBase64;
988
+
989
+ const historyMessages = allMessages.slice(0, editedMsgIndex);
990
+ const messagesToDelete = allMessages.slice(editedMsgIndex);
991
+
992
+ messagesToDelete.forEach(m => store.delete(m.id));
993
+
994
+ const allRows = Array.from(chatArea.querySelectorAll('.message-row'));
995
+ const rowIndex = allRows.indexOf(row);
996
+ if(rowIndex !== -1) {
997
+ for(let i = rowIndex; i < allRows.length; i++) {
998
+ allRows[i].remove();
999
+ }
1000
+ }
1001
+
1002
+ proceedToSend(historyMessages, newText, imageToKeep);
1003
+ };
1004
+ }
1005
+ /* -------------------------------------- */
1006
+
1007
+ function openFilePicker() {
1008
+ document.getElementById('textInput').blur();
1009
+ const fileInput = document.getElementById('imageInput');
1010
+ fileInput.value = '';
1011
+ setTimeout(() => {
1012
+ fileInput.click();
1013
+ }, 50);
1014
+ }
1015
+
1016
+ function handleImageSelection(event) {
1017
+ const file = event.target.files[0];
1018
+ if (!file) return;
1019
+ const reader = new FileReader();
1020
+ reader.onload = function(e) {
1021
+ currentAttachedImageBase64 = e.target.result;
1022
+ document.getElementById('previewImage').src = currentAttachedImageBase64;
1023
+ document.getElementById('imagePreviewBox').style.display = 'block';
1024
+ };
1025
+ reader.readAsDataURL(file);
1026
+ }
1027
+
1028
+ function clearSelectedImage() {
1029
+ currentAttachedImageBase64 = null;
1030
+ document.getElementById('imageInput').value = '';
1031
+ document.getElementById('imagePreviewBox').style.display = 'none';
1032
+ document.getElementById('previewImage').src = '';
1033
+ }
1034
+
1035
+ const DB_NAME = 'AlphaChatDB_V2';
1036
+ const DB_VERSION = 1;
1037
+ let db;
1038
+ let currentSessionId = null;
1039
+ let sessionToDelete = null;
1040
+
1041
+ function initDB() {
1042
+ return new Promise((resolve, reject) => {
1043
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
1044
+ request.onupgradeneeded = (event) => {
1045
+ const database = event.target.result;
1046
+ if (!database.objectStoreNames.contains('sessions')) database.createObjectStore('sessions', { keyPath: 'id' });
1047
+ if (!database.objectStoreNames.contains('messages')) {
1048
+ const msgStore = database.createObjectStore('messages', { keyPath: 'id', autoIncrement: true });
1049
+ msgStore.createIndex('sessionId', 'sessionId', { unique: false });
1050
+ }
1051
+ };
1052
+ request.onsuccess = (event) => { db = event.target.result; resolve(); };
1053
+ request.onerror = (event) => reject();
1054
+ });
1055
+ }
1056
+
1057
+ async function createNewSession(title = "چت جدید") {
1058
+ currentSessionId = Date.now().toString();
1059
+ modelActiveSession[currentModel] = currentSessionId;
1060
+ const session = { id: currentSessionId, title: title, timestamp: Date.now(), model: currentModel };
1061
+ return new Promise((resolve) => {
1062
+ const tx = db.transaction('sessions', 'readwrite');
1063
+ tx.objectStore('sessions').add(session);
1064
+ tx.oncomplete = () => { loadSessionsToMenu(); resolve(currentSessionId); };
1065
+ });
1066
+ }
1067
+
1068
+ async function saveMessage(role, text, audioUrl = null, imageBase64 = null, timestamp = null) {
1069
+ let displayTitle = text ? text.substring(0, 20) + "..." : "تصویر ارسال شد";
1070
+ if (!currentSessionId) {
1071
+ await createNewSession(displayTitle);
1072
+ }
1073
+ const msgTime = timestamp || Date.now();
1074
+ const message = { sessionId: currentSessionId, role: role, text: text, audioUrl: audioUrl, imageBase64: imageBase64, timestamp: msgTime };
1075
+ const tx = db.transaction('messages', 'readwrite');
1076
+ tx.objectStore('messages').add(message);
1077
+ if (role === 'user') {
1078
+ const sessionTx = db.transaction('sessions', 'readwrite');
1079
+ const store = sessionTx.objectStore('sessions');
1080
+ const getReq = store.get(currentSessionId);
1081
+ getReq.onsuccess = () => {
1082
+ if (getReq.result && getReq.result.title === "چت جدید") {
1083
+ getReq.result.title = displayTitle;
1084
+ store.put(getReq.result);
1085
+ loadSessionsToMenu();
1086
+ }
1087
+ };
1088
+ }
1089
+ }
1090
+
1091
+ function loadSessionsToMenu() {
1092
+ const tx = db.transaction('sessions', 'readonly');
1093
+ const request = tx.objectStore('sessions').getAll();
1094
+ request.onsuccess = () => {
1095
+ const sessions = request.result.sort((a, b) => b.timestamp - a.timestamp);
1096
+ const list = document.getElementById('sessionList');
1097
+ list.innerHTML = '';
1098
+ sessions.forEach(s => {
1099
+ const li = document.createElement('li');
1100
+ li.className = `session-item ${s.id === currentSessionId ? 'active' : ''}`;
1101
+ li.onclick = (e) => {
1102
+ if (!e.target.closest('.dots-btn') && !e.target.closest('.popover-menu')) {
1103
+ const targetModel = s.model || 'gpt5';
1104
+ if (currentModel !== targetModel) {
1105
+ modelActiveSession[currentModel] = currentSessionId;
1106
+ updateModelUI(targetModel);
1107
+ }
1108
+ loadSession(s.id, true);
1109
+ }
1110
+ };
1111
+
1112
+ const isAudio = (s.model === 'audio');
1113
+ const modelBadge = isAudio ? '🎤 ' : '🤖 ';
1114
+
1115
+ li.innerHTML = `
1116
+ <div class="session-item-content">
1117
+ <img src="https://uploadkon.ir/uploads/d90813_26IMG-20260513-155052-256.jpg" style="width: 20px; height: 20px; flex-shrink: 0; border-radius: 4px; object-fit: cover;" alt="logo">
1118
+ <span class="session-title" style="direction:rtl; text-align:right;">${modelBadge}${s.title || 'بدون عنوان'}</span>
1119
+ </div>
1120
+ <button class="dots-btn" onclick="toggleDeleteMenu(event, '${s.id}')">
1121
+ <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="1.5"/><circle cx="12" cy="5" r="1.5"/><circle cx="12" cy="19" r="1.5"/></svg>
1122
+ </button>
1123
+ <div class="popover-menu" id="popover-${s.id}">
1124
+ <button class="popover-btn" onclick="openDeleteModal(event, '${s.id}')">
1125
+ <span>حذف</span>
1126
+ <svg viewBox="0 0 24 24"><path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
1127
+ </button>
1128
+ </div>
1129
+ `;
1130
+ list.appendChild(li);
1131
+ });
1132
+ };
1133
+ }
1134
+
1135
+ function loadSession(sessionId, fromSidebar = true) {
1136
+ currentSessionId = sessionId;
1137
+ modelActiveSession[currentModel] = sessionId;
1138
+ clearChatUI();
1139
+ if (fromSidebar) toggleSidebar();
1140
+ loadSessionsToMenu();
1141
+ const tx = db.transaction('messages', 'readonly');
1142
+ const request = tx.objectStore('messages').index('sessionId').getAll(sessionId);
1143
+ request.onsuccess = () => {
1144
+ const messages = request.result.sort((a, b) => a.timestamp - b.timestamp);
1145
+ if(messages.length === 0) {
1146
+ document.getElementById('emptyState').style.display = 'block';
1147
+ } else {
1148
+ document.getElementById('emptyState').style.display = 'none';
1149
+ messages.forEach(msg => {
1150
+ if(msg.role === 'user') appendUserMessageUI(msg.text, true, msg.imageBase64, msg.timestamp);
1151
+ else appendBotMessageUI(msg.text, msg.audioUrl, true);
1152
+ });
1153
+ setTimeout(() => {
1154
+ const rows = chatArea.querySelectorAll('.message-row');
1155
+ if (rows.length > 0) {
1156
+ const lastRow = rows[rows.length - 1];
1157
+ document.getElementById('dynamicSpacer').style.height = '0px';
1158
+ let targetPos = lastRow.offsetTop + lastRow.clientHeight - chatArea.clientHeight + 140;
1159
+ chatArea.scrollTo({ top: Math.max(0, targetPos), behavior: 'auto' });
1160
+ }
1161
+ }, 50);
1162
+ }
1163
+ };
1164
+ }
1165
+
1166
+ function startNewSession() {
1167
+ currentSessionId = null;
1168
+ modelActiveSession[currentModel] = null;
1169
+ clearChatUI();
1170
+ document.getElementById('emptyState').style.display = 'block';
1171
+ loadSessionsToMenu();
1172
+ }
1173
+
1174
+ function clearChatUI() {
1175
+ document.querySelectorAll('.message-row').forEach(m => m.remove());
1176
+ document.getElementById('emptyState').style.display = 'none';
1177
+ document.getElementById('dynamicSpacer').style.height = '0px';
1178
+ activeUserRow = null;
1179
+ if(!globalAudioPlayer.paused) globalAudioPlayer.pause();
1180
+
1181
+ activeSources.forEach(source => {
1182
+ try { source.stop(); } catch(e) {}
1183
+ });
1184
+ activeSources = [];
1185
+ nextAudioTime = 0;
1186
+ }
1187
+
1188
+ function toggleDeleteMenu(event, sessionId) {
1189
+ event.stopPropagation();
1190
+ document.querySelectorAll('.popover-menu').forEach(menu => {
1191
+ if (menu.id !== `popover-${sessionId}`) menu.classList.remove('show');
1192
+ });
1193
+ const popover = document.getElementById(`popover-${sessionId}`);
1194
+ popover.classList.toggle('show');
1195
+ }
1196
+
1197
+ document.addEventListener('click', (e) => {
1198
+ if (!e.target.closest('.dots-btn')) {
1199
+ document.querySelectorAll('.popover-menu').forEach(m => m.classList.remove('show'));
1200
+ }
1201
+ });
1202
+
1203
+ // --- مدیریت مودال‌ها ---
1204
+ function closeAllModals() {
1205
+ sessionToDelete = null;
1206
+ document.getElementById('modalOverlay').classList.remove('open');
1207
+ document.getElementById('deleteModal').classList.remove('open');
1208
+ document.getElementById('premiumModal').classList.remove('open');
1209
+ }
1210
+
1211
+ function openDeleteModal(event, sessionId) {
1212
+ event.stopPropagation();
1213
+ sessionToDelete = sessionId;
1214
+ document.getElementById('modalOverlay').classList.add('open');
1215
+ document.getElementById('deleteModal').classList.add('open');
1216
+ document.querySelectorAll('.popover-menu').forEach(m => m.classList.remove('show'));
1217
+ }
1218
+
1219
+ function openPremiumModal() {
1220
+ document.getElementById('modalOverlay').classList.add('open');
1221
+ document.getElementById('premiumModal').classList.add('open');
1222
+ }
1223
+ // -----------------------
1224
+
1225
+ function confirmDeleteSession() {
1226
+ if (!sessionToDelete) return;
1227
+ const tx = db.transaction(['sessions', 'messages'], 'readwrite');
1228
+ tx.objectStore('sessions').delete(sessionToDelete);
1229
+ const msgStore = tx.objectStore('messages');
1230
+ const index = msgStore.index('sessionId');
1231
+ const request = index.openCursor(IDBKeyRange.only(sessionToDelete));
1232
+ request.onsuccess = (e) => {
1233
+ const cursor = e.target.result;
1234
+ if (cursor) {
1235
+ cursor.delete();
1236
+ cursor.continue();
1237
+ }
1238
+ };
1239
+ tx.oncomplete = () => {
1240
+ closeAllModals();
1241
+ if (modelActiveSession.gpt5 === sessionToDelete) modelActiveSession.gpt5 = null;
1242
+ if (modelActiveSession.audio === sessionToDelete) modelActiveSession.audio = null;
1243
+
1244
+ if (currentSessionId === sessionToDelete) {
1245
+ startNewSession();
1246
+ } else {
1247
+ loadSessionsToMenu();
1248
+ }
1249
+ };
1250
+ }
1251
+
1252
+ const chatArea = document.getElementById('chatArea');
1253
+ const textInput = document.getElementById('textInput');
1254
+ const sendBtn = document.getElementById('sendBtn');
1255
+ const statusBar = document.getElementById('statusBar');
1256
+
1257
+ function toggleSidebar() {
1258
+ document.getElementById('sidebar').classList.toggle('open');
1259
+ document.getElementById('overlay').classList.toggle('visible');
1260
+ }
1261
+
1262
+ function appendUserMessageUI(text, isRestore = false, imageBase64 = null, timestamp = null) {
1263
+ document.getElementById('emptyState').style.display = 'none';
1264
+ const row = document.createElement('div');
1265
+ row.className = 'message-row user';
1266
+ if (timestamp) row.setAttribute('data-timestamp', timestamp);
1267
+
1268
+ let html = '<div class="message-bubble">';
1269
+ if (imageBase64) {
1270
+ let src = imageBase64;
1271
+ if (!src.startsWith('data:')) src = 'data:image/jpeg;base64,' + src;
1272
+ html += `<img src="${src}" style="max-width: 100%; border-radius: 8px; margin-bottom: 8px; display: block;">`;
1273
+ }
1274
+ if (text) {
1275
+ html += text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1276
+ }
1277
+ html += '</div>';
1278
+
1279
+ html += `<div class="action-buttons">`;
1280
+ html += `<button class="action-btn" title="ویرایش" onclick="editUserMessage(this)">${EDIT_ICON_SVG}</button>`;
1281
+ html += `<button class="action-btn" title="کپی" onclick="copyMessageText(this)">${COPY_ICON_SVG}</button>`;
1282
+ html += `</div>`;
1283
+
1284
+ row.innerHTML = html;
1285
+ chatArea.insertBefore(row, document.getElementById('dynamicSpacer'));
1286
+ if (!isRestore) {
1287
+ activeUserRow = row;
1288
+ setTimeout(() => {
1289
+ const visibleSpace = chatArea.clientHeight - 130;
1290
+ let requiredSpacer = visibleSpace - row.offsetHeight - 20;
1291
+ if (requiredSpacer < 0) requiredSpacer = 0;
1292
+ document.getElementById('dynamicSpacer').style.height = requiredSpacer + 'px';
1293
+ const targetScrollPosition = row.offsetTop - 20;
1294
+ chatArea.scrollTo({ top: targetScrollPosition, behavior: 'smooth' });
1295
+ }, 150);
1296
+ }
1297
+ }
1298
+
1299
+ function appendBotMessageUI(text, audioUrl = null, isRestore = false) {
1300
+ document.getElementById('emptyState').style.display = 'none';
1301
+ const row = document.createElement('div');
1302
+ row.className = 'message-row bot';
1303
+ let html = `<div class="message-bubble">${formatMessageText(text)}</div><div class="action-buttons">`;
1304
+ html += `<button class="action-btn" title="کپی" onclick="copyMessageText(this)">${COPY_ICON_SVG}</button>`;
1305
+ if (audioUrl) {
1306
+ html += `<button class="action-btn play-btn" title="پخش صدا" onclick="toggleAudioReplay('${audioUrl}', this)">${PLAY_ICON_SVG}</button>`;
1307
+ }
1308
+ html += `</div>`;
1309
+ row.innerHTML = html;
1310
+ chatArea.insertBefore(row, document.getElementById('dynamicSpacer'));
1311
+ }
1312
+
1313
+ let currentBotRow = null;
1314
+ let currentBotBubble = null;
1315
+ let currentBotText = "";
1316
+ let audioContext;
1317
+ let nextAudioTime = 0;
1318
+ let activeSources = [];
1319
+
1320
+ function initAudioContext() {
1321
+ if (!audioContext) audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 24000 });
1322
+ if (audioContext.state === 'suspended') audioContext.resume();
1323
+ }
1324
+
1325
+ function playStreamingAudio(base64Data) {
1326
+ if (!base64Data || isMuted) return;
1327
+ const binaryString = atob(base64Data);
1328
+ const len = binaryString.length;
1329
+ const bytes = new Uint8Array(len);
1330
+ for (let i = 0; i < len; i++) bytes[i] = binaryString.charCodeAt(i);
1331
+ const int16Array = new Int16Array(bytes.buffer);
1332
+ const float32Array = new Float32Array(int16Array.length);
1333
+ for (let i = 0; i < int16Array.length; i++) float32Array[i] = int16Array[i] / 32768.0;
1334
+ const audioBuffer = audioContext.createBuffer(1, float32Array.length, 24000);
1335
+ audioBuffer.getChannelData(0).set(float32Array);
1336
+ const source = audioContext.createBufferSource();
1337
+ source.buffer = audioBuffer;
1338
+ source.connect(audioContext.destination);
1339
+
1340
+ source.onended = () => {
1341
+ const index = activeSources.indexOf(source);
1342
+ if (index > -1) activeSources.splice(index, 1);
1343
+ };
1344
+ activeSources.push(source);
1345
+
1346
+ // استفاده از کمترین بافر (0.1) برای پخش روان و کاملا Real-time
1347
+ if (nextAudioTime < audioContext.currentTime) {
1348
+ nextAudioTime = audioContext.currentTime + 0.1;
1349
+ }
1350
+
1351
+ source.start(nextAudioTime);
1352
+ nextAudioTime += audioBuffer.duration;
1353
+ }
1354
+
1355
+ function updateDynamicSpacer() {
1356
+ if (!activeUserRow || !currentBotRow) return;
1357
+ const turnHeight = activeUserRow.offsetHeight + 20 + currentBotRow.offsetHeight;
1358
+ const visibleSpace = chatArea.clientHeight - 130;
1359
+ const spacer = document.getElementById('dynamicSpacer');
1360
+ if (turnHeight < visibleSpace) {
1361
+ spacer.style.height = (visibleSpace - turnHeight) + 'px';
1362
+ } else {
1363
+ spacer.style.height = '0px';
1364
+ }
1365
+ }
1366
+
1367
+ async function proceedToSend(messages, text, base64Image) {
1368
+ let textHistoryStr = "";
1369
+ messages.sort((a, b) => a.timestamp - b.timestamp).forEach(msg => {
1370
+ if (msg.role === 'user' && msg.text) {
1371
+ textHistoryStr += "کاربر: " + msg.text + "\n";
1372
+ } else if (msg.role === 'bot' && msg.text) {
1373
+ textHistoryStr += "هوش مصنوعی: " + msg.text + "\n";
1374
+ }
1375
+ });
1376
+
1377
+ const msgTimestamp = Date.now();
1378
+ appendUserMessageUI(text, false, base64Image, msgTimestamp);
1379
+ await saveMessage('user', text, null, base64Image, msgTimestamp);
1380
+
1381
+ isGenerating = true;
1382
+ currentAbortController = new AbortController();
1383
+
1384
+ // تغییر شکل دکمه به حالت توقف (مربع با رنگ آبی بنفش)
1385
+ sendBtn.innerHTML = `<svg viewBox="0 0 24 24"><rect x="7" y="7" width="10" height="10" fill="white" stroke="none" rx="2" ry="2"/></svg>`;
1386
+ sendBtn.style.backgroundColor = "#5a67d8";
1387
+
1388
+ statusBar.textContent = "در حال پردازش...";
1389
+ currentBotRow = document.createElement('div');
1390
+ currentBotRow.className = 'message-row bot';
1391
+ currentBotBubble = document.createElement('div');
1392
+ currentBotBubble.className = 'message-bubble';
1393
+
1394
+ // نمایش انیمیشن سه نقطه متحرک بلافاصله پس از ارسال
1395
+ currentBotBubble.innerHTML = `
1396
+ <div class="typing-indicator" id="typingIndicator">
1397
+ <span class="dot"></span>
1398
+ <span class="dot"></span>
1399
+ <span class="dot"></span>
1400
+ </div>
1401
+ `;
1402
+
1403
+ currentBotText = "";
1404
+ currentBotRow.appendChild(currentBotBubble);
1405
+ chatArea.insertBefore(currentBotRow, document.getElementById('dynamicSpacer'));
1406
+
1407
+ try {
1408
+ const pureBase64 = base64Image ? base64Image.split(',')[1] : null;
1409
+ const response = await fetch('/api/chat_proxy', {
1410
+ method: 'POST',
1411
+ headers: { 'Content-Type': 'application/json' },
1412
+ signal: currentAbortController.signal,
1413
+ body: JSON.stringify({
1414
+ type: 'text', content: text, model: currentModel,
1415
+ text_history: textHistoryStr, image_base64: pureBase64,
1416
+ session_id: currentSessionId
1417
+ })
1418
+ });
1419
+
1420
+ const reader = response.body.getReader();
1421
+ const decoder = new TextDecoder("utf-8");
1422
+ let streamBuffer = ""; // بافر دریافت پیام‌ها برای اینترنت ملی (جلوگیری از نصفه رسیدن JSON)
1423
+
1424
+ while (true) {
1425
+ const { done, value } = await reader.read();
1426
+ if (done) {
1427
+ if (streamBuffer.trim()) {
1428
+ try {
1429
+ const data = JSON.parse(streamBuffer);
1430
+ if (data.status === "streaming") {
1431
+ if (data.text) {
1432
+ currentBotText += data.text;
1433
+ currentBotBubble.innerHTML = formatMessageText(currentBotText);
1434
+ }
1435
+ if (data.audio) playStreamingAudio(data.audio);
1436
+ }
1437
+ } catch(e) {}
1438
+ }
1439
+ break;
1440
+ }
1441
+
1442
+ streamBuffer += decoder.decode(value, { stream: true });
1443
+ const lines = streamBuffer.split('\n');
1444
+ streamBuffer = lines.pop(); // ذخیره خط آخر (که معمولا ناقص است) برای دور بعدی
1445
+
1446
+ for (let line of lines) {
1447
+ if (!line.trim()) continue;
1448
+ try {
1449
+ const data = JSON.parse(line);
1450
+ if (data.status === "streaming") {
1451
+ if (data.text) {
1452
+ currentBotText += data.text;
1453
+ // با جایگذاری این متن، انیمیشن به‌طور خودکار پاک می‌شود
1454
+ currentBotBubble.innerHTML = formatMessageText(currentBotText);
1455
+ updateDynamicSpacer();
1456
+
1457
+ const isAtBottom = chatArea.scrollHeight - chatArea.scrollTop - chatArea.clientHeight < 100;
1458
+
1459
+ if (isAtBottom) {
1460
+ const rect = currentBotRow.getBoundingClientRect();
1461
+ const chatRect = chatArea.getBoundingClientRect();
1462
+ let diff = rect.bottom - (chatRect.bottom - 130);
1463
+ if (diff > 0) {
1464
+ chatArea.scrollBy({ top: diff, behavior: 'auto' });
1465
+ }
1466
+ }
1467
+ }
1468
+ if (data.audio) playStreamingAudio(data.audio);
1469
+ }
1470
+ else if (data.status === "success" || data.status === "error") {
1471
+ statusBar.textContent = "";
1472
+ if (data.status === "error") {
1473
+ currentBotText = data.message || "خطا در برقراری ارتباط";
1474
+ if (currentModel === 'audio') forceNewSession = true;
1475
+ }
1476
+ let finalHtml = `<div class="message-bubble">${formatMessageText(currentBotText)}</div><div class="action-buttons">`;
1477
+ finalHtml += `<button class="action-btn" title="کپی" onclick="copyMessageText(this)">${COPY_ICON_SVG}</button>`;
1478
+ if (data.audio_url) {
1479
+ finalHtml += `<button class="action-btn play-btn" title="پخش صدا" onclick="toggleAudioReplay('${data.audio_url}', this)">${PLAY_ICON_SVG}</button>`;
1480
+ }
1481
+ finalHtml += `</div>`;
1482
+ currentBotRow.innerHTML = finalHtml;
1483
+ updateDynamicSpacer();
1484
+ if (data.status === "success") {
1485
+ saveMessage('bot', currentBotText, data.audio_url, null, Date.now());
1486
+ }
1487
+
1488
+ if (chatArea.scrollHeight - chatArea.scrollTop - chatArea.clientHeight < 150) {
1489
+ const rect = currentBotRow.getBoundingClientRect();
1490
+ const chatRect = chatArea.getBoundingClientRect();
1491
+ let diff = rect.bottom - (chatRect.bottom - 130);
1492
+ if (diff > 0) chatArea.scrollBy({ top: diff + 20, behavior: 'smooth' });
1493
+ }
1494
+ currentBotRow = null; currentBotBubble = null; currentBotText = "";
1495
+ }
1496
+ } catch(e) {}
1497
+ }
1498
+ }
1499
+ } catch (error) {
1500
+ if (error.name === 'AbortError') {
1501
+ statusBar.textContent = "تولید پیام متوقف شد";
1502
+
1503
+ if (!globalAudioPlayer.paused) globalAudioPlayer.pause();
1504
+ activeSources.forEach(source => { try { source.stop(); } catch(e) {} });
1505
+ activeSources = [];
1506
+ nextAudioTime = 0;
1507
+
1508
+ if (currentBotRow) {
1509
+ let finalHtml = `<div class="message-bubble">${formatMessageText(currentBotText)}</div><div class="action-buttons">`;
1510
+ finalHtml += `<button class="action-btn" title="کپی" onclick="copyMessageText(this)">${COPY_ICON_SVG}</button>`;
1511
+ finalHtml += `</div>`;
1512
+ currentBotRow.innerHTML = finalHtml;
1513
+ updateDynamicSpacer();
1514
+ if (currentBotText.trim() !== "") {
1515
+ saveMessage('bot', currentBotText, null, null, Date.now());
1516
+ }
1517
+ currentBotRow = null; currentBotBubble = null; currentBotText = "";
1518
+ }
1519
+ } else {
1520
+ statusBar.textContent = "خطا در ارتباط با سرور";
1521
+ if (currentModel === 'audio') forceNewSession = true;
1522
+ }
1523
+ } finally {
1524
+ isGenerating = false;
1525
+ currentAbortController = null;
1526
+ // بازگرداندن دکمه به حالت ارسال عادی
1527
+ sendBtn.innerHTML = `<svg viewBox="0 0 24 24"><path d="M12 19V5M5 12l7-7 7 7"/></svg>`;
1528
+ sendBtn.style.backgroundColor = "";
1529
+ }
1530
+ }
1531
+
1532
+ sendBtn.addEventListener('click', () => {
1533
+ // اگر در حال تولید است، درخواست متوقف شود
1534
+ if (isGenerating) {
1535
+ if (currentAbortController) {
1536
+ currentAbortController.abort();
1537
+ }
1538
+ return;
1539
+ }
1540
+
1541
+ initAudioContext();
1542
+ if (forceNewSession) {
1543
+ startNewSession();
1544
+ forceNewSession = false;
1545
+ }
1546
+
1547
+ const text = textInput.value.trim();
1548
+ const imageToSend = currentAttachedImageBase64;
1549
+ const hasImage = !!imageToSend;
1550
+
1551
+ // اگر کادری خالی بود کلا کاری نکند
1552
+ if (!text && !imageToSend) return;
1553
+
1554
+ // بررسی اینکه آیا کاربر مجاز به ارس��ل است یا خیر (مدیریت پولی/رایگان)
1555
+ if (!checkCanSend(hasImage)) {
1556
+ return;
1557
+ }
1558
+
1559
+ // اگر مجاز بود، یک واحد از محدودیت‌هایش کم کن
1560
+ incrementUsage(hasImage);
1561
+
1562
+ textInput.value = '';
1563
+ clearSelectedImage();
1564
+
1565
+ if (!currentSessionId) {
1566
+ proceedToSend([], text, imageToSend);
1567
+ } else {
1568
+ const tx = db.transaction('messages', 'readonly');
1569
+ const request = tx.objectStore('messages').index('sessionId').getAll(currentSessionId);
1570
+ request.onsuccess = () => {
1571
+ proceedToSend(request.result || [], text, imageToSend);
1572
+ };
1573
+ }
1574
+ });
1575
+
1576
+ textInput.addEventListener('keypress', (e) => {
1577
+ if (e.key === 'Enter') sendBtn.click();
1578
+ });
1579
+
1580
+ window.onload = async () => {
1581
+ document.getElementById('emptyState').style.display = 'block';
1582
+
1583
+ // بازیابی تم ذخیره شده
1584
+ const savedTheme = localStorage.getItem('appTheme');
1585
+ if (savedTheme === 'dark') {
1586
+ document.getElementById('themeCheckbox').checked = true;
1587
+ document.body.classList.add('dark-theme');
1588
+ }
1589
+
1590
+ // درخواست اطلاعات کاربر (برای تشخیص پولی/رایگان بودن) از سمت اندروید
1591
+ parent.postMessage({ type: 'REQUEST_USER_DATA' }, '*');
1592
+
1593
+ await initDB();
1594
+ loadSessionsToMenu();
1595
+ };
1596
+ </script>
1597
+
1598
+ </body>
1599
+ </html>