Skip to content

Commit 8800769

Browse files
committed
feat: files "new file" menu generate image action
Signed-off-by: Andrey Borysenko <andrey18106x@gmail.com>
1 parent ca215d3 commit 8800769

File tree

10 files changed

+348
-16
lines changed

10 files changed

+348
-16
lines changed

lib/AppInfo/Application.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use OCA\Assistant\Listener\ChattyLLMTaskListener;
1313
use OCA\Assistant\Listener\CSPListener;
1414
use OCA\Assistant\Listener\FreePrompt\FreePromptReferenceListener;
15+
use OCA\Assistant\Listener\LoadAdditionalScriptsListener;
1516
use OCA\Assistant\Listener\SpeechToText\SpeechToTextReferenceListener;
1617
use OCA\Assistant\Listener\TaskFailedListener;
1718
use OCA\Assistant\Listener\TaskOutputFileReferenceListener;
@@ -24,6 +25,7 @@
2425
use OCA\Assistant\Reference\Text2ImageReferenceProvider;
2526
use OCA\Assistant\TaskProcessing\AudioToAudioChatProvider;
2627
use OCA\Assistant\TaskProcessing\ContextAgentAudioInteractionProvider;
28+
use OCA\Files\Event\LoadAdditionalScriptsEvent;
2729
use OCP\AppFramework\App;
2830
use OCP\AppFramework\Bootstrap\IBootContext;
2931

@@ -64,6 +66,7 @@ public function register(IRegistrationContext $context): void {
6466
$context->registerEventListener(RenderReferenceEvent::class, TaskOutputFileReferenceListener::class);
6567

6668
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
69+
$context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalScriptsListener::class);
6770

6871
$context->registerEventListener(TaskSuccessfulEvent::class, TaskSuccessfulListener::class);
6972
$context->registerEventListener(TaskFailedEvent::class, TaskFailedListener::class);
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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\AssistantService;
14+
use OCA\Files\Event\LoadAdditionalScriptsEvent;
15+
use OCP\AppFramework\Services\IInitialState;
16+
use OCP\EventDispatcher\Event;
17+
use OCP\EventDispatcher\IEventListener;
18+
use OCP\IConfig;
19+
use OCP\Util;
20+
21+
/**
22+
* @template-extends IEventListener<LoadAdditionalScriptsEvent>
23+
*/
24+
class LoadAdditionalScriptsListener implements IEventListener {
25+
26+
public function __construct(
27+
private readonly IConfig $config,
28+
private readonly IInitialState $initialState,
29+
private readonly AssistantService $assistantService,
30+
) {
31+
}
32+
33+
public function handle(Event $event): void {
34+
if (!$event instanceof LoadAdditionalScriptsEvent) {
35+
return;
36+
}
37+
38+
if ($this->config->getAppValue(Application::APP_ID, 'disableFilesNewMenuPlugin', '0') === '1') {
39+
return;
40+
}
41+
42+
$taskTypes = $this->assistantService->getAvailableTaskTypes();
43+
$hasText2Image = false;
44+
foreach ($taskTypes as $taskType) {
45+
if ($taskType['id'] === 'core:text2image') {
46+
$hasText2Image = true;
47+
break;
48+
}
49+
}
50+
51+
$this->initialState->provideInitialState('new-file-generate-image', [
52+
'hasText2Image' => $hasText2Image,
53+
'taskTypes' => $taskTypes,
54+
]);
55+
56+
Util::addScript(Application::APP_ID, Application::APP_ID . '-filesNewMenu');
57+
}
58+
}

lib/Listener/TaskSuccessfulListener.php

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@
1010
use OCA\Assistant\AppInfo\Application;
1111
use OCA\Assistant\Db\TaskNotificationMapper;
1212
use OCA\Assistant\Event\BeforeAssistantNotificationEvent;
13+
use OCA\Assistant\Service\AssistantService;
1314
use OCA\Assistant\Service\NotificationService;
1415
use OCP\EventDispatcher\Event;
1516
use OCP\EventDispatcher\IEventDispatcher;
1617
use OCP\EventDispatcher\IEventListener;
18+
use OCP\IURLGenerator;
1719
use OCP\TaskProcessing\Events\TaskSuccessfulEvent;
20+
use Psr\Log\LoggerInterface;
1821

