Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
['name' => 'config#setSensitiveUserConfig', 'url' => '/config/sensitive', 'verb' => 'PUT'],
['name' => 'config#setAdminConfig', 'url' => '/admin-config', 'verb' => 'PUT'],
['name' => 'config#setSensitiveAdminConfig', 'url' => '/admin-config/sensitive', 'verb' => 'PUT'],
['name' => 'config#autoDetectFeatures', 'url' => '/admin-config/auto-detect-features', 'verb' => 'POST'],

['name' => 'openAiAPI#getModels', 'url' => '/models', 'verb' => 'GET'],
['name' => 'openAiAPI#getUserQuotaInfo', 'url' => '/quota-info', 'verb' => 'GET'],
Expand Down
13 changes: 8 additions & 5 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ public function register(IRegistrationContext $context): void {
$context->registerTaskProcessingProvider(AudioToTextProvider::class);
}

$serviceUrl = $this->appConfig->getValueString(Application::APP_ID, 'url');
$isUsingOpenAI = $serviceUrl === '' || $serviceUrl === Application::OPENAI_API_BASE_URL;

if ($this->appConfig->getValueString(Application::APP_ID, 'llm_provider_enabled', '1') === '1') {
$context->registerTaskProcessingProvider(TextToTextProvider::class);
$context->registerTaskProcessingProvider(TextToTextChatProvider::class);
Expand All @@ -119,10 +122,12 @@ public function register(IRegistrationContext $context): void {
if (class_exists('OCP\\TaskProcessing\\TaskTypes\\TextToTextProofread')) {
$context->registerTaskProcessingProvider(\OCA\OpenAi\TaskProcessing\ProofreadProvider::class);
}
if (!class_exists('OCP\\TaskProcessing\\TaskTypes\\AnalyzeImages')) {
$context->registerTaskProcessingTaskType(\OCA\OpenAi\TaskProcessing\AnalyzeImagesTaskType::class);
if ($isUsingOpenAI || $this->appConfig->getValueString(Application::APP_ID, 'analyze_image_provider_enabled') === '1') {
if (!class_exists('OCP\\TaskProcessing\\TaskTypes\\AnalyzeImages')) {
$context->registerTaskProcessingTaskType(\OCA\OpenAi\TaskProcessing\AnalyzeImagesTaskType::class);
}
$context->registerTaskProcessingProvider(\OCA\OpenAi\TaskProcessing\AnalyzeImagesProvider::class);
}
$context->registerTaskProcessingProvider(\OCA\OpenAi\TaskProcessing\AnalyzeImagesProvider::class);
}
if (!class_exists('OCP\\TaskProcessing\\TaskTypes\\TextToSpeech')) {
$context->registerTaskProcessingTaskType(\OCA\OpenAi\TaskProcessing\TextToSpeechTaskType::class);
Expand All @@ -133,8 +138,6 @@ public function register(IRegistrationContext $context): void {
}

// only register audio chat stuff if we're using OpenAI or stt+llm+tts are enabled
$serviceUrl = $this->appConfig->getValueString(Application::APP_ID, 'url');
$isUsingOpenAI = $serviceUrl === '' || $serviceUrl === Application::OPENAI_API_BASE_URL;
if (
$isUsingOpenAI
|| (
Expand Down
15 changes: 15 additions & 0 deletions lib/Controller/ConfigController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace OCA\OpenAi\Controller;

use Exception;
use OCA\OpenAi\Service\OpenAiAPIService;
use OCA\OpenAi\Service\OpenAiSettingsService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
Expand All @@ -22,6 +23,7 @@ public function __construct(
string $appName,
IRequest $request,
private OpenAiSettingsService $openAiSettingsService,
private OpenAiAPIService $openAiAPIService,
private ?string $userId,
) {
parent::__construct($appName, $request);
Expand Down Expand Up @@ -98,4 +100,17 @@ public function setSensitiveAdminConfig(array $values): DataResponse {

return new DataResponse('');
}

/**
* Set admin config values
* @return DataResponse
*/
public function autoDetectFeatures(): DataResponse {
try {
$config = $this->openAiAPIService->autoDetectFeatures();
return new DataResponse($config);
} catch (Exception $e) {
return new DataResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
}
}
}
95 changes: 90 additions & 5 deletions lib/Service/OpenAiAPIService.php
Original file line number Diff line number Diff line change
Expand Up @@ -891,10 +891,11 @@ public function updateExpImgProcessingTime(int $runtime): void {
* @param array $params Query parameters (key/val pairs)
* @param string $method HTTP query method
* @param string|null $contentType
* @param bool $logErrors if set to false error logs will be suppressed
* @return array decoded request result or error
* @throws Exception
*/
public function request(?string $userId, string $endPoint, array $params = [], string $method = 'GET', ?string $contentType = null): array {
public function request(?string $userId, string $endPoint, array $params = [], string $method = 'GET', ?string $contentType = null, bool $logErrors = true): array {
try {
$serviceUrl = $this->openAiSettingsService->getServiceUrl();
if ($serviceUrl === '') {
Expand Down Expand Up @@ -1000,10 +1001,12 @@ public function request(?string $userId, string $endPoint, array $params = [], s
} catch (ClientException|ServerException $e) {
$responseBody = $e->getResponse()->getBody();
$parsedResponseBody = json_decode($responseBody, true);
if ($e->getResponse()->getStatusCode() === 404) {
$this->logger->debug('API request error : ' . $e->getMessage(), ['response_body' => $responseBody, 'exception' => $e]);
} else {
$this->logger->warning('API request error : ' . $e->getMessage(), ['response_body' => $responseBody, 'exception' => $e]);
if ($logErrors) {
if ($e->getResponse()->getStatusCode() === 404) {
$this->logger->debug('API request error : ' . $e->getMessage(), ['response_body' => $responseBody, 'exception' => $e]);
} else {
$this->logger->warning('API request error : ' . $e->getMessage(), ['response_body' => $responseBody, 'exception' => $e]);
}
}
throw new Exception(
$this->l10n->t('API request error: ') . (
Expand All @@ -1019,4 +1022,86 @@ public function request(?string $userId, string $endPoint, array $params = [], s
);
}
}

/**
* Check if the T2I provider is available
*
* @return bool whether the T2I provider is available
*/
public function isT2IAvailable(): bool {
if ($this->isUsingOpenAi()) {
return true;
}
try {
$params = [
'prompt' => 'a',
'model' => 'invalid-model',
];
$this->request(null, 'images/generations', $params, 'POST', logErrors: false);
} catch (Exception $e) {
return $e->getCode() !== Http::STATUS_NOT_FOUND && $e->getCode() !== Http::STATUS_UNAUTHORIZED;
}
return true;
}

/**
* Check if the STT provider is available
*
* @return bool whether the STT provider is available
*/
public function isSTTAvailable(): bool {
if ($this->isUsingOpenAi()) {
return true;
}
try {
$params = [
'model' => 'invalid-model',
'file' => 'a',
];
$this->request(null, 'audio/translations', $params, 'POST', 'multipart/form-data', logErrors: false);
} catch (Exception $e) {
return $e->getCode() !== Http::STATUS_NOT_FOUND && $e->getCode() !== Http::STATUS_UNAUTHORIZED;
}
return true;
}

/**
* Check if the TTS provider is available
*
* @return bool whether the TTS provider is available
*/
public function isTTSAvailable(): bool {
if ($this->isUsingOpenAi()) {
return true;
}
try {
$params = [
'input' => 'a',
'voice' => 'invalid-voice',
'model' => 'invalid-model',
'response_format' => 'mp3',
];

$this->request(null, 'audio/speech', $params, 'POST', logErrors: false);
} catch (Exception $e) {
return $e->getCode() !== Http::STATUS_NOT_FOUND && $e->getCode() !== Http::STATUS_UNAUTHORIZED;
}
return true;
}

/**
* Updates the admin config with the availability of the providers
*
* @return array the updated config
* @throws Exception
*/
public function autoDetectFeatures(): array {
$config = [];
$config['t2i_provider_enabled'] = $this->isT2IAvailable();
$config['stt_provider_enabled'] = $this->isSTTAvailable();
$config['tts_provider_enabled'] = $this->isTTSAvailable();
$this->openAiSettingsService->setAdminConfig($config);
$config['analyze_image_provider_enabled'] = $this->openAiSettingsService->getAnalyzeImageProviderEnabled();
return $config;
}
}
26 changes: 26 additions & 0 deletions lib/Service/OpenAiSettingsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class OpenAiSettingsService {
't2i_provider_enabled' => 'boolean',
'stt_provider_enabled' => 'boolean',
'tts_provider_enabled' => 'boolean',
'analyze_image_provider_enabled' => 'boolean',
'chat_endpoint_enabled' => 'boolean',
'basic_user' => 'string',
'basic_password' => 'string',
Expand Down Expand Up @@ -321,6 +322,7 @@ public function getAdminConfig(): array {
't2i_provider_enabled' => $this->getT2iProviderEnabled(),
'stt_provider_enabled' => $this->getSttProviderEnabled(),
'tts_provider_enabled' => $this->getTtsProviderEnabled(),
'analyze_image_provider_enabled' => $this->getAnalyzeImageProviderEnabled(),
'chat_endpoint_enabled' => $this->getChatEndpointEnabled(),
'basic_user' => $this->getAdminBasicUser(),
'basic_password' => $this->getAdminBasicPassword(),
Expand Down Expand Up @@ -400,6 +402,19 @@ public function getTtsProviderEnabled(): bool {
return $this->appConfig->getValueString(Application::APP_ID, 'tts_provider_enabled', '1') === '1';
}

/**
* @return bool
*/
public function getAnalyzeImageProviderEnabled(): bool {
$config = $this->appConfig->getValueString(Application::APP_ID, 'analyze_image_provider_enabled');
if ($config === '') {
$serviceUrl = $this->getServiceUrl();
$isUsingOpenAI = $serviceUrl === '' || $serviceUrl === Application::OPENAI_API_BASE_URL;
return $isUsingOpenAI;
}
return $config === '1';
}

////////////////////////////////////////////
//////////// Setters for settings //////////

Expand Down Expand Up @@ -731,6 +746,9 @@ public function setAdminConfig(array $adminConfig): void {
if (isset($adminConfig['tts_provider_enabled'])) {
$this->setTtsProviderEnabled($adminConfig['tts_provider_enabled']);
}
if (isset($adminConfig['analyze_image_provider_enabled'])) {
$this->setAnalyzeImageProviderEnabled($adminConfig['analyze_image_provider_enabled']);
}
if (isset($adminConfig['default_tts_voice'])) {
$this->setAdminDefaultTtsVoice($adminConfig['default_tts_voice']);
}
Expand Down Expand Up @@ -833,6 +851,14 @@ public function setTtsProviderEnabled(bool $enabled): void {
$this->appConfig->setValueString(Application::APP_ID, 'tts_provider_enabled', $enabled ? '1' : '0');
}

/**
* @param bool $enabled
* @return void
*/
public function setAnalyzeImageProviderEnabled(bool $enabled): void {
$this->appConfig->setValueString(Application::APP_ID, 'analyze_image_provider_enabled', $enabled ? '1' : '0');
}

/**
* @param bool $enabled
*/
Expand Down
23 changes: 23 additions & 0 deletions src/components/AdminSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,11 @@
@update:model-value="onCheckboxChanged($event, 'tts_provider_enabled', false)">
{{ t('integration_openai', 'Text-to-speech provider') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch
:model-value="state.analyze_image_provider_enabled"
@update:model-value="onCheckboxChanged($event, 'analyze_image_provider_enabled', false)">
{{ t('integration_openai', 'Analyze image provider') }}
</NcCheckboxRadioSwitch>
</div>
</div>
</div>
Expand Down Expand Up @@ -700,6 +705,23 @@ export default {
label: model.id + (model.owned_by ? ' (' + model.owned_by + ')' : ''),
}
},
autoDetectFeatures() {
return axios.post(generateUrl('/apps/integration_openai/admin-config/auto-detect-features')).then((response) => {
const data = response.data ?? {}
console.debug(data)
this.state = {
...this.state,
...data,
}
}).catch((error) => {
showError(
t('integration_openai', 'Failed to auto update config')
+ ': ' + this.reduceStars(error.response?.data?.error),
{ timeout: 10000 },
)
console.error(error)
})
},

getModels(shouldSave = true) {
this.models = null
Expand Down Expand Up @@ -837,6 +859,7 @@ export default {
if (getModels) {
this.getModels()
}
this.autoDetectFeatures()
}, 2000),
onInput: debounce(async function() {
// sanitize quotas
Expand Down
Loading