File size: 7,365 Bytes
b7082b3
 
 
 
 
 
563534f
b7082b3
563534f
b7082b3
 
 
 
563534f
b7082b3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563534f
b7082b3
563534f
b7082b3
 
 
563534f
b7082b3
 
 
 
 
 
563534f
b7082b3
563534f
 
b7082b3
 
 
 
 
 
 
563534f
b7082b3
563534f
 
b7082b3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563534f
 
b7082b3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563534f
 
b7082b3
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
\
import os
import re
import json
import tempfile
import zipfile
import gradio as gr
from huggingface_hub import hf_hub_download

# ---- LLM: llama.cpp via llama_cpp_agent ----
from llama_cpp import Llama
from llama_cpp_agent import LlamaCppAgent, MessagesFormatterType
from llama_cpp_agent.providers import LlamaCppPythonProvider

# ----------------------
# Model configuration
# ----------------------
# You can change these defaults in the UI
DEFAULT_REPO_ID = "tHottie/NeuralDaredevil-8B-abliterated-Q4_K_M-GGUF"
DEFAULT_FILENAME = "neuraldaredevil-8b-abliterated-q4_k_m-imat.gguf"
MODELS_DIR = "models"

os.makedirs(MODELS_DIR, exist_ok=True)

def ensure_model(repo_id: str, filename: str) -> str:
    local_path = os.path.join(MODELS_DIR, filename)
    if not os.path.exists(local_path):
        hf_hub_download(repo_id=repo_id, filename=filename, local_dir=MODELS_DIR)
    return local_path

def build_agent(model_path: str, n_ctx=8192, n_gpu_layers=81, n_batch=1024, flash_attn=True):
    llm = Llama(
        model_path=model_path,
        n_ctx=n_ctx,
        n_gpu_layers=n_gpu_layers,
        n_batch=n_batch,
        flash_attn=flash_attn,
    )
    provider = LlamaCppPythonProvider(llm)
    agent = LlamaCppAgent(
        provider,
        system_prompt=(
            "You are an expert code-packager and software project compiler. "
            "Given an AI-generated project description containing code blocks and hints "
            "about filenames or structure, extract each file with its most likely filename "
            "and exact code content. Return ONLY a strict JSON array named manifest, "
            "where each element is an object with keys 'filename' and 'content'. "
            "Do not add commentary outside JSON. "
            "Ensure filenames include directories if implied (e.g., 'src/main.py'). "
            "Preserve code exactly as provided inside code fences."
        ),
        predefined_messages_formatter_type=MessagesFormatterType.GEMMA_2,
        debug_output=False
    )
    return agent, provider

JSON_FALLBACK_NAME = "project.txt"

def call_llm_manifest(agent, provider, text, temperature=0.2, top_p=0.9, top_k=40, repeat_penalty=1.1, max_tokens=2048):
    # Instruction prompt. Model must respond with STRICT JSON.
    prompt = f"""
Read the following AI project description and return ONLY JSON. 
Output schema (strict):
[{{"filename": "server.js", "content": "// code..."}}]

AI project description:
{text}
"""
    settings = provider.get_provider_default_settings()
    settings.temperature = float(temperature)
    settings.top_p = float(top_p)
    settings.top_k = int(top_k)
    settings.repeat_penalty = float(repeat_penalty)
    settings.max_tokens = int(max_tokens)
    settings.stream = False

    out = agent.get_chat_response(prompt, llm_sampling_settings=settings, print_output=False)
    # Try to extract a JSON array from the output robustly
    json_text = None
    try:
        # Prefer the largest bracketed array slice
        start = out.find('[')
        end = out.rfind(']')
        if start != -1 and end != -1 and end > start:
            json_text = out[start:end+1]
            manifest = json.loads(json_text)
        else:
            raise ValueError("No JSON array found")
    except Exception:
        # Fallback: single-file package of raw output for transparency
        manifest = [ {"filename": JSON_FALLBACK_NAME, "content": out} ]
    return manifest

