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

368 lines
15 KiB
PHP

<?php
namespace Isekai\AIToolbox\Api\AIToolboxBot;
use ApiBase;
use Isekai\AIToolbox\Api\ApiAIToolbox;
use Isekai\AIToolbox\Api\ApiAIToolboxBot;
use Isekai\AIToolbox\AIToolboxUtils;
use MediaWiki\MediaWikiServices;
use Wikimedia\ParamValidator\ParamValidator;
use Message;
class ApiReportUsage extends ApiBase {
const TRANSACTION_TIMEOUT = 60 * 10;
/** @var MediaWikiServices */
private $services;
/** @var \WANObjectCache */
private $cache;
/** @var ApiAIToolboxBot */
private $mParent;
public function __construct(ApiAIToolboxBot $main, $method) {
parent::__construct($main->getMain(), $method);
$this->mParent = $main;
$this->services = MediaWikiServices::getInstance();
$this->cache = $this->services->getMainWANObjectCache();
}
public function requireParameter(array $paramKeys) {
$missing = [];
foreach ($paramKeys as $paramKey) {
if ($this->getParameter($paramKey) === null) {
$missing[] = $paramKey;
}
}
if (count($missing) > 0) {
$this->dieWithError( [
'apierror-missingparam',
Message::listParam( array_map(
function ( $p ) {
return '<var>' . $this->encodeParamName( $p ) . '</var>';
},
$missing
) ),
count( $missing ),
], 'missingparam' );
}
}
public function execute() {
// Check permission
$this->checkUserRightsAny('aitoolboxbot');
$step = $this->getParameter('step');
$result = $this->getResult();
if ($step) {
switch ($step) {
case 'start':
$this->requireParameter(['userid', 'useraction']);
$userId = $this->getParameter('userid');
$userAction = $this->getParameter('useraction');
$botId = $this->getParameter('botid');
$tokens = $this->getParameter('tokens');
$pointUsage = $this->getParameter('pointusage');
$user = $this->services->getUserFactory()->newFromId($userId);
if (!$user->isRegistered()) {
$this->addError('apierror-isekai-ai-toolbox-user-not-exists', 'user-not-exists');
return false;
}
$permissionManager = $this->services->getPermissionManager();
$permissionMap = ApiAIToolbox::$permissionMap;
$permissionKey = $permissionMap[$userAction] ?? null;
if ($permissionKey && !$user->isAllowedAny($permissionKey)) {
$this->addError('apierror-isekai-ai-toolbox-nopermission', 'nopermission', [
'permission' => $permissionKey,
'user' => $user->getName(),
'user_permissions' => $permissionManager->getUserPermissions($this->getUser()),
]);
return false;
}
$noCost = false;
if ($user->isAllowedAny('aitoolbox-unlimited')) {
$noCost = true;
}
$transactionid = bin2hex(random_bytes(8));
$pointType = MediaWikiServices::getInstance()->getMainConfig()->get('IsekaiAIToolboxPointType');
$data = [
'userid' => $userId,
'useraction' => $userAction,
'pointtype' => $pointType,
'pointusage' => $pointUsage,
'botid' => $botId,
'tokens' => $tokens,
'step' => $step,
];
if (!$noCost) {
/** @var \Isekai\UserPoints\Service\IsekaiUserPointsFactory */
$pointFactory = $this->services->getService('IsekaiUserPoints');
$pointService = $pointFactory->newFromUserId($userId, $pointType);
if (!$pointService->hasEnoughPoints($pointUsage)) { // User doesn't have enough points
$this->addError('apierror-isekai-ai-toolbox-noenoughpoints', 'noenoughpoints');
return false;
}
$pointTransactionId = $pointService->startConsumePointsTransaction($pointUsage, 600, 'isekai-ai-toolbox', $userAction);
$data['pointtransaction'] = $pointTransactionId;
}
$this->cache->set(
$this->cache->makeKey('aitoolboxbot', 'reportusage', 'transaction', $transactionid),
$data,
self::TRANSACTION_TIMEOUT
);
$result->addValue(['aitoolboxbot', $this->getModuleName()], 'success', 1);
$result->addValue(['aitoolboxbot', $this->getModuleName()], 'transactionid', $transactionid);
break;
case 'end':
$transactionId = $this->getParameter('transactionid');
$transactionData = $this->cache->get(
$this->cache->makeKey('aitoolboxbot', 'reportusage', 'transaction', $transactionId)
);
$isSuccess = true;
if ($transactionData) {
$pointTransactionId = $transactionData['pointtransaction'] ?? null;
$pointType = $transactionData['pointtype'] ?? null;
if ($pointTransactionId && $pointType) {
/** @var \Isekai\UserPoints\Service\IsekaiUserPointsFactory */
$pointFactory = $this->services->getService('IsekaiUserPoints');
$pointService = $pointFactory->newFromUserId($transactionData['userid'], $pointType);
$ret = $pointService->commitConsumePointsTransaction($pointTransactionId);
if (!$ret) {
$this->addWarning('apiwarn-isekai-ai-toolbox-pointtransactionfailed', 'pointtransactionfailed');
$isSuccess = false;
}
}
$realTokens = $this->getParameter('tokens') ?? $transactionData['tokens'];
$dbw = $this->services->getDBLoadBalancer()->getConnection(DB_PRIMARY);
$dbw->insert(
'aitoolbox_usage',
[
'user_id' => $transactionData['userid'],
'action' => $transactionData['useraction'],
'bot_id' => $transactionData['botid'],
'tokens' => $transactionData['tokens'],
'real_tokens' => $realTokens,
'timestamp' => $dbw->timestamp(),
'success' => 1,
],
__METHOD__
);
$this->cache->delete(
$this->cache->makeKey('aitoolboxbot', 'reportusage', 'transaction', $transactionId)
);
} else {
$this->addWarning('apierror-isekai-ai-toolbox-invalidtransaction', 'invalidtransaction');
$isSuccess = false;
}
$result->addValue(['aitoolboxbot', $this->getModuleName()], 'success', $isSuccess ? 1 : 0);
break;
case 'cancel':
$transactionId = $this->getParameter('transactionid');
$transactionData = $this->cache->get(
$this->cache->makeKey('aitoolboxbot', 'reportusage', 'transaction', $transactionId)
);
$isSuccess = true;
if ($transactionData) {
$pointTransactionId = $transactionData['pointtransaction'] ?? null;
$pointType = $transactionData['pointtype'] ?? null;
if ($pointTransactionId && $pointType) {
/** @var \Isekai\UserPoints\Service\IsekaiUserPointsFactory */
$pointFactory = $this->services->getService('IsekaiUserPoints');
$pointService = $pointFactory->newFromUserId($transactionData['userid'], $pointType);
$ret = $pointService->rollbackConsumePointsTransaction($pointTransactionId);
if (!$ret) {
$this->addWarning('apiwarn-isekai-ai-toolbox-pointtransactionfailed', 'pointtransactionfailed');
$isSuccess = false;
}
}
$reportedError = $this->getParameter('error');
$dbw = $this->services->getDBLoadBalancer()->getConnection(DB_PRIMARY);
$dbw->insert(
'aitoolbox_usage',
[
'user_id' => $transactionData['userid'],
'action' => $transactionData['useraction'],
'bot_id' => $transactionData['botid'],
'tokens' => $transactionData['tokens'],
'timestamp' => $dbw->timestamp(),
'success' => 0,
'error' => $reportedError,
],
__METHOD__
);
$this->cache->delete(
$this->cache->makeKey('aitoolboxbot', 'reportusage', 'transaction', $transactionId)
);
} else {
$this->addWarning('apierror-isekai-ai-toolbox-invalidtransaction', 'invalidtransaction');
$isSuccess = false;
}
$result->addValue(['aitoolboxbot', $this->getModuleName()], 'success', $isSuccess ? 1 : 0);
break;
}
} else {
$this->requireParameter(['userid', 'useraction']);
$userId = $this->getParameter('userid');
$userAction = strtolower($this->getParameter('useraction'));
$botId = $this->getParameter('botid');
$realTokens = $tokens = $this->getParameter('tokens');
$pointUsage = $this->getParameter('pointusage');
$user = $this->services->getUserFactory()->newFromId($userId);
if (!$user->isRegistered()) {
$this->addError('apierror-isekai-ai-toolbox-nopermission', 'nopermission');
return false;
}
$permissionMap = ApiAIToolbox::$permissionMap;
$permissionKey = $permissionMap[$userAction] ?? null;
if ($permissionKey && !$user->isAllowed($permissionKey)) {
$this->addError('apierror-isekai-ai-toolbox-nopermission', 'nopermission');
return false;
}
$noCost = false;
if ($userAction === 'chatcomplete' && $user->isAllowed('aitoolbox-unlimited')) {
$noCost = true;
}
$isSuccess = true;
$pointType = MediaWikiServices::getInstance()->getMainConfig()->get('IsekaiAIToolboxPointType');
if (!$noCost) {
/** @var \Isekai\UserPoints\Service\IsekaiUserPointsFactory */
$pointFactory = $this->services->getService('IsekaiUserPoints');
$pointService = $pointFactory->newFromUserId($userId, $pointType);
if (!$pointService->hasEnoughPoints($pointUsage)) { // User doesn't have enough points
$this->addError('apierror-isekai-ai-toolbox-notenoughpoints', 'notenoughpoints');
return false;
}
$ret = $pointService->consumePoints($pointUsage, true, false, 'isekai-ai-toolbox', $userAction);
if (!$ret) {
$this->addWarning('apiwarn-isekai-ai-toolbox-pointtransactionfailed', 'pointtransactionfailed');
$isSuccess = false;
}
}
$reportedError = $this->getParameter('error');
$dbw = $this->services->getDBLoadBalancer()->getConnection(DB_PRIMARY);
$dbw->insert(
'aitoolbox_usage',
[
'user_id' => $userId,
'action' => $userAction,
'bot_id' => $botId,
'tokens' => $tokens,
'real_tokens' => $realTokens,
'timestamp' => $dbw->timestamp(),
'success' => $reportedError ? 0 : 1,
'error' => $reportedError,
],
__METHOD__
);
$result->addValue(['aitoolboxbot', $this->getModuleName()], 'success', $isSuccess ? 1 : 0);
}
return true;
}
public function getAllowedParams($flags = 0) {
return [
'userid' => [
ParamValidator::PARAM_TYPE => 'integer',
ParamValidator::PARAM_DEFAULT => null,
ParamValidator::PARAM_REQUIRED => false,
],
'useraction' => [
ParamValidator::PARAM_TYPE => [
'embeddingpage',
'chatcomplete',
],
ParamValidator::PARAM_DEFAULT => null,
ParamValidator::PARAM_REQUIRED => false,
],
'botid' => [
ParamValidator::PARAM_TYPE => 'string',
ParamValidator::PARAM_DEFAULT => null,
ParamValidator::PARAM_REQUIRED => false,
],
'tokens' => [
ParamValidator::PARAM_TYPE => 'integer',
ParamValidator::PARAM_DEFAULT => 100,
ParamValidator::PARAM_REQUIRED => false,
],
'pointusage' => [
ParamValidator::PARAM_TYPE => 'integer',
ParamValidator::PARAM_DEFAULT => 0,
ParamValidator::PARAM_REQUIRED => false,
],
'error' => [
ParamValidator::PARAM_TYPE => 'string',
ParamValidator::PARAM_DEFAULT => null,
ParamValidator::PARAM_REQUIRED => false,
],
'step' => [
ParamValidator::PARAM_TYPE => [
'start',
'end',
'cancel',
],
ParamValidator::PARAM_DEFAULT => null,
ParamValidator::PARAM_REQUIRED => false,
],
'transactionid' => [
ParamValidator::PARAM_TYPE => 'string',
ParamValidator::PARAM_DEFAULT => null,
ParamValidator::PARAM_REQUIRED => false,
],
];
}
public function getCacheMode($params) {
return 'private';
}
public function getParent() {
return $this->mParent;
}
}