Elias207 commited on
Commit
9651d97
·
verified ·
1 Parent(s): ae35232

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +344 -140
index.html CHANGED
@@ -29,46 +29,241 @@
29
  flex-direction: column;
30
  }
31
 
32
- .header { text-align: center; padding: 20px 0 30px; }
33
- .logo { font-size: 3rem; margin-bottom: 10px; filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3)); }
34
- .title { font-size: 1.5rem; font-weight: 600; margin-bottom: 5px; }
35
- .subtitle { font-size: 0.9rem; opacity: 0.8; }
36
-
37
- .tabs { display: flex; background: rgba(255,255,255,0.15); border-radius: 15px; padding: 5px; margin-bottom: 20px; backdrop-filter: blur(10px); }
38
- .tab { flex: 1; padding: 12px; text-align: center; border-radius: 10px; cursor: pointer; transition: all 0.3s ease; font-weight: 500; font-size: 0.9rem; }
39
- .tab.active { background: rgba(255,255,255,0.3); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.2); }
40
-
41
- .card { background: rgba(255,255,255,0.2); border-radius: 20px; padding: 25px; backdrop-filter: blur(15px); border: 1px solid rgba(255,255,255,0.3); box-shadow: 0 8px 32px rgba(0,0,0,0.1); flex: 1; display: none; flex-direction: column; }
42
- .tab-content.active { display: flex; }
43
-
44
- .form-group { margin-bottom: 20px; }
45
- .label { display: block; margin-bottom: 8px; font-size: 0.9rem; opacity: 0.9; font-weight: 500; }
46
- .input { width: 100%; padding: 15px; border: none; border-radius: 12px; background: rgba(255,255,255,0.9); color: #333; font-size: 1rem; min-height: 100px; resize: vertical; outline: none; transition: all 0.3s ease; }
47
- .input:focus { background: white; transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,0.15); }
48
- .input::placeholder { color: #666; }
49
-
50
- .file-upload { position: relative; background: rgba(255,255,255,0.9); border: 2px dashed #667eea; border-radius: 12px; padding: 30px 20px; text-align: center; cursor: pointer; transition: all 0.3s ease; min-height: 140px; display: flex; flex-direction: column; align-items: center; justify-content: center; }
51
- .file-upload.has-preview { padding: 5px; border-style: solid; }
52
- .file-upload:hover { background: white; transform: translateY(-2px); }
53
- .file-upload input { position: absolute; left: -9999px; }
54
- .file-upload img { max-width: 100%; max-height: 120px; height: auto; border-radius: 8px; object-fit: contain; }
55
- .upload-icon { font-size: 2.5rem; margin-bottom: 10px; color: #667eea; }
56
- .upload-text { color: #333; font-size: 0.9rem; font-weight: 500; }
57
-
58
- .btn { width: 100%; padding: 16px; border: none; border-radius: 12px; font-size: 1.1rem; font-weight: 600; cursor: pointer; margin-top: auto; margin-bottom: 20px; transition: all 0.3s ease; position: relative; overflow: hidden; }
59
- .btn-generate, .btn-read { background: linear-gradient(45deg, #00d2d3, #54a0ff); color: white; box-shadow: 0 6px 20px rgba(84, 160, 255, 0.4); }
60
- .btn:hover:not(:disabled) { transform: translateY(-3px); box-shadow: 0 8px 25px rgba(0,0,0,0.3); }
61
- .btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
62
- .btn:active { transform: translateY(0); }
63
-
64
- .result { flex: 1; background: rgba(255,255,255,0.15); border-radius: 12px; padding: 20px; min-height: 150px; display: flex; align-items: center; justify-content: center; text-align: center; backdrop-filter: blur(5px); margin-bottom: 20px; }
65
- .result-empty { opacity: 0.7; font-style: italic; }
66
- .result img { max-width: 100%; height: auto; border-radius: 8px; background: white; padding: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); animation: zoomIn 0.4s ease; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
- .result-text-wrapper { width: 100%; background: rgba(255,255,255,0.2); border-radius: 8px; padding: 10px 15px; display: flex; align-items: center; justify-content: space-between; animation: slideUp 0.4s ease; }
69
- .result-text { flex-grow: 1; margin-left: 10px; word-break: break-all; line-height: 1.5; text-align: right; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
- /* ===== CHANGE HERE: New styles for the copy button animation ===== */
72
  .copy-btn {
73
  background: #fff;
74
  color: #54a0ff;
@@ -78,43 +273,37 @@
78
  font-family: 'Vazirmatn', sans-serif;
79
  font-weight: 600;
80
  cursor: pointer;
81
- transition: background-color 0.3s ease, color 0.3s ease;
82
  flex-shrink: 0;
83
- position: relative;
84
- overflow: hidden;
85
- min-width: 50px; /* Ensure button has width */
86
  }
87
- .copy-btn .copy-btn-text {
88
- transition: opacity 0.2s ease;
89
- }
90
- .copy-btn::after {
91
- content: '✓';
92
- position: absolute;
93
- top: 50%;
94
- left: 50%;
95
- transform: translate(-50%, -50%) scale(0);
96
- opacity: 0;
97
- font-size: 1rem;
98
- font-weight: bold;
99
- color: white;
100
- transition: transform 0.3s ease, opacity 0.3s ease;
101
  }
102
  .copy-btn.copied {
103
- background-color: #28a745; /* Success Green */
104
- }
105
- .copy-btn.copied .copy-btn-text {
106
- opacity: 0;
107
  }
108
- .copy-btn.copied::after {
109
- transform: translate(-50%, -50%) scale(1);
110
- opacity: 1;
 
 
 
 
 
 
111
  }
112
 
113
- .loader { width: 40px; height: 40px; border: 3px solid rgba(255,255,255,0.3); border-top: 3px solid white; border-radius: 50%; animation: spin 1s linear infinite; }
114
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
115
  @keyframes zoomIn { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } }
116
  @keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
117
- .icon { display: inline-block; margin-left: 8px; }
 
 
 
 
118
  </style>
119
  </head>
120
  <body>
@@ -177,30 +366,34 @@
177
  const fileUpload = document.querySelector('.file-upload');
178
  const uploadContent = document.querySelector('.upload-content');
179
 
180
- tabs.forEach(tab => {
181
- tab.addEventListener('click', () => {
182
- tabs.forEach(t => t.classList.remove('active'));
183
- tabContents.forEach(tc => tc.classList.remove('active'));
184
- tab.classList.add('active');
185
- document.getElementById(tab.getAttribute('data-tab') + '-content').classList.add('active');
186
- });
187
- });
188
-
189
- // ===== CHANGE HERE: Replaced old copy function with an event listener for animation =====
190
- decodedText.addEventListener('click', function(event) {
191
- const button = event.target.closest('.copy-btn');
192
- if (!button || button.classList.contains('copied')) return; // Exit if not a copy button or if already in copied state
193
 
194
- const textToCopy = button.dataset.textToCopy;
195
- navigator.clipboard.writeText(textToCopy).then(() => {
 
196
  button.classList.add('copied');
 
 
197
  setTimeout(() => {
 
198
  button.classList.remove('copied');
199
- }, 2000); // Revert back after 2 seconds
 
200
  }).catch(err => {
201
- console.error('Failed to copy: ', err);
202
  alert('کپی کردن با خطا مواجه شد.');
203
  });
 
 
 
 
 
 
 
 
 
204
  });
205
 
206
  fileInput.addEventListener('change', () => {
@@ -208,7 +401,7 @@
208
  if (file) {
209
  if (file.type.startsWith('image/')) {
210
  const reader = new FileReader();
211
- reader.onload = e => {
212
  const previewImage = document.createElement('img');
213
  previewImage.src = e.target.result;
214
  uploadContent.style.display = 'none';
@@ -234,59 +427,8 @@
234
  });
235
 
236
  const generateSessionHash = () => Math.random().toString(36).substring(2, 15);
237
- const listenForData = (sessionHash, onResult, onError) => { /* ... (no changes here) ... */ };
238
-
239
- generateBtn.addEventListener('click', async () => { /* ... (no changes here) ... */ });
240
-
241
- readBtn.addEventListener('click', async () => {
242
- const file = fileInput.files[0];
243
- if (!file) return alert("لطفاً یک تصویر انتخاب کنید.");
244
- if (!file.type.startsWith('image/')) return alert("لطفاً یک فایل تصویری انتخاب کنید.");
245
-
246
- readLoader.style.display = 'block';
247
- decodedText.innerHTML = '';
248
- readBtn.disabled = true;
249
- try {
250
- const formData = new FormData();
251
- formData.append('files', file);
252
- const uploadRes = await fetch(`${SPACE_URL}/gradio_api/upload`, { method: 'POST', body: formData });
253
- if (!uploadRes.ok) throw new Error('خطا در آپلود فایل');
254
- const [serverFilePath] = await uploadRes.json();
255
- const fileData = { path: serverFilePath, url: `${SPACE_URL}/gradio_api/file=${serverFilePath}`, orig_name: file.name };
256
-
257
- const sessionHash = generateSessionHash();
258
- const joinRes = await fetch(`${SPACE_URL}/gradio_api/queue/join`, {
259
- method: 'POST',
260
- headers: { 'Content-Type': 'application/json' },
261
- body: JSON.stringify({ fn_index: 1, data: [fileData], session_hash: sessionHash })
262
- });
263
- if (!joinRes.ok) throw new Error('خطا در پردازش');
264
-
265
- listenForData(sessionHash,
266
- (result) => {
267
- const textResult = result[0];
268
- // ===== CHANGE HERE: New HTML for the button with data attribute =====
269
- decodedText.innerHTML = `
270
- <div class="result-text-wrapper">
271
- <span class="result-text">📝 ${textResult}</span>
272
- <button class="copy-btn" data-text-to-copy="${textResult.replace(/"/g, '&quot;')}">
273
- <span class="copy-btn-text">کپی</span>
274
- </button>
275
- </div>`;
276
- },
277
- (error) => { alert(`خطا: ${error}`); decodedText.innerHTML = '<span class="result-empty">خطا در خواندن</span>'; }
278
- );
279
- } catch (error) {
280
- alert(`خطا: ${error.message}`);
281
- decodedText.innerHTML = '<span class="result-empty">خطا در پردازش</span>';
282
- } finally {
283
- readLoader.style.display = 'none';
284
- readBtn.disabled = false;
285
- }
286
- });
287
 
288
- // Re-pasting unchanged functions for completeness
289
- const listenForDataFull = (sessionHash, onResult, onError) => {
290
  const eventSource = new EventSource(`${SPACE_URL}/gradio_api/queue/data?session_hash=${sessionHash}`);
291
  eventSource.onmessage = (event) => {
292
  const data = JSON.parse(event.data);
@@ -304,7 +446,6 @@
304
  onError("خطا در ارتباط با سرور.");
305
  };
306
  };
307
- listenForData = listenForDataFull;
308
 
309
  generateBtn.addEventListener('click', async () => {
310
  const text = textInput.value.trim();
@@ -333,10 +474,73 @@
333
  }
334
  });
335
 
336
- ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { fileUpload.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); }); });
337
- ['dragenter', 'dragover'].forEach(eventName => { fileUpload.addEventListener(eventName, () => { fileUpload.style.background = 'white'; fileUpload.style.transform = 'translateY(-2px)'; }); });
338
- ['dragleave', 'drop'].forEach(eventName => { fileUpload.addEventListener(eventName, () => { fileUpload.style.background = 'rgba(255,255,255,0.9)'; fileUpload.style.transform = 'translateY(0)'; }); });
339
- fileUpload.addEventListener('drop', e => { if (e.dataTransfer.files.length > 0) { fileInput.files = e.dataTransfer.files; fileInput.dispatchEvent(new Event('change')); } });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  </script>
341
  </body>
342
  </html>
 
29
  flex-direction: column;
30
  }
31
 
32
+ .header {
33
+ text-align: center;
34
+ padding: 20px 0 30px;
35
+ }
36
+
37
+ .logo {
38
+ font-size: 3rem;
39
+ margin-bottom: 10px;
40
+ filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));
41
+ }
42
+
43
+ .title {
44
+ font-size: 1.5rem;
45
+ font-weight: 600;
46
+ margin-bottom: 5px;
47
+ }
48
+
49
+ .subtitle {
50
+ font-size: 0.9rem;
51
+ opacity: 0.8;
52
+ }
53
+
54
+ .tabs {
55
+ display: flex;
56
+ background: rgba(255,255,255,0.15);
57
+ border-radius: 15px;
58
+ padding: 5px;
59
+ margin-bottom: 20px;
60
+ backdrop-filter: blur(10px);
61
+ }
62
+
63
+ .tab {
64
+ flex: 1;
65
+ padding: 12px;
66
+ text-align: center;
67
+ border-radius: 10px;
68
+ cursor: pointer;
69
+ transition: all 0.3s ease;
70
+ font-weight: 500;
71
+ font-size: 0.9rem;
72
+ }
73
+
74
+ .tab.active {
75
+ background: rgba(255,255,255,0.3);
76
+ transform: translateY(-2px);
77
+ box-shadow: 0 4px 12px rgba(0,0,0,0.2);
78
+ }
79
+
80
+ .card {
81
+ background: rgba(255,255,255,0.2);
82
+ border-radius: 20px;
83
+ padding: 25px;
84
+ backdrop-filter: blur(15px);
85
+ border: 1px solid rgba(255,255,255,0.3);
86
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
87
+ flex: 1;
88
+ display: none;
89
+ flex-direction: column;
90
+ }
91
+
92
+ .tab-content.active {
93
+ display: flex;
94
+ }
95
+
96
+ .form-group {
97
+ margin-bottom: 20px;
98
+ }
99
+
100
+ .label {
101
+ display: block;
102
+ margin-bottom: 8px;
103
+ font-size: 0.9rem;
104
+ opacity: 0.9;
105
+ font-weight: 500;
106
+ }
107
+
108
+ .input {
109
+ width: 100%;
110
+ padding: 15px;
111
+ border: none;
112
+ border-radius: 12px;
113
+ background: rgba(255,255,255,0.9);
114
+ color: #333;
115
+ font-size: 1rem;
116
+ min-height: 100px;
117
+ resize: vertical;
118
+ outline: none;
119
+ transition: all 0.3s ease;
120
+ }
121
+
122
+ .input:focus {
123
+ background: white;
124
+ transform: translateY(-2px);
125
+ box-shadow: 0 6px 20px rgba(0,0,0,0.15);
126
+ }
127
+
128
+ .input::placeholder {
129
+ color: #666;
130
+ }
131
+
132
+ .file-upload {
133
+ position: relative;
134
+ background: rgba(255,255,255,0.9);
135
+ border: 2px dashed #667eea;
136
+ border-radius: 12px;
137
+ padding: 30px 20px;
138
+ text-align: center;
139
+ cursor: pointer;
140
+ transition: all 0.3s ease;
141
+ min-height: 140px;
142
+ display: flex;
143
+ flex-direction: column;
144
+ align-items: center;
145
+ justify-content: center;
146
+ }
147
 
148
+ .file-upload.has-preview {
149
+ padding: 5px;
150
+ border-style: solid;
151
+ }
152
+
153
+ .file-upload:hover {
154
+ background: white;
155
+ transform: translateY(-2px);
156
+ }
157
+
158
+ .file-upload input {
159
+ position: absolute;
160
+ left: -9999px;
161
+ }
162
+
163
+ .file-upload img {
164
+ max-width: 100%;
165
+ max-height: 120px;
166
+ height: auto;
167
+ border-radius: 8px;
168
+ object-fit: contain;
169
+ }
170
+
171
+ .upload-icon {
172
+ font-size: 2.5rem;
173
+ margin-bottom: 10px;
174
+ color: #667eea;
175
+ }
176
+
177
+ .upload-text {
178
+ color: #333;
179
+ font-size: 0.9rem;
180
+ font-weight: 500;
181
+ }
182
+
183
+ .btn {
184
+ width: 100%;
185
+ padding: 16px;
186
+ border: none;
187
+ border-radius: 12px;
188
+ font-size: 1.1rem;
189
+ font-weight: 600;
190
+ cursor: pointer;
191
+ margin-top: auto;
192
+ margin-bottom: 20px;
193
+ transition: all 0.3s ease;
194
+ position: relative;
195
+ overflow: hidden;
196
+ }
197
+
198
+ .btn-generate, .btn-read {
199
+ background: linear-gradient(45deg, #00d2d3, #54a0ff);
200
+ color: white;
201
+ box-shadow: 0 6px 20px rgba(84, 160, 255, 0.4);
202
+ }
203
+
204
+ .btn:hover:not(:disabled) {
205
+ transform: translateY(-3px);
206
+ box-shadow: 0 8px 25px rgba(0,0,0,0.3);
207
+ }
208
+
209
+ .btn:disabled {
210
+ opacity: 0.6;
211
+ cursor: not-allowed;
212
+ transform: none;
213
+ }
214
+
215
+ .btn:active {
216
+ transform: translateY(0);
217
+ }
218
+
219
+ .result {
220
+ flex: 1;
221
+ background: rgba(255,255,255,0.15);
222
+ border-radius: 12px;
223
+ padding: 20px;
224
+ min-height: 150px;
225
+ display: flex;
226
+ align-items: center;
227
+ justify-content: center;
228
+ text-align: center;
229
+ backdrop-filter: blur(5px);
230
+ margin-bottom: 20px;
231
+ }
232
+
233
+ .result-empty {
234
+ opacity: 0.7;
235
+ font-style: italic;
236
+ }
237
+
238
+ .result img {
239
+ max-width: 100%;
240
+ height: auto;
241
+ border-radius: 8px;
242
+ background: white;
243
+ padding: 10px;
244
+ box-shadow: 0 4px 15px rgba(0,0,0,0.2);
245
+ animation: zoomIn 0.4s ease;
246
+ }
247
+
248
+ .result-text-wrapper {
249
+ width: 100%;
250
+ background: rgba(255,255,255,0.2);
251
+ border-radius: 8px;
252
+ padding: 10px 15px;
253
+ display: flex;
254
+ align-items: center;
255
+ justify-content: space-between;
256
+ animation: slideUp 0.4s ease;
257
+ }
258
+ .result-text {
259
+ flex-grow: 1;
260
+ margin-left: 10px;
261
+ word-break: break-all;
262
+ line-height: 1.5;
263
+ text-align: right;
264
+ }
265
 
266
+ /* ===== CHANGE HERE: Styles for the copy button and its animation ===== */
267
  .copy-btn {
268
  background: #fff;
269
  color: #54a0ff;
 
273
  font-family: 'Vazirmatn', sans-serif;
274
  font-weight: 600;
275
  cursor: pointer;
276
+ transition: all 0.3s ease; /* Smooth transition for all properties */
277
  flex-shrink: 0;
278
+ font-size: 0.9rem;
 
 
279
  }
280
+ .copy-btn:hover {
281
+ background: #eef;
 
 
 
 
 
 
 
 
 
 
 
 
282
  }
283
  .copy-btn.copied {
284
+ background-color: #28a745; /* Success green */
285
+ color: white;
286
+ transform: scale(1.05); /* Slightly enlarge */
 
287
  }
288
+
289
+
290
+ .loader {
291
+ width: 40px;
292
+ height: 40px;
293
+ border: 3px solid rgba(255,255,255,0.3);
294
+ border-top: 3px solid white;
295
+ border-radius: 50%;
296
+ animation: spin 1s linear infinite;
297
  }
298
 
 
299
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
300
  @keyframes zoomIn { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } }
301
  @keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
302
+
303
+ .icon {
304
+ display: inline-block;
305
+ margin-left: 8px;
306
+ }
307
  </style>
308
  </head>
309
  <body>
 
366
  const fileUpload = document.querySelector('.file-upload');
367
  const uploadContent = document.querySelector('.upload-content');
368
 
369
+ // ===== CHANGE HERE: New copy function with animation =====
370
+ function copyToClipboard(button, text) {
371
+ if (button.classList.contains('copied')) return; // Prevent re-triggering animation
 
 
 
 
 
 
 
 
 
 
372
 
373
+ navigator.clipboard.writeText(text).then(() => {
374
+ const originalText = button.innerHTML;
375
+ button.innerHTML = '✓';
376
  button.classList.add('copied');
377
+ button.disabled = true;
378
+
379
  setTimeout(() => {
380
+ button.innerHTML = originalText;
381
  button.classList.remove('copied');
382
+ button.disabled = false;
383
+ }, 2000); // 2 seconds
384
  }).catch(err => {
385
+ console.error('Failed to copy text: ', err);
386
  alert('کپی کردن با خطا مواجه شد.');
387
  });
388
+ }
389
+
390
+ tabs.forEach(tab => {
391
+ tab.addEventListener('click', () => {
392
+ tabs.forEach(t => t.classList.remove('active'));
393
+ tabContents.forEach(tc => tc.classList.remove('active'));
394
+ tab.classList.add('active');
395
+ document.getElementById(tab.getAttribute('data-tab') + '-content').classList.add('active');
396
+ });
397
  });
398
 
399
  fileInput.addEventListener('change', () => {
 
401
  if (file) {
402
  if (file.type.startsWith('image/')) {
403
  const reader = new FileReader();
404
+ reader.onload = function(e) {
405
  const previewImage = document.createElement('img');
406
  previewImage.src = e.target.result;
407
  uploadContent.style.display = 'none';
 
427
  });
428
 
429
  const generateSessionHash = () => Math.random().toString(36).substring(2, 15);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
 
431
+ const listenForData = (sessionHash, onResult, onError) => {
 
432
  const eventSource = new EventSource(`${SPACE_URL}/gradio_api/queue/data?session_hash=${sessionHash}`);
433
  eventSource.onmessage = (event) => {
434
  const data = JSON.parse(event.data);
 
446
  onError("خطا در ارتباط با سرور.");
447
  };
448
  };
 
449
 
450
  generateBtn.addEventListener('click', async () => {
451
  const text = textInput.value.trim();
 
474
  }
475
  });
476
 
477
+ readBtn.addEventListener('click', async () => {
478
+ const file = fileInput.files[0];
479
+ if (!file) return alert("لطفاً یک تصویر انتخاب کنید.");
480
+ if (!file.type.startsWith('image/')) return alert("لطفاً یک فایل تصویری انتخاب کنید.");
481
+
482
+ readLoader.style.display = 'block';
483
+ decodedText.innerHTML = '';
484
+ readBtn.disabled = true;
485
+ try {
486
+ const formData = new FormData();
487
+ formData.append('files', file);
488
+ const uploadRes = await fetch(`${SPACE_URL}/gradio_api/upload`, { method: 'POST', body: formData });
489
+ if (!uploadRes.ok) throw new Error('خطا در آپلود فایل');
490
+ const [serverFilePath] = await uploadRes.json();
491
+ const fileData = { path: serverFilePath, url: `${SPACE_URL}/gradio_api/file=${serverFilePath}`, orig_name: file.name };
492
+
493
+ const sessionHash = generateSessionHash();
494
+ const joinRes = await fetch(`${SPACE_URL}/gradio_api/queue/join`, {
495
+ method: 'POST',
496
+ headers: { 'Content-Type': 'application/json' },
497
+ body: JSON.stringify({ fn_index: 1, data: [fileData], session_hash: sessionHash })
498
+ });
499
+ if (!joinRes.ok) throw new Error('خطا در پردازش');
500
+
501
+ listenForData(sessionHash,
502
+ (result) => {
503
+ const textToCopy = result[0];
504
+ // ===== CHANGE HERE: onclick now passes `this` to the function =====
505
+ const escapedText = textToCopy.replace(/'/g, "\\'").replace(/"/g, '&quot;');
506
+ decodedText.innerHTML = `
507
+ <div class="result-text-wrapper">
508
+ <span class="result-text">📝 ${textToCopy}</span>
509
+ <button class="copy-btn" onclick="copyToClipboard(this, '${escapedText}')">کپی</button>
510
+ </div>`;
511
+ },
512
+ (error) => { alert(`خطا: ${error}`); decodedText.innerHTML = '<span class="result-empty">خطا در خواندن</span>'; }
513
+ );
514
+ } catch (error) {
515
+ alert(`خطا: ${error.message}`);
516
+ decodedText.innerHTML = '<span class="result-empty">خطا در پردازش</span>';
517
+ } finally {
518
+ readLoader.style.display = 'none';
519
+ readBtn.disabled = false;
520
+ }
521
+ });
522
+
523
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
524
+ fileUpload.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); });
525
+ });
526
+ ['dragenter', 'dragover'].forEach(eventName => {
527
+ fileUpload.addEventListener(eventName, () => {
528
+ fileUpload.style.background = 'white';
529
+ fileUpload.style.transform = 'translateY(-2px)';
530
+ });
531
+ });
532
+ ['dragleave', 'drop'].forEach(eventName => {
533
+ fileUpload.addEventListener(eventName, () => {
534
+ fileUpload.style.background = 'rgba(255,255,255,0.9)';
535
+ fileUpload.style.transform = 'translateY(0)';
536
+ });
537
+ });
538
+ fileUpload.addEventListener('drop', e => {
539
+ if (e.dataTransfer.files.length > 0) {
540
+ fileInput.files = e.dataTransfer.files;
541
+ fileInput.dispatchEvent(new Event('change'));
542
+ }
543
+ });
544
  </script>
545
  </body>
546
  </html>