Merge branch 'master' of github.com:khoj-ai/khoj into features/new-sign-in-page

This commit is contained in:
sabaimran 2024-12-11 10:30:13 -08:00
commit 530b44cf56
166 changed files with 3869 additions and 4150 deletions

View file

@ -1,10 +1,11 @@
.git/ .*
.pytest_cache/ **/__pycache__/
.vscode/ *.egg-info/
.venv/ documentation/
docs/
tests/ tests/
build/ build/
dist/ dist/
scripts/ scripts/
*.egg-info/ src/interface/
src/telemetry/
!src/interface/web

View file

@ -38,13 +38,23 @@ env:
jobs: jobs:
build: build:
name: Publish Khoj Docker Images name: Publish Khoj Docker Images
runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
image: include:
- 'local' - image: 'local'
- 'cloud' platform: linux/amd64
runner: ubuntu-latest
- image: 'local'
platform: linux/arm64
runner: ubuntu-linux-arm64
- image: 'cloud'
platform: linux/amd64
runner: ubuntu-latest
- image: 'cloud'
platform: linux/arm64
runner: ubuntu-linux-arm64
runs-on: ${{ matrix.runner }}
steps: steps:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@v3 uses: actions/checkout@v3
@ -52,9 +62,6 @@ jobs:
# Get all history to correctly infer Khoj version using hatch # Get all history to correctly infer Khoj version using hatch
fetch-depth: 0 fetch-depth: 0
- name: 🧹 Delete huge unnecessary tools folder
run: rm -rf /opt/hostedtoolcache
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
@ -73,31 +80,68 @@ jobs:
run: rm -rf /opt/hostedtoolcache run: rm -rf /opt/hostedtoolcache
- name: 📦 Build and Push Docker Image - name: 📦 Build and Push Docker Image
uses: docker/build-push-action@v2 uses: docker/build-push-action@v4
if: (matrix.image == 'local' && github.event_name == 'workflow_dispatch') && github.event.inputs.khoj == 'true' || (matrix.image == 'local' && github.event_name == 'push') if: (matrix.image == 'local' && github.event_name == 'workflow_dispatch') && github.event.inputs.khoj == 'true' || (matrix.image == 'local' && github.event_name == 'push')
with: with:
context: . context: .
file: Dockerfile file: Dockerfile
platforms: linux/amd64, linux/arm64
push: true push: true
tags: | tags: |
ghcr.io/${{ github.repository }}:${{ env.DOCKER_IMAGE_TAG }} ghcr.io/${{ github.repository }}:${{ env.DOCKER_IMAGE_TAG }}-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
${{ github.ref_type == 'tag' && format('ghcr.io/{0}:latest', github.repository) || '' }}
build-args: | build-args: |
VERSION=${{ steps.hatch.outputs.version }} VERSION=${{ steps.hatch.outputs.version }}
PORT=42110 PORT=42110
cache-from: type=gha,scope=${{ matrix.image }}-${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=${{ matrix.image }}-${{ matrix.platform}}
labels: |
org.opencontainers.image.description=Khoj AI - Your second brain powered by LLMs and Neural Search
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
- name: 📦️⛅️ Build and Push Cloud Docker Image - name: 📦️⛅️ Build and Push Cloud Docker Image
uses: docker/build-push-action@v2 uses: docker/build-push-action@v4
if: (matrix.image == 'cloud' && github.event_name == 'workflow_dispatch') && github.event.inputs.khoj-cloud == 'true' || (matrix.image == 'cloud' && github.event_name == 'push') if: (matrix.image == 'cloud' && github.event_name == 'workflow_dispatch') && github.event.inputs.khoj-cloud == 'true' || (matrix.image == 'cloud' && github.event_name == 'push')
with: with:
context: . context: .
file: prod.Dockerfile file: prod.Dockerfile
platforms: linux/amd64, linux/arm64
push: true push: true
tags: | tags: |
ghcr.io/${{ github.repository }}-cloud:${{ env.DOCKER_IMAGE_TAG }} ghcr.io/${{ github.repository }}-cloud:${{ env.DOCKER_IMAGE_TAG }}-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
${{ github.ref_type == 'tag' && format('ghcr.io/{0}-cloud:latest', github.repository) || '' }}
build-args: | build-args: |
VERSION=${{ steps.hatch.outputs.version }} VERSION=${{ steps.hatch.outputs.version }}
PORT=42110 PORT=42110
cache-from: type=gha,scope=${{ matrix.image }}-${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=${{ matrix.image }}-${{ matrix.platform}}
labels: |
org.opencontainers.image.description=Khoj AI Cloud - Your second brain powered by LLMs and Neural Search
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
manifest:
needs: build
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.PAT }}
- name: Create and Push Local Manifest
if: github.event.inputs.khoj == 'true' || github.event_name == 'push'
run: |
docker buildx imagetools create \
-t ghcr.io/${{ github.repository }}:${{ env.DOCKER_IMAGE_TAG }} \
ghcr.io/${{ github.repository }}:${{ env.DOCKER_IMAGE_TAG }}-amd64 \
ghcr.io/${{ github.repository }}:${{ env.DOCKER_IMAGE_TAG }}-arm64
- name: Create and Push Cloud Manifest
if: github.event.inputs.khoj-cloud == 'true' || github.event_name == 'push'
run: |
docker buildx imagetools create \
-t ghcr.io/${{ github.repository }}-cloud:${{ env.DOCKER_IMAGE_TAG }} \
ghcr.io/${{ github.repository }}-cloud:${{ env.DOCKER_IMAGE_TAG }}-amd64 \
ghcr.io/${{ github.repository }}-cloud:${{ env.DOCKER_IMAGE_TAG }}-arm64

View file

@ -25,6 +25,8 @@ on:
options: options:
- frames - frames
- simpleqa - simpleqa
- gpqa
- math500
sample_size: sample_size:
description: 'Number of samples to evaluate' description: 'Number of samples to evaluate'
required: false required: false
@ -74,12 +76,12 @@ jobs:
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
run: | run: |
# install postgres and other dependencies # install postgres and other dependencies
apt update && apt install -y git python3-pip libegl1 sqlite3 libsqlite3-dev libsqlite3-0 ffmpeg libsm6 libxext6 sudo apt update && sudo apt install -y git python3-pip libegl1 sqlite3 libsqlite3-dev libsqlite3-0 ffmpeg libsm6 libxext6
apt install -y postgresql postgresql-client && apt install -y postgresql-server-dev-14 sudo apt install -y postgresql postgresql-client && sudo apt install -y postgresql-server-dev-14
# upgrade pip # upgrade pip
python -m ensurepip --upgrade && python -m pip install --upgrade pip python -m ensurepip --upgrade && python -m pip install --upgrade pip
# install terrarium for code sandbox # install terrarium for code sandbox
git clone https://github.com/cohere-ai/cohere-terrarium.git && cd cohere-terrarium && npm install && mkdir pyodide_cache git clone https://github.com/khoj-ai/terrarium.git && cd terrarium && npm install --legacy-peer-deps && mkdir pyodide_cache
- name: ⬇️ Install Application - name: ⬇️ Install Application
run: | run: |
@ -89,14 +91,15 @@ jobs:
- name: 📝 Run Eval - name: 📝 Run Eval
env: env:
KHOJ_MODE: ${{ matrix.khoj_mode }} KHOJ_MODE: ${{ matrix.khoj_mode }}
SAMPLE_SIZE: ${{ inputs.sample_size }} SAMPLE_SIZE: ${{ github.event_name == 'workflow_dispatch' && inputs.sample_size || 200 }}
BATCH_SIZE: "20" BATCH_SIZE: "20"
RANDOMIZE: "True" RANDOMIZE: "True"
KHOJ_URL: "http://localhost:42110" KHOJ_URL: "http://localhost:42110"
KHOJ_LLM_SEED: "42" KHOJ_LLM_SEED: "42"
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
SERPER_DEV_API_KEY: ${{ secrets.SERPER_DEV_API_KEY }} SERPER_DEV_API_KEY: ${{ matrix.dataset != 'math500' && secrets.SERPER_DEV_API_KEY }}
OLOSTEP_API_KEY: ${{ secrets.OLOSTEP_API_KEY }} OLOSTEP_API_KEY: ${{ matrix.dataset != 'math500' && secrets.OLOSTEP_API_KEY }}
HF_TOKEN: ${{ secrets.HF_TOKEN }}
KHOJ_ADMIN_EMAIL: khoj KHOJ_ADMIN_EMAIL: khoj
KHOJ_ADMIN_PASSWORD: khoj KHOJ_ADMIN_PASSWORD: khoj
POSTGRES_HOST: localhost POSTGRES_HOST: localhost
@ -110,7 +113,7 @@ jobs:
khoj --anonymous-mode --non-interactive & khoj --anonymous-mode --non-interactive &
# Start code sandbox # Start code sandbox
npm run dev --prefix cohere-terrarium & npm run dev --prefix terrarium &
# Wait for server to be ready # Wait for server to be ready
timeout=120 timeout=120

View file

@ -55,15 +55,13 @@ jobs:
with: with:
python-version: ${{ matrix.python_version }} python-version: ${{ matrix.python_version }}
- name: Install Git
run: |
apt update && apt install -y git
- name: ⏬️ Install Dependencies - name: ⏬️ Install Dependencies
env: env:
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
run: | run: |
apt update && apt install -y libegl1 sqlite3 libsqlite3-dev libsqlite3-0 ffmpeg libsm6 libxext6 apt update && apt install -y git libegl1 sqlite3 libsqlite3-dev libsqlite3-0 ffmpeg libsm6 libxext6
# required by llama-cpp-python prebuilt wheels
apt install -y musl-dev && ln -s /usr/lib/x86_64-linux-musl/libc.so /lib/libc.musl-x86_64.so.1
- name: ⬇️ Install Postgres - name: ⬇️ Install Postgres
env: env:
@ -78,6 +76,9 @@ jobs:
python -m pip install --upgrade pip python -m pip install --upgrade pip
- name: ⬇️ Install Application - name: ⬇️ Install Application
env:
PIP_EXTRA_INDEX_URL: "https://download.pytorch.org/whl/cpu https://abetlen.github.io/llama-cpp-python/whl/cpu"
CUDA_VISIBLE_DEVICES: ""
run: sed -i 's/dynamic = \["version"\]/version = "0.0.0"/' pyproject.toml && pip install --upgrade .[dev] run: sed -i 's/dynamic = \["version"\]/version = "0.0.0"/' pyproject.toml && pip install --upgrade .[dev]
- name: 🧪 Test Application - name: 🧪 Test Application

10
.gitignore vendored
View file

