diff --git a/src/interface/desktop/chat.html b/src/interface/desktop/chat.html
index 7f0fc8a4..ac7cc42b 100644
--- a/src/interface/desktop/chat.html
+++ b/src/interface/desktop/chat.html
@@ -103,7 +103,7 @@
let conversationID = chatBody.dataset.conversationId;
let hostURL = await window.hostURLAPI.getURL();
const khojToken = await window.tokenAPI.getToken();
- const headers = { 'Authorization': `Bearer ${khojToken}` };
+ const headers = { 'Authorization': `Bearer ${khojToken}`, 'Content-Type': 'application/json' };
if (!conversationID) {
let response = await fetch(`${hostURL}/api/chat/sessions`, { method: "POST", headers });
@@ -149,12 +149,22 @@
document.getElementById("send-button").style.display = "none";
// Call Khoj chat API
- let chatApi = `${hostURL}/api/chat?q=${encodeURIComponent(query)}&conversation_id=${conversationID}&stream=true&client=desktop`;
- chatApi += (!!region && !!city && !!countryName && !!timezone)
- ? `®ion=${region}&city=${city}&country=${countryName}&timezone=${timezone}`
- : '';
+ const chatApi = `${hostURL}/api/chat?client=desktop`;
+ const chatApiBody = {
+ q: query,
+ conversation_id: parseInt(conversationID),
+ stream: true,
+ ...(!!city && { city: city }),
+ ...(!!region && { region: region }),
+ ...(!!countryName && { country: countryName }),
+ ...(!!timezone && { timezone: timezone }),
+ };
- const response = await fetch(chatApi, { method: 'POST', headers });
+ const response = await fetch(chatApi, {
+ method: "POST",
+ headers: headers,
+ body: JSON.stringify(chatApiBody),
+ });
try {
if (!response.ok) throw new Error(response.statusText);
diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el
index b984519a..00e16400 100644
--- a/src/interface/emacs/khoj.el
+++ b/src/interface/emacs/khoj.el
@@ -675,14 +675,15 @@ Optionally apply CALLBACK with JSON parsed response and CBARGS."
(json-parse-buffer :object-type 'alist))))
('file-error (message "Chat exception: [%s]" ex))))))
-(defun khoj--call-api-async (path &optional method params callback &rest cbargs)
- "Async call to API at PATH with METHOD and query PARAMS as kv assoc list.
+(defun khoj--call-api-async (path &optional method params body callback &rest cbargs)
+ "Async call to API at PATH with specified METHOD, query PARAMS and request BODY.
Optionally apply CALLBACK with JSON parsed response and CBARGS."
(let* ((url-request-method (or method "GET"))
- (url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key))))
- (param-string (if params (url-build-query-string params) ""))
+ (url-request-extra-headers `(("Authorization" . ,(format "Bearer %s" khoj-api-key)) ("Content-Type" . "application/json")))
+ (url-request-data (if body (json-encode body) nil))
+ (param-string (url-build-query-string (append params '((client "emacs")))))
(cbargs (if (and (listp cbargs) (listp (car cbargs))) (car cbargs) cbargs)) ; normalize cbargs to (a b) from ((a b)) if required
- (query-url (format "%s%s?%s&client=emacs" khoj-server-url path param-string)))
+ (query-url (format "%s%s?%s" khoj-server-url path param-string)))
(url-retrieve query-url
(lambda (status)
(if (plist-get status :error)
@@ -710,6 +711,7 @@ Filter out first similar result if IS-FIND-SIMILAR set."
(khoj--call-api-async path
"GET"
params
+ nil
'khoj--render-search-results
content-type query buffer-name is-find-similar)))
@@ -875,10 +877,11 @@ Filter out first similar result if IS-FIND-SIMILAR set."
(defun khoj--query-chat-api (query session-id callback &rest cbargs)
"Send QUERY for SESSION-ID to Khoj Chat API.
Call CALLBACK func with response and CBARGS."
- (let ((params `(("q" ,query) ("n" ,khoj-results-count))))
- (when session-id (push `("conversation_id" ,session-id) params))
+ (let ((params `(("q" . ,query) ("n" . ,khoj-results-count))))
+ (when session-id (push `("conversation_id" . ,session-id) params))
(khoj--call-api-async "/api/chat"
"POST"
+ nil
params
callback cbargs)))
diff --git a/src/interface/obsidian/src/chat_view.ts b/src/interface/obsidian/src/chat_view.ts
index f55ad46d..365548f5 100644
--- a/src/interface/obsidian/src/chat_view.ts
+++ b/src/interface/obsidian/src/chat_view.ts
@@ -1050,9 +1050,19 @@ export class KhojChatView extends KhojPaneView {
}
// Get chat response from Khoj backend
- let encodedQuery = encodeURIComponent(query);
- let chatUrl = `${this.setting.khojUrl}/api/chat?q=${encodedQuery}&conversation_id=${conversationId}&n=${this.setting.resultsCount}&stream=true&client=obsidian`;
- if (!!this.location) chatUrl += `®ion=${this.location.region}&city=${this.location.city}&country=${this.location.countryName}&timezone=${this.location.timezone}`;
+ const chatUrl = `${this.setting.khojUrl}/api/chat?client=obsidian`;
+ const body = {
+ q: query,
+ n: this.setting.resultsCount,
+ stream: true,
+ ...(!!conversationId && { conversation_id: parseInt(conversationId) }),
+ ...(!!this.location && {
+ city: this.location.city,
+ region: this.location.region,
+ country: this.location.countryName,
+ timezone: this.location.timezone,
+ }),
+ };
let newResponseEl = this.createKhojResponseDiv();
let newResponseTextEl = newResponseEl.createDiv();
@@ -1079,6 +1089,7 @@ export class KhojChatView extends KhojPaneView {
"Content-Type": "application/json",
"Authorization": `Bearer ${this.setting.khojApiKey}`,
},
+ body: JSON.stringify(body),
})
try {
diff --git a/src/interface/web/app/chat/page.tsx b/src/interface/web/app/chat/page.tsx
index 2b9155d2..1a9611f6 100644
--- a/src/interface/web/app/chat/page.tsx
+++ b/src/interface/web/app/chat/page.tsx
@@ -232,17 +232,26 @@ export default function Chat() {
async function chat() {
localStorage.removeItem("message");
if (!queryToProcess || !conversationId) return;
- let chatAPI = `/api/chat?q=${encodeURIComponent(queryToProcess)}&conversation_id=${conversationId}&stream=true&client=web`;
- if (locationData) {
- chatAPI += `®ion=${locationData.region}&country=${locationData.country}&city=${locationData.city}&timezone=${locationData.timezone}`;
- }
+ const chatAPI = "/api/chat?client=web";
+ const chatAPIBody = {
+ q: queryToProcess,
+ conversation_id: parseInt(conversationId),
+ stream: true,
+ ...(locationData && {
+ region: locationData.region,
+ country: locationData.country,
+ city: locationData.city,
+ timezone: locationData.timezone,
+ }),
+ ...(image64 && { image: image64 }),
+ };
const response = await fetch(chatAPI, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
- body: image64 ? JSON.stringify({ image: image64 }) : undefined,
+ body: JSON.stringify(chatAPIBody),
});
try {
diff --git a/src/interface/web/app/share/chat/page.tsx b/src/interface/web/app/share/chat/page.tsx
index eefc159c..f6788948 100644
--- a/src/interface/web/app/share/chat/page.tsx
+++ b/src/interface/web/app/share/chat/page.tsx
@@ -222,17 +222,26 @@ export default function SharedChat() {
async function chat() {
if (!queryToProcess || !conversationId) return;
- let chatAPI = `/api/chat?q=${encodeURIComponent(queryToProcess)}&conversation_id=${conversationId}&stream=true&client=web`;
- if (locationData) {
- chatAPI += `®ion=${locationData.region}&country=${locationData.country}&city=${locationData.city}&timezone=${locationData.timezone}`;
- }
+ const chatAPI = "/api/chat?client=web";
+ const chatAPIBody = {
+ q: queryToProcess,
+ conversation_id: parseInt(conversationId),
+ stream: true,
+ ...(locationData && {
+ region: locationData.region,
+ country: locationData.country,
+ city: locationData.city,
+ timezone: locationData.timezone,
+ }),
+ ...(image64 && { image: image64 }),
+ };
const response = await fetch(chatAPI, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
- body: image64 ? JSON.stringify({ image: image64 }) : undefined,
+ body: JSON.stringify(chatAPIBody),
});
try {
diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py
index 38cf5d3d..0e83b9c2 100644
--- a/src/khoj/routers/api_chat.py
+++ b/src/khoj/routers/api_chat.py
@@ -520,8 +520,18 @@ async def set_conversation_title(
)
-class ImageUploadObject(BaseModel):
- image: str
+class ChatRequestBody(BaseModel):
+ q: str
+ n: Optional[int] = 7
+ d: Optional[float] = None
+ stream: Optional[bool] = False
+ title: Optional[str] = None
+ conversation_id: Optional[int] = None
+ city: Optional[str] = None
+ region: Optional[str] = None
+ country: Optional[str] = None
+ timezone: Optional[str] = None
+ image: Optional[str] = None
@api_chat.post("")
@@ -529,17 +539,7 @@ class ImageUploadObject(BaseModel):
async def chat(
request: Request,
common: CommonQueryParams,
- q: str,
- n: int = 7,
- d: float = None,
- stream: Optional[bool] = False,
- title: Optional[str] = None,
- conversation_id: Optional[int] = None,
- city: Optional[str] = None,
- region: Optional[str] = None,
- country: Optional[str] = None,
- timezone: Optional[str] = None,
- image: Optional[ImageUploadObject] = None,
+ body: ChatRequestBody,
rate_limiter_per_minute=Depends(
ApiUserRateLimiter(requests=60, subscribed_requests=60, window=60, slug="chat_minute")
),
@@ -547,7 +547,20 @@ async def chat(
ApiUserRateLimiter(requests=600, subscribed_requests=600, window=60 * 60 * 24, slug="chat_day")
),
):
- async def event_generator(q: str, image: ImageUploadObject):
+ # Access the parameters from the body
+ q = body.q
+ n = body.n
+ d = body.d
+ stream = body.stream
+ title = body.title
+ conversation_id = body.conversation_id
+ city = body.city
+ region = body.region
+ country = body.country
+ timezone = body.timezone
+ image = body.image
+
+ async def event_generator(q: str, image: str):
start_time = time.perf_counter()
ttft = None
chat_metadata: dict = {}
@@ -560,7 +573,7 @@ async def chat(
uploaded_image_url = None
if image:
- decoded_string = unquote(image.image)
+ decoded_string = unquote(image)
base64_data = decoded_string.split(",", 1)[1]
image_bytes = base64.b64decode(base64_data)
webp_image_bytes = convert_image_to_webp(image_bytes)