Compare commits

...

7 Commits

Author SHA1 Message Date
Debanjum
9258f57dce Release Khoj version 2.0.0-beta.28 2026-03-26 09:03:47 +05:30
Debanjum
171ac5d243 Add deprecation banner to top of web landing page as well 2026-03-26 08:59:05 +05:30
Debanjum
fdd5fd8f74 Fix getting billing config to show deprecation banner on Khoj cloud 2026-03-26 08:59:05 +05:30
Debanjum
8965db7087 Bump server, ciient dependencies 2026-03-26 08:59:05 +05:30
lif
8b8504edb8 Fix AttributeError when memories disabled and setting is None (#1296)
## Summary
- Add null checks for `config.setting` in `get_chat_model()` and
`aget_chat_model()` to prevent `AttributeError` when memories are
disabled
- When the memory toggle creates a `UserConversationConfig` via
`get_or_create` with `setting=None`, accessing
`config.setting.price_tier` crashes — now falls through to the default
chat model instead

## Root Cause
The "Enable Memories" toggle PATCH endpoint uses `get_or_create` on
`UserConversationConfig`, which can create a config with `setting=None`.
Both `get_chat_model()` and `aget_chat_model()` then crash:
- For subscribed users: `if config:` passes but `return config.setting`
returns `None`, causing downstream crashes
- For non-subscribed users: `config.setting.price_tier` raises
`AttributeError` on `None`

## Fix
Change `if config:` → `if config and config.setting:` (subscribed path)
and add `and config.setting` guard before `.price_tier` access
(non-subscribed path), in both sync and async variants.

## Test plan
- [ ] Toggle memories off with no prior chat model configured — settings
page should still load
- [ ] Chat responses should use default model when setting is None
- [ ] Existing users with configured chat models should be unaffected

Fixes #1287

Signed-off-by: majiayu000 <1835304752@qq.com>
2026-03-26 08:26:25 +05:30
Debanjum
7475a781bc Release Khoj version 2.0.0-beta.27 2026-03-26 01:40:02 +05:30
Debanjum
a19e7acd5a Fix TemplateResponse calls to be compatible with Starlette 1.0.0
Starlette 1.0.0 removed the deprecated TemplateResponse signature
where `name` was the first positional arg and `request` was passed
inside `context`. The new signature requires `request` as the first
positional argument: TemplateResponse(request, name=...).

This caused a 500 error in production on web client endpoints with:
"Jinja2Templates.TemplateResponse() missing 1 required positional
argument: 'name'" (with older Starlette) or "'request'" (with 1.0.0).

Update all TemplateResponse calls in web_client.py to use the new
Starlette 1.0.0 signature: pass `request` as the first positional
arg and `name` as an explicit keyword argument.

Issue didn't trigger locally as uv is used locally and pip in docker
builds. These resolve dependencies including starletter version to
install differently. Locally 0.52.0 was installed while on production
starlette 1.0.0 was used. This is what caused the issue and the
mismatch in expectation
2026-03-26 01:38:41 +05:30
19 changed files with 85 additions and 71 deletions

View File

@@ -45,6 +45,7 @@
},
"resolutions": {
"webpack-dev-server": "^5.2.1",
"serialize-javascript": "^7.0.3"
"serialize-javascript": "^7.0.3",
"picomatch": ">=2.3.2"
}
}

View File

@@ -6904,10 +6904,10 @@ picocolors@^1.0.0, picocolors@^1.1.1:
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
picomatch@>=2.3.2, picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1:
version "4.0.4"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589"
integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
pkg-dir@^7.0.0:
version "7.0.0"

View File

