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
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,'&').replace(/</g,'<').replace(/>/g,'>')
|
||
|
|
.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,'&').replace(/</g,'<').replace(/>/g,'>');
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|