Add support for multiple chat sessions in the desktop application (#639)

* Add chat sessions to the desktop application
* Increase width of the main chat body to 90vw
* Update the version of electron
* Render the default message if chat history fails to load
* Merge conversation migrations and fix slug setting
* Update the welcome message, use the hostURL, and update background color for chat actions
* Only update the window's web contents if the page is config
This commit is contained in:
sabaimran 2024-02-11 02:35:28 -08:00 committed by GitHub
parent 1412ed6a00
commit 69344a6aa6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 531 additions and 56 deletions

View file

@ -140,6 +140,9 @@
// Scroll to bottom of chat-body element
chatBody.scrollTop = chatBody.scrollHeight;
let chatBodyWrapper = document.getElementById("chat-body-wrapper");
chatBodyWrapperHeight = chatBodyWrapper.clientHeight;
}
function processOnlineReferences(referenceSection, onlineContext) {
@ -319,14 +322,25 @@
autoResize();
document.getElementById("chat-input").setAttribute("disabled", "disabled");
let chat_body = document.getElementById("chat-body");
let conversationID = chat_body.dataset.conversationId;
let hostURL = await window.hostURLAPI.getURL();
if (!conversationID) {
let response = await fetch(`${hostURL}/api/chat/sessions`, { method: "POST" });
let data = await response.json();
conversationID = data.conversation_id;
chat_body.dataset.conversationId = conversationID;
}
// Generate backend API URL to execute query
let url = `${hostURL}/api/chat?q=${encodeURIComponent(query)}&n=${resultsCount}&client=web&stream=true`;
let url = `${hostURL}/api/chat?q=${encodeURIComponent(query)}&n=${resultsCount}&client=web&stream=true&conversation_id=${conversationID}`;
const khojToken = await window.tokenAPI.getToken();
const headers = { 'Authorization': `Bearer ${khojToken}` };
let chat_body = document.getElementById("chat-body");
let new_response = document.createElement("div");
new_response.classList.add("chat-message", "khoj");
new_response.attributes["data-meta"] = "🏮 Khoj at " + formatDate(new Date());
@ -559,7 +573,14 @@
const khojToken = await window.tokenAPI.getToken();
const headers = { 'Authorization': `Bearer ${khojToken}` };
fetch(`${hostURL}/api/chat/history?client=desktop`, { headers })
let chatBody = document.getElementById("chat-body");
let conversationId = chatBody.dataset.conversationId;
let chatHistoryUrl = `/api/chat/history?client=desktop`;
if (conversationId) {
chatHistoryUrl += `&conversation_id=${conversationId}`;
}
fetch(`${hostURL}${chatHistoryUrl}`, { headers })
.then(response => response.json())
.then(data => {
if (data.detail) {
@ -584,17 +605,186 @@
return data.response;
})
.then(response => {
conversationId = response.conversation_id;
const conversationTitle = response.slug || `New conversation 🌱`;
let chatBody = document.getElementById("chat-body");
chatBody.dataset.conversationId = conversationId;
chatBody.dataset.conversationTitle = conversationTitle;
const fullChatLog = response.chat || [];
const fullChatLog = response.chat;
// Render conversation history, if any
fullChatLog.forEach(chat_log => {
renderMessageWithReference(chat_log.message, chat_log.by, chat_log.context, new Date(chat_log.created), chat_log.onlineContext, chat_log.intent?.type, chat_log.intent?.["inferred-queries"]);
});
if (chat_log.message != null) {
renderMessageWithReference(
chat_log.message,
chat_log.by,
chat_log.context,
new Date(chat_log.created),
chat_log.onlineContext,
chat_log.intent?.type,
chat_log.intent?.["inferred-queries"]);
}
})
let chatBodyWrapper = document.getElementById("chat-body-wrapper");
chatBodyWrapperHeight = chatBodyWrapper.clientHeight;
chatBody.style.height = chatBodyWrapperHeight;
})
.catch(err => {
// If the server returns a 500 error with detail, render a setup hint.
first_run_message = `Hi 👋🏾, to get started:
<ol>
<li>Generate an API token in the <a class='inline-chat-link' href="#" onclick="window.navigateAPI.navigateToWebSettings()">Khoj Web settings</a></li>
<li>Paste it into the API Key field in the <a class='inline-chat-link' href="#" onclick="window.navigateAPI.navigateToSettings()">Khoj Desktop settings</a></li>
</ol>`
.trim()
.replace(/(\r\n|\n|\r)/gm, "");
renderMessage(first_run_message, "khoj", null, null, true);
// Disable chat input field and update placeholder text
document.getElementById("chat-input").setAttribute("disabled", "disabled");
document.getElementById("chat-input").setAttribute("placeholder", "Configure Khoj to enable chat");
return;
});
fetch(`${hostURL}/api/chat/sessions`, { method: "GET", headers })
.then(response => response.json())
.then(data => {
let conversationListBody = document.getElementById("conversation-list-body");
conversationListBody.innerHTML = "";
let conversationListBodyHeader = document.getElementById("conversation-list-header");
let chatBody = document.getElementById("chat-body");
conversationId = chatBody.dataset.conversationId;
if (data.length > 0) {
conversationListBodyHeader.style.display = "block";
for (let index in data) {
let conversation = data[index];
let conversationButton = document.createElement('div');
let incomingConversationId = conversation["conversation_id"];
const conversationTitle = conversation["slug"] || `New conversation 🌱`;
conversationButton.innerHTML = conversationTitle;
conversationButton.classList.add("conversation-button");
if (incomingConversationId == conversationId) {
conversationButton.classList.add("selected-conversation");
}
conversationButton.addEventListener('click', function() {
let chatBody = document.getElementById("chat-body");
chatBody.innerHTML = "";
chatBody.dataset.conversationId = incomingConversationId;
chatBody.dataset.conversationTitle = conversationTitle;
loadChat();
});
let threeDotMenu = document.createElement('div');
threeDotMenu.classList.add("three-dot-menu");
let threeDotMenuButton = document.createElement('button');
threeDotMenuButton.innerHTML = "⋮";
threeDotMenuButton.classList.add("three-dot-menu-button");
threeDotMenuButton.addEventListener('click', function(event) {
event.stopPropagation();
let existingChildren = threeDotMenu.children;
if (existingChildren.length > 1) {
// Skip deleting the first, since that's the menu button.
for (let i = 1; i < existingChildren.length; i++) {
existingChildren[i].remove();
}
return;
}
let conversationMenu = document.createElement('div');
conversationMenu.classList.add("conversation-menu");
let deleteButton = document.createElement('button');
deleteButton.innerHTML = "Delete";
deleteButton.classList.add("delete-conversation-button");
deleteButton.classList.add("three-dot-menu-button-item");
deleteButton.addEventListener('click', function() {
let deleteURL = `/api/chat/history?client=web&conversation_id=${incomingConversationId}`;
fetch(`${hostURL}${deleteURL}` , { method: "DELETE", headers })
.then(response => response.ok ? response.json() : Promise.reject(response))
.then(data => {
let chatBody = document.getElementById("chat-body");
chatBody.innerHTML = "";
chatBody.dataset.conversationId = "";
chatBody.dataset.conversationTitle = "";
loadChat();
})
.catch(err => {
return;
});
});
conversationMenu.appendChild(deleteButton);
threeDotMenu.appendChild(conversationMenu);
let editTitleButton = document.createElement('button');
editTitleButton.innerHTML = "Rename";
editTitleButton.classList.add("edit-title-button");
editTitleButton.classList.add("three-dot-menu-button-item");
editTitleButton.addEventListener('click', function(event) {
event.stopPropagation();
let conversationMenuChildren = conversationMenu.children;
let totalItems = conversationMenuChildren.length;
for (let i = totalItems - 1; i >= 0; i--) {
conversationMenuChildren[i].remove();
}
// Create a dialog box to get new title for conversation
let conversationTitleInputBox = document.createElement('div');
conversationTitleInputBox.classList.add("conversation-title-input-box");
let conversationTitleInput = document.createElement('input');
conversationTitleInput.classList.add("conversation-title-input");
conversationTitleInput.value = conversationTitle;
conversationTitleInput.addEventListener('click', function(event) {
event.stopPropagation();
if (event.key === "Enter") {
event.preventDefault();
conversationTitleInputButton.click();
}
});
conversationTitleInputBox.appendChild(conversationTitleInput);
let conversationTitleInputButton = document.createElement('button');
conversationTitleInputButton.innerHTML = "Save";
conversationTitleInputButton.classList.add("three-dot-menu-button-item");
conversationTitleInputButton.addEventListener('click', function(event) {
event.stopPropagation();
let newTitle = conversationTitleInput.value;
if (newTitle != null) {
let editURL = `/api/chat/title?client=web&conversation_id=${incomingConversationId}&title=${newTitle}`;
fetch(`${hostURL}${editURL}` , { method: "PATCH" })
.then(response => response.ok ? response.json() : Promise.reject(response))
.then(data => {
conversationButton.innerHTML = newTitle;
})
.catch(err => {
return;
});
conversationTitleInputBox.remove();
}});
conversationTitleInputBox.appendChild(conversationTitleInputButton);
conversationMenu.appendChild(conversationTitleInputBox);
});
conversationMenu.appendChild(editTitleButton);
threeDotMenu.appendChild(conversationMenu);
});
threeDotMenu.appendChild(threeDotMenuButton);
conversationButton.appendChild(threeDotMenu);
conversationListBody.appendChild(conversationButton);
}
}
})
fetch(`${hostURL}/api/chat/starters?client=desktop`, { headers })
.then(response => response.json())
.then(data => {
@ -652,19 +842,36 @@
}, 2000);
}
function createNewConversation() {
let chatBody = document.getElementById("chat-body");
chatBody.innerHTML = "";
flashStatusInChatInput("📝 New conversation started");
chatBody.dataset.conversationId = "";
chatBody.dataset.conversationTitle = "";
renderMessage("Hey 👋🏾, what's up?", "khoj");
}
async function clearConversationHistory() {
let chatInput = document.getElementById("chat-input");
let originalPlaceholder = chatInput.placeholder;
let chatBody = document.getElementById("chat-body");
let conversationId = chatBody.dataset.conversationId;
let deleteURL = `/api/chat/history?client=desktop`;
if (conversationId) {
deleteURL += `&conversation_id=${conversationId}`;
}
const hostURL = await window.hostURLAPI.getURL();
const khojToken = await window.tokenAPI.getToken();
const headers = { 'Authorization': `Bearer ${khojToken}` };
fetch(`${hostURL}/api/chat/history?client=desktop`, { method: "DELETE", headers })
fetch(`${hostURL}${deleteURL}`, { method: "DELETE", headers })
.then(response => response.ok ? response.json() : Promise.reject(response))
.then(data => {
chatBody.innerHTML = "";
chatBody.dataset.conversationId = "";
chatBody.dataset.conversationTitle = "";
loadChat();
flashStatusInChatInput("🗑 Cleared conversation history");
})
@ -776,6 +983,14 @@
// Stop the countdown timer UI
document.getElementById('countdown-circle').style.animation = "none";
};
function handleCollapseSidePanel() {
document.getElementById('side-panel').classList.toggle('collapsed');
document.getElementById('new-conversation').classList.toggle('collapsed');
document.getElementById('existing-conversations').classList.toggle('collapsed');
document.getElementById('chat-section-wrapper').classList.toggle('mobile-friendly');
}
</script>
<body>
<div id="khoj-empty-container" class="khoj-empty-container">
@ -793,47 +1008,80 @@
</nav>
</div>
<!-- Chat Body -->
<div id="chat-body"></div>
<div id="chat-section-wrapper">
<div id="side-panel-wrapper">
<div id="side-panel">
<div id="new-conversation">
<button class="side-panel-button" id="new-conversation-button" onclick="createNewConversation()">
New Topic
<svg class="new-convo-button" viewBox="0 0 35 35" fill="#000000" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="M16 0c-8.836 0-16 7.163-16 16s7.163 16 16 16c8.837 0 16-7.163 16-16s-7.163-16-16-16zM16 30.032c-7.72 0-14-6.312-14-14.032s6.28-14 14-14 14 6.28 14 14-6.28 14.032-14 14.032zM23 15h-6v-6c0-0.552-0.448-1-1-1s-1 0.448-1 1v6h-6c-0.552 0-1 0.448-1 1s0.448 1 1 1h6v6c0 0.552 0.448 1 1 1s1-0.448 1-1v-6h6c0.552 0 1-0.448 1-1s-0.448-1-1-1z"></path>
</svg>
</button>
</div>
<div id="existing-conversations">
<div id="conversation-list">
<div id="conversation-list-header" style="display: none;">Recent Conversations</div>
<div id="conversation-list-body"></div>
</div>
</div>
</div>
<div id="collapse-side-panel">
<button
class="side-panel-button"
id="collapse-side-panel-button"
onclick="handleCollapseSidePanel()"
>
<svg class="side-panel-collapse" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.82054 20.7313C8.21107 21.1218 8.84423 21.1218 9.23476 20.7313L15.8792 14.0868C17.0505 12.9155 17.0508 11.0167 15.88 9.84497L9.3097 3.26958C8.91918 2.87905 8.28601 2.87905 7.89549 3.26958C7.50497 3.6601 7.50497 4.29327 7.89549 4.68379L14.4675 11.2558C14.8581 11.6464 14.8581 12.2795 14.4675 12.67L7.82054 19.317C7.43002 19.7076 7.43002 20.3407 7.82054 20.7313Z" fill="#0F0F0F"/>
</svg>
</button>
</div>
</div>
<div id="chat-body-wrapper">
<!-- Chat Body -->
<div id="chat-body"></div>
<!-- Chat Suggestions -->
<div id="question-starters" style="display: none;"></div>
<!-- Chat Suggestions -->
<div id="question-starters" style="display: none;"></div>
<!-- Chat Footer -->
<div id="chat-footer">
<div id="chat-tooltip" style="display: none;"></div>
<div id="input-row">
<button id="clear-chat-button" class="input-row-button" onclick="clearConversationHistory()">
<svg class="input-row-button-img" alt="Clear Chat History" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<rect width="128" height="128" fill="none"/>
<line x1="216" y1="56" x2="40" y2="56" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"/>
<line x1="104" y1="104" x2="104" y2="168" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"/>
<line x1="152" y1="104" x2="152" y2="168" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"/>
<path d="M200,56V208a8,8,0,0,1-8,8H64a8,8,0,0,1-8-8V56" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"/>
<path d="M168,56V40a16,16,0,0,0-16-16H104A16,16,0,0,0,88,40V56" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"/>
</svg>
</button>
<textarea id="chat-input" class="option" oninput="onChatInput()" onkeydown=incrementalChat(event) autofocus="autofocus" placeholder="Type / to see a list of commands"></textarea>
<button id="speak-button" class="input-row-button"
ontouchstart="speechToText(event)" ontouchend="speechToText(event)" ontouchcancel="speechToText(event)" onmousedown="speechToText(event)">
<svg id="speak-button-img" class="input-row-button-img" alt="Transcribe" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M3.5 6.5A.5.5 0 0 1 4 7v1a4 4 0 0 0 8 0V7a.5.5 0 0 1 1 0v1a5 5 0 0 1-4.5 4.975V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 .5-.5z"/>
<path d="M10 8a2 2 0 1 1-4 0V3a2 2 0 1 1 4 0v5zM8 0a3 3 0 0 0-3 3v5a3 3 0 0 0 6 0V3a3 3 0 0 0-3-3z"/>
</svg>
<svg id="stop-record-button-img" style="display: none" class="input-row-button-img" alt="Stop Transcribing" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M5 6.5A1.5 1.5 0 0 1 6.5 5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3z"/>
</svg>
</button>
<button id="send-button" class="input-row-button" alt="Send message">
<svg id="send-button-img" onclick="chat()" class="input-row-button-img" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8zm15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-7.5 3.5a.5.5 0 0 1-1 0V5.707L5.354 7.854a.5.5 0 1 1-.708-.708l3-3a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 5.707V11.5z"/>
</svg>
<svg id="stop-send-button-img" onclick="cancelSendMessage()" style="display: none" class="input-row-button-img" alt="Stop Message Send" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<circle id="countdown-circle" class="countdown-circle" cx="8" cy="8" r="7" />
<path d="M5 6.5A1.5 1.5 0 0 1 6.5 5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3z"/>
</svg>
</button>
<!-- Chat Footer -->
<div id="chat-footer">
<div id="chat-tooltip" style="display: none;"></div>
<div id="input-row">
<button id="clear-chat-button" class="input-row-button" onclick="clearConversationHistory()">
<svg class="input-row-button-img" alt="Clear Chat History" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<rect width="128" height="128" fill="none"/>
<line x1="216" y1="56" x2="40" y2="56" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"/>
<line x1="104" y1="104" x2="104" y2="168" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"/>
<line x1="152" y1="104" x2="152" y2="168" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"/>
<path d="M200,56V208a8,8,0,0,1-8,8H64a8,8,0,0,1-8-8V56" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"/>
<path d="M168,56V40a16,16,0,0,0-16-16H104A16,16,0,0,0,88,40V56" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"/>
</svg>
</button>
<textarea id="chat-input" class="option" oninput="onChatInput()" onkeydown=incrementalChat(event) autofocus="autofocus" placeholder="Type / to see a list of commands"></textarea>
<button id="speak-button" class="input-row-button"
ontouchstart="speechToText(event)" ontouchend="speechToText(event)" ontouchcancel="speechToText(event)" onmousedown="speechToText(event)">
<svg id="speak-button-img" class="input-row-button-img" alt="Transcribe" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M3.5 6.5A.5.5 0 0 1 4 7v1a4 4 0 0 0 8 0V7a.5.5 0 0 1 1 0v1a5 5 0 0 1-4.5 4.975V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 .5-.5z"/>
<path d="M10 8a2 2 0 1 1-4 0V3a2 2 0 1 1 4 0v5zM8 0a3 3 0 0 0-3 3v5a3 3 0 0 0 6 0V3a3 3 0 0 0-3-3z"/>
</svg>
<svg id="stop-record-button-img" style="display: none" class="input-row-button-img" alt="Stop Transcribing" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M5 6.5A1.5 1.5 0 0 1 6.5 5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3z"/>
</svg>
</button>
<button id="send-button" class="input-row-button" alt="Send message">
<svg id="send-button-img" onclick="chat()" class="input-row-button-img" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8zm15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-7.5 3.5a.5.5 0 0 1-1 0V5.707L5.354 7.854a.5.5 0 1 1-.708-.708l3-3a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 5.707V11.5z"/>
</svg>
<svg id="stop-send-button-img" onclick="cancelSendMessage()" style="display: none" class="input-row-button-img" alt="Stop Message Send" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<circle id="countdown-circle" class="countdown-circle" cx="8" cy="8" r="7" />
<path d="M5 6.5A1.5 1.5 0 0 1 6.5 5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3z"/>
</svg>
</button>
</div>
</div>
</div>
</div>
</body>
@ -855,15 +1103,86 @@
font-weight: 300;
line-height: 1.5em;
}
body > * {
padding: 10px;
margin: 10px;
}
input.conversation-title-input {
font-family: var(--font-family);
font-size: 14px;
font-weight: 300;
line-height: 1.5em;
padding: 5px;
border: 1px solid var(--main-text-color);
border-radius: 5px;
margin: 4px;
}
input.conversation-title-input:focus {
outline: none;
}
#chat-section-wrapper {
display: grid;
grid-template-columns: auto auto;
grid-column-gap: 10px;
grid-row-gap: 10px;
padding: 10px;
margin: 10px;
overflow-y: scroll;
}
#chat-section-wrapper.mobile-friendly {
grid-template-columns: auto auto;
}
#chat-body-wrapper {
display: flex;
flex-direction: column;
overflow: hidden;
}
#side-panel {
padding: 10px;
background: var(--background-color);
border-radius: 5px;
box-shadow: 0 0 11px #aaa;
overflow-y: scroll;
text-align: left;
transition: width 0.3s ease-in-out;
width: 250px;
}
div#side-panel.collapsed {
width: 1px;
display: block;
overflow: hidden;
}
div#collapse-side-panel {
align-self: center;
padding: 8px;
}
div#conversation-list-body {
display: grid;
grid-template-columns: 1fr;
grid-gap: 8px;
}
div#side-panel-wrapper {
display: flex
}
#chat-body {
font-size: small;
margin: 0px;
line-height: 20px;
overflow-y: scroll; /* Make chat body scroll to see history */
overflow-y: scroll;
overflow-x: hidden;
}
/* add chat metatdata to bottom of bubble */
.chat-message::after {
@ -871,7 +1190,7 @@
display: block;
font-size: x-small;
color: #475569;
margin: -8px 4px 0 -5px;
margin: -8px 4px 0px 0px;
}
/* move message by khoj to left */
.chat-message.khoj {
@ -962,6 +1281,7 @@
grid-row-gap: 10px;
background: #f9fafc;
align-items: center;
background-color: var(--background-color);
}
.option:hover {
box-shadow: 0 0 11px #aaa;
@ -998,9 +1318,33 @@
margin-top: -2px;
margin-left: -5px;
}
.side-panel-button {
background: var(--background-color);
border: none;
box-shadow: none;
font-size: 14px;
font-weight: 300;
line-height: 1.5em;
cursor: pointer;
transition: background 0.3s ease-in-out;
border-radius: 5%;;
font-family: var(--font-family);
padding: 8px;
font-size: large;
}
svg.side-panel-collapse {
width: 30px;
height: 30px;
}
.side-panel-button:hover,
.input-row-button:hover {
background: var(--primary-hover);
}
.side-panel-button:active,
.input-row-button:active {
background: var(--primary-active);
}
@ -1236,10 +1580,36 @@
#clear-chat-button {
margin-left: 0;
}
div#side-panel.collapsed {
width: 0px;
display: block;
overflow: hidden;
padding: 0;
}
svg.side-panel-collapse {
width: 24px;
height: 24px;
}
#chat-body-wrapper {
min-width: 0;
}
div#chat-section-wrapper {
padding: 4px;
margin: 4px;
grid-column-gap: 4px;
}
div#collapse-side-panel {
align-self: center;
padding: 0px;
}
}
@media only screen and (min-width: 600px) {
body {
grid-template-columns: auto min(70vw, 100%) auto;
grid-template-columns: auto min(90vw, 100%) auto;
grid-template-rows: auto auto minmax(80px, 100%) auto;
}
body > * {
@ -1252,6 +1622,110 @@
font-size: medium;
}
svg.new-convo-button {
width: 20px;
margin-left: 5px;
}
div#new-conversation {
text-align: left;
border-bottom: 1px solid var(--main-text-color);
margin-bottom: 8px;
}
button#new-conversation-button {
display: inline-flex;
align-items: center;
}
div.conversation-button {
background: var(--background-color);
color: var(--main-text-color);
border: 1px solid var(--main-text-color);
border-radius: 5px;
padding: 5px;
font-size: 14px;
font-weight: 300;
line-height: 1.5em;
cursor: pointer;
transition: background 0.2s ease-in-out;
text-align: left;
display: flex;
position: relative;
}
.three-dot-menu {
display: none;
/* background: var(--background-color); */
/* border: 1px solid var(--main-text-color); */
border-radius: 5px;
/* position: relative; */
position: absolute;
right: 4;
top: 4;
}
button.three-dot-menu-button-item {
background: var(--background-color);
color: var(--main-text-color);
border: none;
box-shadow: none;
font-size: 14px;
font-weight: 300;
line-height: 1.5em;
cursor: pointer;
transition: background 0.3s ease-in-out;
font-family: var(--font-family);
border-radius: 4px;
right: 0;
}
button.three-dot-menu-button-item:hover {
background: var(--primary-hover);
color: var(--primary-inverse);
}
.three-dot-menu-button {
background: var(--background-color);
border: none;
box-shadow: none;
font-size: 14px;
font-weight: 300;
line-height: 1.5em;
cursor: pointer;
transition: background 0.3s ease-in-out;
font-family: var(--font-family);
border-radius: 4px;
right: 0;
}
.conversation-button:hover .three-dot-menu {
display: block;
}
div.conversation-menu {
position: absolute;
z-index: 1;
top: 100%;
right: 0;
text-align: right;
background-color: var(--background-color);
border: 1px solid var(--main-text-color);
border-radius: 5px;
padding: 5px;
box-shadow: 0 0 11px #aaa;
}
div.conversation-button:hover {
background: var(--primary-hover);
color: var(--primary-inverse);
}
div.selected-conversation {
background: var(--primary-hover) !important;
color: var(--primary-inverse) !important;
}
@keyframes gradient {
0% {
background-position: 0% 50%;

View file

@ -225,7 +225,8 @@ function pushDataToKhoj (regenerate = false) {
.finally(() => {
// Syncing complete
syncing = false;
if (win = BrowserWindow.getAllWindows()[0]) {
const win = BrowserWindow.getAllWindows().find(win => win.webContents.getURL().includes('config'));
if (win) {
win.webContents.send('update-state', state);
}
});

View file

@ -10,7 +10,7 @@
"main": "main.js",
"private": false,
"devDependencies": {
"electron": "25.8.4"
"electron": "28.2.1"
},
"scripts": {
"start": "yarn electron ."

View file

@ -379,10 +379,10 @@ electron-updater@^4.6.1:
lodash.isequal "^4.5.0"
semver "^7.3.5"
electron@25.8.4:
version "25.8.4"
resolved "https://registry.yarnpkg.com/electron/-/electron-25.8.4.tgz#b50877aac7d96323920437baf309ad86382cb455"
integrity sha512-hUYS3RGdaa6E1UWnzeGnsdsBYOggwMMg4WGxNGvAoWtmRrr6J1BsjFW/yRq4WsJHJce2HdzQXtz4OGXV6yUCLg==
electron@28.2.1:
version "28.2.1"
resolved "https://registry.yarnpkg.com/electron/-/electron-28.2.1.tgz#8edf2be24d97160b7eb52b7ce9a2424cf14c0791"
integrity sha512-wlzXf+OvOiVlBf9dcSeMMf7Q+N6DG+wtgFbMK0sA/JpIJcdosRbLMQwLg/LTwNVKIbmayqFLDp4FmmFkEMhbYA==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^18.11.18"