mirror of
https://github.com/nextcloud/richdocuments.git
synced 2025-12-17 21:12:14 +01:00
feat: Implement AI Watermarking
to comply with EU AI Act Signed-off-by: Marcel Klehr <mklehr@gmx.net>
This commit is contained in:
parent
d3bcc030aa
commit
0c248a14c6
4 changed files with 29 additions and 12 deletions
|
|
@ -10,6 +10,7 @@ use League\CommonMark\GithubFlavoredMarkdownConverter;
|
|||
use OCA\Richdocuments\AppInfo\Application;
|
||||
use OCA\Richdocuments\TaskProcessing\TextToDocumentProvider;
|
||||
use OCA\Richdocuments\TaskProcessing\TextToSpreadsheetProvider;
|
||||
use OCP\IL10N;
|
||||
use OCP\TaskProcessing\Exception\Exception;
|
||||
use OCP\TaskProcessing\Exception\PreConditionNotMetException;
|
||||
use OCP\TaskProcessing\Exception\UnauthorizedException;
|
||||
|
|
@ -37,15 +38,17 @@ EOF;
|
|||
public function __construct(
|
||||
private IManager $taskProcessingManager,
|
||||
private RemoteService $remoteService,
|
||||
private IL10N $l10n,
|
||||
) {
|
||||
}
|
||||
|
||||
public function generateTextDocument(?string $userId, string $description, string $targetFormat = TextToDocumentProvider::DEFAULT_TARGET_FORMAT) {
|
||||
public function generateTextDocument(?string $userId, string $description, string $targetFormat = TextToDocumentProvider::DEFAULT_TARGET_FORMAT, bool $includeWatermark = true) {
|
||||
$prompt = self::TEXT_PROMPT;
|
||||
$taskInput = $prompt . "\n\n" . $description;
|
||||
$markdownContent = $this->runTextToTextTask($taskInput, $userId);
|
||||
$converter = new GithubFlavoredMarkdownConverter();
|
||||
$htmlContent = $converter->convert($markdownContent)->getContent();
|
||||
$markdownContentWithAiNote = $includeWatermark ? $markdownContent . "\n\n" . $this->l10n->t('This document was generated using Artificial Intelligence') : $markdownContent;
|
||||
$htmlContent = $converter->convert($markdownContentWithAiNote)->getContent();
|
||||
$htmlStream = $this->stringToStream($htmlContent);
|
||||
$docxContent = $this->remoteService->convertTo('document.html', $htmlStream, $targetFormat);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use OCA\Richdocuments\TaskProcessing\Presentation\Slides\TitleContentSlide;
|
|||
use OCA\Richdocuments\TaskProcessing\Presentation\Slides\TitleSlide;
|
||||
use OCA\Richdocuments\TemplateManager;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\TaskProcessing\Exception\Exception;
|
||||
use OCP\TaskProcessing\Exception\PreConditionNotMetException;
|
||||
use OCP\TaskProcessing\Exception\UnauthorizedException;
|
||||
|
|
@ -70,17 +71,18 @@ EOF;
|
|||
private TemplateManager $templateManager,
|
||||
private RemoteService $remoteService,
|
||||
private IConfig $config,
|
||||
private IL10N $l10n,
|
||||
) {
|
||||
}
|
||||
|
||||
public function generateSlideDeck(?string $userId, string $presentationText) {
|
||||
public function generateSlideDeck(?string $userId, string $presentationText, bool $includeWatermark = true) {
|
||||
$rawModelOutput = $this->runLLMQuery($userId, $presentationText);
|
||||
|
||||
$ooxml = $this->config->getAppValue(Application::APPNAME, 'doc_format', 'ooxml') === 'ooxml';
|
||||
$format = $ooxml ? 'pptx' : 'odp';
|
||||
|
||||
try {
|
||||
[$presentationStyle, $parsedStructure] = $this->parseModelJSON($rawModelOutput);
|
||||
[$presentationStyle, $parsedStructure] = $this->parseModelJSON($rawModelOutput, $includeWatermark);
|
||||
} catch (\JsonException) {
|
||||
throw new RuntimeException('LLM generated faulty JSON data');
|
||||
}
|
||||
|
|
@ -108,7 +110,7 @@ EOF;
|
|||
* @param string $jsonString
|
||||
* @return array
|
||||
*/
|
||||
private function parseModelJSON(string $jsonString): array {
|
||||
private function parseModelJSON(string $jsonString, bool $includeWatermark = true): array {
|
||||
$jsonString = trim($jsonString, "` \n\r\t\v\0");
|
||||
$modelJSON = json_decode(
|
||||
$jsonString,
|
||||
|
|
@ -148,9 +150,19 @@ EOF;
|
|||
$presentation->addSlide($slide);
|
||||
}
|
||||
|
||||
if ($includeWatermark) {
|
||||
// Add a final slide with a note that this was generated by AI
|
||||
$this->addAiComment(isset($index) ? $index + 1 : 0, $presentation);
|
||||
}
|
||||
|
||||
return [$presentation->getStyle(), $presentation->getSlideCommands()];
|
||||
}
|
||||
|
||||
private function addAiComment(int $index, Presentation $presentation) : void {
|
||||
$slide = new TitleContentSlide($index, '', $this->l10n->t('Generated using Artificial Intelligence'));
|
||||
$presentation->addSlide($slide);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a presentation file in memory
|
||||
*
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ namespace OCA\Richdocuments\TaskProcessing;
|
|||
use OCA\Richdocuments\AppInfo\Application;
|
||||
use OCA\Richdocuments\Service\SlideDeckService;
|
||||
use OCP\IL10N;
|
||||
use OCP\TaskProcessing\ISynchronousProvider;
|
||||
use OCP\TaskProcessing\ISynchronousWatermarkingProvider;
|
||||
|
||||
class SlideDeckGenerationProvider implements ISynchronousProvider {
|
||||
class SlideDeckGenerationProvider implements ISynchronousWatermarkingProvider {
|
||||
|
||||
public function __construct(
|
||||
private SlideDeckService $slideDeckService,
|
||||
|
|
@ -71,7 +71,7 @@ class SlideDeckGenerationProvider implements ISynchronousProvider {
|
|||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function process(?string $userId, array $input, callable $reportProgress): array {
|
||||
public function process(?string $userId, array $input, callable $reportProgress, bool $includeWatermark = true): array {
|
||||
if ($userId === null) {
|
||||
throw new \RuntimeException('User ID is required to process the prompt.');
|
||||
}
|
||||
|
|
@ -80,10 +80,11 @@ class SlideDeckGenerationProvider implements ISynchronousProvider {
|
|||
throw new \RuntimeException('Invalid input, expected "text" key with string value');
|
||||
}
|
||||
|
||||
$response = $this->withRetry(function () use ($userId, $input) {
|
||||
$response = $this->withRetry(function () use ($userId, $input, $includeWatermark) {
|
||||
return $this->slideDeckService->generateSlideDeck(
|
||||
$userId,
|
||||
$input['text'],
|
||||
$includeWatermark,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ use OCA\Richdocuments\AppInfo\Application;
|
|||
use OCA\Richdocuments\Service\DocumentGenerationService;
|
||||
use OCP\IL10N;
|
||||
use OCP\TaskProcessing\EShapeType;
|
||||
use OCP\TaskProcessing\ISynchronousProvider;
|
||||
use OCP\TaskProcessing\ISynchronousWatermarkingProvider;
|
||||
use OCP\TaskProcessing\ShapeDescriptor;
|
||||
use OCP\TaskProcessing\ShapeEnumValue;
|
||||
|
||||
class TextToDocumentProvider implements ISynchronousProvider {
|
||||
class TextToDocumentProvider implements ISynchronousWatermarkingProvider {
|
||||
public const DEFAULT_TARGET_FORMAT = 'docx';
|
||||
|
||||
public function __construct(
|
||||
|
|
@ -89,7 +89,7 @@ class TextToDocumentProvider implements ISynchronousProvider {
|
|||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function process(?string $userId, array $input, callable $reportProgress): array {
|
||||
public function process(?string $userId, array $input, callable $reportProgress, bool $includeWatermark = true): array {
|
||||
if ($userId === null) {
|
||||
throw new \RuntimeException('User ID is required to process the prompt.');
|
||||
}
|
||||
|
|
@ -108,6 +108,7 @@ class TextToDocumentProvider implements ISynchronousProvider {
|
|||
$userId,
|
||||
$input['text'],
|
||||
$targetFormat,
|
||||
$includeWatermark
|
||||
);
|
||||
|
||||
return [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue