mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-02 21:19:12 +00:00
280 lines
9.3 KiB
HTML
280 lines
9.3 KiB
HTML
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
|
|
<title>Khoj</title>
|
|
|
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 144 144%22><text y=%22.86em%22 font-size=%22144%22>🦅</text></svg>">
|
|
<link rel="icon" type="image/png" sizes="144x144" href="/static/assets/icons/favicon-144x144.png">
|
|
<link rel="manifest" href="/static/khoj.webmanifest">
|
|
</head>
|
|
<script type="text/javascript" src="static/assets/org.min.js"></script>
|
|
<script type="text/javascript" src="static/assets/markdown-it.min.js"></script>
|
|
|
|
<script>
|
|
function render_image(item) {
|
|
return `
|
|
<a href="${item.entry}" class="image-link">
|
|
<img id=${item.score} src="${item.entry}?${Math.random()}"
|
|
title="Effective Score: ${item.score}, Meta: ${item.metadata_score}, Image: ${item.image_score}"
|
|
class="image">
|
|
</a>`
|
|
}
|
|
|
|
function render_org(query, data, classPrefix="") {
|
|
var orgCode = data.map(function (item) {
|
|
return `${item.entry}`
|
|
}).join("\n")
|
|
var orgParser = new Org.Parser();
|
|
var orgDocument = orgParser.parse(orgCode);
|
|
var orgHTMLDocument = orgDocument.convert(Org.ConverterHTML, { htmlClassPrefix: classPrefix });
|
|
return orgHTMLDocument.toString();
|
|
}
|
|
|
|
function render_markdown(query, data) {
|
|
var md = window.markdownit();
|
|
return md.render(data.map(function (item) {
|
|
return `${item.entry}`
|
|
}).join("\n"));
|
|
}
|
|
|
|
function render_ledger(query, data) {
|
|
return `<div id="results-ledger">` + data.map(function (item) {
|
|
return `<p>${item.entry}</p>`
|
|
}).join("\n") + `</div>`;
|
|
}
|
|
|
|
function render_json(data, query, type) {
|
|
if (type === "markdown") {
|
|
return render_markdown(query, data);
|
|
} else if (type === "org") {
|
|
return render_org(query, data);
|
|
} else if (type === "music") {
|
|
return render_org(query, data, "music-");
|
|
} else if (type === "image") {
|
|
return data.map(render_image).join('');
|
|
} else if (type === "ledger") {
|
|
return render_ledger(query, data);
|
|
} else {
|
|
return `<pre id="json">${JSON.stringify(data, null, 2)}</pre>`;
|
|
}
|
|
}
|
|
|
|
function search(rerank=false) {
|
|
query = document.getElementById("query").value;
|
|
type = document.getElementById("type").value;
|
|
console.log(query, type);
|
|
url = type === "image"
|
|
? `/search?q=${query}&t=${type}&n=6`
|
|
: `/search?q=${query}&t=${type}&n=6&r=${rerank}`;
|
|
fetch(url)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
console.log(data);
|
|
document.getElementById("results").innerHTML =
|
|
`<div id=results-${type}>`
|
|
+ render_json(data, query, type)
|
|
+ `</div>`;
|
|
});
|
|
}
|
|
|
|
function regenerate() {
|
|
type = document.getElementById("type").value;
|
|
fetch(`/regenerate?t=${type}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
console.log(data);
|
|
document.getElementById("results").innerHTML =
|
|
render_json(data);
|
|
});
|
|
}
|
|
|
|
function incremental_search(event) {
|
|
type = document.getElementById("type").value;
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
function populate_type_dropdown() {
|
|
// Populate type dropdown field with enabled search types only
|
|
var possible_search_types = ["org", "markdown", "ledger", "music", "image"];
|
|
fetch("/config/data")
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
document.getElementById("type").innerHTML =
|
|
possible_search_types
|
|
.filter(type => data["content-type"].hasOwnProperty(type) && data["content-type"][type])
|
|
.map(type => `<option value="${type}">${type.slice(0,1).toUpperCase() + type.slice(1)}</option>`)
|
|
.join('');
|
|
})
|
|
.then(() => {
|
|
// Set type field to search type passed in URL query parameter, if valid
|
|
var type_via_url = new URLSearchParams(window.location.search).get("t");
|
|
if (type_via_url && possible_search_types.includes(type_via_url))
|
|
document.getElementById("type").value = type_via_url;
|
|
});
|
|
}
|
|
|
|
window.onload = function () {
|
|
// Dynamically populate type dropdown based on enabled search types and type passed as URL query parameter
|
|
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>
|
|
<h1>Khoj</h1>
|
|
|
|
<!--Add Text Box To Enter Query, Trigger Incremental Search OnChange -->
|
|
<input type="text" id="query" onkeyup=incremental_search(event) autofocus="autofocus" placeholder="What is the meaning of life?">
|
|
|
|
<div id="options">
|
|
<!--Add Dropdown to Select Query Type -->
|
|
<select id="type"></select>
|
|
|
|
<!--Add Button To Regenerate -->
|
|
<button id="regenerate" onclick="regenerate()">Regenerate</button>
|
|
</div>
|
|
|
|
<!-- Section to Render Results -->
|
|
<div id="results"></div>
|
|
</body>
|
|
|
|
<style>
|
|
@media only screen and (max-width: 600px) {
|
|
body {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
}
|
|
body > * {
|
|
grid-column: 1;
|
|
}
|
|
}
|
|
@media only screen and (min-width: 600px) {
|
|
body {
|
|
display: grid;
|
|
grid-template-columns: 1fr min(70vw, 100%) 1fr;
|
|
padding-top: 60vw;
|
|
}
|
|
body > * {
|
|
grid-column: 2;
|
|
}
|
|
}
|
|
|
|
body {
|
|
padding: 0px;
|
|
margin: 0px;
|
|
background: #eee;
|
|
color: #888;
|
|
text-align: center;
|
|
font-family: roboto, karma, segoe ui, sans-serif;
|
|
font-size: 20px;
|
|
font-weight: 300;
|
|
line-height: 1.5em;
|
|
}
|
|
body > * {
|
|
padding: 10px;
|
|
margin: 10px;
|
|
}
|
|
h1 {
|
|
font-weight: 200;
|
|
color: #888;
|
|
}
|
|
|
|
#options {
|
|
padding: 0;
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
}
|
|
#options > * {
|
|
padding: 15px;
|
|
border-radius: 5px;
|
|
border: 1px solid #ccc;
|
|
}
|
|
#options > select {
|
|
margin-right: 5px;
|
|
}
|
|
#options > button {
|
|
margin-left: 5px;
|
|
}
|
|
|
|
#query {
|
|
font-size: larger;
|
|
}
|
|
#results {
|
|
font-size: medium;
|
|
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 #ccc;
|
|
}
|
|
#json {
|
|
white-space: pre-wrap;
|
|
}
|
|
#results-ledger {
|
|
white-space: pre-line;
|
|
text-align: left;
|
|
}
|
|
#results-markdown {
|
|
text-align: left;
|
|
}
|
|
#results-music,
|
|
#results-org {
|
|
text-align: left;
|
|
white-space: pre-line;
|
|
}
|
|
#results-music h3,
|
|
#results-org h3 {
|
|
margin: 20px 0 0 0;
|
|
font-size: larger;
|
|
}
|
|
span.music-task-status,
|
|
span.task-status {
|
|
color: white;
|
|
padding: 3.5px 3.5px 0;
|
|
margin-right: 5px;
|
|
border-radius: 5px;
|
|
background-color: #ed6f00;
|
|
font-size: medium;
|
|
}
|
|
span.music-task-status.todo,
|
|
span.task-status.todo {
|
|
background-color: #048ba8
|
|
}
|
|
span.music-task-status.done,
|
|
span.task-status.done {
|
|
background-color: #06a77d;
|
|
}
|
|
span.music-task-tag,
|
|
span.task-tag {
|
|
color: white;
|
|
padding: 3.5px 3.5px 0;
|
|
margin-right: 5px;
|
|
border-radius: 5px;
|
|
background-color: #bbb;
|
|
font-size: small;
|
|
}
|
|
</style>
|
|
|
|
</html>
|