mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-06 13:22:12 +00:00
Create Chat Modal for Obsidian Plugin
Merge pull request #196 from debanjum/create-chat-modal-for-obsidian - Set your OpenAI API key in the Khoj Obsidian Settings - Use Modal in Obsidian for Chat - Style Chat Modal combining the Khoj Web interface and Obsidian theme style
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
- [Setup Plugin](#2-Setup-Plugin)
|
- [Setup Plugin](#2-Setup-Plugin)
|
||||||
- [Use](#Use)
|
- [Use](#Use)
|
||||||
- [Search](#search)
|
- [Search](#search)
|
||||||
|
- [Chat](#chat)
|
||||||
- [Find Similar Notes](#find-similar-notes)
|
- [Find Similar Notes](#find-similar-notes)
|
||||||
- [Upgrade](#Upgrade)
|
- [Upgrade](#Upgrade)
|
||||||
- [Upgrade Backend](#1-Upgrade-Backend)
|
- [Upgrade Backend](#1-Upgrade-Backend)
|
||||||
@@ -21,9 +22,14 @@
|
|||||||
- [Implementation](#Implementation)
|
- [Implementation](#Implementation)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- **Natural**: Advanced natural language understanding using Transformer based ML Models
|
- **Search**
|
||||||
- **Local**: Your personal data stays local. All search, indexing is done on your machine[\*](https://github.com/debanjum/khoj#miscellaneous)
|
- **Natural**: Advanced natural language understanding using Transformer based ML Models
|
||||||
- **Incremental**: Incremental search for a fast, search-as-you-type experience
|
- **Local**: Your personal data stays local. All search and indexing is done on your machine. *Unlike chat which requires access to GPT.*
|
||||||
|
- **Incremental**: Incremental search for a fast, search-as-you-type experience
|
||||||
|
- **Chat**
|
||||||
|
- **Faster answers**: Find answers faster and with less effort than search
|
||||||
|
- **Iterative discovery**: Iteratively explore and (re-)discover your notes
|
||||||
|
- **Assisted creativity**: Smoothly weave across answers retrieval and content generation
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
https://user-images.githubusercontent.com/6413477/210486007-36ee3407-e6aa-4185-8a26-b0bfc0a4344f.mp4
|
https://user-images.githubusercontent.com/6413477/210486007-36ee3407-e6aa-4185-8a26-b0bfc0a4344f.mp4
|
||||||
@@ -55,10 +61,21 @@ pip install khoj-assistant && khoj --no-gui
|
|||||||
### 2. Setup Plugin
|
### 2. Setup Plugin
|
||||||
1. Open [Khoj](https://obsidian.md/plugins?id=khoj) from the *Community plugins* tab in Obsidian settings panel
|
1. Open [Khoj](https://obsidian.md/plugins?id=khoj) from the *Community plugins* tab in Obsidian settings panel
|
||||||
2. Click *Install*, then *Enable* on the Khoj plugin page in Obsidian
|
2. Click *Install*, then *Enable* on the Khoj plugin page in Obsidian
|
||||||
|
3. [Optional] To enable Khoj Chat, set your [OpenAI API key](https://platform.openai.com/account/api-keys) in the Khoj plugin settings
|
||||||
|
|
||||||
See [official Obsidian plugin docs](https://help.obsidian.md/Extending+Obsidian/Community+plugins) for details
|
See [official Obsidian plugin docs](https://help.obsidian.md/Extending+Obsidian/Community+plugins) for details
|
||||||
|
|
||||||
## Use
|
## Use
|
||||||
|
### Chat
|
||||||
|
Run *Khoj: Chat* from the [Command Palette](https://help.obsidian.md/Plugins/Command+palette) and ask questions in a natural, conversational style.
|
||||||
|
E.g "When did I file my taxes last year?"
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- *Using Khoj Chat will result in query relevant notes being shared with OpenAI for ChatGPT to respond.*
|
||||||
|
- *To use Khoj Chat, ensure you've set your [OpenAI API key](https://platform.openai.com/account/api-keys) in the Khoj plugin settings.*
|
||||||
|
|
||||||
|
See [[https://github.com/debanjum/khoj/tree/master/#Khoj-Chat][Khoj Chat]] for more details
|
||||||
|
|
||||||
### Search
|
### Search
|
||||||
Click the *Khoj search* icon 🔎 on the [Ribbon](https://help.obsidian.md/User+interface/Workspace/Ribbon) or run *Khoj: Search* from the [Command Palette](https://help.obsidian.md/Plugins/Command+palette)
|
Click the *Khoj search* icon 🔎 on the [Ribbon](https://help.obsidian.md/User+interface/Workspace/Ribbon) or run *Khoj: Search* from the [Command Palette](https://help.obsidian.md/Plugins/Command+palette)
|
||||||
|
|
||||||
|
|||||||
130
src/interface/obsidian/src/chat_modal.ts
Normal file
130
src/interface/obsidian/src/chat_modal.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { App, Modal, request, Setting } from 'obsidian';
|
||||||
|
import { KhojSetting } from 'src/settings';
|
||||||
|
|
||||||
|
|
||||||
|
export class KhojChatModal extends Modal {
|
||||||
|
result: string;
|
||||||
|
setting: KhojSetting;
|
||||||
|
|
||||||
|
constructor(app: App, setting: KhojSetting) {
|
||||||
|
super(app);
|
||||||
|
this.setting = setting;
|
||||||
|
|
||||||
|
// Register Modal Keybindings to send user message
|
||||||
|
this.scope.register([], 'Enter', async () => {
|
||||||
|
// Get text in chat input elmenet
|
||||||
|
let input_el = <HTMLInputElement>this.contentEl.getElementsByClassName("khoj-chat-input")[0];
|
||||||
|
|
||||||
|
// Clear text after extracting message to send
|
||||||
|
let user_message = input_el.value;
|
||||||
|
input_el.value = "";
|
||||||
|
|
||||||
|
// Get and render chat response to user message
|
||||||
|
await this.getChatResponse(user_message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async onOpen() {
|
||||||
|
let { contentEl } = this;
|
||||||
|
contentEl.addClass("khoj-chat");
|
||||||
|
|
||||||
|
// Add title to the Khoj Chat modal
|
||||||
|
contentEl.createEl("h1", ({ attr: { id: "khoj-chat-title" }, text: "Khoj Chat" }));
|
||||||
|
|
||||||
|
// Create area for chat logs
|
||||||
|
contentEl.createDiv({ attr: { id: "khoj-chat-body", class: "khoj-chat-body" } });
|
||||||
|
|
||||||
|
// Get conversation history from Khoj backend
|
||||||
|
let chatUrl = `${this.setting.khojUrl}/api/chat?`;
|
||||||
|
let response = await request(chatUrl);
|
||||||
|
let chatLogs = JSON.parse(response).response;
|
||||||
|
chatLogs.forEach((chatLog: any) => {
|
||||||
|
this.renderMessageWithReferences(chatLog.message, chatLog.by, chatLog.context, new Date(chatLog.created));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add chat input field
|
||||||
|
contentEl.createEl("input",
|
||||||
|
{
|
||||||
|
attr: {
|
||||||
|
type: "text",
|
||||||
|
id: "khoj-chat-input",
|
||||||
|
autofocus: "autofocus",
|
||||||
|
placeholder: "Chat with Khoj 🦅 [Hit Enter to send message]",
|
||||||
|
class: "khoj-chat-input option"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addEventListener('change', (event) => { this.result = (<HTMLInputElement>event.target).value });
|
||||||
|
|
||||||
|
// Scroll to bottom of modal, till the send message input box
|
||||||
|
this.modalEl.scrollTop = this.modalEl.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateReference(messageEl: any, reference: string, index: number) {
|
||||||
|
// Generate HTML for Chat Reference
|
||||||
|
// `<sup><abbr title="${escaped_ref}" tabindex="0">${index}</abbr></sup>`;
|
||||||
|
let escaped_ref = reference.replace(/"/g, "\\\"")
|
||||||
|
return messageEl.createEl("sup").createEl("abbr", {
|
||||||
|
attr: {
|
||||||
|
title: escaped_ref,
|
||||||
|
tabindex: "0",
|
||||||
|
},
|
||||||
|
text: `[${index}] `,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMessageWithReferences(message: string, sender: string, context?: [string], dt?: Date) {
|
||||||
|
let messageEl = this.renderMessage(message, sender, dt);
|
||||||
|
if (context && !!messageEl) {
|
||||||
|
context.map((reference, index) => this.generateReference(messageEl, reference, index+1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMessage(message: string, sender: string, dt?: Date): Element | null {
|
||||||
|
let message_time = this.formatDate(dt ?? new Date());
|
||||||
|
let emojified_sender = sender == "khoj" ? "🦅 Khoj" : "🤔 You";
|
||||||
|
|
||||||
|
// Append message to conversation history HTML element.
|
||||||
|
// The chat logs should display above the message input box to follow standard UI semantics
|
||||||
|
let chat_body_el = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
|
||||||
|
let chat_message_el = chat_body_el.createDiv({
|
||||||
|
attr: {
|
||||||
|
"data-meta": `${emojified_sender} at ${message_time}`,
|
||||||
|
class: `khoj-chat-message ${sender}`
|
||||||
|
},
|
||||||
|
}).createDiv({
|
||||||
|
attr: {
|
||||||
|
class: `khoj-chat-message-text ${sender}`
|
||||||
|
},
|
||||||
|
text: `${message}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Scroll to bottom after inserting chat messages
|
||||||
|
this.modalEl.scrollTop = this.modalEl.scrollHeight;
|
||||||
|
|
||||||
|
return chat_message_el
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDate(date: Date): string {
|
||||||
|
// Format date in HH:MM, DD MMM YYYY format
|
||||||
|
let time_string = date.toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit', hour12: false });
|
||||||
|
let date_string = date.toLocaleString('en-IN', { year: 'numeric', month: 'short', day: '2-digit' }).replace(/-/g, ' ');
|
||||||
|
return `${time_string}, ${date_string}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getChatResponse(query: string | undefined | null): Promise<void> {
|
||||||
|
// Exit if query is empty
|
||||||
|
if (!query || query === "") return;
|
||||||
|
|
||||||
|
// Render user query as chat message
|
||||||
|
this.renderMessage(query, "you");
|
||||||
|
|
||||||
|
// Get chat response from Khoj backend
|
||||||
|
let encodedQuery = encodeURIComponent(query);
|
||||||
|
let chatUrl = `${this.setting.khojUrl}/api/chat?q=${encodedQuery}`;
|
||||||
|
let response = await request(chatUrl);
|
||||||
|
let data = JSON.parse(response);
|
||||||
|
|
||||||
|
// Render Khoj response as chat message
|
||||||
|
this.renderMessage(data.response, "khoj");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Notice, Plugin } from 'obsidian';
|
import { Notice, Plugin } from 'obsidian';
|
||||||
import { KhojSetting, KhojSettingTab, DEFAULT_SETTINGS } from 'src/settings'
|
import { KhojSetting, KhojSettingTab, DEFAULT_SETTINGS } from 'src/settings'
|
||||||
import { KhojModal } from 'src/modal'
|
import { KhojSearchModal } from 'src/search_modal'
|
||||||
|
import { KhojChatModal } from 'src/chat_modal'
|
||||||
import { configureKhojBackend } from './utils';
|
import { configureKhojBackend } from './utils';
|
||||||
|
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ export default class Khoj extends Plugin {
|
|||||||
name: 'Search',
|
name: 'Search',
|
||||||
checkCallback: (checking) => {
|
checkCallback: (checking) => {
|
||||||
if (!checking && this.settings.connectedToBackend)
|
if (!checking && this.settings.connectedToBackend)
|
||||||
new KhojModal(this.app, this.settings).open();
|
new KhojSearchModal(this.app, this.settings).open();
|
||||||
return this.settings.connectedToBackend;
|
return this.settings.connectedToBackend;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -27,16 +28,27 @@ export default class Khoj extends Plugin {
|
|||||||
name: 'Find similar notes',
|
name: 'Find similar notes',
|
||||||
editorCheckCallback: (checking) => {
|
editorCheckCallback: (checking) => {
|
||||||
if (!checking && this.settings.connectedToBackend)
|
if (!checking && this.settings.connectedToBackend)
|
||||||
new KhojModal(this.app, this.settings, true).open();
|
new KhojSearchModal(this.app, this.settings, true).open();
|
||||||
return this.settings.connectedToBackend;
|
return this.settings.connectedToBackend;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add chat command. It can be triggered from anywhere
|
||||||
|
this.addCommand({
|
||||||
|
id: 'chat',
|
||||||
|
name: 'Chat',
|
||||||
|
checkCallback: (checking) => {
|
||||||
|
if (!checking && this.settings.connectedToBackend && !!this.settings.openaiApiKey)
|
||||||
|
new KhojChatModal(this.app, this.settings).open();
|
||||||
|
return !!this.settings.openaiApiKey;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Create an icon in the left ribbon.
|
// Create an icon in the left ribbon.
|
||||||
this.addRibbonIcon('search', 'Khoj', (_: MouseEvent) => {
|
this.addRibbonIcon('search', 'Khoj', (_: MouseEvent) => {
|
||||||
// Called when the user clicks the icon.
|
// Called when the user clicks the icon.
|
||||||
this.settings.connectedToBackend
|
this.settings.connectedToBackend
|
||||||
? new KhojModal(this.app, this.settings).open()
|
? new KhojSearchModal(this.app, this.settings).open()
|
||||||
: new Notice(`❗️Ensure Khoj backend is running and Khoj URL is pointing to it in the plugin settings`);
|
: new Notice(`❗️Ensure Khoj backend is running and Khoj URL is pointing to it in the plugin settings`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -59,5 +71,5 @@ export default class Khoj extends Plugin {
|
|||||||
await configureKhojBackend(this.app.vault, this.settings, false);
|
await configureKhojBackend(this.app.vault, this.settings, false);
|
||||||
}
|
}
|
||||||
this.saveData(this.settings);
|
this.saveData(this.settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export interface SearchResult {
|
|||||||
file: string;
|
file: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KhojModal extends SuggestModal<SearchResult> {
|
export class KhojSearchModal extends SuggestModal<SearchResult> {
|
||||||
setting: KhojSetting;
|
setting: KhojSetting;
|
||||||
rerank: boolean = false;
|
rerank: boolean = false;
|
||||||
find_similar_notes: boolean;
|
find_similar_notes: boolean;
|
||||||
@@ -2,6 +2,7 @@ import { App, Notice, PluginSettingTab, request, Setting } from 'obsidian';
|
|||||||
import Khoj from 'src/main';
|
import Khoj from 'src/main';
|
||||||
|
|
||||||
export interface KhojSetting {
|
export interface KhojSetting {
|
||||||
|
openaiApiKey: string;
|
||||||
resultsCount: number;
|
resultsCount: number;
|
||||||
khojUrl: string;
|
khojUrl: string;
|
||||||
connectedToBackend: boolean;
|
connectedToBackend: boolean;
|
||||||
@@ -13,6 +14,7 @@ export const DEFAULT_SETTINGS: KhojSetting = {
|
|||||||
khojUrl: 'http://localhost:8000',
|
khojUrl: 'http://localhost:8000',
|
||||||
connectedToBackend: false,
|
connectedToBackend: false,
|
||||||
autoConfigure: true,
|
autoConfigure: true,
|
||||||
|
openaiApiKey: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KhojSettingTab extends PluginSettingTab {
|
export class KhojSettingTab extends PluginSettingTab {
|
||||||
@@ -41,7 +43,16 @@ export class KhojSettingTab extends PluginSettingTab {
|
|||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
containerEl.firstElementChild?.setText(this.getBackendStatusMessage());
|
containerEl.firstElementChild?.setText(this.getBackendStatusMessage());
|
||||||
}));
|
}));
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
|
.setName('OpenAI API Key')
|
||||||
|
.setDesc('Your OpenAI API Key for Khoj Chat')
|
||||||
|
.addText(text => text
|
||||||
|
.setValue(`${this.plugin.settings.openaiApiKey}`)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.openaiApiKey = value.trim();
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
}));
|
||||||
|
new Setting(containerEl)
|
||||||
.setName('Results Count')
|
.setName('Results Count')
|
||||||
.setDesc('The number of search results to show')
|
.setDesc('The number of search results to show')
|
||||||
.addSlider(slider => slider
|
.addSlider(slider => slider
|
||||||
@@ -110,7 +121,7 @@ export class KhojSettingTab extends PluginSettingTab {
|
|||||||
|
|
||||||
getBackendStatusMessage() {
|
getBackendStatusMessage() {
|
||||||
return !this.plugin.settings.connectedToBackend
|
return !this.plugin.settings.connectedToBackend
|
||||||
? '❗Disconnected from Khoj backend. Ensure Khoj backend is running and Khoj URL is correctly set below.'
|
? '❗Disconnected from Khoj backend. Ensure Khoj backend is running and Khoj URL is correctly set below.'
|
||||||
: '✅ Connected to Khoj backend.';
|
: '✅ Connected to Khoj backend.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,10 +29,11 @@ export async function configureKhojBackend(vault: Vault, setting: KhojSetting, n
|
|||||||
|
|
||||||
// Set index name from the path of the current vault
|
// Set index name from the path of the current vault
|
||||||
let indexName = getVaultAbsolutePath(vault).replace(/\//g, '_').replace(/ /g, '_');
|
let indexName = getVaultAbsolutePath(vault).replace(/\//g, '_').replace(/ /g, '_');
|
||||||
// Get default index directory from khoj backend
|
// Get default config fields from khoj backend
|
||||||
let khojDefaultIndexDirectory = await request(`${khojConfigUrl}/default`)
|
let defaultConfig = await request(`${khojConfigUrl}/default`).then(response => JSON.parse(response));
|
||||||
.then(response => JSON.parse(response))
|
let khojDefaultIndexDirectory = getIndexDirectoryFromBackendConfig(defaultConfig["content-type"]["markdown"]["embeddings-file"]);
|
||||||
.then(data => { return getIndexDirectoryFromBackendConfig(data); });
|
let khojDefaultChatDirectory = getIndexDirectoryFromBackendConfig(defaultConfig["processor"]["conversation"]["conversation-logfile"]);
|
||||||
|
let khojDefaultChatModelName = defaultConfig["processor"]["conversation"]["model"];
|
||||||
|
|
||||||
// Get current config if khoj backend configured, else get default config from khoj backend
|
// Get current config if khoj backend configured, else get default config from khoj backend
|
||||||
await request(khoj_already_configured ? khojConfigUrl : `${khojConfigUrl}/default`)
|
await request(khoj_already_configured ? khojConfigUrl : `${khojConfigUrl}/default`)
|
||||||
@@ -49,14 +50,7 @@ export async function configureKhojBackend(vault: Vault, setting: KhojSetting, n
|
|||||||
"compressed-jsonl": `${khojDefaultIndexDirectory}/${indexName}.jsonl.gz`,
|
"compressed-jsonl": `${khojDefaultIndexDirectory}/${indexName}.jsonl.gz`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Disable khoj processors, as not required
|
|
||||||
delete data["processor"];
|
|
||||||
|
|
||||||
// Save new config and refresh index on khoj backend
|
|
||||||
updateKhojBackend(setting.khojUrl, data);
|
|
||||||
console.log(`Khoj: Created khoj backend config:\n${JSON.stringify(data)}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Else if khoj config has no markdown content config
|
// Else if khoj config has no markdown content config
|
||||||
else if (!data["content-type"]["markdown"]) {
|
else if (!data["content-type"]["markdown"]) {
|
||||||
// Add markdown config to khoj content-type config
|
// Add markdown config to khoj content-type config
|
||||||
@@ -67,28 +61,59 @@ export async function configureKhojBackend(vault: Vault, setting: KhojSetting, n
|
|||||||
"embeddings-file": `${khojDefaultIndexDirectory}/${indexName}.pt`,
|
"embeddings-file": `${khojDefaultIndexDirectory}/${indexName}.pt`,
|
||||||
"compressed-jsonl": `${khojDefaultIndexDirectory}/${indexName}.jsonl.gz`,
|
"compressed-jsonl": `${khojDefaultIndexDirectory}/${indexName}.jsonl.gz`,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save updated config and refresh index on khoj backend
|
|
||||||
updateKhojBackend(setting.khojUrl, data);
|
|
||||||
console.log(`Khoj: Added markdown config to khoj backend config:\n${JSON.stringify(data["content-type"])}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Else if khoj is not configured to index markdown files in configured obsidian vault
|
// Else if khoj is not configured to index markdown files in configured obsidian vault
|
||||||
else if (data["content-type"]["markdown"]["input-filter"].length != 1 ||
|
else if (data["content-type"]["markdown"]["input-filter"].length != 1 ||
|
||||||
data["content-type"]["markdown"]["input-filter"][0] !== mdInVault) {
|
data["content-type"]["markdown"]["input-filter"][0] !== mdInVault) {
|
||||||
// Update markdown config in khoj content-type config
|
// Update markdown config in khoj content-type config
|
||||||
// Set markdown config to only index markdown files in configured obsidian vault
|
// Set markdown config to only index markdown files in configured obsidian vault
|
||||||
let khojIndexDirectory = getIndexDirectoryFromBackendConfig(data);
|
let khojIndexDirectory = getIndexDirectoryFromBackendConfig(data["content-type"]["markdown"]["embeddings-file"]);
|
||||||
data["content-type"]["markdown"] = {
|
data["content-type"]["markdown"] = {
|
||||||
"input-filter": [mdInVault],
|
"input-filter": [mdInVault],
|
||||||
"input-files": null,
|
"input-files": null,
|
||||||
"embeddings-file": `${khojIndexDirectory}/${indexName}.pt`,
|
"embeddings-file": `${khojIndexDirectory}/${indexName}.pt`,
|
||||||
"compressed-jsonl": `${khojIndexDirectory}/${indexName}.jsonl.gz`,
|
"compressed-jsonl": `${khojIndexDirectory}/${indexName}.jsonl.gz`,
|
||||||
}
|
}
|
||||||
// Save updated config and refresh index on khoj backend
|
|
||||||
updateKhojBackend(setting.khojUrl, data);
|
|
||||||
console.log(`Khoj: Updated markdown config in khoj backend config:\n${JSON.stringify(data["content-type"]["markdown"])}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If OpenAI API key not set in Khoj plugin settings
|
||||||
|
if (!setting.openaiApiKey) {
|
||||||
|
// Disable khoj processors, as not required
|
||||||
|
delete data["processor"];
|
||||||
|
}
|
||||||
|
// Else if khoj backend not configured yet
|
||||||
|
else if (!khoj_already_configured || !data["processor"]) {
|
||||||
|
data["processor"] = {
|
||||||
|
"conversation": {
|
||||||
|
"conversation-logfile": `${khojDefaultChatDirectory}/conversation.json`,
|
||||||
|
"model": khojDefaultChatModelName,
|
||||||
|
"openai-api-key": setting.openaiApiKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Else if khoj config has no conversation processor config
|
||||||
|
else if (!data["processor"]["conversation"]) {
|
||||||
|
data["processor"]["conversation"] = {
|
||||||
|
"conversation-logfile": `${khojDefaultChatDirectory}/conversation.json`,
|
||||||
|
"model": khojDefaultChatModelName,
|
||||||
|
"openai-api-key": setting.openaiApiKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Else if khoj is not configured with OpenAI API key from khoj plugin settings
|
||||||
|
else if (data["processor"]["conversation"]["openai-api-key"] !== setting.openaiApiKey) {
|
||||||
|
data["processor"]["conversation"] = {
|
||||||
|
"conversation-logfile": data["processor"]["conversation"]["conversation-logfile"],
|
||||||
|
"model": data["procesor"]["conversation"]["model"],
|
||||||
|
"openai-api-key": setting.openaiApiKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save updated config and refresh index on khoj backend
|
||||||
|
updateKhojBackend(setting.khojUrl, data);
|
||||||
|
if (!khoj_already_configured)
|
||||||
|
console.log(`Khoj: Created khoj backend config:\n${JSON.stringify(data)}`)
|
||||||
|
else
|
||||||
|
console.log(`Khoj: Updated khoj backend config:\n${JSON.stringify(data)}`)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (notify)
|
if (notify)
|
||||||
@@ -111,6 +136,6 @@ export async function updateKhojBackend(khojUrl: string, khojConfig: Object) {
|
|||||||
.then(_ => request(`${khojUrl}/api/update?t=markdown`));
|
.then(_ => request(`${khojUrl}/api/update?t=markdown`));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIndexDirectoryFromBackendConfig(khojConfig: any) {
|
function getIndexDirectoryFromBackendConfig(filepath: string) {
|
||||||
return khojConfig["content-type"]["markdown"]["embeddings-file"].split("/").slice(0, -1).join("/");
|
return filepath.split("/").slice(0, -1).join("/");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,3 +6,142 @@ available in the app when your plugin is enabled.
|
|||||||
If your plugin does not need CSS, delete this file.
|
If your plugin does not need CSS, delete this file.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--khoj-chat-blue: #017eff;
|
||||||
|
--khoj-chat-dark-grey: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.khoj-chat {
|
||||||
|
display: grid;
|
||||||
|
background: var(--background-primary);
|
||||||
|
color: var(--text-normal);
|
||||||
|
text-align: center;
|
||||||
|
font-family: roboto, karma, segoe ui, sans-serif;
|
||||||
|
font-size: var(--font-ui-large);
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
.khoj-chat > * {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#khoj-chat-title {
|
||||||
|
font-weight: 200;
|
||||||
|
color: var(--khoj-chat-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#khoj-chat-body {
|
||||||
|
font-size: var(--font-ui-medium);
|
||||||
|
margin: 0px;
|
||||||
|
line-height: 20px;
|
||||||
|
overflow-y: scroll; /* Make chat body scroll to see history */
|
||||||
|
}
|
||||||
|
/* add chat metatdata to bottom of bubble */
|
||||||
|
.khoj-chat-message::after {
|
||||||
|
content: attr(data-meta);
|
||||||
|
display: block;
|
||||||
|
font-size: var(--font-ui-smaller);
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin: -12px 7px 0 -5px;
|
||||||
|
}
|
||||||
|
/* move message by khoj to left */
|
||||||
|
.khoj-chat-message.khoj {
|
||||||
|
margin-left: auto;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
/* move message by you to right */
|
||||||
|
.khoj-chat-message.you {
|
||||||
|
margin-right: auto;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
/* basic style chat message text */
|
||||||
|
.khoj-chat-message-text {
|
||||||
|
margin: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 80%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
/* color chat bubble by khoj blue */
|
||||||
|
.khoj-chat-message-text.khoj {
|
||||||
|
color: var(--text-on-accent);
|
||||||
|
background: var(--khoj-chat-blue);
|
||||||
|
margin-left: auto;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
/* add left protrusion to khoj chat bubble */
|
||||||
|
.khoj-chat-message-text.khoj:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
left: -7px;
|
||||||
|
border: 10px solid transparent;
|
||||||
|
border-top-color: var(--khoj-chat-blue);
|
||||||
|
border-bottom: 0;
|
||||||
|
transform: rotate(-60deg);
|
||||||
|
}
|
||||||
|
/* color chat bubble by you dark grey */
|
||||||
|
.khoj-chat-message-text.you {
|
||||||
|
color: var(--text-on-accent);
|
||||||
|
background: var(--khoj-chat-dark-grey);
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
/* add right protrusion to you chat bubble */
|
||||||
|
.khoj-chat-message-text.you:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 91%;
|
||||||
|
right: -2px;
|
||||||
|
border: 10px solid transparent;
|
||||||
|
border-left-color: var(--khoj-chat-dark-grey);
|
||||||
|
border-right: 0;
|
||||||
|
margin-top: -10px;
|
||||||
|
transform: rotate(-60deg)
|
||||||
|
}
|
||||||
|
|
||||||
|
#khoj-chat-footer {
|
||||||
|
padding: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(70px, 100%);
|
||||||
|
grid-column-gap: 10px;
|
||||||
|
grid-row-gap: 10px;
|
||||||
|
}
|
||||||
|
#khoj-chat-footer > * {
|
||||||
|
padding: 15px;
|
||||||
|
background: #f9fafc
|
||||||
|
}
|
||||||
|
#khoj-chat-input.option:hover {
|
||||||
|
box-shadow: 0 0 11px var(--background-modifier-box-shadow);
|
||||||
|
}
|
||||||
|
#khoj-chat-input {
|
||||||
|
font-size: var(--font-ui-medium);
|
||||||
|
padding: 25px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (pointer: coarse), (hover: none) {
|
||||||
|
#khoj-chat-body.abbr[title] {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 4px; /* space references out to ease tapping */
|
||||||
|
}
|
||||||
|
#khoj-chat-body.abbr[title]:focus:after {
|
||||||
|
content: attr(title);
|
||||||
|
|
||||||
|
/* position tooltip */
|
||||||
|
position: absolute;
|
||||||
|
left: 16px; /* open tooltip to right of ref link, instead of on top of it */
|
||||||
|
width: auto;
|
||||||
|
z-index: 1; /* show tooltip above chat messages */
|
||||||
|
|
||||||
|
/* style tooltip */
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
color: var(--text-muted);
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: 1px 1px 4px 0 var(--background-modifier-box-shadow);
|
||||||
|
font-size: var(--font-ui-small);
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user