seawolf2357 commited on
Commit
4372751
ยท
verified ยท
1 Parent(s): 907dfcf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +383 -208
app.py CHANGED
@@ -1,41 +1,162 @@
1
  from flask import Flask, render_template, request, jsonify, session
 
 
2
  import os
3
  from datetime import timedelta
 
 
 
 
 
 
 
4
 
5
  app = Flask(__name__)
6
  app.secret_key = os.urandom(24)
7
  app.permanent_session_lifetime = timedelta(days=7)
8
 
9
- # Hugging Face URL ๋ชฉ๋ก - ์ผ๋ถ€ URL์€ ๋ฏธ๋ฆฌ ์ข‹์•„์š” ์ƒํƒœ๋กœ ์„ค์ •
10
- # 'is_liked'๋ฅผ true๋กœ ์„ค์ •ํ•œ URL์€ ํ•ญ์ƒ ์ข‹์•„์š” ์ƒํƒœ๋กœ ํ‘œ์‹œ๋จ
11
  HUGGINGFACE_URLS = [
12
- {"url": "https://huggingface.co/spaces/ginipick/Tech_Hangman_Game", "is_liked": True},
13
- {"url": "https://huggingface.co/spaces/openfree/deepseek_r1_API", "is_liked": False},
14
- {"url": "https://huggingface.co/spaces/ginipick/open_Deep-Research", "is_liked": True},
15
- {"url": "https://huggingface.co/spaces/aiqmaster/open-deep-research", "is_liked": False},
16
- {"url": "https://huggingface.co/spaces/seawolf2357/DeepSeek-R1-32b-search", "is_liked": True},
17
- {"url": "https://huggingface.co/spaces/ginigen/LLaDA", "is_liked": False},
18
- {"url": "https://huggingface.co/spaces/VIDraft/PHI4-Multimodal", "is_liked": True},
19
- {"url": "https://huggingface.co/spaces/ginigen/Ovis2-8B", "is_liked": False},
20
- {"url": "https://huggingface.co/spaces/ginigen/Graph-Mind", "is_liked": True},
21
- {"url": "https://huggingface.co/spaces/ginigen/Workflow-Canvas", "is_liked": False},
22
- {"url": "https://huggingface.co/spaces/ginigen/Design", "is_liked": True},
23
- {"url": "https://huggingface.co/spaces/ginigen/Diagram", "is_liked": False},
24
- {"url": "https://huggingface.co/spaces/ginigen/Mockup", "is_liked": True},
25
- {"url": "https://huggingface.co/spaces/ginigen/Infographic", "is_liked": False},
26
- {"url": "https://huggingface.co/spaces/ginigen/Flowchart", "is_liked": True},
27
- {"url": "https://huggingface.co/spaces/aiqcamp/FLUX-Vision", "is_liked": False},
28
- {"url": "https://huggingface.co/spaces/ginigen/VoiceClone-TTS", "is_liked": True},
29
- {"url": "https://huggingface.co/spaces/openfree/Perceptron-Network", "is_liked": False},
30
- {"url": "https://huggingface.co/spaces/openfree/Article-Generator", "is_liked": True},
31
  ]
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  # URL์˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์„ ์ œ๋ชฉ์œผ๋กœ ์ถ”์ถœ
34
  def extract_title(url):
35
  parts = url.split("/")
36
  title = parts[-1] if parts else ""
37
  return title.replace("_", " ").replace("-", " ")
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  @app.route('/')
40
  def home():
41
  return render_template('index.html')
@@ -47,16 +168,36 @@ def login():
47
  if not token:
48
  return jsonify({'success': False, 'message': 'ํ† ํฐ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.'})
49
 
50
- # ๊ฐ„๋‹จํžˆ ํ† ํฐ ๊ธธ์ด๋งŒ ๊ฒ€์‚ฌ (์‹ค์ œ๋กœ๋Š” ๋” ๋ณต์žกํ•œ ๊ฒ€์ฆ ํ•„์š”)
51
- if len(token) < 5:
 
52
  return jsonify({'success': False, 'message': '์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ž…๋‹ˆ๋‹ค.'})
53
 
54
- # ํ† ํฐ์˜ ์ฒซ ๊ธ€์ž๋ฅผ ์‚ฌ์šฉ์ž ์ด๋ฆ„์œผ๋กœ ์„ค์ • (ํ…Œ์ŠคํŠธ์šฉ)
55
- username = f"์‚ฌ์šฉ์ž_{token[:3]}"
 
 
 
 
 
 
 
 
56
 
57
  # ์„ธ์…˜์— ์ €์žฅ
 
58
  session['username'] = username
59
 
 
 
 
 
 
 
 
 
 
 
