Skip to content

Commit bb08f62

Browse files
committed
Vectors: Finished core fetch & display functionality
1 parent 8eef5a1 commit bb08f62

File tree

4 files changed

+77
-51
lines changed

4 files changed

+77
-51
lines changed

app/Search/Queries/QueryController.php

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace BookStack\Search\Queries;
44

55
use BookStack\Http\Controller;
6-
use BookStack\Search\SearchOptions;
76
use BookStack\Search\SearchRunner;
87
use Illuminate\Http\Request;
98

@@ -12,24 +11,26 @@ class QueryController extends Controller
1211
public function __construct(
1312
protected SearchRunner $searchRunner,
1413
) {
14+
// TODO - Check via testing
15+
$this->middleware(function ($request, $next) {
16+
if (!VectorQueryServiceProvider::isEnabled()) {
17+
$this->showPermissionError('/');
18+
}
19+
return $next($request);
20+
});
1521
}
1622

1723
/**
1824
* Show the view to start a vector/LLM-based query search.
1925
*/
2026
public function show(Request $request)
2127
{
22-
// TODO - Validate if query system is active
2328
$query = $request->get('ask', '');
2429

25-
// TODO - Placeholder
26-
$entities = $this->searchRunner->searchEntities(SearchOptions::fromString("cat"), 'all', 1, 20)['results'];
27-
2830
// TODO - Set page title
2931

3032
return view('search.query', [
3133
'query' => $query,
32-
'entities' => $entities,
3334
]);
3435
}
3536

@@ -38,16 +39,23 @@ public function show(Request $request)
3839
*/
3940
public function run(Request $request, VectorSearchRunner $searchRunner, LlmQueryRunner $llmRunner)
4041
{
41-
// TODO - Validate if query system is active
42+
// TODO - Rate limiting
4243
$query = $request->get('query', '');
4344

4445
return response()->eventStream(function () use ($query, $searchRunner, $llmRunner) {
4546
$results = $query ? $searchRunner->run($query) : [];
4647

47-
$count = count($results);
48-
yield "Found {$count} results for query: {$query}!";
49-
$llmResult = $llmRunner->run($query, $results);
50-
yield "LLM result: {$llmResult}";
48+
$entities = [];
49+
foreach ($results as $result) {
50+
$entityKey = $result->entity->getMorphClass() . ':' . $result->entity->id;
51+
if (!isset($entities[$entityKey])) {
52+
$entities[$entityKey] = $result->entity;
53+
}
54+
}
55+
56+
yield ['view' => view('entities.list', ['entities' => $entities])->render()];
57+
58+
yield ['result' => $llmRunner->run($query, $results)];
5159
});
5260
}
5361
}
Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {Component} from "./component";
2-
import {createEventSource} from "eventsource-client";
32

