mirror of
https://github.com/khoj-ai/khoj.git
synced 2024-11-27 17:35:07 +01:00
Merge pull request #228 from debanjum/features/pretty-config-page
Update the config page to be more usable
This commit is contained in:
commit
6224dce49d
27 changed files with 669 additions and 183 deletions
|
@ -50,7 +50,7 @@ pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
|||
if system() != 'Darwin':
|
||||
# Add Splash screen to show on app launch
|
||||
splash = Splash(
|
||||
'src/khoj/interface/web/assets/icons/favicon-144x144.png',
|
||||
'src/khoj/interface/web/assets/icons/favicon-128x128.png',
|
||||
binaries=a.binaries,
|
||||
datas=a.datas,
|
||||
text_pos=(10, 160),
|
||||
|
@ -82,7 +82,7 @@ if system() != 'Darwin':
|
|||
target_arch='x86_64',
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon='src/khoj/interface/web/assets/icons/favicon-144x144.ico',
|
||||
icon='src/khoj/interface/web/assets/icons/favicon-128x128.ico',
|
||||
)
|
||||
else:
|
||||
exe = EXE(
|
||||
|
|
|
@ -49,7 +49,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
self.setFixedWidth(600)
|
||||
|
||||
# Set Window Icon
|
||||
icon_path = constants.web_directory / "assets/icons/favicon-144x144.png"
|
||||
icon_path = constants.web_directory / "assets/icons/favicon-128x128.png"
|
||||
self.setWindowIcon(QtGui.QIcon(f"{icon_path.absolute()}"))
|
||||
|
||||
# Initialize Configure Window Layout
|
||||
|
@ -228,6 +228,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
# Search Type (re)-Enabled
|
||||
if child.isChecked():
|
||||
current_search_config = self.current_config["content-type"].get(child.search_type, {})
|
||||
if current_search_config == None:
|
||||
current_search_config = {}
|
||||
default_search_config = self.get_default_config(search_type=child.search_type)
|
||||
self.new_config["content-type"][child.search_type.value] = merge_dicts(
|
||||
current_search_config, default_search_config
|
||||
|
|
|
@ -17,7 +17,7 @@ def create_system_tray(gui: QtWidgets.QApplication, main_window: MainWindow):
|
|||
"""
|
||||
|
||||
# Create the system tray with icon
|
||||
icon_path = constants.web_directory / "assets/icons/favicon-144x144.png"
|
||||
icon_path = constants.web_directory / "assets/icons/favicon-128x128.png"
|
||||
icon = QtGui.QIcon(f"{icon_path.absolute()}")
|
||||
tray = QtWidgets.QSystemTrayIcon(icon)
|
||||
tray.setVisible(True)
|
||||
|
|
22
src/khoj/interface/web/404.html
Normal file
22
src/khoj/interface/web/404.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Khoj: An AI Personal Assistant for your digital brain</title>
|
||||
<link rel=”stylesheet” href=”static/styles.css”>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
|
||||
</head>
|
||||
<body class="not-found">
|
||||
<header class=”header”>
|
||||
<h1>Oops, this is awkward. That page couldn't be found.</h1>
|
||||
</header>
|
||||
<a href="/config">Go Home</a>
|
||||
|
||||
<footer class=”footer”>
|
||||
</footer>
|
||||
</body>
|
||||
<style>
|
||||
body.not-found {
|
||||
padding: 0 10%
|
||||
}
|
||||
</style>
|
||||
</html>
|
|
@ -1,29 +0,0 @@
|
|||
:root {
|
||||
--primary-color: #ffffff;
|
||||
--bold-color: #2073ee;
|
||||
--complementary-color: #124408;
|
||||
--accent-color-0: #57f0b5;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
div.config-element {
|
||||
color: var(--bold-color);
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
div.config-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
span.config-element-value {
|
||||
color: var(--complementary-color);
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
// Retrieve elements from the DOM.
|
||||
var showConfig = document.getElementById("show-config");
|
||||
var configForm = document.getElementById("config-form");
|
||||
var regenerateButton = document.getElementById("config-regenerate");
|
||||
|
||||
// Global variables.
|
||||
var rawConfig = {};
|
||||
var emptyValueDefault = "🖊️";
|
||||
|
||||
/**
|
||||
* Fetch the existing config file.
|
||||
*/
|
||||
fetch("/api/config/data")
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
rawConfig = data;
|
||||
configForm.style.display = "block";
|
||||
processChildren(configForm, data);
|
||||
|
||||
var submitButton = document.createElement("button");
|
||||
submitButton.type = "submit";
|
||||
submitButton.innerHTML = "update";
|
||||
configForm.appendChild(submitButton);
|
||||
|
||||
// The config form's submit handler.
|
||||
configForm.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
console.log(rawConfig);
|
||||
fetch("/api/config/data", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(rawConfig)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => console.log(data));
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The click handler for the Regenerate button.
|
||||
*/
|
||||
regenerateButton.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
regenerateButton.style.cursor = "progress";
|
||||
regenerateButton.disabled = true;
|
||||
fetch("/api/update?force=true&client=web")
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
regenerateButton.style.cursor = "pointer";
|
||||
regenerateButton.disabled = false;
|
||||
console.log(data);
|
||||
});
|
||||
})
|
||||
|
||||
/**
|
||||
* Adds config elements to the DOM representing the sub-components
|
||||
* of one of the fields in the raw config file.
|
||||
* @param {the parent element} element
|
||||
* @param {the data to be rendered for this element and its children} data
|
||||
*/
|
||||
function processChildren(element, data) {
|
||||
for (let key in data) {
|
||||
var child = document.createElement("div");
|
||||
child.id = key;
|
||||
child.className = "config-element";
|
||||
child.appendChild(document.createTextNode(key + ": "));
|
||||
if (data[key] === Object(data[key]) && !Array.isArray(data[key])) {
|
||||
child.className+=" config-title";
|
||||
processChildren(child, data[key]);
|
||||
} else {
|
||||
child.appendChild(createValueNode(data, key));
|
||||
}
|
||||
element.appendChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an element, and replaces it with an editable
|
||||
* element with the same data in place.
|
||||
* @param {the original element to be replaced} original
|
||||
* @param {the source data to be rendered for the new element} data
|
||||
* @param {the key for this input in the source data} key
|
||||
*/
|
||||
function makeElementEditable(original, data, key) {
|
||||
original.addEventListener("click", () => {
|
||||
var inputNewText = document.createElement("input");
|
||||
inputNewText.type = "text";
|
||||
inputNewText.className = "config-element-edit";
|
||||
inputNewText.value = (original.textContent == emptyValueDefault) ? "" : original.textContent;
|
||||
fixInputOnFocusOut(inputNewText, data, key);
|
||||
original.parentNode.replaceChild(inputNewText, original);
|
||||
inputNewText.focus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a node corresponding to the value of a config element.
|
||||
* @param {the source data} data
|
||||
* @param {the key corresponding to this node's data} key
|
||||
* @returns A new element which corresponds to the value in some field.
|
||||
*/
|
||||
function createValueNode(data, key) {
|
||||
var valueElement = document.createElement("span");
|
||||
valueElement.className = "config-element-value";
|
||||
valueElement.textContent = !data[key] ? emptyValueDefault : data[key];
|
||||
makeElementEditable(valueElement, data, key);
|
||||
return valueElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an existing input element with an element with the same data, which is not an input.
|
||||
* If the input data for this element was changed, update the corresponding data in the raw config.
|
||||
* @param {the original element to be replaced} original
|
||||
* @param {the source data} data
|
||||
* @param {the key corresponding to this node's data} key
|
||||
*/
|
||||
function fixInputOnFocusOut(original, data, key) {
|
||||
original.addEventListener("blur", () => {
|
||||
data[key] = (original.value != emptyValueDefault) ? original.value : "";
|
||||
original.parentNode.replaceChild(createValueNode(data, key), original);
|
||||
})
|
||||
}
|
BIN
src/khoj/interface/web/assets/icons/favicon-128x128.ico
Normal file
BIN
src/khoj/interface/web/assets/icons/favicon-128x128.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 200 KiB |
Binary file not shown.
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 159 KiB |
Binary file not shown.
Before Width: | Height: | Size: 29 KiB |
Binary file not shown.
44
src/khoj/interface/web/base_config.html
Normal file
44
src/khoj/interface/web/base_config.html
Normal file
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="icon" type="image/png" sizes="128x128" href="/static/assets/icons/favicon-128x128.png">
|
||||
<title>Khoj - Settings</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
|
||||
</head>
|
||||
<body class="khoj-configure">
|
||||
<header>
|
||||
<div>
|
||||
<h1>Khoj Settings</h1>
|
||||
<p>Check out our <a href="https://github.com/debanjum/khoj">source code on Github</a></p>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Ready?</h2>
|
||||
<div id="actions">
|
||||
<button onclick="window.location.href='/';" >
|
||||
Search
|
||||
</button>
|
||||
<button onclick="window.location.href='/chat';">
|
||||
Chat
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class=”content”>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
<style>
|
||||
header {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 12px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
header {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</html>
|
27
src/khoj/interface/web/base_data_integration.html
Normal file
27
src/khoj/interface/web/base_data_integration.html
Normal file
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Khoj: Data Settings</title>
|
||||
<link rel=”stylesheet” href=”static/styles.css”>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
|
||||
</head>
|
||||
<body class="data-integration">
|
||||
<header class=”header”>
|
||||
<h1>Configure your data integrations for Khoj</h1>
|
||||
</header>
|
||||
<a href="/config">Go back</a>
|
||||
|
||||
<div class=”content”>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
<footer class=”footer”>
|
||||
</footer>
|
||||
</body>
|
||||
<style>
|
||||
body.data-integration {
|
||||
padding: 0 10%
|
||||
}
|
||||
</style>
|
||||
</html>
|
27
src/khoj/interface/web/base_processor_integration.html
Normal file
27
src/khoj/interface/web/base_processor_integration.html
Normal file
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Khoj: Processor Settings</title>
|
||||
<link rel=”stylesheet” href=”static/styles.css”>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
|
||||
</head>
|
||||
<body class="data-integration">
|
||||
<header class=”header”>
|
||||
<h1>Configure your processor integrations for Khoj</h1>
|
||||
</header>
|
||||
<a href="/config">Go back</a>
|
||||
|
||||
<div class=”content”>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
<footer class=”footer”>
|
||||
</footer>
|
||||
</body>
|
||||
<style>
|
||||
body.data-integration {
|
||||
padding: 0 10%
|
||||
}
|
||||
</style>
|
||||
</html>
|
|
@ -4,8 +4,7 @@
|
|||
<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="icon" type="image/png" sizes="128x128" href="/static/assets/icons/favicon-128x128.png">
|
||||
<link rel="manifest" href="/static/khoj_chat.webmanifest">
|
||||
</head>
|
||||
<script>
|
||||
|
|
|
@ -1,14 +1,71 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🦅</text></svg>">
|
||||
<link rel="stylesheet" href="static/assets/config.css">
|
||||
<title>Khoj - Configure App</title>
|
||||
</head>
|
||||
<body>
|
||||
<form id="config-form">
|
||||
</form>
|
||||
<button id="config-regenerate">regenerate</button>
|
||||
</body>
|
||||
<script src="static/assets/config.js"></script>
|
||||
</html>
|
||||
{% extends "base_config.html" %}
|
||||
{% block content %}
|
||||
<h2>Content Types</h2>
|
||||
<div id="content-configuration">
|
||||
<button onclick="window.location.href='/config/content_type/pdf';">
|
||||
PDF
|
||||
</button>
|
||||
<button onclick="window.location.href='/config/content_type/markdown';">
|
||||
Markdown
|
||||
</button>
|
||||
<button onclick="window.location.href='/config/content_type/org';">
|
||||
Org
|
||||
</button>
|
||||
<button onclick="window.location.href='/config/content_type/ledger';">
|
||||
Ledger
|
||||
</button>
|
||||
<button onclick="window.location.href='/config/content_type/github';">
|
||||
GitHub
|
||||
</button>
|
||||
</div>
|
||||
<h2>Processors</h2>
|
||||
<button onclick="window.location.href='/config/processor/conversation/';">
|
||||
Conversation
|
||||
</button>
|
||||
<h1>Finalize</h1>
|
||||
<button id="regenerate" type="submit">Regenerate</button>
|
||||
<style>
|
||||
body.khoj-configure {
|
||||
padding: 0 10%
|
||||
}
|
||||
div#content-configuration, div#actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 12px;
|
||||
}
|
||||
button#regenerate {
|
||||
background-color: #4CAF50;
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 15px 32px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
var regenerate = document.getElementById("regenerate");
|
||||
regenerate.addEventListener("click", function(event) {
|
||||
event.preventDefault();
|
||||
regenerate.disabled = true;
|
||||
regenerate.innerHTML = "Regenerating...";
|
||||
fetch('/api/update?force=true&client=web', {
|
||||
method: 'GET',
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Success:', data);
|
||||
alert("Regenerated!");
|
||||
regenerate.disabled = false;
|
||||
regenerate.innerHTML = "Regenerate";
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error:', error);
|
||||
alert("Regeneration was not successful. Check debug logs.");
|
||||
regenerate.disabled = false;
|
||||
regenerate.innerHTML = "Regenerate";
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
99
src/khoj/interface/web/content_type_github_input.html
Normal file
99
src/khoj/interface/web/content_type_github_input.html
Normal file
|
@ -0,0 +1,99 @@
|
|||
{% extends "base_data_integration.html" %}
|
||||
{% block content %}
|
||||
<h2>Github</h2>
|
||||
<form id="config-form">
|
||||
<div id="success" style="display: none;"></div>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="pat-token">Personal Access Token</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="pat-token" name="pat" value="{{ current_config['pat_token'] }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="repo-owner">Repository Owner</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="repo-owner" name="repo_owner" value="{{ current_config['repo_owner'] }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="repo-name">Repository Name</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="repo-name" name="repo_name" value="{{ current_config['repo_name'] }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="repo-branch">Repository Branch</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="repo-branch" name="repo_branch" value="{{ current_config['repo_branch'] }}">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h4>You probably don't need to edit these.</h4>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="compressed-jsonl">Compressed JSONL (Output)</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="compressed-jsonl" name="compressed-jsonl" value="{{ current_config['compressed_jsonl'] }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="embeddings-file">Embeddings File (Output)</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="embeddings-file" name="embeddings-file" value="{{ current_config['embeddings_file'] }}">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button id="submit" type="submit">Submit</button>
|
||||
</form>
|
||||
<script>
|
||||
submit.addEventListener("click", function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
var compressed_jsonl = document.getElementById("compressed-jsonl").value;
|
||||
var embeddings_file = document.getElementById("embeddings-file").value;
|
||||
var pat_token = document.getElementById("pat-token").value;
|
||||
var repo_owner = document.getElementById("repo-owner").value;
|
||||
var repo_name = document.getElementById("repo-name").value;
|
||||
var repo_branch = document.getElementById("repo-branch").value;
|
||||
|
||||
fetch('/api/config/data/content_type/github', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"pat_token": pat_token,
|
||||
"repo_owner": repo_owner,
|
||||
"repo_name": repo_name,
|
||||
"repo_branch": repo_branch,
|
||||
"compressed_jsonl": compressed_jsonl,
|
||||
"embeddings_file": embeddings_file,
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data["status"] == "ok") {
|
||||
document.getElementById("success").innerHTML = "✅ Successfully updated. Go to <a href='/config'>your settings</a> to regenerate your index.";
|
||||
document.getElementById("success").style.display = "block";
|
||||
} else {
|
||||
document.getElementById("success").innerHTML = "⚠️ Failed to update settings.";
|
||||
document.getElementById("success").style.display = "block";
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
150
src/khoj/interface/web/content_type_input.html
Normal file
150
src/khoj/interface/web/content_type_input.html
Normal file
|
@ -0,0 +1,150 @@
|
|||
{% extends "base_data_integration.html" %}
|
||||
{% block content %}
|
||||
<h2>{{ content_type }}</h2>
|
||||
<form id="config-form">
|
||||
<div id="success" style="display: none;" ></div>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="input-files">Input Files</label>
|
||||
</td>
|
||||
<td id="input-files-cell">
|
||||
{% if current_config['input_files'] is none %}
|
||||
<input type="text" id="input-files" name="input-files">
|
||||
{% else %}
|
||||
{% for input_file in current_config['input_files'] %}
|
||||
<input type="text" id="input-files" name="input-files" value="{{ input_file }}">
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" id="input-files-button">Add</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="input-filter">Input Filter</label>
|
||||
</td>
|
||||
<td id="input-filter-cell">
|
||||
{% if current_config['input_filter'] is none %}
|
||||
<input type="text" id="input-filter" name="input-filter">
|
||||
{% else %}
|
||||
{% for input_filter in current_config['input_filter'] %}
|
||||
<input type="text" id="input-filter" name="input-filter" value="{{ input_filter }}">
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" id="input-filter-button">Add</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h4>You probably don't need to edit these.</h4>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="compressed-jsonl">Compressed JSONL (Output)</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="compressed-jsonl" name="compressed-jsonl" value="{{ current_config['compressed_jsonl'] }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="embeddings-file">Embeddings File (Output)</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="embeddings-file" name="embeddings-file" value="{{ current_config['embeddings_file'] }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="index-heading-entries">Index Heading Entries</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="index-heading-entries" name="index-heading-entries" value="{{ current_config['index_heading_entries'] }}">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button id="submit" type="submit">Submit</button>
|
||||
</form>
|
||||
<script>
|
||||
function addButtonEventListener(fieldName) {
|
||||
var button = document.getElementById(fieldName + "-button");
|
||||
button.addEventListener("click", function(event) {
|
||||
var cell = document.getElementById(fieldName + "-cell");
|
||||
var newInput = document.createElement("input");
|
||||
newInput.setAttribute("type", "text");
|
||||
newInput.setAttribute("name", fieldName);
|
||||
cell.appendChild(newInput);
|
||||
})
|
||||
}
|
||||
|
||||
addButtonEventListener("input-files");
|
||||
addButtonEventListener("input-filter");
|
||||
|
||||
function getValidInputNodes(nodes) {
|
||||
var validNodes = [];
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
const nodeValue = nodes[i].value;
|
||||
if (nodeValue === "" || nodeValue === null || nodeValue === undefined || nodeValue === "None") {
|
||||
continue;
|
||||
}
|
||||
validNodes.push(nodes[i]);
|
||||
}
|
||||
return validNodes;
|
||||
}
|
||||
|
||||
submit.addEventListener("click", function(event) {
|
||||
event.preventDefault();
|
||||
var inputFileNodes = document.getElementsByName("input-files");
|
||||
var input_files = getValidInputNodes(inputFileNodes).map(node => node.value);
|
||||
|
||||
var inputFilterNodes = document.getElementsByName("input-filter");
|
||||
var input_filter = getValidInputNodes(inputFilterNodes).map(node => node.value);
|
||||
|
||||
if (input_files.length === 0 && input_filter.length === 0) {
|
||||
alert("You must specify at least one input file or input filter.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (input_files.length == 0) {
|
||||
input_files = null;
|
||||
}
|
||||
|
||||
if (input_filter.length == 0) {
|
||||
input_filter = null;
|
||||
}
|
||||
|
||||
var compressed_jsonl = document.getElementById("compressed-jsonl").value;
|
||||
var embeddings_file = document.getElementById("embeddings-file").value;
|
||||
var index_heading_entries = document.getElementById("index-heading-entries").value;
|
||||
|
||||
fetch('/api/config/data/content_type/{{ content_type }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"input_files": input_files,
|
||||
"input_filter": input_filter,
|
||||
"compressed_jsonl": compressed_jsonl,
|
||||
"embeddings_file": embeddings_file,
|
||||
"index_heading_entries": index_heading_entries
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data["status"] == "ok") {
|
||||
document.getElementById("success").innerHTML = "✅ Successfully updated. Go to <a href='/config'>your settings</a> to regenerate your index.";
|
||||
document.getElementById("success").style.display = "block";
|
||||
} else {
|
||||
document.getElementById("success").innerHTML = "⚠️ Failed to update settings.";
|
||||
document.getElementById("success").style.display = "block";
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -4,8 +4,7 @@
|
|||
<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="icon" type="image/png" sizes="128x128" href="/static/assets/icons/favicon-128x128.png">
|
||||
<link rel="manifest" href="/static/khoj.webmanifest">
|
||||
</head>
|
||||
<script type="text/javascript" src="static/assets/org.min.js"></script>
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
"description": "An AI search assistant for your digital brain",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/static/assets/icons/favicon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"src": "/static/assets/icons/favicon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
"description": "An AI personal assistant for your digital brain",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/static/assets/icons/favicon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"src": "/static/assets/icons/favicon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
|
|
79
src/khoj/interface/web/processor_conversation_input.html
Normal file
79
src/khoj/interface/web/processor_conversation_input.html
Normal file
|
@ -0,0 +1,79 @@
|
|||
{% extends "base_processor_integration.html" %}
|
||||
{% block content %}
|
||||
<h2>Conversation</h2>
|
||||
<form id="config-form">
|
||||
<div id="success" style="display: none;" ></div>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="openai-api-key">OpenAI API key</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="openai-api-key" name="openai-api-key" value="{{ current_config['openai_api_key'] }}">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h4>You probably don't need to edit these.</h4>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="conversation-logfile">Conversation Logfile</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="conversation-logfile" name="conversation-logfile" value="{{ current_config['conversation_logfile'] }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="model">Model</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="model" name="model" value="{{ current_config['model'] }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="chat-model">Chat Model</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="chat-model" name="chat-model" value="{{ current_config['chat_model'] }}">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button id="submit" type="submit">Submit</button>
|
||||
</form>
|
||||
<script>
|
||||
submit.addEventListener("click", function(event) {
|
||||
event.preventDefault();
|
||||
var openai_api_key = document.getElementById("openai-api-key").value;
|
||||
var conversation_logfile = document.getElementById("conversation-logfile").value;
|
||||
var model = document.getElementById("model").value;
|
||||
var chat_model = document.getElementById("chat-model").value;
|
||||
|
||||
fetch('/api/config/data/processor/conversation', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"openai_api_key": openai_api_key,
|
||||
"conversation_logfile": conversation_logfile,
|
||||
"model": model,
|
||||
"chat_model": chat_model
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data["status"] == "ok") {
|
||||
document.getElementById("success").innerHTML = "✅ Successfully updated. Go to <a href='/config'>your settings</a> to regenerate your index.";
|
||||
document.getElementById("success").style.display = "block";
|
||||
} else {
|
||||
document.getElementById("success").innerHTML = "⚠️ Failed to update settings.";
|
||||
document.getElementById("success").style.display = "block";
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -15,9 +15,16 @@ from khoj.processor.conversation.gpt import converse, extract_questions
|
|||
from khoj.processor.conversation.utils import message_to_log, message_to_prompt
|
||||
from khoj.search_type import image_search, text_search
|
||||
from khoj.utils.helpers import log_telemetry, timer
|
||||
from khoj.utils.rawconfig import FullConfig, SearchResponse
|
||||
from khoj.utils.rawconfig import (
|
||||
FullConfig,
|
||||
SearchResponse,
|
||||
TextContentConfig,
|
||||
ConversationProcessorConfig,
|
||||
GithubContentConfig,
|
||||
)
|
||||
from khoj.utils.state import SearchType
|
||||
from khoj.utils import state, constants
|
||||
from khoj.utils.yaml import save_config_to_file_updated_state
|
||||
|
||||
# Initialize Router
|
||||
api = APIRouter()
|
||||
|
@ -65,6 +72,36 @@ async def set_config_data(updated_config: FullConfig):
|
|||
return state.config
|
||||
|
||||
|
||||
@api.post("/config/data/content_type/github", status_code=200)
|
||||
async def set_content_config_github_data(updated_config: GithubContentConfig):
|
||||
state.config.content_type.github = updated_config
|
||||
try:
|
||||
save_config_to_file_updated_state()
|
||||
return {"status": "ok"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
|
||||
@api.post("/config/data/content_type/{content_type}", status_code=200)
|
||||
async def set_content_config_data(content_type: str, updated_config: TextContentConfig):
|
||||
state.config.content_type[content_type] = updated_config
|
||||
try:
|
||||
save_config_to_file_updated_state()
|
||||
return {"status": "ok"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
|
||||
@api.post("/config/data/processor/conversation", status_code=200)
|
||||
async def set_processor_conversation_config_data(updated_config: ConversationProcessorConfig):
|
||||
state.config.processor.conversation = updated_config
|
||||
try:
|
||||
save_config_to_file_updated_state()
|
||||
return {"status": "ok"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
|
||||
@api.get("/search", response_model=List[SearchResponse])
|
||||
def search(
|
||||
q: str,
|
||||
|
|
|
@ -3,15 +3,21 @@ from fastapi import APIRouter
|
|||
from fastapi import Request
|
||||
from fastapi.responses import HTMLResponse, FileResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from khoj.utils.rawconfig import TextContentConfig, ConversationProcessorConfig
|
||||
|
||||
# Internal Packages
|
||||
from khoj.utils import constants
|
||||
from khoj.utils import constants, state
|
||||
|
||||
import logging
|
||||
import json
|
||||
|
||||
|
||||
# Initialize Router
|
||||
web_client = APIRouter()
|
||||
templates = Jinja2Templates(directory=constants.web_directory)
|
||||
|
||||
VALID_CONTENT_TYPES = ["org", "ledger", "markdown", "pdf"]
|
||||
|
||||
|
||||
# Create Routes
|
||||
@web_client.get("/", response_class=FileResponse)
|
||||
|
@ -24,6 +30,83 @@ def config_page(request: Request):
|
|||
return templates.TemplateResponse("config.html", context={"request": request})
|
||||
|
||||
|
||||
@web_client.get("/config/content_type/github", response_class=HTMLResponse)
|
||||
def github_config_page(request: Request):
|
||||
default_copy = constants.default_config.copy()
|
||||
default_github = default_copy["content-type"]["github"] # type: ignore
|
||||
|
||||
default_config = TextContentConfig(
|
||||
compressed_jsonl=default_github["compressed-jsonl"],
|
||||
embeddings_file=default_github["embeddings-file"],
|
||||
)
|
||||
|
||||
current_config = (
|
||||
state.config.content_type.github if state.config.content_type.github is not None else default_config
|
||||
)
|
||||
|
||||
current_config = json.loads(current_config.json())
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"content_type_github_input.html", context={"request": request, "current_config": current_config}
|
||||
)
|
||||
|
||||
|
||||
@web_client.get("/config/content_type/{content_type}", response_class=HTMLResponse)
|
||||
def content_config_page(request: Request, content_type: str):
|
||||
if content_type not in VALID_CONTENT_TYPES:
|
||||
return templates.TemplateResponse("config.html", context={"request": request})
|
||||
|
||||
default_copy = constants.default_config.copy()
|
||||
default_content_type = default_copy["content-type"][content_type] # type: ignore
|
||||
|
||||
default_config = TextContentConfig(
|
||||
compressed_jsonl=default_content_type["compressed-jsonl"],
|
||||
embeddings_file=default_content_type["embeddings-file"],
|
||||
)
|
||||
current_config = (
|
||||
state.config.content_type[content_type]
|
||||
if state.config.content_type[content_type] is not None
|
||||
else default_config
|
||||
)
|
||||
current_config = json.loads(current_config.json())
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"content_type_input.html",
|
||||
context={
|
||||
"request": request,
|
||||
"current_config": current_config,
|
||||
"content_type": content_type,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@web_client.get("/config/processor/conversation", response_class=HTMLResponse)
|
||||
def conversation_processor_config_page(request: Request):
|
||||
default_copy = constants.default_config.copy()
|
||||
default_processor_config = default_copy["processor"]["conversation"] # type: ignore
|
||||
default_processor_config = ConversationProcessorConfig(
|
||||
openai_api_key="",
|
||||
model=default_processor_config["model"],
|
||||
conversation_logfile=default_processor_config["conversation-logfile"],
|
||||
chat_model=default_processor_config["chat-model"],
|
||||
)
|
||||
|
||||
current_processor_conversation_config = (
|
||||
state.config.processor.conversation
|
||||
if state.config.processor.conversation is not None
|
||||
else default_processor_config
|
||||
)
|
||||
current_processor_conversation_config = json.loads(current_processor_conversation_config.json())
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"processor_conversation_input.html",
|
||||
context={
|
||||
"request": request,
|
||||
"current_config": current_processor_conversation_config,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@web_client.get("/chat", response_class=FileResponse)
|
||||
def chat_page():
|
||||
return FileResponse(constants.web_directory / "chat.html")
|
||||
|
|
|
@ -14,7 +14,7 @@ default_config = {
|
|||
"input-filter": None,
|
||||
"compressed-jsonl": "~/.khoj/content/org/org.jsonl.gz",
|
||||
"embeddings-file": "~/.khoj/content/org/org_embeddings.pt",
|
||||
"index_heading_entries": False,
|
||||
"index-heading-entries": False,
|
||||
},
|
||||
"markdown": {
|
||||
"input-files": None,
|
||||
|
@ -74,6 +74,7 @@ default_config = {
|
|||
"openai-api-key": None,
|
||||
"model": "text-davinci-003",
|
||||
"conversation-logfile": "~/.khoj/processor/conversation/conversation_logs.json",
|
||||
"chat-model": "gpt-3.5-turbo",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -15,6 +15,12 @@ class ConfigBase(BaseModel):
|
|||
alias_generator = to_snake_case_from_dash
|
||||
allow_population_by_field_name = True
|
||||
|
||||
def __getitem__(self, item):
|
||||
return getattr(self, item)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
return setattr(self, key, value)
|
||||
|
||||
|
||||
class TextConfigBase(ConfigBase):
|
||||
compressed_jsonl: Path
|
||||
|
|
|
@ -6,12 +6,20 @@ import yaml
|
|||
|
||||
# Internal Packages
|
||||
from khoj.utils.rawconfig import FullConfig
|
||||
from khoj.utils import state
|
||||
|
||||
|
||||
# Do not emit tags when dumping to YAML
|
||||
yaml.emitter.Emitter.process_tag = lambda self, *args, **kwargs: None # type: ignore[assignment]
|
||||
|
||||
|
||||
def save_config_to_file_updated_state():
|
||||
with open(state.config_file, "w") as outfile:
|
||||
yaml.dump(yaml.safe_load(state.config.json(by_alias=True)), outfile)
|
||||
outfile.close()
|
||||
return state.config
|
||||
|
||||
|
||||
def save_config_to_file(yaml_config: dict, yaml_config_file: Path):
|
||||
"Write config to YML file"
|
||||
# Create output directory, if it doesn't exist
|
||||
|
|
Loading…
Reference in a new issue