Skip to content

Commit

Permalink
Merge pull request #122 from nextcloud/feat/image-generation-model
Browse files Browse the repository at this point in the history
admin-settings: add image model selection dropdown
  • Loading branch information
kyteinsky authored Sep 5, 2024
2 parents e1a52a5 + 6fa92ba commit e35c22b
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 116 deletions.
8 changes: 4 additions & 4 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Application extends App implements IBootstrap {
public const OPENAI_DEFAULT_REQUEST_TIMEOUT = 60 * 4;
public const USER_AGENT = 'Nextcloud OpenAI/LocalAI integration';

public const DEFAULT_MODEL_ID = 'Default';
public const DEFAULT_COMPLETION_MODEL_ID = 'gpt-3.5-turbo';
public const DEFAULT_TRANSCRIPTION_MODEL_ID = 'whisper-1';
public const DEFAULT_IMAGE_SIZE = '1024x1024';
Expand Down
4 changes: 1 addition & 3 deletions lib/Controller/OpenAiAPIController.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,11 @@ public function __construct(
public function getModels(): DataResponse {
try {
$response = $this->openAiAPIService->getModels($this->userId);
return new DataResponse($response);
} catch (Exception $e) {
$code = $e->getCode() === 0 ? Http::STATUS_BAD_REQUEST : intval($e->getCode());
return new DataResponse(['error' => $e->getMessage()], $code);
}

$response['default_completion_model_id'] = $this->openAiSettingsService->getUserDefaultCompletionModelId($this->userId);
return new DataResponse($response);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/OldProcessing/SpeechToText/STTProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function getName(): string {
public function transcribeFile(File $file): string {
try {
return $this->openAiAPIService->transcribeFile($this->userId, $file);
} catch(\Exception $e) {
} catch (\Exception $e) {
$this->logger->warning('OpenAI\'s Whisper transcription failed with: ' . $e->getMessage(), ['exception' => $e]);
throw new \RuntimeException('OpenAI\'s Whisper transcription failed with: ' . $e->getMessage());
}
Expand Down
2 changes: 1 addition & 1 deletion lib/OldProcessing/TextToImage/TextToImageProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public function generate(string $prompt, array $resources): void {
}
$endTime = time();
$this->openAiAPIService->updateExpImgProcessingTime($endTime - $startTime);
} catch(\Exception $e) {
} catch (\Exception $e) {
$this->logger->warning('OpenAI/LocalAI\'s text to image generation failed with: ' . $e->getMessage(), ['exception' => $e]);
throw new \RuntimeException('OpenAI/LocalAI\'s text to image generation failed with: ' . $e->getMessage());
}
Expand Down
30 changes: 13 additions & 17 deletions lib/Service/OpenAiAPIService.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public function isQuotaExceeded(?string $userId, int $type): bool {

try {
$quotaUsage = $this->quotaUsageMapper->getQuotaUnitsOfUserInTimePeriod($userId, $type, $quotaPeriod);
} catch (DoesNotExistException | MultipleObjectsReturnedException | DBException | RuntimeException $e) {
} catch (DoesNotExistException|MultipleObjectsReturnedException|DBException|RuntimeException $e) {
$this->logger->warning('Could not retrieve quota usage for user: ' . $userId . ' and quota type: ' . $type . '. Error: ' . $e->getMessage());
throw new Exception('Could not retrieve quota usage.', Http::STATUS_INTERNAL_SERVER_ERROR);
}
Expand Down Expand Up @@ -196,7 +196,7 @@ public function getUserQuotaInfo(string $userId): array {
$quotaInfo[$quotaType]['type'] = $this->translatedQuotaType($quotaType);
try {
$quotaInfo[$quotaType]['used'] = $this->quotaUsageMapper->getQuotaUnitsOfUserInTimePeriod($userId, $quotaType, $quotaPeriod);
} catch (DoesNotExistException | MultipleObjectsReturnedException | DBException | RuntimeException $e) {
} catch (DoesNotExistException|MultipleObjectsReturnedException|DBException|RuntimeException $e) {
$this->logger->warning('Could not retrieve quota usage for user: ' . $userId . ' and quota type: ' . $quotaType . '. Error: ' . $e->getMessage(), ['app' => Application::APP_ID]);
throw new Exception($this->l10n->t('Unknown error while retrieving quota usage.'), Http::STATUS_INTERNAL_SERVER_ERROR);
}
Expand All @@ -223,7 +223,7 @@ public function getAdminQuotaInfo(): array {
$quotaInfo[$quotaType]['type'] = $this->translatedQuotaType($quotaType);
try {
$quotaInfo[$quotaType]['used'] = $this->quotaUsageMapper->getQuotaUnitsInTimePeriod($quotaType, $quotaPeriod);
} catch (DoesNotExistException | MultipleObjectsReturnedException | DBException | RuntimeException $e) {
} catch (DoesNotExistException|MultipleObjectsReturnedException|DBException|RuntimeException $e) {
$this->logger->warning('Could not retrieve quota usage for quota type: ' . $quotaType . '. Error: ' . $e->getMessage(), ['app' => Application::APP_ID]);
// We can pass detailed error info to the UI here since the user is an admin in any case:
throw new Exception('Could not retrieve quota usage: ' . $e->getMessage(), Http::STATUS_INTERNAL_SERVER_ERROR);
Expand Down Expand Up @@ -263,7 +263,7 @@ public function createCompletion(
}

$params = [
'model' => $model,
'model' => $model === Application::DEFAULT_MODEL_ID ? Application::DEFAULT_COMPLETION_MODEL_ID : $model,
'prompt' => $prompt,
'max_tokens' => $maxTokens,
'n' => $n,
Expand Down Expand Up @@ -360,7 +360,7 @@ public function createChatCompletion(
$messages[] = ['role' => 'user', 'content' => $userPrompt];

$params = [
'model' => $model,
'model' => $model === Application::DEFAULT_MODEL_ID ? Application::DEFAULT_COMPLETION_MODEL_ID : $model,
'messages' => $messages,
'max_tokens' => $maxTokens,
'n' => $n,
Expand Down Expand Up @@ -454,7 +454,7 @@ public function transcribeFile(
): string {
try {
$transcriptionResponse = $this->transcribe($userId, $file->getContent(), $translate, $model);
} catch (NotPermittedException | LockedException | GenericFileException $e) {
} catch (NotPermittedException|LockedException|GenericFileException $e) {
$this->logger->warning('Could not read audio file: ' . $file->getPath() . '. Error: ' . $e->getMessage(), ['app' => Application::APP_ID]);
throw new Exception($this->l10n->t('Could not read audio file.'), Http::STATUS_INTERNAL_SERVER_ERROR);
}
Expand Down Expand Up @@ -522,19 +522,15 @@ public function requestImageCreation(?string $userId, string $prompt, int $n = 1
throw new Exception($this->l10n->t('Image generation quota exceeded'), Http::STATUS_TOO_MANY_REQUESTS);
}

$model = $this->config->getAppValue(Application::APP_ID, 'default_image_model_id') ?: Application::DEFAULT_MODEL_ID;
$params = [
'prompt' => $prompt,
'size' => $size,
'n' => $n,
'response_format' => 'url',
];

if ($this->isUsingOpenAi()) {
$params['model'] = 'dall-e-2';
$params['n'] = $n;
$params['response_format'] = 'url';

if ($userId !== null) {
$params['user'] = $userId;
}
if ($model !== Application::DEFAULT_MODEL_ID) {
$params['model'] = $model;
}

$apiResponse = $this->request($userId, 'images/generations', $params, 'POST');
Expand Down Expand Up @@ -731,7 +727,7 @@ public function request(?string $userId, string $endPoint, array $params = [], s
} else {
return json_decode($body, true) ?: [];
}
} catch (ClientException | ServerException $e) {
} catch (ClientException|ServerException $e) {
$responseBody = $e->getResponse()->getBody();
$parsedResponseBody = json_decode($responseBody, true);
if ($e->getResponse()->getStatusCode() === 404) {
Expand All @@ -744,7 +740,7 @@ public function request(?string $userId, string $endPoint, array $params = [], s
} else {
throw new Exception($this->l10n->t('API request error: ') . $e->getMessage(), intval($e->getCode()));
}
} catch (Exception | Throwable $e) {
} catch (Exception|Throwable $e) {
$this->logger->warning('API request error : ' . $e->getMessage(), ['exception' => $e]);
throw new Exception($this->l10n->t('Unknown API request error.'), Http::STATUS_INTERNAL_SERVER_ERROR);
}
Expand Down
34 changes: 16 additions & 18 deletions lib/Service/OpenAiSettingsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class OpenAiSettingsService {
'service_name' => 'string',
'api_key' => 'string',
'default_completion_model_id' => 'string',
'default_image_model_id' => 'string',
'max_tokens' => 'integer',
'llm_extra_params' => 'string',
'quota_period' => 'integer',
Expand All @@ -50,7 +51,6 @@ class OpenAiSettingsService {

private const USER_CONFIG_TYPES = [
'api_key' => 'string',
'default_completion_model_id' => 'string',
'basic_user' => 'string',
'basic_password' => 'string',
];
Expand Down Expand Up @@ -89,6 +89,13 @@ public function getAdminDefaultCompletionModelId(): string {
return $this->config->getAppValue(Application::APP_ID, 'default_completion_model_id', Application::DEFAULT_COMPLETION_MODEL_ID) ?: Application::DEFAULT_COMPLETION_MODEL_ID;
}

/**
* @return string
*/
public function getAdminDefaultImageModelId(): string {
return $this->config->getAppValue(Application::APP_ID, 'default_image_model_id') ?: Application::DEFAULT_MODEL_ID;
}

/**
* @return string
*/
Expand Down Expand Up @@ -212,6 +219,7 @@ public function getAdminConfig(): array {
'service_name' => $this->getServiceName(),
'api_key' => $this->getAdminApiKey(),
'default_completion_model_id' => $this->getAdminDefaultCompletionModelId(),
'default_image_model_id' => $this->getAdminDefaultImageModelId(),
'max_tokens' => $this->getMaxTokens(),
'llm_extra_params' => $this->getLlmExtraParams(),
// Updated to get max tokens
Expand Down Expand Up @@ -274,16 +282,6 @@ public function getSttProviderEnabled(): bool {
return $this->config->getAppValue(Application::APP_ID, 'stt_provider_enabled', '1') === '1';
}

/**
* @param string $userId
* @return string
*/
public function getUserDefaultCompletionModelId(string $userId): string {
// Fall back on admin model setting if necessary:
$adminModel = $this->getAdminDefaultCompletionModelId();
return $this->config->getUserValue($userId, Application::APP_ID, 'default_completion_model_id', $adminModel) ?: $adminModel;
}

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

Expand Down Expand Up @@ -339,12 +337,12 @@ public function setAdminDefaultCompletionModelId(string $defaultCompletionModelI
}

/**
* @param string $userId
* @param string $defaultCompletionModelId
* @param string $defaultImageModelId
* @return void
*/
public function setUserDefaultCompletionModelId(string $userId, string $defaultCompletionModelId): void {
public function setAdminDefaultImageModelId(string $defaultImageModelId): void {
// No need to validate. As long as it's a string, we're happy campers
$this->config->setUserValue($userId, Application::APP_ID, 'default_completion_model_id', $defaultCompletionModelId);
$this->config->setAppValue(Application::APP_ID, 'default_image_model_id', $defaultImageModelId);
}

/**
Expand Down Expand Up @@ -489,6 +487,9 @@ public function setAdminConfig(array $adminConfig): void {
if (isset($adminConfig['default_completion_model_id'])) {
$this->setAdminDefaultCompletionModelId($adminConfig['default_completion_model_id']);
}
if (isset($adminConfig['default_image_model_id'])) {
$this->setAdminDefaultImageModelId($adminConfig['default_image_model_id']);
}
if (isset($adminConfig['max_tokens'])) {
$this->setMaxTokens($adminConfig['max_tokens']);
}
Expand Down Expand Up @@ -544,9 +545,6 @@ public function setUserConfig(string $userId, array $userConfig): void {
if (isset($userConfig['api_key'])) {
$this->setUserApiKey($userId, $userConfig['api_key']);
}
if (isset($userConfig['default_completion_model_id'])) {
$this->setUserDefaultCompletionModelId($userId, $userConfig['default_completion_model_id']);
}
if (isset($userConfig['basic_user'])) {
$this->setUserBasicUser($userId, $userConfig['basic_user']);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/TaskProcessing/AudioToTextProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public function process(?string $userId, array $input, callable $reportProgress)
try {
$transcription = $this->openAiAPIService->transcribeFile($userId, $inputFile);
return ['output' => $transcription];
} catch(Exception $e) {
} catch (Exception $e) {
$this->logger->warning('OpenAI\'s Whisper transcription failed with: ' . $e->getMessage(), ['exception' => $e]);
throw new RuntimeException('OpenAI\'s Whisper transcription failed with: ' . $e->getMessage());
}
Expand Down
2 changes: 1 addition & 1 deletion lib/TaskProcessing/TextToImageProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public function process(?string $userId, array $input, callable $reportProgress)
$this->openAiAPIService->updateExpImgProcessingTime($endTime - $startTime);
/** @var array<string, list<numeric|string>|numeric|string> $output */
return $output;
} catch(\Exception $e) {
} catch (\Exception $e) {
$this->logger->warning('OpenAI/LocalAI\'s text to image generation failed with: ' . $e->getMessage(), ['exception' => $e]);
throw new RuntimeException('OpenAI/LocalAI\'s text to image generation failed with: ' . $e->getMessage());
}
Expand Down
Loading

0 comments on commit e35c22b

Please sign in to comment.