60
  return jsonify({
61
  'success': True,
62
  'username': username
@@ -64,20 +205,30 @@ def login():
64
 
65
  @app.route('/api/logout', methods=['POST'])
66
  def logout():
 
67
  session.pop('username', None)
 
68
  return jsonify({'success': True})
69
 
70
  @app.route('/api/urls', methods=['GET'])
71
  def get_urls():
 
 
72
  results = []
73
- for url_item in HUGGINGFACE_URLS:
74
- url = url_item["url"]
75
- is_liked = url_item["is_liked"]
76
  title = extract_title(url)
 
 
 
 
 
 
 
77
 
78
  results.append({
79
  'url': url,
80
  'title': title,
 
81
  'is_liked': is_liked
82
  })
83
 
@@ -85,7 +236,7 @@ def get_urls():
85
 
86
  @app.route('/api/toggle-like', methods=['POST'])
87
  def toggle_like():
88
- if 'username' not in session:
89
  return jsonify({'success': False, 'message': '๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'})
90
 
91
  data = request.json
@@ -94,19 +245,42 @@ def toggle_like():
94
  if not url:
95
  return jsonify({'success': False, 'message': 'URL์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'})
96
 
97
- # URL ๋ชฉ๋ก์—์„œ ํ•ด๋‹น URL ์ฐพ๊ธฐ
98
- for url_item in HUGGINGFACE_URLS:
99
- if url_item["url"] == url:
100
- # ์ข‹์•„์š” ์ƒํƒœ ํ† ๊ธ€
101
- url_item["is_liked"] = not url_item["is_liked"]
102
-
103
- return jsonify({
104
- 'success': True,
105
- 'is_liked': url_item["is_liked"],
106
- 'message': '์ข‹์•„์š”๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.' if url_item["is_liked"] else '์ข‹์•„์š”๋ฅผ ์ทจ์†Œํ–ˆ์Šต๋‹ˆ๋‹ค.'
107
- })
108
 
109
- return jsonify({'success': False, 'message': 'ํ•ด๋‹น URL์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.'})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
  @app.route('/api/session-status', methods=['GET'])
112
  def session_status():
@@ -190,6 +364,14 @@ if __name__ == '__main__':
190
  background-color: #45a049;
191
  }
192
 
 
 
 
 
 
 
 
 
193
  button.logout {
194
  background-color: #f44336;
195
  }
@@ -387,6 +569,14 @@ if __name__ == '__main__':
387
  margin-top: 1rem;
388
  }
389
 
 
 
 
 
 
 
 
 
390
  @media (max-width: 768px) {
391
  .user-controls {
392
  flex-direction: column;
@@ -429,11 +619,16 @@ if __name__ == '__main__':
429
  </div>
430
 
431
  <div id="loggedInSection" class="logged-in-section">
 
432
  <button id="logoutButton" class="logout">๋กœ๊ทธ์•„์›ƒ</button>
433
  </div>
434
  </div>
435
  </div>
436
 
 
 
 
 
437
  <div id="likeStatus" class="like-status">
438
  <div id="likeStatsText">์ด <span id="totalUrlCount">0</span>๊ฐœ ์ค‘ <strong><span id="likedUrlCount">0</span>๊ฐœ</strong>์˜ URL์„ ์ข‹์•„์š” ํ–ˆ์Šต๋‹ˆ๋‹ค.</div>
439
  </div>
@@ -463,6 +658,7 @@ if __name__ == '__main__':
463
  tokenInput: document.getElementById('tokenInput'),
464
  loginButton: document.getElementById('loginButton'),
465
  logoutButton: document.getElementById('logoutButton'),
 
466
  currentUser: document.getElementById('currentUser'),
467
  cardsContainer: document.getElementById('cardsContainer'),
468
  loadingIndicator: document.getElementById('loadingIndicator'),
@@ -619,63 +815,54 @@ if __name__ == '__main__':
619
  }
620
  }
621
 
622
- // URL ๋ชฉ๋ก ๋กœ๋“œ
623
- async function loadUrls() {
624
  setLoading(true);
625
 
626
  try {
627
- const response = await fetch('/api/urls');
628
- const urls = await handleApiResponse(response);
629
-
630
- // URL ๋ฐ ์ข‹์•„์š” ์ƒํƒœ ์ €์žฅ
631
- state.allURLs = urls;
632
 
633
- // ํ•„ํ„ฐ๋ง ๋ฐ ๋ Œ๋”๋ง
634
- filterAndRenderCards();
635
 
636
- // ์ข‹์•„์š” ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ
637
- updateLikeStats();
 
 
 
 
 
 
638
  } catch (error) {
639
- console.error('URL ๋ชฉ๋ก ๋กœ๋“œ ์˜ค๋ฅ˜:', error);
640
- showMessage(`URL ๋กœ๋“œ ์˜ค๋ฅ˜: ${error.message}`, true);
641
  } finally {
642
  setLoading(false);
643
  }
644
  }
645
 
646
- // ํ•„ํ„ฐ๋ง ๋ฐ ์นด๋“œ ๋ Œ๋”๋ง
647
- function filterAndRenderCards() {
648
- const searchText = elements.searchInput.value.toLowerCase();
649
 
650
- // ํ•„ํ„ฐ๋ง ์ ์šฉ
651
- const filteredUrls = state.allURLs.filter(item => {
652
- const { url, title, is_liked } = item;
653
-
654
- // ์ข‹์•„์š” ํ•„ํ„ฐ๋ง (์ข‹์•„์š”๋งŒ ๋ณด๊ธฐ ๋ชจ๋“œ)
655
- if (state.viewMode === 'liked' && !is_liked) {
656
- return false;
657
- }
658
-
659
- // ๊ฒ€์ƒ‰ ํ•„ํ„ฐ๋ง
660
- if (searchText && !url.toLowerCase().includes(searchText) && !title.toLowerCase().includes(searchText)) {
661
- return false;
662
- }
663
 
664
- return true;
665
- });
666
-
667
- renderCards(filteredUrls);
 
 
 
 
 
668
  }
669
 
670
  // ์ข‹์•„์š” ํ† ๊ธ€
