ginipick's picture
Update app.py
db3cf6b verified
raw
history blame
17.9 kB
import gradio as gr
from huggingface_hub import InferenceClient, HfApi
import os
import requests
from typing import List, Dict, Union, Tuple
import traceback
from PIL import Image
from io import BytesIO
import asyncio
from gradio_client import Client
import time
import threading
import json
import re
# HuggingFace κ΄€λ ¨ API ν‚€ (슀페이슀 뢄석 용)
HF_TOKEN = os.getenv("HF_TOKEN")
hf_api = HfApi(token=HF_TOKEN)
# Gemini 2.0 Thinking λͺ¨λΈ κ΄€λ ¨ API ν‚€ 및 ν΄λΌμ΄μ–ΈνŠΈ (LLM 용)
G_API_KEY = os.getenv("G_API_KEY")
gemini_client = InferenceClient("Gemini-2.0-thinking", token=G_API_KEY)
def get_headers():
if not HF_TOKEN:
raise ValueError("Hugging Face token not found in environment variables")
return {"Authorization": f"Bearer {HF_TOKEN}"}
def get_file_content(space_id: str, file_path: str) -> str:
file_url = f"https://huggingface.co/spaces/{space_id}/raw/main/{file_path}"
try:
response = requests.get(file_url, headers=get_headers())
if response.status_code == 200:
return response.text
else:
return f"File not found or inaccessible: {file_path}"
except requests.RequestException:
return f"Error fetching content for file: {file_path}"
def get_space_structure(space_id: str) -> Dict:
try:
files = hf_api.list_repo_files(repo_id=space_id, repo_type="space")
tree = {"type": "directory", "path": "", "name": space_id, "children": []}
for file in files:
path_parts = file.split('/')
current = tree
for i, part in enumerate(path_parts):
if i == len(path_parts) - 1: # 파일
current["children"].append({"type": "file", "path": file, "name": part})
else: # 디렉토리
found = False
for child in current["children"]:
if child["type"] == "directory" and child["name"] == part:
current = child
found = True
break
if not found:
new_dir = {"type": "directory", "path": '/'.join(path_parts[:i+1]), "name": part, "children": []}
current["children"].append(new_dir)
current = new_dir
return tree
except Exception as e:
print(f"Error in get_space_structure: {str(e)}")
return {"error": f"API request error: {str(e)}"}
def format_tree_structure(tree_data: Dict, indent: str = "") -> str:
if "error" in tree_data:
return tree_data["error"]
formatted = f"{indent}{'πŸ“' if tree_data.get('type') == 'directory' else 'πŸ“„'} {tree_data.get('name', 'Unknown')}\n"
if tree_data.get("type") == "directory":
for child in sorted(tree_data.get("children", []), key=lambda x: (x.get("type", "") != "directory", x.get("name", ""))):
formatted += format_tree_structure(child, indent + " ")
return formatted
def summarize_code(app_content: str):
system_message = "당신은 Python μ½”λ“œλ₯Ό λΆ„μ„ν•˜κ³  μš”μ•½ν•˜λŠ” AI μ‘°μˆ˜μž…λ‹ˆλ‹€. 주어진 μ½”λ“œλ₯Ό 3쀄 μ΄λ‚΄λ‘œ κ°„κ²°ν•˜κ²Œ μš”μ•½ν•΄μ£Όμ„Έμš”."
user_message = f"λ‹€μŒ Python μ½”λ“œλ₯Ό 3쀄 μ΄λ‚΄λ‘œ μš”μ•½ν•΄μ£Όμ„Έμš”:\n\n{app_content}"
messages = [
{"role": "system", "content": system_message},
{"role": "user", "content": user_message}
]
try:
response = gemini_client.chat_completion(messages, max_tokens=200, temperature=0.7)
return response.choices[0].message.content
except Exception as e:
return f"μš”μ•½ 생성 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
def analyze_code(app_content: str):
system_message = """당신은 Python μ½”λ“œλ₯Ό λΆ„μ„ν•˜λŠ” AI μ‘°μˆ˜μž…λ‹ˆλ‹€. 주어진 μ½”λ“œλ₯Ό λΆ„μ„ν•˜μ—¬ λ‹€μŒ ν•­λͺ©μ— λŒ€ν•΄ μ„€λͺ…ν•΄μ£Όμ„Έμš”:
A. λ°°κ²½ 및 ν•„μš”μ„±
B. κΈ°λŠ₯적 νš¨μš©μ„± 및 κ°€μΉ˜
C. 특μž₯점
D. 적용 λŒ€μƒ 및 νƒ€κ²Ÿ
E. κΈ°λŒ€νš¨κ³Ό
κΈ°μ‘΄ 및 μœ μ‚¬ ν”„λ‘œμ νŠΈμ™€ λΉ„κ΅ν•˜μ—¬ λΆ„μ„ν•΄μ£Όμ„Έμš”. Markdown ν˜•μ‹μœΌλ‘œ 좜λ ₯ν•˜μ„Έμš”."""
user_message = f"λ‹€μŒ Python μ½”λ“œλ₯Ό λΆ„μ„ν•΄μ£Όμ„Έμš”:\n\n{app_content}"
messages = [
{"role": "system", "content": system_message},
{"role": "user", "content": user_message}
]
try:
response = gemini_client.chat_completion(messages, max_tokens=1000, temperature=0.7)
return response.choices[0].message.content
except Exception as e:
return f"뢄석 생성 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
def explain_usage(app_content: str):
system_message = "당신은 Python μ½”λ“œλ₯Ό λΆ„μ„ν•˜μ—¬ μ‚¬μš©λ²•μ„ μ„€λͺ…ν•˜λŠ” AI μ‘°μˆ˜μž…λ‹ˆλ‹€. 주어진 μ½”λ“œλ₯Ό λ°”νƒ•μœΌλ‘œ 마치 화면을 λ³΄λŠ” κ²ƒμ²˜λŸΌ μ‚¬μš©λ²•μ„ μƒμ„Ένžˆ μ„€λͺ…ν•΄μ£Όμ„Έμš”. Markdown ν˜•μ‹μœΌλ‘œ 좜λ ₯ν•˜μ„Έμš”."
user_message = f"λ‹€μŒ Python μ½”λ“œμ˜ μ‚¬μš©λ²•μ„ μ„€λͺ…ν•΄μ£Όμ„Έμš”:\n\n{app_content}"
messages = [
{"role": "system", "content": system_message},
{"role": "user", "content": user_message}
]
try:
response = gemini_client.chat_completion(messages, max_tokens=800, temperature=0.7)
return response.choices[0].message.content
except Exception as e:
return f"μ‚¬μš©λ²• μ„€λͺ… 생성 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
def adjust_lines_for_code(code_content: str, min_lines: int = 10, max_lines: int = 100) -> int:
"""
μ½”λ“œ λ‚΄μš©μ— 따라 lines 수λ₯Ό λ™μ μœΌλ‘œ μ‘°μ •ν•©λ‹ˆλ‹€.
Parameters:
- code_content (str): μ½”λ“œ ν…μŠ€νŠΈ λ‚΄μš©
- min_lines (int): μ΅œμ†Œ lines 수
- max_lines (int): μ΅œλŒ€ lines 수
Returns:
- int: μ„€μ •λœ lines 수
"""
# μ½”λ“œμ˜ 쀄 수 계산
num_lines = len(code_content.split('\n'))
# 쀄 μˆ˜κ°€ min_lines보닀 적닀면 min_lines μ‚¬μš©, max_lines보닀 크면 max_lines μ‚¬μš©
return min(max(num_lines, min_lines), max_lines)
def analyze_space(url: str, progress=gr.Progress()):
try:
space_id = url.split('spaces/')[-1]
# Space ID μœ νš¨μ„± 검사 μˆ˜μ •
if not re.match(r'^[\w.-]+/[\w.-]+$', space_id):
raise ValueError(f"Invalid Space ID format: {space_id}")
progress(0.1, desc="파일 ꡬ쑰 뢄석 쀑...")
tree_structure = get_space_structure(space_id)
if "error" in tree_structure:
raise ValueError(tree_structure["error"])
tree_view = format_tree_structure(tree_structure)
progress(0.3, desc="app.py λ‚΄μš© κ°€μ Έμ˜€λŠ” 쀑...")
app_content = get_file_content(space_id, "app.py")
progress(0.5, desc="μ½”λ“œ μš”μ•½ 쀑...")
summary = summarize_code(app_content)
progress(0.7, desc="μ½”λ“œ 뢄석 쀑...")
analysis = analyze_code(app_content)
progress(0.9, desc="μ‚¬μš©λ²• μ„€λͺ… 생성 쀑...")
usage = explain_usage(app_content)
# 쀄 수 κ³„μ‚°ν•˜μ—¬ lines μ„€μ •
app_py_lines = adjust_lines_for_code(app_content)
progress(1.0, desc="μ™„λ£Œ")
return app_content, tree_view, tree_structure, space_id, summary, analysis, usage, app_py_lines
except Exception as e:
print(f"Error in analyze_space: {str(e)}")
print(traceback.format_exc())
return f"였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {str(e)}", "", None, "", "", "", "", 10
def respond(
message: str,
history: List[Tuple[str, str]],
system_message: str = "",
max_tokens: int = 1024,
temperature: float = 0.7,
top_p: float = 0.9,
):
system_prefix = """당신은 ν—ˆκΉ…νŽ˜μ΄μŠ€μ— νŠΉν™”λœ AI μ½”λ”© μ „λ¬Έκ°€μž…λ‹ˆλ‹€. μ‚¬μš©μžμ˜ μ§ˆλ¬Έμ— μΉœμ ˆν•˜κ³  μƒμ„Έν•˜κ²Œ λ‹΅λ³€ν•΄μ£Όμ„Έμš”.
Gradio νŠΉμ„±μ„ μ •ν™•νžˆ μΈμ‹ν•˜κ³  Requirements.txt λˆ„λ½μ—†μ΄ μ½”λ”©κ³Ό 였λ₯˜λ₯Ό ν•΄κ²°ν•΄μ•Ό ν•©λ‹ˆλ‹€.
항상 μ •ν™•ν•˜κ³  μœ μš©ν•œ 정보λ₯Ό μ œκ³΅ν•˜λ„λ‘ λ…Έλ ₯ν•˜μ„Έμš”."""
messages = [{"role": "system", "content": f"{system_prefix} {system_message}"}]
for user_msg, assistant_msg in history:
messages.append({"role": "user", "content": user_msg})
if assistant_msg:
messages.append({"role": "assistant", "content": assistant_msg})
messages.append({"role": "user", "content": message})
response = ""
for message in gemini_client.chat_completion(
messages,
max_tokens=max_tokens,
stream=True,
temperature=temperature,
top_p=top_p,
):
token = message.choices[0].delta.get('content', None)
if token:
response += token.strip("")
yield response
def create_ui():
try:
css = """
/* 전체 λ°°κ²½ 및 κΈ°λ³Έ κΈ€κΌ΄ μ„€μ • */
body {
background-color: #f9f9f9;
font-family: 'Helvetica Neue', Arial, sans-serif;
color: #333;
}
/* ν•˜λ‹¨ ν‘Έν„° μˆ¨κΉ€ */
footer { visibility: hidden; }
/* 좜λ ₯ κ·Έλ£Ή μŠ€νƒ€μΌ: 밝은 λ°°κ²½, λΆ€λ“œλŸ¬μš΄ ν…Œλ‘λ¦¬μ™€ 그림자 */
.output-group {
border: 1px solid #ccc;
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
background-color: #ffffff;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
/* 슀크둀 μ˜μ—­ μ„€μ • */
.scroll-lock {
overflow-y: auto !important;
max-height: 300px !important;
}
.tree-view-scroll {
overflow-y: auto !important;
max-height: 400px !important;
}
.full-height {
height: 80vh !important;
overflow-y: auto !important;
}
/* μ½”λ“œ λ°•μŠ€ μŠ€νƒ€μΌ: λͺ¨λ…ΈμŠ€νŽ˜μ΄μŠ€ ν°νŠΈμ™€ 밝은 λ°°κ²½ */
.code-box {
overflow-x: auto !important;
overflow-y: auto !important;
white-space: pre !important;
background-color: #f5f5f5;
border-radius: 4px;
padding: 10px;
font-family: 'Courier New', Courier, monospace;
}
.code-box > div { min-width: 100% !important; }
.code-box > div > textarea {
word-break: normal !important;
overflow-wrap: normal !important;
}
/* νƒ­ λ‚΄λΉ„κ²Œμ΄μ…˜ μŠ€νƒ€μΌ: λ‹¨μˆœν•˜κ³  κΉ”λ”ν•œ λ””μžμΈ */
.tab-nav {
background-color: #ffffff;
border-bottom: 1px solid #ccc;
display: flex;
}
.tab-nav button {
background: none;
border: none;
padding: 10px 20px;
margin: 0;
cursor: pointer;
font-size: 16px;
color: #555;
transition: color 0.3s, border-bottom 0.3s;
}
.tab-nav button:hover,
.tab-nav button.selected {
color: #000;
border-bottom: 2px solid #007BFF;
}
/* μž…λ ₯μ°½ 및 ν…μŠ€νŠΈ μ˜μ—­ μŠ€νƒ€μΌ */
input[type="text"], textarea {
color: #333;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
padding: 8px;
}
"""
with gr.Blocks(theme="default", css=css) as demo:
gr.Markdown("# MOUSE: HF Space Deep-Research", elem_classes="header-markdown")
with gr.Tabs() as tabs:
with gr.TabItem("뢄석"):
with gr.Row():
with gr.Column(scale=6): # μ™Όμͺ½ μ˜μ—­
url_input = gr.Textbox(label="HuggingFace Space URL", placeholder="예: https://huggingface.co/spaces/username/space_name")
analyze_button = gr.Button("뢄석", variant="primary")
with gr.Group(elem_classes="output-group scroll-lock"):
summary_output = gr.Markdown(label="μš”μ•½ (3쀄 이내)")
with gr.Group(elem_classes="output-group scroll-lock"):
analysis_output = gr.Markdown(label="뢄석")
with gr.Group(elem_classes="output-group scroll-lock"):
usage_output = gr.Markdown(label="μ‚¬μš©λ²•")
with gr.Group(elem_classes="output-group tree-view-scroll"):
tree_view_output = gr.Textbox(label="파일 ꡬ쑰 (Tree View)", lines=30)
with gr.Column(scale=4): # 였λ₯Έμͺ½ μ˜μ—­
with gr.Group(elem_classes="output-group full-height"):
code_tabs = gr.Tabs()
with code_tabs:
with gr.TabItem("app.py"):
app_py_content = gr.Code(
language="python",
label="app.py",
lines=200,
elem_classes="full-height code-box"
)
with gr.TabItem("requirements.txt"):
requirements_content = gr.Textbox(
label="requirements.txt",
lines=200,
elem_classes="full-height code-box"
)
with gr.TabItem("AI μ½”λ”©"):
chatbot = gr.Chatbot(label="λŒ€ν™”", elem_classes="output-group full-height")
msg = gr.Textbox(label="λ©”μ‹œμ§€", placeholder="λ©”μ‹œμ§€λ₯Ό μž…λ ₯ν•˜μ„Έμš”...")
# μˆ¨κ²¨μ§„ νŒŒλΌλ―Έν„°
max_tokens = gr.Slider(minimum=1, maximum=8000, value=4000, label="Max Tokens", visible=False)
temperature = gr.Slider(minimum=0, maximum=1, value=0.7, label="Temperature", visible=False)
top_p = gr.Slider(minimum=0, maximum=1, value=0.9, label="Top P", visible=False)
examples = [
["μƒμ„Έν•œ μ‚¬μš© 방법을 마치 화면을 λ³΄λ©΄μ„œ μ„€λͺ…ν•˜λ“―이 4000 토큰 이상 μžμ„Ένžˆ μ„€λͺ…ν•˜λΌ"],
["FAQ 20건을 μƒμ„Έν•˜κ²Œ μž‘μ„±ν•˜λΌ. 4000토큰 이상 μ‚¬μš©ν•˜λΌ."],
["μ‚¬μš© 방법과 차별점, νŠΉμ§•, 강점을 μ€‘μ‹¬μœΌλ‘œ 4000 토큰 이상 유튜브 μ˜μƒ 슀크립트 ν˜•νƒœλ‘œ μž‘μ„±ν•˜λΌ"],
["λ³Έ μ„œλΉ„μŠ€λ₯Ό SEO μ΅œμ ν™”ν•˜μ—¬ λΈ”λ‘œκ·Έ 포슀트둜 4000 토큰 이상 μž‘μ„±ν•˜λΌ"],
["νŠΉν—ˆ μΆœμ›μ— ν™œμš©ν•  ν˜μ‹ μ μΈ 창의 발λͺ… λ‚΄μš©μ„ μ€‘μ‹¬μœΌλ‘œ 4000 토큰 이상 μž‘μ„±ν•˜λΌ."],
["계속 μ΄μ–΄μ„œ λ‹΅λ³€ν•˜λΌ"],
]
gr.Examples(examples, inputs=msg)
def respond_wrapper(message, chat_history, max_tokens, temperature, top_p):
bot_message = ""
for response in respond(message, chat_history, max_tokens=max_tokens, temperature=temperature, top_p=top_p):
bot_message = response # λ§ˆμ§€λ§‰ 응닡 μ €μž₯
yield "", chat_history + [(message, bot_message)]
chat_history.append((message, bot_message))
return "", chat_history
msg.submit(respond_wrapper, [msg, chatbot, max_tokens, temperature, top_p], [msg, chatbot])
with gr.TabItem("Recommended Best"):
gr.Markdown(
"Discover the best recommended HuggingFace Spaces [here](https://huggingface.co/spaces/openfree/Korean-Leaderboard).",
elem_id="recommended-best"
)
# μƒνƒœ μ €μž₯용 λ³€μˆ˜
space_id_state = gr.State()
tree_structure_state = gr.State()
app_py_content_lines = gr.State()
analyze_button.click(
analyze_space,
inputs=[url_input],
outputs=[app_py_content, tree_view_output, tree_structure_state, space_id_state, summary_output, analysis_output, usage_output, app_py_content_lines]
).then(
lambda space_id: get_file_content(space_id, "requirements.txt"),
inputs=[space_id_state],
outputs=[requirements_content]
)
# λ™μ μœΌλ‘œ app.py의 쀄 수 μ‘°μ •
app_py_content.change(lambda lines: gr.update(lines=lines), inputs=[app_py_content_lines], outputs=[app_py_content])
return demo
except Exception as e:
print(f"Error in create_ui: {str(e)}")
print(traceback.format_exc())
raise
if __name__ == "__main__":
try:
print("Starting HuggingFace Space Analyzer...")
demo = create_ui()
print("UI created successfully.")
print("Configuring Gradio queue...")
demo.queue()
print("Gradio queue configured.")
print("Launching Gradio app...")
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
debug=True,
show_api=False
)
print("Gradio app launched successfully.")
except Exception as e:
print(f"Error in main: {str(e)}")
print("Detailed error information:")
print(traceback.format_exc())
raise