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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +80 -184
app.py CHANGED
@@ -1,78 +1,41 @@
1
  from flask import Flask, render_template, request, jsonify, session
2
- import requests
3
  import os
4
  from datetime import timedelta
5
- import json
6
 
7
  app = Flask(__name__)
8
  app.secret_key = os.urandom(24)
9
  app.permanent_session_lifetime = timedelta(days=7)
10
 
11
- # Hugging Face URL ๋ชฉ๋ก
 
12
  HUGGINGFACE_URLS = [
13
- "https://huggingface.co/spaces/ginipick/Tech_Hangman_Game",
14
- "https://huggingface.co/spaces/openfree/deepseek_r1_API",
15
- "https://huggingface.co/spaces/ginipick/open_Deep-Research",
16
- "https://huggingface.co/spaces/aiqmaster/open-deep-research",
17
- "https://huggingface.co/spaces/seawolf2357/DeepSeek-R1-32b-search",
18
- "https://huggingface.co/spaces/ginigen/LLaDA",
19
- "https://huggingface.co/spaces/VIDraft/PHI4-Multimodal",
20
- "https://huggingface.co/spaces/ginigen/Ovis2-8B",
21
- "https://huggingface.co/spaces/ginigen/Graph-Mind",
22
- "https://huggingface.co/spaces/ginigen/Workflow-Canvas",
23
- "https://huggingface.co/spaces/ginigen/Design",
24
- "https://huggingface.co/spaces/ginigen/Diagram",
25
- "https://huggingface.co/spaces/ginigen/Mockup",
26
- "https://huggingface.co/spaces/ginigen/Infographic",
27
- "https://huggingface.co/spaces/ginigen/Flowchart",
28
- "https://huggingface.co/spaces/aiqcamp/FLUX-Vision",
29
- "https://huggingface.co/spaces/ginigen/VoiceClone-TTS",
30
- "https://huggingface.co/spaces/openfree/Perceptron-Network",
31
- "https://huggingface.co/spaces/openfree/Article-Generator",
32
  ]
33
 
34
- # URL์—์„œ ๋ชจ๋ธ/์ŠคํŽ˜์ด์Šค ์ •๋ณด ์ถ”์ถœ
35
- def extract_model_info(url):
36
- parts = url.split('/')
37
- if len(parts) < 6:
38
- return None
39
-
40
- if parts[3] == 'spaces' or parts[3] == 'models':
41
- return {
42
- 'type': parts[3],
43
- 'owner': parts[4],
44
- 'repo': parts[5],
45
- 'full_id': f"{parts[4]}/{parts[5]}"
46
- }
47
- elif len(parts) >= 5:
48
- return {
49
- 'type': 'models',
50
- 'owner': parts[3],
51
- 'repo': parts[4],
52
- 'full_id': f"{parts[3]}/{parts[4]}"
53
- }
54
-
55
- return None
56
-
57
  # URL์˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์„ ์ œ๋ชฉ์œผ๋กœ ์ถ”์ถœ
58
  def extract_title(url):
59
  parts = url.split("/")
60
  title = parts[-1] if parts else ""
61
  return title.replace("_", " ").replace("-", " ")
62
 
63
- # ํ—ˆ๊น…ํŽ˜์ด์Šค ์‚ฌ์šฉ์ž ์ธ์ฆ
64
- def validate_token(token):
65
- headers = {"Authorization": f"Bearer {token}"}
66
-
67
- try:
68
- response = requests.get("https://huggingface.co/api/whoami-v2", headers=headers)
69
- if response.ok:
70
- return True, response.json()
71
- except Exception as e:
72
- print(f"ํ† ํฐ ๊ฒ€์ฆ ์˜ค๋ฅ˜: {e}")
73
-
74
- return False, None
75
-
76
  @app.route('/')
77
  def home():
78
  return render_template('index.html')
@@ -84,30 +47,16 @@ def login():
84
  if not token:
85
  return jsonify({'success': False, 'message': 'ํ† ํฐ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.'})
86
 
87
- is_valid, user_info = validate_token(token)
88
-
89
- if not is_valid or not user_info:
90
  return jsonify({'success': False, 'message': '์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ž…๋‹ˆ๋‹ค.'})
91
 
92
- # ์‚ฌ์šฉ์ž ์ด๋ฆ„ ์ฐพ๊ธฐ
93
- username = None
94
- if 'name' in user_info:
95
- username = user_info['name']
96
- elif 'user' in user_info and 'username' in user_info['user']:
97
- username = user_info['user']['username']
98
- elif 'username' in user_info:
99
- username = user_info['username']
100
- else:
101
- username = '์ธ์ฆ๋œ ์‚ฌ์šฉ์ž'
102
 
