Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions lib/Settings/Admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
109 changes: 109 additions & 0 deletions lib/TaskProcessing/TextToStickerProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Assistant\TaskProcessing;

use Exception;
use OCA\Assistant\AppInfo\Application;
use OCA\Assistant\Service\TaskProcessingService;
use OCP\IL10N;
use OCP\TaskProcessing\ISynchronousProvider;
use OCP\TaskProcessing\Task;
use OCP\TaskProcessing\TaskTypes\TextToImage;
use Psr\Log\LoggerInterface;
use RuntimeException;

class TextToStickerProvider implements ISynchronousProvider {

public function __construct(
private IL10N $l,
private TaskProcessingService $taskProcessingService,
private LoggerInterface $logger,
) {
}

public function getId(): string {
return Application::APP_ID . '-text2sticker';
}

public function getName(): string {
return $this->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());
}
}
}
72 changes: 72 additions & 0 deletions lib/TaskProcessing/TextToStickerTaskType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Assistant\TaskProcessing;

use OCA\Assistant\AppInfo\Application;
use OCP\IL10N;
use OCP\TaskProcessing\EShapeType;
use OCP\TaskProcessing\ITaskType;
use OCP\TaskProcessing\ShapeDescriptor;

class TextToStickerTaskType implements ITaskType {
public const ID = Application::APP_ID . ':text2sticker';

public function __construct(
private IL10N $l,
) {
}

/**
* @inheritDoc
*/
public function getName(): string {
return $this->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,
),
];
}
}
2 changes: 1 addition & 1 deletion src/components/TaskTypeSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
9 changes: 3 additions & 6 deletions src/stickerGeneration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 } })
Expand Down
Loading