c
File size: 11,163 Bytes
e0c0af8
f75669b
 
db2f9f4
a5c7603
db2f9f4
c1ec9e7
f75669b
667d6ee
 
db2f9f4
fbae41a
667d6ee
6494181
667d6ee
 
 
 
6494181
db2f9f4
667d6ee
a5c7603
6494181
db2f9f4
a5c7603
 
6494181
a5c7603
667d6ee
db2f9f4
6494181
667d6ee
6494181
 
 
667d6ee
db2f9f4
f75669b
302b980
6494181
 
667d6ee
 
302b980
f75669b
6494181
 
 
 
 
 
 
 
 
667d6ee
302b980
6494181
 
667d6ee
bb4dd6d
6494181
 
 
 
 
 
 
 
 
 
 
 
 
c1ec9e7
6494181
 
667d6ee
c1ec9e7
6494181
c1ec9e7
6494181
 
 
 
 
667d6ee
c1ec9e7
6494181
667d6ee
6494181
 
 
 
667d6ee
c1ec9e7
6494181
 
 
 
 
 
 
 
 
 
 
c1ec9e7
6494181
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667d6ee
6494181
 
 
 
 
c1ec9e7
 
6494181
 
667d6ee
6494181
 
c1ec9e7
6494181
 
c1ec9e7
6494181
b328beb
6494181
db2f9f4
b328beb
c1ec9e7
 
b328beb
c1ec9e7
b328beb
 
6494181
c1ec9e7
6494181
 
c1ec9e7
 
6494181
c1ec9e7
 
 
 
 
6494181
c1ec9e7
b328beb
302b980
b328beb
302b980
 
667d6ee
e0c0af8
 
9c4c102
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import gradio as gr
import os
import subprocess
import requests
import json
import re
import time

# --- 1. ํ™˜๊ฒฝ ์„ค์ • ๋ฐ ๊ฐ•ํ™”๋œ API ํ˜ธ์ถœ ํ•จ์ˆ˜ ---
MISTRAL_API_KEY = os.environ.get("MISTRAL_API_KEY")
CODESTRAL_ENDPOINT = "https://codestral.mistral.ai/v1/chat/completions"

def call_mistral_api(system_prompt: str, user_prompt: str):
    """ํ–ฅ์ƒ๋œ Mistral API ํ˜ธ์ถœ ํ•จ์ˆ˜"""
    if not MISTRAL_API_KEY:
        raise gr.Error("MISTRAL_API_KEY is not set. Please add it to your Space Secrets.")
    
    headers = {"Authorization": f"Bearer {MISTRAL_API_KEY}", "Content-Type": "application/json"}
    messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}]
    data = {"model": "codestral-latest", "messages": messages}
    
    try:
        response = requests.post(CODESTRAL_ENDPOINT, headers=headers, data=json.dumps(data), timeout=60)
        response.raise_for_status()
        return response.json()["choices"][0]["message"]["content"]
    except requests.exceptions.RequestException as e:
        raise gr.Error(f"API Call Error: {e}")

# --- 2. ๋ฐฑ์—”๋“œ ํ•ต์‹ฌ ๊ธฐ๋Šฅ (์•ˆ์ •์„ฑ ๊ฐ•ํ™”) ---
def parse_code_from_response(response_text: str) -> str | None:
    """C ์ฝ”๋“œ ๋ธ”๋ก์„ ํŒŒ์‹ฑํ•˜๋Š” ์•ˆ์ •์ ์ธ ํ•จ์ˆ˜"""
    match = re.search(r'```(?:c)?\n(.*?)\n```', response_text, re.DOTALL)
    if match: return match.group(1).strip()
    # ๋น„์ƒ์‹œ ์ˆœ์ˆ˜ ์ฝ”๋“œ ์‘๋‹ต ์ฒ˜๋ฆฌ
    if response_text.strip().startswith("#include") and response_text.strip().endswith("}"):
        return response_text.strip()
    return None

