<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>
<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>
--- /dev/null
+<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
--- /dev/null
+<?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();
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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_.*/'],
+ ];
+ }
+}
--- /dev/null
+<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>
--- /dev/null
+<?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>
*/
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
*/
public function hasProviders(): bool;
+ /**
+ * @return IProvider[]
+ * @since 27.1.0
+ */
+ public function getProviders(): array;
+
/**
* @return class-string<ITaskType>[]
* @since 27.1.0
*/
public function hasProviders(): bool;
+ /**
+ * @return ITranslationProvider[]
+ * @since 27.1.0
+ */
+ public function getProviders(): array;
+
/**
* @since 26.0.0
*/
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'),