]> source.dussan.org Git - nextcloud-server.git/commitdiff
First pass at ai admin settings
authorMarcel Klehr <mklehr@gmx.net>
Mon, 24 Jul 2023 10:12:06 +0000 (12:12 +0200)
committerJulien Veyssier <julien-nc@posteo.net>
Wed, 2 Aug 2023 10:37:35 +0000 (12:37 +0200)
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
apps/settings/appinfo/info.xml
apps/settings/img/ai.svg [new file with mode: 0644]
apps/settings/lib/Controller/AISettingsController.php [new file with mode: 0644]
apps/settings/lib/Sections/Admin/ArtificialIntelligence.php [new file with mode: 0644]
apps/settings/lib/Settings/Admin/ArtificialIntelligence.php [new file with mode: 0644]
apps/settings/src/components/AdminAI.vue [new file with mode: 0644]
apps/settings/templates/settings/admin/ai.php [new file with mode: 0644]
lib/public/SpeechToText/ISpeechToTextManager.php
lib/public/TextProcessing/IManager.php
lib/public/Translation/ITranslationManager.php
webpack.modules.js

index 92d2928cd31a43c51fa3470953ecae9171cfcf36..fefa2fadaeffe096e049f049f9f7daf1d46ead27 100644 (file)
@@ -19,6 +19,7 @@
        <settings>
                <admin>OCA\Settings\Settings\Admin\Mail</admin>
                <admin>OCA\Settings\Settings\Admin\Overview</admin>
+               <admin>OCA\Settings\Settings\Admin\ArtificialIntelligence</admin>
                <admin>OCA\Settings\Settings\Admin\Server</admin>
                <admin>OCA\Settings\Settings\Admin\Sharing</admin>
                <admin>OCA\Settings\Settings\Admin\Security</admin>
@@ -27,6 +28,7 @@
                <admin-section>OCA\Settings\Sections\Admin\Delegation</admin-section>
                <admin-section>OCA\Settings\Sections\Admin\Groupware</admin-section>
                <admin-section>OCA\Settings\Sections\Admin\Overview</admin-section>
+               <admin-section>OCA\Settings\Sections\Admin\ArtificialIntelligence</admin-section>
                <admin-section>OCA\Settings\Sections\Admin\Security</admin-section>
                <admin-section>OCA\Settings\Sections\Admin\Server</admin-section>
                <admin-section>OCA\Settings\Sections\Admin\Sharing</admin-section>
