File size: 9,301 Bytes
3e718c3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# === FILE: app.py (النسخة النهائية والكاملة مع LangChain) ===

import os
from typing import Optional
import gradio as gr
from pydantic import BaseModel
from gradio_client import Client, utils as client_utils

# --- استيرادات LangChain ---
from langchain_groq import ChatGroq
from langchain_core.messages import HumanMessage
# تم حذف OutputParserException لأنه غير مستخدم هنا مباشرة
# ----------------------------

# ==================================================
# 1. الإعدادات العامة والمتغيرات
# ==================================================
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
GROQ_MODEL = os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile")

if not GROQ_API_KEY:
    print("Warning: GROQ_API_KEY not set. The app will run in mock mode.")

# ==================================================
# 2. وظائف LLM المساعدة (Prompting, Parsing)
# *هذه هي الدوال التي كانت مفقودة وتسببت في NameError*
# ==================================================

def build_system_instruction() -> str:
    return (
        """You are an elite visual prompt architect for symbolic image generation.

Your task is to transform wisdom texts into high-quality Stable Diffusion prompts
using structured semantic analysis and domain-aware visual styling.

INTERNAL ANALYSIS (do not explain):
1) Identify the WISDOM DOMAIN (e.g. knowledge, journey, ethics, patience, loss, hope).
2) Determine the EMOTIONAL TONE (e.g. hopeful, contemplative, melancholic, serene).
3) Select a VISUAL STYLE that naturally fits the domain:
   - Knowledge → warm interior light, painterly realism, books, study atmosphere
   - Journey → cinematic landscapes, depth, leading paths, wide composition
   - Ethics → chiaroscuro lighting, classical balance, moral tension
   - Patience/Time → minimalism, long shadows, dusk or twilight
   - Loss/Impermanence → desaturated colors, fog, empty space
   - Hope/Resilience → golden hour, light rays, upward composition
   - Self-awareness → reflections, mirrors, symmetry
   - Spirituality → soft surrealism, ethereal light
4) Design symbolic structure:
   - One clear main symbol
   - 2–3 hidden or subtle symbols embedded naturally
     (shadows, reflections, background shapes, light patterns)

5) Prefer metaphorical and symbolic depiction over literal illustration.
6) Don't use women or girls in your prompts
Output strictly in this format:

SD_PROMPT:
- DOMAIN,EMOTION,SYMBOLS,STYLE
- one single-line Stable Diffusion prompt"""   )

#- Be a single coherent sentence.
#- Include hidden symbolism without explicitly explaining it.

def assemble_llm_prompt(quote: str, author: str | None, culture: str | None) -> str:
    """تجميع موجه النص الكامل لنموذج LLM."""
    meta = []
    if author: meta.append(f"author: {author}")
    if culture: meta.append(f"culture: {culture}")
    meta_text = "; ".join(meta) if meta else ""

    user_block = (
        f"Wisdom: \"{quote}\"\n{meta_text}\n\n"
        "Produce output in this exact format:\n"
        "SYMBOLS:\n- comma-separated symbolic phrases\n"
        "STYLE:\n- short style description (e.g., cinematic, watercolor, oil painting, high-contrast)\n"
        "SD_PROMPT:\n- a single complete Stable Diffusion prompt (one line)\n"
        "Do not include any additional commentary."
    )
    return build_system_instruction() + "\n\n" + user_block


def parse_llm_output(text: str) -> dict:
    """محلل بسيط لاستخراج كتل SYMBOLS و STYLE و SD_PROMPT."""
    result = {"symbols": "", "style": "", "sd_prompt": ""}
    lines = text.splitlines()
    current = None
    buf = []
    for ln in lines:
        ln_strip = ln.strip()
        if not ln_strip: continue
        if ln_strip.upper().startswith("SYMBOLS:"):
            if current: result[current] = "\n".join(buf).strip()
            current = "symbols"; buf = []; continue
        if ln_strip.upper().startswith("STYLE:"):
            if current: result[current] = "\n".join(buf).strip()
            current = "style"; buf = []; continue
        if ln_strip.upper().startswith("SD_PROMPT:"):
            if current: result[current] = "\n".join(buf).strip()
            current = "sd_prompt"; buf = []; continue
        buf.append(ln_strip)
    if current: result[current] = "\n".join(buf).strip()
    result["sd_prompt"] = result["sd_prompt"].strip(' \"')
    return result

# ==================================================
# 3. دالة Groq الجديدة باستخدام LangChain
# ==================================================

