Skip to content

Commit 6f5560d

Browse files
committed
feat(file-actions): add success/failure notifications for file actions
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
1 parent a8d7f69 commit 6f5560d

File tree

6 files changed

+324
-129
lines changed

6 files changed

+324
-129
lines changed

lib/AppInfo/Application.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
use OCA\Assistant\Listener\BeforeTemplateRenderedListener;
1212
use OCA\Assistant\Listener\ChattyLLMTaskListener;
1313
use OCA\Assistant\Listener\CSPListener;
14-
use OCA\Assistant\Listener\FileActionTaskListener;
14+
use OCA\Assistant\Listener\FileActionTaskFailedListener;
15+
use OCA\Assistant\Listener\FileActionTaskSuccessfulListener;
1516
use OCA\Assistant\Listener\FreePrompt\FreePromptReferenceListener;
1617
use OCA\Assistant\Listener\LoadAdditionalScriptsListener;
1718
use OCA\Assistant\Listener\SpeechToText\SpeechToTextReferenceListener;
@@ -72,7 +73,8 @@ public function register(IRegistrationContext $context): void {
7273
$context->registerEventListener(TaskSuccessfulEvent::class, TaskSuccessfulListener::class);
7374
$context->registerEventListener(TaskFailedEvent::class, TaskFailedListener::class);
7475
$context->registerEventListener(TaskSuccessfulEvent::class, ChattyLLMTaskListener::class);
75-
$context->registerEventListener(TaskSuccessfulEvent::class, FileActionTaskListener::class);
76+
$context->registerEventListener(TaskSuccessfulEvent::class, FileActionTaskSuccessfulListener::class);
77+
$context->registerEventListener(TaskFailedEvent::class, FileActionTaskFailedListener::class);
7678

7779
$context->registerNotifierService(Notifier::class);
7880

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Assistant\Listener;
11+
12+
use OCA\Assistant\AppInfo\Application;
13+
use OCA\Assistant\Service\NotificationService;
14+
use OCA\Assistant\Service\TaskProcessingService;
15+
use OCP\EventDispatcher\Event;
16+
use OCP\EventDispatcher\IEventListener;
17+
use OCP\Files\IRootFolder;
18+
use OCP\TaskProcessing\Events\TaskFailedEvent;
19+
use Psr\Log\LoggerInterface;
20+
21+
/**
22+
* @template-implements IEventListener<TaskFailedEvent>
23+
*/
24+
class FileActionTaskFailedListener implements IEventListener {
25+
26+
public function __construct(
27+
private TaskProcessingService $taskProcessingService,
28+
private NotificationService $notificationService,
29+
private IRootFolder $rootFolder,
30+
private LoggerInterface $logger,
31+
) {
32+
}
33+
34+
public function handle(Event $event): void {
35+
if (!($event instanceof TaskFailedEvent)) {
36+
return;
37+
}
38+
39+
$task = $event->getTask();
40+
$customId = $task->getCustomId();
41+
$appId = $task->getAppId();
42+
$taskTypeId = $task->getTaskTypeId();
43+
44+
if ($customId === null || $appId !== (Application::APP_ID . ':file-action')) {
45+
return;
46+
}
47+
48+
if (!$this->taskProcessingService->isFileActionTaskTypeAuthorized($taskTypeId)) {
49+
return;
50+
}
51+
52+
if (preg_match('/^file-action:(\d+)$/', $customId, $matches)) {
53+
$sourceFileId = (int)$matches[1];
54+
$this->logger->debug('FileActionTaskListener', ['source file id' => $sourceFileId]);
55+
$userFolder = $this->rootFolder->getUserFolder($task->getUserId());
56+
$sourceFile = $userFolder->getFirstNodeById($sourceFileId);
57+
$this->notificationService->sendFileActionNotification(
58+
$task->getUserId(), $taskTypeId,
59+
$sourceFileId, $sourceFile->getName(), $userFolder->getRelativePath($sourceFile->getPath()),
60+
);
61+
}
62+
}
63+
}

lib/Listener/FileActionTaskListener.php

Lines changed: 0 additions & 84 deletions
This file was deleted.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Assistant\Listener;
11+
12+
use OCA\Assistant\AppInfo\Application;
13+
use OCA\Assistant\Service\NotificationService;
14+
use OCA\Assistant\Service\TaskProcessingService;
15+
use OCP\EventDispatcher\Event;
16+
use OCP\EventDispatcher\IEventListener;
17+
use OCP\Files\IRootFolder;
18+
use OCP\TaskProcessing\Events\TaskSuccessfulEvent;
19+
use OCP\TaskProcessing\TaskTypes\TextToTextSummary;
20+
use Psr\Log\LoggerInterface;
21+
22+
/**
23+
* @template-implements IEventListener<TaskSuccessfulEvent>
24+
*/
25+
class FileActionTaskSuccessfulListener implements IEventListener {
26+
27+
public function __construct(
28+
private TaskProcessingService $taskProcessingService,
29+
private NotificationService $notificationService,
30+
private IRootFolder $rootFolder,
31+
private LoggerInterface $logger,
32+
) {
33+
}
34+
35+
public function handle(Event $event): void {
36+
if (!($event instanceof TaskSuccessfulEvent)) {
37+
return;
38+
}
39+
40+
$task = $event->getTask();
41+
$customId = $task->getCustomId();
42+
$appId = $task->getAppId();
43+
$taskTypeId = $task->getTaskTypeId();
44+
45+
if ($customId === null || $appId !== (Application::APP_ID . ':file-action')) {
46+
return;
47+
}
48+
49+
if (!$this->taskProcessingService->isFileActionTaskTypeAuthorized($taskTypeId)) {
50+
return;
51+
}
52+
53+
if (preg_match('/^file-action:(\d+)$/', $customId, $matches)) {
54+
// we get the task output, write it in the output file (in the same dir as the source one)
55+
$sourceFileId = (int)$matches[1];
56+
$userFolder = $this->rootFolder->getUserFolder($task->getUserId());
57+
$sourceFile = $userFolder->getFirstNodeById($sourceFileId);
58+
$this->logger->debug('FileActionTaskListener', ['source file id' => $sourceFileId]);
59+
try {
60+
$sourceFileParent = $sourceFile->getParent();
61+
$this->logger->debug('FileActionTaskListener', ['source file PARENT id' => $sourceFileParent->getId()]);
62+
if (
63+
class_exists('OCP\\TaskProcessing\\TaskTypes\\TextToSpeech')
64+
&& $taskTypeId === \OCP\TaskProcessing\TaskTypes\TextToSpeech::ID
65+
) {
66+
$speechFileId = (int)$task->getOutput()['speech'];
67+
$speechFile = $this->taskProcessingService->getOutputFile($speechFileId);
68+
$mimeType = mime_content_type($speechFile->fopen('rb'));
69+
$mimeType = $mimeType ?: 'audio/wav';
70+
$mimes = new \Mimey\MimeTypes;
71+
$extension = $mimes->getExtension($mimeType);
72+
if ($extension === 'mpga') {
73+
$extension = 'mp3';
74+
}
75+
$targetFileName = $sourceFile->getName() . ' - text to speech.' . $extension;
76+
$targetFile = $sourceFileParent->newFile($targetFileName, $speechFile->fopen('rb'));
77+
} else {
78+
$textResult = $task->getOutput()['output'];
79+
$suffix = $taskTypeId === TextToTextSummary::ID ? 'summarized' : 'transcribed';
80+
$targetFileName = $sourceFile->getName() . ' - ' . $suffix . '.txt';
81+
$targetFile = $sourceFileParent->newFile($targetFileName, $textResult);
82+
$this->logger->debug('FileActionTaskListener wrote file', ['target' => $targetFileName]);
83+
}
84+
$this->notificationService->sendFileActionNotification(
85+
$task->getUserId(), $taskTypeId,
86+
$sourceFileId, $sourceFile->getName(), $userFolder->getRelativePath($sourceFile->getPath()),
87+
$targetFile->getId(), $targetFile->getName(), $userFolder->getRelativePath($targetFile->getPath()),
88+
);
89+
} catch (\Exception|\Throwable $e) {
90+
$this->logger->error('FileActionTaskListener task succeeded but listener failed to write the result file', [
91+
'source file id' => $sourceFileId,
92+
'exception' => $e,
93+
]);
94+
$this->notificationService->sendFileActionNotification(
95+
$task->getUserId(), $taskTypeId,
96+
$sourceFileId, $sourceFile->getName(), $userFolder->getRelativePath($sourceFile->getPath()),
97+
);
98+
}
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)