1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-10-22 07:52:19 +03:00

Vectors: Started front-end work, moved to own controller

This commit is contained in:
Dan Brown
2025-08-19 15:19:04 +01:00
parent 54f883e815
commit 2c3100e401
7 changed files with 155 additions and 40 deletions

View File

@@ -0,0 +1,53 @@
<?php
namespace BookStack\Search;
use BookStack\Http\Controller;
use BookStack\Search\Vectors\VectorSearchRunner;
use Illuminate\Http\Request;
class QueryController extends Controller
{
public function __construct(
protected SearchRunner $searchRunner,
) {
}
/**
* Show the view to start a vector/LLM-based query search.
*/
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,
]);
}
/**
* Perform a vector/LLM-based query search.
*/
public function run(Request $request, VectorSearchRunner $runner)
{
// TODO - Validate if query system is active
$query = $request->get('query', '');
if ($query) {
$results = $runner->run($query);
} else {
$results = null;
}
return view('search.query', [
'results' => $results,
]);
}
}

View File

@@ -129,7 +129,7 @@ class SearchController extends Controller
} }
/** /**
* Search siblings items in the system. * Search sibling items in the system.
*/ */
public function searchSiblings(Request $request, SiblingFetcher $siblingFetcher) public function searchSiblings(Request $request, SiblingFetcher $siblingFetcher)
{ {
@@ -140,23 +140,4 @@ class SearchController extends Controller
return view('entities.list-basic', ['entities' => $entities, 'style' => 'compact']); return view('entities.list-basic', ['entities' => $entities, 'style' => 'compact']);
} }
/**
* Perform a vector/LLM-based query search.
*/
public function searchQuery(Request $request, VectorSearchRunner $runner)
{
// TODO - Validate if query system is active
$query = $request->get('query', '');
if ($query) {
$results = $runner->run($query);
} else {
$results = null;
}
return view('search.query', [
'results' => $results,
]);
}
} }

View File

@@ -44,6 +44,7 @@ export {PagePicker} from './page-picker';
export {PermissionsTable} from './permissions-table'; export {PermissionsTable} from './permissions-table';
export {Pointer} from './pointer'; export {Pointer} from './pointer';
export {Popup} from './popup'; export {Popup} from './popup';
export {QueryManager} from './query-manager';
export {SettingAppColorScheme} from './setting-app-color-scheme'; export {SettingAppColorScheme} from './setting-app-color-scheme';
export {SettingColorPicker} from './setting-color-picker'; export {SettingColorPicker} from './setting-color-picker';
export {SettingHomepageControl} from './setting-homepage-control'; export {SettingHomepageControl} from './setting-homepage-control';

View File

@@ -0,0 +1,25 @@
import {Component} from "./component";
export class QueryManager extends Component {
protected input!: HTMLTextAreaElement;
protected generatedLoading!: HTMLElement;
protected generatedDisplay!: HTMLElement;
protected contentLoading!: HTMLElement;
protected contentDisplay!: HTMLElement;
protected form!: HTMLFormElement;
setup() {
this.input = this.$refs.input as HTMLTextAreaElement;
this.form = this.$refs.form as HTMLFormElement;
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
// TODO - Update URL on query change
// TODO - Handle query form submission
}
}

View File

@@ -597,3 +597,21 @@ input.shortcut-input {
max-width: 120px; max-width: 120px;
height: auto; height: auto;
} }
.query-form {
display: flex;
flex-direction: row;
gap: vars.$m;
textarea {
font-size: 1.4rem;
height: 100px;
box-shadow: vars.$bs-card;
border-radius: 8px;
color: #444;
}
button {
align-self: start;
margin: 0;
font-size: 1.6rem;
}
}

View File

