Elias207 commited on
Commit
ae35232
·
verified ·
1 Parent(s): 8b15e94

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +149 -351
index.html CHANGED
@@ -29,240 +29,46 @@
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
- /* ===== CHANGE HERE: Styles for the copy button and its container ===== */
249
- .result-text-wrapper {
250
- width: 100%;
251
- background: rgba(255,255,255,0.2);
252
- border-radius: 8px;
253
- padding: 10px 15px;
254
- display: flex;
255
- align-items: center;
256
- justify-content: space-between;
257
- animation: slideUp 0.4s ease;
258
- }
259
- .result-text {
260
- flex-grow: 1;
261
- margin-left: 10px;
262
- word-break: break-all;
263
- line-height: 1.5;
264
- text-align: right;
265
- }
266
  .copy-btn {
267
  background: #fff;
268
  color: #54a0ff;
@@ -272,31 +78,43 @@
272
  font-family: 'Vazirmatn', sans-serif;
273
  font-weight: 600;
274
  cursor: pointer;
275
- transition: background 0.2s ease;
276
  flex-shrink: 0;
 
 
 
277
  }
278
- .copy-btn:hover {
279
- background: #eef;
280
  }
281
-
282
-
283
- .loader {
284
- width: 40px;
285
- height: 40px;
286
- border: 3px solid rgba(255,255,255,0.3);
287
- border-top: 3px solid white;
288
- border-radius: 50%;
289
- animation: spin 1s linear infinite;
 
 
 
 
 
 
 
 
 
 
 
 
290
  }
291
 
 
292
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
293
  @keyframes zoomIn { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } }
294
  @keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
295
-
296
- .icon {
297
- display: inline-block;
298
- margin-left: 8px;
299
- }
300
  </style>
301
  </head>
302
  <body>
@@ -308,32 +126,20 @@
308
  </div>
309
 
310
  <div class="tabs">
311
- <div class="tab active" data-tab="generate">
312
- <span class="icon">➕</span>
313
- ساخت
314
- </div>
315
- <div class="tab" data-tab="read">
316
- <span class="icon">👁</span>
317
- خواندن
318
- </div>
319
  </div>
320
 
321
  <div class="card tab-content active" id="generate-content">
322
  <div class="form-group">
323
  <label class="label">متن یا لینک خود را بنویسید:</label>
324
- <!-- ===== CHANGE HERE: Default text "سلام دنیا" is removed ===== -->
325
  <textarea id="text-input" class="input" placeholder="مثال: https://google.com یا سلام دنیا"></textarea>
326
  </div>
327
  <div class="result" id="qr-result">
328
  <div id="generate-loader" class="loader" style="display: none;"></div>
329
- <div id="qr-display">
330
- <span class="result-empty">QR کد شما اینجا ظاهر می‌شود</span>
331
- </div>
332
  </div>
333
- <button id="generate-btn" class="btn btn-generate">
334
- <span class="icon">✨</span>
335
- ساخت QR کد
336
- </button>
337
  </div>
338
 
339
  <div class="card tab-content" id="read-content">
@@ -349,14 +155,9 @@
349
  </div>
350
  <div class="result" id="read-result">
351
  <div id="read-loader" class="loader" style="display: none;"></div>
352
- <div id="decoded-text">
353
- <span class="result-empty">نتیجه اینجا نمایش داده می‌شود</span>
354
- </div>
355
  </div>
356
- <button id="read-btn" class="btn btn-read">
357
- <span class="icon">🔍</span>
358
- خواندن QR کد
359
- </button>
360
  </div>
361
  </div>
362
 
@@ -376,16 +177,6 @@
376
  const fileUpload = document.querySelector('.file-upload');
377
  const uploadContent = document.querySelector('.upload-content');
378
 
379
- // ===== CHANGE HERE: New function to copy text to clipboard =====
380
- function copyToClipboard(text) {
381
- navigator.clipboard.writeText(text).then(() => {
382
- alert('متن با موفقیت کپی شد!');
383
- }).catch(err => {
384
- console.error('Failed to copy text: ', err);
385
- alert('کپی کردن با خطا مواجه شد.');
386
- });
387
- }
388
-
389
  tabs.forEach(tab => {
390
  tab.addEventListener('click', () => {
391
  tabs.forEach(t => t.classList.remove('active'));
@@ -395,12 +186,29 @@
395
  });
396
  });
