Download static/widget.js from nikeshn/kulibrary: direct link, hf CLI and curl.
- Browser
- Download file 19.2 kB
-
https://huggingface.co/spaces/nikeshn/kulibrary/resolve/main/static/widget.js
- Command line
-
hf download hf://spaces/nikeshn/kulibrary/static/widget.js
-
curl -L -o widget.js https://huggingface.co/spaces/nikeshn/kulibrary/resolve/main/static/widget.js
19.2 kB
| /*! | |
| * Khazna AI Assistant — embeddable widget | |
| * Usage (LibGuides "Media / Widget" box, or any page): | |
| * <div data-khazna-assistant data-height="680"></div> | |
| * <script src="https://nikeshn-kulibrary.hf.space/widget.js" defer></script> | |
| * Options (data-* on the div): | |
| * data-api backend base URL (default: origin this script was loaded from) | |
| * data-scope initial tab: all | publications | persons | organisations | datasets | |
| * data-height box height in px (default 680; "auto" = grow with page, used by the Space's own page) | |
| * data-scopes comma list of tabs to show (default all five) | |
| * data-model gpt | claude | |
| */ | |
| (function () { | |
| "use strict"; | |
| var SCRIPT = document.currentScript; | |
| var DEFAULT_API = SCRIPT ? new URL(SCRIPT.src, location.href).origin : ""; | |
| var SCOPE_LABELS = { all: "Everything", publications: "Publications", theses: "Theses", persons: "Profiles", organisations: "Departments", projects: "Projects", equipment: "Equipment", collaborations: "Collaborations", datasets: "Datasets" }; | |
| var EXAMPLES = { | |
| all: ["KU research on hydrogen storage since 2022", "Who at KU works on agricultural robotics?", "How do I submit my thesis to Khazna?"], | |
| publications: ["Machine learning for corrosion prediction", "KU journal articles on 6G networks", "How many KU publications on COVID-19?"], | |
| theses: ["PhD theses on solar-powered desalination", "Master's theses on cybersecurity", "Theses supervised in robotics since 2022"], | |
| persons: ["Experts in lithium-ion batteries", "Who works on Arabic natural language processing?", "Find a supervisor for 6G wireless research"], | |
| organisations: ["Research centres working on space technology", "Department of Mechanical Engineering", "Labs focused on water and environment"], | |
| projects: ["Funded projects on water security", "Research projects on autonomous vehicles", "Projects funded by ASPIRE on AI"], | |
| equipment: ["Which labs have an electron microscope?", "Mass spectrometry facilities", "3D printing equipment"], | |
| datasets: ["Datasets on air quality in the UAE", "Seismic or geophysics datasets", "Medical imaging datasets"] | |
| }; | |
| var CSS = [ | |
| ".khz{--b:#0b3a63;--a:#b98a2e;--ink:#1b2430;--mu:#5d6876;--ln:#e1dfd8;--bg:#f7f6f2;--pn:#fff;--oa:#1f7a4d;--ch:#eef2f6;", | |
| "font:15px/1.55 'IBM Plex Sans',-apple-system,'Segoe UI',Roboto,Arial,sans-serif;color:var(--ink);background:var(--bg);", | |
| "border:1px solid var(--ln);border-radius:12px;display:flex;flex-direction:column;overflow:hidden;text-align:left;box-sizing:border-box}", | |
| ".khz *,.khz *:before,.khz *:after{box-sizing:border-box}", | |
| ".khz p,.khz ul{margin:0 0 8px}.khz ul{padding-left:20px}.khz li{margin:2px 0}", | |
| ".khz a{color:var(--b)}", | |
| ".khz button{font:inherit;line-height:1.3;cursor:pointer;text-transform:none;letter-spacing:normal;box-shadow:none}", | |
| ".khz-hd{display:flex;align-items:center;gap:10px;padding:12px 14px;background:var(--pn);border-bottom:1px solid var(--ln)}", | |
| ".khz-mk{flex:0 0 32px;height:32px;border-radius:8px;background:var(--b);color:#fff;display:flex;align-items:center;justify-content:center;font:600 16px Georgia,serif}", | |
| ".khz-hd b{display:block;font-size:15px}.khz-hd small{color:var(--mu);font-size:12px}", | |
| ".khz-tabs{display:flex;gap:4px;padding:8px 10px 0;background:var(--pn);border-bottom:1px solid var(--ln);overflow-x:auto;scrollbar-width:none}", | |
| ".khz-tab{background:none;border:0;border-bottom:2px solid transparent;color:var(--mu);padding:7px 10px;white-space:nowrap;border-radius:0}", | |
| ".khz-tab[aria-selected=true]{color:var(--b);border-bottom-color:var(--b);font-weight:600}", | |
| ".khz-log{flex:1;overflow-y:auto;padding:14px}", | |
| ".khz-intro{color:var(--mu);margin:0 0 12px}", | |
| ".khz-ex{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:6px}", | |
| ".khz-ex button{background:var(--pn);border:1px solid var(--ln);color:var(--ink);border-radius:999px;padding:5px 11px;font-size:13px}", | |
| ".khz-ex button:hover{border-color:var(--b)}", | |
| ".khz-m{margin:12px 0}.khz-u{display:flex;justify-content:flex-end}", | |
| ".khz-u span{background:var(--b);color:#fff;border-radius:14px 14px 4px 14px;padding:8px 13px;max-width:85%;white-space:pre-wrap}", | |
| ".khz-ans{background:var(--pn);border:1px solid var(--ln);border-radius:4px 14px 14px 14px;padding:12px 14px}", | |
| ".khz-ans p:last-child,.khz-ans ul:last-child{margin-bottom:0}", | |
| ".khz-wait{color:var(--mu);font-style:italic}", | |
| ".khz-cite{text-decoration:none;font-weight:600;font-size:11px;vertical-align:super;margin-left:1px}", | |
| ".khz-sr{margin:7px 2px 0;font-size:12px;color:var(--mu);display:flex;flex-wrap:wrap;gap:5px;align-items:center}", | |
| ".khz-chip{background:var(--ch);border-radius:5px;padding:1px 7px;font:12px ui-monospace,Menlo,Consolas,monospace;color:var(--ink)}", | |
| ".khz-cards{display:grid;gap:8px;margin-top:9px}", | |
| ".khz-c{background:var(--pn);border:1px solid var(--ln);border-left:3px solid var(--b);border-radius:8px;padding:10px 13px}", | |
| ".khz-c.p{border-left-color:var(--a)}.khz-c.o{border-left-color:#5b6b7d}.khz-c.d{border-left-color:var(--oa)}", | |
| ".khz-c.fl{outline:2px solid var(--a)}", | |
| ".khz-c .t{font-weight:600;color:var(--ink);text-decoration:none}.khz-c .t:hover{text-decoration:underline}", | |
| ".khz-c .n{color:var(--mu);font-weight:500;margin-right:5px}", | |
| ".khz-c .k{font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--mu);margin-left:6px}", | |
| ".khz-c .mt{color:var(--mu);font-size:13px;margin-top:2px}.khz-c .mt b{color:var(--ink);font-weight:500}", | |
| ".khz-c .ln{margin-top:5px;display:flex;gap:10px;flex-wrap:wrap;font-size:12.5px}", | |
| ".khz-bd{font-size:11px;border-radius:4px;padding:0 6px;border:1px solid currentColor;color:var(--oa)}", | |
| ".khz-more{background:none;border:1px dashed var(--ln);color:var(--mu);border-radius:8px;padding:7px;width:100%}", | |
| ".khz-fb{margin-top:6px;font-size:12px;color:var(--mu);display:flex;gap:6px;align-items:center}", | |
| ".khz-fb button{background:none;border:1px solid var(--ln);border-radius:6px;padding:1px 8px;color:var(--mu)}", | |
| ".khz-ft{border-top:1px solid var(--ln);background:var(--pn);padding:10px}", | |
| ".khz-f{display:flex;gap:8px}", | |
| ".khz-f textarea{flex:1;resize:none;border:1px solid var(--ln);background:var(--bg);color:var(--ink);border-radius:10px;padding:9px 11px;font:inherit;min-height:42px;max-height:120px;margin:0}", | |
| ".khz-f textarea:focus{outline:2px solid var(--b);outline-offset:-1px}", | |
| ".khz-f button{background:var(--b);color:#fff;border:0;border-radius:10px;padding:0 16px;font-weight:600}", | |
| ".khz-f button:disabled{opacity:.5;cursor:default}", | |
| ".khz-nt{color:var(--mu);font-size:11px;margin:6px 2px 0}", | |
| ".khz-nt a{color:inherit}", | |
| "@media (max-width:520px){.khz-u span{max-width:94%}.khz-hd small{display:none}}" | |
| ].join(""); | |
| function esc(s) { return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) { return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]; }); } | |
| function safeUrl(u) { return /^https?:\/\//i.test(u || "") ? esc(u) : "#"; } | |
| function md(text, t) { | |
| var h = esc(text) | |
| .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>") | |
| .replace(/(https?:\/\/[^\s<)]+)/g, '<a href="$1" target="_blank" rel="noopener">$1</a>') | |
| .replace(/\[(\d+(?:\s*,\s*\d+)*)\]/g, function (m, g) { | |
| return g.split(",").map(function (n) { n = n.trim(); return '<a class="khz-cite" href="#" data-c="' + t + "-" + n + '">[' + n + "]</a>"; }).join(""); | |
| }); | |
| var out = [], list = false; | |
| h.split("\n").forEach(function (line) { | |
| var l = line.trim(); | |
| if (/^[-•*]\s+/.test(l)) { if (!list) { out.push("<ul>"); list = true; } out.push("<li>" + l.replace(/^[-•*]\s+/, "") + "</li>"); return; } | |
| if (list) { out.push("</ul>"); list = false; } | |
| if (l) out.push("<p>" + l + "</p>"); | |
| }); | |
| if (list) out.push("</ul>"); | |
| return out.join(""); | |
| } | |
| function card(r, t) { | |
| var id = ' data-id="' + t + "-" + r.n + '"', num = r.n ? '<span class="n">' + r.n + "</span>" : ""; | |
| var link = '<a class="t" href="' + safeUrl(r.portal_url) + '" target="_blank" rel="noopener">'; | |
| if (r.kind === "person") { | |
| var units = (r.organisations || []).map(function (o) { return o.name; }).filter(Boolean).join("; "); | |
| var ev = (r.evidence || []).map(function (e) { return '<a href="' + safeUrl(e.portal_url) + '" target="_blank" rel="noopener">' + esc(e.title) + "</a>" + (e.year ? " (" + e.year + ")" : ""); }).join("<br>"); | |
| return '<div class="khz-c p"' + id + "><div>" + num + link + esc(r.name) + '</a><span class="k">Profile</span></div>' + | |
| ((r.job_titles || []).length || units ? '<div class="mt">' + esc([(r.job_titles || []).join(", "), units].filter(Boolean).join(" · ")) + "</div>" : "") + | |
| ((r.keywords || []).length ? '<div class="mt">Expertise: ' + esc(r.keywords.slice(0, 6).join(", ")) + "</div>" : "") + | |
| (r.matching_outputs ? '<div class="mt"><b>' + r.matching_outputs + " matching output" + (r.matching_outputs > 1 ? "s" : "") + "</b>" + (ev ? "<br>" + ev : "") + "</div>" : "") + | |
| '<div class="ln"><a href="' + safeUrl(r.portal_url) + '" target="_blank" rel="noopener">View profile in Khazna</a>' + | |
| (r.orcid ? '<a href="https://orcid.org/' + esc(r.orcid) + '" target="_blank" rel="noopener">ORCID</a>' : "") + "</div></div>"; | |
| } | |
| if (r.kind === "organisation") { | |
| return '<div class="khz-c o"' + id + "><div>" + num + link + esc(r.name) + '</a><span class="k">' + esc(r.type || "Unit") + "</span></div>" + | |
| ((r.parents || []).length ? '<div class="mt">Part of ' + esc(r.parents.join(", ")) + "</div>" : "") + | |
| '<div class="ln"><a href="' + safeUrl(r.portal_url) + '" target="_blank" rel="noopener">View unit, people & outputs</a></div></div>'; | |
| } | |
| var ku = {}; (r.ku_authors || []).forEach(function (a) { ku[a.name] = 1; }); | |
| var au = (r.authors || []).slice(0, 8).map(function (a) { return ku[a] ? "<b>" + esc(a) + "</b>" : esc(a); }).join(", ") + ((r.authors || []).length > 8 ? " et al." : ""); | |
| var meta = [r.year, r.type, r.venue].filter(Boolean).map(esc).join(" · "); | |
| return '<div class="khz-c' + (r.kind === "dataset" ? " d" : "") + '"' + id + "><div>" + num + link + esc(r.title) + "</a>" + | |
| (r.kind === "dataset" ? '<span class="k">Dataset</span>' : r.kind === "thesis" ? '<span class="k">Thesis</span>' : "") + "</div>" + | |
| '<div class="mt">' + (au || "—") + "</div>" + (meta ? '<div class="mt">' + meta + "</div>" : "") + | |
| '<div class="ln"><a href="' + safeUrl(r.portal_url) + '" target="_blank" rel="noopener">View in Khazna</a>' + | |
| (r.doi_url ? '<a href="' + safeUrl(r.doi_url) + '" target="_blank" rel="noopener">DOI</a>' : "") + | |
| (r.open_access ? '<span class="khz-bd">Open access</span>' : "") + "</div></div>"; | |
| } | |
| function mount(root) { | |
| if (root.__khz) return; root.__khz = 1; | |
| var api = (root.getAttribute("data-api") || DEFAULT_API).replace(/\/$/, ""); | |
| var height = root.getAttribute("data-height") || "680"; | |
| var scopes = (root.getAttribute("data-scopes") || "all,publications,theses,persons,organisations,projects,equipment").split(",").map(function (s) { return s.trim(); }).filter(function (s) { return SCOPE_LABELS[s]; }); | |
| var scope = root.getAttribute("data-scope") || scopes[0] || "all"; | |
| var model = root.getAttribute("data-model") || "gpt"; | |
| var history = [], turn = 0, busy = false, uid = Math.random().toString(36).slice(2, 7); | |
| root.innerHTML = | |
| '<div class="khz" style="' + (height === "auto" ? "min-height:100%;height:100%" : "height:" + parseInt(height, 10) + "px") + '">' + | |
| '<div class="khz-hd"><div class="khz-mk">K</div><div><b>Khazna AI Assistant</b><small>Ask in plain language — searches KU\'s research repository</small></div></div>' + | |
| '<div class="khz-tabs" role="tablist">' + scopes.map(function (s) { return '<button class="khz-tab" role="tab" data-s="' + s + '" aria-selected="' + (s === scope) + '">' + SCOPE_LABELS[s] + "</button>"; }).join("") + "</div>" + | |
| '<div class="khz-log" aria-live="polite"><p class="khz-intro">I turn your question into searches on <a href="https://khazna.ku.ac.ae/" target="_blank" rel="noopener">Khazna</a> and summarise what KU has published, who works on it, and where, citing every claim.</p><div class="khz-ex"></div></div>' + | |
| '<div class="khz-ft"><form class="khz-f"><label for="khz-q-' + uid + '" style="position:absolute;left:-9999px">Your question</label><textarea id="khz-q-' + uid + '" rows="1" placeholder="e.g. What has KU published on 6G networks?"></textarea><button type="submit">Ask</button></form>' + | |
| '<p class="khz-nt">AI-generated from public Khazna records; answers may be incomplete. Need help? <a href="https://library.ku.ac.ae/" target="_blank" rel="noopener">KU Library Research Support</a></p></div></div>'; | |
| var log = root.querySelector(".khz-log"), ex = root.querySelector(".khz-ex"), ta = root.querySelector("textarea"), btn = root.querySelector(".khz-f button"); | |
| function drawExamples() { | |
| if (!ex.isConnected) return; | |
| ex.innerHTML = (EXAMPLES[scope] || EXAMPLES.all).map(function (e) { return "<button type=\"button\">" + esc(e) + "</button>"; }).join(""); | |
| ex.querySelectorAll("button").forEach(function (b) { b.onclick = function () { ask(b.textContent); }; }); | |
| } | |
| drawExamples(); | |
| root.querySelectorAll(".khz-tab").forEach(function (b) { | |
| b.onclick = function () { | |
| scope = b.getAttribute("data-s"); | |
| root.querySelectorAll(".khz-tab").forEach(function (x) { x.setAttribute("aria-selected", x === b); }); | |
| ta.placeholder = { persons: "e.g. Who at KU works on desalination membranes?", organisations: "e.g. Which centres work on robotics?", datasets: "e.g. Datasets on UAE groundwater" }[scope] || "e.g. What has KU published on 6G networks?"; | |
| drawExamples(); ta.focus(); | |
| }; | |
| }); | |
| function add(html, cls) { var d = document.createElement("div"); d.className = "khz-m " + cls; d.innerHTML = html; log.appendChild(d); log.scrollTop = log.scrollHeight; return d; } | |
| // Free HF Spaces sleep when idle; the first request after a sleep can take ~30-60 s or return 502/503. | |
| function post(body, onWake) { | |
| var tries = 0; | |
| function go() { | |
| tries++; | |
| return fetch(api + "/agent", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }) | |
| .then(function (r) { | |
| if ((r.status === 502 || r.status === 503 || r.status === 504) && tries < 6) { onWake(); return new Promise(function (ok) { setTimeout(ok, 8000); }).then(go); } | |
| return r.json(); | |
| }) | |
| .catch(function (e) { if (tries < 6) { onWake(); return new Promise(function (ok) { setTimeout(ok, 8000); }).then(go); } throw e; }); | |
| } | |
| return go(); | |
| } | |
| function ask(question) { | |
| if (busy) return; busy = true; btn.disabled = true; | |
| if (ex.parentNode) ex.parentNode.removeChild(ex); | |
| var t = uid + (++turn); | |
| add("<span>" + esc(question) + "</span>", "khz-u"); | |
| var p = add('<div class="khz-ans khz-wait">Searching Khazna…</div>', "khz-b"); | |
| post({ question: question, history: history, model: model, scope: scope }, function () { | |
| p.innerHTML = '<div class="khz-ans khz-wait">Waking up the assistant — this can take up to a minute after a quiet period…</div>'; | |
| }).then(function (d) { | |
| var h = '<div class="khz-ans">' + md(d.answer || "No answer.", t) + "</div>"; | |
| var seenQ = {}, qs = (d.trace || []).filter(function (x) { var k = (x.query || "").toLowerCase(); if (!k || seenQ[k]) return false; seenQ[k] = 1; return true; }).map(function (x) { return '<span class="khz-chip">' + esc(x.query) + "</span>"; }).join(""); | |
| if (qs) h += '<div class="khz-sr">Searched: ' + qs + (d.portal_search_url ? ' · <a href="' + safeUrl(d.portal_search_url) + '" target="_blank" rel="noopener">open in Khazna</a>' : "") + "</div>"; | |
| var cards = (d.person ? [card(d.person, t)] : []).concat((d.search_results || []).map(function (r) { return card(r, t); })); | |
| if (cards.length) h += '<div class="khz-cards">' + cards.join("") + "</div>"; | |
| if ((d.more_results || []).length) h += '<div class="khz-cards"><button type="button" class="khz-more">Show ' + d.more_results.length + ' more</button><div class="khz-cards" hidden>' + d.more_results.map(function (r) { return card(r, t); }).join("") + "</div></div>"; | |
| if (d.intent && !/social|rate_limited|empty/.test(d.intent)) h += '<div class="khz-fb">Helpful? <button type="button" data-r="up" aria-label="Yes">👍</button><button type="button" data-r="down" aria-label="No">👎</button></div>'; | |
| p.innerHTML = h; | |
| var more = p.querySelector(".khz-more"); | |
| if (more) more.onclick = function () { more.nextElementSibling.hidden = false; more.parentNode.removeChild(more); }; | |
| p.querySelectorAll(".khz-fb button").forEach(function (b) { | |
| b.onclick = function () { | |
| fetch(api + "/feedback", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ question: question, rating: b.getAttribute("data-r") }) }); | |
| b.parentNode.textContent = "Thanks for the feedback."; | |
| }; | |
| }); | |
| p.querySelectorAll(".khz-cite").forEach(function (a) { | |
| a.onclick = function (e) { | |
| e.preventDefault(); | |
| var el = root.querySelector('[data-id="' + a.getAttribute("data-c") + '"]'); if (!el) return; | |
| if (el.parentNode.hidden) el.parentNode.hidden = false; | |
| el.scrollIntoView({ behavior: "smooth", block: "center" }); el.classList.add("fl"); setTimeout(function () { el.classList.remove("fl"); }, 1400); | |
| }; | |
| }); | |
| history.push({ role: "user", content: question }, { role: "assistant", content: d.answer || "" }); | |
| history = history.slice(-8); | |
| }).catch(function () { | |
| p.innerHTML = '<div class="khz-ans">Sorry — the assistant is unavailable right now. You can search directly at <a href="https://khazna.ku.ac.ae/" target="_blank" rel="noopener">khazna.ku.ac.ae</a>.</div>'; | |
| }).then(function () { busy = false; btn.disabled = false; log.scrollTop = log.scrollHeight; }); | |
| } | |
| root.querySelector("form").onsubmit = function (e) { e.preventDefault(); var q = ta.value.trim(); if (!q) return; ta.value = ""; ask(q); }; | |
| ta.onkeydown = function (e) { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); root.querySelector("form").requestSubmit ? root.querySelector("form").requestSubmit() : root.querySelector("form").onsubmit(e); } }; | |
| fetch(api + "/health").catch(function () {}); // wake a sleeping Space as soon as the guide loads | |
| } | |
| function init() { | |
| if (!document.getElementById("khz-css")) { | |
| var st = document.createElement("style"); st.id = "khz-css"; st.textContent = CSS; document.head.appendChild(st); | |
| } | |
| document.querySelectorAll("[data-khazna-assistant]").forEach(mount); | |
| } | |
| if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init); else init(); | |
| })(); | |