103
  # ์„ธ์…˜์— ์ €์žฅ
104
- session['token'] = token
105
  session['username'] = username
106
 
107
- # ์‚ฌ์šฉ์ž๊ฐ€ ์ข‹์•„์š”ํ•œ URL ์ƒํƒœ๋ฅผ ์„ธ์…˜์— ์ดˆ๊ธฐํ™”
108
- if 'liked_urls' not in session:
109
- session['liked_urls'] = {}
110
-
111
  return jsonify({
112
  'success': True,
113
  'username': username
@@ -115,30 +64,20 @@ def login():
115
 
116
  @app.route('/api/logout', methods=['POST'])
117
  def logout():
118
- session.pop('token', None)
119
  session.pop('username', None)
120
- session.pop('liked_urls', None)
121
  return jsonify({'success': True})
122
 
123
  @app.route('/api/urls', methods=['GET'])
124
  def get_urls():
125
  results = []
126
- for url in HUGGINGFACE_URLS:
 
 
127
  title = extract_title(url)
128
- model_info = extract_model_info(url)
129
-
130
- if not model_info:
131
- continue
132
-
133
- # ์‚ฌ์šฉ์ž์˜ ์ข‹์•„์š” ์ƒํƒœ ํ™•์ธ
134
- is_liked = False
135
- if 'liked_urls' in session and url in session['liked_urls']:
136
- is_liked = True
137
 
138
  results.append({
139
  'url': url,
140
  'title': title,
141
- 'model_info': model_info,
142
  'is_liked': is_liked
143
  })
144
 
@@ -155,38 +94,19 @@ def toggle_like():
155
  if not url:
156
  return jsonify({'success': False, 'message': 'URL์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'})
157
 
158
- # ์„ธ์…˜์—์„œ ์ข‹์•„์š” ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
159
- liked_urls = session.get('liked_urls', {})
160
-
161
- # ์ข‹์•„์š” ์ƒํƒœ ํ† ๊ธ€
162
- if url in liked_urls:
163
- del liked_urls[url]
164
- is_liked = False
165
- message = '์ข‹์•„์š”๋ฅผ ์ทจ์†Œํ–ˆ์Šต๋‹ˆ๋‹ค.'
166
- else:
167
- liked_urls[url] = True
168
- is_liked = True
169
- message = '์ข‹์•„์š”๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.'
170
-
171
- # ์„ธ์…˜์— ์ข‹์•„์š” ์ •๋ณด ์—…๋ฐ์ดํŠธ
172
- session['liked_urls'] = liked_urls
173
-
174
- return jsonify({
175
- 'success': True,
176
- 'is_liked': is_liked,
177
- 'message': message
178
- })
179
-
180
- @app.route('/api/get-likes', methods=['GET'])
181
- def get_likes():
182
- if 'username' not in session:
183
- return jsonify({'success': False, 'message': '๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'})
184
-
185
- liked_urls = session.get('liked_urls', {})
186
- return jsonify({
187
- 'success': True,
188
- 'liked_urls': liked_urls
189
- })
190
 
191
  @app.route('/api/session-status', methods=['GET'])
192
  def session_status():
@@ -560,7 +480,6 @@ if __name__ == '__main__':
560
  // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ
561
  const state = {
562
  username: null,
563
- likedURLs: {},
564
  allURLs: [],
565
  isLoading: false,
566
  viewMode: 'all' // 'all' ๋˜๋Š” 'liked'
@@ -596,7 +515,7 @@ if __name__ == '__main__':
596
  // ์ข‹์•„์š” ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ
597
  function updateLikeStats() {
598
  const totalCount = state.allURLs.length;
599
- const likedCount = Object.keys(state.likedURLs).length;
600
 
601
  elements.totalUrlCount.textContent = totalCount;
602
  elements.likedUrlCount.textContent = likedCount;
@@ -615,9 +534,6 @@ if __name__ == '__main__':
615
  elements.loggedInSection.style.display = 'block';
616
  elements.likeStatus.style.display = 'block';
617
 
618
- // ์ข‹์•„์š” ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
619
- await getLikedUrls();
620
-
621
  // URL ๋ชฉ๋ก ๋กœ๋“œ
622
  loadUrls();
623
  }
@@ -626,23 +542,6 @@ if __name__ == '__main__':
626
  }
627
  }
628
 
629
- // ์ข‹์•„์š” URL ๊ฐ€์ ธ์˜ค๊ธฐ
630
- async function getLikedUrls() {
631
- try {
632
- const response = await fetch('/api/get-likes');
633
- const data = await handleApiResponse(response);
634
-
635
- if (data.success) {
636
- state.likedURLs = data.liked_urls || {};
637
- return true;
638
- }
639
- } catch (error) {
640
- console.error('์ข‹์•„์š” ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ ์˜ค๋ฅ˜:', error);
641
- }
642
-
643
- return false;
644
- }
645
-
646
  // ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
647
  async function login(token) {
648
  if (!token.trim()) {
@@ -673,9 +572,6 @@ if __name__ == '__main__':
673
 
674
  showMessage(`${state.username}๋‹˜์œผ๋กœ ๋กœ๊ทธ์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`);
675
 
676
- // ์ข‹์•„์š” ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
677
- await getLikedUrls();
678
-
679
  // URL ๋ชฉ๋ก ๋กœ๋“œ
680
  loadUrls();
681
  } else {
@@ -702,7 +598,6 @@ if __name__ == '__main__':
702
 
703
  if (data.success) {
704
  state.username = null;
705
- state.likedURLs = {};
706
  state.allURLs = [];
707
 
708
  elements.currentUser.textContent = '๋กœ๊ทธ์ธ๋˜์ง€ ์•Š์Œ';
@@ -735,13 +630,6 @@ if __name__ == '__main__':
735
  // URL ๋ฐ ์ข‹์•„์š” ์ƒํƒœ ์ €์žฅ
736
  state.allURLs = urls;
737
 
738
- // ์„œ๋ฒ„์—์„œ ๋ฐ›์€ ์ข‹์•„์š” ์ƒํƒœ ์—…๋ฐ์ดํŠธ
739
- urls.forEach(item => {
740
- if (item.is_liked) {
741
- state.likedURLs[item.url] = true;
742
- }
743
- });
744
-
745
  // ํ•„ํ„ฐ๋ง ๋ฐ ๋ Œ๋”๋ง
746
  filterAndRenderCards();
747
 
@@ -761,10 +649,10 @@ if __name__ == '__main__':
761
 
762
  // ํ•„ํ„ฐ๋ง ์ ์šฉ
763
  const filteredUrls = state.allURLs.filter(item => {
764
- const { url, title } = item;
765
 
766
  // ์ข‹์•„์š” ํ•„ํ„ฐ๋ง (์ข‹์•„์š”๋งŒ ๋ณด๊ธฐ ๋ชจ๋“œ)
767
- if (state.viewMode === 'liked' && !state.likedURLs[url]) {
768
  return false;
769
  }
770
 
@@ -800,23 +688,34 @@ if __name__ == '__main__':
800
  const data = await handleApiResponse(response);
801
 
802
  if (data.success) {
803
- // ์ข‹์•„์š” ์ƒํƒœ ์—…๋ฐ์ดํŠธ
804
- if (data.is_liked) {
805
- state.likedURLs[url] = true;
806
- card.classList.add('liked');
807
- const likeBtn = card.querySelector('.like-button');
808
- if (likeBtn) likeBtn.classList.add('liked');
809
- const likeBadge = document.createElement('div');
810
- likeBadge.className = 'like-badge';
811
- likeBadge.textContent = '์ข‹์•„์š”';
812
- card.appendChild(likeBadge);
813
- } else {
814
- delete state.likedURLs[url];
815
- card.classList.remove('liked');
816
- const likeBtn = card.querySelector('.like-button');
817
- if (likeBtn) likeBtn.classList.remove('liked');
818
- const likeBadge = card.querySelector('.like-badge');
819
- if (likeBadge) card.removeChild(likeBadge);
 
 
 
 
 
 
 
 
 
 
 
820
  }
821
 
822
  showMessage(data.message);
@@ -853,12 +752,11 @@ if __name__ == '__main__':
853
  }
854
 
855
  urls.forEach(item => {
856
- const { url, title } = item;
857
- const isLiked = state.likedURLs[url] || false;
858
 
859
  // ์นด๋“œ ์ƒ์„ฑ
860
  const card = document.createElement('div');
861
- card.className = `card ${isLiked ? 'liked' : ''}`;
862
 
863
  // ์นด๋“œ ํ—ค๋”
864
  const cardHeader = document.createElement('div');
@@ -874,18 +772,16 @@ if __name__ == '__main__':
874
 
875
  // URL ๋งํฌ
876
  const linkEl = document.createElement('a');
877
-
878
-
879
- linkEl.href = url;
880
  linkEl.textContent = url;
881
  linkEl.target = '_blank';
882
  card.appendChild(linkEl);
883
 
884
  // ์ข‹์•„์š” ๋ฒ„ํŠผ
885
  const likeBtn = document.createElement('button');
886
- likeBtn.className = `like-button ${isLiked ? 'liked' : ''}`;
887
  likeBtn.innerHTML = 'โ™ฅ';
888
- likeBtn.title = isLiked ? '์ข‹์•„์š” ์ทจ์†Œ' : '์ข‹์•„์š”';
889
 
890
  likeBtn.addEventListener('click', (e) => {
891
  e.preventDefault();
@@ -895,7 +791,7 @@ linkEl.href = url;
895
  card.appendChild(likeBtn);
896
 
897
  // ์ข‹์•„์š” ๋ฐฐ์ง€ (์ข‹์•„์š” ์ƒํƒœ์ผ ๋•Œ๋งŒ)
898
- if (isLiked) {
899
  const likeBadge = document.createElement('div');
900
  likeBadge.className = 'like-badge';
901
  likeBadge.textContent = '์ข‹์•„์š”';
@@ -919,7 +815,7 @@ linkEl.href = url;
919
  filterAndRenderCards();
920
  }
921
 
922
- // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์„ค์ •
923
  elements.loginButton.addEventListener('click', () => {
924
  login(elements.tokenInput.value);
925
  });
@@ -952,4 +848,4 @@ linkEl.href = url;
952
  ''')
953
 
954
  # ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค์—์„œ๋Š” 7860 ํฌํŠธ ์‚ฌ์šฉ
955
- app.run(host='0.0.0.0', port=7860)
 
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
  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
 
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
 
 
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():
 
480
  // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ
481
  const state = {
482
  username: null,
 
483
  allURLs: [],
484
  isLoading: false,
485
  viewMode: 'all' // 'all' ๋˜๋Š” 'liked'
 
515
  // ์ข‹์•„์š” ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ
516
  function updateLikeStats() {
517
  const totalCount = state.allURLs.length;
518
+ const likedCount = state.allURLs.filter(item => item.is_liked).length;
519
 
520
  elements.totalUrlCount.textContent = totalCount;
521
  elements.likedUrlCount.textContent = likedCount;
 
534
  elements.loggedInSection.style.display = 'block';
535
  elements.likeStatus.style.display = 'block';
536
 
 
 
 
537
  // URL ๋ชฉ๋ก ๋กœ๋“œ
538
  loadUrls();
539
  }
 
542
  }
543
  }
544
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
545
  // ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
546
  async function login(token) {
547
  if (!token.trim()) {
 
572
 
573
  showMessage(`${state.username}๋‹˜์œผ๋กœ ๋กœ๊ทธ์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`);
574
 
 
 
 
575
  // URL ๋ชฉ๋ก ๋กœ๋“œ
576
  loadUrls();
577
  } else {
 
598
 
599
  if (data.success) {
600
  state.username = null;
 
601
  state.allURLs = [];
602
 
603
  elements.currentUser.textContent = '๋กœ๊ทธ์ธ๋˜์ง€ ์•Š์Œ';
 
630
  // URL ๋ฐ ์ข‹์•„์š” ์ƒํƒœ ์ €์žฅ
631
  state.allURLs = urls;
632
 
 
 
 
 
 
 
 
633
  // ํ•„ํ„ฐ๋ง ๋ฐ ๋ Œ๋”๋ง
634
  filterAndRenderCards();
635
 
 
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
 
 
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);
 
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');
 
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();
 
791
  card.appendChild(likeBtn);
792
 
793
  // ์ข‹์•„์š” ๋ฐฐ์ง€ (์ข‹์•„์š” ์ƒํƒœ์ผ ๋•Œ๋งŒ)
794
+ if (is_liked) {
795
  const likeBadge = document.createElement('div');
796
  likeBadge.className = 'like-badge';
797
  likeBadge.textContent = '์ข‹์•„์š”';
 
815
  filterAndRenderCards();
816
  }
817
 
818
+ // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์„ค์ •
819
  elements.loginButton.addEventListener('click', () => {
820
  login(elements.tokenInput.value);
821
  });
 
848
  ''')
849
 
850
  # ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค์—์„œ๋Š” 7860 ํฌํŠธ ์‚ฌ์šฉ
851
+ app.run(host='0.0.0.0', port=7860)