diff --git a/apps/settings/img/ai.svg b/apps/settings/img/ai.svg
new file mode 100644 (file)
index 0000000..5d59fd6
--- /dev/null
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,2A2,2 0 0,1 14,4C14,4.74 13.6,5.39 13,5.73V7H14A7,7 0 0,1 21,14H22A1,1 0 0,1 23,15V18A1,1 0 0,1 22,19H21V20A2,2 0 0,1 19,22H5A2,2 0 0,1 3,20V19H2A1,1 0 0,1 1,18V15A1,1 0 0,1 2,14H3A7,7 0 0,1 10,7H11V5.73C10.4,5.39 10,4.74 10,4A2,2 0 0,1 12,2M7.5,13A2.5,2.5 0 0,0 5,15.5A2.5,2.5 0 0,0 7.5,18A2.5,2.5 0 0,0 10,15.5A2.5,2.5 0 0,0 7.5,13M16.5,13A2.5,2.5 0 0,0 14,15.5A2.5,2.5 0 0,0 16.5,18A2.5,2.5 0 0,0 19,15.5A2.5,2.5 0 0,0 16.5,13Z" /></svg>
\ 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 (file)
index 0000000..53aca66
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017  Joas Schilling <coding@schilljs.com>
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Daniel Kesselberg <mail@danielkesselberg.de>
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @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 <http://www.gnu.org/licenses/>
+ *
+ */
+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 (file)
index 0000000..1a25cdf
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ *
+ */
+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 (file)
index 0000000..d33802d
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+/**
+ * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
+ *
+ * @author Marcel Klehr <mklehr@gmx.net>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ *
+ */
+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<ITaskType>, class-string<IProvider>> $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 (file)
index 0000000..2e1104a
--- /dev/null
@@ -0,0 +1,83 @@
+<template>
+       <NcSettingsSection :title="t('settings', 'Artificial Intelligence')"
+               :description="t('settings', 'Artificial Intelligence features can be implemented by different apps. Here you can set which app should be used for which features.')">
+               <h3>{{ t('settings', 'Translations') }}</h3>
+               <h3>{{ t('settings', 'Speech-To-Text') }}</h3>
+               <template v-for="provider in sttProviders">
+                       <NcCheckboxRadioSwitch :key="provider.class"
+                               :checked.sync="settings['ai.stt_provider']"
+                               :value="provider.class"
+                               name="stt_provider"
+                               type="radio">{{ provider.name }}</NcCheckboxRadioSwitch>
+               </template>
+               <template v-if="sttProviders.length === 0">
+               <NcCheckboxRadioSwitch disabled type="radio">{{ t('settings', 'No apps are currently installed that provide Speech-To-Text functionality') }}</NcCheckboxRadioSwitch>
+               </template>
+               <h3>{{ t('settings', 'Text processing') }}</h3>
+               <template v-for="(type, provider) in settings['ai.textprocessing_provider_preferences']">
+                       <h4>{{ type }}</h4>
+                       <!--<p>{{ getTaskType(type).description }}</p>
+                       <NcSelect v-model="settings['ai.textprocessing_provider_preferences'][type]" :options="textProcessingProviders.filter(provider => provider.taskType === type)" />-->
+               </template>
+               <template v-if="Object.keys(settings['ai.textprocessing_provider_preferences']).length === 0 || !Array.isArray(this.textProcessingTaskTypes)">
+                       <p>{{ t('settings', 'No apps are currently installed that provide Text processing functionality') }}</p>
+               </template>
+       </NcSettingsSection>
+</template>
+
+<script>
+import axios from '@nextcloud/axios'
+import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
+import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js'
+import { loadState } from '@nextcloud/initial-state'
+
+import { generateUrl } from '@nextcloud/router'
+
+export default {
+       name: 'AdminAI',
+       components: {
+               NcCheckboxRadioSwitch,
+               NcSettingsSection,
+       },
+       data() {
+               return {
+                       loading: false,
+                       dirty: false,
+                       groups: [],
+                       loadingGroups: false,
+                       sttProviders: loadState('settings', 'ai-stt-providers'),
+                       translationProviders: loadState('settings', 'ai-translation-providers'),
+                       textProcessingProviders: loadState('settings', 'ai-text-processing-providers'),
+                       textProcessingTaskTypes: loadState('settings', 'ai-text-processing-task-types'),
+                       settings: loadState('settings', 'ai-settings'),
+               }
+       },
+       methods: {
+               saveChanges() {
+                       this.loading = true
+
+                       const data = {
+                               enforced: this.enforced,
+                               enforcedGroups: this.enforcedGroups,
+                               excludedGroups: this.excludedGroups,
+                       }
+                       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 })
+               },
+               getTaskType(type) {
+                 if (!Array.isArray(this.textProcessingTaskTypes)) {
+                               return null
+                       }
+                       return this.textProcessingTaskTypes.find(taskType => taskType === type)
+               }
+       },
+}
+</script>
diff --git a/apps/settings/templates/settings/admin/ai.php b/apps/settings/templates/settings/admin/ai.php
new file mode 100644 (file)
index 0000000..fac7ad8
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+/**
+ * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+script('settings', [
+       'vue-settings-admin-ai',
+]);
+?>
+
+<div id="ai-settings">
+</div>
index eff00ec0fa1990db65f5d80f0780cddb34276efd..96973cfca08ef21bb45d04d6791019476e930bca 100644 (file)
@@ -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
index 90e25894d4f2eaab81d0becc0265b84cde3d4dda..50d012eca6862c0108fc0ea1a6f58d1e422fecee 100644 (file)
@@ -41,6 +41,12 @@ interface IManager {
         */
        public function hasProviders(): bool;
 
+       /**
+        * @return IProvider[]
+        * @since 27.1.0
+        */
+       public function getProviders(): array;
+
        /**
         * @return class-string<ITaskType>[]
         * @since 27.1.0
index 4450f19c42419260b299951152449883e999431e..5b342faea75d6d60236c4f342c8600ff7f1f39bb 100644 (file)
@@ -38,6 +38,12 @@ interface ITranslationManager {
         */
        public function hasProviders(): bool;
 
+       /**
+        * @return ITranslationProvider[]
+        * @since 27.1.0
+        */
+       public function getProviders(): array;
+
        /**
         * @since 26.0.0
         */
index bbdbb720245c91a99e8d27768077cda29eb35e38..31c12f09fb26765de3c95788e09e8a9af25d79d3 100644 (file)
@@ -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'),