promptforge / backend /simple.js
Really-amin's picture
Upload 18 files
a5f611b verified
const API = "";
const $ = id => document.getElementById(id);
let currentPromptId = null;
function toast(msg, type = "info") {
const el = $("toast");
el.textContent = `✅ ${msg}`;
el.classList.add("show");
setTimeout(() => el.classList.remove("show"), 3000);
}
async function apiFetch(path, method = "GET", body = null) {
const opts = { method, headers: { "Content-Type": "application/json" } };
if (body) opts.body = JSON.stringify(body);
const r = await fetch(API + path, opts);
if (!r.ok) {
const e = await r.json().catch(() => ({ detail: r.statusText }));
throw new Error(e.detail || "Request failed");
}
return r.json();
}
// ========== API Key Management with localStorage ==========
const STORAGE_KEY = "promptforge_hf_key";
function loadSavedKey() {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
$("hf-key").value = saved;
updateKeyStatus(saved.length >= 10);
}
}
function saveKey(key) {
if (key && key.length >= 10) {
localStorage.setItem(STORAGE_KEY, key);
} else {
localStorage.removeItem(STORAGE_KEY);
}
}
function updateKeyStatus(hasKey) {
$("check-key").disabled = !hasKey;
$("key-dot").className = "dot" + (hasKey ? " ok" : "");
$("key-status-text").textContent = hasKey ? "Key saved" : "No key saved";
}
loadSavedKey();
// Toggle key section visibility
function toggleKeySection() {
const body = $("key-body");
const chevron = $("key-chevron").parentElement; // کلید header
body.classList.toggle("hidden");
chevron.classList.toggle("open");
}
window.toggleKeySection = toggleKeySection; // برای onclick در HTML
$("toggle-key").addEventListener("click", () => {
const input = $("hf-key");
input.type = input.type === "password" ? "text" : "password";
});
$("hf-key").addEventListener("input", (e) => {
const val = e.target.value.trim();
updateKeyStatus(val.length >= 10);
// ذخیره خودکار بعد از تأیید (اما فعلاً فقط وضعیت)
});
$("check-key").addEventListener("click", async () => {
const key = $("hf-key").value.trim();
if (!key) return;
const btn = $("check-key");
btn.disabled = true;
btn.innerHTML = `<span class="spinner"></span>`;
try {
const res = await apiFetch("/api/check-key", "POST", { provider: "huggingface", api_key: key });
if (res.valid) {
$("key-dot").className = "dot ok";
$("key-status-text").textContent = "Valid – saved";
saveKey(key);
} else {
$("key-dot").className = "dot err";
$("key-status-text").textContent = "Invalid";
localStorage.removeItem(STORAGE_KEY);
}
toast(res.message, res.valid ? "success" : "error");
} catch (e) {
$("key-dot").className = "dot err";
$("key-status-text").textContent = "Check failed";
toast("Check failed: " + e.message, "error");
} finally {
btn.disabled = false;
btn.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
}
});
$("clear-key").addEventListener("click", () => {
localStorage.removeItem(STORAGE_KEY);
$("hf-key").value = "";
updateKeyStatus(false);
toast("Key cleared", "info");
});
// ========== Speech Recognition (Persian) ==========
const micBtn = $("mic-btn");
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (SpeechRecognition) {
const recognition = new SpeechRecognition();
recognition.lang = "fa-IR";
recognition.continuous = false;
recognition.interimResults = false;
micBtn.addEventListener("click", () => {
recognition.start();
micBtn.classList.add("recording");
});
recognition.addEventListener("result", (e) => {
const text = e.results[0][0].transcript;
$("instruction").value += text;
});
recognition.addEventListener("end", () => micBtn.classList.remove("recording"));
recognition.addEventListener("error", () => micBtn.classList.remove("recording"));
} else {
micBtn.style.opacity = "0.3";
micBtn.title = "Speech not supported in this browser";
}
// ========== Generate ==========
$("generate-btn").addEventListener("click", async () => {
const instruction = $("instruction").value.trim();
if (!instruction) { toast("Please enter an instruction", "error"); return; }
const targetModel = $("target-model").value;
const apiKey = $("hf-key").value.trim() || null; // از input (که با localStorage پر شده)
const btn = $("generate-btn");
btn.disabled = true;
btn.innerHTML = `<span class="spinner"></span> Generating...`;
try {
const data = await apiFetch("/api/generate", "POST", {
instruction,
target_model: targetModel,
provider: "huggingface",
api_key: apiKey,
enhance: !!apiKey,
output_format: "both",
persona: "default",
style: "professional",
user_constraints: []
});
currentPromptId = data.prompt_id;
renderOutput(data.manifest.structured_prompt);
$("output-area").classList.remove("hidden");
toast("Prompt generated!", "success");
} catch (e) {
toast(`Error: ${e.message}`, "error");
} finally {
btn.disabled = false;
btn.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg> Generate Prompt`;
}
});
function renderOutput(sp) {
const container = $("quad-sections");
const sections = [
{ title: "ROLE", content: sp.role },
{ title: "TASK", content: sp.task },
{ title: "INPUT FORMAT", content: sp.input_format },
{ title: "OUTPUT FORMAT", content: sp.output_format },
{ title: "CONSTRAINTS", content: sp.constraints.join("\n") },
{ title: "SAFETY", content: sp.safety.join("\n") }
];
container.innerHTML = sections.map(s => `
<div class="quad-section">
<div class="section-header">
<strong>${s.title}</strong>
<button class="copy-section" data-content="${escapeHtml(s.content)}">📋 Copy</button>
</div>
<pre class="section-content">${escapeHtml(s.content)}</pre>
</div>
`).join("");
// Copy individual section
document.querySelectorAll(".copy-section").forEach(btn => {
btn.addEventListener("click", () => {
navigator.clipboard.writeText(btn.dataset.content);
btn.textContent = "✅";
setTimeout(() => btn.textContent = "📋 Copy", 1500);
});
});
// Copy all
$("copy-all").addEventListener("click", () => {
const full = sections.map(s => `## ${s.title}\n${s.content}`).join("\n\n");
navigator.clipboard.writeText(full);
toast("Full prompt copied!", "success");
});
// Export JSON
$("export-json").addEventListener("click", () => {
const json = JSON.stringify(sp, null, 2);
const blob = new Blob([json], { type: "application/json" });
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = `prompt-${currentPromptId.slice(0,8)}.json`;
a.click();
URL.revokeObjectURL(a.href);
});
}
function escapeHtml(unsafe) {
return unsafe.replace(/[&<>"]/g, function(m) {
if (m === "&") return "&amp;";
if (m === "<") return "&lt;";
if (m === ">") return "&gt;";
if (m === '"') return "&quot;";
return m;
});
}