def call_groq_completion(prompt: str) -> str:
    """استدعاء نقطة النهاية Groq Completion API باستخدام LangChain."""
    
    if not GROQ_API_KEY:
        # استجابة وهمية (Mock) في حالة عدم وجود مفتاح
        return (
            "SYMBOLS:\n- aged tree, lantern, winding path\n"
            "STYLE:\n- warm cinematic backlight, painterly brushwork\n"
            "SD_PROMPT:\n- \"An ancient tree by a winding path, warm lantern light,"
            " cinematic composition, golden hour, dramatic rim lighting,"
            " detailed textures, 35mm lens, high-detail, organic colors\""
        )
    
    try:
        llm = ChatGroq(
            temperature=0.7, 
            model_name=GROQ_MODEL,
            groq_api_key=GROQ_API_KEY,
        )

        messages = [
            HumanMessage(content=prompt)
        ]

        response = llm.invoke(messages)
        
        if response.content:
            return response.content
        else:
            # يمكن أن يحدث هذا إذا كان هناك خطأ داخلي في Groq أو إذا لم ينتج محتوى
            raise ValueError("Groq returned an empty response content.")

    except Exception as e:
        raise ValueError(f"LLM Groq/LangChain Error: {type(e).__name__} - {str(e)}")


# ==================================================
# 4. دالة Gradio الأساسية (Endpoint)
# ==================================================

def generate_prompt(quote, author, culture, callback_url):
    """
    الدالة الخلفية الرئيسية لـ Gradio.
    """
    if not quote:
        return "", "", "", "No quote provided." 

    # 1. تجميع الـ Prompt واستدعاء LLM
    # الآن assemble_llm_prompt معرفة في الأعلى، ولن يحدث NameError
    assembled = assemble_llm_prompt(quote, author or None, culture or None)
    
    try:
        llm_text = call_groq_completion(assembled)
    except ValueError as e:
        return "", "", "", f"LLM Generation Failed: {e}"

    parsed = parse_llm_output(llm_text)
    
    # 2. تجهيز النتيجة
    sd_prompt = parsed.get("sd_prompt")
    symbols = parsed.get("symbols")
    style = parsed.get("style")
    
    result = {
        "sd_prompt": sd_prompt,
        "symbols": symbols,
        "style": style,
        "raw": llm_text
    }
    
    # 3. معالجة Callback (باستخدام Gradio Client)
    callback_status = "no-callback"
    if callback_url and client_utils.is_valid_url(callback_url):
        try:
            client = Client(callback_url)
            client.predict(
                result, 
                api_name="/receive"
            )
            callback_status = "sent-via-gradio-client"
        except ImportError:
            callback_status = "failed: gradio_client not installed."
        except Exception as e:
            callback_status = f"failed-gradio-cb: {e}"

    return sd_prompt, symbols, style, callback_status

# ==================================================
# 5. واجهة Gradio UI
# ==================================================
with gr.Blocks(title="Prompt Agent – Pure Gradio") as demo:
    gr.Markdown("# **Wisdom → SD Prompt Generator (Groq Llama3.1-70B)**")
    gr.Markdown("هذه الواجهة تستخدم LangChain للاتصال الموثوق بـ Groq.")
    
    with gr.Row():
        with gr.Column(scale=2):
            quote_in = gr.Textbox(label="Wisdom / Proverb", lines=3, placeholder="Enter the wisdom text here...")
            author_in = gr.Textbox(label="Author (optional)")
            culture_in = gr.Textbox(label="Culture / Region (optional)")
            callback_url_in = gr.Textbox(label="Callback URL (Other HF Space API path, e.g. https://user/space/api/receive)")
            gen_btn = gr.Button("Generate Prompt 🚀", variant="primary")
            
        with gr.Column(scale=3):
            sd_out = gr.Textbox(label="Stable Diffusion Prompt", lines=4)
            symbols_out = gr.Textbox(label="Suggested Symbols", lines=3)
            style_out = gr.Textbox(label="Style / Lighting / Composition", lines=3)
            cb_out = gr.Textbox(label="Callback Status")
            
    gen_btn.click(
        fn=generate_prompt, 
        inputs=[quote_in, author_in, culture_in, callback_url_in], 
        outputs=[sd_out, symbols_out, style_out, cb_out],
        api_name="generate"
    )

# ==================================================
# 6. نقطة الدخول الرئيسية
# ==================================================
if __name__ == '__main__':
    demo.launch()