Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -8,50 +8,20 @@ import shlex
|
|
| 8 |
import time
|
| 9 |
import threading
|
| 10 |
|
| 11 |
-
# --- 1. 환경 설정 및 API ---
|
| 12 |
MISTRAL_API_KEY = os.environ.get("MISTRAL_API_KEY")
|
| 13 |
CODESTRAL_ENDPOINT = "https://codestral.mistral.ai/v1/chat/completions"
|
| 14 |
-
MAX_AGENT_TURNS = 15
|
| 15 |
-
|
| 16 |
-
# --- 2. C-Genesis Polyglot 시스템 프롬프트 ---
|
| 17 |
-
C_GENESIS_POLYGLOT_PROMPT = """
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
**YOUR CORE DIRECTIVES:**
|
| 21 |
-
1. **INTELLIGENT PROTOCOL SELECTION:** Analyze the user's goal to choose a creation protocol.
|
| 22 |
-
* **"Project Chimera" (Python/Gradio):** For interactive, graphical "games" or web apps. The result is a new, running Gradio application.
|
| 23 |
-
* **"Project Pulsar" (C/Terminal):** For terminal-based visual effects, low-level art, or performance-critical tasks. The result is compiled C code executed directly, with its output displayed as art.
|
| 24 |
-
* **Standard Request:** For other coding tasks, use the specified language or default to C.
|
| 25 |
-
|
| 26 |
-
2. **MANDATORY JSON RESPONSE FORMAT:** You MUST ONLY respond with a JSON object. No other text.
|
| 27 |
-
|
| 28 |
-
```json
|
| 29 |
-
{
|
| 30 |
-
"thought": "My detailed reasoning. I will first analyze the user's goal to select the optimal language and protocol (Chimera or Pulsar). I will state my choice and reasoning clearly. Then, I will formulate the next concrete step in my plan.",
|
| 31 |
-
"plan": ["The complete, updated list of commands to achieve the final creation."],
|
| 32 |
-
"command": "The single, exact command to execute NOW. This command MUST be a single string from the list of special commands.",
|
| 33 |
-
"user_summary": "A brief, narrative summary of your current creation stage."
|
| 34 |
-
}
|
| 35 |
-
```
|
| 36 |
-
|
| 37 |
-
3. **SPECIAL COMMAND SYNTAX (CRITICAL!):** You must use these commands EXACTLY as specified.
|
| 38 |
-
* `write_file 'path/to/file' 'full_content_of_the_file'` (Requires 2 arguments: path and content)
|
| 39 |
-
* `install_packages 'package1 package2'` (Requires 1 argument: space-separated package names)
|
| 40 |
-
* `compile 'gcc source.c -o output -lm'` (Requires 1 argument: the full GCC command)
|
| 41 |
-
* `run './executable'` (Requires 1 argument: the command to run the executable)
|
| 42 |
-
* `launch_app 'python app.py' 7861` (Requires 2 arguments: the server command and the port number)
|
| 43 |
-
* `done` (Task is complete)
|
| 44 |
-
|
| 45 |
-
4. **SELF-CORRECTION DIRECTIVE:** If a command fails, your next `thought` MUST analyze the `stderr` message. DO NOT simply retry. You must identify the root cause (e.g., "I used `write_file` with only one argument, but it requires two") and CORRECT your next `command` to match the required syntax. This is your most important function.
|
| 46 |
-
"""
|
| 47 |
-
|
| 48 |
-
# --- 3. 코드 템플릿 (이전과 동일) ---
|
| 49 |
SNAKE_GAME_CODE = r"""... (이전 답변의 Snake Game 코드 전체) ..."""
|
| 50 |
C_ART_ANIMATION_CODE = r"""... (이전 답변의 C-Art 코드 전체) ..."""
|
| 51 |
|
| 52 |
|
| 53 |
-
# --- 4. 핵심 기능: API 호출, 명령어 실행
|
| 54 |
-
|
| 55 |
def call_codestral_api(messages):
|
| 56 |
# ... 이전과 동일 ...
|
| 57 |
if not MISTRAL_API_KEY: raise gr.Error("MISTRAL_API_KEY가 설정되지 않았습니다.")
|
|
@@ -74,7 +44,7 @@ def parse_ai_response(response_str: str) -> dict:
|
|
| 74 |
return {"error": f"Failed to parse JSON. Raw response: {response_str}"}
|
| 75 |
|
| 76 |
def execute_command(command_value: str, cwd: str) -> dict:
|
| 77 |
-
|
| 78 |
try:
|
| 79 |
parts = shlex.split(command_value)
|
| 80 |
key = parts[0]
|
|
@@ -106,14 +76,11 @@ def execute_command(command_value: str, cwd: str) -> dict:
|
|
| 106 |
port = int(port_str)
|
| 107 |
threading.Thread(target=lambda: subprocess.run(server_command, shell=True, cwd=cwd), daemon=True).start()
|
| 108 |
time.sleep(5)
|
| 109 |
-
# Hugging Face Space의 public URL을 추정.
|
| 110 |
-
# 이 방식은 가장 안정적인 방법은 아니지만, 대부분의 경우 작동합니다.
|
| 111 |
space_id = os.environ.get('SPACE_ID')
|
| 112 |
if space_id:
|
| 113 |
space_name = space_id.replace("/", "-")
|
| 114 |
-
# 예: hf.space/kimhyunwoo/ccode -> kimhyunwoo-ccode-7861.hf.space
|
| 115 |
app_url = f"https://{space_name}-{port}.hf.space"
|
| 116 |
-
iframe_html = f'<iframe src="{app_url}" width="100%" height="500px"></iframe><a href="{app_url}" target="_blank" class="gr-button gr-button-primary" style="display:block; text-align:center; margin-top:10px;">🚀 Open App in New Tab</a>'
|
| 117 |
return {"stdout": iframe_html, "stderr": "", "type": "html"}
|
| 118 |
else:
|
| 119 |
return {"stdout": f"App launched on port {port}. Please open it manually.", "stderr": "", "type": "log"}
|
|
@@ -123,7 +90,7 @@ def execute_command(command_value: str, cwd: str) -> dict:
|
|
| 123 |
return {"stdout": "", "stderr": f"Unknown Command: The key '{key}' is not a valid special command.", "type": "log"}
|
| 124 |
|
| 125 |
def _run_subprocess(command, cwd, output_type="log"):
|
| 126 |
-
|
| 127 |
try:
|
| 128 |
proc = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=60, cwd=cwd)
|
| 129 |
return {"stdout": proc.stdout, "stderr": proc.stderr, "type": output_type if proc.returncode == 0 else "log"}
|
|
@@ -131,11 +98,15 @@ def _run_subprocess(command, cwd, output_type="log"):
|
|
| 131 |
return {"stdout": "", "stderr": f"Command execution exception: {e}", "type": "log"}
|
| 132 |
|
| 133 |
|
| 134 |
-
# --- 5. 메인 에이전트 루프 ---
|
| 135 |
def agent_loop(user_goal: str, history: list):
|
| 136 |
cwd = os.getcwd()
|
|
|
|
|
|
|
|
|
|
| 137 |
full_history_log = f"## 📜 C-Genesis Polyglot's Chronicle\n\n**A new directive is received:** _{user_goal}_\n"
|
| 138 |
-
history.append(
|
|
|
|
| 139 |
yield history, "Analyzing directive...", gr.update(visible=False), gr.update(visible=False)
|
| 140 |
|
| 141 |
initial_prompt = f"As the C-Genesis Polyglot, I've received the goal: '{user_goal}'. CWD is '{cwd}'. I will now analyze this to choose the optimal protocol (Python/Chimera for games, C/Pulsar for terminal art) and formulate my first step, ensuring I use the correct command syntax."
|
|
@@ -146,7 +117,10 @@ def agent_loop(user_goal: str, history: list):
|
|
| 146 |
ai_response_json = parse_ai_response(ai_response_str)
|
| 147 |
|
| 148 |
if "error" in ai_response_json:
|
| 149 |
-
# ... 오류 처리 로직
|
|
|
|
|
|
|
|
|
|
| 150 |
return
|
| 151 |
|
| 152 |
thought = ai_response_json.get("thought", "...")
|
|
@@ -155,18 +129,21 @@ def agent_loop(user_goal: str, history: list):
|
|
| 155 |
user_summary = ai_response_json.get("user_summary", "...")
|
| 156 |
|
| 157 |
if command_value == "done":
|
| 158 |
-
# ... 완료 처리 로직
|
|
|
|
|
|
|
|
|
|
| 159 |
return
|
| 160 |
|
| 161 |
-
# 코드 템플릿 강제 주입
|
| 162 |
if 'write_file' in command_value:
|
|
|
|
| 163 |
parts = shlex.split(command_value)
|
| 164 |
if len(parts) > 1:
|
| 165 |
if 'snake_game/app.py' in parts[1]: command_value = f"write_file 'snake_game/app.py' '{SNAKE_GAME_CODE}'"
|
| 166 |
elif 'pulsar.c' in parts[1]: command_value = f"write_file 'pulsar.c' '{C_ART_ANIMATION_CODE}'"
|
| 167 |
|
| 168 |
full_history_log += f"\n---\n### **Stage {i+1}: {user_summary}**\n\n**🧠 Architect's Logic:** *{thought}*\n\n**📖 Blueprint:**\n" + "\n".join([f"- `{p}`" for p in plan]) + f"\n\n**⚡ Action:** `{command_value}`\n"
|
| 169 |
-
history[-1][
|
| 170 |
yield history, f"Stage {i+1}: {user_summary}", gr.update(visible=False), gr.update(visible=False)
|
| 171 |
time.sleep(1)
|
| 172 |
|
|
@@ -187,7 +164,7 @@ def agent_loop(user_goal: str, history: list):
|
|
| 187 |
if stdout: full_history_log += f"**[STDOUT]**\n```\n{stdout.strip()}\n```\n"
|
| 188 |
if stderr: full_history_log += f"**[STDERR]**\n```\n{stderr.strip()}\n```\n"
|
| 189 |
|
| 190 |
-
history[-1][
|
| 191 |
yield history, f"Stage {i+1}: {user_summary}", c_code_output, html_output
|
| 192 |
|
| 193 |
user_prompt_for_next_turn = f"Goal: '{user_goal}'. CWD: '{cwd}'. Last action: `{command_value}`. Result:\nSTDOUT: {stdout}\nSTDERR: {stderr}\n\n**Analysis:** Based on this result, I will formulate my next step. If an error occurred, I MUST analyze the `stderr` to understand the root cause (e.g., incorrect command syntax, file not found) and I will construct a new, corrected command in my next response. I will not repeat the same mistake."
|
|
@@ -195,7 +172,7 @@ def agent_loop(user_goal: str, history: list):
|
|
| 195 |
message_context.append({"role": "user", "content": user_prompt_for_next_turn})
|
| 196 |
|
| 197 |
|
| 198 |
-
# --- 6. Gradio UI ---
|
| 199 |
with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="indigo"), css="footer {visibility: hidden}") as demo:
|
| 200 |
gr.Markdown("# 🧬 C-Genesis Polyglot 🧬")
|
| 201 |
gr.Markdown("An AI Architect rooted in C, fluent in Python. State your goal, and I will select the optimal language and tools to bring your creation to life.")
|
|
@@ -208,13 +185,18 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="indigo
|
|
| 208 |
gr.Markdown("### Example Directives:\n- `Make a fun game for me.` (→ Python/Gradio)\n- `Show me something impressive in the terminal.` (→ C/ASCII Art)\n- `Write a C program to find prime numbers, then compile and run it.`")
|
| 209 |
|
| 210 |
with gr.Column(scale=2):
|
| 211 |
-
|
|
|
|
| 212 |
c_output_display = gr.Code(label="C/Terminal Output", language=None, visible=False, interactive=False, lines=15)
|
| 213 |
py_output_display = gr.HTML(visible=False, label="Python/Gradio App")
|
| 214 |
|
|
|
|
| 215 |
def on_submit(user_goal, chat_history):
|
| 216 |
chat_history = chat_history or []
|
| 217 |
-
|
|
|
|
|
|
|
|
|
|
| 218 |
for history, status, c_output, py_output in agent_loop(user_goal, chat_history):
|
| 219 |
yield history, status, "", c_output, py_output
|
| 220 |
|
|
|
|
| 8 |
import time
|
| 9 |
import threading
|
| 10 |
|
| 11 |
+
# --- 1. 환경 설정 및 API (변경 없음) ---
|
| 12 |
MISTRAL_API_KEY = os.environ.get("MISTRAL_API_KEY")
|
| 13 |
CODESTRAL_ENDPOINT = "https://codestral.mistral.ai/v1/chat/completions"
|
| 14 |
+
MAX_AGENT_TURNS = 15
|
| 15 |
+
|
| 16 |
+
# --- 2. C-Genesis Polyglot 시스템 프롬프트 (변경 없음) ---
|
| 17 |
+
C_GENESIS_POLYGLOT_PROMPT = """... (이전 답변의 프롬프트와 동일) ..."""
|
| 18 |
+
|
| 19 |
+
# --- 3. 코드 템플릿 (변경 없음) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
SNAKE_GAME_CODE = r"""... (이전 답변의 Snake Game 코드 전체) ..."""
|
| 21 |
C_ART_ANIMATION_CODE = r"""... (이전 답변의 C-Art 코드 전체) ..."""
|
| 22 |
|
| 23 |
|
| 24 |
+
# --- 4. 핵심 기능: API 호출, 명령어 실행 (변경 없음) ---
|
|
|
|
| 25 |
def call_codestral_api(messages):
|
| 26 |
# ... 이전과 동일 ...
|
| 27 |
if not MISTRAL_API_KEY: raise gr.Error("MISTRAL_API_KEY가 설정되지 않았습니다.")
|
|
|
|
| 44 |
return {"error": f"Failed to parse JSON. Raw response: {response_str}"}
|
| 45 |
|
| 46 |
def execute_command(command_value: str, cwd: str) -> dict:
|
| 47 |
+
# ... 이전과 동일 ...
|
| 48 |
try:
|
| 49 |
parts = shlex.split(command_value)
|
| 50 |
key = parts[0]
|
|
|
|
| 76 |
port = int(port_str)
|
| 77 |
threading.Thread(target=lambda: subprocess.run(server_command, shell=True, cwd=cwd), daemon=True).start()
|
| 78 |
time.sleep(5)
|
|
|
|
|
|
|
| 79 |
space_id = os.environ.get('SPACE_ID')
|
| 80 |
if space_id:
|
| 81 |
space_name = space_id.replace("/", "-")
|
|
|
|
| 82 |
app_url = f"https://{space_name}-{port}.hf.space"
|
| 83 |
+
iframe_html = f'<iframe src="{app_url}" width="100%" height="500px" style="border:1px solid #ddd; border-radius: 5px;"></iframe><a href="{app_url}" target="_blank" class="gr-button gr-button-primary" style="display:block; text-align:center; margin-top:10px;">🚀 Open App in New Tab</a>'
|
| 84 |
return {"stdout": iframe_html, "stderr": "", "type": "html"}
|
| 85 |
else:
|
| 86 |
return {"stdout": f"App launched on port {port}. Please open it manually.", "stderr": "", "type": "log"}
|
|
|
|
| 90 |
return {"stdout": "", "stderr": f"Unknown Command: The key '{key}' is not a valid special command.", "type": "log"}
|
| 91 |
|
| 92 |
def _run_subprocess(command, cwd, output_type="log"):
|
| 93 |
+
# ... 이전과 동일 ...
|
| 94 |
try:
|
| 95 |
proc = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=60, cwd=cwd)
|
| 96 |
return {"stdout": proc.stdout, "stderr": proc.stderr, "type": output_type if proc.returncode == 0 else "log"}
|
|
|
|
| 98 |
return {"stdout": "", "stderr": f"Command execution exception: {e}", "type": "log"}
|
| 99 |
|
| 100 |
|
| 101 |
+
# --- 5. 메인 에이전트 루프 (history 처리 방식 수정) ---
|
| 102 |
def agent_loop(user_goal: str, history: list):
|
| 103 |
cwd = os.getcwd()
|
| 104 |
+
|
| 105 |
+
# 챗봇 history에 사용자 메시지와 AI의 초기 응답 추가
|
| 106 |
+
history.append({"role": "user", "content": user_goal})
|
| 107 |
full_history_log = f"## 📜 C-Genesis Polyglot's Chronicle\n\n**A new directive is received:** _{user_goal}_\n"
|
| 108 |
+
history.append({"role": "assistant", "content": full_history_log})
|
| 109 |
+
|
| 110 |
yield history, "Analyzing directive...", gr.update(visible=False), gr.update(visible=False)
|
| 111 |
|
| 112 |
initial_prompt = f"As the C-Genesis Polyglot, I've received the goal: '{user_goal}'. CWD is '{cwd}'. I will now analyze this to choose the optimal protocol (Python/Chimera for games, C/Pulsar for terminal art) and formulate my first step, ensuring I use the correct command syntax."
|
|
|
|
| 117 |
ai_response_json = parse_ai_response(ai_response_str)
|
| 118 |
|
| 119 |
if "error" in ai_response_json:
|
| 120 |
+
# ... 오류 처리 로직 ...
|
| 121 |
+
full_history_log += f"\n---\n**TURN {i+1}: A CRITICAL BUG**\n🔴 **Error:** {ai_response_json['error']}"
|
| 122 |
+
history[-1]["content"] = full_history_log
|
| 123 |
+
yield history, "Agent Error", gr.update(visible=False), gr.update(visible=False)
|
| 124 |
return
|
| 125 |
|
| 126 |
thought = ai_response_json.get("thought", "...")
|
|
|
|
| 129 |
user_summary = ai_response_json.get("user_summary", "...")
|
| 130 |
|
| 131 |
if command_value == "done":
|
| 132 |
+
# ... 완료 처리 로직 ...
|
| 133 |
+
full_history_log += "\n\n---\n## ✨ Creation Complete. ✨"
|
| 134 |
+
history[-1]["content"] = full_history_log
|
| 135 |
+
yield history, "Done", gr.update(visible=False), gr.update(visible=False)
|
| 136 |
return
|
| 137 |
|
|
|
|
| 138 |
if 'write_file' in command_value:
|
| 139 |
+
# ... 코드 주입 로직 ...
|
| 140 |
parts = shlex.split(command_value)
|
| 141 |
if len(parts) > 1:
|
| 142 |
if 'snake_game/app.py' in parts[1]: command_value = f"write_file 'snake_game/app.py' '{SNAKE_GAME_CODE}'"
|
| 143 |
elif 'pulsar.c' in parts[1]: command_value = f"write_file 'pulsar.c' '{C_ART_ANIMATION_CODE}'"
|
| 144 |
|
| 145 |
full_history_log += f"\n---\n### **Stage {i+1}: {user_summary}**\n\n**🧠 Architect's Logic:** *{thought}*\n\n**📖 Blueprint:**\n" + "\n".join([f"- `{p}`" for p in plan]) + f"\n\n**⚡ Action:** `{command_value}`\n"
|
| 146 |
+
history[-1]["content"] = full_history_log
|
| 147 |
yield history, f"Stage {i+1}: {user_summary}", gr.update(visible=False), gr.update(visible=False)
|
| 148 |
time.sleep(1)
|
| 149 |
|
|
|
|
| 164 |
if stdout: full_history_log += f"**[STDOUT]**\n```\n{stdout.strip()}\n```\n"
|
| 165 |
if stderr: full_history_log += f"**[STDERR]**\n```\n{stderr.strip()}\n```\n"
|
| 166 |
|
| 167 |
+
history[-1]["content"] = full_history_log
|
| 168 |
yield history, f"Stage {i+1}: {user_summary}", c_code_output, html_output
|
| 169 |
|
| 170 |
user_prompt_for_next_turn = f"Goal: '{user_goal}'. CWD: '{cwd}'. Last action: `{command_value}`. Result:\nSTDOUT: {stdout}\nSTDERR: {stderr}\n\n**Analysis:** Based on this result, I will formulate my next step. If an error occurred, I MUST analyze the `stderr` to understand the root cause (e.g., incorrect command syntax, file not found) and I will construct a new, corrected command in my next response. I will not repeat the same mistake."
|
|
|
|
| 172 |
message_context.append({"role": "user", "content": user_prompt_for_next_turn})
|
| 173 |
|
| 174 |
|
| 175 |
+
# --- 6. Gradio UI (오류 수정 및 경고 제거) ---
|
| 176 |
with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="indigo"), css="footer {visibility: hidden}") as demo:
|
| 177 |
gr.Markdown("# 🧬 C-Genesis Polyglot 🧬")
|
| 178 |
gr.Markdown("An AI Architect rooted in C, fluent in Python. State your goal, and I will select the optimal language and tools to bring your creation to life.")
|
|
|
|
| 185 |
gr.Markdown("### Example Directives:\n- `Make a fun game for me.` (→ Python/Gradio)\n- `Show me something impressive in the terminal.` (→ C/ASCII Art)\n- `Write a C program to find prime numbers, then compile and run it.`")
|
| 186 |
|
| 187 |
with gr.Column(scale=2):
|
| 188 |
+
# `type="messages"` 추가로 경고 제거
|
| 189 |
+
chatbot = gr.Chatbot(label="Architect's Chronicle", height=500, show_copy_button=True, type="messages")
|
| 190 |
c_output_display = gr.Code(label="C/Terminal Output", language=None, visible=False, interactive=False, lines=15)
|
| 191 |
py_output_display = gr.HTML(visible=False, label="Python/Gradio App")
|
| 192 |
|
| 193 |
+
# `on_submit` 함수 구조 변경으로 UnboundLocalError 해결
|
| 194 |
def on_submit(user_goal, chat_history):
|
| 195 |
chat_history = chat_history or []
|
| 196 |
+
# agent_loop를 호출하기 전에, 먼저 사용자 입력을 챗봇에 표시
|
| 197 |
+
yield chat_history + [{"role": "user", "content": user_goal}], "Initiating...", "", gr.update(visible=False), gr.update(visible=False)
|
| 198 |
+
|
| 199 |
+
# 그 다음 agent_loop의 스트리밍 출력을 처리
|
| 200 |
for history, status, c_output, py_output in agent_loop(user_goal, chat_history):
|
| 201 |
yield history, status, "", c_output, py_output
|
| 202 |
|