openfree commited on
Commit
beaf11d
ยท
verified ยท
1 Parent(s): cd1d350

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +42 -670
app.py CHANGED
@@ -428,684 +428,56 @@ def delete_url():
428
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 6. MAIN ROUTES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
429
  @app.route('/')
430
  def home():
431
- os.makedirs('templates', exist_ok=True)
 
 
432
 
433
- with open('templates/index.html', 'w', encoding='utf-8') as fp:
434
- fp.write(r'''<!DOCTYPE html>
435
  <html>
436
  <head>
437
- <meta charset="utf-8">
438
- <meta name="viewport" content="width=device-width, initial-scale=1">
439
- <title>Web Gallery</title>
440
- <style>
441
- @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@300;600&display=swap');
442
- body{margin:0;font-family:Nunito,sans-serif;background:#f6f8fb;}
443
- .tabs{display:flex;flex-wrap:wrap;gap:8px;padding:16px;}
444
- .tab{padding:6px 14px;border:none;border-radius:18px;background:#e2e8f0;font-weight:600;cursor:pointer;}
445
- .tab.active{background:#a78bfa;color:#1a202c;}
446
- .tab.manage{background:#ff6e91;color:white;}
447
- .tab.manage.active{background:#ff2d62;color:white;}
448
- .grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;padding:0 16px 60px;}
449
- @media(max-width:800px){.grid{grid-template-columns:1fr;}}
450
- .card{background:#fff;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,.08);overflow:hidden;height:420px;display:flex;flex-direction:column;position:relative;}
451
- .frame{flex:1;position:relative;overflow:hidden;}
452
- .frame iframe{position:absolute;width:166.667%;height:166.667%;transform:scale(.6);transform-origin:top left;border:0;}
453
- .frame img{width:100%;height:100%;object-fit:cover;}
454
- .card-label{position:absolute;top:10px;left:10px;padding:4px 8px;border-radius:4px;font-size:11px;font-weight:bold;z-index:100;text-transform:uppercase;letter-spacing:0.5px;box-shadow:0 2px 4px rgba(0,0,0,0.2);}
455
- .label-live{background:linear-gradient(135deg, #00c6ff, #0072ff);color:white;}
456
- .label-static{background:linear-gradient(135deg, #ff9a9e, #fad0c4);color:#333;}
457
- .foot{height:44px;background:#fafafa;display:flex;align-items:center;justify-content:center;border-top:1px solid #eee;}
458
- .foot a{font-size:.82rem;font-weight:700;color:#4a6dd8;text-decoration:none;}
459
- .pagination{display:flex;justify-content:center;margin:20px 0;gap:10px;}
460
- .pagination button{padding:5px 15px;border:none;border-radius:20px;background:#e2e8f0;cursor:pointer;}
461
- .pagination button:disabled{opacity:0.5;cursor:not-allowed;}
462
- .manage-panel{background:white;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,.08);margin:16px;padding:20px;}
463
- .form-group{margin-bottom:15px;}
464
- .form-group label{display:block;margin-bottom:5px;font-weight:600;}
465
- .form-control{width:100%;padding:8px;border:1px solid #ddd;border-radius:4px;box-sizing:border-box;}
466
- textarea.form-control{min-height:100px;font-family:monospace;resize:vertical;}
467
- .btn{padding:8px 15px;border:none;border-radius:4px;cursor:pointer;font-weight:600;}
468
- .btn-primary{background:#4a6dd8;color:white;}
469
- .btn-danger{background:#e53e3e;color:white;}
470
- .btn-success{background:#38a169;color:white;}
471
- .status{padding:10px;margin:10px 0;border-radius:4px;display:none;}
472
- .status.success{display:block;background:#c6f6d5;color:#22543d;}
473
- .status.error{display:block;background:#fed7d7;color:#822727;}
474
- .url-list{margin:20px 0;border:1px solid #eee;border-radius:4px;max-height:300px;overflow-y:auto;}
475
- .url-item{padding:10px;border-bottom:1px solid #eee;display:flex;justify-content:space-between;align-items:center;}
476
- .url-item:last-child{border-bottom:none;}
477
- .url-controls{display:flex;gap:5px;}
478
- </style>
479
  </head>
480
  <body>
481
- <header style="text-align: center; padding: 20px; background: linear-gradient(135deg, #f6f8fb, #e2e8f0); border-bottom: 1px solid #ddd;">
482
- <h1 style="margin-bottom: 10px;">๐ŸŒŸ AI Favorite Sites</h1>
483
- <p class="description" style="margin-bottom: 15px;">
484
- ๐Ÿš€ <strong>Free AI Spaces & Website Gallery</strong> โœจ Save and manage your favorite sites! Supports <span style="background: linear-gradient(135deg, #00c6ff, #0072ff); color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px;">LIVE</span> and <span style="background: linear-gradient(135deg, #ff9a9e, #fad0c4); color: #333; padding: 2px 6px; border-radius: 4px; font-size: 12px;">Static</span> preview modes.
485
- </p>
486
- <p>
487
- <a href="https://discord.gg/openfreeai" target="_blank"><img src="https://img.shields.io/static/v1?label=Discord&message=Openfree%20AI&color=%230000ff&labelColor=%23800080&logo=discord&logoColor=white&style=for-the-badge" alt="badge"></a>
488
- </p>
489
- </header>
490
- <div class="tabs" id="tabs"></div>
491
- <div id="content"></div>
492
-
493
- <script>
494
- // Basic configuration
495
- const cats = {{cats|tojson}};
496
- const tabs = document.getElementById('tabs');
497
- const content = document.getElementById('content');
498
- let active = "";
499
- let currentPage = 1;
500
-
501
- // LocalStorage functionality for URL persistence
502
- function saveToLocalStorage(urls) {
503
- try {
504
- localStorage.setItem('favoriteUrls', JSON.stringify(urls));
505
- console.log('Saved URLs to localStorage:', urls.length);
506
- return true;
507
- } catch (e) {
508
- console.error('Error saving to localStorage:', e);
509
- return false;
510
- }
511
- }
512
-
513
- function loadFromLocalStorage() {
514
- try {
515
- const data = localStorage.getItem('favoriteUrls');
516
- if (data) {
517
- const urls = JSON.parse(data);
518
- console.log('Loaded URLs from localStorage:', urls.length);
519
- return urls;
520
- }
521
- } catch (e) {
522
- console.error('Error loading from localStorage:', e);
523
- }
524
- return null;
525
- }
526
-
527
- // Export/Import functionality
528
- function exportUrls() {
529
- makeRequest('/api/favorites?per_page=1000', 'GET', null, function(data) {
530
- if (data.items && data.items.length > 0) {
531
- const urls = data.items.map(item => item.url);
532
-
533
- // Save to localStorage as backup
534
- saveToLocalStorage(urls);
535
-
536
- // Create file for download
537
- const blob = new Blob([JSON.stringify(urls, null, 2)], { type: 'application/json' });
538
- const a = document.createElement('a');
539
- a.href = URL.createObjectURL(blob);
540
- a.download = 'favorite_urls.json';
541
- document.body.appendChild(a);
542
- a.click();
543
- document.body.removeChild(a);
544
- } else {
545
- alert('No URLs to export');
546
- }
547
- });
548
- }
549
-
550
- function importUrls() {
551
- document.getElementById('import-file').click();
552
- }
553
-
554
- function handleImportFile(files) {
555
- if (files.length === 0) return;
556
-
557
- const file = files[0];
558
- const reader = new FileReader();
559
-
560
- reader.onload = function(e) {
561
- try {
562
- const urls = JSON.parse(e.target.result);
563
- if (Array.isArray(urls)) {
564
- // Save to localStorage
565
- saveToLocalStorage(urls);
566
-
567
- // Add each URL to the server
568
- let processed = 0;
569
-
570
- function addNextUrl(index) {
571
- if (index >= urls.length) {
572
- alert(`Import complete. Added ${processed} URLs.`);
573
- loadUrlList();
574
- if (active === 'Favorites') {
575
- loadFavorites(currentPage);
576
- }
577
- return;
578
- }
579
-
580
- const formData = new FormData();
581
- formData.append('url', urls[index]);
582
-
583
- makeRequest('/api/url/add', 'POST', formData, function(data) {
584
- if (data.success) processed++;
585
- addNextUrl(index + 1);
586
- });
587
- }
588
-
589
- addNextUrl(0);
590
- } else {
591
- alert('Invalid format. File must contain a JSON array of URLs.');
592
- }
593
- } catch (e) {
594
- alert('Error parsing file: ' + e.message);
595
- }
596
- };
597
-
598
- reader.readAsText(file);
599
- }
600
-
601
- // Simple utility functions
602
- function loadHTML(url, callback) {
603
- const xhr = new XMLHttpRequest();
604
- xhr.open('GET', url, true);
605
- xhr.onreadystatechange = function() {
606
- if (xhr.readyState === 4 && xhr.status === 200) {
607
- callback(xhr.responseText);
608
- }
609
- };
610
- xhr.send();
611
- }
612
-
613
- function makeRequest(url, method, data, callback) {
614
- const xhr = new XMLHttpRequest();
615
- xhr.open(method, url, true);
616
- xhr.onreadystatechange = function() {
617
- if (xhr.readyState === 4 && xhr.status === 200) {
618
- callback(JSON.parse(xhr.responseText));
619
- }
620
- };
621
- if (method === 'POST') {
622
- xhr.send(data);
623
- } else {
624
- xhr.send();
625
- }
626
- }
627
-
628
- function updateTabs() {
629
- Array.from(tabs.children).forEach(b => {
630
- b.classList.toggle('active', b.dataset.c === active);
631
- });
632
- }
633
-
634
- // Tab handlers
635
- function loadCategory(cat) {
636
- if(cat === active) return;
637
- active = cat;
638
- updateTabs();
639
-
640
- content.innerHTML = '<p style="text-align:center;padding:40px">Loadingโ€ฆ</p>';
641
-
642
- makeRequest('/api/category?name=' + encodeURIComponent(cat), 'GET', null, function(data) {
643
- let html = '<div class="grid">';
644
-
645
- data.forEach(item => {
646
- html += `
647
- <div class="card">
648
- <div class="card-label label-live">LIVE</div>
649
- <div class="frame">
650
- <iframe src="${item.iframe}" loading="lazy" sandbox="allow-forms allow-modals allow-popups allow-same-origin allow-scripts allow-downloads"></iframe>
651
- </div>
652
- <div class="foot">
653
- <a href="${item.hf}" target="_blank">${item.title}</a>
654
- </div>
655
- </div>
656
- `;
657
- });
658
-
659
- html += '</div>';
660
- content.innerHTML = html;
661
- });
662
- }
663
-
664
- function loadFavorites(page) {
665
- if(active === 'Favorites' && currentPage === page) return;
666
- active = 'Favorites';
667
- currentPage = page || 1;
668
- updateTabs();
669
-
670
- content.innerHTML = '<p style="text-align:center;padding:40px">Loadingโ€ฆ</p>';
671
-
672
- makeRequest('/api/favorites?page=' + currentPage, 'GET', null, function(data) {
673
- let html = '<div class="grid">';
674
-
675
- if(data.items.length === 0) {
676
- html += '<p style="grid-column:1/-1;text-align:center;padding:40px">No favorites saved yet.</p>';
677
- } else {
678
- data.items.forEach(item => {
679
- if(item.mode === 'snapshot') {
680
- html += `
681
- <div class="card">
682
- <div class="card-label label-static">Static</div>
683
- <div class="frame">
684
- <img src="${item.preview_url}" loading="lazy">
685
- </div>
686
- <div class="foot">
687
- <a href="${item.url}" target="_blank">${item.title}</a>
688
- </div>
689
- </div>
690
- `;
691
- } else {
692
- html += `
693
- <div class="card">
694
- <div class="card-label label-live">LIVE</div>
695
- <div class="frame">
696
- <iframe src="${item.preview_url}" loading="lazy" sandbox="allow-forms allow-modals allow-popups allow-same-origin allow-scripts allow-downloads"></iframe>
697
- </div>
698
- <div class="foot">
699
- <a href="${item.url}" target="_blank">${item.title}</a>
700
- </div>
701
- </div>
702
- `;
703
- }
704
- });
705
- }
706
-
707
- html += '</div>';
708
-
709
- // Add pagination
710
- html += `
711
- <div class="pagination">
712
- <button ${currentPage <= 1 ? 'disabled' : ''} onclick="loadFavorites(${currentPage-1})">ยซ Previous</button>
713
- <span>Page ${currentPage} of ${data.total_pages}</span>
714
- <button ${currentPage >= data.total_pages ? 'disabled' : ''} onclick="loadFavorites(${currentPage+1})">Next ยป</button>
715
- </div>
716
- `;
717
-
718
- content.innerHTML = html;
719
- });
720
- }
721
-
722
- function loadManage() {
723
- if(active === 'Manage') return;
724
- active = 'Manage';
725
- updateTabs();
726
-
727
- content.innerHTML = `
728
- <div class="manage-panel">
729
- <h2>Add New URL</h2>
730
- <div class="form-group">
731
- <label for="new-url">Single URL</label>
732
- <input type="text" id="new-url" class="form-control" placeholder="https://example.com">
733
- <button onclick="addUrl()" class="btn btn-primary" style="margin-top:10px">Add URL</button>
734
- </div>
735
-
736
- <div class="form-group" style="margin-top:20px">
737
- <label for="batch-urls">Multiple URLs (up to 100 URLs, one per line)</label>
738
- <textarea id="batch-urls" class="form-control" rows="8" placeholder="https://example1.com&#10;https://example2.com&#10;https://example3.com"></textarea>
739
- <button onclick="addBatchUrls()" class="btn btn-primary" style="margin-top:10px">Add All URLs</button>
740
- </div>
741
- <div id="add-status" class="status"></div>
742
- <div id="progress-bar" style="display:none; margin:15px 0;">
743
- <div style="height:20px; background-color:#f0f0f0; border-radius:4px; overflow:hidden;">
744
- <div id="progress-fill" style="height:100%; width:0%; background-color:#4a6dd8; transition:width 0.3s;"></div>
745
- </div>
746
- <div id="progress-text" style="text-align:center; margin-top:5px; font-size:14px;">0%</div>
747
- </div>
748
-
749
- <h2>Manage Saved URLs</h2>
750
- <div id="url-list" class="url-list">Loading...</div>
751
-
752
- <div style="margin-top: 30px;">
753
- <h3>Backup & Restore</h3>
754
- <p>Server storage may not persist across restarts. Use these options to save your data:</p>
755
- <button onclick="exportUrls()" class="btn btn-success">Export URLs</button>
756
- <button onclick="importUrls()" class="btn btn-primary">Import URLs</button>
757
- <input type="file" id="import-file" style="display:none" onchange="handleImportFile(this.files)">
758
- </div>
759
- </div>
760
- `;
761
-
762
- loadUrlList();
763
- }
764
-
765
- // URL management functions
766
- function loadUrlList() {
767
- // First try to load from localStorage as a fallback
768
- const localUrls = loadFromLocalStorage();
769
-
770
- makeRequest('/api/favorites?per_page=100', 'GET', null, function(data) {
771
- const urlList = document.getElementById('url-list');
772
-
773
- // If server has no URLs but localStorage does, restore from localStorage
774
- if (data.items.length === 0 && localUrls && localUrls.length > 0) {
775
- showStatus('add-status', 'Restoring URLs from local backup...', true);
776
-
777
- // Add each URL from localStorage to the server
778
- let restored = 0;
779
-
780
- function restoreNextUrl(index) {
781
- if (index >= localUrls.length) {
782
- showStatus('add-status', `Restored ${restored} URLs from local backup`, true);
783
- // Reload the list after restoration
784
- setTimeout(() => {
785
- loadUrlList();
786
- if (active === 'Favorites') {
787
- loadFavorites(currentPage);
788
- }
789
- }, 1000);
790
- return;
791
- }
792
-
793
- const formData = new FormData();
794
- formData.append('url', localUrls[index]);
795
-
796
- makeRequest('/api/url/add', 'POST', formData, function(data) {
797
- if (data.success) restored++;
798
- restoreNextUrl(index + 1);
799
- });
800
- }
801
-
802
- restoreNextUrl(0);
803
- return;
804
- }
805
-
806
- if(data.items.length === 0) {
807
- urlList.innerHTML = '<p style="text-align:center;padding:20px">No URLs saved yet.</p>';
808
- return;
809
- }
810
-
811
- // Update localStorage with server data
812
- saveToLocalStorage(data.items.map(item => item.url));
813
-
814
- let html = '';
815
- data.items.forEach(item => {
816
- // Escape the URL to prevent JavaScript injection when used in onclick handlers
817
- const escapedUrl = item.url.replace(/'/g, "\\'");
818
-
819
- html += `
820
- <div class="url-item">
821
- <div>${item.url}</div>
822
- <div class="url-controls">
823
- <button class="btn" onclick="editUrl('${escapedUrl}')">Edit</button>
824
- <button class="btn btn-danger" onclick="deleteUrl('${escapedUrl}')">Delete</button>
825
- </div>
826
- </div>
827
- `;
828
- });
829
-
830
- urlList.innerHTML = html;
831
- });
832
- }
833
-
834
- function addUrl() {
835
- const url = document.getElementById('new-url').value.trim();
836
-
837
- if(!url) {
838
- showStatus('add-status', 'Please enter a URL', false);
839
- return;
840
- }
841
-
842
- const formData = new FormData();
843
- formData.append('url', url);
844
-
845
- makeRequest('/api/url/add', 'POST', formData, function(data) {
846
- showStatus('add-status', data.message, data.success);
847
- if(data.success) {
848
- document.getElementById('new-url').value = '';
849
-
850
- // Update localStorage
851
- const localUrls = loadFromLocalStorage() || [];
852
- if (!localUrls.includes(url)) {
853
- localUrls.unshift(url); // Add to beginning
854
- saveToLocalStorage(localUrls);
855
- }
856
-
857
- loadUrlList();
858
- // If currently in Favorites tab, reload to see changes immediately
859
- if(active === 'Favorites') {
860
- loadFavorites(currentPage);
861
- }
862
- }
863
- });
864
- }
865
-
866
- function addBatchUrls() {
867
- const textarea = document.getElementById('batch-urls');
868
- const text = textarea.value.trim();
869
-
870
- if (!text) {
871
- showStatus('add-status', 'Please enter at least one URL', false);
872
- return;
873
- }
874
-
875
- // Split by newlines and filter out empty lines
876
- let urls = text.split(/\r?\n/).filter(url => url.trim() !== '');
877
-
878
- // Limit to 100 URLs
879
- if (urls.length > 100) {
880
- showStatus('add-status', 'Too many URLs. Limited to 100 at once.', false);
881
- urls = urls.slice(0, 100);
882
- }
883
-
884
- if (urls.length === 0) {
885
- showStatus('add-status', 'No valid URLs found', false);
886
- return;
887
- }
888
-
889
- // Show progress bar
890
- const progressBar = document.getElementById('progress-bar');
891
- const progressFill = document.getElementById('progress-fill');
892
- const progressText = document.getElementById('progress-text');
893
- progressBar.style.display = 'block';
894
- progressFill.style.width = '0%';
895
- progressText.textContent = '0%';
896
-
897
- // Add URLs one by one
898
- let processed = 0;
899
- let succeeded = 0;
900
-
901
- function updateProgress() {
902
- const percentage = Math.round((processed / urls.length) * 100);
903
- progressFill.style.width = percentage + '%';
904
- progressText.textContent = `${processed}/${urls.length} (${percentage}%)`;
905
- }
906
-
907
- function addNextUrl(index) {
908
- if (index >= urls.length) {
909
- // All done
910
- setTimeout(() => {
911
- progressBar.style.display = 'none';
912
- textarea.value = '';
913
- showStatus('add-status', `Added ${succeeded} of ${urls.length} URLs successfully`, true);
914
-
915
- // Reload URL list and favorites
916
- loadUrlList();
917
- if (active === 'Favorites') {
918
- loadFavorites(currentPage);
919
- }
920
- }, 500);
921
- return;
922
- }
923
-
924
- const url = urls[index].trim();
925
- if (!url) {
926
- // Skip empty URLs
927
- processed++;
928
- updateProgress();
929
- addNextUrl(index + 1);
930
- return;
931
- }
932
 
933
- const formData = new FormData();
934
- formData.append('url', url);
935
 
936
- makeRequest('/api/url/add', 'POST', formData, function(data) {
937
- processed++;
938
-
939
- if (data.success) {
940
- succeeded++;
941
-
942
- // Update localStorage
943
- const localUrls = loadFromLocalStorage() || [];
944
- if (!localUrls.includes(url)) {
945
- localUrls.unshift(url);
946
- saveToLocalStorage(localUrls);
947
- }
948
- }
949
-
950
- updateProgress();
951
- addNextUrl(index + 1);
952
- });
953
- }
954
-
955
- // Start adding URLs
956
- addNextUrl(0);
957
- }
958
-
959
- function editUrl(url) {
960
- // Decode URL if it was previously escaped
961
- const decodedUrl = url.replace(/\\'/g, "'");
962
- const newUrl = prompt('Edit URL:', decodedUrl);
963
-
964
- if(!newUrl || newUrl === decodedUrl) return;
965
-
966
- const formData = new FormData();
967
- formData.append('old', decodedUrl);
968
- formData.append('new', newUrl);
969
-
970
- makeRequest('/api/url/update', 'POST', formData, function(data) {
971
- if(data.success) {
972
- // Update localStorage
973
- let localUrls = loadFromLocalStorage() || [];
974
- const index = localUrls.indexOf(decodedUrl);
975
- if (index !== -1) {
976
- localUrls[index] = newUrl;
977
- saveToLocalStorage(localUrls);
978
- }
979
-
980
- loadUrlList();
981
- // If currently in Favorites tab, reload to see changes immediately
982
- if(active === 'Favorites') {
983
- loadFavorites(currentPage);
984
- }
985
- } else {
986
- alert(data.message);
987
- }
988
- });
989
- }
990
-
991
- function deleteUrl(url) {
992
- // Decode URL if it was previously escaped
993
- const decodedUrl = url.replace(/\\'/g, "'");
994
- if(!confirm('Are you sure you want to delete this URL?')) return;
995
-
996
- const formData = new FormData();
997
- formData.append('url', decodedUrl);
998
-
999
- makeRequest('/api/url/delete', 'POST', formData, function(data) {
1000
- if(data.success) {
1001
- // Update localStorage
1002
- let localUrls = loadFromLocalStorage() || [];
1003
- const index = localUrls.indexOf(decodedUrl);
1004
- if (index !== -1) {
1005
- localUrls.splice(index, 1);
1006
- saveToLocalStorage(localUrls);
1007
- }
1008
-
1009
- loadUrlList();
1010
- // If currently in Favorites tab, reload to see changes immediately
1011
- if(active === 'Favorites') {
1012
- loadFavorites(currentPage);
1013
- }
1014
- } else {
1015
- alert(data.message);
1016
- }
1017
- });
1018
- }
1019
-
1020
- function showStatus(id, message, success) {
1021
- const status = document.getElementById(id);
1022
- status.textContent = message;
1023
- status.className = success ? 'status success' : 'status error';
1024
- setTimeout(() => {
1025
- status.className = 'status';
1026
- }, 3000);
1027
- }
1028
-
1029
- // Create tabs
1030
- // Favorites tab first
1031
- const favTab = document.createElement('button');
1032
- favTab.className = 'tab';
1033
- favTab.textContent = 'Favorites';
1034
- favTab.dataset.c = 'Favorites';
1035
- favTab.onclick = function() { loadFavorites(1); };
1036
- tabs.appendChild(favTab);
1037
-
1038
- // Category tabs
1039
- cats.forEach(c => {
1040
- const b = document.createElement('button');
1041
- b.className = 'tab';
1042
- b.textContent = c;
1043
- b.dataset.c = c;
1044
- b.onclick = function() { loadCategory(c); };
1045
- tabs.appendChild(b);
1046
- });
1047
-
1048
- // Manage tab last
1049
- const manageTab = document.createElement('button');
1050
- manageTab.className = 'tab manage';
1051
- manageTab.textContent = 'Manage';
1052
- manageTab.dataset.c = 'Manage';
1053
- manageTab.onclick = function() { loadManage(); };
1054
- tabs.appendChild(manageTab);
1055
-
1056
- // Start with Favorites tab
1057
- loadFavorites(1);
1058
-
1059
- // Check for localStorage on page load
1060
- window.addEventListener('load', function() {
1061
- // Try to load URLs from localStorage
1062
- const localUrls = loadFromLocalStorage();
1063
-
1064
- // If we have URLs in localStorage, make sure they're on the server
1065
- if (localUrls && localUrls.length > 0) {
1066
- console.log(`Found ${localUrls.length} URLs in localStorage`);
1067
-
1068
- // First get server URLs to compare
1069
- makeRequest('/api/favorites?per_page=1000', 'GET', null, function(data) {
1070
- const serverUrls = data.items.map(item => item.url);
1071
-
1072
- // Find URLs that are in localStorage but not on server
1073
- const missingUrls = localUrls.filter(url => !serverUrls.includes(url));
1074
-
1075
- if (missingUrls.length > 0) {
1076
- console.log(`Found ${missingUrls.length} URLs missing from server, restoring...`);
1077
-
1078
- // Add missing URLs to server
1079
- let restored = 0;
1080
-
1081
- function restoreNextUrl(index) {
1082
- if (index >= missingUrls.length) {
1083
- console.log(`Restored ${restored} URLs from localStorage`);
1084
- if (active === 'Favorites') {
1085
- loadFavorites(currentPage);
1086
- }
1087
- return;
1088
- }
1089
-
1090
- const formData = new FormData();
1091
- formData.append('url', missingUrls[index]);
1092
-
1093
- makeRequest('/api/url/add', 'POST', formData, function(data) {
1094
- if (data.success) restored++;
1095
- restoreNextUrl(index + 1);
1096
- });
1097
- }
1098
-
1099
- restoreNextUrl(0);
1100
- }
1101
- });
1102
- }
1103
- });
1104
- </script>
1105
- </body>
1106
- </html>''')
1107
 
