Skip to content

Commit

Permalink
Merge pull request #46509 from nextcloud/feat/settings/taskprocessing
Browse files Browse the repository at this point in the history
feat(settings/admin/ai): Add Task Processing API settings
  • Loading branch information
marcelklehr authored Jul 17, 2024
2 parents 19ba872 + 183726a commit 7cb67c6
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 14 deletions.
2 changes: 1 addition & 1 deletion apps/settings/lib/Controller/AISettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function __construct(
* @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;
Expand Down
29 changes: 28 additions & 1 deletion apps/settings/lib/Settings/Admin/ArtificialIntelligence.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public function __construct(
private IManager $textProcessingManager,
private ContainerInterface $container,
private \OCP\TextToImage\IManager $text2imageManager,
private \OCP\TaskProcessing\IManager $taskProcessingManager,
) {
}

Expand Down Expand Up @@ -98,24 +99,50 @@ public function getForm() {
];
}

$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;
$json = $this->config->getAppValue('core', $key, '');
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);
Expand Down
52 changes: 47 additions & 5 deletions apps/settings/src/components/AdminAI.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,35 @@
- 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>&nbsp;</p>
<NcSelect v-model="settings['ai.taskprocessing_provider_preferences'][type.id]"
class="provider-select"
: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>&nbsp;</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">
Expand Down Expand Up @@ -62,10 +90,11 @@
: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>&nbsp;</p>
<NcSelect v-model="settings['ai.textprocessing_provider_preferences'][type]"
class="provider-select"
:clearable="false"
:options="textProcessingProviders.filter(p => p.taskType === type).map(p => p.class)"
@input="saveChanges">
Expand Down Expand Up @@ -127,6 +156,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'),
}
},
Expand All @@ -138,11 +169,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) {
Expand Down Expand Up @@ -171,7 +205,7 @@ export default {
}
this.loading = false
},
getTaskType(type) {
getTextProcessingTaskType(type) {
if (!Array.isArray(this.textProcessingTaskTypes)) {
return null
}
Expand Down Expand Up @@ -203,4 +237,12 @@ export default {
.drag-vertical-icon {
float: left;
}

.ai-settings h3 {
font-size: 16px; /* to offset against the 20px section heading */
}

.provider-select {
min-width: 350px !important;
}
</style>
4 changes: 2 additions & 2 deletions dist/settings-vue-settings-admin-ai.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/settings-vue-settings-admin-ai.js.map

Large diffs are not rendered by default.

22 changes: 18 additions & 4 deletions lib/private/TaskProcessing/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -497,11 +499,23 @@ public function getProviders(): array {
}

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);
$providers = $this->getProviders();
if (isset($preferences[$taskType])) {
$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');
}
Expand Down
1 change: 1 addition & 0 deletions tests/lib/TaskProcessing/TaskProcessingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ protected function setUp(): void {
$this->shareManager = $this->createMock(\OCP\Share\IManager::class);

$this->manager = new Manager(
\OC::$server->get(IConfig::class),
$this->coordinator,
$this->serverContainer,
\OC::$server->get(LoggerInterface::class),
Expand Down

0 comments on commit 7cb67c6

Please sign in to comment.