Skip to content

Commit db2a3cf

Browse files
Text-Processing APIs implementation (#191)
Signed-off-by: Andrey Borysenko <[email protected]> Co-authored-by: Alexander Piskun <[email protected]>
1 parent a8863df commit db2a3cf

13 files changed

+626
-1
lines changed

appinfo/routes.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,5 +105,10 @@
105105
['name' => 'speechToText#registerProvider', 'url' => '/api/v1/ai_provider/speech_to_text', 'verb' => 'POST'],
106106
['name' => 'speechToText#unregisterProvider', 'url' => '/api/v1/ai_provider/speech_to_text', 'verb' => 'DELETE'],
107107
['name' => 'speechToText#getProvider', 'url' => '/api/v1/ai_provider/speech_to_text', 'verb' => 'GET'],
108+
109+
// Text-Processing
110+
['name' => 'textProcessing#registerProvider', 'url' => '/api/v1/ai_provider/text_processing', 'verb' => 'POST'],
111+
['name' => 'textProcessing#unregisterProvider', 'url' => '/api/v1/ai_provider/text_processing', 'verb' => 'DELETE'],
112+
['name' => 'textProcessing#getProvider', 'url' => '/api/v1/ai_provider/text_processing', 'verb' => 'GET'],
108113
],
109114
];

docs/tech_details/api/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ AppAPI Nextcloud APIs
1919
notifications
2020
talkbots
2121
speechtotext
22+
textprocessing
2223
other_ocs

docs/tech_details/api/speechtotext.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ Speech-To-Text
44

55
AppAPI provides a Speech-To-Text (STT) provider registration API for the ExApps.
66

7+
.. note::
8+
9+
Available since Nextcloud 29.
10+
711
Registering ExApp STT provider (OCS)
812
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
913

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
===============
2+
Text-Processing
3+
===============
4+
5+
AppAPI provides a Text-Processing providers registration mechanism for ExApps.
6+
7+
.. note::
8+
9+
Available since Nextcloud 29.
10+
11+
Registering text-processing provider (OCS)
12+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
13+
14+
OCS endpoint: ``POST /apps/app_api/api/v1/ai_provider/text_processing``
15+
16+
Request data
17+
************
18+
19+
.. code-block:: json
20+
21+
{
22+
"name": "unique_provider_name",
23+
"display_name": "Provider Display Name",
24+
"action_handler": "/handler_route_on_ex_app",
25+
"task_type": "supported_task_type",
26+
}
27+
28+
.. note::
29+
30+
``action_type`` is a class name of the Text-Processing task type that can be found in the list of supported task types.
31+
32+
Response
33+
********
34+
35+
On successful registration response with status code 200 is returned.
36+
37+
Unregistering text-processing provider (OCS)
38+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
39+
40+
OCS endpoint: ``DELETE /apps/app_api/api/v1/ai_provider/text_processing``
41+
42+
Request data
43+
************
44+
45+
.. code-block:: json
46+
47+
{
48+
"name": "unique_provider_name",
49+
}
50+
51+
Response
52+
********
53+
54+
On successful unregister response with status code 200 is returned.
55+
56+
57+
Get list of supported Text-Processing task types (capabilities)
58+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
59+
60+
There are limited number of Task Types that can be used for Text-Processing.
61+
You can get list of supported Text-Processing task types from OCS capabilities.
62+
63+
Response
64+
********
65+
66+
.. code-block:: json
67+
68+
{
69+
"text_processing": {
70+
"task_types": ["free_prompt", "headline", "summary", "topics"]
71+
}
72+
}

lib/AppInfo/Application.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use OCA\AppAPI\Profiler\AppAPIDataCollector;
1717
use OCA\AppAPI\PublicCapabilities;
1818
use OCA\AppAPI\Service\SpeechToTextService;
19+
use OCA\AppAPI\Service\TextProcessingService;
1920
use OCA\AppAPI\Service\UI\TopMenuService;
2021
use OCA\DAV\Events\SabrePluginAuthInitEvent;
2122
use OCA\Files\Event\LoadAdditionalScriptsEvent;
@@ -66,6 +67,10 @@ public function register(IRegistrationContext $context): void {
6667
/** @var SpeechToTextService $speechToTextService */
6768
$speechToTextService = $container->get(SpeechToTextService::class);
6869
$speechToTextService->registerExAppSpeechToTextProviders($context, $container->getServer());
70+
71+
/** @var TextProcessingService $textProcessingService */
72+
$textProcessingService = $container->get(TextProcessingService::class);
73+
$textProcessingService->registerExAppTextProcessingProviders($context, $container->getServer());
6974
} catch (NotFoundExceptionInterface|ContainerExceptionInterface) {
7075
}
7176
}

