From 9318dfc16cb0f6af3e459520e7e9f8b32f2e8059 Mon Sep 17 00:00:00 2001 From: Lukas Schaefer Date: Thu, 21 May 2026 10:28:23 +0200 Subject: [PATCH 1/4] Implement scheduled tasks ui Signed-off-by: Lukas Schaefer --- lib/Controller/ChattyLLMController.php | 9 +- lib/Db/ChattyLLM/MessageMapper.php | 7 +- lib/Db/ChattyLLM/SessionMapper.php | 9 +- lib/Service/AssistantService.php | 21 ++ lib/Service/ChatService.php | 8 +- .../AssistantTextProcessingForm.vue | 8 +- .../ChattyLLM/ChattyLLMInputForm.vue | 208 ++++++++++++++---- src/components/TaskTypeSelect.vue | 3 + 8 files changed, 222 insertions(+), 51 deletions(-) diff --git a/lib/Controller/ChattyLLMController.php b/lib/Controller/ChattyLLMController.php index 8a6aac36..2f6e200b 100644 --- a/lib/Controller/ChattyLLMController.php +++ b/lib/Controller/ChattyLLMController.php @@ -318,9 +318,9 @@ public function deleteSession(int $sessionId): JSONResponse { */ #[NoAdminRequired] #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT, tags: ['chat_api'])] - public function getSessions(): JSONResponse { + public function getSessions(?bool $isAssignment = false): JSONResponse { try { - $sessions = $this->chatService->getSessionsForUser($this->userId); + $sessions = $this->chatService->getSessionsForUser($this->userId, $isAssignment); /** @var list $serializedSessions */ $serializedSessions = array_map(static function ($session) { return $session->jsonSerialize(); @@ -380,6 +380,7 @@ public function newMessage( * @param int $sessionId The session ID * @param int $limit The max number of messages to return * @param int $cursor The index of the first result to return + * @param bool $hideUserMessages Whether to hide user messages from the response * @return JSONResponse, array{}>|JSONResponse * * 200: The message list has been successfully obtained @@ -388,9 +389,9 @@ public function newMessage( */ #[NoAdminRequired] #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT, tags: ['chat_api'])] - public function getMessages(int $sessionId, int $limit = 20, int $cursor = 0): JSONResponse { + public function getMessages(int $sessionId, int $limit = 20, int $cursor = 0, bool $hideUserMessages = false): JSONResponse { try { - $messages = $this->chatService->getSessionMessages($this->userId, $sessionId, $limit, $cursor); + $messages = $this->chatService->getSessionMessages($this->userId, $sessionId, $limit, $cursor, $hideUserMessages); return new JSONResponse(array_map(static function (Message $message) { return $message->jsonSerialize(); }, $messages)); diff --git a/lib/Db/ChattyLLM/MessageMapper.php b/lib/Db/ChattyLLM/MessageMapper.php index a0c4c0ce..1c69fef6 100644 --- a/lib/Db/ChattyLLM/MessageMapper.php +++ b/lib/Db/ChattyLLM/MessageMapper.php @@ -80,10 +80,11 @@ public function getLastNonEmptyHumanMessage(int $sessionId): Message { * @param int $sessionId * @param int $cursor * @param int $limit + * @param bool $hideUserMessages * @return list * @throws \OCP\DB\Exception */ - public function getMessages(int $sessionId, int $cursor, int $limit): array { + public function getMessages(int $sessionId, int $cursor, int $limit, bool $hideUserMessages = false): array { $qb = $this->db->getQueryBuilder(); $qb->select(Message::$columns) ->from($this->getTableName()) @@ -91,6 +92,10 @@ public function getMessages(int $sessionId, int $cursor, int $limit): array { ->orderBy('id', 'DESC') ->setFirstResult($cursor); + if ($hideUserMessages) { + $qb->andWhere($qb->expr()->neq('role', $qb->createPositionalParameter(Message::ROLE_HUMAN, IQueryBuilder::PARAM_STR))); + } + if ($limit > 0) { $qb->setMaxResults($limit); } diff --git a/lib/Db/ChattyLLM/SessionMapper.php b/lib/Db/ChattyLLM/SessionMapper.php index babf812c..dfc6fe73 100644 --- a/lib/Db/ChattyLLM/SessionMapper.php +++ b/lib/Db/ChattyLLM/SessionMapper.php @@ -84,16 +84,23 @@ public function getUserSessionForAssignment(string $userId, int $assignmentId): /** * @param string $userId + * @param bool $isAssignment * @return list * @throws \OCP\DB\Exception */ - public function getUserSessions(string $userId): array { + public function getUserSessions(string $userId, bool $isAssignment): array { $qb = $this->db->getQueryBuilder(); $qb->select(Session::$columns) ->from($this->getTableName()) ->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId, IQueryBuilder::PARAM_STR))) ->orderBy('timestamp', 'DESC'); + if ($isAssignment) { + $qb->andWhere($qb->expr()->isNotNull('assignment_id')); + } else { + $qb->andWhere($qb->expr()->isNull('assignment_id')); + } + return $this->findEntities($qb); } diff --git a/lib/Service/AssistantService.php b/lib/Service/AssistantService.php index cf0438e7..dd00054e 100644 --- a/lib/Service/AssistantService.php +++ b/lib/Service/AssistantService.php @@ -64,6 +64,7 @@ class AssistantService { private const TASK_TYPE_PRIORITIES = [ 'chatty-llm' => 1, + 'assignments' => 1, TextToText::ID => 2, 'context_chat:context_chat' => 3, 'legacy:TextProcessing:OCA\ContextChat\TextProcessing\ContextChatTaskType' => 3, @@ -287,6 +288,9 @@ private function getCategory(string $typeId): array { if (str_starts_with($typeId, 'chatty')) { $categoryId = 'chat'; $categoryName = $this->l10n->t('Chat with AI'); + } elseif (str_starts_with($typeId, 'assignments')) { + $categoryId = 'assignments'; + $categoryName = $this->l10n->t('Scheduled tasks'); } elseif (str_starts_with($typeId, 'context_chat')) { $categoryId = 'context'; $categoryName = $this->l10n->t('Context Chat'); @@ -435,6 +439,23 @@ public function getAvailableTaskTypes(): array { 'priority' => self::TASK_TYPE_PRIORITIES['chatty-llm'] ?? 1000, 'preferredProviderName' => $preferredProviderName, ]; + // add the chattyUI assignments virtual task type + $types[] = [ + 'id' => 'assignments', + 'name' => $this->l10n->t('Scheduled tasks'), + 'description' => $this->l10n->t('Scheduled tasks'), + 'category' => $this->getCategory('assignments'), + 'inputShape' => [], + 'inputShapeEnumValues' => [], + 'inputShapeDefaults' => [], + 'outputShape' => [], + 'optionalInputShape' => [], + 'optionalInputShapeEnumValues' => [], + 'optionalInputShapeDefaults' => [], + 'optionalOutputShape' => [], + 'priority' => self::TASK_TYPE_PRIORITIES['assignments'] ?? 1000, + 'preferredProviderName' => $preferredProviderName, + ]; // do not add the raw TextToTextChat type if (!self::DEBUG) { continue; diff --git a/lib/Service/ChatService.php b/lib/Service/ChatService.php index 7586c4b9..6ee6c7b7 100644 --- a/lib/Service/ChatService.php +++ b/lib/Service/ChatService.php @@ -157,12 +157,12 @@ public function deleteSession(?string $userId, int $sessionId): void { * @throws InternalException * @throws UnauthorizedException */ - public function getSessionsForUser(?string $userId): array { + public function getSessionsForUser(?string $userId, bool $isAssignment): array { if ($userId === null) { throw new UnauthorizedException($this->l10n->t('Unauthorized')); } try { - return $this->sessionMapper->getUserSessions($userId); + return $this->sessionMapper->getUserSessions($userId, $isAssignment); } catch (Exception $e) { throw new InternalException(previous: $e); } @@ -250,7 +250,7 @@ public function createMessage(?string $userId, int $sessionId, string $role, str * @throws NotFoundException * @throws UnauthorizedException */ - public function getSessionMessages(?string $userId, int $sessionId, int $limit = 20, int $cursor = 0): array { + public function getSessionMessages(?string $userId, int $sessionId, int $limit = 20, int $cursor = 0, bool $hideUserMessages = false): array { if ($userId === null) { throw new UnauthorizedException($this->l10n->t('Unauthorized')); } @@ -268,7 +268,7 @@ public function getSessionMessages(?string $userId, int $sessionId, int $limit = /** @var list $messages */ try { - $messages = $this->messageMapper->getMessages($sessionId, $cursor, $limit); + $messages = $this->messageMapper->getMessages($sessionId, $cursor, $limit, $hideUserMessages); } catch (Exception $e) { throw new InternalException(previous: $e); } diff --git a/src/components/AssistantTextProcessingForm.vue b/src/components/AssistantTextProcessingForm.vue index 7f35482b..a176c5a9 100644 --- a/src/components/AssistantTextProcessingForm.vue +++ b/src/components/AssistantTextProcessingForm.vue @@ -17,7 +17,10 @@ :options="sortedTaskTypes" @update:model-value="onTaskTypeUserChange" />
- +
@@ -516,6 +519,9 @@ export default { this.loadingTaskTypes = false }) }, + onOpenChatFromAssignment() { + this.mySelectedTaskTypeId = CHAT_TASK_TYPE_ID + }, onTaskTypeUserChange() { this.$emit('new-task') diff --git a/src/components/ChattyLLM/ChattyLLMInputForm.vue b/src/components/ChattyLLM/ChattyLLMInputForm.vue index 7c11ef18..e50837a2 100644 --- a/src/components/ChattyLLM/ChattyLLMInputForm.vue +++ b/src/components/ChattyLLM/ChattyLLMInputForm.vue @@ -1,12 +1,13 @@ + -->