def generate_c_code(description: str) -> str:
    system_prompt = "You are an expert C code generator..." # (์ด์ „๊ณผ ๋™์ผ)
    user_prompt = f"Generate C code for: '{description}'"
    response = call_mistral_api(system_prompt, user_prompt)
    return parse_code_from_response(response) or f"// Failed to parse code from response:\n{response}"

def compile_and_run_c_code(code: str) -> str:
    """์ปดํŒŒ์ผ ๋ฐ ์‹คํ–‰ ํ•จ์ˆ˜"""
    if not code.strip(): return "--- SYSTEM ERROR ---\nCode is empty."
    with open("main.c", "w", encoding='utf-8') as f: f.write(code)
    compile_proc = subprocess.run(["gcc", "main.c", "-o", "main.out", "-lm", "-w"], capture_output=True, text=True, timeout=15)
    if compile_proc.returncode != 0: return f"--- COMPILATION FAILED ---\n{compile_proc.stderr}"
    run_proc = subprocess.run(["./main.out"], capture_output=True, text=True, timeout=15)
    if run_proc.returncode != 0: return f"--- RUNTIME ERROR ---\n{run_proc.stderr}"
    output = run_proc.stdout
    return f"--- EXECUTION SUCCEEDED ---\n{output}" if output.strip() else "--- EXECUTION SUCCEEDED ---\n(No output)"

def analyze_and_refactor_code(code: str, instruction: str) -> str:
    system_prompt = "You are a world-class C code reviewer..." # (์ด์ „๊ณผ ๋™์ผ)
    user_prompt = f"Instruction: '{instruction}'\n\nC Code:\n```c\n{code}\n```"
    return call_mistral_api(system_prompt, user_prompt)

# โญ๏ธ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ: ์™ธ๋ถ€ MCP ํˆด์„ ์‚ฌ์šฉํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ ํ•จ์ˆ˜
def call_external_mcp_tool(tool_url: str, code: str, instruction: str) -> str:
    """๋‹ค๋ฅธ Gradio Space MCP ํˆด์„ API๋กœ ํ˜ธ์ถœํ•˜๋Š” ํ•จ์ˆ˜"""
    # Gradio ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์™ธ๋ถ€ API ํ˜ธ์ถœ (gradio_client ์„ค์น˜ ํ•„์š”)
    from gradio_client import Client
    try:
        client = Client(tool_url)
        # ์™ธ๋ถ€ ํˆด์˜ API ์—”๋“œํฌ์ธํŠธ์™€ ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„์— ๋งž์ถฐ์•ผ ํ•จ
        # ์˜ˆ์‹œ: predict(code_to_analyze=code, user_instruction=instruction)
        result = client.predict(code, instruction, api_name="/predict") # api_name์€ ์™ธ๋ถ€ ํˆด์— ๋”ฐ๋ผ ๋‹ค๋ฆ„
        return f"--- EXTERNAL TOOL SUCCEEDED ---\n{result}"
    except Exception as e:
        return f"--- EXTERNAL TOOL FAILED ---\nCould not call tool at {tool_url}. Error: {e}"