lib/Capabilities.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use OCA\AppAPI\Service\AppAPIService;
1010
use OCA\AppAPI\Service\ExAppScopesService;
1111

12+
use OCA\AppAPI\Service\TextProcessingService;
1213
use OCP\App\IAppManager;
1314
use OCP\Capabilities\ICapability;
1415
use OCP\IConfig;
@@ -29,6 +30,9 @@ public function getCapabilities(): array {
2930
$capabilities = [
3031
'loglevel' => intval($this->config->getSystemValue('loglevel', 2)),
3132
'version' => $this->appManager->getAppVersion(Application::APP_ID),
33+
'text_processing' => [
34+
'task_types' => array_keys(TextProcessingService::TASK_TYPES),
35+
]
3236
];
3337
$this->attachExAppScopes($capabilities);
3438
return [
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OCA\AppAPI\Controller;
6+
7+
use OCA\AppAPI\AppInfo\Application;
8+
use OCA\AppAPI\Attribute\AppAPIAuth;
9+
use OCA\AppAPI\Service\TextProcessingService;
10+
use OCP\AppFramework\Http;
11+
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
12+
use OCP\AppFramework\Http\Attribute\PublicPage;
13+
use OCP\AppFramework\Http\DataResponse;
14+
use OCP\AppFramework\Http\Response;
15+
use OCP\AppFramework\OCSController;
16+
use OCP\IConfig;
17+
use OCP\IRequest;
18+
19+
class TextProcessingController extends OCSController {
20+
protected $request;
21+
22+
public function __construct(
23+
IRequest $request,
24+
private readonly IConfig $config,
25+
private readonly TextProcessingService $textProcessingService,
26+
) {
27+
parent::__construct(Application::APP_ID, $request);
28+
29+
$this->request = $request;
30+
}
31+
32+
#[NoCSRFRequired]
33+
#[PublicPage]
34+
#[AppAPIAuth]
35+
public function registerProvider(
36+
string $name,
37+
string $displayName,
38+
string $actionHandler,
39+
string $taskType
40+
): DataResponse {
41+
$ncVersion = $this->config->getSystemValueString('version', '0.0.0');
42+
if (version_compare($ncVersion, '29.0', '<')) {
43+
return new DataResponse([], Http::STATUS_NOT_IMPLEMENTED);
44+
}
45+
46+
$provider = $this->textProcessingService->registerTextProcessingProvider(
47+
$this->request->getHeader('EX-APP-ID'), $name, $displayName, $actionHandler, $taskType,
48+
);
49+
50+
if ($provider === null) {
51+
return new DataResponse([], Http::STATUS_BAD_REQUEST);
52+
}
53+
54+
return new DataResponse();
55+
}
56+
57+
#[NoCSRFRequired]
58+
#[PublicPage]
59+
#[AppAPIAuth]
60+
public function unregisterProvider(string $name): Response {
61+
$ncVersion = $this->config->getSystemValueString('version', '0.0.0');
62+
if (version_compare($ncVersion, '29.0', '<')) {
63+
return new DataResponse([], Http::STATUS_NOT_IMPLEMENTED);
64+
}
65+
66+
$unregistered = $this->textProcessingService->unregisterTextProcessingProvider(
67+
$this->request->getHeader('EX-APP-ID'), $name
68+
);
69+
70+
if ($unregistered === null) {
71+
return new DataResponse([], Http::STATUS_NOT_FOUND);
72+
}
73+
74+
return new DataResponse();
75+
}
76+
77+
#[NoCSRFRequired]
78+
#[PublicPage]
79+
#[AppAPIAuth]
80+
public function getProvider(string $name): DataResponse {
81+
$ncVersion = $this->config->getSystemValueString('version', '0.0.0');
82+
if (version_compare($ncVersion, '29.0', '<')) {
83+
return new DataResponse([], Http::STATUS_NOT_IMPLEMENTED);
84+
}
85+
$result = $this->textProcessingService->getExAppTextProcessingProvider(
86+
$this->request->getHeader('EX-APP-ID'), $name
87+
);
88+
if (!$result) {
89+
return new DataResponse([], Http::STATUS_NOT_FOUND);
90+
}
91+
return new DataResponse($result, Http::STATUS_OK);
92+
}
93+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OCA\AppAPI\Db\TextProcessing;
6+
7+
use OCP\AppFramework\Db\Entity;
8+
9+
/**
10+
* Class TextProcessingProvider
11+
*
12+
* @package OCA\AppAPI\Db\TextProcessing
13+
*
14+
* @method string getAppid()
15+
* @method string getName()
16+
* @method string getDisplayName()
17+
* @method string getActionHandler()
18+
* @method string getTaskType()
19+
* @method void setAppid(string $appid)
20+
* @method void setName(string $name)
21+
* @method void setDisplayName(string $displayName)
22+
* @method void setActionHandler(string $actionHandler)
23+
* @method void setTaskType(string $taskType)
24+
*/
25+
class TextProcessingProvider extends Entity implements \JsonSerializable {
26+
protected $appid;
27+
protected $name;
28+
protected $displayName;
29+
protected $actionHandler;
30+
protected $taskType;
31+
32+
public function __construct(array $params = []) {
33+
$this->addType('appid', 'string');
34+
$this->addType('name', 'string');
35+
$this->addType('displayName', 'string');
36+
$this->addType('actionHandler', 'string');
37+
$this->addType('taskType', 'string');
38+
39+
if (isset($params['id'])) {
40+
$this->setId($params['id']);
41+
}
42+
if (isset($params['appid'])) {
43+
$this->setAppid($params['appid']);
44+
}
45+
if (isset($params['name'])) {
46+
$this->setName($params['name']);
47+
}
48+
if (isset($params['display_name'])) {
49+
$this->setDisplayName($params['display_name']);
50+
}
51+
if (isset($params['action_handler'])) {
52+
$this->setActionHandler($params['action_handler']);
53+
}
54+
if (isset($params['task_type'])) {
55+
$this->setTaskType($params['task_type']);
56+
}
57+
}
58+
59+
public function jsonSerialize(): array {
60+
return [
61+
'id' => $this->getId(),
62+
'appid' => $this->getAppid(),
63+
'name' => $this->getName(),
64+
'display_name' => $this->getDisplayName(),
65+
'action_handler' => $this->getActionHandler(),
66+
'task_type' => $this->getTaskType(),
67+
];
68+
}
69+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OCA\AppAPI\Db\TextProcessing;
6+
7+
use OCP\AppFramework\Db\DoesNotExistException;
8+
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
9+
use OCP\AppFramework\Db\QBMapper;
10+
use OCP\DB\Exception;
11+
use OCP\DB\QueryBuilder\IQueryBuilder;
12+
use OCP\IDBConnection;
13+
14+
/**
15+
* @template-extends QBMapper<TextProcessingProvider>
16+
*/
17+
class TextProcessingProviderMapper extends QBMapper {
18+
public function __construct(IDBConnection $db) {
19+
parent::__construct($db, 'ex_text_processing');
20+
}
21+
22+
/**
23+
* @throws Exception
24+
*/
25+
public function findAllEnabled(): array {
26+
$qb = $this->db->getQueryBuilder();
27+
$result = $qb->select(
28+
'ex_text_processing.appid',
29+
'ex_text_processing.name',
30+
'ex_text_processing.display_name',
31+
'ex_text_processing.action_handler',
32+
'ex_text_processing.task_type',
33+
)
34+
->from($this->tableName, 'ex_text_processing')
35+
->innerJoin('ex_text_processing', 'ex_apps', 'exa', 'exa.appid = ex_text_processing.appid')
36+
->where(
37+
$qb->expr()->eq('exa.enabled', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT))
38+
)->executeQuery();
39+
return $result->fetchAll();
40+
}
41+
42+
/**
43+
* @param string $appId
44+
* @param string $name
45+
*
46+
* @throws DoesNotExistException
47+
* @throws Exception
48+
* @throws MultipleObjectsReturnedException
49+
*
50+
* @return TextProcessingProvider
51+
*/
52+
public function findByAppidName(string $appId, string $name): TextProcessingProvider {
53+
$qb = $this->db->getQueryBuilder();
54+
return $this->findEntity($qb->select('*')
55+
->from($this->tableName)
56+
->where($qb->expr()->eq('appid', $qb->createNamedParameter($appId), IQueryBuilder::PARAM_STR))
57+
->andWhere($qb->expr()->eq('name', $qb->createNamedParameter($name), IQueryBuilder::PARAM_STR))
58+
);
59+
}
60+
61+
/**
62+
* @throws Exception
63+
*/
64+
public function removeAllByAppId(string $appId): int {
65+
$qb = $this->db->getQueryBuilder();
66+
$qb->delete($this->tableName)
67+
->where(
68+
$qb->expr()->eq('appid', $qb->createNamedParameter($appId, IQueryBuilder::PARAM_STR))
69+
);
70+
return $qb->executeStatement();
71+
}
72+
}

0 commit comments

Comments
 (0)