From 7503377ff9112ee89f944a135469165e95fd2923 Mon Sep 17 00:00:00 2001 From: Danil Afanasyev Date: Sun, 25 Oct 2026 15:55:54 +0100 Subject: [PATCH 1/4] feat(chat): add a search endpoint for chat messages (#710) Signed-off-by: Danil Afanasyev Assisted-by: ClaudeCode:claude-opus-5-5 --- appinfo/routes.php | 1 + lib/Controller/ChattyLLMController.php | 31 ++++++ lib/Db/ChattyLLM/MessageMapper.php | 30 ++++++ lib/Service/ChatService.php | 24 +++++ openapi.json | 143 +++++++++++++++++++++++++ tests/unit/Service/ChatServiceTest.php | 43 +++++++- 6 files changed, 269 insertions(+), 3 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index 3dc4df1d0..34fa878ae 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -52,6 +52,7 @@ ['name' => 'chattyLLM#deleteSession', 'url' => '/chat/delete_session', 'verb' => 'DELETE'], ['name' => 'chattyLLM#deleteSessions', 'url' => '/chat/delete_sessions', 'verb' => 'DELETE'], ['name' => 'chattyLLM#getSessions', 'url' => '/chat/sessions', 'verb' => 'GET'], + ['name' => 'chattyLLM#searchMessages', 'url' => '/chat/search', 'verb' => 'GET'], ['name' => 'chattyLLM#newMessage', 'url' => '/chat/new_message', 'verb' => 'PUT'], ['name' => 'chattyLLM#deleteMessage', 'url' => '/chat/delete_message', 'verb' => 'DELETE'], ['name' => 'chattyLLM#getMessages', 'url' => '/chat/messages', 'verb' => 'GET'], diff --git a/lib/Controller/ChattyLLMController.php b/lib/Controller/ChattyLLMController.php index 0123d80ef..b77967737 100644 --- a/lib/Controller/ChattyLLMController.php +++ b/lib/Controller/ChattyLLMController.php @@ -365,6 +365,37 @@ public function getSessions(?bool $isAssignment = false): JSONResponse { } } + /** + * Search chat messages + * + * Get the messages of the current user's chat sessions matching the search query, most recent first + * + * @param string $query Text to search for in the message contents (case-insensitive) + * @param bool|null $isAssignment Whether to search only the sessions for assignments (true) or not (false) defaults to false + * @return JSONResponse, array{}>|JSONResponse + * + * 200: The matching messages have been obtained successfully + * 400: The search query is empty + * 401: Not logged in + */ + #[NoAdminRequired] + #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT, tags: ['chat_api'])] + public function searchMessages(string $query, ?bool $isAssignment = false): JSONResponse { + try { + $messages = $this->chatService->searchMessages($this->userId, $query, $isAssignment); + return new JSONResponse(array_map(static function (Message $message) { + return $message->jsonSerialize(); + }, $messages)); + } catch (BadRequestException $e) { + return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST); + } catch (InternalException $e) { + $this->logger->warning('Failed to search chat messages', ['exception' => $e]); + return new JSONResponse(['error' => $this->l10n->t('Failed to search chat messages')], Http::STATUS_INTERNAL_SERVER_ERROR); + } catch (\OCA\Assistant\Service\UnauthorizedException $e) { + return new JSONResponse(['error' => $this->l10n->t('User not logged in')], Http::STATUS_UNAUTHORIZED); + } + } + /** * Add a message * diff --git a/lib/Db/ChattyLLM/MessageMapper.php b/lib/Db/ChattyLLM/MessageMapper.php index 5522f9c6f..bf0147239 100644 --- a/lib/Db/ChattyLLM/MessageMapper.php +++ b/lib/Db/ChattyLLM/MessageMapper.php @@ -109,6 +109,36 @@ public function getMessages(int $sessionId, int $cursor, int $limit, string $fil return array_reverse($messages); } + /** + * Find the messages of a user's sessions whose content contains the query (case-insensitive), most recent first + * + * @param string $userId + * @param string $query + * @param bool $isAssignment Whether to search in the sessions for assignments (true) or not (false) + * @param int $limit + * @return list + * @throws \OCP\DB\Exception + */ + public function searchUserMessages(string $userId, string $query, bool $isAssignment, int $limit): array { + $qb = $this->db->getQueryBuilder(); + $qb->select(array_map(static fn (string $column) => 'm.' . $column, Message::$columns)) + ->from($this->getTableName(), 'm') + ->innerJoin('m', 'assistant_chat_sns', 's', $qb->expr()->eq('m.session_id', 's.id')) + ->where($qb->expr()->eq('s.user_id', $qb->createPositionalParameter($userId, IQueryBuilder::PARAM_STR))) + ->andWhere($qb->expr()->iLike('m.content', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($query) . '%', IQueryBuilder::PARAM_STR))) + ->orderBy('m.timestamp', 'DESC') + ->addOrderBy('m.id', 'DESC') + ->setMaxResults($limit); + + if ($isAssignment) { + $qb->andWhere($qb->expr()->isNotNull('s.assignment_id')); + } else { + $qb->andWhere($qb->expr()->isNull('s.assignment_id')); + } + + return $this->findEntities($qb); + } + /** * @param int $sessionId * @param int $maxTimestamp diff --git a/lib/Service/ChatService.php b/lib/Service/ChatService.php index b2be960a3..2a3fec344 100644 --- a/lib/Service/ChatService.php +++ b/lib/Service/ChatService.php @@ -30,6 +30,7 @@ class ChatService { public const EMPTY_CONVERSATION_TOKEN = '{}'; + public const SEARCH_MESSAGE_LIMIT = 200; public function __construct( private readonly IUserManager $userManager, @@ -226,6 +227,29 @@ public function getSessionsForUser(?string $userId, bool $isAssignment): array { } } + /** + * Find the user's messages matching the query, the SEARCH_MESSAGE_LIMIT most recent ones first + * + * @return list + * @throws BadRequestException + * @throws InternalException + * @throws UnauthorizedException + */ + public function searchMessages(?string $userId, string $query, bool $isAssignment): array { + if ($userId === null) { + throw new UnauthorizedException($this->l10n->t('Unauthorized')); + } + $query = trim($query); + if ($query === '') { + throw new BadRequestException($this->l10n->t('The search query is empty')); + } + try { + return $this->messageMapper->searchUserMessages($userId, $query, $isAssignment, self::SEARCH_MESSAGE_LIMIT); + } catch (Exception $e) { + throw new InternalException(previous: $e); + } + } + /** * @throws BadRequestException * @throws InternalException diff --git a/openapi.json b/openapi.json index 1ed2389b1..8a2f11557 100644 --- a/openapi.json +++ b/openapi.json @@ -4878,6 +4878,149 @@ } } }, + "/ocs/v2.php/apps/assistant/chat/search": { + "get": { + "operationId": "chattyllm-search-messages", + "summary": "Search chat messages", + "description": "Get the messages of the current user's chat sessions matching the search query, most recent first", + "tags": [ + "chat_api" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "query", + "in": "query", + "description": "Text to search for in the message contents (case-insensitive)", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "isAssignment", + "in": "query", + "description": "Whether to search only the sessions for assignments (true) or not (false) defaults to false", + "schema": { + "type": "boolean", + "nullable": true, + "default": false + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "The matching messages have been obtained successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChatMessage" + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + } + } + } + } + }, + "401": { + "description": "Not logged in", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + ] + } + } + } + }, + "400": { + "description": "The search query is empty", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/assistant/chat/new_message": { "put": { "operationId": "chattyllm-new-message", diff --git a/tests/unit/Service/ChatServiceTest.php b/tests/unit/Service/ChatServiceTest.php index eb784c6c9..bd173859b 100644 --- a/tests/unit/Service/ChatServiceTest.php +++ b/tests/unit/Service/ChatServiceTest.php @@ -13,6 +13,7 @@ use OCA\Assistant\Db\ChattyLLM\MessageMapper; use OCA\Assistant\Db\ChattyLLM\Session; use OCA\Assistant\Db\ChattyLLM\SessionMapper; +use OCA\Assistant\Service\BadRequestException; use OCA\Assistant\Service\ChatService; use OCA\Assistant\Service\UnauthorizedException; use OCP\IUserManager; @@ -84,12 +85,12 @@ private function createSession(string $userId): Session { return $session; } - private function createMessage(int $sessionId): Message { + private function createMessage(int $sessionId, string $content = 'test message', string $role = Message::ROLE_HUMAN): Message { $message = new Message(); $message->setSessionId($sessionId); - $message->setRole(Message::ROLE_HUMAN); + $message->setRole($role); $message->setAttachments('[]'); - $message->setContent('test message'); + $message->setContent($content); $message->setSources('[]'); $message->setTimestamp(time()); return $this->messageMapper->insert($message); @@ -156,4 +157,40 @@ public function testDeleteSessionsWithoutUser(): void { $this->expectException(UnauthorizedException::class); $this->service->deleteSessions(null, [1, 2]); } + + /** + * @return list + */ + private function searchMessageIds(string $userId, string $query): array { + return array_map(static function (Message $message) { + return $message->getId(); + }, $this->service->searchMessages($userId, $query, false)); + } + + public function testSearchMessages(): void { + $session = $this->createSession(self::TEST_USER); + $humanMessage = $this->createMessage($session->getId(), 'Where did I put my Banana?'); + $this->createMessage($session->getId(), 'apples only'); + $otherSession = $this->createSession(self::TEST_USER); + $assistantMessage = $this->createMessage($otherSession->getId(), 'You put the banana in the fridge', Message::ROLE_ASSISTANT); + $otherUserSession = $this->createSession(self::OTHER_USER); + $this->createMessage($otherUserSession->getId(), 'banana'); + + // case-insensitive, both roles, only the user's own sessions, most recent first + $this->assertSame([$assistantMessage->getId(), $humanMessage->getId()], $this->searchMessageIds(self::TEST_USER, 'BANANA')); + } + + public function testSearchMessagesEscapesWildcards(): void { + $session = $this->createSession(self::TEST_USER); + $literalMessage = $this->createMessage($session->getId(), 'progress is 100% done'); + $this->createMessage($session->getId(), 'progress is 1000 done'); + + $this->assertSame([$literalMessage->getId()], $this->searchMessageIds(self::TEST_USER, '100%')); + $this->assertSame([], $this->searchMessageIds(self::TEST_USER, '1_0%')); + } + + public function testSearchMessagesWithEmptyQuery(): void { + $this->expectException(BadRequestException::class); + $this->service->searchMessages(self::TEST_USER, ' ', false); + } } From 52707692dc2ebd427f0c1ae71f7979f8ea7333e9 Mon Sep 17 00:00:00 2001 From: Danil Afanasyev Date: Sun, 25 Oct 2026 16:10:20 +0100 Subject: [PATCH 2/4] refactor(chat): put the message search behind an interface Signed-off-by: Danil Afanasyev Assisted-by: ClaudeCode:claude-opus-5-5 --- lib/AppInfo/Application.php | 4 +++ .../ChatSearch/DatabaseChatMessageSearch.php | 32 +++++++++++++++++++ lib/Service/ChatSearch/IChatMessageSearch.php | 30 +++++++++++++++++ lib/Service/ChatService.php | 10 +++--- tests/unit/Service/ChatServiceAgencyTest.php | 2 ++ 5 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 lib/Service/ChatSearch/DatabaseChatMessageSearch.php create mode 100644 lib/Service/ChatSearch/IChatMessageSearch.php diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 228b846b8..dc7ac1c2d 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -30,6 +30,8 @@ use OCA\Assistant\Reference\TaskOutputFileReferenceProvider; use OCA\Assistant\Reference\Text2ImageReferenceProvider; use OCA\Assistant\Reference\Text2StickerProvider; +use OCA\Assistant\Service\ChatSearch\DatabaseChatMessageSearch; +use OCA\Assistant\Service\ChatSearch\IChatMessageSearch; use OCA\Assistant\TaskProcessing\AudioToAudioChatProvider; use OCA\Assistant\TaskProcessing\AudioToAudioTranslateProvider; use OCA\Assistant\TaskProcessing\ContextAgentAudioInteractionProvider; @@ -108,6 +110,8 @@ public function register(IRegistrationContext $context): void { $context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class); + $context->registerServiceAlias(IChatMessageSearch::class, DatabaseChatMessageSearch::class); + if (class_exists('OCP\\TaskProcessing\\TaskTypes\\AudioToAudioChat')) { $context->registerTaskProcessingProvider(AudioToAudioChatProvider::class); } diff --git a/lib/Service/ChatSearch/DatabaseChatMessageSearch.php b/lib/Service/ChatSearch/DatabaseChatMessageSearch.php new file mode 100644 index 000000000..db2371b06 --- /dev/null +++ b/lib/Service/ChatSearch/DatabaseChatMessageSearch.php @@ -0,0 +1,32 @@ +messageMapper->searchUserMessages($userId, $query, $isAssignment, $limit); + } catch (Exception $e) { + throw new InternalException(previous: $e); + } + } +} diff --git a/lib/Service/ChatSearch/IChatMessageSearch.php b/lib/Service/ChatSearch/IChatMessageSearch.php new file mode 100644 index 000000000..87429e940 --- /dev/null +++ b/lib/Service/ChatSearch/IChatMessageSearch.php @@ -0,0 +1,30 @@ + + * @throws InternalException + */ + public function search(string $userId, string $query, bool $isAssignment, int $limit): array; +} diff --git a/lib/Service/ChatService.php b/lib/Service/ChatService.php index 2a3fec344..1c6086223 100644 --- a/lib/Service/ChatService.php +++ b/lib/Service/ChatService.php @@ -12,6 +12,7 @@ use OCA\Assistant\Db\ChattyLLM\MessageMapper; use OCA\Assistant\Db\ChattyLLM\Session; use OCA\Assistant\Db\ChattyLLM\SessionMapper; +use OCA\Assistant\Service\ChatSearch\IChatMessageSearch; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\AppFramework\Utility\ITimeFactory; @@ -43,6 +44,7 @@ public function __construct( private readonly IManager $taskProcessingManager, private readonly LoggerInterface $logger, private readonly ITimeFactory $timeFactory, + private readonly IChatMessageSearch $chatMessageSearch, ) { } @@ -228,7 +230,7 @@ public function getSessionsForUser(?string $userId, bool $isAssignment): array { } /** - * Find the user's messages matching the query, the SEARCH_MESSAGE_LIMIT most recent ones first + * Find the user's messages matching the query, at most SEARCH_MESSAGE_LIMIT of them * * @return list * @throws BadRequestException @@ -243,11 +245,7 @@ public function searchMessages(?string $userId, string $query, bool $isAssignmen if ($query === '') { throw new BadRequestException($this->l10n->t('The search query is empty')); } - try { - return $this->messageMapper->searchUserMessages($userId, $query, $isAssignment, self::SEARCH_MESSAGE_LIMIT); - } catch (Exception $e) { - throw new InternalException(previous: $e); - } + return $this->chatMessageSearch->search($userId, $query, $isAssignment, self::SEARCH_MESSAGE_LIMIT); } /** diff --git a/tests/unit/Service/ChatServiceAgencyTest.php b/tests/unit/Service/ChatServiceAgencyTest.php index ea5cfab99..126db9817 100644 --- a/tests/unit/Service/ChatServiceAgencyTest.php +++ b/tests/unit/Service/ChatServiceAgencyTest.php @@ -13,6 +13,7 @@ use OCA\Assistant\Db\ChattyLLM\MessageMapper; use OCA\Assistant\Db\ChattyLLM\Session; use OCA\Assistant\Db\ChattyLLM\SessionMapper; +use OCA\Assistant\Service\ChatSearch\IChatMessageSearch; use OCA\Assistant\Service\ChatService; use OCA\Assistant\Service\SessionSummaryService; use OCP\AppFramework\Db\DoesNotExistException; @@ -79,6 +80,7 @@ protected function setUp(): void { $this->taskProcessingManager, $this->createMock(LoggerInterface::class), $this->createMock(ITimeFactory::class), + $this->createMock(IChatMessageSearch::class), ); } From 04039b09bafd4b765e730c7aec4728e17ef874db Mon Sep 17 00:00:00 2001 From: Danil Afanasyev Date: Wed, 28 Oct 2026 08:13:54 +0100 Subject: [PATCH 3/4] Revert "refactor(chat): put the message search behind an interface" This reverts commit 52707692dc2ebd427f0c1ae71f7979f8ea7333e9. --- lib/AppInfo/Application.php | 4 --- .../ChatSearch/DatabaseChatMessageSearch.php | 32 ------------------- lib/Service/ChatSearch/IChatMessageSearch.php | 30 ----------------- lib/Service/ChatService.php | 10 +++--- tests/unit/Service/ChatServiceAgencyTest.php | 2 -- 5 files changed, 6 insertions(+), 72 deletions(-) delete mode 100644 lib/Service/ChatSearch/DatabaseChatMessageSearch.php delete mode 100644 lib/Service/ChatSearch/IChatMessageSearch.php diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index dc7ac1c2d..228b846b8 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -30,8 +30,6 @@ use OCA\Assistant\Reference\TaskOutputFileReferenceProvider; use OCA\Assistant\Reference\Text2ImageReferenceProvider; use OCA\Assistant\Reference\Text2StickerProvider; -use OCA\Assistant\Service\ChatSearch\DatabaseChatMessageSearch; -use OCA\Assistant\Service\ChatSearch\IChatMessageSearch; use OCA\Assistant\TaskProcessing\AudioToAudioChatProvider; use OCA\Assistant\TaskProcessing\AudioToAudioTranslateProvider; use OCA\Assistant\TaskProcessing\ContextAgentAudioInteractionProvider; @@ -110,8 +108,6 @@ public function register(IRegistrationContext $context): void { $context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class); - $context->registerServiceAlias(IChatMessageSearch::class, DatabaseChatMessageSearch::class); - if (class_exists('OCP\\TaskProcessing\\TaskTypes\\AudioToAudioChat')) { $context->registerTaskProcessingProvider(AudioToAudioChatProvider::class); } diff --git a/lib/Service/ChatSearch/DatabaseChatMessageSearch.php b/lib/Service/ChatSearch/DatabaseChatMessageSearch.php deleted file mode 100644 index db2371b06..000000000 --- a/lib/Service/ChatSearch/DatabaseChatMessageSearch.php +++ /dev/null @@ -1,32 +0,0 @@ -messageMapper->searchUserMessages($userId, $query, $isAssignment, $limit); - } catch (Exception $e) { - throw new InternalException(previous: $e); - } - } -} diff --git a/lib/Service/ChatSearch/IChatMessageSearch.php b/lib/Service/ChatSearch/IChatMessageSearch.php deleted file mode 100644 index 87429e940..000000000 --- a/lib/Service/ChatSearch/IChatMessageSearch.php +++ /dev/null @@ -1,30 +0,0 @@ - - * @throws InternalException - */ - public function search(string $userId, string $query, bool $isAssignment, int $limit): array; -} diff --git a/lib/Service/ChatService.php b/lib/Service/ChatService.php index 1c6086223..2a3fec344 100644 --- a/lib/Service/ChatService.php +++ b/lib/Service/ChatService.php @@ -12,7 +12,6 @@ use OCA\Assistant\Db\ChattyLLM\MessageMapper; use OCA\Assistant\Db\ChattyLLM\Session; use OCA\Assistant\Db\ChattyLLM\SessionMapper; -use OCA\Assistant\Service\ChatSearch\IChatMessageSearch; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\AppFramework\Utility\ITimeFactory; @@ -44,7 +43,6 @@ public function __construct( private readonly IManager $taskProcessingManager, private readonly LoggerInterface $logger, private readonly ITimeFactory $timeFactory, - private readonly IChatMessageSearch $chatMessageSearch, ) { } @@ -230,7 +228,7 @@ public function getSessionsForUser(?string $userId, bool $isAssignment): array { } /** - * Find the user's messages matching the query, at most SEARCH_MESSAGE_LIMIT of them + * Find the user's messages matching the query, the SEARCH_MESSAGE_LIMIT most recent ones first * * @return list * @throws BadRequestException @@ -245,7 +243,11 @@ public function searchMessages(?string $userId, string $query, bool $isAssignmen if ($query === '') { throw new BadRequestException($this->l10n->t('The search query is empty')); } - return $this->chatMessageSearch->search($userId, $query, $isAssignment, self::SEARCH_MESSAGE_LIMIT); + try { + return $this->messageMapper->searchUserMessages($userId, $query, $isAssignment, self::SEARCH_MESSAGE_LIMIT); + } catch (Exception $e) { + throw new InternalException(previous: $e); + } } /** diff --git a/tests/unit/Service/ChatServiceAgencyTest.php b/tests/unit/Service/ChatServiceAgencyTest.php index 126db9817..ea5cfab99 100644 --- a/tests/unit/Service/ChatServiceAgencyTest.php +++ b/tests/unit/Service/ChatServiceAgencyTest.php @@ -13,7 +13,6 @@ use OCA\Assistant\Db\ChattyLLM\MessageMapper; use OCA\Assistant\Db\ChattyLLM\Session; use OCA\Assistant\Db\ChattyLLM\SessionMapper; -use OCA\Assistant\Service\ChatSearch\IChatMessageSearch; use OCA\Assistant\Service\ChatService; use OCA\Assistant\Service\SessionSummaryService; use OCP\AppFramework\Db\DoesNotExistException; @@ -80,7 +79,6 @@ protected function setUp(): void { $this->taskProcessingManager, $this->createMock(LoggerInterface::class), $this->createMock(ITimeFactory::class), - $this->createMock(IChatMessageSearch::class), ); } From 4971b4305e325a3733f656fd49750883f34157e5 Mon Sep 17 00:00:00 2001 From: Danil Afanasyev Date: Wed, 28 Oct 2026 08:39:02 +0100 Subject: [PATCH 4/4] feat(chat): search chat messages from the sidebar - frontend (#710) Signed-off-by: Danil Afanasyev Assisted-by: ClaudeCode:claude-opus-5-5 --- .../ChattyLLM/ChattyLLMInputForm.vue | 171 +++++++++++++++++- 1 file changed, 164 insertions(+), 7 deletions(-) diff --git a/src/components/ChattyLLM/ChattyLLMInputForm.vue b/src/components/ChattyLLM/ChattyLLMInputForm.vue index 12f374801..c629808ac 100644 --- a/src/components/ChattyLLM/ChattyLLMInputForm.vue +++ b/src/components/ChattyLLM/ChattyLLMInputForm.vue @@ -5,6 +5,10 @@ - @@ -52,11 +56,18 @@ {{ isAssignment ? t('assistant', 'Loading scheduled tasks…') : t('assistant', 'Loading conversations…') }} -
+
+ + {{ t('assistant', 'Searching messages…') }} +
+
+ {{ isAssignment ? t('assistant', 'No scheduled tasks with matching messages') : t('assistant', 'No conversations with matching messages') }} +
+
{{ isAssignment ? t('assistant', 'No scheduled tasks yet') : t('assistant', 'No conversations yet') }}
+