chatbot1 / build_hg_viewer.py
Nguyen5's picture
commit
6548bf5
# build_hg_viewer.py
import os
from supabase import create_client
from dotenv import load_dotenv
load_dotenv()
SUPABASE_URL = os.environ["SUPABASE_URL"]
SUPABASE_SERVICE_ROLE = os.environ["SUPABASE_SERVICE_ROLE"]
if not SUPABASE_URL or not SUPABASE_SERVICE_ROLE:
raise RuntimeError("Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE")
supabase = create_client(SUPABASE_URL, SUPABASE_SERVICE_ROLE)
from upload_weblink_to_supabase import extract_paragraphs
# ======== HTML TEMPLATE ========
VIEW_TEMPLATE = """
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Hochschulgesetz NRW – Paragraph Viewer</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
}
/* ----------- SIDEBAR ------------- */
#sidebar {
width: 280px;
height: 100vh;
overflow-y: auto;
background: #f5f5f5;
border-right: 1px solid #ccc;
padding: 15px;
position: sticky;
top: 0;
}
#sidebar h2 {
margin-top: 0;
}
#searchBox {
width: 100%;
padding: 8px;
font-size: 15px;
margin-bottom: 10px;
border: 1px solid #aaa;
border-radius: 5px;
}
.sidebar-link {
display: block;
padding: 6px 8px;
margin-bottom: 4px;
text-decoration: none;
color: #003366;
border-radius: 4px;
}
.sidebar-link:hover {
background: #e0e7ff;
color: #001d4d;
}
/* ----------- CONTENT ------------- */
#content {
flex: 1;
padding: 25px;
max-width: 900px;
}
/* Absatz block */
.para {
padding: 20px 0;
border-bottom: 1px solid #ddd;
}
.para h2 {
color: #003366;
margin-bottom: 10px;
}
/* ----------- Fußnoten ------------- */
.fn-block {
background: #fafafa;
border-left: 4px solid #999;
padding: 12px;
margin-top: 10px;
margin-bottom: 25px;
}
.fn-toggle {
cursor: pointer;
font-weight: bold;
color: #003366;
margin-bottom: 5px;
}
.fn-content {
display: none;
padding-left: 10px;
}
.fn-title {
font-weight: bold;
margin-bottom: 6px;
}
.fn-item {
margin-bottom: 8px;
}
/* ----------- Highlight beim Öffnen ------------- */
.highlight {
animation: flash 2s ease-in-out;
background: #fff8c6 !important;
}
@keyframes flash {
0% { background: #fff8c6; }
100% { background: transparent; }
}
/* Keyword highlight */
.keyword {
background: yellow;
padding: 2px 3px;
border-radius: 3px;
}
/* Back to top button */
#topBtn {
position: fixed;
bottom: 25px;
right: 25px;
background: #003366;
color: white;
border-radius: 8px;
padding: 10px 14px;
cursor: pointer;
font-size: 16px;
display: none;
}
</style>
</head>
<body>
<div id="sidebar">
<h2>Inhaltsverzeichnis</h2>
<input type="text" id="searchBox" placeholder="Suchen nach § …">
<!-- SIDEBAR_LINKS -->
</div>
<div id="content">
<h1>Hochschulgesetz NRW – Paragraph Viewer</h1>
<!-- PARAGRAPH_CONTENT -->
</div>
<div id="topBtn" onclick="scrollToTop()">⬆️ Top</div>
<script>
// ------ TỰ ĐỘNG HIGHLIGHT Absatz khi có #anchor HIGHLIGHT ABSATZ & SCROLL ------
window.onload = function() {
const anchor = window.location.hash.substring(1);
const params = new URLSearchParams(window.location.search);
const keywords = params.get("k");
if (anchor) {
const el = document.getElementById(anchor);
if (el) {
el.classList.add("highlight");
el.scrollIntoView({ behavior: "smooth", block: "center" });
}
}
/* KEYWORD HIGHLIGHT */
if (keywords) {
const words = keywords.split("%20");
highlightKeywords(words);
}
};
/* --- KEYWORD HIGHLIGHT FUNCTION --- */
function highlightKeywords(words) {
const container = document.getElementById("content");
let html = container.innerHTML;
words.forEach(word => {
if (word.length < 2) return;
const regex = new RegExp(`(${decodeURIComponent(word)})`, "gi");
html = html.replace(regex, `<span class="keyword">$1</span>`);
});
container.innerHTML = html;
}
/* --- SEARCH IN SIDEBAR --- */
document.getElementById("searchBox").addEventListener("input", function() {
const q = this.value.toLowerCase();
document.querySelectorAll(".sidebar-link").forEach(link => {
const txt = link.innerText.toLowerCase();
link.style.display = txt.includes(q) ? "block" : "none";
});
});
/* --- COLLAPSIBLE FUSSNOTEN --- */
document.addEventListener("click", function(e) {
if (e.target.classList.contains("fn-toggle")) {
const content = e.target.nextElementSibling;
content.style.display = content.style.display === "block" ? "none" : "block";
}
});
/* --- BACK TO TOP BUTTON --- */
window.onscroll = function() {
document.getElementById("topBtn").style.display =
window.scrollY > 300 ? "block" : "none";
};
function scrollToTop() {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
</script>
</body>
</html>
"""
# -------------------------------------------------------------------
# 2. BUILD VIEWER
# -------------------------------------------------------------------
def build_html_from_db():
"""
Liest alle Paragraphen aus hg_nrw und baut daraus HTML.
"""
print(">>> Lade Paragraphen aus Supabase (hg_nrw) …")
paras = extract_paragraphs()
# 5.12_2:13
res = supabase.table("hg_nrw").select("*").order("order_index").execute()
rows = res.data or []
sidebar_links = ""
content_html = ""
for p in paras:
pid = p["abs_id"]
title = p["title"]
body = p["content"]
# Sidebar item
sidebar_links += f'<a class="sidebar-link" href="#{pid}">{title}</a>\n'
# Fußnoten tách riêng (bắt đầu bằng "Fn 1", "Fn 2", ...)
lines = body.split("\n")
main_text = []
fn_text = []
in_fn = False
for line in lines:
if line.startswith("Fn "):
in_fn = True
if in_fn:
fn_text.append(line)
else:
main_text.append(line)
footnotes_html = ""
if fn_text:
footnotes_html += '<div class="fn-block">'
footnotes_html += '<div class="fn-title">Fußnoten:</div>'
for fn in fn_text:
footnotes_html += f'<div class="fn-item">{fn}</div>'
footnotes_html += "</div>"
# Paragraph block
content_html += f"""
<div class="para" id="{pid}">
<h2>{title}</h2>
<div>{'<br>'.join(main_text)}</div>
{footnotes_html}
</div>
"""
html = VIEW_TEMPLATE.replace("<!-- SIDEBAR_LINKS -->", sidebar_links)
html = html.replace("<!-- PARAGRAPH_CONTENT -->", content_html)
return html
# -------------------------------------------------------------------
# 3. UPLOAD TO SUPABASE STORAGE
# -------------------------------------------------------------------
def upload_html():
html = build_html_from_db()
supabase.storage.from_("hg_viewer").update(
"hg_clean.html",
html.encode("utf-8"),
{
"content-type": "text/html",
"x-upsert": "true"
}
)
print("✔ hg_clean.html uploaded!")
if __name__ == "__main__":
upload_html()