Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace OCA\OpenAi\AppInfo;

use OCA\OpenAi\Capabilities;
use OCA\OpenAi\Notification\Notifier;
use OCA\OpenAi\OldProcessing\Translation\TranslationProvider as OldTranslationProvider;
use OCA\OpenAi\TaskProcessing\AudioToAudioChatProvider;
use OCA\OpenAi\TaskProcessing\AudioToTextProvider;
Expand Down Expand Up @@ -152,6 +153,7 @@ public function register(IRegistrationContext $context): void {
}

$context->registerCapability(Capabilities::class);
$context->registerNotifierService(Notifier::class);
}

public function boot(IBootContext $context): void {
Expand Down
104 changes: 104 additions & 0 deletions lib/Notification/Notifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\OpenAi\Notification;

use InvalidArgumentException;
use OCA\OpenAi\AppInfo\Application;
use OCP\IURLGenerator;
use OCP\L10N\IFactory;
use OCP\Notification\IAction;
use OCP\Notification\INotification;

use OCP\Notification\INotifier;

class Notifier implements INotifier {

public function __construct(
private IFactory $factory,
private IURLGenerator $url,
) {
}

/**
* Identifier of the notifier, only use [a-z0-9_]
*
* @return string
* @since 17.0.0
*/
public function getID(): string {
return Application::APP_ID;
}
/**
* Human readable name describing the notifier
*
* @return string
* @since 17.0.0
*/
public function getName(): string {
return $this->factory->get(Application::APP_ID)->t('integration_openai');
}

/**
* @param INotification $notification
* @param string $languageCode The code of the language that should be used to prepare the notification
* @return INotification
* @throws InvalidArgumentException When the notification was not prepared by a notifier
* @since 9.0.0
*/
public function prepare(INotification $notification, string $languageCode): INotification {
if ($notification->getApp() !== Application::APP_ID) {
// Not my app => throw
throw new InvalidArgumentException();
}
if ($notification->getSubject() !== 'quota_exceeded') {
// Not a valid subject => throw
throw new InvalidArgumentException();
}

$l = $this->factory->get(Application::APP_ID, $languageCode);

$params = $notification->getSubjectParameters();

$subject = $l->t('Quota exceeded');
$content = '';
switch ($params['type']) {
case Application::QUOTA_TYPE_TEXT:
$content = $l->t('Text generation quota exceeded');
break;
case Application::QUOTA_TYPE_IMAGE:
$content = $l->t('Image generation quota exceeded');
break;
case Application::QUOTA_TYPE_TRANSCRIPTION:
$content = $l->t('Audio transcription quota exceeded');
break;
case Application::QUOTA_TYPE_SPEECH:
$content = $l->t('Speech generation quota exceeded');
break;
}

$link = $this->url->getWebroot() . '/settings/user/ai';
$iconUrl = $this->url->getAbsoluteURL($this->url->imagePath(Application::APP_ID, 'app-dark.svg'));

$notification
->setParsedSubject($subject)
->setParsedMessage($content)
->setLink($link)
->setIcon($iconUrl);

$actionLabel = $params['actionLabel'] ?? $l->t('View quota');
$action = $notification->createAction();
$action->setLabel($actionLabel)
->setParsedLabel($actionLabel)
->setLink($notification->getLink(), IAction::TYPE_WEB)
->setPrimary(true);

$notification->addParsedAction($action);

return $notification;
}
}
16 changes: 14 additions & 2 deletions lib/Service/OpenAiAPIService.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace OCA\OpenAi\Service;

use DateTime;
use Exception;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
Expand All @@ -25,6 +26,7 @@
use OCP\ICacheFactory;
use OCP\IL10N;
use OCP\Lock\LockedException;
use OCP\Notification\IManager as INotificationManager;
use OCP\TaskProcessing\ShapeEnumValue;
use Psr\Log\LoggerInterface;
use RuntimeException;
Expand All @@ -46,6 +48,7 @@ public function __construct(
private ICacheFactory $cacheFactory,
private QuotaUsageMapper $quotaUsageMapper,
private OpenAiSettingsService $openAiSettingsService,
private INotificationManager $notificationManager,
IClientService $clientService,
) {
$this->client = $clientService->newClient();
Expand Down Expand Up @@ -252,8 +255,17 @@ public function isQuotaExceeded(?string $userId, int $type): bool {
$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);
}

return $quotaUsage >= $quota;
if ($quotaUsage >= $quota) {
$notification = $this->notificationManager->createNotification();
$notification->setApp(Application::APP_ID)
->setUser($userId)
->setDateTime(new DateTime())
->setObject('quota_exceeded', (string)$type)
->setSubject('quota_exceeded', ['type' => $type]);
$this->notificationManager->notify($notification);
return true;
}
return false;
}

/**
Expand Down
1 change: 1 addition & 0 deletions tests/unit/Providers/OpenAiProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ protected function setUp(): void {
\OC::$server->get(ICacheFactory::class),
\OC::$server->get(QuotaUsageMapper::class),
$this->openAiSettingsService,
$this->createMock(\OCP\Notification\IManager::class),
$clientService,
);

Expand Down
Loading