Skip to content

Commit

Permalink
Pass query params to chat API in POST body instead of URL query string
Browse files Browse the repository at this point in the history
Closes #899, #678
  • Loading branch information
debanjum committed Sep 10, 2024
1 parent fc6345e commit 596db60
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 41 deletions.
22 changes: 16 additions & 6 deletions src/interface/desktop/chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down Expand Up @@ -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)
? `&region=${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);
Expand Down
17 changes: 10 additions & 7 deletions src/interface/emacs/khoj.el
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)))

Expand Down Expand Up @@ -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)))

Expand Down
17 changes: 14 additions & 3 deletions src/interface/obsidian/src/chat_view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 += `&region=${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();
Expand All @@ -1079,6 +1089,7 @@ export class KhojChatView extends KhojPaneView {
"Content-Type": "application/json",
"Authorization": `Bearer ${this.setting.khojApiKey}`,
},
body: JSON.stringify(body),
})

try {
Expand Down
19 changes: 14 additions & 5 deletions src/interface/web/app/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 += `&region=${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 {
Expand Down
19 changes: 14 additions & 5 deletions src/interface/web/app/share/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 += `&region=${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 {
Expand Down
43 changes: 28 additions & 15 deletions src/khoj/routers/api_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,34 +520,47 @@ 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("")
@requires(["authenticated"])
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")
),
rate_limiter_per_day=Depends(
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 = {}
Expand All @@ -560,7 +573,7 @@ async def event_generator(q: str, image: ImageUploadObject):

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)
Expand Down

0 comments on commit 596db60

Please sign in to comment.