Skip to content

Commit d7890f5

Browse files
marcelklehrbackportbot[bot]
authored andcommitted
feat: Implement AI Watermarking
to comply with EU AI Act Signed-off-by: Marcel Klehr <mklehr@gmx.net>
1 parent 9b10fa9 commit d7890f5

4 files changed

Lines changed: 29 additions & 12 deletions

File tree

lib/Service/DocumentGenerationService.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use OCA\Richdocuments\AppInfo\Application;
1111
use OCA\Richdocuments\TaskProcessing\TextToDocumentProvider;
1212
use OCA\Richdocuments\TaskProcessing\TextToSpreadsheetProvider;
13+
use OCP\IL10N;
1314
use OCP\TaskProcessing\Exception\Exception;
1415
use OCP\TaskProcessing\Exception\PreConditionNotMetException;
1516
use OCP\TaskProcessing\Exception\UnauthorizedException;
@@ -37,15 +38,17 @@ class DocumentGenerationService {
3738
public function __construct(
3839
private IManager $taskProcessingManager,
3940
private RemoteService $remoteService,
41+
private IL10N $l10n,
4042
) {
4143
}
4244

43-
public function generateTextDocument(?string $userId, string $description, string $targetFormat = TextToDocumentProvider::DEFAULT_TARGET_FORMAT) {
45+
public function generateTextDocument(?string $userId, string $description, string $targetFormat = TextToDocumentProvider::DEFAULT_TARGET_FORMAT, bool $includeWatermark = true) {
4446
$prompt = self::TEXT_PROMPT;
4547
$taskInput = $prompt . "\n\n" . $description;
4648
$markdownContent = $this->runTextToTextTask($taskInput, $userId);
4749
$converter = new GithubFlavoredMarkdownConverter();
48-
$htmlContent = $converter->convert($markdownContent)->getContent();
50+
$markdownContentWithAiNote = $includeWatermark ? $markdownContent . "\n\n" . $this->l10n->t('This document was generated using Artificial Intelligence') : $markdownContent;
51+
$htmlContent = $converter->convert($markdownContentWithAiNote)->getContent();
4952
$htmlStream = $this->stringToStream($htmlContent);
5053
$docxContent = $this->remoteService->convertTo('document.html', $htmlStream, $targetFormat);
5154

lib/Service/SlideDeckService.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use OCA\Richdocuments\TaskProcessing\Presentation\Slides\TitleSlide;
1414
use OCA\Richdocuments\TemplateManager;
1515
use OCP\IConfig;
16+
use OCP\IL10N;
1617
use OCP\TaskProcessing\Exception\Exception;
1718
use OCP\TaskProcessing\Exception\PreConditionNotMetException;
1819
use OCP\TaskProcessing\Exception\UnauthorizedException;
@@ -70,17 +71,18 @@ public function __construct(
7071
private TemplateManager $templateManager,
7172
private RemoteService $remoteService,
7273
private IConfig $config,
74+
private IL10N $l10n,
7375
) {
7476
}
7577

76-
public function generateSlideDeck(?string $userId, string $presentationText) {
78+
public function generateSlideDeck(?string $userId, string $presentationText, bool $includeWatermark = true) {
7779
$rawModelOutput = $this->runLLMQuery($userId, $presentationText);
7880

7981
$ooxml = $this->config->getAppValue(Application::APPNAME, 'doc_format', 'ooxml') === 'ooxml';
8082
$format = $ooxml ? 'pptx' : 'odp';
8183

8284
try {
83-
[$presentationStyle, $parsedStructure] = $this->parseModelJSON($rawModelOutput);
85+
[$presentationStyle, $parsedStructure] = $this->parseModelJSON($rawModelOutput, $includeWatermark);
8486
} catch (\JsonException) {
8587
throw new RuntimeException('LLM generated faulty JSON data');
8688
}
@@ -108,7 +110,7 @@ public function generateSlideDeck(?string $userId, string $presentationText) {
108110
* @param string $jsonString
109111
* @return array
110112
*/
111-
private function parseModelJSON(string $jsonString): array {
113+
private function parseModelJSON(string $jsonString, bool $includeWatermark = true): array {
112114
$jsonString = trim($jsonString, "` \n\r\t\v\0");
113115
$modelJSON = json_decode(
114116
$jsonString,
@@ -148,9 +150,19 @@ private function parseModelJSON(string $jsonString): array {
148150
$presentation->addSlide($slide);
149151
}
150152

153+
if ($includeWatermark) {
154+
// Add a final slide with a note that this was generated by AI
155+
$this->addAiComment(isset($index) ? $index + 1 : 0, $presentation);
156+
}
157+
151158
return [$presentation->getStyle(), $presentation->getSlideCommands()];
152159
}
153160

161+
private function addAiComment(int $index, Presentation $presentation) : void {
162+
$slide = new TitleContentSlide($index, '', $this->l10n->t('Generated using Artificial Intelligence'));
163+
$presentation->addSlide($slide);
164+
}
165+
154166
/**
155167
* Creates a presentation file in memory
156168
*

lib/TaskProcessing/SlideDeckGenerationProvider.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
use OCA\Richdocuments\AppInfo\Application;
1212
use OCA\Richdocuments\Service\SlideDeckService;
1313
use OCP\IL10N;
14-
use OCP\TaskProcessing\ISynchronousProvider;
14+
use OCP\TaskProcessing\ISynchronousWatermarkingProvider;
1515

16-
class SlideDeckGenerationProvider implements ISynchronousProvider {
16+
class SlideDeckGenerationProvider implements ISynchronousWatermarkingProvider {
1717

1818
public function __construct(
1919
private SlideDeckService $slideDeckService,
@@ -84,7 +84,7 @@ public function getOptionalOutputShapeEnumValues(): array {
8484
* @inheritDoc
8585
*/
8686
#[\Override]
87-
public function process(?string $userId, array $input, callable $reportProgress): array {
87+
public function process(?string $userId, array $input, callable $reportProgress, bool $includeWatermark = true): array {
8888
if ($userId === null) {
8989
throw new \RuntimeException('User ID is required to process the prompt.');
9090
}
@@ -93,10 +93,11 @@ public function process(?string $userId, array $input, callable $reportProgress)
9393
throw new \RuntimeException('Invalid input, expected "text" key with string value');
9494
}
9595

96-
$response = $this->withRetry(function () use ($userId, $input) {
96+
$response = $this->withRetry(function () use ($userId, $input, $includeWatermark) {
9797
return $this->slideDeckService->generateSlideDeck(
9898
$userId,
9999
$input['text'],
100+
$includeWatermark,
100101
);
101102
});
102103

lib/TaskProcessing/TextToDocumentProvider.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
use OCA\Richdocuments\Service\DocumentGenerationService;
1313
use OCP\IL10N;
1414
use OCP\TaskProcessing\EShapeType;
15-
use OCP\TaskProcessing\ISynchronousProvider;
15+
use OCP\TaskProcessing\ISynchronousWatermarkingProvider;
1616
use OCP\TaskProcessing\ShapeDescriptor;
1717
use OCP\TaskProcessing\ShapeEnumValue;
1818

19-
class TextToDocumentProvider implements ISynchronousProvider {
19+
class TextToDocumentProvider implements ISynchronousWatermarkingProvider {
2020
public const DEFAULT_TARGET_FORMAT = 'docx';
2121

2222
public function __construct(
@@ -102,7 +102,7 @@ public function getOptionalOutputShapeEnumValues(): array {
102102
* @inheritDoc
103103
*/
104104
#[\Override]
105-
public function process(?string $userId, array $input, callable $reportProgress): array {
105+
public function process(?string $userId, array $input, callable $reportProgress, bool $includeWatermark = true): array {
106106
if ($userId === null) {
107107
throw new \RuntimeException('User ID is required to process the prompt.');
108108
}
@@ -121,6 +121,7 @@ public function process(?string $userId, array $input, callable $reportProgress)
121121
$userId,
122122
$input['text'],
123123
$targetFormat,
124+
$includeWatermark
124125
);
125126

126127
return [

0 commit comments

Comments
 (0)