# --- 3. 1๋“ฑ์„ ์œ„ํ•œ ์ง€๋Šฅํ˜• ์—์ด์ „ํŠธ ๋กœ์ง (์ตœ์ข… ๋ฒ„์ „) ---
def ultimate_agent_ide(initial_code: str, full_instruction: str):
    tasks = [task.strip() for task in re.split(r'\s+and then\s+|\s+and\s+|,\s*then\s*|\s*๊ทธ๋ฆฌ๊ณ \s+|\s*ํ›„์—\s*', full_instruction, flags=re.IGNORECASE) if task.strip()]
    current_code = initial_code
    log = []

    # Step 1: ๊ณ„ํš ์ˆ˜๋ฆฝ
    log.append("### ๐Ÿ“ Agent's Plan")
    plan = "".join([f"\n{i+1}. {task}" for i, task in enumerate(tasks)])
    log.append(plan)
    yield current_code, "\n".join(log)
    time.sleep(1)

    # Step 2: ๊ณ„ํš ์‹คํ–‰
    for i, task in enumerate(tasks):
        log.append(f"\n<details><summary><b>โ–ถ Step {i+1}: {task}</b></summary>\n")
        yield current_code, "\n".join(log)
        time.sleep(0.5)

        lower_task = task.lower()
        
        # โญ๏ธ ์—์ด์ „ํŠธ์˜ '์ƒ๊ฐ'๊ณผ 'ํ–‰๋™'
        if "generate" in lower_task or "create" in lower_task or "๋งŒ๋“ค์–ด" in lower_task:
            log.append("๐Ÿง  **Thought:** The user wants new code. Using `generate_c_code` tool.")
            yield current_code, "\n".join(log)
            new_code = generate_c_code(task)
            if new_code and not new_code.startswith("//"):
                current_code = new_code
                log.append("\nโœ… **Action Result:** Code generated and updated in the editor.")
            else:
                log.append(f"\nโŒ **Action Result:** Generation failed. {new_code}")

        elif "compile" in lower_task or "run" in lower_task or "์‹คํ–‰" in lower_task:
            log.append("๐Ÿง  **Thought:** The user wants to compile and run. Using `compile_and_run_c_code` tool.")
            yield current_code, "\n".join(log)
            result = compile_and_run_c_code(current_code)
            log.append(f"\n๐Ÿ’ป **Action Result:**\n```\n{result}\n```")

            # โญ๏ธโญ๏ธ ์ž๊ฐ€ ์ˆ˜์ • (SELF-CORRECTION) ๋กœ์ง โญ๏ธโญ๏ธ
            if "COMPILATION FAILED" in result:
                log.append("\n\n๐Ÿง  **Thought:** Compilation failed. I will try to fix the code myself.")
                yield current_code, "\n".join(log)
                time.sleep(1)

                error_message = result.split("--- COMPILATION FAILED ---")[1]
                fix_instruction = f"The following C code failed to compile with this error:\n\n**Error:**\n```\n{error_message}\n```\n\nPlease fix the code so it compiles successfully. Provide only the complete, corrected C code."
                
                log.append("\n๐Ÿ› ๏ธ **Self-Correction:** Asking the LLM to fix the error...")
                yield current_code, "\n".join(log)
                
                fixed_code_response = analyze_and_refactor_code(current_code, fix_instruction)
                fixed_code = parse_code_from_response(fixed_code_response)

                if fixed_code:
                    current_code = fixed_code
                    log.append("\nโœ… **Self-Correction Result:** A potential fix has been applied to the code editor. Please try compiling again.")
                else:
                    log.append("\nโŒ **Self-Correction Result:** Failed to automatically fix the code.")
        
        # โญ๏ธโญ๏ธ ์™ธ๋ถ€ MCP ํˆด ์‚ฌ์šฉ ์˜ˆ์‹œ โญ๏ธโญ๏ธ
        elif "security" in lower_task or "๋ณด์•ˆ" in lower_task:
            log.append("๐Ÿง  **Thought:** The user wants a security analysis. I will use an external MCP tool for this.")
            yield current_code, "\n".join(log)
            # ์ด URL์€ ์˜ˆ์‹œ์ด๋ฉฐ, ์‹ค์ œ ์ž‘๋™ํ•˜๋Š” ๋ณด์•ˆ ๋ถ„์„ MCP Space๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ทธ ์ฃผ์†Œ๋ฅผ ๋„ฃ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
            # ํ•ด์ปคํ†ค ์ œ์ถœ ์‹œ, ์ง์ ‘ ๊ฐ„๋‹จํ•œ ๋ณด์•ˆ๋ถ„์„ ํˆด์„ ํ•˜๋‚˜ ๋” ๋งŒ๋“ค๊ฑฐ๋‚˜, ๋‹ค๋ฅธ ์ฐธ๊ฐ€์ž์˜ ํˆด์„ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ์Šต์„ ๋ณด์—ฌ์ฃผ๋ฉด ์ตœ๊ณ ์ž…๋‹ˆ๋‹ค.
            external_tool_url = "user-provided-security-tool-space-url" 
            log.append(f"\n๐Ÿ”Œ **Action:** Calling external tool at `{external_tool_url}`...")
            yield current_code, "\n".join(log)
            
            # ์‹ค์ œ๋กœ๋Š” instruction์—์„œ URL์„ ํŒŒ์‹ฑํ•ด์•ผ ํ•˜์ง€๋งŒ, ์—ฌ๊ธฐ์„œ๋Š” ํ•˜๋“œ์ฝ”๋”ฉ์œผ๋กœ ์˜ˆ์‹œ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
            security_result = call_external_mcp_tool(external_tool_url, current_code, task)
            log.append(f"\n๐Ÿ›ก๏ธ **Action Result:**\n```\n{security_result}\n```")

        else:
            log.append("๐Ÿง  **Thought:** The user wants to analyze or refactor. Using `analyze_and_refactor_code` tool.")
            yield current_code, "\n".join(log)
            analysis_result = analyze_and_refactor_code(current_code, task)
            refactored_code = parse_code_from_response(analysis_result)
            if refactored_code:
                current_code = refactored_code
                log.append("\nโœ… **Action Result:** Code refactored and updated in the editor.")
            log.append(f"\n๐Ÿ”Ž **Analysis Result:**\n{analysis_result}")
        
        log.append("</details>")
        yield current_code, "\n".join(log)

    log.append("\n\n--- All tasks complete. ---")
    yield current_code, "\n".join(log)

