2023-09-06 19:04:18 +00:00
< html >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0 maximum-scale=1.0" >
< title > Khoj - Search< / title >
< link rel = "icon" type = "image/png" sizes = "128x128" href = "./assets/icons/favicon-128x128.png" >
< link rel = "manifest" href = "./khoj.webmanifest" >
< link rel = "stylesheet" href = "./assets/khoj.css" >
< / head >
< script type = "text/javascript" src = "./assets/org.min.js" > < / script >
< script type = "text/javascript" src = "./assets/markdown-it.min.js" > < / script >
2023-11-04 01:13:37 +00:00
< script src = "./utils.js" > < / script >
2023-09-06 19:04:18 +00:00
< script >
function render_image(item) {
return `
< div class = "results-image" >
< a href = "${item.entry}" class = "image-link" >
< img id = ${item.score} src = "${item.entry}?${Math.random()}"
title="Effective Score: ${item.score}, Meta: ${item.additional.metadata_score}, Image: ${item.additional.image_score}"
class="image">
< / a >
< / div > `;
}
function render_org(query, data, classPrefix="") {
return data.map(function (item) {
var orgParser = new Org.Parser();
var orgDocument = orgParser.parse(item.entry);
var orgHTMLDocument = orgDocument.convert(Org.ConverterHTML, { htmlClassPrefix: classPrefix, suppressNewLines: true });
return `< div class = "results-org" > ` + orgHTMLDocument.toString() + `< / div > `;
}).join("\n");
}
function render_markdown(query, data) {
var md = window.markdownit();
return data.map(function (item) {
let rendered = "";
if (item.additional.file.startsWith("http")) {
lines = item.entry.split("\n");
rendered = md.render(`${lines[0]}\t[*](${item.additional.file})\n${lines.slice(1).join("\n")}`);
}
else {
rendered = md.render(`${item.entry}`);
}
return `< div class = "results-markdown" > ` + rendered + `< / div > `;
}).join("\n");
}
function render_pdf(query, data) {
return data.map(function (item) {
let compiled_lines = item.additional.compiled.split("\n");
let filename = compiled_lines.shift();
let text_match = compiled_lines.join("\n")
return `< div class = "results-pdf" > ` + `< h2 > ${filename}< / h2 > \n< p > ${text_match}< / p > ` + `< / div > `;
}).join("\n");
}
function render_html(query, data) {
return data.map(function (item) {
let document = new DOMParser().parseFromString(item.entry, "text/html");
// Scrub the HTML to remove any script tags and associated content
let script_tags = document.querySelectorAll("script");
for (let i = 0; i < script _tags . length ; i + + ) {
script_tags[i].remove();
}
// Scrub the HTML to remove any style tags and associated content
let style_tags = document.querySelectorAll("style");
for (let i = 0; i < style _tags . length ; i + + ) {
style_tags[i].remove();
}
// Scrub the HTML to remove any noscript tags and associated content
let noscript_tags = document.querySelectorAll("noscript");
for (let i = 0; i < noscript_tags.length ; i + + ) {
noscript_tags[i].remove();
}
// Scrub the HTML to remove any iframe tags and associated content
let iframe_tags = document.querySelectorAll("iframe");
for (let i = 0; i < iframe_tags.length ; i + + ) {
iframe_tags[i].remove();
}
// Scrub the HTML to remove any object tags and associated content
let object_tags = document.querySelectorAll("object");
for (let i = 0; i < object_tags.length ; i + + ) {
object_tags[i].remove();
}
// Scrub the HTML to remove any embed tags and associated content
let embed_tags = document.querySelectorAll("embed");
for (let i = 0; i < embed_tags.length ; i + + ) {
embed_tags[i].remove();
}
let scrubbedHTML = document.body.outerHTML;
return `< div class = "results-html" > ` + scrubbedHTML + `< / div > `;
}).join("\n");
}
2023-11-01 00:59:53 +00:00
function render_xml(query, data) {
return data.map(function (item) {
return `< div class = "results-xml" > ` +
`< b > < a href = "${item.additional.file}" > ${item.additional.heading}< / a > < / b > ` +
`< xml > ${item.entry}< / xml > ` +
`< / div > `
}).join("\n");
}
2023-09-06 19:04:18 +00:00
function render_multiple(query, data, type) {
let html = "";
data.forEach(item => {
if (item.additional.file.endsWith(".org")) {
html += render_org(query, [item], "org-");
} else if (
item.additional.file.endsWith(".md") ||
item.additional.file.endsWith(".markdown") ||
(item.additional.file.includes("issues") & & item.additional.file.includes("github.com")) ||
(item.additional.file.includes("commit") & & item.additional.file.includes("github.com"))
)
{
html += render_markdown(query, [item]);
} else if (item.additional.file.endsWith(".pdf")) {
html += render_pdf(query, [item]);
} else if (item.additional.file.includes("notion.so")) {
html += `< div class = "results-notion" > ` + `< b > < a href = "${item.additional.file}" > ${item.additional.heading}< / a > < / b > ` + `< p > ${item.entry}< / p > ` + `< / div > `;
} else if (item.additional.file.endsWith(".html")) {
html += render_html(query, [item]);
2023-11-01 00:59:53 +00:00
} else if (item.additional.file.endsWith(".xml")) {
html += render_xml(query, [item])
2023-09-06 19:04:18 +00:00
} else {
html += `< div class = "results-plugin" > ` + `< b > < a href = "${item.additional.file}" > ${item.additional.heading}< / a > < / b > ` + `< p > ${item.entry}< / p > ` + `< / div > `;
}
});
return html;
}
function render_results(data, query, type) {
let results = "";
if (type === "markdown") {
results = render_markdown(query, data);
} else if (type === "org") {
results = render_org(query, data, "org-");
} else if (type === "image") {
results = data.map(render_image).join('');
} else if (type === "pdf") {
results = render_pdf(query, data);
} else if (type === "github" || type === "all" || type === "notion") {
results = render_multiple(query, data, type);
} else {
results = data.map((item) => `< div class = "results-plugin" > ` + `< p > ${item.entry}< / p > ` + `< / div > `).join("\n")
}
// Any POST rendering goes here.
let renderedResults = document.createElement("div");
renderedResults.id = `results-${type}`;
renderedResults.innerHTML = results;
// For all elements that are of type img in the results html and have a src with 'avatar' in the URL, add the class 'avatar'
// This is used to make the avatar images round
let images = renderedResults.querySelectorAll("img[src*='avatar']");
for (let i = 0; i < images.length ; i + + ) {
images[i].classList.add("avatar");
}
return renderedResults.outerHTML;
}
async function search(rerank=false) {
// Extract required fields for search from form
query = document.getElementById("query").value.trim();
type = 'all';
results_count = localStorage.getItem("khojResultsCount") || 5;
console.log(`Query: ${query}, Type: ${type}, Results Count: ${results_count}`);
// Short circuit on empty query
if (query.length === 0) {
return;
}
// If set query field in url query param on rerank
if (rerank)
setQueryFieldInUrl(query);
// Execute Search and Render Results
url = await createRequestUrl(query, type, results_count || 5, rerank);
2023-10-26 19:33:03 +00:00
const khojToken = await window.tokenAPI.getToken();
const headers = { 'Authorization': `Bearer ${khojToken}` };
fetch(url, { headers })
2023-09-06 19:04:18 +00:00
.then(response => response.json())
.then(data => {
console.log(data);
document.getElementById("results").innerHTML = render_results(data, query, type);
});
}
function incrementalSearch(event) {
type = 'all';
// Search with reranking on 'Enter'
if (event.key === 'Enter') {
search(rerank=true);
}
// Limit incremental search to text types
else if (type !== "image") {
search(rerank=false);
}
}
async function populate_type_dropdown() {
const hostURL = await window.hostURLAPI.getURL();
2023-10-26 19:33:03 +00:00
const khojToken = await window.tokenAPI.getToken();
const headers = { 'Authorization': `Bearer ${khojToken}` };
2023-09-06 19:04:18 +00:00
// Populate type dropdown field with enabled content types only
2023-10-26 19:33:03 +00:00
fetch(`${hostURL}/api/config/types`, { headers })
2023-09-06 19:04:18 +00:00
.then(response => response.json())
.then(enabled_types => {
// Show warning if no content types are enabled
if (enabled_types.detail) {
document.getElementById("results").innerHTML = "< div id = 'results-error' > To use Khoj search, setup your content plugins on the Khoj < a class = 'inline-chat-link' href = '/config' > settings page< / a > .< / div > ";
document.getElementById("query").setAttribute("disabled", "disabled");
document.getElementById("query").setAttribute("placeholder", "Configure Khoj to enable search");
return [];
}
return enabled_types;
});
}
async function createRequestUrl(query, type, results_count, rerank) {
// Generate Backend API URL to execute Search
const hostURL = await window.hostURLAPI.getURL();
let url = `${hostURL}/api/search?q=${encodeURIComponent(query)}&n=${results_count}&client=web`;
// If type is not 'all', append type to URL
if (type !== 'all')
url += `&t=${type}`;
// Rerank is only supported by text types
if (type !== "image")
url += `&r=${rerank}`;
return url;
}
function setQueryFieldInUrl(query) {
var url = new URL(window.location.href);
url.searchParams.set("q", query);
window.history.pushState({}, "", url.href);
}
window.addEventListener("load", async function() {
// Dynamically populate type dropdown based on enabled content types and type passed as URL query parameter
await populate_type_dropdown();
// Fill query field with value passed in URL query parameters, if any.
var query_via_url = new URLSearchParams(window.location.search).get("q");
if (query_via_url)
document.getElementById("query").value = query_via_url;
});
< / script >
< body >
<!-- Add Header Logo and Nav Pane -->
< div class = "khoj-header" >
< a class = "khoj-logo" href = "./index.html" >
< img class = "khoj-logo" src = "./assets/icons/khoj-logo-sideways-500.png" alt = "Khoj" > < / img >
< / a >
< nav class = "khoj-nav" >
2023-10-26 19:33:03 +00:00
< a class = "khoj-nav" href = "./chat.html" > 💬 Chat< / a >
2023-11-03 03:40:35 +00:00
< a class = "khoj-nav khoj-nav-selected" href = "./search.html" > 🔎 Search< / a >
2023-10-26 19:33:03 +00:00
< a class = "khoj-nav" href = "./config.html" > ⚙️ Settings< / a >
2023-09-06 19:04:18 +00:00
< / nav >
< / div >
<!-- Add Text Box To Enter Query, Trigger Incremental Search OnChange -->
< input type = "text" id = "query" class = "option" onkeyup = incrementalSearch(event) autofocus = "autofocus" placeholder = "Search your knowledge base using natural language" >
<!-- Section to Render Results -->
< div id = "results" > < / div >
< / body >
< style >
@media only screen and (max-width: 600px) {
body {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr auto auto auto minmax(80px, 100%);
font-size: small!important;
}
body > * {
grid-column: 1;
}
}
@media only screen and (min-width: 600px) {
body {
display: grid;
grid-template-columns: 1fr min(70vw, 100%) 1fr;
grid-template-rows: 1fr auto auto auto minmax(80px, 100%);
padding-top: 60vw;
}
body > * {
grid-column: 2;
}
}
body {
padding: 0px;
margin: 0px;
2023-11-03 07:14:07 +00:00
background: var(--background-color);
2023-09-06 19:04:18 +00:00
color: #475569;
font-family: roboto, karma, segoe ui, sans-serif;
font-size: small;
font-weight: 300;
line-height: 1.5em;
}
body > * {
padding: 10px;
margin: 10px;
}
#options {
padding: 0;
display: grid;
grid-template-columns: 1fr;
}
#options > * {
padding: 15px;
border-radius: 5px;
border: 1px solid #475569;
background: #f9fafc
}
.option:hover {
box-shadow: 0 0 11px #aaa;
}
#options > button {
margin-right: 10px;
}
#query {
font-size: small;
}
#results {
font-size: small;
margin: 0px;
line-height: 20px;
}
.results-image {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.image-link {
place-self: center;
}
.image {
width: 20vw;
border-radius: 10px;
border: 1px solid #475569;
}
#json {
white-space: pre-wrap;
}
.results-pdf,
.results-notion,
.results-html,
.results-plugin {
text-align: left;
white-space: pre-line;
}
.results-markdown,
.results-github {
text-align: left;
}
.results-org {
text-align: left;
/* white-space: pre-line; */
}
.results-org h3 {
margin: 20px 0 0 0;
font-size: small;
}
span.org-task-status {
color: white;
padding: 3.5px 3.5px 0;
margin-right: 5px;
border-radius: 5px;
background-color: #eab308;
font-size: small;
}
span.org-task-status.todo {
background-color: #3b82f6
}
span.org-task-status.done {
background-color: #22c55e;
}
span.org-task-tag {
color: white;
padding: 3.5px 3.5px 0;
margin-right: 5px;
border-radius: 5px;
border: 1px solid #475569;
background-color: #ef4444;
font-size: small;
}
pre {
max-width: 100;
}
a {
color: #3b82f6;
text-decoration: none;
}
img.avatar {
width: 20px;
height: 20px;
border-radius: 50%;
}
div#results-error,
div.results-markdown,
div.results-notion,
div.results-org,
div.results-plugin,
div.results-html,
div.results-pdf {
text-align: left;
box-shadow: 2px 2px 2px var(--primary-hover);
border-radius: 5px;
padding: 10px;
margin: 10px 0;
border: 4px solid rgb(229, 229, 229);
}
div#results-error {
box-shadow: 2px 2px 2px #FF5722;
}
img {
max-width: 90%;
}
div.khoj-banner-container {
background: linear-gradient(-45deg, #FFC107, #FF9800, #FF5722, #FF9800, #FFC107);
background-size: 400% 400%;
animation: gradient 15s ease infinite;
text-align: center;
padding: 10px;
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
a.khoj-logo {
text-align: center;
}
button#khoj-banner-submit,
input#khoj-banner-email {
padding: 10px;
border-radius: 5px;
border: 1px solid #475569;
background: #f9fafc;
}
button#khoj-banner-submit:hover,
input#khoj-banner-email:hover {
box-shadow: 0 0 11px #aaa;
}
@media only screen and (max-width: 600px) {
a.khoj-banner {
display: block;
}
p.khoj-banner {
padding: 0;
}
}
< / style >
< script >
var khojBannerSubmit = document.getElementById("khoj-banner-submit");
khojBannerSubmit?.addEventListener("click", function(event) {
event.preventDefault();
var email = document.getElementById("khoj-banner-email").value;
fetch("https://app.khoj.dev/beta/users/", {
method: "POST",
body: JSON.stringify({
email: email
}),
headers: {
"Content-Type": "application/json"
}
}).then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
if (data.user != null) {
document.getElementById("khoj-banner").innerHTML = "Thanks for signing up. We'll be in touch soon! 🚀";
document.getElementById("khoj-banner-submit").remove();
} else {
document.getElementById("khoj-banner").innerHTML = "There was an error signing up. Please contact team@khoj.dev";
}
}).catch(function(error) {
console.log(error);
document.getElementById("khoj-banner").innerHTML = "There was an error signing up. Please contact team@khoj.dev";
});
});
< / script >
< / html >