diff options
-rw-r--r-- | apps/settings/lib/Controller/AISettingsController.php | 2 | ||||
-rw-r--r-- | apps/settings/lib/Settings/Admin/ArtificialIntelligence.php | 29 | ||||
-rw-r--r-- | apps/settings/src/components/AdminAI.vue | 46 | ||||
-rw-r--r-- | lib/private/TaskProcessing/Manager.php | 22 |
4 files changed, 88 insertions, 11 deletions
diff --git a/apps/settings/lib/Controller/AISettingsController.php b/apps/settings/lib/Controller/AISettingsController.php index 832a7d6d96b..106b2378ba1 100644 --- a/apps/settings/lib/Controller/AISettingsController.php +++ b/apps/settings/lib/Controller/AISettingsController.php @@ -37,7 +37,7 @@ class AISettingsController extends Controller { * @return DataResponse */ public function update($settings) { - $keys = ['ai.stt_provider', 'ai.textprocessing_provider_preferences', 'ai.translation_provider_preferences', 'ai.text2image_provider']; + $keys = ['ai.stt_provider', 'ai.textprocessing_provider_preferences', 'ai.taskprocessing_provider_preferences', 'ai.translation_provider_preferences', 'ai.text2image_provider']; foreach ($keys as $key) { if (!isset($settings[$key])) { continue; diff --git a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php index ac4badc2434..9a291243949 100644 --- a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php +++ b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php @@ -35,6 +35,7 @@ class ArtificialIntelligence implements IDelegatedSettings { private IManager $textProcessingManager, private ContainerInterface $container, private \OCP\TextToImage\IManager $text2imageManager, + private \OCP\TaskProcessing\IManager $taskProcessingManager, ) { } @@ -98,17 +99,42 @@ class ArtificialIntelligence implements IDelegatedSettings { ]; } + $taskProcessingProviders = []; + /** @var array<class-string<ITaskType>, string|class-string<IProvider>> $taskProcessingSettings */ + $taskProcessingSettings = []; + foreach ($this->taskProcessingManager->getProviders() as $provider) { + $taskProcessingProviders[] = [ + 'id' => $provider->getId(), + 'name' => $provider->getName(), + 'taskType' => $provider->getTaskTypeId(), + ]; + if (!isset($taskProcessingSettings[$provider->getTaskTypeId()])) { + $taskProcessingSettings[$provider->getTaskTypeId()] = $provider->getId(); + } + } + $taskProcessingTaskTypes = []; + foreach ($this->taskProcessingManager->getAvailableTaskTypes() as $taskTypeId => $taskTypeDefinition) { + $taskProcessingTaskTypes[] = [ + 'id' => $taskTypeId, + 'name' => $taskTypeDefinition['name'], + 'description' => $taskTypeDefinition['description'], + ]; + } + $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); $this->initialState->provideInitialState('ai-text2image-providers', $text2imageProviders); + $this->initialState->provideInitialState('ai-task-processing-providers', $taskProcessingProviders); + $this->initialState->provideInitialState('ai-task-processing-task-types', $taskProcessingTaskTypes); $settings = [ 'ai.stt_provider' => count($sttProviders) > 0 ? $sttProviders[0]['class'] : null, - 'ai.textprocessing_provider_preferences' => $textProcessingSettings, 'ai.translation_provider_preferences' => $translationPreferences, + 'ai.textprocessing_provider_preferences' => $textProcessingSettings, 'ai.text2image_provider' => count($text2imageProviders) > 0 ? $text2imageProviders[0]['id'] : null, + 'ai.taskprocessing_provider_preferences' => $taskProcessingSettings, ]; foreach ($settings as $key => $defaultValue) { $value = $defaultValue; @@ -116,6 +142,7 @@ class ArtificialIntelligence implements IDelegatedSettings { if ($json !== '') { $value = json_decode($json, true); switch($key) { + case 'ai.taskprocessing_provider_preferences': case 'ai.textprocessing_provider_preferences': // fill $value with $defaultValue values $value = array_merge($defaultValue, $value); diff --git a/apps/settings/src/components/AdminAI.vue b/apps/settings/src/components/AdminAI.vue index 70f5a03ec70..0b323b56b5e 100644 --- a/apps/settings/src/components/AdminAI.vue +++ b/apps/settings/src/components/AdminAI.vue @@ -3,7 +3,34 @@ - SPDX-License-Identifier: AGPL-3.0-or-later --> <template> - <div> + <div class="ai-settings"> + <NcSettingsSection :name="t('settings', 'Unified task processing')" + :description="t('settings', 'AI tasks can be implemented by different apps. Here you can set which app should be used for which task.')"> + <template v-for="type in taskProcessingTaskTypes"> + <div :key="type"> + <h3>{{ t('settings', 'Task:') }} {{ type.name }}</h3> + <p>{{ type.description }}</p> + <p> </p> + <NcSelect v-model="settings['ai.taskprocessing_provider_preferences'][type.id]" + :clearable="false" + :options="taskProcessingProviders.filter(p => p.taskType === type.id).map(p => p.id)" + @input="saveChanges"> + <template #option="{label}"> + {{ taskProcessingProviders.find(p => p.id === label)?.name }} + </template> + <template #selected-option="{label}"> + {{ taskProcessingProviders.find(p => p.id === label)?.name }} + </template> + </NcSelect> + <p> </p> + </div> + </template> + <template v-if="!hasTaskProcessing"> + <NcNoteCard type="info"> + {{ t('settings', 'None of your currently installed apps provide Task processing functionality') }} + </NcNoteCard> + </template> + </NcSettingsSection> <NcSettingsSection :name="t('settings', 'Machine translation')" :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.')"> <draggable v-model="settings['ai.translation_provider_preferences']" @change="saveChanges"> @@ -62,8 +89,8 @@ :description="t('settings', 'Text processing tasks can be implemented by different apps. Here you can set which app should be used for which task.')"> <template v-for="type in tpTaskTypes"> <div :key="type"> - <h3>{{ t('settings', 'Task:') }} {{ getTaskType(type).name }}</h3> - <p>{{ getTaskType(type).description }}</p> + <h3>{{ t('settings', 'Task:') }} {{ getTextProcessingTaskType(type).name }}</h3> + <p>{{ getTextProcessingTaskType(type).description }}</p> <p> </p> <NcSelect v-model="settings['ai.textprocessing_provider_preferences'][type]" :clearable="false" @@ -127,6 +154,8 @@ export default { textProcessingProviders: loadState('settings', 'ai-text-processing-providers'), textProcessingTaskTypes: loadState('settings', 'ai-text-processing-task-types'), text2imageProviders: loadState('settings', 'ai-text2image-providers'), + taskProcessingProviders: loadState('settings', 'ai-task-processing-providers'), + taskProcessingTaskTypes: loadState('settings', 'ai-task-processing-task-types'), settings: loadState('settings', 'ai-settings'), } }, @@ -138,11 +167,14 @@ export default { return Object.keys(this.settings['ai.textprocessing_provider_preferences']).length > 0 && Array.isArray(this.textProcessingTaskTypes) }, tpTaskTypes() { - return Object.keys(this.settings['ai.textprocessing_provider_preferences']).filter(type => !!this.getTaskType(type)) + return Object.keys(this.settings['ai.textprocessing_provider_preferences']).filter(type => !!this.getTextProcessingTaskType(type)) }, hasText2ImageProviders() { return this.text2imageProviders.length > 0 }, + hasTaskProcessing() { + return Object.keys(this.settings['ai.taskprocessing_provider_preferences']).length > 0 && Array.isArray(this.taskProcessingTaskTypes) + }, }, methods: { moveUp(i) { @@ -171,7 +203,7 @@ export default { } this.loading = false }, - getTaskType(type) { + getTextProcessingTaskType(type) { if (!Array.isArray(this.textProcessingTaskTypes)) { return null } @@ -203,4 +235,8 @@ export default { .drag-vertical-icon { float: left; } + +.ai-settings h3 { + font-size: 16px; /* to offset against the 20px section heading */ +} </style> diff --git a/lib/private/TaskProcessing/Manager.php b/lib/private/TaskProcessing/Manager.php index f0d1d4ba51a..834ca488033 100644 --- a/lib/private/TaskProcessing/Manager.php +++ b/lib/private/TaskProcessing/Manager.php @@ -24,6 +24,7 @@ use OCP\Files\IAppData; use OCP\Files\IRootFolder; use OCP\Files\NotPermittedException; use OCP\Files\SimpleFS\ISimpleFile; +use OCP\IConfig; use OCP\IL10N; use OCP\IServerContainer; use OCP\L10N\IFactory; @@ -66,6 +67,7 @@ class Manager implements IManager { private IAppData $appData; public function __construct( + private IConfig $config, private Coordinator $coordinator, private IServerContainer $serverContainer, private LoggerInterface $logger, @@ -497,11 +499,23 @@ class Manager implements IManager { } public function getPreferredProvider(string $taskType) { - $providers = $this->getProviders(); - foreach ($providers as $provider) { - if ($provider->getTaskTypeId() === $taskType) { - return $provider; + try { + $preferences = json_decode($this->config->getAppValue('core', 'ai.taskprocessing_provider_preferences', 'null'), associative: true, flags: JSON_THROW_ON_ERROR); + if (isset($preferences[$taskType])) { + $providers = $this->getProviders(); + $provider = current(array_values(array_filter($providers, fn ($provider) => $provider->getId() === $preferences[$taskType]))); + if ($provider !== false) { + return $provider; + } + } + // By default, use the first available provider + foreach ($providers as $provider) { + if ($provider->getTaskTypeId() === $taskType) { + return $provider; + } } + } catch (\JsonException $e) { + $this->logger->warning('Failed to parse provider preferences while getting preferred provider for task type ' . $taskType, ['exception' => $e]); } throw new \OCP\TaskProcessing\Exception\Exception('No matching provider found'); } |