43
export class QueryManager extends Component {
54
protected input!: HTMLTextAreaElement;
@@ -8,50 +7,71 @@ export class QueryManager extends Component {
87
protected contentLoading!: HTMLElement;
98
protected contentDisplay!: HTMLElement;
109
protected form!: HTMLFormElement;
10+
protected fieldset!: HTMLFieldSetElement;
1111

1212
setup() {
1313
this.input = this.$refs.input as HTMLTextAreaElement;
1414
this.form = this.$refs.form as HTMLFormElement;
15+
this.fieldset = this.$refs.fieldset as HTMLFieldSetElement;
1516
this.generatedLoading = this.$refs.generatedLoading;
1617
this.generatedDisplay = this.$refs.generatedDisplay;
1718
this.contentLoading = this.$refs.contentLoading;
1819
this.contentDisplay = this.$refs.contentDisplay;
1920

20-
// TODO - Start lookup if query set
21+
this.setupListeners();
2122

22-
// TODO - Update URL on query change
23+
// Start lookup if a query is set
24+
if (this.input.value.trim() !== '') {
25+
this.runQuery();
26+
}
27+
}
2328

24-
// TODO - Handle query form submission
29+
protected setupListeners(): void {
30+
// Handle form submission
2531
this.form.addEventListener('submit', event => {
2632
event.preventDefault();
2733
this.runQuery();
2834
});
35+
36+
// Allow Ctrl+Enter to run a query
37+
this.input.addEventListener('keydown', event => {
38+
if (event.key === 'Enter' && event.ctrlKey && this.input.value.trim() !== '') {
39+
this.runQuery();
40+
}
41+
});
2942
}
3043

31-
async runQuery() {
44+
protected async runQuery(): Promise<void> {
3245
this.contentLoading.hidden = false;
3346
this.generatedLoading.hidden = false;
3447
this.contentDisplay.innerHTML = '';
3548
this.generatedDisplay.innerHTML = '';
49+
this.fieldset.disabled = true;
50+
51+
const query = this.input.value.trim();
52+
const url = new URL(window.location.href);
53+
url.searchParams.set('ask', query);
54+
window.history.pushState({}, '', url.toString());
3655

37-
const query = this.input.value;
3856
const es = window.$http.eventSource('/query', 'POST', {query});
3957

4058
let messageCount = 0;
4159
for await (const {data, event, id} of es) {
4260
messageCount++;
4361
if (messageCount === 1) {
4462
// Entity results
45-
this.contentDisplay.innerText = data; // TODO - Update to HTML
63+
this.contentDisplay.innerHTML = JSON.parse(data).view;
4664
this.contentLoading.hidden = true;
4765
} else if (messageCount === 2) {
4866
// LLM Output
49-
this.generatedDisplay.innerText = data; // TODO - Update to HTML
67+
this.generatedDisplay.innerText = JSON.parse(data).result;
5068
this.generatedLoading.hidden = true;
5169
} else {
52-
es.close()
70+
es.close();
5371
break;
5472
}
5573
}
74+
75+
this.fieldset.disabled = false;
5676
}
5777
}

resources/sass/_forms.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,4 +614,12 @@ input.shortcut-input {
614614
margin: 0;
615615
font-size: 1.6rem;
616616
}
617+
button:disabled {
618+
opacity: 0.5;
619+
cursor: not-allowed;
620+
}
621+
textarea:disabled {
622+
opacity: 0.5;
623+
cursor: not-allowed;
624+
}
617625
}

resources/views/search/query.blade.php

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,55 +8,45 @@
88
<form action="{{ url('/query') }}"
99
refs="query-manager@form"
1010
title="Run Query"
11-
method="post"
12-
class="query-form">
13-
<textarea name="query"
14-
refs="query-manager@input"
15-
class="input-fill-width"
16-
rows="5"
17-
placeholder="Enter a query"
18-
autocomplete="off">{{ $query }}</textarea>
19-
<button class="button icon">@icon('search')</button>
11+
method="post">
12+
<fieldset class="query-form" refs="query-manager@fieldset">
13+
<textarea name="query"
14+
refs="query-manager@input"
15+
class="input-fill-width"
16+
rows="5"
17+
placeholder="Enter a query"
18+
autocomplete="off">{{ $query }}</textarea>
19+
<button class="button icon">@icon('search')</button>
20+
</fieldset>
2021
</form>
2122
</div>
2223

2324
<div class="card content-wrap auto-height pb-xl">
2425
<h2 class="list-heading">Generated Response</h2>
25-
<div refs="query-manager@generated-loading">
26+
<div refs="query-manager@generated-loading" hidden>
2627
@include('common.loading-icon')
2728
</div>
28-
<p refs="query-manager@generated-display">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad adipisci aliquid architecto cupiditate dolor doloribus eligendi et expedita facilis fugiat fugit illo, ipsa laboriosam maiores, molestias mollitia non obcaecati porro quasi quis quos reprehenderit rerum sunt tenetur ullam unde voluptate voluptates! Distinctio et eum id molestiae nisi quisquam sed ut.</p>
29+
<p refs="query-manager@generated-display">
30+
<span class="text-muted italic">
31+
When you run a query, the relevant content found & shown below will be used to help generate a smart machine generated response.
32+
</span>
33+
</p>
2934
</div>
3035

3136

3237
<div class="card content-wrap auto-height pb-xl">
3338
<h2 class="list-heading">Relevant Content</h2>
34-
<div refs="query-manager@content-loading">
39+
<div refs="query-manager@content-loading" hidden>
3540
@include('common.loading-icon')
3641
</div>
3742
<div class="book-contents">
3843
<div refs="query-manager@content-display" class="entity-list">
39-
@include('entities.list', ['entities' => $entities, 'showPath' => true, 'showTags' => true])
44+
<p class="text-muted italic mx-m">
45+
Start a query to find relevant matching content.
46+
The items shown here reflect those used to help provide the above response.
47+
</p>
4048
</div>
4149
</div>
4250
</div>
43-
44-
{{-- @if($results)--}}
45-
{{-- <h2>Results</h2>--}}
46-
47-
{{-- <h3>LLM Output</h3>--}}
48-
{{-- <p>{{ $results['llm_result'] }}</p>--}}
49-
50-
{{-- <h3>Entity Matches</h3>--}}
51-
{{-- @foreach($results['entity_matches'] as $match)--}}
52-
{{-- <div>--}}
53-
{{-- <div><strong>{{ $match['entity_type'] }}:{{ $match['entity_id'] }}; Distance: {{ $match['distance'] }}</strong></div>--}}
54-
{{-- <details>--}}
55-
{{-- <summary>match text</summary>--}}
56-
{{-- <div>{{ $match['text'] }}</div>--}}
57-
{{-- </details>--}}
58-
{{-- </div>--}}
59-
{{-- @endforeach--}}
60-
{{-- @endif--}}
6151
</div>
6252
@stop

0 commit comments

Comments
 (0)