Add developer support for using next.js to serve generated static files (#814)
To improve the developer experience for front-end development, we're migrating to Next.js. In order to do this migration page-by-page, we're using static site generation via Next.js. This also helps us avoid making cross site requests from front-end to back-end for the time being, while giving a ramp to separating out server and client if needed for scale down the road. Dev instructions for using the next.js setup are in the added README. This adds scaffolding for including the built files in the python package as well as the docker images. Docker setup has been tested locally. In order to verify the build is working as expected, we can navigate to the {khoj_host}:42110/experimental and verify that the experiment page comes up. This setup works with serving static files included in the src/interface/web folder from the Django app. The key bit for understanding the setup is in the yarn export command in package.json.
4
.github/workflows/dockerize.yml
vendored
|
@ -8,6 +8,7 @@ on:
|
||||||
- master
|
- master
|
||||||
paths:
|
paths:
|
||||||
- src/khoj/**
|
- src/khoj/**
|
||||||
|
- src/interface/web/**
|
||||||
- pyproject.toml
|
- pyproject.toml
|
||||||
- Dockerfile
|
- Dockerfile
|
||||||
- prod.Dockerfile
|
- prod.Dockerfile
|
||||||
|
@ -30,8 +31,9 @@ on:
|
||||||
env:
|
env:
|
||||||
# Tag Image with tag name on release
|
# Tag Image with tag name on release
|
||||||
# else with user specified tag (default 'dev') if triggered via workflow
|
# else with user specified tag (default 'dev') if triggered via workflow
|
||||||
|
# else with run_id if triggered via a pull request
|
||||||
# else with 'pre' (if push to master)
|
# else with 'pre' (if push to master)
|
||||||
DOCKER_IMAGE_TAG: ${{ github.ref_type == 'tag' && github.ref_name || github.event_name == 'workflow_dispatch' && github.event.inputs.tag || 'pre' }}
|
DOCKER_IMAGE_TAG: ${{ github.ref_type == 'tag' && github.ref_name || github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.event_name == 'push' && github.run_id || 'pre' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
12
.github/workflows/pypi.yml
vendored
|
@ -8,6 +8,7 @@ on:
|
||||||
- 'master'
|
- 'master'
|
||||||
paths:
|
paths:
|
||||||
- src/khoj/**
|
- src/khoj/**
|
||||||
|
- src/interface/web/**
|
||||||
- pyproject.toml
|
- pyproject.toml
|
||||||
- .github/workflows/pypi.yml
|
- .github/workflows/pypi.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
|
@ -15,6 +16,7 @@ on:
|
||||||
- 'master'
|
- 'master'
|
||||||
paths:
|
paths:
|
||||||
- src/khoj/**
|
- src/khoj/**
|
||||||
|
- src/interface/web/**
|
||||||
- pyproject.toml
|
- pyproject.toml
|
||||||
- .github/workflows/pypi.yml
|
- .github/workflows/pypi.yml
|
||||||
|
|
||||||
|
@ -37,6 +39,16 @@ jobs:
|
||||||
- name: ⬇️ Install Application
|
- name: ⬇️ Install Application
|
||||||
run: python -m pip install --upgrade pip && pip install --upgrade .
|
run: python -m pip install --upgrade pip && pip install --upgrade .
|
||||||
|
|
||||||
|
- name: Install the Next.js application
|
||||||
|
run: |
|
||||||
|
yarn install
|
||||||
|
working-directory: src/interface/web
|
||||||
|
|
||||||
|
- name: Build & export static Next.js app to Django static assets
|
||||||
|
run: |
|
||||||
|
yarn ciexport
|
||||||
|
working-directory: src/interface/web
|
||||||
|
|
||||||
- name: ⚙️ Build Python Package
|
- name: ⚙️ Build Python Package
|
||||||
run: |
|
run: |
|
||||||
# Setup Environment for Reproducible Builds
|
# Setup Environment for Reproducible Builds
|
||||||
|
|
1
.gitignore
vendored
|
@ -16,6 +16,7 @@ todesktop.json
|
||||||
|
|
||||||
# Build artifacts
|
# Build artifacts
|
||||||
/src/khoj/interface/web/images
|
/src/khoj/interface/web/images
|
||||||
|
/src/khoj/interface/built/
|
||||||
/build/
|
/build/
|
||||||
/dist/
|
/dist/
|
||||||
khoj_assistant.egg-info
|
khoj_assistant.egg-info
|
||||||
|
|
15
Dockerfile
|
@ -3,11 +3,17 @@ FROM ubuntu:jammy
|
||||||
LABEL org.opencontainers.image.source https://github.com/khoj-ai/khoj
|
LABEL org.opencontainers.image.source https://github.com/khoj-ai/khoj
|
||||||
|
|
||||||
# Install System Dependencies
|
# Install System Dependencies
|
||||||
RUN apt update -y && apt -y install python3-pip swig
|
RUN apt update -y && apt -y install python3-pip swig curl
|
||||||
|
|
||||||
WORKDIR /app
|
# Install Node.js and Yarn
|
||||||
|
RUN curl -sL https://deb.nodesource.com/setup_22.x | bash -
|
||||||
|
RUN apt -y install nodejs
|
||||||
|
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
||||||
|
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
|
||||||
|
RUN apt update && apt -y install yarn
|
||||||
|
|
||||||
# Install Application
|
# Install Application
|
||||||
|
WORKDIR /app
|
||||||
COPY pyproject.toml .
|
COPY pyproject.toml .
|
||||||
COPY README.md .
|
COPY README.md .
|
||||||
ARG VERSION=0.0.0
|
ARG VERSION=0.0.0
|
||||||
|
@ -20,6 +26,11 @@ COPY . .
|
||||||
# Set the PYTHONPATH environment variable in order for it to find the Django app.
|
# Set the PYTHONPATH environment variable in order for it to find the Django app.
|
||||||
ENV PYTHONPATH=/app/src:$PYTHONPATH
|
ENV PYTHONPATH=/app/src:$PYTHONPATH
|
||||||
|
|
||||||
|
# Go to the directory src/interface/web and export the built Next.js assets
|
||||||
|
WORKDIR /app/src/interface/web
|
||||||
|
RUN bash -c "yarn install && yarn ciexport"
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
# 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 these should be passed in through the docker-compose.yml file.
|
||||||
|
|
Before Width: | Height: | Size: 6.3 KiB |
|
@ -3,7 +3,14 @@ FROM ubuntu:jammy
|
||||||
LABEL org.opencontainers.image.source https://github.com/khoj-ai/khoj
|
LABEL org.opencontainers.image.source https://github.com/khoj-ai/khoj
|
||||||
|
|
||||||
# Install System Dependencies
|
# Install System Dependencies
|
||||||
RUN apt update -y && apt -y install python3-pip libsqlite3-0 ffmpeg libsm6 libxext6
|
RUN apt update -y && apt -y install python3-pip libsqlite3-0 ffmpeg libsm6 libxext6 swig curl
|
||||||
|
|
||||||
|
# Install Node.js and Yarn
|
||||||
|
RUN curl -sL https://deb.nodesource.com/setup_22.x | bash -
|
||||||
|
RUN apt -y install nodejs
|
||||||
|
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
||||||
|
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
|
||||||
|
RUN apt update && apt -y install yarn
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
@ -20,6 +27,11 @@ COPY . .
|
||||||
# Set the PYTHONPATH environment variable in order for it to find the Django app.
|
# Set the PYTHONPATH environment variable in order for it to find the Django app.
|
||||||
ENV PYTHONPATH=/app/src:$PYTHONPATH
|
ENV PYTHONPATH=/app/src:$PYTHONPATH
|
||||||
|
|
||||||
|
# Go to the directory src/interface/web and export the built Next.js assets
|
||||||
|
WORKDIR /app/src/interface/web
|
||||||
|
RUN bash -c "yarn install && yarn ciexport"
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
# 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 these should be passed in through the docker-compose.yml file.
|
||||||
|
|
1
src/interface/web/.env.development
Normal file
|
@ -0,0 +1 @@
|
||||||
|
NEXT_PUBLIC_ENV='development'
|
1
src/interface/web/.env.production
Normal file
|
@ -0,0 +1 @@
|
||||||
|
NEXT_PUBLIC_ENV='production'
|
3
src/interface/web/.eslintrc.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals"
|
||||||
|
}
|
36
src/interface/web/.gitignore
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
.yarn/install-state.gz
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
93
src/interface/web/README.md
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
This is a [Next.js](https://nextjs.org/) project.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, install the dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
In case you run into any dependency linking issues, you can try running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn add next
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure the `rewrites` in `next.config.mjs` are set up correctly for your environment. The rewrites are used to proxy requests to the API server.
|
||||||
|
|
||||||
|
```js
|
||||||
|
rewrites: async () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: '/api/:path*',
|
||||||
|
destination: 'http://localhost:42110/api/:path*',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
The `destination` should be the URL of the API server.
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying any of the `.tsx` pages. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
### Testing built files
|
||||||
|
|
||||||
|
We've setup a utility command for building and serving the built files. This is useful for testing the production build locally.
|
||||||
|
|
||||||
|
1. Exporting code
|
||||||
|
To build the files once and serve them, run:
|
||||||
|
```bash
|
||||||
|
yarn export
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're using Windows:
|
||||||
|
```bash
|
||||||
|
yarn windowsexport
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
2. Continuously building code
|
||||||
|
|
||||||
|
To keep building the files and serving them, run:
|
||||||
|
```bash
|
||||||
|
yarn watch
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're using Windows:
|
||||||
|
```bash
|
||||||
|
yarn windowswatch
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you should be able to load your custom pages from the Khoj app at http://localhost:42110/. To server any of the built files, you should update the routes in the `web_client.py` like so, where `new_file` is the new page you've added in this repo:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@web_client.post("/new_route", response_class=FileResponse)
|
||||||
|
@requires(["authenticated"], redirect="login_page")
|
||||||
|
def index_post(request: Request):
|
||||||
|
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"new_file/index.html",
|
||||||
|
context={
|
||||||
|
"request": request,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Next.js App Router](https://nextjs.org/docs/app) - learn about the Next.js router.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
BIN
src/interface/web/app/favicon.ico
Normal file
After Width: | Height: | Size: 200 KiB |
158
src/interface/web/app/globals.css
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Arabic:wght@100..900&family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--max-width: 1100px;
|
||||||
|
--border-radius: 12px;
|
||||||
|
--font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono",
|
||||||
|
"Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro",
|
||||||
|
"Fira Mono", "Droid Sans Mono", "Courier New", monospace;
|
||||||
|
|
||||||
|
--foreground-rgb: 0, 0, 0;
|
||||||
|
--background-start-rgb: 214, 219, 220;
|
||||||
|
--background-end-rgb: 255, 255, 255;
|
||||||
|
|
||||||
|
--primary-glow: conic-gradient(
|
||||||
|
from 180deg at 50% 50%,
|
||||||
|
#16abff33 0deg,
|
||||||
|
#0885ff33 55deg,
|
||||||
|
#54d6ff33 120deg,
|
||||||
|
#0071ff33 160deg,
|
||||||
|
transparent 360deg
|
||||||
|
);
|
||||||
|
--secondary-glow: radial-gradient(
|
||||||
|
rgba(255, 255, 255, 1),
|
||||||
|
rgba(255, 255, 255, 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
--tile-start-rgb: 239, 245, 249;
|
||||||
|
--tile-end-rgb: 228, 232, 233;
|
||||||
|
--tile-border: conic-gradient(
|
||||||
|
#00000080,
|
||||||
|
#00000040,
|
||||||
|
#00000030,
|
||||||
|
#00000020,
|
||||||
|
#00000010,
|
||||||
|
#00000010,
|
||||||
|
#00000080
|
||||||
|
);
|
||||||
|
|
||||||
|
--callout-rgb: 238, 240, 241;
|
||||||
|
--callout-border-rgb: 172, 175, 176;
|
||||||
|
--card-rgb: 180, 185, 188;
|
||||||
|
--card-border-rgb: 131, 134, 135;
|
||||||
|
|
||||||
|
--primary: #f9f5de;
|
||||||
|
--primary-hover: #fee285;
|
||||||
|
--primary-focus: rgba(255, 179, 0, 0.125);
|
||||||
|
--primary-inverse: rgba(0, 0, 0, 0.75);
|
||||||
|
--background-color: #f5f4f3;
|
||||||
|
--frosted-background-color: rgba(245, 244, 243, 0.75);
|
||||||
|
--main-text-color: #475569;
|
||||||
|
--summer-sun: #fcc50b;
|
||||||
|
--water: #44b9da;
|
||||||
|
--leaf: #7b990a;
|
||||||
|
--flower: #ffaeae;
|
||||||
|
--font-family: "Noto Sans", "Noto Sans Arabic", sans-serif !important;
|
||||||
|
--calm-blue: #E4D9D9;
|
||||||
|
--calmer-blue: #e4cdcd;
|
||||||
|
--calm-green: #d0f5d6;
|
||||||
|
--intense-green: #1C2841;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--foreground-rgb: 255, 255, 255;
|
||||||
|
--background-start-rgb: 0, 0, 0;
|
||||||
|
--background-end-rgb: 0, 0, 0;
|
||||||
|
|
||||||
|
--primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
|
||||||
|
--secondary-glow: linear-gradient(
|
||||||
|
to bottom right,
|
||||||
|
rgba(1, 65, 255, 0),
|
||||||
|
rgba(1, 65, 255, 0),
|
||||||
|
rgba(1, 65, 255, 0.3)
|
||||||
|
);
|
||||||
|
|
||||||
|
--tile-start-rgb: 2, 13, 46;
|
||||||
|
--tile-end-rgb: 2, 5, 19;
|
||||||
|
--tile-border: conic-gradient(
|
||||||
|
#ffffff80,
|
||||||
|
#ffffff40,
|
||||||
|
#ffffff30,
|
||||||
|
#ffffff20,
|
||||||
|
#ffffff10,
|
||||||
|
#ffffff10,
|
||||||
|
#ffffff80
|
||||||
|
);
|
||||||
|
|
||||||
|
--callout-rgb: 20, 20, 20;
|
||||||
|
--callout-border-rgb: 108, 108, 108;
|
||||||
|
--card-rgb: 100, 100, 100;
|
||||||
|
--card-border-rgb: 200, 200, 200;
|
||||||
|
|
||||||
|
--primary: #f9f5de;
|
||||||
|
--primary-hover: #fee285;
|
||||||
|
--primary-focus: rgba(255, 179, 0, 0.125);
|
||||||
|
--primary-inverse: rgba(0, 0, 0, 0.75);
|
||||||
|
--background-color: #f5f4f3;
|
||||||
|
--frosted-background-color: rgba(245, 244, 243, 0.75);
|
||||||
|
--main-text-color: #475569;
|
||||||
|
--summer-sun: #fcc50b;
|
||||||
|
--water: #44b9da;
|
||||||
|
--leaf: #7b990a;
|
||||||
|
--flower: #ffaeae;
|
||||||
|
--font-family: "Noto Sans", "Noto Sans Arabic", sans-serif !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
max-width: 100vw;
|
||||||
|
overflow-x: hidden;
|
||||||
|
font-family: var(--font-family);
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
div,
|
||||||
|
button,
|
||||||
|
p,
|
||||||
|
textarea {
|
||||||
|
font-family: var(--font-family);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: var(--main-text-color);
|
||||||
|
background-color: var(--background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code.hljs {
|
||||||
|
white-space: preserve;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Uncomment when ready for dark-mode */
|
||||||
|
/* @media (prefers-color-scheme: dark) {
|
||||||
|
html {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
} */
|
22
src/interface/web/app/layout.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Noto_Sans } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
const inter = Noto_Sans({ subsets: ["latin"] });
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Khoj AI",
|
||||||
|
description: "An AI copilot for your second brain",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={inter.className}>{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
230
src/interface/web/app/page.module.css
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6rem;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
display: inherit;
|
||||||
|
justify-content: inherit;
|
||||||
|
align-items: inherit;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
max-width: var(--max-width);
|
||||||
|
width: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
.description a {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description p {
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: rgba(var(--callout-rgb), 0.5);
|
||||||
|
border: 1px solid rgba(var(--callout-border-rgb), 0.3);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(25%, auto));
|
||||||
|
max-width: 100%;
|
||||||
|
width: var(--max-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 1rem 1.2rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background: rgba(var(--card-rgb), 0);
|
||||||
|
border: 1px solid rgba(var(--card-border-rgb), 0);
|
||||||
|
transition: background 200ms, border 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card span {
|
||||||
|
display: inline-block;
|
||||||
|
transition: transform 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h2 {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card p {
|
||||||
|
margin: 0;
|
||||||
|
opacity: 0.6;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
max-width: 30ch;
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
padding: 4rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center::before {
|
||||||
|
background: var(--secondary-glow);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 480px;
|
||||||
|
height: 360px;
|
||||||
|
margin-left: -400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center::after {
|
||||||
|
background: var(--primary-glow);
|
||||||
|
width: 240px;
|
||||||
|
height: 180px;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center::before,
|
||||||
|
.center::after {
|
||||||
|
content: "";
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
filter: blur(45px);
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
/* Enable hover only on non-touch devices */
|
||||||
|
@media (hover: hover) and (pointer: fine) {
|
||||||
|
.card:hover {
|
||||||
|
background: rgba(var(--card-rgb), 0.1);
|
||||||
|
border: 1px solid rgba(var(--card-border-rgb), 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover span {
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion) {
|
||||||
|
.card:hover span {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile */
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.content {
|
||||||
|
padding: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
margin-bottom: 120px;
|
||||||
|
max-width: 320px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 1rem 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h2 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
padding: 8rem 0 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center::before {
|
||||||
|
transform: none;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description a {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description p,
|
||||||
|
.description div {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description p {
|
||||||
|
align-items: center;
|
||||||
|
inset: 0 0 auto;
|
||||||
|
padding: 2rem 1rem 1.4rem;
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(var(--background-start-rgb), 1),
|
||||||
|
rgba(var(--callout-rgb), 0.5)
|
||||||
|
);
|
||||||
|
background-clip: padding-box;
|
||||||
|
backdrop-filter: blur(24px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.description div {
|
||||||
|
align-items: flex-end;
|
||||||
|
pointer-events: none;
|
||||||
|
inset: auto 0 0;
|
||||||
|
padding: 2rem;
|
||||||
|
height: 200px;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
transparent 0%,
|
||||||
|
rgb(var(--background-end-rgb)) 40%
|
||||||
|
);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablet and Smaller Desktop */
|
||||||
|
@media (min-width: 701px) and (max-width: 1120px) {
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: repeat(2, 50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.vercelLogo {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
from {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
}
|
9
src/interface/web/app/page.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import styles from "./page.module.css";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<main className={styles.main}>
|
||||||
|
Hi, Khoj here.
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
19
src/interface/web/image-loader.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
export default function khojLoader({
|
||||||
|
src,
|
||||||
|
width,
|
||||||
|
quality,
|
||||||
|
}: {
|
||||||
|
src: string
|
||||||
|
width: number
|
||||||
|
quality?: number
|
||||||
|
}) {
|
||||||
|
if (src.startsWith("http")) {
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src.startsWith("/")) {
|
||||||
|
src = src.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return `/static/${src}`
|
||||||
|
}
|
38
src/interface/web/next.config.mjs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
|
||||||
|
const isProd = process.env.NEXT_PUBLIC_ENV === 'production';
|
||||||
|
|
||||||
|
const nextConfig = {
|
||||||
|
output: isProd ? 'export' : undefined,
|
||||||
|
rewrites: isProd ? undefined : async () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: '/api/:path*',
|
||||||
|
destination: 'http://localhost:42110/api/:path*',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
trailingSlash: true,
|
||||||
|
skipTrailingSlashRedirect: true,
|
||||||
|
distDir: 'out',
|
||||||
|
images: {
|
||||||
|
loader: isProd ? 'custom' : 'default',
|
||||||
|
loaderFile: isProd ? './image-loader.ts' : undefined,
|
||||||
|
remotePatterns: isProd ? [
|
||||||
|
{
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "**.googleusercontent.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "generated.khoj.dev",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "assets.khoj.dev",
|
||||||
|
},
|
||||||
|
] : undefined,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
39
src/interface/web/package.json
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"name": "khoj-ai",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint",
|
||||||
|
"collectstatic": "bash -c 'pushd ../../../ && source .venv/bin/activate && python3 src/khoj/manage.py collectstatic --noinput && deactivate && popd'",
|
||||||
|
"cicollectstatic": "bash -c 'pushd ../../../ && python3 src/khoj/manage.py collectstatic --noinput && popd'",
|
||||||
|
"export": "yarn build && cp -r out/ ../../khoj/interface/built && yarn collectstatic",
|
||||||
|
"ciexport": "yarn build && cp -r out/ ../../khoj/interface/built && yarn cicollectstatic",
|
||||||
|
"watch": "nodemon --watch . --ext js,jsx,ts,tsx,css --ignore 'out/**/*' --exec 'yarn export'",
|
||||||
|
"windowswatch": "nodemon --watch . --ext js,jsx,ts,tsx,css --ignore 'out/**/*' --exec 'yarn windowsexport'",
|
||||||
|
"windowscollectstatic": "cd ..\\..\\.. && .\\.venv\\Scripts\\Activate.bat && py .\\src\\khoj\\manage.py collectstatic --noinput && .\\.venv\\Scripts\\deactivate.bat && cd ..",
|
||||||
|
"windowsexport": "yarn build && xcopy out ..\\..\\khoj\\interface\\built /E /Y && yarn windowscollectstatic"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/katex": "^0.16.7",
|
||||||
|
"@types/markdown-it": "^14.1.1",
|
||||||
|
"katex": "^0.16.10",
|
||||||
|
"markdown-it": "^14.1.0",
|
||||||
|
"markdown-it-highlightjs": "^4.1.0",
|
||||||
|
"next": "14.2.3",
|
||||||
|
"react": "^18",
|
||||||
|
"react-dom": "^18",
|
||||||
|
"swr": "^2.2.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^18",
|
||||||
|
"@types/react-dom": "^18",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "14.2.3",
|
||||||
|
"nodemon": "^3.1.3",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
6
src/interface/web/public/agents.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none" x="0px" y="0px">
|
||||||
|
<!--
|
||||||
|
Icon Source: AI by Palash Jain from <a href="https://thenounproject.com/browse/icons/term/ai/" target="_blank" title="AI Icons">Noun Project</a> (CC BY 3.0)
|
||||||
|
-->
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M42 10C42 8.89543 41.1046 8 40 8C38.8954 8 38 8.89543 38 10V16C38 16.0206 38.0003 16.0411 38.0009 16.0615C24.2142 16.9142 13 26.5663 13 41C13 44.7195 13.7548 47.7715 15.2618 50.2177C16.7767 52.6766 18.9515 54.3606 21.5055 55.5033C26.4785 57.7282 33.156 58 40 58C46.844 58 53.5215 57.7282 58.4945 55.5033C61.0485 54.3606 63.2233 52.6766 64.7382 50.2177C66.2452 47.7715 67 44.7195 67 41C67 26.5663 55.7858 16.9142 41.9991 16.0615C41.9997 16.0411 42 16.0206 42 16V10ZM40 20C27.0708 20 17 28.5112 17 41C17 44.184 17.6443 46.4588 18.6674 48.1196C19.6827 49.7675 21.169 50.9706 23.1391 51.8521C27.2144 53.6754 33.0369 54 40 54C46.9631 54 52.7856 53.6754 56.8609 51.8521C58.831 50.9706 60.3173 49.7675 61.3326 48.1196C62.3557 46.4588 63 44.184 63 41C63 28.5112 52.9292 20 40 20ZM28 32C29.1046 32 30 32.8954 30 34V40C30 41.1046 29.1046 42 28 42C26.8954 42 26 41.1046 26 40V34C26 32.8954 26.8954 32 28 32ZM51 32C52.1046 32 53 32.8954 53 34V40C53 41.1046 52.1046 42 51 42C49.8954 42 49 41.1046 49 40V34C49 32.8954 49.8954 32 51 32ZM19.3598 68.8634C26.0525 67.6396 32.9507 67 40 67C47.0494 67 53.9476 67.6396 60.6403 68.8634C61.7268 69.0621 62.7687 68.3423 62.9674 67.2558C63.1661 66.1692 62.4463 65.1273 61.3598 64.9287C54.4308 63.6616 47.2918 63 40 63C32.7082 63 25.5692 63.6616 18.6403 64.9287C17.5537 65.1273 16.8339 66.1692 17.0326 67.2558C17.2313 68.3423 18.2732 69.0621 19.3598 68.8634Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
37
src/interface/web/public/automation.svg
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<svg
|
||||||
|
width="800px"
|
||||||
|
height="800px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="m 19.402765,19.007843 c 1.380497,-1.679442 2.307667,-4.013099 2.307667,-6.330999 C 21.710432,7.2551476 17.193958,2.86 11.622674,2.86 6.0513555,2.86 1.5349153,7.2551476 1.5349153,12.676844 c 0,5.421663 4.5164402,9.816844 10.0877587,9.816844 2.381867,0 4.570922,-0.803307 6.296712,-2.14673 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 14.177351,11.200265 0.05184,2.153289"
|
||||||
|
stroke="#1c274c"
|
||||||
|
stroke-width="1.95702"
|
||||||
|
stroke-linecap="round" />
|
||||||
|
<path
|
||||||
|
d="m 9.271347,11.140946 0.051844,2.153289"
|
||||||
|
stroke="#1c274c"
|
||||||
|
stroke-width="1.95701"
|
||||||
|
stroke-linecap="round" />
|
||||||
|
<path
|
||||||
|
d="m 13.557051,1.4687179 c -1.779392,0.00605 -3.082184,0.01209 -3.6968064,0.018135"
|
||||||
|
stroke="#1c274c"
|
||||||
|
stroke-width="1.77333"
|
||||||
|
stroke-linecap="round" />
|
||||||
|
<path
|
||||||
|
d="M 20.342466,5.7144363 19.140447,6.8696139"
|
||||||
|
stroke="#1c274c"
|
||||||
|
stroke-width="1.95701"
|
||||||
|
stroke-linecap="round" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
24
src/interface/web/public/chat.svg
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<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>
|
After Width: | Height: | Size: 2.4 KiB |
5
src/interface/web/public/close.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?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">
|
||||||
|
<circle cx="12" cy="12" r="10" stroke="#1C274C" stroke-width="1.5"/>
|
||||||
|
<path d="M14.5 9.50002L9.5 14.5M9.49998 9.5L14.5 14.5" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 417 B |
6
src/interface/web/public/copy-button-success.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?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 opacity="0.4" d="M22 11.1V6.9C22 3.4 20.6 2 17.1 2H12.9C9.4 2 8 3.4 8 6.9V8H11.1C14.6 8 16 9.4 16 12.9V16H17.1C20.6 16 22 14.6 22 11.1Z" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path fill="#00ff00" d="M16 17.1V12.9C16 9.4 14.6 8 11.1 8H6.9C3.4 8 2 9.4 2 12.9V17.1C2 20.6 3.4 22 6.9 22H11.1C14.6 22 16 20.6 16 17.1Z" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M6.08008 14.9998L8.03008 16.9498L11.9201 13.0498" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 829 B |
5
src/interface/web/public/copy-button.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?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 opacity="0.4" d="M16 12.9V17.1C16 20.6 14.6 22 11.1 22H6.9C3.4 22 2 20.6 2 17.1V12.9C2 9.4 3.4 8 6.9 8H11.1C14.6 8 16 9.4 16 12.9Z" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M22 6.9V11.1C22 14.6 20.6 16 17.1 16H16V12.9C16 9.4 14.6 8 11.1 8H8V6.9C8 3.4 9.4 2 12.9 2H17.1C20.6 2 22 3.4 22 6.9Z" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 669 B |
5385
src/interface/web/public/khoj-logo.svg
Normal file
After Width: | Height: | Size: 1.2 MiB |
1
src/interface/web/public/send.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="-4.08 -4.08 32.16 32.16" fill="none" xmlns="http://www.w3.org/2000/svg" transform="rotate(0)" stroke="#000000" stroke-width="0.40800000000000003"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M11 13L15.4564 8.5M11 13L6.38202 9.57695C5.7407 9.07229 5.94107 8.06115 6.72742 7.834L20 4L17.117 15.9189C16.9651 16.6489 16.0892 16.9637 15.5 16.5L13.5 15M11 13V18L13.5 15M11 13L13.5 15M7 20L9 18M4 19L8.5 14.5M4 15L6.5 12.5" stroke="#464455" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>
|
After Width: | Height: | Size: 635 B |
8
src/interface/web/public/share.svg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?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.5 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6.75 15C8.13071 15 9.25 13.8807 9.25 12.5C9.25 11.1193 8.13071 10 6.75 10C5.36929 10 4.25 11.1193 4.25 12.5C4.25 13.8807 5.36929 15 6.75 15Z" stroke="#0F0F0F" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M17.25 8C18.6307 8 19.75 6.88071 19.75 5.5C19.75 4.11929 18.6307 3 17.25 3C15.8693 3 14.75 4.11929 14.75 5.5C14.75 6.88071 15.8693 8 17.25 8Z" stroke="#0F0F0F" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M17.25 22C18.6307 22 19.75 20.8807 19.75 19.5C19.75 18.1193 18.6307 17 17.25 17C15.8693 17 14.75 18.1193 14.75 19.5C14.75 20.8807 15.8693 22 17.25 22Z" stroke="#0F0F0F" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M13.49 17.05L10.45 15.06" stroke="#0F0F0F" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M13.48 7.96001L10.46 9.94001" stroke="#0F0F0F" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
6
src/interface/web/public/thumbs-down.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg fill="#000000" width="800px" height="800px" viewBox="-7.5 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>thumbs-down</title>
|
||||||
|
<path d="M5.92 25.24c-0.64 0-1.36-0.2-2.2-0.64-0.28-0.16-0.44-0.44-0.44-0.76v-14.96c0-0.36 0.24-0.72 0.6-0.8 0.2-0.040 4.8-1.32 8.16-1.32 1.36 0 2.36 0.2 3 0.6 0.88 0.56 1.44 1.52 1.6 2.92 0.36 2.44-0.52 5.92-1.44 6.96-0.8 0.88-2.36 1.040-3.84 1.2-0.72 0.080-1.72 0.2-2 0.36s-0.44 1.4-0.52 2.12c-0.24 1.84-0.6 4.32-2.92 4.32zM4.92 23.32c0.48 0.2 0.8 0.24 1 0.24 0.72 0 1-0.88 1.24-2.84 0.2-1.36 0.36-2.68 1.24-3.28 0.6-0.4 1.6-0.52 2.76-0.64 0.96-0.12 2.44-0.28 2.8-0.68 0.48-0.52 1.36-3.36 1.040-5.6-0.080-0.6-0.32-1.4-0.84-1.72-0.24-0.12-0.8-0.36-2.12-0.36-2.44 0-5.76 0.76-7.12 1.080 0 0 0 13.8 0 13.8zM0.84 18.64c-0.48 0-0.84-0.36-0.84-0.84v-8.92c0-0.48 0.36-0.84 0.84-0.84s0.84 0.36 0.84 0.84v8.96c0 0.44-0.36 0.8-0.84 0.8z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1,019 B |
6
src/interface/web/public/thumbs-up.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg fill="#000000" width="800px" height="800px" viewBox="-7.5 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>thumbs-up</title>
|
||||||
|
<path d="M12.040 25.24v0c-3.36 0-7.96-1.24-8.16-1.32-0.36-0.080-0.6-0.44-0.6-0.8v-14.96c0-0.32 0.16-0.6 0.44-0.76 0.84-0.44 1.56-0.64 2.2-0.64 2.32 0 2.68 2.48 2.92 4.28 0.080 0.72 0.24 1.92 0.52 2.12s1.28 0.28 2 0.36c1.52 0.16 3.080 0.32 3.84 1.2 0.92 1.040 1.8 4.52 1.44 6.96-0.2 1.4-0.76 2.36-1.6 2.92-0.68 0.44-1.68 0.64-3 0.64zM4.92 22.48c1.36 0.32 4.64 1.080 7.12 1.080 1.32 0 1.92-0.24 2.12-0.36 0.52-0.32 0.76-1.12 0.84-1.72 0.32-2.24-0.56-5.080-1.040-5.6-0.36-0.4-1.84-0.56-2.8-0.68-1.16-0.12-2.12-0.24-2.76-0.64-0.88-0.6-1.080-1.92-1.24-3.28-0.28-1.96-0.52-2.84-1.24-2.84-0.2 0-0.52 0.040-1 0.24 0 0 0 13.8 0 13.8zM0.84 23.96c-0.48 0-0.84-0.36-0.84-0.84v-8.92c0-0.48 0.36-0.84 0.84-0.84s0.84 0.36 0.84 0.84v8.96c0 0.44-0.36 0.8-0.84 0.8z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1 KiB |
40
src/interface/web/tsconfig.json
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
"out/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
2581
src/interface/web/yarn.lock
Normal file
|
@ -163,7 +163,7 @@ USE_TZ = True
|
||||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||||
|
|
||||||
STATIC_ROOT = BASE_DIR / "static"
|
STATIC_ROOT = BASE_DIR / "static"
|
||||||
STATICFILES_DIRS = [BASE_DIR / "interface/web", BASE_DIR / "interface/email"]
|
STATICFILES_DIRS = [BASE_DIR / "interface/web", BASE_DIR / "interface/email", BASE_DIR / "interface/built"]
|
||||||
STATIC_URL = "/static/"
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
|
|
|
@ -16,9 +16,11 @@ from starlette.authentication import (
|
||||||
SimpleUser,
|
SimpleUser,
|
||||||
UnauthenticatedUser,
|
UnauthenticatedUser,
|
||||||
)
|
)
|
||||||
|
from starlette.middleware import Middleware
|
||||||
from starlette.middleware.authentication import AuthenticationMiddleware
|
from starlette.middleware.authentication import AuthenticationMiddleware
|
||||||
from starlette.middleware.sessions import SessionMiddleware
|
from starlette.middleware.sessions import SessionMiddleware
|
||||||
from starlette.requests import HTTPConnection
|
from starlette.requests import HTTPConnection
|
||||||
|
from starlette.types import ASGIApp, Receive, Scope, Send
|
||||||
|
|
||||||
from khoj.database.adapters import (
|
from khoj.database.adapters import (
|
||||||
AgentAdapters,
|
AgentAdapters,
|
||||||
|
@ -306,7 +308,18 @@ def configure_routes(app):
|
||||||
|
|
||||||
|
|
||||||
def configure_middleware(app):
|
def configure_middleware(app):
|
||||||
|
class NextJsMiddleware(Middleware):
|
||||||
|
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
||||||
|
if scope["type"] == "http" and scope["path"].startswith("/_next"):
|
||||||
|
scope["path"] = "/static" + scope["path"]
|
||||||
|
await self.app(scope, receive, send)
|
||||||
|
|
||||||
|
def __init__(self, app: ASGIApp) -> None:
|
||||||
|
super().__init__(app)
|
||||||
|
self.app = app
|
||||||
|
|
||||||
app.add_middleware(AuthenticationMiddleware, backend=UserAuthenticationBackend())
|
app.add_middleware(AuthenticationMiddleware, backend=UserAuthenticationBackend())
|
||||||
|
app.add_middleware(NextJsMiddleware)
|
||||||
app.add_middleware(SessionMiddleware, secret_key=os.environ.get("KHOJ_DJANGO_SECRET_KEY", "!secret"))
|
app.add_middleware(SessionMiddleware, secret_key=os.environ.get("KHOJ_DJANGO_SECRET_KEY", "!secret"))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ from khoj.utils.rawconfig import (
|
||||||
|
|
||||||
# Initialize Router
|
# Initialize Router
|
||||||
web_client = APIRouter()
|
web_client = APIRouter()
|
||||||
templates = Jinja2Templates(directory=constants.web_directory)
|
templates = Jinja2Templates([constants.web_directory, constants.next_js_directory])
|
||||||
|
|
||||||
|
|
||||||
# Create Routes
|
# Create Routes
|
||||||
|
@ -118,6 +118,17 @@ def chat_page(request: Request):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@web_client.get("/experimental", response_class=FileResponse)
|
||||||
|
@requires(["authenticated"], redirect="login_page")
|
||||||
|
def experimental_page(request: Request):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"index.html",
|
||||||
|
context={
|
||||||
|
"request": request,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@web_client.get("/login", response_class=FileResponse)
|
@web_client.get("/login", response_class=FileResponse)
|
||||||
def login_page(request: Request):
|
def login_page(request: Request):
|
||||||
next_url = get_next_url(request)
|
next_url = get_next_url(request)
|
||||||
|
|
|
@ -2,6 +2,7 @@ from pathlib import Path
|
||||||
|
|
||||||
app_root_directory = Path(__file__).parent.parent.parent
|
app_root_directory = Path(__file__).parent.parent.parent
|
||||||
web_directory = app_root_directory / "khoj/interface/web/"
|
web_directory = app_root_directory / "khoj/interface/web/"
|
||||||
|
next_js_directory = app_root_directory / "khoj/interface/built/"
|
||||||
empty_escape_sequences = "\n|\r|\t| "
|
empty_escape_sequences = "\n|\r|\t| "
|
||||||
app_env_filepath = "~/.khoj/env"
|
app_env_filepath = "~/.khoj/env"
|
||||||
telemetry_server = "https://khoj.beta.haletic.com/v1/telemetry"
|
telemetry_server = "https://khoj.beta.haletic.com/v1/telemetry"
|
||||||
|
|