@ -42,3 +42,13 @@ src/interface/obsidian/main.js
# obsidian # obsidian
data.json data.json
# Android
src/interface/android/.gradle
src/interface/android/app/build
src/interface/android/build
src/interface/android/*.aab
src/interface/android/*.apk
src/interface/android/*.apk.idsig
src/interface/android/*.keystore
src/interface/android/local.properties

View file

@ -26,7 +26,7 @@ repos:
rev: v1.0.0 rev: v1.0.0
hooks: hooks:
- id: mypy - id: mypy
stages: [push, manual] stages: [pre-push, manual]
pass_filenames: false pass_filenames: false
args: args:
- --config-file=pyproject.toml - --config-file=pyproject.toml

View file

@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
FROM ubuntu:jammy FROM ubuntu:jammy AS base
LABEL homepage="https://khoj.dev" LABEL homepage="https://khoj.dev"
LABEL repository="https://github.com/khoj-ai/khoj" LABEL repository="https://github.com/khoj-ai/khoj"
LABEL org.opencontainers.image.source="https://github.com/khoj-ai/khoj" LABEL org.opencontainers.image.source="https://github.com/khoj-ai/khoj"
@ -13,36 +13,51 @@ RUN apt update -y && apt -y install \
# Required by RapidOCR # Required by RapidOCR
libgl1 \ libgl1 \
libglx-mesa0 \ libglx-mesa0 \
libglib2.0-0 && \ libglib2.0-0 \
# Required by Next.js Web app # Required by llama-cpp-python pre-built wheels. See #1628
curl -sL https://deb.nodesource.com/setup_20.x | bash - && \ musl-dev && \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ ln -s /usr/lib/x86_64-linux-musl/libc.so /lib/libc.musl-x86_64.so.1 && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ # Clean up
apt update -y && apt -y --no-install-recommends install nodejs yarn && \
apt clean && rm -rf /var/lib/apt/lists/* apt clean && rm -rf /var/lib/apt/lists/*
# Install Application # Build Server
FROM base AS server-deps
WORKDIR /app WORKDIR /app
COPY pyproject.toml . COPY pyproject.toml .
COPY README.md . COPY README.md .
ARG VERSION=0.0.0 ARG VERSION=0.0.0
# use the pre-built llama-cpp-python, torch cpu wheel
ENV PIP_EXTRA_INDEX_URL="https://download.pytorch.org/whl/cpu https://abetlen.github.io/llama-cpp-python/whl/cpu"
# avoid downloading unused cuda specific python packages
ENV CUDA_VISIBLE_DEVICES=""
RUN sed -i "s/dynamic = \\[\"version\"\\]/version = \"$VERSION\"/" pyproject.toml && \ RUN sed -i "s/dynamic = \\[\"version\"\\]/version = \"$VERSION\"/" pyproject.toml && \
pip install --no-cache-dir . pip install --no-cache-dir .
# Copy Source Code # Build Web App
COPY . . FROM node:20-alpine AS web-app
# Set build optimization env vars
# Set the PYTHONPATH environment variable in order for it to find the Django app. ENV NODE_ENV=production
ENV PYTHONPATH=/app/src:$PYTHONPATH ENV NEXT_TELEMETRY_DISABLED=1
# Go to the directory src/interface/web and export the built Next.js assets
WORKDIR /app/src/interface/web WORKDIR /app/src/interface/web
RUN bash -c "yarn install --frozen-lockfile && yarn ciexport && yarn cache clean" # Install dependencies first (cache layer)
COPY src/interface/web/package.json src/interface/web/yarn.lock ./
RUN yarn install --frozen-lockfile
# Copy source and build
COPY src/interface/web/. ./
RUN yarn build
# Merge the Server and Web App into a Single Image
FROM base
ENV PYTHONPATH=/app/src
WORKDIR /app WORKDIR /app
COPY --from=server-deps /usr/local/lib/python3.10/dist-packages /usr/local/lib/python3.10/dist-packages
COPY --from=web-app /app/src/interface/web/out ./src/khoj/interface/built
COPY . .
RUN cd src && python3 khoj/manage.py collectstatic --noinput
# Run the Application # Run the Application
# There are more arguments required for the application to run, # There are more arguments required for the application to run,
# but these should be passed in through the docker-compose.yml file. # but those should be passed in through the docker-compose.yml file.
ARG PORT ARG PORT
EXPOSE ${PORT} EXPOSE ${PORT}
ENTRYPOINT ["python3", "src/khoj/main.py"] ENTRYPOINT ["python3", "src/khoj/main.py"]

View file

@ -29,10 +29,17 @@
</div> </div>
<div align="left"> ***
### 🎁 New
* Start any message with `/research` to try out the experimental research mode with Khoj.
* Anyone can now [create custom agents](https://blog.khoj.dev/posts/create-agents-on-khoj/) with tunable personality, tools and knowledge bases.
* [Read](https://blog.khoj.dev/posts/evaluate-khoj-quality/) about Khoj's excellent performance on modern retrieval and reasoning benchmarks.
*** ***
## Overview
[Khoj](https://khoj.dev) is a personal AI app to extend your capabilities. It smoothly scales up from an on-device personal AI to a cloud-scale enterprise AI. [Khoj](https://khoj.dev) is a personal AI app to extend your capabilities. It smoothly scales up from an on-device personal AI to a cloud-scale enterprise AI.
- Chat with any local or online LLM (e.g llama3, qwen, gemma, mistral, gpt, claude, gemini). - Chat with any local or online LLM (e.g llama3, qwen, gemma, mistral, gpt, claude, gemini).
@ -47,8 +54,6 @@
*** ***
</div>
## See it in action ## See it in action
![demo_chat](https://github.com/khoj-ai/khoj/blob/master/documentation/assets/img/quadratic_equation_khoj_web.gif?raw=true) ![demo_chat](https://github.com/khoj-ai/khoj/blob/master/documentation/assets/img/quadratic_equation_khoj_web.gif?raw=true)

View file

@ -22,9 +22,9 @@ services:
depends_on: depends_on:
database: database:
condition: service_healthy condition: service_healthy
# Use the following line to use the latest version of khoj. Otherwise, it will build from source. # Use the following line to use the latest version of khoj. Otherwise, it will build from source. Set this to ghcr.io/khoj-ai/khoj-cloud if you want to use the prod image.
image: ghcr.io/khoj-ai/khoj:latest image: ghcr.io/khoj-ai/khoj:latest
# Uncomment the following line to build from source. This will take a few minutes. Comment the next two lines out if you want to use the offiicial image. # Uncomment the following line to build from source. This will take a few minutes. Comment the next two lines out if you want to use the official image.
# build: # build:
# context: . # context: .
ports: ports:
@ -49,6 +49,8 @@ services:
- KHOJ_DEBUG=False - KHOJ_DEBUG=False
- KHOJ_ADMIN_EMAIL=username@example.com - KHOJ_ADMIN_EMAIL=username@example.com
- KHOJ_ADMIN_PASSWORD=password - KHOJ_ADMIN_PASSWORD=password
# Default URL of Terrarium, the Python sandbox used by Khoj to run code. Its container is specified above
- KHOJ_TERRARIUM_URL=http://host.docker.internal:8080
# Uncomment line below to use with Ollama running on your local machine at localhost:11434. # Uncomment line below to use with Ollama running on your local machine at localhost:11434.
# Change URL to use with other OpenAI API compatible providers like VLLM, LMStudio etc. # Change URL to use with other OpenAI API compatible providers like VLLM, LMStudio etc.
# - OPENAI_API_BASE=http://host.docker.internal:11434/v1/ # - OPENAI_API_BASE=http://host.docker.internal:11434/v1/
@ -64,7 +66,7 @@ services:
# Ensure you set your provider specific API keys. # Ensure you set your provider specific API keys.
# --- # ---
# Free, Slower API. Does both web search and webpage read. Get API key from https://jina.ai/ # Free, Slower API. Does both web search and webpage read. Get API key from https://jina.ai/
# - JINA_API_KEY=you_jina_api_key # - JINA_API_KEY=your_jina_api_key
# Paid, Fast API. Only does web search. Get API key from https://serper.dev/ # Paid, Fast API. Only does web search. Get API key from https://serper.dev/
# - SERPER_DEV_API_KEY=your_serper_dev_api_key # - SERPER_DEV_API_KEY=your_serper_dev_api_key
# Paid, Fast, Open API. Only does webpage read. Get API key from https://firecrawl.dev/ # Paid, Fast, Open API. Only does webpage read. Get API key from https://firecrawl.dev/
@ -83,6 +85,7 @@ services:
# Telemetry helps us prioritize feature development and understand how people are using Khoj # Telemetry helps us prioritize feature development and understand how people are using Khoj
# Read more at https://docs.khoj.dev/miscellaneous/telemetry # Read more at https://docs.khoj.dev/miscellaneous/telemetry
# - KHOJ_TELEMETRY_DISABLE=True # - KHOJ_TELEMETRY_DISABLE=True
# Comment out this line when you're using the official ghcr.io/khoj-ai/khoj-cloud prod image.
command: --host="0.0.0.0" --port=42110 -vv --anonymous-mode --non-interactive command: --host="0.0.0.0" --port=42110 -vv --anonymous-mode --non-interactive

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 333 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -38,14 +38,13 @@ To add a server chat setting:
- The `Advanced` field doesn't need to be set when self-hosting. When unset, the `Default` chat model is used for all users and the intermediate steps. - The `Advanced` field doesn't need to be set when self-hosting. When unset, the `Default` chat model is used for all users and the intermediate steps.
### OpenAI Processor Conversation Configs ### AI Model API
These settings configure chat model providers to be accessed over API. These settings configure APIs to interact with AI models.
The name of this setting is kind of a misnomer, we know, it'll hopefully be changed at some point. For each AI Model API you [add](http://localhost:42110/server/admin/database/aimodelapi/add):
For each chat model provider you [add](http://localhost:42110/server/admin/database/openaiprocessorconversationconfig/add):
- `Api key`: Set to your [OpenAI](https://platform.openai.com/api-keys), [Anthropic](https://console.anthropic.com/account/keys) or [Gemini](https://aistudio.google.com/app/apikey) API keys. - `Api key`: Set to your [OpenAI](https://platform.openai.com/api-keys), [Anthropic](https://console.anthropic.com/account/keys) or [Gemini](https://aistudio.google.com/app/apikey) API keys.
- `Name`: Give the configuration any friendly name like `OpenAI`, `Gemini`, `Anthropic`. - `Name`: Give the configuration any friendly name like `OpenAI`, `Gemini`, `Anthropic`.
- `Api base url`: Set the API base URL. This is only relevant to set if you're using another OpenAI-compatible proxy server like [Ollama](/advanced/ollama) or [LMStudio](/advanced/lmstudio). - `Api base url`: Set the API base URL. This is only relevant to set if you're using another OpenAI-compatible proxy server like [Ollama](/advanced/ollama) or [LMStudio](/advanced/lmstudio).
![example configuration for openai processor](/img/example_openai_processor_config.png) ![example configuration for ai model api](/img/example_openai_processor_config.png)
### Search Model Configs ### Search Model Configs
Search models are used to generate vector embeddings of your documents for natural language search and chat. You can choose any [embeddings models on HuggingFace](https://huggingface.co/models?pipeline_tag=sentence-similarity) to try, use for your to create vector embeddings of your documents for natural language search and chat. Search models are used to generate vector embeddings of your documents for natural language search and chat. You can choose any [embeddings models on HuggingFace](https://huggingface.co/models?pipeline_tag=sentence-similarity) to try, use for your to create vector embeddings of your documents for natural language search and chat.

View file

@ -21,7 +21,7 @@ Using LiteLLM with Khoj makes it possible to turn any LLM behind an API into you
export MISTRAL_API_KEY=<MISTRAL_API_KEY> export MISTRAL_API_KEY=<MISTRAL_API_KEY>
litellm --model mistral/mistral-tiny --drop_params litellm --model mistral/mistral-tiny --drop_params
``` ```
3. Create a new [OpenAI Processor Conversation Config](http://localhost:42110/server/admin/database/openaiprocessorconversationconfig/add) on your Khoj admin panel 3. Create a new [API Model API](http://localhost:42110/server/admin/database/aimodelapi/add) on your Khoj admin panel
- Name: `proxy-name` - Name: `proxy-name`
- Api Key: `any string` - Api Key: `any string`
- Api Base Url: **URL of your Openai Proxy API** - Api Base Url: **URL of your Openai Proxy API**

View file

@ -60,7 +60,7 @@ Restart your Khoj server after first run or update to the settings below to ensu
```bash ```bash
ollama pull llama3.1 ollama pull llama3.1
``` ```
3. Create a new [OpenAI Processor Conversation Config](http://localhost:42110/server/admin/database/openaiprocessorconversationconfig/add) on your Khoj admin panel 3. Create a new [AI Model API](http://localhost:42110/server/admin/database/aimodelapi/add) on your Khoj admin panel
- Name: `ollama` - Name: `ollama`
- Api Key: `any string` - Api Key: `any string`
- Api Base Url: `http://localhost:11434/v1/` (default for Ollama) - Api Base Url: `http://localhost:11434/v1/` (default for Ollama)

View file

@ -11,7 +11,7 @@ This is only helpful for self-hosted users. If you're using [Khoj Cloud](https:/
Khoj natively supports local LLMs [available on HuggingFace in GGUF format](https://huggingface.co/models?library=gguf). Using an OpenAI API proxy with Khoj maybe useful for ease of setup, trying new models or using commercial LLMs via API. Khoj natively supports local LLMs [available on HuggingFace in GGUF format](https://huggingface.co/models?library=gguf). Using an OpenAI API proxy with Khoj maybe useful for ease of setup, trying new models or using commercial LLMs via API.
::: :::
Khoj can use any OpenAI API compatible server including [Ollama](/advanced/ollama), [LMStudio](/advanced/lmstudio) and [LiteLLM](/advanced/litellm). Khoj can use any OpenAI API compatible server including local providers like [Ollama](/advanced/ollama), [LMStudio](/advanced/lmstudio) and [LiteLLM](/advanced/litellm) and commercial providers like [HuggingFace](https://huggingface.co/docs/api-inference/tasks/chat-completion#using-the-api), [OpenRouter](https://openrouter.ai/docs/quick-start) etc.
Configuring this allows you to use non-standard, open or commercial, local or hosted LLM models for Khoj Configuring this allows you to use non-standard, open or commercial, local or hosted LLM models for Khoj
Combine them with Khoj can turn your favorite LLM into an AI agent. Allowing you to chat with your docs, find answers from the internet, build custom agents and run automations. Combine them with Khoj can turn your favorite LLM into an AI agent. Allowing you to chat with your docs, find answers from the internet, build custom agents and run automations.
@ -20,8 +20,8 @@ For specific integrations, see our [Ollama](/advanced/ollama), [LMStudio](/advan
## General Setup ## General Setup
1. Start your preferred OpenAI API compatible app 1. Start your preferred OpenAI API compatible app locally or get API keys from commercial AI model providers.
2. Create a new [OpenAI Processor Conversation Config](http://localhost:42110/server/admin/database/openaiprocessorconversationconfig/add) on your Khoj admin panel 3. Create a new [API Model API](http://localhost:42110/server/admin/database/aimodelapi/add) on your Khoj admin panel
- Name: `any name` - Name: `any name`
- Api Key: `any string` - Api Key: `any string`
- Api Base Url: **URL of your Openai Proxy API** - Api Base Url: **URL of your Openai Proxy API**

View file

@ -4,22 +4,13 @@ sidebar_position: 1
# Desktop # Desktop
> Query your Second Brain from your machine > Upload your knowledge base to Khoj and chat with your whole corpus
Use the Desktop app to chat and search with Khoj. ## Companion App
You can also share your files, folders with Khoj using the app.
Share your files, folders with Khoj using the app.
Khoj will keep these files in sync to provide contextual responses when you search or chat. Khoj will keep these files in sync to provide contextual responses when you search or chat.
## Features
- **Chat**
- **Faster answers**: Find answers quickly, from your private notes or the public internet
- **Assisted creativity**: Smoothly weave across retrieving answers and generating content
- **Iterative discovery**: Iteratively explore and re-discover your notes
- **Quick access**: Use [Khoj Mini](/features/khoj_mini) on the desktop to quickly pull up a mini chat module for quicker answers
- **Search**
- **Natural**: Advanced natural language understanding using Transformer based ML Models
- **Incremental**: Incremental search for a fast, search-as-you-type experience
## Setup ## Setup
:::info[Self Hosting] :::info[Self Hosting]
If you are self-hosting the Khoj server, update the *Settings* page on the Khoj Desktop app to: If you are self-hosting the Khoj server, update the *Settings* page on the Khoj Desktop app to:
@ -34,7 +25,20 @@ If you are self-hosting the Khoj server, update the *Settings* page on the Khoj
4. [Optional] Add any files, folders you'd like Khoj to be aware of on the *Settings* page and Click *Save*. 4. [Optional] Add any files, folders you'd like Khoj to be aware of on the *Settings* page and Click *Save*.
These files and folders will be automatically kept in sync for you These files and folders will be automatically kept in sync for you
## Interface # Main App
| Chat | Search |
|:----:|:------:| You can also install the Khoj application on your desktop as a progressive web app.
| ![](/img/khoj_chat_on_desktop.png) | ![](/img/khoj_search_on_desktop.png) |
1. Open the [Khoj Web App](https://app.khoj.dev) in Chrome.
2. Click on the install button in the address bar to install the app. You must be logged into your Chrome browser for this to work.
![progressive web app install icon](/img/pwa_install_desktop.png)
Alternatively, you can also install using this route:
1. Open the three-dot menu in the top right corner of the browser.
2. Go to 'Cast, Save, and Share' option.
3. Click on the "Open in Khoj" option.
![progressive web app install route](/img/chrome_pwa_alt.png)

View file

@ -114,7 +114,7 @@ This feature finds entries similar to the one you are currently on.
2. Hit `C-c s f` (or `M-x khoj RET f`) to find similar entries 2. Hit `C-c s f` (or `M-x khoj RET f`) to find similar entries
### Advanced Usage ### Advanced Usage
- Add [query filters](https://github.com/khoj-ai/khoj/#query-filters) during search to narrow down results further - Add [query filters](/miscellaneous/advanced#query-filters) during search to narrow down results further
e.g. `What is the meaning of life? -"god" +"none" dt>"last week"` e.g. `What is the meaning of life? -"god" +"none" dt>"last week"`
- Use `C-c C-o 2` to open the current result at cursor in its source org file - Use `C-c C-o 2` to open the current result at cursor in its source org file

View file

@ -302,11 +302,11 @@ Setup which chat model you'd want to use. Khoj supports local and online chat mo
Using Ollama? See the [Ollama Integration](/advanced/ollama) section for more custom setup instructions. Using Ollama? See the [Ollama Integration](/advanced/ollama) section for more custom setup instructions.
::: :::
1. Create a new [OpenAI processor conversation config](http://localhost:42110/server/admin/database/openaiprocessorconversationconfig/add) in the server admin settings. This is kind of a misnomer, we know. 1. Create a new [AI Model Api](http://localhost:42110/server/admin/database/aimodelapi/add) in the server admin settings.
- Add your [OpenAI API key](https://platform.openai.com/api-keys) - Add your [OpenAI API key](https://platform.openai.com/api-keys)
- Give the configuration a friendly name like `OpenAI` - Give the configuration a friendly name like `OpenAI`
- (Optional) Set the API base URL. It is only relevant if you're using another OpenAI-compatible proxy server like [Ollama](/advanced/ollama) or [LMStudio](/advanced/lmstudio).<br /> - (Optional) Set the API base URL. It is only relevant if you're using another OpenAI-compatible proxy server like [Ollama](/advanced/ollama) or [LMStudio](/advanced/lmstudio).<br />
![example configuration for openai processor](/img/example_openai_processor_config.png) ![example configuration for ai model api](/img/example_openai_processor_config.png)
2. Create a new [chat model options](http://localhost:42110/server/admin/database/chatmodeloptions/add) 2. Create a new [chat model options](http://localhost:42110/server/admin/database/chatmodeloptions/add)
- Set the `chat-model` field to an [OpenAI chat model](https://platform.openai.com/docs/models). Example: `gpt-4o`. - Set the `chat-model` field to an [OpenAI chat model](https://platform.openai.com/docs/models). Example: `gpt-4o`.
- Make sure to set the `model-type` field to `OpenAI`. - Make sure to set the `model-type` field to `OpenAI`.
@ -315,22 +315,22 @@ Using Ollama? See the [Ollama Integration](/advanced/ollama) section for more cu
![example configuration for chat model options](/img/example_chatmodel_option.png) ![example configuration for chat model options](/img/example_chatmodel_option.png)
</TabItem> </TabItem>
<TabItem value="anthropic" label="Anthropic"> <TabItem value="anthropic" label="Anthropic">
1. Create a new [OpenAI processor conversation config](http://localhost:42110/server/admin/database/openaiprocessorconversationconfig/add) in the server admin settings. This is kind of a misnomer, we know. 1. Create a new [AI Model API](http://localhost:42110/server/admin/database/aimodelapi/add) in the server admin settings.
- Add your [Anthropic API key](https://console.anthropic.com/account/keys) - Add your [Anthropic API key](https://console.anthropic.com/account/keys)
- Give the configuration a friendly name like `Anthropic`. Do not configure the API base url. - Give the configuration a friendly name like `Anthropic`. Do not configure the API base url.
2. Create a new [chat model options](http://localhost:42110/server/admin/database/chatmodeloptions/add) 2. Create a new [chat model options](http://localhost:42110/server/admin/database/chatmodeloptions/add)
- Set the `chat-model` field to an [Anthropic chat model](https://docs.anthropic.com/en/docs/about-claude/models#model-names). Example: `claude-3-5-sonnet-20240620`. - Set the `chat-model` field to an [Anthropic chat model](https://docs.anthropic.com/en/docs/about-claude/models#model-names). Example: `claude-3-5-sonnet-20240620`.
- Set the `model-type` field to `Anthropic`. - Set the `model-type` field to `Anthropic`.
- Set the `Openai config` field to the OpenAI processor conversation config for Anthropic you created in step 1. - Set the `ai model api` field to the Anthropic AI Model API you created in step 1.
</TabItem> </TabItem>
<TabItem value="gemini" label="Gemini"> <TabItem value="gemini" label="Gemini">
1. Create a new [OpenAI processor conversation config](http://localhost:42110/server/admin/database/openaiprocessorconversationconfig/add) in the server admin settings. This is kind of a misnomer, we know. 1. Create a new [AI Model API](http://localhost:42110/server/admin/database/aimodelapi/add) in the server admin settings.
- Add your [Gemini API key](https://aistudio.google.com/app/apikey) - Add your [Gemini API key](https://aistudio.google.com/app/apikey)
- Give the configuration a friendly name like `Gemini`. Do not configure the API base url. - Give the configuration a friendly name like `Gemini`. Do not configure the API base url.
2. Create a new [chat model options](http://localhost:42110/server/admin/database/chatmodeloptions/add) 2. Create a new [chat model options](http://localhost:42110/server/admin/database/chatmodeloptions/add)
- Set the `chat-model` field to a [Google Gemini chat model](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#gemini-models). Example: `gemini-1.5-flash`. - Set the `chat-model` field to a [Google Gemini chat model](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#gemini-models). Example: `gemini-1.5-flash`.
- Set the `model-type` field to `Gemini`. - Set the `model-type` field to `Gemini`.
- Set the `Openai config` field to the OpenAI processor conversation config for Gemini you created in step 1. - Set the `ai model api` field to the Gemini AI Model API you created in step 1.
</TabItem> </TabItem>
<TabItem value="offline" label="Offline"> <TabItem value="offline" label="Offline">

View file

@ -37,6 +37,9 @@ const config = {
locales: ['en'], locales: ['en'],
}, },
// Add a widget for Chatwoot for live chat if users need help
clientModules: [require.resolve('./src/components/ChatwootWidget.js')],
presets: [ presets: [
[ [
'classic', 'classic',
@ -69,7 +72,6 @@ const config = {
}), }),
], ],
], ],
themeConfig: themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */ /** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({ ({

View file

@ -0,0 +1,19 @@
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
// Only execute on client-side
if (ExecutionEnvironment.canUseDOM) {
(function (d, t) {
var BASE_URL = "https://app.chatwoot.com";
var g = d.createElement(t), s = d.getElementsByTagName(t)[0];
g.src = BASE_URL + "/packs/js/sdk.js";
g.defer = true;
g.async = true;
s.parentNode.insertBefore(g, s);
g.onload = function () {
window.chatwootSDK.run({
websiteToken: 'cFxvnLSjfE2UF4UUiPCA5NsF',
baseUrl: BASE_URL
})
}
})(document, 'script');
}

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
{ {
"id": "khoj", "id": "khoj",
"name": "Khoj", "name": "Khoj",
"version": "1.30.1", "version": "1.31.0",
"minAppVersion": "0.15.0", "minAppVersion": "0.15.0",
"description": "Your Second Brain", "description": "Your Second Brain",
"author": "Khoj Inc.", "author": "Khoj Inc.",

View file

@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
FROM ubuntu:jammy FROM ubuntu:jammy AS base
LABEL homepage="https://khoj.dev" LABEL homepage="https://khoj.dev"
LABEL repository="https://github.com/khoj-ai/khoj" LABEL repository="https://github.com/khoj-ai/khoj"
LABEL org.opencontainers.image.source="https://github.com/khoj-ai/khoj" LABEL org.opencontainers.image.source="https://github.com/khoj-ai/khoj"
@ -13,36 +13,52 @@ RUN apt update -y && apt -y install \
libsm6 \ libsm6 \
libxext6 \ libxext6 \
swig \ swig \
curl && \ curl \
# Required by Next.js Web app # Required by llama-cpp-python pre-built wheels. See #1628
curl -sL https://deb.nodesource.com/setup_20.x | bash - && \ musl-dev && \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ ln -s /usr/lib/x86_64-linux-musl/libc.so /lib/libc.musl-x86_64.so.1 && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ # Clean up
apt update -y && apt -y --no-install-recommends install nodejs yarn && \
apt clean && rm -rf /var/lib/apt/lists/* apt clean && rm -rf /var/lib/apt/lists/*
# Install Application # Build Server
FROM base AS server-deps
WORKDIR /app WORKDIR /app
COPY pyproject.toml . COPY pyproject.toml .
COPY README.md . COPY README.md .
ARG VERSION=0.0.0 ARG VERSION=0.0.0
# use the pre-built llama-cpp-python, torch cpu wheel
ENV PIP_EXTRA_INDEX_URL="https://download.pytorch.org/whl/cpu https://abetlen.github.io/llama-cpp-python/whl/cpu"
# avoid downloading unused cuda specific python packages
ENV CUDA_VISIBLE_DEVICES=""
RUN sed -i "s/dynamic = \\[\"version\"\\]/version = \"$VERSION\"/" pyproject.toml && \ RUN sed -i "s/dynamic = \\[\"version\"\\]/version = \"$VERSION\"/" pyproject.toml && \
pip install --no-cache-dir -e .[prod] pip install --no-cache-dir -e .[prod]
# Copy Source Code # Build Web App
COPY . . FROM node:20-alpine AS web-app
# Set build optimization env vars
# Set the PYTHONPATH environment variable in order for it to find the Django app. ENV NODE_ENV=production
ENV PYTHONPATH=/app/src:$PYTHONPATH ENV NEXT_TELEMETRY_DISABLED=1
# Go to the directory src/interface/web and export the built Next.js assets
WORKDIR /app/src/interface/web WORKDIR /app/src/interface/web
RUN bash -c "yarn install --frozen-lockfile --verbose && yarn ciexport && yarn cache clean" # Install dependencies first (cache layer)
COPY src/interface/web/package.json src/interface/web/yarn.lock ./
RUN yarn install --frozen-lockfile
# Copy source and build
COPY src/interface/web/. ./
RUN yarn build
# Merge the Server and Web App into a Single Image
FROM base
ENV PYTHONPATH=/app/src:$PYTHONPATH
WORKDIR /app WORKDIR /app
COPY --from=server-deps /usr/local/lib/python3.10/dist-packages /usr/local/lib/python3.10/dist-packages
COPY --from=server-deps /usr/local/bin /usr/local/bin
COPY --from=web-app /app/src/interface/web/out ./src/khoj/interface/built
COPY . .
RUN cd src && python3 khoj/manage.py collectstatic --noinput
# Run the Application # Run the Application
# There are more arguments required for the application to run, # There are more arguments required for the application to run,
# but these should be passed in through the docker-compose.yml file. # but those should be passed in through the docker-compose.yml file.
ARG PORT ARG PORT
EXPOSE ${PORT} EXPOSE ${PORT}
ENTRYPOINT ["gunicorn", "-c", "gunicorn-config.py", "src.khoj.main:app"] ENTRYPOINT ["gunicorn", "-c", "gunicorn-config.py", "src.khoj.main:app"]

View file

@ -63,7 +63,8 @@ dependencies = [
"tenacity == 8.3.0", "tenacity == 8.3.0",
"anyio == 3.7.1", "anyio == 3.7.1",
"pymupdf == 1.24.11", "pymupdf == 1.24.11",
"django == 5.0.9", "django == 5.0.10",
"django-unfold == 0.42.0",
"authlib == 1.2.1", "authlib == 1.2.1",
"llama-cpp-python == 0.2.88", "llama-cpp-python == 0.2.88",
"itsdangerous == 2.1.2", "itsdangerous == 2.1.2",
@ -88,6 +89,7 @@ dependencies = [
"anthropic == 0.26.1", "anthropic == 0.26.1",
"docx2txt == 0.8", "docx2txt == 0.8",
"google-generativeai == 0.8.3", "google-generativeai == 0.8.3",
"pyjson5 == 1.6.7",
] ]
dynamic = ["version"] dynamic = ["version"]

View file

@ -0,0 +1,211 @@
/*
* Copyright 2019 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import groovy.xml.MarkupBuilder
plugins {
id 'com.android.application'
}
def twaManifest = [
applicationId: 'dev.khoj.app',
hostName: 'app.khoj.dev', // The domain being opened in the TWA.
launchUrl: '/', // The start path for the TWA. Must be relative to the domain.
name: 'Khoj AI', // The application name.
launcherName: 'Khoj', // The name shown on the Android Launcher.
themeColor: '#FFFFFF', // The color used for the status bar.
themeColorDark: '#000000', // The color used for the dark status bar.
navigationColor: '#000000', // The color used for the navigation bar.
navigationColorDark: '#000000', // The color used for the dark navbar.
navigationDividerColor: '#000000', // The navbar divider color.
navigationDividerColorDark: '#000000', // The dark navbar divider color.
backgroundColor: '#FFFFFF', // The color used for the splash screen background.
enableNotifications: true, // Set to true to enable notification delegation.
// Every shortcut must include the following fields:
// - name: String that will show up in the shortcut.
// - short_name: Shorter string used if |name| is too long.
// - url: Absolute path of the URL to launch the app with (e.g '/create').
// - icon: Name of the resource in the drawable folder to use as an icon.
shortcuts: [],
// The duration of fade out animation in milliseconds to be played when removing splash screen.
splashScreenFadeOutDuration: 300,
generatorApp: 'bubblewrap-cli', // Application that generated the Android Project
// The fallback strategy for when Trusted Web Activity is not available. Possible values are
// 'customtabs' and 'webview'.
fallbackType: 'customtabs',
enableSiteSettingsShortcut: 'true',
orientation: 'natural',
]
android {
compileSdkVersion 35
namespace "dev.khoj.app"
defaultConfig {
applicationId "dev.khoj.app"
minSdkVersion 19
targetSdkVersion 35
versionCode 4
versionName "4"
// The name for the application
resValue "string", "appName", twaManifest.name
// The name for the application on the Android Launcher
resValue "string", "launcherName", twaManifest.launcherName
// The URL that will be used when launching the TWA from the Android Launcher
def launchUrl = "https://" + twaManifest.hostName + twaManifest.launchUrl
resValue "string", "launchUrl", launchUrl
// The URL the Web Manifest for the Progressive Web App that the TWA points to. This
// is used by Chrome OS and Meta Quest to open the Web version of the PWA instead of
// the TWA, as it will probably give a better user experience for non-mobile devices.
resValue "string", "webManifestUrl", 'https://app.khoj.dev/static/khoj.webmanifest'
// This is used by Meta Quest.
resValue "string", "fullScopeUrl", 'https://app.khoj.dev/'
// The hostname is used when building the intent-filter, so the TWA is able to
// handle Intents to open host url of the application.
resValue "string", "hostName", twaManifest.hostName
// This attribute sets the status bar color for the TWA. It can be either set here or in
// `res/values/colors.xml`. Setting in both places is an error and the app will not
// compile. If not set, the status bar color defaults to #FFFFFF - white.
resValue "color", "colorPrimary", twaManifest.themeColor
// This attribute sets the dark status bar color for the TWA. It can be either set here or in
// `res/values/colors.xml`. Setting in both places is an error and the app will not
// compile. If not set, the status bar color defaults to #000000 - white.
resValue "color", "colorPrimaryDark", twaManifest.themeColorDark
// This attribute sets the navigation bar color for the TWA. It can be either set here or
// in `res/values/colors.xml`. Setting in both places is an error and the app will not
// compile. If not set, the navigation bar color defaults to #FFFFFF - white.
resValue "color", "navigationColor", twaManifest.navigationColor
// This attribute sets the dark navigation bar color for the TWA. It can be either set here
// or in `res/values/colors.xml`. Setting in both places is an error and the app will not
// compile. If not set, the navigation bar color defaults to #000000 - black.
resValue "color", "navigationColorDark", twaManifest.navigationColorDark
// This attribute sets the navbar divider color for the TWA. It can be either
// set here or in `res/values/colors.xml`. Setting in both places is an error and the app
// will not compile. If not set, the divider color defaults to #00000000 - transparent.
resValue "color", "navigationDividerColor", twaManifest.navigationDividerColor
// This attribute sets the dark navbar divider color for the TWA. It can be either
// set here or in `res/values/colors.xml`. Setting in both places is an error and the
//app will not compile. If not set, the divider color defaults to #000000 - black.
resValue "color", "navigationDividerColorDark", twaManifest.navigationDividerColorDark
// Sets the color for the background used for the splash screen when launching the
// Trusted Web Activity.
resValue "color", "backgroundColor", twaManifest.backgroundColor
// Defines a provider authority for the Splash Screen
resValue "string", "providerAuthority", twaManifest.applicationId + '.fileprovider'
// The enableNotification resource is used to enable or disable the
// TrustedWebActivityService, by changing the android:enabled and android:exported
// attributes
resValue "bool", "enableNotification", twaManifest.enableNotifications.toString()
twaManifest.shortcuts.eachWithIndex { shortcut, index ->
resValue "string", "shortcut_name_$index", "$shortcut.name"
resValue "string", "shortcut_short_name_$index", "$shortcut.short_name"
}
// The splashScreenFadeOutDuration resource is used to set the duration of fade out animation in milliseconds
// to be played when removing splash screen. The default is 0 (no animation).
resValue "integer", "splashScreenFadeOutDuration", twaManifest.splashScreenFadeOutDuration.toString()
resValue "string", "generatorApp", twaManifest.generatorApp
resValue "string", "fallbackType", twaManifest.fallbackType
resValue "bool", "enableSiteSettingsShortcut", twaManifest.enableSiteSettingsShortcut
resValue "string", "orientation", twaManifest.orientation
}
buildTypes {
release {
minifyEnabled true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
checkReleaseBuilds false
}
}
task generateShorcutsFile {
assert twaManifest.shortcuts.size() < 5, "You can have at most 4 shortcuts."
twaManifest.shortcuts.eachWithIndex { s, i ->
assert s.name != null, 'Missing `name` in shortcut #' + i
assert s.short_name != null, 'Missing `short_name` in shortcut #' + i
assert s.url != null, 'Missing `icon` in shortcut #' + i
assert s.icon != null, 'Missing `url` in shortcut #' + i
}
def shortcutsFile = new File("$projectDir/src/main/res/xml", "shortcuts.xml")
def xmlWriter = new StringWriter()
def xmlMarkup = new MarkupBuilder(new IndentPrinter(xmlWriter, " ", true))
xmlMarkup
.'shortcuts'('xmlns:android': 'http://schemas.android.com/apk/res/android') {
twaManifest.shortcuts.eachWithIndex { s, i ->
'shortcut'(
'android:shortcutId': 'shortcut' + i,
'android:enabled': 'true',
'android:icon': '@drawable/' + s.icon,
'android:shortcutShortLabel': '@string/shortcut_short_name_' + i,
'android:shortcutLongLabel': '@string/shortcut_name_' + i) {
'intent'(
'android:action': 'android.intent.action.MAIN',
'android:targetPackage': twaManifest.applicationId,
'android:targetClass': twaManifest.applicationId + '.LauncherActivity',
'android:data': s.url)
'categories'('android:name': 'android.intent.category.LAUNCHER')
}
}
}
shortcutsFile.text = xmlWriter.toString() + '\n'
}
preBuild.dependsOn(generateShorcutsFile)
repositories {
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.google.androidbrowserhelper:locationdelegation:1.1.1'
implementation 'com.google.androidbrowserhelper:androidbrowserhelper:2.5.0'
}

View file

@ -0,0 +1,188 @@
<!--
Copyright 2019 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- The "package" attribute is rewritten by the Gradle build with the value of applicationId.
It is still required here, as it is used to derive paths, for instance when referring
to an Activity by ".MyActivity" instead of the full name. If more Activities are added to the
application, the package attribute will need to reflect the correct path in order to use
the abbreviated format. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="dev.khoj.app">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application
android:name="Application"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/appName"
android:manageSpaceActivity="com.google.androidbrowserhelper.trusted.ManageDataLauncherActivity"
android:supportsRtl="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<meta-data
android:name="asset_statements"
android:resource="@string/assetStatements" />
<meta-data
android:name="web_manifest_url"
android:value="@string/webManifestUrl" />
<meta-data
android:name="twa_generator"
android:value="@string/generatorApp" />
<activity android:name="com.google.androidbrowserhelper.trusted.ManageDataLauncherActivity">
<meta-data
android:name="android.support.customtabs.trusted.MANAGE_SPACE_URL"
android:value="@string/launchUrl" />
</activity>
<activity android:name="LauncherActivity"
android:alwaysRetainTaskState="true"
android:label="@string/launcherName"
android:exported="true">
<meta-data android:name="android.support.customtabs.trusted.DEFAULT_URL"
android:value="@string/launchUrl" />
<meta-data
android:name="android.support.customtabs.trusted.STATUS_BAR_COLOR"
android:resource="@color/colorPrimary" />
<meta-data
android:name="android.support.customtabs.trusted.STATUS_BAR_COLOR_DARK"
android:resource="@color/colorPrimaryDark" />
<meta-data
android:name="android.support.customtabs.trusted.NAVIGATION_BAR_COLOR"
android:resource="@color/navigationColor" />
<meta-data
android:name="android.support.customtabs.trusted.NAVIGATION_BAR_COLOR_DARK"
android:resource="@color/navigationColorDark" />
<meta-data
android:name="androix.browser.trusted.NAVIGATION_BAR_DIVIDER_COLOR"
android:resource="@color/navigationDividerColor" />
<meta-data
android:name="androix.browser.trusted.NAVIGATION_BAR_DIVIDER_COLOR_DARK"
android:resource="@color/navigationDividerColorDark" />
<meta-data android:name="android.support.customtabs.trusted.SPLASH_IMAGE_DRAWABLE"
android:resource="@drawable/splash"/>
<meta-data android:name="android.support.customtabs.trusted.SPLASH_SCREEN_BACKGROUND_COLOR"
android:resource="@color/backgroundColor"/>
<meta-data android:name="android.support.customtabs.trusted.SPLASH_SCREEN_FADE_OUT_DURATION"
android:value="@integer/splashScreenFadeOutDuration"/>
<meta-data android:name="android.support.customtabs.trusted.FILE_PROVIDER_AUTHORITY"
android:value="@string/providerAuthority"/>
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts" />
<meta-data android:name="android.support.customtabs.trusted.FALLBACK_STRATEGY"
android:value="@string/fallbackType" />
<meta-data android:name="android.support.customtabs.trusted.SCREEN_ORIENTATION"
android:value="@string/orientation"/>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="https"
android:host="@string/hostName"/>
</intent-filter>
</activity>
<activity android:name="com.google.androidbrowserhelper.trusted.FocusActivity" />
<activity android:name="com.google.androidbrowserhelper.trusted.WebViewFallbackActivity"
android:configChanges="orientation|screenSize" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="@string/providerAuthority"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
<service
android:name=".DelegationService"
android:enabled="@bool/enableNotification"
android:exported="@bool/enableNotification">
<meta-data
android:name="android.support.customtabs.trusted.SMALL_ICON"
android:resource="@drawable/ic_notification_icon" />
<intent-filter>
<action android:name="android.support.customtabs.trusted.TRUSTED_WEB_ACTIVITY_SERVICE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
<activity android:name="com.google.androidbrowserhelper.trusted.NotificationPermissionRequestActivity" />
<activity android:name=
"com.google.androidbrowserhelper.locationdelegation.PermissionRequestActivity"/>
</application>
</manifest>

View file

@ -0,0 +1,29 @@
/*
* Copyright 2020 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.khoj.app;
public class Application extends android.app.Application {
@Override
public void onCreate() {
super.onCreate();
}
}

View file

@ -0,0 +1,17 @@
package dev.khoj.app;
import com.google.androidbrowserhelper.locationdelegation.LocationDelegationExtraCommandHandler;
public class DelegationService extends
com.google.androidbrowserhelper.trusted.DelegationService {
@Override
public void onCreate() {
super.onCreate();
registerExtraCommandHandler(new LocationDelegationExtraCommandHandler());
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright 2020 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.khoj.app;
import android.content.pm.ActivityInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
public class LauncherActivity
extends com.google.androidbrowserhelper.trusted.LauncherActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Setting an orientation crashes the app due to the transparent background on Android 8.0
// Oreo and below. We only set the orientation on Oreo and above. This only affects the
// splash screen and Chrome will still respect the orientation.
// See https://github.com/GoogleChromeLabs/bubblewrap/issues/496 for details.
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
}
@Override
protected Uri getLaunchingUrl() {
// Get the original launch Url.
Uri uri = super.getLaunchingUrl();
return uri;
}
}

View file

@ -0,0 +1,25 @@
<!--
Copyright 2020 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:inset="2dp">
<aapt:attr name="android:drawable">
<shape android:shape="oval">
<solid android:color="@color/shortcut_background" />
<size android:width="44dp" android:height="44dp" />
</shape>
</aapt:attr>
</inset>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1 @@
{"name":"Khoj","short_name":"Khoj","display":"standalone","start_url":"/","description":"The open, personal AI for your digital brain. You can ask Khoj to draft a message, paint your imagination, find information on the internet and even answer questions from your documents.","theme_color":"#ffffff","background_color":"#ffffff","icons":[{"src":"/static/assets/icons/khoj_lantern_128x128.png","sizes":"128x128","type":"image/png"},{"src":"/static/assets/icons/khoj_lantern_256x256.png","sizes":"256x256","type":"image/png"}],"screenshots":[{"src":"/static/assets/samples/phone-remember-plan-sample.png","sizes":"419x900","type":"image/png","form_factor":"narrow","label":"Remember and Plan"},{"src":"/static/assets/samples/phone-browse-draw-sample.png","sizes":"419x900","type":"image/png","form_factor":"narrow","label":"Browse and Draw"},{"src":"/static/assets/samples/desktop-remember-plan-sample.png","sizes":"1260x742","type":"image/png","form_factor":"wide","label":"Remember and Plan"},{"src":"/static/assets/samples/desktop-browse-draw-sample.png","sizes":"1260x742","type":"image/png","form_factor":"wide","label":"Browse and Draw"}]}

View file

@ -0,0 +1,18 @@
<!--
Copyright 2020 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<color name="shortcut_background">#F5F5F5</color>
</resources>

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2021 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!--
This variable below expresses the relationship between the app and the site,
as documented in the TWA documentation at
https://developers.google.com/web/updates/2017/10/using-twa#set_up_digital_asset_links_in_an_android_app
and is injected into the AndroidManifest.xml
-->
<string name="assetStatements">
[{
\"relation\": [\"delegate_permission/common.handle_all_urls\"],
\"target\": {
\"namespace\": \"web\",
\"site\": \"https://app.khoj.dev\"
}
}]
</string>
</resources>

View file

@ -0,0 +1,18 @@
<!--
Copyright 2019 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<paths>
<files-path path="twa_splash/" name="twa_splash" />
</paths>

View file

@ -0,0 +1 @@
<shortcuts xmlns:android='http://schemas.android.com/apk/res/android' />

View file

@ -0,0 +1,42 @@
/*
* Copyright 2019 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.7.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
tasks.register('clean', Delete) {
delete rootProject.buildDir
}

View file

@ -0,0 +1,14 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useAndroidX=true

Binary file not shown.

View file

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true

249
src/interface/android/gradlew vendored Executable file
View file

@ -0,0 +1,249 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

92
src/interface/android/gradlew.bat vendored Normal file
View file

@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -0,0 +1 @@
6ee1711cf4f745dafc80c1cc13c3025342a0f5da

View file

@ -0,0 +1 @@
include ':app'

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,55 @@
{
"packageId": "dev.khoj.app",
"host": "app.khoj.dev",
"name": "Khoj AI",
"launcherName": "Khoj",
"display": "standalone",
"themeColor": "#FFFFFF",
"themeColorDark": "#000000",
"navigationColor": "#000000",
"navigationColorDark": "#000000",
"navigationDividerColor": "#000000",
"navigationDividerColorDark": "#000000",
"backgroundColor": "#FFFFFF",
"enableNotifications": true,
"startUrl": "/",
"iconUrl": "https://assets.khoj.dev/khoj_lantern_1200x1200.png",
"splashScreenFadeOutDuration": 300,
"signingKey": {
"path": "android.keystore",
"alias": "android"
},
"appVersionName": "4",
"appVersionCode": 4,
"shortcuts": [],
"generatorApp": "bubblewrap-cli",
"webManifestUrl": "https://app.khoj.dev/static/khoj.webmanifest",
"fallbackType": "customtabs",
"features": {
"locationDelegation": {
"enabled": true
}
},
"alphaDependencies": {
"enabled": false
},
"enableSiteSettingsShortcut": true,
"isChromeOSOnly": false,
"isMetaQuest": false,
"fullScopeUrl": "https://app.khoj.dev/",
"minSdkVersion": 19,
"orientation": "natural",
"fingerprints": [
{
"name": "signing",
"value": "CC:98:4A:0A:F1:CC:84:26:AC:02:86:49:AA:69:64:B9:5E:63:A3:EF:18:56:EA:CA:13:C1:3A:15:CA:49:77:46"
},
{
"name": "upload",
"value": "D4:5A:6F:6C:18:28:D2:1C:78:27:92:C6:AC:DB:4C:12:C4:52:A1:88:9B:A1:F5:67:D1:22:FE:A0:0F:B1:AE:92"
}
],
"additionalTrustedOrigins": [],
"retainedBundles": [],
"appVersion": "4"
}

View file

@ -1,24 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg"
width="800px"
height="800px"
viewBox="0 0 24 24"
fill="none"
version="1.1">
<path
d="m 14.024348,9.8497703 0.04627,1.9750167"
stroke="#1c274c"
stroke-width="1.77073"
stroke-linecap="round" />
<path
d="m 9.6453624,9.7953624 0.046275,1.9750166"
stroke="#1c274c"
stroke-width="1.77072"
stroke-linecap="round" />
<path
d="m 11.90538,2.3619994 c -5.4939109,0 -9.6890976,4.0608185 -9.6890976,9.8578926 0,1.477202 0.2658016,2.542848 0.6989332,3.331408 0.433559,0.789293 1.0740097,1.372483 1.9230615,1.798517 1.7362861,0.87132 4.1946007,1.018626 7.0671029,1.018626 0.317997,0 0.593711,0.167879 0.784844,0.458501 0.166463,0.253124 0.238617,0.552748 0.275566,0.787233 0.07263,0.460801 0.05871,1.030165 0.04785,1.474824 v 4.8e-5 l -2.26e-4,0.0091 c -0.0085,0.348246 -0.01538,0.634247 -0.0085,0.861186 0.105589,-0.07971 0.227925,-0.185287 0.36735,-0.31735 0.348613,-0.330307 0.743513,-0.767362 1.176607,-1.246635 l 0.07837,-0.08673 c 0.452675,-0.500762 0.941688,-1.037938 1.41216,-1.473209 0.453774,-0.419787 0.969948,-0.822472 1.476003,-0.953853 1.323661,-0.343655 2.330132,-0.904027 3.005749,-1.76381 0.658957,-0.838568 1.073167,-2.051868 1.073167,-3.898667 0,-5.7970748 -4.195186,-9.8578946 -9.689097,-9.8578946 z M 0.92440678,12.219892 c 0,-7.0067939 5.05909412,-11.47090892 10.98097322,-11.47090892 5.921878,0 10.980972,4.46411502 10.980972,11.47090892 0,2.172259 -0.497596,3.825405 -1.442862,5.028357 -0.928601,1.181693 -2.218843,1.837914 -3.664937,2.213334 -0.211641,0.05502 -0.53529,0.268579 -0.969874,0.670658 -0.417861,0.386604 -0.865628,0.876836 -1.324566,1.384504 l -0.09131,0.101202 c -0.419252,0.464136 -0.849637,0.94059 -1.239338,1.309807 -0.210187,0.199169 -0.425281,0.383422 -0.635348,0.523424 -0.200911,0.133819 -0.449635,0.263369 -0.716376,0.281474 -0.327812,0.02226 -0.61539,-0.149209 -0.804998,-0.457293 -0.157614,-0.255993 -0.217622,-0.557143 -0.246564,-0.778198 -0.0542,-0.414027 -0.04101,-0.933065 -0.03027,-1.355183 l 0.0024,-0.0922 c 0.01099,-0.463865 0.01489,-0.820507 -0.01611,-1.06842 C 8.9434608,19.975238 6.3139711,19.828758 4.356743,18.84659 3.3355029,18.334136 2.4624526,17.578678 1.8500164,16.463713 1.2372016,15.348029 0.92459928,13.943803 0.92459928,12.219967 Z"
clip-rule="evenodd"
stroke-width="0.360886"
fill="#1c274c"
fill-rule="evenodd"
fill-opacity="1" />
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 12L12 12M12 12L9 12M12 12L12 9M12 12L12 15" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
<path d="M7 3.33782C8.47087 2.48697 10.1786 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 10.1786 2.48697 8.47087 3.33782 7" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 580 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><polyline points="128 80 128 128 168 152" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><polyline points="184 104 224 104 224 64" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M188.4,192a88,88,0,1,1,1.83-126.23C202,77.69,211.72,88.93,224,104" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>

After

Width:  |  Height:  |  Size: 573 B

View file

@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><defs><style>.cls-1{fill:#00a912;}.cls-1,.cls-2{fill-rule:evenodd;}.cls-2{fill:#fff;}</style></defs><title>confirm</title><path class="cls-1" d="M61.44,0A61.44,61.44,0,1,1,0,61.44,61.44,61.44,0,0,1,61.44,0Z"/><path class="cls-2" d="M42.37,51.68,53.26,62,79,35.87c2.13-2.16,3.47-3.9,6.1-1.19l8.53,8.74c2.8,2.77,2.66,4.4,0,7L58.14,85.34c-5.58,5.46-4.61,5.79-10.26.19L28,65.77c-1.18-1.28-1.05-2.57.24-3.84l9.9-10.27c1.5-1.58,2.7-1.44,4.22,0Z"/></svg>

Before

Width:  |  Height:  |  Size: 549 B

View file

@ -1,9 +1 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><line x1="48" y1="40" x2="208" y2="216" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M193.89,200.49A79.66,79.66,0,0,1,160,208H72A56,56,0,1,1,85.92,97.74" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M112.63,63.52A80,80,0,0,1,219.68,181.28" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M80,128A79.68,79.68,0,0,1,91.07,87.37" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="-2.4 -2.4 28.80 28.80" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0">
<rect x="-2.4" y="-2.4" width="28.80" height="28.80" rx="14.4" fill="#ffad9f" strokewidth="0"/>
</g>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <path d="M22 12.3529C22 15.0599 20.0726 17.3221 17.5 17.8722M6.28571 18C3.91878 18 2 16.1038 2 13.7647C2 11.4256 3.91878 9.52941 6.28571 9.52941C6.56983 9.52941 6.8475 9.55673 7.11616 9.60887M14.381 7.02721C14.9767 6.81911 15.6178 6.70588 16.2857 6.70588C16.9404 6.70588 17.5693 6.81468 18.1551 7.01498M7.11616 9.60887C6.88706 8.9978 6.7619 8.33687 6.7619 7.64706C6.7619 4.52827 9.32028 2 12.4762 2C15.4159 2 17.8371 4.19371 18.1551 7.01498M7.11616 9.60887C7.68059 9.71839 8.20528 9.9374 8.66667 10.2426M18.1551 7.01498C18.8381 7.24853 19.4623 7.60648 20 8.06141" stroke="#d42400" stroke-width="1.5" stroke-linecap="round"/> <path d="M8.5 17C8.5 15.5858 8.5 14.8787 8.93934 14.4393C9.37868 14 10.0858 14 11.5 14H12.5C13.9142 14 14.6213 14 15.0607 14.4393C15.5 14.8787 15.5 15.5858 15.5 17V19C15.5 20.4142 15.5 21.1213 15.0607 21.5607C14.6213 22 13.9142 22 12.5 22H11.5C10.0858 22 9.37868 22 8.93934 21.5607C8.5 21.1213 8.5 20.4142 8.5 19V17Z" stroke="#d42400" stroke-width="1.5"/> <path d="M11 18H13" stroke="#d42400" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </g>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 729 B

View file

@ -1,9 +1 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M80,128a80,80,0,1,1,80,80H72A56,56,0,1,1,85.92,97.74" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><polyline points="120 136 144 160 192 112" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="-2.4 -2.4 28.80 28.80" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#16ba00">
<g id="SVGRepo_bgCarrier" stroke-width="0">
<rect x="-2.4" y="-2.4" width="28.80" height="28.80" rx="14.4" fill="#7aff00" strokewidth="0"/>
</g>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <path d="M22 13.3529C22 16.0599 20.0726 18.3221 17.5 18.8722M6.28571 19C3.91878 19 2 17.1038 2 14.7647C2 12.4256 3.91878 10.5294 6.28571 10.5294C6.56983 10.5294 6.8475 10.5567 7.11616 10.6089M14.381 8.02721C14.9767 7.81911 15.6178 7.70588 16.2857 7.70588C16.9404 7.70588 17.5693 7.81468 18.1551 8.01498M7.11616 10.6089C6.88706 9.9978 6.7619 9.33687 6.7619 8.64706C6.7619 5.52827 9.32028 3 12.4762 3C15.4159 3 17.8371 5.19371 18.1551 8.01498M7.11616 10.6089C7.68059 10.7184 8.20528 10.9374 8.66667 11.2426M18.1551 8.01498C18.8381 8.24853 19.4623 8.60648 20 9.06141" stroke="#16ba00" stroke-width="1.5" stroke-linecap="round"/> <path d="M10 19.8L11.1429 21L14 18" stroke="#16ba00" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 419 B

View file

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 512 512" xml:space="preserve">
<path id="SVGCleanerId_0" style="fill:#FFC36E;" d="M183.295,123.586H55.05c-6.687,0-12.801-3.778-15.791-9.76l-12.776-25.55
l12.776-25.55c2.99-5.982,9.103-9.76,15.791-9.76h128.246c6.687,0,12.801,3.778,15.791,9.76l12.775,25.55l-12.776,25.55
C196.096,119.808,189.983,123.586,183.295,123.586z"/>
<g>
<path id="SVGCleanerId_0_1_" style="fill:#FFC36E;" d="M183.295,123.586H55.05c-6.687,0-12.801-3.778-15.791-9.76l-12.776-25.55
l12.776-25.55c2.99-5.982,9.103-9.76,15.791-9.76h128.246c6.687,0,12.801,3.778,15.791,9.76l12.775,25.55l-12.776,25.55
C196.096,119.808,189.983,123.586,183.295,123.586z"/>
</g>
<path style="fill:#EFF2FA;" d="M485.517,70.621H26.483c-4.875,0-8.828,3.953-8.828,8.828v44.138h476.69V79.448
C494.345,74.573,490.392,70.621,485.517,70.621z"/>
<rect x="17.655" y="105.931" style="fill:#E1E6F2;" width="476.69" height="17.655"/>
<path style="fill:#FFD782;" d="M494.345,88.276H217.318c-3.343,0-6.4,1.889-7.895,4.879l-10.336,20.671
c-2.99,5.982-9.105,9.76-15.791,9.76H55.05c-6.687,0-12.801-3.778-15.791-9.76L28.922,93.155c-1.495-2.99-4.552-4.879-7.895-4.879
h-3.372C7.904,88.276,0,96.18,0,105.931v335.448c0,9.751,7.904,17.655,17.655,17.655h476.69c9.751,0,17.655-7.904,17.655-17.655
V105.931C512,96.18,504.096,88.276,494.345,88.276z"/>
<path style="fill:#FFC36E;" d="M485.517,441.379H26.483c-4.875,0-8.828-3.953-8.828-8.828l0,0c0-4.875,3.953-8.828,8.828-8.828
h459.034c4.875,0,8.828,3.953,8.828,8.828l0,0C494.345,437.427,490.392,441.379,485.517,441.379z"/>
<path style="fill:#EFF2FA;" d="M326.621,220.69h132.414c4.875,0,8.828-3.953,8.828-8.828v-70.621c0-4.875-3.953-8.828-8.828-8.828
H326.621c-4.875,0-8.828,3.953-8.828,8.828v70.621C317.793,216.737,321.746,220.69,326.621,220.69z"/>
<path style="fill:#C7CFE2;" d="M441.379,167.724h-97.103c-4.875,0-8.828-3.953-8.828-8.828l0,0c0-4.875,3.953-8.828,8.828-8.828
h97.103c4.875,0,8.828,3.953,8.828,8.828l0,0C450.207,163.772,446.254,167.724,441.379,167.724z"/>
<path style="fill:#D7DEED;" d="M441.379,203.034h-97.103c-4.875,0-8.828-3.953-8.828-8.828l0,0c0-4.875,3.953-8.828,8.828-8.828
h97.103c4.875,0,8.828,3.953,8.828,8.828l0,0C450.207,199.082,446.254,203.034,441.379,203.034z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 8.29344C22 11.7692 19.1708 14.5869 15.6807 14.5869C15.0439 14.5869 13.5939 14.4405 12.8885 13.8551L12.0067 14.7333C11.4883 15.2496 11.6283 15.4016 11.8589 15.652C11.9551 15.7565 12.0672 15.8781 12.1537 16.0505C12.1537 16.0505 12.8885 17.075 12.1537 18.0995C11.7128 18.6849 10.4783 19.5045 9.06754 18.0995L8.77362 18.3922C8.77362 18.3922 9.65538 19.4167 8.92058 20.4412C8.4797 21.0267 7.30403 21.6121 6.27531 20.5876L5.2466 21.6121C4.54119 22.3146 3.67905 21.9048 3.33616 21.6121L2.45441 20.7339C1.63143 19.9143 2.1115 19.0264 2.45441 18.6849L10.0963 11.0743C10.0963 11.0743 9.3615 9.90338 9.3615 8.29344C9.3615 4.81767 12.1907 2 15.6807 2C19.1708 2 22 4.81767 22 8.29344ZM15.681 10.4889C16.8984 10.4889 17.8853 9.50601 17.8853 8.29353C17.8853 7.08105 16.8984 6.09814 15.681 6.09814C14.4635 6.09814 13.4766 7.08105 13.4766 8.29353C13.4766 9.50601 14.4635 10.4889 15.681 10.4889Z" fill="#1C274C"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.16488 17.6505C8.92513 17.8743 8.73958 18.0241 8.54996 18.1336C7.62175 18.6695 6.47816 18.6695 5.54996 18.1336C5.20791 17.9361 4.87912 17.6073 4.22153 16.9498C3.56394 16.2922 3.23514 15.9634 3.03767 15.6213C2.50177 14.6931 2.50177 13.5495 3.03767 12.6213C3.23514 12.2793 3.56394 11.9505 4.22153 11.2929L7.04996 8.46448C7.70755 7.80689 8.03634 7.47809 8.37838 7.28062C9.30659 6.74472 10.4502 6.74472 11.3784 7.28061C11.7204 7.47809 12.0492 7.80689 12.7068 8.46448C13.3644 9.12207 13.6932 9.45086 13.8907 9.7929C14.4266 10.7211 14.4266 11.8647 13.8907 12.7929C13.7812 12.9825 13.6314 13.1681 13.4075 13.4078M10.5919 10.5922C10.368 10.8319 10.2182 11.0175 10.1087 11.2071C9.57284 12.1353 9.57284 13.2789 10.1087 14.2071C10.3062 14.5492 10.635 14.878 11.2926 15.5355C11.9502 16.1931 12.279 16.5219 12.621 16.7194C13.5492 17.2553 14.6928 17.2553 15.621 16.7194C15.9631 16.5219 16.2919 16.1931 16.9495 15.5355L19.7779 12.7071C20.4355 12.0495 20.7643 11.7207 20.9617 11.3787C21.4976 10.4505 21.4976 9.30689 20.9617 8.37869C20.7643 8.03665 20.4355 7.70785 19.7779 7.05026C19.1203 6.39267 18.7915 6.06388 18.4495 5.8664C17.5212 5.3305 16.3777 5.3305 15.4495 5.8664C15.2598 5.97588 15.0743 6.12571 14.8345 6.34955" stroke="#000000" stroke-width="2" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 616 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><polyline points="216 104 215.99 40.01 152 40" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="136" y1="120" x2="216" y2="40" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M184,136v72a8,8,0,0,1-8,8H48a8,8,0,0,1-8-8V80a8,8,0,0,1,8-8h72" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>

After

Width:  |  Height:  |  Size: 574 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100px" height="100px"><path fill="#fefdef" d="M29.614,12.307h-1.268c-4.803,0-8.732,3.93-8.732,8.732v61.535c0,4.803,3.93,8.732,8.732,8.732h43.535c4.803,0,8.732-3.93,8.732-8.732v-50.02C72.74,24.68,68.241,20.182,60.367,12.307H41.614"/><path fill="#1f212b" d="M71.882,92.307H28.347c-5.367,0-9.732-4.366-9.732-9.732V21.04c0-5.367,4.366-9.732,9.732-9.732h1.268c0.552,0,1,0.448,1,1s-0.448,1-1,1h-1.268c-4.264,0-7.732,3.469-7.732,7.732v61.535c0,4.264,3.469,7.732,7.732,7.732h43.535c4.264,0,7.732-3.469,7.732-7.732V32.969L59.953,13.307H41.614c-0.552,0-1-0.448-1-1s0.448-1,1-1h18.752c0.265,0,0.52,0.105,0.707,0.293l20.248,20.248c0.188,0.188,0.293,0.442,0.293,0.707v50.02C81.614,87.941,77.248,92.307,71.882,92.307z"/><path fill="#fef6aa" d="M60.114,12.807v10.986c0,4.958,4.057,9.014,9.014,9.014h11.986"/><path fill="#1f212b" d="M81.114 33.307H69.129c-5.247 0-9.515-4.268-9.515-9.515V12.807c0-.276.224-.5.5-.5s.5.224.5.5v10.985c0 4.695 3.82 8.515 8.515 8.515h11.985c.276 0 .5.224.5.5S81.391 33.307 81.114 33.307zM75.114 51.307c-.276 0-.5-.224-.5-.5v-3c0-.276.224-.5.5-.5s.5.224.5.5v3C75.614 51.083 75.391 51.307 75.114 51.307zM75.114 59.307c-.276 0-.5-.224-.5-.5v-6c0-.276.224-.5.5-.5s.5.224.5.5v6C75.614 59.083 75.391 59.307 75.114 59.307zM67.956 86.307H32.272c-4.223 0-7.658-3.45-7.658-7.689V25.955c0-2.549 1.264-4.931 3.382-6.371.228-.156.54-.095.695.132.155.229.096.54-.132.695-1.844 1.254-2.944 3.326-2.944 5.544v52.663c0 3.688 2.987 6.689 6.658 6.689h35.685c3.671 0 6.658-3.001 6.658-6.689V60.807c0-.276.224-.5.5-.5s.5.224.5.5v17.811C75.614 82.857 72.179 86.307 67.956 86.307z"/><path fill="#1f212b" d="M39.802 14.307l-.117 11.834c0 2.21-2.085 3.666-4.036 3.666-1.951 0-4.217-1.439-4.217-3.649l.037-12.58c0-1.307 1.607-2.451 2.801-2.451 1.194 0 2.345 1.149 2.345 2.456l.021 10.829c0 0-.083.667-1.005.645-.507-.012-1.145-.356-1.016-.906v-9.843h-.813l-.021 9.708c0 1.38.54 1.948 1.875 1.948s1.959-.714 1.959-2.094V13.665c0-2.271-1.36-3.5-3.436-3.5s-3.564 1.261-3.564 3.532l.032 12.11c0 3.04 2.123 4.906 4.968 4.906 2.845 0 5-1.71 5-4.75V14.307H39.802zM53.114 52.307h-23c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h23c.276 0 .5.224.5.5S53.391 52.307 53.114 52.307zM44.114 59.307h-14c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h14c.276 0 .5.224.5.5S44.391 59.307 44.114 59.307zM70.114 59.307h-24c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h24c.276 0 .5.224.5.5S70.391 59.307 70.114 59.307zM61.114 66.307h-11c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h11c.276 0 .5.224.5.5S61.391 66.307 61.114 66.307zM71.114 66.307h-8c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h8c.276 0 .5.224.5.5S71.391 66.307 71.114 66.307zM48.114 66.307h-18c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h18c.276 0 .5.224.5.5S48.391 66.307 48.114 66.307zM70.114 73.307h-13c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h13c.276 0 .5.224.5.5S70.391 73.307 70.114 73.307zM54.114 73.307h-24c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h24c.276 0 .5.224.5.5S54.391 73.307 54.114 73.307z"/></svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 512"><path fill-rule="nonzero" d="M256 0c70.69 0 134.7 28.66 181.02 74.98C483.34 121.31 512 185.31 512 256c0 70.69-28.66 134.7-74.98 181.02C390.7 483.34 326.69 512 256 512c-70.69 0-134.69-28.66-181.02-74.98C28.66 390.7 0 326.69 0 256c0-70.69 28.66-134.69 74.98-181.02C121.31 28.66 185.31 0 256 0zm-21.49 301.51v-2.03c.16-13.46 1.48-24.12 4.07-32.05 2.54-7.92 6.19-14.37 10.97-19.25 4.77-4.92 10.51-9.39 17.22-13.46 4.31-2.74 8.22-5.78 11.68-9.18 3.45-3.36 6.19-7.27 8.23-11.69 2.02-4.37 3.04-9.24 3.04-14.62 0-6.4-1.52-11.94-4.57-16.66-3-4.68-7.06-8.28-12.04-10.87-5.03-2.54-10.61-3.81-16.76-3.81-5.53 0-10.81 1.11-15.89 3.45-5.03 2.29-9.25 5.89-12.55 10.77-3.3 4.87-5.23 11.12-5.74 18.74h-32.91c.51-12.95 3.81-23.92 9.85-32.91 6.1-8.99 14.13-15.8 24.08-20.42 10.01-4.62 21.08-6.9 33.16-6.9 13.31 0 24.89 2.43 34.84 7.41 9.96 4.93 17.73 11.83 23.27 20.67 5.48 8.84 8.28 19.1 8.28 30.88 0 8.08-1.27 15.34-3.81 21.79-2.54 6.45-6.1 12.24-10.77 17.27-4.68 5.08-10.21 9.54-16.71 13.41-6.15 3.86-11.12 7.82-14.88 11.93-3.81 4.11-6.56 8.99-8.28 14.58-1.73 5.63-2.69 12.59-2.84 20.92v2.03h-30.94zm16.36 65.82c-5.94-.04-11.02-2.13-15.29-6.35-4.26-4.21-6.35-9.34-6.35-15.33 0-5.89 2.09-10.97 6.35-15.19 4.27-4.21 9.35-6.35 15.29-6.35 5.84 0 10.92 2.14 15.18 6.35 4.32 4.22 6.45 9.3 6.45 15.19 0 3.96-1.01 7.62-2.99 10.87-1.98 3.3-4.57 5.94-7.82 7.87-3.25 1.93-6.86 2.9-10.82 2.94zM417.71 94.29C376.33 52.92 319.15 27.32 256 27.32c-63.15 0-120.32 25.6-161.71 66.97C52.92 135.68 27.32 192.85 27.32 256c0 63.15 25.6 120.33 66.97 161.71 41.39 41.37 98.56 66.97 161.71 66.97 63.15 0 120.33-25.6 161.71-66.97 41.37-41.38 66.97-98.56 66.97-161.71 0-63.15-25.6-120.32-66.97-161.71z"/></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1,25 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg"
width="800px"
height="800px"
viewBox="0 0 24 24"
fill="none"
version="1.1">
<path
d="m 18.562765,17.147843 c 1.380497,-1.679442 2.307667,-4.013099 2.307667,-6.330999 C 20.870432,5.3951476 16.353958,1 10.782674,1 5.2113555,1 0.69491525,5.3951476 0.69491525,10.816844 c 0,5.421663 4.51644025,9.816844 10.08775875,9.816844 2.381867,0 4.570922,-0.803307 6.296712,-2.14673 0.508475,-0.508475 4.514633,4.192839 4.514633,4.192839 1.036377,1.008544 2.113087,-0.02559 1.07671,-1.034139 z m -7.780091,1.925408 c -4.3394583,0 -8.6708434,-4.033489 -8.6708434,-8.256407 0,-4.2229187 4.3313851,-8.2564401 8.6708434,-8.2564401 4.339458,0 8.670809,4.2369112 8.670809,8.4598301 0,4.222918 -4.331351,8.053017 -8.670809,8.053017 z"
fill="#1c274c"
fill-rule="evenodd"
clip-rule="evenodd"
fill-opacity="1"
stroke-width="1.10519"
stroke-dasharray="none" />
<path
d="m 13.337351,9.3402647 0.05184,2.1532893"
stroke="#1c274c"
stroke-width="1.95702"
stroke-linecap="round" />
<path
d="M 8.431347,9.2809457 8.483191,11.434235"
stroke="#1c274c"
stroke-width="1.95701"
stroke-linecap="round" />
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -1,37 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1 +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> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><line x1="216" y1="56" x2="40" y2="56" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="88" y1="24" x2="168" y2="24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M200,56V208a8,8,0,0,1-8,8H64a8,8,0,0,1-8-8V56" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>

Before

Width:  |  Height:  |  Size: 503 B

After

Width:  |  Height:  |  Size: 547 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M176,128h48a8,8,0,0,1,8,8v64a8,8,0,0,1-8,8H32a8,8,0,0,1-8-8V136a8,8,0,0,1,8-8H80" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="128" y1="128" x2="128" y2="24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><polyline points="80 72 128 24 176 72" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><circle cx="188" cy="168" r="12"/></svg>

After

Width:  |  Height:  |  Size: 618 B

View file

@ -8,13 +8,12 @@
--primary-hover: #fee285; --primary-hover: #fee285;
--primary-focus: rgba(255, 179, 0, 0.125); --primary-focus: rgba(255, 179, 0, 0.125);
--primary-inverse: rgba(0, 0, 0, 0.75); --primary-inverse: rgba(0, 0, 0, 0.75);
--background-color: #f5f4f3; --background-color: #fff;
--main-text-color: #475569; --main-text-color: #475569;
--summer-sun: #fcc50b; --summer-sun: #fcc50b;
--water: #44b9da; --water: #44b9da;
--leaf: #7b990a; --leaf: #7b990a;
--flower: #d1684e; --flower: #d1684e;
--font-family: "Noto Sans", "Noto Sans Arabic", sans-serif !important;
} }
/* Amber Dark scheme (Auto) */ /* Amber Dark scheme (Auto) */
@ -25,13 +24,12 @@
--primary-hover: #fee285; --primary-hover: #fee285;
--primary-focus: rgba(255, 179, 0, 0.25); --primary-focus: rgba(255, 179, 0, 0.25);
--primary-inverse: rgba(0, 0, 0, 0.75); --primary-inverse: rgba(0, 0, 0, 0.75);
--background-color: #f5f4f3; --background-color: #fff;
--main-text-color: #475569; --main-text-color: #475569;
--summer-sun: #fcc50b; --summer-sun: #fcc50b;
--water: #44b9da; --water: #44b9da;
--leaf: #7b990a; --leaf: #7b990a;
--flower: #d1684e; --flower: #d1684e;
--font-family: "Noto Sans", "Noto Sans Arabic", sans-serif !important;
} }
} }
/* Amber Dark scheme (Forced) */ /* Amber Dark scheme (Forced) */
@ -41,13 +39,12 @@
--primary-hover: #fcc50b; --primary-hover: #fcc50b;
--primary-focus: rgba(255, 179, 0, 0.25); --primary-focus: rgba(255, 179, 0, 0.25);
--primary-inverse: rgba(0, 0, 0, 0.75); --primary-inverse: rgba(0, 0, 0, 0.75);
--background-color: #f5f4f3; --background-color: #fff;
--main-text-color: #475569; --main-text-color: #475569;
--summer-sun: #fcc50b; --summer-sun: #fcc50b;
--water: #44b9da; --water: #44b9da;
--leaf: #7b990a; --leaf: #7b990a;
--flower: #d1684e; --flower: #d1684e;
--font-family: "Noto Sans", "Noto Sans Arabic", sans-serif !important;
} }
/* Amber (Common styles) */ /* Amber (Common styles) */
:root { :root {
@ -90,36 +87,46 @@ nav.khoj-nav {
grid-gap: 32px; grid-gap: 32px;
justify-self: right; justify-self: right;
align-items: center; align-items: center;
} }
.khoj-status-box { .khoj-status-box {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; padding: 8px 12px;
min-width: 52px; border-radius: 16px;
gap: 4px; font-size: 14px;
-webkit-app-region: no-drag; font-weight: 500;
background-color: #f5f5f5; /* Neutral background */
color: #333; /* Neutral text color */
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); /* Subtle shadow */
transition: background-color 0.3s ease, color 0.3s ease;
} }
.khoj-status-box .khoj-status-connected { .khoj-status-connected {
height: 12px; width: 10px;
width: 12px; height: 10px;
border-radius: 50%; border-radius: 50%;
background-color: rgb(90, 235, 90); background-color: #4CAF50; /* Green for connected */
} margin-right: 8px;
.khoj-status-box .khoj-status-not-connected { }
height: 12px;
width: 12px; .khoj-status-not-connected {
border-radius: 50%; width: 10px;
background-color: rgb(235, 90, 90); height: 10px;
} border-radius: 50%;
background-color: #F44336; /* Red for not connected */
.khoj-status-box .khoj-status-text { margin-right: 8px;
display: none; }
.khoj-status-text {
color: #333; /* Neutral text color */
font-family: inherit;
}
.khoj-status-box:hover {
background-color: #e0e0e0; /* Slightly darker background on hover */
color: #000; /* Darker text on hover */
} }
.khoj-status-box:hover .khoj-status-text {
display: block;
}
a.khoj-nav { a.khoj-nav {
display: flex; display: flex;
@ -188,18 +195,18 @@ img.khoj-logo {
.khoj-nav-dropdown-content.show { .khoj-nav-dropdown-content.show {
opacity: 1; opacity: 1;
pointer-events: auto; pointer-events: auto;
border-radius: 20px; border-radius: 8px;
} }
.khoj-nav-dropdown-content a { .khoj-nav-dropdown-content a {
color: black; color: black;
padding: 12px 16px; padding: 12px 16px;
text-decoration: none; text-decoration: none;
display: block; display: block;
border-radius: 20px;
} }
.khoj-nav-dropdown-content a:hover { .khoj-nav-dropdown-content a:hover {
background-color: var(--primary-hover); background-color: hsla(24.6 95% 53.1% / 0.125);
} }
.khoj-nav-username { .khoj-nav-username {
padding: 12px 16px; padding: 12px 16px;
text-decoration: none; text-decoration: none;
@ -233,6 +240,17 @@ img.khoj-logo {
border: 3px solid var(--primary-hover); border: 3px solid var(--primary-hover);
} }
.khoj-nav-icon {
width: 20px;
height: 20px;
}
a.khoj-nav-link {
display: flex;
align-items: center;
gap: 8px;
}
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
.khoj-nav-dropdown-content { .khoj-nav-dropdown-content {
display: block; display: block;

File diff suppressed because it is too large Load diff

View file

@ -440,7 +440,7 @@ let titleBarStyle = process.platform === 'win32' ? 'default' : 'hidden';
const {globalShortcut, clipboard} = require('electron'); // global shortcut and clipboard dependencies for shortcut window const {globalShortcut, clipboard} = require('electron'); // global shortcut and clipboard dependencies for shortcut window
const openShortcutWindowKeyBind = 'CommandOrControl+Shift+K' const openShortcutWindowKeyBind = 'CommandOrControl+Shift+K'
const createWindow = (tab = 'chat.html') => { const createWindow = (tab = 'settings.html') => {
win = new BrowserWindow({ win = new BrowserWindow({
width: 800, width: 800,
height: 800, height: 800,
@ -602,6 +602,14 @@ app.whenReady().then(() => {
}); });
ipcMain.handle('deleteAllFiles', deleteAllFiles); ipcMain.handle('deleteAllFiles', deleteAllFiles);
ipcMain.handle('openFile', async (_, path) => {
try {
await shell.openPath(path);
} catch (error) {
console.error('Error opening file:', error);
}
});
const mainWindow = createWindow(); const mainWindow = createWindow();
app.setAboutPanelOptions({ app.setAboutPanelOptions({
@ -652,7 +660,7 @@ app.whenReady().then(() => {
globalShortcut.unregister('Escape'); globalShortcut.unregister('Escape');
}); });
ipcMain.on('continue-conversation-button-clicked', () => { ipcMain.on('continue-conversation-button-clicked', () => {
openWindow('chat.html'); openWindow('settings.html');
if (shortcutWin && !shortcutWin.isDestroyed()) { if (shortcutWin && !shortcutWin.isDestroyed()) {
shortcutWin.close(); shortcutWin.close();
} }
@ -727,8 +735,6 @@ app.whenReady().then(() => {
tray = new Tray(icon) tray = new Tray(icon)
const contextMenu = Menu.buildFromTemplate([ const contextMenu = Menu.buildFromTemplate([
{ label: 'Chat', type: 'normal', click: () => { openWindow('chat.html'); }},
{ label: 'Search', type: 'normal', click: () => { openWindow('search.html') }},
{ label: 'Configure', type: 'normal', click: () => { openWindow('settings.html') }}, { label: 'Configure', type: 'normal', click: () => { openWindow('settings.html') }},
{ type: 'separator' }, { type: 'separator' },
{ label: 'About Khoj', type: 'normal', click: () => { openAboutWindow(); } }, { label: 'About Khoj', type: 'normal', click: () => { openAboutWindow(); } },

View file

@ -1,6 +1,6 @@
{ {
"name": "Khoj", "name": "Khoj",
"version": "1.30.1", "version": "1.31.0",
"description": "Your Second Brain", "description": "Your Second Brain",
"author": "Khoj Inc. <team@khoj.dev>", "author": "Khoj Inc. <team@khoj.dev>",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
@ -16,7 +16,7 @@
"start": "yarn electron ." "start": "yarn electron ."
}, },
"dependencies": { "dependencies": {
"@todesktop/runtime": "^1.6.4", "@todesktop/runtime": "^2.0.0",
"axios": "^1.7.4", "axios": "^1.7.4",
"cron": "^2.4.3", "cron": "^2.4.3",
"electron-store": "^8.1.0" "electron-store": "^8.1.0"

View file

@ -83,4 +83,9 @@ contextBridge.exposeInMainWorld('appInfoAPI', {
contextBridge.exposeInMainWorld('navigateAPI', { contextBridge.exposeInMainWorld('navigateAPI', {
navigateToSettings: () => ipcRenderer.send('navigate', 'settings.html'), navigateToSettings: () => ipcRenderer.send('navigate', 'settings.html'),
navigateToWebSettings: () => ipcRenderer.send('navigateToWebApp', 'settings'), navigateToWebSettings: () => ipcRenderer.send('navigateToWebApp', 'settings'),
navigateToWebHome: () => ipcRenderer.send('navigateToWebApp', ''),
}) })
contextBridge.exposeInMainWorld('openFileAPI', {
openFile: (path) => ipcRenderer.invoke('openFile', path)
});

View file

@ -26,37 +26,10 @@ async function removeFolder(folderPath) {
} }
} }
const toggleFilesButton = document.getElementById('toggle-files');
const currentFiles = document.getElementById('current-files'); const currentFiles = document.getElementById('current-files');
const toggleFilesSVG = document.getElementById('toggle-files-svg');
toggleFilesButton.addEventListener('click', () => {
if (currentFiles.style.display === 'none') {
currentFiles.style.display = 'block';
toggleFilesSVG.style.transform = 'rotate(0deg)';
} else {
currentFiles.style.display = 'none';
toggleFilesSVG.style.transform = 'rotate(180deg)';
}
});
const toggleFoldersButton = document.getElementById('toggle-folders');
const currentFolders = document.getElementById('current-folders'); const currentFolders = document.getElementById('current-folders');
const toggleFoldersSVG = document.getElementById('toggle-folders-svg');
toggleFoldersButton.addEventListener('click', () => {
if (currentFolders.style.display === 'none') {
currentFolders.style.display = 'block';
toggleFoldersSVG.style.transform = 'rotate(0deg)';
} else {
currentFolders.style.display = 'none';
toggleFoldersSVG.style.transform = 'rotate(180deg)';
}
});
function makeFileElement(file) { function makeFileElement(file) {
let fileElement = document.createElement("div"); let fileElement = document.createElement("div");
fileElement.classList.add("file-element"); fileElement.classList.add("file-element");
@ -64,20 +37,34 @@ function makeFileElement(file) {
let fileNameElement = document.createElement("div"); let fileNameElement = document.createElement("div");
fileNameElement.classList.add("content-name"); fileNameElement.classList.add("content-name");
fileNameElement.innerHTML = file.path; fileNameElement.innerHTML = file.path;
fileNameElement.style.cursor = "pointer";
fileNameElement.addEventListener("click", () => {
window.openFileAPI.openFile(file.path);
});
fileElement.appendChild(fileNameElement); fileElement.appendChild(fileNameElement);
let buttonContainer = document.createElement("div"); let buttonContainer = document.createElement("div");
buttonContainer.classList.add("remove-button-container"); buttonContainer.classList.add("remove-button-container");
let removeFileButton = document.createElement("button"); let removeFileButton = document.createElement("button");
let fileSyncedImage = document.createElement("img") let fileSyncedImage = document.createElement("img");
fileSyncedImage.classList.add("file-synced-image"); fileSyncedImage.classList.add("file-synced-image");
fileSyncedImage.src = "./assets/icons/file-synced.svg";
// Create trash icon image
let trashIcon = document.createElement("img");
trashIcon.src = "./assets/icons/trash-solid.svg";
trashIcon.classList.add("trash-icon");
removeFileButton.classList.add("remove-file-button"); removeFileButton.classList.add("remove-file-button");
removeFileButton.innerHTML = "🗑️"; removeFileButton.appendChild(trashIcon);
removeFileButton.addEventListener("click", () => { removeFileButton.addEventListener("click", () => {
removeFile(file.path); removeFile(file.path);
}); });
buttonContainer.appendChild(removeFileButton); buttonContainer.appendChild(removeFileButton);
buttonContainer.insertAdjacentElement("afterbegin",fileSyncedImage); buttonContainer.insertAdjacentElement("afterbegin", fileSyncedImage);
fileElement.appendChild(buttonContainer); fileElement.appendChild(buttonContainer);
return fileElement; return fileElement;
} }
@ -89,13 +76,26 @@ function makeFolderElement(folder) {
let folderNameElement = document.createElement("div"); let folderNameElement = document.createElement("div");
folderNameElement.classList.add("content-name"); folderNameElement.classList.add("content-name");
folderNameElement.innerHTML = folder.path; folderNameElement.innerHTML = folder.path;
folderNameElement.style.cursor = "pointer";
folderNameElement.addEventListener("click", () => {
window.openFileAPI.openFile(folder.path);
});
folderElement.appendChild(folderNameElement); folderElement.appendChild(folderNameElement);
let buttonContainer = document.createElement("div"); let buttonContainer = document.createElement("div");
buttonContainer.classList.add("remove-button-container"); buttonContainer.classList.add("remove-button-container");
let removeFolderButton = document.createElement("button"); let removeFolderButton = document.createElement("button");
removeFolderButton.classList.add("remove-folder-button"); removeFolderButton.classList.add("remove-folder-button");
removeFolderButton.innerHTML = "🗑️";
// Create trash icon image
let trashIcon = document.createElement("img");
trashIcon.src = "./assets/icons/trash-solid.svg";
trashIcon.classList.add("trash-icon");
removeFolderButton.appendChild(trashIcon);
removeFolderButton.addEventListener("click", () => { removeFolderButton.addEventListener("click", () => {
removeFolder(folder.path); removeFolder(folder.path);
}); });
@ -104,7 +104,7 @@ function makeFolderElement(folder) {
return folderElement; return folderElement;
} }
(async function() { (async function () {
const files = await window.getFilesAPI.getFiles(); const files = await window.getFilesAPI.getFiles();
let currentFilesElement = document.getElementById("current-files"); let currentFilesElement = document.getElementById("current-files");
for (const file of files) { for (const file of files) {
@ -157,26 +157,35 @@ window.updateStateAPI.onUpdateState((event, state) => {
console.log("state was updated", state); console.log("state was updated", state);
loadingBar.style.display = 'none'; loadingBar.style.display = 'none';
let syncStatusElement = document.getElementById("sync-status"); let syncStatusElement = document.getElementById("sync-status");
syncStatusElement.innerHTML = '';
const currentTime = new Date(); const currentTime = new Date();
nextSyncTime = new Date(); nextSyncTime = new Date();
nextSyncTime.setMinutes(Math.ceil((nextSyncTime.getMinutes() + 1) / 10) * 10); nextSyncTime.setMinutes(Math.ceil((nextSyncTime.getMinutes() + 1) / 10) * 10);
if (state.completed == false) { if (state.completed == false) {
fileSyncedImage.forEach((image)=> { fileSyncedImage.forEach((image) => {
image.style.display = "block" image.style.display = "block"
image.src = "./assets/icons/file-not-synced.svg" image.src = "./assets/icons/file-not-synced.svg"
}) })
if (state.error) syncStatusElement.innerHTML = state.error; if (state.error) syncStatusElement.innerHTML = state.error;
return; return;
} else { } else {
fileSyncedImage.forEach((image)=> { fileSyncedImage.forEach((image) => {
image.style.display = "block" image.style.display = "block"
image.src = "./assets/icons/file-synced.svg" image.src = "./assets/icons/file-synced.svg"
}) })
} }
const options = { hour: '2-digit', minute: '2-digit' }; const options = { hour: '2-digit', minute: '2-digit' };
syncStatusElement.innerHTML = `⏱️ Synced at ${currentTime.toLocaleTimeString(undefined, options)}. Next sync at ${nextSyncTime.toLocaleTimeString(undefined, options)}.`;
const clockElement = document.createElement("div");
const clockIcon = document.createElement("img");
clockIcon.src = "./assets/icons/clock.svg";
clockIcon.classList.add("clock-icon");
clockElement.appendChild(clockIcon);
syncStatusElement.appendChild(clockElement);
syncStatusElement.innerHTML += ` Synced at ${currentTime.toLocaleTimeString(undefined, options)}. Next sync at ${nextSyncTime.toLocaleTimeString(undefined, options)}.`;
}); });
window.needsSubscriptionAPI.onNeedsSubscription((event, needsSubscription) => { window.needsSubscriptionAPI.onNeedsSubscription((event, needsSubscription) => {
@ -188,7 +197,7 @@ window.needsSubscriptionAPI.onNeedsSubscription((event, needsSubscription) => {
}); });
const urlInput = document.getElementById('khoj-host-url'); const urlInput = document.getElementById('khoj-host-url');
(async function() { (async function () {
const url = await window.hostURLAPI.getURL(); const url = await window.hostURLAPI.getURL();
urlInput.value = url; urlInput.value = url;
})(); })();
@ -210,7 +219,7 @@ urlInput.addEventListener('blur', async () => {
}); });
const khojKeyInput = document.getElementById('khoj-access-key'); const khojKeyInput = document.getElementById('khoj-access-key');
(async function() { (async function () {
const token = await window.tokenAPI.getToken(); const token = await window.tokenAPI.getToken();
khojKeyInput.value = token; khojKeyInput.value = token;
})(); })();

View file

@ -1,458 +0,0 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
<meta property="og:image" content="https://assets.khoj.dev/khoj_hero.png">
<title>Khoj - Search</title>
<link rel="icon" type="image/png" sizes="128x128" href="./assets/icons/favicon-128x128.png">
<link rel="manifest" href="./khoj.webmanifest">
<link rel="stylesheet" href="./assets/khoj.css">
</head>
<script type="text/javascript" src="./assets/org.min.js"></script>
<script type="text/javascript" src="./assets/markdown-it.min.js"></script>
<script src="./utils.js"></script>
<script>
function render_image(item) {
return `
<div class="results-image">
<a href="${item.entry}" class="image-link">
<img id=${item.score} src="${item.entry}?${Math.random()}"
title="Effective Score: ${item.score}, Meta: ${item.additional.metadata_score}, Image: ${item.additional.image_score}"
class="image">
</a>
</div>`;
}
function render_org(query, data, classPrefix="") {
return data.map(function (item) {
var orgParser = new Org.Parser();
var orgDocument = orgParser.parse(item.entry);
var orgHTMLDocument = orgDocument.convert(Org.ConverterHTML, { htmlClassPrefix: classPrefix, suppressNewLines: true });
return `<div class="results-org">` + orgHTMLDocument.toString() + `</div>`;
}).join("\n");
}
function render_markdown(query, data) {
var md = window.markdownit();
return data.map(function (item) {
let rendered = "";
if (item.additional.file.startsWith("http")) {
lines = item.entry.split("\n");
rendered = md.render(`${lines[0]}\t[*](${item.additional.file})\n${lines.slice(1).join("\n")}`);
}
else {
rendered = md.render(`${item.entry}`);
}
return `<div class="results-markdown">` + rendered + `</div>`;
}).join("\n");
}
function render_pdf(query, data) {
return data.map(function (item) {
let compiled_lines = item.additional.compiled.split("\n");
let filename = compiled_lines.shift();
let text_match = compiled_lines.join("\n")
return `<div class="results-pdf">` + `<h2>${filename}</h2>\n<p>${text_match}</p>` + `</div>`;
}).join("\n");
}
function render_html(query, data) {
return data.map(function (item) {
let document = new DOMParser().parseFromString(item.entry, "text/html");
// Scrub the HTML to remove any script tags and associated content
let script_tags = document.querySelectorAll("script");
for (let i = 0; i < script_tags.length; i++) {
script_tags[i].remove();
}
// Scrub the HTML to remove any style tags and associated content
let style_tags = document.querySelectorAll("style");
for (let i = 0; i < style_tags.length; i++) {
style_tags[i].remove();
}
// Scrub the HTML to remove any noscript tags and associated content
let noscript_tags = document.querySelectorAll("noscript");
for (let i = 0; i < noscript_tags.length; i++) {
noscript_tags[i].remove();
}
// Scrub the HTML to remove any iframe tags and associated content
let iframe_tags = document.querySelectorAll("iframe");
for (let i = 0; i < iframe_tags.length; i++) {
iframe_tags[i].remove();
}
// Scrub the HTML to remove any object tags and associated content
let object_tags = document.querySelectorAll("object");
for (let i = 0; i < object_tags.length; i++) {
object_tags[i].remove();
}
// Scrub the HTML to remove any embed tags and associated content
let embed_tags = document.querySelectorAll("embed");
for (let i = 0; i < embed_tags.length; i++) {
embed_tags[i].remove();
}
let scrubbedHTML = document.body.outerHTML;
return `<div class="results-html">` + scrubbedHTML + `</div>`;
}).join("\n");
}
function render_xml(query, data) {
return data.map(function (item) {
return `<div class="results-xml">` +
`<b><a href="${item.additional.file}">${item.additional.heading}</a></b>` +
`<xml>${item.entry}</xml>` +
`</div>`
}).join("\n");
}
function render_multiple(query, data, type) {
let html = "";
data.forEach(item => {
if (item.additional.file.endsWith(".org")) {
html += render_org(query, [item], "org-");
} else if (
item.additional.file.endsWith(".md") ||
item.additional.file.endsWith(".markdown") ||
(item.additional.file.includes("issues") && item.additional.source === "github") ||
(item.additional.file.includes("commit") && item.additional.source === "github")
)
{
html += render_markdown(query, [item]);
} else if (item.additional.file.endsWith(".pdf")) {
html += render_pdf(query, [item]);
} else if (item.additional.source == "notion") {
html += `<div class="results-notion">` + `<b><a href="${item.additional.file}">${item.additional.heading}</a></b>` + `<p>${item.entry}</p>` + `</div>`;
} else if (item.additional.file.endsWith(".html")) {
html += render_html(query, [item]);
} else if (item.additional.file.endsWith(".xml")) {
html += render_xml(query, [item])
} else {
html += `<div class="results-plugin">` + `<b><a href="${item.additional.file}">${item.additional.heading}</a></b>` + `<p>${item.entry}</p>` + `</div>`;
}
});
return html;
}
function render_results(data, query, type) {
let results = "";
if (type === "markdown") {
results = render_markdown(query, data);
} else if (type === "org") {
results = render_org(query, data, "org-");
} else if (type === "image") {
results = data.map(render_image).join('');
} else if (type === "pdf") {
results = render_pdf(query, data);
} else if (type === "github" || type === "all" || type === "notion") {
results = render_multiple(query, data, type);
} else {
results = data.map((item) => `<div class="results-plugin">` + `<p>${item.entry}</p>` + `</div>`).join("\n")
}
// Any POST rendering goes here.
let renderedResults = document.createElement("div");
renderedResults.id = `results-${type}`;
renderedResults.innerHTML = results;
// For all elements that are of type img in the results html and have a src with 'avatar' in the URL, add the class 'avatar'
// This is used to make the avatar images round
let images = renderedResults.querySelectorAll("img[src*='avatar']");
for (let i = 0; i < images.length; i++) {
images[i].classList.add("avatar");
}
return renderedResults.outerHTML;
}
async function search(rerank=false) {
// Extract required fields for search from form
query = document.getElementById("query").value.trim();
type = 'all';
results_count = localStorage.getItem("khojResultsCount") || 5;
console.log(`Query: ${query}, Type: ${type}, Results Count: ${results_count}`);
// Short circuit on empty query
if (query.length === 0) {
return;
}
// If set query field in url query param on rerank
if (rerank)
setQueryFieldInUrl(query);
// Execute Search and Render Results
url = await createRequestUrl(query, type, results_count || 5, rerank);
const khojToken = await window.tokenAPI.getToken();
const headers = { 'Authorization': `Bearer ${khojToken}` };
fetch(url, { headers })
.then(response => response.json())
.then(data => {
document.getElementById("results").innerHTML = render_results(data, query, type);
});
}
let debounceTimeout;
function incrementalSearch(event) {
// Run incremental search only after waitTime passed since the last key press
let waitTime = 300;
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(() => {
type = 'all';
// Search with reranking on 'Enter'
let should_rerank = event.key === 'Enter';
search(rerank=should_rerank);
}, waitTime);
}
async function populate_type_dropdown() {
const hostURL = await window.hostURLAPI.getURL();
const khojToken = await window.tokenAPI.getToken();
const headers = { 'Authorization': `Bearer ${khojToken}` };
// Populate type dropdown field with enabled content types only
fetch(`${hostURL}/api/content/types`, { headers })
.then(response => response.json())
.then(enabled_types => {
// Show warning if no content types are enabled
if (enabled_types.detail) {
document.getElementById("results").innerHTML = "<div id='results-error'>To use Khoj search, setup your content plugins on the Khoj <a class='inline-chat-link' href='/settings'>settings page</a>.</div>";
document.getElementById("query").setAttribute("disabled", "disabled");
document.getElementById("query").setAttribute("placeholder", "Configure Khoj to enable search");
return [];
}
return enabled_types;
});
}
async function createRequestUrl(query, type, results_count, rerank) {
// Generate Backend API URL to execute Search
const hostURL = await window.hostURLAPI.getURL();
let url = `${hostURL}/api/search?q=${encodeURIComponent(query)}&n=${results_count}&client=web`;
// If type is not 'all', append type to URL
if (type !== 'all')
url += `&t=${type}`;
// Rerank is only supported by text types
if (type !== "image")
url += `&r=${rerank}`;
return url;
}
function setQueryFieldInUrl(query) {
var url = new URL(window.location.href);
url.searchParams.set("q", query);
window.history.pushState({}, "", url.href);
}
window.addEventListener("DOMContentLoaded", async() => {
// Setup the header pane
document.getElementById("khoj-header").innerHTML = await populateHeaderPane();
// Setup the nav menu
document.getElementById("profile-picture").addEventListener("click", toggleNavMenu);
// Set the active nav pane
document.getElementById("search-nav")?.classList.add("khoj-nav-selected");
})
window.addEventListener("load", async function() {
// Dynamically populate type dropdown based on enabled content types and type passed as URL query parameter
await populate_type_dropdown();
// Fill query field with value passed in URL query parameters, if any.
var query_via_url = new URLSearchParams(window.location.search).get("q");
if (query_via_url)
document.getElementById("query").value = query_via_url;
});
</script>
<body>
<!--Add Header Logo and Nav Pane-->
<div id="khoj-header" class="khoj-header"></div>
<!--Add Text Box To Enter Query, Trigger Incremental Search OnChange -->
<input type="text" id="query" class="option" onkeyup=incrementalSearch(event) autofocus="autofocus" placeholder="Search your knowledge base using natural language">
<!-- Section to Render Results -->
<div id="results"></div>
</body>
<style>
@media only screen and (max-width: 600px) {
body {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr auto auto auto minmax(80px, 100%);
font-size: small!important;
}
body > * {
grid-column: 1;
}
}
@media only screen and (min-width: 600px) {
body {
display: grid;
grid-template-columns: 1fr min(70vw, 100%) 1fr;
grid-template-rows: 1fr auto auto auto minmax(80px, 100%);
padding-top: 60vw;
}
body > * {
grid-column: 2;
}
}
body {
padding: 0px;
margin: 0px;
background: var(--background-color);
color: var(--main-text-color);
font-family: var(--font-family);
font-size: small;
font-weight: 300;
line-height: 1.5em;
}
body > * {
padding: 10px;
margin: 10px;
}
#options {
padding: 0;
display: grid;
grid-template-columns: 1fr;
}
#options > * {
padding: 15px;
border-radius: 5px;
border: 1px solid #475569;
background: #f9fafc
}
.option:hover {
box-shadow: 0 0 11px #aaa;
}
#options > button {
margin-right: 10px;
}
#query {
font-size: small;
}
#results {
font-size: small;
margin: 0px;
line-height: 20px;
}
.results-image {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.image-link {
place-self: center;
}
.image {
width: 20vw;
border-radius: 10px;
border: 1px solid #475569;
}
#json {
white-space: pre-wrap;
}
.results-pdf,
.results-notion,
.results-html,
.results-plugin {
text-align: left;
white-space: pre-line;
}
.results-markdown,
.results-github {
text-align: left;
}
.results-org {
text-align: left;
/* white-space: pre-line; */
}
.results-org h3 {
margin: 20px 0 0 0;
font-size: small;
}
span.org-task-status {
color: white;
padding: 3.5px 3.5px 0;
margin-right: 5px;
border-radius: 5px;
background-color: #eab308;
font-size: small;
}
span.org-task-status.todo {
background-color: #3b82f6
}
span.org-task-status.done {
background-color: #22c55e;
}
span.org-task-tag {
color: white;
padding: 3.5px 3.5px 0;
margin-right: 5px;
border-radius: 5px;
border: 1px solid #475569;
background-color: #ef4444;
font-size: small;
}
pre {
max-width: 100;
}
a {
color: #3b82f6;
text-decoration: none;
}
img.avatar {
width: 20px;
height: 20px;
border-radius: 50%;
}
div#results-error,
div.results-markdown,
div.results-notion,
div.results-org,
div.results-plugin,
div.results-html,
div.results-pdf {
text-align: left;
box-shadow: 2px 2px 2px var(--primary-hover);
border-radius: 5px;
padding: 10px;
margin: 10px 0;
border: 4px solid rgb(229, 229, 229);
}
div#results-error {
box-shadow: 2px 2px 2px #FF5722;
}
img {
max-width: 90%;
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
a.khoj-logo {
text-align: center;
}
</style>
</html>

View file

@ -1,16 +1,264 @@
<html> <!DOCTYPE html>
<head> <html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
<title>Khoj - Settings</title>
<link rel="icon" type="image/png" sizes="128x128" href="./assets/icons/favicon-128x128.png"> <head>
<link rel="manifest" href="./khoj.webmanifest"> <meta charset="UTF-8">
<link rel="stylesheet" href="./assets/khoj.css"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head> <title>Khoj - Settings</title>
<link rel="icon" type="image/png" sizes="128x128" href="./assets/icons/favicon-128x128.png">
<link rel="stylesheet" href="./assets/khoj.css">
<style>
:root {
--background-color: #f9fafb;
--primary-color: hsla(24.6 95% 53.1%);
--secondary-color: #f3f4f6;
--text-color: #111827;
--card-bg: #ffffff;
--card-border: #e5e7eb;
}
body {
margin: 0;
padding: 0;
font-family: var(--font-family);
background-color: var(--background-color);
color: var(--text-color);
display: grid;
grid-template-columns: 1fr;
height: 100vh;
}
/* Main Content */
.main-content {
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
}
.header {
font-size: 1.5rem;
font-weight: bold;
text-align: left;
margin-bottom: 20px;
}
.section-cards {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
}
.card {
background-color: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 12px;
padding: 20px;
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.15);
}
.card-title {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: 10px;
}
.card-input {
width: -webkit-fill-available;
padding: 10px;
border: 1px solid var(--card-border);
border-radius: 8px;
font-size: 1rem;
}
input,
button {
font-family: var(--font-family);
}
.card-button {
background-color: var(--primary-color);
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s ease;
}
.card-button:hover {
background-color: hsla(24.6 95% 53.1% / 0.5);
}
.secondary-button {
background-color: var(--secondary-color);
color: var(--text-color);
border: none;
padding: 10px 20px;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s ease;
}
.secondary-button:hover {
background-color: #e5e7eb;
}
.sync-data {
display: flex;
justify-content: left;
align-items: baseline;
gap: 10px;
flex-direction: column;
}
div.folder-element,
div.file-element {
display: flex;
justify-content: space-between;
align-items: center;
}
img.file-synced-image {
width: 16px;
height: 16px;
}
div.remove-button-container {
display: flex;
flex-direction: row;
justify-content: right;
align-items: center;
gap: 12px;
}
button.remove-folder-button,
button.remove-file-button {
background-color: transparent;
border: none;
cursor: pointer;
border-radius: 8px;
}
button.remove-folder-button:hover,
button.remove-file-button:hover {
background-color: #f3f4f6;
}
div#sync-status {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
}
div#big-actions {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
}
div#big-actions button {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.content-name:hover {
text-decoration: underline;
color: var(--primary-color);
}
.sync-icon,
.clock-icon,
.trash-icon {
width: 16px;
height: 16px;
}
#loading-bar {
width: 100%;
height: 4px;
background: linear-gradient(90deg,
hsla(24.6 95% 53.1% / 0.2) 0%,
var(--primary-color) 50%,
hsla(24.6 95% 53.1% / 0.2) 100%);
border-radius: 2px;
background-size: 200% 100%;
animation: loading 1.5s infinite;
margin: 10px 0;
transition: opacity 0.3s ease;
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
#loading-bar[style*="display: none"] {
opacity: 0;
}
#loading-bar {
opacity: 1;
}
details.collapsible {
background-color: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 12px;
padding: 20px;
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
details.collapsible:hover {
transform: translateY(-5px);
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.15);
}
details.collapsible summary {
font-size: 1.2rem;
font-weight: 600;
cursor: pointer;
list-style: none;
display: flex;
align-items: center;
gap: 8px;
}
details.collapsible summary::after {
content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>');
font-size: 0.8em;
transition: transform 0.2s;
}
details.collapsible[open] summary::after {
transform: rotate(180deg);
}
details.collapsible .content {
margin-top: 10px;
}
</style>
<script src="./utils.js"></script> <script src="./utils.js"></script>
<script> <script>
window.addEventListener("DOMContentLoaded", async() => { window.addEventListener("DOMContentLoaded", async () => {
// Setup the header pane // Setup the header pane
document.getElementById("khoj-header").innerHTML = await populateHeaderPane(); document.getElementById("khoj-header").innerHTML = await populateHeaderPane();
// Setup the nav menu // Setup the nav menu
@ -19,358 +267,54 @@
document.getElementById("settings-nav")?.classList.add("khoj-nav-selected"); document.getElementById("settings-nav")?.classList.add("khoj-nav-selected");
}) })
</script> </script>
</head>
<body> <body>
<!--Add Header Logo and Nav Pane--> <!-- Main Content -->
<div class="main-content">
<div id="khoj-header" class="khoj-header"></div> <div id="khoj-header" class="khoj-header"></div>
<div class="section-cards"> <div class="section-cards">
<div class="card-description-row"> <!-- Replace the server URL and API key cards with: -->
<div class="card configuration"> <details class="collapsible">
<div class="card-title-row"> <summary>Server URL</summary>
<img class="card-icon" src="./assets/icons/link.svg" alt="Khoj Server URL"> <div class="content">
<h3 class="card-title"> <input type="text" class="card-input" id="khoj-host-url" placeholder="Enter server URL">
Server URL
</h3>
</div>
<div class="card-description-row">
<input id="khoj-host-url" class="card-input" type="text">
</div>
<div class="card-title-row">
<img class="card-icon" src="./assets/icons/key.svg" alt="Khoj Access Key">
<h3 class="card-title">
API Key
</h3>
</div>
<div class="card-description-row">
<input id="khoj-access-key" class="card-input" type="text" placeholder="Enter API key to access your Khoj">
</div>
</div> </div>
</details>
<details class="collapsible">
<summary>API Key</summary>
<div class="content">
<input type="text" class="card-input" id="khoj-access-key" placeholder="Enter API key">
</div>
</details>
<div class="card">
<div class="card-title">Files</div>
<div id="current-files"></div>
<button class="secondary-button" id="update-file" title="Add a file to be indexed to your Khoj knowledge base. Will be synced automatically.">Add File</button>
</div> </div>
<div class="card-description-row"> <div class="card">
<div class="card configuration"> <div class="card-title">Folders</div>
<div class="card-title-row"> <div id="current-folders"></div>
<img class="card-icon" src="./assets/icons/plaintext.svg" alt="File"> <button class="secondary-button" id="update-folder" title="Add a folder to be indexed to your Khoj knowledge base. All valid files will be synced automatically.">Add Folder</button>
<h3 class="card-title">
Files
<button id="toggle-files" class="card-button">
<svg id="toggle-files-svg" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12l7 7 7-7"></path></svg>
</button>
</h3>
</div>
<div class="card-description-row">
<div id="current-files"></div>
</div>
<div class="card-action-row">
<button id="update-file" class="card-button">
Add
<img class="add-files-icon" src="./assets/icons/circular-add.svg" alt="Add">
</button>
</div>
</div>
</div> </div>
<div class="card-description-row"> <div class="sync-data">
<div class="card configuration"> <div id="loading-bar" style="display: none;"></div>
<div class="card-title-row">
<img class="card-icon" src="./assets/icons/folder.svg" alt="Folder">
<h3 class="card-title">
Folders
<button id="toggle-folders" class="card-button">
<svg id="toggle-folders-svg" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12l7 7 7-7"></path></svg>
</button>
</h3>
</div>
<div class="card-description-row">
<div id="current-folders"></div>
</div>
<div class="card-action-row">
<button id="update-folder" class="card-button">
Add
<img class="add-files-icon" src="./assets/icons/circular-add.svg" alt="Add">
</button>
</div>
</div>
</div>
<div class="section-action-row">
<div class="card-description-row">
<button id="sync-force" class="sync-data">💾 Save</button>
</div>
<div class="card-description-row">
<button id="delete-all" class="sync-data">🗑️ Delete All</button>
</div>
</div>
<div id="loading-bar" style="display: none;"></div>
<div class="card-description-row">
<div id="sync-status"></div> <div id="sync-status"></div>
<div id="big-actions">
<button class="card-button" id="sync-force" title="Delete and re-index all configured files and folders">
<img src="./assets/icons/upload.svg" class="sync-icon" alt="Sync">
Force Sync
</button>
<button class="card-button" id="delete-all" title="Remove all indexed content from Khoj">
<img src="./assets/icons/trash-solid.svg" class="trash-icon" alt="Delete All">
Delete All
</button>
</div>
</div> </div>
</div> </div>
</body> </div>
</body>
<style> <script src="./renderer.js"></script>
@media only screen and (max-width: 600px) {
body {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr auto;
font-size: small!important;
}
body > * {
grid-column: 1;
}
}
@media only screen and (min-width: 600px) {
body {
display: grid;
grid-template-columns: 1fr min(70vw, 100%) 1fr;
grid-template-rows: 80px auto;
}
body > * {
grid-column: 2;
}
}
body, input {
padding: 0px;
margin: 0px;
background: var(--background-color);
color: #475569;
font-family: var(--font-family);
font-size: small;
font-weight: 300;
line-height: 1.5em;
}
body > * {
padding: 10px;
margin: 10px;
}
svg {
transition: transform 0.3s ease-in-out;
}
a.khoj-logo {
text-align: center;
}
#loading-bar {
height: 10px;
width: 100%;
background-color: #ddd;
position: relative;
overflow: hidden;
}
#loading-bar:before {
content: "";
display: block;
position: absolute;
left: -200px;
width: 200px;
height: 100%;
background-color: #2980b9;
animation: loading-bar 2s linear infinite;
}
@keyframes loading-bar {
0% {
left: -200px;
}
100% {
left: 100%;
}
}
.card-input {
padding: 4px;
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.3);
border: none;
width: 450px;
}
.card {
display: grid;
gap: 8px;
padding: 24px 16px;
width: 450px;
background: var(--background-color);
border: 1px solid rgb(229, 229, 229);
border-radius: 4px;
box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.1),0px 1px 2px -1px rgba(0,0,0,0.1);
overflow: hidden;
}
.section-cards {
display: grid;
gap: 16px;
justify-items: center;
margin: 0;
}
.section-action-row {
display: grid;
grid-auto-flow: column;
gap: 16px;
height: fit-content;
}
.card-title-row {
display: grid;
grid-template-columns: auto 1fr;
padding: 0;
gap: 12px;
}
.card-icon {
width: 28px;
height: 28px;
}
.add-files-icon {
width: 16px;
height: 16px;
}
.card-title {
font-size: medium;
font-weight: normal;
margin: 0;
padding: 0;
align-self: center;
}
.card-title-text {
vertical-align: middle;
}
.card-description {
margin: 0;
color: grey;
font-size: small;
}
.card-button-row {
display: grid;
grid-template-columns: auto;
text-align: right;
}
.card-button {
border: none;
font-weight: bold;
color: rgb(64,64,64);
background: transparent;
font-size: small;
cursor: pointer;
margin: 0;
padding: 0;
height: 32px;
text-align: right;
}
.primary-button {
border: none;
color: var(--background-color);
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: small;
}
button.card-button.disabled {
color: var(--flower);
background: transparent;
font-size: small;
cursor: pointer;
margin: 0;
padding: 0;
height: 32px;
text-align: right;
text-align: left;
}
button.card-button.happy {
color: var(--leaf);
}
img.configured-icon {
max-width: 16px;
}
div.card-action-row.enabled{
display: block;
}
img.configured-icon.enabled {
display: inline;
}
div.card-action-row.disabled,
img.configured-icon.disabled {
display: none;
}
div.file-element,
div.folder-element {
display: grid;
grid-template-columns: auto 1fr;
border: 1px solid rgb(229, 229, 229);
border-radius: 4px;
box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.1),0px 1px 2px -1px rgba(0,0,0,0.8);
padding: 4px;
margin-bottom: 8px;
}
div.content-name {
overflow-wrap: break-word;
}
div.remove-button-container {
text-align: right;
display: flex;
margin-left: auto;
gap: 8px;
}
.file-synced-image{
width: 20px;
display: none;
}
button.remove-folder-button,
button.remove-file-button {
background-color: rgb(253 214 214);
border-radius: 3px;
border: none;
color: var(--flower);
padding: 4px;
}
button.remove-folder-button:hover,
button.remove-file-button:hover {
background-color: rgb(255 235 235);
border-radius: 3px;
border: none;
color: var(--flower);
padding: 4px;
cursor: pointer;
}
button.sync-data {
background-color: var(--primary-hover);
border: none;
color: var(--main-text-color);
padding: 12px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease;
box-shadow: 0px 5px 0px var(--background-color);
}
button.sync-data:hover {
background-color: var(--summer-sun);
box-shadow: 0px 3px 0px var(--background-color);
cursor: pointer;
}
.sync-force-toggle {
align-content: center;
display: grid;
grid-auto-flow: column;
gap: 4px;
}
</style>
<script src="./renderer.js"></script>
</html> </html>

View file

@ -31,7 +31,7 @@ function toggleNavMenu() {
} }
// Close the dropdown menu if the user clicks outside of it // Close the dropdown menu if the user clicks outside of it
document.addEventListener('click', function(event) { document.addEventListener('click', function (event) {
let menu = document.getElementById("khoj-nav-menu"); let menu = document.getElementById("khoj-nav-menu");
let menuContainer = document.getElementById("khoj-nav-menu-container"); let menuContainer = document.getElementById("khoj-nav-menu-container");
let isClickOnMenu = menuContainer?.contains(event.target) || menuContainer === event.target; let isClickOnMenu = menuContainer?.contains(event.target) || menuContainer === event.target;
@ -56,25 +56,19 @@ async function populateHeaderPane() {
// Populate the header element with the navigation pane // Populate the header element with the navigation pane
return ` return `
<a class="khoj-logo" href="/"> <a class="khoj-logo" href="/">
<img class="khoj-logo" src="./assets/icons/khoj-logo-sideways-500.png" alt="Khoj"></img> <img class="khoj-logo" src="./assets/icons/khoj_logo.png" alt="Khoj"></img>
</a> </a>
<nav class="khoj-nav"> <nav class="khoj-nav">
${ ${userInfo && userInfo.email
userInfo && userInfo.email ? `<div class="khoj-status-box">
? `<div class="khoj-status-box">
<span class="khoj-status-connected"></span> <span class="khoj-status-connected"></span>
<span class="khoj-status-text">Connected to server</span> <span class="khoj-status-text">Connected to server</span>
</div>` </div>`
: `<div class="khoj-status-box"> : `<div class="khoj-status-box">
<span class="khoj-status-not-connected"></span> <span class="khoj-status-not-connected"></span>
<span class="khoj-status-text">Not connected to server</span> <span class="khoj-status-text">Not connected to server</span>
</div>` </div>`
} }
<a id="chat-nav" class="khoj-nav" href="./chat.html">
<img class="nav-icon" src="./assets/icons/chat.svg" alt="Chat">
<span class="khoj-nav-item-text">Chat</span>
</a>
${has_documents ? '<a id="search-nav" class="khoj-nav" href="./search.html"><img class="nav-icon" src="./assets/icons/search.svg" alt="Search"> <span class="khoj-nav-item-text">Search</span></a>' : ''}
${username ? ` ${username ? `
<div id="khoj-nav-menu-container" class="khoj-nav dropdown"> <div id="khoj-nav-menu-container" class="khoj-nav dropdown">
${user_photo && user_photo != "None" ? ` ${user_photo && user_photo != "None" ? `
@ -84,7 +78,10 @@ async function populateHeaderPane() {
`} `}
<div id="khoj-nav-menu" class="khoj-nav-dropdown-content"> <div id="khoj-nav-menu" class="khoj-nav-dropdown-content">
<div class="khoj-nav-username"> ${username} </div> <div class="khoj-nav-username"> ${username} </div>
<a id="settings-nav" class="khoj-nav" href="./settings.html"> Settings</a> <a onclick="window.navigateAPI.navigateToWebHome()" class="khoj-nav-link">
<img class="khoj-nav-icon" src="./assets/icons/open-link.svg" alt="Open Host Url"></img>
Open App
</a>
</div> </div>
</div> </div>
` : ''} ` : ''}

View file

@ -50,17 +50,17 @@
dependencies: dependencies:
defer-to-connect "^2.0.0" defer-to-connect "^2.0.0"
"@todesktop/runtime@^1.6.4": "@todesktop/runtime@^2.0.0":
version "1.6.4" version "2.0.0"
resolved "https://registry.yarnpkg.com/@todesktop/runtime/-/runtime-1.6.4.tgz#a9d62a021cf2647c51371c892bfb1d4c5a29ed7e" resolved "https://registry.yarnpkg.com/@todesktop/runtime/-/runtime-2.0.0.tgz#dfd409186ae664f5e28186a03b99e620ec7b7f82"
integrity sha512-n6dOxhrKKsXMM+i2u9iRvoJSR2KCWw0orYK+FT9RbWNPykhuFIYd0yy8dYgYy/OuClKGyGl4SJFi2757FLhWDA== integrity sha512-0a2tmWpIc/HJE/873xRMZKQNggfrYhoKYIchfN+k8RqKdzTPwTWa5ztur7GdCHLHBUaiMBPNRzF3h4kwHd1NCw==
dependencies: dependencies:
del "^6.0.0" del "^6.1.1"
electron-updater "^4.6.1" electron-updater "^6.3.9"
eventemitter2 "^6.4.5" eventemitter2 "^6.4.9"
execa "^5.0.0" execa "^5.0.0"
lodash.once "^4.1.1" lodash.once "^4.1.1"
semver "^7.3.2" semver "^7.6.3"
"@types/cacheable-request@^6.0.1": "@types/cacheable-request@^6.0.1":
version "6.0.3" version "6.0.3"
@ -90,16 +90,16 @@
integrity sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ== integrity sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==
"@types/node@*": "@types/node@*":
version "22.9.1" version "22.10.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.9.1.tgz#bdf91c36e0e7ecfb7257b2d75bf1b206b308ca71" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.1.tgz#41ffeee127b8975a05f8c4f83fb89bcb2987d766"
integrity sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg== integrity sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==
dependencies: dependencies:
undici-types "~6.19.8" undici-types "~6.20.0"
"@types/node@^18.11.18": "@types/node@^18.11.18":
version "18.19.64" version "18.19.67"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.64.tgz#122897fb79f2a9ec9c979bded01c11461b2b1478" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.67.tgz#77c4b01641a1e3e1509aff7e10d39e4afd5ae06d"
integrity sha512-955mDqvO2vFf/oL7V3WiUtiz+BugyX8uVbaT2H8oj3+8dRyH2FLiNdowe7eNqRM7IOIZvzDH76EoAT+gwm6aIQ== integrity sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==
dependencies: dependencies:
undici-types "~5.26.4" undici-types "~5.26.4"
@ -110,11 +110,6 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/semver@^7.3.6":
version "7.5.8"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"
integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==
"@types/yauzl@^2.9.1": "@types/yauzl@^2.9.1":
version "2.10.3" version "2.10.3"
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999"
@ -168,9 +163,9 @@ atomically@^1.7.0:
integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w== integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==
axios@^1.7.4: axios@^1.7.4:
version "1.7.7" version "1.7.8"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.8.tgz#1997b1496b394c21953e68c14aaa51b7b5de3d6e"
integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== integrity sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==
dependencies: dependencies:
follow-redirects "^1.15.6" follow-redirects "^1.15.6"
form-data "^4.0.0" form-data "^4.0.0"
@ -206,12 +201,12 @@ buffer-crc32@~0.2.3:
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
builder-util-runtime@8.9.2: builder-util-runtime@9.2.10:
version "8.9.2" version "9.2.10"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.9.2.tgz#a9669ae5b5dcabfe411ded26678e7ae997246c28" resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.10.tgz#a0f7d9e214158402e78b74a745c8d9f870c604bc"
integrity sha512-rhuKm5vh7E0aAmT6i8aoSfEjxzdYEFX7zDApK+eNgOhjofnWb74d9SRJv0H/8nsgOkos0TZ4zxW0P8J4N7xQ2A== integrity sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==
dependencies: dependencies:
debug "^4.3.2" debug "^4.3.4"
sax "^1.2.4" sax "^1.2.4"
cacheable-lookup@^5.0.3: cacheable-lookup@^5.0.3:
@ -296,7 +291,7 @@ debounce-fn@^4.0.0:
dependencies: dependencies:
mimic-fn "^3.0.0" mimic-fn "^3.0.0"
debug@^4.1.0, debug@^4.1.1, debug@^4.3.2: debug@^4.1.0, debug@^4.1.1, debug@^4.3.4:
version "4.3.7" version "4.3.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
@ -333,7 +328,7 @@ define-properties@^1.2.1:
has-property-descriptors "^1.0.0" has-property-descriptors "^1.0.0"
object-keys "^1.1.1" object-keys "^1.1.1"
del@^6.0.0: del@^6.1.1:
version "6.1.1" version "6.1.1"
resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a"
integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg== integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==
@ -379,19 +374,19 @@ electron-store@^8.1.0:
conf "^10.2.0" conf "^10.2.0"
type-fest "^2.17.0" type-fest "^2.17.0"
electron-updater@^4.6.1: electron-updater@^6.3.9:
version "4.6.5" version "6.3.9"
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-4.6.5.tgz#e9a75458bbfd6bb41a58a829839e150ad2eb2d3d" resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.3.9.tgz#e1e7f155624c58e6f3760f376c3a584028165ec4"
integrity sha512-kdTly8O9mSZfm9fslc1mnCY+mYOeaYRy7ERa2Fed240u01BKll3aiupzkd07qKw69KvhBSzuHroIW3mF0D8DWA== integrity sha512-2PJNONi+iBidkoC5D1nzT9XqsE8Q1X28Fn6xRQhO3YX8qRRyJ3mkV4F1aQsuRnYPqq6Hw+E51y27W75WgDoofw==
dependencies: dependencies:
"@types/semver" "^7.3.6" builder-util-runtime "9.2.10"
builder-util-runtime "8.9.2" fs-extra "^10.1.0"
fs-extra "^10.0.0"
js-yaml "^4.1.0" js-yaml "^4.1.0"
lazy-val "^1.0.5" lazy-val "^1.0.5"
lodash.escaperegexp "^4.1.2" lodash.escaperegexp "^4.1.2"
lodash.isequal "^4.5.0" lodash.isequal "^4.5.0"
semver "^7.3.5" semver "^7.6.3"
tiny-typed-emitter "^2.1.0"
electron@28.2.1: electron@28.2.1:
version "28.2.1" version "28.2.1"
@ -436,7 +431,7 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
eventemitter2@^6.4.5: eventemitter2@^6.4.9:
version "6.4.9" version "6.4.9"
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125" resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125"
integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg== integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==
@ -530,7 +525,7 @@ form-data@^4.0.0:
combined-stream "^1.0.8" combined-stream "^1.0.8"
mime-types "^2.1.12" mime-types "^2.1.12"
fs-extra@^10.0.0: fs-extra@^10.1.0:
version "10.1.0" version "10.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
@ -1115,7 +1110,7 @@ semver@^6.2.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.3.2, semver@^7.3.5: semver@^7.3.2, semver@^7.3.5, semver@^7.6.3:
version "7.6.3" version "7.6.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
@ -1166,6 +1161,11 @@ sumchecker@^3.0.1:
dependencies: dependencies:
debug "^4.1.0" debug "^4.1.0"
tiny-typed-emitter@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz#b3b027fdd389ff81a152c8e847ee2f5be9fad7b5"
integrity sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==
to-regex-range@^5.0.1: to-regex-range@^5.0.1:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@ -1188,10 +1188,10 @@ undici-types@~5.26.4:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
undici-types@~6.19.8: undici-types@~6.20.0:
version "6.19.8" version "6.20.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
universalify@^0.1.0: universalify@^0.1.0:
version "0.1.2" version "0.1.2"

View file

@ -6,7 +6,7 @@
;; Saba Imran <saba@khoj.dev> ;; Saba Imran <saba@khoj.dev>
;; Description: Your Second Brain ;; Description: Your Second Brain
;; Keywords: search, chat, ai, org-mode, outlines, markdown, pdf, image ;; Keywords: search, chat, ai, org-mode, outlines, markdown, pdf, image
;; Version: 1.30.1 ;; Version: 1.31.0
;; Package-Requires: ((emacs "27.1") (transient "0.3.0") (dash "2.19.1")) ;; 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 ;; URL: https://github.com/khoj-ai/khoj/tree/master/src/interface/emacs
@ -434,9 +434,9 @@ Auto invokes setup steps on calling main entrypoint."
Append 'TYPE-QUERY' as query parameter in request url. Append 'TYPE-QUERY' as query parameter in request url.
Specify `BOUNDARY' used to separate files in request header." Specify `BOUNDARY' used to separate files in request header."
(let ((url-request-method (if force "PUT" "PATCH")) (let ((url-request-method (if force "PUT" "PATCH"))
(url-request-data body) (url-request-data (encode-coding-string body 'utf-8))
(url-request-extra-headers `(("content-type" . ,(format "multipart/form-data; boundary=%s" boundary)) (url-request-extra-headers `(("content-type" . ,(format "multipart/form-data; boundary=%s" boundary))
("Authorization" . ,(format "Bearer %s" khoj-api-key))))) ("Authorization" . ,(encode-coding-string (format "Bearer %s" khoj-api-key) 'utf-8)))))
(with-current-buffer (with-current-buffer
(url-retrieve (format "%s/api/content?%s&client=emacs" khoj-server-url type-query) (url-retrieve (format "%s/api/content?%s&client=emacs" khoj-server-url type-query)
;; render response from indexing API endpoint on server ;; render response from indexing API endpoint on server
@ -668,9 +668,9 @@ Simplified fork of `org-cycle-content' from Emacs 29.1 to work with >=27.1."
"Sync call API at PATH with METHOD, query PARAMS and BODY as kv assoc list. "Sync call API at PATH with METHOD, query PARAMS and BODY as kv assoc list.
Optionally apply CALLBACK with JSON parsed response and CBARGS." Optionally apply CALLBACK with JSON parsed response and CBARGS."
(let* ((url-request-method (or method "GET")) (let* ((url-request-method (or method "GET"))
(url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key)))) (url-request-extra-headers `(("Authorization" . ,(encode-coding-string (format "Bearer %s" khoj-api-key) 'utf-8))
(url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key)) ("Content-Type" . "application/json"))) ("Content-Type" . "application/json")))
(url-request-data (if body (json-encode body) nil)) (url-request-data (if body (encode-coding-string (json-encode body) 'utf-8) nil))
(param-string (url-build-query-string (append params '((client "emacs"))))) (param-string (url-build-query-string (append params '((client "emacs")))))
(query-url (format "%s%s?%s" khoj-server-url path param-string)) (query-url (format "%s%s?%s" khoj-server-url path param-string))
(cbargs (if (and (listp cbargs) (listp (car cbargs))) (car cbargs) cbargs))) ; normalize cbargs to (a b) from ((a b)) if required (cbargs (if (and (listp cbargs) (listp (car cbargs))) (car cbargs) cbargs))) ; normalize cbargs to (a b) from ((a b)) if required
@ -689,8 +689,9 @@ Optionally apply CALLBACK with JSON parsed response and CBARGS."
"Async call to API at PATH with specified METHOD, query PARAMS and request BODY. "Async call to API at PATH with specified METHOD, query PARAMS and request BODY.
Optionally apply CALLBACK with JSON parsed response and CBARGS." Optionally apply CALLBACK with JSON parsed response and CBARGS."
(let* ((url-request-method (or method "GET")) (let* ((url-request-method (or method "GET"))
(url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key)) ("Content-Type" . "application/json"))) (url-request-extra-headers `(("Authorization" . ,(encode-coding-string (format "Bearer %s" khoj-api-key) 'utf-8))
(url-request-data (if body (json-encode body) nil)) ("Content-Type" . "application/json")))
(url-request-data (if body (encode-coding-string (json-encode body) 'utf-8) nil))
(param-string (url-build-query-string (append params '((client "emacs"))))) (param-string (url-build-query-string (append params '((client "emacs")))))
(query-url (format "%s%s?%s" khoj-server-url path param-string)) (query-url (format "%s%s?%s" khoj-server-url path param-string))
(cbargs (if (and (listp cbargs) (listp (car cbargs))) (car cbargs) cbargs))) ; normalize cbargs to (a b) from ((a b)) if required (cbargs (if (and (listp cbargs) (listp (car cbargs))) (car cbargs) cbargs))) ; normalize cbargs to (a b) from ((a b)) if required
@ -716,7 +717,10 @@ Optionally apply CALLBACK with JSON parsed response and CBARGS."
Render search results in BUFFER-NAME using CONTENT-TYPE and QUERY. Render search results in BUFFER-NAME using CONTENT-TYPE and QUERY.
Filter out first similar result if IS-FIND-SIMILAR set." Filter out first similar result if IS-FIND-SIMILAR set."
(let* ((rerank (or rerank "false")) (let* ((rerank (or rerank "false"))
(params `((q ,query) (t ,content-type) (r ,rerank) (n ,khoj-results-count))) (params `((q ,(encode-coding-string query 'utf-8))
(t ,content-type)
(r ,rerank)
(n ,khoj-results-count)))
(path "/api/search")) (path "/api/search"))
(khoj--call-api-async path (khoj--call-api-async path
"GET" "GET"

View file

@ -1,7 +1,7 @@
{ {
"id": "khoj", "id": "khoj",
"name": "Khoj", "name": "Khoj",
"version": "1.30.1", "version": "1.31.0",
"minAppVersion": "0.15.0", "minAppVersion": "0.15.0",
"description": "Your Second Brain", "description": "Your Second Brain",
"author": "Khoj Inc.", "author": "Khoj Inc.",

View file

@ -1,6 +1,6 @@
{ {
"name": "Khoj", "name": "Khoj",
"version": "1.30.1", "version": "1.31.0",
"description": "Your Second Brain", "description": "Your Second Brain",
"author": "Debanjum Singh Solanky, Saba Imran <team@khoj.dev>", "author": "Debanjum Singh Solanky, Saba Imran <team@khoj.dev>",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",

View file

@ -1,4 +1,4 @@
import {ItemView, MarkdownRenderer, Scope, WorkspaceLeaf, request, requestUrl, setIcon, Platform} from 'obsidian'; import { ItemView, MarkdownRenderer, Scope, WorkspaceLeaf, request, requestUrl, setIcon, Platform } from 'obsidian';
import * as DOMPurify from 'dompurify'; import * as DOMPurify from 'dompurify';
import { KhojSetting } from 'src/settings'; import { KhojSetting } from 'src/settings';
import { KhojPaneView } from 'src/pane_view'; import { KhojPaneView } from 'src/pane_view';
@ -27,6 +27,7 @@ interface ChatMessageState {
newResponseEl: HTMLElement | null; newResponseEl: HTMLElement | null;
loadingEllipsis: HTMLElement | null; loadingEllipsis: HTMLElement | null;
references: any; references: any;
generatedAssets: string;
rawResponse: string; rawResponse: string;
rawQuery: string; rawQuery: string;
isVoice: boolean; isVoice: boolean;
@ -46,10 +47,10 @@ export class KhojChatView extends KhojPaneView {
waitingForLocation: boolean; waitingForLocation: boolean;
location: Location = { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone }; location: Location = { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone };
keyPressTimeout: NodeJS.Timeout | null = null; keyPressTimeout: NodeJS.Timeout | null = null;
userMessages: string[] = []; // Store user sent messages for input history cycling userMessages: string[] = []; // Store user sent messages for input history cycling
currentMessageIndex: number = -1; // Track current message index in userMessages array currentMessageIndex: number = -1; // Track current message index in userMessages array
private currentUserInput: string = ""; // Stores the current user input that is being typed in chat private currentUserInput: string = ""; // Stores the current user input that is being typed in chat
private startingMessage: string = "Message"; private startingMessage: string = "Message";
chatMessageState: ChatMessageState; chatMessageState: ChatMessageState;
constructor(leaf: WorkspaceLeaf, setting: KhojSetting) { constructor(leaf: WorkspaceLeaf, setting: KhojSetting) {
@ -102,14 +103,14 @@ export class KhojChatView extends KhojPaneView {
// Clear text after extracting message to send // Clear text after extracting message to send
let user_message = input_el.value.trim(); let user_message = input_el.value.trim();
// Store the message in the array if it's not empty // Store the message in the array if it's not empty
if (user_message) { if (user_message) {
this.userMessages.push(user_message); this.userMessages.push(user_message);
// Update starting message after sending a new message // Update starting message after sending a new message
const modifierKey = Platform.isMacOS ? '⌘' : '^'; const modifierKey = Platform.isMacOS ? '⌘' : '^';
this.startingMessage = `(${modifierKey}+↑/↓) for prev messages`; this.startingMessage = `(${modifierKey}+↑/↓) for prev messages`;
input_el.placeholder = this.startingMessage; input_el.placeholder = this.startingMessage;
} }
input_el.value = ""; input_el.value = "";
this.autoResize(); this.autoResize();
@ -162,9 +163,9 @@ export class KhojChatView extends KhojPaneView {
}) })
chatInput.addEventListener('input', (_) => { this.onChatInput() }); chatInput.addEventListener('input', (_) => { this.onChatInput() });
chatInput.addEventListener('keydown', (event) => { chatInput.addEventListener('keydown', (event) => {
this.incrementalChat(event); this.incrementalChat(event);
this.handleArrowKeys(event); this.handleArrowKeys(event);
}); });
// Add event listeners for long press keybinding // Add event listeners for long press keybinding
this.contentEl.addEventListener('keydown', this.handleKeyDown.bind(this)); this.contentEl.addEventListener('keydown', this.handleKeyDown.bind(this));
@ -199,7 +200,7 @@ export class KhojChatView extends KhojPaneView {
// Get chat history from Khoj backend and set chat input state // Get chat history from Khoj backend and set chat input state
let getChatHistorySucessfully = await this.getChatHistory(chatBodyEl); let getChatHistorySucessfully = await this.getChatHistory(chatBodyEl);
let placeholderText : string = getChatHistorySucessfully ? this.startingMessage : "Configure Khoj to enable chat"; let placeholderText: string = getChatHistorySucessfully ? this.startingMessage : "Configure Khoj to enable chat";
chatInput.placeholder = placeholderText; chatInput.placeholder = placeholderText;
chatInput.disabled = !getChatHistorySucessfully; chatInput.disabled = !getChatHistorySucessfully;
@ -214,7 +215,7 @@ export class KhojChatView extends KhojPaneView {
}); });
} }
startSpeechToText(event: KeyboardEvent | MouseEvent | TouchEvent, timeout=200) { startSpeechToText(event: KeyboardEvent | MouseEvent | TouchEvent, timeout = 200) {
if (!this.keyPressTimeout) { if (!this.keyPressTimeout) {
this.keyPressTimeout = setTimeout(async () => { this.keyPressTimeout = setTimeout(async () => {
// Reset auto send voice message timer, UI if running // Reset auto send voice message timer, UI if running
@ -320,7 +321,7 @@ export class KhojChatView extends KhojPaneView {
referenceButton.tabIndex = 0; referenceButton.tabIndex = 0;
// Add event listener to toggle full reference on click // Add event listener to toggle full reference on click
referenceButton.addEventListener('click', function() { referenceButton.addEventListener('click', function () {
if (this.classList.contains("collapsed")) { if (this.classList.contains("collapsed")) {
this.classList.remove("collapsed"); this.classList.remove("collapsed");
this.classList.add("expanded"); this.classList.add("expanded");
@ -375,7 +376,7 @@ export class KhojChatView extends KhojPaneView {
referenceButton.tabIndex = 0; referenceButton.tabIndex = 0;
// Add event listener to toggle full reference on click // Add event listener to toggle full reference on click
referenceButton.addEventListener('click', function() { referenceButton.addEventListener('click', function () {
if (this.classList.contains("collapsed")) { if (this.classList.contains("collapsed")) {
this.classList.remove("collapsed"); this.classList.remove("collapsed");
this.classList.add("expanded"); this.classList.add("expanded");
@ -420,23 +421,23 @@ export class KhojChatView extends KhojPaneView {
"Authorization": `Bearer ${this.setting.khojApiKey}`, "Authorization": `Bearer ${this.setting.khojApiKey}`,
}, },
}) })
.then(response => response.arrayBuffer()) .then(response => response.arrayBuffer())
.then(arrayBuffer => context.decodeAudioData(arrayBuffer)) .then(arrayBuffer => context.decodeAudioData(arrayBuffer))
.then(audioBuffer => { .then(audioBuffer => {
const source = context.createBufferSource(); const source = context.createBufferSource();
source.buffer = audioBuffer; source.buffer = audioBuffer;
source.connect(context.destination); source.connect(context.destination);
source.start(0); source.start(0);
source.onended = function() { source.onended = function () {
speechButton.removeChild(loader);
speechButton.disabled = false;
};
})
.catch(err => {
console.error("Error playing speech:", err);
speechButton.removeChild(loader); speechButton.removeChild(loader);
speechButton.disabled = false; speechButton.disabled = false; // Consider enabling the button again to allow retrying
}; });
})
.catch(err => {
console.error("Error playing speech:", err);
speechButton.removeChild(loader);
speechButton.disabled = false; // Consider enabling the button again to allow retrying
});
} }
formatHTMLMessage(message: string, raw = false, willReplace = true) { formatHTMLMessage(message: string, raw = false, willReplace = true) {
@ -463,7 +464,7 @@ export class KhojChatView extends KhojPaneView {
let virtualChatMessageBodyTextEl = document.createElement("div"); let virtualChatMessageBodyTextEl = document.createElement("div");
// Convert the message to html // Convert the message to html
MarkdownRenderer.renderMarkdown(markdownText, virtualChatMessageBodyTextEl, '', component); MarkdownRenderer.render(this.app, markdownText, virtualChatMessageBodyTextEl, '', component);
// Remove image HTML elements with any non whitelisted src prefix // Remove image HTML elements with any non whitelisted src prefix
virtualChatMessageBodyTextEl.innerHTML = virtualChatMessageBodyTextEl.innerHTML.replace( virtualChatMessageBodyTextEl.innerHTML = virtualChatMessageBodyTextEl.innerHTML.replace(
@ -485,12 +486,18 @@ export class KhojChatView extends KhojPaneView {
intentType?: string, intentType?: string,
inferredQueries?: string[], inferredQueries?: string[],
conversationId?: string, conversationId?: string,
images?: string[],
excalidrawDiagram?: string
) { ) {
if (!message) return; if (!message) return;
let chatMessageEl; let chatMessageEl;
if (intentType?.includes("text-to-image") || intentType === "excalidraw") { if (
let imageMarkdown = this.generateImageMarkdown(message, intentType, inferredQueries, conversationId); intentType?.includes("text-to-image") ||
intentType === "excalidraw" ||
(images && images.length > 0) ||
excalidrawDiagram) {
let imageMarkdown = this.generateImageMarkdown(message, intentType ?? "", inferredQueries, conversationId, images, excalidrawDiagram);
chatMessageEl = this.renderMessage(chatEl, imageMarkdown, sender, dt); chatMessageEl = this.renderMessage(chatEl, imageMarkdown, sender, dt);
} else { } else {
chatMessageEl = this.renderMessage(chatEl, message, sender, dt); chatMessageEl = this.renderMessage(chatEl, message, sender, dt);
@ -510,7 +517,7 @@ export class KhojChatView extends KhojPaneView {
chatMessageBodyEl.appendChild(this.createReferenceSection(references)); chatMessageBodyEl.appendChild(this.createReferenceSection(references));
} }
generateImageMarkdown(message: string, intentType: string, inferredQueries?: string[], conversationId?: string): string { generateImageMarkdown(message: string, intentType: string, inferredQueries?: string[], conversationId?: string, images?: string[], excalidrawDiagram?: string): string {
let imageMarkdown = ""; let imageMarkdown = "";
if (intentType === "text-to-image") { if (intentType === "text-to-image") {
imageMarkdown = `![](data:image/png;base64,${message})`; imageMarkdown = `![](data:image/png;base64,${message})`;
@ -518,12 +525,23 @@ export class KhojChatView extends KhojPaneView {
imageMarkdown = `![](${message})`; imageMarkdown = `![](${message})`;
} else if (intentType === "text-to-image-v3") { } else if (intentType === "text-to-image-v3") {
imageMarkdown = `![](data:image/webp;base64,${message})`; imageMarkdown = `![](data:image/webp;base64,${message})`;
} else if (intentType === "excalidraw") { } else if (intentType === "excalidraw" || excalidrawDiagram) {
const domain = this.setting.khojUrl.endsWith("/") ? this.setting.khojUrl : `${this.setting.khojUrl}/`; const domain = this.setting.khojUrl.endsWith("/") ? this.setting.khojUrl : `${this.setting.khojUrl}/`;
const redirectMessage = `Hey, I'm not ready to show you diagrams yet here. But you can view it in ${domain}chat?conversationId=${conversationId}`; const redirectMessage = `Hey, I'm not ready to show you diagrams yet here. But you can view it in ${domain}chat?conversationId=${conversationId}`;
imageMarkdown = redirectMessage; imageMarkdown = redirectMessage;
} else if (images && images.length > 0) {
for (let image of images) {
if (image.startsWith("https://")) {
imageMarkdown += `![](${image})\n\n`;
} else {
imageMarkdown += `![](data:image/png;base64,${image})\n\n`;
}
}
imageMarkdown += `${message}`;
} }
if (inferredQueries) {
if (images?.length === 0 && inferredQueries) {
imageMarkdown += "\n\n**Inferred Query**:"; imageMarkdown += "\n\n**Inferred Query**:";
for (let inferredQuery of inferredQueries) { for (let inferredQuery of inferredQueries) {
imageMarkdown += `\n\n${inferredQuery}`; imageMarkdown += `\n\n${inferredQuery}`;
@ -534,13 +552,12 @@ export class KhojChatView extends KhojPaneView {
renderMessage(chatBodyEl: Element, message: string, sender: string, dt?: Date, raw: boolean = false, willReplace: boolean = true): Element { renderMessage(chatBodyEl: Element, message: string, sender: string, dt?: Date, raw: boolean = false, willReplace: boolean = true): Element {
let message_time = this.formatDate(dt ?? new Date()); let message_time = this.formatDate(dt ?? new Date());
let emojified_sender = sender == "khoj" ? "🏮 Khoj" : "🤔 You";
// Append message to conversation history HTML element. // Append message to conversation history HTML element.
// The chat logs should display above the message input box to follow standard UI semantics // The chat logs should display above the message input box to follow standard UI semantics
let chatMessageEl = chatBodyEl.createDiv({ let chatMessageEl = chatBodyEl.createDiv({
attr: { attr: {
"data-meta": `${emojified_sender} at ${message_time}`, "data-meta": message_time,
class: `khoj-chat-message ${sender}` class: `khoj-chat-message ${sender}`
}, },
}) })
@ -580,7 +597,7 @@ export class KhojChatView extends KhojPaneView {
let chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0]; let chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
let chatMessageEl = chatBodyEl.createDiv({ let chatMessageEl = chatBodyEl.createDiv({
attr: { attr: {
"data-meta": `🏮 Khoj at ${messageTime}`, "data-meta": messageTime,
class: `khoj-chat-message khoj` class: `khoj-chat-message khoj`
}, },
}) })
@ -650,26 +667,26 @@ export class KhojChatView extends KhojPaneView {
chatBodyEl.innerHTML = ""; chatBodyEl.innerHTML = "";
chatBodyEl.dataset.conversationId = ""; chatBodyEl.dataset.conversationId = "";
chatBodyEl.dataset.conversationTitle = ""; chatBodyEl.dataset.conversationTitle = "";
this.userMessages = []; this.userMessages = [];
this.startingMessage = "Message"; this.startingMessage = "Message";
// Update the placeholder of the chat input // Update the placeholder of the chat input
const chatInput = this.contentEl.querySelector('.khoj-chat-input') as HTMLTextAreaElement; const chatInput = this.contentEl.querySelector('.khoj-chat-input') as HTMLTextAreaElement;
if (chatInput) { if (chatInput) {
chatInput.placeholder = this.startingMessage; chatInput.placeholder = this.startingMessage;
} }
this.renderMessage(chatBodyEl, "Hey 👋🏾, what's up?", "khoj"); this.renderMessage(chatBodyEl, "Hey 👋🏾, what's up?", "khoj");
} }
async toggleChatSessions(forceShow: boolean = false): Promise<boolean> { async toggleChatSessions(forceShow: boolean = false): Promise<boolean> {
this.userMessages = []; // clear user previous message history this.userMessages = []; // clear user previous message history
let chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0] as HTMLElement; let chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0] as HTMLElement;
if (!forceShow && this.contentEl.getElementsByClassName("side-panel")?.length > 0) { if (!forceShow && this.contentEl.getElementsByClassName("side-panel")?.length > 0) {
chatBodyEl.innerHTML = ""; chatBodyEl.innerHTML = "";
return this.getChatHistory(chatBodyEl); return this.getChatHistory(chatBodyEl);
} }
chatBodyEl.innerHTML = ""; chatBodyEl.innerHTML = "";
const sidePanelEl = this.contentEl.createDiv("side-panel"); const sidePanelEl = chatBodyEl.createDiv("side-panel");
const newConversationEl = sidePanelEl.createDiv("new-conversation"); const newConversationEl = sidePanelEl.createDiv("new-conversation");
const conversationHeaderTitleEl = newConversationEl.createDiv("conversation-header-title"); const conversationHeaderTitleEl = newConversationEl.createDiv("conversation-header-title");
conversationHeaderTitleEl.textContent = "Conversations"; conversationHeaderTitleEl.textContent = "Conversations";
@ -727,7 +744,6 @@ export class KhojChatView extends KhojPaneView {
conversationSessionEl.appendChild(conversationMenuEl); conversationSessionEl.appendChild(conversationMenuEl);
conversationListBodyEl.appendChild(conversationSessionEl); conversationListBodyEl.appendChild(conversationSessionEl);
chatBodyEl.appendChild(sidePanelEl);
} }
} }
} catch (err) { } catch (err) {
@ -768,10 +784,10 @@ export class KhojChatView extends KhojPaneView {
let editConversationTitleInputEl = this.contentEl.createEl('input'); let editConversationTitleInputEl = this.contentEl.createEl('input');
editConversationTitleInputEl.classList.add("conversation-title-input"); editConversationTitleInputEl.classList.add("conversation-title-input");
editConversationTitleInputEl.value = conversationTitle; editConversationTitleInputEl.value = conversationTitle;
editConversationTitleInputEl.addEventListener('click', function(event) { editConversationTitleInputEl.addEventListener('click', function (event) {
event.stopPropagation(); event.stopPropagation();
}); });
editConversationTitleInputEl.addEventListener('keydown', function(event) { editConversationTitleInputEl.addEventListener('keydown', function (event) {
if (event.key === "Enter") { if (event.key === "Enter") {
event.preventDefault(); event.preventDefault();
editConversationTitleSaveButtonEl.click(); editConversationTitleSaveButtonEl.click();
@ -890,15 +906,17 @@ export class KhojChatView extends KhojPaneView {
chatLog.intent?.type, chatLog.intent?.type,
chatLog.intent?.["inferred-queries"], chatLog.intent?.["inferred-queries"],
chatBodyEl.dataset.conversationId ?? "", chatBodyEl.dataset.conversationId ?? "",
chatLog.images,
chatLog.excalidrawDiagram,
); );
// push the user messages to the chat history // push the user messages to the chat history
if(chatLog.by === "you"){ if (chatLog.by === "you") {
this.userMessages.push(chatLog.message); this.userMessages.push(chatLog.message);
} }
}); });
// Update starting message after loading history // Update starting message after loading history
const modifierKey: string = Platform.isMacOS ? '⌘' : '^'; const modifierKey: string = Platform.isMacOS ? '⌘' : '^';
this.startingMessage = this.userMessages.length > 0 this.startingMessage = this.userMessages.length > 0
? `(${modifierKey}+↑/↓) for prev messages` ? `(${modifierKey}+↑/↓) for prev messages`
: "Message"; : "Message";
@ -922,15 +940,15 @@ export class KhojChatView extends KhojPaneView {
try { try {
let jsonChunk = JSON.parse(rawChunk); let jsonChunk = JSON.parse(rawChunk);
if (!jsonChunk.type) if (!jsonChunk.type)
jsonChunk = {type: 'message', data: jsonChunk}; jsonChunk = { type: 'message', data: jsonChunk };
return jsonChunk; return jsonChunk;
} catch (e) { } catch (e) {
return {type: 'message', data: rawChunk}; return { type: 'message', data: rawChunk };
} }
} else if (rawChunk.length > 0) { } else if (rawChunk.length > 0) {
return {type: 'message', data: rawChunk}; return { type: 'message', data: rawChunk };
} }
return {type: '', data: ''}; return { type: '', data: '' };
} }
processMessageChunk(rawChunk: string): void { processMessageChunk(rawChunk: string): void {
@ -941,6 +959,11 @@ export class KhojChatView extends KhojPaneView {
console.log(`status: ${chunk.data}`); console.log(`status: ${chunk.data}`);
const statusMessage = chunk.data; const statusMessage = chunk.data;
this.handleStreamResponse(this.chatMessageState.newResponseTextEl, statusMessage, this.chatMessageState.loadingEllipsis, false); this.handleStreamResponse(this.chatMessageState.newResponseTextEl, statusMessage, this.chatMessageState.loadingEllipsis, false);
} else if (chunk.type === 'generated_assets') {
const generatedAssets = chunk.data;
const imageData = this.handleImageResponse(generatedAssets, this.chatMessageState.rawResponse);
this.chatMessageState.generatedAssets = imageData;
this.handleStreamResponse(this.chatMessageState.newResponseTextEl, imageData, this.chatMessageState.loadingEllipsis, false);
} else if (chunk.type === 'start_llm_response') { } else if (chunk.type === 'start_llm_response') {
console.log("Started streaming", new Date()); console.log("Started streaming", new Date());
} else if (chunk.type === 'end_llm_response') { } else if (chunk.type === 'end_llm_response') {
@ -963,9 +986,10 @@ export class KhojChatView extends KhojPaneView {
rawResponse: "", rawResponse: "",
rawQuery: liveQuery, rawQuery: liveQuery,
isVoice: false, isVoice: false,
generatedAssets: "",
}; };
} else if (chunk.type === "references") { } else if (chunk.type === "references") {
this.chatMessageState.references = {"notes": chunk.data.context, "online": chunk.data.onlineContext}; this.chatMessageState.references = { "notes": chunk.data.context, "online": chunk.data.onlineContext };
} else if (chunk.type === 'message') { } else if (chunk.type === 'message') {
const chunkData = chunk.data; const chunkData = chunk.data;
if (typeof chunkData === 'object' && chunkData !== null) { if (typeof chunkData === 'object' && chunkData !== null) {
@ -978,17 +1002,17 @@ export class KhojChatView extends KhojPaneView {
this.handleJsonResponse(jsonData); this.handleJsonResponse(jsonData);
} catch (e) { } catch (e) {
this.chatMessageState.rawResponse += chunkData; this.chatMessageState.rawResponse += chunkData;
this.handleStreamResponse(this.chatMessageState.newResponseTextEl, this.chatMessageState.rawResponse, this.chatMessageState.loadingEllipsis); this.handleStreamResponse(this.chatMessageState.newResponseTextEl, this.chatMessageState.rawResponse + this.chatMessageState.generatedAssets, this.chatMessageState.loadingEllipsis);
} }
} else { } else {
this.chatMessageState.rawResponse += chunkData; this.chatMessageState.rawResponse += chunkData;
this.handleStreamResponse(this.chatMessageState.newResponseTextEl, this.chatMessageState.rawResponse, this.chatMessageState.loadingEllipsis); this.handleStreamResponse(this.chatMessageState.newResponseTextEl, this.chatMessageState.rawResponse + this.chatMessageState.generatedAssets, this.chatMessageState.loadingEllipsis);
} }
} }
} }
handleJsonResponse(jsonData: any): void { handleJsonResponse(jsonData: any): void {
if (jsonData.image || jsonData.detail) { if (jsonData.image || jsonData.detail || jsonData.images || jsonData.excalidrawDiagram) {
this.chatMessageState.rawResponse = this.handleImageResponse(jsonData, this.chatMessageState.rawResponse); this.chatMessageState.rawResponse = this.handleImageResponse(jsonData, this.chatMessageState.rawResponse);
} else if (jsonData.response) { } else if (jsonData.response) {
this.chatMessageState.rawResponse = jsonData.response; this.chatMessageState.rawResponse = jsonData.response;
@ -1088,6 +1112,7 @@ export class KhojChatView extends KhojPaneView {
rawQuery: query, rawQuery: query,
rawResponse: "", rawResponse: "",
isVoice: isVoice, isVoice: isVoice,
generatedAssets: "",
}; };
let response = await fetch(chatUrl, { let response = await fetch(chatUrl, {
@ -1234,11 +1259,11 @@ export class KhojChatView extends KhojPaneView {
const recordingConfig = { mimeType: 'audio/webm' }; const recordingConfig = { mimeType: 'audio/webm' };
this.mediaRecorder = new MediaRecorder(stream, recordingConfig); this.mediaRecorder = new MediaRecorder(stream, recordingConfig);
this.mediaRecorder.addEventListener("dataavailable", function(event) { this.mediaRecorder.addEventListener("dataavailable", function (event) {
if (event.data.size > 0) audioChunks.push(event.data); if (event.data.size > 0) audioChunks.push(event.data);
}); });
this.mediaRecorder.addEventListener("stop", async function() { this.mediaRecorder.addEventListener("stop", async function () {
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' }); const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
await sendToServer(audioBlob); await sendToServer(audioBlob);
}); });
@ -1368,7 +1393,22 @@ export class KhojChatView extends KhojPaneView {
if (inferredQuery) { if (inferredQuery) {
rawResponse += `\n\n**Inferred Query**:\n\n${inferredQuery}`; rawResponse += `\n\n**Inferred Query**:\n\n${inferredQuery}`;
} }
} else if (imageJson.images) {
// If response has images field, response is a list of generated images.
imageJson.images.forEach((image: any) => {
if (image.startsWith("http")) {
rawResponse += `![generated_image](${image})\n\n`;
} else {
rawResponse += `![generated_image](data:image/png;base64,${image})\n\n`;
}
});
} else if (imageJson.excalidrawDiagram) {
const domain = this.setting.khojUrl.endsWith("/") ? this.setting.khojUrl : `${this.setting.khojUrl}/`;
const redirectMessage = `Hey, I'm not ready to show you diagrams yet here. But you can view it in ${domain}`;
rawResponse += redirectMessage;
} }
// If response has detail field, response is an error message. // If response has detail field, response is an error message.
if (imageJson.detail) rawResponse += imageJson.detail; if (imageJson.detail) rawResponse += imageJson.detail;
@ -1407,7 +1447,7 @@ export class KhojChatView extends KhojPaneView {
referenceExpandButton.classList.add("reference-expand-button"); referenceExpandButton.classList.add("reference-expand-button");
referenceExpandButton.innerHTML = numReferences == 1 ? "1 reference" : `${numReferences} references`; referenceExpandButton.innerHTML = numReferences == 1 ? "1 reference" : `${numReferences} references`;
referenceExpandButton.addEventListener('click', function() { referenceExpandButton.addEventListener('click', function () {
if (referenceSection.classList.contains("collapsed")) { if (referenceSection.classList.contains("collapsed")) {
referenceSection.classList.remove("collapsed"); referenceSection.classList.remove("collapsed");
referenceSection.classList.add("expanded"); referenceSection.classList.add("expanded");

View file

@ -50,12 +50,12 @@ If your plugin does not need CSS, delete this file.
overflow-y: scroll; /* Make chat body scroll to see history */ overflow-y: scroll; /* Make chat body scroll to see history */
} }
/* add chat metatdata to bottom of bubble */ /* add chat metatdata to bottom of bubble */
.khoj-chat-message::after { .khoj-chat-message.khoj::after {
content: attr(data-meta); content: attr(data-meta);
display: block; display: block;
font-size: var(--font-ui-smaller); font-size: var(--font-ui-smaller);
color: var(--text-muted); color: var(--text-muted);
margin: -12px 7px 0 -5px; margin: -12px 7px 0 0px;
} }
/* move message by khoj to left */ /* move message by khoj to left */
.khoj-chat-message.khoj { .khoj-chat-message.khoj {
@ -82,7 +82,8 @@ If your plugin does not need CSS, delete this file.
} }
/* color chat bubble by khoj blue */ /* color chat bubble by khoj blue */
.khoj-chat-message-text.khoj { .khoj-chat-message-text.khoj {
border: 1px solid var(--khoj-sun); border-left: 2px solid var(--khoj-sun);
border-radius: 0px;
margin-left: auto; margin-left: auto;
white-space: pre-line; white-space: pre-line;
} }
@ -104,8 +105,9 @@ If your plugin does not need CSS, delete this file.
} }
/* color chat bubble by you dark grey */ /* color chat bubble by you dark grey */
.khoj-chat-message-text.you { .khoj-chat-message-text.you {
border: 1px solid var(--color-accent); color: var(--text-normal);
margin-right: auto; margin-right: auto;
background-color: var(--background-modifier-cover);
} }
/* add right protrusion to you chat bubble */ /* add right protrusion to you chat bubble */
.khoj-chat-message-text.you:after { .khoj-chat-message-text.you:after {
@ -240,6 +242,10 @@ div.new-conversation {
grid-auto-flow: column; grid-auto-flow: column;
grid-template-columns: 1fr auto; grid-template-columns: 1fr auto;
margin-bottom: 16px; margin-bottom: 16px;
position: sticky;
top: 0;
z-index: 10;
background-color: var(--background-primary)
} }
div.conversation-header-title { div.conversation-header-title {
text-align: left; text-align: left;
@ -314,6 +320,9 @@ div.selected-conversation {
background: var(--background-primary); background: var(--background-primary);
margin: 0 0 0 -8px; margin: 0 0 0 -8px;
align-items: center; align-items: center;
position: sticky;
bottom: 0;
z-index: 10;
} }
#khoj-chat-input.option:hover { #khoj-chat-input.option:hover {
box-shadow: 0 0 11px var(--background-modifier-box-shadow); box-shadow: 0 0 11px var(--background-modifier-box-shadow);

View file

@ -92,5 +92,15 @@
"1.29.0": "0.15.0", "1.29.0": "0.15.0",
"1.29.1": "0.15.0", "1.29.1": "0.15.0",
"1.30.0": "0.15.0", "1.30.0": "0.15.0",
"1.30.1": "0.15.0" "1.30.1": "0.15.0",
"1.30.2": "0.15.0",
"1.30.3": "0.15.0",
"1.30.4": "0.15.0",
"1.30.5": "0.15.0",
"1.30.6": "0.15.0",
"1.30.7": "0.15.0",
"1.30.8": "0.15.0",
"1.30.9": "0.15.0",
"1.30.10": "0.15.0",
"1.31.0": "0.15.0"
} }

View file

@ -1,8 +1,7 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Noto_Sans } from "next/font/google"; import { noto_sans, noto_sans_arabic } from "@/app/fonts";
import "../globals.css"; import "../globals.css";
import { ContentSecurityPolicy } from "../common/layoutHelper";
const inter = Noto_Sans({ subsets: ["latin"] });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Khoj AI - Agents", title: "Khoj AI - Agents",
@ -33,20 +32,9 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html lang="en"> <html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
<meta <ContentSecurityPolicy />
httpEquiv="Content-Security-Policy" <body>{children}</body>
content="default-src 'self' https://assets.khoj.dev;
media-src * blob:;
script-src 'self' https://assets.khoj.dev 'unsafe-inline' 'unsafe-eval';
connect-src 'self' https://ipapi.co/json ws://localhost:42110;
style-src 'self' https://assets.khoj.dev 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https://*.khoj.dev https://*.googleusercontent.com https://*.google.com/ https://*.gstatic.com;
font-src 'self' https://assets.khoj.dev https://fonts.gstatic.com;
child-src 'none';
object-src 'none';"
></meta>
<body className={inter.className}>{children}</body>
</html> </html>
); );
} }

Some files were not shown because too many files have changed in this diff Show more