diff --git a/packages/knowledge-base/Classes/Controller/BackendKnowledgeBaseController.php b/packages/knowledge-base/Classes/Controller/BackendKnowledgeBaseController.php index dcdc9ea..3e4d514 100644 --- a/packages/knowledge-base/Classes/Controller/BackendKnowledgeBaseController.php +++ b/packages/knowledge-base/Classes/Controller/BackendKnowledgeBaseController.php @@ -53,8 +53,6 @@ public function indexAction(): ResponseInterface $this->moduleTemplate->assign('openDocumentId', $openDocumentId); $loadChildrenUrl = $this->uriBuilder->reset()->uriFor('loadDocumentChildren', ['documentUid' => 'DOCUMENT_ID_PLACEHOLDER']); $this->moduleTemplate->assign('loadChildrenUrl', $loadChildrenUrl); - $this->moduleTemplate->assign('semanticSearchAvailable', $this->modelAvailabilityService->isEmbeddingServerAvailable()); - $this->moduleTemplate->assign('ragSearchAvailable', $this->modelAvailabilityService->isGenerationServerAvailable()); return $this->moduleTemplate->renderResponse('Backend/Index'); } @@ -133,6 +131,22 @@ public function createAction(string $documentHeadline, int $parentId = 0, string return $this->redirect('index', null, null, ['openDocumentId' => $result['documentUid']]); } + public function ajaxCheckAvailabilityAction(ServerRequest $request): ResponseInterface + { + $service = $request->getQueryParams()['service'] ?? ''; + + $result = match($service) { + 'semantic' => ['available' => $this->modelAvailabilityService->isEmbeddingServerAvailable()], + 'rag' => ['available' => $this->modelAvailabilityService->isGenerationServerAvailable()], + default => [ + 'semantic' => $this->modelAvailabilityService->isEmbeddingServerAvailable(), + 'rag' => $this->modelAvailabilityService->isGenerationServerAvailable(), + ], + }; + + return $this->jsonResponse((string)json_encode($result)); + } + public function ajaxLoadDocumentAction(ServerRequest $request): ResponseInterface { $params = $request->getQueryParams(); diff --git a/packages/knowledge-base/Configuration/Backend/AjaxRoutes.php b/packages/knowledge-base/Configuration/Backend/AjaxRoutes.php index 6a27543..246f33e 100644 --- a/packages/knowledge-base/Configuration/Backend/AjaxRoutes.php +++ b/packages/knowledge-base/Configuration/Backend/AjaxRoutes.php @@ -17,4 +17,8 @@ 'path' => '/knowledgebase/searchDocuments', 'target' => BackendKnowledgeBaseController::class . '::ajaxSearchAction', ], + 'checkAvailability' => [ + 'path' => '/knowledgebase/checkAvailability', + 'target' => BackendKnowledgeBaseController::class . '::ajaxCheckAvailabilityAction', + ], ]; diff --git a/packages/knowledge-base/Resources/Public/Css/Search.css b/packages/knowledge-base/Resources/Public/Css/Search.css index 16f2f1c..a166745 100644 --- a/packages/knowledge-base/Resources/Public/Css/Search.css +++ b/packages/knowledge-base/Resources/Public/Css/Search.css @@ -89,6 +89,16 @@ background: rgba(255, 135, 0, 0.06); } +.kb-flyout-mode-btn:disabled { + opacity: 0.35; + cursor: not-allowed; +} + +.kb-flyout-mode-btn:disabled:hover { + background: transparent; + color: #6b6b6b; +} + /* Flyout status (loading / error) */ .kb-flyout-status { padding: 10px 12px; diff --git a/packages/knowledge-base/Resources/Public/JavaScript/Searchbar.js b/packages/knowledge-base/Resources/Public/JavaScript/Searchbar.js index ae9acf2..45ea1ea 100644 --- a/packages/knowledge-base/Resources/Public/JavaScript/Searchbar.js +++ b/packages/knowledge-base/Resources/Public/JavaScript/Searchbar.js @@ -30,6 +30,7 @@ class KnowledgeBaseSearchBar { this.iconBoard = document.getElementById('kb-icon-board')?.innerHTML ?? ''; this.bindEvents(); + this.checkAvailability(); } bindEvents() { @@ -75,6 +76,36 @@ class KnowledgeBaseSearchBar { this.form.addEventListener('submit', e => e.preventDefault()); } + async checkAvailability() { + const base = TYPO3?.settings?.ajaxUrls?.checkAvailability; + if (!base) return; + + const check = async (service) => { + try { + const response = await fetch(`${base}&service=${service}`, { + headers: { 'X-Requested-With': 'XMLHttpRequest' }, + }); + if (!response.ok) return true; + const data = await response.json(); + return data.available !== false; + } catch { + return true; // leave enabled if check fails + } + }; + + const [semanticOk, ragOk] = await Promise.all([check('semantic'), check('rag')]); + + this.modeButtons.forEach(btn => { + if (btn.dataset.mode === 'semantic' && !semanticOk) { + btn.disabled = true; + btn.title = 'Semantic search server unavailable'; + } else if (btn.dataset.mode === 'rag' && !ragOk) { + btn.disabled = true; + btn.title = 'RAG search server unavailable'; + } + }); + } + openFlyout() { this.flyout.classList.add('is-open'); }