|
8 | 8 |
|
9 | 9 | namespace OCA\Talk\Controller;
|
10 | 10 |
|
| 11 | +use OCA\Talk\AppInfo\Application; |
11 | 12 | use OCA\Talk\Chat\AutoComplete\SearchPlugin;
|
12 | 13 | use OCA\Talk\Chat\AutoComplete\Sorter;
|
13 | 14 | use OCA\Talk\Chat\ChatManager;
|
14 | 15 | use OCA\Talk\Chat\MessageParser;
|
15 | 16 | use OCA\Talk\Chat\Notifier;
|
16 | 17 | use OCA\Talk\Chat\ReactionManager;
|
17 | 18 | use OCA\Talk\Exceptions\CannotReachRemoteException;
|
| 19 | +use OCA\Talk\Exceptions\ChatSummaryException; |
18 | 20 | use OCA\Talk\Federation\Authenticator;
|
19 | 21 | use OCA\Talk\GuestManager;
|
20 | 22 | use OCA\Talk\MatterbridgeManager;
|
|
51 | 53 | use OCP\AppFramework\Http\Attribute\PublicPage;
|
52 | 54 | use OCP\AppFramework\Http\Attribute\UserRateLimit;
|
53 | 55 | use OCP\AppFramework\Http\DataResponse;
|
| 56 | +use OCP\AppFramework\Services\IAppConfig; |
54 | 57 | use OCP\AppFramework\Utility\ITimeFactory;
|
55 | 58 | use OCP\Collaboration\AutoComplete\IManager;
|
56 | 59 | use OCP\Collaboration\Collaborators\ISearchResult;
|
|
62 | 65 | use OCP\IRequest;
|
63 | 66 | use OCP\IUserManager;
|
64 | 67 | use OCP\RichObjectStrings\InvalidObjectExeption;
|
| 68 | +use OCP\RichObjectStrings\IRichTextFormatter; |
65 | 69 | use OCP\RichObjectStrings\IValidator;
|
66 | 70 | use OCP\Security\ITrustedDomainHelper;
|
67 | 71 | use OCP\Security\RateLimiting\IRateLimitExceededException;
|
68 | 72 | use OCP\Share\Exceptions\ShareNotFound;
|
69 | 73 | use OCP\Share\IShare;
|
| 74 | +use OCP\TaskProcessing\Exception\Exception; |
| 75 | +use OCP\TaskProcessing\IManager as ITaskProcessingManager; |
| 76 | +use OCP\TaskProcessing\Task; |
| 77 | +use OCP\TaskProcessing\TaskTypes\TextToTextSummary; |
70 | 78 | use OCP\User\Events\UserLiveStatusEvent;
|
71 | 79 | use OCP\UserStatus\IManager as IUserStatusManager;
|
72 | 80 | use OCP\UserStatus\IUserStatus;
|
| 81 | +use Psr\Log\LoggerInterface; |
73 | 82 |
|
74 | 83 | /**
|
75 | 84 | * @psalm-import-type TalkChatMentionSuggestion from ResponseDefinitions
|
@@ -114,6 +123,10 @@ public function __construct(
|
114 | 123 | protected Authenticator $federationAuthenticator,
|
115 | 124 | protected ProxyCacheMessageService $pcmService,
|
116 | 125 | protected Notifier $notifier,
|
| 126 | + protected IRichTextFormatter $richTextFormatter, |
| 127 | + protected ITaskProcessingManager $taskProcessingManager, |
| 128 | + protected IAppConfig $appConfig, |
| 129 | + protected LoggerInterface $logger, |
117 | 130 | ) {
|
118 | 131 | parent::__construct($appName, $request);
|
119 | 132 | }
|
@@ -489,6 +502,138 @@ public function receiveMessages(int $lookIntoFuture,
|
489 | 502 | return $this->prepareCommentsAsDataResponse($comments, $lastCommonReadId);
|
490 | 503 | }
|
491 | 504 |
|
| 505 | + /** |
| 506 | + * Summarize the next bunch of chat messages from a given offset |
| 507 | + * |
| 508 | + * Required capability: `chat-summary-api` |
| 509 | + * |
| 510 | + * @param positive-int $fromMessageId Offset from where on the summary should be generated |
| 511 | + * @return DataResponse<Http::STATUS_CREATED, array{taskId: int, nextOffset?: int}, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{error: 'ai-no-provider'|'ai-error'}, array{}>|DataResponse<Http::STATUS_NO_CONTENT, array<empty>, array{}> |
| 512 | + * @throws \InvalidArgumentException |
| 513 | + * |
| 514 | + * 201: Summary was scheduled, use the returned taskId to get the status |
| 515 | + * information and output from the TaskProcessing API: |
| 516 | + * https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-taskprocessing-api.html#fetch-a-task-by-id |
| 517 | + * If the response data contains nextOffset, not all messages could be handled in a single request. |
| 518 | + * After receiving the response a second summary should be requested with the provided nextOffset. |
| 519 | + * 204: No messages found to summarize |
| 520 | + * 400: No AI provider available or summarizing failed |
| 521 | + */ |
| 522 | + #[PublicPage] |
| 523 | + #[RequireModeratorOrNoLobby] |
| 524 | + #[RequireParticipant] |
| 525 | + public function summarizeChat( |
| 526 | + int $fromMessageId, |
| 527 | + ): DataResponse { |
| 528 | + $fromMessageId = max(0, $fromMessageId); |
| 529 | + |
| 530 | + $supportedTaskTypes = $this->taskProcessingManager->getAvailableTaskTypes(); |
| 531 | + if (!isset($supportedTaskTypes[TextToTextSummary::ID])) { |
| 532 | + return new DataResponse([ |
| 533 | + 'error' => ChatSummaryException::REASON_AI_ERROR, |
| 534 | + ], Http::STATUS_BAD_REQUEST); |
| 535 | + } |
| 536 | + |
| 537 | + // if ($this->room->isFederatedConversation()) { |
| 538 | + // /** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\ChatController $proxy */ |
| 539 | + // $proxy = \OCP\Server::get(\OCA\Talk\Federation\Proxy\TalkV1\Controller\ChatController::class); |
| 540 | + // return $proxy->summarizeChat( |
| 541 | + // $this->room, |
| 542 | + // $this->participant, |
| 543 | + // $fromMessageId, |
| 544 | + // ); |
| 545 | + // } |
| 546 | + |
| 547 | + $currentUser = $this->userManager->get($this->userId); |
| 548 | + $batchSize = $this->appConfig->getAppValueInt('ai_unread_summary_batch_size', 500); |
| 549 | + $comments = $this->chatManager->waitForNewMessages($this->room, $fromMessageId, $batchSize, 0, $currentUser, true, false); |
| 550 | + $this->preloadShares($comments); |
| 551 | + |
| 552 | + $messages = []; |
| 553 | + $nextOffset = 0; |
| 554 | + foreach ($comments as $comment) { |
| 555 | + $message = $this->messageParser->createMessage($this->room, $this->participant, $comment, $this->l); |
| 556 | + $this->messageParser->parseMessage($message); |
| 557 | + |
| 558 | + if (!$message->getVisibility()) { |
| 559 | + continue; |
| 560 | + } |
| 561 | + |
| 562 | + if ($message->getMessageType() === ChatManager::VERB_SYSTEM |
| 563 | + && !in_array($message->getMessageRaw(), [ |
| 564 | + 'call_ended', |
| 565 | + 'call_ended_everyone', |
| 566 | + 'file_shared', |
| 567 | + 'object_shared', |
| 568 | + ], true)) { |
| 569 | + // Ignore system messages apart from calls, shared objects and files |
| 570 | + continue; |
| 571 | + } |
| 572 | + |
| 573 | + $parsedMessage = $this->richTextFormatter->richToParsed( |
| 574 | + $message->getMessage(), |
| 575 | + $message->getMessageParameters(), |
| 576 | + ); |
| 577 | + |
| 578 | + $displayName = $message->getActorDisplayName(); |
| 579 | + if (in_array($message->getActorType(), [ |
| 580 | + Attendee::ACTOR_GUESTS, |
| 581 | + Attendee::ACTOR_EMAILS, |
| 582 | + ], true)) { |
| 583 | + if ($displayName === '') { |
| 584 | + $displayName = $this->l->t('Guest'); |
| 585 | + } else { |
| 586 | + $displayName = $this->l->t('%s (guest)', $displayName); |
| 587 | + } |
| 588 | + } |
| 589 | + |
| 590 | + if ($comment->getParentId() !== '0') { |
| 591 | + // FIXME should add something? |
| 592 | + } |
| 593 | + |
| 594 | + $messages[] = $displayName . ': ' . $parsedMessage; |
| 595 | + $nextOffset = (int)$comment->getId(); |
| 596 | + } |
| 597 | + |
| 598 | + if (empty($messages)) { |
| 599 | + return new DataResponse([], Http::STATUS_NO_CONTENT); |
| 600 | + } |
| 601 | + |
| 602 | + $task = new Task( |
| 603 | + TextToTextSummary::ID, |
| 604 | + ['input' => implode("\n\n", $messages)], |
| 605 | + Application::APP_ID, |
| 606 | + $this->userId, |
| 607 | + 'summary/' . $this->room->getToken(), |
| 608 | + ); |
| 609 | + |
| 610 | + try { |
| 611 | + $this->taskProcessingManager->scheduleTask($task); |
| 612 | + } catch (Exception $e) { |
| 613 | + $this->logger->error('An error occurred while trying to summarize unread messages', ['exception' => $e]); |
| 614 | + return new DataResponse([ |
| 615 | + 'error' => ChatSummaryException::REASON_AI_ERROR, |
| 616 | + ], Http::STATUS_BAD_REQUEST); |
| 617 | + } |
| 618 | + |
| 619 | + $taskId = $task->getId(); |
| 620 | + if ($taskId === null) { |
| 621 | + return new DataResponse([ |
| 622 | + 'error' => ChatSummaryException::REASON_AI_ERROR, |
| 623 | + ], Http::STATUS_BAD_REQUEST); |
| 624 | + } |
| 625 | + |
| 626 | + $data = [ |
| 627 | + 'taskId' => $taskId, |
| 628 | + ]; |
| 629 | + |
| 630 | + if ($nextOffset !== $this->room->getLastMessageId()) { |
| 631 | + $data['nextOffset'] = $nextOffset; |
| 632 | + } |
| 633 | + |
| 634 | + return new DataResponse($data, Http::STATUS_CREATED); |
| 635 | + } |
| 636 | + |
492 | 637 | /**
|
493 | 638 | * @return DataResponse<Http::STATUS_OK|Http::STATUS_NOT_MODIFIED, TalkChatMessageWithParent[], array{X-Chat-Last-Common-Read?: numeric-string, X-Chat-Last-Given?: numeric-string}>
|
494 | 639 | */
|
|
0 commit comments