@@ -1,7 +1,7 @@
{
"id": "khoj",
"name": "Khoj",
"version": "2.0.0-beta.26",
"version": "2.0.0-beta.28",
"minAppVersion": "0.15.0",
"description": "Your Second Brain",
"author": "Khoj Inc.",

View File

@@ -60,7 +60,7 @@ dependencies = [
"aiohttp ~= 3.13.0",
"langchain-text-splitters == 0.3.11",
"langchain-community == 0.3.31",
"requests >= 2.32.4",
"requests >= 2.33.0",
"anyio ~= 4.8.0",
"pymupdf == 1.24.11",
"django == 5.1.15",

View File

@@ -1,6 +1,6 @@
{
"name": "Khoj",
"version": "2.0.0-beta.26",
"version": "2.0.0-beta.28",
"description": "Your Second Brain",
"author": "Khoj Inc. <team@khoj.dev>",
"license": "GPL-3.0-or-later",
@@ -22,6 +22,7 @@
"electron-store": "^8.1.0"
},
"resolutions": {
"ajv": "^8.18.0"
"ajv": "^8.18.0",
"picomatch": ">=2.3.2"
}
}

View File

@@ -1059,10 +1059,10 @@ pend@~1.2.0:
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
picomatch@>=2.3.2, picomatch@^2.3.1:
version "4.0.4"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589"
integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
pkg-up@^3.1.0:
version "3.1.0"

View File

@@ -6,7 +6,7 @@
;; Saba Imran <saba@khoj.dev>
;; Description: Your Second Brain
;; Keywords: search, chat, ai, org-mode, outlines, markdown, pdf, image
;; Version: 2.0.0-beta.26
;; Version: 2.0.0-beta.28
;; 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

View File

@@ -1,7 +1,7 @@
{
"id": "khoj",
"name": "Khoj",
"version": "2.0.0-beta.26",
"version": "2.0.0-beta.28",
"minAppVersion": "0.15.0",
"description": "Your Second Brain",
"author": "Khoj Inc.",

View File

@@ -1,6 +1,6 @@
{
"name": "Khoj",
"version": "2.0.0-beta.26",
"version": "2.0.0-beta.28",
"description": "Your Second Brain",
"author": "Debanjum Singh Solanky, Saba Imran <team@khoj.dev>",
"license": "GPL-3.0-or-later",
@@ -31,5 +31,8 @@
"dependencies": {
"diff": "^8.0.3",
"isomorphic-dompurify": "^2.25.0"
},
"resolutions": {
"picomatch": ">=2.3.2"
}
}

View File

@@ -162,5 +162,7 @@
"2.0.0-beta.23": "0.15.0",
"2.0.0-beta.24": "0.15.0",
"2.0.0-beta.25": "0.15.0",
"2.0.0-beta.26": "0.15.0"
"2.0.0-beta.26": "0.15.0",
"2.0.0-beta.27": "0.15.0",
"2.0.0-beta.28": "0.15.0"
}

View File

@@ -662,10 +662,10 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
picomatch@>=2.3.2, picomatch@^2.3.1:
version "4.0.4"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589"
integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
punycode@^2.3.1:
version "2.3.1"

View File

@@ -9,7 +9,7 @@ const DISMISS_KEY = "khoj-cloud-deprecation-dismissed";
export function DeprecationBanner() {
const [isDismissed, setIsDismissed] = useState(true);
const { data: userConfig } = useUserConfig();
const { data: userConfig } = useUserConfig(true);
useEffect(() => {
setIsDismissed(localStorage.getItem(DISMISS_KEY) === "true");

View File

@@ -84,6 +84,9 @@
},
},
},
"overrides": {
"picomatch": ">=2.3.2",
},
"packages": {
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
@@ -1169,7 +1172,7 @@
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="],
@@ -1577,8 +1580,6 @@
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
"tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],

View File

@@ -1,6 +1,6 @@
{
"name": "khoj-ai",
"version": "2.0.0-beta.26",
"version": "2.0.0-beta.28",
"private": true,
"scripts": {
"dev": "next dev",
@@ -102,5 +102,8 @@
},
"lint-staged": {
"*": "bun run lint"
},
"overrides": {
"picomatch": ">=2.3.2"
}
}

View File

@@ -1192,13 +1192,13 @@ class ConversationAdapters:
config = UserConversationConfig.objects.filter(user=user).first()
if subscribed:
# Subscibed users can use any available chat model
if config:
if config and config.setting:
return config.setting
# Fallback to the default advanced chat model
return ConversationAdapters.get_advanced_chat_model(user)
else:
# Non-subscribed users can use any free chat model
if config and config.setting.price_tier == PriceTier.FREE:
if config and config.setting and config.setting.price_tier == PriceTier.FREE:
return config.setting
# Fallback to the default chat model
return ConversationAdapters.get_default_chat_model(user)
@@ -1213,13 +1213,13 @@ class ConversationAdapters:
)
if subscribed:
# Subscibed users can use any available chat model
if config:
if config and config.setting:
return config.setting
# Fallback to the default advanced chat model
return await ConversationAdapters.aget_advanced_chat_model(user)
else:
# Non-subscribed users can use any free chat model
if config and config.setting.price_tier == PriceTier.FREE:
if config and config.setting and config.setting.price_tier == PriceTier.FREE:
return config.setting
# Fallback to the default chat model
return await ConversationAdapters.aget_default_chat_model(user)

View File

@@ -13,6 +13,32 @@
<link rel="icon" href="/home/favicon.png" type="image/png">
</head>
<body>
<!-- Deprecation Banner -->
<div id="deprecation-banner" style="display:none; width:100%; padding:8px 16px; background:rgba(234,88,12,0.9); text-align:center; font-size:14px; color:#fff7ed; position:fixed; top:0; left:0; right:0; z-index:1100;">
<span style="margin-right:8px;">&#9888;</span>
<strong>Khoj Cloud is being deprecated on April 15, 2026.</strong>
Please <a href="/settings#account" style="color:white; font-weight:500; text-decoration:underline;">export your data</a> before then.
To continue using Khoj, you can <a href="https://docs.khoj.dev/get-started/setup" target="_blank" rel="noopener noreferrer" style="color:white; font-weight:500; text-decoration:underline;">self-host it</a>.
<button onclick="dismissDeprecationBanner()" style="margin-left:8px; background:none; border:none; color:#fff7ed; cursor:pointer; font-size:16px;" aria-label="Dismiss banner">&times;</button>
</div>
<script>
function dismissDeprecationBanner() {
localStorage.setItem('khoj-cloud-deprecation-dismissed', 'true');
document.getElementById('deprecation-banner').style.display = 'none';
document.querySelector('.navbar').style.top = '0';
document.body.style.paddingTop = '0';
}
if (localStorage.getItem('khoj-cloud-deprecation-dismissed') !== 'true') {
var banner = document.getElementById('deprecation-banner');
banner.style.display = 'block';
requestAnimationFrame(function() {
var bannerHeight = banner.offsetHeight + 'px';
document.querySelector('.navbar').style.top = bannerHeight;
document.body.style.paddingTop = bannerHeight;
});
}
</script>
<!-- Navigation -->
<nav class="navbar">
<div class="nav-container">

View File

@@ -25,13 +25,13 @@ def index(request: Request):
if not state.anonymous_mode and not request.user.is_authenticated:
if "v" not in request.query_params:
return RedirectResponse(url="/home")
return templates.TemplateResponse("index.html", context={"request": request})
return templates.TemplateResponse(request, name="index.html")
@web_client.post("/", response_class=FileResponse)
@requires(["authenticated"], redirect="login_page")
def index_post(request: Request):
return templates.TemplateResponse("index.html", context={"request": request})
return templates.TemplateResponse(request, name="index.html")
@web_client.get("/home", response_class=HTMLResponse)
@@ -40,7 +40,7 @@ def home_page(request: Request):
# If user is authenticated, redirect to main app
if request.user.is_authenticated:
return RedirectResponse(url="/")
return home_templates.TemplateResponse("index.html", context={"request": request})
return home_templates.TemplateResponse(request, name="index.html")
@web_client.get("/home/{file_path:path}", response_class=FileResponse)
@@ -55,23 +55,13 @@ def home_static_files(file_path: str):
@web_client.get("/search", response_class=FileResponse)
@requires(["authenticated"], redirect="login_page")
def search_page(request: Request):
return templates.TemplateResponse(
"search/index.html",
context={
"request": request,
},
)
return templates.TemplateResponse(request, name="search/index.html")
@web_client.get("/chat", response_class=FileResponse)
@requires(["authenticated"], redirect="login_page")
def chat_page(request: Request):
return templates.TemplateResponse(
"chat/index.html",
context={
"request": request,
},
)
return templates.TemplateResponse(request, name="chat/index.html")
@web_client.get("/login", response_class=FileResponse)
@@ -87,18 +77,13 @@ def login_page(request: Request):
@web_client.get("/agents", response_class=HTMLResponse)
def agents_page(request: Request):
return templates.TemplateResponse(
"agents/index.html",
context={
"request": request,
},
)
return templates.TemplateResponse(request, name="agents/index.html")
@web_client.get("/settings", response_class=HTMLResponse)
@requires(["authenticated"], redirect="login_page")
def config_page(request: Request):
return templates.TemplateResponse("settings/index.html", context={"request": request})
return templates.TemplateResponse(request, name="settings/index.html")
@web_client.get("/settings/content/github", response_class=HTMLResponse)
@@ -128,29 +113,19 @@ def github_config_page(request: Request):
current_config = {} # type: ignore
user_config["current_config"] = current_config
return templates.TemplateResponse("content_source_github_input.html", context=user_config)
return templates.TemplateResponse(request, name="content_source_github_input.html", context=user_config)
@web_client.get("/share/chat/{public_conversation_slug}", response_class=HTMLResponse)
def view_public_conversation(request: Request):
return templates.TemplateResponse(
"share/chat/index.html",
context={
"request": request,
},
)
return templates.TemplateResponse(request, name="share/chat/index.html")
@web_client.get("/automations", response_class=HTMLResponse)
def automations_config_page(
request: Request,
):
return templates.TemplateResponse(
"automations/index.html",
context={
"request": request,
},
)
return templates.TemplateResponse(request, name="automations/index.html")
@web_client.get("/.well-known/assetlinks.json", response_class=FileResponse)
@@ -160,4 +135,4 @@ def assetlinks(request: Request):
@web_client.get("/server/error", response_class=HTMLResponse)
def server_error_page(request: Request):
return templates.TemplateResponse("error.html", context={"request": request})
return templates.TemplateResponse(request, name="error.html")

8
uv.lock generated
View File

@@ -1394,7 +1394,7 @@ requires-dist = [
{ name = "pytz", specifier = "~=2024.1" },
{ name = "pyyaml", specifier = "~=6.0" },
{ name = "rapidocr-onnxruntime", specifier = "==1.4.4" },
{ name = "requests", specifier = ">=2.32.4" },
{ name = "requests", specifier = ">=2.33.0" },
{ name = "resend", specifier = "==1.2.0" },
{ name = "rich", specifier = ">=13.3.1" },
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.12.0" },
@@ -3040,7 +3040,7 @@ wheels = [
[[package]]
name = "requests"
version = "2.32.5"
version = "2.33.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
@@ -3048,9 +3048,9 @@ dependencies = [
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
{ url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" },
]
[[package]]

View File

@@ -162,5 +162,7 @@
"2.0.0-beta.23": "0.15.0",
"2.0.0-beta.24": "0.15.0",
"2.0.0-beta.25": "0.15.0",
"2.0.0-beta.26": "0.15.0"
"2.0.0-beta.26": "0.15.0",
"2.0.0-beta.27": "0.15.0",
"2.0.0-beta.28": "0.15.0"
}