File size: 2,943 Bytes
9ebf75a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import base64
import gradio as gr
from huggingface_hub import InferenceClient

# Free serverless models
TEXT_MODEL = "Qwen/Qwen2.5-Coder-32B-Instruct"   # strong at PHP / WordPress code
VISION_MODEL = "Qwen/Qwen2.5-VL-72B-Instruct"     # can read screenshots

client = InferenceClient()

SYSTEM_PROMPT = (
    "You are a senior WordPress plugin developer and web developer assistant. "
    "You help write, debug, and explain PHP, WordPress plugin code (hooks, filters, "
    "shortcodes, admin settings pages, Media Library integration), HTML, CSS, and "
    "JavaScript. When shown a screenshot of a website, describe the layout/section "
    "the user points to, then rebuild it as real HTML/CSS/PHP as closely as possible. "
    "Give complete, working code. When code is long, deliver it in full, installable "
    "form (ready to save as a .php file)."
)

def image_to_data_url(img_path):
    with open(img_path, "rb") as f:
        b64 = base64.b64encode(f.read()).decode("utf-8")
    ext = img_path.split(".")[-1].lower()
    mime = "jpeg" if ext in ("jpg", "jpeg") else ext
    return f"data:image/{mime};base64,{b64}"

def chat(message, history):
    # message can be a dict with "text" and "files" when multimodal input is used
    text = message.get("text", "") if isinstance(message, dict) else message
    files = message.get("files", []) if isinstance(message, dict) else []

    messages = [{"role": "system", "content": SYSTEM_PROMPT}]
    for turn in history:
        if isinstance(turn, dict):
            role = turn.get("role")
            content = turn.get("content")
            if role and content:
                messages.append({"role": role, "content": content})

    if files:
        # Vision path: send image + text together to the VL model
        content = [{"type": "text", "text": text or "What is in this screenshot? Rebuild the relevant section as code."}]
        for f in files:
            content.append({"type": "image_url", "image_url": {"url": image_to_data_url(f)}})
        messages.append({"role": "user", "content": content})
        model = VISION_MODEL
    else:
        messages.append({"role": "user", "content": text})
        model = TEXT_MODEL

    try:
        response = client.chat_completion(
            model=model,
            messages=messages,
            max_tokens=2048,
            temperature=0.3,
        )
        return response.choices[0].message.content
    except Exception as e:
        return f"⚠️ Error calling the model: {e}"

demo = gr.ChatInterface(
    fn=chat,
    title="Free WordPress Dev Assistant (with screenshot support)",
    description=(
        "Free coding helper for WordPress plugins, PHP, HTML, and JS. "
        "Attach a screenshot and ask it to rebuild that section as code. "
        "Runs on Qwen2.5-Coder and Qwen2.5-VL via Hugging Face's free Inference API."
    ),
    multimodal=True,
)

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