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.
This commit is contained in:
sabaimran 2024-06-22 07:42:41 -07:00 committed by GitHub
parent 59edb99f04
commit a53178cab9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 8828 additions and 7 deletions

View file

@ -8,6 +8,7 @@ on:
- master
paths:
- src/khoj/**
- src/interface/web/**
- pyproject.toml
- Dockerfile
- prod.Dockerfile
@ -30,8 +31,9 @@ on:
env:
# Tag Image with tag name on release
# 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)
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:
build:

View file

@ -8,6 +8,7 @@ on:
- 'master'
paths:
- src/khoj/**
- src/interface/web/**
- pyproject.toml
- .github/workflows/pypi.yml
pull_request:
@ -15,6 +16,7 @@ on:
- 'master'
paths:
- src/khoj/**
- src/interface/web/**
- pyproject.toml
- .github/workflows/pypi.yml
@ -37,6 +39,16 @@ jobs:
- name: ⬇️ Install Application
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
run: |
# Setup Environment for Reproducible Builds

1
.gitignore vendored
View file

@ -16,6 +16,7 @@ todesktop.json
# Build artifacts
/src/khoj/interface/web/images
/src/khoj/interface/built/
/build/
/dist/
khoj_assistant.egg-info

View file

@ -3,11 +3,17 @@ FROM ubuntu:jammy
LABEL org.opencontainers.image.source https://github.com/khoj-ai/khoj
# Install System Dependencies
RUN apt update -y && apt -y install python3-pip 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
WORKDIR /app
COPY pyproject.toml .
COPY README.md .
ARG VERSION=0.0.0
@ -20,6 +26,11 @@ COPY . .
# Set the PYTHONPATH environment variable in order for it to find the Django app.
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
# There are more arguments required for the application to run,
# but these should be passed in through the docker-compose.yml file.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.3 KiB

View file

@ -3,7 +3,14 @@ FROM ubuntu:jammy
LABEL org.opencontainers.image.source https://github.com/khoj-ai/khoj
# Install System Dependencies
RUN apt update -y && apt -y install python3-pip 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
@ -20,6 +27,11 @@ COPY . .
# Set the PYTHONPATH environment variable in order for it to find the Django app.
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
# There are more arguments required for the application to run,
# but these should be passed in through the docker-compose.yml file.

View file

@ -0,0 +1 @@
NEXT_PUBLIC_ENV='development'

View file

@ -0,0 +1 @@
NEXT_PUBLIC_ENV='production'

View file

@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

36
src/interface/web/.gitignore vendored Normal file
View 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

View 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!

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

View 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;
}
} */

View 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>
);
}

View 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);
}
}

View file

@ -0,0 +1,9 @@
import styles from "./page.module.css";
export default function Home() {
return (
<main className={styles.main}>
Hi, Khoj here.
</main>
);
}

View 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}`
}

View 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;

View 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"
}
}

View 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

View 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

View 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

View 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

View 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

View 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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.2 MiB

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load diff

View file

@ -163,7 +163,7 @@ USE_TZ = True
# https://docs.djangoproject.com/en/4.2/howto/static-files/
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/"
# Default primary key field type

View file

@ -16,9 +16,11 @@ from starlette.authentication import (
SimpleUser,
UnauthenticatedUser,
)
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.middleware.sessions import SessionMiddleware
from starlette.requests import HTTPConnection
from starlette.types import ASGIApp, Receive, Scope, Send
from khoj.database.adapters import (
AgentAdapters,
@ -306,7 +308,18 @@ def configure_routes(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(NextJsMiddleware)
app.add_middleware(SessionMiddleware, secret_key=os.environ.get("KHOJ_DJANGO_SECRET_KEY", "!secret"))

View file

@ -34,7 +34,7 @@ from khoj.utils.rawconfig import (
# Initialize Router
web_client = APIRouter()
templates = Jinja2Templates(directory=constants.web_directory)
templates = Jinja2Templates([constants.web_directory, constants.next_js_directory])
# 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)
def login_page(request: Request):
next_url = get_next_url(request)

View file

@ -2,6 +2,7 @@ from pathlib import Path
app_root_directory = Path(__file__).parent.parent.parent
web_directory = app_root_directory / "khoj/interface/web/"
next_js_directory = app_root_directory / "khoj/interface/built/"
empty_escape_sequences = "\n|\r|\t| "
app_env_filepath = "~/.khoj/env"
telemetry_server = "https://khoj.beta.haletic.com/v1/telemetry"