Skip to content

Commit 8eef5a1

Browse files
committed
Vectors: Updated query response to use server-side-events
Allowing the vector query results and the LLM response to each come back over the same HTTP request at two different times via a somewhat standard. Uses a package for JS SSE client, since native browser client does not support over POST, which is probably important for this endpoint as we don't want crawlers or other bots abusing this via accidentally.
1 parent 88ccd9e commit 8eef5a1

File tree

5 files changed

+88
-7
lines changed

5 files changed

+88
-7
lines changed

app/Search/Queries/QueryController.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,13 @@ public function run(Request $request, VectorSearchRunner $searchRunner, LlmQuery
4141
// TODO - Validate if query system is active
4242
$query = $request->get('query', '');
4343

44-
$results = $query ? $searchRunner->run($query) : [];
45-
$llmResult = $llmRunner->run($query, $results);
46-
dd($results, $llmResult);
44+
return response()->eventStream(function () use ($query, $searchRunner, $llmRunner) {
45+
$results = $query ? $searchRunner->run($query) : [];
46+
47+
$count = count($results);
48+
yield "Found {$count} results for query: {$query}!";
49+
$llmResult = $llmRunner->run($query, $results);
50+
yield "LLM result: {$llmResult}";
51+
});
4752
}
4853
}

package-lock.json

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"@ssddanbrown/codemirror-lang-twig": "^1.0.0",
5656
"@types/jest": "^29.5.14",
5757
"codemirror": "^6.0.1",
58+
"eventsource-client": "^1.1.4",
5859
"idb-keyval": "^6.2.1",
5960
"markdown-it": "^14.1.0",
6061
"markdown-it-task-lists": "^2.1.1",

resources/js/components/query-manager.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {Component} from "./component";
2+
import {createEventSource} from "eventsource-client";
23

34
export class QueryManager extends Component {
45
protected input!: HTMLTextAreaElement;
@@ -21,5 +22,36 @@ export class QueryManager extends Component {
2122
// TODO - Update URL on query change
2223

2324
// TODO - Handle query form submission
25+
this.form.addEventListener('submit', event => {
26+
event.preventDefault();
27+
this.runQuery();
28+
});
29+
}
30+
31+
async runQuery() {
32+
this.contentLoading.hidden = false;
33+
this.generatedLoading.hidden = false;
34+
this.contentDisplay.innerHTML = '';
35+
this.generatedDisplay.innerHTML = '';
36+
37+
const query = this.input.value;
38+
const es = window.$http.eventSource('/query', 'POST', {query});
39+
40+
let messageCount = 0;
41+
for await (const {data, event, id} of es) {
42+
messageCount++;
43+
if (messageCount === 1) {
44+
// Entity results
45+
this.contentDisplay.innerText = data; // TODO - Update to HTML
46+
this.contentLoading.hidden = true;
47+
} else if (messageCount === 2) {
48+
// LLM Output
49+
this.generatedDisplay.innerText = data; // TODO - Update to HTML
50+
this.generatedLoading.hidden = true;
51+
} else {
52+
es.close()
53+
break;
54+
}
55+
}
2456
}
2557
}

resources/js/services/http.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import {createEventSource, EventSourceClient} from "eventsource-client";
2+
13
type ResponseData = Record<any, any>|string;
24

35
type RequestOptions = {
@@ -59,7 +61,6 @@ export class HttpManager {
5961
}
6062

6163
createXMLHttpRequest(method: string, url: string, events: Record<string, (e: Event) => void> = {}): XMLHttpRequest {
62-
const csrfToken = document.querySelector('meta[name=token]')?.getAttribute('content');
6364
const req = new XMLHttpRequest();
6465

6566
for (const [eventName, callback] of Object.entries(events)) {
@@ -68,7 +69,7 @@ export class HttpManager {
6869

6970
req.open(method, url);
7071
req.withCredentials = true;
71-
req.setRequestHeader('X-CSRF-TOKEN', csrfToken || '');
72+
req.setRequestHeader('X-CSRF-TOKEN', this.getCSRFToken());
7273

7374
return req;
7475
}
@@ -95,12 +96,11 @@ export class HttpManager {
9596
requestUrl = urlObj.toString();
9697
}
9798

98-
const csrfToken = document.querySelector('meta[name=token]')?.getAttribute('content') || '';
9999
const requestOptions: RequestInit = {...options, credentials: 'same-origin'};
100100
requestOptions.headers = {
101101
...requestOptions.headers || {},
102102
baseURL: window.baseUrl(''),
103-
'X-CSRF-TOKEN': csrfToken,
103+
'X-CSRF-TOKEN': this.getCSRFToken(),
104104
};
105105

106106
const response = await fetch(requestUrl, requestOptions);
@@ -191,6 +191,27 @@ export class HttpManager {
191191
return this.dataRequest('DELETE', url, data);
192192
}
193193

194+
eventSource(url: string, method: string = 'GET', body: object = {}): EventSourceClient {
195+
if (!url.startsWith('http')) {
196+
url = window.baseUrl(url);
197+
}
198+
199+
return createEventSource({
200+
url,
201+
method,
202+
body: JSON.stringify(body),
203+
credentials: 'same-origin',
204+
headers: {
205+
'Content-Type': 'application/json',
206+
'X-CSRF-TOKEN': this.getCSRFToken(),
207+
}
208+
});
209+
}
210+
211+
protected getCSRFToken(): string {
212+
return document.querySelector('meta[name=token]')?.getAttribute('content') || '';
213+
}
214+
194215
/**
195216
* Parse the response text for an error response to a user
196217
* presentable string. Handles a range of errors responses including

0 commit comments

Comments
 (0)