You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

365 lines
12 KiB

4 days ago
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>AI Prompt Compressor Engine</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:Arial;background:#f5f5f5;padding:40px}
.container{max-width:900px;margin:auto}
h1{margin-bottom:20px}
.prompt-row{display:flex;gap:8px;width:100%}
.prompt-row input{flex:1;padding:10px;border:1px solid #ccc;border-radius:8px;font-size:14px}
.extra-row{margin-top:10px}
.extra-row input{width:100%;padding:10px;border:1px solid #ccc;border-radius:8px;font-size:13px}
button{padding:10px 16px;border:none;border-radius:8px;background:black;color:white;cursor:pointer;font-size:13px}
button.secondary{background:#555}
button:disabled{background:#999;cursor:not-allowed}
.keyword-box{margin-top:20px;padding:16px;background:white;border-radius:10px;border:1px solid #ddd;min-height:80px;width:100%}
.keyword-box b{display:block;margin-bottom:10px;font-size:14px}
.selected-area{margin-top:16px;padding:12px 16px;background:white;border-radius:10px;border:1px solid #ddd;display:none}
.selected-area b{display:block;margin-bottom:8px;font-size:14px}
.chip{display:inline-block;padding:7px 14px;margin:4px;border-radius:20px;border:1px solid #ccc;cursor:pointer;background:#fafafa;font-size:13px;transition:all .15s}
.chip:hover{border-color:#000;background:#f0f0f0}
.chip.selected{background:#16a34a;color:white;border-color:#16a34a}
.chip.top{border-color:#f59e0b;background:#fffbeb;font-weight:600}
.chip.top:hover{background:#fef3c7}
.chip.top.selected{background:#16a34a;border-color:#16a34a;color:white}
.kw-loading-spinner{display:flex;align-items:center;gap:8px;color:#999;font-size:13px;padding:8px 0}
.spinner{width:16px;height:16px;border:2px solid #ddd;border-top:2px solid #333;border-radius:50%;animation:spin .8s linear infinite;flex-shrink:0}
@keyframes spin{to{transform:rotate(360deg)}}
.kw-msg{color:#999;font-size:13px;padding:8px 0;display:block}
.load-more-btn{display:inline-block;padding:6px 14px;margin:6px 4px;border-radius:20px;border:1px dashed #ccc;background:white;color:#666;font-size:12px;cursor:pointer;transition:all .2s}
.load-more-btn:hover{border-color:#333;color:#333;border-style:solid}
.load-more-btn:disabled{color:#bbb;cursor:not-allowed}
.actions{margin-top:16px;display:flex;gap:8px;align-items:center}
.chat-block{margin-top:24px;background:white;padding:20px;border-radius:10px;border:1px solid #ddd}
.chat-block .go-label{font-weight:bold;font-size:15px;margin-bottom:10px}
.chat-block .prompt-line{margin:4px 0;font-size:14px;color:#333}
.chat-block .kw-line{margin:4px 0;font-size:13px;color:#555}
.answer{background:#f1f1f1;padding:14px;border-radius:8px;margin-top:12px;line-height:1.7;font-size:14px}
.answer strong{color:#111}
.answer h2{font-size:15px;margin:10px 0 6px}
</style>
</head>
<body>
<div class="container">
<h1>AI Prompt Compressor Engine</h1>
<div class="prompt-row">
<input type="text" id="prompt" placeholder="Type your prompt or topic..." />
<button type="button" id="goBtn" onclick="askAI()">GO</button>
</div>
<div class="extra-row">
<input type="text" id="extra" placeholder="Add extra instruction to refine the prompt (optional)" />
</div>
<div class="selected-area" id="selectedArea">
<b>Selected Keywords</b>
<div id="selectedKeywords"></div>
</div>
<div class="keyword-box">
<b>Suggestions</b>
<div id="keywords"><span class="kw-msg">Start typing to see suggestions...</span></div>
</div>
<div class="actions">
<button type="button" id="askBtn" onclick="askAI()">Ask AI</button>
<button type="button" class="secondary" onclick="clearAll()">Clear</button>
</div>
<div id="chat"></div>
</div>
<script>
let selected = [];
let chatHistory = [];
let step = 0;
let debounceTimer;
let currentQuery = "";
let currentOffset = 0;
let lastFetchedQuery = ""; // tracks last fetched query to avoid duplicate calls
const promptInput = document.getElementById("prompt");
const keywordsDiv = document.getElementById("keywords");
const selectedDiv = document.getElementById("selectedKeywords");
const selectedArea = document.getElementById("selectedArea");
// ── Gibberish detector ──
function isGibberish(text) {
const words = text.toLowerCase().split(/\s+/);
let gibberishCount = 0;
for (const word of words) {
if (word.length <= 3) continue; // allow short words like ev, ai, bmw
if (word.match(/[^aeiou]{6,}/)) { gibberishCount++; continue; } // raised to 6
const vowels = (word.match(/[aeiou]/g) || []).length;
if (word.length > 5 && vowels / word.length < 0.1) { gibberishCount++; continue; }
if (/[a-z]\d{3,}[a-z]|[0-9]{4,}[a-z]/.test(word)) { gibberishCount++; continue; }
}
return gibberishCount > words.length / 2;
}
// ── Listen to typing ──
promptInput.addEventListener("input", () => {
clearTimeout(debounceTimer);
const q = promptInput.value.trim();
currentQuery = q;
if (q.length < 3) {
keywordsDiv.innerHTML = '<span class="kw-msg">Keep typing...</span>';
return;
}
if (isGibberish(q)) {
keywordsDiv.innerHTML = '<span class="kw-msg">⚠️ Please enter a meaningful topic.</span>';
return;
}
keywordsDiv.innerHTML = `
<div class="kw-loading-spinner">
<div class="spinner"></div>
<span>Loading suggestions for "<b>${q}</b>"...</span>
</div>
`;
debounceTimer = setTimeout(() => fetchKeywords(q), 500);
});
// ── Fetch suggestions ──
async function fetchKeywords(query) {
// Skip if same query already fetched — avoids duplicate API calls
if (query === lastFetchedQuery) return;
lastFetchedQuery = query;
currentOffset = 0;
try {
const res = await fetch(`http://127.0.0.1:8000/suggest?query=${encodeURIComponent(query)}&offset=0&limit=15`);
const data = await res.json();
// Backend detected gibberish
if (data.cache === "gibberish") {
keywordsDiv.innerHTML = '<span class="kw-msg">⚠️ Please enter a meaningful topic.</span>';
return;
}
// No domain found or query skipped
if (data.cache === "skip" || data.cache === "no_domain" || !data.suggestions || !data.suggestions.length) {
keywordsDiv.innerHTML = '<span class="kw-msg">No suggestions found. Try a more specific topic.</span>';
return;
}
renderChips(data.suggestions, false);
} catch (e) {
keywordsDiv.innerHTML = '<span class="kw-msg">Backend not reachable — is the server running?</span>';
}
}
// ── Render chips ──
function renderChips(items, append) {
if (!append) {
keywordsDiv.innerHTML = "";
currentOffset = 15;
} else {
const old = document.getElementById("loadMoreBtn");
if (old) old.remove();
}
if (!items.length && !append) {
keywordsDiv.innerHTML = '<span class="kw-msg">No suggestions found.</span>';
return;
}
items.forEach((k, i) => {
const chip = document.createElement("span");
chip.className = "chip" + (selected.includes(k) ? " selected" : "");
if (!append && i < 5) chip.classList.add("top"); // top 5 highlighted in gold
chip.innerText = k;
chip.dataset.kw = k;
chip.onclick = () => toggleChip(k, chip);
keywordsDiv.appendChild(chip);
});
// Load more button
const btn = document.createElement("button");
btn.id = "loadMoreBtn";
btn.className = "load-more-btn";
btn.innerText = "+ More";
btn.onclick = loadMore;
keywordsDiv.appendChild(btn);
}
// ── Toggle chip selection ──
function toggleChip(k, chipEl) {
if (selected.includes(k)) {
selected = selected.filter(x => x !== k);
chipEl.classList.remove("selected");
} else {
selected.push(k);
chipEl.classList.add("selected");
}
renderSelected();
}
// ── Render selected keywords ──
function renderSelected() {
selectedDiv.innerHTML = "";
if (!selected.length) {
selectedArea.style.display = "none";
return;
}
selectedArea.style.display = "block";
selected.forEach(k => {
const chip = document.createElement("span");
chip.className = "chip selected";
chip.innerText = k + " ✕";
chip.onclick = () => {
selected = selected.filter(x => x !== k);
const kwChip = keywordsDiv.querySelector(`[data-kw="${k}"]`);
if (kwChip) kwChip.classList.remove("selected");
renderSelected();
};
selectedDiv.appendChild(chip);
});
}
// ── Load more suggestions ──
async function loadMore() {
const btn = document.getElementById("loadMoreBtn");
btn.innerText = "Loading...";
btn.disabled = true;
try {
const res = await fetch(`http://127.0.0.1:8000/suggest?query=${encodeURIComponent(currentQuery)}&offset=${currentOffset}&limit=10`);
const data = await res.json();
if (!data.suggestions || !data.suggestions.length) {
btn.innerText = "No more suggestions";
return;
}
const existing = Array.from(keywordsDiv.querySelectorAll(".chip"))
.map(c => c.dataset.kw?.toLowerCase()).filter(Boolean);
const newItems = data.suggestions.filter(s => !existing.includes(s.toLowerCase()));
if (!newItems.length) {
btn.innerText = "No more suggestions";
return;
}
currentOffset += 10;
renderChips(newItems, true);
} catch (e) {
btn.innerText = "+ More";
btn.disabled = false;
}
}
// ── Ask AI ──
async function askAI() {
const prompt = promptInput.value.trim();
const extra = document.getElementById("extra").value.trim();
const combined = extra ? `${prompt}. ${extra}` : prompt;
if (!combined) return;
document.getElementById("goBtn").disabled = true;
document.getElementById("askBtn").disabled = true;
step++;
const chat = document.getElementById("chat");
const block = document.createElement("div");
block.className = "chat-block";
block.innerHTML = `
<div class="go-label">${step}. Go</div>
<div class="prompt-line"><b>Prompt:</b> ${escHtml(combined)}</div>
<div class="kw-line"><b>Keywords:</b> ${selected.length ? selected.map(escHtml).join(", ") : "<i>none selected</i>"}</div>
<div class="answer"><i>⏳ AI is generating answer...</i></div>
`;
chat.appendChild(block);
block.scrollIntoView({ behavior: "smooth" });
let answer = "";
try {
const res = await fetch("http://127.0.0.1:8000/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: combined,
selected_attributes: selected,
chat_history: chatHistory
})
});
const data = await res.json();
answer = data.answer || "No answer returned.";
} catch {
answer = "Backend not running. Start the server on port 8000.";
}
block.querySelector(".answer").innerHTML = `<b>AI Answer</b><br/><br/>${formatAnswer(answer)}`;
chatHistory.push({ query: combined, chips: [...selected], answer });
// Reset after submit
promptInput.value = "";
document.getElementById("extra").value = "";
selected = [];
lastFetchedQuery = ""; // reset so next query fetches fresh
renderSelected();
keywordsDiv.innerHTML = '<span class="kw-msg">Start typing to see suggestions...</span>';
document.getElementById("goBtn").disabled = false;
document.getElementById("askBtn").disabled = false;
promptInput.focus();
}
// ── Clear all ──
function clearAll() {
promptInput.value = "";
document.getElementById("extra").value = "";
selected = [];
chatHistory = [];
step = 0;
lastFetchedQuery = "";
renderSelected();
keywordsDiv.innerHTML = '<span class="kw-msg">Start typing to see suggestions...</span>';
document.getElementById("chat").innerHTML = "";
}
// ── Format AI answer (markdown-like) ──
function formatAnswer(text) {
return text
.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
.replace(/## (.*)/g, '<h2>$1</h2>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/---/g, '<hr style="border:none;border-top:1px solid #ddd;margin:10px 0"/>')
.replace(/\n\n+/g, '</p><p>')
.replace(/\n/g, '<br/>')
.replace(/^/, '<p>').replace(/$/, '</p>');
}
function escHtml(t) {
return String(t).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
</script>
</body>
</html>