1922
/**
2023
* @template-implements IEventListener<Event>
@@ -25,6 +28,9 @@ public function __construct(
2528
private TaskNotificationMapper $taskNotificationMapper,
2629
private NotificationService $notificationService,
2730
private IEventDispatcher $eventDispatcher,
31+
private AssistantService $assistantService,
32+
private LoggerInterface $logger,
33+
private IUrlGenerator $url,
2834
) {
2935
}
3036

@@ -38,7 +44,11 @@ public function handle(Event $event): void {
3844
return;
3945
}
4046

41-
if ($this->taskNotificationMapper->getByTaskId($task->getId()) === null) {
47+
$customIdPattern = '/^new-image-file:(\d+)$/';
48+
$hasTargetDirectory = preg_match($customIdPattern, $task->getCustomId(), $matches) === 1;
49+
50+
// For tasks with customId "new-image-file:<directoryIdNumber>" we always send a notification
51+
if ($this->taskNotificationMapper->getByTaskId($task->getId()) === null && !$hasTargetDirectory) {
4252
return;
4353
}
4454

@@ -57,7 +67,31 @@ public function handle(Event $event): void {
5767
$notificationActionLabel = $beforeAssistantNotificationEvent->getNotificationActionLabel();
5868
}
5969

70+
if ($hasTargetDirectory) {
71+
$directoryId = (int)$matches[1];
72+
$fileId = (int) $task->getOutput()['images'][0];
73+
try {
74+
$file = $this->assistantService->saveNewFileMenuActionFile($task->getUserId(), $task->getId(), $fileId, $directoryId);
75+
$notificationTarget = $this->url->linkToRouteAbsolute(
76+
'files.viewcontroller.showFile',
77+
[
78+
'fileid' => $file->getId(),
79+
'opendetails' => 'true',
80+
'openfile' => 'false',
81+
],
82+
);
83+
} catch (\Exception $e) {
84+
$this->logger->error('TaskSuccessfulListener: Failed to save new file menu action file.', [
85+
'task' => $task->jsonSerialize(),
86+
'exception' => $e,
87+
]);
88+
}
89+
}
90+
6091
$this->notificationService->sendNotification($task, $notificationTarget, $notificationActionLabel);
61-
$this->taskNotificationMapper->deleteByTaskId($task->getId());
92+
93+
if (!$hasTargetDirectory) {
94+
$this->taskNotificationMapper->deleteByTaskId($task->getId());
95+
}
6296
}
6397
}

lib/Service/AssistantService.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,25 @@ private function saveFile(string $userId, int $ocpTaskId, int $fileId): File {
533533
}
534534
}
535535

536+
/**
537+
* @throws TaskProcessingException
538+
* @throws NotPermittedException
539+
* @throws NotFoundException
540+
* @throws Exception
541+
* @throws LockedException
542+
* @throws NoUserException
543+
*/
544+
public function saveNewFileMenuActionFile(string $userId, int $ocpTaskId, int $fileId, int $targetDirectoryId): File {
545+
$taskOutputFile = $this->getTaskOutputFile($userId, $ocpTaskId, $fileId);
546+
$userFolder = $this->rootFolder->getUserFolder($userId);
547+
$targetDirectory = $userFolder->getFirstNodeById($targetDirectoryId);
548+
if (!$targetDirectory instanceof Folder) {
549+
throw new NotFoundException('Target directory not found');
550+
}
551+
$targetFileName = $this->getTargetFileName($taskOutputFile);
552+
return $targetDirectory->newFile($targetFileName, $taskOutputFile->fopen('rb'));
553+
}
554+
536555
/**
537556
* @param string $userId
538557
* @param int $ocpTaskId

package-lock.json

Lines changed: 52 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"@nextcloud/axios": "^2.0.0",
4646
"@nextcloud/dialogs": "^7.0.0-rc.1",
4747
"@nextcloud/event-bus": "^3.1.0",
48+
"@nextcloud/files": "^3.11.0",
4849
"@nextcloud/initial-state": "^2.0.0",
4950
"@nextcloud/l10n": "^3.1.0",
5051
"@nextcloud/moment": "^1.3.1",

0 commit comments

Comments
 (0)