Delete static
Browse files- static/css/style.css +0 -1756
- static/js/api.js +0 -218
- static/js/db.js +0 -153
- static/js/main.js +0 -649
- static/js/state.js +0 -204
- static/js/ui/chat.js +0 -792
- static/js/ui/dom.js +0 -92
- static/js/ui/modals.js +0 -428
- static/js/ui/tools.js +0 -181
- static/js/ui/tts.js +0 -162
static/css/style.css
DELETED
|
@@ -1,1756 +0,0 @@
|
|
| 1 |
-
/* --- START OF FILE Chatalpha-main/static/css/style.css --- */
|
| 2 |
-
|
| 3 |
-
html, body {
|
| 4 |
-
height: 100%;
|
| 5 |
-
overflow: hidden;
|
| 6 |
-
}
|
| 7 |
-
body { font-family: 'Vazirmatn', 'Inter', sans-serif; -webkit-tap-highlight-color: transparent; }
|
| 8 |
-
.custom-scrollbar::-webkit-scrollbar { width: 8px; height: 8px; }
|
| 9 |
-
.custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
|
| 10 |
-
.custom-scrollbar::-webkit-scrollbar-thumb { background-color: rgba(209, 213, 219, 0.7); border-radius: 10px; }
|
| 11 |
-
.dark .custom-scrollbar::-webkit-scrollbar-thumb { background-color: rgba(107, 114, 128, 0.5); }
|
| 12 |
-
.hidden { display: none !important; }
|
| 13 |
-
#history-sidebar, #confirm-modal-content, #rename-modal-content, #edit-modal-content, #html-preview-content, #settings-modal-content { transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1); }
|
| 14 |
-
#sidebar-overlay, #confirm-modal-overlay, #rename-modal-overlay, #edit-modal-overlay, #html-preview-overlay, #settings-modal #settings-modal-overlay { transition: opacity 300ms ease-in-out; }
|
| 15 |
-
#modal-content { transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1); }
|
| 16 |
-
|
| 17 |
-
@keyframes fade-slide-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
| 18 |
-
.message-entry { animation: fade-slide-in 300ms ease-out forwards; }
|
| 19 |
-
.typing-indicator span { height: 8px; width: 8px; background-color: #9ca3af; border-radius: 50%; display: inline-block; animation: typing-blink 1.4s infinite both; }
|
| 20 |
-
.dark .typing-indicator span { background-color: #6b7280; }
|
| 21 |
-
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
|
| 22 |
-
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
|
| 23 |
-
@keyframes typing-blink { 0% { opacity: 0.2; } 20% { opacity: 1; } 100% { opacity: 0.2; } }
|
| 24 |
-
|
| 25 |
-
/* === استایل بلوک کد === */
|
| 26 |
-
.prose pre {
|
| 27 |
-
position: relative;
|
| 28 |
-
background-color: #282c34;
|
| 29 |
-
border-radius: 0.75rem;
|
| 30 |
-
padding-top: 2.8rem;
|
| 31 |
-
overflow-x: auto;
|
| 32 |
-
margin-top: 1em;
|
| 33 |
-
margin-bottom: 1em;
|
| 34 |
-
direction: ltr;
|
| 35 |
-
text-align: left;
|
| 36 |
-
}
|
| 37 |
-
.prose pre code {
|
| 38 |
-
white-space: pre;
|
| 39 |
-
word-break: normal;
|
| 40 |
-
display: block;
|
| 41 |
-
padding: 0.75rem 1rem;
|
| 42 |
-
direction: ltr;
|
| 43 |
-
text-align: left;
|
| 44 |
-
}
|
| 45 |
-
.code-button-container {
|
| 46 |
-
position: absolute;
|
| 47 |
-
top: 0.75rem;
|
| 48 |
-
right: 0.75rem;
|
| 49 |
-
display: flex;
|
| 50 |
-
gap: 0.5rem;
|
| 51 |
-
opacity: 0;
|
| 52 |
-
transition: opacity 0.2s;
|
| 53 |
-
}
|
| 54 |
-
.prose pre:hover .code-button-container {
|
| 55 |
-
opacity: 1;
|
| 56 |
-
}
|
| 57 |
-
.code-button {
|
| 58 |
-
padding: 0.25rem 0.6rem;
|
| 59 |
-
background-color: rgba(75, 85, 99, 0.8);
|
| 60 |
-
color: white;
|
| 61 |
-
border: none;
|
| 62 |
-
border-radius: 0.375rem;
|
| 63 |
-
font-size: 0.8rem;
|
| 64 |
-
font-family: 'Vazirmatn', sans-serif;
|
| 65 |
-
cursor: pointer;
|
| 66 |
-
display: flex;
|
| 67 |
-
align-items: center;
|
| 68 |
-
gap: 0.3rem;
|
| 69 |
-
transition: background-color 0.2s;
|
| 70 |
-
}
|
| 71 |
-
.code-button:hover { background-color: rgba(107, 114, 128, 0.8); }
|
| 72 |
-
.code-button:active { background-color: #4b5563; }
|
| 73 |
-
|
| 74 |
-
/* === استایل دکمههای اکشن پیام === */
|
| 75 |
-
.message-actions { opacity: 0; transition: opacity 0.3s ease-in-out; position: absolute; bottom: -1rem; left: 0; display: flex; align-items: center; }
|
| 76 |
-
.group:hover .message-actions { opacity: 1; }
|
| 77 |
-
.action-button { padding: 0.375rem; border-radius: 9999px; background-color: rgba(255, 255, 255, 0.7); backdrop-filter: blur(4px); border: 1px solid #e5e7eb; color: #6b7280; transition: all 0.2s; box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); }
|
| 78 |
-
.dark .action-button { background-color: rgba(55, 65, 81, 0.7); border-color: #4b5563; color: #9ca3af; }
|
| 79 |
-
.action-button:hover { color: #1f2937; background-color: rgba(243, 244, 246, 0.9); border-color: #d1d5db; }
|
| 80 |
-
.dark .action-button:hover { color: #e5e7eb; background-color: rgba(75, 85, 99, 0.9); border-color: #6b7280; }
|
| 81 |
-
.action-button.active { color: #3b82f6; border-color: #93c5fd; background-color: #eff6ff; }
|
| 82 |
-
.dark .action-button.active { color: #60a5fa; border-color: #3b82f6; background-color: #1e3a8a; }
|
| 83 |
-
.like-animation { animation: like-pop 0.4s ease-in-out; }
|
| 84 |
-
@keyframes like-pop { 0% { transform: scale(1); } 50% { transform: scale(1.4); } 100% { transform: scale(1); } }
|
| 85 |
-
.action-button .copy-feedback {
|
| 86 |
-
position: absolute;
|
| 87 |
-
left: calc(100% + 8px);
|
| 88 |
-
top: 50%;
|
| 89 |
-
transform: translateY(-50%);
|
| 90 |
-
background-color: #1f2937;
|
| 91 |
-
color: white;
|
| 92 |
-
padding: 4px 8px;
|
| 93 |
-
border-radius: 6px;
|
| 94 |
-
font-size: 12px;
|
| 95 |
-
white-space: nowrap;
|
| 96 |
-
opacity: 0;
|
| 97 |
-
transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
|
| 98 |
-
pointer-events: none;
|
| 99 |
-
}
|
| 100 |
-
.action-button .copy-feedback.visible {
|
| 101 |
-
opacity: 1;
|
| 102 |
-
transform: translateY(-50%) translateX(-4px);
|
| 103 |
-
}
|
| 104 |
-
|
| 105 |
-
/* === بخش اصلاح شده برای دکمه سه نقطه و منو === */
|
| 106 |
-
|
| 107 |
-
/* 1. اطمینان از کلیکخور بودن دکمه سه نقطه */
|
| 108 |
-
.history-item-button {
|
| 109 |
-
opacity: 1;
|
| 110 |
-
position: relative; /* برای کار کردن z-index */
|
| 111 |
-
z-index: 50; /* بالاتر از متن آیتم */
|
| 112 |
-
cursor: pointer;
|
| 113 |
-
padding: 8px; /* فضای کلیک بیشتر */
|
| 114 |
-
margin: -8px; /* جبران فضای اضافه شده */
|
| 115 |
-
display: flex;
|
| 116 |
-
align-items: center;
|
| 117 |
-
justify-content: center;
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
/* 2. حذف استایلهای مزاحم لیست */
|
| 121 |
-
#history-list, #history-list li {
|
| 122 |
-
list-style-type: none !important;
|
| 123 |
-
padding: 0;
|
| 124 |
-
margin: 0;
|
| 125 |
-
}
|
| 126 |
-
|
| 127 |
-
/* 3. تنظیمات جدید و قدرتمند برای منوی بازشونده */
|
| 128 |
-
#history-item-menu {
|
| 129 |
-
position: fixed !important; /* حتما fixed باشد */
|
| 130 |
-
top: 0; left: 0; /* پیشفرض */
|
| 131 |
-
z-index: 2147483647 !important; /* بالاترین لایه ممکن در مرورگر */
|
| 132 |
-
transform-origin: top right;
|
| 133 |
-
transform: scale(0.95);
|
| 134 |
-
opacity: 0;
|
| 135 |
-
visibility: hidden; /* مخفی کردن کامل */
|
| 136 |
-
transition: transform 150ms ease-out, opacity 150ms ease-out, visibility 0s linear 150ms; /* تاخیر در visibility هنگام بسته شدن */
|
| 137 |
-
background-color: white;
|
| 138 |
-
border-radius: 0.75rem;
|
| 139 |
-
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
| 140 |
-
}
|
| 141 |
-
.dark #history-item-menu {
|
| 142 |
-
background-color: #1f2937;
|
| 143 |
-
border: 1px solid #374151;
|
| 144 |
-
}
|
| 145 |
-
|
| 146 |
-
/* کلاس نمایش منو */
|
| 147 |
-
#history-item-menu.visible {
|
| 148 |
-
transform: scale(1);
|
| 149 |
-
opacity: 1;
|
| 150 |
-
visibility: visible !important; /* نمایش فوری */
|
| 151 |
-
pointer-events: auto;
|
| 152 |
-
transition: transform 150ms ease-out, opacity 150ms ease-out, visibility 0s; /* بدون تاخیر هنگام باز شدن */
|
| 153 |
-
}
|
| 154 |
-
|
| 155 |
-
#message-item-menu { transition: opacity 200ms ease-in-out; }
|
| 156 |
-
#message-item-menu-content { transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1); transform: scale(0.95); opacity: 0; }
|
| 157 |
-
#message-item-menu.visible { opacity: 1; }
|
| 158 |
-
#message-item-menu.visible #message-item-menu-content { transform: scale(1); opacity: 1; }
|
| 159 |
-
.message-preview-container { padding: 0.75rem 1rem; border-bottom: 1px solid #e5e7eb; margin-bottom: 0.5rem; }
|
| 160 |
-
.dark .message-preview-container { border-bottom-color: #374151; }
|
| 161 |
-
.message-preview-text { font-size: 0.875rem; color: #6b7280; line-height: 1.5; max-height: 4.5rem; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; text-overflow: ellipsis; }
|
| 162 |
-
.dark .message-preview-text { color: #d1d5db; }
|
| 163 |
-
|
| 164 |
-
#confirm-modal-content, #rename-modal-content, #edit-modal-content, #html-preview-content { transform: scale(0.95); opacity: 0; }
|
| 165 |
-
|
| 166 |
-
#message-input, #edit-input { resize: none; overflow-y: auto; max-height: 120px; line-height: 1.5; padding-top: 10px; padding-bottom: 10px; }
|
| 167 |
-
|
| 168 |
-
#app-container { display: flex; flex-direction: column; }
|
| 169 |
-
#main-header, #main-footer { position: static; flex-shrink: 0; }
|
| 170 |
-
#chat-window { flex-grow: 1; }
|
| 171 |
-
|
| 172 |
-
#html-preview-modal { position: fixed; inset: 0; background-color: rgba(0, 0, 0, 0.6); backdrop-filter: blur(5px); display: flex; align-items: center; justify-content: center; padding: 1rem; z-index: 60; }
|
| 173 |
-
#html-preview-content { background-color: #fff; border-radius: 1rem; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); width: 100%; height: 90%; max-width: 800px; display: flex; flex-direction: column; overflow: hidden; transform: scale(0.95); opacity: 0; }
|
| 174 |
-
#html-preview-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid #e2e8f0; background-color: #f8fafc; }
|
| 175 |
-
.dark #html-preview-header { border-bottom-color: #374151; background-color: #1f2937; }
|
| 176 |
-
#html-preview-iframe-container { flex-grow: 1; overflow: hidden; }
|
| 177 |
-
#html-preview-iframe { width: 100%; height: 100%; border: none; background-color: #fff; }
|
| 178 |
-
|
| 179 |
-
.message-entry p.whitespace-pre-wrap {
|
| 180 |
-
white-space: pre-wrap;
|
| 181 |
-
overflow-wrap: break-word;
|
| 182 |
-
word-break: break-word;
|
| 183 |
-
}
|
| 184 |
-
|
| 185 |
-
.prose {
|
| 186 |
-
overflow-wrap: break-word;
|
| 187 |
-
word-break: break-word;
|
| 188 |
-
}
|
| 189 |
-
.dark .prose { --tw-prose-body: #d1d5db; --tw-prose-headings: #fff; --tw-prose-lead: #9ca3af; --tw-prose-links: #fff; --tw-prose-bold: #fff; --tw-prose-counters: #9ca3af; --tw-prose-bullets: #4b5563; --tw-prose-hr: #374151; --tw-prose-quotes: #f3f4f6; --tw-prose-quote-borders: #374151; --tw-prose-captions: #9ca3af; --tw-prose-code: #fff; --tw-prose-pre-code: #d1d5db; --tw-prose-pre-bg: #1f2937; --tw-prose-th-borders: #4b5563; --tw-prose-td-borders: #374151; }
|
| 190 |
-
|
| 191 |
-
.prose table {
|
| 192 |
-
width: 100%;
|
| 193 |
-
border-collapse: collapse;
|
| 194 |
-
margin-top: 1em;
|
| 195 |
-
margin-bottom: 1em;
|
| 196 |
-
display: block;
|
| 197 |
-
overflow-x: auto;
|
| 198 |
-
white-space: nowrap;
|
| 199 |
-
-webkit-overflow-scrolling: touch;
|
| 200 |
-
min-width: 100%;
|
| 201 |
-
table-layout: auto;
|
| 202 |
-
}
|
| 203 |
-
|
| 204 |
-
.prose th,
|
| 205 |
-
.prose td {
|
| 206 |
-
border: 1px solid #e2e8f0;
|
| 207 |
-
padding: 0.75em 0.5em;
|
| 208 |
-
text-align: right;
|
| 209 |
-
vertical-align: top;
|
| 210 |
-
white-space: normal;
|
| 211 |
-
overflow-wrap: break-word;
|
| 212 |
-
word-break: normal;
|
| 213 |
-
}
|
| 214 |
-
.dark .prose th, .dark .prose td { border-color: #4b5563; }
|
| 215 |
-
|
| 216 |
-
.prose th {
|
| 217 |
-
background-color: #f8fafc;
|
| 218 |
-
font-weight: 600;
|
| 219 |
-
color: #334155;
|
| 220 |
-
}
|
| 221 |
-
.dark .prose th { background-color: #374151; color: #d1d5db; }
|
| 222 |
-
|
| 223 |
-
.prose tr:nth-child(even) {
|
| 224 |
-
background-color: #fcfdfe;
|
| 225 |
-
}
|
| 226 |
-
.dark .prose tr:nth-child(even) { background-color: #1f2937; }
|
| 227 |
-
|
| 228 |
-
@media (max-width: 768px) {
|
| 229 |
-
.prose th,
|
| 230 |
-
.prose td {
|
| 231 |
-
font-size: 0.85rem;
|
| 232 |
-
padding: 0.6em 0.4em;
|
| 233 |
-
}
|
| 234 |
-
}
|
| 235 |
-
|
| 236 |
-
#image-preview-container {
|
| 237 |
-
display: flex;
|
| 238 |
-
align-items: center;
|
| 239 |
-
gap: 0.5rem;
|
| 240 |
-
padding: 0.5rem;
|
| 241 |
-
width: fit-content;
|
| 242 |
-
}
|
| 243 |
-
#image-preview-container.hidden { display: none !important; }
|
| 244 |
-
|
| 245 |
-
#file-info-text {
|
| 246 |
-
font-size: 0.875rem;
|
| 247 |
-
color: #475569;
|
| 248 |
-
max-width: 150px;
|
| 249 |
-
white-space: nowrap;
|
| 250 |
-
overflow: hidden;
|
| 251 |
-
text-overflow: ellipsis;
|
| 252 |
-
}
|
| 253 |
-
.dark #file-info-text {
|
| 254 |
-
color: #cbd5e1;
|
| 255 |
-
}
|
| 256 |
-
|
| 257 |
-
/* === استایلهای صفحه خوشآمدگویی === */
|
| 258 |
-
.welcome-screen {
|
| 259 |
-
position: absolute;
|
| 260 |
-
top: 0;
|
| 261 |
-
left: 0;
|
| 262 |
-
right: 0;
|
| 263 |
-
bottom: 0;
|
| 264 |
-
display: flex;
|
| 265 |
-
flex-direction: column;
|
| 266 |
-
justify-content: center;
|
| 267 |
-
align-items: center;
|
| 268 |
-
padding: 40px 20px;
|
| 269 |
-
overflow-x: hidden;
|
| 270 |
-
box-sizing: border-box;
|
| 271 |
-
}
|
| 272 |
-
|
| 273 |
-
.welcome-container {
|
| 274 |
-
max-width: 500px;
|
| 275 |
-
width: 100%;
|
| 276 |
-
text-align: center;
|
| 277 |
-
}
|
| 278 |
-
|
| 279 |
-
.chatbot-name {
|
| 280 |
-
font-size: 1.8rem;
|
| 281 |
-
font-weight: 700;
|
| 282 |
-
margin-bottom: 0.5rem;
|
| 283 |
-
background: linear-gradient(45deg, #0ea5e9, #6f42c1, #fd7e14);
|
| 284 |
-
background-size: 200% auto;
|
| 285 |
-
-webkit-background-clip: text;
|
| 286 |
-
-webkit-text-fill-color: transparent;
|
| 287 |
-
background-clip: text;
|
| 288 |
-
color: transparent;
|
| 289 |
-
display: inline-block;
|
| 290 |
-
line-height: 1.2;
|
| 291 |
-
opacity: 0;
|
| 292 |
-
transition: opacity 0.5s ease-in-out;
|
| 293 |
-
animation: gradient-flow 8s linear infinite alternate;
|
| 294 |
-
}
|
| 295 |
-
|
| 296 |
-
@keyframes gradient-flow {
|
| 297 |
-
0% { background-position: 0% 50%; }
|
| 298 |
-
50% { background-position: 100% 50%; }
|
| 299 |
-
100% { background-position: 0% 50%; }
|
| 300 |
-
}
|
| 301 |
-
|
| 302 |
-
.main-title {
|
| 303 |
-
font-size: 1.8rem;
|
| 304 |
-
font-weight: 600;
|
| 305 |
-
margin-bottom: 2rem;
|
| 306 |
-
color: #2c3e50;
|
| 307 |
-
line-height: 1.5;
|
| 308 |
-
opacity: 0;
|
| 309 |
-
transition: opacity 0.8s ease-in;
|
| 310 |
-
}
|
| 311 |
-
.dark .main-title { color: #e5e7eb; }
|
| 312 |
-
|
| 313 |
-
/* === استایلهای جدید برای دکمههای پیشنهادی (بدون کارت پسزمینه) === */
|
| 314 |
-
.suggestions-container {
|
| 315 |
-
display: grid;
|
| 316 |
-
grid-template-columns: repeat(2, 1fr);
|
| 317 |
-
gap: 16px;
|
| 318 |
-
margin-top: 2rem;
|
| 319 |
-
opacity: 0;
|
| 320 |
-
transform: translateY(20px);
|
| 321 |
-
transition: opacity 0.5s ease-out, transform 0.5s ease-out;
|
| 322 |
-
}
|
| 323 |
-
|
| 324 |
-
.suggestion-button {
|
| 325 |
-
display: flex;
|
| 326 |
-
flex-direction: row-reverse;
|
| 327 |
-
align-items: center;
|
| 328 |
-
justify-content: center;
|
| 329 |
-
background-color: #ffffff;
|
| 330 |
-
border: 1px solid #eef0f2;
|
| 331 |
-
border-radius: 40px;
|
| 332 |
-
padding: 12px 18px;
|
| 333 |
-
font-size: 15px;
|
| 334 |
-
font-weight: 500;
|
| 335 |
-
color: #333;
|
| 336 |
-
white-space: nowrap;
|
| 337 |
-
cursor: pointer;
|
| 338 |
-
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
| 339 |
-
transition: all 0.2s ease-in-out;
|
| 340 |
-
}
|
| 341 |
-
.dark .suggestion-button {
|
| 342 |
-
background-color: #374151;
|
| 343 |
-
border-color: #4b5563;
|
| 344 |
-
color: #e5e7eb;
|
| 345 |
-
}
|
| 346 |
-
|
| 347 |
-
.suggestion-button:hover {
|
| 348 |
-
transform: translateY(-3px);
|
| 349 |
-
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.08);
|
| 350 |
-
border-color: #e0e2e5;
|
| 351 |
-
}
|
| 352 |
-
.dark .suggestion-button:hover {
|
| 353 |
-
background-color: #4b5563;
|
| 354 |
-
border-color: #6b7280;
|
| 355 |
-
}
|
| 356 |
-
|
| 357 |
-
.suggestion-button:active {
|
| 358 |
-
transform: translateY(-1px) scale(0.98);
|
| 359 |
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06);
|
| 360 |
-
}
|
| 361 |
-
|
| 362 |
-
.suggestion-button svg {
|
| 363 |
-
width: 20px;
|
| 364 |
-
height: 20px;
|
| 365 |
-
margin-left: 10px;
|
| 366 |
-
}
|
| 367 |
-
|
| 368 |
-
@media (max-width: 480px) {
|
| 369 |
-
.chatbot-name, .main-title { font-size: 1.5rem; }
|
| 370 |
-
.suggestion-button { padding: 10px 14px; font-size: 14px; }
|
| 371 |
-
}
|
| 372 |
-
|
| 373 |
-
#image-gallery-modal {
|
| 374 |
-
transition: opacity 300ms ease-in-out;
|
| 375 |
-
}
|
| 376 |
-
#image-gallery-modal.hidden {
|
| 377 |
-
pointer-events: none;
|
| 378 |
-
}
|
| 379 |
-
#image-gallery-modal.visible {
|
| 380 |
-
opacity: 1;
|
| 381 |
-
}
|
| 382 |
-
#image-gallery-modal.visible #image-gallery-content {
|
| 383 |
-
opacity: 1;
|
| 384 |
-
transform: scale(1);
|
| 385 |
-
}
|
| 386 |
-
#gallery-thumbnails .gallery-thumb {
|
| 387 |
-
width: 60px;
|
| 388 |
-
height: 60px;
|
| 389 |
-
object-fit: cover;
|
| 390 |
-
border-radius: 8px;
|
| 391 |
-
cursor: pointer;
|
| 392 |
-
border: 3px solid transparent;
|
| 393 |
-
transition: all 0.2s ease-in-out;
|
| 394 |
-
opacity: 0.6;
|
| 395 |
-
}
|
| 396 |
-
#gallery-thumbnails .gallery-thumb:hover {
|
| 397 |
-
opacity: 1;
|
| 398 |
-
transform: scale(1.05);
|
| 399 |
-
}
|
| 400 |
-
#gallery-thumbnails .gallery-thumb.active {
|
| 401 |
-
border-color: #3b82f6;
|
| 402 |
-
opacity: 1;
|
| 403 |
-
transform: scale(1.1);
|
| 404 |
-
}
|
| 405 |
-
|
| 406 |
-
#gallery-download-btn {
|
| 407 |
-
padding: 0.75rem 1.5rem;
|
| 408 |
-
font-size: 1rem;
|
| 409 |
-
border-radius: 9999px;
|
| 410 |
-
background: linear-gradient(45deg, #3b82f6, #6366f1);
|
| 411 |
-
box-shadow: 0 4px 14px 0 rgba(59, 130, 246, 0.39);
|
| 412 |
-
transition: all 0.3s ease-in-out;
|
| 413 |
-
cursor: pointer;
|
| 414 |
-
min-width: 150px;
|
| 415 |
-
min-height: 48px;
|
| 416 |
-
animation: download-pulse 2.5s infinite;
|
| 417 |
-
}
|
| 418 |
-
#gallery-download-btn:hover {
|
| 419 |
-
transform: translateY(-2px);
|
| 420 |
-
box-shadow: 0 6px 20px 0 rgba(59, 130, 246, 0.45);
|
| 421 |
-
animation-play-state: paused;
|
| 422 |
-
}
|
| 423 |
-
#gallery-download-btn:active {
|
| 424 |
-
transform: translateY(0px) scale(0.98);
|
| 425 |
-
box-shadow: 0 2px 8px 0 rgba(59, 130, 246, 0.3);
|
| 426 |
-
}
|
| 427 |
-
#gallery-download-btn .typing-indicator span {
|
| 428 |
-
background-color: white;
|
| 429 |
-
}
|
| 430 |
-
@keyframes download-pulse {
|
| 431 |
-
0% {
|
| 432 |
-
box-shadow: 0 4px 14px 0 rgba(59, 130, 246, 0.39);
|
| 433 |
-
}
|
| 434 |
-
50% {
|
| 435 |
-
box-shadow: 0 6px 20px 0 rgba(99, 102, 241, 0.5);
|
| 436 |
-
}
|
| 437 |
-
100% {
|
| 438 |
-
box-shadow: 0 4px 14px 0 rgba(59, 130, 246, 0.39);
|
| 439 |
-
}
|
| 440 |
-
}
|
| 441 |
-
|
| 442 |
-
@keyframes fade-slide-in-button {
|
| 443 |
-
from {
|
| 444 |
-
opacity: 0;
|
| 445 |
-
transform: translateY(10px);
|
| 446 |
-
}
|
| 447 |
-
to {
|
| 448 |
-
opacity: 1;
|
| 449 |
-
transform: translateY(0);
|
| 450 |
-
}
|
| 451 |
-
}
|
| 452 |
-
|
| 453 |
-
.clarification-message {
|
| 454 |
-
background-color: #f8fafc;
|
| 455 |
-
border: 1px solid #e2e8f0;
|
| 456 |
-
color: #1e293b;
|
| 457 |
-
}
|
| 458 |
-
.dark .clarification-message { background-color: #1f2937; border-color: #374151; color: #d1d5db; }
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
.clarification-button.glass-button {
|
| 462 |
-
width: 100%;
|
| 463 |
-
display: flex;
|
| 464 |
-
align-items: center;
|
| 465 |
-
justify-content: flex-start;
|
| 466 |
-
gap: 12px;
|
| 467 |
-
padding: 12px 16px;
|
| 468 |
-
font-size: 0.95rem;
|
| 469 |
-
font-weight: 500;
|
| 470 |
-
color: #334155;
|
| 471 |
-
background: rgba(255, 255, 255, 0.5);
|
| 472 |
-
backdrop-filter: blur(8px);
|
| 473 |
-
-webkit-backdrop-filter: blur(8px);
|
| 474 |
-
border: 1px solid rgba(200, 200, 200, 0.4);
|
| 475 |
-
border-radius: 14px;
|
| 476 |
-
cursor: pointer;
|
| 477 |
-
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
| 478 |
-
animation: fade-slide-in-button 0.5s ease-out forwards;
|
| 479 |
-
opacity: 0;
|
| 480 |
-
}
|
| 481 |
-
.dark .clarification-button.glass-button { color: #e5e7eb; background: rgba(55, 65, 81, 0.5); border-color: rgba(107, 114, 128, 0.4); }
|
| 482 |
-
|
| 483 |
-
.clarification-button.glass-button:nth-of-type(1) {
|
| 484 |
-
animation-delay: 0.1s;
|
| 485 |
-
}
|
| 486 |
-
.clarification-button.glass-button:nth-of-type(2) {
|
| 487 |
-
animation-delay: 0.2s;
|
| 488 |
-
}
|
| 489 |
-
|
| 490 |
-
.clarification-button.glass-button:hover {
|
| 491 |
-
background: rgba(255, 255, 255, 0.8);
|
| 492 |
-
border-color: rgba(107, 114, 128, 0.5);
|
| 493 |
-
transform: translateY(-2px);
|
| 494 |
-
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.07);
|
| 495 |
-
}
|
| 496 |
-
.dark .clarification-button.glass-button:hover { background: rgba(75, 85, 99, 0.8); border-color: rgba(156, 163, 175, 0.5); }
|
| 497 |
-
|
| 498 |
-
.clarification-button.glass-button:active {
|
| 499 |
-
transform: translateY(0px) scale(0.98);
|
| 500 |
-
box-shadow: none;
|
| 501 |
-
}
|
| 502 |
-
|
| 503 |
-
.clarification-button.glass-button svg {
|
| 504 |
-
flex-shrink: 0;
|
| 505 |
-
color: #4f46e5;
|
| 506 |
-
}
|
| 507 |
-
.dark .clarification-button.glass-button svg { color: #818cf8; }
|
| 508 |
-
|
| 509 |
-
.menu-item {
|
| 510 |
-
display: flex;
|
| 511 |
-
align-items: center;
|
| 512 |
-
gap: 0.75rem;
|
| 513 |
-
padding: 0.5rem 0.75rem;
|
| 514 |
-
font-size: 0.875rem;
|
| 515 |
-
color: #374151;
|
| 516 |
-
border-radius: 0.375rem;
|
| 517 |
-
transition: background-color 0.2s;
|
| 518 |
-
cursor: pointer;
|
| 519 |
-
width: 100%;
|
| 520 |
-
}
|
| 521 |
-
.dark .menu-item { color: #d1d5db; }
|
| 522 |
-
.menu-item:hover {
|
| 523 |
-
background-color: #f3f4f6;
|
| 524 |
-
}
|
| 525 |
-
.dark .menu-item:hover { background-color: #374151; }
|
| 526 |
-
.menu-item.danger:hover {
|
| 527 |
-
background-color: #fee2e2;
|
| 528 |
-
color: #b91c1c;
|
| 529 |
-
}
|
| 530 |
-
.dark .menu-item.danger:hover { background-color: #450a0a; color: #f87171; }
|
| 531 |
-
.menu-item svg {
|
| 532 |
-
width: 1.25rem;
|
| 533 |
-
height: 1.25rem;
|
| 534 |
-
}
|
| 535 |
-
.menu-divider {
|
| 536 |
-
height: 1px;
|
| 537 |
-
background-color: #e5e7eb;
|
| 538 |
-
margin: 0.5rem 0;
|
| 539 |
-
}
|
| 540 |
-
.dark .menu-divider { background-color: #374151; }
|
| 541 |
-
|
| 542 |
-
.sidebar-button {
|
| 543 |
-
display: flex;
|
| 544 |
-
align-items: center;
|
| 545 |
-
gap: 0.75rem;
|
| 546 |
-
padding: 0.75rem;
|
| 547 |
-
width: 100%;
|
| 548 |
-
text-align: right;
|
| 549 |
-
font-size: 0.875rem;
|
| 550 |
-
font-weight: 500;
|
| 551 |
-
border-radius: 0.5rem;
|
| 552 |
-
color: #4b5563;
|
| 553 |
-
transition: background-color 0.2s, color 0.2s;
|
| 554 |
-
}
|
| 555 |
-
.dark .sidebar-button { color: #d1d5db; }
|
| 556 |
-
.sidebar-button:hover { background-color: #e5e7eb; color: #1f2937; }
|
| 557 |
-
.dark .sidebar-button:hover { background-color: #374151; color: #f9fafb; }
|
| 558 |
-
.sidebar-button svg { flex-shrink: 0; }
|
| 559 |
-
|
| 560 |
-
.theme-switch {
|
| 561 |
-
position: relative;
|
| 562 |
-
display: inline-block;
|
| 563 |
-
width: 50px;
|
| 564 |
-
height: 28px;
|
| 565 |
-
}
|
| 566 |
-
.theme-switch input {
|
| 567 |
-
opacity: 0;
|
| 568 |
-
width: 0;
|
| 569 |
-
height: 0;
|
| 570 |
-
}
|
| 571 |
-
.slider {
|
| 572 |
-
position: absolute;
|
| 573 |
-
cursor: pointer;
|
| 574 |
-
top: 0;
|
| 575 |
-
left: 0;
|
| 576 |
-
right: 0;
|
| 577 |
-
bottom: 0;
|
| 578 |
-
background-color: #ccc;
|
| 579 |
-
transition: .4s;
|
| 580 |
-
border-radius: 28px;
|
| 581 |
-
}
|
| 582 |
-
.dark .slider { background-color: #374151; }
|
| 583 |
-
.slider:before {
|
| 584 |
-
position: absolute;
|
| 585 |
-
content: "";
|
| 586 |
-
height: 20px;
|
| 587 |
-
width: 20px;
|
| 588 |
-
left: 4px;
|
| 589 |
-
bottom: 4px;
|
| 590 |
-
background-color: white;
|
| 591 |
-
transition: .4s;
|
| 592 |
-
border-radius: 50%;
|
| 593 |
-
}
|
| 594 |
-
input:checked + .slider {
|
| 595 |
-
background-color: #4f46e5;
|
| 596 |
-
}
|
| 597 |
-
input:checked + .slider:before {
|
| 598 |
-
transform: translateX(22px);
|
| 599 |
-
}
|
| 600 |
-
|
| 601 |
-
.message-entry.model {
|
| 602 |
-
align-items: start;
|
| 603 |
-
}
|
| 604 |
-
|
| 605 |
-
.message-entry .message-content.model-bubble {
|
| 606 |
-
display: flex;
|
| 607 |
-
flex-direction: column;
|
| 608 |
-
width: 100%;
|
| 609 |
-
padding: 0;
|
| 610 |
-
}
|
| 611 |
-
|
| 612 |
-
.thinking-header-area {
|
| 613 |
-
display: flex;
|
| 614 |
-
align-items: center;
|
| 615 |
-
gap: 0.5rem;
|
| 616 |
-
padding: 1rem;
|
| 617 |
-
}
|
| 618 |
-
|
| 619 |
-
.model-icon-in-bubble {
|
| 620 |
-
width: 36px;
|
| 621 |
-
height: 36px;
|
| 622 |
-
border-radius: 50%;
|
| 623 |
-
background-color: #334155;
|
| 624 |
-
color: white;
|
| 625 |
-
display: flex;
|
| 626 |
-
align-items: center;
|
| 627 |
-
justify-content: center;
|
| 628 |
-
flex-shrink: 0;
|
| 629 |
-
margin-right: 0.75rem;
|
| 630 |
-
}
|
| 631 |
-
.dark .model-icon-in-bubble {
|
| 632 |
-
background-color: #475569;
|
| 633 |
-
}
|
| 634 |
-
|
| 635 |
-
.thinking-head {
|
| 636 |
-
display: inline-flex;
|
| 637 |
-
align-items: center;
|
| 638 |
-
gap: 0.5rem;
|
| 639 |
-
padding: 0.25rem 0.75rem;
|
| 640 |
-
border-radius: 9999px;
|
| 641 |
-
background-color: #f1f5f9;
|
| 642 |
-
border: 1px solid #e2e8f0;
|
| 643 |
-
cursor: pointer;
|
| 644 |
-
user-select: none;
|
| 645 |
-
transition: background-color 0.2s;
|
| 646 |
-
}
|
| 647 |
-
.dark .thinking-head {
|
| 648 |
-
background-color: #334151;
|
| 649 |
-
border-color: #475569;
|
| 650 |
-
}
|
| 651 |
-
|
| 652 |
-
.thinking-atom-icon {
|
| 653 |
-
width: 1.25rem;
|
| 654 |
-
height: 1.25rem;
|
| 655 |
-
}
|
| 656 |
-
|
| 657 |
-
.thinking-label {
|
| 658 |
-
font-size: 0.875rem;
|
| 659 |
-
font-weight: 500;
|
| 660 |
-
color: #334151;
|
| 661 |
-
}
|
| 662 |
-
.dark .thinking-label {
|
| 663 |
-
color: #cbd5e1;
|
| 664 |
-
}
|
| 665 |
-
|
| 666 |
-
.thinking-chevron {
|
| 667 |
-
width: 1rem;
|
| 668 |
-
height: 1rem;
|
| 669 |
-
color: #94a3b8;
|
| 670 |
-
transition: transform 0.3s;
|
| 671 |
-
}
|
| 672 |
-
.thinking-chevron.collapsed {
|
| 673 |
-
transform: rotate(-180deg);
|
| 674 |
-
}
|
| 675 |
-
|
| 676 |
-
.thinking-body {
|
| 677 |
-
max-height: 500px;
|
| 678 |
-
overflow-y: auto;
|
| 679 |
-
margin-top: 0.75rem;
|
| 680 |
-
padding: 0.75rem;
|
| 681 |
-
border-radius: 0.75rem;
|
| 682 |
-
background-color: #f8fafc;
|
| 683 |
-
border: 1px solid #e2e8f0;
|
| 684 |
-
color: #1e293b;
|
| 685 |
-
font-size: 0.875rem;
|
| 686 |
-
line-height: 1.6;
|
| 687 |
-
transition: all 0.4s ease-in-out;
|
| 688 |
-
}
|
| 689 |
-
.dark .thinking-body {
|
| 690 |
-
background-color: #1e293b;
|
| 691 |
-
border-color: #334151;
|
| 692 |
-
color: #e2e8f0;
|
| 693 |
-
}
|
| 694 |
-
.thinking-body.collapsed {
|
| 695 |
-
max-height: 0;
|
| 696 |
-
padding-top: 0;
|
| 697 |
-
padding-bottom: 0;
|
| 698 |
-
opacity: 0;
|
| 699 |
-
border: none;
|
| 700 |
-
margin-top: 0;
|
| 701 |
-
overflow: hidden;
|
| 702 |
-
}
|
| 703 |
-
|
| 704 |
-
.final-answer-wrapper {
|
| 705 |
-
width: 100%;
|
| 706 |
-
transition: opacity 0.5s, transform 0.5s;
|
| 707 |
-
}
|
| 708 |
-
|
| 709 |
-
.message-content.user-bubble-multipart {
|
| 710 |
-
padding: 0;
|
| 711 |
-
overflow: hidden;
|
| 712 |
-
}
|
| 713 |
-
|
| 714 |
-
.user-file-part {
|
| 715 |
-
padding: 8px;
|
| 716 |
-
}
|
| 717 |
-
|
| 718 |
-
.user-file-part.single {
|
| 719 |
-
padding: 0;
|
| 720 |
-
}
|
| 721 |
-
|
| 722 |
-
.user-file-part img,
|
| 723 |
-
.user-file-part video {
|
| 724 |
-
width: 100%;
|
| 725 |
-
display: block;
|
| 726 |
-
border-radius: 12px;
|
| 727 |
-
border: 2px solid rgba(255, 255, 255, 0.6);
|
| 728 |
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
| 729 |
-
}
|
| 730 |
-
|
| 731 |
-
.user-text-part {
|
| 732 |
-
padding: 10px 16px;
|
| 733 |
-
background-color: rgba(0, 0, 0, 0.1);
|
| 734 |
-
}
|
| 735 |
-
|
| 736 |
-
.beautiful-upgrade-btn {
|
| 737 |
-
font-family: 'Vazirmatn', sans-serif;
|
| 738 |
-
font-weight: 700;
|
| 739 |
-
font-size: 1rem;
|
| 740 |
-
padding: 0.8rem 2rem;
|
| 741 |
-
border-radius: 9999px;
|
| 742 |
-
color: white;
|
| 743 |
-
background-size: 200% auto;
|
| 744 |
-
background-image: linear-gradient(45deg, #f87171 0%, #fb923c 50%, #f87171 100%);
|
| 745 |
-
border: none;
|
| 746 |
-
cursor: pointer;
|
| 747 |
-
box-shadow: 0 4px 15px rgba(251, 146, 60, 0.4);
|
| 748 |
-
transition: all 0.4s ease-in-out;
|
| 749 |
-
display: inline-flex;
|
| 750 |
-
align-items: center;
|
| 751 |
-
justify-content: center;
|
| 752 |
-
gap: 0.5rem;
|
| 753 |
-
}
|
| 754 |
-
.dark .beautiful-upgrade-btn {
|
| 755 |
-
box-shadow: 0 4px 20px rgba(251, 146, 60, 0.5);
|
| 756 |
-
}
|
| 757 |
-
|
| 758 |
-
.beautiful-upgrade-btn:hover {
|
| 759 |
-
background-position: right center;
|
| 760 |
-
transform: translateY(-3px) scale(1.05);
|
| 761 |
-
box-shadow: 0 8px 25px rgba(251, 146, 60, 0.5);
|
| 762 |
-
}
|
| 763 |
-
|
| 764 |
-
.beautiful-upgrade-btn:active {
|
| 765 |
-
transform: translateY(0px) scale(1);
|
| 766 |
-
box-shadow: 0 2px 10px rgba(251, 146, 60, 0.3);
|
| 767 |
-
}
|
| 768 |
-
|
| 769 |
-
.premium-modal-content {
|
| 770 |
-
transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
| 771 |
-
transform: scale(0.95);
|
| 772 |
-
opacity: 0;
|
| 773 |
-
}
|
| 774 |
-
#premium-feature-modal.visible .premium-modal-content {
|
| 775 |
-
transform: scale(1);
|
| 776 |
-
opacity: 1;
|
| 777 |
-
}
|
| 778 |
-
#plus-required-modal.visible .premium-modal-content {
|
| 779 |
-
transform: scale(1);
|
| 780 |
-
opacity: 1;
|
| 781 |
-
}
|
| 782 |
-
|
| 783 |
-
@keyframes icon-float {
|
| 784 |
-
0% { transform: translateY(0px); }
|
| 785 |
-
50% { transform: translateY(-10px); }
|
| 786 |
-
100% { transform: translateY(0px); }
|
| 787 |
-
}
|
| 788 |
-
|
| 789 |
-
#premium-modal-icon-container svg,
|
| 790 |
-
#plus-modal-icon-container svg {
|
| 791 |
-
animation: icon-float 3s ease-in-out infinite;
|
| 792 |
-
filter: drop-shadow(0 5px 15px rgba(0,0,0,0.15));
|
| 793 |
-
}
|
| 794 |
-
|
| 795 |
-
.settings-tier-container {
|
| 796 |
-
display: flex;
|
| 797 |
-
align-items: center;
|
| 798 |
-
gap: 12px;
|
| 799 |
-
animation: fade-slide-in 0.5s ease-out;
|
| 800 |
-
}
|
| 801 |
-
.settings-tier-icon {
|
| 802 |
-
width: 40px;
|
| 803 |
-
height: 40px;
|
| 804 |
-
}
|
| 805 |
-
.settings-tier-text {
|
| 806 |
-
font-size: 1rem;
|
| 807 |
-
font-weight: 600;
|
| 808 |
-
}
|
| 809 |
-
.settings-tier-text .tier-name-premium {
|
| 810 |
-
background: linear-gradient(45deg, #a78bfa, #60a5fa);
|
| 811 |
-
-webkit-background-clip: text;
|
| 812 |
-
background-clip: text;
|
| 813 |
-
color: transparent;
|
| 814 |
-
}
|
| 815 |
-
.settings-tier-text .tier-name-free {
|
| 816 |
-
color: #64748b;
|
| 817 |
-
}
|
| 818 |
-
.dark .settings-tier-text .tier-name-free {
|
| 819 |
-
color: #94a3b8;
|
| 820 |
-
}
|
| 821 |
-
|
| 822 |
-
#main-footer {
|
| 823 |
-
background: none;
|
| 824 |
-
}
|
| 825 |
-
|
| 826 |
-
.chat-input-box {
|
| 827 |
-
display: flex;
|
| 828 |
-
flex-direction: column;
|
| 829 |
-
justify-content: space-between;
|
| 830 |
-
background-color: #ffffff;
|
| 831 |
-
border: 1px solid #e0e0e0;
|
| 832 |
-
border-radius: 28px;
|
| 833 |
-
padding: 12px 16px;
|
| 834 |
-
padding-top: 18px;
|
| 835 |
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
| 836 |
-
box-sizing: border-box;
|
| 837 |
-
transform: scale(1);
|
| 838 |
-
transition: all 0.2s ease-out;
|
| 839 |
-
}
|
| 840 |
-
.dark .chat-input-box {
|
| 841 |
-
background-color: #1f2937;
|
| 842 |
-
border-color: #374151;
|
| 843 |
-
}
|
| 844 |
-
|
| 845 |
-
.chat-input-box:focus-within {
|
| 846 |
-
border-color: #a0a0a0;
|
| 847 |
-
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1), 0 4px 12px rgba(0, 0, 0, 0.1);
|
| 848 |
-
transform: scale(1.01);
|
| 849 |
-
}
|
| 850 |
-
.dark .chat-input-box:focus-within {
|
| 851 |
-
border-color: #6b7280;
|
| 852 |
-
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15), 0 4px 12px rgba(0, 0, 0, 0.2);
|
| 853 |
-
}
|
| 854 |
-
|
| 855 |
-
.chat-text-input {
|
| 856 |
-
width: 100%;
|
| 857 |
-
border: none;
|
| 858 |
-
outline: none;
|
| 859 |
-
background-color: transparent;
|
| 860 |
-
font-size: 17px;
|
| 861 |
-
font-family: 'Vazirmatn', sans-serif;
|
| 862 |
-
color: #111827;
|
| 863 |
-
padding: 0;
|
| 864 |
-
margin-bottom: 10px;
|
| 865 |
-
resize: none;
|
| 866 |
-
max-height: 150px;
|
| 867 |
-
overflow-y: auto;
|
| 868 |
-
}
|
| 869 |
-
.dark .chat-text-input {
|
| 870 |
-
color: #f3f4f6;
|
| 871 |
-
}
|
| 872 |
-
|
| 873 |
-
.chat-text-input::placeholder {
|
| 874 |
-
color: #8e8e8e;
|
| 875 |
-
}
|
| 876 |
-
.dark .chat-text-input::placeholder {
|
| 877 |
-
color: #6b7280;
|
| 878 |
-
}
|
| 879 |
-
|
| 880 |
-
.buttons-wrapper {
|
| 881 |
-
display: flex;
|
| 882 |
-
align-items: center;
|
| 883 |
-
justify-content: flex-end;
|
| 884 |
-
gap: 8px;
|
| 885 |
-
width: 100%;
|
| 886 |
-
flex-shrink: 0;
|
| 887 |
-
}
|
| 888 |
-
|
| 889 |
-
.icon-button {
|
| 890 |
-
display: flex;
|
| 891 |
-
align-items: center;
|
| 892 |
-
justify-content: center;
|
| 893 |
-
width: 38px;
|
| 894 |
-
height: 38px;
|
| 895 |
-
background-color: #f0f2f5;
|
| 896 |
-
border: none;
|
| 897 |
-
border-radius: 50%;
|
| 898 |
-
cursor: pointer;
|
| 899 |
-
font-size: 18px;
|
| 900 |
-
color: #1f2937;
|
| 901 |
-
transition: all 0.3s ease;
|
| 902 |
-
}
|
| 903 |
-
.dark .icon-button {
|
| 904 |
-
background-color: #374151;
|
| 905 |
-
color: #e5e7eb;
|
| 906 |
-
}
|
| 907 |
-
|
| 908 |
-
.icon-button:hover {
|
| 909 |
-
background-color: #e4e6e9;
|
| 910 |
-
}
|
| 911 |
-
.dark .icon-button:hover {
|
| 912 |
-
background-color: #4b5563;
|
| 913 |
-
}
|
| 914 |
-
|
| 915 |
-
.send-button {
|
| 916 |
-
position: relative;
|
| 917 |
-
transition: all 0.3s ease !important;
|
| 918 |
-
}
|
| 919 |
-
.send-button.active {
|
| 920 |
-
background: linear-gradient(45deg, #4f46e5, #a259ff);
|
| 921 |
-
color: white;
|
| 922 |
-
transform: scale(1.05);
|
| 923 |
-
}
|
| 924 |
-
.send-button.active svg {
|
| 925 |
-
stroke: white;
|
| 926 |
-
}
|
| 927 |
-
.send-button svg {
|
| 928 |
-
position: absolute;
|
| 929 |
-
top: 50%;
|
| 930 |
-
left: 50%;
|
| 931 |
-
transform: translate(-50%, -50%);
|
| 932 |
-
transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
|
| 933 |
-
}
|
| 934 |
-
.send-button #stop-icon {
|
| 935 |
-
opacity: 0;
|
| 936 |
-
transform: translate(-50%, -50%) scale(0.5);
|
| 937 |
-
}
|
| 938 |
-
.send-button.is-loading #send-icon {
|
| 939 |
-
opacity: 0;
|
| 940 |
-
transform: translate(-50%, -50%) scale(0.5);
|
| 941 |
-
}
|
| 942 |
-
.send-button.is-loading #stop-icon {
|
| 943 |
-
opacity: 1;
|
| 944 |
-
transform: translate(-50%, -50%) scale(1);
|
| 945 |
-
display: block !important;
|
| 946 |
-
}
|
| 947 |
-
.dark .send-button.active {
|
| 948 |
-
background: linear-gradient(45deg, #6366f1, #c084fc);
|
| 949 |
-
}
|
| 950 |
-
|
| 951 |
-
.tools-container, .attach-container {
|
| 952 |
-
position: relative;
|
| 953 |
-
display: flex;
|
| 954 |
-
}
|
| 955 |
-
|
| 956 |
-
.tools-button {
|
| 957 |
-
display: flex;
|
| 958 |
-
align-items: center;
|
| 959 |
-
gap: 6px;
|
| 960 |
-
background-color: #f0f2f5;
|
| 961 |
-
border: none;
|
| 962 |
-
border-radius: 20px;
|
| 963 |
-
padding: 8px 14px;
|
| 964 |
-
font-size: 15px;
|
| 965 |
-
font-family: 'Vazirmatn', sans-serif;
|
| 966 |
-
cursor: pointer;
|
| 967 |
-
color: #1f2937;
|
| 968 |
-
transition: all 0.2s ease;
|
| 969 |
-
flex-shrink: 0;
|
| 970 |
-
}
|
| 971 |
-
.dark .tools-button {
|
| 972 |
-
background-color: #374151;
|
| 973 |
-
color: #e5e7eb;
|
| 974 |
-
}
|
| 975 |
-
|
| 976 |
-
.tools-button:hover {
|
| 977 |
-
background-color: #e4e6e9;
|
| 978 |
-
}
|
| 979 |
-
.dark .tools-button:hover {
|
| 980 |
-
background-color: #4b5563;
|
| 981 |
-
}
|
| 982 |
-
|
| 983 |
-
.tools-button.tool-selected {
|
| 984 |
-
padding: 8px 10px;
|
| 985 |
-
gap: 4px;
|
| 986 |
-
}
|
| 987 |
-
|
| 988 |
-
.tools-icon svg {
|
| 989 |
-
width: 20px;
|
| 990 |
-
height: 20px;
|
| 991 |
-
stroke: #333;
|
| 992 |
-
}
|
| 993 |
-
.dark .tools-icon svg {
|
| 994 |
-
stroke: #e5e7eb;
|
| 995 |
-
}
|
| 996 |
-
|
| 997 |
-
.tools-menu, #file-popup-menu {
|
| 998 |
-
position: absolute;
|
| 999 |
-
bottom: calc(100% + 10px);
|
| 1000 |
-
left: 0;
|
| 1001 |
-
background-color: #ffffff;
|
| 1002 |
-
border-radius: 12px;
|
| 1003 |
-
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
| 1004 |
-
padding: 8px;
|
| 1005 |
-
width: max-content;
|
| 1006 |
-
z-index: 50;
|
| 1007 |
-
list-style: none;
|
| 1008 |
-
margin: 0;
|
| 1009 |
-
opacity: 0;
|
| 1010 |
-
transform: translateY(10px) scale(0.95);
|
| 1011 |
-
pointer-events: none;
|
| 1012 |
-
transition: opacity 0.2s ease-out, transform 0.2s ease-out;
|
| 1013 |
-
transform-origin: bottom left;
|
| 1014 |
-
}
|
| 1015 |
-
.dark .tools-menu, .dark #file-popup-menu {
|
| 1016 |
-
background-color: #262f3d;
|
| 1017 |
-
box-shadow: 0 4px 20px rgba(0,0,0,0.4);
|
| 1018 |
-
}
|
| 1019 |
-
|
| 1020 |
-
|
| 1021 |
-
.tools-menu.active, #file-popup-menu.active {
|
| 1022 |
-
opacity: 1;
|
| 1023 |
-
transform: translateY(0) scale(1);
|
| 1024 |
-
pointer-events: auto;
|
| 1025 |
-
}
|
| 1026 |
-
|
| 1027 |
-
.tool-item, .popup-option {
|
| 1028 |
-
display: flex;
|
| 1029 |
-
align-items: center;
|
| 1030 |
-
padding: 0.75rem 1rem;
|
| 1031 |
-
border-radius: 8px;
|
| 1032 |
-
cursor: pointer;
|
| 1033 |
-
transition: background-color 0.2s ease;
|
| 1034 |
-
white-space: nowrap;
|
| 1035 |
-
color: #374151;
|
| 1036 |
-
font-size: 16px;
|
| 1037 |
-
background: none;
|
| 1038 |
-
border: none;
|
| 1039 |
-
font-family: 'Vazirmatn', sans-serif;
|
| 1040 |
-
width: 100%;
|
| 1041 |
-
text-align: right;
|
| 1042 |
-
}
|
| 1043 |
-
.dark .tool-item, .dark .popup-option {
|
| 1044 |
-
color: #e5e7eb;
|
| 1045 |
-
}
|
| 1046 |
-
|
| 1047 |
-
.tool-item:hover, .popup-option:hover {
|
| 1048 |
-
background-color: #f0f2f5;
|
| 1049 |
-
}
|
| 1050 |
-
.dark .tool-item:hover, .dark .popup-option:hover {
|
| 1051 |
-
background-color: #374151;
|
| 1052 |
-
}
|
| 1053 |
-
|
| 1054 |
-
.tool-item-svg, .popup-option-svg {
|
| 1055 |
-
width: 32px;
|
| 1056 |
-
height: 32px;
|
| 1057 |
-
margin-left: 15px;
|
| 1058 |
-
flex-shrink: 0;
|
| 1059 |
-
filter: drop-shadow(0px 2px 3px rgba(0,0,0,0.2));
|
| 1060 |
-
transition: transform 0.3s ease, filter 0.3s ease;
|
| 1061 |
-
}
|
| 1062 |
-
|
| 1063 |
-
.tool-item:hover .tool-item-svg, .popup-option:hover .popup-option-svg {
|
| 1064 |
-
transform: translateY(-3px) scale(1.05);
|
| 1065 |
-
filter: drop-shadow(0px 4px 6px rgba(0,0,0,0.3));
|
| 1066 |
-
}
|
| 1067 |
-
|
| 1068 |
-
.clear-tool-selection {
|
| 1069 |
-
background: none;
|
| 1070 |
-
border: none;
|
| 1071 |
-
cursor: pointer;
|
| 1072 |
-
padding: 0;
|
| 1073 |
-
margin-right: 5px;
|
| 1074 |
-
display: flex;
|
| 1075 |
-
align-items: center;
|
| 1076 |
-
justify-content: center;
|
| 1077 |
-
color: #8e8e8e;
|
| 1078 |
-
transition: color 0.2s ease;
|
| 1079 |
-
}
|
| 1080 |
-
.dark .clear-tool-selection {
|
| 1081 |
-
color: #9ca3af;
|
| 1082 |
-
}
|
| 1083 |
-
|
| 1084 |
-
.clear-tool-selection:hover {
|
| 1085 |
-
color: #333;
|
| 1086 |
-
}
|
| 1087 |
-
.dark .clear-tool-selection:hover {
|
| 1088 |
-
color: #e5e7eb;
|
| 1089 |
-
}
|
| 1090 |
-
|
| 1091 |
-
.clear-tool-selection svg {
|
| 1092 |
-
width: 16px;
|
| 1093 |
-
height: 16px;
|
| 1094 |
-
stroke: currentColor;
|
| 1095 |
-
}
|
| 1096 |
-
|
| 1097 |
-
.popup-option .upload-arrow {
|
| 1098 |
-
animation: uploadPulse 1.5s infinite alternate ease-in-out;
|
| 1099 |
-
}
|
| 1100 |
-
@keyframes uploadPulse {
|
| 1101 |
-
0% { transform: translateY(0); opacity: 1; }
|
| 1102 |
-
50% { transform: translateY(-3px); opacity: 0.8; }
|
| 1103 |
-
100% { transform: translateY(0); opacity: 1; }
|
| 1104 |
-
}
|
| 1105 |
-
|
| 1106 |
-
.popup-option .magnifier-group {
|
| 1107 |
-
transform-origin: 12px 14px;
|
| 1108 |
-
animation: magnifierFloatScan 3s infinite alternate ease-in-out;
|
| 1109 |
-
}
|
| 1110 |
-
@keyframes magnifierFloatScan {
|
| 1111 |
-
0% { transform: translate(0px, 0px) scale(1); }
|
| 1112 |
-
33% { transform: translate(1px, -1px) scale(1.02); }
|
| 1113 |
-
66% { transform: translate(-1px, 1px) scale(1.02); }
|
| 1114 |
-
100% { transform: translate(0px, 0px) scale(1); }
|
| 1115 |
-
}
|
| 1116 |
-
|
| 1117 |
-
.popup-option .magnifying-lens {
|
| 1118 |
-
animation: lensGlow 2s infinite alternate ease-in-out;
|
| 1119 |
-
}
|
| 1120 |
-
@keyframes lensGlow {
|
| 1121 |
-
0% { fill: rgba(180, 220, 255, 0.7); }
|
| 1122 |
-
50% { fill: rgba(220, 240, 255, 1); }
|
| 1123 |
-
100% { fill: rgba(180, 220, 255, 0.7); }
|
| 1124 |
-
}
|
| 1125 |
-
|
| 1126 |
-
/* DEEP THINK TV STYLES */
|
| 1127 |
-
.deep-think-tv-container {
|
| 1128 |
-
padding: 1rem;
|
| 1129 |
-
box-sizing: border-box;
|
| 1130 |
-
width: 100%;
|
| 1131 |
-
}
|
| 1132 |
-
.deep-think-tv {
|
| 1133 |
-
width: 100%;
|
| 1134 |
-
aspect-ratio: 16 / 9;
|
| 1135 |
-
position:relative;
|
| 1136 |
-
border-radius: 12px;
|
| 1137 |
-
padding: 1.5vmin;
|
| 1138 |
-
isolation:isolate;
|
| 1139 |
-
transition: background .3s ease, box-shadow .3s ease;
|
| 1140 |
-
background: #dbe1e8;
|
| 1141 |
-
box-shadow: 0 4vmin 9vmin rgba(0,0,0,.25), 0 0 0 1px #fff inset;
|
| 1142 |
-
}
|
| 1143 |
-
.dark .deep-think-tv {
|
| 1144 |
-
background: #0a0f1e;
|
| 1145 |
-
box-shadow: 0 4vmin 9vmin rgba(0,0,0,.45), 0 0 0 2px rgba(255,255,255,.04) inset;
|
| 1146 |
-
}
|
| 1147 |
-
.deep-think-tv::before{
|
| 1148 |
-
content:"";
|
| 1149 |
-
position:absolute; inset:-2px;
|
| 1150 |
-
border-radius: calc(12px + 2px);
|
| 1151 |
-
padding:2px;
|
| 1152 |
-
background: linear-gradient(135deg, #60a5fa, #a78bfa);
|
| 1153 |
-
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
| 1154 |
-
-webkit-mask-composite: xor;
|
| 1155 |
-
mask-composite: exclude;
|
| 1156 |
-
pointer-events:none;
|
| 1157 |
-
filter: blur(.3px);
|
| 1158 |
-
}
|
| 1159 |
-
.deep-think-tv .screen{
|
| 1160 |
-
position:relative;
|
| 1161 |
-
width:100%; height:100%;
|
| 1162 |
-
border-radius: 8px;
|
| 1163 |
-
overflow:hidden;
|
| 1164 |
-
transition: background .3s ease, box-shadow .3s ease;
|
| 1165 |
-
background: linear-gradient(135deg, #e1e7f0, #f8f9fc);
|
| 1166 |
-
box-shadow: inset 0 0 4vmin rgba(0,0,0,.15);
|
| 1167 |
-
}
|
| 1168 |
-
.dark .deep-think-tv .screen {
|
| 1169 |
-
background: radial-gradient(120% 140% at 50% 10%, rgba(167,139,250,.12), rgba(96,165,250,.10) 40%, transparent 60%), radial-gradient(120% 170% at 50% 120%, rgba(10,16,34,1) 40%, rgba(7,11,20,1) 100%), linear-gradient(135deg, #0b1430, #0b1026);
|
| 1170 |
-
box-shadow: inset 0 0 4vmin rgba(0,0,0,.55), inset 0 0 14vmin rgba(88, 101, 242, .12);
|
| 1171 |
-
}
|
| 1172 |
-
.deep-think-tv .scanlines::after{
|
| 1173 |
-
content:"";
|
| 1174 |
-
position:absolute; inset:0;
|
| 1175 |
-
background: repeating-linear-gradient( to bottom, rgba(0,0,0,.08) 0 1px, transparent 1px 3px );
|
| 1176 |
-
opacity:.15;
|
| 1177 |
-
pointer-events:none;
|
| 1178 |
-
}
|
| 1179 |
-
.dark .deep-think-tv .scanlines::after {
|
| 1180 |
-
background: repeating-linear-gradient( to bottom, rgba(255,255,255,.04) 0 1px, rgba(0,0,0,0) 1px 3px );
|
| 1181 |
-
mix-blend-mode: overlay;
|
| 1182 |
-
opacity:.35;
|
| 1183 |
-
}
|
| 1184 |
-
.deep-think-tv .gloss::before{
|
| 1185 |
-
content:"";
|
| 1186 |
-
position:absolute;
|
| 1187 |
-
top:-10%; left:-10%;
|
| 1188 |
-
width:80%; height:80%;
|
| 1189 |
-
background:linear-gradient(160deg, rgba(255,255,255,.35), rgba(255,255,255,0) 60%);
|
| 1190 |
-
border-radius:80% 50% 40% 60% / 50% 50% 40% 50%;
|
| 1191 |
-
filter: blur(6px);
|
| 1192 |
-
transform: rotate(4deg);
|
| 1193 |
-
pointer-events:none;
|
| 1194 |
-
}
|
| 1195 |
-
.dark .deep-think-tv .gloss::before {
|
| 1196 |
-
background:linear-gradient(160deg, rgba(255,255,255,.18), rgba(255,255,255,0) 60%);
|
| 1197 |
-
}
|
| 1198 |
-
.deep-think-tv .ui{
|
| 1199 |
-
position:absolute; inset:0;
|
| 1200 |
-
display:flex; flex-direction:column;
|
| 1201 |
-
padding: 2vmin 2.2vmin;
|
| 1202 |
-
gap: 1.5vmin;
|
| 1203 |
-
color: #2c3e50;
|
| 1204 |
-
}
|
| 1205 |
-
.dark .deep-think-tv .ui { color: #e6ecff; }
|
| 1206 |
-
.deep-think-tv .header { display: flex; flex-direction: column; gap: 1.2vmin; }
|
| 1207 |
-
.deep-think-tv .topbar{ display:flex; align-items:center; justify-content:space-between; gap: 1.2vmin; }
|
| 1208 |
-
.deep-think-tv .badge{
|
| 1209 |
-
display:inline-flex; align-items:center; gap: .8vmin;
|
| 1210 |
-
padding: .8vmin 1.2vmin;
|
| 1211 |
-
border-radius:999px;
|
| 1212 |
-
font-weight:600;
|
| 1213 |
-
letter-spacing:.2px;
|
| 1214 |
-
font-size: 1.4vmin;
|
| 1215 |
-
background: rgba(0, 0, 0, 0.05);
|
| 1216 |
-
border:1px solid rgba(0, 0, 0, 0.1);
|
| 1217 |
-
}
|
| 1218 |
-
.dark .deep-think-tv .badge {
|
| 1219 |
-
background: linear-gradient(135deg, rgba(96,165,250,.18), rgba(167,139,250,.18));
|
| 1220 |
-
border:1px solid rgba(255,255,255,.08);
|
| 1221 |
-
box-shadow: inset 0 0 1.2vmin rgba(96,165,250,.18);
|
| 1222 |
-
}
|
| 1223 |
-
.deep-think-tv .dot{
|
| 1224 |
-
width: .8vmin; height: .8vmin; border-radius:50%;
|
| 1225 |
-
background: #34d399;
|
| 1226 |
-
box-shadow: 0 0 1vmin #34d399, 0 0 2vmin #34d399;
|
| 1227 |
-
animation: blink 1.4s infinite;
|
| 1228 |
-
}
|
| 1229 |
-
@keyframes blink{ 0%, 100% { opacity: .25 } 50% { opacity: 1 } }
|
| 1230 |
-
.deep-think-tv .topic{
|
| 1231 |
-
display:flex; align-items:center; gap: .8vmin; flex-wrap:nowrap;
|
| 1232 |
-
font-size: 1.5vmin;
|
| 1233 |
-
color: #52617a;
|
| 1234 |
-
overflow: hidden;
|
| 1235 |
-
}
|
| 1236 |
-
.dark .deep-think-tv .topic { color: #a9b3d1; }
|
| 1237 |
-
.deep-think-tv .topic strong{
|
| 1238 |
-
font-weight:700;
|
| 1239 |
-
background: linear-gradient(135deg, #4f46e5, #7c3aed);
|
| 1240 |
-
-webkit-background-clip: text; background-clip: text;
|
| 1241 |
-
-webkit-text-fill-color: transparent;
|
| 1242 |
-
white-space: nowrap; text-overflow: ellipsis; overflow: hidden;
|
| 1243 |
-
}
|
| 1244 |
-
.dark .deep-think-tv .topic strong {
|
| 1245 |
-
background: linear-gradient(135deg, #60a5fa, #a78bfa);
|
| 1246 |
-
-webkit-background-clip: text; background-clip: text;
|
| 1247 |
-
color: #f0f4ff;
|
| 1248 |
-
}
|
| 1249 |
-
.deep-think-tv .steps{ display:flex; gap: .8vmin; flex-wrap:wrap; }
|
| 1250 |
-
.deep-think-tv .step{
|
| 1251 |
-
padding: .8vmin 1vmin;
|
| 1252 |
-
border-radius: 1vmin;
|
| 1253 |
-
font-size: 1.3vmin;
|
| 1254 |
-
opacity:.65;
|
| 1255 |
-
transition:.25s ease;
|
| 1256 |
-
background: rgba(0,0,0,.05);
|
| 1257 |
-
border: 1px solid rgba(0,0,0,.08);
|
| 1258 |
-
}
|
| 1259 |
-
.dark .deep-think-tv .step {
|
| 1260 |
-
background: linear-gradient(135deg, rgba(96,165,250,.14), rgba(167,139,250,.14));
|
| 1261 |
-
border: 1px solid rgba(255,255,255,.08);
|
| 1262 |
-
}
|
| 1263 |
-
.deep-think-tv .step.active{
|
| 1264 |
-
opacity:1;
|
| 1265 |
-
box-shadow: 0 0 1.8vmin rgba(96,165,250,.28);
|
| 1266 |
-
}
|
| 1267 |
-
.deep-think-tv .step .mini{
|
| 1268 |
-
display:inline-block; width: .8vmin; height: .8vmin; border-radius:50%; margin-left: .6vmin;
|
| 1269 |
-
background:linear-gradient(135deg, #60a5fa, #a78bfa);
|
| 1270 |
-
box-shadow:0 0 .8vmin rgba(167,139,250,.6);
|
| 1271 |
-
}
|
| 1272 |
-
.deep-think-tv .progress{
|
| 1273 |
-
height: 1vmin; border-radius: .8vmin; overflow:hidden;
|
| 1274 |
-
background: rgba(0,0,0,.1);
|
| 1275 |
-
border:1px solid rgba(0,0,0,.08);
|
| 1276 |
-
}
|
| 1277 |
-
.dark .deep-think-tv .progress {
|
| 1278 |
-
background: rgba(255,255,255,.07);
|
| 1279 |
-
border:1px solid rgba(255,255,255,.06);
|
| 1280 |
-
}
|
| 1281 |
-
.deep-think-tv .bar{
|
| 1282 |
-
height:100%; width:0%;
|
| 1283 |
-
background: linear-gradient(90deg, #60a5fa, #a78bfa);
|
| 1284 |
-
box-shadow: 0 0 2.4vmin rgba(167,139,250,.45);
|
| 1285 |
-
transition: width .6s cubic-bezier(.4, .0, .2, 1);
|
| 1286 |
-
}
|
| 1287 |
-
.deep-think-tv .log{
|
| 1288 |
-
flex:1;
|
| 1289 |
-
display:flex; flex-direction:column; gap: 1vmin;
|
| 1290 |
-
padding: 1.2vmin;
|
| 1291 |
-
border-radius: 1.2vmin;
|
| 1292 |
-
overflow:auto;
|
| 1293 |
-
background: rgba(255,255,255,.4);
|
| 1294 |
-
border:1px solid rgba(255,255,255,.6);
|
| 1295 |
-
}
|
| 1296 |
-
.dark .deep-think-tv .log {
|
| 1297 |
-
background: linear-gradient(180deg, rgba(10,16,34,.65), rgba(10,16,34,.35));
|
| 1298 |
-
border:1px solid rgba(255,255,255,.06);
|
| 1299 |
-
}
|
| 1300 |
-
.deep-think-tv .row{
|
| 1301 |
-
display:flex; align-items:flex-start; gap: 1vmin;
|
| 1302 |
-
font-size: 1.5vmin;
|
| 1303 |
-
color: #3d4c66;
|
| 1304 |
-
animation: fade-in-item 0.4s ease-out;
|
| 1305 |
-
}
|
| 1306 |
-
.dark .deep-think-tv .row { color: #eaf1ff; }
|
| 1307 |
-
.deep-think-tv .row .icon{
|
| 1308 |
-
width: 1.8vmin; height: 1.8vmin; flex:none;
|
| 1309 |
-
border-radius: .6vmin;
|
| 1310 |
-
display:grid; place-items:center;
|
| 1311 |
-
font-size: 1.1vmin;
|
| 1312 |
-
background: rgba(0,0,0,.08);
|
| 1313 |
-
}
|
| 1314 |
-
.dark .deep-think-tv .row .icon {
|
| 1315 |
-
background: linear-gradient(135deg, rgba(96,165,250,.22), rgba(167,139,250,.22));
|
| 1316 |
-
box-shadow: inset 0 0 1vmin rgba(167,139,250,.22);
|
| 1317 |
-
}
|
| 1318 |
-
.deep-think-tv .final-answer-wrapper {
|
| 1319 |
-
transition: opacity 0.5s ease-in-out, max-height 0.5s ease-in-out;
|
| 1320 |
-
max-height: 0;
|
| 1321 |
-
opacity: 0;
|
| 1322 |
-
overflow: hidden;
|
| 1323 |
-
}
|
| 1324 |
-
.deep-think-tv .final-answer-wrapper.visible {
|
| 1325 |
-
max-height: 1000px; /* or a large enough value */
|
| 1326 |
-
opacity: 1;
|
| 1327 |
-
}
|
| 1328 |
-
@keyframes fade-in-item {
|
| 1329 |
-
from { opacity: 0; transform: translateY(5px); }
|
| 1330 |
-
to { opacity: 1; transform: translateY(0); }
|
| 1331 |
-
}
|
| 1332 |
-
|
| 1333 |
-
.ws-loading-container .dots {
|
| 1334 |
-
display: flex;
|
| 1335 |
-
gap: 8px;
|
| 1336 |
-
padding: 0.5rem 0.2rem;
|
| 1337 |
-
}
|
| 1338 |
-
|
| 1339 |
-
.ws-loading-container .dot {
|
| 1340 |
-
width: 14px;
|
| 1341 |
-
height: 14px;
|
| 1342 |
-
border-radius: 50%;
|
| 1343 |
-
background: linear-gradient(135deg, #3a8fff, #a44fff);
|
| 1344 |
-
animation: bounce 1s infinite ease-in-out;
|
| 1345 |
-
}
|
| 1346 |
-
|
| 1347 |
-
.ws-loading-container .dot:nth-child(2) {
|
| 1348 |
-
animation-delay: 0.2s;
|
| 1349 |
-
}
|
| 1350 |
-
|
| 1351 |
-
.ws-loading-container .dot:nth-child(3) {
|
| 1352 |
-
animation-delay: 0.4s;
|
| 1353 |
-
}
|
| 1354 |
-
|
| 1355 |
-
@keyframes bounce {
|
| 1356 |
-
0%, 80%, 100% {
|
| 1357 |
-
transform: translateY(0);
|
| 1358 |
-
}
|
| 1359 |
-
40% {
|
| 1360 |
-
transform: translateY(-8px);
|
| 1361 |
-
}
|
| 1362 |
-
}
|
| 1363 |
-
|
| 1364 |
-
/* Premium Feature Animation */
|
| 1365 |
-
@keyframes premium-glow-pulse {
|
| 1366 |
-
0%, 100% {
|
| 1367 |
-
transform: scale(1);
|
| 1368 |
-
box-shadow: 0 0 8px rgba(167, 139, 250, 0.4);
|
| 1369 |
-
}
|
| 1370 |
-
50% {
|
| 1371 |
-
transform: scale(1.05);
|
| 1372 |
-
box-shadow: 0 0 20px rgba(167, 139, 250, 0.8);
|
| 1373 |
-
}
|
| 1374 |
-
}
|
| 1375 |
-
|
| 1376 |
-
.animate-premium-lock > * {
|
| 1377 |
-
animation: premium-glow-pulse 0.8s ease-in-out;
|
| 1378 |
-
}
|
| 1379 |
-
|
| 1380 |
-
.premium-locked-item {
|
| 1381 |
-
position: relative;
|
| 1382 |
-
}
|
| 1383 |
-
|
| 1384 |
-
.premium-required-notice {
|
| 1385 |
-
position: absolute;
|
| 1386 |
-
bottom: -28px;
|
| 1387 |
-
left: 50%;
|
| 1388 |
-
transform: translateX(-50%) translateY(10px);
|
| 1389 |
-
background-color: #1f2937;
|
| 1390 |
-
color: #f9fafb;
|
| 1391 |
-
padding: 4px 10px;
|
| 1392 |
-
border-radius: 6px;
|
| 1393 |
-
font-size: 0.75rem;
|
| 1394 |
-
white-space: nowrap;
|
| 1395 |
-
opacity: 0;
|
| 1396 |
-
transition: all 0.3s ease-out;
|
| 1397 |
-
pointer-events: none;
|
| 1398 |
-
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
| 1399 |
-
}
|
| 1400 |
-
.dark .premium-required-notice {
|
| 1401 |
-
background-color: #f9fafb;
|
| 1402 |
-
color: #1f2937;
|
| 1403 |
-
}
|
| 1404 |
-
|
| 1405 |
-
.premium-locked-item.show-notice .premium-required-notice {
|
| 1406 |
-
opacity: 1;
|
| 1407 |
-
transform: translateX(-50%) translateY(0);
|
| 1408 |
-
}
|
| 1409 |
-
/* === START: REASONING HUD STYLES === */
|
| 1410 |
-
.reasoning-hud-container {
|
| 1411 |
-
padding: 1rem;
|
| 1412 |
-
box-sizing: border-box;
|
| 1413 |
-
width: 100%;
|
| 1414 |
-
}
|
| 1415 |
-
.reasoning-hud {
|
| 1416 |
-
width: 100%;
|
| 1417 |
-
aspect-ratio: 16 / 9;
|
| 1418 |
-
position: relative;
|
| 1419 |
-
border-radius: 12px;
|
| 1420 |
-
padding: 1.5vmin;
|
| 1421 |
-
isolation: isolate;
|
| 1422 |
-
transition: background .3s ease, box-shadow .3s ease;
|
| 1423 |
-
background: #eef2f7;
|
| 1424 |
-
box-shadow: 0 4vmin 9vmin rgba(0,0,0,.15), 0 0 0 1px #fff inset;
|
| 1425 |
-
overflow: hidden;
|
| 1426 |
-
}
|
| 1427 |
-
.dark .reasoning-hud {
|
| 1428 |
-
background: #111827;
|
| 1429 |
-
box-shadow: 0 4vmin 9vmin rgba(0,0,0,.35), 0 0 0 2px rgba(255,255,255,.04) inset;
|
| 1430 |
-
}
|
| 1431 |
-
|
| 1432 |
-
.reasoning-hud::before {
|
| 1433 |
-
content: "";
|
| 1434 |
-
position: absolute;
|
| 1435 |
-
inset: -2px;
|
| 1436 |
-
border-radius: calc(12px + 2px);
|
| 1437 |
-
padding: 2px;
|
| 1438 |
-
background: linear-gradient(135deg, #34d399, #3b82f6);
|
| 1439 |
-
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
| 1440 |
-
-webkit-mask-composite: xor;
|
| 1441 |
-
mask-composite: exclude;
|
| 1442 |
-
pointer-events: none;
|
| 1443 |
-
filter: blur(.3px);
|
| 1444 |
-
}
|
| 1445 |
-
|
| 1446 |
-
.reasoning-hud .grid-bg {
|
| 1447 |
-
position: absolute;
|
| 1448 |
-
inset: 0;
|
| 1449 |
-
background-image:
|
| 1450 |
-
linear-gradient(rgba(0, 160, 255, 0.1) 1px, transparent 1px),
|
| 1451 |
-
linear-gradient(90deg, rgba(0, 160, 255, 0.1) 1px, transparent 1px);
|
| 1452 |
-
background-size: 20px 20px;
|
| 1453 |
-
opacity: 0.5;
|
| 1454 |
-
animation: pan-grid 10s linear infinite;
|
| 1455 |
-
}
|
| 1456 |
-
.dark .reasoning-hud .grid-bg {
|
| 1457 |
-
background-image:
|
| 1458 |
-
linear-gradient(rgba(59, 130, 246, 0.15) 1px, transparent 1px),
|
| 1459 |
-
linear-gradient(90deg, rgba(59, 130, 246, 0.15) 1px, transparent 1px);
|
| 1460 |
-
}
|
| 1461 |
-
|
| 1462 |
-
@keyframes pan-grid {
|
| 1463 |
-
from { background-position: 0 0; }
|
| 1464 |
-
to { background-position: -20px -20px; }
|
| 1465 |
-
}
|
| 1466 |
-
|
| 1467 |
-
.reasoning-hud .ui-content {
|
| 1468 |
-
position: relative;
|
| 1469 |
-
z-index: 2;
|
| 1470 |
-
width: 100%;
|
| 1471 |
-
height: 100%;
|
| 1472 |
-
display: flex;
|
| 1473 |
-
flex-direction: column;
|
| 1474 |
-
padding: 1.5vmin;
|
| 1475 |
-
gap: 1.5vmin;
|
| 1476 |
-
color: #1f2937;
|
| 1477 |
-
}
|
| 1478 |
-
.dark .reasoning-hud .ui-content {
|
| 1479 |
-
color: #e5e7eb;
|
| 1480 |
-
}
|
| 1481 |
-
|
| 1482 |
-
.reasoning-hud .header {
|
| 1483 |
-
display: flex;
|
| 1484 |
-
flex-direction: column;
|
| 1485 |
-
gap: 1vmin;
|
| 1486 |
-
}
|
| 1487 |
-
|
| 1488 |
-
.reasoning-hud .badge {
|
| 1489 |
-
display: inline-flex;
|
| 1490 |
-
align-items: center;
|
| 1491 |
-
gap: .8vmin;
|
| 1492 |
-
padding: .8vmin 1.2vmin;
|
| 1493 |
-
border-radius: 9999px;
|
| 1494 |
-
font-weight: 600;
|
| 1495 |
-
font-size: 1.4vmin;
|
| 1496 |
-
background: rgba(255, 255, 255, 0.5);
|
| 1497 |
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
| 1498 |
-
backdrop-filter: blur(4px);
|
| 1499 |
-
}
|
| 1500 |
-
.dark .reasoning-hud .badge {
|
| 1501 |
-
background: rgba(31, 41, 55, 0.5);
|
| 1502 |
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 1503 |
-
}
|
| 1504 |
-
|
| 1505 |
-
.reasoning-hud .dot {
|
| 1506 |
-
width: .8vmin;
|
| 1507 |
-
height: .8vmin;
|
| 1508 |
-
border-radius: 50%;
|
| 1509 |
-
background: #34d399;
|
| 1510 |
-
box-shadow: 0 0 1vmin #34d399, 0 0 2vmin #34d399;
|
| 1511 |
-
animation: blink 1.4s infinite;
|
| 1512 |
-
}
|
| 1513 |
-
|
| 1514 |
-
.reasoning-hud .topic {
|
| 1515 |
-
font-size: 1.5vmin;
|
| 1516 |
-
color: #4b5563;
|
| 1517 |
-
overflow: hidden;
|
| 1518 |
-
white-space: nowrap;
|
| 1519 |
-
text-overflow: ellipsis;
|
| 1520 |
-
}
|
| 1521 |
-
.dark .reasoning-hud .topic {
|
| 1522 |
-
color: #9ca3af;
|
| 1523 |
-
}
|
| 1524 |
-
|
| 1525 |
-
.reasoning-hud .topic strong {
|
| 1526 |
-
font-weight: 700;
|
| 1527 |
-
color: #1d4ed8;
|
| 1528 |
-
}
|
| 1529 |
-
.dark .reasoning-hud .topic strong {
|
| 1530 |
-
color: #60a5fa;
|
| 1531 |
-
}
|
| 1532 |
-
|
| 1533 |
-
.reasoning-hud .progress {
|
| 1534 |
-
height: 1vmin;
|
| 1535 |
-
border-radius: .8vmin;
|
| 1536 |
-
overflow: hidden;
|
| 1537 |
-
background: rgba(0,0,0,.1);
|
| 1538 |
-
border: 1px solid rgba(0,0,0,.08);
|
| 1539 |
-
}
|
| 1540 |
-
.dark .reasoning-hud .progress {
|
| 1541 |
-
background: rgba(255,255,255,.1);
|
| 1542 |
-
border: 1px solid rgba(255,255,255,.1);
|
| 1543 |
-
}
|
| 1544 |
-
|
| 1545 |
-
.reasoning-hud .bar {
|
| 1546 |
-
height: 100%;
|
| 1547 |
-
width: 0%;
|
| 1548 |
-
background: linear-gradient(90deg, #34d399, #3b82f6);
|
| 1549 |
-
box-shadow: 0 0 2.4vmin rgba(59, 130, 246, .45);
|
| 1550 |
-
transition: width .6s cubic-bezier(.4, .0, .2, 1);
|
| 1551 |
-
}
|
| 1552 |
-
|
| 1553 |
-
.reasoning-hud .log {
|
| 1554 |
-
flex: 1;
|
| 1555 |
-
display: flex;
|
| 1556 |
-
flex-direction: column;
|
| 1557 |
-
gap: 1vmin;
|
| 1558 |
-
padding: 1.2vmin;
|
| 1559 |
-
border-radius: 1vmin;
|
| 1560 |
-
overflow: auto;
|
| 1561 |
-
background: rgba(255, 255, 255, 0.4);
|
| 1562 |
-
border: 1px solid rgba(0, 0, 0, 0.05);
|
| 1563 |
-
backdrop-filter: blur(2px);
|
| 1564 |
-
}
|
| 1565 |
-
.dark .reasoning-hud .log {
|
| 1566 |
-
background: rgba(31, 41, 55, 0.4);
|
| 1567 |
-
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 1568 |
-
}
|
| 1569 |
-
|
| 1570 |
-
.reasoning-hud .row {
|
| 1571 |
-
display: flex;
|
| 1572 |
-
align-items: flex-start;
|
| 1573 |
-
gap: 1vmin;
|
| 1574 |
-
font-size: 1.5vmin;
|
| 1575 |
-
color: #374151;
|
| 1576 |
-
animation: fade-in-item 0.4s ease-out;
|
| 1577 |
-
}
|
| 1578 |
-
.dark .reasoning-hud .row {
|
| 1579 |
-
color: #d1d5db;
|
| 1580 |
-
}
|
| 1581 |
-
|
| 1582 |
-
.reasoning-hud .row .icon {
|
| 1583 |
-
width: 1.8vmin;
|
| 1584 |
-
height: 1.8vmin;
|
| 1585 |
-
flex: none;
|
| 1586 |
-
color: #3b82f6;
|
| 1587 |
-
}
|
| 1588 |
-
.dark .reasoning-hud .row .icon {
|
| 1589 |
-
color: #60a5fa;
|
| 1590 |
-
}
|
| 1591 |
-
/* === END: REASONING HUD STYLES === */
|
| 1592 |
-
|
| 1593 |
-
@keyframes spin {
|
| 1594 |
-
to { transform: rotate(360deg); }
|
| 1595 |
-
}
|
| 1596 |
-
|
| 1597 |
-
#global-audio-player.visible {
|
| 1598 |
-
transform: translateY(0);
|
| 1599 |
-
}
|
| 1600 |
-
.action-button .loading-spinner {
|
| 1601 |
-
display: none;
|
| 1602 |
-
width: 1rem;
|
| 1603 |
-
height: 1rem;
|
| 1604 |
-
border-width: 2px;
|
| 1605 |
-
border-color: #9ca3af;
|
| 1606 |
-
border-top-color: #3b82f6;
|
| 1607 |
-
border-radius: 50%;
|
| 1608 |
-
animation: spin 0.8s linear infinite;
|
| 1609 |
-
}
|
| 1610 |
-
.dark .action-button .loading-spinner {
|
| 1611 |
-
border-color: #6b7280;
|
| 1612 |
-
border-top-color: #60a5fa;
|
| 1613 |
-
}
|
| 1614 |
-
.action-button.loading .speak-icon {
|
| 1615 |
-
display: none;
|
| 1616 |
-
}
|
| 1617 |
-
.action-button.loading .loading-spinner {
|
| 1618 |
-
display: block;
|
| 1619 |
-
}
|
| 1620 |
-
/* ... (کدهای قبلی شما) ... */
|
| 1621 |
-
|
| 1622 |
-
/* === START: استایلهای جدید برای دکمه پخش/توقف صدا === */
|
| 1623 |
-
.action-button .loading-spinner,
|
| 1624 |
-
.action-button .speak-icon,
|
| 1625 |
-
.action-button .pause-icon {
|
| 1626 |
-
display: none; /* در حالت عادی همه مخفی هستند */
|
| 1627 |
-
}
|
| 1628 |
-
|
| 1629 |
-
.action-button .speak-icon {
|
| 1630 |
-
display: block; /* آیکون پخش به صورت پیشفرض نمایش داده میشود */
|
| 1631 |
-
}
|
| 1632 |
-
|
| 1633 |
-
.action-button.loading .speak-icon,
|
| 1634 |
-
.action-button.loading .pause-icon {
|
| 1635 |
-
display: none;
|
| 1636 |
-
}
|
| 1637 |
-
.action-button.loading .loading-spinner {
|
| 1638 |
-
display: block;
|
| 1639 |
-
}
|
| 1640 |
-
|
| 1641 |
-
.action-button.playing .speak-icon,
|
| 1642 |
-
.action-button.playing .loading-spinner {
|
| 1643 |
-
display: none;
|
| 1644 |
-
}
|
| 1645 |
-
.action-button.playing .pause-icon {
|
| 1646 |
-
display: block !important;
|
| 1647 |
-
}
|
| 1648 |
-
/* === END: استایلهای جدید === */
|
| 1649 |
-
|
| 1650 |
-
|
| 1651 |
-
@keyframes spin {
|
| 1652 |
-
to { transform: rotate(360deg); }
|
| 1653 |
-
}
|
| 1654 |
-
|
| 1655 |
-
#global-audio-player.visible {
|
| 1656 |
-
transform: translateY(0);
|
| 1657 |
-
}
|
| 1658 |
-
/* START: CSS for Thoughts Toggle Switch */
|
| 1659 |
-
.tool-item-full-width {
|
| 1660 |
-
padding: 0.5rem 0.75rem;
|
| 1661 |
-
width: 100%;
|
| 1662 |
-
cursor: default;
|
| 1663 |
-
}
|
| 1664 |
-
.tool-item-full-width:hover {
|
| 1665 |
-
background-color: transparent !important;
|
| 1666 |
-
}
|
| 1667 |
-
.dark .tool-item-full-width:hover {
|
| 1668 |
-
background-color: transparent !important;
|
| 1669 |
-
}
|
| 1670 |
-
|
| 1671 |
-
.toggle-wrapper {
|
| 1672 |
-
display: flex;
|
| 1673 |
-
align-items: center;
|
| 1674 |
-
justify-content: space-between;
|
| 1675 |
-
width: 100%;
|
| 1676 |
-
}
|
| 1677 |
-
|
| 1678 |
-
.toggle-label {
|
| 1679 |
-
font-size: 16px;
|
| 1680 |
-
color: #374151;
|
| 1681 |
-
font-weight: 500;
|
| 1682 |
-
user-select: none;
|
| 1683 |
-
}
|
| 1684 |
-
.dark .toggle-label {
|
| 1685 |
-
color: #e5e7eb;
|
| 1686 |
-
}
|
| 1687 |
-
|
| 1688 |
-
.toggle-switch {
|
| 1689 |
-
--switch-width: 60px;
|
| 1690 |
-
--switch-height: 30px;
|
| 1691 |
-
--thumb-size: 24px;
|
| 1692 |
-
--padding: 3px;
|
| 1693 |
-
width: var(--switch-width);
|
| 1694 |
-
height: var(--switch-height);
|
| 1695 |
-
background-color: #d1d5db; /* Gray for inactive */
|
| 1696 |
-
border-radius: calc(var(--switch-height) / 2);
|
| 1697 |
-
position: relative;
|
| 1698 |
-
cursor: pointer;
|
| 1699 |
-
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
| 1700 |
-
box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.2);
|
| 1701 |
-
display: flex;
|
| 1702 |
-
align-items: center;
|
| 1703 |
-
padding: var(--padding);
|
| 1704 |
-
box-sizing: border-box;
|
| 1705 |
-
flex-shrink: 0;
|
| 1706 |
-
}
|
| 1707 |
-
.dark .toggle-switch {
|
| 1708 |
-
background-color: #4b5563;
|
| 1709 |
-
}
|
| 1710 |
-
|
| 1711 |
-
.toggle-switch.active {
|
| 1712 |
-
background-color: #28a745; /* Green for active */
|
| 1713 |
-
box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.25), 0 0 8px rgba(40, 167, 69, 0.4);
|
| 1714 |
-
}
|
| 1715 |
-
|
| 1716 |
-
.toggle-thumb {
|
| 1717 |
-
width: var(--thumb-size);
|
| 1718 |
-
height: var(--thumb-size);
|
| 1719 |
-
background: linear-gradient(145deg, #ffd700, #ccac00);
|
| 1720 |
-
border-radius: 50%;
|
| 1721 |
-
position: absolute;
|
| 1722 |
-
left: var(--padding);
|
| 1723 |
-
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.3s ease;
|
| 1724 |
-
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
|
| 1725 |
-
display: flex;
|
| 1726 |
-
justify-content: center;
|
| 1727 |
-
align-items: center;
|
| 1728 |
-
}
|
| 1729 |
-
|
| 1730 |
-
.toggle-switch.active .toggle-thumb {
|
| 1731 |
-
transform: translateX(calc(var(--switch-width) - var(--thumb-size) - 2 * var(--padding)));
|
| 1732 |
-
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3);
|
| 1733 |
-
}
|
| 1734 |
-
|
| 1735 |
-
.thumb-text {
|
| 1736 |
-
color: #4a4a4a;
|
| 1737 |
-
font-size: 11px;
|
| 1738 |
-
font-weight: 900;
|
| 1739 |
-
user-select: none;
|
| 1740 |
-
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5);
|
| 1741 |
-
}
|
| 1742 |
-
/* END: CSS for Thoughts Toggle Switch */
|
| 1743 |
-
|
| 1744 |
-
/* START: NEW STYLES for Final Answer Wrapper */
|
| 1745 |
-
.final-answer-wrapper {
|
| 1746 |
-
transition: opacity 0.5s ease-in-out, max-height 0.5s ease-in-out;
|
| 1747 |
-
max-height: 0;
|
| 1748 |
-
opacity: 0;
|
| 1749 |
-
overflow: hidden;
|
| 1750 |
-
}
|
| 1751 |
-
|
| 1752 |
-
.final-answer-wrapper.visible {
|
| 1753 |
-
max-height: 50000px;
|
| 1754 |
-
opacity: 1;
|
| 1755 |
-
}
|
| 1756 |
-
/* END: NEW STYLES for Final Answer Wrapper */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/js/api.js
DELETED
|
@@ -1,218 +0,0 @@
|
|
| 1 |
-
// --- START OF FILE api.js ---
|
| 2 |
-
|
| 3 |
-
// static/js/api.js
|
| 4 |
-
|
| 5 |
-
import * as state from './state.js';
|
| 6 |
-
import * as db from './db.js';
|
| 7 |
-
import * as chatUI from './ui/chat.js';
|
| 8 |
-
import * as toolUI from './ui/tools.js';
|
| 9 |
-
|
| 10 |
-
// *** START: MODIFIED - بازگرداندن تابع به حالت استاندارد ***
|
| 11 |
-
// این تابع اکنون در صورت اتمام زمان با موفقیت resolve میشود و فقط در صورت لغو شدن reject میکند.
|
| 12 |
-
export function interruptibleTimeout(duration, signal) {
|
| 13 |
-
return new Promise((resolve, reject) => {
|
| 14 |
-
if (signal.aborted) {
|
| 15 |
-
return reject(new DOMException('Aborted', 'AbortError'));
|
| 16 |
-
}
|
| 17 |
-
const timeoutId = setTimeout(resolve, duration);
|
| 18 |
-
signal.addEventListener('abort', () => {
|
| 19 |
-
clearTimeout(timeoutId);
|
| 20 |
-
reject(new DOMException('Aborted', 'AbortError'));
|
| 21 |
-
});
|
| 22 |
-
});
|
| 23 |
-
}
|
| 24 |
-
// *** END: MODIFIED ***
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
export async function getBrowserFingerprint() {
|
| 28 |
-
const components = [navigator.userAgent, navigator.language, screen.width + 'x' + screen.height, new Date().getTimezoneOffset()];
|
| 29 |
-
try {
|
| 30 |
-
const canvas = document.createElement('canvas');
|
| 31 |
-
const ctx = canvas.getContext('2d');
|
| 32 |
-
ctx.textBaseline = "top";
|
| 33 |
-
ctx.font = "14px 'Arial'";
|
| 34 |
-
ctx.textBaseline = "alphabetic";
|
| 35 |
-
ctx.fillStyle = "#f60";
|
| 36 |
-
ctx.fillRect(125, 1, 62, 20);
|
| 37 |
-
ctx.fillStyle = "#069";
|
| 38 |
-
ctx.fillText("a1b2c3d4e5f6g7h8i9j0_!@#$%^&*()", 2, 15);
|
| 39 |
-
components.push(canvas.toDataURL());
|
| 40 |
-
} catch (e) {
|
| 41 |
-
components.push("canvas-error");
|
| 42 |
-
}
|
| 43 |
-
const fingerprintString = components.join('~~~');
|
| 44 |
-
let hash = 0;
|
| 45 |
-
for (let i = 0; i < fingerprintString.length; i++) {
|
| 46 |
-
const char = fingerprintString.charCodeAt(i);
|
| 47 |
-
hash = ((hash << 5) - hash) + char;
|
| 48 |
-
hash |= 0;
|
| 49 |
-
}
|
| 50 |
-
return 'fp_' + Math.abs(hash).toString(16);
|
| 51 |
-
}
|
| 52 |
-
|
| 53 |
-
export async function convertTextToFile(content, format, buttonElement) {
|
| 54 |
-
chatUI.showLoadingOnButton(buttonElement, true);
|
| 55 |
-
try {
|
| 56 |
-
const convertFormData = new FormData();
|
| 57 |
-
convertFormData.append('content', content);
|
| 58 |
-
convertFormData.append('format', format);
|
| 59 |
-
const convertResponse = await fetch('https://texttopdf-5irq.onrender.com/', { method: 'POST', body: convertFormData });
|
| 60 |
-
if (!convertResponse.ok) throw new Error(`خطا در ارتباط با سرور تبدیل: ${convertResponse.statusText}`);
|
| 61 |
-
const fileBlob = await convertResponse.blob();
|
| 62 |
-
const fileName = `alpha-export-${Date.now()}.${format}`;
|
| 63 |
-
const uploadFormData = new FormData();
|
| 64 |
-
uploadFormData.append('image', fileBlob, fileName);
|
| 65 |
-
const uploadResponse = await fetch('https://www.aisada.ir/hamed/upload.php', { method: 'POST', body: uploadFormData });
|
| 66 |
-
if (!uploadResponse.ok) {
|
| 67 |
-
const errorText = await uploadResponse.text().catch(() => `HTTP ${uploadResponse.status}`);
|
| 68 |
-
throw new Error(`آپلود فایل ساخته شده به سرور شما ناموفق بود: ${errorText}`);
|
| 69 |
-
}
|
| 70 |
-
const uploadData = await uploadResponse.json();
|
| 71 |
-
if (uploadData.success && uploadData.url) {
|
| 72 |
-
window.parent.postMessage({ type: 'OPEN_EXTERNAL_URL', url: uploadData.url }, '*');
|
| 73 |
-
} else {
|
| 74 |
-
throw new Error(uploadData.message || 'پاسخ سرور آپلود شما پس از ساخت فایل، نامعتبر بود.');
|
| 75 |
-
}
|
| 76 |
-
} catch (error) {
|
| 77 |
-
console.error('خطا در فرآیند تبدیل و آپلود فایل:', error);
|
| 78 |
-
alert(`متاسفانه در آمادهسازی فایل برای باز کردن خطایی رخ داد: ${error.message}`);
|
| 79 |
-
} finally {
|
| 80 |
-
chatUI.showLoadingOnButton(buttonElement, false);
|
| 81 |
-
}
|
| 82 |
-
}
|
| 83 |
-
|
| 84 |
-
export async function processAndUploadFile(file) {
|
| 85 |
-
const readFileAsBase64 = (file) => new Promise((resolve, reject) => {
|
| 86 |
-
const reader = new FileReader();
|
| 87 |
-
reader.onload = () => resolve(reader.result.split(',')[1]);
|
| 88 |
-
reader.onerror = (error) => reject(error);
|
| 89 |
-
reader.readAsDataURL(file);
|
| 90 |
-
});
|
| 91 |
-
try {
|
| 92 |
-
const [fileId, base64Data] = await Promise.all([db.storeFile(file), readFileAsBase64(file)]);
|
| 93 |
-
const blobUrl = URL.createObjectURL(file);
|
| 94 |
-
return { id: fileId, blobUrl, base64Data, name: file.name, mimeType: file.type };
|
| 95 |
-
} catch (error) {
|
| 96 |
-
console.error("خطا در پردازش و ذخیره فایل:", error);
|
| 97 |
-
throw error;
|
| 98 |
-
}
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
export async function getChatStream(conversationHistory, signal) {
|
| 102 |
-
const activeChat = state.getActiveChat();
|
| 103 |
-
const messagesForApi = conversationHistory
|
| 104 |
-
.filter(msg => !msg.isTemporary)
|
| 105 |
-
.map(msg => {
|
| 106 |
-
const apiMsg = { role: msg.role, parts: [] };
|
| 107 |
-
msg.parts.forEach(part => {
|
| 108 |
-
if (part.text) apiMsg.parts.push({ type: 'text', text: part.text });
|
| 109 |
-
if (part.base64Data && part.mimeType) apiMsg.parts.push({ base64Data: part.base64Data, mimeType: part.mimeType });
|
| 110 |
-
});
|
| 111 |
-
return apiMsg;
|
| 112 |
-
}).filter(msg => msg.parts.length > 0);
|
| 113 |
-
|
| 114 |
-
const bodyPayload = {
|
| 115 |
-
messages: messagesForApi,
|
| 116 |
-
id: 'chat_' + Date.now(),
|
| 117 |
-
trigger: 'submit-message',
|
| 118 |
-
show_thoughts: activeChat ? activeChat.showThoughts : false
|
| 119 |
-
};
|
| 120 |
-
|
| 121 |
-
const response = await fetch('/chat', {
|
| 122 |
-
method: 'POST',
|
| 123 |
-
headers: { 'Content-Type': 'application/json' },
|
| 124 |
-
signal: signal,
|
| 125 |
-
body: JSON.stringify(bodyPayload),
|
| 126 |
-
});
|
| 127 |
-
|
| 128 |
-
if (!response.ok) {
|
| 129 |
-
const errorText = await response.text();
|
| 130 |
-
throw new Error(`خطای شبکه: ${response.status} - ${errorText}`);
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
return response;
|
| 134 |
-
}
|
| 135 |
-
|
| 136 |
-
export async function readStreamAndDisplay(response, modelBubbleOuterDivElement) {
|
| 137 |
-
let fullBotResponse = "";
|
| 138 |
-
const activeChat = state.getActiveChat();
|
| 139 |
-
const activeTool = state.getActiveTool();
|
| 140 |
-
|
| 141 |
-
try {
|
| 142 |
-
const reader = response.body.getReader();
|
| 143 |
-
const decoder = new TextDecoder();
|
| 144 |
-
let buffer = '';
|
| 145 |
-
|
| 146 |
-
while (true) {
|
| 147 |
-
const { value, done } = await reader.read();
|
| 148 |
-
if (done) break;
|
| 149 |
-
|
| 150 |
-
buffer += decoder.decode(value, { stream: true });
|
| 151 |
-
const events = buffer.split('\n\n');
|
| 152 |
-
buffer = events.pop();
|
| 153 |
-
|
| 154 |
-
for (const event of events) {
|
| 155 |
-
if (event.startsWith('data: ')) {
|
| 156 |
-
const dataString = event.substring(6);
|
| 157 |
-
if (dataString.trim() === '[DONE]') break;
|
| 158 |
-
try {
|
| 159 |
-
const jsonData = JSON.parse(dataString);
|
| 160 |
-
if (jsonData.type === 'thought' && jsonData.content) {
|
| 161 |
-
chatUI.streamThought(jsonData.content, modelBubbleOuterDivElement);
|
| 162 |
-
} else if (jsonData.choices?.[0]?.delta?.content) {
|
| 163 |
-
const textChunk = jsonData.choices[0].delta.content;
|
| 164 |
-
fullBotResponse += textChunk;
|
| 165 |
-
if (activeTool === 'deep-think' || activeTool === 'reasoning') {
|
| 166 |
-
chatUI.streamFinalText(fullBotResponse, modelBubbleOuterDivElement);
|
| 167 |
-
} else {
|
| 168 |
-
const hasThoughtsUI = modelBubbleOuterDivElement.querySelector('.thinking-panel-wrapper');
|
| 169 |
-
if (hasThoughtsUI) chatUI.streamFinalText(fullBotResponse, modelBubbleOuterDivElement);
|
| 170 |
-
else chatUI.streamFreeWsChunk(modelBubbleOuterDivElement, fullBotResponse);
|
| 171 |
-
}
|
| 172 |
-
} else if (jsonData.type === 'error') {
|
| 173 |
-
throw new Error(jsonData.message || 'خطای ناشناخته از سرور');
|
| 174 |
-
}
|
| 175 |
-
} catch (e) {
|
| 176 |
-
console.warn('خطا در پردازش قطعه JSON:', dataString, e);
|
| 177 |
-
}
|
| 178 |
-
}
|
| 179 |
-
}
|
| 180 |
-
}
|
| 181 |
-
} catch (error) {
|
| 182 |
-
throw error;
|
| 183 |
-
} finally {
|
| 184 |
-
if (fullBotResponse) {
|
| 185 |
-
const finalMessageObject = {
|
| 186 |
-
role: 'assistant',
|
| 187 |
-
parts: [{ text: fullBotResponse }],
|
| 188 |
-
toolUsed: activeTool,
|
| 189 |
-
wasGeneratedWithThoughts: activeChat ? activeChat.showThoughts : false
|
| 190 |
-
};
|
| 191 |
-
const tempMsgIndex = activeChat.messages.findIndex(m => m.isTemporary);
|
| 192 |
-
if (tempMsgIndex > -1) activeChat.messages[tempMsgIndex] = finalMessageObject;
|
| 193 |
-
else activeChat.messages.push(finalMessageObject);
|
| 194 |
-
|
| 195 |
-
if (activeTool === 'deep-think') {
|
| 196 |
-
chatUI.finalizeFinalText(modelBubbleOuterDivElement, fullBotResponse);
|
| 197 |
-
toolUI.hideDeepThinkPanel(modelBubbleOuterDivElement);
|
| 198 |
-
} else if (activeTool === 'reasoning') {
|
| 199 |
-
chatUI.finalizeFinalText(modelBubbleOuterDivElement, fullBotResponse);
|
| 200 |
-
toolUI.hideReasoningPanel(modelBubbleOuterDivElement);
|
| 201 |
-
} else {
|
| 202 |
-
const hasThoughtsUI = modelBubbleOuterDivElement.querySelector('.thinking-panel-wrapper');
|
| 203 |
-
if (hasThoughtsUI) chatUI.finalizeFinalText(modelBubbleOuterDivElement, fullBotResponse);
|
| 204 |
-
else chatUI.finalizeFreeWsMessage(modelBubbleOuterDivElement, fullBotResponse);
|
| 205 |
-
}
|
| 206 |
-
chatUI.updateMessageActions(modelBubbleOuterDivElement, finalMessageObject, false, true);
|
| 207 |
-
} else {
|
| 208 |
-
const tempMsgIndex = activeChat.messages.findIndex(m => m.isTemporary);
|
| 209 |
-
if (tempMsgIndex > -1) {
|
| 210 |
-
if(!modelBubbleOuterDivElement.querySelector('.message-content')?.innerText.includes('متوقف شد')) {
|
| 211 |
-
activeChat.messages.splice(tempMsgIndex, 1);
|
| 212 |
-
const tempElement = document.getElementById('chat-window').querySelector(`.message-entry[data-index="${tempMsgIndex}"]`);
|
| 213 |
-
if (tempElement) tempElement.remove();
|
| 214 |
-
}
|
| 215 |
-
}
|
| 216 |
-
}
|
| 217 |
-
}
|
| 218 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/js/db.js
DELETED
|
@@ -1,153 +0,0 @@
|
|
| 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
DELETED
|
@@ -1,649 +0,0 @@
|
|
| 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 |
-
if (state.isMessageLimitReached(checkUserPremiumStatus())) {
|
| 266 |
-
chatUI.showLimitReachedUpgrade();
|
| 267 |
-
return;
|
| 268 |
-
}
|
| 269 |
-
|
| 270 |
-
const activeChat = state.getActiveChat();
|
| 271 |
-
if (!activeChat) return;
|
| 272 |
-
|
| 273 |
-
const userMessageText = dom.messageInput.value.trim();
|
| 274 |
-
if (!userMessageText && !state.attachedFile) return;
|
| 275 |
-
|
| 276 |
-
chatUI.setGeneratingState(true);
|
| 277 |
-
let modelBubbleOuterDiv;
|
| 278 |
-
|
| 279 |
-
try {
|
| 280 |
-
const isFirstMessageOfChat = activeChat.messages.length === 0;
|
| 281 |
-
if (isFirstMessageOfChat && dom.chatWindow.querySelector('.welcome-screen')) {
|
| 282 |
-
dom.chatWindow.querySelector('.welcome-screen').remove();
|
| 283 |
-
}
|
| 284 |
-
|
| 285 |
-
const userParts = [];
|
| 286 |
-
let fileAttachedInThisTurn = null;
|
| 287 |
-
if (state.attachedFile) {
|
| 288 |
-
fileAttachedInThisTurn = { ...state.attachedFile };
|
| 289 |
-
userParts.push({
|
| 290 |
-
id: fileAttachedInThisTurn.id, blobUrl: fileAttachedInThisTurn.blobUrl,
|
| 291 |
-
mimeType: fileAttachedInThisTurn.mimeType, name: fileAttachedInThisTurn.name,
|
| 292 |
-
base64Data: fileAttachedInThisTurn.base64Data
|
| 293 |
-
});
|
| 294 |
-
chatUI.hideFilePreview();
|
| 295 |
-
state.setAttachedFile(null);
|
| 296 |
-
}
|
| 297 |
-
if (userMessageText) userParts.push({ text: userMessageText });
|
| 298 |
-
|
| 299 |
-
const newUserMessage = { role: 'user', parts: userParts, tool: state.getActiveTool() };
|
| 300 |
-
activeChat.messages.push(newUserMessage);
|
| 301 |
-
|
| 302 |
-
state.incrementMessageCount(checkUserPremiumStatus());
|
| 303 |
-
|
| 304 |
-
await chatUI.addMessageToUI(newUserMessage, activeChat.messages.length - 1, {isLastUser: true, animate: true});
|
| 305 |
-
|
| 306 |
-
const modelPlaceholderMessage = { role: 'assistant', isTemporary: true, parts: [] };
|
| 307 |
-
activeChat.messages.push(modelPlaceholderMessage);
|
| 308 |
-
modelBubbleOuterDiv = await chatUI.addMessageToUI(modelPlaceholderMessage, activeChat.messages.length - 1, {animate: true});
|
| 309 |
-
|
| 310 |
-
const historyForApi = activeChat.messages.map((msg, index) => {
|
| 311 |
-
const isLastMessage = index === activeChat.messages.length - 1;
|
| 312 |
-
const isUserMessageJustSent = index === activeChat.messages.length - 2;
|
| 313 |
-
|
| 314 |
-
if (isUserMessageJustSent) {
|
| 315 |
-
return JSON.parse(JSON.stringify(msg));
|
| 316 |
-
}
|
| 317 |
-
if (isLastMessage) {
|
| 318 |
-
return msg;
|
| 319 |
-
}
|
| 320 |
-
|
| 321 |
-
const cleanMsg = { role: msg.role, parts: [] };
|
| 322 |
-
if (msg.parts) {
|
| 323 |
-
msg.parts.forEach(part => {
|
| 324 |
-
if (part.text) {
|
| 325 |
-
cleanMsg.parts.push({ text: part.text });
|
| 326 |
-
}
|
| 327 |
-
});
|
| 328 |
-
}
|
| 329 |
-
return cleanMsg;
|
| 330 |
-
});
|
| 331 |
-
|
| 332 |
-
const toolPrefix = state.getActiveToolPrefix();
|
| 333 |
-
const lastUserMsgInHistory = historyForApi.findLast(m => m.role === 'user');
|
| 334 |
-
const textPartInHistory = lastUserMsgInHistory.parts.find(p => p.text);
|
| 335 |
-
if (textPartInHistory) {
|
| 336 |
-
textPartInHistory.text = (toolPrefix ? toolPrefix : '') + (textPartInHistory.text || '');
|
| 337 |
-
} else if (toolPrefix) {
|
| 338 |
-
lastUserMsgInHistory.parts.push({ text: toolPrefix });
|
| 339 |
-
}
|
| 340 |
-
|
| 341 |
-
if (isFirstMessageOfChat && userMessageText) {
|
| 342 |
-
activeChat.title = userMessageText.substring(0, 30);
|
| 343 |
-
chatUI.renderHistoryList();
|
| 344 |
-
}
|
| 345 |
-
|
| 346 |
-
dom.messageInput.value = '';
|
| 347 |
-
dom.messageInput.dispatchEvent(new Event('input'));
|
| 348 |
-
|
| 349 |
-
const activeTool = state.getActiveTool();
|
| 350 |
-
if (activeTool === 'deep-think' || activeTool === 'reasoning') {
|
| 351 |
-
if (activeTool === 'deep-think') toolUI.updateDeepThinkPanel({ topic: userMessageText || 'فایل ضمیمه شده' }, modelBubbleOuterDiv);
|
| 352 |
-
else if (activeTool === 'reasoning') toolUI.updateReasoningPanel({ topic: userMessageText || 'فایل ضمیمه شده' }, modelBubbleOuterDiv);
|
| 353 |
-
|
| 354 |
-
const progressBar = modelBubbleOuterDiv.querySelector('.bar');
|
| 355 |
-
|
| 356 |
-
if (progressBar) {
|
| 357 |
-
requestAnimationFrame(() => {
|
| 358 |
-
progressBar.style.transition = 'width 30s ease-out';
|
| 359 |
-
progressBar.style.width = '100%';
|
| 360 |
-
});
|
| 361 |
-
}
|
| 362 |
-
}
|
| 363 |
-
|
| 364 |
-
// *** تغییر مهم: حذف منطق Retry کلاینت و تایماوت کوتاه ***
|
| 365 |
-
// ما فقط یک درخواست میزنیم و صبر میکنیم. بکاند خودش چرخش کلید را انجام میدهد.
|
| 366 |
-
// به این ترتیب خطای "Server did not respond" که ناشی از عجله کلاینت بود حذف میشود.
|
| 367 |
-
|
| 368 |
-
const response = await api.getChatStream(historyForApi, state.globalAbortController.signal);
|
| 369 |
-
await api.readStreamAndDisplay(response, modelBubbleOuterDiv);
|
| 370 |
-
|
| 371 |
-
} catch (error) {
|
| 372 |
-
if (error.name !== 'AbortError') {
|
| 373 |
-
console.error("خطا در هنگام تولید پیام:", error);
|
| 374 |
-
// اگر خطای واقعی رخ داد، فقط در کنسول لاگ میکنیم و به کاربر چیزی نشان نمیدهیم یا یک متن عمومی
|
| 375 |
-
// طبق خواسته شما که گفتید خطا نشان داده نشود.
|
| 376 |
-
if (modelBubbleOuterDiv) {
|
| 377 |
-
// اینجا میتوانیم هیچ کاری نکنیم یا یک متن خالی بگذاریم
|
| 378 |
-
// اما برای جلوگیری از گیر کردن، وضعیت را ریست میکنیم
|
| 379 |
-
}
|
| 380 |
-
} else {
|
| 381 |
-
if (modelBubbleOuterDiv && !modelBubbleOuterDiv.querySelector('.message-content')?.innerText.includes('متوقف شد')) {
|
| 382 |
-
const contentArea = modelBubbleOuterDiv.querySelector('.message-content') || modelBubbleOuterDiv;
|
| 383 |
-
contentArea.innerHTML += '<p class="text-xs text-slate-500 mt-2 text-center p-4">-- عملیات متوقف شد --</p>';
|
| 384 |
-
}
|
| 385 |
-
}
|
| 386 |
-
} finally {
|
| 387 |
-
chatUI.resetState();
|
| 388 |
-
state.saveSessions();
|
| 389 |
-
state.setActiveToolPrefix(null);
|
| 390 |
-
state.setActiveTool(null);
|
| 391 |
-
toolUI.updateToolsButton(null);
|
| 392 |
-
}
|
| 393 |
-
});
|
| 394 |
-
|
| 395 |
-
dom.chatWindow.addEventListener('click', async (e) => {
|
| 396 |
-
const button = e.target.closest('.action-button');
|
| 397 |
-
if (!button) return;
|
| 398 |
-
const action = button.dataset.action;
|
| 399 |
-
const messageEntry = button.closest('.message-entry');
|
| 400 |
-
if (!messageEntry) return;
|
| 401 |
-
const messageIndex = parseInt(messageEntry.dataset.index, 10);
|
| 402 |
-
const activeChat = state.getActiveChat();
|
| 403 |
-
if (!activeChat || isNaN(messageIndex)) return;
|
| 404 |
-
const message = activeChat.messages[messageIndex];
|
| 405 |
-
if (action === 'copy') {
|
| 406 |
-
const textToCopy = message.parts?.find(p => p.text)?.text || '';
|
| 407 |
-
if (textToCopy) navigator.clipboard.writeText(textToCopy).then(() => chatUI.showCopyFeedback(button));
|
| 408 |
-
} else if (action === 'like' || action === 'dislike') {
|
| 409 |
-
chatUI.handleLikeDislike(button, messageEntry);
|
| 410 |
-
} else if (action === 'speak') {
|
| 411 |
-
const audioState = ttsUI.getAudioState();
|
| 412 |
-
if (audioState.messageIndex === messageIndex && (audioState.status === 'running' || audioState.status === 'suspended')) {
|
| 413 |
-
ttsUI.stopAudio();
|
| 414 |
-
return;
|
| 415 |
-
}
|
| 416 |
-
if (ttsUI.hasCacheForMessage(messageIndex)) {
|
| 417 |
-
ttsUI.playFromCache(messageIndex, button);
|
| 418 |
-
return;
|
| 419 |
-
}
|
| 420 |
-
const fullText = message.parts?.find(p => p.text)?.text;
|
| 421 |
-
if (!fullText) return;
|
| 422 |
-
const codeBlockRegex = /```[\s\S]*?```/g;
|
| 423 |
-
const textToSpeak = fullText.replace(codeBlockRegex, '').trim();
|
| 424 |
-
if (!textToSpeak) {
|
| 425 |
-
alert("محتوای متنی برای خواندن وجود ندارد (فقط کد شناسایی شد).");
|
| 426 |
-
return;
|
| 427 |
-
}
|
| 428 |
-
ttsUI.stream(messageIndex, textToSpeak, button);
|
| 429 |
-
} else if (action === 'regenerate') {
|
| 430 |
-
if (state.isGenerating) return;
|
| 431 |
-
chatUI.setGeneratingState(true);
|
| 432 |
-
const lastModelMessageIndex = state.findLastIndex(activeChat.messages, msg => msg.role === 'assistant');
|
| 433 |
-
if (messageIndex === lastModelMessageIndex) {
|
| 434 |
-
ttsUI.clearCacheForMessage(messageIndex);
|
| 435 |
-
activeChat.messages.length = messageIndex;
|
| 436 |
-
messageEntry.remove();
|
| 437 |
-
const lastUserMessageIndex = state.findLastIndex(activeChat.messages, msg => msg.role === 'user');
|
| 438 |
-
if (lastUserMessageIndex !== -1) {
|
| 439 |
-
const lastUserMessageElement = dom.chatWindow.querySelector(`.message-entry[data-index="${lastUserMessageIndex}"]`);
|
| 440 |
-
if (lastUserMessageElement) chatUI.updateMessageActions(lastUserMessageElement, activeChat.messages[lastUserMessageIndex], true, false);
|
| 441 |
-
}
|
| 442 |
-
const modelPlaceholderMessage = { role: 'assistant', isTemporary: true, parts: [] };
|
| 443 |
-
activeChat.messages.push(modelPlaceholderMessage);
|
| 444 |
-
const newModelBubble = await chatUI.addMessageToUI(modelPlaceholderMessage, activeChat.messages.length - 1, { animate: true });
|
| 445 |
-
try {
|
| 446 |
-
const lastUserMessage = activeChat.messages.findLast(m => m.role === 'user');
|
| 447 |
-
const lastTool = lastUserMessage?.tool;
|
| 448 |
-
|
| 449 |
-
const historyForApi = activeChat.messages.map((msg, index) => {
|
| 450 |
-
if (index === activeChat.messages.length - 1) return msg;
|
| 451 |
-
if (index === activeChat.messages.length - 2) return JSON.parse(JSON.stringify(msg));
|
| 452 |
-
const cleanMsg = { role: msg.role, parts: [] };
|
| 453 |
-
if (msg.parts) msg.parts.forEach(part => { if (part.text) cleanMsg.parts.push({ text: part.text }); });
|
| 454 |
-
return cleanMsg;
|
| 455 |
-
});
|
| 456 |
-
|
| 457 |
-
if (lastTool === 'deep-think' || lastTool === 'reasoning') {
|
| 458 |
-
if (lastTool === 'deep-think') toolUI.updateDeepThinkPanel({ topic: lastUserMessage.parts.find(p=>p.text)?.text || 'فایل ضمیمه شده' }, newModelBubble);
|
| 459 |
-
if (lastTool === 'reasoning') toolUI.updateReasoningPanel({ topic: lastUserMessage.parts.find(p=>p.text)?.text || 'فایل ضمیمه شده' }, newModelBubble);
|
| 460 |
-
const progressBar = newModelBubble.querySelector('.bar');
|
| 461 |
-
if (progressBar) {
|
| 462 |
-
requestAnimationFrame(() => {
|
| 463 |
-
progressBar.style.transition = 'width 30s ease-out';
|
| 464 |
-
progressBar.style.width = '100%';
|
| 465 |
-
});
|
| 466 |
-
}
|
| 467 |
-
}
|
| 468 |
-
|
| 469 |
-
const response = await api.getChatStream(historyForApi, state.globalAbortController.signal);
|
| 470 |
-
await api.readStreamAndDisplay(response, newModelBubble);
|
| 471 |
-
|
| 472 |
-
} catch(error) {
|
| 473 |
-
if (error.name !== 'AbortError') console.error("Regeneration failed:", error);
|
| 474 |
-
} finally {
|
| 475 |
-
chatUI.resetState();
|
| 476 |
-
state.saveSessions();
|
| 477 |
-
}
|
| 478 |
-
} else {
|
| 479 |
-
chatUI.resetState();
|
| 480 |
-
}
|
| 481 |
-
} else if (action === 'edit') {
|
| 482 |
-
if (state.isGenerating) return;
|
| 483 |
-
const lastUserMessageIndex = state.findLastIndex(activeChat.messages, msg => msg.role === 'user');
|
| 484 |
-
if (messageIndex === lastUserMessageIndex) {
|
| 485 |
-
const textPart = message.parts.find(p => p.text);
|
| 486 |
-
const filePart = message.parts.find(p => p.id);
|
| 487 |
-
if (textPart || filePart) {
|
| 488 |
-
modalUI.showEditModal(textPart ? textPart.text : '', async (newText) => {
|
| 489 |
-
chatUI.setGeneratingState(true);
|
| 490 |
-
try {
|
| 491 |
-
const allMessagesInDOM = dom.chatWindow.querySelectorAll('.message-entry');
|
| 492 |
-
allMessagesInDOM.forEach(msgEl => {
|
| 493 |
-
const idx = parseInt(msgEl.dataset.index, 10);
|
| 494 |
-
if (idx >= messageIndex) {
|
| 495 |
-
ttsUI.clearCacheForMessage(idx);
|
| 496 |
-
msgEl.remove();
|
| 497 |
-
}
|
| 498 |
-
});
|
| 499 |
-
activeChat.messages.length = messageIndex;
|
| 500 |
-
const newParts = [];
|
| 501 |
-
if (filePart) {
|
| 502 |
-
const file = await db.getFile(filePart.id);
|
| 503 |
-
const blobUrl = URL.createObjectURL(file);
|
| 504 |
-
const base64 = await api.processAndUploadFile(file).then(d => d.base64Data);
|
| 505 |
-
newParts.push({ ...filePart, blobUrl, base64Data: base64 });
|
| 506 |
-
}
|
| 507 |
-
if (newText.trim()) newParts.push({ text: newText });
|
| 508 |
-
if (newParts.length > 0) {
|
| 509 |
-
const editedUserMessage = { role: 'user', parts: newParts };
|
| 510 |
-
activeChat.messages.push(editedUserMessage);
|
| 511 |
-
await chatUI.addMessageToUI(editedUserMessage, activeChat.messages.length - 1, { isLastUser: true, animate: true });
|
| 512 |
-
}
|
| 513 |
-
const modelPlaceholderMessage = { role: 'assistant', isTemporary: true, parts: [] };
|
| 514 |
-
activeChat.messages.push(modelPlaceholderMessage);
|
| 515 |
-
const newModelBubble = await chatUI.addMessageToUI(modelPlaceholderMessage, activeChat.messages.length - 1, { animate: true });
|
| 516 |
-
|
| 517 |
-
const historyForApi = activeChat.messages.map((msg, index) => {
|
| 518 |
-
if (index >= activeChat.messages.length - 2) return JSON.parse(JSON.stringify(msg));
|
| 519 |
-
const cleanMsg = { role: msg.role, parts: [] };
|
| 520 |
-
if (msg.parts) msg.parts.forEach(part => { if (part.text) cleanMsg.parts.push({ text: part.text }); });
|
| 521 |
-
return cleanMsg;
|
| 522 |
-
});
|
| 523 |
-
|
| 524 |
-
const response = await api.getChatStream(historyForApi, state.globalAbortController.signal);
|
| 525 |
-
await api.readStreamAndDisplay(response, newModelBubble);
|
| 526 |
-
} catch (error) {
|
| 527 |
-
if (error.name !== 'AbortError') console.error("Edit failed:", error);
|
| 528 |
-
} finally {
|
| 529 |
-
chatUI.resetState();
|
| 530 |
-
state.saveSessions();
|
| 531 |
-
}
|
| 532 |
-
});
|
| 533 |
-
}
|
| 534 |
-
}
|
| 535 |
-
}
|
| 536 |
-
else if (action === 'show-message-menu') {
|
| 537 |
-
modalUI.showMessageMenu(e, messageIndex, activeChat, chatUI.escapeHTML);
|
| 538 |
-
}
|
| 539 |
-
});
|
| 540 |
-
|
| 541 |
-
dom.historyItemMenu.addEventListener('click', (e) => {
|
| 542 |
-
const button = e.target.closest('.menu-item');
|
| 543 |
-
if (!button) return;
|
| 544 |
-
const action = button.dataset.action;
|
| 545 |
-
const format = button.dataset.format;
|
| 546 |
-
const sessionId = dom.historyItemMenu.dataset.sessionId;
|
| 547 |
-
const session = state.chatSessions.find(s => s.id === sessionId);
|
| 548 |
-
if (!session) return;
|
| 549 |
-
if (action === 'rename') {
|
| 550 |
-
modalUI.showRenameModal(session.title, (newTitle) => {
|
| 551 |
-
session.title = newTitle;
|
| 552 |
-
state.saveSessions();
|
| 553 |
-
chatUI.renderHistoryList();
|
| 554 |
-
});
|
| 555 |
-
} else if (action === 'delete') {
|
| 556 |
-
modalUI.showConfirmModal(`آیا از حذف گفتگوی "${session.title}" مطمئن هستید؟`, () => {
|
| 557 |
-
state.setChatSessions(state.chatSessions.filter(s => s.id !== sessionId));
|
| 558 |
-
state.saveSessions();
|
| 559 |
-
if (state.activeChatId === sessionId) {
|
| 560 |
-
if (state.chatSessions.length > 0) {
|
| 561 |
-
state.setActiveChatId(state.chatSessions[0].id);
|
| 562 |
-
chatUI.renderActiveChat();
|
| 563 |
-
} else {
|
| 564 |
-
handleNewChat();
|
| 565 |
-
}
|
| 566 |
-
}
|
| 567 |
-
chatUI.renderHistoryList();
|
| 568 |
-
});
|
| 569 |
-
} else if (action === 'convert-chat') {
|
| 570 |
-
const fullText = getFullChatText(session);
|
| 571 |
-
api.convertTextToFile(fullText, format, button);
|
| 572 |
-
}
|
| 573 |
-
dom.historyItemMenu.classList.remove('visible');
|
| 574 |
-
});
|
| 575 |
-
|
| 576 |
-
dom.messageItemMenu.addEventListener('click', (e) => {
|
| 577 |
-
const menu = dom.messageItemMenu;
|
| 578 |
-
const closeMenu = () => {
|
| 579 |
-
menu.classList.remove('visible');
|
| 580 |
-
setTimeout(() => { menu.classList.add('hidden'); }, 300);
|
| 581 |
-
};
|
| 582 |
-
if (e.target === dom.messageItemMenuOverlay) {
|
| 583 |
-
closeMenu();
|
| 584 |
-
return;
|
| 585 |
-
}
|
| 586 |
-
const button = e.target.closest('.menu-item');
|
| 587 |
-
if (!button) return;
|
| 588 |
-
const action = button.dataset.action;
|
| 589 |
-
const format = button.dataset.format;
|
| 590 |
-
const messageIndex = parseInt(menu.dataset.messageIndex, 10);
|
| 591 |
-
const activeChat = state.getActiveChat();
|
| 592 |
-
if (!activeChat || isNaN(messageIndex)) {
|
| 593 |
-
closeMenu();
|
| 594 |
-
return;
|
| 595 |
-
}
|
| 596 |
-
const message = activeChat.messages[messageIndex];
|
| 597 |
-
if (action === 'delete-message') {
|
| 598 |
-
modalUI.showConfirmModal('آیا از حذف این پیام مطمئن هستید؟', () => {
|
| 599 |
-
state.deleteMessage(activeChat.id, messageIndex);
|
| 600 |
-
ttsUI.clearCacheForMessage(messageIndex);
|
| 601 |
-
chatUI.renderActiveChat();
|
| 602 |
-
});
|
| 603 |
-
} else if (action === 'convert-message') {
|
| 604 |
-
const textContent = message.parts?.find(p => p.text)?.text || '';
|
| 605 |
-
if (textContent) api.convertTextToFile(textContent, format, button);
|
| 606 |
-
else alert('محتوای متنی برای تبدیل وجود ندارد.');
|
| 607 |
-
}
|
| 608 |
-
closeMenu();
|
| 609 |
-
});
|
| 610 |
-
|
| 611 |
-
dom.messageInput.addEventListener('input', () => {
|
| 612 |
-
chatUI.adjustTextareaHeight(dom.messageInput);
|
| 613 |
-
if (dom.messageInput.value.trim().length > 0 || state.attachedFile) {
|
| 614 |
-
dom.submitButton.classList.add('active');
|
| 615 |
-
} else {
|
| 616 |
-
dom.submitButton.classList.remove('active');
|
| 617 |
-
}
|
| 618 |
-
});
|
| 619 |
-
|
| 620 |
-
dom.editInput.addEventListener('input', () => {
|
| 621 |
-
chatUI.adjustTextareaHeight(dom.editInput);
|
| 622 |
-
});
|
| 623 |
-
|
| 624 |
-
window.addEventListener('message', (event) => {
|
| 625 |
-
if (event.data && event.data.type === 'USER_DATA_RESPONSE_SIMPLE_CHECK') {
|
| 626 |
-
const PREMIUM_PAGE_ID = '1149636';
|
| 627 |
-
let isUserPremium = false;
|
| 628 |
-
if (event.data.payload) {
|
| 629 |
-
try {
|
| 630 |
-
const userObject = JSON.parse(event.data.payload);
|
| 631 |
-
if (userObject && userObject.isLogin && userObject.accessible_pages) {
|
| 632 |
-
if (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) || userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID))) {
|
| 633 |
-
isUserPremium = true;
|
| 634 |
-
}
|
| 635 |
-
}
|
| 636 |
-
} catch (e) { console.error("Error parsing user data from parent:", e); }
|
| 637 |
-
}
|
| 638 |
-
currentUserStatus.isPremium = isUserPremium;
|
| 639 |
-
currentUserStatus.hasBeenChecked = true;
|
| 640 |
-
if (!dom.settingsModal.classList.contains('hidden')) {
|
| 641 |
-
modalUI.updateSettingsUI(isUserPremium);
|
| 642 |
-
}
|
| 643 |
-
}
|
| 644 |
-
});
|
| 645 |
-
|
| 646 |
-
parent.postMessage({ type: 'REQUEST_USER_DATA_SIMPLE_CHECK' }, '*');
|
| 647 |
-
});
|
| 648 |
-
|
| 649 |
-
window.handleSuggestionClick = chatUI.handleSuggestionClick;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/js/state.js
DELETED
|
@@ -1,204 +0,0 @@
|
|
| 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 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/js/ui/chat.js
DELETED
|
@@ -1,792 +0,0 @@
|
|
| 1 |
-
// --- START OF FILE chat.js ---
|
| 2 |
-
// static/js/ui/chat.js
|
| 3 |
-
|
| 4 |
-
import * as state from '../state.js';
|
| 5 |
-
import * as db from '../db.js';
|
| 6 |
-
import { dom } from './dom.js';
|
| 7 |
-
import { toggleHtmlPreviewModal, toggleSidebar, showHistoryMenu, showMessageMenu } from './modals.js';
|
| 8 |
-
import { createDeepThinkPanel, createReasoningPanel, hideDeepThinkPanel, hideReasoningPanel, updateDeepThinkPanel, updateReasoningPanel } from './tools.js';
|
| 9 |
-
|
| 10 |
-
export const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTc1NDIyNGVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
|
| 11 |
-
|
| 12 |
-
const MAX_TEXTAREA_HEIGHT = 150;
|
| 13 |
-
export let minTextareaHeight = 0;
|
| 14 |
-
|
| 15 |
-
const atomIconSVG = `<svg class="thinking-atom-icon w-5 h-5" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="chatbot-gradient" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#3b82f6;" /><stop offset="100%" style="stop-color:#8b5cf6;" /></linearGradient></defs><g><animateTransform attributeName="transform" type="rotate" from="0 50 50" to="360 50 50" dur="8s" repeatCount="indefinite"/><g stroke-width="8" stroke-linecap="round"><circle cx="50" cy="50" r="10" fill="url(#chatbot-gradient)" stroke="none"/><g fill="none" stroke="url(#chatbot-gradient)"><ellipse cx="50" cy="50" rx="22" ry="45"/><ellipse cx="50" cy="50" rx="45" ry="22"/></g></g></g></svg>`;
|
| 16 |
-
const robotIconInBubbleSVG = `<div class="model-icon-in-bubble"><svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9.5 14.5L2 12l7.5-2.5L12 2l2.5 7.5L22 12l-7.5 2.5L12 22l-2.5-7.5z"></path></svg></div>`;
|
| 17 |
-
|
| 18 |
-
window.toggleThinkingPanel = function(headElement) {
|
| 19 |
-
const wrapper = headElement.closest('.thinking-panel-wrapper');
|
| 20 |
-
const body = wrapper.querySelector('.thinking-body');
|
| 21 |
-
const chevron = headElement.querySelector('.thinking-chevron');
|
| 22 |
-
if (body && chevron) {
|
| 23 |
-
body.classList.toggle('collapsed');
|
| 24 |
-
chevron.classList.toggle('collapsed');
|
| 25 |
-
}
|
| 26 |
-
};
|
| 27 |
-
|
| 28 |
-
export function startThinking(modelBubbleOuterDivElement) {
|
| 29 |
-
const contentArea = modelBubbleOuterDivElement?.querySelector('.message-content');
|
| 30 |
-
if (!contentArea) return;
|
| 31 |
-
|
| 32 |
-
const modelContent = `
|
| 33 |
-
<div class="thinking-header-area">
|
| 34 |
-
${robotIconInBubbleSVG}
|
| 35 |
-
<div class="thinking-panel-wrapper">
|
| 36 |
-
<div class="thinking-head" onclick="toggleThinkingPanel(this)">
|
| 37 |
-
${atomIconSVG}
|
| 38 |
-
<span class="thinking-label">افکار</span>
|
| 39 |
-
<svg class="thinking-chevron collapsed w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="3" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" /></svg>
|
| 40 |
-
</div>
|
| 41 |
-
<div class="thinking-body collapsed custom-scrollbar whitespace-pre-wrap"></div>
|
| 42 |
-
</div>
|
| 43 |
-
</div>
|
| 44 |
-
<div class="final-answer-wrapper"></div>
|
| 45 |
-
`;
|
| 46 |
-
contentArea.innerHTML = modelContent;
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
export function streamThought(text, modelBubbleOuterDivElement) {
|
| 50 |
-
const thinkingBody = modelBubbleOuterDivElement.querySelector('.thinking-body');
|
| 51 |
-
if (!thinkingBody) return;
|
| 52 |
-
|
| 53 |
-
const existingContent = thinkingBody.innerHTML;
|
| 54 |
-
const newContent = DOMPurify.sanitize(marked.parse(thinkingBody.textContent + text, { breaks: true, gfm: true }));
|
| 55 |
-
thinkingBody.innerHTML = newContent;
|
| 56 |
-
|
| 57 |
-
thinkingBody.scrollTop = thinkingBody.scrollHeight;
|
| 58 |
-
}
|
| 59 |
-
|
| 60 |
-
function isScrolledToBottom() {
|
| 61 |
-
const { chatWindow } = dom;
|
| 62 |
-
const scrollThreshold = 15;
|
| 63 |
-
return chatWindow.scrollHeight - chatWindow.clientHeight <= chatWindow.scrollTop + scrollThreshold;
|
| 64 |
-
}
|
| 65 |
-
|
| 66 |
-
export function escapeHTML(str) {
|
| 67 |
-
const p = document.createElement("p");
|
| 68 |
-
p.textContent = str;
|
| 69 |
-
return p.innerHTML;
|
| 70 |
-
}
|
| 71 |
-
|
| 72 |
-
function getFileIcon(mimeType) {
|
| 73 |
-
if (mimeType.startsWith('image/')) return '🖼️';
|
| 74 |
-
if (mimeType.startsWith('video/')) return '🎬';
|
| 75 |
-
if (mimeType.startsWith('audio/')) return '🎵';
|
| 76 |
-
if (mimeType.startsWith('application/pdf')) return '📄';
|
| 77 |
-
if (mimeType.startsWith('text/')) return '📝';
|
| 78 |
-
return '📁';
|
| 79 |
-
}
|
| 80 |
-
|
| 81 |
-
export function hideFilePreview() {
|
| 82 |
-
dom.imagePreviewContainer.classList.add('hidden');
|
| 83 |
-
dom.imagePreview.src = '';
|
| 84 |
-
dom.fileInfoText.innerHTML = '';
|
| 85 |
-
dom.imageFileInput.value = '';
|
| 86 |
-
dom.generalFileInput.value = '';
|
| 87 |
-
}
|
| 88 |
-
|
| 89 |
-
export function showFileUploading(fileName) {
|
| 90 |
-
dom.imagePreview.src = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='animate-spin' fill='none' viewBox='0 0 24 24' stroke-width='2' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707'/%3E%3C/svg%3E`;
|
| 91 |
-
dom.fileInfoText.innerHTML = `<div class='flex flex-col'><span class='font-semibold'>${escapeHTML(fileName)}</span><div class='text-xs text-slate-500 dark:text-slate-400'>در حال آپلود... <span class='upload-progress'>0%</span></div></div>`;
|
| 92 |
-
dom.imagePreviewContainer.classList.remove('hidden');
|
| 93 |
-
}
|
| 94 |
-
|
| 95 |
-
export function showFileReady(fileName, mimeType, url) {
|
| 96 |
-
const icon = getFileIcon(mimeType);
|
| 97 |
-
if(mimeType.startsWith('image/')) {
|
| 98 |
-
dom.imagePreview.src = url;
|
| 99 |
-
} else {
|
| 100 |
-
dom.imagePreview.src = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z'%3E%3C/path%3E%3Cpolyline points='13 2 13 9 20 9'%3E%3C/polyline%3E%3C/svg%3E`;
|
| 101 |
-
}
|
| 102 |
-
dom.fileInfoText.innerHTML = `<div class='flex flex-col'><span class='font-semibold'>${icon} ${escapeHTML(fileName)}</span><span class='text-xs text-green-600 dark:text-green-500'>فایل برای ارسال آماده است.</span></div>`;
|
| 103 |
-
dom.imagePreviewContainer.classList.remove('hidden');
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
export function showFileError(errorMessage) {
|
| 107 |
-
dom.imagePreview.src = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z' /%3E%3C/svg%3E`;
|
| 108 |
-
dom.fileInfoText.innerHTML = `<div class='flex flex-col'><span class='font-semibold text-red-600'>خطا در آپلود</span><span class='text-xs text-red-500'>${escapeHTML(errorMessage)}</span></div>`;
|
| 109 |
-
dom.imagePreviewContainer.classList.remove('hidden');
|
| 110 |
-
setTimeout(hideFilePreview, 5000);
|
| 111 |
-
}
|
| 112 |
-
|
| 113 |
-
export function handleSuggestionClick(text) {
|
| 114 |
-
dom.messageInput.value = text;
|
| 115 |
-
dom.messageInput.dispatchEvent(new Event('input', { bubbles: true }));
|
| 116 |
-
dom.messageInput.focus();
|
| 117 |
-
}
|
| 118 |
-
|
| 119 |
-
export function runWelcomeAnimation() {
|
| 120 |
-
const chatbotNameContainer = document.querySelector('.chatbot-name');
|
| 121 |
-
const mainTitle = document.querySelector('.main-title');
|
| 122 |
-
const suggestionsContainer = document.querySelector('.suggestions-container');
|
| 123 |
-
if (!chatbotNameContainer || !mainTitle || !suggestionsContainer) return;
|
| 124 |
-
|
| 125 |
-
const textToType = "چت بات آلفا";
|
| 126 |
-
let charIndex = 0;
|
| 127 |
-
const typingSpeed = 90;
|
| 128 |
-
|
| 129 |
-
function typeChatbotName() {
|
| 130 |
-
if (charIndex < textToType.length) {
|
| 131 |
-
chatbotNameContainer.textContent += textToType.charAt(charIndex);
|
| 132 |
-
charIndex++;
|
| 133 |
-
setTimeout(typeChatbotName, typingSpeed);
|
| 134 |
-
} else {
|
| 135 |
-
chatbotNameContainer.style.opacity = '1';
|
| 136 |
-
setTimeout(() => { mainTitle.style.opacity = '1'; }, 300);
|
| 137 |
-
setTimeout(() => { suggestionsContainer.style.opacity = '1'; suggestionsContainer.style.transform = 'translateY(0)'; }, 600);
|
| 138 |
-
}
|
| 139 |
-
}
|
| 140 |
-
chatbotNameContainer.textContent = '';
|
| 141 |
-
typeChatbotName();
|
| 142 |
-
}
|
| 143 |
-
|
| 144 |
-
export function setupCodeBlockActions(container) {
|
| 145 |
-
container.querySelectorAll('pre').forEach(preElement => {
|
| 146 |
-
preElement.setAttribute('dir', 'ltr');
|
| 147 |
-
|
| 148 |
-
if (preElement.querySelector('.code-button-container')) return;
|
| 149 |
-
const codeElement = preElement.querySelector('code');
|
| 150 |
-
if (!codeElement) return;
|
| 151 |
-
|
| 152 |
-
hljs.highlightElement(codeElement);
|
| 153 |
-
|
| 154 |
-
const buttonContainer = document.createElement('div');
|
| 155 |
-
buttonContainer.className = 'code-button-container';
|
| 156 |
-
|
| 157 |
-
const copyButton = document.createElement('button');
|
| 158 |
-
copyButton.className = 'code-button';
|
| 159 |
-
copyButton.innerHTML = `<span class="copy-text">کپی</span>`;
|
| 160 |
-
const copyTextSpan = copyButton.querySelector('.copy-text');
|
| 161 |
-
|
| 162 |
-
copyButton.onclick = () => {
|
| 163 |
-
navigator.clipboard.writeText(codeElement.innerText).then(() => {
|
| 164 |
-
copyTextSpan.textContent = 'کپی شد!';
|
| 165 |
-
copyButton.style.backgroundColor = '#4CAF50';
|
| 166 |
-
setTimeout(() => {
|
| 167 |
-
copyTextSpan.textContent = 'کپی';
|
| 168 |
-
copyButton.style.backgroundColor = '';
|
| 169 |
-
}, 2000);
|
| 170 |
-
});
|
| 171 |
-
};
|
| 172 |
-
buttonContainer.appendChild(copyButton);
|
| 173 |
-
|
| 174 |
-
const languageClass = Array.from(codeElement.classList).find(cls => cls.startsWith('language-'));
|
| 175 |
-
if (languageClass === 'language-html') {
|
| 176 |
-
const runButton = document.createElement('button');
|
| 177 |
-
runButton.className = 'code-button';
|
| 178 |
-
runButton.innerHTML = `<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"></path></svg><span>اجرا</span>`;
|
| 179 |
-
runButton.onclick = () => { toggleHtmlPreviewModal(true, codeElement.innerText); };
|
| 180 |
-
buttonContainer.appendChild(runButton);
|
| 181 |
-
}
|
| 182 |
-
|
| 183 |
-
preElement.appendChild(buttonContainer);
|
| 184 |
-
});
|
| 185 |
-
}
|
| 186 |
-
|
| 187 |
-
export function renderHistoryList() {
|
| 188 |
-
dom.historyList.innerHTML = '';
|
| 189 |
-
const chatsToDisplay = state.chatSessions.filter(session => session.messages.length > 0 || session.id === state.activeChatId);
|
| 190 |
-
if (chatsToDisplay.length > 0) {
|
| 191 |
-
chatsToDisplay.forEach((session) => {
|
| 192 |
-
const itemContainer = document.createElement('div');
|
| 193 |
-
itemContainer.className = 'history-item flex items-center justify-between rounded-lg';
|
| 194 |
-
|
| 195 |
-
const itemLink = document.createElement('a');
|
| 196 |
-
itemLink.href = '#';
|
| 197 |
-
itemLink.className = `flex-grow p-3 truncate transition-colors rounded-lg ${session.id === state.activeChatId ? 'bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 font-semibold' : 'hover:bg-slate-200/60 dark:hover:bg-slate-700/60 text-slate-700 dark:text-slate-300'}`;
|
| 198 |
-
itemLink.textContent = session.title;
|
| 199 |
-
itemLink.onclick = (e) => {
|
| 200 |
-
e.preventDefault();
|
| 201 |
-
state.setActiveChatId(session.id);
|
| 202 |
-
renderActiveChat();
|
| 203 |
-
renderHistoryList();
|
| 204 |
-
toggleSidebar(false);
|
| 205 |
-
};
|
| 206 |
-
|
| 207 |
-
const menuButton = document.createElement('button');
|
| 208 |
-
menuButton.className = 'history-item-button p-2 ml-1 text-slate-500 dark:text-slate-400 hover:bg-slate-200/80 dark:hover:bg-slate-700/80 rounded-full flex-shrink-0';
|
| 209 |
-
menuButton.innerHTML = '<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM12.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM18.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0z" /></svg>';
|
| 210 |
-
|
| 211 |
-
menuButton.onclick = (e) => {
|
| 212 |
-
e.preventDefault();
|
| 213 |
-
e.stopPropagation();
|
| 214 |
-
showHistoryMenu(e, session.id);
|
| 215 |
-
};
|
| 216 |
-
|
| 217 |
-
itemContainer.appendChild(itemLink);
|
| 218 |
-
itemContainer.appendChild(menuButton);
|
| 219 |
-
dom.historyList.appendChild(itemContainer);
|
| 220 |
-
});
|
| 221 |
-
}
|
| 222 |
-
}
|
| 223 |
-
|
| 224 |
-
export async function renderActiveChat() {
|
| 225 |
-
dom.chatWindow.innerHTML = '';
|
| 226 |
-
const activeChat = state.getActiveChat();
|
| 227 |
-
|
| 228 |
-
if (activeChat && activeChat.messages.length === 0) {
|
| 229 |
-
dom.chatWindow.innerHTML = `
|
| 230 |
-
<div class="welcome-screen">
|
| 231 |
-
<div class="welcome-container">
|
| 232 |
-
<div class="chatbot-name"></div>
|
| 233 |
-
<h1 class="main-title">چطور میتوانم به شما کمک کنم؟</h1>
|
| 234 |
-
|
| 235 |
-
<div class="suggestions-container">
|
| 236 |
-
<button class="suggestion-button" onclick="handleSuggestionClick('یک برنامه بنویس برای ')">
|
| 237 |
-
<span>برنامه بچین</span>
|
| 238 |
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2196F3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
|
| 239 |
-
</button>
|
| 240 |
-
<button class="suggestion-button" onclick="handleSuggestionClick('بهم مشاوره بده در مورد ')">
|
| 241 |
-
<span>مشاوره بده</span>
|
| 242 |
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4CAF50" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 10v6M2 10l10-5 10 5-10 5z"></path><path d="M6 12v5c0 1.1.9 2 2 2h8a2 2 0 0 0 2-2v-5"></path></svg>
|
| 243 |
-
</button>
|
| 244 |
-
<button class="suggestion-button" onclick="handleSuggestionClick('این تصویر رو آنالیز کن')">
|
| 245 |
-
<span>آنالیز تصاویر</span>
|
| 246 |
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#673AB7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"></path><circle cx="12" cy="12" r="3"></circle></svg>
|
| 247 |
-
</button>
|
| 248 |
-
<button class="suggestion-button" onclick="handleSuggestionClick('سورپرایزم کن')">
|
| 249 |
-
<span>سورپرایزم کن</span>
|
| 250 |
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#009688" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 12 20 22 4 22 4 12"></polyline><rect x="2" y="7" width="20" height="5"></rect><line x1="12" y1="22" x2="12" y2="7"></line><path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path><path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path></svg>
|
| 251 |
-
</button>
|
| 252 |
-
<button class="suggestion-button" onclick="handleSuggestionClick('تحلیل کن ')">
|
| 253 |
-
<span>تحلیل کن</span>
|
| 254 |
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#009688" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20V10"></path><path d="M18 20V4"></path><path d="M6 20V16"></path></svg>
|
| 255 |
-
</button>
|
| 256 |
-
<button class="suggestion-button" onclick="handleSuggestionClick('کمک کن بنویسم در مورد ')">
|
| 257 |
-
<span>کمک کن بنویسم</span>
|
| 258 |
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#E91E63" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 19-7-7 7-7"></path><path d="m19 12-7-7"></path></svg>
|
| 259 |
-
</button>
|
| 260 |
-
<button class="suggestion-button" onclick="handleSuggestionClick('خلاصه متن ')">
|
| 261 |
-
<span>خلاصه کن</span>
|
| 262 |
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FF9800" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 6h13"></path><path d="M8 12h13"></path><path d="M8 18h13"></path><path d="M3 6h.01"></path><path d="M3 12h.01"></path><path d="M3 18h.01"></path></svg>
|
| 263 |
-
</button>
|
| 264 |
-
<button class="suggestion-button" onclick="handleSuggestionClick('ایده بده در مورد ')">
|
| 265 |
-
<span>ایده بده</span>
|
| 266 |
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FFC107" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 16a5 5 0 1 1 6 0a3.5 3.5 0 0 0 -1 3a2 2 0 0 1 -4 0a3.5 3.5 0 0 0 -1 -3" /><line x1="9.7" y1="17" x2="14.3" y2="17" /></svg>
|
| 267 |
-
</button>
|
| 268 |
-
</div>
|
| 269 |
-
</div>
|
| 270 |
-
</div>`;
|
| 271 |
-
runWelcomeAnimation();
|
| 272 |
-
} else if (activeChat && activeChat.messages.length > 0) {
|
| 273 |
-
const lastMessageIndex = activeChat.messages.length - 1;
|
| 274 |
-
const lastUserMessageIndex = state.findLastIndex(activeChat.messages, msg => msg.role === 'user');
|
| 275 |
-
|
| 276 |
-
for (const [index, msg] of activeChat.messages.entries()) {
|
| 277 |
-
if (msg.isTemporary) continue;
|
| 278 |
-
const isLastUser = (index === lastUserMessageIndex);
|
| 279 |
-
const isLastModel = (index === lastMessageIndex && msg.role === 'assistant');
|
| 280 |
-
await addMessageToUI(msg, index, { isLastUser, isLastModel, animate: false });
|
| 281 |
-
}
|
| 282 |
-
}
|
| 283 |
-
|
| 284 |
-
requestAnimationFrame(() => { dom.chatWindow.scrollTop = dom.chatWindow.scrollHeight; });
|
| 285 |
-
}
|
| 286 |
-
|
| 287 |
-
export function createMessageActionsHtml(options) {
|
| 288 |
-
const { role, isLastUser, isLastModel, messageObject } = options;
|
| 289 |
-
let buttonsHtml = '';
|
| 290 |
-
const textContent = messageObject?.parts.find(p => p.text)?.text;
|
| 291 |
-
const copyButtonHtml = `<button data-action="copy" title="کپی" class="action-button relative"><svg class="w-4 h-4 copy-icon" fill="currentColor" viewBox="0 0 24 24"><path d="M16 1H4c-1.1 0-2 .9-2 2v12h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2z"/></svg><svg class="w-4 h-4 check-icon hidden text-green-500" fill="none" viewBox="0 0 24 24" stroke-width="3" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /></svg><span class="copy-feedback">کپی شد!</span></button>`;
|
| 292 |
-
const menuButtonHtml = `<button data-action="show-message-menu" title="گزینههای بیشتر" class="action-button"><svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></svg></button>`;
|
| 293 |
-
|
| 294 |
-
if (role === 'user') {
|
| 295 |
-
if (textContent) {
|
| 296 |
-
buttonsHtml += copyButtonHtml;
|
| 297 |
-
}
|
| 298 |
-
if (isLastUser && textContent) {
|
| 299 |
-
buttonsHtml += `<button data-action="edit" title="ویرایش" class="action-button"><svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>`;
|
| 300 |
-
}
|
| 301 |
-
buttonsHtml += menuButtonHtml;
|
| 302 |
-
}
|
| 303 |
-
|
| 304 |
-
if (role === 'assistant') {
|
| 305 |
-
const hasTextContent = messageObject?.parts.some(p => p.text);
|
| 306 |
-
const isClarification = !!messageObject?.clarification;
|
| 307 |
-
const isGpuGuide = !!messageObject?.isGpuGuide;
|
| 308 |
-
|
| 309 |
-
if (hasTextContent) {
|
| 310 |
-
buttonsHtml += `<button data-action="speak" title="پخش صدا" class="action-button">
|
| 311 |
-
<svg class="w-4 h-4 speak-icon" fill="currentColor" viewBox="0 0 20 20"><path d="M8.25 3.75a.75.75 0 00-1.5 0v12.5a.75.75 0 001.5 0V3.75zM11.75 3.75a.75.75 0 00-1.5 0v12.5a.75.75 0 001.5 0V3.75zM4 6a.75.75 0 01.75.75v6.5a.75.75 0 01-1.5 0V6.75A.75.75 0 014 6zM16 6a.75.75 0 01.75.75v6.5a.75.75 0 01-1.5 0V6.75A.75.75 0 0116 6z"></path></svg>
|
| 312 |
-
<svg class="w-4 h-4 pause-icon hidden" fill="currentColor" viewBox="0 0 20 20"><path d="M5.75 4.5a.75.75 0 00-.75.75v10.5a.75.75 0 001.5 0V5.25a.75.75 0 00-.75-.75zM14.25 4.5a.75.75 0 00-.75.75v10.5a.75.75 0 001.5 0V5.25a.75.75 0 00-.75-.75z"></path></svg>
|
| 313 |
-
<div class="loading-spinner"></div>
|
| 314 |
-
</button>`;
|
| 315 |
-
buttonsHtml += copyButtonHtml;
|
| 316 |
-
}
|
| 317 |
-
|
| 318 |
-
if (isLastModel && !isClarification && !isGpuGuide) {
|
| 319 |
-
buttonsHtml += `<button data-action="regenerate" title="تولید مجدد" class="action-button"><svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 6v3l4-4-4-4v3c-4.42 0-8 3.58-8 8 0 1.57.46 3.03 1.24 4.26L6.7 14.8c-.45-.83-.7-1.79-.7-2.8 0-3.31 2.69-6 6-6zm6.76 1.74L17.3 9.2c.44.84.7 1.79.7 2.8 0 3.31-2.69 6-6 6v-3l-4 4 4 4v-3c4.42 0 8-3.58 8-8 0-1.57-.46-3.03-1.24-4.26z"/></svg></button>`;
|
| 320 |
-
}
|
| 321 |
-
|
| 322 |
-
if (hasTextContent && !isClarification && !isGpuGuide) {
|
| 323 |
-
buttonsHtml += `<button data-action="like" title="پسندیدم" class="action-button"><svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg></button><button data-action="dislike" title="نپسندیدم" class="action-button"><svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14-.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41-.17-.79-.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg></button>`;
|
| 324 |
-
}
|
| 325 |
-
buttonsHtml += menuButtonHtml;
|
| 326 |
-
}
|
| 327 |
-
return buttonsHtml ? `<div class="message-actions"><div class="flex items-center gap-1.5">${buttonsHtml}</div></div>` : '';
|
| 328 |
-
}
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
function createFileContentHtml(filePart) {
|
| 332 |
-
const { fileUrl, mimeType, name } = filePart;
|
| 333 |
-
let fileHtml = '';
|
| 334 |
-
|
| 335 |
-
if (!fileUrl) {
|
| 336 |
-
return `<div class="p-3 text-red-500">خطا: فایل برای نمایش یافت نشد.</div>`;
|
| 337 |
-
}
|
| 338 |
-
|
| 339 |
-
if (mimeType.startsWith('image/')) {
|
| 340 |
-
fileHtml = `<img src="${fileUrl}" alt="${escapeHTML(name) || 'Uploaded image'}">`;
|
| 341 |
-
} else if (mimeType.startsWith('video/')) {
|
| 342 |
-
fileHtml = `<video controls src="${fileUrl}"></video>`;
|
| 343 |
-
} else if (mimeType.startsWith('audio/')) {
|
| 344 |
-
fileHtml = `<audio controls src="${fileUrl}" class="w-full"></audio>`;
|
| 345 |
-
} else {
|
| 346 |
-
fileHtml = `<div class="flex items-center gap-3 p-3 bg-slate-100 dark:bg-slate-700/50 rounded-lg border border-slate-200 dark:border-slate-600 text-slate-700 dark:text-slate-200 text-sm">
|
| 347 |
-
<svg class="w-8 h-8 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m.75 12l3 3m0 0l3-3m-3 3v-6m-1.5-9H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>
|
| 348 |
-
<div class="flex flex-col overflow-hidden">
|
| 349 |
-
<span class="font-semibold truncate">${escapeHTML(name)}</span>
|
| 350 |
-
<a href="${fileUrl}" target="_blank" rel="noopener noreferrer" class="text-xs text-blue-600 hover:underline">دانلود فایل</a>
|
| 351 |
-
</div>
|
| 352 |
-
</div>`;
|
| 353 |
-
}
|
| 354 |
-
return fileHtml;
|
| 355 |
-
}
|
| 356 |
-
|
| 357 |
-
export async function addMessageToUI(message, index, options = {}, existingElement = null) {
|
| 358 |
-
const { role, parts } = message;
|
| 359 |
-
const { isLastUser = false, isLastModel = false, animate = true } = options;
|
| 360 |
-
const isUser = role === 'user';
|
| 361 |
-
|
| 362 |
-
let finalElement = existingElement;
|
| 363 |
-
|
| 364 |
-
// *** منطق جدید و دقیق برای پاکسازی دکمهها ***
|
| 365 |
-
if (!existingElement) {
|
| 366 |
-
const activeChat = state.getActiveChat();
|
| 367 |
-
|
| 368 |
-
if (isUser) {
|
| 369 |
-
// وقتی کاربر پیام جدید میده:
|
| 370 |
-
// 1. دکمه ویرایش از تمام پیامهای قبلی کاربر حذف بشه
|
| 371 |
-
const prevUserMsgs = dom.chatWindow.querySelectorAll('.message-entry.user');
|
| 372 |
-
prevUserMsgs.forEach(el => {
|
| 373 |
-
const idx = parseInt(el.dataset.index, 10);
|
| 374 |
-
if (activeChat.messages[idx]) updateMessageActions(el, activeChat.messages[idx], false, false);
|
| 375 |
-
});
|
| 376 |
-
|
| 377 |
-
// 2. دکمه تلاش مجدد از تمام پیامهای قبلی هوش مصنوعی حذف بشه (چون بحث ادامه پیدا کرده)
|
| 378 |
-
const prevModelMsgs = dom.chatWindow.querySelectorAll('.message-entry.model');
|
| 379 |
-
prevModelMsgs.forEach(el => {
|
| 380 |
-
const idx = parseInt(el.dataset.index, 10);
|
| 381 |
-
if (activeChat.messages[idx]) updateMessageActions(el, activeChat.messages[idx], false, false);
|
| 382 |
-
});
|
| 383 |
-
}
|
| 384 |
-
else {
|
| 385 |
-
// وقتی هوش مصنوعی پیام جدید میده:
|
| 386 |
-
// 1. دکمه تلاش مجدد از پیامهای قبلی هوش مصنوعی حذف بشه
|
| 387 |
-
const prevModelMsgs = dom.chatWindow.querySelectorAll('.message-entry.model');
|
| 388 |
-
prevModelMsgs.forEach(el => {
|
| 389 |
-
const idx = parseInt(el.dataset.index, 10);
|
| 390 |
-
if (activeChat.messages[idx]) updateMessageActions(el, activeChat.messages[idx], false, false);
|
| 391 |
-
});
|
| 392 |
-
}
|
| 393 |
-
}
|
| 394 |
-
|
| 395 |
-
if (!finalElement) {
|
| 396 |
-
finalElement = document.createElement('div');
|
| 397 |
-
const roleClass = isUser ? 'user' : 'model';
|
| 398 |
-
finalElement.className = `message-entry ${roleClass} mb-6 flex items-end gap-3 ${isUser ? 'justify-end' : 'justify-start'}`;
|
| 399 |
-
finalElement.dataset.index = index;
|
| 400 |
-
if (animate) finalElement.classList.add('message-entry');
|
| 401 |
-
|
| 402 |
-
const userIcon = `<div class="w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0 bg-blue-600 text-white"><svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"></path></svg></div>`;
|
| 403 |
-
|
| 404 |
-
const bubbleClasses = isUser
|
| 405 |
-
? 'bg-gradient-to-br from-blue-500 to-purple-600 text-white rounded-br-none'
|
| 406 |
-
: 'bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 text-slate-800 dark:text-slate-200 rounded-bl-none';
|
| 407 |
-
|
| 408 |
-
const messageBubbleHTML = `<div class="message-content p-4 rounded-2xl shadow-md ${bubbleClasses}"></div>`;
|
| 409 |
-
|
| 410 |
-
finalElement.innerHTML = `
|
| 411 |
-
<div class="relative group w-full">
|
| 412 |
-
${messageBubbleHTML}
|
| 413 |
-
</div>
|
| 414 |
-
${isUser ? userIcon : ''}
|
| 415 |
-
`;
|
| 416 |
-
dom.chatWindow.appendChild(finalElement);
|
| 417 |
-
}
|
| 418 |
-
|
| 419 |
-
const contentArea = finalElement.querySelector('.message-content');
|
| 420 |
-
|
| 421 |
-
if (!isUser) {
|
| 422 |
-
contentArea.classList.add('model-bubble');
|
| 423 |
-
contentArea.style.padding = '0';
|
| 424 |
-
} else {
|
| 425 |
-
contentArea.innerHTML = '';
|
| 426 |
-
contentArea.style.padding = '1rem';
|
| 427 |
-
}
|
| 428 |
-
|
| 429 |
-
if (isUser) {
|
| 430 |
-
const textParts = parts.filter(p => p.text);
|
| 431 |
-
const fileParts = parts.filter(p => p.id);
|
| 432 |
-
|
| 433 |
-
const processedFileParts = await Promise.all(
|
| 434 |
-
fileParts.map(async (part) => {
|
| 435 |
-
if (part.id) {
|
| 436 |
-
try {
|
| 437 |
-
const file = await db.getFile(part.id);
|
| 438 |
-
if (file) {
|
| 439 |
-
const newBlobUrl = URL.createObjectURL(file);
|
| 440 |
-
return { ...part, fileUrl: newBlobUrl, mimeType: file.type, name: file.name };
|
| 441 |
-
}
|
| 442 |
-
} catch (error) {
|
| 443 |
-
console.error(`Error retrieving file ${part.id} from DB:`, error);
|
| 444 |
-
}
|
| 445 |
-
}
|
| 446 |
-
return { ...part, fileUrl: null };
|
| 447 |
-
})
|
| 448 |
-
);
|
| 449 |
-
|
| 450 |
-
const fileHtml = processedFileParts.map(p => createFileContentHtml(p)).join('');
|
| 451 |
-
const textHtml = textParts.map(p => `<div class="whitespace-pre-wrap">${escapeHTML(p.text)}</div>`).join('');
|
| 452 |
-
|
| 453 |
-
if (processedFileParts.length > 0 && textParts.length > 0) {
|
| 454 |
-
contentArea.classList.add('user-bubble-multipart');
|
| 455 |
-
contentArea.innerHTML = `<div class="user-file-part">${fileHtml}</div><div class="user-text-part">${textHtml}</div>`;
|
| 456 |
-
} else {
|
| 457 |
-
contentArea.classList.remove('user-bubble-multipart');
|
| 458 |
-
contentArea.innerHTML = fileHtml + textHtml;
|
| 459 |
-
if (processedFileParts.length === 1 && (processedFileParts[0].mimeType?.startsWith('image/') || processedFileParts[0].mimeType?.startsWith('video/'))) {
|
| 460 |
-
contentArea.style.padding = '0';
|
| 461 |
-
const filePartElement = contentArea.querySelector('.user-file-part');
|
| 462 |
-
if (filePartElement) filePartElement.classList.add('single');
|
| 463 |
-
}
|
| 464 |
-
}
|
| 465 |
-
|
| 466 |
-
} else if (message.isTemporary) {
|
| 467 |
-
const activeTool = state.getActiveTool();
|
| 468 |
-
|
| 469 |
-
if (activeTool === 'deep-think') {
|
| 470 |
-
createDeepThinkPanel(finalElement);
|
| 471 |
-
} else if (activeTool === 'reasoning') {
|
| 472 |
-
createReasoningPanel(finalElement);
|
| 473 |
-
} else {
|
| 474 |
-
const activeChat = state.getActiveChat();
|
| 475 |
-
if (activeChat && activeChat.showThoughts) {
|
| 476 |
-
startThinking(finalElement);
|
| 477 |
-
} else {
|
| 478 |
-
showFreeWsLoadingIndicator(finalElement);
|
| 479 |
-
}
|
| 480 |
-
}
|
| 481 |
-
} else {
|
| 482 |
-
const allContent = parts?.filter(p => p.text).map(p => p.text).join('') || '';
|
| 483 |
-
|
| 484 |
-
if (message.toolUsed === 'deep-think') {
|
| 485 |
-
createDeepThinkPanel(finalElement);
|
| 486 |
-
hideDeepThinkPanel(finalElement);
|
| 487 |
-
finalizeFinalText(finalElement, allContent);
|
| 488 |
-
} else if (message.toolUsed === 'reasoning') {
|
| 489 |
-
createReasoningPanel(finalElement);
|
| 490 |
-
hideReasoningPanel(finalElement);
|
| 491 |
-
finalizeFinalText(finalElement, allContent);
|
| 492 |
-
} else if (message.wasGeneratedWithThoughts) {
|
| 493 |
-
startThinking(finalElement);
|
| 494 |
-
finalizeFinalText(finalElement, allContent);
|
| 495 |
-
} else {
|
| 496 |
-
finalizeFreeWsMessage(finalElement, allContent);
|
| 497 |
-
}
|
| 498 |
-
}
|
| 499 |
-
|
| 500 |
-
updateMessageActions(finalElement, message, isLastUser, isLastModel);
|
| 501 |
-
|
| 502 |
-
if (!existingElement && animate) {
|
| 503 |
-
finalElement.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
| 504 |
-
}
|
| 505 |
-
return finalElement;
|
| 506 |
-
}
|
| 507 |
-
|
| 508 |
-
export function showLimitReachedUpgrade() {
|
| 509 |
-
const message = "محدودیت پیامهای روزانه شما به پایان رسیده است.";
|
| 510 |
-
const modelBubbleOuterDivElement = document.createElement('div');
|
| 511 |
-
modelBubbleOuterDivElement.className = 'message-entry model mb-6 flex items-end gap-3 justify-start';
|
| 512 |
-
modelBubbleOuterDivElement.style.animation = 'fade-slide-in 300ms ease-out forwards';
|
| 513 |
-
|
| 514 |
-
const limitReachedHTML = `
|
| 515 |
-
<div class="relative group w-full">
|
| 516 |
-
<div class="message-content w-full rounded-2xl shadow-md bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700">
|
| 517 |
-
<div class="p-4 flex flex-col items-center text-center">
|
| 518 |
-
<svg class="w-12 h-12 text-orange-400 mb-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
| 519 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
|
| 520 |
-
</svg>
|
| 521 |
-
<h3 class="text-lg font-bold text-slate-800 dark:text-white mb-2">محدودیت استفاده رایگان</h3>
|
| 522 |
-
<p class="text-slate-600 dark:text-slate-300 mb-6 text-sm">${message} برای ادامه استفاده نامحدود، حساب خود را ارتقا دهید.</p>
|
| 523 |
-
<button id="limit-upgrade-btn" class="beautiful-upgrade-btn">
|
| 524 |
-
✨ ارتقا به نسخه کامل
|
| 525 |
-
</button>
|
| 526 |
-
</div>
|
| 527 |
-
</div>
|
| 528 |
-
</div>
|
| 529 |
-
`;
|
| 530 |
-
modelBubbleOuterDivElement.innerHTML = limitReachedHTML;
|
| 531 |
-
dom.chatWindow.appendChild(modelBubbleOuterDivElement);
|
| 532 |
-
const upgradeButton = modelBubbleOuterDivElement.querySelector('#limit-upgrade-btn');
|
| 533 |
-
if (upgradeButton) {
|
| 534 |
-
upgradeButton.addEventListener('click', () => {
|
| 535 |
-
parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM', payload: { url: PREMIUM_URL } }, '*');
|
| 536 |
-
});
|
| 537 |
-
}
|
| 538 |
-
modelBubbleOuterDivElement.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
| 539 |
-
}
|
| 540 |
-
|
| 541 |
-
export function streamFinalText(text, modelBubbleOuterDivElement) {
|
| 542 |
-
const finalAnswerWrapper = modelBubbleOuterDivElement.querySelector('.final-answer-wrapper');
|
| 543 |
-
if (!finalAnswerWrapper) return;
|
| 544 |
-
|
| 545 |
-
if (!finalAnswerWrapper.classList.contains('visible')) {
|
| 546 |
-
finalAnswerWrapper.classList.add('visible');
|
| 547 |
-
}
|
| 548 |
-
|
| 549 |
-
const shouldScroll = isScrolledToBottom();
|
| 550 |
-
|
| 551 |
-
if (finalAnswerWrapper.innerHTML.trim() === '') {
|
| 552 |
-
finalAnswerWrapper.innerHTML = `<div class="border-t border-slate-200 dark:border-slate-700 mt-4 p-4 prose dark:prose-invert max-w-none"></div>`;
|
| 553 |
-
}
|
| 554 |
-
|
| 555 |
-
const contentContainer = finalAnswerWrapper.querySelector('.p-4');
|
| 556 |
-
if (!contentContainer) return;
|
| 557 |
-
|
| 558 |
-
const content = DOMPurify.sanitize(marked.parse(text + '▍' || " ", { breaks: true, gfm: true }));
|
| 559 |
-
contentContainer.innerHTML = content;
|
| 560 |
-
|
| 561 |
-
contentContainer.querySelectorAll('pre code').forEach(block => {
|
| 562 |
-
hljs.highlightElement(block);
|
| 563 |
-
});
|
| 564 |
-
|
| 565 |
-
if (shouldScroll) {
|
| 566 |
-
requestAnimationFrame(() => {
|
| 567 |
-
dom.chatWindow.scrollTop = dom.chatWindow.scrollHeight;
|
| 568 |
-
});
|
| 569 |
-
}
|
| 570 |
-
}
|
| 571 |
-
|
| 572 |
-
export function finalizeFinalText(modelBubbleOuterDivElement, fullText) {
|
| 573 |
-
const finalAnswerWrapper = modelBubbleOuterDivElement.querySelector('.final-answer-wrapper');
|
| 574 |
-
if (!finalAnswerWrapper) return;
|
| 575 |
-
|
| 576 |
-
const shouldScroll = isScrolledToBottom();
|
| 577 |
-
|
| 578 |
-
if (!finalAnswerWrapper.classList.contains('visible')) {
|
| 579 |
-
finalAnswerWrapper.classList.add('visible');
|
| 580 |
-
}
|
| 581 |
-
|
| 582 |
-
if (finalAnswerWrapper.innerHTML.trim() === '') {
|
| 583 |
-
finalAnswerWrapper.innerHTML = `<div class="p-4 prose dark:prose-invert max-w-none"></div>`;
|
| 584 |
-
}
|
| 585 |
-
const contentContainer = finalAnswerWrapper.querySelector('.p-4');
|
| 586 |
-
|
| 587 |
-
const content = DOMPurify.sanitize(marked.parse(fullText || " ", { breaks: true, gfm: true }));
|
| 588 |
-
contentContainer.innerHTML = content;
|
| 589 |
-
|
| 590 |
-
setupCodeBlockActions(finalAnswerWrapper);
|
| 591 |
-
|
| 592 |
-
if (shouldScroll) {
|
| 593 |
-
requestAnimationFrame(() => {
|
| 594 |
-
dom.chatWindow.scrollTop = dom.chatWindow.scrollHeight;
|
| 595 |
-
});
|
| 596 |
-
}
|
| 597 |
-
}
|
| 598 |
-
|
| 599 |
-
export function updateMessageActions(messageOuterDivElement, messageObject, isLastUser, isLastModel) {
|
| 600 |
-
const messageWrapper = messageOuterDivElement.querySelector('.group');
|
| 601 |
-
if (!messageWrapper) return;
|
| 602 |
-
let oldActionsContainer = messageWrapper.querySelector('.message-actions');
|
| 603 |
-
if (oldActionsContainer) { oldActionsContainer.remove(); }
|
| 604 |
-
const newActionsHtml = createMessageActionsHtml({ role: messageObject.role, isLastUser: isLastUser, isLastModel: isLastModel, messageObject: messageObject });
|
| 605 |
-
if (newActionsHtml) { messageWrapper.insertAdjacentHTML('beforeend', newActionsHtml); }
|
| 606 |
-
}
|
| 607 |
-
|
| 608 |
-
export function adjustTextareaHeight(el) {
|
| 609 |
-
el.style.height = 'auto';
|
| 610 |
-
el.style.height = `${el.scrollHeight}px`;
|
| 611 |
-
}
|
| 612 |
-
|
| 613 |
-
export function showCopyFeedback(button) {
|
| 614 |
-
const copyIcon = button.querySelector('.copy-icon');
|
| 615 |
-
const checkIcon = button.querySelector('.check-icon');
|
| 616 |
-
const feedback = button.querySelector('.copy-feedback');
|
| 617 |
-
if (copyIcon && checkIcon && feedback) {
|
| 618 |
-
copyIcon.classList.add('hidden');
|
| 619 |
-
checkIcon.classList.remove('hidden');
|
| 620 |
-
feedback.classList.add('visible');
|
| 621 |
-
setTimeout(() => {
|
| 622 |
-
copyIcon.classList.remove('hidden');
|
| 623 |
-
checkIcon.classList.add('hidden');
|
| 624 |
-
feedback.classList.remove('visible');
|
| 625 |
-
}, 2000);
|
| 626 |
-
}
|
| 627 |
-
}
|
| 628 |
-
|
| 629 |
-
export function handleLikeDislike(button, messageEntry) {
|
| 630 |
-
const isActive = button.classList.toggle('active');
|
| 631 |
-
if (isActive) {
|
| 632 |
-
button.classList.add('like-animation');
|
| 633 |
-
button.addEventListener('animationend', () => button.classList.remove('like-animation'), { once: true });
|
| 634 |
-
const action = button.dataset.action;
|
| 635 |
-
const siblingAction = action === 'like' ? 'dislike' : 'like';
|
| 636 |
-
const siblingButton = messageEntry.querySelector(`[data-action="${siblingAction}"]`);
|
| 637 |
-
if (siblingButton) siblingButton.classList.remove('active');
|
| 638 |
-
}
|
| 639 |
-
}
|
| 640 |
-
|
| 641 |
-
export function resetState() {
|
| 642 |
-
state.setGenerating(false);
|
| 643 |
-
dom.submitButton.classList.remove('is-loading');
|
| 644 |
-
dom.sendIcon.classList.remove('hidden');
|
| 645 |
-
dom.stopIcon.classList.add('hidden');
|
| 646 |
-
dom.submitButton.title = 'ارسال';
|
| 647 |
-
dom.submitButton.disabled = false;
|
| 648 |
-
dom.messageInput.disabled = false;
|
| 649 |
-
dom.attachFileButton.disabled = false;
|
| 650 |
-
state.setGlobalAbortController(null);
|
| 651 |
-
}
|
| 652 |
-
|
| 653 |
-
export function setGeneratingState(generating) {
|
| 654 |
-
state.setGenerating(generating);
|
| 655 |
-
dom.submitButton.disabled = !generating;
|
| 656 |
-
if (generating) {
|
| 657 |
-
state.setGlobalAbortController(new AbortController());
|
| 658 |
-
dom.submitButton.classList.add('is-loading');
|
| 659 |
-
dom.sendIcon.classList.add('hidden');
|
| 660 |
-
dom.stopIcon.classList.remove('hidden');
|
| 661 |
-
dom.submitButton.title = 'توقف تولید';
|
| 662 |
-
dom.messageInput.disabled = true;
|
| 663 |
-
dom.attachFileButton.disabled = true;
|
| 664 |
-
} else {
|
| 665 |
-
resetState();
|
| 666 |
-
}
|
| 667 |
-
}
|
| 668 |
-
|
| 669 |
-
export function displayError(modelBubbleOuterDivElement, errorMessage) {
|
| 670 |
-
const messageBubbleContentDiv = modelBubbleOuterDivElement.querySelector('.message-content');
|
| 671 |
-
const messageWrapper = modelBubbleOuterDivElement.querySelector('.group');
|
| 672 |
-
|
| 673 |
-
let oldActionsContainer = messageWrapper.querySelector('.message-actions');
|
| 674 |
-
if (oldActionsContainer) { oldActionsContainer.remove(); }
|
| 675 |
-
|
| 676 |
-
const errorIcon = `<svg class="w-6 h-6 mr-3 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"></path></svg>`;
|
| 677 |
-
messageBubbleContentDiv.innerHTML = `<div class="p-4 flex items-center">${errorIcon}<p class="whitespace-pre-wrap">${escapeHTML(errorMessage)}</p></div>`;
|
| 678 |
-
|
| 679 |
-
messageBubbleContentDiv.className = 'message-content model-bubble rounded-2xl shadow-sm relative bg-red-100 dark:bg-red-800/20 border border-red-200 dark:border-red-600/30 text-red-800 dark:text-red-300';
|
| 680 |
-
|
| 681 |
-
const regenerateButtonHtml = `<button data-action="regenerate" title="تلاش مجدد" class="action-button"><svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 6v3l4-4-4-4v3c-4.42 0-8 3.58-8 8 0 1.57.46 3.03 1.24 4.26L6.7 14.8c-.45-.83-.7-1.79-.7-2.8 0-3.31 2.69-6 6-6zm6.76 1.74L17.3 9.2c.44.84.7 1.79.7 2.8 0 3.31-2.69 6-6 6v-3l-4 4 4 4v-3c4.42 0 8-3.58 8-8 0-1.57-.46-3.03-1.24-4.26z"/></path></svg></button>`;
|
| 682 |
-
const newActionsHtml = `<div class="message-actions"><div class="flex items-center gap-1.5">${regenerateButtonHtml}</div></div>`;
|
| 683 |
-
if (messageWrapper) {
|
| 684 |
-
messageWrapper.insertAdjacentHTML('beforeend', newActionsHtml);
|
| 685 |
-
}
|
| 686 |
-
|
| 687 |
-
resetState();
|
| 688 |
-
}
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
export function setupMobileKeyboardFix() {
|
| 692 |
-
if ('visualViewport' in window) {
|
| 693 |
-
const handleViewportResize = () => {
|
| 694 |
-
const vp = window.visualViewport;
|
| 695 |
-
document.body.style.height = `${vp.height}px`;
|
| 696 |
-
document.body.style.top = `${vp.offsetTop}px`;
|
| 697 |
-
dom.mainFooter.scrollIntoView({ behavior: "instant", block: "end" });
|
| 698 |
-
};
|
| 699 |
-
window.visualViewport.addEventListener('resize', handleViewportResize);
|
| 700 |
-
handleViewportResize();
|
| 701 |
-
}
|
| 702 |
-
}
|
| 703 |
-
|
| 704 |
-
export function showLoadingOnButton(button, isLoading) {
|
| 705 |
-
const spinner = button.querySelector('.animate-spin');
|
| 706 |
-
const textSpan = button.querySelector('span');
|
| 707 |
-
if (isLoading) {
|
| 708 |
-
button.disabled = true;
|
| 709 |
-
if(textSpan) textSpan.style.opacity = '0.5';
|
| 710 |
-
if(spinner) spinner.classList.remove('hidden');
|
| 711 |
-
} else {
|
| 712 |
-
button.disabled = false;
|
| 713 |
-
if(textSpan) textSpan.style.opacity = '1';
|
| 714 |
-
if(spinner) spinner.classList.add('hidden');
|
| 715 |
-
}
|
| 716 |
-
}
|
| 717 |
-
|
| 718 |
-
export function applyTheme(theme) {
|
| 719 |
-
if (theme === 'dark') {
|
| 720 |
-
document.documentElement.classList.add('dark');
|
| 721 |
-
dom.themeToggle.checked = true;
|
| 722 |
-
} else {
|
| 723 |
-
document.documentElement.classList.remove('dark');
|
| 724 |
-
dom.themeToggle.checked = false;
|
| 725 |
-
}
|
| 726 |
-
}
|
| 727 |
-
|
| 728 |
-
export function initTheme() {
|
| 729 |
-
const savedTheme = localStorage.getItem('theme');
|
| 730 |
-
if (savedTheme) {
|
| 731 |
-
applyTheme(savedTheme);
|
| 732 |
-
} else {
|
| 733 |
-
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
| 734 |
-
applyTheme(systemPrefersDark ? 'dark' : 'light');
|
| 735 |
-
}
|
| 736 |
-
}
|
| 737 |
-
|
| 738 |
-
export function showFreeWsLoadingIndicator(modelBubbleOuterDivElement) {
|
| 739 |
-
const contentArea = modelBubbleOuterDivElement.querySelector('.message-content');
|
| 740 |
-
if (!contentArea) return;
|
| 741 |
-
contentArea.style.padding = '1rem';
|
| 742 |
-
contentArea.innerHTML = `<div class="ws-loading-container">
|
| 743 |
-
<div class="dots">
|
| 744 |
-
<div class="dot"></div>
|
| 745 |
-
<div class="dot"></div>
|
| 746 |
-
<div class="dot"></div>
|
| 747 |
-
</div>
|
| 748 |
-
</div>`;
|
| 749 |
-
}
|
| 750 |
-
|
| 751 |
-
export function streamFreeWsChunk(modelBubbleOuterDivElement, fullText) {
|
| 752 |
-
const contentArea = modelBubbleOuterDivElement.querySelector('.message-content');
|
| 753 |
-
if (!contentArea) return;
|
| 754 |
-
|
| 755 |
-
const shouldScroll = isScrolledToBottom();
|
| 756 |
-
|
| 757 |
-
const loadingIndicator = contentArea.querySelector('.ws-loading-container');
|
| 758 |
-
if (loadingIndicator) {
|
| 759 |
-
contentArea.innerHTML = '';
|
| 760 |
-
contentArea.classList.add('prose', 'dark:prose-invert', 'max-w-none');
|
| 761 |
-
}
|
| 762 |
-
|
| 763 |
-
contentArea.innerHTML = DOMPurify.sanitize(marked.parse(fullText + '▍', { breaks: true, gfm: true }));
|
| 764 |
-
|
| 765 |
-
contentArea.querySelectorAll('pre code').forEach(block => {
|
| 766 |
-
hljs.highlightElement(block);
|
| 767 |
-
});
|
| 768 |
-
|
| 769 |
-
if (shouldScroll) {
|
| 770 |
-
requestAnimationFrame(() => {
|
| 771 |
-
dom.chatWindow.scrollTop = dom.chatWindow.scrollHeight;
|
| 772 |
-
});
|
| 773 |
-
}
|
| 774 |
-
}
|
| 775 |
-
|
| 776 |
-
export function finalizeFreeWsMessage(modelBubbleOuterDivElement, fullText) {
|
| 777 |
-
const contentArea = modelBubbleOuterDivElement.querySelector('.message-content');
|
| 778 |
-
if (!contentArea) return;
|
| 779 |
-
|
| 780 |
-
const shouldScroll = isScrolledToBottom();
|
| 781 |
-
contentArea.classList.add('prose', 'dark:prose-invert', 'max-w-none');
|
| 782 |
-
contentArea.style.padding = '1rem';
|
| 783 |
-
contentArea.innerHTML = DOMPurify.sanitize(marked.parse(fullText || " ", { breaks: true, gfm: true }));
|
| 784 |
-
setupCodeBlockActions(modelBubbleOuterDivElement);
|
| 785 |
-
|
| 786 |
-
if (shouldScroll) {
|
| 787 |
-
requestAnimationFrame(() => {
|
| 788 |
-
dom.chatWindow.scrollTop = dom.chatWindow.scrollHeight;
|
| 789 |
-
});
|
| 790 |
-
}
|
| 791 |
-
}
|
| 792 |
-
// --- END OF FILE chat.js ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/js/ui/dom.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
| 1 |
-
// static/js/ui/dom.js
|
| 2 |
-
|
| 3 |
-
export const dom = {
|
| 4 |
-
appContainer: document.getElementById('app-container'),
|
| 5 |
-
chatWindow: document.getElementById('chat-window'),
|
| 6 |
-
mainHeader: document.getElementById('main-header'),
|
| 7 |
-
mainFooter: document.getElementById('main-footer'),
|
| 8 |
-
messageForm: document.getElementById('message-form'),
|
| 9 |
-
messageInput: document.getElementById('message-input'),
|
| 10 |
-
submitButton: document.getElementById('submit-button'),
|
| 11 |
-
sendIcon: document.getElementById('send-icon'),
|
| 12 |
-
stopIcon: document.getElementById('stop-icon'),
|
| 13 |
-
menuButton: document.getElementById('menu-button'),
|
| 14 |
-
newChatButton: document.getElementById('new-chat-button'),
|
| 15 |
-
historySidebar: document.getElementById('history-sidebar'),
|
| 16 |
-
sidebarOverlay: document.getElementById('sidebar-overlay'),
|
| 17 |
-
historyList: document.getElementById('history-list'),
|
| 18 |
-
deleteAllChatsButton: document.getElementById('delete-all-chats'),
|
| 19 |
-
attachFileButton: document.getElementById('attach-file-button'),
|
| 20 |
-
imageFileInput: document.getElementById('image-file-input'),
|
| 21 |
-
generalFileInput: document.getElementById('general-file-input'),
|
| 22 |
-
imagePreviewContainer: document.getElementById('image-preview-container'),
|
| 23 |
-
imagePreview: document.getElementById('image-preview'),
|
| 24 |
-
removeImageButton: document.getElementById('remove-image-button'),
|
| 25 |
-
fileInfoText: document.getElementById('file-info-text'),
|
| 26 |
-
historyItemMenu: document.getElementById('history-item-menu'),
|
| 27 |
-
messageItemMenu: document.getElementById('message-item-menu'),
|
| 28 |
-
messageItemMenuOverlay: document.getElementById('message-item-menu-overlay'),
|
| 29 |
-
messageItemMenuContent: document.getElementById('message-item-menu-content'),
|
| 30 |
-
confirmModal: document.getElementById('confirm-modal'),
|
| 31 |
-
confirmModalOverlay: document.getElementById('confirm-modal-overlay'),
|
| 32 |
-
confirmModalContent: document.getElementById('confirm-modal-content'),
|
| 33 |
-
confirmModalMessage: document.getElementById('confirm-modal-message'),
|
| 34 |
-
confirmModalConfirmBtn: document.getElementById('confirm-modal-confirm-btn'),
|
| 35 |
-
confirmModalCancelBtn: document.getElementById('confirm-modal-cancel-btn'),
|
| 36 |
-
renameModal: document.getElementById('rename-modal'),
|
| 37 |
-
renameModalOverlay: document.getElementById('rename-modal-overlay'),
|
| 38 |
-
renameModalContent: document.getElementById('rename-modal-content'),
|
| 39 |
-
renameInput: document.getElementById('rename-input'),
|
| 40 |
-
renameModalConfirmBtn: document.getElementById('rename-modal-confirm-btn'),
|
| 41 |
-
renameModalCancelBtn: document.getElementById('rename-modal-cancel-btn'),
|
| 42 |
-
editModal: document.getElementById('edit-modal'),
|
| 43 |
-
editModalOverlay: document.getElementById('edit-modal-overlay'),
|
| 44 |
-
editModalContent: document.getElementById('edit-modal-content'),
|
| 45 |
-
editInput: document.getElementById('edit-input'),
|
| 46 |
-
editModalConfirmBtn: document.getElementById('edit-modal-confirm-btn'),
|
| 47 |
-
editModalCancelBtn: document.getElementById('edit-modal-cancel-btn'),
|
| 48 |
-
htmlPreviewModal: document.getElementById('html-preview-modal'),
|
| 49 |
-
htmlPreviewOverlay: document.getElementById('html-preview-overlay'),
|
| 50 |
-
htmlPreviewContent: document.getElementById('html-preview-content'),
|
| 51 |
-
htmlPreviewIframe: document.getElementById('html-preview-iframe'),
|
| 52 |
-
htmlPreviewCloseBtn: document.getElementById('html-preview-close-btn'),
|
| 53 |
-
selectImageOption: document.getElementById('select-image-option'),
|
| 54 |
-
selectFileOption: document.getElementById('select-file-option'),
|
| 55 |
-
imageGalleryModal: document.getElementById('image-gallery-modal'),
|
| 56 |
-
imageGalleryContent: document.getElementById('image-gallery-content'),
|
| 57 |
-
galleryCloseBtn: document.getElementById('gallery-close-btn'),
|
| 58 |
-
galleryMainImage: document.getElementById('gallery-main-image'),
|
| 59 |
-
galleryPrevBtn: document.getElementById('gallery-prev-btn'),
|
| 60 |
-
galleryNextBtn: document.getElementById('gallery-next-btn'),
|
| 61 |
-
galleryThumbnails: document.getElementById('gallery-thumbnails'),
|
| 62 |
-
galleryDownloadBtn: document.getElementById('gallery-download-btn'),
|
| 63 |
-
settingsButton: document.getElementById('settings-button'),
|
| 64 |
-
settingsModal: document.getElementById('settings-modal'),
|
| 65 |
-
settingsModalContent: document.getElementById('settings-modal-content'),
|
| 66 |
-
themeToggle: document.getElementById('theme-toggle'),
|
| 67 |
-
settingsUserTier: document.getElementById('settings-user-tier'),
|
| 68 |
-
premiumFeatureModal: document.getElementById('premium-feature-modal'),
|
| 69 |
-
premiumModalIconContainer: document.getElementById('premium-modal-icon-container'),
|
| 70 |
-
premiumModalCloseBtn: document.getElementById('premium-modal-close-btn'),
|
| 71 |
-
premiumModalUpgradeBtn: document.getElementById('premium-modal-upgrade-btn'),
|
| 72 |
-
plusRequiredModal: document.getElementById('plus-required-modal'),
|
| 73 |
-
plusModalIconContainer: document.getElementById('plus-modal-icon-container'),
|
| 74 |
-
plusModalCloseBtn: document.getElementById('plus-modal-close-btn'),
|
| 75 |
-
toolsButton: document.getElementById('tools-button'),
|
| 76 |
-
toolsMenu: document.getElementById('tools-menu'),
|
| 77 |
-
filePopupMenu: document.getElementById('file-popup-menu'),
|
| 78 |
-
toolsButtonText: document.getElementById('tools-button-text'),
|
| 79 |
-
toolsDefaultIcon: document.getElementById('tools-default-icon'),
|
| 80 |
-
clearToolSelection: document.getElementById('clear-tool-selection'),
|
| 81 |
-
globalAudioPlayer: document.getElementById('global-audio-player'),
|
| 82 |
-
globalAudioElement: document.getElementById('global-audio-element'),
|
| 83 |
-
globalPlayerPlayPause: document.getElementById('global-player-play-pause'),
|
| 84 |
-
globalPlayerPlayIcon: document.getElementById('global-player-play-icon'),
|
| 85 |
-
globalPlayerPauseIcon: document.getElementById('global-player-pause-icon'),
|
| 86 |
-
globalPlayerText: document.getElementById('global-player-text'),
|
| 87 |
-
globalPlayerCurrentTime: document.getElementById('global-player-current-time'),
|
| 88 |
-
globalPlayerTotalTime: document.getElementById('global-player-total-time'),
|
| 89 |
-
waveformCanvas: document.getElementById('waveform-canvas'),
|
| 90 |
-
globalPlayerLoading: document.getElementById('global-player-loading'),
|
| 91 |
-
globalPlayerClose: document.getElementById('global-player-close'),
|
| 92 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/js/ui/modals.js
DELETED
|
@@ -1,428 +0,0 @@
|
|
| 1 |
-
// --- START OF FILE modals.js ---
|
| 2 |
-
// static/js/ui/modals.js
|
| 3 |
-
|
| 4 |
-
import { dom } from './dom.js';
|
| 5 |
-
|
| 6 |
-
let currentGalleryImages = [];
|
| 7 |
-
let currentGalleryIndex = 0;
|
| 8 |
-
|
| 9 |
-
// تابع کمکی برای ساخت آیتمهای منو
|
| 10 |
-
const createMenuItem = (options) => {
|
| 11 |
-
const { action, format = '', text, icon, isDanger = false, type = 'button' } = options;
|
| 12 |
-
const element = document.createElement(type);
|
| 13 |
-
element.className = `menu-item ${isDanger ? 'danger' : ''}`;
|
| 14 |
-
element.dataset.action = action;
|
| 15 |
-
if (format) element.dataset.format = format;
|
| 16 |
-
// اضافه کردن آیکون و متن + لودینگ مخفی
|
| 17 |
-
element.innerHTML = `${icon}<span>${text}</span><div class="hidden w-4 h-4 border-2 border-slate-300 border-t-blue-500 rounded-full animate-spin ml-auto"></div>`;
|
| 18 |
-
return element;
|
| 19 |
-
};
|
| 20 |
-
|
| 21 |
-
// تابع نمایش تصویر در گالری (بدون تغییر)
|
| 22 |
-
function showImageInGallery(index) {
|
| 23 |
-
if (index < 0 || index >= currentGalleryImages.length) return;
|
| 24 |
-
currentGalleryIndex = index;
|
| 25 |
-
const newImageUrl = currentGalleryImages[index];
|
| 26 |
-
|
| 27 |
-
dom.galleryMainImage.style.opacity = '0';
|
| 28 |
-
setTimeout(() => {
|
| 29 |
-
dom.galleryMainImage.src = newImageUrl;
|
| 30 |
-
dom.galleryMainImage.style.opacity = '1';
|
| 31 |
-
}, 150);
|
| 32 |
-
|
| 33 |
-
const thumbnails = dom.galleryThumbnails.querySelectorAll('.gallery-thumb');
|
| 34 |
-
thumbnails.forEach((thumb, i) => {
|
| 35 |
-
thumb.classList.toggle('active', i === index);
|
| 36 |
-
if (i === index) {
|
| 37 |
-
thumb.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
|
| 38 |
-
}
|
| 39 |
-
});
|
| 40 |
-
}
|
| 41 |
-
|
| 42 |
-
const handleGalleryKeyDown = (e) => {
|
| 43 |
-
if (e.key === 'ArrowRight') showImageInGallery((currentGalleryIndex + 1) % currentGalleryImages.length);
|
| 44 |
-
else if (e.key === 'ArrowLeft') showImageInGallery((currentGalleryIndex - 1 + currentGalleryImages.length) % currentGalleryImages.length);
|
| 45 |
-
else if (e.key === 'Escape') closeImageGallery();
|
| 46 |
-
};
|
| 47 |
-
|
| 48 |
-
export function openImageGallery(imageUrlsString, startIndex) {
|
| 49 |
-
try {
|
| 50 |
-
currentGalleryImages = JSON.parse(imageUrlsString);
|
| 51 |
-
} catch (e) {
|
| 52 |
-
console.error("Failed to parse image URLs for gallery:", e);
|
| 53 |
-
return;
|
| 54 |
-
}
|
| 55 |
-
|
| 56 |
-
if (!currentGalleryImages || currentGalleryImages.length === 0) return;
|
| 57 |
-
|
| 58 |
-
dom.galleryThumbnails.innerHTML = '';
|
| 59 |
-
currentGalleryImages.forEach((url, index) => {
|
| 60 |
-
const thumb = document.createElement('img');
|
| 61 |
-
thumb.src = url;
|
| 62 |
-
thumb.className = 'gallery-thumb';
|
| 63 |
-
thumb.onclick = () => showImageInGallery(index);
|
| 64 |
-
dom.galleryThumbnails.appendChild(thumb);
|
| 65 |
-
});
|
| 66 |
-
|
| 67 |
-
showImageInGallery(startIndex);
|
| 68 |
-
|
| 69 |
-
dom.imageGalleryModal.classList.remove('hidden');
|
| 70 |
-
requestAnimationFrame(() => {
|
| 71 |
-
dom.imageGalleryModal.classList.add('visible');
|
| 72 |
-
});
|
| 73 |
-
|
| 74 |
-
dom.galleryCloseBtn.onclick = closeImageGallery;
|
| 75 |
-
dom.imageGalleryModal.onclick = (e) => { if (e.target === dom.imageGalleryModal) closeImageGallery(); };
|
| 76 |
-
dom.galleryNextBtn.onclick = () => showImageInGallery((currentGalleryIndex + 1) % currentGalleryImages.length);
|
| 77 |
-
dom.galleryPrevBtn.onclick = () => showImageInGallery((currentGalleryIndex - 1 + currentGalleryImages.length) % currentGalleryImages.length);
|
| 78 |
-
window.addEventListener('keydown', handleGalleryKeyDown);
|
| 79 |
-
}
|
| 80 |
-
|
| 81 |
-
function closeImageGallery() {
|
| 82 |
-
dom.imageGalleryModal.classList.remove('visible');
|
| 83 |
-
setTimeout(() => {
|
| 84 |
-
dom.imageGalleryModal.classList.add('hidden');
|
| 85 |
-
dom.galleryMainImage.src = '';
|
| 86 |
-
}, 300);
|
| 87 |
-
window.removeEventListener('keydown', handleGalleryKeyDown);
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
-
export function toggleSidebar(show) {
|
| 91 |
-
if (show) {
|
| 92 |
-
dom.sidebarOverlay.classList.remove('hidden');
|
| 93 |
-
requestAnimationFrame(() => {
|
| 94 |
-
dom.sidebarOverlay.style.opacity = '1';
|
| 95 |
-
dom.historySidebar.style.transform = 'translateX(0)';
|
| 96 |
-
});
|
| 97 |
-
} else {
|
| 98 |
-
dom.sidebarOverlay.style.opacity = '0';
|
| 99 |
-
dom.historySidebar.style.transform = 'translateX(100%)';
|
| 100 |
-
setTimeout(() => dom.sidebarOverlay.classList.add('hidden'), 300);
|
| 101 |
-
}
|
| 102 |
-
}
|
| 103 |
-
|
| 104 |
-
export function toggleEditModal(show) {
|
| 105 |
-
if (show) {
|
| 106 |
-
dom.editModal.classList.remove('hidden');
|
| 107 |
-
requestAnimationFrame(() => {
|
| 108 |
-
dom.editModalOverlay.style.opacity = '1';
|
| 109 |
-
dom.editModalContent.style.opacity = '1';
|
| 110 |
-
dom.editModalContent.style.transform = 'scale(1)';
|
| 111 |
-
dom.editInput.focus();
|
| 112 |
-
});
|
| 113 |
-
} else {
|
| 114 |
-
dom.editModalOverlay.style.opacity = '0';
|
| 115 |
-
dom.editModalContent.style.opacity = '0';
|
| 116 |
-
dom.editModalContent.style.transform = 'scale(0.95)';
|
| 117 |
-
setTimeout(() => dom.editModal.classList.add('hidden'), 300);
|
| 118 |
-
}
|
| 119 |
-
}
|
| 120 |
-
|
| 121 |
-
export function toggleHtmlPreviewModal(show, htmlContent = '') {
|
| 122 |
-
if (show) {
|
| 123 |
-
dom.htmlPreviewIframe.srcdoc = htmlContent;
|
| 124 |
-
dom.htmlPreviewModal.classList.remove('hidden');
|
| 125 |
-
requestAnimationFrame(() => {
|
| 126 |
-
dom.htmlPreviewOverlay.style.opacity = '1';
|
| 127 |
-
dom.htmlPreviewContent.style.opacity = '1';
|
| 128 |
-
dom.htmlPreviewContent.style.transform = 'scale(1)';
|
| 129 |
-
});
|
| 130 |
-
} else {
|
| 131 |
-
dom.htmlPreviewOverlay.style.opacity = '0';
|
| 132 |
-
dom.htmlPreviewContent.style.opacity = '0';
|
| 133 |
-
dom.htmlPreviewContent.style.transform = 'scale(0.95)';
|
| 134 |
-
setTimeout(() => {
|
| 135 |
-
dom.htmlPreviewModal.classList.add('hidden');
|
| 136 |
-
dom.htmlPreviewIframe.srcdoc = '';
|
| 137 |
-
}, 300);
|
| 138 |
-
}
|
| 139 |
-
}
|
| 140 |
-
|
| 141 |
-
// *** تابع اصلی که منوی سه نقطه تاریخچه را میسازد (به روز شده) ***
|
| 142 |
-
export function showHistoryMenu(event, sessionId) {
|
| 143 |
-
event.stopPropagation();
|
| 144 |
-
const menu = dom.historyItemMenu;
|
| 145 |
-
|
| 146 |
-
// تعریف دقیق آیتمها به ترتیب خواسته شده
|
| 147 |
-
menu.innerHTML = `
|
| 148 |
-
${createMenuItem({
|
| 149 |
-
action: 'rename',
|
| 150 |
-
text: 'تغییر نام گفتگو',
|
| 151 |
-
icon: `<svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125" /></svg>`
|
| 152 |
-
}).outerHTML}
|
| 153 |
-
|
| 154 |
-
<div class="menu-divider"></div>
|
| 155 |
-
|
| 156 |
-
${createMenuItem({
|
| 157 |
-
action: 'convert-chat',
|
| 158 |
-
format: 'pdf',
|
| 159 |
-
text: 'تبدیل به PDF',
|
| 160 |
-
icon: `<svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m.75 12l3 3m0 0l3-3m-3 3v-6m-1.5-9H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>`
|
| 161 |
-
}).outerHTML}
|
| 162 |
-
|
| 163 |
-
${createMenuItem({
|
| 164 |
-
action: 'convert-chat',
|
| 165 |
-
format: 'docx',
|
| 166 |
-
text: 'تبدیل به Word',
|
| 167 |
-
icon: `<svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H15M2.25 3h1.5M2.25 6h1.5M2.25 9h1.5M2.25 12h1.5M2.25 15h1.5M2.25 18h1.5M4.5 21h15a2.25 2.25 0 002.25-2.25V5.25A2.25 2.25 0 0019.5 3h-15A2.25 2.25 0 002.25 5.25v13.5A2.25 2.25 0 004.5 21z" /></svg>`
|
| 168 |
-
}).outerHTML}
|
| 169 |
-
|
| 170 |
-
${createMenuItem({
|
| 171 |
-
action: 'convert-chat',
|
| 172 |
-
format: 'txt',
|
| 173 |
-
text: 'تبدیل به Text',
|
| 174 |
-
icon: `<svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12" /></svg>`
|
| 175 |
-
}).outerHTML}
|
| 176 |
-
|
| 177 |
-
${createMenuItem({
|
| 178 |
-
action: 'convert-chat',
|
| 179 |
-
format: 'html',
|
| 180 |
-
text: 'تبدیل به HTML',
|
| 181 |
-
icon: `<svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5" /></svg>`
|
| 182 |
-
}).outerHTML}
|
| 183 |
-
|
| 184 |
-
<div class="menu-divider"></div>
|
| 185 |
-
|
| 186 |
-
${createMenuItem({
|
| 187 |
-
action: 'delete',
|
| 188 |
-
text: 'حذف گفتگو',
|
| 189 |
-
icon: `<svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" /></svg>`,
|
| 190 |
-
isDanger: true
|
| 191 |
-
}).outerHTML}
|
| 192 |
-
`;
|
| 193 |
-
|
| 194 |
-
menu.dataset.sessionId = sessionId;
|
| 195 |
-
|
| 196 |
-
// تنظیم موقعیت منو
|
| 197 |
-
const buttonRect = event.currentTarget.getBoundingClientRect();
|
| 198 |
-
const menuHeight = 350; // ارتفاع تخمینی منو با گزینههای بیشتر
|
| 199 |
-
const margin = 8;
|
| 200 |
-
|
| 201 |
-
let top = buttonRect.bottom + margin;
|
| 202 |
-
let right = window.innerWidth - buttonRect.right;
|
| 203 |
-
|
| 204 |
-
// اگر پایین صفحه جا نیست، منو را به سمت بالا باز کن
|
| 205 |
-
if (top + menuHeight > window.innerHeight) {
|
| 206 |
-
top = buttonRect.top - menuHeight - margin;
|
| 207 |
-
}
|
| 208 |
-
|
| 209 |
-
// جلوگیری از بیرون زدن از بالای صفحه
|
| 210 |
-
if (top < 0) top = 10;
|
| 211 |
-
|
| 212 |
-
menu.style.top = `${top}px`;
|
| 213 |
-
menu.style.right = `${right}px`;
|
| 214 |
-
menu.style.left = 'auto';
|
| 215 |
-
menu.style.transformOrigin = (top > buttonRect.top) ? 'top right' : 'bottom right';
|
| 216 |
-
|
| 217 |
-
menu.classList.add('visible');
|
| 218 |
-
const closeMenu = () => {
|
| 219 |
-
menu.classList.remove('visible');
|
| 220 |
-
window.removeEventListener('click', closeMenu);
|
| 221 |
-
};
|
| 222 |
-
window.addEventListener('click', closeMenu, { once: true });
|
| 223 |
-
}
|
| 224 |
-
|
| 225 |
-
export function showMessageMenu(event, messageIndex, activeChat, escapeHTML) {
|
| 226 |
-
event.stopPropagation();
|
| 227 |
-
const menu = dom.messageItemMenu;
|
| 228 |
-
const menuContent = dom.messageItemMenuContent;
|
| 229 |
-
const message = activeChat.messages[messageIndex];
|
| 230 |
-
if (!message) return;
|
| 231 |
-
|
| 232 |
-
const textPart = message.parts.find(p => p.text);
|
| 233 |
-
const textContent = textPart ? textPart.text : '[محتوای غیر متنی]';
|
| 234 |
-
|
| 235 |
-
// برای منوی پیام تکی هم همان گزینهها را با استفاده از تابع کمکی میسازیم
|
| 236 |
-
let menuItemsHtml = '';
|
| 237 |
-
|
| 238 |
-
if (message.role === 'assistant' && textPart) {
|
| 239 |
-
menuItemsHtml += createMenuItem({ action: 'convert-message', format: 'pdf', text: 'تبدیل به PDF', icon: `<svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m.75 12l3 3m0 0l3-3m-3 3v-6m-1.5-9H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>` }).outerHTML;
|
| 240 |
-
menuItemsHtml += createMenuItem({ action: 'convert-message', format: 'docx', text: 'تبدیل به Word', icon: `<svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H15M2.25 3h1.5M2.25 6h1.5M2.25 9h1.5M2.25 12h1.5M2.25 15h1.5M2.25 18h1.5M4.5 21h15a2.25 2.25 0 002.25-2.25V5.25A2.25 2.25 0 0019.5 3h-15A2.25 2.25 0 002.25 5.25v13.5A2.25 2.25 0 004.5 21z" /></svg>` }).outerHTML;
|
| 241 |
-
menuItemsHtml += createMenuItem({ action: 'convert-message', format: 'txt', text: 'تبدیل به Text', icon: `<svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12" /></svg>` }).outerHTML;
|
| 242 |
-
menuItemsHtml += createMenuItem({ action: 'convert-message', format: 'html', text: 'تبدیل به HTML', icon: `<svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5" /></svg>` }).outerHTML;
|
| 243 |
-
menuItemsHtml += '<div class="menu-divider"></div>';
|
| 244 |
-
}
|
| 245 |
-
|
| 246 |
-
menuItemsHtml += createMenuItem({
|
| 247 |
-
action: 'delete-message',
|
| 248 |
-
text: 'حذف پیام',
|
| 249 |
-
icon: `<svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" /></svg>`,
|
| 250 |
-
isDanger: true
|
| 251 |
-
}).outerHTML;
|
| 252 |
-
|
| 253 |
-
const escapedContent = escapeHTML(textContent);
|
| 254 |
-
|
| 255 |
-
menuContent.innerHTML = `
|
| 256 |
-
<div class="message-preview-container">
|
| 257 |
-
<p class="message-preview-text">${escapedContent}</p>
|
| 258 |
-
</div>
|
| 259 |
-
${menuItemsHtml}
|
| 260 |
-
`;
|
| 261 |
-
|
| 262 |
-
menu.dataset.messageIndex = messageIndex;
|
| 263 |
-
|
| 264 |
-
menu.classList.remove('hidden');
|
| 265 |
-
requestAnimationFrame(() => {
|
| 266 |
-
menu.classList.add('visible');
|
| 267 |
-
});
|
| 268 |
-
}
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
export function showConfirmModal(message, onConfirm) {
|
| 272 |
-
dom.confirmModalMessage.textContent = message;
|
| 273 |
-
dom.confirmModal.classList.remove('hidden');
|
| 274 |
-
requestAnimationFrame(() => {
|
| 275 |
-
dom.confirmModalOverlay.style.opacity = '1';
|
| 276 |
-
dom.confirmModalContent.style.opacity = '1';
|
| 277 |
-
dom.confirmModalContent.style.transform = 'scale(1)';
|
| 278 |
-
});
|
| 279 |
-
const hide = () => {
|
| 280 |
-
dom.confirmModalOverlay.style.opacity = '0';
|
| 281 |
-
dom.confirmModalContent.style.opacity = '0';
|
| 282 |
-
dom.confirmModalContent.style.transform = 'scale(0.95)';
|
| 283 |
-
setTimeout(() => dom.confirmModal.classList.add('hidden'), 300);
|
| 284 |
-
};
|
| 285 |
-
dom.confirmModalConfirmBtn.onclick = () => { onConfirm(); hide(); };
|
| 286 |
-
dom.confirmModalCancelBtn.onclick = hide;
|
| 287 |
-
dom.confirmModalOverlay.onclick = hide;
|
| 288 |
-
}
|
| 289 |
-
|
| 290 |
-
export function showRenameModal(currentTitle, onConfirm) {
|
| 291 |
-
dom.renameInput.value = currentTitle;
|
| 292 |
-
dom.renameModal.classList.remove('hidden');
|
| 293 |
-
requestAnimationFrame(() => {
|
| 294 |
-
dom.renameModalOverlay.style.opacity = '1';
|
| 295 |
-
dom.renameModalContent.style.opacity = '1';
|
| 296 |
-
dom.renameModalContent.style.transform = 'scale(1)';
|
| 297 |
-
dom.renameInput.focus();
|
| 298 |
-
dom.renameInput.select();
|
| 299 |
-
});
|
| 300 |
-
const hide = () => {
|
| 301 |
-
dom.renameModalOverlay.style.opacity = '0';
|
| 302 |
-
dom.renameModalContent.style.opacity = '0';
|
| 303 |
-
dom.renameModalContent.style.transform = 'scale(0.95)';
|
| 304 |
-
setTimeout(() => dom.renameModal.classList.add('hidden'), 300);
|
| 305 |
-
};
|
| 306 |
-
dom.renameModalContent.onsubmit = (e) => {
|
| 307 |
-
e.preventDefault();
|
| 308 |
-
const newTitle = dom.renameInput.value.trim();
|
| 309 |
-
if (newTitle) { onConfirm(newTitle); }
|
| 310 |
-
hide();
|
| 311 |
-
};
|
| 312 |
-
dom.renameModalCancelBtn.onclick = hide;
|
| 313 |
-
dom.renameModalOverlay.onclick = hide;
|
| 314 |
-
}
|
| 315 |
-
|
| 316 |
-
export function showEditModal(currentText, onConfirm) {
|
| 317 |
-
dom.editInput.value = currentText;
|
| 318 |
-
toggleEditModal(true);
|
| 319 |
-
dom.editModalContent.onsubmit = (e) => {
|
| 320 |
-
e.preventDefault();
|
| 321 |
-
const newText = dom.editInput.value.trim();
|
| 322 |
-
if (newText === '') {
|
| 323 |
-
showConfirmModal('متن پیام شما خالی است. آیا مایل به حذف پیام هستید؟', () => { onConfirm(''); });
|
| 324 |
-
} else if (newText !== currentText) {
|
| 325 |
-
onConfirm(newText);
|
| 326 |
-
}
|
| 327 |
-
toggleEditModal(false);
|
| 328 |
-
};
|
| 329 |
-
dom.editModalCancelBtn.onclick = () => toggleEditModal(false);
|
| 330 |
-
dom.editModalOverlay.onclick = () => toggleEditModal(false);
|
| 331 |
-
}
|
| 332 |
-
|
| 333 |
-
export function toggleSettingsModal(show) {
|
| 334 |
-
const modal = dom.settingsModal;
|
| 335 |
-
const content = dom.settingsModalContent;
|
| 336 |
-
if (show) {
|
| 337 |
-
modal.classList.remove('hidden');
|
| 338 |
-
requestAnimationFrame(() => {
|
| 339 |
-
modal.style.opacity = '1';
|
| 340 |
-
content.style.opacity = '1';
|
| 341 |
-
content.style.transform = 'scale(1)';
|
| 342 |
-
});
|
| 343 |
-
} else {
|
| 344 |
-
modal.style.opacity = '0';
|
| 345 |
-
content.style.opacity = '0';
|
| 346 |
-
content.style.transform = 'scale(0.95)';
|
| 347 |
-
setTimeout(() => modal.classList.add('hidden'), 200);
|
| 348 |
-
}
|
| 349 |
-
}
|
| 350 |
-
|
| 351 |
-
export function updateSettingsUI(isPremium) {
|
| 352 |
-
if (!dom.settingsUserTier) return;
|
| 353 |
-
|
| 354 |
-
let content = '';
|
| 355 |
-
if (isPremium) {
|
| 356 |
-
const premiumIcon = `<svg class="settings-tier-icon" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="premium-gradient" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#818cf8"/><stop offset="100%" stop-color="#c084fc"/></linearGradient></defs><path fill="url(#premium-gradient)" d="M50,5 C74.85,5 95,25.15 95,50 C95,74.85 74.85,95 50,95 C25.15,95 5,74.85 5,50 C5,25.15 25.15,5 50,5 Z M50,15 C30.67,15 15,30.67 15,50 C15,69.33 30.67,85 50,85 C69.33,85 85,69.33 85,50 C85,30.67 69.33,15 50,15 Z" /><text x="50" y="62" font-family="Arial" font-size="30" fill="white" text-anchor="middle" font-weight="bold">∞</text></svg>`;
|
| 357 |
-
content = `
|
| 358 |
-
<div class="settings-tier-container">
|
| 359 |
-
${premiumIcon}
|
| 360 |
-
<div class="settings-tier-text text-slate-800 dark:text-slate-200">
|
| 361 |
-
نسخه: <span class="tier-name-premium">نامحدود</span>
|
| 362 |
-
</div>
|
| 363 |
-
</div>`;
|
| 364 |
-
} else {
|
| 365 |
-
const freeIcon = `<svg class="settings-tier-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 13.8214 2.48697 15.5291 3.33782 17" stroke="#64748b" stroke-width="2" stroke-linecap="round"/><path d="M19 9L12 16L9.5 13.5" stroke="#64748b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
| 366 |
-
content = `
|
| 367 |
-
<div class="settings-tier-container">
|
| 368 |
-
${freeIcon}
|
| 369 |
-
<div class="settings-tier-text">
|
| 370 |
-
نسخه: <span class="tier-name-free">رایگان</span>
|
| 371 |
-
</div>
|
| 372 |
-
</div>`;
|
| 373 |
-
}
|
| 374 |
-
dom.settingsUserTier.innerHTML = content;
|
| 375 |
-
}
|
| 376 |
-
|
| 377 |
-
export function togglePremiumFeatureModal(show) {
|
| 378 |
-
if (show) {
|
| 379 |
-
const icon = `<svg width="80" height="80" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
| 380 |
-
<defs>
|
| 381 |
-
<linearGradient id="icon-gradient" x1="0" y1="0" x2="1" y2="1">
|
| 382 |
-
<stop offset="0%" stop-color="#818cf8" />
|
| 383 |
-
<stop offset="100%" stop-color="#c084fc" />
|
| 384 |
-
</linearGradient>
|
| 385 |
-
</defs>
|
| 386 |
-
<path d="M12 2.5C12.41 2.5 12.75 2.84 12.75 3.25V4.44C15.96 4.97 18.5 7.79 18.5 11.06V17L20 18.5V19.5H4V18.5L5.5 17V11.06C5.5 7.79 8.04 4.97 11.25 4.44V3.25C11.25 2.84 11.59 2.5 12 2.5ZM12 22C13.1 22 14 21.1 14 20H10C10 21.1 10.9 22 12 22Z" fill="url(#icon-gradient)"/>
|
| 387 |
-
</svg>`;
|
| 388 |
-
dom.premiumModalIconContainer.innerHTML = icon;
|
| 389 |
-
dom.premiumFeatureModal.classList.remove('hidden');
|
| 390 |
-
requestAnimationFrame(() => {
|
| 391 |
-
dom.premiumFeatureModal.classList.add('visible');
|
| 392 |
-
});
|
| 393 |
-
} else {
|
| 394 |
-
dom.premiumFeatureModal.classList.remove('visible');
|
| 395 |
-
setTimeout(() => {
|
| 396 |
-
dom.premiumFeatureModal.classList.add('hidden');
|
| 397 |
-
}, 300);
|
| 398 |
-
}
|
| 399 |
-
}
|
| 400 |
-
|
| 401 |
-
export function togglePlusRequiredModal(show) {
|
| 402 |
-
const modal = dom.plusRequiredModal;
|
| 403 |
-
if (!modal) return;
|
| 404 |
-
|
| 405 |
-
if (show) {
|
| 406 |
-
const icon = `<svg width="80" height="80" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
| 407 |
-
<defs>
|
| 408 |
-
<linearGradient id="plus-icon-gradient" x1="0" y1="0" x2="1" y2="1">
|
| 409 |
-
<stop offset="0%" stop-color="#3b82f6" />
|
| 410 |
-
<stop offset="100%" stop-color="#14b8a6" />
|
| 411 |
-
</linearGradient>
|
| 412 |
-
</defs>
|
| 413 |
-
<path d="M11 11V3H13V11H21V13H13V21H11V13H3V11H11Z" fill="url(#plus-icon-gradient)"/>
|
| 414 |
-
<path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 20C7.58 20 4 16.42 4 12C4 7.58 7.58 4 12 4C16.42 4 20 7.58 20 12C20 16.42 16.42 20 12 20Z" fill="url(#plus-icon-gradient)" fill-opacity="0.6"/>
|
| 415 |
-
</svg>`;
|
| 416 |
-
dom.plusModalIconContainer.innerHTML = icon;
|
| 417 |
-
modal.classList.remove('hidden');
|
| 418 |
-
requestAnimationFrame(() => {
|
| 419 |
-
modal.classList.add('visible');
|
| 420 |
-
});
|
| 421 |
-
} else {
|
| 422 |
-
modal.classList.remove('visible');
|
| 423 |
-
setTimeout(() => {
|
| 424 |
-
modal.classList.add('hidden');
|
| 425 |
-
}, 300);
|
| 426 |
-
}
|
| 427 |
-
}
|
| 428 |
-
// --- END OF FILE modals.js ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/js/ui/tools.js
DELETED
|
@@ -1,181 +0,0 @@
|
|
| 1 |
-
// static/js/ui/tools.js
|
| 2 |
-
|
| 3 |
-
import { dom } from './dom.js';
|
| 4 |
-
import { escapeHTML } from './chat.js';
|
| 5 |
-
|
| 6 |
-
export function toggleFilePopupMenu(show) {
|
| 7 |
-
dom.toolsMenu.classList.remove('active');
|
| 8 |
-
if (show) {
|
| 9 |
-
dom.filePopupMenu.classList.add('active');
|
| 10 |
-
} else {
|
| 11 |
-
dom.filePopupMenu.classList.remove('active');
|
| 12 |
-
}
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
-
export function toggleToolsMenu(show) {
|
| 16 |
-
dom.filePopupMenu.classList.remove('active');
|
| 17 |
-
if (show) {
|
| 18 |
-
dom.toolsMenu.classList.add('active');
|
| 19 |
-
} else {
|
| 20 |
-
dom.toolsMenu.classList.remove('active');
|
| 21 |
-
}
|
| 22 |
-
}
|
| 23 |
-
|
| 24 |
-
export function updateToolsButton(toolName) {
|
| 25 |
-
if (toolName) {
|
| 26 |
-
dom.toolsButtonText.textContent = toolName;
|
| 27 |
-
dom.toolsDefaultIcon.style.display = 'none';
|
| 28 |
-
dom.clearToolSelection.style.display = 'flex';
|
| 29 |
-
dom.toolsButton.classList.add('tool-selected');
|
| 30 |
-
} else {
|
| 31 |
-
dom.toolsButtonText.textContent = 'ابزارها';
|
| 32 |
-
dom.toolsDefaultIcon.style.display = 'flex';
|
| 33 |
-
dom.clearToolSelection.style.display = 'none';
|
| 34 |
-
dom.toolsButton.classList.remove('tool-selected');
|
| 35 |
-
dom.messageInput.placeholder = 'هر چی میخوای بپرس';
|
| 36 |
-
}
|
| 37 |
-
}
|
| 38 |
-
|
| 39 |
-
function getDeepThinkTVHTML() {
|
| 40 |
-
return `
|
| 41 |
-
<div class="deep-think-tv-container">
|
| 42 |
-
<section class="deep-think-tv">
|
| 43 |
-
<div class="screen scanlines gloss">
|
| 44 |
-
<div class="ui">
|
| 45 |
-
<div class="header">
|
| 46 |
-
<div class="topbar">
|
| 47 |
-
<div class="badge"><span class="dot"></span>تفکر عمیق چت باتآلفا</div>
|
| 48 |
-
</div>
|
| 49 |
-
<div class="topic"><span>موضوع:</span><strong id="tv-topic">...</strong></div>
|
| 50 |
-
</div>
|
| 51 |
-
<div class="steps">
|
| 52 |
-
<div class="step active" data-step="analyzing"><span class="mini"></span>تحقیق و بررسی</div>
|
| 53 |
-
</div>
|
| 54 |
-
<div class="progress"><div class="bar" id="tv-bar" style="width:0%"></div></div>
|
| 55 |
-
<div id="tv-log" class="log custom-scrollbar"></div>
|
| 56 |
-
</div>
|
| 57 |
-
</div>
|
| 58 |
-
</section>
|
| 59 |
-
</div>
|
| 60 |
-
<div class="final-answer-wrapper"></div>
|
| 61 |
-
`;
|
| 62 |
-
}
|
| 63 |
-
|
| 64 |
-
export function createDeepThinkPanel(modelBubbleOuterDivElement) {
|
| 65 |
-
const contentArea = modelBubbleOuterDivElement.querySelector('.message-content');
|
| 66 |
-
if (!contentArea || contentArea.querySelector('.deep-think-tv')) return;
|
| 67 |
-
contentArea.innerHTML = getDeepThinkTVHTML();
|
| 68 |
-
const tv = contentArea.querySelector('.deep-think-tv');
|
| 69 |
-
if (document.documentElement.classList.contains('dark')) {
|
| 70 |
-
tv.classList.add('dark');
|
| 71 |
-
}
|
| 72 |
-
}
|
| 73 |
-
|
| 74 |
-
export function updateDeepThinkPanel(data, modelBubbleOuterDivElement) {
|
| 75 |
-
const tv = modelBubbleOuterDivElement.querySelector('.deep-think-tv');
|
| 76 |
-
if (!tv) return;
|
| 77 |
-
|
| 78 |
-
if (data.topic) {
|
| 79 |
-
const topicEl = tv.querySelector('#tv-topic');
|
| 80 |
-
if (topicEl) topicEl.textContent = data.topic;
|
| 81 |
-
}
|
| 82 |
-
if (data.log) {
|
| 83 |
-
const logEl = tv.querySelector('#tv-log');
|
| 84 |
-
if (logEl) {
|
| 85 |
-
const row = document.createElement('div');
|
| 86 |
-
row.className = 'row';
|
| 87 |
-
row.innerHTML = `<div class="icon">✓</div><div>${escapeHTML(data.log)}</div>`;
|
| 88 |
-
logEl.appendChild(row);
|
| 89 |
-
logEl.scrollTop = logEl.scrollHeight;
|
| 90 |
-
}
|
| 91 |
-
}
|
| 92 |
-
if (data.progress) {
|
| 93 |
-
const barEl = tv.querySelector('#tv-bar');
|
| 94 |
-
if (barEl) barEl.style.width = `${data.progress}%`;
|
| 95 |
-
}
|
| 96 |
-
}
|
| 97 |
-
|
| 98 |
-
export function hideDeepThinkPanel(modelBubbleOuterDivElement) {
|
| 99 |
-
const tvContainer = modelBubbleOuterDivElement.querySelector('.deep-think-tv-container');
|
| 100 |
-
const finalAnswerWrapper = modelBubbleOuterDivElement.querySelector('.final-answer-wrapper');
|
| 101 |
-
if (tvContainer) {
|
| 102 |
-
tvContainer.style.transition = 'opacity 0.5s, max-height 0.5s, padding 0.5s, margin 0.5s';
|
| 103 |
-
tvContainer.style.opacity = '0';
|
| 104 |
-
tvContainer.style.maxHeight = '0';
|
| 105 |
-
tvContainer.style.padding = '0';
|
| 106 |
-
tvContainer.style.margin = '0';
|
| 107 |
-
tvContainer.style.overflow = 'hidden';
|
| 108 |
-
}
|
| 109 |
-
if(finalAnswerWrapper) {
|
| 110 |
-
finalAnswerWrapper.classList.add('visible');
|
| 111 |
-
}
|
| 112 |
-
}
|
| 113 |
-
|
| 114 |
-
function getReasoningHUDHTML() {
|
| 115 |
-
return `
|
| 116 |
-
<div class="reasoning-hud-container">
|
| 117 |
-
<section class="reasoning-hud">
|
| 118 |
-
<div class="grid-bg"></div>
|
| 119 |
-
<div class="ui-content">
|
| 120 |
-
<div class="header">
|
| 121 |
-
<div class="badge"><span class="dot"></span>استدلال منطقی</div>
|
| 122 |
-
<div class="topic"><span>موضوع:</span><strong id="reasoning-topic">...</strong></div>
|
| 123 |
-
</div>
|
| 124 |
-
<div class="progress"><div class="bar" id="reasoning-bar" style="width:0%"></div></div>
|
| 125 |
-
<div id="reasoning-log" class="log custom-scrollbar"></div>
|
| 126 |
-
</div>
|
| 127 |
-
</section>
|
| 128 |
-
</div>
|
| 129 |
-
<div class="final-answer-wrapper"></div>
|
| 130 |
-
`;
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
export function createReasoningPanel(modelBubbleOuterDivElement) {
|
| 134 |
-
const contentArea = modelBubbleOuterDivElement.querySelector('.message-content');
|
| 135 |
-
if (!contentArea || contentArea.querySelector('.reasoning-hud')) return;
|
| 136 |
-
contentArea.innerHTML = getReasoningHUDHTML();
|
| 137 |
-
const hud = contentArea.querySelector('.reasoning-hud');
|
| 138 |
-
if (document.documentElement.classList.contains('dark')) {
|
| 139 |
-
hud.classList.add('dark');
|
| 140 |
-
}
|
| 141 |
-
}
|
| 142 |
-
|
| 143 |
-
export function updateReasoningPanel(data, modelBubbleOuterDivElement) {
|
| 144 |
-
const hud = modelBubbleOuterDivElement.querySelector('.reasoning-hud');
|
| 145 |
-
if (!hud) return;
|
| 146 |
-
|
| 147 |
-
if (data.topic) {
|
| 148 |
-
const topicEl = hud.querySelector('#reasoning-topic');
|
| 149 |
-
if (topicEl) topicEl.textContent = data.topic;
|
| 150 |
-
}
|
| 151 |
-
if (data.log) {
|
| 152 |
-
const logEl = hud.querySelector('#reasoning-log');
|
| 153 |
-
if (logEl) {
|
| 154 |
-
const row = document.createElement('div');
|
| 155 |
-
row.className = 'row';
|
| 156 |
-
row.innerHTML = `<div class="icon"><svg fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg></div><div>${escapeHTML(data.log)}</div>`;
|
| 157 |
-
logEl.appendChild(row);
|
| 158 |
-
logEl.scrollTop = logEl.scrollHeight;
|
| 159 |
-
}
|
| 160 |
-
}
|
| 161 |
-
if (data.progress) {
|
| 162 |
-
const barEl = hud.querySelector('#reasoning-bar');
|
| 163 |
-
if (barEl) barEl.style.width = `${data.progress}%`;
|
| 164 |
-
}
|
| 165 |
-
}
|
| 166 |
-
|
| 167 |
-
export function hideReasoningPanel(modelBubbleOuterDivElement) {
|
| 168 |
-
const hudContainer = modelBubbleOuterDivElement.querySelector('.reasoning-hud-container');
|
| 169 |
-
const finalAnswerWrapper = modelBubbleOuterDivElement.querySelector('.final-answer-wrapper');
|
| 170 |
-
if (hudContainer) {
|
| 171 |
-
hudContainer.style.transition = 'opacity 0.5s, max-height 0.5s, padding 0.5s, margin 0.5s';
|
| 172 |
-
hudContainer.style.opacity = '0';
|
| 173 |
-
hudContainer.style.maxHeight = '0';
|
| 174 |
-
hudContainer.style.padding = '0';
|
| 175 |
-
hudContainer.style.margin = '0';
|
| 176 |
-
hudContainer.style.overflow = 'hidden';
|
| 177 |
-
}
|
| 178 |
-
if(finalAnswerWrapper) {
|
| 179 |
-
finalAnswerWrapper.classList.add('visible');
|
| 180 |
-
}
|
| 181 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/js/ui/tts.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
| 1 |
-
// static/js/ui/tts.js
|
| 2 |
-
|
| 3 |
-
import { dom } from './dom.js';
|
| 4 |
-
|
| 5 |
-
const ttsCache = new Map();
|
| 6 |
-
|
| 7 |
-
const ttsState = {
|
| 8 |
-
currentMessageIndex: null,
|
| 9 |
-
animationFrameId: null,
|
| 10 |
-
activeButton: null,
|
| 11 |
-
};
|
| 12 |
-
|
| 13 |
-
const ttsStreamManager = {
|
| 14 |
-
HF_WEBSOCKET_URL: "wss://ezmary-ttslive.hf.space/ws",
|
| 15 |
-
socket: null,
|
| 16 |
-
activeStreamController: null,
|
| 17 |
-
pendingRequest: null, // برای نگهداری درخواست در زمان اتصال مجدد
|
| 18 |
-
|
| 19 |
-
connectWebSocket() {
|
| 20 |
-
// فقط اگر اتصالی وجود ندارد یا قطع شده، اتصال جدید بساز
|
| 21 |
-
if (this.socket && this.socket.readyState !== WebSocket.CLOSED) {
|
| 22 |
-
return;
|
| 23 |
-
}
|
| 24 |
-
|
| 25 |
-
this.socket = new WebSocket(this.HF_WEBSOCKET_URL);
|
| 26 |
-
|
| 27 |
-
this.socket.onopen = () => {
|
| 28 |
-
console.log("اتصال WebSocket به سرور TTS برقرار شد.");
|
| 29 |
-
// اگر درخواستی در صف انتظار بود، آن را ارسال کن
|
| 30 |
-
if (this.pendingRequest) {
|
| 31 |
-
const { messageIndex, text, button } = this.pendingRequest;
|
| 32 |
-
this.pendingRequest = null;
|
| 33 |
-
this.stream(messageIndex, text, button, true); // ارسال مجدد درخواست
|
| 34 |
-
}
|
| 35 |
-
};
|
| 36 |
-
|
| 37 |
-
this.socket.onmessage = (event) => {
|
| 38 |
-
if (this.activeStreamController && this.activeStreamController.handleMessage) {
|
| 39 |
-
this.activeStreamController.handleMessage(event);
|
| 40 |
-
}
|
| 41 |
-
};
|
| 42 |
-
|
| 43 |
-
this.socket.onclose = () => {
|
| 44 |
-
console.warn("اتصال WebSocket قطع شد.");
|
| 45 |
-
this.socket = null; // سوکت را null کن تا اتصال بعدی دوباره برقرار شود
|
| 46 |
-
if (this.activeStreamController) {
|
| 47 |
-
this.activeStreamController.stop();
|
| 48 |
-
}
|
| 49 |
-
};
|
| 50 |
-
|
| 51 |
-
this.socket.onerror = (error) => {
|
| 52 |
-
console.error("خطای WebSocket:", error);
|
| 53 |
-
if (this.socket) this.socket.close();
|
| 54 |
-
};
|
| 55 |
-
},
|
| 56 |
-
|
| 57 |
-
_createStreamController(messageIndex, button) {
|
| 58 |
-
// ... (این تابع داخلی بدون تغییر باقی میماند)
|
| 59 |
-
const controller = {
|
| 60 |
-
messageIndex: messageIndex, button: button, audioContext: null, audioQueue: [], receivedPcmChunks: [], sourceNodes: [], isStopped: false, isPlaying: false, nextStartTime: 0, timerInterval: null, startTime: 0, elapsedTime: 0,
|
| 61 |
-
_initializeAudio() { if (!this.audioContext || this.audioContext.state === 'closed') { this.audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 24000 }); } if(this.audioContext.state === 'suspended') { this.audioContext.resume(); } this.nextStartTime = this.audioContext.currentTime; },
|
| 62 |
-
async handleMessage(event) { if (this.isStopped) return; if (typeof event.data === 'string') { const message = JSON.parse(event.data); if (message.event === "STREAM_ENDED") this._handleStreamEnd(); else if (message.event === "ERROR") { console.error(`خطا از سرور TTS: ${message.message}`); this.stop(); } } else { const arrayBuffer = await event.data.arrayBuffer(); const pcmData = new Int16Array(arrayBuffer); this.audioQueue.push(pcmData); this.receivedPcmChunks.push(pcmData); if (!this.isPlaying) this._playFromQueue(); } },
|
| 63 |
-
async _playFromQueue() { if (this.audioQueue.length === 0 || this.isStopped) { this.isPlaying = false; return; } this.isPlaying = true; if (!dom.globalAudioPlayer.classList.contains('visible')) { showGlobalPlayer(false, true); this.startTime = Date.now(); this.timerInterval = setInterval(() => updateGlobalPlayerUI(this), 250); ttsState.animationFrameId = requestAnimationFrame(drawWaveform); if(this.button) { this.button.classList.remove('loading'); this.button.classList.add('playing'); } } while (this.audioQueue.length > 0) { if (this.isStopped) break; const pcmData = this.audioQueue.shift(); const float32Data = new Float32Array(pcmData.length); for (let i = 0; i < pcmData.length; i++) float32Data[i] = pcmData[i] / 32768.0; if (this.audioContext.state === 'closed') return; const audioBuffer = this.audioContext.createBuffer(1, float32Data.length, this.audioContext.sampleRate); audioBuffer.getChannelData(0).set(float32Data); const source = this.audioContext.createBufferSource(); source.buffer = audioBuffer; source.connect(this.audioContext.destination); const currentTime = this.audioContext.currentTime; this.nextStartTime = Math.max(this.nextStartTime, currentTime); source.start(this.nextStartTime); this.sourceNodes.push(source); this.nextStartTime += audioBuffer.duration; } this.isPlaying = false; },
|
| 64 |
-
_handleStreamEnd() { this._finalizeAndCacheAudio(); const checkPlaybackEnd = setInterval(() => { if (this.audioQueue.length === 0 && this.audioContext && this.audioContext.currentTime > this.nextStartTime - 0.1) { if(!this.isStopped) this.stop(); clearInterval(checkPlaybackEnd); } }, 100); },
|
| 65 |
-
_finalizeAndCacheAudio() { if (this.receivedPcmChunks.length === 0) return; const totalLength = this.receivedPcmChunks.reduce((acc, val) => acc + val.length, 0); const concatenatedPcm = new Int16Array(totalLength); let offset = 0; for (const chunk of this.receivedPcmChunks) { concatenatedPcm.set(chunk, offset); offset += chunk.length; } const wavBlob = this._createWavBlob(concatenatedPcm, this.audioContext.sampleRate); ttsCache.set(this.messageIndex, wavBlob); this.receivedPcmChunks = []; },
|
| 66 |
-
_createWavBlob(pcmData, sampleRate) { const numChannels = 1, bitsPerSample = 16; const blockAlign = (numChannels * bitsPerSample) / 8; const byteRate = sampleRate * blockAlign; const dataSize = pcmData.length * (bitsPerSample / 8); const buffer = new ArrayBuffer(44 + dataSize); const view = new DataView(buffer); function writeString(view, offset, string) { for (let i = 0; i < string.length; i++) view.setUint8(offset + i, string.charCodeAt(i)); } writeString(view, 0, 'RIFF'); view.setUint32(4, 36 + dataSize, true); writeString(view, 8, 'WAVE'); writeString(view, 12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, numChannels, true); view.setUint32(24, sampleRate, true); view.setUint32(28, byteRate, true); view.setUint16(32, blockAlign, true); view.setUint16(34, bitsPerSample, true); writeString(view, 36, 'data'); view.setUint32(40, dataSize, true); for (let i = 0; i < pcmData.length; i++) view.setInt16(44 + i * 2, pcmData[i], true); return new Blob([view], { type: 'audio/wav' }); },
|
| 67 |
-
stop() { this.isStopped = true; if (this.timerInterval) clearInterval(this.timerInterval); this.timerInterval = null; this.sourceNodes.forEach(source => { try { source.stop(); } catch(e) {} }); if (this.audioContext && this.audioContext.state !== 'closed') { this.audioContext.close(); } if (ttsStreamManager.activeStreamController === this) { ttsStreamManager.activeStreamController = null; hideGlobalPlayer(); if (this.button) this.button.classList.remove('playing', 'loading'); ttsState.activeButton = null; } },
|
| 68 |
-
pause() { if (!this.audioContext || this.audioContext.state !== 'running') return; this.audioContext.suspend().then(() => { clearInterval(this.timerInterval); this.timerInterval = null; this.elapsedTime += (Date.now() - this.startTime) / 1000; showGlobalPlayer(false, false); if(this.button) this.button.classList.remove('playing'); }); },
|
| 69 |
-
resume() { if (!this.audioContext || this.audioContext.state !== 'suspended') return; this.audioContext.resume().then(() => { this.startTime = Date.now(); this.timerInterval = setInterval(() => updateGlobalPlayerUI(this), 250); showGlobalPlayer(false, true); if(this.button) this.button.classList.add('playing'); }); }
|
| 70 |
-
};
|
| 71 |
-
controller._initializeAudio();
|
| 72 |
-
return controller;
|
| 73 |
-
},
|
| 74 |
-
|
| 75 |
-
stopCurrentStream() {
|
| 76 |
-
if (this.activeStreamController) {
|
| 77 |
-
this.activeStreamController.stop();
|
| 78 |
-
this.activeStreamController = null;
|
| 79 |
-
}
|
| 80 |
-
// *** مهم: اتصال وبسوکت را قطع کن ***
|
| 81 |
-
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
| 82 |
-
this.socket.close();
|
| 83 |
-
console.log("اتصال WebSocket به دلیل توقف توسط کاربر، عمداً قطع شد.");
|
| 84 |
-
}
|
| 85 |
-
this.pendingRequest = null; // هر درخواست در حال انتظاری را لغو کن
|
| 86 |
-
},
|
| 87 |
-
|
| 88 |
-
stream(messageIndex, text, button, isRetrying = false) {
|
| 89 |
-
if (!isRetrying) {
|
| 90 |
-
this.stopCurrentStream(); // همیشه پخش قبلی را کاملا متوقف کن
|
| 91 |
-
}
|
| 92 |
-
|
| 93 |
-
// اگر اتصال برقرار نیست، برقرار کن و درخواست را در صف بگذار
|
| 94 |
-
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
| 95 |
-
this.pendingRequest = { messageIndex, text, button };
|
| 96 |
-
setSpeakButtonLoading(button, true);
|
| 97 |
-
this.connectWebSocket(); // این تابع اتصال جدید را شروع میکند
|
| 98 |
-
return;
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
this.activeStreamController = this._createStreamController(messageIndex, button);
|
| 102 |
-
ttsState.currentMessageIndex = messageIndex;
|
| 103 |
-
ttsState.activeButton = button;
|
| 104 |
-
|
| 105 |
-
this.socket.send(text);
|
| 106 |
-
setSpeakButtonLoading(button, true);
|
| 107 |
-
},
|
| 108 |
-
|
| 109 |
-
async playFromCache(messageIndex, button) {
|
| 110 |
-
const audioBlob = ttsCache.get(messageIndex);
|
| 111 |
-
if (!audioBlob) return;
|
| 112 |
-
|
| 113 |
-
this.stopCurrentStream();
|
| 114 |
-
this.activeStreamController = this._createStreamController(messageIndex, button);
|
| 115 |
-
ttsState.currentMessageIndex = messageIndex;
|
| 116 |
-
ttsState.activeButton = button;
|
| 117 |
-
|
| 118 |
-
try {
|
| 119 |
-
const controller = this.activeStreamController;
|
| 120 |
-
const arrayBuffer = await audioBlob.arrayBuffer();
|
| 121 |
-
if (controller.audioContext.state === 'closed') return;
|
| 122 |
-
const decodedBuffer = await controller.audioContext.decodeAudioData(arrayBuffer);
|
| 123 |
-
const source = controller.audioContext.createBufferSource();
|
| 124 |
-
source.buffer = decodedBuffer;
|
| 125 |
-
source.connect(controller.audioContext.destination);
|
| 126 |
-
|
| 127 |
-
showGlobalPlayer(false, true);
|
| 128 |
-
button.classList.add('playing');
|
| 129 |
-
|
| 130 |
-
controller.startTime = Date.now();
|
| 131 |
-
controller.timerInterval = setInterval(() => updateGlobalPlayerUI(controller), 250);
|
| 132 |
-
ttsState.animationFrameId = requestAnimationFrame(drawWaveform);
|
| 133 |
-
|
| 134 |
-
source.start(0);
|
| 135 |
-
source.onended = () => {
|
| 136 |
-
if (controller.audioContext && controller.audioContext.state !== 'closed') {
|
| 137 |
-
controller.stop();
|
| 138 |
-
}
|
| 139 |
-
};
|
| 140 |
-
controller.sourceNodes.push(source);
|
| 141 |
-
} catch (error) {
|
| 142 |
-
console.error("خطا در پخش از کش:", error);
|
| 143 |
-
this.stopCurrentStream();
|
| 144 |
-
}
|
| 145 |
-
}
|
| 146 |
-
};
|
| 147 |
-
|
| 148 |
-
export function clearCacheForMessage(messageIndex) { ttsCache.delete(messageIndex); }
|
| 149 |
-
export function clearAllCache() { ttsCache.clear(); }
|
| 150 |
-
export function hasCacheForMessage(messageIndex) { return ttsCache.has(messageIndex); }
|
| 151 |
-
export function getAudioState() { const controller = ttsStreamManager.activeStreamController; if (!controller || !controller.audioContext) return { status: 'idle', messageIndex: null }; return { status: controller.audioContext.state, messageIndex: controller.messageIndex }; }
|
| 152 |
-
export function togglePauseResumeAudio() { const controller = ttsStreamManager.activeStreamController; if (!controller) return; if (controller.audioContext.state === 'running') controller.pause(); else if (controller.audioContext.state === 'suspended') controller.resume(); }
|
| 153 |
-
export function stopAudio() { ttsStreamManager.stopCurrentStream(); }
|
| 154 |
-
export function stream(messageIndex, text, button) { ttsStreamManager.stream(messageIndex, text, button); }
|
| 155 |
-
export function playFromCache(messageIndex, button) { ttsStreamManager.playFromCache(messageIndex, button); }
|
| 156 |
-
export function initTtsPlayer() { dom.globalPlayerPlayPause.addEventListener('click', togglePauseResumeAudio); dom.globalPlayerClose.addEventListener('click', stopAudio); }
|
| 157 |
-
function formatTime(s) { if (isNaN(s) || s < 0) return '0:00'; const minutes = Math.floor(s / 60); const seconds = Math.floor(s % 60).toString().padStart(2, '0'); return `${minutes}:${seconds}`; }
|
| 158 |
-
function drawWaveform() { if (!dom.waveformCanvas || !ttsStreamManager.activeStreamController) { if (ttsState.animationFrameId) { cancelAnimationFrame(ttsState.animationFrameId); ttsState.animationFrameId = null; } return; } const canvas = dom.waveformCanvas; const ctx = canvas.getContext('2d'); const dpr = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect(); if (canvas.width !== rect.width * dpr || canvas.height !== rect.height * dpr) { canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; ctx.scale(dpr, dpr); } const width = canvas.width / dpr, height = canvas.height / dpr; ctx.clearRect(0, 0, width, height); const barWidth = 2, barGap = 1.5, totalBarWidth = barWidth + barGap; const numBars = Math.floor(width / totalBarWidth); const offset = (width - numBars * totalBarWidth) / 2; const inactiveColor = getComputedStyle(document.documentElement).classList.contains('dark') ? '#4b5563' : '#d1d5db'; const controller = ttsStreamManager.activeStreamController; for (let i = 0; i < numBars; i++) { let barHeight = height * 0.1; if(controller && controller.audioContext && controller.audioContext.state === 'running') { barHeight = (Math.sin((i + Date.now() / 200) * 0.2) + 1) / 2 * height * 0.7 + height * 0.1; } const x = offset + i * totalBarWidth, y = (height - barHeight) / 2; ctx.fillStyle = inactiveColor; ctx.fillRect(x, y, barWidth, barHeight); } ttsState.animationFrameId = requestAnimationFrame(drawWaveform); }
|
| 159 |
-
function updateGlobalPlayerUI(controller) { if (!controller || !controller.audioContext || controller.audioContext.state !== 'running') return; const currentTime = controller.elapsedTime + (Date.now() - controller.startTime) / 1000; dom.globalPlayerCurrentTime.textContent = formatTime(currentTime); }
|
| 160 |
-
function showGlobalPlayer(isLoading = false, isPlaying = false) { dom.globalAudioPlayer.classList.add('visible'); dom.globalPlayerText.classList.add('hidden'); dom.globalPlayerTotalTime.classList.add('hidden'); dom.globalPlayerPlayPause.style.display = isLoading ? 'none' : 'flex'; dom.globalPlayerLoading.classList.toggle('hidden', !isLoading); dom.globalPlayerPlayIcon.classList.toggle('hidden', isPlaying); dom.globalPlayerPauseIcon.classList.toggle('hidden', !isPlaying); }
|
| 161 |
-
export function hideGlobalPlayer() { dom.globalAudioPlayer.classList.remove('visible'); ttsState.currentMessageIndex = null; if (ttsState.animationFrameId) { cancelAnimationFrame(ttsState.animationFrameId); ttsState.animationFrameId = null; } }
|
| 162 |
-
export function setSpeakButtonLoading(button, isLoading) { if (button) { button.classList.toggle('loading', isLoading); if(isLoading) button.classList.remove('playing'); } }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|