1108
- # Return the rendered template
1109
  return render_template('index.html', cats=list(CATEGORIES.keys()))
1110
 
1111
  # Initialize database on startup
 
428
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 6. MAIN ROUTES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
429
  @app.route('/')
430
  def home():
431
+ # Create a simple initial template for debugging
432
+ template_dir = os.path.join(BASE_DIR, 'templates')
433
+ os.makedirs(template_dir, exist_ok=True)
434
 
435
+ index_html = '''<!DOCTYPE html>
 
436
  <html>
437
  <head>
438
+ <meta charset="utf-8">
439
+ <meta name="viewport" content="width=device-width, initial-scale=1">
440
+ <title>AI Favorite Sites</title>
441
+ <style>
442
+ body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
443
+ h1 { text-align: center; }
444
+ </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  </head>
446
  <body>
447
+ <h1>๐ŸŒŸ AI Favorite Sites</h1>
448
+ <p style="text-align: center;">
449
+ <a href="https://discord.gg/openfreeai" target="_blank">
450
+ <img src="https://img.shields.io/static/v1?label=Discord&message=Openfree%20AI&color=%230000ff&labelColor=%23800080&logo=discord&logoColor=white&style=for-the-badge" alt="Discord">
451
+ </a>
452
+ </p>
453
+ <div id="content">Loading...</div>
454
+
455
+ <script>
456
+ // Simple alert to check if JavaScript is working
457
+ window.onload = function() {
458
+ document.getElementById('content').innerHTML = 'JavaScript is working! Loading content...';
459
+
460
+ // List available categories
461
+ const cats = {{cats|tojson}};
462
+ let catList = '<ul>';
463
+ cats.forEach(cat => {
464
+ catList += '<li>' + cat + '</li>';
465
+ });
466
+ catList += '</ul>';
467
+
468
+ document.getElementById('content').innerHTML += '<p>Available categories:</p>' + catList;
469
+ };
470
+ </script>
471
+ </body>
472
+ </html>'''
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
 
474
+ with open(os.path.join(template_dir, 'index.html'), 'w', encoding='utf-8') as f:
475
+ f.write(index_html)
476
 
477
+ # Log for debugging
478
+ logger.info(f"Template written to: {os.path.join(template_dir, 'index.html')}")
479
+ logger.info(f"Categories: {list(CATEGORIES.keys())}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480
 
 
481
  return render_template('index.html', cats=list(CATEGORIES.keys()))
482
 
483
  # Initialize database on startup