Skip to content

Commit 71f29df

Browse files
committed
feat: add notification when quota exceeded
Signed-off-by: Lukas Schaefer <[email protected]>
1 parent 5d46c3d commit 71f29df

File tree

4 files changed

+121
-2
lines changed

4 files changed

+121
-2
lines changed

lib/AppInfo/Application.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace OCA\OpenAi\AppInfo;
99

1010
use OCA\OpenAi\Capabilities;
11+
use OCA\OpenAi\Notification\Notifier;
1112
use OCA\OpenAi\OldProcessing\Translation\TranslationProvider as OldTranslationProvider;
1213
use OCA\OpenAi\TaskProcessing\AudioToAudioChatProvider;
1314
use OCA\OpenAi\TaskProcessing\AudioToTextProvider;
@@ -152,6 +153,7 @@ public function register(IRegistrationContext $context): void {
152153
}
153154

154155
$context->registerCapability(Capabilities::class);
156+
$context->registerNotifierService(Notifier::class);
155157
}
156158

157159
public function boot(IBootContext $context): void {

lib/Notification/Notifier.php

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
/**
4+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
namespace OCA\OpenAi\Notification;
9+
10+
use InvalidArgumentException;
11+
use OCA\OpenAi\AppInfo\Application;
12+
use OCP\IURLGenerator;
13+
use OCP\L10N\IFactory;
14+
use OCP\Notification\IAction;
15+
use OCP\Notification\INotification;
16+
17+
use OCP\Notification\INotifier;
18+
19+
class Notifier implements INotifier {
20+
21+
public function __construct(
22+
private IFactory $factory,
23+
private IURLGenerator $url,
24+
) {
25+
}
26+
27+
/**
28+
* Identifier of the notifier, only use [a-z0-9_]
29+
*
30+
* @return string
31+
* @since 17.0.0
32+
*/
33+
public function getID(): string {
34+
return Application::APP_ID;
35+
}
36+
/**
37+
* Human readable name describing the notifier
38+
*
39+
* @return string
40+
* @since 17.0.0
41+
*/
42+
public function getName(): string {
43+
return $this->factory->get(Application::APP_ID)->t('integration_openai');
44+
}
45+
46+
/**
47+
* @param INotification $notification
48+
* @param string $languageCode The code of the language that should be used to prepare the notification
49+
* @return INotification
50+
* @throws InvalidArgumentException When the notification was not prepared by a notifier
51+
* @since 9.0.0
52+
*/
53+
public function prepare(INotification $notification, string $languageCode): INotification {
54+
if ($notification->getApp() !== Application::APP_ID) {
55+
// Not my app => throw
56+
throw new InvalidArgumentException();
57+
}
58+
if ($notification->getSubject() !== 'quota_exceeded') {
59+
// Not a valid subject => throw
60+
throw new InvalidArgumentException();
61+
}
62+
63+
$l = $this->factory->get(Application::APP_ID, $languageCode);
64+
65+
$params = $notification->getSubjectParameters();
66+
67+
$subject = $l->t('Quota exceeded');
68+
$content = '';
69+
switch ($params['type']) {
70+
case Application::QUOTA_TYPE_TEXT:
71+
$content = $l->t('Text generation quota exceeded');
72+
break;
73+
case Application::QUOTA_TYPE_IMAGE:
74+
$content = $l->t('Image generation quota exceeded');
75+
break;
76+
case Application::QUOTA_TYPE_TRANSCRIPTION:
77+
$content = $l->t('Audio transcription quota exceeded');
78+
break;
79+
case Application::QUOTA_TYPE_SPEECH:
80+
$content = $l->t('Speech generation quota exceeded');
81+
break;
82+
}
83+
84+
$link = $this->url->getWebroot() . '/settings/user/ai';
85+
$iconUrl = $this->url->getAbsoluteURL($this->url->imagePath(Application::APP_ID, 'app-dark.svg'));
86+
87+
$notification
88+
->setParsedSubject($subject)
89+
->setParsedMessage($content)
90+
->setLink($link)
91+
->setIcon($iconUrl);
92+
93+
$actionLabel = $params['actionLabel'] ?? $l->t('View quota');
94+
$action = $notification->createAction();
95+
$action->setLabel($actionLabel)
96+
->setParsedLabel($actionLabel)
97+
->setLink($notification->getLink(), IAction::TYPE_WEB)
98+
->setPrimary(true);
99+
100+
$notification->addParsedAction($action);
101+
102+
return $notification;
103+
}
104+
}

lib/Service/OpenAiAPIService.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace OCA\OpenAi\Service;
99

10+
use DateTime;
1011
use Exception;
1112
use GuzzleHttp\Exception\ClientException;
1213
use GuzzleHttp\Exception\ServerException;
@@ -25,6 +26,7 @@
2526
use OCP\ICacheFactory;
2627
use OCP\IL10N;
2728
use OCP\Lock\LockedException;
29+
use OCP\Notification\IManager as INotificationManager;
2830
use OCP\TaskProcessing\ShapeEnumValue;
2931
use Psr\Log\LoggerInterface;
3032
use RuntimeException;
@@ -46,6 +48,7 @@ public function __construct(
4648
private ICacheFactory $cacheFactory,
4749
private QuotaUsageMapper $quotaUsageMapper,
4850
private OpenAiSettingsService $openAiSettingsService,
51+
private INotificationManager $notificationManager,
4952
IClientService $clientService,
5053
) {
5154
$this->client = $clientService->newClient();
@@ -252,8 +255,17 @@ public function isQuotaExceeded(?string $userId, int $type): bool {
252255
$this->logger->warning('Could not retrieve quota usage for user: ' . $userId . ' and quota type: ' . $type . '. Error: ' . $e->getMessage());
253256
throw new Exception('Could not retrieve quota usage.', Http::STATUS_INTERNAL_SERVER_ERROR);
254257
}
255-
256-
return $quotaUsage >= $quota;
258+
if ($quotaUsage >= $quota) {
259+
$notification = $this->notificationManager->createNotification();
260+
$notification->setApp(Application::APP_ID)
261+
->setUser($userId)
262+
->setDateTime(new DateTime())
263+
->setObject('quota_exceeded', (string)$type)
264+
->setSubject('quota_exceeded', ['type' => $type]);
265+
$this->notificationManager->notify($notification);
266+
return true;
267+
}
268+
return false;
257269
}
258270

259271
/**

tests/unit/Providers/OpenAiProviderTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ protected function setUp(): void {
8282
\OC::$server->get(ICacheFactory::class),
8383
\OC::$server->get(QuotaUsageMapper::class),
8484
$this->openAiSettingsService,
85+
$this->createMock(\OCP\Notification\IManager::class),
8586
$clientService,
8687
);
8788

0 commit comments

Comments
 (0)