mirror of
https://github.com/khoj-ai/khoj.git
synced 2025-02-17 08:04:21 +00:00
Improve Syncing Obsidian Vault, Invalidate Static Assets in Browser Cache in Web Client (#657)
- Improve - Only send files modified since their last sync for indexing on server from the Obsidian client - Fix - Invalidate static asset browser cache in Web client when Khoj version changes
This commit is contained in:
commit
8855529637
13 changed files with 83 additions and 31 deletions
|
@ -106,6 +106,7 @@ function filenameToMimeType (filename) {
|
||||||
case 'org':
|
case 'org':
|
||||||
return 'text/org';
|
return 'text/org';
|
||||||
default:
|
default:
|
||||||
|
console.warn(`Unknown file type: ${extension}. Defaulting to text/plain.`);
|
||||||
return 'text/plain';
|
return 'text/plain';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,9 +44,7 @@ export default class Khoj extends Plugin {
|
||||||
// Add scheduled job to update index every 60 minutes
|
// Add scheduled job to update index every 60 minutes
|
||||||
this.indexingTimer = setInterval(async () => {
|
this.indexingTimer = setInterval(async () => {
|
||||||
if (this.settings.autoConfigure) {
|
if (this.settings.autoConfigure) {
|
||||||
this.settings.lastSyncedFiles = await updateContentIndex(
|
this.settings.lastSync = await updateContentIndex(this.app.vault, this.settings, this.settings.lastSync);
|
||||||
this.app.vault, this.settings, this.settings.lastSyncedFiles
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}, 60 * 60 * 1000);
|
}, 60 * 60 * 1000);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ export interface KhojSetting {
|
||||||
khojApiKey: string;
|
khojApiKey: string;
|
||||||
connectedToBackend: boolean;
|
connectedToBackend: boolean;
|
||||||
autoConfigure: boolean;
|
autoConfigure: boolean;
|
||||||
lastSyncedFiles: TFile[];
|
lastSync: Map<TFile, number>;
|
||||||
userEmail: string;
|
userEmail: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ export const DEFAULT_SETTINGS: KhojSetting = {
|
||||||
khojApiKey: '',
|
khojApiKey: '',
|
||||||
connectedToBackend: false,
|
connectedToBackend: false,
|
||||||
autoConfigure: true,
|
autoConfigure: true,
|
||||||
lastSyncedFiles: [],
|
lastSync: new Map(),
|
||||||
userEmail: '',
|
userEmail: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,8 +132,8 @@ export class KhojSettingTab extends PluginSettingTab {
|
||||||
}, 300);
|
}, 300);
|
||||||
this.plugin.registerInterval(progress_indicator);
|
this.plugin.registerInterval(progress_indicator);
|
||||||
|
|
||||||
this.plugin.settings.lastSyncedFiles = await updateContentIndex(
|
this.plugin.settings.lastSync = await updateContentIndex(
|
||||||
this.app.vault, this.plugin.settings, this.plugin.settings.lastSyncedFiles, true
|
this.app.vault, this.plugin.settings, this.plugin.settings.lastSync, true
|
||||||
);
|
);
|
||||||
new Notice('✅ Updated Khoj index.');
|
new Notice('✅ Updated Khoj index.');
|
||||||
|
|
||||||
|
|
|
@ -28,17 +28,43 @@ function fileExtensionToMimeType (extension: string): string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateContentIndex(vault: Vault, setting: KhojSetting, lastSyncedFiles: TFile[], regenerate: boolean = false): Promise<TFile[]> {
|
function filenameToMimeType (filename: TFile): string {
|
||||||
|
switch (filename.extension) {
|
||||||
|
case 'pdf':
|
||||||
|
return 'application/pdf';
|
||||||
|
case 'png':
|
||||||
|
return 'image/png';
|
||||||
|
case 'jpg':
|
||||||
|
case 'jpeg':
|
||||||
|
return 'image/jpeg';
|
||||||
|
case 'md':
|
||||||
|
case 'markdown':
|
||||||
|
return 'text/markdown';
|
||||||
|
case 'org':
|
||||||
|
return 'text/org';
|
||||||
|
default:
|
||||||
|
console.warn(`Unknown file type: ${filename.extension}. Defaulting to text/plain.`);
|
||||||
|
return 'text/plain';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateContentIndex(vault: Vault, setting: KhojSetting, lastSync: Map<TFile, number>, regenerate: boolean = false): Promise<Map<TFile, number>> {
|
||||||
// Get all markdown, pdf files in the vault
|
// Get all markdown, pdf files in the vault
|
||||||
console.log(`Khoj: Updating Khoj content index...`)
|
console.log(`Khoj: Updating Khoj content index...`)
|
||||||
const files = vault.getFiles().filter(file => file.extension === 'md' || file.extension === 'markdown' || file.extension === 'pdf');
|
const files = vault.getFiles().filter(file => file.extension === 'md' || file.extension === 'markdown' || file.extension === 'pdf');
|
||||||
const binaryFileTypes = ['pdf']
|
const binaryFileTypes = ['pdf']
|
||||||
let countOfFilesToIndex = 0;
|
let countOfFilesToIndex = 0;
|
||||||
let countOfFilesToDelete = 0;
|
let countOfFilesToDelete = 0;
|
||||||
|
lastSync = lastSync.size > 0 ? lastSync : new Map<TFile, number>();
|
||||||
|
|
||||||
// Add all files to index as multipart form data
|
// Add all files to index as multipart form data
|
||||||
const fileData = [];
|
const fileData = [];
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
// Only push files that have been modified since last sync if not regenerating
|
||||||
|
if (!regenerate && file.stat.mtime < (lastSync.get(file) ?? 0)){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
countOfFilesToIndex++;
|
countOfFilesToIndex++;
|
||||||
const encoding = binaryFileTypes.includes(file.extension) ? "binary" : "utf8";
|
const encoding = binaryFileTypes.includes(file.extension) ? "binary" : "utf8";
|
||||||
const mimeType = fileExtensionToMimeType(file.extension) + (encoding === "utf8" ? "; charset=UTF-8" : "");
|
const mimeType = fileExtensionToMimeType(file.extension) + (encoding === "utf8" ? "; charset=UTF-8" : "");
|
||||||
|
@ -47,14 +73,18 @@ export async function updateContentIndex(vault: Vault, setting: KhojSetting, las
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add any previously synced files to be deleted to multipart form data
|
// Add any previously synced files to be deleted to multipart form data
|
||||||
for (const lastSyncedFile of lastSyncedFiles) {
|
let filesToDelete: TFile[] = [];
|
||||||
|
for (const lastSyncedFile of lastSync.keys()) {
|
||||||
if (!files.includes(lastSyncedFile)) {
|
if (!files.includes(lastSyncedFile)) {
|
||||||
countOfFilesToDelete++;
|
countOfFilesToDelete++;
|
||||||
fileData.push({blob: new Blob([]), path: lastSyncedFile.path});
|
let fileObj = new Blob([""], { type: filenameToMimeType(lastSyncedFile) });
|
||||||
|
fileData.push({blob: fileObj, path: lastSyncedFile.path});
|
||||||
|
filesToDelete.push(lastSyncedFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate through all indexable files in vault, 1000 at a time
|
// Iterate through all indexable files in vault, 1000 at a time
|
||||||
|
let responses: string[] = [];
|
||||||
let error_message = null;
|
let error_message = null;
|
||||||
for (let i = 0; i < fileData.length; i += 1000) {
|
for (let i = 0; i < fileData.length; i += 1000) {
|
||||||
const filesGroup = fileData.slice(i, i + 1000);
|
const filesGroup = fileData.slice(i, i + 1000);
|
||||||
|
@ -79,16 +109,31 @@ export async function updateContentIndex(vault: Vault, setting: KhojSetting, las
|
||||||
} else {
|
} else {
|
||||||
error_message = `❗️Failed to sync your content with Khoj server. Raise issue on Khoj Discord or Github\nError: ${response.statusText}`;
|
error_message = `❗️Failed to sync your content with Khoj server. Raise issue on Khoj Discord or Github\nError: ${response.statusText}`;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
responses.push(await response.text());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update last sync time for each successfully indexed file
|
||||||
|
files
|
||||||
|
.filter(file => responses.find(response => response.includes(file.path)))
|
||||||
|
.reduce((newSync, file) => {
|
||||||
|
newSync.set(file, new Date().getTime());
|
||||||
|
return newSync;
|
||||||
|
}, lastSync);
|
||||||
|
|
||||||
|
// Remove files that were deleted from last sync
|
||||||
|
filesToDelete
|
||||||
|
.filter(file => responses.find(response => response.includes(file.path)))
|
||||||
|
.forEach(file => lastSync.delete(file));
|
||||||
|
|
||||||
if (error_message) {
|
if (error_message) {
|
||||||
new Notice(error_message);
|
new Notice(error_message);
|
||||||
} else {
|
} else {
|
||||||
console.log(`✅ Refreshed Khoj content index. Updated: ${countOfFilesToIndex} files, Deleted: ${countOfFilesToDelete} files.`);
|
console.log(`✅ Refreshed Khoj content index. Updated: ${countOfFilesToIndex} files, Deleted: ${countOfFilesToDelete} files.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return files;
|
return lastSync;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createNote(name: string, newLeaf = false): Promise<void> {
|
export async function createNote(name: string, newLeaf = false): Promise<void> {
|
||||||
|
|
|
@ -3,17 +3,17 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
|
||||||
<link rel="icon" type="image/png" sizes="128x128" href="/static/assets/icons/favicon-128x128.png">
|
<link rel="icon" type="image/png" sizes="128x128" href="/static/assets/icons/favicon-128x128.png?v={{ khoj_version }}">
|
||||||
<title>Khoj - Settings</title>
|
<title>Khoj - Settings</title>
|
||||||
<link rel="stylesheet" href="/static/assets/pico.min.css">
|
<link rel="stylesheet" href="/static/assets/pico.min.css?v={{ khoj_version }}">
|
||||||
<link rel="stylesheet" href="/static/assets/khoj.css">
|
<link rel="stylesheet" href="/static/assets/khoj.css?v={{ khoj_version }}">
|
||||||
<script
|
<script
|
||||||
integrity="sha384-05IkdNHoAlkhrFVUCCN805WC/h4mcI98GUBssmShF2VJAXKyZTrO/TmJ+4eBo0Cy"
|
integrity="sha384-05IkdNHoAlkhrFVUCCN805WC/h4mcI98GUBssmShF2VJAXKyZTrO/TmJ+4eBo0Cy"
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.13/js/intlTelInput.min.js"></script>
|
src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.13/js/intlTelInput.min.js"></script>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.13/css/intlTelInput.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.13/css/intlTelInput.css">
|
||||||
</head>
|
</head>
|
||||||
<script type="text/javascript" src="/static/assets/utils.js"></script>
|
<script type="text/javascript" src="/static/assets/utils.js?v={{ khoj_version }}"></script>
|
||||||
<body class="khoj-configure">
|
<body class="khoj-configure">
|
||||||
<div class="khoj-header-wrapper">
|
<div class="khoj-header-wrapper">
|
||||||
<div class="filler"></div>
|
<div class="filler"></div>
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
|
||||||
<title>Khoj - Chat</title>
|
<title>Khoj - Chat</title>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" sizes="128x128" href="/static/assets/icons/favicon-128x128.png">
|
<link rel="icon" type="image/png" sizes="128x128" href="/static/assets/icons/favicon-128x128.png?v={{ khoj_version }}">
|
||||||
<link rel="manifest" href="/static/khoj.webmanifest">
|
<link rel="manifest" href="/static/khoj.webmanifest?v={{ khoj_version }}">
|
||||||
<link rel="stylesheet" href="/static/assets/khoj.css">
|
<link rel="stylesheet" href="/static/assets/khoj.css?v={{ khoj_version }}">
|
||||||
</head>
|
</head>
|
||||||
<script type="text/javascript" src="/static/assets/utils.js"></script>
|
<script type="text/javascript" src="/static/assets/utils.js?v={{ khoj_version }}"></script>
|
||||||
<script type="text/javascript" src="/static/assets/markdown-it.min.js"></script>
|
<script type="text/javascript" src="/static/assets/markdown-it.min.js?v={{ khoj_version }}"></script>
|
||||||
<script>
|
<script>
|
||||||
let welcome_message = `
|
let welcome_message = `
|
||||||
Hi, I am Khoj, your open, personal AI 👋🏽. I can help:
|
Hi, I am Khoj, your open, personal AI 👋🏽. I can help:
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2 class="section-title">
|
<h2 class="section-title">
|
||||||
<img class="card-icon" src="/static/assets/icons/computer.png" alt="files">
|
<img class="card-icon" src="/static/assets/icons/computer.png?v={{ khoj_version }}" alt="files">
|
||||||
<span class="card-title-text">Files</span>
|
<span class="card-title-text">Files</span>
|
||||||
<div class="instructions">
|
<div class="instructions">
|
||||||
<p class="card-description">Manage files from your computer</p>
|
<p class="card-description">Manage files from your computer</p>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2 class="section-title">
|
<h2 class="section-title">
|
||||||
<img class="card-icon" src="/static/assets/icons/github.svg" alt="Github">
|
<img class="card-icon" src="/static/assets/icons/github.svg?v={{ khoj_version }}" alt="Github">
|
||||||
<span class="card-title-text">Github</span>
|
<span class="card-title-text">Github</span>
|
||||||
<div class="instructions">
|
<div class="instructions">
|
||||||
<a href="https://docs.khoj.dev/#/github_integration">ⓘ Help</a>
|
<a href="https://docs.khoj.dev/#/github_integration">ⓘ Help</a>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2 class="section-title">
|
<h2 class="section-title">
|
||||||
<img class="card-icon" src="/static/assets/icons/notion.svg" alt="Notion">
|
<img class="card-icon" src="/static/assets/icons/notion.svg?v={{ khoj_version }}" alt="Notion">
|
||||||
<span class="card-title-text">Notion</span>
|
<span class="card-title-text">Notion</span>
|
||||||
<div class="instructions">
|
<div class="instructions">
|
||||||
<a href="https://docs.khoj.dev/#/notion_integration">ⓘ Help</a>
|
<a href="https://docs.khoj.dev/#/notion_integration">ⓘ Help</a>
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
|
||||||
<title>Khoj - Search</title>
|
<title>Khoj - Search</title>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" sizes="128x128" href="/static/assets/icons/favicon-128x128.png">
|
<link rel="icon" type="image/png" sizes="128x128" href="/static/assets/icons/favicon-128x128.png?v={{ khoj_version }}">
|
||||||
<link rel="manifest" href="/static/khoj.webmanifest">
|
<link rel="manifest" href="/static/khoj.webmanifest?v={{ khoj_version }}">
|
||||||
<link rel="stylesheet" href="/static/assets/khoj.css">
|
<link rel="stylesheet" href="/static/assets/khoj.css?v={{ khoj_version }}">
|
||||||
</head>
|
</head>
|
||||||
<script type="text/javascript" src="/static/assets/org.min.js"></script>
|
<script type="text/javascript" src="/static/assets/org.min.js?v={{ khoj_version }}"></script>
|
||||||
<script type="text/javascript" src="/static/assets/markdown-it.min.js"></script>
|
<script type="text/javascript" src="/static/assets/markdown-it.min.js?v={{ khoj_version }}"></script>
|
||||||
<script type="text/javascript" src="/static/assets/utils.js"></script>
|
<script type="text/javascript" src="/static/assets/utils.js?v={{ khoj_version }}"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function render_image(item) {
|
function render_image(item) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% macro heading_pane(user_photo, username, is_active, has_documents) -%}
|
{% macro heading_pane(user_photo, username, is_active, has_documents) -%}
|
||||||
<div class="khoj-header">
|
<div class="khoj-header">
|
||||||
<a class="khoj-logo" href="/">
|
<a class="khoj-logo" href="/">
|
||||||
<img class="khoj-logo" src="/static/assets/icons/khoj-logo-sideways-500.png" alt="Khoj"></img>
|
<img class="khoj-logo" src="/static/assets/icons/khoj-logo-sideways-500.png?v={{ khoj_version }}" alt="Khoj"></img>
|
||||||
</a>
|
</a>
|
||||||
<nav class="khoj-nav">
|
<nav class="khoj-nav">
|
||||||
{% if has_documents %}
|
{% if has_documents %}
|
||||||
|
|
|
@ -47,6 +47,7 @@ def index(request: Request):
|
||||||
"user_photo": user_picture,
|
"user_photo": user_picture,
|
||||||
"is_active": has_required_scope(request, ["premium"]),
|
"is_active": has_required_scope(request, ["premium"]),
|
||||||
"has_documents": has_documents,
|
"has_documents": has_documents,
|
||||||
|
"khoj_version": state.khoj_version,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -66,6 +67,7 @@ def index_post(request: Request):
|
||||||
"user_photo": user_picture,
|
"user_photo": user_picture,
|
||||||
"is_active": has_required_scope(request, ["premium"]),
|
"is_active": has_required_scope(request, ["premium"]),
|
||||||
"has_documents": has_documents,
|
"has_documents": has_documents,
|
||||||
|
"khoj_version": state.khoj_version,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -85,6 +87,7 @@ def search_page(request: Request):
|
||||||
"user_photo": user_picture,
|
"user_photo": user_picture,
|
||||||
"is_active": has_required_scope(request, ["premium"]),
|
"is_active": has_required_scope(request, ["premium"]),
|
||||||
"has_documents": has_documents,
|
"has_documents": has_documents,
|
||||||
|
"khoj_version": state.khoj_version,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -104,6 +107,7 @@ def chat_page(request: Request):
|
||||||
"user_photo": user_picture,
|
"user_photo": user_picture,
|
||||||
"is_active": has_required_scope(request, ["premium"]),
|
"is_active": has_required_scope(request, ["premium"]),
|
||||||
"has_documents": has_documents,
|
"has_documents": has_documents,
|
||||||
|
"khoj_version": state.khoj_version,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -183,6 +187,7 @@ def config_page(request: Request):
|
||||||
"is_twilio_enabled": is_twilio_enabled(),
|
"is_twilio_enabled": is_twilio_enabled(),
|
||||||
"phone_number": user.phone_number,
|
"phone_number": user.phone_number,
|
||||||
"is_phone_number_verified": user.verified_phone_number,
|
"is_phone_number_verified": user.verified_phone_number,
|
||||||
|
"khoj_version": state.khoj_version,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -223,6 +228,7 @@ def github_config_page(request: Request):
|
||||||
"user_photo": user_picture,
|
"user_photo": user_picture,
|
||||||
"is_active": has_required_scope(request, ["premium"]),
|
"is_active": has_required_scope(request, ["premium"]),
|
||||||
"has_documents": has_documents,
|
"has_documents": has_documents,
|
||||||
|
"khoj_version": state.khoj_version,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -250,6 +256,7 @@ def notion_config_page(request: Request):
|
||||||
"user_photo": user_picture,
|
"user_photo": user_picture,
|
||||||
"is_active": has_required_scope(request, ["premium"]),
|
"is_active": has_required_scope(request, ["premium"]),
|
||||||
"has_documents": has_documents,
|
"has_documents": has_documents,
|
||||||
|
"khoj_version": state.khoj_version,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -269,5 +276,6 @@ def computer_config_page(request: Request):
|
||||||
"user_photo": user_picture,
|
"user_photo": user_picture,
|
||||||
"is_active": has_required_scope(request, ["premium"]),
|
"is_active": has_required_scope(request, ["premium"]),
|
||||||
"has_documents": has_documents,
|
"has_documents": has_documents,
|
||||||
|
"khoj_version": state.khoj_version,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -211,7 +211,7 @@ def setup(
|
||||||
file_names = [file_name for file_name in files]
|
file_names = [file_name for file_name in files]
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Deleted {num_deleted_embeddings} entries. Created {num_new_embeddings} new entries for user {user} from files {file_names}"
|
f"Deleted {num_deleted_embeddings} entries. Created {num_new_embeddings} new entries for user {user} from files {file_names[:10]} ..."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue