mmotahari commited on
Commit
a9d39e7
·
verified ·
1 Parent(s): d2ef3bc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +246 -237
app.py CHANGED
@@ -1,269 +1,278 @@
1
  import gradio as gr
2
- import requests
3
 
4
- OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions"
5
- FREE_MODEL = "meta-llama/llama-3.3-8b-instruct:free"
6
-
7
- def call_llm(api_key, messages):
8
- headers = {
9
- "Authorization": f"Bearer {api_key}",
10
- "Content-Type": "application/json",
11
- "HTTP-Referer": "https://huggingface.co/spaces",
12
- "X-Title": "MultiStep Task Planner"
13
- }
14
- payload = {
15
- "model": FREE_MODEL,
16
- "messages": messages,
17
- "max_tokens": 1000,
18
- "temperature": 0.7,
19
- }
20
- resp = requests.post(OPENROUTER_API_URL, headers=headers, json=payload, timeout=60)
21
- resp.raise_for_status()
22
- return resp.json()["choices"][0]["message"]["content"].strip()
23
-
24
-
25
- STEPS = [
26
- {
27
- "id": "decompose",
28
- "title": "مرحله ۱ — تجزیه هدف به زیروظایف",
29
- "prompt": "You are a task planning assistant. Given the user's goal, break it down into 5 clear sub-tasks. Output ONLY a numbered list, nothing else.\n\nGoal: {goal}"
30
- },
31
- {
32
- "id": "analyze",
33
- "title": "مرحله ۲ — تحلیل وابستگی‌ها و ریسک‌ها",
34
- "prompt": "For each sub-task below, identify: dependency, risk, required resource. Be concise.\n\nSub-tasks:\n{decompose}"
35
- },
36
- {
37
- "id": "prioritize",
38
- "title": "مرحله ۳ — اولویت‌بندی و زمان‌بندی",
39
- "prompt": "Given these sub-tasks and analysis, create an optimized execution order with time estimates.\n\nAnalysis:\n{analyze}"
40
- },
41
- {
42
- "id": "actions",
43
- "title": "مرحله ۴ — اقدامات عملی",
44
- "prompt": "Convert this plan into concrete actionable steps with clear success criteria for each.\n\nPlan:\n{prioritize}"
45
- },
46
- {
47
- "id": "summary",
48
- "title": "مرحله ۵ — خلاصه اجرایی نهایی",
49
- "prompt": "Write a brief executive summary (3-4 sentences) of the complete plan below.\n\nFull plan:\n{actions}"
50
- },
51
- ]
52
-
53
- def make_steps_html(done_steps, active_idx, results):
54
- html = ""
55
- for i, step in enumerate(STEPS):
56
- if i < active_idx:
57
- # done
58
- result_text = results.get(step["id"], "")
59
- html += f"""
60
- <div class="step done">
61
- <div class="step-header">
62
- <span class="step-icon">✅</span>
63
- <span class="step-title">{step['title']}</span>
64
- </div>
65
- <div class="step-body">{result_text.replace(chr(10), '<br>')}</div>
66
- </div>"""
67
- elif i == active_idx:
68
- # active
69
- html += f"""
70
- <div class="step active">
71
- <div class="step-header">
72
- <span class="step-icon spin">⏳</span>
73
- <span class="step-title">{step['title']}</span>
74
- </div>
75
- <div class="step-body muted">در حال پردازش...</div>
76
- </div>"""
77
- else:
78
- # pending
79
- html += f"""
80
- <div class="step pending">
81
- <div class="step-header">
82
- <span class="step-icon">⬜</span>
83
- <span class="step-title muted-title">{step['title']}</span>
84
- </div>
85
- </div>"""
86
- return html
87
-
88
-
89
- def run_planner(goal, api_key):
90
- if not api_key.strip():
91
- yield "<div class='err'>❌ API Key وارد کنید</div>"
92
- return
93
- if not goal.strip():
94
- yield "<div class='err'>❌ هدف را وارد کنید</div>"
95
- return
96
-
97
- results = {}
98
- context = {"goal": goal}
99
-
100
- for i, step in enumerate(STEPS):
101
- # show current step as active
102
- yield make_steps_html(results, i, results)
103
-
104
- try:
105
- prompt = step["prompt"].format(**context)
106
- result = call_llm(api_key, [{"role": "user", "content": prompt}])
107
- results[step["id"]] = result
108
- context[step["id"]] = result
109
- except Exception as e:
110
- results[step["id"]] = f"❌ خطا: {str(e)}"
111
- context[step["id"]] = results[step["id"]]
112
-
113
- # show this step as done
114
- yield make_steps_html(results, i + 1, results)
115
-
116
- # all done
117
- final = make_steps_html(results, len(STEPS), results)
118
- final += "<div class='done-banner'>✅ برنامه‌ریزی کامل شد!</div>"
119
- yield final
120
-
121
-
122
- CSS = """
123
- @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;600;800&display=swap');
124
-
125
- * { box-sizing: border-box; }
126
-
127
- body, .gradio-container {
128
- background: #0d1117 !important;
129
- font-family: 'Vazirmatn', sans-serif !important;
130
- color: #e6edf3 !important;
131
- direction: rtl;
132
  }
133
-
134
- .gradio-container { max-width: 860px !important; margin: 0 auto !important; }
135
-
136
- .header {
137
- text-align: center;
138
- padding: 36px 20px 16px;
139
- border-bottom: 1px solid #21262d;
140
- margin-bottom: 24px;
141
  }
142
- .header h1 {
143
- font-size: 2rem; font-weight: 800;
144
- background: linear-gradient(135deg, #58a6ff, #bc8cff);
145
- -webkit-background-clip: text; -webkit-text-fill-color: transparent;
146
- margin: 0 0 6px;
147
- }
148
- .header p { color: #8b949e; font-size: 0.9rem; margin: 0; }
149
 
150
- label { color: #e6edf3 !important; font-weight: 600 !important; font-size: 0.95rem !important; }
 
151
 
152
- textarea, input[type="text"], input[type="password"] {
153
- background: #161b22 !important;
154
- border: 1px solid #30363d !important;
155
- border-radius: 8px !important;
156
- color: #e6edf3 !important;
157
- font-family: 'Vazirmatn', sans-serif !important;
158
  }
159
- textarea:focus, input:focus {
160
- border-color: #58a6ff !important;
161
- box-shadow: 0 0 0 3px rgba(88,166,255,0.1) !important;
162
- outline: none !important;
163
  }
164
-
165
- button.primary {
166
- background: linear-gradient(135deg, #7c3aed, #4f46e5) !important;
167
- border: none !important; color: white !important;
168
- font-family: 'Vazirmatn', sans-serif !important;
169
- font-weight: 700 !important; font-size: 1.05rem !important;
170
- padding: 12px !important; border-radius: 8px !important;
171
- transition: all 0.2s !important;
172
- box-shadow: 0 4px 15px rgba(124,58,237,0.35) !important;
 
173
  }
174
- button.primary:hover { filter: brightness(1.15) !important; transform: translateY(-1px) !important; }
 
175
 
176
- /* Step cards */
177
  .step {
178
- border-radius: 10px;
179
- margin: 10px 0;
180
- overflow: hidden;
181
- border: 1px solid #21262d;
182
- transition: all 0.3s;
183
  }
184
- .step.active {
185
- border-color: #58a6ff;
186
- box-shadow: 0 0 0 3px rgba(88,166,255,0.12);
187
- }
188
- .step.done { border-color: #238636; }
189
- .step.pending { opacity: 0.5; }
190
 
191
  .step-header {
192
- display: flex; align-items: center; gap: 10px;
193
- padding: 12px 16px;
194
- background: #161b22;
195
- font-weight: 600; font-size: 0.95rem;
196
  }
197
- .step.active .step-header { background: #1c2a3a; color: #58a6ff; }
198
- .step.done .step-header { background: #0f2d16; color: #3fb950; }
 
 
199
 
200
  .step-body {
201
- padding: 14px 16px;
202
- font-size: 0.88rem;
203
- line-height: 1.8;
204
- background: #0d1117;
205
- color: #c9d1d9;
206
- white-space: pre-wrap;
207
- border-top: 1px solid #21262d;
208
  }
209
- .step-body.muted { color: #8b949e; font-style: italic; }
210
- .muted-title { color: #8b949e !important; }
211
-
212
- .step-icon { font-size: 1.1rem; min-width: 22px; }
213
 
 
214
  @keyframes spin { to { transform: rotate(360deg); } }
215
- .spin { display: inline-block; animation: spin 1.2s linear infinite; }
216
 
217
  .done-banner {
218
- text-align: center;
219
- margin-top: 20px;
220
- padding: 16px;
221
- background: #0f2d16;
222
- border: 1px solid #238636;
223
- border-radius: 10px;
224
- color: #3fb950;
225
- font-weight: 700;
226
- font-size: 1.1rem;
227
  }
228
- .err {
229
- background: #2d1515; border: 1px solid #f85149;
230
- color: #f85149; padding: 14px; border-radius: 8px;
231
- font-weight: 600;
232
  }
233
- """
234
-
235
- with gr.Blocks(css=CSS, title="MultiStep Task Planner") as demo:
236
- gr.HTML("""
237
- <div class="header">
238
- <h1>🧩 MultiStep Task Planner</h1>
239
- <p>برنامه‌ریزی هوشمند با استدلال چندمرحله‌ای</p>
 
 
 
 
 
 
 
 
240
  </div>
241
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
 
243
- with gr.Row():
244
- with gr.Column(scale=2):
245
- goal_input = gr.Textbox(
246
- label="🎯 هدف شما",
247
- placeholder="مثال: برنامه سفر ۳ روزه به ماسال",
248
- lines=3,
249
- )
250
- with gr.Column(scale=1):
251
- api_key_input = gr.Textbox(
252
- label="🔑 OpenRouter API Key",
253
- placeholder="sk-or-v1-...",
254
- type="password",
255
- lines=3,
256
- )
 
 
 
 
 
 
 
 
 
257
 
258
- run_btn = gr.Button("🚀 شروع برنامه‌ریزی", variant="primary", size="lg")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
- output_html = gr.HTML(value="")
 
 
 
 
 
 
 
261
 
262
- run_btn.click(
263
- fn=run_planner,
264
- inputs=[goal_input, api_key_input],
265
- outputs=[output_html],
266
- )
267
 
268
  if __name__ == "__main__":
269
  demo.launch()
 
1
  import gradio as gr
 
2
 
3
+ # تمام منطق در JavaScript اجرا میشه تا از مرورگر کاربر به OpenRouter وصل بشه
4
+ # (HuggingFace Spaces سرور openrouter.ai رو block میکنه)
5
+
6
+ HTML_APP = """
7
+ <!DOCTYPE html>
8
+ <html lang="fa" dir="rtl">
9
+ <head>
10
+ <meta charset="UTF-8">
11
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
+ <title>MultiStep Task Planner</title>
13
+ <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;600;800&display=swap" rel="stylesheet">
14
+ <style>
15
+ * { box-sizing: border-box; margin: 0; padding: 0; }
16
+ body {
17
+ font-family: 'Vazirmatn', sans-serif;
18
+ background: #0d1117;
19
+ color: #e6edf3;
20
+ min-height: 100vh;
21
+ padding: 30px 16px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  }
23
+ .container { max-width: 820px; margin: 0 auto; }
24
+
25
+ h1 {
26
+ text-align: center;
27
+ font-size: 1.9rem; font-weight: 800;
28
+ background: linear-gradient(135deg, #58a6ff, #bc8cff);
29
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
30
+ margin-bottom: 6px;
31
  }
32
+ .subtitle { text-align: center; color: #8b949e; margin-bottom: 28px; font-size: 0.9rem; }
 
 
 
 
 
 
33
 
34
+ .inputs { display: grid; grid-template-columns: 2fr 1fr; gap: 14px; margin-bottom: 14px; }
35
+ @media(max-width:600px){ .inputs { grid-template-columns: 1fr; } }
36
 
37
+ label { display: block; font-weight: 600; font-size: 0.88rem; color: #8b949e; margin-bottom: 6px; }
38
+ input, textarea {
39
+ width: 100%; background: #161b22; border: 1px solid #30363d;
40
+ border-radius: 8px; color: #e6edf3; font-family: 'Vazirmatn', sans-serif;
41
+ font-size: 0.95rem; padding: 10px 12px; resize: vertical;
42
+ transition: border-color 0.2s, box-shadow 0.2s;
43
  }
44
+ input:focus, textarea:focus {
45
+ outline: none; border-color: #58a6ff;
46
+ box-shadow: 0 0 0 3px rgba(88,166,255,0.12);
 
47
  }
48
+ input[type="password"] { letter-spacing: 2px; }
49
+
50
+ button {
51
+ width: 100%; padding: 13px;
52
+ background: linear-gradient(135deg, #7c3aed, #4f46e5);
53
+ border: none; border-radius: 8px; color: white;
54
+ font-family: 'Vazirmatn', sans-serif; font-weight: 700; font-size: 1.05rem;
55
+ cursor: pointer; transition: all 0.2s;
56
+ box-shadow: 0 4px 15px rgba(124,58,237,0.35);
57
+ margin-bottom: 24px;
58
  }
59
+ button:hover:not(:disabled) { filter: brightness(1.12); transform: translateY(-1px); }
60
+ button:disabled { opacity: 0.6; cursor: not-allowed; }
61
 
62
+ /* Steps */
63
  .step {
64
+ border-radius: 10px; margin: 10px 0;
65
+ border: 1px solid #21262d; overflow: hidden;
66
+ transition: border-color 0.3s, box-shadow 0.3s;
 
 
67
  }
68
+ .step.active { border-color: #58a6ff; box-shadow: 0 0 0 3px rgba(88,166,255,0.1); }
69
+ .step.done { border-color: #238636; }
70
+ .step.pending { opacity: 0.45; }
71
+ .step.error { border-color: #f85149; }
 
 
72
 
73
  .step-header {
74
+ display: flex; align-items: center; gap: 10px;
75
+ padding: 12px 16px; font-weight: 600; font-size: 0.93rem;
 
 
76
  }
77
+ .step.pending .step-header { background: #161b22; color: #8b949e; }
78
+ .step.active .step-header { background: #1c2a3a; color: #58a6ff; }
79
+ .step.done .step-header { background: #0f2d16; color: #3fb950; }
80
+ .step.error .step-header { background: #2d1515; color: #f85149; }
81
 
82
  .step-body {
83
+ padding: 14px 16px; font-size: 0.87rem; line-height: 1.9;
84
+ background: #0d1117; color: #c9d1d9;
85
+ border-top: 1px solid #21262d;
86
+ white-space: pre-wrap; display: none;
 
 
 
87
  }
88
+ .step.active .step-body,
89
+ .step.done .step-body,
90
+ .step.error .step-body { display: block; }
91
+ .step.active .step-body { color: #8b949e; font-style: italic; }
92
 
93
+ .icon { font-size: 1.1rem; min-width: 22px; }
94
  @keyframes spin { to { transform: rotate(360deg); } }
95
+ .spin { display: inline-block; animation: spin 1s linear infinite; }
96
 
97
  .done-banner {
98
+ text-align: center; margin-top: 20px; padding: 16px;
99
+ background: #0f2d16; border: 1px solid #238636;
100
+ border-radius: 10px; color: #3fb950; font-weight: 700; font-size: 1.05rem;
101
+ display: none;
 
 
 
 
 
102
  }
103
+ .err-msg {
104
+ background: #2d1515; border: 1px solid #f85149;
105
+ color: #f85149; padding: 14px; border-radius: 8px;
106
+ font-weight: 600; margin-bottom: 14px; display: none;
107
  }
108
+ </style>
109
+ </head>
110
+ <body>
111
+ <div class="container">
112
+ <h1>🧩 MultiStep Task Planner</h1>
113
+ <p class="subtitle">برنامه‌ریزی هوشمند با استدلال چندمرحله‌ای</p>
114
+
115
+ <div class="inputs">
116
+ <div>
117
+ <label>🎯 هدف شما</label>
118
+ <textarea id="goal" rows="3" placeholder="مثال: برنامه سفر ۳ روزه به ماسال"></textarea>
119
+ </div>
120
+ <div>
121
+ <label>🔑 OpenRouter API Key</label>
122
+ <input type="password" id="apikey" placeholder="sk-or-v1-...">
123
  </div>
124
+ </div>
125
+
126
+ <button id="startBtn" onclick="startPlanning()">🚀 شروع برنامه‌ریزی</button>
127
+
128
+ <div class="err-msg" id="errMsg"></div>
129
+
130
+ <div id="steps"></div>
131
+ <div class="done-banner" id="doneBanner">✅ برنامه‌ریزی کامل شد!</div>
132
+ </div>
133
+
134
+ <script>
135
+ const STEPS = [
136
+ {
137
+ id: "decompose",
138
+ title: "مرحله ۱ — تجزیه هدف به زیروظایف",
139
+ getPrompt: (ctx) => `You are a task planning assistant. Break down this goal into exactly 5 clear sub-tasks. Output ONLY a numbered list, no extra text.\\n\\nGoal: ${ctx.goal}`
140
+ },
141
+ {
142
+ id: "analyze",
143
+ title: "مرحله ۲ — تحلیل وابستگی‌ها و ریسک‌ها",
144
+ getPrompt: (ctx) => `For each sub-task below, write one line: "N. [task]: dependency=X, risk=Y, resource=Z"\\n\\nSub-tasks:\\n${ctx.decompose}`
145
+ },
146
+ {
147
+ id: "prioritize",
148
+ title: "مرحله ۳ — اولویت‌بندی و زمان‌بندی",
149
+ getPrompt: (ctx) => `Based on this analysis, create an optimized execution order with time estimates (Day/Hour).\\n\\nAnalysis:\\n${ctx.analyze}`
150
+ },
151
+ {
152
+ id: "actions",
153
+ title: "مرحله ۴ — اقدامات عملی و معیار موفقیت",
154
+ getPrompt: (ctx) => `Convert this plan into concrete action steps, each with 2 bullet points and a success criteria.\\n\\nPlan:\\n${ctx.prioritize}`
155
+ },
156
+ {
157
+ id: "summary",
158
+ title: "مرحله ۵ — خلاصه اجرایی نهایی",
159
+ getPrompt: (ctx) => `Write a 3-4 sentence executive summary of this complete plan.\\n\\nFull plan:\\n${ctx.actions}`
160
+ },
161
+ ];
162
+
163
+ function renderSteps(activeIdx, results) {
164
+ const container = document.getElementById("steps");
165
+ container.innerHTML = "";
166
+ STEPS.forEach((step, i) => {
167
+ const div = document.createElement("div");
168
+ let cls = "step ";
169
+ let icon = "⬜";
170
+ let bodyContent = "";
171
+
172
+ if (i < activeIdx) {
173
+ cls += "done"; icon = "✅";
174
+ bodyContent = results[step.id] || "";
175
+ } else if (i === activeIdx) {
176
+ cls += "active"; icon = '<span class="spin">⏳</span>';
177
+ bodyContent = "در حال پردازش...";
178
+ } else {
179
+ cls += "pending"; icon = "⬜";
180
+ }
181
+
182
+ div.className = cls;
183
+ div.innerHTML = `
184
+ <div class="step-header">
185
+ <span class="icon">${icon}</span>
186
+ <span>${step.title}</span>
187
+ </div>
188
+ <div class="step-body">${bodyContent}</div>
189
+ `;
190
+ container.appendChild(div);
191
+ });
192
+ }
193
+
194
+ function showError(msg) {
195
+ const el = document.getElementById("errMsg");
196
+ el.textContent = "❌ " + msg;
197
+ el.style.display = "block";
198
+ }
199
 
200
+ async function callLLM(apiKey, prompt) {
201
+ const resp = await fetch("https://openrouter.ai/api/v1/chat/completions", {
202
+ method: "POST",
203
+ headers: {
204
+ "Authorization": "Bearer " + apiKey,
205
+ "Content-Type": "application/json",
206
+ "HTTP-Referer": "https://huggingface.co/spaces",
207
+ "X-Title": "MultiStep Task Planner"
208
+ },
209
+ body: JSON.stringify({
210
+ model: "meta-llama/llama-3.3-8b-instruct:free",
211
+ messages: [{ role: "user", content: prompt }],
212
+ max_tokens: 1000,
213
+ temperature: 0.7
214
+ })
215
+ });
216
+ if (!resp.ok) {
217
+ const err = await resp.text();
218
+ throw new Error(`${resp.status}: ${err}`);
219
+ }
220
+ const data = await resp.json();
221
+ return data.choices[0].message.content.trim();
222
+ }
223
 
224
+ async function startPlanning() {
225
+ const goal = document.getElementById("goal").value.trim();
226
+ const apiKey = document.getElementById("apikey").value.trim();
227
+ const btn = document.getElementById("startBtn");
228
+ const errMsg = document.getElementById("errMsg");
229
+ const doneBanner = document.getElementById("doneBanner");
230
+
231
+ errMsg.style.display = "none";
232
+ doneBanner.style.display = "none";
233
+
234
+ if (!goal) { showError("هدف را وارد کنید"); return; }
235
+ if (!apiKey) { showError("API Key را وارد کنید"); return; }
236
+
237
+ btn.disabled = true;
238
+ btn.textContent = "⏳ در حال برنامه‌ریزی...";
239
+
240
+ const results = {};
241
+ const ctx = { goal };
242
+
243
+ for (let i = 0; i < STEPS.length; i++) {
244
+ renderSteps(i, results);
245
+ try {
246
+ const prompt = STEPS[i].getPrompt(ctx);
247
+ const result = await callLLM(apiKey, prompt);
248
+ results[STEPS[i].id] = result;
249
+ ctx[STEPS[i].id] = result;
250
+ } catch (e) {
251
+ results[STEPS[i].id] = "خطا: " + e.message;
252
+ ctx[STEPS[i].id] = "";
253
+ // mark as error
254
+ const stepDivs = document.getElementById("steps").children;
255
+ if (stepDivs[i]) {
256
+ stepDivs[i].className = "step error";
257
+ stepDivs[i].querySelector(".step-header").innerHTML =
258
+ `<span class="icon">❌</span><span>${STEPS[i].title}</span>`;
259
+ stepDivs[i].querySelector(".step-body").textContent = results[STEPS[i].id];
260
+ }
261
+ }
262
+ renderSteps(i + 1, results);
263
+ }
264
 
265
+ doneBanner.style.display = "block";
266
+ btn.disabled = false;
267
+ btn.textContent = "🔄 برنامه‌ریزی مجدد";
268
+ }
269
+ </script>
270
+ </body>
271
+ </html>
272
+ """
273
 
274
+ with gr.Blocks(title="MultiStep Task Planner") as demo:
275
+ gr.HTML(HTML_APP)
 
 
 
276
 
277
  if __name__ == "__main__":
278
  demo.launch()