ginipick's picture
Update app.py
5dfb15d verified
raw
history blame
22.5 kB
import os
import gradio as gr
from gradio import ChatMessage
from typing import Iterator, List, Dict, Tuple, Any
import google.generativeai as genai
from huggingface_hub import HfApi
import requests
import re
import traceback
import time
import threading
import json
# HuggingFace κ΄€λ ¨ API ν‚€ (슀페이슀 뢄석 용)
HF_TOKEN = os.getenv("HF_TOKEN")
hf_api = HfApi(token=HF_TOKEN)
# Gemini 2.0 Flash Thinking λͺ¨λΈ κ΄€λ ¨ API ν‚€ 및 ν΄λΌμ΄μ–ΈνŠΈ (LLM 용)
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
genai.configure(api_key=GEMINI_API_KEY)
model = genai.GenerativeModel("gemini-2.0-flash-thinking-exp-01-21")
# --------------------------------------------------
# 파일 및 슀페이슀 뢄석 κ΄€λ ¨ ν•¨μˆ˜λ“€ (κΈ°μ‘΄ μ½”λ“œ μœ μ§€)
# --------------------------------------------------
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 adjust_lines_for_code(code_content: str, min_lines: int = 10, max_lines: int = 100) -> int:
num_lines = len(code_content.split('\n'))
return min(max(num_lines, min_lines), max_lines)
def analyze_space(url: str, progress=gr.Progress()):
try:
space_id = url.split('spaces/')[-1]
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)
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
# --------------------------------------------------
# Gemini 2.0 Flash Thinking λͺ¨λΈ κ΄€λ ¨ 헬퍼 ν•¨μˆ˜λ“€
# --------------------------------------------------
def format_chat_history(messages: List[ChatMessage]) -> List[Dict]:
formatted_history = []
for message in messages:
# thinking λ©”μ‹œμ§€(메타데이터 μžˆλŠ” λ©”μ‹œμ§€)λŠ” κ±΄λ„ˆλœλ‹ˆλ‹€.
if not (hasattr(message, "metadata") and message.metadata):
formatted_history.append({
"role": "user" if message.role == "user" else "assistant",
"parts": [message.content or ""]
})
return formatted_history
def gemini_chat_completion(system_message: str, user_message: str, max_tokens: int = 200, temperature: float = 0.7) -> str:
initial_messages = [
ChatMessage(role="system", content=system_message),
ChatMessage(role="user", content=user_message)
]
chat_history = format_chat_history(initial_messages)
chat = model.start_chat(history=chat_history)
final_response = ""
try:
for chunk in chat.send_message(user_message, stream=True):
parts = chunk.candidates[0].content.parts
if len(parts) == 2:
final_response += parts[1].text
else:
final_response += parts[0].text
return final_response.strip()
except Exception as e:
return f"LLM 호좜 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
def summarize_code(app_content: str) -> str:
system_message = "당신은 Python μ½”λ“œλ₯Ό λΆ„μ„ν•˜κ³  μš”μ•½ν•˜λŠ” AI μ‘°μˆ˜μž…λ‹ˆλ‹€. 주어진 μ½”λ“œλ₯Ό 3쀄 μ΄λ‚΄λ‘œ κ°„κ²°ν•˜κ²Œ μš”μ•½ν•΄μ£Όμ„Έμš”."
user_message = f"λ‹€μŒ Python μ½”λ“œλ₯Ό 3쀄 μ΄λ‚΄λ‘œ μš”μ•½ν•΄μ£Όμ„Έμš”:\n\n{app_content}"
try:
return gemini_chat_completion(system_message, user_message, max_tokens=200, temperature=0.7)
except Exception as e:
return f"μš”μ•½ 생성 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
def analyze_code(app_content: str) -> str:
system_message = (
"당신은 Python μ½”λ“œλ₯Ό λΆ„μ„ν•˜λŠ” AI μ‘°μˆ˜μž…λ‹ˆλ‹€. 주어진 μ½”λ“œλ₯Ό λΆ„μ„ν•˜μ—¬ λ‹€μŒ ν•­λͺ©μ— λŒ€ν•΄ μ„€λͺ…ν•΄μ£Όμ„Έμš”:\n"
"A. λ°°κ²½ 및 ν•„μš”μ„±\n"
"B. κΈ°λŠ₯적 νš¨μš©μ„± 및 κ°€μΉ˜\n"
"C. 특μž₯점\n"
"D. 적용 λŒ€μƒ 및 νƒ€κ²Ÿ\n"
"E. κΈ°λŒ€νš¨κ³Ό\n"
"κΈ°μ‘΄ 및 μœ μ‚¬ ν”„λ‘œμ νŠΈμ™€ λΉ„κ΅ν•˜μ—¬ λΆ„μ„ν•΄μ£Όμ„Έμš”. Markdown ν˜•μ‹μœΌλ‘œ 좜λ ₯ν•˜μ„Έμš”."
)
user_message = f"λ‹€μŒ Python μ½”λ“œλ₯Ό λΆ„μ„ν•΄μ£Όμ„Έμš”:\n\n{app_content}"
try:
return gemini_chat_completion(system_message, user_message, max_tokens=1000, temperature=0.7)
except Exception as e:
return f"뢄석 생성 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
def explain_usage(app_content: str) -> str:
system_message = "당신은 Python μ½”λ“œλ₯Ό λΆ„μ„ν•˜μ—¬ μ‚¬μš©λ²•μ„ μ„€λͺ…ν•˜λŠ” AI μ‘°μˆ˜μž…λ‹ˆλ‹€. 주어진 μ½”λ“œλ₯Ό λ°”νƒ•μœΌλ‘œ 마치 화면을 λ³΄λŠ” κ²ƒμ²˜λŸΌ μ‚¬μš©λ²•μ„ μƒμ„Ένžˆ μ„€λͺ…ν•΄μ£Όμ„Έμš”. Markdown ν˜•μ‹μœΌλ‘œ 좜λ ₯ν•˜μ„Έμš”."
user_message = f"λ‹€μŒ Python μ½”λ“œμ˜ μ‚¬μš©λ²•μ„ μ„€λͺ…ν•΄μ£Όμ„Έμš”:\n\n{app_content}"
try:
return gemini_chat_completion(system_message, user_message, max_tokens=800, temperature=0.7)
except Exception as e:
return f"μ‚¬μš©λ²• μ„€λͺ… 생성 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
def convert_chat_history(messages: List[Any]) -> List[Tuple[str, str]]:
"""
λ©”μ‹œμ§€ λͺ©λ‘μ˜ 각 ν•­λͺ©μ΄ ChatMessage 객체라면 (user, assistant) νŠœν”Œλ‘œ,
이미 νŠœν”ŒμΈ 경우 κ·ΈλŒ€λ‘œ λ°˜ν™˜ν•©λ‹ˆλ‹€.
"""
conv = []
i = 0
while i < len(messages):
# λ§Œμ•½ 이미 νŠœν”Œμ΄λ©΄ κ·ΈλŒ€λ‘œ μ‚¬μš©
if isinstance(messages[i], tuple):
conv.append(messages[i])
i += 1
# λ§Œμ•½ ChatMessage 객체라면
elif hasattr(messages[i], "role"):
if messages[i].role == "user":
user_text = messages[i].content
bot_text = ""
if i + 1 < len(messages) and hasattr(messages[i+1], "role") and messages[i+1].role == "assistant":
bot_text = messages[i+1].content
i += 2
else:
i += 1
conv.append((user_text, bot_text))
else:
conv.append(("", messages[i].content))
i += 1
else:
i += 1
return conv
def convert_to_chatmessage(history: List[Tuple[str, str]]) -> List[ChatMessage]:
"""
νŠœν”Œ λͺ©λ‘μ„ ChatMessage 객체 λͺ©λ‘μœΌλ‘œ λ³€ν™˜ν•©λ‹ˆλ‹€.
"""
new_history = []
for tup in history:
if tup[0]:
new_history.append(ChatMessage(role="user", content=tup[0]))
if tup[1]:
new_history.append(ChatMessage(role="assistant", content=tup[1]))
return new_history
def stream_gemini_response(user_message: str, messages: List[ChatMessage]) -> Iterator[List[ChatMessage]]:
if not user_message.strip():
messages.append(ChatMessage(role="assistant", content="Please provide a non-empty text message. Empty input is not allowed."))
yield messages
return
try:
print(f"\n=== New Request (Text) ===")
print(f"User message: {user_message}")
chat_history = format_chat_history(messages)
chat = model.start_chat(history=chat_history)
response = chat.send_message(user_message, stream=True)
thought_buffer = ""
response_buffer = ""
thinking_complete = False
messages.append(
ChatMessage(
role="assistant",
content="",
metadata={"title": "βš™οΈ Thinking: *The thoughts produced by the model are experimental"}
)
)
for chunk in response:
parts = chunk.candidates[0].content.parts
current_chunk = parts[0].text
if len(parts) == 2 and not thinking_complete:
thought_buffer += current_chunk
print(f"\n=== Complete Thought ===\n{thought_buffer}")
messages[-1] = ChatMessage(
role="assistant",
content=thought_buffer,
metadata={"title": "βš™οΈ Thinking: *The thoughts produced by the model are experimental"}
)
yield messages
response_buffer = parts[1].text
print(f"\n=== Starting Response ===\n{response_buffer}")
messages.append(
ChatMessage(
role="assistant",
content=response_buffer
)
)
thinking_complete = True
elif thinking_complete:
response_buffer += current_chunk
print(f"\n=== Response Chunk ===\n{current_chunk}")
messages[-1] = ChatMessage(
role="assistant",
content=response_buffer
)
else:
thought_buffer += current_chunk
print(f"\n=== Thinking Chunk ===\n{current_chunk}")
messages[-1] = ChatMessage(
role="assistant",
content=thought_buffer,
metadata={"title": "βš™οΈ Thinking: *The thoughts produced by the model are experimental"}
)
yield messages
print(f"\n=== Final Response ===\n{response_buffer}")
except Exception as e:
print(f"\n=== Error ===\n{str(e)}")
messages.append(
ChatMessage(
role="assistant",
content=f"I apologize, but I encountered an error: {str(e)}"
)
)
yield messages
def respond(message: str, history: List[ChatMessage]) -> Iterator[List[Tuple[str, str]]]:
"""
stream_gemini_response()λ₯Ό ν˜ΈμΆœν•œ ν›„, 좜λ ₯ κ²°κ³Όλ₯Ό νŠœν”Œ λͺ©λ‘μœΌλ‘œ λ³€ν™˜ν•˜μ—¬ λ°˜ν™˜ν•©λ‹ˆλ‹€.
"""
for updated_messages in stream_gemini_response(message, history):
yield convert_chat_history(updated_messages)
def user_message(msg: str, history: List[ChatMessage]) -> Tuple[str, List[ChatMessage]]:
history.append(ChatMessage(role="user", content=msg))
return "", history
def respond_wrapper(message, chat_history, max_tokens, temperature, top_p):
# chat_historyκ°€ νŠœν”Œ λͺ©λ‘μ΄λΌλ©΄ ChatMessage 객체둜 λ³€ν™˜
if chat_history and isinstance(chat_history[0], tuple):
chat_history = convert_to_chatmessage(chat_history)
for updated in stream_gemini_response(message, chat_history):
yield "", convert_chat_history(updated)
# --------------------------------------------------
# Gradio UI ꡬ성
# --------------------------------------------------
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: Space Research Thinking", 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 μ½”λ“œμ±—"):
gr.Markdown("## : 예제λ₯Ό μž…λ ₯/μ„ νƒν•˜κ³ , μ΄μ–΄μ„œ λ³΅μ‚¬ν•œ app.py μ†ŒμŠ€μ½”λ“œλ₯Ό λΆ™μ—¬ λ„£μœΌμ„Έμš”", elem_classes="header-markdown")
# μ±„νŒ… λ°•μŠ€ 높이λ₯Ό 400px둜 μ§€μ •ν•˜μ—¬ ν™”λ©΄ 높이에 맞게 μ€„μž„.
chatbot = gr.Chatbot(
label="λŒ€ν™”",
elem_classes="output-group",
height=400
)
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 토큰 이상 μž‘μ„±ν•˜λΌ."],
["ν˜μ‹ μ μ΄κ³  논리적인 μ „λ¬Έ λ…Όλ¬Έμ˜ ν˜•μ‹μœΌλ‘œ 4000 토큰 이상 μž‘μ„±ν•˜λΌ."],
["계속 μ΄μ–΄μ„œ λ‹΅λ³€ν•˜λΌ"],
]
gr.Examples(examples, inputs=msg)
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_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