mmotahari commited on
Commit
52d71d6
·
verified ·
1 Parent(s): 6c0f4ec

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +146 -63
app.py CHANGED
@@ -19,24 +19,18 @@ HTML_APP = """
19
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
20
  margin: 0 0 6px;
21
  }
22
- #planner-root .sub {
23
- text-align: center; color: #718096;
24
- font-size: 0.88rem; margin-bottom: 28px;
25
- }
26
  .row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 14px; }
27
  @media(max-width:560px){ .row { grid-template-columns: 1fr; } }
28
 
29
- .field label {
30
- display: block; font-size: 0.85rem; font-weight: 600;
31
- color: #4a5568; margin-bottom: 6px;
32
- }
33
  .field textarea, .field input {
34
- width: 100%; background: #ffffff;
35
  border: 1px solid #cbd5e0; border-radius: 8px;
36
  color: #1a202c; font-family: 'Vazirmatn', sans-serif;
37
  font-size: 0.95rem; padding: 10px 12px;
38
- transition: border-color .2s, box-shadow .2s;
39
- resize: vertical;
40
  }
41
  .field textarea { min-height: 80px; }
42
  .field input[type=password] { height: 44px; }
@@ -49,46 +43,72 @@ HTML_APP = """
49
  width: 100%; padding: 13px;
50
  background: linear-gradient(135deg, #667eea, #764ba2);
51
  border: none; border-radius: 8px; color: #fff;
52
- font-family: 'Vazirmatn', sans-serif;
53
- font-weight: 700; font-size: 1.05rem;
54
  cursor: pointer; margin-bottom: 24px;
55
- box-shadow: 0 4px 18px rgba(102,126,234,.35);
56
- transition: all .2s;
57
  }
58
  #startBtn:hover:not(:disabled) { filter: brightness(1.08); transform: translateY(-1px); }
59
  #startBtn:disabled { opacity: .55; cursor: not-allowed; transform: none; }
