diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 82e8f205d..0c7e677cc 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -31,6 +31,8 @@ use OCA\Assistant\Reference\Text2StickerProvider; use OCA\Assistant\TaskProcessing\AudioToAudioChatProvider; use OCA\Assistant\TaskProcessing\ContextAgentAudioInteractionProvider; +use OCA\Assistant\TaskProcessing\TextToStickerProvider; +use OCA\Assistant\TaskProcessing\TextToStickerTaskType; use OCA\Files\Event\LoadAdditionalScriptsEvent; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; @@ -39,6 +41,7 @@ use OCP\AppFramework\Bootstrap\IRegistrationContext; use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; use OCP\Collaboration\Reference\RenderReferenceEvent; +use OCP\IAppConfig; use OCP\Security\CSP\AddContentSecurityPolicyEvent; use OCP\TaskProcessing\Events\TaskFailedEvent; use OCP\TaskProcessing\Events\TaskSuccessfulEvent; @@ -52,8 +55,12 @@ class Application extends App implements IBootstrap { public const CHAT_USER_INSTRUCTIONS = 'This is a conversation in a specific language between the user and you, Nextcloud Assistant. You are a kind, polite and helpful AI that helps the user to the best of its abilities. If you do not understand something, you will ask for clarification. Detect the language that the user is using. Make sure to use the same language in your response. Do not mention the language explicitly.'; public const CHAT_USER_INSTRUCTIONS_TITLE = 'Above is a chat session in a specific language between the user and you, Nextcloud Assistant. Generate a suitable title summarizing the conversation in the same language. Output only the title in plain text, nothing else.'; + private IAppConfig $appConfig; public function __construct(array $urlParams = []) { parent::__construct(self::APP_ID, $urlParams); + + $container = $this->getContainer(); + $this->appConfig = $container->get(IAppConfig::class); } public function register(IRegistrationContext $context): void { @@ -94,6 +101,10 @@ public function register(IRegistrationContext $context): void { if (class_exists('OCP\\TaskProcessing\\TaskTypes\\ContextAgentAudioInteraction')) { $context->registerTaskProcessingProvider(ContextAgentAudioInteractionProvider::class); } + if ($this->appConfig->getValueString(Application::APP_ID, 'text_to_sticker_picker_enabled', '1') === '1') { + $context->registerTaskProcessingTaskType(TextToStickerTaskType::class); + $context->registerTaskProcessingProvider(TextToStickerProvider::class); + } } public function boot(IBootContext $context): void { diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index f65f6f38f..b410b5afa 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -43,6 +43,10 @@ public function getForm(): TemplateResponse { $freePromptPickerEnabled = $this->appConfig->getValueString(Application::APP_ID, 'free_prompt_picker_enabled', '1') === '1'; $textToImagePickerEnabled = $this->appConfig->getValueString(Application::APP_ID, 'text_to_image_picker_enabled', '1') === '1'; $textToStickerPickerEnabled = $this->appConfig->getValueString(Application::APP_ID, 'text_to_sticker_picker_enabled', '1') === '1'; + if ($textToStickerPickerEnabled && !$textToImageAvailable) { + $this->appConfig->setValueString(Application::APP_ID, 'text_to_sticker_picker_enabled', '0'); + $textToStickerPickerEnabled = false; + } $speechToTextEnabled = $this->appConfig->getValueString(Application::APP_ID, 'speech_to_text_picker_enabled', '1') === '1'; $chattyLLMUserInstructions = $this->appConfig->getValueString(Application::APP_ID, 'chat_user_instructions', Application::CHAT_USER_INSTRUCTIONS) ?: Application::CHAT_USER_INSTRUCTIONS; diff --git a/lib/TaskProcessing/TextToStickerProvider.php b/lib/TaskProcessing/TextToStickerProvider.php new file mode 100644 index 000000000..77bfc5d70 --- /dev/null +++ b/lib/TaskProcessing/TextToStickerProvider.php @@ -0,0 +1,109 @@ +l->t('Assistant'); + } + + public function getTaskTypeId(): string { + return TextToStickerTaskType::ID; + } + + public function getExpectedRuntime(): int { + return 60; + } + + public function getInputShapeEnumValues(): array { + return []; + } + + public function getInputShapeDefaults(): array { + return []; + } + + + public function getOptionalInputShape(): array { + return []; + } + + public function getOptionalInputShapeEnumValues(): array { + return []; + } + + public function getOptionalInputShapeDefaults(): array { + return []; + } + + public function getOutputShapeEnumValues(): array { + return []; + } + + public function getOptionalOutputShape(): array { + return []; + } + + public function getOptionalOutputShapeEnumValues(): array { + return []; + } + + public function process(?string $userId, array $input, callable $reportProgress): array { + if (!isset($input['input']) || !is_string($input['input'])) { + throw new RuntimeException('Invalid prompt'); + } + $input = $input['input']; + + // Generate Image with custom prompt + try { + $task = new Task( + TextToImage::ID, + [ + 'input' => $this->l->t('cartoon, neutral background, sticker of %1$s', [$input]), + 'numberOfImages' => 1 + ], + Application::APP_ID . ':internal', + $userId, + ); + $taskOutput = $this->taskProcessingService->runTaskProcessingTask($task); + $images = $taskOutput['images']; + if (empty($images)) { + throw new RuntimeException('No sticker generated'); + } + $outputImage = $this->taskProcessingService->getOutputFileContent($images[0]); + return ['image' => $outputImage]; + } catch (Exception $e) { + $this->logger->warning('Generating sticker failed with: ' . $e->getMessage(), ['exception' => $e]); + throw new RuntimeException('Generating sticker failed with: ' . $e->getMessage()); + } + } +} diff --git a/lib/TaskProcessing/TextToStickerTaskType.php b/lib/TaskProcessing/TextToStickerTaskType.php new file mode 100644 index 000000000..ae0b9386d --- /dev/null +++ b/lib/TaskProcessing/TextToStickerTaskType.php @@ -0,0 +1,72 @@ +l->t('Generate sticker'); + } + + /** + * @inheritDoc + */ + public function getDescription(): string { + return $this->l->t('Generate sticker from text'); + } + + /** + * @return string + */ + public function getId(): string { + return self::ID; + } + + /** + * @return ShapeDescriptor[] + */ + public function getInputShape(): array { + return [ + 'input' => new ShapeDescriptor( + $this->l->t('Prompt'), + $this->l->t('Describe the sticker you would like to create'), + EShapeType::Text, + ), + ]; + } + + /** + * @return ShapeDescriptor[] + */ + public function getOutputShape(): array { + return [ + 'image' => new ShapeDescriptor( + $this->l->t('Output stickers'), + $this->l->t('The generated sticker'), + EShapeType::Image, + ), + ]; + } +} diff --git a/src/components/TaskTypeSelect.vue b/src/components/TaskTypeSelect.vue index 4ccabc5a6..83749fcf8 100644 --- a/src/components/TaskTypeSelect.vue +++ b/src/components/TaskTypeSelect.vue @@ -217,7 +217,7 @@ export default { return 'translate' } else if (id.startsWith('richdocuments')) { return 'generate' - } else if (id.includes('image')) { + } else if (id.includes('image') || id.includes('sticker')) { return 'image' } else if (id.includes('audio') || id.includes('speech')) { return 'audio' diff --git a/src/stickerGeneration.js b/src/stickerGeneration.js index 37e9c0b19..cd0bbd25d 100644 --- a/src/stickerGeneration.js +++ b/src/stickerGeneration.js @@ -11,14 +11,11 @@ registerCustomPickerElement('assistant_sticker_generation', async (el, { provide const app = createApp( ImageResultCustomPickerElement, { - inputs: { - input: t('assistant', 'cartoon, neutral background, sticker of '), - }, providerId, accessible, - taskType: 'core:text2image', - outputKey: 'images', - multipleImages: true, + taskType: 'assistant:text2sticker', + outputKey: 'image', + multipleImages: false, }, ) app.mixin({ methods: { t, n } })