Skip to content

Commit

Permalink
Fix and Improve Chat UI in Web, Desktop apps (#655)
Browse files Browse the repository at this point in the history
### Improvements to Chat UI on Web, Desktop apps
- Improve styling of chat session side panel
- Improve styling of chat message bubble in Desktop, Web app
- Add frosted, minimal chat UI to background of Login screen
- Improve PWA install experience of Khoj

### Fixes to Chat UI on Web, Desktop apps
- Fix creating new chat sessions from the Desktop app
- Only show 3 starter questions even when consecutive chat sessions created

### Other Improvements
- Update Khoj cloud trial period to a fortnight instead of a week
- Document using venv to handle dependency conflict on khoj pip install

Resolves #276
  • Loading branch information
debanjum authored Feb 23, 2024
2 parents b490209 + c70ca78 commit 9afb2a1
Show file tree
Hide file tree
Showing 19 changed files with 153 additions and 71 deletions.
16 changes: 14 additions & 2 deletions documentation/docs/get-started/setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ import TabItem from '@theme/TabItem';
```

## Setup
These are the general setup instructions for Khoj.
These are the general setup instructions for self-hosted Khoj.

- Make sure [python](https://realpython.com/installing-python/) and [pip](https://pip.pypa.io/en/stable/installation/) are installed on your machine
- Check the [Khoj Emacs docs](/clients/emacs#setup) to setup Khoj with Emacs<br />
It's simpler as it can skip the server *install*, *run* and *configure* step below.
- Check the [Khoj Obsidian docs](/clients/obsidian#setup) to setup Khoj with Obsidian<br />
Its simpler as it can skip the *configure* step below.

For Installation, you can either use Docker or install Khoj locally.
For Installation, you can either use Docker or install the Khoj server locally.

### Installation Option 1 (Docker)

Expand Down Expand Up @@ -267,6 +267,18 @@ You can head to http://localhost:42110 to use the web interface. You can also us
## Troubleshoot
#### Dependency conflict when trying to install Khoj python package with pip
- **Reason**: When conflicting dependency versions are required by Khoj vs other python packages installed on your system
- **Fix**: Install Khoj in a python virtual environment using [venv](https://docs.python.org/3/library/venv.html) or [pipx](https://pypa.github.io/pipx) to avoid this dependency conflicts
- **Process**:
1. Install [pipx](https://pypa.github.io/pipx/#install-pipx)
2. Use `pipx` to install Khoj to avoid dependency conflicts with other python packages.
```shell
pipx install khoj-assistant
```
3. Now start `khoj` using the standard steps described earlier


#### Install fails while building Tokenizer dependency
- **Details**: `pip install khoj-assistant` fails while building the `tokenizers` dependency. Complains about Rust.
- **Fix**: Install Rust to build the tokenizers package. For example on Mac run:
Expand Down
2 changes: 1 addition & 1 deletion src/interface/desktop/about.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<title>Khoj - About</title>

<link rel="icon" type="image/png" sizes="128x128" href="./assets/icons/favicon-128x128.png">
<link rel="manifest" href="/static/khoj_chat.webmanifest">
<link rel="manifest" href="/static/khoj.webmanifest">
<link rel="stylesheet" href="./assets/khoj.css">
</head>
<script type="text/javascript" src="./utils.js"></script>
Expand Down
2 changes: 1 addition & 1 deletion src/interface/desktop/assets/khoj.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* Amber Light scheme (Default) */
/* Can be forced with data-theme="light" */
@import url('https://fonts.googleapis.com/css2?family=Tajawal&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;500;700&display=swap');

[data-theme="light"],
:root:not([data-theme="dark"]) {
Expand Down
61 changes: 38 additions & 23 deletions src/interface/desktop/chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<title>Khoj - Chat</title>

<link rel="icon" type="image/png" sizes="128x128" href="./assets/icons/favicon-128x128.png">
<link rel="manifest" href="/static/khoj_chat.webmanifest">
<link rel="manifest" href="/static/khoj.webmanifest">
<link rel="stylesheet" href="./assets/khoj.css">
</head>
<script type="text/javascript" src="./assets/markdown-it.min.js"></script>
Expand Down Expand Up @@ -104,7 +104,7 @@
linkElement.classList.add("inline-chat-link");
linkElement.classList.add("reference-link");
linkElement.setAttribute('title', title);
linkElement.innerHTML = title;
linkElement.textContent = title;

let referenceButton = document.createElement('button');
referenceButton.innerHTML = linkElement.outerHTML;
Expand Down Expand Up @@ -133,7 +133,6 @@
let message_time = formatDate(dt ?? new Date());
let by_name = by == "khoj" ? "🏮 Khoj" : "🤔 You";
let formattedMessage = formatHTMLMessage(message, raw);
let chatBody = document.getElementById("chat-body");

// Create a new div for the chat message
let chatMessage = document.createElement('div');
Expand All @@ -152,6 +151,7 @@
}

// Append chat message div to chat body
let chatBody = document.getElementById("chat-body");
chatBody.appendChild(chatMessage);

// Scroll to bottom of chat-body element
Expand Down Expand Up @@ -285,9 +285,12 @@

// Render markdown
newHTML = raw ? newHTML : md.render(newHTML);
// Get any elements with a class that starts with "language"
// Set rendered markdown to HTML DOM element
let element = document.createElement('div');
element.innerHTML = newHTML;
element.className = "chat-message-text-response";

// Get any elements with a class that starts with "language"
let codeBlockElements = element.querySelectorAll('[class^="language-"]');
// For each element, add a parent div with the class "programmatic-output"
codeBlockElements.forEach((codeElement) => {
Expand Down Expand Up @@ -341,22 +344,20 @@
let chat_body = document.getElementById("chat-body");

let conversationID = chat_body.dataset.conversationId;

let hostURL = await window.hostURLAPI.getURL();
const khojToken = await window.tokenAPI.getToken();
const headers = { 'Authorization': `Bearer ${khojToken}` };

if (!conversationID) {
let response = await fetch(`${hostURL}/api/chat/sessions`, { method: "POST" });
let response = await fetch(`${hostURL}/api/chat/sessions`, { method: "POST", headers });
let data = await response.json();
conversationID = data.conversation_id;
chat_body.dataset.conversationId = conversationID;
await refreshChatSessionsPanel();
}


// Generate backend API URL to execute query
let url = `${hostURL}/api/chat?q=${encodeURIComponent(query)}&n=${resultsCount}&client=web&stream=true&conversation_id=${conversationID}&region=${region}&city=${city}&country=${countryName}`;
const khojToken = await window.tokenAPI.getToken();
const headers = { 'Authorization': `Bearer ${khojToken}` };
let chatApi = `${hostURL}/api/chat?q=${encodeURIComponent(query)}&n=${resultsCount}&client=web&stream=true&conversation_id=${conversationID}&region=${region}&city=${city}&country=${countryName}`;

let new_response = document.createElement("div");
new_response.classList.add("chat-message", "khoj");
Expand All @@ -379,8 +380,8 @@
let chatInput = document.getElementById("chat-input");
chatInput.classList.remove("option-enabled");

// Call specified Khoj API
let response = await fetch(url, { headers });
// Call Khoj chat API
let response = await fetch(chatApi, { headers });
let rawResponse = "";
const contentType = response.headers.get("content-type");

Expand Down Expand Up @@ -540,6 +541,7 @@
chatInput.value = chatInput.value.trimStart();

let questionStarterSuggestions = document.getElementById("question-starters");
questionStarterSuggestions.innerHTML = "";
questionStarterSuggestions.style.display = "none";

if (chatInput.value.startsWith("/") && chatInput.value.split(" ").length === 1) {
Expand Down Expand Up @@ -591,6 +593,7 @@
const headers = { 'Authorization': `Bearer ${khojToken}` };

let chatBody = document.getElementById("chat-body");
chatBody.innerHTML = "";
let conversationId = chatBody.dataset.conversationId;
let chatHistoryUrl = `/api/chat/history?client=desktop`;
if (conversationId) {
Expand Down Expand Up @@ -671,11 +674,11 @@
fetch(`${hostURL}/api/chat/starters?client=desktop`, { headers })
.then(response => response.json())
.then(data => {
// Render chat options, if any
// Render conversation starters, if any
if (data.length > 0) {
let questionStarterSuggestions = document.getElementById("question-starters");
for (let index in data) {
let questionStarter = data[index];
questionStarterSuggestions.innerHTML = "";
data.forEach((questionStarter) => {
let questionStarterButton = document.createElement('button');
questionStarterButton.innerHTML = questionStarter;
questionStarterButton.classList.add("question-starter");
Expand All @@ -685,7 +688,7 @@
chat();
});
questionStarterSuggestions.appendChild(questionStarterButton);
}
});
questionStarterSuggestions.style.display = "grid";
}
})
Expand Down Expand Up @@ -785,7 +788,7 @@
let conversationButton = document.createElement('div');
let incomingConversationId = conversation["conversation_id"];
const conversationTitle = conversation["slug"] || `New conversation 🌱`;
conversationButton.innerHTML = conversationTitle;
conversationButton.textContent = conversationTitle;
conversationButton.classList.add("conversation-button");
if (incomingConversationId == conversationId) {
conversationButton.classList.add("selected-conversation");
Expand Down Expand Up @@ -883,7 +886,7 @@
fetch(`${hostURL}${editURL}` , { method: "PATCH" })
.then(response => response.ok ? response.json() : Promise.reject(response))
.then(data => {
conversationButton.innerHTML = newTitle;
conversationButton.textContent = newTitle;
})
.catch(err => {
return;
Expand Down Expand Up @@ -1014,6 +1017,7 @@
document.getElementById('side-panel').classList.toggle('collapsed');
document.getElementById('new-conversation').classList.toggle('collapsed');
document.getElementById('existing-conversations').classList.toggle('collapsed');
document.getElementById('side-panel-collapse').style.transform = document.getElementById('side-panel').classList.contains('collapsed') ? 'rotate(0deg)' : 'rotate(180deg)';

document.getElementById('chat-section-wrapper').classList.toggle('mobile-friendly');
}
Expand Down Expand Up @@ -1058,7 +1062,7 @@
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">
<svg id="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>
Expand Down Expand Up @@ -1161,18 +1165,19 @@
}

#side-panel {
width: 250px;
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;
width: 0;
padding: 0;
display: block;
overflow: hidden;
}
Expand Down Expand Up @@ -1227,13 +1232,23 @@
display: inline-block;
max-width: 80%;
text-align: left;
white-space: pre-line;
}
/* color chat bubble by khoj blue */
.chat-message-text.khoj {
color: var(--primary-inverse);
background: var(--primary);
margin-left: auto;
}
.chat-message-text ol,
.chat-message-text ul {
white-space: normal;
margin: 0;
}
.chat-message-text-response {
margin-bottom: -16px;
}

/* Spinner symbol when the chat message is loading */
.spinner {
border: 4px solid #f3f3f3;
Expand Down Expand Up @@ -1350,7 +1365,7 @@
font-size: large;
}

svg.side-panel-collapse {
svg#side-panel-collapse {
width: 30px;
height: 30px;
}
Expand Down Expand Up @@ -1604,7 +1619,7 @@
padding: 0;
}

svg.side-panel-collapse {
svg#side-panel-collapse {
width: 24px;
height: 24px;
}
Expand Down
5 changes: 2 additions & 3 deletions src/khoj/database/adapters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def subscription_to_state(subscription: Subscription) -> str:
return SubscriptionState.INVALID.value
elif subscription.type == Subscription.Type.TRIAL:
# Trial subscription is valid for 7 days
if datetime.now(tz=timezone.utc) - subscription.created_at > timedelta(days=7):
if datetime.now(tz=timezone.utc) - subscription.created_at > timedelta(days=14):
return SubscriptionState.EXPIRED.value

return SubscriptionState.TRIAL.value
Expand Down Expand Up @@ -573,7 +573,7 @@ async def get_speech_to_text_config():
return await SpeechToTextModelOptions.objects.filter().afirst()

@staticmethod
async def aget_conversation_starters(user: KhojUser):
async def aget_conversation_starters(user: KhojUser, max_results=3):
all_questions = []
if await ReflectiveQuestion.objects.filter(user=user).aexists():
all_questions = await sync_to_async(ReflectiveQuestion.objects.filter(user=user).values_list)(
Expand All @@ -584,7 +584,6 @@ async def aget_conversation_starters(user: KhojUser):
"question", flat=True
)

max_results = 3
all_questions = await sync_to_async(list)(all_questions) # type: ignore
if len(all_questions) < max_results:
return all_questions
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion src/khoj/interface/web/assets/khoj.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* Amber Light scheme (Default) */
/* Can be forced with data-theme="light" */
@import url('https://fonts.googleapis.com/css2?family=Tajawal&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;500;700&display=swap');

[data-theme="light"],
:root:not([data-theme="dark"]) {
Expand All @@ -9,6 +9,7 @@
--primary-focus: rgba(255, 179, 0, 0.125);
--primary-inverse: rgba(0, 0, 0, 0.75);
--background-color: #f5f4f3;
--frosted-background-color: rgba(245, 244, 243, 0.75);
--main-text-color: #475569;
--summer-sun: #fcc50b;
--water: #44b9da;
Expand All @@ -26,6 +27,7 @@
--primary-focus: rgba(255, 179, 0, 0.25);
--primary-inverse: rgba(0, 0, 0, 0.75);
--background-color: #f5f4f3;
--frosted-background-color: rgba(245, 244, 243, 0.75);
--main-text-color: #475569;
--summer-sun: #fcc50b;
--water: #44b9da;
Expand All @@ -42,6 +44,7 @@
--primary-focus: rgba(255, 179, 0, 0.25);
--primary-inverse: rgba(0, 0, 0, 0.75);
--background-color: #f5f4f3;
--frosted-background-color: rgba(245, 244, 243, 0.75);
--main-text-color: #475569;
--summer-sun: #fcc50b;
--water: #44b9da;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 9afb2a1

Please sign in to comment.