%PDF- %PDF-
| Direktori : /www/varak.net/nextcloud.varak.net/apps/app_api/lib/Service/ProvidersAI/ |
| Current File : /www/varak.net/nextcloud.varak.net/apps/app_api/lib/Service/ProvidersAI/TextProcessingService.php |
<?php
declare(strict_types=1);
namespace OCA\AppAPI\Service\ProvidersAI;
use OCA\AppAPI\AppInfo\Application;
use OCA\AppAPI\Db\TextProcessing\TextProcessingProvider;
use OCA\AppAPI\Db\TextProcessing\TextProcessingProviderMapper;
use OCA\AppAPI\Db\TextProcessing\TextProcessingProviderQueue;
use OCA\AppAPI\Db\TextProcessing\TextProcessingProviderQueueMapper;
use OCA\AppAPI\PublicFunctions;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\Exception;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IServerContainer;
use OCP\TextProcessing\IProviderWithId;
use OCP\TextProcessing\IProviderWithUserId;
use Psr\Log\LoggerInterface;
class TextProcessingService {
// We do support only available on server-side Text Processing Task Types
public const TASK_TYPES = [
'free_prompt' => 'OCP\TextProcessing\FreePromptTaskType',
'headline' => 'OCP\TextProcessing\HeadlineTaskType',
'summary' => 'OCP\TextProcessing\SummaryTaskType',
'topics' => 'OCP\TextProcessing\TopicsTaskType',
];
private ?ICache $cache = null;
public function __construct(
ICacheFactory $cacheFactory,
private readonly TextProcessingProviderMapper $mapper,
private readonly LoggerInterface $logger,
) {
if ($cacheFactory->isAvailable()) {
$this->cache = $cacheFactory->createDistributed(Application::APP_ID . '/ex_text_processing_providers');
}
}
/**
* Get list of registered TextProcessing providers (only for enabled ExApps)
*
* @return TextProcessingProvider[]
*/
public function getRegisteredTextProcessingProviders(): array {
try {
$cacheKey = '/ex_text_processing_providers';
$records = $this->cache?->get($cacheKey);
if ($records === null) {
$records = $this->mapper->findAllEnabled();
$this->cache?->set($cacheKey, $records);
}
return array_map(function ($record) {
return new TextProcessingProvider($record);
}, $records);
} catch (Exception) {
return [];
}
}
public function getExAppTextProcessingProvider(string $appId, string $name): ?TextProcessingProvider {
foreach ($this->getRegisteredTextProcessingProviders() as $provider) {
if (($provider->getAppid() === $appId) && ($provider->getName() === $name)) {
return $provider;
}
}
try {
return $this->mapper->findByAppIdName($appId, $name);
} catch (DoesNotExistException|MultipleObjectsReturnedException|Exception) {
return null;
}
}
public function registerTextProcessingProvider(
string $appId,
string $name,
string $displayName,
string $actionHandler,
string $taskType
): ?TextProcessingProvider {
try {
$textProcessingProvider = $this->mapper->findByAppidName($appId, $name);
} catch (DoesNotExistException|MultipleObjectsReturnedException|Exception) {
$textProcessingProvider = null;
}
try {
if (!$this->isTaskTypeValid($taskType)) {
return null;
}
$newTextProcessingProvider = new TextProcessingProvider([
'appid' => $appId,
'name' => $name,
'display_name' => $displayName,
'action_handler' => ltrim($actionHandler, '/'),
'task_type' => $taskType,
]);
if ($textProcessingProvider !== null) {
$newTextProcessingProvider->setId($textProcessingProvider->getId());
}
$textProcessingProvider = $this->mapper->insertOrUpdate($newTextProcessingProvider);
$this->resetCacheEnabled();
} catch (Exception $e) {
$this->logger->error(
sprintf('Failed to register ExApp %s TextProcessingProvider %s. Error: %s', $appId, $name, $e->getMessage()), ['exception' => $e]
);
return null;
}
return $textProcessingProvider;
}
public function unregisterTextProcessingProvider(string $appId, string $name): ?TextProcessingProvider {
try {
$textProcessingProvider = $this->getExAppTextProcessingProvider($appId, $name);
if ($textProcessingProvider !== null) {
$this->mapper->delete($textProcessingProvider);
$this->resetCacheEnabled();
return $textProcessingProvider;
}
} catch (Exception $e) {
$this->logger->error(sprintf('Failed to unregister ExApp %s TextProcessingProvider %s. Error: %s', $appId, $name, $e->getMessage()), ['exception' => $e]);
}
return null;
}
/**
* Register dynamically ExApps TextProcessing providers with ID using anonymous classes.
*
* @param IRegistrationContext $context
* @param IServerContainer $serverContainer
*
* @return void
*/
public function registerExAppTextProcessingProviders(IRegistrationContext &$context, IServerContainer $serverContainer): void {
$exAppsProviders = $this->getRegisteredTextProcessingProviders();
/** @var TextProcessingProvider $exAppProvider */
foreach ($exAppsProviders as $exAppProvider) {
if (!$this->isTaskTypeValid($exAppProvider->getTaskType())) {
continue;
}
$className = '\\OCA\\AppAPI\\' . $exAppProvider->getAppid() . '\\' . $exAppProvider->getName();
$provider = $this->getAnonymousExAppProvider($exAppProvider, $className, $serverContainer);
$context->registerService($className, function () use ($provider) {
return $provider;
});
$context->registerTextProcessingProvider($className);
}
}
/**
* @psalm-suppress UndefinedClass, MissingDependency, InvalidReturnStatement, InvalidReturnType, MissingTemplateParam
*/
private function getAnonymousExAppProvider(
TextProcessingProvider $provider,
string $className,
IServerContainer $serverContainer
): IProviderWithId {
return new class($provider, $serverContainer, $className) implements IProviderWithId, IProviderWithUserId {
private ?string $userId;
public function __construct(
private readonly TextProcessingProvider $provider,
private readonly IServerContainer $serverContainer,
private readonly string $className,
) {
}
public function getId(): string {
return $this->className;
}
public function getName(): string {
return $this->provider->getDisplayName();
}
public function process(string $prompt, float $maxExecutionTime = 0): string {
/** @var PublicFunctions $service */
$service = $this->serverContainer->get(PublicFunctions::class);
$mapper = $this->serverContainer->get(TextProcessingProviderQueueMapper::class);
$route = $this->provider->getActionHandler();
$queueRecord = $mapper->insert(new TextProcessingProviderQueue(['created_time' => time()]));
$taskId = $queueRecord->getId();
$response = $service->exAppRequest($this->provider->getAppid(),
$route,
$this->userId,
params: [
'prompt' => $prompt,
'task_id' => $taskId,
'max_execution_time' => $maxExecutionTime,
],
options: [
'timeout' => $maxExecutionTime,
],
);
if (is_array($response)) {
$mapper->delete($mapper->getById($taskId));
throw new \Exception(sprintf('Failed process text task: %s:%s:%s. Error: %s',
$this->provider->getAppid(),
$this->provider->getName(),
$this->provider->getTaskType(),
$response['error']
));
}
do {
$taskResults = $mapper->getById($taskId);
usleep(300000); // 0.3s
} while ($taskResults->getFinished() === 0);
$mapper->delete($taskResults);
if (!empty($taskResults->getError())) {
throw new \Exception(sprintf('Text task returned error: %s:%s:%s. Error: %s',
$this->provider->getAppid(),
$this->provider->getName(),
$this->provider->getTaskType(),
$taskResults->getError(),
));
}
return $taskResults->getResult();
}
public function getTaskType(): string {
return TextProcessingService::TASK_TYPES[$this->provider->getTaskType()];
}
public function setUserId(?string $userId): void {
$this->userId = $userId;
}
};
}
private function isTaskTypeValid(string $getActionType): bool {
return in_array($getActionType, array_keys(self::TASK_TYPES));
}
public function resetCacheEnabled(): void {
$this->cache?->remove('/ex_text_processing_providers');
}
public function unregisterExAppTextProcessingProviders(string $appId): int {
try {
$result = $this->mapper->removeAllByAppId($appId);
} catch (Exception) {
$result = -1;
}
$this->resetCacheEnabled();
return $result;
}
}