Unify login via popup on home. No need for separate login html page.

Delete old login html page. Login via popup on home is the single,
unified login experience.

Have docs mention khoj home url, no need to mention /login as login
popup shows on home page too
This commit is contained in:
Debanjum
2025-12-28 17:11:52 -08:00
parent f65f6ae848
commit 5e65754a8b
7 changed files with 11 additions and 328 deletions

View File

@@ -24,7 +24,7 @@ It's still possible to use the magic links feature without Resend, but you'll ne
## Manually sending magic links
1. The user will have to enter their email address in the login page at http://localhost:42110/login.
1. The user will have to enter their email address in the login popup shown at http://localhost:42110/?v=app.
They'll click `Get Login Link`. Without the Resend API key, this will just create an unverified account for them in the backend
<img src="/img/magic_link.png" alt="Magic link login form" width="400"/>

View File

@@ -17,7 +17,7 @@ Khoj supports a variety of features, including search and chat with a wide range
- **Works online or offline**: Chat using online or offline AI chat models
#### General
- **Cloud or Self-Host**: Use [cloud](https://app.khoj.dev/login) to use Khoj anytime from anywhere or [self-host](/get-started/setup) for privacy
- **Cloud or Self-Host**: Use [cloud](https://app.khoj.dev) to use Khoj anytime from anywhere or [self-host](/get-started/setup) for privacy
- **Natural**: Advanced natural language understanding using Transformer based ML Models
- **Pluggable**: Modular architecture makes it easy to plug in new data sources, frontends and ML models
- **Multiple Sources**: Index your Org-mode, Markdown, PDF, plaintext files, Github repos and Notion pages

View File

@@ -28,7 +28,7 @@ Welcome to the Khoj Docs! This is the best place to get setup and explore Khoj's
- Quickly [find](/features/search) relevant notes and documents using natural language
- It understands pdf, plaintext, markdown, org-mode files, and [notion pages](/data-sources/notion_integration).
- Access it from your [Emacs](/clients/emacs), [Obsidian](/clients/obsidian), the [Khoj desktop app](/clients/desktop), or [any web browser](/clients/web)
- Use our [cloud](https://app.khoj.dev/login) instance to access your Khoj anytime from anywhere, [self-host](/get-started/setup) on consumer hardware for privacy
- Use our [cloud](https://app.khoj.dev) instance to access your Khoj anytime from anywhere, [self-host](/get-started/setup) on consumer hardware for privacy
![demo_chat](https://assets.khoj.dev/quadratic_equation_khoj_web.gif)

View File

@@ -17,7 +17,7 @@ Here's what to consider if you're using Khoj, whether self-hosted or on our clou
- If you're self-hosting, you can opt out of telemetry by following [these instructions](/miscellaneous/telemetry).
Self-hosting isn't for everyone, so we've still taken steps to make Khoj privacy-friendly, even if you choose to use our [cloud offering](https://app.khoj.dev/login). Here's what to consider when using Khoj Cloud:
Self-hosting isn't for everyone, so we've still taken steps to make Khoj privacy-friendly, even if you choose to use our [cloud offering](https://app.khoj.dev). Here's what to consider when using Khoj Cloud:
1. Your embeddings are generated by an open source model within our own dedicated endpoint [hosted on AWS with Huggingface](https://huggingface.co/inference-endpoints/dedicated). There's zero persistent memory to the Huggingface Inference endpoints (it's stateless).
1. Your embeddings and the associated raw text are stored in a secure Postgres DB in our private AWS cloud. Your data is sharded on a unique user ID. We store the raw text in your files to improve file syncing and provide context when you chat with Khoj.
1. When you use the single-sign-on option with Google, we only receive your name, a link to your profile photo, and your email address.

View File

@@ -103,7 +103,7 @@ const config = {
'aria-label': 'GitHub repository',
},
{
href: 'https://app.khoj.dev/login',
href: 'https://app.khoj.dev',
position: 'right',
className: 'header-cloud-link',
title: 'Khoj Cloud',
@@ -191,14 +191,14 @@ const config = {
},
{
label: 'Khoj Cloud',
href: 'https://app.khoj.dev/login',
href: 'https://app.khoj.dev',
},
{
label: 'GitHub',
href: 'https://github.com/khoj-ai/khoj',
},
{
label: 'Website',
label: 'Khoj Inc.',
href: 'https://khoj.dev',
},
],

View File

@@ -1,310 +0,0 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<title>Khoj - Login</title>
<link rel="icon" type="image/png" sizes="128x128" href="https://assets.khoj.dev/khoj_lantern_128x128.png">
<link rel="manifest" href="/static/khoj.webmanifest">
<link rel="stylesheet" href="/static/assets/khoj.css">
<meta property="og:image" content="https://assets.khoj.dev/khoj_hero.png">
</head>
<body>
<div class="split-container">
<!-- Left side with login -->
<div class="split left">
<div id="login-modal">
<img class="khoj-logo" src="https://assets.khoj.dev/khoj_lantern_128x128.png" alt="Khoj">
<div class="login-modal-title">Login to Khoj</div>
<!-- Sign Up/Login with Google OAuth -->
<div class="g_id_signin" data-shape="circle" data-text="continue_with" data-logo_alignment="center"
data-size="large" data-type="standard"></div>
<div id="g_id_onload" data-client_id="{{ google_client_id }}" data-ux_mode="popup"
data-use_fedcm_for_prompt="true" data-login_uri="{{ redirect_uri }}" data-auto-select="true"></div>
<!-- Divider -->
<div class="divider">OR</div>
<!-- Sign in with Magic Link -->
<div class="khoj-magic-link">
<input type="email" id="email" placeholder="Email" autofocus required>
<button id="magic-link-button">Get Login Link</button>
</div>
</div>
<!-- Footer links -->
<div class="footer-links">
<a href="https://khoj.dev/terms-of-service" target="_blank">Terms of Service</a>
<span class="divider-vertical"></span>
<a href="https://khoj.dev/privacy-policy" target="_blank">Privacy Policy</a>
</div>
</div>
<!-- Right side with content -->
<div class="split right">
<div class="right-content">
<h1>Unlock Your Second Brain</h1>
<p>Transform the way you think, create, and remember</p>
<div class="features">
<div class="feature">
<img src="/static/assets/icons/chat.svg" alt="Chat" width="24" height="24">
<span>Get answers across your documents and the internet</span>
</div>
<div class="feature">
<img src="/static/assets/icons/agents.svg" alt="Agents" width="24" height="24">
<span>Create agents with the knowledge and tools to take on any role</span>
</div>
<div class="feature">
<img src="/static/assets/icons/automation.svg" alt="Automations" width="24" height="24">
<span>Automate away repetitive research</span>
</div>
</div>
</div>
</div>
</div>
<style>
body {
margin: 0;
height: 100vh;
font-family: 'Arial', sans-serif;
color: #333;
overflow: hidden;
}
.split-container {
display: flex;
height: 100vh;
}
.split {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.left {
background-color: #fff;
}
.right {
background: linear-gradient(135deg, #FFA07A 0%, #c4e4c6 100%);
color: white;
position: relative;
overflow: hidden;
}
.right::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0.1;
}
.right-content {
text-align: center;
padding: 2rem;
position: relative;
z-index: 1;
}
.right-content h1 {
font-size: 3rem;
margin-bottom: 1rem;
font-weight: 700;
}
.right-content p {
font-size: 1.2rem;
margin-bottom: 3rem;
opacity: 0.9;
}
.features {
display: flex;
flex-direction: column;
gap: 1.5rem;
align-items: center;
}
.feature {
display: flex;
align-items: center;
gap: 1rem;
font-size: 1.1rem;
}
.feature svg {
width: 24px;
height: 24px;
stroke: white;
}
.feature img {
width: 24px;
height: 24px;
filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(100%) contrast(100%);
stroke: white;
}
#login-modal {
display: grid;
background: white;
border-radius: 10px;
padding: 40px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
text-align: center;
width: max-content;
}
.khoj-logo {
width: 80px;
margin-bottom: 20px;
}
.login-modal-title {
font-size: 24px;
font-weight: 600;
margin-bottom: 20px;
}
.khoj-magic-link {
display: flex;
flex-direction: column;
gap: 10px;
}
#email {
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
width: 100%;
font-size: 16px;
}
#magic-link-button {
padding: 10px;
border: none;
border-radius: 5px;
background-color: #FFA07A;
color: white;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
#magic-link-button:hover {
background-color: #FFA07A
}
#magic-link-button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.divider {
display: flex;
align-items: center;
text-align: center;
color: #000;
/* Adjust the text color as needed */
margin: 20px 0;
/* Adjust the margin as needed */
}
.divider::before,
.divider::after {
content: '';
flex: 1;
border-bottom: 1px solid #000;
/* Adjust the line color as needed */
margin: 0 10px;
/* Adjust the spacing as needed */
}
.g_id_signin {
margin: 0 auto;
display: block;
}
.footer-links {
width: 100%;
text-align: center;
font-size: 0.9em;
color: #666;
margin-top: 20px;
}
.footer-links a {
color: #666;
text-decoration: none;
margin: 0 10px;
}
.footer-links a:hover {
text-decoration: underline;
}
.divider-vertical {
display: inline-block;
width: 1px;
height: 12px;
background-color: #666;
margin: 0 10px;
vertical-align: middle;
}
@media (max-width: 768px) {
.split-container {
flex-direction: column;
}
.right {
display: none;
}
.left {
padding: 2rem;
}
}
</style>
<script>
const magicLinkButton = document.getElementById('magic-link-button');
const emailInput = document.getElementById('email');
magicLinkButton.addEventListener('click', async () => {
const email = emailInput.value;
if (!email) {
alert('Please enter a valid email address');
return;
}
if (!email.includes('@')) {
alert('Please enter a valid email address');
return;
}
magicLinkButton.disabled = true;
magicLinkButton.innerText = 'Check your email for a sign-in link!';
const response = await fetch('/auth/magic', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ "email": email }),
});
if (response.status === 200) {
console.log('Magic link sent to your email');
} else {
alert('Failed to send magic link');
magicLinkButton.disabled = false;
magicLinkButton.innerText = 'Get Login Link';
}
});
</script>
<script src="https://accounts.google.com/gsi/client" async defer></script>
</body>
</html>

View File

@@ -1,6 +1,5 @@
# System Packages
import json
import os
from fastapi import APIRouter, Request
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
@@ -56,16 +55,10 @@ def login_page(request: Request):
next_url = get_next_url(request)
if request.user.is_authenticated:
return RedirectResponse(url=next_url)
google_client_id = os.environ.get("GOOGLE_CLIENT_ID")
redirect_uri = str(request.app.url_path_for("auth_post"))
return templates.TemplateResponse(
"login.html",
context={
"request": request,
"google_client_id": google_client_id,
"redirect_uri": f"{redirect_uri}?next={next_url}",
},
)
# Redirect to main app which shows the login popup for unauthenticated users
# Append v=app to prevent redirect loop back to /home
redirect_url = f"/?v=app&next={next_url}" if next_url != "/" else "/?v=app"
return RedirectResponse(url=redirect_url)
@web_client.get("/agents", response_class=HTMLResponse)