671
- async function toggleLike(url, card) {
672
- if (!state.username) {
673
- showMessage('์ข‹์•„์š”๋ฅผ ํ•˜๋ ค๋ฉด ํ—ˆ๊น…ํŽ˜์ด์Šค API ํ† ํฐ์œผ๋กœ ์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.', true);
674
- return;
675
- }
676
-
677
- setLoading(true);
678
-
679
  try {
680
  const response = await fetch('/api/toggle-like', {
681
  method: 'POST',
@@ -688,164 +875,152 @@ if __name__ == '__main__':
688
  const data = await handleApiResponse(response);
689
 
690
  if (data.success) {
691
- // ์ƒํƒœ ๊ฐ์ฒด์—์„œ URL ์ฐพ๊ธฐ
692
- const urlItem = state.allURLs.find(item => item.url === url);
693
- if (urlItem) {
694
- // ์ข‹์•„์š” ์ƒํƒœ ์—…๋ฐ์ดํŠธ
695
- urlItem.is_liked = data.is_liked;
696
-
697
- // ์นด๋“œ UI ์—…๋ฐ์ดํŠธ
698
- if (data.is_liked) {
699
- card.classList.add('liked');
700
- const likeBtn = card.querySelector('.like-button');
701
- if (likeBtn) likeBtn.classList.add('liked');
702
-
703
- // ์ข‹์•„์š” ๋ฐฐ์ง€ ์ถ”๊ฐ€
704
- if (!card.querySelector('.like-badge')) {
705
- const likeBadge = document.createElement('div');
706
- likeBadge.className = 'like-badge';
707
- likeBadge.textContent = '์ข‹์•„์š”';
708
- card.appendChild(likeBadge);
709
- }
710
- } else {
711
- card.classList.remove('liked');
712
- const likeBtn = card.querySelector('.like-button');
713
- if (likeBtn) likeBtn.classList.remove('liked');
714
-
715
- // ์ข‹์•„์š” ๋ฐฐ์ง€ ์ œ๊ฑฐ
716
- const likeBadge = card.querySelector('.like-badge');
717
- if (likeBadge) card.removeChild(likeBadge);
718
- }
719
  }
720
 
721
  showMessage(data.message);
722
-
723
- // ์ข‹์•„์š” ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ
724
- updateLikeStats();
725
-
726
- // ์ข‹์•„์š”๋งŒ ๋ณด๊ธฐ ๋ชจ๋“œ์ธ ๊ฒฝ์šฐ ๋ชฉ๋ก ๋‹ค์‹œ ํ•„ํ„ฐ๋ง
727
- if (state.viewMode === 'liked') {
728
- filterAndRenderCards();
729
- }
730
  } else {
731
- showMessage(data.message || '์ข‹์•„์š” ์ฒ˜๋ฆฌ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.', true);
732
  }
733
  } catch (error) {
734
  console.error('์ข‹์•„์š” ํ† ๊ธ€ ์˜ค๋ฅ˜:', error);
735
- showMessage(`์ข‹์•„์š” ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: ${error.message}`, true);
736
- } finally {
737
- setLoading(false);
738
  }
739
  }
740
 
741
  // ์นด๋“œ ๋ Œ๋”๋ง
742
- function renderCards(urls) {
743
  elements.cardsContainer.innerHTML = '';
744
 
745
- if (!urls || urls.length === 0) {
746
- const noResultsMsg = document.createElement('p');
747
- noResultsMsg.textContent = 'ํ‘œ์‹œํ•  URL์ด ์—†์Šต๋‹ˆ๋‹ค.';
748
- noResultsMsg.style.padding = '1rem';
749
- noResultsMsg.style.fontStyle = 'italic';
750
- elements.cardsContainer.appendChild(noResultsMsg);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
  return;
752
  }
753
 
754
- urls.forEach(item => {
755
- const { url, title, is_liked } = item;
756
-
757
- // ์นด๋“œ ์ƒ์„ฑ
758
  const card = document.createElement('div');
759
- card.className = `card ${is_liked ? 'liked' : ''}`;
760
-
761
- // ์นด๋“œ ํ—ค๋”
762
- const cardHeader = document.createElement('div');
763
- cardHeader.className = 'card-header';
764
 
765
- // ์ œ๋ชฉ
766
- const titleEl = document.createElement('h3');
767
- titleEl.className = 'card-title';
768
- titleEl.textContent = title;
769
- cardHeader.appendChild(titleEl);
770
-
771
- card.appendChild(cardHeader);
772
 
773
- // URL ๋งํฌ
774
- const linkEl = document.createElement('a');
775
- linkEl.href = url;
776
- linkEl.textContent = url;
777
- linkEl.target = '_blank';
778
- card.appendChild(linkEl);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
779
 
780
- // ์ข‹์•„์š” ๋ฒ„ํŠผ
781
- const likeBtn = document.createElement('button');
782
- likeBtn.className = `like-button ${is_liked ? 'liked' : ''}`;
783
- likeBtn.innerHTML = 'โ™ฅ';
784
- likeBtn.title = is_liked ? '์ข‹์•„์š” ์ทจ์†Œ' : '์ข‹์•„์š”';
785
 
786
- likeBtn.addEventListener('click', (e) => {
 
 
787
  e.preventDefault();
788
- toggleLike(url, card);
 
789
  });
790
-
791
- card.appendChild(likeBtn);
792
-
793
- // ์ข‹์•„์š” ๋ฐฐ์ง€ (์ข‹์•„์š” ์ƒํƒœ์ผ ๋•Œ๋งŒ)
794
- if (is_liked) {
795
- const likeBadge = document.createElement('div');
796
- likeBadge.className = 'like-badge';
797
- likeBadge.textContent = '์ข‹์•„์š”';
798
- card.appendChild(likeBadge);
799
- }
800
-
801
- // ์นด๋“œ ์ถ”๊ฐ€
802
- elements.cardsContainer.appendChild(card);
803
  });
804
  }
805
 
806
- // ๋ทฐ ๋ชจ๋“œ ๋ณ€๊ฒฝ
807
- function changeViewMode(mode) {
808
- state.viewMode = mode;
 
 
 
809
 
810
- // ๋ฒ„ํŠผ ํ™œ์„ฑํ™” ์ƒํƒœ ์—…๋ฐ์ดํŠธ
811
- elements.allUrlsBtn.classList.toggle('active', mode === 'all');
812
- elements.likedUrlsBtn.classList.toggle('active', mode === 'liked');
 
 
 
 
 
 
 
 
 
813
 
814
- // ์นด๋“œ ๋‹ค์‹œ ๋ Œ๋”๋ง
815
- filterAndRenderCards();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
816
  }
817
 
818
- // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์„ค์ •
819
- elements.loginButton.addEventListener('click', () => {
820
- login(elements.tokenInput.value);
821
- });
822
-
823
- elements.logoutButton.addEventListener('click', logout);
824
-
825
- // ์—”ํ„ฐ ํ‚ค๋กœ ๋กœ๊ทธ์ธ ๊ฐ€๋Šฅํ•˜๊ฒŒ
826
- elements.tokenInput.addEventListener('keypress', (event) => {
827
- if (event.key === 'Enter') {
828
- login(elements.tokenInput.value);
829
- }
830
- });
831
-
832
- // ๊ฒ€์ƒ‰ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
833
- elements.searchInput.addEventListener('input', () => {
834
- // ๋””๋ฐ”์šด์‹ฑ (์ž…๋ ฅ ์ง€์—ฐ ์ฒ˜๋ฆฌ)
835
- clearTimeout(state.searchTimeout);
836
- state.searchTimeout = setTimeout(filterAndRenderCards, 300);
837
- });
838
-
839
- // ํ•„ํ„ฐ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
840
- elements.allUrlsBtn.addEventListener('click', () => changeViewMode('all'));
841
- elements.likedUrlsBtn.addEventListener('click', () => changeViewMode('liked'));
842
 
843
- // ์ดˆ๊ธฐํ™”
844
- checkSessionStatus();
845
  </script>
846
  </body>
847
  </html>
848
- ''')
 
849
 
850
- # ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค์—์„œ๋Š” 7860 ํฌํŠธ ์‚ฌ์šฉ
851
- app.run(host='0.0.0.0', port=7860)
 
1
  from flask import Flask, render_template, request, jsonify, session
2
+ import requests
3
+ from bs4 import BeautifulSoup
4
  import os
5
  from datetime import timedelta
6
+ import logging
7
+ import time
8
+
9
+ # ๋กœ๊น… ์„ค์ •
10
+ logging.basicConfig(level=logging.INFO,
11
+ format='%(asctime)s - %(levelname)s - %(message)s')
12
+ logger = logging.getLogger(__name__)
13
 
14
  app = Flask(__name__)
15
  app.secret_key = os.urandom(24)
16
  app.permanent_session_lifetime = timedelta(days=7)
17
 
18
+ # Hugging Face URL ๋ชฉ๋ก
 
19
  HUGGINGFACE_URLS = [
20
+ "https://huggingface.co/spaces/ginipick/Tech_Hangman_Game",
21
+ "https://huggingface.co/spaces/openfree/deepseek_r1_API",
22
+ "https://huggingface.co/spaces/ginipick/open_Deep-Research",
23
+ "https://huggingface.co/spaces/aiqmaster/open-deep-research",
24
+ "https://huggingface.co/spaces/seawolf2357/DeepSeek-R1-32b-search",
25
+ "https://huggingface.co/spaces/ginigen/LLaDA",
26
+ "https://huggingface.co/spaces/VIDraft/PHI4-Multimodal",
27
+ "https://huggingface.co/spaces/ginigen/Ovis2-8B",
28
+ "https://huggingface.co/spaces/ginigen/Graph-Mind",
29
+ "https://huggingface.co/spaces/ginigen/Workflow-Canvas",
30
+ "https://huggingface.co/spaces/ginigen/Design",
31
+ "https://huggingface.co/spaces/ginigen/Diagram",
32
+ "https://huggingface.co/spaces/ginigen/Mockup",
33
+ "https://huggingface.co/spaces/ginigen/Infographic",
34
+ "https://huggingface.co/spaces/ginigen/Flowchart",
35
+ "https://huggingface.co/spaces/aiqcamp/FLUX-Vision",
36
+ "https://huggingface.co/spaces/ginigen/VoiceClone-TTS",
37
+ "https://huggingface.co/spaces/openfree/Perceptron-Network",
38
+ "https://huggingface.co/spaces/openfree/Article-Generator",
39
  ]
40
 
41
+ # URL์—์„œ ๋ชจ๋ธ/์ŠคํŽ˜์ด์Šค ์ •๋ณด ์ถ”์ถœ
42
+ def extract_model_info(url):
43
+ parts = url.split('/')
44
+ if len(parts) < 6:
45
+ return None
46
+
47
+ if parts[3] == 'spaces' or parts[3] == 'models':
48
+ return {
49
+ 'type': parts[3],
50
+ 'owner': parts[4],
51
+ 'repo': parts[5],
52
+ 'full_id': f"{parts[4]}/{parts[5]}"
53
+ }
54
+ elif len(parts) >= 5:
55
+ return {
56
+ 'type': 'models',
57
+ 'owner': parts[3],
58
+ 'repo': parts[4],
59
+ 'full_id': f"{parts[3]}/{parts[4]}"
60
+ }
61
+
62
+ return None
63
+
64
  # URL์˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์„ ์ œ๋ชฉ์œผ๋กœ ์ถ”์ถœ
65
  def extract_title(url):
66
  parts = url.split("/")
67
  title = parts[-1] if parts else ""
68
  return title.replace("_", " ").replace("-", " ")
69
 
70
+ # ํ—ˆ๊น…ํŽ˜์ด์Šค ํ† ํฐ ๊ฒ€์ฆ
71
+ def validate_token(token):
72
+ headers = {"Authorization": f"Bearer {token}"}
73
+
74
+ try:
75
+ response = requests.get("https://huggingface.co/api/whoami-v2", headers=headers)
76
+ if response.ok:
77
+ return True, response.json()
78
+ except Exception as e:
79
+ logger.error(f"ํ† ํฐ ๊ฒ€์ฆ ์˜ค๋ฅ˜: {e}")
80
+
81
+ return False, None
82
+
83
+ # ์›น ์Šคํฌ๋ž˜ํ•‘์œผ๋กœ ์ข‹์•„์š” ์ƒํƒœ ํ™•์ธ
84
+ def check_like_status_by_scraping(url, token):
85
+ headers = {
86
+ "Authorization": f"Bearer {token}",
87
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
88
+ }
89
+
90
+ try:
91
+ # ํŽ˜์ด์ง€ ์š”์ฒญ
92
+ response = requests.get(url, headers=headers)
93
+ if not response.ok:
94
+ logger.warning(f"ํŽ˜์ด์ง€ ์š”์ฒญ ์‹คํŒจ: {url}, ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}")
95
+ return False
96
+
97
+ # HTML ํŒŒ์‹ฑ
98
+ soup = BeautifulSoup(response.text, 'html.parser')
99
+
100
+ # ์ข‹์•„์š” ๋ฒ„ํŠผ ์ฐพ๊ธฐ (๋‹ค์–‘ํ•œ ์„ ํƒ์ž ์‹œ๋„)
101
+ like_button = None
102
+ selectors = [
103
+ '.like-button-container button',
104
+ 'button[aria-label="Like"]',
105
+ 'button.like-button',
106
+ 'button[data-testid="like-button"]',
107
+ 'button.heart-button'
108
+ ]
109
+
110
+ for selector in selectors:
111
+ like_button = soup.select_one(selector)
112
+ if like_button:
113
+ break
114
+
115
+ if not like_button:
116
+ logger.warning(f"์ข‹์•„์š” ๋ฒ„ํŠผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ: {url}")
117
+ return False
118
+
119
+ # ์ข‹์•„์š” ์ƒํƒœ ํ™•์ธ (ํด๋ž˜์Šค, aria-pressed ์†์„ฑ ๋“ฑ์œผ๋กœ ํ™•์ธ)
120
+ is_liked = False
121
+
122
+ # ํด๋ž˜์Šค๋กœ ํ™•์ธ
123
+ if 'liked' in like_button.get('class', []) or 'active' in like_button.get('class', []):
124
+ is_liked = True
125
+
126
+ # aria-pressed ์†์„ฑ์œผ๋กœ ํ™•์ธ
127
+ elif like_button.get('aria-pressed') == 'true':
128
+ is_liked = True
129
+
130
+ # ๋‚ด๋ถ€ ํ…์ŠคํŠธ ๋˜๋Š” ์•„์ด์ฝ˜์œผ๋กœ ํ™•์ธ
131
+ elif like_button.find('span', class_='liked') or like_button.find('svg', class_='liked'):
132
+ is_liked = True
133
+
134
+ logger.info(f"์Šคํฌ๋ž˜ํ•‘ ๊ฒฐ๊ณผ: {url} - ์ข‹์•„์š” {is_liked}")
135
+ return is_liked
136
+
137
+ except Exception as e:
138
+ logger.error(f"์Šคํฌ๋ž˜ํ•‘ ์˜ค๋ฅ˜ ({url}): {e}")
139
+ return False
140
+
141
+ # ์ „์ฒด URL ๋ชฉ๋ก์˜ ์ข‹์•„์š” ์ƒํƒœ ์Šคํฌ๋ž˜ํ•‘
142
+ def scrape_all_like_status(token):
143
+ like_status = {}
144
+
145
+ for url in HUGGINGFACE_URLS:
146
+ try:
147
+ # ๊ณผ๋„ํ•œ ์š”์ฒญ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ์ง€์—ฐ
148
+ time.sleep(1)
149
+
150
+ is_liked = check_like_status_by_scraping(url, token)
151
+ like_status[url] = is_liked
152
+
153
+ logger.info(f"์ข‹์•„์š” ์ƒํƒœ ํ™•์ธ: {url} - {is_liked}")
154
+ except Exception as e:
155
+ logger.error(f"URL ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜: {url} - {e}")
156
+ like_status[url] = False
157
+
158
+ return like_status
159
+
160
  @app.route('/')
161
  def home():
162
  return render_template('index.html')
 
168
  if not token:
169
  return jsonify({'success': False, 'message': 'ํ† ํฐ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.'})
170
 
171
+ is_valid, user_info = validate_token(token)
172
+
173
+ if not is_valid or not user_info:
174
  return jsonify({'success': False, 'message': '์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ž…๋‹ˆ๋‹ค.'})
175
 
176
+ # ์‚ฌ์šฉ์ž ์ด๋ฆ„ ์ฐพ๊ธฐ
177
+ username = None
178
+ if 'name' in user_info:
179
+ username = user_info['name']
180
+ elif 'user' in user_info and 'username' in user_info['user']:
181
+ username = user_info['user']['username']
182
+ elif 'username' in user_info:
183
+ username = user_info['username']
184
+ else:
185
+ username = '์ธ์ฆ๋œ ์‚ฌ์šฉ์ž'
186
 
187
  # ์„ธ์…˜์— ์ €์žฅ
188
+ session['token'] = token
189
  session['username'] = username
190
 
191
+ # ์›น ์Šคํฌ๋ž˜ํ•‘์œผ๋กœ ์ข‹์•„์š” ์ƒํƒœ ํ™•์ธ
192
+ # ์ฐธ๊ณ : ์ด ์ž‘์—…์ด ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค
193
+ # ํ˜„์žฌ๋Š” ์˜ˆ์‹œ๋กœ ๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค
194
+ try:
195
+ like_status = scrape_all_like_status(token)
196
+ session['like_status'] = like_status
197
+ except Exception as e:
198
+ logger.error(f"์ข‹์•„์š” ์ƒํƒœ ์Šคํฌ๋ž˜ํ•‘ ์ค‘ ์˜ค๋ฅ˜: {e}")
199
+ session['like_status'] = {}
200
+
201
  return jsonify({
202
  'success': True,
203
  'username': username
 
205
 
206
  @app.route('/api/logout', methods=['POST'])
207
  def logout():
208
+ session.pop('token', None)
209
  session.pop('username', None)
210
+ session.pop('like_status', None)
211
  return jsonify({'success': True})
212
 
213
  @app.route('/api/urls', methods=['GET'])
214
  def get_urls():
215
+ like_status = session.get('like_status', {})
216
+
217
  results = []
218
+ for url in HUGGINGFACE_URLS:
 
 
219
  title = extract_title(url)
220
+ model_info = extract_model_info(url)
221
+
222
+ if not model_info:
223
+ continue
224
+
225
+ # ์ข‹์•„์š” ์ƒํƒœ ํ™•์ธ
226
+ is_liked = like_status.get(url, False)
227
 
228
  results.append({
229
  'url': url,
230
  'title': title,
231
+ 'model_info': model_info,
232
  'is_liked': is_liked
233
  })
234
 
 
236
 
237
  @app.route('/api/toggle-like', methods=['POST'])
238
  def toggle_like():
239
+ if 'token' not in session:
240
  return jsonify({'success': False, 'message': '๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'})
241
 
242
  data = request.json
 
245
  if not url:
246
  return jsonify({'success': False, 'message': 'URL์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'})
247
 
248
+ # ํ˜„์žฌ ์ข‹์•„์š” ์ƒํƒœ ํ™•์ธ
249
+ like_status = session.get('like_status', {})
250
+ current_status = like_status.get(url, False)
251
+
252
+ # ์‹ค์ œ๋กœ๋Š” ์—ฌ๊ธฐ์„œ ํ—ˆ๊น…ํŽ˜์ด์Šค API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ข‹์•„์š” ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
253
+ # ํ˜„์žฌ๋Š” ์˜ˆ์‹œ๋กœ ์„ธ์…˜์—๋งŒ ์ƒํƒœ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค
254
+ like_status[url] = not current_status
255
+ session['like_status'] = like_status
 
 
 
256
 
257
+ return jsonify({
258
+ 'success': True,
259
+ 'is_liked': like_status[url],
260
+ 'message': '์ข‹์•„์š”๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.' if like_status[url] else '์ข‹์•„์š”๋ฅผ ์ทจ์†Œํ–ˆ์Šต๋‹ˆ๋‹ค.'
261
+ })
262
+
263
+ @app.route('/api/refresh-likes', methods=['POST'])
264
+ def refresh_likes():
265
+ if 'token' not in session:
266
+ return jsonify({'success': False, 'message': '๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'})
267
+
268
+ try:
269
+ # ์›น ์Šคํฌ๋ž˜ํ•‘์œผ๋กœ ์ข‹์•„์š” ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ
270
+ like_status = scrape_all_like_status(session['token'])
271
+ session['like_status'] = like_status
272
+
273
+ return jsonify({
274
+ 'success': True,
275
+ 'message': '์ข‹์•„์š” ์ƒํƒœ๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.',
276
+ 'like_status': like_status
277
+ })
278
+ except Exception as e:
279
+ logger.error(f"์ข‹์•„์š” ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ ์ค‘ ์˜ค๋ฅ˜: {e}")
280
+ return jsonify({
281
+ 'success': False,
282
+ 'message': f'์ข‹์•„์š” ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ ์ค‘ ์˜ค๋ฅ˜: {str(e)}'
283
+ })
284
 
285
  @app.route('/api/session-status', methods=['GET'])
286
  def session_status():
 
364
  background-color: #45a049;
365
  }
366
 
367
+ button.refresh {
368
+ background-color: #2196F3;
369
+ }
370
+
371
+ button.refresh:hover {
372
+ background-color: #0b7dda;
373
+ }
374
+
375
  button.logout {
376
  background-color: #f44336;
377
  }
 
569
  margin-top: 1rem;
570
  }
571
 
572
+ .note {
573
+ padding: 0.5rem;
574
+ background-color: #fffde7;
575
+ border-left: 3px solid #ffd600;
576
+ margin-bottom: 1rem;
577
+ font-size: 0.9rem;
578
+ }
579
+
580
  @media (max-width: 768px) {
581
  .user-controls {
582
  flex-direction: column;
 
619
  </div>
620
 
621
  <div id="loggedInSection" class="logged-in-section">
622
+ <button id="refreshButton" class="refresh">์ƒˆ๋กœ๊ณ ์นจ</button>
623
  <button id="logoutButton" class="logout">๋กœ๊ทธ์•„์›ƒ</button>
624
  </div>
625
  </div>
626
  </div>
627
 
628
+ <div class="note">
629
+ <p><strong>์ฐธ๊ณ :</strong> ์ด ํŽ˜์ด์ง€๋Š” ์›น ์Šคํฌ๋ž˜ํ•‘ ๋ฐฉ์‹์œผ๋กœ ์ข‹์•„์š” ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์ข‹์•„์š” ์ƒํƒœ๊ฐ€ ์ •ํ™•ํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ์ง€์—ฐ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. '์ƒˆ๋กœ๊ณ ์นจ' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ ์ตœ์‹  ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.</p>
630
+ </div>
631
+
632
  <div id="likeStatus" class="like-status">
633
  <div id="likeStatsText">์ด <span id="totalUrlCount">0</span>๊ฐœ ์ค‘ <strong><span id="likedUrlCount">0</span>๊ฐœ</strong>์˜ URL์„ ์ข‹์•„์š” ํ–ˆ์Šต๋‹ˆ๋‹ค.</div>
634
  </div>
 
658
  tokenInput: document.getElementById('tokenInput'),
659
  loginButton: document.getElementById('loginButton'),
660
  logoutButton: document.getElementById('logoutButton'),
661
+ refreshButton: document.getElementById('refreshButton'),
662
  currentUser: document.getElementById('currentUser'),
663
  cardsContainer: document.getElementById('cardsContainer'),
664
  loadingIndicator: document.getElementById('loadingIndicator'),
 
815
  }
816
  }
817
 
818
+ // ์ข‹์•„์š” ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ
819
+ async function refreshLikes() {
820
  setLoading(true);
821
 
822
  try {
823
+ const response = await fetch('/api/refresh-likes', {
824
+ method: 'POST'
825
+ });
 
 
826
 
827
+ const data = await handleApiResponse(response);
 
828
 
829
+
830
+ if (data.success) {
831
+ // URL ๋ชฉ๋ก ๋‹ค์‹œ ๋กœ๋“œ
832
+ loadUrls();
833
+ showMessage('์ข‹์•„์š” ์ƒํƒœ๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
834
+ } else {
835
+ showMessage(data.message || '์ข‹์•„์š” ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.', true);
836
+ }
837
  } catch (error) {
838
+ console.error('์ข‹์•„์š” ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ ์˜ค๋ฅ˜:', error);
839
+ showMessage(`์ข‹์•„์š” ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ ์˜ค๋ฅ˜: ${error.message}`, true);
840
  } finally {
841
  setLoading(false);
842
  }
843
  }
844
 
845
+ // URL ๋ชฉ๋ก ๋กœ๋“œ
846
+ async function loadUrls() {
847
+ setLoading(true);
848
 
849
+ try {
850
+ const response = await fetch('/api/urls');
851
+ const data = await handleApiResponse(response);
 
 
 
 
 
 
 
 
 
 
852
 
853
+ state.allURLs = data;
854
+ updateLikeStats();
855
+ renderCards();
856
+ } catch (error) {
857
+ console.error('URL ๋ชฉ๋ก ๋กœ๋“œ ์˜ค๋ฅ˜:', error);
858
+ showMessage(`URL ๋ชฉ๋ก ๋กœ๋“œ ์˜ค๋ฅ˜: ${error.message}`, true);
859
+ } finally {
860
+ setLoading(false);
861
+ }
862
  }
863
 
864
  // ์ข‹์•„์š” ํ† ๊ธ€
865
+ async function toggleLike(url) {
 
 
 
 
 
 
 
866
  try {
867
  const response = await fetch('/api/toggle-like', {
868
  method: 'POST',
 
875
  const data = await handleApiResponse(response);
876
 
877
  if (data.success) {
878
+ // URL ๊ฐ์ฒด ์ฐพ๊ธฐ
879
+ const urlObj = state.allURLs.find(item => item.url === url);
880
+ if (urlObj) {
881
+ urlObj.is_liked = data.is_liked;
882
+ updateLikeStats();
883
+ renderCards();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
884
  }
885
 
886
  showMessage(data.message);
 
 
 
 
 
 
 
 
887
  } else {
888
+ showMessage(data.message || '์ข‹์•„์š” ์ƒํƒœ ๋ณ€๊ฒฝ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.', true);
889
  }
890
  } catch (error) {
891
  console.error('์ข‹์•„์š” ํ† ๊ธ€ ์˜ค๋ฅ˜:', error);
892
+ showMessage(`์ข‹์•„์š” ํ† ๊ธ€ ์˜ค๋ฅ˜: ${error.message}`, true);
 
 
893
  }
894
  }
895
 
896
  // ์นด๋“œ ๋ Œ๋”๋ง
897
+ function renderCards() {
898
  elements.cardsContainer.innerHTML = '';
899
 
900
+ let urlsToShow = state.allURLs;
901
+
902
+ // ๊ฒ€์ƒ‰์–ด๋กœ ํ•„ํ„ฐ๋ง
903
+ const searchTerm = elements.searchInput.value.trim().toLowerCase();
904
+ if (searchTerm) {
905
+ urlsToShow = urlsToShow.filter(item =>
906
+ item.url.toLowerCase().includes(searchTerm) ||
907
+ item.title.toLowerCase().includes(searchTerm)
908
+ );
909
+ }
910
+
911
+ // ๋ณด๊ธฐ ๋ชจ๋“œ๋กœ ํ•„ํ„ฐ๋ง (์ „์ฒด ๋˜๋Š” ์ข‹์•„์š”๋งŒ)
912
+ if (state.viewMode === 'liked') {
913
+ urlsToShow = urlsToShow.filter(item => item.is_liked);
914
+ }
915
+
916
+ if (urlsToShow.length === 0) {
917
+ const emptyMessage = document.createElement('div');
918
+ emptyMessage.textContent = 'ํ‘œ์‹œํ•  URL์ด ์—†์Šต๋‹ˆ๋‹ค.';
919
+ emptyMessage.style.padding = '1rem';
920
+ emptyMessage.style.width = '100%';
921
+ emptyMessage.style.textAlign = 'center';
922
+ elements.cardsContainer.appendChild(emptyMessage);
923
  return;
924
  }
925
 
926
+ // ์นด๋“œ ์ƒ์„ฑ
927
+ urlsToShow.forEach(item => {
 
 
928
  const card = document.createElement('div');
929
+ card.className = `card ${item.is_liked ? 'liked' : ''}`;
 
 
 
 
930
 
931
+ if (item.is_liked) {
932
+ const badge = document.createElement('div');
933
+ badge.className = 'like-badge';
934
+ badge.textContent = '์ข‹์•„์š”';
935
+ card.appendChild(badge);
936
+ }
 
937
 
938
+ const cardContent = `
939
+ <div class="card-header">
940
+ <h3 class="card-title">${item.title}</h3>
941
+ <button class="like-button ${item.is_liked ? 'liked' : ''}" data-url="${item.url}">
942
+ โค
943
+ </button>
944
+ </div>
945
+ <div class="card-body">
946
+ <a href="${item.url}" target="_blank">${item.url}</a>
947
+ <div style="margin-top: 0.5rem;">
948
+ <span>์†Œ์œ ์ž: ${item.model_info.owner}</span>
949
+ </div>
950
+ <div>
951
+ <span>์ €์žฅ์†Œ: ${item.model_info.repo}</span>
952
+ </div>
953
+ <div>
954
+ <span>์œ ํ˜•: ${item.model_info.type}</span>
955
+ </div>
956
+ </div>
957
+ `;
958
 
959
+ card.innerHTML = cardContent;
960
+ elements.cardsContainer.appendChild(card);
 
 
 
961
 
962
+ // ์ข‹์•„์š” ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์ถ”๊ฐ€
963
+ const likeButton = card.querySelector('.like-button');
964
+ likeButton.addEventListener('click', (e) => {
965
  e.preventDefault();
966
+ const url = e.target.dataset.url;
967
+ toggleLike(url);
968
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
969
  });
970
  }
971
 
972
+ // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก
973
+ function registerEventListeners() {
974
+ // ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ
975
+ elements.loginButton.addEventListener('click', () => {
976
+ login(elements.tokenInput.value);
977
+ });
978
 
979
+ // ์—”ํ„ฐ ํ‚ค๋กœ ๋กœ๊ทธ์ธ
980
+ elements.tokenInput.addEventListener('keydown', (e) => {
981
+ if (e.key === 'Enter') {
982
+ login(elements.tokenInput.value);
983
+ }
984
+ });
985
+
986
+ // ๋กœ๊ทธ์•„์›ƒ ๋ฒ„ํŠผ
987
+ elements.logoutButton.addEventListener('click', logout);
988
+
989
+ // ์ƒˆ๋กœ๊ณ ์นจ ๋ฒ„ํŠผ
990
+ elements.refreshButton.addEventListener('click', refreshLikes);
991
 
992
+ // ๊ฒ€์ƒ‰ ์ž…๋ ฅ ํ•„๋“œ
993
+ elements.searchInput.addEventListener('input', renderCards);
994
+
995
+ // ํ•„ํ„ฐ ๋ฒ„ํŠผ - ์ „์ฒด ๋ณด๊ธฐ
996
+ elements.allUrlsBtn.addEventListener('click', () => {
997
+ elements.allUrlsBtn.classList.add('active');
998
+ elements.likedUrlsBtn.classList.remove('active');
999
+ state.viewMode = 'all';
1000
+ renderCards();
1001
+ });
1002
+
1003
+ // ํ•„ํ„ฐ ๋ฒ„ํŠผ - ์ข‹์•„์š”๋งŒ ๋ณด๊ธฐ
1004
+ elements.likedUrlsBtn.addEventListener('click', () => {
1005
+ elements.likedUrlsBtn.classList.add('active');
1006
+ elements.allUrlsBtn.classList.remove('active');
1007
+ state.viewMode = 'liked';
1008
+ renderCards();
1009
+ });
1010
  }
1011
 
1012
+ // ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜
1013
+ function init() {
1014
+ registerEventListeners();
1015
+ checkSessionStatus();
1016
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1017
 
1018
+ // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ดˆ๊ธฐํ™”
1019
+ init();
1020
  </script>
1021
  </body>
1022
  </html>
1023
+ '''
1024
+ )
1025
 
1026
+ app.run(debug=True, host='0.0.0.0', port=5000)