Spaces:
Sleeping
Sleeping
Download app.py from mmotahari/TaskPlanner: direct link, hf CLI and curl.
- Browser
- Download file 14.6 kB
-
https://huggingface.co/spaces/mmotahari/TaskPlanner/resolve/main/app.py
- Command line
-
hf download hf://spaces/mmotahari/TaskPlanner/app.py
-
curl -L -o app.py https://huggingface.co/spaces/mmotahari/TaskPlanner/resolve/main/app.py
14.6 kB
| import gradio as gr | |
| HTML_APP = """ | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;600;800&display=swap'); | |
| #planner-root { | |
| font-family: 'Vazirmatn', sans-serif; | |
| direction: rtl; | |
| background: #f5f7fa; | |
| color: #1a202c; | |
| padding: 32px 20px 60px; | |
| min-height: 100vh; | |
| } | |
| #planner-root h1 { | |
| text-align: center; font-size: 2rem; font-weight: 800; | |
| background: linear-gradient(135deg, #4f46e5, #7c3aed); | |
| -webkit-background-clip: text; -webkit-text-fill-color: transparent; | |
| margin: 0 0 6px; | |
| } | |
| .sub { text-align: center; color: #718096; font-size: 0.88rem; margin-bottom: 24px; } | |
| .row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 10px; } | |
| @media(max-width:560px){ .row { grid-template-columns: 1fr; } } | |
| .field label { display: block; font-size: 0.85rem; font-weight: 600; color: #4a5568; margin-bottom: 5px; } | |
| .field textarea, .field input, .field select { | |
| width: 100%; background: #fff; | |
| border: 1px solid #cbd5e0; border-radius: 8px; | |
| color: #1a202c; font-family: 'Vazirmatn', sans-serif; | |
| font-size: 0.9rem; padding: 9px 12px; | |
| transition: border-color .2s, box-shadow .2s; resize: vertical; | |
| } | |
| .field textarea { min-height: 75px; } | |
| .field input[type=password], .field select { height: 42px; } | |
| .field textarea:focus, .field input:focus, .field select:focus { | |
| outline: none; border-color: #667eea; | |
| box-shadow: 0 0 0 3px rgba(102,126,234,.15); | |
| } | |
| .field select { cursor: pointer; } | |
| .field .hint { font-size: .75rem; color: #a0aec0; margin-top: 4px; } | |
| #startBtn { | |
| width: 100%; padding: 12px; | |
| background: linear-gradient(135deg, #667eea, #764ba2); | |
| border: none; border-radius: 8px; color: #fff; | |
| font-family: 'Vazirmatn', sans-serif; font-weight: 700; font-size: 1rem; | |
| cursor: pointer; margin-top: 10px; margin-bottom: 22px; | |
| box-shadow: 0 4px 18px rgba(102,126,234,.3); transition: all .2s; | |
| } | |
| #startBtn:hover:not(:disabled) { filter: brightness(1.08); transform: translateY(-1px); } | |
| #startBtn:disabled { opacity: .5; cursor: not-allowed; transform: none; } | |
| .step { | |
| border-radius: 12px; margin: 9px 0; | |
| border: 1px solid #e2e8f0; background: #fff; | |
| overflow: hidden; transition: border-color .3s, box-shadow .3s; | |
| } | |
| .step.s-pending { opacity: .38; } | |
| .step.s-active { border-color: #667eea; box-shadow: 0 0 0 3px rgba(102,126,234,.1); } | |
| .step.s-done { border-color: #68d391; } | |
| .step.s-error { border-color: #fc8181; } | |
| .step-hd { | |
| display: flex; align-items: center; gap: 10px; | |
| padding: 12px 16px; font-weight: 700; font-size: .9rem; | |
| background: #f7fafc; color: #2d3748; border-bottom: 1px solid #e2e8f0; | |
| } | |
| .step.s-active .step-hd { background: #ebf4ff; color: #3c5fb8; border-color: #bee3f8; } | |
| .step.s-done .step-hd { background: #f0fff4; color: #276749; border-color: #c6f6d5; } | |
| .step.s-error .step-hd { background: #fff5f5; color: #c53030; border-color: #fed7d7; } | |
| .step-hd .badge { | |
| margin-right: auto; font-size: .72rem; font-weight: 600; | |
| padding: 2px 9px; border-radius: 20px; | |
| background: rgba(0,0,0,.06); color: inherit; white-space: nowrap; | |
| } | |
| .step-body { | |
| max-height: 0; overflow: hidden; | |
| transition: max-height .35s ease; | |
| } | |
| .step.s-active .step-body, | |
| .step.s-done .step-body, | |
| .step.s-error .step-body { max-height: 700px; } | |
| .item { | |
| display: flex; gap: 10px; align-items: flex-start; | |
| padding: 8px 14px; border-bottom: 1px solid #f0f4f8; | |
| font-size: .87rem; line-height: 1.7; color: #2d3748; | |
| } | |
| .item:last-child { border-bottom: none; } | |
| .item .num { | |
| min-width: 22px; height: 22px; border-radius: 50%; | |
| background: #667eea; color: #fff; | |
| font-size: .72rem; font-weight: 700; | |
| display: flex; align-items: center; justify-content: center; margin-top: 2px; flex-shrink: 0; | |
| } | |
| .step.s-done .item .num { background: #48bb78; } | |
| .step.s-error .item .num { background: #fc8181; } | |
| .thinking { | |
| padding: 10px 16px; color: #a0aec0; | |
| font-style: italic; font-size: .87rem; | |
| } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| .spin-icon { display: inline-block; animation: spin 1s linear infinite; } | |
| #doneBanner { | |
| text-align: center; margin-top: 16px; padding: 14px; | |
| background: #f0fff4; border: 1px solid #68d391; | |
| border-radius: 10px; color: #276749; | |
| font-weight: 700; font-size: .97rem; display: none; | |
| } | |
| #errBox { | |
| background: #fff5f5; border: 1px solid #fc8181; | |
| color: #c53030; padding: 11px 14px; border-radius: 8px; | |
| font-weight: 600; margin-bottom: 12px; display: none; font-size: .88rem; | |
| } | |
| #modelStatus { | |
| font-size: .75rem; color: #a0aec0; margin-top: 3px; | |
| } | |
| </style> | |
| <div id="planner-root"> | |
| <h1>🧩 MultiStep Task Planner</h1> | |
| <p class="sub">برنامهریزی هوشمند با استدلال چندمرحلهای</p> | |
| <div class="row"> | |
| <div class="field"> | |
| <label>🎯 هدف شما</label> | |
| <textarea id="goal" placeholder="مثال: برنامه سفر ۳ روزه به ماسال"></textarea> | |
| </div> | |
| <div class="field"> | |
| <label>🔑 OpenRouter API Key</label> | |
| <input type="password" id="apikey" placeholder="sk-or-v1-..." oninput="onKeyChange()"> | |
| <div class="hint">برای دریافت کلید رایگان: openrouter.ai</div> | |
| </div> | |
| </div> | |
| <div class="field" style="margin-bottom:0"> | |
| <label>🤖 مدل هوش مصنوعی</label> | |
| <select id="modelSelect"> | |
| <option value="">— ابتدا API Key وارد کنید —</option> | |
| </select> | |
| <div id="modelStatus">وارد کردن API Key مدلهای رایگان را بارگذاری میکند</div> | |
| </div> | |
| <button id="startBtn" onclick="startPlanning()">🚀 شروع برنامهریزی</button> | |
| <div id="errBox"></div> | |
| <div id="stepsArea"></div> | |
| <div id="doneBanner">✅ برنامهریزی کامل شد!</div> | |
| </div> | |
| <script> | |
| // Known fallback free models (always valid) | |
| const FALLBACK_MODELS = [ | |
| { id: "deepseek/deepseek-r1:free", name: "DeepSeek R1 (رایگان)" }, | |
| { id: "deepseek/deepseek-v3-base:free", name: "DeepSeek V3 (رایگان)" }, | |
| { id: "nvidia/nemotron-3-ultra:free", name: "NVIDIA Nemotron 3 Ultra (رایگان)" }, | |
| { id: "openrouter/owl-alpha:free", name: "OpenRouter Owl Alpha (رایگان)" }, | |
| { id: "openai/gpt-oss-20b:free", name: "GPT-OSS 20B (رایگان)" }, | |
| ]; | |
| let keyTimeout = null; | |
| function onKeyChange() { | |
| clearTimeout(keyTimeout); | |
| keyTimeout = setTimeout(loadModels, 800); | |
| } | |
| async function loadModels() { | |
| const apiKey = document.getElementById("apikey").value.trim(); | |
| const sel = document.getElementById("modelSelect"); | |
| const status = document.getElementById("modelStatus"); | |
| if (!apiKey) return; | |
| status.textContent = "⏳ در حال بارگذاری مدلهای رایگان..."; | |
| sel.innerHTML = '<option value="">در حال بارگذاری...</option>'; | |
| try { | |
| const r = await fetch("https://openrouter.ai/api/v1/models", { | |
| headers: { "Authorization": "Bearer " + apiKey } | |
| }); | |
| if (!r.ok) throw new Error("خطا در اتصال"); | |
| const data = await r.json(); | |
| const freeModels = data.data.filter(m => | |
| m.id.endsWith(":free") && | |
| m.pricing && | |
| parseFloat(m.pricing.prompt) === 0 | |
| ).slice(0, 20); | |
| sel.innerHTML = ""; | |
| freeModels.forEach((m, i) => { | |
| const opt = document.createElement("option"); | |
| opt.value = m.id; | |
| opt.textContent = (m.name || m.id) + " (رایگان)"; | |
| if (i === 0) opt.selected = true; | |
| sel.appendChild(opt); | |
| }); | |
| if (freeModels.length === 0) throw new Error("مدل رایگان پیدا نشد"); | |
| status.textContent = "✅ " + freeModels.length + " مدل رایگان بارگذاری شد"; | |
| } catch(e) { | |
| // fallback | |
| sel.innerHTML = ""; | |
| FALLBACK_MODELS.forEach((m, i) => { | |
| const opt = document.createElement("option"); | |
| opt.value = m.id; | |
| opt.textContent = m.name; | |
| if (i === 0) opt.selected = true; | |
| sel.appendChild(opt); | |
| }); | |
| status.textContent = "⚠️ لیست پیشفرض (بارگذاری خودکار ناموفق بود)"; | |
| } | |
| } | |
| const STEPS = [ | |
| { | |
| id: "s1", title: "تحلیل هدف", emoji: "🎯", badge: "شناخت", | |
| prompt: (c) => | |
| `هدف: "${c.goal}" | |
| این هدف را به ۵ گام اصلی تقسیم کن. | |
| فقط لیست شمارهگذاریشده فارسی، هر مورد یک جمله.` | |
| }, | |
| { | |
| id: "s2", title: "منابع و ابزارها", emoji: "🛠️", badge: "آمادگی", | |
| prompt: (c) => | |
| `هدف: "${c.goal}" | |
| گامها: ${c.s1} | |
| چه منابع، ابزارها یا پیشنیازهایی لازم است؟ | |
| لیست مختصر فارسی.` | |
| }, | |
| { | |
| id: "s3", title: "زمانبندی", emoji: "📅", badge: "برنامه", | |
| prompt: (c) => | |
| `هدف: "${c.goal}" | |
| گامها: ${c.s1} | |
| یک زمانبندی واقعی برای رسیدن به این هدف بنویس. | |
| فرمت: هفته/ماه X: فعالیت ...` | |
| }, | |
| { | |
| id: "s4", title: "برنامه عملیاتی", emoji: "📋", badge: "اجرا", | |
| prompt: (c) => | |
| `هدف: "${c.goal}" | |
| گامها: ${c.s1} | منابع: ${c.s2} | زمانبندی: ${c.s3} | |
| یک برنامه عملی و دقیق برای شروع بنویس.` | |
| }, | |
| { | |
| id: "s5", title: "نکات کلیدی", emoji: "✅", badge: "توصیه", | |
| prompt: (c) => | |
| `هدف: "${c.goal}" | |
| برنامه: ${c.s4} | |
| ۵ نکته مهم برای موفقیت در این مسیر بنویس. | |
| لیست شمارهگذاریشده فارسی.` | |
| } | |
| ]; | |
| function parseLines(text) { | |
| return text.split('\\n') | |
| .map(l => l.replace(/^[\\d]+[\\.\\-\\)]\\.?\\s*/, '').trim()) | |
| .filter(l => l.length > 3); | |
| } | |
| function buildSteps() { | |
| const area = document.getElementById("stepsArea"); | |
| area.innerHTML = ""; | |
| STEPS.forEach((s, i) => { | |
| const d = document.createElement("div"); | |
| d.className = "step s-pending"; d.id = "step-" + i; | |
| d.innerHTML = ` | |
| <div class="step-hd"> | |
| <span>${s.emoji}</span> | |
| <span>مرحله ${i+1} — ${s.title}</span> | |
| <span class="badge">${s.badge}</span> | |
| </div> | |
| <div class="step-body"></div>`; | |
| area.appendChild(d); | |
| }); | |
| } | |
| function setStep(idx, state, lines) { | |
| const el = document.getElementById("step-" + idx); | |
| if (!el) return; | |
| el.className = "step s-" + state; | |
| const s = STEPS[idx]; | |
| const icons = { pending: s.emoji, active: '<span class="spin-icon">⏳</span>', done: "✅", error: "❌" }; | |
| el.querySelector(".step-hd").innerHTML = ` | |
| <span>${icons[state]}</span> | |
| <span>مرحله ${idx+1} — ${s.title}</span> | |
| <span class="badge">${s.badge}</span>`; | |
| const body = el.querySelector(".step-body"); | |
| if (state === "active") { | |
| body.innerHTML = '<div class="thinking">در حال پردازش...</div>'; | |
| } else if (lines && lines.length) { | |
| body.innerHTML = lines.map((l, i) => | |
| `<div class="item"><span class="num">${state==="error"?"!":i+1}</span><span>${l}</span></div>` | |
| ).join(""); | |
| } | |
| } | |
| function showErr(msg) { | |
| const b = document.getElementById("errBox"); | |
| b.textContent = "❌ " + msg; b.style.display = "block"; | |
| } | |
| async function callLLM(apiKey, model, prompt) { | |
| const controller = new AbortController(); | |
| const tid = setTimeout(() => controller.abort(), 50000); | |
| try { | |
| const r = await fetch("https://openrouter.ai/api/v1/chat/completions", { | |
| method: "POST", signal: controller.signal, | |
| headers: { | |
| "Authorization": "Bearer " + apiKey, | |
| "Content-Type": "application/json", | |
| "HTTP-Referer": "https://huggingface.co/spaces", | |
| "X-Title": "MultiStep Task Planner" | |
| }, | |
| body: JSON.stringify({ | |
| model: model, | |
| messages: [{ role: "user", content: prompt }], | |
| max_tokens: 500, | |
| temperature: 0.6 | |
| }) | |
| }); | |
| clearTimeout(tid); | |
| if (!r.ok) { | |
| const errText = await r.text(); | |
| // If model not found, try next fallback | |
| if (r.status === 404) throw new Error("MODEL_NOT_FOUND:" + errText); | |
| throw new Error(errText.slice(0, 120)); | |
| } | |
| const d = await r.json(); | |
| return d.choices[0].message.content.trim(); | |
| } catch(e) { | |
| clearTimeout(tid); | |
| throw e; | |
| } | |
| } | |
| async function callWithFallback(apiKey, prompt) { | |
| const sel = document.getElementById("modelSelect"); | |
| // Try selected model first, then fallbacks | |
| const modelsToTry = [sel.value, ...FALLBACK_MODELS.map(m => m.id)] | |
| .filter((v, i, a) => v && a.indexOf(v) === i); // unique, non-empty | |
| for (const model of modelsToTry) { | |
| try { | |
| const result = await callLLM(apiKey, model, prompt); | |
| // Update selector to show which model worked | |
| if (model !== sel.value) { | |
| document.getElementById("modelStatus").textContent = "✅ استفاده از: " + model; | |
| } | |
| return result; | |
| } catch(e) { | |
| if (e.message.startsWith("MODEL_NOT_FOUND") || e.name === "AbortError") { | |
| continue; // try next model | |
| } | |
| throw e; // real error, stop | |
| } | |
| } | |
| throw new Error("هیچ مدل رایگانی در دسترس نیست. لطفاً بعداً امتحان کنید."); | |
| } | |
| async function startPlanning() { | |
| const goal = document.getElementById("goal").value.trim(); | |
| const apiKey = document.getElementById("apikey").value.trim(); | |
| const btn = document.getElementById("startBtn"); | |
| document.getElementById("errBox").style.display = "none"; | |
| document.getElementById("doneBanner").style.display = "none"; | |
| if (!goal) { showErr("هدف را وارد کنید"); return; } | |
| if (!apiKey) { showErr("API Key را وارد کنید"); return; } | |
| btn.disabled = true; btn.textContent = "⏳ در حال برنامهریزی..."; | |
| buildSteps(); | |
| const ctx = { goal }; | |
| for (let i = 0; i < STEPS.length; i++) { | |
| setStep(i, "active"); | |
| try { | |
| const raw = await callWithFallback(apiKey, STEPS[i].prompt(ctx)); | |
| ctx[STEPS[i].id] = raw; | |
| const lines = parseLines(raw); | |
| setStep(i, "done", lines.length > 0 ? lines : [raw]); | |
| } catch(e) { | |
| ctx[STEPS[i].id] = ""; | |
| setStep(i, "error", [e.message]); | |
| } | |
| } | |
| document.getElementById("doneBanner").style.display = "block"; | |
| btn.disabled = false; btn.textContent = "🔄 برنامهریزی مجدد"; | |
| } | |
| </script> | |
| """ | |
| with gr.Blocks(title="MultiStep Task Planner") as demo: | |
| gr.HTML(HTML_APP) | |
| if __name__ == "__main__": | |
| demo.launch() |