@@ -1,29 +1,62 @@
@extends('layouts.simple') @extends('layouts.simple')
@section('body') @section('body')
<div class="container mt-xl" id="search-system"> <div component="query-manager" class="container small pt-xxl">
<form action="{{ url('/search/query') }}" method="get"> <div class="card content-wrap auto-height">
<input name="query" type="text"> <h1 class="list-heading">Start a Query</h1>
<button class="button">Query</button> <form action="{{ url('/query') }}"
</form> refs="query-manager@form"
title="Run Query"
method="post"
class="query-form">
<textarea name="query"
refs="query-manager@input"
class="input-fill-width"
rows="5"
placeholder="Enter a query"
autocomplete="off">{{ $query }}</textarea>
<button class="button icon">@icon('search')</button>
</form>
</div>
@if($results) <div class="card content-wrap auto-height pb-xl">
<h2>Results</h2> <h2 class="list-heading">Generated Response</h2>
<div refs="query-manager@generated-loading">
@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>
</div>
<h3>LLM Output</h3>
<p>{{ $results['llm_result'] }}</p>
<h3>Entity Matches</h3> <div class="card content-wrap auto-height pb-xl">
@foreach($results['entity_matches'] as $match) <h2 class="list-heading">Relevant Content</h2>
<div> <div refs="query-manager@content-loading">
<div><strong>{{ $match['entity_type'] }}:{{ $match['entity_id'] }}; Distance: {{ $match['distance'] }}</strong></div> @include('common.loading-icon')
<details> </div>
<summary>match text</summary> <div class="book-contents">
<div>{{ $match['text'] }}</div> <div refs="query-manager@content-display" class="entity-list">
</details> @include('entities.list', ['entities' => $entities, 'showPath' => true, 'showTags' => true])
</div> </div>
@endforeach </div>
@endif </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> </div>
@stop @stop

View File

@@ -11,6 +11,7 @@ use BookStack\Exports\Controllers as ExportControllers;
use BookStack\Http\Middleware\VerifyCsrfToken; use BookStack\Http\Middleware\VerifyCsrfToken;
use BookStack\Permissions\PermissionsController; use BookStack\Permissions\PermissionsController;
use BookStack\References\ReferenceController; use BookStack\References\ReferenceController;
use BookStack\Search\QueryController;
use BookStack\Search\SearchController; use BookStack\Search\SearchController;
use BookStack\Settings as SettingControllers; use BookStack\Settings as SettingControllers;
use BookStack\Sorting as SortingControllers; use BookStack\Sorting as SortingControllers;
@@ -189,7 +190,6 @@ Route::middleware('auth')->group(function () {
// Search // Search
Route::get('/search', [SearchController::class, 'search']); Route::get('/search', [SearchController::class, 'search']);
Route::get('/search/query', [SearchController::class, 'searchQuery']);
Route::get('/search/book/{bookId}', [SearchController::class, 'searchBook']); Route::get('/search/book/{bookId}', [SearchController::class, 'searchBook']);
Route::get('/search/chapter/{bookId}', [SearchController::class, 'searchChapter']); Route::get('/search/chapter/{bookId}', [SearchController::class, 'searchChapter']);
Route::get('/search/entity/siblings', [SearchController::class, 'searchSiblings']); Route::get('/search/entity/siblings', [SearchController::class, 'searchSiblings']);
@@ -197,6 +197,10 @@ Route::middleware('auth')->group(function () {
Route::get('/search/entity-selector-templates', [SearchController::class, 'templatesForSelector']); Route::get('/search/entity-selector-templates', [SearchController::class, 'templatesForSelector']);
Route::get('/search/suggest', [SearchController::class, 'searchSuggestions']); Route::get('/search/suggest', [SearchController::class, 'searchSuggestions']);
// Queries
Route::get('/query', [QueryController::class, 'show']);
Route::post('/query', [QueryController::class, 'run']);
// User Search // User Search
Route::get('/search/users/select', [UserControllers\UserSearchController::class, 'forSelect']); Route::get('/search/users/select', [UserControllers\UserSearchController::class, 'forSelect']);