def naive_regex_merge(text):
    """
    Heuristic backup that maps code fences to probable filenames by scanning nearby lines.
    This runs only when the model output is a single fallback file OR user ticks 'Force Heuristic Merge'.
    """
    blocks = []
    # Find all triple-backtick code blocks
    code_pattern = re.compile(r"```([a-zA-Z0-9]*)\n(.*?)```", re.DOTALL)
    # Find filename candidates in preceding lines such as '### STEP: server.js' or '`server.js`'
    lines = text.splitlines()
    candidates = []
    for i, line in enumerate(lines):
        m = re.search(r"([A-Za-z0-9_\\-./]+?\\.[A-Za-z0-9]+)", line)
        if m:
            candidates.append(m.group(1))

    for idx, m in enumerate(code_pattern.finditer(text)):
        lang = m.group(1) or "txt"
        code = m.group(2)
        filename = candidates[idx] if idx < len(candidates) else f"file_{idx+1}.{lang}"
        blocks.append({"filename": filename, "content": code})
    return blocks

def create_zip_from_manifest(manifest):
    temp_dir = tempfile.mkdtemp()
    zip_path = os.path.join(temp_dir, "project.zip")
    with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as z:
        for item in manifest:
            fname = item.get("filename", JSON_FALLBACK_NAME).lstrip("/")
            content = item.get("content", "")
            fpath = os.path.join(temp_dir, fname)
            os.makedirs(os.path.dirname(fpath), exist_ok=True)
            with open(fpath, "w", encoding="utf-8") as f:
                f.write(content)
            z.write(fpath, arcname=fname)
    return zip_path

def package_with_llm(ai_text, repo_id, filename, temperature, top_p, top_k, repeat_penalty, max_tokens, force_heuristic):
    model_path = ensure_model(repo_id, filename)
    agent, provider = build_agent(model_path=model_path)

    manifest = call_llm_manifest(
        agent, provider, ai_text,
        temperature=temperature, top_p=top_p, top_k=top_k,
        repeat_penalty=repeat_penalty, max_tokens=max_tokens
    )

    # If model failed to JSON-ify properly (single fallback) or user forces merge, try heuristic merge
    if force_heuristic or (len(manifest) == 1 and manifest[0]["filename"] == JSON_FALLBACK_NAME):
        heuristic = naive_regex_merge(ai_text)
        if heuristic:
            manifest = heuristic

    zip_path = create_zip_from_manifest(manifest)
    return zip_path

with gr.Blocks(title="AI Project Packager (GGUF, llama.cpp)") as demo:
    gr.Markdown("# AI Project Packager (GGUF, llama.cpp)")
    gr.Markdown("Paste an AI-generated multi-file project description. A local GGUF model will infer filenames and contents, then return a downloadable ZIP.")

    with gr.Row():
        ai_text = gr.Textbox(lines=24, label="Paste AI response here")

    with gr.Accordion("LLM Settings", open=False):
        repo_id = gr.Textbox(value=DEFAULT_REPO_ID, label="Model repo_id")
        filename = gr.Textbox(value=DEFAULT_FILENAME, label="Model filename (*.gguf)")
        with gr.Row():
            temperature = gr.Slider(0.0, 2.0, value=0.2, step=0.05, label="Temperature")
            top_p = gr.Slider(0.1, 1.0, value=0.9, step=0.05, label="Top-p")
            top_k = gr.Slider(0, 100, value=40, step=1, label="Top-k")
        with gr.Row():
            repeat_penalty = gr.Slider(0.8, 2.0, value=1.1, step=0.05, label="Repeat penalty")
            max_tokens = gr.Slider(256, 4096, value=2048, step=32, label="Max tokens")
        force_heuristic = gr.Checkbox(value=False, label="Force heuristic filename/code merge if JSON parse fails")

    out_zip = gr.File(label="Download packaged ZIP")

    run_btn = gr.Button("Package Project", variant="primary")
    run_btn.click(
        fn=package_with_llm,
        inputs=[ai_text, repo_id, filename, temperature, top_p, top_k, repeat_penalty, max_tokens, force_heuristic],
        outputs=[out_zip]
    )

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