Signed-off-by: Marcel Klehr <mklehr@gmx.net>tags/v28.0.0beta1
@@ -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> |
@@ -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> |
@@ -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(); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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_.*/'], | |||
]; | |||
} | |||
} |
@@ -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> |
@@ -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> |
@@ -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 |
@@ -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 |
@@ -38,6 +38,12 @@ interface ITranslationManager { | |||
*/ | |||
public function hasProviders(): bool; | |||
/** | |||
* @return ITranslationProvider[] | |||
* @since 27.1.0 | |||
*/ | |||
public function getProviders(): array; | |||
/** | |||
* @since 26.0.0 | |||
*/ |
@@ -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'), |