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
2 changes: 1 addition & 1 deletion REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ SPDX-FileCopyrightText = "2024 Nextcloud GmbH and Nextcloud contributors"
SPDX-License-Identifier = "AGPL-3.0-or-later"

[[annotations]]
path = ["img/app.svg", "img/app-dark.svg"]
path = ["img/app.svg", "img/app-dark.svg", "img/client_integration/speech_to_text.svg", "img/client_integration/summarize.svg", "img/client_integration/text_to_speech.svg"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2018-2024 Google LLC"
SPDX-License-Identifier = "Apache-2.0"
1 change: 1 addition & 0 deletions img/client_integration/speech_to_text.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions img/client_integration/summarize.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions img/client_integration/text_to_speech.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
119 changes: 111 additions & 8 deletions lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,21 @@
use OCP\Capabilities\IPublicCapability;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\TaskProcessing\IManager;
use OCP\TaskProcessing\TaskTypes\AudioToText;
use OCP\TaskProcessing\TaskTypes\TextToTextSummary;

class Capabilities implements IPublicCapability {

public function __construct(
private IAppManager $appManager,
private IConfig $config,
private IAppConfig $appConfig,
private IManager $taskProcessingManager,
private IL10N $l,
private IUrlGenerator $urlGenerator,
private ?string $userId,
) {
}
Expand All @@ -30,22 +38,117 @@ public function __construct(
* assistant: array{
* version: string,
* enabled?: bool
* }
* },
* client_integration?: array<string, array{
* version: float,
* context-menu: list<array{
* name: string,
* url: string,
* method: string,
* mimetype_filters: string,
* icon: string,
* }>
* }>,
* }
*/
public function getCapabilities(): array {
// App version
$appVersion = $this->appManager->getAppVersion(Application::APP_ID);
$capability = [
$capabilities = [
Application::APP_ID => [
'version' => $appVersion,
],
];
if ($this->userId !== null) {
$adminAssistantEnabled = $this->appConfig->getValueString(Application::APP_ID, 'assistant_enabled', '1') === '1';
$userAssistantEnabled = $this->config->getUserValue($this->userId, Application::APP_ID, 'assistant_enabled', '1') === '1';
$assistantEnabled = $adminAssistantEnabled && $userAssistantEnabled;
$capability[Application::APP_ID]['enabled'] = $assistantEnabled;
if ($this->userId === null) {
return $capabilities;
}
return $capability;

$adminAssistantEnabled = $this->appConfig->getValueString(Application::APP_ID, 'assistant_enabled', '1') === '1';
$userAssistantEnabled = $this->config->getUserValue($this->userId, Application::APP_ID, 'assistant_enabled', '1') === '1';
$assistantEnabled = $adminAssistantEnabled && $userAssistantEnabled;
$capabilities[Application::APP_ID]['enabled'] = $assistantEnabled;

// client integration UI
$availableTaskTypes = $this->taskProcessingManager->getAvailableTaskTypes();
$summarizeAvailable = array_key_exists(TextToTextSummary::ID, $availableTaskTypes);
$sttAvailable = array_key_exists(AudioToText::ID, $availableTaskTypes);
$ttsAvailable = false;
if (class_exists('OCP\\TaskProcessing\\TaskTypes\\TextToSpeech')) {
if (array_key_exists(\OCP\TaskProcessing\TaskTypes\TextToSpeech::ID, $availableTaskTypes)) {
$ttsAvailable = true;
}
}

if ($summarizeAvailable || $sttAvailable || $ttsAvailable) {
$capabilities['client_integration'] = [
Application::APP_ID => [
'version' => 0.1,
'context-menu' => [],
],
];

$textMimeTypes = [
'text/',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.oasis.opendocument.text',
'application/pdf',
];
if ($summarizeAvailable) {
$url = $this->urlGenerator->linkToOCSRouteAbsolute(Application::APP_ID . '.assistantApi.runFileAction', [
'apiVersion' => 'v1',
'fileId' => '123456789',
'taskTypeId' => TextToTextSummary::ID,
]);
$url = str_replace($this->urlGenerator->getBaseUrl(), '', $url);
$url = str_replace('123456789', '{fileId}', $url);
$endpoint = [
'name' => $this->l->t('Summarize'),
'url' => $url,
'method' => 'POST',
'mimetype_filters' => implode(', ', $textMimeTypes),
'icon' => $this->urlGenerator->imagePath(Application::APP_ID, 'client_integration/summarize.svg'),
];
$capabilities['client_integration'][Application::APP_ID]['context-menu'][] = $endpoint;
}

if ($sttAvailable) {
$url = $this->urlGenerator->linkToOCSRouteAbsolute(Application::APP_ID . '.assistantApi.runFileAction', [
'apiVersion' => 'v1',
'fileId' => '123456789',
'taskTypeId' => AudioToText::ID,
]);
$url = str_replace($this->urlGenerator->getBaseUrl(), '', $url);
$url = str_replace('123456789', '{fileId}', $url);
$endpoint = [
'name' => $this->l->t('Transcribe audio'),
'url' => $url,
'method' => 'POST',
'mimetype_filters' => 'audio/',
'icon' => $this->urlGenerator->imagePath(Application::APP_ID, 'client_integration/speech_to_text.svg'),
];
$capabilities['client_integration'][Application::APP_ID]['context-menu'][] = $endpoint;
}

if ($ttsAvailable) {
$url = $this->urlGenerator->linkToOCSRouteAbsolute(Application::APP_ID . '.assistantApi.runFileAction', [
'apiVersion' => 'v1',
'fileId' => '123456789',
'taskTypeId' => \OCP\TaskProcessing\TaskTypes\TextToSpeech::ID,
]);
$url = str_replace($this->urlGenerator->getBaseUrl(), '', $url);
$url = str_replace('123456789', '{fileId}', $url);
$endpoint = [
'name' => $this->l->t('Text to speech'),
'url' => $url,
'method' => 'POST',
'mimetype_filters' => implode(', ', $textMimeTypes),
'icon' => $this->urlGenerator->imagePath(Application::APP_ID, 'client_integration/text_to_speech.svg'),
];
$capabilities['client_integration'][Application::APP_ID]['context-menu'][] = $endpoint;
}
}

return $capabilities;
}
}
18 changes: 15 additions & 3 deletions lib/Controller/AssistantApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
use OCP\IRequest;
use OCP\Lock\LockedException;
use OCP\TaskProcessing\Task;
use OCP\TaskProcessing\TaskTypes\AudioToText;
use OCP\TaskProcessing\TaskTypes\TextToTextSummary;
use Psr\Log\LoggerInterface;
use Throwable;

Expand Down Expand Up @@ -418,16 +420,26 @@ public function getOutputFile(int $ocpTaskId, int $fileId): DataDownloadResponse
*
* @param int $fileId The input file ID
* @param string $taskTypeId The task type of the operation to perform
* @return DataResponse<Http::STATUS_OK, array{taskId: int}, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{error: string}, array{}>
* @return DataResponse<Http::STATUS_OK, string, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{error: string}, array{}>
*
* 200: The task has been scheduled successfully
* 400: There was an issue while scheduling the task
*/
#[NoAdminRequired]
public function runFileAction(int $fileId, string $taskTypeId): DataResponse {
try {
$taskId = $this->taskProcessingService->runFileAction($this->userId, $fileId, $taskTypeId);
return new DataResponse(['taskId' => $taskId]);
$this->taskProcessingService->runFileAction($this->userId, $fileId, $taskTypeId);
$message = $this->l10n->t('Assistant task submitted successfully');
if ($taskTypeId === AudioToText::ID) {
$message = $this->l10n->t('Transcription task submitted successfully');
} elseif ($taskTypeId === TextToTextSummary::ID) {
$message = $this->l10n->t('Summarization task submitted successfully');
} elseif (class_exists('OCP\\TaskProcessing\\TaskTypes\\TextToSpeech')) {
if ($taskTypeId === \OCP\TaskProcessing\TaskTypes\TextToSpeech::ID) {
$message = $this->l10n->t('Text-to-speech task submitted successfully');
}
}
return new DataResponse($message);
} catch (Exception|Throwable $e) {
return new DataResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
}
Expand Down
57 changes: 47 additions & 10 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,52 @@
"type": "boolean"
}
}
},
"client_integration": {
"type": "object",
"additionalProperties": {
"type": "object",
"required": [
"version",
"context-menu"
],
"properties": {
"version": {
"type": "number",
"format": "double"
},
"context-menu": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"url",
"method",
"mimetype_filters",
"icon"
],
"properties": {
"name": {
"type": "string"
},
"url": {
"type": "string"
},
"method": {
"type": "string"
},
"mimetype_filters": {
"type": "string"
},
"icon": {
"type": "string"
}
}
}
}
}
}
}
}
},
Expand Down Expand Up @@ -2513,16 +2559,7 @@
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"type": "object",
"required": [
"taskId"
],
"properties": {
"taskId": {
"type": "integer",
"format": "int64"
}
}
"type": "string"
}
}
}
Expand Down
9 changes: 3 additions & 6 deletions src/files/fileActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ function registerSummarizeAction() {
const { showError, showSuccess } = await import('@nextcloud/dialogs')
const url = generateOcsUrl('/apps/assistant/api/v1/file-action/{fileId}/core:text2text:summary', { fileId: node.fileid })
try {
const response = await axios.post(url)
console.debug('taskId', response.data.ocs.data.taskId)
await axios.post(url)
showSuccess(
t('assistant', 'Summarization task submitted successfully.') + '\n'
+ t('assistant', 'You will be notified when it is ready.') + '\n'
Expand Down Expand Up @@ -100,8 +99,7 @@ function registerTtsAction() {
const { showError, showSuccess } = await import('@nextcloud/dialogs')
const url = generateOcsUrl('/apps/assistant/api/v1/file-action/{fileId}/core:text2speech', { fileId: node.fileid })
try {
const response = await axios.post(url)
console.debug('taskId', response.data.ocs.data.taskId)
await axios.post(url)
showSuccess(
t('assistant', 'Text-to-speech task submitted successfully.') + '\n'
+ t('assistant', 'You will be notified when it is ready.') + '\n'
Expand Down Expand Up @@ -139,8 +137,7 @@ function registerSttAction() {
const { showError, showSuccess } = await import('@nextcloud/dialogs')
const url = generateOcsUrl('/apps/assistant/api/v1/file-action/{fileId}/core:audio2text', { fileId: node.fileid })
try {
const response = await axios.post(url)
console.debug('taskId', response.data.ocs.data.taskId)
await axios.post(url)
showSuccess(
t('assistant', 'Transcription task submitted successfully.') + '\n'
+ t('assistant', 'You will be notified when it is ready.') + '\n'
Expand Down