60
 
 
61
  .step {
62
- border-radius: 10px; margin: 10px 0;
63
- border: 1px solid #e2e8f0; overflow: hidden;
64
- transition: border-color .3s, box-shadow .3s;
65
- background: #ffffff;
66
- }
67
- .step.s-pending { opacity: .45; }
68
- .step.s-active { border-color: #667eea; box-shadow: 0 0 0 3px rgba(102,126,234,.12); }
69
- .step.s-done { border-color: #48bb78; }
70
  .step.s-error { border-color: #fc8181; }
71
 
72
  .step-hd {
73
  display: flex; align-items: center; gap: 10px;
74
- padding: 12px 16px; font-weight: 600; font-size: .93rem;
75
- background: #f7fafc; color: #2d3748;
76
- border-bottom: 1px solid #e2e8f0;
 
 
 
 
 
 
 
 
 
 
77
  }
78
- .step.s-active .step-hd { background: #ebf4ff; color: #3c5fb8; border-color: #bee3f8; }
79
- .step.s-done .step-hd { background: #f0fff4; color: #276749; border-color: #c6f6d5; }
80
- .step.s-error .step-hd { background: #fff5f5; color: #c53030; border-color: #fed7d7; }
81
 
82
  .step-body {
83
- padding: 14px 16px; font-size: .88rem; line-height: 1.85;
84
- background: #ffffff; color: #2d3748;
85
- white-space: pre-wrap; display: none;
86
  }
87
  .step.s-active .step-body,
88
  .step.s-done .step-body,
89
- .step.s-error .step-body { display: block; }
90
- .step.s-active .step-body { color: #a0aec0; font-style: italic; }
91
- .step.s-error .step-body { color: #c53030; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
  @keyframes spin { to { transform: rotate(360deg); } }
94
  .spin-icon { display: inline-block; animation: spin 1s linear infinite; }
@@ -102,8 +122,7 @@ HTML_APP = """
102
  #errBox {
103
  background: #fff5f5; border: 1px solid #fc8181;
104
  color: #c53030; padding: 12px 16px; border-radius: 8px;
105
- font-weight: 600; margin-bottom: 14px; display: none;
106
- font-size: .9rem;
107
  }
108
  </style>
109
 
@@ -133,31 +152,78 @@ HTML_APP = """
133
  const STEPS = [
134
  {
135
  id: "s1",
136
- title: "مرحله ۱ — تجزیه هدف به زیروظایف",
137
- prompt: (c) => "Break this goal into exactly 5 clear sub-tasks. Output ONLY a numbered list, no other text.\\n\\nGoal: " + c.goal
 
 
 
 
 
138
  },
139
  {
140
  id: "s2",
141
- title: "مرحله ۲ — تحلیل وابستگی‌ها و ریسک‌ها",
142
- prompt: (c) => "For each sub-task, write one line: N. [task] | dependency: X | risk: Y | resource: Z\\n\\nSub-tasks:\\n" + c.s1
 
 
 
 
 
 
 
143
  },
144
  {
145
  id: "s3",
146
- title: "مرحله ۳ — اولویت‌بندی و زمان‌بندی",
147
- prompt: (c) => "Create an optimized execution order with time estimates (Day 1, Day 2 etc).\\n\\nAnalysis:\\n" + c.s2
 
 
 
 
 
 
 
148
  },
149
  {
150
  id: "s4",
151
- title: "مرحله ۴ — اقدامات عملی",
152
- prompt: (c) => "Convert each step into 2-3 concrete actions with a success criteria.\\n\\nPlan:\\n" + c.s3
 
 
 
 
 
 
 
153
  },
154
  {
155
  id: "s5",
156
- title: "مرحله ۵ — خلاصه اجرایی نهایی",
157
- prompt: (c) => "Write a 3-4 sentence executive summary of this complete plan.\\n\\nFull plan:\\n" + c.s4
 
 
 
 
 
 
 
 
158
  }
159
  ];
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  function buildSteps() {
162
  const area = document.getElementById("stepsArea");
163
  area.innerHTML = "";
@@ -165,24 +231,41 @@ function buildSteps() {
165
  const d = document.createElement("div");
166
  d.className = "step s-pending";
167
  d.id = "step-" + i;
168
- d.innerHTML = `<div class="step-hd"><span>⬜</span>&nbsp;${s.title}</div><div class="step-body"></div>`;
 
 
 
 
 
 
169
  area.appendChild(d);
170
  });
171
  }
172
 
173
- function setStep(idx, state, bodyText) {
174
  const el = document.getElementById("step-" + idx);
175
  if (!el) return;
176
  el.className = "step s-" + state;
177
- const icons = { pending:"⬜", active:'<span class="spin-icon">⏳</span>', done:"✅", error:"❌" };
178
- el.querySelector(".step-hd").innerHTML = `<span>${icons[state]}</span>&nbsp;${STEPS[idx].title}`;
179
- if (bodyText !== undefined) el.querySelector(".step-body").textContent = bodyText;
 
 
 
 
 
 
 
 
 
 
 
 
180
  }
181
 
182
  function showErr(msg) {
183
  const b = document.getElementById("errBox");
184
- b.textContent = "❌ " + msg;
185
- b.style.display = "block";
186
  }
187
 
188
  async function callLLM(apiKey, prompt) {
@@ -197,11 +280,11 @@ async function callLLM(apiKey, prompt) {
197
  body: JSON.stringify({
198
  model: "openrouter/auto",
199
  messages: [{ role: "user", content: prompt }],
200
- max_tokens: 1000,
201
- temperature: 0.7
202
  })
203
  });
204
- if (!r.ok) throw new Error((await r.text()).slice(0, 200));
205
  const d = await r.json();
206
  return d.choices[0].message.content.trim();
207
  }
@@ -218,19 +301,19 @@ async function startPlanning() {
218
 
219
  btn.disabled = true;
220
  btn.textContent = "⏳ در حال برنامه‌ریزی...";
221
-
222
  buildSteps();
223
 
224
  const ctx = { goal };
225
  for (let i = 0; i < STEPS.length; i++) {
226
- setStep(i, "active", "در حال پردازش...");
227
  try {
228
- const result = await callLLM(apiKey, STEPS[i].prompt(ctx));
229
- ctx[STEPS[i].id] = result;
230
- setStep(i, "done", result);
 
231
  } catch(e) {
232
  ctx[STEPS[i].id] = "";
233
- setStep(i, "error", "خطا: " + e.message);
234
  }
235
  }
236
 
 
19
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
20
  margin: 0 0 6px;
21
  }
22
+ .sub { text-align: center; color: #718096; font-size: 0.88rem; margin-bottom: 28px; }
23
+
 
 
24
  .row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 14px; }
25
  @media(max-width:560px){ .row { grid-template-columns: 1fr; } }
26
 
27
+ .field label { display: block; font-size: 0.85rem; font-weight: 600; color: #4a5568; margin-bottom: 6px; }
 
 
 
28
  .field textarea, .field input {
29
+ width: 100%; background: #fff;
30
  border: 1px solid #cbd5e0; border-radius: 8px;
31
  color: #1a202c; font-family: 'Vazirmatn', sans-serif;
32
  font-size: 0.95rem; padding: 10px 12px;
33
+ transition: border-color .2s, box-shadow .2s; resize: vertical;
 
34
  }
35
  .field textarea { min-height: 80px; }
36
  .field input[type=password] { height: 44px; }
 
43
  width: 100%; padding: 13px;
44
  background: linear-gradient(135deg, #667eea, #764ba2);
45
  border: none; border-radius: 8px; color: #fff;
46
+ font-family: 'Vazirmatn', sans-serif; font-weight: 700; font-size: 1.05rem;
 
47
  cursor: pointer; margin-bottom: 24px;
48
+ box-shadow: 0 4px 18px rgba(102,126,234,.35); transition: all .2s;
 
49
  }
50
  #startBtn:hover:not(:disabled) { filter: brightness(1.08); transform: translateY(-1px); }
51
  #startBtn:disabled { opacity: .55; cursor: not-allowed; transform: none; }
52
 
53
+ /* Step cards */
54
  .step {
55
+ border-radius: 12px; margin: 10px 0;
56
+ border: 1px solid #e2e8f0; background: #fff;
57
+ overflow: hidden; transition: border-color .3s, box-shadow .3s;
58
+ }
59
+ .step.s-pending { opacity: .4; }
60
+ .step.s-active { border-color: #667eea; box-shadow: 0 0 0 3px rgba(102,126,234,.1); }
61
+ .step.s-done { border-color: #68d391; }
 
62
  .step.s-error { border-color: #fc8181; }
63
 
64
  .step-hd {
65
  display: flex; align-items: center; gap: 10px;
66
+ padding: 13px 16px; font-weight: 700; font-size: .92rem;
67
+ background: #f7fafc; color: #2d3748; border-bottom: 1px solid #e2e8f0;
68
+ cursor: pointer; user-select: none;
69
+ }
70
+ .step.s-active .step-hd { background: #ebf4ff; color: #3c5fb8; border-color: #bee3f8; }
71
+ .step.s-done .step-hd { background: #f0fff4; color: #276749; border-color: #c6f6d5; }
72
+ .step.s-error .step-hd { background: #fff5f5; color: #c53030; border-color: #fed7d7; }
73
+
74
+ .step-hd .badge {
75
+ margin-right: auto; /* push to left in rtl */
76
+ font-size: .75rem; font-weight: 600; padding: 2px 10px;
77
+ border-radius: 20px; background: rgba(0,0,0,.06); color: inherit;
78
+ white-space: nowrap;
79
  }
80
+ .step.s-active .badge { background: rgba(102,126,234,.15); }
81
+ .step.s-done .badge { background: rgba(72,187,120,.15); }
 
82
 
83
  .step-body {
84
+ padding: 0; max-height: 0; overflow: hidden;
85
+ transition: max-height .4s ease, padding .3s;
 
86
  }
87
  .step.s-active .step-body,
88
  .step.s-done .step-body,
89
+ .step.s-error .step-body {
90
+ max-height: 600px; padding: 14px 16px;
91
+ }
92
+
93
+ /* Items inside step body */
94
+ .item {
95
+ display: flex; gap: 10px; align-items: flex-start;
96
+ padding: 8px 10px; border-radius: 8px; margin-bottom: 6px;
97
+ background: #f7fafc; font-size: .88rem; line-height: 1.7; color: #2d3748;
98
+ }
99
+ .item:last-child { margin-bottom: 0; }
100
+ .item .num {
101
+ min-width: 24px; height: 24px; border-radius: 50%;
102
+ background: #667eea; color: white;
103
+ font-size: .75rem; font-weight: 700;
104
+ display: flex; align-items: center; justify-content: center; margin-top: 1px;
105
+ }
106
+ .step.s-done .item .num { background: #48bb78; }
107
+
108
+ .thinking {
109
+ color: #a0aec0; font-style: italic; font-size: .88rem;
110
+ padding: 6px 4px;
111
+ }
112
 
113
  @keyframes spin { to { transform: rotate(360deg); } }
114
  .spin-icon { display: inline-block; animation: spin 1s linear infinite; }
 
122
  #errBox {
123
  background: #fff5f5; border: 1px solid #fc8181;
124
  color: #c53030; padding: 12px 16px; border-radius: 8px;
125
+ font-weight: 600; margin-bottom: 14px; display: none; font-size: .9rem;
 
126
  }
127
  </style>
128
 
 
152
  const STEPS = [
153
  {
154
  id: "s1",
155
+ title: "تجزیه هدف",
156
+ emoji: "🔍",
157
+ badge: "زیروظایف",
158
+ prompt: (c) => `هدف زیر را به فارسی به دقیقاً ۵ زیروظیفه کوتاه و واضح تقسیم کن.
159
+ فقط یک لیست شماره‌گذاری شده بنویس. هر آیتم حداکثر ۱۵ کلمه باشد. بدون توضیح اضافه.
160
+
161
+ هدف: ${c.goal}`
162
  },
163
  {
164
  id: "s2",
165
+ title: "تحلیل ریسک‌ها",
166
+ emoji: "🧠",
167
+ badge: "وابستگی‌ها",
168
+ prompt: (c) => `برای هر زیروظیفه زیر، یک خط فارسی بنویس با این فرمت:
169
+ شماره. [زیروظیفه] ← وابستگی: ... | ریسک: ... | منبع: ...
170
+ فقط همین فرمت، بدون توضیح اضافه.
171
+
172
+ زیروظایف:
173
+ ${c.s1}`
174
  },
175
  {
176
  id: "s3",
177
+ title: "اولویت‌بندی",
178
+ emoji: "📅",
179
+ badge: "زمان‌بندی",
180
+ prompt: (c) => `بر اساس تحلیل زیر، یک برنامه زمانی فارسی بنویس.
181
+ فرمت: روز X — وظیفه: ... | زمان: ... ساعت
182
+ فقط برنامه روزانه، بدون توضیح اضافه. حداکثر ۶ خط.
183
+
184
+ تحلیل:
185
+ ${c.s2}`
186
  },
187
  {
188
  id: "s4",
189
+ title: "اقدامات عملی",
190
+ emoji: "⚙️",
191
+ badge: "گام‌های اجرایی",
192
+ prompt: (c) => `برای هر روز از برنامه زیر، ۲ اقدام عملی فارسی بنویس.
193
+ فرمت: روز X: اقدام ۱ / اقدام ۲
194
+ خلاصه، عملی و قابل اجرا. بدون توضیح اضافه.
195
+
196
+ برنامه:
197
+ ${c.s3}`
198
  },
199
  {
200
  id: "s5",
201
+ title: "خلاصه نهایی",
202
+ emoji: "✅",
203
+ badge: "نتیجه‌گیری",
204
+ prompt: (c) => `یک خلاصه اجرایی فارسی ۳ جمله‌ای از این برنامه کامل بنویس.
205
+ جمله ۱: هدف اصلی
206
+ جمله ۲: مسیر اجرا
207
+ جمله ۳: نکته کلیدی موفقیت
208
+
209
+ برنامه کامل:
210
+ ${c.s4}`
211
  }
212
  ];
213
 
214
+ // Parse numbered list into array of strings
215
+ function parseList(text) {
216
+ return text.split('\\n')
217
+ .map(l => l.replace(/^\\d+[\\.\\-\\)]\\.?\\s*/, '').trim())
218
+ .filter(l => l.length > 5);
219
+ }
220
+
221
+ function renderItems(lines) {
222
+ return lines.map((line, i) =>
223
+ `<div class="item"><span class="num">${i+1}</span><span>${line}</span></div>`
224
+ ).join('');
225
+ }
226
+
227
  function buildSteps() {
228
  const area = document.getElementById("stepsArea");
229
  area.innerHTML = "";
 
231
  const d = document.createElement("div");
232
  d.className = "step s-pending";
233
  d.id = "step-" + i;
234
+ d.innerHTML = `
235
+ <div class="step-hd">
236
+ <span>${s.emoji}</span>
237
+ <span>مرحله ${i+1} — ${s.title}</span>
238
+ <span class="badge">${s.badge}</span>
239
+ </div>
240
+ <div class="step-body"></div>`;
241
  area.appendChild(d);
242
  });
243
  }
244
 
245
+ function setStep(idx, state, items) {
246
  const el = document.getElementById("step-" + idx);
247
  if (!el) return;
248
  el.className = "step s-" + state;
249
+ const s = STEPS[idx];
250
+ const icons = { pending: s.emoji, active: '<span class="spin-icon">⏳</span>', done: '✅', error: '❌' };
251
+ el.querySelector(".step-hd").innerHTML = `
252
+ <span>${icons[state]}</span>
253
+ <span>مرحله ${idx+1} — ${s.title}</span>
254
+ <span class="badge">${s.badge}</span>`;
255
+
256
+ const body = el.querySelector(".step-body");
257
+ if (state === 'active') {
258
+ body.innerHTML = '<div class="thinking">در حال پردازش...</div>';
259
+ } else if (state === 'done' && items) {
260
+ body.innerHTML = renderItems(items);
261
+ } else if (state === 'error' && items) {
262
+ body.innerHTML = `<div class="thinking" style="color:#c53030">${items[0]}</div>`;
263
+ }
264
  }
265
 
266
  function showErr(msg) {
267
  const b = document.getElementById("errBox");
268
+ b.textContent = "❌ " + msg; b.style.display = "block";
 
269
  }
270
 
271
  async function callLLM(apiKey, prompt) {
 
280
  body: JSON.stringify({
281
  model: "openrouter/auto",
282
  messages: [{ role: "user", content: prompt }],
283
+ max_tokens: 600,
284
+ temperature: 0.6
285
  })
286
  });
287
+ if (!r.ok) throw new Error((await r.text()).slice(0, 150));
288
  const d = await r.json();
289
  return d.choices[0].message.content.trim();
290
  }
 
301
 
302
  btn.disabled = true;
303
  btn.textContent = "⏳ در حال برنامه‌ریزی...";
 
304
  buildSteps();
305
 
306
  const ctx = { goal };
307
  for (let i = 0; i < STEPS.length; i++) {
308
+ setStep(i, "active");
309
  try {
310
+ const raw = await callLLM(apiKey, STEPS[i].prompt(ctx));
311
+ ctx[STEPS[i].id] = raw;
312
+ const lines = parseList(raw);
313
+ setStep(i, "done", lines.length > 0 ? lines : [raw]);
314
  } catch(e) {
315
  ctx[STEPS[i].id] = "";
316
+ setStep(i, "error", ["خطا: " + e.message]);
317
  }
318
  }
319