From fc9780a41d586e2983f18e128a4095484e5860ac Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 24 Jul 2023 12:12:06 +0200 Subject: First pass at ai admin settings Signed-off-by: Marcel Klehr --- apps/settings/appinfo/info.xml | 2 + apps/settings/img/ai.svg | 1 + .../lib/Controller/AISettingsController.php | 105 +++++++++++++ .../lib/Sections/Admin/ArtificialIntelligence.php | 58 ++++++++ .../lib/Settings/Admin/ArtificialIntelligence.php | 163 +++++++++++++++++++++ apps/settings/src/components/AdminAI.vue | 83 +++++++++++ apps/settings/templates/settings/admin/ai.php | 28 ++++ lib/public/SpeechToText/ISpeechToTextManager.php | 6 + lib/public/TextProcessing/IManager.php | 6 + lib/public/Translation/ITranslationManager.php | 6 + webpack.modules.js | 1 + 11 files changed, 459 insertions(+) create mode 100644 apps/settings/img/ai.svg create mode 100644 apps/settings/lib/Controller/AISettingsController.php create mode 100644 apps/settings/lib/Sections/Admin/ArtificialIntelligence.php create mode 100644 apps/settings/lib/Settings/Admin/ArtificialIntelligence.php create mode 100644 apps/settings/src/components/AdminAI.vue create mode 100644 apps/settings/templates/settings/admin/ai.php diff --git a/apps/settings/appinfo/info.xml b/apps/settings/appinfo/info.xml index 92d2928cd31..fefa2fadaef 100644 --- a/apps/settings/appinfo/info.xml +++ b/apps/settings/appinfo/info.xml @@ -19,6 +19,7 @@ OCA\Settings\Settings\Admin\Mail OCA\Settings\Settings\Admin\Overview + OCA\Settings\Settings\Admin\ArtificialIntelligence OCA\Settings\Settings\Admin\Server OCA\Settings\Settings\Admin\Sharing OCA\Settings\Settings\Admin\Security @@ -27,6 +28,7 @@ OCA\Settings\Sections\Admin\Delegation OCA\Settings\Sections\Admin\Groupware OCA\Settings\Sections\Admin\Overview + OCA\Settings\Sections\Admin\ArtificialIntelligence OCA\Settings\Sections\Admin\Security OCA\Settings\Sections\Admin\Server OCA\Settings\Sections\Admin\Sharing diff --git a/apps/settings/img/ai.svg b/apps/settings/img/ai.svg new file mode 100644 index 00000000000..5d59fd6afe8 --- /dev/null +++ b/apps/settings/img/ai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/settings/lib/Controller/AISettingsController.php b/apps/settings/lib/Controller/AISettingsController.php new file mode 100644 index 00000000000..53aca66c946 --- /dev/null +++ b/apps/settings/lib/Controller/AISettingsController.php @@ -0,0 +1,105 @@ + + * @copyright Copyright (c) 2016, ownCloud, Inc. + * + * @author Christoph Wurst + * @author Daniel Kesselberg + * @author Joas Schilling + * @author Lukas Reschke + * @author Morris Jobke + * @author Roeland Jago Douma + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace OCA\Settings\Controller; + +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\DataResponse; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IRequest; +use OCP\IURLGenerator; +use OCP\IUserSession; +use OCP\Mail\IMailer; + +class AISettingsController extends Controller { + + /** @var IL10N */ + private $l10n; + /** @var IConfig */ + private $config; + /** @var IUserSession */ + private $userSession; + /** @var IMailer */ + private $mailer; + /** @var IURLGenerator */ + private $urlGenerator; + + /** + * @param string $appName + * @param IRequest $request + * @param IL10N $l10n + * @param IConfig $config + * @param IUserSession $userSession + * @param IURLGenerator $urlGenerator, + * @param IMailer $mailer + */ + public function __construct($appName, + IRequest $request, + IL10N $l10n, + IConfig $config, + IUserSession $userSession, + IURLGenerator $urlGenerator, + IMailer $mailer) { + parent::__construct($appName, $request); + $this->l10n = $l10n; + $this->config = $config; + $this->userSession = $userSession; + $this->urlGenerator = $urlGenerator; + $this->mailer = $mailer; + } + + /** + * Sets the email settings + * + * @PasswordConfirmationRequired + * @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\ArtificialIntelligence) + * + * @param array $settings + * @return DataResponse + */ + public function setAISettings($settings) { + $params = get_defined_vars(); + $configs = []; + foreach ($params as $key => $value) { + $configs[$key] = empty($value) ? null : $value; + } + + // Delete passwords from config in case no auth is specified + if ($params['mail_smtpauth'] !== 1) { + $configs['mail_smtpname'] = null; + $configs['mail_smtppassword'] = null; + } + + $this->config->setSystemValues($configs); + + $this->config->setAppValue('core', 'emailTestSuccessful', '0'); + + return new DataResponse(); + } +} diff --git a/apps/settings/lib/Sections/Admin/ArtificialIntelligence.php b/apps/settings/lib/Sections/Admin/ArtificialIntelligence.php new file mode 100644 index 00000000000..1a25cdf5156 --- /dev/null +++ b/apps/settings/lib/Sections/Admin/ArtificialIntelligence.php @@ -0,0 +1,58 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OCA\Settings\Sections\Admin; + +use OCP\IL10N; +use OCP\IURLGenerator; +use OCP\Settings\IIconSection; + +class ArtificialIntelligence implements IIconSection { + + /** @var IL10N */ + private $l; + + /** @var IURLGenerator */ + private $urlGenerator; + + public function __construct(IL10N $l, IURLGenerator $urlGenerator) { + $this->l = $l; + $this->urlGenerator = $urlGenerator; + } + + public function getIcon(): string { + return $this->urlGenerator->imagePath('settings', 'ai.svg'); + } + + public function getID(): string { + return 'ai'; + } + + public function getName(): string { + return $this->l->t('Artificial Intelligence'); + } + + public function getPriority(): int { + return 40; + } +} diff --git a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php new file mode 100644 index 00000000000..d33802d6c45 --- /dev/null +++ b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php @@ -0,0 +1,163 @@ + + * + * @author Marcel Klehr + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OCA\Settings\Settings\Admin; + +use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Services\IInitialState; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IServerContainer; +use OCP\Settings\IDelegatedSettings; +use OCP\SpeechToText\ISpeechToTextManager; +use OCP\TextProcessing\IManager; +use OCP\TextProcessing\IProvider; +use OCP\TextProcessing\ITaskType; +use OCP\Translation\ITranslationManager; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; + +class ArtificialIntelligence implements IDelegatedSettings { + public function __construct( + private IConfig $config, + private IL10N $l, + private IInitialState $initialState, + private ITranslationManager $translationManager, + private ISpeechToTextManager $sttManager, + private IManager $textProcessingManager, + private IServerContainer $container, + ) { + } + + /** + * @return TemplateResponse + */ + public function getForm() { + $translationProviders = []; + $translationPreferences = []; + foreach ($this->translationManager->getProviders() as $provider) { + $translationProviders[] = [ + 'class' => $provider::class, + 'name' => $provider->getName(), + ]; + $translationPreferences[] = $provider::class; + } + + $sttProviders = []; + foreach ($this->sttManager->getProviders() as $provider) { + $sttProviders[] = [ + 'class' => $provider::class, + 'name' => $provider->getName(), + ]; + } + + $textProcessingProviders = []; + /** @var array, class-string> $textProcessingSettings */ + $textProcessingSettings = []; + foreach ($this->textProcessingManager->getProviders() as $provider) { + $textProcessingProviders[] = [ + 'class' => $provider::class, + 'name' => $provider->getName(), + 'taskType' => $provider->getTaskType(), + ]; + $textProcessingSettings[$provider->getTaskType()] = $provider::class; + } + $textProcessingTaskTypes = []; + foreach ($textProcessingSettings as $taskTypeClass => $providerClass) { + /** @var ITaskType $taskType */ + try { + $taskType = $this->container->get($taskTypeClass); + } catch (NotFoundExceptionInterface $e) { + continue; + } catch (ContainerExceptionInterface $e) { + continue; + } + $textProcessingTaskTypes[] = [ + 'class' => $taskTypeClass, + 'name' => $taskType->getName(), + 'description' => $taskType->getDescription(), + ]; + } + + $this->initialState->provideInitialState('ai-stt-providers', $sttProviders); + $this->initialState->provideInitialState('ai-translation-providers', $translationProviders); + $this->initialState->provideInitialState('ai-text-processing-providers', $textProcessingProviders); + $this->initialState->provideInitialState('ai-text-processing-task-types', $textProcessingTaskTypes); + + $settings = [ + 'ai.stt_provider' => count($sttProviders) > 0 ? $sttProviders[0]['class'] : null, + 'ai.textprocessing_provider_preferences' => $textProcessingSettings, + 'ai.translation_provider_preferences' => $translationPreferences, + ]; + foreach ($settings as $key => $defaultValue) { + $value = $defaultValue; + $json = $this->config->getAppValue('core', $key, ''); + if ($json !== '') { + $value = json_decode($json, JSON_OBJECT_AS_ARRAY); + switch($key) { + case 'ai.textprocessing_provider_preferences': + // fill $value with $defaultValue values + $value = array_merge($defaultValue, $value); + break; + case 'ai.translation_provider_preferences': + $value += array_diff($defaultValue, $value); // Add entries from $defaultValue that are not in $value to the end of $value + break; + default: + break; + } + } + $settings[$key] = $value; + } + + $this->initialState->provideInitialState('ai-settings', $settings); + + return new TemplateResponse('settings', 'settings/admin/ai'); + } + + /** + * @return string the section ID, e.g. 'sharing' + */ + public function getSection() { + return 'ai'; + } + + /** + * @return int whether the form should be rather on the top or bottom of + * the admin section. The forms are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * + * E.g.: 70 + */ + public function getPriority() { + return 10; + } + + public function getName(): ?string { + return $this->l->t('Artificial Intelligence'); + } + + public function getAuthorizedAppConfig(): array { + return [ + 'core' => ['/ai_.*/'], + ]; + } +} diff --git a/apps/settings/src/components/AdminAI.vue b/apps/settings/src/components/AdminAI.vue new file mode 100644 index 00000000000..2e1104a49d2 --- /dev/null +++ b/apps/settings/src/components/AdminAI.vue @@ -0,0 +1,83 @@ + + + diff --git a/apps/settings/templates/settings/admin/ai.php b/apps/settings/templates/settings/admin/ai.php new file mode 100644 index 00000000000..fac7ad8d25b --- /dev/null +++ b/apps/settings/templates/settings/admin/ai.php @@ -0,0 +1,28 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +script('settings', [ + 'vue-settings-admin-ai', +]); +?> + +
+
diff --git a/lib/public/SpeechToText/ISpeechToTextManager.php b/lib/public/SpeechToText/ISpeechToTextManager.php index eff00ec0fa1..96973cfca08 100644 --- a/lib/public/SpeechToText/ISpeechToTextManager.php +++ b/lib/public/SpeechToText/ISpeechToTextManager.php @@ -40,6 +40,12 @@ interface ISpeechToTextManager { */ public function hasProviders(): bool; + /** + * @return ISpeechToTextProvider[] + * @since 27.1.0 + */ + public function getProviders(): array; + /** * Will schedule a transcription process in the background. The result will become available * with the \OCP\SpeechToText\Events\TranscriptionFinishedEvent diff --git a/lib/public/TextProcessing/IManager.php b/lib/public/TextProcessing/IManager.php index 90e25894d4f..50d012eca68 100644 --- a/lib/public/TextProcessing/IManager.php +++ b/lib/public/TextProcessing/IManager.php @@ -41,6 +41,12 @@ interface IManager { */ public function hasProviders(): bool; + /** + * @return IProvider[] + * @since 27.1.0 + */ + public function getProviders(): array; + /** * @return class-string[] * @since 27.1.0 diff --git a/lib/public/Translation/ITranslationManager.php b/lib/public/Translation/ITranslationManager.php index 4450f19c424..5b342faea75 100644 --- a/lib/public/Translation/ITranslationManager.php +++ b/lib/public/Translation/ITranslationManager.php @@ -38,6 +38,12 @@ interface ITranslationManager { */ public function hasProviders(): bool; + /** + * @return ITranslationProvider[] + * @since 27.1.0 + */ + public function getProviders(): array; + /** * @since 26.0.0 */ diff --git a/webpack.modules.js b/webpack.modules.js index bbdbb720245..31c12f09fb2 100644 --- a/webpack.modules.js +++ b/webpack.modules.js @@ -82,6 +82,7 @@ module.exports = { apps: path.join(__dirname, 'apps/settings/src', 'apps.js'), 'legacy-admin': path.join(__dirname, 'apps/settings/src', 'admin.js'), 'vue-settings-admin-basic-settings': path.join(__dirname, 'apps/settings/src', 'main-admin-basic-settings.js'), + 'vue-settings-admin-ai': path.join(__dirname, 'apps/settings/src', 'main-admin-ai.js'), 'vue-settings-admin-delegation': path.join(__dirname, 'apps/settings/src', 'main-admin-delegation.js'), 'vue-settings-admin-security': path.join(__dirname, 'apps/settings/src', 'main-admin-security.js'), 'vue-settings-apps-users-management': path.join(__dirname, 'apps/settings/src', 'main-apps-users-management.js'), -- cgit v1.2.3 From 4e33d044443973f2fc82e74bca0bc43e845a36e7 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 24 Jul 2023 14:06:31 +0200 Subject: AI Admin settings: Implement mt settings, stt settings and tp settings Signed-off-by: Marcel Klehr --- apps/settings/src/components/AdminAI.vue | 85 ++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/apps/settings/src/components/AdminAI.vue b/apps/settings/src/components/AdminAI.vue index 2e1104a49d2..f11cb0c3da0 100644 --- a/apps/settings/src/components/AdminAI.vue +++ b/apps/settings/src/components/AdminAI.vue @@ -1,34 +1,53 @@ + -- cgit v1.2.3 From 8ec1926aba549eeaea872d7dd2fcb69592b3a3bc Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 24 Jul 2023 14:07:34 +0200 Subject: fix(TextProcessing): Inject L10N\IFactory instead of IL10N Signed-off-by: Marcel Klehr --- lib/public/TextProcessing/FreePromptTaskType.php | 8 ++++++-- lib/public/TextProcessing/HeadlineTaskType.php | 10 +++++++--- lib/public/TextProcessing/SummaryTaskType.php | 9 +++++++-- lib/public/TextProcessing/TopicsTaskType.php | 8 ++++++-- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/lib/public/TextProcessing/FreePromptTaskType.php b/lib/public/TextProcessing/FreePromptTaskType.php index dcc27df77c9..a1e52e268a3 100644 --- a/lib/public/TextProcessing/FreePromptTaskType.php +++ b/lib/public/TextProcessing/FreePromptTaskType.php @@ -26,21 +26,25 @@ declare(strict_types=1); namespace OCP\TextProcessing; use OCP\IL10N; +use OCP\L10N\IFactory; /** * This is the text processing task type for free prompting * @since 27.1.0 */ class FreePromptTaskType implements ITaskType { + private IL10N $l; + /** * Constructor for FreePromptTaskType * - * @param IL10N $l + * @param IFactory $l10nFactory * @since 27.1.0 */ public function __construct( - private IL10N $l, + IFactory $l10nFactory, ) { + $this->l = $l10nFactory->get('core'); } diff --git a/lib/public/TextProcessing/HeadlineTaskType.php b/lib/public/TextProcessing/HeadlineTaskType.php index ad38848ea87..7061ca1d69b 100644 --- a/lib/public/TextProcessing/HeadlineTaskType.php +++ b/lib/public/TextProcessing/HeadlineTaskType.php @@ -26,21 +26,25 @@ declare(strict_types=1); namespace OCP\TextProcessing; use OCP\IL10N; +use OCP\L10N\IFactory; /** * This is the text processing task type for creating headline * @since 27.1.0 */ class HeadlineTaskType implements ITaskType { + private IL10N $l; + /** * Constructor for HeadlineTaskType * - * @param IL10N $l + * @param IFactory $l10nFactory * @since 27.1.0 */ public function __construct( - private IL10N $l, + IFactory $l10nFactory, ) { + $this->l = $l10nFactory->get('core'); } @@ -57,6 +61,6 @@ class HeadlineTaskType implements ITaskType { * @since 27.1.0 */ public function getDescription(): string { - return $this->l->t('Generates a possible headline for a text'); + return $this->l->t('Generates a possible headline for a text.'); } } diff --git a/lib/public/TextProcessing/SummaryTaskType.php b/lib/public/TextProcessing/SummaryTaskType.php index 3d80cee47f8..03e4b5b8ece 100644 --- a/lib/public/TextProcessing/SummaryTaskType.php +++ b/lib/public/TextProcessing/SummaryTaskType.php @@ -26,21 +26,26 @@ declare(strict_types=1); namespace OCP\TextProcessing; use OCP\IL10N; +use OCP\L10N\IFactory; /** * This is the text processing task type for summaries * @since 27.1.0 */ class SummaryTaskType implements ITaskType { + + private IL10N $l; + /** * Constructor for SummaryTaskType * - * @param IL10N $l + * @param IFactory $l10nFactory * @since 27.1.0 */ public function __construct( - private IL10N $l, + IFactory $l10nFactory, ) { + $this->l = $l10nFactory->get('core'); } diff --git a/lib/public/TextProcessing/TopicsTaskType.php b/lib/public/TextProcessing/TopicsTaskType.php index 6162b9a13e9..5a994a7a8d2 100644 --- a/lib/public/TextProcessing/TopicsTaskType.php +++ b/lib/public/TextProcessing/TopicsTaskType.php @@ -26,21 +26,25 @@ declare(strict_types=1); namespace OCP\TextProcessing; use OCP\IL10N; +use OCP\L10N\IFactory; /** * This is the text processing task type for topics extraction * @since 27.1.0 */ class TopicsTaskType implements ITaskType { + private IL10N $l; + /** * Constructor for TopicsTaskType * - * @param IL10N $l + * @param IFactory $l10nFactory * @since 27.1.0 */ public function __construct( - private IL10N $l, + IFactory $l10nFactory, ) { + $this->l = $l10nFactory->get('core'); } -- cgit v1.2.3 From a840e8c6e5a0d7b7c3a371776fd395976b2c2fdf Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 24 Jul 2023 14:47:15 +0200 Subject: AI admin settings: Add save mechanism Signed-off-by: Marcel Klehr --- apps/settings/appinfo/routes.php | 1 + .../lib/Controller/AISettingsController.php | 66 +++++----------------- .../lib/Settings/Admin/ArtificialIntelligence.php | 2 +- apps/settings/src/components/AdminAI.vue | 45 +++++++-------- 4 files changed, 39 insertions(+), 75 deletions(-) diff --git a/apps/settings/appinfo/routes.php b/apps/settings/appinfo/routes.php index 938842dd576..e238510b1a7 100644 --- a/apps/settings/appinfo/routes.php +++ b/apps/settings/appinfo/routes.php @@ -75,6 +75,7 @@ return [ ['name' => 'ChangePassword#changeUserPassword', 'url' => '/settings/users/changepassword', 'verb' => 'POST' , 'root' => ''], ['name' => 'TwoFactorSettings#index', 'url' => '/settings/api/admin/twofactorauth', 'verb' => 'GET' , 'root' => ''], ['name' => 'TwoFactorSettings#update', 'url' => '/settings/api/admin/twofactorauth', 'verb' => 'PUT' , 'root' => ''], + ['name' => 'AISettings#update', 'url' => '/settings/api/admin/ai', 'verb' => 'PUT' , 'root' => ''], ['name' => 'Help#help', 'url' => '/settings/help/{mode}', 'verb' => 'GET', 'defaults' => ['mode' => ''] , 'root' => ''], diff --git a/apps/settings/lib/Controller/AISettingsController.php b/apps/settings/lib/Controller/AISettingsController.php index 53aca66c946..2c443c1a771 100644 --- a/apps/settings/lib/Controller/AISettingsController.php +++ b/apps/settings/lib/Controller/AISettingsController.php @@ -1,14 +1,6 @@ - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst - * @author Daniel Kesselberg - * @author Joas Schilling - * @author Lukas Reschke - * @author Morris Jobke - * @author Roeland Jago Douma + * @copyright Copyright (c) 2023 Marcel Klehr * * @license AGPL-3.0 * @@ -36,70 +28,40 @@ use OCP\IRequest; use OCP\IURLGenerator; use OCP\IUserSession; use OCP\Mail\IMailer; +use function GuzzleHttp\Promise\queue; class AISettingsController extends Controller { - /** @var IL10N */ - private $l10n; - /** @var IConfig */ - private $config; - /** @var IUserSession */ - private $userSession; - /** @var IMailer */ - private $mailer; - /** @var IURLGenerator */ - private $urlGenerator; - /** * @param string $appName * @param IRequest $request - * @param IL10N $l10n * @param IConfig $config - * @param IUserSession $userSession - * @param IURLGenerator $urlGenerator, - * @param IMailer $mailer */ - public function __construct($appName, - IRequest $request, - IL10N $l10n, - IConfig $config, - IUserSession $userSession, - IURLGenerator $urlGenerator, - IMailer $mailer) { + public function __construct( + $appName, + IRequest $request, + private IConfig $config, + ) { parent::__construct($appName, $request); - $this->l10n = $l10n; - $this->config = $config; - $this->userSession = $userSession; - $this->urlGenerator = $urlGenerator; - $this->mailer = $mailer; } /** * Sets the email settings * - * @PasswordConfirmationRequired * @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\ArtificialIntelligence) * * @param array $settings * @return DataResponse */ - public function setAISettings($settings) { - $params = get_defined_vars(); - $configs = []; - foreach ($params as $key => $value) { - $configs[$key] = empty($value) ? null : $value; - } - - // Delete passwords from config in case no auth is specified - if ($params['mail_smtpauth'] !== 1) { - $configs['mail_smtpname'] = null; - $configs['mail_smtppassword'] = null; + public function update($settings) { + $keys = ['ai.stt_provider', 'ai.textprocessing_provider_preferences', 'ai.translation_provider_preferences']; + foreach ($keys as $key) { + if (!isset($settings[$key])) { + continue; + } + $this->config->setAppValue('core', $key, json_encode($settings[$key])); } - $this->config->setSystemValues($configs); - - $this->config->setAppValue('core', 'emailTestSuccessful', '0'); - return new DataResponse(); } } diff --git a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php index d33802d6c45..ad5425d8ded 100644 --- a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php +++ b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php @@ -157,7 +157,7 @@ class ArtificialIntelligence implements IDelegatedSettings { public function getAuthorizedAppConfig(): array { return [ - 'core' => ['/ai_.*/'], + 'core' => ['/ai..*/'], ]; } } diff --git a/apps/settings/src/components/AdminAI.vue b/apps/settings/src/components/AdminAI.vue index f11cb0c3da0..649c484c70a 100644 --- a/apps/settings/src/components/AdminAI.vue +++ b/apps/settings/src/components/AdminAI.vue @@ -2,8 +2,10 @@
- -
{{i+1}} {{ translationProviders.find(p => p.class === providerClass)?.name }}
+ +
+ {{ i+1 }} {{ translationProviders.find(p => p.class === providerClass)?.name }} +
+ type="radio" + @update:checked="saveChanges"> {{ provider.name }} @@ -29,9 +32,16 @@

{{ t('settings', 'Task:') }} {{ getTaskType(type).name }}

{{ getTaskType(type).description }}

 

- - - + + +

 

@@ -74,24 +84,15 @@ export default { } }, methods: { - saveChanges() { + async saveChanges() { this.loading = true - - const data = { - enforced: this.enforced, - enforcedGroups: this.enforcedGroups, - excludedGroups: this.excludedGroups, + const data = this.settings + try { + await axios.put(generateUrl('/settings/api/admin/ai'), data) + } catch (err) { + console.error('could not save changes', err) } - axios.put(generateUrl('/settings/api/admin/twofactorauth'), data) - .then(resp => resp.data) - .then(state => { - this.state = state - this.dirty = false - }) - .catch(err => { - console.error('could not save changes', err) - }) - .then(() => { this.loading = false }) + this.loading = false }, getTaskType(type) { if (!Array.isArray(this.textProcessingTaskTypes)) { -- cgit v1.2.3 From 2a3ef102f7527041609ad96bc59d6b21f42980d4 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 25 Jul 2023 14:17:57 +0200 Subject: AI admin settings: Use config values in AI feature managers Signed-off-by: Marcel Klehr --- .../lib/Settings/Admin/ArtificialIntelligence.php | 2 +- lib/private/SpeechToText/SpeechToTextManager.php | 15 ++++++++++++- lib/private/TextProcessing/Manager.php | 18 +++++++++++++++- lib/private/Translation/TranslationManager.php | 25 +++++++++++++++++++--- 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php index ad5425d8ded..2f36e4b0330 100644 --- a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php +++ b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php @@ -112,7 +112,7 @@ class ArtificialIntelligence implements IDelegatedSettings { $value = $defaultValue; $json = $this->config->getAppValue('core', $key, ''); if ($json !== '') { - $value = json_decode($json, JSON_OBJECT_AS_ARRAY); + $value = json_decode($json, true); switch($key) { case 'ai.textprocessing_provider_preferences': // fill $value with $defaultValue values diff --git a/lib/private/SpeechToText/SpeechToTextManager.php b/lib/private/SpeechToText/SpeechToTextManager.php index 757fc02485e..bdd04ad3651 100644 --- a/lib/private/SpeechToText/SpeechToTextManager.php +++ b/lib/private/SpeechToText/SpeechToTextManager.php @@ -34,6 +34,7 @@ use OCP\BackgroundJob\IJobList; use OCP\Files\File; use OCP\Files\InvalidPathException; use OCP\Files\NotFoundException; +use OCP\IConfig; use OCP\IServerContainer; use OCP\PreConditionNotMetException; use OCP\SpeechToText\ISpeechToTextManager; @@ -53,6 +54,7 @@ class SpeechToTextManager implements ISpeechToTextManager { private Coordinator $coordinator, private LoggerInterface $logger, private IJobList $jobList, + private IConfig $config, ) { } @@ -111,7 +113,18 @@ class SpeechToTextManager implements ISpeechToTextManager { throw new PreConditionNotMetException('No SpeechToText providers have been registered'); } - foreach ($this->getProviders() as $provider) { + $providers = $this->getProviders(); + + $json = $this->config->getAppValue('core', 'ai.stt_provider', ''); + if ($json !== '') { + $className = json_decode($json, true); + $provider = current(array_filter($providers, fn ($provider) => $provider::class === $className)); + if ($provider !== false) { + $providers = [$provider]; + } + } + + foreach ($providers as $provider) { try { return $provider->transcribeFile($file); } catch (\Throwable $e) { diff --git a/lib/private/TextProcessing/Manager.php b/lib/private/TextProcessing/Manager.php index f52482bbb32..05e046a0049 100644 --- a/lib/private/TextProcessing/Manager.php +++ b/lib/private/TextProcessing/Manager.php @@ -27,6 +27,7 @@ namespace OC\TextProcessing; use OC\AppFramework\Bootstrap\Coordinator; use OC\TextProcessing\Db\Task as DbTask; +use OCP\IConfig; use OCP\TextProcessing\Task as OCPTask; use OC\TextProcessing\Db\TaskMapper; use OCP\AppFramework\Db\DoesNotExistException; @@ -52,6 +53,7 @@ class Manager implements IManager { private LoggerInterface $logger, private IJobList $jobList, private TaskMapper $taskMapper, + private IConfig $config, ) { } @@ -111,7 +113,21 @@ class Manager implements IManager { if (!$this->canHandleTask($task)) { throw new PreConditionNotMetException('No text processing provider is installed that can handle this task'); } - foreach ($this->getProviders() as $provider) { + $providers = $this->getProviders(); + $json = $this->config->getAppValue('core', 'ai.textprocessing_provider_preferences', ''); + if ($json !== '') { + $preferences = json_decode($json, true); + if (isset($preferences[$task->getType()])) { + // If a preference for this task type is set, move the preferred provider to the start + $provider = current(array_filter($providers, fn ($provider) => $provider::class === $preferences[$task->getType()])); + if ($provider !== false) { + $providers = array_filter($providers, fn ($p) => $p !== $provider); + array_unshift($providers, $provider); + } + } + } + + foreach ($providers as $provider) { if (!$task->canUseProvider($provider)) { continue; } diff --git a/lib/private/Translation/TranslationManager.php b/lib/private/Translation/TranslationManager.php index 8456c41cdfc..48a0e2cdebd 100644 --- a/lib/private/Translation/TranslationManager.php +++ b/lib/private/Translation/TranslationManager.php @@ -28,6 +28,7 @@ namespace OC\Translation; use InvalidArgumentException; use OC\AppFramework\Bootstrap\Coordinator; +use OCP\IConfig; use OCP\IServerContainer; use OCP\PreConditionNotMetException; use OCP\Translation\CouldNotTranslateException; @@ -48,6 +49,7 @@ class TranslationManager implements ITranslationManager { private IServerContainer $serverContainer, private Coordinator $coordinator, private LoggerInterface $logger, + private IConfig $config, ) { } @@ -64,8 +66,25 @@ class TranslationManager implements ITranslationManager { throw new PreConditionNotMetException('No translation providers available'); } + $providers = $this->getProviders(); + $json = $this->config->getAppValue('core', 'ai.translation_provider_preferences', ''); + + if ($json !== '') { + $precedence = json_decode($json, true); + $newProviders = []; + foreach ($precedence as $className) { + $provider = current(array_filter($providers, fn ($provider) => $provider::class === $className)); + if ($provider !== false) { + $newProviders[] = $provider; + } + } + // Add all providers that haven't been added so far + $newProviders += array_udiff($providers, $newProviders, fn ($a, $b) => $a::class > $b::class ? 1 : ($a::class < $b::class ? -1 : 0)); + $providers = $newProviders; + } + if ($fromLanguage === null) { - foreach ($this->getProviders() as $provider) { + foreach ($providers as $provider) { if ($provider instanceof IDetectLanguageProvider) { $fromLanguage = $provider->detectLanguage($text); } @@ -84,11 +103,11 @@ class TranslationManager implements ITranslationManager { return $text; } - foreach ($this->getProviders() as $provider) { + foreach ($providers as $provider) { try { return $provider->translate($fromLanguage, $toLanguage, $text); } catch (RuntimeException $e) { - $this->logger->warning("Failed to translate from {$fromLanguage} to {$toLanguage}", ['exception' => $e]); + $this->logger->warning("Failed to translate from {$fromLanguage} to {$toLanguage} using provider {$provider->getName()}", ['exception' => $e]); } } -- cgit v1.2.3 From 2d29413d5d7193b97c808c983d57d73efd463d23 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 25 Jul 2023 14:18:28 +0200 Subject: AI admin settings: Add a draggable icon in UI for translation provider precedence Signed-off-by: Marcel Klehr --- apps/settings/src/components/AdminAI.vue | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/settings/src/components/AdminAI.vue b/apps/settings/src/components/AdminAI.vue index 649c484c70a..5461e3f69d8 100644 --- a/apps/settings/src/components/AdminAI.vue +++ b/apps/settings/src/components/AdminAI.vue @@ -4,7 +4,7 @@ :description="t('settings', 'Machine translation can be implemented by different apps. Here you can define the precedence of the machine translation apps you have installed at the moment.')">
- {{ i+1 }} {{ translationProviders.find(p => p.class === providerClass)?.name }} + {{ i+1 }} {{ translationProviders.find(p => p.class === providerClass)?.name }}
@@ -58,6 +58,7 @@ import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadi import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js' import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' import draggable from 'vuedraggable' +import DragVerticalIcon from 'vue-material-design-icons/DragVertical.vue' import { loadState } from '@nextcloud/initial-state' import { generateUrl } from '@nextcloud/router' @@ -69,6 +70,7 @@ export default { NcSettingsSection, NcSelect, draggable, + DragVerticalIcon, }, data() { return { @@ -86,7 +88,7 @@ export default { methods: { async saveChanges() { this.loading = true - const data = this.settings + const data = {settings: this.settings} try { await axios.put(generateUrl('/settings/api/admin/ai'), data) } catch (err) { @@ -108,10 +110,19 @@ export default { margin-bottom: 5px; } +.draggable__item, +.draggable__item * { + cursor: grab; +} + .draggable__number { border-radius: 20px; border: 2px solid var(--color-primary-default); color: var(--color-primary-default); padding: 2px 7px; } + +.drag-vertical-icon { + float: left; +} -- cgit v1.2.3 From aa74d6fd31fb4324fe8daead1d3a2c57ee39f4f3 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 25 Jul 2023 14:21:37 +0200 Subject: AI admin settings: Update composer autoloaders Signed-off-by: Marcel Klehr --- apps/settings/composer/composer/autoload_classmap.php | 3 +++ apps/settings/composer/composer/autoload_static.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/apps/settings/composer/composer/autoload_classmap.php b/apps/settings/composer/composer/autoload_classmap.php index 32de1ff6d2a..a2d62b53017 100644 --- a/apps/settings/composer/composer/autoload_classmap.php +++ b/apps/settings/composer/composer/autoload_classmap.php @@ -16,6 +16,7 @@ return array( 'OCA\\Settings\\Activity\\Setting' => $baseDir . '/../lib/Activity/Setting.php', 'OCA\\Settings\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php', 'OCA\\Settings\\BackgroundJobs\\VerifyUserData' => $baseDir . '/../lib/BackgroundJobs/VerifyUserData.php', + 'OCA\\Settings\\Controller\\AISettingsController' => $baseDir . '/../lib/Controller/AISettingsController.php', 'OCA\\Settings\\Controller\\AdminSettingsController' => $baseDir . '/../lib/Controller/AdminSettingsController.php', 'OCA\\Settings\\Controller\\AppSettingsController' => $baseDir . '/../lib/Controller/AppSettingsController.php', 'OCA\\Settings\\Controller\\AuthSettingsController' => $baseDir . '/../lib/Controller/AuthSettingsController.php', @@ -42,6 +43,7 @@ return array( 'OCA\\Settings\\Search\\AppSearch' => $baseDir . '/../lib/Search/AppSearch.php', 'OCA\\Settings\\Search\\SectionSearch' => $baseDir . '/../lib/Search/SectionSearch.php', 'OCA\\Settings\\Sections\\Admin\\Additional' => $baseDir . '/../lib/Sections/Admin/Additional.php', + 'OCA\\Settings\\Sections\\Admin\\ArtificialIntelligence' => $baseDir . '/../lib/Sections/Admin/ArtificialIntelligence.php', 'OCA\\Settings\\Sections\\Admin\\Delegation' => $baseDir . '/../lib/Sections/Admin/Delegation.php', 'OCA\\Settings\\Sections\\Admin\\Groupware' => $baseDir . '/../lib/Sections/Admin/Groupware.php', 'OCA\\Settings\\Sections\\Admin\\Overview' => $baseDir . '/../lib/Sections/Admin/Overview.php', @@ -56,6 +58,7 @@ return array( 'OCA\\Settings\\Service\\AuthorizedGroupService' => $baseDir . '/../lib/Service/AuthorizedGroupService.php', 'OCA\\Settings\\Service\\NotFoundException' => $baseDir . '/../lib/Service/NotFoundException.php', 'OCA\\Settings\\Service\\ServiceException' => $baseDir . '/../lib/Service/ServiceException.php', + 'OCA\\Settings\\Settings\\Admin\\ArtificialIntelligence' => $baseDir . '/../lib/Settings/Admin/ArtificialIntelligence.php', 'OCA\\Settings\\Settings\\Admin\\Delegation' => $baseDir . '/../lib/Settings/Admin/Delegation.php', 'OCA\\Settings\\Settings\\Admin\\Mail' => $baseDir . '/../lib/Settings/Admin/Mail.php', 'OCA\\Settings\\Settings\\Admin\\Overview' => $baseDir . '/../lib/Settings/Admin/Overview.php', diff --git a/apps/settings/composer/composer/autoload_static.php b/apps/settings/composer/composer/autoload_static.php index 57235766a7c..c76e3d84ae3 100644 --- a/apps/settings/composer/composer/autoload_static.php +++ b/apps/settings/composer/composer/autoload_static.php @@ -31,6 +31,7 @@ class ComposerStaticInitSettings 'OCA\\Settings\\Activity\\Setting' => __DIR__ . '/..' . '/../lib/Activity/Setting.php', 'OCA\\Settings\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php', 'OCA\\Settings\\BackgroundJobs\\VerifyUserData' => __DIR__ . '/..' . '/../lib/BackgroundJobs/VerifyUserData.php', + 'OCA\\Settings\\Controller\\AISettingsController' => __DIR__ . '/..' . '/../lib/Controller/AISettingsController.php', 'OCA\\Settings\\Controller\\AdminSettingsController' => __DIR__ . '/..' . '/../lib/Controller/AdminSettingsController.php', 'OCA\\Settings\\Controller\\AppSettingsController' => __DIR__ . '/..' . '/../lib/Controller/AppSettingsController.php', 'OCA\\Settings\\Controller\\AuthSettingsController' => __DIR__ . '/..' . '/../lib/Controller/AuthSettingsController.php', @@ -57,6 +58,7 @@ class ComposerStaticInitSettings 'OCA\\Settings\\Search\\AppSearch' => __DIR__ . '/..' . '/../lib/Search/AppSearch.php', 'OCA\\Settings\\Search\\SectionSearch' => __DIR__ . '/..' . '/../lib/Search/SectionSearch.php', 'OCA\\Settings\\Sections\\Admin\\Additional' => __DIR__ . '/..' . '/../lib/Sections/Admin/Additional.php', + 'OCA\\Settings\\Sections\\Admin\\ArtificialIntelligence' => __DIR__ . '/..' . '/../lib/Sections/Admin/ArtificialIntelligence.php', 'OCA\\Settings\\Sections\\Admin\\Delegation' => __DIR__ . '/..' . '/../lib/Sections/Admin/Delegation.php', 'OCA\\Settings\\Sections\\Admin\\Groupware' => __DIR__ . '/..' . '/../lib/Sections/Admin/Groupware.php', 'OCA\\Settings\\Sections\\Admin\\Overview' => __DIR__ . '/..' . '/../lib/Sections/Admin/Overview.php', @@ -71,6 +73,7 @@ class ComposerStaticInitSettings 'OCA\\Settings\\Service\\AuthorizedGroupService' => __DIR__ . '/..' . '/../lib/Service/AuthorizedGroupService.php', 'OCA\\Settings\\Service\\NotFoundException' => __DIR__ . '/..' . '/../lib/Service/NotFoundException.php', 'OCA\\Settings\\Service\\ServiceException' => __DIR__ . '/..' . '/../lib/Service/ServiceException.php', + 'OCA\\Settings\\Settings\\Admin\\ArtificialIntelligence' => __DIR__ . '/..' . '/../lib/Settings/Admin/ArtificialIntelligence.php', 'OCA\\Settings\\Settings\\Admin\\Delegation' => __DIR__ . '/..' . '/../lib/Settings/Admin/Delegation.php', 'OCA\\Settings\\Settings\\Admin\\Mail' => __DIR__ . '/..' . '/../lib/Settings/Admin/Mail.php', 'OCA\\Settings\\Settings\\Admin\\Overview' => __DIR__ . '/..' . '/../lib/Settings/Admin/Overview.php', -- cgit v1.2.3 From f72f3f7df4f551b8a6c01d3e3b5969d1bb0264d8 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 25 Jul 2023 14:27:20 +0200 Subject: AI admin settings: strict_types Signed-off-by: Marcel Klehr --- apps/settings/lib/Controller/AISettingsController.php | 3 +++ apps/settings/lib/Settings/Admin/ArtificialIntelligence.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/apps/settings/lib/Controller/AISettingsController.php b/apps/settings/lib/Controller/AISettingsController.php index 2c443c1a771..7f016d79c25 100644 --- a/apps/settings/lib/Controller/AISettingsController.php +++ b/apps/settings/lib/Controller/AISettingsController.php @@ -1,4 +1,7 @@ * diff --git a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php index 2f36e4b0330..ab95358683e 100644 --- a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php +++ b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php @@ -1,4 +1,7 @@ * -- cgit v1.2.3 From 4ec150c9b6d24e877a7843fc005cfb6b8997363b Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 25 Jul 2023 14:28:57 +0200 Subject: AI admin settings: cs:fix Signed-off-by: Marcel Klehr --- lib/public/TextProcessing/SummaryTaskType.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/public/TextProcessing/SummaryTaskType.php b/lib/public/TextProcessing/SummaryTaskType.php index 03e4b5b8ece..670a7cb4da6 100644 --- a/lib/public/TextProcessing/SummaryTaskType.php +++ b/lib/public/TextProcessing/SummaryTaskType.php @@ -33,7 +33,6 @@ use OCP\L10N\IFactory; * @since 27.1.0 */ class SummaryTaskType implements ITaskType { - private IL10N $l; /** -- cgit v1.2.3 From 112268a48aebf2f2025ee106f41fd6186b08bb59 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 25 Jul 2023 14:47:27 +0200 Subject: AI admin settings: lint:fix Signed-off-by: Marcel Klehr --- apps/settings/src/admin.js | 2 +- apps/settings/src/components/AdminAI.vue | 2 +- .../src/components/AdminDelegation/GroupSelect.vue | 4 +-- apps/settings/src/components/AdminTwoFactor.vue | 8 ++--- apps/settings/src/components/AuthTokenSection.vue | 2 +- apps/settings/src/components/Markdown.vue | 2 +- .../src/components/PersonalInfo/AvatarSection.vue | 12 ++++--- .../src/components/PersonalInfo/DetailsSection.vue | 20 ++++++----- .../components/PersonalInfo/DisplayNameSection.vue | 2 +- .../PersonalInfo/EmailSection/EmailSection.vue | 6 ++-- .../PersonalInfo/LanguageSection/Language.vue | 2 +- .../PersonalInfo/LocaleSection/Locale.vue | 4 +-- .../PersonalInfo/shared/AccountPropertySection.vue | 2 +- .../components/PersonalInfo/shared/HeaderBar.vue | 2 +- apps/settings/src/components/UserList.vue | 4 +-- .../settings/src/components/Users/NewUserModal.vue | 32 +++++++++--------- apps/settings/src/components/Users/UserRow.vue | 6 ++-- apps/settings/src/main-admin-ai.js | 39 ++++++++++++++++++++++ apps/settings/src/main-admin-security.js | 2 +- apps/settings/src/utils/userUtils.ts | 2 ++ 20 files changed, 99 insertions(+), 56 deletions(-) create mode 100644 apps/settings/src/main-admin-ai.js diff --git a/apps/settings/src/admin.js b/apps/settings/src/admin.js index 3bbfb564763..c8d04049ded 100644 --- a/apps/settings/src/admin.js +++ b/apps/settings/src/admin.js @@ -242,7 +242,7 @@ window.addEventListener('DOMContentLoaded', () => { OC.SetupChecks.checkSetup(), OC.SetupChecks.checkGeneric(), OC.SetupChecks.checkWOFF2Loading(OC.filePath('core', '', 'fonts/NotoSans-Regular-latin.woff2'), OC.theme.docPlaceholderUrl), - OC.SetupChecks.checkDataProtected() + OC.SetupChecks.checkDataProtected(), ).then((check1, check2, check3, check4, check5, check6, check7, check8, check9, check10, check11) => { const messages = [].concat(check1, check2, check3, check4, check5, check6, check7, check8, check9, check10, check11) const $el = $('#postsetupchecks') diff --git a/apps/settings/src/components/AdminAI.vue b/apps/settings/src/components/AdminAI.vue index 5461e3f69d8..05175bb7988 100644 --- a/apps/settings/src/components/AdminAI.vue +++ b/apps/settings/src/components/AdminAI.vue @@ -88,7 +88,7 @@ export default { methods: { async saveChanges() { this.loading = true - const data = {settings: this.settings} + const data = { settings: this.settings } try { await axios.put(generateUrl('/settings/api/admin/ai'), data) } catch (err) { diff --git a/apps/settings/src/components/AdminDelegation/GroupSelect.vue b/apps/settings/src/components/AdminDelegation/GroupSelect.vue index 82b5e51fb45..91593516760 100644 --- a/apps/settings/src/components/AdminDelegation/GroupSelect.vue +++ b/apps/settings/src/components/AdminDelegation/GroupSelect.vue @@ -1,6 +1,6 @@