mistpe commited on
Commit
f50acb2
·
verified ·
1 Parent(s): 53f22b0

Create templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +1010 -0
templates/index.html ADDED
@@ -0,0 +1,1010 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>GitHub 文件合并器</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Consolas', 'Monaco', monospace;
16
+ background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #16213e 100%);
17
+ color: #00ff88;
18
+ min-height: 100vh;
19
+ overflow-x: hidden;
20
+ }
21
+
22
+ .container {
23
+ max-width: 1400px;
24
+ margin: 0 auto;
25
+ padding: 20px;
26
+ }
27
+
28
+ .header {
29
+ text-align: center;
30
+ margin-bottom: 40px;
31
+ position: relative;
32
+ }
33
+
34
+ .title {
35
+ font-size: 3rem;
36
+ color: #00ff88;
37
+ text-shadow: 0 0 20px #00ff88, 0 0 40px #00ff88;
38
+ margin-bottom: 10px;
39
+ animation: glow 2s ease-in-out infinite alternate;
40
+ }
41
+
42
+ @keyframes glow {
43
+ from { text-shadow: 0 0 20px #00ff88, 0 0 30px #00ff88, 0 0 40px #00ff88; }
44
+ to { text-shadow: 0 0 30px #00ff88, 0 0 40px #00ff88, 0 0 50px #00ff88; }
45
+ }
46
+
47
+ .subtitle {
48
+ color: #66ccff;
49
+ font-size: 1.2rem;
50
+ opacity: 0.8;
51
+ }
52
+
53
+ .cyber-panel {
54
+ background: rgba(0, 20, 40, 0.8);
55
+ border: 2px solid #00ff88;
56
+ border-radius: 10px;
57
+ padding: 30px;
58
+ margin-bottom: 30px;
59
+ box-shadow:
60
+ 0 0 30px rgba(0, 255, 136, 0.3),
61
+ inset 0 0 30px rgba(0, 255, 136, 0.1);
62
+ backdrop-filter: blur(10px);
63
+ position: relative;
64
+ overflow: hidden;
65
+ }
66
+
67
+ .cyber-panel::before {
68
+ content: '';
69
+ position: absolute;
70
+ top: -2px;
71
+ left: -2px;
72
+ right: -2px;
73
+ bottom: -2px;
74
+ background: linear-gradient(45deg, #00ff88, #66ccff, #00ff88);
75
+ border-radius: 10px;
76
+ z-index: -1;
77
+ animation: borderGlow 3s linear infinite;
78
+ }
79
+
80
+ @keyframes borderGlow {
81
+ 0% { transform: rotate(0deg); }
82
+ 100% { transform: rotate(360deg); }
83
+ }
84
+
85
+ .input-group {
86
+ margin-bottom: 25px;
87
+ }
88
+
89
+ .input-label {
90
+ display: block;
91
+ margin-bottom: 10px;
92
+ color: #66ccff;
93
+ font-weight: bold;
94
+ font-size: 1.1rem;
95
+ }
96
+
97
+ .cyber-input {
98
+ width: 100%;
99
+ padding: 15px;
100
+ background: rgba(0, 0, 0, 0.6);
101
+ border: 2px solid #00ff88;
102
+ border-radius: 8px;
103
+ color: #00ff88;
104
+ font-family: 'Consolas', monospace;
105
+ font-size: 1rem;
106
+ transition: all 0.3s ease;
107
+ }
108
+
109
+ .cyber-input:focus {
110
+ outline: none;
111
+ border-color: #66ccff;
112
+ box-shadow: 0 0 20px rgba(102, 204, 255, 0.5);
113
+ background: rgba(0, 0, 0, 0.8);
114
+ }
115
+
116
+ .cyber-input::placeholder {
117
+ color: rgba(0, 255, 136, 0.5);
118
+ }
119
+
120
+ .cyber-button {
121
+ background: linear-gradient(45deg, #00ff88, #66ccff);
122
+ border: none;
123
+ padding: 15px 30px;
124
+ border-radius: 8px;
125
+ color: #000;
126
+ font-weight: bold;
127
+ font-size: 1.1rem;
128
+ cursor: pointer;
129
+ transition: all 0.3s ease;
130
+ text-transform: uppercase;
131
+ letter-spacing: 1px;
132
+ position: relative;
133
+ overflow: hidden;
134
+ }
135
+
136
+ .cyber-button:hover {
137
+ transform: translateY(-2px);
138
+ box-shadow: 0 10px 30px rgba(0, 255, 136, 0.4);
139
+ }
140
+
141
+ .source-tabs {
142
+ display: flex;
143
+ margin-bottom: 20px;
144
+ border-radius: 8px;
145
+ overflow: hidden;
146
+ border: 2px solid #00ff88;
147
+ }
148
+
149
+ .tab-button {
150
+ flex: 1;
151
+ padding: 15px 20px;
152
+ background: rgba(0, 0, 0, 0.6);
153
+ border: none;
154
+ color: #00ff88;
155
+ font-family: 'Consolas', monospace;
156
+ font-size: 1rem;
157
+ cursor: pointer;
158
+ transition: all 0.3s ease;
159
+ position: relative;
160
+ }
161
+
162
+ .tab-button:hover {
163
+ background: rgba(0, 255, 136, 0.1);
164
+ }
165
+
166
+ .tab-button.active {
167
+ background: linear-gradient(45deg, #00ff88, #66ccff);
168
+ color: #000;
169
+ font-weight: bold;
170
+ }
171
+
172
+ .tab-content {
173
+ animation: fadeIn 0.3s ease-in-out;
174
+ }
175
+
176
+ @keyframes fadeIn {
177
+ from { opacity: 0; transform: translateY(10px); }
178
+ to { opacity: 1; transform: translateY(0); }
179
+ }
180
+
181
+ .file-upload-area {
182
+ border: 2px dashed #00ff88;
183
+ border-radius: 8px;
184
+ padding: 40px 20px;
185
+ text-align: center;
186
+ background: rgba(0, 0, 0, 0.3);
187
+ cursor: pointer;
188
+ transition: all 0.3s ease;
189
+ position: relative;
190
+ overflow: hidden;
191
+ }
192
+
193
+ .file-upload-area:hover {
194
+ border-color: #66ccff;
195
+ background: rgba(0, 255, 136, 0.05);
196
+ transform: translateY(-2px);
197
+ }
198
+
199
+ .file-upload-area.dragover {
200
+ border-color: #66ccff;
201
+ background: rgba(102, 204, 255, 0.1);
202
+ transform: scale(1.02);
203
+ }
204
+
205
+ .file-input {
206
+ position: absolute;
207
+ top: 0;
208
+ left: 0;
209
+ width: 100%;
210
+ height: 100%;
211
+ opacity: 0;
212
+ cursor: pointer;
213
+ }
214
+
215
+ .upload-icon {
216
+ font-size: 3rem;
217
+ margin-bottom: 15px;
218
+ animation: float 3s ease-in-out infinite;
219
+ }
220
+
221
+ @keyframes float {
222
+ 0%, 100% { transform: translateY(0px); }
223
+ 50% { transform: translateY(-10px); }
224
+ }
225
+
226
+ .upload-text p {
227
+ margin: 8px 0;
228
+ color: #00ff88;
229
+ }
230
+
231
+ .upload-hint {
232
+ color: #66ccff !important;
233
+ font-size: 0.9rem;
234
+ opacity: 0.8;
235
+ }
236
+
237
+ .upload-limit {
238
+ color: rgba(0, 255, 136, 0.6) !important;
239
+ font-size: 0.8rem;
240
+ }
241
+
242
+ .file-selected {
243
+ background: rgba(0, 255, 136, 0.1) !important;
244
+ border-color: #66ccff !important;
245
+ }
246
+
247
+ .file-info {
248
+ display: none;
249
+ background: rgba(0, 20, 40, 0.8);
250
+ border: 1px solid #66ccff;
251
+ border-radius: 8px;
252
+ padding: 15px;
253
+ margin: 15px 0;
254
+ }
255
+
256
+ .file-info.show {
257
+ display: block;
258
+ }
259
+
260
+ .file-name {
261
+ color: #00ff88;
262
+ font-weight: bold;
263
+ margin-bottom: 5px;
264
+ }
265
+
266
+ .file-size {
267
+ color: #66ccff;
268
+ font-size: 0.9rem;
269
+ }
270
+ opacity: 0.5;
271
+ cursor: not-allowed;
272
+ transform: none;
273
+ }
274
+
275
+ .progress-container {
276
+ display: none;
277
+ margin: 20px 0;
278
+ }
279
+
280
+ .progress-bar {
281
+ width: 100%;
282
+ height: 8px;
283
+ background: rgba(0, 0, 0, 0.6);
284
+ border-radius: 4px;
285
+ overflow: hidden;
286
+ border: 1px solid #00ff88;
287
+ }
288
+
289
+ .progress-fill {
290
+ height: 100%;
291
+ background: linear-gradient(90deg, #00ff88, #66ccff);
292
+ width: 0%;
293
+ transition: width 0.3s ease;
294
+ box-shadow: 0 0 10px #00ff88;
295
+ }
296
+
297
+ .progress-text {
298
+ text-align: center;
299
+ margin-top: 10px;
300
+ color: #66ccff;
301
+ font-weight: bold;
302
+ }
303
+
304
+ .file-tree-container {
305
+ display: none;
306
+ max-height: 600px;
307
+ overflow-y: auto;
308
+ background: rgba(0, 0, 0, 0.4);
309
+ border: 1px solid #00ff88;
310
+ border-radius: 8px;
311
+ padding: 20px;
312
+ margin: 20px 0;
313
+ }
314
+
315
+ .file-tree-container::-webkit-scrollbar {
316
+ width: 8px;
317
+ }
318
+
319
+ .file-tree-container::-webkit-scrollbar-track {
320
+ background: rgba(0, 0, 0, 0.4);
321
+ border-radius: 4px;
322
+ }
323
+
324
+ .file-tree-container::-webkit-scrollbar-thumb {
325
+ background: #00ff88;
326
+ border-radius: 4px;
327
+ }
328
+
329
+ .file-item {
330
+ padding: 8px 12px;
331
+ margin: 2px 0;
332
+ border-radius: 4px;
333
+ cursor: pointer;
334
+ transition: all 0.2s ease;
335
+ display: flex;
336
+ align-items: center;
337
+ user-select: none;
338
+ }
339
+
340
+ .file-item:hover {
341
+ background: rgba(0, 255, 136, 0.1);
342
+ transform: translateX(5px);
343
+ }
344
+
345
+ .file-item.selected {
346
+ background: rgba(0, 255, 136, 0.2);
347
+ border-left: 3px solid #00ff88;
348
+ }
349
+
350
+ .file-item.folder {
351
+ color: #66ccff;
352
+ font-weight: bold;
353
+ }
354
+
355
+ .file-item.file {
356
+ color: #00ff88;
357
+ }
358
+
359
+ .file-icon {
360
+ margin-right: 10px;
361
+ font-size: 1.2rem;
362
+ }
363
+
364
+ .controls {
365
+ display: none;
366
+ text-align: center;
367
+ margin: 30px 0;
368
+ }
369
+
370
+ .controls .cyber-button {
371
+ margin: 0 10px;
372
+ }
373
+
374
+ .stats {
375
+ background: rgba(0, 20, 40, 0.6);
376
+ border: 1px solid #66ccff;
377
+ border-radius: 8px;
378
+ padding: 15px;
379
+ margin: 20px 0;
380
+ text-align: center;
381
+ }
382
+
383
+ .stats-item {
384
+ display: inline-block;
385
+ margin: 0 20px;
386
+ color: #66ccff;
387
+ }
388
+
389
+ .stats-value {
390
+ font-size: 1.5rem;
391
+ font-weight: bold;
392
+ color: #00ff88;
393
+ }
394
+
395
+ .error-message {
396
+ background: rgba(255, 0, 0, 0.1);
397
+ border: 1px solid #ff3366;
398
+ border-radius: 8px;
399
+ padding: 15px;
400
+ margin: 20px 0;
401
+ color: #ff3366;
402
+ text-align: center;
403
+ display: none;
404
+ }
405
+
406
+ .success-message {
407
+ background: rgba(0, 255, 136, 0.1);
408
+ border: 1px solid #00ff88;
409
+ border-radius: 8px;
410
+ padding: 15px;
411
+ margin: 20px 0;
412
+ color: #00ff88;
413
+ text-align: center;
414
+ display: none;
415
+ }
416
+
417
+ .loading {
418
+ display: inline-block;
419
+ width: 20px;
420
+ height: 20px;
421
+ border: 2px solid rgba(0, 255, 136, 0.3);
422
+ border-radius: 50%;
423
+ border-top-color: #00ff88;
424
+ animation: spin 1s ease-in-out infinite;
425
+ margin-right: 10px;
426
+ }
427
+
428
+ @keyframes spin {
429
+ to { transform: rotate(360deg); }
430
+ }
431
+
432
+ .matrix-bg {
433
+ position: fixed;
434
+ top: 0;
435
+ left: 0;
436
+ width: 100%;
437
+ height: 100%;
438
+ z-index: -1;
439
+ opacity: 0.05;
440
+ pointer-events: none;
441
+ }
442
+
443
+ @media (max-width: 768px) {
444
+ .container {
445
+ padding: 10px;
446
+ }
447
+
448
+ .title {
449
+ font-size: 2rem;
450
+ }
451
+
452
+ .cyber-panel {
453
+ padding: 20px;
454
+ }
455
+ }
456
+ </style>
457
+ </head>
458
+ <body>
459
+ <canvas class="matrix-bg" id="matrixCanvas"></canvas>
460
+
461
+ <div class="container">
462
+ <div class="header">
463
+ <h1 class="title">GitHub 文件合并器</h1>
464
+ <p class="subtitle">下载、解析、合并 - 一键搞定</p>
465
+ </div>
466
+
467
+ <div class="cyber-panel">
468
+ <div class="input-group">
469
+ <label class="input-label">选择数据源</label>
470
+ <div class="source-tabs">
471
+ <button class="tab-button active" id="githubTab">GitHub 仓库</button>
472
+ <button class="tab-button" id="uploadTab">本地上传</button>
473
+ </div>
474
+ </div>
475
+
476
+ <div class="tab-content" id="githubContent">
477
+ <div class="input-group">
478
+ <label class="input-label">GitHub 仓库 URL</label>
479
+ <input type="text" id="githubUrl" class="cyber-input"
480
+ placeholder="https://github.com/username/repository">
481
+ </div>
482
+
483
+ <button id="downloadBtn" class="cyber-button">
484
+ 开始下载
485
+ </button>
486
+ </div>
487
+
488
+ <div class="tab-content" id="uploadContent" style="display: none;">
489
+ <div class="input-group">
490
+ <label class="input-label">选择文件或压缩包</label>
491
+ <div class="file-upload-area" id="fileUploadArea">
492
+ <div class="upload-icon">📁</div>
493
+ <div class="upload-text">
494
+ <p>拖拽文件到此处或点击选择</p>
495
+ <p class="upload-hint">支持 ZIP, RAR, 7Z, TAR 等压缩包,以及常见代码文件</p>
496
+ <p class="upload-limit">最大文件大小: 500MB</p>
497
+ </div>
498
+ <input type="file" id="fileInput" class="file-input"
499
+ accept=".zip,.rar,.7z,.tar,.tar.gz,.tgz,.tar.bz2,.tar.xz,.py,.js,.html,.css,.java,.cpp,.c,.h,.php,.rb,.go,.rs,.ts,.vue,.jsx,.tsx,.md,.txt,.json,.xml,.yaml,.yml,.ini,.cfg,.conf,.sh,.bat,.ps1,.sql">
500
+ </div>
501
+ </div>
502
+
503
+ <button id="uploadBtn" class="cyber-button" disabled>
504
+ 开始上传
505
+ </button>
506
+ </div>
507
+
508
+ <div class="file-info" id="fileInfo">
509
+ <div class="file-name" id="fileName"></div>
510
+ <div class="file-size" id="fileSize"></div>
511
+ </div>
512
+
513
+ <div class="progress-container" id="progressContainer">
514
+ <div class="progress-bar">
515
+ <div class="progress-fill" id="progressFill"></div>
516
+ </div>
517
+ <div class="progress-text" id="progressText">准备下载...</div>
518
+ </div>
519
+
520
+ <div class="error-message" id="errorMessage"></div>
521
+ <div class="success-message" id="successMessage"></div>
522
+ </div>
523
+
524
+ <div class="cyber-panel" id="filePanel" style="display: none;">
525
+ <h3 style="color: #66ccff; margin-bottom: 20px;">📁 文件树结构</h3>
526
+
527
+ <div class="stats" id="statsPanel">
528
+ <div class="stats-item">
529
+ <div class="stats-value" id="totalFiles">0</div>
530
+ <div>总文件数</div>
531
+ </div>
532
+ <div class="stats-item">
533
+ <div class="stats-value" id="selectedFiles">0</div>
534
+ <div>已选择</div>
535
+ </div>
536
+ </div>
537
+
538
+ <div class="controls">
539
+ <button id="selectAllBtn" class="cyber-button">全选</button>
540
+ <button id="clearSelectionBtn" class="cyber-button">清除选择</button>
541
+ <button id="mergeBtn" class="cyber-button">合并文件</button>
542
+ </div>
543
+
544
+ <div class="file-tree-container" id="fileTree"></div>
545
+ </div>
546
+ </div>
547
+
548
+ <script>
549
+ let currentSessionId = null;
550
+ let selectedFiles = new Set();
551
+ let allFiles = [];
552
+ let selectedFile = null;
553
+
554
+ // 标签页切换
555
+ function switchTab(activeTab) {
556
+ const tabs = document.querySelectorAll('.tab-button');
557
+ const contents = document.querySelectorAll('.tab-content');
558
+
559
+ tabs.forEach(tab => tab.classList.remove('active'));
560
+ contents.forEach(content => content.style.display = 'none');
561
+
562
+ document.getElementById(activeTab + 'Tab').classList.add('active');
563
+ document.getElementById(activeTab + 'Content').style.display = 'block';
564
+ }
565
+
566
+ // 文件上传相关功能
567
+ function initFileUpload() {
568
+ const fileUploadArea = document.getElementById('fileUploadArea');
569
+ const fileInput = document.getElementById('fileInput');
570
+ const uploadBtn = document.getElementById('uploadBtn');
571
+ const fileInfo = document.getElementById('fileInfo');
572
+
573
+ // 点击上传区域
574
+ fileUploadArea.addEventListener('click', () => {
575
+ fileInput.click();
576
+ });
577
+
578
+ // 文件选择
579
+ fileInput.addEventListener('change', handleFileSelect);
580
+
581
+ // 拖拽上传
582
+ fileUploadArea.addEventListener('dragover', (e) => {
583
+ e.preventDefault();
584
+ fileUploadArea.classList.add('dragover');
585
+ });
586
+
587
+ fileUploadArea.addEventListener('dragleave', (e) => {
588
+ e.preventDefault();
589
+ fileUploadArea.classList.remove('dragover');
590
+ });
591
+
592
+ fileUploadArea.addEventListener('drop', (e) => {
593
+ e.preventDefault();
594
+ fileUploadArea.classList.remove('dragover');
595
+
596
+ const files = e.dataTransfer.files;
597
+ if (files.length > 0) {
598
+ fileInput.files = files;
599
+ handleFileSelect();
600
+ }
601
+ });
602
+ }
603
+
604
+ // 处理文件选择
605
+ function handleFileSelect() {
606
+ const fileInput = document.getElementById('fileInput');
607
+ const uploadBtn = document.getElementById('uploadBtn');
608
+ const fileInfo = document.getElementById('fileInfo');
609
+ const fileUploadArea = document.getElementById('fileUploadArea');
610
+
611
+ if (fileInput.files.length > 0) {
612
+ selectedFile = fileInput.files[0];
613
+
614
+ // 显示文件信息
615
+ document.getElementById('fileName').textContent = selectedFile.name;
616
+ document.getElementById('fileSize').textContent = formatFileSize(selectedFile.size);
617
+ fileInfo.classList.add('show');
618
+
619
+ // 更新上传区域样式
620
+ fileUploadArea.classList.add('file-selected');
621
+
622
+ // 启用上传按钮
623
+ uploadBtn.disabled = false;
624
+ uploadBtn.textContent = '开始上传';
625
+ } else {
626
+ selectedFile = null;
627
+ fileInfo.classList.remove('show');
628
+ fileUploadArea.classList.remove('file-selected');
629
+ uploadBtn.disabled = true;
630
+ uploadBtn.textContent = '请先选择文件';
631
+ }
632
+ }
633
+
634
+ // 上传文件
635
+ async function uploadFile() {
636
+ if (!selectedFile) {
637
+ showError('请先选择文件');
638
+ return;
639
+ }
640
+
641
+ const uploadBtn = document.getElementById('uploadBtn');
642
+ const progressContainer = document.getElementById('progressContainer');
643
+
644
+ uploadBtn.disabled = true;
645
+ uploadBtn.innerHTML = '<span class="loading"></span>上传中...';
646
+ progressContainer.style.display = 'block';
647
+
648
+ currentSessionId = Date.now().toString();
649
+
650
+ try {
651
+ const formData = new FormData();
652
+ formData.append('file', selectedFile);
653
+
654
+ const response = await fetch('/upload', {
655
+ method: 'POST',
656
+ body: formData
657
+ });
658
+
659
+ const data = await response.json();
660
+ if (data.error) {
661
+ showError(data.error);
662
+ return;
663
+ }
664
+
665
+ currentSessionId = data.session_id;
666
+
667
+ // 监控处理进度
668
+ monitorProgress(currentSessionId);
669
+
670
+ } catch (error) {
671
+ showError('上传失败: ' + error.message);
672
+ resetUploadButton();
673
+ }
674
+ }
675
+
676
+ // 重置上传按钮
677
+ function resetUploadButton() {
678
+ const uploadBtn = document.getElementById('uploadBtn');
679
+ uploadBtn.disabled = selectedFile === null;
680
+ uploadBtn.textContent = selectedFile ? '开始上传' : '请先选择文件';
681
+ document.getElementById('progressContainer').style.display = 'none';
682
+ }
683
+
684
+ // Matrix 背景效果
685
+ function initMatrix() {
686
+ const canvas = document.getElementById('matrixCanvas');
687
+ const ctx = canvas.getContext('2d');
688
+
689
+ canvas.width = window.innerWidth;
690
+ canvas.height = window.innerHeight;
691
+
692
+ const matrix = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789@#$%^&*()*&^%+-/~{[|`]}";
693
+ const matrixArray = matrix.split("");
694
+
695
+ const fontSize = 10;
696
+ const columns = canvas.width / fontSize;
697
+
698
+ const drops = [];
699
+ for(let x = 0; x < columns; x++) {
700
+ drops[x] = 1;
701
+ }
702
+
703
+ function draw() {
704
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.04)';
705
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
706
+
707
+ ctx.fillStyle = '#00ff88';
708
+ ctx.font = fontSize + 'px monospace';
709
+
710
+ for(let i = 0; i < drops.length; i++) {
711
+ const text = matrixArray[Math.floor(Math.random() * matrixArray.length)];
712
+ ctx.fillText(text, i * fontSize, drops[i] * fontSize);
713
+
714
+ if(drops[i] * fontSize > canvas.height && Math.random() > 0.975) {
715
+ drops[i] = 0;
716
+ }
717
+ drops[i]++;
718
+ }
719
+ }
720
+
721
+ setInterval(draw, 35);
722
+ }
723
+
724
+ // 下载仓库
725
+ async function downloadRepo() {
726
+ const url = document.getElementById('githubUrl').value.trim();
727
+ if (!url) {
728
+ showError('请输入GitHub仓库URL');
729
+ return;
730
+ }
731
+
732
+ const downloadBtn = document.getElementById('downloadBtn');
733
+ const progressContainer = document.getElementById('progressContainer');
734
+
735
+ downloadBtn.disabled = true;
736
+ downloadBtn.innerHTML = '<span class="loading"></span>下载中...';
737
+ progressContainer.style.display = 'block';
738
+
739
+ currentSessionId = Date.now().toString();
740
+
741
+ try {
742
+ const response = await fetch('/download', {
743
+ method: 'POST',
744
+ headers: {
745
+ 'Content-Type': 'application/json',
746
+ },
747
+ body: JSON.stringify({
748
+ url: url,
749
+ session_id: currentSessionId
750
+ })
751
+ });
752
+
753
+ const data = await response.json();
754
+ if (data.error) {
755
+ showError(data.error);
756
+ return;
757
+ }
758
+
759
+ // 监控下载进度
760
+ monitorProgress(currentSessionId);
761
+
762
+ } catch (error) {
763
+ showError('下载失败: ' + error.message);
764
+ resetDownloadButton();
765
+ }
766
+ }
767
+
768
+ // 监控下载进度
769
+ async function monitorProgress(sessionId) {
770
+ const checkProgress = async () => {
771
+ try {
772
+ const response = await fetch(`/progress/${sessionId}`);
773
+ const progress = await response.json();
774
+
775
+ const progressFill = document.getElementById('progressFill');
776
+ const progressText = document.getElementById('progressText');
777
+
778
+ progressFill.style.width = progress.progress + '%';
779
+
780
+ if (progress.status === 'starting') {
781
+ progressText.textContent = '准备下载...';
782
+ } else if (progress.status === 'downloading') {
783
+ progressText.textContent = '正在下载仓库...';
784
+ } else if (progress.status === 'extracting') {
785
+ progressText.textContent = '正在解压文件...';
786
+ } else if (progress.status === 'completed') {
787
+ progressText.textContent = '下载完成!';
788
+ setTimeout(() => loadFileTree(sessionId), 1000);
789
+ return;
790
+ } else if (progress.status === 'error') {
791
+ showError(progress.message);
792
+ resetDownloadButton();
793
+ return;
794
+ }
795
+
796
+ setTimeout(checkProgress, 1000);
797
+ } catch (error) {
798
+ showError('获取进度失败: ' + error.message);
799
+ resetDownloadButton();
800
+ }
801
+ };
802
+
803
+ checkProgress();
804
+ }
805
+
806
+ // 加载文件树
807
+ async function loadFileTree(sessionId) {
808
+ try {
809
+ const response = await fetch(`/files/${sessionId}`);
810
+ const data = await response.json();
811
+
812
+ if (data.error) {
813
+ showError(data.error);
814
+ return;
815
+ }
816
+
817
+ allFiles = data.files;
818
+ renderFileTree(data.files);
819
+
820
+ document.getElementById('filePanel').style.display = 'block';
821
+ document.querySelector('.controls').style.display = 'block';
822
+
823
+ updateStats();
824
+ resetDownloadButton();
825
+ resetUploadButton();
826
+ showSuccess('文件处理完成,请选择要合并的文件');
827
+
828
+ } catch (error) {
829
+ showError('加载文件树失败: ' + error.message);
830
+ resetDownloadButton();
831
+ }
832
+ }
833
+
834
+ // 渲染文件树
835
+ function renderFileTree(files) {
836
+ const fileTree = document.getElementById('fileTree');
837
+ fileTree.innerHTML = '';
838
+
839
+ files.forEach(file => {
840
+ const fileItem = document.createElement('div');
841
+ fileItem.className = `file-item ${file.type}`;
842
+ fileItem.style.paddingLeft = (file.level * 20 + 12) + 'px';
843
+
844
+ const icon = file.type === 'folder' ? '📁' : '📄';
845
+ const size = file.type === 'file' ? ` (${formatFileSize(file.size)})` : '';
846
+
847
+ fileItem.innerHTML = `
848
+ <span class="file-icon">${icon}</span>
849
+ <span>${file.name}${size}</span>
850
+ `;
851
+
852
+ if (file.type === 'file') {
853
+ fileItem.addEventListener('click', () => toggleFileSelection(file, fileItem));
854
+ }
855
+
856
+ fileTree.appendChild(fileItem);
857
+ });
858
+ }
859
+
860
+ // 切换文件选择状态
861
+ function toggleFileSelection(file, element) {
862
+ if (selectedFiles.has(file.path)) {
863
+ selectedFiles.delete(file.path);
864
+ element.classList.remove('selected');
865
+ } else {
866
+ selectedFiles.add(file.path);
867
+ element.classList.add('selected');
868
+ }
869
+ updateStats();
870
+ }
871
+
872
+ // 更新统计信息
873
+ function updateStats() {
874
+ const totalFileCount = allFiles.filter(f => f.type === 'file').length;
875
+ document.getElementById('totalFiles').textContent = totalFileCount;
876
+ document.getElementById('selectedFiles').textContent = selectedFiles.size;
877
+ }
878
+
879
+ // 全选文件
880
+ function selectAllFiles() {
881
+ selectedFiles.clear();
882
+ allFiles.filter(f => f.type === 'file').forEach(file => {
883
+ selectedFiles.add(file.path);
884
+ });
885
+
886
+ document.querySelectorAll('.file-item.file').forEach(item => {
887
+ item.classList.add('selected');
888
+ });
889
+
890
+ updateStats();
891
+ }
892
+
893
+ // 清除选择
894
+ function clearSelection() {
895
+ selectedFiles.clear();
896
+ document.querySelectorAll('.file-item.selected').forEach(item => {
897
+ item.classList.remove('selected');
898
+ });
899
+ updateStats();
900
+ }
901
+
902
+ // 合并文件
903
+ async function mergeFiles() {
904
+ if (selectedFiles.size === 0) {
905
+ showError('请至少选择一个文件');
906
+ return;
907
+ }
908
+
909
+ const mergeBtn = document.getElementById('mergeBtn');
910
+ mergeBtn.disabled = true;
911
+ mergeBtn.innerHTML = '<span class="loading"></span>合并中...';
912
+
913
+ try {
914
+ const response = await fetch('/merge', {
915
+ method: 'POST',
916
+ headers: {
917
+ 'Content-Type': 'application/json',
918
+ },
919
+ body: JSON.stringify({
920
+ session_id: currentSessionId,
921
+ selected_files: Array.from(selectedFiles)
922
+ })
923
+ });
924
+
925
+ const data = await response.json();
926
+
927
+ if (data.error) {
928
+ showError(data.error);
929
+ } else {
930
+ showSuccess('文件合并完成!');
931
+
932
+ // 下载合并后的文件
933
+ const link = document.createElement('a');
934
+ link.href = data.download_url;
935
+ link.click();
936
+ }
937
+
938
+ } catch (error) {
939
+ showError('合并文件失败: ' + error.message);
940
+ } finally {
941
+ mergeBtn.disabled = false;
942
+ mergeBtn.textContent = '合并文件';
943
+ }
944
+ }
945
+
946
+ // 辅助函数
947
+ function formatFileSize(bytes) {
948
+ if (bytes === 0) return '0 B';
949
+ const k = 1024;
950
+ const sizes = ['B', 'KB', 'MB', 'GB'];
951
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
952
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
953
+ }
954
+
955
+ function showError(message) {
956
+ const errorDiv = document.getElementById('errorMessage');
957
+ errorDiv.textContent = message;
958
+ errorDiv.style.display = 'block';
959
+ setTimeout(() => {
960
+ errorDiv.style.display = 'none';
961
+ }, 5000);
962
+ }
963
+
964
+ function showSuccess(message) {
965
+ const successDiv = document.getElementById('successMessage');
966
+ successDiv.textContent = message;
967
+ successDiv.style.display = 'block';
968
+ setTimeout(() => {
969
+ successDiv.style.display = 'none';
970
+ }, 5000);
971
+ }
972
+
973
+ function resetDownloadButton() {
974
+ const downloadBtn = document.getElementById('downloadBtn');
975
+ downloadBtn.disabled = false;
976
+ downloadBtn.textContent = '开始下载';
977
+ document.getElementById('progressContainer').style.display = 'none';
978
+ }
979
+
980
+ // 事件监听器
981
+ document.getElementById('githubTab').addEventListener('click', () => switchTab('github'));
982
+ document.getElementById('uploadTab').addEventListener('click', () => switchTab('upload'));
983
+ document.getElementById('downloadBtn').addEventListener('click', downloadRepo);
984
+ document.getElementById('uploadBtn').addEventListener('click', uploadFile);
985
+ document.getElementById('selectAllBtn').addEventListener('click', selectAllFiles);
986
+ document.getElementById('clearSelectionBtn').addEventListener('click', clearSelection);
987
+ document.getElementById('mergeBtn').addEventListener('click', mergeFiles);
988
+
989
+ // 回车键下载
990
+ document.getElementById('githubUrl').addEventListener('keypress', function(e) {
991
+ if (e.key === 'Enter') {
992
+ downloadRepo();
993
+ }
994
+ });
995
+
996
+ // 初始化
997
+ window.addEventListener('load', () => {
998
+ initMatrix();
999
+ initFileUpload();
1000
+ });
1001
+
1002
+ // 响应式处理
1003
+ window.addEventListener('resize', () => {
1004
+ const canvas = document.getElementById('matrixCanvas');
1005
+ canvas.width = window.innerWidth;
1006
+ canvas.height = window.innerHeight;
1007
+ });
1008
+ </script>
1009
+ </body>
1010
+ </html>