",
"license": "agpl",
"private": true,
diff --git a/src/components/Envelope.vue b/src/components/Envelope.vue
index 6e5ce77cdc..b4666ec3a9 100644
--- a/src/components/Envelope.vue
+++ b/src/components/Envelope.vue
@@ -79,8 +79,9 @@
- {{ isEncrypted ? t('mail', 'Encrypted message') : data.previewText.trim() }}
+ :title="data.summary ? t('mail', 'This summary was AI generated') : null">
+
+ {{ isEncrypted ? t('mail', 'Encrypted message') : data.summary ? data.summary.trim() : data.previewText.trim() }}
@@ -337,6 +338,7 @@ import EnvelopeSkeleton from './EnvelopeSkeleton.vue'
import AlertOctagonIcon from 'vue-material-design-icons/AlertOctagon.vue'
import Avatar from './Avatar.vue'
import IconCreateEvent from 'vue-material-design-icons/Calendar.vue'
+import SparkleIcon from 'vue-material-design-icons/Creation.vue'
import ClockOutlineIcon from 'vue-material-design-icons/ClockOutline.vue'
import CheckIcon from 'vue-material-design-icons/Check.vue'
import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'
@@ -406,6 +408,7 @@ export default {
PlusIcon,
TagIcon,
TagModal,
+ SparkleIcon,
Star,
StarOutline,
EmailRead,
@@ -919,11 +922,21 @@ export default {
}
&__preview-text {
color: var(--color-text-maxcontrast);
- white-space: nowrap;
overflow: hidden;
- text-overflow: ellipsis;
font-weight: initial;
- flex: 1 1;
+ max-height: calc(var(--default-font-size) * var(--default-line-height) * 2);
+
+ /* Weird CSS hacks to make text ellipsize without white-space: nowrap */
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+
+ .material-design-icon {
+ display: inline;
+
+ position: relative;
+ top: 2px;
+ }
}
}
diff --git a/tests/Unit/Service/AiIntegrationsServiceTest.php b/tests/Unit/Service/AiIntegrationsServiceTest.php
index ffa8ba4b1a..b1f1c28af3 100644
--- a/tests/Unit/Service/AiIntegrationsServiceTest.php
+++ b/tests/Unit/Service/AiIntegrationsServiceTest.php
@@ -22,6 +22,9 @@
use OCA\Mail\Service\AiIntegrations\Cache;
use OCP\AppFramework\QueryException;
use OCP\IConfig;
+use OCP\TaskProcessing\Exception\Exception as TaskProcessingException;
+use OCP\TaskProcessing\IManager as TaskProcessingManager;
+use OCP\TaskProcessing\IProvider as TaskProcessingProvider;
use OCP\TextProcessing\FreePromptTaskType;
use OCP\TextProcessing\IManager;
use OCP\TextProcessing\SummaryTaskType;
@@ -30,30 +33,22 @@
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\MockObject\UnknownTypeException;
use Psr\Container\ContainerInterface;
+use Psr\Log\NullLogger;
+
use function interface_exists;
class AiIntegrationsServiceTest extends TestCase {
- /** @var ContainerInterface|MockObject */
- private $container;
-
- /** @var IManager|MockObject */
- private $manager;
-
- /** @var AiIntegrationsService */
- private $aiIntegrationsService;
-
- /** @var Cache */
- private $cache;
-
- /** @var IMAPClientFactory|MockObject */
- private $clientFactory;
-
- /** @var IMailManager|MockObject */
- private $mailManager;
-
- /** @var IConfig|MockObject */
- private $config;
+ private ContainerInterface|MockObject $container;
+ private IManager|MockObject $manager;
+ private IConfig|MockObject $config;
+ private NullLogger|MockObject $logger;
+ private AiIntegrationsService $aiIntegrationsService;
+ private Cache|MockObject $cache;
+ private IMAPClientFactory|MockObject $clientFactory;
+ private IMailManager|MockObject $mailManager;
+ private TaskProcessingManager|MockObject $taskProcessingManager;
+ private TaskProcessingProvider|MockObject $taskProcessingProvider;
protected function setUp(): void {
parent::setUp();
@@ -64,17 +59,23 @@ protected function setUp(): void {
$this->manager = null;
}
+ $this->logger = $this->createMock(NullLogger::class);
+ $this->config = $this->createMock(IConfig::class);
$this->cache = $this->createMock(Cache::class);
$this->clientFactory = $this->createMock(IMAPClientFactory::class);
$this->mailManager = $this->createMock(IMailManager::class);
- $this->config = $this->createMock(IConfig::class);
+ $this->taskProcessingManager = $this->createMock(TaskProcessingManager::class);
$this->aiIntegrationsService = new AiIntegrationsService(
$this->container,
+ $this->logger,
+ $this->config,
$this->cache,
$this->clientFactory,
$this->mailManager,
- $this->config,
+ $this->taskProcessingManager,
);
+
+ $this->taskProcessingProvider = $this->createMock(TaskProcessingProvider::class);
}
public function testSummarizeThreadNoBackend() {
@@ -374,4 +375,94 @@ public function testGenerateEventData(): void {
self::assertSame('* Q&A', $result->getDescription());
}
+ public function testSummarizeMessagesNoProvider() {
+ $account = new Account(new MailAccount());
+ $message = new Message();
+ $this->taskProcessingManager->expects(self::once())
+ ->method('getPreferredProvider')
+ ->willThrowException(new TaskProcessingException());
+ $this->logger->expects(self::once())
+ ->method('info')
+ ->with('No text summary provider available');
+
+ $this->aiIntegrationsService->summarizeMessages($account, [$message]);
+ }
+
+ public function testSummarizeMessagesContainsSummary() {
+ $mailAccount = new MailAccount();
+ $mailAccount->setId(123);
+ $mailAccount->setEmail('user@domain.tld');
+ $mailAccount->setInboundHost('127.0.0.1');
+ $mailAccount->setInboundPort(993);
+ $mailAccount->setInboundSslMode('ssl');
+ $mailAccount->setInboundUser('user@domain.tld');
+ $mailAccount->setInboundPassword('encrypted');
+ $account = new Account($mailAccount);
+ $message = new Message();
+ $message->setSummary('Test Summary');
+
+ $this->taskProcessingManager->expects(self::once())
+ ->method('getPreferredProvider')
+ ->willReturn($this->taskProcessingProvider);
+ $this->clientFactory->expects(self::once())
+ ->method('getClient');
+
+ $this->aiIntegrationsService->summarizeMessages($account, [$message]);
+ }
+
+ public function testSummarizeMessages() {
+ $mailAccount = new MailAccount();
+ $mailAccount->setId(123);
+ $mailAccount->setUserId('user1');
+ $mailAccount->setEmail('user1@domain.tld');
+ $mailAccount->setInboundHost('127.0.0.1');
+ $mailAccount->setInboundPort(993);
+ $mailAccount->setInboundSslMode('ssl');
+ $mailAccount->setInboundUser('user1@domain.tld');
+ $mailAccount->setInboundPassword('encrypted');
+ $account = new Account($mailAccount);
+
+ $mailBox = new Mailbox();
+ $mailBox->setId(1);
+
+ $message = new Message();
+ $message->setId(1);
+ $message->setUid(100);
+ $message->setMailboxId(1);
+
+ $imapClient = $this->clientFactory->getClient($account);
+
+ $imapMessage = $this->createMock(IMAPMessage::class);
+ $imapMessage->method('getPlainBody')->willReturn('This is a test message');
+
+ $this->taskProcessingManager->expects(self::once())
+ ->method('getPreferredProvider')
+ ->willReturn($this->taskProcessingProvider);
+ $this->taskProcessingManager->expects(self::once())
+ ->method('scheduleTask');
+
+ $this->clientFactory->expects(self::once())
+ ->method('getClient');
+
+ $this->mailManager->expects(self::once())
+ ->method('getMailbox')
+ ->with(
+ $account->getUserId(),
+ $message->getMailboxId()
+ )
+ ->willReturn($mailBox);
+ $this->mailManager->expects(self::once())
+ ->method('getImapMessage')
+ ->with(
+ $imapClient,
+ $account,
+ $mailBox,
+ $message->getUid(),
+ true
+ )
+ ->willReturn($imapMessage);
+
+ $this->aiIntegrationsService->summarizeMessages($account, [$message]);
+ }
+
}