diff --git a/appinfo/routes.php b/appinfo/routes.php index 5555ab1adb..8c417fbb81 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -325,11 +325,6 @@ 'url' => '/api/settings/threadsummary', 'verb' => 'PUT' ], - [ - 'name' => 'settings#isLlmConfigured', - 'url' => '/api/settings/llmconfigured', - 'verb' => 'GET' - ], [ 'name' => 'trusted_senders#setTrusted', 'url' => '/api/trustedsenders/{email}', diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index db5cc3c436..ba75039e3f 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -33,6 +33,7 @@ use OCA\Mail\Db\SmimeCertificate; use OCA\Mail\Db\TagMapper; use OCA\Mail\Service\AccountService; +use OCA\Mail\Service\AiIntegrationsService; use OCA\Mail\Service\AliasesService; use OCA\Mail\Service\OutboxService; use OCA\Mail\Service\SmimeService; @@ -73,6 +74,7 @@ class PageController extends Controller { private IEventDispatcher $dispatcher; private ICredentialstore $credentialStore; private SmimeService $smimeService; + private AiIntegrationsService $aiIntegrationsService; public function __construct(string $appName, IRequest $request, @@ -90,7 +92,8 @@ public function __construct(string $appName, OutboxService $outboxService, IEventDispatcher $dispatcher, ICredentialStore $credentialStore, - SmimeService $smimeService) { + SmimeService $smimeService, + AiIntegrationsService $aiIntegrationsService) { parent::__construct($appName, $request); $this->urlGenerator = $urlGenerator; @@ -108,6 +111,7 @@ public function __construct(string $appName, $this->dispatcher = $dispatcher; $this->credentialStore = $credentialStore; $this->smimeService = $smimeService; + $this->aiIntegrationsService = $aiIntegrationsService; } /** @@ -246,7 +250,7 @@ public function index(): TemplateResponse { $this->initialStateService->provideInitialState( 'enabled_thread_summary', - $this->config->getAppValue('mail', 'enabled_thread_summary', 'no') === 'yes' + $this->config->getAppValue('mail', 'enabled_thread_summary', 'no') === 'yes' && $this->aiIntegrationsService->isLlmAvailable() ); $this->initialStateService->provideInitialState( diff --git a/lib/Service/AiIntegrationsService.php b/lib/Service/AiIntegrationsService.php index bd02050f21..84041da15d 100644 --- a/lib/Service/AiIntegrationsService.php +++ b/lib/Service/AiIntegrationsService.php @@ -68,4 +68,13 @@ public function summarizeThread(string $threadId, array $messages, string $curre throw new ServiceException('No language model available for summary'); } } + + public function isLlmAvailable() :bool { + try { + $manager = $this->container->get(IManager::class); + } catch (\Throwable $e) { + return false; + } + return in_array(SummaryTaskType::class, $manager->getAvailableTaskTypes(), true); + } } diff --git a/lib/Settings/AdminSettings.php b/lib/Settings/AdminSettings.php index 0f96ce2562..66e2e17951 100644 --- a/lib/Settings/AdminSettings.php +++ b/lib/Settings/AdminSettings.php @@ -28,6 +28,7 @@ use OCA\Mail\AppInfo\Application; use OCA\Mail\Integration\GoogleIntegration; use OCA\Mail\Integration\MicrosoftIntegration; +use OCA\Mail\Service\AiIntegrationsService; use OCA\Mail\Service\AntiSpamService; use OCA\Mail\Service\Provisioning\Manager as ProvisioningManager; use OCP\AppFramework\Http\TemplateResponse; @@ -49,19 +50,22 @@ class AdminSettings implements ISettings { private GoogleIntegration $googleIntegration; private MicrosoftIntegration $microsoftIntegration; private IConfig $config; + private AiIntegrationsService $aiIntegrationsService; public function __construct(IInitialStateService $initialStateService, ProvisioningManager $provisioningManager, AntiSpamService $antiSpamService, GoogleIntegration $googleIntegration, MicrosoftIntegration $microsoftIntegration, - IConfig $config) { + IConfig $config, + AiIntegrationsService $aiIntegrationsService) { $this->initialStateService = $initialStateService; $this->provisioningManager = $provisioningManager; $this->antiSpamService = $antiSpamService; $this->googleIntegration = $googleIntegration; $this->microsoftIntegration = $microsoftIntegration; $this->config = $config; + $this->aiIntegrationsService = $aiIntegrationsService; } public function getForm() { @@ -92,6 +96,12 @@ public function getForm() { $this->config->getAppValue('mail', 'enabled_thread_summary', 'no') === 'yes' ); + $this->initialStateService->provideInitialState( + Application::APP_ID, + 'enabled_llm_backend', + $this->aiIntegrationsService->isLlmAvailable() + ); + $this->initialStateService->provideLazyInitialState( Application::APP_ID, 'ldap_aliases_integration', diff --git a/src/components/settings/AdminSettings.vue b/src/components/settings/AdminSettings.vue index 34a6600aa7..0f3a6702b7 100644 --- a/src/components/settings/AdminSettings.vue +++ b/src/components/settings/AdminSettings.vue @@ -279,8 +279,6 @@ import { provisionAll, updateAllowNewMailAccounts, updateEnabledThreadSummary, - isLlmConfigured, - } from '../../service/SettingsService' const googleOauthClientId = loadState('mail', 'google_oauth_client_id', null) ?? undefined @@ -341,12 +339,9 @@ export default { }, allowNewMailAccounts: loadState('mail', 'allow_new_mail_accounts', true), enabledThreadSummary: loadState('mail', 'enabled_thread_summary', false), - isLlmConfigured: false, + isLlmConfigured: loadState('mail', 'enabled_llm_backend'), } }, - async beforeMount() { - this.isLlmConfigured = await isLlmConfigured() - }, methods: { async saveSettings(settings) { try { diff --git a/src/service/AiIntergrationsService.js b/src/service/AiIntergrationsService.js index a1c7443200..ca5be042c6 100644 --- a/src/service/AiIntergrationsService.js +++ b/src/service/AiIntergrationsService.js @@ -9,6 +9,7 @@ export const summarizeThread = async (threadId) => { try { const resp = await axios.get(url) + if (resp.status === 204) throw convertAxiosError() return resp.data.data } catch (e) { throw convertAxiosError(e) diff --git a/src/service/SettingsService.js b/src/service/SettingsService.js index 1cad1fccc4..18f2dcbf3b 100644 --- a/src/service/SettingsService.js +++ b/src/service/SettingsService.js @@ -85,10 +85,3 @@ export const updateEnabledThreadSummary = async (enabled) => { const resp = await axios.put(url, data) return resp.data } - -export const isLlmConfigured = async () => { - const url = generateUrl('/apps/mail/api/settings/llmconfigured') - - const resp = await axios.get(url) - return resp.data.data -} diff --git a/tests/Unit/Controller/PageControllerTest.php b/tests/Unit/Controller/PageControllerTest.php index 891c87843c..b5ffc24da2 100644 --- a/tests/Unit/Controller/PageControllerTest.php +++ b/tests/Unit/Controller/PageControllerTest.php @@ -31,6 +31,7 @@ use OCA\Mail\Db\Mailbox; use OCA\Mail\Db\TagMapper; use OCA\Mail\Service\AccountService; +use OCA\Mail\Service\AiIntegrationsService; use OCA\Mail\Service\AliasesService; use OCA\Mail\Service\MailManager; use OCA\Mail\Service\OutboxService; @@ -69,6 +70,9 @@ class PageControllerTest extends TestCase { /** @var AccountService|MockObject */ private $accountService; + /** @var AiIntegrationsService|MockObject */ + private $aiIntegrationsService; + /** @var AliasesService|MockObject */ private $aliasesService; @@ -113,6 +117,7 @@ protected function setUp(): void { $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->config = $this->createMock(IConfig::class); $this->accountService = $this->createMock(AccountService::class); + $this->aiIntegrationsService = $this->createMock(AiIntegrationsService::class); $this->aliasesService = $this->createMock(AliasesService::class); $this->userSession = $this->createMock(IUserSession::class); $this->preferences = $this->createMock(IUserPreferences::class); @@ -143,6 +148,7 @@ protected function setUp(): void { $this->eventDispatcher, $this->credentialStore, $this->smimeService, + $this->aiIntegrationsService, ); } diff --git a/tests/Unit/Settings/AdminSettingsTest.php b/tests/Unit/Settings/AdminSettingsTest.php index 40b1604fb4..840a44adf2 100644 --- a/tests/Unit/Settings/AdminSettingsTest.php +++ b/tests/Unit/Settings/AdminSettingsTest.php @@ -53,7 +53,7 @@ public function testGetSection() { } public function testGetForm() { - $this->serviceMock->getParameter('initialStateService')->expects($this->exactly(9)) + $this->serviceMock->getParameter('initialStateService')->expects($this->exactly(10)) ->method('provideInitialState') ->withConsecutive( [ @@ -76,6 +76,11 @@ public function testGetForm() { 'enabled_thread_summary', $this->anything() ], + [ + Application::APP_ID, + 'enabled_llm_backend', + $this->anything() + ], [ Application::APP_ID, 'google_oauth_client_id',