mirror of
https://github.com/hoshikawa2/rfp_response_automation.git
synced 2026-03-03 16:09:35 +00:00
203 lines
6.3 KiB
HTML
203 lines
6.3 KiB
HTML
{% extends "base.html" %}
|
||
{% block content %}
|
||
|
||
<h1>🔐 RAG Knowledge Governance</h1>
|
||
|
||
<!-- ===================== -->
|
||
<!-- 🔎 INVALIDATE SEARCH -->
|
||
<!-- ===================== -->
|
||
<section style="margin-bottom: 40px;">
|
||
<h2>❌ Invalidate Knowledge</h2>
|
||
|
||
<form method="post" action="/admin/search">
|
||
<textarea name="statement" rows="4" style="width:100%;"
|
||
placeholder="Paste the invalid or outdated statement here">{{ statement }}</textarea>
|
||
<br><br>
|
||
<button type="submit">Search occurrences</button>
|
||
</form>
|
||
</section>
|
||
|
||
<hr>
|
||
|
||
<!-- ===================== -->
|
||
<!-- ➕ MANUAL KNOWLEDGE -->
|
||
<!-- ===================== -->
|
||
<section style="margin-bottom: 40px;">
|
||
<h2>➕ Add Manual Knowledge</h2>
|
||
|
||
<form id="manualForm">
|
||
<textarea id="manualText" rows="8" style="width:100%;"
|
||
placeholder="Paste validated Oracle technical information here..."></textarea>
|
||
|
||
<br><br>
|
||
|
||
<input type="text" id="reason" style="width:100%;"
|
||
placeholder="Reason / justification (e.g. official Oracle clarification)"/>
|
||
|
||
<br><br>
|
||
|
||
<button type="button" onclick="submitManual()">Add Knowledge</button>
|
||
</form>
|
||
|
||
<pre id="manualResult" style="margin-top:15px;"></pre>
|
||
</section>
|
||
|
||
<hr>
|
||
|
||
<!-- ===================== -->
|
||
<!-- 📚 SEARCH RESULTS -->
|
||
<!-- ===================== -->
|
||
<section>
|
||
<h2>📚 Knowledge Matches</h2>
|
||
|
||
{% if results|length == 0 %}
|
||
<p><i>No matching knowledge found.</i></p>
|
||
{% endif %}
|
||
|
||
{% for r in results %}
|
||
<div style="border:1px solid #ccc; padding:15px; margin:15px 0; border-radius:6px;">
|
||
|
||
<strong>Chunk Hash:</strong>
|
||
<span data-hash-label>{{ r.chunk_hash or "—" }}</span><br>
|
||
<strong>Origin:</strong> {{ r.origin or "UNKNOWN" }}<br>
|
||
<strong>Created at:</strong> {{ r.created_at or "—" }}<br>
|
||
<strong>Status:</strong> {{ r.status }}<br>
|
||
<strong>Source:</strong> {{ r.source }}<br>
|
||
|
||
<div style="margin-top:10px;">
|
||
<strong>Content:</strong>
|
||
<pre style="
|
||
white-space: pre-wrap;
|
||
background:#f8f8f8;
|
||
padding:10px;
|
||
border-radius:4px;
|
||
border:1px solid #ddd;
|
||
max-height:400px;
|
||
overflow:auto;
|
||
">{{ r.text }}</pre>
|
||
<div style="margin-top:10px;">
|
||
<strong>Change to:</strong>
|
||
|
||
<textarea class="edit-box" data-hash="{{ r.chunk_hash }}"
|
||
style="
|
||
width:100%;
|
||
min-height:140px;
|
||
background:#f8f8f8;
|
||
padding:10px;
|
||
border-radius:4px;
|
||
border:1px solid #ddd;
|
||
font-family: monospace;
|
||
"
|
||
data-original="{{ r.text | e }}"
|
||
>{{ r.text }}</textarea>
|
||
</div>
|
||
</div>
|
||
|
||
{% if r.chunk_hash %}
|
||
<button
|
||
style="color:red;"
|
||
data-hash="{{ (r.chunk_hash or '') | string | e }}"
|
||
onclick="invalidateChunk(this)">
|
||
Invalidate
|
||
</button>
|
||
<button
|
||
style="color:blue; margin-left:10px;"
|
||
data-hash="{{ (r.chunk_hash or '') | string | e }}"
|
||
onclick="updateChunk(this)">
|
||
Change content
|
||
</button>
|
||
{% else %}
|
||
<p><i>Derived from Knowledge Graph (non-revocable)</i></p>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
</section>
|
||
|
||
<script>
|
||
async function submitManual() {
|
||
const text = document.getElementById("manualText").value;
|
||
const reason = document.getElementById("reason").value;
|
||
|
||
const res = await fetch("/admin/add-knowledge", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
credentials: "same-origin",
|
||
body: JSON.stringify({ text, reason })
|
||
});
|
||
|
||
const data = await res.json();
|
||
document.getElementById("manualResult").textContent = JSON.stringify(data, null, 2);
|
||
}
|
||
async function invalidateChunk(btn) {
|
||
const chunkHash = btn.dataset.hash;
|
||
console.log("chunkHash:", chunkHash, "type:", typeof chunkHash);
|
||
|
||
const res = await fetch("/admin/revoke", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
credentials: "same-origin",
|
||
body: JSON.stringify({
|
||
chunk_hash: chunkHash,
|
||
reason: "Invalidated via admin interface"
|
||
})
|
||
});
|
||
|
||
const data = await res.json().catch(() => ({}));
|
||
console.log("server:", res.status, data);
|
||
|
||
if (res.ok) btn.closest("div").remove();
|
||
}
|
||
async function updateChunk(btn) {
|
||
|
||
const card = btn.closest("div");
|
||
|
||
const chunkHash = btn.dataset.hash;
|
||
|
||
const textarea = card.querySelector(`.edit-box[data-hash="${chunkHash}"]`);
|
||
const newText = textarea.value;
|
||
|
||
if (!chunkHash) return;
|
||
|
||
if (!confirm("Update this chunk content?")) return;
|
||
|
||
btn.disabled = true;
|
||
btn.textContent = "Saving...";
|
||
|
||
const res = await fetch("/admin/update-chunk", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
credentials: "same-origin",
|
||
body: JSON.stringify({
|
||
chunk_hash: chunkHash,
|
||
text: newText
|
||
})
|
||
});
|
||
|
||
const data = await res.json().catch(() => ({}));
|
||
|
||
btn.disabled = false;
|
||
|
||
if (res.ok) {
|
||
const newHash = data.chunk_hash;
|
||
|
||
const hashLabel = card.querySelector("[data-hash-label]");
|
||
if (hashLabel && newHash) {
|
||
hashLabel.textContent = newHash;
|
||
}
|
||
|
||
btn.dataset.hash = newHash;
|
||
|
||
textarea.style.borderColor = "green";
|
||
|
||
setTimeout(() => {
|
||
btn.textContent = "Change content";
|
||
textarea.style.borderColor = "#ddd";
|
||
}, 1200);
|
||
|
||
} else {
|
||
alert("Failed to update");
|
||
btn.textContent = "Change content";
|
||
}
|
||
}
|
||
</script>
|
||
{% endblock %} |