# --- 4. ํ†ตํ•ฉ๋œ Gradio UI (์ถœ๋ ฅ ์ปดํฌ๋„ŒํŠธ๋ฅผ Markdown์œผ๋กœ ๋ณ€๊ฒฝ) ---
with gr.Blocks(theme=gr.themes.Monochrome(primary_hue="indigo", secondary_hue="blue"), css="footer {visibility: hidden}") as demo:
    gr.Markdown("# ๐Ÿ† The Ultimate C-Codestral IDE Agent ๐Ÿ†")

    with gr.Tabs():
        with gr.TabItem("๐Ÿ‘จโ€๐Ÿ’ป IDE Agent"):
            with gr.Row(equal_height=True):
                with gr.Column(scale=2):
                    code_editor = gr.Code(label="C Code Editor", language="c", lines=28, interactive=True, value='#include <stdio.h>\n\nint main() {\n    printf("Hello, World!\\n");\n    return 0;\n}')
                
                with gr.Column(scale=1):
                    instruction_box = gr.Textbox(label="Instruction", placeholder="e.g., 'Refactor this code, then compile it, then check security'", lines=4)
                    execute_btn = gr.Button("Execute", variant="primary", size="lg")
                    # ์ถœ๋ ฅ์„ Markdown์œผ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ํ’๋ถ€ํ•œ UI๋ฅผ ์ œ๊ณต
                    output_box = gr.Markdown(label="Console / Output")

            execute_btn.click(
                fn=ultimate_agent_ide, 
                inputs=[code_editor, instruction_box], 
                outputs=[code_editor, output_box]
            )

        with gr.TabItem("๐Ÿ› ๏ธ MCP Tools API"):
             # MCP ํƒญ์€ ์ด์ „๊ณผ ๋™์ผํ•˜๊ฒŒ ์œ ์ง€
            gr.Markdown("## Available MCP Tools for other Agents\nThese APIs are the building blocks of our IDE agent.")
            with gr.Accordion("Tool: Generate C Code", open=False):
                gr.Interface(fn=generate_c_code, inputs="text", outputs=gr.Code(language="c", label="Generated C Code"))
            with gr.Accordion("Tool: Compile & Run C Code", open=False):
                gr.Interface(fn=compile_and_run_c_code, inputs=gr.Code(language="c"), outputs=gr.Textbox(label="Output"))
            with gr.Accordion("Tool: Analyze & Refactor C Code", open=False):
                gr.Interface(fn=analyze_and_refactor_code, inputs=[gr.Code(language="c", label="Code to Analyze"), gr.Textbox(label="Instruction")], outputs=gr.Markdown())

if __name__ == "__main__":
    demo.queue().launch()