Compare commits
257 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17107a0337 | ||
|
|
f69eafe95a | ||
|
|
679db51453 | ||
|
|
58bf917775 | ||
|
|
679f0f24a4 | ||
|
|
ec3b837d00 | ||
|
|
f0daa45ae0 | ||
|
|
29a581d2b0 | ||
|
|
699e9ff878 | ||
|
|
26bd3533d8 | ||
|
|
7c8c475c3a | ||
|
|
8a488b9e39 | ||
|
|
07ca137bdf | ||
|
|
d4552117f6 | ||
|
|
c0ad64d9a3 | ||
|
|
ea85ebdacb | ||
|
|
039ed78253 | ||
|
|
9dfe1bb003 | ||
|
|
9b1a66c969 | ||
|
|
93d5cb128c | ||
|
|
24af888c41 | ||
|
|
2f1bb5c2c8 | ||
|
|
e71ebb8068 | ||
|
|
efb4bd6780 | ||
|
|
6165ae56c2 | ||
|
|
8b4dd16255 | ||
|
|
c8dbe8ee7b | ||
|
|
f9420e1209 | ||
|
|
36bf42a860 | ||
|
|
aab75a6ead | ||
|
|
1a46734485 | ||
|
|
e9e49ea098 | ||
|
|
08012c71b1 | ||
|
|
870af19ba4 | ||
|
|
4d30f7d1d9 | ||
|
|
d26a4ffcea | ||
|
|
2752e0d607 | ||
|
|
7039c202c8 | ||
|
|
8917228dbb | ||
|
|
51c59d0059 | ||
|
|
6ded4c1d75 | ||
|
|
c24389cff5 | ||
|
|
45f892dfdd | ||
|
|
e0b381d523 | ||
|
|
16175137e5 | ||
|
|
9fe1c8ae13 | ||
|
|
d74f8e03d3 | ||
|
|
1ae6669fbf | ||
|
|
50575b749b | ||
|
|
ba37b28fb5 | ||
|
|
7dfbcd2e5a | ||
|
|
5f97357fe0 | ||
|
|
bb1c1b39d8 | ||
|
|
ba99089a12 | ||
|
|
b1269fdad2 | ||
|
|
ffdb291fe0 | ||
|
|
af9ceb00a0 | ||
|
|
43423432ce | ||
|
|
5f9ac5a630 | ||
|
|
efe41aaaca | ||
|
|
02187b19bb | ||
|
|
8389108653 | ||
|
|
dbc59b2952 | ||
|
|
efc7b08cd9 | ||
|
|
c82d34b659 | ||
|
|
d920e4d0a7 | ||
|
|
80d1ad5b6f | ||
|
|
ce53bc52c5 | ||
|
|
740453fa18 | ||
|
|
2be7c84203 | ||
|
|
ad95e88838 | ||
|
|
bd9aa578f4 | ||
|
|
9b991eb4fe | ||
|
|
98081bc0d3 | ||
|
|
5d52dc5b35 | ||
|
|
b6d5392c0c | ||
|
|
fca7a5ff32 | ||
|
|
4ded32cc64 | ||
|
|
4a234c8db3 | ||
|
|
e28adf2884 | ||
|
|
5abaed9d08 | ||
|
|
e582639efa | ||
|
|
05536aab6b | ||
|
|
455f78b178 | ||
|
|
79913d4c17 | ||
|
|
738f050086 | ||
|
|
442c913de3 | ||
|
|
d3ab3f1b70 | ||
|
|
6946e038c2 | ||
|
|
00af6baeb6 | ||
|
|
c10602b6c5 | ||
|
|
fe415e1508 | ||
|
|
3280715ca0 | ||
|
|
afec4394f9 | ||
|
|
c50eb8a691 | ||
|
|
21c55b4c0d | ||
|
|
e42111a8af | ||
|
|
6a8c1fe423 | ||
|
|
6879daccc6 | ||
|
|
074123b9b9 | ||
|
|
d101297995 | ||
|
|
350fd89c8d | ||
|
|
8d1e988059 | ||
|
|
b5ae64cb3c | ||
|
|
d3d47dce0b | ||
|
|
e487ec5370 | ||
|
|
70607cbbbb | ||
|
|
b3d7d6a79d | ||
|
|
e1aaff2053 | ||
|
|
a1211f40d7 | ||
|
|
089e4bee12 | ||
|
|
447c1b90e7 | ||
|
|
aa23da60a3 | ||
|
|
e04fe921eb | ||
|
|
5ff9df9d4c | ||
|
|
0f6e4ff683 | ||
|
|
6dd2b05bf5 | ||
|
|
e3557cd8b7 | ||
|
|
927e477f68 | ||
|
|
946305d977 | ||
|
|
903a01745f | ||
|
|
1e14a24f06 | ||
|
|
5b092d59f4 | ||
|
|
03cb86ee46 | ||
|
|
0288804f2e | ||
|
|
49af2148fe | ||
|
|
7cb64cb2f9 | ||
|
|
e9ea0195b0 | ||
|
|
09544dee09 | ||
|
|
0459666beb | ||
|
|
61dde8ed89 | ||
|
|
fefaa2271d | ||
|
|
3065cea562 | ||
|
|
5f6dcf9f2e | ||
|
|
73a107690d | ||
|
|
9b961ed496 | ||
|
|
7504669f2b | ||
|
|
408b7413e9 | ||
|
|
162b219f2b | ||
|
|
8f2f053968 | ||
|
|
d124266923 | ||
|
|
6e3f66c0f1 | ||
|
|
52c5f4170a | ||
|
|
8016a57b5e | ||
|
|
cc051ceb4b | ||
|
|
252b35b2f0 | ||
|
|
ef21d78c99 | ||
|
|
1d9c1333f2 | ||
|
|
f0222f6d08 | ||
|
|
d2ddbef08f | ||
|
|
d20746613a | ||
|
|
316b7d471a | ||
|
|
2b09caa237 | ||
|
|
7009793170 | ||
|
|
62a89f79b7 | ||
|
|
cc064ea57d | ||
|
|
21f8d63e89 | ||
|
|
c5d297a9ed | ||
|
|
a57d529f39 | ||
|
|
106cdbe455 | ||
|
|
10ce4ee11c | ||
|
|
02f40785aa | ||
|
|
a5ffa2342f | ||
|
|
9d4bfdf47c | ||
|
|
d587632700 | ||
|
|
a0686428ff | ||
|
|
48719ee0dd | ||
|
|
1a31a2efcf | ||
|
|
0430fa67b6 | ||
|
|
491a1a949a | ||
|
|
20ef5bfc93 | ||
|
|
8faa63c3c6 | ||
|
|
de5aa5c32e | ||
|
|
fab57cc395 | ||
|
|
c4dcb51c91 | ||
|
|
a6ca2076d5 | ||
|
|
643e018947 | ||
|
|
110d7646fc | ||
|
|
18254850ab | ||
|
|
24b5aaef0a | ||
|
|
6290b463f5 | ||
|
|
eb5e3096e0 | ||
|
|
6e1ba11e59 | ||
|
|
239b31bc85 | ||
|
|
309ba7234c | ||
|
|
5d8dbbdba4 | ||
|
|
71f2d54258 | ||
|
|
9e714d032b | ||
|
|
ebeae543ee | ||
|
|
b249bbb5b5 | ||
|
|
e438853b09 | ||
|
|
c18d52d1af | ||
|
|
a79604b601 | ||
|
|
06f99ceb3c | ||
|
|
56a1a61c77 | ||
|
|
877532a167 | ||
|
|
cc9eae5d18 | ||
|
|
4636390f7f | ||
|
|
a0a7ab7ec8 | ||
|
|
499adf86a0 | ||
|
|
897170ab15 | ||
|
|
28090216f6 | ||
|
|
fc040825b2 | ||
|
|
5a6547677c | ||
|
|
3e252036c3 | ||
|
|
b484795b8e | ||
|
|
6233a957b4 | ||
|
|
52b88de7f4 | ||
|
|
e0a59cff68 | ||
|
|
d0e294d8a5 | ||
|
|
73e38fccf3 | ||
|
|
b2afbaa315 | ||
|
|
07bf365c7c | ||
|
|
dd1badae81 | ||
|
|
48b9116195 | ||
|
|
771f9bcfa1 | ||
|
|
e5b1350523 | ||
|
|
9c868ee10b | ||
|
|
69c8f45830 | ||
|
|
25f3f2367e | ||
|
|
138f4e3f3c | ||
|
|
0885fc6c23 | ||
|
|
c13953311a | ||
|
|
c42ec32a95 | ||
|
|
e3b32e412c | ||
|
|
5fac39afed | ||
|
|
c641b8df58 | ||
|
|
e0949e232b | ||
|
|
256e8de40a | ||
|
|
fd60db766e | ||
|
|
d5a4830761 | ||
|
|
3096544cf2 | ||
|
|
63675b3299 | ||
|
|
2951fc92d7 | ||
|
|
cc77bc4076 | ||
|
|
1ca99b6eb0 | ||
|
|
60c23d9e3a | ||
|
|
c652a7fd2d | ||
|
|
1e2af083f0 | ||
|
|
4cb28aeffb | ||
|
|
d199c4c35f | ||
|
|
2bb989e9d8 | ||
|
|
b142de15a8 | ||
|
|
a9623ef85a | ||
|
|
a8f13f334f | ||
|
|
8fa0b69c67 | ||
|
|
fee99779bf | ||
|
|
b8e6883a81 | ||
|
|
ef5e9d66c1 | ||
|
|
11ccb92755 | ||
|
|
262f3ccb59 | ||
|
|
a7e00898cb | ||
|
|
0fcf234f07 | ||
|
|
bfbe273ffd | ||
|
|
9ddf3b58c3 | ||
|
|
a0b12b001a | ||
|
|
ec06d2c446 |
42
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Create a bug to help fix something that might not be working correctly
|
||||
title: "[FIX]"
|
||||
labels: fix
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Describe the bug
|
||||
A clear and concise description of what the bug is. Please include what you were expecting to happen vs. what actually happened.
|
||||
|
||||
## To Reproduce
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
## Screenshots
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
## Platform
|
||||
- Server:
|
||||
- [ ] Cloud-Hosted (https://app.khoj.dev)
|
||||
- [ ] Self-Hosted Docker
|
||||
- [ ] Self-Hosted Python package
|
||||
- [ ] Self-Hosted source code
|
||||
- Client:
|
||||
- [ ] Obsidian
|
||||
- [ ] Emacs
|
||||
- [ ] Desktop app
|
||||
- [ ] Web browser
|
||||
- [ ] WhatsApp
|
||||
- OS:
|
||||
- [ ] Windows
|
||||
- [ ] macOS
|
||||
- [ ] Linux
|
||||
- [ ] Android
|
||||
- [ ] iOS
|
||||
|
||||
### If self-hosted
|
||||
- Server Version [e.g. 1.0.1]:
|
||||
|
||||
## Additional context
|
||||
Add any other context about the problem here.
|
||||
11
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea to help make Khoj a better tool
|
||||
title: "[IDEA]"
|
||||
labels: "upgrade"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Describe the feature you'd like
|
||||
A clear and concise description of what you want to happen. Include any relevant links or screenshots or inspiration.
|
||||
51
.github/workflows/desktop.yml
vendored
@@ -46,3 +46,54 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
npx todesktop release --latest --force
|
||||
|
||||
- name: ⤵️ Get Desktop Apps
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
build_no=`npx todesktop builds --latest | tail -n 1 | awk -F'/' '{print $NF}'`
|
||||
sleep 900 # wait for 15 minutes for the build to be available
|
||||
wget https://download.khoj.dev/builds/$build_no/mac/dmg/arm64 -O khoj-${{ github.ref_name }}-arm64.dmg
|
||||
wget https://download.khoj.dev/builds/$build_no/mac/dmg/x64 -O khoj-${{ github.ref_name }}-x64.dmg
|
||||
wget https://download.khoj.dev/builds/$build_no/windows/nsis/x64 -O khoj-${{ github.ref_name }}-x64.exe
|
||||
wget https://download.khoj.dev/builds/$build_no/linux/deb/x64 -O khoj-${{ github.ref_name }}-x64.deb
|
||||
wget https://download.khoj.dev/builds/$build_no/linux/appImage/x64 -O khoj-${{ github.ref_name }}-x64.AppImage
|
||||
|
||||
- name: ⏫ Upload Mac ARM App
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
if-no-files-found: warn
|
||||
name: khoj-${{ github.ref_name }}-arm64.dmg
|
||||
path: src/interface/desktop/khoj-${{ github.ref_name }}-arm64.dmg
|
||||
|
||||
- name: ⏫ Upload Mac x64 App
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
if-no-files-found: warn
|
||||
name: khoj-${{ github.ref_name }}-x64.dmg
|
||||
path: src/interface/desktop/khoj-${{ github.ref_name }}-x64.dmg
|
||||
|
||||
- name: ⏫ Upload Windows App
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
if-no-files-found: warn
|
||||
name: khoj-${{ github.ref_name }}-x64.exe
|
||||
path: src/interface/desktop/khoj-${{ github.ref_name }}-x64.exe
|
||||
|
||||
- name: ⏫ Upload Debian App
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
if-no-files-found: warn
|
||||
name: khoj-${{ github.ref_name }}-x64.deb
|
||||
path: src/interface/desktop/khoj-${{ github.ref_name }}-x64.deb
|
||||
|
||||
- name: ⏫ Upload Linux App Image
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
if-no-files-found: warn
|
||||
name: khoj-${{ github.ref_name }}-x64.AppImage
|
||||
path: src/interface/desktop/khoj-${{ github.ref_name }}-x64.AppImage
|
||||
|
||||
55
.github/workflows/dockerize.yml
vendored
@@ -8,23 +8,47 @@ on:
|
||||
- master
|
||||
paths:
|
||||
- src/khoj/**
|
||||
- config/**
|
||||
- pyproject.toml
|
||||
- Dockerfile
|
||||
- prod.Dockerfile
|
||||
- docker-compose.yml
|
||||
- .github/workflows/dockerize.yml
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Docker image tag'
|
||||
default: 'dev'
|
||||
khoj:
|
||||
description: 'Build Khoj docker image'
|
||||
type: boolean
|
||||
default: true
|
||||
khoj-cloud:
|
||||
description: 'Build Khoj cloud docker image'
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
env:
|
||||
DOCKER_IMAGE_TAG: ${{ github.ref == 'refs/heads/master' && 'latest' || github.ref_name }}
|
||||
# Tag Image with tag name on release
|
||||
# else with user specified tag (default 'dev') if triggered via workflow
|
||||
# else with 'pre' (if push to master)
|
||||
DOCKER_IMAGE_TAG: ${{ github.ref_type == 'tag' && github.ref_name || github.event_name == 'workflow_dispatch' && github.event.inputs.tag || 'pre' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Docker Image, Push to Container Registry
|
||||
name: Publish Khoj Docker Images
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image:
|
||||
- 'local'
|
||||
- 'cloud'
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Get all history to correctly infer Khoj version using hatch
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
@@ -36,13 +60,36 @@ jobs:
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.PAT }}
|
||||
|
||||
- name: Get App Version
|
||||
id: hatch
|
||||
run: echo "version=$(pipx run hatch version)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 📦 Build and Push Docker Image
|
||||
uses: docker/build-push-action@v2
|
||||
if: (matrix.image == 'local' && github.event_name == 'workflow_dispatch') && github.event.inputs.khoj == 'true' || (matrix.image == 'local' && github.event_name == 'push')
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
platforms: linux/amd64, linux/arm64
|
||||
push: true
|
||||
tags: ghcr.io/${{ github.repository }}:${{ env.DOCKER_IMAGE_TAG }}
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository }}:${{ env.DOCKER_IMAGE_TAG }}
|
||||
${{ github.ref_type == 'tag' && format('ghcr.io/{0}:latest', github.repository) || '' }}
|
||||
build-args: |
|
||||
VERSION=${{ steps.hatch.outputs.version }}
|
||||
PORT=42110
|
||||
|
||||
- name: 📦️⛅️ Build and Push Cloud Docker Image
|
||||
uses: docker/build-push-action@v2
|
||||
if: (matrix.image == 'cloud' && github.event_name == 'workflow_dispatch') && github.event.inputs.khoj-cloud == 'true' || (matrix.image == 'cloud' && github.event_name == 'push')
|
||||
with:
|
||||
context: .
|
||||
file: prod.Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository }}-cloud:${{ env.DOCKER_IMAGE_TAG }}
|
||||
${{ github.ref_type == 'tag' && format('ghcr.io/{0}-cloud:latest', github.repository) || '' }}
|
||||
build-args: |
|
||||
VERSION=${{ steps.hatch.outputs.version }}
|
||||
PORT=42110
|
||||
|
||||
43
.github/workflows/dockerize_dev.yml
vendored
@@ -1,43 +0,0 @@
|
||||
name: dockerize-dev
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- src/khoj/**
|
||||
- config/**
|
||||
- pyproject.toml
|
||||
- prod.Dockerfile
|
||||
- .github/workflows/dockerize_dev.yml
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DOCKER_IMAGE_TAG: 'dev'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Production Docker Image, Push to Container Registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.PAT }}
|
||||
|
||||
- name: 📦 Build and Push Docker Image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: prod.Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ghcr.io/${{ github.repository }}-cloud:${{ env.DOCKER_IMAGE_TAG }}
|
||||
build-args: |
|
||||
PORT=42110
|
||||
47
.github/workflows/dockerize_production.yml
vendored
@@ -1,47 +0,0 @@
|
||||
name: dockerize-prod
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- src/khoj/**
|
||||
- config/**
|
||||
- pyproject.toml
|
||||
- prod.Dockerfile
|
||||
- .github/workflows/dockerize_production.yml
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DOCKER_IMAGE_TAG: ${{ github.ref == 'refs/heads/master' && 'latest' || github.ref_name }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Production Docker Image, Push to Container Registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.PAT }}
|
||||
|
||||
- name: 📦 Build and Push Docker Image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: prod.Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ghcr.io/${{ github.repository }}-cloud:${{ env.DOCKER_IMAGE_TAG }}
|
||||
build-args: |
|
||||
PORT=42110
|
||||
46
.github/workflows/github_pages_deploy.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: build and deploy github pages for documentation
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: https://docs.khoj.dev
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
# 👇 Build steps
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: yarn
|
||||
cache-dependency-path: documentation/yarn.lock
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd documentation
|
||||
yarn install --frozen-lockfile --non-interactive
|
||||
- name: Build
|
||||
run: |
|
||||
cd documentation
|
||||
yarn build
|
||||
# 👆 Build steps
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v3
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v2
|
||||
with:
|
||||
# 👇 Specify build output path
|
||||
path: documentation/build
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v2
|
||||
@@ -15,6 +15,13 @@ repos:
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
args: ["--profile", "black", "--filter-files"]
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.0.0
|
||||
hooks:
|
||||
|
||||
@@ -3,21 +3,22 @@ FROM ubuntu:jammy
|
||||
LABEL org.opencontainers.image.source https://github.com/khoj-ai/khoj
|
||||
|
||||
# Install System Dependencies
|
||||
RUN apt update -y && apt -y install python3-pip git
|
||||
RUN apt update -y && apt -y install python3-pip git swig
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install Application
|
||||
COPY pyproject.toml .
|
||||
COPY README.md .
|
||||
RUN sed -i 's/dynamic = \["version"\]/version = "0.0.0"/' pyproject.toml && \
|
||||
ARG VERSION=0.0.0
|
||||
RUN sed -i "s/dynamic = \\[\"version\"\\]/version = \"$VERSION\"/" pyproject.toml && \
|
||||
pip install --no-cache-dir .
|
||||
|
||||
# Copy Source Code
|
||||
COPY . .
|
||||
|
||||
# Set the PYTHONPATH environment variable in order for it to find the Django app.
|
||||
ENV PYTHONPATH=/app/src/khoj:$PYTHONPATH
|
||||
ENV PYTHONPATH=/app/src:$PYTHONPATH
|
||||
|
||||
# Run the Application
|
||||
# There are more arguments required for the application to run,
|
||||
|
||||
18
README.md
@@ -28,9 +28,10 @@
|
||||
|
||||
***
|
||||
|
||||
Khoj is a desktop application to search and chat with your notes, documents and images.<br />
|
||||
It is an offline-first, open source AI personal assistant accessible from your Emacs, Obsidian or Web browser.<br />
|
||||
It works with jpeg, markdown, notion, org-mode, pdf files and github repositories.<br />
|
||||
Khoj is an AI application to search and chat with your notes and documents.<br />
|
||||
It is open-source, self-hostable and accessible on Desktop, Emacs, Obsidian, Web and Whatsapp.<br />
|
||||
It works with pdf, markdown, org-mode, notion files and github repositories.<br />
|
||||
It can paint, search the internet and understand speech.<br />
|
||||
|
||||
***
|
||||
|
||||
@@ -40,4 +41,13 @@ It works with jpeg, markdown, notion, org-mode, pdf files and github repositorie
|
||||
|:---------:|:-------:|
|
||||
| Quickly retrieve relevant documents using natural language | Get answers and create content from your existing knowledge base |
|
||||
| Does not need internet | Can be configured to work without internet |
|
||||
| <img src="https://docs.khoj.dev/assets/khoj_search_on_web.png" width="400px"> | <img src="https://docs.khoj.dev/assets/khoj_chat_on_web.png" width="400px"> |
|
||||
| <img src="https://docs.khoj.dev/img/khoj_search_on_web.png" width="400px"> | <img src="https://docs.khoj.dev/img/khoj_chat_on_web.png" width="400px"> |
|
||||
|
||||
## Contributors
|
||||
Cheers to our awesome contributors! 🎉
|
||||
|
||||
<a href="https://github.com/khoj-ai/khoj/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=khoj-ai/khoj" />
|
||||
</a>
|
||||
|
||||
Made with [contrib.rocks](https://contrib.rocks).
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
content-type:
|
||||
# The /data/folder/ prefix to the folders is here because this is
|
||||
# the directory to which the local files are copied in the docker-compose.
|
||||
# If changing, the docker-compose volumes should also be changed to match.
|
||||
org:
|
||||
input-files: null
|
||||
input-filter: ["/data/org/**/*.org"]
|
||||
compressed-jsonl: "/data/embeddings/notes.jsonl.gz"
|
||||
embeddings-file: "/data/embeddings/note_embeddings.pt"
|
||||
index_heading_entries: false
|
||||
|
||||
markdown:
|
||||
input-files: null
|
||||
input-filter: ["/data/markdown/**/*.markdown"]
|
||||
compressed-jsonl: "/data/embeddings/markdown.jsonl.gz"
|
||||
embeddings-file: "/data/embeddings/markdown_embeddings.pt"
|
||||
|
||||
pdf:
|
||||
input-files: null
|
||||
input-filter: ["/data/pdf/**/*.pdf"]
|
||||
compressed-jsonl: "/data/embeddings/pdf.jsonl.gz"
|
||||
embeddings-file: "/data/embeddings/pdf_embeddings.pt"
|
||||
|
||||
image:
|
||||
input-directories: ["/data/images/"]
|
||||
embeddings-file: "/data/embeddings/image_embeddings.pt"
|
||||
batch-size: 50
|
||||
use-xmp-metadata: false
|
||||
|
||||
notion: null
|
||||
github: null
|
||||
plugins: null
|
||||
|
||||
search-type:
|
||||
symmetric: null
|
||||
asymmetric:
|
||||
encoder: "sentence-transformers/multi-qa-MiniLM-L6-cos-v1"
|
||||
cross-encoder: "cross-encoder/ms-marco-MiniLM-L-6-v2"
|
||||
model_directory: "/data/models/asymmetric"
|
||||
image:
|
||||
encoder: "sentence-transformers/clip-ViT-B-32"
|
||||
model_directory: "/data/models/image_encoder"
|
||||
|
||||
processor:
|
||||
conversation:
|
||||
conversation-logfile: "/data/embeddings/conversation_logs.json"
|
||||
enable-offline-chat: false
|
||||
openai: null
|
||||
|
||||
app:
|
||||
should_log_telemetry: true
|
||||
@@ -1,57 +0,0 @@
|
||||
content-type:
|
||||
org:
|
||||
input-files: # ["/path/to/org-file.org"] REQUIRED IF input-filter IS NOT SET OR
|
||||
input-filter: # ["/path/to/org/*.org"] REQUIRED IF input-files IS NOT SET
|
||||
compressed-jsonl: "~/.khoj/content/org/org.jsonl.gz"
|
||||
embeddings-file: "~/.khoj/content/org/org_embeddings.pt"
|
||||
index_heading_entries: false # Set to true to index entries with empty body
|
||||
|
||||
markdown:
|
||||
input-files: # ["/path/to/markdown-file.md"] REQUIRED IF input-filter IS NOT SET OR
|
||||
input-filter: # ["/path/to/markdown/*.md"] REQUIRED IF input-files IS NOT SET
|
||||
compressed-jsonl: "~/.khoj/content/markdown/markdown.jsonl.gz"
|
||||
embeddings-file: "~/.khoj/content/markdown/markdown_embeddings.pt"
|
||||
|
||||
ledger:
|
||||
input-files: # ["/path/to/ledger-file.beancount"] REQUIRED IF input-filter is not set OR
|
||||
input-filter: # ["/path/to/ledger/*.beancount"] REQUIRED IF input-files is not set
|
||||
compressed-jsonl: "~/.khoj/content/ledger/ledger.jsonl.gz"
|
||||
embeddings-file: "~/.khoj/content/ledger/ledger_embeddings.pt"
|
||||
|
||||
image:
|
||||
input-directories: # ["/path/to/images/"] REQUIRED IF input-filter IS NOT SET OR
|
||||
input-filter: # ["/path/to/images/*.jpg"] REQUIRED IF input-directories IS NOT SET
|
||||
embeddings-file: "~/.khoj/content/image/image_embeddings.pt"
|
||||
batch-size: 50
|
||||
use-xmp-metadata: false
|
||||
|
||||
music:
|
||||
input-files: # ["/path/to/music-file.org"] REQUIRED IF input-filter IS NOT SET OR
|
||||
input-filter: # ["/path/to/music/*.org"] REQUIRED IF input-files IS NOT SET
|
||||
compressed-jsonl: "~/.khoj/content/music/music.jsonl.gz"
|
||||
embeddings-file: "~/.khoj/content/music/music_embeddings.pt"
|
||||
|
||||
search-type:
|
||||
symmetric:
|
||||
encoder: "sentence-transformers/all-MiniLM-L6-v2"
|
||||
cross-encoder: "cross-encoder/ms-marco-MiniLM-L-6-v2"
|
||||
encoder-type: sentence_transformers.SentenceTransformer
|
||||
model_directory: "~/.khoj/search/symmetric/"
|
||||
|
||||
asymmetric:
|
||||
encoder: "sentence-transformers/multi-qa-MiniLM-L6-cos-v1"
|
||||
cross-encoder: "cross-encoder/ms-marco-MiniLM-L-6-v2"
|
||||
encoder-type: sentence_transformers.SentenceTransformer
|
||||
model_directory: "~/.khoj/search/asymmetric/"
|
||||
|
||||
image:
|
||||
encoder: "sentence-transformers/clip-ViT-B-32"
|
||||
encoder-type: sentence_transformers.SentenceTransformer
|
||||
model_directory: "~/.khoj/search/image/"
|
||||
|
||||
processor:
|
||||
conversation:
|
||||
openai-api-key: # "YOUR_OPENAI_API_KEY"
|
||||
model: "text-davinci-003"
|
||||
chat-model: "gpt-3.5-turbo"
|
||||
conversation-logfile: "~/.khoj/processor/conversation/conversation_logs.json"
|
||||
@@ -1 +0,0 @@
|
||||
docs.khoj.dev
|
||||
@@ -1,53 +0,0 @@
|
||||
<p align="center"><img src="./assets/khoj-logo-sideways-500.png" width="200" alt="Khoj Logo"></p>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/khoj-ai/khoj/actions/workflows/test.yml)
|
||||
[](https://github.com/khoj-ai/khoj/pkgs/container/khoj)
|
||||
[](https://pypi.org/project/khoj-assistant/)
|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<b>An AI copilot for your Second Brain</b>
|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[📜 Explore Code](https://github.com/khoj-ai/khoj)
|
||||
<span> • </span>
|
||||
[🌍 Try Khoj Cloud](https://khoj.dev)
|
||||
<span> • </span>
|
||||
[💬 Get Involved](https://discord.gg/BDgyabRM6e)
|
||||
|
||||
</div>
|
||||
|
||||
## Introduction
|
||||
Welcome to the Khoj Docs! This is the best place to get setup and explore Khoj's features.
|
||||
|
||||
- Khoj is an open source, personal AI
|
||||
- You can [chat](chat.md) with it about anything. When relevant, it'll use any notes or documents you shared with it to respond
|
||||
- Quickly [find](search.md) relevant notes and documents using natural language
|
||||
- It understands pdf, plaintext, markdown, org-mode files, [notion pages](notion_integration.md) and [github repositories](github_integration.md)
|
||||
- Access it from your [Emacs](emacs.md), [Obsidian](obsidian.md), [Web browser](web.md) or the [Khoj Desktop app](desktop.md)
|
||||
- You can self-host Khoj on your consumer hardware or share it with your family, friends or team from your private cloud
|
||||
|
||||
## Quickstart
|
||||
- [Try Khoj Cloud](https://app.khoj.dev) to get started quickly
|
||||
- [Read these instructions](./setup.md) to self-host a private instance of Khoj
|
||||
|
||||
## Overview
|
||||
<img src="https://docs.khoj.dev/assets/khoj_search_on_web.png" width="400px">
|
||||
<span> </span>
|
||||
<img src="https://docs.khoj.dev/assets/khoj_chat_on_web.png" width="400px">
|
||||
|
||||
#### [Search](search.md)
|
||||
- **Natural**: Use natural language queries to quickly find relevant notes and documents.
|
||||
- **Incremental**: Incremental search for a fast, search-as-you-type experience
|
||||
|
||||
#### [Chat](chat.md)
|
||||
- **Faster answers**: Find answers faster, smoother than search. No need to manually scan through your notes to find answers.
|
||||
- **Iterative discovery**: Iteratively explore and (re-)discover your notes
|
||||
- **Assisted creativity**: Smoothly weave across answers retrieval and content generation
|
||||
- **Online or Offline**: Choose online or offline chat depending on your requirements
|
||||
@@ -1,14 +0,0 @@
|
||||
<!-- _coverpage.md -->
|
||||
|
||||

|
||||
|
||||
> An open source, AI personal assistant for your notes
|
||||
|
||||
- Lightning fast search
|
||||
- Multi-turn chat
|
||||
- Keeps you in control of your data
|
||||
|
||||
[GitHub](https://github.com/khoj-ai/khoj)
|
||||
[Get Started](#khoj)
|
||||
|
||||

|
||||
@@ -1,23 +0,0 @@
|
||||
- Get Started
|
||||
- [Overview](README.md)
|
||||
- [Self-Host](setup.md)
|
||||
- [Demos](demos.md)
|
||||
- Use
|
||||
- [Features](features.md)
|
||||
- [Chat](chat.md)
|
||||
- [Search](search.md)
|
||||
- Clients
|
||||
- [Desktop](desktop.md)
|
||||
- [Obsidian](obsidian.md)
|
||||
- [Emacs](emacs.md)
|
||||
- [Web](web.md)
|
||||
- Online Data Sources
|
||||
- [Github](github_integration.md)
|
||||
- [Notion](notion_integration.md)
|
||||
- Miscellaneous
|
||||
- [Telemetry](telemetry.md)
|
||||
- [Advanced](advanced.md)
|
||||
- [Performance](performance.md)
|
||||
- [Credits](credits.md)
|
||||
- Contributing
|
||||
- [Development](development.md)
|
||||
@@ -1,32 +0,0 @@
|
||||
# Installing the Desktop Application [Deprecated -- for 0.11.4 and below]
|
||||
|
||||
We have beta desktop images available for download with new releases. This is recommended if you don't want to bother with the command line. Download the latest release from [here](https://github.com/khoj-ai/khoj/releases). You can find the latest release under the `Assets` section.
|
||||
|
||||
## MacOS
|
||||
|
||||
1. Download the latest release from [here](https://github.com/khoj-ai/khoj/releases).
|
||||
- If your Mac uses one of the Silicon chips, then download the `Khoj_<version>_arm64.dmg` file. Otherwise, download the `Khoj_<version>_amd64.dmg` file.
|
||||
2. Open the downloaded file and drag the Khoj app to your Applications folder.
|
||||
|
||||
## Windows
|
||||
|
||||
Make sure you meet the prerequisites for Windows installation. You can find them [here](windows_install.md#prerequisites).
|
||||
|
||||
1. Download the latest release from [here](https://github.com/khoj-ai/khoj/releases). You'll want the `khoj_<version>_amd64.exe` file.
|
||||
2. Open the downloaded file and double click to install.
|
||||
|
||||
## Linux
|
||||
|
||||
For the Linux installation, you have to have `glibc` version 2.35 or higher. You can check your version with `ldd --version`.
|
||||
|
||||
1. Download the latest release from [here](https://github.com/khoj-ai/khoj/releases). You'll want the `khoj_<version>_amd64.deb` file.
|
||||
2. In your downloads folder, run `sudo dpkg -i khoj_<version>_amd64.deb` to install Khoj.
|
||||
|
||||
|
||||
# Uninstall
|
||||
|
||||
If you decide you want to uninstall the application, you can uninstall it like any other application on your system. For example, on MacOS, you can drag the application to the trash. On Windows, you can uninstall it from the `Add or Remove Programs` menu. On Linux, you can uninstall it with `sudo apt remove khoj`.
|
||||
|
||||
In addition to that, you might want to `rm -rf` the following directories:
|
||||
- `~/.khoj`
|
||||
- `~/.cache/gpt4all`
|
||||
@@ -1,127 +0,0 @@
|
||||
# Development
|
||||
|
||||
## Setup
|
||||
### Using Pip
|
||||
#### 1. Install
|
||||
|
||||
```shell
|
||||
# Get Khoj Code
|
||||
git clone https://github.com/khoj-ai/khoj && cd khoj
|
||||
|
||||
# Create, Activate Virtual Environment
|
||||
python3 -m venv .venv && source .venv/bin/activate
|
||||
|
||||
# Install Khoj for Development
|
||||
pip install -e .[dev]
|
||||
|
||||
# For MacOS or zsh users run this
|
||||
pip install -e .'[dev]'
|
||||
|
||||
```
|
||||
|
||||
#### 2. Run
|
||||
1. Start Khoj
|
||||
```shell
|
||||
khoj -vv
|
||||
```
|
||||
2. Configure Khoj
|
||||
- **Via the Desktop application**: Add files, directories to index using the settings page of your desktop application. Click "Save" to immediately trigger indexing.
|
||||
|
||||
Note: Wait after configuration for khoj to Load ML model, generate embeddings and expose API to query notes, images, documents etc specified in config YAML
|
||||
|
||||
### Using Docker
|
||||
#### 1. Clone
|
||||
|
||||
```shell
|
||||
git clone https://github.com/khoj-ai/khoj && cd khoj
|
||||
```
|
||||
|
||||
#### 2. Configure
|
||||
|
||||
- **Required**: Update [docker-compose.yml](https://github.com/khoj-ai/khoj/blob/master/docker-compose.yml) to mount your images, (org-mode or markdown) notes, PDFs and Github repositories
|
||||
- **Optional**: Edit application configuration in [khoj_docker.yml](https://github.com/khoj-ai/khoj/blob/master/config/khoj_docker.yml)
|
||||
|
||||
#### 3. Run
|
||||
|
||||
```shell
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
*Note: The first run will take time. Let it run, it\'s mostly not hung, just generating embeddings*
|
||||
|
||||
#### 4. Upgrade
|
||||
|
||||
```shell
|
||||
docker-compose build --pull
|
||||
```
|
||||
|
||||
## Validate
|
||||
### Before Making Changes
|
||||
1. Install Git Hooks for Validation
|
||||
```shell
|
||||
pre-commit install -t pre-push -t pre-commit
|
||||
```
|
||||
- This ensures standard code formatting fixes and other checks run automatically on every commit and push
|
||||
- Note 1: If [pre-commit](https://pre-commit.com/#intro) didn't already get installed, [install it](https://pre-commit.com/#install) via `pip install pre-commit`
|
||||
- Note 2: To run the pre-commit changes manually, use `pre-commit run --hook-stage manual --all` before creating PR
|
||||
|
||||
### Before Creating PR
|
||||
|
||||
1. Run Tests. If you get an error complaining about a missing `fast_tokenizer_file`, follow the solution [in this Github issue](https://github.com/UKPLab/sentence-transformers/issues/1659).
|
||||
```shell
|
||||
pytest
|
||||
```
|
||||
|
||||
2. Run MyPy to check types
|
||||
```shell
|
||||
mypy --config-file pyproject.toml
|
||||
```
|
||||
|
||||
### After Creating PR
|
||||
- Automated [validation workflows](.github/workflows) run for every PR.
|
||||
|
||||
Ensure any issues seen by them our fixed
|
||||
|
||||
- Test the python packge created for a PR
|
||||
1. Download and extract the zipped `.whl` artifact generated from the pypi workflow run for the PR.
|
||||
2. Install (in your virtualenv) with `pip install /path/to/download*.whl>`
|
||||
3. Start and use the application to see if it works fine
|
||||
|
||||
## Create Khoj Release
|
||||
Follow the steps below to [release](https://github.com/debanjum/khoj/releases/) Khoj. This will create a stable release of Khoj on [Pypi](https://pypi.org/project/khoj-assistant/), [Melpa](https://stable.melpa.org/#%252Fkhoj) and [Obsidian](https://obsidian.md/plugins?id%253Dkhoj). It will also create desktop apps of Khoj and attach them to the latest release.
|
||||
|
||||
1. Create and tag release commit by running the bump_version script. The release commit sets version number in required metadata files.
|
||||
```shell
|
||||
./scripts/bump_version.sh -c "<release_version>"
|
||||
```
|
||||
2. Push commit and then the tag to trigger the release workflow to create Release with auto generated release notes.
|
||||
```shell
|
||||
git push origin master # push release commit to khoj repository
|
||||
git push origin <release_version> # push release tag to khoj repository
|
||||
```
|
||||
3. [Optional] Update the Release Notes to highlight new features, fixes and updates
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
## Visualize Codebase
|
||||
|
||||
*[Interactive Visualization](https://mango-dune-07a8b7110.1.azurestaticapps.net/?repo=debanjum%2Fkhoj)*
|
||||
|
||||

|
||||
|
||||
## Visualize Khoj Obsidian Plugin Codebase
|
||||
|
||||

|
||||
|
||||
## Khoj Obsidian Plugin Implementation
|
||||
The plugin implements the following functionality to search your notes with Khoj:
|
||||
- [X] Open the Khoj search modal via left ribbon icon or the *Khoj: Search* command
|
||||
- [X] Render results as Markdown preview to improve readability
|
||||
- [X] Configure Khoj via the plugin setting tab on the settings page
|
||||
- Set Obsidian Vault to Index with Khoj. Defaults to all markdown, PDF files in current Vault
|
||||
- Set URL of Khoj backend
|
||||
- Set Number of Search Results to show in Search Modal
|
||||
- [X] Allow reranking of result to improve search quality
|
||||
- [X] Allow Finding notes similar to current note being viewed
|
||||
@@ -1,34 +0,0 @@
|
||||
## Features
|
||||
|
||||
#### [Search](search.md)
|
||||
- **Local**: Your personal data stays local. All search and indexing is done on your machine.
|
||||
- **Incremental**: Incremental search for a fast, search-as-you-type experience
|
||||
|
||||
#### [Chat](chat.md)
|
||||
- **Faster answers**: Find answers faster, smoother than search. No need to manually scan through your notes to find answers.
|
||||
- **Iterative discovery**: Iteratively explore and (re-)discover your notes
|
||||
- **Assisted creativity**: Smoothly weave across answers retrieval and content generation
|
||||
|
||||
#### General
|
||||
- **Natural**: Advanced natural language understanding using Transformer based ML Models
|
||||
- **Pluggable**: Modular architecture makes it easy to plug in new data sources, frontends and ML models
|
||||
- **Multiple Sources**: Index your Org-mode and Markdown notes, PDF files, Github repositories, and Photos
|
||||
- **Multiple Interfaces**: Interact from your [Web Browser](./web.md), [Emacs](./emacs.md) or [Obsidian](./obsidian.md)
|
||||
|
||||
### Supported Interfaces
|
||||
|
||||
[](./emacs.md)
|
||||
<span> </span>
|
||||
[](./obsidian.md)
|
||||
|
||||
### Supported Data Sources
|
||||
- markdown*
|
||||
- org-mode*
|
||||
- pdf*
|
||||
- images*
|
||||
- [github](./github_integration.md)
|
||||
- [notion](./notion_integration.md)
|
||||
|
||||
\* These data sources are offline only.
|
||||
|
||||
If you're using Github or Notion, you can get on a waitlist for [Khoj Cloud](https://khoj.dev).
|
||||
@@ -1,47 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Document</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="description" content="Description">
|
||||
|
||||
<!-- Open Graph metadata -->
|
||||
<meta property="og:title" content="Khoj Documentation">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Khoj Documentation">
|
||||
<meta property="og:description" content="Quickly get started with using or self-hosting Khoj">
|
||||
<meta property="og:image" content="https://khoj-web-bucket.s3.amazonaws.com/link_preview_docs.png">
|
||||
<meta property="og:url" content="https://docs.khoj.dev">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify/lib/themes/buble.css" />
|
||||
<link rel="icon" href="./assets/favicon-128x128.ico">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
window.$docsify = {
|
||||
name: 'Khoj',
|
||||
repo: 'https://github.com/khoj-ai/khoj',
|
||||
loadSidebar: true,
|
||||
themeColor: '#c2a600',
|
||||
auto2top: true,
|
||||
// coverpage: true,
|
||||
}
|
||||
</script>
|
||||
<!-- Docsify v4 -->
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify-tabs@1"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-diff.min.js"></script>
|
||||
<script defer data-domain="khoj.dev" src="https://plausible.io/js/script.js"></script>
|
||||
</body>
|
||||
<style>
|
||||
video {
|
||||
max-width: 800px;
|
||||
}
|
||||
</style>
|
||||
</html>
|
||||
228
docs/setup.md
@@ -1,228 +0,0 @@
|
||||
## Setup
|
||||
These are the general setup instructions for 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](/emacs?id=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](/obsidian?id=_2-setup-plugin) 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.
|
||||
|
||||
### 1. Installation (Docker)
|
||||
|
||||
Use the sample docker-compose [in Github](https://github.com/khoj-ai/khoj/blob/master/docker-compose.yml) to run Khoj in Docker. Start by configuring all the environment variables to your choosing. Your admin account will automatically be created based on the admin credentials in that file, so pay attention to those. To start the container, run the following command in the same directory as the docker-compose.yml file. This will automatically setup the database and run the Khoj server.
|
||||
|
||||
```shell
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
Khoj should now be running at http://localhost:42110. You can see the web UI in your browser.
|
||||
|
||||
### 1. Installation (Local)
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
##### Install Postgres (with PgVector)
|
||||
|
||||
Khoj uses the `pgvector` package to store embeddings of your index in a Postgres database. In order to use this, you need to have Postgres installed.
|
||||
|
||||
<!-- tabs:start -->
|
||||
|
||||
#### **MacOS**
|
||||
|
||||
Install [Postgres.app](https://postgresapp.com/). This comes pre-installed with `pgvector` and relevant dependencies.
|
||||
|
||||
#### **Windows**
|
||||
|
||||
Use the [recommended installer](https://www.postgresql.org/download/windows/)
|
||||
|
||||
#### **Linux**
|
||||
From [official instructions](https://wiki.postgresql.org/wiki/Apt)
|
||||
|
||||
```bash
|
||||
sudo apt install -y postgresql-common
|
||||
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
|
||||
sudo apt install postgres-16 postgresql-16-pgvector
|
||||
```
|
||||
|
||||
##### **From Source**
|
||||
1. Follow instructions to [Install Postgres](https://www.postgresql.org/download/)
|
||||
2. Follow instructions to [Install PgVector](https://github.com/pgvector/pgvector#installation) in case you need to manually install it. Reproduced instructions below for convenience.
|
||||
|
||||
```bash
|
||||
cd /tmp
|
||||
git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git
|
||||
cd pgvector
|
||||
make
|
||||
make install # may need sudo
|
||||
```
|
||||
|
||||
<!-- tabs:end -->
|
||||
|
||||
|
||||
##### Create the Khoj database
|
||||
|
||||
Make sure to update your environment variables to match your Postgres configuration if you're using a different name. The default values should work for most people.
|
||||
|
||||
<!-- tabs:start -->
|
||||
|
||||
#### **MacOS**
|
||||
```bash
|
||||
createdb khoj -U postgres
|
||||
```
|
||||
|
||||
#### **Windows**
|
||||
```bash
|
||||
createdb khoj -U postgres
|
||||
```
|
||||
|
||||
#### **Linux**
|
||||
```bash
|
||||
sudo -u postgres createdb khoj
|
||||
```
|
||||
|
||||
<!-- tabs:end -->
|
||||
|
||||
#### Install package
|
||||
|
||||
##### Local Server Setup
|
||||
- *Make sure [python](https://realpython.com/installing-python/) and [pip](https://pip.pypa.io/en/stable/installation/) are installed on your machine*
|
||||
|
||||
Run the following command in your terminal to install the Khoj backend.
|
||||
|
||||
<!-- tabs:start -->
|
||||
|
||||
#### **MacOS**
|
||||
|
||||
```shell
|
||||
python -m pip install khoj-assistant
|
||||
```
|
||||
|
||||
#### **Windows**
|
||||
|
||||
```shell
|
||||
py -m pip install khoj-assistant
|
||||
```
|
||||
For more detailed Windows installation and troubleshooting, see [Windows Install](./windows_install.md).
|
||||
|
||||
#### **Linux**
|
||||
|
||||
```shell
|
||||
python -m pip install khoj-assistant
|
||||
```
|
||||
|
||||
<!-- tabs:end -->
|
||||
|
||||
##### Local Server Start
|
||||
|
||||
Run the following command from your terminal to start the Khoj backend and open Khoj in your browser.
|
||||
|
||||
```shell
|
||||
khoj --anonymous-mode
|
||||
```
|
||||
`--anonymous-mode` allows you to run the server without setting up Google credentials for login. This allows you to use any of the clients without a login wall. If you want to use Google login, you can skip this flag, but you will have to add your Google developer credentials.
|
||||
|
||||
On the first run, you will be prompted to input credentials for your admin account and do some basic configuration for your chat model settings. Once created, you can go to http://localhost:42110/server/admin and login with the credentials you just created.
|
||||
|
||||
Khoj should now be running at http://localhost:42110. You can see the web UI in your browser.
|
||||
|
||||
Note: To start Khoj automatically in the background use [Task scheduler](https://www.windowscentral.com/how-create-automated-task-using-task-scheduler-windows-10) on Windows or [Cron](https://en.wikipedia.org/wiki/Cron) on Mac, Linux (e.g with `@reboot khoj`)
|
||||
|
||||
|
||||
### 2. Download the desktop client
|
||||
|
||||
You can use our desktop executables to select file paths and folders to index. You can simply select the folders or files, and they'll be automatically uploaded to the server. Once you specify a file or file path, you don't need to update the configuration again; it will grab any data diffs dynamically over time.
|
||||
|
||||
**To download the latest desktop client, go to https://download.khoj.dev** and the correct executable for your OS will automatically start downloading. Once downloaded, you can configure your folders for indexing using the settings tab. To set your chat configuration, you'll have to use the web interface for the Khoj server you setup in the previous step.
|
||||
|
||||
To use the desktop client, you need to go to your Khoj server's settings page (http://localhost:42110/config) and copy the API key. Then, paste it into the desktop client's settings page. Once you've done that, you can select files and folders to index.
|
||||
|
||||
### 3. Configure
|
||||
1. Go to http://localhost:42110/server/admin and login with your admin credentials. Go to the ChatModelOptions if you want to add additional models for chat.
|
||||
1. Select files and folders to index [using the desktop client](./setup.md?id=_2-download-the-desktop-client). When you click 'Save', the files will be sent to your server for indexing.
|
||||
- Select Notion workspaces and Github repositories to index using the web interface.
|
||||
|
||||
### 4. Install Client Plugins (Optional)
|
||||
Khoj exposes a web interface to search, chat and configure by default.<br />
|
||||
The optional steps below allow using Khoj from within an existing application like Obsidian or Emacs.
|
||||
|
||||
- **Khoj Obsidian**:<br />
|
||||
[Install](/obsidian?id=_2-setup-plugin) the Khoj Obsidian plugin
|
||||
|
||||
- **Khoj Emacs**:<br />
|
||||
[Install](/emacs?id=setup) khoj.el
|
||||
|
||||
#### Setup host URL
|
||||
To configure your host URL on your clients when self-hosting, use `http://127.0.0.1:42110`. This is the default value for the `KHOJ_HOST` environment variable. Note that `localhost` will not work.
|
||||
|
||||
### 5. Use Khoj 🚀
|
||||
|
||||
You can head to http://localhost:42110 to use the web interface. You can also use the desktop client to search and chat.
|
||||
|
||||
## Upgrade
|
||||
### Upgrade Khoj Server
|
||||
|
||||
<!-- tabs:start -->
|
||||
|
||||
#### **Local Setup**
|
||||
|
||||
```shell
|
||||
pip install --upgrade khoj-assistant
|
||||
```
|
||||
|
||||
*Note: To upgrade to the latest pre-release version of the khoj server run below command*
|
||||
```shell
|
||||
# Maps to the latest commit on the master branch
|
||||
pip install --upgrade --pre khoj-assistant
|
||||
```
|
||||
|
||||
#### **Docker**
|
||||
From the same directory where you have your `docker-compose` file, this will fetch the latest build and upgrade your server.
|
||||
|
||||
```shell
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
<!-- tabs:end -->
|
||||
|
||||
|
||||
### Upgrade Khoj on Emacs
|
||||
- Use your Emacs Package Manager to Upgrade
|
||||
- See [khoj.el package setup](/emacs?id=setup) for details
|
||||
|
||||
### Upgrade Khoj on Obsidian
|
||||
- Upgrade via the Community plugins tab on the settings pane in the Obsidian app
|
||||
- See the [khoj plugin setup](/obsidian.md?id=_2-setup-plugin) for details
|
||||
|
||||
## Uninstall
|
||||
1. (Optional) Hit `Ctrl-C` in the terminal running the khoj server to stop it
|
||||
2. Delete the khoj directory in your home folder (i.e `~/.khoj` on Linux, Mac or `C:\Users\<your-username>\.khoj` on Windows)
|
||||
5. You might want to `rm -rf` the following directories:
|
||||
- `~/.khoj`
|
||||
- `~/.cache/gpt4all`
|
||||
3. Uninstall the khoj server with `pip uninstall khoj-assistant`
|
||||
4. (Optional) Uninstall khoj.el or the khoj obsidian plugin in the standard way on Emacs, Obsidian
|
||||
|
||||
## Troubleshoot
|
||||
|
||||
#### 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:
|
||||
```shell
|
||||
brew install rustup
|
||||
rustup-init
|
||||
source ~/.cargo/env
|
||||
```
|
||||
- **Refer**: [Issue with Fix](https://github.com/khoj-ai/khoj/issues/82#issuecomment-1241890946) for more details
|
||||
|
||||
#### Search starts giving wonky results
|
||||
- **Fix**: Open [/api/update?force=true](http://localhost:42110/api/update?force=true) in browser to regenerate index from scratch
|
||||
- **Note**: *This is a fix for when you perceive the search results have degraded. Not if you think they've always given wonky results*
|
||||
|
||||
#### Khoj in Docker errors out with \"Killed\" in error message
|
||||
- **Fix**: Increase RAM available to Docker Containers in Docker Settings
|
||||
- **Refer**: [StackOverflow Solution](https://stackoverflow.com/a/50770267), [Configure Resources on Docker for Mac](https://docs.docker.com/desktop/mac/#resources)
|
||||
|
||||
#### Khoj errors out complaining about Tensors mismatch or null
|
||||
- **Mitigation**: Disable `image` search using the desktop GUI
|
||||
@@ -1,23 +0,0 @@
|
||||
# Windows Installation
|
||||
|
||||
These steps can be used to setup Khoj on a clean, new Windows 11 machine. It has been tested on a Windows VM
|
||||
|
||||
## Prerequisites
|
||||
1. Ensure you have Visual Studio C++ Build tools installed. You can download it [from Microsoft here](https://visualstudio.microsoft.com/visual-cpp-build-tools/). At the minimum, you should have the following configuration:
|
||||
<img width="1152" alt="Screenshot 2023-07-12 at 3 56 25 PM" src="https://github.com/khoj-ai/khoj/assets/65192171/b506a858-2f5e-4c85-946b-5422d83f112a">
|
||||
2. Ensure you have Python installed. You can check by running `python --version`. If you don't, install the latest version [from here](https://www.python.org/downloads/).
|
||||
- Ensure you have pip installed: `py -m ensurepip --upgrade`.
|
||||
|
||||
## Quick start
|
||||
1. Open a PowerShell terminal.
|
||||
2. Run `pip install khoj-assistant`
|
||||
3. Start Khoj with `khoj`
|
||||
|
||||
## Installation in a Virtual Environment
|
||||
Use this if you want to install with a virtual environment. This will make it much easier to manage your dependencies. You can read more about [virtual environments](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/) here.
|
||||
|
||||
1. Open a PowerShell terminal with the `Run as Administrator` privileges.
|
||||
2. Create a virtual environment: `mkdir khoj && cd khoj && py -m venv .venv`
|
||||
3. Activate the virtual environment: `.\.venv\Scripts\activate`. If you get a permissions error, then run `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned`.
|
||||
4. Run `pip install khoj-assistant`
|
||||
5. Start Khoj with `khoj`
|
||||
20
documentation/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
# Production
|
||||
/build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
41
documentation/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Website
|
||||
|
||||
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
|
||||
|
||||
### Installation
|
||||
|
||||
```
|
||||
$ yarn
|
||||
```
|
||||
|
||||
### Local Development
|
||||
|
||||
```
|
||||
$ yarn start
|
||||
```
|
||||
|
||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||
|
||||
### Build
|
||||
|
||||
```
|
||||
$ yarn build
|
||||
```
|
||||
|
||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||
|
||||
### Deployment
|
||||
|
||||
Using SSH:
|
||||
|
||||
```
|
||||
$ USE_SSH=true yarn deploy
|
||||
```
|
||||
|
||||
Not using SSH:
|
||||
|
||||
```
|
||||
$ GIT_USER=<Your GitHub username> yarn deploy
|
||||
```
|
||||
|
||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 350 KiB After Width: | Height: | Size: 350 KiB |
|
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 298 KiB |
|
Before Width: | Height: | Size: 302 KiB After Width: | Height: | Size: 302 KiB |
|
Before Width: | Height: | Size: 394 KiB After Width: | Height: | Size: 394 KiB |
|
Before Width: | Height: | Size: 358 KiB After Width: | Height: | Size: 358 KiB |
74
documentation/assets/img/khoj_clients.svg
Normal file
|
After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 544 KiB After Width: | Height: | Size: 544 KiB |
62
documentation/assets/img/khoj_datasources.svg
Normal file
|
After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 333 KiB After Width: | Height: | Size: 333 KiB |
|
Before Width: | Height: | Size: 445 KiB After Width: | Height: | Size: 445 KiB |
|
Before Width: | Height: | Size: 333 KiB After Width: | Height: | Size: 333 KiB |
|
Before Width: | Height: | Size: 420 KiB After Width: | Height: | Size: 420 KiB |
|
Before Width: | Height: | Size: 478 KiB After Width: | Height: | Size: 478 KiB |
|
Before Width: | Height: | Size: 268 KiB After Width: | Height: | Size: 268 KiB |
1
documentation/assets/img/logo.svg
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
3
documentation/babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
|
||||
};
|
||||
8
documentation/docs/clients/_category_.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Clients",
|
||||
"position": 4,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Different ways for indexing data with the Khoj backend"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
<h1><img src="./assets/khoj-logo-sideways-500.png" width="200" alt="Khoj Logo"> Desktop</h1>
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
> An AI copilot for your Second Brain
|
||||
# Desktop
|
||||
|
||||
> Query your Second Brain from your machine
|
||||
|
||||
Use the Desktop app to chat and search with Khoj.
|
||||
You can also sync any relevant files with Khoj using the app.
|
||||
Khoj will use these files to provide contextual reponses when you search or chat.
|
||||
|
||||
## Features
|
||||
- **Chat**
|
||||
@@ -19,5 +27,6 @@
|
||||
4. [Optional] Add any files, folders you'd like Khoj to be aware of on the *Settings* page and Click *Save*
|
||||
|
||||
## Interface
|
||||

|
||||

|
||||
| Chat | Search |
|
||||
|:----:|:------:|
|
||||
|  |  |
|
||||
@@ -1,13 +1,18 @@
|
||||
<h1><img src="./assets/khoj-logo-sideways-500.png" width="200" alt="Khoj Logo"> Emacs</h1>
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
> An AI copilot for your Second Brain in Emacs
|
||||
# Emacs
|
||||
|
||||
<img src="https://stable.melpa.org/packages/khoj-badge.svg" width="150" alt="Melpa Stable Badge">
|
||||
<img src="https://melpa.org/packages/khoj-badge.svg" width="150" alt="Melpa Badge">
|
||||
<img src="https://stable.melpa.org/packages/khoj-badge.svg" width="130" alt="Melpa Stable Badge" />
|
||||
<img src="https://melpa.org/packages/khoj-badge.svg" width="150" alt="Melpa Badge" />
|
||||
|
||||
<img src="https://github.com/khoj-ai/khoj/actions/workflows/build_khoj_el.yml/badge.svg" width="150" alt="Build Badge">
|
||||
<img src="https://github.com/khoj-ai/khoj/actions/workflows/test_khoj_el.yml/badge.svg" width="150" alt="Test Badge">
|
||||
<img src="https://github.com/khoj-ai/khoj/actions/workflows/build_khoj_el.yml/badge.svg" width="150" alt="Build Badge" />
|
||||
<img src="https://github.com/khoj-ai/khoj/actions/workflows/test_khoj_el.yml/badge.svg" width="150" alt="Test Badge" />
|
||||
|
||||
<br />
|
||||
<br />
|
||||
> Query your Second Brain from Emacs
|
||||
|
||||
## Features
|
||||
- **Chat**
|
||||
@@ -19,19 +24,16 @@
|
||||
- **Incremental**: Incremental search for a fast, search-as-you-type experience
|
||||
|
||||
## Interface
|
||||
#### Search
|
||||

|
||||
|
||||
#### Chat
|
||||

|
||||
| Search | Chat |
|
||||
|:------:|:----:|
|
||||
|  |  |
|
||||
|
||||
## Setup
|
||||
1. Generate an API key on the [Khoj Web App](https://app.khoj.dev/config#clients)
|
||||
2. Add below snippet to your Emacs config file, usually at `~/.emacs.d/init.el`
|
||||
|
||||
|
||||
<!-- tabs:start -->
|
||||
|
||||
#### **Direct Install**
|
||||
*Khoj will index your org-agenda files, by default*
|
||||
|
||||
@@ -83,16 +85,15 @@ M-x package-install khoj
|
||||
khoj-org-files '("~/docs/todo.org" "~/docs/work.org")))
|
||||
```
|
||||
|
||||
<!-- tabs:end -->
|
||||
## Use
|
||||
### Search
|
||||
See [Khoj Search](search.md) for details
|
||||
See [Khoj Search](/features/search) for details
|
||||
1. Hit `C-c s s` (or `M-x khoj RET s`) to open khoj search
|
||||
2. Enter your query in natural language<br/>
|
||||
E.g *"What is the meaning of life?"*, *"My life goals for 2023"*
|
||||
|
||||
### Chat
|
||||
See [Khoj Chat](chat.md) for details
|
||||
See [Khoj Chat](/features/chat) for details
|
||||
1. Hit `C-c s c` (or `M-x khoj RET c`) to open khoj chat
|
||||
2. Ask questions in a natural, conversational style<br/>
|
||||
E.g *"When did I file my taxes last year?"*
|
||||
@@ -113,7 +114,7 @@ This feature finds entries similar to the one you are currently on.
|
||||
- Note: If you have [speed keys](https://orgmode.org/manual/Speed-Keys.html) enabled, `o 2` will also work
|
||||
|
||||
### Khoj Menu
|
||||

|
||||

|
||||
Hit `C-c s` (or `M-x khoj`) to open the khoj menu above. Then:
|
||||
- Hit `t` until you preferred content type is selected in the khoj menu
|
||||
`Content Type` specifies the content to perform `Search`, `Update` or `Find Similar` actions on
|
||||
@@ -1,6 +1,10 @@
|
||||
<h1><img src="./assets/khoj-logo-sideways-500.png" width="200" alt="Khoj Logo"> Obsidian</h1>
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
> An AI copilot for your Second Brain in Obsidian
|
||||
# Obsidian
|
||||
|
||||
> Query your Second Brain from Obsidian
|
||||
|
||||
## Features
|
||||
- **Chat**
|
||||
@@ -12,8 +16,10 @@
|
||||
- **Incremental**: Incremental search for a fast, search-as-you-type experience
|
||||
|
||||
## Interface
|
||||

|
||||

|
||||
|
||||
| Search | Chat |
|
||||
|:------:|:----:|
|
||||
|  |  |
|
||||
|
||||
|
||||
## Setup
|
||||
@@ -30,7 +36,7 @@ See the official [Obsidian Plugin Docs](https://help.obsidian.md/Extending+Obsid
|
||||
Run *Khoj: Chat* from the [Command Palette](https://help.obsidian.md/Plugins/Command+palette) and ask questions in a natural, conversational style.<br />
|
||||
E.g *"When did I file my taxes last year?"*
|
||||
|
||||
See [Khoj Chat](/chat) for more details
|
||||
See [Khoj Chat](/features/chat) for more details
|
||||
|
||||
### Find Similar Notes
|
||||
To see other notes similar to the current one, run *Khoj: Find Similar Notes* from the [Command Palette](https://help.obsidian.md/Plugins/Command+palette)
|
||||
@@ -38,7 +44,7 @@ To see other notes similar to the current one, run *Khoj: Find Similar Notes* fr
|
||||
### 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)
|
||||
|
||||
See [Khoj Search](/search) for more details. Use [query filters](/advanced#query-filters) to limit entries to search
|
||||
See [Khoj Search](/features/search) for more details. Use [query filters](/miscellaneous/advanced#query-filters) to limit entries to search
|
||||
|
||||
[search_demo](https://user-images.githubusercontent.com/6413477/218801155-cd67e8b4-a770-404a-8179-d6b61caa0f93.mp4 ':include :type=mp4')
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<h1><img src="./assets/khoj-logo-sideways-500.png" width="200" alt="Khoj Logo"> Web</h1>
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
> An AI copilot for your Second Brain
|
||||
# Web
|
||||
|
||||
> Query your Second Brain from your Web Browser
|
||||
|
||||
Without any desktop clients, you can start chatting with Khoj on the web. Bear in mind you do need one of the desktop clients in order to share and sync your data with Khoj.
|
||||
|
||||
## Features
|
||||
- **Chat**
|
||||
@@ -15,5 +21,7 @@
|
||||
No setup required. The Khoj web app is the default interface to Khoj. You can access it from any web browser. Try it on [Khoj Cloud](https://app.khoj.dev)
|
||||
|
||||
## Interface
|
||||

|
||||

|
||||
|
||||
| Search | Chat |
|
||||
|:------:|:----:|
|
||||
|  |  |
|
||||
28
documentation/docs/clients/whatsapp.md
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# WhatsApp
|
||||
|
||||
> Query your Second Brain from WhatsApp
|
||||
|
||||
Text [+1 (848) 800 4242](https://wa.me/18488004242) or scan [this QR code](https://khoj.dev/whatsapp) on your phone to chat with Khoj on WhatsApp.
|
||||
|
||||
Without any desktop clients, you can start chatting with Khoj on WhatsApp. Bear in mind you do need one of the desktop clients in order to share and sync your data with Khoj. The WhatsApp AI bot will work right away for answering generic queries and using Khoj in default mode.
|
||||
|
||||
In order to use Khoj on WhatsApp with your own data, you need to setup a Khoj Cloud account and connect your WhatsApp account to it. This is a one time setup and you can do it from the [Khoj Cloud config page](https://app.khoj.dev/config).
|
||||
|
||||
If you hit usage limits for the WhatsApp bot, upgrade to [a paid plan](https://khoj.dev/pricing) on Khoj Cloud.
|
||||
|
||||
## Features
|
||||
|
||||
- **Slash Commands**: Use slash commands to quickly access Khoj features
|
||||
- `/online`: Get responses from Khoj powered by online search.
|
||||
- `/dream`: Generate an image in response to your prompt.
|
||||
- `/notes`: Explicitly force Khoj to retrieve context from your notes. Note: You'll need to connect your WhatsApp account to a Khoj Cloud account for this to work.
|
||||
|
||||
We have more commands under development, including `/share` to uploading documents directly to your Khoj account from WhatsApp, and `/speak` in order to get a speech response from Khoj. Feel free to [raise an issue](https://github.com/khoj-ai/flint/issues) if you have any suggestions for new commands.
|
||||
|
||||
## Nerdy Details
|
||||
|
||||
You can find all of the code for the WhatsApp bot in the the [flint repository](https://github.com/khoj-ai/flint). As all of our code, it is open source and you can contribute to it.
|
||||
8
documentation/docs/contributing/_category_.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Contributing",
|
||||
"position": 2,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Development Setup"
|
||||
}
|
||||
}
|
||||
181
documentation/docs/contributing/development.mdx
Normal file
@@ -0,0 +1,181 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
---
|
||||
|
||||
# Development
|
||||
|
||||
Welcome to the development docs of Khoj! Thanks for you interesting in being a contributor ❤️. Open source contributors are a corner-store of the Khoj community. We welcome all contributions, big or small.
|
||||
|
||||
To get started with contributing, check out the official GitHub docs on [contributing to an open-source project](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project).
|
||||
|
||||
Join the [Discord](https://discord.gg/WaxF3SkFPU) server and click the ✅ for the question "Are you interested in becoming a contributor?" in the `#welcome-and-rules` channel. This will give you access to the `#contributors` channel where you can ask questions and get help from other contributors.
|
||||
|
||||
If you're looking for a place to get started, check out the list of [Github Issues](https://github.com/khoj-ai/khoj/issues) with the tag `good first issue` to find issues that are good for first-time contributors.
|
||||
|
||||
## Local Server Installation
|
||||
### Using Pip
|
||||
#### 1. Install
|
||||
```mdx-code-block
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
```
|
||||
|
||||
```mdx-code-block
|
||||
<Tabs>
|
||||
<TabItem value="macos" label="MacOS">
|
||||
```shell
|
||||
# Get Khoj Code
|
||||
git clone https://github.com/khoj-ai/khoj && cd khoj
|
||||
|
||||
# Create, Activate Virtual Environment
|
||||
python3 -m venv .venv && source .venv/bin/activate
|
||||
|
||||
# For MacOS or zsh users run this
|
||||
pip install -e '.[dev]'
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="win" label="Windows">
|
||||
```shell
|
||||
# Get Khoj Code
|
||||
git clone https://github.com/khoj-ai/khoj && cd khoj
|
||||
|
||||
# Create, Activate Virtual Environment
|
||||
python3 -m venv .venv && .venv\Scripts\activate
|
||||
|
||||
# Install Khoj for Development
|
||||
pip install -e .[dev]
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="unix" label="Linux">
|
||||
```shell
|
||||
# Get Khoj Code
|
||||
git clone https://github.com/khoj-ai/khoj && cd khoj
|
||||
|
||||
# Create, Activate Virtual Environment
|
||||
python3 -m venv .venv && source .venv/bin/activate
|
||||
|
||||
# Install Khoj for Development
|
||||
pip install -e .[dev]
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
|
||||
#### 2. Run
|
||||
1. Start Khoj
|
||||
```bash
|
||||
khoj -vv
|
||||
```
|
||||
2. Configure Khoj
|
||||
- **Via the Desktop application**: Add files, directories to index using the settings page of your desktop application. Click "Save" to immediately trigger indexing.
|
||||
|
||||
Note: Wait after configuration for khoj to Load ML model, generate embeddings and expose API to query notes, images, documents etc specified in config YAML
|
||||
|
||||
### Using Docker
|
||||
|
||||
Make sure you install the latest version of [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/).
|
||||
|
||||
#### 1. Clone
|
||||
|
||||
```shell
|
||||
git clone https://github.com/khoj-ai/khoj && cd khoj
|
||||
```
|
||||
|
||||
#### 2. Configure
|
||||
|
||||
1. Update [docker-compose.yml](https://github.com/khoj-ai/khoj/blob/master/docker-compose.yml) to use relevant environment variables.
|
||||
2. Comment out the `image` line and uncomment the `build` line in the `server` service
|
||||
|
||||
#### 3. Run
|
||||
|
||||
This will start the Khoj server, and the database.
|
||||
|
||||
```shell
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
#### 4. Upgrade
|
||||
|
||||
If you've made changes to the codebase, you'll need to rebuild the Docker image before running the container again.
|
||||
|
||||
```shell
|
||||
docker-compose build --no-cache
|
||||
```
|
||||
|
||||
## Update clients
|
||||
In whichever clients you're using for testing, you'll need to update the server URL to point to your local server. By default, the local server URL should be `http://127.0.0.1:42110`.
|
||||
|
||||
## Validate
|
||||
### Before Making Changes
|
||||
1. Install Git Hooks for Validation
|
||||
```shell
|
||||
pre-commit install -t pre-push -t pre-commit
|
||||
```
|
||||
- This ensures standard code formatting fixes and other checks run automatically on every commit and push
|
||||
- Note 1: If [pre-commit](https://pre-commit.com/#intro) didn't already get installed, [install it](https://pre-commit.com/#install) via `pip install pre-commit`
|
||||
- Note 2: To run the pre-commit changes manually, use `pre-commit run --hook-stage manual --all` before creating PR
|
||||
|
||||
### Before Creating PR
|
||||
|
||||
:::tip[Note]
|
||||
You should be in an active virtual environment for Khoj in order to run the unit tests and linter.
|
||||
:::
|
||||
|
||||
1. Ensure that you have a [Github Issue](https://github.com/khoj-ai/khoj/issues) that can be linked to the PR. If not, create one. Make sure you've tagged one of the maintainers to the issue. This will ensure that the maintainers are notified of the PR and can review it. It's best discuss the code design on an existing issue or Discord thread before creating a PR. This helps get your PR merged faster.
|
||||
1. Run unit tests.
|
||||
```shell
|
||||
pytest
|
||||
```
|
||||
2. Run the linter.
|
||||
```shell
|
||||
mypy
|
||||
```
|
||||
4. Think about how to add unit tests to verify the functionality you're adding in the PR. If you're not sure how to do this, ask for help in the Github issue or on Discord's `#contributors` channel.
|
||||
|
||||
### After Creating PR
|
||||
1. Automated [validation workflows](https://github.com/khoj-ai/khoj/tree/master/.github/workflows) should run for every PR. Tag one of the maintainers in the PR to trigger it.
|
||||
|
||||
## Obsidian Plugin Development
|
||||
### Plugin development setup
|
||||
The core code for the Obsidian plugin is under `src/interface/obsidian`. The file `main.ts` is a good place to start.
|
||||
|
||||
1. In your CLI, go to the directory `src/interface/obsidian` in the Khoj repository.
|
||||
2. Run `yarn install` to install the dependencies.
|
||||
3. Run `yarn dev` to start the development server. This will continually rebuild the plugin as you make changes to the code.
|
||||
- Your code changes will be outputted to a file called `main.js` in the `obsidian` directory.
|
||||
|
||||
### Loading your development plugin in Obsidian
|
||||
1. Make sure you have the Khoj plugin installed in Obsidian. [See the plugin page](https://publish.obsidian.md/hub/02+-+Community+Expansions/02.05+All+Community+Expansions/Plugins/khoj).
|
||||
1. Open Obsidian and go to your settings (gear icon in the bottom left corner)
|
||||
2. Click on 'Community Plugins' in the left panel
|
||||
3. Next to the 'Installed Plugins' heading, click on the folder icon to open the folder with the plugin's source code.
|
||||
4. Open the `khoj` folder in the file explorer that opens. You'll see a file called `main.js` in this folder. To test your changes, replace this file with the `main.js` file that was generated by the development server in the previous section.
|
||||
|
||||
## Create Khoj Release (Only for Maintainers)
|
||||
Follow the steps below to [release](https://github.com/debanjum/khoj/releases/) Khoj. This will create a stable release of Khoj on [Pypi](https://pypi.org/project/khoj-assistant/), [Melpa](https://stable.melpa.org/#%252Fkhoj) and [Obsidian](https://obsidian.md/plugins?id%253Dkhoj). It will also create desktop apps of Khoj and attach them to the latest release.
|
||||
|
||||
1. Create and tag release commit by running the bump_version script. The release commit sets version number in required metadata files.
|
||||
```shell
|
||||
./scripts/bump_version.sh -c "<release_version>"
|
||||
```
|
||||
2. Push commit and then the tag to trigger the release workflow to create Release with auto generated release notes.
|
||||
```shell
|
||||
git push origin master # push release commit to khoj repository
|
||||
git push origin <release_version> # push release tag to khoj repository
|
||||
```
|
||||
3. [Optional] Update the Release Notes to highlight new features, fixes and updates
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
## Visualize Codebase
|
||||
|
||||
*[Interactive Visualization](https://mango-dune-07a8b7110.1.azurestaticapps.net/?repo=debanjum%2Fkhoj)*
|
||||
|
||||

|
||||
|
||||
## Visualize Khoj Obsidian Plugin Codebase
|
||||
|
||||

|
||||
8
documentation/docs/features/_category_.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Features",
|
||||
"position": 3,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Features supported by Khoj"
|
||||
}
|
||||
}
|
||||
34
documentation/docs/features/all_features.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Features
|
||||
|
||||
Khoj supports a variety of features, including search and chat with a wide range of data sources and interfaces.
|
||||
|
||||
#### [Search](/features/search)
|
||||
- **Local**: Your personal data stays local. All search and indexing is done on your machine when you [self-host](/get-started/setup)
|
||||
- **Incremental**: Incremental search for a fast, search-as-you-type experience
|
||||
|
||||
#### [Chat](/features/chat)
|
||||
- **Faster answers**: Find answers faster, smoother than search. No need to manually scan through your notes to find answers.
|
||||
- **Iterative discovery**: Iteratively explore and (re-)discover your notes
|
||||
- **Assisted creativity**: Smoothly weave across answers retrieval and content generation
|
||||
- **Works online or offline**: Chat using online or offline AI chat models
|
||||
|
||||
#### General
|
||||
- **Cloud or Self-Host**: Use [cloud](https://app.khoj.dev/login) to use Khoj anytime from anywhere or [self-host](/get-started/setup) for privacy
|
||||
- **Natural**: Advanced natural language understanding using Transformer based ML Models
|
||||
- **Pluggable**: Modular architecture makes it easy to plug in new data sources, frontends and ML models
|
||||
- **Multiple Sources**: Index your Org-mode, Markdown, PDF, plaintext files, Github repos and Notion pages
|
||||
- **Multiple Interfaces**: Interact from your Web Browser, Emacs, Obsidian, Desktop app or even Whatsapp
|
||||
|
||||
### Supported Interfaces
|
||||
Khoj is available as a [Desktop app](/clients/desktop), [Emacs package](/clients/emacs), [Obsidian plugin](/clients/obsidian), [Web app](/clients/web) and a [Whatsapp AI](https://khoj.dev/whatsapp).
|
||||
|
||||

|
||||
|
||||
### Supported Data Sources
|
||||
Khoj can understand your org-mode, markdown, PDF, plaintext files, [Github projects](/online-data-sources/github_integration) and [Notion pages](/online-data-sources/notion_integration).
|
||||
|
||||

|
||||
@@ -1,4 +1,11 @@
|
||||
## Khoj Chat
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Chat
|
||||
|
||||
You can configure Khoj to chat with you about anything. When relevant, it'll use any notes or documents you shared with it to respond.
|
||||
|
||||
### Overview
|
||||
- Creates a personal assistant for you to inquire and engage with your notes
|
||||
- You can choose to use Online or Offline Chat depending on your requirements
|
||||
@@ -18,12 +25,17 @@ Offline chat stays completely private and works without internet using open-sour
|
||||
1. Open your [Khoj offline settings](http://localhost:42110/server/admin/database/offlinechatprocessorconversationconfig/) and click *Enable* on the Offline Chat configuration.
|
||||
2. Open your [Chat model options](http://localhost:42110/server/admin/database/chatmodeloptions/) and add a new option for the offline chat model you want to use. Make sure to use `Offline` as its type. We currently only support offline models that use the [Llama chat prompt](https://replicate.com/blog/how-to-prompt-llama#wrap-user-input-with-inst-inst-tags) format. We recommend using `mistral-7b-instruct-v0.1.Q4_0.gguf`.
|
||||
|
||||
!> **Note**: Offline chat is not supported for a multi-user scenario. The host machine will encounter segmentation faults if multiple users try to use offline chat at the same time.
|
||||
|
||||
:::tip[Note]
|
||||
Offline chat is not supported for a multi-user scenario. The host machine will encounter segmentation faults if multiple users try to use offline chat at the same time.
|
||||
:::
|
||||
|
||||
#### Online Chat
|
||||
Online chat requires internet to use ChatGPT but is faster, higher quality and less compute intensive.
|
||||
|
||||
!> **Warning**: This will enable Khoj to send your chat queries and query relevant notes to OpenAI for processing
|
||||
:::danger[Warning]
|
||||
This will enable Khoj to send your chat queries and query relevant notes to OpenAI for processing.
|
||||
:::
|
||||
|
||||
1. Get your [OpenAI API Key](https://platform.openai.com/account/api-keys)
|
||||
2. Open your [Khoj Online Chat settings](http://localhost:42110/server/admin/database/openaiprocessorconversationconfig/). Add a new setting with your OpenAI API key, and click *Save*. Only one configuration will be used, so make sure that's the only one you have.
|
||||
@@ -34,9 +46,9 @@ Online chat requires internet to use ChatGPT but is faster, higher quality and l
|
||||
- **On Web**: Open [/chat](https://app.khoj.dev/chat) in your web browser
|
||||
- **On Obsidian**: Search for *Khoj: Chat* in the [Command Palette](https://help.obsidian.md/Plugins/Command+palette)
|
||||
- **On Emacs**: Run `M-x khoj <user-query>`
|
||||
2. Enter your queries to chat with Khoj. Use [slash commands](#commands) and [query filters](./advanced.md#query-filters) to change what Khoj uses to respond
|
||||
2. Enter your queries to chat with Khoj. Use [slash commands](#commands) and [query filters](/miscellaneous/advanced#query-filters) to change what Khoj uses to respond
|
||||
|
||||

|
||||

|
||||
|
||||
#### Details
|
||||
1. Your query is used to retrieve the most relevant notes, if any, using Khoj search
|
||||
@@ -47,4 +59,6 @@ Slash commands allows you to change what Khoj uses to respond to your query
|
||||
- **/notes**: Limit chat to only respond using your notes, not just Khoj's general world knowledge as reference
|
||||
- **/general**: Limit chat to only respond using Khoj's general world knowledge, not using your notes as reference
|
||||
- **/default**: Allow chat to respond using your notes or it's general knowledge as reference. It's the default behavior when no slash command is used
|
||||
- **/online**: Use online information and incorporate it in the prompt to the LLM to send you a response.
|
||||
- **/image**: Generate an image in response to your query.
|
||||
- **/help**: Use /help to get all available commands and general information about Khoj
|
||||
@@ -1,10 +1,17 @@
|
||||
## Khoj Search
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Search
|
||||
|
||||
Take advantage of super fast search to find relevant notes and documents from your Second Brain.
|
||||
|
||||
### Use
|
||||
1. Open Khoj Search
|
||||
- **On Web**: Open <https://app.khoj.dev/> in your web browser
|
||||
- **On Web**: Open https://app.khoj.dev/ in your web browser
|
||||
- **On Obsidian**: Click the *Khoj search* icon 🔎 on the [Ribbon](https://help.obsidian.md/User+interface/Workspace/Ribbon) or Search for *Khoj: Search* in the [Command Palette](https://help.obsidian.md/Plugins/Command+palette)
|
||||
- **On Emacs**: Run `M-x khoj <user-query>`
|
||||
2. Query using natural language to find relevant entries from your knowledge base. Use [query filters](./advanced.md#query-filters) to limit entries to search
|
||||
2. Query using natural language to find relevant entries from your knowledge base. Use [query filters](/miscellaneous/advanced#query-filters) to limit entries to search
|
||||
|
||||
### Demo
|
||||

|
||||

|
||||
8
documentation/docs/get-started/_category_.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Get Started",
|
||||
"position": 1,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Learn how to get started with using Khoj"
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
## Demos
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Demos
|
||||
|
||||
Check out a couple of demos and screenshots of Khoj in action.
|
||||
|
||||
### Screenshots
|
||||
#### Web
|
||||

|
||||

|
||||
|
||||
#### Obsidian
|
||||

|
||||

|
||||
|
||||
#### Emacs
|
||||

|
||||

|
||||
| Web | Obsidian | Emacs |
|
||||
|:---:|:--------:|:-----:|
|
||||
|  |  |  |
|
||||
|  |  |  |
|
||||
|
||||
|
||||
### Videos
|
||||
#### Khoj in Obsidian
|
||||
[KhojObsidian](https://github-production-user-asset-6210df.s3.amazonaws.com/6413477/240061700-3e33d8ea-25bb-46c8-a3bf-c92f78d0f56b.mp4 ':include :type=mp4')
|
||||
[Link to Video](https://github-production-user-asset-6210df.s3.amazonaws.com/6413477/240061700-3e33d8ea-25bb-46c8-a3bf-c92f78d0f56b.mp4)
|
||||
|
||||
##### Installation
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
- Jump to the [search result](https://marcus.se.net/obsidian-plugin-docs/publishing/submit-your-plugin)
|
||||
|
||||
#### Khoj in Emacs, Browser
|
||||
[KhojEmacs](https://user-images.githubusercontent.com/6413477/184735169-92c78bf1-d827-4663-9087-a1ea194b8f4b.mp4 ':include :type=mp4')
|
||||
[Link to Video](https://user-images.githubusercontent.com/6413477/184735169-92c78bf1-d827-4663-9087-a1ea194b8f4b.mp4)
|
||||
|
||||
##### Installation
|
||||
|
||||
52
documentation/docs/get-started/overview.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
slug: /
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
<p align="center"><img src="/img/khoj-logo-sideways-500.png" width="200" alt="Khoj Logo"></img></p>
|
||||
|
||||
<div align="center">
|
||||
<b>An AI copilot for your Second Brain</b>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<div align="center">
|
||||
|
||||
[📜 Explore Code](https://github.com/khoj-ai/khoj)
|
||||
<span> • </span>
|
||||
[🌍 Try Khoj Cloud](https://khoj.dev)
|
||||
<span> • </span>
|
||||
[💬 Get Involved](https://discord.gg/BDgyabRM6e)
|
||||
|
||||
</div>
|
||||
|
||||
## Introduction
|
||||
Welcome to the Khoj Docs! This is the best place to get setup and explore Khoj's features.
|
||||
|
||||
- Khoj is an open source, personal AI
|
||||
- You can [chat](/features/chat) with it about anything. It'll use files you shared with it to respond, when relevant
|
||||
- Quickly [find](/features/search) relevant notes and documents using natural language
|
||||
- It understands pdf, plaintext, markdown, org-mode files, [notion pages](/online-data-sources/notion_integration) and [github repositories](/online-data-sources/github_integration)
|
||||
- Access it from your [Emacs](/clients/emacs), [Obsidian](/clients/obsidian), [Web browser](/clients/web) or the [Khoj Desktop app](/clients/desktop)
|
||||
- Use [cloud](https://app.khoj.dev/login) to access your Khoj anytime from anywhere, [self-host](/get-started/setup) on consumer hardware for privacy
|
||||
|
||||
## Quickstart
|
||||
- [Try Khoj Cloud](https://app.khoj.dev) to get started quickly
|
||||
- [Read these instructions](/get-started/setup) to self-host a private instance of Khoj
|
||||
|
||||
## At a Glance
|
||||
<img src="https://docs.khoj.dev/img/khoj_search_on_web.png" width="400px" />
|
||||
<span> </span>
|
||||
<img src="https://docs.khoj.dev/img/khoj_chat_on_web.png" width="400px" />
|
||||
|
||||
#### [Search](/features/search)
|
||||
- **Natural**: Use natural language queries to quickly find relevant notes and documents.
|
||||
- **Incremental**: Incremental search for a fast, search-as-you-type experience
|
||||
|
||||
#### [Chat](/features/chat)
|
||||
- **Faster answers**: Find answers faster, smoother than search. No need to manually scan through your notes to find answers.
|
||||
- **Iterative discovery**: Iteratively explore and (re-)discover your notes
|
||||
- **Assisted creativity**: Smoothly weave across answers retrieval and content generation
|
||||
- **Online or Offline**: Choose online or offline chat depending on your requirements
|
||||
281
documentation/docs/get-started/setup.mdx
Normal file
@@ -0,0 +1,281 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Self-Host
|
||||
Learn about how to self-host Khoj on your own machine.
|
||||
|
||||
```mdx-code-block
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
```
|
||||
|
||||
## Setup
|
||||
These are the general setup instructions for 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.
|
||||
|
||||
### Installation Option 1 (Docker)
|
||||
|
||||
#### Prerequisites
|
||||
1. Install Docker Engine. See [official instructions](https://docs.docker.com/engine/install/).
|
||||
2. Ensure you have Docker Compose. See [official instructions](https://docs.docker.com/compose/install/).
|
||||
|
||||
#### Setup
|
||||
|
||||
Use the sample docker-compose [in Github](https://github.com/khoj-ai/khoj/blob/master/docker-compose.yml) to run Khoj in Docker. Start by configuring all the environment variables to your choosing. Your admin account will automatically be created based on the admin credentials in that file, so pay attention to those. To start the container, run the following command in the same directory as the docker-compose.yml file. This will automatically setup the database and run the Khoj server.
|
||||
|
||||
```shell
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
Khoj should now be running at http://localhost:42110. You can see the web UI in your browser.
|
||||
|
||||
### Installation Option 2 (Local)
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
##### Install Postgres (with PgVector)
|
||||
|
||||
Khoj uses the `pgvector` package to store embeddings of your index in a Postgres database. In order to use this, you need to have Postgres installed.
|
||||
|
||||
```mdx-code-block
|
||||
<Tabs groupId="operating-systems">
|
||||
<TabItem value="macos" label="MacOS">
|
||||
Install [Postgres.app](https://postgresapp.com/). This comes pre-installed with `pgvector` and relevant dependencies.
|
||||
</TabItem>
|
||||
<TabItem value="win" label="Windows">
|
||||
1. Use the [recommended installer](https://www.postgresql.org/download/windows/).
|
||||
2. Follow instructions to [Install PgVector](https://github.com/pgvector/pgvector#windows) in case you need to manually install it. Windows support is experimental for pgvector currently, so we recommend using Docker.
|
||||
</TabItem>
|
||||
<TabItem value="unix" label="Linux">
|
||||
From [official instructions](https://wiki.postgresql.org/wiki/Apt)
|
||||
</TabItem>
|
||||
<TabItem value="source" label="From Source">
|
||||
1. Follow instructions to [Install Postgres](https://www.postgresql.org/download/)
|
||||
2. Follow instructions to [Install PgVector](https://github.com/pgvector/pgvector#installation) in case you need to manually install it.
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
##### Create the Khoj database
|
||||
|
||||
Make sure to update your environment variables to match your Postgres configuration if you're using a different name. The default values should work for most people. When prompted for a password, you can use the default password `postgres`, or configure it to your preference. Make sure to set the environment variable `POSTGRES_PASSWORD` to the same value as the password you set here.
|
||||
|
||||
```mdx-code-block
|
||||
<Tabs groupId="operating-systems">
|
||||
<TabItem value="macos" label="MacOS">
|
||||
```shell
|
||||
createdb khoj -U postgres --password
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="win" label="Windows">
|
||||
```shell
|
||||
createdb -U postgres khoj --password
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="unix" label="Linux">
|
||||
```shell
|
||||
sudo -u postgres createdb khoj --password
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
|
||||
#### Install package
|
||||
|
||||
##### Local Server Setup
|
||||
- *Make sure [python](https://realpython.com/installing-python/) and [pip](https://pip.pypa.io/en/stable/installation/) are installed on your machine*
|
||||
|
||||
Run the following command in your terminal to install the Khoj backend.
|
||||
|
||||
```mdx-code-block
|
||||
<Tabs groupId="operating-systems">
|
||||
<TabItem value="macos" label="MacOS">
|
||||
```shell
|
||||
python -m pip install khoj-assistant
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="win" label="Windows">
|
||||
```shell
|
||||
py -m pip install khoj-assistant
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="unix" label="Linux">
|
||||
```shell
|
||||
python -m pip install khoj-assistant
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
##### Local Server Start
|
||||
|
||||
Before getting started, configure the following environment variables in your terminal for the first run
|
||||
|
||||
```mdx-code-block
|
||||
<Tabs groupId="operating-systems">
|
||||
<TabItem value="macos" label="MacOS">
|
||||
```shell
|
||||
export KHOJ_ADMIN_EMAIL=<your-email>
|
||||
export KHOJ_ADMIN_PASSWORD=<your-password>
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="win" label="Windows">
|
||||
If you're using PowerShell:
|
||||
```shell
|
||||
$env:KHOJ_ADMIN_EMAIL="<your-email>"
|
||||
$env:KHOJ_ADMIN_PASSWORD="<your-password>"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="unix" label="Linux">
|
||||
```shell
|
||||
export KHOJ_ADMIN_EMAIL=<your-email>
|
||||
export KHOJ_ADMIN_PASSWORD=<your-password>
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
|
||||
Run the following command from your terminal to start the Khoj backend and open Khoj in your browser.
|
||||
|
||||
```shell
|
||||
khoj --anonymous-mode
|
||||
```
|
||||
`--anonymous-mode` allows you to run the server without setting up Google credentials for login. This allows you to use any of the clients without a login wall. If you want to use Google login, you can skip this flag, but you will have to add your Google developer credentials.
|
||||
|
||||
On the first run, you will be prompted to input credentials for your admin account and do some basic configuration for your chat model settings. Once created, you can go to http://localhost:42110/server/admin and login with the credentials you just created.
|
||||
|
||||
Khoj should now be running at http://localhost:42110. You can see the web UI in your browser.
|
||||
|
||||
Note: To start Khoj automatically in the background use [Task scheduler](https://www.windowscentral.com/how-create-automated-task-using-task-scheduler-windows-10) on Windows or [Cron](https://en.wikipedia.org/wiki/Cron) on Mac, Linux (e.g with `@reboot khoj`)
|
||||
|
||||
|
||||
### 2. Download the desktop client
|
||||
|
||||
You can use our desktop executables to select file paths and folders to index. You can simply select the folders or files, and they'll be automatically uploaded to the server. Once you specify a file or file path, you don't need to update the configuration again; it will grab any data diffs dynamically over time.
|
||||
|
||||
**To download the latest desktop client, go to https://download.khoj.dev** and the correct executable for your OS will automatically start downloading. Once downloaded, you can configure your folders for indexing using the settings tab. To set your chat configuration, you'll have to use the web interface for the Khoj server you setup in the previous step.
|
||||
|
||||
To use the desktop client, you need to go to your Khoj server's settings page (http://localhost:42110/config) and copy the API key. Then, paste it into the desktop client's settings page. Once you've done that, you can select files and folders to index.
|
||||
|
||||
### 3. Configure
|
||||
1. Go to http://localhost:42110/server/admin and login with your admin credentials.
|
||||
1. Go to [OpenAI settings](http://localhost:42110/server/admin/database/openaiprocessorconversationconfig/) in the server admin settings to add an Open AI processor conversation config. This is where you set your API key. Alternatively, you can go to the [offline chat settings](http://localhost:42110/server/admin/database/offlinechatprocessorconversationconfig/) and simply create a new setting with `Enabled` set to `True`.
|
||||
2. Go to the ChatModelOptions if you want to add additional models for chat. For example, you can specify `gpt-4` if you're using OpenAI or `mistral-7b-instruct-v0.1.Q4_0.gguf` if you're using offline chat. Make sure to configure the `type` field to `OpenAI` or `Offline` respectively.
|
||||
1. Select files and folders to index [using the desktop client](/get-started/setup#2-download-the-desktop-client). When you click 'Save', the files will be sent to your server for indexing.
|
||||
- Select Notion workspaces and Github repositories to index using the web interface.
|
||||
|
||||
|
||||
:::tip[Note]
|
||||
Using Safari on Mac? You might not be able to login to the admin panel. Try using Chrome or Firefox instead.
|
||||
:::
|
||||
|
||||
|
||||
### 4. Install Client Plugins (Optional)
|
||||
Khoj exposes a web interface to search, chat and configure by default.<br />
|
||||
The optional steps below allow using Khoj from within an existing application like Obsidian or Emacs.
|
||||
|
||||
- **Khoj Obsidian**:<br />
|
||||
[Install](/clients/obsidian#setup) the Khoj Obsidian plugin
|
||||
|
||||
- **Khoj Emacs**:<br />
|
||||
[Install](/clients/emacs#setup) khoj.el
|
||||
|
||||
#### Setup host URL
|
||||
To configure your host URL on your clients when self-hosting, use `http://127.0.0.1:42110`. This is the default value for the `KHOJ_HOST` environment variable. Note that `localhost` will not work.
|
||||
|
||||
### 5. Use Khoj 🚀
|
||||
|
||||
You can head to http://localhost:42110 to use the web interface. You can also use the desktop client to search and chat.
|
||||
|
||||
## Upgrade
|
||||
### Upgrade Khoj Server
|
||||
|
||||
```mdx-code-block
|
||||
<Tabs groupId="environment">
|
||||
<TabItem value="localsetup" label="Local Setup">
|
||||
```shell
|
||||
pip install --upgrade khoj-assistant
|
||||
```
|
||||
*Note: To upgrade to the latest pre-release version of the khoj server run below command*
|
||||
</TabItem>
|
||||
<TabItem value="docker" label="Docker">
|
||||
From the same directory where you have your `docker-compose` file, this will fetch the latest build and upgrade your server.
|
||||
```shell
|
||||
docker-compose up --build
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="emacs" label="Emacs">
|
||||
- Use your Emacs Package Manager to Upgrade
|
||||
- See [khoj.el package setup](/clients/emacs#setup) for details
|
||||
</TabItem>
|
||||
<TabItem value="obsidian" label="Obsidian">
|
||||
- Upgrade via the Community plugins tab on the settings pane in the Obsidian app
|
||||
- See the [khoj plugin setup](/clients/obsidian#setup) for details
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
## Uninstall
|
||||
### Uninstall Khoj Server
|
||||
|
||||
```mdx-code-block
|
||||
<Tabs groupId="environment">
|
||||
<TabItem value="localsetup" label="Local Setup">
|
||||
```shell
|
||||
# uninstall khoj server
|
||||
pip uninstall khoj-assistant
|
||||
|
||||
# delete khoj postgres db
|
||||
dropdb khoj -U postgres
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="docker" label="Docker">
|
||||
From the same directory where you have your `docker-compose` file, run the command below to remove the server to delete its containers, networks, images and volumes.
|
||||
|
||||
```shell
|
||||
docker-compose down --volumes
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="emacs" label="Emacs">
|
||||
Uninstall the khoj Emacs, or desktop client in the standard way from Emacs or your OS respectively
|
||||
You can also `rm -rf ~/.khoj` to remove the Khoj data directory if did a local install.
|
||||
</TabItem>
|
||||
<TabItem value="obsidian" label="Obsidian">
|
||||
Uninstall the khoj Obisidan, or desktop client in the standard way from Obsidian or your OS respectively
|
||||
You can also `rm -rf ~/.khoj` to remove the Khoj data directory if did a local install.
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
## Troubleshoot
|
||||
|
||||
#### 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:
|
||||
```shell
|
||||
brew install rustup
|
||||
rustup-init
|
||||
source ~/.cargo/env
|
||||
```
|
||||
- **Refer**: [Issue with Fix](https://github.com/khoj-ai/khoj/issues/82#issuecomment-1241890946) for more details
|
||||
|
||||
#### Search starts giving wonky results
|
||||
- **Fix**: Open [/api/update?force=true](http://localhost:42110/api/update?force=true) in browser to regenerate index from scratch
|
||||
- **Note**: *This is a fix for when you perceive the search results have degraded. Not if you think they've always given wonky results*
|
||||
|
||||
#### Khoj in Docker errors out with \"Killed\" in error message
|
||||
- **Fix**: Increase RAM available to Docker Containers in Docker Settings
|
||||
- **Refer**: [StackOverflow Solution](https://stackoverflow.com/a/50770267), [Configure Resources on Docker for Mac](https://docs.docker.com/desktop/mac/#resources)
|
||||
|
||||
#### Khoj errors out complaining about Tensors mismatch or null
|
||||
- **Mitigation**: Disable `image` search using the desktop GUI
|
||||
8
documentation/docs/miscellaneous/_category_.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Miscellaneous",
|
||||
"position": 6,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Additional resources for learning about Khoj"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
## Advanced Usage
|
||||
# Advanced Usage
|
||||
|
||||
### Search across Different Languages (Self-Hosting)
|
||||
To search for notes in multiple, different languages, you can use a [multi-lingual model](https://www.sbert.net/docs/pretrained_models.html#multi-lingual-models).<br />
|
||||
@@ -1,4 +1,9 @@
|
||||
## Credits
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Credits
|
||||
Many Open Source projects are used to power Khoj. Here's a few of them:
|
||||
|
||||
- [Multi-QA MiniLM Model](https://huggingface.co/sentence-transformers/multi-qa-MiniLM-L6-cos-v1), [All MiniLM Model](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2) for Text Search. See [SBert Documentation](https://www.sbert.net/examples/applications/retrieve_rerank/README.html)
|
||||
- [OpenAI CLIP Model](https://github.com/openai/CLIP) for Image Search. See [SBert Documentation](https://www.sbert.net/examples/applications/image-search/README.html)
|
||||
@@ -1,4 +1,10 @@
|
||||
## Performance
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Performance
|
||||
|
||||
Here are some top-level performance metrics for Khoj. These are rough estimates and will vary based on your hardware and data.
|
||||
|
||||
### Search performance
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
# Telemetry (self-hosting)
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Telemetry
|
||||
|
||||
We collect some high level, anonymized metadata about usage of Khoj. This includes:
|
||||
- Client (Web, Emacs, Obsidian)
|
||||
8
documentation/docs/online-data-sources/_category_.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Online Data Sources",
|
||||
"position": 5,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Online data sources for indexing via Khoj"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
# 🧑🏾💻 Setup the Github integration
|
||||
# Setup the Github integration
|
||||
|
||||
The Github integration allows you to index as many repositories as you want. It's currently default configured to index Issues, Commits, and all Markdown/Org files in each repository. For large repositories, this takes a fairly long time, but it works well for smaller projects.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## 📜 Notion Integration
|
||||
# Notion Integration
|
||||
|
||||
Khoj now supports search/chat with pages in your Notion workspaces. [Notion](notion.so/) is a platform people use for taking notes, especially for collaboration.
|
||||
The Notion integration allows you to search/chat with your Notion workspaces. [Notion](https://notion.so/) is a platform people use for taking notes, especially for collaboration.
|
||||
|
||||
We haven't setup a fancy integration with OAuth yet, so this integration still requires some effort on your end to generate an API key.
|
||||
|
||||
187
documentation/docusaurus.config.js
Normal file
@@ -0,0 +1,187 @@
|
||||
// @ts-check
|
||||
// `@type` JSDoc annotations allow editor autocompletion and type checking
|
||||
// (when paired with `@ts-check`).
|
||||
// There are various equivalent ways to declare your Docusaurus config.
|
||||
// See: https://docusaurus.io/docs/api/docusaurus-config
|
||||
|
||||
import {themes as prismThemes} from 'prism-react-renderer';
|
||||
|
||||
/** @type {import('@docusaurus/types').Config} */
|
||||
const config = {
|
||||
title: 'Khoj AI',
|
||||
tagline: 'An AI copilot for your Second Brain',
|
||||
|
||||
staticDirectories: ['assets'],
|
||||
|
||||
favicon: 'img/favicon-128x128.ico',
|
||||
|
||||
// Set the production url of your site here
|
||||
url: 'https://docs.khoj.dev',
|
||||
// Set the /<baseUrl>/ pathname under which your site is served
|
||||
// For GitHub pages deployment, it is often '/<projectName>/'
|
||||
baseUrl: '/',
|
||||
|
||||
// GitHub pages deployment config.
|
||||
// If you aren't using GitHub pages, you don't need these.
|
||||
organizationName: 'khoj-ai', // Usually your GitHub org/user name.
|
||||
projectName: 'khoj', // Usually your repo name.
|
||||
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
|
||||
// Even if you don't use internationalization, you can use this field to set
|
||||
// useful metadata like html lang. For example, if your site is Chinese, you
|
||||
// may want to replace "en" with "zh-Hans".
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en'],
|
||||
},
|
||||
|
||||
presets: [
|
||||
[
|
||||
'classic',
|
||||
/** @type {import('@docusaurus/preset-classic').Options} */
|
||||
({
|
||||
docs: {
|
||||
sidebarPath: './sidebars.js',
|
||||
routeBasePath: '/',
|
||||
// Please change this to your repo.
|
||||
// Remove this to remove the "edit this page" links.
|
||||
editUrl:
|
||||
'https://github.com/khoj-ai/khoj/tree/master/documentation/',
|
||||
},
|
||||
blog: {
|
||||
showReadingTime: true,
|
||||
// Please change this to your repo.
|
||||
// Remove this to remove the "edit this page" links.
|
||||
editUrl:
|
||||
'https://github.com/khoj-ai/khoj/tree/master/documentation/blog/',
|
||||
},
|
||||
theme: {
|
||||
customCss: './src/css/custom.css',
|
||||
},
|
||||
}),
|
||||
],
|
||||
],
|
||||
|
||||
themeConfig:
|
||||
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
|
||||
({
|
||||
image: 'img/khoj-logo-sideways-500.png',
|
||||
metadata: [
|
||||
{name: 'keywords', content: 'khoj, khoj ai, chatgpt, open ai, open source, productivity'},
|
||||
{name: 'og:title', content: 'Khoj Documentation'},
|
||||
{name: 'og:type', content: 'website'},
|
||||
{name: 'og:site_name', content: 'Khoj Documentation'},
|
||||
{name: 'og:description', content: 'Quickly get started with using or self-hosting Khoj'},
|
||||
{name: 'og:image', content: 'https://khoj-web-bucket.s3.amazonaws.com/link_preview_docs.png'},
|
||||
{name: 'og:url', content: 'https://docs.khoj.dev'},
|
||||
{name: 'keywords', content: 'khoj, khoj ai, chatgpt, open ai, open source, productivity'}
|
||||
],
|
||||
navbar: {
|
||||
title: 'Khoj',
|
||||
logo: {
|
||||
alt: 'Khoj AI',
|
||||
src: 'img/favicon-128x128.ico',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
href: 'https://github.com/khoj-ai/khoj',
|
||||
label: '📜 Code',
|
||||
position: 'right',
|
||||
},
|
||||
{
|
||||
href: 'https://app.khoj.dev/login',
|
||||
label: '🌍 Cloud',
|
||||
position: 'right',
|
||||
},
|
||||
{
|
||||
href: 'https://discord.gg/BDgyabRM6e',
|
||||
label: '💬 Discord',
|
||||
position: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
style: 'dark',
|
||||
links: [
|
||||
{
|
||||
title: 'Docs',
|
||||
items: [
|
||||
{
|
||||
label: 'Get Started',
|
||||
to: '/',
|
||||
},
|
||||
{
|
||||
label: 'Features',
|
||||
to: '/features/all_features',
|
||||
},
|
||||
{
|
||||
label: 'Client Apps',
|
||||
to: '/category/clients',
|
||||
},
|
||||
{
|
||||
label: 'Self-Hosting',
|
||||
to: '/get-started/setup',
|
||||
},
|
||||
{
|
||||
label: 'Contributing',
|
||||
to: '/contributing/development',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Community',
|
||||
items: [
|
||||
{
|
||||
label: 'Discord',
|
||||
href: 'https://discord.gg/BDgyabRM6e',
|
||||
},
|
||||
{
|
||||
label: 'LinkedIn',
|
||||
href: 'https://www.linkedin.com/company/khoj-ai/'
|
||||
},
|
||||
{
|
||||
label: 'Twitter',
|
||||
href: 'https://twitter.com/khoj_ai',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'More',
|
||||
items: [
|
||||
// {
|
||||
// label: 'Blog',
|
||||
// to: '/blog',
|
||||
// },
|
||||
{
|
||||
label: 'Cloud',
|
||||
href: 'https://app.khoj.dev/login',
|
||||
},
|
||||
{
|
||||
label: 'Code',
|
||||
href: 'https://github.com/khoj-ai/khoj',
|
||||
},
|
||||
{
|
||||
label: 'Website',
|
||||
href: 'https://khoj.dev',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Khoj, Inc.`,
|
||||
},
|
||||
prism: {
|
||||
theme: prismThemes.github,
|
||||
darkTheme: prismThemes.dracula,
|
||||
},
|
||||
algolia: {
|
||||
appId: "NBR0FXJNGW",
|
||||
apiKey: "8841b34192a28b2d06f04dd28d768017",
|
||||
indexName: "khoj",
|
||||
contextualSearch: false,
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
14629
documentation/package-lock.json
generated
Normal file
44
documentation/package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "documentation",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.1.0",
|
||||
"@docusaurus/preset-classic": "3.1.0",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.1.0",
|
||||
"@docusaurus/types": "3.1.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.5%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 3 chrome version",
|
||||
"last 3 firefox version",
|
||||
"last 5 safari version"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
}
|
||||
}
|
||||
33
documentation/sidebars.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
- create an ordered group of docs
|
||||
- render a sidebar for each doc of that group
|
||||
- provide next/previous navigation
|
||||
|
||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||
|
||||
Create as many sidebars as you want.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
||||
const sidebars = {
|
||||
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
|
||||
|
||||
// But you can create a sidebar manually
|
||||
/*
|
||||
tutorialSidebar: [
|
||||
'intro',
|
||||
'hello',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Tutorial',
|
||||
items: ['tutorial-basics/create-a-document'],
|
||||
},
|
||||
],
|
||||
*/
|
||||
};
|
||||
|
||||
export default sidebars;
|
||||
@@ -0,0 +1,11 @@
|
||||
.features {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.featureSvg {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
37
documentation/src/css/custom.css
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Any CSS included here will be global. The classic template
|
||||
* bundles Infima by default. Infima is a CSS framework designed to
|
||||
* work well for content-centric websites.
|
||||
*/
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Source+Sans+3&display=swap');
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
:root {
|
||||
--ifm-color-primary: #fcc50b;
|
||||
--ifm-color-primary-dark: #fcc50b;
|
||||
--ifm-color-primary-darker: #fcc50b;
|
||||
--ifm-color-primary-darkest: #fcc50b;
|
||||
--ifm-color-primary-light: #fcc50b;
|
||||
--ifm-color-primary-lighter: #fcc50b;
|
||||
--ifm-color-primary-lightest: #fcc50b;
|
||||
--ifm-code-font-size: 95%;
|
||||
--ifm-heading-font-family: 'Source Sans 3', sans-serif;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||
[data-theme='dark'] {
|
||||
--ifm-color-primary: #fcc50b;
|
||||
--ifm-color-primary-dark: #fcc50b;
|
||||
--ifm-color-primary-darker: #fcc50b;
|
||||
--ifm-color-primary-darkest: #fcc50b;
|
||||
--ifm-color-primary-light: #fcc50b;
|
||||
--ifm-color-primary-lighter: #fcc50b;
|
||||
--ifm-color-primary-lightest: #fcc50b;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Source Sans 3', sans-serif;
|
||||
}
|
||||
8344
documentation/yarn.lock
Normal file
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "khoj",
|
||||
"name": "Khoj",
|
||||
"version": "1.0.1",
|
||||
"version": "1.4.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "An AI copilot for your Second Brain",
|
||||
"author": "Khoj Inc.",
|
||||
|
||||
@@ -11,7 +11,8 @@ WORKDIR /app
|
||||
# Install Application
|
||||
COPY pyproject.toml .
|
||||
COPY README.md .
|
||||
RUN sed -i 's/dynamic = \["version"\]/version = "0.0.0"/' pyproject.toml && \
|
||||
ARG VERSION=0.0.0
|
||||
RUN sed -i "s/dynamic = \\[\"version\"\\]/version = \"$VERSION\"/" pyproject.toml && \
|
||||
TMPDIR=/home/cache/ pip install --cache-dir=/home/cache/ -e .
|
||||
|
||||
# Copy Source Code
|
||||
|
||||
@@ -41,11 +41,11 @@ dependencies = [
|
||||
"defusedxml == 0.7.1",
|
||||
"fastapi >= 0.104.1",
|
||||
"python-multipart >= 0.0.5",
|
||||
"jinja2 == 3.1.2",
|
||||
"openai >= 0.27.0, < 1.0.0",
|
||||
"jinja2 == 3.1.3",
|
||||
"openai >= 1.0.0",
|
||||
"tiktoken >= 0.3.2",
|
||||
"tenacity >= 8.2.2",
|
||||
"pillow == 9.3.0",
|
||||
"pillow ~= 9.5.0",
|
||||
"pydantic >= 2.0.0",
|
||||
"pyyaml == 6.0",
|
||||
"rich >= 13.3.1",
|
||||
@@ -54,19 +54,19 @@ dependencies = [
|
||||
"transformers >= 4.28.0",
|
||||
"torch == 2.0.1",
|
||||
"uvicorn == 0.17.6",
|
||||
"aiohttp == 3.8.6",
|
||||
"langchain >= 0.0.331",
|
||||
"aiohttp ~= 3.9.0",
|
||||
"langchain <= 0.2.0",
|
||||
"requests >= 2.26.0",
|
||||
"bs4 >= 0.0.1",
|
||||
"anyio == 3.7.1",
|
||||
"pymupdf >= 1.23.5",
|
||||
"django == 4.2.7",
|
||||
"authlib == 1.2.1",
|
||||
"gpt4all >= 2.0.0; platform_system == 'Linux' and platform_machine == 'x86_64'",
|
||||
"gpt4all >= 2.0.0; platform_system == 'Windows' or platform_system == 'Darwin'",
|
||||
"gpt4all >= 2.1.0; platform_system == 'Linux' and platform_machine == 'x86_64'",
|
||||
"gpt4all >= 2.1.0; platform_system == 'Windows' or platform_system == 'Darwin'",
|
||||
"itsdangerous == 2.1.2",
|
||||
"httpx == 0.25.0",
|
||||
"pgvector == 0.2.3",
|
||||
"pgvector == 0.2.4",
|
||||
"psycopg2-binary == 2.9.9",
|
||||
"google-auth == 2.23.3",
|
||||
"python-multipart == 0.0.6",
|
||||
@@ -75,6 +75,10 @@ dependencies = [
|
||||
"tzdata == 2023.3",
|
||||
"rapidocr-onnxruntime == 1.3.8",
|
||||
"stripe == 7.3.0",
|
||||
"openai-whisper >= 20231117",
|
||||
"django-phonenumber-field == 7.3.0",
|
||||
"phonenumbers == 8.13.27",
|
||||
"twilio == 8.11"
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ do
|
||||
# Bump Desktop app to current version
|
||||
cd $project_root/src/interface/desktop
|
||||
sed -E -i.bak "s/version\": \"(.*)\",/version\": \"$current_version\",/" package.json
|
||||
rm *.bak
|
||||
|
||||
# Bump Obsidian plugin to current version
|
||||
cd $project_root/src/interface/obsidian
|
||||
@@ -37,6 +38,7 @@ do
|
||||
|
||||
# Commit changes and tag commit for release
|
||||
git add \
|
||||
$project_root/src/interface/desktop/package.json \
|
||||
$project_root/src/interface/obsidian/package.json \
|
||||
$project_root/src/interface/obsidian/manifest.json \
|
||||
$project_root/src/interface/obsidian/versions.json \
|
||||
@@ -52,6 +54,11 @@ do
|
||||
next_version=$(touch bump.txt && git add bump.txt && hatch version | sed 's/\.dev.*//g')
|
||||
git rm --cached -- bump.txt && rm bump.txt
|
||||
|
||||
# Bump Desktop app to next version
|
||||
cd $project_root/src/interface/desktop
|
||||
sed -E -i.bak "s/version\": \"(.*)\",/version\": \"$current_version\",/" package.json
|
||||
rm *.bak
|
||||
|
||||
# Bump Obsidian plugins to next version
|
||||
cd $project_root/src/interface/obsidian
|
||||
sed -E -i.bak "s/version\": \"(.*)\",/version\": \"$next_version\",/" package.json
|
||||
@@ -69,6 +76,7 @@ do
|
||||
|
||||
# Commit changes
|
||||
git add \
|
||||
$project_root/src/interface/desktop/package.json \
|
||||
$project_root/src/interface/obsidian/package.json \
|
||||
$project_root/src/interface/obsidian/manifest.json \
|
||||
$project_root/src/interface/obsidian/versions.json \
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
background: var(--background-color);
|
||||
color: var(--main-text-color);
|
||||
text-align: center;
|
||||
font-family: roboto, karma, segoe ui, sans-serif;
|
||||
font-family: var(--font-family);
|
||||
font-size: small;
|
||||
font-weight: 300;
|
||||
line-height: 1.5em;
|
||||
|
||||
1
src/interface/desktop/assets/icons/microphone-solid.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M192 0C139 0 96 43 96 96V256c0 53 43 96 96 96s96-43 96-96V96c0-53-43-96-96-96zM64 216c0-13.3-10.7-24-24-24s-24 10.7-24 24v40c0 89.1 66.2 162.7 152 174.4V464H120c-13.3 0-24 10.7-24 24s10.7 24 24 24h72 72c13.3 0 24-10.7 24-24s-10.7-24-24-24H216V430.4c85.8-11.7 152-85.3 152-174.4V216c0-13.3-10.7-24-24-24s-24 10.7-24 24v40c0 70.7-57.3 128-128 128s-128-57.3-128-128V216z"/></svg>
|
||||
|
After Width: | Height: | Size: 616 B |
37
src/interface/desktop/assets/icons/stop-solid.svg
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 384 512"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="stop-solid.svg"
|
||||
inkscape:version="1.3 (0e150ed, 2023-07-21)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.4609375"
|
||||
inkscape:cx="192"
|
||||
inkscape:cy="256"
|
||||
inkscape:window-width="1312"
|
||||
inkscape:window-height="449"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="88"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg1" />
|
||||
<!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path
|
||||
d="M0 128C0 92.7 28.7 64 64 64H320c35.3 0 64 28.7 64 64V384c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V128z"
|
||||
id="path1"
|
||||
style="fill:#aa0000" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
src/interface/desktop/assets/icons/trash-solid.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>
|
||||
|
After Width: | Height: | Size: 503 B |
@@ -1,31 +1,37 @@
|
||||
/* Amber Light scheme (Default) */
|
||||
/* Can be forced with data-theme="light" */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Tajawal&display=swap');
|
||||
|
||||
[data-theme="light"],
|
||||
:root:not([data-theme="dark"]) {
|
||||
--primary: #fee285;
|
||||
--primary-hover: #fcc50b;
|
||||
--primary: #f9f5de;
|
||||
--primary-hover: #fee285;
|
||||
--primary-focus: rgba(255, 179, 0, 0.125);
|
||||
--primary-inverse: rgba(0, 0, 0, 0.75);
|
||||
--background-color: #f5f4f3;
|
||||
--main-text-color: #475569;
|
||||
--summer-sun: #fcc50b;
|
||||
--water: #44b9da;
|
||||
--leaf: #7b990a;
|
||||
--flower: #d1684e;
|
||||
--font-family: 'Tajawal', sans-serif !important;
|
||||
}
|
||||
|
||||
/* Amber Dark scheme (Auto) */
|
||||
/* Automatically enabled if user has Dark mode enabled */
|
||||
@media only screen and (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme]) {
|
||||
--primary: #fee285;
|
||||
--primary-hover: #fcc50b;
|
||||
--primary: #f9f5de;
|
||||
--primary-hover: #fee285;
|
||||
--primary-focus: rgba(255, 179, 0, 0.25);
|
||||
--primary-inverse: rgba(0, 0, 0, 0.75);
|
||||
--background-color: #f5f4f3;
|
||||
--main-text-color: #475569;
|
||||
--summer-sun: #fcc50b;
|
||||
--water: #44b9da;
|
||||
--leaf: #7b990a;
|
||||
--flower: #d1684e;
|
||||
--font-family: 'Tajawal', sans-serif !important;
|
||||
}
|
||||
}
|
||||
/* Amber Dark scheme (Forced) */
|
||||
@@ -37,9 +43,11 @@
|
||||
--primary-inverse: rgba(0, 0, 0, 0.75);
|
||||
--background-color: #f5f4f3;
|
||||
--main-text-color: #475569;
|
||||
--summer-sun: #fcc50b;
|
||||
--water: #44b9da;
|
||||
--leaf: #7b990a;
|
||||
--flower: #d1684e;
|
||||
--font-family: 'Tajawal', sans-serif !important;
|
||||
}
|
||||
/* Amber (Common styles) */
|
||||
:root {
|
||||
@@ -47,12 +55,13 @@
|
||||
--form-element-focus-color: var(--primary-focus);
|
||||
--switch-color: var(--primary-inverse);
|
||||
--switch-checked-background-color: var(--primary);
|
||||
--font-family: 'Tajawal', sans-serif !important;
|
||||
}
|
||||
|
||||
.khoj-configure {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
font-family: roboto, karma, segoe ui, sans-serif;
|
||||
font-family: var(--font-family);
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
|
||||
2
src/interface/desktop/assets/pico.min.css
vendored
@@ -8,17 +8,26 @@
|
||||
<link rel="manifest" href="/static/khoj_chat.webmanifest">
|
||||
<link rel="stylesheet" href="./assets/khoj.css">
|
||||
</head>
|
||||
<script type="text/javascript" src="./assets/markdown-it.min.js"></script>
|
||||
<script src="./utils.js"></script>
|
||||
|
||||
<script>
|
||||
let chatOptions = [];
|
||||
function copyProgrammaticOutput(event) {
|
||||
// Remove the first 4 characters which are the "Copy" button
|
||||
const originalCopyText = event.target.parentNode.textContent.trim().slice(0, 4);
|
||||
const programmaticOutput = event.target.parentNode.textContent.trim().slice(4);
|
||||
navigator.clipboard.writeText(programmaticOutput).then(() => {
|
||||
console.log("Programmatic output copied to clipboard");
|
||||
event.target.textContent = "✅ Copied to clipboard!";
|
||||
setTimeout(() => {
|
||||
event.target.textContent = originalCopyText;
|
||||
}, 1000);
|
||||
}).catch((error) => {
|
||||
console.error("Error copying programmatic output to clipboard:", error);
|
||||
event.target.textContent = "⛔️ Failed to copy!";
|
||||
setTimeout(() => {
|
||||
event.target.textContent = originalCopyText;
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,7 +46,7 @@
|
||||
let short_ref = escaped_ref.slice(0, 100);
|
||||
short_ref = short_ref.length < escaped_ref.length ? short_ref + "..." : short_ref;
|
||||
let referenceButton = document.createElement('button');
|
||||
referenceButton.innerHTML = short_ref;
|
||||
referenceButton.textContent = short_ref;
|
||||
referenceButton.id = `ref-${index}`;
|
||||
referenceButton.classList.add("reference-button");
|
||||
referenceButton.classList.add("collapsed");
|
||||
@@ -49,21 +58,67 @@
|
||||
if (this.classList.contains("collapsed")) {
|
||||
this.classList.remove("collapsed");
|
||||
this.classList.add("expanded");
|
||||
this.innerHTML = escaped_ref;
|
||||
this.textContent = escaped_ref;
|
||||
} else {
|
||||
this.classList.add("collapsed");
|
||||
this.classList.remove("expanded");
|
||||
this.innerHTML = short_ref;
|
||||
this.textContent = short_ref;
|
||||
}
|
||||
});
|
||||
|
||||
return referenceButton;
|
||||
}
|
||||
|
||||
function renderMessage(message, by, dt=null, annotations=null) {
|
||||
function generateOnlineReference(reference, index) {
|
||||
|
||||
// Generate HTML for Chat Reference
|
||||
let title = reference.title;
|
||||
let link = reference.link;
|
||||
let snippet = reference.snippet;
|
||||
let question = reference.question;
|
||||
if (question) {
|
||||
question = `<b>Question:</b> ${question}<br><br>`;
|
||||
} else {
|
||||
question = "";
|
||||
}
|
||||
|
||||
let linkElement = document.createElement('a');
|
||||
linkElement.setAttribute('href', link);
|
||||
linkElement.setAttribute('target', '_blank');
|
||||
linkElement.setAttribute('rel', 'noopener noreferrer');
|
||||
linkElement.classList.add("inline-chat-link");
|
||||
linkElement.classList.add("reference-link");
|
||||
linkElement.setAttribute('title', title);
|
||||
linkElement.innerHTML = title;
|
||||
|
||||
let referenceButton = document.createElement('button');
|
||||
referenceButton.innerHTML = linkElement.outerHTML;
|
||||
referenceButton.id = `ref-${index}`;
|
||||
referenceButton.classList.add("reference-button");
|
||||
referenceButton.classList.add("collapsed");
|
||||
referenceButton.tabIndex = 0;
|
||||
|
||||
// Add event listener to toggle full reference on click
|
||||
referenceButton.addEventListener('click', function() {
|
||||
console.log(`Toggling ref-${index}`)
|
||||
if (this.classList.contains("collapsed")) {
|
||||
this.classList.remove("collapsed");
|
||||
this.classList.add("expanded");
|
||||
this.innerHTML = linkElement.outerHTML + `<br><br>${question + snippet}`;
|
||||
} else {
|
||||
this.classList.add("collapsed");
|
||||
this.classList.remove("expanded");
|
||||
this.innerHTML = linkElement.outerHTML;
|
||||
}
|
||||
});
|
||||
|
||||
return referenceButton;
|
||||
}
|
||||
|
||||
function renderMessage(message, by, dt=null, annotations=null, raw=false) {
|
||||
let message_time = formatDate(dt ?? new Date());
|
||||
let by_name = by == "khoj" ? "🏮 Khoj" : "🤔 You";
|
||||
let formattedMessage = formatHTMLMessage(message);
|
||||
let formattedMessage = formatHTMLMessage(message, raw);
|
||||
let chatBody = document.getElementById("chat-body");
|
||||
|
||||
// Create a new div for the chat message
|
||||
@@ -74,8 +129,7 @@
|
||||
// Create a new div for the chat message text and append it to the chat message
|
||||
let chatMessageText = document.createElement('div');
|
||||
chatMessageText.className = `chat-message-text ${by}`;
|
||||
let textNode = document.createTextNode(formattedMessage);
|
||||
chatMessageText.appendChild(textNode);
|
||||
chatMessageText.appendChild(formattedMessage);
|
||||
chatMessage.appendChild(chatMessageText);
|
||||
|
||||
// Append annotations div to the chat message
|
||||
@@ -90,8 +144,59 @@
|
||||
chatBody.scrollTop = chatBody.scrollHeight;
|
||||
}
|
||||
|
||||
function renderMessageWithReference(message, by, context=null, dt=null) {
|
||||
if (context == null || context.length == 0) {
|
||||
function processOnlineReferences(referenceSection, onlineContext) {
|
||||
let numOnlineReferences = 0;
|
||||
for (let subquery in onlineContext) {
|
||||
let onlineReference = onlineContext[subquery];
|
||||
if (onlineReference.organic && onlineReference.organic.length > 0) {
|
||||
numOnlineReferences += onlineReference.organic.length;
|
||||
for (let index in onlineReference.organic) {
|
||||
let reference = onlineReference.organic[index];
|
||||
let polishedReference = generateOnlineReference(reference, index);
|
||||
referenceSection.appendChild(polishedReference);
|
||||
}
|
||||
}
|
||||
|
||||
if (onlineReference.knowledgeGraph && onlineReference.knowledgeGraph.length > 0) {
|
||||
numOnlineReferences += onlineReference.knowledgeGraph.length;
|
||||
for (let index in onlineReference.knowledgeGraph) {
|
||||
let reference = onlineReference.knowledgeGraph[index];
|
||||
let polishedReference = generateOnlineReference(reference, index);
|
||||
referenceSection.appendChild(polishedReference);
|
||||
}
|
||||
}
|
||||
|
||||
if (onlineReference.peopleAlsoAsk && onlineReference.peopleAlsoAsk.length > 0) {
|
||||
numOnlineReferences += onlineReference.peopleAlsoAsk.length;
|
||||
for (let index in onlineReference.peopleAlsoAsk) {
|
||||
let reference = onlineReference.peopleAlsoAsk[index];
|
||||
let polishedReference = generateOnlineReference(reference, index);
|
||||
referenceSection.appendChild(polishedReference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return numOnlineReferences;
|
||||
}
|
||||
|
||||
function renderMessageWithReference(message, by, context=null, dt=null, onlineContext=null, intentType=null, inferredQueries=null) {
|
||||
if (intentType === "text-to-image") {
|
||||
let imageMarkdown = ``;
|
||||
imageMarkdown += "\n\n";
|
||||
if (inferredQueries) {
|
||||
const inferredQuery = inferredQueries?.[0];
|
||||
imageMarkdown += `**Inferred Query**: ${inferredQuery}`;
|
||||
}
|
||||
renderMessage(imageMarkdown, by, dt);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context == null && onlineContext == null) {
|
||||
renderMessage(message, by, dt);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((context && context.length == 0) && (onlineContext == null || (onlineContext && Object.keys(onlineContext).length == 0))) {
|
||||
renderMessage(message, by, dt);
|
||||
return;
|
||||
}
|
||||
@@ -100,8 +205,11 @@
|
||||
|
||||
let referenceExpandButton = document.createElement('button');
|
||||
referenceExpandButton.classList.add("reference-expand-button");
|
||||
let expandButtonText = context.length == 1 ? "1 reference" : `${context.length} references`;
|
||||
referenceExpandButton.innerHTML = expandButtonText;
|
||||
let numReferences = 0;
|
||||
|
||||
if (context) {
|
||||
numReferences += context.length;
|
||||
}
|
||||
|
||||
references.appendChild(referenceExpandButton);
|
||||
|
||||
@@ -127,20 +235,74 @@
|
||||
referenceSection.appendChild(polishedReference);
|
||||
}
|
||||
}
|
||||
|
||||
if (onlineContext) {
|
||||
numReferences += processOnlineReferences(referenceSection, onlineContext);
|
||||
}
|
||||
|
||||
let expandButtonText = numReferences == 1 ? "1 reference" : `${numReferences} references`;
|
||||
referenceExpandButton.innerHTML = expandButtonText;
|
||||
|
||||
references.appendChild(referenceSection);
|
||||
|
||||
renderMessage(message, by, dt, references);
|
||||
}
|
||||
|
||||
function formatHTMLMessage(htmlMessage) {
|
||||
// Replace any ``` with <div class="programmatic-output">
|
||||
let newHTML = htmlMessage.replace(/```([\s\S]*?)```/g, '<div class="programmatic-output"><button class="copy-button" onclick="copyProgrammaticOutput(event)">Copy</button>$1</div>');
|
||||
// Replace any ** with <b> and __ with <u>
|
||||
newHTML = newHTML.replace(/\*\*([\s\S]*?)\*\*/g, '<b>$1</b>');
|
||||
newHTML = newHTML.replace(/__([\s\S]*?)__/g, '<u>$1</u>');
|
||||
function formatHTMLMessage(htmlMessage, raw=false) {
|
||||
var md = window.markdownit();
|
||||
let newHTML = htmlMessage;
|
||||
|
||||
// Remove any text between <s>[INST] and </s> tags. These are spurious instructions for the AI chat model.
|
||||
newHTML = newHTML.replace(/<s>\[INST\].+(<\/s>)?/g, '');
|
||||
return newHTML;
|
||||
|
||||
// Customize the rendering of images
|
||||
md.renderer.rules.image = function(tokens, idx, options, env, self) {
|
||||
let token = tokens[idx];
|
||||
|
||||
// Add class="text-to-image" to images
|
||||
token.attrPush(['class', 'text-to-image']);
|
||||
|
||||
// Use the default renderer to render image markdown format
|
||||
return self.renderToken(tokens, idx, options);
|
||||
};
|
||||
|
||||
// Render markdown
|
||||
newHTML = raw ? newHTML : md.render(newHTML);
|
||||
// Get any elements with a class that starts with "language"
|
||||
let element = document.createElement('div');
|
||||
element.innerHTML = newHTML;
|
||||
let codeBlockElements = element.querySelectorAll('[class^="language-"]');
|
||||
// For each element, add a parent div with the class "programmatic-output"
|
||||
codeBlockElements.forEach((codeElement) => {
|
||||
// Create the parent div
|
||||
let parentDiv = document.createElement('div');
|
||||
parentDiv.classList.add("programmatic-output");
|
||||
// Add the parent div before the code element
|
||||
codeElement.parentNode.insertBefore(parentDiv, codeElement);
|
||||
// Move the code element into the parent div
|
||||
parentDiv.appendChild(codeElement);
|
||||
// Add a copy button to each element
|
||||
let copyButton = document.createElement('button');
|
||||
copyButton.classList.add("copy-button");
|
||||
copyButton.innerHTML = "Copy";
|
||||
copyButton.addEventListener('click', copyProgrammaticOutput);
|
||||
codeElement.prepend(copyButton);
|
||||
});
|
||||
|
||||
// Get all code elements that have no class.
|
||||
let codeElements = element.querySelectorAll('code:not([class])');
|
||||
codeElements.forEach((codeElement) => {
|
||||
// Add the class "chat-response" to each element
|
||||
codeElement.classList.add("chat-response");
|
||||
});
|
||||
|
||||
let anchorElements = element.querySelectorAll('a');
|
||||
anchorElements.forEach((anchorElement) => {
|
||||
// Add the class "inline-chat-link" to each element
|
||||
anchorElement.classList.add("inline-chat-link");
|
||||
});
|
||||
|
||||
return element
|
||||
}
|
||||
|
||||
async function chat() {
|
||||
@@ -188,81 +350,153 @@
|
||||
let chatInput = document.getElementById("chat-input");
|
||||
chatInput.classList.remove("option-enabled");
|
||||
|
||||
// Call specified Khoj API which returns a streamed response of type text/plain
|
||||
fetch(url, { headers })
|
||||
.then(response => {
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let references = null;
|
||||
// Call specified Khoj API
|
||||
let response = await fetch(url, { headers });
|
||||
let rawResponse = "";
|
||||
const contentType = response.headers.get("content-type");
|
||||
|
||||
function readStream() {
|
||||
reader.read().then(({ done, value }) => {
|
||||
if (done) {
|
||||
// Evaluate the contents of new_response_text.innerHTML after all the data has been streamed
|
||||
const currentHTML = newResponseText.innerHTML;
|
||||
newResponseText.innerHTML = formatHTMLMessage(currentHTML);
|
||||
if (contentType === "application/json") {
|
||||
// Handle JSON response
|
||||
try {
|
||||
const responseAsJson = await response.json();
|
||||
if (responseAsJson.image) {
|
||||
// If response has image field, response is a generated image.
|
||||
rawResponse += ``;
|
||||
rawResponse += "\n\n";
|
||||
const inferredQueries = responseAsJson.inferredQueries?.[0];
|
||||
if (inferredQueries) {
|
||||
rawResponse += `**Inferred Query**: ${inferredQueries}`;
|
||||
}
|
||||
}
|
||||
if (responseAsJson.detail) {
|
||||
// If response has detail field, response is an error message.
|
||||
rawResponse += responseAsJson.detail;
|
||||
}
|
||||
} catch (error) {
|
||||
// If the chunk is not a JSON object, just display it as is
|
||||
rawResponse += chunk;
|
||||
} finally {
|
||||
newResponseText.innerHTML = "";
|
||||
newResponseText.appendChild(formatHTMLMessage(rawResponse));
|
||||
|
||||
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
||||
document.getElementById("chat-input").removeAttribute("disabled");
|
||||
}
|
||||
} else {
|
||||
// Handle streamed response of type text/event-stream or text/plain
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let references = null;
|
||||
|
||||
readStream();
|
||||
|
||||
function readStream() {
|
||||
reader.read().then(({ done, value }) => {
|
||||
if (done) {
|
||||
// Append any references after all the data has been streamed
|
||||
if (references != null) {
|
||||
newResponseText.appendChild(references);
|
||||
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
||||
return;
|
||||
}
|
||||
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
||||
document.getElementById("chat-input").removeAttribute("disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
// Decode message chunk from stream
|
||||
const chunk = decoder.decode(value, { stream: true });
|
||||
// Decode message chunk from stream
|
||||
const chunk = decoder.decode(value, { stream: true });
|
||||
|
||||
if (chunk.includes("### compiled references:")) {
|
||||
const additionalResponse = chunk.split("### compiled references:")[0];
|
||||
newResponseText.innerHTML += additionalResponse;
|
||||
if (chunk.includes("### compiled references:")) {
|
||||
const additionalResponse = chunk.split("### compiled references:")[0];
|
||||
rawResponse += additionalResponse;
|
||||
newResponseText.innerHTML = "";
|
||||
newResponseText.appendChild(formatHTMLMessage(rawResponse));
|
||||
|
||||
const rawReference = chunk.split("### compiled references:")[1];
|
||||
const rawReferenceAsJson = JSON.parse(rawReference);
|
||||
references = document.createElement('div');
|
||||
references.classList.add("references");
|
||||
const rawReference = chunk.split("### compiled references:")[1];
|
||||
const rawReferenceAsJson = JSON.parse(rawReference);
|
||||
references = document.createElement('div');
|
||||
references.classList.add("references");
|
||||
|
||||
let referenceExpandButton = document.createElement('button');
|
||||
referenceExpandButton.classList.add("reference-expand-button");
|
||||
|
||||
let referenceExpandButton = document.createElement('button');
|
||||
referenceExpandButton.classList.add("reference-expand-button");
|
||||
let expandButtonText = rawReferenceAsJson.length == 1 ? "1 reference" : `${rawReferenceAsJson.length} references`;
|
||||
referenceExpandButton.innerHTML = expandButtonText;
|
||||
let referenceSection = document.createElement('div');
|
||||
referenceSection.classList.add("reference-section");
|
||||
referenceSection.classList.add("collapsed");
|
||||
|
||||
references.appendChild(referenceExpandButton);
|
||||
let numReferences = 0;
|
||||
|
||||
let referenceSection = document.createElement('div');
|
||||
referenceSection.classList.add("reference-section");
|
||||
referenceSection.classList.add("collapsed");
|
||||
|
||||
referenceExpandButton.addEventListener('click', function() {
|
||||
if (referenceSection.classList.contains("collapsed")) {
|
||||
referenceSection.classList.remove("collapsed");
|
||||
referenceSection.classList.add("expanded");
|
||||
} else {
|
||||
referenceSection.classList.add("collapsed");
|
||||
referenceSection.classList.remove("expanded");
|
||||
}
|
||||
});
|
||||
// If rawReferenceAsJson is a list, then count the length
|
||||
if (Array.isArray(rawReferenceAsJson)) {
|
||||
numReferences = rawReferenceAsJson.length;
|
||||
|
||||
rawReferenceAsJson.forEach((reference, index) => {
|
||||
let polishedReference = generateReference(reference, index);
|
||||
referenceSection.appendChild(polishedReference);
|
||||
});
|
||||
references.appendChild(referenceSection);
|
||||
readStream();
|
||||
let polishedReference = generateReference(reference, index);
|
||||
referenceSection.appendChild(polishedReference);
|
||||
});
|
||||
} else {
|
||||
// Display response from Khoj
|
||||
if (newResponseText.getElementsByClassName("spinner").length > 0) {
|
||||
newResponseText.removeChild(loadingSpinner);
|
||||
}
|
||||
|
||||
newResponseText.innerHTML += chunk;
|
||||
readStream();
|
||||
numReferences += processOnlineReferences(referenceSection, rawReferenceAsJson);
|
||||
}
|
||||
|
||||
// Scroll to bottom of chat window as chat response is streamed
|
||||
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
||||
});
|
||||
}
|
||||
readStream();
|
||||
document.getElementById("chat-input").removeAttribute("disabled");
|
||||
});
|
||||
references.appendChild(referenceExpandButton);
|
||||
|
||||
referenceExpandButton.addEventListener('click', function() {
|
||||
if (referenceSection.classList.contains("collapsed")) {
|
||||
referenceSection.classList.remove("collapsed");
|
||||
referenceSection.classList.add("expanded");
|
||||
} else {
|
||||
referenceSection.classList.add("collapsed");
|
||||
referenceSection.classList.remove("expanded");
|
||||
}
|
||||
});
|
||||
|
||||
let expandButtonText = numReferences == 1 ? "1 reference" : `${numReferences} references`;
|
||||
referenceExpandButton.innerHTML = expandButtonText;
|
||||
references.appendChild(referenceSection);
|
||||
readStream();
|
||||
} else {
|
||||
// Display response from Khoj
|
||||
if (newResponseText.getElementsByClassName("spinner").length > 0) {
|
||||
newResponseText.removeChild(loadingSpinner);
|
||||
}
|
||||
|
||||
// Try to parse the chunk as a JSON object. It will be a JSON object if there is an error.
|
||||
if (chunk.startsWith("{") && chunk.endsWith("}")) {
|
||||
try {
|
||||
const responseAsJson = JSON.parse(chunk);
|
||||
if (responseAsJson.image) {
|
||||
// If response has image field, response is a generated image.
|
||||
rawResponse += ``;
|
||||
rawResponse += "\n\n";
|
||||
const inferredQueries = responseAsJson.inferredQueries?.[0];
|
||||
if (inferredQueries) {
|
||||
rawResponse += `**Inferred Query**: ${inferredQueries}`;
|
||||
}
|
||||
}
|
||||
if (responseAsJson.detail) {
|
||||
rawResponse += responseAsJson.detail;
|
||||
}
|
||||
} catch (error) {
|
||||
// If the chunk is not a JSON object, just display it as is
|
||||
rawResponse += chunk;
|
||||
} finally {
|
||||
newResponseText.innerHTML = "";
|
||||
newResponseText.appendChild(formatHTMLMessage(rawResponse));
|
||||
}
|
||||
} else {
|
||||
// If the chunk is not a JSON object, just display it as is
|
||||
rawResponse += chunk;
|
||||
newResponseText.innerHTML = "";
|
||||
newResponseText.appendChild(formatHTMLMessage(rawResponse));
|
||||
|
||||
readStream();
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll to bottom of chat window as chat response is streamed
|
||||
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function incrementalChat(event) {
|
||||
@@ -276,6 +510,9 @@
|
||||
let chatInput = document.getElementById("chat-input");
|
||||
chatInput.value = chatInput.value.trimStart();
|
||||
|
||||
let questionStarterSuggestions = document.getElementById("question-starters");
|
||||
questionStarterSuggestions.style.display = "none";
|
||||
|
||||
if (chatInput.value.startsWith("/") && chatInput.value.split(" ").length === 1) {
|
||||
let chatTooltip = document.getElementById("chat-tooltip");
|
||||
chatTooltip.style.display = "block";
|
||||
@@ -309,7 +546,7 @@
|
||||
const textarea = document.getElementById('chat-input');
|
||||
const scrollTop = textarea.scrollTop;
|
||||
textarea.style.height = '0';
|
||||
const scrollHeight = textarea.scrollHeight;
|
||||
const scrollHeight = textarea.scrollHeight + 8; // +8 accounts for padding
|
||||
textarea.style.height = Math.min(scrollHeight, 200) + 'px';
|
||||
textarea.scrollTop = scrollTop;
|
||||
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
||||
@@ -324,7 +561,7 @@
|
||||
const khojToken = await window.tokenAPI.getToken();
|
||||
const headers = { 'Authorization': `Bearer ${khojToken}` };
|
||||
|
||||
fetch(`${hostURL}/api/chat/history?client=web`, { headers })
|
||||
fetch(`${hostURL}/api/chat/history?client=desktop`, { headers })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.detail) {
|
||||
@@ -337,7 +574,7 @@
|
||||
.trim()
|
||||
.replace(/(\r\n|\n|\r)/gm, "");
|
||||
|
||||
renderMessage(first_run_message, "khoj");
|
||||
renderMessage(first_run_message, "khoj", null, null, true);
|
||||
|
||||
// Disable chat input field and update placeholder text
|
||||
document.getElementById("chat-input").setAttribute("disabled", "disabled");
|
||||
@@ -351,13 +588,38 @@
|
||||
.then(response => {
|
||||
// Render conversation history, if any
|
||||
response.forEach(chat_log => {
|
||||
renderMessageWithReference(chat_log.message, chat_log.by, chat_log.context, new Date(chat_log.created));
|
||||
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"]);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
return;
|
||||
});
|
||||
|
||||
fetch(`${hostURL}/api/chat/starters?client=desktop`, { headers })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Render chat options, if any
|
||||
if (data.length > 0) {
|
||||
let questionStarterSuggestions = document.getElementById("question-starters");
|
||||
for (let index in data) {
|
||||
let questionStarter = data[index];
|
||||
let questionStarterButton = document.createElement('button');
|
||||
questionStarterButton.innerHTML = questionStarter;
|
||||
questionStarterButton.classList.add("question-starter");
|
||||
questionStarterButton.addEventListener('click', function() {
|
||||
questionStarterSuggestions.style.display = "none";
|
||||
document.getElementById("chat-input").value = questionStarter;
|
||||
chat();
|
||||
});
|
||||
questionStarterSuggestions.appendChild(questionStarterButton);
|
||||
}
|
||||
questionStarterSuggestions.style.display = "grid";
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
return;
|
||||
});
|
||||
|
||||
fetch(`${hostURL}/api/chat/options`, { headers })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
@@ -377,6 +639,143 @@
|
||||
chat();
|
||||
}
|
||||
}
|
||||
|
||||
function flashStatusInChatInput(message) {
|
||||
// Get chat input element and original placeholder
|
||||
let chatInput = document.getElementById("chat-input");
|
||||
let originalPlaceholder = chatInput.placeholder;
|
||||
// Set placeholder to message
|
||||
chatInput.placeholder = message;
|
||||
// Reset placeholder after 2 seconds
|
||||
setTimeout(() => {
|
||||
chatInput.placeholder = originalPlaceholder;
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
async function clearConversationHistory() {
|
||||
let chatInput = document.getElementById("chat-input");
|
||||
let originalPlaceholder = chatInput.placeholder;
|
||||
let chatBody = document.getElementById("chat-body");
|
||||
|
||||
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 })
|
||||
.then(response => response.ok ? response.json() : Promise.reject(response))
|
||||
.then(data => {
|
||||
chatBody.innerHTML = "";
|
||||
loadChat();
|
||||
flashStatusInChatInput("🗑 Cleared conversation history");
|
||||
})
|
||||
.catch(err => {
|
||||
flashStatusInChatInput("⛔️ Failed to clear conversation history");
|
||||
})
|
||||
}
|
||||
|
||||
let sendMessageTimeout;
|
||||
let mediaRecorder;
|
||||
async function speechToText(event) {
|
||||
event.preventDefault();
|
||||
const speakButtonImg = document.getElementById('speak-button-img');
|
||||
const stopRecordButtonImg = document.getElementById('stop-record-button-img');
|
||||
const sendButtonImg = document.getElementById('send-button-img');
|
||||
const stopSendButtonImg = document.getElementById('stop-send-button-img');
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
|
||||
const hostURL = await window.hostURLAPI.getURL();
|
||||
let url = `${hostURL}/api/transcribe?client=desktop`;
|
||||
const khojToken = await window.tokenAPI.getToken();
|
||||
const headers = { 'Authorization': `Bearer ${khojToken}` };
|
||||
|
||||
const sendToServer = (audioBlob) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', audioBlob);
|
||||
|
||||
fetch(url, { method: 'POST', body: formData, headers})
|
||||
.then(response => response.ok ? response.json() : Promise.reject(response))
|
||||
.then(data => { chatInput.value += data.text.trimStart(); autoResize(); })
|
||||
.then(() => {
|
||||
// Don't auto-send empty messages
|
||||
if (chatInput.value.length === 0) return;
|
||||
|
||||
// Send message after 3 seconds, unless stop send button is clicked
|
||||
sendButtonImg.style.display = 'none';
|
||||
stopSendButtonImg.style.display = 'initial';
|
||||
|
||||
// Start the countdown timer UI
|
||||
document.getElementById('countdown-circle').style.animation = "countdown 3s linear 1 forwards";
|
||||
|
||||
sendMessageTimeout = setTimeout(() => {
|
||||
// Revert to showing send-button and hide the stop-send-button
|
||||
sendButtonImg.style.display = 'initial';
|
||||
stopSendButtonImg.style.display = 'none';
|
||||
|
||||
// Stop the countdown timer UI
|
||||
document.getElementById('countdown-circle').style.animation = "none";
|
||||
|
||||
// Send message
|
||||
chat();
|
||||
}, 3000);
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.status === 501) {
|
||||
flashStatusInChatInput("⛔️ Configure speech-to-text model on server.")
|
||||
} else if (err.status === 422) {
|
||||
flashStatusInChatInput("⛔️ Audio file to large to process.")
|
||||
} else {
|
||||
flashStatusInChatInput("⛔️ Failed to transcribe audio.")
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleRecording = (stream) => {
|
||||
const audioChunks = [];
|
||||
const recordingConfig = { mimeType: 'audio/webm' };
|
||||
mediaRecorder = new MediaRecorder(stream, recordingConfig);
|
||||
|
||||
mediaRecorder.addEventListener("dataavailable", function(event) {
|
||||
if (event.data.size > 0) audioChunks.push(event.data);
|
||||
});
|
||||
|
||||
mediaRecorder.addEventListener("stop", function() {
|
||||
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
|
||||
sendToServer(audioBlob);
|
||||
});
|
||||
|
||||
mediaRecorder.start();
|
||||
speakButtonImg.style.display = 'none';
|
||||
stopRecordButtonImg.style.display = 'initial';
|
||||
};
|
||||
|
||||
// Toggle recording
|
||||
if (!mediaRecorder || mediaRecorder.state === 'inactive' || event.type === 'touchstart') {
|
||||
navigator.mediaDevices
|
||||
?.getUserMedia({ audio: true })
|
||||
.then(handleRecording)
|
||||
.catch((e) => {
|
||||
flashStatusInChatInput("⛔️ Failed to access microphone");
|
||||
});
|
||||
} else if (mediaRecorder.state === 'recording' || event.type === 'touchend' || event.type === 'touchcancel') {
|
||||
mediaRecorder.stop();
|
||||
mediaRecorder.stream.getTracks().forEach(track => track.stop());
|
||||
mediaRecorder = null;
|
||||
speakButtonImg.style.display = 'initial';
|
||||
stopRecordButtonImg.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function cancelSendMessage() {
|
||||
// Cancel the chat() call if the stop-send-button is clicked
|
||||
clearTimeout(sendMessageTimeout);
|
||||
|
||||
// Revert to showing send-button and hide the stop-send-button
|
||||
document.getElementById('stop-send-button-img').style.display = 'none';
|
||||
document.getElementById('send-button-img').style.display = 'initial';
|
||||
|
||||
// Stop the countdown timer UI
|
||||
document.getElementById('countdown-circle').style.animation = "none";
|
||||
};
|
||||
</script>
|
||||
<body>
|
||||
<div id="khoj-empty-container" class="khoj-empty-container">
|
||||
@@ -397,10 +796,45 @@
|
||||
<!-- Chat Body -->
|
||||
<div id="chat-body"></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>
|
||||
<textarea id="chat-input" class="option" oninput="onChatInput()" onkeydown=incrementalChat(event) autofocus="autofocus" placeholder="Type / to see a list of commands, or just type your questions and hit enter."></textarea>
|
||||
<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="Message"></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>
|
||||
</body>
|
||||
|
||||
@@ -416,7 +850,7 @@
|
||||
background: var(--background-color);
|
||||
color: var(--main-text-color);
|
||||
text-align: center;
|
||||
font-family: roboto, karma, segoe ui, sans-serif;
|
||||
font-family: var(--font-family);
|
||||
font-size: small;
|
||||
font-weight: 300;
|
||||
line-height: 1.5em;
|
||||
@@ -448,7 +882,6 @@
|
||||
.chat-message.you {
|
||||
margin-right: auto;
|
||||
text-align: right;
|
||||
white-space: pre-line;
|
||||
}
|
||||
/* basic style chat message text */
|
||||
.chat-message-text {
|
||||
@@ -465,7 +898,6 @@
|
||||
color: var(--primary-inverse);
|
||||
background: var(--primary);
|
||||
margin-left: auto;
|
||||
white-space: pre-line;
|
||||
}
|
||||
/* Spinner symbol when the chat message is loading */
|
||||
.spinner {
|
||||
@@ -511,38 +943,105 @@
|
||||
margin-top: -10px;
|
||||
transform: rotate(-60deg)
|
||||
}
|
||||
img.text-to-image {
|
||||
max-width: 60%;
|
||||
}
|
||||
|
||||
#chat-footer {
|
||||
padding: 0;
|
||||
margin: 8px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(70px, 100%);
|
||||
grid-column-gap: 10px;
|
||||
grid-row-gap: 10px;
|
||||
}
|
||||
#chat-footer > * {
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #475569;
|
||||
background: #f9fafc
|
||||
#input-row {
|
||||
display: grid;
|
||||
grid-template-columns: 32px auto 32px 40px;
|
||||
grid-column-gap: 10px;
|
||||
grid-row-gap: 10px;
|
||||
background: #f9fafc;
|
||||
align-items: center;
|
||||
}
|
||||
.option:hover {
|
||||
box-shadow: 0 0 11px #aaa;
|
||||
}
|
||||
#chat-input {
|
||||
font-family: roboto, karma, segoe ui, sans-serif;
|
||||
font-family: var(--font-family);
|
||||
font-size: small;
|
||||
height: 54px;
|
||||
height: 36px;
|
||||
border-radius: 16px;
|
||||
resize: none;
|
||||
overflow-y: hidden;
|
||||
max-height: 200px;
|
||||
box-sizing: border-box;
|
||||
padding: 15px;
|
||||
padding: 7px 0 0 12px;
|
||||
line-height: 1.5em;
|
||||
margin: 0;
|
||||
}
|
||||
#chat-input:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
.input-row-button {
|
||||
background: var(--background-color);
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border-radius: 50%;
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
padding: 0;
|
||||
line-height: 1.5em;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s ease-in-out;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-top: -2px;
|
||||
margin-left: -5px;
|
||||
}
|
||||
.input-row-button:hover {
|
||||
background: var(--primary-hover);
|
||||
}
|
||||
.input-row-button:active {
|
||||
background: var(--primary-active);
|
||||
}
|
||||
.input-row-button-img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
#send-button {
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
#send-button-img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: var(--primary-hover);
|
||||
border-radius: 50%;
|
||||
}
|
||||
#stop-send-button-img {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
transform: rotateY(-180deg) rotateZ(-90deg);
|
||||
}
|
||||
#countdown-circle {
|
||||
stroke-dasharray: 44px; /* The circumference of the circle with 7px radius */
|
||||
stroke-dashoffset: 0px;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 1px;
|
||||
stroke: var(--main-text-color);
|
||||
fill: none;
|
||||
}
|
||||
@keyframes countdown {
|
||||
from {
|
||||
stroke-dashoffset: 0px;
|
||||
}
|
||||
to {
|
||||
stroke-dashoffset: -44px; /* The circumference of the circle with 7px radius */
|
||||
}
|
||||
}
|
||||
|
||||
.option-enabled {
|
||||
box-shadow: 0 0 12px rgb(119, 156, 46);
|
||||
@@ -574,12 +1073,46 @@
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
div#question-starters {
|
||||
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
||||
grid-column-gap: 8px;
|
||||
}
|
||||
|
||||
button.question-starter {
|
||||
background: var(--background-color);
|
||||
color: var(--main-text-color);
|
||||
border: 1px solid var(--main-text-color);
|
||||
border-radius: 16px;
|
||||
padding: 5px;
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
line-height: 1.5em;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease-in-out;
|
||||
text-align: left;
|
||||
max-height: 75px;
|
||||
transition: max-height 0.3s ease-in-out;
|
||||
overflow: hidden;
|
||||
}
|
||||
button.question-starter:hover {
|
||||
background: var(--primary-hover);
|
||||
}
|
||||
|
||||
code.chat-response {
|
||||
background: var(--primary-hover);
|
||||
color: var(--primary-inverse);
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
button.reference-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;
|
||||
@@ -592,6 +1125,7 @@
|
||||
}
|
||||
button.reference-button.expanded {
|
||||
max-height: none;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
button.reference-button::before {
|
||||
@@ -624,7 +1158,6 @@
|
||||
background: var(--primary-hover);
|
||||
}
|
||||
|
||||
|
||||
.option-enabled:focus {
|
||||
outline: none !important;
|
||||
border:1px solid #475569;
|
||||
@@ -637,6 +1170,25 @@
|
||||
border-bottom: 1px dotted #475569;
|
||||
}
|
||||
|
||||
a.reference-link {
|
||||
color: var(--main-text-color);
|
||||
border-bottom: 1px dotted var(--main-text-color);
|
||||
}
|
||||
|
||||
button.copy-button {
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
button.copy-button:hover {
|
||||
background: #f5f5f5;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
pre {
|
||||
text-wrap: unset;
|
||||
}
|
||||
|
||||
div.khoj-empty-container {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
@@ -661,7 +1213,7 @@
|
||||
color: #f8fafc;
|
||||
border-radius: 2px;
|
||||
box-shadow: 1px 1px 4px 0 rgba(0, 0, 0, 0.4);
|
||||
font-size small;
|
||||
font-size: small;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
}
|
||||
@@ -678,6 +1230,12 @@
|
||||
margin: 4px;
|
||||
grid-template-columns: auto;
|
||||
}
|
||||
img.text-to-image {
|
||||
max-width: 100%;
|
||||
}
|
||||
#clear-chat-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 600px) {
|
||||
body {
|
||||
@@ -710,6 +1268,10 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.programmatic-output {
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #ddd;
|
||||
|
||||
@@ -101,6 +101,9 @@
|
||||
<div class="card-description-row">
|
||||
<div id="sync-status"></div>
|
||||
</div>
|
||||
<div id="needs-subscription" style="display: none;">
|
||||
Looks like you're out of space to sync your files. <a href="https://app.khoj.dev/config">Upgrade your plan</a> to unlock more space.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -131,7 +134,7 @@
|
||||
margin: 0px;
|
||||
background: var(--background-color);
|
||||
color: #475569;
|
||||
font-family: roboto, karma, segoe ui, sans-serif;
|
||||
font-family: var(--font-family);
|
||||
font-size: small;
|
||||
font-weight: 300;
|
||||
line-height: 1.5em;
|
||||
@@ -336,7 +339,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
button.sync-data {
|
||||
background-color: var(--primary);
|
||||
background-color: var(--primary-hover);
|
||||
border: none;
|
||||
color: var(--main-text-color);
|
||||
padding: 12px;
|
||||
@@ -351,7 +354,7 @@
|
||||
}
|
||||
|
||||
button.sync-data:hover {
|
||||
background-color: var(--primary-hover);
|
||||
background-color: var(--summer-sun);
|
||||
box-shadow: 0px 3px 0px var(--background-color);
|
||||
}
|
||||
.sync-force-toggle {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var $wrap = document.getElementById('loading-animation'),
|
||||
let $wrap = document.getElementById('loading-animation'),
|
||||
|
||||
canvassize = 380,
|
||||
|
||||
@@ -29,7 +29,7 @@ mesh = new THREE.Mesh(
|
||||
new THREE.TubeGeometry(new (THREE.Curve.create(function() {},
|
||||
function(percent) {
|
||||
|
||||
var x = length*Math.sin(pi2*percent),
|
||||
let x = length*Math.sin(pi2*percent),
|
||||
y = radius*Math.cos(pi2*3*percent),
|
||||
z, t;
|
||||
|
||||
@@ -63,7 +63,7 @@ group.add(ring);
|
||||
|
||||
// fake shadow
|
||||
(function() {
|
||||
var plain, i;
|
||||
let plain, i;
|
||||
for (i = 0; i < 10; i++) {
|
||||
plain = new THREE.Mesh(new THREE.PlaneGeometry(length*2+1, radius*3, 1), new THREE.MeshBasicMaterial({color: 0xd1684e, transparent: true, opacity: 0.15}));
|
||||
plain.position.z = -2.5+i*0.5;
|
||||
@@ -94,7 +94,7 @@ function tilt(percent) {
|
||||
}
|
||||
|
||||
function render() {
|
||||
var progress;
|
||||
let progress;
|
||||
|
||||
animatestep = Math.max(0, Math.min(240, toend ? animatestep+1 : animatestep-4));
|
||||
acceleration = easing(animatestep, 0, 1, 240);
|
||||
|
||||
@@ -68,7 +68,7 @@ const schema = {
|
||||
};
|
||||
|
||||
let syncing = false;
|
||||
var state = {}
|
||||
let state = {}
|
||||
const store = new Store({ schema });
|
||||
|
||||
console.log(store);
|
||||
@@ -110,6 +110,21 @@ function filenameToMimeType (filename) {
|
||||
}
|
||||
}
|
||||
|
||||
function processDirectory(filesToPush, folder) {
|
||||
const files = fs.readdirSync(folder.path, { withFileTypes: true, recursive: true });
|
||||
|
||||
for (const file of files) {
|
||||
if (file.isFile() && validFileTypes.includes(file.name.split('.').pop())) {
|
||||
console.log(`Add ${file.name} in ${folder.path} for indexing`);
|
||||
filesToPush.push(path.join(folder.path, file.name));
|
||||
}
|
||||
|
||||
if (file.isDirectory()) {
|
||||
processDirectory(filesToPush, {'path': path.join(folder.path, file.name)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pushDataToKhoj (regenerate = false) {
|
||||
// Don't sync if token or hostURL is not set or if already syncing
|
||||
if (store.get('khojToken') === '' || store.get('hostURL') === '' || syncing === true) {
|
||||
@@ -132,16 +147,11 @@ function pushDataToKhoj (regenerate = false) {
|
||||
|
||||
// Collect paths of all indexable files in configured folders
|
||||
for (const folder of folders) {
|
||||
const files = fs.readdirSync(folder.path, { withFileTypes: true });
|
||||
for (const file of files) {
|
||||
if (file.isFile() && validFileTypes.includes(file.name.split('.').pop())) {
|
||||
filesToPush.push(path.join(folder.path, file.name));
|
||||
}
|
||||
}
|
||||
processDirectory(filesToPush, folder);
|
||||
}
|
||||
|
||||
const lastSync = store.get('lastSync') || [];
|
||||
const formData = new FormData();
|
||||
const filesDataToPush = [];
|
||||
for (const file of filesToPush) {
|
||||
const stats = fs.statSync(file);
|
||||
if (!regenerate) {
|
||||
@@ -157,7 +167,7 @@ function pushDataToKhoj (regenerate = false) {
|
||||
let mimeType = filenameToMimeType(file) + (encoding === "utf8" ? "; charset=UTF-8" : "");
|
||||
let fileContent = Buffer.from(fs.readFileSync(file, { encoding: encoding }), encoding);
|
||||
let fileObj = new Blob([fileContent], { type: mimeType });
|
||||
formData.append('files', fileObj, file);
|
||||
filesDataToPush.push({blob: fileObj, path: file});
|
||||
state[file] = {
|
||||
success: true,
|
||||
}
|
||||
@@ -174,44 +184,51 @@ function pushDataToKhoj (regenerate = false) {
|
||||
for (const syncedFile of lastSync) {
|
||||
if (!filesToPush.includes(syncedFile.path)) {
|
||||
fileObj = new Blob([""], { type: filenameToMimeType(syncedFile.path) });
|
||||
formData.append('files', fileObj, syncedFile.path);
|
||||
filesDataToPush.push({blob: fileObj, path: syncedFile.path});
|
||||
}
|
||||
}
|
||||
|
||||
// Send collected files to Khoj server for indexing
|
||||
if (!!formData?.entries()?.next().value) {
|
||||
const hostURL = store.get('hostURL') || KHOJ_URL;
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${store.get("khojToken")}`
|
||||
};
|
||||
axios.post(`${hostURL}/api/v1/index/update?force=${regenerate}&client=desktop`, formData, { headers })
|
||||
.then(response => {
|
||||
console.log(response.data);
|
||||
let lastSync = [];
|
||||
for (const file of filesToPush) {
|
||||
lastSync.push({
|
||||
path: file,
|
||||
datetime: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
store.set('lastSync', lastSync);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
state['completed'] = false
|
||||
})
|
||||
.finally(() => {
|
||||
// Syncing complete
|
||||
syncing = false;
|
||||
const win = BrowserWindow.getAllWindows()[0];
|
||||
if (win) win.webContents.send('update-state', state);
|
||||
});
|
||||
} else {
|
||||
const hostURL = store.get('hostURL') || KHOJ_URL;
|
||||
const headers = { 'Authorization': `Bearer ${store.get("khojToken")}` };
|
||||
let requests = [];
|
||||
|
||||
// Request indexing files on server. With upto 1000 files in each request
|
||||
for (let i = 0; i < filesDataToPush.length; i += 1000) {
|
||||
const filesDataGroup = filesDataToPush.slice(i, i + 1000);
|
||||
const formData = new FormData();
|
||||
filesDataGroup.forEach(fileData => { formData.append('files', fileData.blob, fileData.path) });
|
||||
let request = axios.post(`${hostURL}/api/v1/index/update?force=${regenerate}&client=desktop`, formData, { headers });
|
||||
requests.push(request);
|
||||
}
|
||||
|
||||
// Wait for requests batch to finish
|
||||
Promise
|
||||
.all(requests)
|
||||
.then(responses => {
|
||||
const lastSync = filesToPush
|
||||
.filter(file => responses.find(response => response.data.includes(file)))
|
||||
.map(file => ({ path: file, datetime: new Date().toISOString() }));
|
||||
store.set('lastSync', lastSync);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
state["completed"] = false;
|
||||
if (error?.response?.status === 429 && (win = BrowserWindow.getAllWindows()[0])) {
|
||||
state["error"] = `Looks like you're out of space to sync your files. <a href="https://app.khoj.dev/config">Upgrade your plan</a> to unlock more space.`;
|
||||
} else if (error?.code === 'ECONNREFUSED') {
|
||||
state["error"] = `Could not connect to Khoj server. Ensure you can connect to it at ${error.address}:${error.port}.`;
|
||||
} else {
|
||||
state["error"] = `Sync was unsuccessful at ${currentTime.toLocaleTimeString()}. Contact team@khoj.dev to report this issue.`;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
// Syncing complete
|
||||
syncing = false;
|
||||
const win = BrowserWindow.getAllWindows()[0];
|
||||
if (win) win.webContents.send('update-state', state);
|
||||
}
|
||||
if (win = BrowserWindow.getAllWindows()[0]) {
|
||||
win.webContents.send('update-state', state);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pushDataToKhoj();
|
||||
@@ -369,7 +386,7 @@ const createWindow = (tab = 'chat.html') => {
|
||||
firstRun = false;
|
||||
|
||||
// Create splash screen
|
||||
var splash = new BrowserWindow({width: 400, height: 400, transparent: true, frame: false, alwaysOnTop: true});
|
||||
let splash = new BrowserWindow({width: 400, height: 400, transparent: true, frame: false, alwaysOnTop: true});
|
||||
splash.setOpacity(1.0);
|
||||
splash.setBackgroundColor('#d16b4e');
|
||||
splash.loadFile('splash.html');
|
||||
@@ -396,6 +413,11 @@ app.whenReady().then(() => {
|
||||
event.reply('update-state', arg);
|
||||
});
|
||||
|
||||
ipcMain.on('needsSubscription', (event, arg) => {
|
||||
console.log(arg);
|
||||
event.reply('needsSubscription', arg);
|
||||
});
|
||||
|
||||
ipcMain.on('navigate', (event, page) => {
|
||||
win.loadFile(page);
|
||||
});
|
||||
@@ -506,7 +528,8 @@ openWindow = (page) => {
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const icon = nativeImage.createFromPath('assets/icons/favicon-20x20.png')
|
||||
const iconPath = path.join(__dirname, './assets/icons/favicon-20x20.png')
|
||||
const icon = nativeImage.createFromPath(iconPath)
|
||||
tray = new Tray(icon)
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Khoj",
|
||||
"version": "1.0.0",
|
||||
"version": "1.4.0",
|
||||
"description": "An AI copilot for your Second Brain",
|
||||
"author": "Saba Imran, Debanjum Singh Solanky <team@khoj.dev>",
|
||||
"license": "GPL-3.0-or-later",
|
||||
@@ -17,7 +17,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@todesktop/runtime": "^1.3.0",
|
||||
"axios": "^1.6.0",
|
||||
"axios": "^1.6.4",
|
||||
"cron": "^2.4.3",
|
||||
"electron-store": "^8.1.0",
|
||||
"fs": "^0.0.1-security"
|
||||
|
||||
@@ -31,6 +31,10 @@ contextBridge.exposeInMainWorld('updateStateAPI', {
|
||||
onUpdateState: (callback) => ipcRenderer.on('update-state', callback)
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld('needsSubscriptionAPI', {
|
||||
onNeedsSubscription: (callback) => ipcRenderer.on('needsSubscription', callback)
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld('removeFileAPI', {
|
||||
removeFile: (filePath) => ipcRenderer.invoke('removeFile', filePath)
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const setFolderButton = document.getElementById('update-folder');
|
||||
const setFileButton = document.getElementById('update-file');
|
||||
const showKey = document.getElementById('show-key');
|
||||
const loadingBar = document.getElementById('loading-bar');
|
||||
const needsSubscriptionElement = document.getElementById('needs-subscription');
|
||||
|
||||
async function removeFile(filePath) {
|
||||
const updatedFiles = await window.removeFileAPI.removeFile(filePath);
|
||||
@@ -158,13 +158,22 @@ window.updateStateAPI.onUpdateState((event, state) => {
|
||||
nextSyncTime = new Date();
|
||||
nextSyncTime.setMinutes(Math.ceil((nextSyncTime.getMinutes() + 1) / 10) * 10);
|
||||
if (state.completed == false) {
|
||||
syncStatusElement.innerHTML = `Sync was unsuccessful at ${currentTime.toLocaleTimeString()}. Contact team@khoj.dev to report this issue.`;
|
||||
if (state.error) syncStatusElement.innerHTML = state.error;
|
||||
return;
|
||||
}
|
||||
const options = { hour: '2-digit', minute: '2-digit' };
|
||||
syncStatusElement.innerHTML = `⏱️ Synced at ${currentTime.toLocaleTimeString(undefined, options)}. Next sync at ${nextSyncTime.toLocaleTimeString(undefined, options)}.`;
|
||||
});
|
||||
|
||||
window.needsSubscriptionAPI.onNeedsSubscription((event, needsSubscription) => {
|
||||
console.log("needs subscription", needsSubscription);
|
||||
if (needsSubscription) {
|
||||
needsSubscriptionElement.style.display = 'block';
|
||||
} else {
|
||||
needsSubscriptionElement.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
const urlInput = document.getElementById('khoj-host-url');
|
||||
(async function() {
|
||||
const url = await window.hostURLAPI.getURL();
|
||||
|
||||
@@ -304,7 +304,7 @@
|
||||
margin: 0px;
|
||||
background: var(--background-color);
|
||||
color: var(--main-text-color);
|
||||
font-family: roboto, karma, segoe ui, sans-serif;
|
||||
font-family: var(--font-family);
|
||||
font-size: small;
|
||||
font-weight: 300;
|
||||
line-height: 1.5em;
|
||||
|
||||
@@ -163,12 +163,12 @@ atomically@^1.7.0:
|
||||
resolved "https://registry.yarnpkg.com/atomically/-/atomically-1.7.0.tgz#c07a0458432ea6dbc9a3506fffa424b48bccaafe"
|
||||
integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==
|
||||
|
||||
axios@^1.6.0:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2"
|
||||
integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==
|
||||
axios@^1.6.4:
|
||||
version "1.6.5"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.5.tgz#2c090da14aeeab3770ad30c3a1461bc970fb0cd8"
|
||||
integrity sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.0"
|
||||
follow-redirects "^1.15.4"
|
||||
form-data "^4.0.0"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
@@ -485,10 +485,10 @@ find-up@^3.0.0:
|
||||
dependencies:
|
||||
locate-path "^3.0.0"
|
||||
|
||||
follow-redirects@^1.15.0:
|
||||
version "1.15.2"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
||||
follow-redirects@^1.15.4:
|
||||
version "1.15.5"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020"
|
||||
integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
;; Saba Imran <saba@khoj.dev>
|
||||
;; Description: An AI copilot for your Second Brain
|
||||
;; Keywords: search, chat, org-mode, outlines, markdown, pdf, image
|
||||
;; Version: 1.0.1
|
||||
;; Version: 1.4.0
|
||||
;; Package-Requires: ((emacs "27.1") (transient "0.3.0") (dash "2.19.1"))
|
||||
;; URL: https://github.com/khoj-ai/khoj/tree/master/src/interface/emacs
|
||||
|
||||
@@ -98,6 +98,11 @@
|
||||
:group 'khoj
|
||||
:type 'string)
|
||||
|
||||
(defcustom khoj-auto-index t
|
||||
"Should content be automatically re-indexed every `khoj-index-interval' seconds."
|
||||
:group 'khoj
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom khoj-index-interval 3600
|
||||
"Interval (in seconds) to wait before updating content index."
|
||||
:group 'khoj
|
||||
@@ -236,7 +241,7 @@ for example), set this to the full interpreter path."
|
||||
(member val '("python" "python3" "pythonw" "py")))
|
||||
:group 'khoj)
|
||||
|
||||
(defcustom khoj-org-files (org-agenda-files t t)
|
||||
(defcustom khoj-org-files nil
|
||||
"List of org-files to index on khoj server."
|
||||
:type '(repeat string)
|
||||
:group 'khoj)
|
||||
@@ -246,6 +251,19 @@ for example), set this to the full interpreter path."
|
||||
:type '(repeat string)
|
||||
:group 'khoj)
|
||||
|
||||
(make-obsolete-variable 'khoj-org-directories 'khoj-index-directories "1.2.0" 'set)
|
||||
(make-obsolete-variable 'khoj-org-files 'khoj-index-files "1.2.0" 'set)
|
||||
|
||||
(defcustom khoj-index-files (org-agenda-files t t)
|
||||
"List of org, markdown, pdf and other plaintext to index on khoj server."
|
||||
:type '(repeat string)
|
||||
:group 'khoj)
|
||||
|
||||
(defcustom khoj-index-directories nil
|
||||
"List of directories with org, markdown, pdf and other plaintext files to index on khoj server."
|
||||
:type '(repeat string)
|
||||
:group 'khoj)
|
||||
|
||||
(defcustom khoj-auto-setup t
|
||||
"Automate install, configure and start of khoj server.
|
||||
Auto invokes setup steps on calling main entrypoint."
|
||||
@@ -330,7 +348,7 @@ Auto invokes setup steps on calling main entrypoint."
|
||||
t
|
||||
;; else general check via ping to khoj-server-url
|
||||
(if (ignore-errors
|
||||
(url-retrieve-synchronously (format "%s/api/config/data/default" khoj-server-url)))
|
||||
(url-retrieve-synchronously (format "%s/api/health" khoj-server-url)))
|
||||
;; Successful ping to non-emacs khoj server indicates it is started and ready.
|
||||
;; So update ready state tracker variable (and implicitly return true for started)
|
||||
(setq khoj--server-ready? t)
|
||||
@@ -390,12 +408,16 @@ Auto invokes setup steps on calling main entrypoint."
|
||||
"Send files at `FILE-PATHS' to the Khoj server to index for search and chat.
|
||||
`FORCE' re-indexes all files of `CONTENT-TYPE' even if they are already indexed."
|
||||
(interactive)
|
||||
(let ((boundary (format "-------------------------%d" (random (expt 10 10))))
|
||||
(files-to-index (or file-paths
|
||||
(append (mapcan (lambda (dir) (directory-files-recursively dir "\\.org$")) khoj-org-directories) khoj-org-files)))
|
||||
(type-query (if (or (equal content-type "all") (not content-type)) "" (format "t=%s" content-type)))
|
||||
(inhibit-message t)
|
||||
(message-log-max nil))
|
||||
(let* ((boundary (format "-------------------------%d" (random (expt 10 10))))
|
||||
;; Use `khoj-index-directories', `khoj-index-files' when set, else fallback to `khoj-org-directories', `khoj-org-files'
|
||||
;; This is a temporary change. `khoj-org-directories', `khoj-org-files' are deprecated. They will be removed in a future release
|
||||
(content-directories (or khoj-index-directories khoj-org-directories))
|
||||
(content-files (or khoj-index-files khoj-org-files))
|
||||
(files-to-index (or file-paths
|
||||
(append (mapcan (lambda (dir) (directory-files-recursively dir "\\.\\(org\\|md\\|markdown\\|pdf\\|txt\\|rst\\|xml\\|htm\\|html\\)$")) content-directories) content-files)))
|
||||
(type-query (if (or (equal content-type "all") (not content-type)) "" (format "t=%s" content-type)))
|
||||
(inhibit-message t)
|
||||
(message-log-max nil))
|
||||
(let ((url-request-method "POST")
|
||||
(url-request-data (khoj--render-files-as-request-body files-to-index khoj--indexed-files boundary))
|
||||
(url-request-extra-headers `(("content-type" . ,(format "multipart/form-data; boundary=%s" boundary))
|
||||
@@ -405,14 +427,16 @@ Auto invokes setup steps on calling main entrypoint."
|
||||
;; render response from indexing API endpoint on server
|
||||
(lambda (status)
|
||||
(if (not status)
|
||||
(message "khoj.el: %scontent index %supdated" (if content-type (format "%s " content-type) "") (if force "force " ""))
|
||||
(with-current-buffer (current-buffer)
|
||||
(goto-char "\n\n")
|
||||
(message "khoj.el: Failed to %supdate %s content index. Status: %s. Response: %s"
|
||||
(if force "force " "")
|
||||
content-type
|
||||
status
|
||||
(string-trim (buffer-substring-no-properties (point) (point-max)))))))
|
||||
(message "khoj.el: %scontent index %supdated" (if content-type (format "%s " content-type) "all ") (if force "force " ""))
|
||||
(progn
|
||||
(khoj--delete-open-network-connections-to-server)
|
||||
(with-current-buffer (current-buffer)
|
||||
(search-forward "\n\n" nil t)
|
||||
(message "khoj.el: Failed to %supdate %scontent index. Status: %s%s"
|
||||
(if force "force " "")
|
||||
(if content-type (format "%s " content-type) "all")
|
||||
(string-trim (format "%s %s" (nth 1 (nth 1 status)) (nth 2 (nth 1 status))))
|
||||
(if (> (- (point-max) (point)) 0) (format ". Response: %s" (string-trim (buffer-substring-no-properties (point) (point-max)))) ""))))))
|
||||
nil t t)))
|
||||
(setq khoj--indexed-files files-to-index)))
|
||||
|
||||
@@ -423,20 +447,30 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
|
||||
(set-buffer-multibyte nil)
|
||||
(insert "\n")
|
||||
(dolist (file-to-index files-to-index)
|
||||
;; find file content-type. Choose from org, markdown, pdf, plaintext
|
||||
(let ((content-type (cond ((string-match "\\.org$" file-to-index) "text/org")
|
||||
((string-match "\\.\\(md\\|markdown\\)$" file-to-index) "text/markdown")
|
||||
((string-match "\\.pdf$" file-to-index) "application/pdf")
|
||||
(t "text/plain"))))
|
||||
(insert (format "--%s\r\n" boundary))
|
||||
(insert (format "Content-Disposition: form-data; name=\"files\"; filename=\"%s\"\r\n" file-to-index))
|
||||
(insert "Content-Type: text/org\r\n\r\n")
|
||||
(insert (format "Content-Type: %s\r\n\r\n" content-type))
|
||||
(insert (with-temp-buffer
|
||||
(insert-file-contents-literally file-to-index)
|
||||
(buffer-string)))
|
||||
(insert "\r\n"))
|
||||
(insert "\r\n")))
|
||||
(dolist (file-to-index previously-indexed-files)
|
||||
(when (not (member file-to-index files-to-index))
|
||||
(insert (format "--%s\r\n" boundary))
|
||||
(insert (format "Content-Disposition: form-data; name=\"files\"; filename=\"%s\"\r\n" file-to-index))
|
||||
(insert "Content-Type: text/org\r\n\r\n")
|
||||
(insert "")
|
||||
(insert "\r\n")))
|
||||
;; find file content-type. Choose from org, markdown, pdf, plaintext
|
||||
(let ((content-type (cond ((string-match "\\.org$" file-to-index) "text/org")
|
||||
((string-match "\\.\\(md\\|markdown\\)$" file-to-index) "text/markdown")
|
||||
((string-match "\\.pdf$" file-to-index) "application/pdf")
|
||||
(t "text/plain"))))
|
||||
(insert (format "--%s\r\n" boundary))
|
||||
(insert (format "Content-Disposition: form-data; name=\"files\"; filename=\"%s\"\r\n" file-to-index))
|
||||
(insert "Content-Type: text/org\r\n\r\n")
|
||||
(insert "")
|
||||
(insert "\r\n"))))
|
||||
(insert (format "--%s--\r\n" boundary))
|
||||
(buffer-string)))
|
||||
|
||||
@@ -444,8 +478,9 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
|
||||
(when khoj--index-timer
|
||||
(cancel-timer khoj--index-timer))
|
||||
;; Send files to index on server every `khoj-index-interval' seconds
|
||||
(setq khoj--index-timer
|
||||
(run-with-timer 60 khoj-index-interval 'khoj--server-index-files))
|
||||
(when khoj-auto-index
|
||||
(setq khoj--index-timer
|
||||
(run-with-timer 60 khoj-index-interval 'khoj--server-index-files)))
|
||||
|
||||
|
||||
;; -------------------------------------------
|
||||
@@ -568,22 +603,6 @@ Use `BOUNDARY' to separate files. This is sent to Khoj server as a POST request.
|
||||
;; --------------
|
||||
;; Query Khoj API
|
||||
;; --------------
|
||||
|
||||
(defun khoj--post-new-config (config)
|
||||
"Configure khoj server with provided CONFIG."
|
||||
;; POST provided config to khoj server
|
||||
(let ((url-request-method "POST")
|
||||
(url-request-extra-headers `(("Content-Type" . "application/json")
|
||||
("Authorization" . ,(format "Bearer %s" khoj-api-key))))
|
||||
(url-request-data (encode-coding-string (json-encode-alist config) 'utf-8))
|
||||
(config-url (format "%s/api/config/data" khoj-server-url)))
|
||||
(with-current-buffer (url-retrieve-synchronously config-url)
|
||||
(buffer-string)))
|
||||
;; Update index on khoj server after configuration update
|
||||
(let ((khoj--server-ready? nil)
|
||||
(url-request-extra-headers `(("Authorization" . ,(format "\"Bearer %s\"" khoj-api-key)))))
|
||||
(url-retrieve (format "%s/api/update?client=emacs" khoj-server-url) #'identity)))
|
||||
|
||||
(defun khoj--get-enabled-content-types ()
|
||||
"Get content types enabled for search from API."
|
||||
(let ((config-url (format "%s/api/config/types" khoj-server-url))
|
||||
@@ -858,7 +877,7 @@ RECEIVE-DATE is the message receive date."
|
||||
(let ((proc-buf (buffer-name (process-buffer proc)))
|
||||
(khoj-network-proc-buf (string-join (split-string khoj-server-url "://") " ")))
|
||||
(when (string-match (format "%s" khoj-network-proc-buf) proc-buf)
|
||||
(delete-process proc)))))
|
||||
(ignore-errors (delete-process proc))))))
|
||||
|
||||
(defun khoj--teardown-incremental-search ()
|
||||
"Teardown hooks used for incremental search."
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "khoj",
|
||||
"name": "Khoj",
|
||||
"version": "1.0.1",
|
||||
"version": "1.4.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "An AI copilot for your Second Brain",
|
||||
"author": "Khoj Inc.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Khoj",
|
||||
"version": "1.0.1",
|
||||
"version": "1.4.0",
|
||||
"description": "An AI copilot for your Second Brain",
|
||||
"author": "Debanjum Singh Solanky, Saba Imran <team@khoj.dev>",
|
||||
"license": "GPL-3.0-or-later",
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { App, Modal, request } from 'obsidian';
|
||||
import { App, MarkdownRenderer, Modal, request, requestUrl, setIcon } from 'obsidian';
|
||||
import { KhojSetting } from 'src/settings';
|
||||
import fetch from "node-fetch";
|
||||
|
||||
export interface ChatJsonResult {
|
||||
image?: string;
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
|
||||
export class KhojChatModal extends Modal {
|
||||
result: string;
|
||||
setting: KhojSetting;
|
||||
@@ -11,17 +17,20 @@ export class KhojChatModal extends Modal {
|
||||
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];
|
||||
this.scope.register([], 'Enter', async () => { await this.chat() });
|
||||
}
|
||||
|
||||
// Clear text after extracting message to send
|
||||
let user_message = input_el.value;
|
||||
input_el.value = "";
|
||||
async chat() {
|
||||
// Get text in chat input element
|
||||
let input_el = <HTMLTextAreaElement>this.contentEl.getElementsByClassName("khoj-chat-input")[0];
|
||||
|
||||
// Get and render chat response to user message
|
||||
await this.getChatResponse(user_message);
|
||||
});
|
||||
// Clear text after extracting message to send
|
||||
let user_message = input_el.value.trim();
|
||||
input_el.value = "";
|
||||
this.autoResize();
|
||||
|
||||
// Get and render chat response to user message
|
||||
await this.getChatResponse(user_message);
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
@@ -32,75 +41,175 @@ export class KhojChatModal extends 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" } });
|
||||
let chatBodyEl = contentEl.createDiv({ attr: { id: "khoj-chat-body", class: "khoj-chat-body" } });
|
||||
|
||||
// Get chat history from Khoj backend
|
||||
await this.getChatHistory();
|
||||
let getChatHistorySucessfully = await this.getChatHistory(chatBodyEl);
|
||||
let placeholderText = getChatHistorySucessfully ? "Message" : "Configure Khoj to enable chat";
|
||||
|
||||
// Add chat input field
|
||||
const chatInput = 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"
|
||||
}
|
||||
})
|
||||
chatInput.addEventListener('change', (event) => { this.result = (<HTMLInputElement>event.target).value });
|
||||
let inputRow = contentEl.createDiv("khoj-input-row");
|
||||
let clearChat = inputRow.createEl("button", {
|
||||
text: "Clear History",
|
||||
attr: {
|
||||
class: "khoj-input-row-button clickable-icon",
|
||||
},
|
||||
})
|
||||
clearChat.addEventListener('click', async (_) => { await this.clearConversationHistory() });
|
||||
setIcon(clearChat, "trash");
|
||||
|
||||
let chatInput = inputRow.createEl("textarea", {
|
||||
attr: {
|
||||
id: "khoj-chat-input",
|
||||
autofocus: "autofocus",
|
||||
placeholder: placeholderText,
|
||||
class: "khoj-chat-input option",
|
||||
disabled: !getChatHistorySucessfully ? "disabled" : null
|
||||
},
|
||||
})
|
||||
chatInput.addEventListener('input', (_) => { this.onChatInput() });
|
||||
chatInput.addEventListener('keydown', (event) => { this.incrementalChat(event) });
|
||||
|
||||
let transcribe = inputRow.createEl("button", {
|
||||
text: "Transcribe",
|
||||
attr: {
|
||||
id: "khoj-transcribe",
|
||||
class: "khoj-transcribe khoj-input-row-button clickable-icon ",
|
||||
},
|
||||
})
|
||||
transcribe.addEventListener('mousedown', async (event) => { await this.speechToText(event) });
|
||||
transcribe.addEventListener('touchstart', async (event) => { await this.speechToText(event) });
|
||||
transcribe.addEventListener('touchend', async (event) => { await this.speechToText(event) });
|
||||
transcribe.addEventListener('touchcancel', async (event) => { await this.speechToText(event) });
|
||||
setIcon(transcribe, "mic");
|
||||
|
||||
let send = inputRow.createEl("button", {
|
||||
text: "Send",
|
||||
attr: {
|
||||
id: "khoj-chat-send",
|
||||
class: "khoj-chat-send khoj-input-row-button clickable-icon",
|
||||
},
|
||||
})
|
||||
setIcon(send, "arrow-up-circle");
|
||||
let sendImg = <SVGElement>send.getElementsByClassName("lucide-arrow-up-circle")[0]
|
||||
sendImg.addEventListener('click', async (_) => { await this.chat() });
|
||||
|
||||
// Scroll to bottom of modal, till the send message input box
|
||||
this.modalEl.scrollTop = this.modalEl.scrollHeight;
|
||||
chatInput.focus();
|
||||
}
|
||||
|
||||
generateReference(messageEl: any, reference: string, index: number) {
|
||||
// Generate HTML for Chat Reference
|
||||
// `<sup><abbr title="${escaped_ref}" tabindex="0">${index}</abbr></sup>`;
|
||||
generateReference(messageEl: Element, reference: string, index: number) {
|
||||
// Escape reference for HTML rendering
|
||||
let escaped_ref = reference.replace(/"/g, """)
|
||||
return messageEl.createEl("sup").createEl("abbr", {
|
||||
attr: {
|
||||
title: escaped_ref,
|
||||
tabindex: "0",
|
||||
},
|
||||
text: `[${index}] `,
|
||||
|
||||
// Generate HTML for Chat Reference
|
||||
let short_ref = escaped_ref.slice(0, 100);
|
||||
short_ref = short_ref.length < escaped_ref.length ? short_ref + "..." : short_ref;
|
||||
let referenceButton = messageEl.createEl('button');
|
||||
referenceButton.textContent = short_ref;
|
||||
referenceButton.id = `ref-${index}`;
|
||||
referenceButton.classList.add("reference-button");
|
||||
referenceButton.classList.add("collapsed");
|
||||
referenceButton.tabIndex = 0;
|
||||
|
||||
// Add event listener to toggle full reference on click
|
||||
referenceButton.addEventListener('click', function() {
|
||||
console.log(`Toggling ref-${index}`)
|
||||
if (this.classList.contains("collapsed")) {
|
||||
this.classList.remove("collapsed");
|
||||
this.classList.add("expanded");
|
||||
this.textContent = escaped_ref;
|
||||
} else {
|
||||
this.classList.add("collapsed");
|
||||
this.classList.remove("expanded");
|
||||
this.textContent = short_ref;
|
||||
}
|
||||
});
|
||||
|
||||
return referenceButton;
|
||||
}
|
||||
|
||||
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));
|
||||
renderMessageWithReferences(chatEl: Element, message: string, sender: string, context?: string[], dt?: Date, intentType?: string) {
|
||||
if (!message) {
|
||||
return;
|
||||
} else if (intentType === "text-to-image") {
|
||||
let imageMarkdown = ``;
|
||||
this.renderMessage(chatEl, imageMarkdown, sender, dt);
|
||||
return;
|
||||
} else if (!context) {
|
||||
this.renderMessage(chatEl, message, sender, dt);
|
||||
return;
|
||||
} else if (!!context && context?.length === 0) {
|
||||
this.renderMessage(chatEl, message, sender, dt);
|
||||
return;
|
||||
}
|
||||
let chatMessageEl = this.renderMessage(chatEl, message, sender, dt);
|
||||
let chatMessageBodyEl = chatMessageEl.getElementsByClassName("khoj-chat-message-text")[0]
|
||||
let references = chatMessageBodyEl.createDiv();
|
||||
|
||||
let referenceExpandButton = references.createEl('button');
|
||||
referenceExpandButton.classList.add("reference-expand-button");
|
||||
let numReferences = 0;
|
||||
|
||||
if (context) {
|
||||
numReferences += context.length;
|
||||
}
|
||||
|
||||
let referenceSection = references.createEl('div');
|
||||
referenceSection.classList.add("reference-section");
|
||||
referenceSection.classList.add("collapsed");
|
||||
|
||||
referenceExpandButton.addEventListener('click', function() {
|
||||
if (referenceSection.classList.contains("collapsed")) {
|
||||
referenceSection.classList.remove("collapsed");
|
||||
referenceSection.classList.add("expanded");
|
||||
} else {
|
||||
referenceSection.classList.add("collapsed");
|
||||
referenceSection.classList.remove("expanded");
|
||||
}
|
||||
});
|
||||
|
||||
references.classList.add("references");
|
||||
if (context) {
|
||||
context.map((reference, index) => {
|
||||
this.generateReference(referenceSection, reference, index + 1);
|
||||
});
|
||||
}
|
||||
|
||||
let expandButtonText = numReferences == 1 ? "1 reference" : `${numReferences} references`;
|
||||
referenceExpandButton.innerHTML = expandButtonText;
|
||||
}
|
||||
|
||||
renderMessage(message: string, sender: string, dt?: Date): Element | null {
|
||||
renderMessage(chatEl: Element, message: string, sender: string, dt?: Date, raw: boolean=false): Element {
|
||||
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({
|
||||
let chatMessageEl = chatEl.createDiv({
|
||||
attr: {
|
||||
"data-meta": `${emojified_sender} at ${message_time}`,
|
||||
class: `khoj-chat-message ${sender}`
|
||||
},
|
||||
}).createDiv({
|
||||
attr: {
|
||||
class: `khoj-chat-message-text ${sender}`
|
||||
},
|
||||
text: `${message}`
|
||||
})
|
||||
let chat_message_body_el = chatMessageEl.createDiv();
|
||||
chat_message_body_el.addClasses(["khoj-chat-message-text", sender]);
|
||||
let chat_message_body_text_el = chat_message_body_el.createDiv();
|
||||
if (raw) {
|
||||
chat_message_body_text_el.innerHTML = message;
|
||||
} else {
|
||||
// @ts-ignore
|
||||
MarkdownRenderer.renderMarkdown(message, chat_message_body_text_el, '', null);
|
||||
}
|
||||
|
||||
// Remove user-select: none property to make text selectable
|
||||
chat_message_el.style.userSelect = "text";
|
||||
chatMessageEl.style.userSelect = "text";
|
||||
|
||||
// Scroll to bottom after inserting chat messages
|
||||
this.modalEl.scrollTop = this.modalEl.scrollHeight;
|
||||
|
||||
return chat_message_el
|
||||
return chatMessageEl
|
||||
}
|
||||
|
||||
createKhojResponseDiv(dt?: Date): HTMLDivElement {
|
||||
@@ -126,8 +235,11 @@ export class KhojChatModal extends Modal {
|
||||
return chat_message_el
|
||||
}
|
||||
|
||||
renderIncrementalMessage(htmlElement: HTMLDivElement, additionalMessage: string) {
|
||||
htmlElement.innerHTML += additionalMessage;
|
||||
async renderIncrementalMessage(htmlElement: HTMLDivElement, additionalMessage: string) {
|
||||
this.result += additionalMessage;
|
||||
htmlElement.innerHTML = "";
|
||||
// @ts-ignore
|
||||
await MarkdownRenderer.renderMarkdown(this.result, htmlElement, '', null);
|
||||
// Scroll to bottom of modal, till the send message input box
|
||||
this.modalEl.scrollTop = this.modalEl.scrollHeight;
|
||||
}
|
||||
@@ -139,15 +251,33 @@ export class KhojChatModal extends Modal {
|
||||
return `${time_string}, ${date_string}`;
|
||||
}
|
||||
|
||||
async getChatHistory(): Promise<void> {
|
||||
async getChatHistory(chatBodyEl: Element): Promise<boolean> {
|
||||
// Get chat history from Khoj backend
|
||||
let chatUrl = `${this.setting.khojUrl}/api/chat/history?client=obsidian`;
|
||||
let headers = { "Authorization": `Bearer ${this.setting.khojApiKey}` };
|
||||
let response = await request({ url: chatUrl, headers: headers });
|
||||
let chatLogs = JSON.parse(response).response;
|
||||
chatLogs.forEach((chatLog: any) => {
|
||||
this.renderMessageWithReferences(chatLog.message, chatLog.by, chatLog.context, new Date(chatLog.created));
|
||||
});
|
||||
|
||||
try {
|
||||
let response = await fetch(chatUrl, { method: "GET", headers: headers });
|
||||
let responseJson: any = await response.json();
|
||||
|
||||
if (responseJson.detail) {
|
||||
// If the server returns error details in response, render a setup hint.
|
||||
let setupMsg = "Hi 👋🏾, to start chatting add available chat models options via [the Django Admin panel](/server/admin) on the Server";
|
||||
this.renderMessage(chatBodyEl, setupMsg, "khoj", undefined);
|
||||
|
||||
return false;
|
||||
} else if (responseJson.response) {
|
||||
let chatLogs = responseJson.response;
|
||||
chatLogs.forEach((chatLog: any) => {
|
||||
this.renderMessageWithReferences(chatBodyEl, chatLog.message, chatLog.by, chatLog.context, new Date(chatLog.created), chatLog.intent?.type);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
let errorMsg = "Unable to get response from Khoj server ❤️🩹. Ensure server is running or contact developers for help at [team@khoj.dev](mailto:team@khoj.dev) or in [Discord](https://discord.gg/BDgyabRM6e)";
|
||||
this.renderMessage(chatBodyEl, errorMsg, "khoj", undefined);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async getChatResponse(query: string | undefined | null): Promise<void> {
|
||||
@@ -155,7 +285,8 @@ export class KhojChatModal extends Modal {
|
||||
if (!query || query === "") return;
|
||||
|
||||
// Render user query as chat message
|
||||
this.renderMessage(query, "you");
|
||||
let chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
|
||||
this.renderMessage(chatBodyEl, query, "you");
|
||||
|
||||
// Get chat response from Khoj backend
|
||||
let encodedQuery = encodeURIComponent(query);
|
||||
@@ -163,7 +294,8 @@ export class KhojChatModal extends Modal {
|
||||
let responseElement = this.createKhojResponseDiv();
|
||||
|
||||
// Temporary status message to indicate that Khoj is thinking
|
||||
this.renderIncrementalMessage(responseElement, "🤔");
|
||||
this.result = "";
|
||||
await this.renderIncrementalMessage(responseElement, "🤔");
|
||||
|
||||
let response = await fetch(chatUrl, {
|
||||
method: "GET",
|
||||
@@ -183,15 +315,250 @@ export class KhojChatModal extends Modal {
|
||||
responseElement.innerHTML = "";
|
||||
}
|
||||
|
||||
for await (const chunk of response.body) {
|
||||
const responseText = chunk.toString();
|
||||
if (responseText.startsWith("### compiled references:")) {
|
||||
return;
|
||||
this.result = "";
|
||||
responseElement.innerHTML = "";
|
||||
if (response.headers.get("content-type") == "application/json") {
|
||||
let responseText = ""
|
||||
try {
|
||||
const responseAsJson = await response.json() as ChatJsonResult;
|
||||
if (responseAsJson.image) {
|
||||
responseText = ``;
|
||||
} else if (responseAsJson.detail) {
|
||||
responseText = responseAsJson.detail;
|
||||
}
|
||||
} catch (error) {
|
||||
// If the chunk is not a JSON object, just display it as is
|
||||
responseText = response.body.read().toString()
|
||||
} finally {
|
||||
await this.renderIncrementalMessage(responseElement, responseText);
|
||||
}
|
||||
}
|
||||
|
||||
for await (const chunk of response.body) {
|
||||
let responseText = chunk.toString();
|
||||
if (responseText.includes("### compiled references:")) {
|
||||
const [additionalResponse, rawReference] = responseText.split("### compiled references:", 2);
|
||||
await this.renderIncrementalMessage(responseElement, additionalResponse);
|
||||
|
||||
const rawReferenceAsJson = JSON.parse(rawReference);
|
||||
let references = responseElement.createDiv();
|
||||
references.classList.add("references");
|
||||
|
||||
let referenceExpandButton = references.createEl('button');
|
||||
referenceExpandButton.classList.add("reference-expand-button");
|
||||
|
||||
let referenceSection = references.createDiv();
|
||||
referenceSection.classList.add("reference-section");
|
||||
referenceSection.classList.add("collapsed");
|
||||
|
||||
let numReferences = 0;
|
||||
|
||||
// If rawReferenceAsJson is a list, then count the length
|
||||
if (Array.isArray(rawReferenceAsJson)) {
|
||||
numReferences = rawReferenceAsJson.length;
|
||||
|
||||
rawReferenceAsJson.forEach((reference, index) => {
|
||||
this.generateReference(referenceSection, reference, index);
|
||||
});
|
||||
}
|
||||
references.appendChild(referenceExpandButton);
|
||||
|
||||
referenceExpandButton.addEventListener('click', function() {
|
||||
if (referenceSection.classList.contains("collapsed")) {
|
||||
referenceSection.classList.remove("collapsed");
|
||||
referenceSection.classList.add("expanded");
|
||||
} else {
|
||||
referenceSection.classList.add("collapsed");
|
||||
referenceSection.classList.remove("expanded");
|
||||
}
|
||||
});
|
||||
|
||||
let expandButtonText = numReferences == 1 ? "1 reference" : `${numReferences} references`;
|
||||
referenceExpandButton.innerHTML = expandButtonText;
|
||||
references.appendChild(referenceSection);
|
||||
} else {
|
||||
await this.renderIncrementalMessage(responseElement, responseText);
|
||||
}
|
||||
this.renderIncrementalMessage(responseElement, responseText);
|
||||
}
|
||||
} catch (err) {
|
||||
this.renderIncrementalMessage(responseElement, "Sorry, unable to get response from Khoj backend ❤️🩹. Contact developer for help at team@khoj.dev or <a href='https://discord.gg/BDgyabRM6e'>in Discord</a>")
|
||||
let errorMsg = "Sorry, unable to get response from Khoj backend ❤️🩹. Contact developer for help at team@khoj.dev or [in Discord](https://discord.gg/BDgyabRM6e)";
|
||||
responseElement.innerHTML = errorMsg
|
||||
}
|
||||
}
|
||||
|
||||
flashStatusInChatInput(message: string) {
|
||||
// Get chat input element and original placeholder
|
||||
let chatInput = <HTMLTextAreaElement>this.contentEl.getElementsByClassName("khoj-chat-input")[0];
|
||||
let originalPlaceholder = chatInput.placeholder;
|
||||
// Set placeholder to message
|
||||
chatInput.placeholder = message;
|
||||
// Reset placeholder after 2 seconds
|
||||
setTimeout(() => {
|
||||
chatInput.placeholder = originalPlaceholder;
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
async clearConversationHistory() {
|
||||
let chatBody = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
|
||||
|
||||
let response = await request({
|
||||
url: `${this.setting.khojUrl}/api/chat/history?client=web`,
|
||||
method: "DELETE",
|
||||
headers: { "Authorization": `Bearer ${this.setting.khojApiKey}` },
|
||||
})
|
||||
try {
|
||||
let result = JSON.parse(response);
|
||||
if (result.status !== "ok") {
|
||||
// Throw error if conversation history isn't cleared
|
||||
throw new Error("Failed to clear conversation history");
|
||||
} else {
|
||||
let getChatHistoryStatus = await this.getChatHistory(chatBody);
|
||||
// If conversation history is cleared successfully, clear chat logs from modal
|
||||
if (getChatHistoryStatus) chatBody.innerHTML = "";
|
||||
let statusMsg = getChatHistoryStatus ? result.message : "Failed to clear conversation history";
|
||||
this.flashStatusInChatInput(statusMsg);
|
||||
}
|
||||
} catch (err) {
|
||||
this.flashStatusInChatInput("Failed to clear conversation history");
|
||||
}
|
||||
}
|
||||
|
||||
sendMessageTimeout: NodeJS.Timeout | undefined;
|
||||
mediaRecorder: MediaRecorder | undefined;
|
||||
async speechToText(event: MouseEvent | TouchEvent) {
|
||||
event.preventDefault();
|
||||
const transcribeButton = <HTMLButtonElement>this.contentEl.getElementsByClassName("khoj-transcribe")[0];
|
||||
const chatInput = <HTMLTextAreaElement>this.contentEl.getElementsByClassName("khoj-chat-input")[0];
|
||||
const sendButton = <HTMLButtonElement>this.modalEl.getElementsByClassName("khoj-chat-send")[0]
|
||||
|
||||
const generateRequestBody = async (audioBlob: Blob, boundary_string: string) => {
|
||||
const boundary = `------${boundary_string}`;
|
||||
const chunks: ArrayBuffer[] = [];
|
||||
|
||||
chunks.push(new TextEncoder().encode(`${boundary}\r\n`));
|
||||
chunks.push(new TextEncoder().encode(`Content-Disposition: form-data; name="file"; filename="blob"\r\nContent-Type: "application/octet-stream"\r\n\r\n`));
|
||||
chunks.push(await audioBlob.arrayBuffer());
|
||||
chunks.push(new TextEncoder().encode('\r\n'));
|
||||
|
||||
await Promise.all(chunks);
|
||||
chunks.push(new TextEncoder().encode(`${boundary}--\r\n`));
|
||||
return await new Blob(chunks).arrayBuffer();
|
||||
};
|
||||
|
||||
const sendToServer = async (audioBlob: Blob) => {
|
||||
const boundary_string = `Boundary${Math.random().toString(36).slice(2)}`;
|
||||
const requestBody = await generateRequestBody(audioBlob, boundary_string);
|
||||
|
||||
const response = await requestUrl({
|
||||
url: `${this.setting.khojUrl}/api/transcribe?client=obsidian`,
|
||||
method: 'POST',
|
||||
headers: { "Authorization": `Bearer ${this.setting.khojApiKey}` },
|
||||
contentType: `multipart/form-data; boundary=----${boundary_string}`,
|
||||
body: requestBody,
|
||||
});
|
||||
|
||||
// Parse response from Khoj backend
|
||||
if (response.status === 200) {
|
||||
console.log(response);
|
||||
chatInput.value += response.json.text.trimStart();
|
||||
this.autoResize();
|
||||
} else if (response.status === 501) {
|
||||
throw new Error("⛔️ Configure speech-to-text model on server.");
|
||||
} else if (response.status === 422) {
|
||||
throw new Error("⛔️ Audio file to large to process.");
|
||||
} else {
|
||||
throw new Error("⛔️ Failed to transcribe audio.");
|
||||
}
|
||||
|
||||
// Don't auto-send empty messages
|
||||
if (chatInput.value.length === 0) return;
|
||||
|
||||
// Show stop auto-send button. It stops auto-send when clicked
|
||||
setIcon(sendButton, "stop-circle");
|
||||
let stopSendButtonImg = <SVGElement>sendButton.getElementsByClassName("lucide-stop-circle")[0]
|
||||
stopSendButtonImg.addEventListener('click', (_) => { this.cancelSendMessage() });
|
||||
|
||||
// Start the countdown timer UI
|
||||
stopSendButtonImg.getElementsByTagName("circle")[0].style.animation = "countdown 3s linear 1 forwards";
|
||||
|
||||
// Auto send message after 3 seconds
|
||||
this.sendMessageTimeout = setTimeout(() => {
|
||||
// Stop the countdown timer UI
|
||||
setIcon(sendButton, "arrow-up-circle")
|
||||
let sendImg = <SVGElement>sendButton.getElementsByClassName("lucide-arrow-up-circle")[0]
|
||||
sendImg.addEventListener('click', async (_) => { await this.chat() });
|
||||
|
||||
// Send message
|
||||
this.chat();
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const handleRecording = (stream: MediaStream) => {
|
||||
const audioChunks: Blob[] = [];
|
||||
const recordingConfig = { mimeType: 'audio/webm' };
|
||||
this.mediaRecorder = new MediaRecorder(stream, recordingConfig);
|
||||
|
||||
this.mediaRecorder.addEventListener("dataavailable", function(event) {
|
||||
if (event.data.size > 0) audioChunks.push(event.data);
|
||||
});
|
||||
|
||||
this.mediaRecorder.addEventListener("stop", async function() {
|
||||
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
|
||||
await sendToServer(audioBlob);
|
||||
});
|
||||
|
||||
this.mediaRecorder.start();
|
||||
setIcon(transcribeButton, "mic-off");
|
||||
};
|
||||
|
||||
// Toggle recording
|
||||
if (!this.mediaRecorder || this.mediaRecorder.state === 'inactive' || event.type === 'touchstart') {
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({ audio: true })
|
||||
?.then(handleRecording)
|
||||
.catch((e) => {
|
||||
this.flashStatusInChatInput("⛔️ Failed to access microphone");
|
||||
});
|
||||
} else if (this.mediaRecorder.state === 'recording' || event.type === 'touchend' || event.type === 'touchcancel') {
|
||||
this.mediaRecorder.stop();
|
||||
this.mediaRecorder.stream.getTracks().forEach(track => track.stop());
|
||||
this.mediaRecorder = undefined;
|
||||
setIcon(transcribeButton, "mic");
|
||||
}
|
||||
}
|
||||
|
||||
cancelSendMessage() {
|
||||
// Cancel the auto-send chat message timer if the stop-send-button is clicked
|
||||
clearTimeout(this.sendMessageTimeout);
|
||||
|
||||
// Revert to showing send-button and hide the stop-send-button
|
||||
let sendButton = <HTMLButtonElement>this.modalEl.getElementsByClassName("khoj-chat-send")[0];
|
||||
setIcon(sendButton, "arrow-up-circle");
|
||||
let sendImg = <SVGElement>sendButton.getElementsByClassName("lucide-arrow-up-circle")[0]
|
||||
sendImg.addEventListener('click', async (_) => { await this.chat() });
|
||||
};
|
||||
|
||||
incrementalChat(event: KeyboardEvent) {
|
||||
if (!event.shiftKey && event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
this.chat();
|
||||
}
|
||||
}
|
||||
|
||||
onChatInput() {
|
||||
const chatInput = <HTMLTextAreaElement>this.contentEl.getElementsByClassName("khoj-chat-input")[0];
|
||||
chatInput.value = chatInput.value.trimStart();
|
||||
|
||||
this.autoResize();
|
||||
}
|
||||
|
||||
autoResize() {
|
||||
const chatInput = <HTMLTextAreaElement>this.contentEl.getElementsByClassName("khoj-chat-input")[0];
|
||||
const scrollTop = chatInput.scrollTop;
|
||||
chatInput.style.height = '0';
|
||||
const scrollHeight = chatInput.scrollHeight + 8; // +8 accounts for padding
|
||||
chatInput.style.height = Math.min(scrollHeight, 200) + 'px';
|
||||
chatInput.scrollTop = scrollTop;
|
||||
this.modalEl.scrollTop = this.modalEl.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||