mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-10-22 07:52:19 +03:00
Vectors: Finished core fetch & display functionality
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
namespace BookStack\Search\Queries;
|
||||
|
||||
use BookStack\Http\Controller;
|
||||
use BookStack\Search\SearchOptions;
|
||||
use BookStack\Search\SearchRunner;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@@ -12,6 +11,13 @@ class QueryController extends Controller
|
||||
public function __construct(
|
||||
protected SearchRunner $searchRunner,
|
||||
) {
|
||||
// TODO - Check via testing
|
||||
$this->middleware(function ($request, $next) {
|
||||
if (!VectorQueryServiceProvider::isEnabled()) {
|
||||
$this->showPermissionError('/');
|
||||
}
|
||||
return $next($request);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -19,17 +25,12 @@ class QueryController extends Controller
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
// TODO - Validate if query system is active
|
||||
$query = $request->get('ask', '');
|
||||
|
||||
// TODO - Placeholder
|
||||
$entities = $this->searchRunner->searchEntities(SearchOptions::fromString("cat"), 'all', 1, 20)['results'];
|
||||
|
||||
// TODO - Set page title
|
||||
|
||||
return view('search.query', [
|
||||
'query' => $query,
|
||||
'entities' => $entities,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -38,16 +39,23 @@ class QueryController extends Controller
|
||||
*/
|
||||
public function run(Request $request, VectorSearchRunner $searchRunner, LlmQueryRunner $llmRunner)
|
||||
{
|
||||
// TODO - Validate if query system is active
|
||||
// TODO - Rate limiting
|
||||
$query = $request->get('query', '');
|
||||
|
||||
return response()->eventStream(function () use ($query, $searchRunner, $llmRunner) {
|
||||
$results = $query ? $searchRunner->run($query) : [];
|
||||
|
||||
$count = count($results);
|
||||
yield "Found {$count} results for query: {$query}!";
|
||||
$llmResult = $llmRunner->run($query, $results);
|
||||
yield "LLM result: {$llmResult}";
|
||||
$entities = [];
|
||||
foreach ($results as $result) {
|
||||
$entityKey = $result->entity->getMorphClass() . ':' . $result->entity->id;
|
||||
if (!isset($entities[$entityKey])) {
|
||||
$entities[$entityKey] = $result->entity;
|
||||
}
|
||||
}
|
||||
|
||||
yield ['view' => view('entities.list', ['entities' => $entities])->render()];
|
||||
|
||||
yield ['result' => $llmRunner->run($query, $results)];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import {Component} from "./component";
|
||||
import {createEventSource} from "eventsource-client";
|
||||
|
||||
export class QueryManager extends Component {
|
||||
protected input!: HTMLTextAreaElement;
|
||||
@@ -8,33 +7,52 @@ export class QueryManager extends Component {
|
||||
protected contentLoading!: HTMLElement;
|
||||
protected contentDisplay!: HTMLElement;
|
||||
protected form!: HTMLFormElement;
|
||||
protected fieldset!: HTMLFieldSetElement;
|
||||
|
||||
setup() {
|
||||
this.input = this.$refs.input as HTMLTextAreaElement;
|
||||
this.form = this.$refs.form as HTMLFormElement;
|
||||
this.fieldset = this.$refs.fieldset as HTMLFieldSetElement;
|
||||
this.generatedLoading = this.$refs.generatedLoading;
|
||||
this.generatedDisplay = this.$refs.generatedDisplay;
|
||||
this.contentLoading = this.$refs.contentLoading;
|
||||
this.contentDisplay = this.$refs.contentDisplay;
|
||||
|
||||
// TODO - Start lookup if query set
|
||||
this.setupListeners();
|
||||
|
||||
// TODO - Update URL on query change
|
||||
// Start lookup if a query is set
|
||||
if (this.input.value.trim() !== '') {
|
||||
this.runQuery();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - Handle query form submission
|
||||
protected setupListeners(): void {
|
||||
// Handle form submission
|
||||
this.form.addEventListener('submit', event => {
|
||||
event.preventDefault();
|
||||
this.runQuery();
|
||||
});
|
||||
|
||||
// Allow Ctrl+Enter to run a query
|
||||
this.input.addEventListener('keydown', event => {
|
||||
if (event.key === 'Enter' && event.ctrlKey && this.input.value.trim() !== '') {
|
||||
this.runQuery();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async runQuery() {
|
||||
protected async runQuery(): Promise<void> {
|
||||
this.contentLoading.hidden = false;
|
||||
this.generatedLoading.hidden = false;
|
||||
this.contentDisplay.innerHTML = '';
|
||||
this.generatedDisplay.innerHTML = '';
|
||||
this.fieldset.disabled = true;
|
||||
|
||||
const query = this.input.value.trim();
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('ask', query);
|
||||
window.history.pushState({}, '', url.toString());
|
||||
|
||||
const query = this.input.value;
|
||||
const es = window.$http.eventSource('/query', 'POST', {query});
|
||||
|
||||
let messageCount = 0;
|
||||
@@ -42,16 +60,18 @@ export class QueryManager extends Component {
|
||||
messageCount++;
|
||||
if (messageCount === 1) {
|
||||
// Entity results
|
||||
this.contentDisplay.innerText = data; // TODO - Update to HTML
|
||||
this.contentDisplay.innerHTML = JSON.parse(data).view;
|
||||
this.contentLoading.hidden = true;
|
||||
} else if (messageCount === 2) {
|
||||
// LLM Output
|
||||
this.generatedDisplay.innerText = data; // TODO - Update to HTML
|
||||
this.generatedDisplay.innerText = JSON.parse(data).result;
|
||||
this.generatedLoading.hidden = true;
|
||||
} else {
|
||||
es.close()
|
||||
es.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.fieldset.disabled = false;
|
||||
}
|
||||
}
|
@@ -614,4 +614,12 @@ input.shortcut-input {
|
||||
margin: 0;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
textarea:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
@@ -8,8 +8,8 @@
|
||||
<form action="{{ url('/query') }}"
|
||||
refs="query-manager@form"
|
||||
title="Run Query"
|
||||
method="post"
|
||||
class="query-form">
|
||||
method="post">
|
||||
<fieldset class="query-form" refs="query-manager@fieldset">
|
||||
<textarea name="query"
|
||||
refs="query-manager@input"
|
||||
class="input-fill-width"
|
||||
@@ -17,46 +17,36 @@
|
||||
placeholder="Enter a query"
|
||||
autocomplete="off">{{ $query }}</textarea>
|
||||
<button class="button icon">@icon('search')</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card content-wrap auto-height pb-xl">
|
||||
<h2 class="list-heading">Generated Response</h2>
|
||||
<div refs="query-manager@generated-loading">
|
||||
<div refs="query-manager@generated-loading" hidden>
|
||||
@include('common.loading-icon')
|
||||
</div>
|
||||
<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>
|
||||
<p refs="query-manager@generated-display">
|
||||
<span class="text-muted italic">
|
||||
When you run a query, the relevant content found & shown below will be used to help generate a smart machine generated response.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card content-wrap auto-height pb-xl">
|
||||
<h2 class="list-heading">Relevant Content</h2>
|
||||
<div refs="query-manager@content-loading">
|
||||
<div refs="query-manager@content-loading" hidden>
|
||||
@include('common.loading-icon')
|
||||
</div>
|
||||
<div class="book-contents">
|
||||
<div refs="query-manager@content-display" class="entity-list">
|
||||
@include('entities.list', ['entities' => $entities, 'showPath' => true, 'showTags' => true])
|
||||
<p class="text-muted italic mx-m">
|
||||
Start a query to find relevant matching content.
|
||||
The items shown here reflect those used to help provide the above response.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- @if($results)--}}
|
||||
{{-- <h2>Results</h2>--}}
|
||||
|
||||
{{-- <h3>LLM Output</h3>--}}
|
||||
{{-- <p>{{ $results['llm_result'] }}</p>--}}
|
||||
|
||||
{{-- <h3>Entity Matches</h3>--}}
|
||||
{{-- @foreach($results['entity_matches'] as $match)--}}
|
||||
{{-- <div>--}}
|
||||
{{-- <div><strong>{{ $match['entity_type'] }}:{{ $match['entity_id'] }}; Distance: {{ $match['distance'] }}</strong></div>--}}
|
||||
{{-- <details>--}}
|
||||
{{-- <summary>match text</summary>--}}
|
||||
{{-- <div>{{ $match['text'] }}</div>--}}
|
||||
{{-- </details>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @endforeach--}}
|
||||
{{-- @endif--}}
|
||||
</div>
|
||||
@stop
|
||||
|
Reference in New Issue
Block a user