397
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
  fileInput.addEventListener('change', () => {
399
  const file = fileInput.files[0];
400
  if (file) {
401
  if (file.type.startsWith('image/')) {
402
  const reader = new FileReader();
403
- reader.onload = function(e) {
404
  const previewImage = document.createElement('img');
405
  previewImage.src = e.target.result;
406
  uploadContent.style.display = 'none';
@@ -426,8 +234,59 @@
426
  });
427
 
428
  const generateSessionHash = () => Math.random().toString(36).substring(2, 15);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
429
 
430
- const listenForData = (sessionHash, onResult, onError) => {
 
431
  const eventSource = new EventSource(`${SPACE_URL}/gradio_api/queue/data?session_hash=${sessionHash}`);
432
  eventSource.onmessage = (event) => {
433
  const data = JSON.parse(event.data);
@@ -445,6 +304,7 @@
445
  onError("خطا در ارتباط با سرور.");
446
  };
447
  };
 
448
 
449
  generateBtn.addEventListener('click', async () => {
450
  const text = textInput.value.trim();
@@ -473,72 +333,10 @@
473
  }
474
  });
475
 
476
- readBtn.addEventListener('click', async () => {
477
- const file = fileInput.files[0];
478
- if (!file) return alert("لطفاً یک تصویر انتخاب کنید.");
479
- if (!file.type.startsWith('image/')) return alert("لطفاً یک فایل تصویری انتخاب کنید.");
480
-
481
- readLoader.style.display = 'block';
482
- decodedText.innerHTML = '';
483
- readBtn.disabled = true;
484
- try {
485
- const formData = new FormData();
486
- formData.append('files', file);
487
- const uploadRes = await fetch(`${SPACE_URL}/gradio_api/upload`, { method: 'POST', body: formData });
488
- if (!uploadRes.ok) throw new Error('خطا در آپلود فایل');
489
- const [serverFilePath] = await uploadRes.json();
490
- const fileData = { path: serverFilePath, url: `${SPACE_URL}/gradio_api/file=${serverFilePath}`, orig_name: file.name };
491
-
492
- const sessionHash = generateSessionHash();
493
- const joinRes = await fetch(`${SPACE_URL}/gradio_api/queue/join`, {
494
- method: 'POST',
495
- headers: { 'Content-Type': 'application/json' },
496
- body: JSON.stringify({ fn_index: 1, data: [fileData], session_hash: sessionHash })
497
- });
498
- if (!joinRes.ok) throw new Error('خطا در پردازش');
499
-
500
- listenForData(sessionHash,
501
- (result) => {
502
- const textToCopy = result[0];
503
- // ===== CHANGE HERE: Result now includes a copy button =====
504
- decodedText.innerHTML = `
505
- <div class="result-text-wrapper">
506
- <span class="result-text">📝 ${textToCopy}</span>
507
- <button class="copy-btn" onclick="copyToClipboard('${textToCopy.replace(/'/g, "\\'")}')">کپی</button>
508
- </div>`;
509
- },
510
- (error) => { alert(`خطا: ${error}`); decodedText.innerHTML = '<span class="result-empty">خطا در خواندن</span>'; }
511
- );
512
- } catch (error) {
513
- alert(`خطا: ${error.message}`);
514
- decodedText.innerHTML = '<span class="result-empty">خطا در پردازش</span>';
515
- } finally {
516
- readLoader.style.display = 'none';
517
- readBtn.disabled = false;
518
- }
519
- });
520
-
521
- ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
522
- fileUpload.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); });
523
- });
524
- ['dragenter', 'dragover'].forEach(eventName => {
525
- fileUpload.addEventListener(eventName, () => {
526
- fileUpload.style.background = 'white';
527
- fileUpload.style.transform = 'translateY(-2px)';
528
- });
529
- });
530
- ['dragleave', 'drop'].forEach(eventName => {
531
- fileUpload.addEventListener(eventName, () => {
532
- fileUpload.style.background = 'rgba(255,255,255,0.9)';
533
- fileUpload.style.transform = 'translateY(0)';
534
- });
535
- });
536
- fileUpload.addEventListener('drop', e => {
537
- if (e.dataTransfer.files.length > 0) {
538
- fileInput.files = e.dataTransfer.files;
539
- fileInput.dispatchEvent(new Event('change'));
540
- }
541
- });
542
  </script>
543
  </body>
544
  </html>
 
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
  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>
 
126
  </div>
127
 
128
  <div class="tabs">
129
+ <div class="tab active" data-tab="generate"><span class="icon">➕</span>ساخت</div>
130
+ <div class="tab" data-tab="read"><span class="icon">👁</span>خواندن</div>
 
 
 
 
 
 
131
  </div>
132
 
133
  <div class="card tab-content active" id="generate-content">
134
  <div class="form-group">
135
  <label class="label">متن یا لینک خود را بنویسید:</label>
 
136
  <textarea id="text-input" class="input" placeholder="مثال: https://google.com یا سلام دنیا"></textarea>
137
  </div>
138
  <div class="result" id="qr-result">
139
  <div id="generate-loader" class="loader" style="display: none;"></div>
140
+ <div id="qr-display"><span class="result-empty">QR کد شما اینجا ظاهر می‌شود</span></div>
 
 
141
  </div>
142
+ <button id="generate-btn" class="btn btn-generate"><span class="icon">✨</span>ساخت QR کد</button>
 
 
 
143
  </div>
144
 
145
  <div class="card tab-content" id="read-content">
 
155
  </div>
156
  <div class="result" id="read-result">
157
  <div id="read-loader" class="loader" style="display: none;"></div>
158
+ <div id="decoded-text"><span class="result-empty">نتیجه اینجا نمایش داده می‌شود</span></div>
 
 
159
  </div>
160
+ <button id="read-btn" class="btn btn-read"><span class="icon">🔍</span>خواندن QR کد</button>
 
 
 
161
  </div>
162
  </div>
163
 
 
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'));
 
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', () => {
207
  const file = fileInput.files[0];
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
  });
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
  onError("خطا در ارتباط با سرور.");
305
  };
306
  };
307
+ listenForData = listenForDataFull;
308
 
309
  generateBtn.addEventListener('click', async () => {
310
  const text = textInput.value.trim();
 
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>