ginipick commited on
Commit
d5b5669
ยท
verified ยท
1 Parent(s): 682443b

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +403 -0
app.py ADDED
@@ -0,0 +1,403 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from huggingface_hub import InferenceClient, HfApi
3
+ import os
4
+ import requests
5
+ from typing import List, Dict, Union
6
+ import traceback
7
+ from PIL import Image
8
+ from io import BytesIO
9
+ import asyncio
10
+ from gradio_client import Client
11
+ import time
12
+ import threading
13
+ import json
14
+
15
+ HF_TOKEN = os.getenv("HF_TOKEN")
16
+ hf_client = InferenceClient("CohereForAI/c4ai-command-r-plus-08-2024", token=HF_TOKEN)
17
+ hf_api = HfApi(token=HF_TOKEN)
18
+
19
+ def get_headers():
20
+ if not HF_TOKEN:
21
+ raise ValueError("Hugging Face token not found in environment variables")
22
+ return {"Authorization": f"Bearer {HF_TOKEN}"}
23
+
24
+ def get_most_liked_spaces(limit: int = 300) -> Union[List[Dict], str]:
25
+ url = "https://huggingface.co/api/spaces"
26
+ params = {
27
+ "sort": "likes",
28
+ "direction": -1,
29
+ "limit": limit,
30
+ "full": "true"
31
+ }
32
+
33
+ try:
34
+ response = requests.get(url, params=params, headers=get_headers())
35
+ response.raise_for_status()
36
+ return response.json()
37
+ except requests.RequestException as e:
38
+ return f"API request error: {str(e)}"
39
+ except ValueError as e:
40
+ return f"JSON decoding error: {str(e)}"
41
+
42
+ def format_space(space: Dict) -> Dict:
43
+ space_id = space.get('id', 'Unknown')
44
+ space_name = space_id.split('/')[-1] if '/' in space_id else space_id
45
+
46
+ space_author = space.get('author', 'Unknown')
47
+ if isinstance(space_author, dict):
48
+ space_author = space_author.get('user', space_author.get('name', 'Unknown'))
49
+
50
+ space_likes = space.get('likes', 'N/A')
51
+ space_url = f"https://huggingface.co/spaces/{space_id}"
52
+
53
+ return {
54
+ "id": space_id,
55
+ "name": space_name,
56
+ "author": space_author,
57
+ "likes": space_likes,
58
+ "url": space_url,
59
+ "description": space.get('description', '')
60
+ }
61
+
62
+ def format_spaces(spaces: Union[List[Dict], str]) -> List[Dict]:
63
+ if isinstance(spaces, str):
64
+ return [{"error": spaces}]
65
+
66
+ return [format_space(space) for space in spaces if isinstance(space, dict)]
67
+
68
+ def summarize_space(space: Dict) -> str:
69
+ system_message = "๋‹น์‹ ์€ Hugging Face Space์˜ ๋‚ด์šฉ์„ ์š”์•ฝํ•˜๋Š” AI ์กฐ์ˆ˜์ž…๋‹ˆ๋‹ค. ์ฃผ์–ด์ง„ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๊ฐ„๊ฒฐํ•˜๊ณ  ๋ช…ํ™•ํ•œ ์š”์•ฝ์„ ์ œ๊ณตํ•ด์ฃผ์„ธ์š”."
70
+ user_message = f"๋‹ค์Œ Hugging Face Space๋ฅผ ์š”์•ฝํ•ด์ฃผ์„ธ์š”: {space['name']} by {space['author']}. ์ข‹์•„์š” ์ˆ˜: {space['likes']}. URL: {space['url']}"
71
+
72
+ messages = [
73
+ {"role": "system", "content": system_message},
74
+ {"role": "user", "content": user_message}
75
+ ]
76
+
77
+ try:
78
+ response = hf_client.chat_completion(messages, max_tokens=400, temperature=0.7)
79
+ return response.choices[0].message.content
80
+ except Exception as e:
81
+ return f"์š”์•ฝ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
82
+
83
+ def get_app_py_content(space_id: str) -> str:
84
+ app_py_url = f"https://huggingface.co/spaces/{space_id}/raw/main/app.py"
85
+ try:
86
+ response = requests.get(app_py_url, headers=get_headers())
87
+ if response.status_code == 200:
88
+ return response.text # ์ „์ฒด ๋‚ด์šฉ์„ ๋ฐ˜ํ™˜
89
+ else:
90
+ return f"app.py file not found or inaccessible for space: {space_id}"
91
+ except requests.RequestException:
92
+ return f"Error fetching app.py content for space: {space_id}"
93
+
94
+ def get_space_structure(space_id: str) -> Dict:
95
+ try:
96
+ # space_id์—์„œ owner์™€ repo_name์„ ๋ถ„๋ฆฌ
97
+ owner, repo_name = space_id.split('/')
98
+
99
+ # HfApi๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ด
100
+ files = hf_api.list_repo_files(repo_id=space_id, repo_type="space")
101
+
102
+ # ํŒŒ์ผ ๋ชฉ๋ก์„ ํŠธ๋ฆฌ ๊ตฌ์กฐ๋กœ ๋ณ€ํ™˜
103
+ tree = {"type": "directory", "path": "", "tree": []}
104
+ for file in files:
105
+ path_parts = file.split('/')
106
+ current = tree
107
+ for i, part in enumerate(path_parts):
108
+ if i == len(path_parts) - 1: # ํŒŒ์ผ
109
+ current["tree"].append({"type": "file", "path": part})
110
+ else: # ๋””๋ ‰ํ† ๋ฆฌ
111
+ found = False
112
+ for item in current["tree"]:
113
+ if item["type"] == "directory" and item["path"] == part:
114
+ current = item
115
+ found = True
116
+ break
117
+ if not found:
118
+ new_dir = {"type": "directory", "path": part, "tree": []}
119
+ current["tree"].append(new_dir)
120
+ current = new_dir
121
+
122
+ return tree
123
+ except Exception as e:
124
+ print(f"Error in get_space_structure: {str(e)}")
125
+ return {"error": f"API request error: {str(e)}"}
126
+
127
+ def format_tree_structure(tree_data: Dict, indent: str = "") -> str:
128
+ formatted = ""
129
+ for item in tree_data.get("tree", []):
130
+ if item["type"] == "file":
131
+ formatted += f"{indent}โ”œโ”€โ”€ {item['path']}\n"
132
+ elif item["type"] == "directory":
133
+ formatted += f"{indent}โ”œโ”€โ”€ {item['path']}/\n"
134
+ formatted += format_tree_structure(item, indent + "โ”‚ ")
135
+ return formatted
136
+
137
+ def format_list_structure(tree_data: Dict) -> List[str]:
138
+ formatted = []
139
+ for item in tree_data.get("tree", []):
140
+ if item["type"] == "file":
141
+ formatted.append(item["path"])
142
+ elif item["type"] == "directory":
143
+ formatted.append(f"{item['path']}/")
144
+ formatted.extend(format_list_structure(item))
145
+ return formatted
146
+
147
+ def on_select(space):
148
+ try:
149
+ print(f"Selected space: {space['name']}")
150
+ summary = summarize_space(space)
151
+ app_content = get_app_py_content(space['id'])
152
+ tree_structure = get_space_structure(space['id'])
153
+
154
+ info = f"์„ ํƒ๋œ Space: {space['name']} (ID: {space['id']})\n"
155
+ info += f"Author: {space['author']}\n"
156
+ info += f"Likes: {space['likes']}\n"
157
+ info += f"URL: {space['url']}\n\n"
158
+ info += f"์š”์•ฝ:\n{summary}"
159
+
160
+ tree_view = format_tree_structure(tree_structure)
161
+ list_view = "\n".join(format_list_structure(tree_structure))
162
+
163
+ print(f"Returning URL: {space['url']}")
164
+ return info, app_content, space['url'], tree_view, list_view
165
+ except Exception as e:
166
+ print(f"Error in on_select: {str(e)}")
167
+ print(traceback.format_exc())
168
+ return f"์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}", "", "", "", ""
169
+
170
+ def update_screenshot(url, last_url, force_update=False):
171
+ print(f"Updating screenshot. Current URL: {url}, Last URL: {last_url}, Force update: {force_update}")
172
+ if url and (url != last_url or force_update):
173
+ screenshot = take_screenshot(url)
174
+ print("Screenshot updated")
175
+ return screenshot, url
176
+ print("No update needed")
177
+ return gr.update(), last_url
178
+
179
+ def refresh_screenshot(url, last_url):
180
+ print(f"Refresh button clicked. URL: {url}, Last URL: {last_url}")
181
+ # ํ•ญ์ƒ ๊ฐ•์ œ๋กœ ์—…๋ฐ์ดํŠธ
182
+ return update_screenshot(url, last_url, force_update=True)
183
+
184
+ def take_screenshot(url):
185
+ try:
186
+ print(f"Taking screenshot of URL: {url}")
187
+ client = Client("ginipick/selenium-screenshot-gradio")
188
+ result = client.predict(url=url, api_name="/predict")
189
+ print(f"Screenshot result: {result}")
190
+ if isinstance(result, str) and os.path.exists(result):
191
+ return Image.open(result)
192
+ else:
193
+ print(f"Invalid result from API: {result}")
194
+ return Image.new('RGB', (600, 360), color='lightgray')
195
+ except Exception as e:
196
+ print(f"Screenshot error: {str(e)}")
197
+ return Image.new('RGB', (600, 360), color='lightgray')
198
+
199
+ def generate_usage_guide(app_content):
200
+ system_message = "๋‹น์‹ ์€ Python ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•˜์—ฌ, ํ™”๋ฉด ๋ณด๋“ฏ์ด ์ด์šฉ ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•˜๋Š” AI ์กฐ์ˆ˜์ž…๋‹ˆ๋‹ค. app.py ์ฝ”๋“œ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ฝ”๋“œ์— ๋Œ€ํ•œ ์–ธ๊ธ‰์€ ์ œ์™ธํ•˜๊ณ , ์ด์šฉ์ž ๊ด€์ ์—์„œ 1) ๊ธฐ์กด ์œ ์‚ฌ ๊ธฐ์ˆ  ๋ฐฉ์‹๊ดด ๋น„๊ตํ•ด ํŠน์ง•, ์žฅ์ ์— ๋Œ€ํ•ด ์นœ์ ˆํ•˜๊ณ  ์ž์„ธํ•˜๊ฒŒ ์ƒ์„ธํ•œ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ด์ฃผ์„ธ์š”."
201
+ user_message = f"๋‹ค์Œ Python ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ™”๋ฉด UI/UX์  ์ธก๋ฉด์œผ๋กœ ํŠน์ง•๊ณผ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”:\n\n{app_content}"
202
+
203
+ messages = [
204
+ {"role": "system", "content": system_message},
205
+ {"role": "user", "content": user_message}
206
+ ]
207
+
208
+ try:
209
+ response = hf_client.chat_completion(messages, max_tokens=4000, temperature=0.7)
210
+ return response.choices[0].message.content
211
+ except Exception as e:
212
+ return f"์‚ฌ์šฉ ๋ฐฉ๋ฒ• ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
213
+
214
+ def search_spaces(query: str, spaces: List[Dict]) -> List[Dict]:
215
+ query = query.lower()
216
+ return [
217
+ space for space in spaces
218
+ if query in space['name'].lower() or
219
+ query in space['author'].lower() or
220
+ query in space.get('description', '').lower()
221
+ ]
222
+
223
+ def create_ui():
224
+ try:
225
+ spaces_list = get_most_liked_spaces()
226
+ formatted_spaces = format_spaces(spaces_list)
227
+ print(f"Total spaces loaded: {len(formatted_spaces)}")
228
+
229
+ css = """
230
+ footer {visibility: hidden;}
231
+ .minimal-button {min-width: 30px !important; height: 25px !important; line-height: 1 !important; font-size: 12px !important; padding: 2px 5px !important;}
232
+ .space-row {margin-bottom: 5px !important;}
233
+ #refresh-button, #manual-button, #open-space-button {
234
+ width: 100% !important;
235
+ margin-top: 5px !important;
236
+ }
237
+ #info-output, #usage-guide, #tree-view, #list-view {
238
+ height: 400px !important;
239
+ overflow-y: auto !important;
240
+ padding-right: 10px !important;
241
+ }
242
+ #app-py-content {
243
+ height: auto !important;
244
+ max-height: none !important;
245
+ overflow-y: visible !important;
246
+ }
247
+ .output-group {
248
+ border: 1px solid #ddd;
249
+ border-radius: 5px;
250
+ padding: 10px;
251
+ margin-bottom: 20px;
252
+ }
253
+ .scroll-lock {
254
+ overflow: auto !important;
255
+ max-height: 400px !important;
256
+ }
257
+ .full-height {
258
+ height: auto !important;
259
+ max-height: none !important;
260
+ }
261
+ """
262
+
263
+ with gr.Blocks(css=css, theme="Nymbo/Nymbo_Theme") as demo:
264
+ gr.Markdown("# 300: HuggingFace Most Liked Spaces")
265
+
266
+ with gr.Row():
267
+ with gr.Column(scale=1):
268
+ search_input = gr.Textbox(label="Search Spaces", placeholder="Enter search query...")
269
+ space_list = gr.HTML()
270
+
271
+ def update_space_list(query):
272
+ filtered_spaces = search_spaces(query, formatted_spaces) if query else formatted_spaces
273
+ html = "<div class='space-list'>"
274
+ for space in filtered_spaces:
275
+ html += f"""
276
+ <div class='space-row'>
277
+ <span>{space['name']} by {space['author']} (Likes: {space['likes']})</span>
278
+ <button class='select-space' data-space='{json.dumps(space)}'>์„ ํƒ</button>
279
+ </div>
280
+ """
281
+ html += "</div>"
282
+ return html
283
+
284
+ search_input.change(update_space_list, inputs=[search_input], outputs=[space_list])
285
+
286
+ with gr.Column(scale=2):
287
+ with gr.Tabs():
288
+ with gr.TabItem("๊ธฐ๋ณธ ์ •๋ณด"):
289
+ with gr.Group(elem_classes="output-group scroll-lock"):
290
+ info_output = gr.Textbox(label="Space ์ •๋ณด ๋ฐ ์š”์•ฝ", elem_id="info-output", lines=20, max_lines=30)
291
+ url_state = gr.State("")
292
+ last_url_state = gr.State("")
293
+
294
+ screenshot_output = gr.Image(type="pil", label="Live ํ™”๋ฉด", height=360, width=600)
295
+ refresh_button = gr.Button("๐Ÿ”„ ์„œ๋น„์Šค ํ™”๋ฉด", elem_id="refresh-button")
296
+ manual_button = gr.Button("์„ ํƒ ์„œ๋น„์Šค ํŠน์ง• ๋ฐ ์‚ฌ์šฉ๋ฒ•", elem_id="manual-button")
297
+
298
+ with gr.Group(elem_classes="output-group scroll-lock"):
299
+ usage_guide = gr.Textbox(label="์„ ํƒ ์„œ๋น„์Šค ํŠน์ง• ๋ฐ ์‚ฌ์šฉ๋ฒ•", elem_id="usage-guide", visible=False, lines=20, max_lines=30)
300
+
301
+ with gr.Group(elem_classes="output-group full-height"):
302
+ app_py_content = gr.Code(language="python", label="๋ฉ”์ธ ์†Œ์Šค์ฝ”๋“œ", elem_id="app-py-content", lines=None, max_lines
303
+
304
+ open_space_button = gr.Button("์„ ํƒํ•œ Space ์—ด๊ธฐ", elem_id="open-space-button")
305
+
306
+ with gr.TabItem("์ฝ”๋“œ ๊ตฌ์กฐ ๋ถ„์„"):
307
+ with gr.Group(elem_classes="output-group scroll-lock"):
308
+ tree_view = gr.Textbox(label="ํŠธ๋ฆฌ ๊ตฌ์กฐ", elem_id="tree-view", lines=30, max_lines=50)
309
+ with gr.Group(elem_classes="output-group scroll-lock"):
310
+ list_view = gr.Textbox(label="๋ฆฌ์ŠคํŠธ ๊ตฌ์กฐ", elem_id="list-view", lines=30, max_lines=50)
311
+
312
+ update_trigger = gr.Button("Update Screenshot", visible=False)
313
+
314
+ # JavaScript to handle space selection
315
+ demo.load(None, None, None, _js="""
316
+ function() {
317
+ document.addEventListener('click', function(e) {
318
+ if (e.target.classList.contains('select-space')) {
319
+ var spaceData = JSON.stringify(JSON.parse(e.target.getAttribute('data-space')));
320
+ document.querySelector('#space-data').value = spaceData;
321
+ document.querySelector('#on-select-function').click();
322
+ }
323
+ });
324
+ }
325
+ """)
326
+
327
+ # Hidden elements to pass data
328
+ space_data = gr.Textbox(elem_id="space-data", visible=False)
329
+ on_select_trigger = gr.Button("Hidden Select Trigger", visible=False, elem_id="on-select-function")
330
+
331
+ def on_select_with_link(space_json):
332
+ space = json.loads(space_json)
333
+ info, app_content, url, tree, list_structure = on_select(space)
334
+ info += f"\n\n์„ ํƒํ•œ Space URL: {url}"
335
+ return info, app_content, url, tree, list_structure
336
+
337
+ on_select_trigger.click(
338
+ on_select_with_link,
339
+ inputs=[space_data],
340
+ outputs=[info_output, app_py_content, url_state, tree_view, list_view]
341
+ ).then(
342
+ update_screenshot,
343
+ inputs=[url_state, last_url_state],
344
+ outputs=[screenshot_output, last_url_state]
345
+ )
346
+
347
+ def open_space_in_browser(url):
348
+ if url:
349
+ import webbrowser
350
+ webbrowser.open(url)
351
+ return f"'{url}' ์ฃผ์†Œ๊ฐ€ ์ƒˆ ํƒญ์—์„œ ์—ด๋ ธ์Šต๋‹ˆ๋‹ค."
352
+ return "์„ ํƒ๋œ Space๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
353
+
354
+ open_space_button.click(
355
+ open_space_in_browser,
356
+ inputs=[url_state],
357
+ outputs=[gr.Textbox(label="์ƒํƒœ ๋ฉ”์‹œ์ง€")]
358
+ )
359
+
360
+ refresh_button.click(
361
+ refresh_screenshot,
362
+ inputs=[url_state, last_url_state],
363
+ outputs=[screenshot_output, last_url_state]
364
+ )
365
+
366
+ def show_usage_guide(app_content):
367
+ usage_text = generate_usage_guide(app_content)
368
+ return gr.update(value=usage_text, visible=True)
369
+
370
+ manual_button.click(
371
+ show_usage_guide,
372
+ inputs=[app_py_content],
373
+ outputs=[usage_guide]
374
+ )
375
+
376
+ update_trigger.click(
377
+ update_screenshot,
378
+ inputs=[url_state, last_url_state],
379
+ outputs=[screenshot_output, last_url_state]
380
+ )
381
+
382
+ # Start a background thread to trigger updates
383
+ def trigger_updates():
384
+ while True:
385
+ time.sleep(5)
386
+ update_trigger.click()
387
+
388
+ threading.Thread(target=trigger_updates, daemon=True).start()
389
+
390
+ return demo
391
+
392
+ except Exception as e:
393
+ print(f"Error in create_ui: {str(e)}")
394
+ print(traceback.format_exc())
395
+ raise
396
+
397
+ if __name__ == "__main__":
398
+ try:
399
+ demo = create_ui()
400
+ demo.launch()
401
+ except Exception as e:
402
+ print(f"Error in main: {str(e)}")
403
+ print(traceback.format_exc())