You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

215 lines
7.2 KiB

  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OC\Core\Controller;
  8. use InvalidArgumentException;
  9. use OC\Core\ResponseDefinitions;
  10. use OCP\AppFramework\Http;
  11. use OCP\AppFramework\Http\Attribute\AnonRateLimit;
  12. use OCP\AppFramework\Http\Attribute\ApiRoute;
  13. use OCP\AppFramework\Http\Attribute\NoAdminRequired;
  14. use OCP\AppFramework\Http\Attribute\PublicPage;
  15. use OCP\AppFramework\Http\Attribute\UserRateLimit;
  16. use OCP\AppFramework\Http\DataResponse;
  17. use OCP\AppFramework\OCSController;
  18. use OCP\Common\Exception\NotFoundException;
  19. use OCP\DB\Exception;
  20. use OCP\IL10N;
  21. use OCP\IRequest;
  22. use OCP\PreConditionNotMetException;
  23. use OCP\TextProcessing\Exception\TaskFailureException;
  24. use OCP\TextProcessing\IManager;
  25. use OCP\TextProcessing\ITaskType;
  26. use OCP\TextProcessing\Task;
  27. use Psr\Container\ContainerExceptionInterface;
  28. use Psr\Container\ContainerInterface;
  29. use Psr\Container\NotFoundExceptionInterface;
  30. use Psr\Log\LoggerInterface;
  31. /**
  32. * @psalm-import-type CoreTextProcessingTask from ResponseDefinitions
  33. */
  34. class TextProcessingApiController extends OCSController {
  35. public function __construct(
  36. string $appName,
  37. IRequest $request,
  38. private IManager $textProcessingManager,
  39. private IL10N $l,
  40. private ?string $userId,
  41. private ContainerInterface $container,
  42. private LoggerInterface $logger,
  43. ) {
  44. parent::__construct($appName, $request);
  45. }
  46. /**
  47. * This endpoint returns all available LanguageModel task types
  48. *
  49. * @return DataResponse<Http::STATUS_OK, array{types: list<array{id: string, name: string, description: string}>}, array{}>
  50. *
  51. * 200: Task types returned
  52. */
  53. #[PublicPage]
  54. #[ApiRoute(verb: 'GET', url: '/tasktypes', root: '/textprocessing')]
  55. public function taskTypes(): DataResponse {
  56. $typeClasses = $this->textProcessingManager->getAvailableTaskTypes();
  57. $types = [];
  58. /** @var string $typeClass */
  59. foreach ($typeClasses as $typeClass) {
  60. try {
  61. /** @var ITaskType $object */
  62. $object = $this->container->get($typeClass);
  63. } catch (NotFoundExceptionInterface|ContainerExceptionInterface $e) {
  64. $this->logger->warning('Could not find ' . $typeClass, ['exception' => $e]);
  65. continue;
  66. }
  67. $types[] = [
  68. 'id' => $typeClass,
  69. 'name' => $object->getName(),
  70. 'description' => $object->getDescription(),
  71. ];
  72. }
  73. return new DataResponse([
  74. 'types' => $types,
  75. ]);
  76. }
  77. /**
  78. * This endpoint allows scheduling a language model task
  79. *
  80. * @param string $input Input text
  81. * @param string $type Type of the task
  82. * @param string $appId ID of the app that will execute the task
  83. * @param string $identifier An arbitrary identifier for the task
  84. *
  85. * @return DataResponse<Http::STATUS_OK, array{task: CoreTextProcessingTask}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_BAD_REQUEST|Http::STATUS_PRECONDITION_FAILED, array{message: string}, array{}>
  86. *
  87. * 200: Task scheduled successfully
  88. * 400: Scheduling task is not possible
  89. * 412: Scheduling task is not possible
  90. */
  91. #[PublicPage]
  92. #[UserRateLimit(limit: 20, period: 120)]
  93. #[AnonRateLimit(limit: 5, period: 120)]
  94. #[ApiRoute(verb: 'POST', url: '/schedule', root: '/textprocessing')]
  95. public function schedule(string $input, string $type, string $appId, string $identifier = ''): DataResponse {
  96. try {
  97. $task = new Task($type, $input, $appId, $this->userId, $identifier);
  98. } catch (InvalidArgumentException) {
  99. return new DataResponse(['message' => $this->l->t('Requested task type does not exist')], Http::STATUS_BAD_REQUEST);
  100. }
  101. try {
  102. try {
  103. $this->textProcessingManager->runOrScheduleTask($task);
  104. } catch (TaskFailureException) {
  105. // noop, because the task object has the failure status set already, we just return the task json
  106. }
  107. $json = $task->jsonSerialize();
  108. return new DataResponse([
  109. 'task' => $json,
  110. ]);
  111. } catch (PreConditionNotMetException) {
  112. return new DataResponse(['message' => $this->l->t('Necessary language model provider is not available')], Http::STATUS_PRECONDITION_FAILED);
  113. } catch (Exception) {
  114. return new DataResponse(['message' => 'Internal server error'], Http::STATUS_INTERNAL_SERVER_ERROR);
  115. }
  116. }
  117. /**
  118. * This endpoint allows checking the status and results of a task.
  119. * Tasks are removed 1 week after receiving their last update.
  120. *
  121. * @param int $id The id of the task
  122. *
  123. * @return DataResponse<Http::STATUS_OK, array{task: CoreTextProcessingTask}, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
  124. *
  125. * 200: Task returned
  126. * 404: Task not found
  127. */
  128. #[PublicPage]
  129. #[ApiRoute(verb: 'GET', url: '/task/{id}', root: '/textprocessing')]
  130. public function getTask(int $id): DataResponse {
  131. try {
  132. $task = $this->textProcessingManager->getUserTask($id, $this->userId);
  133. $json = $task->jsonSerialize();
  134. return new DataResponse([
  135. 'task' => $json,
  136. ]);
  137. } catch (NotFoundException $e) {
  138. return new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND);
  139. } catch (\RuntimeException $e) {
  140. return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
  141. }
  142. }
  143. /**
  144. * This endpoint allows to delete a scheduled task for a user
  145. *
  146. * @param int $id The id of the task
  147. *
  148. * @return DataResponse<Http::STATUS_OK, array{task: CoreTextProcessingTask}, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
  149. *
  150. * 200: Task returned
  151. * 404: Task not found
  152. */
  153. #[NoAdminRequired]
  154. #[ApiRoute(verb: 'DELETE', url: '/task/{id}', root: '/textprocessing')]
  155. public function deleteTask(int $id): DataResponse {
  156. try {
  157. $task = $this->textProcessingManager->getUserTask($id, $this->userId);
  158. $this->textProcessingManager->deleteTask($task);
  159. $json = $task->jsonSerialize();
  160. return new DataResponse([
  161. 'task' => $json,
  162. ]);
  163. } catch (NotFoundException $e) {
  164. return new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND);
  165. } catch (\RuntimeException $e) {
  166. return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
  167. }
  168. }
  169. /**
  170. * This endpoint returns a list of tasks of a user that are related
  171. * with a specific appId and optionally with an identifier
  172. *
  173. * @param string $appId ID of the app
  174. * @param string|null $identifier An arbitrary identifier for the task
  175. * @return DataResponse<Http::STATUS_OK, array{tasks: list<CoreTextProcessingTask>}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
  176. *
  177. * 200: Task list returned
  178. */
  179. #[NoAdminRequired]
  180. #[ApiRoute(verb: 'GET', url: '/tasks/app/{appId}', root: '/textprocessing')]
  181. public function listTasksByApp(string $appId, ?string $identifier = null): DataResponse {
  182. try {
  183. $tasks = $this->textProcessingManager->getUserTasksByApp($this->userId, $appId, $identifier);
  184. $json = array_values(array_map(static function (Task $task) {
  185. return $task->jsonSerialize();
  186. }, $tasks));
  187. return new DataResponse([
  188. 'tasks' => $json,
  189. ]);
  190. } catch (\RuntimeException $e) {
  191. return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
  192. }
  193. }
  194. }